local-knowledge-graph 1.6.1 → 1.7.0
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/HELP.md +1 -0
- package/lib/agent.js +4 -4
- package/lib/ask.js +59 -1
- package/lib/db.js +204 -9
- package/lib/embeddings.js +5 -1
- package/lib/ingest.js +355 -0
- package/lib/rdf.js +5 -0
- package/lib/similar.js +81 -0
- package/lib/validator.js +26 -2
- package/mcp/server.js +18 -0
- package/package.json +3 -2
- package/public/app.js +352 -28
- package/public/index.html +30 -1
- package/public/style.css +20 -0
- package/server.js +95 -1
package/server.js
CHANGED
|
@@ -43,7 +43,27 @@ function agentSessionInfo() {
|
|
|
43
43
|
|
|
44
44
|
// ---------- 实体 ----------
|
|
45
45
|
api.get('/entities', (req, res) => res.json(db.listEntities()));
|
|
46
|
-
api.post('/entities', (req, res) =>
|
|
46
|
+
api.post('/entities', (req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
const body = req.body || {};
|
|
49
|
+
const name = body.name;
|
|
50
|
+
// 同名消歧:主名或别名占用时,按 merge_into / force 分流
|
|
51
|
+
if (typeof name === 'string' && name.trim()) {
|
|
52
|
+
const conflicts = db.findNameConflicts(name);
|
|
53
|
+
if (conflicts.length) {
|
|
54
|
+
const mergeInto = Number(body.merge_into);
|
|
55
|
+
if (body.merge_into !== undefined && Number.isInteger(mergeInto) && mergeInto > 0) {
|
|
56
|
+
const alias = db.addAlias(mergeInto, name.trim(), '手工');
|
|
57
|
+
return res.json({ merged: true, alias, entity: db.getEntity(mergeInto) });
|
|
58
|
+
}
|
|
59
|
+
if (body.force !== true && body.force !== 1 && body.force !== '1') {
|
|
60
|
+
return res.status(409).json({ error: `名称"${name.trim()}"已被占用,可并入现有实体、改用限定名或强制创建`, conflicts });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
res.json(db.addEntity(body, '手工'));
|
|
65
|
+
} catch (e) { res.status(e.status || 500).json({ error: e.message, conflicts: e.conflicts }); }
|
|
66
|
+
});
|
|
47
67
|
api.put('/entities/:id', (req, res) => res.json(db.updateEntity(Number(req.params.id), req.body, '手工')));
|
|
48
68
|
api.delete('/entities/:id', (req, res) => {
|
|
49
69
|
const r = db.deleteEntity(Number(req.params.id), '手工');
|
|
@@ -81,6 +101,80 @@ api.get('/graph/path', (req, res) => {
|
|
|
81
101
|
});
|
|
82
102
|
|
|
83
103
|
// ---------- 撤销最近操作(快照逆向写入) ----------
|
|
104
|
+
api.get('/aliases', (req, res) => res.json(db.aliasMap()));
|
|
105
|
+
|
|
106
|
+
api.get('/aliases/records', (req, res) => res.json(db.aliasRecords()));
|
|
107
|
+
|
|
108
|
+
api.post('/aliases', (req, res) => {
|
|
109
|
+
try {
|
|
110
|
+
const { entity_id, alias } = req.body || {};
|
|
111
|
+
res.json(db.addAlias(Number(entity_id), alias, '手工'));
|
|
112
|
+
} catch (e) { res.status(e.status || 500).json({ error: e.message, conflicts: e.conflicts }); }
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
api.delete('/aliases/:id', (req, res) => {
|
|
116
|
+
try { res.json(db.removeAlias(Number(req.params.id), '手工')); }
|
|
117
|
+
catch (e) { res.status(e.status || 500).json({ error: e.message }); }
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
api.get('/graph/paths', (req, res) => {
|
|
121
|
+
try {
|
|
122
|
+
const fromId = db.resolveKey(req.query.from);
|
|
123
|
+
const toId = db.resolveKey(req.query.to);
|
|
124
|
+
let maxHops = 4;
|
|
125
|
+
if (req.query.max !== undefined && String(req.query.max).trim() !== '') {
|
|
126
|
+
maxHops = Number(req.query.max);
|
|
127
|
+
if (!Number.isInteger(maxHops)) return res.status(400).json({ error: 'max必须为整数' });
|
|
128
|
+
}
|
|
129
|
+
res.json(db.findPaths(fromId, toId, { maxHops }));
|
|
130
|
+
} catch (e) { res.status(e.status || 500).json({ error: e.message, candidates: e.candidates }); }
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// ---------- 相似实体(语义Top-K,结构降级) ----------
|
|
134
|
+
api.get('/similar/:id', async (req, res) => {
|
|
135
|
+
try {
|
|
136
|
+
const k = req.query.k !== undefined ? Number(req.query.k) : 8;
|
|
137
|
+
res.json(await require('./lib/similar').similarEntities(Number(req.params.id), Number.isInteger(k) ? k : 8));
|
|
138
|
+
} catch (e) { res.status(e.status || 500).json({ error: e.message }); }
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ---------- 文档批量入图 ----------
|
|
142
|
+
const ingest = require('./lib/ingest');
|
|
143
|
+
ingest.loadPersisted();
|
|
144
|
+
|
|
145
|
+
api.post('/ingest', async (req, res) => {
|
|
146
|
+
if (agentBusy) return res.status(429).json({ error: '已有AI任务执行中(问答、导入或智能检索),请稍后再试' });
|
|
147
|
+
const { filename, content_b64, auto_commit } = req.body || {};
|
|
148
|
+
if (!filename || !content_b64) return res.status(400).json({ error: 'filename 与 content_b64 必填' });
|
|
149
|
+
let buf;
|
|
150
|
+
try { buf = Buffer.from(content_b64, 'base64'); } catch (_) { return res.status(400).json({ error: 'base64解码失败' }); }
|
|
151
|
+
agentBusy = true;
|
|
152
|
+
try {
|
|
153
|
+
res.json(await ingest.createTask(String(filename), buf, {
|
|
154
|
+
autoCommit: auto_commit === true || auto_commit === 1 || auto_commit === '1',
|
|
155
|
+
onSettled: () => { agentBusy = false; },
|
|
156
|
+
}));
|
|
157
|
+
} catch (e) {
|
|
158
|
+
agentBusy = false;
|
|
159
|
+
res.status(e.status || 500).json({ error: e.message });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
api.get('/ingest/tasks', (req, res) => res.json(ingest.listTasks()));
|
|
164
|
+
api.get('/ingest/:id', (req, res) => {
|
|
165
|
+
const t = ingest.getTask(req.params.id);
|
|
166
|
+
if (!t) return res.status(404).json({ error: '任务不存在' });
|
|
167
|
+
res.json(t);
|
|
168
|
+
});
|
|
169
|
+
api.post('/ingest/:id/commit', (req, res) => {
|
|
170
|
+
try { res.json(ingest.commitTask(req.params.id, (req.body || {}).selected || null, '手工')); }
|
|
171
|
+
catch (e) { res.status(e.status || 500).json({ error: e.message }); }
|
|
172
|
+
});
|
|
173
|
+
api.delete('/ingest/:id', (req, res) => {
|
|
174
|
+
try { res.json(ingest.deleteTask(req.params.id)); }
|
|
175
|
+
catch (e) { res.status(e.status || 500).json({ error: e.message }); }
|
|
176
|
+
});
|
|
177
|
+
|
|
84
178
|
api.post('/undo', (req, res) => {
|
|
85
179
|
try { res.json({ ok: true, ...db.undoLast('手工'), counts: db.counts(), version: db.getVersion() }); }
|
|
86
180
|
catch (e) { res.status(e.status || 500).json({ error: e.message }); }
|