@geminilight/mindos 0.5.8 → 0.5.10
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/README.md +9 -10
- package/README_zh.md +8 -9
- package/app/app/api/mcp/agents/route.ts +7 -0
- package/app/app/api/mcp/install-skill/route.ts +6 -0
- package/app/app/api/setup/check-port/route.ts +27 -3
- package/app/app/api/setup/route.ts +2 -9
- package/app/app/api/skills/route.ts +1 -1
- package/app/app/globals.css +28 -4
- package/app/app/login/page.tsx +2 -2
- package/app/app/view/[...path]/ViewPageClient.tsx +15 -10
- package/app/app/view/[...path]/not-found.tsx +1 -1
- package/app/components/AskModal.tsx +5 -5
- package/app/components/Breadcrumb.tsx +2 -2
- package/app/components/DirView.tsx +6 -6
- package/app/components/FileTree.tsx +7 -7
- package/app/components/HomeContent.tsx +8 -8
- package/app/components/OnboardingView.tsx +1 -1
- package/app/components/SearchModal.tsx +1 -1
- package/app/components/SettingsModal.tsx +2 -2
- package/app/components/SetupWizard.tsx +1 -1258
- package/app/components/Sidebar.tsx +4 -4
- package/app/components/SidebarLayout.tsx +9 -0
- package/app/components/SyncStatusBar.tsx +6 -6
- package/app/components/TableOfContents.tsx +1 -1
- package/app/components/UpdateBanner.tsx +1 -1
- package/app/components/ask/FileChip.tsx +1 -1
- package/app/components/ask/MentionPopover.tsx +4 -4
- package/app/components/ask/MessageList.tsx +3 -3
- package/app/components/ask/SessionHistory.tsx +3 -3
- package/app/components/renderers/agent-inspector/AgentInspectorRenderer.tsx +5 -5
- package/app/components/renderers/config/ConfigRenderer.tsx +4 -4
- package/app/components/renderers/csv/BoardView.tsx +2 -2
- package/app/components/renderers/csv/ConfigPanel.tsx +5 -5
- package/app/components/renderers/csv/GalleryView.tsx +1 -1
- package/app/components/renderers/csv/types.ts +1 -1
- package/app/components/renderers/diff/DiffRenderer.tsx +9 -9
- package/app/components/renderers/graph/GraphRenderer.tsx +1 -1
- package/app/components/renderers/summary/SummaryRenderer.tsx +1 -1
- package/app/components/renderers/timeline/TimelineRenderer.tsx +1 -1
- package/app/components/renderers/workflow/WorkflowRenderer.tsx +4 -4
- package/app/components/settings/KnowledgeTab.tsx +1 -1
- package/app/components/settings/McpTab.tsx +93 -47
- package/app/components/settings/PluginsTab.tsx +4 -4
- package/app/components/settings/Primitives.tsx +4 -4
- package/app/components/settings/SyncTab.tsx +13 -13
- package/app/components/setup/StepAI.tsx +67 -0
- package/app/components/setup/StepAgents.tsx +237 -0
- package/app/components/setup/StepDots.tsx +39 -0
- package/app/components/setup/StepKB.tsx +237 -0
- package/app/components/setup/StepPorts.tsx +121 -0
- package/app/components/setup/StepReview.tsx +211 -0
- package/app/components/setup/StepSecurity.tsx +78 -0
- package/app/components/setup/constants.tsx +13 -0
- package/app/components/setup/index.tsx +464 -0
- package/app/components/setup/types.ts +53 -0
- package/app/lib/i18n.ts +52 -8
- package/app/lib/mcp-agents.ts +81 -0
- package/bin/lib/gateway.js +44 -4
- package/bin/lib/mcp-agents.js +81 -0
- package/bin/lib/mcp-install.js +34 -4
- package/package.json +3 -1
- package/scripts/setup.js +43 -6
- package/skills/project-wiki/SKILL.md +92 -63
- package/app/public/landing/index.html +0 -353
- package/app/public/landing/style.css +0 -216
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
Plug, CheckCircle2, AlertCircle, Loader2, Copy, Check,
|
|
6
6
|
ChevronDown, ChevronRight, Trash2, Plus, X,
|
|
7
7
|
} from 'lucide-react';
|
|
8
|
-
import { SectionLabel } from './Primitives';
|
|
9
8
|
import { apiFetch } from '@/lib/api';
|
|
10
9
|
|
|
11
10
|
/* ── Types ─────────────────────────────────────────────────────── */
|
|
@@ -95,8 +94,8 @@ function ServerStatus({ status, t }: { status: McpStatus | null; t: any }) {
|
|
|
95
94
|
<div className="space-y-1.5 text-sm pl-11">
|
|
96
95
|
<div className="flex items-center gap-2">
|
|
97
96
|
<span className="text-muted-foreground w-20 shrink-0 text-xs">{m?.status ?? 'Status'}</span>
|
|
98
|
-
<span className={`text-xs flex items-center gap-1 ${status.running ? 'text-
|
|
99
|
-
<span className={`inline-block w-1.5 h-1.5 rounded-full ${status.running ? 'bg-
|
|
97
|
+
<span className={`text-xs flex items-center gap-1 ${status.running ? 'text-success' : 'text-muted-foreground'}`}>
|
|
98
|
+
<span className={`inline-block w-1.5 h-1.5 rounded-full ${status.running ? 'bg-success' : 'bg-muted-foreground'}`} />
|
|
100
99
|
{status.running ? (m?.running ?? 'Running') : (m?.stopped ?? 'Stopped')}
|
|
101
100
|
</span>
|
|
102
101
|
</div>
|
|
@@ -116,7 +115,7 @@ function ServerStatus({ status, t }: { status: McpStatus | null; t: any }) {
|
|
|
116
115
|
<span className="text-muted-foreground w-20 shrink-0 text-xs">{m?.auth ?? 'Auth'}</span>
|
|
117
116
|
<span className="text-xs">
|
|
118
117
|
{status.authConfigured
|
|
119
|
-
? <span className="text-
|
|
118
|
+
? <span className="text-success">{m?.authSet ?? 'Token set'}</span>
|
|
120
119
|
: <span className="text-muted-foreground">{m?.authNotSet ?? 'No token'}</span>}
|
|
121
120
|
</span>
|
|
122
121
|
</div>
|
|
@@ -206,9 +205,7 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
|
|
|
206
205
|
}));
|
|
207
206
|
|
|
208
207
|
return (
|
|
209
|
-
<div className="space-y-3">
|
|
210
|
-
<SectionLabel>{m?.agentsTitle ?? 'Agent Configuration'}</SectionLabel>
|
|
211
|
-
|
|
208
|
+
<div className="space-y-3 pt-2">
|
|
212
209
|
{/* Agent list */}
|
|
213
210
|
<div className="space-y-1">
|
|
214
211
|
{agents.map(agent => (
|
|
@@ -217,22 +214,23 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
|
|
|
217
214
|
type="checkbox"
|
|
218
215
|
checked={selected.has(agent.key)}
|
|
219
216
|
onChange={() => toggle(agent.key)}
|
|
220
|
-
className="rounded border-border
|
|
217
|
+
className="rounded border-border"
|
|
218
|
+
style={{ accentColor: 'var(--amber)' }}
|
|
221
219
|
/>
|
|
222
220
|
<span className="w-28 shrink-0 text-xs">{agent.name}</span>
|
|
223
|
-
<span className="text-
|
|
221
|
+
<span className="text-2xs px-1.5 py-0.5 rounded font-mono"
|
|
224
222
|
style={{ background: 'rgba(100,100,120,0.08)' }}>
|
|
225
223
|
{getEffectiveTransport(agent)}
|
|
226
224
|
</span>
|
|
227
225
|
{agent.installed ? (
|
|
228
226
|
<>
|
|
229
|
-
<span className="text-
|
|
227
|
+
<span className="text-2xs px-1.5 py-0.5 rounded bg-success/15 text-success font-mono">
|
|
230
228
|
{agent.transport}
|
|
231
229
|
</span>
|
|
232
|
-
<span className="text-
|
|
230
|
+
<span className="text-2xs text-muted-foreground">{agent.scope}</span>
|
|
233
231
|
</>
|
|
234
232
|
) : (
|
|
235
|
-
<span className="text-
|
|
233
|
+
<span className="text-2xs text-muted-foreground">
|
|
236
234
|
{agent.present ? (m?.detected ?? 'Detected') : (m?.notFound ?? 'Not found')}
|
|
237
235
|
</span>
|
|
238
236
|
)}
|
|
@@ -241,7 +239,7 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
|
|
|
241
239
|
<select
|
|
242
240
|
value={scopes[agent.key] || 'project'}
|
|
243
241
|
onChange={e => setScopes({ ...scopes, [agent.key]: e.target.value as 'project' | 'global' })}
|
|
244
|
-
className="ml-auto text-
|
|
242
|
+
className="ml-auto text-2xs px-1.5 py-0.5 rounded border border-border bg-background text-foreground"
|
|
245
243
|
>
|
|
246
244
|
<option value="project">{m?.project ?? 'Project'}</option>
|
|
247
245
|
<option value="global">{m?.global ?? 'Global'}</option>
|
|
@@ -251,6 +249,23 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
|
|
|
251
249
|
))}
|
|
252
250
|
</div>
|
|
253
251
|
|
|
252
|
+
{/* Select detected / Clear buttons */}
|
|
253
|
+
<div className="flex gap-2 text-xs pt-1">
|
|
254
|
+
<button type="button"
|
|
255
|
+
onClick={() => setSelected(new Set(
|
|
256
|
+
agents.filter(a => !a.installed && a.present).map(a => a.key)
|
|
257
|
+
))}
|
|
258
|
+
className="px-2.5 py-1 rounded-md border transition-colors hover:bg-muted/50"
|
|
259
|
+
style={{ borderColor: 'var(--amber)', color: 'var(--amber)' }}>
|
|
260
|
+
{m?.selectDetected ?? 'Select Detected'}
|
|
261
|
+
</button>
|
|
262
|
+
<button type="button"
|
|
263
|
+
onClick={() => setSelected(new Set())}
|
|
264
|
+
className="px-2.5 py-1 rounded-md border border-border text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground">
|
|
265
|
+
{m?.clearSelection ?? 'Clear'}
|
|
266
|
+
</button>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
254
269
|
{/* Transport selector */}
|
|
255
270
|
<div className="flex items-center gap-4 text-xs pt-1">
|
|
256
271
|
<label className="flex items-center gap-1.5 cursor-pointer">
|
|
@@ -259,7 +274,8 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
|
|
|
259
274
|
name="transport"
|
|
260
275
|
checked={transport === 'auto'}
|
|
261
276
|
onChange={() => setTransport('auto')}
|
|
262
|
-
className="
|
|
277
|
+
className=""
|
|
278
|
+
style={{ accentColor: 'var(--amber)' }}
|
|
263
279
|
/>
|
|
264
280
|
{m?.transportAuto ?? 'auto (recommended)'}
|
|
265
281
|
</label>
|
|
@@ -269,7 +285,8 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
|
|
|
269
285
|
name="transport"
|
|
270
286
|
checked={transport === 'stdio'}
|
|
271
287
|
onChange={() => setTransport('stdio')}
|
|
272
|
-
className="
|
|
288
|
+
className=""
|
|
289
|
+
style={{ accentColor: 'var(--amber)' }}
|
|
273
290
|
/>
|
|
274
291
|
{m?.transportStdio ?? 'stdio'}
|
|
275
292
|
</label>
|
|
@@ -279,7 +296,8 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
|
|
|
279
296
|
name="transport"
|
|
280
297
|
checked={transport === 'http'}
|
|
281
298
|
onChange={() => setTransport('http')}
|
|
282
|
-
className="
|
|
299
|
+
className=""
|
|
300
|
+
style={{ accentColor: 'var(--amber)' }}
|
|
283
301
|
/>
|
|
284
302
|
{m?.transportHttp ?? 'http'}
|
|
285
303
|
</label>
|
|
@@ -294,7 +312,7 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
|
|
|
294
312
|
type="text"
|
|
295
313
|
value={httpUrl}
|
|
296
314
|
onChange={e => setHttpUrl(e.target.value)}
|
|
297
|
-
className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background font-mono text-foreground outline-none focus:ring-1 focus:ring-ring"
|
|
315
|
+
className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background font-mono text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
298
316
|
/>
|
|
299
317
|
</div>
|
|
300
318
|
<div className="space-y-1">
|
|
@@ -304,7 +322,7 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
|
|
|
304
322
|
value={httpToken}
|
|
305
323
|
onChange={e => setHttpToken(e.target.value)}
|
|
306
324
|
placeholder="Bearer token"
|
|
307
|
-
className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background font-mono text-foreground outline-none focus:ring-1 focus:ring-ring"
|
|
325
|
+
className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background font-mono text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
308
326
|
/>
|
|
309
327
|
</div>
|
|
310
328
|
</div>
|
|
@@ -315,7 +333,7 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
|
|
|
315
333
|
onClick={handleInstall}
|
|
316
334
|
disabled={selected.size === 0 || installing}
|
|
317
335
|
className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
|
318
|
-
style={{ background: 'var(--amber)', color: '
|
|
336
|
+
style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}
|
|
319
337
|
>
|
|
320
338
|
{installing && <Loader2 size={12} className="animate-spin" />}
|
|
321
339
|
{installing ? (m?.installing ?? 'Installing...') : (m?.installSelected ?? 'Install Selected')}
|
|
@@ -325,7 +343,7 @@ function AgentInstall({ agents, t, onRefresh }: { agents: AgentInfo[]; t: any; o
|
|
|
325
343
|
{message && (
|
|
326
344
|
<div className="flex items-center gap-1.5 text-xs" role="status">
|
|
327
345
|
{message.type === 'success' ? (
|
|
328
|
-
<><CheckCircle2 size={12} className="text-
|
|
346
|
+
<><CheckCircle2 size={12} className="text-success" /><span className="text-success">{message.text}</span></>
|
|
329
347
|
) : (
|
|
330
348
|
<><AlertCircle size={12} className="text-destructive" /><span className="text-destructive">{message.text}</span></>
|
|
331
349
|
)}
|
|
@@ -414,9 +432,7 @@ function SkillsSection({ t }: { t: any }) {
|
|
|
414
432
|
}
|
|
415
433
|
|
|
416
434
|
return (
|
|
417
|
-
<div className="space-y-3">
|
|
418
|
-
<SectionLabel>{m?.skillsTitle ?? 'Skills'}</SectionLabel>
|
|
419
|
-
|
|
435
|
+
<div className="space-y-3 pt-2">
|
|
420
436
|
{/* Skill language switcher */}
|
|
421
437
|
{(() => {
|
|
422
438
|
const mindosEnabled = skills.find(s => s.name === 'mindos')?.enabled ?? true;
|
|
@@ -468,7 +484,7 @@ function SkillsSection({ t }: { t: any }) {
|
|
|
468
484
|
>
|
|
469
485
|
{expanded === skill.name ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
|
470
486
|
<span className="text-xs font-medium flex-1">{skill.name}</span>
|
|
471
|
-
<span className={`text-
|
|
487
|
+
<span className={`text-2xs px-1.5 py-0.5 rounded ${
|
|
472
488
|
skill.source === 'builtin' ? 'bg-blue-500/15 text-blue-500' : 'bg-purple-500/15 text-purple-500'
|
|
473
489
|
}`}>
|
|
474
490
|
{skill.source === 'builtin' ? (m?.skillBuiltin ?? 'Built-in') : (m?.skillUser ?? 'Custom')}
|
|
@@ -477,7 +493,7 @@ function SkillsSection({ t }: { t: any }) {
|
|
|
477
493
|
<button
|
|
478
494
|
onClick={e => { e.stopPropagation(); handleToggle(skill.name, !skill.enabled); }}
|
|
479
495
|
className={`relative inline-flex h-4 w-7 items-center rounded-full transition-colors ${
|
|
480
|
-
skill.enabled ? 'bg-
|
|
496
|
+
skill.enabled ? 'bg-success' : 'bg-muted-foreground/30'
|
|
481
497
|
}`}
|
|
482
498
|
>
|
|
483
499
|
<span className={`inline-block h-3 w-3 rounded-full bg-white transition-transform ${
|
|
@@ -489,11 +505,11 @@ function SkillsSection({ t }: { t: any }) {
|
|
|
489
505
|
{expanded === skill.name && (
|
|
490
506
|
<div className="px-3 py-2 border-t border-border text-xs space-y-1.5 bg-muted/20">
|
|
491
507
|
<p className="text-muted-foreground">{skill.description || 'No description'}</p>
|
|
492
|
-
<p className="text-muted-foreground font-mono text-
|
|
508
|
+
<p className="text-muted-foreground font-mono text-2xs">{skill.path}</p>
|
|
493
509
|
{skill.editable && (
|
|
494
510
|
<button
|
|
495
511
|
onClick={() => handleDelete(skill.name)}
|
|
496
|
-
className="flex items-center gap-1 text-
|
|
512
|
+
className="flex items-center gap-1 text-2xs text-destructive hover:underline"
|
|
497
513
|
>
|
|
498
514
|
<Trash2 size={10} />
|
|
499
515
|
{m?.deleteSkill ?? 'Delete'}
|
|
@@ -514,37 +530,37 @@ function SkillsSection({ t }: { t: any }) {
|
|
|
514
530
|
</button>
|
|
515
531
|
</div>
|
|
516
532
|
<div className="space-y-1">
|
|
517
|
-
<label className="text-
|
|
533
|
+
<label className="text-2xs text-muted-foreground">{m?.skillName ?? 'Name'}</label>
|
|
518
534
|
<input
|
|
519
535
|
type="text"
|
|
520
536
|
value={newName}
|
|
521
537
|
onChange={e => setNewName(e.target.value.replace(/[^a-z0-9-]/g, ''))}
|
|
522
538
|
placeholder="my-skill"
|
|
523
|
-
className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background font-mono text-foreground outline-none focus:ring-1 focus:ring-ring"
|
|
539
|
+
className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background font-mono text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
524
540
|
/>
|
|
525
541
|
</div>
|
|
526
542
|
<div className="space-y-1">
|
|
527
|
-
<label className="text-
|
|
543
|
+
<label className="text-2xs text-muted-foreground">{m?.skillDesc ?? 'Description'}</label>
|
|
528
544
|
<input
|
|
529
545
|
type="text"
|
|
530
546
|
value={newDesc}
|
|
531
547
|
onChange={e => setNewDesc(e.target.value)}
|
|
532
548
|
placeholder="What does this skill do?"
|
|
533
|
-
className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background text-foreground outline-none focus:ring-1 focus:ring-ring"
|
|
549
|
+
className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
534
550
|
/>
|
|
535
551
|
</div>
|
|
536
552
|
<div className="space-y-1">
|
|
537
|
-
<label className="text-
|
|
553
|
+
<label className="text-2xs text-muted-foreground">{m?.skillContent ?? 'Content'}</label>
|
|
538
554
|
<textarea
|
|
539
555
|
value={newContent}
|
|
540
556
|
onChange={e => setNewContent(e.target.value)}
|
|
541
557
|
rows={6}
|
|
542
558
|
placeholder="Skill instructions (markdown)..."
|
|
543
|
-
className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background text-foreground outline-none focus:ring-1 focus:ring-ring resize-y font-mono"
|
|
559
|
+
className="w-full px-2.5 py-1.5 text-xs rounded-md border border-border bg-background text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring resize-y font-mono"
|
|
544
560
|
/>
|
|
545
561
|
</div>
|
|
546
562
|
{error && (
|
|
547
|
-
<p className="text-
|
|
563
|
+
<p className="text-2xs text-destructive flex items-center gap-1">
|
|
548
564
|
<AlertCircle size={10} />
|
|
549
565
|
{error}
|
|
550
566
|
</p>
|
|
@@ -554,7 +570,7 @@ function SkillsSection({ t }: { t: any }) {
|
|
|
554
570
|
onClick={handleCreate}
|
|
555
571
|
disabled={!newName.trim() || saving}
|
|
556
572
|
className="flex items-center gap-1 px-2.5 py-1 text-xs rounded-md disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
557
|
-
style={{ background: 'var(--amber)', color: '
|
|
573
|
+
style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}
|
|
558
574
|
>
|
|
559
575
|
{saving && <Loader2 size={10} className="animate-spin" />}
|
|
560
576
|
{m?.saveSkill ?? 'Save'}
|
|
@@ -586,6 +602,8 @@ export function McpTab({ t }: McpTabProps) {
|
|
|
586
602
|
const [mcpStatus, setMcpStatus] = useState<McpStatus | null>(null);
|
|
587
603
|
const [agents, setAgents] = useState<AgentInfo[]>([]);
|
|
588
604
|
const [loading, setLoading] = useState(true);
|
|
605
|
+
const [showAgents, setShowAgents] = useState(false);
|
|
606
|
+
const [showSkills, setShowSkills] = useState(false);
|
|
589
607
|
|
|
590
608
|
const fetchAll = useCallback(async () => {
|
|
591
609
|
try {
|
|
@@ -609,22 +627,50 @@ export function McpTab({ t }: McpTabProps) {
|
|
|
609
627
|
);
|
|
610
628
|
}
|
|
611
629
|
|
|
630
|
+
const m = t.settings?.mcp;
|
|
631
|
+
|
|
612
632
|
return (
|
|
613
633
|
<div className="space-y-6">
|
|
614
|
-
{/* MCP Server Status */}
|
|
615
|
-
<
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
<div className="border-t border-border" />
|
|
619
|
-
|
|
620
|
-
{/* Agent Install */}
|
|
621
|
-
<AgentInstall agents={agents} t={t} onRefresh={fetchAll} />
|
|
634
|
+
{/* MCP Server Status — prominent card */}
|
|
635
|
+
<div className="rounded-xl border p-4" style={{ borderColor: 'var(--border)', background: 'var(--card)' }}>
|
|
636
|
+
<ServerStatus status={mcpStatus} t={t} />
|
|
637
|
+
</div>
|
|
622
638
|
|
|
623
|
-
{/*
|
|
624
|
-
<div className="
|
|
639
|
+
{/* Agent Install — collapsible */}
|
|
640
|
+
<div className="rounded-xl border overflow-hidden" style={{ borderColor: 'var(--border)' }}>
|
|
641
|
+
<button
|
|
642
|
+
type="button"
|
|
643
|
+
onClick={() => setShowAgents(!showAgents)}
|
|
644
|
+
className="w-full flex items-center justify-between px-4 py-3 text-sm font-medium hover:bg-muted/50 transition-colors"
|
|
645
|
+
style={{ color: 'var(--foreground)' }}
|
|
646
|
+
>
|
|
647
|
+
<span>{m?.agentsTitle ?? 'Agent Configuration'}</span>
|
|
648
|
+
<ChevronDown size={14} className={`transition-transform text-muted-foreground ${showAgents ? 'rotate-180' : ''}`} />
|
|
649
|
+
</button>
|
|
650
|
+
{showAgents && (
|
|
651
|
+
<div className="px-4 pb-4 border-t" style={{ borderColor: 'var(--border)' }}>
|
|
652
|
+
<AgentInstall agents={agents} t={t} onRefresh={fetchAll} />
|
|
653
|
+
</div>
|
|
654
|
+
)}
|
|
655
|
+
</div>
|
|
625
656
|
|
|
626
|
-
{/* Skills */}
|
|
627
|
-
<
|
|
657
|
+
{/* Skills — collapsible */}
|
|
658
|
+
<div className="rounded-xl border overflow-hidden" style={{ borderColor: 'var(--border)' }}>
|
|
659
|
+
<button
|
|
660
|
+
type="button"
|
|
661
|
+
onClick={() => setShowSkills(!showSkills)}
|
|
662
|
+
className="w-full flex items-center justify-between px-4 py-3 text-sm font-medium hover:bg-muted/50 transition-colors"
|
|
663
|
+
style={{ color: 'var(--foreground)' }}
|
|
664
|
+
>
|
|
665
|
+
<span>{m?.skillsTitle ?? 'Skills'}</span>
|
|
666
|
+
<ChevronDown size={14} className={`transition-transform text-muted-foreground ${showSkills ? 'rotate-180' : ''}`} />
|
|
667
|
+
</button>
|
|
668
|
+
{showSkills && (
|
|
669
|
+
<div className="px-4 pb-4 border-t" style={{ borderColor: 'var(--border)' }}>
|
|
670
|
+
<SkillsSection t={t} />
|
|
671
|
+
</div>
|
|
672
|
+
)}
|
|
673
|
+
</div>
|
|
628
674
|
</div>
|
|
629
675
|
);
|
|
630
676
|
}
|
|
@@ -33,25 +33,25 @@ export function PluginsTab({ pluginStates, setPluginStates, t }: PluginsTabProps
|
|
|
33
33
|
<div className="flex items-center gap-2 flex-wrap">
|
|
34
34
|
<span className="text-sm font-medium text-foreground">{renderer.name}</span>
|
|
35
35
|
{isCore && (
|
|
36
|
-
<span className="text-
|
|
36
|
+
<span className="text-2xs px-1.5 py-0.5 rounded bg-amber-600/15 text-amber-600 font-mono">
|
|
37
37
|
core
|
|
38
38
|
</span>
|
|
39
39
|
)}
|
|
40
40
|
{renderer.builtin && !isCore && (
|
|
41
|
-
<span className="text-
|
|
41
|
+
<span className="text-2xs px-1.5 py-0.5 rounded bg-muted text-muted-foreground font-mono">
|
|
42
42
|
{t.settings.plugins.builtinBadge}
|
|
43
43
|
</span>
|
|
44
44
|
)}
|
|
45
45
|
<div className="flex gap-1 flex-wrap">
|
|
46
46
|
{renderer.tags.map(tag => (
|
|
47
|
-
<span key={tag} className="text-
|
|
47
|
+
<span key={tag} className="text-2xs px-1.5 py-0.5 rounded bg-muted/60 text-muted-foreground">
|
|
48
48
|
{tag}
|
|
49
49
|
</span>
|
|
50
50
|
))}
|
|
51
51
|
</div>
|
|
52
52
|
</div>
|
|
53
53
|
<p className="text-xs text-muted-foreground mt-1 leading-relaxed">{renderer.description}</p>
|
|
54
|
-
<p className="text-
|
|
54
|
+
<p className="text-xs text-muted-foreground/60 mt-1.5 font-mono">
|
|
55
55
|
{t.settings.plugins.matchHint}: <code className="bg-muted px-1 rounded">{renderer.match.toString().match(/\/(.+)\//)?.[1] ?? '—'}</code>
|
|
56
56
|
</p>
|
|
57
57
|
</div>
|
|
@@ -18,7 +18,7 @@ export function Input({ className = '', ...props }: React.InputHTMLAttributes<HT
|
|
|
18
18
|
return (
|
|
19
19
|
<input
|
|
20
20
|
{...props}
|
|
21
|
-
className={`w-full px-3 py-2 text-sm bg-background border border-border rounded-lg text-foreground placeholder:text-muted-foreground outline-none focus:ring-1 focus:ring-ring disabled:opacity-50 ${className}`}
|
|
21
|
+
className={`w-full px-3 py-2 text-sm bg-background border border-border rounded-lg text-foreground placeholder:text-muted-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-50 ${className}`}
|
|
22
22
|
/>
|
|
23
23
|
);
|
|
24
24
|
}
|
|
@@ -27,7 +27,7 @@ export function Select({ className = '', ...props }: React.SelectHTMLAttributes<
|
|
|
27
27
|
return (
|
|
28
28
|
<select
|
|
29
29
|
{...props}
|
|
30
|
-
className={`w-full px-3 py-2 text-sm bg-background border border-border rounded-lg text-foreground outline-none focus:ring-1 focus:ring-ring disabled:opacity-50 ${className}`}
|
|
30
|
+
className={`w-full px-3 py-2 text-sm bg-background border border-border rounded-lg text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-50 ${className}`}
|
|
31
31
|
/>
|
|
32
32
|
);
|
|
33
33
|
}
|
|
@@ -35,7 +35,7 @@ export function Select({ className = '', ...props }: React.SelectHTMLAttributes<
|
|
|
35
35
|
export function EnvBadge({ overridden }: { overridden: boolean }) {
|
|
36
36
|
if (!overridden) return null;
|
|
37
37
|
return (
|
|
38
|
-
<span className="text-
|
|
38
|
+
<span className="text-2xs px-1.5 py-0.5 rounded bg-amber-500/15 text-amber-500 font-mono ml-1.5">env</span>
|
|
39
39
|
);
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -53,7 +53,7 @@ export function ApiKeyInput({ value, onChange, placeholder, disabled }: {
|
|
|
53
53
|
onChange={e => onChange(e.target.value)}
|
|
54
54
|
placeholder={placeholder ?? 'sk-...'}
|
|
55
55
|
disabled={disabled}
|
|
56
|
-
className="w-full px-3 py-2 text-sm bg-background border border-border rounded-lg text-foreground placeholder:text-muted-foreground outline-none focus:ring-1 focus:ring-ring disabled:opacity-50"
|
|
56
|
+
className="w-full px-3 py-2 text-sm bg-background border border-border rounded-lg text-foreground placeholder:text-muted-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-50"
|
|
57
57
|
onFocus={() => { if (isMasked) onChange(''); }}
|
|
58
58
|
/>
|
|
59
59
|
);
|
|
@@ -104,19 +104,19 @@ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => v
|
|
|
104
104
|
value={remoteUrl}
|
|
105
105
|
onChange={e => { setRemoteUrl(e.target.value); setError(''); }}
|
|
106
106
|
placeholder="https://github.com/user/my-mind.git"
|
|
107
|
-
className="w-full px-3 py-2 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus:outline-none focus:ring-1"
|
|
107
|
+
className="w-full px-3 py-2 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
108
108
|
style={{
|
|
109
109
|
borderColor: remoteUrl.trim() && !isValid ? 'var(--destructive, red)' : 'var(--border)',
|
|
110
110
|
color: 'var(--foreground)',
|
|
111
111
|
}}
|
|
112
112
|
/>
|
|
113
113
|
{remoteUrl.trim() && !isValid && (
|
|
114
|
-
<p className="text-
|
|
114
|
+
<p className="text-xs" style={{ color: 'var(--destructive, red)' }}>
|
|
115
115
|
{syncT?.invalidUrl ?? 'Invalid Git URL — use HTTPS (https://...) or SSH (git@...)'}
|
|
116
116
|
</p>
|
|
117
117
|
)}
|
|
118
118
|
{urlType === 'ssh' && (
|
|
119
|
-
<p className="text-
|
|
119
|
+
<p className="text-xs text-muted-foreground flex items-center gap-1">
|
|
120
120
|
<AlertCircle size={11} className="shrink-0" />
|
|
121
121
|
{syncT?.sshHint ?? 'SSH URLs require SSH key configured on this machine. HTTPS with token recommended.'}
|
|
122
122
|
</p>
|
|
@@ -136,7 +136,7 @@ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => v
|
|
|
136
136
|
value={token}
|
|
137
137
|
onChange={e => setToken(e.target.value)}
|
|
138
138
|
placeholder="ghp_xxxxxxxxxxxx"
|
|
139
|
-
className="w-full px-3 py-2 pr-9 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus:outline-none focus:ring-1"
|
|
139
|
+
className="w-full px-3 py-2 pr-9 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
140
140
|
style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}
|
|
141
141
|
/>
|
|
142
142
|
<button
|
|
@@ -147,7 +147,7 @@ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => v
|
|
|
147
147
|
{showToken ? <EyeOff size={14} /> : <Eye size={14} />}
|
|
148
148
|
</button>
|
|
149
149
|
</div>
|
|
150
|
-
<p className="text-
|
|
150
|
+
<p className="text-xs text-muted-foreground">
|
|
151
151
|
{syncT?.tokenHint ?? 'GitHub: Settings → Developer settings → Personal access tokens → repo scope'}
|
|
152
152
|
</p>
|
|
153
153
|
</div>
|
|
@@ -163,7 +163,7 @@ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => v
|
|
|
163
163
|
value={branch}
|
|
164
164
|
onChange={e => setBranch(e.target.value)}
|
|
165
165
|
placeholder="main"
|
|
166
|
-
className="w-full max-w-[200px] px-3 py-2 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus:outline-none focus:ring-1"
|
|
166
|
+
className="w-full max-w-[200px] px-3 py-2 text-sm rounded-lg border bg-transparent font-mono text-xs transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
167
167
|
style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}
|
|
168
168
|
/>
|
|
169
169
|
</div>
|
|
@@ -174,7 +174,7 @@ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => v
|
|
|
174
174
|
onClick={handleConnect}
|
|
175
175
|
disabled={!isValid || connecting}
|
|
176
176
|
className="flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
|
177
|
-
style={{ background: 'var(--amber)', color: '
|
|
177
|
+
style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}
|
|
178
178
|
>
|
|
179
179
|
{connecting && <Loader2 size={14} className="animate-spin" />}
|
|
180
180
|
{connecting
|
|
@@ -184,14 +184,14 @@ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => v
|
|
|
184
184
|
|
|
185
185
|
{/* Error */}
|
|
186
186
|
{error && (
|
|
187
|
-
<div className="flex items-start gap-2 text-xs p-3 rounded-lg" role="alert" aria-live="polite" style={{ background: 'rgba(
|
|
187
|
+
<div className="flex items-start gap-2 text-xs p-3 rounded-lg" role="alert" aria-live="polite" style={{ background: 'rgba(200,80,80,0.1)', color: 'var(--error)' }}>
|
|
188
188
|
<AlertCircle size={13} className="shrink-0 mt-0.5" />
|
|
189
189
|
<span>{error}</span>
|
|
190
190
|
</div>
|
|
191
191
|
)}
|
|
192
192
|
|
|
193
193
|
{/* Features */}
|
|
194
|
-
<div className="grid grid-cols-2 gap-2 text-
|
|
194
|
+
<div className="grid grid-cols-2 gap-2 text-xs text-muted-foreground pt-2">
|
|
195
195
|
{[
|
|
196
196
|
syncT?.featureAutoCommit ?? 'Auto-commit on save',
|
|
197
197
|
syncT?.featureAutoPull ?? 'Auto-pull from remote',
|
|
@@ -199,7 +199,7 @@ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => v
|
|
|
199
199
|
syncT?.featureMultiDevice ?? 'Works across devices',
|
|
200
200
|
].map((f, i) => (
|
|
201
201
|
<div key={i} className="flex items-center gap-1.5">
|
|
202
|
-
<CheckCircle2 size={11} className="text-
|
|
202
|
+
<CheckCircle2 size={11} className="text-success/60 shrink-0" />
|
|
203
203
|
<span>{f}</span>
|
|
204
204
|
</div>
|
|
205
205
|
))}
|
|
@@ -336,7 +336,7 @@ export function SyncTab({ t }: SyncTabProps) {
|
|
|
336
336
|
className={`flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg border transition-colors disabled:opacity-40 disabled:cursor-not-allowed ${
|
|
337
337
|
status.enabled
|
|
338
338
|
? 'border-border text-muted-foreground hover:text-destructive hover:border-destructive/50'
|
|
339
|
-
: 'border-
|
|
339
|
+
: 'border-success/30 text-success hover:bg-success/10'
|
|
340
340
|
}`}
|
|
341
341
|
>
|
|
342
342
|
{status.enabled ? 'Disable Auto-sync' : 'Enable Auto-sync'}
|
|
@@ -347,7 +347,7 @@ export function SyncTab({ t }: SyncTabProps) {
|
|
|
347
347
|
{message && (
|
|
348
348
|
<div className="flex items-center gap-1.5 text-xs" role="status" aria-live="polite">
|
|
349
349
|
{message.type === 'success' ? (
|
|
350
|
-
<><CheckCircle2 size={13} className="text-
|
|
350
|
+
<><CheckCircle2 size={13} className="text-success" /><span className="text-success">{message.text}</span></>
|
|
351
351
|
) : (
|
|
352
352
|
<><AlertCircle size={13} className="text-destructive" /><span className="text-destructive">{message.text}</span></>
|
|
353
353
|
)}
|
|
@@ -361,7 +361,7 @@ export function SyncTab({ t }: SyncTabProps) {
|
|
|
361
361
|
<div className="space-y-1.5">
|
|
362
362
|
{conflicts.map((c, i) => (
|
|
363
363
|
<div key={i} className="flex items-center gap-2 text-xs group">
|
|
364
|
-
<AlertCircle size={12} className="text-
|
|
364
|
+
<AlertCircle size={12} className="text-error shrink-0" />
|
|
365
365
|
<a
|
|
366
366
|
href={`/view/${encodeURIComponent(c.file)}`}
|
|
367
367
|
className="font-mono truncate hover:text-foreground hover:underline transition-colors"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Brain, Zap, SkipForward, CheckCircle2 } from 'lucide-react';
|
|
4
|
+
import { Field, Input, ApiKeyInput } from '@/components/settings/Primitives';
|
|
5
|
+
import type { SetupState, SetupMessages } from './types';
|
|
6
|
+
|
|
7
|
+
export interface StepAIProps {
|
|
8
|
+
state: SetupState;
|
|
9
|
+
update: <K extends keyof SetupState>(key: K, val: SetupState[K]) => void;
|
|
10
|
+
s: SetupMessages;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function StepAI({ state, update, s }: StepAIProps) {
|
|
14
|
+
const providers = [
|
|
15
|
+
{ id: 'anthropic' as const, icon: <Brain size={18} />, label: 'Anthropic', desc: 'Claude — claude-sonnet-4-6' },
|
|
16
|
+
{ id: 'openai' as const, icon: <Zap size={18} />, label: 'OpenAI', desc: 'GPT or any OpenAI-compatible API' },
|
|
17
|
+
{ id: 'skip' as const, icon: <SkipForward size={18} />, label: s.aiSkipTitle, desc: s.aiSkipDesc },
|
|
18
|
+
];
|
|
19
|
+
return (
|
|
20
|
+
<div className="space-y-5">
|
|
21
|
+
<div className="grid grid-cols-1 gap-3">
|
|
22
|
+
{providers.map(p => (
|
|
23
|
+
<button key={p.id} onClick={() => update('provider', p.id)}
|
|
24
|
+
className="flex items-start gap-3 p-4 rounded-xl border text-left transition-all duration-150"
|
|
25
|
+
style={{
|
|
26
|
+
background: state.provider === p.id ? 'var(--amber-dim)' : 'var(--card)',
|
|
27
|
+
borderColor: state.provider === p.id ? 'var(--amber)' : 'var(--border)',
|
|
28
|
+
}}>
|
|
29
|
+
<span className="mt-0.5" style={{ color: state.provider === p.id ? 'var(--amber)' : 'var(--muted-foreground)' }}>
|
|
30
|
+
{p.icon}
|
|
31
|
+
</span>
|
|
32
|
+
<div>
|
|
33
|
+
<p className="text-sm font-medium" style={{ color: 'var(--foreground)' }}>{p.label}</p>
|
|
34
|
+
<p className="text-xs mt-0.5" style={{ color: 'var(--muted-foreground)' }}>{p.desc}</p>
|
|
35
|
+
</div>
|
|
36
|
+
{state.provider === p.id && (
|
|
37
|
+
<CheckCircle2 size={16} className="ml-auto mt-0.5 shrink-0" style={{ color: 'var(--amber)' }} />
|
|
38
|
+
)}
|
|
39
|
+
</button>
|
|
40
|
+
))}
|
|
41
|
+
</div>
|
|
42
|
+
{state.provider !== 'skip' && (
|
|
43
|
+
<div className="space-y-4 pt-2">
|
|
44
|
+
<Field label={s.apiKey}>
|
|
45
|
+
<ApiKeyInput
|
|
46
|
+
value={state.provider === 'anthropic' ? state.anthropicKey : state.openaiKey}
|
|
47
|
+
onChange={v => update(state.provider === 'anthropic' ? 'anthropicKey' : 'openaiKey', v)}
|
|
48
|
+
placeholder={state.provider === 'anthropic' ? 'sk-ant-...' : 'sk-...'}
|
|
49
|
+
/>
|
|
50
|
+
</Field>
|
|
51
|
+
<Field label={s.model}>
|
|
52
|
+
<Input
|
|
53
|
+
value={state.provider === 'anthropic' ? state.anthropicModel : state.openaiModel}
|
|
54
|
+
onChange={e => update(state.provider === 'anthropic' ? 'anthropicModel' : 'openaiModel', e.target.value)}
|
|
55
|
+
/>
|
|
56
|
+
</Field>
|
|
57
|
+
{state.provider === 'openai' && (
|
|
58
|
+
<Field label={s.baseUrl} hint={s.baseUrlHint}>
|
|
59
|
+
<Input value={state.openaiBaseUrl} onChange={e => update('openaiBaseUrl', e.target.value)}
|
|
60
|
+
placeholder="https://api.openai.com/v1" />
|
|
61
|
+
</Field>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|