@devicai/ui 0.55.0 → 0.57.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.
Files changed (33) hide show
  1. package/README.md +50 -0
  2. package/dist/cjs/api/types.js.map +1 -1
  3. package/dist/cjs/components/ChatDrawer/ChatDrawer.js +4 -1
  4. package/dist/cjs/components/ChatDrawer/ChatDrawer.js.map +1 -1
  5. package/dist/cjs/components/ChatDrawer/ChatMessages.js +41 -4
  6. package/dist/cjs/components/ChatDrawer/ChatMessages.js.map +1 -1
  7. package/dist/cjs/components/ChatDrawer/CompactionWidget.js +80 -0
  8. package/dist/cjs/components/ChatDrawer/CompactionWidget.js.map +1 -0
  9. package/dist/cjs/hooks/useDevicChat.js +32 -1
  10. package/dist/cjs/hooks/useDevicChat.js.map +1 -1
  11. package/dist/cjs/index.js +2 -0
  12. package/dist/cjs/index.js.map +1 -1
  13. package/dist/cjs/styles.css +1 -1
  14. package/dist/esm/api/types.d.ts +83 -0
  15. package/dist/esm/api/types.js.map +1 -1
  16. package/dist/esm/components/ChatDrawer/ChatDrawer.js +4 -1
  17. package/dist/esm/components/ChatDrawer/ChatDrawer.js.map +1 -1
  18. package/dist/esm/components/ChatDrawer/ChatDrawer.types.d.ts +51 -1
  19. package/dist/esm/components/ChatDrawer/ChatMessages.d.ts +1 -1
  20. package/dist/esm/components/ChatDrawer/ChatMessages.js +41 -4
  21. package/dist/esm/components/ChatDrawer/ChatMessages.js.map +1 -1
  22. package/dist/esm/components/ChatDrawer/CompactionWidget.d.ts +63 -0
  23. package/dist/esm/components/ChatDrawer/CompactionWidget.js +78 -0
  24. package/dist/esm/components/ChatDrawer/CompactionWidget.js.map +1 -0
  25. package/dist/esm/components/ChatDrawer/index.d.ts +2 -0
  26. package/dist/esm/hooks/useDevicChat.d.ts +13 -1
  27. package/dist/esm/hooks/useDevicChat.js +32 -1
  28. package/dist/esm/hooks/useDevicChat.js.map +1 -1
  29. package/dist/esm/index.d.ts +3 -3
  30. package/dist/esm/index.js +1 -0
  31. package/dist/esm/index.js.map +1 -1
  32. package/dist/esm/styles.css +1 -1
  33. package/package.json +1 -1
@@ -0,0 +1,78 @@
1
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
+ import { useState } from 'react';
3
+
4
+ function formatTokens(value) {
5
+ if (!value)
6
+ return "0";
7
+ if (value < 1000)
8
+ return String(value);
9
+ if (value < 1000000)
10
+ return `${(value / 1000).toFixed(1)}k`;
11
+ return `${(value / 1000000).toFixed(2)}M`;
12
+ }
13
+ function CompressIcon() {
14
+ return (jsxs("svg", { className: "devic-compaction-icon", width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [jsx("polyline", { points: "4 14 10 14 10 20" }), jsx("polyline", { points: "20 10 14 10 14 4" }), jsx("line", { x1: "14", y1: "10", x2: "21", y2: "3" }), jsx("line", { x1: "3", y1: "21", x2: "10", y2: "14" })] }));
15
+ }
16
+ function ChevronIcon({ up }) {
17
+ return (jsx("svg", { className: "devic-compaction-chevron", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", style: { transform: up ? "rotate(180deg)" : undefined }, children: jsx("polyline", { points: "6 9 12 15 18 9" }) }));
18
+ }
19
+ function Section({ title, children, }) {
20
+ return (jsxs("div", { className: "devic-compaction-section", children: [jsx("span", { className: "devic-compaction-section-title", children: title }), children] }));
21
+ }
22
+ function Bullets({ items }) {
23
+ if (!items?.length)
24
+ return null;
25
+ return (jsx("ul", { className: "devic-compaction-list", children: items.map((item, i) => (jsx("li", { children: item }, i))) }));
26
+ }
27
+ function hasStructure(summary) {
28
+ return Boolean(summary.goal ||
29
+ summary.constraints?.length ||
30
+ summary.inProgress ||
31
+ summary.pending?.length ||
32
+ summary.decisions?.length ||
33
+ summary.data?.length ||
34
+ summary.done?.length ||
35
+ summary.openQuestions?.length);
36
+ }
37
+ /**
38
+ * The cut line in the conversation.
39
+ *
40
+ * Anchored to the last message a checkpoint folded, it marks the exact point
41
+ * where the assistant's view of the conversation stops: everything above it
42
+ * is still here, still readable, and no longer sent. It is a rule across the
43
+ * thread with the headline numbers, and — where the host opts in with
44
+ * `expandable` — it opens to show what the assistant reads in place of those
45
+ * messages.
46
+ *
47
+ * With no checkpoint yet and a compaction in flight it says so instead — that
48
+ * is a model call of its own, and without it the conversation just appears to
49
+ * have gone quiet.
50
+ *
51
+ * Deliberately not a message bubble: a checkpoint is not something anyone
52
+ * said.
53
+ */
54
+ function CompactionWidget({ checkpoint, isActive = true, activity = null, renderer, expandable = false, }) {
55
+ const [expanded, setExpanded] = useState(false);
56
+ if (renderer) {
57
+ return jsx(Fragment, { children: renderer({ checkpoint, isActive, activity }) });
58
+ }
59
+ const running = activity?.state === "running";
60
+ if (!checkpoint) {
61
+ if (!running)
62
+ return null;
63
+ return (jsxs("div", { className: "devic-compaction devic-compaction-running", children: [jsx("span", { className: "devic-compaction-rule" }), jsxs("span", { className: "devic-compaction-pill", children: [jsx(CompressIcon, {}), jsx("span", { className: "devic-compaction-title", children: "Compacting context" }), jsxs("span", { className: "devic-compaction-meta", children: [activity.messageCount, " message", activity.messageCount === 1 ? "" : "s", " \u00B7", " ", formatTokens(activity.tokensBefore), " tokens"] }), jsxs("span", { className: "devic-compaction-dots", "aria-hidden": "true", children: [jsx("i", {}), jsx("i", {}), jsx("i", {})] })] }), jsx("span", { className: "devic-compaction-rule" })] }));
64
+ }
65
+ const summary = checkpoint.summary || {};
66
+ const structured = hasStructure(summary);
67
+ const tooltip = `${checkpoint.compactedMessageCount} message${checkpoint.compactedMessageCount === 1 ? "" : "s"} are still in this conversation but no longer sent to the assistant`;
68
+ //The headline, identical either way: whether the checkpoint can be opened
69
+ //changes what the reader may inspect, never what the marker claims.
70
+ const headline = (jsxs(Fragment, { children: [jsx(CompressIcon, {}), jsxs("span", { className: "devic-compaction-title", children: ["Context compacted", checkpoint.index > 1 ? ` (#${checkpoint.index})` : ""] }), jsxs("span", { className: "devic-compaction-meta", children: [checkpoint.compactedMessageCount, " messages \u00B7", " ", formatTokens(checkpoint.tokensBefore), " \u2192", " ", formatTokens(checkpoint.tokensAfter), " tokens"] }), !isActive && (jsx("span", { className: "devic-compaction-tag", title: "Superseded by a later compaction, which merged this summary into itself. Kept for the record.", children: "superseded" }))] }));
71
+ return (jsxs("div", { className: "devic-compaction", children: [jsxs("div", { className: "devic-compaction-head", children: [jsx("span", { className: "devic-compaction-rule" }), expandable ? (jsxs("button", { type: "button", className: "devic-compaction-pill", onClick: () => setExpanded((v) => !v), "aria-expanded": expanded, title: tooltip, children: [headline, jsx(ChevronIcon, { up: expanded })] })) : (
72
+ //A span, not a disabled button: there is nothing here to operate, so
73
+ //it should not reach the keyboard or announce itself as a control.
74
+ jsx("span", { className: "devic-compaction-pill devic-compaction-pill-static", title: tooltip, children: headline })), jsx("span", { className: "devic-compaction-rule" })] }), expandable && expanded && (jsxs("div", { className: "devic-compaction-body", children: [jsx("div", { className: "devic-compaction-note", children: "This is what the assistant reads in place of the messages above. The messages themselves are still here." }), structured && (jsxs(Fragment, { children: [summary.goal && jsx(Section, { title: "Goal", children: summary.goal }), !!summary.constraints?.length && (jsx(Section, { title: "Constraints", children: jsx(Bullets, { items: summary.constraints }) })), summary.inProgress && (jsx(Section, { title: "In progress", children: summary.inProgress })), !!summary.pending?.length && (jsx(Section, { title: "Pending", children: jsx(Bullets, { items: summary.pending }) })), !!summary.decisions?.length && (jsx(Section, { title: "Decisions", children: jsx(Bullets, { items: summary.decisions }) })), !!summary.data?.length && (jsx(Section, { title: "Key data", children: jsx(Bullets, { items: summary.data.map((entry) => entry.label ? `${entry.label}: ${entry.value}` : entry.value) }) })), !!summary.done?.length && (jsx(Section, { title: "Done", children: jsx(Bullets, { items: summary.done }) })), !!summary.openQuestions?.length && (jsx(Section, { title: "Open questions", children: jsx(Bullets, { items: summary.openQuestions }) }))] })), summary.raw && (jsx(Section, { title: structured ? "Notes" : "Summary", children: jsx("div", { className: "devic-compaction-raw", children: summary.raw }) })), !!checkpoint.facts?.length && (jsx(Section, { title: "Preserved exactly", children: jsx("div", { className: "devic-compaction-facts", children: checkpoint.facts.map((fact, i) => (jsxs("span", { className: "devic-compaction-fact", children: [fact.label ? (jsxs("span", { className: "devic-compaction-fact-label", children: [fact.label, ":", " "] })) : null, fact.value] }, i))) }) }))] }))] }));
75
+ }
76
+
77
+ export { CompactionWidget };
78
+ //# sourceMappingURL=CompactionWidget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CompactionWidget.js","sources":["../../../../src/components/ChatDrawer/CompactionWidget.tsx"],"sourcesContent":["import { useState, type JSX, type ReactNode } from \"react\";\nimport type {\n CompactionActivity,\n CompactionCheckpoint,\n CompactionSummary,\n} from \"../../api/types\";\n\n/** Data handed to a custom compaction renderer. */\nexport interface CompactionRendererProps {\n /**\n * The checkpoint at this point of the conversation. Null while a compaction\n * is being written and there is nothing to show yet.\n */\n checkpoint: CompactionCheckpoint | null;\n /**\n * True for the checkpoint that currently governs the context. Each\n * compaction merges the previous summary into itself, so the earlier ones\n * are a record rather than something still being sent.\n */\n isActive: boolean;\n /**\n * The compaction happening right now, or null. Present without a checkpoint\n * means one is being written; the numbers say what it is folding.\n */\n activity: CompactionActivity | null;\n}\n\n/** Replaces the built-in compaction marker. Return null to hide it. */\nexport type CompactionRenderer = (props: CompactionRendererProps) => ReactNode;\n\nfunction formatTokens(value: number): string {\n if (!value) return \"0\";\n if (value < 1000) return String(value);\n if (value < 1_000_000) return `${(value / 1000).toFixed(1)}k`;\n return `${(value / 1_000_000).toFixed(2)}M`;\n}\n\nfunction CompressIcon(): JSX.Element {\n return (\n <svg\n className=\"devic-compaction-icon\"\n width=\"13\"\n height=\"13\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <polyline points=\"4 14 10 14 10 20\" />\n <polyline points=\"20 10 14 10 14 4\" />\n <line x1=\"14\" y1=\"10\" x2=\"21\" y2=\"3\" />\n <line x1=\"3\" y1=\"21\" x2=\"10\" y2=\"14\" />\n </svg>\n );\n}\n\nfunction ChevronIcon({ up }: { up: boolean }): JSX.Element {\n return (\n <svg\n className=\"devic-compaction-chevron\"\n width=\"12\"\n height=\"12\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n style={{ transform: up ? \"rotate(180deg)\" : undefined }}\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n );\n}\n\nfunction Section({\n title,\n children,\n}: {\n title: string;\n children: ReactNode;\n}): JSX.Element {\n return (\n <div className=\"devic-compaction-section\">\n <span className=\"devic-compaction-section-title\">{title}</span>\n {children}\n </div>\n );\n}\n\nfunction Bullets({ items }: { items?: string[] }): JSX.Element | null {\n if (!items?.length) return null;\n return (\n <ul className=\"devic-compaction-list\">\n {items.map((item, i) => (\n <li key={i}>{item}</li>\n ))}\n </ul>\n );\n}\n\nfunction hasStructure(summary: CompactionSummary): boolean {\n return Boolean(\n summary.goal ||\n summary.constraints?.length ||\n summary.inProgress ||\n summary.pending?.length ||\n summary.decisions?.length ||\n summary.data?.length ||\n summary.done?.length ||\n summary.openQuestions?.length\n );\n}\n\nexport interface CompactionWidgetProps {\n checkpoint: CompactionCheckpoint | null;\n isActive?: boolean;\n activity?: CompactionActivity | null;\n renderer?: CompactionRenderer;\n /**\n * Lets the reader open the checkpoint and read what it says.\n *\n * Off by default, because what opens is not metadata: it is a model-written\n * account of the conversation — its goal, the decisions taken and why, what\n * is still pending, and the identifiers lifted out of it verbatim. That is\n * the right thing to put in front of an operator and the wrong thing to put\n * in front of the customer the conversation is with, so the closed form is\n * the default and opening it is a decision the host makes.\n *\n * Collapsed it still says a compaction happened and how much it folded:\n * hiding that would misrepresent the conversation, which is the opposite of\n * the point.\n */\n expandable?: boolean;\n}\n\n/**\n * The cut line in the conversation.\n *\n * Anchored to the last message a checkpoint folded, it marks the exact point\n * where the assistant's view of the conversation stops: everything above it\n * is still here, still readable, and no longer sent. It is a rule across the\n * thread with the headline numbers, and — where the host opts in with\n * `expandable` — it opens to show what the assistant reads in place of those\n * messages.\n *\n * With no checkpoint yet and a compaction in flight it says so instead — that\n * is a model call of its own, and without it the conversation just appears to\n * have gone quiet.\n *\n * Deliberately not a message bubble: a checkpoint is not something anyone\n * said.\n */\nexport function CompactionWidget({\n checkpoint,\n isActive = true,\n activity = null,\n renderer,\n expandable = false,\n}: CompactionWidgetProps): JSX.Element | null {\n const [expanded, setExpanded] = useState(false);\n\n if (renderer) {\n return <>{renderer({ checkpoint, isActive, activity })}</>;\n }\n\n const running = activity?.state === \"running\";\n\n if (!checkpoint) {\n if (!running) return null;\n return (\n <div className=\"devic-compaction devic-compaction-running\">\n <span className=\"devic-compaction-rule\" />\n <span className=\"devic-compaction-pill\">\n <CompressIcon />\n <span className=\"devic-compaction-title\">Compacting context</span>\n <span className=\"devic-compaction-meta\">\n {activity!.messageCount} message\n {activity!.messageCount === 1 ? \"\" : \"s\"} ·{\" \"}\n {formatTokens(activity!.tokensBefore)} tokens\n </span>\n <span className=\"devic-compaction-dots\" aria-hidden=\"true\">\n <i />\n <i />\n <i />\n </span>\n </span>\n <span className=\"devic-compaction-rule\" />\n </div>\n );\n }\n\n const summary = checkpoint.summary || {};\n const structured = hasStructure(summary);\n\n const tooltip = `${checkpoint.compactedMessageCount} message${\n checkpoint.compactedMessageCount === 1 ? \"\" : \"s\"\n } are still in this conversation but no longer sent to the assistant`;\n\n //The headline, identical either way: whether the checkpoint can be opened\n //changes what the reader may inspect, never what the marker claims.\n const headline = (\n <>\n <CompressIcon />\n <span className=\"devic-compaction-title\">\n Context compacted\n {checkpoint.index > 1 ? ` (#${checkpoint.index})` : \"\"}\n </span>\n <span className=\"devic-compaction-meta\">\n {checkpoint.compactedMessageCount} messages ·{\" \"}\n {formatTokens(checkpoint.tokensBefore)} →{\" \"}\n {formatTokens(checkpoint.tokensAfter)} tokens\n </span>\n {!isActive && (\n <span\n className=\"devic-compaction-tag\"\n title=\"Superseded by a later compaction, which merged this summary into itself. Kept for the record.\"\n >\n superseded\n </span>\n )}\n </>\n );\n\n return (\n <div className=\"devic-compaction\">\n <div className=\"devic-compaction-head\">\n <span className=\"devic-compaction-rule\" />\n {expandable ? (\n <button\n type=\"button\"\n className=\"devic-compaction-pill\"\n onClick={() => setExpanded((v) => !v)}\n aria-expanded={expanded}\n title={tooltip}\n >\n {headline}\n <ChevronIcon up={expanded} />\n </button>\n ) : (\n //A span, not a disabled button: there is nothing here to operate, so\n //it should not reach the keyboard or announce itself as a control.\n <span\n className=\"devic-compaction-pill devic-compaction-pill-static\"\n title={tooltip}\n >\n {headline}\n </span>\n )}\n <span className=\"devic-compaction-rule\" />\n </div>\n\n {expandable && expanded && (\n <div className=\"devic-compaction-body\">\n <div className=\"devic-compaction-note\">\n This is what the assistant reads in place of the messages above.\n The messages themselves are still here.\n </div>\n\n {structured && (\n <>\n {summary.goal && <Section title=\"Goal\">{summary.goal}</Section>}\n {!!summary.constraints?.length && (\n <Section title=\"Constraints\">\n <Bullets items={summary.constraints} />\n </Section>\n )}\n {summary.inProgress && (\n <Section title=\"In progress\">{summary.inProgress}</Section>\n )}\n {!!summary.pending?.length && (\n <Section title=\"Pending\">\n <Bullets items={summary.pending} />\n </Section>\n )}\n {!!summary.decisions?.length && (\n <Section title=\"Decisions\">\n <Bullets items={summary.decisions} />\n </Section>\n )}\n {!!summary.data?.length && (\n <Section title=\"Key data\">\n <Bullets\n items={summary.data.map((entry) =>\n entry.label ? `${entry.label}: ${entry.value}` : entry.value\n )}\n />\n </Section>\n )}\n {!!summary.done?.length && (\n <Section title=\"Done\">\n <Bullets items={summary.done} />\n </Section>\n )}\n {!!summary.openQuestions?.length && (\n <Section title=\"Open questions\">\n <Bullets items={summary.openQuestions} />\n </Section>\n )}\n </>\n )}\n\n {summary.raw && (\n <Section title={structured ? \"Notes\" : \"Summary\"}>\n <div className=\"devic-compaction-raw\">{summary.raw}</div>\n </Section>\n )}\n\n {!!checkpoint.facts?.length && (\n <Section title=\"Preserved exactly\">\n <div className=\"devic-compaction-facts\">\n {checkpoint.facts.map((fact, i) => (\n <span key={i} className=\"devic-compaction-fact\">\n {fact.label ? (\n <span className=\"devic-compaction-fact-label\">\n {fact.label}:{\" \"}\n </span>\n ) : null}\n {fact.value}\n </span>\n ))}\n </div>\n </Section>\n )}\n </div>\n )}\n </div>\n );\n}\n\nexport default CompactionWidget;\n"],"names":["_jsxs","_jsx","_Fragment"],"mappings":"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"}
@@ -13,6 +13,8 @@ export type { LimitBannerProps } from './LimitBanner';
13
13
  export { QueueNotice } from './QueueNotice';
14
14
  export type { QueueNoticeProps } from './QueueNotice';
15
15
  export { RecalledMemoriesWidget } from './RecalledMemoriesWidget';
16
+ export type { CompactionWidgetProps, CompactionRenderer, CompactionRendererProps, } from './CompactionWidget';
17
+ export { CompactionWidget } from './CompactionWidget';
16
18
  export type { RecalledMemoriesWidgetProps, RecalledMemoriesRenderer, RecalledMemoriesRendererProps, } from './RecalledMemoriesWidget';
17
19
  export type { ChatDrawerProps, ChatDrawerOptions, ChatDrawerHandle, ChatMessagesProps, ChatInputProps, CustomPromptBoxProps, MessageBubbleRenderer, MessageBubbleRendererProps, ToolTimelineProps, AllowedFileTypes, ConversationSelectorProps, SuggestedMessage, } from './ChatDrawer.types';
18
20
  export type { HandoffSubagentWidgetProps } from './HandoffSubagentWidget';
@@ -1,6 +1,6 @@
1
1
  import type { TenantMetadata, SubtenantMetadata } from '../provider';
2
2
  import { type PendingWidgetCall } from './useModelInterface';
3
- import type { ChatMessage, ChatFile, ModelInterfaceTool, QueueDisposition, RealtimeStatus, RecalledMemoryRecord, TenantLimitExceeded } from '../api/types';
3
+ import type { ChatMessage, ChatFile, CompactionActivity, CompactionCheckpoint, ModelInterfaceTool, QueueDisposition, RealtimeStatus, RecalledMemoryRecord, TenantLimitExceeded } from '../api/types';
4
4
  export interface UseDevicChatOptions {
5
5
  /**
6
6
  * Assistant identifier
@@ -177,6 +177,18 @@ export interface UseDevicChatResult {
177
177
  * without memory.
178
178
  */
179
179
  recalledMemories: RecalledMemoryRecord[];
180
+ /**
181
+ * Compaction checkpoints of the conversation, oldest first. Each one folds
182
+ * the messages before its boundary into a summary the model reads instead
183
+ * of them; the messages themselves stay in `messages`.
184
+ */
185
+ compactions: CompactionCheckpoint[];
186
+ /**
187
+ * The compaction being written right now, or null. A compaction is a model
188
+ * call of its own between two assistant messages, so this is what tells a
189
+ * UI why the conversation has gone quiet.
190
+ */
191
+ compaction: CompactionActivity | null;
180
192
  /**
181
193
  * Whether the assistant has handed off to a subagent
182
194
  */
@@ -90,6 +90,22 @@ function useDevicChat(options) {
90
90
  return fresh.length ? [...prev, ...fresh] : prev;
91
91
  });
92
92
  }, []);
93
+ // Compaction checkpoints of the conversation, and the one being written
94
+ // right now. Same merge rule as the recalls: the realtime blob only carries
95
+ // the in-flight run's checkpoints, so incoming batches merge by uid.
96
+ const [compactions, setCompactions] = useState([]);
97
+ const mergeCompactions = useCallback((incoming) => {
98
+ if (!incoming?.length)
99
+ return;
100
+ setCompactions((prev) => {
101
+ const known = new Set(prev.map((c) => c.uid));
102
+ const fresh = incoming.filter((c) => !known.has(c.uid));
103
+ return fresh.length ? [...prev, ...fresh] : prev;
104
+ });
105
+ }, []);
106
+ // Absent on every ordinary realtime write, which is how a finished
107
+ // compaction stops being reported.
108
+ const [compaction, setCompaction] = useState(null);
93
109
  // Handoff state
94
110
  const [handedOff, setHandedOff] = useState(false);
95
111
  const [handedOffSubThreadId, setHandedOffSubThreadId] = useState(null);
@@ -192,6 +208,8 @@ function useDevicChat(options) {
192
208
  setMessages([...(realtime.chatHistory ?? []), ...queuedOnServer]);
193
209
  }
194
210
  mergeRecalledMemories(realtime.recalledMemories);
211
+ mergeCompactions(realtime.compactions);
212
+ setCompaction(realtime.compaction ?? null);
195
213
  setStatus(realtime.status);
196
214
  setQueuedCount(realtime.queuedMessages ?? 0);
197
215
  if (realtime.status === 'processing') {
@@ -231,7 +249,7 @@ function useDevicChat(options) {
231
249
  logRef.current.warn('[useDevicChat] resumeFromRealtimeStatus failed:', err);
232
250
  setIsLoading(false);
233
251
  }
234
- }, [assistantId, mergeRecalledMemories]);
252
+ }, [assistantId, mergeRecalledMemories, mergeCompactions]);
235
253
  // Load initial chat history if chatUid prop is provided
236
254
  // This runs once on mount (or when initialChatUid changes) to fetch existing conversation
237
255
  const initialChatLoadedRef = useRef(false);
@@ -245,6 +263,7 @@ function useDevicChat(options) {
245
263
  const history = await clientRef.current.getChatHistory(assistantId, initialChatUid, { tenantId: resolvedTenantId });
246
264
  setMessages(history.chatContent);
247
265
  mergeRecalledMemories(history.recalledMemories);
266
+ mergeCompactions(history.compactions);
248
267
  setChatUid(initialChatUid);
249
268
  // Check realtime status to resume in-progress conversations
250
269
  await resumeFromRealtimeStatus(initialChatUid);
@@ -373,6 +392,10 @@ function useDevicChat(options) {
373
392
  // Surface recall events while the run is still processing, so the
374
393
  // "recalled memories" strip shows before the first response lands.
375
394
  mergeRecalledMemories(data.recalledMemories);
395
+ // A compaction runs between two assistant messages: without this the
396
+ // conversation looks stalled while it is being written.
397
+ mergeCompactions(data.compactions);
398
+ setCompaction(data.compaction ?? null);
376
399
  setStatus(data.status);
377
400
  // Notify about new messages
378
401
  const lastMessage = data.chatHistory[data.chatHistory.length - 1];
@@ -717,6 +740,8 @@ function useDevicChat(options) {
717
740
  setError(null);
718
741
  setLimitExceeded(null);
719
742
  setRecalledMemories([]);
743
+ setCompactions([]);
744
+ setCompaction(null);
720
745
  resetQueueState();
721
746
  pendingWidgetCallsRef.current = [];
722
747
  setPendingWidgetCalls([]);
@@ -742,12 +767,15 @@ function useDevicChat(options) {
742
767
  setIsLoading(true);
743
768
  setError(null);
744
769
  setRecalledMemories([]);
770
+ setCompactions([]);
771
+ setCompaction(null);
745
772
  // The queue belongs to the conversation being left behind.
746
773
  resetQueueState();
747
774
  try {
748
775
  const history = await clientRef.current.getChatHistory(assistantId, loadChatUid, { tenantId: resolvedTenantId });
749
776
  setMessages(history.chatContent);
750
777
  mergeRecalledMemories(history.recalledMemories);
778
+ mergeCompactions(history.compactions);
751
779
  setChatUid(loadChatUid);
752
780
  // Check realtime status to resume in-progress conversations
753
781
  await resumeFromRealtimeStatus(loadChatUid);
@@ -763,6 +791,7 @@ function useDevicChat(options) {
763
791
  resolvedTenantId,
764
792
  resumeFromRealtimeStatus,
765
793
  mergeRecalledMemories,
794
+ mergeCompactions,
766
795
  resetQueueState,
767
796
  ]);
768
797
  // Handoff polling: while handedOff is true, poll the realtime endpoint every
@@ -891,6 +920,8 @@ function useDevicChat(options) {
891
920
  error,
892
921
  limitExceeded,
893
922
  recalledMemories,
923
+ compactions,
924
+ compaction,
894
925
  handedOff,
895
926
  handedOffSubThreadId,
896
927
  queuedCount,