@shun-js/aibaiban-server 1.4.1 → 1.4.3

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.
@@ -1,11 +1,11 @@
1
1
  // path
2
- const path = require('path');
2
+ const path = require("path");
3
3
 
4
4
  // mime
5
- const mime = require('mime-types');
5
+ const mime = require("mime-types");
6
6
 
7
7
  // qiao
8
- const { readFile } = require('qiao-file');
8
+ const { readFile } = require("qiao-file");
9
9
 
10
10
  /**
11
11
  * robots
@@ -13,7 +13,7 @@ const { readFile } = require('qiao-file');
13
13
  * @param {*} res
14
14
  */
15
15
  exports.robots = async (req, res) => {
16
- const filePath = path.resolve(__dirname, '../../assets/robots.txt');
16
+ const filePath = path.resolve(__dirname, "../../assets/robots.txt");
17
17
  const content = await readFile(filePath);
18
18
  res.send(content, mime.lookup(filePath));
19
19
  };
@@ -24,7 +24,7 @@ exports.robots = async (req, res) => {
24
24
  * @param {*} res
25
25
  */
26
26
  exports.sitemap = async (req, res) => {
27
- const filePath = path.resolve(__dirname, '../../assets/sitemap.xml');
27
+ const filePath = path.resolve(__dirname, "../../assets/sitemap.xml");
28
28
  const content = await readFile(filePath);
29
29
  res.send(content, mime.lookup(filePath));
30
30
  };
@@ -35,7 +35,7 @@ exports.sitemap = async (req, res) => {
35
35
  * @param {*} res
36
36
  */
37
37
  exports.bingIndexNow = async (req, res) => {
38
- const filePath = path.resolve(__dirname, '../../assets/bing.txt');
38
+ const filePath = path.resolve(__dirname, "../../assets/bing.txt");
39
39
  const content = await readFile(filePath);
40
40
  res.send(content, mime.lookup(filePath));
41
41
  };
@@ -46,7 +46,7 @@ exports.bingIndexNow = async (req, res) => {
46
46
  * @param {*} res
47
47
  */
48
48
  exports.manifestJson = async (req, res) => {
49
- const filePath = path.resolve(__dirname, '../../assets/manifest.json');
49
+ const filePath = path.resolve(__dirname, "../../assets/manifest.json");
50
50
  const content = await readFile(filePath);
51
51
  res.send(content, mime.lookup(filePath));
52
52
  };
@@ -57,7 +57,7 @@ exports.manifestJson = async (req, res) => {
57
57
  * @param {*} res
58
58
  */
59
59
  exports.swJs = async (req, res) => {
60
- const filePath = path.resolve(__dirname, '../../assets/sw.js');
60
+ const filePath = path.resolve(__dirname, "../../assets/sw.js");
61
61
  const content = await readFile(filePath);
62
62
  res.send(content, mime.lookup(filePath));
63
63
  };
@@ -68,7 +68,7 @@ exports.swJs = async (req, res) => {
68
68
  * @param {*} res
69
69
  */
70
70
  exports.icon192Png = async (req, res) => {
71
- const filePath = path.resolve(__dirname, '../../assets/icon-192.png');
71
+ const filePath = path.resolve(__dirname, "../../assets/icon-192.png");
72
72
  const content = await readFile(filePath, { encoding: null });
73
73
  res.send(content, mime.lookup(filePath));
74
74
  };
@@ -79,7 +79,7 @@ exports.icon192Png = async (req, res) => {
79
79
  * @param {*} res
80
80
  */
81
81
  exports.icon512Png = async (req, res) => {
82
- const filePath = path.resolve(__dirname, '../../assets/icon-512.png');
82
+ const filePath = path.resolve(__dirname, "../../assets/icon-512.png");
83
83
  const content = await readFile(filePath, { encoding: null });
84
84
  res.send(content, mime.lookup(filePath));
85
85
  };
@@ -1,5 +1,5 @@
1
1
  // qiao
2
- const { userCheck } = require('qiao-z-service');
2
+ const { userCheck } = require("qiao-z-service");
3
3
 
4
4
  /**
5
5
  * checkUserAuth
@@ -16,7 +16,7 @@ exports.checkUserAuth = async function (req, res) {
16
16
  });
17
17
 
18
18
  // pass
19
- if (userCheckRes && userCheckRes.type === 'success') return true;
19
+ if (userCheckRes && userCheckRes.type === "success") return true;
20
20
 
21
21
  // r
22
22
  res.json(userCheckRes);
@@ -1,12 +1,12 @@
1
1
  // services
2
- const { feishuBot } = require('@shun-js/shun-service');
2
+ const { feishuBot } = require("@shun-js/shun-service");
3
3
 
4
4
  /**
5
5
  * feishuMsg
6
6
  * @param {*} msg
7
7
  */
8
8
  exports.feishuMsg = (msg) => {
9
- if (global.QZ_CONFIG.env !== 'production') return;
9
+ if (global.QZ_CONFIG.env !== "production") return;
10
10
 
11
11
  feishuBot({
12
12
  url: global.QZ_CONFIG.feishu.url,
@@ -19,9 +19,14 @@ exports.feishuMsg = (msg) => {
19
19
  function isBot(req) {
20
20
  const ua = req.useragent;
21
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';
22
+ const isGoogleBot = ua && ua.browser && ua.browser.name === "Googlebot";
23
+ const isMetaBot =
24
+ ua && ua.browser && ua.browser.name === "meta-externalagent";
25
+ const isSafariBot =
26
+ ua &&
27
+ ua.engine &&
28
+ ua.engine.name === "WebKit" &&
29
+ ua.engine.version === "605.1.15";
25
30
  return !hasOSName || isGoogleBot || isMetaBot || isSafariBot;
26
31
  }
27
32
 
@@ -145,167 +145,211 @@ flowchart TD
145
145
  */
146
146
  CANVAS_TOOLS: [
147
147
  {
148
- type: 'function',
148
+ type: "function",
149
149
  function: {
150
- name: 'draw_shape',
151
- description: '在白板上绘制基础形状(矩形、椭圆、菱形),可带文字标签。需要被箭头连接时必须指定 id。',
150
+ name: "draw_shape",
151
+ description:
152
+ "在白板上绘制基础形状(矩形、椭圆、菱形),可带文字标签。需要被箭头连接时必须指定 id。",
152
153
  parameters: {
153
- type: 'object',
154
+ type: "object",
154
155
  properties: {
155
- id: { type: 'string', description: '元素ID,箭头绑定时需要引用' },
156
- shape: { type: 'string', enum: ['rectangle', 'ellipse', 'diamond'] },
157
- x: { type: 'number', description: '左上角X坐标' },
158
- y: { type: 'number', description: '左上角Y坐标' },
159
- width: { type: 'number', description: '宽度,默认100' },
160
- height: { type: 'number', description: '高度,默认100' },
161
- label: { type: 'string', description: '形状内的文字标签' },
162
- strokeColor: { type: 'string', description: '边框颜色,默认#1e1e1e' },
163
- backgroundColor: { type: 'string', description: '填充颜色,默认transparent' },
164
- fillStyle: { type: 'string', enum: ['hachure', 'cross-hatch', 'solid', 'zigzag'] },
165
- strokeWidth: { type: 'number', enum: [1, 2, 4], description: '1=细,2=中,4=粗' },
166
- strokeStyle: { type: 'string', enum: ['solid', 'dashed', 'dotted'] },
167
- roughness: { type: 'number', enum: [0, 1, 2], description: '0=精确,1=手绘,2=夸张' },
168
- roundness: { type: 'boolean', description: '是否圆角,默认true' },
169
- opacity: { type: 'number', description: '不透明度0-100,默认100' },
156
+ id: { type: "string", description: "元素ID,箭头绑定时需要引用" },
157
+ shape: {
158
+ type: "string",
159
+ enum: ["rectangle", "ellipse", "diamond"],
160
+ },
161
+ x: { type: "number", description: "左上角X坐标" },
162
+ y: { type: "number", description: "左上角Y坐标" },
163
+ width: { type: "number", description: "宽度,默认100" },
164
+ height: { type: "number", description: "高度,默认100" },
165
+ label: { type: "string", description: "形状内的文字标签" },
166
+ strokeColor: {
167
+ type: "string",
168
+ description: "边框颜色,默认#1e1e1e",
169
+ },
170
+ backgroundColor: {
171
+ type: "string",
172
+ description: "填充颜色,默认transparent",
173
+ },
174
+ fillStyle: {
175
+ type: "string",
176
+ enum: ["hachure", "cross-hatch", "solid", "zigzag"],
177
+ },
178
+ strokeWidth: {
179
+ type: "number",
180
+ enum: [1, 2, 4],
181
+ description: "1=细,2=中,4=粗",
182
+ },
183
+ strokeStyle: {
184
+ type: "string",
185
+ enum: ["solid", "dashed", "dotted"],
186
+ },
187
+ roughness: {
188
+ type: "number",
189
+ enum: [0, 1, 2],
190
+ description: "0=精确,1=手绘,2=夸张",
191
+ },
192
+ roundness: { type: "boolean", description: "是否圆角,默认true" },
193
+ opacity: { type: "number", description: "不透明度0-100,默认100" },
170
194
  },
171
- required: ['shape', 'x', 'y'],
195
+ required: ["shape", "x", "y"],
172
196
  },
173
197
  },
174
198
  },
175
199
  {
176
- type: 'function',
200
+ type: "function",
177
201
  function: {
178
- name: 'draw_arrow',
179
- description: '绘制箭头。可通过 startId/endId 绑定到已有形状(箭头自动吸附),也可直接指定坐标点。',
202
+ name: "draw_arrow",
203
+ description:
204
+ "绘制箭头。可通过 startId/endId 绑定到已有形状(箭头自动吸附),也可直接指定坐标点。",
180
205
  parameters: {
181
- type: 'object',
206
+ type: "object",
182
207
  properties: {
183
- startX: { type: 'number', description: '起点X(无startId时必填)' },
184
- startY: { type: 'number', description: '起点Y(无startId时必填)' },
185
- endX: { type: 'number', description: '终点X(无endId时必填)' },
186
- endY: { type: 'number', description: '终点Y(无endId时必填)' },
187
- startId: { type: 'string', description: '起点绑定的形状ID' },
188
- endId: { type: 'string', description: '终点绑定的形状ID' },
189
- label: { type: 'string', description: '箭头上的文字标签' },
208
+ startX: { type: "number", description: "起点X(无startId时必填)" },
209
+ startY: { type: "number", description: "起点Y(无startId时必填)" },
210
+ endX: { type: "number", description: "终点X(无endId时必填)" },
211
+ endY: { type: "number", description: "终点Y(无endId时必填)" },
212
+ startId: { type: "string", description: "起点绑定的形状ID" },
213
+ endId: { type: "string", description: "终点绑定的形状ID" },
214
+ label: { type: "string", description: "箭头上的文字标签" },
190
215
  startArrowhead: {
191
- type: 'string',
192
- enum: ['arrow', 'bar', 'dot', 'triangle', 'diamond'],
193
- description: '起点箭头样式,默认无',
216
+ type: "string",
217
+ enum: ["arrow", "bar", "dot", "triangle", "diamond"],
218
+ description: "起点箭头样式,默认无",
194
219
  },
195
220
  endArrowhead: {
196
- type: 'string',
197
- enum: ['arrow', 'bar', 'dot', 'triangle', 'diamond'],
198
- description: '终点箭头样式,默认arrow',
221
+ type: "string",
222
+ enum: ["arrow", "bar", "dot", "triangle", "diamond"],
223
+ description: "终点箭头样式,默认arrow",
224
+ },
225
+ strokeColor: { type: "string" },
226
+ strokeWidth: { type: "number", enum: [1, 2, 4] },
227
+ strokeStyle: {
228
+ type: "string",
229
+ enum: ["solid", "dashed", "dotted"],
230
+ },
231
+ elbowed: {
232
+ type: "boolean",
233
+ description: "是否使用直角折线,默认false",
199
234
  },
200
- strokeColor: { type: 'string' },
201
- strokeWidth: { type: 'number', enum: [1, 2, 4] },
202
- strokeStyle: { type: 'string', enum: ['solid', 'dashed', 'dotted'] },
203
- elbowed: { type: 'boolean', description: '是否使用直角折线,默认false' },
204
235
  },
205
236
  required: [],
206
237
  },
207
238
  },
208
239
  },
209
240
  {
210
- type: 'function',
241
+ type: "function",
211
242
  function: {
212
- name: 'draw_line',
213
- description: '绘制线条(无箭头),支持多个折点',
243
+ name: "draw_line",
244
+ description: "绘制线条(无箭头),支持多个折点",
214
245
  parameters: {
215
- type: 'object',
246
+ type: "object",
216
247
  properties: {
217
248
  points: {
218
- type: 'array',
219
- items: { type: 'array', items: { type: 'number' } },
220
- description: '折点坐标数组,如 [[0,0],[100,50],[200,0]]',
249
+ type: "array",
250
+ items: { type: "array", items: { type: "number" } },
251
+ description: "折点坐标数组,如 [[0,0],[100,50],[200,0]]",
252
+ },
253
+ strokeColor: { type: "string" },
254
+ strokeWidth: { type: "number", enum: [1, 2, 4] },
255
+ strokeStyle: {
256
+ type: "string",
257
+ enum: ["solid", "dashed", "dotted"],
221
258
  },
222
- strokeColor: { type: 'string' },
223
- strokeWidth: { type: 'number', enum: [1, 2, 4] },
224
- strokeStyle: { type: 'string', enum: ['solid', 'dashed', 'dotted'] },
225
259
  },
226
- required: ['points'],
260
+ required: ["points"],
227
261
  },
228
262
  },
229
263
  },
230
264
  {
231
- type: 'function',
265
+ type: "function",
232
266
  function: {
233
- name: 'draw_text',
234
- description: '在白板上添加独立文本',
267
+ name: "draw_text",
268
+ description: "在白板上添加独立文本",
235
269
  parameters: {
236
- type: 'object',
270
+ type: "object",
237
271
  properties: {
238
- id: { type: 'string' },
239
- x: { type: 'number' },
240
- y: { type: 'number' },
241
- text: { type: 'string' },
242
- fontSize: { type: 'number', description: '字号,默认20' },
272
+ id: { type: "string" },
273
+ x: { type: "number" },
274
+ y: { type: "number" },
275
+ text: { type: "string" },
276
+ fontSize: { type: "number", description: "字号,默认20" },
243
277
  fontFamily: {
244
- type: 'number',
278
+ type: "number",
245
279
  enum: [1, 2, 3, 5],
246
- description: '1=手写,2=常规,3=等宽,5=圆体',
280
+ description: "1=手写,2=常规,3=等宽,5=圆体",
247
281
  },
248
- textAlign: { type: 'string', enum: ['left', 'center', 'right'] },
249
- strokeColor: { type: 'string' },
282
+ textAlign: { type: "string", enum: ["left", "center", "right"] },
283
+ strokeColor: { type: "string" },
250
284
  },
251
- required: ['x', 'y', 'text'],
285
+ required: ["x", "y", "text"],
252
286
  },
253
287
  },
254
288
  },
255
289
  {
256
- type: 'function',
290
+ type: "function",
257
291
  function: {
258
- name: 'draw_group',
259
- description: '批量创建多个元素并自动编组(移动时一起移动)。适合画一组相关图形。',
292
+ name: "draw_group",
293
+ description:
294
+ "批量创建多个元素并自动编组(移动时一起移动)。适合画一组相关图形。",
260
295
  parameters: {
261
- type: 'object',
296
+ type: "object",
262
297
  properties: {
263
298
  elements: {
264
- type: 'array',
265
- description: '元素数组,每个元素格式同其他 draw 工具的参数',
299
+ type: "array",
300
+ description: "元素数组,每个元素格式同其他 draw 工具的参数",
266
301
  items: {
267
- type: 'object',
302
+ type: "object",
268
303
  properties: {
269
- tool: { type: 'string', enum: ['shape', 'arrow', 'line', 'text'] },
304
+ tool: {
305
+ type: "string",
306
+ enum: ["shape", "arrow", "line", "text"],
307
+ },
270
308
  args: {
271
- type: 'object',
272
- description: '对应 draw_shape/draw_arrow/draw_line/draw_text 的参数',
309
+ type: "object",
310
+ description:
311
+ "对应 draw_shape/draw_arrow/draw_line/draw_text 的参数",
273
312
  },
274
313
  },
275
- required: ['tool', 'args'],
314
+ required: ["tool", "args"],
276
315
  },
277
316
  },
278
317
  },
279
- required: ['elements'],
318
+ required: ["elements"],
280
319
  },
281
320
  },
282
321
  },
283
322
  {
284
- type: 'function',
323
+ type: "function",
285
324
  function: {
286
- name: 'draw_frame',
287
- description: '创建一个 Frame 容器,包含指定的子元素(子元素会被框在一起)',
325
+ name: "draw_frame",
326
+ description:
327
+ "创建一个 Frame 容器,包含指定的子元素(子元素会被框在一起)",
288
328
  parameters: {
289
- type: 'object',
329
+ type: "object",
290
330
  properties: {
291
- name: { type: 'string', description: 'Frame 名称' },
292
- childIds: { type: 'array', items: { type: 'string' }, description: '子元素ID数组' },
293
- x: { type: 'number' },
294
- y: { type: 'number' },
295
- width: { type: 'number' },
296
- height: { type: 'number' },
331
+ name: { type: "string", description: "Frame 名称" },
332
+ childIds: {
333
+ type: "array",
334
+ items: { type: "string" },
335
+ description: "子元素ID数组",
336
+ },
337
+ x: { type: "number" },
338
+ y: { type: "number" },
339
+ width: { type: "number" },
340
+ height: { type: "number" },
297
341
  },
298
- required: ['childIds'],
342
+ required: ["childIds"],
299
343
  },
300
344
  },
301
345
  },
302
346
  {
303
- type: 'function',
347
+ type: "function",
304
348
  function: {
305
- name: 'clear_canvas',
306
- description: '清空白板上的所有内容',
349
+ name: "clear_canvas",
350
+ description: "清空白板上的所有内容",
307
351
  parameters: {
308
- type: 'object',
352
+ type: "object",
309
353
  properties: {},
310
354
  required: [],
311
355
  },
@@ -0,0 +1,70 @@
1
+ const WebSocket = require("ws");
2
+
3
+ /**
4
+ * Send a prompt through the WS relay and return the result
5
+ * @param {object} options
6
+ * @param {string} options.wsUrl - relay client URL (wss://ws.aibaiban.com/ws/client)
7
+ * @param {string} options.authSecret - RELAY_AUTH_SECRET
8
+ * @param {string} options.userId - user ID for relay auth
9
+ * @param {string} options.prompt - prompt text
10
+ * @param {number} [options.timeout=300000] - timeout in ms
11
+ * @returns {Promise<string>} result text
12
+ */
13
+ exports.sendPromptViaRelay = function (options) {
14
+ const { wsUrl, authSecret, userId, prompt, timeout = 300000 } = options;
15
+
16
+ return new Promise((resolve, reject) => {
17
+ const ws = new WebSocket(wsUrl);
18
+ let settled = false;
19
+
20
+ const timer = setTimeout(() => {
21
+ if (!settled) {
22
+ settled = true;
23
+ ws.close();
24
+ reject(new Error("Relay timeout"));
25
+ }
26
+ }, timeout);
27
+
28
+ function done(err, result) {
29
+ if (settled) return;
30
+ settled = true;
31
+ clearTimeout(timer);
32
+ ws.close();
33
+ if (err) reject(err);
34
+ else resolve(result);
35
+ }
36
+
37
+ ws.on("open", () => {
38
+ ws.send(JSON.stringify({ type: "auth", userId, token: authSecret }));
39
+ });
40
+
41
+ ws.on("message", (data) => {
42
+ let msg;
43
+ try {
44
+ msg = JSON.parse(data.toString());
45
+ } catch {
46
+ return;
47
+ }
48
+
49
+ if (msg.type === "auth-ok") {
50
+ ws.send(JSON.stringify({ type: "prompt", prompt }));
51
+ } else if (msg.type === "auth-fail") {
52
+ done(new Error(`Auth failed: ${msg.reason}`));
53
+ } else if (msg.type === "response") {
54
+ done(null, msg.result);
55
+ } else if (msg.type === "error") {
56
+ done(new Error(msg.message || "Relay error"));
57
+ } else if (msg.type === "agent-offline") {
58
+ done(new Error("AI agent is offline"));
59
+ }
60
+ });
61
+
62
+ ws.on("error", (err) => {
63
+ done(new Error(`WebSocket error: ${err.message}`));
64
+ });
65
+
66
+ ws.on("close", () => {
67
+ done(new Error("WebSocket closed unexpectedly"));
68
+ });
69
+ });
70
+ };
package/views/index.html CHANGED
@@ -6,7 +6,10 @@
6
6
 
7
7
  <!-- Primary Meta Tags -->
8
8
  <title>AI白板 - 在线协作白板工具 | AI生成流程图时序图类图ER图</title>
9
- <meta name="title" content="AI白板 - 在线协作白板工具 | AI生成流程图时序图类图ER图" />
9
+ <meta
10
+ name="title"
11
+ content="AI白板 - 在线协作白板工具 | AI生成流程图时序图类图ER图"
12
+ />
10
13
  <meta
11
14
  name="description"
12
15
  content="AI白板是专业的在线协作白板软件,AI驱动自动生成流程图、时序图、类图、ER图。支持在线协作、远程办公、团队协同。Miro、FigJam、boardmix优质替代方案,一句话完成专业图表绘制。"
@@ -26,34 +29,60 @@
26
29
  <!-- Open Graph / Facebook -->
27
30
  <meta property="og:type" content="website" />
28
31
  <meta property="og:url" content="https://aibaiban.com/" />
29
- <meta property="og:title" content="AI白板 - 在线协作白板工具 | AI生成流程图时序图类图ER图" />
32
+ <meta
33
+ property="og:title"
34
+ content="AI白板 - 在线协作白板工具 | AI生成流程图时序图类图ER图"
35
+ />
30
36
  <meta
31
37
  property="og:description"
32
38
  content="专业的在线协作白板软件,AI自动生成流程图、时序图、类图、ER图。支持在线协作、远程办公。Miro/FigJam优质替代方案。"
33
39
  />
34
- <meta property="og:image" content="https://static-small.vincentqiao.com/aibaiban/static/og-image.png" />
40
+ <meta
41
+ property="og:image"
42
+ content="https://static-small.vincentqiao.com/aibaiban/static/og-image.png"
43
+ />
35
44
  <meta property="og:site_name" content="AI白板" />
36
45
  <meta property="og:locale" content="zh_CN" />
37
46
 
38
47
  <!-- Twitter Card -->
39
48
  <meta name="twitter:card" content="summary_large_image" />
40
49
  <meta name="twitter:url" content="https://aibaiban.com/" />
41
- <meta name="twitter:title" content="AI白板 - 在线协作白板工具 | AI生成流程图时序图类图ER图" />
50
+ <meta
51
+ name="twitter:title"
52
+ content="AI白板 - 在线协作白板工具 | AI生成流程图时序图类图ER图"
53
+ />
42
54
  <meta
43
55
  name="twitter:description"
44
56
  content="专业的在线协作白板软件,AI自动生成流程图、时序图、类图、ER图。支持在线协作、远程办公。"
45
57
  />
46
- <meta name="twitter:image" content="https://static-small.vincentqiao.com/aibaiban/static/og-image.png" />
58
+ <meta
59
+ name="twitter:image"
60
+ content="https://static-small.vincentqiao.com/aibaiban/static/og-image.png"
61
+ />
47
62
 
48
63
  <!-- Apple Mobile Web App -->
49
64
  <meta name="mobile-web-app-capable" content="yes" />
50
- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
65
+ <meta
66
+ name="apple-mobile-web-app-status-bar-style"
67
+ content="black-translucent"
68
+ />
51
69
  <meta name="apple-mobile-web-app-title" content="AI白板" />
52
70
 
53
71
  <!-- Favicon -->
54
- <link rel="icon" type="image/x-icon" href="https://static-small.vincentqiao.com/aibaiban/static/favicon.ico" />
55
- <link rel="icon" type="image/svg+xml" href="https://static-small.vincentqiao.com/aibaiban/static/logo.svg" />
56
- <link rel="apple-touch-icon" href="https://static-small.vincentqiao.com/aibaiban/static/apple-touch-icon.png" />
72
+ <link
73
+ rel="icon"
74
+ type="image/x-icon"
75
+ href="https://static-small.vincentqiao.com/aibaiban/static/favicon.ico"
76
+ />
77
+ <link
78
+ rel="icon"
79
+ type="image/svg+xml"
80
+ href="https://static-small.vincentqiao.com/aibaiban/static/logo.svg"
81
+ />
82
+ <link
83
+ rel="apple-touch-icon"
84
+ href="https://static-small.vincentqiao.com/aibaiban/static/apple-touch-icon.png"
85
+ />
57
86
 
58
87
  <!-- Manifest -->
59
88
  <link rel="manifest" href="https://aibaiban.com/manifest.json" />
@@ -81,13 +110,20 @@
81
110
  "description": "专业的在线协作白板软件,AI驱动自动生成流程图、时序图、类图、ER图。支持在线协作、远程办公、团队协同。",
82
111
  "url": "https://aibaiban.com",
83
112
  "image": "https://static-small.vincentqiao.com/aibaiban/static/og-image.png",
84
- "featureList": ["AI生成流程图", "AI生成时序图", "AI生成类图", "AI生成ER图", "在线协作白板", "远程办公支持"]
113
+ "featureList": [
114
+ "AI生成流程图",
115
+ "AI生成时序图",
116
+ "AI生成类图",
117
+ "AI生成ER图",
118
+ "在线协作白板",
119
+ "远程办公支持"
120
+ ]
85
121
  }
86
122
  ]
87
123
  </script>
88
124
  <!-- Microsoft Clarity (production only) -->
89
125
  <script type="text/javascript">
90
- if (location.hostname.includes('aibaiban.com')) {
126
+ if (location.hostname.includes("aibaiban.com")) {
91
127
  (function (c, l, a, r, i, t, y) {
92
128
  c[a] =
93
129
  c[a] ||
@@ -96,10 +132,10 @@
96
132
  };
97
133
  t = l.createElement(r);
98
134
  t.async = 1;
99
- t.src = 'https://www.clarity.ms/tag/' + i;
135
+ t.src = "https://www.clarity.ms/tag/" + i;
100
136
  y = l.getElementsByTagName(r)[0];
101
137
  y.parentNode.insertBefore(t, y);
102
- })(window, document, 'clarity', 'script', 't5b230u2zp');
138
+ })(window, document, "clarity", "script", "t5b230u2zp");
103
139
  }
104
140
  </script>
105
141
  <script
@@ -141,8 +177,8 @@
141
177
  <body>
142
178
  <div id="root"></div>
143
179
  <script>
144
- if ('serviceWorker' in navigator) {
145
- navigator.serviceWorker.register('/sw.js');
180
+ if ("serviceWorker" in navigator) {
181
+ navigator.serviceWorker.register("/sw.js");
146
182
  }
147
183
  </script>
148
184
  </body>