@xinghunm/ai-chat 1.0.0 → 1.0.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/dist/index.d.mts CHANGED
@@ -168,6 +168,31 @@ interface ResultSummary {
168
168
  headline: string;
169
169
  details: string[];
170
170
  }
171
+ /**
172
+ * Merge behavior applied when a keyed custom block is patched repeatedly.
173
+ */
174
+ type ChatCustomBlockMergePolicy = 'append' | 'replace' | 'ignore-duplicate';
175
+ /**
176
+ * Extensible custom block rendered by a consumer-provided block renderer.
177
+ */
178
+ interface ChatCustomBlock {
179
+ type: 'custom';
180
+ kind: string;
181
+ data: unknown;
182
+ /**
183
+ * Stable key used to identify the same logical block across streaming patches.
184
+ * When present, keyed custom blocks are merged by `blockKey` regardless of `kind`.
185
+ */
186
+ blockKey?: string;
187
+ /**
188
+ * Merge strategy used when another custom block with the same key arrives.
189
+ *
190
+ * - `append`: keep appending blocks even if the key matches.
191
+ * - `replace`: replace the existing keyed block with the latest one.
192
+ * - `ignore-duplicate`: keep the first keyed block and ignore later duplicates.
193
+ */
194
+ mergePolicy?: ChatCustomBlockMergePolicy;
195
+ }
171
196
  /**
172
197
  * Structured assistant block variants rendered by the chat thread.
173
198
  */
@@ -190,11 +215,7 @@ type ChatMessageBlock = {
190
215
  } | {
191
216
  type: 'questionnaire';
192
217
  questionnaire: PlanQuestionnaire;
193
- } | {
194
- type: 'custom';
195
- kind: string;
196
- data: unknown;
197
- };
218
+ } | ChatCustomBlock;
198
219
  /**
199
220
  * Supported message body render orders for mixed text and structured blocks.
200
221
  */
package/dist/index.d.ts CHANGED
@@ -168,6 +168,31 @@ interface ResultSummary {
168
168
  headline: string;
169
169
  details: string[];
170
170
  }
171
+ /**
172
+ * Merge behavior applied when a keyed custom block is patched repeatedly.
173
+ */
174
+ type ChatCustomBlockMergePolicy = 'append' | 'replace' | 'ignore-duplicate';
175
+ /**
176
+ * Extensible custom block rendered by a consumer-provided block renderer.
177
+ */
178
+ interface ChatCustomBlock {
179
+ type: 'custom';
180
+ kind: string;
181
+ data: unknown;
182
+ /**
183
+ * Stable key used to identify the same logical block across streaming patches.
184
+ * When present, keyed custom blocks are merged by `blockKey` regardless of `kind`.
185
+ */
186
+ blockKey?: string;
187
+ /**
188
+ * Merge strategy used when another custom block with the same key arrives.
189
+ *
190
+ * - `append`: keep appending blocks even if the key matches.
191
+ * - `replace`: replace the existing keyed block with the latest one.
192
+ * - `ignore-duplicate`: keep the first keyed block and ignore later duplicates.
193
+ */
194
+ mergePolicy?: ChatCustomBlockMergePolicy;
195
+ }
171
196
  /**
172
197
  * Structured assistant block variants rendered by the chat thread.
173
198
  */
@@ -190,11 +215,7 @@ type ChatMessageBlock = {
190
215
  } | {
191
216
  type: 'questionnaire';
192
217
  questionnaire: PlanQuestionnaire;
193
- } | {
194
- type: 'custom';
195
- kind: string;
196
- data: unknown;
197
- };
218
+ } | ChatCustomBlock;
198
219
  /**
199
220
  * Supported message body render orders for mixed text and structured blocks.
200
221
  */
package/dist/index.js CHANGED
@@ -108,6 +108,22 @@ var resolveSessionTitleFromMessage = (message) => {
108
108
  var mergeStreamingBlocks = (existingBlocks, incomingBlocks) => {
109
109
  const nextBlocks = [...existingBlocks ?? []];
110
110
  incomingBlocks.forEach((incomingBlock) => {
111
+ if (incomingBlock.type === "custom" && incomingBlock.blockKey) {
112
+ const mergePolicy = incomingBlock.mergePolicy ?? "append";
113
+ if (mergePolicy !== "append") {
114
+ const existingIndex2 = nextBlocks.findIndex(
115
+ (block) => block.type === "custom" && block.blockKey === incomingBlock.blockKey
116
+ );
117
+ if (existingIndex2 !== -1) {
118
+ if (mergePolicy === "replace") {
119
+ nextBlocks[existingIndex2] = incomingBlock;
120
+ }
121
+ return;
122
+ }
123
+ }
124
+ nextBlocks.push(incomingBlock);
125
+ return;
126
+ }
111
127
  if (incomingBlock.type !== "questionnaire") {
112
128
  nextBlocks.push(incomingBlock);
113
129
  return;
@@ -1010,7 +1026,7 @@ var getTimelineBlockKey = (block, index) => {
1010
1026
  case "questionnaire":
1011
1027
  return `${index}:questionnaire:${block.questionnaire.questionnaireId}`;
1012
1028
  case "custom":
1013
- return `${index}:custom:${block.kind}:${stringifyTimelineKeyPart(block.data)}`;
1029
+ return block.blockKey ? `custom:${block.blockKey}` : `${index}:custom:${block.kind}:${stringifyTimelineKeyPart(block.data)}`;
1014
1030
  default:
1015
1031
  return null;
1016
1032
  }
package/dist/index.mjs CHANGED
@@ -61,6 +61,22 @@ var resolveSessionTitleFromMessage = (message) => {
61
61
  var mergeStreamingBlocks = (existingBlocks, incomingBlocks) => {
62
62
  const nextBlocks = [...existingBlocks ?? []];
63
63
  incomingBlocks.forEach((incomingBlock) => {
64
+ if (incomingBlock.type === "custom" && incomingBlock.blockKey) {
65
+ const mergePolicy = incomingBlock.mergePolicy ?? "append";
66
+ if (mergePolicy !== "append") {
67
+ const existingIndex2 = nextBlocks.findIndex(
68
+ (block) => block.type === "custom" && block.blockKey === incomingBlock.blockKey
69
+ );
70
+ if (existingIndex2 !== -1) {
71
+ if (mergePolicy === "replace") {
72
+ nextBlocks[existingIndex2] = incomingBlock;
73
+ }
74
+ return;
75
+ }
76
+ }
77
+ nextBlocks.push(incomingBlock);
78
+ return;
79
+ }
64
80
  if (incomingBlock.type !== "questionnaire") {
65
81
  nextBlocks.push(incomingBlock);
66
82
  return;
@@ -963,7 +979,7 @@ var getTimelineBlockKey = (block, index) => {
963
979
  case "questionnaire":
964
980
  return `${index}:questionnaire:${block.questionnaire.questionnaireId}`;
965
981
  case "custom":
966
- return `${index}:custom:${block.kind}:${stringifyTimelineKeyPart(block.data)}`;
982
+ return block.blockKey ? `custom:${block.blockKey}` : `${index}:custom:${block.kind}:${stringifyTimelineKeyPart(block.data)}`;
967
983
  default:
968
984
  return null;
969
985
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xinghunm/ai-chat",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "AI chat React component library",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",