@campfire-interactive/ai-chat 0.0.0-stage → 0.2.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.
@@ -0,0 +1,1817 @@
1
+ import {
2
+ createAssistantClient
3
+ } from "../chunk-SI3LIN2H.js";
4
+ import {
5
+ compareCells,
6
+ formatValue,
7
+ safeUrl,
8
+ statusLabel,
9
+ statusTone
10
+ } from "../chunk-2MWBMQMX.js";
11
+
12
+ // src/host/AiChatProvider.tsx
13
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
14
+
15
+ // src/host/context.ts
16
+ import { createContext, useContext } from "react";
17
+ var AiChatContext = createContext(null);
18
+ function useAiChat() {
19
+ return useContext(AiChatContext);
20
+ }
21
+
22
+ // src/host/thread.ts
23
+ var HISTORY_TURNS = 8;
24
+ function newId() {
25
+ const c = globalThis.crypto;
26
+ if (c && typeof c.randomUUID === "function") return c.randomUUID();
27
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
28
+ const r = Math.random() * 16 | 0;
29
+ return (ch === "x" ? r : r & 3 | 8).toString(16);
30
+ });
31
+ }
32
+ function askedOnOf(ctx) {
33
+ return { page: ctx.page, title: ctx.title, breadcrumbs: ctx.breadcrumbs, path: ctx.path };
34
+ }
35
+ function pageLabel(on) {
36
+ const crumb = [...on.breadcrumbs].reverse().find((b) => b && b !== on.title);
37
+ return crumb ? `${on.title} \xB7 ${crumb}` : on.title;
38
+ }
39
+ function applyEvent(ex, event) {
40
+ switch (event.type) {
41
+ case "text": {
42
+ const last = ex.parts[ex.parts.length - 1];
43
+ if (last && last.kind === "markdown") {
44
+ return { ...ex, parts: [...ex.parts.slice(0, -1), { kind: "markdown", text: last.text + event.delta }] };
45
+ }
46
+ return { ...ex, parts: [...ex.parts, { kind: "markdown", text: event.delta }] };
47
+ }
48
+ case "step":
49
+ return {
50
+ ...ex,
51
+ steps: [...ex.steps, event.label],
52
+ sources: addSource(ex.sources ?? [], event.source, event.sourcePath, event.sourceApp)
53
+ };
54
+ case "block":
55
+ return { ...ex, parts: [...ex.parts, { kind: "block", block: event.block }] };
56
+ case "write_pending":
57
+ return { ...ex, parts: [...ex.parts, { kind: "write", item: { write: event.write, state: "pending" } }] };
58
+ case "done":
59
+ return { ...ex, status: "done", answeredAt: Date.now() };
60
+ case "error":
61
+ return { ...ex, status: "error", error: event.message };
62
+ }
63
+ }
64
+ function discardPending(cards) {
65
+ let changed = false;
66
+ const next = cards.map((card) => {
67
+ let cardChanged = false;
68
+ const exchanges = card.exchanges.map((ex) => {
69
+ if (!ex.parts.some((p) => p.kind === "write" && p.item.state === "pending")) return ex;
70
+ cardChanged = true;
71
+ return {
72
+ ...ex,
73
+ parts: ex.parts.map(
74
+ (p) => p.kind === "write" && p.item.state === "pending" ? { ...p, item: { ...p.item, state: "discarded" } } : p
75
+ )
76
+ };
77
+ });
78
+ if (!cardChanged) return card;
79
+ changed = true;
80
+ return { ...card, exchanges };
81
+ });
82
+ return changed ? next : cards;
83
+ }
84
+ function describeBlock(block) {
85
+ switch (block.kind) {
86
+ case "table":
87
+ return `[Showed a table${block.title ? ` "${block.title}"` : ""} with ${block.rows.length} rows]`;
88
+ case "chart":
89
+ return `[Showed a ${block.chartType} chart${block.title ? ` "${block.title}"` : ""}]`;
90
+ case "kpis":
91
+ return `[Showed ${block.items.map((k) => `${k.label}: ${k.value}`).join("; ")}]`;
92
+ case "entities":
93
+ return `[Linked ${block.items.map((e) => e.label).join(", ")}]`;
94
+ case "question":
95
+ return block.question;
96
+ case "understood":
97
+ return `[Understood as: ${block.summary}]`;
98
+ default:
99
+ return null;
100
+ }
101
+ }
102
+ function assistantText(ex) {
103
+ const lines = [];
104
+ for (const part of ex.parts) {
105
+ if (part.kind === "markdown") lines.push(part.text);
106
+ else if (part.kind === "block") {
107
+ const d = describeBlock(part.block);
108
+ if (d) lines.push(d);
109
+ } else if (part.item.state !== "succeeded" && part.item.state !== "failed") {
110
+ lines.push(`Proposed, not confirmed: ${part.item.write.summary}`);
111
+ }
112
+ }
113
+ if (ex.status === "error" && ex.error) lines.push(`(No answer: ${ex.error})`);
114
+ lines.push(...ex.confirmations);
115
+ return lines.join("\n").trim();
116
+ }
117
+ function buildHistory(cards, turns = HISTORY_TURNS) {
118
+ const out = [];
119
+ for (const card of cards) {
120
+ for (const ex of card.exchanges) {
121
+ if (ex.status === "streaming") continue;
122
+ out.push({ role: "user", content: ex.question });
123
+ const content = assistantText(ex);
124
+ if (content) out.push({ role: "assistant", content });
125
+ }
126
+ }
127
+ return out.slice(-turns);
128
+ }
129
+ function fillStarter(template, ctx) {
130
+ let ready = true;
131
+ const text = template.replace(/\{([A-Za-z0-9_.-]+)\}/g, (whole, name) => {
132
+ const entity = ctx.entities.find((e) => e.type === name) ?? (name === "item" ? ctx.entities[ctx.entities.length - 1] : void 0);
133
+ if (entity?.label) return entity.label;
134
+ const filter = ctx.filters?.[name];
135
+ if (filter) return filter;
136
+ if (name === "title" && ctx.title) return ctx.title;
137
+ ready = false;
138
+ return whole;
139
+ });
140
+ return { text, ready };
141
+ }
142
+ var TIME_GROUP_MS = 5 * 60 * 1e3;
143
+ function endsTimeGroup(cards, index) {
144
+ const card = cards[index];
145
+ const last = card.exchanges[card.exchanges.length - 1];
146
+ if (!last || last.status === "streaming") return false;
147
+ const next = cards[index + 1];
148
+ if (!next) return true;
149
+ const ended = last.answeredAt ?? last.askedAt;
150
+ return next.exchanges[0].askedAt - ended > TIME_GROUP_MS;
151
+ }
152
+ function cardTime(card) {
153
+ const last = card.exchanges[card.exchanges.length - 1];
154
+ return last.answeredAt ?? last.askedAt;
155
+ }
156
+ function starterLabel(template, ctx) {
157
+ return fillStarter(template, ctx).text.replace(/\{[A-Za-z0-9_.-]+\}/g, "\u2026");
158
+ }
159
+ function lastAskedPage(cards) {
160
+ const card = cards[cards.length - 1];
161
+ const ex = card?.exchanges[card.exchanges.length - 1];
162
+ return ex ? ex.askedOn.page : null;
163
+ }
164
+ function openQuestionCard(cards) {
165
+ const card = cards[cards.length - 1];
166
+ const ex = card?.exchanges[card.exchanges.length - 1];
167
+ if (!ex || ex.status !== "done") return null;
168
+ return ex.parts.some((p) => p.kind === "block" && p.block.kind === "question") ? card : null;
169
+ }
170
+ function addSource(sources, label, path, app) {
171
+ if (!label) return sources;
172
+ const at = sources.findIndex((s) => s.label === label);
173
+ if (at < 0) return [...sources, path ? { label, path, ...app ? { app } : {} } : { label }];
174
+ if (sources[at].path === path && sources[at].app === app) return sources;
175
+ return sources.map((s, i) => i === at ? { label } : s);
176
+ }
177
+
178
+ // src/host/AiChatProvider.tsx
179
+ import { jsx } from "react/jsx-runtime";
180
+ var MIN_WIDTH = 360;
181
+ var MAX_WIDTH = 900;
182
+ var DEFAULT_WIDTH = 480;
183
+ var PANEL_WIDTH = { min: MIN_WIDTH, max: MAX_WIDTH, default: DEFAULT_WIDTH };
184
+ var clampWidth = (w) => Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, Math.round(w)));
185
+ function readStored(key) {
186
+ try {
187
+ return window.localStorage.getItem(key);
188
+ } catch {
189
+ return null;
190
+ }
191
+ }
192
+ function writeStored(key, value) {
193
+ try {
194
+ window.localStorage.setItem(key, value);
195
+ } catch {
196
+ }
197
+ }
198
+ var ASK_KEY = "cfi.ai-chat.ask";
199
+ var ASK_SETTLE_MS = 400;
200
+ var ASK_LABEL_WAIT_MS = 3e3;
201
+ function readSession(key) {
202
+ try {
203
+ return window.sessionStorage.getItem(key);
204
+ } catch {
205
+ return null;
206
+ }
207
+ }
208
+ function writeSession(key, value) {
209
+ try {
210
+ if (value === null) window.sessionStorage.removeItem(key);
211
+ else window.sessionStorage.setItem(key, value);
212
+ } catch {
213
+ }
214
+ }
215
+ function AiChatProvider(props) {
216
+ return /* @__PURE__ */ jsx(ChatProvider, { ...props, enabled: Boolean(props.hubUrl || props.client) });
217
+ }
218
+ function ChatProvider({
219
+ app,
220
+ hubUrl,
221
+ getToken,
222
+ uploadFile,
223
+ pageContext,
224
+ starters,
225
+ catalog,
226
+ navigate,
227
+ onChanged,
228
+ client: injected,
229
+ children,
230
+ enabled: configured
231
+ }) {
232
+ const onChangedRef = useRef(onChanged);
233
+ onChangedRef.current = onChanged;
234
+ const getTokenRef = useRef(getToken);
235
+ getTokenRef.current = getToken;
236
+ const client = useMemo(
237
+ () => injected ?? createAssistantClient({ hubUrl: hubUrl ?? "", getToken: () => getTokenRef.current() }),
238
+ [injected, hubUrl]
239
+ );
240
+ const [available, setAvailable] = useState(null);
241
+ useEffect(() => {
242
+ if (!configured) {
243
+ setAvailable(null);
244
+ return void 0;
245
+ }
246
+ if (!client.availability) {
247
+ setAvailable(true);
248
+ return void 0;
249
+ }
250
+ let live = true;
251
+ client.availability(app).then((r) => live && setAvailable(r.available === true)).catch(() => live && setAvailable(false));
252
+ return () => {
253
+ live = false;
254
+ };
255
+ }, [configured, client, app]);
256
+ const enabled = configured && available === true;
257
+ const openKey = `cfi.ai-chat.${app}.open`;
258
+ const widthKey = `cfi.ai-chat.${app}.width`;
259
+ const [open, setOpenState] = useState(() => readStored(openKey) === "1");
260
+ const [width, setWidthState] = useState(() => {
261
+ const stored = Number(readStored(widthKey));
262
+ return Number.isFinite(stored) && stored >= MIN_WIDTH && stored <= MAX_WIDTH ? stored : DEFAULT_WIDTH;
263
+ });
264
+ useEffect(() => writeStored(openKey, open ? "1" : "0"), [openKey, open]);
265
+ useEffect(() => writeStored(widthKey, String(width)), [widthKey, width]);
266
+ const setOpen = useCallback((next) => setOpenState(next), []);
267
+ const toggle = useCallback(() => setOpenState((o) => !o), []);
268
+ const setWidth = useCallback((w) => setWidthState(clampWidth(w)), []);
269
+ const expandedKey = `cfi.ai-chat.${app}.expanded`;
270
+ const [expanded, setExpandedState] = useState(() => readStored(expandedKey) === "1");
271
+ useEffect(() => writeStored(expandedKey, expanded ? "1" : "0"), [expandedKey, expanded]);
272
+ const setExpanded = useCallback((next) => setExpandedState(next), []);
273
+ const [sessionId, setSessionId] = useState(newId);
274
+ const [cards, setCards] = useState([]);
275
+ const [busy, setBusy] = useState(false);
276
+ const [draft, setDraft] = useState("");
277
+ const files = useRef(/* @__PURE__ */ new Map());
278
+ const [sessionFiles, setSessionFiles] = useState([]);
279
+ const [staged, setStaged] = useState([]);
280
+ const [attached, setAttached] = useState([]);
281
+ const [changes, setChanges] = useState({ items: [], loading: false, loaded: false });
282
+ const [summaries, setSummaries] = useState({});
283
+ const cardsRef = useRef(cards);
284
+ cardsRef.current = cards;
285
+ const pageRef = useRef(pageContext);
286
+ pageRef.current = pageContext;
287
+ const navigateRef = useRef(navigate);
288
+ navigateRef.current = navigate;
289
+ const sessionRef = useRef(sessionId);
290
+ sessionRef.current = sessionId;
291
+ const busyRef = useRef(false);
292
+ const abortRef = useRef(null);
293
+ const filesMetaRef = useRef({ sessionFiles, attached });
294
+ filesMetaRef.current = { sessionFiles, attached };
295
+ const updateExchange = useCallback((id, fn) => {
296
+ setCards(
297
+ (prev) => prev.map(
298
+ (card) => card.exchanges.some((ex) => ex.id === id) ? { ...card, exchanges: card.exchanges.map((ex) => ex.id === id ? fn(ex) : ex) } : card
299
+ )
300
+ );
301
+ }, []);
302
+ const updateWrite = useCallback(
303
+ (exchangeId, token, fn) => {
304
+ updateExchange(exchangeId, (ex) => ({
305
+ ...ex,
306
+ parts: ex.parts.map((p) => p.kind === "write" && p.item.write.token === token ? { ...p, item: fn(p.item) } : p)
307
+ }));
308
+ },
309
+ [updateExchange]
310
+ );
311
+ const findWrite = (exchangeId, token) => {
312
+ for (const card of cardsRef.current) {
313
+ for (const ex of card.exchanges) {
314
+ if (ex.id !== exchangeId) continue;
315
+ for (const p of ex.parts) if (p.kind === "write" && p.item.write.token === token) return p.item;
316
+ }
317
+ }
318
+ return null;
319
+ };
320
+ const refreshChanges = useCallback(async () => {
321
+ const session = sessionRef.current;
322
+ setChanges((c) => ({ ...c, loading: true, error: void 0 }));
323
+ try {
324
+ const { items } = await client.listActions({ sessionId: session });
325
+ if (sessionRef.current !== session) return;
326
+ setChanges({ items, loading: false, loaded: true });
327
+ } catch (err) {
328
+ if (sessionRef.current !== session) return;
329
+ setChanges((c) => ({ ...c, loading: false, loaded: true, error: messageOf(err, "Could not load the changes.") }));
330
+ }
331
+ }, [client]);
332
+ const send = useCallback(
333
+ async (raw, opts) => {
334
+ const message = raw.trim();
335
+ if (!message || busyRef.current) return;
336
+ const ctx = pageRef.current;
337
+ const session = sessionRef.current;
338
+ const history = buildHistory(cardsRef.current);
339
+ const { sessionFiles: held, attached: done } = filesMetaRef.current;
340
+ const attachments = held.filter((f) => !done.includes(f.fileRef));
341
+ const ex = {
342
+ id: newId(),
343
+ question: message,
344
+ askedOn: { ...askedOnOf(ctx) },
345
+ attachments,
346
+ parts: [],
347
+ steps: [],
348
+ status: "streaming",
349
+ confirmations: [],
350
+ askedAt: Date.now()
351
+ };
352
+ setCards((prev) => {
353
+ const next = discardPending(prev);
354
+ if (opts?.foldInto && next.some((c) => c.id === opts.foldInto)) {
355
+ return next.map((c) => c.id === opts.foldInto ? { ...c, exchanges: [...c.exchanges, ex] } : c);
356
+ }
357
+ return [...next, { id: newId(), exchanges: [ex] }];
358
+ });
359
+ setStaged([]);
360
+ const controller = new AbortController();
361
+ abortRef.current = controller;
362
+ busyRef.current = true;
363
+ setBusy(true);
364
+ try {
365
+ const stream = client.chatStream(
366
+ { app, session_id: session, message, history, page_context: ctx, attachments },
367
+ { signal: controller.signal }
368
+ );
369
+ let opened = false;
370
+ for await (const event of stream) {
371
+ if (controller.signal.aborted) break;
372
+ updateExchange(ex.id, (e) => applyEvent(e, event));
373
+ if (!opened && event.type === "block" && event.block.kind === "actions") {
374
+ const target = event.block.items.find((i) => i.openNow);
375
+ if (target) {
376
+ opened = true;
377
+ navigateRef.current(target.link);
378
+ }
379
+ }
380
+ }
381
+ if (!controller.signal.aborted) {
382
+ updateExchange(
383
+ ex.id,
384
+ (e) => e.status === "streaming" ? { ...e, status: "error", error: "The answer stopped before it finished." } : e
385
+ );
386
+ }
387
+ } catch (err) {
388
+ if (!controller.signal.aborted) {
389
+ updateExchange(ex.id, (e) => ({ ...e, status: "error", error: messageOf(err, "AI chat could not answer that.") }));
390
+ }
391
+ } finally {
392
+ if (controller.signal.aborted && sessionRef.current === session) {
393
+ updateExchange(ex.id, (e) => e.status === "streaming" ? { ...e, status: "done" } : e);
394
+ }
395
+ if (abortRef.current === controller) {
396
+ abortRef.current = null;
397
+ busyRef.current = false;
398
+ setBusy(false);
399
+ }
400
+ }
401
+ },
402
+ [app, client, updateExchange]
403
+ );
404
+ const stop = useCallback(() => abortRef.current?.abort(), []);
405
+ const startOver = useCallback(() => {
406
+ abortRef.current?.abort();
407
+ abortRef.current = null;
408
+ busyRef.current = false;
409
+ setBusy(false);
410
+ setCards([]);
411
+ setDraft("");
412
+ files.current = /* @__PURE__ */ new Map();
413
+ setSessionFiles([]);
414
+ setStaged([]);
415
+ setAttached([]);
416
+ setChanges({ items: [], loading: false, loaded: false });
417
+ setSummaries({});
418
+ const next = newId();
419
+ sessionRef.current = next;
420
+ setSessionId(next);
421
+ }, []);
422
+ const addFiles = useCallback((list) => {
423
+ const added = [];
424
+ for (const file of Array.from(list)) {
425
+ const fileRef = newId();
426
+ files.current.set(fileRef, file);
427
+ added.push({ fileRef, name: file.name, contentType: file.type || "application/octet-stream", size: file.size });
428
+ }
429
+ if (!added.length) return;
430
+ setSessionFiles((prev) => [...prev, ...added]);
431
+ setStaged((prev) => [...prev, ...added.map((a) => a.fileRef)]);
432
+ }, []);
433
+ const removeFile = useCallback((fileRef) => {
434
+ files.current.delete(fileRef);
435
+ setSessionFiles((prev) => prev.filter((f) => f.fileRef !== fileRef));
436
+ setStaged((prev) => prev.filter((r) => r !== fileRef));
437
+ }, []);
438
+ const cancelWrite = useCallback(
439
+ (exchangeId, token) => updateWrite(exchangeId, token, (item) => item.state === "pending" ? { ...item, state: "canceled", error: void 0 } : item),
440
+ [updateWrite]
441
+ );
442
+ const confirmWrite = useCallback(
443
+ async (exchangeId, token) => {
444
+ const item = findWrite(exchangeId, token);
445
+ if (!item || item.state !== "pending") return;
446
+ const { write } = item;
447
+ if (Date.parse(write.expiresAt) <= Date.now()) {
448
+ updateWrite(exchangeId, token, (i) => ({ ...i, state: "expired", error: void 0 }));
449
+ return;
450
+ }
451
+ const session = sessionRef.current;
452
+ updateWrite(exchangeId, token, (i) => ({ ...i, state: "confirming", error: void 0 }));
453
+ let documentId;
454
+ if (write.file) {
455
+ const file = files.current.get(write.file.fileRef);
456
+ if (!file) {
457
+ updateWrite(exchangeId, token, (i) => ({
458
+ ...i,
459
+ state: "pending",
460
+ error: "That file is no longer in this chat. Drop it again and ask once more."
461
+ }));
462
+ return;
463
+ }
464
+ try {
465
+ ({ documentId } = await uploadFile(file, write));
466
+ } catch (err) {
467
+ if (sessionRef.current !== session) return;
468
+ updateWrite(exchangeId, token, (i) => ({
469
+ ...i,
470
+ state: "pending",
471
+ error: messageOf(err, "The file could not be uploaded, so nothing was changed.")
472
+ }));
473
+ return;
474
+ }
475
+ }
476
+ try {
477
+ const res = await client.confirmWrite(documentId ? { token, documentId } : { token });
478
+ if (sessionRef.current !== session) return;
479
+ updateWrite(exchangeId, token, (i) => ({ ...i, state: res.outcome, actionId: res.actionId, error: res.error }));
480
+ updateExchange(exchangeId, (e) => ({
481
+ ...e,
482
+ confirmations: [...e.confirmations, `Confirmed: ${write.summary} (${res.outcome})`]
483
+ }));
484
+ setSummaries((s) => ({ ...s, [res.actionId]: write.summary }));
485
+ if (res.outcome === "succeeded" && write.file) setAttached((a) => [...a, write.file.fileRef]);
486
+ void refreshChanges();
487
+ onChangedRef.current?.(write, res.outcome);
488
+ } catch (err) {
489
+ if (sessionRef.current !== session) return;
490
+ updateWrite(exchangeId, token, (i) => ({ ...i, state: "failed", error: messageOf(err, "The change could not be made.") }));
491
+ }
492
+ },
493
+ // findWrite reads refs only, so it is not a dependency.
494
+ [client, uploadFile, updateWrite, updateExchange, refreshChanges]
495
+ );
496
+ const confirmAll = useCallback(
497
+ async (exchangeId) => {
498
+ const ex = cardsRef.current.flatMap((c) => c.exchanges).find((e) => e.id === exchangeId);
499
+ if (!ex) return;
500
+ const tokens = ex.parts.flatMap((p) => p.kind === "write" && p.item.state === "pending" ? [p.item.write.token] : []);
501
+ for (const token of tokens) await confirmWrite(exchangeId, token);
502
+ },
503
+ [confirmWrite]
504
+ );
505
+ const pageStarters = useMemo(
506
+ () => starters?.[pageContext.page] ?? starters?.["*"] ?? [],
507
+ [starters, pageContext.page]
508
+ );
509
+ const catalogValue = useMemo(() => catalog ?? [], [catalog]);
510
+ const stagedFiles = useMemo(
511
+ () => sessionFiles.filter((f) => staged.includes(f.fileRef)),
512
+ [sessionFiles, staged]
513
+ );
514
+ const ask = useCallback(
515
+ (question, opts) => {
516
+ const text = question.trim();
517
+ if (!text) return;
518
+ setOpenState(true);
519
+ if (opts?.send === false || busy) setDraft(text);
520
+ else void send(text);
521
+ },
522
+ [busy, send]
523
+ );
524
+ const askWaitStarted = useRef(0);
525
+ const [askTick, setAskTick] = useState(0);
526
+ useEffect(() => {
527
+ if (typeof window === "undefined") return void 0;
528
+ const url = new URL(window.location.href);
529
+ const fromUrl = url.searchParams.get("ask");
530
+ if (fromUrl) {
531
+ writeSession(ASK_KEY, fromUrl);
532
+ url.searchParams.delete("ask");
533
+ window.history.replaceState(window.history.state, "", `${url.pathname}${url.search}${url.hash}`);
534
+ }
535
+ if (!enabled || !readSession(ASK_KEY)) return void 0;
536
+ if (!askWaitStarted.current) askWaitStarted.current = Date.now();
537
+ const labelled = pageContext.entities.every((e) => e.label);
538
+ const waited = Date.now() - askWaitStarted.current;
539
+ if (!labelled && waited < ASK_LABEL_WAIT_MS) {
540
+ const retry = window.setTimeout(() => setAskTick((t) => t + 1), 250);
541
+ return () => window.clearTimeout(retry);
542
+ }
543
+ const timer = window.setTimeout(() => {
544
+ const question = readSession(ASK_KEY);
545
+ if (!question) return;
546
+ writeSession(ASK_KEY, null);
547
+ ask(question);
548
+ }, ASK_SETTLE_MS);
549
+ return () => window.clearTimeout(timer);
550
+ }, [ask, enabled, pageContext, askTick]);
551
+ const value = useMemo(
552
+ () => ({
553
+ app,
554
+ pageContext,
555
+ starters: pageStarters,
556
+ catalog: catalogValue,
557
+ open,
558
+ setOpen,
559
+ toggle,
560
+ width,
561
+ setWidth,
562
+ sessionId,
563
+ cards,
564
+ busy,
565
+ draft,
566
+ setDraft,
567
+ send,
568
+ ask,
569
+ stop,
570
+ startOver,
571
+ stagedFiles,
572
+ addFiles,
573
+ removeFile,
574
+ confirmWrite,
575
+ confirmAll,
576
+ cancelWrite,
577
+ navigate,
578
+ expanded,
579
+ setExpanded,
580
+ changes,
581
+ summaries,
582
+ refreshChanges
583
+ }),
584
+ [
585
+ app,
586
+ pageContext,
587
+ pageStarters,
588
+ catalogValue,
589
+ open,
590
+ setOpen,
591
+ toggle,
592
+ width,
593
+ setWidth,
594
+ sessionId,
595
+ cards,
596
+ busy,
597
+ draft,
598
+ send,
599
+ ask,
600
+ stop,
601
+ startOver,
602
+ stagedFiles,
603
+ addFiles,
604
+ removeFile,
605
+ confirmWrite,
606
+ confirmAll,
607
+ cancelWrite,
608
+ navigate,
609
+ expanded,
610
+ setExpanded,
611
+ changes,
612
+ summaries,
613
+ refreshChanges
614
+ ]
615
+ );
616
+ return /* @__PURE__ */ jsx(AiChatContext.Provider, { value: enabled ? value : null, children });
617
+ }
618
+ function messageOf(err, fallback) {
619
+ return err instanceof Error && err.message ? err.message : fallback;
620
+ }
621
+
622
+ // src/host/AiChatSurface.tsx
623
+ import { useCallback as useCallback2, useEffect as useEffect4, useLayoutEffect, useRef as useRef3, useState as useState6 } from "react";
624
+ import {
625
+ ArrowLeft,
626
+ Check as Check3,
627
+ ChevronDown as ChevronDown2,
628
+ ChevronRight as ChevronRight2,
629
+ CircleAlert as CircleAlert2,
630
+ Copy as Copy2,
631
+ History,
632
+ Lightbulb,
633
+ Loader2 as Loader22,
634
+ Lock,
635
+ MapPin,
636
+ Maximize2,
637
+ Minimize2,
638
+ Paperclip as Paperclip2,
639
+ RotateCcw,
640
+ Sparkles,
641
+ Send,
642
+ Square,
643
+ X as X2
644
+ } from "lucide-react";
645
+
646
+ // src/blocks/Blocks.tsx
647
+ import { lazy, Suspense } from "react";
648
+ import { ArrowRight, BookOpen, ExternalLink, FilePen, TrendingDown, TrendingUp } from "lucide-react";
649
+
650
+ // src/blocks/TableBlock.tsx
651
+ import { useMemo as useMemo2, useState as useState2 } from "react";
652
+ import { ArrowDown, ArrowUp } from "lucide-react";
653
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
654
+ var NUMERIC = /* @__PURE__ */ new Set(["number", "money", "percent"]);
655
+ var SHORT = 18;
656
+ function TableBlock({ block }) {
657
+ const [sort, setSort] = useState2(null);
658
+ const rows = useMemo2(() => {
659
+ if (!sort) return block.rows;
660
+ const type = block.columns.find((c) => c.key === sort.key)?.type;
661
+ return [...block.rows].sort((a, b) => compareCells(a[sort.key], b[sort.key], type) * sort.dir);
662
+ }, [block.rows, block.columns, sort]);
663
+ const fit = useMemo2(() => {
664
+ const out = {};
665
+ for (const col of block.columns) {
666
+ const longest = block.rows.reduce((n, row) => Math.max(n, formatValue(row[col.key], col.type, block.currency).length), col.label.length);
667
+ out[col.key] = longest <= SHORT ? "short" : "long";
668
+ }
669
+ return out;
670
+ }, [block.rows, block.columns, block.currency]);
671
+ const toggle = (key) => setSort((s) => s?.key !== key ? { key, dir: 1 } : s.dir === 1 ? { key, dir: -1 } : null);
672
+ return /* @__PURE__ */ jsxs("figure", { className: "cfi-ac-block cfi-ac-table", children: [
673
+ block.title && /* @__PURE__ */ jsx2("figcaption", { className: "cfi-ac-block__title", children: block.title }),
674
+ /* @__PURE__ */ jsx2("div", { className: "cfi-ac-table__scroll", tabIndex: 0, role: "region", "aria-label": block.title ?? "Table", children: /* @__PURE__ */ jsxs("table", { children: [
675
+ /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsx2("tr", { children: block.columns.map((col) => {
676
+ const active = sort?.key === col.key;
677
+ return /* @__PURE__ */ jsx2(
678
+ "th",
679
+ {
680
+ scope: "col",
681
+ "data-numeric": NUMERIC.has(col.type ?? "") || void 0,
682
+ "aria-sort": active ? sort.dir === 1 ? "ascending" : "descending" : "none",
683
+ children: /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => toggle(col.key), children: [
684
+ col.label,
685
+ active && (sort.dir === 1 ? /* @__PURE__ */ jsx2(ArrowUp, { size: 12, "aria-hidden": true }) : /* @__PURE__ */ jsx2(ArrowDown, { size: 12, "aria-hidden": true }))
686
+ ] })
687
+ },
688
+ col.key
689
+ );
690
+ }) }) }),
691
+ /* @__PURE__ */ jsx2("tbody", { children: rows.map((row, i) => /* @__PURE__ */ jsx2("tr", { children: block.columns.map((col) => /* @__PURE__ */ jsx2("td", { "data-numeric": NUMERIC.has(col.type ?? "") || void 0, "data-fit": fit[col.key], children: col.type === "status" && row[col.key] != null && row[col.key] !== "" ? /* @__PURE__ */ jsx2("span", { className: "cfi-ac-status", "data-tone": statusTone(row[col.key]), children: statusLabel(row[col.key]) }) : formatValue(row[col.key], col.type, block.currency) }, col.key)) }, i)) })
692
+ ] }) })
693
+ ] });
694
+ }
695
+
696
+ // src/blocks/DraftBlock.tsx
697
+ import { useState as useState4 } from "react";
698
+ import { Check, Copy } from "lucide-react";
699
+
700
+ // src/host/announce.tsx
701
+ import { useEffect as useEffect2, useState as useState3 } from "react";
702
+ import { jsx as jsx3 } from "react/jsx-runtime";
703
+ var listeners = /* @__PURE__ */ new Set();
704
+ function announce(message) {
705
+ listeners.forEach((l) => l(message));
706
+ }
707
+ function Announcer() {
708
+ const [message, setMessage] = useState3("");
709
+ useEffect2(() => {
710
+ let clear;
711
+ const listener = (next) => {
712
+ setMessage("");
713
+ window.requestAnimationFrame(() => setMessage(next));
714
+ window.clearTimeout(clear);
715
+ clear = window.setTimeout(() => setMessage(""), 4e3);
716
+ };
717
+ listeners.add(listener);
718
+ return () => {
719
+ listeners.delete(listener);
720
+ window.clearTimeout(clear);
721
+ };
722
+ }, []);
723
+ return /* @__PURE__ */ jsx3("p", { className: "cfi-ac-sr-only", role: "status", "aria-live": "polite", children: message });
724
+ }
725
+
726
+ // src/blocks/DraftBlock.tsx
727
+ import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
728
+ function DraftBlock({ block }) {
729
+ const [copied, setCopied] = useState4(false);
730
+ const text = block.subject ? `Subject: ${block.subject}
731
+
732
+ ${block.body}` : block.body;
733
+ return /* @__PURE__ */ jsxs2("figure", { className: "cfi-ac-block cfi-ac-draft", children: [
734
+ /* @__PURE__ */ jsxs2("figcaption", { className: "cfi-ac-draft__head", children: [
735
+ /* @__PURE__ */ jsx4("span", { className: "cfi-ac-block__title", children: block.title }),
736
+ /* @__PURE__ */ jsxs2(
737
+ "button",
738
+ {
739
+ type: "button",
740
+ className: "cfi-ac-btn",
741
+ "data-variant": "secondary",
742
+ onClick: () => {
743
+ void navigator.clipboard?.writeText(text).then(() => {
744
+ setCopied(true);
745
+ announce("Draft copied");
746
+ window.setTimeout(() => setCopied(false), 1500);
747
+ });
748
+ },
749
+ children: [
750
+ copied ? /* @__PURE__ */ jsx4(Check, { size: 14, "aria-hidden": true }) : /* @__PURE__ */ jsx4(Copy, { size: 14, "aria-hidden": true }),
751
+ copied ? "Copied" : "Copy"
752
+ ]
753
+ }
754
+ )
755
+ ] }),
756
+ (block.to || block.subject) && /* @__PURE__ */ jsxs2("dl", { className: "cfi-ac-draft__meta", children: [
757
+ block.to && /* @__PURE__ */ jsxs2(Fragment, { children: [
758
+ /* @__PURE__ */ jsx4("dt", { children: "To" }),
759
+ /* @__PURE__ */ jsx4("dd", { children: block.to })
760
+ ] }),
761
+ block.subject && /* @__PURE__ */ jsxs2(Fragment, { children: [
762
+ /* @__PURE__ */ jsx4("dt", { children: "Subject" }),
763
+ /* @__PURE__ */ jsx4("dd", { children: block.subject })
764
+ ] })
765
+ ] }),
766
+ /* @__PURE__ */ jsx4("div", { className: "cfi-ac-draft__body", tabIndex: 0, role: "region", "aria-label": block.title, children: block.body })
767
+ ] });
768
+ }
769
+
770
+ // src/blocks/Blocks.tsx
771
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
772
+ var ChartBlock = lazy(() => import("../ChartBlock-HDE237VG.js"));
773
+ function BlockView({ block, handlers }) {
774
+ switch (block.kind) {
775
+ case "table":
776
+ return /* @__PURE__ */ jsx5(TableBlock, { block });
777
+ case "chart":
778
+ return /* @__PURE__ */ jsx5(Suspense, { fallback: /* @__PURE__ */ jsx5("div", { className: "cfi-ac-block cfi-ac-chart cfi-ac-chart--loading", "aria-busy": "true" }), children: /* @__PURE__ */ jsx5(ChartBlock, { block }) });
779
+ case "kpis":
780
+ return /* @__PURE__ */ jsx5("div", { className: "cfi-ac-block cfi-ac-kpis", children: block.items.map((k, i) => /* @__PURE__ */ jsxs3("div", { className: "cfi-ac-kpi", "data-tone": k.tone ?? "neutral", children: [
781
+ /* @__PURE__ */ jsx5("span", { className: "cfi-ac-kpi__label", children: k.label }),
782
+ /* @__PURE__ */ jsx5("span", { className: "cfi-ac-kpi__value", children: typeof k.value === "number" ? formatValue(k.value, k.format ?? "number") : k.value }),
783
+ typeof k.change === "number" && /* @__PURE__ */ jsxs3("span", { className: "cfi-ac-kpi__change", children: [
784
+ k.change >= 0 ? /* @__PURE__ */ jsx5(TrendingUp, { size: 12, "aria-hidden": true }) : /* @__PURE__ */ jsx5(TrendingDown, { size: 12, "aria-hidden": true }),
785
+ k.change > 0 ? "+" : "",
786
+ k.change.toLocaleString(void 0, { maximumFractionDigits: 1 }),
787
+ "%"
788
+ ] })
789
+ ] }, i)) });
790
+ case "entities":
791
+ return /* @__PURE__ */ jsx5("div", { className: "cfi-ac-block cfi-ac-entities", children: block.items.map((e) => /* @__PURE__ */ jsxs3("button", { type: "button", className: "cfi-ac-entity", onClick: () => handlers.navigate(e.link), children: [
792
+ /* @__PURE__ */ jsx5("span", { className: "cfi-ac-entity__type", children: e.type }),
793
+ /* @__PURE__ */ jsx5("span", { className: "cfi-ac-entity__label", children: e.label }),
794
+ e.sublabel && /* @__PURE__ */ jsx5("span", { className: "cfi-ac-entity__sub", children: e.sublabel }),
795
+ /* @__PURE__ */ jsx5(ArrowRight, { className: "cfi-ac-entity__go", size: 14, "aria-hidden": true })
796
+ ] }, `${e.type}:${e.id}`)) });
797
+ case "understood":
798
+ return /* @__PURE__ */ jsxs3("p", { className: "cfi-ac-understood", children: [
799
+ /* @__PURE__ */ jsx5("span", { className: "cfi-ac-understood__label", children: "Understood as:" }),
800
+ " ",
801
+ block.summary
802
+ ] });
803
+ case "actions":
804
+ return /* @__PURE__ */ jsx5("div", { className: "cfi-ac-block cfi-ac-actions", children: block.items.map((a, i) => /* @__PURE__ */ jsxs3(
805
+ "button",
806
+ {
807
+ type: "button",
808
+ className: "cfi-ac-btn",
809
+ "data-variant": a.primary ?? (i === 0 && !block.items.some((x) => x.primary)) ? "primary" : "secondary",
810
+ onClick: () => handlers.navigate(a.link),
811
+ children: [
812
+ a.style === "open_form" ? /* @__PURE__ */ jsx5(FilePen, { size: 14, "aria-hidden": true }) : /* @__PURE__ */ jsx5(ArrowRight, { size: 14, "aria-hidden": true }),
813
+ a.label
814
+ ]
815
+ },
816
+ i
817
+ )) });
818
+ case "question":
819
+ return /* @__PURE__ */ jsxs3("div", { className: "cfi-ac-block cfi-ac-question", children: [
820
+ /* @__PURE__ */ jsx5("p", { className: "cfi-ac-question__text", children: block.question }),
821
+ block.options && block.options.length > 0 && /* @__PURE__ */ jsx5("div", { className: "cfi-ac-chips", children: block.options.map((o) => /* @__PURE__ */ jsx5("button", { type: "button", className: "cfi-ac-chip", disabled: handlers.busy, onClick: () => handlers.answer(o), children: o }, o)) })
822
+ ] });
823
+ case "followups":
824
+ return /* @__PURE__ */ jsxs3("div", { className: "cfi-ac-block cfi-ac-followups", children: [
825
+ block.didYouMean && /* @__PURE__ */ jsx5("span", { className: "cfi-ac-followups__label", children: "Did you mean" }),
826
+ /* @__PURE__ */ jsx5("div", { className: "cfi-ac-chips", children: block.items.map((t) => /* @__PURE__ */ jsx5(
827
+ "button",
828
+ {
829
+ type: "button",
830
+ className: "cfi-ac-chip",
831
+ "data-variant": block.didYouMean ? "rewrite" : void 0,
832
+ disabled: !block.didYouMean && handlers.busy,
833
+ onClick: () => block.didYouMean ? handlers.load(t) : handlers.send(t),
834
+ children: t
835
+ },
836
+ t
837
+ )) })
838
+ ] });
839
+ case "draft":
840
+ return /* @__PURE__ */ jsx5(DraftBlock, { block });
841
+ case "citation":
842
+ return /* @__PURE__ */ jsx5("div", { className: "cfi-ac-block cfi-ac-citations", children: block.items.map((c, i) => {
843
+ const href = safeUrl(c.url);
844
+ return /* @__PURE__ */ jsxs3(
845
+ "a",
846
+ {
847
+ className: "cfi-ac-citation",
848
+ href,
849
+ target: "_blank",
850
+ rel: "noopener noreferrer",
851
+ "aria-disabled": href ? void 0 : true,
852
+ children: [
853
+ /* @__PURE__ */ jsx5(BookOpen, { size: 13, "aria-hidden": true }),
854
+ /* @__PURE__ */ jsxs3("span", { children: [
855
+ c.title,
856
+ c.section && /* @__PURE__ */ jsxs3("span", { className: "cfi-ac-citation__section", children: [
857
+ " \xB7 ",
858
+ c.section
859
+ ] }),
860
+ /* @__PURE__ */ jsx5("span", { className: "cfi-ac-sr-only", children: " (opens in a new tab)" })
861
+ ] }),
862
+ /* @__PURE__ */ jsx5(ExternalLink, { size: 12, "aria-hidden": true })
863
+ ]
864
+ },
865
+ i
866
+ );
867
+ }) });
868
+ default:
869
+ return null;
870
+ }
871
+ }
872
+
873
+ // src/blocks/Markdown.tsx
874
+ import ReactMarkdown from "react-markdown";
875
+ import remarkGfm from "remark-gfm";
876
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
877
+ var components = {
878
+ a: ({ href, children }) => /* @__PURE__ */ jsxs4("a", { href, target: "_blank", rel: "noopener noreferrer", children: [
879
+ children,
880
+ /* @__PURE__ */ jsx6("span", { className: "cfi-ac-sr-only", children: " (opens in a new tab)" })
881
+ ] }),
882
+ // The panel's own heading is an h2 and each question an h3, so the
883
+ // answer's headings sit under them whatever level the model wrote.
884
+ h1: ({ children }) => /* @__PURE__ */ jsx6("h4", { children }),
885
+ h2: ({ children }) => /* @__PURE__ */ jsx6("h4", { children }),
886
+ h3: ({ children }) => /* @__PURE__ */ jsx6("h4", { children }),
887
+ // Scrollable regions take keyboard focus, so arrow keys can scroll them.
888
+ table: ({ children }) => /* @__PURE__ */ jsx6("div", { className: "cfi-ac-md-table", tabIndex: 0, role: "region", "aria-label": "Table", children: /* @__PURE__ */ jsx6("table", { children }) }),
889
+ pre: ({ children }) => /* @__PURE__ */ jsx6("pre", { tabIndex: 0, children })
890
+ };
891
+ function Markdown({ text }) {
892
+ return /* @__PURE__ */ jsx6("div", { className: "cfi-ac-md", children: /* @__PURE__ */ jsx6(ReactMarkdown, { remarkPlugins: [remarkGfm], components, children: text }) });
893
+ }
894
+
895
+ // src/blocks/WriteCard.tsx
896
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState5 } from "react";
897
+ import { Check as Check2, ChevronDown, ChevronRight, CircleAlert, Clock, Loader2, Paperclip, X } from "lucide-react";
898
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
899
+ function humanizeKey(key) {
900
+ const spaced = key.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim().toLowerCase();
901
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
902
+ }
903
+ function show(value) {
904
+ if (value === null || value === void 0 || value === "") return "\u2014";
905
+ if (typeof value === "object") return JSON.stringify(value);
906
+ return String(value);
907
+ }
908
+ function formatBytes(bytes) {
909
+ if (bytes < 1024) return `${bytes} B`;
910
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
911
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
912
+ }
913
+ var STATUS = {
914
+ succeeded: { text: "Done", tone: "good" },
915
+ failed: { text: "Not changed", tone: "bad" },
916
+ canceled: { text: "Canceled. Nothing was changed.", tone: "neutral" },
917
+ discarded: { text: "Not confirmed. Nothing was changed.", tone: "neutral" },
918
+ expired: { text: "This proposal expired. Ask again to make the change.", tone: "neutral" }
919
+ };
920
+ function WriteCard({
921
+ item,
922
+ onConfirm,
923
+ onCancel
924
+ }) {
925
+ const { write, state } = item;
926
+ const keys = Array.from(/* @__PURE__ */ new Set([...Object.keys(write.before ?? {}), ...Object.keys(write.after ?? {})]));
927
+ const live = state === "pending" || state === "confirming";
928
+ const status = STATUS[state];
929
+ const [unfolded, setUnfolded] = useState5(false);
930
+ const toggle = useRef2(null);
931
+ const moveFocus = useRef2(false);
932
+ const wasLive = useRef2(live);
933
+ useEffect3(() => {
934
+ if (wasLive.current && !live) {
935
+ if (status) announce(status.text);
936
+ if (!document.activeElement || document.activeElement === document.body) moveFocus.current = true;
937
+ }
938
+ wasLive.current = live;
939
+ }, [live, status]);
940
+ useEffect3(() => {
941
+ if (!moveFocus.current) return;
942
+ moveFocus.current = false;
943
+ toggle.current?.focus();
944
+ });
945
+ const setFold = (next) => {
946
+ moveFocus.current = true;
947
+ setUnfolded(next);
948
+ };
949
+ if (!live && status && state !== "failed" && !unfolded) {
950
+ return /* @__PURE__ */ jsx7("div", { className: "cfi-ac-write cfi-ac-write--folded", "data-state": state, role: "group", "aria-label": "Change", children: /* @__PURE__ */ jsxs5("button", { ref: toggle, type: "button", className: "cfi-ac-write__fold", "aria-expanded": false, onClick: () => setFold(true), children: [
951
+ /* @__PURE__ */ jsx7(ChevronRight, { size: 13, "aria-hidden": true }),
952
+ /* @__PURE__ */ jsx7("span", { className: "cfi-ac-write__fold-mark", "data-tone": status.tone, children: state === "succeeded" ? /* @__PURE__ */ jsx7(Check2, { size: 13, "aria-hidden": true }) : /* @__PURE__ */ jsx7(Clock, { size: 13, "aria-hidden": true }) }),
953
+ /* @__PURE__ */ jsx7("span", { className: "cfi-ac-write__fold-text", children: write.summary }),
954
+ /* @__PURE__ */ jsx7("span", { className: "cfi-ac-write__fold-status", "data-tone": status.tone, children: state === "succeeded" ? "Done" : state === "canceled" ? "Canceled" : "Not made" })
955
+ ] }) });
956
+ }
957
+ return /* @__PURE__ */ jsxs5("div", { className: "cfi-ac-write", "data-state": state, role: "group", "aria-label": "Proposed change", children: [
958
+ !live && status && state !== "failed" && /* @__PURE__ */ jsxs5("button", { ref: toggle, type: "button", className: "cfi-ac-write__fold cfi-ac-write__fold--close", "aria-expanded": true, onClick: () => setFold(false), children: [
959
+ /* @__PURE__ */ jsx7(ChevronDown, { size: 13, "aria-hidden": true }),
960
+ "Hide details"
961
+ ] }),
962
+ /* @__PURE__ */ jsxs5("header", { className: "cfi-ac-write__head", children: [
963
+ /* @__PURE__ */ jsx7("span", { className: "cfi-ac-write__kicker", children: live ? "Confirm this change" : "Proposed change" }),
964
+ /* @__PURE__ */ jsx7("p", { className: "cfi-ac-write__summary", children: write.summary }),
965
+ write.entity.label && !write.summary.includes(write.entity.label) && /* @__PURE__ */ jsx7("p", { className: "cfi-ac-write__entity", children: write.entity.label })
966
+ ] }),
967
+ keys.length > 0 && /* @__PURE__ */ jsxs5("table", { className: "cfi-ac-write__diff", children: [
968
+ /* @__PURE__ */ jsx7("thead", { children: /* @__PURE__ */ jsxs5("tr", { children: [
969
+ /* @__PURE__ */ jsx7("th", { scope: "col", children: "Field" }),
970
+ /* @__PURE__ */ jsx7("th", { scope: "col", children: "Now" }),
971
+ /* @__PURE__ */ jsx7("th", { scope: "col", children: "After" })
972
+ ] }) }),
973
+ /* @__PURE__ */ jsx7("tbody", { children: keys.map((k) => {
974
+ const before = write.before?.[k];
975
+ const after = write.after?.[k];
976
+ return /* @__PURE__ */ jsxs5("tr", { children: [
977
+ /* @__PURE__ */ jsx7("th", { scope: "row", children: humanizeKey(k) }),
978
+ /* @__PURE__ */ jsx7("td", { className: "cfi-ac-write__before", children: show(before) }),
979
+ /* @__PURE__ */ jsx7("td", { className: "cfi-ac-write__after", "data-changed": show(before) !== show(after) || void 0, children: show(after) })
980
+ ] }, k);
981
+ }) })
982
+ ] }),
983
+ write.file && /* @__PURE__ */ jsxs5("p", { className: "cfi-ac-write__file", children: [
984
+ /* @__PURE__ */ jsx7(Paperclip, { size: 13, "aria-hidden": true }),
985
+ /* @__PURE__ */ jsx7("span", { children: write.file.name }),
986
+ /* @__PURE__ */ jsx7("span", { className: "cfi-ac-muted", children: formatBytes(write.file.size) })
987
+ ] }),
988
+ item.error && (state === "pending" || state === "failed") && /* @__PURE__ */ jsxs5("p", { className: "cfi-ac-write__error", role: "alert", children: [
989
+ /* @__PURE__ */ jsx7(CircleAlert, { size: 14, "aria-hidden": true }),
990
+ item.error
991
+ ] }),
992
+ live ? /* @__PURE__ */ jsxs5("div", { className: "cfi-ac-write__buttons", children: [
993
+ /* @__PURE__ */ jsxs5(
994
+ "button",
995
+ {
996
+ type: "button",
997
+ className: "cfi-ac-btn",
998
+ "data-variant": "primary",
999
+ "aria-disabled": state === "confirming" || void 0,
1000
+ onClick: () => state === "pending" && onConfirm(),
1001
+ children: [
1002
+ state === "confirming" ? /* @__PURE__ */ jsx7(Loader2, { className: "cfi-ac-spin", size: 14, "aria-hidden": true }) : /* @__PURE__ */ jsx7(Check2, { size: 14, "aria-hidden": true }),
1003
+ state === "confirming" ? write.file ? "Uploading and saving\u2026" : "Saving\u2026" : "Confirm"
1004
+ ]
1005
+ }
1006
+ ),
1007
+ /* @__PURE__ */ jsxs5(
1008
+ "button",
1009
+ {
1010
+ type: "button",
1011
+ className: "cfi-ac-btn",
1012
+ "data-variant": "secondary",
1013
+ "aria-disabled": state === "confirming" || void 0,
1014
+ onClick: () => state === "pending" && onCancel(),
1015
+ children: [
1016
+ /* @__PURE__ */ jsx7(X, { size: 14, "aria-hidden": true }),
1017
+ "Cancel"
1018
+ ]
1019
+ }
1020
+ )
1021
+ ] }) : status && /* @__PURE__ */ jsxs5("p", { className: "cfi-ac-write__status", "data-tone": status.tone, role: "status", children: [
1022
+ state === "succeeded" ? /* @__PURE__ */ jsx7(Check2, { size: 14, "aria-hidden": true }) : state === "failed" ? /* @__PURE__ */ jsx7(CircleAlert, { size: 14, "aria-hidden": true }) : /* @__PURE__ */ jsx7(Clock, { size: 14, "aria-hidden": true }),
1023
+ status.text
1024
+ ] })
1025
+ ] });
1026
+ }
1027
+
1028
+ // #style-inject:#style-inject
1029
+ function styleInject(css, { insertAt } = {}) {
1030
+ if (!css || typeof document === "undefined") return;
1031
+ const head = document.head || document.getElementsByTagName("head")[0];
1032
+ const style = document.createElement("style");
1033
+ style.type = "text/css";
1034
+ if (insertAt === "top") {
1035
+ if (head.firstChild) {
1036
+ head.insertBefore(style, head.firstChild);
1037
+ } else {
1038
+ head.appendChild(style);
1039
+ }
1040
+ } else {
1041
+ head.appendChild(style);
1042
+ }
1043
+ if (style.styleSheet) {
1044
+ style.styleSheet.cssText = css;
1045
+ } else {
1046
+ style.appendChild(document.createTextNode(css));
1047
+ }
1048
+ }
1049
+
1050
+ // src/styles.css
1051
+ styleInject('@layer components {\n .cfi-ac-root {\n --cfi-ac-bg: var(--cfi-surface, #ffffff);\n --cfi-ac-bg-muted: var(--cfi-surface-muted, #f9fafb);\n --cfi-ac-ground: var(--cfi-surface-sunken, #f9fafb);\n --cfi-ac-bg-hover: var(--cfi-surface-hover, #f3f4f6);\n --cfi-ac-raised: var(--cfi-surface-raised, #ffffff);\n --cfi-ac-fg: var(--cfi-content, #111827);\n --cfi-ac-fg-muted: var(--cfi-content-muted, #4b5563);\n --cfi-ac-fg-subtle: var(--cfi-content-muted, #6b7280);\n --cfi-ac-border: var(--cfi-border, #e5e7eb);\n --cfi-ac-border-subtle: var(--cfi-border-subtle, #f3f4f6);\n --cfi-ac-action: var(--cfi-action, #2563eb);\n --cfi-ac-action-fg: var(--cfi-action-fg, #ffffff);\n --cfi-ac-action-hover: var(--cfi-action-hover, #1d4ed8);\n --cfi-ac-action-soft: var(--cfi-action-soft, #eff6ff);\n --cfi-ac-accent: var(--cfi-ai-accent, var(--cfi-action, #2563eb));\n --cfi-ac-accent-soft: var(--cfi-ai-accent-soft, var(--cfi-action-soft, #eff6ff));\n --cfi-ac-radius: var(--cfi-radius-md, 0.375rem);\n --cfi-ac-radius-lg: var(--cfi-radius-lg, 0.5rem);\n color: var(--cfi-ac-fg);\n font-family: var(--cfi-font-family, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif);\n font-size: var(--cfi-font-size-sm, 0.875rem);\n line-height: var(--cfi-line-height-normal, 1.5);\n }\n .cfi-ac-root *,\n .cfi-ac-root *::before,\n .cfi-ac-root *::after {\n box-sizing: border-box;\n }\n .cfi-ac-root :where(button) {\n font: inherit;\n color: inherit;\n }\n .cfi-ac-header-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: transparent;\n color: var(--cfi-color-gray-600, #4b5563);\n cursor: pointer;\n transition: background var(--cfi-duration-fast, 150ms), color var(--cfi-duration-fast, 150ms);\n }\n .cfi-ac-header-btn:hover {\n background: var(--cfi-ac-bg-hover);\n color: var(--cfi-ac-accent);\n }\n .cfi-ac-header-btn[aria-pressed=true] {\n background: var(--cfi-ac-accent-soft);\n color: var(--cfi-ac-accent);\n }\n .cfi-ac-header-btn:focus-visible,\n .cfi-ac-root :where(button, a, textarea, [tabindex]):focus-visible {\n outline: var(--cfi-focus-ring-width, 2px) solid var(--cfi-focus-ring-color, var(--cfi-ac-action));\n outline-offset: var(--cfi-focus-ring-offset, 2px);\n }\n .cfi-ac-panel {\n position: relative;\n display: flex;\n flex: 0 0 auto;\n flex-direction: column;\n align-self: stretch;\n min-height: 0;\n max-width: calc(100vw - 320px);\n min-width: 360px;\n border-left: 1px solid var(--cfi-ac-border);\n background: var(--cfi-ac-bg);\n }\n .cfi-ac-resize {\n position: absolute;\n top: 0;\n left: -4px;\n z-index: 2;\n width: 8px;\n height: 100%;\n cursor: col-resize;\n }\n .cfi-ac-resize:hover {\n background: color-mix(in srgb, var(--cfi-ac-accent) 25%, transparent);\n }\n .cfi-ac-resize:focus-visible {\n background: var(--cfi-ac-action);\n outline: none;\n }\n .cfi-ac-head {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n gap: var(--cfi-spacing-sm, 0.5rem);\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem);\n border-bottom: 1px solid var(--cfi-ac-border);\n }\n .cfi-ac-head__mark {\n display: grid;\n place-items: center;\n width: 28px;\n height: 28px;\n border-radius: var(--cfi-ac-radius);\n background: var(--cfi-ac-accent-soft);\n color: var(--cfi-ac-accent);\n }\n .cfi-ac-head__title {\n margin: 0;\n font-size: var(--cfi-font-size-base, 1rem);\n font-weight: var(--cfi-font-weight-semibold, 600);\n }\n .cfi-ac-head__tools {\n display: flex;\n gap: 2px;\n margin-left: auto;\n }\n .cfi-ac-icon-btn {\n display: inline-grid;\n place-items: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border: none;\n border-radius: var(--cfi-ac-radius);\n background: transparent;\n color: var(--cfi-ac-fg-muted);\n cursor: pointer;\n }\n .cfi-ac-icon-btn:hover:not(:disabled) {\n background: var(--cfi-ac-bg-hover);\n color: var(--cfi-ac-fg);\n }\n .cfi-ac-icon-btn[aria-pressed=true] {\n background: var(--cfi-ac-action-soft);\n color: var(--cfi-ac-action);\n }\n .cfi-ac-icon-btn:disabled {\n opacity: 0.4;\n cursor: default;\n }\n .cfi-ac-muted {\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-changes {\n flex: 1 1 auto;\n min-height: 0;\n overflow-y: auto;\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem) var(--cfi-spacing-md, 1rem);\n background: var(--cfi-ac-ground);\n }\n .cfi-ac-panel[data-expanded] .cfi-ac-changes {\n padding-inline: max(var(--cfi-spacing-md, 1rem), calc((100% - 52rem) / 2));\n }\n .cfi-ac-changes__back {\n display: inline-flex;\n align-items: center;\n gap: var(--cfi-spacing-xs, 0.25rem);\n margin: 0 0 var(--cfi-spacing-sm, 0.5rem);\n padding: var(--cfi-spacing-xs, 0.25rem) 0;\n border: none;\n background: transparent;\n color: var(--cfi-ac-action);\n font: inherit;\n font-size: var(--cfi-font-size-sm, 0.875rem);\n cursor: pointer;\n }\n .cfi-ac-changes__back:hover {\n text-decoration: underline;\n }\n .cfi-ac-changes__title {\n margin: 0 0 var(--cfi-spacing-sm, 0.5rem);\n font-size: var(--cfi-font-size-sm, 0.875rem);\n font-weight: var(--cfi-font-weight-semibold, 600);\n color: var(--cfi-ac-fg);\n }\n .cfi-ac-changes p {\n margin: 0;\n }\n .cfi-ac-changes__list {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 0.5rem);\n margin: 0;\n padding: 0;\n list-style: none;\n }\n .cfi-ac-changes__list li {\n display: grid;\n gap: 2px;\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem);\n border: 1px solid var(--cfi-ac-border);\n border-left: 3px solid var(--cfi-color-success, #10b981);\n border-radius: var(--cfi-ac-radius-lg);\n background: var(--cfi-ac-raised);\n }\n .cfi-ac-changes__list li[data-outcome=failed] {\n border-left-color: var(--cfi-color-error, #ef4444);\n }\n .cfi-ac-changes__what {\n font-weight: var(--cfi-font-weight-medium, 500);\n }\n .cfi-ac-changes__meta,\n .cfi-ac-changes__q {\n font-size: var(--cfi-font-size-xs, 0.75rem);\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-timeline {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n gap: var(--cfi-spacing-md, 1rem);\n min-height: 0;\n overflow-y: auto;\n padding: var(--cfi-spacing-md, 1rem);\n background: var(--cfi-ac-ground);\n }\n .cfi-ac-empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n margin: auto 0;\n padding: var(--cfi-spacing-lg, 1.5rem) var(--cfi-spacing-sm, 0.5rem);\n text-align: center;\n }\n .cfi-ac-empty__mark {\n display: grid;\n place-items: center;\n width: 44px;\n height: 44px;\n margin-bottom: var(--cfi-spacing-sm, 0.5rem);\n border-radius: 50%;\n background: var(--cfi-ac-accent-soft);\n color: var(--cfi-ac-accent);\n }\n .cfi-ac-empty__title {\n margin: 0;\n font-size: var(--cfi-font-size-base, 1rem);\n font-weight: var(--cfi-font-weight-semibold, 600);\n }\n .cfi-ac-empty__sub {\n max-width: 32ch;\n margin: var(--cfi-spacing-2xs, 0.125rem) 0 var(--cfi-spacing-md, 1rem);\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-empty .cfi-ac-chips {\n align-self: stretch;\n justify-content: flex-start;\n }\n .cfi-ac-card {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 0.5rem);\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem) var(--cfi-spacing-md, 1rem);\n border: 1px solid var(--cfi-ac-border);\n border-radius: var(--cfi-ac-radius-lg);\n background: var(--cfi-ac-raised);\n box-shadow: var(--cfi-shadow-xs, 0 1px 2px rgb(0 0 0 / 0.05));\n animation: cfi-ac-rise 200ms var(--cfi-easing, ease-out) both;\n }\n .cfi-ac-exchange {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 0.5rem);\n }\n .cfi-ac-exchange[data-reply] {\n padding-top: var(--cfi-spacing-sm, 0.5rem);\n }\n .cfi-ac-exchange:not([data-reply]) > .cfi-ac-q {\n margin: calc(-1 * var(--cfi-spacing-sm, 0.5rem)) calc(-1 * var(--cfi-spacing-md, 1rem)) var(--cfi-spacing-xs, 0.25rem);\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem);\n border-radius: var(--cfi-ac-radius-lg) var(--cfi-ac-radius-lg) 0 0;\n background: var(--cfi-ac-action);\n color: var(--cfi-ac-action-fg);\n }\n .cfi-ac-exchange[data-reply] > .cfi-ac-q {\n align-self: flex-end;\n max-width: 85%;\n padding: var(--cfi-spacing-xs, 0.25rem) var(--cfi-spacing-sm, 0.5rem);\n border-radius: var(--cfi-ac-radius-lg) var(--cfi-ac-radius-lg) var(--cfi-ac-radius) var(--cfi-ac-radius-lg);\n background: var(--cfi-ac-action);\n color: var(--cfi-ac-action-fg);\n }\n .cfi-ac-q .cfi-ac-q__where,\n .cfi-ac-q .cfi-ac-q__files li {\n color: var(--cfi-ac-action-fg);\n }\n .cfi-ac-q .cfi-ac-q__files li {\n background: color-mix(in srgb, var(--cfi-ac-action-fg) 18%, transparent);\n }\n .cfi-ac-q__text {\n margin: 0;\n font-size: inherit;\n font-weight: var(--cfi-font-weight-semibold, 600);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n }\n .cfi-ac-q__where,\n .cfi-ac-composer__page {\n display: flex;\n align-items: center;\n gap: 4px;\n margin: 2px 0 0;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-q__files {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n margin: 4px 0 0;\n padding: 0;\n list-style: none;\n }\n .cfi-ac-q__files li {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n padding: 1px 6px;\n border-radius: var(--cfi-radius-full, 9999px);\n background: var(--cfi-ac-bg-muted);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n color: var(--cfi-ac-fg-muted);\n }\n .cfi-ac-a {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 0.5rem);\n min-width: 0;\n }\n .cfi-ac-thinking,\n .cfi-ac-steps--live {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-steps__toggle {\n display: inline-flex;\n align-items: center;\n min-height: 24px;\n gap: 2px;\n padding: 0;\n border: none;\n background: none;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n color: var(--cfi-ac-fg-subtle);\n cursor: pointer;\n }\n .cfi-ac-steps__toggle:hover {\n color: var(--cfi-ac-fg);\n }\n .cfi-ac-steps__list {\n margin: 4px 0 0;\n padding-left: 1.75rem;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n color: var(--cfi-ac-fg-muted);\n }\n .cfi-ac-error,\n .cfi-ac-write__error {\n display: flex;\n align-items: flex-start;\n gap: 6px;\n margin: 0;\n padding: var(--cfi-spacing-xs, 0.25rem) var(--cfi-spacing-sm, 0.5rem);\n border: 1px solid var(--cfi-color-error-border, #fecaca);\n border-radius: var(--cfi-ac-radius);\n background: var(--cfi-color-error-soft, #fef2f2);\n color: var(--cfi-color-error-text, #991b1b);\n }\n .cfi-ac-error svg,\n .cfi-ac-write__error svg {\n flex: 0 0 auto;\n margin-top: 3px;\n }\n .cfi-ac-md {\n overflow-wrap: anywhere;\n }\n .cfi-ac-md > :first-child {\n margin-top: 0;\n }\n .cfi-ac-md > :last-child {\n margin-bottom: 0;\n }\n .cfi-ac-md p,\n .cfi-ac-md ul,\n .cfi-ac-md ol {\n margin: 0.5em 0;\n }\n .cfi-ac-md ul,\n .cfi-ac-md ol {\n padding-left: 1.25rem;\n }\n .cfi-ac-md h1,\n .cfi-ac-md h2,\n .cfi-ac-md h3,\n .cfi-ac-md h4 {\n margin: 0.75em 0 0.25em;\n font-size: var(--cfi-font-size-sm, 0.875rem);\n font-weight: var(--cfi-font-weight-semibold, 600);\n }\n .cfi-ac-md a,\n .cfi-ac-citation {\n color: var(--cfi-content-link, var(--cfi-ac-action));\n }\n .cfi-ac-md code {\n padding: 0.1em 0.3em;\n border-radius: 4px;\n background: var(--cfi-ac-bg-muted);\n font-family: var(--cfi-font-family-mono, ui-monospace, monospace);\n font-size: 0.9em;\n }\n .cfi-ac-md pre {\n overflow-x: auto;\n padding: var(--cfi-spacing-sm, 0.5rem);\n border-radius: var(--cfi-ac-radius);\n background: var(--cfi-ac-bg-muted);\n }\n .cfi-ac-md pre code {\n padding: 0;\n background: none;\n }\n .cfi-ac-md blockquote {\n margin: 0.5em 0;\n padding-left: 0.75em;\n border-left: 3px solid var(--cfi-ac-border);\n color: var(--cfi-ac-fg-muted);\n }\n .cfi-ac-md-table,\n .cfi-ac-table__scroll {\n overflow-x: auto;\n border: 1px solid var(--cfi-ac-border);\n border-radius: var(--cfi-ac-radius);\n }\n .cfi-ac-md-table {\n margin: 0.5em 0;\n }\n .cfi-ac-md table,\n .cfi-ac-table table,\n .cfi-ac-write__diff {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n font-variant-numeric: tabular-nums;\n }\n .cfi-ac-md th,\n .cfi-ac-md td,\n .cfi-ac-table th,\n .cfi-ac-table td {\n padding: 6px 8px;\n border-bottom: 1px solid var(--cfi-ac-border-subtle);\n text-align: left;\n vertical-align: top;\n }\n .cfi-ac-md th,\n .cfi-ac-table th {\n background: var(--cfi-ac-bg-muted);\n font-weight: var(--cfi-font-weight-medium, 500);\n color: var(--cfi-ac-fg-muted);\n white-space: nowrap;\n }\n .cfi-ac-md tr:last-child td,\n .cfi-ac-table tr:last-child td {\n border-bottom: none;\n }\n .cfi-ac-block {\n margin: 0;\n min-width: 0;\n }\n .cfi-ac-block__title {\n margin-bottom: 4px;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n font-weight: var(--cfi-font-weight-semibold, 600);\n color: var(--cfi-ac-fg-muted);\n }\n .cfi-ac-table [data-numeric] {\n text-align: right;\n }\n .cfi-ac-table td[data-fit=short] {\n white-space: nowrap;\n }\n .cfi-ac-table td[data-fit=long] {\n min-width: 11rem;\n }\n .cfi-ac-table th button {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 0;\n border: none;\n background: none;\n font-weight: inherit;\n cursor: pointer;\n }\n .cfi-ac-table th[data-numeric] button {\n flex-direction: row-reverse;\n }\n .cfi-ac-table th button:hover {\n color: var(--cfi-ac-fg);\n }\n .cfi-ac-status {\n display: inline-block;\n padding: 0 6px;\n border-radius: var(--cfi-radius-full, 9999px);\n background: var(--cfi-color-neutral-soft, #f3f4f6);\n color: var(--cfi-color-neutral-text, #374151);\n white-space: nowrap;\n }\n .cfi-ac-status[data-tone=good] {\n background: var(--cfi-color-success-soft, #ecfdf5);\n color: var(--cfi-color-success-text, #065f46);\n }\n .cfi-ac-status[data-tone=bad] {\n background: var(--cfi-color-error-soft, #fef2f2);\n color: var(--cfi-color-error-text, #991b1b);\n }\n .cfi-ac-status[data-tone=warn] {\n background: var(--cfi-color-warning-soft, #fffbeb);\n color: var(--cfi-color-warning-text, #92400e);\n }\n .cfi-ac-chart__frame {\n width: 100%;\n padding: 4px 0;\n }\n .cfi-ac-chart--loading {\n height: 260px;\n border-radius: var(--cfi-ac-radius);\n background: var(--cfi-ac-bg-muted);\n }\n .cfi-ac-kpis {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));\n gap: var(--cfi-spacing-xs, 0.25rem);\n }\n .cfi-ac-kpi {\n display: flex;\n flex-direction: column;\n gap: 2px;\n padding: var(--cfi-spacing-sm, 0.5rem);\n border: 1px solid var(--cfi-ac-border);\n border-radius: var(--cfi-ac-radius);\n }\n .cfi-ac-kpi__label {\n font-size: var(--cfi-font-size-xs, 0.75rem);\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-kpi__value {\n font-size: var(--cfi-font-size-lg, 1.125rem);\n font-weight: var(--cfi-font-weight-semibold, 600);\n font-variant-numeric: tabular-nums;\n }\n .cfi-ac-kpi__change {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-kpi[data-tone=good] .cfi-ac-kpi__change {\n color: var(--cfi-color-success-text, #065f46);\n }\n .cfi-ac-kpi[data-tone=bad] .cfi-ac-kpi__change {\n color: var(--cfi-color-error-text, #991b1b);\n }\n .cfi-ac-entities {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .cfi-ac-entity {\n display: grid;\n grid-template-columns: auto 1fr auto;\n grid-template-areas: "type label go" "type sub go";\n align-items: center;\n column-gap: var(--cfi-spacing-sm, 0.5rem);\n width: 100%;\n padding: 6px var(--cfi-spacing-sm, 0.5rem);\n border: 1px solid var(--cfi-ac-border);\n border-radius: var(--cfi-ac-radius);\n background: var(--cfi-ac-bg);\n text-align: left;\n cursor: pointer;\n }\n .cfi-ac-entity:hover {\n border-color: var(--cfi-ac-action);\n background: var(--cfi-ac-action-soft);\n }\n .cfi-ac-entity__type {\n grid-area: type;\n padding: 1px 6px;\n border-radius: 4px;\n background: var(--cfi-ac-bg-muted);\n font-size: 11px;\n text-transform: capitalize;\n color: var(--cfi-ac-fg-muted);\n }\n .cfi-ac-entity__label {\n grid-area: label;\n font-weight: var(--cfi-font-weight-medium, 500);\n overflow-wrap: anywhere;\n }\n .cfi-ac-entity__sub {\n grid-area: sub;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-entity__go {\n grid-area: go;\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-actions,\n .cfi-ac-write__buttons {\n display: flex;\n flex-wrap: wrap;\n gap: var(--cfi-spacing-xs, 0.25rem);\n }\n .cfi-ac-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n height: 32px;\n padding: 0 12px;\n border: 1px solid var(--cfi-ac-border);\n border-radius: var(--cfi-ac-radius);\n background: var(--cfi-ac-bg);\n font-weight: var(--cfi-font-weight-medium, 500);\n cursor: pointer;\n }\n .cfi-ac-btn:hover:not(:disabled) {\n background: var(--cfi-ac-bg-hover);\n }\n .cfi-ac-btn[data-variant=primary] {\n border-color: var(--cfi-ac-action);\n background: var(--cfi-ac-action);\n color: var(--cfi-ac-action-fg);\n }\n .cfi-ac-btn[data-variant=primary]:hover:not(:disabled) {\n background: var(--cfi-ac-action-hover);\n }\n .cfi-ac-btn:disabled {\n opacity: 0.6;\n cursor: default;\n }\n .cfi-ac-chips {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n }\n .cfi-ac-chip {\n padding: 4px 10px;\n border: 1px solid var(--cfi-ac-border);\n border-radius: var(--cfi-radius-full, 9999px);\n background: var(--cfi-ac-bg);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n text-align: left;\n cursor: pointer;\n }\n .cfi-ac-chip:hover:not(:disabled) {\n border-color: var(--cfi-ac-action);\n background: var(--cfi-ac-action-soft);\n }\n .cfi-ac-chip[data-variant=rewrite] {\n border-style: dashed;\n }\n .cfi-ac-chip:disabled {\n opacity: 0.5;\n cursor: default;\n }\n .cfi-ac-question {\n padding: var(--cfi-spacing-sm, 0.5rem);\n border-left: 3px solid var(--cfi-ac-accent);\n border-radius: 2px;\n background: var(--cfi-ac-accent-soft);\n }\n .cfi-ac-question__text {\n margin: 0 0 6px;\n font-weight: var(--cfi-font-weight-medium, 500);\n }\n .cfi-ac-followups__label {\n display: block;\n margin-bottom: 4px;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-citations {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n .cfi-ac-citation {\n display: inline-flex;\n align-items: center;\n min-height: 24px;\n gap: 6px;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n text-decoration: none;\n }\n .cfi-ac-citation:hover {\n text-decoration: underline;\n }\n .cfi-ac-citation[aria-disabled=true] {\n color: var(--cfi-ac-fg-subtle);\n pointer-events: none;\n }\n .cfi-ac-citation__section {\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-write {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-sm, 0.5rem);\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem) var(--cfi-spacing-md, 1rem);\n border: 1px solid var(--cfi-ac-action);\n border-radius: var(--cfi-ac-radius-lg);\n background: var(--cfi-ac-bg);\n }\n .cfi-ac-write:not([data-state=pending]):not([data-state=confirming]) {\n border-color: var(--cfi-ac-border);\n background: var(--cfi-ac-ground);\n }\n .cfi-ac-write__head p {\n margin: 0;\n }\n .cfi-ac-write__kicker {\n font-size: 11px;\n font-weight: var(--cfi-font-weight-semibold, 600);\n letter-spacing: var(--cfi-letter-spacing-wide, 0.025em);\n text-transform: uppercase;\n color: var(--cfi-ac-action);\n }\n .cfi-ac-write:not([data-state=pending]):not([data-state=confirming]) .cfi-ac-write__kicker {\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-write__summary {\n font-weight: var(--cfi-font-weight-semibold, 600);\n }\n .cfi-ac-write__entity {\n font-size: var(--cfi-font-size-xs, 0.75rem);\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-write__diff th,\n .cfi-ac-write__diff td {\n padding: 4px 6px;\n border-bottom: 1px solid var(--cfi-ac-border-subtle);\n text-align: left;\n vertical-align: top;\n overflow-wrap: anywhere;\n }\n .cfi-ac-write__diff thead th {\n font-weight: var(--cfi-font-weight-medium, 500);\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-write__diff tbody th {\n font-weight: var(--cfi-font-weight-medium, 500);\n color: var(--cfi-ac-fg-muted);\n white-space: nowrap;\n }\n .cfi-ac-write__before {\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-write__after[data-changed] {\n font-weight: var(--cfi-font-weight-semibold, 600);\n }\n .cfi-ac-write__file {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0;\n }\n .cfi-ac-write__status {\n display: flex;\n align-items: center;\n gap: 6px;\n margin: 0;\n font-weight: var(--cfi-font-weight-medium, 500);\n color: var(--cfi-ac-fg-muted);\n }\n .cfi-ac-write__status[data-tone=good] {\n color: var(--cfi-color-success-text, #065f46);\n }\n .cfi-ac-write__status[data-tone=bad] {\n color: var(--cfi-color-error-text, #991b1b);\n }\n .cfi-ac-composer__tools {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n }\n .cfi-ac-understood {\n margin: 0;\n color: var(--cfi-ac-fg-subtle);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n }\n .cfi-ac-understood__label {\n color: var(--cfi-ac-fg-muted);\n font-weight: var(--cfi-font-weight-medium, 500);\n }\n .cfi-ac-based {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--cfi-spacing-xs, 0.25rem);\n margin: 0;\n color: var(--cfi-ac-fg-subtle);\n font-size: 11px;\n }\n .cfi-ac-based__label {\n text-transform: uppercase;\n letter-spacing: 0.04em;\n }\n .cfi-ac-based__chip {\n display: inline-flex;\n align-items: center;\n min-height: 24px;\n padding: 1px 8px;\n border-radius: 999px;\n background: var(--cfi-ac-bg-hover);\n color: var(--cfi-ac-fg-muted);\n }\n a.cfi-ac-based__chip,\n .cfi-ac-based__chip--link {\n border: none;\n color: var(--cfi-ac-action);\n font: inherit;\n text-decoration: none;\n cursor: pointer;\n }\n a.cfi-ac-based__chip:hover,\n .cfi-ac-based__chip--link:hover {\n text-decoration: underline;\n }\n a.cfi-ac-based__chip:focus-visible {\n outline: 2px solid var(--cfi-ac-action);\n outline-offset: 1px;\n }\n .cfi-ac-elapsed {\n margin-left: auto;\n color: var(--cfi-ac-fg-subtle);\n font-variant-numeric: tabular-nums;\n }\n .cfi-ac-card {\n position: relative;\n }\n .cfi-ac-card__foldbtn {\n position: absolute;\n z-index: 1;\n color: var(--cfi-ac-action-fg);\n top: var(--cfi-spacing-xs, 0.25rem);\n right: var(--cfi-spacing-xs, 0.25rem);\n opacity: 0;\n transition: opacity 120ms ease;\n }\n .cfi-ac-card__foldbtn:hover:not(:disabled) {\n background: color-mix(in srgb, var(--cfi-ac-action-fg) 18%, transparent);\n color: var(--cfi-ac-action-fg);\n }\n .cfi-ac-card:hover .cfi-ac-card__foldbtn,\n .cfi-ac-card__foldbtn:focus-visible {\n opacity: 1;\n }\n .cfi-ac-card--folded {\n padding: 0;\n border-color: var(--cfi-ac-action);\n background: var(--cfi-ac-action);\n }\n .cfi-ac-card--folded .cfi-ac-card__fold {\n color: var(--cfi-ac-action-fg);\n }\n .cfi-ac-card__fold {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-xs, 0.25rem);\n width: 100%;\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem);\n border: none;\n background: transparent;\n color: var(--cfi-ac-fg);\n font: inherit;\n font-weight: var(--cfi-font-weight-semibold, 600);\n text-align: left;\n cursor: pointer;\n }\n .cfi-ac-card__fold > span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .cfi-ac-ready {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin: 0;\n padding: 2px 10px;\n border-radius: 999px;\n background: var(--cfi-ac-accent-soft);\n color: var(--cfi-ac-accent);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n }\n .cfi-ac-ready__dot {\n width: 6px;\n height: 6px;\n border-radius: 999px;\n background: var(--cfi-color-success, #10b981);\n }\n .cfi-ac-catalog__heading {\n align-self: stretch;\n margin: var(--cfi-spacing-sm, 0.5rem) 0 0;\n color: var(--cfi-ac-fg-subtle);\n font-size: 11px;\n font-weight: var(--cfi-font-weight-semibold, 600);\n letter-spacing: 0.04em;\n text-align: left;\n text-transform: uppercase;\n }\n .cfi-ac-catalog {\n display: flex;\n flex-direction: column;\n align-self: stretch;\n gap: var(--cfi-spacing-xs, 0.25rem);\n }\n .cfi-ac-catalog__group {\n border: 1px solid var(--cfi-ac-border);\n border-radius: var(--cfi-ac-radius-lg);\n background: var(--cfi-ac-raised);\n overflow: hidden;\n }\n .cfi-ac-catalog__toggle {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-sm, 0.5rem);\n width: 100%;\n padding: var(--cfi-spacing-sm, 0.5rem);\n border: none;\n background: transparent;\n color: var(--cfi-ac-fg);\n font: inherit;\n text-align: left;\n cursor: pointer;\n }\n .cfi-ac-catalog__name {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n min-width: 0;\n font-size: var(--cfi-font-size-sm, 0.875rem);\n font-weight: var(--cfi-font-weight-semibold, 600);\n }\n .cfi-ac-catalog__blurb {\n overflow: hidden;\n color: var(--cfi-ac-fg-subtle);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n font-weight: normal;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .cfi-ac-catalog__count {\n padding: 0 6px;\n border-radius: 999px;\n background: var(--cfi-ac-accent-soft);\n color: var(--cfi-ac-accent);\n font-size: 10px;\n font-weight: var(--cfi-font-weight-semibold, 600);\n }\n .cfi-ac-catalog__questions {\n display: flex;\n flex-direction: column;\n padding: var(--cfi-spacing-xs, 0.25rem);\n border-top: 1px solid var(--cfi-ac-border-subtle);\n background: var(--cfi-ac-ground);\n }\n .cfi-ac-catalog__q {\n display: flex;\n align-items: flex-start;\n gap: var(--cfi-spacing-xs, 0.25rem);\n padding: 6px var(--cfi-spacing-sm, 0.5rem);\n border: none;\n border-radius: var(--cfi-ac-radius);\n background: transparent;\n color: var(--cfi-ac-fg);\n font: inherit;\n font-size: var(--cfi-font-size-sm, 0.875rem);\n text-align: left;\n cursor: pointer;\n }\n .cfi-ac-catalog__q:hover {\n background: var(--cfi-ac-raised);\n color: var(--cfi-ac-accent);\n }\n .cfi-ac-catalog__q svg {\n flex: 0 0 auto;\n margin-top: 4px;\n }\n .cfi-ac-grounded {\n display: flex;\n align-self: stretch;\n gap: var(--cfi-spacing-xs, 0.25rem);\n margin: var(--cfi-spacing-sm, 0.5rem) 0 0;\n padding: 0 var(--cfi-spacing-xs, 0.25rem);\n color: var(--cfi-ac-fg-muted);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n text-align: left;\n }\n .cfi-ac-grounded svg {\n flex: 0 0 auto;\n margin-top: 2px;\n }\n .cfi-ac-grounded strong {\n color: var(--cfi-ac-fg);\n }\n .cfi-ac-write--folded {\n padding: 0;\n }\n .cfi-ac-write__fold {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-xs, 0.25rem);\n width: 100%;\n padding: var(--cfi-spacing-xs, 0.25rem) var(--cfi-spacing-sm, 0.5rem);\n border: none;\n background: transparent;\n color: var(--cfi-ac-fg-muted);\n font: inherit;\n font-size: var(--cfi-font-size-sm, 0.875rem);\n text-align: left;\n cursor: pointer;\n }\n .cfi-ac-write__fold:hover {\n color: var(--cfi-ac-fg);\n }\n .cfi-ac-write__fold--close {\n width: auto;\n padding: 0 0 var(--cfi-spacing-xs, 0.25rem);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n }\n .cfi-ac-write__fold-mark[data-tone=good],\n .cfi-ac-write__fold-status[data-tone=good] {\n color: var(--cfi-color-success-text, #065f46);\n }\n .cfi-ac-write__fold-text {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n color: var(--cfi-ac-fg);\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .cfi-ac-write__fold-status {\n flex: 0 0 auto;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n }\n .cfi-ac-confirm-all {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--cfi-spacing-sm, 0.5rem);\n color: var(--cfi-ac-fg-muted);\n font-size: var(--cfi-font-size-sm, 0.875rem);\n }\n .cfi-ac-cardwrap {\n display: flex;\n flex-direction: column;\n gap: var(--cfi-spacing-xs, 0.25rem);\n }\n .cfi-ac-time {\n align-self: flex-end;\n padding-right: var(--cfi-spacing-xs, 0.25rem);\n color: var(--cfi-ac-fg-subtle);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n }\n .cfi-ac-a__foot {\n display: flex;\n align-items: center;\n gap: var(--cfi-spacing-sm, 0.5rem);\n }\n .cfi-ac-a__foot:empty {\n display: none;\n }\n .cfi-ac-copy {\n margin-left: auto;\n opacity: 0;\n transition: opacity 120ms ease;\n }\n .cfi-ac-a:hover .cfi-ac-copy,\n .cfi-ac-copy:focus-visible {\n opacity: 1;\n }\n .cfi-ac-panel[data-expanded] {\n position: absolute;\n inset: 0;\n z-index: 30;\n max-width: none;\n min-width: 0;\n border-left: none;\n }\n .cfi-ac-panel[data-expanded] .cfi-ac-timeline {\n scrollbar-gutter: stable both-edges;\n }\n .cfi-ac-panel[data-expanded] .cfi-ac-timeline > * {\n width: 100%;\n max-width: 52rem;\n margin-inline: auto;\n }\n .cfi-ac-panel[data-expanded] .cfi-ac-page-starters,\n .cfi-ac-panel[data-expanded] .cfi-ac-composer {\n padding-inline: max(var(--cfi-spacing-md, 1rem), calc((100% - 52rem) / 2));\n }\n .cfi-ac-page-starters {\n flex: 0 0 auto;\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem) 0;\n border-top: 1px solid var(--cfi-ac-border);\n background: var(--cfi-ac-ground);\n }\n .cfi-ac-page-starters__label {\n display: block;\n margin-bottom: 4px;\n font-size: var(--cfi-font-size-xs, 0.75rem);\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-composer {\n flex: 0 0 auto;\n background: var(--cfi-ac-ground);\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-md, 1rem) var(--cfi-spacing-md, 1rem);\n }\n .cfi-ac-composer__page {\n margin: 0 0 4px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .cfi-ac-composer__files {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n margin: 0 0 6px;\n padding: 0;\n list-style: none;\n }\n .cfi-ac-composer__files li {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n max-width: 100%;\n padding: 2px 4px 2px 8px;\n border: 1px solid var(--cfi-ac-border);\n border-radius: var(--cfi-radius-full, 9999px);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n }\n .cfi-ac-composer__file-name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .cfi-ac-composer__files button {\n display: grid;\n place-items: center;\n width: 24px;\n height: 24px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: none;\n cursor: pointer;\n }\n .cfi-ac-composer__files button:hover {\n background: var(--cfi-ac-bg-hover);\n }\n .cfi-ac-composer__box {\n border: 1px solid var(--cfi-border-strong, #d1d5db);\n border-radius: var(--cfi-ac-radius-lg);\n background: var(--cfi-ac-raised);\n }\n .cfi-ac-composer__box:focus-within {\n border-color: var(--cfi-border-focus, var(--cfi-ac-action));\n }\n .cfi-ac-composer textarea {\n display: block;\n width: 100%;\n max-height: 140px;\n overflow-y: auto;\n padding: var(--cfi-spacing-sm, 0.5rem) var(--cfi-spacing-sm, 0.5rem) 0;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n resize: none;\n }\n .cfi-ac-composer textarea::placeholder {\n color: var(--cfi-ac-fg-subtle);\n opacity: 1;\n }\n .cfi-ac-composer textarea:focus-visible {\n outline: none;\n }\n .cfi-ac-composer__box:focus-within {\n outline: var(--cfi-focus-ring-width, 2px) solid var(--cfi-focus-ring-color, var(--cfi-ac-action));\n outline-offset: 1px;\n }\n .cfi-ac-composer__row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 2px 4px 4px;\n }\n .cfi-ac-send {\n display: grid;\n place-items: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border: none;\n border-radius: 50%;\n background: var(--cfi-ac-action);\n color: var(--cfi-ac-action-fg);\n cursor: pointer;\n }\n .cfi-ac-send:hover:not(:disabled) {\n background: var(--cfi-ac-action-hover);\n }\n .cfi-ac-send:disabled {\n background: var(--cfi-surface-disabled, #e5e7eb);\n color: var(--cfi-content-disabled, #9ca3af);\n cursor: default;\n }\n .cfi-ac-dropzone {\n position: absolute;\n inset: 8px;\n z-index: 3;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: var(--cfi-spacing-lg, 1.5rem);\n border: 2px dashed var(--cfi-ac-action);\n border-radius: var(--cfi-ac-radius-lg);\n background: color-mix(in srgb, var(--cfi-ac-bg) 92%, transparent);\n color: var(--cfi-ac-fg-muted);\n text-align: center;\n pointer-events: none;\n }\n @keyframes cfi-ac-rise {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: none;\n }\n }\n @keyframes cfi-ac-spin {\n to {\n transform: rotate(360deg);\n }\n }\n .cfi-ac-spin {\n animation: cfi-ac-spin 0.9s linear infinite;\n }\n @media (prefers-reduced-motion: reduce) {\n .cfi-ac-card {\n animation: none;\n }\n .cfi-ac-spin {\n animation: none;\n }\n .cfi-ac-copy,\n .cfi-ac-card__foldbtn {\n transition: none;\n }\n }\n @media (hover: none) {\n .cfi-ac-copy,\n .cfi-ac-card__foldbtn {\n opacity: 1;\n }\n }\n .cfi-ac-draft {\n border: 1px solid var(--cfi-ac-border);\n border-radius: var(--cfi-ac-radius-lg);\n background: var(--cfi-ac-raised);\n overflow: hidden;\n }\n .cfi-ac-draft__head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--cfi-spacing-sm, 0.5rem);\n padding: var(--cfi-spacing-xs, 0.25rem) var(--cfi-spacing-sm, 0.5rem);\n border-bottom: 1px solid var(--cfi-ac-border-subtle);\n }\n .cfi-ac-draft__head .cfi-ac-block__title {\n margin: 0;\n }\n .cfi-ac-draft__meta {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: 2px var(--cfi-spacing-sm, 0.5rem);\n margin: 0;\n padding: var(--cfi-spacing-xs, 0.25rem) var(--cfi-spacing-sm, 0.5rem);\n border-bottom: 1px solid var(--cfi-ac-border-subtle);\n font-size: var(--cfi-font-size-xs, 0.75rem);\n }\n .cfi-ac-draft__meta dt {\n color: var(--cfi-ac-fg-subtle);\n }\n .cfi-ac-draft__meta dd {\n margin: 0;\n }\n .cfi-ac-draft__body {\n max-height: 320px;\n padding: var(--cfi-spacing-sm, 0.5rem);\n overflow-y: auto;\n font-size: var(--cfi-font-size-sm, 0.875rem);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n }\n .cfi-ac-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0 0 0 0);\n white-space: nowrap;\n border: 0;\n }\n .cfi-ac-btn[aria-disabled=true] {\n cursor: progress;\n opacity: 0.7;\n }\n}\n');
1052
+
1053
+ // src/host/AiChatSurface.tsx
1054
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1055
+ function AiChatSurface() {
1056
+ const chat = useAiChat();
1057
+ if (!chat || !chat.open) return null;
1058
+ return /* @__PURE__ */ jsx8(Panel, { chat });
1059
+ }
1060
+ function Panel({ chat }) {
1061
+ const { cards, busy, width, setWidth, setOpen, startOver, pageContext, starters, expanded, setExpanded, catalog } = chat;
1062
+ const [showChanges, setShowChanges] = useState6(false);
1063
+ const [showIdeas, setShowIdeas] = useState6(false);
1064
+ const showCatalog = cards.length === 0 || showIdeas;
1065
+ const [dragging, setDragging] = useState6(false);
1066
+ const input = useRef3(null);
1067
+ const timeline = useRef3(null);
1068
+ const panel = useRef3(null);
1069
+ useEffect4(() => {
1070
+ input.current?.focus();
1071
+ return () => {
1072
+ if (document.activeElement === document.body || !document.activeElement) {
1073
+ document.querySelector("[data-cfi-ai-chat-button]")?.focus();
1074
+ }
1075
+ };
1076
+ }, []);
1077
+ useEffect4(() => {
1078
+ const root = panel.current;
1079
+ if (!root) return void 0;
1080
+ let last = null;
1081
+ const onIn = (e) => {
1082
+ last = e.target;
1083
+ };
1084
+ const rescue = () => {
1085
+ if (!last) return;
1086
+ if (last.isConnected && !last.disabled) return;
1087
+ if (document.activeElement && document.activeElement !== document.body) return;
1088
+ last = null;
1089
+ const target = input.current ?? root.querySelector("button:not(:disabled), a[href]");
1090
+ target?.focus();
1091
+ };
1092
+ const observer = new MutationObserver(() => window.setTimeout(rescue, 0));
1093
+ observer.observe(root, { childList: true, subtree: true, attributes: true, attributeFilter: ["disabled"] });
1094
+ root.addEventListener("focusin", onIn);
1095
+ return () => {
1096
+ observer.disconnect();
1097
+ root.removeEventListener("focusin", onIn);
1098
+ };
1099
+ }, []);
1100
+ useEffect4(() => {
1101
+ const root = panel.current;
1102
+ if (!expanded || !root?.parentElement) return void 0;
1103
+ const covered = Array.from(root.parentElement.children).filter((el) => el !== root && !el.hasAttribute("inert"));
1104
+ covered.forEach((el) => el.setAttribute("inert", ""));
1105
+ return () => covered.forEach((el) => el.removeAttribute("inert"));
1106
+ }, [expanded]);
1107
+ const streamingId = useRef3(null);
1108
+ useEffect4(() => {
1109
+ const exchanges = cards.flatMap((c) => c.exchanges);
1110
+ const live = exchanges.find((e) => e.status === "streaming");
1111
+ if (live && streamingId.current !== live.id) {
1112
+ streamingId.current = live.id;
1113
+ announce("Looking that up");
1114
+ } else if (!live && streamingId.current) {
1115
+ const done = exchanges.find((e) => e.id === streamingId.current);
1116
+ streamingId.current = null;
1117
+ if (done?.status === "done") announce("Answer ready");
1118
+ }
1119
+ }, [cards]);
1120
+ const pinned = useRef3(true);
1121
+ useEffect4(() => {
1122
+ const el = timeline.current;
1123
+ if (el && pinned.current) el.scrollTop = el.scrollHeight;
1124
+ }, [cards]);
1125
+ const loadDraft = useCallback2(
1126
+ (text) => {
1127
+ chat.setDraft(text);
1128
+ requestAnimationFrame(() => {
1129
+ const el = input.current;
1130
+ if (!el) return;
1131
+ el.focus();
1132
+ el.setSelectionRange(text.length, text.length);
1133
+ });
1134
+ },
1135
+ [chat]
1136
+ );
1137
+ const pickStarter = (template) => {
1138
+ const { text, ready } = fillStarter(template, pageContext);
1139
+ setShowIdeas(false);
1140
+ if (ready && !busy) {
1141
+ pinned.current = true;
1142
+ void chat.send(text);
1143
+ } else loadDraft(starterLabel(template, pageContext));
1144
+ };
1145
+ const onDrop = (e) => {
1146
+ e.preventDefault();
1147
+ setDragging(false);
1148
+ if (e.dataTransfer.files.length) chat.addFiles(e.dataTransfer.files);
1149
+ };
1150
+ const pageChanged = cards.length > 0 && lastAskedPage(cards) !== pageContext.page;
1151
+ return /* @__PURE__ */ jsxs6(
1152
+ "aside",
1153
+ {
1154
+ ref: panel,
1155
+ className: "cfi-ac-root cfi-ac-panel",
1156
+ style: expanded ? void 0 : { width },
1157
+ "aria-label": "AI chat",
1158
+ "data-dragging": dragging || void 0,
1159
+ "data-expanded": expanded || void 0,
1160
+ onKeyDown: (e) => {
1161
+ if (e.key !== "Escape" || e.defaultPrevented) return;
1162
+ e.preventDefault();
1163
+ if (expanded) setExpanded(false);
1164
+ else setOpen(false);
1165
+ },
1166
+ onDragOver: (e) => {
1167
+ if (Array.from(e.dataTransfer.types).includes("Files")) {
1168
+ e.preventDefault();
1169
+ setDragging(true);
1170
+ }
1171
+ },
1172
+ onDragLeave: (e) => {
1173
+ if (!e.currentTarget.contains(e.relatedTarget)) setDragging(false);
1174
+ },
1175
+ onDrop,
1176
+ children: [
1177
+ /* @__PURE__ */ jsx8(Announcer, {}),
1178
+ !expanded && /* @__PURE__ */ jsx8(ResizeHandle, { width, setWidth }),
1179
+ /* @__PURE__ */ jsxs6("header", { className: "cfi-ac-head", children: [
1180
+ /* @__PURE__ */ jsx8("span", { className: "cfi-ac-head__mark", "aria-hidden": true, children: /* @__PURE__ */ jsx8(Sparkles, { size: 16 }) }),
1181
+ /* @__PURE__ */ jsx8("h2", { className: "cfi-ac-head__title", children: "AI chat" }),
1182
+ /* @__PURE__ */ jsxs6("div", { className: "cfi-ac-head__tools", children: [
1183
+ /* @__PURE__ */ jsx8(
1184
+ "button",
1185
+ {
1186
+ type: "button",
1187
+ className: "cfi-ac-icon-btn",
1188
+ "aria-pressed": showChanges,
1189
+ "aria-label": "Changes made this session",
1190
+ title: "Changes made this session",
1191
+ onClick: () => {
1192
+ const next = !showChanges;
1193
+ setShowChanges(next);
1194
+ if (next) void chat.refreshChanges();
1195
+ },
1196
+ children: /* @__PURE__ */ jsx8(History, { size: 16 })
1197
+ }
1198
+ ),
1199
+ /* @__PURE__ */ jsx8(
1200
+ "button",
1201
+ {
1202
+ type: "button",
1203
+ className: "cfi-ac-icon-btn",
1204
+ "aria-label": "Start over",
1205
+ title: "Start over",
1206
+ disabled: cards.length === 0 && chat.stagedFiles.length === 0,
1207
+ onClick: () => {
1208
+ setShowChanges(false);
1209
+ startOver();
1210
+ },
1211
+ children: /* @__PURE__ */ jsx8(RotateCcw, { size: 16 })
1212
+ }
1213
+ ),
1214
+ /* @__PURE__ */ jsx8(
1215
+ "button",
1216
+ {
1217
+ type: "button",
1218
+ className: "cfi-ac-icon-btn",
1219
+ "aria-pressed": expanded,
1220
+ "aria-label": "Expanded view",
1221
+ title: expanded ? "Dock beside the page (Esc)" : "Expand over the page",
1222
+ onClick: () => setExpanded(!expanded),
1223
+ children: expanded ? /* @__PURE__ */ jsx8(Minimize2, { size: 16 }) : /* @__PURE__ */ jsx8(Maximize2, { size: 16 })
1224
+ }
1225
+ ),
1226
+ /* @__PURE__ */ jsx8("button", { type: "button", className: "cfi-ac-icon-btn", "aria-label": "Close AI chat", title: "Close (Ctrl+J)", onClick: () => setOpen(false), children: /* @__PURE__ */ jsx8(X2, { size: 16 }) })
1227
+ ] })
1228
+ ] }),
1229
+ showChanges ? /* @__PURE__ */ jsx8(ChangesList, { chat, onBack: () => setShowChanges(false) }) : /* @__PURE__ */ jsxs6(Fragment2, { children: [
1230
+ /* @__PURE__ */ jsx8(
1231
+ "div",
1232
+ {
1233
+ className: "cfi-ac-timeline",
1234
+ ref: timeline,
1235
+ onScroll: (e) => {
1236
+ const el = e.currentTarget;
1237
+ pinned.current = el.scrollHeight - el.scrollTop - el.clientHeight < 40;
1238
+ },
1239
+ children: showCatalog ? /* @__PURE__ */ jsx8(
1240
+ Catalog,
1241
+ {
1242
+ starters,
1243
+ catalog,
1244
+ page: pageContext,
1245
+ busy,
1246
+ onPick: pickStarter,
1247
+ onBack: cards.length > 0 ? () => setShowIdeas(false) : void 0
1248
+ }
1249
+ ) : cards.map((card, i) => /* @__PURE__ */ jsxs6("div", { className: "cfi-ac-cardwrap", children: [
1250
+ /* @__PURE__ */ jsx8(CardView, { card, chat, loadDraft }),
1251
+ endsTimeGroup(cards, i) && /* @__PURE__ */ jsx8(CardTime, { at: cardTime(card) })
1252
+ ] }, card.id))
1253
+ }
1254
+ ),
1255
+ !showCatalog && pageChanged && starters.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "cfi-ac-page-starters", children: [
1256
+ /* @__PURE__ */ jsxs6("span", { className: "cfi-ac-page-starters__label", children: [
1257
+ "On ",
1258
+ pageContext.title
1259
+ ] }),
1260
+ /* @__PURE__ */ jsx8(Starters, { starters, page: pageContext, onPick: pickStarter, busy })
1261
+ ] }),
1262
+ /* @__PURE__ */ jsx8(
1263
+ Composer,
1264
+ {
1265
+ chat,
1266
+ input,
1267
+ onSent: () => {
1268
+ pinned.current = true;
1269
+ setShowIdeas(false);
1270
+ },
1271
+ ideas: cards.length > 0 ? { open: showIdeas, toggle: () => setShowIdeas((v) => !v) } : void 0
1272
+ }
1273
+ )
1274
+ ] }),
1275
+ dragging && /* @__PURE__ */ jsxs6("div", { className: "cfi-ac-dropzone", "aria-hidden": true, children: [
1276
+ /* @__PURE__ */ jsx8(Paperclip2, { size: 20 }),
1277
+ "Drop to add to your message. Nothing is uploaded until you confirm an attach."
1278
+ ] })
1279
+ ]
1280
+ }
1281
+ );
1282
+ }
1283
+ function ResizeHandle({ width, setWidth }) {
1284
+ const dragging = useRef3(false);
1285
+ useEffect4(() => {
1286
+ const onMove = (e) => {
1287
+ if (!dragging.current) return;
1288
+ setWidth(Math.min(window.innerWidth - 320, window.innerWidth - e.clientX));
1289
+ };
1290
+ const onUp = () => {
1291
+ if (!dragging.current) return;
1292
+ dragging.current = false;
1293
+ document.body.style.cursor = "";
1294
+ document.body.style.userSelect = "";
1295
+ };
1296
+ window.addEventListener("pointermove", onMove);
1297
+ window.addEventListener("pointerup", onUp);
1298
+ return () => {
1299
+ window.removeEventListener("pointermove", onMove);
1300
+ window.removeEventListener("pointerup", onUp);
1301
+ };
1302
+ }, [setWidth]);
1303
+ return /* @__PURE__ */ jsx8(
1304
+ "div",
1305
+ {
1306
+ className: "cfi-ac-resize",
1307
+ role: "separator",
1308
+ "aria-orientation": "vertical",
1309
+ "aria-label": "Resize AI chat",
1310
+ "aria-valuemin": PANEL_WIDTH.min,
1311
+ "aria-valuemax": PANEL_WIDTH.max,
1312
+ "aria-valuenow": width,
1313
+ "aria-valuetext": `${width} pixels wide`,
1314
+ title: "Drag to resize. Double-click for the default width.",
1315
+ tabIndex: 0,
1316
+ onDoubleClick: () => setWidth(PANEL_WIDTH.default),
1317
+ onPointerDown: (e) => {
1318
+ e.preventDefault();
1319
+ dragging.current = true;
1320
+ document.body.style.cursor = "col-resize";
1321
+ document.body.style.userSelect = "none";
1322
+ },
1323
+ onKeyDown: (e) => {
1324
+ if (e.key === "ArrowLeft") {
1325
+ e.preventDefault();
1326
+ setWidth(width + 32);
1327
+ } else if (e.key === "ArrowRight") {
1328
+ e.preventDefault();
1329
+ setWidth(width - 32);
1330
+ } else if (e.key === "Home") {
1331
+ e.preventDefault();
1332
+ setWidth(PANEL_WIDTH.min);
1333
+ } else if (e.key === "End") {
1334
+ e.preventDefault();
1335
+ setWidth(PANEL_WIDTH.max);
1336
+ }
1337
+ }
1338
+ }
1339
+ );
1340
+ }
1341
+ function Starters({
1342
+ starters,
1343
+ page,
1344
+ onPick,
1345
+ busy
1346
+ }) {
1347
+ if (!starters.length) return null;
1348
+ return /* @__PURE__ */ jsx8("div", { className: "cfi-ac-chips cfi-ac-starters", children: starters.map((s) => /* @__PURE__ */ jsx8("button", { type: "button", className: "cfi-ac-chip", disabled: busy, onClick: () => onPick(s), children: starterLabel(s, page) }, s)) });
1349
+ }
1350
+ function Catalog({
1351
+ starters,
1352
+ catalog,
1353
+ page,
1354
+ busy,
1355
+ onPick,
1356
+ onBack
1357
+ }) {
1358
+ const [openGroup, setOpenGroup] = useState6(catalog[0]?.label ?? null);
1359
+ const focus = page.entities[page.entities.length - 1];
1360
+ return /* @__PURE__ */ jsxs6("div", { className: "cfi-ac-empty", children: [
1361
+ onBack && /* @__PURE__ */ jsxs6("button", { type: "button", className: "cfi-ac-changes__back", onClick: onBack, children: [
1362
+ /* @__PURE__ */ jsx8(ArrowLeft, { size: 14, "aria-hidden": true }),
1363
+ "Back to the conversation"
1364
+ ] }),
1365
+ /* @__PURE__ */ jsx8("span", { className: "cfi-ac-empty__mark", "aria-hidden": true, children: /* @__PURE__ */ jsx8(Sparkles, { size: 22 }) }),
1366
+ /* @__PURE__ */ jsx8("p", { className: "cfi-ac-empty__title", children: "Ask about what you are looking at" }),
1367
+ /* @__PURE__ */ jsx8("p", { className: "cfi-ac-empty__sub", children: "Questions about your data, how-to questions, and small changes you confirm before anything is saved." }),
1368
+ focus?.label && /* @__PURE__ */ jsxs6("p", { className: "cfi-ac-ready", children: [
1369
+ /* @__PURE__ */ jsx8("span", { className: "cfi-ac-ready__dot", "aria-hidden": true }),
1370
+ "Ready on ",
1371
+ focus.label
1372
+ ] }),
1373
+ starters.length > 0 && /* @__PURE__ */ jsxs6(Fragment2, { children: [
1374
+ /* @__PURE__ */ jsx8("h3", { className: "cfi-ac-catalog__heading", children: "Ask now" }),
1375
+ /* @__PURE__ */ jsx8(Starters, { starters, page, onPick, busy })
1376
+ ] }),
1377
+ catalog.length > 0 && /* @__PURE__ */ jsxs6(Fragment2, { children: [
1378
+ /* @__PURE__ */ jsx8("h3", { className: "cfi-ac-catalog__heading", children: "Browse by topic" }),
1379
+ /* @__PURE__ */ jsx8("div", { className: "cfi-ac-catalog", children: catalog.map((group) => {
1380
+ const open = openGroup === group.label;
1381
+ return /* @__PURE__ */ jsxs6("section", { className: "cfi-ac-catalog__group", children: [
1382
+ /* @__PURE__ */ jsxs6(
1383
+ "button",
1384
+ {
1385
+ type: "button",
1386
+ className: "cfi-ac-catalog__toggle",
1387
+ "aria-expanded": open,
1388
+ onClick: () => setOpenGroup(open ? null : group.label),
1389
+ children: [
1390
+ /* @__PURE__ */ jsxs6("span", { className: "cfi-ac-catalog__name", children: [
1391
+ group.label,
1392
+ group.blurb && /* @__PURE__ */ jsx8("span", { className: "cfi-ac-catalog__blurb", children: group.blurb })
1393
+ ] }),
1394
+ /* @__PURE__ */ jsxs6("span", { className: "cfi-ac-catalog__count", children: [
1395
+ group.questions.length,
1396
+ /* @__PURE__ */ jsx8("span", { className: "cfi-ac-sr-only", children: " questions" })
1397
+ ] }),
1398
+ open ? /* @__PURE__ */ jsx8(ChevronDown2, { size: 14, "aria-hidden": true }) : /* @__PURE__ */ jsx8(ChevronRight2, { size: 14, "aria-hidden": true })
1399
+ ]
1400
+ }
1401
+ ),
1402
+ open && /* @__PURE__ */ jsx8("div", { className: "cfi-ac-catalog__questions", children: group.questions.map((q) => {
1403
+ const { ready } = fillStarter(q, page);
1404
+ return /* @__PURE__ */ jsxs6(
1405
+ "button",
1406
+ {
1407
+ type: "button",
1408
+ className: "cfi-ac-catalog__q",
1409
+ disabled: busy && ready,
1410
+ title: ready ? void 0 : "Name it, then send",
1411
+ onClick: () => onPick(q),
1412
+ children: [
1413
+ /* @__PURE__ */ jsx8(ChevronRight2, { size: 12, "aria-hidden": true }),
1414
+ /* @__PURE__ */ jsxs6("span", { children: [
1415
+ starterLabel(q, page),
1416
+ !ready && /* @__PURE__ */ jsx8("span", { className: "cfi-ac-muted", children: " (name it)" })
1417
+ ] })
1418
+ ]
1419
+ },
1420
+ q
1421
+ );
1422
+ }) })
1423
+ ] }, group.label);
1424
+ }) })
1425
+ ] }),
1426
+ /* @__PURE__ */ jsxs6("p", { className: "cfi-ac-grounded", children: [
1427
+ /* @__PURE__ */ jsx8(Lock, { size: 12, "aria-hidden": true }),
1428
+ /* @__PURE__ */ jsxs6("span", { children: [
1429
+ /* @__PURE__ */ jsx8("strong", { children: "Grounded in your own data." }),
1430
+ " Every answer is read with your permissions, so you see what you would see on the screens. Nothing changes until you confirm it."
1431
+ ] })
1432
+ ] })
1433
+ ] });
1434
+ }
1435
+ function CardTime({ at }) {
1436
+ const d = new Date(at);
1437
+ const today = (/* @__PURE__ */ new Date()).toDateString() === d.toDateString();
1438
+ const time = d.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
1439
+ const label = today ? time : `${d.toLocaleDateString(void 0, { month: "short", day: "numeric" })}, ${time}`;
1440
+ return /* @__PURE__ */ jsx8("time", { className: "cfi-ac-time", dateTime: d.toISOString(), children: label });
1441
+ }
1442
+ function CopyAnswer({ ex }) {
1443
+ const [copied, setCopied] = useState6(false);
1444
+ const text = ex.parts.flatMap((p) => p.kind === "markdown" ? [p.text] : []).join("\n").trim();
1445
+ if (!text) return null;
1446
+ return /* @__PURE__ */ jsx8(
1447
+ "button",
1448
+ {
1449
+ type: "button",
1450
+ className: "cfi-ac-icon-btn cfi-ac-copy",
1451
+ "aria-label": copied ? "Copied" : "Copy answer",
1452
+ title: copied ? "Copied" : "Copy answer",
1453
+ onClick: () => {
1454
+ void navigator.clipboard?.writeText(text).then(() => {
1455
+ setCopied(true);
1456
+ announce("Answer copied");
1457
+ window.setTimeout(() => setCopied(false), 1500);
1458
+ });
1459
+ },
1460
+ children: copied ? /* @__PURE__ */ jsx8(Check3, { size: 13 }) : /* @__PURE__ */ jsx8(Copy2, { size: 13 })
1461
+ }
1462
+ );
1463
+ }
1464
+ function CardView({ card, chat, loadDraft }) {
1465
+ const [folded, setFolded] = useState6(false);
1466
+ const toggled = useRef3(false);
1467
+ const foldRef = useRef3(null);
1468
+ useEffect4(() => {
1469
+ if (!toggled.current) return;
1470
+ toggled.current = false;
1471
+ foldRef.current?.focus();
1472
+ }, [folded]);
1473
+ const fold = (next) => {
1474
+ toggled.current = true;
1475
+ setFolded(next);
1476
+ };
1477
+ const streaming = card.exchanges.some((e) => e.status === "streaming");
1478
+ const handlers = {
1479
+ navigate: chat.navigate,
1480
+ send: (t) => void chat.send(t),
1481
+ load: loadDraft,
1482
+ answer: (t) => void chat.send(t, { foldInto: card.id }),
1483
+ busy: chat.busy
1484
+ };
1485
+ if (folded) {
1486
+ return /* @__PURE__ */ jsx8("article", { className: "cfi-ac-card cfi-ac-card--folded", children: /* @__PURE__ */ jsxs6("button", { ref: foldRef, type: "button", className: "cfi-ac-card__fold", "aria-expanded": false, onClick: () => fold(false), children: [
1487
+ /* @__PURE__ */ jsx8(ChevronRight2, { size: 13, "aria-hidden": true }),
1488
+ /* @__PURE__ */ jsx8("span", { children: card.exchanges[0].question })
1489
+ ] }) });
1490
+ }
1491
+ return /* @__PURE__ */ jsxs6("article", { className: "cfi-ac-card", children: [
1492
+ !streaming && /* @__PURE__ */ jsx8(
1493
+ "button",
1494
+ {
1495
+ ref: foldRef,
1496
+ type: "button",
1497
+ className: "cfi-ac-icon-btn cfi-ac-card__foldbtn",
1498
+ "aria-label": "Fold this answer",
1499
+ "aria-expanded": true,
1500
+ title: "Fold this answer",
1501
+ onClick: () => fold(true),
1502
+ children: /* @__PURE__ */ jsx8(ChevronDown2, { size: 13 })
1503
+ }
1504
+ ),
1505
+ card.exchanges.map((ex, i) => /* @__PURE__ */ jsx8(ExchangeView, { ex, reply: i > 0, chat, handlers }, ex.id))
1506
+ ] });
1507
+ }
1508
+ function ExchangeView({
1509
+ ex,
1510
+ reply,
1511
+ chat,
1512
+ handlers
1513
+ }) {
1514
+ return /* @__PURE__ */ jsxs6("div", { className: "cfi-ac-exchange", "data-reply": reply || void 0, children: [
1515
+ /* @__PURE__ */ jsxs6("div", { className: "cfi-ac-q", children: [
1516
+ /* @__PURE__ */ jsx8("h3", { className: "cfi-ac-q__text", children: ex.question }),
1517
+ !reply && /* @__PURE__ */ jsxs6("p", { className: "cfi-ac-q__where", children: [
1518
+ /* @__PURE__ */ jsx8(MapPin, { size: 11, "aria-hidden": true }),
1519
+ "asked on ",
1520
+ pageLabel(ex.askedOn)
1521
+ ] }),
1522
+ ex.attachments.length > 0 && !reply && /* @__PURE__ */ jsx8("ul", { className: "cfi-ac-q__files", children: ex.attachments.map((f) => /* @__PURE__ */ jsxs6("li", { children: [
1523
+ /* @__PURE__ */ jsx8(Paperclip2, { size: 11, "aria-hidden": true }),
1524
+ f.name
1525
+ ] }, f.fileRef)) })
1526
+ ] }),
1527
+ /* @__PURE__ */ jsxs6("div", { className: "cfi-ac-a", children: [
1528
+ ex.parts.map(
1529
+ (part, i) => part.kind === "block" && part.block.kind === "understood" ? /* @__PURE__ */ jsx8(BlockView, { block: part.block, handlers }, `u${i}`) : null
1530
+ ),
1531
+ /* @__PURE__ */ jsx8(Steps, { steps: ex.steps, streaming: ex.status === "streaming", since: ex.askedAt }),
1532
+ [
1533
+ ...ex.parts.map((part, i) => ({ part, i })).filter(({ part }) => !(part.kind === "block" && part.block.kind === "followups")),
1534
+ ...ex.parts.map((part, i) => ({ part, i })).filter(({ part }) => part.kind === "block" && part.block.kind === "followups")
1535
+ ].map(
1536
+ ({ part, i }) => part.kind === "markdown" ? /* @__PURE__ */ jsx8(Markdown, { text: part.text }, i) : part.kind === "block" && part.block.kind === "understood" ? null : part.kind === "block" ? /* @__PURE__ */ jsx8(BlockView, { block: part.block, handlers }, i) : /* @__PURE__ */ jsx8(
1537
+ WriteCard,
1538
+ {
1539
+ item: part.item,
1540
+ onConfirm: () => void chat.confirmWrite(ex.id, part.item.write.token),
1541
+ onCancel: () => chat.cancelWrite(ex.id, part.item.write.token)
1542
+ },
1543
+ part.item.write.token
1544
+ )
1545
+ ),
1546
+ ex.status === "done" && /* @__PURE__ */ jsxs6("div", { className: "cfi-ac-a__foot", children: [
1547
+ ex.sources && ex.sources.length > 0 && /* @__PURE__ */ jsxs6("p", { className: "cfi-ac-based", children: [
1548
+ /* @__PURE__ */ jsx8("span", { className: "cfi-ac-based__label", children: "Based on" }),
1549
+ ex.sources.map(
1550
+ (s) => s.path && s.app ? (
1551
+ // Another app's page: the host opens it there (its own address, often a new tab).
1552
+ /* @__PURE__ */ jsx8(
1553
+ "button",
1554
+ {
1555
+ type: "button",
1556
+ className: "cfi-ac-based__chip cfi-ac-based__chip--link",
1557
+ onClick: () => chat.navigate({ app: s.app, path: s.path }),
1558
+ children: s.label
1559
+ },
1560
+ s.label
1561
+ )
1562
+ ) : s.path ? /* @__PURE__ */ jsx8(
1563
+ "a",
1564
+ {
1565
+ className: "cfi-ac-based__chip",
1566
+ href: s.path,
1567
+ onClick: (e) => {
1568
+ if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
1569
+ e.preventDefault();
1570
+ chat.navigate({ app: chat.app, path: s.path });
1571
+ },
1572
+ children: s.label
1573
+ },
1574
+ s.label
1575
+ ) : /* @__PURE__ */ jsx8("span", { className: "cfi-ac-based__chip", children: s.label }, s.label)
1576
+ )
1577
+ ] }),
1578
+ /* @__PURE__ */ jsx8(CopyAnswer, { ex })
1579
+ ] }),
1580
+ pendingCount(ex) > 1 && /* @__PURE__ */ jsxs6("div", { className: "cfi-ac-confirm-all", children: [
1581
+ /* @__PURE__ */ jsxs6("span", { children: [
1582
+ pendingCount(ex),
1583
+ " changes waiting"
1584
+ ] }),
1585
+ /* @__PURE__ */ jsxs6("button", { type: "button", className: "cfi-ac-btn", "data-variant": "primary", onClick: () => void chat.confirmAll(ex.id), children: [
1586
+ /* @__PURE__ */ jsx8(Check3, { size: 14, "aria-hidden": true }),
1587
+ "Confirm all"
1588
+ ] })
1589
+ ] }),
1590
+ ex.status === "streaming" && ex.parts.length === 0 && ex.steps.length === 0 && /* @__PURE__ */ jsxs6("p", { className: "cfi-ac-thinking", children: [
1591
+ /* @__PURE__ */ jsx8(Loader22, { className: "cfi-ac-spin", size: 14, "aria-hidden": true }),
1592
+ "Thinking\u2026",
1593
+ /* @__PURE__ */ jsx8(Elapsed, { since: ex.askedAt })
1594
+ ] }),
1595
+ ex.status === "error" && /* @__PURE__ */ jsxs6("p", { className: "cfi-ac-error", role: "alert", children: [
1596
+ /* @__PURE__ */ jsx8(CircleAlert2, { size: 14, "aria-hidden": true }),
1597
+ ex.error
1598
+ ] })
1599
+ ] })
1600
+ ] });
1601
+ }
1602
+ function pendingCount(ex) {
1603
+ return ex.parts.filter((p) => p.kind === "write" && p.item.state === "pending").length;
1604
+ }
1605
+ function Elapsed({ since }) {
1606
+ const [now, setNow] = useState6(() => Date.now());
1607
+ useEffect4(() => {
1608
+ const id = window.setInterval(() => setNow(Date.now()), 500);
1609
+ return () => window.clearInterval(id);
1610
+ }, []);
1611
+ const seconds = Math.max(0, Math.round((now - since) / 1e3));
1612
+ return /* @__PURE__ */ jsxs6("span", { className: "cfi-ac-elapsed", "aria-hidden": true, children: [
1613
+ seconds,
1614
+ "s"
1615
+ ] });
1616
+ }
1617
+ function Steps({ steps, streaming, since }) {
1618
+ const [open, setOpen] = useState6(false);
1619
+ if (!steps.length) return null;
1620
+ if (streaming) {
1621
+ return /* @__PURE__ */ jsxs6("p", { className: "cfi-ac-steps cfi-ac-steps--live", children: [
1622
+ /* @__PURE__ */ jsx8(Loader22, { className: "cfi-ac-spin", size: 13, "aria-hidden": true }),
1623
+ steps[steps.length - 1],
1624
+ /* @__PURE__ */ jsx8(Elapsed, { since })
1625
+ ] });
1626
+ }
1627
+ return /* @__PURE__ */ jsxs6("div", { className: "cfi-ac-steps", children: [
1628
+ /* @__PURE__ */ jsxs6("button", { type: "button", className: "cfi-ac-steps__toggle", "aria-expanded": open, onClick: () => setOpen((o) => !o), children: [
1629
+ open ? /* @__PURE__ */ jsx8(ChevronDown2, { size: 13, "aria-hidden": true }) : /* @__PURE__ */ jsx8(ChevronRight2, { size: 13, "aria-hidden": true }),
1630
+ "Checked ",
1631
+ steps.length,
1632
+ " ",
1633
+ steps.length === 1 ? "thing" : "things"
1634
+ ] }),
1635
+ open && /* @__PURE__ */ jsx8("ol", { className: "cfi-ac-steps__list", children: steps.map((s, i) => /* @__PURE__ */ jsx8("li", { children: s }, i)) })
1636
+ ] });
1637
+ }
1638
+ function ChangesList({ chat, onBack }) {
1639
+ const { changes, summaries } = chat;
1640
+ return /* @__PURE__ */ jsxs6("section", { className: "cfi-ac-changes", "aria-label": "Changes made this session", children: [
1641
+ /* @__PURE__ */ jsxs6("button", { type: "button", className: "cfi-ac-changes__back", onClick: onBack, children: [
1642
+ /* @__PURE__ */ jsx8(ArrowLeft, { size: 14, "aria-hidden": true }),
1643
+ "Back to chat"
1644
+ ] }),
1645
+ /* @__PURE__ */ jsx8("h3", { className: "cfi-ac-changes__title", children: "Changes made this session" }),
1646
+ changes.loading && !changes.loaded ? /* @__PURE__ */ jsx8("p", { className: "cfi-ac-muted", role: "status", children: "Loading\u2026" }) : changes.error ? /* @__PURE__ */ jsx8("p", { className: "cfi-ac-error", role: "alert", children: changes.error }) : changes.items.length === 0 ? /* @__PURE__ */ jsx8("p", { className: "cfi-ac-muted", children: "No changes yet. Anything you confirm here is listed, with the question that led to it." }) : /* @__PURE__ */ jsx8("ul", { className: "cfi-ac-changes__list", children: changes.items.map((a) => /* @__PURE__ */ jsxs6("li", { "data-outcome": a.outcome, children: [
1647
+ /* @__PURE__ */ jsx8("span", { className: "cfi-ac-changes__what", children: a.summary ?? summaries[a.id] ?? (a.entityType ? `Changed a ${a.entityType}` : "Change") }),
1648
+ /* @__PURE__ */ jsxs6("span", { className: "cfi-ac-changes__meta", children: [
1649
+ a.outcome === "succeeded" ? "Done" : "Not changed",
1650
+ " \xB7",
1651
+ " ",
1652
+ new Date(a.confirmedAt).toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" })
1653
+ ] }),
1654
+ /* @__PURE__ */ jsxs6("span", { className: "cfi-ac-changes__q", children: [
1655
+ "\u201C",
1656
+ a.question,
1657
+ "\u201D"
1658
+ ] })
1659
+ ] }, a.id)) })
1660
+ ] });
1661
+ }
1662
+ var COMPOSER_MAX_HEIGHT = 140;
1663
+ function Composer({
1664
+ chat,
1665
+ input,
1666
+ onSent,
1667
+ ideas
1668
+ }) {
1669
+ const { draft, setDraft, busy, stagedFiles, pageContext, cards } = chat;
1670
+ const picker = useRef3(null);
1671
+ useLayoutEffect(() => {
1672
+ const el = input.current;
1673
+ if (!el) return;
1674
+ el.style.height = "auto";
1675
+ el.style.height = `${Math.min(el.scrollHeight, COMPOSER_MAX_HEIGHT)}px`;
1676
+ }, [draft, input]);
1677
+ const submit = () => {
1678
+ const text = draft.trim();
1679
+ if (!text || busy) return;
1680
+ const fold = openQuestionCard(cards);
1681
+ setDraft("");
1682
+ onSent();
1683
+ void chat.send(text, fold ? { foldInto: fold.id } : void 0);
1684
+ };
1685
+ const onKey = (e) => {
1686
+ if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
1687
+ e.preventDefault();
1688
+ submit();
1689
+ }
1690
+ const panelKey = e.key === "Escape" || (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "j";
1691
+ if (!panelKey) e.nativeEvent.stopImmediatePropagation();
1692
+ };
1693
+ return /* @__PURE__ */ jsxs6(
1694
+ "form",
1695
+ {
1696
+ className: "cfi-ac-composer",
1697
+ onSubmit: (e) => {
1698
+ e.preventDefault();
1699
+ submit();
1700
+ },
1701
+ children: [
1702
+ /* @__PURE__ */ jsxs6("p", { className: "cfi-ac-composer__page", title: pageContext.breadcrumbs.join(" / "), children: [
1703
+ /* @__PURE__ */ jsx8(MapPin, { size: 11, "aria-hidden": true }),
1704
+ "On ",
1705
+ pageLabel(pageContext)
1706
+ ] }),
1707
+ stagedFiles.length > 0 && /* @__PURE__ */ jsx8("ul", { className: "cfi-ac-composer__files", "aria-label": "Files for this message", children: stagedFiles.map((f) => /* @__PURE__ */ jsxs6("li", { children: [
1708
+ /* @__PURE__ */ jsx8(Paperclip2, { size: 12, "aria-hidden": true }),
1709
+ /* @__PURE__ */ jsx8("span", { className: "cfi-ac-composer__file-name", children: f.name }),
1710
+ /* @__PURE__ */ jsx8("span", { className: "cfi-ac-muted", children: formatBytes(f.size) }),
1711
+ /* @__PURE__ */ jsx8("button", { type: "button", "aria-label": `Remove ${f.name}`, onClick: () => chat.removeFile(f.fileRef), children: /* @__PURE__ */ jsx8(X2, { size: 12 }) })
1712
+ ] }, f.fileRef)) }),
1713
+ /* @__PURE__ */ jsx8("p", { id: "cfi-ac-composer-hint", className: "cfi-ac-sr-only", children: "Enter sends. Shift and Enter adds a line." }),
1714
+ /* @__PURE__ */ jsxs6("div", { className: "cfi-ac-composer__box", children: [
1715
+ /* @__PURE__ */ jsx8(
1716
+ "textarea",
1717
+ {
1718
+ ref: input,
1719
+ value: draft,
1720
+ rows: 1,
1721
+ maxLength: 4e3,
1722
+ "aria-label": "Ask a question",
1723
+ "aria-describedby": "cfi-ac-composer-hint",
1724
+ placeholder: "Ask about this page, your data, or how to do something\u2026",
1725
+ onChange: (e) => setDraft(e.target.value),
1726
+ onKeyDown: onKey
1727
+ }
1728
+ ),
1729
+ /* @__PURE__ */ jsxs6("div", { className: "cfi-ac-composer__row", children: [
1730
+ /* @__PURE__ */ jsxs6("span", { className: "cfi-ac-composer__tools", children: [
1731
+ ideas && /* @__PURE__ */ jsx8(
1732
+ "button",
1733
+ {
1734
+ type: "button",
1735
+ className: "cfi-ac-icon-btn",
1736
+ "aria-pressed": ideas.open,
1737
+ "aria-label": "Question ideas",
1738
+ title: ideas.open ? "Back to the conversation" : "What can I ask?",
1739
+ onClick: ideas.toggle,
1740
+ children: /* @__PURE__ */ jsx8(Lightbulb, { size: 16 })
1741
+ }
1742
+ ),
1743
+ /* @__PURE__ */ jsx8(
1744
+ "button",
1745
+ {
1746
+ type: "button",
1747
+ className: "cfi-ac-icon-btn",
1748
+ "aria-label": "Add a file",
1749
+ title: "Add a file. It is uploaded only if you confirm attaching it.",
1750
+ onClick: () => picker.current?.click(),
1751
+ children: /* @__PURE__ */ jsx8(Paperclip2, { size: 16 })
1752
+ }
1753
+ )
1754
+ ] }),
1755
+ /* @__PURE__ */ jsx8(
1756
+ "input",
1757
+ {
1758
+ ref: picker,
1759
+ type: "file",
1760
+ multiple: true,
1761
+ hidden: true,
1762
+ "data-testid": "ai-chat-file-input",
1763
+ onChange: (e) => {
1764
+ if (e.target.files) chat.addFiles(e.target.files);
1765
+ e.target.value = "";
1766
+ }
1767
+ }
1768
+ ),
1769
+ busy ? /* @__PURE__ */ jsx8("button", { type: "button", className: "cfi-ac-send", "aria-label": "Stop", title: "Stop", onClick: chat.stop, children: /* @__PURE__ */ jsx8(Square, { size: 14 }) }) : /* @__PURE__ */ jsx8("button", { type: "submit", className: "cfi-ac-send", "aria-label": "Send", title: "Send (Enter)", disabled: !draft.trim(), children: /* @__PURE__ */ jsx8(Send, { size: 16 }) })
1770
+ ] })
1771
+ ] })
1772
+ ]
1773
+ }
1774
+ );
1775
+ }
1776
+
1777
+ // src/host/AiChatButton.tsx
1778
+ import { useEffect as useEffect5 } from "react";
1779
+ import { Sparkles as Sparkles2 } from "lucide-react";
1780
+ import { jsx as jsx9 } from "react/jsx-runtime";
1781
+ function AiChatButton({ className }) {
1782
+ const chat = useAiChat();
1783
+ const toggle = chat?.toggle;
1784
+ useEffect5(() => {
1785
+ if (!toggle) return void 0;
1786
+ const onKey = (e) => {
1787
+ if ((e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey && e.key.toLowerCase() === "j") {
1788
+ e.preventDefault();
1789
+ toggle();
1790
+ }
1791
+ };
1792
+ window.addEventListener("keydown", onKey);
1793
+ return () => window.removeEventListener("keydown", onKey);
1794
+ }, [toggle]);
1795
+ if (!chat) return null;
1796
+ return /* @__PURE__ */ jsx9(
1797
+ "button",
1798
+ {
1799
+ type: "button",
1800
+ className: `cfi-ac-root cfi-ac-header-btn${className ? ` ${className}` : ""}`,
1801
+ "aria-pressed": chat.open,
1802
+ "aria-label": "AI chat",
1803
+ "data-cfi-ai-chat-button": "",
1804
+ title: "AI chat (Ctrl+J)",
1805
+ onClick: chat.toggle,
1806
+ children: /* @__PURE__ */ jsx9(Sparkles2, { size: 18, "aria-hidden": true })
1807
+ }
1808
+ );
1809
+ }
1810
+ export {
1811
+ AiChatButton,
1812
+ AiChatProvider,
1813
+ AiChatSurface,
1814
+ createAssistantClient,
1815
+ fillStarter,
1816
+ useAiChat
1817
+ };