@invergent/agent-chat-react 2.17.7 → 2.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -23,10 +23,8 @@ var AnsiImport = require('ansi-to-react');
23
23
  var cmdk = require('cmdk');
24
24
  var nanoid = require('nanoid');
25
25
  require('tokenlens');
26
- require('pdfjs-dist/web/pdf_viewer.css');
27
26
  var dateFns = require('date-fns');
28
27
 
29
- var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
30
28
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
31
29
 
32
30
  var AnsiImport__default = /*#__PURE__*/_interopDefault(AnsiImport);
@@ -10216,7 +10214,6 @@ function ChatComposerInner({
10216
10214
  { value: "/loop 30m summarize new threads in support inbox", label: "/loop 30m support inbox", description: "Every 30 minutes: summarize support traffic" },
10217
10215
  { value: "/loop 1h review the on-call dashboard and flag anomalies", label: "/loop 1h on-call check", description: "Hourly: review the on-call dashboard" },
10218
10216
  { value: "/loop 2h scan production error rates and alert if elevated", label: "/loop 2h error scan", description: "Every 2 hours: production error scan" },
10219
- { value: "/loop 1d give me a morning briefing of yesterday's activity", label: "/loop 1d daily briefing", description: "Daily: morning briefing of prior day" },
10220
10217
  { value: "/loop every 5 minutes check whether CI on the current branch has finished", label: "/loop every 5 minutes (verbose)", description: "Verbose interval form" },
10221
10218
  { value: "/loop watch the active deploy and notify me when it stabilises or rolls back", label: "/loop <prompt> (dynamic)", description: "Dynamic loop \u2014 model self-paces 1m\u20131h via loop_wait" },
10222
10219
  { value: "/loop babysit the long-running data migration and only ping me on errors or completion", label: "/loop migration watcher (dynamic)", description: "Dynamic loop \u2014 best when cadence is unpredictable" }
@@ -12607,7 +12604,7 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
12607
12604
  if (msg.systemKind === "worker" || msg.systemKind === "thread_proposal") {
12608
12605
  return [{ kind: "thread_card", key: msg.id, message: msg }];
12609
12606
  }
12610
- if (msg.systemKind === "browser_marker" || msg.systemKind === "browser_marker_warning") {
12607
+ if (msg.systemKind === "browser_marker" || msg.systemKind === "browser_marker_warning" || msg.systemKind === "notice") {
12611
12608
  return [{
12612
12609
  kind: "browser_marker",
12613
12610
  key: msg.id,
@@ -12818,7 +12815,7 @@ function OrphanSystemMarker({
12818
12815
  }
12819
12816
  );
12820
12817
  }
12821
- if (message.systemKind === "browser_marker" || message.systemKind === "browser_marker_warning") {
12818
+ if (message.systemKind === "browser_marker" || message.systemKind === "browser_marker_warning" || message.systemKind === "notice") {
12822
12819
  const warning = message.systemKind === "browser_marker_warning";
12823
12820
  return /* @__PURE__ */ jsxRuntime.jsxs(
12824
12821
  "div",
@@ -13902,7 +13899,9 @@ var AGENT_CHAT_LISTENED_EVENTS = [
13902
13899
  "worker.spawned",
13903
13900
  "worker.complete",
13904
13901
  "worker.failed",
13905
- "thread.proposed"
13902
+ "thread.proposed",
13903
+ "office.conflict",
13904
+ "office.save_failed"
13906
13905
  ];
13907
13906
  var AGENT_CHAT_LISTENED_EVENT_SET = new Set(
13908
13907
  AGENT_CHAT_LISTENED_EVENTS
@@ -14147,7 +14146,8 @@ function createInitialAgentChatState(options = {}) {
14147
14146
  browserTabs: [],
14148
14147
  viewMode: options.viewMode ?? "simple",
14149
14148
  researchSources: [],
14150
- deviceWait: null
14149
+ deviceWait: null,
14150
+ officeEvents: {}
14151
14151
  };
14152
14152
  }
14153
14153
  function applyAgentChatEvent(state, event) {
@@ -14307,6 +14307,21 @@ function applyAgentChatEvent(state, event) {
14307
14307
  };
14308
14308
  case "device.resumed":
14309
14309
  return { ...nextState, deviceWait: null };
14310
+ case "office.conflict":
14311
+ case "office.save_failed": {
14312
+ const path = stringValue2(event.data.path);
14313
+ if (!path) return nextState;
14314
+ const conflict = event.type === "office.conflict";
14315
+ return {
14316
+ ...nextState,
14317
+ officeEvents: {
14318
+ ...nextState.officeEvents,
14319
+ [path]: conflict ? { type: event.type, eventId: event.eventId, conflictPath: stringValue2(event.data.conflict_path) } : { type: event.type, eventId: event.eventId }
14320
+ },
14321
+ // The copy the conflicting save landed as shows in the tree.
14322
+ workspaceRefreshKey: nextState.workspaceRefreshKey + (conflict ? 1 : 0)
14323
+ };
14324
+ }
14310
14325
  case "worker.spawned":
14311
14326
  return withMessages(nextState, applyWorkerSpawned(nextState.messages, event));
14312
14327
  case "worker.complete":
@@ -14647,6 +14662,21 @@ function applyLlmResponse(state, event) {
14647
14662
  const messages = [...state.messages];
14648
14663
  const message = objectValue(event.data.message);
14649
14664
  const responseContent = stringValue2(message?.content) || stringValue2(event.data.content);
14665
+ if (event.data.notice === true) {
14666
+ return {
14667
+ ...state,
14668
+ messages: [...messages, {
14669
+ id: `evt-${event.eventId}`,
14670
+ role: "system",
14671
+ systemKind: "notice",
14672
+ content: responseContent,
14673
+ createdAt: /* @__PURE__ */ new Date(),
14674
+ status: "complete"
14675
+ }],
14676
+ hadDeltas: false,
14677
+ isRunning: false
14678
+ };
14679
+ }
14650
14680
  const toolCalls = Array.isArray(message?.tool_calls) ? message?.tool_calls : [];
14651
14681
  const hasToolCalls = toolCalls.length > 0;
14652
14682
  const responseToolCallIds = toolCallIdsFromResponse(toolCalls);
@@ -19362,12 +19392,42 @@ function chunkPairs(flat) {
19362
19392
  for (let i = 0; i + 1 < flat.length; i += 2) pairs.push([flat[i], flat[i + 1]]);
19363
19393
  return pairs;
19364
19394
  }
19395
+
19396
+ // src/lib/office.ts
19397
+ var OFFICE_EXTENSIONS = /* @__PURE__ */ new Set(["docx", "xlsx", "pptx", "pdf", "doc", "odt", "rtf", "xls", "ods", "ppt", "odp"]);
19398
+ function isOfficeDocument(path) {
19399
+ const dot = path.lastIndexOf(".");
19400
+ return dot >= 0 && OFFICE_EXTENSIONS.has(path.slice(dot + 1).toLowerCase());
19401
+ }
19402
+ var loads = /* @__PURE__ */ new Map();
19403
+ var LOAD_TIMEOUT_MS = 3e4;
19404
+ function loadDocsApi(documentServerUrl) {
19405
+ const src = `${documentServerUrl.replace(/\/$/, "")}/web-apps/apps/api/documents/api.js`;
19406
+ let load = loads.get(src);
19407
+ if (!load) {
19408
+ load = new Promise((resolve, reject) => {
19409
+ const script = document.createElement("script");
19410
+ const settle = (loaded) => {
19411
+ clearTimeout(timer);
19412
+ script.onload = script.onerror = null;
19413
+ if (loaded) return resolve();
19414
+ loads.delete(src);
19415
+ script.remove();
19416
+ reject(new Error("The document editor could not be loaded."));
19417
+ };
19418
+ const timer = setTimeout(() => settle(false), LOAD_TIMEOUT_MS);
19419
+ script.src = src;
19420
+ script.async = true;
19421
+ script.onload = () => settle(Boolean(window.DocsAPI));
19422
+ script.onerror = () => settle(false);
19423
+ document.head.appendChild(script);
19424
+ });
19425
+ loads.set(src, load);
19426
+ }
19427
+ return load;
19428
+ }
19365
19429
  init_utils();
19366
19430
  var SKELETON_WIDTHS = [70, 85, 55, 90, 60, 78, 45, 82, 65, 72];
19367
- var PDF_WORKER_SRC = new URL(
19368
- "pdfjs-dist/legacy/build/pdf.worker.mjs",
19369
- (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))
19370
- ).toString();
19371
19431
  function FileViewer({
19372
19432
  file,
19373
19433
  loading,
@@ -19382,8 +19442,8 @@ function FileViewer({
19382
19442
  if (!visible) return null;
19383
19443
  const fileName2 = file?.path.split("/").pop() ?? "File";
19384
19444
  const lang = file ? getLanguageHint(file.path) : "";
19385
- const isPdf = file?.mime_type === "application/pdf" || file?.path.toLowerCase().endsWith(".pdf") || false;
19386
- const isImage = file?.encoding === "base64" && !isPdf;
19445
+ const isImage = file?.encoding === "base64" && (file.mime_type?.startsWith("image/") ?? false);
19446
+ const isBinary = file?.encoding === "base64" && !isImage;
19387
19447
  const HeaderIcon = isImage ? lucideReact.ImageIcon : lucideReact.FileTextIcon;
19388
19448
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex min-h-0 flex-1 flex-col border-t border-line", className), children: [
19389
19449
  !hideHeader && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-line px-3 py-2", children: [
@@ -19431,12 +19491,7 @@ function FileViewer({
19431
19491
  }
19432
19492
  )
19433
19493
  ] }),
19434
- file && isPdf && file.encoding === "base64" ? (
19435
- // PDF has its own scrollable container — bypass ScrollArea so
19436
- // it can grow with the parent's flex height (Radix Viewport's
19437
- // internal table wrapper sizes to content and breaks h-full).
19438
- /* @__PURE__ */ jsxRuntime.jsx(PdfPreview, { file, fileName: fileName2 })
19439
- ) : /* @__PURE__ */ jsxRuntime.jsxs(ScrollArea, { className: "min-h-0 flex-1", children: [
19494
+ /* @__PURE__ */ jsxRuntime.jsxs(ScrollArea, { className: "min-h-0 flex-1", children: [
19440
19495
  loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5 p-3", children: Array.from({ length: 10 }).map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
19441
19496
  Skeleton,
19442
19497
  {
@@ -19451,7 +19506,7 @@ function FileViewer({
19451
19506
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
19452
19507
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
19453
19508
  ] }),
19454
- file && isPdf && file.encoding !== "base64" && /* @__PURE__ */ jsxRuntime.jsx(FileViewerError, { message: "PDF preview requires base64 file content." }),
19509
+ file && isBinary && /* @__PURE__ */ jsxRuntime.jsx(FileViewerError, { message: "Preview not available. Download the file to open it." }),
19455
19510
  file && isImage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
19456
19511
  "img",
19457
19512
  {
@@ -19460,7 +19515,7 @@ function FileViewer({
19460
19515
  className: "max-h-[60vh] max-w-full rounded object-contain"
19461
19516
  }
19462
19517
  ) }),
19463
- file && !isImage && !isPdf && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-base leading-relaxed text-foreground", children: /* @__PURE__ */ jsxRuntime.jsx("code", { "data-language": lang, children: file.content }) })
19518
+ file && !isImage && !isBinary && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-base leading-relaxed text-foreground", children: /* @__PURE__ */ jsxRuntime.jsx("code", { "data-language": lang, children: file.content }) })
19464
19519
  ] })
19465
19520
  ] });
19466
19521
  }
@@ -19470,277 +19525,94 @@ function FileViewerError({ message }) {
19470
19525
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: message })
19471
19526
  ] });
19472
19527
  }
19473
- function PdfPreview({
19474
- file,
19475
- fileName: fileName2
19476
- }) {
19477
- const containerRef = react.useRef(null);
19478
- const viewerRef = react.useRef(null);
19479
- const pdfViewerRef = react.useRef(null);
19480
- const eventBusRef = react.useRef(null);
19481
- const findControllerRef = react.useRef(null);
19528
+ function OfficeEditorPane({ adapter, sessionId, path, view, officeEvent, onClose }) {
19529
+ const { resolvedTheme } = nextThemes.useTheme();
19530
+ const theme = resolvedTheme === "dark" ? "dark" : "light";
19531
+ const placeholderId = `office-editor-${react.useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
19482
19532
  const [error, setError] = react.useState(null);
19483
- const [pageNumber, setPageNumber] = react.useState(1);
19484
- const [pagesCount, setPagesCount] = react.useState(0);
19485
- const [scale, setScale] = react.useState(1);
19486
- const [query, setQuery] = react.useState("");
19533
+ const [loading, setLoading] = react.useState(true);
19534
+ const [openedAfter] = react.useState(() => officeEvent?.eventId ?? 0);
19535
+ const notice = officeEvent && officeEvent.eventId > openedAfter ? officeEvent : null;
19536
+ const [reopenedFor, setReopenedFor] = react.useState(0);
19537
+ if (notice?.type === "office.conflict" && notice.eventId !== reopenedFor) setReopenedFor(notice.eventId);
19487
19538
  react.useEffect(() => {
19488
19539
  let cancelled = false;
19489
- let loadingTask = null;
19490
- let pdfDocument = null;
19491
- const renderPdf = async () => {
19492
- setError(null);
19493
- const container = containerRef.current;
19494
- const viewerElement = viewerRef.current;
19495
- if (!container || !viewerElement) return;
19496
- const pdfBytes = decodeBase64(file.content);
19497
- const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
19498
- pdfjs.GlobalWorkerOptions.workerSrc = PDF_WORKER_SRC;
19499
- globalThis.pdfjsLib = pdfjs;
19500
- const pdfViewerModule = await import('pdfjs-dist/web/pdf_viewer.mjs');
19501
- loadingTask = pdfjs.getDocument({ data: pdfBytes });
19502
- const pdf = await loadingTask.promise;
19503
- pdfDocument = pdf;
19504
- if (cancelled) return;
19505
- const eventBus = new pdfViewerModule.EventBus();
19506
- const linkService = new pdfViewerModule.PDFLinkService({ eventBus });
19507
- const findController = new pdfViewerModule.PDFFindController({
19508
- eventBus,
19509
- linkService
19510
- });
19511
- const pdfViewer = new pdfViewerModule.PDFViewer({
19512
- container,
19513
- viewer: viewerElement,
19514
- eventBus,
19515
- linkService,
19516
- findController,
19517
- removePageBorders: true
19518
- });
19519
- linkService.setViewer(pdfViewer);
19520
- linkService.setDocument(pdf);
19521
- findController.setDocument(pdf);
19522
- const onPagesInit = () => {
19523
- pdfViewer.currentScaleValue = "page-width";
19524
- setScale(pdfViewer.currentScale || 1);
19525
- setPagesCount(pdfViewer.pagesCount);
19526
- setPageNumber(pdfViewer.currentPageNumber);
19527
- };
19528
- const onPageChanging = (event) => {
19529
- if (typeof event.pageNumber === "number") {
19530
- setPageNumber(event.pageNumber);
19531
- }
19532
- };
19533
- const onScaleChanging = (event) => {
19534
- if (typeof event.scale === "number") {
19535
- setScale(event.scale);
19536
- }
19537
- };
19538
- eventBus.on("pagesinit", onPagesInit);
19539
- eventBus.on("pagechanging", onPageChanging);
19540
- eventBus.on("scalechanging", onScaleChanging);
19541
- pdfViewerRef.current = pdfViewer;
19542
- eventBusRef.current = eventBus;
19543
- findControllerRef.current = findController;
19544
- setPagesCount(pdf.numPages);
19545
- pdfViewer.setDocument(pdf);
19546
- };
19547
- void renderPdf().catch((nextError) => {
19548
- if (cancelled) return;
19549
- if (nextError instanceof Error && nextError.name === "RenderingCancelledException") {
19550
- return;
19540
+ let editor = null;
19541
+ setError(null);
19542
+ setLoading(true);
19543
+ void (async () => {
19544
+ try {
19545
+ if (!adapter.getWorkspaceEditorConfig) throw new Error("Preview not available. Download the file to open it.");
19546
+ const opened = await adapter.getWorkspaceEditorConfig({ sessionId, path, theme, view });
19547
+ await loadDocsApi(opened.document_server_url);
19548
+ if (cancelled) return;
19549
+ if (!window.DocsAPI) throw new Error("The document editor could not be loaded.");
19550
+ editor = new window.DocsAPI.DocEditor(placeholderId, opened.config);
19551
+ } catch (openError) {
19552
+ if (!cancelled) setError(openError.message);
19553
+ } finally {
19554
+ if (!cancelled) setLoading(false);
19551
19555
  }
19552
- setError(formatPdfPreviewError(nextError));
19553
- });
19556
+ })();
19554
19557
  return () => {
19555
19558
  cancelled = true;
19556
- loadingTask?.destroy();
19557
- pdfDocument?.destroy();
19558
- pdfViewerRef.current?.cleanup();
19559
- pdfViewerRef.current = null;
19560
- eventBusRef.current = null;
19561
- findControllerRef.current = null;
19559
+ editor?.destroyEditor();
19562
19560
  };
19563
- }, [file.content]);
19564
- const setViewerPage = (nextPage) => {
19565
- const viewer = pdfViewerRef.current;
19566
- if (!viewer) return;
19567
- const clampedPage = Math.min(
19568
- Math.max(1, nextPage),
19569
- Math.max(1, viewer.pagesCount)
19570
- );
19571
- viewer.currentPageNumber = clampedPage;
19572
- setPageNumber(clampedPage);
19573
- };
19574
- const setViewerScale = (nextScale) => {
19575
- const viewer = pdfViewerRef.current;
19576
- if (!viewer) return;
19577
- const clampedScale = Math.min(3, Math.max(0.5, nextScale));
19578
- viewer.currentScale = clampedScale;
19579
- setScale(clampedScale);
19580
- };
19581
- const runFind = (findPrevious = false) => {
19582
- const eventBus = eventBusRef.current;
19583
- if (!eventBus || !query.trim()) return;
19584
- eventBus.dispatch("find", {
19585
- source: eventBus,
19586
- type: "again",
19587
- query,
19588
- phraseSearch: true,
19589
- caseSensitive: false,
19590
- entireWord: false,
19591
- highlightAll: true,
19592
- findPrevious,
19593
- matchDiacritics: true
19594
- });
19595
- };
19596
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-full flex-col", children: [
19597
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5 border-b border-line px-2 py-1.5", children: [
19598
- /* @__PURE__ */ jsxRuntime.jsx(
19599
- Button,
19600
- {
19601
- type: "button",
19602
- variant: "ghost",
19603
- size: "icon-xs",
19604
- onClick: () => setViewerPage(pageNumber - 1),
19605
- disabled: pageNumber <= 1,
19606
- "aria-label": "Previous PDF page",
19607
- title: "Previous page",
19608
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeftIcon, { className: "size-3.5" })
19609
- }
19610
- ),
19611
- /* @__PURE__ */ jsxRuntime.jsx(
19612
- Input,
19613
- {
19614
- type: "number",
19615
- min: 1,
19616
- max: pagesCount || 1,
19617
- value: pageNumber,
19618
- onChange: (event) => setViewerPage(Number(event.target.value)),
19619
- "aria-label": "PDF page number",
19620
- className: "h-7 w-12 border-input px-1 text-center text-xs"
19621
- }
19622
- ),
19623
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
19624
- "/ ",
19625
- pagesCount || "-"
19626
- ] }),
19627
- /* @__PURE__ */ jsxRuntime.jsx(
19628
- Button,
19629
- {
19630
- type: "button",
19631
- variant: "ghost",
19632
- size: "icon-xs",
19633
- onClick: () => setViewerPage(pageNumber + 1),
19634
- disabled: pagesCount > 0 && pageNumber >= pagesCount,
19635
- "aria-label": "Next PDF page",
19636
- title: "Next page",
19637
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRightIcon, { className: "size-3.5" })
19638
- }
19639
- ),
19640
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
19641
- /* @__PURE__ */ jsxRuntime.jsx(
19642
- Button,
19643
- {
19644
- type: "button",
19645
- variant: "ghost",
19646
- size: "icon-xs",
19647
- onClick: () => setViewerScale(scale - 0.1),
19648
- disabled: scale <= 0.5,
19649
- "aria-label": "Zoom out PDF",
19650
- title: "Zoom out",
19651
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MinusIcon, { className: "size-3.5" })
19652
- }
19653
- ),
19654
- /* @__PURE__ */ jsxRuntime.jsxs(
19655
- "button",
19656
- {
19657
- type: "button",
19658
- className: "h-7 min-w-12 px-1 text-xs text-muted-foreground hover:text-foreground",
19659
- onClick: () => {
19660
- const viewer = pdfViewerRef.current;
19661
- if (!viewer) return;
19662
- viewer.currentScaleValue = "page-width";
19663
- setScale(viewer.currentScale || 1);
19664
- },
19665
- "aria-label": "Fit PDF to width",
19666
- title: "Fit width",
19667
- children: [
19668
- Math.round(scale * 100),
19669
- "%"
19670
- ]
19671
- }
19672
- ),
19673
- /* @__PURE__ */ jsxRuntime.jsx(
19674
- Button,
19675
- {
19676
- type: "button",
19677
- variant: "ghost",
19678
- size: "icon-xs",
19679
- onClick: () => setViewerScale(scale + 0.1),
19680
- disabled: scale >= 3,
19681
- "aria-label": "Zoom in PDF",
19682
- title: "Zoom in",
19683
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlusIcon, { className: "size-3.5" })
19684
- }
19685
- ),
19686
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
19687
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SearchIcon, { className: "size-3.5 text-muted-foreground" }),
19688
- /* @__PURE__ */ jsxRuntime.jsx(
19689
- Input,
19690
- {
19691
- type: "search",
19692
- value: query,
19693
- onChange: (event) => setQuery(event.target.value),
19694
- onKeyDown: (event) => {
19695
- if (event.key === "Enter") runFind(event.shiftKey);
19696
- },
19697
- placeholder: "Find",
19698
- "aria-label": "Find in PDF",
19699
- className: "h-7 w-28 border-input px-1 text-xs"
19700
- }
19701
- ),
19702
- /* @__PURE__ */ jsxRuntime.jsx(
19703
- Button,
19704
- {
19705
- type: "button",
19706
- variant: "ghost",
19707
- size: "xs",
19708
- onClick: () => runFind(false),
19709
- disabled: !query.trim(),
19710
- children: "Find"
19711
- }
19712
- )
19713
- ] }),
19714
- error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-start gap-2 p-3 text-xs text-destructive", children: [
19715
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
19716
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
19717
- ] }),
19718
- !error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative min-h-105 flex-1 bg-muted/20", children: /* @__PURE__ */ jsxRuntime.jsx(
19719
- "div",
19720
- {
19721
- ref: containerRef,
19722
- "aria-label": `PDF viewer for ${fileName2}`,
19723
- className: "absolute inset-0 overflow-auto",
19724
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: viewerRef, className: "pdfViewer" })
19725
- }
19726
- ) })
19727
- ] });
19728
- }
19729
- function decodeBase64(content) {
19730
- const binary = globalThis.atob(content);
19731
- const bytes = new Uint8Array(binary.length);
19732
- for (let index = 0; index < binary.length; index += 1) {
19733
- bytes[index] = binary.charCodeAt(index);
19734
- }
19735
- return bytes;
19561
+ }, [adapter, sessionId, path, theme, view, placeholderId, reopenedFor]);
19562
+ const fileName2 = path.split("/").pop() ?? path;
19563
+ return /* @__PURE__ */ jsxRuntime.jsxs(
19564
+ "aside",
19565
+ {
19566
+ "data-testid": "file-preview-panel",
19567
+ className: "flex h-full min-h-0 w-full flex-col overflow-hidden border-l border-line bg-card",
19568
+ children: [
19569
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-line px-3 py-2", children: [
19570
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileTextIcon, { className: "size-4 shrink-0 text-muted-foreground" }),
19571
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1 truncate font-medium", children: fileName2 }),
19572
+ /* @__PURE__ */ jsxRuntime.jsx(
19573
+ "a",
19574
+ {
19575
+ href: adapter.getWorkspaceDownloadUrl({ sessionId, path }),
19576
+ download: fileName2,
19577
+ "aria-label": `Download ${fileName2}`,
19578
+ title: "Download",
19579
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
19580
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.DownloadIcon, { className: "size-4" })
19581
+ }
19582
+ ),
19583
+ /* @__PURE__ */ jsxRuntime.jsx(
19584
+ "button",
19585
+ {
19586
+ type: "button",
19587
+ onClick: onClose,
19588
+ "aria-label": "Close file",
19589
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
19590
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "size-4" })
19591
+ }
19592
+ )
19593
+ ] }),
19594
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
19595
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
19596
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
19597
+ ] }),
19598
+ notice && /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", className: "border-b border-line px-3 py-2 text-xs text-muted-foreground", children: notice.type === "office.conflict" ? `This document changed outside the editor. Your edits were saved to ${notice.conflictPath}.` : "Your latest changes could not be saved yet." }),
19599
+ loading && !error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 text-sm text-muted-foreground", children: [
19600
+ "Opening ",
19601
+ fileName2,
19602
+ "\u2026"
19603
+ ] }),
19604
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { id: placeholderId }) })
19605
+ ]
19606
+ }
19607
+ );
19736
19608
  }
19737
- function formatPdfPreviewError(error) {
19738
- if (error instanceof Error && (error.name === "InvalidCharacterError" || error.message === "Invalid character")) {
19739
- return "PDF preview data is not valid base64.";
19609
+ function WorkspaceFileDrawer({ adapter, sessionId, path, view = false, officeEvent, onClose }) {
19610
+ if (isOfficeDocument(path)) {
19611
+ return /* @__PURE__ */ jsxRuntime.jsx(OfficeEditorPane, { adapter, sessionId, path, view, officeEvent, onClose });
19740
19612
  }
19741
- return error instanceof Error ? error.message : "Failed to render PDF preview.";
19613
+ return /* @__PURE__ */ jsxRuntime.jsx(WorkspaceFilePreview, { adapter, sessionId, path, onClose });
19742
19614
  }
19743
- function WorkspaceFileDrawer({
19615
+ function WorkspaceFilePreview({
19744
19616
  adapter,
19745
19617
  sessionId,
19746
19618
  path,
@@ -20868,6 +20740,8 @@ function AgentChat({
20868
20740
  adapter,
20869
20741
  sessionId,
20870
20742
  path: previewPath,
20743
+ view: effectiveDisabled,
20744
+ officeEvent: runtime.state.officeEvents[previewPath],
20871
20745
  onClose: () => setOpenPane(null)
20872
20746
  }
20873
20747
  )