opencami 1.7.0 → 1.8.3

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 (83) hide show
  1. package/README.md +167 -7
  2. package/bin/opencami.js +15 -6
  3. package/dist/client/assets/{CSPContext-a-MQmQQt.js → CSPContext-DeJH85nm.js} +1 -1
  4. package/dist/client/assets/{DirectionContext-DRcND-Cm.js → DirectionContext-CxhRpXkm.js} +1 -1
  5. package/dist/client/assets/_sessionKey-CQE0brGK.js +23 -0
  6. package/dist/client/assets/agents-CMTFd_sG.js +2 -0
  7. package/dist/client/assets/agents-screen-BNQGEqcW.js +1 -0
  8. package/dist/client/assets/bots-B6oGzCxP.js +2 -0
  9. package/dist/client/assets/bots-screen-Be3cfGgq.js +1 -0
  10. package/dist/client/assets/button-D9Plv7hu.js +1 -0
  11. package/dist/client/assets/composite-B2KCZKKL.js +1 -0
  12. package/dist/client/assets/{connect-BX1MWO82.js → connect-DuJfnyNK.js} +1 -1
  13. package/dist/client/assets/dashboard-00GpXm5V.js +1 -0
  14. package/dist/client/assets/event-DD8Cz4O9.js +1 -0
  15. package/dist/client/assets/file-explorer-screen-CxwemBES.js +1 -0
  16. package/dist/client/assets/files-DyBJVXBu.js +2 -0
  17. package/dist/client/assets/{index-BlC2sH55.js → index-DtGzE-ea.js} +1 -1
  18. package/dist/client/assets/{index-Dg0mbvtv.js → index-Yo5UhdZV.js} +1 -1
  19. package/dist/client/assets/keyboard-shortcuts-dialog-BZwd-iyV.js +1 -0
  20. package/dist/client/assets/{main-CEuT8-Qi.js → main-CgwdHc9W.js} +16 -8
  21. package/dist/client/assets/{markdown-DKD6ZLRJ.js → markdown-DtWnt4NA.js} +1 -1
  22. package/dist/client/assets/memory-l756yiNq.js +2 -0
  23. package/dist/client/assets/memory-screen-BQtVRuzE.js +1 -0
  24. package/dist/client/assets/menu-BsS6CDf_.js +1 -0
  25. package/dist/client/assets/{opencami-logo-DA69yVKc.js → opencami-logo-Bmge6-FB.js} +1 -1
  26. package/dist/client/assets/popupStateMapping-D0ZbJR_o.js +1 -0
  27. package/dist/client/assets/{proxy-Cawf6X0W.js → proxy-CYZeDXoy.js} +1 -1
  28. package/dist/client/assets/{react-K9goXsVv.js → react-DODKNyyU.js} +1 -1
  29. package/dist/client/assets/search-dialog-DW91SK30.js +1 -0
  30. package/dist/client/assets/session-export-dialog-CliO9Ob-.js +1 -0
  31. package/dist/client/assets/settings-dialog-C1u52aju.js +1 -0
  32. package/dist/client/assets/skills-8T_avaVb.js +2 -0
  33. package/dist/client/assets/{skills-panel-DFL-3BRH.js → skills-panel-DSiH-DLs.js} +1 -1
  34. package/dist/client/assets/styles-DvaLh0o1.css +1 -0
  35. package/dist/client/assets/switch-DbgQPO6i.js +1 -0
  36. package/dist/client/assets/tabs-BsAvZnlD.js +1 -0
  37. package/dist/client/assets/tooltip-DLmutB5C.js +1 -0
  38. package/dist/client/assets/use-file-explorer-state-Cg_yDYJl.js +12 -0
  39. package/dist/client/assets/useBaseUiId-KQTzRPLp.js +1 -0
  40. package/dist/client/assets/useCompositeItem-BPY2_hF_.js +1 -0
  41. package/dist/client/assets/{useControlled-Cl9XA2_f.js → useControlled-B5pEEz2V.js} +1 -1
  42. package/dist/client/assets/{useMutation-C5bTdeC1.js → useMutation-BsQD6FKe.js} +1 -1
  43. package/dist/client/assets/useQuery-CmAJuY2W.js +1 -0
  44. package/dist/client/assets/visuallyHidden-COI6QeQH.js +1 -0
  45. package/dist/client/sw.js +5 -164
  46. package/dist/server/assets/{_sessionKey-BBG3ZUlo.js → _sessionKey-C9o7YfxA.js} +878 -755
  47. package/dist/server/assets/_tanstack-start-manifest_v-BMCAWon2.js +4 -0
  48. package/dist/server/assets/dashboard-GCKodTiJ.js +214 -0
  49. package/dist/server/assets/{index-BgMPaOsU.js → index-Bw-bA_2M.js} +4 -3
  50. package/dist/server/assets/{router-Bl2uabfY.js → router-DCjikH21.js} +704 -207
  51. package/dist/server/assets/{search-dialog-BtSQW9SR.js → search-dialog-BnwiXpdA.js} +5 -4
  52. package/dist/server/assets/settings-dialog-ClKFnZ1x.js +1511 -0
  53. package/dist/server/server.js +2 -2
  54. package/package.json +1 -1
  55. package/dist/client/assets/_sessionKey-BAmpzUOP.js +0 -23
  56. package/dist/client/assets/agents-BkeWu_3a.js +0 -2
  57. package/dist/client/assets/agents-screen-Cb76bcxn.js +0 -1
  58. package/dist/client/assets/bots-CyJwr-JU.js +0 -2
  59. package/dist/client/assets/bots-screen-CzNjLsQH.js +0 -1
  60. package/dist/client/assets/button-DNC5N25i.js +0 -1
  61. package/dist/client/assets/composite-Bliqcmg4.js +0 -1
  62. package/dist/client/assets/file-explorer-screen-CpY1O_ag.js +0 -1
  63. package/dist/client/assets/files-HiN5rXWq.js +0 -2
  64. package/dist/client/assets/keyboard-shortcuts-dialog-C2Hq19LN.js +0 -1
  65. package/dist/client/assets/memory-lhzf-8Q4.js +0 -2
  66. package/dist/client/assets/memory-screen-Zq9qfnJK.js +0 -1
  67. package/dist/client/assets/menu-47ooFeSm.js +0 -1
  68. package/dist/client/assets/owner-CFRNz_Tp.js +0 -1
  69. package/dist/client/assets/popupStateMapping-D5k-jOeY.js +0 -1
  70. package/dist/client/assets/search-dialog-C5Yae9rb.js +0 -1
  71. package/dist/client/assets/session-export-dialog-CBeTfbll.js +0 -1
  72. package/dist/client/assets/settings-dialog-CoeG9M1b.js +0 -1
  73. package/dist/client/assets/skills-BEkw619A.js +0 -2
  74. package/dist/client/assets/styles-D4EBtWYc.css +0 -1
  75. package/dist/client/assets/switch-DAFvLxNX.js +0 -1
  76. package/dist/client/assets/tabs-B2Y_7MvG.js +0 -1
  77. package/dist/client/assets/tooltip-D57Pal0B.js +0 -1
  78. package/dist/client/assets/use-file-explorer-state-DppKEjcl.js +0 -12
  79. package/dist/client/assets/useButton-DVAfkehQ.js +0 -1
  80. package/dist/client/assets/useCompositeItem-CzdGhGcj.js +0 -1
  81. package/dist/client/assets/visuallyHidden-CO3ZD5AQ.js +0 -1
  82. package/dist/server/assets/_tanstack-start-manifest_v-DmMFarHb.js +0 -4
  83. package/dist/server/assets/settings-dialog-D3fOAswX.js +0 -1173
@@ -1,1173 +0,0 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect, useRef } from "react";
3
- import { B as Button, c as cn } from "./button-CwY2OHFj.js";
4
- import { HugeiconsIcon } from "@hugeicons/react";
5
- import { Cancel01Icon, Link01Icon, PaintBoardIcon, MessageEdit01Icon, Settings02Icon, UserIcon, VoiceIcon, AiBrain01Icon, InformationCircleIcon, ComputerIcon, Sun01Icon, Moon01Icon, Leaf01Icon, DropletIcon, Loading02Icon, Tick01Icon, Cancel02Icon } from "@hugeicons/core-free-icons";
6
- import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose } from "./use-file-explorer-state-s7CS50ho.js";
7
- import { S as Switch } from "./switch-BbkUeVDV.js";
8
- import { T as Tabs, a as TabsList, b as TabsTab } from "./tabs-DDFZob0m.js";
9
- import { u as useChatSettings } from "./index-Dl2BOKP7.js";
10
- import { u as useLlmSettings, g as getLlmProviderDefaults } from "./_sessionKey-BBG3ZUlo.js";
11
- import "@base-ui/react/merge-props";
12
- import "@base-ui/react/use-render";
13
- import "class-variance-authority";
14
- import "clsx";
15
- import "tailwind-merge";
16
- import "@base-ui/react/dialog";
17
- import "zustand";
18
- import "@base-ui/react/switch";
19
- import "@base-ui/react/tabs";
20
- import "zustand/middleware";
21
- import "@tanstack/react-router";
22
- import "@tanstack/react-query";
23
- import "./tooltip-DgsSPocE.js";
24
- import "@base-ui/react/tooltip";
25
- import "motion/react";
26
- import "@base-ui/react/alert-dialog";
27
- import "@base-ui/react/collapsible";
28
- import "@base-ui/react/scroll-area";
29
- import "./menu-D90CDTi2.js";
30
- import "@base-ui/react/menu";
31
- import "./opencami-logo-C-43FL3R.js";
32
- import "./markdown-BFE5y9YH.js";
33
- import "marked";
34
- import "react-markdown";
35
- import "remark-breaks";
36
- import "remark-gfm";
37
- import "react-dom";
38
- import "./router-Bl2uabfY.js";
39
- import "node:crypto";
40
- import "ws";
41
- import "node:fs";
42
- import "node:path";
43
- import "node:os";
44
- import "@tanstack/router-core/ssr/client";
45
- import "node:child_process";
46
- import "node:fs/promises";
47
- import "path";
48
- function SettingsSection({ title, tabId, activeTab, children }) {
49
- const hiddenOnDesktop = tabId && activeTab && tabId !== activeTab;
50
- return /* @__PURE__ */ jsxs("div", { className: cn(
51
- "border-b border-primary-200 py-2 last:border-0",
52
- hiddenOnDesktop && "md:hidden"
53
- ), children: [
54
- /* @__PURE__ */ jsx("h3", { className: "mb-1.5 text-sm font-semibold text-primary-700", children: title }),
55
- /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children })
56
- ] });
57
- }
58
- function SettingsRow({ label, description, inline, children }) {
59
- if (inline) {
60
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
61
- /* @__PURE__ */ jsxs("div", { className: "flex-1 select-none", children: [
62
- /* @__PURE__ */ jsx("div", { className: "text-[13px] font-medium text-primary-800", children: label }),
63
- description && /* @__PURE__ */ jsx("div", { className: "text-xs text-primary-500", children: description })
64
- ] }),
65
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children })
66
- ] });
67
- }
68
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
69
- /* @__PURE__ */ jsxs("div", { className: "select-none", children: [
70
- /* @__PURE__ */ jsx("div", { className: "text-[13px] font-medium text-primary-800", children: label }),
71
- description && /* @__PURE__ */ jsx("div", { className: "text-xs text-primary-500", children: description })
72
- ] }),
73
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 flex-wrap", children })
74
- ] });
75
- }
76
- const textSizeOptions = [
77
- { value: "14px", label: "S" },
78
- { value: "16px", label: "M" },
79
- { value: "18px", label: "L" },
80
- { value: "20px", label: "XL" }
81
- ];
82
- const fontFamilyOptions = [
83
- {
84
- value: "system",
85
- label: "System",
86
- cssValue: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
87
- previewClass: "font-sans"
88
- },
89
- {
90
- value: "inter",
91
- label: "Inter",
92
- cssValue: '"Inter", sans-serif',
93
- previewClass: 'font-["Inter",sans-serif]'
94
- },
95
- {
96
- value: "roboto",
97
- label: "Roboto",
98
- cssValue: '"Roboto", sans-serif',
99
- previewClass: 'font-["Roboto",sans-serif]'
100
- }
101
- ];
102
- const densityOptions = [
103
- { value: "compact", label: "Compact" },
104
- { value: "comfortable", label: "Comfortable" },
105
- { value: "spacious", label: "Spacious" }
106
- ];
107
- const accentColorOptions = [
108
- { value: "green", label: "Green", accent: "#22c55e", hover: "#16a34a", light: "rgba(34, 197, 94, 0.10)" },
109
- { value: "blue", label: "Blue", accent: "#3b82f6", hover: "#2563eb", light: "rgba(59, 130, 246, 0.10)" },
110
- { value: "purple", label: "Purple", accent: "#8b5cf6", hover: "#7c3aed", light: "rgba(139, 92, 246, 0.10)" },
111
- { value: "orange", label: "Orange", accent: "#f97316", hover: "#ea580c", light: "rgba(249, 115, 22, 0.10)" },
112
- { value: "pink", label: "Pink", accent: "#ec4899", hover: "#db2777", light: "rgba(236, 72, 153, 0.10)" },
113
- { value: "red", label: "Red", accent: "#ef4444", hover: "#dc2626", light: "rgba(239, 68, 68, 0.10)" },
114
- { value: "cyan", label: "Cyan", accent: "#06b6d4", hover: "#0891b2", light: "rgba(6, 182, 212, 0.10)" },
115
- { value: "yellow", label: "Yellow", accent: "#eab308", hover: "#ca8a04", light: "rgba(234, 179, 8, 0.10)" }
116
- ];
117
- const chatWidthOptions = [
118
- { value: "narrow", label: "Narrow", cssValue: "640px" },
119
- { value: "medium", label: "Medium", cssValue: "800px" },
120
- { value: "wide", label: "Wide", cssValue: "1000px" },
121
- { value: "full", label: "Full", cssValue: "100%" }
122
- ];
123
- const sidebarWidthOptions = [
124
- { value: "compact", label: "Compact", cssValue: "200px" },
125
- { value: "normal", label: "Normal", cssValue: "260px" },
126
- { value: "wide", label: "Wide", cssValue: "320px" },
127
- { value: "xl", label: "XL", cssValue: "400px" }
128
- ];
129
- const bubbleStyleOptions = [
130
- { value: "default", label: "Default" },
131
- { value: "bubbles", label: "Bubbles" },
132
- { value: "minimal", label: "Minimal" }
133
- ];
134
- function isTextSizeValue(value) {
135
- return textSizeOptions.some((option) => option.value === value);
136
- }
137
- function isFontFamilyValue(value) {
138
- return fontFamilyOptions.some((option) => option.value === value);
139
- }
140
- function isDensityValue(value) {
141
- return densityOptions.some((option) => option.value === value);
142
- }
143
- function isAccentColorValue(value) {
144
- return accentColorOptions.some((option) => option.value === value);
145
- }
146
- function isChatWidthValue(value) {
147
- return chatWidthOptions.some((option) => option.value === value);
148
- }
149
- function isSidebarWidthValue(value) {
150
- return sidebarWidthOptions.some((option) => option.value === value);
151
- }
152
- function isBubbleStyleValue(value) {
153
- return bubbleStyleOptions.some((option) => option.value === value);
154
- }
155
- function SettingsDialog({
156
- open,
157
- onOpenChange,
158
- onClose
159
- }) {
160
- const { settings, updateSettings } = useChatSettings();
161
- const {
162
- settings: llmSettings,
163
- updateSettings: updateLlmSettings,
164
- status: llmStatus,
165
- testApiKey
166
- } = useLlmSettings();
167
- const [activeTab, setActiveTab] = useState("appearance");
168
- const [apiKeyInput, setApiKeyInput] = useState(llmSettings.llmApiKey);
169
- const [testingKey, setTestingKey] = useState(false);
170
- const [testResult, setTestResult] = useState(null);
171
- const [textSize, setTextSize] = useState(() => {
172
- if (typeof window === "undefined") return "16px";
173
- try {
174
- const stored = localStorage.getItem("opencami-text-size");
175
- if (stored && isTextSizeValue(stored)) return stored;
176
- } catch {
177
- }
178
- return "16px";
179
- });
180
- const [ttsEnabled, setTtsEnabled] = useState(() => {
181
- if (typeof window === "undefined") return true;
182
- try {
183
- const stored = localStorage.getItem("opencami-tts-enabled");
184
- return stored === null ? true : stored === "true";
185
- } catch {
186
- return true;
187
- }
188
- });
189
- const [ttsProvider, setTtsProvider] = useState(() => {
190
- if (typeof window === "undefined") return "auto";
191
- try {
192
- return localStorage.getItem("opencami-tts-provider") || "auto";
193
- } catch {
194
- return "auto";
195
- }
196
- });
197
- const [ttsVoice, setTtsVoice] = useState(() => {
198
- if (typeof window === "undefined") return "nova";
199
- try {
200
- return localStorage.getItem("opencami-tts-voice") || "nova";
201
- } catch {
202
- return "nova";
203
- }
204
- });
205
- const [sttProvider, setSttProvider] = useState(() => {
206
- if (typeof window === "undefined") return "auto";
207
- try {
208
- return localStorage.getItem("opencami-stt-provider") || "auto";
209
- } catch {
210
- return "auto";
211
- }
212
- });
213
- useEffect(() => {
214
- if (typeof window === "undefined") return;
215
- try {
216
- const storedFontFamily = localStorage.getItem("opencami-font-family");
217
- if (storedFontFamily && isFontFamilyValue(storedFontFamily)) {
218
- applyFontFamily(storedFontFamily);
219
- if (settings.fontFamily !== storedFontFamily) {
220
- updateSettings({ fontFamily: storedFontFamily });
221
- }
222
- } else {
223
- applyFontFamily(settings.fontFamily);
224
- }
225
- const storedDensity = localStorage.getItem("opencami-density");
226
- if (storedDensity && isDensityValue(storedDensity)) {
227
- applyDensity(storedDensity);
228
- if (settings.density !== storedDensity) {
229
- updateSettings({ density: storedDensity });
230
- }
231
- } else {
232
- applyDensity(settings.density);
233
- }
234
- const storedAccentColor = localStorage.getItem("opencami-accent-color");
235
- if (storedAccentColor && isAccentColorValue(storedAccentColor)) {
236
- applyAccentColor(storedAccentColor);
237
- if (settings.accentColor !== storedAccentColor) {
238
- updateSettings({ accentColor: storedAccentColor });
239
- }
240
- } else {
241
- applyAccentColor(settings.accentColor);
242
- }
243
- const storedChatWidth = localStorage.getItem("opencami-chat-width");
244
- if (storedChatWidth && isChatWidthValue(storedChatWidth)) {
245
- applyChatWidth(storedChatWidth);
246
- if (settings.chatWidth !== storedChatWidth) {
247
- updateSettings({ chatWidth: storedChatWidth });
248
- }
249
- } else {
250
- applyChatWidth(settings.chatWidth);
251
- }
252
- const storedSidebarWidth = localStorage.getItem("opencami-sidebar-width");
253
- if (storedSidebarWidth && isSidebarWidthValue(storedSidebarWidth)) {
254
- applySidebarWidth(storedSidebarWidth);
255
- if (settings.sidebarWidth !== storedSidebarWidth) {
256
- updateSettings({ sidebarWidth: storedSidebarWidth });
257
- }
258
- } else {
259
- applySidebarWidth(settings.sidebarWidth);
260
- }
261
- const storedBubbleStyle = localStorage.getItem("opencami-bubble-style");
262
- if (storedBubbleStyle && isBubbleStyleValue(storedBubbleStyle)) {
263
- applyBubbleStyle(storedBubbleStyle);
264
- if (settings.bubbleStyle !== storedBubbleStyle) {
265
- updateSettings({ bubbleStyle: storedBubbleStyle });
266
- }
267
- } else {
268
- applyBubbleStyle(settings.bubbleStyle);
269
- }
270
- } catch {
271
- applyFontFamily(settings.fontFamily);
272
- applyDensity(settings.density);
273
- applyAccentColor(settings.accentColor);
274
- applyChatWidth(settings.chatWidth);
275
- applySidebarWidth(settings.sidebarWidth);
276
- applyBubbleStyle(settings.bubbleStyle);
277
- }
278
- }, []);
279
- useEffect(() => {
280
- applyFontFamily(settings.fontFamily);
281
- }, [settings.fontFamily]);
282
- useEffect(() => {
283
- applyDensity(settings.density);
284
- }, [settings.density]);
285
- useEffect(() => {
286
- applyAccentColor(settings.accentColor);
287
- }, [settings.accentColor]);
288
- useEffect(() => {
289
- applyChatWidth(settings.chatWidth);
290
- }, [settings.chatWidth]);
291
- useEffect(() => {
292
- applySidebarWidth(settings.sidebarWidth);
293
- }, [settings.sidebarWidth]);
294
- useEffect(() => {
295
- applyBubbleStyle(settings.bubbleStyle);
296
- }, [settings.bubbleStyle]);
297
- const handleTtsProviderChange = (value) => {
298
- setTtsProvider(value);
299
- try {
300
- localStorage.setItem("opencami-tts-provider", value);
301
- } catch {
302
- }
303
- };
304
- const handleTtsVoiceChange = (value) => {
305
- setTtsVoice(value);
306
- try {
307
- localStorage.setItem("opencami-tts-voice", value);
308
- } catch {
309
- }
310
- };
311
- const handleSttProviderChange = (value) => {
312
- setSttProvider(value);
313
- try {
314
- localStorage.setItem("opencami-stt-provider", value);
315
- } catch {
316
- }
317
- };
318
- function applyTextSize(value) {
319
- if (typeof document === "undefined") return;
320
- document.documentElement.style.setProperty("--opencami-text-size", value);
321
- }
322
- function applyFontFamily(value) {
323
- if (typeof document === "undefined") return;
324
- const selected = fontFamilyOptions.find((option) => option.value === value);
325
- const cssValue = selected?.cssValue ?? fontFamilyOptions[0].cssValue;
326
- document.documentElement.style.setProperty("--opencami-font-family", cssValue);
327
- }
328
- function applyDensity(value) {
329
- if (typeof document === "undefined") return;
330
- const root = document.documentElement;
331
- root.style.setProperty("--opencami-density", value);
332
- if (value === "compact") {
333
- root.style.setProperty("--opencami-msg-padding-y", "0.25rem");
334
- root.style.setProperty("--opencami-msg-gap", "0.25rem");
335
- root.style.setProperty("--opencami-user-bubble-py", "0.4rem");
336
- return;
337
- }
338
- if (value === "spacious") {
339
- root.style.setProperty("--opencami-msg-padding-y", "1.25rem");
340
- root.style.setProperty("--opencami-msg-gap", "1rem");
341
- root.style.setProperty("--opencami-user-bubble-py", "1rem");
342
- return;
343
- }
344
- root.style.setProperty("--opencami-msg-padding-y", "0.75rem");
345
- root.style.setProperty("--opencami-msg-gap", "0.5rem");
346
- root.style.setProperty("--opencami-user-bubble-py", "0.625rem");
347
- }
348
- function applyAccentColor(value) {
349
- if (typeof document === "undefined") return;
350
- const selected = accentColorOptions.find((option) => option.value === value) ?? accentColorOptions[0];
351
- const root = document.documentElement;
352
- root.style.setProperty("--opencami-accent", selected.accent);
353
- root.style.setProperty("--opencami-accent-hover", selected.hover);
354
- root.style.setProperty("--opencami-accent-light", selected.light);
355
- }
356
- function applyChatWidth(value) {
357
- if (typeof document === "undefined") return;
358
- const selected = chatWidthOptions.find((option) => option.value === value) ?? chatWidthOptions[2];
359
- document.documentElement.style.setProperty("--opencami-chat-width", selected.cssValue);
360
- }
361
- function applySidebarWidth(value) {
362
- if (typeof document === "undefined") return;
363
- const selected = sidebarWidthOptions.find((option) => option.value === value) ?? sidebarWidthOptions[1];
364
- document.documentElement.style.setProperty("--opencami-sidebar-width", selected.cssValue);
365
- }
366
- function applyBubbleStyle(value) {
367
- if (typeof document === "undefined") return;
368
- document.documentElement.setAttribute("data-opencami-bubble-style", value);
369
- }
370
- const handleTextSizeChange = (value) => {
371
- if (!isTextSizeValue(value)) return;
372
- setTextSize(value);
373
- applyTextSize(value);
374
- try {
375
- localStorage.setItem("opencami-text-size", value);
376
- } catch {
377
- }
378
- };
379
- const handleFontFamilyChange = (value) => {
380
- if (!isFontFamilyValue(value)) return;
381
- applyFontFamily(value);
382
- updateSettings({ fontFamily: value });
383
- try {
384
- localStorage.setItem("opencami-font-family", value);
385
- } catch {
386
- }
387
- };
388
- const handleDensityChange = (value) => {
389
- if (!isDensityValue(value)) return;
390
- applyDensity(value);
391
- updateSettings({ density: value });
392
- try {
393
- localStorage.setItem("opencami-density", value);
394
- } catch {
395
- }
396
- };
397
- const handleAccentColorChange = (value) => {
398
- if (!isAccentColorValue(value)) return;
399
- applyAccentColor(value);
400
- updateSettings({ accentColor: value });
401
- try {
402
- localStorage.setItem("opencami-accent-color", value);
403
- } catch {
404
- }
405
- };
406
- const handleChatWidthChange = (value) => {
407
- if (!isChatWidthValue(value)) return;
408
- applyChatWidth(value);
409
- updateSettings({ chatWidth: value });
410
- try {
411
- localStorage.setItem("opencami-chat-width", value);
412
- } catch {
413
- }
414
- };
415
- const handleSidebarWidthChange = (value) => {
416
- if (!isSidebarWidthValue(value)) return;
417
- applySidebarWidth(value);
418
- updateSettings({ sidebarWidth: value });
419
- try {
420
- localStorage.setItem("opencami-sidebar-width", value);
421
- } catch {
422
- }
423
- };
424
- const handleBubbleStyleChange = (value) => {
425
- if (!isBubbleStyleValue(value)) return;
426
- applyBubbleStyle(value);
427
- updateSettings({ bubbleStyle: value });
428
- try {
429
- localStorage.setItem("opencami-bubble-style", value);
430
- } catch {
431
- }
432
- };
433
- const handleTtsToggle = (checked) => {
434
- setTtsEnabled(checked);
435
- try {
436
- localStorage.setItem("opencami-tts-enabled", String(checked));
437
- } catch {
438
- }
439
- window.dispatchEvent(new StorageEvent("storage", {
440
- key: "opencami-tts-enabled",
441
- newValue: String(checked)
442
- }));
443
- };
444
- const [personasAvailable, setPersonasAvailable] = useState(false);
445
- const personasAbortControllerRef = useRef(null);
446
- const [personasEnabled, setPersonasEnabled] = useState(() => {
447
- if (typeof window === "undefined") return true;
448
- try {
449
- const stored = localStorage.getItem("opencami-personas-enabled");
450
- return stored === null ? true : stored === "true";
451
- } catch {
452
- return true;
453
- }
454
- });
455
- useEffect(() => {
456
- let mounted = true;
457
- async function checkPersonas() {
458
- personasAbortControllerRef.current?.abort();
459
- const controller = new AbortController();
460
- personasAbortControllerRef.current = controller;
461
- try {
462
- const res = await fetch("/api/personas", { signal: controller.signal });
463
- if (!res.ok) return;
464
- const data = await res.json();
465
- if (mounted && data.ok) {
466
- setPersonasAvailable(data.available);
467
- }
468
- } catch (error) {
469
- if (error instanceof Error && error.name === "AbortError") return;
470
- }
471
- }
472
- checkPersonas();
473
- return () => {
474
- mounted = false;
475
- personasAbortControllerRef.current?.abort();
476
- };
477
- }, []);
478
- const handlePersonasToggle = (checked) => {
479
- setPersonasEnabled(checked);
480
- try {
481
- localStorage.setItem("opencami-personas-enabled", String(checked));
482
- } catch {
483
- }
484
- window.dispatchEvent(new StorageEvent("storage", {
485
- key: "opencami-personas-enabled",
486
- newValue: String(checked)
487
- }));
488
- };
489
- const handleTestApiKey = async () => {
490
- if (!apiKeyInput.trim()) return;
491
- setTestingKey(true);
492
- setTestResult(null);
493
- try {
494
- const result = await testApiKey(apiKeyInput.trim());
495
- setTestResult(result);
496
- if (result.valid) {
497
- updateLlmSettings({ llmApiKey: apiKeyInput.trim() });
498
- }
499
- } finally {
500
- setTestingKey(false);
501
- }
502
- };
503
- const handleSaveApiKey = () => {
504
- updateLlmSettings({ llmApiKey: apiKeyInput.trim() });
505
- setTestResult(null);
506
- };
507
- const handleClearApiKey = () => {
508
- setApiKeyInput("");
509
- updateLlmSettings({ llmApiKey: "" });
510
- setTestResult(null);
511
- };
512
- const themeOptions = [
513
- { value: "system", label: "Auto", icon: ComputerIcon },
514
- { value: "light", label: "Light", icon: Sun01Icon },
515
- { value: "dark", label: "Dark", icon: Moon01Icon },
516
- { value: "chameleon", label: "Cham", icon: Leaf01Icon },
517
- { value: "frost-light", label: "Ice", icon: DropletIcon }
518
- ];
519
- function applyTheme(theme) {
520
- if (typeof document === "undefined") return;
521
- const root = document.documentElement;
522
- const media = window.matchMedia("(prefers-color-scheme: dark)");
523
- root.classList.remove("light", "dark", "system", "chameleon", "frost", "frost-light", "frost-dark");
524
- root.style.colorScheme = "";
525
- root.classList.add(theme);
526
- const isFrostLight = theme === "frost-light";
527
- const isFrostDark = theme === "frost-dark";
528
- if (isFrostLight || isFrostDark) {
529
- root.classList.add("frost");
530
- }
531
- if (isFrostDark) {
532
- root.classList.add("dark");
533
- }
534
- if (theme === "system" && media.matches) {
535
- root.classList.add("dark");
536
- }
537
- if (isFrostLight) {
538
- root.classList.remove("dark");
539
- root.style.colorScheme = "light";
540
- }
541
- }
542
- return /* @__PURE__ */ jsx(DialogRoot, { open, onOpenChange, children: /* @__PURE__ */ jsx(DialogContent, { className: "w-[min(680px,92vw)] max-h-[85vh] overflow-y-auto", children: /* @__PURE__ */ jsxs("div", { className: "p-3", children: [
543
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
544
- /* @__PURE__ */ jsxs("div", { children: [
545
- /* @__PURE__ */ jsx(DialogTitle, { className: "mb-1", children: "Settings" }),
546
- /* @__PURE__ */ jsx(DialogDescription, { className: "hidden", children: "Configure OpenCami" })
547
- ] }),
548
- /* @__PURE__ */ jsx(
549
- DialogClose,
550
- {
551
- render: /* @__PURE__ */ jsx(
552
- Button,
553
- {
554
- size: "icon-sm",
555
- variant: "ghost",
556
- className: "text-primary-500 hover:bg-primary-100 hover:text-primary-700",
557
- "aria-label": "Close",
558
- children: /* @__PURE__ */ jsx(
559
- HugeiconsIcon,
560
- {
561
- icon: Cancel01Icon,
562
- size: 20,
563
- strokeWidth: 1.5
564
- }
565
- )
566
- }
567
- )
568
- }
569
- )
570
- ] }),
571
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex md:flex-row flex-col md:gap-3 gap-0 md:min-h-[400px]", children: [
572
- /* @__PURE__ */ jsx("nav", { className: "hidden md:flex flex-col gap-0.5 min-w-[160px] border-r border-primary-200 pr-3", children: [
573
- { id: "connection", label: "Connection", icon: Link01Icon },
574
- { id: "appearance", label: "Appearance", icon: PaintBoardIcon },
575
- { id: "chat", label: "Chat", icon: MessageEdit01Icon },
576
- { id: "workspace", label: "Workspace", icon: Settings02Icon },
577
- { id: "personas", label: "Personas", icon: UserIcon },
578
- { id: "voice", label: "Voice", icon: VoiceIcon },
579
- { id: "llm", label: "LLM Features", icon: AiBrain01Icon },
580
- { id: "about", label: "About", icon: InformationCircleIcon }
581
- ].map((tab) => /* @__PURE__ */ jsxs(
582
- "button",
583
- {
584
- onClick: () => setActiveTab(tab.id),
585
- className: cn(
586
- "flex items-center gap-2 px-2.5 py-1.5 rounded-md text-sm text-left transition-colors",
587
- activeTab === tab.id ? "text-primary-900 font-medium" : "text-primary-600 hover:text-primary-900 hover:bg-primary-50"
588
- ),
589
- style: activeTab === tab.id ? { backgroundColor: "var(--opencami-accent-light)" } : void 0,
590
- children: [
591
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: tab.icon, size: 16, strokeWidth: 1.5 }),
592
- tab.label
593
- ]
594
- },
595
- tab.id
596
- )) }),
597
- /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto md:max-h-[calc(85vh-90px)] max-h-none", children: [
598
- /* @__PURE__ */ jsx(SettingsSection, { title: "Connection", tabId: "connection", activeTab, children: /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Status", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-sm text-green-600", children: [
599
- /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-green-500" }),
600
- "Connected"
601
- ] }) }) }),
602
- /* @__PURE__ */ jsxs(SettingsSection, { title: "Appearance", tabId: "appearance", activeTab, children: [
603
- /* @__PURE__ */ jsx(SettingsRow, { label: "Theme", children: /* @__PURE__ */ jsx(
604
- Tabs,
605
- {
606
- value: settings.theme,
607
- onValueChange: (value) => {
608
- const theme = value;
609
- applyTheme(theme);
610
- updateSettings({ theme });
611
- },
612
- children: /* @__PURE__ */ jsx(
613
- TabsList,
614
- {
615
- variant: "default",
616
- className: "gap-1.5 flex-wrap *:data-[slot=tab-indicator]:duration-0",
617
- children: themeOptions.map((option) => /* @__PURE__ */ jsxs(TabsTab, { value: option.value, className: "text-[11px] px-1 py-0.5 gap-0.5", children: [
618
- option.icon && /* @__PURE__ */ jsx(
619
- HugeiconsIcon,
620
- {
621
- icon: option.icon,
622
- size: 13,
623
- strokeWidth: 1.5
624
- }
625
- ),
626
- /* @__PURE__ */ jsx("span", { children: option.label })
627
- ] }, option.value))
628
- }
629
- )
630
- }
631
- ) }),
632
- /* @__PURE__ */ jsx(
633
- SettingsRow,
634
- {
635
- label: "Accent Color",
636
- children: /* @__PURE__ */ jsx("div", { className: "flex gap-0.5 flex-wrap", children: accentColorOptions.map((option) => {
637
- const selected = settings.accentColor === option.value;
638
- return /* @__PURE__ */ jsx(
639
- "button",
640
- {
641
- type: "button",
642
- onClick: () => handleAccentColorChange(option.value),
643
- className: cn(
644
- "flex items-center justify-center rounded-md p-1 transition-all duration-150",
645
- selected ? "scale-110" : "hover:bg-primary-50 hover:scale-105"
646
- ),
647
- children: /* @__PURE__ */ jsx(
648
- "span",
649
- {
650
- className: cn(
651
- "size-6 rounded-full transition-shadow duration-150",
652
- selected ? "ring-2 ring-offset-2" : "border-2 border-primary-200"
653
- ),
654
- style: {
655
- backgroundColor: option.accent,
656
- ...selected ? { "--tw-ring-color": option.accent } : {}
657
- }
658
- }
659
- )
660
- },
661
- option.value
662
- );
663
- }) })
664
- }
665
- ),
666
- /* @__PURE__ */ jsx(
667
- SettingsRow,
668
- {
669
- label: "Text Size",
670
- children: /* @__PURE__ */ jsx(Tabs, { value: textSize, onValueChange: handleTextSizeChange, children: /* @__PURE__ */ jsx(
671
- TabsList,
672
- {
673
- variant: "default",
674
- className: "gap-2 *:data-[slot=tab-indicator]:duration-0",
675
- children: textSizeOptions.map((option) => /* @__PURE__ */ jsx(TabsTab, { value: option.value, children: /* @__PURE__ */ jsx("span", { className: "tabular-nums text-xs", children: option.label }) }, option.value))
676
- }
677
- ) })
678
- }
679
- ),
680
- /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Font", children: /* @__PURE__ */ jsx(Tabs, { value: settings.fontFamily, onValueChange: handleFontFamilyChange, children: /* @__PURE__ */ jsx(TabsList, { variant: "default", className: "gap-1 *:data-[slot=tab-indicator]:duration-0", children: fontFamilyOptions.map((option) => /* @__PURE__ */ jsx(TabsTab, { value: option.value, children: /* @__PURE__ */ jsx("span", { className: "text-xs", style: { fontFamily: option.cssValue }, children: option.label }) }, option.value)) }) }) }),
681
- /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Density", children: /* @__PURE__ */ jsx(Tabs, { value: settings.density, onValueChange: handleDensityChange, children: /* @__PURE__ */ jsx(TabsList, { variant: "default", className: "gap-1 *:data-[slot=tab-indicator]:duration-0", children: densityOptions.map((option) => /* @__PURE__ */ jsx(TabsTab, { value: option.value, children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: option.label }) }, option.value)) }) }) }),
682
- /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Bubbles", children: /* @__PURE__ */ jsx(Tabs, { value: settings.bubbleStyle, onValueChange: handleBubbleStyleChange, children: /* @__PURE__ */ jsx(TabsList, { variant: "default", className: "gap-1 *:data-[slot=tab-indicator]:duration-0", children: bubbleStyleOptions.map((option) => /* @__PURE__ */ jsx(TabsTab, { value: option.value, children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: option.label }) }, option.value)) }) }) }),
683
- /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Chat Width", children: /* @__PURE__ */ jsx(Tabs, { value: settings.chatWidth, onValueChange: handleChatWidthChange, children: /* @__PURE__ */ jsx(TabsList, { variant: "default", className: "gap-1 *:data-[slot=tab-indicator]:duration-0", children: chatWidthOptions.map((option) => /* @__PURE__ */ jsx(TabsTab, { value: option.value, children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: option.label }) }, option.value)) }) }) }),
684
- /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Sidebar", children: /* @__PURE__ */ jsx(Tabs, { value: settings.sidebarWidth, onValueChange: handleSidebarWidthChange, children: /* @__PURE__ */ jsx(TabsList, { variant: "default", className: "gap-1 *:data-[slot=tab-indicator]:duration-0", children: sidebarWidthOptions.map((option) => /* @__PURE__ */ jsx(TabsTab, { value: option.value, children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: option.label }) }, option.value)) }) }) })
685
- ] }),
686
- /* @__PURE__ */ jsxs(SettingsSection, { title: "Chat", tabId: "chat", activeTab, children: [
687
- /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Show tool messages", children: /* @__PURE__ */ jsx(
688
- Switch,
689
- {
690
- checked: settings.showToolMessages,
691
- onCheckedChange: (checked) => updateSettings({ showToolMessages: checked })
692
- }
693
- ) }),
694
- /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Show reasoning blocks", children: /* @__PURE__ */ jsx(
695
- Switch,
696
- {
697
- checked: settings.showReasoningBlocks,
698
- onCheckedChange: (checked) => updateSettings({ showReasoningBlocks: checked })
699
- }
700
- ) }),
701
- /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Show search sources", children: /* @__PURE__ */ jsx(
702
- Switch,
703
- {
704
- checked: settings.showSearchSources,
705
- onCheckedChange: (checked) => updateSettings({ showSearchSources: checked })
706
- }
707
- ) }),
708
- /* @__PURE__ */ jsx(
709
- SettingsRow,
710
- {
711
- inline: true,
712
- label: "Inline File Preview",
713
- description: "Make file paths in messages clickable to preview file contents",
714
- children: /* @__PURE__ */ jsx(
715
- Switch,
716
- {
717
- checked: settings.inlineFilePreview,
718
- onCheckedChange: (checked) => updateSettings({ inlineFilePreview: checked })
719
- }
720
- )
721
- }
722
- )
723
- ] }),
724
- /* @__PURE__ */ jsxs(SettingsSection, { title: "Workspace", tabId: "workspace", activeTab, children: [
725
- /* @__PURE__ */ jsx(
726
- SettingsRow,
727
- {
728
- inline: true,
729
- label: "File Explorer",
730
- description: "Browse and edit workspace files from the sidebar",
731
- children: /* @__PURE__ */ jsx(
732
- Switch,
733
- {
734
- checked: (() => {
735
- try {
736
- const v = localStorage.getItem("opencami-file-explorer");
737
- return v === null ? true : v === "true";
738
- } catch {
739
- return true;
740
- }
741
- })(),
742
- onCheckedChange: (checked) => {
743
- localStorage.setItem("opencami-file-explorer", String(checked));
744
- window.location.reload();
745
- }
746
- }
747
- )
748
- }
749
- ),
750
- /* @__PURE__ */ jsx(
751
- SettingsRow,
752
- {
753
- inline: true,
754
- label: "Memory Viewer",
755
- description: "Browse and edit MEMORY.md and daily memory files",
756
- children: /* @__PURE__ */ jsx(
757
- Switch,
758
- {
759
- checked: (() => {
760
- try {
761
- const v = localStorage.getItem("opencami-memory-viewer");
762
- return v === null ? true : v === "true";
763
- } catch {
764
- return true;
765
- }
766
- })(),
767
- onCheckedChange: (checked) => {
768
- localStorage.setItem("opencami-memory-viewer", String(checked));
769
- window.location.reload();
770
- }
771
- }
772
- )
773
- }
774
- ),
775
- /* @__PURE__ */ jsx(
776
- SettingsRow,
777
- {
778
- inline: true,
779
- label: "Agent Manager (Beta)",
780
- description: "Show Agent Manager in sidebar for creating and managing agents",
781
- children: /* @__PURE__ */ jsx(
782
- Switch,
783
- {
784
- checked: (() => {
785
- try {
786
- return localStorage.getItem("opencami-agent-manager") === "true";
787
- } catch {
788
- return false;
789
- }
790
- })(),
791
- onCheckedChange: (checked) => {
792
- localStorage.setItem("opencami-agent-manager", String(checked));
793
- window.location.reload();
794
- }
795
- }
796
- )
797
- }
798
- ),
799
- /* @__PURE__ */ jsx(
800
- SettingsRow,
801
- {
802
- inline: true,
803
- label: "Skills Browser (Beta)",
804
- description: "Browse and install skills from ClawHub",
805
- children: /* @__PURE__ */ jsx(
806
- Switch,
807
- {
808
- checked: (() => {
809
- try {
810
- return localStorage.getItem("opencami-skills-browser") === "true";
811
- } catch {
812
- return false;
813
- }
814
- })(),
815
- onCheckedChange: (checked) => {
816
- localStorage.setItem("opencami-skills-browser", String(checked));
817
- window.location.reload();
818
- }
819
- }
820
- )
821
- }
822
- ),
823
- /* @__PURE__ */ jsx(
824
- SettingsRow,
825
- {
826
- inline: true,
827
- label: "Cron Jobs Panel (Beta)",
828
- description: "Show Cron Jobs in sidebar for managing OpenClaw cron schedules",
829
- children: /* @__PURE__ */ jsx(
830
- Switch,
831
- {
832
- checked: (() => {
833
- try {
834
- return localStorage.getItem("opencami-cron-manager") === "true";
835
- } catch {
836
- return false;
837
- }
838
- })(),
839
- onCheckedChange: (checked) => {
840
- localStorage.setItem("opencami-cron-manager", String(checked));
841
- window.location.reload();
842
- }
843
- }
844
- )
845
- }
846
- )
847
- ] }),
848
- /* @__PURE__ */ jsxs(SettingsSection, { title: "Personas", tabId: "personas", activeTab, children: [
849
- personasAvailable ? /* @__PURE__ */ jsx(
850
- SettingsRow,
851
- {
852
- inline: true,
853
- label: "Persona Picker",
854
- description: "Show persona picker in chat (20 personalities)",
855
- children: /* @__PURE__ */ jsx(
856
- Switch,
857
- {
858
- checked: personasEnabled,
859
- onCheckedChange: handlePersonasToggle
860
- }
861
- )
862
- }
863
- ) : /* @__PURE__ */ jsx(
864
- SettingsRow,
865
- {
866
- inline: true,
867
- label: "Persona Picker",
868
- description: "Install the Personas skill to unlock 20 AI personalities",
869
- children: /* @__PURE__ */ jsx(Switch, { checked: false, disabled: true })
870
- }
871
- ),
872
- !personasAvailable && /* @__PURE__ */ jsx("div", { className: "pt-1", children: /* @__PURE__ */ jsx(
873
- "a",
874
- {
875
- href: "https://www.clawhub.ai/robbyczgw-cla/personas",
876
- target: "_blank",
877
- rel: "noopener noreferrer",
878
- className: "text-xs text-primary-600 hover:text-primary-900 hover:underline",
879
- children: "Get it on ClawHub →"
880
- }
881
- ) })
882
- ] }),
883
- /* @__PURE__ */ jsxs(SettingsSection, { title: "Text-to-Speech", tabId: "voice", activeTab, children: [
884
- /* @__PURE__ */ jsx(
885
- SettingsRow,
886
- {
887
- inline: true,
888
- label: "Voice Playback",
889
- description: "Add a 🔊 button to AI messages for text-to-speech",
890
- children: /* @__PURE__ */ jsx(
891
- Switch,
892
- {
893
- checked: ttsEnabled,
894
- onCheckedChange: handleTtsToggle
895
- }
896
- )
897
- }
898
- ),
899
- /* @__PURE__ */ jsx(
900
- SettingsRow,
901
- {
902
- inline: true,
903
- label: "TTS Provider",
904
- description: "Choose which service generates speech",
905
- children: /* @__PURE__ */ jsxs(
906
- "select",
907
- {
908
- value: ttsProvider,
909
- onChange: (e) => handleTtsProviderChange(e.target.value),
910
- className: "rounded-md border border-primary-200 bg-surface px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500",
911
- children: [
912
- /* @__PURE__ */ jsx("option", { value: "auto", children: "Auto" }),
913
- /* @__PURE__ */ jsx("option", { value: "elevenlabs", children: "ElevenLabs" }),
914
- /* @__PURE__ */ jsx("option", { value: "openai", children: "OpenAI" }),
915
- /* @__PURE__ */ jsx("option", { value: "edge", children: "Edge TTS (free)" })
916
- ]
917
- }
918
- )
919
- }
920
- ),
921
- ttsProvider === "openai" && /* @__PURE__ */ jsx(
922
- SettingsRow,
923
- {
924
- inline: true,
925
- label: "Voice",
926
- description: "OpenAI voice selection",
927
- children: /* @__PURE__ */ jsx(
928
- "select",
929
- {
930
- value: ttsVoice,
931
- onChange: (e) => handleTtsVoiceChange(e.target.value),
932
- className: "rounded-md border border-primary-200 bg-surface px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500",
933
- children: ["alloy", "echo", "fable", "onyx", "nova", "shimmer"].map((v) => /* @__PURE__ */ jsx("option", { value: v, children: v.charAt(0).toUpperCase() + v.slice(1) }, v))
934
- }
935
- )
936
- }
937
- )
938
- ] }),
939
- /* @__PURE__ */ jsx(SettingsSection, { title: "Speech-to-Text", tabId: "voice", activeTab, children: /* @__PURE__ */ jsx(
940
- SettingsRow,
941
- {
942
- inline: true,
943
- label: "STT Provider",
944
- description: "Choose which service transcribes your voice",
945
- children: /* @__PURE__ */ jsxs(
946
- "select",
947
- {
948
- value: sttProvider,
949
- onChange: (e) => handleSttProviderChange(e.target.value),
950
- className: "rounded-md border border-primary-200 bg-surface px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500",
951
- children: [
952
- /* @__PURE__ */ jsx("option", { value: "auto", children: "Auto" }),
953
- /* @__PURE__ */ jsx("option", { value: "elevenlabs", children: "ElevenLabs" }),
954
- /* @__PURE__ */ jsx("option", { value: "openai", children: "OpenAI" }),
955
- /* @__PURE__ */ jsx("option", { value: "browser", children: "Browser (free)" })
956
- ]
957
- }
958
- )
959
- }
960
- ) }),
961
- /* @__PURE__ */ jsxs(SettingsSection, { title: "LLM Features", tabId: "llm", activeTab, children: [
962
- /* @__PURE__ */ jsxs("div", { className: "text-xs text-primary-500 mb-3", children: [
963
- "Enhance session titles and follow-up suggestions using an LLM provider.",
964
- llmStatus.hasEnvKey && llmSettings.llmProvider === "openai" && /* @__PURE__ */ jsx("span", { className: "block mt-1 text-green-600", children: "✓ Server has OPENAI_API_KEY configured" }),
965
- llmStatus.hasOpenRouterKey && llmSettings.llmProvider === "openrouter" && /* @__PURE__ */ jsx("span", { className: "block mt-1 text-green-600", children: "✓ Server has OPENROUTER_API_KEY configured" })
966
- ] }),
967
- /* @__PURE__ */ jsx(
968
- SettingsRow,
969
- {
970
- inline: true,
971
- label: "Provider",
972
- description: "Choose LLM provider for titles & follow-ups",
973
- children: /* @__PURE__ */ jsxs(
974
- "select",
975
- {
976
- value: llmSettings.llmProvider,
977
- onChange: (e) => {
978
- const provider = e.target.value;
979
- updateLlmSettings({
980
- llmProvider: provider,
981
- llmBaseUrl: "",
982
- llmModel: ""
983
- });
984
- },
985
- className: "px-2 py-1 text-sm rounded-md border border-primary-200 bg-surface focus:outline-none focus:ring-2 focus:ring-primary-500",
986
- children: [
987
- /* @__PURE__ */ jsx("option", { value: "openai", children: "OpenAI" }),
988
- /* @__PURE__ */ jsx("option", { value: "openrouter", children: "OpenRouter" }),
989
- /* @__PURE__ */ jsx("option", { value: "ollama", children: "Ollama (local)" }),
990
- /* @__PURE__ */ jsx("option", { value: "custom", children: "Custom" })
991
- ]
992
- }
993
- )
994
- }
995
- ),
996
- /* @__PURE__ */ jsx(
997
- SettingsRow,
998
- {
999
- inline: true,
1000
- label: "Smart session titles",
1001
- description: "Generate concise titles using AI",
1002
- children: /* @__PURE__ */ jsx(
1003
- Switch,
1004
- {
1005
- checked: llmSettings.useLlmTitles,
1006
- onCheckedChange: (checked) => updateLlmSettings({ useLlmTitles: checked }),
1007
- disabled: !llmStatus.isAvailable
1008
- }
1009
- )
1010
- }
1011
- ),
1012
- /* @__PURE__ */ jsx(
1013
- SettingsRow,
1014
- {
1015
- inline: true,
1016
- label: "Smart follow-up suggestions",
1017
- description: "AI-generated contextual follow-ups",
1018
- children: /* @__PURE__ */ jsx(
1019
- Switch,
1020
- {
1021
- checked: llmSettings.useLlmFollowUps,
1022
- onCheckedChange: (checked) => updateLlmSettings({ useLlmFollowUps: checked }),
1023
- disabled: !llmStatus.isAvailable
1024
- }
1025
- )
1026
- }
1027
- ),
1028
- /* @__PURE__ */ jsxs("div", { className: "mt-2 space-y-2", children: [
1029
- /* @__PURE__ */ jsxs("div", { children: [
1030
- /* @__PURE__ */ jsx("div", { className: "text-xs text-primary-500 mb-1", children: "Model" }),
1031
- /* @__PURE__ */ jsx(
1032
- "input",
1033
- {
1034
- type: "text",
1035
- value: llmSettings.llmModel,
1036
- onChange: (e) => updateLlmSettings({ llmModel: e.target.value }),
1037
- placeholder: getLlmProviderDefaults(llmSettings.llmProvider).model || "model-name",
1038
- className: "w-full px-3 py-1.5 text-sm rounded-md border border-primary-200 bg-surface focus:outline-none focus:ring-2 focus:ring-primary-500"
1039
- }
1040
- )
1041
- ] }),
1042
- (llmSettings.llmProvider === "custom" || llmSettings.llmProvider === "ollama") && /* @__PURE__ */ jsxs("div", { children: [
1043
- /* @__PURE__ */ jsx("div", { className: "text-xs text-primary-500 mb-1", children: "Base URL" }),
1044
- /* @__PURE__ */ jsx(
1045
- "input",
1046
- {
1047
- type: "text",
1048
- value: llmSettings.llmBaseUrl,
1049
- onChange: (e) => updateLlmSettings({ llmBaseUrl: e.target.value }),
1050
- placeholder: getLlmProviderDefaults(llmSettings.llmProvider).baseUrl || "https://...",
1051
- className: "w-full px-3 py-1.5 text-sm rounded-md border border-primary-200 bg-surface focus:outline-none focus:ring-2 focus:ring-primary-500"
1052
- }
1053
- )
1054
- ] })
1055
- ] }),
1056
- /* @__PURE__ */ jsxs("div", { className: "mt-4 pt-3 border-t border-primary-100", children: [
1057
- /* @__PURE__ */ jsx("div", { className: "text-sm text-primary-800 mb-2", children: llmSettings.llmProvider === "ollama" ? "API Key (optional)" : "API Key" }),
1058
- /* @__PURE__ */ jsx("div", { className: "text-xs text-primary-500 mb-2", children: llmSettings.llmProvider === "ollama" ? "Not required for local Ollama" : llmStatus.hasEnvKey && llmSettings.llmProvider === "openai" ? "Optional: Override server key with your own" : `Required for ${llmSettings.llmProvider === "openrouter" ? "OpenRouter" : "LLM features"} (stored locally)` }),
1059
- /* @__PURE__ */ jsxs("div", { className: "text-xs text-amber-600 bg-amber-50 border border-amber-200 rounded-md px-2 py-1.5 mb-2", children: [
1060
- "⚠️ ",
1061
- /* @__PURE__ */ jsx("strong", { children: "Security Note:" }),
1062
- " API keys are stored in your browser's localStorage. This is convenient but not secure for shared computers. For production use, configure keys server-side via environment variables (OPENAI_API_KEY, OPENROUTER_API_KEY)."
1063
- ] }),
1064
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1065
- /* @__PURE__ */ jsx(
1066
- "input",
1067
- {
1068
- type: "password",
1069
- value: apiKeyInput,
1070
- onChange: (e) => {
1071
- setApiKeyInput(e.target.value);
1072
- setTestResult(null);
1073
- },
1074
- placeholder: "sk-...",
1075
- className: "flex-1 px-3 py-1.5 text-sm rounded-md border border-primary-200 bg-surface focus:outline-none focus:ring-2 focus:ring-primary-500"
1076
- }
1077
- ),
1078
- /* @__PURE__ */ jsx(
1079
- Button,
1080
- {
1081
- size: "sm",
1082
- variant: "outline",
1083
- onClick: handleTestApiKey,
1084
- disabled: !apiKeyInput.trim() || testingKey,
1085
- className: "min-w-[60px]",
1086
- children: testingKey ? /* @__PURE__ */ jsx(
1087
- HugeiconsIcon,
1088
- {
1089
- icon: Loading02Icon,
1090
- size: 16,
1091
- className: "animate-spin"
1092
- }
1093
- ) : "Test"
1094
- }
1095
- )
1096
- ] }),
1097
- testResult && /* @__PURE__ */ jsxs(
1098
- "div",
1099
- {
1100
- className: `mt-2 flex items-center gap-1.5 text-xs ${testResult.valid ? "text-green-600" : "text-red-600"}`,
1101
- children: [
1102
- /* @__PURE__ */ jsx(
1103
- HugeiconsIcon,
1104
- {
1105
- icon: testResult.valid ? Tick01Icon : Cancel02Icon,
1106
- size: 14
1107
- }
1108
- ),
1109
- testResult.valid ? "API key is valid" : testResult.error || "Invalid API key"
1110
- ]
1111
- }
1112
- ),
1113
- llmSettings.llmApiKey && /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center justify-between", children: [
1114
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-green-600 flex items-center gap-1", children: [
1115
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Tick01Icon, size: 14 }),
1116
- "Key saved"
1117
- ] }),
1118
- /* @__PURE__ */ jsx(
1119
- Button,
1120
- {
1121
- size: "sm",
1122
- variant: "ghost",
1123
- onClick: handleClearApiKey,
1124
- className: "text-xs text-red-600 hover:text-red-700 hover:bg-red-50",
1125
- children: "Clear"
1126
- }
1127
- )
1128
- ] }),
1129
- apiKeyInput && apiKeyInput !== llmSettings.llmApiKey && !testResult?.valid && /* @__PURE__ */ jsx(
1130
- Button,
1131
- {
1132
- size: "sm",
1133
- variant: "outline",
1134
- onClick: handleSaveApiKey,
1135
- className: "mt-2 w-full",
1136
- children: "Save without testing"
1137
- }
1138
- )
1139
- ] })
1140
- ] }),
1141
- /* @__PURE__ */ jsxs(SettingsSection, { title: "About", tabId: "about", activeTab, children: [
1142
- /* @__PURE__ */ jsx("div", { className: "text-sm text-primary-800", children: "OpenCami" }),
1143
- /* @__PURE__ */ jsxs("div", { className: "flex gap-4 pt-2", children: [
1144
- /* @__PURE__ */ jsx(
1145
- "a",
1146
- {
1147
- href: "https://github.com/robbyczgw-cla/opencami",
1148
- target: "_blank",
1149
- rel: "noopener noreferrer",
1150
- className: "text-sm text-primary-600 hover:text-primary-900 hover:underline",
1151
- children: "GitHub"
1152
- }
1153
- ),
1154
- /* @__PURE__ */ jsx(
1155
- "a",
1156
- {
1157
- href: "https://docs.openclaw.ai",
1158
- target: "_blank",
1159
- rel: "noopener noreferrer",
1160
- className: "text-sm text-primary-600 hover:text-primary-900 hover:underline",
1161
- children: "OpenClaw docs"
1162
- }
1163
- )
1164
- ] })
1165
- ] })
1166
- ] })
1167
- ] }),
1168
- /* @__PURE__ */ jsx("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ jsx(DialogClose, { onClick: onClose, children: "Close" }) })
1169
- ] }) }) });
1170
- }
1171
- export {
1172
- SettingsDialog
1173
- };