diff --git a/src/App.jsx b/src/App.jsx
index 88ffb2c..ced3745 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -8,6 +8,7 @@ import MeetingDetails from './pages/MeetingDetails';
import CreateMeeting from './pages/CreateMeeting';
import EditMeeting from './pages/EditMeeting';
import AdminManagement from './pages/AdminManagement';
+import KnowledgeBasePage from './pages/KnowledgeBasePage';
import './App.css';
function App() {
@@ -81,6 +82,9 @@ function App() {
:
} />
+ :
+ } />
diff --git a/src/components/knowledgebase/PersonalKnowledgeBase.jsx b/src/components/knowledgebase/PersonalKnowledgeBase.jsx
new file mode 100644
index 0000000..60b5568
--- /dev/null
+++ b/src/components/knowledgebase/PersonalKnowledgeBase.jsx
@@ -0,0 +1,369 @@
+import React, { useState, useEffect } from 'react';
+import apiClient from '../../utils/apiClient';
+import { buildApiUrl, API_ENDPOINTS } from '../../config/api';
+import { Plus, X, Eye, Trash2, Calendar, Database, Search } from 'lucide-react';
+import { Link } from 'react-router-dom';
+
+const PersonalKnowledgeBase = ({ user }) => {
+ const [kbs, setKbs] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [showModal, setShowModal] = useState(false);
+ const [meetings, setMeetings] = useState([]);
+ const [selectedMeetings, setSelectedMeetings] = useState([]);
+ const [userPrompt, setUserPrompt] = useState('');
+ const [searchQuery, setSearchQuery] = useState('');
+ const [generating, setGenerating] = useState(false);
+ const [taskId, setTaskId] = useState(null);
+ const [progress, setProgress] = useState(0);
+ const [selectedKb, setSelectedKb] = useState(null);
+ const [showDetailModal, setShowDetailModal] = useState(false);
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
+ const [deletingKb, setDeletingKb] = useState(null);
+
+ useEffect(() => {
+ fetchPersonalKbs();
+ fetchMeetings();
+ }, []);
+
+ useEffect(() => {
+ if (taskId) {
+ const interval = setInterval(() => {
+ apiClient.get(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.TASK_STATUS(taskId)))
+ .then(response => {
+ const { status, progress } = response.data;
+ setProgress(progress || 0);
+ if (status === 'completed') {
+ clearInterval(interval);
+ setTaskId(null);
+ setGenerating(false);
+ setProgress(0);
+ // Reset form
+ setUserPrompt('');
+ setSelectedMeetings([]);
+ setShowModal(false);
+ // Refresh the list
+ fetchPersonalKbs();
+ } else if (status === 'failed') {
+ clearInterval(interval);
+ setTaskId(null);
+ setGenerating(false);
+ setProgress(0);
+ alert('知识库生成失败,请稍后重试');
+ }
+ })
+ .catch(error => {
+ console.error("Error fetching task status:", error);
+ clearInterval(interval);
+ setTaskId(null);
+ setGenerating(false);
+ setProgress(0);
+ });
+ }, 2000); // 缩短轮询间隔到2秒
+ return () => clearInterval(interval);
+ }
+ }, [taskId]);
+
+ const fetchPersonalKbs = () => {
+ setLoading(true);
+ apiClient.get(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.LIST, { is_shared: false }))
+ .then(response => {
+ setKbs(response.data.kbs);
+ setLoading(false);
+ })
+ .catch(error => {
+ console.error("Error fetching personal knowledge bases:", error);
+ setLoading(false);
+ });
+ };
+
+ const fetchTags = () => {
+ // Tags functionality removed - replaced with search
+ };
+
+ const fetchMeetings = () => {
+ apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.LIST, { user_id: user.user_id }))
+ .then(response => {
+ setMeetings(response.data);
+ })
+ .catch(error => {
+ console.error("Error fetching meetings:", error);
+ });
+ };
+
+ const handleGenerate = async () => {
+ if (!selectedMeetings || selectedMeetings.length === 0) {
+ alert('请至少选择一个会议');
+ return;
+ }
+
+ setGenerating(true);
+ try {
+ const response = await apiClient.post(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.CREATE), {
+ user_prompt: userPrompt,
+ source_meeting_ids: selectedMeetings.join(','),
+ is_shared: false
+ });
+ setTaskId(response.data.task_id);
+ } catch (error) {
+ console.error("Error creating knowledge base:", error);
+ setGenerating(false);
+ }
+ };
+
+ const toggleMeetingSelection = (meetingId) => {
+ setSelectedMeetings(prev =>
+ prev.includes(meetingId)
+ ? prev.filter(id => id !== meetingId)
+ : [...prev, meetingId]
+ );
+ };
+
+ const filteredMeetings = (meetings || []).filter(meeting => {
+ if (!searchQuery) return true;
+ return meeting.title.toLowerCase().includes(searchQuery.toLowerCase());
+ });
+
+ const handleDelete = async (kbId) => {
+ setDeletingKb(kbs.find(kb => kb.kb_id === kbId));
+ setShowDeleteConfirm(true);
+ };
+
+ const confirmDelete = async () => {
+ try {
+ await apiClient.delete(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.DELETE(deletingKb.kb_id)));
+ setShowDeleteConfirm(false);
+ setDeletingKb(null);
+ fetchPersonalKbs(); // Refresh the list
+ } catch (error) {
+ console.error("Error deleting knowledge base:", error);
+ alert('删除失败,请稍后重试');
+ setShowDeleteConfirm(false);
+ setDeletingKb(null);
+ }
+ };
+
+ const handleViewDetail = async (kbId) => {
+ try {
+ const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.DETAIL(kbId)));
+ setSelectedKb(response.data);
+ setShowDetailModal(true);
+ } catch (error) {
+ console.error("Error fetching knowledge base detail:", error);
+ alert('获取详情失败,请稍后重试');
+ }
+ };
+
+ const formatDate = (dateString) => {
+ const date = new Date(dateString);
+ return date.toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+ };
+
+ if (loading) {
+ return
Loading...
;
+ }
+
+ return (
+
+
+ {kbs.length === 0 ? (
+
+ ) : (
+ kbs.map(kb => (
+
+
+
{kb.title}
+
+
+
+
+
+
+
+
+
+ {formatDate(kb.created_at)}
+
+
+
+ {kb.source_meeting_count || 0} 个数据源
+
+
+ {kb.user_prompt && (
+
+ 提示词: {kb.user_prompt}
+
+ )}
+
+ {kb.content ? kb.content.substring(0, 200) + '...' : '内容生成中...'}
+
+
+
+ ))
+ )}
+
+
+
+
+
+
+
+
+
+
+ {selectedMeetings.length > 0 && (
+
+ 已选择 {selectedMeetings.length} 个会议
+
+ )}
+
+
+
+ {showModal && (
+
+
+
+
选择会议
+
+
+
+
+
+ setSearchQuery(e.target.value)}
+ className="search-input"
+ />
+ {searchQuery && (
+
+ )}
+
+
+
+ {filteredMeetings.length === 0 ? (
+
+ ) : (
+ filteredMeetings.map(meeting => (
+
toggleMeetingSelection(meeting.meeting_id)}
+ >
+ toggleMeetingSelection(meeting.meeting_id)}
+ />
+
+
+ ))
+ )}
+
+
+
+
+
+
+ )}
+
+ {showDetailModal && selectedKb && (
+
+
+
+
{selectedKb.title}
+
+
+
+
+ {selectedKb.source_meetings && selectedKb.source_meetings.length > 0 && (
+
+
数据源列表:
+
+ {selectedKb.source_meetings.map(meeting => (
+ setShowDetailModal(false)}
+ >
+ {meeting.title}
+
+ ))}
+
+
+ )}
+ {selectedKb.user_prompt && (
+
+ 提示词: {selectedKb.user_prompt}
+
+ )}
+
+
+
内容
+
+ {selectedKb.content || '内容生成中...'}
+
+
+
+
+
+ )}
+
+ {showDeleteConfirm && deletingKb && (
+
{setShowDeleteConfirm(false); setDeletingKb(null);}}>
+
e.stopPropagation()}>
+
确认删除
+
确定要删除知识库条目 "{deletingKb.title}" 吗?此操作无法撤销。
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default PersonalKnowledgeBase;
diff --git a/src/components/knowledgebase/SharedKnowledgeBase.jsx b/src/components/knowledgebase/SharedKnowledgeBase.jsx
new file mode 100644
index 0000000..cfb38f3
--- /dev/null
+++ b/src/components/knowledgebase/SharedKnowledgeBase.jsx
@@ -0,0 +1,157 @@
+import React, { useState, useEffect } from 'react';
+import apiClient from '../../utils/apiClient';
+import { buildApiUrl, API_ENDPOINTS } from '../../config/api';
+import { Eye, Calendar, Database, X } from 'lucide-react';
+import { Link } from 'react-router-dom';
+
+const SharedKnowledgeBase = () => {
+ const [kbs, setKbs] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [selectedKb, setSelectedKb] = useState(null);
+ const [showDetailModal, setShowDetailModal] = useState(false);
+
+ useEffect(() => {
+ apiClient.get(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.LIST, { is_shared: true }))
+ .then(response => {
+ setKbs(response.data.kbs);
+ setLoading(false);
+ })
+ .catch(error => {
+ console.error("Error fetching shared knowledge bases:", error);
+ setLoading(false);
+ });
+ }, []);
+
+ const handleViewDetail = async (kbId) => {
+ try {
+ const response = await apiClient.get(buildApiUrl(API_ENDPOINTS.KNOWLEDGE_BASE.DETAIL(kbId)));
+ setSelectedKb(response.data);
+ setShowDetailModal(true);
+ } catch (error) {
+ console.error("Error fetching knowledge base detail:", error);
+ alert('获取详情失败,请稍后重试');
+ }
+ };
+
+ const formatDate = (dateString) => {
+ const date = new Date(dateString);
+ return date.toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+ };
+
+ if (loading) {
+ return Loading...
;
+ }
+
+ return (
+
+ {kbs.length === 0 ? (
+
+ ) : (
+ kbs.map(kb => (
+
+
+
{kb.title}
+
+
+
+
+
+
+
+
+ {formatDate(kb.created_at)}
+
+
+
+ {kb.source_meeting_count || 0} 个数据源
+
+ {kb.created_by_name && (
+
+ 创建者: {kb.created_by_name}
+
+ )}
+
+ {kb.user_prompt && (
+
+ 提示词: {kb.user_prompt}
+
+ )}
+
+ {kb.content ? kb.content.substring(0, 200) + '...' : '内容生成中...'}
+
+
+
+ ))
+ )}
+
+ {showDetailModal && selectedKb && (
+
+
+
+
{selectedKb.title}
+
+
+
+
+
+ 创建时间: {formatDate(selectedKb.created_at)}
+
+ {selectedKb.source_meetings && selectedKb.source_meetings.length > 0 && (
+
+
数据源会议:
+
+ {selectedKb.source_meetings.map(meeting => (
+ setShowDetailModal(false)}
+ >
+ {meeting.title}
+
+ ))}
+
+
+ )}
+ {selectedKb.created_by_name && (
+
+ 创建者: {selectedKb.created_by_name}
+
+ )}
+ {selectedKb.user_prompt && (
+
+ 提示词: {selectedKb.user_prompt}
+
+ )}
+
+
+
内容
+
+ {selectedKb.content || '内容生成中...'}
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default SharedKnowledgeBase;
diff --git a/src/config/api.js b/src/config/api.js
index 4d39efa..8307de5 100644
--- a/src/config/api.js
+++ b/src/config/api.js
@@ -47,13 +47,26 @@ const API_CONFIG = {
CREATE: '/api/prompts',
UPDATE: (promptId) => `/api/prompts/${promptId}`,
DELETE: (promptId) => `/api/prompts/${promptId}`
+ },
+ KNOWLEDGE_BASE: {
+ LIST: '/api/knowledge-bases',
+ CREATE: '/api/knowledge-bases',
+ DETAIL: (kbId) => `/api/knowledge-bases/${kbId}`,
+ UPDATE: (kbId) => `/api/knowledge-bases/${kbId}`,
+ DELETE: (kbId) => `/api/knowledge-bases/${kbId}`,
+ TASK_STATUS: (taskId) => `/api/knowledge-bases/tasks/${taskId}`
}
}
};
// 构建完整的API URL
-export const buildApiUrl = (endpoint) => {
- return `${API_CONFIG.BASE_URL}${endpoint}`;
+export const buildApiUrl = (endpoint, params = {}) => {
+ const url = `${API_CONFIG.BASE_URL}${endpoint}`;
+ const query = Object.entries(params)
+ .filter(([, value]) => value !== null && value !== undefined)
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
+ .join('&');
+ return query ? `${url}?${query}` : url;
};
// 导出API端点
diff --git a/src/pages/Dashboard.css b/src/pages/Dashboard.css
index aefab4a..99580fa 100644
--- a/src/pages/Dashboard.css
+++ b/src/pages/Dashboard.css
@@ -632,4 +632,29 @@
.success-message {
color: green;
margin-bottom: 1rem;
-}
\ No newline at end of file
+}
+
+.user-card-actions {
+ margin-top: 1rem;
+}
+
+.user-card-action-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ background: linear-gradient(135deg, #667eea, #764ba2);
+ color: white;
+ text-decoration: none;
+ padding: 0.75rem 1.5rem;
+ border-radius: 10px;
+ font-weight: 600;
+ font-size: 0.95rem;
+ transition: all 0.3s ease;
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
+ white-space: nowrap;
+}
+
+.user-card-action-btn:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4);
+}
diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx
index 9f8e872..3ccd780 100644
--- a/src/pages/Dashboard.jsx
+++ b/src/pages/Dashboard.jsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef } from 'react';
-import { LogOut, User, Calendar, Users, TrendingUp, Clock, MessageSquare, Plus, ChevronDown, KeyRound, Shield, Filter, X } from 'lucide-react';
+import { LogOut, User, Calendar, Users, TrendingUp, Clock, MessageSquare, Plus, ChevronDown, KeyRound, Shield, Filter, X, Library } from 'lucide-react';
import apiClient from '../utils/apiClient';
import { Link } from 'react-router-dom';
import { buildApiUrl, API_ENDPOINTS } from '../config/api';
@@ -9,7 +9,7 @@ import './Dashboard.css';
const Dashboard = ({ user, onLogout }) => {
const [userInfo, setUserInfo] = useState(null);
- const [meetings, setMeetings] = useState([]);
+ const [meetings, setMeetings] = useState(null);
const [filteredMeetings, setFilteredMeetings] = useState([]);
const [selectedTags, setSelectedTags] = useState([]);
const [filterType, setFilterType] = useState('all'); // 'all', 'created', 'attended'
@@ -34,6 +34,7 @@ const Dashboard = ({ user, onLogout }) => {
}, [meetings, selectedTags, filterType]);
const filterMeetings = () => {
+ if (!meetings) return;
let filtered = [...meetings];
// 根据创建/参与类型过滤
@@ -93,12 +94,10 @@ const Dashboard = ({ user, onLogout }) => {
const userResponse = await apiClient.get(buildApiUrl(API_ENDPOINTS.USERS.DETAIL(user.user_id)));
console.log('User response:', userResponse.data);
- setUserInfo(userResponse.data);
-
- const meetingsResponse = await apiClient.get(buildApiUrl(`${API_ENDPOINTS.MEETINGS.LIST}?user_id=${user.user_id}`));
- //console.log('Meetings response:', meetingsResponse.data);
- setMeetings(meetingsResponse.data);
+ setUserInfo(userResponse.data);
+ const meetingsResponse = await apiClient.get(buildApiUrl(`${API_ENDPOINTS.MEETINGS.LIST}?user_id=${user.user_id}`));
+ setMeetings(meetingsResponse.data);
} catch (err) {
console.error('Error fetching data:', err);
setError('获取数据失败,请刷新重试');
@@ -172,7 +171,7 @@ const Dashboard = ({ user, onLogout }) => {
});
};
- if (loading) {
+ if (loading || !meetings) {
return (
@@ -241,6 +240,12 @@ const Dashboard = ({ user, onLogout }) => {
{userInfo?.caption}
{userInfo?.email}
加入时间:{formatDate(userInfo?.created_at)}
+
+
+
+ 知识库管理
+
+
diff --git a/src/pages/KnowledgeBasePage.css b/src/pages/KnowledgeBasePage.css
new file mode 100644
index 0000000..6bf904d
--- /dev/null
+++ b/src/pages/KnowledgeBasePage.css
@@ -0,0 +1,700 @@
+
+.kb-management-page {
+ min-height: 100vh;
+ background: #f8fafc;
+}
+
+.kb-header {
+ background: white;
+ border-bottom: 1px solid #e2e8f0;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+ margin-bottom: 0;
+}
+
+.kb-header .header-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 1rem 2rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.kb-header .logo {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 1.5rem;
+ font-weight: bold;
+ color: #667eea;
+}
+
+.kb-header .logo-icon {
+ width: 32px;
+ height: 32px;
+}
+
+.kb-header h1 {
+ margin: 0;
+ font-size: 1.5rem;
+ color: #1e293b;
+ font-weight: 600;
+}
+
+.kb-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 2rem;
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+}
+
+.kb-wrapper {
+ background: white;
+ border-radius: 16px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
+ overflow: hidden;
+}
+
+.kb-tabs .ant-tabs-nav {
+ padding: 0 2rem;
+ margin-bottom: 0 !important;
+ border-bottom: 1px solid #e2e8f0;
+}
+
+.kb-tabs .ant-tabs-tab {
+ font-size: 1rem;
+ color: #475569;
+ padding: 16px 4px;
+ margin: 0 16px;
+}
+
+.kb-tabs .ant-tabs-tab .ant-tabs-tab-btn {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 500;
+}
+
+.kb-tabs .ant-tabs-tab-active .ant-tabs-tab-btn {
+ color: #667eea;
+}
+
+.kb-tabs .ant-tabs-ink-bar {
+ background: #667eea;
+ height: 3px;
+}
+
+.kb-tabs .ant-tabs-content-holder {
+ padding: 2rem;
+ min-height: 60vh;
+}
+
+/* Knowledge Base List Styles */
+.kb-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
+ gap: 1.5rem;
+ margin-bottom: 2rem;
+}
+
+.kb-card {
+ background: white;
+ border: 1px solid #e2e8f0;
+ border-radius: 12px;
+ padding: 1.5rem;
+ transition: all 0.3s ease;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+}
+
+.kb-card:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ transform: translateY(-2px);
+}
+
+.kb-card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 1rem;
+}
+
+.kb-title {
+ margin: 0;
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: #1e293b;
+ flex: 1;
+ word-break: break-word;
+}
+
+.kb-actions {
+ display: flex;
+ gap: 0.5rem;
+ flex-shrink: 0;
+}
+
+.kb-action-btn {
+ padding: 0.5rem;
+ border: none;
+ border-radius: 6px;
+ background: #f1f5f9;
+ color: #64748b;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.kb-action-btn:hover {
+ background: #e2e8f0;
+}
+
+.view-btn:hover {
+ background: #dbeafe;
+ color: #3b82f6;
+}
+
+.delete-btn:hover {
+ background: #fee2e2;
+ color: #ef4444;
+}
+
+.kb-card-body {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.kb-meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ color: #64748b;
+ font-size: 0.875rem;
+}
+
+.kb-meta-item {
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+}
+
+.kb-prompt {
+ padding: 0.75rem;
+ background: #f8fafc;
+ border-left: 3px solid #667eea;
+ border-radius: 4px;
+ font-size: 0.875rem;
+ color: #475569;
+}
+
+.kb-content-preview {
+ color: #64748b;
+ font-size: 0.875rem;
+ line-height: 1.6;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+}
+
+/* Empty State */
+.empty-state {
+ text-align: center;
+ padding: 3rem 2rem;
+ color: #94a3b8;
+}
+
+.empty-state p {
+ margin: 0.5rem 0;
+ font-size: 1rem;
+}
+
+.empty-hint {
+ font-size: 0.875rem;
+ color: #cbd5e1;
+}
+
+/* Generation Section */
+.kb-generation-section {
+ margin-top: 2rem;
+ padding-top: 2rem;
+ border-top: 2px solid #e2e8f0;
+}
+
+.kb-generation-section h2 {
+ margin-bottom: 1.5rem;
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: #1e293b;
+}
+
+.generation-form {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.kb-title-display {
+ padding: 0.75rem 1rem;
+ background: #f8fafc;
+ border-radius: 8px;
+ font-size: 0.95rem;
+ color: #475569;
+}
+
+.generation-actions {
+ display: flex;
+ gap: 1rem;
+ align-items: flex-end;
+}
+
+.prompt-input-container {
+ flex-grow: 1;
+ display: flex;
+ gap: 0.5rem;
+ align-items: flex-end;
+}
+
+.add-meeting-btn {
+ padding: 0.75rem;
+ border-radius: 8px;
+ border: 1px solid #e2e8f0;
+ background: #f8fafc;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.add-meeting-btn:hover {
+ background: #667eea;
+ color: white;
+ border-color: #667eea;
+}
+
+.kb-prompt-input {
+ width: 100%;
+ padding: 0.75rem;
+ border: 1px solid #e2e8f0;
+ border-radius: 8px;
+ font-family: inherit;
+ font-size: 0.95rem;
+ resize: vertical;
+ min-height: 100px;
+ transition: border-color 0.2s ease;
+}
+
+.kb-prompt-input:focus {
+ outline: none;
+ border-color: #667eea;
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+}
+
+.generate-kb-btn {
+ align-self: flex-end;
+ padding: 0.75rem 1.5rem;
+ border-radius: 8px;
+ border: none;
+ background: #667eea;
+ color: white;
+ cursor: pointer;
+ font-weight: 500;
+ transition: all 0.2s ease;
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+
+.generate-kb-btn:hover:not(:disabled) {
+ background: #5568d3;
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
+}
+
+.generate-kb-btn:disabled {
+ background: #cbd5e1;
+ cursor: not-allowed;
+}
+
+.selected-meetings-info {
+ padding: 0.5rem 1rem;
+ background: #dbeafe;
+ color: #1e40af;
+ border-radius: 6px;
+ font-size: 0.875rem;
+ font-weight: 500;
+}
+
+/* Modal Styles */
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ backdrop-filter: blur(2px);
+}
+
+.modal-content {
+ background: white;
+ padding: 2rem;
+ border-radius: 12px;
+ width: 90%;
+ max-width: 800px;
+ max-height: 80vh;
+ display: flex;
+ flex-direction: column;
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+}
+
+.detail-modal {
+ max-width: 900px;
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1.5rem;
+ padding-bottom: 1rem;
+ border-bottom: 1px solid #e2e8f0;
+}
+
+.modal-header h2 {
+ margin: 0;
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: #1e293b;
+}
+
+.close-btn {
+ padding: 0.5rem;
+ border: none;
+ border-radius: 6px;
+ background: #f1f5f9;
+ color: #64748b;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.close-btn:hover {
+ background: #e2e8f0;
+ color: #1e293b;
+}
+
+.tag-cloud-wrapper {
+ flex-shrink: 0;
+ padding-bottom: 1rem;
+ border-bottom: 1px solid #e2e8f0;
+ margin-bottom: 1rem;
+}
+
+/* Search Wrapper Styles */
+.search-wrapper {
+ flex-shrink: 0;
+ padding-bottom: 1rem;
+ border-bottom: 1px solid #e2e8f0;
+ margin-bottom: 1rem;
+}
+
+.search-input-container {
+ position: relative;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.search-icon {
+ position: absolute;
+ left: 12px;
+ color: #94a3b8;
+ pointer-events: none;
+}
+
+.search-input {
+ flex: 1;
+ padding: 0.75rem 0.75rem 0.75rem 2.5rem;
+ border: 1px solid #e2e8f0;
+ border-radius: 8px;
+ font-size: 0.95rem;
+ transition: all 0.2s ease;
+}
+
+.search-input:focus {
+ outline: none;
+ border-color: #667eea;
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+}
+
+.clear-search-btn {
+ position: absolute;
+ right: 8px;
+ padding: 0.25rem;
+ border: none;
+ background: transparent;
+ color: #94a3b8;
+ cursor: pointer;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease;
+}
+
+.clear-search-btn:hover {
+ background: #f1f5f9;
+ color: #64748b;
+}
+
+.meeting-list {
+ flex-grow: 1;
+ overflow-y: auto;
+ padding-right: 0.5rem;
+}
+
+.meeting-item {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 0.75rem;
+ margin-bottom: 0.5rem;
+ border-radius: 6px;
+ transition: background-color 0.2s ease;
+ cursor: pointer;
+}
+
+.meeting-item:hover {
+ background: #f8fafc;
+}
+
+.meeting-item.selected {
+ background: #dbeafe;
+ border: 1px solid #3b82f6;
+}
+
+.meeting-item input[type="checkbox"] {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+}
+
+.meeting-item input[type="radio"] {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+}
+
+.meeting-item label {
+ cursor: pointer;
+ flex: 1;
+ font-size: 0.95rem;
+ color: #475569;
+}
+
+.modal-actions {
+ margin-top: 1.5rem;
+ padding-top: 1rem;
+ border-top: 1px solid #e2e8f0;
+ text-align: right;
+}
+
+.modal-actions button {
+ padding: 0.75rem 1.5rem;
+ border-radius: 8px;
+ border: none;
+ background: #667eea;
+ color: white;
+ cursor: pointer;
+ font-weight: 500;
+ transition: all 0.2s ease;
+}
+
+.modal-actions button:hover {
+ background: #5568d3;
+}
+
+/* Detail Modal Styles */
+.detail-content {
+ overflow-y: auto;
+ flex-grow: 1;
+}
+
+.detail-meta {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ padding: 1rem;
+ background: #f8fafc;
+ border-radius: 8px;
+ margin-bottom: 1.5rem;
+}
+
+.detail-meta-item {
+ font-size: 0.95rem;
+ color: #475569;
+}
+
+.detail-meta-item strong {
+ color: #1e293b;
+ margin-right: 0.5rem;
+}
+
+.detail-body {
+ padding: 1rem 0;
+}
+
+.detail-body h3 {
+ margin: 0 0 1rem 0;
+ font-size: 1.125rem;
+ font-weight: 600;
+ color: #1e293b;
+}
+
+.kb-content-full {
+ line-height: 1.8;
+ color: #475569;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ padding: 1rem;
+ background: #f8fafc;
+ border-radius: 8px;
+ max-height: 400px;
+ overflow-y: auto;
+}
+
+/* Scrollbar Styles */
+.meeting-list::-webkit-scrollbar,
+.kb-content-full::-webkit-scrollbar {
+ width: 6px;
+}
+
+.meeting-list::-webkit-scrollbar-track,
+.kb-content-full::-webkit-scrollbar-track {
+ background: #f1f5f9;
+ border-radius: 3px;
+}
+
+.meeting-list::-webkit-scrollbar-thumb,
+.kb-content-full::-webkit-scrollbar-thumb {
+ background: #cbd5e1;
+ border-radius: 3px;
+}
+
+.meeting-list::-webkit-scrollbar-thumb:hover,
+.kb-content-full::-webkit-scrollbar-thumb:hover {
+ background: #94a3b8;
+}
+
+/* Delete Modal Styles - consistent with MeetingTimeline */
+.delete-modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+}
+
+.delete-modal {
+ background: white;
+ border-radius: 12px;
+ padding: 2rem;
+ max-width: 400px;
+ width: 90%;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+}
+
+.delete-modal h3 {
+ margin: 0 0 1rem 0;
+ color: #1e293b;
+ font-size: 1.25rem;
+}
+
+.delete-modal p {
+ margin: 0 0 2rem 0;
+ color: #64748b;
+ line-height: 1.6;
+}
+
+.delete-modal .modal-actions {
+ display: flex;
+ gap: 1rem;
+ justify-content: flex-end;
+ margin-top: 0;
+ padding-top: 0;
+ border-top: none;
+}
+
+.btn-cancel, .btn-delete {
+ padding: 0.5rem 1rem;
+ border-radius: 6px;
+ font-size: 0.9rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ border: none;
+}
+
+.btn-cancel {
+ background: #f1f5f9;
+ color: #475569;
+}
+
+.btn-cancel:hover {
+ background: #e2e8f0;
+}
+
+.btn-delete {
+ background: #ef4444;
+ color: white;
+}
+
+.btn-delete:hover {
+ background: #dc2626;
+}
+
+/* Source Meetings List Styles */
+.source-meetings-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ margin-top: 0.5rem;
+}
+
+.source-meeting-link {
+ display: inline-block;
+ padding: 0.5rem 0.75rem;
+ background: #f1f5f9;
+ border-radius: 6px;
+ color: #667eea;
+ text-decoration: none;
+ font-size: 0.9rem;
+ transition: all 0.2s ease;
+ border-left: 3px solid #667eea;
+}
+
+.source-meeting-link:hover {
+ background: #e2e8f0;
+ transform: translateX(4px);
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+
diff --git a/src/pages/KnowledgeBasePage.jsx b/src/pages/KnowledgeBasePage.jsx
new file mode 100644
index 0000000..70ed4b0
--- /dev/null
+++ b/src/pages/KnowledgeBasePage.jsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import { MessageSquare, Book, Users } from 'lucide-react';
+import { useNavigate } from 'react-router-dom';
+import { Tabs } from 'antd';
+import PersonalKnowledgeBase from '../components/knowledgebase/PersonalKnowledgeBase';
+import SharedKnowledgeBase from '../components/knowledgebase/SharedKnowledgeBase';
+import './KnowledgeBasePage.css';
+
+const { TabPane } = Tabs;
+
+const KnowledgeBasePage = ({ user }) => {
+ const navigate = useNavigate();
+
+ const handleLogoClick = () => {
+ navigate('/dashboard');
+ };
+
+ return (
+
+
+
+
+
+ iMeeting
+
+
知识库管理
+
+
+
+
+
+ 个人知识库}
+ key="personal"
+ >
+
+
+ 共享知识库}
+ key="shared"
+ >
+
+
+
+
+
+
+ );
+};
+
+export default KnowledgeBasePage;
\ No newline at end of file