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.
- package/README.md +0 -1
- package/dist/client/assets/{CSPContext-KDlbpvjX.js → CSPContext-DI-5GAnQ.js} +1 -1
- package/dist/client/assets/{DirectionContext-DZNUWW6F.js → DirectionContext-CrIsc5n9.js} +1 -1
- package/dist/client/assets/_sessionKey-B4NZmxf3.js +21 -0
- package/dist/client/assets/agents-bptidK8z.js +2 -0
- package/dist/client/assets/agents-screen-6qdnPmx2.js +1 -0
- package/dist/client/assets/bots-BWpbaQ-E.js +2 -0
- package/dist/client/assets/{bots-screen-CvbAVMk0.js → bots-screen-BTKCOohV.js} +1 -1
- package/dist/client/assets/button-8ab4wOwy.js +1 -0
- package/dist/client/assets/{composite-t7Eu0qB0.js → composite-B2qsrzf3.js} +1 -1
- package/dist/client/assets/{connect-Btonx7l6.js → connect-B3_p7C4I.js} +1 -1
- package/dist/client/assets/dashboard-BtClHYpn.js +1 -0
- package/dist/client/assets/event-DG3RKJz8.js +1 -0
- package/dist/client/assets/file-explorer-screen-Djl8x-8P.js +1 -0
- package/dist/client/assets/files-CjbCJDgC.js +2 -0
- package/dist/client/assets/follow-up-suggestions-BSCMXRXh.js +5 -0
- package/dist/client/assets/{index-nheqha2z.js → index-BXiha-Vz.js} +1 -1
- package/dist/client/assets/{index-yPOt98v7.js → index-CtlYu8Ug.js} +1 -1
- package/dist/client/assets/keyboard-shortcuts-dialog-HAufCn9C.js +1 -0
- package/dist/client/assets/main-CQKtcNr3.js +210 -0
- package/dist/client/assets/markdown-DFJF-FsV.js +87 -0
- package/dist/client/assets/memory-DnJOmcwU.js +2 -0
- package/dist/client/assets/memory-screen-Bm4NMAnU.js +1 -0
- package/dist/client/assets/menu-D26Vmgxl.js +1 -0
- package/dist/client/assets/{opencami-logo-CoCA2JIf.js → opencami-logo-BSed2Wez.js} +1 -1
- package/dist/client/assets/popupStateMapping-DkI2OCkW.js +1 -0
- package/dist/client/assets/proxy-CHQ-VCN1.js +9 -0
- package/dist/client/assets/{react-DzPMbcP8.js → react-WkSlhZJd.js} +1 -1
- package/dist/client/assets/search-dialog-CCl4d0Pi.js +1 -0
- package/dist/client/assets/{search-sources-badge-DOd5AI-i.js → search-sources-badge-Du8KpUEb.js} +1 -1
- package/dist/client/assets/session-export-dialog-io9FvLKq.js +1 -0
- package/dist/client/assets/settings-dialog-B93qswor.js +1 -0
- package/dist/client/assets/skills-BNDGnHwM.js +2 -0
- package/dist/client/assets/{skills-panel-_M5k5dty.js → skills-panel-CVh1I-7D.js} +1 -1
- package/dist/client/assets/styles-Ce2xZzc4.css +1 -0
- package/dist/client/assets/switch-CSnzINDW.js +1 -0
- package/dist/client/assets/tabs-CWfn44FL.js +1 -0
- package/dist/client/assets/thinking-BmoLlbFC.js +1 -0
- package/dist/client/assets/tooltip-CSGMH2t4.js +1 -0
- package/dist/client/assets/use-file-explorer-state-BYVzjwPA.js +12 -0
- package/dist/client/assets/{useBaseUiId-CMJ8I9al.js → useBaseUiId-DiwX_3so.js} +1 -1
- package/dist/client/assets/{useCompositeItem-CSriPK0T.js → useCompositeItem-UPIPwR9H.js} +1 -1
- package/dist/client/assets/{useControlled-KFa25CcD.js → useControlled-CT2hRlcU.js} +1 -1
- package/dist/client/assets/{useMutation-Bd89JWif.js → useMutation-rx8UH99I.js} +1 -1
- package/dist/client/assets/{useQuery-DhR_UXNX.js → useQuery-Boaa6oF3.js} +1 -1
- package/dist/server/assets/{_sessionKey-BzM-igv7.js → _sessionKey-B6iYeyCS.js} +379 -395
- package/dist/server/assets/{_tanstack-start-manifest_v-Bfnqdswf.js → _tanstack-start-manifest_v-C9chPgNH.js} +1 -1
- package/dist/server/assets/{dashboard-GCKodTiJ.js → dashboard-UYRCu_mQ.js} +19 -3
- package/dist/server/assets/{follow-up-suggestions-DhBZIszK.js → follow-up-suggestions-mzRQIB0k.js} +2 -2
- package/dist/server/assets/{index-DCMpnyEo.js → index-COElhwGA.js} +1 -1
- package/dist/server/assets/{router-BqPDQeCx.js → router-BqLGFd4L.js} +4 -4
- package/dist/server/assets/{search-dialog-DtYc9e4N.js → search-dialog-CmI7naPN.js} +2 -2
- package/dist/server/assets/{settings-dialog-BQzn6fyF.js → settings-dialog-BZ67gr9N.js} +2 -2
- package/dist/server/assets/{thinking-D6q1tJkk.js → thinking-CA8PSwKJ.js} +2 -2
- package/dist/server/server.js +38 -195
- package/package.json +3 -7
- package/dist/client/assets/_sessionKey-CPRycepq.js +0 -19
- package/dist/client/assets/agents-Bp6Cn1BH.js +0 -2
- package/dist/client/assets/agents-screen-Du8b4SRW.js +0 -1
- package/dist/client/assets/bots-Dl65yZDx.js +0 -2
- package/dist/client/assets/button-BxDVif49.js +0 -1
- package/dist/client/assets/dashboard-Dz6Uyyke.js +0 -1
- package/dist/client/assets/event-DeooaZzN.js +0 -1
- package/dist/client/assets/file-explorer-screen-BnOINWSL.js +0 -1
- package/dist/client/assets/files-Cf87gXsq.js +0 -2
- package/dist/client/assets/follow-up-suggestions-DP_84nA2.js +0 -5
- package/dist/client/assets/keyboard-shortcuts-dialog-BzL7ZJUb.js +0 -1
- package/dist/client/assets/main-DJgQwVqD.js +0 -210
- package/dist/client/assets/markdown-CFbW7h0Q.js +0 -87
- package/dist/client/assets/memory-kYdhgbRR.js +0 -2
- package/dist/client/assets/memory-screen-BKYBnY-V.js +0 -1
- package/dist/client/assets/menu-CnihCZ4y.js +0 -1
- package/dist/client/assets/proxy-C2vtPufK.js +0 -9
- package/dist/client/assets/search-dialog-CDAdFAQI.js +0 -1
- package/dist/client/assets/session-export-dialog-Cf8eisLM.js +0 -1
- package/dist/client/assets/settings-dialog-CB1OiRWU.js +0 -1
- package/dist/client/assets/skills-D6MaebJh.js +0 -2
- package/dist/client/assets/styles-BNkOWk4R.css +0 -1
- package/dist/client/assets/switch-LzFh8co4.js +0 -1
- package/dist/client/assets/tabs-D9o7Irvl.js +0 -1
- package/dist/client/assets/thinking-BqZoM6BP.js +0 -1
- package/dist/client/assets/tooltip-D64CzFXa.js +0 -1
- package/dist/client/assets/use-file-explorer-state-Tzex8EEL.js +0 -12
- 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,
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
{
|
|
4680
|
+
{
|
|
4681
|
+
command: "/usage",
|
|
4682
|
+
description: "Toggle usage footer (off/tokens/full/cost)"
|
|
4683
|
+
},
|
|
4813
4684
|
// Directives
|
|
4814
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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(
|
|
4864
|
-
|
|
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(
|
|
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(
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
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
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
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
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
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
|
-
|
|
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(
|
|
4989
|
-
event
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
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(
|
|
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, {
|
|
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, {
|
|
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(
|
|
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(
|
|
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(
|
|
5017
|
+
const nextIndex = Math.min(
|
|
5018
|
+
selectedCommandIndex,
|
|
5019
|
+
filteredSlashCommands.length - 1
|
|
5020
|
+
);
|
|
5074
5021
|
selectSlashCommand(filteredSlashCommands[nextIndex]);
|
|
5075
5022
|
}
|
|
5076
5023
|
},
|
|
5077
|
-
[
|
|
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
|
-
|
|
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, {
|
|
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(
|
|
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", {
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
5184
|
+
const status = await navigator.permissions.query({
|
|
5185
|
+
name: "microphone"
|
|
5186
|
+
});
|
|
5215
5187
|
if (status.state === "denied") {
|
|
5216
|
-
|
|
5188
|
+
showMicError(
|
|
5189
|
+
"Microphone access is blocked. Please enable it in your browser/app settings."
|
|
5190
|
+
);
|
|
5217
5191
|
} else {
|
|
5218
|
-
|
|
5192
|
+
showMicError(
|
|
5193
|
+
"Microphone permission was not granted. Please try again and allow access when prompted."
|
|
5194
|
+
);
|
|
5219
5195
|
}
|
|
5220
5196
|
} catch {
|
|
5221
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
5295
|
-
|
|
5288
|
+
/* @__PURE__ */ jsx(
|
|
5289
|
+
PromptInputAction,
|
|
5296
5290
|
{
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
":",
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
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-
|
|
6254
|
+
() => import("./search-dialog-CmI7naPN.js").then((m) => ({
|
|
6271
6255
|
default: m.SearchDialog
|
|
6272
6256
|
}))
|
|
6273
6257
|
);
|