update web
This commit is contained in:
205
web/i18n-comparison-report.md
Normal file
205
web/i18n-comparison-report.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# i18n 中英文对比报告
|
||||
|
||||
## 📊 统计概览
|
||||
|
||||
- **中文键总数**: 1136
|
||||
- **英文键总数**: 1052
|
||||
- **中文缺失**: 27 个键
|
||||
- **英文缺失**: 111 个键
|
||||
|
||||
---
|
||||
|
||||
## ❌ 英文缺失的翻译(111个)
|
||||
|
||||
### 1. Application 模块 (3个)
|
||||
- `application.cluster` - 集群
|
||||
- `application.clusterDesc` - 创建Agent集群
|
||||
- `application.fullAmount` - 全量
|
||||
|
||||
### 2. Role 角色管理模块 (15个)
|
||||
- `role.roleManagement` - 角色管理
|
||||
- `role.roleId` - 角色ID
|
||||
- `role.roleName` - 角色名称
|
||||
- `role.roleCode` - 角色编码
|
||||
- `role.description` - 角色描述
|
||||
- `role.status` - 状态
|
||||
- `role.enabled` - 已启用
|
||||
- `role.disabled` - 已停用
|
||||
- `role.createTime` - 创建时间
|
||||
- `role.createRole` - 新建角色
|
||||
- `role.editRole` - 编辑角色
|
||||
- `role.roleTemplate` - 角色模板
|
||||
- `role.emptyTemplate` - 空模板
|
||||
- `role.adminTemplate` - 管理员模板
|
||||
- `role.userTemplate` - 用户模板
|
||||
- `role.confirmDelete` - 确定要删除这个角色吗?
|
||||
- `role.createSuccess` - 角色创建成功
|
||||
- `role.updateSuccess` - 角色更新成功
|
||||
- `role.deleteSuccess` - 角色删除成功
|
||||
- `role.createFailed` - 角色创建失败
|
||||
- `role.updateFailed` - 角色更新失败
|
||||
- `role.deleteFailed` - 角色删除失败
|
||||
|
||||
### 3. Tenant 租户管理模块 (20个)
|
||||
- `tenant.tenantId` - 租户ID
|
||||
- `tenant.tenantName` - 租户名称
|
||||
- `tenant.contactPerson` - 联系人
|
||||
- `tenant.contactInfo` - 联系方式
|
||||
- `tenant.status` - 状态
|
||||
- `tenant.enabled` - 启用
|
||||
- `tenant.disabled` - 禁用
|
||||
- `tenant.expiryDate` - 到期时间
|
||||
- `tenant.createTenant` - 新增租户
|
||||
- `tenant.editTenant` - 编辑租户
|
||||
- `tenant.searchPlaceholder` - 搜索租户ID、名称、联系人或联系方式
|
||||
- `tenant.confirmDelete` - 确定要删除该租户吗?
|
||||
- `tenant.confirmBatchDelete` - 确定要批量删除选中的租户吗?
|
||||
- `tenant.fetchFailed` - 获取租户数据失败
|
||||
- `tenant.batchEnableSuccess` - 批量启用成功
|
||||
- `tenant.batchEnableFailed` - 批量启用失败
|
||||
- `tenant.batchDisableSuccess` - 批量停用成功
|
||||
- `tenant.batchDisableFailed` - 批量停用失败
|
||||
- `tenant.exportSuccess` - 导出成功
|
||||
- `tenant.batchDeleteSuccess` - 批量删除成功
|
||||
- `tenant.batchDeleteFailed` - 批量删除失败
|
||||
- `tenant.saveFailed` - 保存失败
|
||||
- `tenant.batchImport` - 批量导入
|
||||
|
||||
### 4. User 用户管理模块 (13个)
|
||||
- `user.tenantName` - 所属租户
|
||||
- `user.password` - 密码
|
||||
- `user.expiryDate` - 有效期
|
||||
- `user.expiryDateDue` - 有效期至
|
||||
- `user.batchImport` - 批量导入
|
||||
- `user.batchImportUser` - 批量导入用户
|
||||
- `user.downloadTemplate` - 下载导入模板
|
||||
- `user.templateDownloadSuccess` - 模板下载成功
|
||||
- `user.startImport` - 开始导入
|
||||
- `user.batchImportSuccess` - 批量导入成功
|
||||
- `user.importFailed` - 导入失败,请检查文件格式
|
||||
- `user.noFileSelected` - 请选择要导入的文件
|
||||
- `user.onlyXlsxOrCsv` - 只能上传 .xlsx 或 .csv 格式的文件
|
||||
- `user.reselect` - 重新选择
|
||||
- `user.noFileSelectedTip` - 未选择任何文件
|
||||
- `user.downloadTemplateTip` - 请下载模板,填写用户信息后上传。
|
||||
|
||||
### 5. Product 产品管理模块 (13个)
|
||||
- `product.applicationManagement` - 应用管理
|
||||
- `product.createApplication` - 创建应用
|
||||
- `product.applicationName` - 应用名称
|
||||
- `product.applicationIcon` - 应用图标
|
||||
- `product.applicationNameRequired` - 请输入应用名称
|
||||
- `product.associationStatus` - 关联状态
|
||||
- `product.associated` - 已关联
|
||||
- `product.notAssociated` - 未关联
|
||||
- `product.unassociate` - 解除关联
|
||||
- `product.unassociateSuccess` - 解除关联成功
|
||||
- `product.unassociateFailed` - 解除关联失败
|
||||
- `product.viewKey` - 查看KEY
|
||||
- `product.viewStats` - 查看统计
|
||||
- `product.disableSuccess` - 停用成功
|
||||
- `product.enableSuccess` - 启用成功
|
||||
- `product.operationFailed` - 操作失败
|
||||
|
||||
### 6. 其他模块 (47个)
|
||||
- `count` - 计数: {{count}}
|
||||
- `increment` - 增加
|
||||
- `decrement` - 减少
|
||||
- `reset` - 重置
|
||||
- `switchLanguage` - 切换语言
|
||||
- `home.title` - 首页
|
||||
- `home.welcome` - 欢迎使用我们的带单页路由的 React 应用!
|
||||
- `home.counterCard` - 计数器演示
|
||||
- `home.aboutCard` - 关于我们
|
||||
- `home.workflowCard` - 工作流编辑器
|
||||
- `home.websocketDemoCard` - WebSocket 演示
|
||||
- `home.sseDemoCard` - SSE演示
|
||||
- `workflow.title` - 工作流编辑器
|
||||
- `workflow.description` - 拖拽节点创建连接,构建您的工作流程。点击节点可进行配置。
|
||||
- `workflow.addNode` - 添加节点
|
||||
- `workflow.deleteNode` - 删除选中
|
||||
- `workflow.saveWorkflow` - 保存工作流
|
||||
- `workflow.startNode` - 触发节点
|
||||
- `workflow.conditionNode` - 条件判断
|
||||
- `workflow.actionNode` - 执行动作
|
||||
- `workflow.endNode` - 结束节点
|
||||
- `workflow.newNode` - 新节点
|
||||
- `workflow.node` - 节点
|
||||
- `workflow.nodesCreated` - 已创建节点
|
||||
- `workflow.loadingNodes` - 正在加载节点 {{progress}}%
|
||||
- `workflow.loadingFailed` - 加载节点失败
|
||||
- `workflow.create5kNodes` - 创建5000节点
|
||||
- `workflow.create10kNodes` - 创建10000节点
|
||||
- `notFound.title` - 页面未找到
|
||||
- `notFound.description` - 请求的页面不存在。
|
||||
- `notFound.backToHome` - 返回首页
|
||||
|
||||
---
|
||||
|
||||
## ✅ 中文缺失的翻译(27个)
|
||||
|
||||
### 1. Common 通用模块 (1个)
|
||||
- `common.operateSuccess` - Operation successful
|
||||
|
||||
### 2. KnowledgeBase 知识库模块 (3个)
|
||||
- `knowledgeBase.models` - Model
|
||||
- `knowledgeBase.owner` - Owner
|
||||
- `knowledgeBase.operation` - Operation
|
||||
|
||||
### 3. Application 应用模块 (15个)
|
||||
- `application.multi_agent` - Cluster
|
||||
- `application.multi_agentDesc` - Create an Agent Cluster
|
||||
- `application.current` - Current
|
||||
- `application.versionName` - Version Name
|
||||
- `application.versionNameTip` - Version number format: v[major version number].[next version number].[revision number] (e.g. v1.3.0)
|
||||
- `application.agentName` - Agent Name
|
||||
- `application.roleType` - Role Type
|
||||
- `application.coordinator` - Coordinator
|
||||
- `application.analyzer` - Analyzer
|
||||
- `application.executor` - Executor
|
||||
- `application.reviewer` - Reviewer
|
||||
- `application.updateSubAgent` - Update Sub Agent
|
||||
- `application.subAgentMaxLength` - Sub Agent maximum {{maxLength}}
|
||||
- `application.capabilities` - Capabilities
|
||||
|
||||
### 4. Space 空间模块 (5个)
|
||||
- `space.storageType` - Storage Type
|
||||
- `space.rag` - RAG storage
|
||||
- `space.ragDesc` - Based on vector retrieval, suitable for document Q&A and semantic search
|
||||
- `space.neo4j` - Graph storage
|
||||
- `space.neo4jDesc` - Based on knowledge graph, suitable for relational reasoning and path query
|
||||
|
||||
### 5. MemoryExtractionEngine 记忆提取引擎模块 (4个)
|
||||
- `memoryExtractionEngine.coreEntitiesAfterDedup` - Core entities after deduplication
|
||||
- `memoryExtractionEngine.extractRelationalTriples` - Extracted relational triples (partial)
|
||||
- `memoryExtractionEngine.extractRelationalTriplesDesc` - There are a total of {{count}} segments with clear semantic boundaries
|
||||
- `memoryExtractionEngine.theEffectOfEntityDisambiguationLLMDriven` - The effect of entity disambiguation (LLM driven)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 建议
|
||||
|
||||
### 优先级 1 - 核心功能模块(需要立即补充)
|
||||
1. **Role 角色管理** - 完整模块缺失(15个键)
|
||||
2. **Tenant 租户管理** - 完整模块缺失(20个键)
|
||||
3. **Product 产品管理** - 完整模块缺失(13个键)
|
||||
4. **User 用户管理扩展** - 批量导入功能缺失(13个键)
|
||||
|
||||
### 优先级 2 - 功能增强(建议补充)
|
||||
1. **Application 应用模块** - 多代理相关功能(15个键)
|
||||
2. **Space 空间模块** - 存储类型配置(5个键)
|
||||
3. **MemoryExtractionEngine** - 实体去重相关(4个键)
|
||||
|
||||
### 优先级 3 - 演示/测试功能(可选)
|
||||
1. **Home/Workflow/NotFound** - 演示页面(30个键)
|
||||
2. **通用计数器功能** - 测试功能(5个键)
|
||||
|
||||
---
|
||||
|
||||
## 📝 下一步行动
|
||||
|
||||
1. **补充英文翻译**: 优先补充 Role、Tenant、Product、User 模块的英文翻译
|
||||
2. **补充中文翻译**: 补充 Application、Space、MemoryExtractionEngine 模块的中文翻译
|
||||
3. **清理无用翻译**: 如果 Home/Workflow 等演示功能不再使用,可以考虑从中文文件中移除
|
||||
4. **建立翻译规范**: 建议建立翻译键的命名规范和审查流程,避免未来出现遗漏
|
||||
|
||||
10
web/public/auto-imports.d.ts
vendored
10
web/public/auto-imports.d.ts
vendored
@@ -6,31 +6,22 @@
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const Activity: typeof import('react').Activity
|
||||
const Fragment: typeof import('react').Fragment
|
||||
const Link: typeof import('react-router-dom').Link
|
||||
const NavLink: typeof import('react-router-dom').NavLink
|
||||
const Navigate: typeof import('react-router-dom').Navigate
|
||||
const Outlet: typeof import('react-router-dom').Outlet
|
||||
const Route: typeof import('react-router-dom').Route
|
||||
const Routes: typeof import('react-router-dom').Routes
|
||||
const Suspense: typeof import('react').Suspense
|
||||
const cache: typeof import('react').cache
|
||||
const cacheSignal: typeof import('react').cacheSignal
|
||||
const createContext: typeof import('react').createContext
|
||||
const createRef: typeof import('react').createRef
|
||||
const forwardRef: typeof import('react').forwardRef
|
||||
const lazy: typeof import('react').lazy
|
||||
const memo: typeof import('react').memo
|
||||
const startTransition: typeof import('react').startTransition
|
||||
const use: typeof import('react').use
|
||||
const useActionState: typeof import('react').useActionState
|
||||
const useCallback: typeof import('react').useCallback
|
||||
const useContext: typeof import('react').useContext
|
||||
const useDebugValue: typeof import('react').useDebugValue
|
||||
const useDeferredValue: typeof import('react').useDeferredValue
|
||||
const useEffect: typeof import('react').useEffect
|
||||
const useEffectEvent: typeof import('react').useEffectEvent
|
||||
const useHref: typeof import('react-router-dom').useHref
|
||||
const useId: typeof import('react').useId
|
||||
const useImperativeHandle: typeof import('react').useImperativeHandle
|
||||
@@ -42,7 +33,6 @@ declare global {
|
||||
const useMemo: typeof import('react').useMemo
|
||||
const useNavigate: typeof import('react-router-dom').useNavigate
|
||||
const useNavigationType: typeof import('react-router-dom').useNavigationType
|
||||
const useOptimistic: typeof import('react').useOptimistic
|
||||
const useOutlet: typeof import('react-router-dom').useOutlet
|
||||
const useOutletContext: typeof import('react-router-dom').useOutletContext
|
||||
const useParams: typeof import('react-router-dom').useParams
|
||||
|
||||
@@ -28,6 +28,8 @@ import 'dayjs/locale/zh-cn'
|
||||
import 'dayjs/plugin/timezone'
|
||||
import 'dayjs/plugin/utc'
|
||||
|
||||
|
||||
|
||||
function App() {
|
||||
const { t } = useTranslation();
|
||||
const { locale, language, timeZone } = useI18n()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { request } from "@/utils/request";
|
||||
import { request, cookieUtils } from "@/utils/request";
|
||||
import type { AxiosProgressEvent } from "axios";
|
||||
import type {
|
||||
ShareRequestParams,
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
KnowledgeBaseDocumentData,
|
||||
KnowledgeBaseListResponse,
|
||||
KnowledgeBaseShareListResponse,
|
||||
} from "./types";
|
||||
} from "@/views/KnowledgeBase/types";
|
||||
|
||||
const apiPrefix = '';
|
||||
|
||||
@@ -151,7 +151,7 @@ export const uploadFile = async (data: FormData, options?: UploadFileOptions) =>
|
||||
|
||||
// 下载文件
|
||||
export const downloadFile = async (fileId: string, fileName?: string) => {
|
||||
const token = localStorage.getItem('token');
|
||||
const token = cookieUtils.get('authToken');
|
||||
const url = `${apiPrefix}/files/${fileId}`;
|
||||
|
||||
try {
|
||||
@@ -277,4 +277,4 @@ export const createDocumentChunk = async (kb_id:string, document_id:string, data
|
||||
export const getRetrievalModeType = async () => {
|
||||
const response = await request.get(`${apiPrefix}/chunks/retrieve_type`);
|
||||
return response as any;
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import { request } from '@/utils/request'
|
||||
import type { CreateModalData } from '@/views/UserManagement/types'
|
||||
import { cookieUtils } from '@/utils/request'
|
||||
|
||||
// 用户信息
|
||||
export const getUsers = () => {
|
||||
@@ -15,7 +16,7 @@ export const login = (data: { email: string; password: string; invite?: string;
|
||||
// 刷新token
|
||||
export const refreshTokenUrl = '/refresh'
|
||||
export const refreshToken = () => {
|
||||
return request.post(refreshTokenUrl, { refresh_token: localStorage.getItem('refresh_token') })
|
||||
return request.post(refreshTokenUrl, { refresh_token: cookieUtils.get('refreshToken') })
|
||||
}
|
||||
// 重置密码
|
||||
export const changePassword = (data: { user_id: string; new_password: string }) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect, type FC } from 'react';
|
||||
import { Spin, Alert, Button } from 'antd';
|
||||
import { ReloadOutlined } from '@ant-design/icons';
|
||||
import RbMarkdown from '../Markdown';
|
||||
import { cookieUtils } from '@/utils/request'
|
||||
|
||||
type PreviewMode = 'office' | 'google';
|
||||
|
||||
@@ -156,7 +157,7 @@ const DocumentPreview: FC<DocumentPreviewProps> = ({
|
||||
const response = await fetch(requestUrl, {
|
||||
credentials: 'include', // 包含认证信息
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token') || ''}`,
|
||||
'Authorization': `Bearer ${cookieUtils.get('authToken') || ''}`,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ interface EmptyProps {
|
||||
url?: string;
|
||||
size?: number | number[];
|
||||
title?: string;
|
||||
isNeedSubTitle?: boolean;
|
||||
subTitle?: string;
|
||||
className?: string;
|
||||
}
|
||||
@@ -13,6 +14,7 @@ const Empty: FC<EmptyProps> = ({
|
||||
url,
|
||||
size = 200,
|
||||
title,
|
||||
isNeedSubTitle = true,
|
||||
subTitle,
|
||||
className = '',
|
||||
}) => {
|
||||
@@ -20,12 +22,12 @@ const Empty: FC<EmptyProps> = ({
|
||||
const width = Array.isArray(size) ? size[0] : size ? size : url ? 200 : 88;
|
||||
const height = Array.isArray(size) ? size[1] : size ? size : url ? 200 : 88;
|
||||
|
||||
subTitle = subTitle || t('empty.tableEmpty');
|
||||
const curSubTitle = isNeedSubTitle ? (subTitle || t('empty.tableEmpty')) : null;
|
||||
return (
|
||||
<div className={`rb:flex rb:items-center rb:justify-center rb:flex-col ${className}`}>
|
||||
<img src={url || emptyIcon} alt="404" style={{ width: `${width}px`, height: `${height}px` }} />
|
||||
{title && <div className="rb:mt-[8px] rb:leading-[20px]">{title}</div>}
|
||||
{subTitle && <div className={`rb:mt-[${url ? 8 : 5}px] rb:leading-[16px] rb:text-[#5B6167]`}>{subTitle}</div>}
|
||||
{title && <div className="rb:mt-2 rb:leading-5">{title}</div>}
|
||||
{curSubTitle && <div className={`rb:mt-[${url ? 8 : 5}px] rb:leading-4 rb:text-[12px] rb:text-[#A8A9AA]`}>{subTitle}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,10 +66,30 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => {
|
||||
},
|
||||
];
|
||||
const formatBreadcrumbNames = () => {
|
||||
return breadcrumbs.map((menu, index) => ({
|
||||
title: menu.i18nKey ? t(menu.i18nKey) : menu.label,
|
||||
path: index === breadcrumbs.length - 1 ? undefined : menu.path
|
||||
}))
|
||||
return breadcrumbs.map((menu, index) => {
|
||||
const item: any = {
|
||||
title: menu.i18nKey ? t(menu.i18nKey) : menu.label,
|
||||
};
|
||||
|
||||
// 如果是最后一项,不设置 path
|
||||
if (index === breadcrumbs.length - 1) {
|
||||
return item;
|
||||
}
|
||||
|
||||
// 如果有自定义 onClick,使用 onClick 并设置 href 为 '#' 以显示手型光标
|
||||
if ((menu as any).onClick) {
|
||||
item.onClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
(menu as any).onClick(e);
|
||||
};
|
||||
item.href = '#';
|
||||
} else if (menu.path && menu.path !== '#') {
|
||||
// 只有当 path 不是 '#' 时才设置 path
|
||||
item.path = menu.path;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
return (
|
||||
<Header className={styles.header}>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useNavigationBreadcrumbs } from '@/hooks/useNavigationBreadcrumbs';
|
||||
import AppHeader from '@/components/Header';
|
||||
import Sider from '@/components/SiderMenu'
|
||||
import { useUser } from '@/store/user';
|
||||
import { cookieUtils } from '@/utils/request';
|
||||
|
||||
|
||||
const { Content } = Layout;
|
||||
@@ -18,7 +19,12 @@ const AuthLayout: FC = () => {
|
||||
// 自动更新面包屑导航
|
||||
useNavigationBreadcrumbs('manage');
|
||||
useEffect(() => {
|
||||
getUserInfo()
|
||||
const authToken = cookieUtils.get('authToken')
|
||||
if (!authToken && !window.location.hash.includes('#/login')) {
|
||||
window.location.href = `/#/login`;
|
||||
} else {
|
||||
getUserInfo()
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useNavigationBreadcrumbs } from '@/hooks/useNavigationBreadcrumbs';
|
||||
import AppHeader from '@/components/Header';
|
||||
import Sider from '@/components/SiderMenu';
|
||||
import { useUser } from '@/store/user';
|
||||
import { cookieUtils } from '@/utils/request';
|
||||
|
||||
|
||||
const { Content } = Layout;
|
||||
@@ -18,8 +19,13 @@ const AuthSpaceLayout: FC = () => {
|
||||
// 自动更新面包屑导航
|
||||
useNavigationBreadcrumbs('space');
|
||||
useEffect(() => {
|
||||
getUserInfo()
|
||||
getStorageType()
|
||||
const authToken = cookieUtils.get('authToken')
|
||||
if (!authToken && !window.location.hash.includes('#/login')) {
|
||||
window.location.href = `/#/login`;
|
||||
} else {
|
||||
getUserInfo()
|
||||
getStorageType()
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { UploadProps, UploadFile } from 'antd';
|
||||
import type { UploadProps as RcUploadProps } from 'antd/es/upload/interface';
|
||||
import CloudUploadOutlined from '@/assets/images/CloudUploadOutlined.png'
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cookieUtils } from '@/utils/request'
|
||||
|
||||
const { confirm } = Modal;
|
||||
const { Dragger } = Upload;
|
||||
@@ -219,7 +220,7 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
|
||||
fileList,
|
||||
beforeUpload,
|
||||
headers: {
|
||||
authorization: localStorage.getItem('token') || '',
|
||||
authorization: cookieUtils.get('authToken') || '',
|
||||
},
|
||||
onRemove: handleRemove,
|
||||
onChange: handleChange,
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { GetProp, UploadFile, UploadProps } from 'antd';
|
||||
import type { UploadProps as RcUploadProps } from 'antd/es/upload/interface';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PlusIcon from '@/assets/images/plus.svg'
|
||||
import { cookieUtils } from '@/utils/request'
|
||||
|
||||
const { confirm } = Modal;
|
||||
|
||||
@@ -166,7 +167,7 @@ const UploadImages = forwardRef<UploadImagesRef, UploadImagesProps>(({
|
||||
fileList,
|
||||
beforeUpload,
|
||||
headers: {
|
||||
authorization: localStorage.getItem('token') || '',
|
||||
authorization: cookieUtils.get('authToken') || '',
|
||||
},
|
||||
onPreview: handlePreview,
|
||||
onRemove: handleRemove,
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface MenuItem {
|
||||
disposable?: boolean;
|
||||
appSystem?: string | null;
|
||||
subs: MenuItem[] | null;
|
||||
onClick?: (e?: React.MouseEvent) => void | boolean;
|
||||
}
|
||||
interface MenuState {
|
||||
collapsed: boolean;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { clearAuthData } from '@/utils/auth';
|
||||
import type { User } from '@/views/UserManagement/types'
|
||||
import { getUsers, refreshToken, logout } from '@/api/user'
|
||||
import { getWorkspaceStorageType } from '@/api/workspaces';
|
||||
import { cookieUtils } from '@/utils/request'
|
||||
|
||||
export interface LoginInfo {
|
||||
access_token: string;
|
||||
@@ -21,17 +22,18 @@ export interface UserState {
|
||||
logout: () => void;
|
||||
getStorageType: () => void;
|
||||
}
|
||||
|
||||
export const useUser = create<UserState>((set, get) => ({
|
||||
user: localStorage.getItem('user') ? JSON.parse(localStorage.getItem('user') || '{}') as User : {} as User,
|
||||
loginInfo: {} as LoginInfo,
|
||||
storageType: null,
|
||||
updateLoginInfo: (values: LoginInfo) => {
|
||||
localStorage.setItem('token', values.access_token);
|
||||
localStorage.setItem('refresh_token', values.refresh_token);
|
||||
cookieUtils.set('authToken', values.access_token);
|
||||
cookieUtils.set('refreshToken', values.refresh_token);
|
||||
set({ loginInfo: values });
|
||||
},
|
||||
getUserInfo: async (flag?: boolean) => {
|
||||
if (!localStorage.getItem('token')) {
|
||||
if (!cookieUtils.get('authToken')) {
|
||||
return
|
||||
}
|
||||
const localUser = JSON.parse(localStorage.getItem('user') || '{}') as User;
|
||||
@@ -70,7 +72,7 @@ export const useUser = create<UserState>((set, get) => ({
|
||||
refreshToken()
|
||||
.then((res) => {
|
||||
const response = res as { refresh_token: string }
|
||||
localStorage.setItem('token', response.refresh_token);
|
||||
cookieUtils.set('authToken', response.refresh_token);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to refresh token:', err)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { cookieUtils } from './request'
|
||||
export const clearAuthData = () => {
|
||||
console.log("Clearing auth data and redirecting to login");
|
||||
sessionStorage.clear();
|
||||
localStorage.clear()
|
||||
cookieUtils.clear();
|
||||
}
|
||||
@@ -27,6 +27,7 @@ interface data {
|
||||
const service = axios.create({
|
||||
baseURL: '/api', // 与vite.config.ts中的代理配置对应
|
||||
// timeout: 10000, // 请求超时时间
|
||||
withCredentials: false,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
@@ -46,11 +47,12 @@ let requests: RequestQueueItem[] = [];
|
||||
service.interceptors.request.use(
|
||||
(config) => {
|
||||
if (!config.headers.Authorization) {
|
||||
const token = localStorage.getItem('token');
|
||||
const token = cookieUtils.get('authToken');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
}
|
||||
config.headers.Cookie = undefined
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
@@ -63,7 +65,7 @@ service.interceptors.request.use(
|
||||
// 刷新token的函数
|
||||
const tokenRefresh = async (): Promise<string> => {
|
||||
try {
|
||||
const refresh_token = localStorage.getItem('refresh_token');
|
||||
const refresh_token = cookieUtils.get('refreshToken');
|
||||
if (window.location.hash.includes('#/invite-register')) {
|
||||
throw new Error(i18n.t('common.refreshTokenNotExist'));
|
||||
}
|
||||
@@ -73,7 +75,7 @@ const tokenRefresh = async (): Promise<string> => {
|
||||
// 使用原生axios调用refresh接口,避免触发拦截器导致的循环调用
|
||||
const response: any = await refreshToken();
|
||||
const newToken = response.access_token;
|
||||
localStorage.setItem('token', newToken);
|
||||
cookieUtils.set('authToken', newToken);
|
||||
return newToken;
|
||||
} catch (error) {
|
||||
// 如果refresh接口也返回401,则退出登录
|
||||
@@ -274,6 +276,7 @@ export const request = {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
withCredentials: false,
|
||||
...config
|
||||
});
|
||||
},
|
||||
@@ -295,4 +298,38 @@ export const request = {
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 获取父级域名
|
||||
const getParentDomain = () => {
|
||||
const hostname = window.location.hostname
|
||||
const parts = hostname.split('.')
|
||||
return parts.length > 2 ? `.${parts.slice(-2).join('.')}` : hostname
|
||||
}
|
||||
|
||||
// Cookie操作工具
|
||||
export const cookieUtils = {
|
||||
set: (name: string, value: string, domain = getParentDomain()) => {
|
||||
document.cookie = `${name}=${value}; domain=${domain}; path=/; secure; samesite=strict`
|
||||
},
|
||||
get: (name: string) => {
|
||||
const value = `; ${document.cookie}`
|
||||
const parts = value.split(`; ${name}=`)
|
||||
return parts.length === 2 ? parts.pop()?.split(';').shift() : null
|
||||
},
|
||||
remove: (name: string, domain = getParentDomain()) => {
|
||||
document.cookie = `${name}=; domain=${domain}; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`
|
||||
},
|
||||
clear: (domain = getParentDomain()) => {
|
||||
document.cookie.split(';').forEach(cookie => {
|
||||
const eqPos = cookie.indexOf('=');
|
||||
const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
|
||||
if (name) {
|
||||
document.cookie = `${name}=; domain=${domain}; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
||||
document.cookie = `${name}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
export default service;
|
||||
@@ -1,10 +1,11 @@
|
||||
import { message } from 'antd';
|
||||
import i18n from '@/i18n'
|
||||
import { cookieUtils } from './request'
|
||||
const API_PREFIX = '/api'
|
||||
|
||||
export const handleSSE = async (url: string, data: any, onMessage?: (data: string) => void, config = {}) => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const token = cookieUtils.get('authToken');
|
||||
const response = await fetch(`${API_PREFIX}${url}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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.3,lambda_time=1,offset=0.05;
|
||||
// 慢速遗忘:lambda_mem=1,lambda_time=0.3,offset=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);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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');
|
||||
// };
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -359,4 +359,4 @@ export interface ShareSpaceModalRef{
|
||||
|
||||
export interface ShareSpaceModalRefProps {
|
||||
handleShare?: () => void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
|
||||
@@ -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') {
|
||||
|
||||
Reference in New Issue
Block a user