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