@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
|
@@ -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
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
4
|
-
import { Download, RefreshCw, CheckCircle2, AlertCircle, Loader2, ExternalLink } from 'lucide-react';
|
|
4
|
+
import { Download, RefreshCw, CheckCircle2, AlertCircle, Loader2, ExternalLink, Circle } from 'lucide-react';
|
|
5
5
|
import { apiFetch } from '@/lib/api';
|
|
6
6
|
import { useLocale } from '@/lib/LocaleContext';
|
|
7
7
|
|
|
@@ -11,18 +11,53 @@ interface UpdateInfo {
|
|
|
11
11
|
hasUpdate: boolean;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
interface StageInfo {
|
|
15
|
+
id: string;
|
|
16
|
+
status: 'pending' | 'running' | 'done' | 'failed';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface UpdateStatus {
|
|
20
|
+
stage: string;
|
|
21
|
+
stages: StageInfo[];
|
|
22
|
+
error: string | null;
|
|
23
|
+
version: { from: string | null; to: string | null } | null;
|
|
24
|
+
startedAt: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
14
27
|
type UpdateState = 'idle' | 'checking' | 'updating' | 'updated' | 'error' | 'timeout';
|
|
15
28
|
|
|
16
29
|
const CHANGELOG_URL = 'https://github.com/GeminiLight/MindOS/releases';
|
|
17
|
-
const POLL_INTERVAL =
|
|
18
|
-
const POLL_TIMEOUT =
|
|
30
|
+
const POLL_INTERVAL = 3_000;
|
|
31
|
+
const POLL_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
|
32
|
+
|
|
33
|
+
const STAGE_LABELS: Record<string, { en: string; zh: string }> = {
|
|
34
|
+
downloading: { en: 'Downloading update', zh: '下载更新' },
|
|
35
|
+
skills: { en: 'Updating skills', zh: '更新 Skills' },
|
|
36
|
+
rebuilding: { en: 'Rebuilding app', zh: '重新构建应用' },
|
|
37
|
+
restarting: { en: 'Restarting server', zh: '重启服务' },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function StageIcon({ status }: { status: string }) {
|
|
41
|
+
switch (status) {
|
|
42
|
+
case 'done':
|
|
43
|
+
return <CheckCircle2 size={14} className="text-success shrink-0" />;
|
|
44
|
+
case 'running':
|
|
45
|
+
return <Loader2 size={14} className="animate-spin shrink-0" style={{ color: 'var(--amber)' }} />;
|
|
46
|
+
case 'failed':
|
|
47
|
+
return <AlertCircle size={14} className="text-destructive shrink-0" />;
|
|
48
|
+
default:
|
|
49
|
+
return <Circle size={14} className="text-muted-foreground/40 shrink-0" />;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
19
52
|
|
|
20
53
|
export function UpdateTab() {
|
|
21
|
-
const { t } = useLocale();
|
|
54
|
+
const { t, locale } = useLocale();
|
|
22
55
|
const u = t.settings.update;
|
|
23
56
|
const [info, setInfo] = useState<UpdateInfo | null>(null);
|
|
24
57
|
const [state, setState] = useState<UpdateState>('idle');
|
|
25
58
|
const [errorMsg, setErrorMsg] = useState('');
|
|
59
|
+
const [stages, setStages] = useState<StageInfo[]>([]);
|
|
60
|
+
const [updateError, setUpdateError] = useState<string | null>(null);
|
|
26
61
|
const pollRef = useRef<ReturnType<typeof setInterval>>(undefined);
|
|
27
62
|
const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
28
63
|
const originalVersion = useRef<string>('');
|
|
@@ -43,16 +78,23 @@ export function UpdateTab() {
|
|
|
43
78
|
|
|
44
79
|
useEffect(() => { checkUpdate(); }, [checkUpdate]);
|
|
45
80
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
clearTimeout(timeoutRef.current);
|
|
50
|
-
};
|
|
81
|
+
const cleanup = useCallback(() => {
|
|
82
|
+
clearInterval(pollRef.current);
|
|
83
|
+
clearTimeout(timeoutRef.current);
|
|
51
84
|
}, []);
|
|
52
85
|
|
|
86
|
+
useEffect(() => cleanup, [cleanup]);
|
|
87
|
+
|
|
53
88
|
const handleUpdate = useCallback(async () => {
|
|
54
89
|
setState('updating');
|
|
55
90
|
setErrorMsg('');
|
|
91
|
+
setUpdateError(null);
|
|
92
|
+
setStages([
|
|
93
|
+
{ id: 'downloading', status: 'pending' },
|
|
94
|
+
{ id: 'skills', status: 'pending' },
|
|
95
|
+
{ id: 'rebuilding', status: 'pending' },
|
|
96
|
+
{ id: 'restarting', status: 'pending' },
|
|
97
|
+
]);
|
|
56
98
|
|
|
57
99
|
try {
|
|
58
100
|
await apiFetch('/api/update', { method: 'POST' });
|
|
@@ -60,26 +102,67 @@ export function UpdateTab() {
|
|
|
60
102
|
// Expected — server may die during update
|
|
61
103
|
}
|
|
62
104
|
|
|
105
|
+
// Poll update-status for stage progress
|
|
63
106
|
pollRef.current = setInterval(async () => {
|
|
107
|
+
// Try status endpoint first (may fail when server is restarting)
|
|
64
108
|
try {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
109
|
+
const status = await apiFetch<UpdateStatus>('/api/update-status', { timeout: 5000 });
|
|
110
|
+
|
|
111
|
+
if (status.stages?.length > 0) {
|
|
112
|
+
setStages(status.stages);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (status.stage === 'failed') {
|
|
116
|
+
cleanup();
|
|
117
|
+
setUpdateError(status.error || 'Update failed');
|
|
118
|
+
setState('error');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (status.stage === 'done') {
|
|
123
|
+
// Verify version actually changed
|
|
124
|
+
try {
|
|
125
|
+
const data = await apiFetch<UpdateInfo>('/api/update-check');
|
|
126
|
+
if (data.current !== originalVersion.current) {
|
|
127
|
+
cleanup();
|
|
128
|
+
setInfo(data);
|
|
129
|
+
setState('updated');
|
|
130
|
+
setTimeout(() => window.location.reload(), 2000);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
} catch { /* new server may not be fully ready */ }
|
|
72
134
|
}
|
|
73
135
|
} catch {
|
|
74
|
-
// Server
|
|
136
|
+
// Server restarting — also try update-check as fallback
|
|
137
|
+
try {
|
|
138
|
+
const data = await apiFetch<UpdateInfo>('/api/update-check', { timeout: 5000 });
|
|
139
|
+
if (data.current !== originalVersion.current) {
|
|
140
|
+
cleanup();
|
|
141
|
+
setStages(prev => prev.map(s => ({ ...s, status: 'done' as const })));
|
|
142
|
+
setInfo(data);
|
|
143
|
+
setState('updated');
|
|
144
|
+
setTimeout(() => window.location.reload(), 2000);
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
// Both endpoints down — server still restarting
|
|
148
|
+
}
|
|
75
149
|
}
|
|
76
150
|
}, POLL_INTERVAL);
|
|
77
151
|
|
|
78
152
|
timeoutRef.current = setTimeout(() => {
|
|
79
|
-
|
|
153
|
+
cleanup();
|
|
80
154
|
setState('timeout');
|
|
81
155
|
}, POLL_TIMEOUT);
|
|
82
|
-
}, []);
|
|
156
|
+
}, [cleanup]);
|
|
157
|
+
|
|
158
|
+
const handleRetry = useCallback(() => {
|
|
159
|
+
setUpdateError(null);
|
|
160
|
+
handleUpdate();
|
|
161
|
+
}, [handleUpdate]);
|
|
162
|
+
|
|
163
|
+
const lang = locale === 'zh' ? 'zh' : 'en';
|
|
164
|
+
const doneCount = stages.filter(s => s.status === 'done').length;
|
|
165
|
+
const progress = stages.length > 0 ? Math.round((doneCount / stages.length) * 100) : 0;
|
|
83
166
|
|
|
84
167
|
return (
|
|
85
168
|
<div className="space-y-5">
|
|
@@ -114,11 +197,27 @@ export function UpdateTab() {
|
|
|
114
197
|
)}
|
|
115
198
|
|
|
116
199
|
{state === 'updating' && (
|
|
117
|
-
<div className="space-y-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
{
|
|
200
|
+
<div className="space-y-3">
|
|
201
|
+
{/* Stage list */}
|
|
202
|
+
<div className="space-y-1.5">
|
|
203
|
+
{stages.map((s) => (
|
|
204
|
+
<div key={s.id} className="flex items-center gap-2 text-xs">
|
|
205
|
+
<StageIcon status={s.status} />
|
|
206
|
+
<span className={s.status === 'pending' ? 'text-muted-foreground/50' : s.status === 'running' ? 'text-foreground' : 'text-muted-foreground'}>
|
|
207
|
+
{STAGE_LABELS[s.id]?.[lang] ?? s.id}
|
|
208
|
+
</span>
|
|
209
|
+
</div>
|
|
210
|
+
))}
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
{/* Progress bar */}
|
|
214
|
+
<div className="h-1 rounded-full bg-muted overflow-hidden">
|
|
215
|
+
<div
|
|
216
|
+
className="h-full rounded-full transition-all duration-500 ease-out"
|
|
217
|
+
style={{ width: `${Math.max(progress, 5)}%`, background: 'var(--amber)' }}
|
|
218
|
+
/>
|
|
121
219
|
</div>
|
|
220
|
+
|
|
122
221
|
<p className="text-2xs text-muted-foreground">
|
|
123
222
|
{u?.updatingHint ?? 'This may take 1–3 minutes. Do not close this page.'}
|
|
124
223
|
</p>
|
|
@@ -133,21 +232,39 @@ export function UpdateTab() {
|
|
|
133
232
|
)}
|
|
134
233
|
|
|
135
234
|
{state === 'timeout' && (
|
|
136
|
-
<div className="space-y-
|
|
235
|
+
<div className="space-y-2">
|
|
137
236
|
<div className="flex items-center gap-2 text-xs text-amber-600 dark:text-amber-400">
|
|
138
237
|
<AlertCircle size={13} />
|
|
139
238
|
{u?.timeout ?? 'Update may still be in progress.'}
|
|
140
239
|
</div>
|
|
141
240
|
<p className="text-2xs text-muted-foreground">
|
|
142
|
-
{u?.timeoutHint ?? '
|
|
241
|
+
{u?.timeoutHint ?? 'The server may need more time to rebuild. Try refreshing.'}
|
|
143
242
|
</p>
|
|
243
|
+
<button
|
|
244
|
+
onClick={() => window.location.reload()}
|
|
245
|
+
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 transition-colors"
|
|
246
|
+
>
|
|
247
|
+
<RefreshCw size={12} />
|
|
248
|
+
{u?.refreshButton ?? 'Refresh Page'}
|
|
249
|
+
</button>
|
|
144
250
|
</div>
|
|
145
251
|
)}
|
|
146
252
|
|
|
147
253
|
{state === 'error' && (
|
|
148
|
-
<div className="
|
|
149
|
-
<
|
|
150
|
-
|
|
254
|
+
<div className="space-y-2">
|
|
255
|
+
<div className="flex items-center gap-2 text-xs text-destructive">
|
|
256
|
+
<AlertCircle size={13} />
|
|
257
|
+
{updateError || errorMsg}
|
|
258
|
+
</div>
|
|
259
|
+
{updateError && (
|
|
260
|
+
<button
|
|
261
|
+
onClick={handleRetry}
|
|
262
|
+
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 transition-colors"
|
|
263
|
+
>
|
|
264
|
+
<RefreshCw size={12} />
|
|
265
|
+
{u?.retryButton ?? 'Retry Update'}
|
|
266
|
+
</button>
|
|
267
|
+
)}
|
|
151
268
|
</div>
|
|
152
269
|
)}
|
|
153
270
|
</div>
|
|
@@ -60,6 +60,18 @@ async function removeSession(id: string): Promise<void> {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
async function removeSessions(ids: string[]): Promise<void> {
|
|
64
|
+
try {
|
|
65
|
+
await fetch('/api/ask-sessions', {
|
|
66
|
+
method: 'DELETE',
|
|
67
|
+
headers: { 'Content-Type': 'application/json' },
|
|
68
|
+
body: JSON.stringify({ ids }),
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
// ignore persistence errors
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
63
75
|
export function useAskSession(currentFile?: string) {
|
|
64
76
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
65
77
|
const [sessions, setSessions] = useState<ChatSession[]>([]);
|
|
@@ -166,6 +178,17 @@ export function useAskSession(currentFile?: string) {
|
|
|
166
178
|
[activeSessionId, currentFile, sessions],
|
|
167
179
|
);
|
|
168
180
|
|
|
181
|
+
const clearAllSessions = useCallback(() => {
|
|
182
|
+
const allIds = sessions.map(s => s.id);
|
|
183
|
+
void removeSessions(allIds);
|
|
184
|
+
|
|
185
|
+
const fresh = createSession(currentFile);
|
|
186
|
+
setActiveSessionId(fresh.id);
|
|
187
|
+
setMessages([]);
|
|
188
|
+
setSessions([fresh]);
|
|
189
|
+
void upsertSession(fresh);
|
|
190
|
+
}, [currentFile, sessions]);
|
|
191
|
+
|
|
169
192
|
return {
|
|
170
193
|
messages,
|
|
171
194
|
setMessages,
|
|
@@ -177,5 +200,6 @@ export function useAskSession(currentFile?: string) {
|
|
|
177
200
|
resetSession,
|
|
178
201
|
loadSession,
|
|
179
202
|
deleteSession,
|
|
203
|
+
clearAllSessions,
|
|
180
204
|
};
|
|
181
205
|
}
|
package/app/lib/i18n-en.ts
CHANGED
|
@@ -25,6 +25,9 @@ export const en = {
|
|
|
25
25
|
spaceLocation: 'Location',
|
|
26
26
|
rootLevel: 'Root',
|
|
27
27
|
optional: 'optional',
|
|
28
|
+
aiInit: 'AI initialize content',
|
|
29
|
+
aiInitHint: 'AI will generate README and INSTRUCTION for this space',
|
|
30
|
+
aiInitNoKey: 'Configure an API key in Settings → AI to enable',
|
|
28
31
|
createSpace: 'Create',
|
|
29
32
|
cancelCreate: 'Cancel',
|
|
30
33
|
continueEditing: 'Continue editing',
|
|
@@ -108,6 +111,10 @@ export const en = {
|
|
|
108
111
|
'What are the key points?',
|
|
109
112
|
'Find related notes on this topic',
|
|
110
113
|
],
|
|
114
|
+
sessionHistory: 'Session History',
|
|
115
|
+
clearAll: 'Clear all',
|
|
116
|
+
confirmClear: 'Confirm clear?',
|
|
117
|
+
noSessions: 'No saved sessions.',
|
|
111
118
|
},
|
|
112
119
|
panels: {
|
|
113
120
|
agents: {
|
|
@@ -161,6 +168,8 @@ export const en = {
|
|
|
161
168
|
pluginMarketDesc: 'Extend how files are rendered and edited',
|
|
162
169
|
skillMarket: 'Skill Market',
|
|
163
170
|
skillMarketDesc: 'Add new abilities to your AI agents',
|
|
171
|
+
spaceTemplates: 'Space Templates',
|
|
172
|
+
spaceTemplatesDesc: 'Pre-built space structures for common workflows',
|
|
164
173
|
comingSoon: 'Coming soon',
|
|
165
174
|
viewAll: 'View all use cases',
|
|
166
175
|
tryIt: 'Try',
|
|
@@ -439,7 +448,9 @@ export const en = {
|
|
|
439
448
|
updatingHint: 'This may take 1–3 minutes. Do not close this page.',
|
|
440
449
|
updated: 'Updated successfully! Reloading...',
|
|
441
450
|
timeout: 'Update may still be in progress.',
|
|
442
|
-
timeoutHint: '
|
|
451
|
+
timeoutHint: 'The server may need more time to rebuild. Try refreshing.',
|
|
452
|
+
refreshButton: 'Refresh Page',
|
|
453
|
+
retryButton: 'Retry Update',
|
|
443
454
|
error: 'Failed to check for updates. Check your network connection.',
|
|
444
455
|
checkButton: 'Check for Updates',
|
|
445
456
|
updateButton: (version: string) => `Update to v${version}`,
|
|
@@ -666,12 +677,22 @@ export const en = {
|
|
|
666
677
|
subtitle: 'Discover what you can do with MindOS — pick a scenario and try it now.',
|
|
667
678
|
tryIt: 'Try it',
|
|
668
679
|
categories: {
|
|
669
|
-
'
|
|
670
|
-
'
|
|
671
|
-
'
|
|
680
|
+
'knowledge-management': 'Knowledge Management',
|
|
681
|
+
'memory-sync': 'Memory Sync',
|
|
682
|
+
'auto-execute': 'Auto Execute',
|
|
683
|
+
'experience-evolution': 'Experience Evolution',
|
|
684
|
+
'human-insights': 'Human Insights',
|
|
685
|
+
'audit-control': 'Audit & Control',
|
|
686
|
+
},
|
|
687
|
+
scenarios: {
|
|
688
|
+
'first-day': 'First Day',
|
|
689
|
+
'daily': 'Daily Work',
|
|
690
|
+
'project': 'Project Work',
|
|
672
691
|
'advanced': 'Advanced',
|
|
673
692
|
},
|
|
674
693
|
all: 'All',
|
|
694
|
+
byCapability: 'By Capability',
|
|
695
|
+
byScenario: 'By Scenario',
|
|
675
696
|
c1: {
|
|
676
697
|
title: 'Inject Your Identity',
|
|
677
698
|
desc: 'Tell all AI agents who you are — preferences, tech stack, communication style — in one shot.',
|
package/app/lib/i18n-zh.ts
CHANGED
|
@@ -50,6 +50,9 @@ export const zh = {
|
|
|
50
50
|
spaceLocation: '位置',
|
|
51
51
|
rootLevel: '根目录',
|
|
52
52
|
optional: '可选',
|
|
53
|
+
aiInit: 'AI 初始化内容',
|
|
54
|
+
aiInitHint: 'AI 将为此空间生成 README 和 INSTRUCTION',
|
|
55
|
+
aiInitNoKey: '在 设置 → AI 中配置 API 密钥以启用',
|
|
53
56
|
createSpace: '创建',
|
|
54
57
|
cancelCreate: '取消',
|
|
55
58
|
continueEditing: '继续编辑',
|
|
@@ -133,6 +136,10 @@ export const zh = {
|
|
|
133
136
|
'这篇文档的核心要点是什么?',
|
|
134
137
|
'查找与这个主题相关的笔记',
|
|
135
138
|
],
|
|
139
|
+
sessionHistory: '对话历史',
|
|
140
|
+
clearAll: '清除全部',
|
|
141
|
+
confirmClear: '确认清除?',
|
|
142
|
+
noSessions: '暂无历史对话。',
|
|
136
143
|
},
|
|
137
144
|
panels: {
|
|
138
145
|
agents: {
|
|
@@ -186,6 +193,8 @@ export const zh = {
|
|
|
186
193
|
pluginMarketDesc: '扩展文件的渲染和编辑方式',
|
|
187
194
|
skillMarket: '技能市场',
|
|
188
195
|
skillMarketDesc: '为 AI 智能体添加新能力',
|
|
196
|
+
spaceTemplates: '空间模板',
|
|
197
|
+
spaceTemplatesDesc: '预设的空间结构,适用于常见工作流场景',
|
|
189
198
|
comingSoon: '即将推出',
|
|
190
199
|
viewAll: '查看所有使用案例',
|
|
191
200
|
tryIt: '试试',
|
|
@@ -464,7 +473,9 @@ export const zh = {
|
|
|
464
473
|
updatingHint: '预计 1–3 分钟,请勿关闭此页面。',
|
|
465
474
|
updated: '更新成功!正在刷新...',
|
|
466
475
|
timeout: '更新可能仍在进行中。',
|
|
467
|
-
timeoutHint: '
|
|
476
|
+
timeoutHint: '服务器可能需要更多时间重新构建,请尝试刷新页面。',
|
|
477
|
+
refreshButton: '刷新页面',
|
|
478
|
+
retryButton: '重试更新',
|
|
468
479
|
error: '检查更新失败,请检查网络连接。',
|
|
469
480
|
checkButton: '检查更新',
|
|
470
481
|
updateButton: (version: string) => `更新到 v${version}`,
|
|
@@ -691,12 +702,22 @@ export const zh = {
|
|
|
691
702
|
subtitle: '发现 MindOS 能帮你做什么 — 选一个场景,立即体验。',
|
|
692
703
|
tryIt: '试一试',
|
|
693
704
|
categories: {
|
|
694
|
-
'
|
|
695
|
-
'
|
|
696
|
-
'
|
|
705
|
+
'knowledge-management': '知识管理',
|
|
706
|
+
'memory-sync': '记忆同步',
|
|
707
|
+
'auto-execute': '自动执行',
|
|
708
|
+
'experience-evolution': '经验进化',
|
|
709
|
+
'human-insights': '人类洞察',
|
|
710
|
+
'audit-control': '审计纠错',
|
|
711
|
+
},
|
|
712
|
+
scenarios: {
|
|
713
|
+
'first-day': '初次使用',
|
|
714
|
+
'daily': '日常工作',
|
|
715
|
+
'project': '项目协作',
|
|
697
716
|
'advanced': '高级',
|
|
698
717
|
},
|
|
699
718
|
all: '全部',
|
|
719
|
+
byCapability: '按能力',
|
|
720
|
+
byScenario: '按场景',
|
|
700
721
|
c1: {
|
|
701
722
|
title: '注入身份',
|
|
702
723
|
desc: '让所有 AI Agent 一次认识你 — 偏好、技术栈、沟通风格。',
|
package/app/lib/utils.ts
CHANGED
|
@@ -32,3 +32,14 @@ export function relativeTime(mtime: number, labels: {
|
|
|
32
32
|
if (days < 7) return labels.daysAgo(days);
|
|
33
33
|
return new Date(mtime).toLocaleDateString();
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
/** Extract leading emoji from a string, e.g. "📝 Notes" → "📝" */
|
|
37
|
+
export function extractEmoji(name: string): string {
|
|
38
|
+
const match = name.match(/^[\p{Emoji_Presentation}\p{Extended_Pictographic}]+/u);
|
|
39
|
+
return match?.[0] ?? '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Strip leading emoji+space from a string, e.g. "📝 Notes" → "Notes" */
|
|
43
|
+
export function stripEmoji(name: string): string {
|
|
44
|
+
return name.replace(/^[\p{Emoji_Presentation}\p{Extended_Pictographic}\s]+/u, '') || name;
|
|
45
|
+
}
|
package/bin/cli.js
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* mindos config validate — validate config file
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
-
import { execSync } from 'node:child_process';
|
|
41
|
+
import { execSync, spawn as nodeSpawn } from 'node:child_process';
|
|
42
42
|
import { existsSync, readFileSync, writeFileSync, rmSync, cpSync } from 'node:fs';
|
|
43
43
|
import { resolve } from 'node:path';
|
|
44
44
|
import { homedir } from 'node:os';
|
|
@@ -697,32 +697,41 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
697
697
|
|
|
698
698
|
// ── update ─────────────────────────────────────────────────────────────────
|
|
699
699
|
update: async () => {
|
|
700
|
+
const { writeUpdateStatus, writeUpdateFailed, clearUpdateStatus } = await import('./lib/update-status.js');
|
|
700
701
|
const currentVersion = (() => {
|
|
701
702
|
try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; }
|
|
702
703
|
})();
|
|
703
704
|
console.log(`\n${bold('⬆ Updating MindOS...')} ${dim(`(current: ${currentVersion})`)}\n`);
|
|
705
|
+
|
|
706
|
+
// Stage 1: Download
|
|
707
|
+
writeUpdateStatus('downloading', { fromVersion: currentVersion });
|
|
704
708
|
try {
|
|
705
709
|
execSync('npm install -g @geminilight/mindos@latest', { stdio: 'inherit' });
|
|
706
710
|
} catch {
|
|
711
|
+
writeUpdateFailed('downloading', 'npm install failed', { fromVersion: currentVersion });
|
|
707
712
|
console.error(red('Update failed. Try: npm install -g @geminilight/mindos@latest'));
|
|
708
713
|
process.exit(1);
|
|
709
714
|
}
|
|
710
715
|
if (existsSync(BUILD_STAMP)) rmSync(BUILD_STAMP);
|
|
711
716
|
|
|
712
|
-
//
|
|
717
|
+
// Resolve the new installation path (after npm install -g, ROOT is stale)
|
|
718
|
+
const updatedRoot = getUpdatedRoot();
|
|
719
|
+
const newVersion = (() => {
|
|
720
|
+
try { return JSON.parse(readFileSync(resolve(updatedRoot, 'package.json'), 'utf-8')).version; } catch { return '?'; }
|
|
721
|
+
})();
|
|
722
|
+
const vOpts = { fromVersion: currentVersion, toVersion: newVersion };
|
|
723
|
+
|
|
724
|
+
// Stage 2: Skills
|
|
725
|
+
writeUpdateStatus('skills', vOpts);
|
|
713
726
|
try {
|
|
714
|
-
const newRoot = getUpdatedRoot();
|
|
715
727
|
const { checkSkillVersions, updateSkill } = await import('./lib/skill-check.js');
|
|
716
|
-
const mismatches = checkSkillVersions(
|
|
728
|
+
const mismatches = checkSkillVersions(updatedRoot);
|
|
717
729
|
for (const m of mismatches) {
|
|
718
730
|
updateSkill(m.bundledPath, m.installPath);
|
|
719
731
|
console.log(` ${green('✓')} ${dim(`Skill ${m.name}: v${m.installed} → v${m.bundled}`)}`);
|
|
720
732
|
}
|
|
721
733
|
} catch { /* best-effort */ }
|
|
722
734
|
|
|
723
|
-
const newVersion = (() => {
|
|
724
|
-
try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; }
|
|
725
|
-
})();
|
|
726
735
|
if (newVersion !== currentVersion) {
|
|
727
736
|
console.log(`\n${green(`✔ Updated ${currentVersion} → ${newVersion}`)}`);
|
|
728
737
|
} else {
|
|
@@ -746,10 +755,12 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
746
755
|
console.log(cyan('\n Daemon is running — stopping to apply the new version...'));
|
|
747
756
|
await runGatewayCommand('stop');
|
|
748
757
|
|
|
749
|
-
//
|
|
750
|
-
|
|
751
|
-
buildIfNeeded(
|
|
758
|
+
// Stage 3: Rebuild
|
|
759
|
+
writeUpdateStatus('rebuilding', vOpts);
|
|
760
|
+
buildIfNeeded(updatedRoot);
|
|
752
761
|
|
|
762
|
+
// Stage 4: Restart
|
|
763
|
+
writeUpdateStatus('restarting', vOpts);
|
|
753
764
|
await runGatewayCommand('install');
|
|
754
765
|
// install() starts the service:
|
|
755
766
|
// - systemd: daemon-reload + enable + start
|
|
@@ -772,17 +783,78 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
772
783
|
console.log(` ${green('●')} MCP ${cyan(`http://localhost:${mcpPort}/mcp`)}`);
|
|
773
784
|
console.log(`\n ${dim('View changelog:')} ${cyan('https://github.com/GeminiLight/MindOS/releases')}`);
|
|
774
785
|
console.log(`${'─'.repeat(53)}\n`);
|
|
786
|
+
writeUpdateStatus('done', vOpts);
|
|
775
787
|
} else {
|
|
788
|
+
writeUpdateFailed('restarting', 'Server did not come back up in time', vOpts);
|
|
776
789
|
console.error(red('✘ MindOS did not come back up in time. Check logs: mindos logs\n'));
|
|
777
790
|
process.exit(1);
|
|
778
791
|
}
|
|
779
792
|
} else {
|
|
780
|
-
// Non-daemon mode:
|
|
781
|
-
|
|
793
|
+
// Non-daemon mode: check if a MindOS instance is currently running
|
|
794
|
+
// (e.g. user started via `mindos start`, or GUI triggered this update).
|
|
795
|
+
// If so, stop it and restart from the NEW installation path.
|
|
796
|
+
const updateConfig = (() => {
|
|
797
|
+
try { return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch { return {}; }
|
|
798
|
+
})();
|
|
799
|
+
const webPort = Number(updateConfig.port ?? 3456);
|
|
800
|
+
const mcpPort = Number(updateConfig.mcpPort ?? 8781);
|
|
801
|
+
|
|
802
|
+
const wasRunning = await isPortInUse(webPort) || await isPortInUse(mcpPort);
|
|
803
|
+
|
|
804
|
+
if (wasRunning) {
|
|
805
|
+
console.log(cyan('\n MindOS is running — restarting to apply the new version...'));
|
|
806
|
+
stopMindos();
|
|
807
|
+
// Wait for ports to free (up to 15s)
|
|
808
|
+
const deadline = Date.now() + 15_000;
|
|
809
|
+
while (Date.now() < deadline) {
|
|
810
|
+
const busy = await isPortInUse(webPort) || await isPortInUse(mcpPort);
|
|
811
|
+
if (!busy) break;
|
|
812
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
813
|
+
}
|
|
782
814
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
815
|
+
// Stage 3: Rebuild
|
|
816
|
+
writeUpdateStatus('rebuilding', vOpts);
|
|
817
|
+
buildIfNeeded(updatedRoot);
|
|
818
|
+
|
|
819
|
+
// Stage 4: Restart
|
|
820
|
+
writeUpdateStatus('restarting', vOpts);
|
|
821
|
+
const newCliPath = resolve(updatedRoot, 'bin', 'cli.js');
|
|
822
|
+
const childEnv = { ...process.env };
|
|
823
|
+
delete childEnv.MINDOS_WEB_PORT;
|
|
824
|
+
delete childEnv.MINDOS_MCP_PORT;
|
|
825
|
+
delete childEnv.MIND_ROOT;
|
|
826
|
+
delete childEnv.AUTH_TOKEN;
|
|
827
|
+
delete childEnv.WEB_PASSWORD;
|
|
828
|
+
const child = nodeSpawn(
|
|
829
|
+
process.execPath, [newCliPath, 'start'],
|
|
830
|
+
{ detached: true, stdio: 'ignore', env: childEnv },
|
|
831
|
+
);
|
|
832
|
+
child.unref();
|
|
833
|
+
|
|
834
|
+
console.log(dim(' (Waiting for Web UI to come back up...)'));
|
|
835
|
+
const ready = await waitForHttp(webPort, { retries: 120, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
|
|
836
|
+
if (ready) {
|
|
837
|
+
const localIP = getLocalIP();
|
|
838
|
+
console.log(`\n${'─'.repeat(53)}`);
|
|
839
|
+
console.log(`${green('✔')} ${bold(`MindOS updated: ${currentVersion} → ${newVersion}`)}\n`);
|
|
840
|
+
console.log(` ${green('●')} Web UI ${cyan(`http://localhost:${webPort}`)}`);
|
|
841
|
+
if (localIP) console.log(` ${cyan(`http://${localIP}:${webPort}`)}`);
|
|
842
|
+
console.log(` ${green('●')} MCP ${cyan(`http://localhost:${mcpPort}/mcp`)}`);
|
|
843
|
+
console.log(`\n ${dim('View changelog:')} ${cyan('https://github.com/GeminiLight/MindOS/releases')}`);
|
|
844
|
+
console.log(`${'─'.repeat(53)}\n`);
|
|
845
|
+
writeUpdateStatus('done', vOpts);
|
|
846
|
+
} else {
|
|
847
|
+
writeUpdateFailed('restarting', 'Server did not come back up in time', vOpts);
|
|
848
|
+
console.error(red('✘ MindOS did not come back up in time. Check logs: mindos logs\n'));
|
|
849
|
+
process.exit(1);
|
|
850
|
+
}
|
|
851
|
+
} else {
|
|
852
|
+
// No running instance — just build and tell user to start manually
|
|
853
|
+
buildIfNeeded(updatedRoot);
|
|
854
|
+
console.log(`\n${green('✔')} ${bold(`Updated: ${currentVersion} → ${newVersion}`)}`);
|
|
855
|
+
console.log(dim(' Run `mindos start` to start the updated version.'));
|
|
856
|
+
console.log(` ${dim('View changelog:')} ${cyan('https://github.com/GeminiLight/MindOS/releases')}\n`);
|
|
857
|
+
}
|
|
786
858
|
}
|
|
787
859
|
},
|
|
788
860
|
|
package/bin/lib/startup.js
CHANGED
|
@@ -5,6 +5,7 @@ import { bold, dim, cyan, green, yellow } from './colors.js';
|
|
|
5
5
|
import { getSyncStatus } from './sync.js';
|
|
6
6
|
import { checkForUpdate, printUpdateHint } from './update-check.js';
|
|
7
7
|
import { runSkillCheck } from './skill-check.js';
|
|
8
|
+
import { clearUpdateStatus } from './update-status.js';
|
|
8
9
|
|
|
9
10
|
export function getLocalIP() {
|
|
10
11
|
try {
|
|
@@ -18,6 +19,9 @@ export function getLocalIP() {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export async function printStartupInfo(webPort, mcpPort) {
|
|
22
|
+
// Clear stale update status from previous update cycles
|
|
23
|
+
clearUpdateStatus();
|
|
24
|
+
|
|
21
25
|
// Fire update check immediately (non-blocking)
|
|
22
26
|
const updatePromise = checkForUpdate().catch(() => null);
|
|
23
27
|
|