@aparte/core 0.8.0 → 0.10.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/client/aparte-client.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/client/xml-artifact-feed.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts +24 -3
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/bubble/bubble-sync.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-action.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-add-attachment.d.ts +11 -0
- package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-cancel.d.ts +8 -0
- package/dist/components/composer/aparte-composer-cancel.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-send.d.ts +32 -0
- package/dist/components/composer/aparte-composer-send.d.ts.map +1 -1
- package/dist/components/elicitation/aparte-elicitation.d.ts +10 -0
- package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts +13 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/aparte-config.d.ts +34 -1
- package/dist/config/aparte-config.d.ts.map +1 -1
- package/dist/config/config-subscribe.d.ts +4 -0
- package/dist/config/config-subscribe.d.ts.map +1 -0
- package/dist/config/index.d.ts +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/locale.d.ts +98 -0
- package/dist/config/locale.d.ts.map +1 -1
- package/dist/config/sanitize.d.ts.map +1 -1
- package/dist/custom-elements.json +7590 -6959
- package/dist/elicitation/panel.d.ts +13 -0
- package/dist/elicitation/panel.d.ts.map +1 -1
- package/dist/host/aparte-chat-host.d.ts +3 -1
- package/dist/host/aparte-chat-host.d.ts.map +1 -1
- package/dist/{index-BieFbKeW.js → index-BnKBciDG.js} +642 -248
- package/dist/index-BnKBciDG.js.map +1 -0
- package/dist/index.css +147 -310
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +281 -92
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +3 -1
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +60 -55
- package/dist/index.node.js.map +1 -1
- package/dist/parsers/aparte-stream-parser.d.ts +25 -0
- package/dist/parsers/aparte-stream-parser.d.ts.map +1 -1
- package/dist/renderers/highlight-stream.d.ts +17 -0
- package/dist/renderers/highlight-stream.d.ts.map +1 -0
- package/dist/renderers/segment-renderers.d.ts.map +1 -1
- package/dist/renderers/segments/artifact/binary-file.d.ts.map +1 -1
- package/dist/renderers/segments/artifact/card.d.ts.map +1 -1
- package/dist/renderers/segments/artifact/shared.d.ts +19 -2
- package/dist/renderers/segments/artifact/shared.d.ts.map +1 -1
- package/dist/renderers/segments/code.d.ts.map +1 -1
- package/dist/renderers/segments/error.d.ts.map +1 -1
- package/dist/renderers/segments/pipeline-waiting.d.ts.map +1 -1
- package/dist/renderers/segments/thinking.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/dist/types/event-map.d.ts +1 -2
- package/dist/types/event-map.d.ts.map +1 -1
- package/dist/types/events.d.ts +0 -16
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/models.d.ts +1 -6
- package/dist/types/models.d.ts.map +1 -1
- package/dist/types/segments.d.ts +103 -51
- package/dist/types/segments.d.ts.map +1 -1
- package/dist/utils/segments.d.ts +244 -0
- package/dist/utils/segments.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/index-BieFbKeW.js.map +0 -1
- package/dist/renderers/segments/file-tree.d.ts +0 -3
- package/dist/renderers/segments/file-tree.d.ts.map +0 -1
- package/dist/renderers/segments/terminal.d.ts +0 -3
- package/dist/renderers/segments/terminal.d.ts.map +0 -1
|
@@ -69,11 +69,21 @@ const APARTE_DEFAULT_LOCALE = {
|
|
|
69
69
|
elicitationOtherPlaceholder: "Type your answer…",
|
|
70
70
|
elicitationOtherLabel: "Custom answer",
|
|
71
71
|
submitButton: "Submit",
|
|
72
|
+
stopButton: "Stop",
|
|
72
73
|
elicitationNext: "Next",
|
|
73
74
|
elicitationSkip: "Skip",
|
|
74
75
|
elicitationYes: "Yes",
|
|
75
76
|
elicitationNo: "No",
|
|
76
77
|
elicitationAnswerLabel: "Your answer",
|
|
78
|
+
actionUpload: "Attach file",
|
|
79
|
+
download: "Download",
|
|
80
|
+
previewPending: "Press Preview to run this artifact.",
|
|
81
|
+
sandboxError: "The sandbox failed during generation.",
|
|
82
|
+
sandboxErrorHint: "Common cause: the model produced invalid code (undefined variable, wrong argument type). Retry the request — the model may produce different code.",
|
|
83
|
+
preview: "Preview",
|
|
84
|
+
code: "Code",
|
|
85
|
+
generating: "Generating…",
|
|
86
|
+
rebuildingPreview: "Rebuilding preview…",
|
|
77
87
|
typing: "Typing...",
|
|
78
88
|
error: "Error",
|
|
79
89
|
running: "Running...",
|
|
@@ -237,11 +247,11 @@ class AparteStreamParser {
|
|
|
237
247
|
if (seg.type === "text") content = stripTrailingFence(content);
|
|
238
248
|
seg.content = content;
|
|
239
249
|
}
|
|
240
|
-
finalSegments.push(this._state.activeSegment);
|
|
250
|
+
finalSegments.push(this._closed(this._state.activeSegment));
|
|
241
251
|
this._state.activeSegment = null;
|
|
242
252
|
this._state.buffer = "";
|
|
243
253
|
} else if (this._state.buffer.trim()) {
|
|
244
|
-
finalSegments.push(this._createTextSegment(stripTrailingFence(this._state.buffer)));
|
|
254
|
+
finalSegments.push(this._closed(this._createTextSegment(stripTrailingFence(this._state.buffer))));
|
|
245
255
|
this._state.buffer = "";
|
|
246
256
|
}
|
|
247
257
|
return finalSegments;
|
|
@@ -311,7 +321,7 @@ class AparteStreamParser {
|
|
|
311
321
|
}
|
|
312
322
|
let segmentToEmit = null;
|
|
313
323
|
if (this._state.activeSegment && this._state.activeSegment.type === "text") {
|
|
314
|
-
segmentToEmit = this._state.activeSegment;
|
|
324
|
+
segmentToEmit = this._closed(this._state.activeSegment);
|
|
315
325
|
this._state.activeSegment = null;
|
|
316
326
|
}
|
|
317
327
|
if (firstPattern.type === "code") {
|
|
@@ -360,7 +370,7 @@ class AparteStreamParser {
|
|
|
360
370
|
if (this._state.activeSegment && "content" in this._state.activeSegment) {
|
|
361
371
|
this._state.activeSegment.content += codeContent2;
|
|
362
372
|
}
|
|
363
|
-
const segment2 = this._state.activeSegment;
|
|
373
|
+
const segment2 = this._closed(this._state.activeSegment);
|
|
364
374
|
this._state.mode = "text";
|
|
365
375
|
this._state.activeSegment = null;
|
|
366
376
|
return { segment: segment2, remaining: "" };
|
|
@@ -378,7 +388,7 @@ class AparteStreamParser {
|
|
|
378
388
|
if (this._state.activeSegment && "content" in this._state.activeSegment) {
|
|
379
389
|
this._state.activeSegment.content += codeContent;
|
|
380
390
|
}
|
|
381
|
-
const segment = this._state.activeSegment;
|
|
391
|
+
const segment = this._closed(this._state.activeSegment);
|
|
382
392
|
this._state.mode = "text";
|
|
383
393
|
this._state.activeSegment = null;
|
|
384
394
|
const afterClose = buffer.slice(closeIndex + 4);
|
|
@@ -420,7 +430,7 @@ class AparteStreamParser {
|
|
|
420
430
|
if (this._state.activeSegment && "content" in this._state.activeSegment) {
|
|
421
431
|
this._state.activeSegment.content += thinkingContent;
|
|
422
432
|
}
|
|
423
|
-
const segment = this._state.activeSegment;
|
|
433
|
+
const segment = this._closed(this._state.activeSegment);
|
|
424
434
|
this._state.mode = "text";
|
|
425
435
|
this._state.activeSegment = null;
|
|
426
436
|
this._state.thinkingEnd = void 0;
|
|
@@ -482,7 +492,7 @@ class AparteStreamParser {
|
|
|
482
492
|
if (this._state.activeSegment && "content" in this._state.activeSegment) {
|
|
483
493
|
this._state.activeSegment.content += tail;
|
|
484
494
|
}
|
|
485
|
-
const segment = this._state.activeSegment;
|
|
495
|
+
const segment = this._closed(this._state.activeSegment);
|
|
486
496
|
this._state.mode = "text";
|
|
487
497
|
this._state.activeSegment = null;
|
|
488
498
|
return { segment, remaining: buffer.slice(closeIndex + endDelim.length) };
|
|
@@ -503,6 +513,34 @@ class AparteStreamParser {
|
|
|
503
513
|
++this._state.segmentCounter;
|
|
504
514
|
return `${this._options.idPrefix}-${uuid()}`;
|
|
505
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* Mark a segment the parser has just CLOSED.
|
|
518
|
+
*
|
|
519
|
+
* The end of a delimited segment is not a guess: the closing token IS the end.
|
|
520
|
+
* `</think>`, a closing fence, `</artifact>` — and, for a text run, the opening
|
|
521
|
+
* of whatever comes next. The parser computes that moment at six sites and used
|
|
522
|
+
* to drop it, emitting the finished segment with no flag saying so.
|
|
523
|
+
*
|
|
524
|
+
* What that cost: nothing downstream could tell a finished reasoning block from
|
|
525
|
+
* one still streaming, so the only remaining signal was the END OF THE TURN. A
|
|
526
|
+
* reader watched "Thinking" for as long as the answer took to stream — twenty
|
|
527
|
+
* seconds after the model had stopped thinking — and the Markdown flush and the
|
|
528
|
+
* highlight-on-settle waited exactly as long. Two workarounds were written
|
|
529
|
+
* before this one: close at the end of the message (wrong value), and close when
|
|
530
|
+
* the next segment opens (right value, wrong moment, and no help for a reply
|
|
531
|
+
* that is reasoning and nothing else).
|
|
532
|
+
*
|
|
533
|
+
* Both agent loops share this parser, so saying it here says it once.
|
|
534
|
+
*
|
|
535
|
+
* Not every end is in band, and the exceptions are honest: a text run that ends
|
|
536
|
+
* only because the STREAM ended has no delimiter — `finalize()` is its truth —
|
|
537
|
+
* and a `tool_call` never passes through here at all, since its end is its
|
|
538
|
+
* `status`.
|
|
539
|
+
*/
|
|
540
|
+
_closed(segment) {
|
|
541
|
+
if (segment) segment.isStreaming = false;
|
|
542
|
+
return segment;
|
|
543
|
+
}
|
|
506
544
|
_createTextSegment(content) {
|
|
507
545
|
return {
|
|
508
546
|
id: this._options.autoGenerateIds ? this._generateId() : "",
|
|
@@ -523,7 +561,6 @@ class AparteStreamParser {
|
|
|
523
561
|
id: this._options.autoGenerateIds ? this._generateId() : "",
|
|
524
562
|
type: "thinking",
|
|
525
563
|
content,
|
|
526
|
-
collapsed: false,
|
|
527
564
|
label: "Thinking"
|
|
528
565
|
};
|
|
529
566
|
}
|
|
@@ -911,6 +948,9 @@ const SAFE_STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
|
911
948
|
"text-decoration-style",
|
|
912
949
|
"white-space"
|
|
913
950
|
]);
|
|
951
|
+
function isSafeCustomProperty(prop) {
|
|
952
|
+
return prop.startsWith("--") && !prop.startsWith("--aparte-");
|
|
953
|
+
}
|
|
914
954
|
function scrubStyle(value) {
|
|
915
955
|
const kept = [];
|
|
916
956
|
for (const decl of value.split(";")) {
|
|
@@ -919,7 +959,8 @@ function scrubStyle(value) {
|
|
|
919
959
|
if (idx === -1) continue;
|
|
920
960
|
const prop = trimmed.slice(0, idx).trim().toLowerCase();
|
|
921
961
|
const val = trimmed.slice(idx + 1);
|
|
922
|
-
if (!val.trim()
|
|
962
|
+
if (!val.trim()) continue;
|
|
963
|
+
if (!SAFE_STYLE_PROPS.has(prop) && !isSafeCustomProperty(prop)) continue;
|
|
923
964
|
if (val.includes("\\")) continue;
|
|
924
965
|
if (/url\s*\(|expression\s*\(|javascript:|vbscript:|[<>]/i.test(val)) continue;
|
|
925
966
|
kept.push(trimmed);
|
|
@@ -1006,7 +1047,6 @@ const APARTE_DEFAULT_BUBBLE_ACTIONS = {
|
|
|
1006
1047
|
};
|
|
1007
1048
|
const APARTE_DEFAULT_HOST_HANDLERS = {
|
|
1008
1049
|
attachmentPreview: false,
|
|
1009
|
-
terminalRun: false,
|
|
1010
1050
|
artifactRedownload: false,
|
|
1011
1051
|
artifactRehydrate: false
|
|
1012
1052
|
};
|
|
@@ -1069,6 +1109,7 @@ class AparteConfig {
|
|
|
1069
1109
|
// Tool Registry
|
|
1070
1110
|
_tools = /* @__PURE__ */ new Map();
|
|
1071
1111
|
_toolRenderers = /* @__PURE__ */ new Map();
|
|
1112
|
+
_segmentDefaults = /* @__PURE__ */ new Map();
|
|
1072
1113
|
// Host handlers — what the app declares it can actually complete.
|
|
1073
1114
|
_hostHandlers = { ...APARTE_DEFAULT_HOST_HANDLERS };
|
|
1074
1115
|
// Bubble Actions — APARTE_DEFAULT_BUBBLE_ACTIONS is the single source of truth
|
|
@@ -1149,7 +1190,6 @@ class AparteConfig {
|
|
|
1149
1190
|
getHostHandlers() {
|
|
1150
1191
|
return {
|
|
1151
1192
|
attachmentPreview: this._hostHandlers.attachmentPreview ?? APARTE_DEFAULT_HOST_HANDLERS.attachmentPreview,
|
|
1152
|
-
terminalRun: this._hostHandlers.terminalRun ?? APARTE_DEFAULT_HOST_HANDLERS.terminalRun,
|
|
1153
1193
|
artifactRedownload: this._hostHandlers.artifactRedownload ?? APARTE_DEFAULT_HOST_HANDLERS.artifactRedownload,
|
|
1154
1194
|
artifactRehydrate: this._hostHandlers.artifactRehydrate ?? APARTE_DEFAULT_HOST_HANDLERS.artifactRehydrate
|
|
1155
1195
|
};
|
|
@@ -1776,6 +1816,44 @@ class AparteConfig {
|
|
|
1776
1816
|
registerToolRenderer(toolName, renderer) {
|
|
1777
1817
|
this._toolRenderers.set(toolName, renderer);
|
|
1778
1818
|
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Field defaults for a segment TYPE, filled in when a segment enters a message.
|
|
1821
|
+
*
|
|
1822
|
+
* One function for every type rather than one per field: a `setThinkingOpen()`
|
|
1823
|
+
* would need a sibling the next time any type wanted a default, and the type key
|
|
1824
|
+
* is a string, so a consumer's own segment type is covered by the same call.
|
|
1825
|
+
*
|
|
1826
|
+
* The problem it solves: a consumer streaming a reply does not CONSTRUCT its
|
|
1827
|
+
* segments — the parser does — so there was no hook at all for "reasoning blocks
|
|
1828
|
+
* open in my app". Per-segment fields only reach segments you build yourself.
|
|
1829
|
+
*
|
|
1830
|
+
* Applied where identity is stamped (`utils/segments.ts`), so it covers every
|
|
1831
|
+
* path a segment can arrive by — the parser, `addSegment`, and the segments
|
|
1832
|
+
* seeded on an `appendMessage` — and no renderer has to look anything up.
|
|
1833
|
+
*
|
|
1834
|
+
* A field the producer set always wins, and identity is never defaulted. Read at
|
|
1835
|
+
* insertion and baked in: changing a default later does not reach segments already
|
|
1836
|
+
* on screen, because a block the reader opened has state the data does not.
|
|
1837
|
+
*
|
|
1838
|
+
* @example
|
|
1839
|
+
* // Reasoning blocks arrive open in this app, streaming or settled.
|
|
1840
|
+
* aparteGlobalConfig.setSegmentDefaults('thinking', { collapsed: false });
|
|
1841
|
+
*
|
|
1842
|
+
* @example
|
|
1843
|
+
* // A consumer's own type, same call.
|
|
1844
|
+
* aparteGlobalConfig.setSegmentDefaults('my-chart', { theme: 'dark' });
|
|
1845
|
+
*/
|
|
1846
|
+
setSegmentDefaults(type, defaults) {
|
|
1847
|
+
this._segmentDefaults.set(type, { ...defaults });
|
|
1848
|
+
}
|
|
1849
|
+
/** The defaults registered for a type, or undefined. */
|
|
1850
|
+
getSegmentDefaults(type) {
|
|
1851
|
+
return this._segmentDefaults.get(type);
|
|
1852
|
+
}
|
|
1853
|
+
/** Drop the defaults for a type. */
|
|
1854
|
+
clearSegmentDefaults(type) {
|
|
1855
|
+
this._segmentDefaults.delete(type);
|
|
1856
|
+
}
|
|
1779
1857
|
/** Unregister a per-tool renderer */
|
|
1780
1858
|
unregisterToolRenderer(toolName) {
|
|
1781
1859
|
this._toolRenderers.delete(toolName);
|
|
@@ -1860,6 +1938,7 @@ class AparteConfig {
|
|
|
1860
1938
|
this._siblingNavRenderer = void 0;
|
|
1861
1939
|
this._bubbleShellRenderer = void 0;
|
|
1862
1940
|
this._iconProvider = void 0;
|
|
1941
|
+
this._segmentDefaults.clear();
|
|
1863
1942
|
this._avatarProvider = void 0;
|
|
1864
1943
|
this._artifactPreviewBuilder = void 0;
|
|
1865
1944
|
this._keyProvider = void 0;
|
|
@@ -1955,6 +2034,16 @@ function runWithConfig(config, fn) {
|
|
|
1955
2034
|
function contextConfig(el2) {
|
|
1956
2035
|
return _renderConfig ?? resolveConfig(el2);
|
|
1957
2036
|
}
|
|
2037
|
+
const APARTE_CONFIG_CHANGE = "aparte-config-change";
|
|
2038
|
+
function subscribeConfigChange(el2, handler) {
|
|
2039
|
+
const listener = (e) => {
|
|
2040
|
+
const detail = e.detail;
|
|
2041
|
+
if (detail?.config && detail.config !== resolveConfig(el2)) return;
|
|
2042
|
+
handler();
|
|
2043
|
+
};
|
|
2044
|
+
window.addEventListener(APARTE_CONFIG_CHANGE, listener);
|
|
2045
|
+
return () => window.removeEventListener(APARTE_CONFIG_CHANGE, listener);
|
|
2046
|
+
}
|
|
1958
2047
|
const APARTE_DEFAULT_SKELETON_FALLBACKS = {
|
|
1959
2048
|
message: '<div class="aparte-skeleton-fallback" style="padding:16px;color:#9ca3af;">Loading...</div>',
|
|
1960
2049
|
code: '<div class="aparte-skeleton-fallback" style="padding:16px;background:#1e293b;color:#64748b;border-radius:8px;">Loading code...</div>',
|
|
@@ -2035,7 +2124,15 @@ const thinkingRenderer = {
|
|
|
2035
2124
|
* new class of risk. With no Markdown provider registered it degrades to the
|
|
2036
2125
|
* zero-dependency default (escape + `<br>`), which is what it used to be.
|
|
2037
2126
|
*/
|
|
2038
|
-
render: (segment) => `<details class="segment segment-thinking" data-segment-id="${escapeHtml(segment.id)}" ${segment.collapsed ? "" : "
|
|
2127
|
+
render: (segment) => `<details class="segment segment-thinking" data-segment-id="${escapeHtml(segment.id)}" ${segment.collapsed === false ? "open" : ""}><summary class="thinking-header"><span class="thinking-label">${escapeHtml(segment.label || contextConfig().t("thinking"))}</span><span class="thinking-toggle"></span></summary><div class="thinking-content">${contextConfig().renderMarkdown(segment.content)}</div></details>`,
|
|
2128
|
+
/**
|
|
2129
|
+
* The default label is the only config-derived text here — and `segment.label`
|
|
2130
|
+
* still wins, exactly as in `render`, because that string is the app's.
|
|
2131
|
+
*/
|
|
2132
|
+
relabel: (el2, segment) => {
|
|
2133
|
+
const label = el2.querySelector(".thinking-label");
|
|
2134
|
+
if (label) label.textContent = segment.label || contextConfig().t("thinking");
|
|
2135
|
+
},
|
|
2039
2136
|
update: (el2, segment) => {
|
|
2040
2137
|
const contentEl = el2.querySelector(".thinking-content");
|
|
2041
2138
|
if (!contentEl) return;
|
|
@@ -2047,6 +2144,53 @@ const thinkingRenderer = {
|
|
|
2047
2144
|
},
|
|
2048
2145
|
getStyles: () => ``
|
|
2049
2146
|
};
|
|
2147
|
+
const HIGHLIGHT_DEBOUNCE_MS = 400;
|
|
2148
|
+
const MAX_THROTTLE_ENTRIES = 256;
|
|
2149
|
+
const _lastHighlightAt = /* @__PURE__ */ new Map();
|
|
2150
|
+
function markThrottle(map, id, at) {
|
|
2151
|
+
map.delete(id);
|
|
2152
|
+
if (map.size >= MAX_THROTTLE_ENTRIES) {
|
|
2153
|
+
const oldest = map.keys().next().value;
|
|
2154
|
+
if (oldest !== void 0) map.delete(oldest);
|
|
2155
|
+
}
|
|
2156
|
+
map.set(id, at);
|
|
2157
|
+
}
|
|
2158
|
+
function highlightedLen(pane) {
|
|
2159
|
+
const n = Number(pane.dataset.aparteHlLen);
|
|
2160
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
2161
|
+
}
|
|
2162
|
+
function streamHighlight(element, paneSelector, content, lang, segId) {
|
|
2163
|
+
const pane = element.querySelector(paneSelector);
|
|
2164
|
+
if (!pane) return;
|
|
2165
|
+
const tail = pane.querySelector("[data-aparte-tail]");
|
|
2166
|
+
const hlLen = highlightedLen(pane);
|
|
2167
|
+
if (tail && hlLen <= content.length) {
|
|
2168
|
+
tail.textContent = content.slice(hlLen);
|
|
2169
|
+
} else {
|
|
2170
|
+
delete pane.dataset.aparteHlLen;
|
|
2171
|
+
const codeEl = pane.querySelector("code");
|
|
2172
|
+
if (codeEl) codeEl.textContent = content;
|
|
2173
|
+
else pane.innerHTML = `<pre><code class="language-${escapeHtml(lang || "text")}">${escapeHtml(content)}</code></pre>`;
|
|
2174
|
+
}
|
|
2175
|
+
const cut = content.lastIndexOf("\n") + 1;
|
|
2176
|
+
if (cut <= hlLen) return;
|
|
2177
|
+
const now = Date.now();
|
|
2178
|
+
if (now - (_lastHighlightAt.get(segId) ?? 0) < HIGHLIGHT_DEBOUNCE_MS) return;
|
|
2179
|
+
markThrottle(_lastHighlightAt, segId, now);
|
|
2180
|
+
void contextConfig(element).highlightCode(content.slice(0, cut), lang).then((html) => {
|
|
2181
|
+
const live = element.querySelector(paneSelector);
|
|
2182
|
+
if (!live) return;
|
|
2183
|
+
if (cut <= highlightedLen(live)) return;
|
|
2184
|
+
live.innerHTML = html;
|
|
2185
|
+
const codeEl = live.querySelector("code") ?? live;
|
|
2186
|
+
const span = document.createElement("span");
|
|
2187
|
+
span.dataset.aparteTail = "";
|
|
2188
|
+
span.textContent = content.slice(cut);
|
|
2189
|
+
codeEl.appendChild(span);
|
|
2190
|
+
live.dataset.aparteHlLen = String(cut);
|
|
2191
|
+
}).catch(() => {
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2050
2194
|
const codeRenderer = {
|
|
2051
2195
|
type: "code",
|
|
2052
2196
|
render: (segment) => `
|
|
@@ -2063,6 +2207,13 @@ const codeRenderer = {
|
|
|
2063
2207
|
</div>
|
|
2064
2208
|
</div>
|
|
2065
2209
|
`,
|
|
2210
|
+
/** The copy button's icon and tooltip — nothing else here comes from config. */
|
|
2211
|
+
relabel: (element) => {
|
|
2212
|
+
const copyBtn = element.querySelector(".code-copy");
|
|
2213
|
+
if (!copyBtn || copyBtn.dataset.copied) return;
|
|
2214
|
+
copyBtn.setAttribute("title", contextConfig().t("copy"));
|
|
2215
|
+
copyBtn.innerHTML = contextConfig().getIcon("copy");
|
|
2216
|
+
},
|
|
2066
2217
|
setup: (element, segment) => {
|
|
2067
2218
|
const wrapper = element.querySelector(".code-content-wrapper");
|
|
2068
2219
|
if (wrapper) {
|
|
@@ -2074,26 +2225,23 @@ const codeRenderer = {
|
|
|
2074
2225
|
const copyBtn = element.querySelector(".code-copy");
|
|
2075
2226
|
if (copyBtn) {
|
|
2076
2227
|
copyBtn.addEventListener("click", () => {
|
|
2077
|
-
|
|
2228
|
+
const source = element.querySelector(".code-content-wrapper")?.textContent ?? "";
|
|
2229
|
+
void navigator.clipboard.writeText(source).catch(() => {
|
|
2078
2230
|
});
|
|
2079
2231
|
copyBtn.innerHTML = contextConfig(copyBtn).getIcon("check");
|
|
2080
2232
|
copyBtn.setAttribute("title", contextConfig(copyBtn).t("copied"));
|
|
2233
|
+
copyBtn.dataset.copied = "1";
|
|
2081
2234
|
setTimeout(() => {
|
|
2082
2235
|
copyBtn.innerHTML = contextConfig(copyBtn).getIcon("copy");
|
|
2083
2236
|
copyBtn.setAttribute("title", contextConfig(copyBtn).t("copy"));
|
|
2237
|
+
delete copyBtn.dataset.copied;
|
|
2084
2238
|
}, 1500);
|
|
2085
2239
|
});
|
|
2086
2240
|
}
|
|
2087
2241
|
},
|
|
2088
2242
|
update: (element, segment) => {
|
|
2089
2243
|
if (segment.isStreaming) {
|
|
2090
|
-
|
|
2091
|
-
if (codeEl) {
|
|
2092
|
-
codeEl.textContent = segment.content;
|
|
2093
|
-
} else {
|
|
2094
|
-
const wrapper = element.querySelector(".code-content-wrapper");
|
|
2095
|
-
if (wrapper) wrapper.innerHTML = `<pre><code class="language-${escapeHtml(segment.language || "text")}">${escapeHtml(segment.content)}</code></pre>`;
|
|
2096
|
-
}
|
|
2244
|
+
streamHighlight(element, ".code-content-wrapper", segment.content, segment.language || "", segment.id);
|
|
2097
2245
|
} else {
|
|
2098
2246
|
const wrapper = element.querySelector(".code-content-wrapper");
|
|
2099
2247
|
if (wrapper) {
|
|
@@ -2106,58 +2254,6 @@ const codeRenderer = {
|
|
|
2106
2254
|
},
|
|
2107
2255
|
getStyles: () => ``
|
|
2108
2256
|
};
|
|
2109
|
-
const terminalRenderer = {
|
|
2110
|
-
type: "terminal",
|
|
2111
|
-
render: (segment) => `
|
|
2112
|
-
<div class="segment segment-terminal" data-segment-id="${escapeHtml(segment.id)}">
|
|
2113
|
-
<div class="terminal-command-block">
|
|
2114
|
-
<div class="terminal-icon">
|
|
2115
|
-
${contextConfig().getIcon("terminal")}
|
|
2116
|
-
</div>
|
|
2117
|
-
<code class="terminal-command">${escapeHtml(segment.command || "")}</code>
|
|
2118
|
-
<div class="terminal-actions">
|
|
2119
|
-
${segment.isRunning ? `<span class="terminal-running"><span class="spinner"></span>${contextConfig().t("running")}</span>` : contextConfig().getHostHandlers().terminalRun ? `<button class="terminal-run-btn" data-action="run" aria-label="${escapeAttr(contextConfig().t("run"))}" title="${escapeAttr(contextConfig().t("run"))}">${contextConfig().t("run")}</button>` : ""}
|
|
2120
|
-
<button class="terminal-copy-btn" data-action="copy" aria-label="${escapeAttr(contextConfig().t("copy"))}" title="${escapeAttr(contextConfig().t("copy"))}">
|
|
2121
|
-
${contextConfig().getIcon("copy")}
|
|
2122
|
-
</button>
|
|
2123
|
-
</div>
|
|
2124
|
-
</div>
|
|
2125
|
-
${segment.output ? `<div class="terminal-output">${escapeHtml(segment.output)}</div>` : ""}
|
|
2126
|
-
${segment.exitCode !== void 0 && segment.exitCode !== 0 ? `<div class="terminal-error">Command failed with exit code ${segment.exitCode}</div>` : ""}
|
|
2127
|
-
</div>
|
|
2128
|
-
`,
|
|
2129
|
-
setup: (element) => {
|
|
2130
|
-
const copyBtn = element.querySelector(".terminal-copy-btn");
|
|
2131
|
-
const command = element.querySelector(".terminal-command");
|
|
2132
|
-
if (copyBtn && command) {
|
|
2133
|
-
copyBtn.addEventListener("click", () => {
|
|
2134
|
-
void navigator.clipboard.writeText(command.textContent || "").catch(() => {
|
|
2135
|
-
});
|
|
2136
|
-
copyBtn.innerHTML = contextConfig(copyBtn).getIcon("check");
|
|
2137
|
-
copyBtn.setAttribute("title", contextConfig(copyBtn).t("copied"));
|
|
2138
|
-
setTimeout(() => {
|
|
2139
|
-
copyBtn.innerHTML = contextConfig(copyBtn).getIcon("copy");
|
|
2140
|
-
copyBtn.setAttribute("title", contextConfig(copyBtn).t("copy"));
|
|
2141
|
-
}, 1500);
|
|
2142
|
-
});
|
|
2143
|
-
}
|
|
2144
|
-
const runBtn = element.querySelector(".terminal-run-btn");
|
|
2145
|
-
if (runBtn) {
|
|
2146
|
-
runBtn.addEventListener("click", () => {
|
|
2147
|
-
const segmentId = element.getAttribute("data-segment-id");
|
|
2148
|
-
element.dispatchEvent(new CustomEvent("aparte-terminal-run", {
|
|
2149
|
-
bubbles: true,
|
|
2150
|
-
composed: true,
|
|
2151
|
-
detail: {
|
|
2152
|
-
segmentId,
|
|
2153
|
-
command: command?.textContent || ""
|
|
2154
|
-
}
|
|
2155
|
-
}));
|
|
2156
|
-
});
|
|
2157
|
-
}
|
|
2158
|
-
},
|
|
2159
|
-
getStyles: () => ``
|
|
2160
|
-
};
|
|
2161
2257
|
const errorRenderer = {
|
|
2162
2258
|
type: "error",
|
|
2163
2259
|
render: (segment) => {
|
|
@@ -2176,13 +2272,28 @@ const errorRenderer = {
|
|
|
2176
2272
|
${contextConfig().getIcon("error") || "⚠"}
|
|
2177
2273
|
</div>
|
|
2178
2274
|
<div class="error-content">
|
|
2179
|
-
<div class="error-title"
|
|
2275
|
+
<div class="error-title">${escapeHtml(contextConfig().t("error"))}</div>
|
|
2180
2276
|
<div class="error-message">${escapeHtml(segment.content)}</div>
|
|
2181
2277
|
${segment.details ? `<div class="error-details">${escapeHtml(segment.details)}</div>` : ""}
|
|
2182
2278
|
</div>
|
|
2183
2279
|
</div>
|
|
2184
2280
|
`;
|
|
2185
2281
|
},
|
|
2282
|
+
/**
|
|
2283
|
+
* The icon and the heading. The heading used to be the hardcoded literal `Error`
|
|
2284
|
+
* while `locale.error` — a REQUIRED key, documented, and already translated to
|
|
2285
|
+
* "Erreur" — was read by nothing at all. A translated string with no consumer and
|
|
2286
|
+
* a literal with no translation, in the same card.
|
|
2287
|
+
*
|
|
2288
|
+
* Not the message: that is the model's or the transport's text, in whatever
|
|
2289
|
+
* language it arrived in. Relabelling it would be inventing content.
|
|
2290
|
+
*/
|
|
2291
|
+
relabel: (element) => {
|
|
2292
|
+
const wrap = element.querySelector(".error-icon-wrapper");
|
|
2293
|
+
if (wrap) wrap.innerHTML = contextConfig().getIcon("error") || "⚠";
|
|
2294
|
+
const title = element.querySelector(".error-title");
|
|
2295
|
+
if (title) title.textContent = contextConfig().t("error");
|
|
2296
|
+
},
|
|
2186
2297
|
getStyles: () => ``
|
|
2187
2298
|
};
|
|
2188
2299
|
const progressRenderer = {
|
|
@@ -2194,31 +2305,6 @@ const progressRenderer = {
|
|
|
2194
2305
|
},
|
|
2195
2306
|
getStyles: () => ``
|
|
2196
2307
|
};
|
|
2197
|
-
function renderFileNode(node, depth = 0) {
|
|
2198
|
-
const indent = depth * 16;
|
|
2199
|
-
const icon = node.type === "directory" ? "📁" : "📄";
|
|
2200
|
-
const statusClass = node.status ? `file-status-${escapeHtml(node.status)}` : "";
|
|
2201
|
-
let html = `<div class="file-node ${statusClass}" style="padding-left: ${indent}px"><span class="file-icon">${icon}</span><span class="file-name">${escapeHtml(node.name)}</span></div>`;
|
|
2202
|
-
if (node.children) {
|
|
2203
|
-
for (const child of node.children) {
|
|
2204
|
-
html += renderFileNode(child, depth + 1);
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
return html;
|
|
2208
|
-
}
|
|
2209
|
-
const fileTreeRenderer = {
|
|
2210
|
-
type: "file-tree",
|
|
2211
|
-
render: (segment) => {
|
|
2212
|
-
let filesHtml = "";
|
|
2213
|
-
if (segment.files) {
|
|
2214
|
-
for (const file of segment.files) {
|
|
2215
|
-
filesHtml += renderFileNode(file, 0);
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
return `<div class="segment segment-file-tree" data-segment-id="${escapeHtml(segment.id)}">${segment.title ? `<div class="file-tree-title">${escapeHtml(segment.title)}</div>` : ""}<div class="file-tree-content">${filesHtml}</div></div>`;
|
|
2219
|
-
},
|
|
2220
|
-
getStyles: () => ``
|
|
2221
|
-
};
|
|
2222
2308
|
const toolCallRenderer = {
|
|
2223
2309
|
type: "tool_call",
|
|
2224
2310
|
render: (segment) => {
|
|
@@ -2260,6 +2346,39 @@ const toolCallRenderer = {
|
|
|
2260
2346
|
</div>
|
|
2261
2347
|
`;
|
|
2262
2348
|
},
|
|
2349
|
+
/**
|
|
2350
|
+
* The pill's glyphs and — when the turn is paused for a person — the two labels
|
|
2351
|
+
* on the approval gate. Those are the highest-stakes strings in the library: a
|
|
2352
|
+
* reader deciding whether to let a tool run should not be reading them in a
|
|
2353
|
+
* language they did not choose.
|
|
2354
|
+
*
|
|
2355
|
+
* No child node is added or removed, so the buttons keep their listeners and,
|
|
2356
|
+
* critically, their FOCUS — this is the one control a keyboard user may be
|
|
2357
|
+
* sitting on when a config change fires.
|
|
2358
|
+
*/
|
|
2359
|
+
relabel: (element, segment) => {
|
|
2360
|
+
const cfg = contextConfig();
|
|
2361
|
+
const icon = element.querySelector(".tool-pill-icon");
|
|
2362
|
+
if (icon) icon.innerHTML = cfg.getIcon("tool");
|
|
2363
|
+
const statusEl = element.querySelector(".tool-pill-status");
|
|
2364
|
+
if (statusEl) {
|
|
2365
|
+
const s = segment.status;
|
|
2366
|
+
statusEl.innerHTML = s === "resolved" ? cfg.getIcon("check") : s === "aborted" || s === "rejected" ? cfg.getIcon("close") : "";
|
|
2367
|
+
}
|
|
2368
|
+
const loc = cfg.getLocale();
|
|
2369
|
+
const approve = element.querySelector(".tool-approve-btn");
|
|
2370
|
+
if (approve) {
|
|
2371
|
+
const label = loc.approveTool ?? "Approve";
|
|
2372
|
+
approve.textContent = label;
|
|
2373
|
+
approve.setAttribute("aria-label", label);
|
|
2374
|
+
}
|
|
2375
|
+
const reject = element.querySelector(".tool-reject-btn");
|
|
2376
|
+
if (reject) {
|
|
2377
|
+
const label = loc.rejectTool ?? "Reject";
|
|
2378
|
+
reject.textContent = label;
|
|
2379
|
+
reject.setAttribute("aria-label", label);
|
|
2380
|
+
}
|
|
2381
|
+
},
|
|
2263
2382
|
setup: (element, segment) => {
|
|
2264
2383
|
if (segment.status === "awaiting-approval") {
|
|
2265
2384
|
const toolCallId = segment.toolCall?.id;
|
|
@@ -2326,11 +2445,12 @@ const toolCallRenderer = {
|
|
|
2326
2445
|
@keyframes tool-spin { to { transform: rotate(360deg); } }
|
|
2327
2446
|
`
|
|
2328
2447
|
};
|
|
2448
|
+
const waitingLabel = () => contextConfig().t("generating");
|
|
2329
2449
|
const pipelineWaitingRenderer = {
|
|
2330
2450
|
type: "pipeline-waiting",
|
|
2331
2451
|
render: (segment) => {
|
|
2332
2452
|
return `
|
|
2333
|
-
<div class="segment segment-pipeline-waiting" data-segment-id="${escapeHtml(segment.id)}" aria-label="
|
|
2453
|
+
<div class="segment segment-pipeline-waiting" data-segment-id="${escapeHtml(segment.id)}" aria-label="${escapeAttr(waitingLabel())}" role="status">
|
|
2334
2454
|
<span class="pw-dot"></span>
|
|
2335
2455
|
<span class="pw-dot"></span>
|
|
2336
2456
|
<span class="pw-dot"></span>
|
|
@@ -2338,6 +2458,15 @@ const pipelineWaitingRenderer = {
|
|
|
2338
2458
|
},
|
|
2339
2459
|
update: () => {
|
|
2340
2460
|
},
|
|
2461
|
+
/**
|
|
2462
|
+
* Three dots and an accessible name. The dots are CSS; the name is the whole
|
|
2463
|
+
* content as far as a screen reader is concerned, and it was hardcoded English in
|
|
2464
|
+
* every locale — the one string in this library where being untranslated is
|
|
2465
|
+
* invisible to everyone who can see the screen.
|
|
2466
|
+
*/
|
|
2467
|
+
relabel: (element) => {
|
|
2468
|
+
element.setAttribute("aria-label", waitingLabel());
|
|
2469
|
+
},
|
|
2341
2470
|
setup: (el2) => {
|
|
2342
2471
|
const parent = el2.parentElement;
|
|
2343
2472
|
if (!parent) return;
|
|
@@ -2422,26 +2551,6 @@ function findClosingFence(s) {
|
|
|
2422
2551
|
}
|
|
2423
2552
|
return -1;
|
|
2424
2553
|
}
|
|
2425
|
-
const _lastHighlightAt = /* @__PURE__ */ new Map();
|
|
2426
|
-
function markThrottle(map, id, at) {
|
|
2427
|
-
map.delete(id);
|
|
2428
|
-
if (map.size >= MAX_THROTTLE_ENTRIES) {
|
|
2429
|
-
const oldest = map.keys().next().value;
|
|
2430
|
-
if (oldest !== void 0) map.delete(oldest);
|
|
2431
|
-
}
|
|
2432
|
-
map.set(id, at);
|
|
2433
|
-
}
|
|
2434
|
-
function debounceHighlight(element, paneSelector, content, lang, segId) {
|
|
2435
|
-
const now = Date.now();
|
|
2436
|
-
const last = _lastHighlightAt.get(segId) ?? 0;
|
|
2437
|
-
if (now - last < HIGHLIGHT_DEBOUNCE_MS) return;
|
|
2438
|
-
markThrottle(_lastHighlightAt, segId, now);
|
|
2439
|
-
void contextConfig(element).highlightCode(content, lang).then((html) => {
|
|
2440
|
-
const wrapper = element.querySelector(paneSelector);
|
|
2441
|
-
if (wrapper) wrapper.innerHTML = html;
|
|
2442
|
-
}).catch(() => {
|
|
2443
|
-
});
|
|
2444
|
-
}
|
|
2445
2554
|
function labelForKind(kind) {
|
|
2446
2555
|
switch (kind) {
|
|
2447
2556
|
case "react":
|
|
@@ -2480,8 +2589,6 @@ function labelForKind(kind) {
|
|
|
2480
2589
|
return "Artifact";
|
|
2481
2590
|
}
|
|
2482
2591
|
}
|
|
2483
|
-
const MAX_THROTTLE_ENTRIES = 256;
|
|
2484
|
-
const HIGHLIGHT_DEBOUNCE_MS = 400;
|
|
2485
2592
|
const PREVIEW_CSP = "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:";
|
|
2486
2593
|
function withMetaCsp(doc) {
|
|
2487
2594
|
const meta = `<meta http-equiv="Content-Security-Policy" content="${escapeAttr(PREVIEW_CSP)}">`;
|
|
@@ -2498,9 +2605,18 @@ function buildPreviewBody(kind, body, title) {
|
|
|
2498
2605
|
if (startsWithDoctype(body)) return body;
|
|
2499
2606
|
return `<!doctype html><html><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>${escapeAttr(title)}</title></head><body>${body}</body></html>`;
|
|
2500
2607
|
}
|
|
2608
|
+
// An SVG with only a `viewBox` — the recommended, responsive form, and the one
|
|
2609
|
+
// a model writes most often — has NO intrinsic size. As a flex item its cross
|
|
2610
|
+
// size then collapses to zero and the frame is blank: the preview worked for an
|
|
2611
|
+
// SVG that declared `width`/`height` and silently showed nothing for the
|
|
2612
|
+
// idiomatic one. Reported from the landing the moment its demo payload became a
|
|
2613
|
+
// real file instead of a hand-written chart with dimensions on it.
|
|
2614
|
+
//
|
|
2615
|
+
// The rule is narrowed by attribute selector rather than applied to every SVG,
|
|
2616
|
+
// so an SVG that DOES state its size keeps the size it asked for.
|
|
2501
2617
|
case "svg":
|
|
2502
2618
|
return `<!doctype html><html><head><meta charset="utf-8"/><title>${escapeAttr(title)}</title>
|
|
2503
|
-
<style>html,body{margin:0;height:100%;display:flex;align-items:center;justify-content:center;background:#fff}svg{max-width:90%;max-height:90%}</style>
|
|
2619
|
+
<style>html,body{margin:0;height:100%;display:flex;align-items:center;justify-content:center;background:#fff}svg{max-width:90%;max-height:90%}svg:not([width]):not([height]){width:90%;height:90%}</style>
|
|
2504
2620
|
</head><body>${body}</body></html>`;
|
|
2505
2621
|
case "js": {
|
|
2506
2622
|
const safeBody = escapeClosingScriptTag(body);
|
|
@@ -2583,6 +2699,7 @@ function renderBinaryFileArtifact(segment, kind) {
|
|
|
2583
2699
|
const iconLabel = FILE_ICON_LABEL[kind] ?? kind.toUpperCase();
|
|
2584
2700
|
const isStreaming = !!segment.isStreaming;
|
|
2585
2701
|
const canRedownload = contextConfig().getHostHandlers().artifactRedownload;
|
|
2702
|
+
const downloadLabel = escapeHtml(contextConfig().t("download"));
|
|
2586
2703
|
const cached = _binaryArtifactCache.get(segment.id);
|
|
2587
2704
|
if (cached && !isStreaming) {
|
|
2588
2705
|
const previewBody = cached.previewHtml ? contextConfig().sanitizeHtml(cached.previewHtml) : `<div class="aparte-art-file__preview-empty">Preview not available for ${escapeHtml(kind)} yet</div>`;
|
|
@@ -2598,7 +2715,7 @@ function renderBinaryFileArtifact(segment, kind) {
|
|
|
2598
2715
|
<div class="aparte-art-file__meta-sub" data-role="file-sub">${escapeHtml(formatBytes(cached.bytes))} · ${escapeHtml(kind.toUpperCase())}</div>
|
|
2599
2716
|
</div>
|
|
2600
2717
|
<div class="aparte-art-file__actions">
|
|
2601
|
-
${canRedownload ?
|
|
2718
|
+
${canRedownload ? `<button type="button" class="aparte-art-file__btn aparte-art-file__btn--primary" data-action="download">${downloadLabel}</button>` : ""}
|
|
2602
2719
|
</div>
|
|
2603
2720
|
</div>
|
|
2604
2721
|
<div class="aparte-art-file__body">
|
|
@@ -2611,7 +2728,7 @@ function renderBinaryFileArtifact(segment, kind) {
|
|
|
2611
2728
|
`;
|
|
2612
2729
|
}
|
|
2613
2730
|
const cleanContent = stripCodeFences(segment.content || "");
|
|
2614
|
-
const subText = isStreaming ? "
|
|
2731
|
+
const subText = isStreaming ? contextConfig().t("generating") : contextConfig().t("rebuildingPreview");
|
|
2615
2732
|
return `
|
|
2616
2733
|
<div class="segment segment-artifact-file"
|
|
2617
2734
|
data-segment-id="${escapeHtml(segment.id)}"
|
|
@@ -2624,7 +2741,7 @@ function renderBinaryFileArtifact(segment, kind) {
|
|
|
2624
2741
|
<div class="aparte-art-file__meta-sub" data-role="file-sub">${escapeHtml(subText)}</div>
|
|
2625
2742
|
</div>
|
|
2626
2743
|
<div class="aparte-art-file__actions">
|
|
2627
|
-
${canRedownload ?
|
|
2744
|
+
${canRedownload ? `<button type="button" class="aparte-art-file__btn aparte-art-file__btn--primary" data-action="download" disabled>${downloadLabel}</button>` : ""}
|
|
2628
2745
|
</div>
|
|
2629
2746
|
</div>
|
|
2630
2747
|
<div class="aparte-art-file__body">
|
|
@@ -2730,12 +2847,11 @@ function updateBinaryFileArtifact(element, segment, isStreaming) {
|
|
|
2730
2847
|
const state = element.getAttribute("data-state");
|
|
2731
2848
|
if (state === "ready") return;
|
|
2732
2849
|
const cleanContent = stripCodeFences(segment.content || "");
|
|
2733
|
-
const codeEl = element.querySelector('[data-role="code-pane"] code');
|
|
2734
|
-
if (codeEl) {
|
|
2735
|
-
codeEl.textContent = cleanContent;
|
|
2736
|
-
}
|
|
2737
2850
|
if (isStreaming) {
|
|
2738
|
-
|
|
2851
|
+
streamHighlight(element, '[data-role="code-pane"]', cleanContent, "js", segment.id);
|
|
2852
|
+
} else {
|
|
2853
|
+
const codeEl = element.querySelector('[data-role="code-pane"] code');
|
|
2854
|
+
if (codeEl) codeEl.textContent = cleanContent;
|
|
2739
2855
|
}
|
|
2740
2856
|
if (!isStreaming && state === "streaming") {
|
|
2741
2857
|
element.setAttribute("data-state", "compiling");
|
|
@@ -2782,9 +2898,9 @@ function showSandboxError(element, phase, errorMsg) {
|
|
|
2782
2898
|
const short = (errorMsg.split("\n")[0] ?? "").slice(0, 240);
|
|
2783
2899
|
body.innerHTML = `
|
|
2784
2900
|
<div class="aparte-art-file__error">
|
|
2785
|
-
<div class="aparte-art-file__error-title"
|
|
2901
|
+
<div class="aparte-art-file__error-title">${escapeHtml(contextConfig().t("sandboxError"))}</div>
|
|
2786
2902
|
<div class="aparte-art-file__error-msg">${escapeHtml(short)}</div>
|
|
2787
|
-
<div class="aparte-art-file__error-hint"
|
|
2903
|
+
<div class="aparte-art-file__error-hint">${escapeHtml(contextConfig().t("sandboxErrorHint"))}</div>
|
|
2788
2904
|
</div>
|
|
2789
2905
|
`;
|
|
2790
2906
|
}
|
|
@@ -2802,6 +2918,10 @@ const artifactRenderer = {
|
|
|
2802
2918
|
const displayLang = languageForKind(kind);
|
|
2803
2919
|
const isStreaming = !!segment.isStreaming;
|
|
2804
2920
|
const previewable = PREVIEWABLE_KINDS.has(kind);
|
|
2921
|
+
const cfg = contextConfig();
|
|
2922
|
+
const downloadLabel = cfg.t("download");
|
|
2923
|
+
const previewLabel = cfg.t("preview");
|
|
2924
|
+
const codeLabel = cfg.t("code");
|
|
2805
2925
|
const isBinary = BINARY_FILE_KINDS.has(kind);
|
|
2806
2926
|
const cleanContent = stripCodeFences(segment.content || "");
|
|
2807
2927
|
return `
|
|
@@ -2819,17 +2939,17 @@ const artifactRenderer = {
|
|
|
2819
2939
|
${isStreaming ? '<span class="aparte-art-card__pulse" aria-label="Streaming"></span>' : ""}
|
|
2820
2940
|
</div>
|
|
2821
2941
|
<div class="aparte-art-card__actions">
|
|
2822
|
-
<button type="button" class="aparte-art-card__btn" data-action="copy" title="${escapeAttr(contextConfig().t("copy"))}" aria-label="
|
|
2942
|
+
<button type="button" class="aparte-art-card__btn" data-action="copy" title="${escapeAttr(contextConfig().t("copy"))}" aria-label="${escapeAttr(contextConfig().t("copy"))}">
|
|
2823
2943
|
${contextConfig().getIcon("copy")}
|
|
2824
2944
|
</button>
|
|
2825
|
-
<button type="button" class="aparte-art-card__btn" data-action="download" title="
|
|
2945
|
+
<button type="button" class="aparte-art-card__btn" data-action="download" title="${escapeAttr(downloadLabel)}" aria-label="${escapeAttr(downloadLabel)}" ${isStreaming ? "disabled" : ""}>
|
|
2826
2946
|
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2v9m0 0l-3-3m3 3l3-3M2 13h12"/></svg>
|
|
2827
2947
|
</button>
|
|
2828
2948
|
</div>
|
|
2829
2949
|
</header>
|
|
2830
2950
|
<nav class="aparte-art-card__tabs" role="tablist">
|
|
2831
|
-
|
|
2832
|
-
|
|
2951
|
+
<button type="button" role="tab" data-tab-target="code" aria-selected="true">${escapeHtml(codeLabel)}</button>
|
|
2952
|
+
${previewable ? `<button type="button" role="tab" data-tab-target="preview" aria-selected="false" ${isStreaming ? "disabled" : ""}>${escapeHtml(previewLabel)}</button>` : ""}
|
|
2833
2953
|
</nav>
|
|
2834
2954
|
<div class="aparte-art-card__body">
|
|
2835
2955
|
<div class="aparte-art-card__pane" data-pane="code">
|
|
@@ -2839,13 +2959,44 @@ const artifactRenderer = {
|
|
|
2839
2959
|
</div>
|
|
2840
2960
|
${previewable ? `
|
|
2841
2961
|
<div class="aparte-art-card__pane" data-pane="preview">
|
|
2842
|
-
<div class="aparte-art-card__pending"
|
|
2962
|
+
<div class="aparte-art-card__pending">${escapeHtml(cfg.t("previewPending"))}</div>
|
|
2843
2963
|
</div>
|
|
2844
2964
|
` : ""}
|
|
2845
2965
|
</div>
|
|
2846
2966
|
</div>
|
|
2847
2967
|
`;
|
|
2848
2968
|
},
|
|
2969
|
+
/**
|
|
2970
|
+
* Every string this card shows: the copy button's tooltip, glyph and accessible
|
|
2971
|
+
* name, the download button's title and label, and the two tab names. They were
|
|
2972
|
+
* all hardcoded literals until they got locale keys — including the copy button's
|
|
2973
|
+
* `aria-label`, which said "Copy" while its own `title` one attribute away already
|
|
2974
|
+
* went through `t('copy')`, so a French reader got a French tooltip and an English
|
|
2975
|
+
* announcement.
|
|
2976
|
+
*
|
|
2977
|
+
* Nothing here touches the tab state or the preview pane: a mounted iframe is
|
|
2978
|
+
* running model-authored code, and re-rendering this card is exactly what the
|
|
2979
|
+
* hook exists to avoid.
|
|
2980
|
+
*/
|
|
2981
|
+
relabel: (element) => {
|
|
2982
|
+
const cfg = contextConfig();
|
|
2983
|
+
const copyBtn = element.querySelector('.aparte-art-card__btn[data-action="copy"]');
|
|
2984
|
+
if (copyBtn) {
|
|
2985
|
+
copyBtn.setAttribute("title", cfg.t("copy"));
|
|
2986
|
+
copyBtn.setAttribute("aria-label", cfg.t("copy"));
|
|
2987
|
+
copyBtn.innerHTML = cfg.getIcon("copy");
|
|
2988
|
+
}
|
|
2989
|
+
const dl = element.querySelector('.aparte-art-card__btn[data-action="download"]');
|
|
2990
|
+
if (dl) {
|
|
2991
|
+
const label = cfg.t("download");
|
|
2992
|
+
dl.setAttribute("title", label);
|
|
2993
|
+
dl.setAttribute("aria-label", label);
|
|
2994
|
+
}
|
|
2995
|
+
const previewTab = element.querySelector('[data-tab-target="preview"]');
|
|
2996
|
+
if (previewTab) previewTab.textContent = cfg.t("preview");
|
|
2997
|
+
const codeTab = element.querySelector('[data-tab-target="code"]');
|
|
2998
|
+
if (codeTab) codeTab.textContent = cfg.t("code");
|
|
2999
|
+
},
|
|
2849
3000
|
setup: (element, segment) => {
|
|
2850
3001
|
latestSegment.set(element, segment);
|
|
2851
3002
|
const kind = (segment.artifactType || "").toLowerCase();
|
|
@@ -2926,22 +3077,24 @@ const artifactRenderer = {
|
|
|
2926
3077
|
}
|
|
2927
3078
|
const wasStreaming = element.getAttribute("data-streaming") === "true";
|
|
2928
3079
|
const cleanContent = stripCodeFences(segment.content || "");
|
|
2929
|
-
const codeEl = element.querySelector(".code-content-wrapper code");
|
|
2930
|
-
if (codeEl) {
|
|
2931
|
-
codeEl.textContent = cleanContent;
|
|
2932
|
-
} else {
|
|
2933
|
-
const wrapper = element.querySelector(".code-content-wrapper");
|
|
2934
|
-
if (wrapper) {
|
|
2935
|
-
const displayLang = languageForKind(kind);
|
|
2936
|
-
wrapper.innerHTML = `<pre><code class="language-${escapeHtml(displayLang)}">${escapeHtml(cleanContent)}</code></pre>`;
|
|
2937
|
-
}
|
|
2938
|
-
}
|
|
2939
3080
|
if (isStreaming) {
|
|
2940
3081
|
const segId = element.getAttribute("data-segment-id") ?? segment.id;
|
|
2941
|
-
|
|
3082
|
+
streamHighlight(element, ".code-content-wrapper", cleanContent, languageForKind(kind), segId);
|
|
3083
|
+
} else {
|
|
3084
|
+
const codeEl = element.querySelector(".code-content-wrapper code");
|
|
3085
|
+
if (codeEl) {
|
|
3086
|
+
codeEl.textContent = cleanContent;
|
|
3087
|
+
} else {
|
|
3088
|
+
const wrapper = element.querySelector(".code-content-wrapper");
|
|
3089
|
+
if (wrapper) {
|
|
3090
|
+
const displayLang = languageForKind(kind);
|
|
3091
|
+
wrapper.innerHTML = `<pre><code class="language-${escapeHtml(displayLang)}">${escapeHtml(cleanContent)}</code></pre>`;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
2942
3094
|
}
|
|
2943
3095
|
if (wasStreaming && !isStreaming) {
|
|
2944
3096
|
element.setAttribute("data-streaming", "false");
|
|
3097
|
+
element.querySelector(".aparte-art-card__pulse")?.remove();
|
|
2945
3098
|
const wrapper = element.querySelector(".code-content-wrapper");
|
|
2946
3099
|
if (wrapper) {
|
|
2947
3100
|
const displayLang = languageForKind(kind);
|
|
@@ -3013,7 +3166,24 @@ const artifactRenderer = {
|
|
|
3013
3166
|
}
|
|
3014
3167
|
.aparte-art-card__btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3015
3168
|
.aparte-art-card__tabs {
|
|
3016
|
-
|
|
3169
|
+
/* justify-content and the padding are DECLARED, not left to a default.
|
|
3170
|
+
Core is light DOM on purpose - no shadow root, no ::part(), any selector
|
|
3171
|
+
reaches in - and the corollary is that a component must state what its
|
|
3172
|
+
layout depends on, because an undeclared property has nothing to override
|
|
3173
|
+
a host rule with. A consuming page with a bare nav selector setting
|
|
3174
|
+
justify-content: space-between and padding-top (this library's own docs
|
|
3175
|
+
site had exactly that) otherwise pushes these two tabs to opposite ends of
|
|
3176
|
+
the card and pads the row out. No backticks in here: this block is a JS
|
|
3177
|
+
template literal, and one of them ended it.
|
|
3178
|
+
|
|
3179
|
+
flex-end, and Code first in the DOM. The card OPENS on Code - mounting the
|
|
3180
|
+
preview would execute model-authored code with no gesture (ratified
|
|
3181
|
+
decision #8) - and a selected tab sitting second reads backwards. Right,
|
|
3182
|
+
because the header above puts the artifact's identity on the left and its
|
|
3183
|
+
copy/download buttons on the right, so this keeps every control in one
|
|
3184
|
+
column. DOM order is also keyboard order, so the tab a reader lands on
|
|
3185
|
+
first is the one already showing. */
|
|
3186
|
+
display: flex; justify-content: flex-end; align-items: stretch; gap: 2px;
|
|
3017
3187
|
padding: 4px 8px 0;
|
|
3018
3188
|
border-bottom: 1px solid var(--aparte-border, rgba(0,0,0,0.08));
|
|
3019
3189
|
background: var(--aparte-surface-2, rgba(0,0,0,0.02));
|
|
@@ -3036,17 +3206,37 @@ const artifactRenderer = {
|
|
|
3036
3206
|
font-weight: 500;
|
|
3037
3207
|
}
|
|
3038
3208
|
.aparte-art-card__tabs button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3209
|
+
/* The card's heights, as variables with ONE owner each.
|
|
3210
|
+
They were four hardcoded numbers in the only part of this card that did not
|
|
3211
|
+
use a variable - everything else here already reads var(--aparte-code-bg) and
|
|
3212
|
+
friends - and two of them had to agree while a third contradicted a fourth:
|
|
3213
|
+
the code pane repeated the body's 600px, and the "press Preview" placeholder
|
|
3214
|
+
was 120px tall inside a body whose min-height said 80, so that minimum applied
|
|
3215
|
+
to nothing.
|
|
3216
|
+
The frame stays a FIXED height rather than an aspect ratio, which is what
|
|
3217
|
+
embeds of arbitrary HTML actually do - CodeSandbox documents 500px, StackBlitz
|
|
3218
|
+
takes a height parameter - because a frame with an opaque origin cannot be
|
|
3219
|
+
measured and a 16/10 ratio on a wide card is enormous. The vh cap is the part
|
|
3220
|
+
that was missing: a fixed 480px should not own a phone screen.
|
|
3221
|
+
Each default lives in its read, as var(--x, literal), the way every other
|
|
3222
|
+
value in this file already does - not in a declaration block on top of the
|
|
3223
|
+
fallbacks, which would be two owners of one number again. It also means the
|
|
3224
|
+
docs' CSS-variable generator finds them: its sweep looks for reads that
|
|
3225
|
+
carry a fallback, so a read without one is a public knob nobody documents. */
|
|
3039
3226
|
.aparte-art-card__body {
|
|
3040
3227
|
position: relative;
|
|
3041
|
-
|
|
3042
|
-
|
|
3228
|
+
/* The placeholder is the tallest thing this box can hold while empty, so it
|
|
3229
|
+
IS the minimum - one number instead of two that disagreed. */
|
|
3230
|
+
min-height: var(--aparte-artifact-pending-height, 120px);
|
|
3231
|
+
max-height: var(--aparte-artifact-body-max, 600px);
|
|
3043
3232
|
overflow: hidden;
|
|
3044
3233
|
}
|
|
3045
3234
|
.aparte-art-card__pane { display: none; height: 100%; }
|
|
3046
3235
|
.segment-artifact-card[data-tab="code"] .aparte-art-card__pane[data-pane="code"] { display: block; }
|
|
3047
3236
|
.segment-artifact-card[data-tab="preview"] .aparte-art-card__pane[data-pane="preview"] { display: block; }
|
|
3048
3237
|
.aparte-art-card__pane[data-pane="code"] {
|
|
3049
|
-
|
|
3238
|
+
/* The body already caps this; repeating the number was the second owner. */
|
|
3239
|
+
max-height: var(--aparte-artifact-body-max, 600px); overflow: auto;
|
|
3050
3240
|
}
|
|
3051
3241
|
.aparte-art-card__pane[data-pane="code"] pre {
|
|
3052
3242
|
margin: 0; padding: 12px;
|
|
@@ -3055,13 +3245,14 @@ const artifactRenderer = {
|
|
|
3055
3245
|
}
|
|
3056
3246
|
.aparte-art-card__frame {
|
|
3057
3247
|
display: block;
|
|
3058
|
-
width: 100%;
|
|
3248
|
+
width: 100%;
|
|
3249
|
+
height: min(var(--aparte-artifact-frame-height, 480px), var(--aparte-artifact-frame-max, 70vh));
|
|
3059
3250
|
border: 0;
|
|
3060
3251
|
background: #fff;
|
|
3061
3252
|
}
|
|
3062
3253
|
.aparte-art-card__pending {
|
|
3063
3254
|
display: flex; align-items: center; justify-content: center;
|
|
3064
|
-
height: 120px;
|
|
3255
|
+
height: var(--aparte-artifact-pending-height, 120px);
|
|
3065
3256
|
color: var(--aparte-text-muted, rgba(0,0,0,0.5));
|
|
3066
3257
|
font-size: 0.85rem;
|
|
3067
3258
|
font-style: italic;
|
|
@@ -3087,7 +3278,7 @@ const artifactRenderer = {
|
|
|
3087
3278
|
position: relative;
|
|
3088
3279
|
}
|
|
3089
3280
|
.aparte-art-file__code-pane {
|
|
3090
|
-
max-height: 360px; overflow: auto;
|
|
3281
|
+
max-height: var(--aparte-artifact-file-code-max, 360px); overflow: auto;
|
|
3091
3282
|
background: var(--aparte-code-bg, #fafafa);
|
|
3092
3283
|
}
|
|
3093
3284
|
.aparte-art-file__code-pane pre {
|
|
@@ -3096,7 +3287,7 @@ const artifactRenderer = {
|
|
|
3096
3287
|
font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
|
|
3097
3288
|
}
|
|
3098
3289
|
.aparte-art-file__preview-pane {
|
|
3099
|
-
max-height: 460px;
|
|
3290
|
+
max-height: var(--aparte-artifact-file-preview-max, 460px);
|
|
3100
3291
|
overflow: auto;
|
|
3101
3292
|
background: #fff;
|
|
3102
3293
|
/* Preview is a document view — force light scheme regardless of
|
|
@@ -3344,27 +3535,140 @@ function injectRendererStyles(config = contextConfig()) {
|
|
|
3344
3535
|
if (added) styleElement.textContent = Array.from(injectedRendererStyles).join("\n");
|
|
3345
3536
|
}
|
|
3346
3537
|
function registerDefaultRenderers(config = contextConfig()) {
|
|
3347
|
-
registryFor(config)
|
|
3348
|
-
|
|
3538
|
+
const reg = registryFor(config);
|
|
3539
|
+
reg.defaultsInstalled = true;
|
|
3540
|
+
for (const renderer of DEFAULT_RENDERERS) {
|
|
3541
|
+
if (!reg.renderers.has(renderer.type)) registerSegmentRenderer(renderer, config);
|
|
3542
|
+
}
|
|
3543
|
+
injectRendererStyles(config);
|
|
3349
3544
|
installArtifactReadyHook();
|
|
3350
3545
|
}
|
|
3351
3546
|
const DEFAULT_RENDERERS = [
|
|
3352
3547
|
textRenderer,
|
|
3353
3548
|
thinkingRenderer,
|
|
3354
3549
|
codeRenderer,
|
|
3355
|
-
terminalRenderer,
|
|
3356
3550
|
errorRenderer,
|
|
3357
3551
|
progressRenderer,
|
|
3358
|
-
fileTreeRenderer,
|
|
3359
3552
|
toolCallRenderer,
|
|
3360
3553
|
artifactRenderer,
|
|
3361
3554
|
pipelineWaitingRenderer
|
|
3362
3555
|
];
|
|
3556
|
+
const OPEN_TOOL_STATUSES = /* @__PURE__ */ new Set(["pending", "awaiting-approval"]);
|
|
3557
|
+
const ACTIVITY_KEYS = ["content", "output", "result"];
|
|
3558
|
+
function isActivity(updates) {
|
|
3559
|
+
return ACTIVITY_KEYS.some((key) => key in updates);
|
|
3560
|
+
}
|
|
3561
|
+
function stampSegmentOnInsert(segments, segment, messageId, defaults) {
|
|
3562
|
+
const base = applyDefaults(segment, defaults);
|
|
3563
|
+
return {
|
|
3564
|
+
...base,
|
|
3565
|
+
messageId: segment.messageId ?? messageId,
|
|
3566
|
+
index: segment.index ?? segments.length,
|
|
3567
|
+
// A start already present is kept — a segment a consumer rebuilt from their own
|
|
3568
|
+
// storage keeps the number it was persisted with. `adoptSegment` is the path
|
|
3569
|
+
// that never writes one at all.
|
|
3570
|
+
...timingPatch(base, { startedAt: segmentTiming(segment)?.startedAt ?? Date.now() })
|
|
3571
|
+
};
|
|
3572
|
+
}
|
|
3573
|
+
function adoptSegment(segments, segment, messageId) {
|
|
3574
|
+
return {
|
|
3575
|
+
...segment,
|
|
3576
|
+
messageId,
|
|
3577
|
+
index: segments.length,
|
|
3578
|
+
isStreaming: false
|
|
3579
|
+
};
|
|
3580
|
+
}
|
|
3581
|
+
const RESERVED = /* @__PURE__ */ new Set(["id", "type", "messageId", "index"]);
|
|
3582
|
+
function segmentTiming(segment) {
|
|
3583
|
+
return segment.meta?.aparte;
|
|
3584
|
+
}
|
|
3585
|
+
function timingPatch(segment, timing) {
|
|
3586
|
+
return { meta: { ...segment.meta, aparte: { ...segment.meta?.aparte, ...timing } } };
|
|
3587
|
+
}
|
|
3588
|
+
function applyDefaults(segment, defaults) {
|
|
3589
|
+
if (!defaults) return segment;
|
|
3590
|
+
const out = { ...segment };
|
|
3591
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
3592
|
+
if (RESERVED.has(key) || key in segment) continue;
|
|
3593
|
+
if (key === "meta") {
|
|
3594
|
+
const { aparte: _forged, ...rest } = value ?? {};
|
|
3595
|
+
out.meta = rest;
|
|
3596
|
+
continue;
|
|
3597
|
+
}
|
|
3598
|
+
out[key] = value;
|
|
3599
|
+
}
|
|
3600
|
+
return out;
|
|
3601
|
+
}
|
|
3602
|
+
function isSegmentSettled(segment) {
|
|
3603
|
+
if (segment.type === "tool_call") return !OPEN_TOOL_STATUSES.has(segment.status);
|
|
3604
|
+
return segment.isStreaming === false;
|
|
3605
|
+
}
|
|
3606
|
+
function stampSegmentOnUpdate(segment, updates) {
|
|
3607
|
+
if (isSegmentSettled(segment)) return updates;
|
|
3608
|
+
const merged = { ...segment, ...updates };
|
|
3609
|
+
if (isSegmentSettled(merged)) {
|
|
3610
|
+
const ended = segmentTiming(segment)?.endedAt;
|
|
3611
|
+
return { ...updates, ...timingPatch(segment, { endedAt: ended ?? Date.now() }) };
|
|
3612
|
+
}
|
|
3613
|
+
return isActivity(updates) ? { ...updates, ...timingPatch(segment, { endedAt: Date.now() }) } : updates;
|
|
3614
|
+
}
|
|
3615
|
+
function mergeSegmentUpdate(segment, updates) {
|
|
3616
|
+
const merged = { ...segment, ...updates };
|
|
3617
|
+
if (!("meta" in updates)) return merged;
|
|
3618
|
+
return {
|
|
3619
|
+
...merged,
|
|
3620
|
+
meta: {
|
|
3621
|
+
...segment.meta,
|
|
3622
|
+
...updates.meta,
|
|
3623
|
+
// Neither side's timing wins by accident: whichever one carries it, wins,
|
|
3624
|
+
// and a consumer writing `meta` without an `aparte` key keeps ours.
|
|
3625
|
+
...segment.meta?.aparte || updates.meta?.aparte ? { aparte: { ...segment.meta?.aparte, ...updates.meta?.aparte } } : {}
|
|
3626
|
+
}
|
|
3627
|
+
};
|
|
3628
|
+
}
|
|
3629
|
+
function segmentDuration(segment) {
|
|
3630
|
+
const { startedAt, endedAt } = segmentTiming(segment) ?? {};
|
|
3631
|
+
if (startedAt == null || endedAt == null) return void 0;
|
|
3632
|
+
return endedAt - startedAt;
|
|
3633
|
+
}
|
|
3634
|
+
function stampSegmentActivity(segment) {
|
|
3635
|
+
return isSegmentSettled(segment) ? {} : timingPatch(segment, { endedAt: Date.now() });
|
|
3636
|
+
}
|
|
3637
|
+
function segmentContentUpdate(segment) {
|
|
3638
|
+
const content = segment.content;
|
|
3639
|
+
return {
|
|
3640
|
+
content,
|
|
3641
|
+
...segment.isStreaming === false ? { isStreaming: false } : {}
|
|
3642
|
+
};
|
|
3643
|
+
}
|
|
3644
|
+
function isTerminalStatus(status) {
|
|
3645
|
+
return status != null && status !== "streaming" && status !== "pending";
|
|
3646
|
+
}
|
|
3647
|
+
function openSegmentIds(segments) {
|
|
3648
|
+
return segments.filter((segment) => !isSegmentSettled(segment)).map((segment) => segment.id);
|
|
3649
|
+
}
|
|
3650
|
+
function adoptMessageSegments(message) {
|
|
3651
|
+
if (!message.segments?.length) return { ...message };
|
|
3652
|
+
return {
|
|
3653
|
+
...message,
|
|
3654
|
+
segments: message.segments.reduce((acc, segment) => {
|
|
3655
|
+
acc.push(adoptSegment(acc, segment, message.id));
|
|
3656
|
+
return acc;
|
|
3657
|
+
}, [])
|
|
3658
|
+
};
|
|
3659
|
+
}
|
|
3660
|
+
function renumberSegments(segments) {
|
|
3661
|
+
for (let i = 0; i < segments.length; i++) {
|
|
3662
|
+
const segment = segments[i];
|
|
3663
|
+
if (segment.index === i) continue;
|
|
3664
|
+
segments[i] = { ...segment, index: i };
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3363
3667
|
function populateBubbleFromMessage(bubble, message, siblingInfo) {
|
|
3364
3668
|
if (message.segments && message.segments.length > 0) {
|
|
3365
3669
|
const existing = bubble.getSegments?.() ?? [];
|
|
3366
3670
|
if (existing.length === message.segments.length) {
|
|
3367
|
-
const settled = message.status
|
|
3671
|
+
const settled = isTerminalStatus(message.status);
|
|
3368
3672
|
for (const seg of message.segments) {
|
|
3369
3673
|
if ("content" in seg) {
|
|
3370
3674
|
const patch = { content: seg.content };
|
|
@@ -4053,10 +4357,14 @@ class AparteChatHost {
|
|
|
4053
4357
|
// Controller-driven flushes mirror AparteConversationManager state, not
|
|
4054
4358
|
// user intent — deliberately NOT emitted as `messagesChange` to
|
|
4055
4359
|
// avoid a feedback loop with hosts using a two-way `messages` input.
|
|
4360
|
+
// Adopted here rather than passed through: this is the conversation
|
|
4361
|
+
// controller handing back a stored list, and it goes STRAIGHT to the
|
|
4362
|
+
// framework's own reactive setter — so in every wrapper it never met the
|
|
4363
|
+
// stamping seam at all, tree or no tree.
|
|
4056
4364
|
setMessages: (msgs) => {
|
|
4057
4365
|
this._beginConversationSwap();
|
|
4058
4366
|
this._vp()?.resetSpacer?.();
|
|
4059
|
-
this.binding.setMessages(
|
|
4367
|
+
this.binding.setMessages(msgs.map(adoptMessageSegments));
|
|
4060
4368
|
},
|
|
4061
4369
|
appendMessage: (msg) => this.appendMessage(msg),
|
|
4062
4370
|
getMessages: () => this.binding.getMessages(),
|
|
@@ -4107,11 +4415,23 @@ class AparteChatHost {
|
|
|
4107
4415
|
* spec guards ("optimistic-append + parent-push race"). `onMessageAppended`
|
|
4108
4416
|
* is the append-specific signal instead.
|
|
4109
4417
|
*/
|
|
4110
|
-
appendMessage(message) {
|
|
4418
|
+
appendMessage(message, options) {
|
|
4111
4419
|
this._flushStreamState();
|
|
4112
4420
|
if (message.role === "user") this._vp()?.setAutoScroll?.(true);
|
|
4113
|
-
|
|
4114
|
-
|
|
4421
|
+
const stored = options?.historical ? adoptMessageSegments(message) : message.segments?.length ? {
|
|
4422
|
+
...message,
|
|
4423
|
+
segments: message.segments.reduce((acc, segment) => {
|
|
4424
|
+
acc.push(stampSegmentOnInsert(
|
|
4425
|
+
acc,
|
|
4426
|
+
segment,
|
|
4427
|
+
message.id,
|
|
4428
|
+
resolveConfig(this.binding.host).getSegmentDefaults(segment.type)
|
|
4429
|
+
));
|
|
4430
|
+
return acc;
|
|
4431
|
+
}, [])
|
|
4432
|
+
} : message;
|
|
4433
|
+
this.binding.setMessages([...this.binding.getMessages(), stored]);
|
|
4434
|
+
this.binding.onMessageAppended?.(stored);
|
|
4115
4435
|
}
|
|
4116
4436
|
/** Atomic partial update of a message by id. */
|
|
4117
4437
|
updateMessage(messageId, updates) {
|
|
@@ -4120,12 +4440,18 @@ class AparteChatHost {
|
|
|
4120
4440
|
const index = msgs.findIndex((m) => m.id === messageId);
|
|
4121
4441
|
if (index === -1) return;
|
|
4122
4442
|
const next = [...msgs];
|
|
4123
|
-
|
|
4443
|
+
const merged = { ...next[index], ...updates };
|
|
4444
|
+
next[index] = merged;
|
|
4124
4445
|
this.binding.setMessages(next);
|
|
4125
4446
|
this.binding.onMessagesChange?.(next);
|
|
4126
4447
|
if (updates.status) {
|
|
4127
4448
|
this._bubbleById(messageId)?.updateMessage?.({ status: updates.status });
|
|
4128
4449
|
}
|
|
4450
|
+
if (isTerminalStatus(merged.status) && merged.segments) {
|
|
4451
|
+
for (const id of openSegmentIds(merged.segments)) {
|
|
4452
|
+
this.updateSegment(id, { isStreaming: false });
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4129
4455
|
}
|
|
4130
4456
|
/** Append/replace the last message content (streaming text). */
|
|
4131
4457
|
updateLastMessage(content, options) {
|
|
@@ -4142,13 +4468,24 @@ class AparteChatHost {
|
|
|
4142
4468
|
/** Add a segment to the last message (immediate bubble + state sync). */
|
|
4143
4469
|
addSegment(segment) {
|
|
4144
4470
|
this._flushStreamState();
|
|
4145
|
-
const
|
|
4146
|
-
const
|
|
4147
|
-
if (
|
|
4471
|
+
const before = this.binding.getMessages();
|
|
4472
|
+
const target = before[before.length - 1];
|
|
4473
|
+
if (target && this._isOrphan(target.id)) return;
|
|
4148
4474
|
this.binding.onTypingChange?.(false);
|
|
4149
|
-
|
|
4475
|
+
const msgs = before;
|
|
4476
|
+
const last = target;
|
|
4477
|
+
const stamped = last ? stampSegmentOnInsert(
|
|
4478
|
+
last.segments || [],
|
|
4479
|
+
segment,
|
|
4480
|
+
last.id,
|
|
4481
|
+
// The host attaches its config to `binding.host`, so resolving from
|
|
4482
|
+
// that element is what gives a framework-managed chat its OWN
|
|
4483
|
+
// defaults rather than the page's.
|
|
4484
|
+
resolveConfig(this.binding.host).getSegmentDefaults(segment.type)
|
|
4485
|
+
) : segment;
|
|
4486
|
+
this._lastBubble()?.addSegment?.(stamped);
|
|
4150
4487
|
if (!last) return;
|
|
4151
|
-
const segments = [...last.segments || [],
|
|
4488
|
+
const segments = [...last.segments || [], stamped];
|
|
4152
4489
|
const next = [...msgs.slice(0, -1), { ...last, segments }];
|
|
4153
4490
|
this.binding.setMessages(next);
|
|
4154
4491
|
this.binding.onMessagesChange?.(next);
|
|
@@ -4159,10 +4496,12 @@ class AparteChatHost {
|
|
|
4159
4496
|
const msgs = this.binding.getMessages();
|
|
4160
4497
|
const last = msgs[msgs.length - 1];
|
|
4161
4498
|
if (last && this._isOrphan(last.id)) return;
|
|
4162
|
-
|
|
4499
|
+
const current = last?.segments?.find((s) => s.id === segmentId);
|
|
4500
|
+
const stamped = current ? stampSegmentOnUpdate(current, updates) : updates;
|
|
4501
|
+
this._lastBubble()?.updateSegment?.(segmentId, stamped);
|
|
4163
4502
|
if (!last || !last.segments) return;
|
|
4164
4503
|
const segments = last.segments.map(
|
|
4165
|
-
(s) => s.id === segmentId ?
|
|
4504
|
+
(s) => s.id === segmentId ? mergeSegmentUpdate(s, stamped) : s
|
|
4166
4505
|
);
|
|
4167
4506
|
const next = [...msgs.slice(0, -1), { ...last, segments }];
|
|
4168
4507
|
this.binding.setMessages(next);
|
|
@@ -4177,6 +4516,7 @@ class AparteChatHost {
|
|
|
4177
4516
|
if (!last?.segments) return;
|
|
4178
4517
|
const segments = last.segments.filter((s) => s.id !== segmentId);
|
|
4179
4518
|
if (segments.length === last.segments.length) return;
|
|
4519
|
+
renumberSegments(segments);
|
|
4180
4520
|
const next = [...msgs.slice(0, -1), { ...last, segments }];
|
|
4181
4521
|
this.binding.setMessages(next);
|
|
4182
4522
|
this.binding.onMessagesChange?.(next);
|
|
@@ -4258,7 +4598,12 @@ class AparteChatHost {
|
|
|
4258
4598
|
if (last?.segments) {
|
|
4259
4599
|
const segments = last.segments.map((s) => {
|
|
4260
4600
|
const buffered = pending.get(s.id);
|
|
4261
|
-
|
|
4601
|
+
if (buffered === void 0 || !("content" in s)) return s;
|
|
4602
|
+
return {
|
|
4603
|
+
...s,
|
|
4604
|
+
content: buffered.base + buffered.text,
|
|
4605
|
+
...stampSegmentActivity(s)
|
|
4606
|
+
};
|
|
4262
4607
|
});
|
|
4263
4608
|
msgs = [...msgs.slice(0, -1), { ...last, segments }];
|
|
4264
4609
|
changed = true;
|
|
@@ -4587,7 +4932,7 @@ function feedXmlArtifactDelta(delta, xml, ctx) {
|
|
|
4587
4932
|
targetElement.addSegment?.(seg);
|
|
4588
4933
|
streamingSegmentIds.add(seg.id);
|
|
4589
4934
|
} else if ("content" in seg) {
|
|
4590
|
-
targetElement.updateSegment?.(seg.id,
|
|
4935
|
+
targetElement.updateSegment?.(seg.id, segmentContentUpdate(seg));
|
|
4591
4936
|
}
|
|
4592
4937
|
}
|
|
4593
4938
|
const active = textParser.getState().activeSegment;
|
|
@@ -4596,7 +4941,7 @@ function feedXmlArtifactDelta(delta, xml, ctx) {
|
|
|
4596
4941
|
targetElement.addSegment?.(active);
|
|
4597
4942
|
streamingSegmentIds.add(active.id);
|
|
4598
4943
|
} else {
|
|
4599
|
-
targetElement.updateSegment?.(active.id,
|
|
4944
|
+
targetElement.updateSegment?.(active.id, segmentContentUpdate(active));
|
|
4600
4945
|
}
|
|
4601
4946
|
} else if (!r.segments.length && !active) ;
|
|
4602
4947
|
};
|
|
@@ -4707,7 +5052,7 @@ function finalizeXmlArtifact(xml, ctx) {
|
|
|
4707
5052
|
targetElement.addSegment?.(seg);
|
|
4708
5053
|
ctx.streamingSegmentIds.add(seg.id);
|
|
4709
5054
|
} else if ("content" in seg) {
|
|
4710
|
-
targetElement.updateSegment?.(seg.id,
|
|
5055
|
+
targetElement.updateSegment?.(seg.id, segmentContentUpdate(seg));
|
|
4711
5056
|
}
|
|
4712
5057
|
}
|
|
4713
5058
|
const active = ctx.textParser.getState().activeSegment;
|
|
@@ -4716,7 +5061,7 @@ function finalizeXmlArtifact(xml, ctx) {
|
|
|
4716
5061
|
targetElement.addSegment?.(active);
|
|
4717
5062
|
ctx.streamingSegmentIds.add(active.id);
|
|
4718
5063
|
} else {
|
|
4719
|
-
targetElement.updateSegment?.(active.id,
|
|
5064
|
+
targetElement.updateSegment?.(active.id, segmentContentUpdate(active));
|
|
4720
5065
|
}
|
|
4721
5066
|
}
|
|
4722
5067
|
}
|
|
@@ -4782,7 +5127,7 @@ function createStreamAdapter(opts) {
|
|
|
4782
5127
|
}
|
|
4783
5128
|
case "text-delta": {
|
|
4784
5129
|
if (thinkingId && !thinkingCollapsed) {
|
|
4785
|
-
target.updateSegment?.(thinkingId, { collapsed: true });
|
|
5130
|
+
target.updateSegment?.(thinkingId, { collapsed: true, isStreaming: false });
|
|
4786
5131
|
thinkingCollapsed = true;
|
|
4787
5132
|
}
|
|
4788
5133
|
if (e.reduced) {
|
|
@@ -4792,7 +5137,7 @@ function createStreamAdapter(opts) {
|
|
|
4792
5137
|
target.addSegment?.(segment);
|
|
4793
5138
|
streaming.add(segment.id);
|
|
4794
5139
|
} else if ("content" in segment) {
|
|
4795
|
-
target.updateSegment?.(segment.id,
|
|
5140
|
+
target.updateSegment?.(segment.id, segmentContentUpdate(segment));
|
|
4796
5141
|
}
|
|
4797
5142
|
}
|
|
4798
5143
|
break;
|
|
@@ -4816,7 +5161,7 @@ function createStreamAdapter(opts) {
|
|
|
4816
5161
|
target.addSegment?.(segment);
|
|
4817
5162
|
streaming.add(segment.id);
|
|
4818
5163
|
} else if ("content" in segment) {
|
|
4819
|
-
target.updateSegment?.(segment.id,
|
|
5164
|
+
target.updateSegment?.(segment.id, segmentContentUpdate(segment));
|
|
4820
5165
|
}
|
|
4821
5166
|
if (segment.type === "artifact") {
|
|
4822
5167
|
dispatchArtifactLifecycle(target, messageId, segment, artifactProgress, true);
|
|
@@ -4831,7 +5176,7 @@ function createStreamAdapter(opts) {
|
|
|
4831
5176
|
dispatchArtifactLifecycle(target, messageId, active, artifactProgress, false);
|
|
4832
5177
|
}
|
|
4833
5178
|
} else {
|
|
4834
|
-
target.updateSegment?.(active.id,
|
|
5179
|
+
target.updateSegment?.(active.id, segmentContentUpdate(active));
|
|
4835
5180
|
if (active.type === "artifact") {
|
|
4836
5181
|
dispatchArtifactLifecycle(target, messageId, active, artifactProgress, false);
|
|
4837
5182
|
}
|
|
@@ -4862,7 +5207,7 @@ function createStreamAdapter(opts) {
|
|
|
4862
5207
|
}
|
|
4863
5208
|
for (const s of finals) {
|
|
4864
5209
|
if (!streaming.has(s.id)) target.addSegment?.(s);
|
|
4865
|
-
else if ("content" in s) target.updateSegment?.(s.id,
|
|
5210
|
+
else if ("content" in s) target.updateSegment?.(s.id, segmentContentUpdate(s));
|
|
4866
5211
|
if (s.type === "artifact") {
|
|
4867
5212
|
dispatchArtifactLifecycle(target, messageId, s, artifactProgress, true);
|
|
4868
5213
|
}
|
|
@@ -6203,7 +6548,7 @@ ${content}
|
|
|
6203
6548
|
}
|
|
6204
6549
|
case "text": {
|
|
6205
6550
|
if (thinkingSegmentId && !thinkingCollapsed) {
|
|
6206
|
-
targetElement.updateSegment?.(thinkingSegmentId, { collapsed: true });
|
|
6551
|
+
targetElement.updateSegment?.(thinkingSegmentId, { collapsed: true, isStreaming: false });
|
|
6207
6552
|
thinkingCollapsed = true;
|
|
6208
6553
|
}
|
|
6209
6554
|
precedingText += event.delta;
|
|
@@ -6250,7 +6595,7 @@ ${content}
|
|
|
6250
6595
|
targetElement.addSegment?.(segment);
|
|
6251
6596
|
streamingSegmentIds.add(segment.id);
|
|
6252
6597
|
} else if ("content" in segment) {
|
|
6253
|
-
targetElement.updateSegment?.(segment.id,
|
|
6598
|
+
targetElement.updateSegment?.(segment.id, segmentContentUpdate(segment));
|
|
6254
6599
|
}
|
|
6255
6600
|
if (segment.type === "artifact") {
|
|
6256
6601
|
dispatchArtifactLifecycle(targetElement, messageId, segment, artifactProgress, true);
|
|
@@ -6265,7 +6610,7 @@ ${content}
|
|
|
6265
6610
|
dispatchArtifactLifecycle(targetElement, messageId, active, artifactProgress, false);
|
|
6266
6611
|
}
|
|
6267
6612
|
} else {
|
|
6268
|
-
targetElement.updateSegment?.(active.id,
|
|
6613
|
+
targetElement.updateSegment?.(active.id, segmentContentUpdate(active));
|
|
6269
6614
|
if (active.type === "artifact") {
|
|
6270
6615
|
dispatchArtifactLifecycle(targetElement, messageId, active, artifactProgress, false);
|
|
6271
6616
|
}
|
|
@@ -6335,7 +6680,7 @@ ${content}
|
|
|
6335
6680
|
if (!streamingSegmentIds.has(s.id)) {
|
|
6336
6681
|
targetElement.addSegment?.(s);
|
|
6337
6682
|
} else if ("content" in s) {
|
|
6338
|
-
targetElement.updateSegment?.(s.id,
|
|
6683
|
+
targetElement.updateSegment?.(s.id, segmentContentUpdate(s));
|
|
6339
6684
|
}
|
|
6340
6685
|
if (s.type === "artifact") {
|
|
6341
6686
|
dispatchArtifactLifecycle(targetElement, messageId, s, artifactProgress, true);
|
|
@@ -6744,6 +7089,10 @@ function isAwaitingReply(message) {
|
|
|
6744
7089
|
if (message.status === "streaming" || message.status === "pending") return true;
|
|
6745
7090
|
return message.status === void 0 && message.role === "assistant" && !message.content?.trim() && !message.segments?.length;
|
|
6746
7091
|
}
|
|
7092
|
+
let _relabels = null;
|
|
7093
|
+
function onRelabel(fn) {
|
|
7094
|
+
_relabels?.push(fn);
|
|
7095
|
+
}
|
|
6747
7096
|
function el(tag, className, text) {
|
|
6748
7097
|
const node = document.createElement(tag);
|
|
6749
7098
|
if (className) node.className = className;
|
|
@@ -6800,12 +7149,20 @@ function buildEnumField(field, onChange, fallbackLabel) {
|
|
|
6800
7149
|
control.value = "__other__";
|
|
6801
7150
|
const body = el("span", "aparte-elic-option-body");
|
|
6802
7151
|
const t = contextConfig();
|
|
6803
|
-
|
|
7152
|
+
const otherTitle = el("span", "aparte-elic-option-title", t.t("elicitationOther"));
|
|
7153
|
+
body.appendChild(otherTitle);
|
|
6804
7154
|
otherText = el("input", "aparte-elic-other-input");
|
|
6805
7155
|
otherText.type = "text";
|
|
6806
7156
|
otherText.placeholder = t.t("elicitationOtherPlaceholder");
|
|
6807
7157
|
otherText.style.display = "none";
|
|
6808
7158
|
otherText.setAttribute("aria-label", t.t("elicitationOtherLabel"));
|
|
7159
|
+
const otherInput = otherText;
|
|
7160
|
+
onRelabel(() => {
|
|
7161
|
+
const now = contextConfig();
|
|
7162
|
+
otherTitle.textContent = now.t("elicitationOther");
|
|
7163
|
+
otherInput.placeholder = now.t("elicitationOtherPlaceholder");
|
|
7164
|
+
otherInput.setAttribute("aria-label", now.t("elicitationOtherLabel"));
|
|
7165
|
+
});
|
|
6809
7166
|
body.appendChild(otherText);
|
|
6810
7167
|
row.append(control, body);
|
|
6811
7168
|
list.appendChild(row);
|
|
@@ -6849,7 +7206,7 @@ function buildBooleanField(field, onChange, fallbackLabel) {
|
|
|
6849
7206
|
const list = el("div", "aparte-elic-options");
|
|
6850
7207
|
labelGroup(list, { titleId, fallbackLabel });
|
|
6851
7208
|
const name = `elic-${uuid()}`;
|
|
6852
|
-
const mk = (val, label) => {
|
|
7209
|
+
const mk = (val, label, defaulted) => {
|
|
6853
7210
|
const row = el("label", "aparte-elic-option");
|
|
6854
7211
|
const control = el("input", "aparte-elic-control");
|
|
6855
7212
|
control.type = "radio";
|
|
@@ -6857,13 +7214,17 @@ function buildBooleanField(field, onChange, fallbackLabel) {
|
|
|
6857
7214
|
control.value = val;
|
|
6858
7215
|
if (field.default != null && String(field.default) === val) control.checked = true;
|
|
6859
7216
|
const body = el("span", "aparte-elic-option-body");
|
|
6860
|
-
|
|
7217
|
+
const title = el("span", "aparte-elic-option-title", label);
|
|
7218
|
+
if (defaulted) onRelabel(() => {
|
|
7219
|
+
title.textContent = contextConfig().t(defaulted);
|
|
7220
|
+
});
|
|
7221
|
+
body.appendChild(title);
|
|
6861
7222
|
row.append(control, body);
|
|
6862
7223
|
list.appendChild(row);
|
|
6863
7224
|
};
|
|
6864
7225
|
const t = contextConfig();
|
|
6865
|
-
mk("true", field.trueLabel ?? t.t("elicitationYes"));
|
|
6866
|
-
mk("false", field.falseLabel ?? t.t("elicitationNo"));
|
|
7226
|
+
mk("true", field.trueLabel ?? t.t("elicitationYes"), field.trueLabel ? void 0 : "elicitationYes");
|
|
7227
|
+
mk("false", field.falseLabel ?? t.t("elicitationNo"), field.falseLabel ? void 0 : "elicitationNo");
|
|
6867
7228
|
wrap.appendChild(list);
|
|
6868
7229
|
list.addEventListener("change", onChange);
|
|
6869
7230
|
const checked = () => list.querySelector("input:checked");
|
|
@@ -6882,7 +7243,14 @@ function buildStringField(field, onChange, fallbackLabel) {
|
|
|
6882
7243
|
if (field.placeholder) input.setAttribute("placeholder", field.placeholder);
|
|
6883
7244
|
if (field.default) input.value = field.default;
|
|
6884
7245
|
if (field.maxLength != null) input.setAttribute("maxlength", String(field.maxLength));
|
|
6885
|
-
|
|
7246
|
+
const ownName = field.title || field.description || fallbackLabel || void 0;
|
|
7247
|
+
input.setAttribute("aria-label", ownName ?? contextConfig().t("elicitationAnswerLabel"));
|
|
7248
|
+
if (ownName == null) {
|
|
7249
|
+
const named = input;
|
|
7250
|
+
onRelabel(() => {
|
|
7251
|
+
named.setAttribute("aria-label", contextConfig().t("elicitationAnswerLabel"));
|
|
7252
|
+
});
|
|
7253
|
+
}
|
|
6886
7254
|
wrap.appendChild(input);
|
|
6887
7255
|
input.addEventListener("input", onChange);
|
|
6888
7256
|
const required = field.required ?? true;
|
|
@@ -6918,6 +7286,19 @@ function buildField(field, onChange, fallbackLabel, key) {
|
|
|
6918
7286
|
}
|
|
6919
7287
|
}
|
|
6920
7288
|
function buildElicitationPanel(message, schema, onChange) {
|
|
7289
|
+
const relabels = [];
|
|
7290
|
+
const outer = _relabels;
|
|
7291
|
+
_relabels = relabels;
|
|
7292
|
+
try {
|
|
7293
|
+
const built = buildPanel(message, schema, onChange);
|
|
7294
|
+
return { ...built, relabel: () => {
|
|
7295
|
+
for (const fn of relabels) fn();
|
|
7296
|
+
} };
|
|
7297
|
+
} finally {
|
|
7298
|
+
_relabels = outer;
|
|
7299
|
+
}
|
|
7300
|
+
}
|
|
7301
|
+
function buildPanel(message, schema, onChange) {
|
|
6921
7302
|
const panel = el("div", "aparte-elic-panel");
|
|
6922
7303
|
if (message) panel.appendChild(el("p", "aparte-elic-message", message));
|
|
6923
7304
|
const body = el("div", "aparte-elic-body");
|
|
@@ -7021,59 +7402,72 @@ function requestUserInput(request) {
|
|
|
7021
7402
|
return resolveConfig(request.target ?? null).requestUserInput(request);
|
|
7022
7403
|
}
|
|
7023
7404
|
export {
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7405
|
+
runWithConfig as $,
|
|
7406
|
+
APARTE_CONFIG_CHANGE as A,
|
|
7407
|
+
createAparteChatHandler as B,
|
|
7408
|
+
createStreamAdapter as C,
|
|
7409
|
+
cssEscape as D,
|
|
7410
|
+
declineDefaultRenderers as E,
|
|
7411
|
+
defaultSanitizer as F,
|
|
7412
|
+
deriveArtifactKind as G,
|
|
7413
|
+
detachConfig as H,
|
|
7414
|
+
escapeAttr as I,
|
|
7415
|
+
escapeHtml as J,
|
|
7416
|
+
filesToAttachments as K,
|
|
7417
|
+
getAllRenderers as L,
|
|
7418
|
+
getSegmentRenderer as M,
|
|
7419
|
+
installDefaultRenderersOnce as N,
|
|
7420
|
+
isAwaitingReply as O,
|
|
7421
|
+
isFormatAdapter as P,
|
|
7422
|
+
isSafeUrl as Q,
|
|
7423
|
+
isSegmentSettled as R,
|
|
7424
|
+
parseAparteEventStream as S,
|
|
7425
|
+
parseMarkdownToSegments as T,
|
|
7426
|
+
populateBubbleFromMessage as U,
|
|
7427
|
+
readableToAsyncIterable as V,
|
|
7428
|
+
registerDefaultRenderers as W,
|
|
7429
|
+
registerSegmentRenderer as X,
|
|
7430
|
+
requestUserInput as Y,
|
|
7431
|
+
resolveConfig as Z,
|
|
7432
|
+
revokeAttachmentUrls as _,
|
|
7433
|
+
APARTE_CONVERSATION_SCHEMA_VERSION as a,
|
|
7434
|
+
segmentDuration as a0,
|
|
7435
|
+
segmentTiming as a1,
|
|
7436
|
+
subscribeConfigChange as a2,
|
|
7437
|
+
unregisterSegmentRenderer as a3,
|
|
7438
|
+
uuid as a4,
|
|
7439
|
+
mergeSegmentUpdate as a5,
|
|
7440
|
+
stampSegmentActivity as a6,
|
|
7441
|
+
stampSegmentOnInsert as a7,
|
|
7442
|
+
stampSegmentOnUpdate as a8,
|
|
7443
|
+
renumberSegments as a9,
|
|
7444
|
+
openSegmentIds as aa,
|
|
7445
|
+
isTerminalStatus as ab,
|
|
7446
|
+
adoptMessageSegments as ac,
|
|
7447
|
+
APARTE_DEFAULT_BUBBLE_ACTIONS as b,
|
|
7448
|
+
APARTE_DEFAULT_HOST_HANDLERS as c,
|
|
7449
|
+
APARTE_DEFAULT_ICON_FALLBACKS as d,
|
|
7450
|
+
APARTE_DEFAULT_LOCALE as e,
|
|
7451
|
+
APARTE_DEFAULT_SKELETON_FALLBACKS as f,
|
|
7452
|
+
APARTE_DEFAULT_UI_EVENTS as g,
|
|
7453
|
+
APARTE_HOST_ATTR as h,
|
|
7454
|
+
AparteBackendTransport as i,
|
|
7455
|
+
AparteChatHost as j,
|
|
7456
|
+
AparteClient as k,
|
|
7457
|
+
AparteConfig as l,
|
|
7458
|
+
AparteConversationController as m,
|
|
7459
|
+
AparteConversationManager as n,
|
|
7460
|
+
AparteDirectTransport as o,
|
|
7461
|
+
AparteError as p,
|
|
7462
|
+
AparteErrorCode as q,
|
|
7463
|
+
AparteMessageRepository as r,
|
|
7464
|
+
AparteStreamParser as s,
|
|
7465
|
+
aparteGlobalConfig as t,
|
|
7466
|
+
applyElementProps as u,
|
|
7467
|
+
attachConfig as v,
|
|
7468
|
+
buildElicitationPanel as w,
|
|
7469
|
+
collectRendererStyles as x,
|
|
7470
|
+
contentToText as y,
|
|
7471
|
+
contextConfig as z
|
|
7078
7472
|
};
|
|
7079
|
-
//# sourceMappingURL=index-
|
|
7473
|
+
//# sourceMappingURL=index-BnKBciDG.js.map
|