@aparte/core 0.23.0 → 0.25.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 +8 -0
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts +64 -0
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/chat/aparte-chat.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.map +1 -1
- package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/components/composer/aparte-composer-toolbar.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer.d.ts.map +1 -1
- package/dist/components/context/aparte-context.d.ts.map +1 -1
- package/dist/components/conversation-list/aparte-conversation-list.d.ts +38 -0
- package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -1
- package/dist/components/conversation-list/index.d.ts +1 -1
- package/dist/components/conversation-list/index.d.ts.map +1 -1
- package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/scroll-rail/aparte-scroll-rail.d.ts.map +1 -1
- package/dist/components/sidebar/aparte-sidebar.d.ts.map +1 -1
- package/dist/components/split/aparte-split.d.ts.map +1 -1
- package/dist/components/status/aparte-chat-status.d.ts.map +1 -1
- package/dist/components/suggestions/aparte-suggestions.d.ts.map +1 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/aparte-config.d.ts +40 -1
- package/dist/config/aparte-config.d.ts.map +1 -1
- package/dist/config/locale.d.ts +4 -0
- package/dist/config/locale.d.ts.map +1 -1
- package/dist/config/tool-table.d.ts +2 -0
- package/dist/config/tool-table.d.ts.map +1 -1
- package/dist/conversations/conversation-controller.d.ts +19 -0
- package/dist/conversations/conversation-controller.d.ts.map +1 -1
- package/dist/conversations/conversation-status.d.ts +27 -0
- package/dist/conversations/conversation-status.d.ts.map +1 -0
- package/dist/custom-elements.json +11477 -10170
- package/dist/{dialog-triggers-3G9TqG9g.js → defer-hydration-BwMqeRHq.js} +816 -211
- package/dist/defer-hydration-BwMqeRHq.js.map +1 -0
- package/dist/{glyphs-C8z2oJUg.js → glyphs-2bym5jl5.js} +1 -1
- package/dist/{glyphs-C8z2oJUg.js.map → glyphs-2bym5jl5.js.map} +1 -1
- package/dist/host/aparte-chat-host.d.ts +22 -0
- package/dist/host/aparte-chat-host.d.ts.map +1 -1
- package/dist/icons.js +2 -2
- package/dist/index.css +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +338 -58
- package/dist/index.js.map +1 -1
- package/dist/index.node.d.ts +4 -3
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +35 -29
- package/dist/index.node.js.map +1 -1
- package/dist/interop/defer-hydration.d.ts +34 -0
- package/dist/interop/defer-hydration.d.ts.map +1 -0
- package/dist/interop/element-props.d.ts +1 -1
- package/dist/interop/element-props.d.ts.map +1 -1
- package/dist/primitives/icon/aparte-icon.d.ts.map +1 -1
- package/dist/primitives/progress-spinner/aparte-progress-spinner.d.ts.map +1 -1
- package/dist/primitives/select/aparte-optgroup.d.ts.map +1 -1
- package/dist/primitives/select/aparte-option.d.ts.map +1 -1
- package/dist/primitives/select/aparte-select.d.ts.map +1 -1
- package/dist/renderers/index.d.ts +2 -1
- package/dist/renderers/index.d.ts.map +1 -1
- package/dist/renderers/mounted-renderer.d.ts +34 -0
- package/dist/renderers/mounted-renderer.d.ts.map +1 -0
- package/dist/renderers/segment-renderers.d.ts +14 -1
- package/dist/renderers/segment-renderers.d.ts.map +1 -1
- package/dist/renderers/segments/thinking.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts +2 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/dist/types/event-map.d.ts +4 -2
- package/dist/types/event-map.d.ts.map +1 -1
- package/dist/types/events.d.ts +15 -0
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/segments.d.ts +21 -3
- package/dist/types/segments.d.ts.map +1 -1
- package/dist/types/tools.d.ts +95 -0
- package/dist/types/tools.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/dialog-triggers-3G9TqG9g.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { toolRefusal as toolRefusal$1, unansweredTail, runStreamAgent } from "@aparte/engine";
|
|
2
|
-
import { A as APARTE_ICON_GLYPHS } from "./glyphs-
|
|
2
|
+
import { A as APARTE_ICON_GLYPHS } from "./glyphs-2bym5jl5.js";
|
|
3
3
|
function contentToText(content) {
|
|
4
4
|
if (typeof content === "string") return content;
|
|
5
5
|
return content.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
@@ -173,6 +173,8 @@ const APARTE_DEFAULT_LOCALE = {
|
|
|
173
173
|
archiveConversation: "Archive",
|
|
174
174
|
unarchiveConversation: "Unarchive",
|
|
175
175
|
conversationActions: "Conversation actions",
|
|
176
|
+
conversationRunning: "Answering",
|
|
177
|
+
conversationWaiting: "Waiting for you",
|
|
176
178
|
renameConversation: "Rename",
|
|
177
179
|
conversationTitle: "Conversation title",
|
|
178
180
|
pinConversation: "Pin",
|
|
@@ -948,13 +950,13 @@ function stampSegmentOnInsert(segments, segment, messageId, defaults) {
|
|
|
948
950
|
...timingPatch(base, { startedAt: segmentTiming(segment)?.startedAt ?? Date.now() })
|
|
949
951
|
};
|
|
950
952
|
}
|
|
951
|
-
function adoptSegment(segments, segment, messageId,
|
|
953
|
+
function adoptSegment(segments, segment, messageId, live2 = false) {
|
|
952
954
|
return {
|
|
953
955
|
...segment,
|
|
954
956
|
messageId,
|
|
955
957
|
index: segments.length,
|
|
956
958
|
isStreaming: false,
|
|
957
|
-
...segment.type === "tool_call" && (segment.status === "awaiting-approval" || segment.status === "pending" && !
|
|
959
|
+
...segment.type === "tool_call" && (segment.status === "awaiting-approval" || segment.status === "pending" && !live2) && !segment.deferred?.ticket ? { status: "aborted" } : {}
|
|
958
960
|
};
|
|
959
961
|
}
|
|
960
962
|
const RESERVED = /* @__PURE__ */ new Set(["id", "type", "messageId", "index"]);
|
|
@@ -1028,11 +1030,11 @@ function openSegmentIds(segments) {
|
|
|
1028
1030
|
}
|
|
1029
1031
|
function adoptMessageSegments(message) {
|
|
1030
1032
|
if (!message.segments?.length) return { ...message };
|
|
1031
|
-
const
|
|
1033
|
+
const live2 = message.status === "streaming" || message.status === "pending";
|
|
1032
1034
|
return {
|
|
1033
1035
|
...message,
|
|
1034
1036
|
segments: message.segments.reduce((acc, segment) => {
|
|
1035
|
-
acc.push(adoptSegment(acc, segment, message.id,
|
|
1037
|
+
acc.push(adoptSegment(acc, segment, message.id, live2));
|
|
1036
1038
|
return acc;
|
|
1037
1039
|
}, [])
|
|
1038
1040
|
};
|
|
@@ -1068,8 +1070,8 @@ function detachConfig(el2) {
|
|
|
1068
1070
|
if (previous) notifyConfigChanged(el2, resolveConfig(el2.parentElement), previous);
|
|
1069
1071
|
}
|
|
1070
1072
|
function resolveConfig(el2) {
|
|
1071
|
-
const
|
|
1072
|
-
return
|
|
1073
|
+
const host2 = el2?.closest?.(`[${APARTE_HOST_ATTR}]`);
|
|
1074
|
+
return host2?.[CONFIG_SLOT] ?? aparteGlobalConfig;
|
|
1073
1075
|
}
|
|
1074
1076
|
let _renderConfig = null;
|
|
1075
1077
|
function runWithConfig(config, fn) {
|
|
@@ -1102,12 +1104,20 @@ function createStreamAdapter(opts) {
|
|
|
1102
1104
|
};
|
|
1103
1105
|
let thinkingContent = "";
|
|
1104
1106
|
let thinkingCollapsed = false;
|
|
1107
|
+
const settleThinking = (fold) => {
|
|
1108
|
+
if (!thinkingId || thinkingCollapsed) return;
|
|
1109
|
+
const chatConfig = typeof Element !== "undefined" && target instanceof Element ? resolveConfig(target) : config;
|
|
1110
|
+
const keepOpen = !fold || chatConfig.getSegmentDefaults("thinking")?.collapsed === false;
|
|
1111
|
+
target.updateSegment?.(thinkingId, keepOpen ? { isStreaming: false } : { collapsed: true, isStreaming: false });
|
|
1112
|
+
thinkingCollapsed = true;
|
|
1113
|
+
};
|
|
1105
1114
|
return (e) => {
|
|
1106
1115
|
switch (e.type) {
|
|
1107
1116
|
case "run-start":
|
|
1108
1117
|
target.updateMessage?.(messageId, { status: "streaming" });
|
|
1109
1118
|
break;
|
|
1110
1119
|
case "turn-start":
|
|
1120
|
+
settleThinking(false);
|
|
1111
1121
|
parser = new AparteStreamParser({ blocks: config.getStreamBlocks() });
|
|
1112
1122
|
streaming = /* @__PURE__ */ new Set();
|
|
1113
1123
|
thinkingId = null;
|
|
@@ -1120,7 +1130,8 @@ function createStreamAdapter(opts) {
|
|
|
1120
1130
|
const seg = {
|
|
1121
1131
|
id: `think-${uuid()}`,
|
|
1122
1132
|
type: "thinking",
|
|
1123
|
-
content: thinkingContent
|
|
1133
|
+
content: thinkingContent,
|
|
1134
|
+
isStreaming: true
|
|
1124
1135
|
};
|
|
1125
1136
|
thinkingId = seg.id;
|
|
1126
1137
|
streaming.add(seg.id);
|
|
@@ -1131,12 +1142,7 @@ function createStreamAdapter(opts) {
|
|
|
1131
1142
|
break;
|
|
1132
1143
|
}
|
|
1133
1144
|
case "text-delta": {
|
|
1134
|
-
|
|
1135
|
-
const chatConfig = typeof Element !== "undefined" && target instanceof Element ? resolveConfig(target) : config;
|
|
1136
|
-
const keepOpen = chatConfig.getSegmentDefaults("thinking")?.collapsed === false;
|
|
1137
|
-
target.updateSegment?.(thinkingId, keepOpen ? { isStreaming: false } : { collapsed: true, isStreaming: false });
|
|
1138
|
-
thinkingCollapsed = true;
|
|
1139
|
-
}
|
|
1145
|
+
settleThinking(true);
|
|
1140
1146
|
const result = parser.parse(e.delta);
|
|
1141
1147
|
for (const segment of result.segments) {
|
|
1142
1148
|
if (!streaming.has(segment.id)) {
|
|
@@ -1166,6 +1172,7 @@ function createStreamAdapter(opts) {
|
|
|
1166
1172
|
break;
|
|
1167
1173
|
}
|
|
1168
1174
|
case "tool-start": {
|
|
1175
|
+
settleThinking(false);
|
|
1169
1176
|
const toolSeg = {
|
|
1170
1177
|
id: `tool-${e.toolCallId}`,
|
|
1171
1178
|
type: "tool_call",
|
|
@@ -1890,6 +1897,12 @@ class ToolTable {
|
|
|
1890
1897
|
}
|
|
1891
1898
|
return this.any !== void 0 ? { value: this.any, level: "any" } : void 0;
|
|
1892
1899
|
}
|
|
1900
|
+
/** Every value registered, whatever its matcher. */
|
|
1901
|
+
values() {
|
|
1902
|
+
const out = [...this.exact.values(), ...[...this.patterns.values()].map((p) => p.value)];
|
|
1903
|
+
if (this.any !== void 0) out.push(this.any);
|
|
1904
|
+
return out;
|
|
1905
|
+
}
|
|
1893
1906
|
clear() {
|
|
1894
1907
|
this.exact.clear();
|
|
1895
1908
|
this.patterns.clear();
|
|
@@ -1915,6 +1928,34 @@ function renderingVersionOf(cfg) {
|
|
|
1915
1928
|
function bumpRenderingVersion(cfg) {
|
|
1916
1929
|
renderingVersions.set(cfg, renderingVersionOf(cfg) + 1);
|
|
1917
1930
|
}
|
|
1931
|
+
const rendererListeners = /* @__PURE__ */ new Set();
|
|
1932
|
+
const pendingRendererChanges = /* @__PURE__ */ new Set();
|
|
1933
|
+
function onRenderersChange(listener) {
|
|
1934
|
+
rendererListeners.add(listener);
|
|
1935
|
+
return () => {
|
|
1936
|
+
rendererListeners.delete(listener);
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
function notifyRenderersChanged(cfg) {
|
|
1940
|
+
if (pendingRendererChanges.size === 0) {
|
|
1941
|
+
queueMicrotask(() => {
|
|
1942
|
+
const changed = [...pendingRendererChanges];
|
|
1943
|
+
pendingRendererChanges.clear();
|
|
1944
|
+
for (const config of changed) {
|
|
1945
|
+
for (const listener of [...rendererListeners]) {
|
|
1946
|
+
try {
|
|
1947
|
+
listener(config);
|
|
1948
|
+
} catch (err) {
|
|
1949
|
+
console.error("[aparte] a renderer-change listener threw", err);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
pendingRendererChanges.add(cfg);
|
|
1956
|
+
}
|
|
1957
|
+
const matcherKey = (match) => match instanceof RegExp ? `re:${String(match)}` : `name:${match}`;
|
|
1958
|
+
const ELEMENT_SHAPING = ["hidden", "open", "details", "quiet"];
|
|
1918
1959
|
function drawingOf(provider, name) {
|
|
1919
1960
|
if (!provider || !(name in provider) || name in Object.prototype) return void 0;
|
|
1920
1961
|
const fn = provider[name];
|
|
@@ -2025,6 +2066,11 @@ class AparteConfig {
|
|
|
2025
2066
|
_tools = /* @__PURE__ */ new Map();
|
|
2026
2067
|
// By exact name, pattern or '*' — see `tool-table.ts` for the one resolution rule.
|
|
2027
2068
|
_toolRenderers = new ToolTable();
|
|
2069
|
+
/** {@link overrideToolRenderer}'s stacks: what each matcher held below every override. */
|
|
2070
|
+
/** Per matcher: what it held below every override, then one entry per override — an object of its own, so two overrides of the same renderer are told apart. */
|
|
2071
|
+
_toolRendererOverrides = /* @__PURE__ */ new Map();
|
|
2072
|
+
/** Bumped by `reset()`: a restore function from before it must not undo what came after. */
|
|
2073
|
+
_overrideGeneration = 0;
|
|
2028
2074
|
_toolDisplays = new ToolTable();
|
|
2029
2075
|
/** Tagged blocks the stream parser recognises in the prose — see {@link registerStreamBlock}. */
|
|
2030
2076
|
_streamBlocks = /* @__PURE__ */ new Map();
|
|
@@ -2866,12 +2912,64 @@ class AparteConfig {
|
|
|
2866
2912
|
* // Custom pill for a web-search tool
|
|
2867
2913
|
* aparteGlobalConfig.registerToolRenderer('web_search', { render: (seg) => `<div class="aparte-tool-label">Searching...</div>` });
|
|
2868
2914
|
*
|
|
2915
|
+
* Calls already on screen follow: those this registration now draws are drawn again
|
|
2916
|
+
* by it (the renderer that drew them before gets its `cleanup`), and nothing else on
|
|
2917
|
+
* the page is touched.
|
|
2918
|
+
*
|
|
2869
2919
|
* @example
|
|
2870
2920
|
* // One look for every tool an MCP server brings
|
|
2871
2921
|
* aparteGlobalConfig.registerToolRenderer(/^mcp__/, { render: (seg) => myMcpCard(seg) });
|
|
2872
2922
|
*/
|
|
2873
2923
|
registerToolRenderer(match, renderer) {
|
|
2924
|
+
if (this._toolRenderers.get(match) === renderer) return;
|
|
2874
2925
|
this._toolRenderers.set(match, renderer);
|
|
2926
|
+
notifyRenderersChanged(this);
|
|
2927
|
+
}
|
|
2928
|
+
/**
|
|
2929
|
+
* Put `renderer` over whatever this matcher holds, and get back the function that takes
|
|
2930
|
+
* it off again — the registration a component makes for as long as it is mounted.
|
|
2931
|
+
*
|
|
2932
|
+
* Overrides stack per matcher. The function restores what was there before THIS
|
|
2933
|
+
* override, and taking off an override that is no longer on top changes nothing on
|
|
2934
|
+
* screen: two components declaring the same tool, the newer wins, and unmounting the
|
|
2935
|
+
* older does not take the newer's away. When the last one goes, the matcher holds what
|
|
2936
|
+
* it held before the first — another registration, or nothing. Calls on screen follow
|
|
2937
|
+
* each change, as with {@link registerToolRenderer}. The returned function is safe to
|
|
2938
|
+
* call twice.
|
|
2939
|
+
*
|
|
2940
|
+
* @example
|
|
2941
|
+
* const restore = aparteGlobalConfig.overrideToolRenderer('get_weather', weatherCard);
|
|
2942
|
+
* // …later, when the component that declared it unmounts:
|
|
2943
|
+
* restore();
|
|
2944
|
+
*/
|
|
2945
|
+
overrideToolRenderer(match, renderer) {
|
|
2946
|
+
const key = matcherKey(match);
|
|
2947
|
+
const current = this._toolRenderers.get(match);
|
|
2948
|
+
let stack = this._toolRendererOverrides.get(key);
|
|
2949
|
+
if (!stack) {
|
|
2950
|
+
stack = [{ renderer: current }];
|
|
2951
|
+
this._toolRendererOverrides.set(key, stack);
|
|
2952
|
+
} else if (stack[stack.length - 1].renderer !== current) {
|
|
2953
|
+
stack.splice(0, stack.length, { renderer: current });
|
|
2954
|
+
}
|
|
2955
|
+
const entry = { renderer };
|
|
2956
|
+
stack.push(entry);
|
|
2957
|
+
this.registerToolRenderer(match, renderer);
|
|
2958
|
+
const generation = this._overrideGeneration;
|
|
2959
|
+
let restored = false;
|
|
2960
|
+
return () => {
|
|
2961
|
+
if (restored || generation !== this._overrideGeneration) return;
|
|
2962
|
+
restored = true;
|
|
2963
|
+
const i = stack.indexOf(entry);
|
|
2964
|
+
if (i <= 0) return;
|
|
2965
|
+
const wasTop = i === stack.length - 1;
|
|
2966
|
+
stack.splice(i, 1);
|
|
2967
|
+
if (stack.length === 1 && this._toolRendererOverrides.get(key) === stack) this._toolRendererOverrides.delete(key);
|
|
2968
|
+
if (!wasTop || this._toolRenderers.get(match) !== renderer) return;
|
|
2969
|
+
const top = stack[stack.length - 1].renderer;
|
|
2970
|
+
if (top) this.registerToolRenderer(match, top);
|
|
2971
|
+
else this.unregisterToolRenderer(match);
|
|
2972
|
+
};
|
|
2875
2973
|
}
|
|
2876
2974
|
/**
|
|
2877
2975
|
* Field defaults for a segment TYPE, filled in when a segment enters a message.
|
|
@@ -2911,9 +3009,11 @@ class AparteConfig {
|
|
|
2911
3009
|
clearSegmentDefaults(type) {
|
|
2912
3010
|
this._segmentDefaults.delete(type);
|
|
2913
3011
|
}
|
|
2914
|
-
/** Unregister the renderer registered under this matcher. */
|
|
3012
|
+
/** Unregister the renderer registered under this matcher. Calls on screen it drew go back to core's row. */
|
|
2915
3013
|
unregisterToolRenderer(match) {
|
|
3014
|
+
if (this._toolRenderers.get(match) === void 0) return;
|
|
2916
3015
|
this._toolRenderers.delete(match);
|
|
3016
|
+
notifyRenderersChanged(this);
|
|
2917
3017
|
}
|
|
2918
3018
|
/** The renderer that draws a tool named `toolName` — exact, pattern or `'*'` — or undefined. */
|
|
2919
3019
|
getToolRenderer(toolName) {
|
|
@@ -2943,21 +3043,35 @@ class AparteConfig {
|
|
|
2943
3043
|
* @example
|
|
2944
3044
|
* // A consumer chat: the reply is what matters; the calls stay in the history.
|
|
2945
3045
|
* aparteGlobalConfig.registerToolDisplay('*', { hidden: true });
|
|
3046
|
+
*
|
|
3047
|
+
* @example
|
|
3048
|
+
* // The line, and no raw JSON behind it: nothing, or a detail of the app's own.
|
|
3049
|
+
* aparteGlobalConfig.registerToolDisplay('read_file', { label: 'Reading a file', details: false });
|
|
3050
|
+
* aparteGlobalConfig.registerToolDisplay('web_search', {
|
|
3051
|
+
* label: 'Searched the web',
|
|
3052
|
+
* details: (segment) => sourcesList(segment.result),
|
|
3053
|
+
* });
|
|
2946
3054
|
*/
|
|
2947
3055
|
registerToolDisplay(match, display) {
|
|
2948
3056
|
const before = this._toolDisplays.get(match);
|
|
2949
3057
|
const next = { ...before, ...display };
|
|
2950
3058
|
this._toolDisplays.set(match, next);
|
|
2951
|
-
|
|
3059
|
+
const shapes = ELEMENT_SHAPING.some((field) => before?.[field] !== next[field]) || before === void 0 && this._anotherShapes(next);
|
|
3060
|
+
if (shapes) bumpRenderingVersion(this);
|
|
2952
3061
|
this._notify();
|
|
2953
3062
|
}
|
|
2954
3063
|
/** Forget the display registered under this matcher. */
|
|
2955
3064
|
unregisterToolDisplay(match) {
|
|
2956
3065
|
const before = this._toolDisplays.get(match);
|
|
2957
3066
|
this._toolDisplays.delete(match);
|
|
2958
|
-
|
|
3067
|
+
const shapes = ELEMENT_SHAPING.some((field) => before?.[field] !== void 0) || before !== void 0 && this._anotherShapes(before);
|
|
3068
|
+
if (shapes) bumpRenderingVersion(this);
|
|
2959
3069
|
this._notify();
|
|
2960
3070
|
}
|
|
3071
|
+
/** Whether an entry other than `own` sets a field that shapes a row. */
|
|
3072
|
+
_anotherShapes(own) {
|
|
3073
|
+
return this._toolDisplays.values().some((entry) => entry !== own && ELEMENT_SHAPING.some((field) => entry[field] !== void 0));
|
|
3074
|
+
}
|
|
2961
3075
|
/** How a tool named `toolName` is shown — the entry that applies to it — or undefined. */
|
|
2962
3076
|
getToolDisplay(toolName) {
|
|
2963
3077
|
return this._toolDisplays.resolve(toolName)?.value;
|
|
@@ -3250,6 +3364,9 @@ class AparteConfig {
|
|
|
3250
3364
|
this._fetchedModels.clear();
|
|
3251
3365
|
this._tools.clear();
|
|
3252
3366
|
this._toolRenderers.clear();
|
|
3367
|
+
this._toolRendererOverrides.clear();
|
|
3368
|
+
this._overrideGeneration++;
|
|
3369
|
+
notifyRenderersChanged(this);
|
|
3253
3370
|
this._toolDisplays.clear();
|
|
3254
3371
|
this._streamBlocks.clear();
|
|
3255
3372
|
this._modelConfig = {};
|
|
@@ -3301,19 +3418,19 @@ const ONE_SHOT_PROBE = "<i>a</i>";
|
|
|
3301
3418
|
function hasOneShotMarkdownProvider() {
|
|
3302
3419
|
return contextConfig().renderMarkdown(ONE_SHOT_PROBE) !== escapeHtml(ONE_SHOT_PROBE);
|
|
3303
3420
|
}
|
|
3304
|
-
function writeStreamedMarkdown(
|
|
3421
|
+
function writeStreamedMarkdown(host2, contentEl, content, streaming) {
|
|
3305
3422
|
if (streaming) {
|
|
3306
|
-
if (
|
|
3423
|
+
if (host2._aparteSmd === void 0) {
|
|
3307
3424
|
settledFromParser.delete(contentEl);
|
|
3308
3425
|
const renderer = contextConfig().createStreamingMarkdownRenderer(contentEl);
|
|
3309
3426
|
if (renderer) {
|
|
3310
3427
|
contentEl.textContent = "";
|
|
3311
|
-
|
|
3428
|
+
host2._aparteSmd = { renderer, written: 0 };
|
|
3312
3429
|
} else {
|
|
3313
|
-
|
|
3430
|
+
host2._aparteSmd = null;
|
|
3314
3431
|
}
|
|
3315
3432
|
}
|
|
3316
|
-
const smd =
|
|
3433
|
+
const smd = host2._aparteSmd;
|
|
3317
3434
|
if (smd) {
|
|
3318
3435
|
const delta = content.slice(smd.written);
|
|
3319
3436
|
if (delta) {
|
|
@@ -3322,15 +3439,15 @@ function writeStreamedMarkdown(host, contentEl, content, streaming) {
|
|
|
3322
3439
|
}
|
|
3323
3440
|
return;
|
|
3324
3441
|
}
|
|
3325
|
-
} else if (
|
|
3326
|
-
const smd =
|
|
3442
|
+
} else if (host2._aparteSmd) {
|
|
3443
|
+
const smd = host2._aparteSmd;
|
|
3327
3444
|
const tail = content.slice(smd.written);
|
|
3328
3445
|
if (tail) {
|
|
3329
3446
|
smd.renderer.write(tail);
|
|
3330
3447
|
smd.written = content.length;
|
|
3331
3448
|
}
|
|
3332
3449
|
smd.renderer.end();
|
|
3333
|
-
|
|
3450
|
+
host2._aparteSmd = void 0;
|
|
3334
3451
|
if (!hasOneShotMarkdownProvider()) {
|
|
3335
3452
|
contentEl.innerHTML = contextConfig().sanitizeHtml(contentEl.innerHTML);
|
|
3336
3453
|
settledFromParser.set(contentEl, content);
|
|
@@ -3406,10 +3523,12 @@ function labelOf(segment) {
|
|
|
3406
3523
|
if (ms === void 0) return cfg.t("thought");
|
|
3407
3524
|
return cfg.t("thoughtFor").replace("{duration}", () => formatDuration(ms, cfg.getLocale().tag || void 0));
|
|
3408
3525
|
}
|
|
3526
|
+
const arriving = (segment) => segment.isStreaming === true;
|
|
3409
3527
|
function setLabel(el2, segment) {
|
|
3410
3528
|
const text = labelOf(segment);
|
|
3411
3529
|
const label = el2.querySelector(".aparte-thinking-label");
|
|
3412
3530
|
if (label && label.textContent !== text) label.textContent = text;
|
|
3531
|
+
label?.classList.toggle("aparte-shimmer", arriving(segment));
|
|
3413
3532
|
el2.querySelector(".aparte-thinking-content")?.setAttribute("aria-label", text);
|
|
3414
3533
|
}
|
|
3415
3534
|
const thinkingRenderer = {
|
|
@@ -3442,7 +3561,7 @@ const thinkingRenderer = {
|
|
|
3442
3561
|
* only part that is genuinely about reasoning rather than about disclosure.
|
|
3443
3562
|
* `.aparte-thinking-label` stays too: `relabel` below queries it.
|
|
3444
3563
|
*/
|
|
3445
|
-
render: (segment) => `<details class="aparte-segment aparte-segment-thinking aparte-accordion__item" data-segment-id="${escapeHtml(segment.id)}" ${segment.collapsed === false ? "open" : ""}><summary class="aparte-accordion__header aparte-thinking-header"><span class="aparte-thinking-label">${escapeHtml(labelOf(segment))}</span><span class="aparte-accordion__icon">${contextConfig().getIcon("expand")}</span></summary><div class="aparte-accordion__panel aparte-thinking-content" role="region" tabindex="0" aria-label="${escapeAttr(labelOf(segment))}">${contextConfig().renderMarkdown(segment.content)}</div></details>`,
|
|
3564
|
+
render: (segment) => `<details class="aparte-segment aparte-segment-thinking aparte-accordion__item" data-segment-id="${escapeHtml(segment.id)}" ${segment.collapsed === false ? "open" : ""}><summary class="aparte-accordion__header aparte-thinking-header"><span class="aparte-thinking-label${escapeAttr(arriving(segment) ? " aparte-shimmer" : "")}">${escapeHtml(labelOf(segment))}</span><span class="aparte-accordion__icon">${contextConfig().getIcon("expand")}</span></summary><div class="aparte-accordion__panel aparte-thinking-content" role="region" tabindex="0" aria-label="${escapeAttr(labelOf(segment))}">${contextConfig().renderMarkdown(segment.content)}</div></details>`,
|
|
3446
3565
|
/**
|
|
3447
3566
|
* The default label is the only config-derived text here — and `segment.label`
|
|
3448
3567
|
* still wins, exactly as in `render`, because that string is the app's.
|
|
@@ -3557,16 +3676,16 @@ function streamHighlight(element, paneSelector, content, lang, segId) {
|
|
|
3557
3676
|
if (now - (_lastHighlightAt.get(segId) ?? 0) < HIGHLIGHT_DEBOUNCE_MS) return;
|
|
3558
3677
|
markThrottle(_lastHighlightAt, segId, now);
|
|
3559
3678
|
void contextConfig(element).highlightCode(content.slice(0, cut), lang).then((html) => {
|
|
3560
|
-
const
|
|
3561
|
-
if (!
|
|
3562
|
-
if (cut <= highlightedLen(
|
|
3563
|
-
|
|
3564
|
-
const codeEl =
|
|
3679
|
+
const live2 = element.querySelector(paneSelector);
|
|
3680
|
+
if (!live2) return;
|
|
3681
|
+
if (cut <= highlightedLen(live2)) return;
|
|
3682
|
+
live2.innerHTML = html;
|
|
3683
|
+
const codeEl = live2.querySelector("code") ?? live2;
|
|
3565
3684
|
const span = document.createElement("span");
|
|
3566
3685
|
span.dataset.aparteTail = "";
|
|
3567
3686
|
span.textContent = content.slice(cut);
|
|
3568
3687
|
codeEl.appendChild(span);
|
|
3569
|
-
|
|
3688
|
+
live2.dataset.aparteHlLen = String(cut);
|
|
3570
3689
|
}).catch(() => {
|
|
3571
3690
|
});
|
|
3572
3691
|
}
|
|
@@ -3742,6 +3861,72 @@ function outcomeOf(segment) {
|
|
|
3742
3861
|
if (segment.status === "rejected" && segment.refusal) return null;
|
|
3743
3862
|
return { key: "toolOutput", text };
|
|
3744
3863
|
}
|
|
3864
|
+
function behindOf(segment) {
|
|
3865
|
+
const details = contextConfig().getToolDisplay(segment.toolCall?.name ?? "")?.details;
|
|
3866
|
+
if (details === false) return { kind: "none" };
|
|
3867
|
+
if (typeof details !== "function") return { kind: "parts" };
|
|
3868
|
+
try {
|
|
3869
|
+
const out = details(segment);
|
|
3870
|
+
if (!out) return { kind: "none" };
|
|
3871
|
+
return { kind: "body", body: typeof out === "string" ? contextConfig().sanitizeHtml(out) : out };
|
|
3872
|
+
} catch {
|
|
3873
|
+
return { kind: "none" };
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
const appDetailOf = (element) => element.querySelector(':scope > .aparte-tool-detail[data-details="app"]');
|
|
3877
|
+
function setBody(element, body) {
|
|
3878
|
+
const detail = element.querySelector(".aparte-tool-detail");
|
|
3879
|
+
if (!detail) return;
|
|
3880
|
+
if (typeof body === "string") detail.innerHTML = body;
|
|
3881
|
+
else detail.replaceChildren(body);
|
|
3882
|
+
drawnBodies.set(detail, serialized(body));
|
|
3883
|
+
}
|
|
3884
|
+
const drawnBodies = /* @__PURE__ */ new WeakMap();
|
|
3885
|
+
const serialized = (body) => typeof body === "string" ? body : body.outerHTML;
|
|
3886
|
+
function relabelBody(detail, body) {
|
|
3887
|
+
if (typeof body !== "string" && detail.contains(body)) {
|
|
3888
|
+
drawnBodies.set(detail, body.outerHTML);
|
|
3889
|
+
return;
|
|
3890
|
+
}
|
|
3891
|
+
const before = drawnBodies.get(detail);
|
|
3892
|
+
const now = serialized(body);
|
|
3893
|
+
if (before === now) return;
|
|
3894
|
+
const next = document.createElement("div");
|
|
3895
|
+
if (typeof body === "string") next.innerHTML = body;
|
|
3896
|
+
else next.append(body);
|
|
3897
|
+
const prev = document.createElement("div");
|
|
3898
|
+
if (before !== void 0) prev.innerHTML = before;
|
|
3899
|
+
if (before !== void 0 && sameShape(prev, next) && sameShape(prev, detail)) patchChanges(detail, prev, next);
|
|
3900
|
+
else detail.replaceChildren(...Array.from(next.childNodes));
|
|
3901
|
+
drawnBodies.set(detail, now);
|
|
3902
|
+
}
|
|
3903
|
+
function sameShape(a, b) {
|
|
3904
|
+
if (a.childNodes.length !== b.childNodes.length) return false;
|
|
3905
|
+
for (let i = 0; i < a.childNodes.length; i++) {
|
|
3906
|
+
const x = a.childNodes[i];
|
|
3907
|
+
const y = b.childNodes[i];
|
|
3908
|
+
if (x.nodeType !== y.nodeType || x.nodeName !== y.nodeName || !sameShape(x, y)) return false;
|
|
3909
|
+
}
|
|
3910
|
+
return true;
|
|
3911
|
+
}
|
|
3912
|
+
function patchChanges(live2, prev, next) {
|
|
3913
|
+
for (let i = 0; i < live2.childNodes.length; i++) {
|
|
3914
|
+
const l = live2.childNodes[i];
|
|
3915
|
+
const p = prev.childNodes[i];
|
|
3916
|
+
const n = next.childNodes[i];
|
|
3917
|
+
if (l instanceof Element && p instanceof Element && n instanceof Element) {
|
|
3918
|
+
for (const { name, value } of Array.from(n.attributes)) {
|
|
3919
|
+
if (p.getAttribute(name) !== value) l.setAttribute(name, value);
|
|
3920
|
+
}
|
|
3921
|
+
for (const { name } of Array.from(p.attributes)) {
|
|
3922
|
+
if (!n.hasAttribute(name)) l.removeAttribute(name);
|
|
3923
|
+
}
|
|
3924
|
+
patchChanges(l, p, n);
|
|
3925
|
+
} else if (p.nodeValue !== n.nodeValue) {
|
|
3926
|
+
l.nodeValue = n.nodeValue;
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3745
3930
|
function setNote(element, segment) {
|
|
3746
3931
|
const el2 = element.querySelector(".aparte-tool-note");
|
|
3747
3932
|
if (!el2) return;
|
|
@@ -3848,6 +4033,23 @@ function iconOf(segment) {
|
|
|
3848
4033
|
const key = cfg.getToolDisplay(segment.toolCall?.name ?? "")?.icon;
|
|
3849
4034
|
return (key ? cfg.getIcon(key) : void 0) || cfg.getIcon("tool");
|
|
3850
4035
|
}
|
|
4036
|
+
const isQuiet = (segment) => contextConfig().getToolDisplay(segment.toolCall?.name ?? "")?.quiet === true;
|
|
4037
|
+
function quietOf(segment) {
|
|
4038
|
+
const status = segment.status ?? "pending";
|
|
4039
|
+
const quiet = isQuiet(segment);
|
|
4040
|
+
return {
|
|
4041
|
+
shimmer: quiet && status === "pending",
|
|
4042
|
+
spinner: !quiet && status === "pending",
|
|
4043
|
+
stateHidden: quiet && (status === "pending" || status === "resolved")
|
|
4044
|
+
};
|
|
4045
|
+
}
|
|
4046
|
+
function setQuiet(element, segment) {
|
|
4047
|
+
const quiet = quietOf(segment);
|
|
4048
|
+
element.toggleAttribute("data-quiet", isQuiet(segment));
|
|
4049
|
+
element.querySelector(".aparte-tool-spinner")?.toggleAttribute("hidden", !quiet.spinner);
|
|
4050
|
+
element.querySelector(".aparte-tool-name")?.classList.toggle("aparte-shimmer", quiet.shimmer);
|
|
4051
|
+
element.querySelector(".aparte-tool-state")?.classList.toggle("aparte-sr-only", quiet.stateHidden);
|
|
4052
|
+
}
|
|
3851
4053
|
function setName(element, segment) {
|
|
3852
4054
|
const el2 = element.querySelector(".aparte-tool-name");
|
|
3853
4055
|
if (!el2) return;
|
|
@@ -3855,68 +4057,100 @@ function setName(element, segment) {
|
|
|
3855
4057
|
if (el2.textContent !== text) el2.textContent = text;
|
|
3856
4058
|
el2.classList.toggle("aparte-tool-name--label", isLabel);
|
|
3857
4059
|
}
|
|
3858
|
-
function pillMarkup(segment
|
|
4060
|
+
function pillMarkup(segment) {
|
|
3859
4061
|
const badge = stateBadge(segment);
|
|
3860
4062
|
const icon = iconOf(segment);
|
|
3861
4063
|
const target = toolTarget(segment);
|
|
3862
4064
|
const note = noteOf(segment);
|
|
3863
4065
|
const name = nameOf(segment);
|
|
4066
|
+
const quiet = quietOf(segment);
|
|
3864
4067
|
return `
|
|
3865
4068
|
<span class="aparte-tool-label">
|
|
3866
4069
|
<span class="aparte-tool-icon">${icon}</span>
|
|
3867
|
-
<span class="aparte-tool-name${name.isLabel ? " aparte-tool-name--label" : ""}">${escapeHtml(name.text)}</span>
|
|
4070
|
+
<span class="aparte-tool-name${name.isLabel ? " aparte-tool-name--label" : ""}${quiet.shimmer ? " aparte-shimmer" : ""}">${escapeHtml(name.text)}</span>
|
|
3868
4071
|
<span class="aparte-tool-target"${target ? "" : " hidden"}>${escapeHtml(target)}</span>
|
|
3869
4072
|
</span>
|
|
3870
|
-
<span class="aparte-spinner aparte-tool-spinner" aria-hidden="true"${
|
|
3871
|
-
<span class="aparte-tool-state">${badge}</span>
|
|
4073
|
+
<span class="aparte-spinner aparte-tool-spinner" aria-hidden="true"${quiet.spinner ? "" : " hidden"}></span>
|
|
4074
|
+
<span class="aparte-tool-state${quiet.stateHidden ? " aparte-sr-only" : ""}">${badge}</span>
|
|
3872
4075
|
<span class="aparte-tool-note"${note ? ` title="${escapeAttr(note)}"` : " hidden"}>${escapeHtml(note)}</span>`;
|
|
3873
4076
|
}
|
|
3874
4077
|
function placeholderMarkup(segment) {
|
|
3875
4078
|
return `<div class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(segment.status ?? "pending")}" data-tool-call-id="${escapeAttr(segment.toolCall?.id ?? "")}" data-tool-name="${escapeAttr(segment.toolCall?.name ?? "tool")}" hidden></div>`;
|
|
3876
4079
|
}
|
|
3877
4080
|
const isPlaceholder = (element) => element.hidden && element.classList.contains("aparte-segment-tool-call") && element.childElementCount === 0;
|
|
4081
|
+
const drawnBy = /* @__PURE__ */ new WeakMap();
|
|
4082
|
+
const behind = /* @__PURE__ */ new WeakMap();
|
|
4083
|
+
function toolRendererThatDrew(element) {
|
|
4084
|
+
return behind.get(element) ?? null;
|
|
4085
|
+
}
|
|
4086
|
+
function drawerOf(element, current) {
|
|
4087
|
+
const recorded = drawnBy.get(element);
|
|
4088
|
+
if (recorded) return recorded;
|
|
4089
|
+
return element.classList.contains("aparte-segment-tool-call") ? null : current;
|
|
4090
|
+
}
|
|
4091
|
+
function drawDefault(segment, behind2) {
|
|
4092
|
+
const name = segment.toolCall?.name ?? "tool";
|
|
4093
|
+
const status = segment.status ?? "pending";
|
|
4094
|
+
const toolCallId = segment.toolCall?.id ?? "";
|
|
4095
|
+
const pill = pillMarkup(segment);
|
|
4096
|
+
const quiet = isQuiet(segment) ? " data-quiet" : "";
|
|
4097
|
+
const toggle = contextConfig().getIcon("expand");
|
|
4098
|
+
const input = behind2.kind === "parts" ? describeInput(segment) : "";
|
|
4099
|
+
const outcome = behind2.kind === "parts" ? outcomeOf(segment) : null;
|
|
4100
|
+
if (behind2.kind === "none" || behind2.kind === "parts" && !input && !outcome) {
|
|
4101
|
+
return `
|
|
4102
|
+
<div class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}" data-tool-name="${escapeAttr(name)}"${quiet}><div class="aparte-tool-line">${pill}</div>
|
|
4103
|
+
</div>
|
|
4104
|
+
`;
|
|
4105
|
+
}
|
|
4106
|
+
const cfg = contextConfig();
|
|
4107
|
+
const copy = copyButtonMarkup("aparte-tool-copy");
|
|
4108
|
+
const open = segment.collapsed !== void 0 ? !segment.collapsed : cfg.getToolDisplay(name)?.open === true;
|
|
4109
|
+
if (behind2.kind === "body") {
|
|
4110
|
+
const tpl = document.createElement("template");
|
|
4111
|
+
tpl.innerHTML = `
|
|
4112
|
+
<details class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}" data-tool-name="${escapeAttr(name)}"${quiet}${open ? " open" : ""}>
|
|
4113
|
+
<summary class="aparte-tool-summary"><span class="aparte-tool-toggle">${toggle}</span>${pill}</summary>
|
|
4114
|
+
<div class="aparte-tool-detail" data-details="app"></div>
|
|
4115
|
+
</details>`.trim();
|
|
4116
|
+
const row = tpl.content.firstElementChild;
|
|
4117
|
+
setBody(row, behind2.body);
|
|
4118
|
+
return row;
|
|
4119
|
+
}
|
|
4120
|
+
return `
|
|
4121
|
+
<details class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}" data-tool-name="${escapeAttr(name)}"${quiet}${open ? " open" : ""}>
|
|
4122
|
+
<summary class="aparte-tool-summary"><span class="aparte-tool-toggle">${toggle}</span>${pill}</summary>
|
|
4123
|
+
<div class="aparte-tool-detail">
|
|
4124
|
+
${input ? `
|
|
4125
|
+
<div class="aparte-tool-part" data-part="input">
|
|
4126
|
+
<div class="aparte-tool-part-head"><span class="aparte-tool-part-label">${escapeHtml(cfg.t("toolInput"))}</span>${copy}</div>
|
|
4127
|
+
<div class="aparte-tool-part-body" role="region" tabindex="0" aria-label="${escapeAttr(cfg.t("toolInput"))}"><pre><code>${escapeHtml(input)}</code></pre></div>
|
|
4128
|
+
</div>` : ""}
|
|
4129
|
+
${outcome ? `
|
|
4130
|
+
<div class="aparte-tool-part" data-part="output">
|
|
4131
|
+
<div class="aparte-tool-part-head"><span class="aparte-tool-part-label">${escapeHtml(cfg.t(outcome.key))}</span>${copy}</div>
|
|
4132
|
+
<div class="aparte-tool-part-body" role="region" tabindex="0" aria-label="${escapeAttr(cfg.t(outcome.key))}"><pre><code>${escapeHtml(outcome.text)}</code></pre></div>
|
|
4133
|
+
</div>` : ""}
|
|
4134
|
+
</div>
|
|
4135
|
+
</details>
|
|
4136
|
+
`;
|
|
4137
|
+
}
|
|
3878
4138
|
const toolCallRenderer = {
|
|
3879
4139
|
type: "tool_call",
|
|
3880
4140
|
render: (segment) => {
|
|
3881
4141
|
const name = segment.toolCall?.name ?? "tool";
|
|
3882
|
-
const status = segment.status ?? "pending";
|
|
3883
|
-
const toolCallId = segment.toolCall?.id ?? "";
|
|
3884
4142
|
if (contextConfig().isToolHidden(name)) return placeholderMarkup(segment);
|
|
3885
4143
|
const customRenderer = contextConfig().getToolRenderer(name);
|
|
3886
4144
|
if (customRenderer) {
|
|
3887
4145
|
injectToolRendererStyles(name, customRenderer);
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
if (!input && !outcome) {
|
|
3895
|
-
return `
|
|
3896
|
-
<div class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}" data-tool-name="${escapeAttr(name)}">${pill}
|
|
3897
|
-
</div>
|
|
3898
|
-
`;
|
|
4146
|
+
const out = customRenderer.render(segment);
|
|
4147
|
+
if (out instanceof HTMLElement) {
|
|
4148
|
+
drawnBy.set(out, customRenderer);
|
|
4149
|
+
behind.set(out, customRenderer);
|
|
4150
|
+
}
|
|
4151
|
+
return out || placeholderMarkup(segment);
|
|
3899
4152
|
}
|
|
3900
|
-
|
|
3901
|
-
const copy = copyButtonMarkup("aparte-tool-copy");
|
|
3902
|
-
const open = segment.collapsed !== void 0 ? !segment.collapsed : cfg.getToolDisplay(name)?.open === true;
|
|
3903
|
-
return `
|
|
3904
|
-
<details class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}" data-tool-name="${escapeAttr(name)}"${open ? " open" : ""}>
|
|
3905
|
-
<summary class="aparte-tool-summary"><span class="aparte-tool-toggle">${toggle}</span>${pill}</summary>
|
|
3906
|
-
<div class="aparte-tool-detail">
|
|
3907
|
-
${input ? `
|
|
3908
|
-
<div class="aparte-tool-part" data-part="input">
|
|
3909
|
-
<div class="aparte-tool-part-head"><span class="aparte-tool-part-label">${escapeHtml(cfg.t("toolInput"))}</span>${copy}</div>
|
|
3910
|
-
<div class="aparte-tool-part-body" role="region" tabindex="0" aria-label="${escapeAttr(cfg.t("toolInput"))}"><pre><code>${escapeHtml(input)}</code></pre></div>
|
|
3911
|
-
</div>` : ""}
|
|
3912
|
-
${outcome ? `
|
|
3913
|
-
<div class="aparte-tool-part" data-part="output">
|
|
3914
|
-
<div class="aparte-tool-part-head"><span class="aparte-tool-part-label">${escapeHtml(cfg.t(outcome.key))}</span>${copy}</div>
|
|
3915
|
-
<div class="aparte-tool-part-body" role="region" tabindex="0" aria-label="${escapeAttr(cfg.t(outcome.key))}"><pre><code>${escapeHtml(outcome.text)}</code></pre></div>
|
|
3916
|
-
</div>` : ""}
|
|
3917
|
-
</div>
|
|
3918
|
-
</details>
|
|
3919
|
-
`;
|
|
4153
|
+
return drawDefault(segment, behindOf(segment));
|
|
3920
4154
|
},
|
|
3921
4155
|
/**
|
|
3922
4156
|
* The pill's glyphs, and the label it wears while a person is deciding.
|
|
@@ -3931,7 +4165,7 @@ const toolCallRenderer = {
|
|
|
3931
4165
|
relabel: (element, segment) => {
|
|
3932
4166
|
if (isPlaceholder(element)) return;
|
|
3933
4167
|
const cfg = contextConfig();
|
|
3934
|
-
const custom = cfg.getToolRenderer(segment.toolCall?.name);
|
|
4168
|
+
const custom = drawerOf(element, cfg.getToolRenderer(segment.toolCall?.name) ?? null);
|
|
3935
4169
|
if (custom) {
|
|
3936
4170
|
custom.relabel?.(element, segment);
|
|
3937
4171
|
return;
|
|
@@ -3949,8 +4183,14 @@ const toolCallRenderer = {
|
|
|
3949
4183
|
if (outputLabel && outcome) outputLabel.textContent = cfg.t(outcome.key);
|
|
3950
4184
|
if (outcome) element.querySelector('[data-part="output"] .aparte-tool-part-body')?.setAttribute("aria-label", cfg.t(outcome.key));
|
|
3951
4185
|
for (const button of element.querySelectorAll(".aparte-tool-copy")) relabelCopyButton(button);
|
|
4186
|
+
const appDetail = appDetailOf(element);
|
|
4187
|
+
if (appDetail) {
|
|
4188
|
+
const behind2 = behindOf(segment);
|
|
4189
|
+
if (behind2.kind === "body") relabelBody(appDetail, behind2.body);
|
|
4190
|
+
}
|
|
3952
4191
|
const statusEl = element.querySelector(".aparte-tool-state");
|
|
3953
4192
|
if (statusEl) statusEl.innerHTML = stateBadge(segment);
|
|
4193
|
+
setQuiet(element, segment);
|
|
3954
4194
|
},
|
|
3955
4195
|
/**
|
|
3956
4196
|
* Patch in place. NEVER re-render, and never touch `open`.
|
|
@@ -3972,43 +4212,77 @@ const toolCallRenderer = {
|
|
|
3972
4212
|
element.setAttribute("data-status", status);
|
|
3973
4213
|
return;
|
|
3974
4214
|
}
|
|
3975
|
-
const
|
|
3976
|
-
const
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
if (custom?.update && !hidden && !isPlaceholder(element)) {
|
|
3980
|
-
custom.update(element, segment);
|
|
4215
|
+
const custom = hidden ? null : contextConfig().getToolRenderer(segment.toolCall?.name) ?? null;
|
|
4216
|
+
const drawn = isPlaceholder(element) ? null : drawerOf(element, custom);
|
|
4217
|
+
if (drawn && drawn === custom && drawn.update && !isPlaceholder(element)) {
|
|
4218
|
+
drawn.update(element, segment);
|
|
3981
4219
|
return;
|
|
3982
4220
|
}
|
|
3983
|
-
|
|
3984
|
-
const out = toolCallRenderer.render(segment);
|
|
4221
|
+
const rebuild = (out) => {
|
|
3985
4222
|
const tpl = document.createElement("template");
|
|
3986
4223
|
tpl.innerHTML = typeof out === "string" ? out.trim() : "";
|
|
3987
4224
|
const rebuilt = typeof out === "string" ? tpl.content.firstElementChild : out;
|
|
3988
4225
|
if (rebuilt) {
|
|
4226
|
+
if (!rebuilt.hasAttribute("data-segment-id")) rebuilt.setAttribute("data-segment-id", segment.id);
|
|
4227
|
+
toolCallRenderer.cleanup?.(element, segment);
|
|
3989
4228
|
element.replaceWith(rebuilt);
|
|
3990
4229
|
toolCallRenderer.setup?.(rebuilt, segment);
|
|
3991
4230
|
}
|
|
4231
|
+
};
|
|
4232
|
+
if (custom || drawn || hidden || isPlaceholder(element)) {
|
|
4233
|
+
rebuild(toolCallRenderer.render(segment));
|
|
4234
|
+
return;
|
|
4235
|
+
}
|
|
4236
|
+
const behind2 = behindOf(segment);
|
|
4237
|
+
const input = behind2.kind === "parts" ? describeInput(segment) : "";
|
|
4238
|
+
const outcome = behind2.kind === "parts" ? outcomeOf(segment) : null;
|
|
4239
|
+
const wantsDetail = behind2.kind === "body" || !!input || !!outcome;
|
|
4240
|
+
const holdsAppDetail = !!appDetailOf(element);
|
|
4241
|
+
if (wantsDetail !== (element.tagName === "DETAILS") || wantsDetail && behind2.kind === "body" !== holdsAppDetail) {
|
|
4242
|
+
rebuild(drawDefault(segment, behind2));
|
|
3992
4243
|
return;
|
|
3993
4244
|
}
|
|
3994
4245
|
element.setAttribute("data-status", status);
|
|
3995
4246
|
const badge = element.querySelector(".aparte-tool-state");
|
|
3996
4247
|
if (badge) badge.innerHTML = stateBadge(segment);
|
|
3997
|
-
element
|
|
4248
|
+
setQuiet(element, segment);
|
|
3998
4249
|
setName(element, segment);
|
|
3999
4250
|
setTarget(element, segment);
|
|
4000
4251
|
setNote(element, segment);
|
|
4252
|
+
if (behind2.kind === "body") {
|
|
4253
|
+
setBody(element, behind2.body);
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4001
4256
|
setPart(element, "input", input, "toolInput");
|
|
4002
4257
|
setPart(element, "output", outcome?.text ?? "", outcome?.key ?? "toolOutput");
|
|
4003
4258
|
highlightParts(element);
|
|
4004
4259
|
},
|
|
4005
4260
|
setup: (element, segment) => {
|
|
4261
|
+
const hidden = contextConfig().isToolHidden(segment.toolCall?.name ?? "tool");
|
|
4262
|
+
const customRenderer = hidden ? void 0 : contextConfig().getToolRenderer(segment.toolCall?.name);
|
|
4263
|
+
if (customRenderer) behind.set(element, customRenderer);
|
|
4006
4264
|
if (isPlaceholder(element)) return;
|
|
4007
4265
|
wireParts(element);
|
|
4008
4266
|
highlightParts(element);
|
|
4009
|
-
|
|
4267
|
+
if (customRenderer) drawnBy.set(element, customRenderer);
|
|
4010
4268
|
customRenderer?.setup?.(element, segment);
|
|
4011
4269
|
},
|
|
4270
|
+
/**
|
|
4271
|
+
* The row leaves for good: the registered renderer that drew it, if any, cleans up. A
|
|
4272
|
+
* cleanup that throws is reported and goes no further: the row is replaced or removed
|
|
4273
|
+
* all the same, and the stream that caused it is not interrupted.
|
|
4274
|
+
*/
|
|
4275
|
+
cleanup: (element, segment) => {
|
|
4276
|
+
behind.delete(element);
|
|
4277
|
+
const custom = drawnBy.get(element);
|
|
4278
|
+
if (!custom) return;
|
|
4279
|
+
drawnBy.delete(element);
|
|
4280
|
+
try {
|
|
4281
|
+
custom.cleanup?.(element, segment);
|
|
4282
|
+
} catch (err) {
|
|
4283
|
+
console.error("[aparte] a tool renderer's cleanup threw", err);
|
|
4284
|
+
}
|
|
4285
|
+
},
|
|
4012
4286
|
// CSS lives in styles/segment/ — see that folder for why a
|
|
4013
4287
|
// built-in's rules belong there. `getStyles` stays for a CONSUMER's renderer, which
|
|
4014
4288
|
// has no other way onto the page.
|
|
@@ -4032,8 +4306,51 @@ function effectiveRenderers(config) {
|
|
|
4032
4306
|
}
|
|
4033
4307
|
let styleElement = null;
|
|
4034
4308
|
function registerSegmentRenderer(renderer, config = contextConfig()) {
|
|
4035
|
-
registryFor(config)
|
|
4309
|
+
const reg = registryFor(config);
|
|
4310
|
+
if (reg.renderers.get(renderer.type) === renderer) return;
|
|
4311
|
+
reg.renderers.set(renderer.type, renderer);
|
|
4036
4312
|
injectRendererStyles(config);
|
|
4313
|
+
notifyRenderersChanged(config);
|
|
4314
|
+
}
|
|
4315
|
+
const overrideStacks = /* @__PURE__ */ new WeakMap();
|
|
4316
|
+
function overrideSegmentRenderer(renderer, config = contextConfig()) {
|
|
4317
|
+
const type = renderer.type;
|
|
4318
|
+
const reg = registryFor(config);
|
|
4319
|
+
let byType = overrideStacks.get(config);
|
|
4320
|
+
if (!byType) {
|
|
4321
|
+
byType = /* @__PURE__ */ new Map();
|
|
4322
|
+
overrideStacks.set(config, byType);
|
|
4323
|
+
}
|
|
4324
|
+
const current = reg.renderers.get(type);
|
|
4325
|
+
let stack = byType.get(type);
|
|
4326
|
+
if (!stack) {
|
|
4327
|
+
stack = [{ renderer: current }];
|
|
4328
|
+
byType.set(type, stack);
|
|
4329
|
+
} else if (stack[stack.length - 1].renderer !== current) stack.splice(0, stack.length, { renderer: current });
|
|
4330
|
+
const entry = { renderer };
|
|
4331
|
+
stack.push(entry);
|
|
4332
|
+
registerSegmentRenderer(renderer, config);
|
|
4333
|
+
let restored = false;
|
|
4334
|
+
return () => {
|
|
4335
|
+
if (restored) return;
|
|
4336
|
+
restored = true;
|
|
4337
|
+
const i = stack.indexOf(entry);
|
|
4338
|
+
if (i <= 0) return;
|
|
4339
|
+
const wasTop = i === stack.length - 1;
|
|
4340
|
+
stack.splice(i, 1);
|
|
4341
|
+
if (stack.length === 1 && byType.get(type) === stack) byType.delete(type);
|
|
4342
|
+
if (!wasTop || reg.renderers.get(type) !== renderer) return;
|
|
4343
|
+
const top = stack[stack.length - 1].renderer;
|
|
4344
|
+
if (top) {
|
|
4345
|
+
registerSegmentRenderer(top, config);
|
|
4346
|
+
return;
|
|
4347
|
+
}
|
|
4348
|
+
unregisterSegmentRenderer(type, config);
|
|
4349
|
+
if (!effectiveRenderers(config).has(type) && reg.defaultsInstalled && !reg.defaultsDeclined) {
|
|
4350
|
+
const builtIn = DEFAULT_RENDERERS.find((r) => r.type === type);
|
|
4351
|
+
if (builtIn) registerSegmentRenderer(builtIn, config);
|
|
4352
|
+
}
|
|
4353
|
+
};
|
|
4037
4354
|
}
|
|
4038
4355
|
function declineDefaultRenderers(config = contextConfig()) {
|
|
4039
4356
|
registryFor(config).defaultsDeclined = true;
|
|
@@ -4048,7 +4365,10 @@ function installDefaultRenderersOnce(config = contextConfig()) {
|
|
|
4048
4365
|
injectRendererStyles(config);
|
|
4049
4366
|
}
|
|
4050
4367
|
function unregisterSegmentRenderer(type, config = contextConfig()) {
|
|
4051
|
-
registryFor(config)
|
|
4368
|
+
const reg = registryFor(config);
|
|
4369
|
+
if (!reg.renderers.has(type)) return;
|
|
4370
|
+
reg.renderers.delete(type);
|
|
4371
|
+
notifyRenderersChanged(config);
|
|
4052
4372
|
}
|
|
4053
4373
|
function getSegmentRenderer(type, config = contextConfig()) {
|
|
4054
4374
|
return effectiveRenderers(config).get(type);
|
|
@@ -4108,6 +4428,71 @@ const DEFAULT_RENDERERS = [
|
|
|
4108
4428
|
errorRenderer,
|
|
4109
4429
|
toolCallRenderer
|
|
4110
4430
|
];
|
|
4431
|
+
const mountedRenderers = /* @__PURE__ */ new WeakSet();
|
|
4432
|
+
function isMountedRenderer(renderer) {
|
|
4433
|
+
return !!renderer && mountedRenderers.has(renderer);
|
|
4434
|
+
}
|
|
4435
|
+
function toToolCallView(segment) {
|
|
4436
|
+
return {
|
|
4437
|
+
id: segment.toolCall?.id ?? segment.id,
|
|
4438
|
+
name: segment.toolCall?.name ?? "tool",
|
|
4439
|
+
input: segment.toolCall?.input,
|
|
4440
|
+
status: segment.status ?? "pending",
|
|
4441
|
+
result: segment.result,
|
|
4442
|
+
structuredResult: segment.structuredResult,
|
|
4443
|
+
deferred: segment.deferred,
|
|
4444
|
+
refusal: segment.refusal,
|
|
4445
|
+
abortCause: segment.abortCause,
|
|
4446
|
+
segment
|
|
4447
|
+
};
|
|
4448
|
+
}
|
|
4449
|
+
function host(className, segmentId) {
|
|
4450
|
+
const el2 = document.createElement("div");
|
|
4451
|
+
el2.className = className;
|
|
4452
|
+
el2.setAttribute("data-segment-id", segmentId);
|
|
4453
|
+
return el2;
|
|
4454
|
+
}
|
|
4455
|
+
function defineMountedToolRenderer(mount) {
|
|
4456
|
+
const mounted = /* @__PURE__ */ new WeakMap();
|
|
4457
|
+
const renderer = {
|
|
4458
|
+
render: (segment) => host("aparte-tool-ui", segment.id),
|
|
4459
|
+
setup: (el2, segment) => {
|
|
4460
|
+
if (!mounted.has(el2)) mounted.set(el2, mount(el2, toToolCallView(segment)));
|
|
4461
|
+
},
|
|
4462
|
+
update: (el2, segment) => {
|
|
4463
|
+
mounted.get(el2)?.update(toToolCallView(segment));
|
|
4464
|
+
},
|
|
4465
|
+
cleanup: (el2) => {
|
|
4466
|
+
const view = mounted.get(el2);
|
|
4467
|
+
if (!view) return;
|
|
4468
|
+
mounted.delete(el2);
|
|
4469
|
+
view.destroy();
|
|
4470
|
+
}
|
|
4471
|
+
};
|
|
4472
|
+
mountedRenderers.add(renderer);
|
|
4473
|
+
return renderer;
|
|
4474
|
+
}
|
|
4475
|
+
function defineMountedSegmentRenderer(type, mount) {
|
|
4476
|
+
const mounted = /* @__PURE__ */ new WeakMap();
|
|
4477
|
+
const renderer = {
|
|
4478
|
+
type,
|
|
4479
|
+
render: (segment) => host("aparte-segment-ui", segment.id),
|
|
4480
|
+
setup: (el2, segment) => {
|
|
4481
|
+
if (!mounted.has(el2)) mounted.set(el2, mount(el2, segment));
|
|
4482
|
+
},
|
|
4483
|
+
update: (el2, segment) => {
|
|
4484
|
+
mounted.get(el2)?.update(segment);
|
|
4485
|
+
},
|
|
4486
|
+
cleanup: (el2) => {
|
|
4487
|
+
const view = mounted.get(el2);
|
|
4488
|
+
if (!view) return;
|
|
4489
|
+
mounted.delete(el2);
|
|
4490
|
+
view.destroy();
|
|
4491
|
+
}
|
|
4492
|
+
};
|
|
4493
|
+
mountedRenderers.add(renderer);
|
|
4494
|
+
return renderer;
|
|
4495
|
+
}
|
|
4111
4496
|
function populateBubbleFromMessage(bubble, message, siblingInfo) {
|
|
4112
4497
|
if (message.segments && message.segments.length > 0) {
|
|
4113
4498
|
const existing = bubble.getSegments?.() ?? [];
|
|
@@ -4607,6 +4992,21 @@ function revokeAttachmentUrls(attachments) {
|
|
|
4607
4992
|
if (attachment.url?.startsWith("blob:")) URL.revokeObjectURL(attachment.url);
|
|
4608
4993
|
}
|
|
4609
4994
|
}
|
|
4995
|
+
const known = /* @__PURE__ */ new Map();
|
|
4996
|
+
function rememberConversationStatus(id, status) {
|
|
4997
|
+
if (status) known.set(id, status);
|
|
4998
|
+
else known.delete(id);
|
|
4999
|
+
}
|
|
5000
|
+
function tellConversationStatus(id, status) {
|
|
5001
|
+
rememberConversationStatus(id, status);
|
|
5002
|
+
try {
|
|
5003
|
+
window.dispatchEvent(new CustomEvent("aparte-conversation-status", { detail: { id, status } }));
|
|
5004
|
+
} catch {
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
5007
|
+
function conversationStatusOf(id) {
|
|
5008
|
+
return known.get(id) ?? null;
|
|
5009
|
+
}
|
|
4610
5010
|
const MAX_COMPACTION_SAVE_WAITS = 6;
|
|
4611
5011
|
class AparteConversationController {
|
|
4612
5012
|
_binding;
|
|
@@ -4615,6 +5015,10 @@ class AparteConversationController {
|
|
|
4615
5015
|
_isStreaming = false;
|
|
4616
5016
|
_isLoadingConversation = false;
|
|
4617
5017
|
_ensureInFlight = null;
|
|
5018
|
+
/** The conversation the turn in flight started in, and what the lists were told of it. */
|
|
5019
|
+
_turn = null;
|
|
5020
|
+
/** The reply the turn in flight writes into, from its `aparte-message-start`. */
|
|
5021
|
+
_turnMessage = null;
|
|
4618
5022
|
_onSendCapture = null;
|
|
4619
5023
|
_onPathChanged = null;
|
|
4620
5024
|
_onMessageStart = null;
|
|
@@ -4622,6 +5026,7 @@ class AparteConversationController {
|
|
|
4622
5026
|
_onMessageError = null;
|
|
4623
5027
|
_onMessageAborted = null;
|
|
4624
5028
|
_onMessagePaused = null;
|
|
5029
|
+
_onMessageResumed = null;
|
|
4625
5030
|
_onSelectConversation = null;
|
|
4626
5031
|
_onCompactDone = null;
|
|
4627
5032
|
_unsubscribeManager = null;
|
|
@@ -4702,11 +5107,38 @@ class AparteConversationController {
|
|
|
4702
5107
|
"[aparte] This chat resolves its own config, but the conversation manager was registered on aparteGlobalConfig — so nothing is being persisted. Pass the same config to init(): `init(adapter, myConfig)`, using the config you gave the chat."
|
|
4703
5108
|
);
|
|
4704
5109
|
}
|
|
5110
|
+
/**
|
|
5111
|
+
* Tell every conversation list on the page what the turn is doing, through
|
|
5112
|
+
* `aparte-conversation-status` on `window` — the mirror of the list's
|
|
5113
|
+
* `aparte-conversation-select`, which this controller hears. Always about the
|
|
5114
|
+
* conversation the turn started in: the person may have opened another since. A turn
|
|
5115
|
+
* that starts before its conversation exists (a first message) is told once it does.
|
|
5116
|
+
*/
|
|
5117
|
+
_setTurn(status) {
|
|
5118
|
+
if (!status) {
|
|
5119
|
+
if (this._turn) tellConversationStatus(this._turn.id, null);
|
|
5120
|
+
this._turn = null;
|
|
5121
|
+
return;
|
|
5122
|
+
}
|
|
5123
|
+
const id = this._turn?.id ?? (status === "running" ? this._activeId ?? this._manager()?.activeId ?? null : null);
|
|
5124
|
+
if (!id) return;
|
|
5125
|
+
this._turn = { id, status };
|
|
5126
|
+
tellConversationStatus(id, status);
|
|
5127
|
+
}
|
|
5128
|
+
/**
|
|
5129
|
+
* Whether a lifecycle event is about the turn in flight. A turn a newer send replaced
|
|
5130
|
+
* (a chip, a retry, an edit) says it stopped AFTER the new one started: its end must not
|
|
5131
|
+
* end the new turn's state. An event without a message id is taken as this turn's.
|
|
5132
|
+
*/
|
|
5133
|
+
_ofThisTurn(e) {
|
|
5134
|
+
const id = e.detail?.messageId;
|
|
5135
|
+
return !id || !this._turnMessage || id === this._turnMessage;
|
|
5136
|
+
}
|
|
4705
5137
|
/**
|
|
4706
5138
|
* Attach event listeners to the binding host. Returns an unbind function.
|
|
4707
5139
|
*/
|
|
4708
5140
|
bind() {
|
|
4709
|
-
const
|
|
5141
|
+
const host2 = this._binding.host;
|
|
4710
5142
|
this._onSendCapture = (e) => {
|
|
4711
5143
|
const evt = e;
|
|
4712
5144
|
const targetId = evt.detail?.targetId;
|
|
@@ -4725,36 +5157,49 @@ class AparteConversationController {
|
|
|
4725
5157
|
evt.detail.echoed = true;
|
|
4726
5158
|
void this._ensureConversationAndPersist(userMsg);
|
|
4727
5159
|
};
|
|
4728
|
-
|
|
5160
|
+
host2.addEventListener("aparte-send", this._onSendCapture, { capture: true });
|
|
4729
5161
|
this._onPathChanged = () => {
|
|
4730
5162
|
if (!this._isLoadingConversation) {
|
|
4731
5163
|
this._persistActive();
|
|
4732
5164
|
}
|
|
4733
5165
|
};
|
|
4734
|
-
|
|
4735
|
-
this._onMessageStart = () => {
|
|
5166
|
+
host2.addEventListener("aparte-path-changed", this._onPathChanged);
|
|
5167
|
+
this._onMessageStart = (e) => {
|
|
4736
5168
|
this._isStreaming = true;
|
|
5169
|
+
this._turnMessage = e.detail?.messageId ?? null;
|
|
5170
|
+
this._setTurn("running");
|
|
4737
5171
|
};
|
|
4738
|
-
|
|
5172
|
+
const ended = (e) => {
|
|
5173
|
+
if (!this._ofThisTurn(e)) return;
|
|
4739
5174
|
this._isStreaming = false;
|
|
5175
|
+
this._setTurn(null);
|
|
5176
|
+
this._turnMessage = null;
|
|
5177
|
+
};
|
|
5178
|
+
this._onMessageDone = (e) => {
|
|
5179
|
+
ended(e);
|
|
4740
5180
|
queueMicrotask(() => this._persistActive());
|
|
4741
5181
|
};
|
|
4742
|
-
this._onMessageError = () => {
|
|
4743
|
-
|
|
5182
|
+
this._onMessageError = (e) => {
|
|
5183
|
+
ended(e);
|
|
4744
5184
|
queueMicrotask(() => this._persistActive());
|
|
4745
5185
|
};
|
|
4746
|
-
this._onMessageAborted = () => {
|
|
4747
|
-
|
|
5186
|
+
this._onMessageAborted = (e) => {
|
|
5187
|
+
ended(e);
|
|
4748
5188
|
queueMicrotask(() => this._persistActive());
|
|
4749
5189
|
};
|
|
4750
|
-
this._onMessagePaused = () => {
|
|
5190
|
+
this._onMessagePaused = (e) => {
|
|
5191
|
+
if (this._ofThisTurn(e)) this._setTurn("waiting");
|
|
4751
5192
|
queueMicrotask(() => this._persistActive());
|
|
4752
5193
|
};
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
5194
|
+
this._onMessageResumed = (e) => {
|
|
5195
|
+
if (this._ofThisTurn(e)) this._setTurn("running");
|
|
5196
|
+
};
|
|
5197
|
+
host2.addEventListener("aparte-message-start", this._onMessageStart);
|
|
5198
|
+
host2.addEventListener("aparte-message-done", this._onMessageDone);
|
|
5199
|
+
host2.addEventListener("aparte-message-error", this._onMessageError);
|
|
5200
|
+
host2.addEventListener("aparte-message-aborted", this._onMessageAborted);
|
|
5201
|
+
host2.addEventListener("aparte-message-paused", this._onMessagePaused);
|
|
5202
|
+
host2.addEventListener("aparte-message-resumed", this._onMessageResumed);
|
|
4758
5203
|
this._onSelectConversation = (e) => {
|
|
4759
5204
|
const evt = e;
|
|
4760
5205
|
const targetId = evt.detail?.targetId;
|
|
@@ -4781,37 +5226,44 @@ class AparteConversationController {
|
|
|
4781
5226
|
this._bound = false;
|
|
4782
5227
|
this._subscribedTo = null;
|
|
4783
5228
|
this._persistActive();
|
|
5229
|
+
if (this._turn?.status === "running") this._setTurn(null);
|
|
5230
|
+
this._turn = null;
|
|
5231
|
+
this._turnMessage = null;
|
|
4784
5232
|
this._stopHydrationRetry?.();
|
|
4785
5233
|
this._stopHydrationRetry = null;
|
|
4786
|
-
const
|
|
5234
|
+
const host2 = this._binding.host;
|
|
4787
5235
|
if (this._onSendCapture) {
|
|
4788
|
-
|
|
5236
|
+
host2.removeEventListener("aparte-send", this._onSendCapture, { capture: true });
|
|
4789
5237
|
this._onSendCapture = null;
|
|
4790
5238
|
}
|
|
4791
5239
|
if (this._onPathChanged) {
|
|
4792
|
-
|
|
5240
|
+
host2.removeEventListener("aparte-path-changed", this._onPathChanged);
|
|
4793
5241
|
this._onPathChanged = null;
|
|
4794
5242
|
}
|
|
4795
5243
|
if (this._onMessageStart) {
|
|
4796
|
-
|
|
5244
|
+
host2.removeEventListener("aparte-message-start", this._onMessageStart);
|
|
4797
5245
|
this._onMessageStart = null;
|
|
4798
5246
|
}
|
|
4799
5247
|
if (this._onMessageDone) {
|
|
4800
|
-
|
|
5248
|
+
host2.removeEventListener("aparte-message-done", this._onMessageDone);
|
|
4801
5249
|
this._onMessageDone = null;
|
|
4802
5250
|
}
|
|
4803
5251
|
if (this._onMessageError) {
|
|
4804
|
-
|
|
5252
|
+
host2.removeEventListener("aparte-message-error", this._onMessageError);
|
|
4805
5253
|
this._onMessageError = null;
|
|
4806
5254
|
}
|
|
4807
5255
|
if (this._onMessageAborted) {
|
|
4808
|
-
|
|
5256
|
+
host2.removeEventListener("aparte-message-aborted", this._onMessageAborted);
|
|
4809
5257
|
this._onMessageAborted = null;
|
|
4810
5258
|
}
|
|
4811
5259
|
if (this._onMessagePaused) {
|
|
4812
|
-
|
|
5260
|
+
host2.removeEventListener("aparte-message-paused", this._onMessagePaused);
|
|
4813
5261
|
this._onMessagePaused = null;
|
|
4814
5262
|
}
|
|
5263
|
+
if (this._onMessageResumed) {
|
|
5264
|
+
host2.removeEventListener("aparte-message-resumed", this._onMessageResumed);
|
|
5265
|
+
this._onMessageResumed = null;
|
|
5266
|
+
}
|
|
4815
5267
|
if (this._onCompactDone && typeof window !== "undefined") {
|
|
4816
5268
|
window.removeEventListener("aparte-compact-done", this._onCompactDone);
|
|
4817
5269
|
this._onCompactDone = null;
|
|
@@ -4840,6 +5292,8 @@ class AparteConversationController {
|
|
|
4840
5292
|
const isSwitch = id !== this._activeId;
|
|
4841
5293
|
const manager = this._manager();
|
|
4842
5294
|
if (isSwitch && this._isStreaming) {
|
|
5295
|
+
if (this._turn?.status === "waiting") this._turn = null;
|
|
5296
|
+
else this._setTurn(null);
|
|
4843
5297
|
this._persistActive();
|
|
4844
5298
|
try {
|
|
4845
5299
|
window.dispatchEvent(new CustomEvent("aparte-abort", {
|
|
@@ -5021,6 +5475,7 @@ class AparteConversationController {
|
|
|
5021
5475
|
}
|
|
5022
5476
|
this._activeId = convId;
|
|
5023
5477
|
this._options.onConversationCreated?.(convId);
|
|
5478
|
+
if (this._isStreaming && !this._turn) this._setTurn("running");
|
|
5024
5479
|
} else if (this._activeId !== null && this._activeId !== convId) {
|
|
5025
5480
|
await manager.addMessage(convId, userMsg).catch(() => {
|
|
5026
5481
|
});
|
|
@@ -5083,6 +5538,12 @@ class AparteChatHost {
|
|
|
5083
5538
|
_tokenBuffer = null;
|
|
5084
5539
|
/** Handle of the scheduled coalesced sync, or null when none is pending. */
|
|
5085
5540
|
_flushHandle = null;
|
|
5541
|
+
/**
|
|
5542
|
+
* Lists reported through `onMessagesChange` that the parent has not handed back yet, oldest
|
|
5543
|
+
* first — what {@link takeEcho} recognises. Bounded: a parent that never hands a list back
|
|
5544
|
+
* (an uncontrolled chat) must not make it grow for the life of the page.
|
|
5545
|
+
*/
|
|
5546
|
+
_pendingEchoes = [];
|
|
5086
5547
|
_conversationId = null;
|
|
5087
5548
|
_controller;
|
|
5088
5549
|
_teardown = [];
|
|
@@ -5092,6 +5553,34 @@ class AparteChatHost {
|
|
|
5092
5553
|
this.options = options;
|
|
5093
5554
|
this._conversationId = options.conversationId ?? null;
|
|
5094
5555
|
}
|
|
5556
|
+
/** Every report goes through here, so {@link takeEcho} knows what the parent may hand back. */
|
|
5557
|
+
_report(next) {
|
|
5558
|
+
this._pendingEchoes.push(next);
|
|
5559
|
+
if (this._pendingEchoes.length > 64) this._pendingEchoes.shift();
|
|
5560
|
+
this.binding.onMessagesChange?.(next);
|
|
5561
|
+
}
|
|
5562
|
+
/**
|
|
5563
|
+
* Whether a list the parent hands back is this host's own report coming back — the echo of
|
|
5564
|
+
* `onMessagesChange` through a two-way binding (`bind:messages`, `v-model:messages`,
|
|
5565
|
+
* `[(messages)]`, a controlled React prop). A wrapper asks before applying the list it is
|
|
5566
|
+
* given, because applying an echo rolls back what the host did since without reporting (an
|
|
5567
|
+
* optimistic append).
|
|
5568
|
+
*
|
|
5569
|
+
* True for a list reported and not handed back yet, however late it lands — a parent whose
|
|
5570
|
+
* render comes after the host's next write hands back an EARLIER report — and that list and
|
|
5571
|
+
* every older one are taken. False for any other list, the parent's own word, which clears
|
|
5572
|
+
* them: a list the parent restores later (a reopened conversation, an undo) is applied,
|
|
5573
|
+
* where remembering every list ever reported swallowed it.
|
|
5574
|
+
*/
|
|
5575
|
+
takeEcho(list) {
|
|
5576
|
+
const i = this._pendingEchoes.indexOf(list);
|
|
5577
|
+
if (i < 0) {
|
|
5578
|
+
this._pendingEchoes.length = 0;
|
|
5579
|
+
return false;
|
|
5580
|
+
}
|
|
5581
|
+
this._pendingEchoes.splice(0, i + 1);
|
|
5582
|
+
return true;
|
|
5583
|
+
}
|
|
5095
5584
|
// ── lifecycle ──────────────────────────────────────────────────────────
|
|
5096
5585
|
/**
|
|
5097
5586
|
* Install the host-method surface + event listeners and create the
|
|
@@ -5101,19 +5590,19 @@ class AparteChatHost {
|
|
|
5101
5590
|
bind() {
|
|
5102
5591
|
if (this._bound) return () => this.unbind();
|
|
5103
5592
|
this._bound = true;
|
|
5104
|
-
const
|
|
5105
|
-
if (!
|
|
5593
|
+
const host2 = this.binding.host;
|
|
5594
|
+
if (!host2.id) host2.id = this.binding.hostId;
|
|
5106
5595
|
if (this.options.config) attachConfig(this.binding.host, this.options.config);
|
|
5107
5596
|
const vp = this._vp();
|
|
5108
5597
|
vp?.setFrameworkManagedDOM?.(true);
|
|
5109
5598
|
if (this.options.layoutTransitionMs && this.options.layoutTransitionMs > 0) {
|
|
5110
5599
|
vp?.configure?.({ layoutTransitionMs: this.options.layoutTransitionMs });
|
|
5111
5600
|
}
|
|
5112
|
-
this._installHostMethods(
|
|
5113
|
-
this._installLifecycleListeners(
|
|
5114
|
-
this._teardown.push(gateGlide(
|
|
5601
|
+
this._installHostMethods(host2);
|
|
5602
|
+
this._installLifecycleListeners(host2);
|
|
5603
|
+
this._teardown.push(gateGlide(host2));
|
|
5115
5604
|
this._installViewportListeners();
|
|
5116
|
-
this._createController(
|
|
5605
|
+
this._createController(host2);
|
|
5117
5606
|
return () => this.unbind();
|
|
5118
5607
|
}
|
|
5119
5608
|
unbind() {
|
|
@@ -5159,10 +5648,10 @@ class AparteChatHost {
|
|
|
5159
5648
|
return this._controller?.setConversationId(id) ?? Promise.resolve();
|
|
5160
5649
|
}
|
|
5161
5650
|
_unbindController;
|
|
5162
|
-
_createController(
|
|
5651
|
+
_createController(host2) {
|
|
5163
5652
|
const convBinding = {
|
|
5164
5653
|
hostId: this.binding.hostId,
|
|
5165
|
-
host,
|
|
5654
|
+
host: host2,
|
|
5166
5655
|
// Controller-driven flushes mirror AparteConversationManager state, not
|
|
5167
5656
|
// user intent — deliberately NOT emitted as `messagesChange` to
|
|
5168
5657
|
// avoid a feedback loop with hosts using a two-way `messages` input.
|
|
@@ -5269,7 +5758,7 @@ class AparteChatHost {
|
|
|
5269
5758
|
const merged = { ...next[index], ...updates };
|
|
5270
5759
|
next[index] = merged;
|
|
5271
5760
|
this.binding.setMessages(next);
|
|
5272
|
-
this.
|
|
5761
|
+
this._report(next);
|
|
5273
5762
|
if (updates.status) {
|
|
5274
5763
|
this._bubbleById(messageId)?.updateMessage?.({ status: updates.status });
|
|
5275
5764
|
}
|
|
@@ -5289,7 +5778,7 @@ class AparteChatHost {
|
|
|
5289
5778
|
const newContent = options?.append ? (last.content || "") + content : content;
|
|
5290
5779
|
const next = [...msgs.slice(0, -1), { ...last, content: newContent }];
|
|
5291
5780
|
this.binding.setMessages(next);
|
|
5292
|
-
this.
|
|
5781
|
+
this._report(next);
|
|
5293
5782
|
}
|
|
5294
5783
|
/** Add a segment to the last message (immediate bubble + state sync). */
|
|
5295
5784
|
addSegment(segment) {
|
|
@@ -5314,7 +5803,7 @@ class AparteChatHost {
|
|
|
5314
5803
|
const segments = [...last.segments || [], stamped];
|
|
5315
5804
|
const next = [...msgs.slice(0, -1), { ...last, segments }];
|
|
5316
5805
|
this.binding.setMessages(next);
|
|
5317
|
-
this.
|
|
5806
|
+
this._report(next);
|
|
5318
5807
|
}
|
|
5319
5808
|
/** Update a segment in the last message in place. */
|
|
5320
5809
|
updateSegment(segmentId, updates) {
|
|
@@ -5331,7 +5820,7 @@ class AparteChatHost {
|
|
|
5331
5820
|
);
|
|
5332
5821
|
const next = [...msgs.slice(0, -1), { ...last, segments }];
|
|
5333
5822
|
this.binding.setMessages(next);
|
|
5334
|
-
this.
|
|
5823
|
+
this._report(next);
|
|
5335
5824
|
}
|
|
5336
5825
|
/** Remove a transient segment (a status row the host added itself). */
|
|
5337
5826
|
removeSegment(segmentId) {
|
|
@@ -5345,7 +5834,7 @@ class AparteChatHost {
|
|
|
5345
5834
|
renumberSegments(segments);
|
|
5346
5835
|
const next = [...msgs.slice(0, -1), { ...last, segments }];
|
|
5347
5836
|
this.binding.setMessages(next);
|
|
5348
|
-
this.
|
|
5837
|
+
this._report(next);
|
|
5349
5838
|
}
|
|
5350
5839
|
/**
|
|
5351
5840
|
* Append text to a segment's content in the last message — the streaming path
|
|
@@ -5401,8 +5890,8 @@ class AparteChatHost {
|
|
|
5401
5890
|
this._flushHandle = null;
|
|
5402
5891
|
}
|
|
5403
5892
|
const tokens = this._tokenBuffer;
|
|
5404
|
-
const
|
|
5405
|
-
if (!tokens && !
|
|
5893
|
+
const pending2 = this._segmentBuffer.size > 0 ? new Map(this._segmentBuffer) : null;
|
|
5894
|
+
if (!tokens && !pending2) return;
|
|
5406
5895
|
this._tokenBuffer = null;
|
|
5407
5896
|
this._segmentBuffer.clear();
|
|
5408
5897
|
let msgs = this.binding.getMessages();
|
|
@@ -5416,11 +5905,11 @@ class AparteChatHost {
|
|
|
5416
5905
|
changed = true;
|
|
5417
5906
|
}
|
|
5418
5907
|
}
|
|
5419
|
-
if (
|
|
5908
|
+
if (pending2) {
|
|
5420
5909
|
const last = msgs[msgs.length - 1];
|
|
5421
5910
|
if (last?.segments) {
|
|
5422
5911
|
const segments = last.segments.map((s) => {
|
|
5423
|
-
const buffered =
|
|
5912
|
+
const buffered = pending2.get(s.id);
|
|
5424
5913
|
if (buffered === void 0 || !("content" in s)) return s;
|
|
5425
5914
|
return {
|
|
5426
5915
|
...s,
|
|
@@ -5434,7 +5923,7 @@ class AparteChatHost {
|
|
|
5434
5923
|
}
|
|
5435
5924
|
if (!changed) return;
|
|
5436
5925
|
this.binding.setMessages(msgs);
|
|
5437
|
-
this.
|
|
5926
|
+
this._report(msgs);
|
|
5438
5927
|
}
|
|
5439
5928
|
/** Read the current message list. */
|
|
5440
5929
|
getMessages() {
|
|
@@ -5467,7 +5956,7 @@ class AparteChatHost {
|
|
|
5467
5956
|
const idx = msgs.findIndex((m) => m.id === messageId);
|
|
5468
5957
|
const next = idx >= 0 ? msgs.slice(0, idx) : msgs;
|
|
5469
5958
|
this.binding.setMessages(next);
|
|
5470
|
-
this.
|
|
5959
|
+
this._report(next);
|
|
5471
5960
|
}
|
|
5472
5961
|
/** Keep up to and including a user message, drop later responses (retry). */
|
|
5473
5962
|
truncateResponsesAfter(userMessageId) {
|
|
@@ -5476,7 +5965,7 @@ class AparteChatHost {
|
|
|
5476
5965
|
const idx = msgs.findIndex((m) => m.id === userMessageId);
|
|
5477
5966
|
const next = idx >= 0 ? msgs.slice(0, idx + 1) : msgs;
|
|
5478
5967
|
this.binding.setMessages(next);
|
|
5479
|
-
this.
|
|
5968
|
+
this._report(next);
|
|
5480
5969
|
}
|
|
5481
5970
|
// ── manual token streaming (agnostic core of injectTokenStream) ─────────
|
|
5482
5971
|
/**
|
|
@@ -5504,14 +5993,14 @@ class AparteChatHost {
|
|
|
5504
5993
|
}
|
|
5505
5994
|
ac.signal.addEventListener("abort", () => resolve("aborted"), { once: true });
|
|
5506
5995
|
});
|
|
5507
|
-
const { host, hostId } = this.binding;
|
|
5996
|
+
const { host: host2, hostId } = this.binding;
|
|
5508
5997
|
const onAbort = (e) => {
|
|
5509
5998
|
const target = e.detail?.targetId;
|
|
5510
5999
|
if ((!target || target === hostId) && this._streamAbort === ac) this.stopTokenStream();
|
|
5511
6000
|
};
|
|
5512
6001
|
window.addEventListener("aparte-abort", onAbort);
|
|
5513
6002
|
const replaced = () => this._streamAbort !== void 0 && this._streamAbort !== ac;
|
|
5514
|
-
dispatchLifecycleEvent(
|
|
6003
|
+
dispatchLifecycleEvent(host2, "aparte-message-start", { messageId, role: "assistant" }, hostId);
|
|
5515
6004
|
try {
|
|
5516
6005
|
for (; ; ) {
|
|
5517
6006
|
const res = await Promise.race([iterator.next(), aborted]);
|
|
@@ -5524,7 +6013,7 @@ class AparteChatHost {
|
|
|
5524
6013
|
if (!replaced()) {
|
|
5525
6014
|
this._setStreamingId(null);
|
|
5526
6015
|
dispatchLifecycleEvent(
|
|
5527
|
-
|
|
6016
|
+
host2,
|
|
5528
6017
|
ac.signal.aborted ? "aparte-message-aborted" : "aparte-message-done",
|
|
5529
6018
|
ac.signal.aborted ? { messageId } : { messageId, role: "assistant" },
|
|
5530
6019
|
hostId
|
|
@@ -5534,7 +6023,7 @@ class AparteChatHost {
|
|
|
5534
6023
|
this._flushStreamState();
|
|
5535
6024
|
if (!replaced()) {
|
|
5536
6025
|
this._setStreamingId(null);
|
|
5537
|
-
dispatchLifecycleEvent(
|
|
6026
|
+
dispatchLifecycleEvent(host2, "aparte-message-error", { messageId, error: err }, hostId);
|
|
5538
6027
|
}
|
|
5539
6028
|
throw err;
|
|
5540
6029
|
} finally {
|
|
@@ -5692,22 +6181,22 @@ class AparteChatHost {
|
|
|
5692
6181
|
const all = vp.querySelectorAll("aparte-chat-bubble");
|
|
5693
6182
|
return all.length ? all[all.length - 1] : null;
|
|
5694
6183
|
}
|
|
5695
|
-
_installHostMethods(
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
}
|
|
5710
|
-
_installLifecycleListeners(
|
|
6184
|
+
_installHostMethods(host2) {
|
|
6185
|
+
host2["appendMessage"] = (m) => this.appendMessage(m);
|
|
6186
|
+
host2["updateMessage"] = (id, u) => this.updateMessage(id, u);
|
|
6187
|
+
host2["updateLastMessage"] = (c, o) => this.updateLastMessage(c, o);
|
|
6188
|
+
host2["addSegment"] = (s) => this.addSegment(s);
|
|
6189
|
+
host2["updateSegment"] = (id, u) => this.updateSegment(id, u);
|
|
6190
|
+
host2["removeSegment"] = (id) => this.removeSegment(id);
|
|
6191
|
+
host2["appendToSegment"] = (id, c) => this.appendToSegment(id, c);
|
|
6192
|
+
host2["getMessages"] = () => this.getMessages();
|
|
6193
|
+
host2["addBranch"] = (id) => this.addBranch(id);
|
|
6194
|
+
host2["addSiblingOf"] = (id, m) => this.addSiblingOf(id, m);
|
|
6195
|
+
host2["truncateFrom"] = (id) => this.truncateFrom(id);
|
|
6196
|
+
host2["truncateResponsesAfter"] = (id) => this.truncateResponsesAfter(id);
|
|
6197
|
+
host2["clearAll"] = (options) => this.clearMessages(options);
|
|
6198
|
+
}
|
|
6199
|
+
_installLifecycleListeners(host2) {
|
|
5711
6200
|
const onStart = (e) => {
|
|
5712
6201
|
const d = e?.detail;
|
|
5713
6202
|
if (d?.messageId) this._setStreamingId(d.messageId);
|
|
@@ -5727,7 +6216,7 @@ class AparteChatHost {
|
|
|
5727
6216
|
(m) => m.id === d.messageId ? { ...m, usage: d.usage } : m
|
|
5728
6217
|
);
|
|
5729
6218
|
this.binding.setMessages(next);
|
|
5730
|
-
this.
|
|
6219
|
+
this._report(next);
|
|
5731
6220
|
this._bubbleById(d.messageId)?.setUsage?.(d.usage);
|
|
5732
6221
|
};
|
|
5733
6222
|
const onEnd = (e) => {
|
|
@@ -5735,15 +6224,15 @@ class AparteChatHost {
|
|
|
5735
6224
|
this.binding.onTypingChange?.(false);
|
|
5736
6225
|
this._setStreamingId(null);
|
|
5737
6226
|
};
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
6227
|
+
host2.addEventListener("aparte-message-start", onStart);
|
|
6228
|
+
host2.addEventListener("aparte-message-done", onDone);
|
|
6229
|
+
host2.addEventListener("aparte-message-error", onEnd);
|
|
6230
|
+
host2.addEventListener("aparte-message-aborted", onEnd);
|
|
5742
6231
|
this._teardown.push(() => {
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
6232
|
+
host2.removeEventListener("aparte-message-start", onStart);
|
|
6233
|
+
host2.removeEventListener("aparte-message-done", onDone);
|
|
6234
|
+
host2.removeEventListener("aparte-message-error", onEnd);
|
|
6235
|
+
host2.removeEventListener("aparte-message-aborted", onEnd);
|
|
5747
6236
|
});
|
|
5748
6237
|
}
|
|
5749
6238
|
_installViewportListeners() {
|
|
@@ -5760,7 +6249,7 @@ class AparteChatHost {
|
|
|
5760
6249
|
return old?.usage !== void 0 ? { ...m, usage: old.usage } : m;
|
|
5761
6250
|
});
|
|
5762
6251
|
this.binding.setMessages(merged);
|
|
5763
|
-
this.
|
|
6252
|
+
this._report(merged);
|
|
5764
6253
|
if (evt.detail.siblings.some((s) => s.count > 1)) {
|
|
5765
6254
|
this._pendingSiblings = evt.detail.siblings;
|
|
5766
6255
|
this.binding.afterRender(() => this._applyPendingSiblings());
|
|
@@ -6381,6 +6870,9 @@ function remoteProvider(id) {
|
|
|
6381
6870
|
}
|
|
6382
6871
|
class AparteClient {
|
|
6383
6872
|
_boundHandler = null;
|
|
6873
|
+
/** Between `start()` and `stop()`: the listeners are on `window`. The send handler alone is
|
|
6874
|
+
* not the sign — the constructor prepares it without listening. */
|
|
6875
|
+
_listening = false;
|
|
6384
6876
|
_boundAbortHandler = null;
|
|
6385
6877
|
_boundLoadedHandler = null;
|
|
6386
6878
|
_boundRetryHandler = null;
|
|
@@ -6515,11 +7007,12 @@ class AparteClient {
|
|
|
6515
7007
|
if (!this._boundLoadedHandler) {
|
|
6516
7008
|
this._boundLoadedHandler = (e) => {
|
|
6517
7009
|
if (!this._isForThisInstance(e)) return;
|
|
6518
|
-
const
|
|
6519
|
-
void this.resumeDeferred(
|
|
7010
|
+
const host2 = e.target instanceof HTMLElement ? e.target : void 0;
|
|
7011
|
+
void this.resumeDeferred(host2 ?? e.detail?.targetId);
|
|
6520
7012
|
};
|
|
6521
7013
|
}
|
|
6522
7014
|
window.addEventListener("aparte-conversation-loaded", this._boundLoadedHandler);
|
|
7015
|
+
this._listening = true;
|
|
6523
7016
|
}
|
|
6524
7017
|
/**
|
|
6525
7018
|
* Stop answering: abort the reply in flight, take this client's `keyResolver` off the
|
|
@@ -6529,7 +7022,8 @@ class AparteClient {
|
|
|
6529
7022
|
this.abort();
|
|
6530
7023
|
this._releaseKeyResolver?.();
|
|
6531
7024
|
this._releaseKeyResolver = null;
|
|
6532
|
-
if (!this._boundHandler) return;
|
|
7025
|
+
if (!this._listening || !this._boundHandler) return;
|
|
7026
|
+
this._listening = false;
|
|
6533
7027
|
window.removeEventListener("aparte-send", this._boundHandler);
|
|
6534
7028
|
this._boundHandler = null;
|
|
6535
7029
|
if (this._boundAbortHandler) {
|
|
@@ -6685,6 +7179,7 @@ class AparteClient {
|
|
|
6685
7179
|
return;
|
|
6686
7180
|
}
|
|
6687
7181
|
dispatchLifecycleEvent(targetElement, "aparte-message-start", { messageId, role: "assistant" }, chatId);
|
|
7182
|
+
if (opts?.resume) dispatchLifecycleEvent(targetElement, "aparte-message-paused", { messageId }, chatId);
|
|
6688
7183
|
if (!opts?.resume && provider.id) {
|
|
6689
7184
|
this._updateMessage(targetElement, messageId, {
|
|
6690
7185
|
model: {
|
|
@@ -6697,6 +7192,7 @@ class AparteClient {
|
|
|
6697
7192
|
const streamController = new AbortController();
|
|
6698
7193
|
if (this._streamController !== streamController) this._streamController?.abort();
|
|
6699
7194
|
this._streamController = streamController;
|
|
7195
|
+
this._turnsInFlight.add(messageId);
|
|
6700
7196
|
try {
|
|
6701
7197
|
let result = opts?.resume ? { usage: void 0, suspended: opts.resume } : await this._streamLoop(targetElement, messageId, provider, baseRequest, authConfig, streamController);
|
|
6702
7198
|
const maxResumes = this.options.maxTurns ?? 10;
|
|
@@ -6728,6 +7224,7 @@ class AparteClient {
|
|
|
6728
7224
|
};
|
|
6729
7225
|
const messages2 = this._resumeMessages(targetElement, messageId, baseRequest.messages);
|
|
6730
7226
|
this._spendPause(targetElement, messageId);
|
|
7227
|
+
dispatchLifecycleEvent(targetElement, "aparte-message-resumed", { messageId }, chatId);
|
|
6731
7228
|
result = await this._streamLoop(
|
|
6732
7229
|
targetElement,
|
|
6733
7230
|
messageId,
|
|
@@ -6754,8 +7251,15 @@ class AparteClient {
|
|
|
6754
7251
|
}
|
|
6755
7252
|
const aparteError = AparteError.from(error, AparteErrorCode.UNKNOWN_ERROR);
|
|
6756
7253
|
this._handleLifecycleError(targetElement, messageId, aparteError);
|
|
7254
|
+
} finally {
|
|
7255
|
+
this._turnsInFlight.delete(messageId);
|
|
6757
7256
|
}
|
|
6758
7257
|
}
|
|
7258
|
+
/**
|
|
7259
|
+
* Replies a turn is running into right now, a pause it waits on included: a second
|
|
7260
|
+
* load of their conversation must not start them again (`resumeDeferred`).
|
|
7261
|
+
*/
|
|
7262
|
+
_turnsInFlight = /* @__PURE__ */ new Set();
|
|
6759
7263
|
/**
|
|
6760
7264
|
* Handle aparte-retry — add a sibling branch to the assistant message and re-stream
|
|
6761
7265
|
* using the same conversation history minus the retried reply.
|
|
@@ -7452,15 +7956,15 @@ ${content}
|
|
|
7452
7956
|
* nobody answered — the turn was stopped, or the channel gave up — and there is no
|
|
7453
7957
|
* resume. A channel that CRASHES fails the turn, as it does for a run in the page.
|
|
7454
7958
|
*/
|
|
7455
|
-
async _answerDeferred(targetElement,
|
|
7959
|
+
async _answerDeferred(targetElement, pending2, signal, rowEvents, approvedBefore = /* @__PURE__ */ new Set(), approvedNow = /* @__PURE__ */ new Set()) {
|
|
7456
7960
|
const outcomes = {};
|
|
7457
7961
|
const resolver = this._approvalResolverFor(targetElement, true);
|
|
7458
7962
|
const refuseTheRest = (after) => {
|
|
7459
|
-
for (const rest of
|
|
7963
|
+
for (const rest of pending2.slice(after + 1)) {
|
|
7460
7964
|
outcomes[rest.toolCallId] = { kind: "decision", approved: false, reason: NOT_RUN_AFTER_A_REFUSAL, by: "earlier-refusal" };
|
|
7461
7965
|
}
|
|
7462
7966
|
};
|
|
7463
|
-
for (const [index, p] of
|
|
7967
|
+
for (const [index, p] of pending2.entries()) {
|
|
7464
7968
|
if (signal.aborted) break;
|
|
7465
7969
|
const call = { id: p.toolCallId, name: p.name, input: p.input ?? {} };
|
|
7466
7970
|
if (p.reason === "approval") {
|
|
@@ -7620,14 +8124,14 @@ ${content}
|
|
|
7620
8124
|
* @param target The chat — an element, or its id. Defaults to the one `targetResolver` names, else the first on the page.
|
|
7621
8125
|
*/
|
|
7622
8126
|
async resumeDeferred(target) {
|
|
7623
|
-
const
|
|
7624
|
-
const targetElement =
|
|
8127
|
+
const host2 = typeof target === "string" ? this._byId(target) : target ?? this.options.targetResolver?.() ?? null;
|
|
8128
|
+
const targetElement = host2 ? this._asRenderTarget(host2) : this._scanForTarget();
|
|
7625
8129
|
if (!targetElement) return false;
|
|
7626
8130
|
const messages = targetElement.getMessages?.() ?? [];
|
|
7627
8131
|
const last = [...messages].reverse().find((m) => m.role === "assistant");
|
|
7628
8132
|
const open = (last?.segments ?? []).filter(isOpenDeferred);
|
|
7629
8133
|
const ticket = open.find((s) => s.deferred?.ticket)?.deferred?.ticket;
|
|
7630
|
-
if (!last || open.length === 0 || !ticket || this._resuming.has(last.id)) return false;
|
|
8134
|
+
if (!last || open.length === 0 || !ticket || this._resuming.has(last.id) || this._turnsInFlight.has(last.id)) return false;
|
|
7631
8135
|
const config = this._config.getModelConfig();
|
|
7632
8136
|
const recorded = last.model && this._config.getAIProvider(last.model.providerId) ? last.model : void 0;
|
|
7633
8137
|
const providerId = recorded?.providerId ?? config.defaultProvider;
|
|
@@ -7640,7 +8144,7 @@ ${content}
|
|
|
7640
8144
|
this._resuming.add(last.id);
|
|
7641
8145
|
try {
|
|
7642
8146
|
const authConfig = provider.id ? await this._resolveAuth(provider.id) : void 0;
|
|
7643
|
-
const
|
|
8147
|
+
const pending2 = open.map((s) => ({
|
|
7644
8148
|
toolCallId: s.toolCall.id,
|
|
7645
8149
|
name: s.toolCall.name,
|
|
7646
8150
|
input: s.toolCall.input,
|
|
@@ -7648,7 +8152,7 @@ ${content}
|
|
|
7648
8152
|
}));
|
|
7649
8153
|
const base = this._resumeBase(messages, messages.indexOf(last));
|
|
7650
8154
|
await this._streamTurn(targetElement, last.id, provider, base, recorded?.modelId ?? config.defaultModel ?? "", authConfig, {
|
|
7651
|
-
resume: { pending, ticket },
|
|
8155
|
+
resume: { pending: pending2, ticket },
|
|
7652
8156
|
...recorded?.temperature !== void 0 ? { temperature: recorded.temperature } : {}
|
|
7653
8157
|
});
|
|
7654
8158
|
} finally {
|
|
@@ -8060,6 +8564,7 @@ const APARTE_DEFAULT_UI_EVENTS = [
|
|
|
8060
8564
|
"aparte-message-error",
|
|
8061
8565
|
"aparte-message-aborted",
|
|
8062
8566
|
"aparte-message-paused",
|
|
8567
|
+
"aparte-message-resumed",
|
|
8063
8568
|
"aparte-tool-approval-request"
|
|
8064
8569
|
];
|
|
8065
8570
|
function applyElementProps(el2, props, transformValue = (value) => value) {
|
|
@@ -8134,8 +8639,95 @@ function installDialogTriggersOnce(doc = document) {
|
|
|
8134
8639
|
doc.addEventListener("pointerup", onPointerUp, true);
|
|
8135
8640
|
doc.addEventListener("click", onClick);
|
|
8136
8641
|
}
|
|
8642
|
+
const ATTR = "defer-hydration";
|
|
8643
|
+
const waiting = /* @__PURE__ */ new WeakMap();
|
|
8644
|
+
const holderOf = /* @__PURE__ */ new WeakMap();
|
|
8645
|
+
const pending = /* @__PURE__ */ new WeakSet();
|
|
8646
|
+
const live = /* @__PURE__ */ new WeakSet();
|
|
8647
|
+
const observers = /* @__PURE__ */ new WeakMap();
|
|
8648
|
+
const patched = /* @__PURE__ */ new WeakSet();
|
|
8649
|
+
function isHeld(el2) {
|
|
8650
|
+
if (live.has(el2)) return false;
|
|
8651
|
+
if (holderOf.has(el2)) return true;
|
|
8652
|
+
const holder = el2.closest(`[${ATTR}]`);
|
|
8653
|
+
if (!holder) return false;
|
|
8654
|
+
holderOf.set(el2, holder);
|
|
8655
|
+
let list = waiting.get(holder);
|
|
8656
|
+
if (!list) {
|
|
8657
|
+
list = [];
|
|
8658
|
+
waiting.set(holder, list);
|
|
8659
|
+
const observer = new MutationObserver(() => {
|
|
8660
|
+
if (!holder.hasAttribute(ATTR)) release(holder);
|
|
8661
|
+
});
|
|
8662
|
+
observer.observe(holder, { attributes: true, attributeFilter: [ATTR] });
|
|
8663
|
+
observers.set(holder, observer);
|
|
8664
|
+
}
|
|
8665
|
+
list.push(el2);
|
|
8666
|
+
return true;
|
|
8667
|
+
}
|
|
8668
|
+
function release(holder) {
|
|
8669
|
+
observers.get(holder)?.disconnect();
|
|
8670
|
+
observers.delete(holder);
|
|
8671
|
+
const list = waiting.get(holder) ?? [];
|
|
8672
|
+
waiting.delete(holder);
|
|
8673
|
+
for (const el2 of list) {
|
|
8674
|
+
holderOf.delete(el2);
|
|
8675
|
+
if (el2.isConnected && !live.has(el2)) el2.connectedCallback?.();
|
|
8676
|
+
}
|
|
8677
|
+
}
|
|
8678
|
+
function releaseDeferredHydration(root) {
|
|
8679
|
+
if (!root) return;
|
|
8680
|
+
if (root.hasAttribute(ATTR)) root.removeAttribute(ATTR);
|
|
8681
|
+
else if (!waiting.has(root)) return;
|
|
8682
|
+
release(root);
|
|
8683
|
+
}
|
|
8684
|
+
function deferHydrationOf(ctor) {
|
|
8685
|
+
if (patched.has(ctor)) return;
|
|
8686
|
+
patched.add(ctor);
|
|
8687
|
+
const proto = ctor.prototype;
|
|
8688
|
+
const connected = proto.connectedCallback;
|
|
8689
|
+
const changed = proto.attributeChangedCallback;
|
|
8690
|
+
const disconnected = proto.disconnectedCallback;
|
|
8691
|
+
const observed = ctor.observedAttributes ?? [];
|
|
8692
|
+
proto.connectedCallback = function() {
|
|
8693
|
+
if (isHeld(this)) {
|
|
8694
|
+
pending.add(this);
|
|
8695
|
+
return;
|
|
8696
|
+
}
|
|
8697
|
+
if (pending.has(this)) {
|
|
8698
|
+
pending.delete(this);
|
|
8699
|
+
for (const name of observed) {
|
|
8700
|
+
const value = this.getAttribute(name);
|
|
8701
|
+
if (value !== null) changed?.call(this, name, null, value);
|
|
8702
|
+
}
|
|
8703
|
+
}
|
|
8704
|
+
live.add(this);
|
|
8705
|
+
connected?.call(this);
|
|
8706
|
+
};
|
|
8707
|
+
if (changed) {
|
|
8708
|
+
proto.attributeChangedCallback = function(name, oldValue, newValue) {
|
|
8709
|
+
if (isHeld(this)) {
|
|
8710
|
+
pending.add(this);
|
|
8711
|
+
return;
|
|
8712
|
+
}
|
|
8713
|
+
changed.call(this, name, oldValue, newValue);
|
|
8714
|
+
};
|
|
8715
|
+
}
|
|
8716
|
+
proto.disconnectedCallback = function() {
|
|
8717
|
+
const holder = holderOf.get(this);
|
|
8718
|
+
if (holder) {
|
|
8719
|
+
holderOf.delete(this);
|
|
8720
|
+
const list = waiting.get(holder);
|
|
8721
|
+
if (list) list.splice(list.indexOf(this), 1);
|
|
8722
|
+
return;
|
|
8723
|
+
}
|
|
8724
|
+
if (pending.has(this)) return;
|
|
8725
|
+
live.delete(this);
|
|
8726
|
+
disconnected?.call(this);
|
|
8727
|
+
};
|
|
8728
|
+
}
|
|
8137
8729
|
export {
|
|
8138
|
-
|
|
8730
|
+
parseMarkdownToSegments as $,
|
|
8139
8731
|
APARTE_CONFIG_CHANGE as A,
|
|
8140
8732
|
contextConfig as B,
|
|
8141
8733
|
copyText as C,
|
|
@@ -8146,50 +8738,63 @@ export {
|
|
|
8146
8738
|
cssEscape as H,
|
|
8147
8739
|
declineDefaultRenderers as I,
|
|
8148
8740
|
defaultSanitizer as J,
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8741
|
+
deferHydrationOf as K,
|
|
8742
|
+
defineMountedSegmentRenderer as L,
|
|
8743
|
+
defineMountedToolRenderer as M,
|
|
8744
|
+
detachConfig as N,
|
|
8745
|
+
escapeAttr as O,
|
|
8746
|
+
escapeHtml as P,
|
|
8747
|
+
filesToAttachments as Q,
|
|
8748
|
+
getAllRenderers as R,
|
|
8749
|
+
getSegmentRenderer as S,
|
|
8750
|
+
installDefaultRenderersOnce as T,
|
|
8751
|
+
installDialogTriggersOnce as U,
|
|
8752
|
+
isAwaitingReply as V,
|
|
8753
|
+
isFormatAdapter as W,
|
|
8754
|
+
isSafeUrl as X,
|
|
8755
|
+
isSegmentSettled as Y,
|
|
8756
|
+
overrideSegmentRenderer as Z,
|
|
8757
|
+
parseAparteEventStream as _,
|
|
8166
8758
|
APARTE_CONVERSATION_SCHEMA_VERSION as a,
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8759
|
+
populateBubbleFromMessage as a0,
|
|
8760
|
+
readableToAsyncIterable as a1,
|
|
8761
|
+
registerDefaultRenderers as a2,
|
|
8762
|
+
registerSegmentRenderer as a3,
|
|
8763
|
+
releaseDeferredHydration as a4,
|
|
8764
|
+
requestUserInput as a5,
|
|
8765
|
+
resolveConfig as a6,
|
|
8766
|
+
revokeAttachmentUrls as a7,
|
|
8767
|
+
runWithConfig as a8,
|
|
8768
|
+
segmentDuration as a9,
|
|
8769
|
+
conversationStatusOf as aA,
|
|
8770
|
+
chatBoundaryOf as aB,
|
|
8771
|
+
rootOf as aC,
|
|
8772
|
+
segmentTiming as aa,
|
|
8773
|
+
subscribeConfigChange as ab,
|
|
8774
|
+
toToolCallView as ac,
|
|
8775
|
+
toolRefusal as ad,
|
|
8776
|
+
unregisterSegmentRenderer as ae,
|
|
8777
|
+
uuid as af,
|
|
8778
|
+
eventOrigin as ag,
|
|
8779
|
+
activeElementIn as ah,
|
|
8780
|
+
gateGlide as ai,
|
|
8781
|
+
toolCallRenderer as aj,
|
|
8782
|
+
toolRendererThatDrew as ak,
|
|
8783
|
+
isMountedRenderer as al,
|
|
8784
|
+
renderingVersionOf as am,
|
|
8785
|
+
onRenderersChange as an,
|
|
8786
|
+
mergeSegmentUpdate as ao,
|
|
8787
|
+
writeStreamedMarkdown as ap,
|
|
8788
|
+
stampSegmentActivity as aq,
|
|
8789
|
+
stampSegmentOnInsert as ar,
|
|
8790
|
+
stampSegmentOnUpdate as as,
|
|
8791
|
+
renumberSegments as at,
|
|
8792
|
+
openSegmentIds as au,
|
|
8793
|
+
isTerminalStatus as av,
|
|
8794
|
+
adoptMessageSegments as aw,
|
|
8795
|
+
APARTE_DERIVED_SEGMENTS as ax,
|
|
8796
|
+
rememberConversationStatus as ay,
|
|
8797
|
+
tellConversationStatus as az,
|
|
8193
8798
|
APARTE_DEFAULT_BUBBLE_ACTIONS as b,
|
|
8194
8799
|
APARTE_DEFAULT_HOST_HANDLERS as c,
|
|
8195
8800
|
APARTE_DEFAULT_ICON_FALLBACKS as d,
|
|
@@ -8216,4 +8821,4 @@ export {
|
|
|
8216
8821
|
collectRendererStyles as y,
|
|
8217
8822
|
contentToText as z
|
|
8218
8823
|
};
|
|
8219
|
-
//# sourceMappingURL=
|
|
8824
|
+
//# sourceMappingURL=defer-hydration-BwMqeRHq.js.map
|