From 3da63315154cf99ac7b76df16370466f57dc92e0 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Fri, 27 Mar 2026 14:28:51 +0800 Subject: [PATCH] fix(web): app chat --- web/src/components/Chat/ChatContent.tsx | 7 +- web/src/views/ApplicationConfig/Agent.tsx | 107 ++++++++---------- .../FeaturesConfig/FeaturesConfigModal.tsx | 11 +- .../FeaturesConfig/FileUploadSettingModal.tsx | 13 ++- web/src/views/Conversation/index.tsx | 24 +++- .../[knowledgeBaseId]/DocumentDetails.tsx | 9 +- 6 files changed, 99 insertions(+), 72 deletions(-) diff --git a/web/src/components/Chat/ChatContent.tsx b/web/src/components/Chat/ChatContent.tsx index 34472a2e..ddb25838 100644 --- a/web/src/components/Chat/ChatContent.tsx +++ b/web/src/components/Chat/ChatContent.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2025-12-10 16:46:17 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-26 13:32:29 + * @Last Modified time: 2026-03-27 14:17:38 */ import { type FC, useRef, useEffect, useState } from 'react' import clsx from 'clsx' @@ -194,7 +194,10 @@ const ChatContent: FC = ({ key={idx} size="small" className="rb:text-[12px]!" - onClick={() => window.open(`/knowledge/${citation.knowledge_id}/document/${citation.document_id}`, '_blank')} + onClick={() => { + const params = new URLSearchParams({ documentId: citation.document_id, parentId: citation.knowledge_id }); + window.open(`/#/knowledge-base/${citation.knowledge_id}/DocumentDetails?${params}`, '_blank'); + }} >{citation.file_name} ))} } diff --git a/web/src/views/ApplicationConfig/Agent.tsx b/web/src/views/ApplicationConfig/Agent.tsx index 2a8796ee..9aaac375 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-27 11:39:59 + * @Last Modified time: 2026-03-27 13:46:18 */ import { useEffect, useRef, useState, forwardRef, useImperativeHandle, useMemo } from 'react'; import { useTranslation } from 'react-i18next' @@ -44,6 +44,14 @@ import SwitchFormItem from '@/components/FormItem/SwitchFormItem' import DescWrapper from '@/components/FormItem/DescWrapper' import FeaturesConfig from './components/FeaturesConfig' import { getListLogoUrl } from '@/views/ModelManagement/utils'; +import type { ChatItem } from '@/components/Chat/types' + +export const replaceVariables = (statement: string, variables: Variable[]) => { + return statement.replace(/\{\{([^}]+)\}\}/g, (match, name) => { + const v = variables.find(item => item.name === name) + return v?.value != null && v.value !== '' ? String(v.value) : match + }) +} /** * Agent configuration component @@ -130,7 +138,6 @@ const Agent = forwardRef { - const opening_statement = form.getFieldValue(['features', 'opening_statement']) if (type === 'model') { const { default_model_config_id, capability, ...rest } = vo if (default_model_config_id !== values.default_model_config_id) { @@ -151,16 +158,10 @@ const Agent = forwardRef 0) { const filterValue = modelList.find(item => item.id === values.default_model_config_id) setDefaultModel(filterValue as Model | null) - const opening_statement = form.getFieldValue(['features', 'opening_statement']) setChatList([{ label: filterValue?.name || '', model_config_id: filterValue?.id || '', model_parameters: {...(filterValue?.config || {})} as unknown as ModelConfig, - list: filterValue?.name ? [{ - role: 'assistant', - content: opening_statement?.statement, - meta_data: { - suggested_questions: opening_statement?.suggested_questions || [] - } - }] : [] + list: [] }]) form.setFieldValue('capability', filterValue?.capability) } @@ -346,28 +340,13 @@ const Agent = forwardRef { chatVariableConfigModalRef.current?.handleOpen(chatVariables) } + /** * Save chat variable configuration * @param values - Variable values */ const handleSaveChatVariable = (variables: Variable[]) => { setChatVariables(variables) - const opening_statement = form.getFieldValue(['features', 'opening_statement']) - - if (opening_statement?.statement && opening_statement?.statement.trim() !== '') { - const statement = opening_statement.statement as string - const replacedContent = statement.replace(/\{\{([^}]+)\}\}/g, (match, name) => { - const v = variables.find(item => item.name === name) - return v?.value != null && v.value !== '' ? String(v.value) : match - }) - setChatList(prev => prev.map(item => { - const list = [...(item.list || [])] - if (list.length > 0 && list[0].role === 'assistant') { - list[0] = { ...list[0], content: replacedContent } - } - return { ...item, list } - })) - } } useEffect(() => { setChatVariables(values?.variables || []) @@ -375,34 +354,46 @@ const Agent = forwardRef { form.setFieldValue('features', value) - - if (value?.opening_statement?.statement && value?.opening_statement?.statement.trim() !== '') { - setChatList(prev => (prev.map(item => { - const firstMsg = item.list?.[0] - - if (firstMsg?.role === 'assistant') { - firstMsg.meta_data = { - suggested_questions: value.opening_statement?.suggested_questions || [] - } - return item - } else { - return { - ...item, - list: [{ - role: 'assistant', - content: value.opening_statement?.statement, - meta_data: { - suggested_questions: value.opening_statement?.suggested_questions || [] - } - }, ...(item.list || [])] - } - } - }))) - } } const modelLogo = useMemo(() => { return defaultModel?.name && getListLogoUrl(defaultModel.provider, defaultModel.logo as string) }, [defaultModel]) + + useEffect(() => { + const opening_statement = form.getFieldValue(['features', 'opening_statement']) + console.log('opening_statement', opening_statement, defaultModel, chatList) + + if (opening_statement?.enabled && opening_statement?.statement && opening_statement?.statement.trim() !== '') { + const assistantMsg: ChatItem = { + role: 'assistant', + content: replaceVariables(opening_statement.statement, chatVariables), + meta_data: { + suggested_questions: opening_statement?.suggested_questions + } + } + setChatList(prev => { + if (prev.length === 0 && !defaultModel) return prev + if (defaultModel && prev.length === 1) { + return [{ + label: defaultModel.name, + model_config_id: defaultModel.id, + model_parameters: defaultModel.config as unknown as ModelConfig, + list: [assistantMsg] + }] + } + + return prev.map(vo => { + if (vo.list?.length === 0) { + return { ...vo, list: [assistantMsg] } + } else if (vo.list && vo.list[0].role === 'assistant') { + return { ...vo, list: [assistantMsg, ...vo.list.slice(1)] } + } else { + return { ...vo, list: [assistantMsg, ...(vo.list || [])] } + } + }) + }) + } + }, [defaultModel, chatList.length, form.getFieldValue(['features', 'opening_statement']), chatVariables]) console.log('agent values', values) return ( diff --git a/web/src/views/ApplicationConfig/components/FeaturesConfig/FeaturesConfigModal.tsx b/web/src/views/ApplicationConfig/components/FeaturesConfig/FeaturesConfigModal.tsx index e8761662..0100f054 100644 --- a/web/src/views/ApplicationConfig/components/FeaturesConfig/FeaturesConfigModal.tsx +++ b/web/src/views/ApplicationConfig/components/FeaturesConfig/FeaturesConfigModal.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:27:56 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-26 14:03:01 + * @Last Modified time: 2026-03-27 14:24:47 */ /** * Copy Application Modal @@ -74,16 +74,16 @@ const FeaturesConfigModal = forwardRef { - let options = [{ type: 'document', enabled: fu.document_enabled, maxSize: fu.document_max_size_mb }] + let options = fu.document_enabled ? [{ type: 'document', enabled: fu.document_enabled, maxSize: fu.document_max_size_mb }] : [] if (!capability) return options - if (capability.includes('vision')) { + if ((capability.includes('vision') || source === 'workflow') && fu.image_enabled) { options.push({ type: 'image', enabled: fu.image_enabled, maxSize: fu.image_max_size_mb }) } - if (capability.includes('audio')) { + if ((capability.includes('audio') || source === 'workflow') && fu.audio_enabled) { options.push({ type: 'audio', enabled: fu.audio_enabled, maxSize: fu.audio_max_size_mb }) } - if (capability.includes('video')) { + if ((capability.includes('video') || source === 'workflow') && fu.video_enabled) { options.push({ type: 'video', enabled: fu.video_enabled, maxSize: fu.video_max_size_mb }) } return options.filter(item => item.enabled) @@ -201,6 +201,7 @@ const FeaturesConfigModal = forwardRef @@ -23,6 +24,7 @@ interface FileUploadSettingModalRef { interface FileUploadSettingModalProps { onSave: (values: FileUpload) => void; capability?: Capability[]; + source?: Application['type'] } const documentType = { type: 'document', @@ -108,6 +110,7 @@ const defaultValues: FileUpload = { const FileUploadSettingModal = forwardRef(({ onSave, capability, + source, }, ref) => { const { t } = useTranslation(); const [visible, setVisible] = useState(false); @@ -149,6 +152,14 @@ const FileUploadSettingModal = forwardRef { + if (source === 'workflow') { + return [ + documentType, + imageType, + audioType, + videoType, + ] + } let options = [documentType] if (!capability) return options if (capability.includes('vision')) options = [...options, imageType] diff --git a/web/src/views/Conversation/index.tsx b/web/src/views/Conversation/index.tsx index 3e64dfe1..80394317 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-03-26 13:35:42 + * @Last Modified time: 2026-03-27 14:28:19 */ /** * Conversation Page @@ -30,8 +30,10 @@ import { type SSEMessage } from '@/utils/stream' import { shareFileUploadUrlWithoutApiPrefix } from '@/api/fileStorage' import ChatToolbar, { type ChatToolbarRef } from '@/components/Chat/ChatToolbar' import type { Variable } from '@/views/Workflow/components/Properties/VariableList/types' +import type { Variable as AppVariable } from '@/views/ApplicationConfig/components/VariableList/types' import type { FeaturesConfigForm } from '@/views/ApplicationConfig/types'; import { getFileStatusById } from '@/api/fileStorage'; +import { replaceVariables } from '@/views/ApplicationConfig/Agent' const Conversation: FC = () => { const { t } = useTranslation() @@ -84,11 +86,11 @@ const Conversation: FC = () => { if (shareToken && token) { getExperienceConfig(token) .then(res => { - const response = res as { variables: Variable[]; features: FeaturesConfigForm; app_type: string; memory?: boolean; } + const response = res as { variables: Variable[]; features: FeaturesConfigForm; app_type: string; memory: boolean; } toolbarRef.current?.setVariables(response.variables || []) setConfig(response) setFeatures(response.features) - setIsHasMemory((response.app_type === 'workflow' && response.memory) || (response.app_type !== 'workflow')) + setIsHasMemory((response.app_type === 'workflow' && response.memory) || response.memory) }) } else { setChatList([]) @@ -375,6 +377,17 @@ const Conversation: FC = () => { }) } + const handleChangeVariables = (variables: Variable[]) => { + setChatList(prev => { + const firstMsg = prev[0] + console.log('firstMsg', firstMsg) + if (firstMsg && firstMsg.role === 'assistant' && firstMsg.content && features?.opening_statement.enabled && features?.opening_statement.statement && variables.length > 0) { + firstMsg.content = replaceVariables(features?.opening_statement.statement, variables as unknown as AppVariable[]) + } + return [firstMsg, ...prev.slice(1)] + }) + } + console.log('chatList', chatList) return ( @@ -460,7 +473,8 @@ const Conversation: FC = () => { } }} rightExtra={ - + (features?.web_search?.enabled || isHasMemory) + ? {features?.web_search?.enabled && { } + : undefined } + onVariablesChange={handleChangeVariables} /> diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx index 8859a8c8..f3716cd3 100644 --- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx +++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx @@ -7,7 +7,7 @@ * @LastEditTime: 2025-12-19 20:19:59 */ import { useEffect, useState, useRef, type FC } from 'react'; -import { useNavigate, useParams, useLocation } from 'react-router-dom'; +import { useNavigate, useParams, useLocation, useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useBreadcrumbManager, type BreadcrumbPath } from '@/hooks/useBreadcrumbManager'; import { Button, Spin, message, Switch } from 'antd'; @@ -29,11 +29,16 @@ const DocumentDetails: FC = () => { const { updateBreadcrumbs } = useBreadcrumbManager({ breadcrumbType: 'detail' }); + const [searchParams] = useSearchParams(); const { documentId, parentId: locationParentId, breadcrumbPath - } = (location.state || {}) as { + } = ({ + documentId: searchParams.get('documentId') ?? undefined, + parentId: searchParams.get('parentId') ?? undefined, + ...(location.state || {}) + }) as { documentId?: string; parentId?: string; breadcrumbPath?: BreadcrumbPath;