From 1792cb4d933fb8fa06212f2180e33145bb77d183 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Tue, 3 Mar 2026 13:48:50 +0800 Subject: [PATCH] feat(web): chat add variables --- web/src/api/application.ts | 18 ++-- web/src/i18n/en.ts | 5 +- web/src/i18n/zh.ts | 5 +- web/src/views/Conversation/index.tsx | 83 ++++++++++++++++++- web/src/views/Conversation/types.ts | 3 +- .../components/Chat/VariableConfigModal.tsx | 6 +- .../Properties/VariableList/types.ts | 1 + 7 files changed, 102 insertions(+), 19 deletions(-) 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 5f041a7c..bc1f0c45 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1684,7 +1684,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', @@ -2192,7 +2193,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 0c0f7562..826001ed 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1681,7 +1681,8 @@ export const zh = { fileType: '文件类型', image: '图片', fileUrl: '文件链接', - addRemoteFile: '添加远程文件' + addRemoteFile: '添加远程文件', + variableConfig: '变量配置', }, login: { title: '红熊记忆科学', @@ -2192,7 +2193,7 @@ export const zh = { save: '保存', export: '导出', variableConfig: '变量配置', - variableRequired: '必填', + variableRequired: '必填,请配置变量值', addMessage: '添加消息', answerDesc: '回复', addNode: '添加节点', 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/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;