@baishuyun/chat-backend 0.0.15 → 0.0.17

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.
Files changed (41) hide show
  1. package/.env.example +4 -1
  2. package/CHANGELOG.md +17 -0
  3. package/config/default.ts +8 -0
  4. package/dist/config/default.js +20 -7
  5. package/dist/src/app/main.js +12 -3
  6. package/dist/src/config/hono.config.js +12 -0
  7. package/dist/src/controllers/common/connect.controll.js +20 -0
  8. package/dist/src/controllers/common/model.js +10 -0
  9. package/dist/src/controllers/form/build/build.controller.js +30 -7
  10. package/dist/src/controllers/form/build/model.js +5 -0
  11. package/dist/src/controllers/form/build/utils.js +30 -0
  12. package/dist/src/controllers/form/fill/createBatchFillingTransformStream.js +9 -0
  13. package/dist/src/controllers/form/fill/createFieldsFillingResultTransformStream.js +9 -0
  14. package/dist/src/controllers/form/fill/fill.controller.js +8 -4
  15. package/dist/src/controllers/form/fill/utils.js +30 -0
  16. package/dist/src/controllers/report/query/createQuerySuggestionTransStream.js +34 -0
  17. package/dist/src/controllers/report/query/createQueryTransformStream.js +96 -0
  18. package/dist/src/controllers/report/query/handler-helpers.js +31 -0
  19. package/dist/src/controllers/report/query/handler-registry.js +48 -0
  20. package/dist/src/controllers/report/query/model.js +24 -0
  21. package/dist/src/controllers/report/query/query.controller.js +42 -0
  22. package/dist/src/controllers/report/query/suggest.controller.js +39 -0
  23. package/dist/src/controllers/report/query/types.js +2 -0
  24. package/dist/src/controllers/report/query/utils.js +63 -0
  25. package/dist/src/routes/common/common.route.js +7 -0
  26. package/dist/src/routes/report/report.route.js +10 -0
  27. package/dist/src/utils/createJsonStreamTransformer.js +191 -0
  28. package/ecosystem.config.cjs +2 -2
  29. package/package.json +5 -4
  30. package/src/app/main.ts +2 -0
  31. package/src/config/hono.config.ts +14 -0
  32. package/src/controllers/common/connect.controll.ts +24 -0
  33. package/src/controllers/common/model.ts +12 -0
  34. package/src/controllers/form/build/build.controller.ts +23 -8
  35. package/src/controllers/form/build/model.ts +6 -0
  36. package/src/controllers/form/build/utils.ts +43 -0
  37. package/src/controllers/form/fill/utils.ts +1 -0
  38. package/src/controllers/report/query/createQueryTransformStream.ts +5 -4
  39. package/src/controllers/report/query/handler-registry.ts +5 -1
  40. package/src/controllers/report/query/query.controller.ts +1 -1
  41. package/src/routes/common/common.route.ts +10 -0
package/.env.example CHANGED
@@ -20,4 +20,7 @@ FILL_BOT_ID=xxxxx
20
20
  QUERY_BOT_ID=xxxxx
21
21
 
22
22
  # query suggest bot id
23
- QUERY_SUGGEST_BOT_ID=xxxx
23
+ QUERY_SUGGEST_BOT_ID=xxxx
24
+
25
+ # deepseek api key
26
+ DS_API_KEY=sk-xxxx
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @baishuyun/chat-backend
2
2
 
3
+ ## 0.0.17
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @baishuyun/coze-provider@0.0.17
9
+ - @baishuyun/agents@0.0.17
10
+ - @baishuyun/types@1.0.17
11
+
12
+ ## 0.0.16
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies
17
+ - @baishuyun/coze-provider@0.0.16
18
+ - @baishuyun/types@1.0.16
19
+
3
20
  ## 0.0.15
4
21
 
5
22
  ### Patch Changes
package/config/default.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { deepseek } from '@ai-sdk/deepseek';
1
2
  import { asyncConfig } from 'config/async.js';
2
3
  // load async configurations
3
4
  const fetchRemoteConfig = async () => {
@@ -18,6 +19,13 @@ export default {
18
19
 
19
20
  apiAuthKey: process.env.COZE_API_KEY,
20
21
 
22
+ deepseekApiKey: process.env.DS_API_KEY,
23
+
24
+ common: {
25
+ baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
26
+ apiKey: process.env.BOT_API_KEY, // load from env
27
+ },
28
+
21
29
  form: {
22
30
  build: {
23
31
  botId: process.env.BUILD_BOT_ID || '7579927677256073216',
@@ -1,30 +1,43 @@
1
- import { asyncConfig } from "config/async.js";
1
+ import { deepseek } from '@ai-sdk/deepseek';
2
+ import { asyncConfig } from 'config/async.js';
2
3
  // load async configurations
3
4
  const fetchRemoteConfig = async () => {
4
5
  return {
5
6
  // mock remote config
6
- foo: "bar",
7
+ foo: 'bar',
7
8
  };
8
9
  };
9
10
  export default {
10
11
  app: {
11
12
  port: process.env.PORT || 3001,
12
- host: process.env.HOST || "localhost",
13
+ host: process.env.HOST || 'localhost',
13
14
  },
14
15
  agent: {
15
- host: process.env.AGENT_HOST || "47.99.202.157",
16
+ host: process.env.AGENT_HOST || '47.99.202.157',
16
17
  apiAuthKey: process.env.COZE_API_KEY,
18
+ deepseekApiKey: process.env.DS_API_KEY,
19
+ common: {
20
+ baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
21
+ apiKey: process.env.BOT_API_KEY, // load from env
22
+ },
17
23
  form: {
18
24
  build: {
19
- botId: process.env.BUILD_BOT_ID || "7579927677256073216",
25
+ botId: process.env.BUILD_BOT_ID || '7579927677256073216',
20
26
  baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
21
27
  apiKey: process.env.BOT_API_KEY, // load from env
22
28
  },
23
29
  fill: {
24
- botId: process.env.FILL_BOT_ID || "7586483957357608960",
30
+ botId: process.env.FILL_BOT_ID || '7586483957357608960',
31
+ baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
32
+ apiKey: process.env.BOT_API_KEY, // load from env
33
+ ocrApiKey: process.env.OCR_API_KEY || '', // load from env
34
+ },
35
+ },
36
+ report: {
37
+ query: {
38
+ botId: process.env.QUERY_BOT_ID || '7595888372090929152',
25
39
  baseUrl: `http://${process.env.AGENT_HOST}/v3/`,
26
40
  apiKey: process.env.BOT_API_KEY, // load from env
27
- ocrApiKey: process.env.OCR_API_KEY || "", // load from env
28
41
  },
29
42
  },
30
43
  },
@@ -3,11 +3,20 @@
3
3
  */
4
4
  // 应用实例
5
5
  import app from '../config/hono.config.js';
6
+ import { createCommonRouter } from '../routes/common/common.route.js';
6
7
  // 子路由
7
8
  import { createFormRouter } from '../routes/form/form.route.js';
9
+ import { createReportRouter } from '../routes/report/report.route.js';
8
10
  // 挂载子路由
9
11
  app.route('web/api/form', createFormRouter());
10
- // add test route
11
- app.get('web/api/test', (c) => {
12
- return c.json({ message: 'Test route is working!' });
12
+ app.route('web/api/report', createReportRouter());
13
+ app.route('web/api/common', createCommonRouter());
14
+ // 基础健康检查
15
+ app.get('/web/api/health', (c) => {
16
+ return c.json({
17
+ status: 'ok',
18
+ timestamp: new Date().toISOString(),
19
+ uptime: process.uptime(),
20
+ service: 'hono-app',
21
+ });
13
22
  });
@@ -12,6 +12,18 @@ const app = new Hono();
12
12
  app.use('web/api/*', cors());
13
13
  // Logging middleware
14
14
  app.use(logMiddleware);
15
+ // SSE keep-alive middleware - adds headers to prevent proxy timeouts during streaming
16
+ app.use('web/api/*', async (c, next) => {
17
+ await next();
18
+ // Add keep-alive headers to streaming responses (SSE/text-streaming)
19
+ const contentType = c.res.headers.get('content-type');
20
+ if (contentType?.includes('text/plain') || contentType?.includes('text/event-stream')) {
21
+ c.header('Connection', 'keep-alive');
22
+ c.header('Keep-Alive', 'timeout=300'); // 5 minutes
23
+ c.header('X-Accel-Buffering', 'no'); // Disable nginx buffering
24
+ c.header('Cache-Control', 'no-cache, no-transform');
25
+ }
26
+ });
15
27
  // 全局错误处理中间件(捕获所有路由/中间件异常)
16
28
  app.onError((err, c) => {
17
29
  // 记录错误日志(error 级别,包含异常堆栈)
@@ -0,0 +1,20 @@
1
+ import { convertToModelMessages, streamText } from 'ai';
2
+ import { createBaseModel } from './model.js';
3
+ export const connectToAgent = async (c) => {
4
+ let requestBody;
5
+ try {
6
+ const json = await c.req.json();
7
+ requestBody = json;
8
+ }
9
+ catch (_) {
10
+ return c.json({ error: 'Invalid JSON' }, 400);
11
+ }
12
+ const botId = requestBody.botId;
13
+ const messages = requestBody.messages;
14
+ const stream = streamText({
15
+ model: createBaseModel(botId),
16
+ messages: convertToModelMessages(messages),
17
+ includeRawChunks: true,
18
+ });
19
+ return stream.toUIMessageStreamResponse();
20
+ };
@@ -0,0 +1,10 @@
1
+ import { createCoze } from '@baishuyun/coze-provider';
2
+ import config from 'config';
3
+ export const createBaseModel = (botId) => {
4
+ const coze = createCoze({
5
+ apiKey: config.get('agent.common.apiKey'),
6
+ baseURL: config.get('agent.common.baseUrl'),
7
+ botId: botId,
8
+ });
9
+ return coze.chat('chat');
10
+ };
@@ -1,7 +1,10 @@
1
- import { convertToModelMessages, streamText } from "ai";
1
+ import { convertToModelMessages, streamText, generateText } from "ai";
2
2
  import {} from "hono";
3
3
  import { createModel } from "./model.js";
4
4
  import { createFieldsJsonTransformStream, SuggestionTransformStream, } from "@baishuyun/coze-provider";
5
+ import { logger } from "../../../logger/index.js";
6
+ import config from 'config';
7
+ import { determineUserIntentByInput } from "./utils.js";
5
8
  /**
6
9
  * 搭建表单
7
10
  * @param c
@@ -16,13 +19,33 @@ export const buildForm = async (c) => {
16
19
  catch (_) {
17
20
  return c.json({ error: "Invalid JSON" }, 400);
18
21
  }
19
- const isBuildStage = requestBody.stage === "build";
22
+ const intent = await determineUserIntentByInput(requestBody.text, requestBody.stage);
23
+ const isBuildStage = intent === "build";
24
+ const formName = requestBody.name;
25
+ const model = createModel([
26
+ () => createFieldsJsonTransformStream(isBuildStage),
27
+ () => new SuggestionTransformStream(isBuildStage),
28
+ ]);
29
+ logger.debug("intent: " + intent);
30
+ const allMessages = [...requestBody.messages];
31
+ if (isBuildStage && formName) {
32
+ allMessages.push({
33
+ role: "user",
34
+ parts: [
35
+ {
36
+ type: "text",
37
+ text: `确认搭建:${formName}`,
38
+ },
39
+ ],
40
+ });
41
+ }
42
+ // clear empty text parts to avoid unnecessary streaming
43
+ allMessages.forEach((message) => {
44
+ message.parts = message.parts.filter((part) => part.type === "text" && part.text.trim() !== "");
45
+ });
20
46
  const stream = streamText({
21
- model: createModel([
22
- () => createFieldsJsonTransformStream(isBuildStage),
23
- () => new SuggestionTransformStream(isBuildStage),
24
- ]),
25
- messages: convertToModelMessages(requestBody.messages),
47
+ model,
48
+ messages: convertToModelMessages(allMessages),
26
49
  includeRawChunks: true,
27
50
  headers: {
28
51
  "x-user-stage": requestBody.stage,
@@ -1,3 +1,4 @@
1
+ import { createFormBuildIntentAgent } from "@baishuyun/agents";
1
2
  import { createCoze } from "@baishuyun/coze-provider";
2
3
  import config from "config";
3
4
  export const createModel = (extraStreamTransformers) => {
@@ -9,3 +10,7 @@ export const createModel = (extraStreamTransformers) => {
9
10
  });
10
11
  return coze.chat("chat");
11
12
  };
13
+ export const createUserIntentAgent = () => {
14
+ const dsApiKey = config.get("agent.deepseekApiKey");
15
+ return createFormBuildIntentAgent(dsApiKey);
16
+ };
@@ -0,0 +1,30 @@
1
+ import { createUserIntentAgent } from "./model.js";
2
+ export const determineUserIntentByInput = async (input, userOriginIntent) => {
3
+ if (userOriginIntent === "design") {
4
+ return "design";
5
+ }
6
+ if (!input || input.trim() === "") {
7
+ return userOriginIntent;
8
+ }
9
+ const agent = createUserIntentAgent();
10
+ const intent = await agent.generate({
11
+ messages: [{
12
+ role: "user",
13
+ content: [{
14
+ type: "text",
15
+ text: input,
16
+ }]
17
+ }]
18
+ });
19
+ const result = intent.response.messages;
20
+ if (!result || result.length === 0) {
21
+ return userOriginIntent;
22
+ }
23
+ const lastMessage = result[result.length - 1];
24
+ if (!lastMessage.content || lastMessage.content.length === 0) {
25
+ return userOriginIntent;
26
+ }
27
+ const lastContent = lastMessage.content[0];
28
+ const intentText = lastContent.text === "build" ? "build" : lastContent.text === "design" ? "design" : userOriginIntent;
29
+ return intentText;
30
+ };
@@ -41,6 +41,15 @@ export const createBatchFillingResultTransformer = (enableJsonParser) => {
41
41
  }
42
42
  else {
43
43
  // logger.debug(`bypass chunk in batch filling transformer: ${JSON.stringify(chunk)}`);
44
+ if (chunk.type === 'error') {
45
+ enqueueTextDelta('error', {
46
+ type: 'agent-error',
47
+ error: '操作超时,请刷新重试',
48
+ });
49
+ resolveParseCompleted?.();
50
+ controller.terminate(); // 信号通知下游可读流已关闭
51
+ return;
52
+ }
44
53
  controller.enqueue(chunk);
45
54
  }
46
55
  }
@@ -40,6 +40,15 @@ export const createFieldsFillingResultTransformer = (enableJsonParser) => {
40
40
  }
41
41
  }
42
42
  else {
43
+ if (chunk.type === 'error') {
44
+ enqueueTextDelta('error', {
45
+ type: 'agent-error',
46
+ error: '操作超时,请刷新重试',
47
+ });
48
+ resolveParseCompleted?.();
49
+ controller.terminate(); // 信号通知下游可读流已关闭
50
+ return;
51
+ }
43
52
  controller.enqueue(chunk);
44
53
  }
45
54
  }
@@ -2,7 +2,7 @@ import {} from 'hono';
2
2
  import { convertToModelMessages, streamText, generateId } from 'ai';
3
3
  import { logger } from '../../../logger/index.js';
4
4
  import { createFillingModel } from './model.js';
5
- import { mode2part } from './utils.js';
5
+ import { mode2part, trimFormStructure } from './utils.js';
6
6
  import { batchFillForm } from './batch-fill.controller.js';
7
7
  /**
8
8
  * 表单填写
@@ -18,7 +18,7 @@ export const fillForm = async (c) => {
18
18
  catch (_) {
19
19
  return c.json({ error: 'Invalid JSON' }, 400);
20
20
  }
21
- const formStructure = requestBody.formStructure;
21
+ const formStructure = trimFormStructure(requestBody.formStructure || []);
22
22
  const mode = requestBody.mode;
23
23
  const num = requestBody.count;
24
24
  const messages = requestBody.messages;
@@ -26,13 +26,16 @@ export const fillForm = async (c) => {
26
26
  if (mode === 'batch') {
27
27
  return batchFillForm(c);
28
28
  }
29
+ const flattenParts = messages.flatMap((msg) => msg.parts);
30
+ const msg = [];
29
31
  if (formStructure) {
30
32
  const extraPart = mode2part(mode, num);
31
33
  try {
32
- messages.push({
34
+ msg.push({
33
35
  id: generateId(),
34
36
  role: 'user',
35
37
  parts: [
38
+ ...flattenParts,
36
39
  {
37
40
  type: 'text',
38
41
  text: `formStructure: ${JSON.stringify(formStructure)}`,
@@ -45,9 +48,10 @@ export const fillForm = async (c) => {
45
48
  logger.error('Failed to append form structure');
46
49
  }
47
50
  }
51
+ const modelMessages = convertToModelMessages(msg);
48
52
  const stream = streamText({
49
53
  model: createFillingModel(),
50
- messages: convertToModelMessages(messages),
54
+ messages: modelMessages,
51
55
  includeRawChunks: true,
52
56
  headers: {
53
57
  'x-user-stage': requestBody.stage,
@@ -81,3 +81,33 @@ export const fillingChunkGuard = (chunk, enableGuard = true) => {
81
81
  }
82
82
  return true;
83
83
  };
84
+ /**
85
+ * 裁剪表单结构,子表单只展开一层
86
+ */
87
+ export function trimFormStructure(fields) {
88
+ return fields.map((field) => {
89
+ const trimmed = {
90
+ description: field.description,
91
+ label: field.label,
92
+ widget: {
93
+ type: field.widget.type,
94
+ widgetName: field.widget.widgetName,
95
+ },
96
+ };
97
+ if (field.widget.items && Array.isArray(field.widget.items)) {
98
+ // 过滤掉无效 item(null/undefined 或缺少 widget)
99
+ trimmed.widget.items = field.widget.items
100
+ .filter((item) => item != null && item.widget != null)
101
+ .map((item) => ({
102
+ description: item.description,
103
+ label: item.label,
104
+ widget: {
105
+ type: item.widget.type,
106
+ widgetName: item.widget.widgetName,
107
+ noRepeat: item.widget.noRepeat, // 保留 noRepeat 属性
108
+ },
109
+ }));
110
+ }
111
+ return trimmed;
112
+ });
113
+ }
@@ -0,0 +1,34 @@
1
+ import {} from '@ai-sdk/provider';
2
+ import { JSONParser } from '@streamparser/json';
3
+ import {} from '@baishuyun/types';
4
+ import { createJsonStreamTransformer, } from '../../../utils/createJsonStreamTransformer.js';
5
+ import { logger } from '../../../logger/index.js';
6
+ export const createQuerySuggestionTransformer = () => {
7
+ return createJsonStreamTransformer({
8
+ createJSONParser: createJSONParser,
9
+ onParseValue: handleParsedValue,
10
+ onParseError(error) {
11
+ logger.error('JSON parsing error in query transformer: ', error);
12
+ return '数据解析异常,请重试';
13
+ },
14
+ onErrorChunk(chunk) {
15
+ logger.error('Received error chunk in query transformer');
16
+ return '';
17
+ },
18
+ });
19
+ };
20
+ function createJSONParser() {
21
+ return new JSONParser({
22
+ stringBufferSize: undefined,
23
+ numberBufferSize: undefined,
24
+ separator: '',
25
+ paths: ['$.*'],
26
+ keepStack: true,
27
+ });
28
+ }
29
+ function handleParsedValue(ctx) {
30
+ const { parsedInfo, getResult, currentChunkId, deltaChunkEnqueuer: enqueueTextDelta, ctrl } = ctx;
31
+ const { value } = parsedInfo;
32
+ logger.debug('Parsed JSON value: ' + JSON.stringify(value));
33
+ enqueueTextDelta(`${JSON.stringify(value)},`, { type: 'query-suggestion-parsed-info', result: JSON.stringify(getResult()) }, currentChunkId + 2, true);
34
+ }
@@ -0,0 +1,96 @@
1
+ import {} from '@ai-sdk/provider';
2
+ import { JSONParser } from '@streamparser/json';
3
+ import {} from '@baishuyun/types';
4
+ import { createJsonStreamTransformer, } from '../../../utils/createJsonStreamTransformer.js';
5
+ import { isTargetElement } from './utils.js';
6
+ import { logger } from '../../../logger/index.js';
7
+ import { fieldHandlers } from './handler-registry.js';
8
+ export const createQueryResultTransformer = () => {
9
+ return createJsonStreamTransformer({
10
+ createJSONParser: createJSONParser,
11
+ onParseValue: handleParsedValue,
12
+ onParseError(error) {
13
+ logger.error('JSON parsing error in query transformer: ', error);
14
+ return '数据解析异常,请重试';
15
+ },
16
+ onErrorChunk(chunk) {
17
+ logger.error('Received error chunk in query transformer');
18
+ return '';
19
+ },
20
+ });
21
+ };
22
+ function createJSONParser() {
23
+ return new JSONParser({
24
+ stringBufferSize: undefined,
25
+ numberBufferSize: undefined,
26
+ separator: '',
27
+ paths: [
28
+ '$', // 保留完整结果推送
29
+ '$.title',
30
+ '$.source',
31
+ '$.name',
32
+ '$.type',
33
+ '$.userID',
34
+ '$.forms',
35
+ '$.fields.*',
36
+ '$.xFields.*',
37
+ '$.yFields.*',
38
+ '$.metrics.*',
39
+ '$.formulas.*',
40
+ '$.filter.rel',
41
+ '$.filter.cond.*',
42
+ '$.limit',
43
+ ],
44
+ keepStack: true,
45
+ });
46
+ }
47
+ function handleParsedValue(ctx) {
48
+ const { parsedInfo, getResult, currentChunkId, deltaChunkEnqueuer: enqueueTextDelta, ctrl } = ctx;
49
+ const { value } = parsedInfo;
50
+ logger.debug('Parsed JSON value: ' + JSON.stringify(value));
51
+ // aggregate 类型 —— name 字段触发,独立处理后直接返回
52
+ if (isTargetElement('$.name', parsedInfo)) {
53
+ handleAggregateName(ctx);
54
+ return;
55
+ }
56
+ // Phase 0: 报表标题 —— 初始化结果并发送 text-start, 必须为起始字段
57
+ if (isTargetElement('$.title', parsedInfo)) {
58
+ handleTitle(ctx);
59
+ }
60
+ // Phase 3: aggregate 结果无需后续字段处理
61
+ if (getResult().isAggregate)
62
+ return;
63
+ // Phase 4: 按注册的 handler 分发字段处理
64
+ for (const { path, handler } of fieldHandlers) {
65
+ if (isTargetElement(path, parsedInfo)) {
66
+ handler(ctx, value);
67
+ break;
68
+ }
69
+ }
70
+ // Phase 5: 向下游推送增量结果
71
+ enqueueTextDelta(`${JSON.stringify(value)},`, { type: 'query-stream-parsed-info', result: JSON.stringify(getResult()) }, currentChunkId + 2, true);
72
+ }
73
+ // ===================== Phase Handlers =====================
74
+ function handleAggregateName(ctx) {
75
+ const { parsedInfo, getResult, currentChunkId, deltaChunkEnqueuer: enqueueTextDelta } = ctx;
76
+ const value = parsedInfo.value;
77
+ ctx.setPartialResult({
78
+ title: value,
79
+ source: 'aggregate',
80
+ type: 'data_table',
81
+ isAggregate: true,
82
+ });
83
+ logger.debug('Parsed aggregate table title: ' + value);
84
+ enqueueTextDelta(`${JSON.stringify(value)},`, { type: 'query-stream-parsed-info', result: JSON.stringify(getResult()) }, currentChunkId + 1, false);
85
+ }
86
+ function handleTitle(ctx) {
87
+ const { parsedInfo, currentChunkId, ctrl } = ctx;
88
+ ctx.clearResult();
89
+ ctrl.enqueue({
90
+ type: 'text-start',
91
+ id: currentChunkId + 2,
92
+ });
93
+ ctx.setPartialResult({
94
+ title: parsedInfo.value,
95
+ });
96
+ }
@@ -0,0 +1,31 @@
1
+ import {} from '@baishuyun/types';
2
+ import {} from '../../../utils/createJsonStreamTransformer.js';
3
+ // ===================== Handler Helpers =====================
4
+ /** 创建一个将值推入指定数组字段的 handler */
5
+ export function pushToArray(field) {
6
+ return (ctx, value) => {
7
+ const result = ctx.getResult();
8
+ if (!result[field]) {
9
+ ctx.setPartialResult({ [field]: [] });
10
+ }
11
+ ctx.getResult()[field].push(value);
12
+ };
13
+ }
14
+ /** 创建一个将值推入数组字段、并附加 tag/form 元信息的 handler */
15
+ export function pushToArrayWithMeta(field) {
16
+ return (ctx, value) => {
17
+ const result = ctx.getResult();
18
+ if (!result[field]) {
19
+ ctx.setPartialResult({ [field]: [] });
20
+ }
21
+ value.tag = `f_${Date.now()}`;
22
+ value.form = result.formIds ? result.formIds[0] : '';
23
+ ctx.getResult()[field].push(value);
24
+ };
25
+ }
26
+ /** 确保 filter 对象已初始化 */
27
+ export function ensureFilter(ctx) {
28
+ if (!ctx.getResult().filter) {
29
+ ctx.setPartialResult({ filter: { rel: 'and', conds: [] } });
30
+ }
31
+ }
@@ -0,0 +1,48 @@
1
+ import {} from '@baishuyun/types';
2
+ import { ensureFilter, pushToArray, pushToArrayWithMeta } from './handler-helpers.js';
3
+ export const fieldHandlers = [
4
+ {
5
+ path: '$.source',
6
+ handler: (ctx, value) => ctx.setPartialResult({ source: value }),
7
+ },
8
+ {
9
+ path: '$.userID',
10
+ handler: (ctx, value) => ctx.setPartialResult({ userID: value }),
11
+ },
12
+ {
13
+ path: '$.limit',
14
+ handler: (ctx, value) => ctx.setPartialResult({ limit: value }),
15
+ },
16
+ {
17
+ path: '$.forms',
18
+ handler: (ctx, value) => ctx.setPartialResult({ formIds: [value] }),
19
+ },
20
+ {
21
+ path: '$.type',
22
+ handler: (ctx, value) => ctx.setPartialResult({ type: value }),
23
+ },
24
+ {
25
+ path: '$.filter.rel',
26
+ handler: (ctx, value) => {
27
+ ensureFilter(ctx);
28
+ ctx.setPartialResult({
29
+ filter: { ...ctx.getResult().filter, rel: value },
30
+ });
31
+ },
32
+ },
33
+ {
34
+ path: '$.filter.cond.*',
35
+ handler: (ctx, value) => {
36
+ ensureFilter(ctx);
37
+ const filter = ctx.getResult().filter;
38
+ ctx.setPartialResult({
39
+ filter: { ...filter, conds: [...(filter.conds || []), value] },
40
+ });
41
+ },
42
+ },
43
+ { path: '$.fields.*', handler: pushToArray('fields') },
44
+ { path: '$.formulas.*', handler: pushToArray('formulas') },
45
+ { path: '$.metrics.*', handler: pushToArrayWithMeta('metrics') },
46
+ { path: '$.xFields.*', handler: pushToArrayWithMeta('xFields') },
47
+ { path: '$.yFields.*', handler: pushToArrayWithMeta('yFields') },
48
+ ];
@@ -0,0 +1,24 @@
1
+ import { createCoze } from '@baishuyun/coze-provider';
2
+ import config from 'config';
3
+ import { createQueryResultTransformer } from './createQueryTransformStream.js';
4
+ import { createQuerySuggestionTransformer } from './createQuerySuggestionTransStream.js';
5
+ export const createReportQueryModel = () => {
6
+ const reportQuery = createCoze({
7
+ apiKey: config.get('agent.report.query.apiKey'),
8
+ baseURL: config.get('agent.report.query.baseUrl'),
9
+ botId: config.get('agent.report.query.botId'),
10
+ extraStreamTransformers: [createQueryResultTransformer],
11
+ });
12
+ const reportQueryModel = reportQuery.chat('chat');
13
+ return reportQueryModel;
14
+ };
15
+ export const createQuerySuggestionModel = () => {
16
+ const reportQuery = createCoze({
17
+ apiKey: config.get('agent.report.query.apiKey'),
18
+ baseURL: config.get('agent.report.query.baseUrl'),
19
+ botId: config.get('agent.report.query.botId'),
20
+ extraStreamTransformers: [createQuerySuggestionTransformer],
21
+ });
22
+ const reportQueryModel = reportQuery.chat('chat');
23
+ return reportQueryModel;
24
+ };