@adia-ai/web-modules 0.8.29 → 0.8.30
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/CHANGELOG.md +13 -0
- package/chat/README.md +54 -0
- package/chat/chat-shell/chat-shell.js +19 -0
- package/chat/chat-surfaces/chat-surfaces.d.ts +3 -0
- package/chat/chat-surfaces/chat-surfaces.js +116 -18
- package/dist/chat/chat-shell.min.js +1 -1
- package/dist/everything.min.js +82 -82
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog — @adia-ai/web-modules
|
|
2
2
|
|
|
3
|
+
## [0.8.30] — 2026-08-07
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **`chatShell.setStreamedText(text)`** — replaces the streaming assistant message's text outright, the counterpart to `appendChunk` for a producer that revises what it already streamed.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- **A rewritten or withheld turn now leaves the screen agreeing with the transcript (gh#665).** `wireAgentEvents` treated every `text` event as an append, so an `@adia-ai/agent` output guardrail's re-snapshot (`{ text: '', snapshot: <rewritten> }`) appended nothing and the ORIGINAL text stayed on screen while the session recorded the rewrite — a redaction that redacted nothing visible. An empty delta is now a replace from `snapshot` (an empty snapshot clears the bubble), which is safe for any text event since the snapshot is always the authoritative text.
|
|
10
|
+
|
|
11
|
+
### Maintenance
|
|
12
|
+
- **`billing/` touched in this release window** (1 file(s), e.g. `invoice-history/invoice-history.class.js`) — carried by the entries above.
|
|
13
|
+
- **`chat/` touched in this release window** (5 file(s), e.g. `chat/README.md`) — carried by the entries above.
|
|
14
|
+
- **`dist/` bundles rebuilt** in this cut's window (2 file(s)) — regenerated from the source changes described above, not independent edits.
|
|
15
|
+
|
|
3
16
|
## [0.8.29] — 2026-08-06
|
|
4
17
|
|
|
5
18
|
### Added
|
package/chat/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# `web-modules/chat`
|
|
2
|
+
|
|
3
|
+
The chat cluster: `<chat-shell>` (behavior-only orchestrator) composing
|
|
4
|
+
`<chat-header>` / `<chat-thread>` / `<chat-composer>` / `<chat-status>` /
|
|
5
|
+
`<chat-empty>`, plus `chat-surfaces.js` — the L3 seam that renders an
|
|
6
|
+
`@adia-ai/agent` `AgentEvent` stream (or a raw `@adia-ai/llm` chunk stream)
|
|
7
|
+
into the thread. See `chat-shell/chat-shell.js`'s header comment for the
|
|
8
|
+
bespoke structure vocabulary and the two operating modes (built-in LLM
|
|
9
|
+
streaming vs. external-drive).
|
|
10
|
+
|
|
11
|
+
## `wireAgentEvents` — AgentEvent → rendering (gh#667)
|
|
12
|
+
|
|
13
|
+
`wireAgentEvents(chatShell, registry, { events, statusEl? })` folds ONE
|
|
14
|
+
`AsyncIterable<AgentEvent>` (`@adia-ai/agent`'s stream contract — see
|
|
15
|
+
[`packages/agent/README.md`](../../agent/README.md#events)) into the thread.
|
|
16
|
+
It never touches session state — `reduce(session, event)` is the caller's
|
|
17
|
+
job, the same event object just also flows through this function to paint.
|
|
18
|
+
|
|
19
|
+
| Event | Rendering |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| `text` | Appended to the streaming bubble (`chatShell.appendChunk`); an EMPTY delta REPLACES from `snapshot` (`chatShell.setStreamedText`) — the guardrail-rewrite case (see `@adia-ai/agent`'s README, "Output rewrite"). |
|
|
22
|
+
| `thinking` | `<agent-reasoning-ui>.addThought(text)` — folded into the turn's collapsed-by-default reasoning viewer. |
|
|
23
|
+
| `tool_use` | `<agent-reasoning-ui>.addStep({id, label})` (or `updateStep` if `tool_input_delta` already opened the step) — a running step in the reasoning viewer's timeline. |
|
|
24
|
+
| `tool_input_delta` | Cheap upsert on the running step's `label` (`updateStep`, or `addStep` if this is the first fragment) — progress only, nothing executes off a partial. |
|
|
25
|
+
| `tool_result` | `updateStep(id, {outcomes:[output]})` + `completeStep(id)`, or `failStep(id, output)` when `isError` — the step's status icon flips to done/error and the (truncated) output becomes the step's collapsible outcome. |
|
|
26
|
+
| `step` (`workflow: 'plan'`) | Feed-chrome prose via `describePlanManifest` / `describeTurnSynthesis` (gh#648) — unchanged, NOT routed through the reasoning viewer (a plan manifest/synthesis line is thread narration, not a pipeline step). |
|
|
27
|
+
| `step` (any other workflow) | One-shot attempt marker — `@adia-ai/agent`'s workflow loop emits this AFTER the step already ran, so it lands as an immediately-settled `addStep` + `completeStep`/`failStep` in the reasoning viewer. |
|
|
28
|
+
| `guardrail` | `addThought('Guardrail "<name>" <blocked\|rewrote> <target> — <reason>')` — a subtle note inside the (collapsed) reasoning viewer, never a thread interruption. |
|
|
29
|
+
| `progress` | `statusEl.textContent` from the code-owned `PROGRESS_LABELS` table (CHAT-HARNESS law 3 — never the raw `stage` string, never model text). |
|
|
30
|
+
| `surface` | Routed to the surface-host `registry` (WCH-4 / gh#599) — a generative-UI envelope line, mounted/updated/closed via `createSurfaceRegistry`. |
|
|
31
|
+
| `memory` | Render-silent — a session-state write, not a thread event. |
|
|
32
|
+
| `trace` | Render-silent by default — tracing is devtools' surface (a future inspector panel reads the `trace` event / `@adia-ai/agent`'s `trace` sink), never the thread. |
|
|
33
|
+
| `error` | `reasoning.fail(message)` (if a reasoning viewer is open) + an error bubble + `stopStreaming()`. |
|
|
34
|
+
| `done` | `reasoning.finish()` (or `.fail('Blocked by guardrail')` when `stopReason === 'guardrail_denied'`) + `stopStreaming()`. |
|
|
35
|
+
|
|
36
|
+
**Why `<agent-reasoning-ui>`, not a new primitive:** the primitive audit
|
|
37
|
+
(adia-author §0) found `agent-reasoning-ui` already built for exactly this
|
|
38
|
+
shape — "Agent inner monologue + pipeline viewer with steps, thoughts,
|
|
39
|
+
plans, and iterations," composing `<timeline-ui>` for step rows with a
|
|
40
|
+
collapsible per-step `outcomes` list (the "collapsible tool-result body"
|
|
41
|
+
requirement) and a `collapsed`/status-icon summary row (the "thinking
|
|
42
|
+
affordance" requirement). One viewer, mounted lazily above the streamed
|
|
43
|
+
answer on the first reasoning-worthy event per turn, covers tool activity,
|
|
44
|
+
workflow-step markers, thinking, and guardrail notes — no chip primitive,
|
|
45
|
+
no dedicated step-marker element, and no second collapsible region were
|
|
46
|
+
minted. `<chat-status>` (candidate considered) stays scoped to the header's
|
|
47
|
+
connection/streaming indicator — it is a single, module-scoped element, not
|
|
48
|
+
shaped for a per-event marker stream.
|
|
49
|
+
|
|
50
|
+
**Demo:** `playgrounds/agent-chat/app/agent-chat.contents.js` — the *"Ask
|
|
51
|
+
about a component"* prompt path drives a real `createAgent()` loop with a
|
|
52
|
+
keyless deterministic client (`simulatedClient()`, used whenever
|
|
53
|
+
`npm run proxy` isn't up) through `tool_use` → `tool_result` → final text,
|
|
54
|
+
rendered live via this module's `wireAgentEvents`.
|
|
@@ -184,6 +184,25 @@ class ChatShell extends UIElement {
|
|
|
184
184
|
this.#scrollToBottom();
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Replace the streaming assistant message's text outright, instead of
|
|
189
|
+
* appending to it. The counterpart to `appendChunk` for the case where a
|
|
190
|
+
* producer revises what it already streamed — an `@adia-ai/agent` output
|
|
191
|
+
* guardrail rewriting or withholding the turn's text is the real one.
|
|
192
|
+
* Appending cannot express that: what is on screen has to come off.
|
|
193
|
+
*
|
|
194
|
+
* @param {string} text — the full replacement text ('' clears the bubble).
|
|
195
|
+
*/
|
|
196
|
+
setStreamedText(text) {
|
|
197
|
+
const last = this.#messages[this.#messages.length - 1];
|
|
198
|
+
if (!last || last.role !== 'assistant') return;
|
|
199
|
+
last.content = text;
|
|
200
|
+
|
|
201
|
+
const contentEl = this.#messagesEl?.querySelector('[data-role]:last-child [data-content]');
|
|
202
|
+
if (contentEl) contentEl.textContent = text;
|
|
203
|
+
this.#scrollToBottom();
|
|
204
|
+
}
|
|
205
|
+
|
|
187
206
|
deleteMessage(id) {
|
|
188
207
|
const idx = this.#messages.findIndex(m => m.id === id);
|
|
189
208
|
if (idx === -1) return;
|
|
@@ -58,6 +58,9 @@ export function describeTurnSynthesis(synthesis: {
|
|
|
58
58
|
export function wireAgentEvents(
|
|
59
59
|
chatShell: Element & {
|
|
60
60
|
appendChunk?(text: string): void;
|
|
61
|
+
/** Replace the streaming text outright — used when a text event carries
|
|
62
|
+
* an empty delta, which is a producer revising what it streamed. */
|
|
63
|
+
setStreamedText?(text: string): void;
|
|
61
64
|
appendMessage?(msg: { role: string; content?: string; render?: boolean }): Element;
|
|
62
65
|
startStreaming?(): void;
|
|
63
66
|
stopStreaming?(): void;
|
|
@@ -24,7 +24,22 @@
|
|
|
24
24
|
* specifier. The HOST (a demo page, a consumer app) builds the renderer
|
|
25
25
|
* (`createRenderer` + its own `WidgetAdapter`) and hands it in; this module
|
|
26
26
|
* only ever calls the five methods `Renderer` exposes.
|
|
27
|
+
*
|
|
28
|
+
* `@adia-ai/web-components` primitives ARE a declared dependency edge
|
|
29
|
+
* (unlike `@genui/*`), so `wireAgentEvents` imports `<agent-reasoning-ui>`
|
|
30
|
+
* directly (gh#667) — the "agent inner monologue + pipeline viewer with
|
|
31
|
+
* steps, thoughts, plans, and iterations" primitive already built for
|
|
32
|
+
* exactly this shape (primitive audit, adia-author §0 — no new primitive
|
|
33
|
+
* was minted). One reasoning viewer is lazily mounted per assistant turn
|
|
34
|
+
* and renders `thinking` (→ `addThought`), `tool_use`/`tool_input_delta`/
|
|
35
|
+
* `tool_result` (→ `addStep`/`updateStep`/`completeStep`/`failStep`, with
|
|
36
|
+
* the tool output riding the step's collapsible `outcomes`), non-'plan'
|
|
37
|
+
* `step` events (→ the same step API — one-shot attempt markers), and
|
|
38
|
+
* `guardrail` (→ `addThought`, a subtle note). `memory` and `trace` stay
|
|
39
|
+
* render-silent (trace is devtools' surface, not the thread's — see the
|
|
40
|
+
* event → rendering table in `packages/web-modules/chat/README.md`).
|
|
27
41
|
*/
|
|
42
|
+
import '../../../web-components/components/agent-reasoning/agent-reasoning.js';
|
|
28
43
|
|
|
29
44
|
const CLOSED_ANNOTATION_TEXT = 'Closed.';
|
|
30
45
|
const PROTOCOL_VERSION = 'v1.0';
|
|
@@ -220,10 +235,40 @@ export async function wireAgentEvents(chatShell, registry, opts) {
|
|
|
220
235
|
return thread?.querySelector('[data-role="assistant"]:last-child [data-bubble]') ?? null;
|
|
221
236
|
};
|
|
222
237
|
|
|
238
|
+
// One <agent-reasoning-ui> per turn (gh#667) — lazily mounted above the
|
|
239
|
+
// streamed answer on the first reasoning-worthy event (thinking, tool
|
|
240
|
+
// activity, a non-plan step, or a guardrail note), and re-used for the
|
|
241
|
+
// rest of the turn so tool steps/thoughts accumulate in ONE collapsible
|
|
242
|
+
// region instead of one element per event. `null` until then; a fresh
|
|
243
|
+
// `wireAgentEvents` call (one per turn, per this function's own contract
|
|
244
|
+
// above) starts a fresh viewer.
|
|
245
|
+
let reasoning = null;
|
|
246
|
+
const ensureReasoning = () => {
|
|
247
|
+
if (reasoning) return reasoning;
|
|
248
|
+
const bubble = currentBubble();
|
|
249
|
+
if (!bubble) return null;
|
|
250
|
+
reasoning = document.createElement('agent-reasoning-ui');
|
|
251
|
+
// Collapsed BY DEFAULT (gh#667 scope — thinking is a collapsed-by-default
|
|
252
|
+
// affordance, not a start-expanded one; the primitive's own default is
|
|
253
|
+
// expanded, so this is set explicitly).
|
|
254
|
+
reasoning.collapsed = true;
|
|
255
|
+
// Above the streamed text — the "showed its work, then answered" order.
|
|
256
|
+
bubble.insertBefore(reasoning, bubble.firstChild);
|
|
257
|
+
return reasoning;
|
|
258
|
+
};
|
|
259
|
+
const findStepEntry = (id) => reasoning?.entries?.find((e) => e.kind === 'step' && e.id === id);
|
|
260
|
+
|
|
223
261
|
for await (const event of events) {
|
|
224
262
|
switch (event.type) {
|
|
225
263
|
case 'text':
|
|
226
|
-
|
|
264
|
+
// An EMPTY delta is a replace, not a no-op: the snapshot is the
|
|
265
|
+
// authoritative text, and a producer sends this shape when it
|
|
266
|
+
// revises what it already streamed (an @adia-ai/agent output
|
|
267
|
+
// guardrail rewriting or withholding the turn). Resetting from the
|
|
268
|
+
// snapshot is safe for any text event — appending is only the
|
|
269
|
+
// cheaper path for the ordinary incremental case.
|
|
270
|
+
if (event.text === '') chatShell.setStreamedText?.(event.snapshot ?? '');
|
|
271
|
+
else chatShell.appendChunk?.(event.text);
|
|
227
272
|
break;
|
|
228
273
|
|
|
229
274
|
case 'progress': {
|
|
@@ -240,43 +285,96 @@ export async function wireAgentEvents(chatShell, registry, opts) {
|
|
|
240
285
|
|
|
241
286
|
case 'step': {
|
|
242
287
|
// Plan-envelope chrome (gh#648, ruling 1 — harness-level progress,
|
|
243
|
-
// never wire; ruling 3 — feed chrome, never a canvas)
|
|
244
|
-
//
|
|
245
|
-
//
|
|
288
|
+
// never wire; ruling 3 — feed chrome, never a canvas) keeps its own
|
|
289
|
+
// rendering — a manifest/synthesis summary is feed prose, not a
|
|
290
|
+
// pipeline step. Every OTHER workflow's step is a one-shot attempt
|
|
291
|
+
// marker (`@adia-ai/agent`'s workflow.ts emits it once, after the
|
|
292
|
+
// step already ran) — rendered as an immediately-settled step in
|
|
293
|
+
// the turn's reasoning viewer (gh#667).
|
|
246
294
|
if (event.workflow === 'plan' && event.step === 'manifest') {
|
|
247
295
|
chatShell.appendMessage?.({ role: 'assistant', content: describePlanManifest(event.data), render: true });
|
|
248
296
|
chatShell.appendMessage?.({ role: 'assistant', content: '' });
|
|
249
297
|
} else if (event.workflow === 'plan' && event.step === 'synthesis') {
|
|
250
298
|
chatShell.appendMessage?.({ role: 'assistant', content: describeTurnSynthesis(event.data), render: true });
|
|
251
299
|
chatShell.appendMessage?.({ role: 'assistant', content: '' });
|
|
300
|
+
} else {
|
|
301
|
+
const r = ensureReasoning();
|
|
302
|
+
if (r) {
|
|
303
|
+
const id = `${event.workflow}:${event.step}:${event.data?.round ?? 1}`;
|
|
304
|
+
const label = `${event.workflow}: ${event.step}`;
|
|
305
|
+
r.addStep({ id, label });
|
|
306
|
+
if (event.data?.accepted) r.completeStep(id);
|
|
307
|
+
else r.failStep(id, event.data?.reason ?? 'fell through');
|
|
308
|
+
}
|
|
252
309
|
}
|
|
253
310
|
break;
|
|
254
311
|
}
|
|
255
312
|
|
|
256
|
-
case 'tool_use':
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
313
|
+
case 'tool_use': {
|
|
314
|
+
const r = ensureReasoning();
|
|
315
|
+
if (r) {
|
|
316
|
+
const label = `${event.name}(${JSON.stringify(event.input)})`;
|
|
317
|
+
if (findStepEntry(event.id)) r.updateStep(event.id, { label });
|
|
318
|
+
else r.addStep({ id: event.id, label });
|
|
319
|
+
}
|
|
263
320
|
break;
|
|
321
|
+
}
|
|
264
322
|
|
|
265
|
-
case '
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
323
|
+
case 'tool_input_delta': {
|
|
324
|
+
// Partial args — render progress on the running chip only; never
|
|
325
|
+
// executes off it (README's own rule — the matching tool_use is
|
|
326
|
+
// the call). Cheap upsert since addStep/updateStep already exist.
|
|
327
|
+
const r = ensureReasoning();
|
|
328
|
+
if (r) {
|
|
329
|
+
const label = `${event.name}(${event.partial})`;
|
|
330
|
+
if (findStepEntry(event.id)) r.updateStep(event.id, { label });
|
|
331
|
+
else r.addStep({ id: event.id, label });
|
|
332
|
+
}
|
|
272
333
|
break;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
case 'tool_result': {
|
|
337
|
+
const r = ensureReasoning();
|
|
338
|
+
if (r) {
|
|
339
|
+
const outcome = String(event.output).slice(0, 300);
|
|
340
|
+
if (event.isError) {
|
|
341
|
+
r.failStep(event.id, outcome);
|
|
342
|
+
} else {
|
|
343
|
+
r.updateStep(event.id, { outcomes: [outcome] });
|
|
344
|
+
r.completeStep(event.id);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
case 'guardrail': {
|
|
351
|
+
const r = ensureReasoning();
|
|
352
|
+
const verb = event.action === 'deny' ? 'blocked' : 'rewrote';
|
|
353
|
+
const target = event.toolName ? `tool call "${event.toolName}"` : 'the response';
|
|
354
|
+
r?.addThought?.(`Guardrail "${event.name}" ${verb} ${target}${event.reason ? ` — ${event.reason}` : ''}`);
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
case 'thinking': {
|
|
359
|
+
const r = ensureReasoning();
|
|
360
|
+
r?.addThought?.(event.text);
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 'memory' and 'trace' stay render-silent (see this module's header
|
|
365
|
+
// comment + the chat README's event table) — they fall to `default`.
|
|
273
366
|
|
|
274
367
|
case 'error':
|
|
368
|
+
reasoning?.fail?.(event.error?.message ?? String(event.error));
|
|
275
369
|
chatShell.appendMessage?.({ role: 'error', content: event.error?.message ?? String(event.error) });
|
|
276
370
|
chatShell.stopStreaming?.();
|
|
277
371
|
break;
|
|
278
372
|
|
|
279
373
|
case 'done':
|
|
374
|
+
if (reasoning) {
|
|
375
|
+
if (event.stopReason === 'guardrail_denied') reasoning.fail('Blocked by guardrail');
|
|
376
|
+
else reasoning.finish();
|
|
377
|
+
}
|
|
280
378
|
chatShell.stopStreaming?.();
|
|
281
379
|
break;
|
|
282
380
|
|
|
@@ -47,4 +47,4 @@ var RS=Object.defineProperty;var ee=(i,e,t)=>()=>{if(t)throw t[0];try{return i&&
|
|
|
47
47
|
`):t}async#f(){if(this.#o||this.#e)return;let e=this.#i,t=Zu(this.language);this.#o=!0;try{let n=await Promise.resolve().then(()=>(Au(),Xu)),r=null;try{r=(await n.importWithTimeout(n.languages[t],`lang-${t}`))?.extension??null}catch(s){this.dispatchEvent(new CustomEvent("language-load-error",{bubbles:!0,detail:{phase:"language",language:t,error:s}}))}if(e!==this.#i||!this.isConnected)return;this.#O(n,r,t)}catch(n){this.dispatchEvent(new CustomEvent("language-load-error",{bubbles:!0,detail:{phase:"core",error:n}})),console.warn("[code-ui] CodeMirror failed to load; staying on static fallback.",n)}finally{this.#o=!1}}#O(e,t,n){let{EditorState:r,EditorView:s,Compartment:o,lineNumbers:l,placeholder:a,syntaxHighlighting:h,history:c,historyKeymap:f,defaultKeymap:u,indentWithTab:d,keymap:O,adiaBaseTheme:m,adiaHighlightStyle:g,lintGutter:b,jsonLinter:S}=e,y=this.querySelector(":scope > pre"),C=y?y.querySelector("code")?.textContent??"":"",x=this.text||C||"";this.#l=x;let k=document.createElement("div");k.setAttribute("data-cm-mount",""),y?y.replaceWith(k):this.appendChild(k);let w=[m,h(g)];this.editable&&w.push(c(),O.of([{key:"Mod-s",run:E=>(this.#g(E),!0)},...u,...f,d]),s.updateListener.of(E=>{if(!E.docChanged)return;let j=E.state.doc.toString();this.#d(j),this.dispatchEvent(new CustomEvent("input",{bubbles:!0,detail:{value:j}}))}),s.domEventHandlers({focus:(E,j)=>(this.#n=j.state.doc.toString(),!1),blur:(E,j)=>{let N=j.state.doc.toString();return this.#n!==null&&N!==this.#n&&this.dispatchEvent(new CustomEvent("change",{bubbles:!0,detail:{value:N}})),this.#n=null,!1}})),this.#s=new o,w.push(this.#s.of(this.#u(s,r))),t&&w.push(t),this.lineNumbers&&w.push(l()),this.placeholder&&w.push(a(this.placeholder)),this.editable&&n==="json"&&w.push(S,b()),this.#e=new s({parent:k,state:r.create({doc:x,extensions:w})}),this.editable&&this.#d(x)}#u(e,t){return this.editable&&!this.disabled&&!this.readonly?[]:[e.editable.of(!1),t.readOnly.of(!0)]}async#p(){if(!this.#e||!this.#s)return;let e=await Promise.resolve().then(()=>(Au(),Xu));this.#e.dispatch({effects:this.#s.reconfigure(this.#u(e.EditorView,e.EditorState))})}get form(){return this.internals.form}get labels(){return this.internals.labels}get validity(){return this.internals.validity}get validationMessage(){return this.internals.validationMessage}get willValidate(){return this.internals.willValidate}checkValidity(){return this.internals.checkValidity()}reportValidity(){return this.internals.reportValidity()}#d(e){this.editable&&(e=e??this.value??"",this.internals.setFormValue(e),this.#m(e))}#m(e){return e=e??"",this.required&&!e.trim()?(this.internals.setValidity({valueMissing:!0},this.getAttribute("data-msg-required")||"This field is required.",this),this.setAttribute("aria-invalid","true"),!1):(this.internals.setValidity({}),this.removeAttribute("aria-invalid"),!0)}formResetCallback(){this.#e?this.#e.dispatch({changes:{from:0,to:this.#e.state.doc.length,insert:this.#l}}):this.text=this.#l}formDisabledCallback(e){this.disabled=e}formStateRestoreCallback(e,t){typeof e=="string"&&(this.#e?this.#e.dispatch({changes:{from:0,to:this.#e.state.doc.length,insert:e}}):this.text=e)}#g(e){this.dispatchEvent(new CustomEvent("save",{bubbles:!0,cancelable:!0,detail:{value:e.state.doc.toString()}}))}get value(){return this.#e?this.#e.state.doc.toString():this.querySelector(":scope > pre > code")?.textContent??this.text??""}set value(e){this.text=String(e??"")}#b(){let e=this.#e?this.#e.state.doc.toString():this.querySelector(":scope > pre > code")?.textContent??"";e&&navigator.clipboard.writeText(e).then(()=>{let t=this.querySelector(':scope > header [slot="copy"]');if(!t)return;let n=t.textContent;t.textContent="Copied!",this.#r!=null&&clearTimeout(this.#r),this.#r=setTimeout(()=>{this.#r=null,t.textContent=n},1500)})}render(){let e=this.querySelector(':scope > header [slot="label"]');if(e&&this.language&&(e.textContent=this.language),this.#p(),!this.inline&&this.text!=="")if(this.#e){if(this.editable&&this.#e.hasFocus)return;let t=this.#e.state.doc.toString();t!==this.text&&this.#e.dispatch({changes:{from:0,to:t.length,insert:this.text}})}else{let t=this.querySelector(":scope > pre > code");t&&t.textContent!==this.text&&(t.textContent=this.text)}}};is("code-ui",es);function Ru(i){return i.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}var Dv=0,jl=class extends an{static properties={streaming:{type:Boolean,default:!1,reflect:!0},provider:{type:String,default:"",reflect:!0},model:{type:String,default:"",reflect:!0},system:{type:String,default:"",reflect:!0},proxyUrl:{type:String,default:"",attribute:"proxy-url",reflect:!0},thinking:{type:Boolean,default:!1,reflect:!0}};static template=()=>null;#t=[];#r=null;#e=null;#i=null;#o=null;#n=null;#l="";get messages(){return[...this.#t]}get conversation(){return this.#t.filter(e=>e.role==="user"||e.role==="assistant").map(e=>e.html?{role:e.role,content:e.content,html:!0}:{role:e.role,content:e.content})}set conversation(e){this.clear();for(let t of e)this.appendMessage({role:t.role,content:t.content,render:!t.html,html:!!t.html})}set apiKey(e){this.#l=e}connected(){this.#e=this.querySelector("chat-thread"),this.#i=this.querySelector("chat-composer"),this.#o=this.querySelector("chat-empty"),this.#n=this.querySelector("chat-status"),this.#i?.addEventListener("composer-submit",this.#a)}disconnected(){this.#i?.removeEventListener("composer-submit",this.#a),this.abort()}#s(e,t){this.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:t}))}#a=e=>{if(this.streaming)return;let{text:t,model:n}=e.detail||{};t&&(this.#i.clear(),this.#s("submit",{text:t,model:n}),(this.proxyUrl||this.#l)&&this.send(t,n?{model:n}:{}))};appendMessage({role:e,content:t="",render:n=!1,html:r=!1}){let s=`msg_${++Dv}`;this.#t.push({id:s,role:e,content:t,html:r});let o=document.createElement("div");if(o.setAttribute("data-role",e),o.setAttribute("data-id",s),e==="user")o.innerHTML=`<div data-bubble>${Ru(t)}</div>`;else if(e==="assistant"){let l=n||r,a=r?t:n&&t?ql(t):Ru(t);o.innerHTML=`
|
|
48
48
|
<span data-avatar>AI</span>
|
|
49
49
|
<div data-bubble><div data-content>${a}</div>${l?"":"<span data-cursor></span>"}</div>
|
|
50
|
-
`}else e==="error"&&(o.innerHTML=`<icon-ui name="warning"></icon-ui><span>${Ru(t)}</span>`);return this.#e?.appendChild(o),this.#h(),this.#s("message",{id:s,role:e,content:t}),o}appendChunk(e){let t=this.#t[this.#t.length-1];if(!t||t.role!=="assistant")return;t.content+=e;let n=this.#e?.querySelector("[data-role]:last-child [data-content]");n&&n.insertAdjacentText("beforeend",e),this.#h()}deleteMessage(e){let t=this.#t.findIndex(n=>n.id===e);t!==-1&&(this.#t.splice(t,1),this.#e?.querySelector(`[data-id="${e}"]`)?.remove())}clear(){if(this.#t.length=0,this.#e){let e=this.#o;this.#e.innerHTML="",e&&this.#e.appendChild(e)}this.#s("clear")}startStreaming(){this.streaming=!0,this.#i&&(this.#i.disabled=!0),this.#n&&(this.#n.textContent="Typing..."),this.#e?.tagName?.toLowerCase()==="chat-thread"&&(this.#e.streaming=!0)}stopStreaming(){this.streaming=!1,this.#i&&(this.#i.disabled=!1),this.#n&&(this.#n.textContent=""),this.#e?.tagName?.toLowerCase()==="chat-thread"&&(this.#e.streaming=!1),this.#e?.querySelector("[data-role]:last-child [data-cursor]")?.remove();let e=this.#t[this.#t.length-1];if(e?.role==="assistant"&&e.content&&!e.html){let t=this.#e?.querySelector("[data-role]:last-child [data-content]");t&&(t.innerHTML=ql(e.content),this.#c(t))}this.#i?.focus()}abort(){this.#r&&(this.#r.abort(),this.#r=null,this.#s("abort")),this.streaming&&this.stopStreaming()}async send(e,t={}){let n=t.model||this.model||this.#i?.model;if(!n)throw new Error("No model specified");this.appendMessage({role:"user",content:e}),this.appendMessage({role:"assistant",content:""}),this.startStreaming(),this.#r=new AbortController;try{let r={provider:this.provider||void 0,apiKey:this.#l||void 0,model:n,system:this.system||void 0,proxyUrl:this.proxyUrl||void 0,thinking:this.thinking||void 0,messages:this.conversation.slice(0,-1),signal:this.#r.signal,...t},s;for(let o of["../../../llm/index.js","../../llm/index.js"])try{({streamChat:s}=await import(new URL(o,import.meta.url).href));break}catch{}if(!s){this.appendMessage({role:"error",content:"@adia-ai/llm is not available \u2014 in a fresh checkout run the packages/llm build (see gh#527/gh#541)."}),this.#s("error",{error:new Error("llm build artifact missing")});return}for await(let o of s(r))o.type==="text"?(this.appendChunk(o.text),this.#s("chunk",{text:o.text,snapshot:o.snapshot})):o.type==="thinking"?this.#s("thinking",{text:o.text}):o.type==="done"?this.#s("done",{text:o.text,usage:o.usage,stopReason:o.stopReason}):o.type==="error"&&(this.appendMessage({role:"error",content:o.error.message}),this.#s("error",{error:o.error}))}catch(r){r.name!=="AbortError"&&(this.appendMessage({role:"error",content:r.message}),this.#s("error",{error:r}))}this.#r=null,this.stopStreaming()}export(){return{messages:this.#t.map(e=>({role:e.role,content:e.content})),model:this.model,system:this.system}}import(e){e.model&&(this.model=e.model),e.system&&(this.system=e.system),e.messages&&(this.conversation=e.messages)}#h(){let e=this.#e;e&&requestAnimationFrame(()=>{e.scrollTop=e.scrollHeight})}#c(e){for(let t of e.querySelectorAll("pre")){let n=t.querySelector("code");if(!n)continue;let r=n.getAttribute("data-lang")||"",s=document.createElement("code-ui");r&&s.setAttribute("language",r),s.textContent=n.textContent,t.replaceWith(s)}}};is("chat-shell",jl);export{jl as ChatShell};
|
|
50
|
+
`}else e==="error"&&(o.innerHTML=`<icon-ui name="warning"></icon-ui><span>${Ru(t)}</span>`);return this.#e?.appendChild(o),this.#h(),this.#s("message",{id:s,role:e,content:t}),o}appendChunk(e){let t=this.#t[this.#t.length-1];if(!t||t.role!=="assistant")return;t.content+=e;let n=this.#e?.querySelector("[data-role]:last-child [data-content]");n&&n.insertAdjacentText("beforeend",e),this.#h()}setStreamedText(e){let t=this.#t[this.#t.length-1];if(!t||t.role!=="assistant")return;t.content=e;let n=this.#e?.querySelector("[data-role]:last-child [data-content]");n&&(n.textContent=e),this.#h()}deleteMessage(e){let t=this.#t.findIndex(n=>n.id===e);t!==-1&&(this.#t.splice(t,1),this.#e?.querySelector(`[data-id="${e}"]`)?.remove())}clear(){if(this.#t.length=0,this.#e){let e=this.#o;this.#e.innerHTML="",e&&this.#e.appendChild(e)}this.#s("clear")}startStreaming(){this.streaming=!0,this.#i&&(this.#i.disabled=!0),this.#n&&(this.#n.textContent="Typing..."),this.#e?.tagName?.toLowerCase()==="chat-thread"&&(this.#e.streaming=!0)}stopStreaming(){this.streaming=!1,this.#i&&(this.#i.disabled=!1),this.#n&&(this.#n.textContent=""),this.#e?.tagName?.toLowerCase()==="chat-thread"&&(this.#e.streaming=!1),this.#e?.querySelector("[data-role]:last-child [data-cursor]")?.remove();let e=this.#t[this.#t.length-1];if(e?.role==="assistant"&&e.content&&!e.html){let t=this.#e?.querySelector("[data-role]:last-child [data-content]");t&&(t.innerHTML=ql(e.content),this.#c(t))}this.#i?.focus()}abort(){this.#r&&(this.#r.abort(),this.#r=null,this.#s("abort")),this.streaming&&this.stopStreaming()}async send(e,t={}){let n=t.model||this.model||this.#i?.model;if(!n)throw new Error("No model specified");this.appendMessage({role:"user",content:e}),this.appendMessage({role:"assistant",content:""}),this.startStreaming(),this.#r=new AbortController;try{let r={provider:this.provider||void 0,apiKey:this.#l||void 0,model:n,system:this.system||void 0,proxyUrl:this.proxyUrl||void 0,thinking:this.thinking||void 0,messages:this.conversation.slice(0,-1),signal:this.#r.signal,...t},s;for(let o of["../../../llm/index.js","../../llm/index.js"])try{({streamChat:s}=await import(new URL(o,import.meta.url).href));break}catch{}if(!s){this.appendMessage({role:"error",content:"@adia-ai/llm is not available \u2014 in a fresh checkout run the packages/llm build (see gh#527/gh#541)."}),this.#s("error",{error:new Error("llm build artifact missing")});return}for await(let o of s(r))o.type==="text"?(this.appendChunk(o.text),this.#s("chunk",{text:o.text,snapshot:o.snapshot})):o.type==="thinking"?this.#s("thinking",{text:o.text}):o.type==="done"?this.#s("done",{text:o.text,usage:o.usage,stopReason:o.stopReason}):o.type==="error"&&(this.appendMessage({role:"error",content:o.error.message}),this.#s("error",{error:o.error}))}catch(r){r.name!=="AbortError"&&(this.appendMessage({role:"error",content:r.message}),this.#s("error",{error:r}))}this.#r=null,this.stopStreaming()}export(){return{messages:this.#t.map(e=>({role:e.role,content:e.content})),model:this.model,system:this.system}}import(e){e.model&&(this.model=e.model),e.system&&(this.system=e.system),e.messages&&(this.conversation=e.messages)}#h(){let e=this.#e;e&&requestAnimationFrame(()=>{e.scrollTop=e.scrollHeight})}#c(e){for(let t of e.querySelectorAll("pre")){let n=t.querySelector("code");if(!n)continue;let r=n.getAttribute("data-lang")||"",s=document.createElement("code-ui");r&&s.setAttribute("language",r),s.textContent=n.textContent,t.replaceWith(s)}}};is("chat-shell",jl);export{jl as ChatShell};
|