@geminilight/mindos 0.6.17 → 0.6.19
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/app/app/api/ask/route.ts +220 -2
- package/app/app/api/settings/list-models/route.ts +96 -0
- package/app/app/api/settings/test-key/route.ts +79 -35
- package/app/components/ActivityBar.tsx +8 -5
- package/app/components/JsonView.tsx +2 -5
- package/app/components/OrganizeToast.tsx +237 -82
- package/app/components/Panel.tsx +6 -6
- package/app/components/SidebarLayout.tsx +10 -0
- package/app/components/UpdateOverlay.tsx +6 -6
- package/app/components/agents/AgentDetailContent.tsx +2 -2
- package/app/components/agents/AgentsMcpSection.tsx +2 -2
- package/app/components/agents/AgentsOverviewSection.tsx +11 -11
- package/app/components/agents/AgentsPrimitives.tsx +14 -14
- package/app/components/agents/AgentsSkillsSection.tsx +1 -1
- package/app/components/agents/SkillDetailPopover.tsx +1 -1
- package/app/components/ask/MessageList.tsx +1 -1
- package/app/components/panels/EchoPanel.tsx +7 -7
- package/app/components/renderers/summary/SummaryRenderer.tsx +1 -1
- package/app/components/settings/AiTab.tsx +133 -9
- package/app/components/settings/types.ts +1 -0
- package/app/lib/i18n-en.ts +10 -1
- package/app/lib/i18n-zh.ts +10 -1
- package/app/lib/settings.ts +2 -0
- package/app/next-env.d.ts +1 -1
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
|
|
|
5
5
|
import {
|
|
6
6
|
Check, X, Loader2, Sparkles, AlertCircle, Undo2,
|
|
7
7
|
ChevronDown, FilePlus, FileEdit, ExternalLink,
|
|
8
|
+
Maximize2, Minimize2, FileIcon,
|
|
8
9
|
} from 'lucide-react';
|
|
9
10
|
import { useLocale } from '@/lib/LocaleContext';
|
|
10
11
|
import type { useAiOrganize } from '@/hooks/useAiOrganize';
|
|
@@ -84,6 +85,7 @@ export default function OrganizeToast({
|
|
|
84
85
|
const { elapsed, displayHint } = useOrganizeTimer(isOrganizing, aiOrganize.stageHint);
|
|
85
86
|
|
|
86
87
|
const [expanded, setExpanded] = useState(false);
|
|
88
|
+
const [maximized, setMaximized] = useState(false);
|
|
87
89
|
const [undoing, setUndoing] = useState(false);
|
|
88
90
|
const dismissTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
89
91
|
const historyIdRef = useRef<string | null>(null);
|
|
@@ -197,7 +199,211 @@ export default function OrganizeToast({
|
|
|
197
199
|
|
|
198
200
|
if (!isActive) return null;
|
|
199
201
|
|
|
200
|
-
//
|
|
202
|
+
// ── Shared file-change row renderer ──
|
|
203
|
+
function renderChangeRow(c: typeof aiOrganize.changes[number], idx: number) {
|
|
204
|
+
const wasUndone = c.undone;
|
|
205
|
+
const undoable = aiOrganize.canUndo(c.path);
|
|
206
|
+
const fileName = c.path.split('/').pop() ?? c.path;
|
|
207
|
+
return (
|
|
208
|
+
<div
|
|
209
|
+
key={`${c.path}-${idx}`}
|
|
210
|
+
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors ${wasUndone ? 'bg-muted/30 opacity-50' : 'bg-muted/50'}`}
|
|
211
|
+
>
|
|
212
|
+
{wasUndone ? (
|
|
213
|
+
<Undo2 size={14} className="text-muted-foreground shrink-0" />
|
|
214
|
+
) : c.action === 'create' ? (
|
|
215
|
+
<FilePlus size={14} className="text-success shrink-0" />
|
|
216
|
+
) : (
|
|
217
|
+
<FileEdit size={14} className="text-[var(--amber)] shrink-0" />
|
|
218
|
+
)}
|
|
219
|
+
<span className={`truncate flex-1 ${wasUndone ? 'line-through text-muted-foreground' : 'text-foreground'}`}>
|
|
220
|
+
{fileName}
|
|
221
|
+
</span>
|
|
222
|
+
{wasUndone ? (
|
|
223
|
+
<span className="text-xs text-muted-foreground shrink-0">{fi.organizeUndone as string}</span>
|
|
224
|
+
) : (
|
|
225
|
+
<span className={`text-xs shrink-0 ${c.ok ? 'text-muted-foreground' : 'text-error'}`}>
|
|
226
|
+
{!c.ok ? fi.organizeFailed as string
|
|
227
|
+
: c.action === 'create' ? fi.organizeCreated as string
|
|
228
|
+
: fi.organizeUpdated as string}
|
|
229
|
+
</span>
|
|
230
|
+
)}
|
|
231
|
+
{undoable && (
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
onClick={() => handleUndoOne(c.path)}
|
|
235
|
+
disabled={undoing}
|
|
236
|
+
className="text-2xs text-muted-foreground/60 hover:text-foreground transition-colors shrink-0 px-1 disabled:opacity-40"
|
|
237
|
+
title={fi.organizeUndoOne as string}
|
|
238
|
+
>
|
|
239
|
+
<Undo2 size={12} />
|
|
240
|
+
</button>
|
|
241
|
+
)}
|
|
242
|
+
{c.ok && !c.undone && (
|
|
243
|
+
<button
|
|
244
|
+
type="button"
|
|
245
|
+
onClick={() => handleViewFile(c.path)}
|
|
246
|
+
className="text-2xs text-muted-foreground/60 hover:text-[var(--amber)] transition-colors shrink-0 px-1"
|
|
247
|
+
title={fi.organizeViewFile as string}
|
|
248
|
+
>
|
|
249
|
+
<ExternalLink size={12} />
|
|
250
|
+
</button>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ── Shared footer actions ──
|
|
257
|
+
function renderActions() {
|
|
258
|
+
return (
|
|
259
|
+
<div className="flex items-center justify-end gap-3 px-4 py-3 border-t border-border">
|
|
260
|
+
{isDone && aiOrganize.hasAnyUndoable && (
|
|
261
|
+
<button
|
|
262
|
+
onClick={handleUndoAll}
|
|
263
|
+
disabled={undoing}
|
|
264
|
+
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors px-2 py-1.5 disabled:opacity-50"
|
|
265
|
+
>
|
|
266
|
+
{undoing ? <Loader2 size={12} className="animate-spin" /> : <Undo2 size={12} />}
|
|
267
|
+
{fi.organizeUndoAll as string}
|
|
268
|
+
</button>
|
|
269
|
+
)}
|
|
270
|
+
<button
|
|
271
|
+
onClick={handleDismiss}
|
|
272
|
+
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium bg-[var(--amber)] text-[var(--amber-foreground)] hover:opacity-90 transition-all"
|
|
273
|
+
>
|
|
274
|
+
<Check size={12} />
|
|
275
|
+
{fi.organizeDone as string}
|
|
276
|
+
</button>
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
282
|
+
// Maximized Modal — full detail view with source files, summary, changes
|
|
283
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
284
|
+
if (maximized) {
|
|
285
|
+
return (
|
|
286
|
+
<div
|
|
287
|
+
className="fixed inset-0 z-50 overlay-backdrop flex items-center justify-center p-4"
|
|
288
|
+
onClick={(e) => { if (e.target === e.currentTarget) setMaximized(false); }}
|
|
289
|
+
>
|
|
290
|
+
<div
|
|
291
|
+
className="w-full max-w-xl max-h-[80vh] flex flex-col bg-card rounded-xl shadow-xl border border-border animate-in fade-in-0 zoom-in-95 duration-200"
|
|
292
|
+
onClick={handleUserAction}
|
|
293
|
+
role="dialog"
|
|
294
|
+
aria-modal="true"
|
|
295
|
+
aria-label={fi.organizeDetailTitle as string}
|
|
296
|
+
>
|
|
297
|
+
{/* Header */}
|
|
298
|
+
<div className="flex items-center justify-between px-5 pt-5 pb-3 shrink-0">
|
|
299
|
+
<div className="flex items-center gap-2">
|
|
300
|
+
{isOrganizing ? (
|
|
301
|
+
<div className="relative shrink-0">
|
|
302
|
+
<Sparkles size={16} className="text-[var(--amber)]" />
|
|
303
|
+
<Loader2 size={10} className="absolute -bottom-0.5 -right-0.5 text-[var(--amber)] animate-spin" />
|
|
304
|
+
</div>
|
|
305
|
+
) : isDone ? (
|
|
306
|
+
<Check size={16} className="text-success" />
|
|
307
|
+
) : (
|
|
308
|
+
<AlertCircle size={16} className="text-error" />
|
|
309
|
+
)}
|
|
310
|
+
<h2 className="text-base font-semibold text-foreground">
|
|
311
|
+
{fi.organizeDetailTitle as string}
|
|
312
|
+
</h2>
|
|
313
|
+
{isOrganizing && (
|
|
314
|
+
<span className="text-xs text-muted-foreground/60 tabular-nums">
|
|
315
|
+
{(fi.organizeElapsed as (s: number) => string)(elapsed)}
|
|
316
|
+
</span>
|
|
317
|
+
)}
|
|
318
|
+
</div>
|
|
319
|
+
<div className="flex items-center gap-1">
|
|
320
|
+
<button
|
|
321
|
+
type="button"
|
|
322
|
+
onClick={() => setMaximized(false)}
|
|
323
|
+
className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
|
|
324
|
+
title={fi.organizeMinimizeModal as string}
|
|
325
|
+
>
|
|
326
|
+
<Minimize2 size={14} />
|
|
327
|
+
</button>
|
|
328
|
+
<button
|
|
329
|
+
type="button"
|
|
330
|
+
onClick={handleDismiss}
|
|
331
|
+
className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
|
|
332
|
+
>
|
|
333
|
+
<X size={14} />
|
|
334
|
+
</button>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
{/* Scrollable body */}
|
|
339
|
+
<div className="flex-1 min-h-0 overflow-y-auto px-5 pb-2 space-y-4">
|
|
340
|
+
{/* Live progress during organizing */}
|
|
341
|
+
{isOrganizing && (
|
|
342
|
+
<div className="flex items-center gap-2 px-3 py-2.5 rounded-lg bg-[var(--amber-subtle)] border border-[var(--amber-dim)]">
|
|
343
|
+
<Loader2 size={14} className="text-[var(--amber)] animate-spin shrink-0" />
|
|
344
|
+
<span className="text-xs text-foreground">{stageText(t, displayHint)}</span>
|
|
345
|
+
</div>
|
|
346
|
+
)}
|
|
347
|
+
|
|
348
|
+
{/* Source files */}
|
|
349
|
+
{aiOrganize.sourceFileNames.length > 0 && (
|
|
350
|
+
<div>
|
|
351
|
+
<h3 className="text-xs font-medium text-muted-foreground mb-1.5">
|
|
352
|
+
{fi.organizeSourceFiles as string}
|
|
353
|
+
</h3>
|
|
354
|
+
<div className="flex flex-wrap gap-1.5">
|
|
355
|
+
{aiOrganize.sourceFileNames.map((name, i) => (
|
|
356
|
+
<span key={i} className="inline-flex items-center gap-1 px-2 py-1 rounded-md bg-muted/60 text-xs text-foreground">
|
|
357
|
+
<FileIcon size={11} className="text-muted-foreground shrink-0" />
|
|
358
|
+
{name}
|
|
359
|
+
</span>
|
|
360
|
+
))}
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
)}
|
|
364
|
+
|
|
365
|
+
{/* AI Summary */}
|
|
366
|
+
{(aiOrganize.summary || isOrganizing) && (
|
|
367
|
+
<div>
|
|
368
|
+
<h3 className="text-xs font-medium text-muted-foreground mb-1.5">
|
|
369
|
+
{fi.organizeSummaryLabel as string}
|
|
370
|
+
</h3>
|
|
371
|
+
<div className="px-3 py-2.5 rounded-lg bg-muted/30 border border-border text-sm text-foreground leading-relaxed whitespace-pre-wrap">
|
|
372
|
+
{aiOrganize.summary || (fi.organizeNoSummary as string)}
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
)}
|
|
376
|
+
|
|
377
|
+
{/* File changes */}
|
|
378
|
+
{aiOrganize.changes.length > 0 && (
|
|
379
|
+
<div>
|
|
380
|
+
<h3 className="text-xs font-medium text-muted-foreground mb-1.5">
|
|
381
|
+
{(fi.organizeChangesLabel as (n: number) => string)(aiOrganize.changes.length)}
|
|
382
|
+
</h3>
|
|
383
|
+
<div className="space-y-0.5">
|
|
384
|
+
{aiOrganize.changes.map((c, idx) => renderChangeRow(c, idx))}
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
)}
|
|
388
|
+
|
|
389
|
+
{/* Error detail */}
|
|
390
|
+
{isError && (
|
|
391
|
+
<div className="px-3 py-2.5 rounded-lg bg-error/5 border border-error/20 text-xs text-error">
|
|
392
|
+
{aiOrganize.error}
|
|
393
|
+
</div>
|
|
394
|
+
)}
|
|
395
|
+
</div>
|
|
396
|
+
|
|
397
|
+
{/* Footer actions */}
|
|
398
|
+
{(isDone || isError) && renderActions()}
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
405
|
+
// Expanded panel (file list with per-file undo) — bottom toast size
|
|
406
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
201
407
|
if (expanded && (isDone || isError)) {
|
|
202
408
|
return (
|
|
203
409
|
<div
|
|
@@ -212,71 +418,29 @@ export default function OrganizeToast({
|
|
|
212
418
|
{isDone ? fi.organizeReviewTitle as string : fi.organizeErrorTitle as string}
|
|
213
419
|
</span>
|
|
214
420
|
</div>
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
421
|
+
<div className="flex items-center gap-1">
|
|
422
|
+
<button
|
|
423
|
+
type="button"
|
|
424
|
+
onClick={() => { setExpanded(false); setMaximized(true); }}
|
|
425
|
+
className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
|
|
426
|
+
title={fi.organizeDetailTitle as string}
|
|
427
|
+
>
|
|
428
|
+
<Maximize2 size={13} />
|
|
429
|
+
</button>
|
|
430
|
+
<button
|
|
431
|
+
type="button"
|
|
432
|
+
onClick={() => setExpanded(false)}
|
|
433
|
+
className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"
|
|
434
|
+
>
|
|
435
|
+
<ChevronDown size={14} />
|
|
436
|
+
</button>
|
|
437
|
+
</div>
|
|
222
438
|
</div>
|
|
223
439
|
|
|
224
440
|
{/* File list */}
|
|
225
441
|
{isDone && (
|
|
226
442
|
<div className="max-h-[240px] overflow-y-auto p-2 space-y-0.5">
|
|
227
|
-
{aiOrganize.changes.map((c, idx) =>
|
|
228
|
-
const wasUndone = c.undone;
|
|
229
|
-
const undoable = aiOrganize.canUndo(c.path);
|
|
230
|
-
const fileName = c.path.split('/').pop() ?? c.path;
|
|
231
|
-
|
|
232
|
-
return (
|
|
233
|
-
<div
|
|
234
|
-
key={`${c.path}-${idx}`}
|
|
235
|
-
className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm transition-colors ${wasUndone ? 'bg-muted/30 opacity-50' : 'bg-muted/50'}`}
|
|
236
|
-
>
|
|
237
|
-
{wasUndone ? (
|
|
238
|
-
<Undo2 size={14} className="text-muted-foreground shrink-0" />
|
|
239
|
-
) : c.action === 'create' ? (
|
|
240
|
-
<FilePlus size={14} className="text-success shrink-0" />
|
|
241
|
-
) : (
|
|
242
|
-
<FileEdit size={14} className="text-[var(--amber)] shrink-0" />
|
|
243
|
-
)}
|
|
244
|
-
<span className={`truncate flex-1 ${wasUndone ? 'line-through text-muted-foreground' : 'text-foreground'}`}>
|
|
245
|
-
{fileName}
|
|
246
|
-
</span>
|
|
247
|
-
{wasUndone ? (
|
|
248
|
-
<span className="text-xs text-muted-foreground shrink-0">{fi.organizeUndone as string}</span>
|
|
249
|
-
) : (
|
|
250
|
-
<span className={`text-xs shrink-0 ${c.ok ? 'text-muted-foreground' : 'text-error'}`}>
|
|
251
|
-
{!c.ok ? fi.organizeFailed as string
|
|
252
|
-
: c.action === 'create' ? fi.organizeCreated as string
|
|
253
|
-
: fi.organizeUpdated as string}
|
|
254
|
-
</span>
|
|
255
|
-
)}
|
|
256
|
-
{undoable && (
|
|
257
|
-
<button
|
|
258
|
-
type="button"
|
|
259
|
-
onClick={() => handleUndoOne(c.path)}
|
|
260
|
-
disabled={undoing}
|
|
261
|
-
className="text-2xs text-muted-foreground/60 hover:text-foreground transition-colors shrink-0 px-1 disabled:opacity-40"
|
|
262
|
-
title={fi.organizeUndoOne as string}
|
|
263
|
-
>
|
|
264
|
-
<Undo2 size={12} />
|
|
265
|
-
</button>
|
|
266
|
-
)}
|
|
267
|
-
{c.ok && !c.undone && (
|
|
268
|
-
<button
|
|
269
|
-
type="button"
|
|
270
|
-
onClick={() => handleViewFile(c.path)}
|
|
271
|
-
className="text-2xs text-muted-foreground/60 hover:text-[var(--amber)] transition-colors shrink-0 px-1"
|
|
272
|
-
title={fi.organizeViewFile as string}
|
|
273
|
-
>
|
|
274
|
-
<ExternalLink size={12} />
|
|
275
|
-
</button>
|
|
276
|
-
)}
|
|
277
|
-
</div>
|
|
278
|
-
);
|
|
279
|
-
})}
|
|
443
|
+
{aiOrganize.changes.map((c, idx) => renderChangeRow(c, idx))}
|
|
280
444
|
</div>
|
|
281
445
|
)}
|
|
282
446
|
|
|
@@ -286,31 +450,14 @@ export default function OrganizeToast({
|
|
|
286
450
|
</div>
|
|
287
451
|
)}
|
|
288
452
|
|
|
289
|
-
{
|
|
290
|
-
<div className="flex items-center justify-end gap-3 px-4 py-3 border-t border-border">
|
|
291
|
-
{isDone && aiOrganize.hasAnyUndoable && (
|
|
292
|
-
<button
|
|
293
|
-
onClick={handleUndoAll}
|
|
294
|
-
disabled={undoing}
|
|
295
|
-
className="flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors px-2 py-1.5 disabled:opacity-50"
|
|
296
|
-
>
|
|
297
|
-
{undoing ? <Loader2 size={12} className="animate-spin" /> : <Undo2 size={12} />}
|
|
298
|
-
{fi.organizeUndoAll as string}
|
|
299
|
-
</button>
|
|
300
|
-
)}
|
|
301
|
-
<button
|
|
302
|
-
onClick={handleDismiss}
|
|
303
|
-
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium bg-[var(--amber)] text-[var(--amber-foreground)] hover:opacity-90 transition-all"
|
|
304
|
-
>
|
|
305
|
-
<Check size={12} />
|
|
306
|
-
{fi.organizeDone as string}
|
|
307
|
-
</button>
|
|
308
|
-
</div>
|
|
453
|
+
{renderActions()}
|
|
309
454
|
</div>
|
|
310
455
|
);
|
|
311
456
|
}
|
|
312
457
|
|
|
313
|
-
//
|
|
458
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
459
|
+
// Compact toast bar
|
|
460
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
314
461
|
return (
|
|
315
462
|
<div
|
|
316
463
|
className="fixed bottom-4 left-1/2 -translate-x-1/2 z-50 flex items-center gap-3 bg-card border border-border rounded-xl shadow-lg px-4 py-3 max-w-md animate-in fade-in-0 slide-in-from-bottom-2 duration-200"
|
|
@@ -372,6 +519,14 @@ export default function OrganizeToast({
|
|
|
372
519
|
<span className="text-xs text-muted-foreground/60 tabular-nums shrink-0">
|
|
373
520
|
{(fi.organizeElapsed as (s: number) => string)(elapsed)}
|
|
374
521
|
</span>
|
|
522
|
+
<button
|
|
523
|
+
type="button"
|
|
524
|
+
onClick={() => { setMaximized(true); handleUserAction(); }}
|
|
525
|
+
className="text-muted-foreground/50 hover:text-muted-foreground transition-colors shrink-0"
|
|
526
|
+
title={fi.organizeDetailTitle as string}
|
|
527
|
+
>
|
|
528
|
+
<Maximize2 size={13} />
|
|
529
|
+
</button>
|
|
375
530
|
<button
|
|
376
531
|
type="button"
|
|
377
532
|
onClick={handleDismiss}
|
package/app/components/Panel.tsx
CHANGED
|
@@ -30,7 +30,6 @@ const DEFAULT_PANEL_WIDTH: Record<PanelId, number> = {
|
|
|
30
30
|
echo: 280,
|
|
31
31
|
agents: 280,
|
|
32
32
|
discover: 280,
|
|
33
|
-
history: 280,
|
|
34
33
|
};
|
|
35
34
|
|
|
36
35
|
const MIN_PANEL_WIDTH = 240;
|
|
@@ -108,11 +107,12 @@ export default function Panel({
|
|
|
108
107
|
};
|
|
109
108
|
}, [newPopover]);
|
|
110
109
|
|
|
111
|
-
// Double-click hint: show only until user has used it once
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
110
|
+
// Double-click hint: show only until user has used it once.
|
|
111
|
+
// Initialize false to match SSR; hydrate from localStorage in useEffect.
|
|
112
|
+
const [dblHintSeen, setDblHintSeen] = useState(false);
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
try { if (localStorage.getItem('mindos-tree-dblclick-hint') === '1') setDblHintSeen(true); } catch { /* ignore */ }
|
|
115
|
+
}, []);
|
|
116
116
|
const markDblHintSeen = useCallback(() => {
|
|
117
117
|
if (!dblHintSeen) {
|
|
118
118
|
setDblHintSeen(true);
|
|
@@ -345,12 +345,22 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
345
345
|
<ActivityBar
|
|
346
346
|
activePanel={railActivePanel}
|
|
347
347
|
onPanelChange={lp.setActivePanel}
|
|
348
|
+
onEchoClick={() => {
|
|
349
|
+
const wasActive = lp.activePanel === 'echo';
|
|
350
|
+
lp.setActivePanel(wasActive ? null : 'echo');
|
|
351
|
+
if (!wasActive) router.push('/echo/about-you');
|
|
352
|
+
}}
|
|
348
353
|
onAgentsClick={() => {
|
|
349
354
|
const wasActive = lp.activePanel === 'agents';
|
|
350
355
|
lp.setActivePanel(wasActive ? null : 'agents');
|
|
351
356
|
if (!wasActive) router.push('/agents');
|
|
352
357
|
setAgentDetailKey(null);
|
|
353
358
|
}}
|
|
359
|
+
onDiscoverClick={() => {
|
|
360
|
+
const wasActive = lp.activePanel === 'discover';
|
|
361
|
+
lp.setActivePanel(wasActive ? null : 'discover');
|
|
362
|
+
if (!wasActive) router.push('/explore');
|
|
363
|
+
}}
|
|
354
364
|
syncStatus={syncStatus}
|
|
355
365
|
expanded={lp.railExpanded}
|
|
356
366
|
onExpandedChange={handleExpandedChange}
|
|
@@ -97,21 +97,21 @@ export default function UpdateOverlay() {
|
|
|
97
97
|
>
|
|
98
98
|
{done ? (
|
|
99
99
|
<>
|
|
100
|
-
<CheckCircle2 size={32} style={{ color: '
|
|
101
|
-
<div style={{ color: '
|
|
100
|
+
<CheckCircle2 size={32} style={{ color: 'var(--success)', marginBottom: 12 }} />
|
|
101
|
+
<div style={{ color: 'var(--foreground)', fontSize: 18, fontWeight: 600 }}>
|
|
102
102
|
{zh ? '更新成功!' : 'Update Complete!'}
|
|
103
103
|
</div>
|
|
104
|
-
<div style={{ color: '
|
|
104
|
+
<div style={{ color: 'var(--muted-foreground)', fontSize: 13, marginTop: 6 }}>
|
|
105
105
|
{zh ? '正在刷新页面...' : 'Reloading...'}
|
|
106
106
|
</div>
|
|
107
107
|
</>
|
|
108
108
|
) : (
|
|
109
109
|
<>
|
|
110
|
-
<Loader2 size={32} style={{ color: '
|
|
111
|
-
<div style={{ color: '
|
|
110
|
+
<Loader2 size={32} style={{ color: 'var(--amber)', marginBottom: 12, animation: 'spin 1s linear infinite' }} />
|
|
111
|
+
<div style={{ color: 'var(--foreground)', fontSize: 18, fontWeight: 600 }}>
|
|
112
112
|
{zh ? 'MindOS 正在更新...' : 'MindOS is Updating...'}
|
|
113
113
|
</div>
|
|
114
|
-
<div style={{ color: '
|
|
114
|
+
<div style={{ color: 'var(--muted-foreground)', fontSize: 13, marginTop: 6, textAlign: 'center', maxWidth: 300, lineHeight: 1.5 }}>
|
|
115
115
|
{zh
|
|
116
116
|
? '服务正在重启,请勿关闭此页面。完成后将自动刷新。'
|
|
117
117
|
: 'The server is restarting. Please do not close this page. It will auto-reload when ready.'}
|
|
@@ -243,9 +243,9 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
243
243
|
<h1 className="text-lg font-semibold tracking-tight font-display text-foreground truncate">{agent.name}</h1>
|
|
244
244
|
<div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 mt-0.5 text-2xs text-muted-foreground/60">
|
|
245
245
|
<span className={`font-medium px-1.5 py-px rounded-full ${
|
|
246
|
-
status === 'connected' ? 'bg-
|
|
246
|
+
status === 'connected' ? 'bg-muted text-muted-foreground'
|
|
247
247
|
: status === 'detected' ? 'bg-[var(--amber-subtle)] text-[var(--amber-text)]'
|
|
248
|
-
: 'bg-
|
|
248
|
+
: 'bg-error/10 text-error'
|
|
249
249
|
}`}>{status}</span>
|
|
250
250
|
<span className="font-mono">{agent.transport ?? agent.preferredTransport}</span>
|
|
251
251
|
<span className="text-muted-foreground/25" aria-hidden="true">·</span>
|
|
@@ -296,7 +296,7 @@ function ByAgentView({
|
|
|
296
296
|
const mcpServers = agent.configuredMcpServers ?? [];
|
|
297
297
|
const nativeSkillCount = (agent.installedSkillNames ?? []).length;
|
|
298
298
|
return (
|
|
299
|
-
<div key={agent.key} className={`rounded-xl border bg-card group hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-150 ${status === '
|
|
299
|
+
<div key={agent.key} className={`rounded-xl border bg-card group hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-150 ${status === 'detected' ? 'border-l-2 border-l-[var(--amber)] border-border' : status === 'notFound' ? 'border-l-2 border-l-error border-border' : 'border-border'}`}>
|
|
300
300
|
{/* Card header with avatar */}
|
|
301
301
|
<div className="flex items-center gap-3 p-3">
|
|
302
302
|
<AgentAvatar name={agent.name} status={status} />
|
|
@@ -306,7 +306,7 @@ function ByAgentView({
|
|
|
306
306
|
{agent.name}
|
|
307
307
|
</Link>
|
|
308
308
|
<span className="text-2xs text-muted-foreground font-mono shrink-0">{agent.transport ?? agent.preferredTransport}</span>
|
|
309
|
-
<span className={`text-2xs px-1.5 py-0.5 rounded shrink-0 ${status === 'connected' ? 'bg-
|
|
309
|
+
<span className={`text-2xs px-1.5 py-0.5 rounded shrink-0 ${status === 'connected' ? 'bg-muted text-muted-foreground' : status === 'detected' ? 'bg-[var(--amber-dim)] text-[var(--amber-text)]' : 'bg-error/10 text-error'}`}>
|
|
310
310
|
{copy.status[status]}
|
|
311
311
|
</span>
|
|
312
312
|
</div>
|
|
@@ -269,15 +269,15 @@ function StatCell({
|
|
|
269
269
|
: 'text-muted-foreground';
|
|
270
270
|
const iconColor =
|
|
271
271
|
tone === 'ok'
|
|
272
|
-
? 'text-
|
|
272
|
+
? 'text-[var(--success)]/70'
|
|
273
273
|
: tone === 'warn'
|
|
274
|
-
? 'text-amber
|
|
274
|
+
? 'text-[var(--amber)]/70'
|
|
275
275
|
: 'text-muted-foreground/40';
|
|
276
276
|
const hoverBg =
|
|
277
277
|
tone === 'ok'
|
|
278
|
-
? 'hover:bg-
|
|
278
|
+
? 'hover:bg-muted/20'
|
|
279
279
|
: tone === 'warn'
|
|
280
|
-
? 'hover:bg-amber
|
|
280
|
+
? 'hover:bg-[var(--amber)]/[0.04]'
|
|
281
281
|
: 'hover:bg-muted/20';
|
|
282
282
|
|
|
283
283
|
return (
|
|
@@ -335,8 +335,8 @@ function QuickNavCard({
|
|
|
335
335
|
<span
|
|
336
336
|
className={`text-2xs px-2 py-0.5 rounded-full font-medium select-none ${
|
|
337
337
|
statTone === 'ok'
|
|
338
|
-
? 'bg-
|
|
339
|
-
: 'bg-amber-
|
|
338
|
+
? 'bg-muted text-muted-foreground'
|
|
339
|
+
: 'bg-[var(--amber-dim)] text-[var(--amber-text)]'
|
|
340
340
|
}`}
|
|
341
341
|
>
|
|
342
342
|
{stat}
|
|
@@ -373,10 +373,10 @@ function AgentCard({
|
|
|
373
373
|
status === 'connected' ? copy.connected : status === 'detected' ? copy.detected : copy.notFound;
|
|
374
374
|
const statusColor =
|
|
375
375
|
status === 'connected'
|
|
376
|
-
? 'bg-
|
|
376
|
+
? 'bg-muted text-muted-foreground'
|
|
377
377
|
: status === 'detected'
|
|
378
|
-
? 'bg-amber-
|
|
379
|
-
: 'bg-
|
|
378
|
+
? 'bg-[var(--amber-dim)] text-[var(--amber-text)]'
|
|
379
|
+
: 'bg-error/10 text-error';
|
|
380
380
|
|
|
381
381
|
return (
|
|
382
382
|
<Link
|
|
@@ -410,10 +410,10 @@ function AgentCard({
|
|
|
410
410
|
<span className="flex-1 min-w-[4px]" />
|
|
411
411
|
{hasRuntime && (
|
|
412
412
|
<span
|
|
413
|
-
className="flex items-center gap-1 text-
|
|
413
|
+
className="flex items-center gap-1 text-[var(--success)]"
|
|
414
414
|
title={copy.runtimeActive}
|
|
415
415
|
>
|
|
416
|
-
<span className="w-1.5 h-1.5 rounded-full bg-
|
|
416
|
+
<span className="w-1.5 h-1.5 rounded-full bg-[var(--success)] animate-pulse" aria-hidden="true" />
|
|
417
417
|
<span className="text-2xs font-medium">{copy.runtimeActive}</span>
|
|
418
418
|
</span>
|
|
419
419
|
)}
|
|
@@ -27,7 +27,7 @@ export function StatusDot({ tone, label, count }: { tone: 'ok' | 'warn' | 'neutr
|
|
|
27
27
|
const countCls = tone === 'ok' ? 'text-foreground' : tone === 'warn' ? 'text-[var(--amber)]' : 'text-muted-foreground';
|
|
28
28
|
return (
|
|
29
29
|
<span className="inline-flex items-center gap-1.5 text-muted-foreground">
|
|
30
|
-
<span className={`w-2 h-2 rounded-full ${dotCls}
|
|
30
|
+
<span className={`w-2 h-2 rounded-full ${dotCls}`} aria-hidden="true" />
|
|
31
31
|
<span className="text-xs">{label}</span>
|
|
32
32
|
<span className={`tabular-nums font-medium ${countCls}`}>{count}</span>
|
|
33
33
|
</span>
|
|
@@ -131,20 +131,20 @@ export function EmptyState({ message, icon, className }: { message: string; icon
|
|
|
131
131
|
|
|
132
132
|
/* ────────── Agent Avatar ────────── */
|
|
133
133
|
|
|
134
|
-
/**
|
|
134
|
+
/** Dual-mode palette: soft pastels in light, muted tones in dark — [bg, border, text] */
|
|
135
135
|
const AVATAR_PALETTES: [string, string, string][] = [
|
|
136
|
-
['bg-rose-100/70',
|
|
137
|
-
['bg-violet-100/70', 'border-violet-300/50', 'text-violet-600/80'],
|
|
138
|
-
['bg-emerald-100/70',
|
|
139
|
-
['bg-sky-100/70',
|
|
140
|
-
['bg-amber-100/70',
|
|
141
|
-
['bg-teal-100/70',
|
|
142
|
-
['bg-pink-100/70',
|
|
143
|
-
['bg-indigo-100/70', 'border-indigo-300/50', 'text-indigo-600/80'],
|
|
144
|
-
['bg-lime-100/70',
|
|
145
|
-
['bg-fuchsia-100/70',
|
|
146
|
-
['bg-cyan-100/70',
|
|
147
|
-
['bg-orange-100/70', 'border-orange-300/50', 'text-orange-600/80'],
|
|
136
|
+
['bg-rose-100/70 dark:bg-rose-900/30', 'border-rose-300/50 dark:border-rose-700/40', 'text-rose-600/80 dark:text-rose-400/80'],
|
|
137
|
+
['bg-violet-100/70 dark:bg-violet-900/30', 'border-violet-300/50 dark:border-violet-700/40', 'text-violet-600/80 dark:text-violet-400/80'],
|
|
138
|
+
['bg-emerald-100/70 dark:bg-emerald-900/30', 'border-emerald-300/50 dark:border-emerald-700/40','text-emerald-600/80 dark:text-emerald-400/80'],
|
|
139
|
+
['bg-sky-100/70 dark:bg-sky-900/30', 'border-sky-300/50 dark:border-sky-700/40', 'text-sky-600/80 dark:text-sky-400/80'],
|
|
140
|
+
['bg-amber-100/70 dark:bg-amber-900/30', 'border-amber-300/50 dark:border-amber-700/40', 'text-amber-700/80 dark:text-amber-400/80'],
|
|
141
|
+
['bg-teal-100/70 dark:bg-teal-900/30', 'border-teal-300/50 dark:border-teal-700/40', 'text-teal-600/80 dark:text-teal-400/80'],
|
|
142
|
+
['bg-pink-100/70 dark:bg-pink-900/30', 'border-pink-300/50 dark:border-pink-700/40', 'text-pink-600/80 dark:text-pink-400/80'],
|
|
143
|
+
['bg-indigo-100/70 dark:bg-indigo-900/30', 'border-indigo-300/50 dark:border-indigo-700/40', 'text-indigo-600/80 dark:text-indigo-400/80'],
|
|
144
|
+
['bg-lime-100/70 dark:bg-lime-900/30', 'border-lime-300/50 dark:border-lime-700/40', 'text-lime-700/80 dark:text-lime-400/80'],
|
|
145
|
+
['bg-fuchsia-100/70 dark:bg-fuchsia-900/30', 'border-fuchsia-300/50 dark:border-fuchsia-700/40','text-fuchsia-600/80 dark:text-fuchsia-400/80'],
|
|
146
|
+
['bg-cyan-100/70 dark:bg-cyan-900/30', 'border-cyan-300/50 dark:border-cyan-700/40', 'text-cyan-600/80 dark:text-cyan-400/80'],
|
|
147
|
+
['bg-orange-100/70 dark:bg-orange-900/30', 'border-orange-300/50 dark:border-orange-700/40', 'text-orange-600/80 dark:text-orange-400/80'],
|
|
148
148
|
];
|
|
149
149
|
|
|
150
150
|
function hashName(str: string): number {
|
|
@@ -696,7 +696,7 @@ function AgentCard({
|
|
|
696
696
|
</Link>
|
|
697
697
|
{skillMode && (
|
|
698
698
|
<span className={`text-2xs px-1.5 py-0.5 rounded shrink-0 ${
|
|
699
|
-
skillMode === 'universal' ? 'bg-
|
|
699
|
+
skillMode === 'universal' ? 'bg-muted text-muted-foreground'
|
|
700
700
|
: skillMode === 'additional' ? 'bg-[var(--amber-dim)] text-[var(--amber-text)]'
|
|
701
701
|
: 'bg-muted text-muted-foreground'
|
|
702
702
|
}`}>
|
|
@@ -422,7 +422,7 @@ function MetaCard({
|
|
|
422
422
|
tone?: 'ok' | 'muted' | 'default';
|
|
423
423
|
}) {
|
|
424
424
|
const valueColor =
|
|
425
|
-
tone === 'ok' ? 'text-
|
|
425
|
+
tone === 'ok' ? 'text-[var(--success)]'
|
|
426
426
|
: tone === 'muted' ? 'text-muted-foreground'
|
|
427
427
|
: 'text-foreground';
|
|
428
428
|
return (
|
|
@@ -18,7 +18,7 @@ function UserMessageContent({ content, skillName }: { content: string; skillName
|
|
|
18
18
|
const rest = prefixMatch ? content.slice(prefixMatch[0].length) : content;
|
|
19
19
|
return (
|
|
20
20
|
<>
|
|
21
|
-
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[11px] font-medium bg-[var(--amber
|
|
21
|
+
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[11px] font-medium bg-[var(--amber)]/15 text-[var(--amber)] mr-1 align-middle">
|
|
22
22
|
<Zap size={10} className="shrink-0" />
|
|
23
23
|
{resolved}
|
|
24
24
|
</span>
|
|
@@ -19,12 +19,12 @@ export default function EchoPanel({ active, maximized, onMaximize }: EchoPanelPr
|
|
|
19
19
|
const e = t.panels.echo;
|
|
20
20
|
const pathname = usePathname() ?? '';
|
|
21
21
|
|
|
22
|
-
const rowBySegment: Record<EchoSegment, { icon: ReactNode; title: string
|
|
23
|
-
'about-you': { icon: <UserRound size={14} />, title: e.aboutYouTitle
|
|
24
|
-
continued: { icon: <Bookmark size={14} />, title: e.continuedTitle
|
|
25
|
-
daily: { icon: <Sun size={14} />, title: e.dailyEchoTitle
|
|
26
|
-
'past-you': { icon: <History size={14} />, title: e.pastYouTitle
|
|
27
|
-
growth: { icon: <Brain size={14} />, title: e.intentGrowthTitle
|
|
22
|
+
const rowBySegment: Record<EchoSegment, { icon: ReactNode; title: string }> = {
|
|
23
|
+
'about-you': { icon: <UserRound size={14} />, title: e.aboutYouTitle },
|
|
24
|
+
continued: { icon: <Bookmark size={14} />, title: e.continuedTitle },
|
|
25
|
+
daily: { icon: <Sun size={14} />, title: e.dailyEchoTitle },
|
|
26
|
+
'past-you': { icon: <History size={14} />, title: e.pastYouTitle },
|
|
27
|
+
growth: { icon: <Brain size={14} />, title: e.intentGrowthTitle },
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
return (
|
|
@@ -37,7 +37,7 @@ export default function EchoPanel({ active, maximized, onMaximize }: EchoPanelPr
|
|
|
37
37
|
const href = ECHO_SEGMENT_HREF[segment];
|
|
38
38
|
const isActive = pathname === href || pathname.startsWith(`${href}/`);
|
|
39
39
|
return (
|
|
40
|
-
<PanelNavRow key={segment} href={href} icon={row.icon} title={row.title}
|
|
40
|
+
<PanelNavRow key={segment} href={href} icon={row.icon} title={row.title} active={isActive} />
|
|
41
41
|
);
|
|
42
42
|
})}
|
|
43
43
|
</div>
|