diff --git a/package.json b/package.json
index a5c18c8..3ff1f00 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.4",
"@mui/material": "^7.3.4",
+ "@mui/x-data-grid": "^8.14.0",
"ahooks": "^3.9.5",
"axios": "^1.12.2",
"dayjs": "^1.11.18",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8df5acd..99ab5ee 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,6 +20,9 @@ importers:
'@mui/material':
specifier: ^7.3.4
version: 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@mui/x-data-grid':
+ specifier: ^8.14.0
+ version: 8.14.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
ahooks:
specifier: ^3.9.5
version: 3.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -67,7 +70,7 @@ importers:
version: 13.0.0
zustand:
specifier: ^5.0.8
- version: 5.0.8(@types/react@19.2.2)(react@18.3.1)
+ version: 5.0.8(@types/react@19.2.2)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1))
devDependencies:
'@eslint/js':
specifier: ^9.36.0
@@ -569,6 +572,35 @@ packages:
'@types/react':
optional: true
+ '@mui/x-data-grid@8.14.0':
+ resolution: {integrity: sha512-bzUpD83Wx4mawkgquDQUUbLLnpF+JP7Pe7YQx1ixS6W/AlUwXAVagPTOijwchHvlx0Ky11dJvOQAfrnWu6an/Q==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ '@emotion/react': ^11.9.0
+ '@emotion/styled': ^11.8.1
+ '@mui/material': ^5.15.14 || ^6.0.0 || ^7.0.0
+ '@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@emotion/react':
+ optional: true
+ '@emotion/styled':
+ optional: true
+
+ '@mui/x-internals@8.14.0':
+ resolution: {integrity: sha512-esYyl61nuuFXiN631TWuPh2tqdoyTdBI/4UXgwH3rytF8jiWvy6prPBPRHEH1nvW3fgw9FoBI48FlOO+yEI8xg==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ '@mui/x-virtualizer@0.2.3':
+ resolution: {integrity: sha512-CZ+VxFmeJaTduAOlSyo5cVek0PV5Y8gm4coyaHEpCvms207J9AoMUKqWIcdwsVGlTH1Y71j35xT/MwHKutZiNw==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
+
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -1471,6 +1503,9 @@ packages:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
+ reselect@5.1.1:
+ resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
+
resize-observer-polyfill@1.5.1:
resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==}
@@ -1590,6 +1625,11 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+ use-sync-external-store@1.6.0:
+ resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
uuid@13.0.0:
resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}
hasBin: true
@@ -2116,6 +2156,45 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.2
+ '@mui/x-data-grid@8.14.0(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.4
+ '@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@mui/system': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1)
+ '@mui/utils': 7.3.3(@types/react@19.2.2)(react@18.3.1)
+ '@mui/x-internals': 8.14.0(@types/react@19.2.2)(react@18.3.1)
+ '@mui/x-virtualizer': 0.2.3(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ clsx: 2.1.1
+ prop-types: 15.8.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ use-sync-external-store: 1.6.0(react@18.3.1)
+ optionalDependencies:
+ '@emotion/react': 11.14.0(@types/react@19.2.2)(react@18.3.1)
+ '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+
+ '@mui/x-internals@8.14.0(@types/react@19.2.2)(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.4
+ '@mui/utils': 7.3.3(@types/react@19.2.2)(react@18.3.1)
+ react: 18.3.1
+ reselect: 5.1.1
+ use-sync-external-store: 1.6.0(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+
+ '@mui/x-virtualizer@0.2.3(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@babel/runtime': 7.28.4
+ '@mui/utils': 7.3.3(@types/react@19.2.2)(react@18.3.1)
+ '@mui/x-internals': 8.14.0(@types/react@19.2.2)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -2984,6 +3063,8 @@ snapshots:
dependencies:
loose-envify: 1.4.0
+ reselect@5.1.1: {}
+
resize-observer-polyfill@1.5.1: {}
resolve-from@4.0.0: {}
@@ -3104,6 +3185,10 @@ snapshots:
dependencies:
punycode: 2.3.1
+ use-sync-external-store@1.6.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
uuid@13.0.0: {}
vite@7.1.9(@types/node@24.7.1):
@@ -3132,7 +3217,8 @@ snapshots:
yocto-queue@0.1.0: {}
- zustand@5.0.8(@types/react@19.2.2)(react@18.3.1):
+ zustand@5.0.8(@types/react@19.2.2)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)):
optionalDependencies:
'@types/react': 19.2.2
react: 18.3.1
+ use-sync-external-store: 1.6.0(react@18.3.1)
diff --git a/src/App.tsx b/src/App.tsx
index abf2297..1daf5cc 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -3,6 +3,7 @@ import { CssBaseline, ThemeProvider } from '@mui/material';
import { theme } from './theme';
import AppRoutes from './routes';
import SnackbarProvider from './components/Provider/SnackbarProvider';
+import DialogProvider from './components/Provider/DialogProvider';
import AuthGuard from './components/AuthGuard';
import './locales';
@@ -16,11 +17,13 @@ function MaterialUIApp() {
-
-
-
-
-
+
+
+
+
+
+
+
);
diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx
index a5de7a6..76b7629 100644
--- a/src/components/Layout/Header.tsx
+++ b/src/components/Layout/Header.tsx
@@ -1,9 +1,40 @@
-import { Box, InputBase } from '@mui/material';
+import React, { useState } from 'react';
+import {
+ Box,
+ InputBase,
+ Menu,
+ MenuItem,
+ Avatar,
+ Typography,
+ Divider,
+ ListItemIcon,
+ ListItemText
+} from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
+import LogoutIcon from '@mui/icons-material/Logout';
+import PersonIcon from '@mui/icons-material/Person';
import LanguageSwitcher from '../LanguageSwitcher';
+import { useAuth } from '@/hooks/login_hooks';
const Header = () => {
+ const { userInfo, logout } = useAuth();
+ const [anchorEl, setAnchorEl] = useState(null);
+ const open = Boolean(anchorEl);
+
+ const handleAvatarClick = (event: React.MouseEvent) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ const handleLogout = () => {
+ logout();
+ handleClose();
+ };
+
return (
{
>
RAG Dashboard
-
+
{
placeholder="Search queries, KB names..."
/>
-
+
-
-
+
+ {/* 用户头像和菜单 */}
+
+ {userInfo?.avatar ? (
+
+ ) : (
+
+
+
+ )}
+
+ {/* 用户菜单 */}
+
+
);
};
diff --git a/src/components/Provider/DialogComponent.tsx b/src/components/Provider/DialogComponent.tsx
new file mode 100644
index 0000000..24d0cac
--- /dev/null
+++ b/src/components/Provider/DialogComponent.tsx
@@ -0,0 +1,162 @@
+import React, { useState } from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ Typography,
+ Box,
+ IconButton,
+} from '@mui/material';
+import {
+ Close as CloseIcon,
+ Info as InfoIcon,
+ CheckCircle as SuccessIcon,
+ Warning as WarningIcon,
+ Error as ErrorIcon,
+ Help as ConfirmIcon,
+} from '@mui/icons-material';
+import { type IDialogInstance } from '../../interfaces/common';
+
+interface DialogComponentProps {
+ dialog: IDialogInstance;
+ onClose: (result: boolean) => void;
+}
+
+const DialogComponent: React.FC = ({ dialog, onClose }) => {
+ const [loading, setLoading] = useState(false);
+ const { config } = dialog;
+
+ // 获取对话框图标
+ const getDialogIcon = () => {
+ const iconProps = { sx: { fontSize: 24, mr: 1 } };
+
+ switch (config.type) {
+ case 'info':
+ return ;
+ case 'success':
+ return ;
+ case 'warning':
+ return ;
+ case 'error':
+ return ;
+ case 'confirm':
+ return ;
+ default:
+ return null;
+ }
+ };
+
+ // 获取确认按钮颜色
+ const getConfirmButtonColor = () => {
+ switch (config.type) {
+ case 'error':
+ case 'warning':
+ return 'error';
+ case 'success':
+ return 'success';
+ case 'info':
+ return 'info';
+ default:
+ return 'primary';
+ }
+ };
+
+ // 处理确认操作
+ const handleConfirm = async () => {
+ try {
+ setLoading(true);
+
+ if (config.onConfirm) {
+ await config.onConfirm();
+ }
+
+ onClose(true);
+ } catch (error) {
+ console.error('Dialog confirm error:', error);
+ // 即使出错也关闭对话框,但返回false
+ onClose(false);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 处理取消操作
+ const handleCancel = () => {
+ if (config.onCancel) {
+ config.onCancel();
+ }
+ onClose(false);
+ };
+
+ // 处理遮罩点击
+ const handleBackdropClick = () => {
+ if (config.maskClosable !== false) {
+ handleCancel();
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default DialogComponent;
\ No newline at end of file
diff --git a/src/components/Provider/DialogProvider.tsx b/src/components/Provider/DialogProvider.tsx
new file mode 100644
index 0000000..76fabf3
--- /dev/null
+++ b/src/components/Provider/DialogProvider.tsx
@@ -0,0 +1,117 @@
+import React, { createContext, useContext, useState, useCallback, type PropsWithChildren } from 'react';
+import type { IDialogConfig, IDialogInstance, IDialogContextValue } from '../../interfaces/common';
+import DialogComponent from './DialogComponent';
+
+// 创建Dialog上下文
+export const DialogContext = createContext(null);
+
+// 生成唯一ID的工具函数
+const generateId = () => `dialog_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+
+export const DialogProvider: React.FC = ({ children }) => {
+ const [dialogs, setDialogs] = useState([]);
+
+ // 打开对话框的通用方法
+ const openDialog = useCallback((config: IDialogConfig): Promise => {
+ return new Promise((resolve, reject) => {
+ const id = generateId();
+ const dialogInstance: IDialogInstance = {
+ id,
+ config,
+ resolve,
+ reject,
+ };
+
+ setDialogs(prev => [...prev, dialogInstance]);
+ });
+ }, []);
+
+ // 关闭对话框
+ const closeDialog = useCallback((id: string, result: boolean = false) => {
+ setDialogs(prev => {
+ const dialog = prev.find(d => d.id === id);
+ if (dialog) {
+ dialog.resolve(result);
+ }
+ return prev.filter(d => d.id !== id);
+ });
+ }, []);
+
+ // 确认对话框
+ const confirm = useCallback((config: Omit): Promise => {
+ return openDialog({
+ ...config,
+ type: 'confirm',
+ showCancel: true,
+ confirmText: config.confirmText || '确定',
+ cancelText: config.cancelText || '取消',
+ });
+ }, [openDialog]);
+
+ // 信息对话框
+ const info = useCallback((config: Omit): Promise => {
+ return openDialog({
+ ...config,
+ type: 'info',
+ showCancel: false,
+ confirmText: config.confirmText || '确定',
+ });
+ }, [openDialog]);
+
+ // 成功对话框
+ const success = useCallback((config: Omit): Promise => {
+ return openDialog({
+ ...config,
+ type: 'success',
+ showCancel: false,
+ confirmText: config.confirmText || '确定',
+ });
+ }, [openDialog]);
+
+ // 警告对话框
+ const warning = useCallback((config: Omit): Promise => {
+ return openDialog({
+ ...config,
+ type: 'warning',
+ showCancel: false,
+ confirmText: config.confirmText || '确定',
+ });
+ }, [openDialog]);
+
+ // 错误对话框
+ const error = useCallback((config: Omit): Promise => {
+ return openDialog({
+ ...config,
+ type: 'error',
+ showCancel: false,
+ confirmText: config.confirmText || '确定',
+ });
+ }, [openDialog]);
+
+ const contextValue: IDialogContextValue = {
+ dialogs,
+ openDialog,
+ closeDialog,
+ confirm,
+ info,
+ success,
+ warning,
+ error,
+ };
+
+ return (
+
+ {children}
+ {/* 渲染所有对话框 */}
+ {dialogs.map(dialog => (
+ closeDialog(dialog.id, result)}
+ />
+ ))}
+
+ );
+};
+
+export default DialogProvider;
\ No newline at end of file
diff --git a/src/components/KnowledgeGridView.tsx b/src/components/knowledge/KnowledgeCard.tsx
similarity index 57%
rename from src/components/KnowledgeGridView.tsx
rename to src/components/knowledge/KnowledgeCard.tsx
index 3605438..95900cb 100644
--- a/src/components/KnowledgeGridView.tsx
+++ b/src/components/knowledge/KnowledgeCard.tsx
@@ -20,27 +20,13 @@ import {
} from '@mui/icons-material';
import type { IKnowledge } from '@/interfaces/database/knowledge';
-interface KnowledgeGridViewProps {
- knowledgeBases: IKnowledge[];
- maxItems?: number;
- showSeeAll?: boolean;
- onSeeAll?: () => void;
- onEdit?: (kb: IKnowledge) => void;
- onDelete?: (kb: IKnowledge) => void;
- onView?: (kb: IKnowledge) => void;
- loading?: boolean;
- // 新增属性用于控制空状态显示
- searchTerm?: string;
- teamFilter?: string;
- onCreateKnowledge?: () => void;
-}
-
interface KnowledgeCardProps {
knowledge: IKnowledge;
onMenuClick: (event: React.MouseEvent, kb: IKnowledge) => void;
+ onViewKnowledge: (kb: IKnowledge) => void;
}
-const KnowledgeCard: React.FC = ({ knowledge, onMenuClick }) => {
+const KnowledgeCard: React.FC = ({ knowledge, onMenuClick, onViewKnowledge }) => {
const getStatusInfo = (permission: string) => {
switch (permission) {
case 'me':
@@ -86,13 +72,13 @@ const KnowledgeCard: React.FC = ({ knowledge, onMenuClick })
},
}}
>
-
+ onViewKnowledge(knowledge)}>
{/* 显示avatar */}
{knowledge.avatar ? (
-
) : (
@@ -115,7 +101,10 @@ const KnowledgeCard: React.FC = ({ knowledge, onMenuClick })
/>
onMenuClick(e, knowledge)}
+ onClick={(e) => {
+ e.stopPropagation(); // 阻止事件冒泡
+ onMenuClick(e, knowledge);
+ }}
>
@@ -125,8 +114,8 @@ const KnowledgeCard: React.FC = ({ knowledge, onMenuClick })
= ({ knowledge, onMenuClick })
最后更新: {formatUpdateTime(knowledge.update_time)}
-
+
{/* 显示创建者 */}
{knowledge.nickname && (
@@ -205,127 +194,4 @@ const KnowledgeCard: React.FC = ({ knowledge, onMenuClick })
);
};
-const KnowledgeGridView: React.FC = ({
- knowledgeBases,
- maxItems,
- showSeeAll = false,
- onSeeAll,
- onEdit,
- onDelete,
- onView,
- loading = false,
- searchTerm = '',
- teamFilter = 'all',
- onCreateKnowledge,
-}) => {
- const [anchorEl, setAnchorEl] = React.useState(null);
- const [selectedKB, setSelectedKB] = React.useState(null);
-
- const handleMenuClick = (event: React.MouseEvent, kb: IKnowledge) => {
- setAnchorEl(event.currentTarget);
- setSelectedKB(kb);
- };
-
- const handleMenuClose = () => {
- setAnchorEl(null);
- setSelectedKB(null);
- };
-
- const handleEdit = () => {
- if (selectedKB && onEdit) {
- onEdit(selectedKB);
- }
- handleMenuClose();
- };
-
- const handleDelete = () => {
- if (selectedKB && onDelete) {
- onDelete(selectedKB);
- }
- handleMenuClose();
- };
-
- const handleView = () => {
- if (selectedKB && onView) {
- onView(selectedKB);
- }
- handleMenuClose();
- };
-
- const displayedKBs = maxItems ? knowledgeBases.slice(0, maxItems) : knowledgeBases;
- const hasMore = maxItems && knowledgeBases.length > maxItems;
-
- if (loading) {
- return (
-
- 加载中...
-
- );
- }
-
- if (knowledgeBases.length === 0) {
- return (
-
-
-
- {searchTerm || teamFilter !== 'all' ? '没有找到匹配的知识库' : '暂无知识库'}
-
-
- {searchTerm || teamFilter !== 'all'
- ? '尝试调整搜索条件或筛选器'
- : '创建您的第一个知识库开始使用'
- }
-
- {(!searchTerm && teamFilter === 'all' && onCreateKnowledge) && (
- }
- onClick={onCreateKnowledge}
- >
- 新建知识库
-
- )}
-
- );
- }
-
- return (
-
-
- {displayedKBs.map((kb) => (
-
-
-
- ))}
-
-
- {showSeeAll && hasMore && (
-
- }
- onClick={onSeeAll}
- sx={{ borderRadius: 2 }}
- >
- 查看全部 ({knowledgeBases.length})
-
-
- )}
-
-
-
- );
-};
-
-export default KnowledgeGridView;
\ No newline at end of file
+export default KnowledgeCard;
\ No newline at end of file
diff --git a/src/components/knowledge/KnowledgeGridView.tsx b/src/components/knowledge/KnowledgeGridView.tsx
new file mode 100644
index 0000000..89aee1e
--- /dev/null
+++ b/src/components/knowledge/KnowledgeGridView.tsx
@@ -0,0 +1,161 @@
+import React from 'react';
+import {
+ Box,
+ Typography,
+ Card,
+ CardContent,
+ Grid,
+ Chip,
+ IconButton,
+ Menu,
+ MenuItem,
+ Button,
+ Avatar,
+} from '@mui/material';
+import {
+ MoreVert as MoreVertIcon,
+ Folder as FolderIcon,
+ ArrowForward as ArrowForwardIcon,
+ Add as AddIcon,
+} from '@mui/icons-material';
+import type { IKnowledge } from '@/interfaces/database/knowledge';
+import KnowledgeCard from './KnowledgeCard';
+
+interface KnowledgeGridViewProps {
+ knowledgeBases: IKnowledge[];
+ maxItems?: number;
+ showSeeAll?: boolean;
+ onSeeAll?: () => void;
+ onEdit?: (kb: IKnowledge) => void;
+ onDelete?: (kb: IKnowledge) => void;
+ onView?: (kb: IKnowledge) => void;
+ loading?: boolean;
+ // 新增属性用于控制空状态显示
+ searchTerm?: string;
+ teamFilter?: string;
+ onCreateKnowledge?: () => void;
+}
+
+
+const KnowledgeGridView: React.FC = ({
+ knowledgeBases,
+ maxItems,
+ showSeeAll = false,
+ onSeeAll,
+ onEdit,
+ onDelete,
+ onView,
+ loading = false,
+ searchTerm = '',
+ teamFilter = 'all',
+ onCreateKnowledge,
+}) => {
+ const [anchorEl, setAnchorEl] = React.useState(null);
+ const [selectedKB, setSelectedKB] = React.useState(null);
+
+ const handleMenuClick = (event: React.MouseEvent, kb: IKnowledge) => {
+ setAnchorEl(event.currentTarget);
+ setSelectedKB(kb);
+ };
+
+ const handleMenuClose = () => {
+ setAnchorEl(null);
+ setSelectedKB(null);
+ };
+
+ const handleDelete = () => {
+ if (selectedKB && onDelete) {
+ onDelete(selectedKB);
+ }
+ handleMenuClose();
+ };
+
+ const handleView = () => {
+ if (selectedKB && onView) {
+ onView(selectedKB);
+ }
+ handleMenuClose();
+ }
+
+ const handleViewKnowledge = (kb: IKnowledge) => {
+ if (onView) {
+ onView(kb);
+ }
+ handleMenuClose();
+ };
+
+ const displayedKBs = maxItems ? knowledgeBases.slice(0, maxItems) : knowledgeBases;
+ const hasMore = maxItems && knowledgeBases.length > maxItems;
+
+ if (loading) {
+ return (
+
+ 加载中...
+
+ );
+ }
+
+ if (knowledgeBases.length === 0) {
+ return (
+
+
+
+ {searchTerm || teamFilter !== 'all' ? '没有找到匹配的知识库' : '暂无知识库'}
+
+
+ {searchTerm || teamFilter !== 'all'
+ ? '尝试调整搜索条件或筛选器'
+ : '创建您的第一个知识库开始使用'
+ }
+
+ {(!searchTerm && teamFilter === 'all' && onCreateKnowledge) && (
+ }
+ onClick={onCreateKnowledge}
+ >
+ 新建知识库
+
+ )}
+
+ );
+ }
+
+ return (
+
+
+ {displayedKBs.map((kb) => (
+
+
+
+ ))}
+
+
+ {showSeeAll && hasMore && (
+
+ }
+ onClick={onSeeAll}
+ sx={{ borderRadius: 2 }}
+ >
+ 查看全部 ({knowledgeBases.length})
+
+
+ )}
+
+
+
+ );
+};
+
+export default KnowledgeGridView;
\ No newline at end of file
diff --git a/src/examples/DialogExample.tsx b/src/examples/DialogExample.tsx
new file mode 100644
index 0000000..02e1687
--- /dev/null
+++ b/src/examples/DialogExample.tsx
@@ -0,0 +1,193 @@
+import React from 'react';
+import { Box, Button, Typography, Stack } from '@mui/material';
+import { useDialog } from '../hooks/useDialog';
+
+const DialogExample: React.FC = () => {
+ const dialog = useDialog();
+
+ // 确认对话框示例
+ const handleConfirmDialog = async () => {
+ try {
+ const confirmed = await dialog.confirm({
+ title: '确认删除',
+ content: `确定要删除用户 "张三" 吗?此操作不可恢复。`,
+ confirmText: '删除',
+ cancelText: '取消',
+ onConfirm: async () => {
+ // 模拟异步操作
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ console.log('用户已删除');
+ }
+ });
+
+ if (confirmed) {
+ console.log('用户确认删除');
+ } else {
+ console.log('用户取消删除');
+ }
+ } catch (error) {
+ console.error('删除操作失败:', error);
+ }
+ };
+
+ // 信息对话框示例
+ const handleInfoDialog = async () => {
+ await dialog.info({
+ title: '系统信息',
+ content: '这是一个信息提示对话框,用于显示重要信息。',
+ confirmText: '我知道了'
+ });
+ };
+
+ // 成功对话框示例
+ const handleSuccessDialog = async () => {
+ await dialog.success({
+ title: '操作成功',
+ content: '您的操作已成功完成!',
+ confirmText: '好的'
+ });
+ };
+
+ // 警告对话框示例
+ const handleWarningDialog = async () => {
+ await dialog.warning({
+ title: '警告',
+ content: '请注意:此操作可能会影响系统性能,建议在非高峰期执行。',
+ confirmText: '了解'
+ });
+ };
+
+ // 错误对话框示例
+ const handleErrorDialog = async () => {
+ await dialog.error({
+ title: '操作失败',
+ content: '抱歉,操作执行失败。请检查网络连接后重试。',
+ confirmText: '重试'
+ });
+ };
+
+ // 自定义对话框示例
+ const handleCustomDialog = async () => {
+ const result = await dialog.openDialog({
+ title: '自定义对话框',
+ content: (
+
+
+ 这是一个自定义内容的对话框。
+
+
+ 您可以在这里放置任何React组件。
+
+
+ ),
+ type: 'confirm',
+ confirmText: '同意',
+ cancelText: '拒绝',
+ width: 600,
+ maskClosable: false,
+ onConfirm: async () => {
+ await new Promise(resolve => setTimeout(resolve, 500));
+ console.log('用户同意了自定义操作');
+ }
+ });
+
+ console.log('自定义对话框结果:', result);
+ };
+
+ return (
+
+
+ Dialog 使用示例
+
+
+
+ 以下是各种类型对话框的使用示例:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 使用方法:
+
+
+{`const dialog = useDialog();
+
+// 确认对话框
+const confirmed = await dialog.confirm({
+ title: '确认删除',
+ content: '确定要删除用户 "张三" 吗?此操作不可恢复。',
+ confirmText: '删除',
+ cancelText: '取消',
+});
+
+// 信息对话框
+await dialog.info({
+ title: '系统信息',
+ content: '这是一个信息提示。',
+});
+
+// 其他类型
+await dialog.success({ ... });
+await dialog.warning({ ... });
+await dialog.error({ ... });`}
+
+
+
+ );
+};
+
+export default DialogExample;
\ No newline at end of file
diff --git a/src/hooks/knowledge_hooks.ts b/src/hooks/knowledge_hooks.ts
index bdeb93e..2a0096e 100644
--- a/src/hooks/knowledge_hooks.ts
+++ b/src/hooks/knowledge_hooks.ts
@@ -185,4 +185,212 @@ export const useKnowledgeDetail = (kbId: string) => {
error,
refresh: fetchKnowledgeDetail,
};
-};
\ No newline at end of file
+};
+
+/**
+ * 知识库操作Hook
+ * 提供创建、更新、删除知识库的功能
+ */
+export const useKnowledgeOperations = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ /**
+ * 创建知识库
+ */
+ const createKnowledge = useCallback(async (data: Partial) => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ const response = await knowledgeService.createKnowledge(data);
+
+ if (response.data.code === 0) {
+ return response.data.data;
+ } else {
+ throw new Error(response.data.message || '创建知识库失败');
+ }
+ } catch (err: any) {
+ const errorMessage = err.response?.data?.message || err.message || '创建知识库失败';
+ setError(errorMessage);
+ console.error('Failed to create knowledge:', err);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ /**
+ * 更新知识库基础信息
+ * 包括名称、描述、语言等基本信息
+ */
+ const updateKnowledgeBasicInfo = useCallback(async (data: {
+ id: string;
+ name?: string;
+ description?: string;
+ language?: string;
+ avatar?: any;
+ permission?: string;
+ }) => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ const updateData = {
+ kb_id: data.id,
+ ...data,
+ };
+
+ const response = await knowledgeService.updateKnowledge(updateData);
+
+ if (response.data.code === 0) {
+ return response.data.data;
+ } else {
+ throw new Error(response.data.message || '更新知识库基础信息失败');
+ }
+ } catch (err: any) {
+ const errorMessage = err.response?.data?.message || err.message || '更新知识库基础信息失败';
+ setError(errorMessage);
+ console.error('Failed to update knowledge basic info:', err);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ /**
+ * 更新知识库模型配置
+ * 包括嵌入模型、解析器配置、相似度阈值等
+ */
+ const updateKnowledgeModelConfig = useCallback(async (data: {
+ id: string;
+ embd_id?: string;
+ // parser_config?: Partial;
+ similarity_threshold?: number;
+ vector_similarity_weight?: number;
+ parser_id?: string;
+ }) => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ const updateData = {
+ kb_id: data.id,
+ ...data,
+ };
+
+ const response = await knowledgeService.updateKnowledge(updateData);
+
+ if (response.data.code === 0) {
+ return response.data.data;
+ } else {
+ throw new Error(response.data.message || '更新知识库模型配置失败');
+ }
+ } catch (err: any) {
+ const errorMessage = err.response?.data?.message || err.message || '更新知识库模型配置失败';
+ setError(errorMessage);
+ console.error('Failed to update knowledge model config:', err);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ /**
+ * 删除知识库
+ */
+ const deleteKnowledge = useCallback(async (kbId: string) => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ const response = await knowledgeService.removeKnowledge({ kb_id: kbId });
+
+ if (response.data.code === 0) {
+ return response.data.data;
+ } else {
+ throw new Error(response.data.message || '删除知识库失败');
+ }
+ } catch (err: any) {
+ const errorMessage = err.response?.data?.message || err.message || '删除知识库失败';
+ setError(errorMessage);
+ console.error('Failed to delete knowledge:', err);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ /**
+ * 清除错误状态
+ */
+ const clearError = useCallback(() => {
+ setError(null);
+ }, []);
+
+ return {
+ loading,
+ error,
+ createKnowledge,
+ updateKnowledgeBasicInfo,
+ updateKnowledgeModelConfig,
+ deleteKnowledge,
+ clearError,
+ };
+};
+
+/**
+ * 知识库批量操作Hook
+ * 提供批量删除等功能
+ */
+export const useKnowledgeBatchOperations = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ /**
+ * 批量删除知识库
+ */
+ const batchDeleteKnowledge = useCallback(async (kbIds: string[]) => {
+ try {
+ setLoading(true);
+ setError(null);
+
+ const results = await Promise.allSettled(
+ kbIds.map(kbId => knowledgeService.removeKnowledge({ kb_id: kbId }))
+ );
+
+ const failures = results
+ .map((result, index) => ({ result, index }))
+ .filter(({ result }) => result.status === 'rejected')
+ .map(({ index }) => kbIds[index]);
+
+ if (failures.length > 0) {
+ throw new Error(`删除失败的知识库: ${failures.join(', ')}`);
+ }
+
+ return results;
+ } catch (err: any) {
+ const errorMessage = err.response?.data?.message || err.message || '批量删除知识库失败';
+ setError(errorMessage);
+ console.error('Failed to batch delete knowledge:', err);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ /**
+ * 清除错误状态
+ */
+ const clearError = useCallback(() => {
+ setError(null);
+ }, []);
+
+ return {
+ loading,
+ error,
+ batchDeleteKnowledge,
+ clearError,
+ };
+};
+
diff --git a/src/hooks/login_hooks.ts b/src/hooks/login_hooks.ts
index 146df28..2f1a15c 100644
--- a/src/hooks/login_hooks.ts
+++ b/src/hooks/login_hooks.ts
@@ -99,8 +99,10 @@ export const useAuth = () => {
// 登出功能
const logout = () => {
localStorage.removeItem('token');
+ localStorage.removeItem('Authorization');
localStorage.removeItem('userInfo');
setToken(null);
+ setAuthorization(null);
setUserInfo(null);
navigate('/login');
};
diff --git a/src/hooks/useDialog.ts b/src/hooks/useDialog.ts
new file mode 100644
index 0000000..33e7be7
--- /dev/null
+++ b/src/hooks/useDialog.ts
@@ -0,0 +1,12 @@
+import { useContext } from 'react';
+import { DialogContext } from '../components/Provider/DialogProvider';
+import { type IDialogContextValue } from '../interfaces/common';
+
+// 导出useDialog hook
+export const useDialog = (): IDialogContextValue => {
+ const context = useContext(DialogContext);
+ if (!context) {
+ throw new Error('useDialog must be used within a DialogProvider');
+ }
+ return context;
+};
diff --git a/src/interfaces/common.ts b/src/interfaces/common.ts
index e0840bb..7ae0275 100644
--- a/src/interfaces/common.ts
+++ b/src/interfaces/common.ts
@@ -1,3 +1,5 @@
+import React from 'react';
+
export interface Pagination {
current: number;
pageSize: number;
@@ -23,3 +25,35 @@ export interface ResponseType {
message?: string;
data?: any;
}
+
+// Dialog相关接口定义
+export interface IDialogConfig {
+ title?: string;
+ content?: React.ReactNode;
+ type?: 'info' | 'success' | 'warning' | 'error' | 'confirm';
+ confirmText?: string;
+ cancelText?: string;
+ showCancel?: boolean;
+ maskClosable?: boolean;
+ width?: number | string;
+ onConfirm?: () => void | Promise;
+ onCancel?: () => void;
+}
+
+export interface IDialogInstance {
+ id: string;
+ config: IDialogConfig;
+ resolve: (value: boolean) => void;
+ reject: (reason?: any) => void;
+}
+
+export interface IDialogContextValue {
+ dialogs: IDialogInstance[];
+ openDialog: (config: IDialogConfig) => Promise;
+ closeDialog: (id: string, result?: boolean) => void;
+ confirm: (config: Omit) => Promise;
+ info: (config: Omit) => Promise;
+ success: (config: Omit) => Promise;
+ warning: (config: Omit) => Promise;
+ error: (config: Omit) => Promise;
+}
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx
index 43ea551..93eac28 100644
--- a/src/pages/Dashboard.tsx
+++ b/src/pages/Dashboard.tsx
@@ -29,7 +29,7 @@ import {
CheckCircle as CheckCircleIcon,
} from '@mui/icons-material';
import { styled } from '@mui/material/styles';
-import KnowledgeGridView from '@/components/KnowledgeGridView';
+import KnowledgeGridView from '@/components/knowledge/KnowledgeGridView';
import UserDataDebug from '@/components/UserDataDebug';
import { useNavigate } from 'react-router-dom';
diff --git a/src/pages/knowledge/create.tsx b/src/pages/knowledge/create.tsx
new file mode 100644
index 0000000..b3aeea7
--- /dev/null
+++ b/src/pages/knowledge/create.tsx
@@ -0,0 +1,503 @@
+import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useForm } from 'react-hook-form';
+import {
+ Box,
+ Typography,
+ Card,
+ CardContent,
+ TextField,
+ Button,
+ FormControl,
+ InputLabel,
+ Select,
+ MenuItem,
+ Alert,
+ Grid,
+ Divider,
+ CircularProgress,
+ Stepper,
+ Step,
+ StepLabel,
+ Switch,
+ FormControlLabel,
+ Slider,
+ Chip,
+ Stack,
+} from '@mui/material';
+import {
+ ArrowBack as ArrowBackIcon,
+ Save as SaveIcon,
+ Settings as SettingsIcon,
+ CheckCircle as CheckCircleIcon,
+ SkipNext as SkipNextIcon,
+} from '@mui/icons-material';
+import knowledgeService from '@/services/knowledge_service';
+
+// 基础信息表单数据
+interface BasicFormData {
+ name: string;
+ description: string;
+ permission: string;
+ avatar?: string;
+}
+
+// 配置设置表单数据
+interface ConfigFormData {
+ parser_id: string;
+ embd_id: string;
+ chunk_token_num: number;
+ layout_recognize: string;
+ delimiter: string;
+ auto_keywords: number;
+ auto_questions: number;
+ html4excel: boolean;
+ topn_tags: number;
+ use_raptor: boolean;
+ use_graphrag: boolean;
+ graphrag_method: string;
+ pagerank: number;
+}
+
+const steps = ['基础信息', '配置设置'];
+
+function KnowledgeBaseCreate() {
+ const navigate = useNavigate();
+ const [activeStep, setActiveStep] = useState(0);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [error, setError] = useState('');
+ const [success, setSuccess] = useState('');
+ const [createdKbId, setCreatedKbId] = useState('');
+
+ // 基础信息表单
+ const basicForm = useForm({
+ defaultValues: {
+ name: '',
+ description: '',
+ permission: 'me',
+ avatar: undefined,
+ },
+ });
+
+ // 配置设置表单
+ const configForm = useForm({
+ defaultValues: {
+ parser_id: 'naive',
+ embd_id: 'text-embedding-v3@Tongyi-Qianwen',
+ chunk_token_num: 512,
+ layout_recognize: 'DeepDOC',
+ delimiter: '\n',
+ auto_keywords: 0,
+ auto_questions: 0,
+ html4excel: false,
+ topn_tags: 3,
+ use_raptor: false,
+ use_graphrag: false,
+ graphrag_method: 'light',
+ pagerank: 0,
+ },
+ });
+
+ // 第一步:创建基础知识库
+ const handleBasicSubmit = async (data: BasicFormData) => {
+ setIsSubmitting(true);
+ setError('');
+ setSuccess('');
+
+ try {
+ // 只发送基础字段到 create API
+ const basicData = {
+ name: data.name,
+ avatar: data.avatar,
+ description: data.description,
+ permission: data.permission,
+ };
+
+ const response = await knowledgeService.createKnowledge(basicData);
+
+ // 假设 API 返回包含 kb_id 的响应
+ const kbId = response.data?.kb_id;
+ setCreatedKbId(kbId);
+
+ setSuccess('知识库创建成功!您可以继续配置解析设置,或直接跳过。');
+ setActiveStep(1); // 进入第二步
+
+ } catch (err: any) {
+ console.error('创建知识库失败:', err);
+ setError(err.response?.data?.message || err.message || '创建知识库失败,请重试');
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ // 第二步:更新配置设置
+ const handleConfigSubmit = async (data: ConfigFormData) => {
+ if (!createdKbId) {
+ setError('未找到知识库ID,请重新创建');
+ return;
+ }
+
+ setIsSubmitting(true);
+ setError('');
+
+ try {
+ // 构建 update API 的数据结构
+ const updateData:any = {
+ kb_id: createdKbId,
+ name: basicForm.getValues('name'),
+ description: basicForm.getValues('description'),
+ permission: basicForm.getValues('permission'),
+ parser_id: data.parser_id,
+ embd_id: data.embd_id,
+ parser_config: {
+ layout_recognize: data.layout_recognize,
+ chunk_token_num: data.chunk_token_num,
+ delimiter: data.delimiter,
+ auto_keywords: data.auto_keywords,
+ auto_questions: data.auto_questions,
+ html4excel: data.html4excel,
+ topn_tags: data.topn_tags,
+ raptor: {
+ use_raptor: data.use_raptor,
+ },
+ graphrag: {
+ use_graphrag: data.use_graphrag,
+ entity_types: ["organization", "person", "geo", "event", "category"],
+ method: data.graphrag_method,
+ },
+ },
+ pagerank: data.pagerank,
+ };
+
+ await knowledgeService.updateKnowledge(updateData);
+
+ setSuccess('知识库配置更新成功!');
+
+ // 延迟跳转到知识库列表页面
+ setTimeout(() => {
+ navigate('/knowledge');
+ }, 1500);
+
+ } catch (err: any) {
+ console.error('更新知识库配置失败:', err);
+ setError(err.response?.data?.message || err.message || '更新配置失败,请重试');
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ // 跳过配置设置
+ const handleSkipConfig = () => {
+ setSuccess('知识库创建完成!');
+ setTimeout(() => {
+ navigate('/knowledge');
+ }, 1000);
+ };
+
+ const handleBack = () => {
+ if (activeStep === 0) {
+ navigate('/knowledge');
+ } else {
+ setActiveStep(0);
+ }
+ };
+
+ return (
+
+ {/* 页面标题 */}
+
+ }
+ onClick={handleBack}
+ sx={{ mr: 2 }}
+ >
+ {activeStep === 0 ? '返回' : '上一步'}
+
+
+ 创建知识库
+
+
+
+ {/* 步骤指示器 */}
+
+ {steps.map((label) => (
+
+ {label}
+
+ ))}
+
+
+ {/* 表单卡片 */}
+
+
+ {/* 错误和成功提示 */}
+ {error && (
+
+ {error}
+
+ )}
+
+ {success && (
+
+ {success}
+
+ )}
+
+ {/* 第一步:基础信息 */}
+ {activeStep === 0 && (
+
+
+ 基础信息
+
+
+
+
+ {/* 知识库名称 */}
+
+
+
+
+ {/* 描述 */}
+
+
+
+
+ {/* 权限设置 */}
+
+
+ 权限设置
+
+
+
+
+ {/* 提交按钮 */}
+
+
+
+ : }
+ disabled={isSubmitting}
+ >
+ {isSubmitting ? '创建中...' : '创建知识库'}
+
+
+
+
+
+ )}
+
+ {/* 第二步:配置设置 */}
+ {activeStep === 1 && (
+
+
+
+
+ 知识库已创建成功,现在可以配置解析设置
+
+
+
+ 您可以现在配置这些设置,也可以稍后在知识库详情页面中修改
+
+
+
+
+ {/* 解析器设置 */}
+
+
+ 解析器设置
+
+
+
+
+
+ 解析器类型
+
+
+
+
+
+
+ 嵌入模型
+
+
+
+
+ {/* 分块设置 */}
+
+
+ 分块设置
+
+
+
+
+ 分块大小: {configForm.watch('chunk_token_num')}
+ configForm.setValue('chunk_token_num', value as number)}
+ min={128}
+ max={2048}
+ step={128}
+ marks={[
+ { value: 128, label: '128' },
+ { value: 512, label: '512' },
+ { value: 1024, label: '1024' },
+ { value: 2048, label: '2048' },
+ ]}
+ />
+
+
+
+
+ 布局识别
+
+
+
+
+ {/* 高级功能 */}
+
+
+ 高级功能
+
+
+
+
+ configForm.setValue('use_raptor', e.target.checked)}
+ />
+ }
+ label="启用 Raptor"
+ />
+
+
+
+ configForm.setValue('use_graphrag', e.target.checked)}
+ />
+ }
+ label="启用 GraphRAG"
+ />
+
+
+ {configForm.watch('use_graphrag') && (
+
+
+ GraphRAG 方法
+
+
+
+ )}
+
+ {/* 操作按钮 */}
+
+
+ }
+ onClick={handleSkipConfig}
+ disabled={isSubmitting}
+ >
+ 跳过配置
+
+ : }
+ disabled={isSubmitting}
+ >
+ {isSubmitting ? '配置中...' : '完成配置'}
+
+
+
+
+
+ )}
+
+
+
+ );
+}
+
+export default KnowledgeBaseCreate;
diff --git a/src/pages/knowledge/detail.tsx b/src/pages/knowledge/detail.tsx
new file mode 100644
index 0000000..e1e2f21
--- /dev/null
+++ b/src/pages/knowledge/detail.tsx
@@ -0,0 +1,480 @@
+import React, { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import {
+ Box,
+ Typography,
+ Paper,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Chip,
+ IconButton,
+ Button,
+ TextField,
+ InputAdornment,
+ LinearProgress,
+ Alert,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Menu,
+ MenuItem,
+ Tooltip,
+ Stack,
+ Card,
+ CardContent,
+ Grid,
+ Breadcrumbs,
+ Link,
+} from '@mui/material';
+import {
+ Search as SearchIcon,
+ Upload as UploadIcon,
+ Delete as DeleteIcon,
+ Refresh as RefreshIcon,
+ MoreVert as MoreVertIcon,
+ InsertDriveFile as FileIcon,
+ PictureAsPdf as PdfIcon,
+ Description as DocIcon,
+ Image as ImageIcon,
+ VideoFile as VideoIcon,
+ AudioFile as AudioIcon,
+ CloudUpload as CloudUploadIcon,
+ Settings as SettingsIcon,
+} from '@mui/icons-material';
+import knowledgeService from '@/services/knowledge_service';
+import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge';
+import { RUNNING_STATUS_KEYS, type RunningStatus } from '@/constants/knowledge';
+
+// 文件类型图标映射
+const getFileIcon = (type: string) => {
+ const lowerType = type.toLowerCase();
+ if (lowerType.includes('pdf')) return ;
+ if (lowerType.includes('doc') || lowerType.includes('txt') || lowerType.includes('md')) return ;
+ if (lowerType.includes('jpg') || lowerType.includes('png') || lowerType.includes('jpeg')) return ;
+ if (lowerType.includes('mp4') || lowerType.includes('avi') || lowerType.includes('mov')) return ;
+ if (lowerType.includes('mp3') || lowerType.includes('wav') || lowerType.includes('m4a')) return ;
+ return ;
+};
+
+// 文件大小格式化
+const formatFileSize = (bytes: number): string => {
+ if (bytes === 0) return '0 B';
+ const k = 1024;
+ const sizes = ['B', 'KB', 'MB', 'GB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+};
+
+// 解析状态映射
+const getStatusChip = (status: string, progress: number) => {
+ switch (status) {
+ case '1':
+ return ;
+ case '0':
+ return ;
+ default:
+ return ;
+ }
+};
+
+// 运行状态映射
+const getRunStatusChip = (run: RunningStatus, progress: number) => {
+ switch (run) {
+ case RUNNING_STATUS_KEYS.UNSTART:
+ return ;
+ case RUNNING_STATUS_KEYS.RUNNING:
+ return (
+
+
+
+
+
+ {progress}%
+
+ );
+ case RUNNING_STATUS_KEYS.CANCEL:
+ return ;
+ case RUNNING_STATUS_KEYS.DONE:
+ return ;
+ case RUNNING_STATUS_KEYS.FAIL:
+ return ;
+ default:
+ return ;
+ }
+};
+
+function KnowledgeBaseDetail() {
+ const { id } = useParams<{ id: string }>();
+ const navigate = useNavigate();
+
+ // 状态管理
+ const [knowledgeBase, setKnowledgeBase] = useState(null);
+ const [files, setFiles] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [filesLoading, setFilesLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [searchKeyword, setSearchKeyword] = useState('');
+ const [selectedFiles, setSelectedFiles] = useState([]);
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+
+ // 获取知识库详情
+ const fetchKnowledgeDetail = async () => {
+ if (!id) return;
+
+ try {
+ setLoading(true);
+ const response = await knowledgeService.getKnowledgeDetail({ kb_id: id });
+
+ if (response.data.code === 0) {
+ setKnowledgeBase(response.data.data);
+ } else {
+ setError(response.data.message || '获取知识库详情失败');
+ }
+ } catch (err: any) {
+ setError(err.response?.data?.message || err.message || '获取知识库详情失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 获取文件列表
+ const fetchFileList = async () => {
+ if (!id) return;
+
+ try {
+ setFilesLoading(true);
+ // const response = await knowledgeService.getDocumentList(
+ // { kb_id: id },
+ // { keywords: searchKeyword }
+ // );
+
+ // if (response.data.code === 0) {
+ // setFiles(response.data.data.docs || []);
+ // } else {
+ // setError(response.data.message || '获取文件列表失败');
+ // }
+ } catch (err: any) {
+ setError(err.response?.data?.message || err.message || '获取文件列表失败');
+ } finally {
+ setFilesLoading(false);
+ }
+ };
+
+ // 删除文件
+ const handleDeleteFiles = async () => {
+ if (selectedFiles.length === 0) return;
+
+ try {
+ await knowledgeService.removeDocument({ doc_ids: selectedFiles });
+ setSelectedFiles([]);
+ setDeleteDialogOpen(false);
+ fetchFileList(); // 刷新列表
+ } catch (err: any) {
+ setError(err.response?.data?.message || err.message || '删除文件失败');
+ }
+ };
+
+ // 重新解析文件
+ const handleReparse = async (docIds: string[]) => {
+ try {
+ await knowledgeService.runDocument({ doc_ids: docIds });
+ fetchFileList(); // 刷新列表
+ } catch (err: any) {
+ setError(err.response?.data?.message || err.message || '重新解析失败');
+ }
+ };
+
+ // 初始化数据
+ useEffect(() => {
+ fetchKnowledgeDetail();
+ // fetchFileList();
+ }, [id]);
+
+ // 搜索文件
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ fetchFileList();
+ }, 500);
+
+ return () => clearTimeout(timer);
+ }, [searchKeyword]);
+
+ // 过滤文件
+ const filteredFiles = files.filter(file =>
+ file.name.toLowerCase().includes(searchKeyword.toLowerCase())
+ );
+
+ if (loading) {
+ return (
+
+
+ 加载中...
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ {error}
+
+ );
+ }
+
+ if (!knowledgeBase) {
+ return (
+
+ 知识库不存在
+
+ );
+ }
+
+ return (
+
+ {/* 面包屑导航 */}
+
+ navigate('/knowledge')}
+ sx={{ textDecoration: 'none' }}
+ >
+ 知识库
+
+ {knowledgeBase.name}
+
+
+ {/* 知识库信息卡片 */}
+
+
+
+
+
+ {knowledgeBase.name}
+
+
+ {knowledgeBase.description || '暂无描述'}
+
+
+
+
+
+
+
+
+
+
+ 创建时间: {knowledgeBase.create_date}
+
+
+ 更新时间: {knowledgeBase.update_date}
+
+
+ 语言: {knowledgeBase.language}
+
+
+
+
+
+
+
+ {/* 文件操作栏 */}
+
+
+ setSearchKeyword(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ sx={{ minWidth: 300 }}
+ size="small"
+ />
+
+
+ }
+ onClick={() => setUploadDialogOpen(true)}
+ >
+ 上传文件
+
+
+ {selectedFiles.length > 0 && (
+ <>
+ }
+ onClick={() => handleReparse(selectedFiles)}
+ >
+ 重新解析
+
+ }
+ onClick={() => setDeleteDialogOpen(true)}
+ >
+ 删除 ({selectedFiles.length})
+
+ >
+ )}
+
+ fetchFileList()}>
+
+
+
+
+
+
+ {/* 文件列表 */}
+
+
+
+
+
+ {/* 全选复选框可以在这里添加 */}
+
+ 文件名
+ 类型
+ 大小
+ 分块数
+ 状态
+ 解析状态
+ 上传时间
+ 操作
+
+
+
+ {filesLoading ? (
+
+
+
+ 加载文件列表...
+
+
+ ) : filteredFiles.length === 0 ? (
+
+
+
+ {searchKeyword ? '没有找到匹配的文件' : '暂无文件'}
+
+
+
+ ) : (
+ filteredFiles.map((file) => (
+
+
+ {/* 文件选择复选框 */}
+
+
+
+ {getFileIcon(file.type)}
+ {file.name}
+
+
+
+
+
+ {formatFileSize(file.size)}
+ {file.chunk_num}
+ {getStatusChip(file.status, file.progress)}
+ {getRunStatusChip(file.run, file.progress)}
+ {file.create_date}
+
+ setAnchorEl(e.currentTarget)}
+ >
+
+
+
+
+ ))
+ )}
+
+
+
+
+ {/* 文件操作菜单 */}
+
+
+ {/* 上传文件对话框 */}
+
+
+ {/* 删除确认对话框 */}
+
+
+ );
+}
+
+export default KnowledgeBaseDetail;
\ No newline at end of file
diff --git a/src/pages/knowledge/KnowledgeBaseList.tsx b/src/pages/knowledge/list.tsx
similarity index 89%
rename from src/pages/knowledge/KnowledgeBaseList.tsx
rename to src/pages/knowledge/list.tsx
index 46e8a25..ede1770 100644
--- a/src/pages/knowledge/KnowledgeBaseList.tsx
+++ b/src/pages/knowledge/list.tsx
@@ -19,13 +19,16 @@ import {
Refresh as RefreshIcon,
} from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
-import { useKnowledgeList } from '@/hooks/knowledge_hooks';
+import { useKnowledgeList, useKnowledgeOperations } from '@/hooks/knowledge_hooks';
import { useUserData } from '@/hooks/useUserData';
-import KnowledgeGridView from '@/components/KnowledgeGridView';
+import KnowledgeGridView from '@/components/knowledge/KnowledgeGridView';
import type { IKnowledge } from '@/interfaces/database/knowledge';
+import { useDialog } from '@/hooks/useDialog';
const KnowledgeBaseList: React.FC = () => {
const navigate = useNavigate();
+
+ const {deleteKnowledge} = useKnowledgeOperations();
// 搜索和筛选状态
const [searchTerm, setSearchTerm] = useState('');
@@ -78,22 +81,32 @@ const KnowledgeBaseList: React.FC = () => {
navigate('/knowledge/create');
}, [navigate]);
- // 处理编辑知识库
- const handleEditKnowledge = useCallback((kb: IKnowledge) => {
- navigate(`/knowledge/${kb.id}/edit`);
- }, [navigate]);
+ const dialog = useDialog();
+
+ // 处理删除知识库
+ const handleDeleteKnowledge = useCallback(async (kb: IKnowledge) => {
+ // 需要确认删除
+ dialog.confirm({
+ title: '确认删除',
+ content: `是否确认删除知识库 ${kb.name}?`,
+ onConfirm: async () => {
+ try {
+ await deleteKnowledge(kb.id);
+ // 删除成功后刷新列表
+ refresh();
+ } catch (err) {
+ console.error('Failed to delete knowledge:', err);
+ // 可以添加错误提示
+ }
+ },
+ });
+ }, [deleteKnowledge, refresh, dialog]);
// 处理查看知识库详情
const handleViewKnowledge = useCallback((kb: IKnowledge) => {
navigate(`/knowledge/${kb.id}`);
}, [navigate]);
- // 处理删除知识库
- const handleDeleteKnowledge = useCallback((kb: IKnowledge) => {
- // TODO: 实现删除逻辑
- console.log('删除知识库:', kb.id);
- }, []);
-
// 根据团队筛选过滤知识库
const filteredKnowledgeBases = useMemo(() => {
if (!knowledgeBases) return [];
@@ -226,9 +239,8 @@ const KnowledgeBaseList: React.FC = () => {
<>
{
@@ -16,6 +18,11 @@ const AppRoutes = () => {
{/* 使用MainLayout作为受保护路由的布局 */}
}>
{/* } /> */}
+
+ } />
+ } />
+ } />
+
} />
} />
} />