feat(web): chat add variables
This commit is contained in:
@@ -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}`)}`
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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: '添加节点',
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export interface Variable {
|
||||
name: string;
|
||||
display_name?: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
description: string;
|
||||
|
||||
Reference in New Issue
Block a user