diff --git a/web/src/api/application.ts b/web/src/api/application.ts index f019103e..c769dd91 100644 --- a/web/src/api/application.ts +++ b/web/src/api/application.ts @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 13:59:45 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-28 16:34:15 + * @Last Modified time: 2026-03-03 12:08:42 */ import { request } from '@/utils/request' import type { ApplicationModalData } from '@/views/ApplicationManagement/types' @@ -120,15 +120,19 @@ export const copyApplication = (app_id: string, new_name: string) => { export const getAppStatistics = (app_id: string, data: { start_date: number; end_date: number; }) => { return request.get(`/apps/${app_id}/statistics`, data) } -// 导出工作流 -export const exportWorkflow = (app_id: string, fileName: string) => { - return request.downloadFile(`/apps/${app_id}/workflow/export`, fileName, undefined, undefined, 'GET') -} -// 工作流上传+兼容性分析 +// Upload workflow and analyze compatibility export const importWorkflow = (formData: FormData) => { return request.uploadFile(`/apps/workflow/import`, formData) } -// 完成工作流导入 +// Complete workflow import export const completeImportWorkflow = (data: { temp_id: string; name?: string; description?: string }) => { return request.post(`/apps/workflow/import/save`, data) } +// Get experience config +export const getExperienceConfig = (share_token: string) => { + return request.get(`/public/share/config`, {}, { + headers: { + 'Authorization': `Bearer ${localStorage.getItem(`shareToken_${share_token}`)}` + } + }) +} \ No newline at end of file diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 60354fd5..d404dd6e 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1685,7 +1685,8 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re fileType: 'File Type', image: 'Image', fileUrl: 'File URL', - addRemoteFile: 'Add Remote File' + addRemoteFile: 'Add Remote File', + variableConfig: 'Variable Configuration', }, login: { title: 'Red Bear Memory Science', @@ -2193,7 +2194,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re save: 'Save', export: 'Export', variableConfig: 'Variable Configuration', - variableRequired: 'Required', + variableRequired: 'Required, please configure variable values', addMessage: 'Add Message', answerDesc: 'Reply', addNode: 'Add Node', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index d4c0d42a..fc6bb822 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1682,7 +1682,8 @@ export const zh = { fileType: '文件类型', image: '图片', fileUrl: '文件链接', - addRemoteFile: '添加远程文件' + addRemoteFile: '添加远程文件', + variableConfig: '变量配置', }, login: { title: '红熊记忆科学', @@ -2193,7 +2194,7 @@ export const zh = { save: '保存', export: '导出', variableConfig: '变量配置', - variableRequired: '必填', + variableRequired: '必填,请配置变量值', addMessage: '添加消息', answerDesc: '回复', addNode: '添加节点', diff --git a/web/src/views/ApplicationConfig/Agent.tsx b/web/src/views/ApplicationConfig/Agent.tsx index 6018c600..237c3373 100644 --- a/web/src/views/ApplicationConfig/Agent.tsx +++ b/web/src/views/ApplicationConfig/Agent.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:29:21 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-03 11:14:30 + * @Last Modified time: 2026-03-03 14:24:34 */ import { type FC, type ReactNode, useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react'; import clsx from 'clsx' @@ -403,6 +403,9 @@ const Agent = forwardRef((_props, ref) => { const handleSaveChatVariable = (values: Variable[]) => { setChatVariables(values) } + useEffect(() => { + setChatVariables(values?.variables || []) + }, [values?.variables]) console.log('values', values) return ( <> @@ -507,6 +510,7 @@ const Agent = forwardRef((_props, ref) => { chatList={chatList} updateChatList={setChatList} handleSave={handleSave} + chatVariables={chatVariables} /> diff --git a/web/src/views/ApplicationConfig/components/Chat.tsx b/web/src/views/ApplicationConfig/components/Chat.tsx index 794489c6..8cb6812c 100644 --- a/web/src/views/ApplicationConfig/components/Chat.tsx +++ b/web/src/views/ApplicationConfig/components/Chat.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:27:39 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-10 17:40:15 + * @Last Modified time: 2026-03-03 14:21:54 */ /** * Chat debugging component for application testing @@ -13,7 +13,7 @@ import { type FC, useEffect, useState, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import clsx from 'clsx' -import { Flex, Dropdown, type MenuProps } from 'antd' +import { Flex, Dropdown, type MenuProps, App } from 'antd' import ChatIcon from '@/assets/images/application/chat.png' import DebuggingEmpty from '@/assets/images/application/debuggingEmpty.png' @@ -28,6 +28,7 @@ import UploadFiles from '@/views/Conversation/components/FileUpload' // import AudioRecorder from '@/components/AudioRecorder' import UploadFileListModal from '@/views/Conversation/components/UploadFileListModal' import type { UploadFileListModalRef } from '@/views/Conversation/types' +import type { Variable } from './VariableList/types' /** * Component props @@ -43,14 +44,16 @@ interface ChatProps { handleSave: (flag?: boolean) => Promise; /** Source type: multi-agent cluster or single agent */ source?: 'multi_agent' | 'agent'; + chatVariables?: Variable[]; // Add chatVariables prop } /** * Chat debugging component * Allows testing application with different model configurations side-by-side */ -const Chat: FC = ({ chatList, data, updateChatList, handleSave, source = 'agent' }) => { +const Chat: FC = ({ chatList, data, updateChatList, handleSave, source = 'agent', chatVariables }) => { const { t } = useTranslation(); + const { message: messageApi } = App.useApp() const [loading, setLoading] = useState(false) const [isCluster, setIsCluster] = useState(source === 'multi_agent') const [conversationId, setConversationId] = useState(null) @@ -195,6 +198,27 @@ const Chat: FC = ({ chatList, data, updateChatList, handleSave, sourc }; setTimeout(() => { + // Validate required variables before sending + let isCanSend = true + const params: Record = {} + if (chatVariables && chatVariables.length > 0) { + const needRequired: string[] = [] + chatVariables.forEach(vo => { + params[vo.name] = vo.value + + if (vo.required && (params[vo.name] === null || params[vo.name] === undefined || params[vo.name] === '')) { + isCanSend = false + needRequired.push(vo.name) + } + }) + + if (needRequired.length) { + messageApi.error(`${needRequired.join(',')} ${t('workflow.variableRequired')}`) + } + } + if (!isCanSend) { + return + } runCompare(data.app_id, { message, files: fileList.map(file => { @@ -214,7 +238,7 @@ const Chat: FC = ({ chatList, data, updateChatList, handleSave, sourc model_parameters: item.model_parameters, conversation_id: item.conversation_id })), - variables: {}, + variables: params, "parallel": true, "stream": true, "timeout": 60, diff --git a/web/src/views/ApplicationConfig/components/ReleaseShareModal.tsx b/web/src/views/ApplicationConfig/components/ReleaseShareModal.tsx index b98c1aa4..f26441fd 100644 --- a/web/src/views/ApplicationConfig/components/ReleaseShareModal.tsx +++ b/web/src/views/ApplicationConfig/components/ReleaseShareModal.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:28:46 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:28:46 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-03 14:03:44 */ /** * Release Share Modal @@ -79,7 +79,7 @@ const ReleaseShareModal = forwardRef{t('application.shareVersion')} {version?.version}} + title={<>{t('application.shareVersion')} ({version?.version_name && version.version_name[0].toLocaleLowerCase() === 'v' ? version.version_name : version?.version_name ? `v${version.version_name}` : `v${version?.version}`})} open={visible} onCancel={handleClose} footer={false} diff --git a/web/src/views/Conversation/index.tsx b/web/src/views/Conversation/index.tsx index 825ea834..f532ac53 100644 --- a/web/src/views/Conversation/index.tsx +++ b/web/src/views/Conversation/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:58:03 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-10 17:41:05 + * @Last Modified time: 2026-03-03 13:46:22 */ /** * Conversation Page @@ -14,11 +14,12 @@ import { type FC, useState, useEffect, useRef } from 'react' import { useParams, useLocation } from 'react-router-dom' import { useTranslation } from 'react-i18next' import InfiniteScroll from 'react-infinite-scroll-component'; -import { Flex, Skeleton, Form, Dropdown, type MenuProps } from 'antd' +import { Flex, Skeleton, Form, Dropdown, type MenuProps, App } from 'antd' +import { SettingOutlined } from '@ant-design/icons' import clsx from 'clsx' import dayjs from 'dayjs' -import { getConversationHistory, sendConversation, getConversationDetail, getShareToken } from '@/api/application' +import { getConversationHistory, sendConversation, getConversationDetail, getShareToken, getExperienceConfig } from '@/api/application' import type { HistoryItem, QueryParams, UploadFileListModalRef } from './types' import Empty from '@/components/Empty' import { formatDateTime } from '@/utils/format'; @@ -37,12 +38,16 @@ import UploadFiles from './components/FileUpload' // import AudioRecorder from '@/components/AudioRecorder' import { shareFileUploadUrlWithoutApiPrefix } from '@/api/fileStorage' import UploadFileListModal from './components/UploadFileListModal' +import type { VariableConfigModalRef } from '@/views/Workflow/types' +import type { Variable } from '@/views/Workflow/components/Properties/VariableList/types' +import VariableConfigModal from '@/views/Workflow/components/Chat/VariableConfigModal'; /** * Conversation component for shared applications */ const Conversation: FC = () => { const { t } = useTranslation() + const { message: messageApi } = App.useApp() const { token } = useParams() const location = useLocation() const searchParams = new URLSearchParams(location.search) @@ -64,6 +69,22 @@ const Conversation: FC = () => { const queryValues = Form.useWatch([], form) const uploadFileListModalRef = useRef(null) + + const variableConfigModalRef = useRef(null) + const [variables, setVariables] = useState([]) // Workflow input variables + + /** + * Opens the variable configuration modal + */ + const handleEditVariables = () => { + variableConfigModalRef.current?.handleOpen(variables) + } + /** + * Saves updated variable values from the modal + */ + const handleSave = (values: Variable[]) => { + setVariables([...values]) + } useEffect(() => { const shareToken = localStorage.getItem(`shareToken_${token}`) setShareToken(shareToken) @@ -81,6 +102,17 @@ const Conversation: FC = () => { getHistory() } }, [token, shareToken, page, hasMore, historyList]) + useEffect(() => { + if (shareToken && token) { + getExperienceConfig(token) + .then(res => { + const response = res as { variables: Variable[] } + setVariables(response.variables || []) + }) + } else { + setChatList([]) + } + }, [shareToken, token]) /** Group conversation history by date */ const groupHistoryByDate = (items: HistoryItem[]): Record => { @@ -191,12 +223,35 @@ const Conversation: FC = () => { }) } + const isNeedVariableConfig = variables.some(vo => vo.required && (vo.value === null || vo.value === undefined || vo.value === '')) + /** Send message and handle streaming response */ const handleSend = () => { if (!token || !shareToken) { return } const { files = [], ...rest } = queryValues || {} + // Validate required variables before sending + let isCanSend = true + const params: Record = {} + if (variables.length > 0) { + const needRequired: string[] = [] + variables.forEach(vo => { + params[vo.name] = vo.value ?? vo.defaultValue + + if (vo.required && (params[vo.name] === null || params[vo.name] === undefined || params[vo.name] === '')) { + isCanSend = false + needRequired.push(vo.name) + } + }) + + if (needRequired.length) { + messageApi.error(`${needRequired.join(',')} ${t('workflow.variableRequired')}`) + } + } + if (!isCanSend) { + return + } setLoading(true) setStreamLoading(true) addUserMessage(message, files) @@ -247,7 +302,8 @@ const Conversation: FC = () => { upload_file_id: file.response.data.file_id } } - }) + }), + variables: params }, handleStreamMessage, shareToken) .finally(() => { setLoading(false) @@ -384,6 +440,20 @@ const Conversation: FC = () => { {t(`memoryConversation.memory`)} + {variables.length > 0 && ( + +
+ + {t(`memoryConversation.variableConfig`)} +
+
+ )} {/* @@ -399,6 +469,11 @@ const Conversation: FC = () => { ref={uploadFileListModalRef} refresh={addFileList} /> + ) } diff --git a/web/src/views/Conversation/types.ts b/web/src/views/Conversation/types.ts index deb14d1f..cc074c1b 100644 --- a/web/src/views/Conversation/types.ts +++ b/web/src/views/Conversation/types.ts @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:57:46 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-06 21:11:19 + * @Last Modified time: 2026-03-03 13:46:55 */ /** * Type definitions for Conversation @@ -51,6 +51,7 @@ export interface QueryParams { /** Current conversation ID */ conversation_id?: string | null; files?: any[]; + variables?: Record; } export interface UploadFileListModalRef { diff --git a/web/src/views/Workflow/components/Chat/VariableConfigModal.tsx b/web/src/views/Workflow/components/Chat/VariableConfigModal.tsx index dd84e6ba..5acd3eb1 100644 --- a/web/src/views/Workflow/components/Chat/VariableConfigModal.tsx +++ b/web/src/views/Workflow/components/Chat/VariableConfigModal.tsx @@ -71,20 +71,20 @@ const VariableConfigModal = forwardRef { - field.type === 'string' && + (field.type === 'string' || field.type === 'text') && } { field.type === 'number' && form.setFieldValue(['variables', name, 'value'], value)} /> } { - field.type === 'boolean' && {`${field.name}·${field.description}`} + field.type === 'boolean' && {`${field.name}·${field.display_name || field.description}`} } ) diff --git a/web/src/views/Workflow/components/Properties/HttpRequest/EditableTable.tsx b/web/src/views/Workflow/components/Properties/HttpRequest/EditableTable.tsx index 74593913..19706a71 100644 --- a/web/src/views/Workflow/components/Properties/HttpRequest/EditableTable.tsx +++ b/web/src/views/Workflow/components/Properties/HttpRequest/EditableTable.tsx @@ -59,7 +59,7 @@ const EditableTable: FC = ({ render: (_: any, __: TableRow, index: number) => ( !option.dataType.includes('file'))} type="input" className={contentClassName} size={size} @@ -109,7 +109,7 @@ const EditableTable: FC = ({ const currentType = form.getFieldValue([...Array.isArray(parentName) ? parentName : [parentName], index, 'type']); const filteredOptions = currentType === 'file' ? booleanFilterOptions.filter(option => option.dataType.includes('file')) - : booleanFilterOptions; + : booleanFilterOptions.filter(option => !option.dataType.includes('file')); return ( diff --git a/web/src/views/Workflow/components/Properties/VariableList/types.ts b/web/src/views/Workflow/components/Properties/VariableList/types.ts index 1cc2939a..64e62c87 100644 --- a/web/src/views/Workflow/components/Properties/VariableList/types.ts +++ b/web/src/views/Workflow/components/Properties/VariableList/types.ts @@ -1,5 +1,6 @@ export interface Variable { name: string; + display_name?: string; type: string; required: boolean; description: string;