opencami 1.8.7 → 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 (84) hide show
  1. package/README.md +0 -1
  2. package/dist/client/assets/{CSPContext-KDlbpvjX.js → CSPContext-DI-5GAnQ.js} +1 -1
  3. package/dist/client/assets/{DirectionContext-DZNUWW6F.js → DirectionContext-CrIsc5n9.js} +1 -1
  4. package/dist/client/assets/_sessionKey-B4NZmxf3.js +21 -0
  5. package/dist/client/assets/agents-bptidK8z.js +2 -0
  6. package/dist/client/assets/agents-screen-6qdnPmx2.js +1 -0
  7. package/dist/client/assets/bots-BWpbaQ-E.js +2 -0
  8. package/dist/client/assets/{bots-screen-CvbAVMk0.js → bots-screen-BTKCOohV.js} +1 -1
  9. package/dist/client/assets/button-8ab4wOwy.js +1 -0
  10. package/dist/client/assets/{composite-t7Eu0qB0.js → composite-B2qsrzf3.js} +1 -1
  11. package/dist/client/assets/{connect-Btonx7l6.js → connect-B3_p7C4I.js} +1 -1
  12. package/dist/client/assets/dashboard-BtClHYpn.js +1 -0
  13. package/dist/client/assets/event-DG3RKJz8.js +1 -0
  14. package/dist/client/assets/file-explorer-screen-Djl8x-8P.js +1 -0
  15. package/dist/client/assets/files-CjbCJDgC.js +2 -0
  16. package/dist/client/assets/follow-up-suggestions-BSCMXRXh.js +5 -0
  17. package/dist/client/assets/{index-nheqha2z.js → index-BXiha-Vz.js} +1 -1
  18. package/dist/client/assets/{index-yPOt98v7.js → index-CtlYu8Ug.js} +1 -1
  19. package/dist/client/assets/keyboard-shortcuts-dialog-HAufCn9C.js +1 -0
  20. package/dist/client/assets/main-CQKtcNr3.js +210 -0
  21. package/dist/client/assets/markdown-DFJF-FsV.js +87 -0
  22. package/dist/client/assets/memory-DnJOmcwU.js +2 -0
  23. package/dist/client/assets/memory-screen-Bm4NMAnU.js +1 -0
  24. package/dist/client/assets/menu-D26Vmgxl.js +1 -0
  25. package/dist/client/assets/{opencami-logo-CoCA2JIf.js → opencami-logo-BSed2Wez.js} +1 -1
  26. package/dist/client/assets/popupStateMapping-DkI2OCkW.js +1 -0
  27. package/dist/client/assets/proxy-CHQ-VCN1.js +9 -0
  28. package/dist/client/assets/{react-DzPMbcP8.js → react-WkSlhZJd.js} +1 -1
  29. package/dist/client/assets/search-dialog-CCl4d0Pi.js +1 -0
  30. package/dist/client/assets/{search-sources-badge-DOd5AI-i.js → search-sources-badge-Du8KpUEb.js} +1 -1
  31. package/dist/client/assets/session-export-dialog-io9FvLKq.js +1 -0
  32. package/dist/client/assets/settings-dialog-B93qswor.js +1 -0
  33. package/dist/client/assets/skills-BNDGnHwM.js +2 -0
  34. package/dist/client/assets/{skills-panel-_M5k5dty.js → skills-panel-CVh1I-7D.js} +1 -1
  35. package/dist/client/assets/styles-Ce2xZzc4.css +1 -0
  36. package/dist/client/assets/switch-CSnzINDW.js +1 -0
  37. package/dist/client/assets/tabs-CWfn44FL.js +1 -0
  38. package/dist/client/assets/thinking-BmoLlbFC.js +1 -0
  39. package/dist/client/assets/tooltip-CSGMH2t4.js +1 -0
  40. package/dist/client/assets/use-file-explorer-state-BYVzjwPA.js +12 -0
  41. package/dist/client/assets/{useBaseUiId-CMJ8I9al.js → useBaseUiId-DiwX_3so.js} +1 -1
  42. package/dist/client/assets/{useCompositeItem-CSriPK0T.js → useCompositeItem-UPIPwR9H.js} +1 -1
  43. package/dist/client/assets/{useControlled-KFa25CcD.js → useControlled-CT2hRlcU.js} +1 -1
  44. package/dist/client/assets/{useMutation-Bd89JWif.js → useMutation-rx8UH99I.js} +1 -1
  45. package/dist/client/assets/{useQuery-DhR_UXNX.js → useQuery-Boaa6oF3.js} +1 -1
  46. package/dist/server/assets/{_sessionKey-BzM-igv7.js → _sessionKey-B6iYeyCS.js} +379 -395
  47. package/dist/server/assets/{_tanstack-start-manifest_v-Bfnqdswf.js → _tanstack-start-manifest_v-C9chPgNH.js} +1 -1
  48. package/dist/server/assets/{dashboard-GCKodTiJ.js → dashboard-UYRCu_mQ.js} +19 -3
  49. package/dist/server/assets/{follow-up-suggestions-DhBZIszK.js → follow-up-suggestions-mzRQIB0k.js} +2 -2
  50. package/dist/server/assets/{index-DCMpnyEo.js → index-COElhwGA.js} +1 -1
  51. package/dist/server/assets/{router-BqPDQeCx.js → router-BqLGFd4L.js} +4 -4
  52. package/dist/server/assets/{search-dialog-DtYc9e4N.js → search-dialog-CmI7naPN.js} +2 -2
  53. package/dist/server/assets/{settings-dialog-BQzn6fyF.js → settings-dialog-BZ67gr9N.js} +2 -2
  54. package/dist/server/assets/{thinking-D6q1tJkk.js → thinking-CA8PSwKJ.js} +2 -2
  55. package/dist/server/server.js +38 -195
  56. package/package.json +3 -7
  57. package/dist/client/assets/_sessionKey-CPRycepq.js +0 -19
  58. package/dist/client/assets/agents-Bp6Cn1BH.js +0 -2
  59. package/dist/client/assets/agents-screen-Du8b4SRW.js +0 -1
  60. package/dist/client/assets/bots-Dl65yZDx.js +0 -2
  61. package/dist/client/assets/button-BxDVif49.js +0 -1
  62. package/dist/client/assets/dashboard-Dz6Uyyke.js +0 -1
  63. package/dist/client/assets/event-DeooaZzN.js +0 -1
  64. package/dist/client/assets/file-explorer-screen-BnOINWSL.js +0 -1
  65. package/dist/client/assets/files-Cf87gXsq.js +0 -2
  66. package/dist/client/assets/follow-up-suggestions-DP_84nA2.js +0 -5
  67. package/dist/client/assets/keyboard-shortcuts-dialog-BzL7ZJUb.js +0 -1
  68. package/dist/client/assets/main-DJgQwVqD.js +0 -210
  69. package/dist/client/assets/markdown-CFbW7h0Q.js +0 -87
  70. package/dist/client/assets/memory-kYdhgbRR.js +0 -2
  71. package/dist/client/assets/memory-screen-BKYBnY-V.js +0 -1
  72. package/dist/client/assets/menu-CnihCZ4y.js +0 -1
  73. package/dist/client/assets/proxy-C2vtPufK.js +0 -9
  74. package/dist/client/assets/search-dialog-CDAdFAQI.js +0 -1
  75. package/dist/client/assets/session-export-dialog-Cf8eisLM.js +0 -1
  76. package/dist/client/assets/settings-dialog-CB1OiRWU.js +0 -1
  77. package/dist/client/assets/skills-D6MaebJh.js +0 -2
  78. package/dist/client/assets/styles-BNkOWk4R.css +0 -1
  79. package/dist/client/assets/switch-LzFh8co4.js +0 -1
  80. package/dist/client/assets/tabs-D9o7Irvl.js +0 -1
  81. package/dist/client/assets/thinking-BqZoM6BP.js +0 -1
  82. package/dist/client/assets/tooltip-D64CzFXa.js +0 -1
  83. package/dist/client/assets/use-file-explorer-state-Tzex8EEL.js +0 -12
  84. package/dist/client/assets/useOnFirstRender-DqnUCnnL.js +0 -1
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import React__default, { useState, useCallback, memo, useDeferredValue, useMemo, Suspense, lazy, useRef, useEffect, useLayoutEffect, createContext, useContext } from "react";
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
- import { Tick01Icon, Cancel01Icon, MoreHorizontalIcon, Pen01Icon, Upload01Icon, Delete01Icon, BotIcon, Clock01Icon, Chat01Icon, ArrowRight01Icon, SidebarLeft01Icon, PencilEdit02Icon, Folder01Icon, AiBrain01Icon, PackageOpenIcon, SmartPhone01Icon, DashboardCircleIcon, Search01Icon, Settings01Icon, Menu01Icon, Tick02Icon, Copy01Icon, Loading02Icon, StopIcon, VolumeHighIcon, Loading03Icon, ArrowDown01Icon, File01Icon, ArtificialIntelligence02Icon, CommandIcon, Attachment01Icon, Mic02Icon, ArrowUp02Icon } from "@hugeicons/core-free-icons";
7
+ import { Tick01Icon, Cancel01Icon, MoreHorizontalIcon, Pen01Icon, Upload01Icon, Delete01Icon, BotIcon, Clock01Icon, Chat01Icon, ArrowRight01Icon, SidebarLeft01Icon, PencilEdit02Icon, Folder01Icon, AiBrain01Icon, PackageOpenIcon, SmartPhone01Icon, DashboardCircleIcon, Search01Icon, Settings01Icon, Menu01Icon, Tick02Icon, Copy01Icon, Loading02Icon, StopIcon, VolumeHighIcon, Loading03Icon, ArrowDown01Icon, File01Icon, CommandIcon, Attachment01Icon, Mic02Icon, ArrowUp02Icon } from "@hugeicons/core-free-icons";
8
8
  import { HugeiconsIcon } from "@hugeicons/react";
9
9
  import { motion, AnimatePresence } from "motion/react";
10
10
  import { AlertDialog } from "@base-ui/react/alert-dialog";
@@ -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-BqPDQeCx.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-BQzn6fyF.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-D6q1tJkk.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-DhBZIszK.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: "",
@@ -3965,138 +4079,6 @@ function PromptInputAction({
3965
4079
  /* @__PURE__ */ jsx(TooltipContent, { side, className, children: tooltip })
3966
4080
  ] });
3967
4081
  }
3968
- const STORAGE_KEY = "opencami-selected-model";
3969
- function getStoredModel() {
3970
- if (typeof window === "undefined") return null;
3971
- try {
3972
- return localStorage.getItem(STORAGE_KEY);
3973
- } catch {
3974
- return null;
3975
- }
3976
- }
3977
- function setStoredModel(modelId) {
3978
- if (typeof window === "undefined") return;
3979
- try {
3980
- localStorage.setItem(STORAGE_KEY, modelId);
3981
- } catch {
3982
- }
3983
- }
3984
- function ModelSelector({ className, onModelChange }) {
3985
- const [models, setModels] = useState([]);
3986
- const [selectedModel, setSelectedModel] = useState("");
3987
- const [isLoading, setIsLoading] = useState(true);
3988
- const [error, setError] = useState(null);
3989
- const abortControllerRef = useRef(null);
3990
- useEffect(() => {
3991
- let mounted = true;
3992
- async function fetchModels() {
3993
- abortControllerRef.current?.abort();
3994
- const controller = new AbortController();
3995
- abortControllerRef.current = controller;
3996
- try {
3997
- setIsLoading(true);
3998
- setError(null);
3999
- const response = await fetch("/api/models", { signal: controller.signal });
4000
- if (!response.ok) {
4001
- throw new Error("Failed to fetch models");
4002
- }
4003
- const data = await response.json();
4004
- if (!mounted) return;
4005
- if (data.ok && data.models.length > 0) {
4006
- setModels(data.models);
4007
- const storedModel = getStoredModel();
4008
- const initialModel = storedModel && data.models.some((m) => m.id === storedModel) ? storedModel : data.defaultModel;
4009
- setSelectedModel(initialModel);
4010
- onModelChange?.(initialModel);
4011
- } else {
4012
- throw new Error("No models available");
4013
- }
4014
- } catch (err) {
4015
- if (err instanceof Error && err.name === "AbortError") return;
4016
- if (!mounted) return;
4017
- console.error("[model-selector] Error fetching models:", err);
4018
- setError(err instanceof Error ? err.message : "Failed to load models");
4019
- setModels([{ id: "default", name: "Default Model" }]);
4020
- setSelectedModel("default");
4021
- } finally {
4022
- if (mounted) {
4023
- setIsLoading(false);
4024
- }
4025
- }
4026
- }
4027
- fetchModels();
4028
- return () => {
4029
- mounted = false;
4030
- abortControllerRef.current?.abort();
4031
- };
4032
- }, [onModelChange]);
4033
- function handleModelSelect(modelId) {
4034
- setSelectedModel(modelId);
4035
- setStoredModel(modelId);
4036
- onModelChange?.(modelId);
4037
- }
4038
- if (isLoading) {
4039
- return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2 text-xs text-primary-500", className), children: [
4040
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: ArtificialIntelligence02Icon, size: 14 }),
4041
- /* @__PURE__ */ jsx("span", { children: "Loading..." })
4042
- ] });
4043
- }
4044
- if (error || models.length === 0) {
4045
- return null;
4046
- }
4047
- const selectedModelInfo = models.find((m) => m.id === selectedModel);
4048
- const displayName = selectedModelInfo?.name || "Select Model";
4049
- const shortDisplayName = (() => {
4050
- if (!selectedModelInfo?.name) return displayName;
4051
- const name = selectedModelInfo.name;
4052
- const clean = name.replace(/\s*\([^)]*\)\s*$/, "").trim();
4053
- const words = clean.split(/\s+/);
4054
- if (words.length > 3) return words.slice(0, 3).join(" ");
4055
- return clean;
4056
- })();
4057
- if (models.length === 1) {
4058
- return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2 text-xs text-primary-500", className), children: [
4059
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: ArtificialIntelligence02Icon, size: 14 }),
4060
- /* @__PURE__ */ jsx("span", { className: "font-[450] md:hidden", children: shortDisplayName }),
4061
- /* @__PURE__ */ jsx("span", { className: "font-[450] hidden md:inline", children: displayName })
4062
- ] });
4063
- }
4064
- return /* @__PURE__ */ jsxs(MenuRoot, { children: [
4065
- /* @__PURE__ */ jsxs(
4066
- MenuTrigger,
4067
- {
4068
- className: cn(
4069
- "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",
4070
- className
4071
- ),
4072
- children: [
4073
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: ArtificialIntelligence02Icon, size: 14 }),
4074
- /* @__PURE__ */ jsx("span", { className: "md:hidden", children: shortDisplayName }),
4075
- /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: displayName })
4076
- ]
4077
- }
4078
- ),
4079
- /* @__PURE__ */ jsx(MenuContent, { side: "top", align: "start", children: models.map((model) => /* @__PURE__ */ jsxs(
4080
- MenuItem,
4081
- {
4082
- onClick: () => handleModelSelect(model.id),
4083
- className: "justify-between min-w-[180px]",
4084
- children: [
4085
- /* @__PURE__ */ jsx("span", { children: model.name }),
4086
- selectedModel === model.id && /* @__PURE__ */ jsx(
4087
- HugeiconsIcon,
4088
- {
4089
- icon: Tick02Icon,
4090
- size: 14,
4091
- className: "text-primary-600"
4092
- }
4093
- )
4094
- ]
4095
- },
4096
- model.id
4097
- )) })
4098
- ] });
4099
- }
4100
4082
  const PERSONAS_ENABLED_KEY = "opencami-personas-enabled";
4101
4083
  const ACTIVE_PERSONA_KEY = "opencami-active-persona";
4102
4084
  function isPersonasEnabled() {
@@ -4672,120 +4654,6 @@ function AttachmentPreviewList({
4672
4654
  attachment.id
4673
4655
  )) });
4674
4656
  }
4675
- function SlashCommandMenuComponent({
4676
- commands,
4677
- selectedIndex,
4678
- onSelect,
4679
- className
4680
- }) {
4681
- if (commands.length === 0) return null;
4682
- return /* @__PURE__ */ jsx(
4683
- "div",
4684
- {
4685
- className: cn(
4686
- "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",
4687
- className
4688
- ),
4689
- role: "listbox",
4690
- "aria-label": "Slash commands",
4691
- children: /* @__PURE__ */ jsx("div", { className: "max-h-56 overflow-y-auto p-1", children: commands.map((item, index) => {
4692
- const active = index === selectedIndex;
4693
- return /* @__PURE__ */ jsx(
4694
- "button",
4695
- {
4696
- type: "button",
4697
- role: "option",
4698
- "aria-selected": active,
4699
- onMouseDown: (event) => {
4700
- event.preventDefault();
4701
- onSelect(item);
4702
- },
4703
- className: cn(
4704
- "w-full rounded-md px-3 py-2 text-left transition-colors",
4705
- active ? "bg-neutral-800" : "hover:bg-neutral-800/80"
4706
- ),
4707
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
4708
- /* @__PURE__ */ jsx("code", { className: "min-w-[90px] font-mono text-xs text-primary-300", children: item.command }),
4709
- /* @__PURE__ */ jsx("span", { className: "text-xs text-neutral-300", children: item.description })
4710
- ] })
4711
- },
4712
- item.command
4713
- );
4714
- }) })
4715
- }
4716
- );
4717
- }
4718
- const SlashCommandMenu = memo(SlashCommandMenuComponent);
4719
- const useThinkingLevelStore = create()(
4720
- persist(
4721
- (set) => ({
4722
- level: "low",
4723
- setLevel: (level) => set({ level })
4724
- }),
4725
- { name: "thinking-level" }
4726
- )
4727
- );
4728
- function useThinkingLevel() {
4729
- const level = useThinkingLevelStore((state) => state.level);
4730
- const setLevel = useThinkingLevelStore((state) => state.setLevel);
4731
- return { level, setLevel };
4732
- }
4733
- const THINKING_OPTIONS = [
4734
- { value: "off", label: "Off", description: "No reasoning", shortLabel: "Off" },
4735
- { value: "low", label: "Low", description: "Think", shortLabel: "Low" },
4736
- { value: "medium", label: "Medium", description: "Think harder", shortLabel: "Medium" },
4737
- { value: "high", label: "High", description: "Ultrathink", shortLabel: "High" }
4738
- ];
4739
- function ThinkingLevelSelector({ className }) {
4740
- const { level, setLevel } = useThinkingLevel();
4741
- const current = THINKING_OPTIONS.find((option) => option.value === level) ?? THINKING_OPTIONS[1];
4742
- return /* @__PURE__ */ jsxs(MenuRoot, { children: [
4743
- /* @__PURE__ */ jsxs(
4744
- MenuTrigger,
4745
- {
4746
- className: cn(
4747
- "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",
4748
- className
4749
- ),
4750
- children: [
4751
- /* @__PURE__ */ jsx(
4752
- HugeiconsIcon,
4753
- {
4754
- icon: AiBrain01Icon,
4755
- size: 20,
4756
- strokeWidth: 1.5,
4757
- className: cn(level === "off" ? "text-primary-400" : "text-primary-700")
4758
- }
4759
- ),
4760
- /* @__PURE__ */ jsx("span", { className: "text-pretty", children: current.shortLabel })
4761
- ]
4762
- }
4763
- ),
4764
- /* @__PURE__ */ jsx(MenuContent, { side: "top", align: "start", className: "min-w-[190px]", children: THINKING_OPTIONS.map((option) => /* @__PURE__ */ jsxs(
4765
- MenuItem,
4766
- {
4767
- onClick: () => setLevel(option.value),
4768
- className: "justify-between",
4769
- children: [
4770
- /* @__PURE__ */ jsxs("span", { className: "flex flex-col text-pretty", children: [
4771
- /* @__PURE__ */ jsx("span", { className: "text-sm text-primary-900", children: option.label }),
4772
- /* @__PURE__ */ jsx("span", { className: "text-xs text-primary-500", children: option.description })
4773
- ] }),
4774
- level === option.value && /* @__PURE__ */ jsx(
4775
- HugeiconsIcon,
4776
- {
4777
- icon: Tick02Icon,
4778
- size: 20,
4779
- strokeWidth: 1.5,
4780
- className: "text-primary-600"
4781
- }
4782
- )
4783
- ]
4784
- },
4785
- option.value
4786
- )) })
4787
- ] });
4788
- }
4789
4657
  const FALLBACK_SLASH_COMMANDS = [
4790
4658
  // Model aliases
4791
4659
  { command: "/haiku", description: "Switch to Claude Haiku 4.5" },
@@ -4809,12 +4677,21 @@ const FALLBACK_SLASH_COMMANDS = [
4809
4677
  { command: "/status", description: "Show session status & usage" },
4810
4678
  { command: "/whoami", description: "Show your sender ID" },
4811
4679
  { command: "/context", description: "Show context window details" },
4812
- { command: "/usage", description: "Toggle usage footer (off/tokens/full/cost)" },
4680
+ {
4681
+ command: "/usage",
4682
+ description: "Toggle usage footer (off/tokens/full/cost)"
4683
+ },
4813
4684
  // Directives
4814
- { command: "/think", description: "Set thinking level (off/low/medium/high)" },
4685
+ {
4686
+ command: "/think",
4687
+ description: "Set thinking level (off/low/medium/high)"
4688
+ },
4815
4689
  { command: "/reasoning", description: "Toggle reasoning (on/off/stream)" },
4816
4690
  { command: "/verbose", description: "Toggle verbose mode (on/full/off)" },
4817
- { command: "/elevated", description: "Toggle elevated permissions (on/off/ask)" },
4691
+ {
4692
+ command: "/elevated",
4693
+ description: "Toggle elevated permissions (on/off/ask)"
4694
+ },
4818
4695
  { command: "/exec", description: "Configure exec settings" },
4819
4696
  { command: "/queue", description: "Show/configure message queue" },
4820
4697
  // TTS
@@ -4831,13 +4708,28 @@ const FALLBACK_SLASH_COMMANDS = [
4831
4708
  { command: "/debug", description: "Runtime overrides (owner-only)" },
4832
4709
  { command: "/send", description: "Toggle message sending (on/off)" },
4833
4710
  { command: "/restart", description: "Restart gateway" },
4834
- { command: "/activation", description: "Set activation mode (mention/always)" },
4711
+ {
4712
+ command: "/activation",
4713
+ description: "Set activation mode (mention/always)"
4714
+ },
4835
4715
  // Channels
4836
4716
  { command: "/dock-telegram", description: "Switch replies to Telegram" },
4837
4717
  { command: "/dock-discord", description: "Switch replies to Discord" },
4838
4718
  // Other
4839
4719
  { command: "/bash", description: "Run host shell command" }
4840
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
+ }
4841
4733
  function ChatComposerComponent({
4842
4734
  onSubmit,
4843
4735
  isLoading,
@@ -4854,18 +4746,27 @@ function ChatComposerComponent({
4854
4746
  const [isRecording, setIsRecording] = useState(false);
4855
4747
  const [recordingTime, setRecordingTime] = useState(0);
4856
4748
  const [sttLoading, setSttLoading] = useState(false);
4749
+ const [micError, setMicError] = useState(null);
4857
4750
  const mediaRecorderRef = useRef(null);
4858
4751
  const recordingChunksRef = useRef([]);
4859
4752
  const recordingTimerRef = useRef(null);
4860
4753
  const sttAbortControllerRef = useRef(null);
4861
4754
  const webSpeechRef = useRef(null);
4862
4755
  const promptRef = useRef(null);
4863
- const showSlashCommands = useMemo(() => /^\/\S*$/.test(value) && !slashMenuDismissed, [value, slashMenuDismissed]);
4864
- 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
+ );
4865
4764
  const filteredSlashCommands = useMemo(() => {
4866
4765
  if (!showSlashCommands) return [];
4867
4766
  if (!slashQuery) return FALLBACK_SLASH_COMMANDS;
4868
- 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
+ );
4869
4770
  }, [showSlashCommands, slashQuery]);
4870
4771
  const setPromptRef = useCallback(
4871
4772
  (element) => {
@@ -4886,6 +4787,9 @@ function ChatComposerComponent({
4886
4787
  promptRef.current?.focus();
4887
4788
  });
4888
4789
  }, []);
4790
+ const showMicError = useCallback((message) => {
4791
+ setMicError(message);
4792
+ }, []);
4889
4793
  const reset = useCallback(() => {
4890
4794
  setValue("");
4891
4795
  setAttachments([]);
@@ -4911,66 +4815,86 @@ ${template}`;
4911
4815
  body: JSON.stringify({ path: "/uploads" })
4912
4816
  });
4913
4817
  }, []);
4914
- const uploadAttachmentFile = useCallback(async (file) => {
4915
- const id = crypto.randomUUID();
4916
- if (!isAcceptedNonImage(file)) {
4917
- return {
4918
- id,
4919
- file,
4920
- preview: null,
4921
- type: "file",
4922
- base64: null,
4923
- error: "Unsupported file type for upload."
4924
- };
4925
- }
4926
- try {
4927
- await ensureUploadsDirectory();
4928
- const formData = new FormData();
4929
- formData.append("path", "/uploads");
4930
- formData.append("file", file);
4931
- const response = await fetch("/api/files/upload", {
4932
- method: "POST",
4933
- body: formData
4934
- });
4935
- if (!response.ok) {
4936
- const message = await response.text();
4937
- 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
+ };
4938
4830
  }
4939
- const data = await response.json();
4940
- const uploadedPath = data.files?.[0]?.path;
4941
- if (!uploadedPath) {
4942
- 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
+ };
4943
4867
  }
4944
- appendUploadedFilePrompt(uploadedPath);
4945
- return {
4946
- id,
4947
- file,
4948
- preview: null,
4949
- type: "file",
4950
- base64: null,
4951
- uploadedPath
4952
- };
4953
- } catch (err) {
4954
- return {
4955
- id,
4956
- file,
4957
- preview: null,
4958
- type: "file",
4959
- base64: null,
4960
- error: err instanceof Error ? err.message : "File upload failed"
4961
- };
4962
- }
4963
- }, [appendUploadedFilePrompt, ensureUploadsDirectory]);
4964
- const handleFilesSelect = useCallback(async (files) => {
4965
- if (files.length === 0) return;
4966
- const nextAttachments = await Promise.all(
4967
- files.map((file) => isAcceptedImage(file) ? createAttachmentFromFile(file) : uploadAttachmentFile(file))
4968
- );
4969
- setAttachments((prev) => [...prev, ...nextAttachments]);
4970
- focusPrompt();
4971
- }, [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
+ );
4972
4884
  const handleRemoveAttachment = useCallback((id) => {
4973
- 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
+ }
4974
4898
  }, []);
4975
4899
  const handleDragOver = useCallback((event) => {
4976
4900
  const hasFiles = Array.from(event.dataTransfer.types).includes("Files");
@@ -4985,13 +4909,16 @@ ${template}`;
4985
4909
  if (nextTarget && event.currentTarget.contains(nextTarget)) return;
4986
4910
  setIsDragActive(false);
4987
4911
  }, []);
4988
- const handleDrop = useCallback(async (event) => {
4989
- event.preventDefault();
4990
- setIsDragActive(false);
4991
- const files = Array.from(event.dataTransfer.files ?? []);
4992
- if (files.length === 0) return;
4993
- await handleFilesSelect(files);
4994
- }, [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
+ );
4995
4922
  const setComposerValue = useCallback(
4996
4923
  (nextValue) => {
4997
4924
  setValue(nextValue);
@@ -5018,14 +4945,22 @@ ${template}`;
5018
4945
  const handleSubmit = useCallback(() => {
5019
4946
  if (disabled) return;
5020
4947
  if (showSlashCommands && filteredSlashCommands.length > 0) {
5021
- const nextIndex = Math.min(selectedCommandIndex, filteredSlashCommands.length - 1);
4948
+ const nextIndex = Math.min(
4949
+ selectedCommandIndex,
4950
+ filteredSlashCommands.length - 1
4951
+ );
5022
4952
  selectSlashCommand(filteredSlashCommands[nextIndex]);
5023
4953
  return;
5024
4954
  }
5025
4955
  const body = value.trim();
5026
4956
  const validAttachments2 = attachments.filter((a) => !a.error && a.base64);
5027
4957
  if (body.length === 0 && validAttachments2.length === 0) return;
5028
- 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
+ });
5029
4964
  focusPrompt();
5030
4965
  }, [
5031
4966
  disabled,
@@ -5043,7 +4978,12 @@ ${template}`;
5043
4978
  ]);
5044
4979
  const handlePersonaSelect = useCallback(
5045
4980
  (command) => {
5046
- 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
+ });
5047
4987
  focusPrompt();
5048
4988
  },
5049
4989
  [focusPrompt, onSubmit, reset, setComposerValue, selectedModel]
@@ -5060,21 +5000,33 @@ ${template}`;
5060
5000
  if (filteredSlashCommands.length === 0) return;
5061
5001
  if (event.key === "ArrowDown") {
5062
5002
  event.preventDefault();
5063
- setSelectedCommandIndex((prev) => (prev + 1) % filteredSlashCommands.length);
5003
+ setSelectedCommandIndex(
5004
+ (prev) => (prev + 1) % filteredSlashCommands.length
5005
+ );
5064
5006
  return;
5065
5007
  }
5066
5008
  if (event.key === "ArrowUp") {
5067
5009
  event.preventDefault();
5068
- setSelectedCommandIndex((prev) => (prev - 1 + filteredSlashCommands.length) % filteredSlashCommands.length);
5010
+ setSelectedCommandIndex(
5011
+ (prev) => (prev - 1 + filteredSlashCommands.length) % filteredSlashCommands.length
5012
+ );
5069
5013
  return;
5070
5014
  }
5071
5015
  if (event.key === "Tab") {
5072
5016
  event.preventDefault();
5073
- const nextIndex = Math.min(selectedCommandIndex, filteredSlashCommands.length - 1);
5017
+ const nextIndex = Math.min(
5018
+ selectedCommandIndex,
5019
+ filteredSlashCommands.length - 1
5020
+ );
5074
5021
  selectSlashCommand(filteredSlashCommands[nextIndex]);
5075
5022
  }
5076
5023
  },
5077
- [showSlashCommands, filteredSlashCommands, selectedCommandIndex, selectSlashCommand]
5024
+ [
5025
+ showSlashCommands,
5026
+ filteredSlashCommands,
5027
+ selectedCommandIndex,
5028
+ selectSlashCommand
5029
+ ]
5078
5030
  );
5079
5031
  useEffect(() => {
5080
5032
  return () => {
@@ -5109,11 +5061,12 @@ ${template}`;
5109
5061
  setRecordingTime(0);
5110
5062
  }, []);
5111
5063
  const startRecording = useCallback(async () => {
5064
+ setMicError(null);
5112
5065
  const provider = getSttProvider();
5113
5066
  if (provider === "browser") {
5114
5067
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
5115
5068
  if (!SpeechRecognition) {
5116
- alert("Web Speech API is not supported in this browser.");
5069
+ showMicError("Web Speech API is not supported in this browser.");
5117
5070
  return;
5118
5071
  }
5119
5072
  const recognition = new SpeechRecognition();
@@ -5145,6 +5098,7 @@ ${template}`;
5145
5098
  setRecordingTime(0);
5146
5099
  if (recordingTimerRef.current) clearInterval(recordingTimerRef.current);
5147
5100
  if (transcript.trim()) {
5101
+ setMicError(null);
5148
5102
  setValue((prev) => prev + (prev ? " " : "") + transcript.trim());
5149
5103
  focusPrompt();
5150
5104
  }
@@ -5153,6 +5107,9 @@ ${template}`;
5153
5107
  setIsRecording(false);
5154
5108
  setRecordingTime(0);
5155
5109
  if (recordingTimerRef.current) clearInterval(recordingTimerRef.current);
5110
+ showMicError(
5111
+ "Speech recognition failed. Try the Browser provider again or switch providers in Settings."
5112
+ );
5156
5113
  };
5157
5114
  recognition.start();
5158
5115
  return;
@@ -5168,29 +5125,42 @@ ${template}`;
5168
5125
  };
5169
5126
  recorder.onstop = async () => {
5170
5127
  stream.getTracks().forEach((t) => t.stop());
5171
- const audioBlob = new Blob(recordingChunksRef.current, { type: mimeType });
5128
+ const audioBlob = new Blob(recordingChunksRef.current, {
5129
+ type: mimeType
5130
+ });
5172
5131
  if (audioBlob.size === 0) return;
5173
5132
  setSttLoading(true);
5174
5133
  try {
5175
5134
  const formData = new FormData();
5176
- 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
+ );
5177
5140
  if (provider !== "auto") formData.append("provider", provider);
5178
5141
  sttAbortControllerRef.current?.abort();
5179
5142
  const controller = new AbortController();
5180
5143
  sttAbortControllerRef.current = controller;
5181
- 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
+ });
5182
5149
  const data = await res.json();
5183
5150
  if (data.ok && data.text) {
5151
+ setMicError(null);
5184
5152
  setValue((prev) => prev + (prev ? " " : "") + data.text);
5185
5153
  focusPrompt();
5186
5154
  } else if (!data.ok) {
5187
5155
  console.warn("STT failed:", data.error);
5188
- alert(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
+ );
5189
5159
  }
5190
5160
  } catch (err) {
5191
5161
  if (err instanceof Error && err.name === "AbortError") return;
5192
5162
  console.warn("STT request failed:", err);
5193
- alert("Could not reach speech-to-text service.");
5163
+ showMicError("Could not reach speech-to-text service.");
5194
5164
  } finally {
5195
5165
  setSttLoading(false);
5196
5166
  }
@@ -5211,20 +5181,28 @@ ${template}`;
5211
5181
  const msg = err instanceof Error ? err.message : String(err);
5212
5182
  if (msg.includes("NotAllowedError") || msg.includes("Permission")) {
5213
5183
  try {
5214
- const status = await navigator.permissions.query({ name: "microphone" });
5184
+ const status = await navigator.permissions.query({
5185
+ name: "microphone"
5186
+ });
5215
5187
  if (status.state === "denied") {
5216
- alert("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
+ );
5217
5191
  } else {
5218
- alert("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
+ );
5219
5195
  }
5220
5196
  } catch {
5221
- alert("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
+ );
5222
5200
  }
5223
5201
  } else {
5224
- alert("Could not access microphone: " + msg);
5202
+ showMicError("Could not access microphone: " + msg);
5225
5203
  }
5226
5204
  }
5227
- }, [getSttProvider, stopRecording, focusPrompt]);
5205
+ }, [getSttProvider, stopRecording, focusPrompt, showMicError]);
5228
5206
  const handleMicClick = useCallback(() => {
5229
5207
  if (isRecording) {
5230
5208
  stopRecording();
@@ -5260,11 +5238,23 @@ ${template}`;
5260
5238
  onRemove: handleRemoveAttachment
5261
5239
  }
5262
5240
  ),
5241
+ micError && /* @__PURE__ */ jsx(
5242
+ "div",
5243
+ {
5244
+ className: "mx-3 mt-3 rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700 dark:border-red-900/40 dark:bg-red-950/40 dark:text-red-200",
5245
+ role: "alert",
5246
+ "aria-live": "polite",
5247
+ children: micError
5248
+ }
5249
+ ),
5263
5250
  showSlashCommands && filteredSlashCommands.length > 0 && /* @__PURE__ */ jsx(
5264
5251
  SlashCommandMenu,
5265
5252
  {
5266
5253
  commands: filteredSlashCommands,
5267
- selectedIndex: Math.min(selectedCommandIndex, filteredSlashCommands.length - 1),
5254
+ selectedIndex: Math.min(
5255
+ selectedCommandIndex,
5256
+ filteredSlashCommands.length - 1
5257
+ ),
5268
5258
  onSelect: selectSlashCommand
5269
5259
  }
5270
5260
  ),
@@ -5278,10 +5268,14 @@ ${template}`;
5278
5268
  ),
5279
5269
  /* @__PURE__ */ jsxs(PromptInputActions, { className: "justify-between px-3", children: [
5280
5270
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
5281
- /* @__PURE__ */ jsx(ModelSelector, { onModelChange: setSelectedModel }),
5282
5271
  /* @__PURE__ */ jsx(ThinkingLevelSelector, {}),
5283
5272
  /* @__PURE__ */ jsx(PersonaPicker, { onSelect: handlePersonaSelect }),
5284
- /* @__PURE__ */ jsx(CommandHelp, { onCommandSelect: (cmd) => handleValueChange(cmd + " ") })
5273
+ /* @__PURE__ */ jsx(
5274
+ CommandHelp,
5275
+ {
5276
+ onCommandSelect: (cmd) => handleValueChange(cmd + " ")
5277
+ }
5278
+ )
5285
5279
  ] }),
5286
5280
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
5287
5281
  /* @__PURE__ */ jsx(
@@ -5291,26 +5285,32 @@ ${template}`;
5291
5285
  disabled
5292
5286
  }
5293
5287
  ),
5294
- /* @__PURE__ */ jsx(PromptInputAction, { tooltip: isRecording ? "Stop recording" : "Voice input", children: /* @__PURE__ */ jsx(
5295
- Button,
5288
+ /* @__PURE__ */ jsx(
5289
+ PromptInputAction,
5296
5290
  {
5297
- onClick: handleMicClick,
5298
- disabled: disabled || sttLoading,
5299
- size: "icon-sm",
5300
- variant: isRecording ? "destructive" : "ghost",
5301
- className: `rounded-full ${isRecording ? "animate-pulse" : ""}`,
5302
- "aria-label": isRecording ? "Stop recording" : "Voice input",
5303
- 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: [
5304
- /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-red-500" }),
5305
- /* @__PURE__ */ jsxs("span", { className: "text-xs tabular-nums", children: [
5306
- Math.floor(recordingTime / 60),
5307
- ":",
5308
- String(recordingTime % 60).padStart(2, "0")
5309
- ] }),
5310
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: StopIcon, size: 14, strokeWidth: 2 })
5311
- ] }) : /* @__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
+ )
5312
5312
  }
5313
- ) }),
5313
+ ),
5314
5314
  /* @__PURE__ */ jsx(PromptInputAction, { tooltip: "Send message", children: /* @__PURE__ */ jsx(
5315
5315
  Button,
5316
5316
  {
@@ -5546,10 +5546,25 @@ function useChatSessions({
5546
5546
  isNewChat,
5547
5547
  forcedSessionKey
5548
5548
  }) {
5549
+ const [isDocumentVisible, setIsDocumentVisible] = useState(() => {
5550
+ if (typeof document === "undefined") return true;
5551
+ return document.visibilityState === "visible";
5552
+ });
5553
+ useEffect(() => {
5554
+ if (typeof document === "undefined") return void 0;
5555
+ const handleVisibilityChange = () => {
5556
+ setIsDocumentVisible(document.visibilityState === "visible");
5557
+ };
5558
+ handleVisibilityChange();
5559
+ document.addEventListener("visibilitychange", handleVisibilityChange);
5560
+ return () => {
5561
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
5562
+ };
5563
+ }, []);
5549
5564
  const sessionsQuery = useQuery({
5550
5565
  queryKey: chatQueryKeys.sessions,
5551
5566
  queryFn: fetchSessions,
5552
- refetchInterval: 3e4
5567
+ refetchInterval: isDocumentVisible ? 3e4 : false
5553
5568
  });
5554
5569
  const sessions = useMemo(() => {
5555
5570
  const rawSessions = sessionsQuery.data ?? [];
@@ -6166,7 +6181,6 @@ function useSwipeGesture(options) {
6166
6181
  };
6167
6182
  }
6168
6183
  const ENABLED_KEY = "opencami-browser-notifications-enabled";
6169
- const ASKED_KEY = "opencami-browser-notifications-permission-asked";
6170
6184
  const NOTIFICATION_DEBOUNCE_MS = 5e3;
6171
6185
  function isSupported() {
6172
6186
  return typeof window !== "undefined" && "Notification" in window;
@@ -6199,10 +6213,6 @@ function useNotifications() {
6199
6213
  }
6200
6214
  if (nextEnabled && isSupported() && Notification.permission === "default") {
6201
6215
  await requestPermission();
6202
- try {
6203
- localStorage.setItem(ASKED_KEY, "true");
6204
- } catch {
6205
- }
6206
6216
  }
6207
6217
  }, [requestPermission]);
6208
6218
  const maybeNotifyAssistantMessage = useCallback((payload) => {
@@ -6223,32 +6233,6 @@ function useNotifications() {
6223
6233
  notification.close();
6224
6234
  };
6225
6235
  }, [enabled, navigate]);
6226
- useEffect(() => {
6227
- if (!isSupported()) return;
6228
- const onFirstInteraction = () => {
6229
- if (Notification.permission !== "default") return;
6230
- let asked = false;
6231
- try {
6232
- asked = localStorage.getItem(ASKED_KEY) === "true";
6233
- } catch {
6234
- asked = false;
6235
- }
6236
- if (asked) return;
6237
- void requestPermission().finally(() => {
6238
- try {
6239
- localStorage.setItem(ASKED_KEY, "true");
6240
- } catch {
6241
- }
6242
- });
6243
- };
6244
- const options = { once: true, passive: true };
6245
- window.addEventListener("pointerdown", onFirstInteraction, options);
6246
- window.addEventListener("keydown", onFirstInteraction, { once: true });
6247
- return () => {
6248
- window.removeEventListener("pointerdown", onFirstInteraction);
6249
- window.removeEventListener("keydown", onFirstInteraction);
6250
- };
6251
- }, [requestPermission]);
6252
6236
  useEffect(() => {
6253
6237
  const sync = () => setEnabledState(getNotificationsEnabled());
6254
6238
  window.addEventListener("storage", sync);
@@ -6267,7 +6251,7 @@ const KeyboardShortcutsDialog = lazy(
6267
6251
  }))
6268
6252
  );
6269
6253
  const SearchDialog = lazy(
6270
- () => import("./search-dialog-DtYc9e4N.js").then((m) => ({
6254
+ () => import("./search-dialog-CmI7naPN.js").then((m) => ({
6271
6255
  default: m.SearchDialog
6272
6256
  }))
6273
6257
  );