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
@@ -0,0 +1,1511 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef } from "react";
3
+ import { create } from "zustand";
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 { B as Button, c as cn } from "./button-CwY2OHFj.js";
7
+ import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose } from "./use-file-explorer-state-s7CS50ho.js";
8
+ import { S as Switch } from "./switch-BbkUeVDV.js";
9
+ import { T as Tabs, a as TabsList, b as TabsTab } from "./tabs-DDFZob0m.js";
10
+ import { u as useChatSettings } from "./index-Dl2BOKP7.js";
11
+ import { u as useLlmSettings, g as getLlmProviderDefaults } from "./_sessionKey-C9o7YfxA.js";
12
+ import "@base-ui/react/merge-props";
13
+ import "@base-ui/react/use-render";
14
+ import "class-variance-authority";
15
+ import "clsx";
16
+ import "tailwind-merge";
17
+ import "@base-ui/react/dialog";
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-DCjikH21.js";
39
+ import "node:crypto";
40
+ import "node:fs";
41
+ import "node:os";
42
+ import "node:path";
43
+ import "ws";
44
+ import "@tanstack/router-core/ssr/client";
45
+ import "node:stream";
46
+ import "node:child_process";
47
+ import "node:fs/promises";
48
+ import "path";
49
+ function getInitialEnabled() {
50
+ if (typeof window === "undefined") return false;
51
+ try {
52
+ return window.localStorage.getItem("feature_artifacts") === "true";
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+ const useArtifactsStore = create()((set) => ({
58
+ isEnabled: getInitialEnabled(),
59
+ isPanelOpen: false,
60
+ content: "",
61
+ type: null,
62
+ setArtifact: function setArtifact(content, type) {
63
+ set({ content, type, isPanelOpen: true });
64
+ },
65
+ clearArtifact: function clearArtifact() {
66
+ set({ content: "", type: null });
67
+ },
68
+ openPanel: function openPanel() {
69
+ set({ isPanelOpen: true });
70
+ },
71
+ closePanel: function closePanel() {
72
+ set({ isPanelOpen: false });
73
+ },
74
+ setEnabled: function setEnabled(v) {
75
+ if (typeof window !== "undefined") {
76
+ try {
77
+ window.localStorage.setItem("feature_artifacts", String(v));
78
+ } catch {
79
+ }
80
+ }
81
+ set({ isEnabled: v });
82
+ }
83
+ }));
84
+ function SettingsSection({
85
+ title,
86
+ tabId,
87
+ activeTab,
88
+ children
89
+ }) {
90
+ const hiddenOnDesktop = tabId && activeTab && tabId !== activeTab;
91
+ return /* @__PURE__ */ jsxs(
92
+ "div",
93
+ {
94
+ className: cn(
95
+ "border-b border-primary-200 py-2 last:border-0",
96
+ hiddenOnDesktop && "md:hidden"
97
+ ),
98
+ children: [
99
+ /* @__PURE__ */ jsx("h3", { className: "mb-1.5 text-sm font-semibold text-primary-700", children: title }),
100
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children })
101
+ ]
102
+ }
103
+ );
104
+ }
105
+ function SettingsRow({
106
+ label,
107
+ description,
108
+ inline,
109
+ children
110
+ }) {
111
+ if (inline) {
112
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
113
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 select-none", children: [
114
+ /* @__PURE__ */ jsx("div", { className: "text-[13px] font-medium text-primary-800", children: label }),
115
+ description && /* @__PURE__ */ jsx("div", { className: "text-xs text-primary-500", children: description })
116
+ ] }),
117
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children })
118
+ ] });
119
+ }
120
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
121
+ /* @__PURE__ */ jsxs("div", { className: "select-none", children: [
122
+ /* @__PURE__ */ jsx("div", { className: "text-[13px] font-medium text-primary-800", children: label }),
123
+ description && /* @__PURE__ */ jsx("div", { className: "text-xs text-primary-500", children: description })
124
+ ] }),
125
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 flex-wrap", children })
126
+ ] });
127
+ }
128
+ const textSizeOptions = [
129
+ { value: "14px", label: "S" },
130
+ { value: "16px", label: "M" },
131
+ { value: "18px", label: "L" },
132
+ { value: "20px", label: "XL" }
133
+ ];
134
+ const fontFamilyOptions = [
135
+ {
136
+ value: "system",
137
+ label: "System",
138
+ cssValue: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
139
+ previewClass: "font-sans"
140
+ },
141
+ {
142
+ value: "inter",
143
+ label: "Inter",
144
+ cssValue: '"Inter", sans-serif',
145
+ previewClass: 'font-["Inter",sans-serif]'
146
+ },
147
+ {
148
+ value: "roboto",
149
+ label: "Roboto",
150
+ cssValue: '"Roboto", sans-serif',
151
+ previewClass: 'font-["Roboto",sans-serif]'
152
+ }
153
+ ];
154
+ const densityOptions = [
155
+ { value: "compact", label: "Compact" },
156
+ { value: "comfortable", label: "Comfortable" },
157
+ { value: "spacious", label: "Spacious" }
158
+ ];
159
+ const accentColorOptions = [
160
+ {
161
+ value: "green",
162
+ label: "Green",
163
+ accent: "#22c55e",
164
+ hover: "#16a34a",
165
+ light: "rgba(34, 197, 94, 0.10)"
166
+ },
167
+ {
168
+ value: "blue",
169
+ label: "Blue",
170
+ accent: "#3b82f6",
171
+ hover: "#2563eb",
172
+ light: "rgba(59, 130, 246, 0.10)"
173
+ },
174
+ {
175
+ value: "purple",
176
+ label: "Purple",
177
+ accent: "#8b5cf6",
178
+ hover: "#7c3aed",
179
+ light: "rgba(139, 92, 246, 0.10)"
180
+ },
181
+ {
182
+ value: "orange",
183
+ label: "Orange",
184
+ accent: "#f97316",
185
+ hover: "#ea580c",
186
+ light: "rgba(249, 115, 22, 0.10)"
187
+ },
188
+ {
189
+ value: "pink",
190
+ label: "Pink",
191
+ accent: "#ec4899",
192
+ hover: "#db2777",
193
+ light: "rgba(236, 72, 153, 0.10)"
194
+ },
195
+ {
196
+ value: "red",
197
+ label: "Red",
198
+ accent: "#ef4444",
199
+ hover: "#dc2626",
200
+ light: "rgba(239, 68, 68, 0.10)"
201
+ },
202
+ {
203
+ value: "cyan",
204
+ label: "Cyan",
205
+ accent: "#06b6d4",
206
+ hover: "#0891b2",
207
+ light: "rgba(6, 182, 212, 0.10)"
208
+ },
209
+ {
210
+ value: "yellow",
211
+ label: "Yellow",
212
+ accent: "#eab308",
213
+ hover: "#ca8a04",
214
+ light: "rgba(234, 179, 8, 0.10)"
215
+ }
216
+ ];
217
+ const chatWidthOptions = [
218
+ { value: "narrow", label: "Narrow", cssValue: "640px" },
219
+ { value: "medium", label: "Medium", cssValue: "800px" },
220
+ { value: "wide", label: "Wide", cssValue: "1000px" },
221
+ { value: "full", label: "Full", cssValue: "100%" }
222
+ ];
223
+ const sidebarWidthOptions = [
224
+ { value: "compact", label: "Compact", cssValue: "200px" },
225
+ { value: "normal", label: "Normal", cssValue: "260px" },
226
+ { value: "wide", label: "Wide", cssValue: "320px" },
227
+ { value: "xl", label: "XL", cssValue: "400px" }
228
+ ];
229
+ const bubbleStyleOptions = [
230
+ { value: "default", label: "Default" },
231
+ { value: "bubbles", label: "Bubbles" },
232
+ { value: "minimal", label: "Minimal" }
233
+ ];
234
+ function isTextSizeValue(value) {
235
+ return textSizeOptions.some((option) => option.value === value);
236
+ }
237
+ function isFontFamilyValue(value) {
238
+ return fontFamilyOptions.some((option) => option.value === value);
239
+ }
240
+ function isDensityValue(value) {
241
+ return densityOptions.some((option) => option.value === value);
242
+ }
243
+ function isAccentColorValue(value) {
244
+ return accentColorOptions.some((option) => option.value === value);
245
+ }
246
+ function isChatWidthValue(value) {
247
+ return chatWidthOptions.some((option) => option.value === value);
248
+ }
249
+ function isSidebarWidthValue(value) {
250
+ return sidebarWidthOptions.some((option) => option.value === value);
251
+ }
252
+ function isBubbleStyleValue(value) {
253
+ return bubbleStyleOptions.some((option) => option.value === value);
254
+ }
255
+ function SettingsDialog({
256
+ open,
257
+ onOpenChange,
258
+ onClose
259
+ }) {
260
+ const { settings, updateSettings } = useChatSettings();
261
+ const {
262
+ settings: llmSettings,
263
+ updateSettings: updateLlmSettings,
264
+ status: llmStatus,
265
+ testApiKey
266
+ } = useLlmSettings();
267
+ const [activeTab, setActiveTab] = useState("appearance");
268
+ const [apiKeyInput, setApiKeyInput] = useState(llmSettings.llmApiKey);
269
+ const [testingKey, setTestingKey] = useState(false);
270
+ const [testResult, setTestResult] = useState(null);
271
+ const [textSize, setTextSize] = useState(() => {
272
+ if (typeof window === "undefined") return "16px";
273
+ try {
274
+ const stored = localStorage.getItem("opencami-text-size");
275
+ if (stored && isTextSizeValue(stored)) return stored;
276
+ } catch {
277
+ }
278
+ return "16px";
279
+ });
280
+ const [ttsEnabled, setTtsEnabled] = useState(() => {
281
+ if (typeof window === "undefined") return true;
282
+ try {
283
+ const stored = localStorage.getItem("opencami-tts-enabled");
284
+ return stored === null ? true : stored === "true";
285
+ } catch {
286
+ return true;
287
+ }
288
+ });
289
+ const [ttsProvider, setTtsProvider] = useState(() => {
290
+ if (typeof window === "undefined") return "auto";
291
+ try {
292
+ return localStorage.getItem("opencami-tts-provider") || "auto";
293
+ } catch {
294
+ return "auto";
295
+ }
296
+ });
297
+ const [ttsVoice, setTtsVoice] = useState(() => {
298
+ if (typeof window === "undefined") return "nova";
299
+ try {
300
+ return localStorage.getItem("opencami-tts-voice") || "nova";
301
+ } catch {
302
+ return "nova";
303
+ }
304
+ });
305
+ const [sttProvider, setSttProvider] = useState(() => {
306
+ if (typeof window === "undefined") return "auto";
307
+ try {
308
+ return localStorage.getItem("opencami-stt-provider") || "auto";
309
+ } catch {
310
+ return "auto";
311
+ }
312
+ });
313
+ useEffect(() => {
314
+ if (typeof window === "undefined") return;
315
+ try {
316
+ const storedFontFamily = localStorage.getItem("opencami-font-family");
317
+ if (storedFontFamily && isFontFamilyValue(storedFontFamily)) {
318
+ applyFontFamily(storedFontFamily);
319
+ if (settings.fontFamily !== storedFontFamily) {
320
+ updateSettings({ fontFamily: storedFontFamily });
321
+ }
322
+ } else {
323
+ applyFontFamily(settings.fontFamily);
324
+ }
325
+ const storedDensity = localStorage.getItem("opencami-density");
326
+ if (storedDensity && isDensityValue(storedDensity)) {
327
+ applyDensity(storedDensity);
328
+ if (settings.density !== storedDensity) {
329
+ updateSettings({ density: storedDensity });
330
+ }
331
+ } else {
332
+ applyDensity(settings.density);
333
+ }
334
+ const storedAccentColor = localStorage.getItem("opencami-accent-color");
335
+ if (storedAccentColor && isAccentColorValue(storedAccentColor)) {
336
+ applyAccentColor(storedAccentColor);
337
+ if (settings.accentColor !== storedAccentColor) {
338
+ updateSettings({ accentColor: storedAccentColor });
339
+ }
340
+ } else {
341
+ applyAccentColor(settings.accentColor);
342
+ }
343
+ const storedChatWidth = localStorage.getItem("opencami-chat-width");
344
+ if (storedChatWidth && isChatWidthValue(storedChatWidth)) {
345
+ applyChatWidth(storedChatWidth);
346
+ if (settings.chatWidth !== storedChatWidth) {
347
+ updateSettings({ chatWidth: storedChatWidth });
348
+ }
349
+ } else {
350
+ applyChatWidth(settings.chatWidth);
351
+ }
352
+ const storedSidebarWidth = localStorage.getItem("opencami-sidebar-width");
353
+ if (storedSidebarWidth && isSidebarWidthValue(storedSidebarWidth)) {
354
+ applySidebarWidth(storedSidebarWidth);
355
+ if (settings.sidebarWidth !== storedSidebarWidth) {
356
+ updateSettings({
357
+ sidebarWidth: storedSidebarWidth
358
+ });
359
+ }
360
+ } else {
361
+ applySidebarWidth(settings.sidebarWidth);
362
+ }
363
+ const storedBubbleStyle = localStorage.getItem("opencami-bubble-style");
364
+ if (storedBubbleStyle && isBubbleStyleValue(storedBubbleStyle)) {
365
+ applyBubbleStyle(storedBubbleStyle);
366
+ if (settings.bubbleStyle !== storedBubbleStyle) {
367
+ updateSettings({ bubbleStyle: storedBubbleStyle });
368
+ }
369
+ } else {
370
+ applyBubbleStyle(settings.bubbleStyle);
371
+ }
372
+ } catch {
373
+ applyFontFamily(settings.fontFamily);
374
+ applyDensity(settings.density);
375
+ applyAccentColor(settings.accentColor);
376
+ applyChatWidth(settings.chatWidth);
377
+ applySidebarWidth(settings.sidebarWidth);
378
+ applyBubbleStyle(settings.bubbleStyle);
379
+ }
380
+ }, []);
381
+ useEffect(() => {
382
+ applyFontFamily(settings.fontFamily);
383
+ }, [settings.fontFamily]);
384
+ useEffect(() => {
385
+ applyDensity(settings.density);
386
+ }, [settings.density]);
387
+ useEffect(() => {
388
+ applyAccentColor(settings.accentColor);
389
+ }, [settings.accentColor]);
390
+ useEffect(() => {
391
+ applyChatWidth(settings.chatWidth);
392
+ }, [settings.chatWidth]);
393
+ useEffect(() => {
394
+ applySidebarWidth(settings.sidebarWidth);
395
+ }, [settings.sidebarWidth]);
396
+ useEffect(() => {
397
+ applyBubbleStyle(settings.bubbleStyle);
398
+ }, [settings.bubbleStyle]);
399
+ const handleTtsProviderChange = (value) => {
400
+ setTtsProvider(value);
401
+ try {
402
+ localStorage.setItem("opencami-tts-provider", value);
403
+ } catch {
404
+ }
405
+ };
406
+ const handleTtsVoiceChange = (value) => {
407
+ setTtsVoice(value);
408
+ try {
409
+ localStorage.setItem("opencami-tts-voice", value);
410
+ } catch {
411
+ }
412
+ };
413
+ const handleSttProviderChange = (value) => {
414
+ setSttProvider(value);
415
+ try {
416
+ localStorage.setItem("opencami-stt-provider", value);
417
+ } catch {
418
+ }
419
+ };
420
+ function applyTextSize(value) {
421
+ if (typeof document === "undefined") return;
422
+ document.documentElement.style.setProperty("--opencami-text-size", value);
423
+ }
424
+ function applyFontFamily(value) {
425
+ if (typeof document === "undefined") return;
426
+ const selected = fontFamilyOptions.find((option) => option.value === value);
427
+ const cssValue = selected?.cssValue ?? fontFamilyOptions[0].cssValue;
428
+ document.documentElement.style.setProperty(
429
+ "--opencami-font-family",
430
+ cssValue
431
+ );
432
+ }
433
+ function applyDensity(value) {
434
+ if (typeof document === "undefined") return;
435
+ const root = document.documentElement;
436
+ root.style.setProperty("--opencami-density", value);
437
+ if (value === "compact") {
438
+ root.style.setProperty("--opencami-msg-padding-y", "0.25rem");
439
+ root.style.setProperty("--opencami-msg-gap", "0.25rem");
440
+ root.style.setProperty("--opencami-user-bubble-py", "0.4rem");
441
+ return;
442
+ }
443
+ if (value === "spacious") {
444
+ root.style.setProperty("--opencami-msg-padding-y", "1.25rem");
445
+ root.style.setProperty("--opencami-msg-gap", "1rem");
446
+ root.style.setProperty("--opencami-user-bubble-py", "1rem");
447
+ return;
448
+ }
449
+ root.style.setProperty("--opencami-msg-padding-y", "0.75rem");
450
+ root.style.setProperty("--opencami-msg-gap", "0.5rem");
451
+ root.style.setProperty("--opencami-user-bubble-py", "0.625rem");
452
+ }
453
+ function applyAccentColor(value) {
454
+ if (typeof document === "undefined") return;
455
+ const selected = accentColorOptions.find((option) => option.value === value) ?? accentColorOptions[0];
456
+ const root = document.documentElement;
457
+ root.style.setProperty("--opencami-accent", selected.accent);
458
+ root.style.setProperty("--opencami-accent-hover", selected.hover);
459
+ root.style.setProperty("--opencami-accent-light", selected.light);
460
+ }
461
+ function applyChatWidth(value) {
462
+ if (typeof document === "undefined") return;
463
+ const selected = chatWidthOptions.find((option) => option.value === value) ?? chatWidthOptions[2];
464
+ document.documentElement.style.setProperty(
465
+ "--opencami-chat-width",
466
+ selected.cssValue
467
+ );
468
+ }
469
+ function applySidebarWidth(value) {
470
+ if (typeof document === "undefined") return;
471
+ const selected = sidebarWidthOptions.find((option) => option.value === value) ?? sidebarWidthOptions[1];
472
+ document.documentElement.style.setProperty(
473
+ "--opencami-sidebar-width",
474
+ selected.cssValue
475
+ );
476
+ }
477
+ function applyBubbleStyle(value) {
478
+ if (typeof document === "undefined") return;
479
+ document.documentElement.setAttribute("data-opencami-bubble-style", value);
480
+ }
481
+ const handleTextSizeChange = (value) => {
482
+ if (!isTextSizeValue(value)) return;
483
+ setTextSize(value);
484
+ applyTextSize(value);
485
+ try {
486
+ localStorage.setItem("opencami-text-size", value);
487
+ } catch {
488
+ }
489
+ };
490
+ const handleFontFamilyChange = (value) => {
491
+ if (!isFontFamilyValue(value)) return;
492
+ applyFontFamily(value);
493
+ updateSettings({ fontFamily: value });
494
+ try {
495
+ localStorage.setItem("opencami-font-family", value);
496
+ } catch {
497
+ }
498
+ };
499
+ const handleDensityChange = (value) => {
500
+ if (!isDensityValue(value)) return;
501
+ applyDensity(value);
502
+ updateSettings({ density: value });
503
+ try {
504
+ localStorage.setItem("opencami-density", value);
505
+ } catch {
506
+ }
507
+ };
508
+ const handleAccentColorChange = (value) => {
509
+ if (!isAccentColorValue(value)) return;
510
+ applyAccentColor(value);
511
+ updateSettings({ accentColor: value });
512
+ try {
513
+ localStorage.setItem("opencami-accent-color", value);
514
+ } catch {
515
+ }
516
+ };
517
+ const handleChatWidthChange = (value) => {
518
+ if (!isChatWidthValue(value)) return;
519
+ applyChatWidth(value);
520
+ updateSettings({ chatWidth: value });
521
+ try {
522
+ localStorage.setItem("opencami-chat-width", value);
523
+ } catch {
524
+ }
525
+ };
526
+ const handleSidebarWidthChange = (value) => {
527
+ if (!isSidebarWidthValue(value)) return;
528
+ applySidebarWidth(value);
529
+ updateSettings({ sidebarWidth: value });
530
+ try {
531
+ localStorage.setItem("opencami-sidebar-width", value);
532
+ } catch {
533
+ }
534
+ };
535
+ const handleBubbleStyleChange = (value) => {
536
+ if (!isBubbleStyleValue(value)) return;
537
+ applyBubbleStyle(value);
538
+ updateSettings({ bubbleStyle: value });
539
+ try {
540
+ localStorage.setItem("opencami-bubble-style", value);
541
+ } catch {
542
+ }
543
+ };
544
+ const handleTtsToggle = (checked) => {
545
+ setTtsEnabled(checked);
546
+ try {
547
+ localStorage.setItem("opencami-tts-enabled", String(checked));
548
+ } catch {
549
+ }
550
+ window.dispatchEvent(
551
+ new StorageEvent("storage", {
552
+ key: "opencami-tts-enabled",
553
+ newValue: String(checked)
554
+ })
555
+ );
556
+ };
557
+ const [personasAvailable, setPersonasAvailable] = useState(false);
558
+ const personasAbortControllerRef = useRef(null);
559
+ const [personasEnabled, setPersonasEnabled] = useState(() => {
560
+ if (typeof window === "undefined") return true;
561
+ try {
562
+ const stored = localStorage.getItem("opencami-personas-enabled");
563
+ return stored === null ? true : stored === "true";
564
+ } catch {
565
+ return true;
566
+ }
567
+ });
568
+ useEffect(() => {
569
+ let mounted = true;
570
+ async function checkPersonas() {
571
+ personasAbortControllerRef.current?.abort();
572
+ const controller = new AbortController();
573
+ personasAbortControllerRef.current = controller;
574
+ try {
575
+ const res = await fetch("/api/personas", { signal: controller.signal });
576
+ if (!res.ok) return;
577
+ const data = await res.json();
578
+ if (mounted && data.ok) {
579
+ setPersonasAvailable(data.available);
580
+ }
581
+ } catch (error) {
582
+ if (error instanceof Error && error.name === "AbortError") return;
583
+ }
584
+ }
585
+ checkPersonas();
586
+ return () => {
587
+ mounted = false;
588
+ personasAbortControllerRef.current?.abort();
589
+ };
590
+ }, []);
591
+ const handlePersonasToggle = (checked) => {
592
+ setPersonasEnabled(checked);
593
+ try {
594
+ localStorage.setItem("opencami-personas-enabled", String(checked));
595
+ } catch {
596
+ }
597
+ window.dispatchEvent(
598
+ new StorageEvent("storage", {
599
+ key: "opencami-personas-enabled",
600
+ newValue: String(checked)
601
+ })
602
+ );
603
+ };
604
+ const handleTestApiKey = async () => {
605
+ if (!apiKeyInput.trim()) return;
606
+ setTestingKey(true);
607
+ setTestResult(null);
608
+ try {
609
+ const result = await testApiKey(apiKeyInput.trim());
610
+ setTestResult(result);
611
+ if (result.valid) {
612
+ updateLlmSettings({ llmApiKey: apiKeyInput.trim() });
613
+ }
614
+ } finally {
615
+ setTestingKey(false);
616
+ }
617
+ };
618
+ const handleSaveApiKey = () => {
619
+ updateLlmSettings({ llmApiKey: apiKeyInput.trim() });
620
+ setTestResult(null);
621
+ };
622
+ const handleClearApiKey = () => {
623
+ setApiKeyInput("");
624
+ updateLlmSettings({ llmApiKey: "" });
625
+ setTestResult(null);
626
+ };
627
+ const themeOptions = [
628
+ { value: "system", label: "Auto", icon: ComputerIcon },
629
+ { value: "light", label: "Light", icon: Sun01Icon },
630
+ { value: "dark", label: "Dark", icon: Moon01Icon },
631
+ { value: "chameleon", label: "Cham", icon: Leaf01Icon },
632
+ { value: "frost-light", label: "Ice", icon: DropletIcon }
633
+ ];
634
+ function applyTheme(theme) {
635
+ if (typeof document === "undefined") return;
636
+ const root = document.documentElement;
637
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
638
+ root.classList.remove(
639
+ "light",
640
+ "dark",
641
+ "system",
642
+ "chameleon",
643
+ "frost",
644
+ "frost-light",
645
+ "frost-dark"
646
+ );
647
+ root.style.colorScheme = "";
648
+ root.classList.add(theme);
649
+ const isFrostLight = theme === "frost-light";
650
+ const isFrostDark = theme === "frost-dark";
651
+ if (isFrostLight || isFrostDark) {
652
+ root.classList.add("frost");
653
+ }
654
+ if (isFrostDark) {
655
+ root.classList.add("dark");
656
+ }
657
+ if (theme === "system" && media.matches) {
658
+ root.classList.add("dark");
659
+ }
660
+ if (isFrostLight) {
661
+ root.classList.remove("dark");
662
+ root.style.colorScheme = "light";
663
+ }
664
+ }
665
+ 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: [
666
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
667
+ /* @__PURE__ */ jsxs("div", { children: [
668
+ /* @__PURE__ */ jsx(DialogTitle, { className: "mb-1", children: "Settings" }),
669
+ /* @__PURE__ */ jsx(DialogDescription, { className: "hidden", children: "Configure OpenCami" })
670
+ ] }),
671
+ /* @__PURE__ */ jsx(
672
+ DialogClose,
673
+ {
674
+ render: /* @__PURE__ */ jsx(
675
+ Button,
676
+ {
677
+ size: "icon-sm",
678
+ variant: "ghost",
679
+ className: "text-primary-500 hover:bg-primary-100 hover:text-primary-700",
680
+ "aria-label": "Close",
681
+ children: /* @__PURE__ */ jsx(
682
+ HugeiconsIcon,
683
+ {
684
+ icon: Cancel01Icon,
685
+ size: 20,
686
+ strokeWidth: 1.5
687
+ }
688
+ )
689
+ }
690
+ )
691
+ }
692
+ )
693
+ ] }),
694
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex md:flex-row flex-col md:gap-3 gap-0 md:min-h-[400px]", children: [
695
+ /* @__PURE__ */ jsx("nav", { className: "hidden md:flex flex-col gap-0.5 min-w-[160px] border-r border-primary-200 pr-3", children: [
696
+ { id: "connection", label: "Connection", icon: Link01Icon },
697
+ {
698
+ id: "appearance",
699
+ label: "Appearance",
700
+ icon: PaintBoardIcon
701
+ },
702
+ { id: "chat", label: "Chat", icon: MessageEdit01Icon },
703
+ { id: "workspace", label: "Workspace", icon: Settings02Icon },
704
+ { id: "personas", label: "Personas", icon: UserIcon },
705
+ { id: "voice", label: "Voice", icon: VoiceIcon },
706
+ { id: "llm", label: "LLM Features", icon: AiBrain01Icon },
707
+ { id: "about", label: "About", icon: InformationCircleIcon }
708
+ ].map((tab) => /* @__PURE__ */ jsxs(
709
+ "button",
710
+ {
711
+ onClick: () => setActiveTab(tab.id),
712
+ className: cn(
713
+ "flex items-center gap-2 px-2.5 py-1.5 rounded-md text-sm text-left transition-colors",
714
+ activeTab === tab.id ? "text-primary-900 font-medium" : "text-primary-600 hover:text-primary-900 hover:bg-primary-50"
715
+ ),
716
+ style: activeTab === tab.id ? { backgroundColor: "var(--opencami-accent-light)" } : void 0,
717
+ children: [
718
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: tab.icon, size: 16, strokeWidth: 1.5 }),
719
+ tab.label
720
+ ]
721
+ },
722
+ tab.id
723
+ )) }),
724
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto md:max-h-[calc(85vh-90px)] max-h-none", children: [
725
+ /* @__PURE__ */ jsx(
726
+ SettingsSection,
727
+ {
728
+ title: "Connection",
729
+ tabId: "connection",
730
+ activeTab,
731
+ 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: [
732
+ /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-green-500" }),
733
+ "Connected"
734
+ ] }) })
735
+ }
736
+ ),
737
+ /* @__PURE__ */ jsxs(
738
+ SettingsSection,
739
+ {
740
+ title: "Appearance",
741
+ tabId: "appearance",
742
+ activeTab,
743
+ children: [
744
+ /* @__PURE__ */ jsx(SettingsRow, { label: "Theme", children: /* @__PURE__ */ jsx(
745
+ Tabs,
746
+ {
747
+ value: settings.theme,
748
+ onValueChange: (value) => {
749
+ const theme = value;
750
+ applyTheme(theme);
751
+ updateSettings({ theme });
752
+ },
753
+ children: /* @__PURE__ */ jsx(
754
+ TabsList,
755
+ {
756
+ variant: "default",
757
+ className: "gap-1.5 flex-wrap *:data-[slot=tab-indicator]:duration-0",
758
+ children: themeOptions.map((option) => /* @__PURE__ */ jsxs(
759
+ TabsTab,
760
+ {
761
+ value: option.value,
762
+ className: "text-[11px] px-1 py-0.5 gap-0.5",
763
+ children: [
764
+ option.icon && /* @__PURE__ */ jsx(
765
+ HugeiconsIcon,
766
+ {
767
+ icon: option.icon,
768
+ size: 13,
769
+ strokeWidth: 1.5
770
+ }
771
+ ),
772
+ /* @__PURE__ */ jsx("span", { children: option.label })
773
+ ]
774
+ },
775
+ option.value
776
+ ))
777
+ }
778
+ )
779
+ }
780
+ ) }),
781
+ /* @__PURE__ */ jsx(SettingsRow, { label: "Accent Color", children: /* @__PURE__ */ jsx("div", { className: "flex gap-0.5 flex-wrap", children: accentColorOptions.map((option) => {
782
+ const selected = settings.accentColor === option.value;
783
+ return /* @__PURE__ */ jsx(
784
+ "button",
785
+ {
786
+ type: "button",
787
+ onClick: () => handleAccentColorChange(option.value),
788
+ className: cn(
789
+ "flex items-center justify-center rounded-md p-1 transition-all duration-150",
790
+ selected ? "scale-110" : "hover:bg-primary-50 hover:scale-105"
791
+ ),
792
+ children: /* @__PURE__ */ jsx(
793
+ "span",
794
+ {
795
+ className: cn(
796
+ "size-6 rounded-full transition-shadow duration-150",
797
+ selected ? "ring-2 ring-offset-2" : "border-2 border-primary-200"
798
+ ),
799
+ style: {
800
+ backgroundColor: option.accent,
801
+ ...selected ? {
802
+ "--tw-ring-color": option.accent
803
+ } : {}
804
+ }
805
+ }
806
+ )
807
+ },
808
+ option.value
809
+ );
810
+ }) }) }),
811
+ /* @__PURE__ */ jsx(SettingsRow, { label: "Text Size", children: /* @__PURE__ */ jsx(Tabs, { value: textSize, onValueChange: handleTextSizeChange, children: /* @__PURE__ */ jsx(
812
+ TabsList,
813
+ {
814
+ variant: "default",
815
+ className: "gap-2 *:data-[slot=tab-indicator]:duration-0",
816
+ children: textSizeOptions.map((option) => /* @__PURE__ */ jsx(TabsTab, { value: option.value, children: /* @__PURE__ */ jsx("span", { className: "tabular-nums text-xs", children: option.label }) }, option.value))
817
+ }
818
+ ) }) }),
819
+ /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Font", children: /* @__PURE__ */ jsx(
820
+ Tabs,
821
+ {
822
+ value: settings.fontFamily,
823
+ onValueChange: handleFontFamilyChange,
824
+ children: /* @__PURE__ */ jsx(
825
+ TabsList,
826
+ {
827
+ variant: "default",
828
+ className: "gap-1 *:data-[slot=tab-indicator]:duration-0",
829
+ children: fontFamilyOptions.map((option) => /* @__PURE__ */ jsx(TabsTab, { value: option.value, children: /* @__PURE__ */ jsx(
830
+ "span",
831
+ {
832
+ className: "text-xs",
833
+ style: { fontFamily: option.cssValue },
834
+ children: option.label
835
+ }
836
+ ) }, option.value))
837
+ }
838
+ )
839
+ }
840
+ ) }),
841
+ /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Density", children: /* @__PURE__ */ jsx(
842
+ Tabs,
843
+ {
844
+ value: settings.density,
845
+ onValueChange: handleDensityChange,
846
+ children: /* @__PURE__ */ jsx(
847
+ TabsList,
848
+ {
849
+ variant: "default",
850
+ className: "gap-1 *:data-[slot=tab-indicator]:duration-0",
851
+ children: densityOptions.map((option) => /* @__PURE__ */ jsx(TabsTab, { value: option.value, children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: option.label }) }, option.value))
852
+ }
853
+ )
854
+ }
855
+ ) }),
856
+ /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Bubbles", children: /* @__PURE__ */ jsx(
857
+ Tabs,
858
+ {
859
+ value: settings.bubbleStyle,
860
+ onValueChange: handleBubbleStyleChange,
861
+ children: /* @__PURE__ */ jsx(
862
+ TabsList,
863
+ {
864
+ variant: "default",
865
+ className: "gap-1 *:data-[slot=tab-indicator]:duration-0",
866
+ children: bubbleStyleOptions.map((option) => /* @__PURE__ */ jsx(TabsTab, { value: option.value, children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: option.label }) }, option.value))
867
+ }
868
+ )
869
+ }
870
+ ) }),
871
+ /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Chat Width", children: /* @__PURE__ */ jsx(
872
+ Tabs,
873
+ {
874
+ value: settings.chatWidth,
875
+ onValueChange: handleChatWidthChange,
876
+ children: /* @__PURE__ */ jsx(
877
+ TabsList,
878
+ {
879
+ variant: "default",
880
+ className: "gap-1 *:data-[slot=tab-indicator]:duration-0",
881
+ children: chatWidthOptions.map((option) => /* @__PURE__ */ jsx(TabsTab, { value: option.value, children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: option.label }) }, option.value))
882
+ }
883
+ )
884
+ }
885
+ ) }),
886
+ /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Sidebar", children: /* @__PURE__ */ jsx(
887
+ Tabs,
888
+ {
889
+ value: settings.sidebarWidth,
890
+ onValueChange: handleSidebarWidthChange,
891
+ children: /* @__PURE__ */ jsx(
892
+ TabsList,
893
+ {
894
+ variant: "default",
895
+ className: "gap-1 *:data-[slot=tab-indicator]:duration-0",
896
+ children: sidebarWidthOptions.map((option) => /* @__PURE__ */ jsx(TabsTab, { value: option.value, children: /* @__PURE__ */ jsx("span", { className: "text-xs", children: option.label }) }, option.value))
897
+ }
898
+ )
899
+ }
900
+ ) })
901
+ ]
902
+ }
903
+ ),
904
+ /* @__PURE__ */ jsxs(SettingsSection, { title: "Chat", tabId: "chat", activeTab, children: [
905
+ /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Show tool messages", children: /* @__PURE__ */ jsx(
906
+ Switch,
907
+ {
908
+ checked: settings.showToolMessages,
909
+ onCheckedChange: (checked) => updateSettings({ showToolMessages: checked })
910
+ }
911
+ ) }),
912
+ /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Show reasoning blocks", children: /* @__PURE__ */ jsx(
913
+ Switch,
914
+ {
915
+ checked: settings.showReasoningBlocks,
916
+ onCheckedChange: (checked) => updateSettings({ showReasoningBlocks: checked })
917
+ }
918
+ ) }),
919
+ /* @__PURE__ */ jsx(SettingsRow, { inline: true, label: "Show search sources", children: /* @__PURE__ */ jsx(
920
+ Switch,
921
+ {
922
+ checked: settings.showSearchSources,
923
+ onCheckedChange: (checked) => updateSettings({ showSearchSources: checked })
924
+ }
925
+ ) }),
926
+ /* @__PURE__ */ jsx(
927
+ SettingsRow,
928
+ {
929
+ inline: true,
930
+ label: "Inline File Preview",
931
+ description: "Make file paths in messages clickable to preview file contents",
932
+ children: /* @__PURE__ */ jsx(
933
+ Switch,
934
+ {
935
+ checked: settings.inlineFilePreview,
936
+ onCheckedChange: (checked) => updateSettings({ inlineFilePreview: checked })
937
+ }
938
+ )
939
+ }
940
+ )
941
+ ] }),
942
+ /* @__PURE__ */ jsxs(
943
+ SettingsSection,
944
+ {
945
+ title: "Workspace",
946
+ tabId: "workspace",
947
+ activeTab,
948
+ children: [
949
+ /* @__PURE__ */ jsx(
950
+ SettingsRow,
951
+ {
952
+ inline: true,
953
+ label: "File Explorer",
954
+ description: "Browse and edit workspace files from the sidebar",
955
+ children: /* @__PURE__ */ jsx(
956
+ Switch,
957
+ {
958
+ checked: (() => {
959
+ try {
960
+ const v = localStorage.getItem("opencami-file-explorer");
961
+ return v === null ? true : v === "true";
962
+ } catch {
963
+ return true;
964
+ }
965
+ })(),
966
+ onCheckedChange: (checked) => {
967
+ localStorage.setItem(
968
+ "opencami-file-explorer",
969
+ String(checked)
970
+ );
971
+ window.location.reload();
972
+ }
973
+ }
974
+ )
975
+ }
976
+ ),
977
+ /* @__PURE__ */ jsx(
978
+ SettingsRow,
979
+ {
980
+ inline: true,
981
+ label: "Memory Viewer",
982
+ description: "Browse and edit MEMORY.md and daily memory files",
983
+ children: /* @__PURE__ */ jsx(
984
+ Switch,
985
+ {
986
+ checked: (() => {
987
+ try {
988
+ const v = localStorage.getItem("opencami-memory-viewer");
989
+ return v === null ? true : v === "true";
990
+ } catch {
991
+ return true;
992
+ }
993
+ })(),
994
+ onCheckedChange: (checked) => {
995
+ localStorage.setItem(
996
+ "opencami-memory-viewer",
997
+ String(checked)
998
+ );
999
+ window.location.reload();
1000
+ }
1001
+ }
1002
+ )
1003
+ }
1004
+ ),
1005
+ /* @__PURE__ */ jsx(
1006
+ SettingsRow,
1007
+ {
1008
+ inline: true,
1009
+ label: "Agent Manager (Beta)",
1010
+ description: "Show Agent Manager in sidebar for creating and managing agents",
1011
+ children: /* @__PURE__ */ jsx(
1012
+ Switch,
1013
+ {
1014
+ checked: (() => {
1015
+ try {
1016
+ return localStorage.getItem("opencami-agent-manager") === "true";
1017
+ } catch {
1018
+ return false;
1019
+ }
1020
+ })(),
1021
+ onCheckedChange: (checked) => {
1022
+ localStorage.setItem(
1023
+ "opencami-agent-manager",
1024
+ String(checked)
1025
+ );
1026
+ window.location.reload();
1027
+ }
1028
+ }
1029
+ )
1030
+ }
1031
+ ),
1032
+ /* @__PURE__ */ jsx(
1033
+ SettingsRow,
1034
+ {
1035
+ inline: true,
1036
+ label: "Skills Browser (Beta)",
1037
+ description: "Browse and install skills from ClawHub",
1038
+ children: /* @__PURE__ */ jsx(
1039
+ Switch,
1040
+ {
1041
+ checked: (() => {
1042
+ try {
1043
+ return localStorage.getItem("opencami-skills-browser") === "true";
1044
+ } catch {
1045
+ return false;
1046
+ }
1047
+ })(),
1048
+ onCheckedChange: (checked) => {
1049
+ localStorage.setItem(
1050
+ "opencami-skills-browser",
1051
+ String(checked)
1052
+ );
1053
+ window.location.reload();
1054
+ }
1055
+ }
1056
+ )
1057
+ }
1058
+ ),
1059
+ /* @__PURE__ */ jsx(
1060
+ SettingsRow,
1061
+ {
1062
+ inline: true,
1063
+ label: "Cron Jobs Panel (Beta)",
1064
+ description: "Show Cron Jobs in sidebar for managing OpenClaw cron schedules",
1065
+ children: /* @__PURE__ */ jsx(
1066
+ Switch,
1067
+ {
1068
+ checked: (() => {
1069
+ try {
1070
+ return localStorage.getItem("opencami-cron-manager") === "true";
1071
+ } catch {
1072
+ return false;
1073
+ }
1074
+ })(),
1075
+ onCheckedChange: (checked) => {
1076
+ localStorage.setItem(
1077
+ "opencami-cron-manager",
1078
+ String(checked)
1079
+ );
1080
+ window.location.reload();
1081
+ }
1082
+ }
1083
+ )
1084
+ }
1085
+ ),
1086
+ /* @__PURE__ */ jsx(
1087
+ SettingsRow,
1088
+ {
1089
+ inline: true,
1090
+ label: "Dashboard (Beta)",
1091
+ description: "Show Dashboard in sidebar with system and gateway widgets",
1092
+ children: /* @__PURE__ */ jsx(
1093
+ Switch,
1094
+ {
1095
+ checked: (() => {
1096
+ try {
1097
+ return localStorage.getItem("feature_dashboard") === "true";
1098
+ } catch {
1099
+ return false;
1100
+ }
1101
+ })(),
1102
+ onCheckedChange: (checked) => {
1103
+ localStorage.setItem("feature_dashboard", String(checked));
1104
+ window.location.reload();
1105
+ }
1106
+ }
1107
+ )
1108
+ }
1109
+ ),
1110
+ /* @__PURE__ */ jsx(
1111
+ SettingsRow,
1112
+ {
1113
+ inline: true,
1114
+ label: "Artifacts Preview (Beta)",
1115
+ description: "Show live HTML/SVG preview panel when AI generates code",
1116
+ children: /* @__PURE__ */ jsx(
1117
+ Switch,
1118
+ {
1119
+ checked: (() => {
1120
+ try {
1121
+ return localStorage.getItem("feature_artifacts") === "true";
1122
+ } catch {
1123
+ return false;
1124
+ }
1125
+ })(),
1126
+ onCheckedChange: (checked) => {
1127
+ localStorage.setItem("feature_artifacts", String(checked));
1128
+ useArtifactsStore.getState().setEnabled(checked);
1129
+ }
1130
+ }
1131
+ )
1132
+ }
1133
+ )
1134
+ ]
1135
+ }
1136
+ ),
1137
+ /* @__PURE__ */ jsxs(
1138
+ SettingsSection,
1139
+ {
1140
+ title: "Personas",
1141
+ tabId: "personas",
1142
+ activeTab,
1143
+ children: [
1144
+ personasAvailable ? /* @__PURE__ */ jsx(
1145
+ SettingsRow,
1146
+ {
1147
+ inline: true,
1148
+ label: "Persona Picker",
1149
+ description: "Show persona picker in chat (20 personalities)",
1150
+ children: /* @__PURE__ */ jsx(
1151
+ Switch,
1152
+ {
1153
+ checked: personasEnabled,
1154
+ onCheckedChange: handlePersonasToggle
1155
+ }
1156
+ )
1157
+ }
1158
+ ) : /* @__PURE__ */ jsx(
1159
+ SettingsRow,
1160
+ {
1161
+ inline: true,
1162
+ label: "Persona Picker",
1163
+ description: "Install the Personas skill to unlock 20 AI personalities",
1164
+ children: /* @__PURE__ */ jsx(Switch, { checked: false, disabled: true })
1165
+ }
1166
+ ),
1167
+ !personasAvailable && /* @__PURE__ */ jsx("div", { className: "pt-1", children: /* @__PURE__ */ jsx(
1168
+ "a",
1169
+ {
1170
+ href: "https://www.clawhub.ai/robbyczgw-cla/personas",
1171
+ target: "_blank",
1172
+ rel: "noopener noreferrer",
1173
+ className: "text-xs text-primary-600 hover:text-primary-900 hover:underline",
1174
+ children: "Get it on ClawHub →"
1175
+ }
1176
+ ) })
1177
+ ]
1178
+ }
1179
+ ),
1180
+ /* @__PURE__ */ jsxs(
1181
+ SettingsSection,
1182
+ {
1183
+ title: "Text-to-Speech",
1184
+ tabId: "voice",
1185
+ activeTab,
1186
+ children: [
1187
+ /* @__PURE__ */ jsx(
1188
+ SettingsRow,
1189
+ {
1190
+ inline: true,
1191
+ label: "Voice Playback",
1192
+ description: "Add a 🔊 button to AI messages for text-to-speech",
1193
+ children: /* @__PURE__ */ jsx(
1194
+ Switch,
1195
+ {
1196
+ checked: ttsEnabled,
1197
+ onCheckedChange: handleTtsToggle
1198
+ }
1199
+ )
1200
+ }
1201
+ ),
1202
+ /* @__PURE__ */ jsx(
1203
+ SettingsRow,
1204
+ {
1205
+ inline: true,
1206
+ label: "TTS Provider",
1207
+ description: "Choose which service generates speech",
1208
+ children: /* @__PURE__ */ jsxs(
1209
+ "select",
1210
+ {
1211
+ value: ttsProvider,
1212
+ onChange: (e) => handleTtsProviderChange(e.target.value),
1213
+ 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",
1214
+ children: [
1215
+ /* @__PURE__ */ jsx("option", { value: "auto", children: "Auto" }),
1216
+ /* @__PURE__ */ jsx("option", { value: "elevenlabs", children: "ElevenLabs" }),
1217
+ /* @__PURE__ */ jsx("option", { value: "openai", children: "OpenAI" }),
1218
+ /* @__PURE__ */ jsx("option", { value: "edge", children: "Edge TTS (free)" })
1219
+ ]
1220
+ }
1221
+ )
1222
+ }
1223
+ ),
1224
+ ttsProvider === "openai" && /* @__PURE__ */ jsx(
1225
+ SettingsRow,
1226
+ {
1227
+ inline: true,
1228
+ label: "Voice",
1229
+ description: "OpenAI voice selection",
1230
+ children: /* @__PURE__ */ jsx(
1231
+ "select",
1232
+ {
1233
+ value: ttsVoice,
1234
+ onChange: (e) => handleTtsVoiceChange(e.target.value),
1235
+ 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",
1236
+ children: [
1237
+ "alloy",
1238
+ "echo",
1239
+ "fable",
1240
+ "onyx",
1241
+ "nova",
1242
+ "shimmer"
1243
+ ].map((v) => /* @__PURE__ */ jsx("option", { value: v, children: v.charAt(0).toUpperCase() + v.slice(1) }, v))
1244
+ }
1245
+ )
1246
+ }
1247
+ )
1248
+ ]
1249
+ }
1250
+ ),
1251
+ /* @__PURE__ */ jsx(
1252
+ SettingsSection,
1253
+ {
1254
+ title: "Speech-to-Text",
1255
+ tabId: "voice",
1256
+ activeTab,
1257
+ children: /* @__PURE__ */ jsx(
1258
+ SettingsRow,
1259
+ {
1260
+ inline: true,
1261
+ label: "STT Provider",
1262
+ description: "Choose which service transcribes your voice",
1263
+ children: /* @__PURE__ */ jsxs(
1264
+ "select",
1265
+ {
1266
+ value: sttProvider,
1267
+ onChange: (e) => handleSttProviderChange(e.target.value),
1268
+ 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",
1269
+ children: [
1270
+ /* @__PURE__ */ jsx("option", { value: "auto", children: "Auto" }),
1271
+ /* @__PURE__ */ jsx("option", { value: "elevenlabs", children: "ElevenLabs" }),
1272
+ /* @__PURE__ */ jsx("option", { value: "openai", children: "OpenAI" }),
1273
+ /* @__PURE__ */ jsx("option", { value: "browser", children: "Browser (free)" })
1274
+ ]
1275
+ }
1276
+ )
1277
+ }
1278
+ )
1279
+ }
1280
+ ),
1281
+ /* @__PURE__ */ jsxs(
1282
+ SettingsSection,
1283
+ {
1284
+ title: "LLM Features",
1285
+ tabId: "llm",
1286
+ activeTab,
1287
+ children: [
1288
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-primary-500 mb-3", children: [
1289
+ "Enhance session titles and follow-up suggestions using an LLM provider.",
1290
+ llmStatus.hasEnvKey && /* @__PURE__ */ jsx("span", { className: "block mt-1 text-green-600", children: "✓ Server has OPENAI_API_KEY configured" }),
1291
+ llmStatus.hasOpenRouterKey && /* @__PURE__ */ jsx("span", { className: "block mt-1 text-green-600", children: "✓ Server has OPENROUTER_API_KEY configured" }),
1292
+ llmStatus.hasKilocodeKey && /* @__PURE__ */ jsx("span", { className: "block mt-1 text-green-600", children: "✓ Server has KILOCODE_API_KEY configured" })
1293
+ ] }),
1294
+ /* @__PURE__ */ jsx(
1295
+ SettingsRow,
1296
+ {
1297
+ inline: true,
1298
+ label: "Provider",
1299
+ description: "Choose LLM provider for titles & follow-ups",
1300
+ children: /* @__PURE__ */ jsxs(
1301
+ "select",
1302
+ {
1303
+ value: llmSettings.llmProvider,
1304
+ onChange: (e) => {
1305
+ const provider = e.target.value;
1306
+ updateLlmSettings({
1307
+ llmProvider: provider,
1308
+ llmBaseUrl: "",
1309
+ llmModel: ""
1310
+ });
1311
+ },
1312
+ 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",
1313
+ children: [
1314
+ /* @__PURE__ */ jsx("option", { value: "openai", children: "OpenAI" }),
1315
+ /* @__PURE__ */ jsx("option", { value: "openrouter", children: "OpenRouter" }),
1316
+ /* @__PURE__ */ jsx("option", { value: "kilocode", children: "Kilo Gateway" }),
1317
+ /* @__PURE__ */ jsx("option", { value: "ollama", children: "Ollama (local)" }),
1318
+ /* @__PURE__ */ jsx("option", { value: "custom", children: "Custom" })
1319
+ ]
1320
+ }
1321
+ )
1322
+ }
1323
+ ),
1324
+ /* @__PURE__ */ jsx(
1325
+ SettingsRow,
1326
+ {
1327
+ inline: true,
1328
+ label: "Smart session titles",
1329
+ description: "Generate concise titles using AI",
1330
+ children: /* @__PURE__ */ jsx(
1331
+ Switch,
1332
+ {
1333
+ checked: llmSettings.useLlmTitles,
1334
+ onCheckedChange: (checked) => updateLlmSettings({ useLlmTitles: checked }),
1335
+ disabled: !llmStatus.isAvailable
1336
+ }
1337
+ )
1338
+ }
1339
+ ),
1340
+ /* @__PURE__ */ jsx(
1341
+ SettingsRow,
1342
+ {
1343
+ inline: true,
1344
+ label: "Smart follow-up suggestions",
1345
+ description: "AI-generated contextual follow-ups",
1346
+ children: /* @__PURE__ */ jsx(
1347
+ Switch,
1348
+ {
1349
+ checked: llmSettings.useLlmFollowUps,
1350
+ onCheckedChange: (checked) => updateLlmSettings({ useLlmFollowUps: checked }),
1351
+ disabled: !llmStatus.isAvailable
1352
+ }
1353
+ )
1354
+ }
1355
+ ),
1356
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 space-y-2", children: [
1357
+ /* @__PURE__ */ jsxs("div", { children: [
1358
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-primary-500 mb-1", children: "Model" }),
1359
+ /* @__PURE__ */ jsx(
1360
+ "input",
1361
+ {
1362
+ type: "text",
1363
+ value: llmSettings.llmModel,
1364
+ onChange: (e) => updateLlmSettings({ llmModel: e.target.value }),
1365
+ placeholder: getLlmProviderDefaults(llmSettings.llmProvider).model || "model-name",
1366
+ 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"
1367
+ }
1368
+ )
1369
+ ] }),
1370
+ (llmSettings.llmProvider === "custom" || llmSettings.llmProvider === "ollama") && /* @__PURE__ */ jsxs("div", { children: [
1371
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-primary-500 mb-1", children: "Base URL" }),
1372
+ /* @__PURE__ */ jsx(
1373
+ "input",
1374
+ {
1375
+ type: "text",
1376
+ value: llmSettings.llmBaseUrl,
1377
+ onChange: (e) => updateLlmSettings({ llmBaseUrl: e.target.value }),
1378
+ placeholder: getLlmProviderDefaults(llmSettings.llmProvider).baseUrl || "https://...",
1379
+ 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"
1380
+ }
1381
+ )
1382
+ ] })
1383
+ ] }),
1384
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 pt-3 border-t border-primary-100", children: [
1385
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-primary-800 mb-2", children: llmSettings.llmProvider === "ollama" ? "API Key (optional)" : "API Key" }),
1386
+ /* @__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" : llmSettings.llmProvider === "kilocode" ? "Kilo Gateway" : "LLM features"} (stored locally)` }),
1387
+ /* @__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: [
1388
+ "⚠️ ",
1389
+ /* @__PURE__ */ jsx("strong", { children: "Security Note:" }),
1390
+ " 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, KILOCODE_API_KEY)."
1391
+ ] }),
1392
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1393
+ /* @__PURE__ */ jsx(
1394
+ "input",
1395
+ {
1396
+ type: "password",
1397
+ value: apiKeyInput,
1398
+ onChange: (e) => {
1399
+ setApiKeyInput(e.target.value);
1400
+ setTestResult(null);
1401
+ },
1402
+ placeholder: "sk-...",
1403
+ 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"
1404
+ }
1405
+ ),
1406
+ /* @__PURE__ */ jsx(
1407
+ Button,
1408
+ {
1409
+ size: "sm",
1410
+ variant: "outline",
1411
+ onClick: handleTestApiKey,
1412
+ disabled: !apiKeyInput.trim() || testingKey,
1413
+ className: "min-w-[60px]",
1414
+ children: testingKey ? /* @__PURE__ */ jsx(
1415
+ HugeiconsIcon,
1416
+ {
1417
+ icon: Loading02Icon,
1418
+ size: 16,
1419
+ className: "animate-spin"
1420
+ }
1421
+ ) : "Test"
1422
+ }
1423
+ )
1424
+ ] }),
1425
+ testResult && /* @__PURE__ */ jsxs(
1426
+ "div",
1427
+ {
1428
+ className: `mt-2 flex items-center gap-1.5 text-xs ${testResult.valid ? "text-green-600" : "text-red-600"}`,
1429
+ children: [
1430
+ /* @__PURE__ */ jsx(
1431
+ HugeiconsIcon,
1432
+ {
1433
+ icon: testResult.valid ? Tick01Icon : Cancel02Icon,
1434
+ size: 14
1435
+ }
1436
+ ),
1437
+ testResult.valid ? "API key is valid" : testResult.error || "Invalid API key"
1438
+ ]
1439
+ }
1440
+ ),
1441
+ llmSettings.llmApiKey && /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center justify-between", children: [
1442
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-green-600 flex items-center gap-1", children: [
1443
+ /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Tick01Icon, size: 14 }),
1444
+ "Key saved"
1445
+ ] }),
1446
+ /* @__PURE__ */ jsx(
1447
+ Button,
1448
+ {
1449
+ size: "sm",
1450
+ variant: "ghost",
1451
+ onClick: handleClearApiKey,
1452
+ className: "text-xs text-red-600 hover:text-red-700 hover:bg-red-50",
1453
+ children: "Clear"
1454
+ }
1455
+ )
1456
+ ] }),
1457
+ apiKeyInput && apiKeyInput !== llmSettings.llmApiKey && !testResult?.valid && /* @__PURE__ */ jsx(
1458
+ Button,
1459
+ {
1460
+ size: "sm",
1461
+ variant: "outline",
1462
+ onClick: handleSaveApiKey,
1463
+ className: "mt-2 w-full",
1464
+ children: "Save without testing"
1465
+ }
1466
+ )
1467
+ ] })
1468
+ ]
1469
+ }
1470
+ ),
1471
+ /* @__PURE__ */ jsxs(
1472
+ SettingsSection,
1473
+ {
1474
+ title: "About",
1475
+ tabId: "about",
1476
+ activeTab,
1477
+ children: [
1478
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-primary-800", children: "OpenCami" }),
1479
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-4 pt-2", children: [
1480
+ /* @__PURE__ */ jsx(
1481
+ "a",
1482
+ {
1483
+ href: "https://github.com/robbyczgw-cla/opencami",
1484
+ target: "_blank",
1485
+ rel: "noopener noreferrer",
1486
+ className: "text-sm text-primary-600 hover:text-primary-900 hover:underline",
1487
+ children: "GitHub"
1488
+ }
1489
+ ),
1490
+ /* @__PURE__ */ jsx(
1491
+ "a",
1492
+ {
1493
+ href: "https://docs.openclaw.ai",
1494
+ target: "_blank",
1495
+ rel: "noopener noreferrer",
1496
+ className: "text-sm text-primary-600 hover:text-primary-900 hover:underline",
1497
+ children: "OpenClaw docs"
1498
+ }
1499
+ )
1500
+ ] })
1501
+ ]
1502
+ }
1503
+ )
1504
+ ] })
1505
+ ] }),
1506
+ /* @__PURE__ */ jsx("div", { className: "mt-2 flex justify-end", children: /* @__PURE__ */ jsx(DialogClose, { onClick: onClose, children: "Close" }) })
1507
+ ] }) }) });
1508
+ }
1509
+ export {
1510
+ SettingsDialog
1511
+ };