update web

This commit is contained in:
zhaoying
2025-12-15 15:12:49 +08:00
parent db3d3dee85
commit 9cf22bfae2
48 changed files with 726 additions and 184 deletions

View File

@@ -24,7 +24,7 @@ import { saveAgentConfig } from '@/api/application'
import Knowledge from './components/Knowledge'
import VariableList from './components/VariableList'
import { getApplicationConfig } from '@/api/application'
import { getKnowledgeBaseList } from '@/views/KnowledgeBase/service'
import { getKnowledgeBaseList } from '@/api/knowledgeBase'
import { memoryConfigListUrl } from '@/api/memory'
import CustomSelect from '@/components/CustomSelect'

View File

@@ -44,12 +44,22 @@ const Cluster: FC<{application: SubAgentItem}> = ({application}) => {
priority: 1,
}))
}
console.log('params', params)
form.validateFields().then(() => {
saveMultiAgentConfig(id as string, params).then(() => {
if (flag) {
message.success(t('common.saveSuccess'))
}
return new Promise((resolve, reject) => {
form.validateFields().then(() => {
saveMultiAgentConfig(id as string, params)
.then(() => {
if (flag) {
message.success(t('common.saveSuccess'))
}
resolve(true)
})
.catch(error => {
reject(error)
})
})
.catch(error => {
reject(error)
})
})
}

View File

@@ -2,9 +2,9 @@ import { type FC, useRef, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import clsx from 'clsx'
import { Input, Form } from 'antd'
import ChatIcon from '@/assets/images/application/chat.svg'
import ChatIcon from '@/assets/images/application/chat.png'
import ChatSendIcon from '@/assets/images/application/chatSend.svg'
import DebuggingEmpty from '@/assets/images/application/debuggingEmpty.svg'
import DebuggingEmpty from '@/assets/images/application/debuggingEmpty.png'
import type { ChatItem, ChatData, Config } from '../types'
import { runCompare, draftRun } from '@/api/application'
import Empty from '@/components/Empty'
@@ -114,6 +114,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
if (index === targetIndex) {
return {
...item,
conversation_id: parsed.conversation_id,
list: item.list?.map((msg, msgIndex) => {
if (msgIndex === item.list!.length - 1 && msg.role === 'answer') {
return { ...msg, content: msg.content + parsed.content };
@@ -126,10 +127,6 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
}))
}
}
if (parsed.conversation_id) {
setConversationId(parsed.conversation_id);
}
} else if (line.startsWith('data:') && (isCluster && currentEvent === 'message')) {
const jsonData = line.substring(5).trim();
const parsed = JSON.parse(jsonData);
@@ -175,10 +172,6 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
}))
}
}
if (parsed.conversation_id) {
setConversationId(parsed.conversation_id);
}
} else if (line.startsWith('data:') && (isCluster && currentEvent === 'model_end')) {
const jsonData = line.substring(5).trim();
const parsed = JSON.parse(jsonData);
@@ -247,6 +240,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
{chatList.length === 0
? <Empty
url={DebuggingEmpty}
size={[300, 200]}
title={t('application.debuggingEmpty')}
subTitle={t('application.debuggingEmptyDesc')}
className="rb:h-full"
@@ -278,7 +272,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
</div>
}
{!chat.list || chat.list.length === 0
? <Empty url={ChatIcon} title={t('application.chatEmpty')} className="rb:h-full" />
? <Empty url={ChatIcon} title={t('application.chatEmpty')} isNeedSubTitle={false} className="rb:h-full" size={[240, 200]} />
: (
<div ref={el => scrollContainerRefs.current[index] = el} className={clsx(`rb:relative rb:overflow-y-auto rb:overflow-x-hidden`, {
'rb:h-[calc(100vh-186px)]': isCluster,

View File

@@ -5,7 +5,7 @@ import clsx from 'clsx'
import type { KnowledgeModalRef, KnowledgeBase } from '../types'
import type { KnowledgeBaseListItem } from '@/views/KnowledgeBase/types'
import RbModal from '@/components/RbModal'
import { getKnowledgeBaseList } from '@/views/KnowledgeBase/service'
import { getKnowledgeBaseList } from '@/api/knowledgeBase'
import SearchInput from '@/components/SearchInput'
import Empty from '@/components/Empty'
import { formatDateTime } from '@/utils/format';

View File

@@ -1,4 +1,4 @@
import { type FC, useRef, useEffect, useState } from 'react'
import { type FC, useRef, useEffect, useState, useMemo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import ReactEcharts from 'echarts-for-react';
import type { ConfigForm } from '../types'
@@ -30,6 +30,7 @@ const LineChart: FC<LineCardProps> = ({ config }) => {
const { t } = useTranslation()
const chartRef = useRef<ReactEcharts>(null);
const debounceRef = useRef()
const resizeScheduledRef = useRef(false)
const xAxisData = [1, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60]
const [initialData, setInitialData] = useState([])
const [currentData, setCurrentData] = useState({
@@ -38,7 +39,7 @@ const LineChart: FC<LineCardProps> = ({ config }) => {
data: [],
config: {}
})
const seriesData = [
const seriesData = useMemo(() => [
{
...SeriesConfig,
name: `${t('forgettingEngine.quicklyForget')}(λ_time=0.3)`,
@@ -51,56 +52,77 @@ const LineChart: FC<LineCardProps> = ({ config }) => {
data: [],
config: {lambda_mem: 1, lambda_time: 0.3, offset: 0.2}
}
]
], [t])
useEffect(() => {
getInitData()
}, [])
useEffect(() => {
const handleResize = () => {
if (chartRef.current && !resizeScheduledRef.current) {
resizeScheduledRef.current = true
requestAnimationFrame(() => {
chartRef.current?.getEchartsInstance().resize();
resizeScheduledRef.current = false
});
}
}
const resizeObserver = new ResizeObserver(handleResize)
const chartElement = chartRef.current?.getEchartsInstance().getDom().parentElement
if (chartElement) {
resizeObserver.observe(chartElement)
}
return () => {
resizeObserver.disconnect()
}
}, [initialData])
useEffect(() => {
if (config) {
clearTimeout(debounceRef.current)
debounceRef.current = setTimeout(() => {
getCaculateData(config)
}, 500)
}, 300)
}
return () => {
console.log('clearTimeout')
clearTimeout(debounceRef.current)
}
}, [config])
// 快速遗忘lambda_mem=0.3lambda_time=1offset=0.05
// 慢速遗忘lambda_mem=1lambda_time=0.3offset=0.2
const getInitData = () => {
const getInitData = useCallback(() => {
const list = seriesData.map(item => ({
...item,
data: formatData(item.config)
}))
setInitialData(list)
}
}, [seriesData])
const calculateSeriesData = (days: number, data: ConfigForm) => {
const { offset, lambda_time, lambda_mem } = data;
const S = 1
const calculateSeriesData = useCallback((days: number, data: ConfigForm) => {
const offset = Number(data.offset)
const lambda_time = Number(data.lambda_time)
const lambda_mem = Number(data.lambda_mem)
// R = offset + (1 - offset) × e^(-λtime × t / (λmem × S))
return (Number(offset) + (1 - Number(offset)) * Math.exp(-Number(lambda_time) * days / (Number(lambda_mem) * S))).toFixed(4)
}
const formatData = (data: ConfigForm) => {
return +(offset + (1 - offset) * Math.exp(-lambda_time * days / lambda_mem)).toFixed(4)
}, [])
const formatData = useCallback((data: ConfigForm) => {
return xAxisData.map(days => Number(calculateSeriesData(days, data)))
}
}, [calculateSeriesData])
const getCaculateData = (data: ConfigForm) => {
const getCaculateData = useCallback((data: ConfigForm) => {
if (!data) {
return
}
console.log('getCaculateData', data)
setCurrentData({
...currentData,
setCurrentData(prev => ({
...prev,
config: data,
name: `${t('forgettingEngine.currentConfig')}(λ_time=${data.lambda_time})`,
data: xAxisData.map(days => Number(calculateSeriesData(days, data)))
})
}
}))
}, [t, calculateSeriesData])
return (
<>
@@ -175,17 +197,6 @@ const LineChart: FC<LineCardProps> = ({ config }) => {
opts={{ renderer: 'canvas' }}
notMerge={true}
lazyUpdate={true}
onEvents={{
// 图表渲染完成后再次调整大小,确保宽度正确
// 使用 setTimeout 避免在主渲染过程中调用 resize
rendered: () => {
if (chartRef.current) {
setTimeout(() => {
chartRef.current?.getEchartsInstance().resize();
}, 0);
}
}
}}
/>
)}
</>

View File

@@ -39,10 +39,12 @@ export interface CurveRecord {
}
export interface ConfigForm {
config_id?: string;
statement_granularity?: string;
include_dialogue_context?: boolean;
max_context?: string;
lambda_time: string | number;
lambda_mem: string | number;
offset: string | number;
[key: string]: any;
}

View File

@@ -156,17 +156,6 @@ const LineCard: FC<LineCardProps> = ({ chartData, limit, onChange, type, classNa
opts={{ renderer: 'canvas' }}
notMerge={true}
lazyUpdate={true}
onEvents={{
// 图表渲染完成后再次调整大小,确保宽度正确
// 使用 setTimeout 避免在主渲染过程中调用 resize
rendered: () => {
if (chartRef.current) {
setTimeout(() => {
chartRef.current?.getEchartsInstance().resize();
}, 0);
}
}
}}
/>
) : <Empty size={120} className="rb:mt-[48px] rb:mb-[81px]" />}
</Card>

View File

@@ -1,4 +1,4 @@
import { type FC, useRef } from 'react'
import { type FC, useRef, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import ReactEcharts from 'echarts-for-react';
import Card from './Card'
@@ -14,6 +14,29 @@ const Colors = ['#155EEF', '#31E8FF', '#AD88FF', '#FFB048', '#4DA8FF', '#03BDFF'
const PieCard: FC<PieCardProps> = ({ chartData, loading }) => {
const { t } = useTranslation()
const chartRef = useRef<ReactEcharts>(null);
const resizeScheduledRef = useRef(false)
useEffect(() => {
const handleResize = () => {
if (chartRef.current && !resizeScheduledRef.current) {
resizeScheduledRef.current = true
requestAnimationFrame(() => {
chartRef.current?.getEchartsInstance().resize();
resizeScheduledRef.current = false
});
}
}
const resizeObserver = new ResizeObserver(handleResize)
const chartElement = chartRef.current?.getEchartsInstance().getDom().parentElement
if (chartElement) {
resizeObserver.observe(chartElement)
}
return () => {
resizeObserver.disconnect()
}
}, [chartData])
return (
<Card
@@ -92,17 +115,6 @@ const PieCard: FC<PieCardProps> = ({ chartData, loading }) => {
style={{ height: '265px', width: '100%', minWidth: '400px' }}
notMerge={true}
lazyUpdate={true}
onEvents={{
// 图表渲染完成后再次调整大小,确保宽度正确
// 使用 setTimeout 避免在主渲染过程中调用 resize
rendered: () => {
if (chartRef.current) {
setTimeout(() => {
chartRef.current?.getEchartsInstance().resize();
}, 0);
}
}
}}
/>
}
</Card>

View File

@@ -4,11 +4,11 @@ import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import Table, { type TableRef } from '@/components/Table'
import type { AnyObject } from 'antd/es/_util/type';
import type { UploadFileResponse,KnowledgeBaseDocumentData } from '../types';
import type { UploadFileResponse,KnowledgeBaseDocumentData } from '@/views/KnowledgeBase/types';
import type { ColumnsType } from 'antd/es/table';
import UploadFiles from '@/components/Upload/UploadFiles';
import type { UploadRequestOption } from 'rc-upload/lib/interface';
import { uploadFile, getDocumentList, previewDocumentChunk, parseDocument, updateDocument, deleteDocument } from '../service';
import { uploadFile, getDocumentList, previewDocumentChunk, parseDocument, updateDocument, deleteDocument } from '@/api/knowledgeBase';
import exitIcon from '@/assets/images/knowledgeBase/exit.png';
import { NoData } from '../components/noData';
import noDataIcon from '@/assets/images/knowledgeBase/noData.png';
@@ -227,7 +227,7 @@ const CreateDataset = () => {
return (
<span className="rb:text-xs rb:border rb:border-[#DFE4ED] rb:bg-[#FBFDFF] rb:rounded rb:items-center rb:text-[#212332] rb:py-1 rb:px-2">
<span className="rb:inline-block rb:w-[5px] rb:h-[5px] rb:mr-2 rb:rounded-full" style={{ backgroundColor: value === 1 ? '#369F21' : '#FF8A4C' }}></span>
<span>{value === 1 ? 'Completed' : 'Processing'}</span>
<span>{value === 1 ? t('knowledgeBase.completed') : value === 0 ? t('knowledgeBase.pending') : t('knowledgeBase.processing')}</span>
</span>
);
}

View File

@@ -10,8 +10,8 @@ import { useEffect, useState, useRef, type FC } from 'react';
import { useNavigate, useParams, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Button, Spin, message, Switch } from 'antd';
import { getDocumentDetail, getDocumentChunkList, downloadFile, updateDocument, updateDocumentChunk, createDocumentChunk } from '../service';
import type { KnowledgeBaseDocumentData, RecallTestData } from '../types';
import { getDocumentDetail, getDocumentChunkList, downloadFile, updateDocument, updateDocumentChunk, createDocumentChunk } from '@/api/knowledgeBase';
import type { KnowledgeBaseDocumentData, RecallTestData } from '@/views/KnowledgeBase/types';
import { formatDateTime } from '@/utils/format';
import InfoPanel, { type InfoItem } from '../components/InfoPanel';
import RecallTestResult from '../components/RecallTestResult';

View File

@@ -12,7 +12,7 @@ import { MoreOutlined } from '@ant-design/icons';
import folderIcon from '@/assets/images/knowledgeBase/folder.png';
import textIcon from '@/assets/images/knowledgeBase/text.png';
import editIcon from '@/assets/images/knowledgeBase/edit.png';
import { getKnowledgeBaseDetail, deleteDocument, downloadFile, updateKnowledgeBase } from '../service';
import { getKnowledgeBaseDetail, deleteDocument, downloadFile, updateKnowledgeBase } from '@/api/knowledgeBase';
import type {
CreateModalRef,
KnowledgeBaseListItem,
@@ -22,7 +22,7 @@ import type {
ShareModalRef,
CreateDatasetModalRef,FolderFormData,
KnowledgeBaseDocumentData
} from '../types';
} from '@/views/KnowledgeBase/types';
import RecallTestDrawer from '../components/RecallTestDrawer';
import CreateFolderModal from '../components/CreateFolderModal';
import CreateModal from '../components/CreateModal';
@@ -64,6 +64,7 @@ const Private: FC = () => {
const [folderTreeRefreshKey, setFolderTreeRefreshKey] = useState(0);
const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu();
const [folderPath, setFolderPath] = useState<Array<{ id: string; name: string }>>([]);
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
useEffect(() => {
if (knowledgeBaseId) {
let url = `/documents/${knowledgeBaseId}/${parentId}/documents`;
@@ -143,8 +144,23 @@ const Private: FC = () => {
disposable: false,
appSystem: null,
subs: [],
onClick: (e?: React.MouseEvent) => {
// 阻止默认行为和事件冒泡
e?.preventDefault();
e?.stopPropagation();
// 点击知识库名称,回到根目录
setParentId(knowledgeBaseId);
setFolder({
kb_id: knowledgeBaseId ?? '',
parent_id: knowledgeBaseId ?? ''
});
setTableApi(`/documents/${knowledgeBaseId}/${knowledgeBaseId}/documents`);
setFolderPath([]);
setSelectedKeys([knowledgeBaseId ?? '']);
return false;
},
},
...folderPath.map((folder) => ({
...folderPath.map((folder, index) => ({
id: 0,
parent: 0,
code: null,
@@ -166,6 +182,22 @@ const Private: FC = () => {
disposable: false,
appSystem: null,
subs: [],
onClick: (e?: React.MouseEvent) => {
// 阻止默认行为和事件冒泡
e?.preventDefault();
e?.stopPropagation();
// 点击文件夹,回到该文件夹层级
setParentId(folder.id);
setFolder({
kb_id: knowledgeBaseId ?? '',
parent_id: folder.id
});
setTableApi(`/documents/${knowledgeBaseId}/${folder.id}/documents`);
// 更新文件夹路径,只保留到当前点击的文件夹
setFolderPath(folderPath.slice(0, index + 1));
setSelectedKeys([folder.id]);
return false;
},
})),
];
@@ -173,17 +205,18 @@ const Private: FC = () => {
};
// 处理树节点选择
const onSelect = (selectedKeys: React.Key[]) => {
if (!selectedKeys.length) return;
const onSelect = (keys: React.Key[]) => {
if (!keys.length) return;
if (!folder) return;
const f = {
...folder,
parent_id: String(selectedKeys[0]),
parent_id: String(keys[0]),
}
let url = `/documents/${knowledgeBaseId}/${String(selectedKeys[0])}/documents`;
let url = `/documents/${knowledgeBaseId}/${String(keys[0])}/documents`;
setTableApi(url);
setParentId(String(selectedKeys[0]))
setParentId(String(keys[0]))
setFolder(f)
setSelectedKeys(keys)
};
// 处理文件夹路径变化
@@ -511,6 +544,7 @@ const Private: FC = () => {
refreshKey={folderTreeRefreshKey}
onRootLoad={handleRootTreeLoad}
onFolderPathChange={handleFolderPathChange}
selectedKeys={selectedKeys}
/>
</div>
)}

View File

@@ -1,7 +1,7 @@
import { useEffect, useState, useRef, type FC } from 'react';
import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import type { KnowledgeBaseListItem, RecallTestDrawerRef } from '../types';
import type { KnowledgeBaseListItem, RecallTestDrawerRef } from '@/views/KnowledgeBase/types';
import RecallTest from '../components/RecallTest';
import InfoPanel, { type InfoItem } from '../components/InfoPanel';
import shareUserIcon from '@/assets/images/knowledgeBase/share-user.png';
@@ -13,8 +13,9 @@ import kbSizeIcon from '@/assets/images/knowledgeBase/kb-size.png';
import kbModelIcon from '@/assets/images/knowledgeBase/kb-model.png';
import kbHistoryIcon from '@/assets/images/knowledgeBase/kb-history.png';
import { getKnowledgeBaseDetail } from '../service';
import { getKnowledgeBaseDetail } from '@/api/knowledgeBase';
import { formatDateTime } from '@/utils/format';
import { useMenu } from '@/store/menu';
const Share: FC = () => {
const { t } = useTranslation();
@@ -24,6 +25,7 @@ const Share: FC = () => {
const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBaseListItem | null>(null);
const recallTestRef = useRef<RecallTestDrawerRef>(null);
const [infoItems, setInfoItems] = useState<InfoItem[]>([]);
const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu();
useEffect(() => {
console.log('Share.tsx - useParams result:', params);
console.log('Share.tsx - knowledgeBaseId:', knowledgeBaseId);
@@ -40,6 +42,13 @@ const Share: FC = () => {
console.warn('Share.tsx - knowledgeBaseId is undefined or empty');
}
}, [knowledgeBaseId]);
// 更新面包屑
useEffect(() => {
if (knowledgeBase) {
updateBreadcrumbs();
}
}, [knowledgeBase]);
const formatInfoItems = (data: KnowledgeBaseListItem): InfoItem[] => {
const items: InfoItem[] = [
{
@@ -103,6 +112,47 @@ const Share: FC = () => {
});
};
// 更新面包屑,包含知识库名称
const updateBreadcrumbs = () => {
if (!knowledgeBase) return;
const baseBreadcrumbs = allBreadcrumbs['space'] || [];
// 只保留知识库菜单项之前的面包屑
const knowledgeBaseMenuIndex = baseBreadcrumbs.findIndex(item => item.path === '/knowledge-base');
const filteredBaseBreadcrumbs = knowledgeBaseMenuIndex >= 0
? baseBreadcrumbs.slice(0, knowledgeBaseMenuIndex + 1)
: baseBreadcrumbs;
const customBreadcrumbs = [
...filteredBaseBreadcrumbs,
{
id: 0,
parent: 0,
code: null,
label: knowledgeBase.name,
i18nKey: null,
path: null,
enable: true,
display: true,
level: 0,
sort: 0,
icon: null,
iconActive: null,
menuDesc: null,
deleted: null,
updateTime: 0,
new_: null,
keepAlive: false,
master: null,
disposable: false,
appSystem: null,
subs: [],
},
];
setCustomBreadcrumbs(customBreadcrumbs, 'space');
};
// const handleBack = () => {
// navigate('/knowledge-base');
// };

View File

@@ -11,7 +11,7 @@ import type { RadioChangeEvent } from 'antd';
import { Flex, Radio } from 'antd';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import type { CreateDatasetModalRef, CreateDatasetModalRefProps} from '../types';
import type { CreateDatasetModalRef, CreateDatasetModalRefProps} from '@/views/KnowledgeBase/types';
import RbModal from '@/components/RbModal'
const style: React.CSSProperties = {
display: 'flex',

View File

@@ -1,9 +1,9 @@
import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, Input } from 'antd';
import { useTranslation } from 'react-i18next';
import type { FolderFormData, KnowledgeBaseFormData, CreateFolderModalRef, CreateFolderModalRefProps } from '../types';
import type { FolderFormData, KnowledgeBaseFormData, CreateFolderModalRef, CreateFolderModalRefProps } from '@/views/KnowledgeBase/types';
import RbModal from '@/components/RbModal'
import { createFolder, updateKnowledgeBase } from '../service';
import { createFolder, updateKnowledgeBase } from '@/api/knowledgeBase';
const CreateFolderModal = forwardRef<CreateFolderModalRef,CreateFolderModalRefProps>(({
refreshTable
}, ref) => {

View File

@@ -2,11 +2,11 @@ import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
import { Form, Input } from 'antd';
import { useTranslation } from 'react-i18next';
import type { UploadFile } from 'antd';
import type { CreateImageModalRef, CreateImageMoealRefProps,UploadFileResponse } from '../types';
import type { CreateImageModalRef, CreateImageMoealRefProps,UploadFileResponse } from '@/views/KnowledgeBase/types';
import type { UploadRequestOption } from 'rc-upload/lib/interface';
import RbModal from '@/components/RbModal';
import UploadFiles from '@/components/Upload/UploadFiles';
import { uploadFile } from '../service';
import { uploadFile } from '@/api/knowledgeBase';
interface ImageDatasetFormData {
name: string;

View File

@@ -1,8 +1,8 @@
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react';
import { Form, Input, Select, Modal } from 'antd';
import { useTranslation } from 'react-i18next';
import type { KnowledgeBaseListItem, KnowledgeBaseFormData, CreateModalRef, CreateModalRefProps } from '../types';
import { getModelTypeList, getModelList, createKnowledgeBase, updateKnowledgeBase } from '../service'
import type { KnowledgeBaseListItem, KnowledgeBaseFormData, CreateModalRef, CreateModalRefProps } from '@/views/KnowledgeBase/types';
import { getModelTypeList, getModelList, createKnowledgeBase, updateKnowledgeBase } from '@/api/knowledgeBase'
import RbModal from '@/components/RbModal'
const { TextArea } = Input;
const { confirm } = Modal
@@ -15,7 +15,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
const [modelTypeList, setModelTypeList] = useState<string[]>([]);
const [modelOptionsByType, setModelOptionsByType] = useState<Record<string, { label: string; value: string }[]>>({});
const [datasets, setDatasets] = useState<KnowledgeBaseListItem | null>(null);
const [currentType, setCurrentType] = useState<string>('General'); // 保存当前 type
const [currentType, setCurrentType] = useState<'General' | 'Web' | 'Third-party' | 'Folder'>('General');
const [form] = Form.useForm<KnowledgeBaseFormData>();
const [loading, setLoading] = useState(false)
@@ -102,7 +102,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
const handleOpen = (record?: KnowledgeBaseListItem | null, type?: string) => {
setDatasets(record || null);
const nextType = type || currentType;
setCurrentType(nextType);
setCurrentType(nextType as any);
setBaseFields(record || null, nextType);
getTypeList(record || null);
setVisible(true);

View File

@@ -8,7 +8,7 @@ import textIcon from '@/assets/images/knowledgeBase/text.png';
import imageIcon from '@/assets/images/knowledgeBase/image.png';
import datasetsIcon from '@/assets/images/knowledgeBase/datasets.png';
import switcherIcon from '@/assets/images/knowledgeBase/switcher.png';
import { getFolderList } from '../service';
import { getFolderList } from '@/api/knowledgeBase';
const { DirectoryTree } = Tree;
@@ -59,6 +59,7 @@ interface FolderTreeProps {
refreshKey?: number;
onRootLoad?: (nodes: TreeNodeData[] | null) => void;
onFolderPathChange?: (path: Array<{ id: string; name: string }>) => void;
selectedKeys?: React.Key[];
}
const renderIcon = (icon?: string) => {
@@ -273,6 +274,7 @@ const FolderTree: FC<FolderTreeProps> = ({
refreshKey = 0,
onRootLoad,
onFolderPathChange,
selectedKeys,
}) => {
const [treeData, setTreeData] = useState<TreeNodeData[]>([]);
@@ -396,6 +398,7 @@ const FolderTree: FC<FolderTreeProps> = ({
onExpand={onExpand}
loadData={onLoadData}
treeData={treeNodes}
selectedKeys={selectedKeys}
/>
);
};

View File

@@ -2,10 +2,10 @@
import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
import { Form, Input, Select, Button, InputNumber } from 'antd';
import { useTranslation } from 'react-i18next';
import type { RecallTestDrawerRef, RecallTestData, RecallTestParams } from '../types';
import type { RecallTestDrawerRef, RecallTestData, RecallTestParams } from '@/views/KnowledgeBase/types';
// import refreshIcon from '@/assets/images/knowledgeBase/refresh-blue.png';
import RecallTestResult from './RecallTestResult';
import { reChunks, getRetrievalModeType } from '../service';
import { reChunks, getRetrievalModeType } from '@/api/knowledgeBase';
import { hybrid } from 'react-syntax-highlighter/dist/esm/styles/hljs';
const { TextArea } = Input;

View File

@@ -2,7 +2,7 @@
import { forwardRef, useImperativeHandle, useState, useRef, useLayoutEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import RbDrawer from '@/components/RbDrawer';
import type { RecallTestDrawerRef } from '../types';
import type { RecallTestDrawerRef } from '@/views/KnowledgeBase/types';
import RecallTest from './RecallTest';
const RecallTestDrawer = forwardRef<RecallTestDrawerRef>(({},ref) => {

View File

@@ -9,7 +9,7 @@
import { FileOutlined, FieldTimeOutlined, EditOutlined } from '@ant-design/icons';
import { Skeleton } from 'antd';
import { useTranslation } from 'react-i18next';
import type { RecallTestData } from '../types';
import type { RecallTestData } from '@/views/KnowledgeBase/types';
import { NoData } from './noData';
import { formatDateTime } from '@/utils/format';
import InfiniteScroll from 'react-infinite-scroll-component';

View File

@@ -10,14 +10,14 @@ import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
import { Switch } from 'antd';
import { useTranslation } from 'react-i18next';
import { message } from 'antd';
import type { ShareModalRef, ShareModalRefProps, KnowledgeBase} from '../types';
import type { ShareModalRef, ShareModalRefProps, KnowledgeBase} from '@/views/KnowledgeBase/types';
import RbModal from '@/components/RbModal'
// import betchControlIcon from '@/assets/images/knowledgeBase/betch-control.png';
import kbIcon from '@/assets/images/knowledgeBase/knowledge-management.png';
// import robotIcon from '@/assets/images/knowledgeBase/robot.png';
import { updateKnowledgeBase, getWorkspaceAuthorizationList } from '../service';
import { updateKnowledgeBase, getWorkspaceAuthorizationList } from '@/api/knowledgeBase';
import { NoData } from './noData';
import type { ListQuery, ShareSpaceModalRef } from '../types';
import type { ListQuery, ShareSpaceModalRef } from '@/views/KnowledgeBase/types';
import { formatDateTime } from '@/utils/format';
import ShareSpaceModal from './ShareSpaceModal'
const ShareModal = forwardRef<ShareModalRef,ShareModalRefProps>(({ handleShare: onShare }, ref) => {

View File

@@ -10,14 +10,14 @@ import { forwardRef, useImperativeHandle, useState } from 'react';
import { Switch } from 'antd';
import { useTranslation } from 'react-i18next';
import { message } from 'antd';
import type { ShareModalRef, ShareModalRefProps, KnowledgeBase} from '../types';
import type { ShareModalRef, ShareModalRefProps, KnowledgeBase} from '@/views/KnowledgeBase/types';
import RbModal from '@/components/RbModal'
// import betchControlIcon from '@/assets/images/knowledgeBase/betch-control.png';
import kbIcon from '@/assets/images/knowledgeBase/knowledge-management.png';
// import robotIcon from '@/assets/images/knowledgeBase/robot.png';
import { getSpaceList, shareKnowledgeBase } from '../service';
import { getSpaceList, shareKnowledgeBase } from '@/api/knowledgeBase';
import { NoData } from './noData';
import type { SpaceItem } from '../types';
import type { SpaceItem } from '@/views/KnowledgeBase/types';
import { formatDateTime } from '@/utils/format';
const ShareModal = forwardRef<ShareModalRef,ShareModalRefProps>(({ handleShare: onShare }, ref) => {
const { t } = useTranslation();

View File

@@ -5,7 +5,7 @@ import { Button } from 'antd';
import { ArrowLeftOutlined } from '@ant-design/icons';
import { request } from '@/utils/request';
import type { KnowledgeBase } from './types';
import type { KnowledgeBase } from '@/views/KnowledgeBase/types';
const Datasets: FC = () => {
const { t } = useTranslation();

View File

@@ -10,14 +10,16 @@ import folderIcon from '@/assets/images/knowledgeBase/folder.png';
import generalIcon from '@/assets/images/knowledgeBase/datasets.png';
import webIcon from '@/assets/images/knowledgeBase/general.png';
import tpIcon from '@/assets/images/knowledgeBase/text.png';
import type { KnowledgeBaseListItem, CreateModalRef, KnowledgeBaseListResponse, ListQuery } from './types'
import type { KnowledgeBaseListItem, CreateModalRef, KnowledgeBaseListResponse, ListQuery } from '@/views/KnowledgeBase/types'
import CreateModal from './components/CreateModal'
import RbCard from '@/components/RbCard'
import SearchInput from '@/components/SearchInput'
import Empty from '@/components/Empty'
import { getKnowledgeBaseList, getModelList, getModelTypeList, deleteKnowledgeBase, getKnowledgeBaseTypeList } from './service'
import { getKnowledgeBaseList, getModelList, getModelTypeList, deleteKnowledgeBase, getKnowledgeBaseTypeList } from '@/api/knowledgeBase'
const { confirm } = Modal;
import InfiniteScroll from 'react-infinite-scroll-component';
import { useMenu } from '@/store/menu';
type ModelMenuInfo = {
menu: NonNullable<MenuProps['items']>;
summary: string[];
@@ -41,6 +43,10 @@ const KnowledgeBaseManagement: FC = () => {
const modalRef = useRef<CreateModalRef>(null)
const [messageApi, contextHolder] = message.useMessage();
// 使用 menu store 管理面包屑
const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu();
const [folderPath, setFolderPath] = useState<Array<{ id: string; name: string }>>([]);
// 生成下拉菜单项(根据当前 item
const getOptMenuItems = (item: KnowledgeBaseListItem): MenuProps['items'] => {
@@ -105,7 +111,17 @@ const KnowledgeBaseManagement: FC = () => {
// 处理创建
const handleCreate = (type?: string) => {
modalRef?.current?.handleOpen(null, type)
// 如果在文件夹内,使用 folderPath 的最后一项作为 parent_id
// 这样更可靠,因为 folderPath 是直接管理的状态
const currentParentId = folderPath.length > 0
? folderPath[folderPath.length - 1].id
: query.parent_id; // 降级使用 query.parent_id
const record = currentParentId ? {
parent_id: currentParentId as string,
} as KnowledgeBaseListItem : null;
modalRef?.current?.handleOpen(record, type)
}
// 动态生成 createItems
@@ -118,7 +134,7 @@ const KnowledgeBaseManagement: FC = () => {
handleCreate(type);
},
}));
}, [knowledgeBaseTypes, t]);
}, [knowledgeBaseTypes, t, folderPath, query]);
const typeToFieldKey = (type: string) => {
const normalized = (type || '').toLowerCase();
switch (normalized) {
@@ -176,7 +192,7 @@ const KnowledgeBaseManagement: FC = () => {
const fetchKnowledgeBaseTypes = async () => {
try {
let types = await getKnowledgeBaseTypeList();
types = types.filter(type => (type === 'General' )); //|| type === 'Folder'
types = types.filter(type => (type === 'General' || type === 'Folder' )); //
//暂时未实现 ,过滤掉未实现
setKnowledgeBaseTypes(types);
} catch (error) {
@@ -337,6 +353,25 @@ const KnowledgeBaseManagement: FC = () => {
};
// 处理跳转详情
const handleToDetail = (knowledgeBase: KnowledgeBaseListItem) => {
// 如果是 Folder 类型,刷新当前页面,显示该文件夹下的知识库列表
if (knowledgeBase.type === 'Folder' || knowledgeBase.type === 'folder') {
// 添加到文件夹路径
const newFolderPath = [
...folderPath,
{
id: knowledgeBase.id,
name: knowledgeBase.name,
},
];
setFolderPath(newFolderPath);
setQuery((prev) => ({
...prev,
parent_id: knowledgeBase.id,
}));
return;
}
// 根据权限类型跳转到不同的详情页
if (knowledgeBase.permission_id === 'Private' || knowledgeBase.permission_id === 'private') {
navigate(`/knowledge-base/${knowledgeBase.id}/private`)
@@ -344,6 +379,83 @@ const KnowledgeBaseManagement: FC = () => {
navigate(`/knowledge-base/${knowledgeBase.id}/share`)
}
}
// 更新面包屑的函数
const updateBreadcrumbs = () => {
const baseBreadcrumbs = allBreadcrumbs['space'] || [];
// 只保留知识库菜单项之前的面包屑
const knowledgeBaseMenuIndex = baseBreadcrumbs.findIndex(item => item.path === '/knowledge-base');
const filteredBaseBreadcrumbs = knowledgeBaseMenuIndex >= 0
? baseBreadcrumbs.slice(0, knowledgeBaseMenuIndex + 1)
: baseBreadcrumbs;
// 给"知识库管理"添加点击事件,返回根目录
const breadcrumbsWithClick = filteredBaseBreadcrumbs.map((item) => {
if (item.path === '/knowledge-base') {
return {
...item,
onClick: (e?: React.MouseEvent) => {
e?.preventDefault();
e?.stopPropagation();
// 返回根目录
setFolderPath([]);
setQuery((prev) => ({
...prev,
parent_id: undefined,
}));
return false;
},
};
}
return item;
});
const customBreadcrumbs = [
...breadcrumbsWithClick,
...folderPath.map((folder, index) => ({
id: 0,
parent: 0,
code: null,
label: folder.name,
i18nKey: null,
path: null,
enable: true,
display: true,
level: 0,
sort: 0,
icon: null,
iconActive: null,
menuDesc: null,
deleted: null,
updateTime: 0,
new_: null,
keepAlive: false,
master: null,
disposable: false,
appSystem: null,
subs: [],
onClick: (e?: React.MouseEvent) => {
e?.preventDefault();
e?.stopPropagation();
// 点击文件夹,回到该文件夹层级
const newFolderPath = folderPath.slice(0, index + 1);
setFolderPath(newFolderPath);
setQuery((prev) => ({
...prev,
parent_id: folder.id,
}));
return false;
},
})),
];
setCustomBreadcrumbs(customBreadcrumbs, 'space');
};
// 更新面包屑
useEffect(() => {
updateBreadcrumbs();
}, [folderPath]);
useEffect(() => {
fetchModelTypes();
fetchKnowledgeBaseTypes();

View File

@@ -1,280 +0,0 @@
import { request } from "@/utils/request";
import type { AxiosProgressEvent } from "axios";
import type {
ShareRequestParams,
SpaceItem,
UploadFileFormData,
FolderFormData,
UploadFileResponse,
Model,
PageRequest,
KnowledgeBase,
KnowledgeBaseFormData,
ListQuery,
PathQuery,
KnowledgeBaseDocumentData,
KnowledgeBaseListResponse,
KnowledgeBaseShareListResponse,
} from "./types";
const apiPrefix = '';
// 从路由中获取空间ID (#号后第一个路径段)
export const getSpaceIdFromRoute = (): string | null => {
if (typeof window === 'undefined') return null;
const hash = window.location.hash;
if (!hash || hash === '#') return null;
// 移除 # 号,然后分割路径
const path = hash.slice(1); // 移除 #
const segments = path.split('/').filter(Boolean); // 分割并过滤空字符串
return segments.length > 0 ? segments[0] : null;
};
export const spaceId = getSpaceIdFromRoute();
//获取知识库类型 (返回字符串数组,每个字符串是 KnowledgeBase 的 type 值)
export const getKnowledgeBaseTypeList = async (): Promise<string[]> => {
const response = await request.get(`${apiPrefix}/knowledges/knowledgetype`);
// 如果直接返回字符串数组,直接返回
if (Array.isArray(response)) {
return response.map(item => {
// 如果是字符串,直接返回
if (typeof item === 'string') {
return item;
}
// 如果是对象且有 type 字段,提取 type 值
if (typeof item === 'object' && item !== null && 'type' in item) {
return String(item.type);
}
// 其他情况转换为字符串
return String(item);
});
}
// 如果不是数组,返回空数组
return [];
};
// 知识库文档解析类型
export const getKnowledgeBaseDocumentParseTypeList = async () => {
const response = await request.get(`${apiPrefix}/knowledges/parsertype`);
return response as any[];
};
//获取模型类型
export const getModelTypeList = async () => {
const response = await request.get(`${apiPrefix}/models/type`);
return response as any[];
};
// 获取模型列表
export const getModelList = async (type: string | string[], pageInfo: PageRequest) => {
const response = await request.get(`${apiPrefix}/models`, { type, ...pageInfo });
return response as any;
};
//获取模型提供者
export const getModelProviderList = async () => {
const response = await request.get(`${apiPrefix}/models/provider`);
return response as any[];
};
// 获取模型信息
export const getModelDetail = async (id: string) => {
const response = await request.get(`${apiPrefix}/models/${id}`);
return response as Model;
};
// 知识库列表
export const getKnowledgeBaseList = async (parent_id?: string, query?: ListQuery) => {
const response = await request.get(`${apiPrefix}/knowledges/knowledges`, query);
return response as KnowledgeBaseListResponse;
};
// 知识库详情
export const getKnowledgeBaseDetail = async (id: string) => {
const response = await request.get(`${apiPrefix}/knowledges/${id}`);
return response as KnowledgeBase;
};
// 创建知识库
export const createKnowledgeBase = async (data: KnowledgeBaseFormData) => {
const payload: KnowledgeBaseFormData = {
...data,
permission_id: data.permission_id ?? 'private',
};
const response = await request.post(`${apiPrefix}/knowledges/knowledge`, payload);
return response as KnowledgeBase;
};
// 更新知识库
export const updateKnowledgeBase = async (id: string, data: KnowledgeBaseFormData) => {
const payload: KnowledgeBaseFormData = {
...data,
};
const response = await request.put(`${apiPrefix}/knowledges/${id}`, payload);
return response as any;
};
// 删除知识库(软删除)
export const deleteKnowledgeBase = async (id: string) => {
const response = await request.delete(`${apiPrefix}/knowledges/${id}`);
return response as any;
}
// 知识库分享 获取分享空间列表
export const getShareSpaceList = async (id: string) => {
const response = await request.get(`${apiPrefix}/knowledgeshares/${id}/knowledgeshares`);
return response as KnowledgeBaseShareListResponse;
}
// 获取文件夹列表
export const getFolderList = async (query: FolderFormData) => {
const id = query.parent_id ?? query.kb_id;
const response = await request.get(`${apiPrefix}/files/${query.kb_id}/${id}/files`);
return response as any;
};
// 创建文件夹
export const createFolder = async (params: FolderFormData) => {
const response = await request.post(`${apiPrefix}/files/folder`, undefined, {
params,
});
return response as FolderFormData;
};
interface UploadFileOptions {
kb_id?: string;
parent_id?: string;
onUploadProgress?: (event: AxiosProgressEvent) => void;
}
// 上传文件
export const uploadFile = async (data: FormData, options?: UploadFileOptions) => {
const { kb_id, parent_id, onUploadProgress } = options || {};
const params: Record<string, string> = {};
if (kb_id) params.kb_id = kb_id;
if (parent_id) params.parent_id = parent_id;
const response = await request.uploadFile(`${apiPrefix}/files/file`, data, {
params,
onUploadProgress,
});
return response as UploadFileResponse;
};
// 下载文件
export const downloadFile = async (fileId: string, fileName?: string) => {
const token = localStorage.getItem('token');
const url = `${apiPrefix}/files/${fileId}`;
try {
const response = await fetch(url, {
method: 'GET',
headers: {
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
});
if (!response.ok) {
throw new Error('下载失败');
}
const blob = await response.blob();
const blobUrl = URL.createObjectURL(blob);
// 创建临时链接触发下载
const link = document.createElement('a');
link.href = blobUrl;
link.style.display = 'none';
if (fileName) {
link.setAttribute('download', fileName);
}
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 释放 blob URL
URL.revokeObjectURL(blobUrl);
} catch (error) {
console.error('下载文件失败:', error);
throw error;
}
};
// 更新文件信息
export const updateFile = async (id: string, data: UploadFileFormData) => {
const response = await request.put(`${apiPrefix}/files/${id}`, data);
return response as UploadFileResponse;
};
// 删除文件 文件夹 id
export const deleteFile = async (id: string) => {
const response = await request.delete(`${apiPrefix}/files/${id}`);
return response as any;
};
// 获取文档列表
export const getDocumentList = async (query: PathQuery) => {
const response = await request.get(`${apiPrefix}/documents/${query.kb_id}/${query.parent_id}/documents`, query);
return response as KnowledgeBaseDocumentData[];
};
// 文档详情
export const getDocumentDetail = async (id: string) => {
const response = await request.get(`${apiPrefix}/documents/${id}`);
return response as KnowledgeBaseDocumentData;
};
// 创建文档
export const createDocument = async (data: KnowledgeBaseDocumentData) => {
const response = await request.post(`${apiPrefix}/documents/document`, data);
return response as KnowledgeBaseDocumentData;
};
// 更新文档
export const updateDocument = async (id: string, data: KnowledgeBaseDocumentData) => {
const response = await request.put(`${apiPrefix}/documents/${id}`, data);
return response as KnowledgeBaseDocumentData;
};
// 删除文档
export const deleteDocument = async (id: string) => {
const response = await request.delete(`${apiPrefix}/documents/${id}`);
return response;
};
// 文档解析
export const parseDocument = async (id: string) => {
const response = await request.post(`${apiPrefix}/documents/${id}/chunks`);
return response as any;
};
// 文档分块预览
export const previewDocumentChunk = async (kb_id:string,id: string) => { // id document_id
const response = await request.get(`${apiPrefix}/chunks/${kb_id}/${id}/previewchunks`);
return response as any;
};
//文档分块列表
export const getDocumentChunkList = async (query: PathQuery) => {
const response = await request.get(`${apiPrefix}/chunks/${query.kb_id}/${query.document_id}/chunks`, query);
return response as any;
};
// 回归测试
export const reChunks = async (data: any) => {
const response = await request.post(`${apiPrefix}/chunks/retrieval`, data);
return response as any;
};
// 知识库授权 分享空间列表
export const getWorkspaceAuthorizationList = async (kb_id: string) => {
const response = await request.get(`${apiPrefix}/knowledgeshares/${kb_id}/knowledgeshares`);
return response as any;
};
// 知识库分享
export const shareKnowledgeBase = async (data: ShareRequestParams) => {
const response = await request.post(`${apiPrefix}/knowledgeshares/knowledgeshare`, data);
return response as KnowledgeBase;
}
// 空间列表
export const getSpaceList = async () => {
const response = await request.get(`${apiPrefix}/workspaces`,{include_current:false});
// API 返回的 data 直接是数组,需要包装成 { items: [] } 格式以保持一致性
if (Array.isArray(response)) {
return { items: response };
}
return response as { items: SpaceItem[] };
};
// 更新文档块儿
export const updateDocumentChunk = async (kb_id:string, document_id:string, doc_id:string, data: any) => {
const response = await request.put(`${apiPrefix}/chunks/${kb_id}/${document_id}/${doc_id}`, data);
return response as any;
};
// 文档块儿创建
export const createDocumentChunk = async (kb_id:string, document_id:string, data: any) => {
const response = await request.post(`${apiPrefix}/chunks/${kb_id}/${document_id}/chunk`, data);
return response as any;
};
// 获取检索模式类型
export const getRetrievalModeType = async () => {
const response = await request.get(`${apiPrefix}/chunks/retrieve_type`);
return response as any;
};

View File

@@ -359,4 +359,4 @@ export interface ShareSpaceModalRef{
export interface ShareSpaceModalRefProps {
handleShare?: () => void;
}
}

View File

@@ -4,7 +4,7 @@ import { Col, Row, App, Skeleton, Space, Select } from 'antd'
import clsx from 'clsx'
import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg'
import AnalysisEmptyIcon from '@/assets/images/conversation/analysisEmpty.svg'
import AnalysisEmptyIcon from '@/assets/images/conversation/analysisEmpty.png'
import Card from './components/Card'
import Chat from './components/Chat'
import { readService, getUserMemoryList } from '@/api/memory'
@@ -139,6 +139,9 @@ const MemoryConversation: FC = () => {
<Empty
url={AnalysisEmptyIcon}
className="rb:h-full"
title={t('memoryConversation.memoryConversationAnalysisEmpty')}
subTitle={t('memoryConversation.memoryConversationAnalysisEmptySubTitle')}
size={[270, 170]}
/>
: <Space size={12} direction="vertical" style={{width: '100%'}}>
{logs.map((log, logIndex) => (

View File

@@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next';
import noPermission from '@/assets/images/empty/noPermission.svg';
import noPermission from '@/assets/images/empty/noPermission.png';
import Empty from '@/components/Empty';
const NoPermission = () => {

View File

@@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next';
import notFoundImg from '@/assets/images/empty/404.svg';
import notFoundImg from '@/assets/images/empty/404.png';
import Empty from '@/components/Empty';
const NotFound = () => {

View File

@@ -1,13 +1,14 @@
import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, Input, App } from 'antd';
import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
import { Form, Input, App, Select } from 'antd';
import { useTranslation } from 'react-i18next';
import type { SpaceModalData, SpaceModalRef, Space } from '../types'
import RbModal from '@/components/RbModal'
import { createWorkspace } from '@/api/workspaces'
import RadioGroupCard from '@/components/RadioGroupCard'
import { getModelListUrl } from '@/api/models'
import { getModelListUrl, getModelList } from '@/api/models'
import CustomSelect from '@/components/CustomSelect'
import type { Model } from '@/views/ModelManagement/types'
const FormItem = Form.Item;
@@ -28,6 +29,7 @@ const SpaceModal = forwardRef<SpaceModalRef, SpaceModalProps>(({
const [form] = Form.useForm<SpaceModalData>();
const [loading, setLoading] = useState(false)
const [editVo, setEditVo] = useState<Space | null>(null)
const [modelList, setModelList] = useState<Model[]>([])
const values = Form.useWatch([], form);
@@ -73,6 +75,21 @@ const SpaceModal = forwardRef<SpaceModalRef, SpaceModalProps>(({
});
}
useEffect(() => {
getModels()
}, [])
const getModels = () => {
const requests = [getModelList({ type: 'llm', pagesize: 100, page: 1 }), getModelList({ type: 'chat', pagesize: 100, page: 1 })]
Promise.all(requests)
.then(responses => {
const [chatRes, modelRes] = responses as { items: Model[] }[]
const chatList = chatRes.items || []
const modelList = modelRes.items || []
setModelList([...chatList, ...modelList])
})
}
// 暴露给父组件的方法
useImperativeHandle(ref, () => ({
handleOpen,
@@ -104,13 +121,13 @@ const SpaceModal = forwardRef<SpaceModalRef, SpaceModalProps>(({
name="llm"
rules={[{ required: true, message: t('common.pleaseSelect') }]}
>
<CustomSelect
url={getModelListUrl}
params={{ type: 'llm', pagesize: 100 }}
valueKey="id"
labelKey="name"
hasAll={false}
style={{width: '100%'}}
<Select
placeholder={t('common.pleaseSelect')}
fieldNames={{
label: 'name',
value: 'id',
}}
options={modelList}
/>
</Form.Item>
<Form.Item

View File

@@ -10,6 +10,7 @@ const Colors = ['#155EEF', '#4DA8FF', '#03BDFF', '#31E8FF', '#AD88FF', '#FFB048'
const PieCard: FC = () => {
const { id } = useParams()
const chartRef = useRef<ReactEcharts>(null);
const resizeScheduledRef = useRef(false)
const [loading, setLoading] = useState(false)
const [data, setData] = useState<Array<Record<string, string | number>>>([])
@@ -30,6 +31,29 @@ const PieCard: FC = () => {
})
}
useEffect(() => {
const handleResize = () => {
if (chartRef.current && !resizeScheduledRef.current) {
resizeScheduledRef.current = true
requestAnimationFrame(() => {
chartRef.current?.getEchartsInstance().resize();
resizeScheduledRef.current = false
});
}
}
const resizeObserver = new ResizeObserver(handleResize)
const chartElement = chartRef.current?.getEchartsInstance().getDom().parentElement
if (chartElement) {
resizeObserver.observe(chartElement)
}
return () => {
resizeObserver.disconnect()
}
}, [data])
return (
<>
{loading
@@ -108,17 +132,6 @@ const PieCard: FC = () => {
style={{ height: '340px', width: '100%' }}
notMerge={true}
lazyUpdate={true}
onEvents={{
// 图表渲染完成后再次调整大小,确保宽度正确
// 使用 setTimeout 避免在主渲染过程中调用 resize
rendered: () => {
if (chartRef.current) {
setTimeout(() => {
chartRef.current?.getEchartsInstance().resize();
}, 0);
}
}
}}
/>
}
</>

View File

@@ -23,6 +23,7 @@ const RelationshipNetwork:FC = () => {
const { t } = useTranslation()
const { id } = useParams()
const chartRef = useRef<ReactEcharts>(null)
const resizeScheduledRef = useRef(false)
const [nodes, setNodes] = useState<Node[]>([])
const [links, setLinks] = useState<Edge[]>([])
const [categories, setCategories] = useState<{ name: string }[]>([])
@@ -83,6 +84,28 @@ const RelationshipNetwork:FC = () => {
setNodes(uniqueNodes)
})
}
useEffect(() => {
const handleResize = () => {
if (chartRef.current && !resizeScheduledRef.current) {
resizeScheduledRef.current = true
requestAnimationFrame(() => {
chartRef.current?.getEchartsInstance().resize();
resizeScheduledRef.current = false
});
}
}
const resizeObserver = new ResizeObserver(handleResize)
const chartElement = chartRef.current?.getEchartsInstance().getDom().parentElement
if (chartElement) {
resizeObserver.observe(chartElement)
}
return () => {
resizeObserver.disconnect()
}
}, [nodes])
return (
<>
{/* 关系网络 */}
@@ -147,15 +170,6 @@ const RelationshipNetwork:FC = () => {
notMerge={false}
lazyUpdate={true}
onEvents={{
// 图表渲染完成后再次调整大小,确保宽度正确
// 使用 setTimeout 避免在主渲染过程中调用 resize
rendered: () => {
if (chartRef.current) {
setTimeout(() => {
chartRef.current?.getEchartsInstance().resize();
}, 0);
}
},
// 节点点击事件处理
click: (params: { dataType: string; data: Node }) => {
if (params.dataType === 'node') {