@shun-js/aibaiban-server 0.7.6 → 0.7.7

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.7.6",
3
+ "version": "0.7.7",
4
4
  "description": "aibaiban.com server",
5
5
  "keywords": [
6
6
  "ai aibaiban"
@@ -16,8 +16,6 @@
16
16
  "url": "https://github.com/uikoo9/shun-js/issues"
17
17
  },
18
18
  "files": [
19
- "server",
20
- "assets",
21
19
  "app.js"
22
20
  ],
23
21
  "scripts": {
package/assets/bing.txt DELETED
@@ -1 +0,0 @@
1
- 4cb288d7aef5469c92616c0f5b5aeb89
package/assets/robots.txt DELETED
@@ -1,36 +0,0 @@
1
- # AI白板 - robots.txt
2
- # 更新时间: 2026-01-26
3
-
4
- # 允许所有搜索引擎爬虫
5
- User-agent: *
6
- Allow: /
7
-
8
- # 禁止爬取静态资源文件(可选,减少服务器负载)
9
- # Disallow: /assets/
10
- # Disallow: /*.js$
11
- # Disallow: /*.css$
12
-
13
- # 禁止爬取 API 接口
14
- Disallow: /api/
15
-
16
- # 爬虫访问速率限制(可选,防止爬虫过度占用带宽)
17
- # Crawl-delay: 1
18
-
19
- # 站点地图
20
- Sitemap: https://aibaiban.com/sitemap.xml
21
-
22
- # 特定爬虫配置
23
- # Google Bot
24
- User-agent: Googlebot
25
- Allow: /
26
- Crawl-delay: 0
27
-
28
- # Bing Bot
29
- User-agent: Bingbot
30
- Allow: /
31
- Crawl-delay: 0
32
-
33
- # 百度爬虫
34
- User-agent: Baiduspider
35
- Allow: /
36
- Crawl-delay: 1
@@ -1,16 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <urlset
3
- xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
4
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5
- xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
6
- http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
7
-
8
- <!-- 首页/落地页 - 最高优先级 -->
9
- <url>
10
- <loc>https://aibaiban.com/</loc>
11
- <lastmod>2026-02-03</lastmod>
12
- <changefreq>weekly</changefreq>
13
- <priority>1.0</priority>
14
- </url>
15
-
16
- </urlset>
@@ -1,12 +0,0 @@
1
- // service
2
- const service = require('../service/IndexService.js');
3
-
4
- /**
5
- * controller
6
- */
7
- module.exports = (app) => {
8
- // index
9
- app.get('/', (req, res) => {
10
- service.index(req, res);
11
- });
12
- };
@@ -1,17 +0,0 @@
1
- // service
2
- const service = require('../service/LLMService.js');
3
-
4
- /**
5
- * controller
6
- */
7
- module.exports = (app) => {
8
- // intent
9
- app.post('/intent', (req, res) => {
10
- service.intent(req, res);
11
- });
12
-
13
- // drawWithTools (使用结构化 JSON 输出)
14
- app.post('/drawWithTools', (req, res) => {
15
- service.drawWithTools(req, res);
16
- });
17
- };
@@ -1,18 +0,0 @@
1
- // service
2
- const service = require('../service/SEOService.js');
3
-
4
- /**
5
- * controller
6
- */
7
- module.exports = (app) => {
8
- // seo
9
- app.get('/robots.txt', (req, res) => {
10
- service.robots(req, res);
11
- });
12
- app.get('/sitemap.xml', (req, res) => {
13
- service.sitemap(req, res);
14
- });
15
- app.get('/4cb288d7aef5469c92616c0f5b5aeb89.txt', (req, res) => {
16
- service.bingIndexNow(req, res);
17
- });
18
- };
@@ -1,34 +0,0 @@
1
- /**
2
- * log options
3
- * @returns
4
- */
5
- module.exports = () => {
6
- // log options
7
- const logLevel = 'debug';
8
- const logPattern = 'yyyy-MM-dd-hh';
9
- const logPath = require('path').resolve(__dirname, '../logs/qiao-z.log');
10
-
11
- return {
12
- pm2: true,
13
- pm2InstanceVar: 'INSTANCE_ID',
14
- appenders: {
15
- stdout: {
16
- type: 'stdout',
17
- },
18
- datefile: {
19
- type: 'dateFile',
20
- pattern: logPattern,
21
- filename: logPath,
22
- keepFileExt: true,
23
- numBackups: 30,
24
- compress: true,
25
- },
26
- },
27
- categories: {
28
- default: {
29
- level: logLevel,
30
- appenders: ['stdout', 'datefile'],
31
- },
32
- },
33
- };
34
- };
@@ -1,16 +0,0 @@
1
- /**
2
- * index
3
- * @param {*} req
4
- * @param {*} res
5
- */
6
- exports.index = async (req, res) => {
7
- // const
8
- const pagePath = './views/index.html';
9
-
10
- // is static
11
- const isStatic = await res.staticRender(pagePath);
12
- if (isStatic) return;
13
-
14
- // render
15
- res.render(pagePath, {}, true);
16
- };
@@ -1,85 +0,0 @@
1
- // llm
2
- const { llmParseIntent } = require('../util/llm-intent.js');
3
-
4
- // structured json output
5
- const { generateFlowchartWithTools } = require('../util/llm-toolcall.js');
6
-
7
- // util
8
- const { chatFeishuMsg, chatResFeishuMsg, errorFeishuMsg } = require('../util/feishu.js');
9
-
10
- /**
11
- * intent
12
- * @param {*} req
13
- * @param {*} res
14
- * @returns
15
- */
16
- exports.intent = async (req, res) => {
17
- const methodName = 'intent';
18
-
19
- // check
20
- if (!req.body.userPrompt) {
21
- const msg = 'need userPrompt';
22
- req.logger.error(methodName, msg);
23
- res.jsonFail(msg);
24
- return;
25
- }
26
-
27
- // const
28
- const userPrompt = decodeURIComponent(req.body.userPrompt);
29
- req.logger.info(methodName, 'userPrompt', userPrompt);
30
- chatFeishuMsg(req);
31
-
32
- // go
33
- try {
34
- const llmParseIntentRes = await llmParseIntent(userPrompt);
35
- const llmParseIntentObj = JSON.parse(llmParseIntentRes);
36
- req.logger.info(methodName, 'llmParseIntentObj', llmParseIntentObj);
37
-
38
- // r
39
- chatResFeishuMsg(req, JSON.stringify(llmParseIntentObj));
40
- res.jsonSuccess('success', llmParseIntentObj);
41
- } catch (error) {
42
- const msg = 'parse intent error';
43
- errorFeishuMsg(req, msg);
44
- req.logger.error(methodName, msg, error);
45
- res.jsonFail(msg);
46
- }
47
- };
48
-
49
- /**
50
- * drawWithTools - 使用结构化 JSON 输出生成流程图
51
- * @param {*} req
52
- * @param {*} res
53
- * @returns
54
- */
55
- exports.drawWithTools = async (req, res) => {
56
- const methodName = 'drawWithTools';
57
-
58
- // check
59
- if (!req.body.userPrompt) {
60
- const msg = 'need userPrompt';
61
- req.logger.error(methodName, msg);
62
- res.jsonFail(msg);
63
- return;
64
- }
65
-
66
- // const
67
- const userPrompt = decodeURIComponent(req.body.userPrompt);
68
- req.logger.info(methodName, 'userPrompt', userPrompt);
69
- chatFeishuMsg(req);
70
-
71
- // go
72
- try {
73
- const diagram = await generateFlowchartWithTools(userPrompt);
74
- req.logger.info(methodName, 'diagram', diagram);
75
-
76
- // r
77
- chatResFeishuMsg(req, JSON.stringify(diagram));
78
- res.jsonSuccess('success', diagram);
79
- } catch (error) {
80
- const msg = 'draw with tools error';
81
- errorFeishuMsg(req, msg);
82
- req.logger.error(methodName, msg, error);
83
- res.jsonFail(msg);
84
- }
85
- };
@@ -1,38 +0,0 @@
1
- // path
2
- const path = require('path');
3
-
4
- // qiao
5
- const { readFile } = require('qiao-file');
6
-
7
- /**
8
- * robots
9
- * @param {*} req
10
- * @param {*} res
11
- */
12
- exports.robots = async (req, res) => {
13
- const txtPath = path.resolve(__dirname, '../../assets/robots.txt');
14
- const txt = await readFile(txtPath);
15
- res.send(txt);
16
- };
17
-
18
- /**
19
- * sitemap
20
- * @param {*} req
21
- * @param {*} res
22
- */
23
- exports.sitemap = async (req, res) => {
24
- const sitemapPath = path.resolve(__dirname, '../../assets/sitemap.xml');
25
- const sitemap = await readFile(sitemapPath);
26
- res.send(sitemap);
27
- };
28
-
29
- /**
30
- * bingIndexNow
31
- * @param {*} req
32
- * @param {*} res
33
- */
34
- exports.bingIndexNow = async (req, res) => {
35
- const txtPath = path.resolve(__dirname, '../../assets/bing.txt');
36
- const txt = await readFile(txtPath);
37
- res.send(txt);
38
- };
@@ -1,23 +0,0 @@
1
- // qiao
2
- const { userCheck } = require('qiao-z-service');
3
-
4
- /**
5
- * checkUserAuth
6
- * @param {*} req
7
- * @param {*} res
8
- * @returns
9
- */
10
- exports.checkUserAuth = async function (req, res) {
11
- const userCheckRes = await userCheck(global.QZ_CONFIG.user, {
12
- userid: req.headers.userid,
13
- usertoken: req.headers.usertoken,
14
- paths: JSON.stringify(global.QZ_CONFIG.paths),
15
- path: req.url.pathname,
16
- });
17
-
18
- // pass
19
- if (userCheckRes && userCheckRes.type === 'success') return true;
20
-
21
- // r
22
- res.json(userCheckRes);
23
- };
@@ -1,77 +0,0 @@
1
- // services
2
- const { feishuBot } = require('@shun-js/shun-service');
3
-
4
- /**
5
- * feishuMsg
6
- * @param {*} msg
7
- */
8
- exports.feishuMsg = (msg) => {
9
- if (global.QZ_CONFIG.env !== 'production') return;
10
-
11
- feishuBot({
12
- url: global.QZ_CONFIG.feishu.url,
13
- feishuUrl: global.QZ_CONFIG.feishu.feishuUrl,
14
- feishuMsg: msg,
15
- });
16
- };
17
-
18
- // is bot
19
- function isBot(req) {
20
- const ua = req.useragent;
21
- const hasOSName = ua && ua.os && ua.os.name;
22
- const isGoogleBot = ua && ua.browser && ua.browser.name === 'Googlebot';
23
- const isMetaBot = ua && ua.browser && ua.browser.name === 'meta-externalagent';
24
- const isSafariBot = ua && ua.engine && ua.engine.name === 'WebKit' && ua.engine.version === '605.1.15';
25
- return !hasOSName || isGoogleBot || isMetaBot || isSafariBot;
26
- }
27
-
28
- /**
29
- * errorFeishuMsg
30
- * @param {*} req
31
- * @param {*} msg
32
- * @returns
33
- */
34
- exports.errorFeishuMsg = (req, msg) => {
35
- // check
36
- if (isBot(req)) return;
37
-
38
- // msg
39
- const uaJson = JSON.stringify(req.useragent || {});
40
- exports.feishuMsg(`【通知】服务异常,${msg},请查看日志,ua:${uaJson}`);
41
- };
42
-
43
- /**
44
- * chatFeishuMsg
45
- * @param {*} req
46
- * @returns
47
- */
48
- exports.chatFeishuMsg = (req) => {
49
- // check
50
- if (isBot(req)) return;
51
-
52
- // msg
53
- const uaJson = JSON.stringify(req.useragent || {});
54
- const userid = req.headers.userid;
55
- const prompt = decodeURIComponent(req.body.userPrompt);
56
-
57
- const msg = `【通知】/chat被访问\nuserid:${userid}\nua:\n${uaJson}\nprompt:\n${prompt}`;
58
- exports.feishuMsg(msg);
59
- };
60
-
61
- /**
62
- * chatResFeishuMsg
63
- * @param {*} req
64
- * @returns
65
- */
66
- exports.chatResFeishuMsg = (req, chatRes) => {
67
- // check
68
- if (isBot(req)) return;
69
-
70
- // msg
71
- const uaJson = JSON.stringify(req.useragent || {});
72
- const userid = req.headers.userid;
73
- const prompt = decodeURIComponent(req.body.userPrompt);
74
-
75
- const msg = `【通知】/chat生成成功\nuserid:${userid}\nua:\n${uaJson}\nprompt:\n${prompt}\nres:${chatRes}`;
76
- exports.feishuMsg(msg);
77
- };
@@ -1,79 +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
- const gemini = GeminiVertex({
12
- projectId: global.QZ_CONFIG.gemini.projectId,
13
- location: global.QZ_CONFIG.gemini.location,
14
- modelName: global.QZ_CONFIG.gemini.modelName,
15
- });
16
-
17
- // const
18
- const chatConfig = {
19
- responseMimeType: 'application/json',
20
- temperature: 0.2, // 保持较低温度,确保输出稳定
21
- topP: 0.9,
22
- topK: 40,
23
- maxOutputTokens: 8192, // 足够大以支持复杂图表
24
- thinkingConfig: {
25
- thinkingBudget: 0,
26
- includeThoughts: false,
27
- },
28
- };
29
- const safetySettings = [
30
- {
31
- category: 'HARM_CATEGORY_HATE_SPEECH',
32
- threshold: 'BLOCK_ONLY_HIGH',
33
- },
34
- {
35
- category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
36
- threshold: 'BLOCK_ONLY_HIGH',
37
- },
38
- {
39
- category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
40
- threshold: 'BLOCK_ONLY_HIGH',
41
- },
42
- {
43
- category: 'HARM_CATEGORY_HARASSMENT',
44
- threshold: 'BLOCK_ONLY_HIGH',
45
- },
46
- ];
47
-
48
- /**
49
- * llmParseIntent
50
- * @param {*} userPrompts
51
- */
52
- let intentSystemPrompt = null;
53
- exports.llmParseIntent = async (userPrompts) => {
54
- // intent system prompt - 支持版本切换
55
- if (!intentSystemPrompt) {
56
- const promptFile = './prompt-intent.md';
57
- intentSystemPrompt = await readFile(path.resolve(__dirname, promptFile));
58
- }
59
-
60
- // chat config - 新增 confidence 字段支持
61
- chatConfig.responseJsonSchema = zodToJsonSchema(
62
- z.object({
63
- intent: z.enum(['DRAW', 'REJECT']).describe('用户意图:DRAW(绘图)或 REJECT(非绘图)'),
64
- reason: z.string().describe('判断理由(一句话说明)'),
65
- confidence: z.number().min(0).max(1).optional().describe('置信度评分(0.0-1.0),可选字段'),
66
- }),
67
- );
68
-
69
- // chat options
70
- const chatOptions = {
71
- contents: userPrompts,
72
- systemInstruction: intentSystemPrompt,
73
- config: chatConfig,
74
- safetySettings: safetySettings,
75
- };
76
-
77
- // go
78
- return await gemini.chat(chatOptions);
79
- };
@@ -1,314 +0,0 @@
1
- /**
2
- * Tool Calling 流程图生成器 - 主流程
3
- */
4
-
5
- // path
6
- const path = require('path');
7
- const { readFile } = require('qiao-file');
8
-
9
- // 直接使用 @google/genai,绕过 viho-llm
10
- const { GoogleGenAI } = require('@google/genai');
11
-
12
- // gemini client(直接创建)
13
- const geminiClient = new GoogleGenAI({
14
- vertexai: true,
15
- project: global.QZ_CONFIG.gemini.projectId,
16
- location: global.QZ_CONFIG.gemini.location,
17
- });
18
-
19
- const modelName = global.QZ_CONFIG.gemini.modelName;
20
-
21
- // const
22
- const chatConfig = {
23
- temperature: 0.3,
24
- topP: 0.9,
25
- topK: 40,
26
- maxOutputTokens: 8192,
27
- };
28
-
29
- /**
30
- * 使用结构化输出生成流程图
31
- * @param {string} userPrompt - 用户需求
32
- * @returns {Object} - SimplifiedDiagram
33
- */
34
- exports.generateFlowchartWithTools = async (userPrompt) => {
35
- console.log('[ToolCall] Starting flowchart generation');
36
- console.log('[ToolCall] User prompt:', userPrompt);
37
-
38
- // 加载系统提示词
39
- let systemPromptText = await readFile(path.resolve(__dirname, './prompt-toolcall.md'));
40
-
41
- // 确保是字符串格式
42
- if (Buffer.isBuffer(systemPromptText)) {
43
- systemPromptText = systemPromptText.toString('utf-8');
44
- }
45
-
46
- console.log('[ToolCall] System prompt loaded, length:', systemPromptText.length);
47
-
48
- try {
49
- // 调用 AI 生成 JSON
50
- const chatOptions = {
51
- model: modelName,
52
- contents: [
53
- {
54
- role: 'user',
55
- parts: [
56
- {
57
- text: `${systemPromptText}\n\n---\n\n用户需求:${userPrompt}\n\n请只返回 JSON 对象,不要有任何其他说明文字。`,
58
- },
59
- ],
60
- },
61
- ],
62
- config: chatConfig,
63
- };
64
-
65
- const response = await geminiClient.models.generateContent(chatOptions);
66
-
67
- // 打印原始响应(调试用)
68
- console.log('[ToolCall] Raw response:', JSON.stringify(response, null, 2));
69
-
70
- // 检查响应
71
- if (!response || !response.candidates || response.candidates.length === 0) {
72
- console.error('[ToolCall] Invalid response format');
73
- return { type: 'flowchart', nodes: [], connections: [], error: '响应格式错误' };
74
- }
75
-
76
- const candidate = response.candidates[0];
77
- const content = candidate.content;
78
-
79
- if (!content || !content.parts || content.parts.length === 0) {
80
- console.error('[ToolCall] No content parts in response');
81
- return { type: 'flowchart', nodes: [], connections: [], error: '无响应内容' };
82
- }
83
-
84
- // 提取文本
85
- let responseText = '';
86
- for (const part of content.parts) {
87
- if (part.text) {
88
- responseText += part.text;
89
- }
90
- }
91
-
92
- console.log('[ToolCall] Response text length:', responseText.length);
93
-
94
- // 尝试提取 JSON(可能被包裹在 markdown 代码块中)
95
- let jsonText = responseText.trim();
96
-
97
- // 移除可能的 markdown 代码块标记
98
- const jsonMatch = jsonText.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
99
- if (jsonMatch) {
100
- jsonText = jsonMatch[1].trim();
101
- }
102
-
103
- // 解析 JSON
104
- let diagramData;
105
- try {
106
- diagramData = JSON.parse(jsonText);
107
- } catch (parseError) {
108
- console.error('[ToolCall] JSON parse error:', parseError);
109
- console.error('[ToolCall] JSON text:', jsonText.substring(0, 500));
110
- return { type: 'flowchart', nodes: [], connections: [], error: 'JSON 解析失败' };
111
- }
112
-
113
- // 验证数据结构
114
- if (!diagramData.nodes || !Array.isArray(diagramData.nodes)) {
115
- console.error('[ToolCall] Invalid diagram data: missing nodes array');
116
- return { type: 'flowchart', nodes: [], connections: [], error: '缺少节点数据' };
117
- }
118
-
119
- if (!diagramData.connections || !Array.isArray(diagramData.connections)) {
120
- console.error('[ToolCall] Invalid diagram data: missing connections array');
121
- return { type: 'flowchart', nodes: [], connections: [], error: '缺少连接数据' };
122
- }
123
-
124
- console.log(
125
- `[ToolCall] Parsed diagram: ${diagramData.nodes.length} nodes, ${diagramData.connections.length} connections`,
126
- );
127
-
128
- // 应用布局计算
129
- const nodesWithLayout = calculateMainFlowLayout(diagramData.nodes);
130
-
131
- // 创建节点位置查找表
132
- const nodeMap = {};
133
- nodesWithLayout.forEach((node) => {
134
- nodeMap[node.id] = node;
135
- });
136
-
137
- // 添加连线路由(智能识别连线类型)
138
- const connectionsWithRouting = diagramData.connections.map((conn) => {
139
- const fromNode = nodeMap[conn.from];
140
- const toNode = nodeMap[conn.to];
141
-
142
- if (!fromNode || !toNode) {
143
- // 节点不存在,使用默认路由
144
- return {
145
- ...conn,
146
- routing: {
147
- exitSide: 'bottom',
148
- entrySide: 'top',
149
- },
150
- };
151
- }
152
-
153
- // 判断连线类型
154
- const isErrorBranch = toNode.color === 'red'; // 判断节点 -> 错误节点
155
- const isBackwardFlow = toNode.y < fromNode.y; // 回退连线(向上)
156
- const isHorizontal = Math.abs(fromNode.x - toNode.x) > 100; // 水平偏移较大
157
-
158
- let routing;
159
-
160
- if (isErrorBranch) {
161
- // 错误分支:从判断节点左侧或右侧出去
162
- routing = {
163
- exitSide: toNode.x < fromNode.x ? 'left' : 'right',
164
- exitRatio: 0.5,
165
- entrySide: toNode.x < fromNode.x ? 'right' : 'left',
166
- entryRatio: 0.5,
167
- };
168
- } else if (isBackwardFlow) {
169
- // 回退连线:从错误节点顶部出去,进入输入节点左侧或右侧
170
- routing = {
171
- exitSide: 'top',
172
- exitRatio: 0.5,
173
- entrySide: toNode.x < fromNode.x ? 'right' : 'left',
174
- entryRatio: 0.5,
175
- };
176
- } else if (isHorizontal) {
177
- // 水平连线
178
- routing = {
179
- exitSide: toNode.x < fromNode.x ? 'left' : 'right',
180
- exitRatio: 0.5,
181
- entrySide: toNode.x < fromNode.x ? 'right' : 'left',
182
- entryRatio: 0.5,
183
- };
184
- } else {
185
- // 默认:垂直连线(主流程)
186
- routing = {
187
- exitSide: 'bottom',
188
- entrySide: 'top',
189
- };
190
- }
191
-
192
- return {
193
- ...conn,
194
- routing,
195
- };
196
- });
197
-
198
- const finalDiagram = {
199
- type: 'flowchart',
200
- nodes: nodesWithLayout,
201
- connections: connectionsWithRouting,
202
- };
203
-
204
- console.log('[ToolCall] Diagram generation completed successfully');
205
- return finalDiagram;
206
- } catch (error) {
207
- console.error('[ToolCall] Error:', error);
208
- return { type: 'flowchart', nodes: [], connections: [], error: error.message };
209
- }
210
- };
211
-
212
- // 布局计算函数 - 智能识别分支结构
213
- function calculateMainFlowLayout(nodes) {
214
- const LAYOUT = {
215
- MAIN_X: 500,
216
- ERROR_LEFT_X: 200,
217
- ERROR_RIGHT_X: 800,
218
- START_Y: 80,
219
- STEP_SPACING: 150,
220
- NODE_WIDTH: 180,
221
- NODE_HEIGHT: 80,
222
- ELLIPSE_WIDTH: 120,
223
- ELLIPSE_HEIGHT: 60,
224
- DIAMOND_WIDTH: 140,
225
- DIAMOND_HEIGHT: 80,
226
- };
227
-
228
- // 第1步:识别错误节点(color: red)
229
- const errorNodeIds = new Set();
230
- nodes.forEach((node) => {
231
- if (node.color === 'red') {
232
- errorNodeIds.add(node.id);
233
- }
234
- });
235
-
236
- console.log('[Layout] Error nodes:', Array.from(errorNodeIds));
237
-
238
- // 第2步:为主流程节点分配 y 坐标
239
- let mainFlowIndex = 0;
240
- const nodePositions = {};
241
-
242
- nodes.forEach((node) => {
243
- if (!errorNodeIds.has(node.id)) {
244
- // 主流程节点
245
- const y = LAYOUT.START_Y + mainFlowIndex * LAYOUT.STEP_SPACING;
246
- nodePositions[node.id] = {
247
- x: LAYOUT.MAIN_X,
248
- y: y,
249
- isMainFlow: true,
250
- };
251
- mainFlowIndex++;
252
- }
253
- });
254
-
255
- // 第3步:为错误节点分配位置(与判断节点同高,但在左侧或右侧)
256
- let errorLeftIndex = 0;
257
- let errorRightIndex = 0;
258
-
259
- nodes.forEach((node, index) => {
260
- if (errorNodeIds.has(node.id)) {
261
- // 尝试找到前面最近的判断节点
262
- let judgeNodeY = LAYOUT.START_Y + index * LAYOUT.STEP_SPACING;
263
- for (let i = index - 1; i >= 0; i--) {
264
- const prevNode = nodes[i];
265
- if (prevNode.type === 'diamond' && nodePositions[prevNode.id]) {
266
- judgeNodeY = nodePositions[prevNode.id].y;
267
- break;
268
- }
269
- }
270
-
271
- // 交替放置在左侧和右侧
272
- const isLeft = errorLeftIndex <= errorRightIndex;
273
- const x = isLeft ? LAYOUT.ERROR_LEFT_X : LAYOUT.ERROR_RIGHT_X;
274
-
275
- if (isLeft) {
276
- errorLeftIndex++;
277
- } else {
278
- errorRightIndex++;
279
- }
280
-
281
- nodePositions[node.id] = {
282
- x: x,
283
- y: judgeNodeY,
284
- isMainFlow: false,
285
- };
286
- }
287
- });
288
-
289
- // 第4步:应用位置和尺寸
290
- return nodes.map((node) => {
291
- const pos = nodePositions[node.id];
292
-
293
- // 宽高
294
- let width, height;
295
- if (node.type === 'ellipse') {
296
- width = LAYOUT.ELLIPSE_WIDTH;
297
- height = LAYOUT.ELLIPSE_HEIGHT;
298
- } else if (node.type === 'diamond') {
299
- width = LAYOUT.DIAMOND_WIDTH;
300
- height = LAYOUT.DIAMOND_HEIGHT;
301
- } else {
302
- width = LAYOUT.NODE_WIDTH;
303
- height = LAYOUT.NODE_HEIGHT;
304
- }
305
-
306
- return {
307
- ...node,
308
- x: pos.x,
309
- y: pos.y,
310
- width,
311
- height,
312
- };
313
- });
314
- }
@@ -1,277 +0,0 @@
1
- # 意图识别:判断用户是否要绘制图表 v2.0
2
-
3
- 你是 AI 白板的意图识别助手,负责快速判断用户输入是否为绘图请求。
4
-
5
- ## 输出要求
6
-
7
- **必须**以 JSON 格式输出,不要有任何其他文字:
8
-
9
- ```json
10
- {
11
- "intent": "DRAW" 或 "REJECT",
12
- "reason": "判断理由(一句话说明)",
13
- "confidence": 0.0-1.0
14
- }
15
- ```
16
-
17
- ## 判断标准
18
-
19
- ### ✅ DRAW(绘图请求)- 高置信度场景
20
-
21
- **明确的绘图动词**(置信度 0.9+):
22
-
23
- - 画、绘制、画出、画个、帮我画
24
- - 设计、创建、生成、制作
25
- - 展示、可视化、呈现
26
- - 图示、示意
27
-
28
- **包含图表类型词**(置信度 0.85+):
29
-
30
- - 流程图、架构图、思维导图、时序图
31
- - 结构图、关系图、示意图、拓扑图
32
- - ER图、UML图、组织架构图
33
- - 状态图、活动图、序列图
34
-
35
- **描述视觉元素和布局**(置信度 0.8+):
36
-
37
- - "三个矩形连接起来"
38
- - "从左到右画出..."
39
- - "用箭头连接..."
40
- - "画个圆形,里面写..."
41
- - "上下结构的..."
42
-
43
- **描述系统/流程可视化需求**(置信度 0.75+):
44
-
45
- - "用户登录的流程"
46
- - "微服务的架构"
47
- - "数据库的关系"
48
- - "API 调用的过程"
49
- - "系统的整体设计"
50
-
51
- ### ❌ REJECT(非绘图请求)- 明确排除
52
-
53
- **知识问答**:
54
-
55
- - 什么是、如何、为什么、介绍、解释
56
- - "什么是微服务架构?"(询问定义)
57
- - "如何实现登录功能?"(询问方法)
58
-
59
- **代码编写**:
60
-
61
- - 写代码、写个函数、实现、debug
62
- - "帮我写一段 Python 代码"
63
- - "这段代码怎么优化?"
64
-
65
- **文本任务**:
66
-
67
- - 翻译、写文章、总结、改写
68
- - "帮我翻译这段话"
69
- - "写一篇关于...的文章"
70
-
71
- **问候寒暄**:
72
-
73
- - 你好、早上好、谢谢、再见
74
- - "你能做什么?"
75
- - "今天天气怎么样?"
76
-
77
- **计算分析**(不需要可视化):
78
-
79
- - 纯数值计算、数据统计
80
- - "帮我算一下..."
81
- - "这个公式的结果是..."
82
-
83
- ### 🤔 边界情况处理
84
-
85
- **情况 1:只提架构名称,未明确说画**
86
-
87
- ```
88
- 输入:"微服务架构"
89
- 判断:REJECT(置信度 0.3)
90
- 原因:可能是想了解概念,而非绘图。需要用户明确
91
- ```
92
-
93
- **情况 2:描述流程,但��"介绍"等词**
94
-
95
- ```
96
- 输入:"介绍一下用户登录流程"
97
- 判断:REJECT(置信度 0.4)
98
- 原因:倾向于文字说明,而非绘图
99
- ```
100
-
101
- **情况 3:同时包含问答和绘图意图**
102
-
103
- ```
104
- 输入:"什么是微服务架构?帮我画一个"
105
- 判断:DRAW(置信度 0.9)
106
- 原因:虽然包含问答,但明确提到"画一个"
107
- ```
108
-
109
- **情况 4:隐含的绘图需求**
110
-
111
- ```
112
- 输入:"展示一下电商系统的整体架构"
113
- 判断:DRAW(置信度 0.85)
114
- 原因:"展示"隐含可视化需求
115
- ```
116
-
117
- ## 判断流程
118
-
119
- ```
120
- 1. 检测明确的绘图动词?
121
- ├─ 是 → DRAW (置信度 0.9+)
122
- └─ 否 → 继续
123
-
124
- 2. 包含图表类型词?
125
- ├─ 是 → DRAW (置信度 0.85+)
126
- └─ 否 → 继续
127
-
128
- 3. 描述视觉元素或布局?
129
- ├─ 是 → DRAW (置信度 0.8+)
130
- └─ 否 → 继续
131
-
132
- 4. 描述可视化需求(流程、架构、关系)?
133
- ├─ 是 → DRAW (置信度 0.75+)
134
- └─ 否 → 继续
135
-
136
- 5. 属于明确排除类型?
137
- ├─ 是 → REJECT (置信度 0.9+)
138
- └─ 否 → REJECT (置信度 0.6) 【默认拒绝模糊输入】
139
- ```
140
-
141
- ## 示例标注
142
-
143
- ### ✅ DRAW 示例
144
-
145
- ```json
146
- // 输入:"画一个用户登录流程图"
147
- {
148
- "intent": "DRAW",
149
- "reason": "包含明确的绘图动词'画'和图表类型词'流程图'",
150
- "confidence": 0.95
151
- }
152
- ```
153
-
154
- ```json
155
- // 输入:"设计微服务架构"
156
- {
157
- "intent": "DRAW",
158
- "reason": "包含绘图动词'设计'和可视化需求'架构'",
159
- "confidence": 0.9
160
- }
161
- ```
162
-
163
- ```json
164
- // 输入:"三个矩形连接起来"
165
- {
166
- "intent": "DRAW",
167
- "reason": "描述了具体的图形元素和连接关系",
168
- "confidence": 0.85
169
- }
170
- ```
171
-
172
- ```json
173
- // 输入:"展示一下电商系统的整体结构"
174
- {
175
- "intent": "DRAW",
176
- "reason": "'展示'隐含可视化需求,'结构'暗示图表",
177
- "confidence": 0.8
178
- }
179
- ```
180
-
181
- ```json
182
- // 输入:"用流程图说明用户注册的过程"
183
- {
184
- "intent": "DRAW",
185
- "reason": "明确提到'流程图',需要可视化",
186
- "confidence": 0.9
187
- }
188
- ```
189
-
190
- ### ❌ REJECT 示例
191
-
192
- ```json
193
- // 输入:"简单介绍一下上海市"
194
- {
195
- "intent": "REJECT",
196
- "reason": "这是知识问答请求,不是绘图需求",
197
- "confidence": 0.95
198
- }
199
- ```
200
-
201
- ```json
202
- // 输入:"什么是微服务架构?"
203
- {
204
- "intent": "REJECT",
205
- "reason": "这是概念询问,用户在询问定义而非要求绘图",
206
- "confidence": 0.9
207
- }
208
- ```
209
-
210
- ```json
211
- // 输入:"你好,今天天气怎么样"
212
- {
213
- "intent": "REJECT",
214
- "reason": "这是闲聊问候,与绘图无关",
215
- "confidence": 0.95
216
- }
217
- ```
218
-
219
- ```json
220
- // 输入:"帮我写一段 Python 代码"
221
- {
222
- "intent": "REJECT",
223
- "reason": "这是代码编写任务,不是绘图请求",
224
- "confidence": 0.95
225
- }
226
- ```
227
-
228
- ```json
229
- // 输入:"介绍一下 RESTful API 的设计原则"
230
- {
231
- "intent": "REJECT",
232
- "reason": "虽然提到'设计',但是询问原则而非要求绘图",
233
- "confidence": 0.85
234
- }
235
- ```
236
-
237
- ```json
238
- // 输入:"微服务架构"
239
- {
240
- "intent": "REJECT",
241
- "reason": "只是提到名词,可能是想了解概念,意图不明确",
242
- "confidence": 0.7
243
- }
244
- ```
245
-
246
- ## 特殊处理规则
247
-
248
- ### 规则 1:当置信度 < 0.7 时
249
-
250
- 如果判断为 REJECT 但置信度低于 0.7,说明输入模糊,应该:
251
-
252
- - 倾向于 REJECT(避免误判)
253
- - 在 reason 中说明"意图不明确,建议用户明确说明"
254
-
255
- ### 规则 2:复合意图处理
256
-
257
- 当输入同时包含多��意图时:
258
-
259
- - 如果明确包含绘图动词 → DRAW
260
- - 否则按主要意图判断
261
-
262
- ### 规则 3:上下文无关
263
-
264
- 每次判断独立进行,不考虑历史对话
265
-
266
- ## 性能要求
267
-
268
- - 响应时间:< 500ms
269
- - 准确率目标:> 95%
270
- - 假阳性率(误判为 DRAW):< 3%
271
- - 假阴性率(误拒绝 DRAW):< 5%
272
-
273
- ---
274
-
275
- ## 用户输入
276
-
277
- {USER_INPUT}
@@ -1,273 +0,0 @@
1
- # AI白板绘图助手 - 结构化输出模式
2
-
3
- 你是 **AI白板** (aibaiban.com) 的专业绘图助手,生成高质量的流程图 JSON 数据。
4
-
5
- ## 输出格式要求
6
-
7
- **你必须只返回一个合法的 JSON 对象,不要有任何其他文字说明、Mermaid 代码或 Markdown 格式。**
8
-
9
- ### 输出 JSON 结构
10
-
11
- ```json
12
- {
13
- "type": "flowchart",
14
- "nodes": [
15
- { "id": "start", "label": "开始", "type": "ellipse", "color": "blue" },
16
- { "id": "step1", "label": "处理步骤", "type": "rectangle", "color": "blue" },
17
- { "id": "judge1", "label": "判断条件", "type": "diamond", "color": "orange" },
18
- { "id": "end", "label": "结束", "type": "ellipse", "color": "gray" }
19
- ],
20
- "connections": [
21
- { "from": "start", "to": "step1" },
22
- { "from": "step1", "to": "judge1" },
23
- { "from": "judge1", "to": "end", "label": "通过" }
24
- ]
25
- }
26
- ```
27
-
28
- ## 节点规范
29
-
30
- ### 节点类型
31
-
32
- - **ellipse**: 开始/结束节点(椭圆)
33
- - **rectangle**: 处理步骤(矩形)
34
- - **diamond**: 判断/分支(菱形)
35
-
36
- ### 节点颜色
37
-
38
- - **blue**: 蓝色(主流程步骤)
39
- - **green**: 绿色(成功/完成)
40
- - **orange**: 橙色(判断/警告)
41
- - **red**: 红色(错误/失败)
42
- - **gray**: 灰色(结束节点)
43
-
44
- ### 节点 ID 命名规则
45
-
46
- - 使用驼峰式英文:`start`, `step1`, `step2`, `judge1`, `error1`, `end`
47
- - ID 必须唯一
48
- - label 使用中文,简洁明了
49
-
50
- ## 连接规范
51
-
52
- - **from**: 起始节点 id
53
- - **to**: 目标节点 id
54
- - **label** (可选): 连线标签,如 "通过"、"失败"、"是"、"否"
55
-
56
- ## 设计原则
57
-
58
- 1. **简洁清晰**:不要过度复杂,保持流程易于理解
59
- 2. **完整流程**:包含主流程的所有关键步骤
60
- 3. **判断节点**:重要的决策点使用 diamond 类型
61
- 4. **错误处理**:对于判断节点,考虑"通过"和"失败"两个分支
62
-
63
- ## 示例
64
-
65
- **用户需求**:画出用户注册的完整流程
66
-
67
- **正确输出**(只返回 JSON):
68
-
69
- ```json
70
- {
71
- "type": "flowchart",
72
- "nodes": [
73
- { "id": "start", "label": "开始", "type": "ellipse", "color": "blue" },
74
- { "id": "step1", "label": "访问注册页面", "type": "rectangle", "color": "blue" },
75
- { "id": "step2", "label": "输入注册信息", "type": "rectangle", "color": "blue" },
76
- { "id": "judge1", "label": "验证输入", "type": "diamond", "color": "orange" },
77
- { "id": "error1", "label": "信息无效", "type": "rectangle", "color": "red" },
78
- { "id": "step3", "label": "发送验证码", "type": "rectangle", "color": "blue" },
79
- { "id": "step4", "label": "输入验证码", "type": "rectangle", "color": "blue" },
80
- { "id": "judge2", "label": "验证验证码", "type": "diamond", "color": "orange" },
81
- { "id": "error2", "label": "验证码错误", "type": "rectangle", "color": "red" },
82
- { "id": "step5", "label": "创建账户", "type": "rectangle", "color": "green" },
83
- { "id": "step6", "label": "注册成功", "type": "rectangle", "color": "green" },
84
- { "id": "end", "label": "结束", "type": "ellipse", "color": "gray" }
85
- ],
86
- "connections": [
87
- { "from": "start", "to": "step1" },
88
- { "from": "step1", "to": "step2" },
89
- { "from": "step2", "to": "judge1" },
90
- { "from": "judge1", "to": "step3", "label": "通过" },
91
- { "from": "judge1", "to": "error1", "label": "失败" },
92
- { "from": "error1", "to": "step2" },
93
- { "from": "step3", "to": "step4" },
94
- { "from": "step4", "to": "judge2" },
95
- { "from": "judge2", "to": "step5", "label": "通过" },
96
- { "from": "judge2", "to": "error2", "label": "失败" },
97
- { "from": "error2", "to": "step4" },
98
- { "from": "step5", "to": "step6" },
99
- { "from": "step6", "to": "end" }
100
- ]
101
- }
102
- ```
103
-
104
- ## 重要提醒
105
-
106
- - ❌ 不要返回说明文字
107
- - ❌ 不要使用 Mermaid 语法
108
- - ❌ 不要使用 Markdown 格式
109
- - ✅ 只返回纯 JSON 对象
110
- - ✅ 确保 JSON 格式正确(可以被 `JSON.parse()` 解析)
111
-
112
- ### 第 1 步:生成流程骨架
113
-
114
- **任务**:分析用户需求,识别主流程步骤,调用 `generate_flowchart_skeleton` 工具。
115
-
116
- **要点**:
117
-
118
- - 只生成**主流程**节点(正常路径)
119
- - 不要生成错误分支节点
120
- - 开始节点:`type: "ellipse"`, `color: "blue"`
121
- - 结束节点:`type: "ellipse"`, `color: "gray"`
122
- - 判断节点:`type: "diamond"`, `color: "orange"`
123
- - 处理步骤:`type: "rectangle"`, `color: "blue"`
124
-
125
- **示例**:
126
-
127
- ```
128
- 用户需求:画出用户注册的完整流程
129
-
130
- 你应该调用:
131
- generate_flowchart_skeleton({
132
- nodes: [
133
- { id: "start", label: "开始", type: "ellipse", color: "blue" },
134
- { id: "step1", label: "访问注册页面", type: "rectangle", color: "blue" },
135
- { id: "step2", label: "输入注册信息", type: "rectangle", color: "blue" },
136
- { id: "judge1", label: "验证输入", type: "diamond", color: "orange" },
137
- { id: "step3", label: "发送验证码", type: "rectangle", color: "blue" },
138
- { id: "step4", label: "输入验证码", type: "rectangle", color: "blue" },
139
- { id: "judge2", label: "验证验证码", type: "diamond", color: "orange" },
140
- { id: "step5", label: "创建账户", type: "rectangle", color: "green" },
141
- { id: "step6", label: "注册成功", type: "rectangle", color: "green" },
142
- { id: "end", label: "结束", type: "ellipse", color: "gray" }
143
- ],
144
- connections: [
145
- { from: "start", to: "step1" },
146
- { from: "step1", to: "step2" },
147
- { from: "step2", to: "judge1" },
148
- { from: "judge1", to: "step3", label: "通过" },
149
- { from: "step3", to: "step4" },
150
- { from: "step4", to: "judge2" },
151
- { from: "judge2", to: "step5", label: "通过" },
152
- { from: "step5", to: "step6" },
153
- { from: "step6", to: "end" }
154
- ]
155
- })
156
- ```
157
-
158
- ---
159
-
160
- ### 第 2 步:添加错误分支
161
-
162
- **任务**:收到主流程布局结果后,为每个判断节点添加错误分支。
163
-
164
- **要点**:
165
-
166
- - 错误节点颜色:`red`
167
- - 每个错误节点需要指定:
168
- - `relatedJudgeId`:对应的判断节点 id
169
- - `backToNodeId`:回退到哪个输入节点的 id
170
-
171
- **示例**:
172
-
173
- ```
174
- 调用:
175
- add_error_branches({
176
- errorNodes: [
177
- {
178
- id: "error1",
179
- label: "信息无效",
180
- relatedJudgeId: "judge1",
181
- backToNodeId: "step2"
182
- },
183
- {
184
- id: "error2",
185
- label: "验证码错误",
186
- relatedJudgeId: "judge2",
187
- backToNodeId: "step4"
188
- }
189
- ]
190
- })
191
- ```
192
-
193
- ---
194
-
195
- ### 第 3 步:完成图表
196
-
197
- **任务**:调用 `finalize_diagram` 完成图表生成。
198
-
199
- **示例**:
200
-
201
- ```
202
- finalize_diagram({ title: "用户注册流程图" })
203
- ```
204
-
205
- ---
206
-
207
- ## 重要规则
208
-
209
- ### 规则 1:工具调用顺序
210
-
211
- ```
212
- 第1步:generate_flowchart_skeleton(必须)
213
- ���2步:add_error_branches(可选,如果有判断节点)
214
- 第3步:finalize_diagram(必须)
215
- ```
216
-
217
- ### 规则 2:节点命名规范
218
-
219
- - 使用驼峰式英文:`start`, `step1`, `judge1`, `error1`, `end`
220
- - ID 必须唯一
221
- - label 使用中文,简洁明了
222
-
223
- ### 规则 3:判断节点处理
224
-
225
- - 判断节点必须有两个出口:
226
- - 主分支:继续主流程(label: "通过"/"是")
227
- - 错误分支:指向错误节点(label: "失败"/"否")
228
-
229
- ### 规则 4:错误分支回退
230
-
231
- - 每个错误节点都需要回退到之前的某个输入节点
232
- - 例如:
233
- - "信息无效" → 回退到 "输入注册信息"
234
- - "验证码错误" → 回退到 "输入验证码"
235
-
236
- ---
237
-
238
- ## 示例对话流程
239
-
240
- **User**: 画出用户注册的完整流程
241
-
242
- **Assistant**: [调用 generate_flowchart_skeleton,生成主流程骨架]
243
-
244
- **System**: 工具执行结果:已生成 10 个节点的主流程骨架,所有节点已按垂直布局排列(x: 500)
245
-
246
- **Assistant**: [调用 add_error_branches,添加错误分支]
247
-
248
- **System**: 工具执行结果:已添加 2 个错误分支,包含回退连线
249
-
250
- **Assistant**: [调用 finalize_diagram,完成图表]
251
-
252
- **System**: 工具执行结果:图表生成完成
253
-
254
- ---
255
-
256
- ## 注意事项
257
-
258
- 1. **坐标由系统自动计算**:你只需要提供节点的 id、label、type、color,系统会自动计算 x、y、width、height
259
- 2. **连线路由由系统自动添加**:你只需要指定 from、to、label,系统会自动添加 routing 配置
260
- 3. **专注于流程拆解**:你的核心任务是正确理解用户需求,拆解出合理的流程步骤
261
- 4. **使用工具循序渐进**:不要一次性生成所有内容,按照三步流程逐步完成
262
-
263
- ---
264
-
265
- ## 开始工作
266
-
267
- 现在,根据用户的需求,开始调用工具生成流程图吧!
268
-
269
- 记住:
270
-
271
- - 第 1 步:调用 `generate_flowchart_skeleton` 生成主流程
272
- - 第 2 步:调用 `add_error_branches` 添加错误分支(如有需要)
273
- - 第 3 步:调用 `finalize_diagram` 完成图表