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.
Files changed (70) hide show
  1. package/dist/client/assets/{CSPContext-6t3O1emU.js → CSPContext-KDlbpvjX.js} +1 -1
  2. package/dist/client/assets/{DirectionContext-C6goXEY_.js → DirectionContext-DZNUWW6F.js} +1 -1
  3. package/dist/client/assets/_sessionKey-CPRycepq.js +19 -0
  4. package/dist/client/assets/agents-Bp6Cn1BH.js +2 -0
  5. package/dist/client/assets/{agents-screen-pHUzJxX5.js → agents-screen-Du8b4SRW.js} +1 -1
  6. package/dist/client/assets/bots-Dl65yZDx.js +2 -0
  7. package/dist/client/assets/bots-screen-CvbAVMk0.js +1 -0
  8. package/dist/client/assets/{button-CK8tKQ-Z.js → button-BxDVif49.js} +1 -1
  9. package/dist/client/assets/{composite-feK0c-xF.js → composite-t7Eu0qB0.js} +1 -1
  10. package/dist/client/assets/{connect-02tmQV_v.js → connect-Btonx7l6.js} +1 -1
  11. package/dist/client/assets/{dashboard-DQ0zDQKd.js → dashboard-Dz6Uyyke.js} +1 -1
  12. package/dist/client/assets/{event-BsD1rqGT.js → event-DeooaZzN.js} +1 -1
  13. package/dist/client/assets/{file-explorer-screen-Ds7LeJTd.js → file-explorer-screen-BnOINWSL.js} +1 -1
  14. package/dist/client/assets/files-Cf87gXsq.js +2 -0
  15. package/dist/client/assets/follow-up-suggestions-DP_84nA2.js +5 -0
  16. package/dist/client/assets/index-nheqha2z.js +3 -0
  17. package/dist/client/assets/{index-lK3yGoTI.js → index-yPOt98v7.js} +1 -1
  18. package/dist/client/assets/{keyboard-shortcuts-dialog-Bb_GOr9L.js → keyboard-shortcuts-dialog-BzL7ZJUb.js} +1 -1
  19. package/dist/client/assets/{main-Dq6jpr6-.js → main-DJgQwVqD.js} +3 -3
  20. package/dist/client/assets/{markdown-C7_Aipwd.js → markdown-CFbW7h0Q.js} +14 -14
  21. package/dist/client/assets/memory-kYdhgbRR.js +2 -0
  22. package/dist/client/assets/{memory-screen-CUFBWsq5.js → memory-screen-BKYBnY-V.js} +1 -1
  23. package/dist/client/assets/{menu-n6L--M9R.js → menu-CnihCZ4y.js} +1 -1
  24. package/dist/client/assets/{opencami-logo-zuSBm5Br.js → opencami-logo-CoCA2JIf.js} +1 -1
  25. package/dist/client/assets/{proxy-BU8Bw1Vt.js → proxy-C2vtPufK.js} +1 -1
  26. package/dist/client/assets/{react-BLyCEWpN.js → react-DzPMbcP8.js} +1 -1
  27. package/dist/client/assets/{search-dialog-yB4w5ajo.js → search-dialog-CDAdFAQI.js} +1 -1
  28. package/dist/client/assets/search-sources-badge-DOd5AI-i.js +1 -0
  29. package/dist/client/assets/{session-export-dialog-qbZgd2Zo.js → session-export-dialog-Cf8eisLM.js} +1 -1
  30. package/dist/client/assets/{settings-dialog-CHJbvpgk.js → settings-dialog-CB1OiRWU.js} +1 -1
  31. package/dist/client/assets/skills-D6MaebJh.js +2 -0
  32. package/dist/client/assets/{skills-panel-BH27r3nC.js → skills-panel-_M5k5dty.js} +1 -1
  33. package/dist/client/assets/styles-BNkOWk4R.css +1 -0
  34. package/dist/client/assets/{switch-BD3a0LRm.js → switch-LzFh8co4.js} +1 -1
  35. package/dist/client/assets/{tabs-DI1e-kzz.js → tabs-D9o7Irvl.js} +1 -1
  36. package/dist/client/assets/thinking-BqZoM6BP.js +1 -0
  37. package/dist/client/assets/{tooltip-BbH3QWvK.js → tooltip-D64CzFXa.js} +1 -1
  38. package/dist/client/assets/{use-file-explorer-state-DBfLeAyz.js → use-file-explorer-state-Tzex8EEL.js} +2 -2
  39. package/dist/client/assets/{useBaseUiId-MgM4ouhx.js → useBaseUiId-CMJ8I9al.js} +1 -1
  40. package/dist/client/assets/useCompositeItem-CSriPK0T.js +1 -0
  41. package/dist/client/assets/{useControlled-BQxTgsOd.js → useControlled-KFa25CcD.js} +1 -1
  42. package/dist/client/assets/{useMutation-12DyB3Ox.js → useMutation-Bd89JWif.js} +1 -1
  43. package/dist/client/assets/{useOnFirstRender-7qoaK5sI.js → useOnFirstRender-DqnUCnnL.js} +1 -1
  44. package/dist/client/assets/{useQuery-Ctiljcrr.js → useQuery-DhR_UXNX.js} +1 -1
  45. package/dist/server/assets/{_sessionKey-DzsJfprr.js → _sessionKey-BzM-igv7.js} +405 -703
  46. package/dist/server/assets/{_tanstack-start-manifest_v-C5HBDfQB.js → _tanstack-start-manifest_v-Bfnqdswf.js} +1 -1
  47. package/dist/server/assets/{connect-CbgijWz4.js → connect-BNabuqpW.js} +1 -1
  48. package/dist/server/assets/follow-up-suggestions-DhBZIszK.js +336 -0
  49. package/dist/server/assets/{index-Dl2BOKP7.js → index-BEWnDAH6.js} +24 -5
  50. package/dist/server/assets/{index-BFHEmXpN.js → index-DCMpnyEo.js} +1 -1
  51. package/dist/server/assets/{markdown-BFE5y9YH.js → markdown-DoX5Q7qh.js} +50 -26
  52. package/dist/server/assets/{memory-BqZOoD7Q.js → memory-Cxu7i8ej.js} +1 -1
  53. package/dist/server/assets/{memory-screen-BK5phS8K.js → memory-screen-B5l1NZRY.js} +2 -2
  54. package/dist/server/assets/{router-BZPatFG9.js → router-BqPDQeCx.js} +5 -5
  55. package/dist/server/assets/{search-dialog-DQRkARXw.js → search-dialog-DtYc9e4N.js} +4 -4
  56. package/dist/server/assets/search-sources-badge-B0rAEDs_.js +106 -0
  57. package/dist/server/assets/{settings-dialog-Bc1ta26X.js → settings-dialog-BQzn6fyF.js} +4 -4
  58. package/dist/server/assets/thinking-D6q1tJkk.js +92 -0
  59. package/dist/server/server.js +2 -2
  60. package/package.json +1 -1
  61. package/dist/client/assets/_sessionKey-B5Viv43f.js +0 -23
  62. package/dist/client/assets/agents-BmE6QOwl.js +0 -2
  63. package/dist/client/assets/bots-BeOkwrXr.js +0 -2
  64. package/dist/client/assets/bots-screen-B79bAYvf.js +0 -1
  65. package/dist/client/assets/files-e40B1zFy.js +0 -2
  66. package/dist/client/assets/index-rljDU_1M.js +0 -3
  67. package/dist/client/assets/memory-C7UG-1le.js +0 -2
  68. package/dist/client/assets/skills-DoKPPhNY.js +0 -2
  69. package/dist/client/assets/styles-CXV5jZiD.css +0 -1
  70. 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, ArrowDown01Icon, Loading03Icon, File01Icon, ArtificialIntelligence02Icon, CommandIcon, Attachment01Icon, Mic02Icon, ArrowUp02Icon } from "@hugeicons/core-free-icons";
7
+ import { Tick01Icon, Cancel01Icon, MoreHorizontalIcon, Pen01Icon, Upload01Icon, Delete01Icon, BotIcon, Clock01Icon, Chat01Icon, ArrowRight01Icon, SidebarLeft01Icon, PencilEdit02Icon, Folder01Icon, AiBrain01Icon, PackageOpenIcon, SmartPhone01Icon, DashboardCircleIcon, Search01Icon, Settings01Icon, Menu01Icon, Tick02Icon, Copy01Icon, Loading02Icon, StopIcon, VolumeHighIcon, Loading03Icon, ArrowDown01Icon, File01Icon, 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-BFE5y9YH.js";
18
- import { u as useChatSettings$1 } from "./index-Dl2BOKP7.js";
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 { createPortal } from "react-dom";
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-Bc1ta26X.js").then((m) => ({ default: m.SettingsDialog }))
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
- function getDomain(url) {
2875
- try {
2876
- return new URL(url).hostname.replace("www.", "");
2877
- } catch {
2878
- return url;
2879
- }
2880
- }
2881
- function FaviconCircle({ domain }) {
2882
- return /* @__PURE__ */ jsx(
2883
- "div",
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(() => parseUploadedFileReferences(text), [text]);
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(`/api/files/info?path=${encodeURIComponent(ref.path)}`);
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("div", { className: cn(
3213
- "flex flex-wrap gap-2 mb-2",
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
- type: "button",
3230
- onClick: () => {
3231
- void handleOpenFile(fileRef.path);
3232
- },
3233
- 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",
3234
- children: [
3235
- /* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-primary-100", children: /* @__PURE__ */ jsx(HugeiconsIcon, { icon: File01Icon, size: 18, className: "text-primary-600" }) }),
3236
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
3237
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-primary-900", children: fileRef.filename }),
3238
- /* @__PURE__ */ jsx("p", { className: "text-xs text-primary-600", children: formatFileSize$1(fileSizes[fileRef.path] ?? null) })
3239
- ] })
3240
- ]
3241
- },
3242
- fileRef.path
3243
- )) }),
3244
- /* @__PURE__ */ jsx(Message, { className: cn("min-w-0 max-w-full", isUser ? "flex-row-reverse" : ""), children: /* @__PURE__ */ jsx(
3245
- MessageContent,
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
- "text-primary-900 opencami-text-size min-w-0 max-w-full",
3250
- 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",
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: displayText
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) return false;
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 MemoizedFollowUpSuggestions = memo(FollowUpSuggestionsComponent);
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(/SECURITY NOTICE:[\s\S]*?<<<EXTERNAL_UNTRUSTED_CONTENT>>>/g, "").replace(/<<<\/?EXTERNAL_UNTRUSTED_CONTENT>>>/g, "").replace(/<<<\/?END_EXTERNAL_UNTRUSTED_CONTENT>>>/g, "").replace(/Source: Web (?:Search|Fetch)\n---/g, "").replace(/\n{2,}/g, "\n").trim();
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(item.description || item.snippet || item.content || "")
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 displayMessages) {
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 = toolResultsByCallId.get(tc.id);
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
- }, [displayMessages, toolResultsByCallId]);
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((prev) => prev === jumpToMessageId ? null : prev);
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
- MemoizedFollowUpSuggestions,
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
- MemoizedFollowUpSuggestions,
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-DQRkARXw.js").then((m) => ({
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(null);
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 = 150;
6739
- const NORMAL_POLL_MS = 600;
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
- (_err) => {
6774
- console.warn("[stream] SSE error, falling back to polling");
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((m) => m.role === "assistant");
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(queryClient, sessionKey, activeFriendlyId, result.title);
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(pending.sessionKey, pending.friendlyId, pending.message, true, pending.attachments);
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(body, attachments);
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)) return;
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(sessionKeyForSend, activeFriendlyId, body, false, attachments, helpers.model);
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({ to: "/chat/$sessionKey", params: { sessionKey: result.friendlyId } });
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
  };