@geminilight/mindos 0.5.40 → 0.5.42
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/icon.svg +25 -23
- package/app/app/layout.tsx +10 -4
- package/app/app/page.tsx +20 -1
- package/app/app/view/[...path]/ViewPageClient.tsx +17 -16
- package/app/components/ActivityBar.tsx +29 -3
- package/app/components/CreateSpaceModal.tsx +182 -0
- package/app/components/DirPicker.tsx +129 -0
- package/app/components/HomeContent.tsx +110 -226
- package/app/components/Panel.tsx +1 -0
- package/app/components/SidebarLayout.tsx +4 -0
- package/app/components/ThemeToggle.tsx +1 -1
- package/app/components/UpdateBanner.tsx +22 -30
- package/app/components/panels/DiscoverPanel.tsx +172 -0
- package/app/components/settings/SettingsContent.tsx +23 -2
- package/app/lib/LocaleContext.tsx +12 -2
- package/app/lib/actions.ts +16 -5
- package/app/lib/i18n-en.ts +28 -2
- package/app/lib/i18n-zh.ts +28 -2
- package/app/next-env.d.ts +1 -1
- package/app/package.json +2 -1
- package/bin/cli.js +91 -5
- package/bin/lib/gateway.js +40 -3
- package/bin/lib/skill-check.js +133 -0
- package/bin/lib/startup.js +4 -0
- package/package.json +1 -1
- package/scripts/fix-postcss-deps.cjs +30 -0
- package/skills/mindos/SKILL.md +78 -4
- package/skills/mindos-zh/SKILL.md +78 -4
- package/templates/en//360/237/223/235 Notes/Drafts/README.md" +8 -0
- package/templates/en//360/237/223/235 Notes/README.md" +2 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260/README.md" +2 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//350/215/211/347/250/277/README.md" +8 -0
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import Link from 'next/link';
|
|
4
|
-
import { FileText, Table, Clock, Sparkles, ArrowRight, FilePlus, Search, ChevronDown, Compass, Folder, Puzzle, Brain, Plus
|
|
5
|
-
import { useState, useEffect, useMemo
|
|
4
|
+
import { FileText, Table, Clock, Sparkles, ArrowRight, FilePlus, Search, ChevronDown, Compass, Folder, Puzzle, Brain, Plus } from 'lucide-react';
|
|
5
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
6
6
|
import { useLocale } from '@/lib/LocaleContext';
|
|
7
7
|
import { encodePath, relativeTime } from '@/lib/utils';
|
|
8
8
|
import { getAllRenderers } from '@/lib/renderers/registry';
|
|
9
9
|
import '@/lib/renderers/index'; // registers all renderers
|
|
10
10
|
import OnboardingView from './OnboardingView';
|
|
11
11
|
import GuideCard from './GuideCard';
|
|
12
|
-
import
|
|
13
|
-
import { createSpaceAction } from '@/lib/actions';
|
|
12
|
+
import CreateSpaceModal from './CreateSpaceModal';
|
|
14
13
|
import type { SpaceInfo } from '@/app/page';
|
|
15
14
|
|
|
16
15
|
interface RecentFile {
|
|
@@ -78,22 +77,35 @@ function stripEmoji(name: string): string {
|
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
/* ── Section Title component (shared across all three sections) ── */
|
|
81
|
-
|
|
80
|
+
interface SectionTitleProps {
|
|
81
|
+
icon: React.ReactNode;
|
|
82
|
+
children: React.ReactNode;
|
|
83
|
+
/** Item count badge — only rendered when > 0 */
|
|
84
|
+
count?: number;
|
|
85
|
+
/** Right-aligned action slot (e.g. "View all" button) */
|
|
86
|
+
action?: React.ReactNode;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function SectionTitle({ icon, children, count, action }: SectionTitleProps) {
|
|
82
90
|
return (
|
|
83
91
|
<div className="flex items-center gap-2 mb-4">
|
|
84
92
|
<span className="text-[var(--amber)]">{icon}</span>
|
|
85
|
-
<h2 className="text-
|
|
93
|
+
<h2 className="text-sm font-semibold font-display text-foreground">
|
|
86
94
|
{children}
|
|
87
95
|
</h2>
|
|
96
|
+
{count != null && count > 0 && (
|
|
97
|
+
<span className="text-xs tabular-nums text-muted-foreground font-display">{count}</span>
|
|
98
|
+
)}
|
|
99
|
+
{action ? <div className="ml-auto">{action}</div> : null}
|
|
88
100
|
</div>
|
|
89
101
|
);
|
|
90
102
|
}
|
|
91
103
|
|
|
92
104
|
const FILES_PER_GROUP = 3;
|
|
93
105
|
const SPACES_PER_ROW = 6; // 3 cols × 2 rows on desktop, show 1 row initially
|
|
94
|
-
const PLUGINS_INITIAL =
|
|
106
|
+
const PLUGINS_INITIAL = 4;
|
|
95
107
|
|
|
96
|
-
export default function HomeContent({ recent, existingFiles, spaces }: { recent: RecentFile[]; existingFiles?: string[]; spaces?: SpaceInfo[] }) {
|
|
108
|
+
export default function HomeContent({ recent, existingFiles, spaces, dirPaths }: { recent: RecentFile[]; existingFiles?: string[]; spaces?: SpaceInfo[]; dirPaths?: string[] }) {
|
|
97
109
|
const { t } = useLocale();
|
|
98
110
|
const [showAll, setShowAll] = useState(false);
|
|
99
111
|
const [showAllSpaces, setShowAllSpaces] = useState(false);
|
|
@@ -208,10 +220,88 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
208
220
|
</div>
|
|
209
221
|
</div>
|
|
210
222
|
|
|
211
|
-
{/* ── Section 1:
|
|
223
|
+
{/* ── Section 1: Spaces ── */}
|
|
224
|
+
<section className="mb-8">
|
|
225
|
+
<SectionTitle
|
|
226
|
+
icon={<Brain size={13} />}
|
|
227
|
+
count={spaceList.length > 0 ? spaceList.length : undefined}
|
|
228
|
+
action={<CreateSpaceButton t={t} />}
|
|
229
|
+
>
|
|
230
|
+
{t.home.spaces}
|
|
231
|
+
</SectionTitle>
|
|
232
|
+
{spaceList.length > 0 ? (
|
|
233
|
+
<>
|
|
234
|
+
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
|
235
|
+
{(showAllSpaces ? spaceList : spaceList.slice(0, SPACES_PER_ROW)).map(s => {
|
|
236
|
+
const emoji = extractEmoji(s.name);
|
|
237
|
+
const label = stripEmoji(s.name);
|
|
238
|
+
const isEmpty = s.fileCount === 0;
|
|
239
|
+
return (
|
|
240
|
+
<Link
|
|
241
|
+
key={s.name}
|
|
242
|
+
href={`/view/${encodePath(s.path)}`}
|
|
243
|
+
className={`flex items-start gap-3 px-3.5 py-3 rounded-xl border transition-all duration-150 hover:translate-x-0.5 ${
|
|
244
|
+
isEmpty
|
|
245
|
+
? 'border-dashed border-border/50 opacity-50 hover:opacity-70'
|
|
246
|
+
: 'border-border hover:border-amber-500/30 hover:bg-muted/40'
|
|
247
|
+
}`}
|
|
248
|
+
>
|
|
249
|
+
{emoji ? (
|
|
250
|
+
<span className="text-lg leading-none shrink-0 mt-0.5" suppressHydrationWarning>{emoji}</span>
|
|
251
|
+
) : (
|
|
252
|
+
<Folder size={16} className="shrink-0 text-[var(--amber)] mt-0.5" />
|
|
253
|
+
)}
|
|
254
|
+
<div className="min-w-0 flex-1">
|
|
255
|
+
<span className="text-sm font-medium truncate block text-foreground">{label}</span>
|
|
256
|
+
{s.description && (
|
|
257
|
+
<span className="text-xs text-muted-foreground line-clamp-1 mt-0.5" suppressHydrationWarning>{s.description}</span>
|
|
258
|
+
)}
|
|
259
|
+
<span className="text-xs text-muted-foreground opacity-50 mt-0.5 block">
|
|
260
|
+
{t.home.nFiles(s.fileCount)}
|
|
261
|
+
</span>
|
|
262
|
+
</div>
|
|
263
|
+
</Link>
|
|
264
|
+
);
|
|
265
|
+
})}
|
|
266
|
+
</div>
|
|
267
|
+
{spaceList.length > SPACES_PER_ROW && (
|
|
268
|
+
<button
|
|
269
|
+
onClick={() => setShowAllSpaces(v => !v)}
|
|
270
|
+
className="flex items-center gap-1.5 mt-2 text-xs font-medium text-[var(--amber)] transition-colors hover:opacity-80 cursor-pointer font-display"
|
|
271
|
+
>
|
|
272
|
+
<ChevronDown size={12} className={`transition-transform duration-200 ${showAllSpaces ? 'rotate-180' : ''}`} />
|
|
273
|
+
<span>{showAllSpaces ? t.home.showLess : t.home.showMore}</span>
|
|
274
|
+
</button>
|
|
275
|
+
)}
|
|
276
|
+
</>
|
|
277
|
+
) : (
|
|
278
|
+
<p className="text-xs text-muted-foreground py-2">
|
|
279
|
+
{t.home.noSpacesYet ?? 'No spaces yet. Create one to organize your knowledge.'}
|
|
280
|
+
</p>
|
|
281
|
+
)}
|
|
282
|
+
<CreateSpaceModal t={t} dirPaths={dirPaths ?? []} />
|
|
283
|
+
</section>
|
|
284
|
+
|
|
285
|
+
{/* ── Section 2: Extensions ── */}
|
|
212
286
|
{availablePlugins.length > 0 && (
|
|
213
287
|
<section className="mb-8">
|
|
214
|
-
<SectionTitle
|
|
288
|
+
<SectionTitle
|
|
289
|
+
icon={<Puzzle size={13} />}
|
|
290
|
+
count={availablePlugins.length}
|
|
291
|
+
action={
|
|
292
|
+
availablePlugins.length > PLUGINS_INITIAL ? (
|
|
293
|
+
<button
|
|
294
|
+
onClick={() => setShowAllPlugins(v => !v)}
|
|
295
|
+
className="flex items-center gap-1 text-xs font-medium text-[var(--amber)] transition-colors hover:opacity-80 cursor-pointer font-display"
|
|
296
|
+
>
|
|
297
|
+
<span>{showAllPlugins ? t.home.showLess : t.home.viewAll}</span>
|
|
298
|
+
<ChevronDown size={12} className={`transition-transform duration-200 ${showAllPlugins ? 'rotate-180' : ''}`} />
|
|
299
|
+
</button>
|
|
300
|
+
) : undefined
|
|
301
|
+
}
|
|
302
|
+
>
|
|
303
|
+
{t.home.plugins}
|
|
304
|
+
</SectionTitle>
|
|
215
305
|
<div className="flex flex-wrap gap-2">
|
|
216
306
|
{(showAllPlugins ? availablePlugins : availablePlugins.slice(0, PLUGINS_INITIAL)).map(r => (
|
|
217
307
|
<Link
|
|
@@ -224,85 +314,13 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
224
314
|
</Link>
|
|
225
315
|
))}
|
|
226
316
|
</div>
|
|
227
|
-
{availablePlugins.length > PLUGINS_INITIAL && (
|
|
228
|
-
<button
|
|
229
|
-
onClick={() => setShowAllPlugins(v => !v)}
|
|
230
|
-
className="flex items-center gap-1.5 mt-2 text-xs font-medium text-[var(--amber)] transition-colors hover:opacity-80 cursor-pointer font-display"
|
|
231
|
-
>
|
|
232
|
-
<ChevronDown size={12} className={`transition-transform duration-200 ${showAllPlugins ? 'rotate-180' : ''}`} />
|
|
233
|
-
<span>{showAllPlugins ? t.home.showLess : t.home.showMore}</span>
|
|
234
|
-
</button>
|
|
235
|
-
)}
|
|
236
|
-
</section>
|
|
237
|
-
)}
|
|
238
|
-
|
|
239
|
-
{/* ── Section 2: Spaces ── */}
|
|
240
|
-
{spaceList.length > 0 ? (
|
|
241
|
-
<section className="mb-8">
|
|
242
|
-
<SectionTitle icon={<Brain size={13} />}>{t.home.spaces}</SectionTitle>
|
|
243
|
-
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
|
244
|
-
{(showAllSpaces ? spaceList : spaceList.slice(0, SPACES_PER_ROW)).map(s => {
|
|
245
|
-
const emoji = extractEmoji(s.name);
|
|
246
|
-
const label = stripEmoji(s.name);
|
|
247
|
-
const isEmpty = s.fileCount === 0;
|
|
248
|
-
return (
|
|
249
|
-
<Link
|
|
250
|
-
key={s.name}
|
|
251
|
-
href={`/view/${encodePath(s.path)}`}
|
|
252
|
-
className={`flex items-start gap-3 px-3.5 py-3 rounded-xl border transition-all duration-150 hover:translate-x-0.5 ${
|
|
253
|
-
isEmpty
|
|
254
|
-
? 'border-dashed border-border/50 opacity-50 hover:opacity-70'
|
|
255
|
-
: 'border-border hover:border-amber-500/30 hover:bg-muted/40'
|
|
256
|
-
}`}
|
|
257
|
-
>
|
|
258
|
-
{emoji ? (
|
|
259
|
-
<span className="text-lg leading-none shrink-0 mt-0.5" suppressHydrationWarning>{emoji}</span>
|
|
260
|
-
) : (
|
|
261
|
-
<Folder size={16} className="shrink-0 text-[var(--amber)] mt-0.5" />
|
|
262
|
-
)}
|
|
263
|
-
<div className="min-w-0 flex-1">
|
|
264
|
-
<span className="text-sm font-medium truncate block text-foreground">{label}</span>
|
|
265
|
-
{s.description && (
|
|
266
|
-
<span className="text-xs text-muted-foreground line-clamp-1 mt-0.5" suppressHydrationWarning>{s.description}</span>
|
|
267
|
-
)}
|
|
268
|
-
<span className="text-xs text-muted-foreground opacity-50 mt-0.5 block">
|
|
269
|
-
{t.home.nFiles(s.fileCount)}
|
|
270
|
-
</span>
|
|
271
|
-
</div>
|
|
272
|
-
</Link>
|
|
273
|
-
);
|
|
274
|
-
})}
|
|
275
|
-
{/* Show "+" card inline only when it won't be orphaned on its own row */}
|
|
276
|
-
{(showAllSpaces || spaceList.length < SPACES_PER_ROW) && <CreateSpaceCard t={t} />}
|
|
277
|
-
</div>
|
|
278
|
-
{spaceList.length > SPACES_PER_ROW && (
|
|
279
|
-
<div className="flex items-center gap-3 mt-2">
|
|
280
|
-
<button
|
|
281
|
-
onClick={() => setShowAllSpaces(v => !v)}
|
|
282
|
-
className="flex items-center gap-1.5 text-xs font-medium text-[var(--amber)] transition-colors hover:opacity-80 cursor-pointer font-display"
|
|
283
|
-
>
|
|
284
|
-
<ChevronDown size={12} className={`transition-transform duration-200 ${showAllSpaces ? 'rotate-180' : ''}`} />
|
|
285
|
-
<span>{showAllSpaces ? t.home.showLess : t.home.showMore}</span>
|
|
286
|
-
</button>
|
|
287
|
-
{/* When collapsed and "+" card is hidden from grid, show as text link */}
|
|
288
|
-
{!showAllSpaces && <CreateSpaceTextLink t={t} />}
|
|
289
|
-
</div>
|
|
290
|
-
)}
|
|
291
|
-
</section>
|
|
292
|
-
) : (
|
|
293
|
-
/* Show create card even when no spaces exist */
|
|
294
|
-
<section className="mb-8">
|
|
295
|
-
<SectionTitle icon={<Brain size={13} />}>{t.home.spaces}</SectionTitle>
|
|
296
|
-
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
|
297
|
-
<CreateSpaceCard t={t} />
|
|
298
|
-
</div>
|
|
299
317
|
</section>
|
|
300
318
|
)}
|
|
301
319
|
|
|
302
320
|
{/* ── Section 3: Recently Edited ── */}
|
|
303
321
|
{recent.length > 0 && (
|
|
304
322
|
<section className="mb-12">
|
|
305
|
-
<SectionTitle icon={<Clock size={13} />}>{t.home.recentlyEdited}</SectionTitle>
|
|
323
|
+
<SectionTitle icon={<Clock size={13} />} count={recent.length}>{t.home.recentlyEdited}</SectionTitle>
|
|
306
324
|
|
|
307
325
|
{groups.length > 0 ? (
|
|
308
326
|
/* Space-Grouped View */
|
|
@@ -463,150 +481,16 @@ export default function HomeContent({ recent, existingFiles, spaces }: { recent:
|
|
|
463
481
|
);
|
|
464
482
|
}
|
|
465
483
|
|
|
466
|
-
/* ── Create Space
|
|
467
|
-
function
|
|
468
|
-
const router = useRouter();
|
|
469
|
-
const [editing, setEditing] = useState(false);
|
|
470
|
-
const [name, setName] = useState('');
|
|
471
|
-
const [description, setDescription] = useState('');
|
|
472
|
-
const [loading, setLoading] = useState(false);
|
|
473
|
-
const [error, setError] = useState('');
|
|
474
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
475
|
-
|
|
476
|
-
const open = useCallback(() => {
|
|
477
|
-
setEditing(true);
|
|
478
|
-
setError('');
|
|
479
|
-
setTimeout(() => inputRef.current?.focus(), 50);
|
|
480
|
-
}, []);
|
|
481
|
-
|
|
482
|
-
const close = useCallback(() => {
|
|
483
|
-
setEditing(false);
|
|
484
|
-
setName('');
|
|
485
|
-
setDescription('');
|
|
486
|
-
setError('');
|
|
487
|
-
}, []);
|
|
488
|
-
|
|
489
|
-
const handleCreate = useCallback(async () => {
|
|
490
|
-
if (!name.trim() || loading) return;
|
|
491
|
-
setLoading(true);
|
|
492
|
-
setError('');
|
|
493
|
-
const result = await createSpaceAction(name, description);
|
|
494
|
-
setLoading(false);
|
|
495
|
-
if (result.success) {
|
|
496
|
-
close();
|
|
497
|
-
router.refresh();
|
|
498
|
-
} else {
|
|
499
|
-
setError(result.error ?? 'Failed to create space');
|
|
500
|
-
}
|
|
501
|
-
}, [name, description, loading, close, router]);
|
|
502
|
-
|
|
503
|
-
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
504
|
-
if (e.key === 'Escape') close();
|
|
505
|
-
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleCreate(); }
|
|
506
|
-
}, [close, handleCreate]);
|
|
507
|
-
|
|
508
|
-
if (!editing) {
|
|
509
|
-
return (
|
|
510
|
-
<button
|
|
511
|
-
onClick={open}
|
|
512
|
-
aria-label={t.home.newSpace}
|
|
513
|
-
className="flex items-center justify-center gap-2 px-3.5 py-3 rounded-xl border border-dashed border-border/50 text-muted-foreground opacity-60 transition-all duration-150 hover:opacity-100 hover:border-amber-500/30 cursor-pointer"
|
|
514
|
-
>
|
|
515
|
-
<Plus size={16} />
|
|
516
|
-
<span className="text-sm font-medium">{t.home.newSpace}</span>
|
|
517
|
-
</button>
|
|
518
|
-
);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
return (
|
|
522
|
-
<div className="flex flex-col gap-2 px-3.5 py-3 rounded-xl border border-[var(--amber)] bg-[var(--amber-subtle)]" onKeyDown={handleKeyDown}>
|
|
523
|
-
<input
|
|
524
|
-
ref={inputRef}
|
|
525
|
-
type="text"
|
|
526
|
-
value={name}
|
|
527
|
-
onChange={e => { setName(e.target.value); setError(''); }}
|
|
528
|
-
placeholder={t.home.spaceName}
|
|
529
|
-
maxLength={80}
|
|
530
|
-
className="text-sm font-medium bg-transparent border-b border-border pb-1 outline-none placeholder:text-muted-foreground/50 text-foreground focus:border-[var(--amber)]"
|
|
531
|
-
/>
|
|
532
|
-
<input
|
|
533
|
-
type="text"
|
|
534
|
-
value={description}
|
|
535
|
-
onChange={e => setDescription(e.target.value)}
|
|
536
|
-
placeholder={t.home.spaceDescription}
|
|
537
|
-
maxLength={200}
|
|
538
|
-
className="text-xs bg-transparent border-b border-border/50 pb-1 outline-none placeholder:text-muted-foreground/40 text-muted-foreground focus:border-[var(--amber)]"
|
|
539
|
-
/>
|
|
540
|
-
{error && <span className="text-xs text-error">{error}</span>}
|
|
541
|
-
<div className="flex items-center gap-2 mt-1">
|
|
542
|
-
<button
|
|
543
|
-
onClick={handleCreate}
|
|
544
|
-
disabled={!name.trim() || loading}
|
|
545
|
-
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium bg-[var(--amber)] text-white transition-colors hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed"
|
|
546
|
-
>
|
|
547
|
-
{loading && <Loader2 size={12} className="animate-spin" />}
|
|
548
|
-
{t.home.createSpace}
|
|
549
|
-
</button>
|
|
550
|
-
<button
|
|
551
|
-
onClick={close}
|
|
552
|
-
className="px-3 py-1.5 rounded-lg text-xs font-medium text-muted-foreground transition-colors hover:bg-muted"
|
|
553
|
-
>
|
|
554
|
-
{t.home.cancelCreate}
|
|
555
|
-
</button>
|
|
556
|
-
</div>
|
|
557
|
-
</div>
|
|
558
|
-
);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/** Compact "+ New Space" text link shown next to "Show more" when grid is collapsed */
|
|
562
|
-
function CreateSpaceTextLink({ t }: { t: ReturnType<typeof useLocale>['t'] }) {
|
|
563
|
-
const router = useRouter();
|
|
564
|
-
const [editing, setEditing] = useState(false);
|
|
565
|
-
const [name, setName] = useState('');
|
|
566
|
-
const [loading, setLoading] = useState(false);
|
|
567
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
568
|
-
|
|
569
|
-
if (!editing) {
|
|
570
|
-
return (
|
|
571
|
-
<button
|
|
572
|
-
onClick={() => { setEditing(true); setTimeout(() => inputRef.current?.focus(), 50); }}
|
|
573
|
-
className="flex items-center gap-1 text-xs font-medium text-muted-foreground transition-colors hover:text-[var(--amber)] cursor-pointer"
|
|
574
|
-
>
|
|
575
|
-
<Plus size={11} />
|
|
576
|
-
<span>{t.home.newSpace}</span>
|
|
577
|
-
</button>
|
|
578
|
-
);
|
|
579
|
-
}
|
|
580
|
-
|
|
484
|
+
/* ── Create Space: title-bar button ── */
|
|
485
|
+
function CreateSpaceButton({ t }: { t: ReturnType<typeof useLocale>['t'] }) {
|
|
581
486
|
return (
|
|
582
|
-
<
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
e.preventDefault();
|
|
586
|
-
if (!name.trim() || loading) return;
|
|
587
|
-
setLoading(true);
|
|
588
|
-
const result = await createSpaceAction(name, '');
|
|
589
|
-
setLoading(false);
|
|
590
|
-
if (result.success) { setEditing(false); setName(''); router.refresh(); }
|
|
591
|
-
}}
|
|
487
|
+
<button
|
|
488
|
+
onClick={() => window.dispatchEvent(new Event('mindos:create-space'))}
|
|
489
|
+
className="flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium bg-[var(--amber)] text-white transition-colors hover:opacity-90 cursor-pointer"
|
|
592
490
|
>
|
|
593
|
-
<
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
value={name}
|
|
597
|
-
onChange={e => setName(e.target.value)}
|
|
598
|
-
onKeyDown={e => { if (e.key === 'Escape') { setEditing(false); setName(''); } }}
|
|
599
|
-
placeholder={t.home.spaceName}
|
|
600
|
-
maxLength={80}
|
|
601
|
-
className="text-xs bg-transparent border-b border-border pb-0.5 outline-none w-28 placeholder:text-muted-foreground/40 text-foreground focus:border-[var(--amber)]"
|
|
602
|
-
/>
|
|
603
|
-
<button
|
|
604
|
-
type="submit"
|
|
605
|
-
disabled={!name.trim() || loading}
|
|
606
|
-
className="text-xs font-medium text-[var(--amber)] disabled:opacity-40"
|
|
607
|
-
>
|
|
608
|
-
{loading ? '...' : t.home.createSpace}
|
|
609
|
-
</button>
|
|
610
|
-
</form>
|
|
491
|
+
<Plus size={12} />
|
|
492
|
+
<span>{t.home.newSpace}</span>
|
|
493
|
+
</button>
|
|
611
494
|
);
|
|
612
495
|
}
|
|
496
|
+
|
package/app/components/Panel.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import Logo from './Logo';
|
|
|
11
11
|
import SearchPanel from './panels/SearchPanel';
|
|
12
12
|
import PluginsPanel from './panels/PluginsPanel';
|
|
13
13
|
import AgentsPanel from './panels/AgentsPanel';
|
|
14
|
+
import DiscoverPanel from './panels/DiscoverPanel';
|
|
14
15
|
import RightAskPanel from './RightAskPanel';
|
|
15
16
|
import AskFab from './AskFab';
|
|
16
17
|
import SyncPopover from './panels/SyncPopover';
|
|
@@ -211,6 +212,9 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
211
212
|
<div className={`flex flex-col h-full ${lp.activePanel === 'agents' ? '' : 'hidden'}`}>
|
|
212
213
|
<AgentsPanel active={lp.activePanel === 'agents'} maximized={lp.panelMaximized} onMaximize={lp.handlePanelMaximize} />
|
|
213
214
|
</div>
|
|
215
|
+
<div className={`flex flex-col h-full ${lp.activePanel === 'discover' ? '' : 'hidden'}`}>
|
|
216
|
+
<DiscoverPanel active={lp.activePanel === 'discover'} maximized={lp.panelMaximized} onMaximize={lp.handlePanelMaximize} />
|
|
217
|
+
</div>
|
|
214
218
|
</Panel>
|
|
215
219
|
|
|
216
220
|
{/* ── Right-side Ask AI Panel ── */}
|
|
@@ -14,7 +14,7 @@ export default function ThemeToggle() {
|
|
|
14
14
|
const stored = localStorage.getItem('theme');
|
|
15
15
|
return stored ? stored === 'dark' : window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
16
16
|
},
|
|
17
|
-
() =>
|
|
17
|
+
() => document.documentElement.classList.contains('dark'),
|
|
18
18
|
);
|
|
19
19
|
|
|
20
20
|
const toggle = () => {
|
|
@@ -15,7 +15,6 @@ export default function UpdateBanner() {
|
|
|
15
15
|
const [info, setInfo] = useState<UpdateInfo | null>(null);
|
|
16
16
|
|
|
17
17
|
useEffect(() => {
|
|
18
|
-
// Don't check for updates on setup or login pages
|
|
19
18
|
if (typeof window !== 'undefined') {
|
|
20
19
|
const path = window.location.pathname;
|
|
21
20
|
if (path === '/setup' || path === '/login') return;
|
|
@@ -30,10 +29,12 @@ export default function UpdateBanner() {
|
|
|
30
29
|
if (data.latest === dismissed) return;
|
|
31
30
|
|
|
32
31
|
setInfo({ latest: data.latest, current: data.current });
|
|
32
|
+
// Broadcast for ActivityBar & Settings tab badges
|
|
33
|
+
window.dispatchEvent(new CustomEvent('mindos:update-available', { detail: { latest: data.latest } }));
|
|
33
34
|
} catch {
|
|
34
35
|
// Network error / API failure — silent
|
|
35
36
|
}
|
|
36
|
-
}, 3000);
|
|
37
|
+
}, 3000);
|
|
37
38
|
|
|
38
39
|
return () => clearTimeout(timer);
|
|
39
40
|
}, []);
|
|
@@ -43,53 +44,44 @@ export default function UpdateBanner() {
|
|
|
43
44
|
const handleDismiss = () => {
|
|
44
45
|
localStorage.setItem('mindos_update_dismissed', info.latest);
|
|
45
46
|
setInfo(null);
|
|
47
|
+
window.dispatchEvent(new Event('mindos:update-dismissed'));
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
const handleOpenUpdate = () => {
|
|
49
51
|
window.dispatchEvent(new CustomEvent('mindos:open-settings', { detail: { tab: 'update' } }));
|
|
50
|
-
// Dismiss banner once user engages with Update tab
|
|
51
52
|
localStorage.setItem('mindos_update_dismissed', info.latest);
|
|
52
53
|
setInfo(null);
|
|
54
|
+
window.dispatchEvent(new Event('mindos:update-dismissed'));
|
|
53
55
|
};
|
|
54
56
|
|
|
55
|
-
const
|
|
57
|
+
const u = t.updateBanner;
|
|
56
58
|
|
|
57
59
|
return (
|
|
58
|
-
<div
|
|
59
|
-
className="flex items-center
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<span className="font-medium" style={{ color: 'var(--amber)' }}>
|
|
64
|
-
{updateT?.newVersion
|
|
65
|
-
? updateT.newVersion(info.latest, info.current)
|
|
60
|
+
<div className="flex items-center justify-between gap-3 px-4 py-2 text-xs bg-[var(--amber-subtle)] border-b border-border">
|
|
61
|
+
<div className="flex items-center gap-2 min-w-0 flex-wrap">
|
|
62
|
+
<span className="font-medium text-[var(--amber)]">
|
|
63
|
+
{u?.newVersion
|
|
64
|
+
? u.newVersion(info.latest, info.current)
|
|
66
65
|
: `MindOS v${info.latest} available (current: v${info.current})`}
|
|
67
66
|
</span>
|
|
68
67
|
<button
|
|
69
68
|
onClick={handleOpenUpdate}
|
|
70
|
-
className="px-2 py-0.5 rounded-md text-xs font-medium text-white transition-colors hover:opacity-90"
|
|
71
|
-
style={{ background: 'var(--amber)' }}
|
|
69
|
+
className="px-2 py-0.5 rounded-md text-xs font-medium bg-[var(--amber)] text-white transition-colors hover:opacity-90"
|
|
72
70
|
>
|
|
73
|
-
{
|
|
71
|
+
{u?.updateNow ?? 'Update'}
|
|
74
72
|
</button>
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
or{' '}
|
|
84
|
-
<a href="https://github.com/GeminiLight/mindos/releases" target="_blank" rel="noopener noreferrer" className="underline hover:text-foreground transition-colors">release notes</a>
|
|
85
|
-
</>
|
|
86
|
-
)}
|
|
87
|
-
</span>
|
|
73
|
+
<a
|
|
74
|
+
href="https://github.com/GeminiLight/mindos/releases"
|
|
75
|
+
target="_blank"
|
|
76
|
+
rel="noopener noreferrer"
|
|
77
|
+
className="text-muted-foreground underline hover:text-foreground transition-colors hidden sm:inline"
|
|
78
|
+
>
|
|
79
|
+
{u?.releaseNotes ?? 'Release notes'}
|
|
80
|
+
</a>
|
|
88
81
|
</div>
|
|
89
82
|
<button
|
|
90
83
|
onClick={handleDismiss}
|
|
91
|
-
className="p-0.5 rounded hover:bg-muted transition-colors shrink-0"
|
|
92
|
-
style={{ color: 'var(--muted-foreground)' }}
|
|
84
|
+
className="p-0.5 rounded hover:bg-muted transition-colors shrink-0 text-muted-foreground"
|
|
93
85
|
title="Dismiss"
|
|
94
86
|
>
|
|
95
87
|
<X size={14} />
|