@geminilight/mindos 0.5.42 → 0.5.44
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/api/ask-sessions/route.ts +6 -4
- package/app/app/api/update-status/route.ts +19 -0
- package/app/app/layout.tsx +1 -1
- 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/ThemeToggle.tsx +1 -1
- 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/AppearanceTab.tsx +126 -66
- 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/LocaleContext.tsx +6 -4
- package/app/lib/i18n-en.ts +26 -4
- package/app/lib/i18n-zh.ts +26 -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
|
@@ -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>
|
|
@@ -1,112 +1,172 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
-
import { ChevronDown, ChevronRight } from 'lucide-react';
|
|
4
|
+
import { ChevronDown, ChevronRight, Sun, Moon, Monitor, Type, Columns3, Globe } from 'lucide-react';
|
|
5
5
|
import { Locale } from '@/lib/i18n';
|
|
6
6
|
import { CONTENT_WIDTHS, FONTS, AppearanceTabProps } from './types';
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
/* ── Segmented Control ── */
|
|
9
|
+
function SegmentedControl<T extends string>({ options, value, onChange }: {
|
|
10
|
+
options: { value: T; label: string; icon?: React.ReactNode }[];
|
|
11
|
+
value: T;
|
|
12
|
+
onChange: (v: T) => void;
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<div className="flex rounded-lg border border-border bg-muted/30 p-0.5 gap-0.5">
|
|
16
|
+
{options.map(opt => (
|
|
17
|
+
<button
|
|
18
|
+
key={opt.value}
|
|
19
|
+
type="button"
|
|
20
|
+
onClick={() => onChange(opt.value)}
|
|
21
|
+
className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md transition-all ${
|
|
22
|
+
value === opt.value
|
|
23
|
+
? 'bg-card text-foreground shadow-sm'
|
|
24
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
25
|
+
}`}
|
|
26
|
+
>
|
|
27
|
+
{opt.icon}
|
|
28
|
+
{opt.label}
|
|
29
|
+
</button>
|
|
30
|
+
))}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* ── Setting Group ── */
|
|
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
|
+
}
|
|
8
47
|
|
|
9
48
|
export function AppearanceTab({ font, setFont, contentWidth, setContentWidth, dark, setDark, locale, setLocale, t }: AppearanceTabProps) {
|
|
10
49
|
const [showShortcuts, setShowShortcuts] = useState(false);
|
|
50
|
+
const [themePref, setThemePref] = useState<string>(() =>
|
|
51
|
+
typeof window !== 'undefined' ? (localStorage.getItem('theme') ?? 'system') : 'system'
|
|
52
|
+
);
|
|
53
|
+
const [localePref, setLocalePref] = useState<string>(() =>
|
|
54
|
+
typeof window !== 'undefined' ? (localStorage.getItem('locale') ?? 'system') : 'system'
|
|
55
|
+
);
|
|
56
|
+
const a = t.settings.appearance;
|
|
11
57
|
|
|
12
58
|
return (
|
|
13
|
-
<div className="space-y-
|
|
14
|
-
|
|
15
|
-
|
|
59
|
+
<div className="space-y-6">
|
|
60
|
+
{/* Font */}
|
|
61
|
+
<SettingGroup icon={<Type size={14} />} label={a.readingFont}>
|
|
62
|
+
<div className="flex flex-wrap gap-1.5">
|
|
16
63
|
{FONTS.map(f => (
|
|
17
|
-
<
|
|
64
|
+
<button
|
|
65
|
+
key={f.value}
|
|
66
|
+
type="button"
|
|
67
|
+
onClick={() => setFont(f.value)}
|
|
68
|
+
className={`px-3 py-1.5 text-xs rounded-lg border transition-all ${
|
|
69
|
+
font === f.value
|
|
70
|
+
? 'border-amber-500 bg-amber-500/10 text-foreground font-medium shadow-sm'
|
|
71
|
+
: 'border-border text-muted-foreground hover:text-foreground hover:bg-muted'
|
|
72
|
+
}`}
|
|
73
|
+
style={{ fontFamily: f.style.fontFamily }}
|
|
74
|
+
>
|
|
75
|
+
{f.label}
|
|
76
|
+
</button>
|
|
18
77
|
))}
|
|
19
|
-
</
|
|
20
|
-
<p
|
|
21
|
-
|
|
78
|
+
</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}
|
|
22
84
|
</p>
|
|
23
|
-
</
|
|
85
|
+
</SettingGroup>
|
|
24
86
|
|
|
25
|
-
|
|
26
|
-
|
|
87
|
+
{/* Content Width */}
|
|
88
|
+
<SettingGroup icon={<Columns3 size={14} />} label={a.contentWidth}>
|
|
89
|
+
<div className="flex flex-wrap gap-1.5">
|
|
27
90
|
{CONTENT_WIDTHS.map(w => (
|
|
28
91
|
<button
|
|
29
92
|
key={w.value}
|
|
30
93
|
type="button"
|
|
31
94
|
onClick={() => setContentWidth(w.value)}
|
|
32
|
-
className={`px-3 py-
|
|
95
|
+
className={`px-3 py-1.5 text-xs rounded-lg border transition-all ${
|
|
33
96
|
contentWidth === w.value
|
|
34
|
-
? 'border-amber-500 bg-amber-500/10 text-foreground'
|
|
35
|
-
: 'border-border text-muted-foreground hover:
|
|
97
|
+
? 'border-amber-500 bg-amber-500/10 text-foreground font-medium shadow-sm'
|
|
98
|
+
: 'border-border text-muted-foreground hover:text-foreground hover:bg-muted'
|
|
36
99
|
}`}
|
|
37
100
|
>
|
|
38
101
|
{w.label}
|
|
39
102
|
</button>
|
|
40
103
|
))}
|
|
41
104
|
</div>
|
|
42
|
-
</
|
|
105
|
+
</SettingGroup>
|
|
43
106
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
{ value: '
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
>
|
|
65
|
-
{opt.label}
|
|
66
|
-
</button>
|
|
67
|
-
))}
|
|
68
|
-
</div>
|
|
69
|
-
</Field>
|
|
107
|
+
{/* Theme */}
|
|
108
|
+
<SettingGroup icon={<Sun size={14} />} label={a.colorTheme}>
|
|
109
|
+
<SegmentedControl
|
|
110
|
+
options={[
|
|
111
|
+
{ value: 'system', label: a.system, icon: <Monitor size={12} /> },
|
|
112
|
+
{ value: 'dark', label: a.dark, icon: <Moon size={12} /> },
|
|
113
|
+
{ value: 'light', label: a.light, icon: <Sun size={12} /> },
|
|
114
|
+
]}
|
|
115
|
+
value={themePref}
|
|
116
|
+
onChange={v => {
|
|
117
|
+
setThemePref(v);
|
|
118
|
+
localStorage.setItem('theme', v);
|
|
119
|
+
const isDark = v === 'system'
|
|
120
|
+
? window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
121
|
+
: v === 'dark';
|
|
122
|
+
setDark(isDark);
|
|
123
|
+
document.documentElement.classList.toggle('dark', isDark);
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
126
|
+
</SettingGroup>
|
|
70
127
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
128
|
+
{/* Language */}
|
|
129
|
+
<SettingGroup icon={<Globe size={14} />} label={a.language}>
|
|
130
|
+
<SegmentedControl
|
|
131
|
+
options={[
|
|
132
|
+
{ value: 'system', label: a.system, icon: <Monitor size={12} /> },
|
|
133
|
+
{ value: 'en', label: 'English' },
|
|
134
|
+
{ value: 'zh', label: '中文' },
|
|
135
|
+
]}
|
|
136
|
+
value={localePref}
|
|
137
|
+
onChange={v => {
|
|
138
|
+
setLocalePref(v);
|
|
139
|
+
localStorage.setItem('locale', v);
|
|
140
|
+
const resolved: Locale = v === 'system'
|
|
141
|
+
? (navigator.language.startsWith('zh') ? 'zh' : 'en')
|
|
142
|
+
: v as Locale;
|
|
143
|
+
setLocale(resolved);
|
|
144
|
+
// Sync cookie for SSR (write resolved value, not 'system')
|
|
145
|
+
document.cookie = `locale=${resolved};path=/;max-age=31536000;SameSite=Lax`;
|
|
146
|
+
}}
|
|
147
|
+
/>
|
|
148
|
+
</SettingGroup>
|
|
89
149
|
|
|
90
|
-
<p className="text-xs text-muted-foreground">{
|
|
150
|
+
<p className="text-xs text-muted-foreground/60 px-0.5">{a.browserNote}</p>
|
|
91
151
|
|
|
92
152
|
{/* Keyboard Shortcuts */}
|
|
93
153
|
<div className="border-t border-border pt-4">
|
|
94
154
|
<button
|
|
95
155
|
type="button"
|
|
96
156
|
onClick={() => setShowShortcuts(!showShortcuts)}
|
|
97
|
-
className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground
|
|
157
|
+
className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
|
|
98
158
|
>
|
|
99
159
|
{showShortcuts ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
|
|
100
160
|
{t.settings.tabs.shortcuts}
|
|
101
161
|
</button>
|
|
102
162
|
{showShortcuts && (
|
|
103
|
-
<div className="mt-3 space-y-
|
|
163
|
+
<div className="mt-3 space-y-0.5">
|
|
104
164
|
{t.shortcuts.map((s: { readonly description: string; readonly keys: readonly string[] }, i: number) => (
|
|
105
|
-
<div key={i} className="flex items-center justify-between py-
|
|
106
|
-
<span className="text-
|
|
165
|
+
<div key={i} className="flex items-center justify-between py-1.5 px-1">
|
|
166
|
+
<span className="text-xs text-foreground">{s.description}</span>
|
|
107
167
|
<div className="flex items-center gap-1">
|
|
108
168
|
{s.keys.map((k: string, j: number) => (
|
|
109
|
-
<kbd key={j} className="px-
|
|
169
|
+
<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>
|
|
110
170
|
))}
|
|
111
171
|
</div>
|
|
112
172
|
</div>
|
|
@@ -153,8 +153,8 @@ export default function SettingsContent({ visible, initialTab, variant, onClose
|
|
|
153
153
|
{ id: 'ai', label: t.settings.tabs.ai, icon: <Sparkles size={iconSize} /> },
|
|
154
154
|
{ id: 'mcp', label: t.settings.tabs.mcp ?? 'MCP & Skills', icon: <Plug size={iconSize} /> },
|
|
155
155
|
{ id: 'knowledge', label: t.settings.tabs.knowledge, icon: <Settings size={iconSize} /> },
|
|
156
|
-
{ id: 'sync', label: t.settings.tabs.sync ?? 'Sync', icon: <RefreshCw size={iconSize} /> },
|
|
157
156
|
{ id: 'appearance', label: t.settings.tabs.appearance, icon: <Palette size={iconSize} /> },
|
|
157
|
+
{ id: 'sync', label: t.settings.tabs.sync ?? 'Sync', icon: <RefreshCw size={iconSize} /> },
|
|
158
158
|
{ id: 'update', label: t.settings.tabs.update ?? 'Update', icon: <Download size={iconSize} />, badge: hasUpdate },
|
|
159
159
|
];
|
|
160
160
|
|