@yaoyuanchao/dingtalk 1.3.15 → 1.4.1

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": "@yaoyuanchao/dingtalk",
3
- "version": "1.3.15",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "description": "DingTalk channel plugin for Clawdbot with Stream Mode support",
6
6
  "license": "MIT",
@@ -9,8 +9,8 @@ export const groupPolicySchema = z.enum(['disabled', 'allowlist', 'open'], {
9
9
  description: 'Group chat access control policy',
10
10
  });
11
11
 
12
- export const messageFormatSchema = z.enum(['text', 'markdown', 'richtext', 'auto', 'card'], {
13
- description: 'Message format for bot responses (richtext is an alias for markdown, auto detects markdown features, card uses interactive cards)',
12
+ export const messageFormatSchema = z.enum(['text', 'markdown', 'richtext', 'auto'], {
13
+ description: 'Message format for bot responses (richtext is an alias for markdown, auto detects markdown features)',
14
14
  });
15
15
 
16
16
  // DingTalk 配置 Schema
@@ -61,25 +61,9 @@ export const dingTalkConfigSchema = z.object({
61
61
  ' - text: Plain text (recommended, supports tables)\n' +
62
62
  ' - markdown: DingTalk markdown (limited support, no tables)\n' +
63
63
  ' - richtext: Alias for markdown (deprecated, use markdown instead)\n' +
64
- ' - auto: Auto-detect markdown features in response\n' +
65
- ' - card: Interactive card (requires cardTemplateId)'
64
+ ' - auto: Auto-detect markdown features in response'
66
65
  ),
67
66
 
68
- // 卡片配置(当 messageFormat 为 card 时使用)
69
- card: z.object({
70
- templateId: z.string().min(1)
71
- .describe('Card template ID from DingTalk card platform (e.g., "xxx.schema")'),
72
- title: z.string().optional()
73
- .describe('Card title (optional, if template has a title variable)'),
74
- streamingEnabled: z.boolean().default(false)
75
- .describe('Enable streaming update for AI typewriter effect (costs more API quota)'),
76
- streamingKey: z.string().default('content')
77
- .describe('Variable key for content in the card template (must match template variable name)'),
78
- fallbackToMarkdown: z.boolean().default(true)
79
- .describe('Fall back to markdown when card delivery fails'),
80
- }).optional()
81
- .describe('Interactive card configuration (required when messageFormat is "card")'),
82
-
83
67
  // 思考反馈
84
68
  showThinking: z.boolean().default(false)
85
69
  .describe('Send "正在思考..." feedback before AI responds'),
package/src/monitor.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { DingTalkRobotMessage, ResolvedDingTalkAccount, ExtractedMessage } from "./types.js";
2
2
  import { sendViaSessionWebhook, sendMarkdownViaSessionWebhook, sendDingTalkRestMessage, batchGetUserInfo, downloadPicture, downloadMediaFile, cleanupOldMedia } from "./api.js";
3
- import { sendCardMessage, sendStreamingAICard } from "./card-api.js";
4
3
  import { getDingTalkRuntime } from "./runtime.js";
5
4
 
6
5
  export interface DingTalkMonitorContext {
@@ -153,11 +152,41 @@ async function extractMessageContent(
153
152
  };
154
153
  }
155
154
 
155
+ case 'chatRecord': {
156
+ // Chat record collection - contains multiple forwarded messages
157
+ const chatRecordContent = content || (msg as any).chatRecord;
158
+ log?.info?.("[dingtalk] chatRecord message received, raw structure: " + JSON.stringify(chatRecordContent));
159
+
160
+ if (chatRecordContent?.recordList && Array.isArray(chatRecordContent.recordList)) {
161
+ // Parse recordList into readable text
162
+ const records = chatRecordContent.recordList.map((record: any, idx: number) => {
163
+ const sender = record.senderNick || record.senderName || '未知用户';
164
+ const msgContent = record.text || record.content || '[不支持的消息类型]';
165
+ const time = record.createAt ? new Date(record.createAt).toLocaleString('zh-CN') : '';
166
+ return `[${idx + 1}] ${sender}${time ? ` (${time})` : ''}: ${msgContent}`;
167
+ });
168
+ const text = `[聊天记录合集 - ${records.length}条消息]\n${records.join('\n')}`;
169
+ return {
170
+ text,
171
+ messageType: 'chatRecord',
172
+ };
173
+ }
174
+
175
+ // Fallback if structure is different
176
+ log?.info?.("[dingtalk] chatRecord structure not recognized, full msg: " + JSON.stringify(msg));
177
+ return {
178
+ text: '[聊天记录合集]',
179
+ messageType: 'chatRecord',
180
+ };
181
+ }
182
+
156
183
  default: {
157
184
  // Fallback: try text.content for unknown message types
158
185
  const text = msg.text?.content?.trim() || '';
159
186
  if (!text) {
160
187
  log?.info?.("[dingtalk] Unknown msgtype: " + msgtype + ", no text content found");
188
+ // Log full message structure for debugging unknown types
189
+ log?.info?.("[dingtalk] Unknown msgtype full structure: " + JSON.stringify(msg).slice(0, 1000));
161
190
  }
162
191
  return {
163
192
  text: text || `[${msgtype}消息]`,
@@ -750,95 +779,16 @@ function resolveDeliverText(payload: any, log?: any): string | undefined {
750
779
  return text || undefined;
751
780
  }
752
781
 
753
- /**
754
- * Deliver reply using interactive card
755
- * Returns true if card was sent successfully, false otherwise
756
- */
757
- async function deliverCardReply(
758
- target: any,
759
- text: string,
760
- cardConfig: { templateId: string; title?: string; streamingEnabled?: boolean; streamingKey?: string },
761
- log?: any,
762
- ): Promise<boolean> {
763
- try {
764
- const { clientId, clientSecret, robotCode } = target.account;
765
-
766
- if (!clientId || !clientSecret) {
767
- log?.info?.("[dingtalk-card] Missing credentials for card delivery");
768
- return false;
769
- }
770
-
771
- const robotCodeValue = robotCode || clientId;
772
-
773
- // Prepare card data - use configurable key for content variable (default: 'content')
774
- // For basic cards: send content and title
775
- // For AI streaming cards: also send 'flowStatus'
776
- const isStreaming = cardConfig.streamingEnabled;
777
- const contentKey = cardConfig.streamingKey || 'content';
778
- const cardData: Record<string, string> = {
779
- [contentKey]: text,
780
- // Always send title - required by templates with title variable
781
- title: cardConfig.title || "",
782
- };
783
-
784
- // Only add flowStatus for streaming cards (AI cards)
785
- if (isStreaming) {
786
- cardData.flowStatus = "0"; // "0" = streaming in progress
787
- }
788
-
789
- log?.info?.("[dingtalk-card] Sending card message, templateId=" + cardConfig.templateId + ", contentKey=" + contentKey);
790
-
791
- const result = await sendCardMessage({
792
- clientId,
793
- clientSecret,
794
- robotCode: robotCodeValue,
795
- cardTemplateId: cardConfig.templateId,
796
- cardData,
797
- openConversationId: target.isDm ? undefined : target.conversationId,
798
- senderStaffId: target.isDm ? target.senderId : undefined,
799
- });
800
-
801
- if (result.success) {
802
- log?.info?.("[dingtalk-card] Card sent successfully, outTrackId=" + result.outTrackId);
803
- return true;
804
- } else {
805
- log?.info?.("[dingtalk-card] Card send failed: " + (result.message || result.errmsg || JSON.stringify(result)));
806
- return false;
807
- }
808
- } catch (err) {
809
- log?.info?.("[dingtalk-card] Card delivery error: " + (err instanceof Error ? err.message : String(err)));
810
- return false;
811
- }
812
- }
813
-
814
782
  async function deliverReply(target: any, text: string, log?: any): Promise<void> {
815
783
  const now = Date.now();
816
784
  const chunkLimit = 2000;
817
785
  const messageFormat = target.account.config.messageFormat ?? "text";
818
- const cardConfig = target.account.config.card;
819
-
820
- // Handle card format
821
- if (messageFormat === 'card' && cardConfig?.templateId) {
822
- const cardSent = await deliverCardReply(target, text, cardConfig, log);
823
- if (cardSent) return;
824
-
825
- // Fallback to markdown if card failed and fallback is enabled
826
- if (cardConfig.fallbackToMarkdown !== false) {
827
- log?.info?.("[dingtalk] Card delivery failed, falling back to markdown");
828
- } else {
829
- log?.info?.("[dingtalk] Card delivery failed, no fallback configured");
830
- return;
831
- }
832
- }
833
786
 
834
787
  // Determine if this message should use markdown format
835
788
  let isMarkdown: boolean;
836
789
  if (messageFormat === 'auto') {
837
790
  isMarkdown = detectMarkdownContent(text);
838
791
  log?.info?.("[dingtalk] Auto-detected format: " + (isMarkdown ? "markdown" : "text"));
839
- } else if (messageFormat === 'card') {
840
- // Card failed, fallback to markdown
841
- isMarkdown = true;
842
792
  } else {
843
793
  // Support both "markdown" and "richtext" (they're equivalent for DingTalk)
844
794
  isMarkdown = messageFormat === "markdown" || messageFormat === "richtext";
package/src/card-api.ts DELETED
@@ -1,580 +0,0 @@
1
- /**
2
- * DingTalk Interactive Card API
3
- *
4
- * 钉钉互动卡片 API 封装,支持:
5
- * - 创建卡片实例
6
- * - 投放卡片到会话
7
- * - 普通更新卡片
8
- * - 流式更新卡片(AI 打字机效果)
9
- *
10
- * 参考文档:
11
- * - https://open.dingtalk.com/document/orgapp/api-streamingupdate
12
- * - https://github.com/open-dingtalk/dingtalk-card-examples
13
- */
14
-
15
- import { getDingTalkAccessToken } from "./api.js";
16
-
17
- const DINGTALK_API_BASE = "https://api.dingtalk.com/v1.0";
18
-
19
- /** HTTP POST with JSON body */
20
- async function jsonPost(
21
- url: string,
22
- body: unknown,
23
- headers?: Record<string, string>,
24
- ): Promise<any> {
25
- const response = await fetch(url, {
26
- method: "POST",
27
- headers: {
28
- "Content-Type": "application/json",
29
- ...headers,
30
- },
31
- body: JSON.stringify(body),
32
- });
33
-
34
- const text = await response.text();
35
- try {
36
- return JSON.parse(text);
37
- } catch {
38
- return { raw: text, status: response.status };
39
- }
40
- }
41
-
42
- /** HTTP PUT with JSON body */
43
- async function jsonPut(
44
- url: string,
45
- body: unknown,
46
- headers?: Record<string, string>,
47
- ): Promise<any> {
48
- const response = await fetch(url, {
49
- method: "PUT",
50
- headers: {
51
- "Content-Type": "application/json",
52
- ...headers,
53
- },
54
- body: JSON.stringify(body),
55
- });
56
-
57
- const text = await response.text();
58
- try {
59
- return JSON.parse(text);
60
- } catch {
61
- return { raw: text, status: response.status };
62
- }
63
- }
64
-
65
- /** Card instance creation parameters */
66
- export interface CreateCardInstanceParams {
67
- clientId: string;
68
- clientSecret: string;
69
- cardTemplateId: string;
70
- outTrackId: string;
71
- /** Card data - flat object with variable names as keys */
72
- cardData?: Record<string, any>;
73
- robotCode?: string;
74
- callbackType?: "STREAM" | "HTTP";
75
- userIdType?: number;
76
- }
77
-
78
- /** Card delivery parameters */
79
- export interface DeliverCardParams {
80
- clientId: string;
81
- clientSecret: string;
82
- outTrackId: string;
83
- openSpaceId: string;
84
- robotCode?: string;
85
- /** For group delivery */
86
- imGroupOpenDeliverModel?: {
87
- robotCode: string;
88
- atUserIds?: Record<string, string>;
89
- };
90
- /** For single chat delivery */
91
- imRobotOpenDeliverModel?: {
92
- spaceType: "IM_ROBOT";
93
- robotCode: string;
94
- };
95
- }
96
-
97
- /** Card update parameters */
98
- export interface UpdateCardParams {
99
- clientId: string;
100
- clientSecret: string;
101
- outTrackId: string;
102
- cardData: {
103
- cardParamMap?: Record<string, string>;
104
- cardMediaIdParamMap?: Record<string, string>;
105
- };
106
- userIdType?: number;
107
- }
108
-
109
- /** Streaming update parameters (AI typewriter effect) */
110
- export interface StreamingUpdateParams {
111
- clientId: string;
112
- clientSecret: string;
113
- outTrackId: string;
114
- /** Unique identifier for this streaming session */
115
- key: string;
116
- /** Content to append */
117
- content: string;
118
- /** Whether this is the final update */
119
- isFull?: boolean;
120
- /** GUID for idempotency */
121
- guid?: string;
122
- }
123
-
124
- /** API response with error info */
125
- export interface CardApiResponse {
126
- success: boolean;
127
- result?: any;
128
- errcode?: number;
129
- errmsg?: string;
130
- code?: string;
131
- message?: string;
132
- }
133
-
134
- /**
135
- * Create a card instance
136
- *
137
- * API: POST /v1.0/card/instances
138
- */
139
- export async function createCardInstance(
140
- params: CreateCardInstanceParams,
141
- ): Promise<CardApiResponse> {
142
- try {
143
- const token = await getDingTalkAccessToken(params.clientId, params.clientSecret);
144
-
145
- // cardData is an object with cardParamMap containing the template variables
146
- const body: Record<string, any> = {
147
- cardTemplateId: params.cardTemplateId,
148
- outTrackId: params.outTrackId,
149
- cardData: {
150
- cardParamMap: params.cardData || {},
151
- },
152
- };
153
-
154
- // Only add callbackType if specified (not needed for basic cards)
155
- if (params.callbackType) {
156
- body.callbackType = params.callbackType;
157
- }
158
-
159
- if (params.robotCode) {
160
- body.robotCode = params.robotCode;
161
- }
162
-
163
- if (params.userIdType !== undefined) {
164
- body.userIdType = params.userIdType;
165
- }
166
-
167
- console.log("[dingtalk-card] createCardInstance request:", JSON.stringify(body, null, 2));
168
-
169
- const res = await jsonPost(
170
- `${DINGTALK_API_BASE}/card/instances`,
171
- body,
172
- { "x-acs-dingtalk-access-token": token },
173
- );
174
-
175
- console.log("[dingtalk-card] createCardInstance response:", JSON.stringify(res, null, 2));
176
-
177
- if (res.code || res.errcode) {
178
- console.warn("[dingtalk-card] Failed to create card instance:", {
179
- success: false,
180
- errcode: res.errcode,
181
- errmsg: res.errmsg,
182
- code: res.code,
183
- message: res.message,
184
- });
185
- return {
186
- success: false,
187
- errcode: res.errcode,
188
- errmsg: res.errmsg,
189
- code: res.code,
190
- message: res.message,
191
- };
192
- }
193
-
194
- return { success: true, result: res };
195
- } catch (err) {
196
- return {
197
- success: false,
198
- message: String(err),
199
- };
200
- }
201
- }
202
-
203
- /**
204
- * Add space to card instance
205
- *
206
- * API: POST /v1.0/card/instances/spaces
207
- */
208
- export async function createCardSpace(
209
- clientId: string,
210
- clientSecret: string,
211
- outTrackId: string,
212
- openSpaceId: string,
213
- ): Promise<CardApiResponse> {
214
- try {
215
- const token = await getDingTalkAccessToken(clientId, clientSecret);
216
-
217
- const res = await jsonPost(
218
- `${DINGTALK_API_BASE}/card/instances/spaces`,
219
- { outTrackId, openSpaceId },
220
- { "x-acs-dingtalk-access-token": token },
221
- );
222
-
223
- if (res.code || res.errcode) {
224
- return {
225
- success: false,
226
- errcode: res.errcode,
227
- errmsg: res.errmsg,
228
- code: res.code,
229
- message: res.message,
230
- };
231
- }
232
-
233
- return { success: true, result: res };
234
- } catch (err) {
235
- return {
236
- success: false,
237
- message: String(err),
238
- };
239
- }
240
- }
241
-
242
- /**
243
- * Deliver card to conversation
244
- *
245
- * API: POST /v1.0/card/instances/deliver
246
- */
247
- export async function deliverCard(
248
- params: DeliverCardParams,
249
- ): Promise<CardApiResponse> {
250
- try {
251
- const token = await getDingTalkAccessToken(params.clientId, params.clientSecret);
252
-
253
- const body: Record<string, any> = {
254
- outTrackId: params.outTrackId,
255
- openSpaceId: params.openSpaceId,
256
- };
257
-
258
- if (params.imGroupOpenDeliverModel) {
259
- body.imGroupOpenDeliverModel = params.imGroupOpenDeliverModel;
260
- }
261
-
262
- if (params.imRobotOpenDeliverModel) {
263
- body.imRobotOpenDeliverModel = params.imRobotOpenDeliverModel;
264
- }
265
-
266
- const res = await jsonPost(
267
- `${DINGTALK_API_BASE}/card/instances/deliver`,
268
- body,
269
- { "x-acs-dingtalk-access-token": token },
270
- );
271
-
272
- if (res.code || res.errcode) {
273
- return {
274
- success: false,
275
- errcode: res.errcode,
276
- errmsg: res.errmsg,
277
- code: res.code,
278
- message: res.message,
279
- };
280
- }
281
-
282
- return { success: true, result: res };
283
- } catch (err) {
284
- return {
285
- success: false,
286
- message: String(err),
287
- };
288
- }
289
- }
290
-
291
- /**
292
- * Update card data
293
- *
294
- * API: PUT /v1.0/card/instances
295
- */
296
- export async function updateCard(
297
- params: UpdateCardParams,
298
- ): Promise<CardApiResponse> {
299
- try {
300
- const token = await getDingTalkAccessToken(params.clientId, params.clientSecret);
301
-
302
- // cardData is an object with cardParamMap
303
- const body: Record<string, any> = {
304
- outTrackId: params.outTrackId,
305
- cardData: params.cardData,
306
- };
307
-
308
- if (params.userIdType !== undefined) {
309
- body.userIdType = params.userIdType;
310
- }
311
-
312
- const res = await jsonPut(
313
- `${DINGTALK_API_BASE}/card/instances`,
314
- body,
315
- { "x-acs-dingtalk-access-token": token },
316
- );
317
-
318
- if (res.code || res.errcode) {
319
- return {
320
- success: false,
321
- errcode: res.errcode,
322
- errmsg: res.errmsg,
323
- code: res.code,
324
- message: res.message,
325
- };
326
- }
327
-
328
- return { success: true, result: res };
329
- } catch (err) {
330
- return {
331
- success: false,
332
- message: String(err),
333
- };
334
- }
335
- }
336
-
337
- /**
338
- * Streaming update for AI card (typewriter effect)
339
- *
340
- * API: PUT /v1.0/card/streaming
341
- *
342
- * Note: Requires Card.Streaming.Write permission
343
- */
344
- export async function updateCardStreaming(
345
- params: StreamingUpdateParams,
346
- ): Promise<CardApiResponse> {
347
- try {
348
- const token = await getDingTalkAccessToken(params.clientId, params.clientSecret);
349
-
350
- const body: Record<string, any> = {
351
- outTrackId: params.outTrackId,
352
- key: params.key,
353
- content: params.content,
354
- isFull: params.isFull ?? false,
355
- };
356
-
357
- if (params.guid) {
358
- body.guid = params.guid;
359
- }
360
-
361
- const res = await jsonPut(
362
- `${DINGTALK_API_BASE}/card/streaming`,
363
- body,
364
- { "x-acs-dingtalk-access-token": token },
365
- );
366
-
367
- if (res.code || res.errcode) {
368
- return {
369
- success: false,
370
- errcode: res.errcode,
371
- errmsg: res.errmsg,
372
- code: res.code,
373
- message: res.message,
374
- };
375
- }
376
-
377
- return { success: true, result: res };
378
- } catch (err) {
379
- return {
380
- success: false,
381
- message: String(err),
382
- };
383
- }
384
- }
385
-
386
- /**
387
- * Build openSpaceId for different scenarios
388
- */
389
- export function buildOpenSpaceId(
390
- type: "IM_GROUP" | "IM_ROBOT",
391
- id: string,
392
- ): string {
393
- // Format: dtv1.card//IM_GROUP.{openConversationId}
394
- // or: dtv1.card//IM_ROBOT.{senderStaffId}
395
- return `dtv1.card//${type}.${id}`;
396
- }
397
-
398
- /**
399
- * Generate unique outTrackId
400
- */
401
- export function generateOutTrackId(): string {
402
- const timestamp = Date.now();
403
- const random = Math.random().toString(36).substring(2, 10);
404
- return `card_${timestamp}_${random}`;
405
- }
406
-
407
- /**
408
- * High-level: Send a card message to a conversation
409
- *
410
- * This combines createCardInstance + createCardSpace + deliverCard
411
- */
412
- export async function sendCardMessage(params: {
413
- clientId: string;
414
- clientSecret: string;
415
- robotCode: string;
416
- cardTemplateId: string;
417
- cardData: Record<string, string>;
418
- /** For group chat */
419
- openConversationId?: string;
420
- /** For single chat (DM) */
421
- senderStaffId?: string;
422
- }): Promise<CardApiResponse & { outTrackId?: string }> {
423
- const outTrackId = generateOutTrackId();
424
-
425
- // Determine space type and ID
426
- const isGroup = !!params.openConversationId;
427
- const spaceType = isGroup ? "IM_GROUP" : "IM_ROBOT";
428
- const spaceTargetId = isGroup ? params.openConversationId! : params.senderStaffId!;
429
- const openSpaceId = buildOpenSpaceId(spaceType, spaceTargetId);
430
-
431
- // Step 1: Create card instance
432
- // Note: cardData should be flat object, not wrapped in cardParamMap
433
- const createResult = await createCardInstance({
434
- clientId: params.clientId,
435
- clientSecret: params.clientSecret,
436
- cardTemplateId: params.cardTemplateId,
437
- outTrackId,
438
- cardData: params.cardData,
439
- robotCode: params.robotCode,
440
- // Note: callbackType not needed for basic cards
441
- });
442
-
443
- if (!createResult.success) {
444
- console.warn("[dingtalk-card] Failed to create card instance:", createResult);
445
- return createResult;
446
- }
447
-
448
- // Step 2: Add space
449
- const spaceResult = await createCardSpace(
450
- params.clientId,
451
- params.clientSecret,
452
- outTrackId,
453
- openSpaceId,
454
- );
455
-
456
- if (!spaceResult.success) {
457
- console.warn("[dingtalk-card] Failed to create card space:", spaceResult);
458
- return spaceResult;
459
- }
460
-
461
- // Step 3: Deliver card
462
- const deliverParams: DeliverCardParams = {
463
- clientId: params.clientId,
464
- clientSecret: params.clientSecret,
465
- outTrackId,
466
- openSpaceId,
467
- };
468
-
469
- if (isGroup) {
470
- deliverParams.imGroupOpenDeliverModel = {
471
- robotCode: params.robotCode,
472
- };
473
- } else {
474
- deliverParams.imRobotOpenDeliverModel = {
475
- spaceType: "IM_ROBOT",
476
- robotCode: params.robotCode,
477
- };
478
- }
479
-
480
- const deliverResult = await deliverCard(deliverParams);
481
-
482
- if (!deliverResult.success) {
483
- console.warn("[dingtalk-card] Failed to deliver card:", deliverResult);
484
- return deliverResult;
485
- }
486
-
487
- return { success: true, outTrackId, result: deliverResult.result };
488
- }
489
-
490
- /**
491
- * High-level: Send an AI card with streaming support
492
- *
493
- * Returns a controller object for streaming updates
494
- */
495
- export async function sendStreamingAICard(params: {
496
- clientId: string;
497
- clientSecret: string;
498
- robotCode: string;
499
- cardTemplateId: string;
500
- /** Initial card data (use flowStatus="0" for loading state) */
501
- initialData?: Record<string, string>;
502
- /** Key for the streaming content variable in the template */
503
- streamingKey?: string;
504
- openConversationId?: string;
505
- senderStaffId?: string;
506
- }): Promise<{
507
- success: boolean;
508
- outTrackId?: string;
509
- error?: string;
510
- /** Update streaming content */
511
- update: (content: string) => Promise<CardApiResponse>;
512
- /** Mark streaming as complete */
513
- finish: (finalContent: string) => Promise<CardApiResponse>;
514
- }> {
515
- const streamingKey = params.streamingKey || "content";
516
-
517
- // Send initial card with loading state
518
- const initialCardData: Record<string, string> = {
519
- flowStatus: "0", // 0 = streaming in progress
520
- ...params.initialData,
521
- };
522
-
523
- const sendResult = await sendCardMessage({
524
- clientId: params.clientId,
525
- clientSecret: params.clientSecret,
526
- robotCode: params.robotCode,
527
- cardTemplateId: params.cardTemplateId,
528
- cardData: initialCardData,
529
- openConversationId: params.openConversationId,
530
- senderStaffId: params.senderStaffId,
531
- });
532
-
533
- if (!sendResult.success || !sendResult.outTrackId) {
534
- return {
535
- success: false,
536
- error: sendResult.message || sendResult.errmsg || "Failed to send card",
537
- update: async () => ({ success: false, message: "Card not initialized" }),
538
- finish: async () => ({ success: false, message: "Card not initialized" }),
539
- };
540
- }
541
-
542
- const outTrackId = sendResult.outTrackId;
543
-
544
- // Return controller
545
- return {
546
- success: true,
547
- outTrackId,
548
- update: async (content: string) => {
549
- return updateCardStreaming({
550
- clientId: params.clientId,
551
- clientSecret: params.clientSecret,
552
- outTrackId,
553
- key: streamingKey,
554
- content,
555
- isFull: false,
556
- });
557
- },
558
- finish: async (finalContent: string) => {
559
- // First update with final content
560
- await updateCardStreaming({
561
- clientId: params.clientId,
562
- clientSecret: params.clientSecret,
563
- outTrackId,
564
- key: streamingKey,
565
- content: finalContent,
566
- isFull: true,
567
- });
568
-
569
- // Then update flowStatus to complete
570
- return updateCard({
571
- clientId: params.clientId,
572
- clientSecret: params.clientSecret,
573
- outTrackId,
574
- cardData: {
575
- cardParamMap: { flowStatus: "1" }, // 1 = complete
576
- },
577
- });
578
- },
579
- };
580
- }