@shun-js/aibaiban-server 0.4.8 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shun-js/aibaiban-server",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "aibaiban.com server",
5
5
  "keywords": [
6
6
  "ai aibaiban"
@@ -44,5 +44,5 @@
44
44
  "access": "public",
45
45
  "registry": "https://registry.npmjs.org/"
46
46
  },
47
- "gitHead": "1521c2b05c17217a24c716881c05f35b66480fed"
47
+ "gitHead": "822aa69649e8feaff1bed2c0e02c2b527d432568"
48
48
  }
@@ -11,6 +11,9 @@ module.exports = (app) => {
11
11
  });
12
12
 
13
13
  // chat
14
+ app.post('/chat', (req, res) => {
15
+ service.chat(req, res);
16
+ });
14
17
  app.post('/chat-streaming', (req, res) => {
15
18
  service.chatWithStreaming(req, res);
16
19
  });
@@ -1,8 +1,8 @@
1
1
  // llm
2
- const { chatWithStreaming } = require('../util/llm-intent.js');
2
+ const { llmParseIntent } = require('../util/llm');
3
3
 
4
4
  // util
5
- const { chatFeishuMsg, errorFeishuMsg } = require('../util/feishu.js');
5
+ const { chatFeishuMsg, chatResFeishuMsg, errorFeishuMsg } = require('../util/feishu.js');
6
6
 
7
7
  /**
8
8
  * index
@@ -22,13 +22,13 @@ exports.index = async (req, res) => {
22
22
  };
23
23
 
24
24
  /**
25
- * chatWithStreaming
25
+ * chat
26
26
  * @param {*} req
27
27
  * @param {*} res
28
28
  * @returns
29
29
  */
30
- exports.chatWithStreaming = async (req, res) => {
31
- const methodName = 'chatWithStreaming';
30
+ exports.chat = async (req, res) => {
31
+ const methodName = 'chat';
32
32
 
33
33
  // check
34
34
  if (!req.body.userPrompt) {
@@ -44,28 +44,63 @@ exports.chatWithStreaming = async (req, res) => {
44
44
  chatFeishuMsg(req);
45
45
 
46
46
  // go
47
- const userPrompts = [{ text: userPrompt }];
48
- chatWithStreaming(userPrompts, {
49
- beginCallback: () => {
50
- req.logger.info(methodName, 'beginCallback');
51
- res.streamingStart();
52
- },
53
- firstContentCallback: () => {
54
- req.logger.info(methodName, 'firstContentCallback');
55
- // res.streaming('First chunk received!');
56
- },
57
- contentCallback: (content) => {
58
- req.logger.info(methodName, 'contentCallback', content);
59
- res.streaming(content);
60
- },
61
- endCallback: () => {
62
- req.logger.info(methodName, 'endCallback');
63
- res.streamingEnd();
64
- },
65
- errorCallback: (error) => {
66
- errorFeishuMsg(req, 'errorCallback');
67
- req.logger.info(methodName, 'errorCallback', error);
68
- res.streamingEnd();
69
- },
70
- });
47
+ try {
48
+ const llmParseIntentRes = await llmParseIntent(userPrompt);
49
+ req.logger.info(methodName, 'llmParseIntentRes', llmParseIntentRes);
50
+
51
+ // r
52
+ chatResFeishuMsg(req, JSON.stringify(llmParseIntentRes));
53
+ res.jsonSuccess('success', llmParseIntentRes);
54
+ } catch (error) {
55
+ const msg = 'chat error';
56
+ errorFeishuMsg(req, msg);
57
+ req.logger.error(methodName, msg, error);
58
+ res.jsonFail(msg);
59
+ }
60
+ };
61
+
62
+ /**
63
+ * chatWithStreaming
64
+ * @param {*} req
65
+ * @param {*} res
66
+ * @returns
67
+ */
68
+ exports.chatWithStreaming = async () => {
69
+ // const methodName = 'chatWithStreaming';
70
+ // // check
71
+ // if (!req.body.userPrompt) {
72
+ // const msg = 'need userPrompt';
73
+ // req.logger.error(methodName, msg);
74
+ // res.jsonFail(msg);
75
+ // return;
76
+ // }
77
+ // // const
78
+ // const userPrompt = decodeURIComponent(req.body.userPrompt);
79
+ // req.logger.info(methodName, 'userPrompt', userPrompt);
80
+ // chatFeishuMsg(req);
81
+ // // go
82
+ // const userPrompts = [{ text: userPrompt }];
83
+ // chatWithStreaming(userPrompts, {
84
+ // beginCallback: () => {
85
+ // req.logger.info(methodName, 'beginCallback');
86
+ // res.streamingStart();
87
+ // },
88
+ // firstContentCallback: () => {
89
+ // req.logger.info(methodName, 'firstContentCallback');
90
+ // // res.streaming('First chunk received!');
91
+ // },
92
+ // contentCallback: (content) => {
93
+ // req.logger.info(methodName, 'contentCallback', content);
94
+ // res.streaming(content);
95
+ // },
96
+ // endCallback: () => {
97
+ // req.logger.info(methodName, 'endCallback');
98
+ // res.streamingEnd();
99
+ // },
100
+ // errorCallback: (error) => {
101
+ // errorFeishuMsg(req, 'errorCallback');
102
+ // req.logger.info(methodName, 'errorCallback', error);
103
+ // res.streamingEnd();
104
+ // },
105
+ // });
71
106
  };
@@ -8,172 +8,38 @@ const { zodToJsonSchema } = require('zod-to-json-schema');
8
8
 
9
9
  // llm
10
10
  const { GeminiVertex } = require('viho-llm');
11
-
12
- // gemini
13
11
  const gemini = GeminiVertex({
14
12
  projectId: global.QZ_CONFIG.gemini.projectId,
15
13
  location: global.QZ_CONFIG.gemini.location,
16
14
  modelName: global.QZ_CONFIG.gemini.modelName,
17
15
  });
18
16
 
19
- // ==================== 缓存(懒加载) ====================
20
-
17
+ // const
21
18
  let cachedSystemPrompt = null;
22
- let cachedCompiledSchema = null;
23
-
24
- // ==================== JSON Schema 定义(避免联合类型)====================
25
-
26
19
  const jsonSchema = (() => {
27
- const DiagramNodeSchema = z.object({
28
- id: z.string().describe('节点唯一标识符,使用驼峰命名法,如 userService, mysqlDB'),
29
- label: z.string().describe('节点显示文本,支持 \\n 换行'),
30
- type: z
31
- .enum(['rectangle', 'ellipse', 'diamond', 'hexagon', 'cylinder', 'cloud'])
32
- .optional()
33
- .describe('形状类型,默认 rectangle'),
34
- color: z
35
- .enum(['blue', 'green', 'purple', 'orange', 'red', 'gray', 'yellow', 'pink', 'black'])
36
- .optional()
37
- .describe('节点颜色,默认 blue'),
38
- x: z.number().optional().describe('X 坐标,不指定则自动布局'),
39
- y: z.number().optional().describe('Y 坐标,不指定则自动布局'),
40
- width: z.number().optional().describe('宽度,不指定则使用默认值'),
41
- height: z.number().optional().describe('高度,不指定则使用默认值'),
42
- });
43
-
44
- const DiagramConnectionSchema = z.object({
45
- from: z.string().describe('起始节点 id'),
46
- to: z.string().describe('目标节点 id'),
47
- label: z.string().optional().describe('连接线上的文字标签'),
48
- type: z.enum(['arrow', 'line']).optional().describe('连接类型:arrow=箭头线,line=直线,默认 arrow'),
49
- style: z
50
- .enum(['solid', 'dashed', 'dotted'])
51
- .optional()
52
- .describe('线条样式:solid=实线,dashed=虚线,dotted=点线,默认 solid'),
53
- });
54
-
55
- const TextAnnotationSchema = z.object({
56
- id: z.string().describe('标注唯一标识符'),
57
- text: z.string().describe('标注文本内容'),
58
- x: z.number().optional().describe('X 坐标'),
59
- y: z.number().optional().describe('Y 坐标'),
60
- fontSize: z.number().optional().describe('字体大小'),
61
- color: z.string().optional().describe('文字颜色'),
62
- });
63
-
64
- const FrameSchema = z.object({
65
- id: z.string().describe('框架唯一标识符'),
66
- label: z.string().optional().describe('框架标题'),
67
- children: z.array(z.string()).describe('包含的节点 id 列表'),
68
- color: z.string().optional().describe('框架颜色'),
69
- x: z.number().optional().describe('X 坐标'),
70
- y: z.number().optional().describe('Y 坐标'),
71
- width: z.number().optional().describe('宽度'),
72
- height: z.number().optional().describe('高度'),
73
- });
74
-
75
- const ImageElementSchema = z.object({
76
- id: z.string().describe('图片唯一标识符'),
77
- imageUrl: z.string().url().describe('图片 URL'),
78
- alt: z.string().optional().describe('图片描述'),
79
- x: z.number().optional().describe('X 坐标'),
80
- y: z.number().optional().describe('Y 坐标'),
81
- width: z.number().optional().describe('宽度'),
82
- height: z.number().optional().describe('高度'),
83
- });
84
-
85
- // 注意:freedraws 使用简化格式,避免 tuple 导致的兼容性问题
86
- const FreedrawElementSchema = z.object({
87
- id: z.string().describe('手绘元素唯一标识符'),
88
- points: z.array(z.number()).describe('路径点坐标数组,格式:[x1,y1,x2,y2,...]'),
89
- color: z.string().optional().describe('线条颜色'),
90
- strokeWidth: z.number().optional().describe('线条宽度'),
91
- });
92
-
93
- // 统一的响应 Schema(包含图表和错误两种情况)
94
- // 通过 type 字段区分:'architecture'|'flowchart'|'sequence'|'custom' = 图表,'error' = 错误
95
- return z.object({
96
- // 通用字段:type 用于区分响应类型
97
- type: z
98
- .enum(['architecture', 'flowchart', 'sequence', 'custom', 'error'])
99
- .describe('响应类型:architecture/flowchart/sequence/custom=图表类型,error=错误响应'),
100
-
101
- // ========== 图表相关字段(当 type != error 时使用)==========
102
- title: z.string().optional().describe('图表标题'),
103
- nodes: z.array(DiagramNodeSchema).optional().describe('节点列表'),
104
- connections: z.array(DiagramConnectionSchema).optional().describe('连接关系列表'),
105
- annotations: z.array(TextAnnotationSchema).optional().describe('独立文本标注列表(可选)'),
106
- frames: z.array(FrameSchema).optional().describe('分组框架列表(可选)'),
107
- images: z.array(ImageElementSchema).optional().describe('图片元素列表(可选)'),
108
- freedraws: z.array(FreedrawElementSchema).optional().describe('手绘元素列表(可选)'),
109
-
110
- // ========== 错误相关字段(当 type = error 时使用)==========
111
- error: z
112
- .enum(['NON_DRAWING_REQUEST', 'INVALID_REQUEST', 'UNKNOWN_ERROR'])
113
- .optional()
114
- .describe('错误类型(仅当 type=error 时需要)'),
115
- message: z.string().optional().describe('错误提示消息(仅当 type=error 时需要)'),
116
- });
20
+ return zodToJsonSchema(
21
+ z.object({
22
+ intent: z.enum(['DRAW', 'REJECT']).describe('用户意图:DRAW(绘图)或 REJECT(非绘图)'),
23
+ reason: z.string().describe('判断理由(一句话说明)'),
24
+ }),
25
+ );
117
26
  })();
118
27
 
119
- // ==================== 懒加载函数 ====================
120
-
121
- /**
122
- * 获取系统提示词(懒加载+缓存)
123
- */
124
- async function getSystemPrompt() {
125
- if (!cachedSystemPrompt) {
126
- const promptPath = path.resolve(__dirname, './prompt.md');
127
- cachedSystemPrompt = await readFile(promptPath);
128
- }
129
- return cachedSystemPrompt;
130
- }
131
-
132
- /**
133
- * 获取编译后的 JSON Schema(懒加载+缓存)
134
- */
135
- function getCompiledSchema() {
136
- if (!cachedCompiledSchema) {
137
- const fullSchema = zodToJsonSchema(jsonSchema, {
138
- name: 'AIResponse',
139
- $refStrategy: 'none',
140
- });
141
-
142
- // 关键修复:Gemini 不支持顶层 $ref,直接使用 definitions 中的定义
143
- if (fullSchema.definitions && fullSchema.definitions.AIResponse) {
144
- cachedCompiledSchema = fullSchema.definitions.AIResponse;
145
- } else if (fullSchema.$ref) {
146
- // 如果顶层是 $ref,解析它
147
- const refName = fullSchema.$ref.split('/').pop();
148
- cachedCompiledSchema = fullSchema.definitions[refName];
149
- } else {
150
- cachedCompiledSchema = fullSchema;
151
- }
152
- }
153
- return cachedCompiledSchema;
154
- }
155
-
156
- // ==================== 主要功能 ====================
157
-
158
28
  /**
159
- * chatWithStreaming
29
+ * llmParseIntent
160
30
  * @param {*} userPrompts
161
- * @param {*} callbackOptions
162
31
  */
163
- exports.chatWithStreaming = async (userPrompts, callbackOptions) => {
164
- // 懒加载:第一次使用时加载并缓存
165
- const systemPrompt = await getSystemPrompt();
166
- const compiledSchema = getCompiledSchema();
167
- console.log('compiledSchema');
168
- console.log(JSON.stringify(compiledSchema));
32
+ exports.llmParseIntent = async (userPrompts) => {
33
+ // cache
34
+ if (!cachedSystemPrompt) cachedSystemPrompt = await readFile(path.resolve(__dirname, './prompt-intent.md'));
169
35
 
170
36
  // chat options
171
37
  const chatOptions = {
172
38
  contents: userPrompts,
173
- systemInstruction: systemPrompt,
39
+ systemInstruction: cachedSystemPrompt,
174
40
  config: {
175
41
  responseMimeType: 'application/json',
176
- responseJsonSchema: compiledSchema,
42
+ responseJsonSchema: jsonSchema,
177
43
  temperature: 0.2,
178
44
  topP: 0.9,
179
45
  topK: 40,
@@ -203,6 +69,6 @@ exports.chatWithStreaming = async (userPrompts, callbackOptions) => {
203
69
  },
204
70
  };
205
71
 
206
- // Send a chat message with streaming
207
- await gemini.chatWithStreaming(chatOptions, callbackOptions);
72
+ // go
73
+ return await gemini.chat(chatOptions);
208
74
  };
@@ -0,0 +1,208 @@
1
+ // path
2
+ const path = require('path');
3
+ const { readFile } = require('qiao-file');
4
+
5
+ // z
6
+ const { z } = require('zod');
7
+ const { zodToJsonSchema } = require('zod-to-json-schema');
8
+
9
+ // llm
10
+ const { GeminiVertex } = require('viho-llm');
11
+
12
+ // gemini
13
+ const gemini = GeminiVertex({
14
+ projectId: global.QZ_CONFIG.gemini.projectId,
15
+ location: global.QZ_CONFIG.gemini.location,
16
+ modelName: global.QZ_CONFIG.gemini.modelName,
17
+ });
18
+
19
+ // ==================== 缓存(懒加载) ====================
20
+
21
+ let cachedSystemPrompt = null;
22
+ let cachedCompiledSchema = null;
23
+
24
+ // ==================== JSON Schema 定义(避免联合类型)====================
25
+
26
+ const jsonSchema = (() => {
27
+ const DiagramNodeSchema = z.object({
28
+ id: z.string().describe('节点唯一标识符,使用驼峰命名法,如 userService, mysqlDB'),
29
+ label: z.string().describe('节点显示文本,支持 \\n 换行'),
30
+ type: z
31
+ .enum(['rectangle', 'ellipse', 'diamond', 'hexagon', 'cylinder', 'cloud'])
32
+ .optional()
33
+ .describe('形状类型,默认 rectangle'),
34
+ color: z
35
+ .enum(['blue', 'green', 'purple', 'orange', 'red', 'gray', 'yellow', 'pink', 'black'])
36
+ .optional()
37
+ .describe('节点颜色,默认 blue'),
38
+ x: z.number().optional().describe('X 坐标,不指定则自动布局'),
39
+ y: z.number().optional().describe('Y 坐标,不指定则自动布局'),
40
+ width: z.number().optional().describe('宽度,不指定则使用默认值'),
41
+ height: z.number().optional().describe('高度,不指定则使用默认值'),
42
+ });
43
+
44
+ const DiagramConnectionSchema = z.object({
45
+ from: z.string().describe('起始节点 id'),
46
+ to: z.string().describe('目标节点 id'),
47
+ label: z.string().optional().describe('连接线上的文字标签'),
48
+ type: z.enum(['arrow', 'line']).optional().describe('连接类型:arrow=箭头线,line=直线,默认 arrow'),
49
+ style: z
50
+ .enum(['solid', 'dashed', 'dotted'])
51
+ .optional()
52
+ .describe('线条样式:solid=实线,dashed=虚线,dotted=点线,默认 solid'),
53
+ });
54
+
55
+ const TextAnnotationSchema = z.object({
56
+ id: z.string().describe('标注唯一标识符'),
57
+ text: z.string().describe('标注文本内容'),
58
+ x: z.number().optional().describe('X 坐标'),
59
+ y: z.number().optional().describe('Y 坐标'),
60
+ fontSize: z.number().optional().describe('字体大小'),
61
+ color: z.string().optional().describe('文字颜色'),
62
+ });
63
+
64
+ const FrameSchema = z.object({
65
+ id: z.string().describe('框架唯一标识符'),
66
+ label: z.string().optional().describe('框架标题'),
67
+ children: z.array(z.string()).describe('包含的节点 id 列表'),
68
+ color: z.string().optional().describe('框架颜色'),
69
+ x: z.number().optional().describe('X 坐标'),
70
+ y: z.number().optional().describe('Y 坐标'),
71
+ width: z.number().optional().describe('宽度'),
72
+ height: z.number().optional().describe('高度'),
73
+ });
74
+
75
+ const ImageElementSchema = z.object({
76
+ id: z.string().describe('图片唯一标识符'),
77
+ imageUrl: z.string().url().describe('图片 URL'),
78
+ alt: z.string().optional().describe('图片描述'),
79
+ x: z.number().optional().describe('X 坐标'),
80
+ y: z.number().optional().describe('Y 坐标'),
81
+ width: z.number().optional().describe('宽度'),
82
+ height: z.number().optional().describe('高度'),
83
+ });
84
+
85
+ // 注意:freedraws 使用简化格式,避免 tuple 导致的兼容性问题
86
+ const FreedrawElementSchema = z.object({
87
+ id: z.string().describe('手绘元素唯一标识符'),
88
+ points: z.array(z.number()).describe('路径点坐标数组,格式:[x1,y1,x2,y2,...]'),
89
+ color: z.string().optional().describe('线条颜色'),
90
+ strokeWidth: z.number().optional().describe('线条宽度'),
91
+ });
92
+
93
+ // 统一的响应 Schema(包含图表和错误两种情况)
94
+ // 通过 type 字段区分:'architecture'|'flowchart'|'sequence'|'custom' = 图表,'error' = 错误
95
+ return z.object({
96
+ // 通用字段:type 用于区分响应类型
97
+ type: z
98
+ .enum(['architecture', 'flowchart', 'sequence', 'custom', 'error'])
99
+ .describe('响应类型:architecture/flowchart/sequence/custom=图表类型,error=错误响应'),
100
+
101
+ // ========== 图表相关字段(当 type != error 时使用)==========
102
+ title: z.string().optional().describe('图表标题'),
103
+ nodes: z.array(DiagramNodeSchema).optional().describe('节点列表'),
104
+ connections: z.array(DiagramConnectionSchema).optional().describe('连接关系列表'),
105
+ annotations: z.array(TextAnnotationSchema).optional().describe('独立文本标注列表(可选)'),
106
+ frames: z.array(FrameSchema).optional().describe('分组框架列表(可选)'),
107
+ images: z.array(ImageElementSchema).optional().describe('图片元素列表(可选)'),
108
+ freedraws: z.array(FreedrawElementSchema).optional().describe('手绘元素列表(可选)'),
109
+
110
+ // ========== 错误相关字段(当 type = error 时使用)==========
111
+ error: z
112
+ .enum(['NON_DRAWING_REQUEST', 'INVALID_REQUEST', 'UNKNOWN_ERROR'])
113
+ .optional()
114
+ .describe('错误类型(仅当 type=error 时需要)'),
115
+ message: z.string().optional().describe('错误提示消息(仅当 type=error 时需要)'),
116
+ });
117
+ })();
118
+
119
+ // ==================== 懒加载函数 ====================
120
+
121
+ /**
122
+ * 获取系统提示词(懒加载+缓存)
123
+ */
124
+ async function getSystemPrompt() {
125
+ if (!cachedSystemPrompt) {
126
+ const promptPath = path.resolve(__dirname, './prompt.md');
127
+ cachedSystemPrompt = await readFile(promptPath);
128
+ }
129
+ return cachedSystemPrompt;
130
+ }
131
+
132
+ /**
133
+ * 获取编译后的 JSON Schema(懒加载+缓存)
134
+ */
135
+ function getCompiledSchema() {
136
+ if (!cachedCompiledSchema) {
137
+ const fullSchema = zodToJsonSchema(jsonSchema, {
138
+ name: 'AIResponse',
139
+ $refStrategy: 'none',
140
+ });
141
+
142
+ // 关键修复:Gemini 不支持顶层 $ref,直接使用 definitions 中的定义
143
+ if (fullSchema.definitions && fullSchema.definitions.AIResponse) {
144
+ cachedCompiledSchema = fullSchema.definitions.AIResponse;
145
+ } else if (fullSchema.$ref) {
146
+ // 如果顶层是 $ref,解析它
147
+ const refName = fullSchema.$ref.split('/').pop();
148
+ cachedCompiledSchema = fullSchema.definitions[refName];
149
+ } else {
150
+ cachedCompiledSchema = fullSchema;
151
+ }
152
+ }
153
+ return cachedCompiledSchema;
154
+ }
155
+
156
+ // ==================== 主要功能 ====================
157
+
158
+ /**
159
+ * chatWithStreaming
160
+ * @param {*} userPrompts
161
+ * @param {*} callbackOptions
162
+ */
163
+ exports.chatWithStreaming = async (userPrompts, callbackOptions) => {
164
+ // 懒加载:第一次使用时加载并缓存
165
+ const systemPrompt = await getSystemPrompt();
166
+ const compiledSchema = getCompiledSchema();
167
+ console.log('compiledSchema');
168
+ console.log(JSON.stringify(compiledSchema));
169
+
170
+ // chat options
171
+ const chatOptions = {
172
+ contents: userPrompts,
173
+ systemInstruction: systemPrompt,
174
+ config: {
175
+ responseMimeType: 'application/json',
176
+ responseJsonSchema: compiledSchema,
177
+ temperature: 0.2,
178
+ topP: 0.9,
179
+ topK: 40,
180
+ maxOutputTokens: 8192,
181
+ thinkingConfig: {
182
+ thinkingBudget: 0,
183
+ includeThoughts: false,
184
+ },
185
+ safetySettings: [
186
+ {
187
+ category: 'HARM_CATEGORY_HATE_SPEECH',
188
+ threshold: 'BLOCK_ONLY_HIGH',
189
+ },
190
+ {
191
+ category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
192
+ threshold: 'BLOCK_ONLY_HIGH',
193
+ },
194
+ {
195
+ category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
196
+ threshold: 'BLOCK_ONLY_HIGH',
197
+ },
198
+ {
199
+ category: 'HARM_CATEGORY_HARASSMENT',
200
+ threshold: 'BLOCK_ONLY_HIGH',
201
+ },
202
+ ],
203
+ },
204
+ };
205
+
206
+ // Send a chat message with streaming
207
+ await gemini.chatWithStreaming(chatOptions, callbackOptions);
208
+ };
@@ -1,217 +1,107 @@
1
- # AI 白板助手 - 意图识别与设计说明(流式输出)
1
+ # 意图识别:判断用户是否要绘制图表
2
2
 
3
- 你是一个 AI 白板绘图助手,负责理解用户意图并提供设计说明。
3
+ 分析用户输入,判断是否为绘图请求。
4
4
 
5
- ## 核心职责
5
+ ## 输出要求
6
6
 
7
- 1. **判断用户意图**:识别用户是否在请求绘制图表
8
- 2. **流式输出设计说明**:如果是绘图请求,实时输出设计思路
9
- 3. **友好拒绝**:如果不是绘图请求,礼貌说明能力范围
10
-
11
- ## 响应格式要求
12
-
13
- 你的回复必须以特殊标记开始,用于后续处理:
14
-
15
- ### 非绘图请求格式
16
-
17
- ```
18
- [INTENT:REJECT]
19
-
20
- 抱歉,我是 AI 白板的绘图助手,我只能帮你绘制图表、流程图、架构图等。
21
-
22
- 我可以帮你:
23
- • 绘制流程图和架构图
24
- • 设计系统结构
25
- • 创建各种图形和连接
26
-
27
- 如果你想画图,可以试试这样说:
28
- 「画一个登录流程图」
29
- 「设计一个微服务架构」
30
- 「画三个矩形连接起来」
31
- ```
32
-
33
- ### 绘图请求格式
7
+ JSON 格式输出:
34
8
 
9
+ ```json
10
+ {
11
+ "intent": "DRAW" 或 "REJECT",
12
+ "reason": "判断理由(一句话说明)"
13
+ }
35
14
  ```
36
- [INTENT:DRAW]
37
-
38
- 好的,我来帮你设计[图表类型]!
39
-
40
- 设计思路:
41
- [流式输出你的设计思路,包括:]
42
- 1. 会使用哪些元素(矩形、圆形、箭头等)
43
- 2. 元素之间的关系和布局
44
- 3. 颜色和样式的选择理由
45
- 4. 整体的视觉效果说明
46
-
47
- 正在生成图表数据...
48
- ```
49
-
50
- ## 判断规则
51
-
52
- ### 属于绘图请求的情况:
53
-
54
- - 明确要求绘制图表:「画一个流程图」「设计架构图」
55
- - 描述需要可视化的内容:「用户登录的流程」「微服务架构」
56
- - 要求创建图形:「画三个矩形」「连接这些模块」
57
- - 使用图表相关词汇:流程图、架构图、组织架构、思维导图、ER图、时序图等
58
-
59
- ### 不属于绘图请求的情况:
60
-
61
- - 闲聊和问候:「你好」「今天天气怎么样」
62
- - 知识问答:「什么是微服务」「React 和 Vue 的区别」
63
- - 代码相关:「帮我写一段代码」「这段代码有什么问题」
64
- - 其他任务:「帮我写文章」「翻译这段话」「计算数学题」
65
-
66
- ## 设计说明输出指南
67
15
 
68
- 当确认是绘图请求后,你需要流式输出设计说明,包括:
16
+ ## 判断标准
69
17
 
70
- 1. **欢迎语**:简短确认用户需求
71
- 2. **设计分析**:
72
- - 识别出的关键元素和实体
73
- - 元素之间的关系(顺序、包含、依赖等)
74
- - 推荐的图表类型(流程图、架构图、关系图等)
18
+ ### DRAW(绘图请求)
75
19
 
76
- 3. **布局说明**:
77
- - 元素的排列方式(横向、纵向、层次化)
78
- - 分组和分层的逻辑
79
- - 视觉重点的突出方式
20
+ - 包含动作词:画、绘制、设计、生成、创建、制作
21
+ - 包含图表词:图、流程图、架构图、思维导图、关系图、结构图、示意图
22
+ - 描述可视化需求:「用户登录流程」「微服务架构」「数据库关系」
23
+ - 描述图形元素:「三个矩形」「圆形连接」
80
24
 
81
- 4. **样式选择**:
82
- - 颜色方案的选择理由
83
- - 形状类型的使用场景
84
- - 箭头和连接线的样式
25
+ ### REJECT(非绘图请求)
85
26
 
86
- 5. **结束语**:提示正在生成图表数据
27
+ - 问候寒暄:你好、早上好、谢谢
28
+ - 知识问答:什么是、如何、为什么、介绍、解释
29
+ - 代码任务:写代码、debug、实现功能
30
+ - 文字任务:翻译、写文章、总结
31
+ - 其他任务:计算、推荐、建议
87
32
 
88
33
  ## 示例
89
34
 
90
- ### 示例 1:非绘图请求
35
+ 输入:画一个用户登录流程图
36
+ 输出:
91
37
 
38
+ ```json
39
+ {
40
+ "intent": "DRAW",
41
+ "reason": "包含动作词'画'和图表词'流程图'"
42
+ }
92
43
  ```
93
- 用户:你好,今天天气怎么样?
94
44
 
95
- 助手:
96
- [INTENT:REJECT]
97
-
98
- 抱歉,我是 AI 白板的绘图助手,我只能帮你绘制图表、流程图、架构图等。
99
-
100
- 我可以帮你:
101
- • 绘制流程图和架构图
102
- • 设计系统结构
103
- • 创建各种图形和连接
104
-
105
- 如果你想画图,可以试试这样说:
106
- 「画一个登录流程图」
107
- 「设计一个微服务架构」
108
- 「画三个矩形连接起来」
109
- ```
110
-
111
- ### 示例 2:简单绘图请求
45
+ 输入:设计微服务架构
46
+ 输出:
112
47
 
48
+ ```json
49
+ {
50
+ "intent": "DRAW",
51
+ "reason": "包含动作词'设计'和可视化需求'架构'"
52
+ }
113
53
  ```
114
- 用户:画三个矩形连接起来
115
54
 
116
- 助手:
117
- [INTENT:DRAW]
55
+ 输入:三个矩形连接起来
56
+ 输出:
118
57
 
119
- 好的,我来帮你设计一个简单的连接图!
120
-
121
- 设计思路:
122
- 1. 创建三个矩形节点,使用蓝色作为主色调
123
- 2. 从左到右水平排列,保持均匀间距
124
- 3. 使用箭头连接,表示从左到右的流动关系
125
- 4. 矩形大小统一,简洁清晰
126
-
127
- 正在生成图表数据...
58
+ ```json
59
+ {
60
+ "intent": "DRAW",
61
+ "reason": "描述了具体的图形元素和连接关系"
62
+ }
128
63
  ```
129
64
 
130
- ### 示例 3:复杂绘图请求
65
+ 输入:简单介绍一下上海市
66
+ 输出:
131
67
 
68
+ ```json
69
+ {
70
+ "intent": "REJECT",
71
+ "reason": "这是知识问答请求,不是绘图需求"
72
+ }
132
73
  ```
133
- 用户:设计一个用户登录的流程图
134
-
135
- 助手:
136
- [INTENT:DRAW]
137
-
138
- 好的,我来帮你设计用户登录流程图!
139
-
140
- 设计思路:
141
74
 
142
- 1. **关键节点识别**:
143
- - 开始节点(椭圆形)
144
- - 输入用户名密码(矩形)
145
- - 验证凭证(菱形决策节点)
146
- - 成功/失败处理(矩形)
147
- - 结束节点(椭圆形)
75
+ 输入:什么是微服务架构?
76
+ 输出:
148
77
 
149
- 2. **流程关系**:
150
- - 线性主流程:开始 → 输入 → 验证 → 成功 → 结束
151
- - 异常分支:验证失败 → 错误提示 → 返回输入
152
-
153
- 3. **布局设计**:
154
- - 采用纵向布局,从上到下展示流程
155
- - 决策节点居中,两侧分出成功/失败分支
156
- - 失败分支使用红色标注,成功路径使用绿色
157
-
158
- 4. **视觉效果**:
159
- - 起止节点:绿色椭圆,突出流程边界
160
- - 处理节点:蓝色矩形,表示操作步骤
161
- - 决策节点:橙色菱形,强调判断点
162
- - 错误提示:红色矩形,警示异常情况
163
-
164
- 正在生成图表数据...
78
+ ```json
79
+ {
80
+ "intent": "REJECT",
81
+ "reason": "这是知识问答,用户在询问概念而非要求绘图"
82
+ }
165
83
  ```
166
84
 
167
- ### 示例 4:架构图请求
85
+ 输入:你好,今天天气怎么样
86
+ 输出:
168
87
 
88
+ ```json
89
+ {
90
+ "intent": "REJECT",
91
+ "reason": "这是闲聊问候,与绘图无关"
92
+ }
169
93
  ```
170
- 用户:画一个微服务架构图
171
-
172
- 助手:
173
- [INTENT:DRAW]
174
-
175
- 好的,我来帮你设计微服务架构图!
176
-
177
- 设计思路:
178
-
179
- 1. **架构层次**:
180
- - 前端层:客户端和 Web 应用
181
- - 网关层:API Gateway
182
- - 服务层:多个微服务(用户服务、订单服务、支付服务等)
183
- - 数据层:各服务独立数据库
184
-
185
- 2. **元素选择**:
186
- - 服务节点:使用圆角矩形(cylinder 样式)表示微服务
187
- - 数据库:使用圆柱体(cylinder)图标
188
- - 网关:使用六边形突出其特殊地位
189
- - 客户端:使用矩形
190
-
191
- 3. **连接关系**:
192
- - 客户端到网关:使用粗箭头表示请求流向
193
- - 网关到服务:使用细箭头表示分发
194
- - 服务到数据库:使用虚线表示数据访问
195
- - 服务间调用:使用双向箭头
196
-
197
- 4. **颜色方案**:
198
- - 前端层:蓝色系,表示用户界面
199
- - 网关层:紫色,突出中间层地位
200
- - 服务层:绿色系,表示业务逻辑
201
- - 数据层:灰色,表示存储
202
94
 
203
- 5. **布局安排**:
204
- - 分层垂直布局,从上到下展示请求流向
205
- - 同层服务水平排列,体现并行关系
206
- - 使用 Frame 框将同层元素分组
95
+ 输入:帮我写一段 Python 代码
96
+ 输出:
207
97
 
208
- 正在生成图表数据...
98
+ ```json
99
+ {
100
+ "intent": "REJECT",
101
+ "reason": "这是代码编写任务,不是绘图请求"
102
+ }
209
103
  ```
210
104
 
211
- ## 注意事项
105
+ ## 用户输入
212
106
 
213
- 1. **标记必须准确**:必须以 `[INTENT:REJECT]` 或 `[INTENT:DRAW]` 开头
214
- 2. **流式友好**:设计说明要分段输出,适合流式显示
215
- 3. **简洁清晰**:避免过度冗长,重点突出设计思路
216
- 4. **专业但友好**:使用专业术语但保持易懂
217
- 5. **匹配用户需求**:设计说明要紧密对应用户的具体请求
107
+ {USER_INPUT}
@@ -1,82 +0,0 @@
1
- // path
2
- const path = require('path');
3
- const { readFile } = require('qiao-file');
4
-
5
- // z
6
- // const { z } = require('zod');
7
- // const { zodToJsonSchema } = require('zod-to-json-schema');
8
-
9
- // llm
10
- const { GeminiVertex } = require('viho-llm');
11
-
12
- // gemini
13
- const gemini = GeminiVertex({
14
- projectId: global.QZ_CONFIG.gemini.projectId,
15
- location: global.QZ_CONFIG.gemini.location,
16
- modelName: global.QZ_CONFIG.gemini.modelName,
17
- });
18
-
19
- // ==================== 缓存(懒加载) ====================
20
-
21
- let cachedSystemPrompt = null;
22
-
23
- // ==================== 懒加载函数 ====================
24
-
25
- /**
26
- * 获取系统提示词(懒加载+缓存)
27
- */
28
- async function getSystemPrompt(promptPath) {
29
- if (!cachedSystemPrompt) cachedSystemPrompt = await readFile(path.resolve(__dirname, promptPath));
30
- return cachedSystemPrompt;
31
- }
32
-
33
- // ==================== 主要功能 ====================
34
-
35
- /**
36
- * chatWithStreaming
37
- * @param {*} userPrompts
38
- * @param {*} callbackOptions
39
- */
40
- exports.chatWithStreaming = async (userPrompts, callbackOptions) => {
41
- // 懒加载:第一次使用时加载并缓存
42
- const systemPrompt = await getSystemPrompt('./prompt-intent.md');
43
-
44
- // chat options
45
- const chatOptions = {
46
- contents: userPrompts,
47
- systemInstruction: systemPrompt,
48
- config: {
49
- // responseMimeType: 'application/json',
50
- // responseJsonSchema: compiledSchema,
51
- temperature: 0.2,
52
- topP: 0.9,
53
- topK: 40,
54
- maxOutputTokens: 8192,
55
- thinkingConfig: {
56
- thinkingBudget: 0,
57
- includeThoughts: false,
58
- },
59
- safetySettings: [
60
- {
61
- category: 'HARM_CATEGORY_HATE_SPEECH',
62
- threshold: 'BLOCK_ONLY_HIGH',
63
- },
64
- {
65
- category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
66
- threshold: 'BLOCK_ONLY_HIGH',
67
- },
68
- {
69
- category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
70
- threshold: 'BLOCK_ONLY_HIGH',
71
- },
72
- {
73
- category: 'HARM_CATEGORY_HARASSMENT',
74
- threshold: 'BLOCK_ONLY_HIGH',
75
- },
76
- ],
77
- },
78
- };
79
-
80
- // Send a chat message with streaming
81
- await gemini.chatWithStreaming(chatOptions, callbackOptions);
82
- };