fix(web): app features

This commit is contained in:
zhaoying
2026-03-18 16:10:20 +08:00
parent 4bb2ccfba7
commit 0e9672df80
15 changed files with 111 additions and 78 deletions

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-03-17 14:22:25 * @Date: 2026-03-17 14:22:25
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-17 18:39:49 * @Last Modified time: 2026-03-18 15:55:13
*/ */
// Toolbar component for chat input area, supporting file upload, audio recording, and variable configuration // Toolbar component for chat input area, supporting file upload, audio recording, and variable configuration
import { useRef, forwardRef, useImperativeHandle, type ReactNode, useEffect } from 'react' import { useRef, forwardRef, useImperativeHandle, type ReactNode, useEffect } from 'react'
@@ -120,7 +120,10 @@ const ChatToolbar = forwardRef<ChatToolbarRef, ChatToolbarProps>(({
// Build dropdown menu items based on allowed transfer methods // Build dropdown menu items based on allowed transfer methods
const fileMenus: MenuProps['items'] = [] const fileMenus: MenuProps['items'] = []
if (file_upload?.allowed_transfer_methods?.includes('remote_url')) { const enabledTypes = ['image', 'document', 'video', 'audio'].filter(
type => file_upload?.[`${type}_enabled` as keyof FeaturesConfigForm['file_upload']]
)
if (file_upload?.allowed_transfer_methods?.includes('remote_url') && enabledTypes.length > 0) {
fileMenus.push({ fileMenus.push({
key: 'url', key: 'url',
label: t('memoryConversation.addRemoteFile'), label: t('memoryConversation.addRemoteFile'),
@@ -133,9 +136,6 @@ const ChatToolbar = forwardRef<ChatToolbarRef, ChatToolbarProps>(({
} }
}) })
} }
const enabledTypes = ['image', 'document', 'video', 'audio'].filter(
type => file_upload?.[`${type}_enabled` as keyof FeaturesConfigForm['file_upload']]
)
if (file_upload?.allowed_transfer_methods?.includes('local_file') && enabledTypes.length > 0) { if (file_upload?.allowed_transfer_methods?.includes('local_file') && enabledTypes.length > 0) {
fileMenus.push({ fileMenus.push({
key: 'upload', key: 'upload',
@@ -155,7 +155,7 @@ const ChatToolbar = forwardRef<ChatToolbarRef, ChatToolbarProps>(({
<Form form={form} initialValues={{ files: [], variables: [] }}> <Form form={form} initialValues={{ files: [], variables: [] }}>
<Flex justify="space-between" className="rb:flex-1"> <Flex justify="space-between" className="rb:flex-1">
<Flex gap={8} align="center"> <Flex gap={8} align="center">
<Form.Item name="files" noStyle hidden={!file_upload?.enabled}> <Form.Item name="files" noStyle hidden={!file_upload?.enabled || fileMenus.length === 0}>
<Dropdown menu={{ items: fileMenus }}> <Dropdown menu={{ items: fileMenus }}>
<div className="rb:size-6 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/link.svg')] rb:hover:bg-[url('@/assets/images/conversation/link_hover.svg')]" /> <div className="rb:size-6 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/link.svg')] rb:hover:bg-[url('@/assets/images/conversation/link_hover.svg')]" />
</Dropdown> </Dropdown>

View File

@@ -776,7 +776,7 @@ export const zh = {
singleMaxSize: '单文件最大大小', singleMaxSize: '单文件最大大小',
unix: '个', unix: '个',
text_to_speech: '文字转语音', text_to_speech: '文字转语音',
text_to_speech_desc: '文本可以转换成语', text_to_speech_desc: '文本可以转换成语',
apps: '我的应用', apps: '我的应用',
sharing: '共享', sharing: '共享',

View File

@@ -129,7 +129,7 @@ const SelectWrapper: FC<{ title: string, desc: string, name: string | string[],
* Agent configuration component * Agent configuration component
* Manages single agent configuration including prompts, knowledge, memory, variables, and tools * Manages single agent configuration including prompts, knowledge, memory, variables, and tools
*/ */
const Agent = forwardRef<AgentRef>((_props, ref) => { const Agent = forwardRef<AgentRef, { onFeaturesLoad?: (features: FeaturesConfigForm | undefined) => void }>(({ onFeaturesLoad }, ref) => {
const { t } = useTranslation() const { t } = useTranslation()
const { id } = useParams(); const { id } = useParams();
const { message } = App.useApp() const { message } = App.useApp()
@@ -200,6 +200,7 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
...response, ...response,
tools: allTools tools: allTools
}) })
onFeaturesLoad?.(response.features)
}).finally(() => { }).finally(() => {
setLoading(false) setLoading(false)
}) })

View File

@@ -38,7 +38,7 @@ const MAX_LENGTH = 5;
* Multi-agent cluster configuration component * Multi-agent cluster configuration component
* Manages multi-agent orchestration, sub-agents, and collaboration modes * Manages multi-agent orchestration, sub-agents, and collaboration modes
*/ */
const Cluster = forwardRef<ClusterRef>((_props, ref) => { const Cluster = forwardRef<ClusterRef, { onFeaturesLoad?: (features: FeaturesConfigForm | undefined) => void }>(({ onFeaturesLoad }, ref) => {
const { t } = useTranslation() const { t } = useTranslation()
const { message } = App.useApp() const { message } = App.useApp()
const [form] = Form.useForm() const [form] = Form.useForm()
@@ -131,6 +131,7 @@ const Cluster = forwardRef<ClusterRef>((_props, ref) => {
} else { } else {
setSubAgents(sub_agents) setSubAgents(sub_agents)
} }
onFeaturesLoad?.(response.features)
}) })
} }
/** /**

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-03-13 17:19:13 * @Date: 2026-03-13 17:19:13
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-18 10:47:17 * @Last Modified time: 2026-03-18 16:03:46
*/ */
import { forwardRef, useImperativeHandle, useState } from 'react'; import { forwardRef, useImperativeHandle, useState } from 'react';
import { Checkbox, App, Form } from 'antd'; import { Checkbox, App, Form } from 'antd';
@@ -78,7 +78,7 @@ const AppSharingModal = forwardRef<AppSharingModalRef, AppSharingModalProps>(({
*/ */
const handleToggle = (id: string, isShared: boolean) => { const handleToggle = (id: string, isShared: boolean) => {
if (isShared) return; if (isShared) return;
const prev = form.getFieldValue('target_workspace_ids') as string[] ?? []; const prev: string[] = form.getFieldValue('target_workspace_ids') ?? [];
form.setFieldValue( form.setFieldValue(
'target_workspace_ids', 'target_workspace_ids',
prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id] prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]
@@ -135,10 +135,16 @@ const AppSharingModal = forwardRef<AppSharingModalRef, AppSharingModalProps>(({
{/* Target space: scrollable list of workspaces with checkbox selection */} {/* Target space: scrollable list of workspaces with checkbox selection */}
<Form.Item <Form.Item
name="target_workspace_ids"
label={t('application.selectTargetSpace')} label={t('application.selectTargetSpace')}
rules={[{ required: true, message: t('common.pleaseSelect') }]} required
> >
<Form.Item
name="target_workspace_ids"
noStyle
rules={[{ required: true, message: t('common.pleaseSelect') }]}
>
<input type="hidden" />
</Form.Item>
<div className="rb:rounded-lg rb:border rb:border-[#EBEBEB] rb:divide-y rb:divide-[#EBEBEB] rb:max-h-50 rb:overflow-y-auto"> <div className="rb:rounded-lg rb:border rb:border-[#EBEBEB] rb:divide-y rb:divide-[#EBEBEB] rb:max-h-50 rb:overflow-y-auto">
{spaceList.map(space => { {spaceList.map(space => {
const isShared = sharedIds.includes(space.id); const isShared = sharedIds.includes(space.id);
@@ -146,12 +152,11 @@ const AppSharingModal = forwardRef<AppSharingModalRef, AppSharingModalProps>(({
<div key={space.id} className="rb:flex rb:items-center rb:gap-2 rb:px-4 rb:py-3 rb:cursor-pointer" onClick={() => handleToggle(space.id, isShared)}> <div key={space.id} className="rb:flex rb:items-center rb:gap-2 rb:px-4 rb:py-3 rb:cursor-pointer" onClick={() => handleToggle(space.id, isShared)}>
<Checkbox <Checkbox
checked={isShared || selectedIds.includes(space.id)} checked={isShared || selectedIds.includes(space.id)}
disabled={isShared} // already-shared workspaces cannot be unselected disabled={isShared}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onChange={() => handleToggle(space.id, isShared)} onChange={() => handleToggle(space.id, isShared)}
/> />
<span className="rb:flex-1 rb:text-sm">{space.name}</span> <span className="rb:flex-1 rb:text-sm">{space.name}</span>
{/* Badge shown when the app is already shared with this workspace */}
{isShared && ( {isShared && (
<span className="rb:text-xs rb:text-[#5B6167]">{t('application.alreadyShared')}</span> <span className="rb:text-xs rb:text-[#5B6167]">{t('application.alreadyShared')}</span>
)} )}

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:27:52 * @Date: 2026-02-03 16:27:52
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-16 17:04:56 * @Last Modified time: 2026-03-18 15:40:53
*/ */
import { type FC, useRef, useMemo, useCallback } from 'react'; import { type FC, useRef, useMemo, useCallback } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
@@ -61,6 +61,10 @@ interface ConfigHeaderProps {
workflowRef: React.RefObject<WorkflowRef> workflowRef: React.RefObject<WorkflowRef>
/** App component ref (Agent/Cluster/Workflow) */ /** App component ref (Agent/Cluster/Workflow) */
appRef?: React.RefObject<AgentRef | ClusterRef | WorkflowRef> appRef?: React.RefObject<AgentRef | ClusterRef | WorkflowRef>
/** Features config from parent state */
features?: FeaturesConfigForm;
/** Callback to update features in parent */
onFeaturesChange?: (value: FeaturesConfigForm) => void;
} }
/** /**
@@ -71,6 +75,8 @@ const ConfigHeader: FC<ConfigHeaderProps> = ({
application, activeTab, handleChangeTab, refresh, application, activeTab, handleChangeTab, refresh,
workflowRef, workflowRef,
appRef, appRef,
features,
onFeaturesChange,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -173,14 +179,10 @@ const ConfigHeader: FC<ConfigHeaderProps> = ({
return items return items
}, [t, handleClick, application]) }, [t, handleClick, application])
const features = useMemo(() => {
return (appRef?.current?.features || { file_type: [] }) as FeaturesConfigForm
}, [appRef])
const handleSaveFeaturesConfig = useCallback((value: FeaturesConfigForm) => { const handleSaveFeaturesConfig = useCallback((value: FeaturesConfigForm) => {
appRef?.current?.handleSaveFeaturesConfig?.(value) appRef?.current?.handleSaveFeaturesConfig?.(value)
}, [appRef]) onFeaturesChange?.(value)
}, [appRef, onFeaturesChange])
console.log('formatMenuItems', formatMenuItems)
return ( return (
<> <>
<Header className="rb:w-full rb:h-16 rb:grid rb:grid-cols-3 rb:p-[16px_16px_16px_24px]! rb:border-b rb:border-[#EAECEE] rb:leading-8"> <Header className="rb:w-full rb:h-16 rb:grid rb:grid-cols-3 rb:p-[16px_16px_16px_24px]! rb:border-b rb:border-[#EAECEE] rb:leading-8">
@@ -211,7 +213,7 @@ const ConfigHeader: FC<ConfigHeaderProps> = ({
</div> </div>
{application?.type === 'workflow' {application?.type === 'workflow'
? <div className="rb:h-8 rb:flex rb:items-center rb:justify-end rb:gap-2.5"> ? <div className="rb:h-8 rb:flex rb:items-center rb:justify-end rb:gap-2.5">
<FeaturesConfig value={features} refresh={handleSaveFeaturesConfig} /> <FeaturesConfig source={application?.type} value={features} refresh={handleSaveFeaturesConfig} />
<Button onClick={clear}>{t('workflow.clear')}</Button> <Button onClick={clear}>{t('workflow.clear')}</Button>
<Button onClick={addvariable}>{t('workflow.addvariable')}</Button> <Button onClick={addvariable}>{t('workflow.addvariable')}</Button>
<Button onClick={run}>{t('workflow.run')}</Button> <Button onClick={run}>{t('workflow.run')}</Button>

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-16 18:31:58 * @Last Modified time: 2026-03-18 15:38:14
*/ */
/** /**
* Copy Application Modal * Copy Application Modal
@@ -18,9 +18,11 @@ import type { FeaturesConfigModalRef, FeaturesConfigForm } from '../../types'
import RbModal from '@/components/RbModal' import RbModal from '@/components/RbModal'
import SwitchFormItem from '@/components/FormItem/SwitchFormItem' import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
import FileUploadSettingModal from './FileUploadSettingModal' import FileUploadSettingModal from './FileUploadSettingModal'
import type { Application } from '@/views/ApplicationManagement/types';
interface FeaturesConfigModalProps { interface FeaturesConfigModalProps {
refresh: (value: FeaturesConfigForm) => void; refresh: (value: FeaturesConfigForm) => void;
source?: Application['type'];
} }
/** /**
@@ -28,6 +30,7 @@ interface FeaturesConfigModalProps {
*/ */
const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigModalProps>(({ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigModalProps>(({
refresh, refresh,
source,
}, ref) => { }, ref) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@@ -44,6 +47,7 @@ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigMod
/** Open modal */ /** Open modal */
const handleOpen = (initValue: FeaturesConfigForm) => { const handleOpen = (initValue: FeaturesConfigForm) => {
setVisible(true); setVisible(true);
console.log('initValue', initValue)
form.setFieldsValue(initValue) form.setFieldsValue(initValue)
}; };
/** Copy application with new name */ /** Copy application with new name */
@@ -66,7 +70,6 @@ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigMod
handleClose handleClose
})); }));
console.log('settings values', values)
return ( return (
<> <>
<RbModal <RbModal
@@ -81,20 +84,22 @@ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigMod
layout="vertical" layout="vertical"
> >
<Flex vertical gap={12}> <Flex vertical gap={12}>
<div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]"> {source !== 'workflow' && <>
<SwitchFormItem <div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]">
title={t(`memoryConversation.web_search`)} <SwitchFormItem
name={['web_search', "enabled"]} title={t(`memoryConversation.web_search`)}
/> name={['web_search', "enabled"]}
</div> />
</div>
<div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]"> <div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]">
<SwitchFormItem <SwitchFormItem
title={t('application.text_to_speech')} title={t('application.text_to_speech')}
name={['text_to_speech', "enabled"]} name={['text_to_speech', "enabled"]}
desc={t('application.text_to_speech_desc')} desc={t('application.text_to_speech_desc')}
/> />
</div> </div>
</>}
<div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]"> <div className="rb:relative rb:border rb:border-[#DFE4ED] rb:p-3 rb:rounded-lg rb:bg-[#f5f7fc]">
<SwitchFormItem <SwitchFormItem

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-03-13 17:20:21 * @Date: 2026-03-13 17:20:21
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-16 18:31:43 * @Last Modified time: 2026-03-18 15:38:59
*/ */
import { type FC, useRef } from 'react'; import { type FC, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -10,6 +10,7 @@ import { Button } from 'antd';
import FeaturesConfigModal from './FeaturesConfigModal' import FeaturesConfigModal from './FeaturesConfigModal'
import type { FeaturesConfigModalRef, FeaturesConfigForm } from '../../types' import type { FeaturesConfigModalRef, FeaturesConfigForm } from '../../types'
import type { Application } from '@/views/ApplicationManagement/types';
/** Props for the FeaturesConfig component */ /** Props for the FeaturesConfig component */
interface FeaturesConfigProps { interface FeaturesConfigProps {
@@ -17,11 +18,13 @@ interface FeaturesConfigProps {
value: FeaturesConfigForm; value: FeaturesConfigForm;
/** Callback to propagate updated config back to the parent */ /** Callback to propagate updated config back to the parent */
refresh: (value: FeaturesConfigForm) => void; refresh: (value: FeaturesConfigForm) => void;
source?: Application['type'];
} }
const FeaturesConfig: FC<FeaturesConfigProps> = ({ const FeaturesConfig: FC<FeaturesConfigProps> = ({
value, value,
refresh refresh,
source
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
// Ref used to imperatively open the config modal // Ref used to imperatively open the config modal
@@ -29,6 +32,7 @@ const FeaturesConfig: FC<FeaturesConfigProps> = ({
/** Open the feature config modal pre-populated with the current values */ /** Open the feature config modal pre-populated with the current values */
const handleFeaturesConfig = () => { const handleFeaturesConfig = () => {
console.log('handleFeaturesConfig', value)
funConfigModalRef.current?.handleOpen(value) funConfigModalRef.current?.handleOpen(value)
} }
@@ -41,6 +45,7 @@ const FeaturesConfig: FC<FeaturesConfigProps> = ({
<FeaturesConfigModal <FeaturesConfigModal
ref={funConfigModalRef} ref={funConfigModalRef}
refresh={refresh} refresh={refresh}
source={source}
/> />
</> </>
) )

View File

@@ -37,6 +37,7 @@ const ApplicationConfig: React.FC = () => {
// State // State
const [application, setApplication] = useState<Application | null>(null); const [application, setApplication] = useState<Application | null>(null);
const [activeTab, setActiveTab] = useState('arrangement'); const [activeTab, setActiveTab] = useState('arrangement');
const [features, setFeatures] = useState<import('./types').FeaturesConfigForm | undefined>(undefined);
useEffect(() => { useEffect(() => {
setActiveTab(source === 'sharing' ? 'test' : 'arrangement') setActiveTab(source === 'sharing' ? 'test' : 'arrangement')
@@ -114,10 +115,12 @@ const ApplicationConfig: React.FC = () => {
refresh={getApplicationInfo} refresh={getApplicationInfo}
appRef={application?.type === 'agent' ? agentRef : application?.type === 'multi_agent' ? clusterRef : application?.type === 'workflow' ? workflowRef : undefined} appRef={application?.type === 'agent' ? agentRef : application?.type === 'multi_agent' ? clusterRef : application?.type === 'workflow' ? workflowRef : undefined}
workflowRef={workflowRef} workflowRef={workflowRef}
features={features}
onFeaturesChange={setFeatures}
/> />
{activeTab === 'arrangement' && application?.type === 'agent' && <Agent ref={agentRef} />} {activeTab === 'arrangement' && application?.type === 'agent' && <Agent ref={agentRef} onFeaturesLoad={setFeatures} />}
{activeTab === 'arrangement' && application?.type === 'multi_agent' && <Cluster ref={clusterRef} />} {activeTab === 'arrangement' && application?.type === 'multi_agent' && <Cluster ref={clusterRef} onFeaturesLoad={setFeatures} />}
{activeTab === 'arrangement' && application?.type === 'workflow' && <Workflow ref={workflowRef} />} {activeTab === 'arrangement' && application?.type === 'workflow' && <Workflow ref={workflowRef} onFeaturesLoad={setFeatures} />}
{activeTab === 'api' && <Api application={application} />} {activeTab === 'api' && <Api application={application} />}
{activeTab === 'release' && <ReleasePage data={application as Application} refresh={getApplicationInfo} />} {activeTab === 'release' && <ReleasePage data={application as Application} refresh={getApplicationInfo} />}
{activeTab === 'statistics' && <Statistics application={application} />} {activeTab === 'statistics' && <Statistics application={application} />}

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:34:12 * @Date: 2026-02-03 16:34:12
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-18 11:20:45 * @Last Modified time: 2026-03-18 15:58:36
*/ */
import React, { useState, useEffect, useMemo } from 'react'; import React, { useState, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -73,6 +73,11 @@ const MySharing: React.FC = () => {
} }
}); });
}; };
/** Navigate to application configuration page */
const handleEdit = (item: MySharedOutItem) => {
let url = `/#/application/config/${item.source_app_id}`
window.open(url);
}
return ( return (
<Flex vertical gap={12} className="rb:h-[calc(100vh-148px)]! rb:overflow-y-auto!"> <Flex vertical gap={12} className="rb:h-[calc(100vh-148px)]! rb:overflow-y-auto!">
@@ -108,7 +113,7 @@ const MySharing: React.FC = () => {
children: ( children: (
<Row gutter={[12, 12]}> <Row gutter={[12, 12]}>
{items.map(item => ( {items.map(item => (
<Col key={item.id} span={6} className="rb:bg-[#F6F6F6] rb:rounded-lg rb:py-3! rb:px-4! rb:relative"> <Col key={item.id} span={6} className="rb:bg-[#F6F6F6] rb:rounded-lg rb:py-3! rb:px-4! rb:relative" onClick={() => handleEdit(item)}>
<div <div
className="rb:absolute rb:top-3 rb:right-3 rb:cursor-pointer rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/close.svg')]" className="rb:absolute rb:top-3 rb:right-3 rb:cursor-pointer rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/close.svg')]"
onClick={() => handleCancelOne(item)} onClick={() => handleCancelOne(item)}

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-06 21:09:47 * @Date: 2026-02-06 21:09:47
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-17 10:28:04 * @Last Modified time: 2026-03-18 15:50:31
*/ */
/** /**
* Upload File List Modal Component * Upload File List Modal Component
@@ -115,7 +115,6 @@ const UploadFileListModal = forwardRef<UploadFileListModalRef, UploadFileListMod
<FormItem <FormItem
{...restField} {...restField}
name={[name, 'type']} name={[name, 'type']}
initialValue="image"
className="rb:mb-0!" className="rb:mb-0!"
> >
<Select <Select

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-17 18:30:58 * @Last Modified time: 2026-03-18 15:35:05
*/ */
/** /**
* Conversation Page * Conversation Page
@@ -60,6 +60,7 @@ const Conversation: FC = () => {
const [shareToken, setShareToken] = useState<string | null>(localStorage.getItem(`shareToken_${token}`)) const [shareToken, setShareToken] = useState<string | null>(localStorage.getItem(`shareToken_${token}`))
const [fileList, setFileList] = useState<any[]>([]) const [fileList, setFileList] = useState<any[]>([])
const [webSearch, setWebSearch] = useState(false) const [webSearch, setWebSearch] = useState(false)
const [isHasMemory, setIsHasMemory] = useState(false)
const [memory, setMemory] = useState(true) const [memory, setMemory] = useState(true)
const [features, setFeatures] = useState<FeaturesConfigForm>({} as FeaturesConfigForm) const [features, setFeatures] = useState<FeaturesConfigForm>({} as FeaturesConfigForm)
@@ -85,9 +86,10 @@ 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 } const response = res as { variables: Variable[]; features: FeaturesConfigForm; app_type: string; memory?: boolean; }
toolbarRef.current?.setVariables(response.variables || []) toolbarRef.current?.setVariables(response.variables || [])
setFeatures(response.features) setFeatures(response.features)
setIsHasMemory((response.app_type === 'workflow' && response.memory) || (response.app_type !== 'workflow'))
}) })
} else { } else {
setChatList([]) setChatList([])
@@ -379,14 +381,16 @@ const Conversation: FC = () => {
{t('memoryConversation.web_search')} {t('memoryConversation.web_search')}
</ButtonCheckbox> </ButtonCheckbox>
} }
<ButtonCheckbox {isHasMemory &&
icon={MemoryFunctionIcon} <ButtonCheckbox
checkedIcon={MemoryFunctionCheckedIcon} icon={MemoryFunctionIcon}
checked={memory} checkedIcon={MemoryFunctionCheckedIcon}
onChange={handleChangeMemory} checked={memory}
> onChange={handleChangeMemory}
{t('memoryConversation.memory')} >
</ButtonCheckbox> {t('memoryConversation.memory')}
</ButtonCheckbox>
}
</> </>
} }
/> />

View File

@@ -10,10 +10,6 @@ interface CanvasToolbarProps {
isHandMode: boolean; isHandMode: boolean;
setIsHandMode: React.Dispatch<React.SetStateAction<boolean>>; setIsHandMode: React.Dispatch<React.SetStateAction<boolean>>;
zoomLevel: number; zoomLevel: number;
canUndo: boolean;
canRedo: boolean;
onUndo: () => void;
onRedo: () => void;
addNotes: () => void; addNotes: () => void;
} }

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 15:17:48 * @Date: 2026-02-03 15:17:48
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-18 12:07:03 * @Last Modified time: 2026-03-18 16:08:17
*/ */
import { useRef, useEffect, useState } from 'react'; import { useRef, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
@@ -12,10 +12,11 @@ import { Graph, Node, MiniMap, Snapline, Clipboard, Keyboard, type Edge } from '
import { register } from '@antv/x6-react-shape'; import { register } from '@antv/x6-react-shape';
import type { PortMetadata } from '@antv/x6/lib/model/port'; import type { PortMetadata } from '@antv/x6/lib/model/port';
import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color, portTextAttrs, defaultAbsolutePortGroups, nodeWidth, unknownNode, noteNode, notesConfig } from '../constant'; import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color, portTextAttrs, defaultAbsolutePortGroups, nodeWidth, unknownNode, notesConfig } from '../constant';
import type { WorkflowConfig, NodeProperties, ChatVariable } from '../types'; import type { WorkflowConfig, NodeProperties, ChatVariable } from '../types';
import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application' import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application'
import { useUser } from '@/store/user'; import { useUser } from '@/store/user';
import type { FeaturesConfigForm } from '@/views/ApplicationConfig/types'
/** /**
* Props for useWorkflowGraph hook * Props for useWorkflowGraph hook
@@ -25,6 +26,8 @@ export interface UseWorkflowGraphProps {
containerRef: React.RefObject<HTMLDivElement>; containerRef: React.RefObject<HTMLDivElement>;
/** Reference to the minimap container element */ /** Reference to the minimap container element */
miniMapRef: React.RefObject<HTMLDivElement>; miniMapRef: React.RefObject<HTMLDivElement>;
/** Callback when features config is loaded */
onFeaturesLoad?: (features: FeaturesConfigForm | undefined) => void;
} }
/** /**
@@ -67,6 +70,7 @@ export interface UseWorkflowGraphReturn {
setChatVariables: React.Dispatch<React.SetStateAction<ChatVariable[]>>; setChatVariables: React.Dispatch<React.SetStateAction<ChatVariable[]>>;
handleAddNotes: () => void; handleAddNotes: () => void;
handleSaveFeaturesConfig: (value: FeaturesConfigForm) => void;
} }
/** /**
@@ -78,6 +82,7 @@ export interface UseWorkflowGraphReturn {
export const useWorkflowGraph = ({ export const useWorkflowGraph = ({
containerRef, containerRef,
miniMapRef, miniMapRef,
onFeaturesLoad,
}: UseWorkflowGraphProps): UseWorkflowGraphReturn => { }: UseWorkflowGraphProps): UseWorkflowGraphReturn => {
// Hooks // Hooks
const { id } = useParams(); const { id } = useParams();
@@ -115,6 +120,7 @@ export const useWorkflowGraph = ({
}) })
setChatVariables(initChatVariables) setChatVariables(initChatVariables)
setConfig({ ...rest, variables: initChatVariables }) setConfig({ ...rest, variables: initChatVariables })
onFeaturesLoad?.(rest.features)
}) })
} }
@@ -132,7 +138,7 @@ export const useWorkflowGraph = ({
if (nodes.length) { if (nodes.length) {
const nodeList = nodes.map(node => { const nodeList = nodes.map(node => {
const { id, type, name, position, config = {} } = node const { id, type, name, position, config = {} } = node
let nodeLibraryConfig = [...nodeLibrary, { nodes: [unknownNode, notesConfig] }] let nodeLibraryConfig: NodeProperties | undefined = [...nodeLibrary, { nodes: [unknownNode, notesConfig] }]
.flatMap(category => category.nodes) .flatMap(category => category.nodes)
.find(n => n.type === type) .find(n => n.type === type)
nodeLibraryConfig = JSON.parse(JSON.stringify({ config: {}, ...nodeLibraryConfig })) as NodeProperties nodeLibraryConfig = JSON.parse(JSON.stringify({ config: {}, ...nodeLibraryConfig })) as NodeProperties
@@ -994,6 +1000,9 @@ export const useWorkflowGraph = ({
}) || []; }) || [];
const edges = graphRef.current?.getEdges() || [] const edges = graphRef.current?.getEdges() || []
console.log('config', config)
const params = { const params = {
...config, ...config,
variables: chatVariables.map(v => { variables: chatVariables.map(v => {
@@ -1187,6 +1196,9 @@ export const useWorkflowGraph = ({
data: { ...cleanNodeData }, data: { ...cleanNodeData },
}); });
} }
const handleSaveFeaturesConfig = (value?: FeaturesConfigForm) => {
setConfig(prev => prev ? { ...prev, features: value } as WorkflowConfig : prev)
}
return { return {
config, config,
@@ -1206,6 +1218,7 @@ export const useWorkflowGraph = ({
handleSave, handleSave,
chatVariables, chatVariables,
setChatVariables, setChatVariables,
handleAddNotes handleAddNotes,
handleSaveFeaturesConfig
}; };
}; };

View File

@@ -6,13 +6,13 @@ import Properties from './components/Properties';
import CanvasToolbar from './components/CanvasToolbar'; import CanvasToolbar from './components/CanvasToolbar';
import PortClickHandler from './components/PortClickHandler'; import PortClickHandler from './components/PortClickHandler';
import { useWorkflowGraph } from './hooks/useWorkflowGraph'; import { useWorkflowGraph } from './hooks/useWorkflowGraph';
import type { WorkflowRef } from '@/views/ApplicationConfig/types' import type { WorkflowRef, FeaturesConfigForm } from '@/views/ApplicationConfig/types'
import Chat from './components/Chat/Chat'; import Chat from './components/Chat/Chat';
import type { ChatRef, AddChatVariableRef } from './types' import type { ChatRef, AddChatVariableRef } from './types'
import arrowIcon from '@/assets/images/workflow/arrow.png' import arrowIcon from '@/assets/images/workflow/arrow.png'
import AddChatVariable from './components/AddChatVariable'; import AddChatVariable from './components/AddChatVariable';
const Workflow = forwardRef<WorkflowRef>((_props, ref) => { const Workflow = forwardRef<WorkflowRef, { onFeaturesLoad?: (features: FeaturesConfigForm | undefined) => void }>(({ onFeaturesLoad }, ref) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const miniMapRef = useRef<HTMLDivElement>(null); const miniMapRef = useRef<HTMLDivElement>(null);
const addChatVariableRef = useRef<AddChatVariableRef>(null) const addChatVariableRef = useRef<AddChatVariableRef>(null)
@@ -25,12 +25,8 @@ const Workflow = forwardRef<WorkflowRef>((_props, ref) => {
selectedNode, selectedNode,
setSelectedNode, setSelectedNode,
zoomLevel, zoomLevel,
canUndo,
canRedo,
isHandMode, isHandMode,
setIsHandMode, setIsHandMode,
onUndo,
onRedo,
onDrop, onDrop,
blankClick, blankClick,
deleteEvent, deleteEvent,
@@ -39,8 +35,9 @@ const Workflow = forwardRef<WorkflowRef>((_props, ref) => {
handleSave, handleSave,
chatVariables, chatVariables,
setChatVariables, setChatVariables,
handleAddNotes handleAddNotes,
} = useWorkflowGraph({ containerRef, miniMapRef }); handleSaveFeaturesConfig
} = useWorkflowGraph({ containerRef, miniMapRef, onFeaturesLoad });
const onDragOver = (event: React.DragEvent) => { const onDragOver = (event: React.DragEvent) => {
event.preventDefault(); event.preventDefault();
@@ -61,7 +58,8 @@ const Workflow = forwardRef<WorkflowRef>((_props, ref) => {
graphRef, graphRef,
addVariable, addVariable,
config, config,
features: config?.features features: config?.features,
handleSaveFeaturesConfig
})) }))
return ( return (
<div className="rb:h-[calc(100vh-64px)] rb:relative"> <div className="rb:h-[calc(100vh-64px)] rb:relative">
@@ -93,10 +91,6 @@ const Workflow = forwardRef<WorkflowRef>((_props, ref) => {
isHandMode={isHandMode} isHandMode={isHandMode}
setIsHandMode={setIsHandMode} setIsHandMode={setIsHandMode}
zoomLevel={zoomLevel} zoomLevel={zoomLevel}
canUndo={canUndo}
canRedo={canRedo}
onUndo={onUndo}
onRedo={onRedo}
addNotes={handleAddNotes} addNotes={handleAddNotes}
/> />
</div> </div>