feat: RAG inline citations, source highlighting, and admin panel

- Increase RAG retrieval from 5 to 50 docs with relevance threshold (0.15)
- LLM inline citations with [来源N] format and reference list
- Clickable citation links scroll to source cards with highlight animation
- Source previews with full chunk text and answer-matched highlighting
- Message-scoped source IDs to fix cross-round citation targeting
- Admin panel pages (knowledge, users, forum, course, settings)
- Add rehype-raw dependency for HTML-in-markdown rendering

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 20:51:28 +08:00
parent 5d42a0573a
commit abcced35b8
23 changed files with 2249 additions and 112 deletions
+129
View File
@@ -583,6 +583,9 @@ export const chatAPI = {
} else if (data.type === "chunk") {
console.log("[DEBUG-STREAM] 接收chunk:", data.content);
onChunk?.(data.content);
} else if (data.type === "sources") {
console.log("[DEBUG-STREAM] 收到sources:", data.sources?.length, "个来源");
onChunk?.(JSON.stringify(data));
} else if (data.type === "done") {
console.log("[DEBUG-STREAM] 流式完成, session_id:", data.session_id, "message_id:", data.message_id, "user_message_id:", data.user_message_id);
onComplete?.(data.session_id, data.message_id, data.user_message_id);
@@ -1067,3 +1070,129 @@ export const bookAPI = {
return `${API_BASE_URL}/books/${bookId}/file`;
},
};
// ===== 后台管理 API =====
export const adminAPI = {
// 仪表盘
async getDashboard() {
return apiRequest<{
total_users: number;
active_users_7d: number;
total_sessions: number;
total_messages: number;
total_documents: number;
total_knowledge_bases: number;
total_forum_posts: number;
total_forum_replies: number;
total_generated_images: number;
}>("/admin/dashboard");
},
async getUserTrends(days = 30) {
return apiRequest<{ date: string; count: number }[]>(`/admin/trends/users?days=${days}`);
},
async getMessageTrends(days = 30) {
return apiRequest<{ date: string; count: number }[]>(`/admin/trends/messages?days=${days}`);
},
// 用户管理
async listUsers(skip = 0, limit = 50) {
return apiRequest<{
id: number;
username: string;
email: string;
full_name: string | null;
is_active: boolean;
is_superuser: boolean;
created_at: string | null;
last_login: string | null;
session_count: number;
message_count: number;
}[]>(`/admin/users?skip=${skip}&limit=${limit}`);
},
async toggleUserActive(userId: number) {
return apiRequest<{ success: boolean; is_active: boolean }>(`/admin/users/${userId}/toggle-active`, { method: "PUT" });
},
async toggleUserAdmin(userId: number) {
return apiRequest<{ success: boolean; is_superuser: boolean }>(`/admin/users/${userId}/toggle-admin`, { method: "PUT" });
},
async deleteUser(userId: number) {
return apiRequest<{ success: boolean }>(`/admin/users/${userId}`, { method: "DELETE" });
},
// 论坛管理
async listForumCategories() {
return apiRequest<{
id: number;
slug: string;
name: string;
description: string | null;
post_count: number;
}[]>("/admin/forum/categories");
},
async createForumCategory(data: { name: string; slug: string; description?: string }) {
return apiRequest("/admin/forum/categories", {
method: "POST",
body: JSON.stringify(data),
});
},
async updateForumCategory(categoryId: number, data: { name: string; slug: string; description?: string }) {
return apiRequest(`/admin/forum/categories/${categoryId}`, {
method: "PUT",
body: JSON.stringify(data),
});
},
async deleteForumCategory(categoryId: number) {
return apiRequest<{ success: boolean }>(`/admin/forum/categories/${categoryId}`, { method: "DELETE" });
},
async listForumPosts(skip = 0, limit = 50) {
return apiRequest<{
id: number;
title: string;
author_name: string;
category_name: string;
reply_count: number;
created_at: string;
}[]>(`/admin/forum/posts?skip=${skip}&limit=${limit}`);
},
async deleteForumPost(postId: number) {
return apiRequest<{ success: boolean }>(`/admin/forum/posts/${postId}`, { method: "DELETE" });
},
// 知识库管理
async listKnowledgeBases() {
return apiRequest<{
id: number;
name: string;
description: string | null;
owner_name: string;
is_system: boolean;
document_count: number;
chunk_count: number;
created_at: string;
}[]>("/admin/knowledge-bases");
},
async deleteKnowledgeBase(kbId: number) {
return apiRequest<{ success: boolean }>(`/admin/knowledge-bases/${kbId}`, { method: "DELETE" });
},
// 系统状态
async getSystemStatus() {
return apiRequest<{
database: { status: string };
vector_store: { status: string; vector_count: number };
llm_model: string;
embedding_model: string;
}>("/admin/system/status");
},
};