local-knowledge-graph 1.10.4 → 1.11.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 +24 -1
- package/README.md +10 -4
- package/lib/agent.js +22 -6
- package/lib/db.js +133 -11
- package/lib/ocbin.js +194 -0
- package/lib/ocinstall.js +229 -0
- package/lib/triples_io.js +229 -0
- package/lib/validator.js +35 -8
- package/lib/viewer_template.html +44 -6
- package/package.json +1 -1
- package/public/app.js +454 -19
- package/public/index.html +62 -0
- package/public/style.css +13 -0
- package/server.js +82 -6
package/public/index.html
CHANGED
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
<button id="btn-undo" title="撤销最近一次操作(按快照还原,快捷键Ctrl+Z)">撤销</button>
|
|
21
21
|
<button id="btn-export" title="导出标准RDF(Turtle)格式文件">导出RDF</button>
|
|
22
22
|
<button id="btn-relayout" title="重新进行力导向布局">重新布局</button>
|
|
23
|
+
<button id="btn-multisel" title="多选模式:点击节点加入/移出选区,然后提取子图或导出">多选</button>
|
|
23
24
|
<button id="btn-resetview" title="重置相机视角">重置视角</button>
|
|
25
|
+
<button id="btn-screenshot" title="将当前3D视图截图为PNG图片(2倍分辨率)">截图</button>
|
|
24
26
|
<button id="btn-style" title="自定义实体/关系的颜色、大小与线型">视图设置</button>
|
|
25
27
|
<button id="btn-help" title="帮助:查看版本现状、检查更新与使用指南">帮助</button>
|
|
26
28
|
<button id="btn-inference" title="叠加显示推理得到的隐性关系(虚线,OWL传递/对称/逆规则),点击推理边可查看推导依据">推理</button>
|
|
@@ -33,6 +35,7 @@
|
|
|
33
35
|
<div data-tab="entity" class="active">实体</div>
|
|
34
36
|
<div data-tab="relation">关系</div>
|
|
35
37
|
<div data-tab="search">检索</div>
|
|
38
|
+
<div data-tab="stats">统计</div>
|
|
36
39
|
<div data-tab="mcp">MCP</div>
|
|
37
40
|
<div data-tab="log">日志</div>
|
|
38
41
|
<div data-tab="version">版本</div>
|
|
@@ -88,6 +91,23 @@
|
|
|
88
91
|
</div>
|
|
89
92
|
|
|
90
93
|
<div class="tabbody" id="tab-search">
|
|
94
|
+
<div class="card">
|
|
95
|
+
<h3>属性过滤(按实体属性筛选图上显示)</h3>
|
|
96
|
+
<div class="row">
|
|
97
|
+
<div style="flex:1.4"><label>属性键</label><input id="af-key" placeholder="例如:朝代"></div>
|
|
98
|
+
<div style="width:100px"><label>条件</label>
|
|
99
|
+
<select id="af-op">
|
|
100
|
+
<option value="eq">等于</option>
|
|
101
|
+
<option value="contains">包含</option>
|
|
102
|
+
<option value="gt">大于</option>
|
|
103
|
+
<option value="lt">小于</option>
|
|
104
|
+
<option value="exists">存在该键</option>
|
|
105
|
+
</select>
|
|
106
|
+
</div>
|
|
107
|
+
<div style="flex:1"><label>值</label><input id="af-value" placeholder="大于/小于需填数字"></div>
|
|
108
|
+
</div>
|
|
109
|
+
<div class="row"><button class="primary" id="af-apply">应用到图上</button><button id="af-clear">清除过滤</button><span id="af-status" style="font-size:11px;color:#8fa3c0;align-self:center"></span></div>
|
|
110
|
+
</div>
|
|
91
111
|
<div class="card">
|
|
92
112
|
<h3>智能提问(自然语言关系查找)</h3>
|
|
93
113
|
<label>用一句话描述要找的关系</label>
|
|
@@ -110,6 +130,15 @@
|
|
|
110
130
|
</div>
|
|
111
131
|
<div class="row"><button class="primary" id="s-save">保存配置</button><button id="s-test">测试连接</button><button id="s-build">构建全量向量</button><span id="s-status" style="font-size:11px;color:#8fa3c0;align-self:center"></span></div>
|
|
112
132
|
</div>
|
|
133
|
+
<div class="card" id="oc-install-card" style="display:none">
|
|
134
|
+
<h3>OpenCode 一键安装(AI 对话建图依赖)</h3>
|
|
135
|
+
<div class="row">
|
|
136
|
+
<button class="primary" id="oc-install-btn">下载并安装 OpenCode</button>
|
|
137
|
+
<span id="oc-install-status" style="font-size:11px;color:#8fa3c0;align-self:center">未检测到 opencode,点击按钮自动完成安装</span>
|
|
138
|
+
</div>
|
|
139
|
+
<pre id="oc-install-log" class="mcp-snippet" style="display:none;max-height:160px;overflow:auto"></pre>
|
|
140
|
+
<div class="hint-text">将通过 npm 全局安装 opencode-ai(约几十MB,需 1-3 分钟)。安装后自动配置模型:优先接入本机 Ollama / LM Studio 已有模型,否则使用 OpenCode Zen 免费模型(无需登录)。完成后立即可在右侧 AI 对话使用,无需重启本应用。</div>
|
|
141
|
+
</div>
|
|
113
142
|
<div class="card">
|
|
114
143
|
<h3>混合检索(语义+关键词 RRF 融合)</h3>
|
|
115
144
|
<label>查询</label>
|
|
@@ -143,6 +172,19 @@
|
|
|
143
172
|
</div>
|
|
144
173
|
</div>
|
|
145
174
|
|
|
175
|
+
<div class="tabbody" id="tab-stats">
|
|
176
|
+
<div class="card">
|
|
177
|
+
<h3>图统计(度分布与枢纽节点)</h3>
|
|
178
|
+
<div class="row"><button class="primary" id="st-refresh">刷新统计</button><span id="st-status" style="font-size:11px;color:#8fa3c0;align-self:center"></span></div>
|
|
179
|
+
<div id="st-overview"></div>
|
|
180
|
+
<h3 style="margin-top:14px">度分布</h3>
|
|
181
|
+
<div id="st-dist"></div>
|
|
182
|
+
<h3 style="margin-top:14px">枢纽节点 Top10(点击飞行定位)</h3>
|
|
183
|
+
<div id="st-hubs"></div>
|
|
184
|
+
<h3 style="margin-top:14px">实体大类分布</h3>
|
|
185
|
+
<div id="st-cats"></div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
146
188
|
<div class="tabbody" id="tab-log">
|
|
147
189
|
<div class="card">
|
|
148
190
|
<h3>操作日志(手工与OpenCode共用)</h3>
|
|
@@ -240,6 +282,22 @@
|
|
|
240
282
|
<button id="ego-exit">退出中心</button>
|
|
241
283
|
<div class="ego-hint">清空层数输入框 = 显示全部层级;层级光圈颜色随跳数递进</div>
|
|
242
284
|
</div>
|
|
285
|
+
<div id="ms-bar">
|
|
286
|
+
<span>多选:</span><span class="ego-name" id="ms-count">已选 0 个</span>
|
|
287
|
+
<button id="ms-extract">提取子图</button>
|
|
288
|
+
<button id="ms-export-json">导出JSON</button>
|
|
289
|
+
<button id="ms-export-csv">导出CSV</button>
|
|
290
|
+
<button id="ms-clear">清空</button>
|
|
291
|
+
<button id="ms-exit">退出多选</button>
|
|
292
|
+
<div class="ego-hint">点击图上节点加入/移出选区(金色光圈标记),完成后提取子图或直接导出</div>
|
|
293
|
+
</div>
|
|
294
|
+
<div id="sub-bar">
|
|
295
|
+
<span>子图:</span><span class="ego-name" id="sub-name"></span>
|
|
296
|
+
<button id="sub-export-json">导出JSON</button>
|
|
297
|
+
<button id="sub-export-csv">导出CSV</button>
|
|
298
|
+
<button id="sub-exit">退出子图</button>
|
|
299
|
+
<div class="ego-hint">子图模式:仅显示所选实体及其之间的关联关系,可随时导出</div>
|
|
300
|
+
</div>
|
|
243
301
|
<div id="info-card"></div>
|
|
244
302
|
<div id="style-panel"></div>
|
|
245
303
|
<div id="help-panel">
|
|
@@ -308,8 +366,12 @@
|
|
|
308
366
|
<button id="fm-saveas">另存为图谱</button>
|
|
309
367
|
<button id="fm-savehtml">另存为图谱网页</button>
|
|
310
368
|
<button id="fm-rdf">导出 RDF</button>
|
|
369
|
+
<button id="fm-export-json">导出三元组 JSON</button>
|
|
370
|
+
<button id="fm-export-csv">导出三元组 CSV</button>
|
|
371
|
+
<button id="fm-import-triples">导入三元组(JSON/CSV)</button>
|
|
311
372
|
</div>
|
|
312
373
|
<input type="file" id="db-file-input" accept=".db,.sqlite,.sqlite3" style="display:none">
|
|
374
|
+
<input type="file" id="triples-file-input" accept=".json,.csv" style="display:none">
|
|
313
375
|
<div id="lightbox">
|
|
314
376
|
<button class="lb-close" id="lb-close" title="关闭">x</button>
|
|
315
377
|
<figure>
|
package/public/style.css
CHANGED
|
@@ -133,10 +133,23 @@
|
|
|
133
133
|
#ego-bar label { margin: 0; color: #8fa3c0; }
|
|
134
134
|
#ego-bar input { width: 76px; }
|
|
135
135
|
#ego-bar .ego-hint { color: #5c6f92; font-size: 10.5px; width: 100%; text-align: center; }
|
|
136
|
+
/* 多选条与子图条:布局/配色与中心条一致 */
|
|
137
|
+
#ms-bar, #sub-bar {
|
|
138
|
+
position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
|
|
139
|
+
display: none; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center;
|
|
140
|
+
background: rgba(13,20,36,.92); border: 1px solid #2a3a5c; border-radius: 8px;
|
|
141
|
+
padding: 7px 12px; z-index: 12; max-width: calc(100% - 24px); font-size: 12px;
|
|
142
|
+
}
|
|
143
|
+
#ms-bar.show, #sub-bar.show { display: flex; }
|
|
144
|
+
#ms-bar .ego-name, #sub-bar .ego-name { color: #ffd75f; font-weight: 600; }
|
|
145
|
+
#ms-bar .ego-hint, #sub-bar .ego-hint { color: #5c6f92; font-size: 10.5px; width: 100%; text-align: center; }
|
|
146
|
+
#btn-multisel.active { border-color: #ffd75f; color: #ffd75f; }
|
|
136
147
|
@media (max-width: 768px) {
|
|
137
148
|
#ego-bar { top: 8px; padding: 6px 8px; gap: 6px; }
|
|
138
149
|
#ego-bar .ego-name { max-width: 110px; }
|
|
139
150
|
#ego-bar button { min-height: 30px; padding: 5px 9px; font-size: 11px; }
|
|
151
|
+
#ms-bar, #sub-bar { top: 8px; padding: 6px 8px; gap: 6px; }
|
|
152
|
+
#ms-bar button, #sub-bar button { min-height: 30px; padding: 5px 9px; font-size: 11px; }
|
|
140
153
|
}
|
|
141
154
|
|
|
142
155
|
/* ================= 实体图片 ================= */
|
package/server.js
CHANGED
|
@@ -8,6 +8,8 @@ const db = require('./lib/db');
|
|
|
8
8
|
const git = require('./lib/git');
|
|
9
9
|
const rdf = require('./lib/rdf');
|
|
10
10
|
const agent = require('./lib/agent');
|
|
11
|
+
const ocbin = require('./lib/ocbin');
|
|
12
|
+
const ocinstall = require('./lib/ocinstall');
|
|
11
13
|
const V = require('./lib/validator');
|
|
12
14
|
const inference = require('./lib/inference');
|
|
13
15
|
const recommend = require('./lib/recommend');
|
|
@@ -58,6 +60,7 @@ api.get('/meta', (req, res) => {
|
|
|
58
60
|
counts: db.counts(),
|
|
59
61
|
version: db.getVersion(),
|
|
60
62
|
agent_available: agentAvailable,
|
|
63
|
+
agent_path: agentBin ? agentBin.path : null,
|
|
61
64
|
agent_session: agentSessionInfo(),
|
|
62
65
|
});
|
|
63
66
|
});
|
|
@@ -572,6 +575,60 @@ api.get('/export/html', (req, res) => {
|
|
|
572
575
|
res.send(html);
|
|
573
576
|
});
|
|
574
577
|
|
|
578
|
+
// ---------- 三元组 JSON/CSV 导出(可选 ids 参数用于子图导出) ----------
|
|
579
|
+
function filteredGraph(idsParam) {
|
|
580
|
+
if (!idsParam) return db.getGraph();
|
|
581
|
+
const ids = new Set(String(idsParam).split(',').map((s) => Number(s.trim())).filter((n) => Number.isInteger(n) && n > 0));
|
|
582
|
+
const g = db.getGraph();
|
|
583
|
+
return {
|
|
584
|
+
entities: g.entities.filter((e) => ids.has(e.id)),
|
|
585
|
+
relations: g.relations.filter((r) => ids.has(r.source_id) && ids.has(r.target_id)),
|
|
586
|
+
image_counts: g.image_counts,
|
|
587
|
+
aliases: g.aliases,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
api.get('/export/json', (req, res) => {
|
|
592
|
+
const triples = require('./lib/triples_io');
|
|
593
|
+
const body = triples.exportJson(filteredGraph(req.query.ids));
|
|
594
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
595
|
+
res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(req.query.name || 'kg-backup.json')}`);
|
|
596
|
+
res.send(body);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
api.get('/export/csv', (req, res) => {
|
|
600
|
+
const triples = require('./lib/triples_io');
|
|
601
|
+
const body = '\uFEFF' + triples.exportCsv(filteredGraph(req.query.ids));
|
|
602
|
+
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
603
|
+
res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(req.query.name || 'kg-backup.csv')}`);
|
|
604
|
+
res.send(body);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
// ---------- 三元组 CSV/JSON 批量导入 ----------
|
|
608
|
+
api.post('/import/triples', (req, res) => {
|
|
609
|
+
const { content, format, dry_run } = req.body || {};
|
|
610
|
+
if (!content || typeof content !== 'string') return res.status(400).json({ error: '必须提供 content(CSV/JSON 文本)' });
|
|
611
|
+
if (content.length > 20 * 1024 * 1024) return res.status(400).json({ error: '内容超过20MB上限' });
|
|
612
|
+
const triples = require('./lib/triples_io');
|
|
613
|
+
const parsed = triples.parseImport(content, format || (content.trim().startsWith('{') || content.trim().startsWith('[') ? 'json' : 'csv'));
|
|
614
|
+
if (!parsed.ok) return res.status(400).json({ error: parsed.error });
|
|
615
|
+
if (dry_run) {
|
|
616
|
+
return res.json({ ok: true, dry_run: true, entities: parsed.entities.length, relations: parsed.relations.length, warnings: parsed.warnings.slice(0, 20) });
|
|
617
|
+
}
|
|
618
|
+
try {
|
|
619
|
+
const r = db.applyTriplesImport(parsed, '手工');
|
|
620
|
+
r.entity_ids = undefined;
|
|
621
|
+
bus.emit('graph-changed', { via: 'import' });
|
|
622
|
+
git.savepoint(`三元组导入:实体+${r.entities_added} 关系+${r.relations_added}`, '系统');
|
|
623
|
+
res.json({ ok: true, ...r, warnings: parsed.warnings.slice(0, 20), counts: db.counts(), version: db.getVersion() });
|
|
624
|
+
} catch (e) {
|
|
625
|
+
res.status(e.status || 500).json({ error: e.message });
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
// ---------- 图统计(度分布与枢纽节点) ----------
|
|
630
|
+
api.get('/stats', (req, res) => res.json(db.graphStats()));
|
|
631
|
+
|
|
575
632
|
// ---------- Git保存点与回溯 ----------
|
|
576
633
|
api.get('/git/history', (req, res) => res.json(git.history(Number(req.query.limit) || 100)));
|
|
577
634
|
api.post('/git/savepoint', (req, res) => {
|
|
@@ -814,16 +871,33 @@ app.use((err, req, res, next) => {
|
|
|
814
871
|
|
|
815
872
|
// ---------- 启动流程:异常兜底 + 自动拉起OpenCode ----------
|
|
816
873
|
let agentAvailable = false;
|
|
874
|
+
let agentBin = null;
|
|
817
875
|
|
|
818
876
|
function checkAgent() {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
877
|
+
// 多路径解析:PATH 扫描 + 常见安装位置 + npm 垫片还原(详见 lib/ocbin.js)
|
|
878
|
+
const bin = ocbin.findOpenCodeBin({ refresh: true });
|
|
879
|
+
agentBin = bin;
|
|
880
|
+
const version = bin && ocbin.probeVersion(bin);
|
|
881
|
+
agentAvailable = Boolean(version);
|
|
882
|
+
if (bin && !version) {
|
|
883
|
+
console.warn(`[OpenCode] 已定位到 ${bin.path} 但版本探测失败`);
|
|
824
884
|
}
|
|
825
885
|
}
|
|
826
886
|
|
|
887
|
+
// ---------- OpenCode 一键安装(未检测到时前端可触发) ----------
|
|
888
|
+
app.post('/api/agent/install', (req, res) => {
|
|
889
|
+
const force = !!(req.body && req.body.force);
|
|
890
|
+
const r = ocinstall.startInstall(force);
|
|
891
|
+
if (!r.started) return res.status(409).json({ error: r.reason, install: ocinstall.status() });
|
|
892
|
+
res.json({ ok: true, install: ocinstall.status() });
|
|
893
|
+
});
|
|
894
|
+
app.get('/api/agent/install', (req, res) => {
|
|
895
|
+
res.json({ ...ocinstall.status(), agent_available: agentAvailable });
|
|
896
|
+
});
|
|
897
|
+
ocinstall.onDone = checkAgent;
|
|
898
|
+
|
|
899
|
+
app.use('/api', api);
|
|
900
|
+
|
|
827
901
|
function bootstrap() {
|
|
828
902
|
require('./lib/paths').ensureLegacyMigration();
|
|
829
903
|
git.ensureRepo();
|
|
@@ -851,7 +925,9 @@ function bootstrap() {
|
|
|
851
925
|
}
|
|
852
926
|
seedIfEmpty();
|
|
853
927
|
checkAgent();
|
|
854
|
-
console.log(agentAvailable
|
|
928
|
+
console.log(agentAvailable
|
|
929
|
+
? `[OpenCode] 进程已就绪(${agentBin.path},来源:${agentBin.via}),可在前端输入自然语言指令`
|
|
930
|
+
: '[OpenCode] 未检测到 opencode CLI,自然语言补全不可用(其余功能不受影响)。已安装时可用环境变量 KG_OPENCODE_PATH 指定可执行文件路径');
|
|
855
931
|
}
|
|
856
932
|
|
|
857
933
|
function seedIfEmpty() {
|