archgraph-argo 0.11.0 → 0.12.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/argo/defaults/EA-model-template.qea +0 -0
- package/argo/scripts/argo-mcp-server.js +56 -3
- package/argo/scripts/ea-qea-sync-lib.js +688 -0
- package/argo/scripts/ea-qea-sync.js +109 -0
- package/argo/scripts/systemarchitecture-mcp-server.js +73 -0
- package/install-argo.ps1 +23 -89
- package/package.json +3 -5
- package/scripts/ea-layout-store.js +0 -242
- package/scripts/ea-web-service.js +0 -1349
- package/web/app.js +0 -398
- package/web/index.html +0 -86
- package/web/style.css +0 -251
- package/web/vendor/g6.min.js +0 -68
package/web/app.js
DELETED
|
@@ -1,398 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// ArchGraph 本地知识图谱工具 — 前端逻辑(零构建)。
|
|
4
|
-
// 图形内核:AntV G6 v5(本地 vendor /vendor/g6.min.js,默认 Canvas 渲染),
|
|
5
|
-
// 支持 drag-canvas / zoom-canvas / drag-element / click-select。
|
|
6
|
-
// 布局侧车(M1-S2):打开视图先 GET /views/:id/layout 覆盖节点坐标;
|
|
7
|
-
// 节点拖动结束后 PUT /views/:id/layout 落盘(防抖)。坐标不进图谱 JSON。
|
|
8
|
-
|
|
9
|
-
(function () {
|
|
10
|
-
const state = {
|
|
11
|
-
projects: [],
|
|
12
|
-
selectedProjectId: null,
|
|
13
|
-
views: [],
|
|
14
|
-
currentViewId: null,
|
|
15
|
-
graph: null,
|
|
16
|
-
g6Graph: null,
|
|
17
|
-
layoutSaveTimer: null,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const $ = (id) => document.getElementById(id);
|
|
21
|
-
|
|
22
|
-
async function api(path, options) {
|
|
23
|
-
const res = await fetch(path, options);
|
|
24
|
-
const contentType = res.headers.get('content-type') || '';
|
|
25
|
-
if (contentType.includes('application/json')) {
|
|
26
|
-
const body = await res.json();
|
|
27
|
-
if (!res.ok) {
|
|
28
|
-
throw new Error(body.error || `HTTP ${res.status}`);
|
|
29
|
-
}
|
|
30
|
-
return body;
|
|
31
|
-
}
|
|
32
|
-
if (!res.ok) {
|
|
33
|
-
throw new Error(`HTTP ${res.status}`);
|
|
34
|
-
}
|
|
35
|
-
return res;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function escapeHtml(value) {
|
|
39
|
-
return String(value == null ? '' : value)
|
|
40
|
-
.replace(/&/g, '&')
|
|
41
|
-
.replace(/</g, '<')
|
|
42
|
-
.replace(/>/g, '>')
|
|
43
|
-
.replace(/"/g, '"');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function loadProjects() {
|
|
47
|
-
const { projects } = await api('/api/projects');
|
|
48
|
-
state.projects = projects;
|
|
49
|
-
renderProjects();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function renderProjects() {
|
|
53
|
-
const list = $('project-list');
|
|
54
|
-
list.innerHTML = '';
|
|
55
|
-
for (const project of state.projects) {
|
|
56
|
-
const li = document.createElement('li');
|
|
57
|
-
const valid = project.valid ? '有效' : '无效';
|
|
58
|
-
li.className = project.id === state.selectedProjectId ? 'selected' : '';
|
|
59
|
-
li.innerHTML = `
|
|
60
|
-
<div class="project-name">${escapeHtml(project.name)}</div>
|
|
61
|
-
<div class="project-meta">
|
|
62
|
-
<span class="badge ${project.valid ? 'ok' : 'bad'}">${valid}</span>
|
|
63
|
-
元素 ${project.elements} · 关系 ${project.relationships} · 视图 ${project.views}
|
|
64
|
-
</div>`;
|
|
65
|
-
li.addEventListener('click', () => selectProject(project.id));
|
|
66
|
-
list.appendChild(li);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function selectProject(id) {
|
|
71
|
-
state.selectedProjectId = id;
|
|
72
|
-
state.currentViewId = null;
|
|
73
|
-
state.graph = null;
|
|
74
|
-
renderProjects();
|
|
75
|
-
$('no-selection').hidden = true;
|
|
76
|
-
$('workspace').hidden = false;
|
|
77
|
-
const project = state.projects.find((p) => p.id === id);
|
|
78
|
-
$('current-project').textContent = `当前项目:${project ? project.name : id}`;
|
|
79
|
-
await Promise.all([loadStatus(), loadViews()]);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function loadStatus() {
|
|
83
|
-
const status = await api(`/api/projects/${state.selectedProjectId}/status`);
|
|
84
|
-
$('status-bar').textContent = status.valid
|
|
85
|
-
? `图谱有效 · 元素 ${status.elements} · 关系 ${status.relationships} · 视图 ${status.views} · 最近修改 ${new Date(status.mtime).toLocaleString()}`
|
|
86
|
-
: `图谱无效:${status.error || (status.errors || []).join('; ')}`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function loadViews() {
|
|
90
|
-
const { views } = await api(`/api/projects/${state.selectedProjectId}/views`);
|
|
91
|
-
state.views = views;
|
|
92
|
-
renderViews();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function renderViews() {
|
|
96
|
-
const list = $('view-list');
|
|
97
|
-
list.innerHTML = '';
|
|
98
|
-
for (const view of state.views) {
|
|
99
|
-
const li = document.createElement('li');
|
|
100
|
-
li.className = view.view_id === state.currentViewId ? 'selected' : '';
|
|
101
|
-
li.innerHTML = `${escapeHtml(view.view_name)} <span class="muted">(元素 ${view.element_count})</span>`;
|
|
102
|
-
li.addEventListener('click', () => openView(view.view_id, view.view_name));
|
|
103
|
-
list.appendChild(li);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function openView(viewId, viewName) {
|
|
108
|
-
state.currentViewId = viewId;
|
|
109
|
-
renderViews();
|
|
110
|
-
const data = await api(`/api/projects/${state.selectedProjectId}/views/${viewId}/graph`);
|
|
111
|
-
// 布局侧车(M1-S2):先取合并后的侧车坐标覆盖图数据默认圆形布局。
|
|
112
|
-
// 侧车不可用时回退图端点自带坐标,不阻塞渲染。
|
|
113
|
-
try {
|
|
114
|
-
const layout = await api(`/api/projects/${state.selectedProjectId}/views/${viewId}/layout`);
|
|
115
|
-
const positions = (layout && layout.elements) || {};
|
|
116
|
-
for (const node of data.nodes) {
|
|
117
|
-
const pos = positions[node.id];
|
|
118
|
-
if (pos && Number.isFinite(pos.x) && Number.isFinite(pos.y)) {
|
|
119
|
-
node.x = pos.x;
|
|
120
|
-
node.y = pos.y;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
} catch {
|
|
124
|
-
/* 布局侧车不可用:使用图端点默认坐标 */
|
|
125
|
-
}
|
|
126
|
-
state.graph = data;
|
|
127
|
-
$('graph-title').textContent = `视图:${viewName}`;
|
|
128
|
-
renderGraph();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function renderGraph() {
|
|
132
|
-
if (state.g6Graph) {
|
|
133
|
-
state.g6Graph.destroy();
|
|
134
|
-
state.g6Graph = null;
|
|
135
|
-
}
|
|
136
|
-
const container = $('graph-container');
|
|
137
|
-
container.innerHTML = '';
|
|
138
|
-
if (!state.graph) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
if (!window.G6) {
|
|
142
|
-
container.textContent = 'G6 v5 未加载(/vendor/g6.min.js 缺失)';
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
const { nodes, edges } = state.graph;
|
|
146
|
-
const graph = new G6.Graph({
|
|
147
|
-
container,
|
|
148
|
-
width: container.clientWidth || 760,
|
|
149
|
-
height: container.clientHeight || 520,
|
|
150
|
-
autoFit: 'view',
|
|
151
|
-
data: {
|
|
152
|
-
nodes: nodes.map((node) => ({
|
|
153
|
-
id: node.id,
|
|
154
|
-
data: {
|
|
155
|
-
label: node.label,
|
|
156
|
-
type: node.type,
|
|
157
|
-
layer: node.layer,
|
|
158
|
-
description: node.data ? node.data.description : '',
|
|
159
|
-
},
|
|
160
|
-
style: { x: node.x, y: node.y },
|
|
161
|
-
})),
|
|
162
|
-
edges: edges.map((edge) => ({
|
|
163
|
-
id: edge.id,
|
|
164
|
-
source: edge.source,
|
|
165
|
-
target: edge.target,
|
|
166
|
-
data: { label: edge.label, type: edge.type },
|
|
167
|
-
})),
|
|
168
|
-
},
|
|
169
|
-
node: {
|
|
170
|
-
type: 'rect',
|
|
171
|
-
style: (datum) => ({
|
|
172
|
-
size: [140, 44],
|
|
173
|
-
radius: 6,
|
|
174
|
-
fill: colorForLayer(datum.data && datum.data.layer),
|
|
175
|
-
stroke: '#555',
|
|
176
|
-
labelText: `${(datum.data && datum.data.label) || datum.id}\n${(datum.data && datum.data.type) || ''}`,
|
|
177
|
-
labelFill: '#111',
|
|
178
|
-
labelFontSize: 11,
|
|
179
|
-
labelLineHeight: 14,
|
|
180
|
-
labelPlacement: 'center',
|
|
181
|
-
cursor: 'move',
|
|
182
|
-
}),
|
|
183
|
-
},
|
|
184
|
-
edge: {
|
|
185
|
-
type: 'line',
|
|
186
|
-
style: (datum) => ({
|
|
187
|
-
stroke: '#999',
|
|
188
|
-
lineWidth: 1,
|
|
189
|
-
endArrow: true,
|
|
190
|
-
labelText: (datum.data && datum.data.label) || '',
|
|
191
|
-
labelFill: '#666',
|
|
192
|
-
labelFontSize: 9,
|
|
193
|
-
}),
|
|
194
|
-
},
|
|
195
|
-
behaviors: ['drag-canvas', 'zoom-canvas', 'drag-element', 'click-select'],
|
|
196
|
-
});
|
|
197
|
-
// 拖动结束 → 布局侧车落盘(防抖,见 scheduleLayoutSave)。
|
|
198
|
-
graph.on('node:dragend', () => scheduleLayoutSave());
|
|
199
|
-
graph.render();
|
|
200
|
-
state.g6Graph = graph;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function colorForLayer(layer) {
|
|
204
|
-
switch (layer) {
|
|
205
|
-
case 'Business': return '#dbeafe';
|
|
206
|
-
case 'Application': return '#d1fae5';
|
|
207
|
-
case 'Technology': return '#fef3c7';
|
|
208
|
-
case 'Implementation': return '#fce7f3';
|
|
209
|
-
default: return '#f3f4f6';
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function scheduleLayoutSave() {
|
|
214
|
-
if (state.layoutSaveTimer) {
|
|
215
|
-
clearTimeout(state.layoutSaveTimer);
|
|
216
|
-
}
|
|
217
|
-
state.layoutSaveTimer = setTimeout(saveLayout, 400);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
async function saveLayout() {
|
|
221
|
-
state.layoutSaveTimer = null;
|
|
222
|
-
if (!state.g6Graph || !state.graph || !state.selectedProjectId || !state.currentViewId) {
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
const elements = {};
|
|
226
|
-
for (const node of state.graph.nodes) {
|
|
227
|
-
let pos = null;
|
|
228
|
-
try {
|
|
229
|
-
pos = state.g6Graph.getElementPosition(node.id);
|
|
230
|
-
} catch {
|
|
231
|
-
pos = null;
|
|
232
|
-
}
|
|
233
|
-
if (Array.isArray(pos)) {
|
|
234
|
-
elements[node.id] = { x: Math.round(pos[0]), y: Math.round(pos[1]) };
|
|
235
|
-
} else if (pos && Number.isFinite(pos.x) && Number.isFinite(pos.y)) {
|
|
236
|
-
elements[node.id] = { x: Math.round(pos.x), y: Math.round(pos.y) };
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
try {
|
|
240
|
-
await api(`/api/projects/${state.selectedProjectId}/views/${state.currentViewId}/layout`, {
|
|
241
|
-
method: 'PUT',
|
|
242
|
-
headers: { 'Content-Type': 'application/json' },
|
|
243
|
-
body: JSON.stringify({ elements }),
|
|
244
|
-
});
|
|
245
|
-
} catch {
|
|
246
|
-
/* 布局持久化失败不影响画布交互 */
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async function doSearch() {
|
|
251
|
-
const query = $('search-input').value.trim();
|
|
252
|
-
const mode = $('search-mode').value;
|
|
253
|
-
if (!query) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
const body = await api(`/api/projects/${state.selectedProjectId}/search`, {
|
|
257
|
-
method: 'POST',
|
|
258
|
-
headers: { 'Content-Type': 'application/json' },
|
|
259
|
-
body: JSON.stringify({ query, mode }),
|
|
260
|
-
});
|
|
261
|
-
renderSearchResults(body);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function renderSearchResults(body) {
|
|
265
|
-
const list = $('search-results');
|
|
266
|
-
list.innerHTML = '';
|
|
267
|
-
if (body.supported === false) {
|
|
268
|
-
const li = document.createElement('li');
|
|
269
|
-
li.className = 'muted';
|
|
270
|
-
li.textContent = body.message || '该检索模式暂不支持';
|
|
271
|
-
list.appendChild(li);
|
|
272
|
-
}
|
|
273
|
-
const hits = body.hits || [];
|
|
274
|
-
if (hits.length === 0) {
|
|
275
|
-
const li = document.createElement('li');
|
|
276
|
-
li.className = 'muted';
|
|
277
|
-
li.textContent = '无结果';
|
|
278
|
-
list.appendChild(li);
|
|
279
|
-
}
|
|
280
|
-
for (const hit of hits.slice(0, 20)) {
|
|
281
|
-
const li = document.createElement('li');
|
|
282
|
-
li.innerHTML = `<b>${escapeHtml(hit.kind)}</b> ${escapeHtml(hit.name || hit.id)} <span class="muted">${escapeHtml(hit.type || '')}</span>`;
|
|
283
|
-
li.addEventListener('click', () => {
|
|
284
|
-
$('search-input').value = hit.id || hit.name || '';
|
|
285
|
-
});
|
|
286
|
-
list.appendChild(li);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function renderEditForm(op) {
|
|
291
|
-
const form = $('edit-form');
|
|
292
|
-
const examples = {
|
|
293
|
-
addElement: '{"element":{"id":"9001","name":"新元素","type":"Application Component"},"view_ids":["1800"]}',
|
|
294
|
-
updateElement: '{"id":"9001","patch":{"description":"新描述"}}',
|
|
295
|
-
removeElement: '{"id":"9001"}',
|
|
296
|
-
addView: '{"view":{"view_id":"9002","view_name":"新视图","included_elements":[]}}',
|
|
297
|
-
updateRelationship: '{"id":"1980","patch":{"description":"新描述"}}',
|
|
298
|
-
removeRelationship: '{"id":"1980"}',
|
|
299
|
-
};
|
|
300
|
-
form.innerHTML = `
|
|
301
|
-
<label>操作载荷(JSON)</label>
|
|
302
|
-
<textarea id="edit-payload" rows="6">${examples[op] || '{}'}</textarea>`;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
async function doEdit() {
|
|
306
|
-
const op = $('edit-op').value;
|
|
307
|
-
let payload;
|
|
308
|
-
try {
|
|
309
|
-
payload = JSON.parse($('edit-payload').value || '{}');
|
|
310
|
-
} catch (error) {
|
|
311
|
-
$('edit-feedback').textContent = `载荷不是合法 JSON:${error.message}`;
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
try {
|
|
315
|
-
const result = await api(`/api/projects/${state.selectedProjectId}/edit`, {
|
|
316
|
-
method: 'POST',
|
|
317
|
-
headers: { 'Content-Type': 'application/json' },
|
|
318
|
-
body: JSON.stringify({ op, payload }),
|
|
319
|
-
});
|
|
320
|
-
$('edit-feedback').textContent = `成功:${result.op} → ${result.tool}`;
|
|
321
|
-
await Promise.all([loadStatus(), loadViews()]);
|
|
322
|
-
} catch (error) {
|
|
323
|
-
$('edit-feedback').textContent = `失败:${error.message}`;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
async function doUndoRedo(kind) {
|
|
328
|
-
try {
|
|
329
|
-
await api(`/api/projects/${state.selectedProjectId}/${kind}`, { method: 'POST' });
|
|
330
|
-
$('edit-feedback').textContent = `${kind} 完成`;
|
|
331
|
-
await Promise.all([loadStatus(), loadViews()]);
|
|
332
|
-
} catch (error) {
|
|
333
|
-
$('edit-feedback').textContent = `${kind} 失败:${error.message}`;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
async function doExport() {
|
|
338
|
-
const res = await api(`/api/projects/${state.selectedProjectId}/export`);
|
|
339
|
-
const blob = await res.blob();
|
|
340
|
-
const url = URL.createObjectURL(blob);
|
|
341
|
-
const a = document.createElement('a');
|
|
342
|
-
const project = state.projects.find((p) => p.id === state.selectedProjectId);
|
|
343
|
-
a.href = url;
|
|
344
|
-
a.download = `${project ? project.name : 'SystemArchitecture'}.json`;
|
|
345
|
-
a.click();
|
|
346
|
-
URL.revokeObjectURL(url);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
async function doImport(file) {
|
|
350
|
-
const text = await file.text();
|
|
351
|
-
try {
|
|
352
|
-
const result = await api(`/api/projects/${state.selectedProjectId}/import`, {
|
|
353
|
-
method: 'POST',
|
|
354
|
-
headers: { 'Content-Type': 'application/json' },
|
|
355
|
-
body: text,
|
|
356
|
-
});
|
|
357
|
-
$('edit-feedback').textContent = `导入成功:元素 ${result.elements} · 关系 ${result.relationships} · 视图 ${result.views}`;
|
|
358
|
-
await Promise.all([loadProjects(), loadStatus(), loadViews()]);
|
|
359
|
-
} catch (error) {
|
|
360
|
-
$('edit-feedback').textContent = `导入失败:${error.message}`;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function bindEvents() {
|
|
365
|
-
$('refresh-projects').addEventListener('click', loadProjects);
|
|
366
|
-
$('btn-export').addEventListener('click', doExport);
|
|
367
|
-
$('btn-import').addEventListener('click', () => $('import-file').click());
|
|
368
|
-
$('import-file').addEventListener('change', (event) => {
|
|
369
|
-
if (event.target.files[0]) {
|
|
370
|
-
doImport(event.target.files[0]);
|
|
371
|
-
}
|
|
372
|
-
event.target.value = '';
|
|
373
|
-
});
|
|
374
|
-
$('btn-undo').addEventListener('click', () => doUndoRedo('undo'));
|
|
375
|
-
$('btn-redo').addEventListener('click', () => doUndoRedo('redo'));
|
|
376
|
-
$('btn-search').addEventListener('click', doSearch);
|
|
377
|
-
$('search-input').addEventListener('keydown', (event) => {
|
|
378
|
-
if (event.key === 'Enter') {
|
|
379
|
-
doSearch();
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
$('edit-op').addEventListener('change', (event) => renderEditForm(event.target.value));
|
|
383
|
-
$('btn-edit').addEventListener('click', doEdit);
|
|
384
|
-
renderEditForm('addElement');
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
async function boot() {
|
|
388
|
-
bindEvents();
|
|
389
|
-
try {
|
|
390
|
-
await loadProjects();
|
|
391
|
-
} catch (error) {
|
|
392
|
-
$('no-selection').textContent = `无法连接服务:${error.message}`;
|
|
393
|
-
$('no-selection').hidden = false;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
document.addEventListener('DOMContentLoaded', boot);
|
|
398
|
-
})();
|
package/web/index.html
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>ArchGraph 本地知识图谱工具</title>
|
|
7
|
-
<link rel="stylesheet" href="/style.css">
|
|
8
|
-
</head>
|
|
9
|
-
<body>
|
|
10
|
-
<header>
|
|
11
|
-
<h1>ArchGraph 本地知识图谱工具</h1>
|
|
12
|
-
<p class="subtitle">面向无 EA 经验的用户:导入 / 导出 / 查看 / 编辑本地知识图谱</p>
|
|
13
|
-
</header>
|
|
14
|
-
|
|
15
|
-
<main>
|
|
16
|
-
<section id="projects-panel">
|
|
17
|
-
<h2>项目</h2>
|
|
18
|
-
<button id="refresh-projects">刷新项目</button>
|
|
19
|
-
<ul id="project-list"></ul>
|
|
20
|
-
</section>
|
|
21
|
-
|
|
22
|
-
<section id="main-panel">
|
|
23
|
-
<div id="no-selection" class="placeholder">请从左侧选择一个项目。</div>
|
|
24
|
-
|
|
25
|
-
<div id="workspace" hidden>
|
|
26
|
-
<div id="toolbar">
|
|
27
|
-
<span id="current-project"></span>
|
|
28
|
-
<button id="btn-export" title="导出当前项目图谱 JSON">导出</button>
|
|
29
|
-
<button id="btn-import" title="导入外部图谱 JSON(整体替换)">导入</button>
|
|
30
|
-
<button id="btn-undo" title="撤销上一步编辑">撤销</button>
|
|
31
|
-
<button id="btn-redo" title="重做">重做</button>
|
|
32
|
-
<input id="import-file" type="file" accept="application/json,.json" hidden>
|
|
33
|
-
</div>
|
|
34
|
-
|
|
35
|
-
<div id="status-bar"></div>
|
|
36
|
-
|
|
37
|
-
<div id="columns">
|
|
38
|
-
<aside id="left-col">
|
|
39
|
-
<div id="search-box">
|
|
40
|
-
<input id="search-input" placeholder="搜索元素 / 关系 / 视图">
|
|
41
|
-
<select id="search-mode">
|
|
42
|
-
<option value="local">本地检索</option>
|
|
43
|
-
<option value="semantic">语义检索</option>
|
|
44
|
-
<option value="context">上下文检索</option>
|
|
45
|
-
</select>
|
|
46
|
-
<button id="btn-search">搜索</button>
|
|
47
|
-
</div>
|
|
48
|
-
<ul id="search-results"></ul>
|
|
49
|
-
|
|
50
|
-
<h3>视图列表</h3>
|
|
51
|
-
<ul id="view-list"></ul>
|
|
52
|
-
|
|
53
|
-
<h3>编辑</h3>
|
|
54
|
-
<div id="edit-panel">
|
|
55
|
-
<div id="edit-form"></div>
|
|
56
|
-
<div id="edit-actions">
|
|
57
|
-
<select id="edit-op">
|
|
58
|
-
<option value="addElement">新增元素</option>
|
|
59
|
-
<option value="updateElement">编辑元素属性</option>
|
|
60
|
-
<option value="removeElement">删除元素</option>
|
|
61
|
-
<option value="addView">新增视图</option>
|
|
62
|
-
<option value="updateRelationship">编辑关系属性</option>
|
|
63
|
-
<option value="removeRelationship">删除关系</option>
|
|
64
|
-
</select>
|
|
65
|
-
<button id="btn-edit">执行</button>
|
|
66
|
-
</div>
|
|
67
|
-
<div id="edit-feedback"></div>
|
|
68
|
-
</div>
|
|
69
|
-
</aside>
|
|
70
|
-
|
|
71
|
-
<div id="graph-panel">
|
|
72
|
-
<h3 id="graph-title">图形化视图</h3>
|
|
73
|
-
<div id="graph-container"></div>
|
|
74
|
-
<p class="hint">AntV G6 v5 画布:平移 / 缩放 / 点选 / 拖动节点(拖动后坐标自动持久化到布局侧车)。</p>
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
</section>
|
|
79
|
-
</main>
|
|
80
|
-
|
|
81
|
-
<footer>本地运行(127.0.0.1),数据不离开本机。</footer>
|
|
82
|
-
|
|
83
|
-
<script src="/vendor/g6.min.js"></script>
|
|
84
|
-
<script src="/app.js"></script>
|
|
85
|
-
</body>
|
|
86
|
-
</html>
|