feat(web): chat add variables

This commit is contained in:
zhaoying
2026-03-03 13:48:50 +08:00
parent 45a64dbbac
commit 1792cb4d93
7 changed files with 102 additions and 19 deletions

View File

@@ -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}`)}`
}
})
}

View File

@@ -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',

View File

@@ -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: '添加节点',

View File

@@ -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<QueryParams>([], form)
const uploadFileListModalRef = useRef<UploadFileListModalRef>(null)
const variableConfigModalRef = useRef<VariableConfigModalRef>(null)
const [variables, setVariables] = useState<Variable[]>([]) // 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<string, HistoryItem[]> => {
@@ -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<string, any> = {}
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`)}
</ButtonCheckbox>
</Form.Item>
{variables.length > 0 && (
<Form.Item name="variables" className="rb:mb-0!">
<div
className={clsx("rb:flex rb:items-center rb:border rb:rounded-lg rb:px-2 rb:text-[12px] rb:h-6 rb:cursor-pointer rb:hover:bg-[#F0F3F8] rb:text-[#212332]", {
'rb:border-[#FF5D34] rb:text-[#FF5D34]': isNeedVariableConfig,
'rb:border-[#DFE4ED]': !isNeedVariableConfig,
})}
onClick={handleEditVariables}
>
<SettingOutlined className="rb:mr-1" />
{t(`memoryConversation.variableConfig`)}
</div>
</Form.Item>
)}
</Flex>
{/* <Flex align="center">
<AudioRecorder onRecordingComplete={handleRecordingComplete} />
@@ -399,6 +469,11 @@ const Conversation: FC = () => {
ref={uploadFileListModalRef}
refresh={addFileList}
/>
<VariableConfigModal
ref={variableConfigModalRef}
refresh={handleSave}
variables={variables}
/>
</Flex>
)
}

View File

@@ -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<string, any>;
}
export interface UploadFileListModalRef {

View File

@@ -71,20 +71,20 @@ const VariableConfigModal = forwardRef<VariableConfigModalRef, VariableEditModal
<Form.Item
key={name}
name={[name, 'value']}
label={field.type === 'boolean' ? undefined : `${field.name}·${field.description}`}
label={field.type === 'boolean' ? undefined : `${field.name}·${field.display_name || field.description}`}
valuePropName={field.type === 'boolean' ? 'checked' : 'value'}
rules={[
{ required: field.required, message: field.type === 'boolean' ? t('common.pleaseSelect') : t('common.pleaseEnter') },
]}
>
{
field.type === 'string' && <Input placeholder={t('common.pleaseEnter')} />
(field.type === 'string' || field.type === 'text') && <Input placeholder={t('common.pleaseEnter')} />
}
{
field.type === 'number' && <InputNumber placeholder={t('common.pleaseEnter')} style={{ width: '100%' }} onChange={(value) => form.setFieldValue(['variables', name, 'value'], value)} />
}
{
field.type === 'boolean' && <Checkbox>{`${field.name}·${field.description}`}</Checkbox>
field.type === 'boolean' && <Checkbox>{`${field.name}·${field.display_name || field.description}`}</Checkbox>
}
</Form.Item>
)

View File

@@ -1,5 +1,6 @@
export interface Variable {
name: string;
display_name?: string;
type: string;
required: boolean;
description: string;