@aparte/core 0.10.0 → 0.12.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 +54 -33
- package/dist/client/aparte-client.d.ts.map +1 -1
- package/dist/client/stream-adapter.d.ts +6 -1
- package/dist/client/stream-adapter.d.ts.map +1 -1
- package/dist/components/bubble/aparte-chat-bubble.d.ts +25 -4
- package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
- package/dist/components/chat/aparte-chat.d.ts +8 -4
- package/dist/components/chat/aparte-chat.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-action.d.ts +15 -6
- package/dist/components/composer/aparte-composer-action.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-add-attachment.d.ts +5 -4
- package/dist/components/composer/aparte-composer-add-attachment.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-attachments.d.ts +5 -3
- package/dist/components/composer/aparte-composer-attachments.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-cancel.d.ts +2 -2
- package/dist/components/composer/aparte-composer-input.d.ts +8 -5
- package/dist/components/composer/aparte-composer-input.d.ts.map +1 -1
- package/dist/components/composer/aparte-composer-send.d.ts +2 -2
- package/dist/components/composer/aparte-composer-toolbar.d.ts +3 -3
- package/dist/components/composer/aparte-composer.d.ts +81 -10
- package/dist/components/composer/aparte-composer.d.ts.map +1 -1
- package/dist/components/conversation-list/aparte-conversation-list.d.ts +19 -12
- package/dist/components/conversation-list/aparte-conversation-list.d.ts.map +1 -1
- package/dist/components/elicitation/aparte-elicitation.d.ts +14 -2
- package/dist/components/elicitation/aparte-elicitation.d.ts.map +1 -1
- package/dist/components/status/aparte-chat-status.d.ts +9 -0
- package/dist/components/status/aparte-chat-status.d.ts.map +1 -1
- package/dist/components/viewport/aparte-chat-viewport.d.ts +15 -4
- package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
- package/dist/config/aparte-config.d.ts +72 -15
- package/dist/config/aparte-config.d.ts.map +1 -1
- package/dist/config/locale.d.ts +43 -0
- package/dist/config/locale.d.ts.map +1 -1
- package/dist/custom-elements.json +2824 -1801
- package/dist/elicitation/approval-panel.d.ts +42 -0
- package/dist/elicitation/approval-panel.d.ts.map +1 -0
- package/dist/elicitation/index.d.ts +4 -1
- package/dist/elicitation/index.d.ts.map +1 -1
- package/dist/elicitation/panel.d.ts +1 -1
- package/dist/elicitation/panel.d.ts.map +1 -1
- package/dist/elicitation/types.d.ts +116 -9
- package/dist/elicitation/types.d.ts.map +1 -1
- package/dist/generated/element-attributes.d.ts +177 -0
- package/dist/generated/element-attributes.d.ts.map +1 -0
- package/dist/index.css +621 -92
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +368 -255
- 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 +46 -44
- package/dist/index.node.js.map +1 -1
- package/dist/interop/element-props.d.ts +40 -2
- package/dist/interop/element-props.d.ts.map +1 -1
- package/dist/{index-BnKBciDG.js → is-awaiting-reply-CxWRISBC.js} +949 -957
- package/dist/is-awaiting-reply-CxWRISBC.js.map +1 -0
- package/dist/primitives/select/aparte-optgroup.d.ts +3 -0
- package/dist/primitives/select/aparte-optgroup.d.ts.map +1 -1
- package/dist/primitives/select/aparte-option.d.ts +1 -0
- package/dist/primitives/select/aparte-option.d.ts.map +1 -1
- package/dist/primitives/select/aparte-select.d.ts +22 -18
- package/dist/primitives/select/aparte-select.d.ts.map +1 -1
- package/dist/renderers/segment-renderers.d.ts.map +1 -1
- package/dist/renderers/segments/artifact/card.d.ts.map +1 -1
- package/dist/renderers/segments/pipeline-waiting.d.ts.map +1 -1
- package/dist/renderers/segments/tool-call.d.ts.map +1 -1
- package/dist/types/element-attributes.d.ts +42 -0
- package/dist/types/element-attributes.d.ts.map +1 -0
- package/dist/types/event-map.d.ts +1 -2
- package/dist/types/event-map.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/segments.d.ts +1 -8
- package/dist/types/segments.d.ts.map +1 -1
- package/dist/types/tools.d.ts +26 -23
- package/dist/types/tools.d.ts.map +1 -1
- package/dist/utils/chat-boundary.d.ts +22 -0
- package/dist/utils/chat-boundary.d.ts.map +1 -0
- package/dist/utils/segments.d.ts +8 -0
- package/dist/utils/segments.d.ts.map +1 -1
- package/package.json +3 -2
- package/dist/index-BnKBciDG.js.map +0 -1
- package/dist/renderers/segments/progress.d.ts +0 -3
- package/dist/renderers/segments/progress.d.ts.map +0 -1
|
@@ -75,6 +75,16 @@ const APARTE_DEFAULT_LOCALE = {
|
|
|
75
75
|
elicitationYes: "Yes",
|
|
76
76
|
elicitationNo: "No",
|
|
77
77
|
elicitationAnswerLabel: "Your answer",
|
|
78
|
+
approvalInstructionPlaceholder: "Or tell it what to do instead…",
|
|
79
|
+
approvalAsk: "Run {tool}?",
|
|
80
|
+
approvalWaiting: "waiting for you",
|
|
81
|
+
toolRunning: "Running",
|
|
82
|
+
toolCompleted: "Done",
|
|
83
|
+
toolRejected: "Rejected",
|
|
84
|
+
toolStopped: "Stopped",
|
|
85
|
+
toolInput: "Input",
|
|
86
|
+
toolOutput: "Output",
|
|
87
|
+
approvalOptionsLabel: "Your decision",
|
|
78
88
|
actionUpload: "Attach file",
|
|
79
89
|
download: "Download",
|
|
80
90
|
previewPending: "Press Preview to run this artifact.",
|
|
@@ -1024,6 +1034,23 @@ const defaultSanitizer = (html) => {
|
|
|
1024
1034
|
sanitizeChildren(doc.body, container, doc);
|
|
1025
1035
|
return container.innerHTML;
|
|
1026
1036
|
};
|
|
1037
|
+
class AparteElicitationAbortError extends DOMException {
|
|
1038
|
+
/**
|
|
1039
|
+
* Which of the two ways it ended.
|
|
1040
|
+
*
|
|
1041
|
+
* `'no-presenter'` is a developer's setup problem and `'aborted'` is a person
|
|
1042
|
+
* stopping something; a host that reports them the same way reports a bug as a
|
|
1043
|
+
* user action. The distinction is why this carries a field at all.
|
|
1044
|
+
*/
|
|
1045
|
+
reason;
|
|
1046
|
+
constructor(reason = "aborted") {
|
|
1047
|
+
super(
|
|
1048
|
+
reason === "no-presenter" ? "No elicitation presenter is mounted, so the request could not be shown." : "The request ended without an answer.",
|
|
1049
|
+
"AbortError"
|
|
1050
|
+
);
|
|
1051
|
+
this.reason = reason;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1027
1054
|
function escapeHtml(value) {
|
|
1028
1055
|
let out = "";
|
|
1029
1056
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -1038,6 +1065,19 @@ function escapeHtml(value) {
|
|
|
1038
1065
|
return out;
|
|
1039
1066
|
}
|
|
1040
1067
|
const escapeAttr = escapeHtml;
|
|
1068
|
+
function isChatBoundary(el2) {
|
|
1069
|
+
if (!el2) return false;
|
|
1070
|
+
const tag = el2.tagName?.toLowerCase();
|
|
1071
|
+
return tag === "aparte-chat" || tag === "aparte-chat-component" || el2.hasAttribute?.("data-aparte-chat") === true;
|
|
1072
|
+
}
|
|
1073
|
+
function chatBoundaryOf(el2) {
|
|
1074
|
+
let node = el2 ?? null;
|
|
1075
|
+
while (node) {
|
|
1076
|
+
if (isChatBoundary(node)) return node;
|
|
1077
|
+
node = node.parentElement;
|
|
1078
|
+
}
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1041
1081
|
const APARTE_DEFAULT_BUBBLE_ACTIONS = {
|
|
1042
1082
|
copy: true,
|
|
1043
1083
|
retry: false,
|
|
@@ -1050,6 +1090,8 @@ const APARTE_DEFAULT_HOST_HANDLERS = {
|
|
|
1050
1090
|
artifactRedownload: false,
|
|
1051
1091
|
artifactRehydrate: false
|
|
1052
1092
|
};
|
|
1093
|
+
const NOOP = () => {
|
|
1094
|
+
};
|
|
1053
1095
|
class AparteConfig {
|
|
1054
1096
|
_markdownProvider;
|
|
1055
1097
|
_streamingMarkdownProvider;
|
|
@@ -1101,11 +1143,46 @@ class AparteConfig {
|
|
|
1101
1143
|
_modelPreferenceProvider;
|
|
1102
1144
|
// Conversation persistence (optional, agnostic)
|
|
1103
1145
|
_conversationManager;
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1146
|
+
/*
|
|
1147
|
+
* Human-in-the-loop: presents typed input requests (ask_user, tool approval,
|
|
1148
|
+
* forms). Registered by the `<aparte-elicitation>` Web Component.
|
|
1149
|
+
*
|
|
1150
|
+
* A STACK, not one slot, and that is the fix for two real failures. Since
|
|
1151
|
+
* `<aparte-elicitation>` entered `<aparte-chat>`'s default composition, two plain
|
|
1152
|
+
* `<aparte-chat>` elements on a page each register one on the same global config:
|
|
1153
|
+
*
|
|
1154
|
+
* - the second clobbered the first, so chat A's approval opened under chat B, and
|
|
1155
|
+
* answering it under B decided A's tool call;
|
|
1156
|
+
* - and when B unmounted it cleared the slot, leaving chat A mounted with a
|
|
1157
|
+
* perfectly good presenter that never re-registered — every later approval and
|
|
1158
|
+
* every `ask_user` in A rejected `no-presenter` for the life of the page,
|
|
1159
|
+
* silently, because the warning fires at most once per config.
|
|
1160
|
+
*
|
|
1161
|
+
* Each entry carries the element that registered it, so a request naming a `target`
|
|
1162
|
+
* is routed to the presenter in the SAME chat. `AparteElicitationRequest.target` was
|
|
1163
|
+
* already documented as "used to resolve WHICH instance presents"; the single slot
|
|
1164
|
+
* is what made that impossible.
|
|
1165
|
+
*/
|
|
1166
|
+
_elicitationPresenters = [];
|
|
1107
1167
|
/** Warn once per config, not once per request — a tool loop can ask repeatedly. */
|
|
1108
1168
|
_warnedNoPresenter = false;
|
|
1169
|
+
/**
|
|
1170
|
+
* The tail of the queue of requests waiting for the human, or `null` when none is.
|
|
1171
|
+
*
|
|
1172
|
+
* One request reaches the presenter at a time — not because a presenter could not
|
|
1173
|
+
* cope, but because the COMPOSER cannot: it has one panel slot, and a second
|
|
1174
|
+
* request used to clobber the first's DOM. The previous protection lived in the
|
|
1175
|
+
* default presenter, which answered the second request `cancel` on the spot; that
|
|
1176
|
+
* is a refusal invented for a question nobody was ever shown, the same class of
|
|
1177
|
+
* lie as telling the model a stopped turn was refused. WAITING is the honest
|
|
1178
|
+
* behaviour.
|
|
1179
|
+
*
|
|
1180
|
+
* Here rather than in the presenter because this is the only place that sees
|
|
1181
|
+
* EVERY request whatever raised it — a tool handler, an app's own button, and
|
|
1182
|
+
* (soon) the tool-approval gate. A custom presenter registered by a consumer gets
|
|
1183
|
+
* the same protection for free, which it previously had none of.
|
|
1184
|
+
*/
|
|
1185
|
+
_elicitationQueue = null;
|
|
1109
1186
|
// Tool Registry
|
|
1110
1187
|
_tools = /* @__PURE__ */ new Map();
|
|
1111
1188
|
_toolRenderers = /* @__PURE__ */ new Map();
|
|
@@ -1175,7 +1252,7 @@ class AparteConfig {
|
|
|
1175
1252
|
* trigger only for the ones you claim — see {@link AparteHostHandlersConfig}.
|
|
1176
1253
|
*
|
|
1177
1254
|
* @example
|
|
1178
|
-
* aparteGlobalConfig.setHostHandlers({ attachmentPreview: true,
|
|
1255
|
+
* aparteGlobalConfig.setHostHandlers({ attachmentPreview: true, artifactRedownload: true });
|
|
1179
1256
|
*/
|
|
1180
1257
|
setHostHandlers(config) {
|
|
1181
1258
|
this._hostHandlers = { ...this._hostHandlers, ...config };
|
|
@@ -1811,7 +1888,7 @@ class AparteConfig {
|
|
|
1811
1888
|
*
|
|
1812
1889
|
* @example
|
|
1813
1890
|
* // Custom pill for a web-search tool
|
|
1814
|
-
* aparteGlobalConfig.registerToolRenderer('web_search', { render: (seg) => `<div class="tool-
|
|
1891
|
+
* aparteGlobalConfig.registerToolRenderer('web_search', { render: (seg) => `<div class="aparte-tool-label">Searching...</div>` });
|
|
1815
1892
|
*/
|
|
1816
1893
|
registerToolRenderer(toolName, renderer) {
|
|
1817
1894
|
this._toolRenderers.set(toolName, renderer);
|
|
@@ -1885,39 +1962,112 @@ class AparteConfig {
|
|
|
1885
1962
|
* The `<aparte-elicitation>` Web Component registers itself here; an app can
|
|
1886
1963
|
* override with its own framework-native presenter. Pass `null` to clear.
|
|
1887
1964
|
*/
|
|
1888
|
-
setElicitationPresenter(presenter) {
|
|
1889
|
-
|
|
1965
|
+
setElicitationPresenter(presenter, owner) {
|
|
1966
|
+
if (presenter === null) {
|
|
1967
|
+
this._elicitationPresenters = [];
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
this._elicitationPresenters = this._elicitationPresenters.filter((e) => e.fn !== presenter);
|
|
1971
|
+
this._elicitationPresenters.push(owner === void 0 ? { fn: presenter } : { fn: presenter, owner });
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Withdraw ONE presenter, named, leaving any others registered.
|
|
1975
|
+
*
|
|
1976
|
+
* This is what an unmounting `<aparte-elicitation>` needs and what
|
|
1977
|
+
* `setElicitationPresenter(null)` could not express: clearing the slot took every
|
|
1978
|
+
* other mounted chat's presenter down with it. Removing a presenter that is not
|
|
1979
|
+
* registered is a no-op.
|
|
1980
|
+
*/
|
|
1981
|
+
removeElicitationPresenter(presenter) {
|
|
1982
|
+
this._elicitationPresenters = this._elicitationPresenters.filter((e) => e.fn !== presenter);
|
|
1890
1983
|
}
|
|
1891
|
-
/**
|
|
1984
|
+
/**
|
|
1985
|
+
* The presenter a request with no `target` would reach — the most recently
|
|
1986
|
+
* registered one. Kept for the callers that ask "is anybody able to present?".
|
|
1987
|
+
*/
|
|
1892
1988
|
getElicitationPresenter() {
|
|
1893
|
-
return this.
|
|
1989
|
+
return this._elicitationPresenters[this._elicitationPresenters.length - 1]?.fn;
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* The presenter that should handle THIS request.
|
|
1993
|
+
*
|
|
1994
|
+
* A `target` is an element inside the asking chat, so the presenter registered from
|
|
1995
|
+
* the same chat is the right one — found by comparing chat boundaries rather than by
|
|
1996
|
+
* `contains`, because the presenter is a sibling of the transcript, not an ancestor
|
|
1997
|
+
* of it. With no target, no match, or no boundary to compare, the top of the stack
|
|
1998
|
+
* is the answer, which is exactly the old single-slot behaviour.
|
|
1999
|
+
*/
|
|
2000
|
+
_presenterFor(target) {
|
|
2001
|
+
const stack = this._elicitationPresenters;
|
|
2002
|
+
if (stack.length === 0) return void 0;
|
|
2003
|
+
if (target) {
|
|
2004
|
+
const wanted = chatBoundaryOf(target);
|
|
2005
|
+
if (wanted) {
|
|
2006
|
+
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
|
2007
|
+
const entry = stack[i];
|
|
2008
|
+
if (entry.owner && chatBoundaryOf(entry.owner) === wanted) return entry.fn;
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
return stack[stack.length - 1]?.fn;
|
|
1894
2013
|
}
|
|
1895
2014
|
/**
|
|
1896
2015
|
* Ask the user for typed input mid-run and await their response. This is the
|
|
1897
2016
|
* generic primitive behind `ask_user` and tool approval — the KIND of
|
|
1898
|
-
* question is the schema, not a bespoke tool.
|
|
1899
|
-
*
|
|
1900
|
-
*
|
|
2017
|
+
* question is the schema, not a bespoke tool.
|
|
2018
|
+
*
|
|
2019
|
+
* **Resolves** `accept` with the value, or `decline` when the user declines.
|
|
1901
2020
|
*
|
|
1902
|
-
*
|
|
1903
|
-
*
|
|
1904
|
-
*
|
|
1905
|
-
*
|
|
1906
|
-
*
|
|
1907
|
-
*
|
|
1908
|
-
*
|
|
2021
|
+
* **Rejects** — it does not resolve — when the request ends without an answer, with
|
|
2022
|
+
* an {@link AparteElicitationAbortError} whose `name` is `'AbortError'` and whose
|
|
2023
|
+
* `reason` is `'aborted'` (the turn was stopped, the signal fired, or another request
|
|
2024
|
+
* took the question away) or `'no-presenter'` (nothing was mounted to ask it). So
|
|
2025
|
+
* `await` it inside a `try`, or attach a `.catch()`.
|
|
2026
|
+
*
|
|
2027
|
+
* That is a change, and this block described the old behaviour for a whole release:
|
|
2028
|
+
* "resolves `cancel` when the turn is cancelled", and "with NO presenter registered it
|
|
2029
|
+
* resolves `cancel` rather than hanging". Both were false the moment `cancel` was
|
|
2030
|
+
* removed — and the second is the one that bites, because a developer who reads
|
|
2031
|
+
* "rather than hanging" writes no `.catch()` and gets an unhandled rejection. It ships
|
|
2032
|
+
* in the `.d.ts` and in the custom-elements manifest, which is the API surface the
|
|
2033
|
+
* docs site renders, so it was the most-read wrong sentence in the library.
|
|
2034
|
+
*
|
|
2035
|
+
* A value was removed rather than renamed on purpose: `cancel` was easy to handle as
|
|
2036
|
+
* though it were an answer, and the approval gate did exactly that — it read a stopped
|
|
2037
|
+
* turn as the user refusing a tool they were never shown.
|
|
2038
|
+
*
|
|
2039
|
+
* With no presenter it rejects `'no-presenter'` and warns once, because a request
|
|
2040
|
+
* nobody could see is a setup mistake only the developer can fix. The default
|
|
2041
|
+
* presenter is `<aparte-elicitation>`, which registers itself from its
|
|
2042
|
+
* `connectedCallback` — so it has to be IN THE DOM. A docs page of ours claimed it
|
|
2043
|
+
* "installs itself — nothing to register", which is how that was found.
|
|
1909
2044
|
*/
|
|
1910
2045
|
requestUserInput(request) {
|
|
1911
|
-
|
|
2046
|
+
const previous = this._elicitationQueue;
|
|
2047
|
+
const settled = previous === null ? this._present(request) : previous.then(
|
|
2048
|
+
() => this._present(request),
|
|
2049
|
+
() => this._present(request)
|
|
2050
|
+
);
|
|
2051
|
+
const mine = settled.then(NOOP, NOOP).then(() => {
|
|
2052
|
+
if (this._elicitationQueue === mine) this._elicitationQueue = null;
|
|
2053
|
+
});
|
|
2054
|
+
this._elicitationQueue = mine;
|
|
2055
|
+
return settled;
|
|
2056
|
+
}
|
|
2057
|
+
/** Hand ONE request to the presenter. Called only from the queue. */
|
|
2058
|
+
_present(request) {
|
|
2059
|
+
const presenter = this._presenterFor(request.target);
|
|
2060
|
+
if (!presenter) {
|
|
1912
2061
|
if (!this._warnedNoPresenter) {
|
|
1913
2062
|
this._warnedNoPresenter = true;
|
|
1914
2063
|
console.warn(
|
|
1915
|
-
"[aparte] requestUserInput() was called with no elicitation presenter, so it
|
|
2064
|
+
"[aparte] requestUserInput() was called with no elicitation presenter, so it rejected with an AbortError — nothing was ever shown to anybody. Add <aparte-elicitation></aparte-elicitation> inside your <aparte-chat>, or register your own by calling setElicitationPresenter() on the config this chat resolves — the scoped one if you passed a `config`, aparteGlobalConfig otherwise."
|
|
1916
2065
|
);
|
|
1917
2066
|
}
|
|
1918
|
-
return Promise.
|
|
2067
|
+
return Promise.reject(new AparteElicitationAbortError("no-presenter"));
|
|
1919
2068
|
}
|
|
1920
|
-
return
|
|
2069
|
+
if (request.signal?.aborted) return Promise.reject(new AparteElicitationAbortError("aborted"));
|
|
2070
|
+
return presenter(request);
|
|
1921
2071
|
}
|
|
1922
2072
|
/**
|
|
1923
2073
|
* Reset ALL configuration back to defaults — providers, registries, model
|
|
@@ -1943,7 +2093,8 @@ class AparteConfig {
|
|
|
1943
2093
|
this._artifactPreviewBuilder = void 0;
|
|
1944
2094
|
this._keyProvider = void 0;
|
|
1945
2095
|
this._conversationManager = void 0;
|
|
1946
|
-
this.
|
|
2096
|
+
this._elicitationPresenters = [];
|
|
2097
|
+
this._elicitationQueue = null;
|
|
1947
2098
|
this._locale = APARTE_DEFAULT_LOCALE;
|
|
1948
2099
|
this._actions = [];
|
|
1949
2100
|
this._sanitizer = defaultSanitizer;
|
|
@@ -2094,9 +2245,9 @@ function highlightMarkdownFences(contentEl) {
|
|
|
2094
2245
|
}
|
|
2095
2246
|
const textRenderer = {
|
|
2096
2247
|
type: "text",
|
|
2097
|
-
render: (segment) => `<div class="segment segment-text" data-segment-id="${escapeHtml(segment.id)}"><div class="segment-content">${contextConfig().renderMarkdown(segment.content)}</div></div>`,
|
|
2248
|
+
render: (segment) => `<div class="aparte-segment aparte-segment-text" data-segment-id="${escapeHtml(segment.id)}"><div class="aparte-segment-content">${contextConfig().renderMarkdown(segment.content)}</div></div>`,
|
|
2098
2249
|
update: (el2, segment) => {
|
|
2099
|
-
const contentEl = el2.querySelector(".segment-content");
|
|
2250
|
+
const contentEl = el2.querySelector(".aparte-segment-content");
|
|
2100
2251
|
if (!contentEl) return;
|
|
2101
2252
|
writeStreamedMarkdown(
|
|
2102
2253
|
el2,
|
|
@@ -2124,17 +2275,17 @@ const thinkingRenderer = {
|
|
|
2124
2275
|
* new class of risk. With no Markdown provider registered it degrades to the
|
|
2125
2276
|
* zero-dependency default (escape + `<br>`), which is what it used to be.
|
|
2126
2277
|
*/
|
|
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>`,
|
|
2278
|
+
render: (segment) => `<details class="aparte-segment aparte-segment-thinking" data-segment-id="${escapeHtml(segment.id)}" ${segment.collapsed === false ? "open" : ""}><summary class="aparte-thinking-header"><span class="aparte-thinking-label">${escapeHtml(segment.label || contextConfig().t("thinking"))}</span><span class="aparte-thinking-toggle"></span></summary><div class="aparte-thinking-content">${contextConfig().renderMarkdown(segment.content)}</div></details>`,
|
|
2128
2279
|
/**
|
|
2129
2280
|
* The default label is the only config-derived text here — and `segment.label`
|
|
2130
2281
|
* still wins, exactly as in `render`, because that string is the app's.
|
|
2131
2282
|
*/
|
|
2132
2283
|
relabel: (el2, segment) => {
|
|
2133
|
-
const label = el2.querySelector(".thinking-label");
|
|
2284
|
+
const label = el2.querySelector(".aparte-thinking-label");
|
|
2134
2285
|
if (label) label.textContent = segment.label || contextConfig().t("thinking");
|
|
2135
2286
|
},
|
|
2136
2287
|
update: (el2, segment) => {
|
|
2137
|
-
const contentEl = el2.querySelector(".thinking-content");
|
|
2288
|
+
const contentEl = el2.querySelector(".aparte-thinking-content");
|
|
2138
2289
|
if (!contentEl) return;
|
|
2139
2290
|
const wasAtBottom = contentEl.scrollHeight - contentEl.scrollTop - contentEl.clientHeight <= THINKING_ANCHOR_SLACK;
|
|
2140
2291
|
const streaming = segment.isStreaming !== false;
|
|
@@ -2194,38 +2345,38 @@ function streamHighlight(element, paneSelector, content, lang, segId) {
|
|
|
2194
2345
|
const codeRenderer = {
|
|
2195
2346
|
type: "code",
|
|
2196
2347
|
render: (segment) => `
|
|
2197
|
-
<div class="segment segment-code" data-segment-id="${escapeHtml(segment.id)}">
|
|
2198
|
-
<div class="code-header">
|
|
2199
|
-
${segment.filename ? `<span class="code-filename">${escapeHtml(segment.filename)}</span>` : `<span class="code-header-filler"></span>`}
|
|
2200
|
-
<span class="code-language">${escapeHtml(segment.language || "")}</span>
|
|
2201
|
-
<button class="code-copy" data-action="copy" title="${escapeAttr(contextConfig().t("copy"))}">
|
|
2348
|
+
<div class="aparte-segment aparte-segment-code" data-segment-id="${escapeHtml(segment.id)}">
|
|
2349
|
+
<div class="aparte-code-header">
|
|
2350
|
+
${segment.filename ? `<span class="aparte-code-filename">${escapeHtml(segment.filename)}</span>` : `<span class="aparte-code-header-filler"></span>`}
|
|
2351
|
+
<span class="aparte-code-language">${escapeHtml(segment.language || "")}</span>
|
|
2352
|
+
<button class="aparte-code-copy" data-action="copy" title="${escapeAttr(contextConfig().t("copy"))}">
|
|
2202
2353
|
${contextConfig().getIcon("copy")}
|
|
2203
2354
|
</button>
|
|
2204
2355
|
</div>
|
|
2205
|
-
<div class="code-content-wrapper">
|
|
2356
|
+
<div class="aparte-code-content-wrapper">
|
|
2206
2357
|
<pre><code class="language-${escapeHtml(segment.language || "text")}">${escapeHtml(segment.content)}</code></pre>
|
|
2207
2358
|
</div>
|
|
2208
2359
|
</div>
|
|
2209
2360
|
`,
|
|
2210
2361
|
/** The copy button's icon and tooltip — nothing else here comes from config. */
|
|
2211
2362
|
relabel: (element) => {
|
|
2212
|
-
const copyBtn = element.querySelector(".code-copy");
|
|
2363
|
+
const copyBtn = element.querySelector(".aparte-code-copy");
|
|
2213
2364
|
if (!copyBtn || copyBtn.dataset.copied) return;
|
|
2214
2365
|
copyBtn.setAttribute("title", contextConfig().t("copy"));
|
|
2215
2366
|
copyBtn.innerHTML = contextConfig().getIcon("copy");
|
|
2216
2367
|
},
|
|
2217
2368
|
setup: (element, segment) => {
|
|
2218
|
-
const wrapper = element.querySelector(".code-content-wrapper");
|
|
2369
|
+
const wrapper = element.querySelector(".aparte-code-content-wrapper");
|
|
2219
2370
|
if (wrapper) {
|
|
2220
2371
|
void contextConfig().highlightCode(segment.content, segment.language || "").then((html) => {
|
|
2221
2372
|
wrapper.innerHTML = html;
|
|
2222
2373
|
}).catch(() => {
|
|
2223
2374
|
});
|
|
2224
2375
|
}
|
|
2225
|
-
const copyBtn = element.querySelector(".code-copy");
|
|
2376
|
+
const copyBtn = element.querySelector(".aparte-code-copy");
|
|
2226
2377
|
if (copyBtn) {
|
|
2227
2378
|
copyBtn.addEventListener("click", () => {
|
|
2228
|
-
const source = element.querySelector(".code-content-wrapper")?.textContent ?? "";
|
|
2379
|
+
const source = element.querySelector(".aparte-code-content-wrapper")?.textContent ?? "";
|
|
2229
2380
|
void navigator.clipboard.writeText(source).catch(() => {
|
|
2230
2381
|
});
|
|
2231
2382
|
copyBtn.innerHTML = contextConfig(copyBtn).getIcon("check");
|
|
@@ -2241,9 +2392,9 @@ const codeRenderer = {
|
|
|
2241
2392
|
},
|
|
2242
2393
|
update: (element, segment) => {
|
|
2243
2394
|
if (segment.isStreaming) {
|
|
2244
|
-
streamHighlight(element, ".code-content-wrapper", segment.content, segment.language || "", segment.id);
|
|
2395
|
+
streamHighlight(element, ".aparte-code-content-wrapper", segment.content, segment.language || "", segment.id);
|
|
2245
2396
|
} else {
|
|
2246
|
-
const wrapper = element.querySelector(".code-content-wrapper");
|
|
2397
|
+
const wrapper = element.querySelector(".aparte-code-content-wrapper");
|
|
2247
2398
|
if (wrapper) {
|
|
2248
2399
|
void contextConfig().highlightCode(segment.content, segment.language || "").then((html) => {
|
|
2249
2400
|
wrapper.innerHTML = html;
|
|
@@ -2267,14 +2418,14 @@ const errorRenderer = {
|
|
|
2267
2418
|
return out;
|
|
2268
2419
|
}
|
|
2269
2420
|
return `
|
|
2270
|
-
<div class="segment segment-error" data-segment-id="${escapeHtml(segment.id)}">
|
|
2271
|
-
<div class="error-icon-wrapper">
|
|
2421
|
+
<div class="aparte-segment aparte-segment-error" data-segment-id="${escapeHtml(segment.id)}">
|
|
2422
|
+
<div class="aparte-error-icon-wrapper">
|
|
2272
2423
|
${contextConfig().getIcon("error") || "⚠"}
|
|
2273
2424
|
</div>
|
|
2274
|
-
<div class="error-content">
|
|
2275
|
-
<div class="error-title">${escapeHtml(contextConfig().t("error"))}</div>
|
|
2276
|
-
<div class="error-message">${escapeHtml(segment.content)}</div>
|
|
2277
|
-
${segment.details ? `<div class="error-details">${escapeHtml(segment.details)}</div>` : ""}
|
|
2425
|
+
<div class="aparte-error-content">
|
|
2426
|
+
<div class="aparte-error-title">${escapeHtml(contextConfig().t("error"))}</div>
|
|
2427
|
+
<div class="aparte-error-message">${escapeHtml(segment.content)}</div>
|
|
2428
|
+
${segment.details ? `<div class="aparte-error-details">${escapeHtml(segment.details)}</div>` : ""}
|
|
2278
2429
|
</div>
|
|
2279
2430
|
</div>
|
|
2280
2431
|
`;
|
|
@@ -2289,171 +2440,198 @@ const errorRenderer = {
|
|
|
2289
2440
|
* language it arrived in. Relabelling it would be inventing content.
|
|
2290
2441
|
*/
|
|
2291
2442
|
relabel: (element) => {
|
|
2292
|
-
const wrap = element.querySelector(".error-icon-wrapper");
|
|
2443
|
+
const wrap = element.querySelector(".aparte-error-icon-wrapper");
|
|
2293
2444
|
if (wrap) wrap.innerHTML = contextConfig().getIcon("error") || "⚠";
|
|
2294
|
-
const title = element.querySelector(".error-title");
|
|
2445
|
+
const title = element.querySelector(".aparte-error-title");
|
|
2295
2446
|
if (title) title.textContent = contextConfig().t("error");
|
|
2296
2447
|
},
|
|
2297
2448
|
getStyles: () => ``
|
|
2298
2449
|
};
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
}
|
|
2450
|
+
function describeInput(segment) {
|
|
2451
|
+
const input = segment.toolCall?.input;
|
|
2452
|
+
if (!input || typeof input !== "object" || Object.keys(input).length === 0) return "";
|
|
2453
|
+
try {
|
|
2454
|
+
return JSON.stringify(input, null, 2);
|
|
2455
|
+
} catch {
|
|
2456
|
+
return "";
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
function stateBadge(segment) {
|
|
2460
|
+
const cfg = contextConfig();
|
|
2461
|
+
const status = segment.status ?? "pending";
|
|
2462
|
+
const word = (key) => escapeHtml(cfg.t(key));
|
|
2463
|
+
if (status === "awaiting-approval") return word("approvalWaiting");
|
|
2464
|
+
if (status === "pending") return word("toolRunning");
|
|
2465
|
+
if (status === "resolved") return `${cfg.getIcon("check")}${word("toolCompleted")}`;
|
|
2466
|
+
if (status === "rejected") return `${cfg.getIcon("close")}${word("toolRejected")}`;
|
|
2467
|
+
if (status === "aborted") return `${cfg.getIcon("close")}${word("toolStopped")}`;
|
|
2468
|
+
return "";
|
|
2469
|
+
}
|
|
2470
|
+
function setPart(element, part, text) {
|
|
2471
|
+
const existing = element.querySelector(`[data-part="${part}"]`);
|
|
2472
|
+
if (!text) {
|
|
2473
|
+
existing?.remove();
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
if (existing) {
|
|
2477
|
+
const code2 = existing.querySelector("code");
|
|
2478
|
+
if (code2 && code2.textContent !== text) code2.textContent = text;
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
const detail = element.querySelector(".aparte-tool-detail");
|
|
2482
|
+
if (!detail) return;
|
|
2483
|
+
const cfg = contextConfig();
|
|
2484
|
+
const wrap = document.createElement("div");
|
|
2485
|
+
wrap.className = "aparte-tool-part";
|
|
2486
|
+
wrap.dataset["part"] = part;
|
|
2487
|
+
const label = document.createElement("span");
|
|
2488
|
+
label.className = "aparte-tool-part-label";
|
|
2489
|
+
label.textContent = cfg.t(part === "input" ? "toolInput" : "toolOutput");
|
|
2490
|
+
const body = document.createElement("div");
|
|
2491
|
+
body.className = "aparte-tool-part-body";
|
|
2492
|
+
const pre = document.createElement("pre");
|
|
2493
|
+
const code = document.createElement("code");
|
|
2494
|
+
code.textContent = text;
|
|
2495
|
+
pre.appendChild(code);
|
|
2496
|
+
body.appendChild(pre);
|
|
2497
|
+
wrap.append(label, body);
|
|
2498
|
+
if (part === "input") detail.prepend(wrap);
|
|
2499
|
+
else detail.appendChild(wrap);
|
|
2500
|
+
}
|
|
2501
|
+
function highlightParts(element) {
|
|
2502
|
+
const cfg = contextConfig();
|
|
2503
|
+
for (const body of element.querySelectorAll(".aparte-tool-part-body")) {
|
|
2504
|
+
const text = body.querySelector("code")?.textContent ?? "";
|
|
2505
|
+
if (!text) continue;
|
|
2506
|
+
void cfg.highlightCode(text, "json").then((html) => {
|
|
2507
|
+
if ((body.querySelector("code")?.textContent ?? "") !== text) return;
|
|
2508
|
+
body.innerHTML = html;
|
|
2509
|
+
}).catch(() => {
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
function pillMarkup(segment, name, status) {
|
|
2514
|
+
const badge = stateBadge(segment);
|
|
2515
|
+
return `
|
|
2516
|
+
<span class="aparte-tool-label">
|
|
2517
|
+
<span class="aparte-tool-icon">${contextConfig().getIcon("tool")}</span>
|
|
2518
|
+
<span class="aparte-tool-name">${escapeHtml(name)}</span>
|
|
2519
|
+
</span>
|
|
2520
|
+
<span class="aparte-tool-spinner" aria-hidden="true"${status === "pending" ? "" : " hidden"}></span>
|
|
2521
|
+
<span class="aparte-tool-state">${badge}</span>`;
|
|
2522
|
+
}
|
|
2308
2523
|
const toolCallRenderer = {
|
|
2309
2524
|
type: "tool_call",
|
|
2310
2525
|
render: (segment) => {
|
|
2311
2526
|
const name = segment.toolCall?.name ?? "tool";
|
|
2312
2527
|
const status = segment.status ?? "pending";
|
|
2313
2528
|
const toolCallId = segment.toolCall?.id ?? "";
|
|
2314
|
-
if (status === "awaiting-approval") {
|
|
2315
|
-
const loc = contextConfig().getLocale();
|
|
2316
|
-
const approve = loc.approveTool ?? "Approve";
|
|
2317
|
-
const reject = loc.rejectTool ?? "Reject";
|
|
2318
|
-
return `
|
|
2319
|
-
<div class="segment segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="awaiting-approval" data-tool-call-id="${escapeAttr(toolCallId)}">
|
|
2320
|
-
<span class="tool-pill">
|
|
2321
|
-
<span class="tool-pill-icon">${contextConfig().getIcon("tool")}</span>
|
|
2322
|
-
<span class="tool-pill-name">${escapeHtml(name)}</span>
|
|
2323
|
-
</span>
|
|
2324
|
-
<span class="tool-approval" role="group" aria-label="${escapeAttr(name)}">
|
|
2325
|
-
<button type="button" class="tool-approve-btn" data-tool-decision="approve" aria-label="${escapeAttr(approve)}">${escapeHtml(approve)}</button>
|
|
2326
|
-
<button type="button" class="tool-reject-btn" data-tool-decision="reject" aria-label="${escapeAttr(reject)}">${escapeHtml(reject)}</button>
|
|
2327
|
-
</span>
|
|
2328
|
-
</div>
|
|
2329
|
-
`;
|
|
2330
|
-
}
|
|
2331
2529
|
const customRenderer = contextConfig().getToolRenderer(segment.toolCall?.name);
|
|
2332
2530
|
if (customRenderer) {
|
|
2333
2531
|
const out = customRenderer.render(segment);
|
|
2334
2532
|
if (out) return out;
|
|
2335
2533
|
}
|
|
2336
|
-
const
|
|
2337
|
-
const
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
<span class="tool-pill-name">${escapeHtml(name)}</span>
|
|
2343
|
-
${spinner}
|
|
2344
|
-
${statusIcon ? `<span class="tool-pill-status">${statusIcon}</span>` : ""}
|
|
2345
|
-
</span>
|
|
2534
|
+
const pill = pillMarkup(segment, name, status);
|
|
2535
|
+
const input = describeInput(segment);
|
|
2536
|
+
const result = segment.result;
|
|
2537
|
+
if (!input && !result) {
|
|
2538
|
+
return `
|
|
2539
|
+
<div class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}">${pill}
|
|
2346
2540
|
</div>
|
|
2347
2541
|
`;
|
|
2542
|
+
}
|
|
2543
|
+
const cfg = contextConfig();
|
|
2544
|
+
return `
|
|
2545
|
+
<details class="aparte-segment aparte-segment-tool-call" data-segment-id="${escapeHtml(segment.id)}" data-status="${escapeAttr(status)}" data-tool-call-id="${escapeAttr(toolCallId)}">
|
|
2546
|
+
<summary class="aparte-tool-summary"><span class="aparte-tool-toggle"></span>${pill}</summary>
|
|
2547
|
+
<div class="aparte-tool-detail">
|
|
2548
|
+
${input ? `
|
|
2549
|
+
<div class="aparte-tool-part" data-part="input">
|
|
2550
|
+
<span class="aparte-tool-part-label">${escapeHtml(cfg.t("toolInput"))}</span>
|
|
2551
|
+
<div class="aparte-tool-part-body"><pre><code>${escapeHtml(input)}</code></pre></div>
|
|
2552
|
+
</div>` : ""}
|
|
2553
|
+
${result ? `
|
|
2554
|
+
<div class="aparte-tool-part" data-part="output">
|
|
2555
|
+
<span class="aparte-tool-part-label">${escapeHtml(cfg.t("toolOutput"))}</span>
|
|
2556
|
+
<div class="aparte-tool-part-body"><pre><code>${escapeHtml(result)}</code></pre></div>
|
|
2557
|
+
</div>` : ""}
|
|
2558
|
+
</div>
|
|
2559
|
+
</details>
|
|
2560
|
+
`;
|
|
2348
2561
|
},
|
|
2349
2562
|
/**
|
|
2350
|
-
* The pill's glyphs and
|
|
2351
|
-
*
|
|
2352
|
-
*
|
|
2353
|
-
*
|
|
2563
|
+
* The pill's glyphs, and the label it wears while a person is deciding.
|
|
2564
|
+
*
|
|
2565
|
+
* The two highest-stakes strings in the library — the approval labels — are no
|
|
2566
|
+
* longer here: they are on the panel, which relabels itself. This still matters
|
|
2567
|
+
* though, because the request the pill describes stays open for as long as
|
|
2568
|
+
* somebody takes to decide, which is exactly long enough for a language switch.
|
|
2354
2569
|
*
|
|
2355
|
-
* No child node is added or removed,
|
|
2356
|
-
* critically, their FOCUS — this is the one control a keyboard user may be
|
|
2357
|
-
* sitting on when a config change fires.
|
|
2570
|
+
* No child node is added or removed, per the relabel contract.
|
|
2358
2571
|
*/
|
|
2359
2572
|
relabel: (element, segment) => {
|
|
2360
2573
|
const cfg = contextConfig();
|
|
2361
|
-
const icon = element.querySelector(".tool-
|
|
2574
|
+
const icon = element.querySelector(".aparte-tool-icon");
|
|
2362
2575
|
if (icon) icon.innerHTML = cfg.getIcon("tool");
|
|
2363
|
-
const statusEl = element.querySelector(".tool-
|
|
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
|
-
}
|
|
2576
|
+
const statusEl = element.querySelector(".aparte-tool-state");
|
|
2577
|
+
if (statusEl) statusEl.innerHTML = stateBadge(segment);
|
|
2381
2578
|
},
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2579
|
+
/**
|
|
2580
|
+
* Patch in place. NEVER re-render, and never touch `open`.
|
|
2581
|
+
*
|
|
2582
|
+
* Without an `update` the bubble replaces the element wholesale — and a tool call
|
|
2583
|
+
* changes status several times per turn, so a disclosure the reader opened would
|
|
2584
|
+
* slam shut under them every time. The same rule the reasoning block has, for the
|
|
2585
|
+
* same reason: `collapsed` is honoured only when an update explicitly carries it.
|
|
2586
|
+
*
|
|
2587
|
+
* The one case that cannot be patched is a change of ELEMENT: a call that had
|
|
2588
|
+
* neither arguments nor a result rendered as a plain pill, and gaining a result
|
|
2589
|
+
* makes it a `<details>`. That falls back to a rebuild, which costs nothing because
|
|
2590
|
+
* there was no disclosure to lose.
|
|
2591
|
+
*/
|
|
2592
|
+
update: (element, segment) => {
|
|
2593
|
+
const status = segment.status ?? "pending";
|
|
2594
|
+
const input = describeInput(segment);
|
|
2595
|
+
const wantsDetail = !!input || !!segment.result;
|
|
2596
|
+
const custom = contextConfig().getToolRenderer(segment.toolCall?.name);
|
|
2597
|
+
if (custom || wantsDetail !== (element.tagName === "DETAILS")) {
|
|
2598
|
+
const out = toolCallRenderer.render(segment);
|
|
2599
|
+
const tpl = document.createElement("template");
|
|
2600
|
+
tpl.innerHTML = typeof out === "string" ? out.trim() : "";
|
|
2601
|
+
const rebuilt = typeof out === "string" ? tpl.content.firstElementChild : out;
|
|
2602
|
+
if (rebuilt) {
|
|
2603
|
+
element.replaceWith(rebuilt);
|
|
2604
|
+
toolCallRenderer.setup?.(rebuilt, segment);
|
|
2605
|
+
}
|
|
2394
2606
|
return;
|
|
2395
2607
|
}
|
|
2608
|
+
element.setAttribute("data-status", status);
|
|
2609
|
+
const badge = element.querySelector(".aparte-tool-state");
|
|
2610
|
+
if (badge) badge.innerHTML = stateBadge(segment);
|
|
2611
|
+
element.querySelector(".aparte-tool-spinner")?.toggleAttribute("hidden", status !== "pending");
|
|
2612
|
+
setPart(element, "input", input);
|
|
2613
|
+
setPart(element, "output", segment.result ?? "");
|
|
2614
|
+
highlightParts(element);
|
|
2615
|
+
},
|
|
2616
|
+
setup: (element, segment) => {
|
|
2617
|
+
highlightParts(element);
|
|
2396
2618
|
const customRenderer = contextConfig().getToolRenderer(segment.toolCall?.name);
|
|
2397
2619
|
customRenderer?.setup?.(element, segment);
|
|
2398
2620
|
},
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
align-items: center;
|
|
2404
|
-
gap: 5px;
|
|
2405
|
-
padding: 3px 10px 3px 7px;
|
|
2406
|
-
border-radius: 99px;
|
|
2407
|
-
font-size: 0.78rem;
|
|
2408
|
-
font-weight: 500;
|
|
2409
|
-
border: 1px solid var(--aparte-border, rgba(0,0,0,0.12));
|
|
2410
|
-
background: var(--aparte-surface, #f8f8f8);
|
|
2411
|
-
color: var(--aparte-text-secondary, rgba(0,0,0,0.55));
|
|
2412
|
-
user-select: none;
|
|
2413
|
-
}
|
|
2414
|
-
[data-status="resolved"] .tool-pill {
|
|
2415
|
-
border-color: var(--aparte-success-border, rgba(34,197,94,0.3));
|
|
2416
|
-
background: var(--aparte-success-surface, rgba(34,197,94,0.06));
|
|
2417
|
-
color: var(--aparte-success, rgb(21,128,61));
|
|
2418
|
-
}
|
|
2419
|
-
[data-status="aborted"] .tool-pill,
|
|
2420
|
-
[data-status="rejected"] .tool-pill {
|
|
2421
|
-
border-color: var(--aparte-error-border, rgba(239,68,68,0.3));
|
|
2422
|
-
background: var(--aparte-error-surface, rgba(239,68,68,0.06));
|
|
2423
|
-
color: var(--aparte-error, rgb(185,28,28));
|
|
2424
|
-
}
|
|
2425
|
-
.tool-approval { display: inline-flex; gap: 6px; margin-left: 8px; vertical-align: middle; }
|
|
2426
|
-
.tool-approve-btn, .tool-reject-btn {
|
|
2427
|
-
font: inherit; font-size: 0.78rem; font-weight: 600; line-height: 1;
|
|
2428
|
-
padding: 4px 12px; border-radius: 99px; cursor: pointer;
|
|
2429
|
-
border: 1px solid var(--aparte-border, rgba(0,0,0,0.12));
|
|
2430
|
-
background: var(--aparte-surface, #f8f8f8);
|
|
2431
|
-
}
|
|
2432
|
-
.tool-approve-btn { color: var(--aparte-success, rgb(21,128,61)); border-color: var(--aparte-success-border, rgba(34,197,94,0.4)); }
|
|
2433
|
-
.tool-reject-btn { color: var(--aparte-error, rgb(185,28,28)); border-color: var(--aparte-error-border, rgba(239,68,68,0.4)); }
|
|
2434
|
-
.tool-approve-btn:hover { background: var(--aparte-success-surface, rgba(34,197,94,0.1)); }
|
|
2435
|
-
.tool-reject-btn:hover { background: var(--aparte-error-surface, rgba(239,68,68,0.1)); }
|
|
2436
|
-
.tool-pill-spinner {
|
|
2437
|
-
width: 10px; height: 10px;
|
|
2438
|
-
border: 1.5px solid currentColor;
|
|
2439
|
-
border-top-color: transparent;
|
|
2440
|
-
border-radius: 50%;
|
|
2441
|
-
display: inline-block;
|
|
2442
|
-
animation: tool-spin 0.7s linear infinite;
|
|
2443
|
-
}
|
|
2444
|
-
.tool-pill-status { font-size: 0.75rem; }
|
|
2445
|
-
@keyframes tool-spin { to { transform: rotate(360deg); } }
|
|
2446
|
-
`
|
|
2621
|
+
// CSS lives in styles/aparte.css — see its "Segment renderers" section for why a
|
|
2622
|
+
// built-in's rules belong there. `getStyles` stays for a CONSUMER's renderer, which
|
|
2623
|
+
// has no other way onto the page.
|
|
2624
|
+
getStyles: () => ""
|
|
2447
2625
|
};
|
|
2448
2626
|
const waitingLabel = () => contextConfig().t("generating");
|
|
2449
2627
|
const pipelineWaitingRenderer = {
|
|
2450
2628
|
type: "pipeline-waiting",
|
|
2451
2629
|
render: (segment) => {
|
|
2452
2630
|
return `
|
|
2453
|
-
<div class="segment segment-pipeline-waiting" data-segment-id="${escapeHtml(segment.id)}" aria-label="${escapeAttr(waitingLabel())}" role="status">
|
|
2454
|
-
<span class="pw-dot"></span>
|
|
2455
|
-
<span class="pw-dot"></span>
|
|
2456
|
-
<span class="pw-dot"></span>
|
|
2631
|
+
<div class="aparte-segment aparte-segment-pipeline-waiting" data-segment-id="${escapeHtml(segment.id)}" aria-label="${escapeAttr(waitingLabel())}" role="status">
|
|
2632
|
+
<span class="aparte-pw-dot"></span>
|
|
2633
|
+
<span class="aparte-pw-dot"></span>
|
|
2634
|
+
<span class="aparte-pw-dot"></span>
|
|
2457
2635
|
</div>`;
|
|
2458
2636
|
},
|
|
2459
2637
|
update: () => {
|
|
@@ -2478,30 +2656,10 @@ const pipelineWaitingRenderer = {
|
|
|
2478
2656
|
});
|
|
2479
2657
|
observer.observe(parent, { childList: true });
|
|
2480
2658
|
},
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
gap: 5px;
|
|
2486
|
-
padding: 6px 2px;
|
|
2487
|
-
min-height: 28px;
|
|
2488
|
-
}
|
|
2489
|
-
.pw-dot {
|
|
2490
|
-
display: inline-block;
|
|
2491
|
-
width: 8px;
|
|
2492
|
-
height: 8px;
|
|
2493
|
-
border-radius: 50%;
|
|
2494
|
-
background: var(--aparte-text-muted, #6b7280);
|
|
2495
|
-
opacity: 0.3;
|
|
2496
|
-
animation: pw-pulse 1.2s ease-in-out infinite;
|
|
2497
|
-
}
|
|
2498
|
-
.pw-dot:nth-child(2) { animation-delay: 0.2s; }
|
|
2499
|
-
.pw-dot:nth-child(3) { animation-delay: 0.4s; }
|
|
2500
|
-
@keyframes pw-pulse {
|
|
2501
|
-
0%, 80%, 100% { opacity: 0.3; transform: scale(0.85); }
|
|
2502
|
-
40% { opacity: 1; transform: scale(1.1); }
|
|
2503
|
-
}
|
|
2504
|
-
`
|
|
2659
|
+
// CSS lives in styles/aparte.css — see its "Segment renderers" section for why a
|
|
2660
|
+
// built-in's rules belong there. `getStyles` stays for a CONSUMER's renderer, which
|
|
2661
|
+
// has no other way onto the page.
|
|
2662
|
+
getStyles: () => ""
|
|
2505
2663
|
};
|
|
2506
2664
|
function stripCodeFences(content) {
|
|
2507
2665
|
let s = content;
|
|
@@ -2704,7 +2862,7 @@ function renderBinaryFileArtifact(segment, kind) {
|
|
|
2704
2862
|
if (cached && !isStreaming) {
|
|
2705
2863
|
const previewBody = cached.previewHtml ? contextConfig().sanitizeHtml(cached.previewHtml) : `<div class="aparte-art-file__preview-empty">Preview not available for ${escapeHtml(kind)} yet</div>`;
|
|
2706
2864
|
return `
|
|
2707
|
-
<div class="segment segment-artifact-file"
|
|
2865
|
+
<div class="aparte-segment aparte-segment-artifact-file"
|
|
2708
2866
|
data-segment-id="${escapeHtml(segment.id)}"
|
|
2709
2867
|
data-artifact-type="${escapeHtml(kind)}"
|
|
2710
2868
|
data-state="ready">
|
|
@@ -2730,7 +2888,7 @@ function renderBinaryFileArtifact(segment, kind) {
|
|
|
2730
2888
|
const cleanContent = stripCodeFences(segment.content || "");
|
|
2731
2889
|
const subText = isStreaming ? contextConfig().t("generating") : contextConfig().t("rebuildingPreview");
|
|
2732
2890
|
return `
|
|
2733
|
-
<div class="segment segment-artifact-file"
|
|
2891
|
+
<div class="aparte-segment aparte-segment-artifact-file"
|
|
2734
2892
|
data-segment-id="${escapeHtml(segment.id)}"
|
|
2735
2893
|
data-artifact-type="${escapeHtml(kind)}"
|
|
2736
2894
|
data-state="${isStreaming ? "streaming" : "compiling"}">
|
|
@@ -2925,7 +3083,7 @@ const artifactRenderer = {
|
|
|
2925
3083
|
const isBinary = BINARY_FILE_KINDS.has(kind);
|
|
2926
3084
|
const cleanContent = stripCodeFences(segment.content || "");
|
|
2927
3085
|
return `
|
|
2928
|
-
<div class="segment segment-artifact-card"
|
|
3086
|
+
<div class="aparte-segment aparte-segment-artifact-card"
|
|
2929
3087
|
data-segment-id="${escapeHtml(segment.id)}"
|
|
2930
3088
|
data-artifact-type="${escapeHtml(kind)}"
|
|
2931
3089
|
data-streaming="${isStreaming ? "true" : "false"}"
|
|
@@ -2953,7 +3111,7 @@ const artifactRenderer = {
|
|
|
2953
3111
|
</nav>
|
|
2954
3112
|
<div class="aparte-art-card__body">
|
|
2955
3113
|
<div class="aparte-art-card__pane" data-pane="code">
|
|
2956
|
-
<div class="code-content-wrapper">
|
|
3114
|
+
<div class="aparte-code-content-wrapper">
|
|
2957
3115
|
<pre><code class="language-${escapeHtml(displayLang)}">${escapeHtml(cleanContent)}</code></pre>
|
|
2958
3116
|
</div>
|
|
2959
3117
|
</div>
|
|
@@ -3004,7 +3162,7 @@ const artifactRenderer = {
|
|
|
3004
3162
|
setupBinaryFileArtifact(element, segment, kind);
|
|
3005
3163
|
return;
|
|
3006
3164
|
}
|
|
3007
|
-
const wrapper = element.querySelector(".code-content-wrapper");
|
|
3165
|
+
const wrapper = element.querySelector(".aparte-code-content-wrapper");
|
|
3008
3166
|
if (wrapper) {
|
|
3009
3167
|
const displayLang = languageForKind(kind);
|
|
3010
3168
|
const cleanContent = stripCodeFences(segment.content || "");
|
|
@@ -3079,13 +3237,13 @@ const artifactRenderer = {
|
|
|
3079
3237
|
const cleanContent = stripCodeFences(segment.content || "");
|
|
3080
3238
|
if (isStreaming) {
|
|
3081
3239
|
const segId = element.getAttribute("data-segment-id") ?? segment.id;
|
|
3082
|
-
streamHighlight(element, ".code-content-wrapper", cleanContent, languageForKind(kind), segId);
|
|
3240
|
+
streamHighlight(element, ".aparte-code-content-wrapper", cleanContent, languageForKind(kind), segId);
|
|
3083
3241
|
} else {
|
|
3084
|
-
const codeEl = element.querySelector(".code-content-wrapper code");
|
|
3242
|
+
const codeEl = element.querySelector(".aparte-code-content-wrapper code");
|
|
3085
3243
|
if (codeEl) {
|
|
3086
3244
|
codeEl.textContent = cleanContent;
|
|
3087
3245
|
} else {
|
|
3088
|
-
const wrapper = element.querySelector(".code-content-wrapper");
|
|
3246
|
+
const wrapper = element.querySelector(".aparte-code-content-wrapper");
|
|
3089
3247
|
if (wrapper) {
|
|
3090
3248
|
const displayLang = languageForKind(kind);
|
|
3091
3249
|
wrapper.innerHTML = `<pre><code class="language-${escapeHtml(displayLang)}">${escapeHtml(cleanContent)}</code></pre>`;
|
|
@@ -3095,7 +3253,7 @@ const artifactRenderer = {
|
|
|
3095
3253
|
if (wasStreaming && !isStreaming) {
|
|
3096
3254
|
element.setAttribute("data-streaming", "false");
|
|
3097
3255
|
element.querySelector(".aparte-art-card__pulse")?.remove();
|
|
3098
|
-
const wrapper = element.querySelector(".code-content-wrapper");
|
|
3256
|
+
const wrapper = element.querySelector(".aparte-code-content-wrapper");
|
|
3099
3257
|
if (wrapper) {
|
|
3100
3258
|
const displayLang = languageForKind(kind);
|
|
3101
3259
|
void contextConfig().highlightCode(cleanContent, displayLang).then((html) => {
|
|
@@ -3108,296 +3266,10 @@ const artifactRenderer = {
|
|
|
3108
3266
|
});
|
|
3109
3267
|
}
|
|
3110
3268
|
},
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
border: 1px solid var(--aparte-border, rgba(0,0,0,0.12));
|
|
3116
|
-
border-radius: 12px;
|
|
3117
|
-
background: var(--aparte-surface, #fff);
|
|
3118
|
-
overflow: hidden;
|
|
3119
|
-
font: inherit;
|
|
3120
|
-
color: var(--aparte-text, inherit);
|
|
3121
|
-
}
|
|
3122
|
-
.aparte-art-card__header {
|
|
3123
|
-
display: flex; align-items: center; justify-content: space-between;
|
|
3124
|
-
padding: 8px 10px;
|
|
3125
|
-
border-bottom: 1px solid var(--aparte-border, rgba(0,0,0,0.08));
|
|
3126
|
-
background: var(--aparte-surface-2, rgba(0,0,0,0.02));
|
|
3127
|
-
min-height: 36px;
|
|
3128
|
-
}
|
|
3129
|
-
.aparte-art-card__title-block { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
3130
|
-
.aparte-art-card__kind {
|
|
3131
|
-
font-size: 0.7rem; font-weight: 600;
|
|
3132
|
-
text-transform: uppercase; letter-spacing: 0.04em;
|
|
3133
|
-
padding: 2px 6px; border-radius: 4px;
|
|
3134
|
-
background: var(--aparte-surface, #fff);
|
|
3135
|
-
border: 1px solid var(--aparte-border, rgba(0,0,0,0.1));
|
|
3136
|
-
color: var(--aparte-text-muted, rgba(0,0,0,0.6));
|
|
3137
|
-
}
|
|
3138
|
-
.aparte-art-card__title {
|
|
3139
|
-
font-size: 0.85rem; font-weight: 500;
|
|
3140
|
-
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
3141
|
-
}
|
|
3142
|
-
.aparte-art-card__pulse {
|
|
3143
|
-
width: 8px; height: 8px; border-radius: 50%;
|
|
3144
|
-
background: var(--aparte-accent, #3b82f6);
|
|
3145
|
-
animation: aparte-art-card-pulse 1.2s ease-in-out infinite;
|
|
3146
|
-
}
|
|
3147
|
-
@keyframes aparte-art-card-pulse {
|
|
3148
|
-
0%, 100% { opacity: 0.35; transform: scale(0.85); }
|
|
3149
|
-
50% { opacity: 1; transform: scale(1.1); }
|
|
3150
|
-
}
|
|
3151
|
-
.aparte-art-card__actions { display: flex; gap: 4px; }
|
|
3152
|
-
.aparte-art-card__btn {
|
|
3153
|
-
display: inline-flex; align-items: center; justify-content: center;
|
|
3154
|
-
width: 28px; height: 28px;
|
|
3155
|
-
background: transparent;
|
|
3156
|
-
border: 1px solid transparent;
|
|
3157
|
-
border-radius: 6px;
|
|
3158
|
-
color: var(--aparte-text-muted, rgba(0,0,0,0.55));
|
|
3159
|
-
cursor: pointer;
|
|
3160
|
-
transition: background 0.12s, border-color 0.12s, color 0.12s;
|
|
3161
|
-
}
|
|
3162
|
-
.aparte-art-card__btn:hover:not(:disabled) {
|
|
3163
|
-
background: var(--aparte-surface-hover, rgba(0,0,0,0.04));
|
|
3164
|
-
border-color: var(--aparte-border, rgba(0,0,0,0.1));
|
|
3165
|
-
color: var(--aparte-text, inherit);
|
|
3166
|
-
}
|
|
3167
|
-
.aparte-art-card__btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3168
|
-
.aparte-art-card__tabs {
|
|
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;
|
|
3187
|
-
padding: 4px 8px 0;
|
|
3188
|
-
border-bottom: 1px solid var(--aparte-border, rgba(0,0,0,0.08));
|
|
3189
|
-
background: var(--aparte-surface-2, rgba(0,0,0,0.02));
|
|
3190
|
-
}
|
|
3191
|
-
.aparte-art-card__tabs button {
|
|
3192
|
-
padding: 6px 12px;
|
|
3193
|
-
font-size: 0.78rem;
|
|
3194
|
-
background: transparent;
|
|
3195
|
-
border: 1px solid transparent;
|
|
3196
|
-
border-bottom: none;
|
|
3197
|
-
border-radius: 6px 6px 0 0;
|
|
3198
|
-
color: var(--aparte-text-muted, rgba(0,0,0,0.6));
|
|
3199
|
-
cursor: pointer;
|
|
3200
|
-
transition: background 0.12s, color 0.12s;
|
|
3201
|
-
}
|
|
3202
|
-
.aparte-art-card__tabs button[aria-selected="true"] {
|
|
3203
|
-
background: var(--aparte-surface, #fff);
|
|
3204
|
-
border-color: var(--aparte-border, rgba(0,0,0,0.08));
|
|
3205
|
-
color: var(--aparte-text, inherit);
|
|
3206
|
-
font-weight: 500;
|
|
3207
|
-
}
|
|
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. */
|
|
3226
|
-
.aparte-art-card__body {
|
|
3227
|
-
position: relative;
|
|
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);
|
|
3232
|
-
overflow: hidden;
|
|
3233
|
-
}
|
|
3234
|
-
.aparte-art-card__pane { display: none; height: 100%; }
|
|
3235
|
-
.segment-artifact-card[data-tab="code"] .aparte-art-card__pane[data-pane="code"] { display: block; }
|
|
3236
|
-
.segment-artifact-card[data-tab="preview"] .aparte-art-card__pane[data-pane="preview"] { display: block; }
|
|
3237
|
-
.aparte-art-card__pane[data-pane="code"] {
|
|
3238
|
-
/* The body already caps this; repeating the number was the second owner. */
|
|
3239
|
-
max-height: var(--aparte-artifact-body-max, 600px); overflow: auto;
|
|
3240
|
-
}
|
|
3241
|
-
.aparte-art-card__pane[data-pane="code"] pre {
|
|
3242
|
-
margin: 0; padding: 12px;
|
|
3243
|
-
font-size: 0.82rem;
|
|
3244
|
-
background: var(--aparte-code-bg, #fafafa);
|
|
3245
|
-
}
|
|
3246
|
-
.aparte-art-card__frame {
|
|
3247
|
-
display: block;
|
|
3248
|
-
width: 100%;
|
|
3249
|
-
height: min(var(--aparte-artifact-frame-height, 480px), var(--aparte-artifact-frame-max, 70vh));
|
|
3250
|
-
border: 0;
|
|
3251
|
-
background: #fff;
|
|
3252
|
-
}
|
|
3253
|
-
.aparte-art-card__pending {
|
|
3254
|
-
display: flex; align-items: center; justify-content: center;
|
|
3255
|
-
height: var(--aparte-artifact-pending-height, 120px);
|
|
3256
|
-
color: var(--aparte-text-muted, rgba(0,0,0,0.5));
|
|
3257
|
-
font-size: 0.85rem;
|
|
3258
|
-
font-style: italic;
|
|
3259
|
-
}
|
|
3260
|
-
/* ── Binary file artifact (xlsx/pdf/docx) ──────────────────── */
|
|
3261
|
-
.segment-artifact-file {
|
|
3262
|
-
display: flex; flex-direction: column;
|
|
3263
|
-
margin: 8px 0;
|
|
3264
|
-
border: 1px solid var(--aparte-border, rgba(0,0,0,0.12));
|
|
3265
|
-
border-radius: 12px;
|
|
3266
|
-
background: var(--aparte-surface, #fff);
|
|
3267
|
-
overflow: hidden;
|
|
3268
|
-
color: var(--aparte-text, inherit);
|
|
3269
|
-
font: inherit;
|
|
3270
|
-
}
|
|
3271
|
-
.aparte-art-file__card {
|
|
3272
|
-
display: flex; align-items: center; gap: 12px;
|
|
3273
|
-
padding: 12px 14px;
|
|
3274
|
-
background: var(--aparte-surface-2, rgba(0,0,0,0.02));
|
|
3275
|
-
border-bottom: 1px solid var(--aparte-border, rgba(0,0,0,0.08));
|
|
3276
|
-
}
|
|
3277
|
-
.aparte-art-file__body {
|
|
3278
|
-
position: relative;
|
|
3279
|
-
}
|
|
3280
|
-
.aparte-art-file__code-pane {
|
|
3281
|
-
max-height: var(--aparte-artifact-file-code-max, 360px); overflow: auto;
|
|
3282
|
-
background: var(--aparte-code-bg, #fafafa);
|
|
3283
|
-
}
|
|
3284
|
-
.aparte-art-file__code-pane pre {
|
|
3285
|
-
margin: 0; padding: 12px;
|
|
3286
|
-
font-size: 0.82rem;
|
|
3287
|
-
font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
|
|
3288
|
-
}
|
|
3289
|
-
.aparte-art-file__preview-pane {
|
|
3290
|
-
max-height: var(--aparte-artifact-file-preview-max, 460px);
|
|
3291
|
-
overflow: auto;
|
|
3292
|
-
background: #fff;
|
|
3293
|
-
/* Preview is a document view — force light scheme regardless of
|
|
3294
|
-
the app theme, with a dark text colour so cells stay readable. */
|
|
3295
|
-
color: #1f2937;
|
|
3296
|
-
}
|
|
3297
|
-
.aparte-art-file__icon {
|
|
3298
|
-
width: 40px; height: 40px;
|
|
3299
|
-
border-radius: 8px;
|
|
3300
|
-
display: flex; align-items: center; justify-content: center;
|
|
3301
|
-
background: linear-gradient(135deg, #1d6f42, #0f5132);
|
|
3302
|
-
color: #fff;
|
|
3303
|
-
font-weight: 700;
|
|
3304
|
-
font-size: 0.78rem;
|
|
3305
|
-
letter-spacing: 0.04em;
|
|
3306
|
-
flex-shrink: 0;
|
|
3307
|
-
}
|
|
3308
|
-
.aparte-art-file__icon[data-kind="pdf"] { background: linear-gradient(135deg, #c0392b, #7d1f17); }
|
|
3309
|
-
.aparte-art-file__icon[data-kind="docx"] { background: linear-gradient(135deg, #1e5288, #0f3060); }
|
|
3310
|
-
.aparte-art-file__meta { flex: 1 1 auto; min-width: 0; }
|
|
3311
|
-
.aparte-art-file__meta-name {
|
|
3312
|
-
font-weight: 600; font-size: 0.92rem;
|
|
3313
|
-
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
3314
|
-
}
|
|
3315
|
-
.aparte-art-file__meta-sub {
|
|
3316
|
-
font-size: 0.78rem;
|
|
3317
|
-
color: var(--aparte-text-muted, rgba(0,0,0,0.55));
|
|
3318
|
-
}
|
|
3319
|
-
.aparte-art-file__actions { display: flex; gap: 6px; flex-shrink: 0; }
|
|
3320
|
-
.aparte-art-file__btn {
|
|
3321
|
-
border: 1px solid var(--aparte-border, rgba(0,0,0,0.12));
|
|
3322
|
-
background: var(--aparte-surface, #fff);
|
|
3323
|
-
color: var(--aparte-text, inherit);
|
|
3324
|
-
padding: 6px 10px;
|
|
3325
|
-
border-radius: 6px;
|
|
3326
|
-
font-size: 0.8rem;
|
|
3327
|
-
cursor: pointer;
|
|
3328
|
-
transition: background 0.12s ease;
|
|
3329
|
-
}
|
|
3330
|
-
.aparte-art-file__btn:hover { background: var(--aparte-surface-2, rgba(0,0,0,0.05)); }
|
|
3331
|
-
.aparte-art-file__btn--primary {
|
|
3332
|
-
background: var(--aparte-primary, #6366f1);
|
|
3333
|
-
color: #fff;
|
|
3334
|
-
border-color: var(--aparte-primary, #6366f1);
|
|
3335
|
-
}
|
|
3336
|
-
.aparte-art-file__btn--primary:hover { filter: brightness(1.07); }
|
|
3337
|
-
/* Hide the download button until the sandbox has produced the buffer.
|
|
3338
|
-
Avoids confusing the user with a disabled-but-styled-primary button
|
|
3339
|
-
during streaming / compiling. */
|
|
3340
|
-
.segment-artifact-file:not([data-state="ready"]) .aparte-art-file__btn[data-action="download"] {
|
|
3341
|
-
display: none;
|
|
3342
|
-
}
|
|
3343
|
-
.aparte-art-file__preview-pane table {
|
|
3344
|
-
border-collapse: collapse;
|
|
3345
|
-
width: 100%;
|
|
3346
|
-
font-size: 0.82rem;
|
|
3347
|
-
font-family: inherit;
|
|
3348
|
-
}
|
|
3349
|
-
.aparte-art-file__preview-pane th,
|
|
3350
|
-
.aparte-art-file__preview-pane td {
|
|
3351
|
-
border: 1px solid rgba(0,0,0,0.10);
|
|
3352
|
-
padding: 6px 10px;
|
|
3353
|
-
text-align: left;
|
|
3354
|
-
white-space: nowrap;
|
|
3355
|
-
color: #1f2937;
|
|
3356
|
-
background: #fff;
|
|
3357
|
-
}
|
|
3358
|
-
.aparte-art-file__preview-pane tr:nth-child(odd) td { background: #f9fafb; }
|
|
3359
|
-
.aparte-art-file__preview-pane tr:first-child td {
|
|
3360
|
-
background: #f3f4f6;
|
|
3361
|
-
font-weight: 600;
|
|
3362
|
-
position: sticky; top: 0;
|
|
3363
|
-
color: #111827;
|
|
3364
|
-
}
|
|
3365
|
-
.aparte-art-file__preview-empty {
|
|
3366
|
-
padding: 20px; text-align: center;
|
|
3367
|
-
color: var(--aparte-text-muted, rgba(0,0,0,0.5));
|
|
3368
|
-
font-size: 0.85rem; font-style: italic;
|
|
3369
|
-
}
|
|
3370
|
-
.segment-artifact-file[data-state="error"] .aparte-art-file__icon {
|
|
3371
|
-
background: linear-gradient(135deg, #c0392b, #7d1f17);
|
|
3372
|
-
}
|
|
3373
|
-
.aparte-art-file__error {
|
|
3374
|
-
padding: 16px 18px;
|
|
3375
|
-
background: rgba(239, 68, 68, 0.05);
|
|
3376
|
-
border-top: 1px solid rgba(239, 68, 68, 0.2);
|
|
3377
|
-
color: var(--aparte-text, inherit);
|
|
3378
|
-
}
|
|
3379
|
-
.aparte-art-file__error-title {
|
|
3380
|
-
font-weight: 600;
|
|
3381
|
-
font-size: 0.9rem;
|
|
3382
|
-
margin-bottom: 6px;
|
|
3383
|
-
color: #b91c1c;
|
|
3384
|
-
}
|
|
3385
|
-
.aparte-art-file__error-msg {
|
|
3386
|
-
font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
|
|
3387
|
-
font-size: 0.78rem;
|
|
3388
|
-
padding: 6px 10px;
|
|
3389
|
-
background: rgba(0,0,0,0.04);
|
|
3390
|
-
border-radius: 4px;
|
|
3391
|
-
margin-bottom: 8px;
|
|
3392
|
-
color: #7f1d1d;
|
|
3393
|
-
word-break: break-word;
|
|
3394
|
-
}
|
|
3395
|
-
.aparte-art-file__error-hint {
|
|
3396
|
-
font-size: 0.78rem;
|
|
3397
|
-
color: var(--aparte-text-muted, rgba(0,0,0,0.55));
|
|
3398
|
-
font-style: italic;
|
|
3399
|
-
}
|
|
3400
|
-
`
|
|
3269
|
+
// CSS lives in styles/aparte.css — see its "Segment renderers" section for why a
|
|
3270
|
+
// built-in's rules belong there. `getStyles` stays for a CONSUMER's renderer, which
|
|
3271
|
+
// has no other way onto the page.
|
|
3272
|
+
getStyles: () => ""
|
|
3401
3273
|
};
|
|
3402
3274
|
function languageForKind(kind) {
|
|
3403
3275
|
if (kind === "react") return "jsx";
|
|
@@ -3548,7 +3420,6 @@ const DEFAULT_RENDERERS = [
|
|
|
3548
3420
|
thinkingRenderer,
|
|
3549
3421
|
codeRenderer,
|
|
3550
3422
|
errorRenderer,
|
|
3551
|
-
progressRenderer,
|
|
3552
3423
|
toolCallRenderer,
|
|
3553
3424
|
artifactRenderer,
|
|
3554
3425
|
pipelineWaitingRenderer
|
|
@@ -3575,7 +3446,8 @@ function adoptSegment(segments, segment, messageId) {
|
|
|
3575
3446
|
...segment,
|
|
3576
3447
|
messageId,
|
|
3577
3448
|
index: segments.length,
|
|
3578
|
-
isStreaming: false
|
|
3449
|
+
isStreaming: false,
|
|
3450
|
+
...segment.type === "tool_call" && segment.status === "awaiting-approval" ? { status: "aborted" } : {}
|
|
3579
3451
|
};
|
|
3580
3452
|
}
|
|
3581
3453
|
const RESERVED = /* @__PURE__ */ new Set(["id", "type", "messageId", "index"]);
|
|
@@ -5362,38 +5234,415 @@ function readableToAsyncIterable(stream, signal) {
|
|
|
5362
5234
|
}
|
|
5363
5235
|
};
|
|
5364
5236
|
}
|
|
5365
|
-
|
|
5366
|
-
function
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5237
|
+
let _relabels = null;
|
|
5238
|
+
function onRelabel(fn) {
|
|
5239
|
+
_relabels?.push(fn);
|
|
5240
|
+
}
|
|
5241
|
+
function el$1(tag, className, text) {
|
|
5242
|
+
const node = document.createElement(tag);
|
|
5243
|
+
if (className) node.className = className;
|
|
5244
|
+
if (text != null) node.textContent = text;
|
|
5245
|
+
return node;
|
|
5246
|
+
}
|
|
5247
|
+
function fieldHeader(parent, field, skipTitle) {
|
|
5248
|
+
let titleId;
|
|
5249
|
+
if (field.title && field.title.trim() !== skipTitle?.trim()) {
|
|
5250
|
+
const title = el$1("p", "aparte-elic-title", field.title);
|
|
5251
|
+
titleId = `elic-title-${uuid()}`;
|
|
5252
|
+
title.id = titleId;
|
|
5253
|
+
parent.appendChild(title);
|
|
5254
|
+
}
|
|
5255
|
+
if (field.description) parent.appendChild(el$1("p", "aparte-elic-desc", field.description));
|
|
5256
|
+
return titleId;
|
|
5257
|
+
}
|
|
5258
|
+
function labelGroup(list, opts) {
|
|
5259
|
+
list.setAttribute("role", opts.multiple ? "group" : "radiogroup");
|
|
5260
|
+
if (opts.titleId) list.setAttribute("aria-labelledby", opts.titleId);
|
|
5261
|
+
else if (opts.fallbackLabel) list.setAttribute("aria-label", opts.fallbackLabel);
|
|
5262
|
+
}
|
|
5263
|
+
function buildEnumField(field, onChange, fallbackLabel) {
|
|
5264
|
+
const wrap = el$1("div", "aparte-elic-field aparte-elic-enum");
|
|
5265
|
+
const titleId = fieldHeader(wrap, field, fallbackLabel);
|
|
5266
|
+
const list = el$1("div", "aparte-elic-options");
|
|
5267
|
+
labelGroup(list, { multiple: field.multiple, titleId, fallbackLabel });
|
|
5268
|
+
const name = `elic-${uuid()}`;
|
|
5269
|
+
const type = field.multiple ? "checkbox" : "radio";
|
|
5270
|
+
const allowOther = field.allowOther ?? contextConfig().getElicitationOptions().allowOther;
|
|
5271
|
+
const defaults = new Set(Array.isArray(field.default) ? field.default : field.default != null ? [field.default] : []);
|
|
5272
|
+
const buildOption = (value, label, description, recommended) => {
|
|
5273
|
+
const row = el$1("label", "aparte-elic-option" + (recommended ? " aparte-elic-option--recommended" : ""));
|
|
5274
|
+
const control = el$1("input", "aparte-elic-control");
|
|
5275
|
+
control.type = type;
|
|
5276
|
+
control.name = name;
|
|
5277
|
+
control.value = value;
|
|
5278
|
+
if (defaults.has(value)) control.checked = true;
|
|
5279
|
+
const body = el$1("span", "aparte-elic-option-body");
|
|
5280
|
+
body.appendChild(el$1("span", "aparte-elic-option-title", label));
|
|
5281
|
+
if (description) body.appendChild(el$1("span", "aparte-elic-option-desc", description));
|
|
5282
|
+
row.append(control, body);
|
|
5283
|
+
return row;
|
|
5284
|
+
};
|
|
5285
|
+
for (const opt of field.options) {
|
|
5286
|
+
list.appendChild(buildOption(opt.value, opt.label ?? opt.value, opt.description, opt.recommended));
|
|
5287
|
+
}
|
|
5288
|
+
let otherText = null;
|
|
5289
|
+
if (allowOther) {
|
|
5290
|
+
const row = el$1("label", "aparte-elic-option aparte-elic-option--other");
|
|
5291
|
+
const control = el$1("input", "aparte-elic-control");
|
|
5292
|
+
control.type = type;
|
|
5293
|
+
control.name = name;
|
|
5294
|
+
control.value = "__other__";
|
|
5295
|
+
const body = el$1("span", "aparte-elic-option-body");
|
|
5296
|
+
const t = contextConfig();
|
|
5297
|
+
const otherTitle = el$1("span", "aparte-elic-option-title", t.t("elicitationOther"));
|
|
5298
|
+
body.appendChild(otherTitle);
|
|
5299
|
+
otherText = el$1("input", "aparte-elic-other-input");
|
|
5300
|
+
otherText.type = "text";
|
|
5301
|
+
otherText.placeholder = t.t("elicitationOtherPlaceholder");
|
|
5302
|
+
otherText.style.display = "none";
|
|
5303
|
+
otherText.setAttribute("aria-label", t.t("elicitationOtherLabel"));
|
|
5304
|
+
const otherInput = otherText;
|
|
5305
|
+
onRelabel(() => {
|
|
5306
|
+
const now = contextConfig();
|
|
5307
|
+
otherTitle.textContent = now.t("elicitationOther");
|
|
5308
|
+
otherInput.placeholder = now.t("elicitationOtherPlaceholder");
|
|
5309
|
+
otherInput.setAttribute("aria-label", now.t("elicitationOtherLabel"));
|
|
5310
|
+
});
|
|
5311
|
+
body.appendChild(otherText);
|
|
5312
|
+
row.append(control, body);
|
|
5313
|
+
list.appendChild(row);
|
|
5314
|
+
}
|
|
5315
|
+
wrap.appendChild(list);
|
|
5316
|
+
const controls = () => Array.from(list.querySelectorAll(".aparte-elic-control"));
|
|
5317
|
+
const other = () => list.querySelector('input[value="__other__"]');
|
|
5318
|
+
list.addEventListener("change", () => {
|
|
5319
|
+
const o = other();
|
|
5320
|
+
if (o && otherText) {
|
|
5321
|
+
otherText.style.display = o.checked ? "" : "none";
|
|
5322
|
+
if (o.checked) otherText.focus();
|
|
5323
|
+
}
|
|
5324
|
+
onChange();
|
|
5373
5325
|
});
|
|
5326
|
+
otherText?.addEventListener("input", onChange);
|
|
5327
|
+
const getValue = () => {
|
|
5328
|
+
const otherVal = other()?.checked && otherText?.value.trim() ? otherText.value.trim() : "";
|
|
5329
|
+
if (field.multiple) {
|
|
5330
|
+
const vals = controls().filter((c) => c.checked && c.value !== "__other__").map((c) => c.value);
|
|
5331
|
+
if (otherVal) vals.push(otherVal);
|
|
5332
|
+
return vals;
|
|
5333
|
+
}
|
|
5334
|
+
const checked = controls().find((c) => c.checked);
|
|
5335
|
+
if (!checked) return "";
|
|
5336
|
+
return checked.value === "__other__" ? otherVal : checked.value;
|
|
5337
|
+
};
|
|
5374
5338
|
return {
|
|
5375
|
-
|
|
5376
|
-
|
|
5339
|
+
el: wrap,
|
|
5340
|
+
getValue,
|
|
5341
|
+
isComplete: () => {
|
|
5342
|
+
const v = getValue();
|
|
5343
|
+
return field.multiple ? v.length > 0 : v !== "";
|
|
5344
|
+
},
|
|
5345
|
+
focus: () => controls()[0]?.focus()
|
|
5377
5346
|
};
|
|
5378
5347
|
}
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5348
|
+
function buildBooleanField(field, onChange, fallbackLabel) {
|
|
5349
|
+
const wrap = el$1("div", "aparte-elic-field aparte-elic-boolean");
|
|
5350
|
+
const titleId = fieldHeader(wrap, field, fallbackLabel);
|
|
5351
|
+
const list = el$1("div", "aparte-elic-options");
|
|
5352
|
+
labelGroup(list, { titleId, fallbackLabel });
|
|
5353
|
+
const name = `elic-${uuid()}`;
|
|
5354
|
+
const mk = (val, label, defaulted) => {
|
|
5355
|
+
const row = el$1("label", "aparte-elic-option");
|
|
5356
|
+
const control = el$1("input", "aparte-elic-control");
|
|
5357
|
+
control.type = "radio";
|
|
5358
|
+
control.name = name;
|
|
5359
|
+
control.value = val;
|
|
5360
|
+
if (field.default != null && String(field.default) === val) control.checked = true;
|
|
5361
|
+
const body = el$1("span", "aparte-elic-option-body");
|
|
5362
|
+
const title = el$1("span", "aparte-elic-option-title", label);
|
|
5363
|
+
if (defaulted) onRelabel(() => {
|
|
5364
|
+
title.textContent = contextConfig().t(defaulted);
|
|
5365
|
+
});
|
|
5366
|
+
body.appendChild(title);
|
|
5367
|
+
row.append(control, body);
|
|
5368
|
+
list.appendChild(row);
|
|
5369
|
+
};
|
|
5370
|
+
const t = contextConfig();
|
|
5371
|
+
mk("true", field.trueLabel ?? t.t("elicitationYes"), field.trueLabel ? void 0 : "elicitationYes");
|
|
5372
|
+
mk("false", field.falseLabel ?? t.t("elicitationNo"), field.falseLabel ? void 0 : "elicitationNo");
|
|
5373
|
+
wrap.appendChild(list);
|
|
5374
|
+
list.addEventListener("change", onChange);
|
|
5375
|
+
const checked = () => list.querySelector("input:checked");
|
|
5376
|
+
return {
|
|
5377
|
+
el: wrap,
|
|
5378
|
+
getValue: () => checked()?.value === "true",
|
|
5379
|
+
isComplete: () => checked() != null,
|
|
5380
|
+
focus: () => list.querySelector("input")?.focus()
|
|
5381
|
+
};
|
|
5382
|
+
}
|
|
5383
|
+
function buildStringField(field, onChange, fallbackLabel) {
|
|
5384
|
+
const wrap = el$1("div", "aparte-elic-field aparte-elic-string");
|
|
5385
|
+
fieldHeader(wrap, field, fallbackLabel);
|
|
5386
|
+
const input = field.multiline ? el$1("textarea", "aparte-elic-text") : el$1("input", "aparte-elic-text");
|
|
5387
|
+
if (!field.multiline) input.type = "text";
|
|
5388
|
+
if (field.placeholder) input.setAttribute("placeholder", field.placeholder);
|
|
5389
|
+
if (field.default) input.value = field.default;
|
|
5390
|
+
if (field.maxLength != null) input.setAttribute("maxlength", String(field.maxLength));
|
|
5391
|
+
const ownName = field.title || field.description || fallbackLabel || void 0;
|
|
5392
|
+
input.setAttribute("aria-label", ownName ?? contextConfig().t("elicitationAnswerLabel"));
|
|
5393
|
+
if (ownName == null) {
|
|
5394
|
+
const named = input;
|
|
5395
|
+
onRelabel(() => {
|
|
5396
|
+
named.setAttribute("aria-label", contextConfig().t("elicitationAnswerLabel"));
|
|
5397
|
+
});
|
|
5398
|
+
}
|
|
5399
|
+
wrap.appendChild(input);
|
|
5400
|
+
input.addEventListener("input", onChange);
|
|
5401
|
+
const required = field.required ?? true;
|
|
5402
|
+
const value = () => input.value;
|
|
5403
|
+
return {
|
|
5404
|
+
el: wrap,
|
|
5405
|
+
getValue: () => value(),
|
|
5406
|
+
isComplete: () => {
|
|
5407
|
+
const v = value().trim();
|
|
5408
|
+
if (!required && v === "") return true;
|
|
5409
|
+
return v.length >= (field.minLength ?? 1);
|
|
5410
|
+
},
|
|
5411
|
+
focus: () => input.focus()
|
|
5412
|
+
};
|
|
5413
|
+
}
|
|
5414
|
+
function buildField(field, onChange, fallbackLabel, key) {
|
|
5415
|
+
const custom = contextConfig().getElicitationFieldRenderer()?.(field, { key, notifyChange: onChange });
|
|
5416
|
+
if (custom) {
|
|
5417
|
+
return {
|
|
5418
|
+
el: custom.el,
|
|
5419
|
+
getValue: () => custom.getValue(),
|
|
5420
|
+
isComplete: () => custom.isComplete(),
|
|
5421
|
+
focus: () => custom.focus?.()
|
|
5422
|
+
};
|
|
5423
|
+
}
|
|
5424
|
+
switch (field.type) {
|
|
5425
|
+
case "enum":
|
|
5426
|
+
return buildEnumField(field, onChange, fallbackLabel);
|
|
5427
|
+
case "boolean":
|
|
5428
|
+
return buildBooleanField(field, onChange, fallbackLabel);
|
|
5429
|
+
case "string":
|
|
5430
|
+
return buildStringField(field, onChange, fallbackLabel);
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
function buildElicitationPanel(message, schema, onChange) {
|
|
5434
|
+
const relabels = [];
|
|
5435
|
+
const outer = _relabels;
|
|
5436
|
+
_relabels = relabels;
|
|
5437
|
+
try {
|
|
5438
|
+
const built = buildPanel(message, schema, onChange);
|
|
5439
|
+
return { ...built, relabel: () => {
|
|
5440
|
+
for (const fn of relabels) fn();
|
|
5441
|
+
} };
|
|
5442
|
+
} finally {
|
|
5443
|
+
_relabels = outer;
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
function buildPanel(message, schema, onChange) {
|
|
5447
|
+
const panel = el$1("div", "aparte-elic-panel");
|
|
5448
|
+
const asked = typeof message === "function" ? message() : message;
|
|
5449
|
+
if (asked) panel.appendChild(el$1("p", "aparte-elic-message", asked));
|
|
5450
|
+
const body = el$1("div", "aparte-elic-body");
|
|
5451
|
+
panel.appendChild(body);
|
|
5452
|
+
const dismiss = el$1("div", "aparte-elic-dismiss");
|
|
5453
|
+
panel.appendChild(dismiss);
|
|
5454
|
+
if (schema.type === "object") {
|
|
5455
|
+
let show = function(index) {
|
|
5456
|
+
current = index;
|
|
5457
|
+
fields.forEach((f, i) => {
|
|
5458
|
+
f.field.el.hidden = i !== index;
|
|
5459
|
+
});
|
|
5460
|
+
syncNav();
|
|
5461
|
+
};
|
|
5462
|
+
const entries = Object.entries(schema.properties);
|
|
5463
|
+
const requiredKeys = new Set(schema.required ?? entries.map(([k]) => k));
|
|
5464
|
+
const cfg = contextConfig();
|
|
5465
|
+
let syncNav = () => {
|
|
5466
|
+
};
|
|
5467
|
+
const notify = () => {
|
|
5468
|
+
syncNav();
|
|
5469
|
+
onChange();
|
|
5470
|
+
};
|
|
5471
|
+
const fields = entries.map(([key, field2]) => {
|
|
5472
|
+
const built = buildField(field2, notify, void 0, key);
|
|
5473
|
+
if (!field2.title && !built.el.querySelector(".aparte-elic-title")) {
|
|
5474
|
+
built.el.insertBefore(el$1("p", "aparte-elic-title", key), built.el.firstChild);
|
|
5475
|
+
}
|
|
5476
|
+
body.appendChild(built.el);
|
|
5477
|
+
return { key, field: built, required: requiredKeys.has(key), header: field2.header };
|
|
5478
|
+
});
|
|
5479
|
+
const isComplete = () => fields.every((f) => !f.required || f.field.isComplete());
|
|
5480
|
+
const api = {
|
|
5481
|
+
el: panel,
|
|
5482
|
+
dismiss,
|
|
5483
|
+
getContent: () => Object.fromEntries(fields.map((f) => [f.key, f.field.getValue()])),
|
|
5484
|
+
isComplete,
|
|
5485
|
+
focus: () => fields[0]?.field.focus(),
|
|
5486
|
+
// A form of one behaves like a single field; only a real form steps.
|
|
5487
|
+
mode: () => "submit",
|
|
5488
|
+
canProceed: isComplete,
|
|
5489
|
+
proceed: () => {
|
|
5490
|
+
}
|
|
5491
|
+
};
|
|
5492
|
+
if (fields.length < 2 || cfg.getElicitationOptions().layout !== "stepped") return api;
|
|
5493
|
+
panel.classList.add("aparte-elic-panel--stepped");
|
|
5494
|
+
let current = 0;
|
|
5495
|
+
const steps = el$1("div", "aparte-elic-steps");
|
|
5496
|
+
steps.setAttribute("role", "tablist");
|
|
5497
|
+
const chips = fields.map((f, i) => {
|
|
5498
|
+
const chip = el$1("button", "aparte-elic-step");
|
|
5499
|
+
chip.type = "button";
|
|
5500
|
+
chip.setAttribute("role", "tab");
|
|
5501
|
+
chip.textContent = f.header?.trim() || String(i + 1);
|
|
5502
|
+
chip.addEventListener("click", () => {
|
|
5503
|
+
show(i);
|
|
5504
|
+
f.field.focus();
|
|
5505
|
+
});
|
|
5506
|
+
steps.appendChild(chip);
|
|
5507
|
+
return chip;
|
|
5508
|
+
});
|
|
5509
|
+
panel.insertBefore(steps, body);
|
|
5510
|
+
syncNav = () => {
|
|
5511
|
+
chips.forEach((chip, i) => {
|
|
5512
|
+
chip.setAttribute("aria-selected", String(i === current));
|
|
5513
|
+
chip.toggleAttribute("data-answered", fields[i].field.isComplete());
|
|
5514
|
+
});
|
|
5515
|
+
};
|
|
5516
|
+
show(0);
|
|
5517
|
+
const last = () => current === fields.length - 1;
|
|
5518
|
+
return {
|
|
5519
|
+
...api,
|
|
5520
|
+
focus: () => fields[current]?.field.focus(),
|
|
5521
|
+
mode: () => last() ? "submit" : "advance",
|
|
5522
|
+
// Advancing needs only THIS question answered; submitting needs them all,
|
|
5523
|
+
// which is what stops the last question from accepting a form with a hole
|
|
5524
|
+
// in it.
|
|
5525
|
+
canProceed: () => last() ? isComplete() : !!fields[current]?.field.isComplete(),
|
|
5526
|
+
proceed: () => {
|
|
5527
|
+
if (last()) return;
|
|
5528
|
+
show(current + 1);
|
|
5529
|
+
fields[current]?.field.focus();
|
|
5530
|
+
}
|
|
5531
|
+
};
|
|
5532
|
+
}
|
|
5533
|
+
const field = buildField(schema, onChange, asked);
|
|
5534
|
+
body.appendChild(field.el);
|
|
5535
|
+
return {
|
|
5536
|
+
el: panel,
|
|
5537
|
+
dismiss,
|
|
5538
|
+
getContent: () => field.getValue(),
|
|
5539
|
+
isComplete: () => field.isComplete(),
|
|
5540
|
+
focus: () => field.focus(),
|
|
5541
|
+
mode: () => "submit",
|
|
5542
|
+
canProceed: () => field.isComplete(),
|
|
5543
|
+
proceed: () => {
|
|
5544
|
+
}
|
|
5545
|
+
};
|
|
5546
|
+
}
|
|
5547
|
+
function el(tag, className, text) {
|
|
5548
|
+
const node = document.createElement(tag);
|
|
5549
|
+
if (className) node.className = className;
|
|
5550
|
+
if (text != null) node.textContent = text;
|
|
5551
|
+
return node;
|
|
5552
|
+
}
|
|
5553
|
+
function buildApprovalPanel(message, options, onChange) {
|
|
5554
|
+
const cfg = contextConfig();
|
|
5555
|
+
const panel = el("div", "aparte-elic-panel aparte-approval-panel");
|
|
5556
|
+
const askText = () => typeof message === "function" ? message() : message;
|
|
5557
|
+
const ask = askText() ? el("p", "aparte-elic-message", askText()) : null;
|
|
5558
|
+
if (ask) panel.appendChild(ask);
|
|
5559
|
+
const dismiss = el("div", "aparte-elic-dismiss");
|
|
5560
|
+
panel.appendChild(dismiss);
|
|
5561
|
+
let settle = null;
|
|
5562
|
+
let done = false;
|
|
5563
|
+
const fire = (answer) => {
|
|
5564
|
+
if (done) return;
|
|
5565
|
+
done = true;
|
|
5566
|
+
settle?.(answer);
|
|
5567
|
+
};
|
|
5568
|
+
const row = el("div", "aparte-approval-options");
|
|
5569
|
+
row.setAttribute("role", "group");
|
|
5570
|
+
row.setAttribute("aria-label", cfg.t("approvalOptionsLabel"));
|
|
5571
|
+
const labelled = [];
|
|
5572
|
+
const textOf = (option) => typeof option.label === "function" ? option.label() : option.label;
|
|
5573
|
+
for (const option of options) {
|
|
5574
|
+
const button = el("button", `aparte-approval-option aparte-approval-option--${option.tone ?? "affirm"}`);
|
|
5575
|
+
button.type = "button";
|
|
5576
|
+
button.textContent = textOf(option);
|
|
5577
|
+
button.addEventListener("click", () => fire({ option: option.value }));
|
|
5578
|
+
row.appendChild(button);
|
|
5579
|
+
labelled.push({ button, option });
|
|
5580
|
+
}
|
|
5581
|
+
panel.appendChild(row);
|
|
5582
|
+
const instruction = el("textarea", "aparte-approval-instruction");
|
|
5583
|
+
instruction.rows = 1;
|
|
5584
|
+
instruction.placeholder = cfg.t("approvalInstructionPlaceholder");
|
|
5585
|
+
instruction.setAttribute("aria-label", cfg.t("approvalInstructionPlaceholder"));
|
|
5586
|
+
instruction.addEventListener("input", onChange);
|
|
5587
|
+
panel.appendChild(instruction);
|
|
5588
|
+
const typed = () => instruction.value.trim();
|
|
5589
|
+
return {
|
|
5590
|
+
el: panel,
|
|
5591
|
+
dismiss,
|
|
5592
|
+
onSettle: (cb) => {
|
|
5593
|
+
settle = cb;
|
|
5594
|
+
},
|
|
5595
|
+
getContent: () => ({ instruction: typed() }),
|
|
5596
|
+
isComplete: () => typed().length > 0,
|
|
5597
|
+
focus: () => {
|
|
5598
|
+
panel.querySelector(".aparte-approval-option")?.focus();
|
|
5599
|
+
},
|
|
5600
|
+
relabel: () => {
|
|
5601
|
+
const now = contextConfig();
|
|
5602
|
+
const label = now.t("approvalInstructionPlaceholder");
|
|
5603
|
+
instruction.placeholder = label;
|
|
5604
|
+
instruction.setAttribute("aria-label", label);
|
|
5605
|
+
row.setAttribute("aria-label", now.t("approvalOptionsLabel"));
|
|
5606
|
+
if (ask) ask.textContent = askText();
|
|
5607
|
+
for (const { button, option } of labelled) button.textContent = textOf(option);
|
|
5608
|
+
}
|
|
5609
|
+
};
|
|
5610
|
+
}
|
|
5611
|
+
function requestUserInput(request) {
|
|
5612
|
+
return resolveConfig(request.target ?? null).requestUserInput(request);
|
|
5613
|
+
}
|
|
5614
|
+
const DEFAULT_TOOL_HANDLER_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
5615
|
+
function withToolTimeout(work, controller, timeoutMs) {
|
|
5616
|
+
let timer;
|
|
5617
|
+
const timedOut = new Promise((_resolve, reject) => {
|
|
5618
|
+
timer = setTimeout(() => {
|
|
5619
|
+
controller.abort();
|
|
5620
|
+
reject(Object.assign(new Error(`tool handler exceeded ${timeoutMs}ms`), { name: "AbortError" }));
|
|
5621
|
+
}, timeoutMs);
|
|
5622
|
+
});
|
|
5623
|
+
return {
|
|
5624
|
+
raced: Promise.race([work(controller.signal), timedOut]),
|
|
5625
|
+
done: () => clearTimeout(timer)
|
|
5626
|
+
};
|
|
5627
|
+
}
|
|
5628
|
+
class AparteClient {
|
|
5629
|
+
_boundHandler = null;
|
|
5630
|
+
_boundAbortHandler = null;
|
|
5631
|
+
_boundCompactHandler = null;
|
|
5632
|
+
_boundRetryHandler = null;
|
|
5633
|
+
_boundEditHandler = null;
|
|
5634
|
+
_activeToolControllers = /* @__PURE__ */ new Set();
|
|
5635
|
+
_isAborted = false;
|
|
5636
|
+
/**
|
|
5637
|
+
* Does this window event belong to this client?
|
|
5638
|
+
*
|
|
5639
|
+
* One rule for all five handlers, because it used to be four near-copies and
|
|
5640
|
+
* one omission: `aparte-compact` had no guard at all, so in the two-client
|
|
5641
|
+
* layout the JSDoc documents, a single compact event made BOTH clients run —
|
|
5642
|
+
* two paid summarisation calls against whichever chat the DOM scan found
|
|
5643
|
+
* first, and a global reset that wiped the other conversation.
|
|
5644
|
+
*
|
|
5645
|
+
* A scoped client also answers only events ADDRESSED to it. The old guard let
|
|
5397
5646
|
* an untargeted event through to every scoped client, which turned one
|
|
5398
5647
|
* broadcast into an action on every chat on the page.
|
|
5399
5648
|
*/
|
|
@@ -5523,61 +5772,66 @@ class AparteClient {
|
|
|
5523
5772
|
}
|
|
5524
5773
|
this._activeToolControllers.clear();
|
|
5525
5774
|
}
|
|
5526
|
-
|
|
5527
|
-
*
|
|
5528
|
-
*
|
|
5529
|
-
*
|
|
5530
|
-
*
|
|
5531
|
-
*
|
|
5532
|
-
*
|
|
5533
|
-
*
|
|
5534
|
-
*
|
|
5535
|
-
*
|
|
5775
|
+
/*
|
|
5776
|
+
* Two docblocks stood here, both describing `_awaitToolDecision` — the method
|
|
5777
|
+
* this replaced. Deleting a method and leaving its documentation is worse than
|
|
5778
|
+
* leaving the method: only the LAST comment before a declaration is its JSDoc,
|
|
5779
|
+
* so those two were floating prose, and one of them explained a DOM-containment
|
|
5780
|
+
* check as a live security property. It was the fix for a cross-chat hazard that
|
|
5781
|
+
* only existed because the decision travelled as a bubbling `document` event.
|
|
5782
|
+
* Routing through `requestUserInput` removed the hazard rather than guarding it,
|
|
5783
|
+
* so the guard is gone too — and a reader who found that comment would have
|
|
5784
|
+
* believed a containment check was still protecting them.
|
|
5536
5785
|
*/
|
|
5537
5786
|
/**
|
|
5538
|
-
*
|
|
5787
|
+
* Ask the human at the COMPOSER, and report what they decided.
|
|
5539
5788
|
*
|
|
5540
|
-
*
|
|
5541
|
-
*
|
|
5542
|
-
*
|
|
5543
|
-
*
|
|
5544
|
-
*
|
|
5545
|
-
*
|
|
5546
|
-
* executed action came apart, which is the entire failure mode an approval gate
|
|
5547
|
-
* exists to prevent — and the handler behind it is arbitrary consumer code.
|
|
5789
|
+
* The default channel, replacing a `document` listener for `aparte-tool-decision`
|
|
5790
|
+
* that existed only because the buttons lived in a segment renderer with no
|
|
5791
|
+
* reference to this client. Routing through `requestUserInput` puts the decision
|
|
5792
|
+
* where every other request for the user already goes, which is the whole point:
|
|
5793
|
+
* one queue, one panel slot, one teardown, and the same behaviour whether the
|
|
5794
|
+
* request came from a tool handler or from this gate.
|
|
5548
5795
|
*
|
|
5549
|
-
* The
|
|
5550
|
-
*
|
|
5551
|
-
*
|
|
5552
|
-
* button keeps working as long as it fires from inside its own chat.
|
|
5796
|
+
* The tool NAME is the question; the arguments stay in the transcript, on the pill
|
|
5797
|
+
* this request is anchored to. A panel capped at 50vh cannot hold a diff or a plan,
|
|
5798
|
+
* and the transcript is already scrollable, copyable and persisted.
|
|
5553
5799
|
*
|
|
5554
|
-
*
|
|
5555
|
-
*
|
|
5800
|
+
* `aparte-tool-decision` is GONE with the buttons that dispatched it, and so is the
|
|
5801
|
+
* `document` listener that answered it. It existed only because a segment renderer
|
|
5802
|
+
* has no reference to this client; two seams that can disagree about who answers a
|
|
5803
|
+
* decision are one seam too many, and the ratified elicitation rule already said so
|
|
5804
|
+
* — "a typed presenter registered per config instance, never window events". A host
|
|
5805
|
+
* that wants to answer programmatically registers an `approvalResolver` (headless,
|
|
5806
|
+
* no DOM) or its own presenter, both strictly more capable than an id on an event.
|
|
5556
5807
|
*/
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
document.addEventListener("aparte-tool-decision", onDecision);
|
|
5579
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5808
|
+
async _askForApproval(event, signal, target) {
|
|
5809
|
+
const answer = await requestUserInput({
|
|
5810
|
+
kind: "approval",
|
|
5811
|
+
/*
|
|
5812
|
+
* A function, like the option labels below, and for the same reason: the
|
|
5813
|
+
* FRAME is locale text and follows a language switch while the question is
|
|
5814
|
+
* open. The tool's NAME is substituted into it and never translated — it is
|
|
5815
|
+
* wire format, the identifier the model called.
|
|
5816
|
+
*/
|
|
5817
|
+
message: () => {
|
|
5818
|
+
const loc = this._config.getLocale();
|
|
5819
|
+
return (loc.approvalAsk ?? "Run {tool}?").replace("{tool}", event.name);
|
|
5820
|
+
},
|
|
5821
|
+
// Functions, not strings, same as the question: these follow a language
|
|
5822
|
+
// switch while the request is open, which a resolved string cannot.
|
|
5823
|
+
options: [
|
|
5824
|
+
{ value: "allow", label: () => this._config.getLocale().approveTool ?? "Approve", tone: "affirm" },
|
|
5825
|
+
{ value: "deny", label: () => this._config.getLocale().rejectTool ?? "Reject", tone: "deny" }
|
|
5826
|
+
],
|
|
5827
|
+
signal,
|
|
5828
|
+
target
|
|
5580
5829
|
});
|
|
5830
|
+
if (answer.action !== "accept") return { approved: false };
|
|
5831
|
+
const content = answer.content;
|
|
5832
|
+
const instruction = content?.instruction?.trim();
|
|
5833
|
+
if (instruction) return { approved: false, instruction };
|
|
5834
|
+
return { approved: content?.option === "allow" };
|
|
5581
5835
|
}
|
|
5582
5836
|
/**
|
|
5583
5837
|
* Resolve the auth for a provider: `options.keyResolver` takes precedence,
|
|
@@ -6236,16 +6490,27 @@ ${content}
|
|
|
6236
6490
|
this._activeToolControllers.delete(controller);
|
|
6237
6491
|
}
|
|
6238
6492
|
}
|
|
6493
|
+
/**
|
|
6494
|
+
* What the loop does after one tool call. THREE outcomes, because a boolean
|
|
6495
|
+
* conflated two questions that a refusal answers differently.
|
|
6496
|
+
*
|
|
6497
|
+
* - `'continue'` — run the next tool call of this turn.
|
|
6498
|
+
* - `'respond'` — skip this turn's remaining calls, then take another turn so the
|
|
6499
|
+
* model reads what happened. A human refusal: the model asked for
|
|
6500
|
+
* several calls and refusing one cannot license the others, but a
|
|
6501
|
+
* refusal it never reads is one the user has to retype as a message.
|
|
6502
|
+
* - `'halt'` — the run is over: an abort, a turn limit, a missing handler.
|
|
6503
|
+
*/
|
|
6239
6504
|
/**
|
|
6240
6505
|
* Handle one `tool_use` stream event from {@link _streamLoop}: the built-in
|
|
6241
6506
|
* `create_artifact`, per-tool renderer selection, the human-in-the-loop
|
|
6242
6507
|
* approval gate, and running the registered handler (timeout / abort).
|
|
6243
6508
|
* Mutates the shared `messages` / `toolCallsThisTurn` history in place and
|
|
6244
|
-
* returns
|
|
6509
|
+
* returns what the loop should do next.
|
|
6245
6510
|
*/
|
|
6246
6511
|
async _handleToolUseEvent(event, ctx) {
|
|
6247
6512
|
const { targetElement, messageId, messages, toolCallsThisTurn, precedingText, artifactProgress, turns, globalMaxTurns } = ctx;
|
|
6248
|
-
let
|
|
6513
|
+
let flow = "continue";
|
|
6249
6514
|
toolCallsThisTurn.push({ id: event.id, name: event.name, input: event.input });
|
|
6250
6515
|
if (event.name === "create_artifact") {
|
|
6251
6516
|
const input = event.input;
|
|
@@ -6271,7 +6536,7 @@ ${content}
|
|
|
6271
6536
|
content: "Artifact created successfully.",
|
|
6272
6537
|
toolCallId: event.id
|
|
6273
6538
|
});
|
|
6274
|
-
return {
|
|
6539
|
+
return { flow: "continue" };
|
|
6275
6540
|
}
|
|
6276
6541
|
const toolSeg = {
|
|
6277
6542
|
id: `tool-${event.id}`,
|
|
@@ -6305,7 +6570,7 @@ ${content}
|
|
|
6305
6570
|
if (turns >= effectiveMaxTurns) {
|
|
6306
6571
|
console.warn(`[AparteClient] Tool "${event.name}" maxTurns (${effectiveMaxTurns}) reached.`);
|
|
6307
6572
|
targetElement.updateSegment?.(toolSeg.id, { status: "aborted" });
|
|
6308
|
-
return {
|
|
6573
|
+
return { flow: "halt" };
|
|
6309
6574
|
}
|
|
6310
6575
|
const handler = this._config.getToolHandler(event.name);
|
|
6311
6576
|
if (handler) {
|
|
@@ -6320,14 +6585,24 @@ ${content}
|
|
|
6320
6585
|
input: event.input
|
|
6321
6586
|
});
|
|
6322
6587
|
let decision;
|
|
6323
|
-
const resolveApproval = this.options.approvalResolver ?? ((
|
|
6588
|
+
const resolveApproval = this.options.approvalResolver ?? ((call, sig) => this._askForApproval(call, sig, targetElement));
|
|
6324
6589
|
try {
|
|
6325
|
-
decision = await resolveApproval(event
|
|
6590
|
+
decision = await resolveApproval(event, approvalController.signal);
|
|
6591
|
+
} catch (err) {
|
|
6592
|
+
if (err?.name === "AbortError") {
|
|
6593
|
+
targetElement.updateSegment?.(toolSeg.id, { status: "aborted" });
|
|
6594
|
+
return { flow: "halt" };
|
|
6595
|
+
}
|
|
6596
|
+
throw err;
|
|
6326
6597
|
} finally {
|
|
6327
6598
|
this._activeToolControllers.delete(approvalController);
|
|
6328
6599
|
}
|
|
6600
|
+
if (approvalController.signal.aborted) {
|
|
6601
|
+
targetElement.updateSegment?.(toolSeg.id, { status: "aborted" });
|
|
6602
|
+
return { flow: "halt" };
|
|
6603
|
+
}
|
|
6329
6604
|
if (!decision.approved) {
|
|
6330
|
-
const rejection = "Tool execution was rejected by the user.";
|
|
6605
|
+
const rejection = decision.instruction ? `The user rejected this tool call and said: ${decision.instruction}` : "Tool execution was rejected by the user.";
|
|
6331
6606
|
targetElement.updateSegment?.(toolSeg.id, { status: "rejected", result: rejection });
|
|
6332
6607
|
const existingToolCallMsg = messages.find(
|
|
6333
6608
|
(m) => m.role === "tool_call" && m.toolCalls?.some((tc) => tc.id === event.id)
|
|
@@ -6341,7 +6616,7 @@ ${content}
|
|
|
6341
6616
|
});
|
|
6342
6617
|
}
|
|
6343
6618
|
messages.push({ role: "tool_result", content: rejection, toolCallId: event.id });
|
|
6344
|
-
return {
|
|
6619
|
+
return { flow: "respond" };
|
|
6345
6620
|
}
|
|
6346
6621
|
if (decision.payload && typeof decision.payload === "object" && !Array.isArray(decision.payload)) {
|
|
6347
6622
|
effectiveInput = { ...event.input, ...decision.payload };
|
|
@@ -6381,7 +6656,7 @@ ${content}
|
|
|
6381
6656
|
} catch (err) {
|
|
6382
6657
|
if (err?.name === "AbortError") {
|
|
6383
6658
|
targetElement.updateSegment?.(toolSeg.id, { status: "aborted" });
|
|
6384
|
-
|
|
6659
|
+
flow = "halt";
|
|
6385
6660
|
} else {
|
|
6386
6661
|
throw err;
|
|
6387
6662
|
}
|
|
@@ -6392,9 +6667,9 @@ ${content}
|
|
|
6392
6667
|
} else {
|
|
6393
6668
|
console.warn(`[AparteClient] No handler registered for tool "${event.name}"`);
|
|
6394
6669
|
targetElement.updateSegment?.(toolSeg.id, { status: "aborted" });
|
|
6395
|
-
|
|
6670
|
+
flow = "halt";
|
|
6396
6671
|
}
|
|
6397
|
-
return {
|
|
6672
|
+
return { flow };
|
|
6398
6673
|
}
|
|
6399
6674
|
/**
|
|
6400
6675
|
* Stream loop: runs one provider.chat() call and repeats if a tool was called.
|
|
@@ -6419,6 +6694,7 @@ ${content}
|
|
|
6419
6694
|
targetElement.addSegment?.(seg);
|
|
6420
6695
|
}
|
|
6421
6696
|
while (continueLoop) {
|
|
6697
|
+
let endTurnEarly = false;
|
|
6422
6698
|
if (this._isAborted) {
|
|
6423
6699
|
dispatchLifecycleEvent(targetElement, "aparte-message-aborted", { messageId });
|
|
6424
6700
|
break;
|
|
@@ -6624,7 +6900,8 @@ ${content}
|
|
|
6624
6900
|
{ id: event.id, name: event.name, input: event.input },
|
|
6625
6901
|
{ targetElement, messageId, messages, toolCallsThisTurn, precedingText, artifactProgress, turns, globalMaxTurns }
|
|
6626
6902
|
);
|
|
6627
|
-
if (
|
|
6903
|
+
if (toolResult.flow !== "continue") endTurnEarly = true;
|
|
6904
|
+
if (toolResult.flow === "halt") continueLoop = false;
|
|
6628
6905
|
break;
|
|
6629
6906
|
}
|
|
6630
6907
|
case "error":
|
|
@@ -6639,7 +6916,7 @@ ${content}
|
|
|
6639
6916
|
break;
|
|
6640
6917
|
}
|
|
6641
6918
|
}
|
|
6642
|
-
if (
|
|
6919
|
+
if (endTurnEarly) break;
|
|
6643
6920
|
}
|
|
6644
6921
|
finalizeXmlArtifact(xmlCtx, { targetElement, messageId, artifactProgress, artifactXmlHint, textParser, streamingSegmentIds });
|
|
6645
6922
|
const finals = textParser.finalize();
|
|
@@ -6754,7 +7031,7 @@ ${content}
|
|
|
6754
7031
|
const tool = this._config.getTools().find((t) => t.name === name);
|
|
6755
7032
|
return tool ? { maxTurns: tool.maxTurns, needsApproval: tool.needsApproval } : void 0;
|
|
6756
7033
|
};
|
|
6757
|
-
const approvalResolver = this.options.approvalResolver ?? ((
|
|
7034
|
+
const approvalResolver = this.options.approvalResolver ?? ((call, sig) => this._askForApproval(call, sig, targetElement));
|
|
6758
7035
|
const usage = await streamRunner({
|
|
6759
7036
|
messageId,
|
|
6760
7037
|
baseRequest,
|
|
@@ -7060,22 +7337,47 @@ class AparteMessageRepository {
|
|
|
7060
7337
|
}
|
|
7061
7338
|
}
|
|
7062
7339
|
const APARTE_DEFAULT_UI_EVENTS = [
|
|
7340
|
+
// <aparte-composer> and its parts
|
|
7063
7341
|
"aparte-send",
|
|
7342
|
+
"aparte-cancel",
|
|
7343
|
+
"aparte-composer-change",
|
|
7344
|
+
"aparte-composer-submit",
|
|
7345
|
+
"aparte-action-click",
|
|
7346
|
+
"aparte-attachment-preview",
|
|
7347
|
+
// <aparte-chat-bubble>
|
|
7064
7348
|
"aparte-action",
|
|
7065
7349
|
"aparte-retry",
|
|
7066
7350
|
"aparte-edit",
|
|
7351
|
+
"aparte-feedback",
|
|
7352
|
+
"aparte-message-info",
|
|
7067
7353
|
"aparte-branch-navigate",
|
|
7068
|
-
|
|
7069
|
-
"aparte-
|
|
7354
|
+
// <aparte-chat-viewport>
|
|
7355
|
+
"aparte-segment-update",
|
|
7356
|
+
"aparte-reset-done",
|
|
7357
|
+
"aparte-path-changed",
|
|
7358
|
+
// <aparte-conversation-list>
|
|
7359
|
+
"aparte-select-conversation",
|
|
7360
|
+
"aparte-delete-conversation",
|
|
7361
|
+
"aparte-archive-conversation",
|
|
7362
|
+
"aparte-unarchive-conversation",
|
|
7363
|
+
// the select primitives
|
|
7364
|
+
"aparte-select-change",
|
|
7365
|
+
"aparte-select-open",
|
|
7366
|
+
"aparte-select-close",
|
|
7367
|
+
"aparte-optgroup-toggle"
|
|
7070
7368
|
];
|
|
7071
7369
|
function applyElementProps(el2, props, transformValue = (value) => value) {
|
|
7072
7370
|
for (const [key, value] of Object.entries(props)) {
|
|
7073
7371
|
if (key.startsWith("--")) {
|
|
7074
|
-
el2.style.
|
|
7372
|
+
if (value === null || value === void 0) el2.style.removeProperty(key);
|
|
7373
|
+
else if (typeof value === "object" || typeof value === "function") el2.style.removeProperty(key);
|
|
7374
|
+
else el2.style.setProperty(key, String(value));
|
|
7075
7375
|
} else if (key.startsWith("on")) {
|
|
7076
7376
|
el2.removeAttribute(key);
|
|
7077
7377
|
} else if (value === null || value === void 0 || value === false) {
|
|
7078
7378
|
el2.removeAttribute(key);
|
|
7379
|
+
} else if (typeof value === "number" && Number.isNaN(value)) {
|
|
7380
|
+
el2.removeAttribute(key);
|
|
7079
7381
|
} else if (value === true) {
|
|
7080
7382
|
el2.setAttribute(key, "");
|
|
7081
7383
|
} else if (typeof value === "object" || typeof value === "function") {
|
|
@@ -7089,361 +7391,51 @@ function isAwaitingReply(message) {
|
|
|
7089
7391
|
if (message.status === "streaming" || message.status === "pending") return true;
|
|
7090
7392
|
return message.status === void 0 && message.role === "assistant" && !message.content?.trim() && !message.segments?.length;
|
|
7091
7393
|
}
|
|
7092
|
-
let _relabels = null;
|
|
7093
|
-
function onRelabel(fn) {
|
|
7094
|
-
_relabels?.push(fn);
|
|
7095
|
-
}
|
|
7096
|
-
function el(tag, className, text) {
|
|
7097
|
-
const node = document.createElement(tag);
|
|
7098
|
-
if (className) node.className = className;
|
|
7099
|
-
if (text != null) node.textContent = text;
|
|
7100
|
-
return node;
|
|
7101
|
-
}
|
|
7102
|
-
function fieldHeader(parent, field, skipTitle) {
|
|
7103
|
-
let titleId;
|
|
7104
|
-
if (field.title && field.title.trim() !== skipTitle?.trim()) {
|
|
7105
|
-
const title = el("p", "aparte-elic-title", field.title);
|
|
7106
|
-
titleId = `elic-title-${uuid()}`;
|
|
7107
|
-
title.id = titleId;
|
|
7108
|
-
parent.appendChild(title);
|
|
7109
|
-
}
|
|
7110
|
-
if (field.description) parent.appendChild(el("p", "aparte-elic-desc", field.description));
|
|
7111
|
-
return titleId;
|
|
7112
|
-
}
|
|
7113
|
-
function labelGroup(list, opts) {
|
|
7114
|
-
list.setAttribute("role", opts.multiple ? "group" : "radiogroup");
|
|
7115
|
-
if (opts.titleId) list.setAttribute("aria-labelledby", opts.titleId);
|
|
7116
|
-
else if (opts.fallbackLabel) list.setAttribute("aria-label", opts.fallbackLabel);
|
|
7117
|
-
}
|
|
7118
|
-
function buildEnumField(field, onChange, fallbackLabel) {
|
|
7119
|
-
const wrap = el("div", "aparte-elic-field aparte-elic-enum");
|
|
7120
|
-
const titleId = fieldHeader(wrap, field, fallbackLabel);
|
|
7121
|
-
const list = el("div", "aparte-elic-options");
|
|
7122
|
-
labelGroup(list, { multiple: field.multiple, titleId, fallbackLabel });
|
|
7123
|
-
const name = `elic-${uuid()}`;
|
|
7124
|
-
const type = field.multiple ? "checkbox" : "radio";
|
|
7125
|
-
const allowOther = field.allowOther ?? contextConfig().getElicitationOptions().allowOther;
|
|
7126
|
-
const defaults = new Set(Array.isArray(field.default) ? field.default : field.default != null ? [field.default] : []);
|
|
7127
|
-
const buildOption = (value, label, description, recommended) => {
|
|
7128
|
-
const row = el("label", "aparte-elic-option" + (recommended ? " aparte-elic-option--recommended" : ""));
|
|
7129
|
-
const control = el("input", "aparte-elic-control");
|
|
7130
|
-
control.type = type;
|
|
7131
|
-
control.name = name;
|
|
7132
|
-
control.value = value;
|
|
7133
|
-
if (defaults.has(value)) control.checked = true;
|
|
7134
|
-
const body = el("span", "aparte-elic-option-body");
|
|
7135
|
-
body.appendChild(el("span", "aparte-elic-option-title", label));
|
|
7136
|
-
if (description) body.appendChild(el("span", "aparte-elic-option-desc", description));
|
|
7137
|
-
row.append(control, body);
|
|
7138
|
-
return row;
|
|
7139
|
-
};
|
|
7140
|
-
for (const opt of field.options) {
|
|
7141
|
-
list.appendChild(buildOption(opt.value, opt.label ?? opt.value, opt.description, opt.recommended));
|
|
7142
|
-
}
|
|
7143
|
-
let otherText = null;
|
|
7144
|
-
if (allowOther) {
|
|
7145
|
-
const row = el("label", "aparte-elic-option aparte-elic-option--other");
|
|
7146
|
-
const control = el("input", "aparte-elic-control");
|
|
7147
|
-
control.type = type;
|
|
7148
|
-
control.name = name;
|
|
7149
|
-
control.value = "__other__";
|
|
7150
|
-
const body = el("span", "aparte-elic-option-body");
|
|
7151
|
-
const t = contextConfig();
|
|
7152
|
-
const otherTitle = el("span", "aparte-elic-option-title", t.t("elicitationOther"));
|
|
7153
|
-
body.appendChild(otherTitle);
|
|
7154
|
-
otherText = el("input", "aparte-elic-other-input");
|
|
7155
|
-
otherText.type = "text";
|
|
7156
|
-
otherText.placeholder = t.t("elicitationOtherPlaceholder");
|
|
7157
|
-
otherText.style.display = "none";
|
|
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
|
-
});
|
|
7166
|
-
body.appendChild(otherText);
|
|
7167
|
-
row.append(control, body);
|
|
7168
|
-
list.appendChild(row);
|
|
7169
|
-
}
|
|
7170
|
-
wrap.appendChild(list);
|
|
7171
|
-
const controls = () => Array.from(list.querySelectorAll(".aparte-elic-control"));
|
|
7172
|
-
const other = () => list.querySelector('input[value="__other__"]');
|
|
7173
|
-
list.addEventListener("change", () => {
|
|
7174
|
-
const o = other();
|
|
7175
|
-
if (o && otherText) {
|
|
7176
|
-
otherText.style.display = o.checked ? "" : "none";
|
|
7177
|
-
if (o.checked) otherText.focus();
|
|
7178
|
-
}
|
|
7179
|
-
onChange();
|
|
7180
|
-
});
|
|
7181
|
-
otherText?.addEventListener("input", onChange);
|
|
7182
|
-
const getValue = () => {
|
|
7183
|
-
const otherVal = other()?.checked && otherText?.value.trim() ? otherText.value.trim() : "";
|
|
7184
|
-
if (field.multiple) {
|
|
7185
|
-
const vals = controls().filter((c) => c.checked && c.value !== "__other__").map((c) => c.value);
|
|
7186
|
-
if (otherVal) vals.push(otherVal);
|
|
7187
|
-
return vals;
|
|
7188
|
-
}
|
|
7189
|
-
const checked = controls().find((c) => c.checked);
|
|
7190
|
-
if (!checked) return "";
|
|
7191
|
-
return checked.value === "__other__" ? otherVal : checked.value;
|
|
7192
|
-
};
|
|
7193
|
-
return {
|
|
7194
|
-
el: wrap,
|
|
7195
|
-
getValue,
|
|
7196
|
-
isComplete: () => {
|
|
7197
|
-
const v = getValue();
|
|
7198
|
-
return field.multiple ? v.length > 0 : v !== "";
|
|
7199
|
-
},
|
|
7200
|
-
focus: () => controls()[0]?.focus()
|
|
7201
|
-
};
|
|
7202
|
-
}
|
|
7203
|
-
function buildBooleanField(field, onChange, fallbackLabel) {
|
|
7204
|
-
const wrap = el("div", "aparte-elic-field aparte-elic-boolean");
|
|
7205
|
-
const titleId = fieldHeader(wrap, field, fallbackLabel);
|
|
7206
|
-
const list = el("div", "aparte-elic-options");
|
|
7207
|
-
labelGroup(list, { titleId, fallbackLabel });
|
|
7208
|
-
const name = `elic-${uuid()}`;
|
|
7209
|
-
const mk = (val, label, defaulted) => {
|
|
7210
|
-
const row = el("label", "aparte-elic-option");
|
|
7211
|
-
const control = el("input", "aparte-elic-control");
|
|
7212
|
-
control.type = "radio";
|
|
7213
|
-
control.name = name;
|
|
7214
|
-
control.value = val;
|
|
7215
|
-
if (field.default != null && String(field.default) === val) control.checked = true;
|
|
7216
|
-
const body = el("span", "aparte-elic-option-body");
|
|
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);
|
|
7222
|
-
row.append(control, body);
|
|
7223
|
-
list.appendChild(row);
|
|
7224
|
-
};
|
|
7225
|
-
const t = contextConfig();
|
|
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");
|
|
7228
|
-
wrap.appendChild(list);
|
|
7229
|
-
list.addEventListener("change", onChange);
|
|
7230
|
-
const checked = () => list.querySelector("input:checked");
|
|
7231
|
-
return {
|
|
7232
|
-
el: wrap,
|
|
7233
|
-
getValue: () => checked()?.value === "true",
|
|
7234
|
-
isComplete: () => checked() != null,
|
|
7235
|
-
focus: () => list.querySelector("input")?.focus()
|
|
7236
|
-
};
|
|
7237
|
-
}
|
|
7238
|
-
function buildStringField(field, onChange, fallbackLabel) {
|
|
7239
|
-
const wrap = el("div", "aparte-elic-field aparte-elic-string");
|
|
7240
|
-
fieldHeader(wrap, field, fallbackLabel);
|
|
7241
|
-
const input = field.multiline ? el("textarea", "aparte-elic-text") : el("input", "aparte-elic-text");
|
|
7242
|
-
if (!field.multiline) input.type = "text";
|
|
7243
|
-
if (field.placeholder) input.setAttribute("placeholder", field.placeholder);
|
|
7244
|
-
if (field.default) input.value = field.default;
|
|
7245
|
-
if (field.maxLength != null) input.setAttribute("maxlength", String(field.maxLength));
|
|
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
|
-
}
|
|
7254
|
-
wrap.appendChild(input);
|
|
7255
|
-
input.addEventListener("input", onChange);
|
|
7256
|
-
const required = field.required ?? true;
|
|
7257
|
-
const value = () => input.value;
|
|
7258
|
-
return {
|
|
7259
|
-
el: wrap,
|
|
7260
|
-
getValue: () => value(),
|
|
7261
|
-
isComplete: () => {
|
|
7262
|
-
const v = value().trim();
|
|
7263
|
-
if (!required && v === "") return true;
|
|
7264
|
-
return v.length >= (field.minLength ?? 1);
|
|
7265
|
-
},
|
|
7266
|
-
focus: () => input.focus()
|
|
7267
|
-
};
|
|
7268
|
-
}
|
|
7269
|
-
function buildField(field, onChange, fallbackLabel, key) {
|
|
7270
|
-
const custom = contextConfig().getElicitationFieldRenderer()?.(field, { key, notifyChange: onChange });
|
|
7271
|
-
if (custom) {
|
|
7272
|
-
return {
|
|
7273
|
-
el: custom.el,
|
|
7274
|
-
getValue: () => custom.getValue(),
|
|
7275
|
-
isComplete: () => custom.isComplete(),
|
|
7276
|
-
focus: () => custom.focus?.()
|
|
7277
|
-
};
|
|
7278
|
-
}
|
|
7279
|
-
switch (field.type) {
|
|
7280
|
-
case "enum":
|
|
7281
|
-
return buildEnumField(field, onChange, fallbackLabel);
|
|
7282
|
-
case "boolean":
|
|
7283
|
-
return buildBooleanField(field, onChange, fallbackLabel);
|
|
7284
|
-
case "string":
|
|
7285
|
-
return buildStringField(field, onChange, fallbackLabel);
|
|
7286
|
-
}
|
|
7287
|
-
}
|
|
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) {
|
|
7302
|
-
const panel = el("div", "aparte-elic-panel");
|
|
7303
|
-
if (message) panel.appendChild(el("p", "aparte-elic-message", message));
|
|
7304
|
-
const body = el("div", "aparte-elic-body");
|
|
7305
|
-
panel.appendChild(body);
|
|
7306
|
-
const dismiss = el("div", "aparte-elic-dismiss");
|
|
7307
|
-
panel.appendChild(dismiss);
|
|
7308
|
-
if (schema.type === "object") {
|
|
7309
|
-
let show = function(index) {
|
|
7310
|
-
current = index;
|
|
7311
|
-
fields.forEach((f, i) => {
|
|
7312
|
-
f.field.el.hidden = i !== index;
|
|
7313
|
-
});
|
|
7314
|
-
syncNav();
|
|
7315
|
-
};
|
|
7316
|
-
const entries = Object.entries(schema.properties);
|
|
7317
|
-
const requiredKeys = new Set(schema.required ?? entries.map(([k]) => k));
|
|
7318
|
-
const cfg = contextConfig();
|
|
7319
|
-
let syncNav = () => {
|
|
7320
|
-
};
|
|
7321
|
-
const notify = () => {
|
|
7322
|
-
syncNav();
|
|
7323
|
-
onChange();
|
|
7324
|
-
};
|
|
7325
|
-
const fields = entries.map(([key, field2]) => {
|
|
7326
|
-
const built = buildField(field2, notify, void 0, key);
|
|
7327
|
-
if (!field2.title && !built.el.querySelector(".aparte-elic-title")) {
|
|
7328
|
-
built.el.insertBefore(el("p", "aparte-elic-title", key), built.el.firstChild);
|
|
7329
|
-
}
|
|
7330
|
-
body.appendChild(built.el);
|
|
7331
|
-
return { key, field: built, required: requiredKeys.has(key), header: field2.header };
|
|
7332
|
-
});
|
|
7333
|
-
const isComplete = () => fields.every((f) => !f.required || f.field.isComplete());
|
|
7334
|
-
const api = {
|
|
7335
|
-
el: panel,
|
|
7336
|
-
dismiss,
|
|
7337
|
-
getContent: () => Object.fromEntries(fields.map((f) => [f.key, f.field.getValue()])),
|
|
7338
|
-
isComplete,
|
|
7339
|
-
focus: () => fields[0]?.field.focus(),
|
|
7340
|
-
// A form of one behaves like a single field; only a real form steps.
|
|
7341
|
-
mode: () => "submit",
|
|
7342
|
-
canProceed: isComplete,
|
|
7343
|
-
proceed: () => {
|
|
7344
|
-
}
|
|
7345
|
-
};
|
|
7346
|
-
if (fields.length < 2 || cfg.getElicitationOptions().layout !== "stepped") return api;
|
|
7347
|
-
panel.classList.add("aparte-elic-panel--stepped");
|
|
7348
|
-
let current = 0;
|
|
7349
|
-
const steps = el("div", "aparte-elic-steps");
|
|
7350
|
-
steps.setAttribute("role", "tablist");
|
|
7351
|
-
const chips = fields.map((f, i) => {
|
|
7352
|
-
const chip = el("button", "aparte-elic-step");
|
|
7353
|
-
chip.type = "button";
|
|
7354
|
-
chip.setAttribute("role", "tab");
|
|
7355
|
-
chip.textContent = f.header?.trim() || String(i + 1);
|
|
7356
|
-
chip.addEventListener("click", () => {
|
|
7357
|
-
show(i);
|
|
7358
|
-
f.field.focus();
|
|
7359
|
-
});
|
|
7360
|
-
steps.appendChild(chip);
|
|
7361
|
-
return chip;
|
|
7362
|
-
});
|
|
7363
|
-
panel.insertBefore(steps, body);
|
|
7364
|
-
syncNav = () => {
|
|
7365
|
-
chips.forEach((chip, i) => {
|
|
7366
|
-
chip.setAttribute("aria-selected", String(i === current));
|
|
7367
|
-
chip.toggleAttribute("data-answered", fields[i].field.isComplete());
|
|
7368
|
-
});
|
|
7369
|
-
};
|
|
7370
|
-
show(0);
|
|
7371
|
-
const last = () => current === fields.length - 1;
|
|
7372
|
-
return {
|
|
7373
|
-
...api,
|
|
7374
|
-
focus: () => fields[current]?.field.focus(),
|
|
7375
|
-
mode: () => last() ? "submit" : "advance",
|
|
7376
|
-
// Advancing needs only THIS question answered; submitting needs them all,
|
|
7377
|
-
// which is what stops the last question from accepting a form with a hole
|
|
7378
|
-
// in it.
|
|
7379
|
-
canProceed: () => last() ? isComplete() : !!fields[current]?.field.isComplete(),
|
|
7380
|
-
proceed: () => {
|
|
7381
|
-
if (last()) return;
|
|
7382
|
-
show(current + 1);
|
|
7383
|
-
fields[current]?.field.focus();
|
|
7384
|
-
}
|
|
7385
|
-
};
|
|
7386
|
-
}
|
|
7387
|
-
const field = buildField(schema, onChange, message);
|
|
7388
|
-
body.appendChild(field.el);
|
|
7389
|
-
return {
|
|
7390
|
-
el: panel,
|
|
7391
|
-
dismiss,
|
|
7392
|
-
getContent: () => field.getValue(),
|
|
7393
|
-
isComplete: () => field.isComplete(),
|
|
7394
|
-
focus: () => field.focus(),
|
|
7395
|
-
mode: () => "submit",
|
|
7396
|
-
canProceed: () => field.isComplete(),
|
|
7397
|
-
proceed: () => {
|
|
7398
|
-
}
|
|
7399
|
-
};
|
|
7400
|
-
}
|
|
7401
|
-
function requestUserInput(request) {
|
|
7402
|
-
return resolveConfig(request.target ?? null).requestUserInput(request);
|
|
7403
|
-
}
|
|
7404
7394
|
export {
|
|
7405
|
-
|
|
7395
|
+
resolveConfig as $,
|
|
7406
7396
|
APARTE_CONFIG_CHANGE as A,
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7397
|
+
contentToText as B,
|
|
7398
|
+
contextConfig as C,
|
|
7399
|
+
createAparteChatHandler as D,
|
|
7400
|
+
createStreamAdapter as E,
|
|
7401
|
+
cssEscape as F,
|
|
7402
|
+
declineDefaultRenderers as G,
|
|
7403
|
+
defaultSanitizer as H,
|
|
7404
|
+
deriveArtifactKind as I,
|
|
7405
|
+
detachConfig as J,
|
|
7406
|
+
escapeAttr as K,
|
|
7407
|
+
escapeHtml as L,
|
|
7408
|
+
filesToAttachments as M,
|
|
7409
|
+
getAllRenderers as N,
|
|
7410
|
+
getSegmentRenderer as O,
|
|
7411
|
+
installDefaultRenderersOnce as P,
|
|
7412
|
+
isAwaitingReply as Q,
|
|
7413
|
+
isFormatAdapter as R,
|
|
7414
|
+
isSafeUrl as S,
|
|
7415
|
+
isSegmentSettled as T,
|
|
7416
|
+
parseAparteEventStream as U,
|
|
7417
|
+
parseMarkdownToSegments as V,
|
|
7418
|
+
populateBubbleFromMessage as W,
|
|
7419
|
+
readableToAsyncIterable as X,
|
|
7420
|
+
registerDefaultRenderers as Y,
|
|
7421
|
+
registerSegmentRenderer as Z,
|
|
7422
|
+
requestUserInput as _,
|
|
7433
7423
|
APARTE_CONVERSATION_SCHEMA_VERSION as a,
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7424
|
+
revokeAttachmentUrls as a0,
|
|
7425
|
+
runWithConfig as a1,
|
|
7426
|
+
segmentDuration as a2,
|
|
7427
|
+
segmentTiming as a3,
|
|
7428
|
+
subscribeConfigChange as a4,
|
|
7429
|
+
unregisterSegmentRenderer as a5,
|
|
7430
|
+
uuid as a6,
|
|
7431
|
+
mergeSegmentUpdate as a7,
|
|
7432
|
+
stampSegmentActivity as a8,
|
|
7433
|
+
stampSegmentOnInsert as a9,
|
|
7434
|
+
stampSegmentOnUpdate as aa,
|
|
7435
|
+
renumberSegments as ab,
|
|
7436
|
+
openSegmentIds as ac,
|
|
7437
|
+
isTerminalStatus as ad,
|
|
7438
|
+
adoptMessageSegments as ae,
|
|
7447
7439
|
APARTE_DEFAULT_BUBBLE_ACTIONS as b,
|
|
7448
7440
|
APARTE_DEFAULT_HOST_HANDLERS as c,
|
|
7449
7441
|
APARTE_DEFAULT_ICON_FALLBACKS as d,
|
|
@@ -7458,16 +7450,16 @@ export {
|
|
|
7458
7450
|
AparteConversationController as m,
|
|
7459
7451
|
AparteConversationManager as n,
|
|
7460
7452
|
AparteDirectTransport as o,
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
7471
|
-
|
|
7453
|
+
AparteElicitationAbortError as p,
|
|
7454
|
+
AparteError as q,
|
|
7455
|
+
AparteErrorCode as r,
|
|
7456
|
+
AparteMessageRepository as s,
|
|
7457
|
+
AparteStreamParser as t,
|
|
7458
|
+
aparteGlobalConfig as u,
|
|
7459
|
+
applyElementProps as v,
|
|
7460
|
+
attachConfig as w,
|
|
7461
|
+
buildApprovalPanel as x,
|
|
7462
|
+
buildElicitationPanel as y,
|
|
7463
|
+
collectRendererStyles as z
|
|
7472
7464
|
};
|
|
7473
|
-
//# sourceMappingURL=
|
|
7465
|
+
//# sourceMappingURL=is-awaiting-reply-CxWRISBC.js.map
|