@adecore/agents-react 0.20.0 → 0.21.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/chat/chat-client.d.ts +1 -0
- package/dist/chat/logic/generated-image.d.ts +27 -0
- package/dist/chat/logic/generated-image.js +32 -0
- package/dist/chat/logic/timeline-target.d.ts +19 -1
- package/dist/chat/logic/timeline-target.js +49 -3
- package/dist/chat/logic/timeline.js +25 -17
- package/dist/chat/subagent-conversation.d.ts +2 -1
- package/dist/chat/subagent-conversation.js +7 -1
- package/dist/chat/timeline-flash.d.ts +3 -1
- package/dist/chat/timeline-flash.js +2 -2
- package/dist/chat/ui/CodeBlock.d.ts +3 -1
- package/dist/chat/ui/CodeBlock.js +38 -7
- package/dist/chat/ui/Composer.js +13 -3
- package/dist/chat/ui/ImageView.d.ts +8 -2
- package/dist/chat/ui/ImageView.js +12 -6
- package/dist/chat/ui/Markdown.d.ts +7 -1
- package/dist/chat/ui/Markdown.js +80 -23
- package/dist/chat/ui/MessageActions.js +6 -7
- package/dist/chat/ui/SubagentTimeline.js +7 -3
- package/dist/chat/ui/Timeline.js +68 -8
- package/dist/chat/ui/TimelineMenu.js +7 -3
- package/dist/chat/ui/file-links.d.ts +2 -1
- package/dist/chat/ui/file-links.js +8 -3
- package/dist/chat/ui/intelligent-ui/UiBlockFooter.d.ts +15 -0
- package/dist/chat/ui/intelligent-ui/UiBlockFooter.js +50 -0
- package/dist/chat/ui/intelligent-ui/UiBlockFrame.d.ts +10 -0
- package/dist/chat/ui/intelligent-ui/UiBlockFrame.js +61 -0
- package/dist/chat/ui/intelligent-ui/UiFallbackPart.d.ts +10 -0
- package/dist/chat/ui/intelligent-ui/UiFallbackPart.js +11 -0
- package/dist/chat/ui/intelligent-ui/UiReply.d.ts +13 -0
- package/dist/chat/ui/intelligent-ui/UiReply.js +325 -0
- package/dist/chat/ui/intelligent-ui/block-local.d.ts +2 -0
- package/dist/chat/ui/intelligent-ui/block-local.js +13 -0
- package/dist/chat/ui/intelligent-ui/chart-data.d.ts +16 -0
- package/dist/chat/ui/intelligent-ui/chart-data.js +46 -0
- package/dist/chat/ui/intelligent-ui/node-text.d.ts +9 -0
- package/dist/chat/ui/intelligent-ui/node-text.js +52 -0
- package/dist/chat/ui/intelligent-ui/registry.d.ts +2 -0
- package/dist/chat/ui/intelligent-ui/registry.js +50 -0
- package/dist/chat/ui/intelligent-ui/render-context.d.ts +50 -0
- package/dist/chat/ui/intelligent-ui/render-context.js +3 -0
- package/dist/chat/ui/intelligent-ui/renderers/chart.d.ts +3 -0
- package/dist/chat/ui/intelligent-ui/renderers/chart.js +125 -0
- package/dist/chat/ui/intelligent-ui/renderers/choices.d.ts +4 -0
- package/dist/chat/ui/intelligent-ui/renderers/choices.js +41 -0
- package/dist/chat/ui/intelligent-ui/renderers/content.d.ts +14 -0
- package/dist/chat/ui/intelligent-ui/renderers/content.js +66 -0
- package/dist/chat/ui/intelligent-ui/renderers/data.d.ts +7 -0
- package/dist/chat/ui/intelligent-ui/renderers/data.js +87 -0
- package/dist/chat/ui/intelligent-ui/renderers/inputs.d.ts +7 -0
- package/dist/chat/ui/intelligent-ui/renderers/inputs.js +60 -0
- package/dist/chat/ui/intelligent-ui/renderers/links.d.ts +10 -0
- package/dist/chat/ui/intelligent-ui/renderers/links.js +86 -0
- package/dist/chat/ui/intelligent-ui/renderers/structure.d.ts +6 -0
- package/dist/chat/ui/intelligent-ui/renderers/structure.js +32 -0
- package/dist/chat/ui/intelligent-ui/renderers/text.d.ts +8 -0
- package/dist/chat/ui/intelligent-ui/renderers/text.js +61 -0
- package/dist/chat/ui/intelligent-ui/reply-parts.d.ts +9 -0
- package/dist/chat/ui/intelligent-ui/reply-parts.js +18 -0
- package/dist/chat/ui/intelligent-ui/table-data.d.ts +31 -0
- package/dist/chat/ui/intelligent-ui/table-data.js +79 -0
- package/dist/chat/ui/intelligent-ui/tones.d.ts +7 -0
- package/dist/chat/ui/intelligent-ui/tones.js +31 -0
- package/dist/chat/ui/intelligent-ui/use-later.d.ts +1 -0
- package/dist/chat/ui/intelligent-ui/use-later.js +16 -0
- package/dist/chat/ui/intelligent-ui/use-ui-links.d.ts +9 -0
- package/dist/chat/ui/intelligent-ui/use-ui-links.js +107 -0
- package/dist/chat/ui/intelligent-ui/use-ui-queries.d.ts +20 -0
- package/dist/chat/ui/intelligent-ui/use-ui-queries.js +259 -0
- package/dist/chat/ui/reply-context.d.ts +8 -0
- package/dist/chat/ui/reply-context.js +1 -0
- package/dist/chat/ui/rows/GeneratedImageRow.d.ts +5 -0
- package/dist/chat/ui/rows/GeneratedImageRow.js +121 -0
- package/dist/chat/ui/rows/MessageRows.js +27 -7
- package/dist/chat/ui/rows/Rows.js +4 -2
- package/dist/chat/ui/rows/SubagentRow.js +5 -0
- package/dist/chat/ui/rows/WorkRows.d.ts +4 -1
- package/dist/chat/ui/rows/WorkRows.js +4 -3
- package/dist/host.d.ts +25 -3
- package/dist/host.js +1 -0
- package/dist/locales/en/agent-chat.json +89 -1
- package/dist/locales/nl/agent-chat.json +89 -1
- package/dist/state/chats.js +9 -0
- package/dist/theme.css +38 -2
- package/package.json +133 -7
- package/src/chat/chat-client.ts +1 -0
- package/src/chat/logic/generated-image.ts +48 -0
- package/src/chat/logic/timeline-target.ts +69 -4
- package/src/chat/logic/timeline.ts +28 -17
- package/src/chat/subagent-conversation.ts +13 -2
- package/src/chat/timeline-flash.ts +5 -3
- package/src/chat/ui/CodeBlock.tsx +70 -21
- package/src/chat/ui/Composer.tsx +22 -5
- package/src/chat/ui/ImageView.tsx +51 -8
- package/src/chat/ui/Markdown.tsx +123 -26
- package/src/chat/ui/MessageActions.tsx +15 -8
- package/src/chat/ui/SubagentTimeline.tsx +26 -1
- package/src/chat/ui/Timeline.tsx +98 -22
- package/src/chat/ui/TimelineMenu.tsx +6 -3
- package/src/chat/ui/file-links.ts +9 -3
- package/src/chat/ui/intelligent-ui/UiBlockFooter.tsx +139 -0
- package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +89 -0
- package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +21 -0
- package/src/chat/ui/intelligent-ui/UiReply.tsx +531 -0
- package/src/chat/ui/intelligent-ui/block-local.ts +16 -0
- package/src/chat/ui/intelligent-ui/chart-data.ts +67 -0
- package/src/chat/ui/intelligent-ui/node-text.ts +59 -0
- package/src/chat/ui/intelligent-ui/registry.ts +52 -0
- package/src/chat/ui/intelligent-ui/render-context.ts +78 -0
- package/src/chat/ui/intelligent-ui/renderers/chart.tsx +321 -0
- package/src/chat/ui/intelligent-ui/renderers/choices.tsx +98 -0
- package/src/chat/ui/intelligent-ui/renderers/content.tsx +145 -0
- package/src/chat/ui/intelligent-ui/renderers/data.tsx +187 -0
- package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +121 -0
- package/src/chat/ui/intelligent-ui/renderers/links.tsx +157 -0
- package/src/chat/ui/intelligent-ui/renderers/structure.tsx +84 -0
- package/src/chat/ui/intelligent-ui/renderers/text.tsx +124 -0
- package/src/chat/ui/intelligent-ui/reply-parts.ts +22 -0
- package/src/chat/ui/intelligent-ui/table-data.ts +104 -0
- package/src/chat/ui/intelligent-ui/tones.ts +37 -0
- package/src/chat/ui/intelligent-ui/use-later.ts +17 -0
- package/src/chat/ui/intelligent-ui/use-ui-links.ts +123 -0
- package/src/chat/ui/intelligent-ui/use-ui-queries.ts +282 -0
- package/src/chat/ui/reply-context.ts +7 -0
- package/src/chat/ui/rows/GeneratedImageRow.tsx +244 -0
- package/src/chat/ui/rows/MessageRows.tsx +69 -7
- package/src/chat/ui/rows/Rows.tsx +4 -2
- package/src/chat/ui/rows/SubagentRow.tsx +5 -0
- package/src/chat/ui/rows/WorkRows.tsx +20 -5
- package/src/host.ts +46 -3
- package/src/locales/en/agent-chat.json +89 -1
- package/src/locales/nl/agent-chat.json +89 -1
- package/src/state/chats.ts +9 -0
- package/src/theme.css +38 -2
package/dist/theme.css
CHANGED
|
@@ -117,6 +117,11 @@
|
|
|
117
117
|
}
|
|
118
118
|
@keyframes chat-shine { from { background-position: 200% 0; } to { background-position: -200% 0; } }
|
|
119
119
|
|
|
120
|
+
/* What shows through a transparent image: a checkerboard of two grounds, so its edges read in both themes. */
|
|
121
|
+
.chat-checkerboard {
|
|
122
|
+
background: repeating-conic-gradient(var(--surface-sunken) 0 25%, var(--surface-raised) 0 50%) 0 0 / 16px 16px;
|
|
123
|
+
}
|
|
124
|
+
|
|
120
125
|
/* A word of a reply that just arrived. The duration is REVEAL_FADE_MS in chat/ui/reveal.ts, which
|
|
121
126
|
keeps the spans until it is over; ease-in-out, because ease-out jumps up and then trails. */
|
|
122
127
|
.chat-fade { animation: chat-fade 300ms ease-in-out both; }
|
|
@@ -129,6 +134,20 @@
|
|
|
129
134
|
@starting-style { opacity: 0; }
|
|
130
135
|
}
|
|
131
136
|
@keyframes chat-fade { from { opacity: 0; } to { opacity: 1; } }
|
|
137
|
+
/* A node of an intelligent UI block that lands inside another, such as a step or a row: 160ms like
|
|
138
|
+
the card's own children, but only a fade, so a parent arriving with its children rises once. */
|
|
139
|
+
.chat-ui-nodes > * {
|
|
140
|
+
transition: opacity 160ms ease-out;
|
|
141
|
+
@starting-style { opacity: 0; }
|
|
142
|
+
}
|
|
143
|
+
/* A value a live reading changed lights up in the accent once and settles back in 800ms. */
|
|
144
|
+
.chat-ui-changed { animation: chat-ui-changed 800ms ease-out; }
|
|
145
|
+
@keyframes chat-ui-changed { from { color: var(--accent); } }
|
|
146
|
+
@media (prefers-reduced-motion: reduce) {
|
|
147
|
+
.chat-ui-changed { animation: none; }
|
|
148
|
+
}
|
|
149
|
+
/* A source row a citation revealed. The row only takes focus from code, since its own button takes a click. */
|
|
150
|
+
.chat-ui-source:focus { animation: chat-flash 1600ms ease-out both; }
|
|
132
151
|
/* A visual still on its way says so only once it takes long enough to be noticed. */
|
|
133
152
|
.chat-visual-wait { animation: chat-fade 200ms ease-out 600ms both; }
|
|
134
153
|
|
|
@@ -193,9 +212,26 @@
|
|
|
193
212
|
.chat-markdown :is(th, td) { border: 1px solid var(--tw-prose-td-borders); padding: 4px 8px; text-align: left; }
|
|
194
213
|
|
|
195
214
|
/* A code block is shiki's own `<pre>`, which brings a background of its own to override. */
|
|
196
|
-
.chat-code { border-radius: 8px; border: 1px solid var(--border); background: var(--chat-code-bg); overflow:
|
|
197
|
-
.chat-code pre { margin: 0; padding: round(0.625rem, 1px) round(0.75rem, 1px); background: transparent !important; font-family: var(--font-mono); font-size: var(--text-code); line-height: var(--text-code--line-height); }
|
|
215
|
+
.chat-code { position: relative; min-height: 38px; border-radius: 8px; border: 1px solid var(--border); background: var(--chat-code-bg); overflow: hidden; }
|
|
216
|
+
.chat-code pre { margin: 0; padding: round(0.625rem, 1px) round(0.75rem, 1px); overflow: auto; user-select: text; background: transparent !important; font-family: var(--font-mono); font-size: var(--text-code); line-height: var(--text-code--line-height); }
|
|
198
217
|
.chat-code code { font-family: inherit; }
|
|
218
|
+
.chat-code-actions { position: absolute; top: 4px; right: 4px; border-radius: 6px; background: var(--surface-raised); color: var(--text-muted); box-shadow: 0 0 0 1px var(--border); }
|
|
219
|
+
.chat-code-error { margin: 0; padding: 0 12px 10px; color: var(--status-error); font-size: var(--text-sm); }
|
|
220
|
+
@media (hover: hover) and (pointer: fine) {
|
|
221
|
+
.chat-code-actions { opacity: 0; pointer-events: none; }
|
|
222
|
+
.chat-code:hover .chat-code-actions, .chat-code:focus-within .chat-code-actions,
|
|
223
|
+
.chat-code[data-copy-state="failed"] .chat-code-actions, .chat-code-actions:has([aria-busy="true"]) { opacity: 1; pointer-events: auto; }
|
|
224
|
+
}
|
|
225
|
+
@media (pointer: coarse) {
|
|
226
|
+
.chat-code { min-height: 54px; }
|
|
227
|
+
.chat-code-actions { padding: 8px; gap: 17px; }
|
|
228
|
+
.chat-code-actions .icon-btn { position: relative; }
|
|
229
|
+
.chat-code-actions .icon-btn::after { content: ''; position: absolute; inset: -8px; }
|
|
230
|
+
}
|
|
231
|
+
@media (prefers-reduced-motion: reduce) {
|
|
232
|
+
.chat-code-actions .icon-btn { transition: none; }
|
|
233
|
+
.chat-code-actions .icon-btn:active { scale: 1; }
|
|
234
|
+
}
|
|
199
235
|
|
|
200
236
|
/* The chat composer is a CodeMirror editor, whose base theme lands in an unlayered style tag at
|
|
201
237
|
mount. These rules stay unlayered too, and carry one class more than the theme's selectors. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/agents-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "React agent chat threads, composers, approvals, provider accounts and usage views.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -129,6 +129,11 @@
|
|
|
129
129
|
"types": "./dist/chat/logic/fork.d.ts",
|
|
130
130
|
"default": "./dist/chat/logic/fork.js"
|
|
131
131
|
},
|
|
132
|
+
"./chat/logic/generated-image": {
|
|
133
|
+
"source": "./src/chat/logic/generated-image.ts",
|
|
134
|
+
"types": "./dist/chat/logic/generated-image.d.ts",
|
|
135
|
+
"default": "./dist/chat/logic/generated-image.js"
|
|
136
|
+
},
|
|
132
137
|
"./chat/logic/handback": {
|
|
133
138
|
"source": "./src/chat/logic/handback.ts",
|
|
134
139
|
"types": "./dist/chat/logic/handback.d.ts",
|
|
@@ -514,6 +519,101 @@
|
|
|
514
519
|
"types": "./dist/chat/ui/icons.d.ts",
|
|
515
520
|
"default": "./dist/chat/ui/icons.js"
|
|
516
521
|
},
|
|
522
|
+
"./chat/ui/intelligent-ui/UiBlockFooter": {
|
|
523
|
+
"source": "./src/chat/ui/intelligent-ui/UiBlockFooter.tsx",
|
|
524
|
+
"types": "./dist/chat/ui/intelligent-ui/UiBlockFooter.d.ts",
|
|
525
|
+
"default": "./dist/chat/ui/intelligent-ui/UiBlockFooter.js"
|
|
526
|
+
},
|
|
527
|
+
"./chat/ui/intelligent-ui/UiBlockFrame": {
|
|
528
|
+
"source": "./src/chat/ui/intelligent-ui/UiBlockFrame.tsx",
|
|
529
|
+
"types": "./dist/chat/ui/intelligent-ui/UiBlockFrame.d.ts",
|
|
530
|
+
"default": "./dist/chat/ui/intelligent-ui/UiBlockFrame.js"
|
|
531
|
+
},
|
|
532
|
+
"./chat/ui/intelligent-ui/UiFallbackPart": {
|
|
533
|
+
"source": "./src/chat/ui/intelligent-ui/UiFallbackPart.tsx",
|
|
534
|
+
"types": "./dist/chat/ui/intelligent-ui/UiFallbackPart.d.ts",
|
|
535
|
+
"default": "./dist/chat/ui/intelligent-ui/UiFallbackPart.js"
|
|
536
|
+
},
|
|
537
|
+
"./chat/ui/intelligent-ui/block-local": {
|
|
538
|
+
"source": "./src/chat/ui/intelligent-ui/block-local.ts",
|
|
539
|
+
"types": "./dist/chat/ui/intelligent-ui/block-local.d.ts",
|
|
540
|
+
"default": "./dist/chat/ui/intelligent-ui/block-local.js"
|
|
541
|
+
},
|
|
542
|
+
"./chat/ui/intelligent-ui/chart-data": {
|
|
543
|
+
"source": "./src/chat/ui/intelligent-ui/chart-data.ts",
|
|
544
|
+
"types": "./dist/chat/ui/intelligent-ui/chart-data.d.ts",
|
|
545
|
+
"default": "./dist/chat/ui/intelligent-ui/chart-data.js"
|
|
546
|
+
},
|
|
547
|
+
"./chat/ui/intelligent-ui/node-text": {
|
|
548
|
+
"source": "./src/chat/ui/intelligent-ui/node-text.ts",
|
|
549
|
+
"types": "./dist/chat/ui/intelligent-ui/node-text.d.ts",
|
|
550
|
+
"default": "./dist/chat/ui/intelligent-ui/node-text.js"
|
|
551
|
+
},
|
|
552
|
+
"./chat/ui/intelligent-ui/registry": {
|
|
553
|
+
"source": "./src/chat/ui/intelligent-ui/registry.ts",
|
|
554
|
+
"types": "./dist/chat/ui/intelligent-ui/registry.d.ts",
|
|
555
|
+
"default": "./dist/chat/ui/intelligent-ui/registry.js"
|
|
556
|
+
},
|
|
557
|
+
"./chat/ui/intelligent-ui/render-context": {
|
|
558
|
+
"source": "./src/chat/ui/intelligent-ui/render-context.ts",
|
|
559
|
+
"types": "./dist/chat/ui/intelligent-ui/render-context.d.ts",
|
|
560
|
+
"default": "./dist/chat/ui/intelligent-ui/render-context.js"
|
|
561
|
+
},
|
|
562
|
+
"./chat/ui/intelligent-ui/renderers/chart": {
|
|
563
|
+
"source": "./src/chat/ui/intelligent-ui/renderers/chart.tsx",
|
|
564
|
+
"types": "./dist/chat/ui/intelligent-ui/renderers/chart.d.ts",
|
|
565
|
+
"default": "./dist/chat/ui/intelligent-ui/renderers/chart.js"
|
|
566
|
+
},
|
|
567
|
+
"./chat/ui/intelligent-ui/renderers/choices": {
|
|
568
|
+
"source": "./src/chat/ui/intelligent-ui/renderers/choices.tsx",
|
|
569
|
+
"types": "./dist/chat/ui/intelligent-ui/renderers/choices.d.ts",
|
|
570
|
+
"default": "./dist/chat/ui/intelligent-ui/renderers/choices.js"
|
|
571
|
+
},
|
|
572
|
+
"./chat/ui/intelligent-ui/renderers/content": {
|
|
573
|
+
"source": "./src/chat/ui/intelligent-ui/renderers/content.tsx",
|
|
574
|
+
"types": "./dist/chat/ui/intelligent-ui/renderers/content.d.ts",
|
|
575
|
+
"default": "./dist/chat/ui/intelligent-ui/renderers/content.js"
|
|
576
|
+
},
|
|
577
|
+
"./chat/ui/intelligent-ui/renderers/data": {
|
|
578
|
+
"source": "./src/chat/ui/intelligent-ui/renderers/data.tsx",
|
|
579
|
+
"types": "./dist/chat/ui/intelligent-ui/renderers/data.d.ts",
|
|
580
|
+
"default": "./dist/chat/ui/intelligent-ui/renderers/data.js"
|
|
581
|
+
},
|
|
582
|
+
"./chat/ui/intelligent-ui/renderers/inputs": {
|
|
583
|
+
"source": "./src/chat/ui/intelligent-ui/renderers/inputs.tsx",
|
|
584
|
+
"types": "./dist/chat/ui/intelligent-ui/renderers/inputs.d.ts",
|
|
585
|
+
"default": "./dist/chat/ui/intelligent-ui/renderers/inputs.js"
|
|
586
|
+
},
|
|
587
|
+
"./chat/ui/intelligent-ui/renderers/links": {
|
|
588
|
+
"source": "./src/chat/ui/intelligent-ui/renderers/links.tsx",
|
|
589
|
+
"types": "./dist/chat/ui/intelligent-ui/renderers/links.d.ts",
|
|
590
|
+
"default": "./dist/chat/ui/intelligent-ui/renderers/links.js"
|
|
591
|
+
},
|
|
592
|
+
"./chat/ui/intelligent-ui/renderers/structure": {
|
|
593
|
+
"source": "./src/chat/ui/intelligent-ui/renderers/structure.tsx",
|
|
594
|
+
"types": "./dist/chat/ui/intelligent-ui/renderers/structure.d.ts",
|
|
595
|
+
"default": "./dist/chat/ui/intelligent-ui/renderers/structure.js"
|
|
596
|
+
},
|
|
597
|
+
"./chat/ui/intelligent-ui/renderers/text": {
|
|
598
|
+
"source": "./src/chat/ui/intelligent-ui/renderers/text.tsx",
|
|
599
|
+
"types": "./dist/chat/ui/intelligent-ui/renderers/text.d.ts",
|
|
600
|
+
"default": "./dist/chat/ui/intelligent-ui/renderers/text.js"
|
|
601
|
+
},
|
|
602
|
+
"./chat/ui/intelligent-ui/table-data": {
|
|
603
|
+
"source": "./src/chat/ui/intelligent-ui/table-data.ts",
|
|
604
|
+
"types": "./dist/chat/ui/intelligent-ui/table-data.d.ts",
|
|
605
|
+
"default": "./dist/chat/ui/intelligent-ui/table-data.js"
|
|
606
|
+
},
|
|
607
|
+
"./chat/ui/intelligent-ui/tones": {
|
|
608
|
+
"source": "./src/chat/ui/intelligent-ui/tones.ts",
|
|
609
|
+
"types": "./dist/chat/ui/intelligent-ui/tones.d.ts",
|
|
610
|
+
"default": "./dist/chat/ui/intelligent-ui/tones.js"
|
|
611
|
+
},
|
|
612
|
+
"./chat/ui/intelligent-ui/use-later": {
|
|
613
|
+
"source": "./src/chat/ui/intelligent-ui/use-later.ts",
|
|
614
|
+
"types": "./dist/chat/ui/intelligent-ui/use-later.d.ts",
|
|
615
|
+
"default": "./dist/chat/ui/intelligent-ui/use-later.js"
|
|
616
|
+
},
|
|
517
617
|
"./chat/ui/markdown-blocks": {
|
|
518
618
|
"source": "./src/chat/ui/markdown-blocks.ts",
|
|
519
619
|
"types": "./dist/chat/ui/markdown-blocks.d.ts",
|
|
@@ -554,6 +654,11 @@
|
|
|
554
654
|
"types": "./dist/chat/ui/rows/ForksRow.d.ts",
|
|
555
655
|
"default": "./dist/chat/ui/rows/ForksRow.js"
|
|
556
656
|
},
|
|
657
|
+
"./chat/ui/rows/GeneratedImageRow": {
|
|
658
|
+
"source": "./src/chat/ui/rows/GeneratedImageRow.tsx",
|
|
659
|
+
"types": "./dist/chat/ui/rows/GeneratedImageRow.d.ts",
|
|
660
|
+
"default": "./dist/chat/ui/rows/GeneratedImageRow.js"
|
|
661
|
+
},
|
|
557
662
|
"./chat/ui/rows/MessageRows": {
|
|
558
663
|
"source": "./src/chat/ui/rows/MessageRows.tsx",
|
|
559
664
|
"types": "./dist/chat/ui/rows/MessageRows.d.ts",
|
|
@@ -882,6 +987,26 @@
|
|
|
882
987
|
"source": "./src/chat/visual-viewport.ts",
|
|
883
988
|
"types": "./dist/chat/visual-viewport.d.ts",
|
|
884
989
|
"default": "./dist/chat/visual-viewport.js"
|
|
990
|
+
},
|
|
991
|
+
"./chat/ui/intelligent-ui/use-ui-links": {
|
|
992
|
+
"source": "./src/chat/ui/intelligent-ui/use-ui-links.ts",
|
|
993
|
+
"types": "./dist/chat/ui/intelligent-ui/use-ui-links.d.ts",
|
|
994
|
+
"default": "./dist/chat/ui/intelligent-ui/use-ui-links.js"
|
|
995
|
+
},
|
|
996
|
+
"./chat/ui/intelligent-ui/use-ui-queries": {
|
|
997
|
+
"source": "./src/chat/ui/intelligent-ui/use-ui-queries.ts",
|
|
998
|
+
"types": "./dist/chat/ui/intelligent-ui/use-ui-queries.d.ts",
|
|
999
|
+
"default": "./dist/chat/ui/intelligent-ui/use-ui-queries.js"
|
|
1000
|
+
},
|
|
1001
|
+
"./chat/ui/intelligent-ui/UiReply": {
|
|
1002
|
+
"source": "./src/chat/ui/intelligent-ui/UiReply.tsx",
|
|
1003
|
+
"types": "./dist/chat/ui/intelligent-ui/UiReply.d.ts",
|
|
1004
|
+
"default": "./dist/chat/ui/intelligent-ui/UiReply.js"
|
|
1005
|
+
},
|
|
1006
|
+
"./chat/ui/intelligent-ui/reply-parts": {
|
|
1007
|
+
"source": "./src/chat/ui/intelligent-ui/reply-parts.ts",
|
|
1008
|
+
"types": "./dist/chat/ui/intelligent-ui/reply-parts.d.ts",
|
|
1009
|
+
"default": "./dist/chat/ui/intelligent-ui/reply-parts.js"
|
|
885
1010
|
}
|
|
886
1011
|
},
|
|
887
1012
|
"scripts": {
|
|
@@ -891,6 +1016,8 @@
|
|
|
891
1016
|
"test:visuals": "bun --config=integration.bunfig.toml --conditions=source test ./src/chat/visual-viewport.integration.test.ts"
|
|
892
1017
|
},
|
|
893
1018
|
"dependencies": {
|
|
1019
|
+
"@adecore/agent-contracts": "0.21.0",
|
|
1020
|
+
"@adecore/intelligent-ui": "0.21.0",
|
|
894
1021
|
"@codemirror/commands": "^6.11.1",
|
|
895
1022
|
"@codemirror/language": "^6.12.4",
|
|
896
1023
|
"@codemirror/state": "^6.7.6",
|
|
@@ -899,7 +1026,6 @@
|
|
|
899
1026
|
"@lezer/highlight": "^1.2.3",
|
|
900
1027
|
"@lezer/markdown": "^1.7.2",
|
|
901
1028
|
"@pierre/diffs": "^1.4.3",
|
|
902
|
-
"@adecore/agent-contracts": "0.20.0",
|
|
903
1029
|
"@tanstack/react-virtual": "^3.14.13",
|
|
904
1030
|
"clsx": "^2.1.1",
|
|
905
1031
|
"lucide-react": "^1.47.0",
|
|
@@ -912,7 +1038,7 @@
|
|
|
912
1038
|
"zustand": "^5.0.15"
|
|
913
1039
|
},
|
|
914
1040
|
"peerDependencies": {
|
|
915
|
-
"@adecore/ui": "^0.
|
|
1041
|
+
"@adecore/ui": "^0.21.0",
|
|
916
1042
|
"i18next": "^26.4.2",
|
|
917
1043
|
"react": "^19.3.0",
|
|
918
1044
|
"react-dom": "^19.3.0",
|
|
@@ -920,14 +1046,14 @@
|
|
|
920
1046
|
},
|
|
921
1047
|
"devDependencies": {
|
|
922
1048
|
"@adecore/ui": "workspace:*",
|
|
923
|
-
"i18next": "^26.4.2",
|
|
924
|
-
"react": "^19.3.0",
|
|
925
|
-
"react-dom": "^19.3.0",
|
|
926
|
-
"react-i18next": "^17.0.15",
|
|
927
1049
|
"@types/bun": "^1.4.2",
|
|
928
1050
|
"@types/react": "^19.3.0",
|
|
929
1051
|
"@types/react-dom": "^19.3.0",
|
|
1052
|
+
"i18next": "^26.4.2",
|
|
930
1053
|
"linkedom": "^0.18.13",
|
|
1054
|
+
"react": "^19.3.0",
|
|
1055
|
+
"react-dom": "^19.3.0",
|
|
1056
|
+
"react-i18next": "^17.0.15",
|
|
931
1057
|
"typescript": "^7.0.2"
|
|
932
1058
|
},
|
|
933
1059
|
"license": "FSL-1.1-MIT",
|
package/src/chat/chat-client.ts
CHANGED
|
@@ -28,6 +28,7 @@ interface ChatOpenOptions {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface ChatSendExtras {
|
|
31
|
+
delivery?: 'steer' | 'queue';
|
|
31
32
|
/* Paths picked with `@`; they also sit in the text, this is what the timeline highlights. */
|
|
32
33
|
mentions?: string[];
|
|
33
34
|
/* Skills picked with `$`; they also sit in the text, this is what the timeline chips. */
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { CHAT_ATTACHMENT_MAX_BYTES, GeneratedImageInputSchema, type ChatAttachment, type ChatToolItem } from '@adecore/agent-contracts';
|
|
2
|
+
|
|
3
|
+
export const IMAGE_GENERATION_TOOL = 'ImageGeneration';
|
|
4
|
+
|
|
5
|
+
/* Why a generation shows no image: the provider's own words, or a check of what came back. */
|
|
6
|
+
export type GeneratedImageFailure =
|
|
7
|
+
| { kind: 'provider'; message: string | null }
|
|
8
|
+
| { kind: 'empty' }
|
|
9
|
+
| { kind: 'not-image' }
|
|
10
|
+
| { kind: 'too-large'; limit: number };
|
|
11
|
+
|
|
12
|
+
export type GeneratedImageView =
|
|
13
|
+
| { state: 'generating' }
|
|
14
|
+
| { state: 'failed'; failure: GeneratedImageFailure }
|
|
15
|
+
| { state: 'ready'; attachment: ChatAttachment; prompt: string | null; transparent: boolean };
|
|
16
|
+
|
|
17
|
+
export function isImageGeneration(tool: ChatToolItem): boolean {
|
|
18
|
+
return tool.name === IMAGE_GENERATION_TOOL;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* What the row of an image generation draws, from the tool call alone. */
|
|
22
|
+
export function generatedImageView(tool: ChatToolItem): GeneratedImageView {
|
|
23
|
+
if (tool.state === 'running') {
|
|
24
|
+
return { state: 'generating' };
|
|
25
|
+
}
|
|
26
|
+
const parsed = GeneratedImageInputSchema.safeParse(tool.input);
|
|
27
|
+
const input = parsed.success ? parsed.data : {};
|
|
28
|
+
const output = tool.output?.trim() || null;
|
|
29
|
+
if (tool.state === 'error' || input.attachment === undefined) {
|
|
30
|
+
return { state: 'failed', failure: output === null && tool.state !== 'error' ? { kind: 'empty' } : { kind: 'provider', message: output } };
|
|
31
|
+
}
|
|
32
|
+
const attachment = input.attachment;
|
|
33
|
+
if (attachment.size === 0) {
|
|
34
|
+
return { state: 'failed', failure: { kind: 'empty' } };
|
|
35
|
+
}
|
|
36
|
+
if (!attachment.mime.startsWith('image/')) {
|
|
37
|
+
return { state: 'failed', failure: { kind: 'not-image' } };
|
|
38
|
+
}
|
|
39
|
+
if (attachment.size > CHAT_ATTACHMENT_MAX_BYTES) {
|
|
40
|
+
return { state: 'failed', failure: { kind: 'too-large', limit: CHAT_ATTACHMENT_MAX_BYTES } };
|
|
41
|
+
}
|
|
42
|
+
return { state: 'ready', attachment, prompt: input.revisedPrompt?.trim() || null, transparent: input.transparentBackground === true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Width over height, when the attachment carries both. */
|
|
46
|
+
export function attachmentAspect(attachment: ChatAttachment): number | undefined {
|
|
47
|
+
return attachment.width !== undefined && attachment.height !== undefined ? attachment.width / attachment.height : undefined;
|
|
48
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { ChatItem } from '@adecore/agent-contracts';
|
|
1
|
+
import type { ChatItem, ChatUiChoiceOrigin } from '@adecore/agent-contracts';
|
|
2
2
|
import type { TimelineRow } from './timeline';
|
|
3
3
|
import { selectionWithin } from '@adecore/ui';
|
|
4
|
+
import type { FileRef } from '../../host';
|
|
4
5
|
|
|
5
6
|
/* What the right-click landed on, read once when the menu opens. */
|
|
6
7
|
export interface TimelineTarget {
|
|
@@ -13,6 +14,8 @@ export interface TimelineTarget {
|
|
|
13
14
|
path: string | null;
|
|
14
15
|
/* The line that file reference named, where it named one. */
|
|
15
16
|
line: number | null;
|
|
17
|
+
/* Captured at menu opening, before the rendering scope or cwd can change. */
|
|
18
|
+
file?: { ref: FileRef; cwd: string | null; scopeId: string | null } | null;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
export const EMPTY_TARGET: TimelineTarget = { selection: '', row: null, code: null, path: null, line: null };
|
|
@@ -41,12 +44,74 @@ export function withCurrentText(rows: TimelineRow[], items: Record<string, ChatI
|
|
|
41
44
|
export function readTimelineTarget(element: HTMLElement, scroller: HTMLElement | null, rows: TimelineRow[]): TimelineTarget {
|
|
42
45
|
const id = element.closest<HTMLElement>('[data-item-id]')?.dataset.itemId;
|
|
43
46
|
const file = element.closest<HTMLElement>('[data-file-path]');
|
|
44
|
-
const line =
|
|
47
|
+
const line = positiveInteger(file?.dataset.fileLine);
|
|
48
|
+
const column = positiveInteger(file?.dataset.fileColumn);
|
|
49
|
+
const endLine = positiveInteger(file?.dataset.fileEndLine);
|
|
50
|
+
const path = file?.dataset.filePath ?? null;
|
|
45
51
|
return {
|
|
46
52
|
selection: selectionWithin(scroller),
|
|
47
53
|
row: rows.find((row) => row.id === id) ?? null,
|
|
48
54
|
code: element.closest('pre')?.textContent ?? null,
|
|
49
|
-
path
|
|
50
|
-
line
|
|
55
|
+
path,
|
|
56
|
+
line,
|
|
57
|
+
file:
|
|
58
|
+
path === null
|
|
59
|
+
? null
|
|
60
|
+
: {
|
|
61
|
+
ref: {
|
|
62
|
+
path,
|
|
63
|
+
...(line === null ? {} : { line }),
|
|
64
|
+
...(column === null ? {} : { column }),
|
|
65
|
+
...(line === null || endLine === null || endLine < line ? {} : { endLine }),
|
|
66
|
+
directory: file?.dataset.fileDirectory === 'true'
|
|
67
|
+
},
|
|
68
|
+
cwd: file?.closest<HTMLElement>('[data-file-cwd]')?.dataset.fileCwd || null,
|
|
69
|
+
scopeId: file?.closest<HTMLElement>('[data-file-scope-id]')?.dataset.fileScopeId || null
|
|
70
|
+
}
|
|
51
71
|
};
|
|
52
72
|
}
|
|
73
|
+
|
|
74
|
+
function positiveInteger(value: string | undefined): number | null {
|
|
75
|
+
const number = Number(value);
|
|
76
|
+
return Number.isSafeInteger(number) && number > 0 ? number : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type UiReplyTarget = Pick<ChatUiChoiceOrigin, 'itemId' | 'blockId' | 'revision'>;
|
|
80
|
+
export type UiReplyJump = { kind: 'missing' | 'earlier' } | { kind: 'turn'; turnId: string } | { kind: 'row'; index: number };
|
|
81
|
+
|
|
82
|
+
export function uiReplyJump(
|
|
83
|
+
target: UiReplyTarget,
|
|
84
|
+
rows: readonly TimelineRow[],
|
|
85
|
+
items: Record<string, ChatItem> | undefined,
|
|
86
|
+
expandedTurns: ReadonlySet<string>,
|
|
87
|
+
hasEarlier: boolean
|
|
88
|
+
): UiReplyJump {
|
|
89
|
+
const item = items?.[target.itemId];
|
|
90
|
+
if (!item) {
|
|
91
|
+
return { kind: hasEarlier ? 'earlier' : 'missing' };
|
|
92
|
+
}
|
|
93
|
+
if (item.kind !== 'assistant' || !item.ui?.some((block) => block.id === target.blockId && block.revision === target.revision)) {
|
|
94
|
+
return { kind: 'missing' };
|
|
95
|
+
}
|
|
96
|
+
const index = rows.findIndex((row) => row.kind === 'assistant' && row.id === target.itemId);
|
|
97
|
+
if (index !== -1) {
|
|
98
|
+
return { kind: 'row', index };
|
|
99
|
+
}
|
|
100
|
+
return item.turnId && !expandedTurns.has(item.turnId) ? { kind: 'turn', turnId: item.turnId } : { kind: 'missing' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function revealUiReplyBlock(scroller: HTMLElement, target: UiReplyTarget): boolean {
|
|
104
|
+
// Compare attributes instead of interpolating provider ids into CSS selectors.
|
|
105
|
+
const row = Array.from(scroller.querySelectorAll<HTMLElement>('[data-item-id]')).find((element) => element.dataset.itemId === target.itemId);
|
|
106
|
+
const block =
|
|
107
|
+
row &&
|
|
108
|
+
Array.from(row.querySelectorAll<HTMLElement>('[data-ui-block]')).find(
|
|
109
|
+
(element) => element.dataset.uiBlock === target.blockId && element.dataset.uiRevision === target.revision
|
|
110
|
+
);
|
|
111
|
+
if (!block) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
scroller.scrollTop += block.getBoundingClientRect().top - scroller.getBoundingClientRect().top;
|
|
115
|
+
block.focus({ preventScroll: true });
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
import { abortedByMachine } from '@adecore/agent-contracts';
|
|
17
17
|
import type { ThreadCard } from '../../host';
|
|
18
18
|
import { formatElapsedShort } from '@adecore/ui/format';
|
|
19
|
+
import { isImageGeneration } from './generated-image';
|
|
19
20
|
import { handbackReportOf } from './handback';
|
|
20
21
|
import { toolEntry } from './tool-catalog';
|
|
21
22
|
import { hasFileChanges, isFileChange } from './tools';
|
|
@@ -77,7 +78,7 @@ interface TimelineOptions {
|
|
|
77
78
|
const BLOCK_KINDS = new Set<TimelineRow['kind']>(['assistant', 'report', 'thinking', 'changed-files', 'compaction', 'app-card', 'visual']);
|
|
78
79
|
|
|
79
80
|
export function isBlock(row: TimelineRow): boolean {
|
|
80
|
-
return BLOCK_KINDS.has(row.kind);
|
|
81
|
+
return BLOCK_KINDS.has(row.kind) || isImageRow(row);
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
/* "Read 4 files", "Ran 2 commands", or "12 tool calls" when the run mixes kinds. */
|
|
@@ -245,6 +246,10 @@ function flushTools(buffer: ChatToolItem[], rows: TimelineRow[], options: Timeli
|
|
|
245
246
|
}
|
|
246
247
|
}
|
|
247
248
|
|
|
249
|
+
function isImageRow(row: TimelineRow): boolean {
|
|
250
|
+
return (row.kind === 'work' || row.kind === 'work-live') && isImageGeneration(row.tool);
|
|
251
|
+
}
|
|
252
|
+
|
|
248
253
|
/* Rows for a run of items, in order, with tool runs folded and a subagent's work under its own row. */
|
|
249
254
|
function rowsForItems(items: ChatItem[], options: TimelineOptions, children: Map<string, ChatItem[]>): TimelineRow[] {
|
|
250
255
|
const rows: TimelineRow[] = [];
|
|
@@ -259,6 +264,11 @@ function rowsForItems(items: ChatItem[], options: TimelineOptions, children: Map
|
|
|
259
264
|
rows.push({ kind: 'workflow', id: item.id, tool: item, workflow: item.workflow });
|
|
260
265
|
continue;
|
|
261
266
|
}
|
|
267
|
+
if (item.kind === 'tool' && isImageGeneration(item)) {
|
|
268
|
+
flushTools(tools, rows, options);
|
|
269
|
+
rows.push({ kind: item.state === 'running' ? 'work-live' : 'work', id: item.id, tool: item });
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
262
272
|
if (item.kind === 'tool' && report === null) {
|
|
263
273
|
tools.push(item);
|
|
264
274
|
continue;
|
|
@@ -328,7 +338,7 @@ function lastAssistantRow(rows: TimelineRow[]): TimelineRow | null {
|
|
|
328
338
|
if (row.kind === 'assistant') {
|
|
329
339
|
return row;
|
|
330
340
|
}
|
|
331
|
-
if (row.kind !== 'note') {
|
|
341
|
+
if (row.kind !== 'note' && !isImageRow(row)) {
|
|
332
342
|
return null;
|
|
333
343
|
}
|
|
334
344
|
}
|
|
@@ -368,31 +378,35 @@ export function deriveTimelineRows(items: ChatItem[], options: TimelineOptions):
|
|
|
368
378
|
}
|
|
369
379
|
const turnId = chunk.turnId;
|
|
370
380
|
const turn = chunk.items.find((item): item is ChatTurnItem => item.kind === 'turn');
|
|
371
|
-
const
|
|
372
|
-
const rest = chunk.items.filter((item) => item.kind !== 'user' && item.kind !== 'turn');
|
|
381
|
+
const conversation = chunk.items.filter((item) => item.kind !== 'turn');
|
|
373
382
|
if (turn?.origin === 'agent') {
|
|
374
383
|
// The agent started this one itself, so there is no message of the person to show above it.
|
|
375
384
|
rows.push({ kind: 'turn-start', id: `start-${turnId}`, turn, label: agentTurnLabel(turn) });
|
|
376
385
|
}
|
|
377
|
-
rows.push(...rowsForItems(user, options, children));
|
|
378
386
|
const active = turnId === options.activeTurnId || turn?.state === 'running';
|
|
379
|
-
const work = rowsForItems(rest, options, children);
|
|
380
387
|
if (!turn || active) {
|
|
381
|
-
rows.push(...
|
|
388
|
+
rows.push(...rowsForItems(conversation, options, children));
|
|
382
389
|
if (active) {
|
|
383
390
|
rows.push({ kind: 'working', id: `working-${turnId}`, startedAt: turn?.createdAt ?? Date.now() });
|
|
384
391
|
}
|
|
385
392
|
continue;
|
|
386
393
|
}
|
|
387
|
-
//
|
|
394
|
+
// Steering splits the conversation inside a turn. Earlier responses keep their place above it.
|
|
395
|
+
const lastUser = conversation.findLastIndex((item) => item.kind === 'user');
|
|
396
|
+
rows.push(...rowsForItems(conversation.slice(0, lastUser + 1), options, children));
|
|
397
|
+
const rest = conversation.slice(lastUser + 1);
|
|
398
|
+
const work = rowsForItems(rest, options, children);
|
|
399
|
+
// The closing answer stays visible; the work after the last input folds behind the label.
|
|
388
400
|
const finalAssistant = lastAssistantRow(work);
|
|
389
401
|
const before = finalAssistant ? work.slice(0, work.indexOf(finalAssistant)) : work;
|
|
390
|
-
//
|
|
391
|
-
const standing = before.filter((row) => row.kind === 'workflow' && row.tool.state === 'running');
|
|
402
|
+
// Images remain a turn's visible result, and a workflow may still be running after its turn ends.
|
|
403
|
+
const standing = before.filter((row) => isImageRow(row) || (row.kind === 'workflow' && row.tool.state === 'running'));
|
|
392
404
|
const folded = before.filter((row) => !standing.includes(row));
|
|
393
405
|
const expanded = options.expandedTurns.has(turnId);
|
|
394
406
|
if (folded.length > 0) {
|
|
395
|
-
const tools = rest.filter(
|
|
407
|
+
const tools = rest.filter(
|
|
408
|
+
(item): item is ChatToolItem => item.kind === 'tool' && parentOf(item) === null && handbackReportOf(item) === null && !isImageGeneration(item)
|
|
409
|
+
);
|
|
396
410
|
rows.push({
|
|
397
411
|
kind: 'turn-fold',
|
|
398
412
|
id: `fold-${turnId}`,
|
|
@@ -402,17 +416,14 @@ export function deriveTimelineRows(items: ChatItem[], options: TimelineOptions):
|
|
|
402
416
|
hiddenCount: folded.length,
|
|
403
417
|
expanded
|
|
404
418
|
});
|
|
405
|
-
if (expanded) {
|
|
406
|
-
rows.push(...folded);
|
|
407
|
-
}
|
|
408
419
|
}
|
|
409
|
-
rows.push(...standing);
|
|
410
|
-
const changed = changedFilesRow(turn,
|
|
420
|
+
rows.push(...(expanded ? before : standing));
|
|
421
|
+
const changed = changedFilesRow(turn, conversation);
|
|
411
422
|
if (changed) {
|
|
412
423
|
rows.push(changed);
|
|
413
424
|
}
|
|
414
425
|
if (finalAssistant) {
|
|
415
|
-
rows.push(finalAssistant);
|
|
426
|
+
rows.push(...work.slice(work.indexOf(finalAssistant)));
|
|
416
427
|
}
|
|
417
428
|
if (options.forkedTurns?.has(turnId) === true) {
|
|
418
429
|
rows.push({ kind: 'forks', id: `forks-${turnId}`, turnId });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import i18next from 'i18next';
|
|
2
|
-
import type { ChatItem, ChatSubagentResult } from '@adecore/agent-contracts';
|
|
2
|
+
import type { ChatApprovalItem, ChatItem, ChatQuestionItem, ChatSubagentResult } from '@adecore/agent-contracts';
|
|
3
3
|
import { errorCode, isConnectionError, type ChatTransport } from '../transport';
|
|
4
4
|
|
|
5
5
|
// What the panel opens on: the newest end of the conversation, which is where an agent that works is.
|
|
@@ -8,6 +8,7 @@ export const SUBAGENT_PAGE = 60;
|
|
|
8
8
|
export interface SubagentConversationState {
|
|
9
9
|
status: 'loading' | 'ready' | 'failed';
|
|
10
10
|
items: ChatItem[];
|
|
11
|
+
pending: Array<ChatApprovalItem | ChatQuestionItem>;
|
|
11
12
|
source: ChatSubagentResult['source'] | null;
|
|
12
13
|
context: NonNullable<ChatSubagentResult['context']> | null;
|
|
13
14
|
/* Where the page before the oldest item on screen starts; null once the start is there. */
|
|
@@ -23,6 +24,7 @@ export interface SubagentConversationState {
|
|
|
23
24
|
export const INITIAL_CONVERSATION: SubagentConversationState = {
|
|
24
25
|
status: 'loading',
|
|
25
26
|
items: [],
|
|
27
|
+
pending: [],
|
|
26
28
|
source: null,
|
|
27
29
|
context: null,
|
|
28
30
|
cursor: null,
|
|
@@ -59,6 +61,13 @@ export function mergeNewest(
|
|
|
59
61
|
return { items, cursor: current.length === 0 ? pageCursor : currentCursor };
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
function pendingOf(items: readonly ChatItem[]): Array<ChatApprovalItem | ChatQuestionItem> {
|
|
65
|
+
return items.filter(
|
|
66
|
+
(item): item is ChatApprovalItem | ChatQuestionItem =>
|
|
67
|
+
(item.kind === 'approval' && item.decision === 'pending') || (item.kind === 'question' && item.state === 'pending')
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
62
71
|
function messageOf(error: unknown): string {
|
|
63
72
|
return error instanceof Error ? error.message : i18next.t('agent-chat:subagents.noAnswer');
|
|
64
73
|
}
|
|
@@ -131,7 +140,8 @@ export class SubagentConversation {
|
|
|
131
140
|
this.set({ loadingEarlier: true });
|
|
132
141
|
try {
|
|
133
142
|
const page = await this.ask({ cursor, limit: SUBAGENT_PAGE });
|
|
134
|
-
|
|
143
|
+
const items = [...page.items, ...this.state.items];
|
|
144
|
+
this.set({ items, pending: page.pending ?? pendingOf(items), cursor: page.history.cursor, live: page.live, loadingEarlier: false });
|
|
135
145
|
} catch (error) {
|
|
136
146
|
if (errorCode(error) === 'history-expired') {
|
|
137
147
|
// The record was written again under this panel; what it holds now is the only truth.
|
|
@@ -167,6 +177,7 @@ export class SubagentConversation {
|
|
|
167
177
|
this.set({
|
|
168
178
|
status: 'ready',
|
|
169
179
|
items: merged.items,
|
|
180
|
+
pending: page.pending ?? pendingOf(merged.items),
|
|
170
181
|
cursor: merged.cursor,
|
|
171
182
|
live: page.live,
|
|
172
183
|
source: page.source,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
|
+
import type { UiReplyTarget } from './logic/timeline-target';
|
|
2
3
|
|
|
3
4
|
/* The row of a thread a jump landed on: it lights up once, and a card of changes opens when the jump asked for it. */
|
|
4
5
|
export interface TimelineFlash {
|
|
@@ -6,13 +7,14 @@ export interface TimelineFlash {
|
|
|
6
7
|
key: string;
|
|
7
8
|
rowId: string;
|
|
8
9
|
opens: boolean;
|
|
10
|
+
block?: Pick<UiReplyTarget, 'blockId' | 'revision'>;
|
|
9
11
|
/* A second jump to the same row counts. */
|
|
10
12
|
nonce: number;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
interface TimelineFlashStore {
|
|
14
16
|
target: TimelineFlash | null;
|
|
15
|
-
flash(key: string, rowId: string, opens: boolean): void;
|
|
17
|
+
flash(key: string, rowId: string, opens: boolean, block?: TimelineFlash['block']): void;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/* A little longer than the animation of `.chat-flash`, so a row that scrolls into view again later is neither lit nor opened a second time. */
|
|
@@ -20,9 +22,9 @@ const FLASH_MS = 2000;
|
|
|
20
22
|
|
|
21
23
|
export const useTimelineFlash = create<TimelineFlashStore>((set, get) => ({
|
|
22
24
|
target: null,
|
|
23
|
-
flash(key, rowId, opens) {
|
|
25
|
+
flash(key, rowId, opens, block) {
|
|
24
26
|
const nonce = (get().target?.nonce ?? 0) + 1;
|
|
25
|
-
set({ target: { key, rowId, opens, nonce } });
|
|
27
|
+
set({ target: { key, rowId, opens, nonce, ...(block ? { block } : {}) } });
|
|
26
28
|
const timer = setTimeout(() => {
|
|
27
29
|
if (get().target?.nonce === nonce) {
|
|
28
30
|
set({ target: null });
|