mirror of
https://github.com/microsoft/autogen.git
synced 2026-04-20 03:02:16 -04:00
Improves editing UI for tools in AGS (#5539)
<!-- Thank you for your contribution! Please review https://microsoft.github.io/autogen/docs/Contribute before opening a pull request. --> <!-- Please add a reviewer to the assignee section when you create a PR. If you don't have the access to it, we will shortly find a reviewer and assign them to your PR. --> <img width="1560" alt="image" src="https://github.com/user-attachments/assets/da3d781f-2572-4bd7-802d-1d3900f6c6d9" /> ## Why are these changes needed? Improves editing UI for tools in AGS - add remove tools - add/remove imports - show source code section of FunctionTool in in code editor <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number <!-- For example: "Closes #1234" --> ## Checks - [ ] I've included any doc changes needed for https://microsoft.github.io/autogen/. See https://microsoft.github.io/autogen/docs/Contribute#documentation to build and test documentation locally. - [ ] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [ ] I've made sure all auto checks have passed.
This commit is contained in:
@@ -77,12 +77,20 @@ export type AgentMessageConfig =
|
||||
| ToolCallMessageConfig
|
||||
| ToolCallResultMessageConfig;
|
||||
|
||||
// Tool Configs
|
||||
export interface FromModuleImport {
|
||||
module: string;
|
||||
imports: string[];
|
||||
}
|
||||
|
||||
// Import can be either a string (direct import) or a FromModuleImport
|
||||
export type Import = string | FromModuleImport;
|
||||
|
||||
// The complete FunctionToolConfig interface
|
||||
export interface FunctionToolConfig {
|
||||
source_code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
global_imports: any[]; // Sequence[Import] equivalent
|
||||
global_imports: Import[];
|
||||
has_cancellation_support: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,7 @@ const PROVIDERS = {
|
||||
|
||||
// Models
|
||||
OPENAI: "autogen_ext.models.openai.OpenAIChatCompletionClient",
|
||||
AZURE_OPENAI:
|
||||
"autogen_ext.models.azure_openai.AzureOpenAIChatCompletionClient",
|
||||
AZURE_OPENAI: "autogen_ext.models.openai.AzureOpenAIChatCompletionClient",
|
||||
|
||||
// Tools
|
||||
FUNCTION_TOOL: "autogen_core.tools.FunctionTool",
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
//team/builder/builder.tsx
|
||||
import React, {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
DndContext,
|
||||
useSensor,
|
||||
@@ -26,7 +20,7 @@ import {
|
||||
MiniMap,
|
||||
} from "@xyflow/react";
|
||||
import "@xyflow/react/dist/style.css";
|
||||
import { Button, Layout, message, Modal, Switch, Tooltip } from "antd";
|
||||
import { Button, Layout, message, Switch, Tooltip } from "antd";
|
||||
import {
|
||||
Cable,
|
||||
CheckCircle,
|
||||
@@ -39,7 +33,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import { useTeamBuilderStore } from "./store";
|
||||
import { ComponentLibrary } from "./library";
|
||||
import { ComponentTypes, Team, Session } from "../../../types/datamodel";
|
||||
import { ComponentTypes, Team } from "../../../types/datamodel";
|
||||
import { CustomNode, CustomEdge, DragItem } from "./types";
|
||||
import { edgeTypes, nodeTypes } from "./nodes";
|
||||
|
||||
@@ -49,11 +43,8 @@ import TeamBuilderToolbar from "./toolbar";
|
||||
import { MonacoEditor } from "../../monaco";
|
||||
import { NodeEditor } from "./node-editor/node-editor";
|
||||
import debounce from "lodash.debounce";
|
||||
import { appContext } from "../../../../hooks/provider";
|
||||
import { sessionAPI } from "../../playground/api";
|
||||
import TestDrawer from "./testdrawer";
|
||||
import { teamAPI, validationAPI, ValidationResponse } from "../api";
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
||||
import { validationAPI, ValidationResponse } from "../api";
|
||||
import { ValidationErrors } from "./validationerrors";
|
||||
|
||||
const { Sider, Content } = Layout;
|
||||
|
||||
@@ -1,524 +0,0 @@
|
||||
import React from "react";
|
||||
import { Input, Select, Switch, InputNumber, Form, Button } from "antd";
|
||||
import { Edit } from "lucide-react";
|
||||
import {
|
||||
isTeamComponent,
|
||||
isAgentComponent,
|
||||
isModelComponent,
|
||||
isToolComponent,
|
||||
isTerminationComponent,
|
||||
isSelectorTeam,
|
||||
isRoundRobinTeam,
|
||||
isAssistantAgent,
|
||||
isUserProxyAgent,
|
||||
isWebSurferAgent,
|
||||
isOpenAIModel,
|
||||
isAzureOpenAIModel,
|
||||
isFunctionTool,
|
||||
isOrTermination,
|
||||
isMaxMessageTermination,
|
||||
isTextMentionTermination,
|
||||
} from "../../../../types/guards";
|
||||
import { Component, ComponentConfig } from "../../../../types/datamodel";
|
||||
import DetailGroup from "./detailgroup";
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Option } = Select;
|
||||
|
||||
interface NodeEditorFieldsProps {
|
||||
component: Component<ComponentConfig>;
|
||||
onNavigate: (componentType: string, id: string, parentField: string) => void;
|
||||
}
|
||||
|
||||
export const NodeEditorFields: React.FC<NodeEditorFieldsProps> = ({
|
||||
component,
|
||||
onNavigate,
|
||||
}) => {
|
||||
const renderNestedComponentButton = (
|
||||
label: string,
|
||||
component: Component<ComponentConfig> | Component<ComponentConfig>[],
|
||||
parentField: string
|
||||
) => {
|
||||
if (Array.isArray(component)) {
|
||||
return (
|
||||
<div className="space-y-2 mb-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium">{label}</span>
|
||||
</div>
|
||||
{component.map((item) => (
|
||||
<Button
|
||||
key={item.label}
|
||||
onClick={() =>
|
||||
onNavigate(item.component_type, item.label || "", parentField)
|
||||
}
|
||||
className="w-full flex justify-between items-center"
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
<Edit className="w-4 h-4" />
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return component ? (
|
||||
<div className="mb-4">
|
||||
<Button
|
||||
onClick={() =>
|
||||
onNavigate(
|
||||
component.component_type,
|
||||
component.label || "",
|
||||
parentField
|
||||
)
|
||||
}
|
||||
className="w-full flex justify-between items-center"
|
||||
>
|
||||
<span>{label}</span>
|
||||
<Edit className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
const renderTeamFields = () => {
|
||||
if (!component) return null;
|
||||
|
||||
if (isSelectorTeam(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Selector Prompt"
|
||||
name={["config", "selector_prompt"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Max Turns" name={["config", "max_turns"]}>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Allow Repeated Speaker"
|
||||
name={["config", "allow_repeated_speaker"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
{component.config.model_client &&
|
||||
renderNestedComponentButton(
|
||||
"Model Client",
|
||||
component.config.model_client,
|
||||
"model_client"
|
||||
)}
|
||||
{component.config.termination_condition &&
|
||||
renderNestedComponentButton(
|
||||
"Termination Condition",
|
||||
component.config.termination_condition,
|
||||
"termination_condition"
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (isRoundRobinTeam(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item label="Max Turns" name={["config", "max_turns"]}>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
{component.config.termination_condition &&
|
||||
renderNestedComponentButton(
|
||||
"Termination Condition",
|
||||
component.config.termination_condition,
|
||||
"termination_condition"
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderAgentFields = () => {
|
||||
if (!component) return null;
|
||||
|
||||
if (isAssistantAgent(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Name"
|
||||
name={["config", "name"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Description"
|
||||
name={["config", "description"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label="System Message" name={["config", "system_message"]}>
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Reflect on Tool Use"
|
||||
name={["config", "reflect_on_tool_use"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Tool Call Summary Format"
|
||||
name={["config", "tool_call_summary_format"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
{component.config.model_client &&
|
||||
renderNestedComponentButton(
|
||||
"Model Client",
|
||||
component.config.model_client,
|
||||
"model_client"
|
||||
)}
|
||||
{component.config.tools &&
|
||||
component.config.tools.length > 0 &&
|
||||
renderNestedComponentButton(
|
||||
"Tools",
|
||||
component.config.tools,
|
||||
"tools"
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (isUserProxyAgent(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Name"
|
||||
name={["config", "name"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Description"
|
||||
name={["config", "description"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (isWebSurferAgent(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Name"
|
||||
name={["config", "name"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Downloads Folder"
|
||||
name={["config", "downloads_folder"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Description" name={["config", "description"]}>
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Start Page" name={["config", "start_page"]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Headless"
|
||||
name={["config", "headless"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Animate Actions"
|
||||
name={["config", "animate_actions"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Save Screenshots"
|
||||
name={["config", "to_save_screenshots"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Use OCR"
|
||||
name={["config", "use_ocr"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Browser Channel"
|
||||
name={["config", "browser_channel"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Browser Data Directory"
|
||||
name={["config", "browser_data_dir"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Resize Viewport"
|
||||
name={["config", "to_resize_viewport"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
{component.config.model_client &&
|
||||
renderNestedComponentButton(
|
||||
"Model Client",
|
||||
component.config.model_client,
|
||||
"model_client"
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderModelFields = () => {
|
||||
if (!component) return null;
|
||||
|
||||
const createArgumentsFields = (
|
||||
<>
|
||||
<Form.Item label="Temperature" name={["config", "temperature"]}>
|
||||
<InputNumber min={0} max={2} step={0.1} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Max Tokens" name={["config", "max_tokens"]}>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Top P" name={["config", "top_p"]}>
|
||||
<InputNumber min={0} max={1} step={0.1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Frequency Penalty"
|
||||
name={["config", "frequency_penalty"]}
|
||||
>
|
||||
<InputNumber min={-2} max={2} step={0.1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Presence Penalty"
|
||||
name={["config", "presence_penalty"]}
|
||||
>
|
||||
<InputNumber min={-2} max={2} step={0.1} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Stop Sequences" name={["config", "stop"]}>
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
if (isOpenAIModel(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Model"
|
||||
name={["config", "model"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="API Key" name={["config", "api_key"]}>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
<Form.Item label="Organization" name={["config", "organization"]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Base URL" name={["config", "base_url"]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Timeout" name={["config", "timeout"]}>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Max Retries" name={["config", "max_retries"]}>
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
{createArgumentsFields}
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (isAzureOpenAIModel(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Model"
|
||||
name={["config", "model"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Azure Endpoint"
|
||||
name={["config", "azure_endpoint"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Azure Deployment"
|
||||
name={["config", "azure_deployment"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="API Version"
|
||||
name={["config", "api_version"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Azure AD Token" name={["config", "azure_ad_token"]}>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
{createArgumentsFields}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderToolFields = () => {
|
||||
if (!component) return null;
|
||||
|
||||
if (isFunctionTool(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Name"
|
||||
name={["config", "name"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Description"
|
||||
name={["config", "description"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Source Code"
|
||||
name={["config", "source_code"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<TextArea rows={8} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Has Cancellation Support"
|
||||
name={["config", "has_cancellation_support"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderTerminationFields = () => {
|
||||
if (!component) return null;
|
||||
|
||||
if (isOrTermination(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Number of Conditions"
|
||||
name={["config", "conditions"]}
|
||||
>
|
||||
<InputNumber disabled />
|
||||
</Form.Item>
|
||||
{component.config.conditions &&
|
||||
component.config.conditions.length > 0 &&
|
||||
renderNestedComponentButton(
|
||||
"Conditions",
|
||||
component.config.conditions,
|
||||
"conditions"
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (isMaxMessageTermination(component)) {
|
||||
return (
|
||||
<Form.Item
|
||||
label="Max Messages"
|
||||
name={["config", "max_messages"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
if (isTextMentionTermination(component)) {
|
||||
return (
|
||||
<Form.Item
|
||||
label="Text"
|
||||
name={["config", "text"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Common fields for all components
|
||||
const commonFields = (
|
||||
<DetailGroup title="Component Details">
|
||||
<Form.Item label="Label" name="label">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Description" name="description">
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
</DetailGroup>
|
||||
);
|
||||
// Component-specific fields
|
||||
let specificFields = null;
|
||||
if (isTeamComponent(component)) {
|
||||
specificFields = (
|
||||
<DetailGroup title="Configuration">{renderTeamFields()}</DetailGroup>
|
||||
);
|
||||
} else if (isAgentComponent(component)) {
|
||||
specificFields = (
|
||||
<DetailGroup title="Configuration">{renderAgentFields()}</DetailGroup>
|
||||
);
|
||||
} else if (isModelComponent(component)) {
|
||||
specificFields = (
|
||||
<DetailGroup title="Configuration">{renderModelFields()}</DetailGroup>
|
||||
);
|
||||
} else if (isToolComponent(component)) {
|
||||
specificFields = (
|
||||
<DetailGroup title="Configuration">{renderToolFields()}</DetailGroup>
|
||||
);
|
||||
} else if (isTerminationComponent(component)) {
|
||||
specificFields = (
|
||||
<DetailGroup title="Configuration">
|
||||
{renderTerminationFields()}
|
||||
</DetailGroup>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{commonFields}
|
||||
{specificFields}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodeEditorFields;
|
||||
@@ -0,0 +1,185 @@
|
||||
// fields/agent-fields.tsx
|
||||
import React from "react";
|
||||
import { Form, Input, Switch } from "antd";
|
||||
import {
|
||||
isAssistantAgent,
|
||||
isUserProxyAgent,
|
||||
isWebSurferAgent,
|
||||
} from "../../../../../types/guards";
|
||||
import { NestedComponentButton } from "./fields";
|
||||
import { NodeEditorFieldsProps } from "./fields";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
export const AgentFields: React.FC<NodeEditorFieldsProps> = ({
|
||||
component,
|
||||
onNavigate,
|
||||
workingCopy,
|
||||
setWorkingCopy,
|
||||
editPath,
|
||||
updateComponentAtPath,
|
||||
getCurrentComponent,
|
||||
}) => {
|
||||
if (!component) return null;
|
||||
|
||||
if (isAssistantAgent(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Name"
|
||||
name={["config", "name"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Description"
|
||||
name={["config", "description"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label="System Message" name={["config", "system_message"]}>
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Reflect on Tool Use"
|
||||
name={["config", "reflect_on_tool_use"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Tool Call Summary Format"
|
||||
name={["config", "tool_call_summary_format"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
{component.config.model_client && (
|
||||
<NestedComponentButton
|
||||
label="Model Client"
|
||||
component={component.config.model_client}
|
||||
parentField="model_client"
|
||||
onNavigate={onNavigate}
|
||||
/>
|
||||
)}
|
||||
{component.config.tools && component.config.tools.length > 0 && (
|
||||
<NestedComponentButton
|
||||
label="Tools"
|
||||
component={component.config.tools}
|
||||
parentField="tools"
|
||||
onNavigate={onNavigate}
|
||||
workingCopy={workingCopy}
|
||||
setWorkingCopy={setWorkingCopy}
|
||||
editPath={editPath}
|
||||
updateComponentAtPath={updateComponentAtPath}
|
||||
getCurrentComponent={getCurrentComponent}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (isUserProxyAgent(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Name"
|
||||
name={["config", "name"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Description"
|
||||
name={["config", "description"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (isWebSurferAgent(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Name"
|
||||
name={["config", "name"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Downloads Folder"
|
||||
name={["config", "downloads_folder"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Description" name={["config", "description"]}>
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Start Page" name={["config", "start_page"]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Headless"
|
||||
name={["config", "headless"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Animate Actions"
|
||||
name={["config", "animate_actions"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Save Screenshots"
|
||||
name={["config", "to_save_screenshots"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Use OCR"
|
||||
name={["config", "use_ocr"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item label="Browser Channel" name={["config", "browser_channel"]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Browser Data Directory"
|
||||
name={["config", "browser_data_dir"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Resize Viewport"
|
||||
name={["config", "to_resize_viewport"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
{component.config.model_client && (
|
||||
<NestedComponentButton
|
||||
label="Model Client"
|
||||
component={component.config.model_client}
|
||||
parentField="model_client"
|
||||
onNavigate={onNavigate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default AgentFields;
|
||||
@@ -0,0 +1,282 @@
|
||||
import React from "react";
|
||||
import { Button, Form, Input } from "antd";
|
||||
|
||||
import {
|
||||
Component,
|
||||
ComponentConfig,
|
||||
FunctionToolConfig,
|
||||
} from "../../../../../types/datamodel";
|
||||
import {
|
||||
isTeamComponent,
|
||||
isAgentComponent,
|
||||
isModelComponent,
|
||||
isToolComponent,
|
||||
isTerminationComponent,
|
||||
} from "../../../../../types/guards";
|
||||
import DetailGroup from "../detailgroup";
|
||||
import { TeamFields } from "./team-fields";
|
||||
import { AgentFields } from "./agent-fields";
|
||||
import { ModelFields } from "./model-fields";
|
||||
import { ToolFields } from "./tool-fields";
|
||||
import { TerminationFields } from "./termination-fields";
|
||||
import { Edit, MinusCircle, PlusCircle } from "lucide-react";
|
||||
import { EditPath } from "../node-editor";
|
||||
|
||||
const { TextArea } = Input;
|
||||
|
||||
export interface NodeEditorFieldsProps {
|
||||
component: Component<ComponentConfig>;
|
||||
onNavigate: (componentType: string, id: string, parentField: string) => void;
|
||||
workingCopy: Component<ComponentConfig> | null;
|
||||
setWorkingCopy: (component: Component<ComponentConfig> | null) => void;
|
||||
editPath: EditPath[];
|
||||
updateComponentAtPath: (
|
||||
root: Component<ComponentConfig>,
|
||||
path: EditPath[],
|
||||
updates: Partial<Component<ComponentConfig>>
|
||||
) => Component<ComponentConfig>;
|
||||
getCurrentComponent: (
|
||||
root: Component<ComponentConfig>
|
||||
) => Component<ComponentConfig> | null;
|
||||
}
|
||||
|
||||
const NodeEditorFields: React.FC<NodeEditorFieldsProps> = ({
|
||||
component,
|
||||
onNavigate,
|
||||
workingCopy,
|
||||
setWorkingCopy,
|
||||
editPath,
|
||||
updateComponentAtPath,
|
||||
getCurrentComponent,
|
||||
}) => {
|
||||
let specificFields = null;
|
||||
|
||||
if (isTeamComponent(component)) {
|
||||
specificFields = (
|
||||
<DetailGroup title="Configuration">
|
||||
<TeamFields
|
||||
component={component}
|
||||
onNavigate={onNavigate}
|
||||
workingCopy={workingCopy}
|
||||
setWorkingCopy={setWorkingCopy}
|
||||
editPath={editPath}
|
||||
updateComponentAtPath={updateComponentAtPath}
|
||||
getCurrentComponent={getCurrentComponent}
|
||||
/>
|
||||
</DetailGroup>
|
||||
);
|
||||
} else if (isAgentComponent(component)) {
|
||||
specificFields = (
|
||||
<DetailGroup title="Configuration">
|
||||
<AgentFields
|
||||
component={component}
|
||||
onNavigate={onNavigate}
|
||||
workingCopy={workingCopy}
|
||||
setWorkingCopy={setWorkingCopy}
|
||||
editPath={editPath}
|
||||
updateComponentAtPath={updateComponentAtPath}
|
||||
getCurrentComponent={getCurrentComponent}
|
||||
/>
|
||||
</DetailGroup>
|
||||
);
|
||||
} else if (isModelComponent(component)) {
|
||||
specificFields = (
|
||||
<DetailGroup title="Configuration">
|
||||
<ModelFields component={component} />
|
||||
</DetailGroup>
|
||||
);
|
||||
} else if (isToolComponent(component)) {
|
||||
specificFields = (
|
||||
<DetailGroup title="Configuration">
|
||||
<ToolFields
|
||||
component={component}
|
||||
workingCopy={workingCopy}
|
||||
setWorkingCopy={setWorkingCopy}
|
||||
editPath={editPath}
|
||||
updateComponentAtPath={updateComponentAtPath}
|
||||
getCurrentComponent={getCurrentComponent}
|
||||
/>
|
||||
</DetailGroup>
|
||||
);
|
||||
} else if (isTerminationComponent(component)) {
|
||||
specificFields = (
|
||||
<DetailGroup title="Configuration">
|
||||
<TerminationFields component={component} onNavigate={onNavigate} />
|
||||
</DetailGroup>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DetailGroup title="Component Details">
|
||||
<CommonFields />
|
||||
</DetailGroup>
|
||||
{specificFields}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodeEditorFields;
|
||||
|
||||
// // fields/common-fields.tsx
|
||||
|
||||
export const CommonFields: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<Form.Item label="Label" name="label">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Description" name="description">
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface NestedComponentButtonProps {
|
||||
label: string;
|
||||
component: Component<ComponentConfig> | Component<ComponentConfig>[];
|
||||
parentField: string;
|
||||
onNavigate: (componentType: string, id: string, parentField: string) => void;
|
||||
workingCopy?: Component<ComponentConfig> | null;
|
||||
setWorkingCopy?: (component: Component<ComponentConfig> | null) => void;
|
||||
editPath?: any[];
|
||||
updateComponentAtPath?: any;
|
||||
getCurrentComponent?: any;
|
||||
}
|
||||
|
||||
export const NestedComponentButton: React.FC<NestedComponentButtonProps> = ({
|
||||
label,
|
||||
component,
|
||||
parentField,
|
||||
onNavigate,
|
||||
workingCopy,
|
||||
setWorkingCopy,
|
||||
editPath,
|
||||
updateComponentAtPath,
|
||||
getCurrentComponent,
|
||||
}) => {
|
||||
if (Array.isArray(component)) {
|
||||
return (
|
||||
<div className="space-y-2 mb-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium">{label}</span>
|
||||
{parentField === "tools" && (
|
||||
<Button
|
||||
type="dashed"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const blankTool: Component<FunctionToolConfig> = {
|
||||
provider: "autogen_core.tools.FunctionTool",
|
||||
component_type: "tool",
|
||||
version: 1,
|
||||
component_version: 1,
|
||||
description:
|
||||
"Create custom tools by wrapping standard Python functions.",
|
||||
label: "New Tool",
|
||||
config: {
|
||||
source_code: "def new_function():\n pass",
|
||||
name: "new_function",
|
||||
description: "Description of the new function",
|
||||
global_imports: [],
|
||||
has_cancellation_support: false,
|
||||
},
|
||||
};
|
||||
|
||||
if (
|
||||
workingCopy &&
|
||||
setWorkingCopy &&
|
||||
updateComponentAtPath &&
|
||||
getCurrentComponent &&
|
||||
editPath
|
||||
) {
|
||||
const currentTools =
|
||||
component as Component<ComponentConfig>[];
|
||||
const updatedTools = [...currentTools, blankTool];
|
||||
const updatedCopy = updateComponentAtPath(
|
||||
workingCopy,
|
||||
editPath,
|
||||
{
|
||||
config: {
|
||||
...getCurrentComponent(workingCopy)?.config,
|
||||
tools: updatedTools,
|
||||
},
|
||||
}
|
||||
);
|
||||
setWorkingCopy(updatedCopy);
|
||||
}
|
||||
}}
|
||||
icon={<PlusCircle className="w-4 h-4" />}
|
||||
>
|
||||
Add Tool
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{component.map((item, index) => (
|
||||
<div key={item.label} className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={() =>
|
||||
onNavigate(item.component_type, item.label || "", parentField)
|
||||
}
|
||||
className="w-full flex justify-between items-center"
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
<Edit className="w-4 h-4" />
|
||||
</Button>
|
||||
{parentField === "tools" && (
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
icon={<MinusCircle className="w-4 h-4" />}
|
||||
onClick={() => {
|
||||
if (
|
||||
workingCopy &&
|
||||
setWorkingCopy &&
|
||||
updateComponentAtPath &&
|
||||
getCurrentComponent &&
|
||||
editPath
|
||||
) {
|
||||
const currentTools =
|
||||
component as Component<ComponentConfig>[];
|
||||
const updatedTools = currentTools.filter(
|
||||
(_, idx) => idx !== index
|
||||
);
|
||||
const updatedCopy = updateComponentAtPath(
|
||||
workingCopy,
|
||||
editPath,
|
||||
{
|
||||
config: {
|
||||
...getCurrentComponent(workingCopy)?.config,
|
||||
tools: updatedTools,
|
||||
},
|
||||
}
|
||||
);
|
||||
setWorkingCopy(updatedCopy);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return component ? (
|
||||
<div className="mb-4">
|
||||
<Button
|
||||
onClick={() =>
|
||||
onNavigate(
|
||||
component.component_type,
|
||||
component.label || "",
|
||||
parentField
|
||||
)
|
||||
}
|
||||
className="w-full flex justify-between items-center"
|
||||
>
|
||||
<span>{label}</span>
|
||||
<Edit className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
@@ -0,0 +1,182 @@
|
||||
// fields/model-fields.tsx
|
||||
import React from "react";
|
||||
import { Form, Input, InputNumber, Select } from "antd";
|
||||
import { Component, ComponentConfig } from "../../../../../types/datamodel";
|
||||
import { isOpenAIModel, isAzureOpenAIModel } from "../../../../../types/guards";
|
||||
|
||||
interface ModelFieldsProps {
|
||||
component: Component<ComponentConfig>;
|
||||
}
|
||||
|
||||
export const ModelFields: React.FC<ModelFieldsProps> = ({ component }) => {
|
||||
if (!component) return null;
|
||||
|
||||
// Common arguments fields shared between OpenAI and Azure OpenAI models
|
||||
const ArgumentsFields = () => (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Temperature"
|
||||
name={["config", "temperature"]}
|
||||
tooltip="Controls randomness in the model's output. Higher values (e.g., 0.8) make output more random, lower values (e.g., 0.2) make it more focused."
|
||||
>
|
||||
<InputNumber min={0} max={2} step={0.1} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Max Tokens"
|
||||
name={["config", "max_tokens"]}
|
||||
tooltip="Maximum length of the model's output in tokens"
|
||||
>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Top P"
|
||||
name={["config", "top_p"]}
|
||||
tooltip="Controls diversity via nucleus sampling. Lower values (e.g., 0.1) make output more focused, higher values (e.g., 0.9) make it more diverse."
|
||||
>
|
||||
<InputNumber min={0} max={1} step={0.1} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Frequency Penalty"
|
||||
name={["config", "frequency_penalty"]}
|
||||
tooltip="Decreases the model's likelihood to repeat the same information. Values range from -2.0 to 2.0."
|
||||
>
|
||||
<InputNumber min={-2} max={2} step={0.1} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Presence Penalty"
|
||||
name={["config", "presence_penalty"]}
|
||||
tooltip="Increases the model's likelihood to talk about new topics. Values range from -2.0 to 2.0."
|
||||
>
|
||||
<InputNumber min={-2} max={2} step={0.1} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Stop Sequences"
|
||||
name={["config", "stop"]}
|
||||
tooltip="Sequences where the model will stop generating further tokens"
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
placeholder="Enter stop sequences"
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
if (isOpenAIModel(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Model"
|
||||
name={["config", "model"]}
|
||||
rules={[{ required: true }]}
|
||||
tooltip="The name of the OpenAI model to use (e.g., gpt-4, gpt-3.5-turbo)"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="API Key"
|
||||
name={["config", "api_key"]}
|
||||
tooltip="Your OpenAI API key"
|
||||
>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Organization"
|
||||
name={["config", "organization"]}
|
||||
tooltip="Optional: Your OpenAI organization ID"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Base URL"
|
||||
name={["config", "base_url"]}
|
||||
tooltip="Optional: Custom base URL for API requests"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Timeout"
|
||||
name={["config", "timeout"]}
|
||||
tooltip="Request timeout in seconds"
|
||||
>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Max Retries"
|
||||
name={["config", "max_retries"]}
|
||||
tooltip="Maximum number of retry attempts for failed requests"
|
||||
>
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
|
||||
<ArgumentsFields />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (isAzureOpenAIModel(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Model"
|
||||
name={["config", "model"]}
|
||||
rules={[{ required: true }]}
|
||||
tooltip="The name of the Azure OpenAI model deployment"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Azure Endpoint"
|
||||
name={["config", "azure_endpoint"]}
|
||||
rules={[{ required: true }]}
|
||||
tooltip="Your Azure OpenAI service endpoint URL"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Azure Deployment"
|
||||
name={["config", "azure_deployment"]}
|
||||
tooltip="The name of your Azure OpenAI model deployment"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="API Version"
|
||||
name={["config", "api_version"]}
|
||||
rules={[{ required: true }]}
|
||||
tooltip="Azure OpenAI API version (e.g., 2023-05-15)"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Azure AD Token"
|
||||
name={["config", "azure_ad_token"]}
|
||||
tooltip="Optional: Azure Active Directory token for authentication"
|
||||
>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
|
||||
<ArgumentsFields />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default ModelFields;
|
||||
@@ -0,0 +1,77 @@
|
||||
import React from "react";
|
||||
import { Form, Input, InputNumber, Switch } from "antd";
|
||||
import { isSelectorTeam, isRoundRobinTeam } from "../../../../../types/guards";
|
||||
import { NestedComponentButton, NodeEditorFieldsProps } from "./fields";
|
||||
const { TextArea } = Input;
|
||||
|
||||
export const TeamFields: React.FC<NodeEditorFieldsProps> = ({
|
||||
component,
|
||||
onNavigate,
|
||||
workingCopy,
|
||||
setWorkingCopy,
|
||||
editPath,
|
||||
updateComponentAtPath,
|
||||
getCurrentComponent,
|
||||
}) => {
|
||||
if (!component) return null;
|
||||
|
||||
if (isSelectorTeam(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Selector Prompt"
|
||||
name={["config", "selector_prompt"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Max Turns" name={["config", "max_turns"]}>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Allow Repeated Speaker"
|
||||
name={["config", "allow_repeated_speaker"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
{component.config.model_client && (
|
||||
<NestedComponentButton
|
||||
label="Model Client"
|
||||
component={component.config.model_client}
|
||||
parentField="model_client"
|
||||
onNavigate={onNavigate}
|
||||
/>
|
||||
)}
|
||||
{component.config.termination_condition && (
|
||||
<NestedComponentButton
|
||||
label="Termination Condition"
|
||||
component={component.config.termination_condition}
|
||||
parentField="termination_condition"
|
||||
onNavigate={onNavigate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (isRoundRobinTeam(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item label="Max Turns" name={["config", "max_turns"]}>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
{component.config.termination_condition && (
|
||||
<NestedComponentButton
|
||||
label="Termination Condition"
|
||||
component={component.config.termination_condition}
|
||||
parentField="termination_condition"
|
||||
onNavigate={onNavigate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
// fields/termination-fields.tsx
|
||||
import React from "react";
|
||||
import { Form, Input, InputNumber } from "antd";
|
||||
import { Component, ComponentConfig } from "../../../../../types/datamodel";
|
||||
import {
|
||||
isOrTermination,
|
||||
isMaxMessageTermination,
|
||||
isTextMentionTermination,
|
||||
} from "../../../../../types/guards";
|
||||
import { NestedComponentButton } from "./fields";
|
||||
|
||||
interface TerminationFieldsProps {
|
||||
component: Component<ComponentConfig>;
|
||||
onNavigate: (componentType: string, id: string, parentField: string) => void;
|
||||
}
|
||||
|
||||
export const TerminationFields: React.FC<TerminationFieldsProps> = ({
|
||||
component,
|
||||
onNavigate,
|
||||
}) => {
|
||||
if (!component) return null;
|
||||
|
||||
if (isOrTermination(component)) {
|
||||
return (
|
||||
<>
|
||||
<Form.Item label="Number of Conditions" name={["config", "conditions"]}>
|
||||
<InputNumber disabled />
|
||||
</Form.Item>
|
||||
{component.config.conditions &&
|
||||
component.config.conditions.length > 0 && (
|
||||
<NestedComponentButton
|
||||
label="Conditions"
|
||||
component={component.config.conditions}
|
||||
parentField="conditions"
|
||||
onNavigate={onNavigate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (isMaxMessageTermination(component)) {
|
||||
return (
|
||||
<Form.Item
|
||||
label="Max Messages"
|
||||
name={["config", "max_messages"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
if (isTextMentionTermination(component)) {
|
||||
return (
|
||||
<Form.Item
|
||||
label="Text"
|
||||
name={["config", "text"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default TerminationFields;
|
||||
@@ -0,0 +1,265 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { Form, Input, Switch, Select, Button, Space } from "antd";
|
||||
import { PlusCircle, MinusCircle } from "lucide-react";
|
||||
import { Import } from "../../../../../types/datamodel";
|
||||
import { isFunctionTool } from "../../../../../types/guards";
|
||||
import { MonacoEditor } from "../../../../monaco";
|
||||
import { NodeEditorFieldsProps } from "./fields";
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Option } = Select;
|
||||
|
||||
interface ImportState {
|
||||
module: string;
|
||||
imports: string;
|
||||
}
|
||||
|
||||
export const ToolFields: React.FC<
|
||||
Omit<NodeEditorFieldsProps, "onNavigate">
|
||||
> = ({
|
||||
component,
|
||||
workingCopy,
|
||||
setWorkingCopy,
|
||||
editPath,
|
||||
updateComponentAtPath,
|
||||
getCurrentComponent,
|
||||
}) => {
|
||||
if (!component || !isFunctionTool(component)) return null;
|
||||
|
||||
const editorRef = useRef(null);
|
||||
const [showAddImport, setShowAddImport] = useState(false);
|
||||
const [importType, setImportType] = useState<"direct" | "fromModule">(
|
||||
"direct"
|
||||
);
|
||||
const [directImport, setDirectImport] = useState("");
|
||||
const [moduleImport, setModuleImport] = useState<ImportState>({
|
||||
module: "",
|
||||
imports: "",
|
||||
});
|
||||
|
||||
const formatImport = (imp: Import): string => {
|
||||
if (!imp) return "";
|
||||
if (typeof imp === "string") {
|
||||
return imp;
|
||||
}
|
||||
return `from ${imp.module} import ${imp.imports.join(", ")}`;
|
||||
};
|
||||
|
||||
const handleAddImport = (form: { add: (value: string | Import) => void }) => {
|
||||
if (importType === "direct" && directImport) {
|
||||
form.add(directImport);
|
||||
setDirectImport("");
|
||||
} else if (
|
||||
importType === "fromModule" &&
|
||||
moduleImport.module &&
|
||||
moduleImport.imports
|
||||
) {
|
||||
form.add({
|
||||
module: moduleImport.module,
|
||||
imports: moduleImport.imports
|
||||
.split(",")
|
||||
.map((i) => i.trim())
|
||||
.filter((i) => i),
|
||||
});
|
||||
setModuleImport({ module: "", imports: "" });
|
||||
}
|
||||
setShowAddImport(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Name"
|
||||
name={["config", "name"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Description"
|
||||
name={["config", "description"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<TextArea rows={4} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Global Imports">
|
||||
<div className="space-y-2">
|
||||
<Form.List name={["config", "global_imports"]}>
|
||||
{(fields, { add, remove }) => (
|
||||
<div className="space-y-2">
|
||||
{/* Existing Imports */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{fields.map((field) => (
|
||||
<div
|
||||
key={field.key}
|
||||
className="flex items-center gap-2 bg-tertiary rounded px-2 py-1"
|
||||
>
|
||||
<Form.Item {...field} noStyle>
|
||||
<Input type="hidden" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
shouldUpdate={(prevValues, curValues) => {
|
||||
const prevImport =
|
||||
prevValues.config?.global_imports?.[field.name];
|
||||
const curImport =
|
||||
curValues.config?.global_imports?.[field.name];
|
||||
return (
|
||||
JSON.stringify(prevImport) !==
|
||||
JSON.stringify(curImport)
|
||||
);
|
||||
}}
|
||||
noStyle
|
||||
>
|
||||
{({ getFieldValue }) => {
|
||||
const imp = getFieldValue([
|
||||
"config",
|
||||
"global_imports",
|
||||
field.name,
|
||||
]);
|
||||
return (
|
||||
<span className="text-sm">{formatImport(imp)}</span>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
className="flex items-center justify-center h-6 w-6 p-0"
|
||||
onClick={() => remove(field.name)}
|
||||
icon={<MinusCircle className="h-4 w-4" />}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Add Import UI */}
|
||||
{showAddImport ? (
|
||||
<div className="border rounded p-3 space-y-3">
|
||||
<Form.Item className="mb-2">
|
||||
<Select
|
||||
value={importType}
|
||||
onChange={setImportType}
|
||||
style={{ width: 200 }}
|
||||
>
|
||||
<Option value="direct">Direct Import</Option>
|
||||
<Option value="fromModule">From Module Import</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
{importType === "direct" ? (
|
||||
<Space>
|
||||
<Input
|
||||
placeholder="Package name (e.g., os)"
|
||||
className="w-64"
|
||||
value={directImport}
|
||||
onChange={(e) => setDirectImport(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && directImport) {
|
||||
handleAddImport({ add });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handleAddImport({ add })}
|
||||
disabled={!directImport}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</Space>
|
||||
) : (
|
||||
<Space direction="vertical" className="w-full">
|
||||
<Input
|
||||
placeholder="Module name (e.g., typing)"
|
||||
className="w-64"
|
||||
value={moduleImport.module}
|
||||
onChange={(e) =>
|
||||
setModuleImport((prev) => ({
|
||||
...prev,
|
||||
module: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<Space className="w-full">
|
||||
<Input
|
||||
placeholder="Import names (comma-separated)"
|
||||
className="w-64"
|
||||
value={moduleImport.imports}
|
||||
onChange={(e) =>
|
||||
setModuleImport((prev) => ({
|
||||
...prev,
|
||||
imports: e.target.value,
|
||||
}))
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (
|
||||
e.key === "Enter" &&
|
||||
moduleImport.module &&
|
||||
moduleImport.imports
|
||||
) {
|
||||
handleAddImport({ add });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handleAddImport({ add })}
|
||||
disabled={
|
||||
!moduleImport.module || !moduleImport.imports
|
||||
}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => setShowAddImport(true)}
|
||||
className="w-full"
|
||||
>
|
||||
<PlusCircle className="h-4 w-4 mr-2" />
|
||||
Add Import
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Form.List>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Source Code"
|
||||
name={["config", "source_code"]}
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<div className="h-96">
|
||||
<Form.Item noStyle shouldUpdate>
|
||||
{({ getFieldValue, setFieldValue }) => (
|
||||
<MonacoEditor
|
||||
value={getFieldValue(["config", "source_code"]) || ""}
|
||||
editorRef={editorRef}
|
||||
language="python"
|
||||
onChange={(value) =>
|
||||
setFieldValue(["config", "source_code"], value)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Has Cancellation Support"
|
||||
name={["config", "has_cancellation_support"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToolFields;
|
||||
@@ -3,10 +3,10 @@ import { Form, Button, Drawer, Breadcrumb } from "antd";
|
||||
import { ChevronLeft } from "lucide-react";
|
||||
import { Component, ComponentConfig } from "../../../../types/datamodel";
|
||||
import { NodeEditorProps } from "../types";
|
||||
import NodeEditorFields from "./fields";
|
||||
import { isComponent } from "../../../../types/guards";
|
||||
import NodeEditorFields from "./fields/fields";
|
||||
|
||||
interface EditPath {
|
||||
export interface EditPath {
|
||||
componentType: string;
|
||||
id: string;
|
||||
parentField: string;
|
||||
@@ -48,6 +48,7 @@ export const NodeEditor: React.FC<
|
||||
(acc, [key, value]) => {
|
||||
// Skip nested component fields as they're handled by buttons
|
||||
if (
|
||||
key !== "global_imports" &&
|
||||
typeof value === "object" &&
|
||||
(Array.isArray(value) || value?.component_type)
|
||||
) {
|
||||
@@ -248,6 +249,7 @@ export const NodeEditor: React.FC<
|
||||
<Drawer
|
||||
title={
|
||||
<div className="flex items-center gap-4">
|
||||
{" "}
|
||||
{editPath.length > 0 && (
|
||||
<Button
|
||||
onClick={navigateBack}
|
||||
@@ -261,7 +263,7 @@ export const NodeEditor: React.FC<
|
||||
</div>
|
||||
}
|
||||
placement="right"
|
||||
width={400}
|
||||
size="large"
|
||||
onClose={onClose}
|
||||
open={true}
|
||||
className="node-editor-drawer"
|
||||
@@ -275,9 +277,14 @@ export const NodeEditor: React.FC<
|
||||
<NodeEditorFields
|
||||
component={currentComponent}
|
||||
onNavigate={navigateToComponent}
|
||||
editPath={editPath}
|
||||
workingCopy={workingCopy}
|
||||
setWorkingCopy={setWorkingCopy}
|
||||
updateComponentAtPath={updateComponentAtPath}
|
||||
getCurrentComponent={getCurrentComponent}
|
||||
/>
|
||||
)}
|
||||
<div className="flex justify-end gap-2 mt-4 absolute bottom-0 right-0 left-0 p-4 bg-white border-t">
|
||||
<div className="flex justify-end gap-2 mt-4 absolute bottom-0 right-0 left-0 p-4 bg-primary border-t">
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button type="primary" onClick={handleFormSubmit}>
|
||||
Save Changes
|
||||
|
||||
@@ -234,6 +234,7 @@ export const TeamNode = memo<NodeProps<CustomNode>>((props) => {
|
||||
<TruncatableText
|
||||
content={component.description || component.label || ""}
|
||||
textThreshold={150}
|
||||
showFullscreen={false}
|
||||
/>
|
||||
</div>
|
||||
{isSelectorTeam(component) && component.config.selector_prompt && (
|
||||
@@ -242,6 +243,7 @@ export const TeamNode = memo<NodeProps<CustomNode>>((props) => {
|
||||
<TruncatableText
|
||||
content={component.config.selector_prompt}
|
||||
textThreshold={150}
|
||||
showFullscreen={false}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user