@geminilight/mindos 0.5.69 → 0.5.70
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/file/import/route.ts +197 -0
- package/app/components/ActivityBar.tsx +3 -4
- package/app/components/FileTree.tsx +35 -9
- package/app/components/ImportModal.tsx +415 -0
- package/app/components/OnboardingView.tsx +9 -0
- package/app/components/Panel.tsx +4 -2
- package/app/components/SidebarLayout.tsx +83 -8
- package/app/components/TableOfContents.tsx +1 -0
- package/app/components/agents/AgentDetailContent.tsx +37 -28
- package/app/components/agents/AgentsMcpSection.tsx +16 -12
- package/app/components/agents/AgentsOverviewSection.tsx +48 -34
- package/app/components/agents/AgentsPrimitives.tsx +41 -20
- package/app/components/agents/AgentsSkillsSection.tsx +16 -7
- package/app/components/agents/SkillDetailPopover.tsx +11 -11
- package/app/components/ask/AskContent.tsx +11 -0
- package/app/components/panels/AgentsPanelAgentGroups.tsx +8 -6
- package/app/components/panels/AgentsPanelHubNav.tsx +3 -3
- package/app/components/panels/DiscoverPanel.tsx +88 -2
- package/app/hooks/useFileImport.ts +191 -0
- package/app/hooks/useFileUpload.ts +11 -0
- package/app/lib/agent/tools.ts +146 -0
- package/app/lib/core/file-convert.ts +97 -0
- package/app/lib/core/organize.ts +105 -0
- package/app/lib/i18n-en.ts +51 -0
- package/app/lib/i18n-zh.ts +51 -0
- package/package.json +1 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolveSafe } from './security';
|
|
4
|
+
import { appendContentChange } from './content-changes';
|
|
5
|
+
|
|
6
|
+
export interface OrganizeResult {
|
|
7
|
+
readmeUpdated: boolean;
|
|
8
|
+
relatedFiles: Array<{ path: string; matchType: 'backlink' | 'keyword' }>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const STOP_WORDS = new Set([
|
|
12
|
+
'the', 'a', 'an', 'is', 'of', 'and', 'for', 'to', 'in', 'on', 'at', 'by', 'or',
|
|
13
|
+
'not', 'but', 'with', 'from', 'this', 'that', 'was', 'are', 'be', 'has', 'had',
|
|
14
|
+
'readme', 'instruction', 'md',
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const SKIP_DIRS = new Set(['.git', 'node_modules', '.next', '.DS_Store']);
|
|
18
|
+
|
|
19
|
+
function extractKeywords(filePath: string): string[] {
|
|
20
|
+
const stem = path.basename(filePath, path.extname(filePath));
|
|
21
|
+
return stem
|
|
22
|
+
.split(/[-_\s]+/)
|
|
23
|
+
.map(w => w.toLowerCase())
|
|
24
|
+
.filter(w => w.length >= 3 && !STOP_WORDS.has(w));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function collectMdFiles(dir: string, limit: number): string[] {
|
|
28
|
+
const results: string[] = [];
|
|
29
|
+
function walk(d: string) {
|
|
30
|
+
if (results.length >= limit) return;
|
|
31
|
+
let entries: fs.Dirent[];
|
|
32
|
+
try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (results.length >= limit) return;
|
|
35
|
+
if (entry.isDirectory()) {
|
|
36
|
+
if (!SKIP_DIRS.has(entry.name)) walk(path.join(d, entry.name));
|
|
37
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
38
|
+
results.push(path.relative(dir, path.join(d, entry.name)).replace(/\\/g, '/'));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
walk(dir);
|
|
43
|
+
return results;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function organizeAfterImport(
|
|
47
|
+
mindRoot: string,
|
|
48
|
+
createdFiles: string[],
|
|
49
|
+
targetSpace: string,
|
|
50
|
+
): OrganizeResult {
|
|
51
|
+
for (const fp of createdFiles) {
|
|
52
|
+
try {
|
|
53
|
+
appendContentChange(mindRoot, {
|
|
54
|
+
op: 'import_file',
|
|
55
|
+
path: fp,
|
|
56
|
+
source: 'user',
|
|
57
|
+
summary: 'Imported file into knowledge base',
|
|
58
|
+
});
|
|
59
|
+
} catch { /* ignore */ }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let readmeUpdated = false;
|
|
63
|
+
const space = targetSpace.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '').trim();
|
|
64
|
+
|
|
65
|
+
if (space && createdFiles.length > 0) {
|
|
66
|
+
const readmePath = path.posix.join(space, 'README.md');
|
|
67
|
+
try {
|
|
68
|
+
const resolved = resolveSafe(mindRoot, readmePath);
|
|
69
|
+
if (fs.existsSync(resolved)) {
|
|
70
|
+
const existing = fs.readFileSync(resolved, 'utf-8');
|
|
71
|
+
const bullets = createdFiles.map(f => {
|
|
72
|
+
const base = path.posix.basename(f);
|
|
73
|
+
return `- [${base}](./${base})`;
|
|
74
|
+
}).join('\n');
|
|
75
|
+
fs.writeFileSync(resolved, `${existing.trimEnd()}\n\n${bullets}\n`, 'utf-8');
|
|
76
|
+
readmeUpdated = true;
|
|
77
|
+
}
|
|
78
|
+
} catch { /* README missing or write failed */ }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const createdSet = new Set(createdFiles);
|
|
82
|
+
const allKeywords = new Set<string>();
|
|
83
|
+
for (const f of createdFiles) {
|
|
84
|
+
for (const kw of extractKeywords(f)) allKeywords.add(kw);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const relatedFiles: OrganizeResult['relatedFiles'] = [];
|
|
88
|
+
if (allKeywords.size > 0) {
|
|
89
|
+
const candidates = collectMdFiles(mindRoot, 50);
|
|
90
|
+
const kwArray = [...allKeywords];
|
|
91
|
+
for (const candidate of candidates) {
|
|
92
|
+
if (createdSet.has(candidate)) continue;
|
|
93
|
+
if (relatedFiles.length >= 10) break;
|
|
94
|
+
try {
|
|
95
|
+
const resolved = resolveSafe(mindRoot, candidate);
|
|
96
|
+
const content = fs.readFileSync(resolved, 'utf-8').toLowerCase();
|
|
97
|
+
if (kwArray.some(kw => content.includes(kw))) {
|
|
98
|
+
relatedFiles.push({ path: candidate, matchType: 'keyword' });
|
|
99
|
+
}
|
|
100
|
+
} catch { /* ignore */ }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { readmeUpdated, relatedFiles };
|
|
105
|
+
}
|
package/app/lib/i18n-en.ts
CHANGED
|
@@ -657,6 +657,57 @@ export const en = {
|
|
|
657
657
|
convertToSpace: 'Convert to Space',
|
|
658
658
|
deleteFolder: 'Delete Folder',
|
|
659
659
|
confirmDeleteFolder: (name: string) => `Delete folder "${name}" and all its contents? This cannot be undone.`,
|
|
660
|
+
newFile: 'New File',
|
|
661
|
+
importFile: 'Import File',
|
|
662
|
+
importToSpace: 'Import file to this space',
|
|
663
|
+
},
|
|
664
|
+
fileImport: {
|
|
665
|
+
title: 'Import Files',
|
|
666
|
+
subtitle: 'Save files to your knowledge base or let AI organize them',
|
|
667
|
+
dropzoneText: 'Drag files here, or',
|
|
668
|
+
dropzoneButton: 'click to select',
|
|
669
|
+
dropzoneCompact: 'Drag more files, or',
|
|
670
|
+
dropzoneCompactButton: 'click to add',
|
|
671
|
+
dropzoneMobile: 'Tap to select files',
|
|
672
|
+
fileCount: (n: number) => `${n} file${n !== 1 ? 's' : ''}`,
|
|
673
|
+
clearAll: 'Clear all',
|
|
674
|
+
unsupported: 'Unsupported file type',
|
|
675
|
+
tooLarge: (max: string) => `File too large (max ${max})`,
|
|
676
|
+
archiveTitle: 'Save to Knowledge Base',
|
|
677
|
+
archiveDesc: 'Save as-is to a space',
|
|
678
|
+
digestTitle: 'AI Organize',
|
|
679
|
+
digestDesc: 'Extract key points into notes',
|
|
680
|
+
archiveConfigTitle: 'Save to Knowledge Base',
|
|
681
|
+
back: '← Back',
|
|
682
|
+
targetSpace: 'Target space',
|
|
683
|
+
rootDir: 'Root',
|
|
684
|
+
conflictLabel: 'If file already exists',
|
|
685
|
+
conflictRename: 'Auto-rename (add number suffix)',
|
|
686
|
+
conflictSkip: 'Skip',
|
|
687
|
+
conflictOverwrite: 'Overwrite existing file',
|
|
688
|
+
overwriteWarn: 'This will permanently replace existing file content',
|
|
689
|
+
cancel: 'Cancel',
|
|
690
|
+
importButton: (n: number) => `Save ${n} file${n !== 1 ? 's' : ''}`,
|
|
691
|
+
importing: 'Saving...',
|
|
692
|
+
preparing: 'Preparing...',
|
|
693
|
+
successToast: (n: number, space: string) => `Saved ${n} file${n !== 1 ? 's' : ''} to ${space || 'knowledge base'}`,
|
|
694
|
+
updatedIndex: (n: number) => `Updated ${n} index file${n !== 1 ? 's' : ''}`,
|
|
695
|
+
partialToast: (ok: number, total: number) => `Saved ${ok}/${total} files`,
|
|
696
|
+
skipReason: 'File already exists',
|
|
697
|
+
failToast: 'Import failed',
|
|
698
|
+
retry: 'Retry',
|
|
699
|
+
undo: 'Undo',
|
|
700
|
+
discardTitle: 'Discard import?',
|
|
701
|
+
discardMessage: (n: number) => `Discard ${n} selected file${n !== 1 ? 's' : ''}?`,
|
|
702
|
+
discardConfirm: 'Discard',
|
|
703
|
+
discardCancel: 'Cancel',
|
|
704
|
+
dropOverlay: 'Drop files to import into knowledge base',
|
|
705
|
+
dropOverlayFormats: 'Supports .md .txt .pdf .csv .json .yaml .html',
|
|
706
|
+
onboardingHint: 'Already have notes? Import files →',
|
|
707
|
+
digestPromptSingle: (name: string) => `Please read ${name}, extract key information and organize it into the appropriate place in my knowledge base.`,
|
|
708
|
+
digestPromptMulti: (n: number) => `Please read these ${n} files, extract key information and organize each into the appropriate place in my knowledge base.`,
|
|
709
|
+
arrowTo: '→',
|
|
710
|
+
remove: 'Remove',
|
|
660
711
|
},
|
|
661
712
|
dirView: {
|
|
662
713
|
gridView: 'Grid view',
|
package/app/lib/i18n-zh.ts
CHANGED
|
@@ -681,6 +681,57 @@ export const zh = {
|
|
|
681
681
|
convertToSpace: '转为空间',
|
|
682
682
|
deleteFolder: '删除文件夹',
|
|
683
683
|
confirmDeleteFolder: (name: string) => `删除文件夹「${name}」及其所有内容?此操作不可撤销。`,
|
|
684
|
+
newFile: '新建文件',
|
|
685
|
+
importFile: '导入文件',
|
|
686
|
+
importToSpace: '导入文件到此空间',
|
|
687
|
+
},
|
|
688
|
+
fileImport: {
|
|
689
|
+
title: '导入文件',
|
|
690
|
+
subtitle: '将外部文件存入知识库或让 AI 帮你整理',
|
|
691
|
+
dropzoneText: '拖拽文件到这里,或',
|
|
692
|
+
dropzoneButton: '点击选择',
|
|
693
|
+
dropzoneCompact: '拖拽更多文件,或',
|
|
694
|
+
dropzoneCompactButton: '点击添加',
|
|
695
|
+
dropzoneMobile: '点击选择文件',
|
|
696
|
+
fileCount: (n: number) => `${n} 个文件`,
|
|
697
|
+
clearAll: '清空全部',
|
|
698
|
+
unsupported: '不支持的文件类型',
|
|
699
|
+
tooLarge: (max: string) => `文件过大(最大 ${max})`,
|
|
700
|
+
archiveTitle: '存入知识库',
|
|
701
|
+
archiveDesc: '原样归档到指定空间',
|
|
702
|
+
digestTitle: 'AI 帮我整理',
|
|
703
|
+
digestDesc: '提取要点,沉淀到笔记',
|
|
704
|
+
archiveConfigTitle: '存入知识库',
|
|
705
|
+
back: '← 返回',
|
|
706
|
+
targetSpace: '目标空间',
|
|
707
|
+
rootDir: '根目录',
|
|
708
|
+
conflictLabel: '同名文件已存在时',
|
|
709
|
+
conflictRename: '自动重命名(添加序号后缀)',
|
|
710
|
+
conflictSkip: '跳过不导入',
|
|
711
|
+
conflictOverwrite: '覆盖已有文件',
|
|
712
|
+
overwriteWarn: '将永久替换已有文件内容',
|
|
713
|
+
cancel: '取消',
|
|
714
|
+
importButton: (n: number) => `存入 ${n} 个文件`,
|
|
715
|
+
importing: '正在存入...',
|
|
716
|
+
preparing: '准备中...',
|
|
717
|
+
successToast: (n: number, space: string) => `已存入 ${n} 个文件到 ${space || '知识库'}`,
|
|
718
|
+
updatedIndex: (n: number) => `更新了 ${n} 个索引文件`,
|
|
719
|
+
partialToast: (ok: number, total: number) => `存入 ${ok}/${total} 个文件`,
|
|
720
|
+
skipReason: '同名文件已存在',
|
|
721
|
+
failToast: '导入失败',
|
|
722
|
+
retry: '重试',
|
|
723
|
+
undo: '撤销',
|
|
724
|
+
discardTitle: '放弃导入?',
|
|
725
|
+
discardMessage: (n: number) => `放弃已选的 ${n} 个文件?`,
|
|
726
|
+
discardConfirm: '放弃',
|
|
727
|
+
discardCancel: '取消',
|
|
728
|
+
dropOverlay: '松开鼠标,导入文件到知识库',
|
|
729
|
+
dropOverlayFormats: '支持 .md .txt .pdf .csv .json .yaml .html',
|
|
730
|
+
onboardingHint: '已有笔记?导入文件到知识库 →',
|
|
731
|
+
digestPromptSingle: (name: string) => `请阅读 ${name},提取关键信息整理到知识库中合适的位置。`,
|
|
732
|
+
digestPromptMulti: (n: number) => `请阅读这 ${n} 个文件,提取关键信息分别整理到知识库中合适的位置。`,
|
|
733
|
+
arrowTo: '→',
|
|
734
|
+
remove: '移除',
|
|
684
735
|
},
|
|
685
736
|
dirView: {
|
|
686
737
|
gridView: '网格视图',
|
package/package.json
CHANGED