@geminilight/mindos 0.6.7 → 0.6.12
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 +2 -0
- package/README_zh.md +2 -0
- package/app/app/api/ask/route.ts +35 -2
- package/app/app/api/file/route.ts +27 -0
- package/app/app/api/mcp/install/route.ts +4 -1
- package/app/app/api/setup/check-path/route.ts +2 -7
- package/app/app/api/setup/check-port/route.ts +18 -13
- package/app/app/api/setup/ls/route.ts +3 -9
- package/app/app/api/setup/path-utils.ts +8 -0
- package/app/app/api/setup/route.ts +2 -7
- package/app/app/api/uninstall/route.ts +47 -0
- package/app/app/globals.css +11 -0
- package/app/components/ActivityBar.tsx +10 -3
- package/app/components/AskFab.tsx +7 -3
- package/app/components/CreateSpaceModal.tsx +1 -1
- package/app/components/DirView.tsx +1 -1
- package/app/components/FileTree.tsx +30 -23
- package/app/components/GuideCard.tsx +1 -1
- package/app/components/HomeContent.tsx +137 -109
- package/app/components/ImportModal.tsx +104 -60
- package/app/components/MarkdownView.tsx +3 -0
- package/app/components/OnboardingView.tsx +1 -1
- package/app/components/OrganizeToast.tsx +386 -0
- package/app/components/Panel.tsx +23 -2
- package/app/components/Sidebar.tsx +1 -1
- package/app/components/SidebarLayout.tsx +44 -1
- package/app/components/agents/AgentDetailContent.tsx +33 -12
- package/app/components/agents/AgentsMcpSection.tsx +1 -1
- package/app/components/agents/AgentsOverviewSection.tsx +3 -4
- package/app/components/agents/AgentsPrimitives.tsx +2 -2
- package/app/components/agents/AgentsSkillsSection.tsx +2 -2
- package/app/components/agents/SkillDetailPopover.tsx +24 -8
- package/app/components/ask/AskContent.tsx +124 -70
- package/app/components/ask/HighlightMatch.tsx +14 -0
- package/app/components/ask/MentionPopover.tsx +5 -3
- package/app/components/ask/MessageList.tsx +39 -11
- package/app/components/ask/SlashCommandPopover.tsx +4 -2
- package/app/components/changes/ChangesBanner.tsx +20 -2
- package/app/components/changes/ChangesContentPage.tsx +10 -2
- package/app/components/echo/EchoHero.tsx +1 -1
- package/app/components/echo/EchoInsightCollapsible.tsx +1 -1
- package/app/components/echo/EchoPageSections.tsx +1 -1
- package/app/components/explore/UseCaseCard.tsx +1 -1
- package/app/components/panels/DiscoverPanel.tsx +29 -25
- package/app/components/panels/ImportHistoryPanel.tsx +195 -0
- package/app/components/panels/PluginsPanel.tsx +2 -2
- package/app/components/settings/AiTab.tsx +24 -0
- package/app/components/settings/KnowledgeTab.tsx +1 -1
- package/app/components/settings/McpSkillCreateForm.tsx +1 -1
- package/app/components/settings/McpSkillRow.tsx +1 -1
- package/app/components/settings/McpSkillsSection.tsx +2 -2
- package/app/components/settings/McpTab.tsx +2 -2
- package/app/components/settings/PluginsTab.tsx +1 -1
- package/app/components/settings/Primitives.tsx +118 -6
- package/app/components/settings/SettingsContent.tsx +5 -2
- package/app/components/settings/UninstallTab.tsx +179 -0
- package/app/components/settings/UpdateTab.tsx +17 -5
- package/app/components/settings/types.ts +2 -1
- package/app/components/ui/dialog.tsx +1 -1
- package/app/hooks/useAiOrganize.ts +450 -0
- package/app/hooks/useFileImport.ts +39 -2
- package/app/hooks/useMention.ts +21 -3
- package/app/hooks/useSlashCommand.ts +18 -4
- package/app/lib/agent/reconnect.ts +40 -0
- package/app/lib/core/backlinks.ts +2 -2
- package/app/lib/core/git.ts +14 -10
- package/app/lib/fs.ts +2 -1
- package/app/lib/i18n-en.ts +85 -4
- package/app/lib/i18n-zh.ts +85 -4
- package/app/lib/organize-history.ts +74 -0
- package/app/lib/settings.ts +2 -0
- package/app/lib/types.ts +2 -0
- package/app/next-env.d.ts +1 -1
- package/app/next.config.ts +23 -5
- package/app/package.json +1 -1
- package/bin/cli.js +21 -18
- package/bin/lib/mcp-build.js +74 -0
- package/bin/lib/mcp-spawn.js +8 -5
- package/bin/lib/port.js +17 -2
- package/bin/lib/stop.js +12 -2
- package/mcp/dist/index.cjs +43 -43
- package/mcp/src/index.ts +58 -12
- package/package.json +1 -1
- package/scripts/release.sh +1 -1
- package/scripts/setup.js +2 -2
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
4
4
|
import {
|
|
5
|
-
X, FolderInput, Sparkles, FileText, AlertCircle,
|
|
6
|
-
AlertTriangle, Loader2, Check,
|
|
5
|
+
X, FolderInput, FolderOpen, Sparkles, FileText, AlertCircle,
|
|
6
|
+
AlertTriangle, Loader2, Check, ChevronDown,
|
|
7
7
|
} from 'lucide-react';
|
|
8
8
|
import { useLocale } from '@/lib/LocaleContext';
|
|
9
9
|
import { useFileImport, type ImportIntent, type ConflictMode } from '@/hooks/useFileImport';
|
|
10
|
-
import {
|
|
10
|
+
import type { useAiOrganize } from '@/hooks/useAiOrganize';
|
|
11
11
|
import { ALLOWED_IMPORT_EXTENSIONS } from '@/lib/core/file-convert';
|
|
12
12
|
import type { LocalAttachment } from '@/lib/types';
|
|
13
13
|
|
|
@@ -16,11 +16,14 @@ interface ImportModalProps {
|
|
|
16
16
|
onClose: () => void;
|
|
17
17
|
defaultSpace?: string;
|
|
18
18
|
initialFiles?: File[];
|
|
19
|
+
/** Lifted AI organize hook from SidebarLayout (shared with OrganizeToast) */
|
|
20
|
+
aiOrganize: ReturnType<typeof useAiOrganize>;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
const ACCEPT = Array.from(ALLOWED_IMPORT_EXTENSIONS).join(',');
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
|
|
26
|
+
export default function ImportModal({ open, onClose, defaultSpace, initialFiles, aiOrganize }: ImportModalProps) {
|
|
24
27
|
const { t } = useLocale();
|
|
25
28
|
const im = useFileImport();
|
|
26
29
|
const overlayRef = useRef<HTMLDivElement>(null);
|
|
@@ -28,6 +31,8 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles
|
|
|
28
31
|
const [spaces, setSpaces] = useState<Array<{ name: string; path: string }>>([]);
|
|
29
32
|
const [closing, setClosing] = useState(false);
|
|
30
33
|
const [showSuccess, setShowSuccess] = useState(false);
|
|
34
|
+
const [conflictFiles, setConflictFiles] = useState<string[]>([]);
|
|
35
|
+
const [showConflictOptions, setShowConflictOptions] = useState(false);
|
|
31
36
|
const initializedRef = useRef(false);
|
|
32
37
|
|
|
33
38
|
useEffect(() => {
|
|
@@ -38,6 +43,8 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles
|
|
|
38
43
|
if (initializedRef.current) return;
|
|
39
44
|
initializedRef.current = true;
|
|
40
45
|
im.reset();
|
|
46
|
+
setConflictFiles([]);
|
|
47
|
+
setShowConflictOptions(false);
|
|
41
48
|
if (defaultSpace) im.setTargetSpace(defaultSpace);
|
|
42
49
|
if (initialFiles && initialFiles.length > 0) {
|
|
43
50
|
im.addFiles(initialFiles);
|
|
@@ -53,7 +60,7 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles
|
|
|
53
60
|
if (!confirm(t.fileImport.discardMessage(im.files.length))) return;
|
|
54
61
|
}
|
|
55
62
|
setClosing(true);
|
|
56
|
-
setTimeout(() => { setClosing(false); onClose(); im.reset(); }, 150);
|
|
63
|
+
setTimeout(() => { setClosing(false); onClose(); im.reset(); setConflictFiles([]); setShowConflictOptions(false); }, 150);
|
|
57
64
|
}, [im, onClose, t]);
|
|
58
65
|
|
|
59
66
|
useEffect(() => {
|
|
@@ -65,30 +72,40 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles
|
|
|
65
72
|
return () => window.removeEventListener('keydown', handler, true);
|
|
66
73
|
}, [open, handleClose]);
|
|
67
74
|
|
|
75
|
+
const checkConflicts = useCallback(async (fileNames: string[], space: string) => {
|
|
76
|
+
try {
|
|
77
|
+
const names = fileNames.map(encodeURIComponent).join(',');
|
|
78
|
+
const spaceParam = space ? `&space=${encodeURIComponent(space)}` : '';
|
|
79
|
+
const res = await fetch(`/api/file?op=check_conflicts&names=${names}${spaceParam}`);
|
|
80
|
+
if (res.ok) {
|
|
81
|
+
const data = await res.json();
|
|
82
|
+
setConflictFiles(data.conflicts ?? []);
|
|
83
|
+
setShowConflictOptions((data.conflicts ?? []).length > 0);
|
|
84
|
+
}
|
|
85
|
+
} catch { /* best-effort */ }
|
|
86
|
+
}, []);
|
|
87
|
+
|
|
68
88
|
const handleIntentSelect = useCallback((intent: ImportIntent) => {
|
|
69
89
|
im.setIntent(intent);
|
|
70
90
|
if (intent === 'archive') {
|
|
71
91
|
im.setStep('archive_config');
|
|
92
|
+
const names = im.validFiles.map(f => f.name);
|
|
93
|
+
checkConflicts(names, im.targetSpace);
|
|
72
94
|
} else {
|
|
73
95
|
const attachments: LocalAttachment[] = im.validFiles.map(f => ({
|
|
74
96
|
name: f.name,
|
|
75
97
|
content: f.content!,
|
|
76
98
|
}));
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
? t.fileImport.digestPromptSingle(attachments[0].name)
|
|
86
|
-
: t.fileImport.digestPromptMulti(attachments.length);
|
|
87
|
-
openAskModal(prompt);
|
|
88
|
-
im.reset();
|
|
89
|
-
}, 150);
|
|
99
|
+
const space = im.targetSpace || undefined;
|
|
100
|
+
const prompt = attachments.length === 1
|
|
101
|
+
? (t.fileImport.digestPromptSingle as (name: string, space?: string) => string)(attachments[0].name, space)
|
|
102
|
+
: (t.fileImport.digestPromptMulti as (n: number, space?: string) => string)(attachments.length, space);
|
|
103
|
+
// Start AI organize and immediately close modal — toast takes over
|
|
104
|
+
aiOrganize.start(attachments, prompt);
|
|
105
|
+
onClose();
|
|
106
|
+
im.reset();
|
|
90
107
|
}
|
|
91
|
-
}, [im,
|
|
108
|
+
}, [im, t, aiOrganize, onClose]);
|
|
92
109
|
|
|
93
110
|
const handleArchiveSubmit = useCallback(async () => {
|
|
94
111
|
await im.doArchive();
|
|
@@ -107,6 +124,15 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles
|
|
|
107
124
|
}
|
|
108
125
|
}, [im, onClose]);
|
|
109
126
|
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (im.step === 'archive_config') {
|
|
129
|
+
const names = im.validFiles.map(f => f.name);
|
|
130
|
+
checkConflicts(names, im.targetSpace);
|
|
131
|
+
}
|
|
132
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
133
|
+
}, [im.targetSpace]);
|
|
134
|
+
|
|
135
|
+
|
|
110
136
|
useEffect(() => {
|
|
111
137
|
if (im.step === 'done' && im.result) {
|
|
112
138
|
if (im.result.created.length > 0) {
|
|
@@ -126,28 +152,28 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles
|
|
|
126
152
|
}
|
|
127
153
|
}, [im.step, im.result, onClose, im]);
|
|
128
154
|
|
|
129
|
-
if (!open && !closing) return null;
|
|
130
|
-
|
|
131
155
|
const hasFiles = im.files.length > 0;
|
|
132
156
|
const isSelectStep = im.step === 'select';
|
|
133
157
|
const isArchiveConfig = im.step === 'archive_config';
|
|
134
158
|
const isImporting = im.step === 'importing';
|
|
135
159
|
|
|
160
|
+
if (!open && !closing) return null;
|
|
161
|
+
|
|
136
162
|
return (
|
|
137
163
|
<>
|
|
138
164
|
<div
|
|
139
165
|
ref={overlayRef}
|
|
140
|
-
className={`fixed inset-0 z-50
|
|
166
|
+
className={`fixed inset-0 z-50 modal-backdrop flex items-center justify-center p-4 transition-opacity duration-200 ${closing ? 'opacity-0' : 'opacity-100'}`}
|
|
141
167
|
onClick={(e) => { if (e.target === overlayRef.current) handleClose(); }}
|
|
142
168
|
>
|
|
143
169
|
<div
|
|
144
|
-
className={`w-full max-w-lg bg-card rounded-xl shadow-xl border border-border transition-all duration-200 ${closing ? 'opacity-0 scale-[0.98]' : 'opacity-100 scale-100'}`}
|
|
170
|
+
className={`w-full max-w-lg max-h-[80vh] flex flex-col bg-card rounded-xl shadow-xl border border-border transition-all duration-200 ${closing ? 'opacity-0 scale-[0.98]' : 'opacity-100 scale-100'}`}
|
|
145
171
|
role="dialog"
|
|
146
172
|
aria-modal="true"
|
|
147
173
|
aria-label={t.fileImport.title}
|
|
148
174
|
>
|
|
149
175
|
{/* Header */}
|
|
150
|
-
<div className="flex items-start justify-between px-5 pt-5 pb-2">
|
|
176
|
+
<div className="flex items-start justify-between px-5 pt-5 pb-2 shrink-0">
|
|
151
177
|
<div>
|
|
152
178
|
{isArchiveConfig && (
|
|
153
179
|
<button
|
|
@@ -173,7 +199,7 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles
|
|
|
173
199
|
</button>
|
|
174
200
|
</div>
|
|
175
201
|
|
|
176
|
-
<div className="px-5 pb-5">
|
|
202
|
+
<div className="px-5 pb-5 overflow-y-auto min-h-0">
|
|
177
203
|
{/* DropZone */}
|
|
178
204
|
{isSelectStep && (
|
|
179
205
|
<div
|
|
@@ -289,9 +315,14 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles
|
|
|
289
315
|
const stem = f.name.replace(/\.[^.]+$/, '');
|
|
290
316
|
const targetName = `${stem}.${targetExt}`;
|
|
291
317
|
const targetPath = im.targetSpace ? `${im.targetSpace}/${targetName}` : targetName;
|
|
318
|
+
const hasConflict = conflictFiles.includes(f.name);
|
|
292
319
|
return (
|
|
293
|
-
<div key={`preview-${idx}`} className="text-xs text-muted-foreground px-3">
|
|
294
|
-
|
|
320
|
+
<div key={`preview-${idx}`} className="flex items-center gap-1.5 text-xs text-muted-foreground px-3">
|
|
321
|
+
<span className="truncate">{f.name}</span>
|
|
322
|
+
<span className="text-muted-foreground/50 shrink-0">{t.fileImport.arrowTo}</span>
|
|
323
|
+
<FolderOpen size={12} className="text-muted-foreground/60 shrink-0" />
|
|
324
|
+
<span className={`truncate ${hasConflict ? 'text-[var(--amber)]' : ''}`}>{targetPath}</span>
|
|
325
|
+
{hasConflict && <AlertTriangle size={11} className="text-[var(--amber)] shrink-0" />}
|
|
295
326
|
</div>
|
|
296
327
|
);
|
|
297
328
|
})}
|
|
@@ -315,7 +346,7 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles
|
|
|
315
346
|
<button
|
|
316
347
|
onClick={() => handleIntentSelect('digest')}
|
|
317
348
|
className="flex flex-col items-center gap-2 p-4 border border-border rounded-lg cursor-pointer transition-all duration-150 bg-card hover:border-[var(--amber)]/50 hover:shadow-sm active:scale-[0.98] text-left"
|
|
318
|
-
disabled={im.validFiles.length === 0}
|
|
349
|
+
disabled={im.validFiles.length === 0 || aiOrganize.phase === 'organizing'}
|
|
319
350
|
>
|
|
320
351
|
<Sparkles size={24} className="text-[var(--amber)]" />
|
|
321
352
|
<span className="text-sm font-medium text-foreground">{t.fileImport.digestTitle}</span>
|
|
@@ -342,46 +373,58 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles
|
|
|
342
373
|
</select>
|
|
343
374
|
</div>
|
|
344
375
|
|
|
345
|
-
{/* Conflict strategy */}
|
|
346
|
-
|
|
347
|
-
<
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
{
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
376
|
+
{/* Conflict strategy — progressive disclosure */}
|
|
377
|
+
{conflictFiles.length > 0 ? (
|
|
378
|
+
<div>
|
|
379
|
+
<button
|
|
380
|
+
type="button"
|
|
381
|
+
onClick={() => setShowConflictOptions(v => !v)}
|
|
382
|
+
className="flex items-center gap-1.5 text-xs font-medium text-[var(--amber)] hover:opacity-80 transition-colors"
|
|
383
|
+
>
|
|
384
|
+
<AlertTriangle size={12} className="shrink-0" />
|
|
385
|
+
{t.fileImport.conflictsFound(conflictFiles.length)}
|
|
386
|
+
<ChevronDown size={12} className={`shrink-0 transition-transform duration-200 ${showConflictOptions ? 'rotate-180' : ''}`} />
|
|
387
|
+
</button>
|
|
388
|
+
{showConflictOptions && (
|
|
389
|
+
<div className="flex flex-col gap-1.5 mt-2 pl-0.5">
|
|
390
|
+
{([
|
|
391
|
+
{ value: 'rename' as ConflictMode, label: t.fileImport.conflictRename },
|
|
392
|
+
{ value: 'skip' as ConflictMode, label: t.fileImport.conflictSkip },
|
|
393
|
+
{ value: 'overwrite' as ConflictMode, label: t.fileImport.conflictOverwrite },
|
|
394
|
+
]).map(opt => (
|
|
395
|
+
<label
|
|
396
|
+
key={opt.value}
|
|
397
|
+
className={`flex items-center gap-2 py-0.5 text-xs cursor-pointer ${
|
|
398
|
+
opt.value === 'overwrite' ? 'text-error' : 'text-foreground'
|
|
399
|
+
}`}
|
|
400
|
+
>
|
|
401
|
+
<input
|
|
402
|
+
type="radio"
|
|
403
|
+
name="conflict"
|
|
404
|
+
value={opt.value}
|
|
405
|
+
checked={im.conflict === opt.value}
|
|
406
|
+
onChange={() => im.setConflict(opt.value)}
|
|
407
|
+
className="accent-[var(--amber)]"
|
|
408
|
+
/>
|
|
409
|
+
{opt.label}
|
|
410
|
+
{opt.value === 'overwrite' && (
|
|
411
|
+
<AlertTriangle size={11} className="text-error shrink-0" />
|
|
412
|
+
)}
|
|
413
|
+
</label>
|
|
414
|
+
))}
|
|
415
|
+
{im.conflict === 'overwrite' && (
|
|
416
|
+
<p className="text-2xs text-error/80 pl-5">{t.fileImport.overwriteWarn}</p>
|
|
371
417
|
)}
|
|
372
|
-
</
|
|
373
|
-
))}
|
|
374
|
-
{im.conflict === 'overwrite' && (
|
|
375
|
-
<p className="text-2xs text-error/80 pl-6">{t.fileImport.overwriteWarn}</p>
|
|
418
|
+
</div>
|
|
376
419
|
)}
|
|
377
420
|
</div>
|
|
378
|
-
|
|
421
|
+
) : null}
|
|
379
422
|
|
|
380
423
|
{/* Actions */}
|
|
381
424
|
<div className="flex items-center justify-end gap-3 pt-2">
|
|
382
425
|
<button
|
|
383
426
|
onClick={handleClose}
|
|
384
|
-
className="text-
|
|
427
|
+
className="text-xs text-muted-foreground/70 hover:text-muted-foreground transition-colors px-2 py-1.5"
|
|
385
428
|
>
|
|
386
429
|
{t.fileImport.cancel}
|
|
387
430
|
</button>
|
|
@@ -407,6 +450,7 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles
|
|
|
407
450
|
</div>
|
|
408
451
|
</div>
|
|
409
452
|
)}
|
|
453
|
+
|
|
410
454
|
</div>
|
|
411
455
|
</div>
|
|
412
456
|
</div>
|
|
@@ -137,7 +137,7 @@ export default function OnboardingView() {
|
|
|
137
137
|
<button
|
|
138
138
|
type="button"
|
|
139
139
|
onClick={() => window.dispatchEvent(new CustomEvent('mindos:open-import'))}
|
|
140
|
-
className="text-xs text-[var(--amber)] hover:underline transition-colors"
|
|
140
|
+
className="text-xs text-[var(--amber-text)] hover:underline transition-colors"
|
|
141
141
|
>
|
|
142
142
|
{t.fileImport.onboardingHint}
|
|
143
143
|
</button>
|