opencami 1.8.6 → 1.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/{CSPContext-6t3O1emU.js → CSPContext-KDlbpvjX.js} +1 -1
- package/dist/client/assets/{DirectionContext-C6goXEY_.js → DirectionContext-DZNUWW6F.js} +1 -1
- package/dist/client/assets/_sessionKey-CPRycepq.js +19 -0
- package/dist/client/assets/agents-Bp6Cn1BH.js +2 -0
- package/dist/client/assets/{agents-screen-pHUzJxX5.js → agents-screen-Du8b4SRW.js} +1 -1
- package/dist/client/assets/bots-Dl65yZDx.js +2 -0
- package/dist/client/assets/bots-screen-CvbAVMk0.js +1 -0
- package/dist/client/assets/{button-CK8tKQ-Z.js → button-BxDVif49.js} +1 -1
- package/dist/client/assets/{composite-feK0c-xF.js → composite-t7Eu0qB0.js} +1 -1
- package/dist/client/assets/{connect-02tmQV_v.js → connect-Btonx7l6.js} +1 -1
- package/dist/client/assets/{dashboard-DQ0zDQKd.js → dashboard-Dz6Uyyke.js} +1 -1
- package/dist/client/assets/{event-BsD1rqGT.js → event-DeooaZzN.js} +1 -1
- package/dist/client/assets/{file-explorer-screen-Ds7LeJTd.js → file-explorer-screen-BnOINWSL.js} +1 -1
- package/dist/client/assets/files-Cf87gXsq.js +2 -0
- package/dist/client/assets/follow-up-suggestions-DP_84nA2.js +5 -0
- package/dist/client/assets/index-nheqha2z.js +3 -0
- package/dist/client/assets/{index-lK3yGoTI.js → index-yPOt98v7.js} +1 -1
- package/dist/client/assets/{keyboard-shortcuts-dialog-Bb_GOr9L.js → keyboard-shortcuts-dialog-BzL7ZJUb.js} +1 -1
- package/dist/client/assets/{main-Dq6jpr6-.js → main-DJgQwVqD.js} +3 -3
- package/dist/client/assets/{markdown-C7_Aipwd.js → markdown-CFbW7h0Q.js} +14 -14
- package/dist/client/assets/memory-kYdhgbRR.js +2 -0
- package/dist/client/assets/{memory-screen-CUFBWsq5.js → memory-screen-BKYBnY-V.js} +1 -1
- package/dist/client/assets/{menu-n6L--M9R.js → menu-CnihCZ4y.js} +1 -1
- package/dist/client/assets/{opencami-logo-zuSBm5Br.js → opencami-logo-CoCA2JIf.js} +1 -1
- package/dist/client/assets/{proxy-BU8Bw1Vt.js → proxy-C2vtPufK.js} +1 -1
- package/dist/client/assets/{react-BLyCEWpN.js → react-DzPMbcP8.js} +1 -1
- package/dist/client/assets/{search-dialog-yB4w5ajo.js → search-dialog-CDAdFAQI.js} +1 -1
- package/dist/client/assets/search-sources-badge-DOd5AI-i.js +1 -0
- package/dist/client/assets/{session-export-dialog-qbZgd2Zo.js → session-export-dialog-Cf8eisLM.js} +1 -1
- package/dist/client/assets/{settings-dialog-CHJbvpgk.js → settings-dialog-CB1OiRWU.js} +1 -1
- package/dist/client/assets/skills-D6MaebJh.js +2 -0
- package/dist/client/assets/{skills-panel-BH27r3nC.js → skills-panel-_M5k5dty.js} +1 -1
- package/dist/client/assets/styles-BNkOWk4R.css +1 -0
- package/dist/client/assets/{switch-BD3a0LRm.js → switch-LzFh8co4.js} +1 -1
- package/dist/client/assets/{tabs-DI1e-kzz.js → tabs-D9o7Irvl.js} +1 -1
- package/dist/client/assets/thinking-BqZoM6BP.js +1 -0
- package/dist/client/assets/{tooltip-BbH3QWvK.js → tooltip-D64CzFXa.js} +1 -1
- package/dist/client/assets/{use-file-explorer-state-DBfLeAyz.js → use-file-explorer-state-Tzex8EEL.js} +2 -2
- package/dist/client/assets/{useBaseUiId-MgM4ouhx.js → useBaseUiId-CMJ8I9al.js} +1 -1
- package/dist/client/assets/useCompositeItem-CSriPK0T.js +1 -0
- package/dist/client/assets/{useControlled-BQxTgsOd.js → useControlled-KFa25CcD.js} +1 -1
- package/dist/client/assets/{useMutation-12DyB3Ox.js → useMutation-Bd89JWif.js} +1 -1
- package/dist/client/assets/{useOnFirstRender-7qoaK5sI.js → useOnFirstRender-DqnUCnnL.js} +1 -1
- package/dist/client/assets/{useQuery-Ctiljcrr.js → useQuery-DhR_UXNX.js} +1 -1
- package/dist/server/assets/{_sessionKey-DzsJfprr.js → _sessionKey-BzM-igv7.js} +405 -703
- package/dist/server/assets/{_tanstack-start-manifest_v-C5HBDfQB.js → _tanstack-start-manifest_v-Bfnqdswf.js} +1 -1
- package/dist/server/assets/{connect-CbgijWz4.js → connect-BNabuqpW.js} +1 -1
- package/dist/server/assets/follow-up-suggestions-DhBZIszK.js +336 -0
- package/dist/server/assets/{index-Dl2BOKP7.js → index-BEWnDAH6.js} +24 -5
- package/dist/server/assets/{index-BFHEmXpN.js → index-DCMpnyEo.js} +1 -1
- package/dist/server/assets/{markdown-BFE5y9YH.js → markdown-DoX5Q7qh.js} +50 -26
- package/dist/server/assets/{memory-BqZOoD7Q.js → memory-Cxu7i8ej.js} +1 -1
- package/dist/server/assets/{memory-screen-BK5phS8K.js → memory-screen-B5l1NZRY.js} +2 -2
- package/dist/server/assets/{router-BZPatFG9.js → router-BqPDQeCx.js} +5 -5
- package/dist/server/assets/{search-dialog-DQRkARXw.js → search-dialog-DtYc9e4N.js} +4 -4
- package/dist/server/assets/search-sources-badge-B0rAEDs_.js +106 -0
- package/dist/server/assets/{settings-dialog-Bc1ta26X.js → settings-dialog-BQzn6fyF.js} +4 -4
- package/dist/server/assets/thinking-D6q1tJkk.js +92 -0
- package/dist/server/server.js +2 -2
- package/package.json +1 -1
- package/dist/client/assets/_sessionKey-B5Viv43f.js +0 -23
- package/dist/client/assets/agents-BmE6QOwl.js +0 -2
- package/dist/client/assets/bots-BeOkwrXr.js +0 -2
- package/dist/client/assets/bots-screen-B79bAYvf.js +0 -1
- package/dist/client/assets/files-e40B1zFy.js +0 -2
- package/dist/client/assets/index-rljDU_1M.js +0 -3
- package/dist/client/assets/memory-C7UG-1le.js +0 -2
- package/dist/client/assets/skills-DoKPPhNY.js +0 -2
- package/dist/client/assets/styles-CXV5jZiD.css +0 -1
- package/dist/client/assets/useCompositeItem-OhltNFdZ.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,
|
|
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";
|
|
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";
|
|
@@ -14,12 +14,12 @@ import { Collapsible as Collapsible$1 } from "@base-ui/react/collapsible";
|
|
|
14
14
|
import { ScrollArea } from "@base-ui/react/scroll-area";
|
|
15
15
|
import { M as MenuRoot, a as MenuTrigger, b as MenuContent, c as MenuItem } from "./menu-D90CDTi2.js";
|
|
16
16
|
import { O as OpenCamiLogo, a as OpenCamiText } from "./opencami-logo-C-43FL3R.js";
|
|
17
|
-
import { M as Markdown } from "./markdown-
|
|
18
|
-
import { u as useChatSettings$1 } from "./index-
|
|
17
|
+
import { M as Markdown } from "./markdown-DoX5Q7qh.js";
|
|
18
|
+
import { u as useChatSettings$1 } from "./index-BEWnDAH6.js";
|
|
19
|
+
import { createPortal } from "react-dom";
|
|
19
20
|
import { create } from "zustand";
|
|
20
21
|
import { persist } from "zustand/middleware";
|
|
21
|
-
import {
|
|
22
|
-
import { a as Route } from "./router-BZPatFG9.js";
|
|
22
|
+
import { a as Route } from "./router-BqPDQeCx.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-BQzn6fyF.js").then((m) => ({ default: m.SettingsDialog }))
|
|
1685
1685
|
);
|
|
1686
1686
|
const SessionExportDialog = lazy(
|
|
1687
1687
|
() => import("./session-export-dialog-C53RRAah.js").then((m) => ({
|
|
@@ -2748,50 +2748,6 @@ function MessageContent({
|
|
|
2748
2748
|
);
|
|
2749
2749
|
return markdown ? /* @__PURE__ */ jsx(Markdown, { className: classNames, ...props, children }) : /* @__PURE__ */ jsx("div", { className: classNames, ...props, children });
|
|
2750
2750
|
}
|
|
2751
|
-
function useIsMobile() {
|
|
2752
|
-
const [isMobile, setIsMobile] = useState(
|
|
2753
|
-
() => typeof window !== "undefined" ? window.innerWidth < 768 : false
|
|
2754
|
-
);
|
|
2755
|
-
useEffect(() => {
|
|
2756
|
-
if (typeof window === "undefined") return;
|
|
2757
|
-
const mql = window.matchMedia("(max-width: 767px)");
|
|
2758
|
-
const handler = (e) => setIsMobile(e.matches);
|
|
2759
|
-
mql.addEventListener("change", handler);
|
|
2760
|
-
setIsMobile(mql.matches);
|
|
2761
|
-
return () => mql.removeEventListener("change", handler);
|
|
2762
|
-
}, []);
|
|
2763
|
-
return isMobile;
|
|
2764
|
-
}
|
|
2765
|
-
function Thinking({ content }) {
|
|
2766
|
-
const isMobile = useIsMobile();
|
|
2767
|
-
return /* @__PURE__ */ jsx("div", { className: "inline-flex flex-col", children: /* @__PURE__ */ jsxs(Collapsible, { defaultOpen: !isMobile, children: [
|
|
2768
|
-
/* @__PURE__ */ jsxs(
|
|
2769
|
-
CollapsibleTrigger,
|
|
2770
|
-
{
|
|
2771
|
-
render: /* @__PURE__ */ jsx(
|
|
2772
|
-
Button,
|
|
2773
|
-
{
|
|
2774
|
-
variant: "ghost",
|
|
2775
|
-
className: "h-auto gap-1.5 px-1.5 py-0.5 -mx-2"
|
|
2776
|
-
}
|
|
2777
|
-
),
|
|
2778
|
-
children: [
|
|
2779
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-primary-900", children: "Thinking" }),
|
|
2780
|
-
/* @__PURE__ */ jsx(
|
|
2781
|
-
HugeiconsIcon,
|
|
2782
|
-
{
|
|
2783
|
-
icon: ArrowDown01Icon,
|
|
2784
|
-
size: 14,
|
|
2785
|
-
strokeWidth: 1.5,
|
|
2786
|
-
className: "text-primary-900 transition-transform duration-150 group-data-panel-open:rotate-180"
|
|
2787
|
-
}
|
|
2788
|
-
)
|
|
2789
|
-
]
|
|
2790
|
-
}
|
|
2791
|
-
),
|
|
2792
|
-
/* @__PURE__ */ jsx(CollapsiblePanel, { children: /* @__PURE__ */ jsx("div", { className: "pt-1 mb-3", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-700 whitespace-pre-wrap", children: content }) }) })
|
|
2793
|
-
] }) });
|
|
2794
|
-
}
|
|
2795
2751
|
function Tool({ toolPart, defaultOpen = false }) {
|
|
2796
2752
|
const { state, input, output, toolCallId } = toolPart;
|
|
2797
2753
|
const serialize = (value, maxLength = 3200) => {
|
|
@@ -2871,99 +2827,16 @@ function Tool({ toolPart, defaultOpen = false }) {
|
|
|
2871
2827
|
] }) })
|
|
2872
2828
|
] }) });
|
|
2873
2829
|
}
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
}
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
{
|
|
2885
|
-
className: "flex items-center justify-center w-5 h-5 rounded-full bg-white dark:bg-zinc-800 border border-cyan-500/20 overflow-hidden",
|
|
2886
|
-
title: domain,
|
|
2887
|
-
children: /* @__PURE__ */ jsx(
|
|
2888
|
-
"img",
|
|
2889
|
-
{
|
|
2890
|
-
src: `https://www.google.com/s2/favicons?domain=${domain}&sz=32`,
|
|
2891
|
-
alt: "",
|
|
2892
|
-
className: "w-4 h-4 object-contain",
|
|
2893
|
-
loading: "lazy",
|
|
2894
|
-
onError: (e) => {
|
|
2895
|
-
const target = e.target;
|
|
2896
|
-
target.style.display = "none";
|
|
2897
|
-
const parent = target.parentElement;
|
|
2898
|
-
if (parent) {
|
|
2899
|
-
parent.textContent = domain.charAt(0).toUpperCase();
|
|
2900
|
-
parent.classList.add("text-[10px]", "font-medium", "text-cyan-400");
|
|
2901
|
-
}
|
|
2902
|
-
}
|
|
2903
|
-
}
|
|
2904
|
-
)
|
|
2905
|
-
}
|
|
2906
|
-
);
|
|
2907
|
-
}
|
|
2908
|
-
function SearchSourcesBadge({ sources }) {
|
|
2909
|
-
const [expanded, setExpanded] = useState(false);
|
|
2910
|
-
if (!sources.length) return null;
|
|
2911
|
-
const uniqueDomains = [...new Set(sources.map((s) => getDomain(s.url)))];
|
|
2912
|
-
return /* @__PURE__ */ jsxs("div", { className: "mt-2 w-full", children: [
|
|
2913
|
-
/* @__PURE__ */ jsxs(
|
|
2914
|
-
"button",
|
|
2915
|
-
{
|
|
2916
|
-
onClick: () => setExpanded(!expanded),
|
|
2917
|
-
className: cn(
|
|
2918
|
-
"inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs",
|
|
2919
|
-
"bg-cyan-500/10 hover:bg-cyan-500/20 border border-cyan-500/30",
|
|
2920
|
-
"text-cyan-300 transition-colors"
|
|
2921
|
-
),
|
|
2922
|
-
children: [
|
|
2923
|
-
/* @__PURE__ */ jsx(HugeiconsIcon, { icon: Search01Icon, size: 14 }),
|
|
2924
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium", children: "Sources" }),
|
|
2925
|
-
/* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 rounded-full bg-cyan-500/20 text-cyan-400 font-semibold", children: sources.length }),
|
|
2926
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 ml-1", children: [
|
|
2927
|
-
uniqueDomains.slice(0, 3).map((domain) => /* @__PURE__ */ jsx(FaviconCircle, { domain }, domain)),
|
|
2928
|
-
uniqueDomains.length > 3 && /* @__PURE__ */ jsxs("span", { className: "text-cyan-500/70 text-[10px] ml-0.5", children: [
|
|
2929
|
-
"+",
|
|
2930
|
-
uniqueDomains.length - 3
|
|
2931
|
-
] })
|
|
2932
|
-
] }),
|
|
2933
|
-
/* @__PURE__ */ jsx(
|
|
2934
|
-
HugeiconsIcon,
|
|
2935
|
-
{
|
|
2936
|
-
icon: ArrowRight01Icon,
|
|
2937
|
-
size: 12,
|
|
2938
|
-
className: cn("transition-transform", expanded && "rotate-90")
|
|
2939
|
-
}
|
|
2940
|
-
)
|
|
2941
|
-
]
|
|
2942
|
-
}
|
|
2943
|
-
),
|
|
2944
|
-
expanded && /* @__PURE__ */ jsx("div", { className: "mt-2 rounded-lg border border-cyan-500/20 bg-cyan-500/5 max-h-80 overflow-y-auto", children: sources.map((source, i) => {
|
|
2945
|
-
const domain = getDomain(source.url);
|
|
2946
|
-
return /* @__PURE__ */ jsxs(
|
|
2947
|
-
"a",
|
|
2948
|
-
{
|
|
2949
|
-
href: source.url,
|
|
2950
|
-
target: "_blank",
|
|
2951
|
-
rel: "noopener noreferrer",
|
|
2952
|
-
className: "flex items-start gap-2 p-2.5 border-b border-cyan-500/10 last:border-b-0 hover:bg-cyan-500/5 transition-colors",
|
|
2953
|
-
children: [
|
|
2954
|
-
/* @__PURE__ */ jsx(FaviconCircle, { domain }),
|
|
2955
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
2956
|
-
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium text-primary-900 hover:underline line-clamp-1", children: source.title || domain }),
|
|
2957
|
-
/* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: domain }),
|
|
2958
|
-
source.snippet && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground/80 line-clamp-2 mt-0.5", children: source.snippet })
|
|
2959
|
-
] })
|
|
2960
|
-
]
|
|
2961
|
-
},
|
|
2962
|
-
`${source.url}-${i}`
|
|
2963
|
-
);
|
|
2964
|
-
}) })
|
|
2965
|
-
] });
|
|
2966
|
-
}
|
|
2830
|
+
const Thinking = lazy(
|
|
2831
|
+
() => import("./thinking-D6q1tJkk.js").then((m) => ({
|
|
2832
|
+
default: m.Thinking
|
|
2833
|
+
}))
|
|
2834
|
+
);
|
|
2835
|
+
const SearchSourcesBadge = lazy(
|
|
2836
|
+
() => import("./search-sources-badge-B0rAEDs_.js").then((m) => ({
|
|
2837
|
+
default: m.SearchSourcesBadge
|
|
2838
|
+
}))
|
|
2839
|
+
);
|
|
2967
2840
|
function mapToolCallToToolPart(toolCall, resultMessage) {
|
|
2968
2841
|
const hasResult = resultMessage !== void 0;
|
|
2969
2842
|
const isError = resultMessage?.isError ?? false;
|
|
@@ -3145,10 +3018,19 @@ function MessageItemComponent({
|
|
|
3145
3018
|
const thinking = thinkingFromMessage(message);
|
|
3146
3019
|
const images = imagesFromMessage(message);
|
|
3147
3020
|
const isUser = role === "user";
|
|
3021
|
+
const imageDataUris = useMemo(
|
|
3022
|
+
() => images.map(
|
|
3023
|
+
(img) => `data:${img.source.media_type};base64,${img.source.data}`
|
|
3024
|
+
),
|
|
3025
|
+
[images]
|
|
3026
|
+
);
|
|
3148
3027
|
const timestamp = getMessageTimestamp(message);
|
|
3149
3028
|
const navigate = useNavigate();
|
|
3150
3029
|
const openInEditor = useFileExplorerState((state) => state.openInEditor);
|
|
3151
|
-
const uploadedFileRefs = useMemo(
|
|
3030
|
+
const uploadedFileRefs = useMemo(
|
|
3031
|
+
() => parseUploadedFileReferences(text),
|
|
3032
|
+
[text]
|
|
3033
|
+
);
|
|
3152
3034
|
const [fileSizes, setFileSizes] = useState({});
|
|
3153
3035
|
const displayText = useMemo(() => stripUploadedFileLines(text), [text]);
|
|
3154
3036
|
useEffect(() => {
|
|
@@ -3158,7 +3040,9 @@ function MessageItemComponent({
|
|
|
3158
3040
|
await Promise.all(
|
|
3159
3041
|
uploadedFileRefs.map(async (ref) => {
|
|
3160
3042
|
try {
|
|
3161
|
-
const response = await fetch(
|
|
3043
|
+
const response = await fetch(
|
|
3044
|
+
`/api/files/info?path=${encodeURIComponent(ref.path)}`
|
|
3045
|
+
);
|
|
3162
3046
|
if (!response.ok) {
|
|
3163
3047
|
nextSizes[ref.path] = null;
|
|
3164
3048
|
return;
|
|
@@ -3208,51 +3092,81 @@ function MessageItemComponent({
|
|
|
3208
3092
|
isUser ? "items-end" : "items-start"
|
|
3209
3093
|
),
|
|
3210
3094
|
children: [
|
|
3211
|
-
thinking && settings.showReasoningBlocks && /* @__PURE__ */ jsx("div", { className: "w-full max-w-[var(--opencami-chat-width)]", children: /* @__PURE__ */ jsx(Thinking, { content: thinking }) }),
|
|
3212
|
-
images.length > 0 && /* @__PURE__ */ jsx(
|
|
3213
|
-
"
|
|
3214
|
-
isUser ? "justify-end" : "justify-start"
|
|
3215
|
-
), children: images.map((img, idx) => /* @__PURE__ */ jsx(
|
|
3216
|
-
"img",
|
|
3217
|
-
{
|
|
3218
|
-
src: `data:${img.source.media_type};base64,${img.source.data}`,
|
|
3219
|
-
alt: `Attachment ${idx + 1}`,
|
|
3220
|
-
loading: "lazy",
|
|
3221
|
-
decoding: "async",
|
|
3222
|
-
className: "max-w-[300px] max-h-[300px] rounded-lg object-cover"
|
|
3223
|
-
},
|
|
3224
|
-
idx
|
|
3225
|
-
)) }),
|
|
3226
|
-
uploadedFileRefs.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("mb-2 flex w-full flex-col gap-2", isUser ? "items-end" : "items-start"), children: uploadedFileRefs.map((fileRef) => /* @__PURE__ */ jsxs(
|
|
3227
|
-
"button",
|
|
3095
|
+
thinking && settings.showReasoningBlocks && /* @__PURE__ */ jsx("div", { className: "w-full max-w-[var(--opencami-chat-width)]", children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(Thinking, { content: thinking }) }) }),
|
|
3096
|
+
images.length > 0 && /* @__PURE__ */ jsx(
|
|
3097
|
+
"div",
|
|
3228
3098
|
{
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3099
|
+
className: cn(
|
|
3100
|
+
"flex flex-wrap gap-2 mb-2",
|
|
3101
|
+
isUser ? "justify-end" : "justify-start"
|
|
3102
|
+
),
|
|
3103
|
+
children: images.map((img, idx) => /* @__PURE__ */ jsx(
|
|
3104
|
+
"img",
|
|
3105
|
+
{
|
|
3106
|
+
src: imageDataUris[idx],
|
|
3107
|
+
alt: `Attachment ${idx + 1}`,
|
|
3108
|
+
loading: "lazy",
|
|
3109
|
+
decoding: "async",
|
|
3110
|
+
fetchPriority: "low",
|
|
3111
|
+
className: "max-w-[300px] max-h-[300px] rounded-lg object-cover"
|
|
3112
|
+
},
|
|
3113
|
+
idx
|
|
3114
|
+
))
|
|
3115
|
+
}
|
|
3116
|
+
),
|
|
3117
|
+
uploadedFileRefs.length > 0 && /* @__PURE__ */ jsx(
|
|
3118
|
+
"div",
|
|
3246
3119
|
{
|
|
3247
|
-
markdown: !isUser,
|
|
3248
3120
|
className: cn(
|
|
3249
|
-
"
|
|
3250
|
-
isUser ? "
|
|
3251
|
-
!isUser && isStreaming && "stream-fade-in"
|
|
3121
|
+
"mb-2 flex w-full flex-col gap-2",
|
|
3122
|
+
isUser ? "items-end" : "items-start"
|
|
3252
3123
|
),
|
|
3253
|
-
children:
|
|
3124
|
+
children: uploadedFileRefs.map((fileRef) => /* @__PURE__ */ jsxs(
|
|
3125
|
+
"button",
|
|
3126
|
+
{
|
|
3127
|
+
type: "button",
|
|
3128
|
+
onClick: () => {
|
|
3129
|
+
void handleOpenFile(fileRef.path);
|
|
3130
|
+
},
|
|
3131
|
+
className: "flex max-w-full items-center gap-3 rounded-xl border border-primary-200 bg-primary-50 px-3 py-2 text-left hover:bg-primary-100",
|
|
3132
|
+
children: [
|
|
3133
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-primary-100", children: /* @__PURE__ */ jsx(
|
|
3134
|
+
HugeiconsIcon,
|
|
3135
|
+
{
|
|
3136
|
+
icon: File01Icon,
|
|
3137
|
+
size: 18,
|
|
3138
|
+
className: "text-primary-600"
|
|
3139
|
+
}
|
|
3140
|
+
) }),
|
|
3141
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
3142
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-primary-900", children: fileRef.filename }),
|
|
3143
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-primary-600", children: formatFileSize$1(fileSizes[fileRef.path] ?? null) })
|
|
3144
|
+
] })
|
|
3145
|
+
]
|
|
3146
|
+
},
|
|
3147
|
+
fileRef.path
|
|
3148
|
+
))
|
|
3254
3149
|
}
|
|
3255
|
-
)
|
|
3150
|
+
),
|
|
3151
|
+
/* @__PURE__ */ jsx(
|
|
3152
|
+
Message,
|
|
3153
|
+
{
|
|
3154
|
+
className: cn("min-w-0 max-w-full", isUser ? "flex-row-reverse" : ""),
|
|
3155
|
+
children: /* @__PURE__ */ jsx(
|
|
3156
|
+
MessageContent,
|
|
3157
|
+
{
|
|
3158
|
+
markdown: !isUser,
|
|
3159
|
+
isStreaming: !isUser && isStreaming,
|
|
3160
|
+
className: cn(
|
|
3161
|
+
"text-primary-900 opencami-text-size min-w-0 max-w-full",
|
|
3162
|
+
isUser ? "opencami-message-user bg-primary-100 px-4 py-[var(--opencami-user-bubble-py)] max-w-[85%]" : "opencami-message-assistant bg-transparent w-full",
|
|
3163
|
+
!isUser && isStreaming && "stream-fade-in"
|
|
3164
|
+
),
|
|
3165
|
+
children: displayText
|
|
3166
|
+
}
|
|
3167
|
+
)
|
|
3168
|
+
}
|
|
3169
|
+
),
|
|
3256
3170
|
hasToolCalls && settings.showToolMessages && /* @__PURE__ */ jsx("div", { className: "mt-2 flex w-full min-w-0 max-w-[var(--opencami-chat-width)] flex-col gap-3 overflow-x-hidden", children: toolCalls.map((toolCall) => {
|
|
3257
3171
|
const resultMessage = toolCall.id ? toolResultsByCallId?.get(toolCall.id) : void 0;
|
|
3258
3172
|
const toolPart = mapToolCallToToolPart(toolCall, resultMessage);
|
|
@@ -3265,7 +3179,7 @@ function MessageItemComponent({
|
|
|
3265
3179
|
toolCall.id || toolCall.name
|
|
3266
3180
|
);
|
|
3267
3181
|
}) }),
|
|
3268
|
-
searchSources.length > 0 && /* @__PURE__ */ jsx("div", { className: "w-full max-w-[var(--opencami-chat-width)]", children: /* @__PURE__ */ jsx(SearchSourcesBadge, { sources: searchSources }) }),
|
|
3182
|
+
searchSources.length > 0 && /* @__PURE__ */ jsx("div", { className: "w-full max-w-[var(--opencami-chat-width)]", children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(SearchSourcesBadge, { sources: searchSources }) }) }),
|
|
3269
3183
|
!hasToolCalls && /* @__PURE__ */ jsx(
|
|
3270
3184
|
MessageActionsBar,
|
|
3271
3185
|
{
|
|
@@ -3285,7 +3199,8 @@ function areMessagesEqual(prevProps, nextProps) {
|
|
|
3285
3199
|
}
|
|
3286
3200
|
if (prevProps.isStreaming !== nextProps.isStreaming) return false;
|
|
3287
3201
|
if (prevProps.isLastAssistant !== nextProps.isLastAssistant) return false;
|
|
3288
|
-
if (prevProps.aggregatedSearchSources !== nextProps.aggregatedSearchSources)
|
|
3202
|
+
if (prevProps.aggregatedSearchSources !== nextProps.aggregatedSearchSources)
|
|
3203
|
+
return false;
|
|
3289
3204
|
if (prevProps.wrapperClassName !== nextProps.wrapperClassName) return false;
|
|
3290
3205
|
if (prevProps.wrapperRef !== nextProps.wrapperRef) return false;
|
|
3291
3206
|
if (prevProps.wrapperScrollMarginTop !== nextProps.wrapperScrollMarginTop) {
|
|
@@ -3305,494 +3220,15 @@ function areMessagesEqual(prevProps, nextProps) {
|
|
|
3305
3220
|
if (toolCallsSignature(prevProps.message) !== toolCallsSignature(nextProps.message)) {
|
|
3306
3221
|
return false;
|
|
3307
3222
|
}
|
|
3308
|
-
if (toolResultsSignature(prevProps.message, prevProps.toolResultsByCallId) !== toolResultsSignature(nextProps.message, nextProps.toolResultsByCallId)) {
|
|
3309
|
-
return false;
|
|
3310
|
-
}
|
|
3311
|
-
if (rawTimestamp(prevProps.message) !== rawTimestamp(nextProps.message)) {
|
|
3312
|
-
return false;
|
|
3313
|
-
}
|
|
3314
|
-
return true;
|
|
3315
|
-
}
|
|
3316
|
-
const MemoizedMessageItem = memo(MessageItemComponent, areMessagesEqual);
|
|
3317
|
-
const CODE_PATTERNS = [
|
|
3318
|
-
/```[\s\S]*?```/g,
|
|
3319
|
-
/`[^`]+`/g,
|
|
3320
|
-
/\b(function|const|let|var|class|import|export|return|async|await)\b/g
|
|
3321
|
-
];
|
|
3322
|
-
function extractTopics(text) {
|
|
3323
|
-
const topics = [];
|
|
3324
|
-
const quoted = text.match(/"([^"]+)"|'([^']+)'/g);
|
|
3325
|
-
if (quoted) {
|
|
3326
|
-
topics.push(...quoted.map((q) => q.replace(/['"]/g, "")));
|
|
3327
|
-
}
|
|
3328
|
-
const capitalized = text.match(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b/g);
|
|
3329
|
-
if (capitalized) {
|
|
3330
|
-
topics.push(
|
|
3331
|
-
...capitalized.filter((t) => !["I", "The", "A", "An", "This"].includes(t))
|
|
3332
|
-
);
|
|
3333
|
-
}
|
|
3334
|
-
const backticked = text.match(/`([^`]+)`/g);
|
|
3335
|
-
if (backticked) {
|
|
3336
|
-
topics.push(...backticked.map((t) => t.replace(/`/g, "")));
|
|
3337
|
-
}
|
|
3338
|
-
return [...new Set(topics)].slice(0, 5);
|
|
3339
|
-
}
|
|
3340
|
-
function hasCode(text) {
|
|
3341
|
-
return CODE_PATTERNS.some((pattern) => pattern.test(text));
|
|
3342
|
-
}
|
|
3343
|
-
function hasList(text) {
|
|
3344
|
-
return /^[\s]*[-*•\d]+[.)\s]/m.test(text) || /\b(first|second|third|1\.|2\.|3\.)/i.test(text);
|
|
3345
|
-
}
|
|
3346
|
-
function hasAlternatives(text) {
|
|
3347
|
-
return /\b(alternatively|another|other option|could also|you might|consider)\b/i.test(text);
|
|
3348
|
-
}
|
|
3349
|
-
function isExplanatory(text) {
|
|
3350
|
-
return /\b(because|since|due to|reason|why|how|what|when|where)\b/i.test(text);
|
|
3351
|
-
}
|
|
3352
|
-
function hasCaveats(text) {
|
|
3353
|
-
return /\b(however|but|note|warning|caution|careful|important|keep in mind|be aware)\b/i.test(text);
|
|
3354
|
-
}
|
|
3355
|
-
function generateHeuristicFollowUps(responseText) {
|
|
3356
|
-
const suggestions = [];
|
|
3357
|
-
const topics = extractTopics(responseText);
|
|
3358
|
-
if (hasCode(responseText)) {
|
|
3359
|
-
suggestions.push({
|
|
3360
|
-
text: "Can you explain this code step by step?",
|
|
3361
|
-
type: "clarify"
|
|
3362
|
-
});
|
|
3363
|
-
suggestions.push({
|
|
3364
|
-
text: "How would I modify this for my use case?",
|
|
3365
|
-
type: "actionable"
|
|
3366
|
-
});
|
|
3367
|
-
}
|
|
3368
|
-
if (hasList(responseText)) {
|
|
3369
|
-
suggestions.push({
|
|
3370
|
-
text: "Can you elaborate on the first point?",
|
|
3371
|
-
type: "expand"
|
|
3372
|
-
});
|
|
3373
|
-
suggestions.push({
|
|
3374
|
-
text: "Which of these is most important to start with?",
|
|
3375
|
-
type: "actionable"
|
|
3376
|
-
});
|
|
3377
|
-
}
|
|
3378
|
-
if (hasAlternatives(responseText)) {
|
|
3379
|
-
suggestions.push({
|
|
3380
|
-
text: "What are the pros and cons of each approach?",
|
|
3381
|
-
type: "alternative"
|
|
3382
|
-
});
|
|
3383
|
-
suggestions.push({
|
|
3384
|
-
text: "Which option would you recommend and why?",
|
|
3385
|
-
type: "clarify"
|
|
3386
|
-
});
|
|
3387
|
-
}
|
|
3388
|
-
if (isExplanatory(responseText) && suggestions.length < 3) {
|
|
3389
|
-
suggestions.push({
|
|
3390
|
-
text: "Can you give me a practical example?",
|
|
3391
|
-
type: "example"
|
|
3392
|
-
});
|
|
3393
|
-
}
|
|
3394
|
-
if (hasCaveats(responseText) && suggestions.length < 3) {
|
|
3395
|
-
suggestions.push({
|
|
3396
|
-
text: "What potential issues should I watch out for?",
|
|
3397
|
-
type: "clarify"
|
|
3398
|
-
});
|
|
3399
|
-
}
|
|
3400
|
-
if (topics.length > 0 && suggestions.length < 3) {
|
|
3401
|
-
const topic = topics[0];
|
|
3402
|
-
suggestions.push({
|
|
3403
|
-
text: `Tell me more about ${topic}`,
|
|
3404
|
-
type: "expand"
|
|
3405
|
-
});
|
|
3406
|
-
}
|
|
3407
|
-
const fallbacks = [
|
|
3408
|
-
{ text: "Can you give me a concrete example?", type: "example" },
|
|
3409
|
-
{ text: "What would be the next steps?", type: "actionable" },
|
|
3410
|
-
{ text: "How does this work in practice?", type: "expand" },
|
|
3411
|
-
{ text: "Are there any common mistakes to avoid?", type: "clarify" },
|
|
3412
|
-
{ text: "Can you simplify this explanation?", type: "clarify" }
|
|
3413
|
-
];
|
|
3414
|
-
const usedTypes = new Set(suggestions.map((s) => s.type));
|
|
3415
|
-
for (const fallback of fallbacks) {
|
|
3416
|
-
if (suggestions.length >= 3) break;
|
|
3417
|
-
if (!usedTypes.has(fallback.type)) {
|
|
3418
|
-
suggestions.push(fallback);
|
|
3419
|
-
usedTypes.add(fallback.type);
|
|
3420
|
-
}
|
|
3421
|
-
}
|
|
3422
|
-
for (const fallback of fallbacks) {
|
|
3423
|
-
if (suggestions.length >= 3) break;
|
|
3424
|
-
if (!suggestions.some((s) => s.text === fallback.text)) {
|
|
3425
|
-
suggestions.push(fallback);
|
|
3426
|
-
}
|
|
3427
|
-
}
|
|
3428
|
-
return suggestions.slice(0, 3);
|
|
3429
|
-
}
|
|
3430
|
-
function getHeuristicFollowUpTexts(responseText) {
|
|
3431
|
-
return generateHeuristicFollowUps(responseText).map((s) => s.text);
|
|
3432
|
-
}
|
|
3433
|
-
const LLM_PROVIDER_DEFAULTS = {
|
|
3434
|
-
openai: {
|
|
3435
|
-
baseUrl: "https://api.openai.com/v1",
|
|
3436
|
-
model: "gpt-4.1-nano"
|
|
3437
|
-
},
|
|
3438
|
-
openrouter: {
|
|
3439
|
-
baseUrl: "https://openrouter.ai/api/v1",
|
|
3440
|
-
model: "openai/gpt-oss-120b"
|
|
3441
|
-
},
|
|
3442
|
-
kilocode: {
|
|
3443
|
-
baseUrl: "https://api.kilo.ai/api/gateway",
|
|
3444
|
-
model: "google/gemini-2.5-flash"
|
|
3445
|
-
},
|
|
3446
|
-
ollama: {
|
|
3447
|
-
baseUrl: "http://localhost:11434/v1",
|
|
3448
|
-
model: "llama3.2"
|
|
3449
|
-
},
|
|
3450
|
-
custom: {
|
|
3451
|
-
baseUrl: "",
|
|
3452
|
-
model: ""
|
|
3453
|
-
}
|
|
3454
|
-
};
|
|
3455
|
-
const DEFAULT_LLM_SETTINGS = {
|
|
3456
|
-
useLlmTitles: true,
|
|
3457
|
-
useLlmFollowUps: true,
|
|
3458
|
-
llmProvider: "openai",
|
|
3459
|
-
llmBaseUrl: "",
|
|
3460
|
-
llmModel: "",
|
|
3461
|
-
llmApiKey: ""
|
|
3462
|
-
};
|
|
3463
|
-
function getLlmProviderDefaults(provider) {
|
|
3464
|
-
return LLM_PROVIDER_DEFAULTS[provider];
|
|
3465
|
-
}
|
|
3466
|
-
function getEffectiveLlmBaseUrl(settings) {
|
|
3467
|
-
if (settings.llmBaseUrl.trim()) return settings.llmBaseUrl.trim();
|
|
3468
|
-
return getLlmProviderDefaults(settings.llmProvider).baseUrl;
|
|
3469
|
-
}
|
|
3470
|
-
function getEffectiveLlmModel(settings) {
|
|
3471
|
-
if (settings.llmModel.trim()) return settings.llmModel.trim();
|
|
3472
|
-
return getLlmProviderDefaults(settings.llmProvider).model;
|
|
3473
|
-
}
|
|
3474
|
-
function getAvailability(settings, hasEnvKey) {
|
|
3475
|
-
if (settings.llmProvider === "ollama") return true;
|
|
3476
|
-
if (settings.llmProvider === "custom") {
|
|
3477
|
-
return Boolean(settings.llmApiKey.trim()) || Boolean(settings.llmBaseUrl.trim() && settings.llmModel.trim());
|
|
3478
|
-
}
|
|
3479
|
-
return hasEnvKey || Boolean(settings.llmApiKey.trim());
|
|
3480
|
-
}
|
|
3481
|
-
function migratePersistedState(persistedState) {
|
|
3482
|
-
if (!persistedState || typeof persistedState !== "object") {
|
|
3483
|
-
return { settings: DEFAULT_LLM_SETTINGS };
|
|
3484
|
-
}
|
|
3485
|
-
const { settings } = persistedState;
|
|
3486
|
-
if (!settings) {
|
|
3487
|
-
return { settings: DEFAULT_LLM_SETTINGS };
|
|
3488
|
-
}
|
|
3489
|
-
const { openaiApiKey, ...rest } = settings;
|
|
3490
|
-
const llmApiKey = rest.llmApiKey ?? openaiApiKey ?? "";
|
|
3491
|
-
return {
|
|
3492
|
-
settings: {
|
|
3493
|
-
...DEFAULT_LLM_SETTINGS,
|
|
3494
|
-
...rest,
|
|
3495
|
-
llmApiKey
|
|
3496
|
-
}
|
|
3497
|
-
};
|
|
3498
|
-
}
|
|
3499
|
-
const useLlmSettingsStore = create()(
|
|
3500
|
-
persist(
|
|
3501
|
-
(set) => ({
|
|
3502
|
-
settings: {
|
|
3503
|
-
...DEFAULT_LLM_SETTINGS
|
|
3504
|
-
},
|
|
3505
|
-
updateSettings: (updates) => set((state) => ({
|
|
3506
|
-
settings: { ...state.settings, ...updates }
|
|
3507
|
-
})),
|
|
3508
|
-
clearApiKey: () => set((state) => ({
|
|
3509
|
-
settings: { ...state.settings, llmApiKey: "" }
|
|
3510
|
-
}))
|
|
3511
|
-
}),
|
|
3512
|
-
{
|
|
3513
|
-
name: "llm-settings",
|
|
3514
|
-
version: 2,
|
|
3515
|
-
migrate: (persistedState) => migratePersistedState(persistedState)
|
|
3516
|
-
}
|
|
3517
|
-
)
|
|
3518
|
-
);
|
|
3519
|
-
function useLlmSettings() {
|
|
3520
|
-
const settings = useLlmSettingsStore((state) => state.settings);
|
|
3521
|
-
const updateSettings = useLlmSettingsStore((state) => state.updateSettings);
|
|
3522
|
-
const clearApiKey = useLlmSettingsStore((state) => state.clearApiKey);
|
|
3523
|
-
const [status, setStatus] = useState({
|
|
3524
|
-
hasEnvKey: false,
|
|
3525
|
-
hasOpenRouterKey: false,
|
|
3526
|
-
hasKilocodeKey: false,
|
|
3527
|
-
hasUserKey: Boolean(settings.llmApiKey),
|
|
3528
|
-
isAvailable: getAvailability(settings, false),
|
|
3529
|
-
isLoading: true,
|
|
3530
|
-
error: null
|
|
3531
|
-
});
|
|
3532
|
-
useEffect(() => {
|
|
3533
|
-
let cancelled = false;
|
|
3534
|
-
async function checkStatus() {
|
|
3535
|
-
try {
|
|
3536
|
-
const res = await fetch("/api/llm-features");
|
|
3537
|
-
if (!res.ok) throw new Error("Failed to check LLM status");
|
|
3538
|
-
const data = await res.json();
|
|
3539
|
-
if (cancelled) return;
|
|
3540
|
-
const hasUserKey = Boolean(settings.llmApiKey);
|
|
3541
|
-
const hasProviderKey = settings.llmProvider === "openrouter" ? Boolean(data.hasOpenRouterKey) : settings.llmProvider === "kilocode" ? Boolean(data.hasKilocodeKey) : data.hasEnvKey;
|
|
3542
|
-
setStatus({
|
|
3543
|
-
hasEnvKey: data.hasEnvKey,
|
|
3544
|
-
hasOpenRouterKey: Boolean(data.hasOpenRouterKey),
|
|
3545
|
-
hasKilocodeKey: Boolean(data.hasKilocodeKey),
|
|
3546
|
-
hasUserKey,
|
|
3547
|
-
isAvailable: getAvailability(settings, hasProviderKey),
|
|
3548
|
-
isLoading: false,
|
|
3549
|
-
error: null
|
|
3550
|
-
});
|
|
3551
|
-
} catch (err) {
|
|
3552
|
-
if (cancelled) return;
|
|
3553
|
-
setStatus((prev) => ({
|
|
3554
|
-
...prev,
|
|
3555
|
-
isLoading: false,
|
|
3556
|
-
error: err instanceof Error ? err.message : "Failed to check status"
|
|
3557
|
-
}));
|
|
3558
|
-
}
|
|
3559
|
-
}
|
|
3560
|
-
void checkStatus();
|
|
3561
|
-
return () => {
|
|
3562
|
-
cancelled = true;
|
|
3563
|
-
};
|
|
3564
|
-
}, [
|
|
3565
|
-
settings.llmApiKey,
|
|
3566
|
-
settings.llmProvider,
|
|
3567
|
-
settings.llmBaseUrl,
|
|
3568
|
-
settings.llmModel
|
|
3569
|
-
]);
|
|
3570
|
-
const testApiKey = useCallback(async (key) => {
|
|
3571
|
-
try {
|
|
3572
|
-
const headers = buildLlmHeaders({
|
|
3573
|
-
...settings,
|
|
3574
|
-
llmApiKey: key
|
|
3575
|
-
});
|
|
3576
|
-
const res = await fetch("/api/llm-features", {
|
|
3577
|
-
method: "POST",
|
|
3578
|
-
headers: {
|
|
3579
|
-
"Content-Type": "application/json",
|
|
3580
|
-
...headers
|
|
3581
|
-
},
|
|
3582
|
-
body: JSON.stringify({ action: "test" })
|
|
3583
|
-
});
|
|
3584
|
-
const data = await res.json();
|
|
3585
|
-
if (!data.ok) {
|
|
3586
|
-
return { valid: false, error: data.error || "Test failed" };
|
|
3587
|
-
}
|
|
3588
|
-
return { valid: data.valid ?? false, error: data.error };
|
|
3589
|
-
} catch (err) {
|
|
3590
|
-
return {
|
|
3591
|
-
valid: false,
|
|
3592
|
-
error: err instanceof Error ? err.message : "Network error"
|
|
3593
|
-
};
|
|
3594
|
-
}
|
|
3595
|
-
}, [settings]);
|
|
3596
|
-
return {
|
|
3597
|
-
settings,
|
|
3598
|
-
updateSettings,
|
|
3599
|
-
clearApiKey,
|
|
3600
|
-
status,
|
|
3601
|
-
testApiKey
|
|
3602
|
-
};
|
|
3603
|
-
}
|
|
3604
|
-
function buildLlmHeaders(settings) {
|
|
3605
|
-
const apiKey = settings.llmApiKey;
|
|
3606
|
-
const baseUrl = getEffectiveLlmBaseUrl(settings);
|
|
3607
|
-
const model = getEffectiveLlmModel(settings);
|
|
3608
|
-
if (apiKey) {
|
|
3609
|
-
return {
|
|
3610
|
-
"X-OpenAI-API-Key": apiKey,
|
|
3611
|
-
...baseUrl ? { "X-LLM-Base-URL": baseUrl } : {},
|
|
3612
|
-
...model ? { "X-LLM-Model": model } : {}
|
|
3613
|
-
};
|
|
3614
|
-
}
|
|
3615
|
-
return {
|
|
3616
|
-
...baseUrl ? { "X-LLM-Base-URL": baseUrl } : {},
|
|
3617
|
-
...model ? { "X-LLM-Model": model } : {}
|
|
3618
|
-
};
|
|
3619
|
-
}
|
|
3620
|
-
function getLlmHeaders() {
|
|
3621
|
-
const settings = useLlmSettingsStore.getState().settings;
|
|
3622
|
-
return buildLlmHeaders(settings);
|
|
3623
|
-
}
|
|
3624
|
-
async function fetchLlmFollowUps(conversationContext, signal) {
|
|
3625
|
-
const headers = {
|
|
3626
|
-
"Content-Type": "application/json",
|
|
3627
|
-
...getLlmHeaders()
|
|
3628
|
-
};
|
|
3629
|
-
const res = await fetch("/api/llm-features", {
|
|
3630
|
-
method: "POST",
|
|
3631
|
-
headers,
|
|
3632
|
-
body: JSON.stringify({
|
|
3633
|
-
action: "followups",
|
|
3634
|
-
conversationContext
|
|
3635
|
-
}),
|
|
3636
|
-
signal
|
|
3637
|
-
});
|
|
3638
|
-
if (!res.ok) {
|
|
3639
|
-
throw new Error(`API error: ${res.status}`);
|
|
3640
|
-
}
|
|
3641
|
-
const data = await res.json();
|
|
3642
|
-
if (data.ok && Array.isArray(data.suggestions) && data.suggestions.length > 0) {
|
|
3643
|
-
return data.suggestions;
|
|
3644
|
-
}
|
|
3645
|
-
return [];
|
|
3646
|
-
}
|
|
3647
|
-
function useFollowUpSuggestions(responseText, contextSummary, options) {
|
|
3648
|
-
const llmSettings = useLlmSettingsStore((state) => state.settings);
|
|
3649
|
-
const useLlmFollowUps = llmSettings.useLlmFollowUps;
|
|
3650
|
-
const {
|
|
3651
|
-
minResponseLength = 50,
|
|
3652
|
-
timeoutMs = 8e3,
|
|
3653
|
-
// Use heuristics only if explicitly set OR if LLM follow-ups are disabled
|
|
3654
|
-
heuristicsOnly: forceHeuristicsOnly
|
|
3655
|
-
} = options ?? {};
|
|
3656
|
-
const heuristicsOnly = forceHeuristicsOnly ?? !useLlmFollowUps;
|
|
3657
|
-
const [suggestions, setSuggestions] = useState([]);
|
|
3658
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
3659
|
-
const [error, setError] = useState(null);
|
|
3660
|
-
const [source, setSource] = useState(null);
|
|
3661
|
-
const lastResponseRef = useRef("");
|
|
3662
|
-
const abortControllerRef = useRef(null);
|
|
3663
|
-
useEffect(() => {
|
|
3664
|
-
if (!responseText || responseText.trim().length < minResponseLength) {
|
|
3665
|
-
setSuggestions([]);
|
|
3666
|
-
setSource(null);
|
|
3667
|
-
setIsLoading(false);
|
|
3668
|
-
setError(null);
|
|
3669
|
-
return;
|
|
3670
|
-
}
|
|
3671
|
-
const responseKey = responseText.slice(0, 200) + responseText.length;
|
|
3672
|
-
if (responseKey === lastResponseRef.current) {
|
|
3673
|
-
return;
|
|
3674
|
-
}
|
|
3675
|
-
lastResponseRef.current = responseKey;
|
|
3676
|
-
if (abortControllerRef.current) {
|
|
3677
|
-
abortControllerRef.current.abort();
|
|
3678
|
-
}
|
|
3679
|
-
if (heuristicsOnly) {
|
|
3680
|
-
const heuristicSuggestions2 = getHeuristicFollowUpTexts(responseText);
|
|
3681
|
-
setSuggestions(heuristicSuggestions2);
|
|
3682
|
-
setSource("heuristic");
|
|
3683
|
-
setIsLoading(false);
|
|
3684
|
-
setError(null);
|
|
3685
|
-
return;
|
|
3686
|
-
}
|
|
3687
|
-
const controller = new AbortController();
|
|
3688
|
-
abortControllerRef.current = controller;
|
|
3689
|
-
setIsLoading(true);
|
|
3690
|
-
setError(null);
|
|
3691
|
-
const heuristicSuggestions = getHeuristicFollowUpTexts(responseText);
|
|
3692
|
-
setSuggestions(heuristicSuggestions);
|
|
3693
|
-
setSource("heuristic");
|
|
3694
|
-
const conversationContext = contextSummary ? `Context: ${contextSummary}
|
|
3695
|
-
|
|
3696
|
-
Assistant's response:
|
|
3697
|
-
${responseText.slice(0, 2e3)}` : `Assistant's response:
|
|
3698
|
-
${responseText.slice(0, 2e3)}`;
|
|
3699
|
-
fetchLlmFollowUps(conversationContext, controller.signal).then((llmSuggestions) => {
|
|
3700
|
-
if (controller.signal.aborted) {
|
|
3701
|
-
return;
|
|
3702
|
-
}
|
|
3703
|
-
if (llmSuggestions.length > 0) {
|
|
3704
|
-
setSuggestions(llmSuggestions);
|
|
3705
|
-
setSource("llm");
|
|
3706
|
-
}
|
|
3707
|
-
setIsLoading(false);
|
|
3708
|
-
}).catch((err) => {
|
|
3709
|
-
if (controller.signal.aborted) return;
|
|
3710
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
3711
|
-
setIsLoading(false);
|
|
3712
|
-
});
|
|
3713
|
-
return () => {
|
|
3714
|
-
};
|
|
3715
|
-
}, [
|
|
3716
|
-
responseText,
|
|
3717
|
-
contextSummary,
|
|
3718
|
-
minResponseLength,
|
|
3719
|
-
timeoutMs,
|
|
3720
|
-
heuristicsOnly,
|
|
3721
|
-
llmSettings.llmApiKey,
|
|
3722
|
-
llmSettings.llmBaseUrl,
|
|
3723
|
-
llmSettings.llmModel,
|
|
3724
|
-
llmSettings.llmProvider
|
|
3725
|
-
]);
|
|
3726
|
-
return { suggestions, isLoading, error, source };
|
|
3727
|
-
}
|
|
3728
|
-
function FollowUpSuggestionsComponent({
|
|
3729
|
-
responseText,
|
|
3730
|
-
contextSummary,
|
|
3731
|
-
onSuggestionClick,
|
|
3732
|
-
disabled = false,
|
|
3733
|
-
className
|
|
3734
|
-
}) {
|
|
3735
|
-
const { suggestions, isLoading, source } = useFollowUpSuggestions(
|
|
3736
|
-
responseText,
|
|
3737
|
-
contextSummary,
|
|
3738
|
-
{
|
|
3739
|
-
minResponseLength: 50,
|
|
3740
|
-
timeoutMs: 8e3
|
|
3741
|
-
}
|
|
3742
|
-
);
|
|
3743
|
-
if (suggestions.length === 0 && !isLoading) {
|
|
3744
|
-
return null;
|
|
3745
|
-
}
|
|
3746
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-2 mt-3", className), children: [
|
|
3747
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs text-primary-500", children: [
|
|
3748
|
-
/* @__PURE__ */ jsx("span", { className: "text-primary-400", children: "✨" }),
|
|
3749
|
-
/* @__PURE__ */ jsx("span", { children: isLoading ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
|
|
3750
|
-
"Thinking of follow-ups",
|
|
3751
|
-
/* @__PURE__ */ jsx(
|
|
3752
|
-
HugeiconsIcon,
|
|
3753
|
-
{
|
|
3754
|
-
icon: Loading03Icon,
|
|
3755
|
-
size: 12,
|
|
3756
|
-
strokeWidth: 2,
|
|
3757
|
-
className: "animate-spin text-primary-400"
|
|
3758
|
-
}
|
|
3759
|
-
)
|
|
3760
|
-
] }) : source === "llm" ? "AI suggestions" : "Follow-up suggestions" })
|
|
3761
|
-
] }),
|
|
3762
|
-
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: suggestions.map((suggestion, index) => /* @__PURE__ */ jsxs(
|
|
3763
|
-
"button",
|
|
3764
|
-
{
|
|
3765
|
-
type: "button",
|
|
3766
|
-
disabled,
|
|
3767
|
-
onClick: () => onSuggestionClick(suggestion),
|
|
3768
|
-
className: cn(
|
|
3769
|
-
"group inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full",
|
|
3770
|
-
"text-sm text-primary-700 bg-primary-50 border border-primary-200",
|
|
3771
|
-
"hover:bg-primary-100 hover:border-primary-300 hover:text-primary-900",
|
|
3772
|
-
"focus:outline-none focus:ring-2 focus:ring-primary-500/20 focus:ring-offset-1",
|
|
3773
|
-
"transition-all duration-150 cursor-pointer",
|
|
3774
|
-
"disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-primary-50 disabled:hover:border-primary-200",
|
|
3775
|
-
// Subtle animation when suggestions update from heuristic to LLM
|
|
3776
|
-
isLoading && "opacity-75"
|
|
3777
|
-
),
|
|
3778
|
-
children: [
|
|
3779
|
-
/* @__PURE__ */ jsx("span", { children: suggestion }),
|
|
3780
|
-
/* @__PURE__ */ jsx(
|
|
3781
|
-
HugeiconsIcon,
|
|
3782
|
-
{
|
|
3783
|
-
icon: ArrowRight01Icon,
|
|
3784
|
-
size: 14,
|
|
3785
|
-
strokeWidth: 2,
|
|
3786
|
-
className: "text-primary-400 group-hover:text-primary-600 group-hover:translate-x-0.5 transition-all duration-150"
|
|
3787
|
-
}
|
|
3788
|
-
)
|
|
3789
|
-
]
|
|
3790
|
-
},
|
|
3791
|
-
`${index}-${suggestion.slice(0, 20)}`
|
|
3792
|
-
)) })
|
|
3793
|
-
] });
|
|
3223
|
+
if (toolResultsSignature(prevProps.message, prevProps.toolResultsByCallId) !== toolResultsSignature(nextProps.message, nextProps.toolResultsByCallId)) {
|
|
3224
|
+
return false;
|
|
3225
|
+
}
|
|
3226
|
+
if (rawTimestamp(prevProps.message) !== rawTimestamp(nextProps.message)) {
|
|
3227
|
+
return false;
|
|
3228
|
+
}
|
|
3229
|
+
return true;
|
|
3794
3230
|
}
|
|
3795
|
-
const
|
|
3231
|
+
const MemoizedMessageItem = memo(MessageItemComponent, areMessagesEqual);
|
|
3796
3232
|
function ScrollButton({
|
|
3797
3233
|
className,
|
|
3798
3234
|
variant = "outline",
|
|
@@ -4032,6 +3468,11 @@ function TypingIndicator({ className }) {
|
|
|
4032
3468
|
/* @__PURE__ */ jsx(TextShimmer, { className: "text-sm", duration: 2, children: "Generating..." })
|
|
4033
3469
|
] });
|
|
4034
3470
|
}
|
|
3471
|
+
const FollowUpSuggestions = lazy(
|
|
3472
|
+
() => import("./follow-up-suggestions-DhBZIszK.js").then((m) => ({
|
|
3473
|
+
default: m.FollowUpSuggestions
|
|
3474
|
+
}))
|
|
3475
|
+
);
|
|
4035
3476
|
function ChatMessageListComponent({
|
|
4036
3477
|
messages,
|
|
4037
3478
|
loading,
|
|
@@ -4058,6 +3499,7 @@ function ChatMessageListComponent({
|
|
|
4058
3499
|
const displayMessages = useMemo(() => {
|
|
4059
3500
|
return messages.filter((msg) => msg.role !== "toolResult");
|
|
4060
3501
|
}, [messages]);
|
|
3502
|
+
const deferredDisplayMessages = useDeferredValue(displayMessages);
|
|
4061
3503
|
const toolResultsByCallId = useMemo(() => {
|
|
4062
3504
|
const map = /* @__PURE__ */ new Map();
|
|
4063
3505
|
for (const message of messages) {
|
|
@@ -4069,10 +3511,40 @@ function ChatMessageListComponent({
|
|
|
4069
3511
|
}
|
|
4070
3512
|
return map;
|
|
4071
3513
|
}, [messages]);
|
|
3514
|
+
const deferredToolResultsByCallId = useDeferredValue(toolResultsByCallId);
|
|
3515
|
+
const aggregatedSearchSourcesSignature = useMemo(() => {
|
|
3516
|
+
const parts = [];
|
|
3517
|
+
for (const msg of deferredDisplayMessages) {
|
|
3518
|
+
if (msg.role !== "assistant") continue;
|
|
3519
|
+
const toolCalls = getToolCallsFromMessage(msg);
|
|
3520
|
+
for (const tc of toolCalls) {
|
|
3521
|
+
if (!tc.id) continue;
|
|
3522
|
+
const isSearch = tc.name === "web_search";
|
|
3523
|
+
const isFetch = tc.name === "web_fetch";
|
|
3524
|
+
const isExec = tc.name === "exec";
|
|
3525
|
+
if (!isSearch && !isFetch && !isExec) continue;
|
|
3526
|
+
const result = deferredToolResultsByCallId.get(tc.id);
|
|
3527
|
+
const text = result?.content?.map((p) => p.type === "text" ? String(p.text ?? "") : "").join("").trim() ?? "";
|
|
3528
|
+
parts.push(
|
|
3529
|
+
[
|
|
3530
|
+
tc.id,
|
|
3531
|
+
tc.name ?? "",
|
|
3532
|
+
typeof tc.arguments?.url === "string" ? tc.arguments.url : "",
|
|
3533
|
+
result?.isError ? "1" : "0",
|
|
3534
|
+
text
|
|
3535
|
+
].join("::")
|
|
3536
|
+
);
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
return parts.join("||");
|
|
3540
|
+
}, [deferredDisplayMessages, deferredToolResultsByCallId]);
|
|
4072
3541
|
const aggregatedSearchSources = useMemo(() => {
|
|
4073
3542
|
const strip = (s) => {
|
|
4074
3543
|
if (!s) return "";
|
|
4075
|
-
return s.replace(
|
|
3544
|
+
return s.replace(
|
|
3545
|
+
/SECURITY NOTICE:[\s\S]*?<<<EXTERNAL_UNTRUSTED_CONTENT>>>/g,
|
|
3546
|
+
""
|
|
3547
|
+
).replace(/<<<\/?EXTERNAL_UNTRUSTED_CONTENT>>>/g, "").replace(/<<<\/?END_EXTERNAL_UNTRUSTED_CONTENT>>>/g, "").replace(/Source: Web (?:Search|Fetch)\n---/g, "").replace(/\n{2,}/g, "\n").trim();
|
|
4076
3548
|
};
|
|
4077
3549
|
const extractResults = (text, sources2, seenUrls2) => {
|
|
4078
3550
|
try {
|
|
@@ -4086,7 +3558,9 @@ function ChatMessageListComponent({
|
|
|
4086
3558
|
sources2.push({
|
|
4087
3559
|
title: strip(item.title),
|
|
4088
3560
|
url: item.url,
|
|
4089
|
-
snippet: strip(
|
|
3561
|
+
snippet: strip(
|
|
3562
|
+
item.description || item.snippet || item.content || ""
|
|
3563
|
+
)
|
|
4090
3564
|
});
|
|
4091
3565
|
found = true;
|
|
4092
3566
|
}
|
|
@@ -4098,7 +3572,7 @@ function ChatMessageListComponent({
|
|
|
4098
3572
|
};
|
|
4099
3573
|
const sources = [];
|
|
4100
3574
|
const seenUrls = /* @__PURE__ */ new Set();
|
|
4101
|
-
for (const msg of
|
|
3575
|
+
for (const msg of deferredDisplayMessages) {
|
|
4102
3576
|
if (msg.role !== "assistant") continue;
|
|
4103
3577
|
const toolCalls = getToolCallsFromMessage(msg);
|
|
4104
3578
|
for (const tc of toolCalls) {
|
|
@@ -4107,7 +3581,7 @@ function ChatMessageListComponent({
|
|
|
4107
3581
|
const isFetch = tc.name === "web_fetch";
|
|
4108
3582
|
const isExec = tc.name === "exec";
|
|
4109
3583
|
if (!isSearch && !isFetch && !isExec) continue;
|
|
4110
|
-
const result =
|
|
3584
|
+
const result = deferredToolResultsByCallId.get(tc.id);
|
|
4111
3585
|
if (!result) continue;
|
|
4112
3586
|
const text = result.content?.map((p) => p.type === "text" ? String(p.text ?? "") : "").join("").trim();
|
|
4113
3587
|
if (!text) continue;
|
|
@@ -4137,7 +3611,11 @@ function ChatMessageListComponent({
|
|
|
4137
3611
|
}
|
|
4138
3612
|
}
|
|
4139
3613
|
return sources;
|
|
4140
|
-
}, [
|
|
3614
|
+
}, [
|
|
3615
|
+
aggregatedSearchSourcesSignature,
|
|
3616
|
+
deferredDisplayMessages,
|
|
3617
|
+
deferredToolResultsByCallId
|
|
3618
|
+
]);
|
|
4141
3619
|
const lastAssistantIndex = displayMessages.map((message, index) => ({ message, index })).filter(({ message }) => message.role !== "user").map(({ index }) => index).pop();
|
|
4142
3620
|
const lastTextAssistantIndex = displayMessages.map((message, index) => ({ message, index })).filter(({ message }) => message.role === "assistant").map(({ index }) => index).pop();
|
|
4143
3621
|
const lastUserIndex = displayMessages.map((message, index) => ({ message, index })).filter(({ message }) => message.role === "user").map(({ index }) => index).pop();
|
|
@@ -4179,7 +3657,9 @@ function ChatMessageListComponent({
|
|
|
4179
3657
|
target.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
4180
3658
|
setHighlightedMessageId(jumpToMessageId);
|
|
4181
3659
|
const timer = window.setTimeout(() => {
|
|
4182
|
-
setHighlightedMessageId(
|
|
3660
|
+
setHighlightedMessageId(
|
|
3661
|
+
(prev) => prev === jumpToMessageId ? null : prev
|
|
3662
|
+
);
|
|
4183
3663
|
}, 1800);
|
|
4184
3664
|
return () => window.clearTimeout(timer);
|
|
4185
3665
|
}, [jumpToMessageId, loading, displayMessages]);
|
|
@@ -4244,14 +3724,14 @@ function ChatMessageListComponent({
|
|
|
4244
3724
|
);
|
|
4245
3725
|
}),
|
|
4246
3726
|
showTypingIndicator ? /* @__PURE__ */ jsx("div", { className: "py-2", children: /* @__PURE__ */ jsx(TypingIndicator, {}) }) : null,
|
|
4247
|
-
showFollowUps && onFollowUpClick ? /* @__PURE__ */ jsx(
|
|
4248
|
-
|
|
3727
|
+
showFollowUps && onFollowUpClick ? /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
|
|
3728
|
+
FollowUpSuggestions,
|
|
4249
3729
|
{
|
|
4250
3730
|
responseText: lastAssistantText,
|
|
4251
3731
|
onSuggestionClick: onFollowUpClick,
|
|
4252
3732
|
disabled: waitingForResponse
|
|
4253
3733
|
}
|
|
4254
|
-
) : null
|
|
3734
|
+
) }) : null
|
|
4255
3735
|
]
|
|
4256
3736
|
}
|
|
4257
3737
|
)
|
|
@@ -4277,14 +3757,14 @@ function ChatMessageListComponent({
|
|
|
4277
3757
|
messageKey
|
|
4278
3758
|
);
|
|
4279
3759
|
}),
|
|
4280
|
-
showFollowUps && onFollowUpClick ? /* @__PURE__ */ jsx(
|
|
4281
|
-
|
|
3760
|
+
showFollowUps && onFollowUpClick ? /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
|
|
3761
|
+
FollowUpSuggestions,
|
|
4282
3762
|
{
|
|
4283
3763
|
responseText: lastAssistantText,
|
|
4284
3764
|
onSuggestionClick: onFollowUpClick,
|
|
4285
3765
|
disabled: waitingForResponse
|
|
4286
3766
|
}
|
|
4287
|
-
) : null
|
|
3767
|
+
) }) : null
|
|
4288
3768
|
] }),
|
|
4289
3769
|
notice && noticePosition === "end" ? notice : null,
|
|
4290
3770
|
/* @__PURE__ */ jsx(
|
|
@@ -6109,6 +5589,197 @@ function useChatSessions({
|
|
|
6109
5589
|
sessionsError
|
|
6110
5590
|
};
|
|
6111
5591
|
}
|
|
5592
|
+
const LLM_PROVIDER_DEFAULTS = {
|
|
5593
|
+
openai: {
|
|
5594
|
+
baseUrl: "https://api.openai.com/v1",
|
|
5595
|
+
model: "gpt-4.1-nano"
|
|
5596
|
+
},
|
|
5597
|
+
openrouter: {
|
|
5598
|
+
baseUrl: "https://openrouter.ai/api/v1",
|
|
5599
|
+
model: "openai/gpt-oss-120b"
|
|
5600
|
+
},
|
|
5601
|
+
kilocode: {
|
|
5602
|
+
baseUrl: "https://api.kilo.ai/api/gateway",
|
|
5603
|
+
model: "google/gemini-2.5-flash"
|
|
5604
|
+
},
|
|
5605
|
+
ollama: {
|
|
5606
|
+
baseUrl: "http://localhost:11434/v1",
|
|
5607
|
+
model: "llama3.2"
|
|
5608
|
+
},
|
|
5609
|
+
custom: {
|
|
5610
|
+
baseUrl: "",
|
|
5611
|
+
model: ""
|
|
5612
|
+
}
|
|
5613
|
+
};
|
|
5614
|
+
const DEFAULT_LLM_SETTINGS = {
|
|
5615
|
+
useLlmTitles: true,
|
|
5616
|
+
useLlmFollowUps: true,
|
|
5617
|
+
llmProvider: "openai",
|
|
5618
|
+
llmBaseUrl: "",
|
|
5619
|
+
llmModel: "",
|
|
5620
|
+
llmApiKey: ""
|
|
5621
|
+
};
|
|
5622
|
+
function getLlmProviderDefaults(provider) {
|
|
5623
|
+
return LLM_PROVIDER_DEFAULTS[provider];
|
|
5624
|
+
}
|
|
5625
|
+
function getEffectiveLlmBaseUrl(settings) {
|
|
5626
|
+
if (settings.llmBaseUrl.trim()) return settings.llmBaseUrl.trim();
|
|
5627
|
+
return getLlmProviderDefaults(settings.llmProvider).baseUrl;
|
|
5628
|
+
}
|
|
5629
|
+
function getEffectiveLlmModel(settings) {
|
|
5630
|
+
if (settings.llmModel.trim()) return settings.llmModel.trim();
|
|
5631
|
+
return getLlmProviderDefaults(settings.llmProvider).model;
|
|
5632
|
+
}
|
|
5633
|
+
function getAvailability(settings, hasEnvKey) {
|
|
5634
|
+
if (settings.llmProvider === "ollama") return true;
|
|
5635
|
+
if (settings.llmProvider === "custom") {
|
|
5636
|
+
return Boolean(settings.llmApiKey.trim()) || Boolean(settings.llmBaseUrl.trim() && settings.llmModel.trim());
|
|
5637
|
+
}
|
|
5638
|
+
return hasEnvKey || Boolean(settings.llmApiKey.trim());
|
|
5639
|
+
}
|
|
5640
|
+
function migratePersistedState(persistedState) {
|
|
5641
|
+
if (!persistedState || typeof persistedState !== "object") {
|
|
5642
|
+
return { settings: DEFAULT_LLM_SETTINGS };
|
|
5643
|
+
}
|
|
5644
|
+
const { settings } = persistedState;
|
|
5645
|
+
if (!settings) {
|
|
5646
|
+
return { settings: DEFAULT_LLM_SETTINGS };
|
|
5647
|
+
}
|
|
5648
|
+
const { openaiApiKey, ...rest } = settings;
|
|
5649
|
+
const llmApiKey = rest.llmApiKey ?? openaiApiKey ?? "";
|
|
5650
|
+
return {
|
|
5651
|
+
settings: {
|
|
5652
|
+
...DEFAULT_LLM_SETTINGS,
|
|
5653
|
+
...rest,
|
|
5654
|
+
llmApiKey
|
|
5655
|
+
}
|
|
5656
|
+
};
|
|
5657
|
+
}
|
|
5658
|
+
const useLlmSettingsStore = create()(
|
|
5659
|
+
persist(
|
|
5660
|
+
(set) => ({
|
|
5661
|
+
settings: {
|
|
5662
|
+
...DEFAULT_LLM_SETTINGS
|
|
5663
|
+
},
|
|
5664
|
+
updateSettings: (updates) => set((state) => ({
|
|
5665
|
+
settings: { ...state.settings, ...updates }
|
|
5666
|
+
})),
|
|
5667
|
+
clearApiKey: () => set((state) => ({
|
|
5668
|
+
settings: { ...state.settings, llmApiKey: "" }
|
|
5669
|
+
}))
|
|
5670
|
+
}),
|
|
5671
|
+
{
|
|
5672
|
+
name: "llm-settings",
|
|
5673
|
+
version: 2,
|
|
5674
|
+
migrate: (persistedState) => migratePersistedState(persistedState)
|
|
5675
|
+
}
|
|
5676
|
+
)
|
|
5677
|
+
);
|
|
5678
|
+
function useLlmSettings() {
|
|
5679
|
+
const settings = useLlmSettingsStore((state) => state.settings);
|
|
5680
|
+
const updateSettings = useLlmSettingsStore((state) => state.updateSettings);
|
|
5681
|
+
const clearApiKey = useLlmSettingsStore((state) => state.clearApiKey);
|
|
5682
|
+
const [status, setStatus] = useState({
|
|
5683
|
+
hasEnvKey: false,
|
|
5684
|
+
hasOpenRouterKey: false,
|
|
5685
|
+
hasKilocodeKey: false,
|
|
5686
|
+
hasUserKey: Boolean(settings.llmApiKey),
|
|
5687
|
+
isAvailable: getAvailability(settings, false),
|
|
5688
|
+
isLoading: true,
|
|
5689
|
+
error: null
|
|
5690
|
+
});
|
|
5691
|
+
useEffect(() => {
|
|
5692
|
+
let cancelled = false;
|
|
5693
|
+
async function checkStatus() {
|
|
5694
|
+
try {
|
|
5695
|
+
const res = await fetch("/api/llm-features");
|
|
5696
|
+
if (!res.ok) throw new Error("Failed to check LLM status");
|
|
5697
|
+
const data = await res.json();
|
|
5698
|
+
if (cancelled) return;
|
|
5699
|
+
const hasUserKey = Boolean(settings.llmApiKey);
|
|
5700
|
+
const hasProviderKey = settings.llmProvider === "openrouter" ? Boolean(data.hasOpenRouterKey) : settings.llmProvider === "kilocode" ? Boolean(data.hasKilocodeKey) : data.hasEnvKey;
|
|
5701
|
+
setStatus({
|
|
5702
|
+
hasEnvKey: data.hasEnvKey,
|
|
5703
|
+
hasOpenRouterKey: Boolean(data.hasOpenRouterKey),
|
|
5704
|
+
hasKilocodeKey: Boolean(data.hasKilocodeKey),
|
|
5705
|
+
hasUserKey,
|
|
5706
|
+
isAvailable: getAvailability(settings, hasProviderKey),
|
|
5707
|
+
isLoading: false,
|
|
5708
|
+
error: null
|
|
5709
|
+
});
|
|
5710
|
+
} catch (err) {
|
|
5711
|
+
if (cancelled) return;
|
|
5712
|
+
setStatus((prev) => ({
|
|
5713
|
+
...prev,
|
|
5714
|
+
isLoading: false,
|
|
5715
|
+
error: err instanceof Error ? err.message : "Failed to check status"
|
|
5716
|
+
}));
|
|
5717
|
+
}
|
|
5718
|
+
}
|
|
5719
|
+
void checkStatus();
|
|
5720
|
+
return () => {
|
|
5721
|
+
cancelled = true;
|
|
5722
|
+
};
|
|
5723
|
+
}, [
|
|
5724
|
+
settings.llmApiKey,
|
|
5725
|
+
settings.llmProvider,
|
|
5726
|
+
settings.llmBaseUrl,
|
|
5727
|
+
settings.llmModel
|
|
5728
|
+
]);
|
|
5729
|
+
const testApiKey = useCallback(async (key) => {
|
|
5730
|
+
try {
|
|
5731
|
+
const headers = buildLlmHeaders({
|
|
5732
|
+
...settings,
|
|
5733
|
+
llmApiKey: key
|
|
5734
|
+
});
|
|
5735
|
+
const res = await fetch("/api/llm-features", {
|
|
5736
|
+
method: "POST",
|
|
5737
|
+
headers: {
|
|
5738
|
+
"Content-Type": "application/json",
|
|
5739
|
+
...headers
|
|
5740
|
+
},
|
|
5741
|
+
body: JSON.stringify({ action: "test" })
|
|
5742
|
+
});
|
|
5743
|
+
const data = await res.json();
|
|
5744
|
+
if (!data.ok) {
|
|
5745
|
+
return { valid: false, error: data.error || "Test failed" };
|
|
5746
|
+
}
|
|
5747
|
+
return { valid: data.valid ?? false, error: data.error };
|
|
5748
|
+
} catch (err) {
|
|
5749
|
+
return {
|
|
5750
|
+
valid: false,
|
|
5751
|
+
error: err instanceof Error ? err.message : "Network error"
|
|
5752
|
+
};
|
|
5753
|
+
}
|
|
5754
|
+
}, [settings]);
|
|
5755
|
+
return {
|
|
5756
|
+
settings,
|
|
5757
|
+
updateSettings,
|
|
5758
|
+
clearApiKey,
|
|
5759
|
+
status,
|
|
5760
|
+
testApiKey
|
|
5761
|
+
};
|
|
5762
|
+
}
|
|
5763
|
+
function buildLlmHeaders(settings) {
|
|
5764
|
+
const apiKey = settings.llmApiKey;
|
|
5765
|
+
const baseUrl = getEffectiveLlmBaseUrl(settings);
|
|
5766
|
+
const model = getEffectiveLlmModel(settings);
|
|
5767
|
+
if (apiKey) {
|
|
5768
|
+
return {
|
|
5769
|
+
"X-OpenAI-API-Key": apiKey,
|
|
5770
|
+
...baseUrl ? { "X-LLM-Base-URL": baseUrl } : {},
|
|
5771
|
+
...model ? { "X-LLM-Model": model } : {}
|
|
5772
|
+
};
|
|
5773
|
+
}
|
|
5774
|
+
return {
|
|
5775
|
+
...baseUrl ? { "X-LLM-Base-URL": baseUrl } : {},
|
|
5776
|
+
...model ? { "X-LLM-Model": model } : {}
|
|
5777
|
+
};
|
|
5778
|
+
}
|
|
5779
|
+
function getLlmHeaders() {
|
|
5780
|
+
const settings = useLlmSettingsStore.getState().settings;
|
|
5781
|
+
return buildLlmHeaders(settings);
|
|
5782
|
+
}
|
|
6112
5783
|
function useSmartTitle() {
|
|
6113
5784
|
const llmSettings = useLlmSettingsStore((state) => state.settings);
|
|
6114
5785
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
@@ -6596,7 +6267,7 @@ const KeyboardShortcutsDialog = lazy(
|
|
|
6596
6267
|
}))
|
|
6597
6268
|
);
|
|
6598
6269
|
const SearchDialog = lazy(
|
|
6599
|
-
() => import("./search-dialog-
|
|
6270
|
+
() => import("./search-dialog-DtYc9e4N.js").then((m) => ({
|
|
6600
6271
|
default: m.SearchDialog
|
|
6601
6272
|
}))
|
|
6602
6273
|
);
|
|
@@ -6623,7 +6294,9 @@ function ChatScreen({
|
|
|
6623
6294
|
const [showShortcutsHelp, setShowShortcutsHelp] = useState(false);
|
|
6624
6295
|
const [showSearchDialog, setShowSearchDialog] = useState(false);
|
|
6625
6296
|
const [searchMode, setSearchMode] = useState("global");
|
|
6626
|
-
const [searchJumpMessageId, setSearchJumpMessageId] = useState(
|
|
6297
|
+
const [searchJumpMessageId, setSearchJumpMessageId] = useState(
|
|
6298
|
+
null
|
|
6299
|
+
);
|
|
6627
6300
|
const [isStreaming, setIsStreaming] = useState(false);
|
|
6628
6301
|
const thinkingLevel = useThinkingLevelStore((state) => state.level);
|
|
6629
6302
|
const { maybeNotifyAssistantMessage } = useNotifications();
|
|
@@ -6735,8 +6408,8 @@ function ChatScreen({
|
|
|
6735
6408
|
const llmTitlesEnabled = useLlmTitlesEnabled();
|
|
6736
6409
|
const { generateTitle } = useSmartTitle();
|
|
6737
6410
|
const titleGeneratedRef = useRef(/* @__PURE__ */ new Set());
|
|
6738
|
-
const FAST_POLL_MS =
|
|
6739
|
-
const NORMAL_POLL_MS =
|
|
6411
|
+
const FAST_POLL_MS = 2e3;
|
|
6412
|
+
const NORMAL_POLL_MS = 2e3;
|
|
6740
6413
|
const streamStart = useCallback(() => {
|
|
6741
6414
|
if (!activeFriendlyId || isNewChat) return;
|
|
6742
6415
|
if (streamTimer.current) window.clearInterval(streamTimer.current);
|
|
@@ -6769,12 +6442,9 @@ function ChatScreen({
|
|
|
6769
6442
|
},
|
|
6770
6443
|
[historyQuery, queryClient, stopStream, streamFinish]
|
|
6771
6444
|
);
|
|
6772
|
-
handleStreamErrorRef.current = useCallback(
|
|
6773
|
-
(
|
|
6774
|
-
|
|
6775
|
-
},
|
|
6776
|
-
[]
|
|
6777
|
-
);
|
|
6445
|
+
handleStreamErrorRef.current = useCallback((_err) => {
|
|
6446
|
+
console.warn("[stream] SSE error, falling back to polling");
|
|
6447
|
+
}, []);
|
|
6778
6448
|
const streamingMessage = useMemo(() => {
|
|
6779
6449
|
if (!streaming.text) return null;
|
|
6780
6450
|
const content = [];
|
|
@@ -6917,7 +6587,9 @@ function ChatScreen({
|
|
|
6917
6587
|
if (!sessionKey) return;
|
|
6918
6588
|
if (titleGeneratedRef.current.has(sessionKey)) return;
|
|
6919
6589
|
const userMessages = historyMessages.filter((m) => m.role === "user");
|
|
6920
|
-
const assistantMessages = historyMessages.filter(
|
|
6590
|
+
const assistantMessages = historyMessages.filter(
|
|
6591
|
+
(m) => m.role === "assistant"
|
|
6592
|
+
);
|
|
6921
6593
|
if (userMessages.length === 0 || assistantMessages.length === 0) return;
|
|
6922
6594
|
if (userMessages.length !== 1) return;
|
|
6923
6595
|
const firstUserMessage = textFromMessage(userMessages[0]);
|
|
@@ -6927,7 +6599,12 @@ function ChatScreen({
|
|
|
6927
6599
|
try {
|
|
6928
6600
|
const result = await generateTitle(firstUserMessage);
|
|
6929
6601
|
if (result.title) {
|
|
6930
|
-
await updateSessionLabel(
|
|
6602
|
+
await updateSessionLabel(
|
|
6603
|
+
queryClient,
|
|
6604
|
+
sessionKey,
|
|
6605
|
+
activeFriendlyId,
|
|
6606
|
+
result.title
|
|
6607
|
+
);
|
|
6931
6608
|
}
|
|
6932
6609
|
} catch (err) {
|
|
6933
6610
|
console.error("[smart-title] Error generating title:", err);
|
|
@@ -7025,7 +6702,13 @@ function ChatScreen({
|
|
|
7025
6702
|
}
|
|
7026
6703
|
setWaitingForResponse(true);
|
|
7027
6704
|
setPinToTop(true);
|
|
7028
|
-
sendMessage(
|
|
6705
|
+
sendMessage(
|
|
6706
|
+
pending.sessionKey,
|
|
6707
|
+
pending.friendlyId,
|
|
6708
|
+
pending.message,
|
|
6709
|
+
true,
|
|
6710
|
+
pending.attachments
|
|
6711
|
+
);
|
|
7029
6712
|
}, [
|
|
7030
6713
|
activeFriendlyId,
|
|
7031
6714
|
activeSessionKey,
|
|
@@ -7037,7 +6720,10 @@ function ChatScreen({
|
|
|
7037
6720
|
function sendMessage(sessionKey, friendlyId, body, skipOptimistic = false, attachments, model) {
|
|
7038
6721
|
let optimisticClientId = "";
|
|
7039
6722
|
if (!skipOptimistic) {
|
|
7040
|
-
const { clientId, optimisticMessage } = createOptimisticMessage(
|
|
6723
|
+
const { clientId, optimisticMessage } = createOptimisticMessage(
|
|
6724
|
+
body,
|
|
6725
|
+
attachments
|
|
6726
|
+
);
|
|
7041
6727
|
optimisticClientId = clientId;
|
|
7042
6728
|
appendHistoryMessage(
|
|
7043
6729
|
queryClient,
|
|
@@ -7129,7 +6815,8 @@ function ChatScreen({
|
|
|
7129
6815
|
const send = useCallback(
|
|
7130
6816
|
(body, helpers) => {
|
|
7131
6817
|
const attachments = helpers.attachments;
|
|
7132
|
-
if (body.length === 0 && (!attachments || attachments.length === 0))
|
|
6818
|
+
if (body.length === 0 && (!attachments || attachments.length === 0))
|
|
6819
|
+
return;
|
|
7133
6820
|
helpers.reset();
|
|
7134
6821
|
if (isNewChat) {
|
|
7135
6822
|
const { clientId, optimisticId, optimisticMessage } = createOptimisticMessage(body, attachments);
|
|
@@ -7176,7 +6863,14 @@ function ChatScreen({
|
|
|
7176
6863
|
return;
|
|
7177
6864
|
}
|
|
7178
6865
|
const sessionKeyForSend = forcedSessionKey || resolvedSessionKey || activeSessionKey;
|
|
7179
|
-
sendMessage(
|
|
6866
|
+
sendMessage(
|
|
6867
|
+
sessionKeyForSend,
|
|
6868
|
+
activeFriendlyId,
|
|
6869
|
+
body,
|
|
6870
|
+
false,
|
|
6871
|
+
attachments,
|
|
6872
|
+
helpers.model
|
|
6873
|
+
);
|
|
7180
6874
|
},
|
|
7181
6875
|
[
|
|
7182
6876
|
activeFriendlyId,
|
|
@@ -7431,7 +7125,10 @@ function ChatScreen({
|
|
|
7431
7125
|
} catch {
|
|
7432
7126
|
}
|
|
7433
7127
|
}
|
|
7434
|
-
navigate({
|
|
7128
|
+
navigate({
|
|
7129
|
+
to: "/chat/$sessionKey",
|
|
7130
|
+
params: { sessionKey: result.friendlyId }
|
|
7131
|
+
});
|
|
7435
7132
|
}
|
|
7436
7133
|
}
|
|
7437
7134
|
) })
|
|
@@ -7467,7 +7164,12 @@ const $sessionKey = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineP
|
|
|
7467
7164
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
7468
7165
|
export {
|
|
7469
7166
|
$sessionKey as $,
|
|
7167
|
+
Collapsible as C,
|
|
7168
|
+
useLlmSettingsStore as a,
|
|
7169
|
+
getLlmHeaders as b,
|
|
7470
7170
|
chatQueryKeys as c,
|
|
7171
|
+
CollapsibleTrigger as d,
|
|
7172
|
+
CollapsiblePanel as e,
|
|
7471
7173
|
getLlmProviderDefaults as g,
|
|
7472
7174
|
useLlmSettings as u
|
|
7473
7175
|
};
|