@syntrologie/adapt-chatbot 2.28.0 → 2.29.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/AdaptiveChatBar.composer.test.d.ts +2 -0
- package/dist/AdaptiveChatBar.composer.test.d.ts.map +1 -0
- package/dist/AdaptiveChatBar.d.ts +140 -4
- package/dist/AdaptiveChatBar.d.ts.map +1 -1
- package/dist/AdaptiveChatBar.js +4 -3
- package/dist/AdaptiveChatBarMountable.d.ts +47 -0
- package/dist/AdaptiveChatBarMountable.d.ts.map +1 -1
- package/dist/AdaptiveChatTrail.anchor-scroll.test.d.ts +2 -0
- package/dist/AdaptiveChatTrail.anchor-scroll.test.d.ts.map +1 -0
- package/dist/AdaptiveChatTrail.d.ts +397 -5
- package/dist/AdaptiveChatTrail.d.ts.map +1 -1
- package/dist/AdaptiveChatTrail.js +6 -4
- package/dist/AdaptiveChatTrail.test.d.ts +2 -0
- package/dist/AdaptiveChatTrail.test.d.ts.map +1 -0
- package/dist/AdaptiveChipsStrip.d.ts +17 -11
- package/dist/AdaptiveChipsStrip.d.ts.map +1 -1
- package/dist/AdaptiveChipsStrip.js +2 -2
- package/dist/ChatAssistantLit.d.ts.map +1 -1
- package/dist/ChatAssistantLit.js +4 -3
- package/dist/ChatSession.d.ts +66 -5
- package/dist/ChatSession.d.ts.map +1 -1
- package/dist/ChatTransport.d.ts +107 -0
- package/dist/ChatTransport.d.ts.map +1 -1
- package/dist/InlineRegistryWidgetMount.test.d.ts +2 -0
- package/dist/InlineRegistryWidgetMount.test.d.ts.map +1 -0
- package/dist/InlineWidgetLiveMount.test.d.ts +2 -0
- package/dist/InlineWidgetLiveMount.test.d.ts.map +1 -0
- package/dist/NavLinkMountable.d.ts.map +1 -1
- package/dist/TextAnswerMountable.test.d.ts +2 -0
- package/dist/TextAnswerMountable.test.d.ts.map +1 -0
- package/dist/Turnstile.d.ts.map +1 -1
- package/dist/chunk-A7RBZLIX.js +1678 -0
- package/dist/chunk-A7RBZLIX.js.map +7 -0
- package/dist/{chunk-435KJD27.js → chunk-EACGDSJI.js} +46 -1
- package/dist/chunk-EACGDSJI.js.map +7 -0
- package/dist/chunk-EWPPVPJ4.js +14 -0
- package/dist/chunk-EWPPVPJ4.js.map +7 -0
- package/dist/chunk-FXHYE6FY.js +632 -0
- package/dist/chunk-FXHYE6FY.js.map +7 -0
- package/dist/{chunk-AUER7ZCK.js → chunk-OIGVZGSX.js} +11 -4
- package/dist/chunk-OIGVZGSX.js.map +7 -0
- package/dist/{chunk-UC4XU6GH.js → chunk-VZIUXXAM.js} +761 -52
- package/dist/chunk-VZIUXXAM.js.map +7 -0
- package/dist/{chunk-KUO67E2W.js → chunk-ZXV4GXMO.js} +715 -227
- package/dist/chunk-ZXV4GXMO.js.map +7 -0
- package/dist/elements/ActionHandler.d.ts +1 -1
- package/dist/elements/ActionHandler.d.ts.map +1 -1
- package/dist/elements/ElementInstanceStore.d.ts +62 -0
- package/dist/elements/ElementInstanceStore.d.ts.map +1 -1
- package/dist/elements/TileHandler.d.ts.map +1 -1
- package/dist/elements/TileHandler.test.d.ts +2 -0
- package/dist/elements/TileHandler.test.d.ts.map +1 -0
- package/dist/elements/__tests__/ElementInstanceStore.recall.test.d.ts +2 -0
- package/dist/elements/__tests__/ElementInstanceStore.recall.test.d.ts.map +1 -0
- package/dist/elements/fetcher.d.ts +3 -3
- package/dist/elements/fetcher.d.ts.map +1 -1
- package/dist/elements/index.d.ts +1 -0
- package/dist/elements/index.d.ts.map +1 -1
- package/dist/elements/store-singleton.d.ts +4 -0
- package/dist/elements/store-singleton.d.ts.map +1 -0
- package/dist/elements/types.d.ts +4 -0
- package/dist/elements/types.d.ts.map +1 -1
- package/dist/observer/__tests__/bus-translate.test.d.ts +16 -0
- package/dist/observer/__tests__/bus-translate.test.d.ts.map +1 -0
- package/dist/observer/__tests__/lifecycle.test.d.ts +2 -0
- package/dist/observer/__tests__/lifecycle.test.d.ts.map +1 -0
- package/dist/observer/__tests__/observer-enrich.test.d.ts +10 -0
- package/dist/observer/__tests__/observer-enrich.test.d.ts.map +1 -0
- package/dist/observer/allowlist.d.ts +5 -7
- package/dist/observer/allowlist.d.ts.map +1 -1
- package/dist/observer/bus-translate.d.ts +33 -0
- package/dist/observer/bus-translate.d.ts.map +1 -0
- package/dist/observer/index.d.ts +10 -1
- package/dist/observer/index.d.ts.map +1 -1
- package/dist/observer/queue.d.ts +6 -0
- package/dist/observer/queue.d.ts.map +1 -1
- package/dist/observer/transport.d.ts +1 -0
- package/dist/observer/transport.d.ts.map +1 -1
- package/dist/personas.d.ts +30 -0
- package/dist/personas.d.ts.map +1 -0
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +856 -129
- package/dist/runtime.js.map +4 -4
- package/dist/schema.d.ts +363 -67
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +176 -3
- package/dist/schema.js.map +2 -2
- package/dist/test/setup.d.ts +1 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -1
- package/dist/chunk-435KJD27.js.map +0 -7
- package/dist/chunk-AUER7ZCK.js.map +0 -7
- package/dist/chunk-DOMEUJR7.js +0 -382
- package/dist/chunk-DOMEUJR7.js.map +0 -7
- package/dist/chunk-KUO67E2W.js.map +0 -7
- package/dist/chunk-QELVKBQV.js +0 -214
- package/dist/chunk-QELVKBQV.js.map +0 -7
- package/dist/chunk-UC4XU6GH.js.map +0 -7
|
@@ -223,8 +223,8 @@ var _createTrustedTypesPolicy = function _createTrustedTypesPolicy2(trustedTypes
|
|
|
223
223
|
const policyName = "dompurify" + (suffix ? "#" + suffix : "");
|
|
224
224
|
try {
|
|
225
225
|
return trustedTypes.createPolicy(policyName, {
|
|
226
|
-
createHTML(
|
|
227
|
-
return
|
|
226
|
+
createHTML(html9) {
|
|
227
|
+
return html9;
|
|
228
228
|
},
|
|
229
229
|
createScriptURL(scriptUrl) {
|
|
230
230
|
return scriptUrl;
|
|
@@ -2238,12 +2238,102 @@ var en = x.lex;
|
|
|
2238
2238
|
import { html as html22 } from "lit";
|
|
2239
2239
|
import { html as html3, nothing as nothing2 } from "lit";
|
|
2240
2240
|
import { html as html5 } from "lit";
|
|
2241
|
+
import { html as html8, LitElement as LitElement2 } from "lit";
|
|
2242
|
+
function getCtor() {
|
|
2243
|
+
if (typeof window === "undefined") return void 0;
|
|
2244
|
+
const w2 = window;
|
|
2245
|
+
return w2.SpeechRecognition ?? w2.webkitSpeechRecognition;
|
|
2246
|
+
}
|
|
2247
|
+
function isDictationSupported() {
|
|
2248
|
+
return getCtor() !== void 0;
|
|
2249
|
+
}
|
|
2250
|
+
function mapError(engineCode) {
|
|
2251
|
+
switch (engineCode) {
|
|
2252
|
+
case "not-allowed":
|
|
2253
|
+
case "service-not-allowed":
|
|
2254
|
+
return "not-allowed";
|
|
2255
|
+
case "no-speech":
|
|
2256
|
+
return "no-speech";
|
|
2257
|
+
case "aborted":
|
|
2258
|
+
return "aborted";
|
|
2259
|
+
case "network":
|
|
2260
|
+
return "network";
|
|
2261
|
+
default:
|
|
2262
|
+
return "other";
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
var MAX_SESSION_MS = 6e4;
|
|
2266
|
+
function createDictation(opts) {
|
|
2267
|
+
let rec = null;
|
|
2268
|
+
let listening = false;
|
|
2269
|
+
let capTimer = null;
|
|
2270
|
+
const finish = () => {
|
|
2271
|
+
if (capTimer !== null) {
|
|
2272
|
+
clearTimeout(capTimer);
|
|
2273
|
+
capTimer = null;
|
|
2274
|
+
}
|
|
2275
|
+
if (!listening) return;
|
|
2276
|
+
listening = false;
|
|
2277
|
+
rec = null;
|
|
2278
|
+
opts.onEnd();
|
|
2279
|
+
};
|
|
2280
|
+
return {
|
|
2281
|
+
get listening() {
|
|
2282
|
+
return listening;
|
|
2283
|
+
},
|
|
2284
|
+
start() {
|
|
2285
|
+
if (listening) return;
|
|
2286
|
+
const Ctor = getCtor();
|
|
2287
|
+
if (!Ctor) return;
|
|
2288
|
+
rec = new Ctor();
|
|
2289
|
+
rec.lang = opts.lang ?? (document.documentElement.lang || "en-US");
|
|
2290
|
+
rec.continuous = true;
|
|
2291
|
+
rec.interimResults = true;
|
|
2292
|
+
rec.onresult = (e) => {
|
|
2293
|
+
if (!listening) return;
|
|
2294
|
+
const parts = [];
|
|
2295
|
+
let allFinal = true;
|
|
2296
|
+
for (let i = 0; i < e.results.length; i++) {
|
|
2297
|
+
const result = e.results[i];
|
|
2298
|
+
const seg = result[0]?.transcript.trim() ?? "";
|
|
2299
|
+
if (!result.isFinal) allFinal = false;
|
|
2300
|
+
if (seg && seg !== parts[parts.length - 1]) parts.push(seg);
|
|
2301
|
+
}
|
|
2302
|
+
opts.onTranscript(parts.join(" "), allFinal);
|
|
2303
|
+
};
|
|
2304
|
+
rec.onerror = (e) => {
|
|
2305
|
+
const code = mapError(e.error);
|
|
2306
|
+
console.debug(`chat.dictation:error ${e.error} -> ${code}`);
|
|
2307
|
+
listening = false;
|
|
2308
|
+
rec = null;
|
|
2309
|
+
opts.onError(code);
|
|
2310
|
+
};
|
|
2311
|
+
rec.onend = finish;
|
|
2312
|
+
listening = true;
|
|
2313
|
+
capTimer = setTimeout(() => {
|
|
2314
|
+
console.debug("chat.dictation:cap 60s session limit reached \u2014 stopping");
|
|
2315
|
+
const active = rec;
|
|
2316
|
+
finish();
|
|
2317
|
+
active?.stop();
|
|
2318
|
+
}, MAX_SESSION_MS);
|
|
2319
|
+
rec.start();
|
|
2320
|
+
},
|
|
2321
|
+
stop() {
|
|
2322
|
+
const active = rec;
|
|
2323
|
+
finish();
|
|
2324
|
+
active?.stop();
|
|
2325
|
+
}
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2241
2328
|
var sendIcon = html2`<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 14V2M8 2L3 7M8 2L13 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
|
2242
2329
|
var stopIcon = html2`<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="1" y="1" width="12" height="12" rx="2" fill="currentColor"/></svg>`;
|
|
2330
|
+
var micIcon = html2`<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="6" y="1.5" width="4" height="8" rx="2" stroke="currentColor" stroke-width="1.6"/><path d="M3.5 7.5a4.5 4.5 0 0 0 9 0M8 12v2.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>`;
|
|
2243
2331
|
function autoResize(e) {
|
|
2244
2332
|
const el = e.target;
|
|
2245
2333
|
el.style.height = "auto";
|
|
2246
|
-
|
|
2334
|
+
const capped = Math.min(el.scrollHeight, 150);
|
|
2335
|
+
el.style.height = `${capped}px`;
|
|
2336
|
+
el.style.overflowY = capped >= 150 ? "auto" : "hidden";
|
|
2247
2337
|
}
|
|
2248
2338
|
function renderChatInput(props) {
|
|
2249
2339
|
const canSend = props.value.trim().length > 0 && !props.isStreaming;
|
|
@@ -2273,6 +2363,15 @@ function renderChatInput(props) {
|
|
|
2273
2363
|
aria-label="Message input"
|
|
2274
2364
|
></textarea>
|
|
2275
2365
|
${props.inputActionsSlot ?? nothing}
|
|
2366
|
+
${props.showMic ? html2`<button
|
|
2367
|
+
data-sc-mic-btn
|
|
2368
|
+
data-listening=${props.micListening ? "true" : "false"}
|
|
2369
|
+
?disabled=${props.micDisabled ?? false}
|
|
2370
|
+
@click=${props.onMicClick}
|
|
2371
|
+
aria-pressed=${props.micListening ? "true" : "false"}
|
|
2372
|
+
aria-label=${props.micListening ? "Stop dictation" : "Start dictation"}
|
|
2373
|
+
title=${props.micDisabled ? "Microphone blocked" : props.micListening ? "Stop dictation" : "Dictate"}
|
|
2374
|
+
>${micIcon}</button>` : nothing}
|
|
2276
2375
|
<button
|
|
2277
2376
|
data-sc-send-btn
|
|
2278
2377
|
?disabled=${!canSend && !props.isStreaming}
|
|
@@ -2283,7 +2382,18 @@ function renderChatInput(props) {
|
|
|
2283
2382
|
`;
|
|
2284
2383
|
}
|
|
2285
2384
|
function fingerprint(msg) {
|
|
2286
|
-
const tcIds = msg.toolCalls?.map((tc) =>
|
|
2385
|
+
const tcIds = msg.toolCalls?.map((tc) => {
|
|
2386
|
+
let resultKey = "-";
|
|
2387
|
+
if (tc.result !== void 0) {
|
|
2388
|
+
try {
|
|
2389
|
+
const json = JSON.stringify(tc.result);
|
|
2390
|
+
resultKey = json === void 0 ? "u" : String(json.length);
|
|
2391
|
+
} catch {
|
|
2392
|
+
resultKey = String(String(tc.result).length);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
return `${tc.id}:${tc.status}:${tc.interruptId ?? ""}:${resultKey}:${tc.error ?? ""}`;
|
|
2396
|
+
}).join(",") ?? "";
|
|
2287
2397
|
return `${msg.id}:${msg.role}:${msg.content.length}:${msg.status}:${tcIds}`;
|
|
2288
2398
|
}
|
|
2289
2399
|
var marked = new B({ async: false, gfm: true, breaks: true });
|
|
@@ -2343,16 +2453,50 @@ function statusIcon(status) {
|
|
|
2343
2453
|
return toolIcon;
|
|
2344
2454
|
}
|
|
2345
2455
|
}
|
|
2456
|
+
var STATUS_TEXT = {
|
|
2457
|
+
"args-streaming": "Running\u2026",
|
|
2458
|
+
running: "Running\u2026",
|
|
2459
|
+
pending: "Needs approval",
|
|
2460
|
+
done: "Done",
|
|
2461
|
+
error: "Failed",
|
|
2462
|
+
cancelled: "Cancelled"
|
|
2463
|
+
};
|
|
2464
|
+
function humanizeToolName(name) {
|
|
2465
|
+
const words = name.replace(/[_-]+/g, " ").trim();
|
|
2466
|
+
return words ? words.charAt(0).toUpperCase() + words.slice(1) : name;
|
|
2467
|
+
}
|
|
2468
|
+
var ARGS_SUMMARY_MAX = 90;
|
|
2469
|
+
var ARG_VALUE_MAX = 48;
|
|
2470
|
+
function summarizeArgs(args) {
|
|
2471
|
+
if (!args) return "";
|
|
2472
|
+
const bits = [];
|
|
2473
|
+
for (const [key, value] of Object.entries(args)) {
|
|
2474
|
+
if (value === null || value === void 0) continue;
|
|
2475
|
+
const t = typeof value;
|
|
2476
|
+
if (t !== "string" && t !== "number" && t !== "boolean") continue;
|
|
2477
|
+
let rendered = String(value);
|
|
2478
|
+
if (rendered.length > ARG_VALUE_MAX) rendered = `${rendered.slice(0, ARG_VALUE_MAX)}\u2026`;
|
|
2479
|
+
bits.push(`${key}: ${rendered}`);
|
|
2480
|
+
if (bits.join(" \xB7 ").length >= ARGS_SUMMARY_MAX) break;
|
|
2481
|
+
}
|
|
2482
|
+
let summary = bits.join(" \xB7 ");
|
|
2483
|
+
if (summary.length > ARGS_SUMMARY_MAX) summary = `${summary.slice(0, ARGS_SUMMARY_MAX)}\u2026`;
|
|
2484
|
+
return summary;
|
|
2485
|
+
}
|
|
2346
2486
|
function renderToolCallCard(props) {
|
|
2347
2487
|
const { toolCall, toolLabel, approve, deny } = props;
|
|
2348
|
-
const displayName = toolLabel ?? toolCall.name;
|
|
2488
|
+
const displayName = toolLabel ?? humanizeToolName(toolCall.name);
|
|
2349
2489
|
const isActive = toolCall.status === "args-streaming" || toolCall.status === "running";
|
|
2490
|
+
const statusText = STATUS_TEXT[toolCall.status] ?? "";
|
|
2491
|
+
const argsSummary = summarizeArgs(toolCall.args);
|
|
2350
2492
|
return html3`
|
|
2351
|
-
<div data-sc-tool-card ?data-sc-active=${isActive}>
|
|
2493
|
+
<div data-sc-tool-card ?data-sc-active=${isActive} data-sc-tool-phase=${toolCall.status}>
|
|
2352
2494
|
<div data-sc-tool-header>
|
|
2353
2495
|
<span data-sc-tool-icon data-sc-status=${toolCall.status}>${statusIcon(toolCall.status)}</span>
|
|
2354
2496
|
<span data-sc-tool-name>${displayName}</span>
|
|
2497
|
+
${statusText ? html3`<span data-sc-tool-status-text data-sc-status=${toolCall.status}>${statusText}</span>` : nothing2}
|
|
2355
2498
|
</div>
|
|
2499
|
+
${argsSummary ? html3`<div data-sc-tool-args>${argsSummary}</div>` : nothing2}
|
|
2356
2500
|
${toolCall.status === "error" && toolCall.error ? html3`<div data-sc-tool-error>${toolCall.error}</div>` : nothing2}
|
|
2357
2501
|
${toolCall.status === "pending" ? html3`<div data-sc-tool-actions>
|
|
2358
2502
|
<button data-sc-approve @click=${approve} aria-label="Approve ${displayName}">Approve</button>
|
|
@@ -2369,18 +2513,20 @@ function renderBubbleContent(message) {
|
|
|
2369
2513
|
return html4`<div data-sc-msg-assistant>${unsafeHTML(renderMarkdown(message.content))}</div>`;
|
|
2370
2514
|
}
|
|
2371
2515
|
function renderMessageBubble(props) {
|
|
2372
|
-
const { message, toolLabels, toolRenderers, defaultToolRenderer } = props;
|
|
2516
|
+
const { message, toolLabels, toolRenderers, toolContext, defaultToolRenderer } = props;
|
|
2373
2517
|
const isUser = message.role === "user";
|
|
2374
2518
|
const toolCards = message.toolCalls?.map((tc) => {
|
|
2375
|
-
const approve = () => props.onToolApprove(tc.id);
|
|
2519
|
+
const approve = (payload) => props.onToolApprove(tc.id, payload);
|
|
2376
2520
|
const deny = () => props.onToolDeny(tc.id);
|
|
2521
|
+
const context = toolContext?.[tc.name];
|
|
2377
2522
|
const customRenderer = toolRenderers?.[tc.name];
|
|
2378
2523
|
if (customRenderer) {
|
|
2379
2524
|
return customRenderer.render({
|
|
2380
2525
|
toolCall: tc,
|
|
2381
2526
|
phase: tc.status,
|
|
2382
2527
|
approve,
|
|
2383
|
-
deny
|
|
2528
|
+
deny,
|
|
2529
|
+
context
|
|
2384
2530
|
});
|
|
2385
2531
|
}
|
|
2386
2532
|
if (defaultToolRenderer) {
|
|
@@ -2388,22 +2534,23 @@ function renderMessageBubble(props) {
|
|
|
2388
2534
|
toolCall: tc,
|
|
2389
2535
|
phase: tc.status,
|
|
2390
2536
|
approve,
|
|
2391
|
-
deny
|
|
2537
|
+
deny,
|
|
2538
|
+
context
|
|
2392
2539
|
});
|
|
2393
2540
|
}
|
|
2394
2541
|
return renderToolCallCard({
|
|
2395
2542
|
toolCall: tc,
|
|
2396
2543
|
toolLabel: toolLabels?.[tc.name],
|
|
2397
|
-
approve,
|
|
2544
|
+
approve: () => approve(),
|
|
2398
2545
|
deny
|
|
2399
2546
|
});
|
|
2400
2547
|
});
|
|
2401
2548
|
return html4`
|
|
2402
|
-
<div data-sc-msg-row data-sc-role=${message.role}>
|
|
2549
|
+
<div data-sc-msg-row data-sc-role=${message.role} data-sc-msg-id=${message.id}>
|
|
2403
2550
|
${!isUser ? assistantIcon : nothing3}
|
|
2404
2551
|
<div data-sc-msg-content>
|
|
2405
2552
|
${renderBubbleContent(message)}
|
|
2406
|
-
${message.status === "error" ? html4`<div data-sc-msg-error
|
|
2553
|
+
${message.status === "error" ? html4`<div data-sc-msg-error>${isUser ? "Failed to send" : "Response interrupted"}</div>` : nothing3}
|
|
2407
2554
|
${toolCards ?? nothing3}
|
|
2408
2555
|
${renderActionBar({
|
|
2409
2556
|
messageId: message.id,
|
|
@@ -2435,11 +2582,12 @@ function renderMessageList(props) {
|
|
|
2435
2582
|
props.messages,
|
|
2436
2583
|
(msg) => msg.id,
|
|
2437
2584
|
(msg) => guard(
|
|
2438
|
-
[fingerprint(msg)],
|
|
2585
|
+
[fingerprint(msg), props.toolContext],
|
|
2439
2586
|
() => renderMessageBubble({
|
|
2440
2587
|
message: msg,
|
|
2441
2588
|
toolLabels: props.toolLabels,
|
|
2442
2589
|
toolRenderers: props.toolRenderers,
|
|
2590
|
+
toolContext: props.toolContext,
|
|
2443
2591
|
defaultToolRenderer: props.defaultToolRenderer,
|
|
2444
2592
|
onCopy: props.onCopy,
|
|
2445
2593
|
onRegenerate: props.onRegenerate,
|
|
@@ -2655,7 +2803,11 @@ var STYLE_RULES = `
|
|
|
2655
2803
|
border-radius: var(--sc-chat-textarea-radius, 14px);
|
|
2656
2804
|
padding: var(--sc-chat-textarea-padding, 10px 16px);
|
|
2657
2805
|
font-family: inherit;
|
|
2658
|
-
|
|
2806
|
+
/* >= 16px: iOS Safari auto-zooms a focused input < 16px and never zooms back
|
|
2807
|
+
out (same class as the vela AdaptiveChatBar composer fix). 0.9rem was 14.4px
|
|
2808
|
+
\u2192 zoom-on-focus + stuck-zoomed on the native <syntro-chat> mobile surfaces.
|
|
2809
|
+
A workspace can still override the token, but must keep it >= 16px on mobile. */
|
|
2810
|
+
font-size: var(--sc-chat-textarea-font-size, 1rem);
|
|
2659
2811
|
resize: none;
|
|
2660
2812
|
outline: none;
|
|
2661
2813
|
background: var(--sc-chat-input-bg, #ffffff);
|
|
@@ -2664,7 +2816,7 @@ var STYLE_RULES = `
|
|
|
2664
2816
|
max-height: 150px;
|
|
2665
2817
|
line-height: 1.4;
|
|
2666
2818
|
transition: border-color 0.2s, box-shadow 0.2s;
|
|
2667
|
-
overflow-y:
|
|
2819
|
+
overflow-y: hidden;
|
|
2668
2820
|
}
|
|
2669
2821
|
|
|
2670
2822
|
[data-sc-chat] [data-sc-chat-input] textarea:focus {
|
|
@@ -2706,6 +2858,60 @@ var STYLE_RULES = `
|
|
|
2706
2858
|
display: block;
|
|
2707
2859
|
}
|
|
2708
2860
|
|
|
2861
|
+
[data-sc-chat] [data-sc-mic-btn] {
|
|
2862
|
+
background: transparent;
|
|
2863
|
+
color: var(--sc-content-text-secondary-color, #87919f);
|
|
2864
|
+
border: none;
|
|
2865
|
+
border-radius: 50%;
|
|
2866
|
+
width: var(--sc-chat-send-btn-size, 38px);
|
|
2867
|
+
height: var(--sc-chat-send-btn-size, 38px);
|
|
2868
|
+
cursor: pointer;
|
|
2869
|
+
display: flex;
|
|
2870
|
+
align-items: center;
|
|
2871
|
+
justify-content: center;
|
|
2872
|
+
flex-shrink: 0;
|
|
2873
|
+
transition: color 0.2s, transform 0.15s, opacity 0.2s;
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
[data-sc-chat] [data-sc-mic-btn]:hover:not(:disabled) {
|
|
2877
|
+
color: var(--sc-color-primary, #6a59ce);
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
[data-sc-chat] [data-sc-mic-btn]:disabled {
|
|
2881
|
+
opacity: 0.35;
|
|
2882
|
+
cursor: not-allowed;
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
[data-sc-chat] [data-sc-mic-btn][data-listening='true'] {
|
|
2886
|
+
background: var(--sc-color-primary, #6a59ce);
|
|
2887
|
+
color: var(--sc-color-primary-text, #ffffff);
|
|
2888
|
+
animation: sc-mic-ring 1.5s ease-out infinite;
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
@keyframes sc-mic-ring {
|
|
2892
|
+
0% {
|
|
2893
|
+
box-shadow: 0 0 0 0 color-mix(in srgb, var(--sc-color-primary, #6a59ce) 45%, transparent);
|
|
2894
|
+
transform: scale(1);
|
|
2895
|
+
}
|
|
2896
|
+
50% { transform: scale(1.08); }
|
|
2897
|
+
70% { box-shadow: 0 0 0 12px transparent; }
|
|
2898
|
+
100% {
|
|
2899
|
+
box-shadow: 0 0 0 0 transparent;
|
|
2900
|
+
transform: scale(1);
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2905
|
+
[data-sc-chat] [data-sc-mic-btn][data-listening='true'] {
|
|
2906
|
+
animation: none;
|
|
2907
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--sc-color-primary, #6a59ce) 35%, transparent);
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
[data-sc-chat] [data-sc-mic-btn] svg {
|
|
2912
|
+
display: block;
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2709
2915
|
/* \u2500\u2500 Typing indicator (compact: three bouncing dots) \u2500\u2500 */
|
|
2710
2916
|
[data-sc-chat] [data-sc-typing] {
|
|
2711
2917
|
display: flex;
|
|
@@ -2832,7 +3038,50 @@ var STYLE_RULES = `
|
|
|
2832
3038
|
}
|
|
2833
3039
|
|
|
2834
3040
|
[data-sc-chat] [data-sc-tool-name] {
|
|
3041
|
+
font-weight: 600;
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
[data-sc-chat] [data-sc-tool-status-text] {
|
|
3045
|
+
margin-left: auto;
|
|
3046
|
+
font-size: 0.72rem;
|
|
2835
3047
|
font-weight: 500;
|
|
3048
|
+
letter-spacing: 0.02em;
|
|
3049
|
+
text-transform: uppercase;
|
|
3050
|
+
color: var(--sc-content-text-secondary-color, #6b6b80);
|
|
3051
|
+
white-space: nowrap;
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
[data-sc-chat] [data-sc-tool-status-text][data-sc-status="done"] {
|
|
3055
|
+
color: #22c55e;
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
[data-sc-chat] [data-sc-tool-status-text][data-sc-status="error"] {
|
|
3059
|
+
color: #ef4444;
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
[data-sc-chat] [data-sc-tool-status-text][data-sc-status="running"],
|
|
3063
|
+
[data-sc-chat] [data-sc-tool-status-text][data-sc-status="args-streaming"] {
|
|
3064
|
+
color: #3b82f6;
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
[data-sc-chat] [data-sc-tool-status-text][data-sc-status="cancelled"] {
|
|
3068
|
+
color: var(--sc-content-text-secondary-color, #6b6b80);
|
|
3069
|
+
text-decoration: line-through;
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
[data-sc-chat] [data-sc-tool-args] {
|
|
3073
|
+
margin-top: 6px;
|
|
3074
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
3075
|
+
font-size: 0.76rem;
|
|
3076
|
+
color: var(--sc-content-text-secondary-color, #6b6b80);
|
|
3077
|
+
overflow: hidden;
|
|
3078
|
+
text-overflow: ellipsis;
|
|
3079
|
+
white-space: nowrap;
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
[data-sc-chat] [data-sc-tool-card][data-sc-tool-phase="cancelled"] {
|
|
3083
|
+
opacity: 0.65;
|
|
3084
|
+
border-left-color: var(--sc-content-border-color, #e8e8ee);
|
|
2836
3085
|
}
|
|
2837
3086
|
|
|
2838
3087
|
[data-sc-chat] [data-sc-tool-error] {
|
|
@@ -2892,7 +3141,7 @@ var STYLE_RULES = `
|
|
|
2892
3141
|
margin-top: 4px;
|
|
2893
3142
|
}
|
|
2894
3143
|
|
|
2895
|
-
/* \u2500\u2500 Scroll
|
|
3144
|
+
/* \u2500\u2500 "Scroll to most recent" pill \u2500\u2500 */
|
|
2896
3145
|
[data-sc-chat] [data-sc-scroll-btn] {
|
|
2897
3146
|
position: absolute;
|
|
2898
3147
|
bottom: 80px;
|
|
@@ -2901,12 +3150,17 @@ var STYLE_RULES = `
|
|
|
2901
3150
|
background: var(--sc-chat-assistant-bubble-bg, #f4f4f8);
|
|
2902
3151
|
color: var(--sc-content-text-color, #1a1a2e);
|
|
2903
3152
|
border: 1px solid var(--sc-content-border-color, #e8e8ee);
|
|
2904
|
-
border-radius:
|
|
2905
|
-
|
|
2906
|
-
|
|
3153
|
+
border-radius: 9999px;
|
|
3154
|
+
height: 28px;
|
|
3155
|
+
padding: 0 10px;
|
|
2907
3156
|
display: flex;
|
|
2908
3157
|
align-items: center;
|
|
2909
3158
|
justify-content: center;
|
|
3159
|
+
gap: 5px;
|
|
3160
|
+
font-size: 12px;
|
|
3161
|
+
font-weight: 500;
|
|
3162
|
+
line-height: 1;
|
|
3163
|
+
white-space: nowrap;
|
|
2910
3164
|
cursor: pointer;
|
|
2911
3165
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
2912
3166
|
z-index: 1;
|
|
@@ -2935,34 +3189,50 @@ function injectChatStyles(root = typeof document === "undefined" ? void 0 : docu
|
|
|
2935
3189
|
}
|
|
2936
3190
|
root.insertBefore(style, root.firstChild ?? null);
|
|
2937
3191
|
}
|
|
2938
|
-
var
|
|
3192
|
+
var _SyntroChat = class _SyntroChat2 extends LitElement {
|
|
2939
3193
|
constructor() {
|
|
2940
3194
|
super(...arguments);
|
|
3195
|
+
this.voiceInput = false;
|
|
2941
3196
|
this._messages = [];
|
|
3197
|
+
this._micListening = false;
|
|
3198
|
+
this._micDisabled = false;
|
|
3199
|
+
this._micBase = "";
|
|
3200
|
+
this._micNetworkErrors = 0;
|
|
3201
|
+
this._dictation = null;
|
|
2942
3202
|
this._isTyping = false;
|
|
2943
|
-
this._isConnected = false;
|
|
2944
|
-
this._sessionId = null;
|
|
2945
3203
|
this._inputValue = "";
|
|
2946
3204
|
this._isComposing = false;
|
|
2947
3205
|
this._isStreaming = false;
|
|
2948
3206
|
this._unsubscribe = null;
|
|
2949
3207
|
this._scrollContainer = null;
|
|
2950
|
-
this._userScrolledUp = false;
|
|
2951
3208
|
this._errorMessage = null;
|
|
3209
|
+
this._stickToBottom = true;
|
|
3210
|
+
this._messagesBelow = 0;
|
|
3211
|
+
this._lastUserScrollIntentTs = 0;
|
|
3212
|
+
this._markUserScrollIntent = () => {
|
|
3213
|
+
this._lastUserScrollIntentTs = Date.now();
|
|
3214
|
+
};
|
|
2952
3215
|
this._handleScroll = () => {
|
|
2953
3216
|
if (!this._scrollContainer) return;
|
|
2954
|
-
const
|
|
2955
|
-
this.
|
|
3217
|
+
const atBottom = this._isAtBottom();
|
|
3218
|
+
this._messagesBelow = atBottom ? 0 : this._countMessagesBelow();
|
|
3219
|
+
const isUserDriven = Date.now() - this._lastUserScrollIntentTs < _SyntroChat2._USER_SCROLL_INTENT_WINDOW_MS;
|
|
3220
|
+
if (isUserDriven) {
|
|
3221
|
+
this._stickToBottom = atBottom;
|
|
3222
|
+
}
|
|
3223
|
+
};
|
|
3224
|
+
this._resumeFollowing = () => {
|
|
3225
|
+
this._stickToBottom = true;
|
|
3226
|
+
this._scrollToBottom();
|
|
2956
3227
|
};
|
|
2957
3228
|
this._handleEvent = (event) => {
|
|
2958
3229
|
switch (event.type) {
|
|
2959
3230
|
case "session-ready":
|
|
2960
|
-
this._sessionId = event.sessionId;
|
|
2961
|
-
this._isConnected = true;
|
|
2962
3231
|
break;
|
|
2963
3232
|
case "messages-snapshot":
|
|
2964
3233
|
this._messages = [...event.messages];
|
|
2965
3234
|
this._isStreaming = event.messages.some((m2) => m2.status === "streaming");
|
|
3235
|
+
this._stickToBottom = true;
|
|
2966
3236
|
this._scrollToBottom();
|
|
2967
3237
|
break;
|
|
2968
3238
|
case "message-append":
|
|
@@ -2970,13 +3240,17 @@ var SyntroChat = class extends LitElement {
|
|
|
2970
3240
|
this._messages = [...this._messages, event.message];
|
|
2971
3241
|
}
|
|
2972
3242
|
this._isStreaming = event.message.status === "streaming";
|
|
2973
|
-
|
|
3243
|
+
if (event.message.role === "user") {
|
|
3244
|
+
this._anchorToMessage(event.message.id);
|
|
3245
|
+
} else {
|
|
3246
|
+
this._onContentGrowth();
|
|
3247
|
+
}
|
|
2974
3248
|
break;
|
|
2975
3249
|
case "message-delta": {
|
|
2976
3250
|
this._messages = this._messages.map(
|
|
2977
3251
|
(m2) => m2.id === event.messageId ? { ...m2, content: m2.content + event.delta } : m2
|
|
2978
3252
|
);
|
|
2979
|
-
this.
|
|
3253
|
+
this._onContentGrowth();
|
|
2980
3254
|
break;
|
|
2981
3255
|
}
|
|
2982
3256
|
case "message-complete":
|
|
@@ -2987,9 +3261,22 @@ var SyntroChat = class extends LitElement {
|
|
|
2987
3261
|
break;
|
|
2988
3262
|
case "typing":
|
|
2989
3263
|
this._isTyping = event.isTyping;
|
|
2990
|
-
if (event.isTyping) this.
|
|
3264
|
+
if (event.isTyping) this._onContentGrowth();
|
|
2991
3265
|
break;
|
|
2992
3266
|
case "tool-call": {
|
|
3267
|
+
const hasParent = this._messages.some((m2) => m2.id === event.messageId);
|
|
3268
|
+
if (!hasParent) {
|
|
3269
|
+
this._messages = [
|
|
3270
|
+
...this._messages,
|
|
3271
|
+
{
|
|
3272
|
+
id: event.messageId,
|
|
3273
|
+
role: "assistant",
|
|
3274
|
+
content: "",
|
|
3275
|
+
timestamp: Date.now(),
|
|
3276
|
+
status: "streaming"
|
|
3277
|
+
}
|
|
3278
|
+
];
|
|
3279
|
+
}
|
|
2993
3280
|
this._messages = this._messages.map((m2) => {
|
|
2994
3281
|
if (m2.id !== event.messageId) return m2;
|
|
2995
3282
|
const existing = (m2.toolCalls ?? []).findIndex((tc) => tc.id === event.toolCall.id);
|
|
@@ -3038,17 +3325,125 @@ var SyntroChat = class extends LitElement {
|
|
|
3038
3325
|
});
|
|
3039
3326
|
break;
|
|
3040
3327
|
}
|
|
3041
|
-
case "
|
|
3042
|
-
this.
|
|
3328
|
+
case "tool-call-deferred": {
|
|
3329
|
+
this._messages = this._messages.map((m2) => {
|
|
3330
|
+
if (!m2.toolCalls) return m2;
|
|
3331
|
+
const toolCalls = m2.toolCalls.map(
|
|
3332
|
+
(tc) => tc.id === event.toolCallId ? { ...tc, interruptId: event.interruptId, status: "pending" } : tc
|
|
3333
|
+
);
|
|
3334
|
+
return { ...m2, toolCalls };
|
|
3335
|
+
});
|
|
3336
|
+
break;
|
|
3337
|
+
}
|
|
3338
|
+
case "tool-call-cancelled": {
|
|
3339
|
+
this._messages = this._messages.map((m2) => {
|
|
3340
|
+
if (!m2.toolCalls) return m2;
|
|
3341
|
+
const toolCalls = m2.toolCalls.map(
|
|
3342
|
+
(tc) => tc.id === event.toolCallId ? { ...tc, status: "cancelled" } : tc
|
|
3343
|
+
);
|
|
3344
|
+
return { ...m2, toolCalls };
|
|
3345
|
+
});
|
|
3346
|
+
break;
|
|
3347
|
+
}
|
|
3348
|
+
case "tool-call-result": {
|
|
3349
|
+
let foundToolName;
|
|
3350
|
+
let lastIdx = -1;
|
|
3351
|
+
for (let i = this._messages.length - 1; i >= 0; i--) {
|
|
3352
|
+
if (this._messages[i].toolCalls?.some((tc) => tc.id === event.toolCallId)) {
|
|
3353
|
+
lastIdx = i;
|
|
3354
|
+
break;
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
this._messages = this._messages.map((m2, idx) => {
|
|
3358
|
+
if (idx !== lastIdx || !m2.toolCalls) return m2;
|
|
3359
|
+
const toolCalls = m2.toolCalls.map((tc) => {
|
|
3360
|
+
if (tc.id !== event.toolCallId) return tc;
|
|
3361
|
+
foundToolName = tc.name;
|
|
3362
|
+
const status = tc.status === "cancelled" ? "cancelled" : "done";
|
|
3363
|
+
return { ...tc, result: event.result, status };
|
|
3364
|
+
});
|
|
3365
|
+
return { ...m2, toolCalls };
|
|
3366
|
+
});
|
|
3367
|
+
this.dispatchEvent(
|
|
3368
|
+
new CustomEvent("tool-result-received", {
|
|
3369
|
+
detail: {
|
|
3370
|
+
toolCallId: event.toolCallId,
|
|
3371
|
+
toolName: foundToolName,
|
|
3372
|
+
result: event.result
|
|
3373
|
+
},
|
|
3374
|
+
bubbles: true,
|
|
3375
|
+
composed: true
|
|
3376
|
+
})
|
|
3377
|
+
);
|
|
3378
|
+
break;
|
|
3379
|
+
}
|
|
3380
|
+
case "error": {
|
|
3381
|
+
const isSoftTurnEnd = event.code === "USAGE_LIMIT";
|
|
3382
|
+
this._isStreaming = false;
|
|
3383
|
+
this._messages = this._messages.map((m2) => {
|
|
3384
|
+
const wasStreaming = m2.status === "streaming";
|
|
3385
|
+
const hasLiveTool = m2.toolCalls?.some(
|
|
3386
|
+
(tc) => tc.status === "args-streaming" || tc.status === "running"
|
|
3387
|
+
);
|
|
3388
|
+
if (!wasStreaming && !hasLiveTool) return m2;
|
|
3389
|
+
const toolCalls = hasLiveTool ? m2.toolCalls?.map((tc) => {
|
|
3390
|
+
if (tc.status !== "args-streaming" && tc.status !== "running") return tc;
|
|
3391
|
+
return isSoftTurnEnd ? { ...tc, status: "cancelled" } : { ...tc, status: "error", error: tc.error ?? event.message };
|
|
3392
|
+
}) : m2.toolCalls;
|
|
3393
|
+
const nextStatus = wasStreaming ? isSoftTurnEnd ? "complete" : "error" : m2.status;
|
|
3394
|
+
return { ...m2, status: nextStatus, toolCalls };
|
|
3395
|
+
});
|
|
3396
|
+
if (isSoftTurnEnd) {
|
|
3397
|
+
const id = `usage-limit-${this._messages.length}-${event.message.length}`;
|
|
3398
|
+
this._messages = [
|
|
3399
|
+
...this._messages,
|
|
3400
|
+
{
|
|
3401
|
+
id,
|
|
3402
|
+
role: "assistant",
|
|
3403
|
+
content: event.message,
|
|
3404
|
+
timestamp: Date.now(),
|
|
3405
|
+
status: "complete"
|
|
3406
|
+
}
|
|
3407
|
+
];
|
|
3408
|
+
} else {
|
|
3409
|
+
console.warn("[syntro-chat] run error:", event.message);
|
|
3410
|
+
this._errorMessage = "Hmm, that didn't go through. Please try again.";
|
|
3411
|
+
}
|
|
3043
3412
|
break;
|
|
3413
|
+
}
|
|
3044
3414
|
case "a2ui":
|
|
3045
3415
|
break;
|
|
3046
3416
|
}
|
|
3047
3417
|
};
|
|
3048
3418
|
this._onInput = (e) => {
|
|
3419
|
+
if (this._micListening) this._dictation?.stop();
|
|
3049
3420
|
this._inputValue = e.target.value;
|
|
3050
3421
|
};
|
|
3422
|
+
this._onMicClick = () => {
|
|
3423
|
+
if (this._micListening) {
|
|
3424
|
+
this._dictation?.stop();
|
|
3425
|
+
return;
|
|
3426
|
+
}
|
|
3427
|
+
this._micBase = this._inputValue;
|
|
3428
|
+
this._dictation = createDictation({
|
|
3429
|
+
onTranscript: (text2) => {
|
|
3430
|
+
this._micNetworkErrors = 0;
|
|
3431
|
+
this._inputValue = this._micBase + (this._micBase ? " " : "") + text2;
|
|
3432
|
+
},
|
|
3433
|
+
onEnd: () => {
|
|
3434
|
+
this._micListening = false;
|
|
3435
|
+
},
|
|
3436
|
+
onError: (code) => {
|
|
3437
|
+
this._micListening = false;
|
|
3438
|
+
if (code === "not-allowed") this._micDisabled = true;
|
|
3439
|
+
if (code === "network" && ++this._micNetworkErrors >= 2) this._micDisabled = true;
|
|
3440
|
+
}
|
|
3441
|
+
});
|
|
3442
|
+
this._dictation.start();
|
|
3443
|
+
this._micListening = this._dictation.listening;
|
|
3444
|
+
};
|
|
3051
3445
|
this._onSend = () => {
|
|
3446
|
+
if (this._micListening) this._dictation?.stop();
|
|
3052
3447
|
const text2 = this._inputValue.trim();
|
|
3053
3448
|
if (!text2) return;
|
|
3054
3449
|
this._appendUserMessage(text2);
|
|
@@ -3074,17 +3469,66 @@ var SyntroChat = class extends LitElement {
|
|
|
3074
3469
|
this._onRegenerate = (messageId) => {
|
|
3075
3470
|
this.transport.send({ type: "regenerate", messageId });
|
|
3076
3471
|
};
|
|
3077
|
-
this._onToolApprove = (toolCallId) => {
|
|
3472
|
+
this._onToolApprove = (toolCallId, payload, _retryCount = 0) => {
|
|
3473
|
+
const interruptId = this._findInterruptId(toolCallId);
|
|
3474
|
+
if (interruptId) {
|
|
3475
|
+
this._markToolDone(toolCallId);
|
|
3476
|
+
this.transport.send({
|
|
3477
|
+
type: "deferred-tool-resume",
|
|
3478
|
+
interruptId,
|
|
3479
|
+
decision: "approve",
|
|
3480
|
+
...payload?.editedArgs ? { editedArgs: payload.editedArgs } : {}
|
|
3481
|
+
});
|
|
3482
|
+
return;
|
|
3483
|
+
}
|
|
3484
|
+
if (this._isPendingClientTool(toolCallId)) {
|
|
3485
|
+
this._markToolDone(toolCallId);
|
|
3486
|
+
this.transport.send({
|
|
3487
|
+
type: "tool-result",
|
|
3488
|
+
toolCallId,
|
|
3489
|
+
result: payload?.editedArgs ?? null,
|
|
3490
|
+
approved: true
|
|
3491
|
+
});
|
|
3492
|
+
return;
|
|
3493
|
+
}
|
|
3494
|
+
if (_retryCount < 10) {
|
|
3495
|
+
setTimeout(() => this._onToolApprove(toolCallId, payload, _retryCount + 1), 200);
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3078
3498
|
this._markToolDone(toolCallId);
|
|
3079
3499
|
this.transport.send({
|
|
3080
3500
|
type: "tool-result",
|
|
3081
3501
|
toolCallId,
|
|
3082
|
-
result: null,
|
|
3502
|
+
result: payload?.editedArgs ?? null,
|
|
3083
3503
|
approved: true
|
|
3084
3504
|
});
|
|
3085
3505
|
};
|
|
3086
|
-
this._onToolDeny = (toolCallId) => {
|
|
3087
|
-
this.
|
|
3506
|
+
this._onToolDeny = (toolCallId, _retryCount = 0) => {
|
|
3507
|
+
const interruptId = this._findInterruptId(toolCallId);
|
|
3508
|
+
if (interruptId) {
|
|
3509
|
+
this._markToolCancelled(toolCallId);
|
|
3510
|
+
this.transport.send({
|
|
3511
|
+
type: "deferred-tool-resume",
|
|
3512
|
+
interruptId,
|
|
3513
|
+
decision: "deny"
|
|
3514
|
+
});
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
if (this._isPendingClientTool(toolCallId)) {
|
|
3518
|
+
this._markToolCancelled(toolCallId);
|
|
3519
|
+
this.transport.send({
|
|
3520
|
+
type: "tool-result",
|
|
3521
|
+
toolCallId,
|
|
3522
|
+
result: null,
|
|
3523
|
+
approved: false
|
|
3524
|
+
});
|
|
3525
|
+
return;
|
|
3526
|
+
}
|
|
3527
|
+
if (_retryCount < 10) {
|
|
3528
|
+
setTimeout(() => this._onToolDeny(toolCallId, _retryCount + 1), 200);
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
this._markToolCancelled(toolCallId);
|
|
3088
3532
|
this.transport.send({
|
|
3089
3533
|
type: "tool-result",
|
|
3090
3534
|
toolCallId,
|
|
@@ -3102,6 +3546,58 @@ var SyntroChat = class extends LitElement {
|
|
|
3102
3546
|
this.sendUserMessage(text2);
|
|
3103
3547
|
};
|
|
3104
3548
|
}
|
|
3549
|
+
// ── Public API: thread navigation ──────────────────────────────────
|
|
3550
|
+
//
|
|
3551
|
+
// Composing elements (the per-surface wrappers like
|
|
3552
|
+
// <ap-action-plan-chat>) call these to load past threads and start
|
|
3553
|
+
// new ones. The chat element doesn't own the thread index — that's
|
|
3554
|
+
// the consumer's job (typically a sibling <syntro-chat-thread-list>
|
|
3555
|
+
// + the consumer's REST endpoint). This element just owns the
|
|
3556
|
+
// visible message state.
|
|
3557
|
+
/**
|
|
3558
|
+
* Replace the chat's message history wholesale. Used by the host
|
|
3559
|
+
* shell when switching threads (after fetching the new thread's
|
|
3560
|
+
* messages from persistence) or when restoring a session.
|
|
3561
|
+
*
|
|
3562
|
+
* Emits `chat-messages-replaced` so dependent UI (overlay cards,
|
|
3563
|
+
* action plan sidebar) can react.
|
|
3564
|
+
*/
|
|
3565
|
+
replaceMessages(messages) {
|
|
3566
|
+
this._messages = [...messages];
|
|
3567
|
+
this._isStreaming = messages.some((m2) => m2.status === "streaming");
|
|
3568
|
+
this._stickToBottom = true;
|
|
3569
|
+
requestAnimationFrame(() => this._scrollToBottom());
|
|
3570
|
+
this.dispatchEvent(
|
|
3571
|
+
new CustomEvent("chat-messages-replaced", {
|
|
3572
|
+
detail: { messageCount: messages.length },
|
|
3573
|
+
bubbles: true,
|
|
3574
|
+
composed: true
|
|
3575
|
+
})
|
|
3576
|
+
);
|
|
3577
|
+
}
|
|
3578
|
+
/**
|
|
3579
|
+
* Clear the chat for a fresh conversation. Equivalent to
|
|
3580
|
+
* `replaceMessages([])` plus a state reset.
|
|
3581
|
+
*/
|
|
3582
|
+
reset() {
|
|
3583
|
+
this._messages = [];
|
|
3584
|
+
this._isStreaming = false;
|
|
3585
|
+
this._isTyping = false;
|
|
3586
|
+
this._inputValue = "";
|
|
3587
|
+
this._stickToBottom = true;
|
|
3588
|
+
this._messagesBelow = 0;
|
|
3589
|
+
this._errorMessage = null;
|
|
3590
|
+
}
|
|
3591
|
+
/**
|
|
3592
|
+
* Externally drive the typing indicator. Used by the surface's
|
|
3593
|
+
* run-state restore (BUG-1781288061): a remounted thread whose durable
|
|
3594
|
+
* producer run is still in flight shows the indicator and polls — the
|
|
3595
|
+
* transport's own 'typing' events only exist while the SSE stream lives.
|
|
3596
|
+
*/
|
|
3597
|
+
setTyping(isTyping) {
|
|
3598
|
+
this._isTyping = isTyping;
|
|
3599
|
+
if (isTyping) this._scrollToBottom();
|
|
3600
|
+
}
|
|
3105
3601
|
createRenderRoot() {
|
|
3106
3602
|
return this;
|
|
3107
3603
|
}
|
|
@@ -3118,11 +3614,29 @@ var SyntroChat = class extends LitElement {
|
|
|
3118
3614
|
this._unsubscribe?.();
|
|
3119
3615
|
this._unsubscribe = null;
|
|
3120
3616
|
this.transport?.disconnect();
|
|
3617
|
+
this._scrollContainer?.removeEventListener("scroll", this._handleScroll);
|
|
3618
|
+
this._scrollContainer?.removeEventListener("wheel", this._markUserScrollIntent);
|
|
3619
|
+
this._scrollContainer?.removeEventListener("touchmove", this._markUserScrollIntent);
|
|
3620
|
+
this._scrollContainer = null;
|
|
3121
3621
|
}
|
|
3122
3622
|
firstUpdated() {
|
|
3123
3623
|
this._ensureStylesInRoot();
|
|
3624
|
+
this._attachScrollListener();
|
|
3625
|
+
}
|
|
3626
|
+
/** Attach the scroll listener if not already attached. Called from both
|
|
3627
|
+
* firstUpdated (initial mount) and updated (re-mounts — Lit never
|
|
3628
|
+
* re-fires firstUpdated for the same element instance, and
|
|
3629
|
+
* disconnectedCallback tears the listener down). */
|
|
3630
|
+
_attachScrollListener() {
|
|
3631
|
+
if (this._scrollContainer) return;
|
|
3124
3632
|
this._scrollContainer = this.querySelector("[data-sc-scroll-area]");
|
|
3125
3633
|
this._scrollContainer?.addEventListener("scroll", this._handleScroll);
|
|
3634
|
+
this._scrollContainer?.addEventListener("wheel", this._markUserScrollIntent, {
|
|
3635
|
+
passive: true
|
|
3636
|
+
});
|
|
3637
|
+
this._scrollContainer?.addEventListener("touchmove", this._markUserScrollIntent, {
|
|
3638
|
+
passive: true
|
|
3639
|
+
});
|
|
3126
3640
|
}
|
|
3127
3641
|
/**
|
|
3128
3642
|
* Resolve the *effective* rendering root for this chat instance.
|
|
@@ -3157,6 +3671,7 @@ var SyntroChat = class extends LitElement {
|
|
|
3157
3671
|
injectChatStyles(this._resolveStyleRoot());
|
|
3158
3672
|
}
|
|
3159
3673
|
updated() {
|
|
3674
|
+
this._attachScrollListener();
|
|
3160
3675
|
this._injectCodeCopyButtons();
|
|
3161
3676
|
}
|
|
3162
3677
|
_injectCodeCopyButtons() {
|
|
@@ -3176,8 +3691,32 @@ var SyntroChat = class extends LitElement {
|
|
|
3176
3691
|
block.appendChild(btn);
|
|
3177
3692
|
}
|
|
3178
3693
|
}
|
|
3694
|
+
_isAtBottom() {
|
|
3695
|
+
if (!this._scrollContainer) return true;
|
|
3696
|
+
const { scrollTop, scrollHeight, clientHeight } = this._scrollContainer;
|
|
3697
|
+
return scrollHeight - scrollTop - clientHeight <= _SyntroChat2._BOTTOM_EPSILON_PX;
|
|
3698
|
+
}
|
|
3699
|
+
/** Count messages whose bottom edge hasn't entered the viewport yet.
|
|
3700
|
+
* At the bottom this is 0 (pill hidden); it decrements one-by-one as the
|
|
3701
|
+
* visitor scrolls down past each message. */
|
|
3702
|
+
_countMessagesBelow() {
|
|
3703
|
+
const container = this._scrollContainer;
|
|
3704
|
+
if (!container) return 0;
|
|
3705
|
+
if (this._isAtBottom()) return 0;
|
|
3706
|
+
const viewportBottom = container.getBoundingClientRect().top + container.clientHeight;
|
|
3707
|
+
let count = 0;
|
|
3708
|
+
for (const row of this.querySelectorAll("[data-sc-msg-row]")) {
|
|
3709
|
+
if (row.getBoundingClientRect().bottom > viewportBottom + 4) count += 1;
|
|
3710
|
+
}
|
|
3711
|
+
return count > 0 ? count : 1;
|
|
3712
|
+
}
|
|
3713
|
+
/** Recompute `_messagesBelow` from live geometry. Called on scroll AND on
|
|
3714
|
+
* content growth (deltas grow scrollHeight without a scroll event). */
|
|
3715
|
+
_syncScrollState() {
|
|
3716
|
+
this._messagesBelow = this._isAtBottom() ? 0 : this._countMessagesBelow();
|
|
3717
|
+
}
|
|
3179
3718
|
_scrollToBottom() {
|
|
3180
|
-
if (this.
|
|
3719
|
+
if (!this._stickToBottom) return;
|
|
3181
3720
|
requestAnimationFrame(() => {
|
|
3182
3721
|
if (this._scrollContainer?.scrollTo) {
|
|
3183
3722
|
this._scrollContainer.scrollTo({
|
|
@@ -3185,8 +3724,35 @@ var SyntroChat = class extends LitElement {
|
|
|
3185
3724
|
behavior: "smooth"
|
|
3186
3725
|
});
|
|
3187
3726
|
}
|
|
3727
|
+
this._syncScrollState();
|
|
3188
3728
|
});
|
|
3189
3729
|
}
|
|
3730
|
+
/**
|
|
3731
|
+
* Anchor the view so the given (just-sent) user message sits at the TOP of
|
|
3732
|
+
* the scroll area: the visitor sees their message, then reads the reply
|
|
3733
|
+
* top-down instead of chasing an auto-scrolling tail.
|
|
3734
|
+
*/
|
|
3735
|
+
_anchorToMessage(messageId) {
|
|
3736
|
+
this._stickToBottom = false;
|
|
3737
|
+
requestAnimationFrame(() => {
|
|
3738
|
+
const container = this._scrollContainer;
|
|
3739
|
+
if (!container?.scrollTo) return;
|
|
3740
|
+
const row = this.querySelector(`[data-sc-msg-id="${messageId}"]`);
|
|
3741
|
+
if (!row) return;
|
|
3742
|
+
const top = row.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
|
|
3743
|
+
container.scrollTo({ top, behavior: "smooth" });
|
|
3744
|
+
this._syncScrollState();
|
|
3745
|
+
});
|
|
3746
|
+
}
|
|
3747
|
+
/** Content grew (delta/typing/append while anchored): don't move the
|
|
3748
|
+
* view, but re-evaluate whether the pill should show. */
|
|
3749
|
+
_onContentGrowth() {
|
|
3750
|
+
if (this._stickToBottom) {
|
|
3751
|
+
this._scrollToBottom();
|
|
3752
|
+
return;
|
|
3753
|
+
}
|
|
3754
|
+
requestAnimationFrame(() => this._syncScrollState());
|
|
3755
|
+
}
|
|
3190
3756
|
_appendUserMessage(text2) {
|
|
3191
3757
|
const msg = {
|
|
3192
3758
|
id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
@@ -3196,14 +3762,34 @@ var SyntroChat = class extends LitElement {
|
|
|
3196
3762
|
status: "complete"
|
|
3197
3763
|
};
|
|
3198
3764
|
this._messages = [...this._messages, msg];
|
|
3199
|
-
this.
|
|
3765
|
+
this._anchorToMessage(msg.id);
|
|
3766
|
+
}
|
|
3767
|
+
_isPendingClientTool(toolCallId) {
|
|
3768
|
+
for (const m2 of this._messages) {
|
|
3769
|
+
const tc = m2.toolCalls?.find((t) => t.id === toolCallId);
|
|
3770
|
+
if (tc) return tc.status === "pending" && !tc.interruptId;
|
|
3771
|
+
}
|
|
3772
|
+
return false;
|
|
3773
|
+
}
|
|
3774
|
+
_findInterruptId(toolCallId) {
|
|
3775
|
+
for (const m2 of this._messages) {
|
|
3776
|
+
const tc = m2.toolCalls?.find((t) => t.id === toolCallId);
|
|
3777
|
+
if (tc?.interruptId) return tc.interruptId;
|
|
3778
|
+
}
|
|
3779
|
+
return void 0;
|
|
3200
3780
|
}
|
|
3201
3781
|
_markToolDone(toolCallId) {
|
|
3782
|
+
this._markToolStatus(toolCallId, "done");
|
|
3783
|
+
}
|
|
3784
|
+
/** Deny paths mark 'cancelled' — the frozen card must READ as rejected
|
|
3785
|
+
* ("Rejected" label), never as the done-phase "Approved". */
|
|
3786
|
+
_markToolCancelled(toolCallId) {
|
|
3787
|
+
this._markToolStatus(toolCallId, "cancelled");
|
|
3788
|
+
}
|
|
3789
|
+
_markToolStatus(toolCallId, status) {
|
|
3202
3790
|
this._messages = this._messages.map((m2) => {
|
|
3203
3791
|
if (!m2.toolCalls) return m2;
|
|
3204
|
-
const toolCalls = m2.toolCalls.map(
|
|
3205
|
-
(tc) => tc.id === toolCallId ? { ...tc, status: "done" } : tc
|
|
3206
|
-
);
|
|
3792
|
+
const toolCalls = m2.toolCalls.map((tc) => tc.id === toolCallId ? { ...tc, status } : tc);
|
|
3207
3793
|
return { ...m2, toolCalls };
|
|
3208
3794
|
});
|
|
3209
3795
|
}
|
|
@@ -3216,6 +3802,7 @@ var SyntroChat = class extends LitElement {
|
|
|
3216
3802
|
* duplicating logic.
|
|
3217
3803
|
*/
|
|
3218
3804
|
sendUserMessage(text2) {
|
|
3805
|
+
if (this._micListening) this._dictation?.stop();
|
|
3219
3806
|
const trimmed = text2.trim();
|
|
3220
3807
|
if (!trimmed) return;
|
|
3221
3808
|
this._appendUserMessage(trimmed);
|
|
@@ -3237,6 +3824,7 @@ var SyntroChat = class extends LitElement {
|
|
|
3237
3824
|
isTyping: this._isTyping,
|
|
3238
3825
|
toolLabels: this.toolLabels,
|
|
3239
3826
|
toolRenderers: this.toolRenderers,
|
|
3827
|
+
toolContext: this.toolContext,
|
|
3240
3828
|
defaultToolRenderer: this.defaultToolRenderer,
|
|
3241
3829
|
onCopy: this._onCopy,
|
|
3242
3830
|
onRegenerate: this._onRegenerate,
|
|
@@ -3246,10 +3834,7 @@ var SyntroChat = class extends LitElement {
|
|
|
3246
3834
|
typingIndicatorSlot: this.slots?.typingIndicator
|
|
3247
3835
|
})}
|
|
3248
3836
|
</div>
|
|
3249
|
-
${this.
|
|
3250
|
-
this._userScrolledUp = false;
|
|
3251
|
-
this._scrollToBottom();
|
|
3252
|
-
}} aria-label="Scroll to bottom"><svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button>` : nothing5}
|
|
3837
|
+
${this._messagesBelow > 0 ? html7`<button data-sc-scroll-btn @click=${this._resumeFollowing} aria-label="Scroll to most recent — ${this._messagesBelow} ${this._messagesBelow === 1 ? "message" : "messages"} below"><svg width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg><span>${this._messagesBelow} ${this._messagesBelow === 1 ? "message" : "messages"}</span></button>` : nothing5}
|
|
3253
3838
|
${this._errorMessage ? html7`<div data-sc-error role="alert">${this._errorMessage}</div>` : nothing5}
|
|
3254
3839
|
${renderChatInput({
|
|
3255
3840
|
value: this._inputValue,
|
|
@@ -3265,37 +3850,161 @@ var SyntroChat = class extends LitElement {
|
|
|
3265
3850
|
onSuggestion: this._onSuggestion,
|
|
3266
3851
|
inputActionsSlot: this.slots?.inputActions,
|
|
3267
3852
|
beforeInputSlot: this.slots?.beforeInput,
|
|
3268
|
-
|
|
3853
|
+
// Listening is a mode the visitor must be able to see AND exit:
|
|
3854
|
+
// the placeholder doubles as the tap-to-stop instruction.
|
|
3855
|
+
placeholder: this._micListening ? "Listening \u2014 tap the mic to stop" : this.placeholder,
|
|
3856
|
+
showMic: this.voiceInput && isDictationSupported(),
|
|
3857
|
+
micListening: this._micListening,
|
|
3858
|
+
micDisabled: this._micDisabled,
|
|
3859
|
+
onMicClick: this._onMicClick
|
|
3269
3860
|
})}
|
|
3270
3861
|
</div>
|
|
3271
3862
|
`;
|
|
3272
3863
|
}
|
|
3273
3864
|
};
|
|
3274
|
-
|
|
3865
|
+
_SyntroChat.properties = {
|
|
3275
3866
|
transport: { attribute: false },
|
|
3276
3867
|
greeting: { type: String },
|
|
3277
3868
|
suggestions: { attribute: false },
|
|
3278
3869
|
toolLabels: { attribute: false },
|
|
3279
3870
|
toolRenderers: { attribute: false },
|
|
3871
|
+
toolContext: { attribute: false },
|
|
3280
3872
|
defaultToolRenderer: { attribute: false },
|
|
3281
3873
|
autoExecTools: { attribute: false },
|
|
3282
3874
|
placeholder: { type: String },
|
|
3283
3875
|
slots: { attribute: false },
|
|
3876
|
+
voiceInput: { type: Boolean, attribute: "voice-input" },
|
|
3284
3877
|
_messages: { state: true },
|
|
3878
|
+
_micListening: { state: true },
|
|
3879
|
+
_micDisabled: { state: true },
|
|
3285
3880
|
_isTyping: { state: true },
|
|
3286
|
-
_isConnected: { state: true },
|
|
3287
|
-
_sessionId: { state: true },
|
|
3288
3881
|
_inputValue: { state: true },
|
|
3289
3882
|
_isComposing: { state: true },
|
|
3290
3883
|
_isStreaming: { state: true },
|
|
3291
|
-
|
|
3884
|
+
_messagesBelow: { state: true },
|
|
3292
3885
|
_errorMessage: { state: true }
|
|
3293
3886
|
};
|
|
3887
|
+
_SyntroChat._BOTTOM_EPSILON_PX = 50;
|
|
3888
|
+
_SyntroChat._USER_SCROLL_INTENT_WINDOW_MS = 1200;
|
|
3889
|
+
var SyntroChat = _SyntroChat;
|
|
3890
|
+
var SyntroChatThreadList = class extends LitElement2 {
|
|
3891
|
+
constructor() {
|
|
3892
|
+
super(...arguments);
|
|
3893
|
+
this.threadsEndpoint = "";
|
|
3894
|
+
this.activeThreadId = null;
|
|
3895
|
+
this.newThreadLabel = "New thread";
|
|
3896
|
+
this.emptyStateLabel = "No threads yet";
|
|
3897
|
+
this._threads = [];
|
|
3898
|
+
this._loading = false;
|
|
3899
|
+
this._error = null;
|
|
3900
|
+
this._onThreadClick = (threadId) => {
|
|
3901
|
+
if (threadId === this.activeThreadId) return;
|
|
3902
|
+
this.dispatchEvent(
|
|
3903
|
+
new CustomEvent("thread-selected", {
|
|
3904
|
+
detail: { threadId },
|
|
3905
|
+
bubbles: true,
|
|
3906
|
+
composed: true
|
|
3907
|
+
})
|
|
3908
|
+
);
|
|
3909
|
+
};
|
|
3910
|
+
}
|
|
3911
|
+
createRenderRoot() {
|
|
3912
|
+
return this;
|
|
3913
|
+
}
|
|
3914
|
+
connectedCallback() {
|
|
3915
|
+
super.connectedCallback();
|
|
3916
|
+
if (this.threadsEndpoint) void this.refresh();
|
|
3917
|
+
}
|
|
3918
|
+
updated(changed) {
|
|
3919
|
+
if (changed.has("threadsEndpoint") && this.threadsEndpoint) {
|
|
3920
|
+
void this.refresh();
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
/** Re-fetch the thread index. Call after creating/archiving a thread. */
|
|
3924
|
+
async refresh() {
|
|
3925
|
+
if (!this.threadsEndpoint) return;
|
|
3926
|
+
this._loading = true;
|
|
3927
|
+
this._error = null;
|
|
3928
|
+
try {
|
|
3929
|
+
const res = await fetch(this.threadsEndpoint, {
|
|
3930
|
+
method: "GET",
|
|
3931
|
+
headers: { ...this.authHeaders ?? {}, Accept: "application/json" }
|
|
3932
|
+
});
|
|
3933
|
+
if (!res.ok) {
|
|
3934
|
+
this._error = `Failed to load threads (${res.status})`;
|
|
3935
|
+
this._threads = [];
|
|
3936
|
+
return;
|
|
3937
|
+
}
|
|
3938
|
+
const data = await res.json();
|
|
3939
|
+
this._threads = (data.threads ?? []).filter((t) => t.status !== "archived");
|
|
3940
|
+
} catch (e) {
|
|
3941
|
+
this._error = e instanceof Error ? e.message : "Failed to load threads";
|
|
3942
|
+
this._threads = [];
|
|
3943
|
+
} finally {
|
|
3944
|
+
this._loading = false;
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
/** Imperative: request the host to start a new thread.
|
|
3948
|
+
* Fires `new-thread-requested`. The host clears the chat element and
|
|
3949
|
+
* drops `activeThreadId` so the next user message goes to a fresh
|
|
3950
|
+
* backend thread.
|
|
3951
|
+
*/
|
|
3952
|
+
createNewThread() {
|
|
3953
|
+
this.dispatchEvent(new CustomEvent("new-thread-requested", { bubbles: true, composed: true }));
|
|
3954
|
+
}
|
|
3955
|
+
render() {
|
|
3956
|
+
return html8`
|
|
3957
|
+
<div data-sc-thread-list>
|
|
3958
|
+
<button
|
|
3959
|
+
type="button"
|
|
3960
|
+
data-sc-new-thread-btn
|
|
3961
|
+
@click=${() => this.createNewThread()}
|
|
3962
|
+
aria-label=${this.newThreadLabel}
|
|
3963
|
+
>
|
|
3964
|
+
${this.newThreadLabel}
|
|
3965
|
+
</button>
|
|
3966
|
+
${this._loading ? html8`<div data-sc-thread-list-loading>Loading…</div>` : this._error ? html8`<div data-sc-thread-list-error>${this._error}</div>` : this._threads.length === 0 ? html8`<div data-sc-thread-list-empty>${this.emptyStateLabel}</div>` : html8`<ul data-sc-thread-list-items role="list">
|
|
3967
|
+
${this._threads.map(
|
|
3968
|
+
(t) => html8`
|
|
3969
|
+
<li
|
|
3970
|
+
data-sc-thread-list-item
|
|
3971
|
+
?data-sc-active=${t.remoteId === this.activeThreadId}
|
|
3972
|
+
>
|
|
3973
|
+
<button
|
|
3974
|
+
type="button"
|
|
3975
|
+
@click=${() => this._onThreadClick(t.remoteId)}
|
|
3976
|
+
title=${t.title ?? t.remoteId}
|
|
3977
|
+
>
|
|
3978
|
+
${t.title || "Untitled thread"}
|
|
3979
|
+
</button>
|
|
3980
|
+
</li>
|
|
3981
|
+
`
|
|
3982
|
+
)}
|
|
3983
|
+
</ul>`}
|
|
3984
|
+
</div>
|
|
3985
|
+
`;
|
|
3986
|
+
}
|
|
3987
|
+
};
|
|
3988
|
+
SyntroChatThreadList.properties = {
|
|
3989
|
+
threadsEndpoint: { type: String, attribute: "threads-endpoint" },
|
|
3990
|
+
authHeaders: { attribute: false },
|
|
3991
|
+
activeThreadId: { type: String, attribute: "active-thread-id" },
|
|
3992
|
+
newThreadLabel: { type: String, attribute: "new-thread-label" },
|
|
3993
|
+
emptyStateLabel: { type: String, attribute: "empty-state-label" },
|
|
3994
|
+
_threads: { state: true },
|
|
3995
|
+
_loading: { state: true },
|
|
3996
|
+
_error: { state: true }
|
|
3997
|
+
};
|
|
3998
|
+
if (!customElements.get("syntro-chat-thread-list")) {
|
|
3999
|
+
customElements.define("syntro-chat-thread-list", SyntroChatThreadList);
|
|
4000
|
+
}
|
|
3294
4001
|
if (!customElements.get("syntro-chat")) {
|
|
3295
4002
|
customElements.define("syntro-chat", SyntroChat);
|
|
3296
4003
|
}
|
|
3297
4004
|
|
|
3298
4005
|
export {
|
|
4006
|
+
isDictationSupported,
|
|
4007
|
+
createDictation,
|
|
3299
4008
|
renderMarkdown
|
|
3300
4009
|
};
|
|
3301
4010
|
/*! Bundled license information:
|
|
@@ -3303,4 +4012,4 @@ export {
|
|
|
3303
4012
|
dompurify/dist/purify.es.mjs:
|
|
3304
4013
|
(*! @license DOMPurify 3.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.1/LICENSE *)
|
|
3305
4014
|
*/
|
|
3306
|
-
//# sourceMappingURL=chunk-
|
|
4015
|
+
//# sourceMappingURL=chunk-VZIUXXAM.js.map
|