@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.
- package/README.md +42 -257
- package/README_zh.md +43 -261
- package/app/app/api/ask-sessions/route.ts +6 -4
- package/app/app/api/update-status/route.ts +19 -0
- package/app/components/CreateSpaceModal.tsx +87 -13
- package/app/components/DirPicker.tsx +2 -1
- package/app/components/GuideCard.tsx +38 -26
- package/app/components/HomeContent.tsx +6 -13
- package/app/components/ask/AskContent.tsx +10 -3
- package/app/components/ask/SessionHistory.tsx +39 -3
- package/app/components/explore/ExploreContent.tsx +50 -19
- package/app/components/explore/use-cases.ts +41 -13
- package/app/components/panels/DiscoverPanel.tsx +89 -99
- package/app/components/settings/SettingsContent.tsx +1 -1
- package/app/components/settings/UpdateTab.tsx +145 -28
- package/app/hooks/useAskSession.ts +24 -0
- package/app/lib/i18n-en.ts +25 -4
- package/app/lib/i18n-zh.ts +25 -4
- package/app/lib/utils.ts +11 -0
- package/bin/cli.js +87 -15
- package/bin/lib/startup.js +4 -0
- package/bin/lib/update-status.js +115 -0
- package/package.json +1 -1
- package/scripts/fix-postcss-deps.cjs +4 -2
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
{
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
{
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
<
|
|
249
|
-
|
|
250
|
-
{
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
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=
|
|
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
|
|
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="
|
|
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">
|
|
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 =
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
{/*
|
|
48
|
-
<div className="
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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
|
-
|
|
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: '
|
|
15
|
-
{ id: 'c2', icon: '📥', category: '
|
|
16
|
-
{ id: 'c3', icon: '🔄', category: '
|
|
17
|
-
{ id: 'c4', icon: '🔁', category: '
|
|
18
|
-
{ id: 'c5', icon: '💡', category: '
|
|
19
|
-
{ id: 'c6', icon: '🚀', category: '
|
|
20
|
-
{ id: 'c7', icon: '🔍', category: 'knowledge-
|
|
21
|
-
{ id: 'c8', icon: '🤝', category: '
|
|
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
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
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 {
|
|
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
|
-
/**
|
|
18
|
-
function
|
|
16
|
+
/** Navigation entry — clickable row linking to a page or showing coming soon */
|
|
17
|
+
function NavEntry({
|
|
19
18
|
icon,
|
|
20
19
|
title,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
badge,
|
|
21
|
+
href,
|
|
22
|
+
onClick,
|
|
24
23
|
}: {
|
|
25
24
|
icon: React.ReactNode;
|
|
26
25
|
title: string;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
badge?: React.ReactNode;
|
|
27
|
+
href?: string;
|
|
28
|
+
onClick?: () => void;
|
|
30
29
|
}) {
|
|
31
|
-
const
|
|
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
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
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:
|
|
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-
|
|
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
|
-
/**
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
{/*
|
|
121
|
-
<
|
|
122
|
-
<
|
|
123
|
-
{
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
{/*
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
140
|
+
{/* Quick try — use 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>
|