opencami 1.8.8 → 1.8.9

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 (61) hide show
  1. package/dist/client/assets/{CSPContext-nHSyQniZ.js → CSPContext-DI-5GAnQ.js} +1 -1
  2. package/dist/client/assets/{DirectionContext-B1cuzwIr.js → DirectionContext-CrIsc5n9.js} +1 -1
  3. package/dist/client/assets/_sessionKey-B4NZmxf3.js +21 -0
  4. package/dist/client/assets/agents-bptidK8z.js +2 -0
  5. package/dist/client/assets/{agents-screen-DpNSh5Ok.js → agents-screen-6qdnPmx2.js} +1 -1
  6. package/dist/client/assets/bots-BWpbaQ-E.js +2 -0
  7. package/dist/client/assets/{bots-screen-Qh_IK9hC.js → bots-screen-BTKCOohV.js} +1 -1
  8. package/dist/client/assets/{button-DdG8c-XQ.js → button-8ab4wOwy.js} +1 -1
  9. package/dist/client/assets/{composite-Cx-QHT9o.js → composite-B2qsrzf3.js} +1 -1
  10. package/dist/client/assets/{connect-CSbeSBTn.js → connect-B3_p7C4I.js} +1 -1
  11. package/dist/client/assets/dashboard-BtClHYpn.js +1 -0
  12. package/dist/client/assets/{event-Dwf9IDxK.js → event-DG3RKJz8.js} +1 -1
  13. package/dist/client/assets/{file-explorer-screen-DzDX4HcB.js → file-explorer-screen-Djl8x-8P.js} +1 -1
  14. package/dist/client/assets/files-CjbCJDgC.js +2 -0
  15. package/dist/client/assets/{follow-up-suggestions-BYWq-d8P.js → follow-up-suggestions-BSCMXRXh.js} +1 -1
  16. package/dist/client/assets/{index-BxsgifDH.js → index-BXiha-Vz.js} +1 -1
  17. package/dist/client/assets/{index-B551ln24.js → index-CtlYu8Ug.js} +1 -1
  18. package/dist/client/assets/{keyboard-shortcuts-dialog-BX-hH4Wf.js → keyboard-shortcuts-dialog-HAufCn9C.js} +1 -1
  19. package/dist/client/assets/{main-DBmooBKx.js → main-CQKtcNr3.js} +2 -2
  20. package/dist/client/assets/{markdown-Bpat4kTr.js → markdown-DFJF-FsV.js} +1 -1
  21. package/dist/client/assets/memory-DnJOmcwU.js +2 -0
  22. package/dist/client/assets/{memory-screen-1DgDLyZf.js → memory-screen-Bm4NMAnU.js} +1 -1
  23. package/dist/client/assets/{menu-ByR1BVmq.js → menu-D26Vmgxl.js} +1 -1
  24. package/dist/client/assets/{opencami-logo-BV1uPYe6.js → opencami-logo-BSed2Wez.js} +1 -1
  25. package/dist/client/assets/{popupStateMapping-CGDLUl5Y.js → popupStateMapping-DkI2OCkW.js} +1 -1
  26. package/dist/client/assets/{proxy-DYrSkM35.js → proxy-CHQ-VCN1.js} +1 -1
  27. package/dist/client/assets/{react-dSDkXQu6.js → react-WkSlhZJd.js} +1 -1
  28. package/dist/client/assets/{search-dialog-B3XJLq-O.js → search-dialog-CCl4d0Pi.js} +1 -1
  29. package/dist/client/assets/{search-sources-badge-CTd0gLuz.js → search-sources-badge-Du8KpUEb.js} +1 -1
  30. package/dist/client/assets/{session-export-dialog-Cib97JLm.js → session-export-dialog-io9FvLKq.js} +1 -1
  31. package/dist/client/assets/{settings-dialog-B4NLq1ZS.js → settings-dialog-B93qswor.js} +1 -1
  32. package/dist/client/assets/skills-BNDGnHwM.js +2 -0
  33. package/dist/client/assets/{skills-panel-Cj7yHGbX.js → skills-panel-CVh1I-7D.js} +1 -1
  34. package/dist/client/assets/{switch-kXs1I0oW.js → switch-CSnzINDW.js} +1 -1
  35. package/dist/client/assets/{tabs-B6GW7TBf.js → tabs-CWfn44FL.js} +1 -1
  36. package/dist/client/assets/{thinking-DTP9JDQl.js → thinking-BmoLlbFC.js} +1 -1
  37. package/dist/client/assets/{tooltip-CtHpm-sQ.js → tooltip-CSGMH2t4.js} +1 -1
  38. package/dist/client/assets/{use-file-explorer-state-C-D2CShe.js → use-file-explorer-state-BYVzjwPA.js} +1 -1
  39. package/dist/client/assets/{useBaseUiId-Ckx_aJky.js → useBaseUiId-DiwX_3so.js} +1 -1
  40. package/dist/client/assets/{useCompositeItem-CkvfeGmG.js → useCompositeItem-UPIPwR9H.js} +1 -1
  41. package/dist/client/assets/{useControlled-8D4PSDAL.js → useControlled-CT2hRlcU.js} +1 -1
  42. package/dist/client/assets/{useMutation-DckvFKPC.js → useMutation-rx8UH99I.js} +1 -1
  43. package/dist/client/assets/{useQuery-CtUiG53w.js → useQuery-Boaa6oF3.js} +1 -1
  44. package/dist/server/assets/{_sessionKey-LV6xK9IM.js → _sessionKey-B6iYeyCS.js} +342 -257
  45. package/dist/server/assets/{_tanstack-start-manifest_v-qVhiIEVc.js → _tanstack-start-manifest_v-C9chPgNH.js} +1 -1
  46. package/dist/server/assets/{dashboard-GCKodTiJ.js → dashboard-UYRCu_mQ.js} +19 -3
  47. package/dist/server/assets/{follow-up-suggestions-CSSc4PDe.js → follow-up-suggestions-mzRQIB0k.js} +2 -2
  48. package/dist/server/assets/{index-DMKS4aeI.js → index-COElhwGA.js} +1 -1
  49. package/dist/server/assets/{router-Cr2xCvGA.js → router-BqLGFd4L.js} +3 -3
  50. package/dist/server/assets/{search-dialog-DR6zBnui.js → search-dialog-CmI7naPN.js} +2 -2
  51. package/dist/server/assets/{settings-dialog-DEMlCMCP.js → settings-dialog-BZ67gr9N.js} +2 -2
  52. package/dist/server/assets/{thinking-BpAc3itF.js → thinking-CA8PSwKJ.js} +2 -2
  53. package/dist/server/server.js +2 -2
  54. package/package.json +2 -2
  55. package/dist/client/assets/_sessionKey-CNw4O2rY.js +0 -19
  56. package/dist/client/assets/agents-C6Ev94B1.js +0 -2
  57. package/dist/client/assets/bots-B2_u-OmP.js +0 -2
  58. package/dist/client/assets/dashboard-DA1fTRcH.js +0 -1
  59. package/dist/client/assets/files-PsZnGOUx.js +0 -2
  60. package/dist/client/assets/memory-Bs8hOoEv.js +0 -2
  61. package/dist/client/assets/skills-pueagQNc.js +0 -2
@@ -19,7 +19,7 @@ import { u as useChatSettings$1 } from "./index-BEWnDAH6.js";
19
19
  import { createPortal } from "react-dom";
20
20
  import { create } from "zustand";
21
21
  import { persist } from "zustand/middleware";
22
- import { a as Route } from "./router-Cr2xCvGA.js";
22
+ import { a as Route } from "./router-BqLGFd4L.js";
23
23
  function deriveFriendlyIdFromKey(key) {
24
24
  if (!key) return "main";
25
25
  const trimmed = key.trim();
@@ -1681,7 +1681,7 @@ function areSidebarSessionsEqual(prev, next) {
1681
1681
  return true;
1682
1682
  }
1683
1683
  const SettingsDialog = lazy(
1684
- () => import("./settings-dialog-DEMlCMCP.js").then((m) => ({ default: m.SettingsDialog }))
1684
+ () => import("./settings-dialog-BZ67gr9N.js").then((m) => ({ default: m.SettingsDialog }))
1685
1685
  );
1686
1686
  const SessionExportDialog = lazy(
1687
1687
  () => import("./session-export-dialog-C53RRAah.js").then((m) => ({
@@ -2828,7 +2828,7 @@ function Tool({ toolPart, defaultOpen = false }) {
2828
2828
  ] }) });
2829
2829
  }
2830
2830
  const Thinking = lazy(
2831
- () => import("./thinking-BpAc3itF.js").then((m) => ({
2831
+ () => import("./thinking-CA8PSwKJ.js").then((m) => ({
2832
2832
  default: m.Thinking
2833
2833
  }))
2834
2834
  );
@@ -3469,7 +3469,7 @@ function TypingIndicator({ className }) {
3469
3469
  ] });
3470
3470
  }
3471
3471
  const FollowUpSuggestions = lazy(
3472
- () => import("./follow-up-suggestions-CSSc4PDe.js").then((m) => ({
3472
+ () => import("./follow-up-suggestions-mzRQIB0k.js").then((m) => ({
3473
3473
  default: m.FollowUpSuggestions
3474
3474
  }))
3475
3475
  );
@@ -3783,6 +3783,120 @@ const MemoizedChatMessageList = memo(
3783
3783
  ChatMessageListComponent,
3784
3784
  areChatMessageListEqual
3785
3785
  );
3786
+ const useThinkingLevelStore = create()(
3787
+ persist(
3788
+ (set) => ({
3789
+ level: "low",
3790
+ setLevel: (level) => set({ level })
3791
+ }),
3792
+ { name: "thinking-level" }
3793
+ )
3794
+ );
3795
+ function useThinkingLevel() {
3796
+ const level = useThinkingLevelStore((state) => state.level);
3797
+ const setLevel = useThinkingLevelStore((state) => state.setLevel);
3798
+ return { level, setLevel };
3799
+ }
3800
+ const THINKING_OPTIONS = [
3801
+ { value: "off", label: "Off", description: "No reasoning", shortLabel: "Off" },
3802
+ { value: "low", label: "Low", description: "Think", shortLabel: "Low" },
3803
+ { value: "medium", label: "Medium", description: "Think harder", shortLabel: "Medium" },
3804
+ { value: "high", label: "High", description: "Ultrathink", shortLabel: "High" }
3805
+ ];
3806
+ function ThinkingLevelSelector({ className }) {
3807
+ const { level, setLevel } = useThinkingLevel();
3808
+ const current = THINKING_OPTIONS.find((option) => option.value === level) ?? THINKING_OPTIONS[1];
3809
+ return /* @__PURE__ */ jsxs(MenuRoot, { children: [
3810
+ /* @__PURE__ */ jsxs(
3811
+ MenuTrigger,
3812
+ {
3813
+ className: cn(
3814
+ "inline-flex h-7 items-center gap-2 rounded-md px-2 text-xs font-[450] text-primary-600 hover:text-primary-900 hover:bg-primary-100",
3815
+ className
3816
+ ),
3817
+ children: [
3818
+ /* @__PURE__ */ jsx(
3819
+ HugeiconsIcon,
3820
+ {
3821
+ icon: AiBrain01Icon,
3822
+ size: 20,
3823
+ strokeWidth: 1.5,
3824
+ className: cn(level === "off" ? "text-primary-400" : "text-primary-700")
3825
+ }
3826
+ ),
3827
+ /* @__PURE__ */ jsx("span", { className: "text-pretty", children: current.shortLabel })
3828
+ ]
3829
+ }
3830
+ ),
3831
+ /* @__PURE__ */ jsx(MenuContent, { side: "top", align: "start", className: "min-w-[190px]", children: THINKING_OPTIONS.map((option) => /* @__PURE__ */ jsxs(
3832
+ MenuItem,
3833
+ {
3834
+ onClick: () => setLevel(option.value),
3835
+ className: "justify-between",
3836
+ children: [
3837
+ /* @__PURE__ */ jsxs("span", { className: "flex flex-col text-pretty", children: [
3838
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-primary-900", children: option.label }),
3839
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-primary-500", children: option.description })
3840
+ ] }),
3841
+ level === option.value && /* @__PURE__ */ jsx(
3842
+ HugeiconsIcon,
3843
+ {
3844
+ icon: Tick02Icon,
3845
+ size: 20,
3846
+ strokeWidth: 1.5,
3847
+ className: "text-primary-600"
3848
+ }
3849
+ )
3850
+ ]
3851
+ },
3852
+ option.value
3853
+ )) })
3854
+ ] });
3855
+ }
3856
+ function SlashCommandMenuComponent({
3857
+ commands,
3858
+ selectedIndex,
3859
+ onSelect,
3860
+ className
3861
+ }) {
3862
+ if (commands.length === 0) return null;
3863
+ return /* @__PURE__ */ jsx(
3864
+ "div",
3865
+ {
3866
+ className: cn(
3867
+ "absolute left-2 right-2 md:left-5 md:right-5 bottom-full mb-2 z-20 rounded-xl border border-white/10 bg-neutral-900/95 shadow-2xl backdrop-blur-sm overflow-hidden",
3868
+ className
3869
+ ),
3870
+ role: "listbox",
3871
+ "aria-label": "Slash commands",
3872
+ children: /* @__PURE__ */ jsx("div", { className: "max-h-56 overflow-y-auto p-1", children: commands.map((item, index) => {
3873
+ const active = index === selectedIndex;
3874
+ return /* @__PURE__ */ jsx(
3875
+ "button",
3876
+ {
3877
+ type: "button",
3878
+ role: "option",
3879
+ "aria-selected": active,
3880
+ onMouseDown: (event) => {
3881
+ event.preventDefault();
3882
+ onSelect(item);
3883
+ },
3884
+ className: cn(
3885
+ "w-full rounded-md px-3 py-2 text-left transition-colors",
3886
+ active ? "bg-neutral-800" : "hover:bg-neutral-800/80"
3887
+ ),
3888
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
3889
+ /* @__PURE__ */ jsx("code", { className: "min-w-[90px] font-mono text-xs text-primary-300", children: item.command }),
3890
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-neutral-300", children: item.description })
3891
+ ] })
3892
+ },
3893
+ item.command
3894
+ );
3895
+ }) })
3896
+ }
3897
+ );
3898
+ }
3899
+ const SlashCommandMenu = memo(SlashCommandMenuComponent);
3786
3900
  const PromptInputContext = createContext({
3787
3901
  isLoading: false,
3788
3902
  value: "",
@@ -4540,120 +4654,6 @@ function AttachmentPreviewList({
4540
4654
  attachment.id
4541
4655
  )) });
4542
4656
  }
4543
- function SlashCommandMenuComponent({
4544
- commands,
4545
- selectedIndex,
4546
- onSelect,
4547
- className
4548
- }) {
4549
- if (commands.length === 0) return null;
4550
- return /* @__PURE__ */ jsx(
4551
- "div",
4552
- {
4553
- className: cn(
4554
- "absolute left-2 right-2 md:left-5 md:right-5 bottom-full mb-2 z-20 rounded-xl border border-white/10 bg-neutral-900/95 shadow-2xl backdrop-blur-sm overflow-hidden",
4555
- className
4556
- ),
4557
- role: "listbox",
4558
- "aria-label": "Slash commands",
4559
- children: /* @__PURE__ */ jsx("div", { className: "max-h-56 overflow-y-auto p-1", children: commands.map((item, index) => {
4560
- const active = index === selectedIndex;
4561
- return /* @__PURE__ */ jsx(
4562
- "button",
4563
- {
4564
- type: "button",
4565
- role: "option",
4566
- "aria-selected": active,
4567
- onMouseDown: (event) => {
4568
- event.preventDefault();
4569
- onSelect(item);
4570
- },
4571
- className: cn(
4572
- "w-full rounded-md px-3 py-2 text-left transition-colors",
4573
- active ? "bg-neutral-800" : "hover:bg-neutral-800/80"
4574
- ),
4575
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
4576
- /* @__PURE__ */ jsx("code", { className: "min-w-[90px] font-mono text-xs text-primary-300", children: item.command }),
4577
- /* @__PURE__ */ jsx("span", { className: "text-xs text-neutral-300", children: item.description })
4578
- ] })
4579
- },
4580
- item.command
4581
- );
4582
- }) })
4583
- }
4584
- );
4585
- }
4586
- const SlashCommandMenu = memo(SlashCommandMenuComponent);
4587
- const useThinkingLevelStore = create()(
4588
- persist(
4589
- (set) => ({
4590
- level: "low",
4591
- setLevel: (level) => set({ level })
4592
- }),
4593
- { name: "thinking-level" }
4594
- )
4595
- );
4596
- function useThinkingLevel() {
4597
- const level = useThinkingLevelStore((state) => state.level);
4598
- const setLevel = useThinkingLevelStore((state) => state.setLevel);
4599
- return { level, setLevel };
4600
- }
4601
- const THINKING_OPTIONS = [
4602
- { value: "off", label: "Off", description: "No reasoning", shortLabel: "Off" },
4603
- { value: "low", label: "Low", description: "Think", shortLabel: "Low" },
4604
- { value: "medium", label: "Medium", description: "Think harder", shortLabel: "Medium" },
4605
- { value: "high", label: "High", description: "Ultrathink", shortLabel: "High" }
4606
- ];
4607
- function ThinkingLevelSelector({ className }) {
4608
- const { level, setLevel } = useThinkingLevel();
4609
- const current = THINKING_OPTIONS.find((option) => option.value === level) ?? THINKING_OPTIONS[1];
4610
- return /* @__PURE__ */ jsxs(MenuRoot, { children: [
4611
- /* @__PURE__ */ jsxs(
4612
- MenuTrigger,
4613
- {
4614
- className: cn(
4615
- "inline-flex h-7 items-center gap-2 rounded-md px-2 text-xs font-[450] text-primary-600 hover:text-primary-900 hover:bg-primary-100",
4616
- className
4617
- ),
4618
- children: [
4619
- /* @__PURE__ */ jsx(
4620
- HugeiconsIcon,
4621
- {
4622
- icon: AiBrain01Icon,
4623
- size: 20,
4624
- strokeWidth: 1.5,
4625
- className: cn(level === "off" ? "text-primary-400" : "text-primary-700")
4626
- }
4627
- ),
4628
- /* @__PURE__ */ jsx("span", { className: "text-pretty", children: current.shortLabel })
4629
- ]
4630
- }
4631
- ),
4632
- /* @__PURE__ */ jsx(MenuContent, { side: "top", align: "start", className: "min-w-[190px]", children: THINKING_OPTIONS.map((option) => /* @__PURE__ */ jsxs(
4633
- MenuItem,
4634
- {
4635
- onClick: () => setLevel(option.value),
4636
- className: "justify-between",
4637
- children: [
4638
- /* @__PURE__ */ jsxs("span", { className: "flex flex-col text-pretty", children: [
4639
- /* @__PURE__ */ jsx("span", { className: "text-sm text-primary-900", children: option.label }),
4640
- /* @__PURE__ */ jsx("span", { className: "text-xs text-primary-500", children: option.description })
4641
- ] }),
4642
- level === option.value && /* @__PURE__ */ jsx(
4643
- HugeiconsIcon,
4644
- {
4645
- icon: Tick02Icon,
4646
- size: 20,
4647
- strokeWidth: 1.5,
4648
- className: "text-primary-600"
4649
- }
4650
- )
4651
- ]
4652
- },
4653
- option.value
4654
- )) })
4655
- ] });
4656
- }
4657
4657
  const FALLBACK_SLASH_COMMANDS = [
4658
4658
  // Model aliases
4659
4659
  { command: "/haiku", description: "Switch to Claude Haiku 4.5" },
@@ -4677,12 +4677,21 @@ const FALLBACK_SLASH_COMMANDS = [
4677
4677
  { command: "/status", description: "Show session status & usage" },
4678
4678
  { command: "/whoami", description: "Show your sender ID" },
4679
4679
  { command: "/context", description: "Show context window details" },
4680
- { command: "/usage", description: "Toggle usage footer (off/tokens/full/cost)" },
4680
+ {
4681
+ command: "/usage",
4682
+ description: "Toggle usage footer (off/tokens/full/cost)"
4683
+ },
4681
4684
  // Directives
4682
- { command: "/think", description: "Set thinking level (off/low/medium/high)" },
4685
+ {
4686
+ command: "/think",
4687
+ description: "Set thinking level (off/low/medium/high)"
4688
+ },
4683
4689
  { command: "/reasoning", description: "Toggle reasoning (on/off/stream)" },
4684
4690
  { command: "/verbose", description: "Toggle verbose mode (on/full/off)" },
4685
- { command: "/elevated", description: "Toggle elevated permissions (on/off/ask)" },
4691
+ {
4692
+ command: "/elevated",
4693
+ description: "Toggle elevated permissions (on/off/ask)"
4694
+ },
4686
4695
  { command: "/exec", description: "Configure exec settings" },
4687
4696
  { command: "/queue", description: "Show/configure message queue" },
4688
4697
  // TTS
@@ -4699,13 +4708,28 @@ const FALLBACK_SLASH_COMMANDS = [
4699
4708
  { command: "/debug", description: "Runtime overrides (owner-only)" },
4700
4709
  { command: "/send", description: "Toggle message sending (on/off)" },
4701
4710
  { command: "/restart", description: "Restart gateway" },
4702
- { command: "/activation", description: "Set activation mode (mention/always)" },
4711
+ {
4712
+ command: "/activation",
4713
+ description: "Set activation mode (mention/always)"
4714
+ },
4703
4715
  // Channels
4704
4716
  { command: "/dock-telegram", description: "Switch replies to Telegram" },
4705
4717
  { command: "/dock-discord", description: "Switch replies to Discord" },
4706
4718
  // Other
4707
4719
  { command: "/bash", description: "Run host shell command" }
4708
4720
  ];
4721
+ function escapeRegExp(value) {
4722
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4723
+ }
4724
+ function removeUploadedFilePrompt(value, uploadedPath) {
4725
+ const escapedPath = escapeRegExp(uploadedPath);
4726
+ const uploadedFileBlockRegex = new RegExp(
4727
+ `(?:^|\\n\\n)📎 Uploaded file: ${escapedPath}\\n\\nPlease analyze this file\\.(?=\\n\\n|$)`
4728
+ );
4729
+ return value.replace(uploadedFileBlockRegex, (match, offset) => {
4730
+ return offset === 0 ? "" : "\n\n";
4731
+ }).trim();
4732
+ }
4709
4733
  function ChatComposerComponent({
4710
4734
  onSubmit,
4711
4735
  isLoading,
@@ -4729,12 +4753,20 @@ function ChatComposerComponent({
4729
4753
  const sttAbortControllerRef = useRef(null);
4730
4754
  const webSpeechRef = useRef(null);
4731
4755
  const promptRef = useRef(null);
4732
- const showSlashCommands = useMemo(() => /^\/\S*$/.test(value) && !slashMenuDismissed, [value, slashMenuDismissed]);
4733
- const slashQuery = useMemo(() => showSlashCommands ? value.slice(1).toLowerCase() : "", [showSlashCommands, value]);
4756
+ const showSlashCommands = useMemo(
4757
+ () => /^\/\S*$/.test(value) && !slashMenuDismissed,
4758
+ [value, slashMenuDismissed]
4759
+ );
4760
+ const slashQuery = useMemo(
4761
+ () => showSlashCommands ? value.slice(1).toLowerCase() : "",
4762
+ [showSlashCommands, value]
4763
+ );
4734
4764
  const filteredSlashCommands = useMemo(() => {
4735
4765
  if (!showSlashCommands) return [];
4736
4766
  if (!slashQuery) return FALLBACK_SLASH_COMMANDS;
4737
- return FALLBACK_SLASH_COMMANDS.filter((item) => item.command.slice(1).toLowerCase().startsWith(slashQuery));
4767
+ return FALLBACK_SLASH_COMMANDS.filter(
4768
+ (item) => item.command.slice(1).toLowerCase().startsWith(slashQuery)
4769
+ );
4738
4770
  }, [showSlashCommands, slashQuery]);
4739
4771
  const setPromptRef = useCallback(
4740
4772
  (element) => {
@@ -4783,66 +4815,86 @@ ${template}`;
4783
4815
  body: JSON.stringify({ path: "/uploads" })
4784
4816
  });
4785
4817
  }, []);
4786
- const uploadAttachmentFile = useCallback(async (file) => {
4787
- const id = crypto.randomUUID();
4788
- if (!isAcceptedNonImage(file)) {
4789
- return {
4790
- id,
4791
- file,
4792
- preview: null,
4793
- type: "file",
4794
- base64: null,
4795
- error: "Unsupported file type for upload."
4796
- };
4797
- }
4798
- try {
4799
- await ensureUploadsDirectory();
4800
- const formData = new FormData();
4801
- formData.append("path", "/uploads");
4802
- formData.append("file", file);
4803
- const response = await fetch("/api/files/upload", {
4804
- method: "POST",
4805
- body: formData
4806
- });
4807
- if (!response.ok) {
4808
- const message = await response.text();
4809
- throw new Error(message || "File upload failed");
4818
+ const uploadAttachmentFile = useCallback(
4819
+ async (file) => {
4820
+ const id = crypto.randomUUID();
4821
+ if (!isAcceptedNonImage(file)) {
4822
+ return {
4823
+ id,
4824
+ file,
4825
+ preview: null,
4826
+ type: "file",
4827
+ base64: null,
4828
+ error: "Unsupported file type for upload."
4829
+ };
4810
4830
  }
4811
- const data = await response.json();
4812
- const uploadedPath = data.files?.[0]?.path;
4813
- if (!uploadedPath) {
4814
- throw new Error("Upload succeeded but no path was returned");
4831
+ try {
4832
+ await ensureUploadsDirectory();
4833
+ const formData = new FormData();
4834
+ formData.append("path", "/uploads");
4835
+ formData.append("file", file);
4836
+ const response = await fetch("/api/files/upload", {
4837
+ method: "POST",
4838
+ body: formData
4839
+ });
4840
+ if (!response.ok) {
4841
+ const message = await response.text();
4842
+ throw new Error(message || "File upload failed");
4843
+ }
4844
+ const data = await response.json();
4845
+ const uploadedPath = data.files?.[0]?.path;
4846
+ if (!uploadedPath) {
4847
+ throw new Error("Upload succeeded but no path was returned");
4848
+ }
4849
+ appendUploadedFilePrompt(uploadedPath);
4850
+ return {
4851
+ id,
4852
+ file,
4853
+ preview: null,
4854
+ type: "file",
4855
+ base64: null,
4856
+ uploadedPath
4857
+ };
4858
+ } catch (err) {
4859
+ return {
4860
+ id,
4861
+ file,
4862
+ preview: null,
4863
+ type: "file",
4864
+ base64: null,
4865
+ error: err instanceof Error ? err.message : "File upload failed"
4866
+ };
4815
4867
  }
4816
- appendUploadedFilePrompt(uploadedPath);
4817
- return {
4818
- id,
4819
- file,
4820
- preview: null,
4821
- type: "file",
4822
- base64: null,
4823
- uploadedPath
4824
- };
4825
- } catch (err) {
4826
- return {
4827
- id,
4828
- file,
4829
- preview: null,
4830
- type: "file",
4831
- base64: null,
4832
- error: err instanceof Error ? err.message : "File upload failed"
4833
- };
4834
- }
4835
- }, [appendUploadedFilePrompt, ensureUploadsDirectory]);
4836
- const handleFilesSelect = useCallback(async (files) => {
4837
- if (files.length === 0) return;
4838
- const nextAttachments = await Promise.all(
4839
- files.map((file) => isAcceptedImage(file) ? createAttachmentFromFile(file) : uploadAttachmentFile(file))
4840
- );
4841
- setAttachments((prev) => [...prev, ...nextAttachments]);
4842
- focusPrompt();
4843
- }, [focusPrompt, uploadAttachmentFile]);
4868
+ },
4869
+ [appendUploadedFilePrompt, ensureUploadsDirectory]
4870
+ );
4871
+ const handleFilesSelect = useCallback(
4872
+ async (files) => {
4873
+ if (files.length === 0) return;
4874
+ const nextAttachments = await Promise.all(
4875
+ files.map(
4876
+ (file) => isAcceptedImage(file) ? createAttachmentFromFile(file) : uploadAttachmentFile(file)
4877
+ )
4878
+ );
4879
+ setAttachments((prev) => [...prev, ...nextAttachments]);
4880
+ focusPrompt();
4881
+ },
4882
+ [focusPrompt, uploadAttachmentFile]
4883
+ );
4844
4884
  const handleRemoveAttachment = useCallback((id) => {
4845
- setAttachments((prev) => prev.filter((a) => a.id !== id));
4885
+ let removedUploadedPath;
4886
+ setAttachments((prev) => {
4887
+ const attachmentToRemove = prev.find((attachment) => attachment.id === id);
4888
+ if (attachmentToRemove?.uploadedPath) {
4889
+ removedUploadedPath = attachmentToRemove.uploadedPath;
4890
+ }
4891
+ return prev.filter((attachment) => attachment.id !== id);
4892
+ });
4893
+ if (removedUploadedPath) {
4894
+ setValue(
4895
+ (currentValue) => removeUploadedFilePrompt(currentValue, removedUploadedPath)
4896
+ );
4897
+ }
4846
4898
  }, []);
4847
4899
  const handleDragOver = useCallback((event) => {
4848
4900
  const hasFiles = Array.from(event.dataTransfer.types).includes("Files");
@@ -4857,13 +4909,16 @@ ${template}`;
4857
4909
  if (nextTarget && event.currentTarget.contains(nextTarget)) return;
4858
4910
  setIsDragActive(false);
4859
4911
  }, []);
4860
- const handleDrop = useCallback(async (event) => {
4861
- event.preventDefault();
4862
- setIsDragActive(false);
4863
- const files = Array.from(event.dataTransfer.files ?? []);
4864
- if (files.length === 0) return;
4865
- await handleFilesSelect(files);
4866
- }, [handleFilesSelect]);
4912
+ const handleDrop = useCallback(
4913
+ async (event) => {
4914
+ event.preventDefault();
4915
+ setIsDragActive(false);
4916
+ const files = Array.from(event.dataTransfer.files);
4917
+ if (files.length === 0) return;
4918
+ await handleFilesSelect(files);
4919
+ },
4920
+ [handleFilesSelect]
4921
+ );
4867
4922
  const setComposerValue = useCallback(
4868
4923
  (nextValue) => {
4869
4924
  setValue(nextValue);
@@ -4890,14 +4945,22 @@ ${template}`;
4890
4945
  const handleSubmit = useCallback(() => {
4891
4946
  if (disabled) return;
4892
4947
  if (showSlashCommands && filteredSlashCommands.length > 0) {
4893
- const nextIndex = Math.min(selectedCommandIndex, filteredSlashCommands.length - 1);
4948
+ const nextIndex = Math.min(
4949
+ selectedCommandIndex,
4950
+ filteredSlashCommands.length - 1
4951
+ );
4894
4952
  selectSlashCommand(filteredSlashCommands[nextIndex]);
4895
4953
  return;
4896
4954
  }
4897
4955
  const body = value.trim();
4898
4956
  const validAttachments2 = attachments.filter((a) => !a.error && a.base64);
4899
4957
  if (body.length === 0 && validAttachments2.length === 0) return;
4900
- onSubmit(body, { reset, setValue: setComposerValue, model: selectedModel || void 0, attachments: validAttachments2 });
4958
+ onSubmit(body, {
4959
+ reset,
4960
+ setValue: setComposerValue,
4961
+ model: selectedModel || void 0,
4962
+ attachments: validAttachments2
4963
+ });
4901
4964
  focusPrompt();
4902
4965
  }, [
4903
4966
  disabled,
@@ -4915,7 +4978,12 @@ ${template}`;
4915
4978
  ]);
4916
4979
  const handlePersonaSelect = useCallback(
4917
4980
  (command) => {
4918
- onSubmit(command, { reset, setValue: setComposerValue, model: selectedModel || void 0, attachments: [] });
4981
+ onSubmit(command, {
4982
+ reset,
4983
+ setValue: setComposerValue,
4984
+ model: selectedModel || void 0,
4985
+ attachments: []
4986
+ });
4919
4987
  focusPrompt();
4920
4988
  },
4921
4989
  [focusPrompt, onSubmit, reset, setComposerValue, selectedModel]
@@ -4932,21 +5000,33 @@ ${template}`;
4932
5000
  if (filteredSlashCommands.length === 0) return;
4933
5001
  if (event.key === "ArrowDown") {
4934
5002
  event.preventDefault();
4935
- setSelectedCommandIndex((prev) => (prev + 1) % filteredSlashCommands.length);
5003
+ setSelectedCommandIndex(
5004
+ (prev) => (prev + 1) % filteredSlashCommands.length
5005
+ );
4936
5006
  return;
4937
5007
  }
4938
5008
  if (event.key === "ArrowUp") {
4939
5009
  event.preventDefault();
4940
- setSelectedCommandIndex((prev) => (prev - 1 + filteredSlashCommands.length) % filteredSlashCommands.length);
5010
+ setSelectedCommandIndex(
5011
+ (prev) => (prev - 1 + filteredSlashCommands.length) % filteredSlashCommands.length
5012
+ );
4941
5013
  return;
4942
5014
  }
4943
5015
  if (event.key === "Tab") {
4944
5016
  event.preventDefault();
4945
- const nextIndex = Math.min(selectedCommandIndex, filteredSlashCommands.length - 1);
5017
+ const nextIndex = Math.min(
5018
+ selectedCommandIndex,
5019
+ filteredSlashCommands.length - 1
5020
+ );
4946
5021
  selectSlashCommand(filteredSlashCommands[nextIndex]);
4947
5022
  }
4948
5023
  },
4949
- [showSlashCommands, filteredSlashCommands, selectedCommandIndex, selectSlashCommand]
5024
+ [
5025
+ showSlashCommands,
5026
+ filteredSlashCommands,
5027
+ selectedCommandIndex,
5028
+ selectSlashCommand
5029
+ ]
4950
5030
  );
4951
5031
  useEffect(() => {
4952
5032
  return () => {
@@ -5027,7 +5107,9 @@ ${template}`;
5027
5107
  setIsRecording(false);
5028
5108
  setRecordingTime(0);
5029
5109
  if (recordingTimerRef.current) clearInterval(recordingTimerRef.current);
5030
- showMicError("Speech recognition failed. Try the Browser provider again or switch providers in Settings.");
5110
+ showMicError(
5111
+ "Speech recognition failed. Try the Browser provider again or switch providers in Settings."
5112
+ );
5031
5113
  };
5032
5114
  recognition.start();
5033
5115
  return;
@@ -5043,17 +5125,27 @@ ${template}`;
5043
5125
  };
5044
5126
  recorder.onstop = async () => {
5045
5127
  stream.getTracks().forEach((t) => t.stop());
5046
- const audioBlob = new Blob(recordingChunksRef.current, { type: mimeType });
5128
+ const audioBlob = new Blob(recordingChunksRef.current, {
5129
+ type: mimeType
5130
+ });
5047
5131
  if (audioBlob.size === 0) return;
5048
5132
  setSttLoading(true);
5049
5133
  try {
5050
5134
  const formData = new FormData();
5051
- formData.append("audio", audioBlob, `recording.${mimeType === "audio/webm" ? "webm" : "mp4"}`);
5135
+ formData.append(
5136
+ "audio",
5137
+ audioBlob,
5138
+ `recording.${mimeType === "audio/webm" ? "webm" : "mp4"}`
5139
+ );
5052
5140
  if (provider !== "auto") formData.append("provider", provider);
5053
5141
  sttAbortControllerRef.current?.abort();
5054
5142
  const controller = new AbortController();
5055
5143
  sttAbortControllerRef.current = controller;
5056
- const res = await fetch("/api/stt", { method: "POST", body: formData, signal: controller.signal });
5144
+ const res = await fetch("/api/stt", {
5145
+ method: "POST",
5146
+ body: formData,
5147
+ signal: controller.signal
5148
+ });
5057
5149
  const data = await res.json();
5058
5150
  if (data.ok && data.text) {
5059
5151
  setMicError(null);
@@ -5061,7 +5153,9 @@ ${template}`;
5061
5153
  focusPrompt();
5062
5154
  } else if (!data.ok) {
5063
5155
  console.warn("STT failed:", data.error);
5064
- showMicError(data.error || "Speech-to-text failed. Try the Browser provider in Settings.");
5156
+ showMicError(
5157
+ data.error || "Speech-to-text failed. Try the Browser provider in Settings."
5158
+ );
5065
5159
  }
5066
5160
  } catch (err) {
5067
5161
  if (err instanceof Error && err.name === "AbortError") return;
@@ -5087,14 +5181,22 @@ ${template}`;
5087
5181
  const msg = err instanceof Error ? err.message : String(err);
5088
5182
  if (msg.includes("NotAllowedError") || msg.includes("Permission")) {
5089
5183
  try {
5090
- const status = await navigator.permissions.query({ name: "microphone" });
5184
+ const status = await navigator.permissions.query({
5185
+ name: "microphone"
5186
+ });
5091
5187
  if (status.state === "denied") {
5092
- showMicError("Microphone access is blocked. Please enable it in your browser/app settings.");
5188
+ showMicError(
5189
+ "Microphone access is blocked. Please enable it in your browser/app settings."
5190
+ );
5093
5191
  } else {
5094
- showMicError("Microphone permission was not granted. Please try again and allow access when prompted.");
5192
+ showMicError(
5193
+ "Microphone permission was not granted. Please try again and allow access when prompted."
5194
+ );
5095
5195
  }
5096
5196
  } catch {
5097
- showMicError("Could not access microphone. Please check your browser settings and allow microphone access for this site.");
5197
+ showMicError(
5198
+ "Could not access microphone. Please check your browser settings and allow microphone access for this site."
5199
+ );
5098
5200
  }
5099
5201
  } else {
5100
5202
  showMicError("Could not access microphone: " + msg);
@@ -5149,7 +5251,10 @@ ${template}`;
5149
5251
  SlashCommandMenu,
5150
5252
  {
5151
5253
  commands: filteredSlashCommands,
5152
- selectedIndex: Math.min(selectedCommandIndex, filteredSlashCommands.length - 1),
5254
+ selectedIndex: Math.min(
5255
+ selectedCommandIndex,
5256
+ filteredSlashCommands.length - 1
5257
+ ),
5153
5258
  onSelect: selectSlashCommand
5154
5259
  }
5155
5260
  ),
@@ -5165,7 +5270,12 @@ ${template}`;
5165
5270
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
5166
5271
  /* @__PURE__ */ jsx(ThinkingLevelSelector, {}),
5167
5272
  /* @__PURE__ */ jsx(PersonaPicker, { onSelect: handlePersonaSelect }),
5168
- /* @__PURE__ */ jsx(CommandHelp, { onCommandSelect: (cmd) => handleValueChange(cmd + " ") })
5273
+ /* @__PURE__ */ jsx(
5274
+ CommandHelp,
5275
+ {
5276
+ onCommandSelect: (cmd) => handleValueChange(cmd + " ")
5277
+ }
5278
+ )
5169
5279
  ] }),
5170
5280
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
5171
5281
  /* @__PURE__ */ jsx(
@@ -5175,26 +5285,32 @@ ${template}`;
5175
5285
  disabled
5176
5286
  }
5177
5287
  ),
5178
- /* @__PURE__ */ jsx(PromptInputAction, { tooltip: isRecording ? "Stop recording" : "Voice input", children: /* @__PURE__ */ jsx(
5179
- Button,
5288
+ /* @__PURE__ */ jsx(
5289
+ PromptInputAction,
5180
5290
  {
5181
- onClick: handleMicClick,
5182
- disabled: disabled || sttLoading,
5183
- size: "icon-sm",
5184
- variant: isRecording ? "destructive" : "ghost",
5185
- className: `rounded-full ${isRecording ? "animate-pulse" : ""}`,
5186
- "aria-label": isRecording ? "Stop recording" : "Voice input",
5187
- children: sttLoading ? /* @__PURE__ */ jsx("span", { className: "size-4 animate-spin rounded-full border-2 border-current border-t-transparent" }) : isRecording ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
5188
- /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-red-500" }),
5189
- /* @__PURE__ */ jsxs("span", { className: "text-xs tabular-nums", children: [
5190
- Math.floor(recordingTime / 60),
5191
- ":",
5192
- String(recordingTime % 60).padStart(2, "0")
5193
- ] }),
5194
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: StopIcon, size: 14, strokeWidth: 2 })
5195
- ] }) : /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Mic02Icon, size: 18, strokeWidth: 2 })
5291
+ tooltip: isRecording ? "Stop recording" : "Voice input",
5292
+ children: /* @__PURE__ */ jsx(
5293
+ Button,
5294
+ {
5295
+ onClick: handleMicClick,
5296
+ disabled: disabled || sttLoading,
5297
+ size: "icon-sm",
5298
+ variant: isRecording ? "destructive" : "ghost",
5299
+ className: `rounded-full ${isRecording ? "animate-pulse" : ""}`,
5300
+ "aria-label": isRecording ? "Stop recording" : "Voice input",
5301
+ children: sttLoading ? /* @__PURE__ */ jsx("span", { className: "size-4 animate-spin rounded-full border-2 border-current border-t-transparent" }) : isRecording ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
5302
+ /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-red-500" }),
5303
+ /* @__PURE__ */ jsxs("span", { className: "text-xs tabular-nums", children: [
5304
+ Math.floor(recordingTime / 60),
5305
+ ":",
5306
+ String(recordingTime % 60).padStart(2, "0")
5307
+ ] }),
5308
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: StopIcon, size: 14, strokeWidth: 2 })
5309
+ ] }) : /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Mic02Icon, size: 18, strokeWidth: 2 })
5310
+ }
5311
+ )
5196
5312
  }
5197
- ) }),
5313
+ ),
5198
5314
  /* @__PURE__ */ jsx(PromptInputAction, { tooltip: "Send message", children: /* @__PURE__ */ jsx(
5199
5315
  Button,
5200
5316
  {
@@ -6065,7 +6181,6 @@ function useSwipeGesture(options) {
6065
6181
  };
6066
6182
  }
6067
6183
  const ENABLED_KEY = "opencami-browser-notifications-enabled";
6068
- const ASKED_KEY = "opencami-browser-notifications-permission-asked";
6069
6184
  const NOTIFICATION_DEBOUNCE_MS = 5e3;
6070
6185
  function isSupported() {
6071
6186
  return typeof window !== "undefined" && "Notification" in window;
@@ -6098,10 +6213,6 @@ function useNotifications() {
6098
6213
  }
6099
6214
  if (nextEnabled && isSupported() && Notification.permission === "default") {
6100
6215
  await requestPermission();
6101
- try {
6102
- localStorage.setItem(ASKED_KEY, "true");
6103
- } catch {
6104
- }
6105
6216
  }
6106
6217
  }, [requestPermission]);
6107
6218
  const maybeNotifyAssistantMessage = useCallback((payload) => {
@@ -6122,32 +6233,6 @@ function useNotifications() {
6122
6233
  notification.close();
6123
6234
  };
6124
6235
  }, [enabled, navigate]);
6125
- useEffect(() => {
6126
- if (!isSupported()) return;
6127
- const onFirstInteraction = () => {
6128
- if (Notification.permission !== "default") return;
6129
- let asked = false;
6130
- try {
6131
- asked = localStorage.getItem(ASKED_KEY) === "true";
6132
- } catch {
6133
- asked = false;
6134
- }
6135
- if (asked) return;
6136
- void requestPermission().finally(() => {
6137
- try {
6138
- localStorage.setItem(ASKED_KEY, "true");
6139
- } catch {
6140
- }
6141
- });
6142
- };
6143
- const options = { once: true, passive: true };
6144
- window.addEventListener("pointerdown", onFirstInteraction, options);
6145
- window.addEventListener("keydown", onFirstInteraction, { once: true });
6146
- return () => {
6147
- window.removeEventListener("pointerdown", onFirstInteraction);
6148
- window.removeEventListener("keydown", onFirstInteraction);
6149
- };
6150
- }, [requestPermission]);
6151
6236
  useEffect(() => {
6152
6237
  const sync = () => setEnabledState(getNotificationsEnabled());
6153
6238
  window.addEventListener("storage", sync);
@@ -6166,7 +6251,7 @@ const KeyboardShortcutsDialog = lazy(
6166
6251
  }))
6167
6252
  );
6168
6253
  const SearchDialog = lazy(
6169
- () => import("./search-dialog-DR6zBnui.js").then((m) => ({
6254
+ () => import("./search-dialog-CmI7naPN.js").then((m) => ({
6170
6255
  default: m.SearchDialog
6171
6256
  }))
6172
6257
  );