@geminilight/mindos 0.5.34 → 0.5.36
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/globals.css +2 -0
- package/app/components/AskFab.tsx +1 -1
- package/app/components/GuideCard.tsx +33 -90
- package/app/components/HomeContent.tsx +30 -54
- package/app/components/OnboardingView.tsx +35 -41
- package/app/lib/i18n-en.ts +2 -0
- package/app/lib/i18n-zh.ts +2 -0
- package/app/next-env.d.ts +1 -1
- package/package.json +1 -1
- package/app/components/WelcomeBanner.tsx +0 -63
package/app/app/globals.css
CHANGED
|
@@ -76,6 +76,7 @@ body {
|
|
|
76
76
|
--radius: 0.5rem;
|
|
77
77
|
--amber: #c8873a;
|
|
78
78
|
--amber-dim: rgba(200, 135, 58, 0.12);
|
|
79
|
+
--amber-subtle: rgba(200, 135, 30, 0.08);
|
|
79
80
|
--amber-foreground: #131210;
|
|
80
81
|
--success: #7aad80;
|
|
81
82
|
--error: #c85050;
|
|
@@ -110,6 +111,7 @@ body {
|
|
|
110
111
|
--ring: var(--amber);
|
|
111
112
|
--amber: #d4954a;
|
|
112
113
|
--amber-dim: rgba(212, 149, 74, 0.12);
|
|
114
|
+
--amber-subtle: rgba(212, 149, 74, 0.10);
|
|
113
115
|
--amber-foreground: #131210;
|
|
114
116
|
--success: #7aad80;
|
|
115
117
|
--error: #c85050;
|
|
@@ -27,7 +27,7 @@ export default function AskFab({ onToggle, askPanelOpen }: AskFabProps) {
|
|
|
27
27
|
${askPanelOpen ? 'opacity-0 pointer-events-none translate-y-2' : 'opacity-100 translate-y-0'}
|
|
28
28
|
`}
|
|
29
29
|
style={{
|
|
30
|
-
background: 'linear-gradient(135deg,
|
|
30
|
+
background: 'linear-gradient(135deg, var(--amber), color-mix(in srgb, var(--amber) 80%, white))',
|
|
31
31
|
}}
|
|
32
32
|
title="MindOS Agent (⌘/)"
|
|
33
33
|
aria-label="MindOS Agent"
|
|
@@ -39,7 +39,6 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
|
|
|
39
39
|
setGuideState(gs);
|
|
40
40
|
if (gs.step1Done) setBrowsedCount(1);
|
|
41
41
|
} else {
|
|
42
|
-
// Guide inactive or dismissed — clear local state
|
|
43
42
|
setGuideState(null);
|
|
44
43
|
}
|
|
45
44
|
})
|
|
@@ -49,13 +48,9 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
|
|
|
49
48
|
useEffect(() => {
|
|
50
49
|
fetchGuideState();
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
const handleFirstVisit = () => {
|
|
54
|
-
setIsFirstVisit(true);
|
|
55
|
-
};
|
|
51
|
+
const handleFirstVisit = () => { setIsFirstVisit(true); };
|
|
56
52
|
window.addEventListener('mindos:first-visit', handleFirstVisit);
|
|
57
53
|
|
|
58
|
-
// Re-fetch when guide state is updated (e.g. after AskFab patches askedAI)
|
|
59
54
|
const handleGuideUpdate = () => fetchGuideState();
|
|
60
55
|
window.addEventListener('focus', handleGuideUpdate);
|
|
61
56
|
window.addEventListener('guide-state-updated', handleGuideUpdate);
|
|
@@ -66,14 +61,12 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
|
|
|
66
61
|
};
|
|
67
62
|
}, [fetchGuideState]);
|
|
68
63
|
|
|
69
|
-
// Auto-expand KB task on first visit
|
|
70
64
|
useEffect(() => {
|
|
71
65
|
if (isFirstVisit && guideState && !guideState.step1Done) {
|
|
72
66
|
setExpanded('kb');
|
|
73
67
|
}
|
|
74
68
|
}, [isFirstVisit, guideState]);
|
|
75
69
|
|
|
76
|
-
// Patch guideState to backend
|
|
77
70
|
const patchGuide = useCallback((patch: Partial<GuideState>) => {
|
|
78
71
|
setGuideState(prev => prev ? { ...prev, ...patch } : prev);
|
|
79
72
|
fetch('/api/setup', {
|
|
@@ -93,7 +86,6 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
|
|
|
93
86
|
if (browsedCount === 0) {
|
|
94
87
|
setBrowsedCount(1);
|
|
95
88
|
patchGuide({ step1Done: true });
|
|
96
|
-
// Collapse after a beat
|
|
97
89
|
setTimeout(() => setExpanded(null), 300);
|
|
98
90
|
}
|
|
99
91
|
}, [browsedCount, patchGuide, onNavigate]);
|
|
@@ -109,8 +101,6 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
|
|
|
109
101
|
const isEmpty = gs?.template === 'empty';
|
|
110
102
|
const prompt = isEmpty ? g.ai.promptEmpty : g.ai.prompt;
|
|
111
103
|
openAskModal(prompt, 'guide');
|
|
112
|
-
// Don't optimistically set askedAI here — wait until user actually sends a message
|
|
113
|
-
// AskFab.onFirstMessage will PATCH askedAI:true
|
|
114
104
|
}, [guideState, g]);
|
|
115
105
|
|
|
116
106
|
const handleNextStepClick = useCallback(() => {
|
|
@@ -119,13 +109,11 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
|
|
|
119
109
|
const steps = g.done.steps;
|
|
120
110
|
if (idx < steps.length) {
|
|
121
111
|
openAskModal(steps[idx].prompt, 'guide-next');
|
|
122
|
-
// Optimistic local update — AskFab will persist to backend on first message
|
|
123
112
|
patchGuide({ nextStepIndex: idx + 1 });
|
|
124
113
|
}
|
|
125
114
|
}, [guideState, g, patchGuide]);
|
|
126
115
|
|
|
127
116
|
const handleSyncClick = useCallback(() => {
|
|
128
|
-
// Dispatch ⌘, to open Settings modal
|
|
129
117
|
window.dispatchEvent(new KeyboardEvent('keydown', { key: ',', metaKey: true, bubbles: true }));
|
|
130
118
|
}, []);
|
|
131
119
|
|
|
@@ -144,10 +132,8 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
|
|
|
144
132
|
return () => { if (autoDismissRef.current) clearTimeout(autoDismissRef.current); };
|
|
145
133
|
}, [allNextDone_, handleDismiss]);
|
|
146
134
|
|
|
147
|
-
// Don't render if no active guide
|
|
148
135
|
if (!guideState) return null;
|
|
149
136
|
|
|
150
|
-
// Hide GuideCard while walkthrough is active
|
|
151
137
|
const walkthroughActive = guideState.walkthroughStep !== undefined
|
|
152
138
|
&& guideState.walkthroughStep >= 0
|
|
153
139
|
&& guideState.walkthroughStep < walkthroughSteps.length
|
|
@@ -165,21 +151,18 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
|
|
|
165
151
|
// After all next-steps done → final state (auto-dismisses after 8s)
|
|
166
152
|
if (allDone && allNextDone) {
|
|
167
153
|
return (
|
|
168
|
-
<div className="mb-6 rounded-xl border px-5 py-4 flex items-center gap-3 animate-in fade-in duration-300"
|
|
169
|
-
|
|
170
|
-
<
|
|
171
|
-
<span className="text-sm font-semibold flex-1" style={{ color: 'var(--foreground)' }}>
|
|
154
|
+
<div className="mb-6 rounded-xl border border-[var(--amber)] px-5 py-4 flex items-center gap-3 animate-in fade-in duration-300 bg-[var(--amber-subtle)]">
|
|
155
|
+
<Sparkles size={16} className="animate-spin-slow text-[var(--amber)]" />
|
|
156
|
+
<span className="text-sm font-semibold flex-1 text-foreground">
|
|
172
157
|
✨ {g.done.titleFinal}
|
|
173
158
|
</span>
|
|
174
159
|
<Link
|
|
175
160
|
href="/explore"
|
|
176
|
-
className="text-xs font-medium transition-colors hover:opacity-80"
|
|
177
|
-
style={{ color: 'var(--amber)' }}
|
|
161
|
+
className="text-xs font-medium text-[var(--amber)] transition-colors hover:opacity-80"
|
|
178
162
|
>
|
|
179
163
|
{t.walkthrough.exploreCta}
|
|
180
164
|
</Link>
|
|
181
|
-
<button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors"
|
|
182
|
-
style={{ color: 'var(--muted-foreground)' }}>
|
|
165
|
+
<button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors text-muted-foreground">
|
|
183
166
|
<X size={14} />
|
|
184
167
|
</button>
|
|
185
168
|
</div>
|
|
@@ -190,23 +173,20 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
|
|
|
190
173
|
if (allDone) {
|
|
191
174
|
const step = nextSteps[nextIdx];
|
|
192
175
|
return (
|
|
193
|
-
<div className="mb-6 rounded-xl border px-5 py-4 animate-in fade-in duration-300"
|
|
194
|
-
style={{ background: 'var(--amber-subtle, rgba(200,135,30,0.08))', borderColor: 'var(--amber)' }}>
|
|
176
|
+
<div className="mb-6 rounded-xl border border-[var(--amber)] px-5 py-4 animate-in fade-in duration-300 bg-[var(--amber-subtle)]">
|
|
195
177
|
<div className="flex items-center gap-3">
|
|
196
|
-
<Sparkles size={16}
|
|
197
|
-
<span className="text-sm font-semibold flex-1
|
|
178
|
+
<Sparkles size={16} className="text-[var(--amber)]" />
|
|
179
|
+
<span className="text-sm font-semibold flex-1 text-foreground">
|
|
198
180
|
🎉 {g.done.title}
|
|
199
181
|
</span>
|
|
200
|
-
<button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors"
|
|
201
|
-
style={{ color: 'var(--muted-foreground)' }}>
|
|
182
|
+
<button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors text-muted-foreground">
|
|
202
183
|
<X size={14} />
|
|
203
184
|
</button>
|
|
204
185
|
</div>
|
|
205
186
|
{step && (
|
|
206
187
|
<button
|
|
207
188
|
onClick={handleNextStepClick}
|
|
208
|
-
className="mt-3 flex items-center gap-2 text-sm transition-colors hover:opacity-80 cursor-pointer animate-in fade-in slide-in-from-left-2 duration-300"
|
|
209
|
-
style={{ color: 'var(--amber)' }}
|
|
189
|
+
className="mt-3 flex items-center gap-2 text-sm text-[var(--amber)] transition-colors hover:opacity-80 cursor-pointer animate-in fade-in slide-in-from-left-2 duration-300"
|
|
210
190
|
>
|
|
211
191
|
<ChevronRight size={14} />
|
|
212
192
|
<span>{step.hint}</span>
|
|
@@ -218,60 +198,31 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
|
|
|
218
198
|
|
|
219
199
|
// Main guide card with 3 tasks
|
|
220
200
|
return (
|
|
221
|
-
<div className="mb-6 rounded-xl border overflow-hidden"
|
|
222
|
-
style={{ background: 'var(--amber-subtle, rgba(200,135,30,0.08))', borderColor: 'var(--amber)' }}>
|
|
201
|
+
<div className="mb-6 rounded-xl border border-[var(--amber)] overflow-hidden bg-[var(--amber-subtle)]">
|
|
223
202
|
|
|
224
203
|
{/* Header */}
|
|
225
204
|
<div className="flex items-center gap-3 px-5 pt-4 pb-2">
|
|
226
|
-
<Sparkles size={16}
|
|
227
|
-
<span className="text-sm font-semibold flex-1 font-display
|
|
205
|
+
<Sparkles size={16} className="text-[var(--amber)]" />
|
|
206
|
+
<span className="text-sm font-semibold flex-1 font-display text-foreground">
|
|
228
207
|
{g.title}
|
|
229
208
|
</span>
|
|
230
|
-
<button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors"
|
|
231
|
-
style={{ color: 'var(--muted-foreground)' }}>
|
|
209
|
+
<button onClick={handleDismiss} className="p-1 rounded hover:bg-muted transition-colors text-muted-foreground">
|
|
232
210
|
<X size={14} />
|
|
233
211
|
</button>
|
|
234
212
|
</div>
|
|
235
213
|
|
|
236
214
|
{/* Task cards */}
|
|
237
215
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2 px-5 py-3">
|
|
238
|
-
{
|
|
239
|
-
<TaskCard
|
|
240
|
-
|
|
241
|
-
title={g.kb.title}
|
|
242
|
-
cta={g.kb.cta}
|
|
243
|
-
done={step1Done}
|
|
244
|
-
active={expanded === 'kb'}
|
|
245
|
-
onClick={() => step1Done ? null : setExpanded(expanded === 'kb' ? null : 'kb')}
|
|
246
|
-
/>
|
|
247
|
-
{/* ② Chat with AI */}
|
|
248
|
-
<TaskCard
|
|
249
|
-
icon={<MessageCircle size={16} />}
|
|
250
|
-
title={g.ai.title}
|
|
251
|
-
cta={g.ai.cta}
|
|
252
|
-
done={step2Done}
|
|
253
|
-
active={expanded === 'ai'}
|
|
254
|
-
onClick={() => {
|
|
255
|
-
if (!step2Done) handleStartAI();
|
|
256
|
-
}}
|
|
257
|
-
/>
|
|
258
|
-
{/* ③ Sync (optional) */}
|
|
259
|
-
<TaskCard
|
|
260
|
-
icon={<RefreshCw size={16} />}
|
|
261
|
-
title={g.sync.title}
|
|
262
|
-
cta={g.sync.cta}
|
|
263
|
-
done={false}
|
|
264
|
-
optional={g.sync.optional}
|
|
265
|
-
active={false}
|
|
266
|
-
onClick={handleSyncClick}
|
|
267
|
-
/>
|
|
216
|
+
<TaskCard icon={<FolderOpen size={16} />} title={g.kb.title} cta={g.kb.cta} done={step1Done} active={expanded === 'kb'} onClick={() => step1Done ? null : setExpanded(expanded === 'kb' ? null : 'kb')} />
|
|
217
|
+
<TaskCard icon={<MessageCircle size={16} />} title={g.ai.title} cta={g.ai.cta} done={step2Done} active={expanded === 'ai'} onClick={() => { if (!step2Done) handleStartAI(); }} />
|
|
218
|
+
<TaskCard icon={<RefreshCw size={16} />} title={g.sync.title} cta={g.sync.cta} done={false} optional={g.sync.optional} active={false} onClick={handleSyncClick} />
|
|
268
219
|
</div>
|
|
269
220
|
|
|
270
221
|
{/* Expanded content: Explore KB */}
|
|
271
222
|
{expanded === 'kb' && !step1Done && (
|
|
272
223
|
<div className="px-5 pb-4 animate-in slide-in-from-top-2 duration-200">
|
|
273
|
-
<div className="rounded-lg border p-4"
|
|
274
|
-
<p className="text-xs mb-3
|
|
224
|
+
<div className="rounded-lg border border-border bg-card p-4">
|
|
225
|
+
<p className="text-xs mb-3 text-muted-foreground">
|
|
275
226
|
{isEmptyTemplate ? g.kb.emptyDesc : g.kb.fullDesc}
|
|
276
227
|
</p>
|
|
277
228
|
|
|
@@ -279,12 +230,11 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
|
|
|
279
230
|
<div className="flex flex-col gap-1.5">
|
|
280
231
|
{EMPTY_FILES.map(file => (
|
|
281
232
|
<button key={file} onClick={() => handleFileOpen(file)}
|
|
282
|
-
className="text-left text-xs px-3 py-2 rounded-lg border transition-colors hover:border-amber-500/30 hover:bg-muted/50"
|
|
283
|
-
style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}>
|
|
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">
|
|
284
234
|
📄 {(g.kb.emptyFiles as Record<string, string>)[file.split('.')[0].toLowerCase()] || file}
|
|
285
235
|
</button>
|
|
286
236
|
))}
|
|
287
|
-
<p className="text-xs mt-2
|
|
237
|
+
<p className="text-xs mt-2 text-muted-foreground opacity-70">
|
|
288
238
|
{g.kb.emptyHint}
|
|
289
239
|
</p>
|
|
290
240
|
</div>
|
|
@@ -293,29 +243,27 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
|
|
|
293
243
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-1.5">
|
|
294
244
|
{Object.entries(DIR_ICONS).map(([key, icon]) => (
|
|
295
245
|
<button key={key} onClick={() => handleFileOpen(key.charAt(0).toUpperCase() + key.slice(1))}
|
|
296
|
-
className="text-left text-xs px-3 py-2 rounded-lg border transition-colors hover:border-amber-500/30 hover:bg-muted/50"
|
|
297
|
-
style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}>
|
|
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">
|
|
298
247
|
<span className="mr-1.5">{icon}</span>
|
|
299
248
|
<span className="capitalize">{key}</span>
|
|
300
|
-
<span className="block text-2xs mt-0.5
|
|
249
|
+
<span className="block text-2xs mt-0.5 text-muted-foreground">
|
|
301
250
|
{(g.kb.dirs as Record<string, string>)[key]}
|
|
302
251
|
</span>
|
|
303
252
|
</button>
|
|
304
253
|
))}
|
|
305
254
|
</div>
|
|
306
|
-
<p className="text-xs mt-3
|
|
255
|
+
<p className="text-xs mt-3 text-[var(--amber)]">
|
|
307
256
|
💡 {g.kb.instructionHint}
|
|
308
257
|
</p>
|
|
309
258
|
</>
|
|
310
259
|
)}
|
|
311
260
|
|
|
312
|
-
<div className="flex items-center justify-between mt-3 pt-3
|
|
313
|
-
<span className="text-xs
|
|
261
|
+
<div className="flex items-center justify-between mt-3 pt-3 border-t border-border">
|
|
262
|
+
<span className="text-xs text-muted-foreground">
|
|
314
263
|
{g.kb.progress(browsedCount)}
|
|
315
264
|
</span>
|
|
316
265
|
<button onClick={handleSkipKB}
|
|
317
|
-
className="text-xs px-3 py-1 rounded-lg transition-colors hover:bg-muted"
|
|
318
|
-
style={{ color: 'var(--muted-foreground)' }}>
|
|
266
|
+
className="text-xs px-3 py-1 rounded-lg text-muted-foreground transition-colors hover:bg-muted">
|
|
319
267
|
{g.skip}
|
|
320
268
|
</button>
|
|
321
269
|
</div>
|
|
@@ -326,10 +274,8 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
|
|
|
326
274
|
);
|
|
327
275
|
}
|
|
328
276
|
|
|
329
|
-
// Expose callback for AskModal integration
|
|
330
277
|
export { type GuideCardProps };
|
|
331
278
|
|
|
332
|
-
// Reusable sub-component
|
|
333
279
|
function TaskCard({ icon, title, cta, done, active, optional, onClick }: {
|
|
334
280
|
icon: React.ReactNode;
|
|
335
281
|
title: string;
|
|
@@ -348,25 +294,22 @@ function TaskCard({ icon, title, cta, done, active, optional, onClick }: {
|
|
|
348
294
|
transition-all duration-150
|
|
349
295
|
${done ? 'opacity-60' : 'hover:border-amber-500/30 hover:bg-muted/50 cursor-pointer'}
|
|
350
296
|
${active ? 'border-amber-500/40 bg-muted/50' : ''}
|
|
297
|
+
${done || active ? 'border-[var(--amber)]' : 'border-border'}
|
|
351
298
|
`}
|
|
352
|
-
style={{ borderColor: done || active ? 'var(--amber)' : 'var(--border)' }}
|
|
353
299
|
>
|
|
354
|
-
<span
|
|
355
|
-
className={done ? 'animate-in zoom-in-50 duration-300' : ''}
|
|
356
|
-
style={{ color: done ? 'var(--success)' : 'var(--amber)' }}
|
|
357
|
-
>
|
|
300
|
+
<span className={`${done ? 'animate-in zoom-in-50 duration-300 text-success' : 'text-[var(--amber)]'}`}>
|
|
358
301
|
{done ? <Check size={16} /> : icon}
|
|
359
302
|
</span>
|
|
360
|
-
<span className="text-xs font-medium
|
|
303
|
+
<span className="text-xs font-medium text-foreground">
|
|
361
304
|
{title}
|
|
362
305
|
</span>
|
|
363
306
|
{optional && (
|
|
364
|
-
<span className="text-2xs px-1.5 py-0.5 rounded-full
|
|
307
|
+
<span className="text-2xs px-1.5 py-0.5 rounded-full bg-muted text-muted-foreground">
|
|
365
308
|
{optional}
|
|
366
309
|
</span>
|
|
367
310
|
)}
|
|
368
311
|
{!done && !optional && (
|
|
369
|
-
<span className="text-2xs
|
|
312
|
+
<span className="text-2xs text-[var(--amber)]">
|
|
370
313
|
{cta} →
|
|
371
314
|
</span>
|
|
372
315
|
)}
|
|
@@ -52,8 +52,7 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
|
|
|
52
52
|
const formatTime = (mtime: number) => relativeTime(mtime, t.home.relativeTime);
|
|
53
53
|
|
|
54
54
|
// Only show renderers that are available (have entryPath + file exists) as quick-access chips
|
|
55
|
-
const
|
|
56
|
-
const availablePlugins = renderers.filter(r => r.entryPath && existingSet.has(r.entryPath));
|
|
55
|
+
const availablePlugins = getAllRenderers().filter(r => r.entryPath && existingSet.has(r.entryPath));
|
|
57
56
|
|
|
58
57
|
const lastFile = recent[0];
|
|
59
58
|
|
|
@@ -63,38 +62,29 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
|
|
|
63
62
|
{/* Hero */}
|
|
64
63
|
<div className="mb-10">
|
|
65
64
|
<div className="flex items-center gap-2 mb-3">
|
|
66
|
-
<div className="w-1 h-5 rounded-full
|
|
67
|
-
<h1 className="text-2xl font-semibold tracking-tight font-display
|
|
65
|
+
<div className="w-1 h-5 rounded-full bg-[var(--amber)]" />
|
|
66
|
+
<h1 className="text-2xl font-semibold tracking-tight font-display text-foreground">
|
|
68
67
|
MindOS
|
|
69
68
|
</h1>
|
|
70
69
|
</div>
|
|
71
|
-
<p className="text-sm leading-relaxed mb-5
|
|
70
|
+
<p className="text-sm leading-relaxed mb-5 text-muted-foreground pl-4">
|
|
72
71
|
{t.app.tagline}
|
|
73
72
|
</p>
|
|
74
73
|
|
|
75
74
|
{/* AI-first command bar */}
|
|
76
|
-
<div
|
|
77
|
-
className="w-full max-w-[620px] flex flex-col sm:flex-row items-stretch sm:items-center gap-2"
|
|
78
|
-
style={{
|
|
79
|
-
marginLeft: '1rem',
|
|
80
|
-
}}
|
|
81
|
-
>
|
|
75
|
+
<div className="w-full max-w-[620px] flex flex-col sm:flex-row items-stretch sm:items-center gap-2 ml-4">
|
|
82
76
|
{/* Ask AI (primary) */}
|
|
83
77
|
<button
|
|
84
78
|
onClick={triggerAsk}
|
|
85
79
|
title="⌘/"
|
|
86
80
|
data-walkthrough="ask-button"
|
|
87
|
-
className="flex-1 flex items-center gap-3 px-4 py-3 rounded-xl border transition-all duration-150 hover:border-amber-500/50 hover:bg-amber-500/8"
|
|
88
|
-
style={{ background: 'var(--card)', borderColor: 'var(--border)' }}
|
|
81
|
+
className="flex-1 flex items-center gap-3 px-4 py-3 rounded-xl border border-border bg-card transition-all duration-150 hover:border-amber-500/50 hover:bg-amber-500/8"
|
|
89
82
|
>
|
|
90
|
-
<Sparkles size={15}
|
|
91
|
-
<span className="text-sm flex-1 text-left
|
|
83
|
+
<Sparkles size={15} className="shrink-0 text-[var(--amber)]" />
|
|
84
|
+
<span className="text-sm flex-1 text-left text-foreground">
|
|
92
85
|
{suggestions[suggestionIdx]}
|
|
93
86
|
</span>
|
|
94
|
-
<kbd
|
|
95
|
-
className="hidden sm:inline-flex items-center gap-0.5 px-2 py-0.5 rounded text-xs font-mono font-medium"
|
|
96
|
-
style={{ background: 'var(--amber-dim)', color: 'var(--amber)' }}
|
|
97
|
-
>
|
|
87
|
+
<kbd className="hidden sm:inline-flex items-center gap-0.5 px-2 py-0.5 rounded text-xs font-mono font-medium bg-[var(--amber-dim)] text-[var(--amber)]">
|
|
98
88
|
⌘/
|
|
99
89
|
</kbd>
|
|
100
90
|
</button>
|
|
@@ -103,27 +93,22 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
|
|
|
103
93
|
<button
|
|
104
94
|
onClick={triggerSearch}
|
|
105
95
|
title="⌘K"
|
|
106
|
-
className="flex items-center gap-2 px-3 py-3 rounded-xl border text-sm transition-colors shrink-0 hover:bg-muted"
|
|
107
|
-
style={{ borderColor: 'var(--border)', color: 'var(--muted-foreground)' }}
|
|
96
|
+
className="flex items-center gap-2 px-3 py-3 rounded-xl border border-border text-sm text-muted-foreground transition-colors shrink-0 hover:bg-muted"
|
|
108
97
|
>
|
|
109
98
|
<Search size={14} />
|
|
110
99
|
<span className="hidden sm:inline">{t.home.shortcuts.searchFiles}</span>
|
|
111
|
-
<kbd className="hidden sm:inline-flex items-center px-1.5 py-0.5 rounded text-2xs font-mono
|
|
100
|
+
<kbd className="hidden sm:inline-flex items-center px-1.5 py-0.5 rounded text-2xs font-mono bg-muted">
|
|
112
101
|
⌘K
|
|
113
102
|
</kbd>
|
|
114
103
|
</button>
|
|
115
104
|
</div>
|
|
116
105
|
|
|
117
106
|
{/* Quick Actions */}
|
|
118
|
-
<div className="flex flex-wrap gap-2.5 mt-4"
|
|
107
|
+
<div className="flex flex-wrap gap-2.5 mt-4 pl-4">
|
|
119
108
|
{lastFile && (
|
|
120
109
|
<Link
|
|
121
110
|
href={`/view/${encodePath(lastFile.path)}`}
|
|
122
|
-
className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-all duration-150 hover:translate-x-0.5"
|
|
123
|
-
style={{
|
|
124
|
-
background: 'var(--amber-dim)',
|
|
125
|
-
color: 'var(--amber)',
|
|
126
|
-
}}
|
|
111
|
+
className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-all duration-150 hover:translate-x-0.5 bg-[var(--amber-dim)] text-[var(--amber)]"
|
|
127
112
|
>
|
|
128
113
|
<ArrowRight size={14} />
|
|
129
114
|
<span>{t.home.continueEditing}</span>
|
|
@@ -134,21 +119,14 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
|
|
|
134
119
|
)}
|
|
135
120
|
<Link
|
|
136
121
|
href="/view/Untitled.md"
|
|
137
|
-
className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-colors"
|
|
138
|
-
style={{
|
|
139
|
-
background: 'var(--muted)',
|
|
140
|
-
color: 'var(--muted-foreground)',
|
|
141
|
-
}}
|
|
122
|
+
className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-colors bg-muted text-muted-foreground"
|
|
142
123
|
>
|
|
143
124
|
<FilePlus size={14} />
|
|
144
125
|
<span>{t.home.newNote}</span>
|
|
145
126
|
</Link>
|
|
146
127
|
<Link
|
|
147
128
|
href="/explore"
|
|
148
|
-
className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-all duration-150 hover:translate-x-0.5"
|
|
149
|
-
style={{
|
|
150
|
-
color: 'var(--amber)',
|
|
151
|
-
}}
|
|
129
|
+
className="inline-flex items-center gap-2 px-3.5 py-2 rounded-lg text-sm font-medium transition-all duration-150 hover:translate-x-0.5 text-[var(--amber)]"
|
|
152
130
|
>
|
|
153
131
|
<Compass size={14} />
|
|
154
132
|
<span>{t.explore.title}</span>
|
|
@@ -157,13 +135,12 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
|
|
|
157
135
|
|
|
158
136
|
{/* Plugin quick-access chips — only show available plugins */}
|
|
159
137
|
{availablePlugins.length > 0 && (
|
|
160
|
-
<div className="flex flex-wrap gap-1.5 mt-3"
|
|
138
|
+
<div className="flex flex-wrap gap-1.5 mt-3 pl-4">
|
|
161
139
|
{availablePlugins.map(r => (
|
|
162
140
|
<Link
|
|
163
141
|
key={r.id}
|
|
164
142
|
href={`/view/${encodePath(r.entryPath!)}`}
|
|
165
|
-
className="inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs transition-all duration-100 hover:bg-muted/60"
|
|
166
|
-
style={{ color: 'var(--muted-foreground)' }}
|
|
143
|
+
className="inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs text-muted-foreground transition-all duration-100 hover:bg-muted/60"
|
|
167
144
|
>
|
|
168
145
|
<span className="text-sm leading-none" suppressHydrationWarning>{r.icon}</span>
|
|
169
146
|
<span>{r.name}</span>
|
|
@@ -182,15 +159,15 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
|
|
|
182
159
|
return (
|
|
183
160
|
<section className="mb-12">
|
|
184
161
|
<div className="flex items-center gap-2 mb-5">
|
|
185
|
-
<Clock size={13}
|
|
186
|
-
<h2 className="text-xs font-semibold uppercase tracking-[0.08em] font-display
|
|
162
|
+
<Clock size={13} className="text-[var(--amber)]" />
|
|
163
|
+
<h2 className="text-xs font-semibold uppercase tracking-[0.08em] font-display text-muted-foreground">
|
|
187
164
|
{t.home.recentlyModified}
|
|
188
165
|
</h2>
|
|
189
166
|
</div>
|
|
190
167
|
|
|
191
168
|
<div className="relative pl-4">
|
|
192
169
|
{/* Timeline line */}
|
|
193
|
-
<div className="absolute left-0 top-1 bottom-1 w-px
|
|
170
|
+
<div className="absolute left-0 top-1 bottom-1 w-px bg-border" />
|
|
194
171
|
|
|
195
172
|
<div className="flex flex-col gap-0.5">
|
|
196
173
|
{visibleRecent.map(({ path: filePath, mtime }, idx) => {
|
|
@@ -201,6 +178,7 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
|
|
|
201
178
|
<div key={filePath} className="relative group">
|
|
202
179
|
{/* Timeline dot */}
|
|
203
180
|
<div
|
|
181
|
+
aria-hidden="true"
|
|
204
182
|
className={`absolute -left-4 top-1/2 -translate-y-1/2 rounded-full transition-all duration-150 group-hover:scale-150 ${idx === 0 ? 'w-2 h-2' : 'w-1.5 h-1.5'}`}
|
|
205
183
|
style={{
|
|
206
184
|
background: idx === 0 ? 'var(--amber)' : 'var(--border)',
|
|
@@ -212,14 +190,14 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
|
|
|
212
190
|
className="flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-100 group-hover:translate-x-0.5 hover:bg-muted"
|
|
213
191
|
>
|
|
214
192
|
{isCSV
|
|
215
|
-
? <Table size={13} className="shrink-0
|
|
216
|
-
: <FileText size={13} className="shrink-0
|
|
193
|
+
? <Table size={13} className="shrink-0 text-success" />
|
|
194
|
+
: <FileText size={13} className="shrink-0 text-muted-foreground" />
|
|
217
195
|
}
|
|
218
196
|
<div className="flex-1 min-w-0">
|
|
219
|
-
<span className="text-sm font-medium truncate block
|
|
220
|
-
{dir && <span className="text-xs truncate block
|
|
197
|
+
<span className="text-sm font-medium truncate block text-foreground" suppressHydrationWarning>{name}</span>
|
|
198
|
+
{dir && <span className="text-xs truncate block text-muted-foreground opacity-60" suppressHydrationWarning>{dir}</span>}
|
|
221
199
|
</div>
|
|
222
|
-
<span className="text-xs shrink-0 tabular-nums font-display
|
|
200
|
+
<span className="text-xs shrink-0 tabular-nums font-display text-muted-foreground opacity-50" suppressHydrationWarning>
|
|
223
201
|
{formatTime(mtime)}
|
|
224
202
|
</span>
|
|
225
203
|
</Link>
|
|
@@ -233,13 +211,11 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
|
|
|
233
211
|
<button
|
|
234
212
|
onClick={() => setShowAll(v => !v)}
|
|
235
213
|
aria-expanded={showAll}
|
|
236
|
-
|
|
237
|
-
className="flex items-center gap-1.5 mt-2 ml-3 text-xs font-medium transition-colors hover:opacity-80 cursor-pointer font-display"
|
|
214
|
+
className="flex items-center gap-1.5 mt-2 ml-3 text-xs font-medium text-[var(--amber)] transition-colors hover:opacity-80 cursor-pointer font-display"
|
|
238
215
|
>
|
|
239
216
|
<ChevronDown
|
|
240
217
|
size={12}
|
|
241
|
-
className=
|
|
242
|
-
style={{ transform: showAll ? 'rotate(180deg)' : undefined }}
|
|
218
|
+
className={`transition-transform duration-200 ${showAll ? 'rotate-180' : ''}`}
|
|
243
219
|
/>
|
|
244
220
|
<span>{showAll ? t.home.showLess : t.home.showMore}</span>
|
|
245
221
|
</button>
|
|
@@ -250,8 +226,8 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
|
|
|
250
226
|
})()}
|
|
251
227
|
|
|
252
228
|
{/* Footer */}
|
|
253
|
-
<div className="mt-16 flex items-center gap-1.5 text-xs font-display
|
|
254
|
-
<Sparkles size={10}
|
|
229
|
+
<div className="mt-16 flex items-center gap-1.5 text-xs font-display text-muted-foreground opacity-60">
|
|
230
|
+
<Sparkles size={10} className="text-[var(--amber)]" />
|
|
255
231
|
<span>{t.app.footer}</span>
|
|
256
232
|
</div>
|
|
257
233
|
</div>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation';
|
|
5
|
-
import { Sparkles, Globe, BookOpen, FileText, Loader2, GitBranch } from 'lucide-react';
|
|
5
|
+
import { Sparkles, Globe, BookOpen, FileText, Loader2, GitBranch, AlertCircle } from 'lucide-react';
|
|
6
6
|
import { useLocale } from '@/lib/LocaleContext';
|
|
7
7
|
|
|
8
8
|
type Template = 'en' | 'zh' | 'empty';
|
|
@@ -33,11 +33,13 @@ export default function OnboardingView() {
|
|
|
33
33
|
const { t } = useLocale();
|
|
34
34
|
const router = useRouter();
|
|
35
35
|
const [loading, setLoading] = useState<Template | null>(null);
|
|
36
|
+
const [error, setError] = useState<string | null>(null);
|
|
36
37
|
|
|
37
38
|
const ob = t.onboarding;
|
|
38
39
|
|
|
39
40
|
async function handleSelect(template: Template) {
|
|
40
41
|
setLoading(template);
|
|
42
|
+
setError(null);
|
|
41
43
|
try {
|
|
42
44
|
const res = await fetch('/api/init', {
|
|
43
45
|
method: 'POST',
|
|
@@ -51,6 +53,7 @@ export default function OnboardingView() {
|
|
|
51
53
|
router.refresh();
|
|
52
54
|
} catch (e) {
|
|
53
55
|
console.error('[Onboarding] init failed:', e);
|
|
56
|
+
setError(e instanceof Error ? e.message : 'Unknown error');
|
|
54
57
|
setLoading(null);
|
|
55
58
|
}
|
|
56
59
|
}
|
|
@@ -60,22 +63,33 @@ export default function OnboardingView() {
|
|
|
60
63
|
{/* Header */}
|
|
61
64
|
<div className="text-center mb-10">
|
|
62
65
|
<div className="inline-flex items-center gap-2 mb-4">
|
|
63
|
-
<Sparkles size={18}
|
|
64
|
-
<h1
|
|
65
|
-
className="text-2xl font-semibold tracking-tight font-display"
|
|
66
|
-
style={{ color: 'var(--foreground)' }}
|
|
67
|
-
>
|
|
66
|
+
<Sparkles size={18} className="text-[var(--amber)]" />
|
|
67
|
+
<h1 className="text-2xl font-semibold tracking-tight font-display text-foreground">
|
|
68
68
|
MindOS
|
|
69
69
|
</h1>
|
|
70
70
|
</div>
|
|
71
|
-
<p
|
|
72
|
-
className="text-sm leading-relaxed max-w-md mx-auto"
|
|
73
|
-
style={{ color: 'var(--muted-foreground)' }}
|
|
74
|
-
>
|
|
71
|
+
<p className="text-sm leading-relaxed max-w-md mx-auto text-muted-foreground">
|
|
75
72
|
{ob.subtitle}
|
|
76
73
|
</p>
|
|
77
74
|
</div>
|
|
78
75
|
|
|
76
|
+
{/* Error banner */}
|
|
77
|
+
{error && (
|
|
78
|
+
<div
|
|
79
|
+
role="alert"
|
|
80
|
+
className="max-w-2xl mx-auto mb-6 flex items-center gap-2.5 px-4 py-3 rounded-lg border border-destructive/30 bg-destructive/5 text-sm text-destructive"
|
|
81
|
+
>
|
|
82
|
+
<AlertCircle size={16} className="shrink-0" />
|
|
83
|
+
<span className="flex-1">{ob.initError ?? 'Initialization failed. Please try again.'}</span>
|
|
84
|
+
<button
|
|
85
|
+
onClick={() => setError(null)}
|
|
86
|
+
className="text-xs underline shrink-0 hover:opacity-80"
|
|
87
|
+
>
|
|
88
|
+
{ob.dismiss ?? 'Dismiss'}
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
|
|
79
93
|
{/* Template cards */}
|
|
80
94
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 max-w-2xl mx-auto mb-10">
|
|
81
95
|
{TEMPLATES.map((tpl) => {
|
|
@@ -86,37 +100,26 @@ export default function OnboardingView() {
|
|
|
86
100
|
key={tpl.id}
|
|
87
101
|
disabled={isDisabled}
|
|
88
102
|
onClick={() => handleSelect(tpl.id)}
|
|
89
|
-
className="group relative flex flex-col items-start gap-3 p-5 rounded-xl border text-left transition-all duration-150 hover:border-amber-500/50 hover:bg-amber-500/5 disabled:opacity-60 disabled:cursor-not-allowed"
|
|
90
|
-
style={{ background: 'var(--card)', borderColor: 'var(--border)' }}
|
|
103
|
+
className="group relative flex flex-col items-start gap-3 p-5 rounded-xl border border-border bg-card text-left transition-all duration-150 hover:border-amber-500/50 hover:bg-amber-500/5 disabled:opacity-60 disabled:cursor-not-allowed"
|
|
91
104
|
>
|
|
92
105
|
{/* Icon + title */}
|
|
93
106
|
<div className="flex items-center gap-2.5 w-full">
|
|
94
|
-
<span
|
|
95
|
-
<span
|
|
96
|
-
className="text-sm font-semibold"
|
|
97
|
-
style={{ color: 'var(--foreground)' }}
|
|
98
|
-
>
|
|
107
|
+
<span className="text-[var(--amber)]">{tpl.icon}</span>
|
|
108
|
+
<span className="text-sm font-semibold text-foreground">
|
|
99
109
|
{ob.templates[tpl.id].title}
|
|
100
110
|
</span>
|
|
101
111
|
{isLoading && (
|
|
102
|
-
<Loader2 size={14} className="animate-spin ml-auto
|
|
112
|
+
<Loader2 size={14} className="animate-spin ml-auto text-[var(--amber)]" />
|
|
103
113
|
)}
|
|
104
114
|
</div>
|
|
105
115
|
|
|
106
116
|
{/* Description */}
|
|
107
|
-
<p className="text-xs leading-relaxed
|
|
117
|
+
<p className="text-xs leading-relaxed text-muted-foreground">
|
|
108
118
|
{ob.templates[tpl.id].desc}
|
|
109
119
|
</p>
|
|
110
120
|
|
|
111
121
|
{/* Directory preview */}
|
|
112
|
-
<div
|
|
113
|
-
className="w-full rounded-lg px-3 py-2 text-xs leading-relaxed font-display"
|
|
114
|
-
style={{
|
|
115
|
-
background: 'var(--muted)',
|
|
116
|
-
color: 'var(--muted-foreground)',
|
|
117
|
-
opacity: 0.8,
|
|
118
|
-
}}
|
|
119
|
-
>
|
|
122
|
+
<div className="w-full rounded-lg px-3 py-2 text-xs leading-relaxed font-display bg-muted text-muted-foreground opacity-80">
|
|
120
123
|
{tpl.dirs.map((d) => (
|
|
121
124
|
<div key={d}>{d}</div>
|
|
122
125
|
))}
|
|
@@ -127,27 +130,18 @@ export default function OnboardingView() {
|
|
|
127
130
|
</div>
|
|
128
131
|
|
|
129
132
|
{/* Import hint */}
|
|
130
|
-
<p
|
|
131
|
-
className="text-center text-xs leading-relaxed max-w-sm mx-auto font-display"
|
|
132
|
-
style={{ color: 'var(--muted-foreground)', opacity: 0.6 }}
|
|
133
|
-
>
|
|
133
|
+
<p className="text-center text-xs leading-relaxed max-w-sm mx-auto font-display text-muted-foreground opacity-60">
|
|
134
134
|
{ob.importHint}
|
|
135
135
|
</p>
|
|
136
136
|
|
|
137
137
|
{/* Sync hint card */}
|
|
138
|
-
<div
|
|
139
|
-
className="
|
|
140
|
-
style={{ borderColor: 'var(--border)', background: 'var(--card)' }}
|
|
141
|
-
>
|
|
142
|
-
<GitBranch size={16} style={{ color: 'var(--muted-foreground)', flexShrink: 0 }} />
|
|
138
|
+
<div className="max-w-md mx-auto mt-6 flex items-center gap-3 px-4 py-3 rounded-lg border border-border bg-card text-left">
|
|
139
|
+
<GitBranch size={16} className="text-muted-foreground shrink-0" />
|
|
143
140
|
<div className="min-w-0">
|
|
144
|
-
<p className="text-xs
|
|
141
|
+
<p className="text-xs text-muted-foreground">
|
|
145
142
|
{ob.syncHint ?? 'Want cross-device sync? Run'}
|
|
146
143
|
{' '}
|
|
147
|
-
<code
|
|
148
|
-
className="font-mono px-1 py-0.5 rounded select-all"
|
|
149
|
-
style={{ background: 'var(--muted)', fontSize: '11px' }}
|
|
150
|
-
>
|
|
144
|
+
<code className="font-mono px-1 py-0.5 rounded select-all bg-muted text-[11px]">
|
|
151
145
|
mindos sync init
|
|
152
146
|
</code>
|
|
153
147
|
{' '}
|
package/app/lib/i18n-en.ts
CHANGED
|
@@ -419,6 +419,8 @@ export const en = {
|
|
|
419
419
|
importHint: 'Already have notes? Set MIND_ROOT to your existing directory in Settings.',
|
|
420
420
|
syncHint: 'Want cross-device sync? Run',
|
|
421
421
|
syncHintSuffix: 'in the terminal after setup.',
|
|
422
|
+
initError: 'Initialization failed. Please try again.',
|
|
423
|
+
dismiss: 'Dismiss',
|
|
422
424
|
},
|
|
423
425
|
shortcuts: [
|
|
424
426
|
{ keys: ['⌘', 'K'], description: 'Search' },
|
package/app/lib/i18n-zh.ts
CHANGED
package/app/next-env.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="next" />
|
|
2
2
|
/// <reference types="next/image-types/global" />
|
|
3
|
-
import "./.next/
|
|
3
|
+
import "./.next/types/routes.d.ts";
|
|
4
4
|
|
|
5
5
|
// NOTE: This file should not be edited
|
|
6
6
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
package/package.json
CHANGED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
4
|
-
import { X, Sparkles } from 'lucide-react';
|
|
5
|
-
import { useLocale } from '@/lib/LocaleContext';
|
|
6
|
-
|
|
7
|
-
export default function WelcomeBanner() {
|
|
8
|
-
const { t } = useLocale();
|
|
9
|
-
const s = t.setup;
|
|
10
|
-
const [visible, setVisible] = useState(false);
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
// Show banner if ?welcome=1 is in the URL
|
|
14
|
-
const params = new URLSearchParams(window.location.search);
|
|
15
|
-
if (params.get('welcome') === '1') {
|
|
16
|
-
setVisible(true);
|
|
17
|
-
// Remove ?welcome=1 from URL without reloading
|
|
18
|
-
const url = new URL(window.location.href);
|
|
19
|
-
url.searchParams.delete('welcome');
|
|
20
|
-
const newUrl = url.pathname + (url.searchParams.size > 0 ? '?' + url.searchParams.toString() : '');
|
|
21
|
-
window.history.replaceState({}, '', newUrl);
|
|
22
|
-
}
|
|
23
|
-
}, []);
|
|
24
|
-
|
|
25
|
-
if (!visible) return null;
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div className="mb-6 rounded-xl border px-5 py-4 flex items-start gap-4"
|
|
29
|
-
style={{ background: 'var(--amber-subtle, rgba(200,135,30,0.08))', borderColor: 'var(--amber)' }}>
|
|
30
|
-
<Sparkles size={18} className="mt-0.5 shrink-0" style={{ color: 'var(--amber)' }} />
|
|
31
|
-
<div className="flex-1 min-w-0">
|
|
32
|
-
<p className="text-sm font-semibold mb-1" style={{ color: 'var(--foreground)' }}>
|
|
33
|
-
{s.welcomeTitle}
|
|
34
|
-
</p>
|
|
35
|
-
<p className="text-xs leading-relaxed mb-3" style={{ color: 'var(--muted-foreground)' }}>
|
|
36
|
-
{s.welcomeDesc}
|
|
37
|
-
</p>
|
|
38
|
-
<div className="flex flex-wrap gap-2">
|
|
39
|
-
<a href="/setup?force=1" className="text-xs px-3 py-1.5 rounded-lg border transition-colors"
|
|
40
|
-
style={{ borderColor: 'var(--amber)', color: 'var(--amber)' }}>
|
|
41
|
-
{s.welcomeLinkReconfigure}
|
|
42
|
-
</a>
|
|
43
|
-
<button onClick={() => window.dispatchEvent(new KeyboardEvent('keydown', { key: '/', metaKey: true, bubbles: true }))}
|
|
44
|
-
className="text-xs px-3 py-1.5 rounded-lg border transition-colors"
|
|
45
|
-
style={{ borderColor: 'var(--border)', color: 'var(--muted-foreground)' }}>
|
|
46
|
-
{s.welcomeLinkAskAI}
|
|
47
|
-
</button>
|
|
48
|
-
<button
|
|
49
|
-
onClick={() => window.dispatchEvent(new KeyboardEvent('keydown', { key: ',', metaKey: true, bubbles: true }))}
|
|
50
|
-
className="text-xs px-3 py-1.5 rounded-lg border transition-colors"
|
|
51
|
-
style={{ borderColor: 'var(--border)', color: 'var(--muted-foreground)' }}>
|
|
52
|
-
{s.welcomeLinkMCP}
|
|
53
|
-
</button>
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
<button onClick={() => setVisible(false)}
|
|
57
|
-
className="p-1 rounded hover:bg-muted transition-colors shrink-0"
|
|
58
|
-
style={{ color: 'var(--muted-foreground)' }}>
|
|
59
|
-
<X size={14} />
|
|
60
|
-
</button>
|
|
61
|
-
</div>
|
|
62
|
-
);
|
|
63
|
-
}
|