@customize-agent/knowledge 2.0.0 → 2.1.1

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.
Files changed (37) hide show
  1. package/dist/chunking/text-chunker.d.ts +12 -1
  2. package/dist/chunking/text-chunker.js +219 -41
  3. package/dist/core/change-tracker.d.ts +1 -0
  4. package/dist/core/change-tracker.js +21 -0
  5. package/dist/core/index-state-store.d.ts +35 -1
  6. package/dist/core/index-state-store.js +249 -16
  7. package/dist/core/knowledge-base-manager.d.ts +69 -3
  8. package/dist/core/knowledge-base-manager.js +535 -132
  9. package/dist/core/multi-project-manager.d.ts +7 -4
  10. package/dist/core/multi-project-manager.js +35 -23
  11. package/dist/embedding/embedding-provider.d.ts +1 -0
  12. package/dist/embedding/embedding-provider.js +16 -1
  13. package/dist/extraction/content-extractor.d.ts +2 -0
  14. package/dist/extraction/content-extractor.js +209 -42
  15. package/dist/extraction/module-resolver.d.ts +17 -0
  16. package/dist/extraction/module-resolver.js +113 -0
  17. package/dist/index.d.ts +2 -3
  18. package/dist/index.js +2 -2
  19. package/dist/llm/llm-search-provider.d.ts +23 -0
  20. package/dist/llm/llm-search-provider.js +1 -0
  21. package/dist/search/federation-search.d.ts +29 -0
  22. package/dist/search/federation-search.js +8 -1
  23. package/dist/vector/chroma-store.d.ts +2 -0
  24. package/dist/vector/chroma-store.js +53 -22
  25. package/dist/vector/vector-indexer.d.ts +3 -0
  26. package/dist/vector/vector-indexer.js +23 -0
  27. package/package.json +11 -3
  28. package/dist/server/dashboard-client.d.ts +0 -2
  29. package/dist/server/dashboard-client.js +0 -396
  30. package/dist/server/dashboard-i18n.d.ts +0 -112
  31. package/dist/server/dashboard-i18n.js +0 -220
  32. package/dist/server/dashboard-page.d.ts +0 -6
  33. package/dist/server/dashboard-page.js +0 -138
  34. package/dist/server/dashboard-server.d.ts +0 -13
  35. package/dist/server/dashboard-server.js +0 -225
  36. package/dist/server/dashboard-styles.d.ts +0 -1
  37. package/dist/server/dashboard-styles.js +0 -152
@@ -1,220 +0,0 @@
1
- const zhCN = {
2
- title: 'Customize Agent — 知识库管理',
3
- subtitle: '本地项目知识库、全局知识库、关系与去重状态总览',
4
- reindex: '重新索引',
5
- refresh: '刷新',
6
- stats: '统计',
7
- projects: '项目',
8
- fileOps: '文件操作',
9
- search: '搜索',
10
- relationships: '关系明细',
11
- relationshipSummary: '关系摘要',
12
- duplicates: '跨项目重复',
13
- tags: '标签',
14
- ignoreRules: '忽略规则',
15
- config: '配置',
16
- capabilities: '类型能力矩阵',
17
- pluginGuide: '解析器能力',
18
- vectorizable: '可向量化',
19
- builtInTool: '内置工具',
20
- extraction: '提取策略',
21
- sourceFilePath: '源文件路径',
22
- targetRelativePath: '目标相对路径(可选)',
23
- addFile: '添加文件',
24
- relativePath: '相对路径',
25
- remove: '移除',
26
- filePath: '文件路径',
27
- tagsPlaceholder: '标签,逗号分隔',
28
- tag: '打标签',
29
- ignorePattern: '忽略规则,如 **/*.tmp',
30
- ignore: '忽略',
31
- ignoreSimilar: '忽略相似',
32
- searchPlaceholder: '搜索知识库...',
33
- loading: '加载中...',
34
- noProjects: '暂无项目',
35
- noResults: '无结果',
36
- noRelationships: '暂无关系',
37
- noDuplicates: '暂无重复文件',
38
- noTags: '暂无标签',
39
- noIgnoreRules: '暂无忽略规则',
40
- project: '项目',
41
- global: '全局',
42
- files: '文件',
43
- chunks: 'Chunks',
44
- size: '大小',
45
- path: '路径',
46
- scope: '范围',
47
- score: '得分',
48
- updated: '已更新',
49
- ok: '完成',
50
- failed: '失败',
51
- overviewTab: '总览',
52
- filesTab: '文件管理',
53
- settingsTab: '设置',
54
- searchHelp: '输入你想找的内容,例如“招标文件”“合同”“接口文档”。系统只检索已解析入库的知识,不直接读取 knowledgeBase 原始文件。',
55
- scopeLabel: '搜索范围',
56
- scopeAll: '全部知识库',
57
- scopeProject: '当前项目',
58
- scopeGlobal: '全局知识库',
59
- uploadTitle: '上传资料到知识库',
60
- uploadHelp: '直接选择文件即可。系统会自动按文件类型归类,并立即解析入库。',
61
- uploadChoose: '选择文件上传',
62
- uploadSupport: '支持 PDF、Word、Excel、代码、图纸、数据文件、图表文件等',
63
- filesTitle: '文件管理',
64
- filesHelp: '这里展示的是已经成功解析并入库的文件。你可以按类型或目录筛选,并对单个文件打标签、忽略同类或移除。',
65
- fileType: '文件类型',
66
- allTypes: '全部类型',
67
- folderDir: '文件夹目录',
68
- allFolders: '全部目录',
69
- categoryDirs: '分类文件夹',
70
- categoryDirsHelp: '这些是 knowledgeBase 中的默认分类目录名称。上传文件时会自动归类;技术用户后续可以改这些目录名。',
71
- projectId: '项目 ID',
72
- noCategoryDirs: '暂无分类目录配置',
73
- loadFailed: '加载失败',
74
- uploadSuccess: '上传并入库成功',
75
- filesSyncSkipped: '已同步本地 knowledgeBase,{count} 个文件解析失败未入库',
76
- filesSyncChanged: '已同步本地 knowledgeBase:{count} 个变更',
77
- filesSyncUnchanged: '已同步本地 knowledgeBase,无新增变更',
78
- noFilteredFiles: '没有符合筛选条件的已入库文件。',
79
- rootFolder: '根目录',
80
- confirmRemove: '确定从知识库移除这个文件吗?',
81
- tagPrompt: '给这个文件添加标签,多个标签用逗号分隔:',
82
- defaultTag: '重要',
83
- ignorePrompt: '要忽略哪类文件?',
84
- enabled: '启用',
85
- disabled: '禁用',
86
- yes: '是',
87
- plugin: '插件',
88
- externalPlugins: '外部增强插件',
89
- available: '可用',
90
- unavailable: '不可用',
91
- syncNow: '同步知识库',
92
- language: '语言',
93
- delete: '删除',
94
- cancel: '取消',
95
- confirm: '确认',
96
- retryIndex: '重新入库',
97
- failedFilesTitle: '入库失败文件',
98
- failedFilesHelp: '这里展示最近一次同步中解析失败、未入库的文件。你可以删除无效文件,或修复文件后重新入库。',
99
- noFailedFiles: '暂无入库失败文件。',
100
- reason: '原因',
101
- operation: '操作',
102
- deleteConfirmTitle: '删除文件',
103
- deleteConfirmMessage: '确定要从 knowledgeBase 删除这个文件吗?此操作会删除本地文件。',
104
- retryStarted: '已重新尝试入库',
105
- categoryLabels: { document: '文档', code: '代码', data: '数据', cad: '图纸', diagram: '图表', image: '图片', spreadsheet: '表格', archive: '压缩包', web: '网页', other: '其他' },
106
- };
107
- const enUS = {
108
- title: 'Customize Agent — Knowledge Dashboard',
109
- subtitle: 'Overview for project KB, global KB, relationships and deduplication',
110
- reindex: 'Reindex',
111
- refresh: 'Refresh',
112
- stats: 'Stats',
113
- projects: 'Projects',
114
- fileOps: 'File Ops',
115
- search: 'Search',
116
- relationships: 'Relationships',
117
- relationshipSummary: 'Relationship Summary',
118
- duplicates: 'Cross-project Duplicates',
119
- tags: 'Tags',
120
- ignoreRules: 'Ignore Rules',
121
- config: 'Config',
122
- capabilities: 'Type Capability Matrix',
123
- pluginGuide: 'External Parser Setup',
124
- vectorizable: 'Vectorizable',
125
- builtInTool: 'Built-in Tool',
126
- extraction: 'Extraction',
127
- sourceFilePath: 'source file path',
128
- targetRelativePath: 'target relative path (optional)',
129
- addFile: 'Add File',
130
- relativePath: 'relative path',
131
- remove: 'Remove',
132
- filePath: 'file path',
133
- tagsPlaceholder: 'tags, comma separated',
134
- tag: 'Tag',
135
- ignorePattern: 'ignore pattern, e.g. **/*.tmp',
136
- ignore: 'Ignore',
137
- ignoreSimilar: 'Ignore Similar',
138
- searchPlaceholder: 'Search knowledge base...',
139
- loading: 'Loading...',
140
- noProjects: 'No projects',
141
- noResults: 'No results',
142
- noRelationships: 'No relationships',
143
- noDuplicates: 'No duplicates',
144
- noTags: 'No tags',
145
- noIgnoreRules: 'No ignore rules',
146
- project: 'Project',
147
- global: 'Global',
148
- files: 'Files',
149
- chunks: 'Chunks',
150
- size: 'Size',
151
- path: 'Path',
152
- scope: 'Scope',
153
- score: 'Score',
154
- updated: 'Updated',
155
- ok: 'OK',
156
- failed: 'Failed',
157
- overviewTab: 'Overview',
158
- filesTab: 'File Manager',
159
- settingsTab: 'Settings',
160
- searchHelp: 'Enter what you want to find, such as bid documents, contracts, or API docs. The system searches parsed knowledge only and does not read raw knowledgeBase files directly.',
161
- scopeLabel: 'Search Scope',
162
- scopeAll: 'All Knowledge Bases',
163
- scopeProject: 'Current Project',
164
- scopeGlobal: 'Global Knowledge Base',
165
- uploadTitle: 'Upload Knowledge Files',
166
- uploadHelp: 'Select files directly. The system classifies, parses, and indexes them immediately.',
167
- uploadChoose: 'Choose files to upload',
168
- uploadSupport: 'Supports PDF, Word, Excel, code, drawings, data files, diagrams, and more',
169
- filesTitle: 'File Manager',
170
- filesHelp: 'This page shows files that were parsed and indexed successfully. Filter by type or folder, then tag, ignore similar files, or remove individual files.',
171
- fileType: 'File Type',
172
- allTypes: 'All Types',
173
- folderDir: 'Folder',
174
- allFolders: 'All Folders',
175
- categoryDirs: 'Category Folders',
176
- categoryDirsHelp: 'These are the default category folder names in knowledgeBase. Uploaded files are classified automatically; advanced users can rename them later.',
177
- projectId: 'Project ID',
178
- noCategoryDirs: 'No category folder config',
179
- loadFailed: 'Load failed',
180
- uploadSuccess: 'Uploaded and indexed successfully',
181
- filesSyncSkipped: 'Local knowledgeBase synced, {count} files failed to parse and were not indexed',
182
- filesSyncChanged: 'Local knowledgeBase synced: {count} changes',
183
- filesSyncUnchanged: 'Local knowledgeBase synced, no changes',
184
- noFilteredFiles: 'No indexed files match the current filters.',
185
- rootFolder: 'Root',
186
- confirmRemove: 'Remove this file from the knowledge base?',
187
- tagPrompt: 'Add tags to this file, separated by commas:',
188
- defaultTag: 'important',
189
- ignorePrompt: 'Which files should be ignored?',
190
- enabled: 'Enabled',
191
- disabled: 'Disabled',
192
- yes: 'yes',
193
- plugin: 'plugin',
194
- externalPlugins: 'External Enhancements',
195
- available: 'available',
196
- unavailable: 'unavailable',
197
- syncNow: 'Sync Knowledge Base',
198
- language: 'Language',
199
- delete: 'Delete',
200
- cancel: 'Cancel',
201
- confirm: 'Confirm',
202
- retryIndex: 'Retry Indexing',
203
- failedFilesTitle: 'Failed Files',
204
- failedFilesHelp: 'Files that failed to parse during the latest sync are listed here. Delete invalid files or fix them and retry indexing.',
205
- noFailedFiles: 'No failed files.',
206
- reason: 'Reason',
207
- operation: 'Actions',
208
- deleteConfirmTitle: 'Delete File',
209
- deleteConfirmMessage: 'Remove this file from knowledgeBase? This deletes the local file.',
210
- retryStarted: 'Retry indexing started',
211
- categoryLabels: { document: 'Documents', code: 'Code', data: 'Data', cad: 'Drawings', diagram: 'Diagrams', image: 'Images', spreadsheet: 'Spreadsheets', archive: 'Archives', web: 'Web', other: 'Other' },
212
- };
213
- export const DASHBOARD_MESSAGES = {
214
- 'zh-CN': zhCN,
215
- 'en-US': enUS,
216
- };
217
- export function resolveDashboardMessages(locale) {
218
- const normalized = locale === 'en-US' ? 'en-US' : 'zh-CN';
219
- return { locale: normalized, messages: DASHBOARD_MESSAGES[normalized] };
220
- }
@@ -1,6 +0,0 @@
1
- import type { DashboardLocale, DashboardMessages } from './dashboard-i18n.js';
2
- export interface DashboardPageOptions {
3
- locale: DashboardLocale;
4
- messages: DashboardMessages;
5
- }
6
- export declare function renderDashboardHtml(options: DashboardPageOptions): string;
@@ -1,138 +0,0 @@
1
- import { dashboardStyles } from './dashboard-styles.js';
2
- import { renderDashboardClient } from './dashboard-client.js';
3
- export function renderDashboardHtml(options) {
4
- const { locale, messages } = options;
5
- return `<!doctype html>
6
- <html lang="${locale}">
7
- <head>
8
- <meta charset="utf-8" />
9
- <meta name="viewport" content="width=device-width, initial-scale=1" />
10
- <title>${escapeHtml(messages.title)}</title>
11
- <style>${dashboardStyles}</style>
12
- </head>
13
- <body>
14
- <header>
15
- <div>
16
- <h1>${escapeHtml(messages.title)}</h1>
17
- <div class="subtitle">${escapeHtml(messages.subtitle)}</div>
18
- </div>
19
- <div class="toolbar topbar">
20
- <button class="tab active" data-page="overview" onclick="showPage('overview')">${escapeHtml(messages.overviewTab)}</button>
21
- <button class="tab" data-page="files" onclick="showPage('files')">${escapeHtml(messages.filesTab)}</button>
22
- <button class="tab" data-page="settings" onclick="showPage('settings')">${escapeHtml(messages.settingsTab)}</button>
23
- <label class="select-wrap compact language-select"><select id="languageSelect" class="native-select" aria-label="${escapeAttr(messages.language)}" onchange="switchLanguage(this.value)"><option value="zh-CN" ${locale === 'zh-CN' ? 'selected' : ''}>中文</option><option value="en-US" ${locale === 'en-US' ? 'selected' : ''}>English</option></select></label>
24
- <button id="syncButton" class="primary" onclick="syncNow(this)">${escapeHtml(messages.syncNow)}</button>
25
- </div>
26
- </header>
27
- <main>
28
- <div id="page-overview" class="page active">
29
- <section>
30
- <h2>${escapeHtml(messages.search)}</h2>
31
- <div class="helper-text">${escapeHtml(messages.searchHelp)}</div>
32
- <div class="search-bar">
33
- <label class="select-wrap"><span>${escapeHtml(messages.scopeLabel)}</span><select id="scope"><option value="all">${escapeHtml(messages.scopeAll)}</option><option value="project">${escapeHtml(messages.scopeProject)}</option><option value="global">${escapeHtml(messages.scopeGlobal)}</option></select></label>
34
- <input id="query" placeholder="${escapeAttr(messages.searchPlaceholder)}" onkeydown="if(event.key==='Enter') search(document.getElementById('searchButton'))" />
35
- <button id="searchButton" class="primary" onclick="search(this)">${escapeHtml(messages.search)}</button>
36
- </div>
37
- <div id="results"></div>
38
-
39
- </section>
40
- <section class="panel-gap upload-summary">
41
- <h2>${escapeHtml(messages.fileOps)}</h2>
42
- <div class="helper-title">${escapeHtml(messages.uploadTitle)}</div>
43
- <div class="helper-text">${escapeHtml(messages.uploadHelp)}</div>
44
- <div class="upload-card" onclick="document.getElementById('uploadFile').click()">
45
- <input id="uploadFile" type="file" multiple onchange="uploadFiles()" />
46
- <div class="upload-icon">↑</div>
47
- <div><b>${escapeHtml(messages.uploadChoose)}</b><span>${escapeHtml(messages.uploadSupport)}</span></div>
48
- </div>
49
- <div id="opsStatus" class="status"></div>
50
- </section>
51
- <div class="grid panel-gap">
52
- <section>
53
- <h2>${escapeHtml(messages.stats)}</h2>
54
- <div id="stats">${escapeHtml(messages.loading)}</div>
55
- </section>
56
- <section>
57
- <h2>${escapeHtml(messages.config)}</h2>
58
- <div id="config">${escapeHtml(messages.loading)}</div>
59
- </section>
60
- </div>
61
- <div class="grid panel-gap">
62
- <section>
63
- <h2>${escapeHtml(messages.relationshipSummary)}</h2>
64
- <div id="relationshipSummary">${escapeHtml(messages.loading)}</div>
65
- </section>
66
- <section>
67
- <h2>${escapeHtml(messages.duplicates)}</h2>
68
- <div id="duplicates">${escapeHtml(messages.loading)}</div>
69
- </section>
70
- </div>
71
- </div>
72
-
73
- <div id="page-files" class="page">
74
- <section>
75
- <h2>${escapeHtml(messages.filesTitle)}</h2>
76
- <div class="helper-text">${escapeHtml(messages.filesHelp)}</div>
77
- <div class="filter-bar">
78
- <label class="select-wrap"><span>${escapeHtml(messages.fileType)}</span><select id="fileTypeFilter" onchange="renderFiles()"><option value="all">${escapeHtml(messages.allTypes)}</option></select></label>
79
- <label class="select-wrap"><span>${escapeHtml(messages.folderDir)}</span><select id="folderFilter" onchange="renderFiles()"><option value="all">${escapeHtml(messages.allFolders)}</option></select></label>
80
- </div>
81
- <div id="filesSyncStatus" class="status"></div>
82
- <div id="filesPanel">${escapeHtml(messages.loading)}</div>
83
- <div class="subsection">
84
- <h3>${escapeHtml(messages.failedFilesTitle)}</h3>
85
- <div class="helper-text">${escapeHtml(messages.failedFilesHelp)}</div>
86
- <div id="failedFilesPanel">${escapeHtml(messages.loading)}</div>
87
- </div>
88
- </section>
89
- </div>
90
-
91
- <div id="page-settings" class="page">
92
- <div class="grid">
93
- <section>
94
- <h2>${escapeHtml(messages.capabilities)}</h2>
95
- <div id="capabilities">${escapeHtml(messages.loading)}</div>
96
- </section>
97
- <section>
98
- <h2>${escapeHtml(messages.categoryDirs)}</h2>
99
- <div class="helper-text">${escapeHtml(messages.categoryDirsHelp)}</div>
100
- <div id="categoryDirs">${escapeHtml(messages.loading)}</div>
101
- </section>
102
- </div>
103
- <div class="grid panel-gap">
104
- <section>
105
- <h2>${escapeHtml(messages.ignoreRules)}</h2>
106
- <div id="ignorePanel">${escapeHtml(messages.loading)}</div>
107
- </section>
108
- <section>
109
- <h2>${escapeHtml(messages.projects)}</h2>
110
- <div id="projects">${escapeHtml(messages.loading)}</div>
111
- </section>
112
- </div>
113
- <section class="panel-gap">
114
- <h2>${escapeHtml(messages.relationships)}</h2>
115
- <div id="relationships">${escapeHtml(messages.loading)}</div>
116
- </section>
117
- </div>
118
- </main>
119
- <div id="modalBackdrop" class="modal-backdrop" hidden>
120
- <div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
121
- <h3 id="modalTitle"></h3>
122
- <p id="modalMessage"></p>
123
- <div class="modal-actions">
124
- <button id="modalCancel" type="button"></button>
125
- <button id="modalConfirm" class="danger" type="button"></button>
126
- </div>
127
- </div>
128
- </div>
129
- <script>${renderDashboardClient(messages)}</script>
130
- </body>
131
- </html>`;
132
- }
133
- function escapeHtml(value) {
134
- return value.replace(/[&<>"]/g, char => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[char] ?? char);
135
- }
136
- function escapeAttr(value) {
137
- return escapeHtml(value).replace(/'/g, '&#39;');
138
- }
@@ -1,13 +0,0 @@
1
- import { type DashboardLocale } from './dashboard-i18n.js';
2
- export interface DashboardServerOptions {
3
- projectRoot: string;
4
- port?: number;
5
- host?: string;
6
- locale?: DashboardLocale;
7
- storageRoot?: string;
8
- }
9
- export interface DashboardServerHandle {
10
- url: string;
11
- close(): Promise<void>;
12
- }
13
- export declare function startKnowledgeDashboard(options: DashboardServerOptions): Promise<DashboardServerHandle>;
@@ -1,225 +0,0 @@
1
- import * as http from 'node:http';
2
- import * as path from 'node:path';
3
- import { MultiProjectManager } from '../core/multi-project-manager.js';
4
- import { computeProjectId } from '../core/project-id.js';
5
- import { getProjectKbPath } from '../core/project-config.js';
6
- import { ExternalExtractorRegistry } from '../extraction/external-extractor.js';
7
- import { resolveDashboardMessages } from './dashboard-i18n.js';
8
- import { renderDashboardHtml } from './dashboard-page.js';
9
- export async function startKnowledgeDashboard(options) {
10
- const host = options.host ?? '127.0.0.1';
11
- const port = options.port ?? 17321;
12
- const manager = new MultiProjectManager(options.storageRoot);
13
- const projectRoot = path.resolve(options.projectRoot);
14
- const dashboard = resolveDashboardMessages(options.locale);
15
- const server = http.createServer((req, res) => {
16
- void handleRequest(req, res, manager, projectRoot, dashboard).catch(error => {
17
- sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });
18
- });
19
- });
20
- await new Promise((resolve, reject) => {
21
- server.once('error', reject);
22
- server.listen(port, host, () => {
23
- server.off('error', reject);
24
- resolve();
25
- });
26
- });
27
- return {
28
- url: `http://${host}:${port}`,
29
- close: async () => {
30
- await new Promise((resolve, reject) => {
31
- server.close(error => error ? reject(error) : resolve());
32
- });
33
- await manager.shutdown();
34
- },
35
- };
36
- }
37
- async function handleRequest(req, res, manager, projectRoot, dashboard) {
38
- const url = new URL(req.url ?? '/', 'http://localhost');
39
- if (req.method === 'GET' && url.pathname === '/') {
40
- const currentDashboard = resolveDashboardMessages(url.searchParams.get('lang') ?? dashboard.locale);
41
- sendHtml(res, renderDashboardHtml(currentDashboard));
42
- return;
43
- }
44
- if (req.method === 'GET' && url.pathname === '/api/kb/stats') {
45
- const project = await manager.getProject(projectRoot);
46
- const global = await manager.getGlobalKB();
47
- sendJson(res, 200, {
48
- project: {
49
- projectId: project.projectId,
50
- projectRoot,
51
- kbPath: project.kbPath,
52
- ...project.getStats(),
53
- },
54
- global: {
55
- kbPath: global.kbPath,
56
- ...global.getStats(),
57
- },
58
- });
59
- return;
60
- }
61
- if (req.method === 'GET' && url.pathname === '/api/kb/projects') {
62
- sendJson(res, 200, { projects: await manager.listProjects() });
63
- return;
64
- }
65
- if (req.method === 'GET' && url.pathname === '/api/kb/files') {
66
- const project = await manager.getProject(projectRoot);
67
- const diff = await project.incrementalIndex();
68
- sendJson(res, 200, { files: project.listFiles(), sync: diff });
69
- return;
70
- }
71
- if (req.method === 'GET' && url.pathname === '/api/kb/search') {
72
- const query = url.searchParams.get('q') ?? '';
73
- const scope = normalizeScope(url.searchParams.get('scope'));
74
- const limit = Number(url.searchParams.get('limit') ?? 10);
75
- if (!query.trim()) {
76
- sendJson(res, 400, { error: 'Missing query parameter q' });
77
- return;
78
- }
79
- const results = await manager.search(projectRoot, query, { scope, limit });
80
- sendJson(res, 200, results);
81
- return;
82
- }
83
- if (req.method === 'POST' && url.pathname === '/api/kb/reindex') {
84
- const project = await manager.getProject(projectRoot);
85
- const diff = await project.incrementalIndex();
86
- sendJson(res, 200, diff);
87
- return;
88
- }
89
- if (req.method === 'POST' && url.pathname === '/api/kb/files') {
90
- const body = await readJsonBody(req);
91
- const project = await manager.getProject(projectRoot);
92
- const diff = await project.addFile(body.sourcePath, body.targetRelativePath);
93
- if (diff.skippedFiles.length > 0) {
94
- const reason = diff.skippedFiles.map(item => `${item.file.relativePath}: ${item.reason}`).join('\n');
95
- sendJson(res, 422, { error: `文件已复制但解析失败,未入库:\n${reason}`, diff });
96
- return;
97
- }
98
- sendJson(res, 200, diff);
99
- return;
100
- }
101
- if (req.method === 'POST' && url.pathname === '/api/kb/upload') {
102
- const body = await readJsonBody(req);
103
- const project = await manager.getProject(projectRoot);
104
- const diff = await project.uploadFile(body.fileName, Buffer.from(body.contentBase64, 'base64'), body.targetRelativePath);
105
- const uploadedBaseName = path.basename(body.targetRelativePath ?? body.fileName);
106
- const relevantSkipped = diff.skippedFiles.filter(item => path.basename(item.file.relativePath) === uploadedBaseName);
107
- if (relevantSkipped.length > 0) {
108
- const reason = relevantSkipped.map(item => `${item.file.relativePath}: ${item.reason}`).join('\n');
109
- sendJson(res, 422, { error: `文件上传成功但解析失败,未入库:\n${reason}`, diff });
110
- return;
111
- }
112
- sendJson(res, 200, diff);
113
- return;
114
- }
115
- if (req.method === 'GET' && url.pathname === '/api/kb/failed-files') {
116
- const project = await manager.getProject(projectRoot);
117
- await project.incrementalIndex();
118
- sendJson(res, 200, { failedFiles: project.listFailedFiles() });
119
- return;
120
- }
121
- if (req.method === 'POST' && url.pathname.startsWith('/api/kb/failed-files/')) {
122
- const project = await manager.getProject(projectRoot);
123
- const diff = await project.incrementalIndex();
124
- const relativePath = decodeURIComponent(url.pathname.slice('/api/kb/failed-files/'.length));
125
- sendJson(res, 200, { ok: true, diff, failed: diff.skippedFiles.find(item => item.file.relativePath === relativePath) });
126
- return;
127
- }
128
- if (req.method === 'DELETE' && url.pathname.startsWith('/api/kb/files/')) {
129
- const relativePath = decodeURIComponent(url.pathname.slice('/api/kb/files/'.length));
130
- const project = await manager.getProject(projectRoot);
131
- await project.removeFile(relativePath);
132
- sendJson(res, 200, { ok: true });
133
- return;
134
- }
135
- if (req.method === 'POST' && url.pathname === '/api/kb/tags') {
136
- const body = await readJsonBody(req);
137
- const project = await manager.getProject(projectRoot);
138
- project.tagFile(body.filePath, body.tags);
139
- sendJson(res, 200, { ok: true });
140
- return;
141
- }
142
- if (req.method === 'GET' && url.pathname === '/api/kb/tags') {
143
- const project = await manager.getProject(projectRoot);
144
- sendJson(res, 200, { tags: project.listTags(url.searchParams.get('file') ?? undefined) });
145
- return;
146
- }
147
- if (req.method === 'POST' && url.pathname === '/api/kb/ignore') {
148
- const body = await readJsonBody(req);
149
- const project = await manager.getProject(projectRoot);
150
- project.addIgnoreRule(body.pattern);
151
- sendJson(res, 200, { ok: true });
152
- return;
153
- }
154
- if (req.method === 'GET' && url.pathname === '/api/kb/ignore') {
155
- const project = await manager.getProject(projectRoot);
156
- sendJson(res, 200, { rules: project.listIgnoreRules() });
157
- return;
158
- }
159
- if (req.method === 'GET' && url.pathname === '/api/kb/relationships') {
160
- const project = await manager.getProject(projectRoot);
161
- sendJson(res, 200, { relationships: project.listRelationships(url.searchParams.get('file') ?? undefined) });
162
- return;
163
- }
164
- if (req.method === 'GET' && url.pathname === '/api/kb/duplicates') {
165
- sendJson(res, 200, { duplicates: await manager.findCrossProjectDuplicates() });
166
- return;
167
- }
168
- if (req.method === 'GET' && url.pathname === '/api/kb/config') {
169
- const project = await manager.getProject(projectRoot);
170
- sendJson(res, 200, {
171
- projectId: computeProjectId(projectRoot),
172
- projectRoot,
173
- kbPath: getProjectKbPath(projectRoot),
174
- categoryDirs: project.getProjectConfig()?.categoryDirs ?? {},
175
- });
176
- return;
177
- }
178
- if (req.method === 'GET' && url.pathname === '/api/kb/capabilities') {
179
- sendJson(res, 200, { capabilities: getCapabilities(), externalExtractors: ExternalExtractorRegistry.fromEnvironment().listCapabilities() });
180
- return;
181
- }
182
- sendJson(res, 404, { error: 'Not found' });
183
- }
184
- function getCapabilities() {
185
- return [
186
- { category: 'cad', format: 'autocad/dxf', vectorizable: true, extraction: 'builtin_dxf_layers_blocks_entities_text', builtInTool: true, note: '内置 DXF 高精度结构解析:图层、块、实体类型、标注文本' },
187
- { category: 'cad', format: 'step/iges', vectorizable: true, extraction: 'builtin_step_iges_products_materials_entities', builtInTool: true, note: '内置 STEP/IGES 结构解析:产品、零件、材料、实体类型、名称属性' },
188
- { category: 'cad', format: 'mesh/obj/gltf/stl/3mf', vectorizable: true, extraction: 'builtin_mesh_structure_strings', builtInTool: true, note: '内置 Mesh 解析:对象、节点、分组、STL 头信息、3MF 部件字符串' },
189
- { category: 'cad', format: 'dwg/solidworks/binary_mesh', vectorizable: true, extraction: 'builtin_cad_binary_strings_optional_native', builtInTool: true, note: '内置二进制字符串/标题块提取,可选 native 引擎增强专有格式精度' },
190
- { category: 'data', format: 'json/jsonl', vectorizable: true, extraction: 'json_paths_values', builtInTool: true },
191
- { category: 'data', format: 'yaml/graphql/protobuf', vectorizable: true, extraction: 'structured_text', builtInTool: true },
192
- { category: 'data', format: 'xml/xsd/wsdl', vectorizable: true, extraction: 'element_text_xpath_like', builtInTool: true },
193
- { category: 'diagram', format: 'drawio/excalidraw', vectorizable: true, extraction: 'nodes_edges_text', builtInTool: true },
194
- { category: 'diagram', format: 'plantuml/mermaid', vectorizable: true, extraction: 'diagram_source_text', builtInTool: true },
195
- { category: 'diagram', format: 'visio', vectorizable: true, extraction: 'builtin_visio_adapter_optional_native', builtInTool: true, note: '已纳入正式解析链路;可选 native 引擎增强' },
196
- { category: 'document', format: 'pdf', vectorizable: true, extraction: 'builtin_pdf_parse_with_ocr_adapter', builtInTool: true, note: '内置 pdf-parse 与 OCR 适配链路,native 命令仅作为增强' },
197
- { category: 'document', format: 'office/presentation', vectorizable: true, extraction: 'builtin_mammoth_zip_xml', builtInTool: true, note: '内置 mammoth 与 Office XML 解析,native 命令仅作为增强' },
198
- { category: 'spreadsheet', format: 'excel/opendoc/csv/tsv', vectorizable: true, extraction: 'builtin_xlsx_structured_cells_formulas_merges', builtInTool: true, note: '内置高精度表格解析:工作表、范围、单元格地址、显示值、公式、合并区域、CSV/TSV' },
199
- { category: 'image', format: 'raster/raw', vectorizable: true, extraction: 'builtin_tesseract_ocr', builtInTool: true, note: '内置 tesseract.js OCR;视觉模型适配器作为增强' },
200
- ];
201
- }
202
- function normalizeScope(value) {
203
- return value === 'project' || value === 'global' || value === 'all' ? value : 'all';
204
- }
205
- async function readJsonBody(req) {
206
- const chunks = [];
207
- for await (const chunk of req)
208
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
209
- const raw = Buffer.concat(chunks).toString('utf8').trim();
210
- return (raw ? JSON.parse(raw) : {});
211
- }
212
- function sendJson(res, status, body) {
213
- res.writeHead(status, {
214
- 'content-type': 'application/json; charset=utf-8',
215
- 'cache-control': 'no-store',
216
- });
217
- res.end(JSON.stringify(body));
218
- }
219
- function sendHtml(res, html) {
220
- res.writeHead(200, {
221
- 'content-type': 'text/html; charset=utf-8',
222
- 'cache-control': 'no-store',
223
- });
224
- res.end(html);
225
- }
@@ -1 +0,0 @@
1
- export declare const dashboardStyles = "\n:root {\n color-scheme: light;\n --bg:#f5f5f7;\n --bg-glass:rgba(255,255,255,.72);\n --panel:rgba(255,255,255,.86);\n --panel-solid:#ffffff;\n --line:rgba(60,60,67,.16);\n --line-strong:rgba(60,60,67,.26);\n --text:#1d1d1f;\n --muted:#6e6e73;\n --accent:#007aff;\n --accent-2:#5856d6;\n --danger:#ff3b30;\n --ok:#34c759;\n --warn:#ff9500;\n --shadow:0 18px 45px rgba(0,0,0,.08);\n --shadow-soft:0 8px 24px rgba(0,0,0,.06);\n}\n* { box-sizing: border-box; }\nhtml { background:var(--bg); }\nbody {\n margin:0; min-height:100vh; font-family: -apple-system, BlinkMacSystemFont, \"SF Pro Display\", \"SF Pro Text\", \"Segoe UI\", sans-serif;\n background:\n radial-gradient(circle at 15% 0%, rgba(0,122,255,.12), transparent 28rem),\n radial-gradient(circle at 85% 8%, rgba(88,86,214,.10), transparent 26rem),\n linear-gradient(180deg, #fbfbfd 0%, var(--bg) 100%);\n color:var(--text);\n}\nheader {\n position: sticky; top:0; z-index:10; backdrop-filter: saturate(180%) blur(22px); -webkit-backdrop-filter: saturate(180%) blur(22px);\n padding:20px max(32px, calc((100vw - 1480px) / 2 + 32px)); border-bottom:1px solid var(--line); display:flex; align-items:center; justify-content:space-between; gap:18px;\n background:var(--bg-glass);\n}\nmain { padding:28px 32px 40px; max-width:1480px; margin:0 auto; }\nmain, aside, main > div { min-width:0; }\n.page { display:none; }\n.page.active { display:block; }\n.topbar { flex-wrap:nowrap; justify-content:flex-start; overflow-x:auto; padding-bottom:2px; scrollbar-width:none; }\n.topbar::-webkit-scrollbar { display:none; }\n.topbar > * { flex:0 0 auto; }\n.tab { background:rgba(255,255,255,.72); border-radius:999px; padding:9px 15px; color:var(--muted); }\n.tab.active { background:#1d1d1f; color:white; border-color:#1d1d1f; }\nsection {\n position:relative; overflow:visible;\n background:var(--panel); border:1px solid rgba(255,255,255,.7); border-radius:26px; padding:20px; box-shadow:var(--shadow-soft);\n backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);\n}\nsection:has(.custom-select.open), section.select-host-open { z-index:10000; }\nsection:hover { box-shadow:var(--shadow); transform:translateY(-1px); transition:.2s ease; }\nh1 { font-size:24px; margin:0; letter-spacing:-.02em; font-weight:700; }\nh2 { font-size:12px; margin:0 0 14px; color:var(--muted); text-transform:uppercase; letter-spacing:.12em; font-weight:700; }\nh3 { font-size:15px; margin:14px 0 8px; }\n.subtitle { margin-top:6px; color:var(--muted); font-size:14px; }\nbutton, input, select {\n min-width:0; background:rgba(255,255,255,.88); color:var(--text); border:1px solid var(--line); border-radius:14px; padding:10px 13px; outline:none;\n font: inherit; box-shadow: inset 0 0 0 1px rgba(255,255,255,.25);\n}\nselect {\n min-width:132px; appearance:none; -webkit-appearance:none; padding-right:34px;\n background-image:linear-gradient(45deg, transparent 50%, #6e6e73 50%), linear-gradient(135deg, #6e6e73 50%, transparent 50%);\n background-position:calc(100% - 18px) 50%, calc(100% - 12px) 50%; background-size:6px 6px, 6px 6px; background-repeat:no-repeat;\n}\nbutton { cursor:pointer; white-space:nowrap; transition:.16s ease; font-weight:600; }\nbutton:hover { border-color:rgba(0,122,255,.35); transform:translateY(-1px); box-shadow:0 8px 18px rgba(0,122,255,.12); }\nbutton.primary { background:linear-gradient(180deg, #0a84ff, #007aff); border-color:transparent; color:white; box-shadow:0 10px 22px rgba(0,122,255,.24); }\ninput { width:100%; }\ninput::placeholder { color:#a1a1a6; }\ninput:focus, select:focus { border-color:rgba(0,122,255,.55); box-shadow:0 0 0 4px rgba(0,122,255,.12); }\n.row { display:flex; gap:10px; margin-bottom:10px; align-items:center; }\n.row input { flex:1; }\n.stack { display:grid; gap:10px; }\n.stack.compact { gap:8px; }\n.field-row { display:grid; grid-template-columns:minmax(0, 1fr) auto; gap:8px; align-items:center; }\n.field-row:has(input + input + button) { grid-template-columns:minmax(0, 1fr) minmax(0, 1fr) auto; }\n.helper-title { font-size:15px; font-weight:700; color:var(--text); margin:2px 0 6px; }\n.helper-text { color:var(--muted); font-size:13px; line-height:1.6; margin-bottom:12px; overflow-wrap:anywhere; }\n.upload-card { display:flex; gap:14px; align-items:center; border:1.5px dashed rgba(0,122,255,.32); background:rgba(0,122,255,.06); border-radius:20px; padding:16px; cursor:pointer; margin-bottom:10px; }\n.upload-card:hover { background:rgba(0,122,255,.10); border-color:rgba(0,122,255,.55); }\nbutton.loading, .upload-card.loading { position:relative; pointer-events:none; opacity:.82; cursor:progress; }\nbutton.loading::after, .upload-card.loading::after { content:''; width:14px; height:14px; border:2px solid currentColor; border-top-color:transparent; border-radius:50%; display:inline-block; margin-left:8px; vertical-align:-2px; animation:spin .8s linear infinite; }\n.upload-card.loading::after { margin-left:auto; color:#007aff; width:18px; height:18px; }\nbutton:disabled { cursor:progress; }\n@keyframes spin { to { transform:rotate(360deg); } }\n.upload-card input { display:none; }\n.upload-card b { display:block; font-size:15px; }\n.upload-card span { display:block; color:var(--muted); font-size:12px; line-height:1.45; margin-top:3px; overflow-wrap:anywhere; }\n.upload-icon { width:38px; height:38px; flex:0 0 38px; border-radius:50%; display:grid; place-items:center; background:#007aff; color:white; font-weight:800; font-size:20px; }\n.advanced-block { margin-top:12px; border:1px solid var(--line); border-radius:16px; padding:10px 12px; background:rgba(255,255,255,.55); }\n.advanced-block summary { cursor:pointer; color:var(--muted); font-size:13px; font-weight:600; margin-bottom:8px; }\n.divider { height:1px; background:var(--line); margin:16px 0; }\n.search-bar { display:grid; grid-template-columns: 170px minmax(0, 1fr) auto; gap:10px; align-items:end; }\n.filter-bar { display:grid; grid-template-columns: repeat(2, minmax(180px, 260px)); gap:10px; align-items:end; margin:12px 0 14px; }\n.select-wrap { position:relative; display:grid; gap:5px; color:var(--muted); font-size:12px; font-weight:600; min-width:0; overflow:visible; }\n.select-wrap.compact { min-width:130px; }\n.language-select { width:96px; min-width:96px; }\n.language-select select { min-width:96px; width:96px; cursor:pointer; }\n.select-wrap select { width:100%; }\n.select-native-hidden { position:absolute !important; opacity:0 !important; pointer-events:none !important; width:1px !important; height:1px !important; }\n.custom-select { position:relative; min-width:0; z-index:1; }\n.custom-select.open { z-index:10001; }\n.custom-select-trigger { width:100%; justify-content:space-between; text-align:left; display:flex; align-items:center; gap:10px; background:rgba(255,255,255,.88); color:var(--text); border:1px solid var(--line); border-radius:14px; padding:10px 13px; box-shadow: inset 0 0 0 1px rgba(255,255,255,.25); }\n.custom-select-trigger::after { content:'\u2304'; color:var(--muted); font-size:13px; line-height:1; display:inline-flex; align-items:center; justify-content:center; transform:translateY(-1px); flex:0 0 auto; }\n.custom-select.open .custom-select-trigger { border-color:rgba(0,122,255,.55); box-shadow:0 0 0 4px rgba(0,122,255,.12); }\n.custom-select-menu { display:none; position:absolute; top:calc(100% + 6px); left:0; right:0; z-index:10002; max-height:260px; overflow:auto; padding:6px; border:1px solid var(--line); border-radius:16px; background:rgba(255,255,255,.98); box-shadow:0 18px 40px rgba(0,0,0,.16); backdrop-filter:blur(16px); }\n.custom-select.open .custom-select-menu { display:block; }\n.custom-select-option { display:block; width:100%; border:0; box-shadow:none; background:transparent; text-align:left; color:var(--text); border-radius:10px; padding:9px 10px; white-space:normal; }\n.custom-select-option:hover, .custom-select-option.active { background:rgba(0,122,255,.10); color:var(--accent); transform:none; box-shadow:none; }\n.config-row { display:grid; grid-template-columns:90px minmax(0, 1fr); gap:10px; align-items:start; padding:8px 0; border-top:1px solid var(--line); }\n.config-row:first-child { border-top:0; }\n.config-row b, .config-row span { min-width:0; overflow-wrap:anywhere; line-height:1.5; }\n.file-group { border:1px solid var(--line); border-radius:18px; background:rgba(255,255,255,.58); margin-top:10px; overflow:hidden; }\n.file-group summary { cursor:pointer; padding:12px 14px; font-weight:700; display:flex; justify-content:space-between; align-items:center; }\n.file-group summary span { color:var(--muted); font-size:12px; }\n.file-item { display:grid; grid-template-columns:minmax(0, 1fr) auto; gap:12px; padding:12px 14px; border-top:1px solid var(--line); align-items:center; }\n.file-main { min-width:0; }\n.file-actions { display:flex; gap:6px; flex-wrap:wrap; justify-content:flex-end; }\n.file-actions button { padding:7px 9px; font-size:12px; }\n.grid { display:grid; gap:20px; grid-template-columns: repeat(2, minmax(0, 1fr)); }\n.panel-gap { margin-top:20px; }\n.stat-grid { display:grid; grid-template-columns: repeat(3, 1fr); gap:10px; margin:10px 0 12px; }\n.metric { background:linear-gradient(180deg, #fff, #f7f7fa); border:1px solid var(--line); border-radius:18px; padding:14px; }\n.metric b { display:block; font-size:24px; margin-top:4px; letter-spacing:-.03em; }\n.metric span { color:var(--muted); font-size:12px; }\n.stat { display:flex; justify-content:space-between; align-items:flex-start; color:var(--muted); margin:8px 0; gap:12px; }\n.stat b { color:var(--text); }\n.card { border-top:1px solid var(--line); padding:14px 0; }\n.card:first-child { border-top:0; padding-top:0; }\n.path { color:var(--accent); font-size:13px; overflow-wrap:anywhere; word-break:normal; font-weight:600; line-height:1.45; }\n.meta { color:var(--muted); font-size:12px; overflow-wrap:anywhere; word-break:normal; line-height:1.55; min-width:0; }\n.badge { display:inline-flex; align-items:center; border:1px solid var(--line); border-radius:999px; padding:3px 9px; font-size:12px; color:var(--muted); margin:3px 5px 3px 0; background:rgba(255,255,255,.7); }\n.badge.ok { color:#1d7f3a; border-color:rgba(52,199,89,.28); background:rgba(52,199,89,.10); }\n.badge.warn { color:#9a5a00; border-color:rgba(255,149,0,.30); background:rgba(255,149,0,.10); }\n#capabilities { max-height: 460px; overflow:auto; padding-right:4px; }\n#results .card { background:var(--panel-solid); border-radius:20px; border:1px solid var(--line); margin-top:12px; padding:16px; box-shadow:var(--shadow-soft); }\n.status { color:var(--muted); min-height:20px; font-size:12px; margin-top:8px; }\n.status.ok { color:var(--ok); }\n.status.error { color:var(--danger); }\n.toolbar { display:flex; gap:10px; align-items:center; justify-content:flex-end; }\npre { white-space:pre-wrap; word-break:break-word; color:#3a3a3c; margin:10px 0 0; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:12px; line-height:1.55; }\n.subsection { margin-top:18px; padding-top:16px;}\n.modal-backdrop { position:fixed; inset:0; z-index:20000; display:grid; place-items:center; background:rgba(15,23,42,.35); backdrop-filter:blur(10px); }\n.modal-backdrop[hidden] { display:none; }\n.modal-card { width:min(520px, calc(100vw - 32px)); background:rgba(255,255,255,.98); border:1px solid var(--line); border-radius:24px; box-shadow:0 28px 70px rgba(0,0,0,.22); padding:22px; }\n.modal-card h3 { margin:0 0 8px; }\n.modal-card p { white-space:pre-wrap; color:var(--muted); margin:0 0 14px; }\n.modal-input { width:100%; box-sizing:border-box; margin:0 0 14px; border:1px solid var(--line); border-radius:14px; padding:11px 12px; font:inherit; }\n.modal-actions { display:flex; justify-content:flex-end; gap:10px; }\nbutton.danger { background:linear-gradient(135deg,#ff3b30,#ff6b5f); color:white; border-color:rgba(255,59,48,.25); }\n@media (max-width: 1080px) { .grid { grid-template-columns:1fr; } }\n@media (max-width: 680px) { main { padding:16px; } header { padding:16px; align-items:flex-start; flex-direction:column; } .toolbar { width:100%; justify-content:flex-start; flex-wrap:wrap; } .row { flex-direction:column; align-items:stretch; } .search-bar, .filter-bar { grid-template-columns:1fr; } select { width:100%; } .field-row, .field-row:has(input + input + button), .file-item, .config-row { grid-template-columns:1fr; } .file-actions { justify-content:flex-start; } .stat-grid { grid-template-columns:1fr; } .upload-card { align-items:flex-start; } }\n";