opencami 1.2.0 → 1.3.1

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 (34) hide show
  1. package/README.md +3 -1
  2. package/dist/client/assets/_sessionKey-CaDTbjXx.js +97 -0
  3. package/dist/client/assets/{agents-BiadNAEz.js → agents-CGtqdyBi.js} +2 -2
  4. package/dist/client/assets/{agents-screen-C5TPJbry.js → agents-screen-DzIMcmHe.js} +1 -1
  5. package/dist/client/assets/button-BcnCTjUT.js +1 -0
  6. package/dist/client/assets/{connect-DlVsi-ya.js → connect-B8sfEQyb.js} +1 -1
  7. package/dist/client/assets/{file-explorer-screen-B0hwrjdF.js → file-explorer-screen-hjggspl-.js} +1 -1
  8. package/dist/client/assets/{files-dkr2U72N.js → files-BHXmS1J5.js} +2 -2
  9. package/dist/client/assets/{index-D1Et4XlM.js → index-BfhiL2JN.js} +1 -1
  10. package/dist/client/assets/{index-CwoJpeG1.js → index-CxJ4zG_W.js} +1 -1
  11. package/dist/client/assets/{keyboard-shortcuts-dialog-zdnAmDiH.js → keyboard-shortcuts-dialog-DEknElTu.js} +1 -1
  12. package/dist/client/assets/{main-DOBfHXDF.js → main-BlX7CS2-.js} +3 -3
  13. package/dist/client/assets/{opencami-logo-Bxp_4d3X.js → opencami-logo-5KynvViW.js} +1 -1
  14. package/dist/client/assets/{react-DD8SkMJn.js → react-ONC2UaeC.js} +1 -1
  15. package/dist/client/assets/{search-dialog-Ck1AQreu.js → search-dialog-40gW31ca.js} +1 -1
  16. package/dist/client/assets/{session-export-dialog-Bh3zH4nZ.js → session-export-dialog-DSRWU2YO.js} +1 -1
  17. package/dist/client/assets/settings-dialog-AsvYzSBc.js +1 -0
  18. package/dist/client/assets/styles-BGTCU8mq.css +1 -0
  19. package/dist/client/assets/{switch-BoGcWcnr.js → switch-dAvZcYA-.js} +1 -1
  20. package/dist/client/assets/{use-file-explorer-state-B5JLNsSa.js → use-file-explorer-state-C6lX-h0n.js} +1 -1
  21. package/dist/client/assets/{useButton-csSB2uQJ.js → useButton-DhneLsMA.js} +1 -1
  22. package/dist/server/assets/{_sessionKey-DQwkBUS7.js → _sessionKey-ZF1_Jqbt.js} +206 -29
  23. package/dist/server/assets/{_tanstack-start-manifest_v-cW8K1Mnv.js → _tanstack-start-manifest_v-C2FijMus.js} +1 -1
  24. package/dist/server/assets/{index-BwD7ufHE.js → index-CTTNe_Sf.js} +1 -1
  25. package/dist/server/assets/{router-5qObg5aX.js → router-D5D0udHV.js} +33 -25
  26. package/dist/server/assets/{search-dialog-BE2ZBkj0.js → search-dialog-C8Oy-FBs.js} +2 -2
  27. package/dist/server/assets/settings-dialog-CQFuAt9B.js +820 -0
  28. package/dist/server/server.js +2 -2
  29. package/package.json +1 -1
  30. package/dist/client/assets/_sessionKey-xUqdePGW.js +0 -97
  31. package/dist/client/assets/button-seqh98ae.js +0 -1
  32. package/dist/client/assets/settings-dialog-CvUsgnnZ.js +0 -1
  33. package/dist/client/assets/styles-DogPvrGY.css +0 -1
  34. package/dist/server/assets/settings-dialog-BBQcU55J.js +0 -731
@@ -1,731 +0,0 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect } from "react";
3
- import { HugeiconsIcon } from "@hugeicons/react";
4
- import { Cancel01Icon, ComputerIcon, Sun01Icon, Moon01Icon, Leaf01Icon, Loading02Icon, Tick01Icon, Cancel02Icon } from "@hugeicons/core-free-icons";
5
- import { D as DialogRoot, a as DialogContent, b as DialogTitle, c as DialogDescription, d as DialogClose } from "./use-file-explorer-state-DMHdtb7D.js";
6
- import { S as Switch } from "./switch-DnX0MjGS.js";
7
- import { Tabs as Tabs$1 } from "@base-ui/react/tabs";
8
- import { c as cn, B as Button } from "./button-DtQ3rV1m.js";
9
- import { a as useChatSettings } from "./index-CmbNTqa2.js";
10
- import { u as useLlmSettings } from "./_sessionKey-DQwkBUS7.js";
11
- import "@base-ui/react/dialog";
12
- import "@base-ui/react/menu";
13
- import "zustand";
14
- import "@base-ui/react/switch";
15
- import "@base-ui/react/merge-props";
16
- import "@base-ui/react/use-render";
17
- import "class-variance-authority";
18
- import "clsx";
19
- import "tailwind-merge";
20
- import "shiki/core";
21
- import "shiki/engine/javascript";
22
- import "@shikijs/themes/vitesse-dark";
23
- import "@shikijs/themes/vitesse-light";
24
- import "@shikijs/langs/bash";
25
- import "@shikijs/langs/c";
26
- import "@shikijs/langs/cpp";
27
- import "@shikijs/langs/csharp";
28
- import "@shikijs/langs/css";
29
- import "@shikijs/langs/diff";
30
- import "@shikijs/langs/dockerfile";
31
- import "@shikijs/langs/go";
32
- import "@shikijs/langs/graphql";
33
- import "@shikijs/langs/html";
34
- import "@shikijs/langs/java";
35
- import "@shikijs/langs/javascript";
36
- import "@shikijs/langs/json";
37
- import "@shikijs/langs/jsx";
38
- import "@shikijs/langs/kotlin";
39
- import "@shikijs/langs/markdown";
40
- import "@shikijs/langs/php";
41
- import "@shikijs/langs/python";
42
- import "@shikijs/langs/regexp";
43
- import "@shikijs/langs/ruby";
44
- import "@shikijs/langs/rust";
45
- import "@shikijs/langs/shell";
46
- import "@shikijs/langs/sql";
47
- import "@shikijs/langs/swift";
48
- import "@shikijs/langs/toml";
49
- import "@shikijs/langs/typescript";
50
- import "@shikijs/langs/tsx";
51
- import "@shikijs/langs/xml";
52
- import "@shikijs/langs/yaml";
53
- import "zustand/middleware";
54
- import "@tanstack/react-router";
55
- import "@tanstack/react-query";
56
- import "./tooltip-gbV6rEVv.js";
57
- import "@base-ui/react/tooltip";
58
- import "motion/react";
59
- import "@base-ui/react/alert-dialog";
60
- import "@base-ui/react/collapsible";
61
- import "@base-ui/react/scroll-area";
62
- import "./opencami-logo-C-43FL3R.js";
63
- import "marked";
64
- import "react-markdown";
65
- import "remark-breaks";
66
- import "remark-gfm";
67
- import "react-dom";
68
- import "./router-5qObg5aX.js";
69
- import "node:crypto";
70
- import "ws";
71
- import "node:fs";
72
- import "node:path";
73
- import "node:os";
74
- import "@tanstack/router-core/ssr/client";
75
- import "node:fs/promises";
76
- import "path";
77
- function Tabs({ className, ...props }) {
78
- return /* @__PURE__ */ jsx(
79
- Tabs$1.Root,
80
- {
81
- className: cn(
82
- "flex flex-col gap-2 data-[orientation=vertical]:flex-row",
83
- className
84
- ),
85
- "data-slot": "tabs",
86
- ...props
87
- }
88
- );
89
- }
90
- function TabsList({
91
- variant = "default",
92
- className,
93
- children,
94
- ...props
95
- }) {
96
- return /* @__PURE__ */ jsxs(
97
- Tabs$1.List,
98
- {
99
- className: cn(
100
- "relative z-0 flex w-fit items-center justify-center gap-x-0.5 text-primary-600",
101
- "data-[orientation=vertical]:flex-col",
102
- variant === "default" ? "p-0.5 text-primary-600/80" : "data-[orientation=vertical]:px-1 data-[orientation=horizontal]:py-1",
103
- className
104
- ),
105
- "data-slot": "tabs-list",
106
- ...props,
107
- children: [
108
- children,
109
- /* @__PURE__ */ jsx(
110
- Tabs$1.Indicator,
111
- {
112
- className: cn(
113
- "-translate-y-(--active-tab-bottom) absolute bottom-0 left-0 h-(--active-tab-height) w-(--active-tab-width) translate-x-(--active-tab-left) transition-[width,translate] duration-200 ease-in-out",
114
- variant === "underline" ? "data-[orientation=vertical]:-translate-x-px z-10 bg-primary-900 data-[orientation=horizontal]:h-0.5 data-[orientation=vertical]:w-0.5 data-[orientation=horizontal]:translate-y-px" : "z-0 rounded-md bg-primary-200"
115
- ),
116
- "data-slot": "tab-indicator"
117
- }
118
- )
119
- ]
120
- }
121
- );
122
- }
123
- function TabsTab({ className, ...props }) {
124
- return /* @__PURE__ */ jsx(
125
- Tabs$1.Tab,
126
- {
127
- className: cn(
128
- '[&_svg]:-mx-0.5 relative z-10 flex h-8 shrink-0 grow cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap rounded-md px-3 text-sm font-medium outline-none transition-[color,background-color,box-shadow] hover:text-primary-900 focus-visible:ring-2 focus-visible:ring-primary-400 data-disabled:pointer-events-none data-[orientation=vertical]:w-full data-[orientation=vertical]:justify-start data-active:text-primary-900 data-disabled:opacity-64 [&_svg:not([class*="size-"])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0',
129
- className
130
- ),
131
- "data-slot": "tabs-tab",
132
- ...props
133
- }
134
- );
135
- }
136
- function SettingsSection({ title, children }) {
137
- return /* @__PURE__ */ jsxs("div", { className: "border-b border-primary-200 py-4 last:border-0", children: [
138
- /* @__PURE__ */ jsx("h3", { className: "mb-3 text-sm font-medium text-primary-900", children: title }),
139
- /* @__PURE__ */ jsx("div", { className: "space-y-3", children })
140
- ] });
141
- }
142
- function SettingsRow({ label, description, children }) {
143
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
144
- /* @__PURE__ */ jsxs("div", { className: "flex-1 select-none", children: [
145
- /* @__PURE__ */ jsx("div", { className: "text-sm text-primary-800", children: label }),
146
- description && /* @__PURE__ */ jsx("div", { className: "text-xs text-primary-500", children: description })
147
- ] }),
148
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children })
149
- ] });
150
- }
151
- const textSizeOptions = [
152
- { value: "14px", label: "S" },
153
- { value: "16px", label: "M" },
154
- { value: "18px", label: "L" },
155
- { value: "20px", label: "XL" }
156
- ];
157
- function isTextSizeValue(value) {
158
- return textSizeOptions.some((option) => option.value === value);
159
- }
160
- function SettingsDialog({
161
- open,
162
- onOpenChange,
163
- onClose
164
- }) {
165
- const { settings, updateSettings } = useChatSettings();
166
- const {
167
- settings: llmSettings,
168
- updateSettings: updateLlmSettings,
169
- status: llmStatus,
170
- testApiKey
171
- } = useLlmSettings();
172
- const [apiKeyInput, setApiKeyInput] = useState(llmSettings.openaiApiKey);
173
- const [testingKey, setTestingKey] = useState(false);
174
- const [testResult, setTestResult] = useState(null);
175
- const [textSize, setTextSize] = useState(() => {
176
- if (typeof window === "undefined") return "16px";
177
- try {
178
- const stored = localStorage.getItem("opencami-text-size");
179
- if (stored && isTextSizeValue(stored)) return stored;
180
- } catch {
181
- }
182
- return "16px";
183
- });
184
- const [ttsEnabled, setTtsEnabled] = useState(() => {
185
- if (typeof window === "undefined") return true;
186
- try {
187
- const stored = localStorage.getItem("opencami-tts-enabled");
188
- return stored === null ? true : stored === "true";
189
- } catch {
190
- return true;
191
- }
192
- });
193
- const [ttsProvider, setTtsProvider] = useState(() => {
194
- if (typeof window === "undefined") return "auto";
195
- try {
196
- return localStorage.getItem("opencami-tts-provider") || "auto";
197
- } catch {
198
- return "auto";
199
- }
200
- });
201
- const [ttsVoice, setTtsVoice] = useState(() => {
202
- if (typeof window === "undefined") return "nova";
203
- try {
204
- return localStorage.getItem("opencami-tts-voice") || "nova";
205
- } catch {
206
- return "nova";
207
- }
208
- });
209
- const [sttProvider, setSttProvider] = useState(() => {
210
- if (typeof window === "undefined") return "auto";
211
- try {
212
- return localStorage.getItem("opencami-stt-provider") || "auto";
213
- } catch {
214
- return "auto";
215
- }
216
- });
217
- const handleTtsProviderChange = (value) => {
218
- setTtsProvider(value);
219
- try {
220
- localStorage.setItem("opencami-tts-provider", value);
221
- } catch {
222
- }
223
- };
224
- const handleTtsVoiceChange = (value) => {
225
- setTtsVoice(value);
226
- try {
227
- localStorage.setItem("opencami-tts-voice", value);
228
- } catch {
229
- }
230
- };
231
- const handleSttProviderChange = (value) => {
232
- setSttProvider(value);
233
- try {
234
- localStorage.setItem("opencami-stt-provider", value);
235
- } catch {
236
- }
237
- };
238
- function applyTextSize(value) {
239
- if (typeof document === "undefined") return;
240
- document.documentElement.style.setProperty("--opencami-text-size", value);
241
- }
242
- const handleTextSizeChange = (value) => {
243
- if (!isTextSizeValue(value)) return;
244
- setTextSize(value);
245
- applyTextSize(value);
246
- try {
247
- localStorage.setItem("opencami-text-size", value);
248
- } catch {
249
- }
250
- };
251
- const handleTtsToggle = (checked) => {
252
- setTtsEnabled(checked);
253
- try {
254
- localStorage.setItem("opencami-tts-enabled", String(checked));
255
- } catch {
256
- }
257
- window.dispatchEvent(new StorageEvent("storage", {
258
- key: "opencami-tts-enabled",
259
- newValue: String(checked)
260
- }));
261
- };
262
- const [personasAvailable, setPersonasAvailable] = useState(false);
263
- const [personasEnabled, setPersonasEnabled] = useState(() => {
264
- if (typeof window === "undefined") return true;
265
- try {
266
- const stored = localStorage.getItem("opencami-personas-enabled");
267
- return stored === null ? true : stored === "true";
268
- } catch {
269
- return true;
270
- }
271
- });
272
- useEffect(() => {
273
- let mounted = true;
274
- async function checkPersonas() {
275
- try {
276
- const res = await fetch("/api/personas");
277
- if (!res.ok) return;
278
- const data = await res.json();
279
- if (mounted && data.ok) {
280
- setPersonasAvailable(data.available);
281
- }
282
- } catch {
283
- }
284
- }
285
- checkPersonas();
286
- return () => {
287
- mounted = false;
288
- };
289
- }, []);
290
- const handlePersonasToggle = (checked) => {
291
- setPersonasEnabled(checked);
292
- try {
293
- localStorage.setItem("opencami-personas-enabled", String(checked));
294
- } catch {
295
- }
296
- window.dispatchEvent(new StorageEvent("storage", {
297
- key: "opencami-personas-enabled",
298
- newValue: String(checked)
299
- }));
300
- };
301
- const handleTestApiKey = async () => {
302
- if (!apiKeyInput.trim()) return;
303
- setTestingKey(true);
304
- setTestResult(null);
305
- try {
306
- const result = await testApiKey(apiKeyInput.trim());
307
- setTestResult(result);
308
- if (result.valid) {
309
- updateLlmSettings({ openaiApiKey: apiKeyInput.trim() });
310
- }
311
- } finally {
312
- setTestingKey(false);
313
- }
314
- };
315
- const handleSaveApiKey = () => {
316
- updateLlmSettings({ openaiApiKey: apiKeyInput.trim() });
317
- setTestResult(null);
318
- };
319
- const handleClearApiKey = () => {
320
- setApiKeyInput("");
321
- updateLlmSettings({ openaiApiKey: "" });
322
- setTestResult(null);
323
- };
324
- const themeOptions = [
325
- { value: "system", label: "System", icon: ComputerIcon },
326
- { value: "light", label: "Light", icon: Sun01Icon },
327
- { value: "dark", label: "Dark", icon: Moon01Icon },
328
- { value: "chameleon", label: "Chameleon", icon: Leaf01Icon }
329
- ];
330
- function applyTheme(theme) {
331
- if (typeof document === "undefined") return;
332
- const root = document.documentElement;
333
- const media = window.matchMedia("(prefers-color-scheme: dark)");
334
- root.classList.remove("light", "dark", "system", "chameleon");
335
- root.classList.add(theme);
336
- if (theme === "system" && media.matches) {
337
- root.classList.add("dark");
338
- }
339
- }
340
- return /* @__PURE__ */ jsx(DialogRoot, { open, onOpenChange, children: /* @__PURE__ */ jsx(DialogContent, { className: "w-[min(480px,92vw)] max-h-[80vh] overflow-auto", children: /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
341
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
342
- /* @__PURE__ */ jsxs("div", { children: [
343
- /* @__PURE__ */ jsx(DialogTitle, { className: "mb-1", children: "Settings" }),
344
- /* @__PURE__ */ jsx(DialogDescription, { className: "hidden", children: "Configure OpenCami" })
345
- ] }),
346
- /* @__PURE__ */ jsx(
347
- DialogClose,
348
- {
349
- render: /* @__PURE__ */ jsx(
350
- Button,
351
- {
352
- size: "icon-sm",
353
- variant: "ghost",
354
- className: "text-primary-500 hover:bg-primary-100 hover:text-primary-700",
355
- "aria-label": "Close",
356
- children: /* @__PURE__ */ jsx(
357
- HugeiconsIcon,
358
- {
359
- icon: Cancel01Icon,
360
- size: 20,
361
- strokeWidth: 1.5
362
- }
363
- )
364
- }
365
- )
366
- }
367
- )
368
- ] }),
369
- /* @__PURE__ */ jsx(SettingsSection, { title: "Connection", children: /* @__PURE__ */ jsx(SettingsRow, { label: "Status", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-sm text-green-600", children: [
370
- /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-green-500" }),
371
- "Connected"
372
- ] }) }) }),
373
- /* @__PURE__ */ jsxs(SettingsSection, { title: "Appearance", children: [
374
- /* @__PURE__ */ jsx(SettingsRow, { label: "Theme", children: /* @__PURE__ */ jsx(
375
- Tabs,
376
- {
377
- value: settings.theme,
378
- onValueChange: (value) => {
379
- const theme = value;
380
- applyTheme(theme);
381
- updateSettings({ theme });
382
- },
383
- children: /* @__PURE__ */ jsx(
384
- TabsList,
385
- {
386
- variant: "default",
387
- className: "gap-2 *:data-[slot=tab-indicator]:duration-0",
388
- children: themeOptions.map((option) => /* @__PURE__ */ jsxs(TabsTab, { value: option.value, children: [
389
- /* @__PURE__ */ jsx(
390
- HugeiconsIcon,
391
- {
392
- icon: option.icon,
393
- size: 20,
394
- strokeWidth: 1.5
395
- }
396
- ),
397
- /* @__PURE__ */ jsx("span", { children: option.label })
398
- ] }, option.value))
399
- }
400
- )
401
- }
402
- ) }),
403
- /* @__PURE__ */ jsx(
404
- SettingsRow,
405
- {
406
- label: "Text Size",
407
- description: "Adjust chat and composer text",
408
- children: /* @__PURE__ */ jsx(Tabs, { value: textSize, onValueChange: handleTextSizeChange, children: /* @__PURE__ */ jsx(
409
- TabsList,
410
- {
411
- variant: "default",
412
- className: "gap-2 *:data-[slot=tab-indicator]:duration-0",
413
- children: textSizeOptions.map((option) => /* @__PURE__ */ jsx(TabsTab, { value: option.value, children: /* @__PURE__ */ jsx("span", { className: "tabular-nums text-xs", children: option.label }) }, option.value))
414
- }
415
- ) })
416
- }
417
- )
418
- ] }),
419
- /* @__PURE__ */ jsxs(SettingsSection, { title: "Chat", children: [
420
- /* @__PURE__ */ jsx(SettingsRow, { label: "Show tool messages", children: /* @__PURE__ */ jsx(
421
- Switch,
422
- {
423
- checked: settings.showToolMessages,
424
- onCheckedChange: (checked) => updateSettings({ showToolMessages: checked })
425
- }
426
- ) }),
427
- /* @__PURE__ */ jsx(SettingsRow, { label: "Show reasoning blocks", children: /* @__PURE__ */ jsx(
428
- Switch,
429
- {
430
- checked: settings.showReasoningBlocks,
431
- onCheckedChange: (checked) => updateSettings({ showReasoningBlocks: checked })
432
- }
433
- ) }),
434
- /* @__PURE__ */ jsx(SettingsRow, { label: "Show search sources", children: /* @__PURE__ */ jsx(
435
- Switch,
436
- {
437
- checked: settings.showSearchSources,
438
- onCheckedChange: (checked) => updateSettings({ showSearchSources: checked })
439
- }
440
- ) }),
441
- /* @__PURE__ */ jsx(
442
- SettingsRow,
443
- {
444
- label: "Agent Manager (Beta)",
445
- description: "Show Agent Manager in sidebar for creating and managing agents",
446
- children: /* @__PURE__ */ jsx(
447
- Switch,
448
- {
449
- checked: (() => {
450
- try {
451
- return localStorage.getItem("opencami-agent-manager") === "true";
452
- } catch {
453
- return false;
454
- }
455
- })(),
456
- onCheckedChange: (checked) => {
457
- localStorage.setItem("opencami-agent-manager", String(checked));
458
- window.location.reload();
459
- }
460
- }
461
- )
462
- }
463
- ),
464
- /* @__PURE__ */ jsx(
465
- SettingsRow,
466
- {
467
- label: "Inline File Preview",
468
- description: "Make file paths in messages clickable to preview file contents",
469
- children: /* @__PURE__ */ jsx(
470
- Switch,
471
- {
472
- checked: settings.inlineFilePreview,
473
- onCheckedChange: (checked) => updateSettings({ inlineFilePreview: checked })
474
- }
475
- )
476
- }
477
- )
478
- ] }),
479
- /* @__PURE__ */ jsxs(SettingsSection, { title: "Personas", children: [
480
- personasAvailable ? /* @__PURE__ */ jsx(
481
- SettingsRow,
482
- {
483
- label: "Persona Picker",
484
- description: "Show persona picker in chat (20 personalities)",
485
- children: /* @__PURE__ */ jsx(
486
- Switch,
487
- {
488
- checked: personasEnabled,
489
- onCheckedChange: handlePersonasToggle
490
- }
491
- )
492
- }
493
- ) : /* @__PURE__ */ jsx(
494
- SettingsRow,
495
- {
496
- label: "Persona Picker",
497
- description: "Install the Personas skill to unlock 20 AI personalities",
498
- children: /* @__PURE__ */ jsx(Switch, { checked: false, disabled: true })
499
- }
500
- ),
501
- !personasAvailable && /* @__PURE__ */ jsx("div", { className: "pt-1", children: /* @__PURE__ */ jsx(
502
- "a",
503
- {
504
- href: "https://www.clawhub.ai/robbyczgw-cla/personas",
505
- target: "_blank",
506
- rel: "noopener noreferrer",
507
- className: "text-xs text-primary-600 hover:text-primary-900 hover:underline",
508
- children: "Get it on ClawHub →"
509
- }
510
- ) })
511
- ] }),
512
- /* @__PURE__ */ jsxs(SettingsSection, { title: "Text-to-Speech", children: [
513
- /* @__PURE__ */ jsx(
514
- SettingsRow,
515
- {
516
- label: "Voice Playback",
517
- description: "Add a 🔊 button to AI messages for text-to-speech",
518
- children: /* @__PURE__ */ jsx(
519
- Switch,
520
- {
521
- checked: ttsEnabled,
522
- onCheckedChange: handleTtsToggle
523
- }
524
- )
525
- }
526
- ),
527
- /* @__PURE__ */ jsx(
528
- SettingsRow,
529
- {
530
- label: "TTS Provider",
531
- description: "Choose which service generates speech",
532
- children: /* @__PURE__ */ jsxs(
533
- "select",
534
- {
535
- value: ttsProvider,
536
- onChange: (e) => handleTtsProviderChange(e.target.value),
537
- 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",
538
- children: [
539
- /* @__PURE__ */ jsx("option", { value: "auto", children: "Auto" }),
540
- /* @__PURE__ */ jsx("option", { value: "elevenlabs", children: "ElevenLabs" }),
541
- /* @__PURE__ */ jsx("option", { value: "openai", children: "OpenAI" }),
542
- /* @__PURE__ */ jsx("option", { value: "edge", children: "Edge TTS (free)" })
543
- ]
544
- }
545
- )
546
- }
547
- ),
548
- ttsProvider === "openai" && /* @__PURE__ */ jsx(
549
- SettingsRow,
550
- {
551
- label: "Voice",
552
- description: "OpenAI voice selection",
553
- children: /* @__PURE__ */ jsx(
554
- "select",
555
- {
556
- value: ttsVoice,
557
- onChange: (e) => handleTtsVoiceChange(e.target.value),
558
- 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",
559
- children: ["alloy", "echo", "fable", "onyx", "nova", "shimmer"].map((v) => /* @__PURE__ */ jsx("option", { value: v, children: v.charAt(0).toUpperCase() + v.slice(1) }, v))
560
- }
561
- )
562
- }
563
- )
564
- ] }),
565
- /* @__PURE__ */ jsx(SettingsSection, { title: "Speech-to-Text", children: /* @__PURE__ */ jsx(
566
- SettingsRow,
567
- {
568
- label: "STT Provider",
569
- description: "Choose which service transcribes your voice",
570
- children: /* @__PURE__ */ jsxs(
571
- "select",
572
- {
573
- value: sttProvider,
574
- onChange: (e) => handleSttProviderChange(e.target.value),
575
- 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",
576
- children: [
577
- /* @__PURE__ */ jsx("option", { value: "auto", children: "Auto" }),
578
- /* @__PURE__ */ jsx("option", { value: "elevenlabs", children: "ElevenLabs" }),
579
- /* @__PURE__ */ jsx("option", { value: "openai", children: "OpenAI" }),
580
- /* @__PURE__ */ jsx("option", { value: "browser", children: "Browser (free)" })
581
- ]
582
- }
583
- )
584
- }
585
- ) }),
586
- /* @__PURE__ */ jsxs(SettingsSection, { title: "LLM Features", children: [
587
- /* @__PURE__ */ jsxs("div", { className: "text-xs text-primary-500 mb-3", children: [
588
- "Enhance session titles and follow-up suggestions using OpenAI API.",
589
- llmStatus.hasEnvKey && /* @__PURE__ */ jsx("span", { className: "block mt-1 text-green-600", children: "✓ Server has OPENAI_API_KEY configured" })
590
- ] }),
591
- /* @__PURE__ */ jsx(
592
- SettingsRow,
593
- {
594
- label: "Smart session titles",
595
- description: "Generate concise titles using AI",
596
- children: /* @__PURE__ */ jsx(
597
- Switch,
598
- {
599
- checked: llmSettings.useLlmTitles,
600
- onCheckedChange: (checked) => updateLlmSettings({ useLlmTitles: checked }),
601
- disabled: !llmStatus.isAvailable
602
- }
603
- )
604
- }
605
- ),
606
- /* @__PURE__ */ jsx(
607
- SettingsRow,
608
- {
609
- label: "Smart follow-up suggestions",
610
- description: "AI-generated contextual follow-ups",
611
- children: /* @__PURE__ */ jsx(
612
- Switch,
613
- {
614
- checked: llmSettings.useLlmFollowUps,
615
- onCheckedChange: (checked) => updateLlmSettings({ useLlmFollowUps: checked }),
616
- disabled: !llmStatus.isAvailable
617
- }
618
- )
619
- }
620
- ),
621
- /* @__PURE__ */ jsxs("div", { className: "mt-4 pt-3 border-t border-primary-100", children: [
622
- /* @__PURE__ */ jsx("div", { className: "text-sm text-primary-800 mb-2", children: "OpenAI API Key" }),
623
- /* @__PURE__ */ jsx("div", { className: "text-xs text-primary-500 mb-2", children: llmStatus.hasEnvKey ? "Optional: Override server key with your own" : "Required for LLM features (stored locally)" }),
624
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
625
- /* @__PURE__ */ jsx(
626
- "input",
627
- {
628
- type: "password",
629
- value: apiKeyInput,
630
- onChange: (e) => {
631
- setApiKeyInput(e.target.value);
632
- setTestResult(null);
633
- },
634
- placeholder: "sk-...",
635
- 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"
636
- }
637
- ),
638
- /* @__PURE__ */ jsx(
639
- Button,
640
- {
641
- size: "sm",
642
- variant: "outline",
643
- onClick: handleTestApiKey,
644
- disabled: !apiKeyInput.trim() || testingKey,
645
- className: "min-w-[60px]",
646
- children: testingKey ? /* @__PURE__ */ jsx(
647
- HugeiconsIcon,
648
- {
649
- icon: Loading02Icon,
650
- size: 16,
651
- className: "animate-spin"
652
- }
653
- ) : "Test"
654
- }
655
- )
656
- ] }),
657
- testResult && /* @__PURE__ */ jsxs(
658
- "div",
659
- {
660
- className: `mt-2 flex items-center gap-1.5 text-xs ${testResult.valid ? "text-green-600" : "text-red-600"}`,
661
- children: [
662
- /* @__PURE__ */ jsx(
663
- HugeiconsIcon,
664
- {
665
- icon: testResult.valid ? Tick01Icon : Cancel02Icon,
666
- size: 14
667
- }
668
- ),
669
- testResult.valid ? "API key is valid" : testResult.error || "Invalid API key"
670
- ]
671
- }
672
- ),
673
- llmSettings.openaiApiKey && /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center justify-between", children: [
674
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-green-600 flex items-center gap-1", children: [
675
- /* @__PURE__ */ jsx(HugeiconsIcon, { icon: Tick01Icon, size: 14 }),
676
- "Key saved"
677
- ] }),
678
- /* @__PURE__ */ jsx(
679
- Button,
680
- {
681
- size: "sm",
682
- variant: "ghost",
683
- onClick: handleClearApiKey,
684
- className: "text-xs text-red-600 hover:text-red-700 hover:bg-red-50",
685
- children: "Clear"
686
- }
687
- )
688
- ] }),
689
- apiKeyInput && apiKeyInput !== llmSettings.openaiApiKey && !testResult?.valid && /* @__PURE__ */ jsx(
690
- Button,
691
- {
692
- size: "sm",
693
- variant: "outline",
694
- onClick: handleSaveApiKey,
695
- className: "mt-2 w-full",
696
- children: "Save without testing"
697
- }
698
- )
699
- ] })
700
- ] }),
701
- /* @__PURE__ */ jsxs(SettingsSection, { title: "About", children: [
702
- /* @__PURE__ */ jsx("div", { className: "text-sm text-primary-800", children: "OpenCami" }),
703
- /* @__PURE__ */ jsxs("div", { className: "flex gap-4 pt-2", children: [
704
- /* @__PURE__ */ jsx(
705
- "a",
706
- {
707
- href: "https://github.com/robbyczgw-cla/opencami",
708
- target: "_blank",
709
- rel: "noopener noreferrer",
710
- className: "text-sm text-primary-600 hover:text-primary-900 hover:underline",
711
- children: "GitHub"
712
- }
713
- ),
714
- /* @__PURE__ */ jsx(
715
- "a",
716
- {
717
- href: "https://docs.openclaw.ai",
718
- target: "_blank",
719
- rel: "noopener noreferrer",
720
- className: "text-sm text-primary-600 hover:text-primary-900 hover:underline",
721
- children: "OpenClaw docs"
722
- }
723
- )
724
- ] })
725
- ] }),
726
- /* @__PURE__ */ jsx("div", { className: "mt-6 flex justify-end", children: /* @__PURE__ */ jsx(DialogClose, { onClick: onClose, children: "Close" }) })
727
- ] }) }) });
728
- }
729
- export {
730
- SettingsDialog
731
- };