Merge branch 'develop' into feature/tool_yjp
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2025-12-10 16:46:14
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-04 18:42:49
|
||||
* @Last Modified time: 2026-03-06 13:36:20
|
||||
*/
|
||||
import { type FC, useEffect, useMemo } from 'react'
|
||||
import { Flex, Input, Form } from 'antd'
|
||||
@@ -50,13 +50,17 @@ const ChatInput: FC<ChatInputProps> = ({
|
||||
|
||||
|
||||
const handleDelete = (file: any) => {
|
||||
fileChange?.(fileList?.filter(item => item.uid !== file.uid) || [])
|
||||
fileChange?.(fileList?.filter(item => {
|
||||
return item.thumbUrl && file.thumbUrl ? item.thumbUrl !== file.thumbUrl
|
||||
: item.url && file.url ? item.url !== file.url
|
||||
: item.uid !== file.uid
|
||||
}) || [])
|
||||
}
|
||||
// Convert file object to preview URL
|
||||
const previewFileList = useMemo(() => {
|
||||
return fileList?.map(file => ({
|
||||
...file,
|
||||
url: file.url || (file.originFileObj ? URL.createObjectURL(file.originFileObj) : file.thumbUrl)
|
||||
url: file.thumbUrl || file.url || (file.originFileObj ? URL.createObjectURL(file.originFileObj) : undefined)
|
||||
})) || []
|
||||
}, [fileList])
|
||||
|
||||
@@ -72,7 +76,7 @@ const ChatInput: FC<ChatInputProps> = ({
|
||||
{previewFileList.map((file) => {
|
||||
if (file.type.includes('image')) {
|
||||
return (
|
||||
<div key={file.uid} className="rb:inline-block rb:group rb:relative rb:rounded-lg">
|
||||
<div key={file.url || file.uid} className="rb:inline-block rb:group rb:relative rb:rounded-lg">
|
||||
<img src={file.url} alt={file.name} className="rb:size-12! rb:rounded-lg rb:object-cover rb:cursor-pointer" />
|
||||
<div
|
||||
className="rb:hidden rb:group-hover:block rb:absolute rb:-right-1 rb:-top-1 rb:size-3.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/delete.svg')] rb:hover:bg-[url('@/assets/images/conversation/delete_hover.svg')]"
|
||||
@@ -83,7 +87,7 @@ const ChatInput: FC<ChatInputProps> = ({
|
||||
}
|
||||
if (file.type.includes('video')) {
|
||||
return (
|
||||
<div key={file.uid} className="rb:w-45 rb:h-16 rb:inline-block rb:group rb:relative rb:rounded-lg">
|
||||
<div key={file.url || file.uid} className="rb:w-45 rb:h-16 rb:inline-block rb:group rb:relative rb:rounded-lg">
|
||||
<video src={file.url} controls className="rb:w-45 rb:h-16 rb:rounded-lg rb:object-cover rb:cursor-pointer" />
|
||||
<div
|
||||
className="rb:hidden rb:group-hover:block rb:absolute rb:-right-1 rb:-top-1 rb:size-3.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/delete.svg')] rb:hover:bg-[url('@/assets/images/conversation/delete_hover.svg')]"
|
||||
@@ -94,7 +98,7 @@ const ChatInput: FC<ChatInputProps> = ({
|
||||
}
|
||||
if (file.type.includes('audio')) {
|
||||
return (
|
||||
<div key={file.uid} className="rb:w-45 rb:h-16 rb:inline-flex rb:items-center rb:group rb:relative rb:rounded-lg rb:bg-[#F0F3F8] rb:py-2 rb:px-2.5 rb:gap-2">
|
||||
<div key={file.url || file.uid} className="rb:w-45 rb:h-16 rb:inline-flex rb:items-center rb:group rb:relative rb:rounded-lg rb:bg-[#F0F3F8] rb:py-2 rb:px-2.5 rb:gap-2">
|
||||
<audio src={file.url} controls className="rb:w-45 rb:h-16" />
|
||||
<div
|
||||
className="rb:hidden rb:group-hover:block rb:absolute rb:-right-1 rb:-top-1 rb:size-3.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/delete.svg')] rb:hover:bg-[url('@/assets/images/conversation/delete_hover.svg')]"
|
||||
@@ -104,7 +108,7 @@ const ChatInput: FC<ChatInputProps> = ({
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div key={file.uid} className="rb:w-45 rb:text-[12px] rb:gap-2.5 rb:flex rb:items-center rb:group rb:relative rb:rounded-lg rb:bg-[#F0F3F8] rb:py-2 rb:px-2.5">
|
||||
<div key={file.url || file.uid} className="rb:w-45 rb:text-[12px] rb:gap-2.5 rb:flex rb:items-center rb:group rb:relative rb:rounded-lg rb:bg-[#F0F3F8] rb:py-2 rb:px-2.5">
|
||||
{(file.type.includes('doc') || file.type.includes('docx') || file.type.includes('word') || file.type.includes('wordprocessingml.document')) && <div
|
||||
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/conversation/word_disabled.svg')] rb:hover:bg-[url('@/assets/images/conversation/word.svg')]"
|
||||
></div>}
|
||||
|
||||
@@ -1361,6 +1361,7 @@ export const en = {
|
||||
complex: 'Compatibility Analysis',
|
||||
sureInfo: 'Information Confirmation',
|
||||
completed: 'Import Completed',
|
||||
baseInfo: 'Basic Information',
|
||||
workflowName: 'Workflow Name',
|
||||
fileName: 'File Name',
|
||||
fileSize: 'File Size',
|
||||
@@ -1572,7 +1573,7 @@ export const en = {
|
||||
intelligentSemanticPruningFunction: 'Intelligent Semantic Pruning Function',
|
||||
intelligentSemanticPruningFunctionDesc: 'Whether to activate intelligent semantic pruning (true/false).',
|
||||
intelligentSemanticPruningScene: 'Intelligent Semantic Pruning Scene',
|
||||
intelligentSemanticPruningSceneDesc: 'Select intelligent semantic pruning scene (education, online_service, outbound).',
|
||||
intelligentSemanticPruningSceneDesc: 'Semantic pruning scenarios are consistent with ontology engineering scenarios',
|
||||
intelligentSemanticPruningThreshold: 'Intelligent Semantic Pruning Threshold',
|
||||
intelligentSemanticPruningThresholdDesc: 'Set intelligent semantic pruning threshold (0-0.9).',
|
||||
reflectionEngine: 'Self-Reflexion Engine',
|
||||
|
||||
@@ -96,7 +96,7 @@ export const zh = {
|
||||
createMemorySummary: '创建记忆摘要',
|
||||
memoryManagement: '记忆管理',
|
||||
spaceManagement: '空间管理',
|
||||
memoryExtractionEngine: '记忆提取引擎',
|
||||
memoryExtractionEngine: '记忆萃取引擎',
|
||||
forgettingEngine: '遗忘引擎',
|
||||
apiKeyManagement: 'API KEY管理',
|
||||
knowledgePrivate: '详情',
|
||||
@@ -1283,7 +1283,7 @@ export const zh = {
|
||||
createConfiguration: '创建配置',
|
||||
editConfiguration: '编辑配置',
|
||||
desc: '描述',
|
||||
memoryExtractionEngine: '记忆提取引擎',
|
||||
memoryExtractionEngine: '记忆萃取引擎',
|
||||
forgottenEngine: '遗忘引擎',
|
||||
active: '活跃',
|
||||
inactive: '不活跃',
|
||||
@@ -1571,7 +1571,7 @@ export const zh = {
|
||||
intelligentSemanticPruningFunction: '智能语义修剪功能',
|
||||
intelligentSemanticPruningFunctionDesc: '是否激活智能语义修剪(true/false)。',
|
||||
intelligentSemanticPruningScene: '智能语义修剪场景',
|
||||
intelligentSemanticPruningSceneDesc: '选择智能语义修剪场景(education、online_service、outbound)。',
|
||||
intelligentSemanticPruningSceneDesc: '语义剪枝场景与本体工程场景一致',
|
||||
intelligentSemanticPruningThreshold: '智能语义修剪阈值',
|
||||
intelligentSemanticPruningThresholdDesc: '设置智能语义修剪阈值(0-0.9)。',
|
||||
reflectionEngine: '自我反思引擎',
|
||||
|
||||
@@ -356,12 +356,11 @@ export const request = {
|
||||
* Get parent domain for cookie setting
|
||||
* @returns Parent domain or IP address
|
||||
*/
|
||||
const isIp = (hostname: string) => /^\d+\.\d+\.\d+\.\d+$/.test(hostname)
|
||||
|
||||
const getParentDomain = () => {
|
||||
const hostname = window.location.hostname
|
||||
// Check if it's an IP address
|
||||
if (/^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
|
||||
return hostname
|
||||
}
|
||||
if (isIp(hostname)) return hostname
|
||||
const parts = hostname.split('.')
|
||||
return parts.length > 2 ? `.${parts.slice(-2).join('.')}` : hostname
|
||||
}
|
||||
@@ -371,7 +370,10 @@ const getParentDomain = () => {
|
||||
*/
|
||||
export const cookieUtils = {
|
||||
set: (name: string, value: string, domain = getParentDomain()) => {
|
||||
document.cookie = `${name}=${value}; domain=${domain}; path=/; secure; samesite=strict`
|
||||
const ip = isIp(window.location.hostname)
|
||||
const domainPart = ip ? '' : `; domain=${domain}`
|
||||
const securePart = window.location.protocol === 'https:' ? '; secure' : ''
|
||||
document.cookie = `${name}=${value}${domainPart}; path=/${securePart}; samesite=strict`
|
||||
},
|
||||
get: (name: string) => {
|
||||
const value = `; ${document.cookie}`
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-28 14:08:14
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-02 17:39:49
|
||||
* @Last Modified time: 2026-03-06 12:05:46
|
||||
*/
|
||||
/**
|
||||
* UploadWorkflowModal Component
|
||||
@@ -101,6 +101,7 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
|
||||
formData.append('platform', values.platform);
|
||||
formData.append('file', values.file[0]);
|
||||
|
||||
setLoading(true)
|
||||
// Call import workflow API
|
||||
importWorkflow(formData)
|
||||
.then(res => {
|
||||
@@ -114,21 +115,24 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
|
||||
} else {
|
||||
setCurrent(2);
|
||||
// Pre-fill form with file information
|
||||
const fileNameSplit = values.file[0].name.split('.')
|
||||
form.setFieldsValue({
|
||||
name: values.file[0].name.split('.')[0],
|
||||
name: fileNameSplit.slice(0, fileNameSplit.length - 1).join('.'),
|
||||
platform: values.platform,
|
||||
fileName: values.file[0].name,
|
||||
fileSize: values.file[0].size,
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
break;
|
||||
case 1: // Step 2: Error/warning display
|
||||
if (firstFormData) {
|
||||
const { file, platform } = firstFormData;
|
||||
const fileNameSplit = firstFormData.file[0].name.split('.')
|
||||
// Pre-fill form with file information
|
||||
form.setFieldsValue({
|
||||
name: file[0].name.split('.')[0],
|
||||
name: fileNameSplit.slice(0, fileNameSplit.length - 1).join('.'),
|
||||
platform: platform,
|
||||
fileName: file[0].name,
|
||||
fileSize: file[0].size,
|
||||
@@ -138,6 +142,7 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
|
||||
break;
|
||||
case 2: // Step 3: Confirm information
|
||||
if (data) {
|
||||
setLoading(true);
|
||||
// Complete import workflow
|
||||
completeImportWorkflow({
|
||||
temp_id: data.temp_id,
|
||||
@@ -148,7 +153,8 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
|
||||
const response = res as { id: string };
|
||||
setCurrent(3);
|
||||
setAppId(response.id);
|
||||
});
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -175,7 +181,9 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
|
||||
}
|
||||
|
||||
// Reset form if not going back to error/warning step
|
||||
if (newStep !== 1) {
|
||||
if (newStep === 0) {
|
||||
form.setFieldsValue(firstFormData || {})
|
||||
} else if (newStep !== 1) {
|
||||
form.resetFields();
|
||||
}
|
||||
setCurrent(newStep);
|
||||
@@ -186,14 +194,16 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
|
||||
* @param {string} type - Navigation type ('detail' or 'list')
|
||||
*/
|
||||
const handleJump = (type: string) => {
|
||||
switch(type) {
|
||||
case 'detail':
|
||||
// Open application detail page in new tab
|
||||
window.open(`/#/application/config/${appId}`, '_blank');
|
||||
break;
|
||||
}
|
||||
refresh();
|
||||
handleClose();
|
||||
refresh();
|
||||
setTimeout(() => {
|
||||
switch (type) {
|
||||
case 'detail':
|
||||
// Open application detail page in new tab
|
||||
window.open(`/#/application/config/${appId}`, '_blank');
|
||||
break;
|
||||
}
|
||||
}, 100)
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -235,7 +245,7 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
|
||||
</Button>
|
||||
];
|
||||
}
|
||||
}, [current]);
|
||||
}, [current, loading]);
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
@@ -350,7 +360,7 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
|
||||
title={t('application.importSuccess')}
|
||||
subTitle={t('application.importSuccessDesc')}
|
||||
extra={[
|
||||
<Button key="back" onClick={() => handleJump('list')}>
|
||||
<Button key="back" onClick={() => handleJump('list')}>
|
||||
{t('application.gotoList')}
|
||||
</Button>,
|
||||
<Button
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-06 21:09:42
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-05 15:09:22
|
||||
* @Last Modified time: 2026-03-06 12:20:43
|
||||
*/
|
||||
/**
|
||||
* File Upload Component
|
||||
@@ -208,6 +208,7 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
||||
newFileList.map(file => {
|
||||
const type = (file.type && transform_file_type[file.type as keyof typeof transform_file_type]) || file.type || 'document'
|
||||
file.type = type
|
||||
file.thumbUrl = file.thumbUrl || URL.createObjectURL(file.originFileObj as Blob)
|
||||
})
|
||||
setFileList(newFileList);
|
||||
if (onChange) {
|
||||
|
||||
@@ -672,9 +672,17 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
|
||||
{currentType !== 'Folder' && dynamicTypeList.map((tp) => {
|
||||
const fieldKey = typeToFieldKey(tp);
|
||||
// When tp is 'llm', merge llm and chat options
|
||||
const options = tp.toLowerCase() === 'llm' || tp.toLowerCase() === 'image2text'
|
||||
let options = tp.toLowerCase() === 'llm' || tp.toLowerCase() === 'image2text'
|
||||
? [...(modelOptionsByType['llm'] || []), ...(modelOptionsByType['chat'] || [])]
|
||||
: modelOptionsByType[tp] || [];
|
||||
|
||||
// When tp is 'image2text', filter to only include models with 'vision' capability
|
||||
if (tp.toLowerCase() === 'image2text') {
|
||||
options = options.filter((opt: any) => {
|
||||
const model = models?.items?.find((m: any) => m.id === opt.value);
|
||||
return model?.capability?.includes('vision');
|
||||
});
|
||||
}
|
||||
return (
|
||||
<Form.Item
|
||||
key={tp}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 17:30:06
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-04 10:09:45
|
||||
* @Last Modified time: 2026-03-06 13:49:00
|
||||
*/
|
||||
/**
|
||||
* Memory Extraction Engine Configuration Constants
|
||||
@@ -140,13 +140,8 @@ export const configList: ConfigVo[] = [
|
||||
{
|
||||
label: 'intelligentSemanticPruningScene',
|
||||
variableName: 'pruning_scene',
|
||||
control: 'select',
|
||||
control: 'text',
|
||||
type: 'enum',
|
||||
options: [
|
||||
{ label: 'education', value: 'education' },
|
||||
{ label: 'online_service', value: 'online_service' },
|
||||
{ label: 'outbound', value: 'outbound' },
|
||||
],
|
||||
meaning: 'intelligentSemanticPruningSceneDesc',
|
||||
},
|
||||
// Intelligent semantic pruning阈值
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 17:30:02
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-03 17:30:02
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-06 13:50:05
|
||||
*/
|
||||
/**
|
||||
* Memory Extraction Engine Configuration Page
|
||||
@@ -13,7 +13,7 @@
|
||||
import { type FC, useState, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { Row, Col, Space, Select, InputNumber, Slider, App, Form } from 'antd'
|
||||
import { Row, Col, Space, Select, InputNumber, Slider, App, Form, Input } from 'antd'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import Card from './components/Card'
|
||||
@@ -35,15 +35,15 @@ const keys = [
|
||||
/**
|
||||
* Configuration description component
|
||||
*/
|
||||
const ConfigDesc: FC<{ config: Variable, className?: string }> = ({config, className}) => {
|
||||
const ConfigDesc: FC<{ config: Variable, className?: string; onlyMeaning?: boolean; }> = ({ config, className, onlyMeaning = false}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className={className}>
|
||||
<Space size={8} className={clsx("rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4 ")}>
|
||||
{!onlyMeaning && <Space size={8} className={clsx("rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4 ")}>
|
||||
{config.variableName && <span className="rb:font-regular">{t('memoryExtractionEngine.variableName')}: {config.variableName}</span>}
|
||||
{config.control && <span className="rb:font-regular">{t('memoryExtractionEngine.control')}: {t(`memoryExtractionEngine.${config.control}`)}</span>}
|
||||
{config.type && <span className="rb:font-regular">{t('memoryExtractionEngine.type')}: {config.type}</span>}
|
||||
</Space>
|
||||
</Space>}
|
||||
{config.meaning && <div className={clsx("rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4 ")}>{t('memoryExtractionEngine.Meaning')}: {t(`memoryExtractionEngine.${config.meaning}`)}</div>}
|
||||
</div>
|
||||
)
|
||||
@@ -253,6 +253,21 @@ const MemoryExtractionEngine: FC = () => {
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
{config.control === 'text' &&
|
||||
<>
|
||||
<div className="rb:text-[14px] rb:font-medium rb:leading-5 rb:mt-6 rb:mb-2">
|
||||
-{t(`memoryExtractionEngine.${config.label}`)}
|
||||
</div>
|
||||
<div className="rb:pl-2">
|
||||
<Form.Item
|
||||
name={config.variableName}
|
||||
>
|
||||
<Input placeholder={t('common.pleaseEnter')} disabled />
|
||||
</Form.Item>
|
||||
<ConfigDesc config={config} onlyMeaning={true} className="rb:-mt-4!" />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 17:33:15
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-05 16:28:58
|
||||
* @Last Modified time: 2026-03-06 13:53:53
|
||||
*/
|
||||
/**
|
||||
* Memory Management Page
|
||||
@@ -154,10 +154,10 @@ const MemoryManagement: React.FC = () => {
|
||||
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/edit.svg')] rb:hover:bg-[url('@/assets/images/edit_hover.svg')]"
|
||||
onClick={() => handleEdit(item)}
|
||||
></div>
|
||||
<div
|
||||
{!item.is_system_default && <div
|
||||
className="rb:w-5 rb:h-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/delete.svg')] rb:hover:bg-[url('@/assets/images/delete_hover.svg')]"
|
||||
onClick={() => handleDelete(item)}
|
||||
></div>
|
||||
></div>}
|
||||
</Space>
|
||||
</div>
|
||||
</RbCard>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 16:49:45
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-04 11:50:47
|
||||
* @Last Modified time: 2026-03-06 12:26:12
|
||||
*/
|
||||
/**
|
||||
* Model List Detail Drawer
|
||||
@@ -144,7 +144,7 @@ const ModelListDetail = forwardRef<ModelListDetailRef, ModelListDetailProps>(({
|
||||
{item.name[0]}
|
||||
</div>
|
||||
}
|
||||
extra={<Switch defaultChecked={item.is_active} disabled={loading} onChange={() => handleChange(item)} />}
|
||||
extra={<Switch checked={item.is_active} disabled={loading} onChange={() => handleChange(item)} />}
|
||||
bodyClassName="rb:relative rb:pb-[64px]! rb:h-[calc(100%-64px)]!"
|
||||
>
|
||||
<Tooltip title={item.description}>
|
||||
@@ -153,7 +153,7 @@ const ModelListDetail = forwardRef<ModelListDetailRef, ModelListDetailProps>(({
|
||||
<div className="rb:absolute rb:bottom-4 rb:left-6 rb:right-6">
|
||||
<Row gutter={12}>
|
||||
<Col span={12}>
|
||||
<Button block onClick={() => handleEdit(item)}>{t('modelNew.modelConfiguration')}</Button>
|
||||
{!item.model_id && <Button block onClick={() => handleEdit(item)}>{t('modelNew.modelConfiguration')}</Button>}
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Button type="primary" ghost block onClick={() => handleKeyConfig(item)}>{t('modelNew.keyConfig')}</Button>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 16:50:18
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-03-04 11:39:20
|
||||
* @Last Modified time: 2026-03-06 12:26:11
|
||||
*/
|
||||
/**
|
||||
* Type definitions for Model Management
|
||||
@@ -121,6 +121,7 @@ export interface ModelApiKey {
|
||||
* Model list item data structure
|
||||
*/
|
||||
export interface ModelListItem {
|
||||
model_id?: string;
|
||||
/** Model name */
|
||||
model_name?: string;
|
||||
/** Associated model config IDs */
|
||||
|
||||
@@ -102,7 +102,7 @@ const Detail: FC = () => {
|
||||
<PageHeader
|
||||
name={<Space>
|
||||
{data.scene_name}
|
||||
<Tag color="warning">{t('common.default')}</Tag>
|
||||
{data.is_system_default ? <Tag color="warning">{t('common.default')}</Tag> : undefined}
|
||||
</Space>}
|
||||
subTitle={<Tooltip title={data.scene_description}><div className="rb:h-4 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{data.scene_description}</div></Tooltip>}
|
||||
extra={data.is_system_default ? undefined : (<Space>
|
||||
|
||||
@@ -25,6 +25,7 @@ const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options
|
||||
const textContent = root.getTextContent();
|
||||
if (textContent !== prevValueRef.current) {
|
||||
isUserInputRef.current = true;
|
||||
prevValueRef.current = textContent;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -33,7 +34,13 @@ const InitialValuePlugin: React.FC<InitialValuePluginProps> = ({ value, options
|
||||
}, [editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if ((value !== prevValueRef.current || enableLineNumbers !== prevEnableLineNumbersRef.current) && !isUserInputRef.current) {
|
||||
if (value !== prevValueRef.current || enableLineNumbers !== prevEnableLineNumbersRef.current) {
|
||||
// Skip reset if the change was triggered by user input (avoid cursor jump)
|
||||
if (isUserInputRef.current && enableLineNumbers === prevEnableLineNumbersRef.current) {
|
||||
prevValueRef.current = value;
|
||||
isUserInputRef.current = false;
|
||||
return;
|
||||
}
|
||||
queueMicrotask(() => {
|
||||
editor.update(() => {
|
||||
const root = $getRoot();
|
||||
|
||||
@@ -35,7 +35,8 @@ const NODE_VARIABLES = {
|
||||
],
|
||||
'http-request': [
|
||||
{ label: 'body', dataType: 'string', field: 'body' },
|
||||
{ label: 'status_code', dataType: 'number', field: 'status_code' }
|
||||
{ label: 'status_code', dataType: 'number', field: 'status_code' },
|
||||
{ label: 'headers', dataType: 'object', field: 'headers' },
|
||||
],
|
||||
'question-classifier': [{ label: 'class_name', dataType: 'string', field: 'class_name' }],
|
||||
'memory-read': [
|
||||
@@ -390,11 +391,6 @@ export const useVariableList = (
|
||||
addVariable(list, keys, `${pid}_item`, 'item', itemType, `${pid}.item`, pd);
|
||||
addVariable(list, keys, `${pid}_index`, 'index', 'number', `${pid}.index`, pd);
|
||||
} else if (pd.type === 'iteration' && !pd.config.input.defaultValue) {
|
||||
let itemType = 'object';
|
||||
const iv = list.find(v => `{{${v.value}}}` === pd.config.input.defaultValue);
|
||||
if (iv?.dataType.startsWith('array[')) {
|
||||
itemType = iv.dataType.replace(/^array\[(.+)\]$/, '$1');
|
||||
}
|
||||
addVariable(list, keys, `${pid}_item`, 'item', 'string', `${pid}.item`, pd);
|
||||
addVariable(list, keys, `${pid}_index`, 'index', 'number', `${pid}.index`, pd);
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ const Properties: FC<PropertiesProps> = ({
|
||||
initialValue[key] = config[key].defaultValue
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
form.setFieldsValue({
|
||||
type,
|
||||
id: selectedNode.id,
|
||||
@@ -114,16 +114,16 @@ const Properties: FC<PropertiesProps> = ({
|
||||
*/
|
||||
const updateNodeLabel = (newLabel: string) => {
|
||||
if (selectedNode && form) {
|
||||
const nodeData = selectedNode.data as NodeProperties;
|
||||
const nodeData = selectedNode.getData() as NodeProperties;
|
||||
selectedNode.setAttrByPath('text/text', `${nodeData.icon} ${newLabel}`);
|
||||
selectedNode.setData({ ...selectedNode.data, name: newLabel });
|
||||
selectedNode.setData({ ...selectedNode.getData(), name: newLabel });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (values && selectedNode) {
|
||||
const { id, knowledge_retrieval, group, group_variables, ...rest } = values
|
||||
const { knowledge_bases = [], ...restKnowledgeConfig } = (knowledge_retrieval as any) || {}
|
||||
const { knowledge_bases = [], name: _name, description: _description, ...restKnowledgeConfig } = (knowledge_retrieval as any) || {}
|
||||
|
||||
let allRest = {
|
||||
...rest,
|
||||
@@ -136,21 +136,23 @@ const Properties: FC<PropertiesProps> = ({
|
||||
}))
|
||||
}
|
||||
|
||||
const nodeData = selectedNode.getData()
|
||||
|
||||
Object.keys(values).forEach(key => {
|
||||
if (selectedNode.data?.config?.[key]) {
|
||||
if (nodeData?.config?.[key]) {
|
||||
// Create a deep copy to avoid reference sharing between nodes
|
||||
if (!selectedNode.data.config[key]) {
|
||||
selectedNode.data.config[key] = {};
|
||||
if (!nodeData.config[key]) {
|
||||
nodeData.config[key] = {};
|
||||
}
|
||||
selectedNode.data.config[key] = {
|
||||
...selectedNode.data.config[key],
|
||||
nodeData.config[key] = {
|
||||
...nodeData.config[key],
|
||||
defaultValue: values[key]
|
||||
};
|
||||
}
|
||||
})
|
||||
|
||||
selectedNode?.setData({
|
||||
...selectedNode.data,
|
||||
...nodeData,
|
||||
...allRest,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -529,6 +529,10 @@ export const unknownNode = {
|
||||
type: 'unknown',
|
||||
icon: unknownIcon
|
||||
}
|
||||
export const noteNode = {
|
||||
type: 'notes',
|
||||
icon: unknownIcon
|
||||
}
|
||||
|
||||
export const nodeWidth = 240;
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-03 15:17:48
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-02-28 17:59:34
|
||||
* @Last Modified time: 2026-03-07 15:23:39
|
||||
*/
|
||||
import { useRef, useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@@ -12,7 +12,7 @@ import { Graph, Node, MiniMap, Snapline, Clipboard, Keyboard, type Edge } from '
|
||||
import { register } from '@antv/x6-react-shape';
|
||||
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 } from '../constant';
|
||||
import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color, portTextAttrs, defaultAbsolutePortGroups, nodeWidth, unknownNode, noteNode } from '../constant';
|
||||
import type { WorkflowConfig, NodeProperties, ChatVariable } from '../types';
|
||||
import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application'
|
||||
|
||||
@@ -128,7 +128,7 @@ export const useWorkflowGraph = ({
|
||||
if (nodes.length) {
|
||||
const nodeList = nodes.map(node => {
|
||||
const { id, type, name, position, config = {} } = node
|
||||
let nodeLibraryConfig = [...nodeLibrary, { nodes: [unknownNode] }]
|
||||
let nodeLibraryConfig = [...nodeLibrary, { nodes: [unknownNode, noteNode] }]
|
||||
.flatMap(category => category.nodes)
|
||||
.find(n => n.type === type)
|
||||
nodeLibraryConfig = JSON.parse(JSON.stringify({ config: {}, ...nodeLibraryConfig })) as NodeProperties
|
||||
@@ -715,6 +715,8 @@ export const useWorkflowGraph = ({
|
||||
panning: isHandMode,
|
||||
mousewheel: {
|
||||
enabled: true,
|
||||
factor: 0.1,
|
||||
modifiers: null,
|
||||
},
|
||||
connecting: {
|
||||
connector: {
|
||||
|
||||
Reference in New Issue
Block a user