Переглянути джерело

优化了项目启动时的 masks加载

Surface 7 місяців тому
батько
коміт
aa688ed1f2
10 змінених файлів з 260 додано та 990 видалено
  1. 124 0
      README.md
  2. 2 22
      app/masks/build.ts
  3. 0 11
      app/masks/en.ts
  4. 4 7
      app/masks/index.ts
  5. 0 420
      app/masks/tw.ts
  6. 126 122
      app/store/prompt.ts
  7. 1 1
      next.config.mjs
  8. 3 3
      package.json
  9. 0 400
      public/masks_bk.json
  10. 0 4
      public/prompts_bk.json

+ 124 - 0
README.md

@@ -0,0 +1,124 @@
+
+# 建科·小智客户端项目概述
+
+## 业务目标
+1. 提供统一的多AI平台接入能力
+2. 实现企业级智能问答解决方案
+3. 支持文档辅助分析和知识检索
+4. 优化招聘流程中的信息交互
+
+## 核心功能
+### 多AI平台集成
+- 支持BigModel和DeepSeek双引擎
+- 可扩展的AI服务提供商接入架构
+- 统一API代理层实现
+
+### 智能对话系统
+- 多轮对话上下文管理
+- 自动会话摘要生成
+- 智能问题推荐
+- 流式消息处理
+
+### 企业级特性
+- 招聘信息智能问答
+- 文档辅助分析
+- 多语言支持(20+语言)
+- 响应式设计适配多端
+
+## 技术架构
+### 前端架构
+```
+┌───────────────────────────────────────────────┐
+│                    UI层                      │
+│  ┌─────────────┐ ┌─────────────┐ ┌────────┐ │
+│  │  聊天组件   │ │  设置面板   │ │ 导出  │ │
+│  └─────────────┘ └─────────────┘ └────────┘ │
+├───────────────────────────────────────────────┤
+│                  状态管理层                   │
+│  ┌─────────────────────────────────────────┐ │
+│  │                Zustand Store            │ │
+│  │  ┌────────┐ ┌────────┐ ┌──────────────┐ │ │
+│  │  │ 聊天状态│ │ 配置状态│ │ 全局状态    │ │ │
+│  │  └────────┘ └────────┘ └──────────────┘ │ │
+│  └─────────────────────────────────────────┘ │
+├───────────────────────────────────────────────┤
+│                  服务层                      │
+│  ┌─────────────────────────────────────────┐ │
+│  │               API代理层                  │ │
+│  │  ┌───────┐ ┌───────┐ ┌────────────────┐ │ │
+│  │  │阿里云 │ │OpenAI │ │ 自定义大模型    │ │ │
+│  │  └───────┘ └───────┘ └────────────────┘ │ │
+│  └─────────────────────────────────────────┘ │
+└───────────────────────────────────────────────┘
+```
+
+### 技术栈
+- **前端框架**: Next.js 14 + React 18
+- **状态管理**: Zustand
+- **UI组件库**: Ant Design
+- **构建工具**: Webpack 5
+- **桌面端**: Tauri集成
+- **样式**: SCSS模块化
+
+## 数据实体
+### 核心数据模型
+```mermaid
+classDiagram
+    class ChatSession {
+        +String id
+        +String topic
+        +ChatMessage[] messages
+        +ChatStat stat
+        +Mask mask
+        +Date lastUpdate
+    }
+
+    class ChatMessage {
+        +String id
+        +String role
+        +String content
+        +Date date
+        +Boolean streaming
+        +String model
+    }
+
+    class Mask {
+        +String name
+        +String avatar
+        +ModelConfig modelConfig
+        +String[] context
+    }
+
+    ChatSession "1" *-- "many" ChatMessage
+    ChatSession "1" -- "1" Mask
+```
+
+## 业务流程
+### 典型用户交互流程
+```mermaid
+sequenceDiagram
+    participant 用户
+    participant UI组件
+    participant 状态管理
+    participant API服务
+
+    用户->>UI组件: 输入问题
+    UI组件->>状态管理: 更新输入状态
+    用户->>UI组件: 提交问题
+    UI组件->>API服务: 发送请求
+    API服务->>状态管理: 流式返回响应
+    状态管理->>UI组件: 实时更新消息
+    UI组件->>用户: 显示AI回复
+    状态管理->>状态管理: 自动生成会话摘要
+```
+
+## 部署架构
+- 支持三种构建模式:
+  1. **Standalone**: 独立部署模式
+  2. **Export**: 静态导出模式
+  3. **默认模式**: 完整服务端渲染
+
+## 扩展能力
+1. **插件系统**: 支持功能模块动态扩展
+2. **配置中心**: 运行时配置热更新
+3. **多模型路由**: 根据请求自动路由到最优AI服务

+ 2 - 22
app/masks/build.ts

@@ -1,26 +1,6 @@
-import fs from "fs";
-import path from "path";
 import { CN_MASKS } from "./cn";
-// import { TW_MASKS } from "./tw";
-// import { EN_MASKS } from "./en";
-
 import { type BuiltinMask } from "./typing";
 
-const BUILTIN_MASKS: Record<string, BuiltinMask[]> = {
+export const BUILTIN_MASKS: Record<string, BuiltinMask[]> = {
   cn: CN_MASKS,
-  // tw: TW_MASKS,
-  // en: EN_MASKS,
-};
-
-const dirname = path.dirname(__filename);
-
-// 不创建masks.json文件
-// fs.writeFile(
-//   dirname + "/../../public/masks.json",
-//   JSON.stringify(BUILTIN_MASKS, null, 4),
-//   function (error) {
-//     if (error) {
-//       console.error("[Build] failed to build masks", error);
-//     }
-//   },
-// );
+};

Різницю між файлами не показано, бо вона завелика
+ 0 - 11
app/masks/en.ts


+ 4 - 7
app/masks/index.ts

@@ -1,8 +1,5 @@
 import { Mask } from "../store/mask";
 import { CN_MASKS } from "./cn";
-import { TW_MASKS } from "./tw";
-import { EN_MASKS } from "./en";
-
 import { type BuiltinMask } from "./typing";
 export { type BuiltinMask } from "./typing";
 
@@ -22,7 +19,7 @@ export const BUILTIN_MASK_STORE = {
   },
 };
 
-export const BUILTIN_MASKS: BuiltinMask[] = [];
+export const BUILTIN_MASKS: BuiltinMask[] = CN_MASKS.map(mask => BUILTIN_MASK_STORE.add(mask));
 
 // if (typeof window != "undefined") {
 //   // run in browser skip in next server
@@ -30,11 +27,11 @@ export const BUILTIN_MASKS: BuiltinMask[] = [];
 //     .then((res) => res.json())
 //     .catch((error) => {
 //       console.error("[Fetch] failed to fetch masks", error);
-//       return { cn: [], tw: [], en: [] };
+//       return { cn: [] };
 //     })
 //     .then((masks) => {
-//       const { cn = [], tw = [], en = [] } = masks;
-//       return [...cn, ...tw, ...en].map((m) => {
+//       const { cn = [] } = masks;
+//       return cn.map((m) => {
 //         BUILTIN_MASKS.push(BUILTIN_MASK_STORE.add(m));
 //       });
 //     });

Різницю між файлами не показано, бо вона завелика
+ 0 - 420
app/masks/tw.ts


+ 126 - 122
app/store/prompt.ts

@@ -14,8 +14,8 @@ export interface Prompt {
 
 export const SearchService = {
   ready: false,
-  builtinEngine: new Fuse<Prompt>([], { keys: ["title"] }),
-  userEngine: new Fuse<Prompt>([], { keys: ["title"] }),
+  builtinEngine: new Fuse<Prompt>([], {keys: [ "title" ]}),
+  userEngine: new Fuse<Prompt>([], {keys: [ "title" ]}),
   count: {
     builtin: 0,
   },
@@ -23,7 +23,7 @@ export const SearchService = {
   builtinPrompts: [] as Prompt[],
 
   init(builtinPrompts: Prompt[], userPrompts: Prompt[]) {
-    if (this.ready) {
+    if ( this.ready ) {
       return;
     }
     this.allPrompts = userPrompts.concat(builtinPrompts);
@@ -49,136 +49,140 @@ export const SearchService = {
 };
 
 export const usePromptStore = createPersistStore(
-  {
-    counter: 0,
-    prompts: {} as Record<string, Prompt>,
-  },
-
-  (set, get) => ({
-    add(prompt: Prompt) {
-      const prompts = get().prompts;
-      prompt.id = nanoid();
-      prompt.isUser = true;
-      prompt.createdAt = Date.now();
-      prompts[prompt.id] = prompt;
-
-      set(() => ({
-        prompts: prompts,
-      }));
-
-      return prompt.id!;
+    {
+      counter: 0,
+      prompts: {} as Record<string, Prompt>,
     },
 
-    get(id: string) {
-      const targetPrompt = get().prompts[id];
+    (set, get) => ({
+      add(prompt: Prompt) {
+        const prompts = get().prompts;
+        prompt.id = nanoid();
+        prompt.isUser = true;
+        prompt.createdAt = Date.now();
+        prompts[prompt.id] = prompt;
 
-      if (!targetPrompt) {
-        return SearchService.builtinPrompts.find((v) => v.id === id);
-      }
+        set(() => ({
+          prompts: prompts,
+        }));
 
-      return targetPrompt;
-    },
+        return prompt.id!;
+      },
 
-    remove(id: string) {
-      const prompts = get().prompts;
-      delete prompts[id];
+      get(id: string) {
+        const targetPrompt = get().prompts[id];
 
-      Object.entries(prompts).some(([key, prompt]) => {
-        if (prompt.id === id) {
-          delete prompts[key];
-          return true;
+        if ( !targetPrompt ) {
+          return SearchService.builtinPrompts.find((v) => v.id === id);
         }
-        return false;
-      });
-
-      SearchService.remove(id);
 
-      set(() => ({
-        prompts,
-        counter: get().counter + 1,
-      }));
-    },
-
-    getUserPrompts() {
-      const userPrompts = Object.values(get().prompts ?? {});
-      userPrompts.sort((a, b) =>
-        b.id && a.id ? b.createdAt - a.createdAt : 0,
-      );
-      return userPrompts;
-    },
+        return targetPrompt;
+      },
 
-    updatePrompt(id: string, updater: (prompt: Prompt) => void) {
-      const prompt = get().prompts[id] ?? {
-        title: "",
-        content: "",
-        id,
-      };
-
-      SearchService.remove(id);
-      updater(prompt);
-      const prompts = get().prompts;
-      prompts[id] = prompt;
-      set(() => ({ prompts }));
-      SearchService.add(prompt);
-    },
-
-    search(text: string) {
-      if (text.length === 0) {
-        // return all rompts
-        return this.getUserPrompts().concat(SearchService.builtinPrompts);
-      }
-      return SearchService.search(text) as Prompt[];
-    },
-  }),
-  {
-    name: StoreKey.Prompt,
-    version: 3,
+      remove(id: string) {
+        const prompts = get().prompts;
+        delete prompts[id];
 
-    migrate(state, version) {
-      const newState = JSON.parse(JSON.stringify(state)) as {
-        prompts: Record<string, Prompt>;
-      };
-
-      if (version < 3) {
-        Object.values(newState.prompts).forEach((p) => (p.id = nanoid()));
-      }
-
-      return newState as any;
-    },
-
-    onRehydrateStorage(state) {
-      const PROMPT_URL = "./prompts.json";
-
-      type PromptList = Array<[string, string]>;
-
-      fetch(PROMPT_URL)
-        .then((res) => res.json())
-        .then((res) => {
-          let fetchPrompts = [res.en, res.tw, res.cn];
-          if (getLang() === "cn") {
-            fetchPrompts = fetchPrompts.reverse();
+        Object.entries(prompts).some(([ key, prompt ]) => {
+          if ( prompt.id === id ) {
+            delete prompts[key];
+            return true;
           }
-          const builtinPrompts = fetchPrompts.filter(item => item).map((promptList: PromptList) => {
-            return promptList.map(
-              ([title, content]) =>
-                ({
-                  id: nanoid(),
-                  title,
-                  content,
-                  createdAt: Date.now(),
-                }) as Prompt,
-            );
-          });
-
-          const userPrompts = usePromptStore.getState().getUserPrompts() ?? [];
-
-          const allPromptsForSearch = builtinPrompts
-            .reduce((pre, cur) => pre.concat(cur), [])
-            .filter((v) => !!v.title && !!v.content);
-          SearchService.count.builtin =
-            res.en.length + res.cn.length + res.tw.length;
-          SearchService.init(allPromptsForSearch, userPrompts);
+          return false;
         });
+
+        SearchService.remove(id);
+
+        set(() => ({
+          prompts,
+          counter: get().counter + 1,
+        }));
+      },
+
+      getUserPrompts() {
+        const userPrompts = Object.values(get().prompts ?? {});
+        userPrompts.sort((a, b) =>
+            b.id && a.id ? b.createdAt - a.createdAt : 0,
+        );
+        return userPrompts;
+      },
+
+      updatePrompt(id: string, updater: (prompt: Prompt) => void) {
+        const prompt = get().prompts[id] ?? {
+          title: "",
+          content: "",
+          id,
+        };
+
+        SearchService.remove(id);
+        updater(prompt);
+        const prompts = get().prompts;
+        prompts[id] = prompt;
+        set(() => ({prompts}));
+        SearchService.add(prompt);
+      },
+
+      search(text: string) {
+        if ( text.length === 0 ) {
+          // return all rompts
+          return this.getUserPrompts().concat(SearchService.builtinPrompts);
+        }
+        return SearchService.search(text) as Prompt[];
+      },
+    }),
+    {
+      name: StoreKey.Prompt,
+      version: 3,
+
+      migrate(state, version) {
+        const newState = JSON.parse(JSON.stringify(state)) as {
+          prompts: Record<string, Prompt>;
+        };
+
+        if ( version < 3 ) {
+          Object.values(newState.prompts).forEach((p) => (p.id = nanoid()));
+        }
+
+        return newState as any;
+      },
+
+      onRehydrateStorage(state) {
+        const PROMPT_URL = "./prompts.json";
+
+        type PromptList = Array<[ string, string ]>; // [标题, 内容]的元组数组
+        // tw: Array<[string, string]>; // 已停用繁体中文提示词
+        // en: Array<[string, string]>; // 已停用英文提示词
+
+        fetch(PROMPT_URL)
+            .then((res) => res.json())
+            .then((res) => {
+              let fetchPrompts = [
+                // res.en, res.tw,  // 注释掉英文和繁体中文提示词
+                res.cn ];
+          // if ( getLang() === "cn" ) {
+          //   fetchPrompts = fetchPrompts.reverse();
+          // }
+              const builtinPrompts = fetchPrompts.filter(item => item).map((promptList: PromptList) => {
+                return promptList.map(
+                    ([ title, content ]) =>
+                        ({
+                          id: nanoid(),
+                          title,
+                          content,
+                          createdAt: Date.now(),
+                        }) as Prompt,
+                );
+              });
+
+              const userPrompts = usePromptStore.getState().getUserPrompts() ?? [];
+
+              const allPromptsForSearch = builtinPrompts
+                  .reduce((pre, cur) => pre.concat(cur), [])
+                  .filter((v) => !!v.title && !!v.content);
+              SearchService.count.builtin =
+                  res.cn.length; // 仅计算中文提示词数量
+              SearchService.init(allPromptsForSearch, userPrompts);
+            });
+      },
     },
-  },
 );

+ 1 - 1
next.config.mjs

@@ -96,7 +96,7 @@ if (mode !== "export") {
       },
       {
         source: "/deepseek-api/:path*",
-        destination: "http://117.176.244.35:18078/:path*",
+        destination: "http://10.1.27.6:11817/:path*",
       },
     ];
 

+ 3 - 3
package.json

@@ -5,7 +5,7 @@
   "description": "AI问答-客户端",
   "license": "MIT",
   "scripts": {
-    "dev": "concurrently -r \"yarn run mask:watch\" \"next dev\"",
+    "dev": "next dev",
     "out": "yarn run build && yarn run export",
     "mask": "npx tsx app/masks/build.ts",
     "mask:watch": "npx watch \"yarn mask\" app/masks",
@@ -13,8 +13,8 @@
     "start": "next start",
     "lint": "next lint",
     "export": "cross-env BUILD_MODE=export BUILD_APP=1 next build",
-    "export:dev": "concurrently -r \"yarn mask:watch\"  \"cross-env BUILD_MODE=export BUILD_APP=1 next dev\"",
-    "app:dev": "concurrently -r \"yarn mask:watch\" \"yarn tauri dev\"",
+    "export:dev": "cross-env BUILD_MODE=export BUILD_APP=1 next dev",
+    "app:dev": "yarn tauri dev",
     "app:build": "yarn mask && yarn tauri build",
     "prompts": "node ./scripts/fetch-prompts.mjs",
     "prepare": "husky install",

Різницю між файлами не показано, бо вона завелика
+ 0 - 400
public/masks_bk.json


Різницю між файлами не показано, бо вона завелика
+ 0 - 4
public/prompts_bk.json


Деякі файли не було показано, через те що забагато файлів було змінено