opencami 1.6.0 → 1.7.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.
Files changed (57) hide show
  1. package/README.md +48 -31
  2. package/dist/client/assets/{CSPContext-Bq8j4nl9.js → CSPContext-a-MQmQQt.js} +1 -1
  3. package/dist/client/assets/{DirectionContext-BdX86BHP.js → DirectionContext-DRcND-Cm.js} +1 -1
  4. package/dist/client/assets/_sessionKey-BAmpzUOP.js +23 -0
  5. package/dist/client/assets/agents-BkeWu_3a.js +2 -0
  6. package/dist/client/assets/{agents-screen-DwIY8hze.js → agents-screen-Cb76bcxn.js} +1 -1
  7. package/dist/client/assets/bots-CyJwr-JU.js +2 -0
  8. package/dist/client/assets/{bots-screen-c78I920d.js → bots-screen-CzNjLsQH.js} +1 -1
  9. package/dist/client/assets/{button-Dg7VFQQn.js → button-DNC5N25i.js} +1 -1
  10. package/dist/client/assets/{composite-DBl8R3ae.js → composite-Bliqcmg4.js} +1 -1
  11. package/dist/client/assets/{connect-NYvOqiBJ.js → connect-BX1MWO82.js} +1 -1
  12. package/dist/client/assets/{file-explorer-screen-BSMbs0vi.js → file-explorer-screen-CpY1O_ag.js} +1 -1
  13. package/dist/client/assets/files-HiN5rXWq.js +2 -0
  14. package/dist/client/assets/{index-CMATW8VA.js → index-BlC2sH55.js} +1 -1
  15. package/dist/client/assets/{index-rOIRO-8E.js → index-Dg0mbvtv.js} +1 -1
  16. package/dist/client/assets/{keyboard-shortcuts-dialog-BTGWdJMl.js → keyboard-shortcuts-dialog-C2Hq19LN.js} +1 -1
  17. package/dist/client/assets/{main-B_dlfHME.js → main-CEuT8-Qi.js} +14 -5
  18. package/dist/client/assets/{markdown-BVzT7z4x.js → markdown-DKD6ZLRJ.js} +1 -1
  19. package/dist/client/assets/memory-lhzf-8Q4.js +2 -0
  20. package/dist/client/assets/{memory-screen-C-Z9o31m.js → memory-screen-Zq9qfnJK.js} +1 -1
  21. package/dist/client/assets/{menu-DHNgWk_8.js → menu-47ooFeSm.js} +1 -1
  22. package/dist/client/assets/{opencami-logo-BQQETnJG.js → opencami-logo-DA69yVKc.js} +1 -1
  23. package/dist/client/assets/{owner-CpRnf1fI.js → owner-CFRNz_Tp.js} +1 -1
  24. package/dist/client/assets/{popupStateMapping-BRPDXnjv.js → popupStateMapping-D5k-jOeY.js} +1 -1
  25. package/dist/client/assets/{proxy-BcUh9kMA.js → proxy-Cawf6X0W.js} +1 -1
  26. package/dist/client/assets/{react-irH8OzhB.js → react-K9goXsVv.js} +1 -1
  27. package/dist/client/assets/{search-dialog-B96zx_ng.js → search-dialog-C5Yae9rb.js} +1 -1
  28. package/dist/client/assets/{session-export-dialog-DPuHnhgv.js → session-export-dialog-CBeTfbll.js} +1 -1
  29. package/dist/client/assets/settings-dialog-CoeG9M1b.js +1 -0
  30. package/dist/client/assets/skills-BEkw619A.js +2 -0
  31. package/dist/client/assets/{skills-panel-WDUfIwnI.js → skills-panel-DFL-3BRH.js} +1 -1
  32. package/dist/client/assets/styles-D4EBtWYc.css +1 -0
  33. package/dist/client/assets/{switch-DPocNFRG.js → switch-DAFvLxNX.js} +1 -1
  34. package/dist/client/assets/{tabs-B0cro1hL.js → tabs-B2Y_7MvG.js} +1 -1
  35. package/dist/client/assets/{tooltip-Dg9fy-vT.js → tooltip-D57Pal0B.js} +1 -1
  36. package/dist/client/assets/{use-file-explorer-state-DzT0bksg.js → use-file-explorer-state-DppKEjcl.js} +1 -1
  37. package/dist/client/assets/{useButton-Cbl_9oFG.js → useButton-DVAfkehQ.js} +1 -1
  38. package/dist/client/assets/{useCompositeItem-BDAzTxVe.js → useCompositeItem-CzdGhGcj.js} +1 -1
  39. package/dist/client/assets/{useControlled-Dscz_s4f.js → useControlled-Cl9XA2_f.js} +1 -1
  40. package/dist/client/assets/{useMutation-B1FlDsNN.js → useMutation-C5bTdeC1.js} +1 -1
  41. package/dist/client/assets/{visuallyHidden-ONmQ-0U2.js → visuallyHidden-CO3ZD5AQ.js} +1 -1
  42. package/dist/server/assets/{_sessionKey-B0ZlLAjH.js → _sessionKey-BBG3ZUlo.js} +279 -27
  43. package/dist/server/assets/{_tanstack-start-manifest_v-D5UVTs1o.js → _tanstack-start-manifest_v-DmMFarHb.js} +1 -1
  44. package/dist/server/assets/{index-CiUjUD0t.js → index-BgMPaOsU.js} +1 -1
  45. package/dist/server/assets/{router-Uuagl6O7.js → router-Bl2uabfY.js} +38 -9
  46. package/dist/server/assets/{search-dialog-DZTS5SEi.js → search-dialog-BtSQW9SR.js} +2 -2
  47. package/dist/server/assets/{settings-dialog-CSYDj2qm.js → settings-dialog-D3fOAswX.js} +11 -4
  48. package/dist/server/server.js +2 -2
  49. package/package.json +1 -1
  50. package/dist/client/assets/_sessionKey-DsjnpErt.js +0 -14
  51. package/dist/client/assets/agents-DwxKcpP6.js +0 -2
  52. package/dist/client/assets/bots-CRlm-3-d.js +0 -2
  53. package/dist/client/assets/files-BJbMx0_w.js +0 -2
  54. package/dist/client/assets/memory-S3Yws6a5.js +0 -2
  55. package/dist/client/assets/settings-dialog-DZcRCaPj.js +0 -1
  56. package/dist/client/assets/skills-YZe3I63y.js +0 -2
  57. package/dist/client/assets/styles-Bwo-K6Y4.css +0 -1
@@ -5,9 +5,9 @@ import React__default, { memo, useDeferredValue, useState, useMemo, useCallback,
5
5
  import { useQueryClient, useMutation, useQuery } from "@tanstack/react-query";
6
6
  import { T as TooltipProvider, a as TooltipRoot, b as TooltipTrigger, c as TooltipContent, s as setChatUiState, d as chatUiQueryKey, g as getChatUiState } from "./tooltip-DgsSPocE.js";
7
7
  import { HugeiconsIcon } from "@hugeicons/react";
8
- import { Tick01Icon, Cancel01Icon, MoreHorizontalIcon, Pen01Icon, Upload01Icon, Delete01Icon, BotIcon, Clock01Icon, Chat01Icon, ArrowRight01Icon, SidebarLeft01Icon, PencilEdit02Icon, Folder01Icon, AiBrain01Icon, PackageOpenIcon, SmartPhone01Icon, Search01Icon, Settings01Icon, Menu01Icon, Tick02Icon, Copy01Icon, Loading02Icon, StopIcon, VolumeHighIcon, ArrowDown01Icon, Loading03Icon, ArtificialIntelligence02Icon, CommandIcon, Attachment01Icon, File01Icon, Mic02Icon, ArrowUp02Icon } from "@hugeicons/core-free-icons";
8
+ import { Tick01Icon, Cancel01Icon, MoreHorizontalIcon, Pen01Icon, Upload01Icon, Delete01Icon, BotIcon, Clock01Icon, Chat01Icon, ArrowRight01Icon, SidebarLeft01Icon, PencilEdit02Icon, Folder01Icon, AiBrain01Icon, PackageOpenIcon, SmartPhone01Icon, Search01Icon, Settings01Icon, Menu01Icon, Tick02Icon, Copy01Icon, Loading02Icon, StopIcon, VolumeHighIcon, ArrowDown01Icon, Loading03Icon, File01Icon, ArtificialIntelligence02Icon, CommandIcon, Attachment01Icon, Mic02Icon, ArrowUp02Icon } from "@hugeicons/core-free-icons";
9
9
  import { motion, AnimatePresence } from "motion/react";
10
- import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose } from "./use-file-explorer-state-s7CS50ho.js";
10
+ import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose, u as useFileExplorerState } from "./use-file-explorer-state-s7CS50ho.js";
11
11
  import { B as Button, c as cn, b as buttonVariants } from "./button-CwY2OHFj.js";
12
12
  import { AlertDialog } from "@base-ui/react/alert-dialog";
13
13
  import { Collapsible as Collapsible$1 } from "@base-ui/react/collapsible";
@@ -19,7 +19,7 @@ import { u as useChatSettings$1 } from "./index-Dl2BOKP7.js";
19
19
  import { create } from "zustand";
20
20
  import { persist } from "zustand/middleware";
21
21
  import { createPortal } from "react-dom";
22
- import { a as Route } from "./router-Uuagl6O7.js";
22
+ import { a as Route } from "./router-Bl2uabfY.js";
23
23
  function deriveFriendlyIdFromKey(key) {
24
24
  if (!key) return "main";
25
25
  const trimmed = key.trim();
@@ -1662,7 +1662,7 @@ function useRenameSession() {
1662
1662
  return { renameSession, renaming, error };
1663
1663
  }
1664
1664
  const SettingsDialog = lazy(
1665
- () => import("./settings-dialog-CSYDj2qm.js").then((m) => ({ default: m.SettingsDialog }))
1665
+ () => import("./settings-dialog-D3fOAswX.js").then((m) => ({ default: m.SettingsDialog }))
1666
1666
  );
1667
1667
  const SessionExportDialog = lazy(
1668
1668
  () => import("./session-export-dialog-C53RRAah.js").then((m) => ({
@@ -2998,6 +2998,73 @@ function imagesFromMessage(msg) {
2998
2998
  }
2999
2999
  return images;
3000
3000
  }
3001
+ const UPLOADED_FILE_LINE_REGEX = /^📎 Uploaded file:\s*(\/uploads\/\S+)\s*$/;
3002
+ function formatFileSize$1(bytes) {
3003
+ if (bytes === null || Number.isNaN(bytes)) return "Unknown size";
3004
+ if (bytes < 1024) return `${bytes} B`;
3005
+ const units = ["KB", "MB", "GB", "TB"];
3006
+ let value = bytes / 1024;
3007
+ let unitIndex = 0;
3008
+ while (value >= 1024 && unitIndex < units.length - 1) {
3009
+ value /= 1024;
3010
+ unitIndex++;
3011
+ }
3012
+ return `${value.toFixed(1)} ${units[unitIndex]}`;
3013
+ }
3014
+ function getLeadingUploadedFileLines(text) {
3015
+ if (!text) return [];
3016
+ const trimmedStart = text.trimStart();
3017
+ if (trimmedStart.startsWith(">") || trimmedStart.startsWith("```")) {
3018
+ return [];
3019
+ }
3020
+ const lines = text.split("\n");
3021
+ const uploadedLines = [];
3022
+ for (const line of lines) {
3023
+ if (UPLOADED_FILE_LINE_REGEX.test(line)) {
3024
+ uploadedLines.push(line);
3025
+ continue;
3026
+ }
3027
+ if (uploadedLines.length > 0 && line.trim() === "") {
3028
+ uploadedLines.push(line);
3029
+ continue;
3030
+ }
3031
+ break;
3032
+ }
3033
+ const firstLine = lines[0] ?? "";
3034
+ if (!UPLOADED_FILE_LINE_REGEX.test(firstLine)) {
3035
+ return [];
3036
+ }
3037
+ return uploadedLines.filter((line) => UPLOADED_FILE_LINE_REGEX.test(line));
3038
+ }
3039
+ function parseUploadedFileReferences(text) {
3040
+ const refs = [];
3041
+ const seen = /* @__PURE__ */ new Set();
3042
+ for (const line of getLeadingUploadedFileLines(text)) {
3043
+ const match = line.match(UPLOADED_FILE_LINE_REGEX);
3044
+ const filePath = match?.[1];
3045
+ if (!filePath || seen.has(filePath)) continue;
3046
+ seen.add(filePath);
3047
+ refs.push({
3048
+ path: filePath,
3049
+ filename: filePath.split("/").pop() || filePath
3050
+ });
3051
+ }
3052
+ return refs;
3053
+ }
3054
+ function stripUploadedFileLines(text) {
3055
+ const lines = text.split("\n");
3056
+ if (!UPLOADED_FILE_LINE_REGEX.test(lines[0] ?? "")) {
3057
+ return text.trim();
3058
+ }
3059
+ let index = 0;
3060
+ while (index < lines.length && UPLOADED_FILE_LINE_REGEX.test(lines[index])) {
3061
+ index++;
3062
+ }
3063
+ while (index < lines.length && lines[index].trim() === "") {
3064
+ index++;
3065
+ }
3066
+ return lines.slice(index).join("\n").replace(/\n{3,}/g, "\n\n").trim();
3067
+ }
3001
3068
  function MessageItemComponent({
3002
3069
  message,
3003
3070
  toolResultsByCallId,
@@ -3018,6 +3085,47 @@ function MessageItemComponent({
3018
3085
  const images = imagesFromMessage(message);
3019
3086
  const isUser = role === "user";
3020
3087
  const timestamp = getMessageTimestamp(message);
3088
+ const navigate = useNavigate();
3089
+ const openInEditor = useFileExplorerState((state) => state.openInEditor);
3090
+ const uploadedFileRefs = useMemo(() => parseUploadedFileReferences(text), [text]);
3091
+ const [fileSizes, setFileSizes] = useState({});
3092
+ const displayText = useMemo(() => stripUploadedFileLines(text), [text]);
3093
+ useEffect(() => {
3094
+ let cancelled = false;
3095
+ async function loadSizes() {
3096
+ const nextSizes = {};
3097
+ await Promise.all(
3098
+ uploadedFileRefs.map(async (ref) => {
3099
+ try {
3100
+ const response = await fetch(`/api/files/info?path=${encodeURIComponent(ref.path)}`);
3101
+ if (!response.ok) {
3102
+ nextSizes[ref.path] = null;
3103
+ return;
3104
+ }
3105
+ const data = await response.json();
3106
+ nextSizes[ref.path] = typeof data.size === "number" ? data.size : null;
3107
+ } catch {
3108
+ nextSizes[ref.path] = null;
3109
+ }
3110
+ })
3111
+ );
3112
+ if (!cancelled) {
3113
+ setFileSizes(nextSizes);
3114
+ }
3115
+ }
3116
+ if (uploadedFileRefs.length > 0) {
3117
+ void loadSizes();
3118
+ } else {
3119
+ setFileSizes({});
3120
+ }
3121
+ return () => {
3122
+ cancelled = true;
3123
+ };
3124
+ }, [uploadedFileRefs]);
3125
+ const handleOpenFile = async (filePath) => {
3126
+ openInEditor(filePath);
3127
+ await navigate({ to: "/files" });
3128
+ };
3021
3129
  const toolCalls = role === "assistant" ? getToolCallsFromMessage(message) : [];
3022
3130
  const hasToolCalls = toolCalls.length > 0;
3023
3131
  const searchSources = isLastAssistant && !isStreaming && settings.showSearchSources && aggregatedSearchSources ? aggregatedSearchSources : [];
@@ -3054,6 +3162,24 @@ function MessageItemComponent({
3054
3162
  },
3055
3163
  idx
3056
3164
  )) }),
3165
+ uploadedFileRefs.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("mb-2 flex w-full flex-col gap-2", isUser ? "items-end" : "items-start"), children: uploadedFileRefs.map((fileRef) => /* @__PURE__ */ jsxs(
3166
+ "button",
3167
+ {
3168
+ type: "button",
3169
+ onClick: () => {
3170
+ void handleOpenFile(fileRef.path);
3171
+ },
3172
+ className: "flex max-w-full items-center gap-3 rounded-xl border border-primary-200 bg-primary-50 px-3 py-2 text-left hover:bg-primary-100",
3173
+ children: [
3174
+ /* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-primary-100", children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: File01Icon, size: 18, className: "text-primary-600" }) }),
3175
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
3176
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-primary-900", children: fileRef.filename }),
3177
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-primary-600", children: formatFileSize$1(fileSizes[fileRef.path] ?? null) })
3178
+ ] })
3179
+ ]
3180
+ },
3181
+ fileRef.path
3182
+ )) }),
3057
3183
  /* @__PURE__ */ jsx(Message, { className: cn("min-w-0 max-w-full", isUser ? "flex-row-reverse" : ""), children: /* @__PURE__ */ jsx(
3058
3184
  MessageContent,
3059
3185
  {
@@ -3063,7 +3189,7 @@ function MessageItemComponent({
3063
3189
  isUser ? "opencami-message-user bg-primary-100 px-4 py-[var(--opencami-user-bubble-py)] max-w-[85%]" : "opencami-message-assistant bg-transparent w-full",
3064
3190
  !isUser && isStreaming && "stream-fade-in"
3065
3191
  ),
3066
- children: text
3192
+ children: displayText
3067
3193
  }
3068
3194
  ) }),
3069
3195
  hasToolCalls && settings.showToolMessages && /* @__PURE__ */ jsx("div", { className: "mt-2 flex w-full min-w-0 max-w-[var(--opencami-chat-width)] flex-col gap-3 overflow-x-hidden", children: toolCalls.map((toolCall) => {
@@ -4690,7 +4816,30 @@ const MAX_IMAGE_DIMENSION = 1280;
4690
4816
  const IMAGE_QUALITY = 0.75;
4691
4817
  const TARGET_IMAGE_SIZE = 300 * 1024;
4692
4818
  const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
4693
- const ACCEPTED_EXTENSIONS = ".png,.jpg,.jpeg,.gif,.webp";
4819
+ const ACCEPTED_NON_IMAGE_EXTENSIONS = [
4820
+ "pdf",
4821
+ "txt",
4822
+ "md",
4823
+ "csv",
4824
+ "json",
4825
+ "xml",
4826
+ "yaml",
4827
+ "yml",
4828
+ "log",
4829
+ "py",
4830
+ "js",
4831
+ "ts",
4832
+ "html",
4833
+ "css"
4834
+ ];
4835
+ const ACCEPTED_EXTENSIONS = [
4836
+ ".png",
4837
+ ".jpg",
4838
+ ".jpeg",
4839
+ ".gif",
4840
+ ".webp",
4841
+ ...ACCEPTED_NON_IMAGE_EXTENSIONS.map((ext) => `.${ext}`)
4842
+ ].join(",");
4694
4843
  function isCanvasSupported() {
4695
4844
  if (typeof document === "undefined") return false;
4696
4845
  try {
@@ -4766,6 +4915,10 @@ async function compressImage(file) {
4766
4915
  function isAcceptedImage(file) {
4767
4916
  return ACCEPTED_IMAGE_TYPES.includes(file.type);
4768
4917
  }
4918
+ function isAcceptedNonImage(file) {
4919
+ const extension = file.name.includes(".") ? file.name.split(".").pop()?.toLowerCase() : "";
4920
+ return Boolean(extension && ACCEPTED_NON_IMAGE_EXTENSIONS.includes(extension));
4921
+ }
4769
4922
  async function createAttachmentFromFile(file) {
4770
4923
  const id = crypto.randomUUID();
4771
4924
  if (!isAcceptedImage(file)) {
@@ -4773,9 +4926,9 @@ async function createAttachmentFromFile(file) {
4773
4926
  id,
4774
4927
  file,
4775
4928
  preview: null,
4776
- type: "image",
4929
+ type: "file",
4777
4930
  base64: null,
4778
- error: "Unsupported file type. Please use PNG, JPG, GIF, or WebP images."
4931
+ error: "Unsupported image type. Please use PNG, JPG, GIF, or WebP images."
4779
4932
  };
4780
4933
  }
4781
4934
  if (file.size > MAX_FILE_SIZE) {
@@ -4810,7 +4963,7 @@ async function createAttachmentFromFile(file) {
4810
4963
  }
4811
4964
  }
4812
4965
  function AttachmentButton({
4813
- onFileSelect,
4966
+ onFilesSelect,
4814
4967
  disabled = false,
4815
4968
  className
4816
4969
  }) {
@@ -4820,13 +4973,12 @@ function AttachmentButton({
4820
4973
  }, []);
4821
4974
  const handleFileChange = useCallback(
4822
4975
  async (event) => {
4823
- const file = event.target.files?.[0];
4824
- if (!file) return;
4976
+ const files = Array.from(event.target.files ?? []);
4977
+ if (files.length === 0) return;
4825
4978
  event.target.value = "";
4826
- const attachment = await createAttachmentFromFile(file);
4827
- onFileSelect(attachment);
4979
+ onFilesSelect(files);
4828
4980
  },
4829
- [onFileSelect]
4981
+ [onFilesSelect]
4830
4982
  );
4831
4983
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4832
4984
  /* @__PURE__ */ jsx(
@@ -4835,6 +4987,7 @@ function AttachmentButton({
4835
4987
  ref: inputRef,
4836
4988
  type: "file",
4837
4989
  accept: ACCEPTED_EXTENSIONS,
4990
+ multiple: true,
4838
4991
  onChange: handleFileChange,
4839
4992
  className: "hidden",
4840
4993
  "aria-hidden": "true"
@@ -4848,7 +5001,7 @@ function AttachmentButton({
4848
5001
  onClick: handleClick,
4849
5002
  disabled,
4850
5003
  className,
4851
- "aria-label": "Attach image",
5004
+ "aria-label": "Attach file",
4852
5005
  type: "button",
4853
5006
  children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Attachment01Icon, size: 18, strokeWidth: 1.8 })
4854
5007
  }
@@ -4877,6 +5030,36 @@ function AttachmentPreview({
4877
5030
  };
4878
5031
  }, [attachment.preview]);
4879
5032
  const hasError = Boolean(attachment.error);
5033
+ if (attachment.type === "file" && !hasError) {
5034
+ return /* @__PURE__ */ jsxs(
5035
+ "div",
5036
+ {
5037
+ className: cn(
5038
+ "inline-flex max-w-full items-center gap-2 rounded-full border border-primary-200 bg-primary-50 px-3 py-1.5 text-xs text-primary-800",
5039
+ className
5040
+ ),
5041
+ children: [
5042
+ /* @__PURE__ */ jsxs("span", { className: "truncate", children: [
5043
+ "📄 ",
5044
+ attachment.file.name,
5045
+ " (",
5046
+ formatFileSize(attachment.file.size),
5047
+ ")"
5048
+ ] }),
5049
+ /* @__PURE__ */ jsx(
5050
+ "button",
5051
+ {
5052
+ onClick: () => onRemove(attachment.id),
5053
+ className: "shrink-0 text-primary-500 hover:text-primary-800",
5054
+ "aria-label": "Remove attachment",
5055
+ type: "button",
5056
+ children: "✕"
5057
+ }
5058
+ )
5059
+ ]
5060
+ }
5061
+ );
5062
+ }
4880
5063
  return /* @__PURE__ */ jsxs(
4881
5064
  "div",
4882
5065
  {
@@ -4932,7 +5115,7 @@ function AttachmentPreviewList({
4932
5115
  className
4933
5116
  }) {
4934
5117
  if (attachments.length === 0) return null;
4935
- return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-2 px-4", className), children: attachments.map((attachment) => /* @__PURE__ */ jsx(
5118
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap gap-2 px-4", className), children: attachments.map((attachment) => /* @__PURE__ */ jsx(
4936
5119
  AttachmentPreview,
4937
5120
  {
4938
5121
  attachment,
@@ -5162,9 +5345,82 @@ function ChatComposerComponent({
5162
5345
  setSlashMenuDismissed(false);
5163
5346
  focusPrompt();
5164
5347
  }, [focusPrompt]);
5165
- const handleFileSelect = useCallback((file) => {
5166
- setAttachments((prev) => [...prev, file]);
5348
+ const appendUploadedFilePrompt = useCallback((uploadedPath) => {
5349
+ setValue((prev) => {
5350
+ const template = `📎 Uploaded file: ${uploadedPath}
5351
+
5352
+ Please analyze this file.`;
5353
+ if (!prev.trim()) return template;
5354
+ return `${prev.trim()}
5355
+
5356
+ ${template}`;
5357
+ });
5358
+ }, []);
5359
+ const ensureUploadsDirectory = useCallback(async () => {
5360
+ await fetch("/api/files/mkdir", {
5361
+ method: "POST",
5362
+ headers: { "Content-Type": "application/json" },
5363
+ body: JSON.stringify({ path: "/uploads" })
5364
+ });
5167
5365
  }, []);
5366
+ const uploadAttachmentFile = useCallback(async (file) => {
5367
+ const id = crypto.randomUUID();
5368
+ if (!isAcceptedNonImage(file)) {
5369
+ return {
5370
+ id,
5371
+ file,
5372
+ preview: null,
5373
+ type: "file",
5374
+ base64: null,
5375
+ error: "Unsupported file type for upload."
5376
+ };
5377
+ }
5378
+ try {
5379
+ await ensureUploadsDirectory();
5380
+ const formData = new FormData();
5381
+ formData.append("path", "/uploads");
5382
+ formData.append("file", file);
5383
+ const response = await fetch("/api/files/upload", {
5384
+ method: "POST",
5385
+ body: formData
5386
+ });
5387
+ if (!response.ok) {
5388
+ const message = await response.text();
5389
+ throw new Error(message || "File upload failed");
5390
+ }
5391
+ const data = await response.json();
5392
+ const uploadedPath = data.files?.[0]?.path;
5393
+ if (!uploadedPath) {
5394
+ throw new Error("Upload succeeded but no path was returned");
5395
+ }
5396
+ appendUploadedFilePrompt(uploadedPath);
5397
+ return {
5398
+ id,
5399
+ file,
5400
+ preview: null,
5401
+ type: "file",
5402
+ base64: null,
5403
+ uploadedPath
5404
+ };
5405
+ } catch (err) {
5406
+ return {
5407
+ id,
5408
+ file,
5409
+ preview: null,
5410
+ type: "file",
5411
+ base64: null,
5412
+ error: err instanceof Error ? err.message : "File upload failed"
5413
+ };
5414
+ }
5415
+ }, [appendUploadedFilePrompt, ensureUploadsDirectory]);
5416
+ const handleFilesSelect = useCallback(async (files) => {
5417
+ if (files.length === 0) return;
5418
+ const nextAttachments = await Promise.all(
5419
+ files.map((file) => isAcceptedImage(file) ? createAttachmentFromFile(file) : uploadAttachmentFile(file))
5420
+ );
5421
+ setAttachments((prev) => [...prev, ...nextAttachments]);
5422
+ focusPrompt();
5423
+ }, [focusPrompt, uploadAttachmentFile]);
5168
5424
  const handleRemoveAttachment = useCallback((id) => {
5169
5425
  setAttachments((prev) => prev.filter((a) => a.id !== id));
5170
5426
  }, []);
@@ -5186,12 +5442,8 @@ function ChatComposerComponent({
5186
5442
  setIsDragActive(false);
5187
5443
  const files = Array.from(event.dataTransfer.files ?? []);
5188
5444
  if (files.length === 0) return;
5189
- const imageFiles = files.filter((file) => isAcceptedImage(file));
5190
- if (imageFiles.length === 0) return;
5191
- const newAttachments = await Promise.all(imageFiles.map((file) => createAttachmentFromFile(file)));
5192
- setAttachments((prev) => [...prev, ...newAttachments]);
5193
- focusPrompt();
5194
- }, [focusPrompt]);
5445
+ await handleFilesSelect(files);
5446
+ }, [handleFilesSelect]);
5195
5447
  const setComposerValue = useCallback(
5196
5448
  (nextValue) => {
5197
5449
  setValue(nextValue);
@@ -5443,7 +5695,7 @@ function ChatComposerComponent({
5443
5695
  onDragLeave: handleDragLeave,
5444
5696
  onDrop: handleDrop,
5445
5697
  children: [
5446
- isDragActive && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-2 z-20 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary-400 bg-primary-50/90 text-sm font-medium text-primary-700", children: "Drop image here" }),
5698
+ isDragActive && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-2 z-20 flex items-center justify-center rounded-2xl border-2 border-dashed border-primary-400 bg-primary-50/90 text-sm font-medium text-primary-700", children: "Drop files here" }),
5447
5699
  /* @__PURE__ */ jsxs(
5448
5700
  PromptInput,
5449
5701
  {
@@ -5487,7 +5739,7 @@ function ChatComposerComponent({
5487
5739
  /* @__PURE__ */ jsx(
5488
5740
  AttachmentButton,
5489
5741
  {
5490
- onFileSelect: handleFileSelect,
5742
+ onFilesSelect: handleFilesSelect,
5491
5743
  disabled
5492
5744
  }
5493
5745
  ),
@@ -6223,7 +6475,7 @@ const KeyboardShortcutsDialog = lazy(
6223
6475
  }))
6224
6476
  );
6225
6477
  const SearchDialog = lazy(
6226
- () => import("./search-dialog-DZTS5SEi.js").then((m) => ({
6478
+ () => import("./search-dialog-BtSQW9SR.js").then((m) => ({
6227
6479
  default: m.SearchDialog
6228
6480
  }))
6229
6481
  );
@@ -1,4 +1,4 @@
1
- const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "/home/runner/work/opencami/opencami/src/routes/__root.tsx", "children": ["/", "/agents", "/bots", "/connect", "/files", "/memory", "/new", "/skills", "/api/agents", "/api/cron", "/api/follow-ups", "/api/history", "/api/llm-features", "/api/models", "/api/paths", "/api/personas", "/api/ping", "/api/send", "/api/sessions", "/api/skills", "/api/stream", "/api/stt", "/api/tts", "/chat/$sessionKey", "/api/files/delete", "/api/files/download", "/api/files/info", "/api/files/list", "/api/files/mkdir", "/api/files/read", "/api/files/rename", "/api/files/save", "/api/files/upload"], "preloads": ["/assets/main-B_dlfHME.js"], "assets": [] }, "/": { "filePath": "/home/runner/work/opencami/opencami/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-rOIRO-8E.js"] }, "/agents": { "filePath": "/home/runner/work/opencami/opencami/src/routes/agents.tsx", "assets": [], "preloads": ["/assets/agents-DwxKcpP6.js"] }, "/bots": { "filePath": "/home/runner/work/opencami/opencami/src/routes/bots.tsx", "assets": [], "preloads": ["/assets/bots-CRlm-3-d.js"] }, "/connect": { "filePath": "/home/runner/work/opencami/opencami/src/routes/connect.tsx", "assets": [], "preloads": ["/assets/connect-NYvOqiBJ.js", "/assets/index-CMATW8VA.js", "/assets/button-Dg7VFQQn.js", "/assets/react-irH8OzhB.js"] }, "/files": { "filePath": "/home/runner/work/opencami/opencami/src/routes/files.tsx", "assets": [], "preloads": ["/assets/files-BJbMx0_w.js"] }, "/memory": { "filePath": "/home/runner/work/opencami/opencami/src/routes/memory.tsx", "assets": [], "preloads": ["/assets/memory-S3Yws6a5.js"] }, "/new": { "filePath": "/home/runner/work/opencami/opencami/src/routes/new.tsx", "assets": [], "preloads": ["/assets/new-DLlBm66g.js"] }, "/skills": { "filePath": "/home/runner/work/opencami/opencami/src/routes/skills.tsx", "assets": [], "preloads": ["/assets/skills-YZe3I63y.js"] }, "/api/agents": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/agents.ts" }, "/api/cron": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/cron.ts" }, "/api/follow-ups": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/follow-ups.ts" }, "/api/history": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/history.ts" }, "/api/llm-features": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/llm-features.ts" }, "/api/models": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/models.ts" }, "/api/paths": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/paths.ts" }, "/api/personas": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/personas.ts" }, "/api/ping": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/ping.ts" }, "/api/send": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/send.ts" }, "/api/sessions": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/sessions.ts" }, "/api/skills": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/skills.ts" }, "/api/stream": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/stream.ts" }, "/api/stt": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/stt.ts" }, "/api/tts": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/tts.ts" }, "/chat/$sessionKey": { "filePath": "/home/runner/work/opencami/opencami/src/routes/chat/$sessionKey.tsx", "assets": [], "preloads": ["/assets/_sessionKey-DsjnpErt.js", "/assets/visuallyHidden-ONmQ-0U2.js", "/assets/tooltip-Dg9fy-vT.js", "/assets/button-Dg7VFQQn.js", "/assets/use-file-explorer-state-DzT0bksg.js", "/assets/useButton-Cbl_9oFG.js", "/assets/useControlled-Dscz_s4f.js", "/assets/popupStateMapping-BRPDXnjv.js", "/assets/owner-CpRnf1fI.js", "/assets/CSPContext-Bq8j4nl9.js", "/assets/DirectionContext-BdX86BHP.js", "/assets/menu-DHNgWk_8.js", "/assets/useMutation-B1FlDsNN.js", "/assets/opencami-logo-BQQETnJG.js", "/assets/proxy-BcUh9kMA.js", "/assets/markdown-BVzT7z4x.js", "/assets/index-CMATW8VA.js", "/assets/react-irH8OzhB.js"] }, "/api/files/delete": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/delete.ts" }, "/api/files/download": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/download.ts" }, "/api/files/info": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/info.ts" }, "/api/files/list": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/list.ts" }, "/api/files/mkdir": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/mkdir.ts" }, "/api/files/read": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/read.ts" }, "/api/files/rename": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/rename.ts" }, "/api/files/save": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/save.ts" }, "/api/files/upload": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/upload.ts" } }, "clientEntry": "/assets/main-B_dlfHME.js" });
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "/home/runner/work/opencami/opencami/src/routes/__root.tsx", "children": ["/", "/agents", "/bots", "/connect", "/files", "/memory", "/new", "/skills", "/api/agents", "/api/cron", "/api/follow-ups", "/api/history", "/api/llm-features", "/api/models", "/api/paths", "/api/personas", "/api/ping", "/api/send", "/api/sessions", "/api/skills", "/api/stream", "/api/stt", "/api/tts", "/chat/$sessionKey", "/api/files/delete", "/api/files/download", "/api/files/info", "/api/files/list", "/api/files/mkdir", "/api/files/read", "/api/files/rename", "/api/files/save", "/api/files/upload"], "preloads": ["/assets/main-CEuT8-Qi.js"], "assets": [] }, "/": { "filePath": "/home/runner/work/opencami/opencami/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-Dg0mbvtv.js"] }, "/agents": { "filePath": "/home/runner/work/opencami/opencami/src/routes/agents.tsx", "assets": [], "preloads": ["/assets/agents-BkeWu_3a.js"] }, "/bots": { "filePath": "/home/runner/work/opencami/opencami/src/routes/bots.tsx", "assets": [], "preloads": ["/assets/bots-CyJwr-JU.js"] }, "/connect": { "filePath": "/home/runner/work/opencami/opencami/src/routes/connect.tsx", "assets": [], "preloads": ["/assets/connect-BX1MWO82.js", "/assets/index-BlC2sH55.js", "/assets/button-DNC5N25i.js", "/assets/react-K9goXsVv.js"] }, "/files": { "filePath": "/home/runner/work/opencami/opencami/src/routes/files.tsx", "assets": [], "preloads": ["/assets/files-HiN5rXWq.js"] }, "/memory": { "filePath": "/home/runner/work/opencami/opencami/src/routes/memory.tsx", "assets": [], "preloads": ["/assets/memory-lhzf-8Q4.js"] }, "/new": { "filePath": "/home/runner/work/opencami/opencami/src/routes/new.tsx", "assets": [], "preloads": ["/assets/new-DLlBm66g.js"] }, "/skills": { "filePath": "/home/runner/work/opencami/opencami/src/routes/skills.tsx", "assets": [], "preloads": ["/assets/skills-BEkw619A.js"] }, "/api/agents": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/agents.ts" }, "/api/cron": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/cron.ts" }, "/api/follow-ups": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/follow-ups.ts" }, "/api/history": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/history.ts" }, "/api/llm-features": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/llm-features.ts" }, "/api/models": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/models.ts" }, "/api/paths": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/paths.ts" }, "/api/personas": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/personas.ts" }, "/api/ping": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/ping.ts" }, "/api/send": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/send.ts" }, "/api/sessions": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/sessions.ts" }, "/api/skills": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/skills.ts" }, "/api/stream": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/stream.ts" }, "/api/stt": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/stt.ts" }, "/api/tts": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/tts.ts" }, "/chat/$sessionKey": { "filePath": "/home/runner/work/opencami/opencami/src/routes/chat/$sessionKey.tsx", "assets": [], "preloads": ["/assets/_sessionKey-BAmpzUOP.js", "/assets/visuallyHidden-CO3ZD5AQ.js", "/assets/tooltip-D57Pal0B.js", "/assets/button-DNC5N25i.js", "/assets/use-file-explorer-state-DppKEjcl.js", "/assets/useButton-DVAfkehQ.js", "/assets/useControlled-Cl9XA2_f.js", "/assets/popupStateMapping-D5k-jOeY.js", "/assets/owner-CFRNz_Tp.js", "/assets/CSPContext-a-MQmQQt.js", "/assets/DirectionContext-DRcND-Cm.js", "/assets/menu-47ooFeSm.js", "/assets/useMutation-C5bTdeC1.js", "/assets/opencami-logo-DA69yVKc.js", "/assets/proxy-Cawf6X0W.js", "/assets/markdown-DKD6ZLRJ.js", "/assets/index-BlC2sH55.js", "/assets/react-K9goXsVv.js"] }, "/api/files/delete": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/delete.ts" }, "/api/files/download": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/download.ts" }, "/api/files/info": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/info.ts" }, "/api/files/list": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/list.ts" }, "/api/files/mkdir": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/mkdir.ts" }, "/api/files/read": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/read.ts" }, "/api/files/rename": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/rename.ts" }, "/api/files/save": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/save.ts" }, "/api/files/upload": { "filePath": "/home/runner/work/opencami/opencami/src/routes/api/files/upload.ts" } }, "clientEntry": "/assets/main-CEuT8-Qi.js" });
2
2
  export {
3
3
  tsrStartManifest
4
4
  };
@@ -1,6 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useEffect } from "react";
3
- import { R as Route } from "./router-Uuagl6O7.js";
3
+ import { R as Route } from "./router-Bl2uabfY.js";
4
4
  import "@tanstack/react-router";
5
5
  import "@tanstack/react-query";
6
6
  import "node:crypto";
@@ -10,7 +10,7 @@ import { json } from "@tanstack/router-core/ssr/client";
10
10
  import { execSync } from "node:child_process";
11
11
  import { readFile, mkdir, writeFile, rename, stat, readdir, rm, realpath, lstat } from "node:fs/promises";
12
12
  import { posix } from "path";
13
- const appCss = "/assets/styles-Bwo-K6Y4.css";
13
+ const appCss = "/assets/styles-D4EBtWYc.css";
14
14
  const swRegisterScript = `
15
15
  (() => {
16
16
  // Skip PWA service worker inside Capacitor native shell — they conflict
@@ -55,16 +55,25 @@ const themeScript = `
55
55
  const media = window.matchMedia('(prefers-color-scheme: dark)')
56
56
  const apply = () => {
57
57
  root.classList.remove('light', 'dark', 'system', 'chameleon', 'frost', 'frost-light', 'frost-dark')
58
+ root.style.colorScheme = ''
58
59
  root.classList.add(theme)
59
- if (theme === 'frost-light' || theme === 'frost-dark') {
60
+
61
+ const isFrostLight = theme === 'frost-light'
62
+ const isFrostDark = theme === 'frost-dark'
63
+
64
+ if (isFrostLight || isFrostDark) {
60
65
  root.classList.add('frost')
61
66
  }
62
- if (theme === 'frost-dark') {
67
+ if (isFrostDark) {
63
68
  root.classList.add('dark')
64
69
  }
65
70
  if (theme === 'system' && media.matches) {
66
71
  root.classList.add('dark')
67
72
  }
73
+ if (isFrostLight) {
74
+ root.classList.remove('dark')
75
+ root.style.colorScheme = 'light'
76
+ }
68
77
  }
69
78
  apply()
70
79
  media.addEventListener('change', () => {
@@ -347,11 +356,11 @@ const $$splitComponentImporter$2 = () => import("./agents-CmQ4vvXm.js");
347
356
  const Route$q = createFileRoute("/agents")({
348
357
  component: lazyRouteComponent($$splitComponentImporter$2, "component")
349
358
  });
350
- const $$splitComponentImporter$1 = () => import("./index-CiUjUD0t.js");
359
+ const $$splitComponentImporter$1 = () => import("./index-BgMPaOsU.js");
351
360
  const Route$p = createFileRoute("/")({
352
361
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
353
362
  });
354
- const $$splitComponentImporter = () => import("./_sessionKey-B0ZlLAjH.js").then((n) => n.$);
363
+ const $$splitComponentImporter = () => import("./_sessionKey-BBG3ZUlo.js").then((n) => n.$);
355
364
  const Route$o = createFileRoute("/chat/$sessionKey")({
356
365
  component: lazyRouteComponent($$splitComponentImporter, "component")
357
366
  });
@@ -372,7 +381,7 @@ function buildConnectParams(token, password) {
372
381
  maxProtocol: 3,
373
382
  client: {
374
383
  id: "gateway-client",
375
- displayName: "webclaw",
384
+ displayName: "OpenCami",
376
385
  version: "dev",
377
386
  platform: process.platform,
378
387
  mode: "ui",
@@ -383,7 +392,7 @@ function buildConnectParams(token, password) {
383
392
  password: password || void 0
384
393
  },
385
394
  role: "operator",
386
- scopes: ["operator.admin"]
395
+ scopes: ["operator.read", "operator.write", "operator.admin"]
387
396
  };
388
397
  }
389
398
  class PersistentGatewayConnection {
@@ -3069,8 +3078,10 @@ const Route$2 = createFileRoute("/api/files/info")({
3069
3078
  );
3070
3079
  }
3071
3080
  const path2 = validatePath(rawPath, "Path parameter");
3072
- const fileInfo = await getFileInfo(path2);
3073
- return json(fileInfo);
3081
+ const root = process.env.FILES_ROOT?.trim() ? resolve(process.env.FILES_ROOT) : process.env.HOME || "/home";
3082
+ const absolutePath = resolve(root, path2.replace(/^\/+/, ""));
3083
+ const stats = await stat(absolutePath);
3084
+ return json({ size: stats.size });
3074
3085
  } catch (err) {
3075
3086
  const error = err;
3076
3087
  if (error.message.includes("invalid characters") || error.message.includes("traversal attempts")) {
@@ -3082,6 +3093,24 @@ const Route$2 = createFileRoute("/api/files/info")({
3082
3093
  { status: 400 }
3083
3094
  );
3084
3095
  }
3096
+ if (error.code === "ENOENT") {
3097
+ return json(
3098
+ {
3099
+ error: "File not found",
3100
+ code: "NOT_FOUND"
3101
+ },
3102
+ { status: 404 }
3103
+ );
3104
+ }
3105
+ if (error.code === "EACCES") {
3106
+ return json(
3107
+ {
3108
+ error: "Permission denied",
3109
+ code: "PERMISSION_DENIED"
3110
+ },
3111
+ { status: 403 }
3112
+ );
3113
+ }
3085
3114
  const status = error.status || 500;
3086
3115
  const code = error.code || "INTERNAL_ERROR";
3087
3116
  return json(
@@ -5,7 +5,7 @@ import { HugeiconsIcon } from "@hugeicons/react";
5
5
  import { Search01Icon, Cancel01Icon, Loading03Icon } from "@hugeicons/core-free-icons";
6
6
  import { D as DialogRoot, a as DialogContent } from "./use-file-explorer-state-s7CS50ho.js";
7
7
  import { useQueryClient } from "@tanstack/react-query";
8
- import { c as chatQueryKeys } from "./_sessionKey-B0ZlLAjH.js";
8
+ import { c as chatQueryKeys } from "./_sessionKey-BBG3ZUlo.js";
9
9
  import { c as cn } from "./button-CwY2OHFj.js";
10
10
  import "@base-ui/react/dialog";
11
11
  import "zustand";
@@ -26,7 +26,7 @@ import "remark-gfm";
26
26
  import "./index-Dl2BOKP7.js";
27
27
  import "zustand/middleware";
28
28
  import "react-dom";
29
- import "./router-Uuagl6O7.js";
29
+ import "./router-Bl2uabfY.js";
30
30
  import "node:crypto";
31
31
  import "ws";
32
32
  import "node:fs";