visuddhinanda 6 months ago
parent
commit
b26f53d307

+ 269 - 0
dashboard-v4/dashboard/src/services/mockChatApi.ts

@@ -0,0 +1,269 @@
+import { CreateChatRequest, ChatResponse, ApiResponse } from "../types/chat";
+
+// Mock 存储,模拟数据库
+let mockChats: ChatResponse[] = [
+  {
+    id: "550e8400-e29b-41d4-a716-446655440000",
+    title: "天气查询助手",
+    user_id: "ba5463f3-72d1-4410-858e-eadd10884713",
+    created_at: "2025-01-15T10:30:00.000000Z",
+    updated_at: "2025-01-15T10:30:00.000000Z",
+  },
+  {
+    id: "660f8400-e29b-41d4-a716-446655440001",
+    title: "编程问题讨论",
+    user_id: "ba5463f3-72d1-4410-858e-eadd10884713",
+    created_at: "2025-01-14T15:20:00.000000Z",
+    updated_at: "2025-01-14T15:20:00.000000Z",
+  },
+  {
+    id: "770e8400-e29b-41d4-a716-446655440002",
+    title: "数学问题求解",
+    user_id: "ba5463f3-72d1-4410-858e-eadd10884713",
+    created_at: "2025-01-13T09:15:00.000000Z",
+    updated_at: "2025-01-13T09:15:00.000000Z",
+  },
+];
+
+// 工具函数:生成UUID
+function generateUUID(): string {
+  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
+    const r = (Math.random() * 16) | 0;
+    const v = c === "x" ? r : (r & 0x3) | 0x8;
+    return v.toString(16);
+  });
+}
+
+// 工具函数:模拟网络延迟
+function mockDelay(ms: number = 500): Promise<void> {
+  return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+// 工具函数:创建成功响应
+function createSuccessResponse<T>(data: T): ApiResponse<T> {
+  return {
+    ok: true,
+    message: "ok",
+    data,
+  };
+}
+
+// 工具函数:创建错误响应
+function createErrorResponse(
+  message: string,
+  errors?: Record<string, string[]>
+): ApiResponse<any> {
+  return {
+    ok: false,
+    message,
+    data: "",
+    errors,
+  };
+}
+
+// Mock Chat API 实现
+export const mockChatApi = {
+  async createChat(
+    request: CreateChatRequest
+  ): Promise<ApiResponse<ChatResponse>> {
+    console.log("[Mock API] Creating chat:", request);
+
+    await mockDelay(300);
+
+    // 验证请求
+    if (!request.title || request.title.trim() === "") {
+      return createErrorResponse("标题不能为空", {
+        title: ["标题是必填字段"],
+      });
+    }
+
+    if (request.title.length > 255) {
+      return createErrorResponse("标题过长", {
+        title: ["标题长度不能超过255个字符"],
+      });
+    }
+
+    // 创建新聊天
+    const newChat: ChatResponse = {
+      id: generateUUID(),
+      title: request.title.trim(),
+      user_id: request.user_id,
+      created_at: new Date().toISOString(),
+      updated_at: new Date().toISOString(),
+    };
+
+    mockChats.unshift(newChat); // 新聊天放在最前面
+    console.log("[Mock API] Chat created:", newChat);
+
+    return createSuccessResponse(newChat);
+  },
+
+  async getChat(chatId: string): Promise<ApiResponse<ChatResponse>> {
+    console.log("[Mock API] Getting chat:", chatId);
+
+    await mockDelay(200);
+
+    const chat = mockChats.find((c) => c.id === chatId);
+
+    if (!chat) {
+      return createErrorResponse("聊天不存在");
+    }
+
+    return createSuccessResponse(chat);
+  },
+
+  async updateChat(
+    chatId: string,
+    updates: Partial<CreateChatRequest>
+  ): Promise<ApiResponse<ChatResponse>> {
+    console.log("[Mock API] Updating chat:", chatId, updates);
+
+    await mockDelay(300);
+
+    const chatIndex = mockChats.findIndex((c) => c.id === chatId);
+
+    if (chatIndex === -1) {
+      return createErrorResponse("聊天不存在");
+    }
+
+    // 验证更新数据
+    if (updates.title !== undefined) {
+      if (!updates.title || updates.title.trim() === "") {
+        return createErrorResponse("标题不能为空", {
+          title: ["标题是必填字段"],
+        });
+      }
+
+      if (updates.title.length > 255) {
+        return createErrorResponse("标题过长", {
+          title: ["标题长度不能超过255个字符"],
+        });
+      }
+    }
+
+    // 更新聊天
+    const updatedChat: ChatResponse = {
+      ...mockChats[chatIndex],
+      ...updates,
+      title: updates.title?.trim() || mockChats[chatIndex].title,
+      updated_at: new Date().toISOString(),
+    };
+
+    mockChats[chatIndex] = updatedChat;
+    console.log("[Mock API] Chat updated:", updatedChat);
+
+    return createSuccessResponse(updatedChat);
+  },
+
+  async deleteChat(chatId: string): Promise<ApiResponse<void>> {
+    console.log("[Mock API] Deleting chat:", chatId);
+
+    await mockDelay(200);
+
+    const chatIndex = mockChats.findIndex((c) => c.id === chatId);
+
+    if (chatIndex === -1) {
+      return createErrorResponse("聊天不存在");
+    }
+
+    // 软删除:实际项目中会设置 deleted_at 字段
+    // 这里为了简化,直接从数组中移除
+    mockChats.splice(chatIndex, 1);
+    console.log("[Mock API] Chat deleted");
+
+    return createSuccessResponse(undefined as any);
+  },
+
+  // 额外的辅助方法,便于测试和开发
+  async getChats(params?: {
+    limit?: number;
+    user_id?: string;
+    offset?: number;
+  }): Promise<
+    ApiResponse<{
+      rows: ChatResponse[];
+      total: number;
+    }>
+  > {
+    console.log("[Mock API] Getting chats:", params);
+
+    await mockDelay(300);
+
+    let filteredChats = [...mockChats];
+
+    // 按用户过滤
+    if (params?.user_id) {
+      filteredChats = filteredChats.filter(
+        (chat) => chat.user_id === params.user_id
+      );
+    }
+
+    // 分页
+    const total = filteredChats.length;
+    const offset = params?.offset || 0;
+    const limit = params?.limit || 20;
+    const paginatedChats = filteredChats.slice(offset, offset + limit);
+
+    return createSuccessResponse({
+      rows: paginatedChats,
+      total,
+    });
+  },
+
+  // 重置 Mock 数据(用于测试)
+  resetMockData(): void {
+    console.log("[Mock API] Resetting mock data");
+    mockChats = [
+      {
+        id: "550e8400-e29b-41d4-a716-446655440000",
+        title: "天气查询助手",
+        user_id: "ba5463f3-72d1-4410-858e-eadd10884713",
+        created_at: "2025-01-15T10:30:00.000000Z",
+        updated_at: "2025-01-15T10:30:00.000000Z",
+      },
+      {
+        id: "660f8400-e29b-41d4-a716-446655440001",
+        title: "编程问题讨论",
+        user_id: "ba5463f3-72d1-4410-858e-eadd10884713",
+        created_at: "2025-01-14T15:20:00.000000Z",
+        updated_at: "2025-01-14T15:20:00.000000Z",
+      },
+      {
+        id: "770e8400-e29b-41d4-a716-446655440002",
+        title: "数学问题求解",
+        user_id: "ba5463f3-72d1-4410-858e-eadd10884713",
+        created_at: "2025-01-13T09:15:00.000000Z",
+        updated_at: "2025-01-13T09:15:00.000000Z",
+      },
+    ];
+  },
+
+  // 获取当前 Mock 数据(用于调试)
+  getMockData(): ChatResponse[] {
+    return [...mockChats];
+  },
+
+  // 模拟网络错误
+  async createChatWithNetworkError(): Promise<ApiResponse<ChatResponse>> {
+    await mockDelay(1000);
+    throw new Error("网络连接失败");
+  },
+
+  // 模拟服务器错误
+  async createChatWithServerError(): Promise<ApiResponse<ChatResponse>> {
+    await mockDelay(500);
+    return {
+      ok: false,
+      message: "服务器内部错误",
+    } as ApiResponse<any>;
+  },
+
+  // 设置延迟时间(用于测试不同网络条件)
+  setMockDelay(ms: number): void {
+    // 这里可以实现动态调整延迟的逻辑
+    console.log(`[Mock API] Mock delay set to ${ms}ms`);
+  },
+};
+
+// 导出类型,便于在其他地方使用
+export type MockChatApi = typeof mockChatApi;

+ 600 - 0
dashboard-v4/dashboard/src/services/mockMessageApi.ts

@@ -0,0 +1,600 @@
+// services/mockMessageApi.ts
+import {
+  CreateMessageRequest,
+  MessageListResponse,
+  ApiResponse,
+  MessageNode,
+} from "../types/chat";
+// Mock 消息数据库
+let mockMessages: MessageNode[] = [
+  // System 消息 (根节点)
+  {
+    id: 1,
+    uid: "msg-system-001",
+    chat_id: "chat-001",
+    session_id: "system-session",
+    role: "system",
+    content:
+      "你是一个巴利语专家和佛教术语解释助手。当用户询问佛教术语时,你可以调用 searchTerm 函数来查询详细信息。",
+    is_active: true,
+    created_at: "2025-01-15T10:00:00Z",
+    updated_at: "2025-01-15T10:00:00Z",
+  },
+
+  // 第一轮对话 - 用户询问佛教术语
+  {
+    id: 2,
+    uid: "msg-user-001",
+    chat_id: "chat-001",
+    parent_id: "msg-system-001",
+    session_id: "session-001",
+    role: "user",
+    content: "什么是 dhamma?请详细解释一下。",
+    is_active: true,
+    created_at: "2025-01-15T10:01:00Z",
+    updated_at: "2025-01-15T10:01:00Z",
+  },
+
+  // AI 回答 - 带 Function Call
+  {
+    id: 3,
+    uid: "msg-assistant-001",
+    chat_id: "chat-001",
+    parent_id: "msg-user-001",
+    session_id: "session-001",
+    role: "assistant",
+    model_id: "gpt-4",
+    tool_calls: [
+      {
+        id: "call_dhamma_001",
+        function: "searchTerm",
+        arguments: { term: "dhamma" },
+      },
+    ],
+    is_active: true,
+    metadata: {
+      generation_params: {
+        temperature: 0.7,
+        max_tokens: 2048,
+      },
+    },
+    created_at: "2025-01-15T10:01:30Z",
+    updated_at: "2025-01-15T10:01:30Z",
+  },
+
+  // Tool 调用结果
+  {
+    id: 4,
+    uid: "msg-tool-001",
+    chat_id: "chat-001",
+    parent_id: "msg-assistant-001",
+    session_id: "session-001",
+    role: "tool",
+    content:
+      '{"term":"dhamma","definition":"法;教法;正义;真理","etymology":"来自梵语dharma","category":"佛教基本概念","explanation":"Dhamma是佛教中最核心的概念之一,指佛陀的教导、宇宙的法则以及存在的真理。"}',
+    tool_call_id: "call_dhamma_001",
+    is_active: true,
+    created_at: "2025-01-15T10:01:35Z",
+    updated_at: "2025-01-15T10:01:35Z",
+  },
+
+  // AI 最终回答
+  {
+    id: 5,
+    uid: "msg-assistant-002",
+    chat_id: "chat-001",
+    parent_id: "msg-tool-001",
+    session_id: "session-001",
+    role: "assistant",
+    content:
+      'Dhamma(法)是佛教中最重要的概念之一。根据巴利语词典,Dhamma有以下几层含义:\n\n1. **佛陀的教导**:指佛陀所传授的教法和智慧\n2. **宇宙法则**:指支配宇宙运行的自然规律\n3. **正义与真理**:指正确的行为准则和道德标准\n\nDhamma来自梵语"dharma",在不同语境下可以指代教法、法则、正义、真理等。对于佛教修行者来说,学习和实践Dhamma是走向解脱的根本途径。',
+    model_id: "gpt-4",
+    is_active: true,
+    metadata: {
+      generation_params: {
+        temperature: 0.7,
+        max_tokens: 2048,
+      },
+      token_usage: {
+        prompt_tokens: 180,
+        completion_tokens: 220,
+        total_tokens: 400,
+      },
+      performance: {
+        response_time_ms: 1500,
+        first_token_time_ms: 600,
+      },
+    },
+    created_at: "2025-01-15T10:01:45Z",
+    updated_at: "2025-01-15T10:01:45Z",
+  },
+
+  // 第二轮对话 - 用户追问
+  {
+    id: 6,
+    uid: "msg-user-002",
+    chat_id: "chat-001",
+    parent_id: "msg-assistant-002",
+    session_id: "session-002",
+    role: "user",
+    content: "那 karma 和 dhamma 有什么关系呢?",
+    is_active: true,
+    created_at: "2025-01-15T10:02:00Z",
+    updated_at: "2025-01-15T10:02:00Z",
+  },
+
+  // AI 第二次回答(第一个版本,当前激活)
+  {
+    id: 7,
+    uid: "msg-assistant-003",
+    chat_id: "chat-001",
+    parent_id: "msg-user-002",
+    session_id: "session-002",
+    role: "assistant",
+    content:
+      "Karma(业)和 Dhamma(法)是密切相关的佛教概念:\n\n**Karma(业力法则)**是 Dhamma 的重要组成部分。Karma 描述了行为与后果之间的因果关系,而这个因果法则本身就是宇宙运行的 Dhamma 之一。\n\n简单来说:\n- Dhamma 是更大的框架,包含了所有佛教教义和宇宙法则\n- Karma 是 Dhamma 中的具体法则之一,专门解释行为的因果关系\n\n通过理解 Karma,我们能更好地实践 Dhamma;通过实践 Dhamma,我们能更好地净化 Karma。",
+    model_id: "gpt-4",
+    is_active: true,
+    metadata: {
+      generation_params: {
+        temperature: 0.7,
+        max_tokens: 2048,
+      },
+      token_usage: {
+        prompt_tokens: 420,
+        completion_tokens: 150,
+        total_tokens: 570,
+      },
+    },
+    created_at: "2025-01-15T10:02:15Z",
+    updated_at: "2025-01-15T10:02:15Z",
+  },
+
+  // 第二个版本的AI回答(用户点了刷新,但当前未激活)
+  {
+    id: 8,
+    uid: "msg-assistant-004",
+    chat_id: "chat-001",
+    parent_id: "msg-user-002",
+    session_id: "session-v2-002",
+    role: "assistant",
+    content:
+      "Karma 和 Dhamma 的关系可以从三个层面来理解:\n\n1. **本质关系**:Karma 法则本身就是 Dhamma 的体现,它揭示了宇宙中行为与结果的必然联系\n\n2. **实践关系**:\n   - 学习 Dhamma → 了解 Karma 的运作机制\n   - 正确认识 Karma → 能够依 Dhamma 而行\n\n3. **解脱关系**:通过 Dhamma 的指导,我们可以创造善业、净化恶业,最终超越业力束缚\n\n总的来说,Dhamma 为我们提供了理解和转化 Karma 的智慧工具。",
+    model_id: "claude-3-sonnet",
+    is_active: false, // 这是第二个版本,当前不激活
+    metadata: {
+      generation_params: {
+        temperature: 0.8,
+        max_tokens: 2048,
+      },
+      token_usage: {
+        prompt_tokens: 420,
+        completion_tokens: 180,
+        total_tokens: 600,
+      },
+    },
+    created_at: "2025-01-15T10:03:00Z",
+    updated_at: "2025-01-15T10:03:00Z",
+  },
+
+  // 第三轮对话 - 用户继续提问
+  {
+    id: 9,
+    uid: "msg-user-003",
+    chat_id: "chat-001",
+    parent_id: "msg-assistant-003", // 基于第一个版本的回答
+    session_id: "session-003",
+    role: "user",
+    content: "佛教的八正道具体包括哪些内容?",
+    is_active: true,
+    created_at: "2025-01-15T10:04:00Z",
+    updated_at: "2025-01-15T10:04:00Z",
+  },
+
+  {
+    id: 10,
+    uid: "msg-assistant-005",
+    chat_id: "chat-001",
+    parent_id: "msg-user-003",
+    session_id: "session-003",
+    role: "assistant",
+    content:
+      "八正道(Noble Eightfold Path)是佛教修行的核心方法,包含八个相互关联的修行要素:\n\n**慧学(智慧)**:\n1. **正见**(Right View)- 正确的世界观和人生观\n2. **正思维**(Right Intention)- 正确的思考和发心\n\n**戒学(道德)**:\n3. **正语**(Right Speech)- 正确的言语\n4. **正业**(Right Action)- 正确的行为\n5. **正命**(Right Livelihood)- 正确的生活方式\n\n**定学(禅定)**:\n6. **正精进**(Right Effort)- 正确的努力\n7. **正念**(Right Mindfulness)- 正确的觉知\n8. **正定**(Right Concentration)- 正确的禅定\n\n这八个方面需要同时修习,相互支持,共同导向解脱。",
+    model_id: "gpt-4",
+    is_active: true,
+    metadata: {
+      generation_params: {
+        temperature: 0.7,
+        max_tokens: 2048,
+      },
+      token_usage: {
+        prompt_tokens: 600,
+        completion_tokens: 250,
+        total_tokens: 850,
+      },
+    },
+    created_at: "2025-01-15T10:04:20Z",
+    updated_at: "2025-01-15T10:04:20Z",
+  },
+
+  // 分支路径 - 用户编辑了第一个问题
+  {
+    id: 11,
+    uid: "msg-user-004",
+    chat_id: "chat-001",
+    parent_id: "msg-system-001", // 回到系统消息后
+    session_id: "session-v2-004",
+    role: "user",
+    content: "请解释一下佛教中的 nirvana(涅槃)概念。", // 用户编辑了第一个问题
+    is_active: false, // 用户当前查看的是主线路,这条是分支
+    created_at: "2025-01-15T10:05:00Z",
+    updated_at: "2025-01-15T10:05:00Z",
+  },
+
+  {
+    id: 12,
+    uid: "msg-assistant-006",
+    chat_id: "chat-001",
+    parent_id: "msg-user-004",
+    session_id: "session-004",
+    role: "assistant",
+    content:
+      'Nirvana(涅槃)是佛教修行的最终目标,具有深刻的内涵:\n\n**字面含义**:\n- 巴利语 "Nibbāna",梵语 "Nirvāṇa"\n- 原意为"熄灭"、"吹灭",指烦恼之火的熄灭\n\n**实质内容**:\n1. **烦恼的熄灭**:贪、嗔、痴等根本烦恼的彻底断除\n2. **轮回的终止**:不再受业力牵引而轮回生死\n3. **痛苦的止息**:一切苦的根源得到根除\n4. **究竟的解脱**:获得绝对的自由和安宁\n\n**两种涅槃**:\n- **有余涅槃**:烦恼已断,但身心仍存\n- **无余涅槃**:身心俱灭,彻底解脱\n\n涅槃不是虚无,而是超越一切对立和限制的究竟实相。',
+    model_id: "gpt-4",
+    is_active: false,
+    metadata: {
+      generation_params: {
+        temperature: 0.7,
+      },
+      token_usage: {
+        prompt_tokens: 150,
+        completion_tokens: 280,
+        total_tokens: 430,
+      },
+    },
+    created_at: "2025-01-15T10:05:20Z",
+    updated_at: "2025-01-15T10:05:20Z",
+  },
+];
+// Mock 数据生成工具
+const generateMockId = () => {
+  const ids = mockMessages.map((m) => m.id);
+  return Math.max(...ids) + 1;
+};
+const generateUuid = () =>
+  `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
+const mockDelay = (ms: number = 200) =>
+  new Promise((resolve) => setTimeout(resolve, ms));
+
+export const mockMessageApi = {
+  // 获取聊天消息列表
+  async getMessages(chatId: string): Promise<ApiResponse<MessageListResponse>> {
+    await mockDelay();
+
+    console.log(`[Mock API] Getting messages for chat: ${chatId}`);
+
+    return {
+      ok: true,
+      message: "Messages retrieved successfully",
+      data: {
+        rows: mockMessages,
+        total: mockMessages.length,
+      },
+    };
+  },
+
+  //
+  /**
+   * 创建新消息
+   * 检查request.messages里面每一项的parent_id。如果parent_id是mockMessages里面有的。那么保留这个parent_id.
+   * 如果这个parent_id是mockMessages里面没有的,但是是request.messages里面有的。修改为输入messages的修改过的uuid
+   * @param chatId
+   * @param request
+   * @returns
+   */
+  async createMessages(
+    chatId: string,
+    request: CreateMessageRequest
+  ): Promise<ApiResponse<MessageNode[]>> {
+    await mockDelay();
+
+    console.log(`[Mock API] Creating messages for chat: ${chatId}`, request);
+
+    const createdMessages: MessageNode[] = [];
+    const now = new Date().toISOString();
+    const uidMap = new Map<string, string>();
+
+    for (const msgRequest of request.messages) {
+      const uuid = generateUuid();
+      msgRequest.uid && uidMap.set(msgRequest.uid, uuid);
+      let parent_id = msgRequest.parent_id;
+      if (parent_id && uidMap.has(parent_id)) {
+        parent_id = uidMap.get(parent_id);
+      }
+      const newMessage: MessageNode = {
+        id: generateMockId(),
+        uid: uuid,
+        chat_id: chatId,
+        parent_id: parent_id,
+        session_id: generateUuid(), // 在实际应用中,这可能由前端或后端逻辑确定
+        role: msgRequest.role,
+        content: msgRequest.content,
+        model_id: msgRequest.model_id,
+        tool_calls: msgRequest.tool_calls,
+        tool_call_id: msgRequest.tool_call_id,
+        metadata: msgRequest.metadata,
+        is_active: true,
+        created_at: now,
+        updated_at: now,
+        save_status: "saved",
+      };
+
+      createdMessages.push(newMessage);
+      mockMessages.push(newMessage);
+    }
+
+    return {
+      ok: true,
+      message: "Messages created successfully",
+      data: createdMessages,
+    };
+  },
+
+  // 切换消息版本
+  async switchVersion(
+    chatId: string,
+    messageUids: string[]
+  ): Promise<ApiResponse<void>> {
+    await mockDelay();
+
+    console.log(
+      `[Mock API] Switching version for chat: ${chatId}`,
+      messageUids
+    );
+
+    // 首先将所有消息设为非活跃
+    mockMessages.forEach((msg) => {
+      if (msg.chat_id === chatId) {
+        msg.is_active = false;
+      }
+    });
+
+    // 然后激活指定的消息链
+    messageUids.forEach((uid) => {
+      const message = mockMessages.find((msg) => msg.uid === uid);
+      if (message) {
+        message.is_active = true;
+      }
+    });
+
+    return {
+      ok: true,
+      message: "Version switched successfully",
+      data: undefined,
+    };
+  },
+
+  // 点赞消息
+  async likeMessage(messageId: string): Promise<ApiResponse<void>> {
+    await mockDelay();
+
+    console.log(`[Mock API] Liking message: ${messageId}`);
+
+    const message = mockMessages.find((msg) => msg.uid === messageId);
+    if (!message) {
+      return {
+        ok: false,
+        message: "Message not found",
+        data: undefined,
+      };
+    }
+
+    // 模拟点赞逻辑 - 可以在 metadata 中记录
+    if (!message.metadata) {
+      message.metadata = {};
+    }
+    if (!message.metadata.custom_data) {
+      message.metadata.custom_data = {};
+    }
+    message.metadata.custom_data.liked = true;
+    message.metadata.custom_data.disliked = false;
+
+    return {
+      ok: true,
+      message: "Message liked successfully",
+      data: undefined,
+    };
+  },
+
+  // 踩消息
+  async dislikeMessage(messageId: string): Promise<ApiResponse<void>> {
+    await mockDelay();
+
+    console.log(`[Mock API] Disliking message: ${messageId}`);
+
+    const message = mockMessages.find((msg) => msg.uid === messageId);
+    if (!message) {
+      return {
+        ok: false,
+        message: "Message not found",
+        data: undefined,
+      };
+    }
+
+    // 模拟踩的逻辑
+    if (!message.metadata) {
+      message.metadata = {};
+    }
+    if (!message.metadata.custom_data) {
+      message.metadata.custom_data = {};
+    }
+    message.metadata.custom_data.liked = false;
+    message.metadata.custom_data.disliked = true;
+
+    return {
+      ok: true,
+      message: "Message disliked successfully",
+      data: undefined,
+    };
+  },
+
+  // 分享消息
+  async shareMessage(
+    messageId: string
+  ): Promise<ApiResponse<{ shareUrl: string }>> {
+    await mockDelay();
+
+    console.log(`[Mock API] Sharing message: ${messageId}`);
+
+    const message = mockMessages.find((msg) => msg.uid === messageId);
+    if (!message) {
+      return {
+        ok: false,
+        message: "Message not found",
+        data: { shareUrl: "" },
+      };
+    }
+
+    // 生成模拟的分享链接
+    const shareUrl = `https://chat.example.com/share/${messageId}`;
+
+    return {
+      ok: true,
+      message: "Message shared successfully",
+      data: { shareUrl },
+    };
+  },
+
+  // 删除消息
+  async deleteMessage(messageId: string): Promise<ApiResponse<void>> {
+    await mockDelay();
+
+    console.log(`[Mock API] Deleting message: ${messageId}`);
+
+    const messageIndex = mockMessages.findIndex((msg) => msg.uid === messageId);
+    if (messageIndex === -1) {
+      return {
+        ok: false,
+        message: "Message not found",
+        data: undefined,
+      };
+    }
+
+    // 软删除 - 设置 deleted_at
+    mockMessages[messageIndex].deleted_at = new Date().toISOString();
+
+    return {
+      ok: true,
+      message: "Message deleted successfully",
+      data: undefined,
+    };
+  },
+};
+
+// 工具函数:获取 mock 数据用于调试
+export const mockMessageUtils = {
+  // 获取所有 mock 消息
+  getAllMessages: () => [...mockMessages],
+
+  // 重置 mock 数据
+  resetMessages: () => {
+    mockMessages = mockMessages.slice(0, 8); // 保留初始数据
+  },
+
+  // 添加新的 mock 消息
+  addMockMessage: (message: Partial<MessageNode>) => {
+    const fullMessage: MessageNode = {
+      id: generateMockId(),
+      uid: generateUuid(),
+      chat_id: "chat-1",
+      session_id: generateUuid(),
+      role: "user",
+      content: "",
+      is_active: true,
+      created_at: new Date().toISOString(),
+      updated_at: new Date().toISOString(),
+      save_status: "saved",
+      ...message,
+    };
+    mockMessages.push(fullMessage);
+    return fullMessage;
+  },
+
+  // 设置特定消息的活跃状态
+  setMessageActive: (messageId: string, isActive: boolean) => {
+    const message = mockMessages.find((msg) => msg.uid === messageId);
+    if (message) {
+      message.is_active = isActive;
+    }
+  },
+
+  // 获取特定会话的消息
+  getSessionMessages: (chatId: string, sessionId: string) => {
+    return mockMessages.filter(
+      (msg) => msg.chat_id === chatId && msg.session_id === sessionId
+    );
+  },
+
+  // 模拟创建完整的对话会话(用户问题 + AI回答)
+  createMockSession: (
+    chatId: string,
+    userContent: string,
+    assistantContent: string,
+    parentId?: string
+  ) => {
+    const sessionId = generateUuid();
+    const now = new Date().toISOString();
+
+    const userMessage = mockMessageUtils.addMockMessage({
+      chat_id: chatId,
+      parent_id: parentId,
+      session_id: sessionId,
+      role: "user",
+      content: userContent,
+      created_at: now,
+      updated_at: now,
+    });
+
+    const assistantMessage = mockMessageUtils.addMockMessage({
+      chat_id: chatId,
+      parent_id: userMessage.uid,
+      session_id: sessionId,
+      role: "assistant",
+      content: assistantContent,
+      model_id: "gpt-4",
+      metadata: {
+        token_usage: {
+          prompt_tokens: Math.floor(Math.random() * 200) + 50,
+          completion_tokens: Math.floor(Math.random() * 150) + 30,
+          total_tokens: 0,
+        },
+        performance: {
+          response_time_ms: Math.floor(Math.random() * 2000) + 500,
+          first_token_time_ms: Math.floor(Math.random() * 300) + 100,
+        },
+      },
+      created_at: new Date(Date.now() + 1000).toISOString(),
+      updated_at: new Date(Date.now() + 1000).toISOString(),
+    });
+
+    // 计算总 token
+    if (assistantMessage.metadata?.token_usage) {
+      assistantMessage.metadata.token_usage.total_tokens =
+        assistantMessage.metadata.token_usage.prompt_tokens! +
+        assistantMessage.metadata.token_usage.completion_tokens!;
+    }
+
+    return { userMessage, assistantMessage };
+  },
+};
+
+/**
+ * 调用范例
+ * // 在 useChatData 或其他地方
+    import { mockMessageApi } from './services/mockMessageApi';
+
+    // 条件性使用 mock API
+    const messageApi = process.env.NODE_ENV === 'development' 
+    ? mockMessageApi 
+    : realMessageApi;
+ */

+ 416 - 0
dashboard-v4/dashboard/src/services/modelAdapters/mockOpenAI.ts

@@ -0,0 +1,416 @@
+// services/modelAdapters/mockOpenAI.ts
+import { BaseModelAdapter } from "./base";
+import {
+  OpenAIMessage,
+  SendOptions,
+  ParsedChunk,
+  ToolCall,
+} from "../../types/chat";
+
+export class MockOpenAIAdapter extends BaseModelAdapter {
+  name = "mock-gpt-4";
+  supportsFunctionCall = true;
+
+  protected mockDelay = (ms: number) =>
+    new Promise((resolve) => setTimeout(resolve, ms));
+
+  async sendMessage(
+    messages: OpenAIMessage[],
+    options: SendOptions
+  ): Promise<AsyncIterable<string>> {
+    const payload = this.buildRequestPayload(messages, options);
+
+    console.log("[Mock OpenAI] Sending message with payload:", payload);
+
+    return this.createMockStreamIterable(messages, options);
+  }
+
+  private async *createMockStreamIterable(
+    messages: OpenAIMessage[],
+    options: SendOptions
+  ): AsyncIterable<string> {
+    // 模拟初始延迟
+    await this.mockDelay(100);
+
+    const lastMessage = messages[messages.length - 1];
+    const userContent = lastMessage?.content || "";
+
+    // 检查是否需要函数调用
+    const needsFunctionCall = this.shouldCallFunction(userContent);
+
+    if (needsFunctionCall) {
+      // 模拟函数调用流程
+      yield* this.generateFunctionCallResponse(userContent);
+    } else {
+      // 模拟普通文本响应
+      yield* this.generateTextResponse(userContent);
+    }
+  }
+
+  private shouldCallFunction(content: string): boolean {
+    const functionTriggers = [
+      "天气",
+      "weather",
+      "温度",
+      "气温",
+      "搜索",
+      "search",
+      "查找",
+      "find",
+      "翻译",
+      "translate",
+      "巴利",
+      "pali",
+    ];
+
+    return functionTriggers.some((trigger) =>
+      content.toLowerCase().includes(trigger.toLowerCase())
+    );
+  }
+
+  private async *generateFunctionCallResponse(
+    userContent: string
+  ): AsyncIterable<string> {
+    // 第一步:生成函数调用意图的响应
+    const functionCallChunk = {
+      choices: [
+        {
+          delta: {
+            content: null,
+            function_call: this.determineFunctionCall(userContent),
+          },
+          finish_reason: "function_call",
+        },
+      ],
+    };
+
+    yield JSON.stringify(functionCallChunk);
+    await this.mockDelay(200);
+
+    // 模拟函数执行后的最终响应
+    const finalResponseText = this.generateFunctionBasedResponse(userContent);
+    yield* this.streamText(finalResponseText);
+  }
+
+  private determineFunctionCall(content: string): any {
+    if (content.includes("天气") || content.toLowerCase().includes("weather")) {
+      const city = this.extractCityFromContent(content);
+      return {
+        name: "getWeather",
+        arguments: JSON.stringify({ city }),
+      };
+    } else if (
+      content.includes("搜索") ||
+      content.toLowerCase().includes("search")
+    ) {
+      const term = this.extractSearchTermFromContent(content);
+      return {
+        name: "searchTerm",
+        arguments: JSON.stringify({ term }),
+      };
+    }
+
+    return {
+      name: "getWeather",
+      arguments: JSON.stringify({ city: "北京" }),
+    };
+  }
+
+  private extractCityFromContent(content: string): string {
+    const cities = [
+      "北京",
+      "上海",
+      "广州",
+      "深圳",
+      "杭州",
+      "成都",
+      "西安",
+      "武汉",
+      "New York",
+      "London",
+      "Tokyo",
+      "Paris",
+      "Sydney",
+    ];
+
+    for (const city of cities) {
+      if (content.includes(city)) {
+        return city;
+      }
+    }
+    return "北京"; // 默认城市
+  }
+
+  private extractSearchTermFromContent(content: string): string {
+    // 简单提取逻辑,实际应用中可能需要更复杂的NLP处理
+    const matches = content.match(/搜索["']?([^"']+)["']?/);
+    if (matches) {
+      return matches[1];
+    }
+
+    const searchMatches = content.match(/search\s+["']?([^"']+)["']?/i);
+    if (searchMatches) {
+      return searchMatches[1];
+    }
+
+    return "佛法"; // 默认搜索词
+  }
+
+  private generateFunctionBasedResponse(userContent: string): string {
+    if (
+      userContent.includes("天气") ||
+      userContent.toLowerCase().includes("weather")
+    ) {
+      const city = this.extractCityFromContent(userContent);
+      return `根据天气查询结果,${city}今天的天气情况如下:\n\n🌤️ **天气状况**:晴朗\n🌡️ **温度**:25°C\n💧 **湿度**:60%\n\n今天是个出行的好日子!记得适当补充水分。`;
+    } else if (
+      userContent.includes("搜索") ||
+      userContent.toLowerCase().includes("search")
+    ) {
+      const term = this.extractSearchTermFromContent(userContent);
+      return `我已经为您搜索了"${term}"相关的内容。以下是搜索结果:\n\n📚 **搜索结果**:\n• 找到了 15 个相关条目\n• 包含词汇解释、语法分析等\n• 提供了详细的语言学资料\n\n如需查看具体内容,请告诉我您感兴趣的具体方面。`;
+    }
+
+    return "我理解您的请求,已经为您处理完成。如有其他需要帮助的地方,请随时告诉我。";
+  }
+
+  private async *generateTextResponse(
+    userContent: string
+  ): AsyncIterable<string> {
+    const responses = [
+      "我很高兴为您提供帮助!",
+      "这是一个很有趣的问题。",
+      "让我为您详细解答一下。",
+      "根据我的理解,我认为...",
+      "这个问题涉及到多个方面,让我逐一为您分析。",
+      "希望这个回答对您有所帮助。",
+    ];
+
+    // 根据用户输入选择合适的响应
+    let responseText = "";
+
+    if (userContent.length > 50) {
+      responseText = `您提出了一个详细的问题。${
+        responses[Math.floor(Math.random() * responses.length)]
+      }
+
+针对您的问题,我可以从以下几个角度来回答:
+
+1. **主要观点**:${userContent.substring(0, 20)}...这个话题确实值得深入讨论。
+
+2. **相关背景**:这类问题通常需要综合考虑多个因素。
+
+3. **建议方案**:我建议您可以从实际情况出发,结合具体需求来选择最合适的方法。
+
+如果您需要更详细的解释或有其他相关问题,请随时告诉我!`;
+    } else {
+      responseText = `${responses[Math.floor(Math.random() * responses.length)]}
+
+关于"${userContent}"这个问题,我的理解是这样的:
+
+这确实是一个值得探讨的话题。根据我的知识,我认为最重要的是要考虑实际的应用场景和具体需求。
+
+希望我的回答对您有帮助。如果您还有任何疑问,欢迎继续提问!`;
+    }
+
+    yield* this.streamText(responseText);
+  }
+
+  private async *streamText(text: string): AsyncIterable<string> {
+    const words = text.split("");
+
+    for (let i = 0; i < words.length; i++) {
+      const chunk = {
+        choices: [
+          {
+            delta: {
+              content: words[i],
+            },
+            finish_reason: i === words.length - 1 ? "stop" : null,
+          },
+        ],
+      };
+
+      yield JSON.stringify(chunk);
+
+      // 模拟打字机效果,随机延迟
+      const delay = Math.random() * 50 + 10; // 10-60ms 随机延迟
+      await this.mockDelay(delay);
+    }
+
+    // 发送结束标记
+    const finishChunk = {
+      choices: [
+        {
+          delta: {},
+          finish_reason: "stop",
+        },
+      ],
+    };
+
+    yield JSON.stringify(finishChunk);
+  }
+
+  parseStreamChunk(chunk: string): ParsedChunk | null {
+    try {
+      const parsed = JSON.parse(chunk);
+      const delta = parsed.choices?.[0]?.delta;
+      const finishReason = parsed.choices?.[0]?.finish_reason;
+
+      return {
+        content: delta?.content,
+        function_call: delta?.function_call,
+        finish_reason: finishReason,
+      };
+    } catch (error) {
+      console.warn("[Mock OpenAI] Failed to parse chunk:", chunk, error);
+      return null;
+    }
+  }
+
+  async handleFunctionCall(functionCall: ToolCall): Promise<any> {
+    console.log("[Mock OpenAI] Handling function call:", functionCall);
+
+    // 模拟函数执行延迟
+    await this.mockDelay(300);
+
+    switch (functionCall.function) {
+      case "searchTerm":
+        return await this.mockSearchTerm(functionCall.arguments.term);
+      case "getWeather":
+        return await this.mockGetWeather(functionCall.arguments.city);
+      default:
+        console.warn(
+          `[Mock OpenAI] Unknown function: ${functionCall.function}`
+        );
+        throw new Error(`未知函数: ${functionCall.function}`);
+    }
+  }
+
+  private async mockSearchTerm(term: string): Promise<any> {
+    console.log(`[Mock OpenAI] Searching for term: ${term}`);
+
+    // 模拟搜索延迟
+    await this.mockDelay(500);
+
+    // 生成模拟的搜索结果
+    const mockResults = [
+      {
+        id: 1,
+        word: term,
+        definition: `${term}的定义:这是一个重要的概念`,
+        grammar: "名词",
+        example: `使用${term}的例句示例`,
+      },
+      {
+        id: 2,
+        word: `${term}相关词`,
+        definition: `与${term}相关的另一个概念`,
+        grammar: "动词",
+        example: `相关用法示例`,
+      },
+      {
+        id: 3,
+        word: `${term}变体`,
+        definition: `${term}的变体形式`,
+        grammar: "形容词",
+        example: `变体使用示例`,
+      },
+    ];
+
+    return {
+      ok: true,
+      data: {
+        rows: mockResults,
+        total: mockResults.length,
+      },
+    };
+  }
+
+  private async mockGetWeather(city: string): Promise<any> {
+    console.log(`[Mock OpenAI] Getting weather for city: ${city}`);
+
+    // 模拟天气API延迟
+    await this.mockDelay(300);
+
+    // 生成随机天气数据
+    const conditions = ["晴朗", "多云", "小雨", "阴天", "晴转多云"];
+    const temperatures = ["22°C", "25°C", "18°C", "28°C", "20°C"];
+    const humidities = ["45%", "60%", "75%", "55%", "65%"];
+
+    return {
+      city,
+      temperature:
+        temperatures[Math.floor(Math.random() * temperatures.length)],
+      condition: conditions[Math.floor(Math.random() * conditions.length)],
+      humidity: humidities[Math.floor(Math.random() * humidities.length)],
+      timestamp: new Date().toISOString(),
+      source: "Mock Weather API",
+    };
+  }
+
+  // 工具方法:构建请求负载(继承自BaseModelAdapter)
+  protected buildRequestPayload(
+    messages: OpenAIMessage[],
+    options: SendOptions
+  ) {
+    return {
+      model: this.name, // 添加required的model字段
+      messages: messages, // 直接使用原始messages,不需要重新映射
+      stream: true,
+      temperature: options.temperature || 0.7,
+      max_tokens: options.max_tokens || 2000,
+      top_p: options.top_p || 1,
+      functions: options.functions,
+      function_call: options.function_call || "auto", // 确保不为undefined
+    };
+  }
+}
+
+// 导出工厂函数,便于在不同环境中使用
+export function createMockOpenAIAdapter(): MockOpenAIAdapter {
+  return new MockOpenAIAdapter();
+}
+
+// 导出配置选项
+export interface MockOpenAIOptions {
+  responseDelay?: number; // 响应延迟
+  streamDelay?: number; // 流式输出延迟
+  enableFunctionCall?: boolean; // 是否启用函数调用
+  mockErrorRate?: number; // 模拟错误率 (0-1)
+}
+
+// 可配置的Mock适配器
+export class ConfigurableMockOpenAIAdapter extends MockOpenAIAdapter {
+  constructor(private options: MockOpenAIOptions = {}) {
+    super();
+    this.name = "configurable-mock-gpt-4";
+  }
+
+  private get responseDelay() {
+    return this.options.responseDelay || 100;
+  }
+
+  private get streamDelay() {
+    return this.options.streamDelay || 30;
+  }
+
+  protected mockDelay = (ms?: number) =>
+    new Promise((resolve) => setTimeout(resolve, ms || this.responseDelay));
+
+  async handleFunctionCall(functionCall: ToolCall): Promise<any> {
+    if (!this.options.enableFunctionCall) {
+      throw new Error("Function call disabled in mock adapter");
+    }
+
+    // 模拟随机错误
+    if (
+      this.options.mockErrorRate &&
+      Math.random() < this.options.mockErrorRate
+    ) {
+      throw new Error("Mock function call error");
+    }
+
+    return super.handleFunctionCall(functionCall);
+  }
+}

+ 646 - 0
dashboard-v4/documents/development/chat/frontend.s.md

@@ -0,0 +1,646 @@
+# AI Chat 前端重构设计文档
+
+## 1. 项目概述
+
+本项目旨在重构一个类似 Claude 的 AI 聊天系统前端,支持以下核心功能:
+
+- 多轮对话管理和消息树结构
+- Function Call 集成(支持多厂商模型)
+- 消息版本控制和切换
+- 实时流式输出和打字机效果
+- 消息编辑、刷新、点赞、分享等操作
+- 乐观更新和失败重试机制
+- Mock 数据支持便于开发调试
+- 与后端 API 的完整集成
+
+## 2. 核心设计理念
+
+### 2.1 数据流设计
+
+- **线性存储**:从数据库加载的消息为线性数组,无需构建树结构
+- **激活路径**:通过 `is_active` 字段标记用户当前查看的消息链
+- **Session 分组**:相同 `session_id` 的消息在前端显示为一个对话组
+- **乐观更新**:用户操作立即响应,异步同步到服务器
+
+### 2.2 版本管理
+
+- 同一 `parent_id` 的消息作为不同版本
+- 用户可通过版本切换器查看历史版本
+- 编辑和刷新都创建新版本,不修改原消息
+- 切换版本,根据 session_id 和版本序号,从现有版本列表中搜索当前版本链,
+
+### 2.3 错误处理
+
+- 手动重试机制,不自动重试
+- 临时消息状态标记,区分已保存/待保存/失败状态
+- 完整的错误恢复流程
+
+## 3. 架构设计
+
+```text
+├── types/
+│   └── chat.ts                # 所有类型定义
+├── hooks/                     # 数据管理层
+│   ├── useChatData.ts        # 主要数据管理
+│   ├── useActivePath.ts      # 激活路径计算
+│   ├── useSessionGroups.ts   # Session 分组管理
+│   └── mockChatData.ts           # Mock 测试数据
+├── services/                  # API调用层
+│   ├── chatApi.ts            # 聊天 API
+│   ├── messageApi.ts         # 消息 API
+│   └── modelAdapters/        # 模型适配器
+│       ├── base.ts           # 基础适配器
+│       ├── openai.ts         # OpenAI 适配器
+│       └── index.ts          # 适配器工厂
+├── components/               #
+│   └── chat                  # 显示层
+│       ├── ChatContainer.tsx     # 主容器组件
+│       ├── SessionGroup.tsx      # Session 消息组
+│       ├── UserMessage.tsx       # 用户消息
+│       ├── AssistantMessage.tsx  # AI 回答
+│       ├── MessageActions.tsx    # 消息操作
+│       ├── ChatInput.tsx         # 输入组件
+│       └── VersionSwitcher.tsx   # 版本切换
+└── mockChatData.ts           # Mock 测试数据
+```
+
+## 4. 类型定义
+
+### types/chat.ts
+
+```typescript
+// 工具调用相关类型
+export interface ToolCall {
+  id: string;
+  function: string;
+  arguments: Record<string, any>;
+}
+
+// 消息元数据
+export interface MessageMetadata {
+  generation_params?: {
+    temperature?: number;
+    max_tokens?: number;
+    top_p?: number;
+    frequency_penalty?: number;
+    presence_penalty?: number;
+  };
+  token_usage?: {
+    prompt_tokens?: number;
+    completion_tokens?: number;
+    total_tokens?: number;
+  };
+  performance?: {
+    response_time_ms?: number;
+    first_token_time_ms?: number;
+  };
+  tool_stats?: {
+    total_calls?: number;
+    successful_calls?: number;
+    execution_time_ms?: number;
+  };
+  custom_data?: Record<string, any>;
+}
+
+// 消息节点(对应数据库结构)
+export interface MessageNode {
+  id: number; // DB自增ID,用于版本排序
+  uid: string; // UUID
+  chat_id: string;
+  parent_id?: string;
+  session_id: string;
+  role: "system" | "user" | "assistant" | "tool";
+  content?: string;
+  model_id?: string;
+  tool_calls?: ToolCall[];
+  tool_call_id?: string;
+  metadata?: MessageMetadata;
+  is_active: boolean;
+  editor_id?: string;
+  created_at: string;
+  updated_at: string;
+  deleted_at?: string;
+
+  // 临时状态字段(前端使用)
+  save_status?: "saved" | "pending" | "failed";
+  temp_id?: string; // 临时ID,用于未保存消息
+}
+
+// 版本信息
+export interface VersionInfo {
+  version_index: number; // 版本索引(0,1,2...)
+  model_id?: string; // 该版本使用的模型
+  model_name?: string; // 模型显示名称
+  created_at: string; // 版本创建时间
+  message_count: number; // 该版本包含的消息数量
+  token_usage?: number; // 该版本的token使用量
+}
+
+// Session 信息
+export interface SessionInfo {
+  session_id: string;
+  messages: MessageNode[]; // 该session的所有消息(按激活路径过滤)
+  versions: VersionInfo[]; // 该session所有版本信息
+  current_version: number; // 当前显示的版本索引
+  user_message?: MessageNode; // 该session的用户消息(便于访问)
+  ai_messages: MessageNode[]; // 该session的AI消息列表
+}
+
+// 待保存消息组
+export interface PendingMessage {
+  temp_id: string;
+  session_id: string;
+  messages: MessageNode[]; // 待保存的消息组
+  retry_count: number;
+  error?: string;
+  created_at: string;
+}
+
+// 聊天状态
+export interface ChatState {
+  chat_id: string;
+  title: string;
+  raw_messages: MessageNode[]; // 从DB加载的原始线性数据
+  active_path: MessageNode[]; // 当前激活路径上的消息
+  session_groups: SessionInfo[]; // 按session分组的显示数据
+  pending_messages: PendingMessage[]; // 待保存的消息组
+  is_loading: boolean;
+  streaming_message?: string;
+  streaming_session_id?: string;
+  current_model: string;
+  error?: string;
+}
+
+// 聊天操作接口
+export interface ChatActions {
+  switchVersion: (sessionId: string, versionIndex: number) => Promise<void>;
+  editMessage: (
+    sessionId: string,
+    content: string,
+    role?: "user" | "assistant"
+  ) => Promise<void>;
+  retryMessage: (tempId: string) => Promise<void>;
+  refreshResponse: (sessionId: string, modelId?: string) => Promise<void>;
+  loadMessages: () => Promise<void>;
+  likeMessage: (messageId: string) => Promise<void>;
+  dislikeMessage: (messageId: string) => Promise<void>;
+  copyMessage: (messageId: string) => void;
+  shareMessage: (messageId: string) => Promise<string>;
+  deleteMessage: (messageId: string) => Promise<void>;
+}
+
+// API 请求类型
+export interface CreateMessageRequest {
+  messages: Array<{
+    parent_id?: string;
+    role: "user" | "assistant" | "tool";
+    content?: string;
+    model_id?: string;
+    tool_calls?: ToolCall[];
+    tool_call_id?: string;
+    metadata?: MessageMetadata;
+  }>;
+}
+
+export interface CreateChatRequest {
+  title: string;
+  user_id?: string;
+}
+
+// API 响应类型
+export interface ApiResponse<T> {
+  ok: boolean;
+  message: string;
+  data: T;
+}
+
+export interface ChatResponse {
+  id: string;
+  title: string;
+  user_id?: string;
+  created_at: string;
+  updated_at: string;
+}
+
+export interface MessageListResponse {
+  rows: MessageNode[];
+  total: number;
+}
+
+// 模型适配器相关类型
+export interface ModelAdapter {
+  name: string;
+  supportsFunctionCall: boolean;
+  sendMessage(
+    messages: OpenAIMessage[],
+    options: SendOptions
+  ): Promise<AsyncIterable<string>>;
+  parseStreamChunk(chunk: string): ParsedChunk | null;
+  handleFunctionCall(functionCall: ToolCall): Promise<any>;
+}
+
+export interface OpenAIMessage {
+  role: "system" | "user" | "assistant" | "function" | "tool";
+  content?: string;
+  name?: string;
+  tool_calls?: ToolCall[];
+  tool_call_id?: string;
+}
+
+export interface SendOptions {
+  temperature?: number;
+  max_tokens?: number;
+  top_p?: number;
+  functions?: Array<{
+    name: string;
+    description: string;
+    parameters: any;
+  }>;
+  function_call?: string | { name: string };
+}
+
+export interface ParsedChunk {
+  content?: string;
+  function_call?: {
+    name?: string;
+    arguments?: string;
+  };
+  finish_reason?: string;
+}
+
+// 组件 Props 类型
+export interface SessionGroupProps {
+  session: SessionInfo;
+  onVersionSwitch: (sessionId: string, versionIndex: number) => void;
+  onRefresh: (sessionId: string, modelId?: string) => void;
+  onEdit: (sessionId: string, content: string) => void;
+  onRetry?: (tempId: string) => void;
+  onLike?: (messageId: string) => void;
+  onDislike?: (messageId: string) => void;
+  onCopy?: (messageId: string) => void;
+  onShare?: (messageId: string) => Promise<string>;
+}
+
+export interface UserMessageProps {
+  message: MessageNode;
+  onEdit?: (content: string) => void;
+  onCopy?: () => void;
+}
+
+export interface AssistantMessageProps {
+  messages: MessageNode[];
+  onRefresh?: () => void;
+  onEdit?: (content: string) => void;
+  isPending?: boolean;
+  onLike?: (messageId: string) => void;
+  onDislike?: (messageId: string) => void;
+  onCopy?: (messageId: string) => void;
+  onShare?: (messageId: string) => Promise<string>;
+}
+
+export interface VersionSwitcherProps {
+  versions: VersionInfo[];
+  currentVersion: number;
+  onSwitch: (versionIndex: number) => void;
+}
+
+export interface ChatInputProps {
+  onSend: (content: string) => void;
+  disabled?: boolean;
+  placeholder?: string;
+}
+
+// Mock 数据支持
+export interface UseChatDataOptions {
+  useMockData?: boolean;
+  mockDelay?: number;
+}
+```
+
+## 5. 数据管理层
+
+### hooks/useActivePath.ts
+
+**核心功能**:从线性消息数组计算当前激活的消息链
+
+**算法说明**:
+
+1. 找到 system 消息作为根节点
+2. 沿着 `is_active=true` 的路径构建消息链
+3. 通过 `parent_id` 关系链接父子消息
+4. 返回完整的激活路径数组
+
+**接口**:
+
+```typescript
+export function useActivePath(rawMessages: MessageNode[]): MessageNode[];
+```
+
+### hooks/useSessionGroups.ts
+
+**核心功能**:将激活路径上的消息按 session_id 分组,计算版本信息
+
+**算法说明**:
+
+1. 按 `session_id` 对激活消息分组(排除 system 消息)
+2. 为每个 session 计算版本信息:
+   - 从 raw_messages 中找到相同 parent_id 的不同版本
+   - 按创建时间排序版本
+   - 计算当前激活版本的索引
+3. 构建 SessionInfo 结构,包含用户消息和 AI 消息
+
+**接口**:
+
+```typescript
+export function useSessionGroups(
+  activePath: MessageNode[],
+  rawMessages: MessageNode[]
+): SessionInfo[];
+```
+
+### hooks/useChatData.ts
+
+**核心功能**:主要的聊天数据管理 Hook,支持 Mock 模式
+
+**主要职责**:
+
+- 管理聊天状态(消息、pending、loading 等)
+- 处理用户操作(发送、编辑、重试等)
+- 支持 Mock 数据模式便于开发调试
+- 协调 API 调用和本地状态更新
+
+**接口**:
+
+```typescript
+export function useChatData(
+  chatId: string,
+  options?: UseChatDataOptions
+): { chatState: ChatState; actions: ChatActions };
+```
+
+**Mock 模式支持**:
+
+- `useMockData: true` 启用 Mock 模式
+- 模拟 API 延迟和流式响应
+- 提供完整的测试数据集
+
+## 6. API 调用层
+
+### services/chatApi.ts
+
+**功能**:聊天会话相关的 API 调用
+
+**主要接口**:
+
+- `createChat(request: CreateChatRequest): Promise<ApiResponse<ChatResponse>>`
+- `getChat(chatId: string): Promise<ApiResponse<ChatResponse>>`
+- `updateChat(chatId: string, updates: Partial<CreateChatRequest>): Promise<ApiResponse<ChatResponse>>`
+- `deleteChat(chatId: string): Promise<ApiResponse<void>>`
+
+### services/messageApi.ts
+
+**功能**:消息相关的 API 调用
+
+**主要接口**:
+
+- `getMessages(chatId: string): Promise<ApiResponse<MessageListResponse>>`
+- `createMessages(chatId: string, request: CreateMessageRequest): Promise<ApiResponse<MessageNode[]>>`
+- `switchVersion(chatId: string, messageUids: string[]): Promise<ApiResponse<void>>`
+- `likeMessage/dislikeMessage/shareMessage/deleteMessage`: 消息操作接口
+
+### services/modelAdapters/
+
+**设计模式**:适配器模式,支持多模型厂商
+
+#### base.ts
+
+**功能**:定义模型适配器的基础抽象类
+
+**核心方法**:
+
+- `sendMessage`: 发送消息,返回流式响应
+- `parseStreamChunk`: 解析流数据块
+- `handleFunctionCall`: 处理函数调用
+
+#### openai.ts
+
+**功能**:OpenAI 模型的具体实现
+
+**关键特性**:
+
+- 支持流式响应处理
+- Function Call 循环处理逻辑
+- 错误处理和重试机制
+
+**实现要点**:
+
+```typescript
+// sendMessage 返回 Promise<AsyncIterable<string>>
+async sendMessage(messages: OpenAIMessage[], options: SendOptions): Promise<AsyncIterable<string>> {
+  // 创建内部流生成器
+  return this.createStreamIterable(payload);
+}
+
+private async *createStreamIterable(payload: any): AsyncIterable<string> {
+  // 实际的流处理逻辑
+}
+```
+
+#### index.ts
+
+**功能**:适配器工厂,管理不同模型的适配器实例
+
+**主要接口**:
+
+- `getModelAdapter(modelId: string): ModelAdapter`
+- `registerAdapter(modelId: string, adapter: ModelAdapter): void`
+
+## 7. 显示层组件
+
+### components/ChatContainer.tsx
+
+**功能**:主容器组件,协调整个聊天界面
+
+**职责**:
+
+- 初始化和管理聊天数据
+- 处理用户操作事件
+- 渲染消息列表和输入区域
+- 错误状态显示
+
+**Props**:`{ chatId: string }`
+
+### components/SessionGroup.tsx
+
+**功能**:渲染单个对话 session,包含用户问题和 AI 完整回答
+
+**核心特性**:
+
+- 显示用户消息和 AI 消息组
+- 版本切换控制
+- 失败重试 UI
+- 消息状态指示(pending、failed)
+
+**Props**:`SessionGroupProps`
+
+### components/UserMessage.tsx
+
+**功能**:用户消息显示和编辑
+
+**特性**:
+
+- 内联编辑功能
+- 消息状态显示
+- 复制功能
+
+**Props**:`UserMessageProps`
+
+### components/AssistantMessage.tsx
+
+**功能**:AI 回答显示,支持多消息组合(Function Call 场景)
+
+**特性**:
+
+- 显示主要回答内容
+- Tool Call 结果展示
+- 消息操作按钮(点赞、复制、分享等)
+- Token 使用量显示
+
+**Props**:`AssistantMessageProps`
+
+### components/VersionSwitcher.tsx
+
+**功能**:版本切换控制器
+
+**特性**:
+
+- 前后版本导航
+- 版本信息提示(模型、时间、Token)
+- 当只有一个版本时自动隐藏
+
+**Props**:`VersionSwitcherProps`
+
+### components/ChatInput.tsx
+
+**功能**:用户输入组件
+
+**特性**:
+
+- 多行文本输入
+- Enter 发送,Shift+Enter 换行
+- 附件上传按钮(预留)
+- 发送状态控制
+
+**Props**:`ChatInputProps`
+
+## 8. Mock 数据支持
+
+### mockChatData.ts
+
+**功能**:提供完整的测试数据集,支持各种场景
+
+**数据场景**:
+
+- 完整的 Function Call 对话流程
+- 多版本消息示例
+- 分支对话(用户编辑问题)
+- 各种消息状态(pending、failed、streaming)
+
+**主要导出**:
+
+- `mockChatState`: 正常对话状态
+- `mockChatStateWithPending`: 带待发送消息
+- `mockChatStateWithError`: 错误状态
+- `mockChatStateStreaming`: 流式输出状态
+
+**工具函数**:
+
+- `getActivePath()`: 获取激活路径
+- `mockApiDelay()`: 模拟网络延迟
+- `mockStreamResponse()`: 模拟流式响应
+- `mockFunctionCallResponse`: 模拟函数调用结果
+
+## 9. 核心流程说明
+
+### 9.1 消息发送流程
+
+1. **用户输入** → `ChatInput.onSend` → `actions.editMessage('new', content)`
+2. **创建临时消息** → 乐观更新 UI,显示 pending 状态
+3. **调用 AI API** → 支持 Mock 模式和真实 API 模式
+4. **流式显示** → 实时更新 `streaming_message` 状态
+5. **Function Call 处理** → 循环处理工具调用,避免递归
+6. **保存到数据库** → 批量保存整个对话组
+7. **状态同步** → 更新本地状态,移除临时标记
+
+### 9.2 版本切换流程
+
+1. **用户点击版本按钮** → `VersionSwitcher.onSwitch`
+2. **计算目标版本** → 找到对应版本的消息组
+3. **调用 API 更新** → 更新数据库激活状态
+4. **重新加载数据** → 刷新本地状态
+5. **重新渲染** → 显示新版本内容
+
+### 9.3 Mock 模式开发
+
+**启用方式**:
+
+```typescript
+const { chatState, actions } = useChatData(chatId, {
+  useMockData: process.env.NODE_ENV === "development",
+  mockDelay: 500,
+});
+```
+
+**Mock 特性**:
+
+- 模拟 API 延迟
+- 模拟流式输出效果
+- 模拟 Function Call 处理
+- 模拟各种错误状态
+
+## 10. 性能优化
+
+### 10.1 渲染优化
+
+- React.memo 防止不必要重渲染
+- 消息列表虚拟化(长对话)
+- 懒加载历史消息版本
+
+### 10.2 状态管理优化
+
+- 最小化状态更新粒度
+- 使用 useCallback/useMemo 优化计算
+- 及时清理临时状态
+
+### 10.3 网络优化
+
+- 请求去重和缓存
+- 批量 API 调用
+- 流式响应优化
+
+## 11. 开发建议
+
+### 11.1 开发顺序
+
+1. **建立项目结构**:创建文件夹和基础文件
+2. **实现类型定义**:复制完整的 types/chat.ts
+3. **创建 Mock 数据**:使用提供的 mockChatData.ts
+4. **开发基础 Hook**:先实现 useActivePath 和 useSessionGroups
+5. **实现 UI 组件**:从简单组件开始(ChatInput、UserMessage)
+6. **集成 Mock 模式**:完善 useChatData 的 Mock 支持
+7. **真实 API 集成**:替换 Mock 调用为真实 API
+
+### 11.2 调试策略
+
+- 使用 Mock 模式进行 UI 开发
+- 逐步验证数据流转换
+- 测试各种边界情况(错误、重试、版本切换)
+
+### 11.3 测试覆盖
+
+- Hook 逻辑单元测试
+- 组件渲染测试
+- Mock 数据完整性验证
+- 用户交互流程测试
+
+这个设计提供了完整的架构蓝图,支持快速开发和调试,同时保持了良好的可维护性和扩展性。Mock 数据支持让开发者无需依赖后端 API 即可完成前端功能开发。