fix(web): app chat

This commit is contained in:
zhaoying
2026-03-27 14:28:51 +08:00
parent 4f69224cfd
commit 3da6331515
6 changed files with 99 additions and 72 deletions

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2025-12-10 16:46:17 * @Date: 2025-12-10 16:46:17
* @Last Modified by: ZhaoYing * @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 { type FC, useRef, useEffect, useState } from 'react'
import clsx from 'clsx' import clsx from 'clsx'
@@ -194,7 +194,10 @@ const ChatContent: FC<ChatContentProps> = ({
key={idx} key={idx}
size="small" size="small"
className="rb:text-[12px]!" 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}</Button> >{citation.file_name}</Button>
))} ))}
</div>} </div>}

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:29:21 * @Date: 2026-02-03 16:29:21
* @Last Modified by: ZhaoYing * @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 { useEffect, useRef, useState, forwardRef, useImperativeHandle, useMemo } from 'react';
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@@ -44,6 +44,14 @@ import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
import DescWrapper from '@/components/FormItem/DescWrapper' import DescWrapper from '@/components/FormItem/DescWrapper'
import FeaturesConfig from './components/FeaturesConfig' import FeaturesConfig from './components/FeaturesConfig'
import { getListLogoUrl } from '@/views/ModelManagement/utils'; 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 * Agent configuration component
@@ -130,7 +138,6 @@ const Agent = forwardRef<AgentRef, { onFeaturesLoad?: (features: FeaturesConfigF
* @param type - Source type (model or chat) * @param type - Source type (model or chat)
*/ */
const refresh = (vo: ModelConfig, type: Source) => { const refresh = (vo: ModelConfig, type: Source) => {
const opening_statement = form.getFieldValue(['features', 'opening_statement'])
if (type === 'model') { if (type === 'model') {
const { default_model_config_id, capability, ...rest } = vo const { default_model_config_id, capability, ...rest } = vo
if (default_model_config_id !== values.default_model_config_id) { if (default_model_config_id !== values.default_model_config_id) {
@@ -151,16 +158,10 @@ const Agent = forwardRef<AgentRef, { onFeaturesLoad?: (features: FeaturesConfigF
if (default_model_config_id === values?.default_model_config_id) { if (default_model_config_id === values?.default_model_config_id) {
const label = defaultModel?.id === default_model_config_id && defaultModel?.name ? defaultModel.name : vo.label || '' const label = defaultModel?.id === default_model_config_id && defaultModel?.name ? defaultModel.name : vo.label || ''
setChatList([{ setChatList([{
label: defaultModel?.id === default_model_config_id && defaultModel?.name ? defaultModel.name : vo.label || '', label: label,
model_config_id: default_model_config_id || '', model_config_id: default_model_config_id || '',
model_parameters: {...rest}, model_parameters: {...rest},
list: label !== '' ? [{ list: []
role: 'assistant',
content: opening_statement?.statement,
meta_data: {
suggested_questions: opening_statement?.suggested_questions || []
}
}] : []
}]) }])
} }
} else if (type === 'chat') { } else if (type === 'chat') {
@@ -283,18 +284,11 @@ const Agent = forwardRef<AgentRef, { onFeaturesLoad?: (features: FeaturesConfigF
if (values?.default_model_config_id && modelList.length > 0) { if (values?.default_model_config_id && modelList.length > 0) {
const filterValue = modelList.find(item => item.id === values.default_model_config_id) const filterValue = modelList.find(item => item.id === values.default_model_config_id)
setDefaultModel(filterValue as Model | null) setDefaultModel(filterValue as Model | null)
const opening_statement = form.getFieldValue(['features', 'opening_statement'])
setChatList([{ setChatList([{
label: filterValue?.name || '', label: filterValue?.name || '',
model_config_id: filterValue?.id || '', model_config_id: filterValue?.id || '',
model_parameters: {...(filterValue?.config || {})} as unknown as ModelConfig, model_parameters: {...(filterValue?.config || {})} as unknown as ModelConfig,
list: filterValue?.name ? [{ list: []
role: 'assistant',
content: opening_statement?.statement,
meta_data: {
suggested_questions: opening_statement?.suggested_questions || []
}
}] : []
}]) }])
form.setFieldValue('capability', filterValue?.capability) form.setFieldValue('capability', filterValue?.capability)
} }
@@ -346,28 +340,13 @@ const Agent = forwardRef<AgentRef, { onFeaturesLoad?: (features: FeaturesConfigF
const handleOpenVariableConfig = () => { const handleOpenVariableConfig = () => {
chatVariableConfigModalRef.current?.handleOpen(chatVariables) chatVariableConfigModalRef.current?.handleOpen(chatVariables)
} }
/** /**
* Save chat variable configuration * Save chat variable configuration
* @param values - Variable values * @param values - Variable values
*/ */
const handleSaveChatVariable = (variables: Variable[]) => { const handleSaveChatVariable = (variables: Variable[]) => {
setChatVariables(variables) 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(() => { useEffect(() => {
setChatVariables(values?.variables || []) setChatVariables(values?.variables || [])
@@ -375,34 +354,46 @@ const Agent = forwardRef<AgentRef, { onFeaturesLoad?: (features: FeaturesConfigF
const handleSaveFeaturesConfig = (value: FeaturesConfigForm) => { const handleSaveFeaturesConfig = (value: FeaturesConfigForm) => {
form.setFieldValue('features', value) 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(() => { const modelLogo = useMemo(() => {
return defaultModel?.name && getListLogoUrl(defaultModel.provider, defaultModel.logo as string) return defaultModel?.name && getListLogoUrl(defaultModel.provider, defaultModel.logo as string)
}, [defaultModel]) }, [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) console.log('agent values', values)
return ( return (

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:27:56 * @Date: 2026-02-03 16:27:56
* @Last Modified by: ZhaoYing * @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 * Copy Application Modal
@@ -74,16 +74,16 @@ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigMod
} }
const formatFileTypeOptions = (fu: FeaturesConfigForm['file_upload']) => { const formatFileTypeOptions = (fu: FeaturesConfigForm['file_upload']) => {
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) 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 }) 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 }) 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 }) options.push({ type: 'video', enabled: fu.video_enabled, maxSize: fu.video_max_size_mb })
} }
return options.filter(item => item.enabled) return options.filter(item => item.enabled)
@@ -201,6 +201,7 @@ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigMod
ref={fileUploadSettingModalRef} ref={fileUploadSettingModalRef}
onSave={handleSaveSettings} onSave={handleSaveSettings}
capability={capability} capability={capability}
source={source}
/> />
<OpenStatementSettingModal <OpenStatementSettingModal
ref={openStatementSettingModalRef} ref={openStatementSettingModalRef}

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-03-05 * @Date: 2026-03-05
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-24 11:00:14 * @Last Modified time: 2026-03-27 14:02:40
*/ */
import { forwardRef, useImperativeHandle, useState, useMemo } from 'react'; import { forwardRef, useImperativeHandle, useState, useMemo } from 'react';
import { Form, InputNumber, Flex, Switch, Row, Col, Radio } from 'antd'; import { Form, InputNumber, Flex, Switch, Row, Col, Radio } from 'antd';
@@ -12,6 +12,7 @@ import clsx from 'clsx';
import RbModal from '@/components/RbModal'; import RbModal from '@/components/RbModal';
import type { FeaturesConfigForm } from '../../types' import type { FeaturesConfigForm } from '../../types'
import type { Capability } from '@/views/ModelManagement/types' import type { Capability } from '@/views/ModelManagement/types'
import type { Application } from '@/views/ApplicationManagement/types';
type FileUpload = Omit<FeaturesConfigForm['file_upload'], 'settings'> type FileUpload = Omit<FeaturesConfigForm['file_upload'], 'settings'>
@@ -23,6 +24,7 @@ interface FileUploadSettingModalRef {
interface FileUploadSettingModalProps { interface FileUploadSettingModalProps {
onSave: (values: FileUpload) => void; onSave: (values: FileUpload) => void;
capability?: Capability[]; capability?: Capability[];
source?: Application['type']
} }
const documentType = { const documentType = {
type: 'document', type: 'document',
@@ -108,6 +110,7 @@ const defaultValues: FileUpload = {
const FileUploadSettingModal = forwardRef<FileUploadSettingModalRef, FileUploadSettingModalProps>(({ const FileUploadSettingModal = forwardRef<FileUploadSettingModalRef, FileUploadSettingModalProps>(({
onSave, onSave,
capability, capability,
source,
}, ref) => { }, ref) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@@ -149,6 +152,14 @@ const FileUploadSettingModal = forwardRef<FileUploadSettingModalRef, FileUploadS
})); }));
const fileTypeOptions = useMemo(() => { const fileTypeOptions = useMemo(() => {
if (source === 'workflow') {
return [
documentType,
imageType,
audioType,
videoType,
]
}
let options = [documentType] let options = [documentType]
if (!capability) return options if (!capability) return options
if (capability.includes('vision')) options = [...options, imageType] if (capability.includes('vision')) options = [...options, imageType]

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:58:03 * @Date: 2026-02-03 16:58:03
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-26 13:35:42 * @Last Modified time: 2026-03-27 14:28:19
*/ */
/** /**
* Conversation Page * Conversation Page
@@ -30,8 +30,10 @@ import { type SSEMessage } from '@/utils/stream'
import { shareFileUploadUrlWithoutApiPrefix } from '@/api/fileStorage' import { shareFileUploadUrlWithoutApiPrefix } from '@/api/fileStorage'
import ChatToolbar, { type ChatToolbarRef } from '@/components/Chat/ChatToolbar' import ChatToolbar, { type ChatToolbarRef } from '@/components/Chat/ChatToolbar'
import type { Variable } from '@/views/Workflow/components/Properties/VariableList/types' 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 type { FeaturesConfigForm } from '@/views/ApplicationConfig/types';
import { getFileStatusById } from '@/api/fileStorage'; import { getFileStatusById } from '@/api/fileStorage';
import { replaceVariables } from '@/views/ApplicationConfig/Agent'
const Conversation: FC = () => { const Conversation: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
@@ -84,11 +86,11 @@ const Conversation: FC = () => {
if (shareToken && token) { if (shareToken && token) {
getExperienceConfig(token) getExperienceConfig(token)
.then(res => { .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 || []) toolbarRef.current?.setVariables(response.variables || [])
setConfig(response) setConfig(response)
setFeatures(response.features) setFeatures(response.features)
setIsHasMemory((response.app_type === 'workflow' && response.memory) || (response.app_type !== 'workflow')) setIsHasMemory((response.app_type === 'workflow' && response.memory) || response.memory)
}) })
} else { } else {
setChatList([]) 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) console.log('chatList', chatList)
return ( return (
@@ -460,7 +473,8 @@ const Conversation: FC = () => {
} }
}} }}
rightExtra={ rightExtra={
<Flex align="center" justify="end" gap={8}> (features?.web_search?.enabled || isHasMemory)
? <Flex align="center" justify="end" gap={8}>
{features?.web_search?.enabled && {features?.web_search?.enabled &&
<Tooltip title={t('memoryConversation.web_search')}> <Tooltip title={t('memoryConversation.web_search')}>
<Flex justify="center" align="center" <Flex justify="center" align="center"
@@ -496,7 +510,9 @@ const Conversation: FC = () => {
</Tooltip> </Tooltip>
} }
</Flex> </Flex>
: undefined
} }
onVariablesChange={handleChangeVariables}
/> />
</Chat> </Chat>
</div> </div>

View File

@@ -7,7 +7,7 @@
* @LastEditTime: 2025-12-19 20:19:59 * @LastEditTime: 2025-12-19 20:19:59
*/ */
import { useEffect, useState, useRef, type FC } from 'react'; 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 { useTranslation } from 'react-i18next';
import { useBreadcrumbManager, type BreadcrumbPath } from '@/hooks/useBreadcrumbManager'; import { useBreadcrumbManager, type BreadcrumbPath } from '@/hooks/useBreadcrumbManager';
import { Button, Spin, message, Switch } from 'antd'; import { Button, Spin, message, Switch } from 'antd';
@@ -29,11 +29,16 @@ const DocumentDetails: FC = () => {
const { updateBreadcrumbs } = useBreadcrumbManager({ const { updateBreadcrumbs } = useBreadcrumbManager({
breadcrumbType: 'detail' breadcrumbType: 'detail'
}); });
const [searchParams] = useSearchParams();
const { const {
documentId, documentId,
parentId: locationParentId, parentId: locationParentId,
breadcrumbPath breadcrumbPath
} = (location.state || {}) as { } = ({
documentId: searchParams.get('documentId') ?? undefined,
parentId: searchParams.get('parentId') ?? undefined,
...(location.state || {})
}) as {
documentId?: string; documentId?: string;
parentId?: string; parentId?: string;
breadcrumbPath?: BreadcrumbPath; breadcrumbPath?: BreadcrumbPath;