@crewx/memory 0.1.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/README.md +7 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +365 -0
- package/dist/cli.js.map +1 -0
- package/dist/src/engine.d.ts +43 -0
- package/dist/src/engine.d.ts.map +1 -0
- package/dist/src/engine.js +942 -0
- package/dist/src/engine.js.map +1 -0
- package/dist/src/mindmap.d.ts +75 -0
- package/dist/src/mindmap.d.ts.map +1 -0
- package/dist/src/mindmap.js +838 -0
- package/dist/src/mindmap.js.map +1 -0
- package/dist/src/parser.d.ts +3 -0
- package/dist/src/parser.d.ts.map +1 -0
- package/dist/src/parser.js +7 -0
- package/dist/src/parser.js.map +1 -0
- package/dist/src/types.d.ts +75 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +15 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,838 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MindmapEngine = void 0;
|
|
7
|
+
exports.loadAllEntries = loadAllEntries;
|
|
8
|
+
exports.buildGraph = buildGraph;
|
|
9
|
+
exports.saveGraph = saveGraph;
|
|
10
|
+
exports.loadGraph = loadGraph;
|
|
11
|
+
exports.generateHtml = generateHtml;
|
|
12
|
+
exports.filterGraphByPeriod = filterGraphByPeriod;
|
|
13
|
+
exports.renderMermaid = renderMermaid;
|
|
14
|
+
exports.renderText = renderText;
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const child_process_1 = require("child_process");
|
|
18
|
+
const parser_1 = require("./parser");
|
|
19
|
+
const knowledge_core_1 = require("@crewx/knowledge-core");
|
|
20
|
+
const MEMORY_SHORT_TERM_HOURS = 24;
|
|
21
|
+
const MEMORY_SUMMARY_DAYS = 7;
|
|
22
|
+
const MEMORY_RECENT_DAYS = 30;
|
|
23
|
+
const PERIOD_CONFIG = {
|
|
24
|
+
stm: {
|
|
25
|
+
label: `STM ${MEMORY_SHORT_TERM_HOURS}h`,
|
|
26
|
+
ms: MEMORY_SHORT_TERM_HOURS * 60 * 60 * 1000,
|
|
27
|
+
},
|
|
28
|
+
mtm: {
|
|
29
|
+
label: `MTM ${MEMORY_SUMMARY_DAYS}d`,
|
|
30
|
+
ms: MEMORY_SUMMARY_DAYS * 24 * 60 * 60 * 1000,
|
|
31
|
+
},
|
|
32
|
+
ltm: {
|
|
33
|
+
label: `LTM ${MEMORY_RECENT_DAYS}d`,
|
|
34
|
+
ms: MEMORY_RECENT_DAYS * 24 * 60 * 60 * 1000,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
const DEFAULT_COLORS = [
|
|
38
|
+
'#6366f1', '#8b5cf6', '#ec4899', '#f43f5e', '#f97316',
|
|
39
|
+
'#eab308', '#22c55e', '#14b8a6', '#06b6d4', '#3b82f6',
|
|
40
|
+
];
|
|
41
|
+
function loadAllEntries(entriesDir) {
|
|
42
|
+
if (!fs_1.default.existsSync(entriesDir))
|
|
43
|
+
return [];
|
|
44
|
+
const files = fs_1.default.readdirSync(entriesDir).filter(f => f.endsWith('.md'));
|
|
45
|
+
const entries = [];
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
const raw = fs_1.default.readFileSync(path_1.default.join(entriesDir, file), 'utf-8');
|
|
48
|
+
const { data, content } = (0, parser_1.parseFrontmatter)(raw);
|
|
49
|
+
entries.push({ file, content, ...data });
|
|
50
|
+
}
|
|
51
|
+
return entries;
|
|
52
|
+
}
|
|
53
|
+
function buildGraph(entriesDir, options = {}) {
|
|
54
|
+
const { edgeThreshold = 0.3, includeSameDay = false, } = options;
|
|
55
|
+
const entries = loadAllEntries(entriesDir);
|
|
56
|
+
if (entries.length === 0) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const kcEntries = entries.map(e => ({
|
|
60
|
+
id: e.id,
|
|
61
|
+
type: e['type'] || 'memory',
|
|
62
|
+
category: e.category || 'general',
|
|
63
|
+
tags: e.tags || [],
|
|
64
|
+
date: e.date || '',
|
|
65
|
+
summary: e.summary || '',
|
|
66
|
+
body: e.content || '',
|
|
67
|
+
}));
|
|
68
|
+
const coreGraph = (0, knowledge_core_1.buildGraph)(kcEntries, { edgeThreshold, includeSameDay });
|
|
69
|
+
return {
|
|
70
|
+
nodes: coreGraph.nodes.map(n => ({
|
|
71
|
+
...n,
|
|
72
|
+
important: entries.find(e => e.id === n.id)?.important,
|
|
73
|
+
})),
|
|
74
|
+
edges: coreGraph.edges,
|
|
75
|
+
stats: coreGraph.stats,
|
|
76
|
+
updated: new Date().toISOString(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function saveGraph(outputPath, graph) {
|
|
80
|
+
graph.updated = new Date().toISOString();
|
|
81
|
+
fs_1.default.writeFileSync(outputPath, JSON.stringify(graph, null, 2), 'utf-8');
|
|
82
|
+
}
|
|
83
|
+
function loadGraph(inputPath) {
|
|
84
|
+
if (!fs_1.default.existsSync(inputPath)) {
|
|
85
|
+
return { nodes: [], edges: [], stats: { nodeCount: 0, edgeCount: 0 }, updated: null };
|
|
86
|
+
}
|
|
87
|
+
return JSON.parse(fs_1.default.readFileSync(inputPath, 'utf-8'));
|
|
88
|
+
}
|
|
89
|
+
function generateHtml(graphs, options = {}) {
|
|
90
|
+
const { colors = DEFAULT_COLORS, title = 'Memory Graph' } = options;
|
|
91
|
+
if (Object.keys(graphs).length === 0) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const agentData = {};
|
|
95
|
+
for (const [agentId, graph] of Object.entries(graphs)) {
|
|
96
|
+
if (!graph || !graph.nodes || graph.nodes.length === 0) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const topics = [...new Set(graph.nodes.map(n => (n.tags?.[0]) || 'general'))];
|
|
100
|
+
const topicColors = {};
|
|
101
|
+
topics.forEach((topic, i) => {
|
|
102
|
+
topicColors[topic] = colors[i % colors.length];
|
|
103
|
+
});
|
|
104
|
+
const nodes = graph.nodes.map(n => {
|
|
105
|
+
const summary = n.summary ? String(n.summary) : (n.id ? String(n.id) : 'Unknown');
|
|
106
|
+
const primaryTag = (n.tags?.[0]) || 'general';
|
|
107
|
+
return {
|
|
108
|
+
id: n.id || 'unknown',
|
|
109
|
+
label: summary.slice(0, 40),
|
|
110
|
+
fullLabel: summary,
|
|
111
|
+
topic: primaryTag,
|
|
112
|
+
date: n.date || '-',
|
|
113
|
+
color: topicColors[primaryTag],
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
const links = graph.edges.map(e => ({
|
|
117
|
+
source: e.from,
|
|
118
|
+
target: e.to,
|
|
119
|
+
weight: e.weight,
|
|
120
|
+
type: e.type,
|
|
121
|
+
}));
|
|
122
|
+
agentData[agentId] = { nodes, links, topics, topicColors, updated: graph.updated };
|
|
123
|
+
}
|
|
124
|
+
const agentOptions = Object.keys(agentData)
|
|
125
|
+
.map(agentId => `<option value="${agentId}">${agentId}</option>`)
|
|
126
|
+
.join('');
|
|
127
|
+
return `<!DOCTYPE html>
|
|
128
|
+
<html lang="ko">
|
|
129
|
+
<head>
|
|
130
|
+
<meta charset="UTF-8">
|
|
131
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
132
|
+
<title>${title}</title>
|
|
133
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
134
|
+
<style>
|
|
135
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
136
|
+
body {
|
|
137
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
138
|
+
background: #1a1a2e;
|
|
139
|
+
overflow: hidden;
|
|
140
|
+
}
|
|
141
|
+
#graph { width: 100vw; height: 100vh; }
|
|
142
|
+
.node circle {
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
stroke: #fff;
|
|
145
|
+
stroke-width: 1.5px;
|
|
146
|
+
transition: all 0.2s;
|
|
147
|
+
}
|
|
148
|
+
.node circle:hover {
|
|
149
|
+
stroke-width: 3px;
|
|
150
|
+
filter: brightness(1.2);
|
|
151
|
+
}
|
|
152
|
+
.node text {
|
|
153
|
+
font-size: 10px;
|
|
154
|
+
fill: #e0e0e0;
|
|
155
|
+
pointer-events: none;
|
|
156
|
+
text-shadow: 0 0 3px #1a1a2e;
|
|
157
|
+
}
|
|
158
|
+
.link {
|
|
159
|
+
stroke: #4a4a6a;
|
|
160
|
+
stroke-opacity: 0.6;
|
|
161
|
+
}
|
|
162
|
+
.tooltip {
|
|
163
|
+
position: absolute;
|
|
164
|
+
background: #2d2d44;
|
|
165
|
+
border: 1px solid #4a4a6a;
|
|
166
|
+
border-radius: 8px;
|
|
167
|
+
padding: 12px;
|
|
168
|
+
color: #fff;
|
|
169
|
+
font-size: 13px;
|
|
170
|
+
max-width: 300px;
|
|
171
|
+
pointer-events: none;
|
|
172
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
|
|
173
|
+
z-index: 10;
|
|
174
|
+
}
|
|
175
|
+
.tooltip .title { font-weight: bold; margin-bottom: 8px; color: #a5b4fc; }
|
|
176
|
+
.tooltip .meta { color: #9ca3af; font-size: 11px; }
|
|
177
|
+
.controls {
|
|
178
|
+
position: absolute;
|
|
179
|
+
top: 20px;
|
|
180
|
+
left: 20px;
|
|
181
|
+
background: #2d2d44;
|
|
182
|
+
border-radius: 8px;
|
|
183
|
+
padding: 15px;
|
|
184
|
+
color: #fff;
|
|
185
|
+
font-size: 13px;
|
|
186
|
+
min-width: 200px;
|
|
187
|
+
z-index: 5;
|
|
188
|
+
}
|
|
189
|
+
.controls h3 { margin-bottom: 10px; color: #a5b4fc; }
|
|
190
|
+
.controls select {
|
|
191
|
+
width: 100%;
|
|
192
|
+
padding: 8px;
|
|
193
|
+
background: #1a1a2e;
|
|
194
|
+
color: #fff;
|
|
195
|
+
border: 1px solid #4a4a6a;
|
|
196
|
+
border-radius: 4px;
|
|
197
|
+
font-size: 13px;
|
|
198
|
+
cursor: pointer;
|
|
199
|
+
}
|
|
200
|
+
.controls select + select {
|
|
201
|
+
margin-top: 6px;
|
|
202
|
+
}
|
|
203
|
+
.controls select:hover {
|
|
204
|
+
border-color: #6366f1;
|
|
205
|
+
}
|
|
206
|
+
.legend {
|
|
207
|
+
position: absolute;
|
|
208
|
+
top: 100px;
|
|
209
|
+
left: 20px;
|
|
210
|
+
background: #2d2d44;
|
|
211
|
+
border-radius: 8px;
|
|
212
|
+
padding: 15px;
|
|
213
|
+
color: #fff;
|
|
214
|
+
font-size: 12px;
|
|
215
|
+
max-height: calc(100vh - 200px);
|
|
216
|
+
overflow-y: auto;
|
|
217
|
+
z-index: 5;
|
|
218
|
+
}
|
|
219
|
+
.legend h4 { margin-bottom: 8px; color: #a5b4fc; font-size: 11px; }
|
|
220
|
+
.legend-item { display: flex; align-items: center; margin: 4px 0; }
|
|
221
|
+
.legend-color { width: 12px; height: 12px; border-radius: 50%; margin-right: 8px; flex-shrink: 0; }
|
|
222
|
+
.stats {
|
|
223
|
+
position: absolute;
|
|
224
|
+
bottom: 20px;
|
|
225
|
+
left: 20px;
|
|
226
|
+
background: #2d2d44;
|
|
227
|
+
border-radius: 8px;
|
|
228
|
+
padding: 10px 15px;
|
|
229
|
+
color: #9ca3af;
|
|
230
|
+
font-size: 11px;
|
|
231
|
+
z-index: 5;
|
|
232
|
+
}
|
|
233
|
+
</style>
|
|
234
|
+
</head>
|
|
235
|
+
<body>
|
|
236
|
+
<div id="graph"></div>
|
|
237
|
+
|
|
238
|
+
<div class="controls">
|
|
239
|
+
<h3>📊 ${title}</h3>
|
|
240
|
+
<select id="agentSelect">${agentOptions}</select>
|
|
241
|
+
<select id="periodSelect">
|
|
242
|
+
<option value="all">All</option>
|
|
243
|
+
<option value="stm">STM (24h)</option>
|
|
244
|
+
<option value="mtm">MTM (7d)</option>
|
|
245
|
+
<option value="ltm">LTM (30d)</option>
|
|
246
|
+
</select>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<div class="legend" id="legend"></div>
|
|
250
|
+
|
|
251
|
+
<div class="stats" id="stats"></div>
|
|
252
|
+
|
|
253
|
+
<div class="tooltip" style="display:none"></div>
|
|
254
|
+
|
|
255
|
+
<script>
|
|
256
|
+
const agentData = ${JSON.stringify(agentData)};
|
|
257
|
+
|
|
258
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
259
|
+
const agentFromUrl = urlParams.get('agent');
|
|
260
|
+
let currentAgent = (agentFromUrl && agentData[agentFromUrl]) ? agentFromUrl : Object.keys(agentData)[0];
|
|
261
|
+
let currentPeriod = urlParams.get('period') || 'all';
|
|
262
|
+
|
|
263
|
+
let simulation = null;
|
|
264
|
+
let svg = null;
|
|
265
|
+
let g = null;
|
|
266
|
+
|
|
267
|
+
const width = window.innerWidth;
|
|
268
|
+
const height = window.innerHeight;
|
|
269
|
+
|
|
270
|
+
function initSVG() {
|
|
271
|
+
svg = d3.select('#graph')
|
|
272
|
+
.append('svg')
|
|
273
|
+
.attr('width', width)
|
|
274
|
+
.attr('height', height);
|
|
275
|
+
|
|
276
|
+
g = svg.append('g');
|
|
277
|
+
|
|
278
|
+
svg.call(d3.zoom()
|
|
279
|
+
.extent([[0, 0], [width, height]])
|
|
280
|
+
.scaleExtent([0.1, 4])
|
|
281
|
+
.on('zoom', (event) => g.attr('transform', event.transform)));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function parseDateForFilter(dateStr) {
|
|
285
|
+
if (!dateStr || dateStr === '-') return null;
|
|
286
|
+
const raw = String(dateStr);
|
|
287
|
+
const date = raw.includes('T') ? new Date(raw) : new Date(raw + 'T00:00:00Z');
|
|
288
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function filterDataByPeriod(data, period) {
|
|
292
|
+
if (!period || period === 'all') return data;
|
|
293
|
+
const periodMap = {
|
|
294
|
+
stm: 24 * 60 * 60 * 1000,
|
|
295
|
+
mtm: 7 * 24 * 60 * 60 * 1000,
|
|
296
|
+
ltm: 30 * 24 * 60 * 60 * 1000
|
|
297
|
+
};
|
|
298
|
+
const windowMs = periodMap[period];
|
|
299
|
+
if (!windowMs) return data;
|
|
300
|
+
const cutoff = Date.now() - windowMs;
|
|
301
|
+
const nodes = data.nodes.filter(n => {
|
|
302
|
+
const date = parseDateForFilter(n.date);
|
|
303
|
+
return date && date.getTime() >= cutoff;
|
|
304
|
+
});
|
|
305
|
+
const nodeIds = new Set(nodes.map(n => n.id));
|
|
306
|
+
const links = data.links.filter(l => nodeIds.has(l.source) && nodeIds.has(l.target));
|
|
307
|
+
return { ...data, nodes, links };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function getFilteredData() {
|
|
311
|
+
return filterDataByPeriod(agentData[currentAgent], currentPeriod);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function updateLegend(data) {
|
|
315
|
+
const topics = [...new Set(data.nodes.map(n => n.topic))];
|
|
316
|
+
const colors = agentData[currentAgent].topicColors || {};
|
|
317
|
+
const items = topics.map(t =>
|
|
318
|
+
'<div class="legend-item"><div class="legend-color" style="background:' + (colors[t] || '#666') + '"></div><span>' + t + '</span></div>'
|
|
319
|
+
).join('');
|
|
320
|
+
document.getElementById('legend').innerHTML = '<h4>토픽</h4>' + items;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function updateStats(data) {
|
|
324
|
+
const periodLabels = {
|
|
325
|
+
all: 'All',
|
|
326
|
+
stm: 'STM (24h)',
|
|
327
|
+
mtm: 'MTM (7d)',
|
|
328
|
+
ltm: 'LTM (30d)'
|
|
329
|
+
};
|
|
330
|
+
const periodLabel = periodLabels[currentPeriod] || 'All';
|
|
331
|
+
const periodText = currentPeriod === 'all' ? '' : (' | 기간: ' + periodLabel);
|
|
332
|
+
document.getElementById('stats').innerHTML =
|
|
333
|
+
'에이전트: ' + currentAgent + ' | ' +
|
|
334
|
+
'노드: ' + data.nodes.length + ' | ' +
|
|
335
|
+
'엣지: ' + data.links.length + ' | ' +
|
|
336
|
+
'업데이트: ' + (data.updated ? data.updated.slice(0, 10) : '-') +
|
|
337
|
+
periodText;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function renderGraph() {
|
|
341
|
+
const data = getFilteredData();
|
|
342
|
+
|
|
343
|
+
if (simulation) {
|
|
344
|
+
simulation.stop();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
g.selectAll('*').remove();
|
|
348
|
+
|
|
349
|
+
const nodes = JSON.parse(JSON.stringify(data.nodes));
|
|
350
|
+
const links = JSON.parse(JSON.stringify(data.links));
|
|
351
|
+
|
|
352
|
+
simulation = d3.forceSimulation(nodes)
|
|
353
|
+
.force('link', d3.forceLink(links).id(d => d.id).distance(80))
|
|
354
|
+
.force('charge', d3.forceManyBody().strength(-200))
|
|
355
|
+
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
356
|
+
.force('collision', d3.forceCollide().radius(30));
|
|
357
|
+
|
|
358
|
+
const link = g.append('g')
|
|
359
|
+
.selectAll('line')
|
|
360
|
+
.data(links)
|
|
361
|
+
.join('line')
|
|
362
|
+
.attr('class', 'link')
|
|
363
|
+
.attr('stroke-width', d => Math.sqrt(d.weight) * 2);
|
|
364
|
+
|
|
365
|
+
const node = g.append('g')
|
|
366
|
+
.selectAll('g')
|
|
367
|
+
.data(nodes)
|
|
368
|
+
.join('g')
|
|
369
|
+
.attr('class', 'node')
|
|
370
|
+
.call(d3.drag()
|
|
371
|
+
.on('start', dragstarted)
|
|
372
|
+
.on('drag', dragged)
|
|
373
|
+
.on('end', dragended));
|
|
374
|
+
|
|
375
|
+
node.append('circle')
|
|
376
|
+
.attr('r', d => {
|
|
377
|
+
const connections = links.filter(l => l.source.id === d.id || l.target.id === d.id).length;
|
|
378
|
+
return Math.max(6, Math.min(20, 6 + connections));
|
|
379
|
+
})
|
|
380
|
+
.attr('fill', d => d.color);
|
|
381
|
+
|
|
382
|
+
node.append('text')
|
|
383
|
+
.attr('dx', 15)
|
|
384
|
+
.attr('dy', 4)
|
|
385
|
+
.text(d => d.label);
|
|
386
|
+
|
|
387
|
+
const tooltip = d3.select('.tooltip');
|
|
388
|
+
|
|
389
|
+
node.on('mouseover', (event, d) => {
|
|
390
|
+
tooltip.style('display', 'block')
|
|
391
|
+
.style('left', (event.pageX + 15) + 'px')
|
|
392
|
+
.style('top', (event.pageY - 10) + 'px')
|
|
393
|
+
.html('<div class="title">' + d.fullLabel + '</div><div class="meta">ID: ' + d.id + '<br>토픽: ' + d.topic + '<br>날짜: ' + d.date + '</div>');
|
|
394
|
+
}).on('mouseout', () => {
|
|
395
|
+
tooltip.style('display', 'none');
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
simulation.on('tick', () => {
|
|
399
|
+
link
|
|
400
|
+
.attr('x1', d => d.source.x)
|
|
401
|
+
.attr('y1', d => d.source.y)
|
|
402
|
+
.attr('x2', d => d.target.x)
|
|
403
|
+
.attr('y2', d => d.target.y);
|
|
404
|
+
node.attr('transform', d => 'translate(' + d.x + ',' + d.y + ')');
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
function dragstarted(event) {
|
|
408
|
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
409
|
+
event.subject.fx = event.subject.x;
|
|
410
|
+
event.subject.fy = event.subject.y;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function dragged(event) {
|
|
414
|
+
event.subject.fx = event.x;
|
|
415
|
+
event.subject.fy = event.y;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function dragended(event) {
|
|
419
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
420
|
+
event.subject.fx = null;
|
|
421
|
+
event.subject.fy = null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
updateLegend(data);
|
|
425
|
+
updateStats(data);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
429
|
+
document.getElementById('agentSelect').value = currentAgent;
|
|
430
|
+
document.getElementById('periodSelect').value = currentPeriod;
|
|
431
|
+
|
|
432
|
+
document.getElementById('agentSelect').addEventListener('change', (e) => {
|
|
433
|
+
currentAgent = e.target.value;
|
|
434
|
+
const url = new URL(window.location);
|
|
435
|
+
url.searchParams.set('agent', currentAgent);
|
|
436
|
+
url.searchParams.set('period', currentPeriod);
|
|
437
|
+
history.replaceState({}, '', url);
|
|
438
|
+
renderGraph();
|
|
439
|
+
});
|
|
440
|
+
document.getElementById('periodSelect').addEventListener('change', (e) => {
|
|
441
|
+
currentPeriod = e.target.value;
|
|
442
|
+
const url = new URL(window.location);
|
|
443
|
+
url.searchParams.set('agent', currentAgent);
|
|
444
|
+
url.searchParams.set('period', currentPeriod);
|
|
445
|
+
history.replaceState({}, '', url);
|
|
446
|
+
renderGraph();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
initSVG();
|
|
450
|
+
renderGraph();
|
|
451
|
+
});
|
|
452
|
+
</script>
|
|
453
|
+
</body>
|
|
454
|
+
</html>`;
|
|
455
|
+
}
|
|
456
|
+
function parseDateForFilter(dateStr) {
|
|
457
|
+
if (!dateStr)
|
|
458
|
+
return null;
|
|
459
|
+
const raw = String(dateStr);
|
|
460
|
+
const date = raw.includes('T') ? new Date(raw) : new Date(`${raw}T00:00:00Z`);
|
|
461
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
462
|
+
}
|
|
463
|
+
function filterGraphByPeriod(graph, period) {
|
|
464
|
+
if (!period || period === true || period === 'all') {
|
|
465
|
+
return { graph, label: null, invalid: false };
|
|
466
|
+
}
|
|
467
|
+
const key = String(period).toLowerCase();
|
|
468
|
+
const config = PERIOD_CONFIG[key];
|
|
469
|
+
if (!config) {
|
|
470
|
+
return { graph, label: null, invalid: true };
|
|
471
|
+
}
|
|
472
|
+
const cutoff = Date.now() - config.ms;
|
|
473
|
+
const nodes = (graph.nodes || []).filter(node => {
|
|
474
|
+
const date = parseDateForFilter(node.date);
|
|
475
|
+
return date && date.getTime() >= cutoff;
|
|
476
|
+
});
|
|
477
|
+
const nodeIds = new Set(nodes.map(n => n.id));
|
|
478
|
+
const edges = (graph.edges || []).filter(e => nodeIds.has(e.from) && nodeIds.has(e.to));
|
|
479
|
+
return {
|
|
480
|
+
graph: {
|
|
481
|
+
...graph,
|
|
482
|
+
nodes,
|
|
483
|
+
edges,
|
|
484
|
+
stats: { nodeCount: nodes.length, edgeCount: edges.length },
|
|
485
|
+
},
|
|
486
|
+
label: config.label,
|
|
487
|
+
invalid: false,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
function truncateLabel(value, limit = 30) {
|
|
491
|
+
const text = value ? String(value) : '';
|
|
492
|
+
if (text.length <= limit)
|
|
493
|
+
return text;
|
|
494
|
+
return text.slice(0, limit);
|
|
495
|
+
}
|
|
496
|
+
function escapeMermaidLabel(value) {
|
|
497
|
+
return String(value)
|
|
498
|
+
.replace(/\\/g, '\\\\')
|
|
499
|
+
.replace(/"/g, '\\"')
|
|
500
|
+
.replace(/\r?\n/g, ' ');
|
|
501
|
+
}
|
|
502
|
+
function sanitizeMermaidId(value) {
|
|
503
|
+
return String(value || '').replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
504
|
+
}
|
|
505
|
+
function mergeImportantFlags(graph, entries) {
|
|
506
|
+
if (!entries || entries.length === 0)
|
|
507
|
+
return graph;
|
|
508
|
+
const importantMap = new Map();
|
|
509
|
+
for (const entry of entries) {
|
|
510
|
+
if (entry && entry.id) {
|
|
511
|
+
importantMap.set(entry.id, Boolean(entry.important));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (importantMap.size === 0)
|
|
515
|
+
return graph;
|
|
516
|
+
const nodes = (graph.nodes || []).map(node => {
|
|
517
|
+
if (importantMap.has(node.id)) {
|
|
518
|
+
return { ...node, important: importantMap.get(node.id) };
|
|
519
|
+
}
|
|
520
|
+
return node;
|
|
521
|
+
});
|
|
522
|
+
return { ...graph, nodes };
|
|
523
|
+
}
|
|
524
|
+
function renderMermaid(viewGraph) {
|
|
525
|
+
const lines = [];
|
|
526
|
+
lines.push('```mermaid');
|
|
527
|
+
lines.push('graph LR');
|
|
528
|
+
const byTag = {};
|
|
529
|
+
for (const node of viewGraph.nodes || []) {
|
|
530
|
+
const tag = (node.tags?.[0]) || 'general';
|
|
531
|
+
if (!byTag[tag])
|
|
532
|
+
byTag[tag] = [];
|
|
533
|
+
byTag[tag].push(node);
|
|
534
|
+
}
|
|
535
|
+
const tagKeys = Object.keys(byTag).sort((a, b) => a.localeCompare(b));
|
|
536
|
+
for (const topic of tagKeys) {
|
|
537
|
+
const topicId = sanitizeMermaidId(topic || 'general');
|
|
538
|
+
lines.push(` subgraph ${topicId}`);
|
|
539
|
+
const nodes = byTag[topic].slice().sort((a, b) => {
|
|
540
|
+
const aId = String(a.id || '');
|
|
541
|
+
const bId = String(b.id || '');
|
|
542
|
+
return aId.localeCompare(bId);
|
|
543
|
+
});
|
|
544
|
+
for (const node of nodes) {
|
|
545
|
+
const baseLabel = truncateLabel(node.summary || node.id || 'Unknown', 30);
|
|
546
|
+
const label = node.important ? `⭐ ${baseLabel}` : baseLabel;
|
|
547
|
+
const safeLabel = escapeMermaidLabel(label);
|
|
548
|
+
const nodeId = sanitizeMermaidId(node.id);
|
|
549
|
+
lines.push(` ${nodeId}["${safeLabel}"]`);
|
|
550
|
+
}
|
|
551
|
+
lines.push(' end');
|
|
552
|
+
}
|
|
553
|
+
const edges = (viewGraph.edges || []).filter(edge => Number(edge.weight) >= 0.5);
|
|
554
|
+
for (const edge of edges) {
|
|
555
|
+
const fromId = sanitizeMermaidId(edge.from);
|
|
556
|
+
const toId = sanitizeMermaidId(edge.to);
|
|
557
|
+
const weight = Number.isFinite(edge.weight) ? edge.weight : Number(edge.weight);
|
|
558
|
+
const weightLabel = Number.isFinite(weight) ? weight : edge.weight;
|
|
559
|
+
lines.push(` ${fromId} -->|${weightLabel}| ${toId}`);
|
|
560
|
+
}
|
|
561
|
+
lines.push('```');
|
|
562
|
+
return lines.join('\n');
|
|
563
|
+
}
|
|
564
|
+
function renderText(viewGraph, agentId, filtered) {
|
|
565
|
+
console.log(`# ${agentId} 연결 그래프\n`);
|
|
566
|
+
if (filtered.label && !filtered.invalid) {
|
|
567
|
+
console.log(`> (기간 필터: ${filtered.label})`);
|
|
568
|
+
}
|
|
569
|
+
console.log(`> 최종 업데이트: ${viewGraph.updated || '-'}`);
|
|
570
|
+
console.log(`> 노드: ${viewGraph.nodes.length}개 | 엣지: ${viewGraph.edges.length}개\n`);
|
|
571
|
+
if (viewGraph.nodes.length === 0) {
|
|
572
|
+
console.log('해당 기간에 해당하는 노드가 없습니다.');
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const byTag = {};
|
|
576
|
+
for (const node of viewGraph.nodes) {
|
|
577
|
+
const tag = (node.tags?.[0]) || 'general';
|
|
578
|
+
if (!byTag[tag])
|
|
579
|
+
byTag[tag] = [];
|
|
580
|
+
byTag[tag].push(node);
|
|
581
|
+
}
|
|
582
|
+
console.log(`## 태그별 클러스터\n`);
|
|
583
|
+
for (const [topic, nodes] of Object.entries(byTag).sort((a, b) => b[1].length - a[1].length)) {
|
|
584
|
+
console.log(`### ${topic} (${nodes.length}개)`);
|
|
585
|
+
for (const node of nodes.slice(0, 5)) {
|
|
586
|
+
const connectionCount = viewGraph.edges.filter(e => e.from === node.id || e.to === node.id).length;
|
|
587
|
+
console.log(`- [${node.id}] ${node.summary} (연결: ${connectionCount})`);
|
|
588
|
+
}
|
|
589
|
+
if (nodes.length > 5) {
|
|
590
|
+
console.log(`- ... 외 ${nodes.length - 5}개`);
|
|
591
|
+
}
|
|
592
|
+
console.log('');
|
|
593
|
+
}
|
|
594
|
+
const connectionCounts = {};
|
|
595
|
+
for (const edge of viewGraph.edges) {
|
|
596
|
+
connectionCounts[edge.from] = (connectionCounts[edge.from] || 0) + 1;
|
|
597
|
+
connectionCounts[edge.to] = (connectionCounts[edge.to] || 0) + 1;
|
|
598
|
+
}
|
|
599
|
+
const hubs = Object.entries(connectionCounts)
|
|
600
|
+
.sort((a, b) => b[1] - a[1])
|
|
601
|
+
.slice(0, 5);
|
|
602
|
+
if (hubs.length > 0) {
|
|
603
|
+
console.log(`## 허브 노드 (연결 많은 기억)\n`);
|
|
604
|
+
const nodeMap = {};
|
|
605
|
+
viewGraph.nodes.forEach(n => (nodeMap[n.id] = n));
|
|
606
|
+
for (const [id, count] of hubs) {
|
|
607
|
+
const node = nodeMap[id];
|
|
608
|
+
if (node) {
|
|
609
|
+
console.log(`- [${id}] ${node.summary} - ${count}개 연결`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
class MindmapEngine {
|
|
615
|
+
constructor(dataDir) {
|
|
616
|
+
this.dataDir = dataDir;
|
|
617
|
+
}
|
|
618
|
+
getAgentDir(agentId) {
|
|
619
|
+
return path_1.default.join(this.dataDir, agentId);
|
|
620
|
+
}
|
|
621
|
+
getEntriesDir(agentId) {
|
|
622
|
+
return path_1.default.join(this.getAgentDir(agentId), 'entries');
|
|
623
|
+
}
|
|
624
|
+
getGraphPath(agentId) {
|
|
625
|
+
return path_1.default.join(this.getAgentDir(agentId), 'graph.json');
|
|
626
|
+
}
|
|
627
|
+
getHtmlPath() {
|
|
628
|
+
return path_1.default.join(this.dataDir, 'mindmap.html');
|
|
629
|
+
}
|
|
630
|
+
build(agentId, options = {}) {
|
|
631
|
+
console.log(`🔨 ${agentId} 그래프 빌드 중...\n`);
|
|
632
|
+
const entriesDir = this.getEntriesDir(agentId);
|
|
633
|
+
const graph = buildGraph(entriesDir, { edgeThreshold: 0.3, ...options });
|
|
634
|
+
if (!graph) {
|
|
635
|
+
console.log('기억이 없습니다.');
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
saveGraph(this.getGraphPath(agentId), graph);
|
|
639
|
+
console.log(`✅ 그래프 빌드 완료!`);
|
|
640
|
+
console.log(` 노드: ${graph.stats.nodeCount}개`);
|
|
641
|
+
console.log(` 엣지: ${graph.stats.edgeCount}개`);
|
|
642
|
+
console.log(` 파일: ${agentId}/graph.json`);
|
|
643
|
+
if (graph.edges.length > 0) {
|
|
644
|
+
console.log(`\n📊 주요 연결 (weight 상위 10개):`);
|
|
645
|
+
const topEdges = graph.edges.sort((a, b) => b.weight - a.weight).slice(0, 10);
|
|
646
|
+
const nodeMap = {};
|
|
647
|
+
graph.nodes.forEach(n => (nodeMap[n.id] = n.summary));
|
|
648
|
+
for (const edge of topEdges) {
|
|
649
|
+
const fromSummary = (nodeMap[edge.from] || edge.from).slice(0, 25);
|
|
650
|
+
const toSummary = (nodeMap[edge.to] || edge.to).slice(0, 25);
|
|
651
|
+
console.log(` [${edge.weight}] ${fromSummary}... ↔ ${toSummary}...`);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
show(agentId, options = {}) {
|
|
656
|
+
const graph = loadGraph(this.getGraphPath(agentId));
|
|
657
|
+
if (!graph.nodes || graph.nodes.length === 0) {
|
|
658
|
+
console.log('그래프가 없습니다. 먼저 build 명령을 실행하세요.');
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const filtered = filterGraphByPeriod(graph, options.period);
|
|
662
|
+
if (filtered.invalid) {
|
|
663
|
+
console.log(`⚠️ 알 수 없는 period: ${options.period} (stm|mtm|ltm)`);
|
|
664
|
+
}
|
|
665
|
+
let viewGraph = filtered.invalid ? graph : filtered.graph;
|
|
666
|
+
const entries = loadAllEntries(this.getEntriesDir(agentId));
|
|
667
|
+
viewGraph = mergeImportantFlags(viewGraph, entries);
|
|
668
|
+
const format = normalizeFormatOption(options.format);
|
|
669
|
+
if (format === 'text') {
|
|
670
|
+
renderText(viewGraph, agentId, filtered);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (!viewGraph.nodes || viewGraph.nodes.length === 0) {
|
|
674
|
+
console.log('해당 기간에 해당하는 노드가 없습니다.');
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
if (filtered.label && !filtered.invalid) {
|
|
678
|
+
console.log(`(기간 필터: ${filtered.label})`);
|
|
679
|
+
}
|
|
680
|
+
console.log(renderMermaid(viewGraph));
|
|
681
|
+
}
|
|
682
|
+
related(agentId, memoryId) {
|
|
683
|
+
const graph = loadGraph(this.getGraphPath(agentId));
|
|
684
|
+
if (!graph.nodes || graph.nodes.length === 0) {
|
|
685
|
+
console.log('그래프가 없습니다. 먼저 build 명령을 실행하세요.');
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
const node = graph.nodes.find(n => n.id === memoryId);
|
|
689
|
+
if (!node) {
|
|
690
|
+
console.log(`ID '${memoryId}'를 찾을 수 없습니다.`);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
console.log(`## 🔗 [${memoryId}] ${node.summary}\n`);
|
|
694
|
+
console.log(`- 날짜: ${node.date || '-'}`);
|
|
695
|
+
console.log(`- 태그: ${(node.tags || []).join(', ') || '-'}\n`);
|
|
696
|
+
const connectedEdges = graph.edges.filter(e => e.from === memoryId || e.to === memoryId);
|
|
697
|
+
if (connectedEdges.length === 0) {
|
|
698
|
+
console.log('연결된 기억이 없습니다.');
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
console.log(`### 연결된 기억 (${connectedEdges.length}개)\n`);
|
|
702
|
+
const nodeMap = {};
|
|
703
|
+
graph.nodes.forEach(n => (nodeMap[n.id] = n));
|
|
704
|
+
connectedEdges.sort((a, b) => b.weight - a.weight);
|
|
705
|
+
for (const edge of connectedEdges) {
|
|
706
|
+
const otherId = edge.from === memoryId ? edge.to : edge.from;
|
|
707
|
+
const otherNode = nodeMap[otherId];
|
|
708
|
+
if (otherNode) {
|
|
709
|
+
const reasons = edge.reasons.join(', ');
|
|
710
|
+
console.log(`[${edge.weight}] [${otherId}] ${otherNode.summary}`);
|
|
711
|
+
console.log(` └─ 연결 이유: ${reasons}`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
add(agentId, fromId, toId, type = 'manual') {
|
|
716
|
+
const graph = loadGraph(this.getGraphPath(agentId));
|
|
717
|
+
if (!graph.nodes || graph.nodes.length === 0) {
|
|
718
|
+
console.log('그래프가 없습니다. 먼저 build 명령을 실행하세요.');
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
const fromNode = graph.nodes.find(n => n.id === fromId);
|
|
722
|
+
const toNode = graph.nodes.find(n => n.id === toId);
|
|
723
|
+
if (!fromNode) {
|
|
724
|
+
console.log(`ID '${fromId}'를 찾을 수 없습니다.`);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
if (!toNode) {
|
|
728
|
+
console.log(`ID '${toId}'를 찾을 수 없습니다.`);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const existingEdge = graph.edges.find(e => (e.from === fromId && e.to === toId) || (e.from === toId && e.to === fromId));
|
|
732
|
+
if (existingEdge) {
|
|
733
|
+
console.log(`이미 연결되어 있습니다: weight ${existingEdge.weight}`);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
graph.edges.push({
|
|
737
|
+
from: fromId,
|
|
738
|
+
to: toId,
|
|
739
|
+
type,
|
|
740
|
+
weight: 1.0,
|
|
741
|
+
reasons: [type],
|
|
742
|
+
manual: true,
|
|
743
|
+
});
|
|
744
|
+
saveGraph(this.getGraphPath(agentId), graph);
|
|
745
|
+
console.log(`✅ 연결 추가 완료!`);
|
|
746
|
+
console.log(` ${(fromNode.summary || '').slice(0, 30)}...`);
|
|
747
|
+
console.log(` ↔`);
|
|
748
|
+
console.log(` ${(toNode.summary || '').slice(0, 30)}...`);
|
|
749
|
+
}
|
|
750
|
+
remove(agentId, fromId, toId) {
|
|
751
|
+
const graph = loadGraph(this.getGraphPath(agentId));
|
|
752
|
+
if (!graph.edges || graph.edges.length === 0) {
|
|
753
|
+
console.log('연결이 없습니다.');
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const edgeIndex = graph.edges.findIndex(e => (e.from === fromId && e.to === toId) || (e.from === toId && e.to === fromId));
|
|
757
|
+
if (edgeIndex === -1) {
|
|
758
|
+
console.log('해당 연결을 찾을 수 없습니다.');
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
graph.edges.splice(edgeIndex, 1);
|
|
762
|
+
saveGraph(this.getGraphPath(agentId), graph);
|
|
763
|
+
console.log(`🗑️ 연결 제거 완료: ${fromId} ↔ ${toId}`);
|
|
764
|
+
}
|
|
765
|
+
html() {
|
|
766
|
+
if (!fs_1.default.existsSync(this.dataDir)) {
|
|
767
|
+
console.log('data 디렉토리가 없습니다.');
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const agentDirs = fs_1.default.readdirSync(this.dataDir).filter(name => {
|
|
771
|
+
const dirPath = path_1.default.join(this.dataDir, name);
|
|
772
|
+
try {
|
|
773
|
+
return fs_1.default.statSync(dirPath).isDirectory();
|
|
774
|
+
}
|
|
775
|
+
catch {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
const agentGraphs = {};
|
|
780
|
+
let totalNodes = 0;
|
|
781
|
+
let totalEdges = 0;
|
|
782
|
+
for (const agentId of agentDirs) {
|
|
783
|
+
const graph = loadGraph(this.getGraphPath(agentId));
|
|
784
|
+
if (graph.nodes && graph.nodes.length > 0) {
|
|
785
|
+
agentGraphs[agentId] = graph;
|
|
786
|
+
totalNodes += graph.nodes.length;
|
|
787
|
+
totalEdges += graph.edges.length;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
if (Object.keys(agentGraphs).length === 0) {
|
|
791
|
+
console.log('그래프가 없습니다. 먼저 build 명령을 실행하세요.');
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const htmlContent = generateHtml(agentGraphs, {
|
|
795
|
+
title: 'Memory Graph - All Agents',
|
|
796
|
+
});
|
|
797
|
+
if (!htmlContent) {
|
|
798
|
+
console.log('HTML 생성 실패');
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const htmlPath = this.getHtmlPath();
|
|
802
|
+
fs_1.default.writeFileSync(htmlPath, htmlContent, 'utf-8');
|
|
803
|
+
console.log(`✅ 공용 그래프 시각화 생성 완료!`);
|
|
804
|
+
console.log(` 파일: ${htmlPath}`);
|
|
805
|
+
console.log(` 에이전트: ${Object.keys(agentGraphs).length}개`);
|
|
806
|
+
console.log(` 전체 노드: ${totalNodes}개 | 전체 엣지: ${totalEdges}개`);
|
|
807
|
+
console.log(`\n브라우저에서 열기: open ${htmlPath}`);
|
|
808
|
+
}
|
|
809
|
+
open(agentId) {
|
|
810
|
+
const htmlPath = this.getHtmlPath();
|
|
811
|
+
if (!fs_1.default.existsSync(htmlPath)) {
|
|
812
|
+
console.log('mindmap.html이 없습니다. 먼저 html 명령을 실행하세요.');
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
const url = `file://${htmlPath}${agentId ? '?agent=' + agentId : ''}`;
|
|
816
|
+
try {
|
|
817
|
+
(0, child_process_1.execSync)(`osascript -e 'tell application "Google Chrome" to open location "${url}"'`);
|
|
818
|
+
console.log(`✅ Chrome에서 그래프 열기: ${agentId || '전체'}`);
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
try {
|
|
822
|
+
(0, child_process_1.execSync)(`osascript -e 'tell application "Safari" to open location "${url}"'`);
|
|
823
|
+
console.log(`✅ Safari에서 그래프 열기: ${agentId || '전체'}`);
|
|
824
|
+
}
|
|
825
|
+
catch {
|
|
826
|
+
(0, child_process_1.execSync)(`open "${url}"`);
|
|
827
|
+
console.log(`✅ 기본 브라우저에서 그래프 열기: ${agentId || '전체'}`);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
exports.MindmapEngine = MindmapEngine;
|
|
833
|
+
function normalizeFormatOption(format) {
|
|
834
|
+
if (!format || format === true)
|
|
835
|
+
return 'mermaid';
|
|
836
|
+
return String(format).toLowerCase();
|
|
837
|
+
}
|
|
838
|
+
//# sourceMappingURL=mindmap.js.map
|