@ppdocs/mcp 3.2.34 → 3.2.36
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/dist/cli.js +4 -48
- package/dist/storage/httpClient.d.ts +2 -27
- package/dist/storage/httpClient.js +3 -132
- package/dist/storage/types.d.ts +0 -28
- package/dist/storage/types.js +1 -1
- package/dist/tools/flowchart.d.ts +1 -5
- package/dist/tools/flowchart.js +406 -454
- package/dist/tools/index.d.ts +3 -3
- package/dist/tools/index.js +10 -13
- package/dist/tools/kg_status.d.ts +1 -1
- package/dist/tools/kg_status.js +6 -20
- package/dist/tools/projects.d.ts +2 -3
- package/dist/tools/projects.js +2 -3
- package/dist/tools/shared.d.ts +0 -12
- package/dist/tools/shared.js +0 -59
- package/package.json +1 -1
- package/templates/AGENT.md +63 -38
- package/templates/cursorrules.md +63 -153
- package/templates/kiro-rules/ppdocs.md +63 -142
- package/dist/agent.d.ts +0 -6
- package/dist/agent.js +0 -130
- package/dist/tools/docs.d.ts +0 -8
- package/dist/tools/docs.js +0 -285
- package/dist/tools/helpers.d.ts +0 -37
- package/dist/tools/helpers.js +0 -94
- package/dist/vector/index.d.ts +0 -56
- package/dist/vector/index.js +0 -228
- package/dist/vector/manager.d.ts +0 -48
- package/dist/vector/manager.js +0 -250
- package/dist/web/server.d.ts +0 -43
- package/dist/web/server.js +0 -808
- package/dist/web/ui.d.ts +0 -5
- package/dist/web/ui.js +0 -642
package/dist/tools/helpers.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP 工具辅助函数
|
|
3
|
-
*/
|
|
4
|
-
// ==================== 通用包装 ====================
|
|
5
|
-
/**
|
|
6
|
-
* 包装返回结果为 MCP 格式
|
|
7
|
-
*/
|
|
8
|
-
export function wrap(text) {
|
|
9
|
-
return { content: [{ type: 'text', text }] };
|
|
10
|
-
}
|
|
11
|
-
// ==================== 文档格式化 ====================
|
|
12
|
-
/**
|
|
13
|
-
* 格式化文档为 Markdown 输出
|
|
14
|
-
*/
|
|
15
|
-
export function formatDocMarkdown(doc, path) {
|
|
16
|
-
const lines = [];
|
|
17
|
-
const name = path.split('/').pop() || path;
|
|
18
|
-
// 标题和简介
|
|
19
|
-
lines.push(`## ${name}\n`);
|
|
20
|
-
if (doc.summary) {
|
|
21
|
-
lines.push(`> ${doc.summary}\n`);
|
|
22
|
-
}
|
|
23
|
-
// 内容
|
|
24
|
-
if (doc.content) {
|
|
25
|
-
lines.push('**内容**');
|
|
26
|
-
lines.push(doc.content);
|
|
27
|
-
lines.push('');
|
|
28
|
-
}
|
|
29
|
-
// 更新历史
|
|
30
|
-
if (doc.versions && doc.versions.length > 0) {
|
|
31
|
-
lines.push('**更新历史**');
|
|
32
|
-
lines.push('| 版本 | 日期 | 变更 |');
|
|
33
|
-
lines.push('|:---|:---|:---|');
|
|
34
|
-
doc.versions.forEach((v) => lines.push(`| ${v.version} | ${v.date} | ${v.changes} |`));
|
|
35
|
-
lines.push('');
|
|
36
|
-
}
|
|
37
|
-
// 修复历史
|
|
38
|
-
if (doc.bugfixes && doc.bugfixes.length > 0) {
|
|
39
|
-
lines.push('**修复历史**');
|
|
40
|
-
lines.push('| 日期 | 问题 | 方案 |');
|
|
41
|
-
lines.push('|:---|:---|:---|');
|
|
42
|
-
doc.bugfixes.forEach((b) => lines.push(`| ${b.date} | ${b.issue} | ${b.solution} |`));
|
|
43
|
-
lines.push('');
|
|
44
|
-
}
|
|
45
|
-
return lines;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* 格式化树为文本
|
|
49
|
-
*/
|
|
50
|
-
export function formatTreeText(tree) {
|
|
51
|
-
function format(nodes, prefix = '') {
|
|
52
|
-
const lines = [];
|
|
53
|
-
nodes.forEach((node, index) => {
|
|
54
|
-
const isLast = index === nodes.length - 1;
|
|
55
|
-
const connector = isLast ? '└── ' : '├── ';
|
|
56
|
-
const childPrefix = isLast ? ' ' : '│ ';
|
|
57
|
-
const icon = node.isDir ? '📁' : '📄';
|
|
58
|
-
const summary = node.summary ? ` # ${node.summary}` : '';
|
|
59
|
-
lines.push(`${prefix}${connector}${icon} ${node.name}${summary}`);
|
|
60
|
-
if (node.children && node.children.length > 0) {
|
|
61
|
-
lines.push(...format(node.children, prefix + childPrefix));
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
return lines;
|
|
65
|
-
}
|
|
66
|
-
return format(tree).join('\n');
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* 统计树中的文档数
|
|
70
|
-
*/
|
|
71
|
-
export function countTreeDocs(tree) {
|
|
72
|
-
let count = 0;
|
|
73
|
-
function traverse(nodes) {
|
|
74
|
-
for (const node of nodes) {
|
|
75
|
-
if (!node.isDir)
|
|
76
|
-
count++;
|
|
77
|
-
if (node.children)
|
|
78
|
-
traverse(node.children);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
traverse(tree);
|
|
82
|
-
return count;
|
|
83
|
-
}
|
|
84
|
-
// ==================== 文件大小格式化 ====================
|
|
85
|
-
/**
|
|
86
|
-
* 格式化文件大小
|
|
87
|
-
*/
|
|
88
|
-
export function formatFileSize(bytes) {
|
|
89
|
-
if (bytes < 1024)
|
|
90
|
-
return `${bytes} B`;
|
|
91
|
-
if (bytes < 1024 * 1024)
|
|
92
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
93
|
-
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
94
|
-
}
|
package/dist/vector/index.d.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 向量检索服务 - 基于 transformers.js 的语义搜索
|
|
3
|
-
* @version 0.2
|
|
4
|
-
*/
|
|
5
|
-
interface VectorConfig {
|
|
6
|
-
model: string;
|
|
7
|
-
quantized: boolean;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* 获取向量配置 (从 ~/.ppdocs/config/vector.json 读取)
|
|
11
|
-
*/
|
|
12
|
-
export declare function getVectorConfig(): VectorConfig;
|
|
13
|
-
interface VectorSearchResult {
|
|
14
|
-
id: string;
|
|
15
|
-
title: string;
|
|
16
|
-
similarity: number;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* 获取索引文件路径
|
|
20
|
-
*/
|
|
21
|
-
export declare function getIndexPath(projectId: string): string;
|
|
22
|
-
/**
|
|
23
|
-
* 添加或更新节点向量
|
|
24
|
-
*/
|
|
25
|
-
export declare function upsertNode(projectId: string, nodeId: string, title: string, description: string, categories?: string[]): Promise<void>;
|
|
26
|
-
/**
|
|
27
|
-
* 删除节点向量
|
|
28
|
-
*/
|
|
29
|
-
export declare function removeNode(projectId: string, nodeId: string): Promise<void>;
|
|
30
|
-
/**
|
|
31
|
-
* 向量语义搜索
|
|
32
|
-
*/
|
|
33
|
-
export declare function semanticSearch(projectId: string, query: string, limit?: number): Promise<VectorSearchResult[]>;
|
|
34
|
-
/**
|
|
35
|
-
* 批量构建索引 (用于初始化或重建)
|
|
36
|
-
*/
|
|
37
|
-
export declare function buildIndex(projectId: string, nodes: Array<{
|
|
38
|
-
id: string;
|
|
39
|
-
title: string;
|
|
40
|
-
description: string;
|
|
41
|
-
categories?: string[];
|
|
42
|
-
}>): Promise<number>;
|
|
43
|
-
/**
|
|
44
|
-
* 检查索引状态
|
|
45
|
-
*/
|
|
46
|
-
export declare function getIndexStats(projectId: string): Promise<{
|
|
47
|
-
count: number;
|
|
48
|
-
model: string;
|
|
49
|
-
dimension: number;
|
|
50
|
-
loaded: boolean;
|
|
51
|
-
}>;
|
|
52
|
-
/**
|
|
53
|
-
* 预加载模型 (可选, 用于启动时预热)
|
|
54
|
-
*/
|
|
55
|
-
export declare function preloadModel(): Promise<void>;
|
|
56
|
-
export {};
|
package/dist/vector/index.js
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 向量检索服务 - 基于 transformers.js 的语义搜索
|
|
3
|
-
* @version 0.2
|
|
4
|
-
*/
|
|
5
|
-
import { pipeline } from '@xenova/transformers';
|
|
6
|
-
import * as fs from 'fs';
|
|
7
|
-
import * as path from 'path';
|
|
8
|
-
// 默认模型配置
|
|
9
|
-
const DEFAULT_MODEL = 'Xenova/multilingual-e5-small';
|
|
10
|
-
const VECTOR_DIM = 384;
|
|
11
|
-
// 当前加载的模型名
|
|
12
|
-
let currentModel = null;
|
|
13
|
-
/**
|
|
14
|
-
* 获取向量配置 (从 ~/.ppdocs/config/vector.json 读取)
|
|
15
|
-
*/
|
|
16
|
-
export function getVectorConfig() {
|
|
17
|
-
const baseDir = process.env.PPDOCS_DATA_DIR || path.join(process.env.HOME || process.env.USERPROFILE || '', '.ppdocs');
|
|
18
|
-
const configPath = path.join(baseDir, 'config', 'vector.json');
|
|
19
|
-
try {
|
|
20
|
-
if (fs.existsSync(configPath)) {
|
|
21
|
-
const content = fs.readFileSync(configPath, 'utf-8');
|
|
22
|
-
return JSON.parse(content);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
catch (e) {
|
|
26
|
-
console.warn('[Vector] Failed to load config:', e);
|
|
27
|
-
}
|
|
28
|
-
return { model: DEFAULT_MODEL, quantized: true };
|
|
29
|
-
}
|
|
30
|
-
// 单例模式
|
|
31
|
-
let extractor = null;
|
|
32
|
-
let isLoading = false;
|
|
33
|
-
let loadPromise = null;
|
|
34
|
-
// 内存索引 (projectId -> entries)
|
|
35
|
-
const vectorIndex = new Map();
|
|
36
|
-
/**
|
|
37
|
-
* 加载嵌入模型 (懒加载, 首次调用时下载)
|
|
38
|
-
*/
|
|
39
|
-
async function getExtractor() {
|
|
40
|
-
const config = getVectorConfig();
|
|
41
|
-
// 如果模型变更,重新加载
|
|
42
|
-
if (extractor && currentModel === config.model) {
|
|
43
|
-
return extractor;
|
|
44
|
-
}
|
|
45
|
-
if (isLoading && loadPromise && currentModel === config.model) {
|
|
46
|
-
return loadPromise;
|
|
47
|
-
}
|
|
48
|
-
isLoading = true;
|
|
49
|
-
currentModel = config.model;
|
|
50
|
-
console.log(`[Vector] Loading embedding model: ${config.model}...`);
|
|
51
|
-
loadPromise = pipeline('feature-extraction', config.model, {
|
|
52
|
-
quantized: config.quantized,
|
|
53
|
-
});
|
|
54
|
-
extractor = await loadPromise;
|
|
55
|
-
isLoading = false;
|
|
56
|
-
console.log('[Vector] Model loaded successfully');
|
|
57
|
-
return extractor;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* 文本转向量
|
|
61
|
-
*/
|
|
62
|
-
async function embed(text) {
|
|
63
|
-
const ext = await getExtractor();
|
|
64
|
-
const output = await ext(text, { pooling: 'mean', normalize: true });
|
|
65
|
-
return Array.from(output.data);
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* 计算余弦相似度
|
|
69
|
-
*/
|
|
70
|
-
function cosineSimilarity(a, b) {
|
|
71
|
-
if (a.length !== b.length)
|
|
72
|
-
return 0;
|
|
73
|
-
let dotProduct = 0;
|
|
74
|
-
let normA = 0;
|
|
75
|
-
let normB = 0;
|
|
76
|
-
for (let i = 0; i < a.length; i++) {
|
|
77
|
-
dotProduct += a[i] * b[i];
|
|
78
|
-
normA += a[i] * a[i];
|
|
79
|
-
normB += b[i] * b[i];
|
|
80
|
-
}
|
|
81
|
-
const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
|
|
82
|
-
return magnitude === 0 ? 0 : dotProduct / magnitude;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* 获取索引文件路径
|
|
86
|
-
*/
|
|
87
|
-
export function getIndexPath(projectId) {
|
|
88
|
-
const baseDir = process.env.PPDOCS_DATA_DIR || path.join(process.env.HOME || process.env.USERPROFILE || '', '.ppdocs');
|
|
89
|
-
return path.join(baseDir, 'projects', projectId, 'vector-index.json');
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* 加载持久化索引
|
|
93
|
-
*/
|
|
94
|
-
async function loadIndex(projectId) {
|
|
95
|
-
// 先检查内存
|
|
96
|
-
if (vectorIndex.has(projectId)) {
|
|
97
|
-
return vectorIndex.get(projectId);
|
|
98
|
-
}
|
|
99
|
-
// 尝试从文件加载
|
|
100
|
-
const indexPath = getIndexPath(projectId);
|
|
101
|
-
try {
|
|
102
|
-
if (fs.existsSync(indexPath)) {
|
|
103
|
-
const data = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
|
|
104
|
-
vectorIndex.set(projectId, data.entries || []);
|
|
105
|
-
console.log(`[Vector] Loaded ${data.entries?.length || 0} entries from disk`);
|
|
106
|
-
return data.entries || [];
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
catch (e) {
|
|
110
|
-
console.warn('[Vector] Failed to load index:', e);
|
|
111
|
-
}
|
|
112
|
-
vectorIndex.set(projectId, []);
|
|
113
|
-
return [];
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* 保存索引到磁盘
|
|
117
|
-
*/
|
|
118
|
-
async function saveIndex(projectId) {
|
|
119
|
-
const entries = vectorIndex.get(projectId) || [];
|
|
120
|
-
const indexPath = getIndexPath(projectId);
|
|
121
|
-
try {
|
|
122
|
-
const dir = path.dirname(indexPath);
|
|
123
|
-
if (!fs.existsSync(dir)) {
|
|
124
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
125
|
-
}
|
|
126
|
-
const config = getVectorConfig();
|
|
127
|
-
fs.writeFileSync(indexPath, JSON.stringify({
|
|
128
|
-
version: 1,
|
|
129
|
-
model: config.model,
|
|
130
|
-
dimension: VECTOR_DIM,
|
|
131
|
-
updatedAt: new Date().toISOString(),
|
|
132
|
-
entries
|
|
133
|
-
}, null, 2));
|
|
134
|
-
console.log(`[Vector] Saved ${entries.length} entries to disk`);
|
|
135
|
-
}
|
|
136
|
-
catch (e) {
|
|
137
|
-
console.warn('[Vector] Failed to save index:', e);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* 添加或更新节点向量
|
|
142
|
-
*/
|
|
143
|
-
export async function upsertNode(projectId, nodeId, title, description, categories = []) {
|
|
144
|
-
const entries = await loadIndex(projectId);
|
|
145
|
-
// 合并可搜索文本
|
|
146
|
-
const searchableText = `${title} ${description} ${categories.join(' ')}`;
|
|
147
|
-
const embedding = await embed(searchableText);
|
|
148
|
-
// 查找现有条目
|
|
149
|
-
const existingIndex = entries.findIndex(e => e.id === nodeId);
|
|
150
|
-
const entry = { id: nodeId, title, embedding };
|
|
151
|
-
if (existingIndex >= 0) {
|
|
152
|
-
entries[existingIndex] = entry;
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
entries.push(entry);
|
|
156
|
-
}
|
|
157
|
-
vectorIndex.set(projectId, entries);
|
|
158
|
-
await saveIndex(projectId);
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* 删除节点向量
|
|
162
|
-
*/
|
|
163
|
-
export async function removeNode(projectId, nodeId) {
|
|
164
|
-
const entries = await loadIndex(projectId);
|
|
165
|
-
const filtered = entries.filter(e => e.id !== nodeId);
|
|
166
|
-
vectorIndex.set(projectId, filtered);
|
|
167
|
-
await saveIndex(projectId);
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* 向量语义搜索
|
|
171
|
-
*/
|
|
172
|
-
export async function semanticSearch(projectId, query, limit = 10) {
|
|
173
|
-
const entries = await loadIndex(projectId);
|
|
174
|
-
if (entries.length === 0) {
|
|
175
|
-
console.log('[Vector] Index is empty, skipping semantic search');
|
|
176
|
-
return [];
|
|
177
|
-
}
|
|
178
|
-
// 查询向量化
|
|
179
|
-
const queryEmbedding = await embed(query);
|
|
180
|
-
// 计算相似度
|
|
181
|
-
const results = entries.map(entry => ({
|
|
182
|
-
id: entry.id,
|
|
183
|
-
title: entry.title,
|
|
184
|
-
similarity: cosineSimilarity(queryEmbedding, entry.embedding)
|
|
185
|
-
}));
|
|
186
|
-
// 排序并返回 top-k
|
|
187
|
-
results.sort((a, b) => b.similarity - a.similarity);
|
|
188
|
-
return results.slice(0, limit);
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* 批量构建索引 (用于初始化或重建)
|
|
192
|
-
*/
|
|
193
|
-
export async function buildIndex(projectId, nodes) {
|
|
194
|
-
console.log(`[Vector] Building index for ${nodes.length} nodes...`);
|
|
195
|
-
const entries = [];
|
|
196
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
197
|
-
const node = nodes[i];
|
|
198
|
-
const searchableText = `${node.title} ${node.description} ${(node.categories || []).join(' ')}`;
|
|
199
|
-
const embedding = await embed(searchableText);
|
|
200
|
-
entries.push({ id: node.id, title: node.title, embedding });
|
|
201
|
-
if ((i + 1) % 10 === 0) {
|
|
202
|
-
console.log(`[Vector] Processed ${i + 1}/${nodes.length}`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
vectorIndex.set(projectId, entries);
|
|
206
|
-
await saveIndex(projectId);
|
|
207
|
-
console.log(`[Vector] Index built with ${entries.length} entries`);
|
|
208
|
-
return entries.length;
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* 检查索引状态
|
|
212
|
-
*/
|
|
213
|
-
export async function getIndexStats(projectId) {
|
|
214
|
-
const entries = await loadIndex(projectId);
|
|
215
|
-
const config = getVectorConfig();
|
|
216
|
-
return {
|
|
217
|
-
count: entries.length,
|
|
218
|
-
model: config.model,
|
|
219
|
-
dimension: VECTOR_DIM,
|
|
220
|
-
loaded: extractor !== null
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* 预加载模型 (可选, 用于启动时预热)
|
|
225
|
-
*/
|
|
226
|
-
export async function preloadModel() {
|
|
227
|
-
await getExtractor();
|
|
228
|
-
}
|
package/dist/vector/manager.d.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 向量索引管理器 (已弃用)
|
|
3
|
-
*
|
|
4
|
-
* ⚠️ 自 v2.7.0 起,向量索引由 Tauri 后端自动维护
|
|
5
|
-
* 此模块保留仅用于向后兼容,不再被主动调用
|
|
6
|
-
*
|
|
7
|
-
* @deprecated 请使用 Tauri 后端的 vector 模块
|
|
8
|
-
* @version 0.2
|
|
9
|
-
*/
|
|
10
|
-
interface RebuildReason {
|
|
11
|
-
needed: boolean;
|
|
12
|
-
reason: 'missing' | 'model_mismatch' | 'empty' | 'count_mismatch' | 'none';
|
|
13
|
-
missingCount?: number;
|
|
14
|
-
}
|
|
15
|
-
interface RebuildResult {
|
|
16
|
-
projectId: string;
|
|
17
|
-
count: number;
|
|
18
|
-
reason: string;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* 检测索引是否需要重建
|
|
22
|
-
*/
|
|
23
|
-
export declare function needsRebuild(projectId: string): Promise<RebuildReason>;
|
|
24
|
-
/**
|
|
25
|
-
* 强制重建项目索引
|
|
26
|
-
*/
|
|
27
|
-
export declare function rebuildIndex(projectId: string): Promise<number>;
|
|
28
|
-
/**
|
|
29
|
-
* 增量同步缺失节点 (仅补漏,不全量重建)
|
|
30
|
-
*/
|
|
31
|
-
export declare function syncMissingNodes(projectId: string): Promise<number>;
|
|
32
|
-
/**
|
|
33
|
-
* 确保索引存在 (自动检测 + 按需构建/增量补漏)
|
|
34
|
-
*/
|
|
35
|
-
export declare function ensureIndex(projectId: string): Promise<void>;
|
|
36
|
-
/**
|
|
37
|
-
* 重建所有项目索引
|
|
38
|
-
*/
|
|
39
|
-
export declare function rebuildAllIndexes(): Promise<RebuildResult[]>;
|
|
40
|
-
/**
|
|
41
|
-
* 获取所有项目的索引状态
|
|
42
|
-
*/
|
|
43
|
-
export declare function getAllIndexStats(): Promise<Array<{
|
|
44
|
-
projectId: string;
|
|
45
|
-
status: RebuildReason;
|
|
46
|
-
nodeCount: number;
|
|
47
|
-
}>>;
|
|
48
|
-
export {};
|
package/dist/vector/manager.js
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 向量索引管理器 (已弃用)
|
|
3
|
-
*
|
|
4
|
-
* ⚠️ 自 v2.7.0 起,向量索引由 Tauri 后端自动维护
|
|
5
|
-
* 此模块保留仅用于向后兼容,不再被主动调用
|
|
6
|
-
*
|
|
7
|
-
* @deprecated 请使用 Tauri 后端的 vector 模块
|
|
8
|
-
* @version 0.2
|
|
9
|
-
*/
|
|
10
|
-
import * as fs from 'fs';
|
|
11
|
-
import * as path from 'path';
|
|
12
|
-
import * as vectorCore from './index.js';
|
|
13
|
-
// ==================== 检测函数 ====================
|
|
14
|
-
/**
|
|
15
|
-
* 获取节点目录下的节点ID列表
|
|
16
|
-
*/
|
|
17
|
-
function getNodeIdsFromDisk(projectId) {
|
|
18
|
-
const baseDir = process.env.PPDOCS_DATA_DIR || path.join(process.env.HOME || process.env.USERPROFILE || '', '.ppdocs');
|
|
19
|
-
const nodesDir = path.join(baseDir, 'projects', projectId, 'nodes');
|
|
20
|
-
const nodeIds = new Set();
|
|
21
|
-
if (!fs.existsSync(nodesDir))
|
|
22
|
-
return nodeIds;
|
|
23
|
-
try {
|
|
24
|
-
const files = fs.readdirSync(nodesDir).filter(f => f.endsWith('.json'));
|
|
25
|
-
for (const file of files) {
|
|
26
|
-
try {
|
|
27
|
-
const content = fs.readFileSync(path.join(nodesDir, file), 'utf-8');
|
|
28
|
-
const node = JSON.parse(content);
|
|
29
|
-
// 跳过根节点
|
|
30
|
-
if (node.isOrigin || node.id === 'root')
|
|
31
|
-
continue;
|
|
32
|
-
nodeIds.add(node.id);
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
// 跳过无法解析的文件
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
// 忽略读取错误
|
|
41
|
-
}
|
|
42
|
-
return nodeIds;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* 检测索引是否需要重建
|
|
46
|
-
*/
|
|
47
|
-
export async function needsRebuild(projectId) {
|
|
48
|
-
const indexPath = vectorCore.getIndexPath(projectId);
|
|
49
|
-
const config = vectorCore.getVectorConfig();
|
|
50
|
-
// 1. 索引文件不存在
|
|
51
|
-
if (!fs.existsSync(indexPath)) {
|
|
52
|
-
return { needed: true, reason: 'missing' };
|
|
53
|
-
}
|
|
54
|
-
// 2. 读取索引元数据
|
|
55
|
-
try {
|
|
56
|
-
const data = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
|
|
57
|
-
// 3. 模型不匹配
|
|
58
|
-
if (data.model !== config.model) {
|
|
59
|
-
return { needed: true, reason: 'model_mismatch' };
|
|
60
|
-
}
|
|
61
|
-
// 4. 索引为空
|
|
62
|
-
if (!data.entries || data.entries.length === 0) {
|
|
63
|
-
return { needed: true, reason: 'empty' };
|
|
64
|
-
}
|
|
65
|
-
// 5. 检测节点数量不匹配 (新增)
|
|
66
|
-
const diskNodeIds = getNodeIdsFromDisk(projectId);
|
|
67
|
-
const indexedIds = new Set(data.entries.map(e => e.id));
|
|
68
|
-
const missingIds = [...diskNodeIds].filter(id => !indexedIds.has(id));
|
|
69
|
-
if (missingIds.length > 0) {
|
|
70
|
-
return { needed: true, reason: 'count_mismatch', missingCount: missingIds.length };
|
|
71
|
-
}
|
|
72
|
-
return { needed: false, reason: 'none' };
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
// 读取失败视为需要重建
|
|
76
|
-
return { needed: true, reason: 'missing' };
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
// ==================== 重建函数 ====================
|
|
80
|
-
/**
|
|
81
|
-
* 获取节点数据 (用于重建索引)
|
|
82
|
-
*/
|
|
83
|
-
async function fetchNodesForIndex(projectId) {
|
|
84
|
-
// 从项目目录读取节点文件
|
|
85
|
-
const baseDir = process.env.PPDOCS_DATA_DIR || path.join(process.env.HOME || process.env.USERPROFILE || '', '.ppdocs');
|
|
86
|
-
const nodesDir = path.join(baseDir, 'projects', projectId, 'nodes');
|
|
87
|
-
if (!fs.existsSync(nodesDir)) {
|
|
88
|
-
console.log(`[VectorManager] No nodes directory found for project ${projectId}`);
|
|
89
|
-
return [];
|
|
90
|
-
}
|
|
91
|
-
const nodes = [];
|
|
92
|
-
try {
|
|
93
|
-
const files = fs.readdirSync(nodesDir).filter(f => f.endsWith('.json'));
|
|
94
|
-
for (const file of files) {
|
|
95
|
-
try {
|
|
96
|
-
const content = fs.readFileSync(path.join(nodesDir, file), 'utf-8');
|
|
97
|
-
const node = JSON.parse(content);
|
|
98
|
-
// 跳过根节点
|
|
99
|
-
if (node.isOrigin || node.id === 'root')
|
|
100
|
-
continue;
|
|
101
|
-
nodes.push({
|
|
102
|
-
id: node.id,
|
|
103
|
-
title: node.title || '',
|
|
104
|
-
description: node.description || '',
|
|
105
|
-
categories: node.categories || node.tags || []
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
// 跳过无法解析的文件
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
catch (e) {
|
|
114
|
-
console.warn(`[VectorManager] Failed to read nodes:`, e);
|
|
115
|
-
}
|
|
116
|
-
return nodes;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* 强制重建项目索引
|
|
120
|
-
*/
|
|
121
|
-
export async function rebuildIndex(projectId) {
|
|
122
|
-
console.log(`[VectorManager] Rebuilding index for project: ${projectId}`);
|
|
123
|
-
const nodes = await fetchNodesForIndex(projectId);
|
|
124
|
-
if (nodes.length === 0) {
|
|
125
|
-
console.log(`[VectorManager] No nodes to index`);
|
|
126
|
-
return 0;
|
|
127
|
-
}
|
|
128
|
-
return vectorCore.buildIndex(projectId, nodes);
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* 增量同步缺失节点 (仅补漏,不全量重建)
|
|
132
|
-
*/
|
|
133
|
-
export async function syncMissingNodes(projectId) {
|
|
134
|
-
const indexPath = vectorCore.getIndexPath(projectId);
|
|
135
|
-
// 读取当前索引
|
|
136
|
-
let indexedIds = new Set();
|
|
137
|
-
try {
|
|
138
|
-
if (fs.existsSync(indexPath)) {
|
|
139
|
-
const data = JSON.parse(fs.readFileSync(indexPath, 'utf-8'));
|
|
140
|
-
indexedIds = new Set(data.entries.map(e => e.id));
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
// 索引损坏,需要全量重建
|
|
145
|
-
return rebuildIndex(projectId);
|
|
146
|
-
}
|
|
147
|
-
// 找出缺失的节点
|
|
148
|
-
const allNodes = await fetchNodesForIndex(projectId);
|
|
149
|
-
const missingNodes = allNodes.filter(n => !indexedIds.has(n.id));
|
|
150
|
-
if (missingNodes.length === 0) {
|
|
151
|
-
console.log(`[VectorManager] No missing nodes to sync`);
|
|
152
|
-
return 0;
|
|
153
|
-
}
|
|
154
|
-
console.log(`[VectorManager] Syncing ${missingNodes.length} missing nodes...`);
|
|
155
|
-
// 增量添加缺失节点
|
|
156
|
-
for (let i = 0; i < missingNodes.length; i++) {
|
|
157
|
-
const node = missingNodes[i];
|
|
158
|
-
await vectorCore.upsertNode(projectId, node.id, node.title, node.description, node.categories || []);
|
|
159
|
-
if ((i + 1) % 10 === 0) {
|
|
160
|
-
console.log(`[VectorManager] Synced ${i + 1}/${missingNodes.length}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
console.log(`[VectorManager] Sync complete: ${missingNodes.length} nodes added`);
|
|
164
|
-
return missingNodes.length;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* 确保索引存在 (自动检测 + 按需构建/增量补漏)
|
|
168
|
-
*/
|
|
169
|
-
export async function ensureIndex(projectId) {
|
|
170
|
-
const check = await needsRebuild(projectId);
|
|
171
|
-
if (!check.needed)
|
|
172
|
-
return;
|
|
173
|
-
// count_mismatch 使用增量补漏,其他情况全量重建
|
|
174
|
-
if (check.reason === 'count_mismatch') {
|
|
175
|
-
console.log(`[VectorManager] Auto-syncing ${check.missingCount} missing nodes`);
|
|
176
|
-
await syncMissingNodes(projectId);
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
console.log(`[VectorManager] Auto-rebuilding index, reason: ${check.reason}`);
|
|
180
|
-
await rebuildIndex(projectId);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// ==================== 批量操作 ====================
|
|
184
|
-
/**
|
|
185
|
-
* 获取所有项目 ID
|
|
186
|
-
*/
|
|
187
|
-
function listAllProjects() {
|
|
188
|
-
const baseDir = process.env.PPDOCS_DATA_DIR || path.join(process.env.HOME || process.env.USERPROFILE || '', '.ppdocs');
|
|
189
|
-
const projectsDir = path.join(baseDir, 'projects');
|
|
190
|
-
if (!fs.existsSync(projectsDir)) {
|
|
191
|
-
return [];
|
|
192
|
-
}
|
|
193
|
-
try {
|
|
194
|
-
return fs.readdirSync(projectsDir)
|
|
195
|
-
.filter(name => {
|
|
196
|
-
const stat = fs.statSync(path.join(projectsDir, name));
|
|
197
|
-
return stat.isDirectory() && name.startsWith('project-');
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
catch {
|
|
201
|
-
return [];
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* 重建所有项目索引
|
|
206
|
-
*/
|
|
207
|
-
export async function rebuildAllIndexes() {
|
|
208
|
-
const projects = listAllProjects();
|
|
209
|
-
console.log(`[VectorManager] Rebuilding indexes for ${projects.length} projects...`);
|
|
210
|
-
const results = [];
|
|
211
|
-
for (const projectId of projects) {
|
|
212
|
-
try {
|
|
213
|
-
const check = await needsRebuild(projectId);
|
|
214
|
-
const count = await rebuildIndex(projectId);
|
|
215
|
-
results.push({
|
|
216
|
-
projectId,
|
|
217
|
-
count,
|
|
218
|
-
reason: check.reason
|
|
219
|
-
});
|
|
220
|
-
console.log(`[VectorManager] Project ${projectId}: ${count} nodes indexed`);
|
|
221
|
-
}
|
|
222
|
-
catch (e) {
|
|
223
|
-
console.error(`[VectorManager] Failed to rebuild ${projectId}:`, e);
|
|
224
|
-
results.push({
|
|
225
|
-
projectId,
|
|
226
|
-
count: 0,
|
|
227
|
-
reason: 'error'
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
console.log(`[VectorManager] All indexes rebuilt`);
|
|
232
|
-
return results;
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* 获取所有项目的索引状态
|
|
236
|
-
*/
|
|
237
|
-
export async function getAllIndexStats() {
|
|
238
|
-
const projects = listAllProjects();
|
|
239
|
-
const stats = [];
|
|
240
|
-
for (const projectId of projects) {
|
|
241
|
-
const status = await needsRebuild(projectId);
|
|
242
|
-
const indexStats = await vectorCore.getIndexStats(projectId);
|
|
243
|
-
stats.push({
|
|
244
|
-
projectId,
|
|
245
|
-
status,
|
|
246
|
-
nodeCount: indexStats.count
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
return stats;
|
|
250
|
-
}
|