@geminilight/mindos 0.6.30 → 0.6.32
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_zh.md +10 -4
- package/app/app/api/ask/route.ts +12 -7
- package/app/app/api/export/route.ts +105 -0
- package/app/app/globals.css +2 -2
- package/app/app/trash/page.tsx +7 -0
- package/app/app/view/[...path]/ViewPageClient.tsx +234 -2
- package/app/components/ExportModal.tsx +220 -0
- package/app/components/FileTree.tsx +22 -2
- package/app/components/HomeContent.tsx +91 -20
- package/app/components/MarkdownView.tsx +45 -10
- package/app/components/Sidebar.tsx +10 -1
- package/app/components/TrashPageClient.tsx +263 -0
- package/app/components/ask/ToolCallBlock.tsx +102 -18
- package/app/components/changes/ChangesContentPage.tsx +58 -14
- package/app/components/explore/ExploreContent.tsx +4 -7
- package/app/components/explore/UseCaseCard.tsx +18 -1
- package/app/components/explore/use-cases.generated.ts +76 -0
- package/app/components/explore/use-cases.yaml +185 -0
- package/app/components/panels/DiscoverPanel.tsx +1 -1
- package/app/components/renderers/workflow-yaml/StepEditor.tsx +98 -91
- package/app/components/renderers/workflow-yaml/WorkflowEditor.tsx +72 -72
- package/app/components/renderers/workflow-yaml/WorkflowRunner.tsx +175 -119
- package/app/components/renderers/workflow-yaml/WorkflowYamlRenderer.tsx +61 -61
- package/app/components/renderers/workflow-yaml/execution.ts +64 -12
- package/app/components/renderers/workflow-yaml/selectors.tsx +65 -13
- package/app/components/settings/AiTab.tsx +191 -174
- package/app/components/settings/AppearanceTab.tsx +168 -77
- package/app/components/settings/KnowledgeTab.tsx +131 -136
- package/app/components/settings/McpTab.tsx +11 -11
- package/app/components/settings/Primitives.tsx +60 -0
- package/app/components/settings/SettingsContent.tsx +15 -8
- package/app/components/settings/SyncTab.tsx +12 -12
- package/app/components/settings/UninstallTab.tsx +8 -18
- package/app/components/settings/UpdateTab.tsx +82 -82
- package/app/components/settings/types.ts +17 -8
- package/app/lib/acp/session.ts +12 -3
- package/app/lib/actions.ts +57 -3
- package/app/lib/agent/stream-consumer.ts +18 -0
- package/app/lib/agent/tools.ts +56 -9
- package/app/lib/core/export.ts +116 -0
- package/app/lib/core/trash.ts +241 -0
- package/app/lib/fs.ts +47 -0
- package/app/lib/hooks/usePinnedFiles.ts +90 -0
- package/app/lib/i18n/generated/explore-i18n.generated.ts +138 -0
- package/app/lib/i18n/index.ts +3 -0
- package/app/lib/i18n/modules/knowledge.ts +120 -6
- package/app/lib/i18n/modules/onboarding.ts +2 -134
- package/app/lib/i18n/modules/settings.ts +12 -0
- package/app/package.json +8 -2
- package/app/scripts/generate-explore.ts +145 -0
- package/package.json +1 -1
- package/app/components/explore/use-cases.ts +0 -58
|
@@ -1,27 +1,41 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import { ChevronDown, ChevronRight, Sun, Moon, Monitor, Type, Columns3, Globe } from 'lucide-react';
|
|
4
|
+
import { ChevronDown, ChevronRight, Sun, Moon, Monitor, Type, ALargeSmall, Columns3, Globe, BookOpen, Palette } from 'lucide-react';
|
|
5
5
|
import { Locale } from '@/lib/i18n';
|
|
6
|
-
import { CONTENT_WIDTHS, FONTS, AppearanceTabProps } from './types';
|
|
6
|
+
import { CONTENT_WIDTHS, FONTS, FONT_SIZES, AppearanceTabProps } from './types';
|
|
7
|
+
import { SettingCard } from './Primitives';
|
|
7
8
|
|
|
8
|
-
/* ──
|
|
9
|
-
function
|
|
9
|
+
/* ── Setting Group ── */
|
|
10
|
+
function SettingGroup({ icon, label, children }: { icon: React.ReactNode; label: string; children: React.ReactNode }) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="space-y-3">
|
|
13
|
+
<div className="flex items-center gap-2">
|
|
14
|
+
<span className="text-muted-foreground">{icon}</span>
|
|
15
|
+
<span className="text-sm font-medium text-foreground">{label}</span>
|
|
16
|
+
</div>
|
|
17
|
+
{children}
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* ── Pill Selector — compact horizontal pills ── */
|
|
23
|
+
function PillSelector<T extends string>({ options, value, onChange }: {
|
|
10
24
|
options: { value: T; label: string; icon?: React.ReactNode }[];
|
|
11
25
|
value: T;
|
|
12
26
|
onChange: (v: T) => void;
|
|
13
27
|
}) {
|
|
14
28
|
return (
|
|
15
|
-
<div className="flex
|
|
29
|
+
<div className="flex gap-2">
|
|
16
30
|
{options.map(opt => (
|
|
17
31
|
<button
|
|
18
32
|
key={opt.value}
|
|
19
33
|
type="button"
|
|
20
34
|
onClick={() => onChange(opt.value)}
|
|
21
|
-
className={`flex
|
|
35
|
+
className={`flex items-center gap-1.5 px-3.5 py-2 text-sm font-medium rounded-xl transition-all ${
|
|
22
36
|
value === opt.value
|
|
23
|
-
? 'bg-
|
|
24
|
-
: 'text-muted-foreground hover:text-foreground'
|
|
37
|
+
? 'bg-[var(--amber)] text-[var(--amber-foreground)] shadow-sm'
|
|
38
|
+
: 'bg-muted/50 text-muted-foreground hover:text-foreground hover:bg-muted'
|
|
25
39
|
}`}
|
|
26
40
|
>
|
|
27
41
|
{opt.icon}
|
|
@@ -32,20 +46,7 @@ function SegmentedControl<T extends string>({ options, value, onChange }: {
|
|
|
32
46
|
);
|
|
33
47
|
}
|
|
34
48
|
|
|
35
|
-
|
|
36
|
-
function SettingGroup({ icon, label, children }: { icon: React.ReactNode; label: string; children: React.ReactNode }) {
|
|
37
|
-
return (
|
|
38
|
-
<div className="space-y-2.5">
|
|
39
|
-
<div className="flex items-center gap-2">
|
|
40
|
-
<span className="text-muted-foreground">{icon}</span>
|
|
41
|
-
<span className="text-xs font-medium text-foreground">{label}</span>
|
|
42
|
-
</div>
|
|
43
|
-
{children}
|
|
44
|
-
</div>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function AppearanceTab({ font, setFont, contentWidth, setContentWidth, dark, setDark, locale, setLocale, t }: AppearanceTabProps) {
|
|
49
|
+
export function AppearanceTab({ font, setFont, fontSize, setFontSize, contentWidth, setContentWidth, dark, setDark, locale, setLocale, t }: AppearanceTabProps) {
|
|
49
50
|
const [showShortcuts, setShowShortcuts] = useState(false);
|
|
50
51
|
const [themePref, setThemePref] = useState<string>(() =>
|
|
51
52
|
typeof window !== 'undefined' ? (localStorage.getItem('theme') ?? 'system') : 'system'
|
|
@@ -56,61 +57,150 @@ export function AppearanceTab({ font, setFont, contentWidth, setContentWidth, da
|
|
|
56
57
|
const a = t.settings.appearance;
|
|
57
58
|
|
|
58
59
|
return (
|
|
59
|
-
<div className="space-y-
|
|
60
|
-
|
|
60
|
+
<div className="space-y-4">
|
|
61
|
+
|
|
62
|
+
{/* ── Card 1: Reading — font, size, width ── */}
|
|
63
|
+
<SettingCard icon={<BookOpen size={15} />} title={a.readingTitle ?? 'Reading'} description={a.readingDesc ?? 'Customize how your notes look'}>
|
|
64
|
+
|
|
65
|
+
{/* ── Font — each font previews itself ── */}
|
|
61
66
|
<SettingGroup icon={<Type size={14} />} label={a.readingFont}>
|
|
62
|
-
<div className="
|
|
63
|
-
{FONTS.map(f =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
<div className="space-y-0.5">
|
|
68
|
+
{FONTS.map(f => {
|
|
69
|
+
const selected = font === f.value;
|
|
70
|
+
return (
|
|
71
|
+
<button
|
|
72
|
+
key={f.value}
|
|
73
|
+
type="button"
|
|
74
|
+
onClick={() => setFont(f.value)}
|
|
75
|
+
className={`flex items-center gap-4 w-full px-3 py-2.5 rounded-lg transition-all text-left relative ${
|
|
76
|
+
selected
|
|
77
|
+
? 'bg-[var(--amber-subtle)]'
|
|
78
|
+
: 'hover:bg-muted/50'
|
|
79
|
+
}`}
|
|
80
|
+
>
|
|
81
|
+
{/* Active indicator — left bar */}
|
|
82
|
+
{selected && (
|
|
83
|
+
<div className="absolute left-0 top-2 bottom-2 w-[3px] rounded-r-full bg-[var(--amber)]" />
|
|
84
|
+
)}
|
|
85
|
+
{/* Large Aa preview */}
|
|
86
|
+
<span
|
|
87
|
+
className={`text-xl font-medium w-10 text-center shrink-0 ${selected ? 'text-foreground' : 'text-muted-foreground/60'}`}
|
|
88
|
+
style={{ fontFamily: f.style.fontFamily }}
|
|
89
|
+
>
|
|
90
|
+
Aa
|
|
91
|
+
</span>
|
|
92
|
+
{/* Font name + sample */}
|
|
93
|
+
<div className="flex-1 min-w-0">
|
|
94
|
+
<div className="flex items-baseline gap-2">
|
|
95
|
+
<span
|
|
96
|
+
className={`text-sm font-medium ${selected ? 'text-foreground' : 'text-muted-foreground'}`}
|
|
97
|
+
style={{ fontFamily: f.style.fontFamily }}
|
|
98
|
+
>
|
|
99
|
+
{f.label}
|
|
100
|
+
</span>
|
|
101
|
+
<span className="text-xs text-muted-foreground/50">{f.category}</span>
|
|
102
|
+
</div>
|
|
103
|
+
<p
|
|
104
|
+
className={`text-sm truncate mt-0.5 ${selected ? 'text-muted-foreground' : 'text-muted-foreground/40'}`}
|
|
105
|
+
style={{ fontFamily: f.style.fontFamily }}
|
|
106
|
+
>
|
|
107
|
+
{a.fontPreview}
|
|
108
|
+
</p>
|
|
109
|
+
</div>
|
|
110
|
+
</button>
|
|
111
|
+
);
|
|
112
|
+
})}
|
|
78
113
|
</div>
|
|
79
|
-
<p
|
|
80
|
-
className="text-sm text-muted-foreground leading-relaxed px-0.5 mt-1"
|
|
81
|
-
style={{ fontFamily: FONTS.find(f => f.value === font)?.style.fontFamily }}
|
|
82
|
-
>
|
|
83
|
-
{a.fontPreview}
|
|
84
|
-
</p>
|
|
85
114
|
</SettingGroup>
|
|
86
115
|
|
|
87
|
-
{/*
|
|
116
|
+
{/* ── Font Size — elegant range slider ── */}
|
|
117
|
+
<SettingGroup icon={<ALargeSmall size={14} />} label={a.fontSize}>
|
|
118
|
+
<div className="px-1">
|
|
119
|
+
{/* Slider */}
|
|
120
|
+
<div className="flex items-center gap-3">
|
|
121
|
+
<span className="text-xs text-muted-foreground/60 shrink-0" style={{ fontSize: '11px' }}>A</span>
|
|
122
|
+
<input
|
|
123
|
+
type="range"
|
|
124
|
+
min={14}
|
|
125
|
+
max={17}
|
|
126
|
+
step={1}
|
|
127
|
+
value={parseInt(fontSize)}
|
|
128
|
+
onChange={e => setFontSize(`${e.target.value}px`)}
|
|
129
|
+
className="flex-1 h-1.5 rounded-full appearance-none bg-muted cursor-pointer accent-[var(--amber)]
|
|
130
|
+
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:h-4
|
|
131
|
+
[&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-[var(--amber)]
|
|
132
|
+
[&::-webkit-slider-thumb]:shadow-sm [&::-webkit-slider-thumb]:cursor-pointer
|
|
133
|
+
[&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:hover:scale-110
|
|
134
|
+
[&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:rounded-full
|
|
135
|
+
[&::-moz-range-thumb]:bg-[var(--amber)] [&::-moz-range-thumb]:border-0
|
|
136
|
+
[&::-moz-range-thumb]:shadow-sm [&::-moz-range-thumb]:cursor-pointer"
|
|
137
|
+
/>
|
|
138
|
+
<span className="text-base text-muted-foreground/60 shrink-0">A</span>
|
|
139
|
+
</div>
|
|
140
|
+
{/* Current value */}
|
|
141
|
+
<div className="text-center mt-1.5">
|
|
142
|
+
<span className="text-xs tabular-nums text-muted-foreground">{parseInt(fontSize)}px</span>
|
|
143
|
+
</div>
|
|
144
|
+
{/* Live preview */}
|
|
145
|
+
<p
|
|
146
|
+
className="text-muted-foreground leading-relaxed mt-2 px-1"
|
|
147
|
+
style={{
|
|
148
|
+
fontSize,
|
|
149
|
+
fontFamily: FONTS.find(f => f.value === font)?.style.fontFamily,
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
{a.fontSizePreview}
|
|
153
|
+
</p>
|
|
154
|
+
</div>
|
|
155
|
+
</SettingGroup>
|
|
156
|
+
|
|
157
|
+
{/* ── Content Width — visual width bars ── */}
|
|
88
158
|
<SettingGroup icon={<Columns3 size={14} />} label={a.contentWidth}>
|
|
89
|
-
<div className="
|
|
90
|
-
{CONTENT_WIDTHS.map(w =>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
159
|
+
<div className="space-y-1">
|
|
160
|
+
{CONTENT_WIDTHS.map(w => {
|
|
161
|
+
const selected = contentWidth === w.value;
|
|
162
|
+
return (
|
|
163
|
+
<button
|
|
164
|
+
key={w.value}
|
|
165
|
+
type="button"
|
|
166
|
+
onClick={() => setContentWidth(w.value)}
|
|
167
|
+
className={`flex items-center gap-3 w-full px-3 py-2 rounded-lg transition-all ${
|
|
168
|
+
selected ? 'bg-[var(--amber-subtle)]' : 'hover:bg-muted/50'
|
|
169
|
+
}`}
|
|
170
|
+
>
|
|
171
|
+
{/* Width indicator bar */}
|
|
172
|
+
<div className="flex-1 h-2 rounded-full bg-muted/40 overflow-hidden">
|
|
173
|
+
<div
|
|
174
|
+
className={`h-full rounded-full transition-all ${
|
|
175
|
+
selected ? 'bg-[var(--amber)]' : 'bg-muted-foreground/20'
|
|
176
|
+
}`}
|
|
177
|
+
style={{ width: `${w.width}%` }}
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
180
|
+
{/* Label */}
|
|
181
|
+
<span className={`text-sm shrink-0 w-14 text-right ${
|
|
182
|
+
selected ? 'text-foreground font-medium' : 'text-muted-foreground'
|
|
183
|
+
}`}>
|
|
184
|
+
{w.label}
|
|
185
|
+
</span>
|
|
186
|
+
</button>
|
|
187
|
+
);
|
|
188
|
+
})}
|
|
104
189
|
</div>
|
|
105
190
|
</SettingGroup>
|
|
106
191
|
|
|
107
|
-
|
|
192
|
+
</SettingCard>
|
|
193
|
+
|
|
194
|
+
{/* ── Card 2: Preferences — theme, language ── */}
|
|
195
|
+
<SettingCard icon={<Palette size={15} />} title={a.preferencesTitle ?? 'Preferences'}>
|
|
196
|
+
|
|
197
|
+
{/* ── Theme — amber pills ── */}
|
|
108
198
|
<SettingGroup icon={<Sun size={14} />} label={a.colorTheme}>
|
|
109
|
-
<
|
|
199
|
+
<PillSelector
|
|
110
200
|
options={[
|
|
111
|
-
{ value: 'system', label: a.system, icon: <Monitor size={
|
|
112
|
-
{ value: 'dark', label: a.dark, icon: <Moon size={
|
|
113
|
-
{ value: 'light', label: a.light, icon: <Sun size={
|
|
201
|
+
{ value: 'system', label: a.system, icon: <Monitor size={14} /> },
|
|
202
|
+
{ value: 'dark', label: a.dark, icon: <Moon size={14} /> },
|
|
203
|
+
{ value: 'light', label: a.light, icon: <Sun size={14} /> },
|
|
114
204
|
]}
|
|
115
205
|
value={themePref}
|
|
116
206
|
onChange={v => {
|
|
@@ -125,12 +215,12 @@ export function AppearanceTab({ font, setFont, contentWidth, setContentWidth, da
|
|
|
125
215
|
/>
|
|
126
216
|
</SettingGroup>
|
|
127
217
|
|
|
128
|
-
{/* Language */}
|
|
218
|
+
{/* ── Language — amber pills ── */}
|
|
129
219
|
<SettingGroup icon={<Globe size={14} />} label={a.language}>
|
|
130
|
-
<
|
|
220
|
+
<PillSelector
|
|
131
221
|
options={[
|
|
132
|
-
{ value: 'system', label: a.system, icon: <Monitor size={
|
|
133
|
-
{ value: 'en', label: '
|
|
222
|
+
{ value: 'system', label: a.system, icon: <Monitor size={14} /> },
|
|
223
|
+
{ value: 'en', label: 'EN' },
|
|
134
224
|
{ value: 'zh', label: '中文' },
|
|
135
225
|
]}
|
|
136
226
|
value={localePref}
|
|
@@ -141,29 +231,30 @@ export function AppearanceTab({ font, setFont, contentWidth, setContentWidth, da
|
|
|
141
231
|
? (navigator.language.startsWith('zh') ? 'zh' : 'en')
|
|
142
232
|
: v as Locale;
|
|
143
233
|
setLocale(resolved);
|
|
144
|
-
// Sync cookie for SSR (write resolved value, not 'system')
|
|
145
234
|
document.cookie = `locale=${resolved};path=/;max-age=31536000;SameSite=Lax`;
|
|
146
235
|
}}
|
|
147
236
|
/>
|
|
148
237
|
</SettingGroup>
|
|
149
238
|
|
|
150
|
-
|
|
239
|
+
</SettingCard>
|
|
240
|
+
|
|
241
|
+
<p className="text-xs text-muted-foreground/40 px-0.5">{a.browserNote}</p>
|
|
151
242
|
|
|
152
|
-
{/* Keyboard Shortcuts */}
|
|
243
|
+
{/* ── Keyboard Shortcuts ── */}
|
|
153
244
|
<div className="border-t border-border pt-4">
|
|
154
245
|
<button
|
|
155
246
|
type="button"
|
|
156
247
|
onClick={() => setShowShortcuts(!showShortcuts)}
|
|
157
|
-
className="flex items-center gap-1.5 text-
|
|
248
|
+
className="flex items-center gap-1.5 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
158
249
|
>
|
|
159
|
-
{showShortcuts ? <ChevronDown size={
|
|
250
|
+
{showShortcuts ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
|
160
251
|
{t.settings.tabs.shortcuts}
|
|
161
252
|
</button>
|
|
162
253
|
{showShortcuts && (
|
|
163
254
|
<div className="mt-3 space-y-0.5">
|
|
164
255
|
{t.shortcuts.map((s: { readonly description: string; readonly keys: readonly string[] }, i: number) => (
|
|
165
256
|
<div key={i} className="flex items-center justify-between py-1.5 px-1">
|
|
166
|
-
<span className="text-
|
|
257
|
+
<span className="text-sm text-foreground">{s.description}</span>
|
|
167
258
|
<div className="flex items-center gap-1">
|
|
168
259
|
{s.keys.map((k: string, j: number) => (
|
|
169
260
|
<kbd key={j} className="px-1.5 py-0.5 text-2xs font-mono bg-muted border border-border rounded text-muted-foreground">{k}</kbd>
|
|
@@ -4,7 +4,7 @@ import { useState, useEffect, useCallback, useSyncExternalStore, useRef } from '
|
|
|
4
4
|
import { Copy, Check, RefreshCw, Trash2, Sparkles, ChevronDown, ChevronRight, Loader2, Cpu, Zap, Database as DatabaseIcon, HardDrive, RotateCcw } from 'lucide-react';
|
|
5
5
|
import { toast } from '@/lib/toast';
|
|
6
6
|
import type { KnowledgeTabProps } from './types';
|
|
7
|
-
import { Field, Input, EnvBadge, SectionLabel, Toggle } from './Primitives';
|
|
7
|
+
import { Field, Input, EnvBadge, SectionLabel, Toggle, SettingCard, SettingRow } from './Primitives';
|
|
8
8
|
import { ConfirmDialog } from '@/components/agents/AgentsPrimitives';
|
|
9
9
|
import { apiFetch } from '@/lib/api';
|
|
10
10
|
import { copyToClipboard } from '@/lib/clipboard';
|
|
@@ -135,164 +135,159 @@ export function KnowledgeTab({ data, setData, t }: KnowledgeTabProps) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
return (
|
|
138
|
-
<div className="space-y-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
<div className="space-y-4">
|
|
139
|
+
{/* ── Card 1: Knowledge Base ── */}
|
|
140
|
+
<SettingCard
|
|
141
|
+
icon={<DatabaseIcon size={15} />}
|
|
142
|
+
title="Knowledge Base"
|
|
143
|
+
description={k.sopRootHint}
|
|
144
144
|
>
|
|
145
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
/>
|
|
150
|
-
</Field>
|
|
151
|
-
|
|
152
|
-
<div className="flex items-center justify-between">
|
|
153
|
-
<div>
|
|
154
|
-
<div className="text-sm text-foreground">{k.showHiddenFiles}</div>
|
|
155
|
-
<div className="text-xs text-muted-foreground mt-0.5">{k.showHiddenFilesHint}</div>
|
|
156
|
-
</div>
|
|
157
|
-
<Toggle checked={showHidden} onChange={() => {
|
|
158
|
-
const next = !showHidden;
|
|
159
|
-
setShowHidden(next);
|
|
160
|
-
setShowHiddenFiles(next);
|
|
161
|
-
}} />
|
|
162
|
-
</div>
|
|
163
|
-
|
|
164
|
-
{exampleCount !== null && exampleCount > 0 && cleanupResult === null && (
|
|
165
|
-
<div className="flex items-center justify-between">
|
|
166
|
-
<div>
|
|
167
|
-
<div className="text-sm text-foreground">{k.cleanupExamples}</div>
|
|
168
|
-
<div className="text-xs text-muted-foreground mt-0.5">{k.cleanupExamplesHint}</div>
|
|
169
|
-
</div>
|
|
170
|
-
<button
|
|
171
|
-
onClick={() => setShowCleanupConfirm(true)}
|
|
172
|
-
disabled={cleaningUp}
|
|
173
|
-
title={cleaningUp ? t.hints.cleanupInProgress : undefined}
|
|
174
|
-
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0 disabled:opacity-50"
|
|
175
|
-
>
|
|
176
|
-
{cleaningUp ? <Loader2 size={12} className="animate-spin" /> : <Trash2 size={12} />}
|
|
177
|
-
{k.cleanupExamplesButton}
|
|
178
|
-
<span className="ml-1 tabular-nums text-2xs opacity-70">{exampleCount}</span>
|
|
179
|
-
</button>
|
|
180
|
-
</div>
|
|
181
|
-
)}
|
|
182
|
-
{cleanupResult !== null && (
|
|
183
|
-
<div className="flex items-center gap-2 text-xs text-success">
|
|
184
|
-
<Check size={14} />
|
|
185
|
-
{k.cleanupExamplesDone(cleanupResult)}
|
|
186
|
-
</div>
|
|
187
|
-
)}
|
|
188
|
-
|
|
189
|
-
<div className="border-t border-border pt-5">
|
|
190
|
-
<SectionLabel>Security</SectionLabel>
|
|
191
|
-
</div>
|
|
192
|
-
|
|
193
|
-
<Field label={k.webPassword} hint={k.webPasswordHint}>
|
|
194
|
-
<div className="flex gap-2">
|
|
145
|
+
<Field
|
|
146
|
+
label={<>{k.sopRoot} <EnvBadge overridden={env.MIND_ROOT} /></>}
|
|
147
|
+
hint={env.MIND_ROOT ? k.envNote : k.sopRootHint}
|
|
148
|
+
>
|
|
195
149
|
<Input
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
onFocus={() => { if (isPasswordMasked) setData(d => d ? { ...d, webPassword: '' } : d); }}
|
|
200
|
-
placeholder="Leave empty to disable"
|
|
150
|
+
value={data.mindRoot}
|
|
151
|
+
onChange={e => setData(d => d ? { ...d, mindRoot: e.target.value } : d)}
|
|
152
|
+
placeholder="/path/to/your/notes"
|
|
201
153
|
/>
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
154
|
+
</Field>
|
|
155
|
+
|
|
156
|
+
<SettingRow label={k.showHiddenFiles} hint={k.showHiddenFilesHint}>
|
|
157
|
+
<Toggle checked={showHidden} onChange={() => {
|
|
158
|
+
const next = !showHidden;
|
|
159
|
+
setShowHidden(next);
|
|
160
|
+
setShowHiddenFiles(next);
|
|
161
|
+
}} />
|
|
162
|
+
</SettingRow>
|
|
163
|
+
|
|
164
|
+
{exampleCount !== null && exampleCount > 0 && cleanupResult === null && (
|
|
165
|
+
<SettingRow label={k.cleanupExamples} hint={k.cleanupExamplesHint}>
|
|
166
|
+
<button
|
|
167
|
+
onClick={() => setShowCleanupConfirm(true)}
|
|
168
|
+
disabled={cleaningUp}
|
|
169
|
+
title={cleaningUp ? t.hints.cleanupInProgress : undefined}
|
|
170
|
+
className="flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0 disabled:opacity-50"
|
|
171
|
+
>
|
|
172
|
+
{cleaningUp ? <Loader2 size={12} className="animate-spin" /> : <Trash2 size={12} />}
|
|
173
|
+
{k.cleanupExamplesButton}
|
|
174
|
+
<span className="ml-1 tabular-nums text-2xs opacity-70">{exampleCount}</span>
|
|
175
|
+
</button>
|
|
176
|
+
</SettingRow>
|
|
177
|
+
)}
|
|
178
|
+
{cleanupResult !== null && (
|
|
179
|
+
<div className="flex items-center gap-2 text-xs text-success">
|
|
180
|
+
<Check size={14} />
|
|
181
|
+
{k.cleanupExamplesDone(cleanupResult)}
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
</SettingCard>
|
|
211
185
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
186
|
+
{/* ── Card 2: Security ── */}
|
|
187
|
+
<SettingCard
|
|
188
|
+
icon={<HardDrive size={15} />}
|
|
189
|
+
title="Security"
|
|
215
190
|
>
|
|
216
|
-
<
|
|
217
|
-
{/* Token display */}
|
|
218
|
-
<div className="flex items-center gap-2 px-3 py-2 bg-background border border-border rounded-lg min-h-[38px]">
|
|
219
|
-
<code className="flex-1 text-xs font-mono text-foreground break-all select-all">
|
|
220
|
-
{displayToken || <span className="text-muted-foreground italic">— not set —</span>}
|
|
221
|
-
</code>
|
|
222
|
-
{displayToken && (
|
|
223
|
-
<button
|
|
224
|
-
type="button"
|
|
225
|
-
onClick={handleCopy}
|
|
226
|
-
className="shrink-0 p-1 rounded text-muted-foreground hover:text-foreground transition-colors"
|
|
227
|
-
title={k.authTokenCopy}
|
|
228
|
-
>
|
|
229
|
-
<Copy size={13} />
|
|
230
|
-
</button>
|
|
231
|
-
)}
|
|
232
|
-
</div>
|
|
233
|
-
{/* MCP port info */}
|
|
234
|
-
{data.mcpPort && (
|
|
235
|
-
<p className="text-xs text-muted-foreground">
|
|
236
|
-
{k.authTokenMcpPort}: <code className="font-mono">{data.mcpPort}</code>
|
|
237
|
-
{displayToken && (
|
|
238
|
-
<> · MCP URL: <code className="font-mono select-all">
|
|
239
|
-
{`${origin}:${data.mcpPort}/mcp`}
|
|
240
|
-
</code></>
|
|
241
|
-
)}
|
|
242
|
-
</p>
|
|
243
|
-
)}
|
|
244
|
-
{/* Action buttons */}
|
|
191
|
+
<Field label={k.webPassword} hint={k.webPasswordHint}>
|
|
245
192
|
<div className="flex gap-2">
|
|
193
|
+
<Input
|
|
194
|
+
type={showPassword ? 'text' : 'password'}
|
|
195
|
+
value={isPasswordMasked ? '••••••••' : (data.webPassword ?? '')}
|
|
196
|
+
onChange={e => setData(d => d ? { ...d, webPassword: e.target.value } : d)}
|
|
197
|
+
onFocus={() => { if (isPasswordMasked) setData(d => d ? { ...d, webPassword: '' } : d); }}
|
|
198
|
+
placeholder="Leave empty to disable"
|
|
199
|
+
/>
|
|
246
200
|
<button
|
|
247
201
|
type="button"
|
|
248
|
-
onClick={
|
|
249
|
-
|
|
250
|
-
title={resetting ? t.hints.tokenResetInProgress : undefined}
|
|
251
|
-
className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
202
|
+
onClick={() => setShowPassword(v => !v)}
|
|
203
|
+
className="px-3 py-2 text-sm rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0"
|
|
252
204
|
>
|
|
253
|
-
|
|
254
|
-
{k.authTokenReset}
|
|
205
|
+
{showPassword ? 'Hide' : 'Show'}
|
|
255
206
|
</button>
|
|
256
|
-
|
|
207
|
+
</div>
|
|
208
|
+
</Field>
|
|
209
|
+
|
|
210
|
+
<Field
|
|
211
|
+
label={k.authToken}
|
|
212
|
+
hint={hasToken ? k.authTokenHint : k.authTokenNone}
|
|
213
|
+
>
|
|
214
|
+
<div className="space-y-2">
|
|
215
|
+
{/* Token display */}
|
|
216
|
+
<div className="flex items-center gap-2 px-3 py-2 bg-background border border-border rounded-lg min-h-[38px]">
|
|
217
|
+
<code className="flex-1 text-xs font-mono text-foreground break-all select-all">
|
|
218
|
+
{displayToken || <span className="text-muted-foreground italic">— not set —</span>}
|
|
219
|
+
</code>
|
|
220
|
+
{displayToken && (
|
|
221
|
+
<button
|
|
222
|
+
type="button"
|
|
223
|
+
onClick={handleCopy}
|
|
224
|
+
className="shrink-0 p-1 rounded text-muted-foreground hover:text-foreground transition-colors"
|
|
225
|
+
title={k.authTokenCopy}
|
|
226
|
+
>
|
|
227
|
+
<Copy size={13} />
|
|
228
|
+
</button>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
{/* MCP port info */}
|
|
232
|
+
{data.mcpPort && (
|
|
233
|
+
<p className="text-xs text-muted-foreground">
|
|
234
|
+
{k.authTokenMcpPort}: <code className="font-mono">{data.mcpPort}</code>
|
|
235
|
+
{displayToken && (
|
|
236
|
+
<> · MCP URL: <code className="font-mono select-all">
|
|
237
|
+
{`${origin}:${data.mcpPort}/mcp`}
|
|
238
|
+
</code></>
|
|
239
|
+
)}
|
|
240
|
+
</p>
|
|
241
|
+
)}
|
|
242
|
+
{/* Action buttons */}
|
|
243
|
+
<div className="flex gap-2">
|
|
257
244
|
<button
|
|
258
245
|
type="button"
|
|
259
|
-
onClick={
|
|
260
|
-
|
|
246
|
+
onClick={handleResetToken}
|
|
247
|
+
disabled={resetting}
|
|
248
|
+
title={resetting ? t.hints.tokenResetInProgress : undefined}
|
|
249
|
+
className="flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
261
250
|
>
|
|
262
|
-
<
|
|
263
|
-
{k.
|
|
251
|
+
<RefreshCw size={14} className={resetting ? 'animate-spin' : ''} />
|
|
252
|
+
{k.authTokenReset}
|
|
264
253
|
</button>
|
|
254
|
+
{hasToken && (
|
|
255
|
+
<button
|
|
256
|
+
type="button"
|
|
257
|
+
onClick={handleClearToken}
|
|
258
|
+
className="flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg border border-border text-muted-foreground hover:text-destructive hover:border-destructive/50 transition-colors"
|
|
259
|
+
>
|
|
260
|
+
<Trash2 size={14} />
|
|
261
|
+
{k.authTokenClear}
|
|
262
|
+
</button>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
{revealedToken && (
|
|
266
|
+
<p className="text-xs text-[var(--amber-text)]">
|
|
267
|
+
New token generated. Copy it now — it won't be shown in full again.
|
|
268
|
+
</p>
|
|
265
269
|
)}
|
|
266
270
|
</div>
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
New token generated. Copy it now — it won't be shown in full again.
|
|
270
|
-
</p>
|
|
271
|
-
)}
|
|
272
|
-
</div>
|
|
273
|
-
</Field>
|
|
271
|
+
</Field>
|
|
272
|
+
</SettingCard>
|
|
274
273
|
|
|
275
|
-
{/* Getting Started
|
|
274
|
+
{/* ── Card 3: Getting Started ── */}
|
|
276
275
|
{guideActive !== null && (
|
|
277
|
-
<
|
|
278
|
-
<
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
<div>
|
|
283
|
-
<div className="text-sm text-foreground">{t.guide?.showGuide ?? 'Show getting started guide'}</div>
|
|
284
|
-
</div>
|
|
285
|
-
</div>
|
|
276
|
+
<SettingCard
|
|
277
|
+
icon={<Sparkles size={15} />}
|
|
278
|
+
title={t.guide?.title ?? 'Getting Started'}
|
|
279
|
+
>
|
|
280
|
+
<SettingRow label={t.guide?.showGuide ?? 'Show getting started guide'}>
|
|
286
281
|
<Toggle checked={!guideDismissed} onChange={() => handleGuideToggle()} />
|
|
287
|
-
</
|
|
282
|
+
</SettingRow>
|
|
288
283
|
<button
|
|
289
284
|
onClick={handleRestartWalkthrough}
|
|
290
|
-
className="flex items-center gap-1.5 mt-2 px-3 py-1.5 text-
|
|
285
|
+
className="flex items-center gap-1.5 mt-2 px-3 py-1.5 text-sm rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
|
291
286
|
>
|
|
292
|
-
<RotateCcw size={
|
|
287
|
+
<RotateCcw size={14} />
|
|
293
288
|
{k.restartWalkthrough ?? 'Restart walkthrough'}
|
|
294
289
|
</button>
|
|
295
|
-
</
|
|
290
|
+
</SettingCard>
|
|
296
291
|
)}
|
|
297
292
|
|
|
298
293
|
{/* System Monitoring — collapsible */}
|
|
@@ -380,7 +375,7 @@ function MonitoringSection() {
|
|
|
380
375
|
<div className="border-t border-border pt-5">
|
|
381
376
|
<button
|
|
382
377
|
onClick={() => setExpanded(v => !v)}
|
|
383
|
-
className="flex items-center gap-1.5 text-
|
|
378
|
+
className="flex items-center gap-1.5 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors w-full"
|
|
384
379
|
>
|
|
385
380
|
{expanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
|
386
381
|
<Cpu size={12} />
|