@invergent/agent-chat-react 2.17.8 → 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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { cn, __publicField } from './chunk-47MF6JUW.js';
2
2
  import { createContext, memo, useRef, useEffect, useCallback, useMemo, useState, useLayoutEffect, forwardRef, useId, lazy, useContext, useSyncExternalStore, Fragment as Fragment$1, Suspense, Children } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
- import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight as ChevronRight$1, RefreshCw, CreditCard, File as File$1, FileImage, FileAudio, FileVideo, FileText, FileCode, FileArchive, FileSpreadsheet, PlugZapIcon, ChevronRightIcon, ArrowLeftIcon, Loader2Icon, InboxIcon, Loader2, AlertCircle, Pause, Play, Ban, TargetIcon, CalendarClockIcon, UsersIcon, Pen, Eraser, Type, MousePointer2, Hand, Maximize2, Undo2, Redo2, CheckIcon, CopyIcon, DownloadIcon, Maximize2Icon, LaptopIcon, GlobeIcon, FolderIcon, MessageSquareIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, CircleDotIcon, ChevronLeftIcon, XIcon, Trash2Icon, SquareIcon, ArrowDownIcon, TrashIcon, ImageIcon, FileTextIcon, Clock, ArrowRight, ListTreeIcon, PenLineIcon, TerminalIcon, SparklesIcon, ClockIcon, PaperclipIcon, CloudIcon, HardDriveIcon, IdCardIcon, PlusIcon, ShrinkIcon, FolderOpenIcon, FileIcon, MinusIcon, SearchIcon, CircleDot, XCircle, CheckCircle2, ZapIcon, ArrowUpIcon, CircleCheckIcon, ThumbsUpIcon, ThumbsDownIcon, BookOpenIcon, GitBranchIcon, ActivityIcon, EyeIcon, LibraryIcon, ListTodoIcon, BotIcon, ListIcon, FileEditIcon, WrenchIcon, FileCodeIcon, CodeIcon, FileArchiveIcon, FileVideoIcon, FileAudioIcon, FileImageIcon, FileChartLineIcon, FileSpreadsheetIcon, XCircleIcon, CheckCircle2Icon, Code, CircleXIcon, SkullIcon, CheckCircleIcon, CircleIcon } from 'lucide-react';
4
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight as ChevronRight$1, RefreshCw, CreditCard, File as File$1, FileImage, FileAudio, FileVideo, FileText, FileCode, FileArchive, FileSpreadsheet, PlugZapIcon, ChevronRightIcon, ArrowLeftIcon, Loader2Icon, InboxIcon, Loader2, AlertCircle, Pause, Play, Ban, TargetIcon, CalendarClockIcon, UsersIcon, Pen, Eraser, Type, MousePointer2, Hand, Maximize2, Undo2, Redo2, CheckIcon, CopyIcon, DownloadIcon, Maximize2Icon, LaptopIcon, GlobeIcon, FolderIcon, MessageSquareIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, ExternalLinkIcon, ClipboardCheckIcon, ShieldAlertIcon, TimerIcon, CircleDotIcon, ChevronLeftIcon, XIcon, Trash2Icon, SquareIcon, ArrowDownIcon, TrashIcon, FileTextIcon, Clock, ArrowRight, ListTreeIcon, PenLineIcon, TerminalIcon, SparklesIcon, ClockIcon, PaperclipIcon, CloudIcon, HardDriveIcon, IdCardIcon, PlusIcon, ShrinkIcon, FolderOpenIcon, FileIcon, ImageIcon, CircleDot, XCircle, CheckCircle2, SearchIcon, ZapIcon, ArrowUpIcon, CircleCheckIcon, ThumbsUpIcon, ThumbsDownIcon, BookOpenIcon, GitBranchIcon, ActivityIcon, EyeIcon, LibraryIcon, ListTodoIcon, BotIcon, ListIcon, FileEditIcon, WrenchIcon, FileCodeIcon, CodeIcon, FileArchiveIcon, FileVideoIcon, FileAudioIcon, FileImageIcon, FileChartLineIcon, FileSpreadsheetIcon, XCircleIcon, CheckCircle2Icon, Code, CircleXIcon, SkullIcon, CheckCircleIcon, CircleIcon } from 'lucide-react';
5
5
  import { cva } from 'class-variance-authority';
6
6
  import { Collapsible as Collapsible$1, Slot, Tooltip as Tooltip$1, Checkbox as Checkbox$1, Label as Label$1, Progress as Progress$1, Tabs as Tabs$1, Dialog as Dialog$1, ScrollArea as ScrollArea$1, Popover as Popover$1, DropdownMenu as DropdownMenu$1 } from 'radix-ui';
7
7
  import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';
@@ -18,7 +18,7 @@ import AnsiImport from 'ansi-to-react';
18
18
  import { Command as Command$1 } from 'cmdk';
19
19
  import { nanoid } from 'nanoid';
20
20
  import 'tokenlens';
21
- import 'pdfjs-dist/web/pdf_viewer.css';
21
+ import { useTheme } from 'next-themes';
22
22
  import { formatDistanceToNow } from 'date-fns';
23
23
 
24
24
  var AgentChatAdapterContext = createContext(null);
@@ -12284,7 +12284,7 @@ function messageToEntries(msg, isLast, artifactFallbacks) {
12284
12284
  if (msg.systemKind === "worker" || msg.systemKind === "thread_proposal") {
12285
12285
  return [{ kind: "thread_card", key: msg.id, message: msg }];
12286
12286
  }
12287
- if (msg.systemKind === "browser_marker" || msg.systemKind === "browser_marker_warning") {
12287
+ if (msg.systemKind === "browser_marker" || msg.systemKind === "browser_marker_warning" || msg.systemKind === "notice") {
12288
12288
  return [{
12289
12289
  kind: "browser_marker",
12290
12290
  key: msg.id,
@@ -12495,7 +12495,7 @@ function OrphanSystemMarker({
12495
12495
  }
12496
12496
  );
12497
12497
  }
12498
- if (message.systemKind === "browser_marker" || message.systemKind === "browser_marker_warning") {
12498
+ if (message.systemKind === "browser_marker" || message.systemKind === "browser_marker_warning" || message.systemKind === "notice") {
12499
12499
  const warning = message.systemKind === "browser_marker_warning";
12500
12500
  return /* @__PURE__ */ jsxs(
12501
12501
  "div",
@@ -13576,7 +13576,9 @@ var AGENT_CHAT_LISTENED_EVENTS = [
13576
13576
  "worker.spawned",
13577
13577
  "worker.complete",
13578
13578
  "worker.failed",
13579
- "thread.proposed"
13579
+ "thread.proposed",
13580
+ "office.conflict",
13581
+ "office.save_failed"
13580
13582
  ];
13581
13583
  var AGENT_CHAT_LISTENED_EVENT_SET = new Set(
13582
13584
  AGENT_CHAT_LISTENED_EVENTS
@@ -13821,7 +13823,8 @@ function createInitialAgentChatState(options = {}) {
13821
13823
  browserTabs: [],
13822
13824
  viewMode: options.viewMode ?? "simple",
13823
13825
  researchSources: [],
13824
- deviceWait: null
13826
+ deviceWait: null,
13827
+ officeEvents: {}
13825
13828
  };
13826
13829
  }
13827
13830
  function applyAgentChatEvent(state, event) {
@@ -13981,6 +13984,21 @@ function applyAgentChatEvent(state, event) {
13981
13984
  };
13982
13985
  case "device.resumed":
13983
13986
  return { ...nextState, deviceWait: null };
13987
+ case "office.conflict":
13988
+ case "office.save_failed": {
13989
+ const path = stringValue2(event.data.path);
13990
+ if (!path) return nextState;
13991
+ const conflict = event.type === "office.conflict";
13992
+ return {
13993
+ ...nextState,
13994
+ officeEvents: {
13995
+ ...nextState.officeEvents,
13996
+ [path]: conflict ? { type: event.type, eventId: event.eventId, conflictPath: stringValue2(event.data.conflict_path) } : { type: event.type, eventId: event.eventId }
13997
+ },
13998
+ // The copy the conflicting save landed as shows in the tree.
13999
+ workspaceRefreshKey: nextState.workspaceRefreshKey + (conflict ? 1 : 0)
14000
+ };
14001
+ }
13984
14002
  case "worker.spawned":
13985
14003
  return withMessages(nextState, applyWorkerSpawned(nextState.messages, event));
13986
14004
  case "worker.complete":
@@ -14321,6 +14339,21 @@ function applyLlmResponse(state, event) {
14321
14339
  const messages = [...state.messages];
14322
14340
  const message = objectValue(event.data.message);
14323
14341
  const responseContent = stringValue2(message?.content) || stringValue2(event.data.content);
14342
+ if (event.data.notice === true) {
14343
+ return {
14344
+ ...state,
14345
+ messages: [...messages, {
14346
+ id: `evt-${event.eventId}`,
14347
+ role: "system",
14348
+ systemKind: "notice",
14349
+ content: responseContent,
14350
+ createdAt: /* @__PURE__ */ new Date(),
14351
+ status: "complete"
14352
+ }],
14353
+ hadDeltas: false,
14354
+ isRunning: false
14355
+ };
14356
+ }
14324
14357
  const toolCalls = Array.isArray(message?.tool_calls) ? message?.tool_calls : [];
14325
14358
  const hasToolCalls = toolCalls.length > 0;
14326
14359
  const responseToolCallIds = toolCallIdsFromResponse(toolCalls);
@@ -19027,11 +19060,41 @@ function chunkPairs(flat) {
19027
19060
  for (let i = 0; i + 1 < flat.length; i += 2) pairs.push([flat[i], flat[i + 1]]);
19028
19061
  return pairs;
19029
19062
  }
19063
+
19064
+ // src/lib/office.ts
19065
+ var OFFICE_EXTENSIONS = /* @__PURE__ */ new Set(["docx", "xlsx", "pptx", "pdf", "doc", "odt", "rtf", "xls", "ods", "ppt", "odp"]);
19066
+ function isOfficeDocument(path) {
19067
+ const dot = path.lastIndexOf(".");
19068
+ return dot >= 0 && OFFICE_EXTENSIONS.has(path.slice(dot + 1).toLowerCase());
19069
+ }
19070
+ var loads = /* @__PURE__ */ new Map();
19071
+ var LOAD_TIMEOUT_MS = 3e4;
19072
+ function loadDocsApi(documentServerUrl) {
19073
+ const src = `${documentServerUrl.replace(/\/$/, "")}/web-apps/apps/api/documents/api.js`;
19074
+ let load = loads.get(src);
19075
+ if (!load) {
19076
+ load = new Promise((resolve, reject) => {
19077
+ const script = document.createElement("script");
19078
+ const settle = (loaded) => {
19079
+ clearTimeout(timer);
19080
+ script.onload = script.onerror = null;
19081
+ if (loaded) return resolve();
19082
+ loads.delete(src);
19083
+ script.remove();
19084
+ reject(new Error("The document editor could not be loaded."));
19085
+ };
19086
+ const timer = setTimeout(() => settle(false), LOAD_TIMEOUT_MS);
19087
+ script.src = src;
19088
+ script.async = true;
19089
+ script.onload = () => settle(Boolean(window.DocsAPI));
19090
+ script.onerror = () => settle(false);
19091
+ document.head.appendChild(script);
19092
+ });
19093
+ loads.set(src, load);
19094
+ }
19095
+ return load;
19096
+ }
19030
19097
  var SKELETON_WIDTHS = [70, 85, 55, 90, 60, 78, 45, 82, 65, 72];
19031
- var PDF_WORKER_SRC = new URL(
19032
- "pdfjs-dist/legacy/build/pdf.worker.mjs",
19033
- import.meta.url
19034
- ).toString();
19035
19098
  function FileViewer({
19036
19099
  file,
19037
19100
  loading,
@@ -19046,8 +19109,8 @@ function FileViewer({
19046
19109
  if (!visible) return null;
19047
19110
  const fileName2 = file?.path.split("/").pop() ?? "File";
19048
19111
  const lang = file ? getLanguageHint(file.path) : "";
19049
- const isPdf = file?.mime_type === "application/pdf" || file?.path.toLowerCase().endsWith(".pdf") || false;
19050
- const isImage = file?.encoding === "base64" && !isPdf;
19112
+ const isImage = file?.encoding === "base64" && (file.mime_type?.startsWith("image/") ?? false);
19113
+ const isBinary = file?.encoding === "base64" && !isImage;
19051
19114
  const HeaderIcon = isImage ? ImageIcon : FileTextIcon;
19052
19115
  return /* @__PURE__ */ jsxs("div", { className: cn("flex min-h-0 flex-1 flex-col border-t border-line", className), children: [
19053
19116
  !hideHeader && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-line px-3 py-2", children: [
@@ -19095,12 +19158,7 @@ function FileViewer({
19095
19158
  }
19096
19159
  )
19097
19160
  ] }),
19098
- file && isPdf && file.encoding === "base64" ? (
19099
- // PDF has its own scrollable container — bypass ScrollArea so
19100
- // it can grow with the parent's flex height (Radix Viewport's
19101
- // internal table wrapper sizes to content and breaks h-full).
19102
- /* @__PURE__ */ jsx(PdfPreview, { file, fileName: fileName2 })
19103
- ) : /* @__PURE__ */ jsxs(ScrollArea, { className: "min-h-0 flex-1", children: [
19161
+ /* @__PURE__ */ jsxs(ScrollArea, { className: "min-h-0 flex-1", children: [
19104
19162
  loading && /* @__PURE__ */ jsx("div", { className: "space-y-1.5 p-3", children: Array.from({ length: 10 }).map((_, index) => /* @__PURE__ */ jsx(
19105
19163
  Skeleton,
19106
19164
  {
@@ -19115,7 +19173,7 @@ function FileViewer({
19115
19173
  /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
19116
19174
  /* @__PURE__ */ jsx("span", { children: error })
19117
19175
  ] }),
19118
- file && isPdf && file.encoding !== "base64" && /* @__PURE__ */ jsx(FileViewerError, { message: "PDF preview requires base64 file content." }),
19176
+ file && isBinary && /* @__PURE__ */ jsx(FileViewerError, { message: "Preview not available. Download the file to open it." }),
19119
19177
  file && isImage && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsx(
19120
19178
  "img",
19121
19179
  {
@@ -19124,7 +19182,7 @@ function FileViewer({
19124
19182
  className: "max-h-[60vh] max-w-full rounded object-contain"
19125
19183
  }
19126
19184
  ) }),
19127
- file && !isImage && !isPdf && /* @__PURE__ */ jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-base leading-relaxed text-foreground", children: /* @__PURE__ */ jsx("code", { "data-language": lang, children: file.content }) })
19185
+ file && !isImage && !isBinary && /* @__PURE__ */ jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-base leading-relaxed text-foreground", children: /* @__PURE__ */ jsx("code", { "data-language": lang, children: file.content }) })
19128
19186
  ] })
19129
19187
  ] });
19130
19188
  }
@@ -19134,277 +19192,94 @@ function FileViewerError({ message }) {
19134
19192
  /* @__PURE__ */ jsx("span", { children: message })
19135
19193
  ] });
19136
19194
  }
19137
- function PdfPreview({
19138
- file,
19139
- fileName: fileName2
19140
- }) {
19141
- const containerRef = useRef(null);
19142
- const viewerRef = useRef(null);
19143
- const pdfViewerRef = useRef(null);
19144
- const eventBusRef = useRef(null);
19145
- const findControllerRef = useRef(null);
19195
+ function OfficeEditorPane({ adapter, sessionId, path, view, officeEvent, onClose }) {
19196
+ const { resolvedTheme } = useTheme();
19197
+ const theme = resolvedTheme === "dark" ? "dark" : "light";
19198
+ const placeholderId = `office-editor-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
19146
19199
  const [error, setError] = useState(null);
19147
- const [pageNumber, setPageNumber] = useState(1);
19148
- const [pagesCount, setPagesCount] = useState(0);
19149
- const [scale, setScale] = useState(1);
19150
- const [query, setQuery] = useState("");
19200
+ const [loading, setLoading] = useState(true);
19201
+ const [openedAfter] = useState(() => officeEvent?.eventId ?? 0);
19202
+ const notice = officeEvent && officeEvent.eventId > openedAfter ? officeEvent : null;
19203
+ const [reopenedFor, setReopenedFor] = useState(0);
19204
+ if (notice?.type === "office.conflict" && notice.eventId !== reopenedFor) setReopenedFor(notice.eventId);
19151
19205
  useEffect(() => {
19152
19206
  let cancelled = false;
19153
- let loadingTask = null;
19154
- let pdfDocument = null;
19155
- const renderPdf = async () => {
19156
- setError(null);
19157
- const container = containerRef.current;
19158
- const viewerElement = viewerRef.current;
19159
- if (!container || !viewerElement) return;
19160
- const pdfBytes = decodeBase64(file.content);
19161
- const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
19162
- pdfjs.GlobalWorkerOptions.workerSrc = PDF_WORKER_SRC;
19163
- globalThis.pdfjsLib = pdfjs;
19164
- const pdfViewerModule = await import('pdfjs-dist/web/pdf_viewer.mjs');
19165
- loadingTask = pdfjs.getDocument({ data: pdfBytes });
19166
- const pdf = await loadingTask.promise;
19167
- pdfDocument = pdf;
19168
- if (cancelled) return;
19169
- const eventBus = new pdfViewerModule.EventBus();
19170
- const linkService = new pdfViewerModule.PDFLinkService({ eventBus });
19171
- const findController = new pdfViewerModule.PDFFindController({
19172
- eventBus,
19173
- linkService
19174
- });
19175
- const pdfViewer = new pdfViewerModule.PDFViewer({
19176
- container,
19177
- viewer: viewerElement,
19178
- eventBus,
19179
- linkService,
19180
- findController,
19181
- removePageBorders: true
19182
- });
19183
- linkService.setViewer(pdfViewer);
19184
- linkService.setDocument(pdf);
19185
- findController.setDocument(pdf);
19186
- const onPagesInit = () => {
19187
- pdfViewer.currentScaleValue = "page-width";
19188
- setScale(pdfViewer.currentScale || 1);
19189
- setPagesCount(pdfViewer.pagesCount);
19190
- setPageNumber(pdfViewer.currentPageNumber);
19191
- };
19192
- const onPageChanging = (event) => {
19193
- if (typeof event.pageNumber === "number") {
19194
- setPageNumber(event.pageNumber);
19195
- }
19196
- };
19197
- const onScaleChanging = (event) => {
19198
- if (typeof event.scale === "number") {
19199
- setScale(event.scale);
19200
- }
19201
- };
19202
- eventBus.on("pagesinit", onPagesInit);
19203
- eventBus.on("pagechanging", onPageChanging);
19204
- eventBus.on("scalechanging", onScaleChanging);
19205
- pdfViewerRef.current = pdfViewer;
19206
- eventBusRef.current = eventBus;
19207
- findControllerRef.current = findController;
19208
- setPagesCount(pdf.numPages);
19209
- pdfViewer.setDocument(pdf);
19210
- };
19211
- void renderPdf().catch((nextError) => {
19212
- if (cancelled) return;
19213
- if (nextError instanceof Error && nextError.name === "RenderingCancelledException") {
19214
- return;
19207
+ let editor = null;
19208
+ setError(null);
19209
+ setLoading(true);
19210
+ void (async () => {
19211
+ try {
19212
+ if (!adapter.getWorkspaceEditorConfig) throw new Error("Preview not available. Download the file to open it.");
19213
+ const opened = await adapter.getWorkspaceEditorConfig({ sessionId, path, theme, view });
19214
+ await loadDocsApi(opened.document_server_url);
19215
+ if (cancelled) return;
19216
+ if (!window.DocsAPI) throw new Error("The document editor could not be loaded.");
19217
+ editor = new window.DocsAPI.DocEditor(placeholderId, opened.config);
19218
+ } catch (openError) {
19219
+ if (!cancelled) setError(openError.message);
19220
+ } finally {
19221
+ if (!cancelled) setLoading(false);
19215
19222
  }
19216
- setError(formatPdfPreviewError(nextError));
19217
- });
19223
+ })();
19218
19224
  return () => {
19219
19225
  cancelled = true;
19220
- loadingTask?.destroy();
19221
- pdfDocument?.destroy();
19222
- pdfViewerRef.current?.cleanup();
19223
- pdfViewerRef.current = null;
19224
- eventBusRef.current = null;
19225
- findControllerRef.current = null;
19226
+ editor?.destroyEditor();
19226
19227
  };
19227
- }, [file.content]);
19228
- const setViewerPage = (nextPage) => {
19229
- const viewer = pdfViewerRef.current;
19230
- if (!viewer) return;
19231
- const clampedPage = Math.min(
19232
- Math.max(1, nextPage),
19233
- Math.max(1, viewer.pagesCount)
19234
- );
19235
- viewer.currentPageNumber = clampedPage;
19236
- setPageNumber(clampedPage);
19237
- };
19238
- const setViewerScale = (nextScale) => {
19239
- const viewer = pdfViewerRef.current;
19240
- if (!viewer) return;
19241
- const clampedScale = Math.min(3, Math.max(0.5, nextScale));
19242
- viewer.currentScale = clampedScale;
19243
- setScale(clampedScale);
19244
- };
19245
- const runFind = (findPrevious = false) => {
19246
- const eventBus = eventBusRef.current;
19247
- if (!eventBus || !query.trim()) return;
19248
- eventBus.dispatch("find", {
19249
- source: eventBus,
19250
- type: "again",
19251
- query,
19252
- phraseSearch: true,
19253
- caseSensitive: false,
19254
- entireWord: false,
19255
- highlightAll: true,
19256
- findPrevious,
19257
- matchDiacritics: true
19258
- });
19259
- };
19260
- return /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col", children: [
19261
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5 border-b border-line px-2 py-1.5", children: [
19262
- /* @__PURE__ */ jsx(
19263
- Button,
19264
- {
19265
- type: "button",
19266
- variant: "ghost",
19267
- size: "icon-xs",
19268
- onClick: () => setViewerPage(pageNumber - 1),
19269
- disabled: pageNumber <= 1,
19270
- "aria-label": "Previous PDF page",
19271
- title: "Previous page",
19272
- children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-3.5" })
19273
- }
19274
- ),
19275
- /* @__PURE__ */ jsx(
19276
- Input,
19277
- {
19278
- type: "number",
19279
- min: 1,
19280
- max: pagesCount || 1,
19281
- value: pageNumber,
19282
- onChange: (event) => setViewerPage(Number(event.target.value)),
19283
- "aria-label": "PDF page number",
19284
- className: "h-7 w-12 border-input px-1 text-center text-xs"
19285
- }
19286
- ),
19287
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
19288
- "/ ",
19289
- pagesCount || "-"
19290
- ] }),
19291
- /* @__PURE__ */ jsx(
19292
- Button,
19293
- {
19294
- type: "button",
19295
- variant: "ghost",
19296
- size: "icon-xs",
19297
- onClick: () => setViewerPage(pageNumber + 1),
19298
- disabled: pagesCount > 0 && pageNumber >= pagesCount,
19299
- "aria-label": "Next PDF page",
19300
- title: "Next page",
19301
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5" })
19302
- }
19303
- ),
19304
- /* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
19305
- /* @__PURE__ */ jsx(
19306
- Button,
19307
- {
19308
- type: "button",
19309
- variant: "ghost",
19310
- size: "icon-xs",
19311
- onClick: () => setViewerScale(scale - 0.1),
19312
- disabled: scale <= 0.5,
19313
- "aria-label": "Zoom out PDF",
19314
- title: "Zoom out",
19315
- children: /* @__PURE__ */ jsx(MinusIcon, { className: "size-3.5" })
19316
- }
19317
- ),
19318
- /* @__PURE__ */ jsxs(
19319
- "button",
19320
- {
19321
- type: "button",
19322
- className: "h-7 min-w-12 px-1 text-xs text-muted-foreground hover:text-foreground",
19323
- onClick: () => {
19324
- const viewer = pdfViewerRef.current;
19325
- if (!viewer) return;
19326
- viewer.currentScaleValue = "page-width";
19327
- setScale(viewer.currentScale || 1);
19328
- },
19329
- "aria-label": "Fit PDF to width",
19330
- title: "Fit width",
19331
- children: [
19332
- Math.round(scale * 100),
19333
- "%"
19334
- ]
19335
- }
19336
- ),
19337
- /* @__PURE__ */ jsx(
19338
- Button,
19339
- {
19340
- type: "button",
19341
- variant: "ghost",
19342
- size: "icon-xs",
19343
- onClick: () => setViewerScale(scale + 0.1),
19344
- disabled: scale >= 3,
19345
- "aria-label": "Zoom in PDF",
19346
- title: "Zoom in",
19347
- children: /* @__PURE__ */ jsx(PlusIcon, { className: "size-3.5" })
19348
- }
19349
- ),
19350
- /* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
19351
- /* @__PURE__ */ jsx(SearchIcon, { className: "size-3.5 text-muted-foreground" }),
19352
- /* @__PURE__ */ jsx(
19353
- Input,
19354
- {
19355
- type: "search",
19356
- value: query,
19357
- onChange: (event) => setQuery(event.target.value),
19358
- onKeyDown: (event) => {
19359
- if (event.key === "Enter") runFind(event.shiftKey);
19360
- },
19361
- placeholder: "Find",
19362
- "aria-label": "Find in PDF",
19363
- className: "h-7 w-28 border-input px-1 text-xs"
19364
- }
19365
- ),
19366
- /* @__PURE__ */ jsx(
19367
- Button,
19368
- {
19369
- type: "button",
19370
- variant: "ghost",
19371
- size: "xs",
19372
- onClick: () => runFind(false),
19373
- disabled: !query.trim(),
19374
- children: "Find"
19375
- }
19376
- )
19377
- ] }),
19378
- error && /* @__PURE__ */ jsxs("div", { className: "flex w-full items-start gap-2 p-3 text-xs text-destructive", children: [
19379
- /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
19380
- /* @__PURE__ */ jsx("span", { children: error })
19381
- ] }),
19382
- !error && /* @__PURE__ */ jsx("div", { className: "relative min-h-105 flex-1 bg-muted/20", children: /* @__PURE__ */ jsx(
19383
- "div",
19384
- {
19385
- ref: containerRef,
19386
- "aria-label": `PDF viewer for ${fileName2}`,
19387
- className: "absolute inset-0 overflow-auto",
19388
- children: /* @__PURE__ */ jsx("div", { ref: viewerRef, className: "pdfViewer" })
19389
- }
19390
- ) })
19391
- ] });
19392
- }
19393
- function decodeBase64(content) {
19394
- const binary = globalThis.atob(content);
19395
- const bytes = new Uint8Array(binary.length);
19396
- for (let index = 0; index < binary.length; index += 1) {
19397
- bytes[index] = binary.charCodeAt(index);
19398
- }
19399
- return bytes;
19228
+ }, [adapter, sessionId, path, theme, view, placeholderId, reopenedFor]);
19229
+ const fileName2 = path.split("/").pop() ?? path;
19230
+ return /* @__PURE__ */ jsxs(
19231
+ "aside",
19232
+ {
19233
+ "data-testid": "file-preview-panel",
19234
+ className: "flex h-full min-h-0 w-full flex-col overflow-hidden border-l border-line bg-card",
19235
+ children: [
19236
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-line px-3 py-2", children: [
19237
+ /* @__PURE__ */ jsx(FileTextIcon, { className: "size-4 shrink-0 text-muted-foreground" }),
19238
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 truncate font-medium", children: fileName2 }),
19239
+ /* @__PURE__ */ jsx(
19240
+ "a",
19241
+ {
19242
+ href: adapter.getWorkspaceDownloadUrl({ sessionId, path }),
19243
+ download: fileName2,
19244
+ "aria-label": `Download ${fileName2}`,
19245
+ title: "Download",
19246
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
19247
+ children: /* @__PURE__ */ jsx(DownloadIcon, { className: "size-4" })
19248
+ }
19249
+ ),
19250
+ /* @__PURE__ */ jsx(
19251
+ "button",
19252
+ {
19253
+ type: "button",
19254
+ onClick: onClose,
19255
+ "aria-label": "Close file",
19256
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
19257
+ children: /* @__PURE__ */ jsx(XIcon, { className: "size-4" })
19258
+ }
19259
+ )
19260
+ ] }),
19261
+ error && /* @__PURE__ */ jsxs("div", { role: "alert", className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
19262
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
19263
+ /* @__PURE__ */ jsx("span", { children: error })
19264
+ ] }),
19265
+ notice && /* @__PURE__ */ 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." }),
19266
+ loading && !error && /* @__PURE__ */ jsxs("div", { className: "p-3 text-sm text-muted-foreground", children: [
19267
+ "Opening ",
19268
+ fileName2,
19269
+ "\u2026"
19270
+ ] }),
19271
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx("div", { id: placeholderId }) })
19272
+ ]
19273
+ }
19274
+ );
19400
19275
  }
19401
- function formatPdfPreviewError(error) {
19402
- if (error instanceof Error && (error.name === "InvalidCharacterError" || error.message === "Invalid character")) {
19403
- return "PDF preview data is not valid base64.";
19276
+ function WorkspaceFileDrawer({ adapter, sessionId, path, view = false, officeEvent, onClose }) {
19277
+ if (isOfficeDocument(path)) {
19278
+ return /* @__PURE__ */ jsx(OfficeEditorPane, { adapter, sessionId, path, view, officeEvent, onClose });
19404
19279
  }
19405
- return error instanceof Error ? error.message : "Failed to render PDF preview.";
19280
+ return /* @__PURE__ */ jsx(WorkspaceFilePreview, { adapter, sessionId, path, onClose });
19406
19281
  }
19407
- function WorkspaceFileDrawer({
19282
+ function WorkspaceFilePreview({
19408
19283
  adapter,
19409
19284
  sessionId,
19410
19285
  path,
@@ -20525,6 +20400,8 @@ function AgentChat({
20525
20400
  adapter,
20526
20401
  sessionId,
20527
20402
  path: previewPath,
20403
+ view: effectiveDisabled,
20404
+ officeEvent: runtime.state.officeEvents[previewPath],
20528
20405
  onClose: () => setOpenPane(null)
20529
20406
  }
20530
20407
  )