diff --git a/web/src/routes/routes.json b/web/src/routes/routes.json index 9d3e9cb8..73431f67 100644 --- a/web/src/routes/routes.json +++ b/web/src/routes/routes.json @@ -32,7 +32,6 @@ { "path": "/api-key", "element": "ApiKeyManagement" }, { "path": "/emotion-engine/:id", "element": "EmotionEngine" }, { "path": "/reflection-engine/:id", "element": "SelfReflectionEngine" }, - { "path": "/statement/:id", "element": "StatementDetail" }, { "path": "/no-permission", "element": "NoPermission" }, { "path": "/*", "element": "NotFound" } ] @@ -42,7 +41,8 @@ "children": [ { "path": "/application/config/:id", "element": "ApplicationConfig" }, { "path": "/conversation/:token", "element": "Conversation" }, - { "path": "/user-memory/neo4j/:id", "element": "Neo4jUserMemoryDetail" } + { "path": "/user-memory/neo4j/:id", "element": "Neo4jUserMemoryDetail" }, + { "path": "/statement/:id", "element": "StatementDetail" } ] }, { diff --git a/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx b/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx index 002d0b6b..67fd654c 100644 --- a/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx +++ b/web/src/views/ApplicationConfig/components/ModelConfigModal.tsx @@ -2,7 +2,7 @@ import { forwardRef, useImperativeHandle, useState, useEffect } from 'react'; import { Form, Select } from 'antd'; import { useTranslation } from 'react-i18next'; -import type { ModelConfig, ModelConfigModalRef, Config, ChatData } from '../types' +import type { ModelConfig, ModelConfigModalRef, Config, Source } from '../types' import type { Model } from '@/views/ModelManagement/types' import RbModal from '@/components/RbModal' import RbSlider from '@/components/RbSlider' @@ -10,10 +10,9 @@ import RbSlider from '@/components/RbSlider' const FormItem = Form.Item; interface ModelConfigModalProps { - modelList: Model[]; - refresh: (values: ModelConfig, type: 'model') => void; + modelList?: Model[]; + refresh: (values: ModelConfig, type: Source) => void; data: Config; - chatList: ChatData[] } const configFields = [ @@ -28,12 +27,12 @@ const configFields = [ const ModelConfigModal = forwardRef(({ refresh, data, - modelList + modelList = [] }, ref) => { const { t } = useTranslation(); const [visible, setVisible] = useState(false); const [form] = Form.useForm(); - const [source, setSource] = useState<'chat' | 'model'>('model') + const [source, setSource] = useState('model') const values = Form.useWatch([], form); @@ -43,14 +42,14 @@ const ModelConfigModal = forwardRef( form.resetFields(); }; - const handleOpen = (source: 'chat' | 'model', model) => { + const handleOpen = (source: Source, model?: any) => { setSource(source) if (source === 'model') { form.setFieldsValue({ ...(data?.model_parameters || {}), default_model_config_id: data.default_model_config_id || '' }) - } else if (source === 'chat') { + } else if (source === 'chat' || source === 'multi_agent') { if (model) { form.setFieldsValue({ ...(model?.model_parameters || {}), @@ -77,9 +76,9 @@ const ModelConfigModal = forwardRef( console.log('err', err) }); } - const handleChange = (value: string, option: Model) => { + const handleChange = (_value: string, option: Model | Model[] | undefined) => { if (source === 'chat') { - form.setFieldValue('label', option.name) + form.setFieldValue('label', (option as Model).name) } } @@ -104,14 +103,15 @@ const ModelConfigModal = forwardRef(
+ {values?.auth !== 'none' && <> + + + + } + + + + } +
+ + ); +}); + +export default AuthConfigModal; \ No newline at end of file diff --git a/web/src/views/Workflow/components/Properties/HttpRequest/EditableTable.tsx b/web/src/views/Workflow/components/Properties/HttpRequest/EditableTable.tsx new file mode 100644 index 00000000..1dc1d8b4 --- /dev/null +++ b/web/src/views/Workflow/components/Properties/HttpRequest/EditableTable.tsx @@ -0,0 +1,232 @@ +import { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next' +import { Button, Select, Table } from 'antd'; +import { PlusOutlined, DeleteOutlined } from '@ant-design/icons'; +import Editor from '../../Editor'; +import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'; +import Empty from '@/components/Empty'; +import VariableSelect from '../VariableSelect'; + +export interface TableRow { + key: string; + name: string; + value: string; + type?: string; +} + +interface EditableTableProps { + title?: string; + value?: Record | TableRow[]; + onChange?: (value: TableRow[]) => void; + options?: Suggestion[]; + typeOptions?: {value: string, label: string}[] +} + +const EditableTable: React.FC = ({ + title, + value, + onChange, + options = [], + typeOptions = [] +}) => { + const { t } = useTranslation() + const [rows, setRows] = useState([]); + + useEffect(() => { + console.log('EditableTable value', value) + if (Array.isArray(value)) { + setRows([...value]) + } else if (value && Object.keys(value).length > 0) { + // Only update if rows are empty or significantly different + const valueEntries = Object.entries(value) + if (rows.length === 0 || rows.length !== valueEntries.length) { + setRows(valueEntries.map(([key, val], index) => { + console.log('val', val) + return { + key: index.toString(), + name: key || '', + value: val || '', + type: typeOptions.length > 0 ? typeOptions[0].value : undefined + } + })) + } + } else { + setRows([]) + } + }, [JSON.stringify(value), typeOptions.length]) + + const handleChange = (key: string, field: 'name' | 'value' | 'type', val: string) => { + const newRows = [...rows.map(row => + row.key === key ? { ...row, [field]: val } : row + )]; + + setRows(newRows); + onChange?.(newRows); + }; + + const handleAdd = () => { + const newKey = Date.now().toString(); + if (typeOptions.length) { + setRows([...rows, { key: newKey, name: '', value: '', type: typeOptions[0].value }]); + } else { + setRows([...rows, { key: newKey, name: '', value: '' }]); + } + }; + + const handleDelete = (key: string, index: number) => { + console.log('index', index) + + if (rows.length === 1) { + setRows([]); + onChange?.([]); + } else { + const newRows = rows.filter(row => row.key !== key); + setRows(newRows); + onChange?.(newRows); + } + }; + + const columns = typeOptions?.length > 0 ? [ + { + title: t('workflow.config.name'), + dataIndex: 'name', + width: '45%', + render: (text: string, record: TableRow) => ( + handleChange(record.key, 'name', value)} + /> + ), + }, + { + title: t('workflow.config.type'), + dataIndex: 'type', + width: '20%', + render: (text: string, record: TableRow) => ( + + + + + + + + + + + + + updateObjectList(headers, 'headers')} + /> + + + + updateObjectList(params, 'params')} + /> + + + + + + + {values?.error_handle?.method === 'default' && + <> + + + + + + + { + if (!value) return Promise.resolve(); + try { + JSON.parse(value); + return Promise.resolve(); + } catch { + return Promise.reject(new Error('Please enter valid JSON format')); + } + } + } + ]} + > + + + + } + + + + ); +}; +export default HttpRequest; \ No newline at end of file diff --git a/web/src/views/Workflow/components/Properties/HttpRequest/types.ts b/web/src/views/Workflow/components/Properties/HttpRequest/types.ts new file mode 100644 index 00000000..e208ee3c --- /dev/null +++ b/web/src/views/Workflow/components/Properties/HttpRequest/types.ts @@ -0,0 +1,44 @@ +export interface HttpRequestConfigForm { + method?: string; + url?: string; + auth?: { + auth?: string; + auth_type?: string; + header?: string; + api_key?: string; + }; + headers?: { + [key: string]: string; + }; + params?: { + [key: string]: string; + }; + body?: { + content_type?: string; + data: string | Record; + }; + verify_ssl?: boolean; + timeouts?: { + connect_timeout: number; + read_timeout: number; + write_timeout: number; + }; + retry?: { + max_attempts: number; + retry_interval: number; + }; + error_handle?: { + method: string; + default: { + body: string; + status_code: number; + headers: { + [key: string]: string; + }; + }; + }; +} + +export interface AuthConfigModalRef { + handleOpen: (vo?: HttpRequestConfigForm['auth']) => void; +} \ No newline at end of file diff --git a/web/src/views/Workflow/components/Properties/MappingList/index.tsx b/web/src/views/Workflow/components/Properties/MappingList/index.tsx new file mode 100644 index 00000000..9c8ed265 --- /dev/null +++ b/web/src/views/Workflow/components/Properties/MappingList/index.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next' +import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; +import { Button, Form, Input, Space } from 'antd'; + +interface MappingListProps { + name: string; +} +const MappingList: React.FC = ({ name }) => { + const { t } = useTranslation() + return ( + <> + + {(fields, { add, remove }) => ( + <> + {fields.map(({ key, name, ...restField }) => ( + + + + + + + + remove(name)} /> + + ))} + + + + + )} + + + ) +}; + +export default MappingList; \ No newline at end of file diff --git a/web/src/views/Workflow/components/Properties/index.tsx b/web/src/views/Workflow/components/Properties/index.tsx index f97f4532..c0f5ae7e 100644 --- a/web/src/views/Workflow/components/Properties/index.tsx +++ b/web/src/views/Workflow/components/Properties/index.tsx @@ -15,6 +15,8 @@ import VariableSelect from './VariableSelect'; import ParamsList from './ParamsList'; import GroupVariableList from './GroupVariableList' import CaseList from './CaseList' +import HttpRequest from './HttpRequest'; +import MappingList from './MappingList' interface PropertiesProps { selectedNode?: Node | null; @@ -38,10 +40,10 @@ const Properties: FC = ({ const [editIndex, setEditIndex] = useState(null) useEffect(() => { - if (selectedNode?.getData().id) { + if (selectedNode?.getData()?.id) { form.resetFields() } - }, [selectedNode?.getData().id]) + }, [selectedNode?.getData()?.id]) useEffect(() => { if (selectedNode && form) { @@ -97,7 +99,7 @@ const Properties: FC = ({ } Object.keys(values).forEach(key => { - if (selectedNode.data?.config[key]) { + if (selectedNode.data?.config?.[key]) { selectedNode.data.config[key].defaultValue = values[key] } }) @@ -190,6 +192,7 @@ const Properties: FC = ({ ...(nodeData.config?.variables?.value ?? []) ] list.forEach((variable: any) => { + if (!variable || !variable?.name) return; const key = `${nodeId}_${variable.name}`; if (!addedKeys.has(key)) { addedKeys.add(key); @@ -203,7 +206,8 @@ const Properties: FC = ({ }); } }); - nodeData.config?.variables?.sys.forEach((variable: any) => { + nodeData.config?.variables?.sys?.forEach((variable: any) => { + if (!variable || !variable?.name) return; const key = `${nodeId}_sys_${variable.name}`; if (!addedKeys.has(key)) { addedKeys.add(key); @@ -238,6 +242,8 @@ const Properties: FC = ({ return variableList; }, [selectedNode, graphRef]); + console.log('values', values) + return (
{t('workflow.nodeProperties')}
@@ -251,178 +257,195 @@ const Properties: FC = ({ updateNodeLabel(e.target.value); }} /> - - - - - {configs && Object.keys(configs).length > 0 && Object.keys(configs).map((key) => { - const config = configs[key] || {} + + + + + + {selectedNode?.data?.type === 'http-request' + ? + : configs && Object.keys(configs).length > 0 && Object.keys(configs).map((key) => { + const config = configs[key] || {} - if (selectedNode.data?.type === 'start' && key === 'variables' && config.type === 'define') { - return ( -
-
-
- {t(`workflow.config.${selectedNode.data.type}.${key}`)} + if (selectedNode?.data?.type === 'start' && key === 'variables' && config.type === 'define') { + return ( +
+
+
+ {t(`workflow.config.${selectedNode?.data?.type}.${key}`)} +
+
- -
- - {Array.isArray(config.defaultValue) && config.defaultValue?.map((vo, index) => -
- {vo.name}·{vo.description} + + {Array.isArray(config.defaultValue) && config.defaultValue?.map((vo, index) => +
+ {vo.name}·{vo.description} -
- {vo.required && {t('workflow.config.start.required')}} +
+ {vo.required && {t('workflow.config.start.required')}} + {vo.type} +
+ +
handleEditVariable(index, vo)} + >
+
handleDeleteVariable(index, vo)} + >
+
+
+ )} + + {config.sys?.map((vo, index) => +
+
+ sys.{vo.name} +
{vo.type}
- -
handleEditVariable(index, vo)} - >
-
handleDeleteVariable(index, vo)} - >
-
-
- )} - - {config.sys?.map((vo, index) => -
-
- sys.{vo.name} -
- {vo.type} -
- )} -
-
- ) - } + )} +
+
+ ) + } - if (selectedNode.data?.type === 'llm' && key === 'messages' && config.type === 'define') { - return ( - - - - ) - } - if (selectedNode.data?.type === 'end' && key === 'output') { - return ( - - - - ) - } + if (selectedNode?.data?.type === 'llm' && key === 'messages' && config.type === 'define') { + return ( + + + + ) + } + if (selectedNode?.data?.type === 'end' && key === 'output') { + return ( + + + + ) + } - if (config.type === 'define') { - return null - } + if (config.type === 'define') { + return null + } - if (config.type === 'knowledge') { - return ( - - - - ) - } - - if (config.type === 'messageEditor') { - return ( - - - - ) - } - - if (config.type === 'paramList') { - return ( - - - - - ) - } - if (config.type === 'groupVariableList') { - return ( - - - - - ) - } - if (config.type === 'caseList') { - console.log('key', key) - return ( - - - - ) - } + > + + + ) + } - return ( - - {config.type === 'input' - ? - : config.type === 'textarea' - ? - : config.type === 'select' - ? + : config.type === 'textarea' + ? + : config.type === 'select' + ?