@geminilight/mindos 0.5.43 → 0.5.45

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.
@@ -5,22 +5,26 @@ import { X, Sparkles, FolderOpen, MessageCircle, RefreshCw, Check, ChevronRight
5
5
  import Link from 'next/link';
6
6
  import { useLocale } from '@/lib/LocaleContext';
7
7
  import { openAskModal } from '@/hooks/useAskModal';
8
+ import { extractEmoji, stripEmoji } from '@/lib/utils';
8
9
  import { walkthroughSteps } from './walkthrough/steps';
9
10
  import type { GuideState } from '@/lib/settings';
11
+ import type { SpaceInfo } from '@/app/page';
10
12
 
11
- const DIR_ICONS: Record<string, string> = {
12
- profile: '👤', notes: '📝', connections: '🔗',
13
- workflows: '🔄', resources: '📚', projects: '🚀',
14
- };
15
-
16
- const EMPTY_FILES = ['INSTRUCTION.md', 'README.md', 'CONFIG.json'];
13
+ interface RecentFile {
14
+ path: string;
15
+ mtime: number;
16
+ }
17
17
 
18
18
  interface GuideCardProps {
19
19
  /** Called when user clicks a file/dir to open it in FileView */
20
20
  onNavigate?: (path: string) => void;
21
+ /** Existing spaces for dynamic directory listing */
22
+ spaces?: SpaceInfo[];
23
+ /** Recent files for empty-template fallback */
24
+ recentFiles?: RecentFile[];
21
25
  }
22
26
 
23
- export default function GuideCard({ onNavigate }: GuideCardProps) {
27
+ export default function GuideCard({ onNavigate, spaces = [], recentFiles = [] }: GuideCardProps) {
24
28
  const { t } = useLocale();
25
29
  const g = t.guide;
26
30
 
@@ -228,29 +232,37 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
228
232
 
229
233
  {isEmptyTemplate ? (
230
234
  <div className="flex flex-col gap-1.5">
231
- {EMPTY_FILES.map(file => (
232
- <button key={file} onClick={() => handleFileOpen(file)}
233
- className="text-left text-xs px-3 py-2 rounded-lg border border-border text-foreground transition-colors hover:border-amber-500/30 hover:bg-muted/50">
234
- 📄 {(g.kb.emptyFiles as Record<string, string>)[file.split('.')[0].toLowerCase()] || file}
235
- </button>
236
- ))}
237
- <p className="text-xs mt-2 text-muted-foreground opacity-70">
238
- {g.kb.emptyHint}
239
- </p>
235
+ {recentFiles.length > 0 ? (
236
+ recentFiles.map(file => {
237
+ const fileName = file.path.split('/').pop() || file.path;
238
+ return (
239
+ <button key={file.path} onClick={() => handleFileOpen(file.path)}
240
+ className="text-left text-xs px-3 py-2 rounded-lg border border-border text-foreground transition-colors hover:border-amber-500/30 hover:bg-muted/50 truncate">
241
+ 📄 {fileName}
242
+ </button>
243
+ );
244
+ })
245
+ ) : (
246
+ <p className="text-xs text-muted-foreground">{g.kb.emptyHint}</p>
247
+ )}
240
248
  </div>
241
249
  ) : (
242
250
  <>
243
251
  <div className="grid grid-cols-2 sm:grid-cols-3 gap-1.5">
244
- {Object.entries(DIR_ICONS).map(([key, icon]) => (
245
- <button key={key} onClick={() => handleFileOpen(key.charAt(0).toUpperCase() + key.slice(1))}
246
- className="text-left text-xs px-3 py-2 rounded-lg border border-border text-foreground transition-colors hover:border-amber-500/30 hover:bg-muted/50">
247
- <span className="mr-1.5">{icon}</span>
248
- <span className="capitalize">{key}</span>
249
- <span className="block text-2xs mt-0.5 text-muted-foreground">
250
- {(g.kb.dirs as Record<string, string>)[key]}
251
- </span>
252
- </button>
253
- ))}
252
+ {spaces.slice(0, 6).map(s => {
253
+ const emoji = extractEmoji(s.name);
254
+ const label = stripEmoji(s.name);
255
+ return (
256
+ <button key={s.name} onClick={() => handleFileOpen(s.path)}
257
+ className="text-left text-xs px-3 py-2 rounded-lg border border-border text-foreground transition-colors hover:border-amber-500/30 hover:bg-muted/50">
258
+ <span className="mr-1.5">{emoji || '📁'}</span>
259
+ <span>{label}</span>
260
+ <span className="block text-2xs mt-0.5 text-muted-foreground">
261
+ {t.home.nFiles(s.fileCount)}
262
+ </span>
263
+ </button>
264
+ );
265
+ })}
254
266
  </div>
255
267
  <p className="text-xs mt-3 text-[var(--amber)]">
256
268
  💡 {g.kb.instructionHint}
@@ -4,7 +4,7 @@ import Link from 'next/link';
4
4
  import { FileText, Table, Clock, Sparkles, ArrowRight, FilePlus, Search, ChevronDown, Compass, Folder, Puzzle, Brain, Plus } from 'lucide-react';
5
5
  import { useState, useEffect, useMemo } from 'react';
6
6
  import { useLocale } from '@/lib/LocaleContext';
7
- import { encodePath, relativeTime } from '@/lib/utils';
7
+ import { encodePath, relativeTime, extractEmoji, stripEmoji } 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';
@@ -65,17 +65,6 @@ function groupBySpace(recent: RecentFile[], spaces: SpaceInfo[]): { groups: Spac
65
65
  return { groups, rootFiles };
66
66
  }
67
67
 
68
- /** Extract leading emoji from a directory name, e.g. "📝 Notes" → "📝" */
69
- function extractEmoji(name: string): string {
70
- const match = name.match(/^[\p{Emoji_Presentation}\p{Extended_Pictographic}]+/u);
71
- return match?.[0] ?? '';
72
- }
73
-
74
- /** Strip leading emoji+space from name for display, e.g. "📝 Notes" → "Notes" */
75
- function stripEmoji(name: string): string {
76
- return name.replace(/^[\p{Emoji_Presentation}\p{Extended_Pictographic}\s]+/u, '') || name;
77
- }
78
-
79
68
  /* ── Section Title component (shared across all three sections) ── */
80
69
  interface SectionTitleProps {
81
70
  icon: React.ReactNode;
@@ -146,7 +135,11 @@ export default function HomeContent({ recent, existingFiles, spaces, dirPaths }:
146
135
 
147
136
  return (
148
137
  <div className="content-width px-4 md:px-6 py-8 md:py-12">
149
- <GuideCard onNavigate={(path) => { window.location.href = `/view/${encodeURIComponent(path)}`; }} />
138
+ <GuideCard
139
+ onNavigate={(path) => { window.location.href = `/view/${encodeURIComponent(path)}`; }}
140
+ spaces={spaceList}
141
+ recentFiles={recent.slice(0, 5)}
142
+ />
150
143
 
151
144
  {/* ── Hero ── */}
152
145
  <div className="mb-10">
@@ -267,7 +267,7 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
267
267
  </span>
268
268
  </div>
269
269
  <div className="flex items-center gap-1">
270
- <button type="button" onClick={() => setShowHistory(v => !v)} className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title="Session history">
270
+ <button type="button" onClick={() => setShowHistory(v => !v)} aria-pressed={showHistory} className={`p-1 rounded transition-colors ${showHistory ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted'}`} title="Session history">
271
271
  <History size={iconSize} />
272
272
  </button>
273
273
  <button type="button" onClick={handleResetSession} disabled={isLoading} className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors disabled:opacity-40" title="New session">
@@ -284,7 +284,7 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
284
284
  </button>
285
285
  )}
286
286
  {onClose && (
287
- <button onClick={onClose} className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" aria-label="Close">
287
+ <button type="button" onClick={onClose} className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" aria-label="Close">
288
288
  <X size={isPanel ? iconSize : 15} />
289
289
  </button>
290
290
  )}
@@ -297,6 +297,13 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
297
297
  activeSessionId={session.activeSessionId}
298
298
  onLoad={handleLoadSession}
299
299
  onDelete={session.deleteSession}
300
+ onClearAll={session.clearAllSessions}
301
+ labels={{
302
+ title: t.ask.sessionHistory ?? 'Session History',
303
+ clearAll: t.ask.clearAll ?? 'Clear all',
304
+ confirmClear: t.ask.confirmClear ?? 'Confirm clear?',
305
+ noSessions: t.ask.noSessions ?? 'No saved sessions.',
306
+ }}
300
307
  />
301
308
  )}
302
309
 
@@ -400,7 +407,7 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
400
407
  <StopCircle size={inputIconSize} />
401
408
  </button>
402
409
  ) : (
403
- <button type="submit" disabled={!input.trim()} className="p-1.5 rounded-md disabled:opacity-40 disabled:cursor-not-allowed transition-opacity shrink-0" style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}>
410
+ <button type="submit" disabled={!input.trim() || mention.mentionQuery !== null} className="p-1.5 rounded-md disabled:opacity-40 disabled:cursor-not-allowed transition-opacity shrink-0 bg-[var(--amber)] text-[var(--amber-foreground)]">
404
411
  <Send size={isPanel ? 13 : 14} />
405
412
  </button>
406
413
  )}
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { useState, useRef, useEffect } from 'react';
3
4
  import { Trash2 } from 'lucide-react';
4
5
  import type { ChatSession } from '@/lib/types';
5
6
  import { sessionTitle } from '@/hooks/useAskSession';
@@ -9,15 +10,50 @@ interface SessionHistoryProps {
9
10
  activeSessionId: string | null;
10
11
  onLoad: (id: string) => void;
11
12
  onDelete: (id: string) => void;
13
+ onClearAll: () => void;
14
+ labels: { title: string; clearAll: string; confirmClear: string; noSessions: string };
12
15
  }
13
16
 
14
- export default function SessionHistory({ sessions, activeSessionId, onLoad, onDelete }: SessionHistoryProps) {
17
+ export default function SessionHistory({ sessions, activeSessionId, onLoad, onDelete, onClearAll, labels }: SessionHistoryProps) {
18
+ const [confirmClearAll, setConfirmClearAll] = useState(false);
19
+ const clearTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
20
+
21
+ // Cleanup timer on unmount
22
+ useEffect(() => () => { if (clearTimerRef.current) clearTimeout(clearTimerRef.current); }, []);
23
+
24
+ const handleClearAll = () => {
25
+ if (!confirmClearAll) {
26
+ setConfirmClearAll(true);
27
+ if (clearTimerRef.current) clearTimeout(clearTimerRef.current);
28
+ clearTimerRef.current = setTimeout(() => setConfirmClearAll(false), 3000);
29
+ return;
30
+ }
31
+ if (clearTimerRef.current) clearTimeout(clearTimerRef.current);
32
+ onClearAll();
33
+ setConfirmClearAll(false);
34
+ };
35
+
15
36
  return (
16
37
  <div className="border-b border-border px-4 py-2.5 max-h-[190px] overflow-y-auto">
17
- <div className="text-xs text-muted-foreground mb-2">Session History</div>
38
+ <div className="flex items-center justify-between mb-2">
39
+ <span className="text-xs text-muted-foreground">{labels.title}</span>
40
+ {sessions.length > 1 && (
41
+ <button
42
+ type="button"
43
+ onClick={handleClearAll}
44
+ className={`text-2xs px-1.5 py-0.5 rounded transition-colors ${
45
+ confirmClearAll
46
+ ? 'bg-error/10 text-error font-medium'
47
+ : 'text-muted-foreground hover:text-error hover:bg-muted'
48
+ }`}
49
+ >
50
+ {confirmClearAll ? labels.confirmClear : labels.clearAll}
51
+ </button>
52
+ )}
53
+ </div>
18
54
  <div className="flex flex-col gap-1.5">
19
55
  {sessions.length === 0 && (
20
- <div className="text-xs text-muted-foreground/70">No saved sessions.</div>
56
+ <div className="text-xs text-muted-foreground/70">{labels.noSessions}</div>
21
57
  )}
22
58
  {sessions.map((s) => (
23
59
  <div key={s.id} className="flex items-center gap-1.5">
@@ -2,17 +2,20 @@
2
2
 
3
3
  import { useState } from 'react';
4
4
  import { useLocale } from '@/lib/LocaleContext';
5
- import { useCases, categories, type UseCaseCategory } from './use-cases';
5
+ import { useCases, categories, scenarios, type UseCaseCategory, type UseCaseScenario } from './use-cases';
6
6
  import UseCaseCard from './UseCaseCard';
7
7
 
8
8
  export default function ExploreContent() {
9
9
  const { t } = useLocale();
10
10
  const e = t.explore;
11
11
  const [activeCategory, setActiveCategory] = useState<UseCaseCategory | 'all'>('all');
12
+ const [activeScenario, setActiveScenario] = useState<UseCaseScenario | 'all'>('all');
12
13
 
13
- const filtered = activeCategory === 'all'
14
- ? useCases
15
- : useCases.filter(uc => uc.category === activeCategory);
14
+ const filtered = useCases.filter(uc => {
15
+ if (activeCategory !== 'all' && uc.category !== activeCategory) return false;
16
+ if (activeScenario !== 'all' && uc.scenario !== activeScenario) return false;
17
+ return true;
18
+ });
16
19
 
17
20
  /** Type-safe lookup for use case i18n data by id */
18
21
  const getUseCaseText = (id: string): { title: string; desc: string; prompt: string } | undefined => {
@@ -44,21 +47,42 @@ export default function ExploreContent() {
44
47
  </p>
45
48
  </div>
46
49
 
47
- {/* Category tabs */}
48
- <div className="flex flex-wrap gap-2 mb-6" style={{ paddingLeft: '1rem' }}>
49
- <CategoryChip
50
- label={e.all}
51
- active={activeCategory === 'all'}
52
- onClick={() => setActiveCategory('all')}
53
- />
54
- {categories.map(cat => (
55
- <CategoryChip
56
- key={cat}
57
- label={(e.categories as Record<string, string>)[cat]}
58
- active={activeCategory === cat}
59
- onClick={() => setActiveCategory(cat)}
50
+ {/* Dual-axis filter */}
51
+ <div className="space-y-3 mb-6" style={{ paddingLeft: '1rem' }}>
52
+ {/* Capability axis */}
53
+ <div className="flex flex-wrap items-center gap-2">
54
+ <span className="text-2xs text-muted-foreground uppercase tracking-wider font-medium w-16 shrink-0">{e.byCapability}</span>
55
+ <FilterChip
56
+ label={e.all}
57
+ active={activeCategory === 'all'}
58
+ onClick={() => setActiveCategory('all')}
60
59
  />
61
- ))}
60
+ {categories.map(cat => (
61
+ <FilterChip
62
+ key={cat}
63
+ label={(e.categories as Record<string, string>)[cat]}
64
+ active={activeCategory === cat}
65
+ onClick={() => setActiveCategory(cat)}
66
+ />
67
+ ))}
68
+ </div>
69
+ {/* Scenario axis */}
70
+ <div className="flex flex-wrap items-center gap-2">
71
+ <span className="text-2xs text-muted-foreground uppercase tracking-wider font-medium w-16 shrink-0">{e.byScenario}</span>
72
+ <FilterChip
73
+ label={e.all}
74
+ active={activeScenario === 'all'}
75
+ onClick={() => setActiveScenario('all')}
76
+ />
77
+ {scenarios.map(sc => (
78
+ <FilterChip
79
+ key={sc}
80
+ label={(e.scenarios as Record<string, string>)[sc]}
81
+ active={activeScenario === sc}
82
+ onClick={() => setActiveScenario(sc)}
83
+ />
84
+ ))}
85
+ </div>
62
86
  </div>
63
87
 
64
88
  {/* Card grid */}
@@ -78,11 +102,18 @@ export default function ExploreContent() {
78
102
  );
79
103
  })}
80
104
  </div>
105
+
106
+ {/* Empty state */}
107
+ {filtered.length === 0 && (
108
+ <p className="text-sm text-muted-foreground text-center py-12" style={{ paddingLeft: '1rem' }}>
109
+ No use cases match the current filters.
110
+ </p>
111
+ )}
81
112
  </div>
82
113
  );
83
114
  }
84
115
 
85
- function CategoryChip({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) {
116
+ function FilterChip({ label, active, onClick }: { label: string; active: boolean; onClick: () => void }) {
86
117
  return (
87
118
  <button
88
119
  onClick={onClick}
@@ -1,30 +1,58 @@
1
- export type UseCaseCategory = 'getting-started' | 'cross-agent' | 'knowledge-evolution' | 'advanced';
1
+ /** Capability axis maps to product pillars */
2
+ export type UseCaseCategory = 'knowledge-management' | 'memory-sync' | 'auto-execute' | 'experience-evolution' | 'human-insights' | 'audit-control';
3
+
4
+ /** Scenario axis — maps to user journey phase */
5
+ export type UseCaseScenario = 'first-day' | 'daily' | 'project' | 'advanced';
2
6
 
3
7
  export interface UseCase {
4
8
  id: string;
5
9
  icon: string;
6
10
  category: UseCaseCategory;
11
+ scenario: UseCaseScenario;
7
12
  }
8
13
 
9
14
  /**
10
15
  * C1-C9 use case definitions.
11
16
  * All display text (title, description, prompt) comes from i18n — this file is structure only.
17
+ *
18
+ * Category (capability axis):
19
+ * knowledge-management — Inject, organize, and maintain knowledge
20
+ * memory-sync — Record once, all Agents know
21
+ * auto-execute — One sentence, auto-execute
22
+ * experience-evolution — Gets smarter with use
23
+ * human-insights — Understand and manage relationships
24
+ * audit-control — You have final say
25
+ *
26
+ * Scenario (journey axis):
27
+ * first-day — Onboarding / first-time tasks
28
+ * daily — Everyday workflows
29
+ * project — Project-scoped work
30
+ * advanced — Power-user patterns
12
31
  */
13
32
  export const useCases: UseCase[] = [
14
- { id: 'c1', icon: '👤', category: 'getting-started' },
15
- { id: 'c2', icon: '📥', category: 'getting-started' },
16
- { id: 'c3', icon: '🔄', category: 'cross-agent' },
17
- { id: 'c4', icon: '🔁', category: 'knowledge-evolution' },
18
- { id: 'c5', icon: '💡', category: 'cross-agent' },
19
- { id: 'c6', icon: '🚀', category: 'cross-agent' },
20
- { id: 'c7', icon: '🔍', category: 'knowledge-evolution' },
21
- { id: 'c8', icon: '🤝', category: 'knowledge-evolution' },
22
- { id: 'c9', icon: '🛡️', category: 'advanced' },
33
+ { id: 'c1', icon: '👤', category: 'memory-sync', scenario: 'first-day' },
34
+ { id: 'c2', icon: '📥', category: 'knowledge-management', scenario: 'daily' },
35
+ { id: 'c3', icon: '🔄', category: 'memory-sync', scenario: 'project' },
36
+ { id: 'c4', icon: '🔁', category: 'experience-evolution', scenario: 'daily' },
37
+ { id: 'c5', icon: '💡', category: 'auto-execute', scenario: 'daily' },
38
+ { id: 'c6', icon: '🚀', category: 'auto-execute', scenario: 'project' },
39
+ { id: 'c7', icon: '🔍', category: 'knowledge-management', scenario: 'project' },
40
+ { id: 'c8', icon: '🤝', category: 'human-insights', scenario: 'daily' },
41
+ { id: 'c9', icon: '🛡️', category: 'audit-control', scenario: 'advanced' },
23
42
  ];
24
43
 
25
44
  export const categories: UseCaseCategory[] = [
26
- 'getting-started',
27
- 'cross-agent',
28
- 'knowledge-evolution',
45
+ 'knowledge-management',
46
+ 'memory-sync',
47
+ 'auto-execute',
48
+ 'experience-evolution',
49
+ 'human-insights',
50
+ 'audit-control',
51
+ ];
52
+
53
+ export const scenarios: UseCaseScenario[] = [
54
+ 'first-day',
55
+ 'daily',
56
+ 'project',
29
57
  'advanced',
30
58
  ];
@@ -1,8 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { useState } from 'react';
4
3
  import Link from 'next/link';
5
- import { ChevronDown, ChevronRight, ExternalLink, Blocks, Zap } from 'lucide-react';
4
+ import { Lightbulb, Blocks, Zap, LayoutTemplate, ChevronRight, User, Download, RefreshCw, Repeat, Rocket, Search, Handshake, ShieldCheck } from 'lucide-react';
6
5
  import PanelHeader from './PanelHeader';
7
6
  import { useLocale } from '@/lib/LocaleContext';
8
7
  import { useCases } from '@/components/explore/use-cases';
@@ -14,56 +13,61 @@ interface DiscoverPanelProps {
14
13
  onMaximize?: () => void;
15
14
  }
16
15
 
17
- /** Collapsible section with count badge */
18
- function Section({
16
+ /** Navigation entry clickable row linking to a page or showing coming soon */
17
+ function NavEntry({
19
18
  icon,
20
19
  title,
21
- count,
22
- defaultOpen = true,
23
- children,
20
+ badge,
21
+ href,
22
+ onClick,
24
23
  }: {
25
24
  icon: React.ReactNode;
26
25
  title: string;
27
- count?: number;
28
- defaultOpen?: boolean;
29
- children: React.ReactNode;
26
+ badge?: React.ReactNode;
27
+ href?: string;
28
+ onClick?: () => void;
30
29
  }) {
31
- const [open, setOpen] = useState(defaultOpen);
30
+ const content = (
31
+ <>
32
+ <span className="flex items-center justify-center w-7 h-7 rounded-md bg-muted shrink-0">{icon}</span>
33
+ <span className="text-sm font-medium text-foreground flex-1">{title}</span>
34
+ {badge}
35
+ <ChevronRight size={14} className="text-muted-foreground shrink-0" />
36
+ </>
37
+ );
38
+
39
+ const className = "flex items-center gap-3 px-4 py-2.5 hover:bg-muted/50 transition-colors cursor-pointer";
40
+
41
+ if (href) {
42
+ return <Link href={href} className={className}>{content}</Link>;
43
+ }
44
+ return <button onClick={onClick} className={`${className} w-full`}>{content}</button>;
45
+ }
46
+
47
+ /** Coming soon badge */
48
+ function ComingSoonBadge({ label }: { label: string }) {
32
49
  return (
33
- <div>
34
- <button
35
- onClick={() => setOpen(v => !v)}
36
- className="flex items-center gap-2 w-full px-4 py-2 text-xs font-medium text-muted-foreground uppercase tracking-wider hover:text-foreground transition-colors"
37
- >
38
- {open ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
39
- <span className="flex items-center gap-1.5">
40
- {icon}
41
- {title}
42
- </span>
43
- {count !== undefined && (
44
- <span className="ml-auto text-2xs tabular-nums opacity-60">{count}</span>
45
- )}
46
- </button>
47
- {open && <div className="pb-2">{children}</div>}
48
- </div>
50
+ <span className="text-2xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground shrink-0">
51
+ {label}
52
+ </span>
49
53
  );
50
54
  }
51
55
 
52
- /** Compact use case row for panel display */
56
+ /** Compact use case row */
53
57
  function UseCaseRow({
54
58
  icon,
55
59
  title,
56
60
  prompt,
57
61
  tryLabel,
58
62
  }: {
59
- icon: string;
63
+ icon: React.ReactNode;
60
64
  title: string;
61
65
  prompt: string;
62
66
  tryLabel: string;
63
67
  }) {
64
68
  return (
65
- <div className="group flex items-center gap-2 px-4 py-1.5 hover:bg-muted/50 transition-colors rounded-sm mx-1">
66
- <span className="text-sm leading-none shrink-0" suppressHydrationWarning>{icon}</span>
69
+ <div className="group flex items-center gap-2.5 px-4 py-1.5 hover:bg-muted/50 transition-colors rounded-sm mx-1">
70
+ <span className="text-muted-foreground shrink-0">{icon}</span>
67
71
  <span className="text-xs text-foreground truncate flex-1">{title}</span>
68
72
  <button
69
73
  onClick={() => openAskModal(prompt, 'user')}
@@ -75,29 +79,18 @@ function UseCaseRow({
75
79
  );
76
80
  }
77
81
 
78
- /** Coming soon placeholder */
79
- function ComingSoonSection({
80
- icon,
81
- title,
82
- description,
83
- comingSoonLabel,
84
- }: {
85
- icon: React.ReactNode;
86
- title: string;
87
- description: string;
88
- comingSoonLabel: string;
89
- }) {
90
- return (
91
- <Section icon={icon} title={title} defaultOpen={false}>
92
- <div className="px-4 py-3 mx-1">
93
- <p className="text-xs text-muted-foreground leading-relaxed">{description}</p>
94
- <span className="inline-block mt-2 text-2xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground">
95
- {comingSoonLabel}
96
- </span>
97
- </div>
98
- </Section>
99
- );
100
- }
82
+ /** Map use case id → lucide icon */
83
+ const useCaseIcons: Record<string, React.ReactNode> = {
84
+ c1: <User size={12} />, // Inject Identity
85
+ c2: <Download size={12} />, // Save Information
86
+ c3: <RefreshCw size={12} />, // Cross-Agent Handoff
87
+ c4: <Repeat size={12} />, // Experience → SOP
88
+ c5: <Lightbulb size={12} />, // Capture Ideas
89
+ c6: <Rocket size={12} />, // Project Cold Start
90
+ c7: <Search size={12} />, // Research & Archive
91
+ c8: <Handshake size={12} />, // Network Management
92
+ c9: <ShieldCheck size={12} />, // Audit & Correct
93
+ };
101
94
 
102
95
  export default function DiscoverPanel({ active, maximized, onMaximize }: DiscoverPanelProps) {
103
96
  const { t } = useLocale();
@@ -117,54 +110,51 @@ export default function DiscoverPanel({ active, maximized, onMaximize }: Discove
117
110
  <div className={`flex flex-col h-full ${active ? '' : 'hidden'}`}>
118
111
  <PanelHeader title={d.title} maximized={maximized} onMaximize={onMaximize} />
119
112
  <div className="flex-1 overflow-y-auto min-h-0">
120
- {/* Use Cases */}
121
- <Section icon={<span className="text-xs" suppressHydrationWarning>🎯</span>} title={d.useCases} count={useCases.length}>
122
- <div className="flex flex-col">
123
- {useCases.map(uc => {
124
- const data = getUseCaseText(uc.id);
125
- if (!data) return null;
126
- return (
127
- <UseCaseRow
128
- key={uc.id}
129
- icon={uc.icon}
130
- title={data.title}
131
- prompt={data.prompt}
132
- tryLabel={d.tryIt}
133
- />
134
- );
135
- })}
136
- </div>
137
- </Section>
138
-
139
- <div className="mx-4 border-t border-border" />
140
-
141
- {/* Plugin Market — Coming Soon */}
142
- <ComingSoonSection
143
- icon={<Blocks size={11} />}
144
- title={d.pluginMarket}
145
- description={d.pluginMarketDesc}
146
- comingSoonLabel={d.comingSoon}
147
- />
113
+ {/* Navigation entries */}
114
+ <div className="py-2">
115
+ <NavEntry
116
+ icon={<Lightbulb size={14} className="text-[var(--amber)]" />}
117
+ title={d.useCases}
118
+ badge={<span className="text-2xs tabular-nums text-muted-foreground">{useCases.length}</span>}
119
+ href="/explore"
120
+ />
121
+ <NavEntry
122
+ icon={<Blocks size={14} className="text-muted-foreground" />}
123
+ title={d.pluginMarket}
124
+ badge={<ComingSoonBadge label={d.comingSoon} />}
125
+ />
126
+ <NavEntry
127
+ icon={<Zap size={14} className="text-muted-foreground" />}
128
+ title={d.skillMarket}
129
+ badge={<ComingSoonBadge label={d.comingSoon} />}
130
+ />
131
+ <NavEntry
132
+ icon={<LayoutTemplate size={14} className="text-muted-foreground" />}
133
+ title={d.spaceTemplates}
134
+ badge={<ComingSoonBadge label={d.comingSoon} />}
135
+ />
136
+ </div>
148
137
 
149
138
  <div className="mx-4 border-t border-border" />
150
139
 
151
- {/* Skill MarketComing Soon */}
152
- <ComingSoonSection
153
- icon={<Zap size={11} />}
154
- title={d.skillMarket}
155
- description={d.skillMarketDesc}
156
- comingSoonLabel={d.comingSoon}
157
- />
158
-
159
- {/* View all link */}
160
- <div className="px-4 py-3 mt-2">
161
- <Link
162
- href="/explore"
163
- className="inline-flex items-center gap-1.5 text-xs text-[var(--amber)] hover:opacity-80 transition-opacity"
164
- >
165
- {d.viewAll}
166
- <ExternalLink size={11} />
167
- </Link>
140
+ {/* Quick tryuse case list */}
141
+ <div className="py-2">
142
+ <div className="px-4 py-1.5">
143
+ <span className="text-2xs font-medium text-muted-foreground uppercase tracking-wider">{d.useCases}</span>
144
+ </div>
145
+ {useCases.map(uc => {
146
+ const data = getUseCaseText(uc.id);
147
+ if (!data) return null;
148
+ return (
149
+ <UseCaseRow
150
+ key={uc.id}
151
+ icon={useCaseIcons[uc.id] || <Lightbulb size={12} />}
152
+ title={data.title}
153
+ prompt={data.prompt}
154
+ tryLabel={d.tryIt}
155
+ />
156
+ );
157
+ })}
168
158
  </div>
169
159
  </div>
170
160
  </div>