📁 一、整体目录结构
frontend/
├── @/ # 公共 UI 组件库(shadcn/ui)
│ └── components/ui/ # 可复用 UI 组件
│ ├── button.tsx
│ ├── input.tsx
│ ├── dialog.tsx
│ ├── table.tsx
│ └── ... (15+ 组件)
│
├── src/ # 源代码目录
│ ├── main.tsx # 应用入口
│ ├── App.tsx # 根组件
│ ├── router.tsx # 路由配置
│ │
│ ├── components/ # 组件目录
│ │ ├── admin/ # 管理后台组件
│ │ ├── chat/ # 聊天相关组件
│ │ ├── common/ # 通用组件
│ │ ├── layout/ # 布局组件
│ │ ├── session/ # 会话列表组件
│ │ └── ui/ # UI 组件
│ │
│ ├── pages/ # 页面目录
│ │ ├── LoginPage.tsx # 登录页
│ │ ├── ChatPage.tsx # 聊天页
│ │ ├── NotFoundPage.tsx # 404 页面
│ │ │
│ │ └── admin/ # 管理后台页面
│ │ ├── AdminLayout.tsx # 管理后台布局
│ │ ├── dashboard/ # 仪表盘
│ │ ├── knowledge/ # 知识库管理
│ │ ├── intent-tree/ # 意图树管理
│ │ ├── ingestion/ # 文档摄取
│ │ ├── traces/ # RAG 追踪
│ │ ├── sample-questions/ # 示例问题
│ │ ├── query-term-mapping/ # 查询词映射
│ │ ├── settings/ # 系统设置
│ │ └── users/ # 用户管理
│ │
│ ├── services/ # API 服务层
│ │ ├── api.ts # Axios 实例配置
│ │ ├── authService.ts # 认证服务
│ │ ├── chatService.ts # 聊天服务
│ │ ├── sessionService.ts # 会话服务
│ │ ├── knowledgeService.ts # 知识库服务
│ │ └── ...
│ │
│ ├── stores/ # 状态管理(Zustand)
│ │ ├── authStore.ts # 认证状态
│ │ ├── chatStore.ts # 聊天状态
│ │ └── themeStore.ts # 主题状态
│ │
│ ├── hooks/ # 自定义 Hooks
│ │ ├── useAuth.ts # 认证 Hook
│ │ ├── useChat.ts # 聊天 Hook
│ │ └── useStreamResponse.ts # 流式响应 Hook
│ │
│ ├── types/ # TypeScript 类型定义
│ │ └── index.ts # 统一类型导出
│ │
│ ├── utils/ # 工具函数
│ │ ├── storage.ts # 本地存储
│ │ ├── helpers.ts # 辅助函数
│ │ └── error.ts # 错误处理
│ │
│ └── lib/ # 库配置
│ └── utils.ts # 工具函数(cn 等)
│
├── public/ # 静态资源
├── package.json # 依赖配置
├── vite.config.ts # Vite 配置
├── tailwind.config.cjs # Tailwind 配置
└── tsconfig.json # TypeScript 配置
🎯 二、技术栈概览
{
"框架": "React 18 + TypeScript",
"构建工具": "Vite",
"路由": "React Router 6",
"状态管理": "Zustand",
"UI 组件": "shadcn/ui (Radix UI)",
"样式": "Tailwind CSS",
"表单验证": "React Hook Form + Zod",
"HTTP 客户端": "Axios",
"Markdown 渲染": "react-markdown + remark-gfm",
"图表": "Recharts",
"图标": "Lucide React",
"提示": "Sonner (Toast)"
}
📦 三、核心目录详解
3.1 src/components/ - 组件目录
components/
├── admin/ # 管理后台专用组件
│ ├── CreateKnowledgeBaseDialog.tsx # 创建知识库对话框
│ └── SimpleLineChart.tsx # 简单折线图
│
├── chat/ # 聊天相关组件
│ ├── ChatInput.tsx # 聊天输入框
│ ├── MessageList.tsx # 消息列表
│ ├── MessageItem.tsx # 单条消息
│ ├── MarkdownRenderer.tsx # Markdown 渲染器
│ ├── ThinkingIndicator.tsx # 思考指示器
│ ├── FeedbackButtons.tsx # 反馈按钮
│ └── WelcomeScreen.tsx # 欢迎屏幕
│
├── common/ # 通用组件
│ ├── Avatar.tsx # 头像组件
│ ├── ErrorBoundary.tsx # 错误边界
│ ├── Loading.tsx # 加载状态
│ └── Toast.tsx # 提示组件
│
├── layout/ # 布局组件
│ ├── MainLayout.tsx # 主布局(聊天页)
│ ├── Sidebar.tsx # 侧边栏
│ └── Header.tsx # 顶部导航
│
├── session/ # 会话列表组件
│ ├── SessionList.tsx # 会话列表
│ └── SessionItem.tsx # 单个会话项
│
└── ui/ # shadcn/ui 基础组件
├── button.tsx
├── input.tsx
├── dialog.tsx
├── table.tsx
├── select.tsx
└── ... (15+ 组件)
聊天组件详解
// ChatInput.tsx - 聊天输入框
export function ChatInput() {
// 状态
const [value, setValue] = React.useState("");
const [isFocused, setIsFocused] = React.useState(false);
// 引用
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
// 自动调整高度
const adjustHeight = React.useCallback(() => {
const el = textareaRef.current;
if (!el) return;
el.style.height = "auto";
el.style.height = `${Math.min(el.scrollHeight, 160)}px`;
}, []);
// 发送消息
const handleSubmit = async () => {
if (isStreaming) {
cancelGeneration(); // 停止生成
return;
}
await sendMessage(value); // 发送消息
};
return (
<div className="flex items-end gap-2">
<Textarea
ref={textareaRef}
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="输入问题..."
/>
<Button onClick={handleSubmit}>
{isStreaming ? <Square /> : <Send />}
</Button>
</div>
);
}
3.2 src/pages/ - 页面目录
pages/
├── LoginPage.tsx # 登录页
├── ChatPage.tsx # 聊天页(用户端)
├── NotFoundPage.tsx # 404 页面
│
└── admin/ # 管理后台页面
├── AdminLayout.tsx # 管理后台布局(带侧边栏)
│
├── dashboard/
│ └── DashboardPage.tsx # 仪表盘首页
│
├── knowledge/ # 知识库管理
│ ├── KnowledgeListPage.tsx # 知识库列表
│ ├── KnowledgeDocumentsPage.tsx # 文档列表
│ └── KnowledgeChunksPage.tsx # 分块列表
│
├── intent-tree/ # 意图树管理
│ ├── IntentTreePage.tsx # 意图树视图
│ ├── IntentListPage.tsx # 意图列表
│ └── IntentEditPage.tsx # 意图编辑
│
├── ingestion/
│ └── IngestionPage.tsx # 文档摄取
│
├── traces/ # RAG 追踪
│ ├── RagTracePage.tsx # 追踪列表
│ ├── RagTraceDetailPage.tsx # 追踪详情
│ └── components/
│ ├── FilterBar.tsx
│ ├── RunsTable.tsx
│ └── StatCard.tsx
│
├── sample-questions/
│ └── SampleQuestionPage.tsx # 示例问题管理
│
├── query-term-mapping/
│ └── QueryTermMappingPage.tsx # 查询词映射
│
├── settings/
│ └── SystemSettingsPage.tsx # 系统设置
│
└── users/
└── UserListPage.tsx # 用户列表
页面组件示例
// ChatPage.tsx - 聊天页面
export function ChatPage() {
const {
messages,
isLoading,
isStreaming,
fetchSessions,
selectSession,
createSession
} = useChatStore();
// 初始化:加载会话列表
React.useEffect(() => {
fetchSessions();
}, [fetchSessions]);
// 渲染
return (
<MainLayout>
{/* 左侧:会话列表 */}
<aside className="w-64 border-r">
<SessionList />
</aside>
{/* 右侧:聊天区域 */}
<main className="flex-1 flex flex-col">
{/* 消息列表 */}
<MessageList messages={messages} isLoading={isLoading} />
{/* 输入框 */}
<ChatInput />
</main>
</MainLayout>
);
}
3.3 src/services/ - API 服务层
services/
├── api.ts # Axios 实例 + 拦截器
├── authService.ts # 认证相关
├── chatService.ts # 聊天 API
├── sessionService.ts # 会话管理 API
├── knowledgeService.ts # 知识库 API
├── intentTreeService.ts # 意图树 API
├── ingestionService.ts # 文档摄取 API
├── ragTraceService.ts # RAG 追踪 API
├── settingsService.ts # 设置 API
├── userService.ts # 用户管理 API
├── dashboardService.ts # 仪表盘 API
├── sampleQuestionService.ts # 示例问题 API
└── queryTermMappingService.ts # 查询词映射 API
Axios 实例配置
// services/api.ts
import axios from "axios";
import { storage } from "@/utils/storage";
export const api = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || "",
timeout: 60000
});
// 请求拦截器:添加 Token
api.interceptors.request.use((config) => {
const token = storage.getToken();
if (token) {
config.headers.Authorization = token;
}
return config;
});
// 响应拦截器:统一错误处理
api.interceptors.response.use(
(response) => {
const payload = response.data;
// 业务错误处理
if (payload.code !== "0") {
const message = payload.message || "请求失败";
// Token 过期,跳转登录
if (message.includes("未登录")) {
storage.clearAuth();
window.location.href = "/login";
}
return Promise.reject(new Error(message));
}
return payload.data;
},
(error) => {
// HTTP 错误处理
if (error?.response?.status === 401) {
storage.clearAuth();
window.location.href = "/login";
}
return Promise.reject(error);
}
);
服务层示例
// services/chatService.ts
import { api } from "./api";
import type { Message, FeedbackValue } from "@/types";
export interface SendMessageParams {
question: string;
conversationId?: string;
thinking?: boolean;
}
export async function sendMessage(params: SendMessageParams) {
return api.post("/rag/v3/chat", params);
}
export async function stopTask(taskId: string) {
return api.post(`/rag/v3/stop/${taskId}`);
}
export async function submitFeedback(messageId: string, feedback: FeedbackValue) {
return api.post("/feedback", { messageId, feedback });
}
3.4 src/stores/ - 状态管理(Zustand)
stores/
├── authStore.ts # 认证状态
├── chatStore.ts # 聊天状态
└── themeStore.ts # 主题状态
ChatStore 详解
// stores/chatStore.ts
interface ChatState {
// 数据
sessions: Session[];
currentSessionId: string | null;
messages: Message[];
// UI 状态
isLoading: boolean;
isStreaming: boolean;
deepThinkingEnabled: boolean;
// Actions
fetchSessions: () => Promise<void>;
createSession: () => Promise<string>;
selectSession: (sessionId: string) => Promise<void>;
sendMessage: (content: string) => Promise<void>;
cancelGeneration: () => void;
}
export const useChatStore = create<ChatState>((set, get) => ({
// 初始状态
sessions: [],
currentSessionId: null,
messages: [],
isLoading: false,
isStreaming: false,
deepThinkingEnabled: false,
// Actions
sendMessage: async (content: string) => {
set({ isLoading: true });
try {
// 调用 API
const response = await chatService.sendMessage({
question: content,
conversationId: get().currentSessionId
});
// 处理响应
// ...
} finally {
set({ isLoading: false });
}
}
}));
3.5 src/hooks/ - 自定义 Hooks
hooks/
├── useAuth.ts # 认证相关 Hook
├── useChat.ts # 聊天相关 Hook(useChatStore 的包装)
└── useStreamResponse.ts # SSE 流式响应处理
流式响应 Hook
// hooks/useStreamResponse.ts
export interface StreamHandlers {
onMeta?: (payload: StreamMetaPayload) => void;
onMessage?: (payload: MessageDeltaPayload) => void;
onThinking?: (payload: MessageDeltaPayload) => void;
onFinish?: (payload: CompletionPayload) => void;
onError?: (error: Error) => void;
}
export async function readSseStream(
response: Response,
handlers: StreamHandlers,
signal?: AbortSignal
) {
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// 按行解析 SSE 事件
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (line.startsWith("event:")) {
// 事件类型
} else if (line.startsWith("data:")) {
// 数据内容
const data = JSON.parse(line.slice(5));
handlers.onMessage?.(data);
}
}
}
}
3.6 src/router.tsx - 路由配置
// router.tsx
export const router = createBrowserRouter([
{
path: "/",
element: <HomeRedirect /> // 首页重定向
},
{
path: "/login",
element: <LoginPage /> // 登录页(已登录则重定向)
},
{
path: "/chat",
element: (
<RequireAuth>
<ChatPage /> // 聊天页(需登录)
</RequireAuth>
)
},
{
path: "/admin",
element: (
<RequireAdmin>
<AdminLayout /> // 管理后台布局
</RequireAdmin>
),
children: [
{ path: "dashboard", element: <DashboardPage /> },
{ path: "knowledge", element: <KnowledgeListPage /> },
{ path: "knowledge/:id/documents", element: <KnowledgeDocumentsPage /> },
{ path: "knowledge/:id/chunks", element: <KnowledgeChunksPage /> },
{ path: "intent-tree", element: <IntentTreePage /> },
{ path: "intent-tree/list", element: <IntentListPage /> },
{ path: "intent-tree/edit/:id", element: <IntentEditPage /> },
{ path: "ingestion", element: <IngestionPage /> },
{ path: "traces", element: <RagTracePage /> },
{ path: "traces/:id", element: <RagTraceDetailPage /> },
{ path: "settings", element: <SystemSettingsPage /> },
{ path: "sample-questions", element: <SampleQuestionPage /> },
{ path: "query-term-mapping", element: <QueryTermMappingPage /> },
{ path: "users", element: <UserListPage /> }
]
}
]);
3.7 src/types/index.ts - 类型定义
// types/index.ts
// 会话
export interface Session {
id: string;
title: string;
lastTime?: string;
messageCount?: number;
}
// 消息
export interface Message {
id: string;
role: "user" | "assistant" | "system";
content: string;
thinking?: string;
vote?: 1 | -1 | null;
createdAt: string;
}
// 流式事件载荷
export interface MessageDeltaPayload {
id: string;
type: "think" | "content";
delta: string;
}
export interface CompletionPayload {
id: string;
content: string;
thinking?: string;
taskId: string;
}
export interface StreamMetaPayload {
conversationId: string;
messageId: string;
taskId: string;
}
// 反馈
export type FeedbackValue = "like" | "dislike" | null;
🏗️ 四、数据流架构
┌─────────────────────────────────────────────────────────────────────┐
│ 用户交互 │
│ (输入问题/点击) │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ UI 组件层 │
│ ChatInput ─→ useChatStore ─→ MessageList ─→ ChatPage │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 状态管理层 (Zustand) │
│
│ useChatStore: │
│ ├── sessions: Session[] # 会话列表 │
│ ├── messages: Message[] # 消息列表 │
│ ├── isStreaming: boolean # 流式状态 │
│ └── methods: sendMessage() # 发送消息 │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 服务层 (API Services) │
│
│ chatService.sendMessage() # HTTP 请求 │
│ sessionService.listSessions() # 获取会话 │
│ ragTraceService.query() # 查询追踪 │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ HTTP 层 (Axios) │
│
│ api.ts: │
│ ├── 请求拦截器(添加 Token) │
│ ├── 响应拦截器(统一错误处理) │
│ └── SSE 流式处理(readSseStream) │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ 后端服务 (Java/Spring) │
│
│ RAGChatController ─→ RAGChatService ─→ MultiChannelRetrieval │
└─────────────────────────────────────────────────────────────────────┘
📊 五、管理后台功能模块
| 模块 |
页面路径 |
主要功能 |
| 仪表盘 |
/admin/dashboard |
数据统计、趋势图表 |
| 知识库管理 |
/admin/knowledge |
创建/编辑/删除知识库 |
| 文档管理 |
/admin/knowledge/:id/documents |
上传/查看/删除文档 |
| 分块管理 |
/admin/knowledge/:id/chunks |
查看文档分块结果 |
| 意图树 |
/admin/intent-tree |
可视化意图树 |
| 意图列表 |
/admin/intent-tree/list |
列表管理意图 |
| 意图编辑 |
/admin/intent-tree/edit/:id |
编辑意图节点 |
| 文档摄取 |
/admin/ingestion |
触发文档处理任务 |
| RAG 追踪 |
/admin/traces |
查看 RAG 执行链路 |
| 追踪详情 |
/admin/traces/:id |
查看单次执行详情 |
| 示例问题 |
/admin/sample-questions |
管理示例问题 |
| 查询词映射 |
/admin/query-term-mapping |
查询词优化 |
| 系统设置 |
/admin/settings |
配置系统参数 |
| 用户管理 |
/admin/users |
用户列表/权限 |
🔧 六、组件复用示例
6.1 shadcn/ui 组件使用
// 使用 Button 组件
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
<Button
variant="outline"
size="sm"
onClick={handleClick}
className="gap-2"
>
<Plus className="h-4 w-4" />
新建
</Button>
6.2 自定义 Hook 封装
// hooks/useChat.ts - 封装 chatStore
import { useChatStore } from "@/stores/chatStore";
export function useChat() {
return useChatStore();
}
// 使用
const { messages, sendMessage } = useChat();
📁 七、相关文件路径汇总
| 功能 |
文件路径 |
| 应用入口 |
src/main.tsx |
| 根组件 |
src/App.tsx |
| 路由配置 |
src/router.tsx |
| 聊天页面 |
src/pages/ChatPage.tsx |
| 登录页面 |
src/pages/LoginPage.tsx |
| 管理后台布局 |
src/pages/admin/AdminLayout.tsx |
| 聊天输入框 |
src/components/chat/ChatInput.tsx |
| 消息列表 |
src/components/chat/MessageList.tsx |
| 会话列表 |
src/components/session/SessionList.tsx |
| 状态管理 |
src/stores/chatStore.ts |
| API 配置 |
src/services/api.ts |
| 聊天服务 |
src/services/chatService.ts |
| 流式响应 |
src/hooks/useStreamResponse.ts |
| 类型定义 |
src/types/index.ts |
💡 八、开发建议
8.1 新增页面步骤
1. 在 src/pages/admin/ 下创建页面组件
2. 在 router.tsx 中添加路由
3. 在 AdminLayout.tsx 中添加菜单项
4. 在对应的 services/ 中添加 API 方法
8.2 新增组件步骤
1. UI 组件:放在 src/components/ui/
2. 业务组件:放在对应的功能目录下
3. 使用 shadcn/ui:运行 npx shadcn@latest add button
8.3 状态管理原则
✅ 页面级状态:使用 React useState
✅ 跨组件状态:使用 Zustand Store
✅ 服务端状态:使用 API Service + Store
这个前端架构设计清晰,模块化程度高,便于维护和扩展!