groove-dev 0.27.73 → 0.27.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/api.js +256 -4
- package/node_modules/@groove-dev/daemon/src/conversations.js +16 -0
- package/node_modules/@groove-dev/daemon/src/index.js +41 -1
- package/node_modules/@groove-dev/daemon/src/preview.js +18 -2
- package/node_modules/@groove-dev/daemon/src/process.js +6 -1
- package/node_modules/@groove-dev/daemon/src/providers/base.js +4 -0
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +2 -1
- package/node_modules/@groove-dev/daemon/src/providers/codex.js +41 -1
- package/node_modules/@groove-dev/daemon/src/providers/gemini.js +2 -1
- package/node_modules/@groove-dev/daemon/src/providers/grok.js +156 -0
- package/node_modules/@groove-dev/daemon/src/providers/index.js +26 -9
- package/node_modules/@groove-dev/daemon/src/providers/nano-banana.js +103 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CAT9SCJi.js +8620 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CVzz6zyb.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/app.css +29 -0
- package/node_modules/@groove-dev/gui/src/app.jsx +2 -0
- package/node_modules/@groove-dev/gui/src/components/chat/chat-header.jsx +16 -5
- package/node_modules/@groove-dev/gui/src/components/chat/chat-input.jsx +40 -7
- package/node_modules/@groove-dev/gui/src/components/chat/chat-messages.jsx +149 -31
- package/node_modules/@groove-dev/gui/src/components/chat/chat-view.jsx +26 -2
- package/node_modules/@groove-dev/gui/src/components/chat/model-picker.jsx +105 -52
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +5 -2
- package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +215 -88
- package/node_modules/@groove-dev/gui/src/components/preview/preview-toolbar.jsx +81 -0
- package/node_modules/@groove-dev/gui/src/components/preview/preview-workspace.jsx +263 -0
- package/node_modules/@groove-dev/gui/src/components/preview/screenshot-overlay.jsx +203 -0
- package/node_modules/@groove-dev/gui/src/components/ui/toast.jsx +6 -2
- package/node_modules/@groove-dev/gui/src/stores/groove.js +149 -9
- package/node_modules/@groove-dev/gui/src/views/preview.jsx +6 -0
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +278 -123
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/api.js +256 -4
- package/packages/daemon/src/conversations.js +16 -0
- package/packages/daemon/src/index.js +41 -1
- package/packages/daemon/src/preview.js +18 -2
- package/packages/daemon/src/process.js +6 -1
- package/packages/daemon/src/providers/base.js +4 -0
- package/packages/daemon/src/providers/claude-code.js +2 -1
- package/packages/daemon/src/providers/codex.js +41 -1
- package/packages/daemon/src/providers/gemini.js +2 -1
- package/packages/daemon/src/providers/grok.js +156 -0
- package/packages/daemon/src/providers/index.js +26 -9
- package/packages/daemon/src/providers/nano-banana.js +103 -0
- package/packages/gui/dist/assets/index-CAT9SCJi.js +8620 -0
- package/packages/gui/dist/assets/index-CVzz6zyb.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/app.css +29 -0
- package/packages/gui/src/app.jsx +2 -0
- package/packages/gui/src/components/chat/chat-header.jsx +16 -5
- package/packages/gui/src/components/chat/chat-input.jsx +40 -7
- package/packages/gui/src/components/chat/chat-messages.jsx +149 -31
- package/packages/gui/src/components/chat/chat-view.jsx +26 -2
- package/packages/gui/src/components/chat/model-picker.jsx +105 -52
- package/packages/gui/src/components/layout/activity-bar.jsx +5 -2
- package/packages/gui/src/components/layout/welcome-splash.jsx +215 -88
- package/packages/gui/src/components/preview/preview-toolbar.jsx +81 -0
- package/packages/gui/src/components/preview/preview-workspace.jsx +263 -0
- package/packages/gui/src/components/preview/screenshot-overlay.jsx +203 -0
- package/packages/gui/src/components/ui/toast.jsx +6 -2
- package/packages/gui/src/stores/groove.js +149 -9
- package/packages/gui/src/views/preview.jsx +6 -0
- package/packages/gui/src/views/settings.jsx +278 -123
- package/node_modules/@groove-dev/gui/dist/assets/index-BFc7Ov6v.css +0 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-Deza1S0i.js +0 -8615
- package/packages/gui/dist/assets/index-BFc7Ov6v.css +0 -1
- package/packages/gui/dist/assets/index-Deza1S0i.js +0 -8615
|
@@ -14,7 +14,7 @@ import { api } from '../lib/api';
|
|
|
14
14
|
import { cn } from '../lib/cn';
|
|
15
15
|
import { fmtUptime } from '../lib/format';
|
|
16
16
|
import {
|
|
17
|
-
Key, Eye, EyeOff, Check, Cpu, Download, Loader2,
|
|
17
|
+
Key, Eye, EyeOff, Check, Cpu, Download, Loader2, RefreshCw, Terminal, Copy,
|
|
18
18
|
FolderOpen, FolderSearch, Users, Gauge, ChevronRight,
|
|
19
19
|
ShieldCheck, Settings, Lock,
|
|
20
20
|
Newspaper, Radio, Send, MessageSquare, MessageCircle,
|
|
@@ -46,6 +46,7 @@ const KEY_PLACEHOLDERS = {
|
|
|
46
46
|
'claude-code': 'sk-ant-...',
|
|
47
47
|
codex: 'Paste from platform.openai.com',
|
|
48
48
|
gemini: 'Paste from aistudio.google.com',
|
|
49
|
+
grok: 'xai-...',
|
|
49
50
|
};
|
|
50
51
|
|
|
51
52
|
function ProviderCard({ provider, onKeyChange }) {
|
|
@@ -58,10 +59,14 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
58
59
|
const [customPathOpen, setCustomPathOpen] = useState(false);
|
|
59
60
|
const [customPath, setCustomPath] = useState('');
|
|
60
61
|
const [savingPath, setSavingPath] = useState(false);
|
|
62
|
+
const [loginPending, setLoginPending] = useState(false);
|
|
61
63
|
const addToast = useGrooveStore((s) => s.addToast);
|
|
62
64
|
const installProgress = useGrooveStore((s) => s.providerInstallProgress[provider.id]);
|
|
63
65
|
const loginProvider = useGrooveStore((s) => s.loginProvider);
|
|
64
66
|
const setProviderPath = useGrooveStore((s) => s.setProviderPath);
|
|
67
|
+
const verifyProvider = useGrooveStore((s) => s.verifyProvider);
|
|
68
|
+
const installProvider = useGrooveStore((s) => s.installProvider);
|
|
69
|
+
const [checking, setChecking] = useState(false);
|
|
65
70
|
|
|
66
71
|
const isLocal = provider.authType === 'local';
|
|
67
72
|
const isSubscription = provider.authType === 'subscription';
|
|
@@ -94,8 +99,11 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
94
99
|
|
|
95
100
|
async function handleLogin(body) {
|
|
96
101
|
try {
|
|
102
|
+
setLoginPending(true);
|
|
97
103
|
await loginProvider(provider.id, body);
|
|
98
|
-
} catch {
|
|
104
|
+
} catch {
|
|
105
|
+
setLoginPending(false);
|
|
106
|
+
}
|
|
99
107
|
}
|
|
100
108
|
|
|
101
109
|
async function handleSavePath() {
|
|
@@ -221,73 +229,240 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
221
229
|
</div>
|
|
222
230
|
)}
|
|
223
231
|
|
|
224
|
-
{/* Not installed
|
|
232
|
+
{/* Not installed */}
|
|
225
233
|
{!provider.installed && !isInstalling && !settingKey && (
|
|
226
|
-
<div className="flex flex-col
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
<div className="flex flex-col gap-2.5 flex-1">
|
|
235
|
+
{/* Install error from last attempt */}
|
|
236
|
+
{installProgress?.error && (
|
|
237
|
+
<div className="p-2.5 bg-danger/5 border border-danger/15 rounded-md">
|
|
238
|
+
<p className="text-2xs text-danger font-sans break-all">{installProgress.error}</p>
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
241
|
+
|
|
242
|
+
{/* Auto-install button */}
|
|
230
243
|
<Button
|
|
231
244
|
variant="primary"
|
|
232
245
|
size="sm"
|
|
233
|
-
onClick={() =>
|
|
234
|
-
className="h-8
|
|
246
|
+
onClick={() => installProvider(provider.id).catch(() => {})}
|
|
247
|
+
className="w-full h-8 text-2xs gap-1.5"
|
|
235
248
|
>
|
|
236
|
-
<Download size={
|
|
249
|
+
<Download size={11} /> Install {provider.name}
|
|
237
250
|
</Button>
|
|
251
|
+
|
|
252
|
+
{/* Manual install command */}
|
|
253
|
+
{provider.installCommand && (
|
|
254
|
+
<div className="space-y-1">
|
|
255
|
+
<p className="text-2xs text-text-4 font-sans">Or install manually in your terminal:</p>
|
|
256
|
+
<div className="flex items-center gap-1">
|
|
257
|
+
<code className="flex-1 px-2 py-1.5 bg-surface-0 border border-border-subtle rounded text-2xs font-mono text-text-2 select-all">
|
|
258
|
+
{provider.installCommand}
|
|
259
|
+
</code>
|
|
260
|
+
<button
|
|
261
|
+
onClick={() => { navigator.clipboard.writeText(provider.installCommand); addToast('success', 'Copied'); }}
|
|
262
|
+
className="p-1.5 text-text-4 hover:text-text-2 cursor-pointer"
|
|
263
|
+
>
|
|
264
|
+
<Copy size={10} />
|
|
265
|
+
</button>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
)}
|
|
269
|
+
|
|
270
|
+
{/* Re-check + custom path */}
|
|
271
|
+
<div className="flex items-center gap-2 pt-1">
|
|
272
|
+
<Button
|
|
273
|
+
variant="ghost"
|
|
274
|
+
size="sm"
|
|
275
|
+
onClick={async () => {
|
|
276
|
+
setChecking(true);
|
|
277
|
+
try {
|
|
278
|
+
await verifyProvider(provider.id);
|
|
279
|
+
if (onKeyChange) onKeyChange();
|
|
280
|
+
} catch { /* handled in store */ }
|
|
281
|
+
setChecking(false);
|
|
282
|
+
}}
|
|
283
|
+
disabled={checking}
|
|
284
|
+
className="h-7 text-2xs gap-1 px-2"
|
|
285
|
+
>
|
|
286
|
+
<RefreshCw size={10} className={checking ? 'animate-spin' : ''} /> Re-check
|
|
287
|
+
</Button>
|
|
288
|
+
<button
|
|
289
|
+
onClick={() => setCustomPathOpen(!customPathOpen)}
|
|
290
|
+
className="text-2xs text-text-4 hover:text-accent cursor-pointer font-sans"
|
|
291
|
+
>
|
|
292
|
+
Set custom path
|
|
293
|
+
</button>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
{/* Custom path input (shown for not-installed too) */}
|
|
297
|
+
{customPathOpen && (
|
|
298
|
+
<div className="space-y-2">
|
|
299
|
+
<div className="flex gap-2">
|
|
300
|
+
<input
|
|
301
|
+
value={customPath}
|
|
302
|
+
onChange={(e) => setCustomPath(e.target.value)}
|
|
303
|
+
onKeyDown={(e) => e.key === 'Enter' && handleSavePath()}
|
|
304
|
+
placeholder={`/path/to/${provider.id}`}
|
|
305
|
+
className="flex-1 h-7 px-2 text-2xs bg-surface-0 border border-border-subtle rounded-md text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent"
|
|
306
|
+
/>
|
|
307
|
+
<Button variant="primary" size="sm" onClick={handleSavePath} disabled={!customPath.trim() || savingPath} className="h-7 text-2xs px-2.5">
|
|
308
|
+
{savingPath ? '...' : 'Save'}
|
|
309
|
+
</Button>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
)}
|
|
238
313
|
</div>
|
|
239
314
|
)}
|
|
240
315
|
|
|
241
316
|
{/* Installed but needs auth */}
|
|
242
317
|
{provider.installed && !isReady && !settingKey && !isInstalling && (
|
|
243
|
-
<div className="flex flex-col gap-
|
|
244
|
-
{
|
|
318
|
+
<div className="flex flex-col gap-3 flex-1">
|
|
319
|
+
{/* ── Claude Code auth ── */}
|
|
320
|
+
{provider.id === 'claude-code' && !loginPending && (
|
|
245
321
|
<>
|
|
246
|
-
<
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
322
|
+
<div className="space-y-1.5">
|
|
323
|
+
<p className="text-xs text-text-1 font-sans font-medium">Sign in with your Claude account</p>
|
|
324
|
+
<p className="text-2xs text-text-3 font-sans">A browser window will open where you can sign in with your existing Anthropic account or Claude subscription.</p>
|
|
325
|
+
</div>
|
|
326
|
+
<Button variant="primary" size="sm" onClick={() => handleLogin()} className="w-full h-9 text-xs gap-1.5">
|
|
327
|
+
<ExternalLink size={12} /> Sign In
|
|
328
|
+
</Button>
|
|
329
|
+
<button
|
|
330
|
+
onClick={() => { setSettingKey(true); setShowKey(false); setKeyInput(''); }}
|
|
331
|
+
className="text-2xs text-text-4 hover:text-accent cursor-pointer font-sans text-center"
|
|
251
332
|
>
|
|
252
|
-
|
|
333
|
+
I have an API key instead
|
|
334
|
+
</button>
|
|
335
|
+
</>
|
|
336
|
+
)}
|
|
337
|
+
|
|
338
|
+
{/* ── Codex auth ── */}
|
|
339
|
+
{provider.id === 'codex' && !loginPending && (
|
|
340
|
+
<>
|
|
341
|
+
<div className="space-y-1.5">
|
|
342
|
+
<p className="text-xs text-text-1 font-sans font-medium">Sign in with your ChatGPT account</p>
|
|
343
|
+
<p className="text-2xs text-text-3 font-sans">A browser window will open where you can sign in with your ChatGPT Plus or Teams subscription.</p>
|
|
344
|
+
</div>
|
|
345
|
+
<Button variant="primary" size="sm" onClick={() => handleLogin({ method: 'chatgpt-plus' })} className="w-full h-9 text-xs gap-1.5">
|
|
346
|
+
<ExternalLink size={12} /> Sign In
|
|
253
347
|
</Button>
|
|
254
|
-
<
|
|
255
|
-
variant="secondary"
|
|
256
|
-
size="sm"
|
|
348
|
+
<button
|
|
257
349
|
onClick={() => { setSettingKey(true); setShowKey(false); setKeyInput(''); }}
|
|
258
|
-
className="
|
|
350
|
+
className="text-2xs text-text-4 hover:text-accent cursor-pointer font-sans text-center"
|
|
259
351
|
>
|
|
260
|
-
|
|
352
|
+
I have an API key instead
|
|
353
|
+
</button>
|
|
354
|
+
</>
|
|
355
|
+
)}
|
|
356
|
+
|
|
357
|
+
{/* ── Gemini auth ── */}
|
|
358
|
+
{provider.id === 'gemini' && (
|
|
359
|
+
<>
|
|
360
|
+
<div className="space-y-2">
|
|
361
|
+
<p className="text-xs text-text-1 font-sans font-medium">Add your Gemini API key</p>
|
|
362
|
+
<div className="space-y-1.5">
|
|
363
|
+
<div className="flex items-start gap-2">
|
|
364
|
+
<span className="text-2xs font-bold text-accent font-mono mt-0.5">1</span>
|
|
365
|
+
<p className="text-2xs text-text-2 font-sans">
|
|
366
|
+
Go to <button onClick={() => window.open('https://aistudio.google.com/apikey', '_blank')} className="text-accent hover:underline cursor-pointer font-sans">aistudio.google.com</button> and sign in with Google
|
|
367
|
+
</p>
|
|
368
|
+
</div>
|
|
369
|
+
<div className="flex items-start gap-2">
|
|
370
|
+
<span className="text-2xs font-bold text-accent font-mono mt-0.5">2</span>
|
|
371
|
+
<p className="text-2xs text-text-2 font-sans">Click "Create API Key" and copy it</p>
|
|
372
|
+
</div>
|
|
373
|
+
<div className="flex items-start gap-2">
|
|
374
|
+
<span className="text-2xs font-bold text-accent font-mono mt-0.5">3</span>
|
|
375
|
+
<p className="text-2xs text-text-2 font-sans">Paste it below</p>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
<div className="relative">
|
|
380
|
+
<input
|
|
381
|
+
value={keyInput}
|
|
382
|
+
onChange={(e) => setKeyInput(e.target.value)}
|
|
383
|
+
onKeyDown={(e) => e.key === 'Enter' && handleSetKey()}
|
|
384
|
+
type={showKey ? 'text' : 'password'}
|
|
385
|
+
placeholder="AIza..."
|
|
386
|
+
className="w-full h-9 px-3 pr-9 text-xs bg-surface-0 border border-border rounded-md text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent"
|
|
387
|
+
autoFocus
|
|
388
|
+
/>
|
|
389
|
+
<button onClick={() => setShowKey(!showKey)} className="absolute right-2.5 top-1/2 -translate-y-1/2 text-text-4 hover:text-text-2 cursor-pointer">
|
|
390
|
+
{showKey ? <EyeOff size={12} /> : <Eye size={12} />}
|
|
391
|
+
</button>
|
|
392
|
+
</div>
|
|
393
|
+
<Button variant="primary" size="sm" onClick={handleSetKey} disabled={!keyInput.trim()} className="w-full h-8 text-xs">
|
|
394
|
+
Save Key
|
|
261
395
|
</Button>
|
|
262
396
|
</>
|
|
263
|
-
)
|
|
397
|
+
)}
|
|
398
|
+
|
|
399
|
+
{/* ── Grok (xAI) auth ── */}
|
|
400
|
+
{provider.id === 'grok' && (
|
|
264
401
|
<>
|
|
402
|
+
<div className="space-y-2">
|
|
403
|
+
<p className="text-xs text-text-1 font-sans font-medium">Add your xAI API key</p>
|
|
404
|
+
<div className="space-y-1.5">
|
|
405
|
+
<div className="flex items-start gap-2">
|
|
406
|
+
<span className="text-2xs font-bold text-accent font-mono mt-0.5">1</span>
|
|
407
|
+
<p className="text-2xs text-text-2 font-sans">
|
|
408
|
+
Go to <button onClick={() => window.open('https://console.x.ai', '_blank')} className="text-accent hover:underline cursor-pointer font-sans">console.x.ai</button> and sign in
|
|
409
|
+
</p>
|
|
410
|
+
</div>
|
|
411
|
+
<div className="flex items-start gap-2">
|
|
412
|
+
<span className="text-2xs font-bold text-accent font-mono mt-0.5">2</span>
|
|
413
|
+
<p className="text-2xs text-text-2 font-sans">Create an API key and copy it</p>
|
|
414
|
+
</div>
|
|
415
|
+
<div className="flex items-start gap-2">
|
|
416
|
+
<span className="text-2xs font-bold text-accent font-mono mt-0.5">3</span>
|
|
417
|
+
<p className="text-2xs text-text-2 font-sans">Paste it below</p>
|
|
418
|
+
</div>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
<div className="relative">
|
|
422
|
+
<input
|
|
423
|
+
value={keyInput}
|
|
424
|
+
onChange={(e) => setKeyInput(e.target.value)}
|
|
425
|
+
onKeyDown={(e) => e.key === 'Enter' && handleSetKey()}
|
|
426
|
+
type={showKey ? 'text' : 'password'}
|
|
427
|
+
placeholder="xai-..."
|
|
428
|
+
className="w-full h-9 px-3 pr-9 text-xs bg-surface-0 border border-border rounded-md text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:ring-1 focus:ring-accent"
|
|
429
|
+
autoFocus
|
|
430
|
+
/>
|
|
431
|
+
<button onClick={() => setShowKey(!showKey)} className="absolute right-2.5 top-1/2 -translate-y-1/2 text-text-4 hover:text-text-2 cursor-pointer">
|
|
432
|
+
{showKey ? <EyeOff size={12} /> : <Eye size={12} />}
|
|
433
|
+
</button>
|
|
434
|
+
</div>
|
|
435
|
+
<Button variant="primary" size="sm" onClick={handleSetKey} disabled={!keyInput.trim()} className="w-full h-8 text-xs">
|
|
436
|
+
Save Key
|
|
437
|
+
</Button>
|
|
438
|
+
</>
|
|
439
|
+
)}
|
|
440
|
+
|
|
441
|
+
{/* ── Any provider: login pending state ── */}
|
|
442
|
+
{(provider.id === 'claude-code' || provider.id === 'codex') && loginPending && (
|
|
443
|
+
<div className="flex flex-col gap-3">
|
|
444
|
+
<div className="flex items-center gap-2 p-3 bg-accent/5 border border-accent/15 rounded-md">
|
|
445
|
+
<Loader2 size={14} className="text-accent animate-spin" />
|
|
446
|
+
<div>
|
|
447
|
+
<p className="text-xs text-accent font-sans font-medium">Check your browser</p>
|
|
448
|
+
<p className="text-2xs text-text-3 font-sans">Complete the sign-in in the browser window that opened.</p>
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
265
451
|
<Button
|
|
266
452
|
variant="primary"
|
|
267
453
|
size="sm"
|
|
268
|
-
onClick={() => {
|
|
269
|
-
className="w-full h-8 text-
|
|
454
|
+
onClick={() => { setLoginPending(false); if (onKeyChange) onKeyChange(); }}
|
|
455
|
+
className="w-full h-8 text-xs gap-1.5"
|
|
270
456
|
>
|
|
271
|
-
<
|
|
457
|
+
<Check size={12} /> I've signed in
|
|
272
458
|
</Button>
|
|
273
|
-
<
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
onClick={() => handleLogin({ method: 'chatgpt-plus' })}
|
|
277
|
-
className="w-full h-8 text-2xs gap-1.5"
|
|
459
|
+
<button
|
|
460
|
+
onClick={() => setLoginPending(false)}
|
|
461
|
+
className="text-2xs text-text-4 hover:text-text-2 cursor-pointer font-sans text-center"
|
|
278
462
|
>
|
|
279
|
-
|
|
280
|
-
</
|
|
281
|
-
|
|
282
|
-
) : (
|
|
283
|
-
<Button
|
|
284
|
-
variant="primary"
|
|
285
|
-
size="sm"
|
|
286
|
-
onClick={() => { setSettingKey(true); setShowKey(false); setKeyInput(''); }}
|
|
287
|
-
className="w-full h-8 text-2xs gap-1.5"
|
|
288
|
-
>
|
|
289
|
-
<Key size={11} /> Add API Key
|
|
290
|
-
</Button>
|
|
463
|
+
Cancel
|
|
464
|
+
</button>
|
|
465
|
+
</div>
|
|
291
466
|
)}
|
|
292
467
|
</div>
|
|
293
468
|
)}
|
|
@@ -320,6 +495,26 @@ function ProviderCard({ provider, onKeyChange }) {
|
|
|
320
495
|
<label className="text-2xs font-semibold text-text-2 font-sans mb-1.5 block">
|
|
321
496
|
{provider.hasKey ? 'Update API Key' : `${provider.name} API Key`}
|
|
322
497
|
</label>
|
|
498
|
+
{!provider.hasKey && provider.id === 'claude-code' && (
|
|
499
|
+
<p className="text-2xs text-text-3 font-sans mb-1.5">
|
|
500
|
+
Get yours at <button onClick={() => window.open('https://console.anthropic.com/settings/keys', '_blank')} className="text-accent hover:underline cursor-pointer font-sans">console.anthropic.com</button>
|
|
501
|
+
</p>
|
|
502
|
+
)}
|
|
503
|
+
{!provider.hasKey && provider.id === 'codex' && (
|
|
504
|
+
<p className="text-2xs text-text-3 font-sans mb-1.5">
|
|
505
|
+
Get yours at <button onClick={() => window.open('https://platform.openai.com/api-keys', '_blank')} className="text-accent hover:underline cursor-pointer font-sans">platform.openai.com</button>
|
|
506
|
+
</p>
|
|
507
|
+
)}
|
|
508
|
+
{!provider.hasKey && provider.id === 'gemini' && (
|
|
509
|
+
<p className="text-2xs text-text-3 font-sans mb-1.5">
|
|
510
|
+
Get yours at <button onClick={() => window.open('https://aistudio.google.com/apikey', '_blank')} className="text-accent hover:underline cursor-pointer font-sans">aistudio.google.com</button>
|
|
511
|
+
</p>
|
|
512
|
+
)}
|
|
513
|
+
{!provider.hasKey && provider.id === 'grok' && (
|
|
514
|
+
<p className="text-2xs text-text-3 font-sans mb-1.5">
|
|
515
|
+
Get yours at <button onClick={() => window.open('https://console.x.ai', '_blank')} className="text-accent hover:underline cursor-pointer font-sans">console.x.ai</button>
|
|
516
|
+
</p>
|
|
517
|
+
)}
|
|
323
518
|
<div className="relative">
|
|
324
519
|
<input
|
|
325
520
|
value={keyInput}
|
|
@@ -1375,85 +1570,45 @@ export default function SettingsView() {
|
|
|
1375
1570
|
</div>
|
|
1376
1571
|
</ConfigCard>
|
|
1377
1572
|
|
|
1378
|
-
<ConfigCard icon={
|
|
1379
|
-
<div className="
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
</ConfigCard>
|
|
1418
|
-
|
|
1419
|
-
<ConfigCard icon={Users} label="Max Agents" description="Concurrent agent limit. 0 = unlimited.">
|
|
1420
|
-
<div className="flex bg-surface-0 rounded-md p-0.5 border border-border-subtle">
|
|
1421
|
-
{[0, 4, 8, 12, 20].map((n) => {
|
|
1422
|
-
const isActive = (config.maxAgents || 0) === n;
|
|
1423
|
-
return (
|
|
1424
|
-
<button
|
|
1425
|
-
key={n}
|
|
1426
|
-
onClick={() => updateConfig('maxAgents', n)}
|
|
1427
|
-
className={cn(
|
|
1428
|
-
'flex-1 px-2 py-1.5 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
|
|
1429
|
-
isActive ? 'bg-accent/15 text-accent shadow-sm' : 'text-text-3 hover:text-text-1',
|
|
1430
|
-
)}
|
|
1431
|
-
>
|
|
1432
|
-
{n === 0 ? '\u221E' : n}
|
|
1433
|
-
</button>
|
|
1434
|
-
);
|
|
1435
|
-
})}
|
|
1436
|
-
</div>
|
|
1437
|
-
</ConfigCard>
|
|
1438
|
-
|
|
1439
|
-
<ConfigCard icon={Newspaper} label="Journalist Interval" description="Seconds between synthesis cycles.">
|
|
1440
|
-
<div className="flex bg-surface-0 rounded-md p-0.5 border border-border-subtle">
|
|
1441
|
-
{[60, 120, 300, 600].map((n) => {
|
|
1442
|
-
const isActive = (config.journalistInterval || 120) === n;
|
|
1443
|
-
const label = n < 60 ? `${n}s` : `${n / 60}m`;
|
|
1444
|
-
return (
|
|
1445
|
-
<button
|
|
1446
|
-
key={n}
|
|
1447
|
-
onClick={() => updateConfig('journalistInterval', n)}
|
|
1448
|
-
className={cn(
|
|
1449
|
-
'flex-1 px-2 py-1.5 text-2xs font-semibold font-sans rounded transition-all cursor-pointer',
|
|
1450
|
-
isActive ? 'bg-accent/15 text-accent shadow-sm' : 'text-text-3 hover:text-text-1',
|
|
1451
|
-
)}
|
|
1452
|
-
>
|
|
1453
|
-
{label}
|
|
1454
|
-
</button>
|
|
1455
|
-
);
|
|
1456
|
-
})}
|
|
1573
|
+
<ConfigCard icon={MessageSquare} label="Default Chat Model" description="Provider and model for new chat conversations.">
|
|
1574
|
+
<div className="space-y-2">
|
|
1575
|
+
<select
|
|
1576
|
+
value={config.defaultChatProvider || config.defaultProvider || 'claude-code'}
|
|
1577
|
+
onChange={(e) => {
|
|
1578
|
+
updateConfig('defaultChatProvider', e.target.value);
|
|
1579
|
+
const prov = providers.find((p) => p.id === e.target.value);
|
|
1580
|
+
const chatModels = (prov?.models || []).filter((m) => {
|
|
1581
|
+
const id = (typeof m === 'string' ? m : m.id || '').toLowerCase();
|
|
1582
|
+
return !id.includes('dall-e') && !id.includes('imagen') && !id.includes('image');
|
|
1583
|
+
});
|
|
1584
|
+
if (chatModels.length > 0) {
|
|
1585
|
+
const first = typeof chatModels[0] === 'string' ? chatModels[0] : chatModels[0].id;
|
|
1586
|
+
updateConfig('defaultChatModel', first);
|
|
1587
|
+
}
|
|
1588
|
+
}}
|
|
1589
|
+
className="w-full h-8 px-2.5 text-xs bg-surface-0 border border-border-subtle rounded-md text-text-0 font-mono focus:outline-none focus:ring-1 focus:ring-accent cursor-pointer"
|
|
1590
|
+
>
|
|
1591
|
+
{visibleProviders.filter((p) => p.installed && (p.authType === 'local' || p.authType === 'subscription' || p.hasKey)).map((p) => (
|
|
1592
|
+
<option key={p.id} value={p.id}>{p.name}</option>
|
|
1593
|
+
))}
|
|
1594
|
+
</select>
|
|
1595
|
+
<select
|
|
1596
|
+
value={config.defaultChatModel || ''}
|
|
1597
|
+
onChange={(e) => updateConfig('defaultChatModel', e.target.value || null)}
|
|
1598
|
+
className="w-full h-8 px-2.5 text-xs bg-surface-0 border border-border-subtle rounded-md text-text-0 font-mono focus:outline-none focus:ring-1 focus:ring-accent cursor-pointer"
|
|
1599
|
+
>
|
|
1600
|
+
<option value="">Auto (Sonnet)</option>
|
|
1601
|
+
{(providers.find((p) => p.id === (config.defaultChatProvider || config.defaultProvider || 'claude-code'))?.models || [])
|
|
1602
|
+
.filter((m) => {
|
|
1603
|
+
const id = (typeof m === 'string' ? m : m.id || '').toLowerCase();
|
|
1604
|
+
return !id.includes('dall-e') && !id.includes('imagen') && !id.includes('image');
|
|
1605
|
+
})
|
|
1606
|
+
.map((m) => {
|
|
1607
|
+
const id = typeof m === 'string' ? m : m.id;
|
|
1608
|
+
const name = typeof m === 'string' ? m : m.name || m.id;
|
|
1609
|
+
return <option key={id} value={id}>{name}</option>;
|
|
1610
|
+
})}
|
|
1611
|
+
</select>
|
|
1457
1612
|
</div>
|
|
1458
1613
|
</ConfigCard>
|
|
1459
1614
|
|