-
{knowledgeBase.title}
-
{knowledgeBase.description}
+
{knowledgeBase.name}
+
{knowledgeBase.desc}
diff --git a/src/pages/KnowledgeBase/components/CreateKnowledgeBaseModal.jsx b/src/pages/KnowledgeBase/components/CreateKnowledgeBaseModal.jsx
new file mode 100644
index 0000000..c5af8f4
--- /dev/null
+++ b/src/pages/KnowledgeBase/components/CreateKnowledgeBaseModal.jsx
@@ -0,0 +1,92 @@
+import React from 'react';
+import SvgIcon from '../../../components/SvgIcon';
+
+/**
+ * 创建知识库模态框组件
+ */
+const CreateKnowledgeBaseModal = ({ show, formData, formErrors, isSubmitting, onClose, onChange, onSubmit }) => {
+ if (!show) return null;
+
+ return (
+
+
+
+
新建知识库
+
+
+
+
+
+
+ {formErrors.name &&
{formErrors.name}
}
+
+
+
+
+ {formErrors.desc &&
{formErrors.desc}
}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default CreateKnowledgeBaseModal;
diff --git a/src/pages/KnowledgeBase/components/KnowledgeBaseList.jsx b/src/pages/KnowledgeBase/components/KnowledgeBaseList.jsx
new file mode 100644
index 0000000..eb042e5
--- /dev/null
+++ b/src/pages/KnowledgeBase/components/KnowledgeBaseList.jsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import KnowledgeCard from '../KnowledgeCard';
+
+/**
+ * 知识库列表组件
+ */
+const KnowledgeBaseList = ({ knowledgeBases, isSearching, onCardClick, onRequestAccess }) => {
+ if (knowledgeBases.length === 0) {
+ return (
+
+ {isSearching ? '没有找到匹配的知识库' : '暂无知识库,请创建新的知识库'}
+
+ );
+ }
+
+ return (
+
+ {knowledgeBases.map((item) => (
+
+ onCardClick(item.id)}
+ onRequestAccess={onRequestAccess}
+ />
+
+ ))}
+
+ );
+};
+
+export default KnowledgeBaseList;
diff --git a/src/pages/KnowledgeBase/components/Pagination.jsx b/src/pages/KnowledgeBase/components/Pagination.jsx
new file mode 100644
index 0000000..8c8f4b1
--- /dev/null
+++ b/src/pages/KnowledgeBase/components/Pagination.jsx
@@ -0,0 +1,53 @@
+import React from 'react';
+
+/**
+ * 分页组件
+ */
+const Pagination = ({ currentPage, totalPages, pageSize, onPageChange, onPageSizeChange }) => {
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default Pagination;
diff --git a/src/pages/KnowledgeBase/components/SearchBar.jsx b/src/pages/KnowledgeBase/components/SearchBar.jsx
new file mode 100644
index 0000000..d20a2e6
--- /dev/null
+++ b/src/pages/KnowledgeBase/components/SearchBar.jsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import SvgIcon from '../../../components/SvgIcon';
+
+/**
+ * 知识库搜索栏组件
+ */
+const SearchBar = ({ searchKeyword, isSearching, onSearchChange, onSearch, onClearSearch }) => {
+ return (
+
+ );
+};
+
+export default SearchBar;
diff --git a/src/services/api.js b/src/services/api.js
index 7f69131..80e61ea 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -34,7 +34,7 @@ api.interceptors.response.use(
// Handle errors in the response
if (error.response) {
// monitor /verify
- if (error.response.status === 401 && error.config.url === '/check-token/') {
+ if (error.response.status === 401 && error.config.url === '/auth/verify-token/') {
if (window.location.pathname !== '/login' && window.location.pathname !== '/signup') {
window.location.href = '/login';
}
diff --git a/src/store/auth/auth.thunk.js b/src/store/auth/auth.thunk.js
index a29ace2..8efd5bb 100644
--- a/src/store/auth/auth.thunk.js
+++ b/src/store/auth/auth.thunk.js
@@ -10,15 +10,16 @@ export const loginThunk = createAsyncThunk(
'auth/login',
async ({ username, password }, { rejectWithValue, dispatch }) => {
try {
- const { message, user, token } = await post('/login/', { username, password });
- if (!user) {
+ const { message, data } = await post('/auth/login/', { username, password });
+ if (!data) {
throw new Error(message || 'Something went wrong');
}
+ const { token } = data;
// encrypt token
const encryptedToken = CryptoJS.AES.encrypt(token, secretKey).toString();
sessionStorage.setItem('token', encryptedToken);
- return user;
+ return data;
} catch (error) {
const errorMessage = error.response?.data?.message || 'Something went wrong';
dispatch(
@@ -51,9 +52,9 @@ export const signupThunk = createAsyncThunk('auth/signup', async (config, { reje
}
});
-export const checkAuthThunk = createAsyncThunk('auth/check', async (_, { rejectWithValue, dispatch }) => {
+export const checkAuthThunk = createAsyncThunk('auth/verify', async (_, { rejectWithValue, dispatch }) => {
try {
- const { user, message } = await get('/check-token/');
+ const { user, message } = await get('/auth/verify-token/');
if (!user) {
dispatch(logout());
throw new Error(message || 'No token found');
@@ -69,7 +70,7 @@ export const checkAuthThunk = createAsyncThunk('auth/check', async (_, { rejectW
export const logoutThunk = createAsyncThunk('auth/logout', async (_, { rejectWithValue, dispatch }) => {
try {
// Send the logout request to the server (this assumes your server clears any session-related info)
- await post('/logout/');
+ await post('/auth/logout/');
dispatch(logout());
} catch (error) {
const errorMessage = error.response?.data?.message || 'Log out failed';
diff --git a/src/store/knowledgeBase/knowledgeBase.slice.js b/src/store/knowledgeBase/knowledgeBase.slice.js
new file mode 100644
index 0000000..d37445a
--- /dev/null
+++ b/src/store/knowledgeBase/knowledgeBase.slice.js
@@ -0,0 +1,198 @@
+import { createSlice } from '@reduxjs/toolkit';
+import {
+ fetchKnowledgeBases,
+ searchKnowledgeBases,
+ createKnowledgeBase,
+ getKnowledgeBaseById,
+ updateKnowledgeBase,
+ deleteKnowledgeBase,
+} from './knowledgeBase.thunks';
+
+const initialState = {
+ // List state
+ list: {
+ items: [],
+ total: 0,
+ page: 1,
+ page_size: 10,
+ status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
+ error: null,
+ },
+ // Search state
+ search: {
+ items: [],
+ total: 0,
+ page: 1,
+ page_size: 10,
+ keyword: '',
+ status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
+ error: null,
+ },
+ // Current knowledge base details
+ current: {
+ data: null,
+ status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
+ error: null,
+ },
+ // Create/update/delete operations status
+ operations: {
+ status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
+ error: null,
+ operationType: null, // 'create' | 'update' | 'delete'
+ },
+};
+
+const knowledgeBaseSlice = createSlice({
+ name: 'knowledgeBase',
+ initialState,
+ reducers: {
+ resetOperationStatus: (state) => {
+ state.operations = {
+ status: 'idle',
+ error: null,
+ operationType: null,
+ };
+ },
+ resetCurrentKnowledgeBase: (state) => {
+ state.current = {
+ data: null,
+ status: 'idle',
+ error: null,
+ };
+ },
+ resetSearchState: (state) => {
+ state.search = {
+ items: [],
+ total: 0,
+ page: 1,
+ page_size: 10,
+ keyword: '',
+ status: 'idle',
+ error: null,
+ };
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ // Fetch knowledge bases
+ .addCase(fetchKnowledgeBases.pending, (state) => {
+ state.list.status = 'loading';
+ })
+ .addCase(fetchKnowledgeBases.fulfilled, (state, action) => {
+ state.list.status = 'succeeded';
+ state.list.items = action.payload.items;
+ state.list.total = action.payload.total;
+ state.list.page = action.payload.page;
+ state.list.page_size = action.payload.page_size;
+ state.list.error = null;
+ })
+ .addCase(fetchKnowledgeBases.rejected, (state, action) => {
+ state.list.status = 'failed';
+ state.list.error = action.payload;
+ })
+
+ // Search knowledge bases
+ .addCase(searchKnowledgeBases.pending, (state, action) => {
+ state.search.status = 'loading';
+ // Store the keyword for reference
+ if (action.meta.arg.keyword) {
+ state.search.keyword = action.meta.arg.keyword;
+ }
+ })
+ .addCase(searchKnowledgeBases.fulfilled, (state, action) => {
+ state.search.status = 'succeeded';
+ state.search.items = action.payload.items;
+ state.search.total = action.payload.total;
+ state.search.page = action.payload.page;
+ state.search.page_size = action.payload.page_size;
+ state.search.error = null;
+ })
+ .addCase(searchKnowledgeBases.rejected, (state, action) => {
+ state.search.status = 'failed';
+ state.search.error = action.payload;
+ })
+
+ // Get knowledge base by ID
+ .addCase(getKnowledgeBaseById.pending, (state) => {
+ state.current.status = 'loading';
+ })
+ .addCase(getKnowledgeBaseById.fulfilled, (state, action) => {
+ state.current.status = 'succeeded';
+ state.current.data = action.payload;
+ state.current.error = null;
+ })
+ .addCase(getKnowledgeBaseById.rejected, (state, action) => {
+ state.current.status = 'failed';
+ state.current.error = action.payload;
+ })
+
+ // Create knowledge base
+ .addCase(createKnowledgeBase.pending, (state) => {
+ state.operations.status = 'loading';
+ state.operations.operationType = 'create';
+ })
+ .addCase(createKnowledgeBase.fulfilled, (state, action) => {
+ state.operations.status = 'succeeded';
+ // Don't add to list here - better to refetch the list to ensure consistency
+ state.operations.error = null;
+ })
+ .addCase(createKnowledgeBase.rejected, (state, action) => {
+ state.operations.status = 'failed';
+ state.operations.error = action.payload;
+ })
+
+ // Update knowledge base
+ .addCase(updateKnowledgeBase.pending, (state) => {
+ state.operations.status = 'loading';
+ state.operations.operationType = 'update';
+ })
+ .addCase(updateKnowledgeBase.fulfilled, (state, action) => {
+ state.operations.status = 'succeeded';
+ // Update in list if present
+ const index = state.list.items.findIndex((item) => item.id === action.payload.id);
+ if (index !== -1) {
+ state.list.items[index] = action.payload;
+ }
+ // Update in search results if present
+ const searchIndex = state.search.items.findIndex((item) => item.id === action.payload.id);
+ if (searchIndex !== -1) {
+ state.search.items[searchIndex] = action.payload;
+ }
+ // Update current if it's the same knowledge base
+ if (state.current.data && state.current.data.id === action.payload.id) {
+ state.current.data = action.payload;
+ }
+ state.operations.error = null;
+ })
+ .addCase(updateKnowledgeBase.rejected, (state, action) => {
+ state.operations.status = 'failed';
+ state.operations.error = action.payload;
+ })
+
+ // Delete knowledge base
+ .addCase(deleteKnowledgeBase.pending, (state) => {
+ state.operations.status = 'loading';
+ state.operations.operationType = 'delete';
+ })
+ .addCase(deleteKnowledgeBase.fulfilled, (state, action) => {
+ state.operations.status = 'succeeded';
+ // Remove from list if present
+ state.list.items = state.list.items.filter((item) => item.id !== action.payload);
+ // Remove from search results if present
+ state.search.items = state.search.items.filter((item) => item.id !== action.payload);
+ // Reset current if it's the same knowledge base
+ if (state.current.data && state.current.data.id === action.payload) {
+ state.current.data = null;
+ }
+ state.operations.error = null;
+ })
+ .addCase(deleteKnowledgeBase.rejected, (state, action) => {
+ state.operations.status = 'failed';
+ state.operations.error = action.payload;
+ });
+ },
+});
+
+export const { resetOperationStatus, resetCurrentKnowledgeBase, resetSearchState } = knowledgeBaseSlice.actions;
+const knowledgeBaseReducer = knowledgeBaseSlice.reducer;
+export default knowledgeBaseReducer;
diff --git a/src/store/knowledgeBase/knowledgeBase.thunks.js b/src/store/knowledgeBase/knowledgeBase.thunks.js
new file mode 100644
index 0000000..e961002
--- /dev/null
+++ b/src/store/knowledgeBase/knowledgeBase.thunks.js
@@ -0,0 +1,107 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { get, post, put, del } from '../../services/api';
+
+/**
+ * Fetch knowledge bases with pagination
+ * @param {Object} params - Pagination parameters
+ * @param {number} params.page - Page number (default: 1)
+ * @param {number} params.page_size - Page size (default: 10)
+ */
+export const fetchKnowledgeBases = createAsyncThunk(
+ 'knowledgeBase/fetchKnowledgeBases',
+ async ({ page = 1, page_size = 10 } = {}, { rejectWithValue }) => {
+ try {
+ const response = await get('/knowledge-bases/', { page, page_size });
+ return response.data;
+ } catch (error) {
+ return rejectWithValue(error.response?.data || 'Failed to fetch knowledge bases');
+ }
+ }
+);
+
+/**
+ * Search knowledge bases
+ * @param {Object} params - Search parameters
+ * @param {string} params.keyword - Search keyword
+ * @param {number} params.page - Page number (default: 1)
+ * @param {number} params.page_size - Page size (default: 10)
+ */
+export const searchKnowledgeBases = createAsyncThunk(
+ 'knowledgeBase/searchKnowledgeBases',
+ async ({ keyword, page = 1, page_size = 10 }, { rejectWithValue }) => {
+ try {
+ const response = await get('/knowledge-bases/search/', {
+ keyword,
+ page,
+ page_size,
+ });
+ return response.data;
+ } catch (error) {
+ return rejectWithValue(error.response?.data || 'Failed to search knowledge bases');
+ }
+ }
+);
+
+/**
+ * Create a new knowledge base
+ */
+export const createKnowledgeBase = createAsyncThunk(
+ 'knowledgeBase/createKnowledgeBase',
+ async (knowledgeBaseData, { rejectWithValue }) => {
+ try {
+ const response = await post('/knowledge-bases/', knowledgeBaseData);
+ return response.data;
+ } catch (error) {
+ return rejectWithValue(error.response?.data || 'Failed to create knowledge base');
+ }
+ }
+);
+
+/**
+ * Get knowledge base details by ID
+ */
+export const getKnowledgeBaseById = createAsyncThunk(
+ 'knowledgeBase/getKnowledgeBaseById',
+ async (id, { rejectWithValue }) => {
+ try {
+ const response = await get(`/knowledge-bases/${id}/`);
+ return response.data;
+ } catch (error) {
+ return rejectWithValue(error.response?.data || 'Failed to get knowledge base details');
+ }
+ }
+);
+
+/**
+ * Update knowledge base
+ * @param {Object} params - Update parameters
+ * @param {string} params.id - Knowledge base ID
+ * @param {Object} params.data - Update data (name, desc)
+ */
+export const updateKnowledgeBase = createAsyncThunk(
+ 'knowledgeBase/updateKnowledgeBase',
+ async ({ id, data }, { rejectWithValue }) => {
+ try {
+ const response = await put(`/knowledge-bases/${id}/`, data);
+ return response.data;
+ } catch (error) {
+ return rejectWithValue(error.response?.data || 'Failed to update knowledge base');
+ }
+ }
+);
+
+/**
+ * Delete knowledge base
+ * @param {string} id - Knowledge base ID
+ */
+export const deleteKnowledgeBase = createAsyncThunk(
+ 'knowledgeBase/deleteKnowledgeBase',
+ async (id, { rejectWithValue }) => {
+ try {
+ await del(`/knowledge-bases/${id}/`);
+ return id;
+ } catch (error) {
+ return rejectWithValue(error.response?.data || 'Failed to delete knowledge base');
+ }
+ }
+);
diff --git a/src/store/store.js b/src/store/store.js
index 6e06a4d..f83d727 100644
--- a/src/store/store.js
+++ b/src/store/store.js
@@ -3,10 +3,12 @@ import { persistReducer, persistStore } from 'redux-persist';
import sessionStorage from 'redux-persist/lib/storage/session';
import notificationReducer from './notification.slice.js';
import authReducer from './auth/auth.slice.js';
+import knowledgeBaseReducer from './knowledgeBase/knowledgeBase.slice.js';
const rootRducer = combineReducers({
auth: authReducer,
notification: notificationReducer,
+ knowledgeBase: knowledgeBaseReducer,
});
const persistConfig = {