@aparte/core 0.9.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/components/bubble/aparte-chat-bubble.d.ts +24 -3
- package/dist/components/bubble/aparte-chat-bubble.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 +3 -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 +4588 -4293
- 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-hQQljwfX.js → index-BnKBciDG.js} +515 -232
- package/dist/index-BnKBciDG.js.map +1 -0
- package/dist/index.css +147 -310
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +248 -87
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +3 -2
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +60 -57
- package/dist/index.node.js.map +1 -1
- 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 -22
- 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 +87 -67
- package/dist/types/segments.d.ts.map +1 -1
- package/dist/utils/segments.d.ts +75 -5
- package/dist/utils/segments.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/index-hQQljwfX.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...",
|
|
@@ -551,7 +561,6 @@ class AparteStreamParser {
|
|
|
551
561
|
id: this._options.autoGenerateIds ? this._generateId() : "",
|
|
552
562
|
type: "thinking",
|
|
553
563
|
content,
|
|
554
|
-
collapsed: false,
|
|
555
564
|
label: "Thinking"
|
|
556
565
|
};
|
|
557
566
|
}
|
|
@@ -939,6 +948,9 @@ const SAFE_STYLE_PROPS = /* @__PURE__ */ new Set([
|
|
|
939
948
|
"text-decoration-style",
|
|
940
949
|
"white-space"
|
|
941
950
|
]);
|
|
951
|
+
function isSafeCustomProperty(prop) {
|
|
952
|
+
return prop.startsWith("--") && !prop.startsWith("--aparte-");
|
|
953
|
+
}
|
|
942
954
|
function scrubStyle(value) {
|
|
943
955
|
const kept = [];
|
|
944
956
|
for (const decl of value.split(";")) {
|
|
@@ -947,7 +959,8 @@ function scrubStyle(value) {
|
|
|
947
959
|
if (idx === -1) continue;
|
|
948
960
|
const prop = trimmed.slice(0, idx).trim().toLowerCase();
|
|
949
961
|
const val = trimmed.slice(idx + 1);
|
|
950
|
-
if (!val.trim()
|
|
962
|
+
if (!val.trim()) continue;
|
|
963
|
+
if (!SAFE_STYLE_PROPS.has(prop) && !isSafeCustomProperty(prop)) continue;
|
|
951
964
|
if (val.includes("\\")) continue;
|
|
952
965
|
if (/url\s*\(|expression\s*\(|javascript:|vbscript:|[<>]/i.test(val)) continue;
|
|
953
966
|
kept.push(trimmed);
|
|
@@ -1034,7 +1047,6 @@ const APARTE_DEFAULT_BUBBLE_ACTIONS = {
|
|
|
1034
1047
|
};
|
|
1035
1048
|
const APARTE_DEFAULT_HOST_HANDLERS = {
|
|
1036
1049
|
attachmentPreview: false,
|
|
1037
|
-
terminalRun: false,
|
|
1038
1050
|
artifactRedownload: false,
|
|
1039
1051
|
artifactRehydrate: false
|
|
1040
1052
|
};
|
|
@@ -1097,6 +1109,7 @@ class AparteConfig {
|
|
|
1097
1109
|
// Tool Registry
|
|
1098
1110
|
_tools = /* @__PURE__ */ new Map();
|
|
1099
1111
|
_toolRenderers = /* @__PURE__ */ new Map();
|
|
1112
|
+
_segmentDefaults = /* @__PURE__ */ new Map();
|
|
1100
1113
|
// Host handlers — what the app declares it can actually complete.
|
|
1101
1114
|
_hostHandlers = { ...APARTE_DEFAULT_HOST_HANDLERS };
|
|
1102
1115
|
// Bubble Actions — APARTE_DEFAULT_BUBBLE_ACTIONS is the single source of truth
|
|
@@ -1177,7 +1190,6 @@ class AparteConfig {
|
|
|
1177
1190
|
getHostHandlers() {
|
|
1178
1191
|
return {
|
|
1179
1192
|
attachmentPreview: this._hostHandlers.attachmentPreview ?? APARTE_DEFAULT_HOST_HANDLERS.attachmentPreview,
|
|
1180
|
-
terminalRun: this._hostHandlers.terminalRun ?? APARTE_DEFAULT_HOST_HANDLERS.terminalRun,
|
|
1181
1193
|
artifactRedownload: this._hostHandlers.artifactRedownload ?? APARTE_DEFAULT_HOST_HANDLERS.artifactRedownload,
|
|
1182
1194
|
artifactRehydrate: this._hostHandlers.artifactRehydrate ?? APARTE_DEFAULT_HOST_HANDLERS.artifactRehydrate
|
|
1183
1195
|
};
|
|
@@ -1804,6 +1816,44 @@ class AparteConfig {
|
|
|
1804
1816
|
registerToolRenderer(toolName, renderer) {
|
|
1805
1817
|
this._toolRenderers.set(toolName, renderer);
|
|
1806
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
|
+
}
|
|
1807
1857
|
/** Unregister a per-tool renderer */
|
|
1808
1858
|
unregisterToolRenderer(toolName) {
|
|
1809
1859
|
this._toolRenderers.delete(toolName);
|
|
@@ -1888,6 +1938,7 @@ class AparteConfig {
|
|
|
1888
1938
|
this._siblingNavRenderer = void 0;
|
|
1889
1939
|
this._bubbleShellRenderer = void 0;
|
|
1890
1940
|
this._iconProvider = void 0;
|
|
1941
|
+
this._segmentDefaults.clear();
|
|
1891
1942
|
this._avatarProvider = void 0;
|
|
1892
1943
|
this._artifactPreviewBuilder = void 0;
|
|
1893
1944
|
this._keyProvider = void 0;
|
|
@@ -1983,6 +2034,16 @@ function runWithConfig(config, fn) {
|
|
|
1983
2034
|
function contextConfig(el2) {
|
|
1984
2035
|
return _renderConfig ?? resolveConfig(el2);
|
|
1985
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
|
+
}
|
|
1986
2047
|
const APARTE_DEFAULT_SKELETON_FALLBACKS = {
|
|
1987
2048
|
message: '<div class="aparte-skeleton-fallback" style="padding:16px;color:#9ca3af;">Loading...</div>',
|
|
1988
2049
|
code: '<div class="aparte-skeleton-fallback" style="padding:16px;background:#1e293b;color:#64748b;border-radius:8px;">Loading code...</div>',
|
|
@@ -2063,7 +2124,15 @@ const thinkingRenderer = {
|
|
|
2063
2124
|
* new class of risk. With no Markdown provider registered it degrades to the
|
|
2064
2125
|
* zero-dependency default (escape + `<br>`), which is what it used to be.
|
|
2065
2126
|
*/
|
|
2066
|
-
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
|
+
},
|
|
2067
2136
|
update: (el2, segment) => {
|
|
2068
2137
|
const contentEl = el2.querySelector(".thinking-content");
|
|
2069
2138
|
if (!contentEl) return;
|
|
@@ -2075,6 +2144,53 @@ const thinkingRenderer = {
|
|
|
2075
2144
|
},
|
|
2076
2145
|
getStyles: () => ``
|
|
2077
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
|
+
}
|
|
2078
2194
|
const codeRenderer = {
|
|
2079
2195
|
type: "code",
|
|
2080
2196
|
render: (segment) => `
|
|
@@ -2091,6 +2207,13 @@ const codeRenderer = {
|
|
|
2091
2207
|
</div>
|
|
2092
2208
|
</div>
|
|
2093
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
|
+
},
|
|
2094
2217
|
setup: (element, segment) => {
|
|
2095
2218
|
const wrapper = element.querySelector(".code-content-wrapper");
|
|
2096
2219
|
if (wrapper) {
|
|
@@ -2107,22 +2230,18 @@ const codeRenderer = {
|
|
|
2107
2230
|
});
|
|
2108
2231
|
copyBtn.innerHTML = contextConfig(copyBtn).getIcon("check");
|
|
2109
2232
|
copyBtn.setAttribute("title", contextConfig(copyBtn).t("copied"));
|
|
2233
|
+
copyBtn.dataset.copied = "1";
|
|
2110
2234
|
setTimeout(() => {
|
|
2111
2235
|
copyBtn.innerHTML = contextConfig(copyBtn).getIcon("copy");
|
|
2112
2236
|
copyBtn.setAttribute("title", contextConfig(copyBtn).t("copy"));
|
|
2237
|
+
delete copyBtn.dataset.copied;
|
|
2113
2238
|
}, 1500);
|
|
2114
2239
|
});
|
|
2115
2240
|
}
|
|
2116
2241
|
},
|
|
2117
2242
|
update: (element, segment) => {
|
|
2118
2243
|
if (segment.isStreaming) {
|
|
2119
|
-
|
|
2120
|
-
if (codeEl) {
|
|
2121
|
-
codeEl.textContent = segment.content;
|
|
2122
|
-
} else {
|
|
2123
|
-
const wrapper = element.querySelector(".code-content-wrapper");
|
|
2124
|
-
if (wrapper) wrapper.innerHTML = `<pre><code class="language-${escapeHtml(segment.language || "text")}">${escapeHtml(segment.content)}</code></pre>`;
|
|
2125
|
-
}
|
|
2244
|
+
streamHighlight(element, ".code-content-wrapper", segment.content, segment.language || "", segment.id);
|
|
2126
2245
|
} else {
|
|
2127
2246
|
const wrapper = element.querySelector(".code-content-wrapper");
|
|
2128
2247
|
if (wrapper) {
|
|
@@ -2135,58 +2254,6 @@ const codeRenderer = {
|
|
|
2135
2254
|
},
|
|
2136
2255
|
getStyles: () => ``
|
|
2137
2256
|
};
|
|
2138
|
-
const terminalRenderer = {
|
|
2139
|
-
type: "terminal",
|
|
2140
|
-
render: (segment) => `
|
|
2141
|
-
<div class="segment segment-terminal" data-segment-id="${escapeHtml(segment.id)}">
|
|
2142
|
-
<div class="terminal-command-block">
|
|
2143
|
-
<div class="terminal-icon">
|
|
2144
|
-
${contextConfig().getIcon("terminal")}
|
|
2145
|
-
</div>
|
|
2146
|
-
<code class="terminal-command">${escapeHtml(segment.command || "")}</code>
|
|
2147
|
-
<div class="terminal-actions">
|
|
2148
|
-
${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>` : ""}
|
|
2149
|
-
<button class="terminal-copy-btn" data-action="copy" aria-label="${escapeAttr(contextConfig().t("copy"))}" title="${escapeAttr(contextConfig().t("copy"))}">
|
|
2150
|
-
${contextConfig().getIcon("copy")}
|
|
2151
|
-
</button>
|
|
2152
|
-
</div>
|
|
2153
|
-
</div>
|
|
2154
|
-
${segment.output ? `<div class="terminal-output">${escapeHtml(segment.output)}</div>` : ""}
|
|
2155
|
-
${segment.exitCode !== void 0 && segment.exitCode !== 0 ? `<div class="terminal-error">Command failed with exit code ${segment.exitCode}</div>` : ""}
|
|
2156
|
-
</div>
|
|
2157
|
-
`,
|
|
2158
|
-
setup: (element, segment) => {
|
|
2159
|
-
const copyBtn = element.querySelector(".terminal-copy-btn");
|
|
2160
|
-
const command = element.querySelector(".terminal-command");
|
|
2161
|
-
if (copyBtn && command) {
|
|
2162
|
-
copyBtn.addEventListener("click", () => {
|
|
2163
|
-
void navigator.clipboard.writeText(command.textContent || "").catch(() => {
|
|
2164
|
-
});
|
|
2165
|
-
copyBtn.innerHTML = contextConfig(copyBtn).getIcon("check");
|
|
2166
|
-
copyBtn.setAttribute("title", contextConfig(copyBtn).t("copied"));
|
|
2167
|
-
setTimeout(() => {
|
|
2168
|
-
copyBtn.innerHTML = contextConfig(copyBtn).getIcon("copy");
|
|
2169
|
-
copyBtn.setAttribute("title", contextConfig(copyBtn).t("copy"));
|
|
2170
|
-
}, 1500);
|
|
2171
|
-
});
|
|
2172
|
-
}
|
|
2173
|
-
const runBtn = element.querySelector(".terminal-run-btn");
|
|
2174
|
-
if (runBtn) {
|
|
2175
|
-
runBtn.addEventListener("click", () => {
|
|
2176
|
-
element.dispatchEvent(new CustomEvent("aparte-terminal-run", {
|
|
2177
|
-
bubbles: true,
|
|
2178
|
-
composed: true,
|
|
2179
|
-
detail: {
|
|
2180
|
-
segmentId: segment.id,
|
|
2181
|
-
messageId: segment.messageId,
|
|
2182
|
-
command: command?.textContent || ""
|
|
2183
|
-
}
|
|
2184
|
-
}));
|
|
2185
|
-
});
|
|
2186
|
-
}
|
|
2187
|
-
},
|
|
2188
|
-
getStyles: () => ``
|
|
2189
|
-
};
|
|
2190
2257
|
const errorRenderer = {
|
|
2191
2258
|
type: "error",
|
|
2192
2259
|
render: (segment) => {
|
|
@@ -2205,13 +2272,28 @@ const errorRenderer = {
|
|
|
2205
2272
|
${contextConfig().getIcon("error") || "⚠"}
|
|
2206
2273
|
</div>
|
|
2207
2274
|
<div class="error-content">
|
|
2208
|
-
<div class="error-title"
|
|
2275
|
+
<div class="error-title">${escapeHtml(contextConfig().t("error"))}</div>
|
|
2209
2276
|
<div class="error-message">${escapeHtml(segment.content)}</div>
|
|
2210
2277
|
${segment.details ? `<div class="error-details">${escapeHtml(segment.details)}</div>` : ""}
|
|
2211
2278
|
</div>
|
|
2212
2279
|
</div>
|
|
2213
2280
|
`;
|
|
2214
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
|
+
},
|
|
2215
2297
|
getStyles: () => ``
|
|
2216
2298
|
};
|
|
2217
2299
|
const progressRenderer = {
|
|
@@ -2223,31 +2305,6 @@ const progressRenderer = {
|
|
|
2223
2305
|
},
|
|
2224
2306
|
getStyles: () => ``
|
|
2225
2307
|
};
|
|
2226
|
-
function renderFileNode(node, depth = 0) {
|
|
2227
|
-
const indent = depth * 16;
|
|
2228
|
-
const icon = node.type === "directory" ? "📁" : "📄";
|
|
2229
|
-
const statusClass = node.status ? `file-status-${escapeHtml(node.status)}` : "";
|
|
2230
|
-
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>`;
|
|
2231
|
-
if (node.children) {
|
|
2232
|
-
for (const child of node.children) {
|
|
2233
|
-
html += renderFileNode(child, depth + 1);
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
|
-
return html;
|
|
2237
|
-
}
|
|
2238
|
-
const fileTreeRenderer = {
|
|
2239
|
-
type: "file-tree",
|
|
2240
|
-
render: (segment) => {
|
|
2241
|
-
let filesHtml = "";
|
|
2242
|
-
if (segment.files) {
|
|
2243
|
-
for (const file of segment.files) {
|
|
2244
|
-
filesHtml += renderFileNode(file, 0);
|
|
2245
|
-
}
|
|
2246
|
-
}
|
|
2247
|
-
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>`;
|
|
2248
|
-
},
|
|
2249
|
-
getStyles: () => ``
|
|
2250
|
-
};
|
|
2251
2308
|
const toolCallRenderer = {
|
|
2252
2309
|
type: "tool_call",
|
|
2253
2310
|
render: (segment) => {
|
|
@@ -2289,6 +2346,39 @@ const toolCallRenderer = {
|
|
|
2289
2346
|
</div>
|
|
2290
2347
|
`;
|
|
2291
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
|
+
},
|
|
2292
2382
|
setup: (element, segment) => {
|
|
2293
2383
|
if (segment.status === "awaiting-approval") {
|
|
2294
2384
|
const toolCallId = segment.toolCall?.id;
|
|
@@ -2355,11 +2445,12 @@ const toolCallRenderer = {
|
|
|
2355
2445
|
@keyframes tool-spin { to { transform: rotate(360deg); } }
|
|
2356
2446
|
`
|
|
2357
2447
|
};
|
|
2448
|
+
const waitingLabel = () => contextConfig().t("generating");
|
|
2358
2449
|
const pipelineWaitingRenderer = {
|
|
2359
2450
|
type: "pipeline-waiting",
|
|
2360
2451
|
render: (segment) => {
|
|
2361
2452
|
return `
|
|
2362
|
-
<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">
|
|
2363
2454
|
<span class="pw-dot"></span>
|
|
2364
2455
|
<span class="pw-dot"></span>
|
|
2365
2456
|
<span class="pw-dot"></span>
|
|
@@ -2367,6 +2458,15 @@ const pipelineWaitingRenderer = {
|
|
|
2367
2458
|
},
|
|
2368
2459
|
update: () => {
|
|
2369
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
|
+
},
|
|
2370
2470
|
setup: (el2) => {
|
|
2371
2471
|
const parent = el2.parentElement;
|
|
2372
2472
|
if (!parent) return;
|
|
@@ -2451,26 +2551,6 @@ function findClosingFence(s) {
|
|
|
2451
2551
|
}
|
|
2452
2552
|
return -1;
|
|
2453
2553
|
}
|
|
2454
|
-
const _lastHighlightAt = /* @__PURE__ */ new Map();
|
|
2455
|
-
function markThrottle(map, id, at) {
|
|
2456
|
-
map.delete(id);
|
|
2457
|
-
if (map.size >= MAX_THROTTLE_ENTRIES) {
|
|
2458
|
-
const oldest = map.keys().next().value;
|
|
2459
|
-
if (oldest !== void 0) map.delete(oldest);
|
|
2460
|
-
}
|
|
2461
|
-
map.set(id, at);
|
|
2462
|
-
}
|
|
2463
|
-
function debounceHighlight(element, paneSelector, content, lang, segId) {
|
|
2464
|
-
const now = Date.now();
|
|
2465
|
-
const last = _lastHighlightAt.get(segId) ?? 0;
|
|
2466
|
-
if (now - last < HIGHLIGHT_DEBOUNCE_MS) return;
|
|
2467
|
-
markThrottle(_lastHighlightAt, segId, now);
|
|
2468
|
-
void contextConfig(element).highlightCode(content, lang).then((html) => {
|
|
2469
|
-
const wrapper = element.querySelector(paneSelector);
|
|
2470
|
-
if (wrapper) wrapper.innerHTML = html;
|
|
2471
|
-
}).catch(() => {
|
|
2472
|
-
});
|
|
2473
|
-
}
|
|
2474
2554
|
function labelForKind(kind) {
|
|
2475
2555
|
switch (kind) {
|
|
2476
2556
|
case "react":
|
|
@@ -2509,8 +2589,6 @@ function labelForKind(kind) {
|
|
|
2509
2589
|
return "Artifact";
|
|
2510
2590
|
}
|
|
2511
2591
|
}
|
|
2512
|
-
const MAX_THROTTLE_ENTRIES = 256;
|
|
2513
|
-
const HIGHLIGHT_DEBOUNCE_MS = 400;
|
|
2514
2592
|
const PREVIEW_CSP = "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:";
|
|
2515
2593
|
function withMetaCsp(doc) {
|
|
2516
2594
|
const meta = `<meta http-equiv="Content-Security-Policy" content="${escapeAttr(PREVIEW_CSP)}">`;
|
|
@@ -2527,9 +2605,18 @@ function buildPreviewBody(kind, body, title) {
|
|
|
2527
2605
|
if (startsWithDoctype(body)) return body;
|
|
2528
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>`;
|
|
2529
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.
|
|
2530
2617
|
case "svg":
|
|
2531
2618
|
return `<!doctype html><html><head><meta charset="utf-8"/><title>${escapeAttr(title)}</title>
|
|
2532
|
-
<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>
|
|
2533
2620
|
</head><body>${body}</body></html>`;
|
|
2534
2621
|
case "js": {
|
|
2535
2622
|
const safeBody = escapeClosingScriptTag(body);
|
|
@@ -2612,6 +2699,7 @@ function renderBinaryFileArtifact(segment, kind) {
|
|
|
2612
2699
|
const iconLabel = FILE_ICON_LABEL[kind] ?? kind.toUpperCase();
|
|
2613
2700
|
const isStreaming = !!segment.isStreaming;
|
|
2614
2701
|
const canRedownload = contextConfig().getHostHandlers().artifactRedownload;
|
|
2702
|
+
const downloadLabel = escapeHtml(contextConfig().t("download"));
|
|
2615
2703
|
const cached = _binaryArtifactCache.get(segment.id);
|
|
2616
2704
|
if (cached && !isStreaming) {
|
|
2617
2705
|
const previewBody = cached.previewHtml ? contextConfig().sanitizeHtml(cached.previewHtml) : `<div class="aparte-art-file__preview-empty">Preview not available for ${escapeHtml(kind)} yet</div>`;
|
|
@@ -2627,7 +2715,7 @@ function renderBinaryFileArtifact(segment, kind) {
|
|
|
2627
2715
|
<div class="aparte-art-file__meta-sub" data-role="file-sub">${escapeHtml(formatBytes(cached.bytes))} · ${escapeHtml(kind.toUpperCase())}</div>
|
|
2628
2716
|
</div>
|
|
2629
2717
|
<div class="aparte-art-file__actions">
|
|
2630
|
-
${canRedownload ?
|
|
2718
|
+
${canRedownload ? `<button type="button" class="aparte-art-file__btn aparte-art-file__btn--primary" data-action="download">${downloadLabel}</button>` : ""}
|
|
2631
2719
|
</div>
|
|
2632
2720
|
</div>
|
|
2633
2721
|
<div class="aparte-art-file__body">
|
|
@@ -2640,7 +2728,7 @@ function renderBinaryFileArtifact(segment, kind) {
|
|
|
2640
2728
|
`;
|
|
2641
2729
|
}
|
|
2642
2730
|
const cleanContent = stripCodeFences(segment.content || "");
|
|
2643
|
-
const subText = isStreaming ? "
|
|
2731
|
+
const subText = isStreaming ? contextConfig().t("generating") : contextConfig().t("rebuildingPreview");
|
|
2644
2732
|
return `
|
|
2645
2733
|
<div class="segment segment-artifact-file"
|
|
2646
2734
|
data-segment-id="${escapeHtml(segment.id)}"
|
|
@@ -2653,7 +2741,7 @@ function renderBinaryFileArtifact(segment, kind) {
|
|
|
2653
2741
|
<div class="aparte-art-file__meta-sub" data-role="file-sub">${escapeHtml(subText)}</div>
|
|
2654
2742
|
</div>
|
|
2655
2743
|
<div class="aparte-art-file__actions">
|
|
2656
|
-
${canRedownload ?
|
|
2744
|
+
${canRedownload ? `<button type="button" class="aparte-art-file__btn aparte-art-file__btn--primary" data-action="download" disabled>${downloadLabel}</button>` : ""}
|
|
2657
2745
|
</div>
|
|
2658
2746
|
</div>
|
|
2659
2747
|
<div class="aparte-art-file__body">
|
|
@@ -2759,12 +2847,11 @@ function updateBinaryFileArtifact(element, segment, isStreaming) {
|
|
|
2759
2847
|
const state = element.getAttribute("data-state");
|
|
2760
2848
|
if (state === "ready") return;
|
|
2761
2849
|
const cleanContent = stripCodeFences(segment.content || "");
|
|
2762
|
-
const codeEl = element.querySelector('[data-role="code-pane"] code');
|
|
2763
|
-
if (codeEl) {
|
|
2764
|
-
codeEl.textContent = cleanContent;
|
|
2765
|
-
}
|
|
2766
2850
|
if (isStreaming) {
|
|
2767
|
-
|
|
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;
|
|
2768
2855
|
}
|
|
2769
2856
|
if (!isStreaming && state === "streaming") {
|
|
2770
2857
|
element.setAttribute("data-state", "compiling");
|
|
@@ -2811,9 +2898,9 @@ function showSandboxError(element, phase, errorMsg) {
|
|
|
2811
2898
|
const short = (errorMsg.split("\n")[0] ?? "").slice(0, 240);
|
|
2812
2899
|
body.innerHTML = `
|
|
2813
2900
|
<div class="aparte-art-file__error">
|
|
2814
|
-
<div class="aparte-art-file__error-title"
|
|
2901
|
+
<div class="aparte-art-file__error-title">${escapeHtml(contextConfig().t("sandboxError"))}</div>
|
|
2815
2902
|
<div class="aparte-art-file__error-msg">${escapeHtml(short)}</div>
|
|
2816
|
-
<div class="aparte-art-file__error-hint"
|
|
2903
|
+
<div class="aparte-art-file__error-hint">${escapeHtml(contextConfig().t("sandboxErrorHint"))}</div>
|
|
2817
2904
|
</div>
|
|
2818
2905
|
`;
|
|
2819
2906
|
}
|
|
@@ -2831,6 +2918,10 @@ const artifactRenderer = {
|
|
|
2831
2918
|
const displayLang = languageForKind(kind);
|
|
2832
2919
|
const isStreaming = !!segment.isStreaming;
|
|
2833
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");
|
|
2834
2925
|
const isBinary = BINARY_FILE_KINDS.has(kind);
|
|
2835
2926
|
const cleanContent = stripCodeFences(segment.content || "");
|
|
2836
2927
|
return `
|
|
@@ -2848,17 +2939,17 @@ const artifactRenderer = {
|
|
|
2848
2939
|
${isStreaming ? '<span class="aparte-art-card__pulse" aria-label="Streaming"></span>' : ""}
|
|
2849
2940
|
</div>
|
|
2850
2941
|
<div class="aparte-art-card__actions">
|
|
2851
|
-
<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"))}">
|
|
2852
2943
|
${contextConfig().getIcon("copy")}
|
|
2853
2944
|
</button>
|
|
2854
|
-
<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" : ""}>
|
|
2855
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>
|
|
2856
2947
|
</button>
|
|
2857
2948
|
</div>
|
|
2858
2949
|
</header>
|
|
2859
2950
|
<nav class="aparte-art-card__tabs" role="tablist">
|
|
2860
|
-
|
|
2861
|
-
|
|
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>` : ""}
|
|
2862
2953
|
</nav>
|
|
2863
2954
|
<div class="aparte-art-card__body">
|
|
2864
2955
|
<div class="aparte-art-card__pane" data-pane="code">
|
|
@@ -2868,13 +2959,44 @@ const artifactRenderer = {
|
|
|
2868
2959
|
</div>
|
|
2869
2960
|
${previewable ? `
|
|
2870
2961
|
<div class="aparte-art-card__pane" data-pane="preview">
|
|
2871
|
-
<div class="aparte-art-card__pending"
|
|
2962
|
+
<div class="aparte-art-card__pending">${escapeHtml(cfg.t("previewPending"))}</div>
|
|
2872
2963
|
</div>
|
|
2873
2964
|
` : ""}
|
|
2874
2965
|
</div>
|
|
2875
2966
|
</div>
|
|
2876
2967
|
`;
|
|
2877
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
|
+
},
|
|
2878
3000
|
setup: (element, segment) => {
|
|
2879
3001
|
latestSegment.set(element, segment);
|
|
2880
3002
|
const kind = (segment.artifactType || "").toLowerCase();
|
|
@@ -2955,22 +3077,24 @@ const artifactRenderer = {
|
|
|
2955
3077
|
}
|
|
2956
3078
|
const wasStreaming = element.getAttribute("data-streaming") === "true";
|
|
2957
3079
|
const cleanContent = stripCodeFences(segment.content || "");
|
|
2958
|
-
const codeEl = element.querySelector(".code-content-wrapper code");
|
|
2959
|
-
if (codeEl) {
|
|
2960
|
-
codeEl.textContent = cleanContent;
|
|
2961
|
-
} else {
|
|
2962
|
-
const wrapper = element.querySelector(".code-content-wrapper");
|
|
2963
|
-
if (wrapper) {
|
|
2964
|
-
const displayLang = languageForKind(kind);
|
|
2965
|
-
wrapper.innerHTML = `<pre><code class="language-${escapeHtml(displayLang)}">${escapeHtml(cleanContent)}</code></pre>`;
|
|
2966
|
-
}
|
|
2967
|
-
}
|
|
2968
3080
|
if (isStreaming) {
|
|
2969
3081
|
const segId = element.getAttribute("data-segment-id") ?? segment.id;
|
|
2970
|
-
|
|
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
|
+
}
|
|
2971
3094
|
}
|
|
2972
3095
|
if (wasStreaming && !isStreaming) {
|
|
2973
3096
|
element.setAttribute("data-streaming", "false");
|
|
3097
|
+
element.querySelector(".aparte-art-card__pulse")?.remove();
|
|
2974
3098
|
const wrapper = element.querySelector(".code-content-wrapper");
|
|
2975
3099
|
if (wrapper) {
|
|
2976
3100
|
const displayLang = languageForKind(kind);
|
|
@@ -3042,7 +3166,24 @@ const artifactRenderer = {
|
|
|
3042
3166
|
}
|
|
3043
3167
|
.aparte-art-card__btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3044
3168
|
.aparte-art-card__tabs {
|
|
3045
|
-
|
|
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;
|
|
3046
3187
|
padding: 4px 8px 0;
|
|
3047
3188
|
border-bottom: 1px solid var(--aparte-border, rgba(0,0,0,0.08));
|
|
3048
3189
|
background: var(--aparte-surface-2, rgba(0,0,0,0.02));
|
|
@@ -3065,17 +3206,37 @@ const artifactRenderer = {
|
|
|
3065
3206
|
font-weight: 500;
|
|
3066
3207
|
}
|
|
3067
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. */
|
|
3068
3226
|
.aparte-art-card__body {
|
|
3069
3227
|
position: relative;
|
|
3070
|
-
|
|
3071
|
-
|
|
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);
|
|
3072
3232
|
overflow: hidden;
|
|
3073
3233
|
}
|
|
3074
3234
|
.aparte-art-card__pane { display: none; height: 100%; }
|
|
3075
3235
|
.segment-artifact-card[data-tab="code"] .aparte-art-card__pane[data-pane="code"] { display: block; }
|
|
3076
3236
|
.segment-artifact-card[data-tab="preview"] .aparte-art-card__pane[data-pane="preview"] { display: block; }
|
|
3077
3237
|
.aparte-art-card__pane[data-pane="code"] {
|
|
3078
|
-
|
|
3238
|
+
/* The body already caps this; repeating the number was the second owner. */
|
|
3239
|
+
max-height: var(--aparte-artifact-body-max, 600px); overflow: auto;
|
|
3079
3240
|
}
|
|
3080
3241
|
.aparte-art-card__pane[data-pane="code"] pre {
|
|
3081
3242
|
margin: 0; padding: 12px;
|
|
@@ -3084,13 +3245,14 @@ const artifactRenderer = {
|
|
|
3084
3245
|
}
|
|
3085
3246
|
.aparte-art-card__frame {
|
|
3086
3247
|
display: block;
|
|
3087
|
-
width: 100%;
|
|
3248
|
+
width: 100%;
|
|
3249
|
+
height: min(var(--aparte-artifact-frame-height, 480px), var(--aparte-artifact-frame-max, 70vh));
|
|
3088
3250
|
border: 0;
|
|
3089
3251
|
background: #fff;
|
|
3090
3252
|
}
|
|
3091
3253
|
.aparte-art-card__pending {
|
|
3092
3254
|
display: flex; align-items: center; justify-content: center;
|
|
3093
|
-
height: 120px;
|
|
3255
|
+
height: var(--aparte-artifact-pending-height, 120px);
|
|
3094
3256
|
color: var(--aparte-text-muted, rgba(0,0,0,0.5));
|
|
3095
3257
|
font-size: 0.85rem;
|
|
3096
3258
|
font-style: italic;
|
|
@@ -3116,7 +3278,7 @@ const artifactRenderer = {
|
|
|
3116
3278
|
position: relative;
|
|
3117
3279
|
}
|
|
3118
3280
|
.aparte-art-file__code-pane {
|
|
3119
|
-
max-height: 360px; overflow: auto;
|
|
3281
|
+
max-height: var(--aparte-artifact-file-code-max, 360px); overflow: auto;
|
|
3120
3282
|
background: var(--aparte-code-bg, #fafafa);
|
|
3121
3283
|
}
|
|
3122
3284
|
.aparte-art-file__code-pane pre {
|
|
@@ -3125,7 +3287,7 @@ const artifactRenderer = {
|
|
|
3125
3287
|
font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
|
|
3126
3288
|
}
|
|
3127
3289
|
.aparte-art-file__preview-pane {
|
|
3128
|
-
max-height: 460px;
|
|
3290
|
+
max-height: var(--aparte-artifact-file-preview-max, 460px);
|
|
3129
3291
|
overflow: auto;
|
|
3130
3292
|
background: #fff;
|
|
3131
3293
|
/* Preview is a document view — force light scheme regardless of
|
|
@@ -3385,10 +3547,8 @@ const DEFAULT_RENDERERS = [
|
|
|
3385
3547
|
textRenderer,
|
|
3386
3548
|
thinkingRenderer,
|
|
3387
3549
|
codeRenderer,
|
|
3388
|
-
terminalRenderer,
|
|
3389
3550
|
errorRenderer,
|
|
3390
3551
|
progressRenderer,
|
|
3391
|
-
fileTreeRenderer,
|
|
3392
3552
|
toolCallRenderer,
|
|
3393
3553
|
artifactRenderer,
|
|
3394
3554
|
pipelineWaitingRenderer
|
|
@@ -3398,14 +3558,47 @@ const ACTIVITY_KEYS = ["content", "output", "result"];
|
|
|
3398
3558
|
function isActivity(updates) {
|
|
3399
3559
|
return ACTIVITY_KEYS.some((key) => key in updates);
|
|
3400
3560
|
}
|
|
3401
|
-
function stampSegmentOnInsert(segments, segment, messageId) {
|
|
3561
|
+
function stampSegmentOnInsert(segments, segment, messageId, defaults) {
|
|
3562
|
+
const base = applyDefaults(segment, defaults);
|
|
3402
3563
|
return {
|
|
3403
|
-
...
|
|
3564
|
+
...base,
|
|
3404
3565
|
messageId: segment.messageId ?? messageId,
|
|
3405
3566
|
index: segment.index ?? segments.length,
|
|
3406
|
-
|
|
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() })
|
|
3407
3571
|
};
|
|
3408
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
|
+
}
|
|
3409
3602
|
function isSegmentSettled(segment) {
|
|
3410
3603
|
if (segment.type === "tool_call") return !OPEN_TOOL_STATUSES.has(segment.status);
|
|
3411
3604
|
return segment.isStreaming === false;
|
|
@@ -3414,17 +3607,32 @@ function stampSegmentOnUpdate(segment, updates) {
|
|
|
3414
3607
|
if (isSegmentSettled(segment)) return updates;
|
|
3415
3608
|
const merged = { ...segment, ...updates };
|
|
3416
3609
|
if (isSegmentSettled(merged)) {
|
|
3417
|
-
|
|
3610
|
+
const ended = segmentTiming(segment)?.endedAt;
|
|
3611
|
+
return { ...updates, ...timingPatch(segment, { endedAt: ended ?? Date.now() }) };
|
|
3418
3612
|
}
|
|
3419
|
-
return isActivity(updates) ? { ...updates, endedAt: Date.now() } : updates;
|
|
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
|
+
};
|
|
3420
3628
|
}
|
|
3421
3629
|
function segmentDuration(segment) {
|
|
3422
|
-
const { startedAt, endedAt } = segment;
|
|
3630
|
+
const { startedAt, endedAt } = segmentTiming(segment) ?? {};
|
|
3423
3631
|
if (startedAt == null || endedAt == null) return void 0;
|
|
3424
3632
|
return endedAt - startedAt;
|
|
3425
3633
|
}
|
|
3426
3634
|
function stampSegmentActivity(segment) {
|
|
3427
|
-
return isSegmentSettled(segment) ? {} : { endedAt: Date.now() };
|
|
3635
|
+
return isSegmentSettled(segment) ? {} : timingPatch(segment, { endedAt: Date.now() });
|
|
3428
3636
|
}
|
|
3429
3637
|
function segmentContentUpdate(segment) {
|
|
3430
3638
|
const content = segment.content;
|
|
@@ -3439,6 +3647,16 @@ function isTerminalStatus(status) {
|
|
|
3439
3647
|
function openSegmentIds(segments) {
|
|
3440
3648
|
return segments.filter((segment) => !isSegmentSettled(segment)).map((segment) => segment.id);
|
|
3441
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
|
+
}
|
|
3442
3660
|
function renumberSegments(segments) {
|
|
3443
3661
|
for (let i = 0; i < segments.length; i++) {
|
|
3444
3662
|
const segment = segments[i];
|
|
@@ -4139,10 +4357,14 @@ class AparteChatHost {
|
|
|
4139
4357
|
// Controller-driven flushes mirror AparteConversationManager state, not
|
|
4140
4358
|
// user intent — deliberately NOT emitted as `messagesChange` to
|
|
4141
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.
|
|
4142
4364
|
setMessages: (msgs) => {
|
|
4143
4365
|
this._beginConversationSwap();
|
|
4144
4366
|
this._vp()?.resetSpacer?.();
|
|
4145
|
-
this.binding.setMessages(
|
|
4367
|
+
this.binding.setMessages(msgs.map(adoptMessageSegments));
|
|
4146
4368
|
},
|
|
4147
4369
|
appendMessage: (msg) => this.appendMessage(msg),
|
|
4148
4370
|
getMessages: () => this.binding.getMessages(),
|
|
@@ -4193,11 +4415,23 @@ class AparteChatHost {
|
|
|
4193
4415
|
* spec guards ("optimistic-append + parent-push race"). `onMessageAppended`
|
|
4194
4416
|
* is the append-specific signal instead.
|
|
4195
4417
|
*/
|
|
4196
|
-
appendMessage(message) {
|
|
4418
|
+
appendMessage(message, options) {
|
|
4197
4419
|
this._flushStreamState();
|
|
4198
4420
|
if (message.role === "user") this._vp()?.setAutoScroll?.(true);
|
|
4199
|
-
|
|
4200
|
-
|
|
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);
|
|
4201
4435
|
}
|
|
4202
4436
|
/** Atomic partial update of a message by id. */
|
|
4203
4437
|
updateMessage(messageId, updates) {
|
|
@@ -4240,7 +4474,15 @@ class AparteChatHost {
|
|
|
4240
4474
|
this.binding.onTypingChange?.(false);
|
|
4241
4475
|
const msgs = before;
|
|
4242
4476
|
const last = target;
|
|
4243
|
-
const stamped = last ? stampSegmentOnInsert(
|
|
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;
|
|
4244
4486
|
this._lastBubble()?.addSegment?.(stamped);
|
|
4245
4487
|
if (!last) return;
|
|
4246
4488
|
const segments = [...last.segments || [], stamped];
|
|
@@ -4259,7 +4501,7 @@ class AparteChatHost {
|
|
|
4259
4501
|
this._lastBubble()?.updateSegment?.(segmentId, stamped);
|
|
4260
4502
|
if (!last || !last.segments) return;
|
|
4261
4503
|
const segments = last.segments.map(
|
|
4262
|
-
(s) => s.id === segmentId ?
|
|
4504
|
+
(s) => s.id === segmentId ? mergeSegmentUpdate(s, stamped) : s
|
|
4263
4505
|
);
|
|
4264
4506
|
const next = [...msgs.slice(0, -1), { ...last, segments }];
|
|
4265
4507
|
this.binding.setMessages(next);
|
|
@@ -6847,6 +7089,10 @@ function isAwaitingReply(message) {
|
|
|
6847
7089
|
if (message.status === "streaming" || message.status === "pending") return true;
|
|
6848
7090
|
return message.status === void 0 && message.role === "assistant" && !message.content?.trim() && !message.segments?.length;
|
|
6849
7091
|
}
|
|
7092
|
+
let _relabels = null;
|
|
7093
|
+
function onRelabel(fn) {
|
|
7094
|
+
_relabels?.push(fn);
|
|
7095
|
+
}
|
|
6850
7096
|
function el(tag, className, text) {
|
|
6851
7097
|
const node = document.createElement(tag);
|
|
6852
7098
|
if (className) node.className = className;
|
|
@@ -6903,12 +7149,20 @@ function buildEnumField(field, onChange, fallbackLabel) {
|
|
|
6903
7149
|
control.value = "__other__";
|
|
6904
7150
|
const body = el("span", "aparte-elic-option-body");
|
|
6905
7151
|
const t = contextConfig();
|
|
6906
|
-
|
|
7152
|
+
const otherTitle = el("span", "aparte-elic-option-title", t.t("elicitationOther"));
|
|
7153
|
+
body.appendChild(otherTitle);
|
|
6907
7154
|
otherText = el("input", "aparte-elic-other-input");
|
|
6908
7155
|
otherText.type = "text";
|
|
6909
7156
|
otherText.placeholder = t.t("elicitationOtherPlaceholder");
|
|
6910
7157
|
otherText.style.display = "none";
|
|
6911
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
|
+
});
|
|
6912
7166
|
body.appendChild(otherText);
|
|
6913
7167
|
row.append(control, body);
|
|
6914
7168
|
list.appendChild(row);
|
|
@@ -6952,7 +7206,7 @@ function buildBooleanField(field, onChange, fallbackLabel) {
|
|
|
6952
7206
|
const list = el("div", "aparte-elic-options");
|
|
6953
7207
|
labelGroup(list, { titleId, fallbackLabel });
|
|
6954
7208
|
const name = `elic-${uuid()}`;
|
|
6955
|
-
const mk = (val, label) => {
|
|
7209
|
+
const mk = (val, label, defaulted) => {
|
|
6956
7210
|
const row = el("label", "aparte-elic-option");
|
|
6957
7211
|
const control = el("input", "aparte-elic-control");
|
|
6958
7212
|
control.type = "radio";
|
|
@@ -6960,13 +7214,17 @@ function buildBooleanField(field, onChange, fallbackLabel) {
|
|
|
6960
7214
|
control.value = val;
|
|
6961
7215
|
if (field.default != null && String(field.default) === val) control.checked = true;
|
|
6962
7216
|
const body = el("span", "aparte-elic-option-body");
|
|
6963
|
-
|
|
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);
|
|
6964
7222
|
row.append(control, body);
|
|
6965
7223
|
list.appendChild(row);
|
|
6966
7224
|
};
|
|
6967
7225
|
const t = contextConfig();
|
|
6968
|
-
mk("true", field.trueLabel ?? t.t("elicitationYes"));
|
|
6969
|
-
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");
|
|
6970
7228
|
wrap.appendChild(list);
|
|
6971
7229
|
list.addEventListener("change", onChange);
|
|
6972
7230
|
const checked = () => list.querySelector("input:checked");
|
|
@@ -6985,7 +7243,14 @@ function buildStringField(field, onChange, fallbackLabel) {
|
|
|
6985
7243
|
if (field.placeholder) input.setAttribute("placeholder", field.placeholder);
|
|
6986
7244
|
if (field.default) input.value = field.default;
|
|
6987
7245
|
if (field.maxLength != null) input.setAttribute("maxlength", String(field.maxLength));
|
|
6988
|
-
|
|
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
|
+
}
|
|
6989
7254
|
wrap.appendChild(input);
|
|
6990
7255
|
input.addEventListener("input", onChange);
|
|
6991
7256
|
const required = field.required ?? true;
|
|
@@ -7021,6 +7286,19 @@ function buildField(field, onChange, fallbackLabel, key) {
|
|
|
7021
7286
|
}
|
|
7022
7287
|
}
|
|
7023
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) {
|
|
7024
7302
|
const panel = el("div", "aparte-elic-panel");
|
|
7025
7303
|
if (message) panel.appendChild(el("p", "aparte-elic-message", message));
|
|
7026
7304
|
const body = el("div", "aparte-elic-body");
|
|
@@ -7124,67 +7402,72 @@ function requestUserInput(request) {
|
|
|
7124
7402
|
return resolveConfig(request.target ?? null).requestUserInput(request);
|
|
7125
7403
|
}
|
|
7126
7404
|
export {
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
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
|
|
7189
7472
|
};
|
|
7190
|
-
//# sourceMappingURL=index-
|
|
7473
|
+
//# sourceMappingURL=index-BnKBciDG.js.map
|