@coral-ai/chat 0.4.2 → 0.5.0
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.ts +33 -6
- package/dist/index.js +82 -49
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.d.ts
CHANGED
|
@@ -44,18 +44,29 @@ type ChatMessage = {
|
|
|
44
44
|
approval?: ChatApprovalRequest;
|
|
45
45
|
/**
|
|
46
46
|
* If present, this assistant message carries a multi-step task plan.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
47
|
+
* Legacy field — new messages use `blocks` to position the todo list
|
|
48
|
+
* inline at the stream point where it first appeared. Still read by the
|
|
49
|
+
* renderer as a fallback for persisted messages saved before blocks.
|
|
49
50
|
*/
|
|
50
51
|
todos?: ChatTodo[];
|
|
51
52
|
/**
|
|
52
53
|
* If present, this assistant message carries one or more chart specs.
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* the visualizations survive reloads.
|
|
54
|
+
* Legacy field — new messages use `blocks` to position each chart at
|
|
55
|
+
* the stream point where it was emitted. Still read by the renderer
|
|
56
|
+
* as a fallback for persisted messages saved before blocks.
|
|
57
57
|
*/
|
|
58
58
|
charts?: CoralChartSpec[];
|
|
59
|
+
/**
|
|
60
|
+
* Ordered list of inline blocks (text, charts, todos) in the order the
|
|
61
|
+
* agent emitted them. Renderers iterate this in stream order so a chart
|
|
62
|
+
* tool call appears at the exact point in the prose where it happened,
|
|
63
|
+
* matching the convention used by Claude.ai and ChatGPT.
|
|
64
|
+
*
|
|
65
|
+
* When absent, the renderer synthesizes blocks from the legacy
|
|
66
|
+
* `content` / `charts` / `todos` fields so old persisted conversations
|
|
67
|
+
* still display correctly.
|
|
68
|
+
*/
|
|
69
|
+
blocks?: ChatBlock[];
|
|
59
70
|
/**
|
|
60
71
|
* If present, the turn that produced this assistant message failed. The
|
|
61
72
|
* message renders as an error card (with a Retry button) instead of normal
|
|
@@ -97,6 +108,22 @@ type ChatApprovalRequest = {
|
|
|
97
108
|
text?: string;
|
|
98
109
|
};
|
|
99
110
|
};
|
|
111
|
+
/**
|
|
112
|
+
* One inline block within an assistant message. Blocks are rendered in the
|
|
113
|
+
* order the agent emitted them, so charts and todo lists appear at the exact
|
|
114
|
+
* stream position where they happened (e.g. text → chart → text), rather than
|
|
115
|
+
* being pinned above the message.
|
|
116
|
+
*/
|
|
117
|
+
type ChatBlock = {
|
|
118
|
+
type: "text";
|
|
119
|
+
content: string;
|
|
120
|
+
} | {
|
|
121
|
+
type: "chart";
|
|
122
|
+
spec: CoralChartSpec;
|
|
123
|
+
} | {
|
|
124
|
+
type: "todos";
|
|
125
|
+
todos: ChatTodo[];
|
|
126
|
+
};
|
|
100
127
|
/**
|
|
101
128
|
* One step in a multi-step task plan. The agent emits the whole list on each
|
|
102
129
|
* update; the convention is exactly one item `in_progress` at a time.
|
package/dist/index.js
CHANGED
|
@@ -424,32 +424,54 @@ var ChatCanvas = ({
|
|
|
424
424
|
const response = await resolvedHandler(textToSend, historyForAI, snapshotAttachments.map((a) => a.file));
|
|
425
425
|
if (isAsyncIterable(response)) {
|
|
426
426
|
let assistantContent = "";
|
|
427
|
+
const blocks = [];
|
|
428
|
+
let openText = "";
|
|
429
|
+
let todosIndex = null;
|
|
427
430
|
let rafId = null;
|
|
428
|
-
let pendingContent = "";
|
|
429
431
|
let streamedApproval;
|
|
430
|
-
|
|
431
|
-
|
|
432
|
+
const snapshotBlocks = () => openText ? [...blocks, { type: "text", content: openText }] : blocks;
|
|
433
|
+
const flushOpenText = () => {
|
|
434
|
+
if (openText) {
|
|
435
|
+
blocks.push({ type: "text", content: openText });
|
|
436
|
+
openText = "";
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
const commit = () => {
|
|
440
|
+
const nextBlocks = snapshotBlocks();
|
|
441
|
+
let nextMsg = {
|
|
442
|
+
id: assistantId,
|
|
443
|
+
role: "assistant",
|
|
444
|
+
content: assistantContent,
|
|
445
|
+
blocks: nextBlocks
|
|
446
|
+
};
|
|
447
|
+
setMessages((prev) => {
|
|
448
|
+
const updated = [...prev];
|
|
449
|
+
const last = updated[updated.length - 1];
|
|
450
|
+
if (last && last.id === assistantId) {
|
|
451
|
+
nextMsg = {
|
|
452
|
+
...last,
|
|
453
|
+
content: assistantContent,
|
|
454
|
+
blocks: nextBlocks
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
updated[updated.length - 1] = nextMsg;
|
|
458
|
+
return updated;
|
|
459
|
+
});
|
|
460
|
+
onMessagesChange?.(
|
|
461
|
+
[...updatedMessages, nextMsg],
|
|
462
|
+
sessionAtSend
|
|
463
|
+
);
|
|
464
|
+
};
|
|
432
465
|
for await (const chunk of response) {
|
|
433
466
|
if (shouldStopRef.current) break;
|
|
434
467
|
if (sessionAtSend !== void 0 && currentConversationIdRef.current !== sessionAtSend) break;
|
|
435
468
|
if (typeof chunk === "string") {
|
|
436
469
|
assistantContent += chunk;
|
|
437
|
-
|
|
470
|
+
openText += chunk;
|
|
438
471
|
if (rafId === null) {
|
|
439
472
|
rafId = requestAnimationFrame(() => {
|
|
440
|
-
const content = pendingContent;
|
|
441
|
-
let nextMsg = { id: assistantId, role: "assistant", content };
|
|
442
|
-
setMessages((prev) => {
|
|
443
|
-
const updated = [...prev];
|
|
444
|
-
const last = updated[updated.length - 1];
|
|
445
|
-
if (last && last.id === assistantId) {
|
|
446
|
-
nextMsg = { ...last, content };
|
|
447
|
-
}
|
|
448
|
-
updated[updated.length - 1] = nextMsg;
|
|
449
|
-
return updated;
|
|
450
|
-
});
|
|
451
|
-
onMessagesChange?.([...updatedMessages, nextMsg], sessionAtSend);
|
|
452
473
|
rafId = null;
|
|
474
|
+
commit();
|
|
453
475
|
});
|
|
454
476
|
}
|
|
455
477
|
} else if (chunk.type === "reasoning") {
|
|
@@ -468,26 +490,18 @@ var ChatCanvas = ({
|
|
|
468
490
|
return updated;
|
|
469
491
|
});
|
|
470
492
|
} else if (chunk.type === "todos") {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
});
|
|
493
|
+
if (todosIndex === null) {
|
|
494
|
+
flushOpenText();
|
|
495
|
+
todosIndex = blocks.length;
|
|
496
|
+
blocks.push({ type: "todos", todos: chunk.todos });
|
|
497
|
+
} else {
|
|
498
|
+
blocks[todosIndex] = { type: "todos", todos: chunk.todos };
|
|
499
|
+
}
|
|
500
|
+
commit();
|
|
480
501
|
} else if (chunk.type === "chart") {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
const updated = [...prev];
|
|
485
|
-
const last = updated[updated.length - 1];
|
|
486
|
-
if (last && last.id === assistantId) {
|
|
487
|
-
updated[updated.length - 1] = { ...last, charts: nextCharts };
|
|
488
|
-
}
|
|
489
|
-
return updated;
|
|
490
|
-
});
|
|
502
|
+
flushOpenText();
|
|
503
|
+
blocks.push({ type: "chart", spec: chunk.spec });
|
|
504
|
+
commit();
|
|
491
505
|
}
|
|
492
506
|
}
|
|
493
507
|
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
@@ -495,14 +509,14 @@ var ChatCanvas = ({
|
|
|
495
509
|
if (sessionAtSend !== void 0 && currentConversationIdRef.current !== sessionAtSend) {
|
|
496
510
|
break;
|
|
497
511
|
}
|
|
512
|
+
flushOpenText();
|
|
498
513
|
const finalMsg = {
|
|
499
514
|
id: assistantId,
|
|
500
515
|
role: "assistant",
|
|
501
516
|
content: assistantContent,
|
|
517
|
+
blocks,
|
|
502
518
|
...reasoningRef.current ? { metadata: { reasoning: reasoningRef.current } } : {},
|
|
503
|
-
...streamedApproval ? { approval: streamedApproval } : {}
|
|
504
|
-
...streamedTodos ? { todos: streamedTodos } : {},
|
|
505
|
-
...streamedCharts ? { charts: streamedCharts } : {}
|
|
519
|
+
...streamedApproval ? { approval: streamedApproval } : {}
|
|
506
520
|
};
|
|
507
521
|
const finalMessages = [...updatedMessages, finalMsg];
|
|
508
522
|
setMessages(finalMessages);
|
|
@@ -1215,6 +1229,18 @@ var Chat_TodoList_default = ChatTodoList;
|
|
|
1215
1229
|
import { CoralChart } from "@coral-ai/charts";
|
|
1216
1230
|
import { CheckmarkCircle20Filled } from "@fluentui/react-icons";
|
|
1217
1231
|
import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1232
|
+
function resolveBlocks(msg) {
|
|
1233
|
+
if (msg.blocks) return msg.blocks;
|
|
1234
|
+
const legacy = [];
|
|
1235
|
+
if (msg.todos && msg.todos.length > 0) {
|
|
1236
|
+
legacy.push({ type: "todos", todos: msg.todos });
|
|
1237
|
+
}
|
|
1238
|
+
if (msg.charts) {
|
|
1239
|
+
for (const spec of msg.charts) legacy.push({ type: "chart", spec });
|
|
1240
|
+
}
|
|
1241
|
+
if (msg.content) legacy.push({ type: "text", content: msg.content });
|
|
1242
|
+
return legacy;
|
|
1243
|
+
}
|
|
1218
1244
|
var useStyles7 = makeStyles7({
|
|
1219
1245
|
messages: {
|
|
1220
1246
|
flex: "1",
|
|
@@ -1272,7 +1298,7 @@ var useStyles7 = makeStyles7({
|
|
|
1272
1298
|
display: "flex",
|
|
1273
1299
|
flexDirection: "column",
|
|
1274
1300
|
alignItems: "stretch",
|
|
1275
|
-
width: "min(640px, 80vw)",
|
|
1301
|
+
// width: "min(640px, 80vw)",
|
|
1276
1302
|
gap: "8px"
|
|
1277
1303
|
},
|
|
1278
1304
|
userEditTextarea: {
|
|
@@ -1578,17 +1604,24 @@ var ChatMessages = ({
|
|
|
1578
1604
|
isStreaming: isLatest && isTyping && !msg.content
|
|
1579
1605
|
}
|
|
1580
1606
|
),
|
|
1581
|
-
msg.
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
CoralMarkdown,
|
|
1585
|
-
{
|
|
1586
|
-
onNavigate,
|
|
1587
|
-
fontSize,
|
|
1588
|
-
fontFamily,
|
|
1589
|
-
children: msg.content
|
|
1607
|
+
resolveBlocks(msg).map((block, i) => {
|
|
1608
|
+
if (block.type === "todos") {
|
|
1609
|
+
return /* @__PURE__ */ jsx7(Chat_TodoList_default, { todos: block.todos }, `todos-${i}`);
|
|
1590
1610
|
}
|
|
1591
|
-
|
|
1611
|
+
if (block.type === "chart") {
|
|
1612
|
+
return /* @__PURE__ */ jsx7(CoralChart, { spec: block.spec }, `chart-${i}`);
|
|
1613
|
+
}
|
|
1614
|
+
return /* @__PURE__ */ jsx7(
|
|
1615
|
+
CoralMarkdown,
|
|
1616
|
+
{
|
|
1617
|
+
onNavigate,
|
|
1618
|
+
fontSize,
|
|
1619
|
+
fontFamily,
|
|
1620
|
+
children: block.content
|
|
1621
|
+
},
|
|
1622
|
+
`text-${i}`
|
|
1623
|
+
);
|
|
1624
|
+
}),
|
|
1592
1625
|
msg.error && /* @__PURE__ */ jsx7(
|
|
1593
1626
|
Chat_Error_default,
|
|
1594
1627
|
{
|