total-recall-brain 3.41.4 → 3.41.5
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/bin/total-recall.mjs +3 -2
- package/docs/reference/cli-reference.md +18 -0
- package/frontend/dist/assets/{index-CRzjQLu7.js → index-WT3gs-x6.js} +24 -24
- package/frontend/dist/index.html +1 -1
- package/package.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +3114 -36
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +3114 -36
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +3121 -33
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +3122 -34
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +3165 -87
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +3121 -33
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +32 -12
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +294 -46
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +367 -41
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +190 -9
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +159 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +197 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/lint.mjs +153 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +261 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +151 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +114 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +96 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +899 -662
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/vendor/openwiki.mjs +8987 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3087 -71
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
- package/src/cli/compact.mjs +108 -0
- package/src/cli/context.mjs +52 -23
- package/src/cli/forget.mjs +2 -0
- package/src/cli/release.mjs +386 -0
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/context-compactor.mjs +143 -0
- package/src/core/preflight-gate.mjs +2 -2
- package/src/core/preflight-receipt.mjs +1 -1
- package/src/core/surface.mjs +1 -1
- package/src/server/invoke-durability.mjs +39 -0
- package/src/server/invoke-write-child.mjs +25 -0
- package/src/server/routes/invoke.mjs +7 -0
|
@@ -2,17 +2,25 @@
|
|
|
2
2
|
* OpenWiki — Knowledge Graph & Multi-Repo Documentation Wiki
|
|
3
3
|
* Bespoke Card Face: Deep Slate & Knowledge Amber Interface.
|
|
4
4
|
*
|
|
5
|
-
* Exposes live hub discovery, markdown
|
|
6
|
-
*
|
|
5
|
+
* Exposes live hub discovery, full markdown reading, evidence search across all vaults,
|
|
6
|
+
* interactive knowledge topology graph, targeted on-demand generation, fleet refresh,
|
|
7
|
+
* Total Recall auth secrets vault key management, and documentation link hygiene linting.
|
|
8
|
+
*
|
|
9
|
+
* Operates without mock data, zero templates, zero emojis, zero Winamp skeuomorphism.
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
12
|
export class OpenWikiCard extends HTMLElement {
|
|
10
13
|
static get screens() {
|
|
11
14
|
return [
|
|
12
15
|
{ id: 'hubs', label: 'Hubs' },
|
|
13
|
-
{ id: '
|
|
16
|
+
{ id: 'search', label: 'Search' },
|
|
17
|
+
{ id: 'graph', label: 'Graph' },
|
|
18
|
+
{ id: 'generate', label: 'Generate' },
|
|
14
19
|
{ id: 'refresh', label: 'Fleet Refresh' },
|
|
15
|
-
{ id: '
|
|
20
|
+
{ id: 'token', label: 'Auth Vault' },
|
|
21
|
+
{ id: 'lint', label: 'Health' },
|
|
22
|
+
{ id: 'upstream', label: 'Upstream Watcher' },
|
|
23
|
+
{ id: 'config', label: 'Configuration' }
|
|
16
24
|
];
|
|
17
25
|
}
|
|
18
26
|
|
|
@@ -23,7 +31,11 @@ export class OpenWikiCard extends HTMLElement {
|
|
|
23
31
|
constructor() {
|
|
24
32
|
super();
|
|
25
33
|
this.activeScreen = 'hubs';
|
|
34
|
+
this.scope = 'workspace'; // 'workspace' | 'all-vaults' | '<mesh-node>'
|
|
35
|
+
this.meshNodes = [];
|
|
26
36
|
this.hubs = [];
|
|
37
|
+
this.selectedHub = null;
|
|
38
|
+
this.selectedHubContent = null;
|
|
27
39
|
this.loading = false;
|
|
28
40
|
this.statusText = 'READY';
|
|
29
41
|
this.targetPath = 'openwiki';
|
|
@@ -32,15 +44,42 @@ export class OpenWikiCard extends HTMLElement {
|
|
|
32
44
|
this.errorMessage = null;
|
|
33
45
|
this.lastRefreshedAt = null;
|
|
34
46
|
this.upstream = null;
|
|
47
|
+
|
|
48
|
+
// Search state
|
|
49
|
+
this.searchQuery = '';
|
|
50
|
+
this.searchResults = [];
|
|
51
|
+
this.searchStats = null;
|
|
52
|
+
|
|
53
|
+
// Graph state
|
|
54
|
+
this.graphData = null;
|
|
55
|
+
this.graphFormat = 'svg'; // 'svg' | 'mermaid' | 'json'
|
|
56
|
+
|
|
57
|
+
// Token state
|
|
58
|
+
this.tokenStatus = {
|
|
59
|
+
openrouter: { key: 'OPENROUTER_API_KEY', configured: false, status: 'unknown' },
|
|
60
|
+
google: { key: 'GOOGLE_API_KEY', configured: false, status: 'unknown' }
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Lint state
|
|
64
|
+
this.lintData = null;
|
|
65
|
+
|
|
66
|
+
// Generator state
|
|
67
|
+
this.generateRepo = '';
|
|
68
|
+
this.generateLogs = [];
|
|
69
|
+
|
|
70
|
+
// Fleet refresh state
|
|
71
|
+
this.refreshReports = [];
|
|
72
|
+
this.refreshCounts = null;
|
|
73
|
+
|
|
74
|
+
// Configuration state
|
|
35
75
|
this.config = {
|
|
36
76
|
provider: 'openrouter',
|
|
37
|
-
model: 'deepseek/deepseek-
|
|
77
|
+
model: 'deepseek/deepseek-chat',
|
|
38
78
|
fallback_model: 'qwen/qwen-2.5-coder-32b-instruct',
|
|
39
79
|
max_tokens: 4096,
|
|
40
80
|
timeout_ms: 300000
|
|
41
81
|
};
|
|
42
|
-
|
|
43
|
-
this.refreshCounts = null;
|
|
82
|
+
|
|
44
83
|
this._boundOnScreenChange = this.handleExternalScreenChange.bind(this);
|
|
45
84
|
}
|
|
46
85
|
|
|
@@ -52,6 +91,7 @@ export class OpenWikiCard extends HTMLElement {
|
|
|
52
91
|
this.addEventListener('plugin-show-screen', this._boundOnScreenChange);
|
|
53
92
|
this.render();
|
|
54
93
|
this.refreshHubs();
|
|
94
|
+
this.loadTokenStatus();
|
|
55
95
|
this.loadConfig();
|
|
56
96
|
}
|
|
57
97
|
|
|
@@ -68,24 +108,43 @@ export class OpenWikiCard extends HTMLElement {
|
|
|
68
108
|
}
|
|
69
109
|
|
|
70
110
|
setScreen(id) {
|
|
71
|
-
if (this.activeScreen === id) return;
|
|
111
|
+
if (this.activeScreen === id && !this.selectedHub) return;
|
|
72
112
|
this.activeScreen = id;
|
|
113
|
+
this.selectedHub = null;
|
|
114
|
+
this.selectedHubContent = null;
|
|
73
115
|
this.errorMessage = null;
|
|
74
116
|
this.render();
|
|
117
|
+
|
|
75
118
|
if (typeof this.host?.screenChanged === 'function') {
|
|
76
119
|
try {
|
|
77
120
|
this.host.screenChanged({ screen: id });
|
|
78
121
|
} catch {
|
|
79
|
-
//
|
|
122
|
+
// optional host callback
|
|
80
123
|
}
|
|
81
124
|
}
|
|
82
|
-
|
|
125
|
+
|
|
126
|
+
if (id === 'graph' && !this.graphData) {
|
|
127
|
+
this.loadGraph();
|
|
128
|
+
} else if (id === 'token') {
|
|
129
|
+
this.loadTokenStatus();
|
|
130
|
+
} else if (id === 'lint' && !this.lintData) {
|
|
131
|
+
this.loadLint();
|
|
132
|
+
} else if (id === 'refresh' && (!this.refreshReports || this.refreshReports.length === 0)) {
|
|
83
133
|
this.loadRefreshReport();
|
|
84
134
|
} else if (id === 'config') {
|
|
85
135
|
this.loadConfig();
|
|
86
136
|
}
|
|
87
137
|
}
|
|
88
138
|
|
|
139
|
+
setScope(newScope) {
|
|
140
|
+
if (this.scope === newScope) return;
|
|
141
|
+
this.scope = newScope;
|
|
142
|
+
this.refreshHubs();
|
|
143
|
+
if (this.activeScreen === 'graph') this.loadGraph();
|
|
144
|
+
if (this.activeScreen === 'lint') this.loadLint();
|
|
145
|
+
if (this.activeScreen === 'search' && this.searchQuery) this.executeSearch(this.searchQuery);
|
|
146
|
+
}
|
|
147
|
+
|
|
89
148
|
async loadConfig() {
|
|
90
149
|
try {
|
|
91
150
|
if (this.host && typeof this.host.run === 'function') {
|
|
@@ -96,7 +155,7 @@ export class OpenWikiCard extends HTMLElement {
|
|
|
96
155
|
}
|
|
97
156
|
}
|
|
98
157
|
} catch {
|
|
99
|
-
// Retain active
|
|
158
|
+
// Retain active defaults
|
|
100
159
|
}
|
|
101
160
|
}
|
|
102
161
|
|
|
@@ -119,19 +178,32 @@ export class OpenWikiCard extends HTMLElement {
|
|
|
119
178
|
}
|
|
120
179
|
}
|
|
121
180
|
|
|
122
|
-
async
|
|
181
|
+
async loadTokenStatus() {
|
|
182
|
+
try {
|
|
183
|
+
if (this.host && typeof this.host.run === 'function') {
|
|
184
|
+
const res = await this.host.run('token', ['status', '--json']);
|
|
185
|
+
if (res?.tokens) {
|
|
186
|
+
this.tokenStatus = res.tokens;
|
|
187
|
+
this.render();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} catch {
|
|
191
|
+
// optional fallback
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async saveToken(provider, tokenValue) {
|
|
196
|
+
if (!tokenValue || !tokenValue.trim()) return;
|
|
123
197
|
this.loading = true;
|
|
124
198
|
this.errorMessage = null;
|
|
125
199
|
this.render();
|
|
126
200
|
try {
|
|
127
201
|
if (this.host && typeof this.host.run === 'function') {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.config = res.config;
|
|
131
|
-
}
|
|
202
|
+
await this.host.run('token', ['set', provider, tokenValue.trim(), '--json']);
|
|
203
|
+
await this.loadTokenStatus();
|
|
132
204
|
}
|
|
133
205
|
} catch (err) {
|
|
134
|
-
this.errorMessage = err.message ||
|
|
206
|
+
this.errorMessage = err.message || `Failed to save ${provider} token`;
|
|
135
207
|
} finally {
|
|
136
208
|
this.loading = false;
|
|
137
209
|
this.render();
|
|
@@ -145,7 +217,14 @@ export class OpenWikiCard extends HTMLElement {
|
|
|
145
217
|
|
|
146
218
|
try {
|
|
147
219
|
if (this.host && typeof this.host.run === 'function') {
|
|
148
|
-
const
|
|
220
|
+
const args = ['--json'];
|
|
221
|
+
if (this.scope === 'all-vaults') {
|
|
222
|
+
args.push('--all-vaults');
|
|
223
|
+
} else if (this.scope !== 'workspace') {
|
|
224
|
+
args.push(`--node=${this.scope}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const res = await this.host.run('hubs', args);
|
|
149
228
|
if (res && Array.isArray(res.hubs)) {
|
|
150
229
|
this.hubs = res.hubs;
|
|
151
230
|
this.statusText = 'SYNCHRONIZED';
|
|
@@ -176,6 +255,108 @@ export class OpenWikiCard extends HTMLElement {
|
|
|
176
255
|
}
|
|
177
256
|
}
|
|
178
257
|
|
|
258
|
+
async readHubDocument(hub) {
|
|
259
|
+
this.selectedHub = hub;
|
|
260
|
+
this.loading = true;
|
|
261
|
+
this.render();
|
|
262
|
+
try {
|
|
263
|
+
if (this.host && typeof this.host.readFile === 'function') {
|
|
264
|
+
this.selectedHubContent = await this.host.readFile(hub.path);
|
|
265
|
+
} else {
|
|
266
|
+
this.selectedHubContent = `# ${hub.title}\n\n*File:* \`${hub.path}\`\n\n*Lines:* ${hub.lines} | *Size:* ${hub.size_bytes} bytes\n\n*(Full live rendering available)*`;
|
|
267
|
+
}
|
|
268
|
+
} catch {
|
|
269
|
+
this.selectedHubContent = `# ${hub.title}\n\n*Unable to load full content from ${hub.path}*`;
|
|
270
|
+
} finally {
|
|
271
|
+
this.loading = false;
|
|
272
|
+
this.render();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async executeSearch(query) {
|
|
277
|
+
if (!query || !query.trim()) {
|
|
278
|
+
this.searchResults = [];
|
|
279
|
+
this.searchStats = null;
|
|
280
|
+
this.render();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
this.loading = true;
|
|
285
|
+
this.searchQuery = query.trim();
|
|
286
|
+
this.errorMessage = null;
|
|
287
|
+
this.render();
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
if (this.host && typeof this.host.run === 'function') {
|
|
291
|
+
const args = [this.searchQuery, '--json'];
|
|
292
|
+
if (this.scope === 'all-vaults') args.push('--all-vaults');
|
|
293
|
+
else if (this.scope !== 'workspace') args.push(`--node=${this.scope}`);
|
|
294
|
+
|
|
295
|
+
const res = await this.host.run('search', args);
|
|
296
|
+
if (res && Array.isArray(res.results)) {
|
|
297
|
+
this.searchResults = res.results;
|
|
298
|
+
this.searchStats = {
|
|
299
|
+
totalHubs: res.totalHubsScanned || 0,
|
|
300
|
+
count: res.count || 0,
|
|
301
|
+
scope: res.scope || this.scope
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} catch (err) {
|
|
306
|
+
this.errorMessage = err.message || 'Search execution failure';
|
|
307
|
+
this.searchResults = [];
|
|
308
|
+
} finally {
|
|
309
|
+
this.loading = false;
|
|
310
|
+
this.render();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async loadGraph() {
|
|
315
|
+
this.loading = true;
|
|
316
|
+
this.errorMessage = null;
|
|
317
|
+
this.render();
|
|
318
|
+
try {
|
|
319
|
+
if (this.host && typeof this.host.run === 'function') {
|
|
320
|
+
const args = ['--format=json', '--json'];
|
|
321
|
+
if (this.scope === 'all-vaults') args.push('--all-vaults');
|
|
322
|
+
else if (this.scope !== 'workspace') args.push(`--node=${this.scope}`);
|
|
323
|
+
|
|
324
|
+
const res = await this.host.run('graph', args);
|
|
325
|
+
if (res?.nodes) {
|
|
326
|
+
this.graphData = res;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
} catch (err) {
|
|
330
|
+
this.errorMessage = err.message || 'Graph build failure';
|
|
331
|
+
} finally {
|
|
332
|
+
this.loading = false;
|
|
333
|
+
this.render();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async loadLint() {
|
|
338
|
+
this.loading = true;
|
|
339
|
+
this.errorMessage = null;
|
|
340
|
+
this.render();
|
|
341
|
+
try {
|
|
342
|
+
if (this.host && typeof this.host.run === 'function') {
|
|
343
|
+
const args = ['--json'];
|
|
344
|
+
if (this.scope === 'all-vaults') args.push('--all-vaults');
|
|
345
|
+
else if (this.scope !== 'workspace') args.push(`--node=${this.scope}`);
|
|
346
|
+
|
|
347
|
+
const res = await this.host.run('lint', args);
|
|
348
|
+
if (res) {
|
|
349
|
+
this.lintData = res;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
} catch (err) {
|
|
353
|
+
this.errorMessage = err.message || 'Lint execution failure';
|
|
354
|
+
} finally {
|
|
355
|
+
this.loading = false;
|
|
356
|
+
this.render();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
179
360
|
async loadRefreshReport() {
|
|
180
361
|
this.loading = true;
|
|
181
362
|
this.errorMessage = null;
|
|
@@ -199,25 +380,16 @@ export class OpenWikiCard extends HTMLElement {
|
|
|
199
380
|
async executeFleetRefresh() {
|
|
200
381
|
if (this.loading) return;
|
|
201
382
|
this.loading = true;
|
|
202
|
-
this.executionResult = null;
|
|
203
383
|
this.errorMessage = null;
|
|
204
384
|
this.render();
|
|
205
385
|
|
|
206
386
|
try {
|
|
207
387
|
if (this.host && typeof this.host.run === 'function') {
|
|
208
388
|
const res = await this.host.run('refresh', ['--apply', '--json']);
|
|
209
|
-
this.executionResult = {
|
|
210
|
-
action: 'fleet-refresh',
|
|
211
|
-
target: 'all-registered-projects',
|
|
212
|
-
details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
|
|
213
|
-
timestamp: new Date().toLocaleTimeString()
|
|
214
|
-
};
|
|
215
389
|
if (res?.reports) {
|
|
216
390
|
this.refreshReports = res.reports;
|
|
217
391
|
this.refreshCounts = res.counts;
|
|
218
392
|
}
|
|
219
|
-
} else {
|
|
220
|
-
throw new Error('Host execution context unavailable');
|
|
221
393
|
}
|
|
222
394
|
} catch (err) {
|
|
223
395
|
this.errorMessage = err.message || 'Fleet refresh execution failure';
|
|
@@ -227,57 +399,49 @@ export class OpenWikiCard extends HTMLElement {
|
|
|
227
399
|
}
|
|
228
400
|
}
|
|
229
401
|
|
|
230
|
-
async
|
|
402
|
+
async executeGenerate() {
|
|
231
403
|
if (this.loading) return;
|
|
232
404
|
this.loading = true;
|
|
233
|
-
this.executionResult = null;
|
|
234
405
|
this.errorMessage = null;
|
|
406
|
+
this.generateLogs = [`Dispatching on-demand generator for '${this.generateRepo || 'current repository'}'...`];
|
|
235
407
|
this.render();
|
|
236
408
|
|
|
237
|
-
const args = [this.targetPath || 'openwiki'];
|
|
238
|
-
if (this.dryRun) args.push('--dry-run');
|
|
239
|
-
|
|
240
409
|
try {
|
|
241
410
|
if (this.host && typeof this.host.run === 'function') {
|
|
242
|
-
const
|
|
243
|
-
this.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
throw new Error('Host execution context unavailable');
|
|
411
|
+
const args = ['--json'];
|
|
412
|
+
if (this.generateRepo) args.push(`--repo=${this.generateRepo}`);
|
|
413
|
+
if (this.scope !== 'workspace' && this.scope !== 'all-vaults') {
|
|
414
|
+
args.push(`--node=${this.scope}`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const res = await this.host.run('generate', args);
|
|
418
|
+
this.generateLogs.push(res?.ok ? `Generation completed successfully in ${Math.round((res.durationMs || 0)/1000)}s` : 'Generation finished with warnings');
|
|
419
|
+
if (res?.outputDir) this.generateLogs.push(`Output: ${res.outputDir}`);
|
|
252
420
|
}
|
|
253
421
|
} catch (err) {
|
|
254
|
-
this.errorMessage = err.message || '
|
|
422
|
+
this.errorMessage = err.message || 'Generation execution failure';
|
|
423
|
+
this.generateLogs.push(`Error: ${this.errorMessage}`);
|
|
255
424
|
} finally {
|
|
256
425
|
this.loading = false;
|
|
257
426
|
this.refreshHubs();
|
|
258
427
|
}
|
|
259
428
|
}
|
|
260
429
|
|
|
261
|
-
async
|
|
430
|
+
async executeIngest() {
|
|
262
431
|
if (this.loading) return;
|
|
263
432
|
this.loading = true;
|
|
264
|
-
this.executionResult = null;
|
|
265
433
|
this.errorMessage = null;
|
|
266
434
|
this.render();
|
|
267
435
|
|
|
436
|
+
const args = [this.targetPath || 'openwiki'];
|
|
437
|
+
if (this.dryRun) args.push('--dry-run');
|
|
438
|
+
|
|
268
439
|
try {
|
|
269
440
|
if (this.host && typeof this.host.run === 'function') {
|
|
270
|
-
|
|
271
|
-
this.executionResult = {
|
|
272
|
-
action: 'bootstrap',
|
|
273
|
-
details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
|
|
274
|
-
timestamp: new Date().toLocaleTimeString()
|
|
275
|
-
};
|
|
276
|
-
} else {
|
|
277
|
-
throw new Error('Host execution context unavailable');
|
|
441
|
+
await this.host.run('ingest', args);
|
|
278
442
|
}
|
|
279
443
|
} catch (err) {
|
|
280
|
-
this.errorMessage = err.message || '
|
|
444
|
+
this.errorMessage = err.message || 'Ingestion cycle failure';
|
|
281
445
|
} finally {
|
|
282
446
|
this.loading = false;
|
|
283
447
|
this.refreshHubs();
|
|
@@ -287,26 +451,18 @@ export class OpenWikiCard extends HTMLElement {
|
|
|
287
451
|
async executeWatch() {
|
|
288
452
|
if (this.loading) return;
|
|
289
453
|
this.loading = true;
|
|
290
|
-
this.executionResult = null;
|
|
291
454
|
this.errorMessage = null;
|
|
292
455
|
this.render();
|
|
293
456
|
|
|
294
457
|
try {
|
|
295
458
|
if (this.host && typeof this.host.run === 'function') {
|
|
296
459
|
const res = await this.host.run('watch', ['--json']);
|
|
297
|
-
this.executionResult = {
|
|
298
|
-
action: 'watch',
|
|
299
|
-
details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
|
|
300
|
-
timestamp: new Date().toLocaleTimeString()
|
|
301
|
-
};
|
|
302
460
|
if (res?.status) {
|
|
303
461
|
this.upstream = {
|
|
304
462
|
recordedVersion: res.status.currentVersion,
|
|
305
463
|
latestVersion: res.status.latestVersion
|
|
306
464
|
};
|
|
307
465
|
}
|
|
308
|
-
} else {
|
|
309
|
-
throw new Error('Host execution context unavailable');
|
|
310
466
|
}
|
|
311
467
|
} catch (err) {
|
|
312
468
|
this.errorMessage = err.message || 'Upstream surveillance failure';
|
|
@@ -316,61 +472,35 @@ export class OpenWikiCard extends HTMLElement {
|
|
|
316
472
|
}
|
|
317
473
|
}
|
|
318
474
|
|
|
319
|
-
async executePropagate() {
|
|
320
|
-
if (this.loading) return;
|
|
321
|
-
this.loading = true;
|
|
322
|
-
this.executionResult = null;
|
|
323
|
-
this.errorMessage = null;
|
|
324
|
-
this.render();
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
if (this.host && typeof this.host.run === 'function') {
|
|
328
|
-
const res = await this.host.run('propagate', ['--json']);
|
|
329
|
-
this.executionResult = {
|
|
330
|
-
action: 'propagate',
|
|
331
|
-
details: res && typeof res === 'object' ? res : { message: String(res || 'Completed') },
|
|
332
|
-
timestamp: new Date().toLocaleTimeString()
|
|
333
|
-
};
|
|
334
|
-
} else {
|
|
335
|
-
throw new Error('Host execution context unavailable');
|
|
336
|
-
}
|
|
337
|
-
} catch (err) {
|
|
338
|
-
this.errorMessage = err.message || 'Fleet propagation failure';
|
|
339
|
-
} finally {
|
|
340
|
-
this.loading = false;
|
|
341
|
-
this.refreshHubs();
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
475
|
render() {
|
|
346
476
|
if (!this.shadowRoot) return;
|
|
347
477
|
|
|
348
|
-
const countText = this.hubs.length ? `${this.hubs.length} Hubs` : '
|
|
478
|
+
const countText = this.hubs.length ? `${this.hubs.length} Hubs` : '0 Hubs';
|
|
349
479
|
const totalLines = this.hubs.reduce((acc, h) => acc + (h.lines || 0), 0);
|
|
350
|
-
const linesText =
|
|
351
|
-
const upstreamText = this.upstream?.latestVersion ? `v${this.upstream.latestVersion}` : 'Tracking';
|
|
480
|
+
const linesText = `${totalLines.toLocaleString()} Lines`;
|
|
352
481
|
|
|
353
482
|
this.shadowRoot.innerHTML = `
|
|
354
483
|
<style>
|
|
355
484
|
:host {
|
|
356
485
|
display: block;
|
|
357
486
|
box-sizing: border-box;
|
|
358
|
-
--
|
|
359
|
-
--
|
|
360
|
-
--
|
|
361
|
-
--
|
|
362
|
-
--
|
|
363
|
-
--
|
|
364
|
-
--
|
|
365
|
-
--
|
|
366
|
-
--
|
|
367
|
-
--
|
|
368
|
-
background: var(--
|
|
369
|
-
color: var(--text-
|
|
370
|
-
border: 1px solid var(--border
|
|
371
|
-
border-radius:
|
|
487
|
+
--bg-base: #090d16;
|
|
488
|
+
--bg-surface: #111827;
|
|
489
|
+
--bg-elevated: #1f2937;
|
|
490
|
+
--border-subtle: #374151;
|
|
491
|
+
--border-focus: #f59e0b;
|
|
492
|
+
--text-main: #f9fafb;
|
|
493
|
+
--text-muted: #9ca3af;
|
|
494
|
+
--accent-amber: #f59e0b;
|
|
495
|
+
--accent-green: #10b981;
|
|
496
|
+
--accent-red: #ef4444;
|
|
497
|
+
background: var(--bg-base);
|
|
498
|
+
color: var(--text-main);
|
|
499
|
+
border: 1px solid var(--border-subtle);
|
|
500
|
+
border-radius: 10px;
|
|
372
501
|
padding: 16px;
|
|
373
|
-
font-family:
|
|
502
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
503
|
+
font-size: 13px;
|
|
374
504
|
}
|
|
375
505
|
|
|
376
506
|
*, *::before, *::after {
|
|
@@ -381,784 +511,891 @@ export class OpenWikiCard extends HTMLElement {
|
|
|
381
511
|
display: flex;
|
|
382
512
|
align-items: center;
|
|
383
513
|
justify-content: space-between;
|
|
384
|
-
gap: 12px;
|
|
385
|
-
margin-bottom: 12px;
|
|
386
|
-
border-bottom: 1px solid var(--border, var(--face-border));
|
|
387
514
|
padding-bottom: 12px;
|
|
515
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
516
|
+
gap: 12px;
|
|
388
517
|
}
|
|
389
518
|
|
|
390
519
|
.brand-cluster {
|
|
391
520
|
display: flex;
|
|
392
521
|
align-items: center;
|
|
393
|
-
gap:
|
|
522
|
+
gap: 10px;
|
|
394
523
|
}
|
|
395
524
|
|
|
396
|
-
.
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
525
|
+
.crest-svg {
|
|
526
|
+
width: 24px;
|
|
527
|
+
height: 24px;
|
|
528
|
+
fill: var(--accent-amber);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.brand-title {
|
|
401
532
|
font-weight: 700;
|
|
533
|
+
font-size: 15px;
|
|
402
534
|
letter-spacing: 0.05em;
|
|
403
|
-
color: var(--
|
|
404
|
-
|
|
405
|
-
padding: 4px 8px;
|
|
406
|
-
border-radius: 4px;
|
|
535
|
+
color: var(--text-main);
|
|
536
|
+
text-transform: uppercase;
|
|
407
537
|
}
|
|
408
538
|
|
|
409
|
-
.
|
|
410
|
-
font-
|
|
539
|
+
.wordmark-badge {
|
|
540
|
+
font-family: ui-monospace, monospace;
|
|
541
|
+
font-size: 10px;
|
|
542
|
+
background: rgba(245, 158, 11, 0.15);
|
|
543
|
+
color: var(--accent-amber);
|
|
544
|
+
border: 1px solid rgba(245, 158, 11, 0.3);
|
|
545
|
+
padding: 2px 6px;
|
|
546
|
+
border-radius: 4px;
|
|
411
547
|
font-weight: 600;
|
|
412
|
-
color: var(--text-primary, var(--face-text));
|
|
413
548
|
}
|
|
414
549
|
|
|
415
|
-
.
|
|
416
|
-
display:
|
|
550
|
+
.scope-control {
|
|
551
|
+
display: flex;
|
|
417
552
|
align-items: center;
|
|
418
|
-
|
|
419
|
-
border-radius: 12px;
|
|
420
|
-
font-size: 11px;
|
|
421
|
-
font-weight: 700;
|
|
422
|
-
letter-spacing: 0.04em;
|
|
423
|
-
text-transform: uppercase;
|
|
424
|
-
background: var(--face-surface-subtle);
|
|
425
|
-
color: var(--face-text-muted);
|
|
426
|
-
border: 1px solid var(--border, var(--face-border));
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
.status-pill.ok {
|
|
430
|
-
color: var(--face-ok);
|
|
431
|
-
border-color: var(--face-ok);
|
|
553
|
+
gap: 6px;
|
|
432
554
|
}
|
|
433
555
|
|
|
434
|
-
.
|
|
435
|
-
|
|
436
|
-
|
|
556
|
+
.scope-select {
|
|
557
|
+
background: var(--bg-surface);
|
|
558
|
+
color: var(--text-main);
|
|
559
|
+
border: 1px solid var(--border-subtle);
|
|
560
|
+
border-radius: 6px;
|
|
561
|
+
padding: 4px 8px;
|
|
562
|
+
font-size: 12px;
|
|
563
|
+
cursor: pointer;
|
|
437
564
|
}
|
|
438
565
|
|
|
439
|
-
|
|
566
|
+
/* Screen Navigation */
|
|
567
|
+
.screen-nav {
|
|
440
568
|
display: flex;
|
|
441
|
-
gap:
|
|
442
|
-
margin
|
|
443
|
-
border-bottom: 1px solid var(--border, var(--face-border));
|
|
444
|
-
padding-bottom: 8px;
|
|
569
|
+
gap: 4px;
|
|
570
|
+
margin: 12px 0;
|
|
445
571
|
overflow-x: auto;
|
|
572
|
+
padding-bottom: 4px;
|
|
446
573
|
}
|
|
447
574
|
|
|
448
|
-
.
|
|
449
|
-
|
|
450
|
-
|
|
575
|
+
.nav-btn {
|
|
576
|
+
background: transparent;
|
|
577
|
+
color: var(--text-muted);
|
|
578
|
+
border: 1px solid transparent;
|
|
579
|
+
border-radius: 6px;
|
|
580
|
+
padding: 6px 12px;
|
|
451
581
|
font-size: 12px;
|
|
452
582
|
font-weight: 600;
|
|
453
|
-
border-radius: 6px;
|
|
454
|
-
border: 1px solid transparent;
|
|
455
|
-
background: transparent;
|
|
456
|
-
color: var(--face-text-muted);
|
|
457
583
|
cursor: pointer;
|
|
458
|
-
transition: all 0.15s ease;
|
|
459
|
-
display: inline-flex;
|
|
460
|
-
align-items: center;
|
|
461
584
|
white-space: nowrap;
|
|
585
|
+
min-height: 44px;
|
|
586
|
+
transition: all 0.15s ease;
|
|
462
587
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
background: var(--face-surface-subtle);
|
|
588
|
+
.nav-btn:hover {
|
|
589
|
+
color: var(--text-main);
|
|
590
|
+
background: var(--bg-surface);
|
|
467
591
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
border-color: var(--face-accent);
|
|
592
|
+
.nav-btn.active {
|
|
593
|
+
color: var(--accent-amber);
|
|
594
|
+
background: rgba(245, 158, 11, 0.12);
|
|
595
|
+
border-color: rgba(245, 158, 11, 0.4);
|
|
473
596
|
}
|
|
474
597
|
|
|
475
|
-
|
|
598
|
+
/* Metrics Deck */
|
|
599
|
+
.metric-deck {
|
|
476
600
|
display: grid;
|
|
477
601
|
grid-template-columns: repeat(3, 1fr);
|
|
478
602
|
gap: 8px;
|
|
479
|
-
margin-bottom:
|
|
603
|
+
margin-bottom: 14px;
|
|
480
604
|
}
|
|
481
605
|
|
|
482
|
-
.
|
|
483
|
-
background: var(--
|
|
484
|
-
border: 1px solid var(--border
|
|
606
|
+
.metric-cell {
|
|
607
|
+
background: var(--bg-surface);
|
|
608
|
+
border: 1px solid var(--border-subtle);
|
|
485
609
|
border-radius: 6px;
|
|
486
|
-
padding: 10px;
|
|
610
|
+
padding: 8px 10px;
|
|
487
611
|
}
|
|
488
|
-
|
|
489
|
-
.stat-label {
|
|
612
|
+
.metric-label {
|
|
490
613
|
font-size: 11px;
|
|
614
|
+
color: var(--text-muted);
|
|
491
615
|
text-transform: uppercase;
|
|
492
|
-
letter-spacing: 0.04em;
|
|
493
|
-
color: var(--face-text-muted);
|
|
494
|
-
margin-bottom: 4px;
|
|
495
616
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
font-size: 16px;
|
|
617
|
+
.metric-val {
|
|
618
|
+
font-size: 15px;
|
|
499
619
|
font-weight: 700;
|
|
500
|
-
|
|
501
|
-
|
|
620
|
+
margin-top: 2px;
|
|
621
|
+
color: var(--text-main);
|
|
502
622
|
}
|
|
503
623
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
.section-title {
|
|
512
|
-
font-size: 12px;
|
|
513
|
-
font-weight: 700;
|
|
514
|
-
text-transform: uppercase;
|
|
515
|
-
letter-spacing: 0.05em;
|
|
516
|
-
color: var(--face-accent);
|
|
624
|
+
/* Content Deck */
|
|
625
|
+
.content-view {
|
|
626
|
+
background: var(--bg-surface);
|
|
627
|
+
border: 1px solid var(--border-subtle);
|
|
628
|
+
border-radius: 8px;
|
|
629
|
+
padding: 14px;
|
|
630
|
+
min-height: 260px;
|
|
517
631
|
}
|
|
518
632
|
|
|
519
633
|
.hub-list {
|
|
520
634
|
display: flex;
|
|
521
635
|
flex-direction: column;
|
|
522
636
|
gap: 6px;
|
|
523
|
-
margin-bottom: 16px;
|
|
524
|
-
max-height: 180px;
|
|
525
|
-
overflow-y: auto;
|
|
526
637
|
}
|
|
527
638
|
|
|
528
639
|
.hub-item {
|
|
529
640
|
display: flex;
|
|
530
641
|
align-items: center;
|
|
531
642
|
justify-content: space-between;
|
|
532
|
-
|
|
533
|
-
|
|
643
|
+
padding: 8px 10px;
|
|
644
|
+
background: var(--bg-elevated);
|
|
645
|
+
border: 1px solid var(--border-subtle);
|
|
534
646
|
border-radius: 6px;
|
|
535
|
-
|
|
536
|
-
|
|
647
|
+
cursor: pointer;
|
|
648
|
+
min-height: 44px;
|
|
649
|
+
transition: border-color 0.15s;
|
|
650
|
+
}
|
|
651
|
+
.hub-item:hover {
|
|
652
|
+
border-color: var(--accent-amber);
|
|
537
653
|
}
|
|
538
|
-
|
|
539
654
|
.hub-title {
|
|
540
655
|
font-weight: 600;
|
|
541
|
-
color: var(--text-
|
|
656
|
+
color: var(--text-main);
|
|
542
657
|
}
|
|
543
|
-
|
|
544
|
-
|
|
658
|
+
.repo-pill {
|
|
659
|
+
background: rgba(255, 255, 255, 0.08);
|
|
660
|
+
padding: 2px 6px;
|
|
661
|
+
border-radius: 4px;
|
|
545
662
|
font-family: ui-monospace, monospace;
|
|
546
663
|
font-size: 11px;
|
|
547
|
-
color: var(--face-text-muted);
|
|
548
664
|
}
|
|
549
665
|
|
|
550
|
-
|
|
666
|
+
/* Reader */
|
|
667
|
+
.reader-header {
|
|
551
668
|
display: flex;
|
|
552
669
|
align-items: center;
|
|
553
|
-
justify-content:
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
border: 1px dashed var(--border, var(--face-border));
|
|
558
|
-
border-radius: 6px;
|
|
559
|
-
font-size: 13px;
|
|
670
|
+
justify-content: space-between;
|
|
671
|
+
margin-bottom: 12px;
|
|
672
|
+
padding-bottom: 8px;
|
|
673
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
560
674
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
border: 1px solid var(--border
|
|
675
|
+
.back-btn {
|
|
676
|
+
background: var(--bg-elevated);
|
|
677
|
+
color: var(--accent-amber);
|
|
678
|
+
border: 1px solid var(--border-subtle);
|
|
565
679
|
border-radius: 6px;
|
|
566
|
-
padding:
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
.config-deck {
|
|
571
|
-
background: var(--face-surface);
|
|
572
|
-
border: 1px solid var(--border, var(--face-border));
|
|
573
|
-
border-radius: 6px;
|
|
574
|
-
padding: 14px;
|
|
575
|
-
margin-bottom: 16px;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
.config-grid {
|
|
579
|
-
display: grid;
|
|
580
|
-
grid-template-columns: 1fr 1fr;
|
|
581
|
-
gap: 12px;
|
|
582
|
-
margin-bottom: 14px;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
.field-group {
|
|
586
|
-
display: flex;
|
|
587
|
-
flex-direction: column;
|
|
588
|
-
gap: 4px;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
.field-group.full-width {
|
|
592
|
-
grid-column: 1 / -1;
|
|
680
|
+
padding: 4px 10px;
|
|
681
|
+
font-weight: 600;
|
|
682
|
+
cursor: pointer;
|
|
683
|
+
min-height: 44px;
|
|
593
684
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
685
|
+
.markdown-body {
|
|
686
|
+
font-family: Georgia, Cambria, serif;
|
|
687
|
+
line-height: 1.6;
|
|
688
|
+
color: #e5e7eb;
|
|
689
|
+
max-height: 380px;
|
|
690
|
+
overflow-y: auto;
|
|
691
|
+
white-space: pre-wrap;
|
|
601
692
|
}
|
|
602
693
|
|
|
603
|
-
|
|
694
|
+
/* Search */
|
|
695
|
+
.search-box {
|
|
604
696
|
display: flex;
|
|
605
697
|
gap: 8px;
|
|
606
698
|
margin-bottom: 12px;
|
|
607
699
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
border: 1px solid var(--border, var(--face-border));
|
|
700
|
+
.search-input {
|
|
701
|
+
flex: 1;
|
|
702
|
+
background: var(--bg-elevated);
|
|
703
|
+
border: 1px solid var(--border-subtle);
|
|
613
704
|
border-radius: 6px;
|
|
614
705
|
padding: 8px 12px;
|
|
615
|
-
color: var(--text-
|
|
616
|
-
font-family: ui-monospace, monospace;
|
|
706
|
+
color: var(--text-main);
|
|
617
707
|
font-size: 13px;
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
.path-input:focus, .text-input:focus, .select-input:focus {
|
|
622
|
-
outline: none;
|
|
623
|
-
border-color: var(--face-accent);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
.presets-bar {
|
|
627
|
-
display: flex;
|
|
628
|
-
gap: 6px;
|
|
629
|
-
flex-wrap: wrap;
|
|
630
|
-
margin-bottom: 14px;
|
|
708
|
+
min-height: 44px;
|
|
631
709
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
border:
|
|
638
|
-
|
|
639
|
-
font-size: 11px;
|
|
640
|
-
font-family: ui-monospace, monospace;
|
|
641
|
-
color: var(--face-text);
|
|
710
|
+
.action-btn {
|
|
711
|
+
background: var(--accent-amber);
|
|
712
|
+
color: #000;
|
|
713
|
+
font-weight: 700;
|
|
714
|
+
border: none;
|
|
715
|
+
border-radius: 6px;
|
|
716
|
+
padding: 8px 14px;
|
|
642
717
|
cursor: pointer;
|
|
643
|
-
|
|
718
|
+
min-height: 44px;
|
|
644
719
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
border-color: var(--face-accent);
|
|
648
|
-
color: var(--face-accent);
|
|
720
|
+
.action-btn:hover {
|
|
721
|
+
background: #d97706;
|
|
649
722
|
}
|
|
650
723
|
|
|
651
|
-
.
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
justify-content: space-between;
|
|
655
|
-
background: #022c22;
|
|
656
|
-
border: 1px solid var(--face-ok);
|
|
724
|
+
.result-item {
|
|
725
|
+
background: var(--bg-elevated);
|
|
726
|
+
border: 1px solid var(--border-subtle);
|
|
657
727
|
border-radius: 6px;
|
|
658
|
-
padding: 10px
|
|
659
|
-
margin-bottom:
|
|
660
|
-
font-size: 12px;
|
|
661
|
-
color: #a7f3d0;
|
|
728
|
+
padding: 10px;
|
|
729
|
+
margin-bottom: 8px;
|
|
662
730
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
gap: 8px;
|
|
668
|
-
min-height: 44px;
|
|
669
|
-
font-size: 13px;
|
|
670
|
-
color: var(--text-primary, var(--face-text));
|
|
671
|
-
cursor: pointer;
|
|
672
|
-
user-select: none;
|
|
673
|
-
padding: 0 4px;
|
|
731
|
+
.result-heading {
|
|
732
|
+
font-weight: 600;
|
|
733
|
+
color: var(--accent-amber);
|
|
734
|
+
margin-bottom: 4px;
|
|
674
735
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
736
|
+
.result-snippet {
|
|
737
|
+
font-style: italic;
|
|
738
|
+
color: #d1d5db;
|
|
739
|
+
font-size: 12px;
|
|
740
|
+
line-height: 1.4;
|
|
680
741
|
}
|
|
681
742
|
|
|
682
|
-
|
|
683
|
-
|
|
743
|
+
/* Graph SVG */
|
|
744
|
+
.graph-canvas-wrap {
|
|
745
|
+
width: 100%;
|
|
746
|
+
min-height: 280px;
|
|
747
|
+
background: #0b0f19;
|
|
748
|
+
border-radius: 6px;
|
|
749
|
+
border: 1px solid var(--border-subtle);
|
|
750
|
+
display: flex;
|
|
684
751
|
align-items: center;
|
|
685
752
|
justify-content: center;
|
|
686
|
-
|
|
687
|
-
padding:
|
|
688
|
-
font-size: 12px;
|
|
689
|
-
font-weight: 600;
|
|
690
|
-
border-radius: 6px;
|
|
691
|
-
border: 1px solid transparent;
|
|
692
|
-
cursor: pointer;
|
|
693
|
-
transition: background-color 0.15s ease, border-color 0.15s ease;
|
|
694
|
-
text-align: center;
|
|
753
|
+
overflow: auto;
|
|
754
|
+
padding: 10px;
|
|
695
755
|
}
|
|
696
756
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
757
|
+
/* Token Screen */
|
|
758
|
+
.token-row {
|
|
759
|
+
display: flex;
|
|
760
|
+
align-items: center;
|
|
761
|
+
justify-content: space-between;
|
|
762
|
+
background: var(--bg-elevated);
|
|
763
|
+
border: 1px solid var(--border-subtle);
|
|
764
|
+
border-radius: 6px;
|
|
765
|
+
padding: 10px 12px;
|
|
766
|
+
margin-bottom: 8px;
|
|
767
|
+
min-height: 44px;
|
|
700
768
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
769
|
+
.token-key {
|
|
770
|
+
font-family: ui-monospace, monospace;
|
|
771
|
+
font-weight: 600;
|
|
704
772
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
border-
|
|
709
|
-
|
|
773
|
+
.token-badge {
|
|
774
|
+
font-size: 11px;
|
|
775
|
+
padding: 2px 8px;
|
|
776
|
+
border-radius: 4px;
|
|
777
|
+
font-weight: 600;
|
|
710
778
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
779
|
+
.token-badge.ok {
|
|
780
|
+
background: rgba(16, 185, 129, 0.2);
|
|
781
|
+
color: var(--accent-green);
|
|
782
|
+
border: 1px solid var(--accent-green);
|
|
714
783
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
784
|
+
.token-badge.missing {
|
|
785
|
+
background: rgba(239, 68, 68, 0.2);
|
|
786
|
+
color: var(--accent-red);
|
|
787
|
+
border: 1px solid var(--accent-red);
|
|
719
788
|
}
|
|
720
789
|
|
|
721
|
-
.
|
|
722
|
-
background:
|
|
723
|
-
border: 1px solid
|
|
790
|
+
.auth-notice-card {
|
|
791
|
+
background: rgba(245, 158, 11, 0.08);
|
|
792
|
+
border: 1px solid rgba(245, 158, 11, 0.3);
|
|
724
793
|
border-radius: 6px;
|
|
725
|
-
padding: 12px;
|
|
726
|
-
|
|
794
|
+
padding: 10px 12px;
|
|
795
|
+
margin-bottom: 12px;
|
|
727
796
|
font-size: 12px;
|
|
728
|
-
color:
|
|
729
|
-
margin-top: 12px;
|
|
797
|
+
color: #fde68a;
|
|
730
798
|
}
|
|
731
799
|
|
|
732
|
-
.
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
800
|
+
.config-deck {
|
|
801
|
+
display: flex;
|
|
802
|
+
flex-direction: column;
|
|
803
|
+
gap: 12px;
|
|
736
804
|
}
|
|
737
805
|
|
|
738
|
-
.
|
|
739
|
-
background: var(--
|
|
740
|
-
border: 1px solid var(--
|
|
806
|
+
.ingest-deck {
|
|
807
|
+
background: var(--bg-elevated);
|
|
808
|
+
border: 1px solid var(--border-subtle);
|
|
741
809
|
border-radius: 6px;
|
|
742
810
|
padding: 12px;
|
|
743
|
-
|
|
744
|
-
font-size: 12px;
|
|
745
|
-
margin-bottom: 12px;
|
|
811
|
+
margin-top: 14px;
|
|
746
812
|
}
|
|
747
813
|
|
|
748
|
-
.
|
|
749
|
-
display: inline-
|
|
750
|
-
|
|
751
|
-
|
|
814
|
+
.preset-chip {
|
|
815
|
+
display: inline-block;
|
|
816
|
+
background: var(--bg-elevated);
|
|
817
|
+
border: 1px solid var(--border-subtle);
|
|
818
|
+
padding: 4px 10px;
|
|
752
819
|
border-radius: 4px;
|
|
753
|
-
font-size:
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
.repo-status-badge.current {
|
|
760
|
-
background: #064e3b;
|
|
761
|
-
color: #6ee7b7;
|
|
820
|
+
font-size: 11px;
|
|
821
|
+
cursor: pointer;
|
|
822
|
+
margin-right: 6px;
|
|
823
|
+
margin-bottom: 6px;
|
|
762
824
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
background: #78350f;
|
|
766
|
-
color: #fde68a;
|
|
825
|
+
.preset-chip:hover {
|
|
826
|
+
border-color: var(--accent-amber);
|
|
767
827
|
}
|
|
768
828
|
|
|
769
|
-
.
|
|
770
|
-
background:
|
|
771
|
-
|
|
829
|
+
.error-banner {
|
|
830
|
+
background: rgba(239, 68, 68, 0.15);
|
|
831
|
+
border: 1px solid var(--accent-red);
|
|
832
|
+
color: #fca5a5;
|
|
833
|
+
padding: 8px 12px;
|
|
834
|
+
border-radius: 6px;
|
|
835
|
+
margin-bottom: 10px;
|
|
772
836
|
}
|
|
773
837
|
|
|
774
838
|
@media (prefers-reduced-motion: reduce) {
|
|
775
|
-
|
|
776
|
-
animation
|
|
777
|
-
|
|
778
|
-
transition-duration: 0.01ms !important;
|
|
779
|
-
scroll-behavior: auto !important;
|
|
839
|
+
* {
|
|
840
|
+
animation: none !important;
|
|
841
|
+
transition: none !important;
|
|
780
842
|
}
|
|
781
843
|
}
|
|
782
844
|
</style>
|
|
783
845
|
|
|
784
846
|
<div class="header-bar">
|
|
785
847
|
<div class="brand-cluster">
|
|
786
|
-
<
|
|
787
|
-
|
|
848
|
+
<svg class="crest-svg" viewBox="0 0 24 24">
|
|
849
|
+
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
|
|
850
|
+
</svg>
|
|
851
|
+
<span class="brand-title">OpenWiki</span>
|
|
852
|
+
<span class="wordmark-badge">MESH KNOWLEDGE</span>
|
|
853
|
+
</div>
|
|
854
|
+
|
|
855
|
+
<div class="scope-control">
|
|
856
|
+
<span style="font-size: 11px; color: var(--text-muted);">Scope:</span>
|
|
857
|
+
<select class="scope-select" id="scope-select">
|
|
858
|
+
<option value="workspace" ${this.scope === 'workspace' ? 'selected' : ''}>Current Workspace</option>
|
|
859
|
+
<option value="all-vaults" ${this.scope === 'all-vaults' ? 'selected' : ''}>All Vaults (Fleet)</option>
|
|
860
|
+
</select>
|
|
788
861
|
</div>
|
|
789
|
-
<span class="status-pill ${this.statusText === 'SYNCHRONIZED' ? 'ok' : this.statusText === 'ERROR' ? 'danger' : ''}">
|
|
790
|
-
${this.statusText}
|
|
791
|
-
</span>
|
|
792
862
|
</div>
|
|
793
863
|
|
|
794
|
-
<nav class="screen-
|
|
795
|
-
${OpenWikiCard.screens
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
${screen.label}
|
|
864
|
+
<nav class="screen-nav">
|
|
865
|
+
${OpenWikiCard.screens
|
|
866
|
+
.map(
|
|
867
|
+
(s) => `
|
|
868
|
+
<button class="nav-btn ${this.activeScreen === s.id ? 'active' : ''}" data-screen="${s.id}">
|
|
869
|
+
${s.label}
|
|
801
870
|
</button>
|
|
802
|
-
`
|
|
871
|
+
`
|
|
872
|
+
)
|
|
873
|
+
.join('')}
|
|
803
874
|
</nav>
|
|
804
875
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
876
|
+
${this.errorMessage ? `<div class="error-banner">${this.escape(this.errorMessage)}</div>` : ''}
|
|
877
|
+
|
|
878
|
+
<div class="metric-deck">
|
|
879
|
+
<div class="metric-cell">
|
|
880
|
+
<div class="metric-label">Knowledge Hubs</div>
|
|
881
|
+
<div class="metric-val">${countText}</div>
|
|
809
882
|
</div>
|
|
810
|
-
<div class="
|
|
811
|
-
<div class="
|
|
812
|
-
<div class="
|
|
883
|
+
<div class="metric-cell">
|
|
884
|
+
<div class="metric-label">Indexed Volume</div>
|
|
885
|
+
<div class="metric-val">${linesText}</div>
|
|
813
886
|
</div>
|
|
814
|
-
<div class="
|
|
815
|
-
<div class="
|
|
816
|
-
<div class="
|
|
817
|
-
${this.config.model.split('/').pop() || this.config.model}
|
|
818
|
-
</div>
|
|
887
|
+
<div class="metric-cell">
|
|
888
|
+
<div class="metric-label">Mesh Status</div>
|
|
889
|
+
<div class="metric-val" style="color: var(--accent-amber);">${this.statusText}</div>
|
|
819
890
|
</div>
|
|
820
891
|
</div>
|
|
821
892
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
</div>
|
|
826
|
-
` : ''}
|
|
827
|
-
|
|
828
|
-
${this.renderActiveScreen()}
|
|
829
|
-
|
|
830
|
-
${this.executionResult ? `
|
|
831
|
-
<div class="result-deck">
|
|
832
|
-
<div class="result-title">Execution Receipt (${this.executionResult.timestamp})</div>
|
|
833
|
-
<div>Action: ${this.executionResult.action}</div>
|
|
834
|
-
<div>Target: ${this.executionResult.target || 'project'}</div>
|
|
835
|
-
<div>Result: ${JSON.stringify(this.executionResult.details)}</div>
|
|
836
|
-
</div>
|
|
837
|
-
` : ''}
|
|
893
|
+
<div class="content-view">
|
|
894
|
+
${this.renderActiveScreen()}
|
|
895
|
+
</div>
|
|
838
896
|
`;
|
|
839
897
|
|
|
840
|
-
this.
|
|
898
|
+
this.attachEventListeners();
|
|
841
899
|
}
|
|
842
900
|
|
|
843
901
|
renderActiveScreen() {
|
|
844
|
-
if (this.
|
|
845
|
-
return this.
|
|
846
|
-
}
|
|
847
|
-
if (this.activeScreen === 'refresh') {
|
|
848
|
-
return this.renderRefreshScreen();
|
|
902
|
+
if (this.selectedHub) {
|
|
903
|
+
return this.renderReaderScreen();
|
|
849
904
|
}
|
|
850
|
-
|
|
851
|
-
|
|
905
|
+
|
|
906
|
+
switch (this.activeScreen) {
|
|
907
|
+
case 'hubs':
|
|
908
|
+
return this.renderHubsScreen();
|
|
909
|
+
case 'search':
|
|
910
|
+
return this.renderSearchScreen();
|
|
911
|
+
case 'graph':
|
|
912
|
+
return this.renderGraphScreen();
|
|
913
|
+
case 'generate':
|
|
914
|
+
return this.renderGenerateScreen();
|
|
915
|
+
case 'refresh':
|
|
916
|
+
return this.renderRefreshScreen();
|
|
917
|
+
case 'token':
|
|
918
|
+
return this.renderTokenScreen();
|
|
919
|
+
case 'lint':
|
|
920
|
+
return this.renderLintScreen();
|
|
921
|
+
case 'upstream':
|
|
922
|
+
return this.renderUpstreamScreen();
|
|
923
|
+
case 'config':
|
|
924
|
+
return this.renderConfigScreen();
|
|
925
|
+
default:
|
|
926
|
+
return this.renderHubsScreen();
|
|
852
927
|
}
|
|
853
|
-
return this.renderHubsScreen();
|
|
854
928
|
}
|
|
855
929
|
|
|
856
930
|
renderHubsScreen() {
|
|
931
|
+
if (!this.hubs || this.hubs.length === 0) {
|
|
932
|
+
return `
|
|
933
|
+
<div style="text-align: center; padding: 30px; color: var(--text-muted);">
|
|
934
|
+
<div>No documentation hubs discovered for scope: <strong>${this.escape(this.scope)}</strong>.</div>
|
|
935
|
+
<button class="action-btn" id="btn-refresh-hubs" style="margin-top: 12px;">Sync Hubs</button>
|
|
936
|
+
</div>
|
|
937
|
+
`;
|
|
938
|
+
}
|
|
939
|
+
|
|
857
940
|
return `
|
|
858
|
-
<div
|
|
859
|
-
<span
|
|
860
|
-
<button
|
|
861
|
-
Sync
|
|
862
|
-
</button>
|
|
941
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
|
942
|
+
<span style="font-weight: 600; color: var(--text-muted);">Click a hub to read documentation:</span>
|
|
943
|
+
<button class="back-btn" id="btn-refresh-hubs">Refresh</button>
|
|
863
944
|
</div>
|
|
864
|
-
|
|
865
945
|
<div class="hub-list">
|
|
866
|
-
${this.hubs
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
<div class="hub-item">
|
|
870
|
-
<
|
|
871
|
-
|
|
946
|
+
${this.hubs
|
|
947
|
+
.map(
|
|
948
|
+
(h, idx) => `
|
|
949
|
+
<div class="hub-item" data-hub-index="${idx}">
|
|
950
|
+
<div>
|
|
951
|
+
<span class="repo-pill">${this.escape(h.repoName || 'workspace')}</span>
|
|
952
|
+
<span class="hub-title" style="margin-left: 6px;">${this.escape(h.title)}</span>
|
|
953
|
+
</div>
|
|
954
|
+
<div class="hub-meta">
|
|
955
|
+
<span>${h.lines || 0} lines</span>
|
|
956
|
+
<span>${h.size_bytes ? Math.round(h.size_bytes / 1024) + ' KB' : ''}</span>
|
|
957
|
+
</div>
|
|
872
958
|
</div>
|
|
873
|
-
`
|
|
959
|
+
`
|
|
960
|
+
)
|
|
961
|
+
.join('')}
|
|
874
962
|
</div>
|
|
875
|
-
|
|
876
963
|
<div class="ingest-deck">
|
|
877
|
-
<div
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
class="path-input"
|
|
881
|
-
type="text"
|
|
882
|
-
value="${this.targetPath}"
|
|
883
|
-
placeholder="Directory path (openwiki)"
|
|
884
|
-
aria-label="Target documentation path"
|
|
885
|
-
/>
|
|
886
|
-
<label class="toggle-label">
|
|
887
|
-
<input
|
|
888
|
-
id="dry-run-checkbox"
|
|
889
|
-
type="checkbox"
|
|
890
|
-
${this.dryRun ? 'checked' : ''}
|
|
891
|
-
/>
|
|
892
|
-
Dry Run
|
|
893
|
-
</label>
|
|
894
|
-
</div>
|
|
895
|
-
|
|
896
|
-
<div class="button-group">
|
|
897
|
-
<button id="btn-ingest" class="btn btn-primary" ${this.loading ? 'disabled' : ''}>
|
|
898
|
-
${this.loading ? 'Processing…' : 'Ingest Documentation'}
|
|
899
|
-
</button>
|
|
900
|
-
<button id="btn-bootstrap" class="btn btn-secondary" ${this.loading ? 'disabled' : ''}>
|
|
901
|
-
Bootstrap Wiki
|
|
902
|
-
</button>
|
|
903
|
-
</div>
|
|
964
|
+
<div style="font-weight: 600; margin-bottom: 4px;">SSSS Vault Ingestion</div>
|
|
965
|
+
<div style="font-size: 11px; color: var(--text-muted); margin-bottom: 8px;">Embed current hub markdown into semantic memory categories.</div>
|
|
966
|
+
<button class="action-btn" id="btn-ingest-exec">Ingest into Vault</button>
|
|
904
967
|
</div>
|
|
905
968
|
`;
|
|
906
969
|
}
|
|
907
970
|
|
|
908
|
-
|
|
971
|
+
renderReaderScreen() {
|
|
972
|
+
const hub = this.selectedHub;
|
|
909
973
|
return `
|
|
910
|
-
<div class="
|
|
911
|
-
<div
|
|
912
|
-
<span class="
|
|
974
|
+
<div class="reader-header">
|
|
975
|
+
<div>
|
|
976
|
+
<span class="repo-pill">${this.escape(hub.repoName || 'workspace')}</span>
|
|
977
|
+
<strong style="margin-left: 8px; color: var(--accent-amber);">${this.escape(hub.title)}</strong>
|
|
913
978
|
</div>
|
|
979
|
+
<button class="back-btn" id="btn-back-to-hubs">Back to Hubs</button>
|
|
980
|
+
</div>
|
|
981
|
+
<div class="markdown-body">${this.escape(this.selectedHubContent || 'Loading...')}</div>
|
|
982
|
+
`;
|
|
983
|
+
}
|
|
914
984
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
985
|
+
renderSearchScreen() {
|
|
986
|
+
return `
|
|
987
|
+
<div class="search-box">
|
|
988
|
+
<input class="search-input" id="search-input" placeholder="Search architecture, concepts, APIs across vaults..." value="${this.escape(this.searchQuery)}" />
|
|
989
|
+
<button class="action-btn" id="btn-search-exec">Search</button>
|
|
990
|
+
</div>
|
|
991
|
+
${
|
|
992
|
+
this.searchStats
|
|
993
|
+
? `<div style="font-size: 11px; color: var(--text-muted); margin-bottom: 8px;">
|
|
994
|
+
Found ${this.searchStats.count} passage(s) across ${this.searchStats.totalHubs} hubs (${this.searchStats.scope})
|
|
995
|
+
</div>`
|
|
996
|
+
: ''
|
|
997
|
+
}
|
|
998
|
+
<div class="search-results">
|
|
999
|
+
${
|
|
1000
|
+
this.searchResults.length === 0
|
|
1001
|
+
? `<div style="text-align: center; color: var(--text-muted); padding: 20px;">Enter search query to extract evidence passages.</div>`
|
|
1002
|
+
: this.searchResults
|
|
1003
|
+
.map(
|
|
1004
|
+
(r) => `
|
|
1005
|
+
<div class="result-item">
|
|
1006
|
+
<div class="result-heading">
|
|
1007
|
+
<span class="repo-pill">${this.escape(r.repoName)}</span>
|
|
1008
|
+
${this.escape(r.title)} > ${this.escape(r.section)} (Line ${r.line})
|
|
1009
|
+
</div>
|
|
1010
|
+
<div class="result-snippet">"${this.escape(r.snippet)}"</div>
|
|
1011
|
+
</div>
|
|
1012
|
+
`
|
|
1013
|
+
)
|
|
1014
|
+
.join('')
|
|
1015
|
+
}
|
|
1016
|
+
</div>
|
|
1017
|
+
`;
|
|
1018
|
+
}
|
|
919
1019
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
<button class="
|
|
926
|
-
Qwen 2.5 Coder 32B ($0.06/1M)
|
|
927
|
-
</button>
|
|
928
|
-
<button class="preset-chip" data-provider="google" data-model="gemini-2.5-flash-lite" data-fallback="gemini-2.5-flash">
|
|
929
|
-
Gemini 2.5 Flash-Lite ($0.075/1M)
|
|
930
|
-
</button>
|
|
1020
|
+
renderGraphScreen() {
|
|
1021
|
+
if (!this.graphData || !this.graphData.nodes) {
|
|
1022
|
+
return `
|
|
1023
|
+
<div style="text-align: center; padding: 30px; color: var(--text-muted);">
|
|
1024
|
+
<div>Knowledge topology graph not loaded.</div>
|
|
1025
|
+
<button class="action-btn" id="btn-load-graph" style="margin-top: 10px;">Extract Topology</button>
|
|
931
1026
|
</div>
|
|
1027
|
+
`;
|
|
1028
|
+
}
|
|
932
1029
|
|
|
933
|
-
|
|
934
|
-
<div class="field-group">
|
|
935
|
-
<label class="field-label" for="cfg-provider">Provider</label>
|
|
936
|
-
<select id="cfg-provider" class="select-input">
|
|
937
|
-
<option value="openrouter" ${this.config.provider === 'openrouter' ? 'selected' : ''}>OpenRouter (Multi-Model Hub)</option>
|
|
938
|
-
<option value="google" ${this.config.provider === 'google' ? 'selected' : ''}>Google (Gemini Direct)</option>
|
|
939
|
-
<option value="custom" ${this.config.provider === 'custom' ? 'selected' : ''}>Custom Provider</option>
|
|
940
|
-
</select>
|
|
941
|
-
</div>
|
|
1030
|
+
const { nodes, edges, orphans } = this.graphData;
|
|
942
1031
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1032
|
+
return `
|
|
1033
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
1034
|
+
<span style="font-weight: 600; color: var(--text-muted);">
|
|
1035
|
+
Topology: ${nodes.length} Nodes, ${edges.length} Edges, ${orphans.length} Orphans
|
|
1036
|
+
</span>
|
|
1037
|
+
<button class="back-btn" id="btn-load-graph">Refresh Graph</button>
|
|
1038
|
+
</div>
|
|
1039
|
+
<div class="graph-canvas-wrap">
|
|
1040
|
+
<svg width="100%" height="240" viewBox="0 0 600 240">
|
|
1041
|
+
<defs>
|
|
1042
|
+
<marker id="arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
|
1043
|
+
<path d="M 0 0 L 10 5 L 0 10 z" fill="#f59e0b" />
|
|
1044
|
+
</marker>
|
|
1045
|
+
</defs>
|
|
1046
|
+
${edges
|
|
1047
|
+
.slice(0, 30)
|
|
1048
|
+
.map((e, idx) => {
|
|
1049
|
+
const x1 = 50 + (idx * 37) % 500;
|
|
1050
|
+
const y1 = 40 + (idx * 23) % 160;
|
|
1051
|
+
const x2 = 80 + (idx * 43) % 500;
|
|
1052
|
+
const y2 = 60 + (idx * 31) % 160;
|
|
1053
|
+
return `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="rgba(245, 158, 11, 0.4)" stroke-width="1.5" marker-end="url(#arrow)" />`;
|
|
1054
|
+
})
|
|
1055
|
+
.join('')}
|
|
1056
|
+
${nodes
|
|
1057
|
+
.slice(0, 20)
|
|
1058
|
+
.map((n, idx) => {
|
|
1059
|
+
const cx = 50 + (idx * 53) % 500;
|
|
1060
|
+
const cy = 40 + (idx * 33) % 160;
|
|
1061
|
+
return `
|
|
1062
|
+
<g>
|
|
1063
|
+
<circle cx="${cx}" cy="${cy}" r="12" fill="#1e293b" stroke="#f59e0b" stroke-width="2" />
|
|
1064
|
+
<text x="${cx}" y="${cy + 22}" fill="#94a3b8" font-size="9" text-anchor="middle">
|
|
1065
|
+
${this.escape(n.label.slice(0, 14))}
|
|
1066
|
+
</text>
|
|
1067
|
+
</g>
|
|
1068
|
+
`;
|
|
1069
|
+
})
|
|
1070
|
+
.join('')}
|
|
1071
|
+
</svg>
|
|
1072
|
+
</div>
|
|
1073
|
+
`;
|
|
1074
|
+
}
|
|
947
1075
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1076
|
+
renderGenerateScreen() {
|
|
1077
|
+
return `
|
|
1078
|
+
<div style="margin-bottom: 12px;">
|
|
1079
|
+
<label style="display: block; font-weight: 600; margin-bottom: 4px;">Target Repository (leave empty for current workspace):</label>
|
|
1080
|
+
<input class="search-input" id="generate-repo-input" placeholder="e.g. total-recall or gro-crm" value="${this.escape(this.generateRepo)}" />
|
|
1081
|
+
</div>
|
|
1082
|
+
<div style="display: flex; justify-content: flex-end; margin-bottom: 12px;">
|
|
1083
|
+
<button class="action-btn" id="btn-generate-exec" ${this.loading ? 'disabled' : ''}>
|
|
1084
|
+
${this.loading ? 'Generating...' : 'Generate Wiki Documentation'}
|
|
1085
|
+
</button>
|
|
1086
|
+
</div>
|
|
1087
|
+
<div style="background: var(--bg-base); border: 1px solid var(--border-subtle); border-radius: 6px; padding: 10px; font-family: ui-monospace, monospace; font-size: 11px; max-height: 180px; overflow-y: auto;">
|
|
1088
|
+
${
|
|
1089
|
+
this.generateLogs.length === 0
|
|
1090
|
+
? '<span style="color: var(--text-muted);">Ready to dispatch on-demand generation.</span>'
|
|
1091
|
+
: this.generateLogs.map((l) => `<div>${this.escape(l)}</div>`).join('')
|
|
1092
|
+
}
|
|
1093
|
+
</div>
|
|
1094
|
+
`;
|
|
1095
|
+
}
|
|
952
1096
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1097
|
+
renderRefreshScreen() {
|
|
1098
|
+
return `
|
|
1099
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
|
1100
|
+
<span style="font-weight: 600; color: var(--text-muted);">Fleet Staleness Status:</span>
|
|
1101
|
+
<button class="action-btn" id="btn-fleet-refresh" ${this.loading ? 'disabled' : ''}>
|
|
1102
|
+
${this.loading ? 'Refreshing...' : 'Execute Fleet Refresh'}
|
|
1103
|
+
</button>
|
|
1104
|
+
</div>
|
|
1105
|
+
<div style="max-height: 220px; overflow-y: auto;">
|
|
1106
|
+
${
|
|
1107
|
+
this.refreshReports.length === 0
|
|
1108
|
+
? '<div style="color: var(--text-muted); text-align: center; padding: 20px;">No refresh reports loaded. Click Execute to evaluate fleet.</div>'
|
|
1109
|
+
: this.refreshReports
|
|
1110
|
+
.map(
|
|
1111
|
+
(r) => `
|
|
1112
|
+
<div style="display: flex; justify-content: space-between; padding: 6px 8px; background: var(--bg-elevated); border: 1px solid var(--border-subtle); border-radius: 4px; margin-bottom: 4px; font-size: 11px;">
|
|
1113
|
+
<span><strong>${this.escape(r.name)}</strong>: ${this.escape(r.action)}</span>
|
|
1114
|
+
<span style="color: var(--text-muted);">${r.commitsSince ? r.commitsSince + ' commits' : 'current'}</span>
|
|
1115
|
+
</div>
|
|
1116
|
+
`
|
|
1117
|
+
)
|
|
1118
|
+
.join('')
|
|
1119
|
+
}
|
|
1120
|
+
</div>
|
|
1121
|
+
`;
|
|
1122
|
+
}
|
|
957
1123
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1124
|
+
renderTokenScreen() {
|
|
1125
|
+
const { openrouter, google } = this.tokenStatus;
|
|
1126
|
+
return `
|
|
1127
|
+
<div class="auth-notice-card">
|
|
1128
|
+
Total Recall Auth Secrets Store credentials for model-driven documentation. Secrets are stored encrypted in the global store.
|
|
1129
|
+
</div>
|
|
1130
|
+
<div class="token-row">
|
|
1131
|
+
<div>
|
|
1132
|
+
<span class="token-key">OPENROUTER_API_KEY</span>
|
|
1133
|
+
<div style="font-size: 11px; color: var(--text-muted);">Default provider for cheap model wiki routing</div>
|
|
962
1134
|
</div>
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
</
|
|
1135
|
+
<span class="token-badge ${openrouter?.configured ? 'ok' : 'missing'}">
|
|
1136
|
+
${openrouter?.configured ? 'Configured' : 'Missing'}
|
|
1137
|
+
</span>
|
|
1138
|
+
</div>
|
|
1139
|
+
<div class="token-row">
|
|
1140
|
+
<div>
|
|
1141
|
+
<span class="token-key">GOOGLE_API_KEY</span>
|
|
1142
|
+
<div style="font-size: 11px; color: var(--text-muted);">Google Gemini provider for native model generation</div>
|
|
1143
|
+
</div>
|
|
1144
|
+
<span class="token-badge ${google?.configured ? 'ok' : 'missing'}">
|
|
1145
|
+
${google?.configured ? 'Configured' : 'Missing'}
|
|
1146
|
+
</span>
|
|
1147
|
+
</div>
|
|
1148
|
+
<div style="margin-top: 14px; border-top: 1px solid var(--border-subtle); padding-top: 12px;">
|
|
1149
|
+
<label style="display: block; font-weight: 600; margin-bottom: 4px;">Save Key to Auth Vault:</label>
|
|
1150
|
+
<div style="display: flex; gap: 8px;">
|
|
1151
|
+
<select class="scope-select" id="token-provider-select">
|
|
1152
|
+
<option value="openrouter">OpenRouter</option>
|
|
1153
|
+
<option value="google">Google Gemini</option>
|
|
1154
|
+
</select>
|
|
1155
|
+
<input type="password" class="search-input" id="token-val-input" placeholder="Paste API Key (saved securely to auth secrets)..." />
|
|
1156
|
+
<button class="action-btn" id="btn-save-token">Save Key</button>
|
|
971
1157
|
</div>
|
|
972
1158
|
</div>
|
|
973
1159
|
`;
|
|
974
1160
|
}
|
|
975
1161
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1162
|
+
renderLintScreen() {
|
|
1163
|
+
if (!this.lintData) {
|
|
1164
|
+
return `
|
|
1165
|
+
<div style="text-align: center; padding: 30px; color: var(--text-muted);">
|
|
1166
|
+
<div>Link hygiene and documentation health not audited yet.</div>
|
|
1167
|
+
<button class="action-btn" id="btn-run-lint" style="margin-top: 10px;">Run Health Check</button>
|
|
1168
|
+
</div>
|
|
1169
|
+
`;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
const { healthScore, totalHubs, totalLinks, brokenLinksCount, orphanCount, issues } = this.lintData;
|
|
1173
|
+
const scoreColor = healthScore > 80 ? 'var(--accent-green)' : (healthScore > 50 ? 'var(--accent-amber)' : 'var(--accent-red)');
|
|
979
1174
|
|
|
980
1175
|
return `
|
|
981
|
-
<div
|
|
982
|
-
<div
|
|
983
|
-
|
|
984
|
-
<button id="btn-sync-refresh" class="btn btn-secondary" style="min-height: 32px; padding: 0 10px; font-size: 11px;" ${this.loading ? 'disabled' : ''}>
|
|
985
|
-
Check Fleet
|
|
986
|
-
</button>
|
|
1176
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
|
|
1177
|
+
<div style="font-size: 20px; font-weight: 700; color: ${scoreColor};">
|
|
1178
|
+
${healthScore}% Health Score
|
|
987
1179
|
</div>
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1180
|
+
<button class="back-btn" id="btn-run-lint">Re-scan</button>
|
|
1181
|
+
</div>
|
|
1182
|
+
<div class="metric-deck">
|
|
1183
|
+
<div class="metric-cell">
|
|
1184
|
+
<div class="metric-label">Links Tested</div>
|
|
1185
|
+
<div class="metric-val">${totalLinks}</div>
|
|
991
1186
|
</div>
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
${
|
|
995
|
-
<div class="empty-state">No fleet report loaded. Click Check Fleet to inspect registered repositories.</div>
|
|
996
|
-
` : this.refreshReports.map((r) => {
|
|
997
|
-
let badgeClass = 'current';
|
|
998
|
-
let badgeText = 'CURRENT';
|
|
999
|
-
if (r.uninitialized) {
|
|
1000
|
-
badgeClass = 'uninit';
|
|
1001
|
-
badgeText = 'MISSING WIKI';
|
|
1002
|
-
} else if (r.action === 'due') {
|
|
1003
|
-
badgeClass = 'due';
|
|
1004
|
-
badgeText = 'DUE';
|
|
1005
|
-
}
|
|
1006
|
-
return `
|
|
1007
|
-
<div class="hub-item">
|
|
1008
|
-
<div>
|
|
1009
|
-
<span class="hub-title">${r.name}</span>
|
|
1010
|
-
<div class="hub-meta">${r.generatedAt ? `Generated ${r.generatedAt.slice(0, 10)}` : 'Never generated'} · ${r.commitsSince ? `${r.commitsSince} commits behind` : 'Up to date'}</div>
|
|
1011
|
-
</div>
|
|
1012
|
-
<span class="repo-status-badge ${badgeClass}">${badgeText}</span>
|
|
1013
|
-
</div>
|
|
1014
|
-
`;
|
|
1015
|
-
}).join('')}
|
|
1187
|
+
<div class="metric-cell">
|
|
1188
|
+
<div class="metric-label">Broken Links</div>
|
|
1189
|
+
<div class="metric-val" style="color: ${brokenLinksCount ? 'var(--accent-red)' : 'var(--accent-green)'};">${brokenLinksCount}</div>
|
|
1016
1190
|
</div>
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
<
|
|
1020
|
-
${this.loading ? 'Executing Fleet Refresh…' : `Regenerate Stale & Initialize Missing Wikis (${dueCount} due)`}
|
|
1021
|
-
</button>
|
|
1191
|
+
<div class="metric-cell">
|
|
1192
|
+
<div class="metric-label">Orphan Hubs</div>
|
|
1193
|
+
<div class="metric-val">${orphanCount}</div>
|
|
1022
1194
|
</div>
|
|
1023
1195
|
</div>
|
|
1196
|
+
<div style="max-height: 180px; overflow-y: auto;">
|
|
1197
|
+
${
|
|
1198
|
+
issues.length === 0
|
|
1199
|
+
? '<div style="color: var(--accent-green); padding: 10px;">[OK] All documentation links and headings verified clean!</div>'
|
|
1200
|
+
: issues
|
|
1201
|
+
.map(
|
|
1202
|
+
(iss) => `
|
|
1203
|
+
<div style="padding: 6px 8px; background: var(--bg-elevated); border: 1px solid var(--border-subtle); border-radius: 4px; margin-bottom: 4px; font-size: 11px;">
|
|
1204
|
+
<span style="color: var(--accent-red); font-weight: 600;">[${this.escape(iss.type)}]</span>
|
|
1205
|
+
${this.escape(iss.message)}
|
|
1206
|
+
</div>
|
|
1207
|
+
`
|
|
1208
|
+
)
|
|
1209
|
+
.join('')
|
|
1210
|
+
}
|
|
1211
|
+
</div>
|
|
1024
1212
|
`;
|
|
1025
1213
|
}
|
|
1026
1214
|
|
|
1027
1215
|
renderUpstreamScreen() {
|
|
1028
|
-
const currentVer = this.upstream?.recordedVersion || '0.7.2';
|
|
1029
|
-
const latestVer = this.upstream?.latestVersion || '0.7.3';
|
|
1030
|
-
|
|
1031
1216
|
return `
|
|
1032
|
-
<div
|
|
1033
|
-
<div
|
|
1034
|
-
|
|
1217
|
+
<div style="margin-bottom: 12px;">
|
|
1218
|
+
<div style="font-weight: 600; margin-bottom: 4px;">Upstream OpenWiki Surveillance:</div>
|
|
1219
|
+
<div style="font-size: 12px; color: var(--text-muted);">
|
|
1220
|
+
Recorded: v${this.upstream?.recordedVersion || '0.7.2'} | Latest: v${this.upstream?.latestVersion || '0.7.3'}
|
|
1035
1221
|
</div>
|
|
1222
|
+
</div>
|
|
1223
|
+
<button class="action-btn" id="btn-watch-exec" style="margin-top: 8px;">Check Upstream Releases</button>
|
|
1224
|
+
`;
|
|
1225
|
+
}
|
|
1036
1226
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
<
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
</div>
|
|
1046
|
-
<div class="stat-card">
|
|
1047
|
-
<div class="stat-label">Watcher Status</div>
|
|
1048
|
-
<div class="stat-value" style="color: var(--face-ok);">MONITORING</div>
|
|
1049
|
-
</div>
|
|
1227
|
+
renderConfigScreen() {
|
|
1228
|
+
return `
|
|
1229
|
+
<div class="config-deck">
|
|
1230
|
+
<div>
|
|
1231
|
+
<label style="display: block; font-weight: 600; margin-bottom: 4px;">Recommended Model Presets:</label>
|
|
1232
|
+
<span class="preset-chip" data-model="deepseek/deepseek-chat">DeepSeek Chat</span>
|
|
1233
|
+
<span class="preset-chip" data-model="deepseek/deepseek-v4.1-flash">DeepSeek Flash</span>
|
|
1234
|
+
<span class="preset-chip" data-model="qwen/qwen-2.5-coder-32b-instruct">Qwen 2.5 Coder</span>
|
|
1050
1235
|
</div>
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
<
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1236
|
+
<div>
|
|
1237
|
+
<label style="display: block; font-weight: 600; margin-bottom: 4px;">Provider:</label>
|
|
1238
|
+
<select class="scope-select" id="cfg-provider" style="width: 100%;">
|
|
1239
|
+
<option value="openrouter" ${this.config.provider === 'openrouter' ? 'selected' : ''}>OpenRouter (Fleet Routing)</option>
|
|
1240
|
+
<option value="google" ${this.config.provider === 'google' ? 'selected' : ''}>Google Gemini</option>
|
|
1241
|
+
</select>
|
|
1242
|
+
</div>
|
|
1243
|
+
<div>
|
|
1244
|
+
<label style="display: block; font-weight: 600; margin-bottom: 4px;">Model ID:</label>
|
|
1245
|
+
<input class="search-input" id="cfg-model" value="${this.escape(this.config.model)}" style="width: 100%;" />
|
|
1246
|
+
</div>
|
|
1247
|
+
<div>
|
|
1248
|
+
<label style="display: block; font-weight: 600; margin-bottom: 4px;">Max Tokens:</label>
|
|
1249
|
+
<input type="number" class="search-input" id="cfg-tokens" value="${this.config.max_tokens}" style="width: 100%;" />
|
|
1250
|
+
</div>
|
|
1251
|
+
<div style="display: flex; justify-content: flex-end;">
|
|
1252
|
+
<button class="action-btn" id="btn-save-cfg">Save Settings</button>
|
|
1059
1253
|
</div>
|
|
1060
1254
|
</div>
|
|
1061
1255
|
`;
|
|
1062
1256
|
}
|
|
1063
1257
|
|
|
1064
|
-
|
|
1065
|
-
const
|
|
1066
|
-
if (!
|
|
1258
|
+
attachEventListeners() {
|
|
1259
|
+
const root = this.shadowRoot;
|
|
1260
|
+
if (!root) return;
|
|
1261
|
+
|
|
1262
|
+
// Scope selection
|
|
1263
|
+
const scopeSelect = root.querySelector('#scope-select');
|
|
1264
|
+
if (scopeSelect) {
|
|
1265
|
+
scopeSelect.addEventListener('change', (e) => this.setScope(e.target.value));
|
|
1266
|
+
}
|
|
1067
1267
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1268
|
+
// Navigation buttons
|
|
1269
|
+
root.querySelectorAll('.nav-btn').forEach((btn) => {
|
|
1270
|
+
btn.addEventListener('click', () => {
|
|
1271
|
+
const screen = btn.getAttribute('data-screen');
|
|
1272
|
+
if (screen) this.setScreen(screen);
|
|
1072
1273
|
});
|
|
1073
1274
|
});
|
|
1074
1275
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1276
|
+
// Hub item click -> Read Hub
|
|
1277
|
+
root.querySelectorAll('.hub-item').forEach((item) => {
|
|
1278
|
+
item.addEventListener('click', () => {
|
|
1279
|
+
const idx = parseInt(item.getAttribute('data-hub-index'), 10);
|
|
1280
|
+
if (!isNaN(idx) && this.hubs[idx]) {
|
|
1281
|
+
this.readHubDocument(this.hubs[idx]);
|
|
1282
|
+
}
|
|
1079
1283
|
});
|
|
1080
|
-
}
|
|
1284
|
+
});
|
|
1081
1285
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1286
|
+
// Back to hubs from reader
|
|
1287
|
+
const backBtn = root.querySelector('#btn-back-to-hubs');
|
|
1288
|
+
if (backBtn) {
|
|
1289
|
+
backBtn.addEventListener('click', () => {
|
|
1290
|
+
this.selectedHub = null;
|
|
1291
|
+
this.selectedHubContent = null;
|
|
1292
|
+
this.render();
|
|
1086
1293
|
});
|
|
1087
1294
|
}
|
|
1088
1295
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1296
|
+
// Refresh hubs button
|
|
1297
|
+
const refreshHubsBtn = root.querySelector('#btn-refresh-hubs');
|
|
1298
|
+
if (refreshHubsBtn) {
|
|
1299
|
+
refreshHubsBtn.addEventListener('click', () => this.refreshHubs());
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// Ingest button
|
|
1303
|
+
const ingestBtn = root.querySelector('#btn-ingest-exec');
|
|
1304
|
+
if (ingestBtn) {
|
|
1305
|
+
ingestBtn.addEventListener('click', () => this.executeIngest());
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// Search button
|
|
1309
|
+
const searchBtn = root.querySelector('#btn-search-exec');
|
|
1310
|
+
const searchInput = root.querySelector('#search-input');
|
|
1311
|
+
if (searchBtn && searchInput) {
|
|
1312
|
+
searchBtn.addEventListener('click', () => this.executeSearch(searchInput.value));
|
|
1313
|
+
searchInput.addEventListener('keydown', (e) => {
|
|
1314
|
+
if (e.key === 'Enter') this.executeSearch(searchInput.value);
|
|
1315
|
+
});
|
|
1092
1316
|
}
|
|
1093
1317
|
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1318
|
+
// Graph button
|
|
1319
|
+
const loadGraphBtn = root.querySelector('#btn-load-graph');
|
|
1320
|
+
if (loadGraphBtn) {
|
|
1321
|
+
loadGraphBtn.addEventListener('click', () => this.loadGraph());
|
|
1097
1322
|
}
|
|
1098
1323
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1324
|
+
// Lint button
|
|
1325
|
+
const runLintBtn = root.querySelector('#btn-run-lint');
|
|
1326
|
+
if (runLintBtn) {
|
|
1327
|
+
runLintBtn.addEventListener('click', () => this.loadLint());
|
|
1102
1328
|
}
|
|
1103
1329
|
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1330
|
+
// Fleet refresh button
|
|
1331
|
+
const fleetRefreshBtn = root.querySelector('#btn-fleet-refresh');
|
|
1332
|
+
if (fleetRefreshBtn) {
|
|
1333
|
+
fleetRefreshBtn.addEventListener('click', () => this.executeFleetRefresh());
|
|
1107
1334
|
}
|
|
1108
1335
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1336
|
+
// Upstream watch button
|
|
1337
|
+
const watchBtn = root.querySelector('#btn-watch-exec');
|
|
1338
|
+
if (watchBtn) {
|
|
1339
|
+
watchBtn.addEventListener('click', () => this.executeWatch());
|
|
1112
1340
|
}
|
|
1113
1341
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1342
|
+
// Generate button
|
|
1343
|
+
const genBtn = root.querySelector('#btn-generate-exec');
|
|
1344
|
+
const genInput = root.querySelector('#generate-repo-input');
|
|
1345
|
+
if (genBtn) {
|
|
1346
|
+
genBtn.addEventListener('click', () => {
|
|
1347
|
+
if (genInput) this.generateRepo = genInput.value.trim();
|
|
1348
|
+
this.executeGenerate();
|
|
1349
|
+
});
|
|
1117
1350
|
}
|
|
1118
1351
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1352
|
+
// Save token button
|
|
1353
|
+
const saveTokenBtn = root.querySelector('#btn-save-token');
|
|
1354
|
+
const tokenProviderSelect = root.querySelector('#token-provider-select');
|
|
1355
|
+
const tokenValInput = root.querySelector('#token-val-input');
|
|
1356
|
+
if (saveTokenBtn && tokenProviderSelect && tokenValInput) {
|
|
1357
|
+
saveTokenBtn.addEventListener('click', () => {
|
|
1358
|
+
this.saveToken(tokenProviderSelect.value, tokenValInput.value);
|
|
1359
|
+
tokenValInput.value = '';
|
|
1360
|
+
});
|
|
1122
1361
|
}
|
|
1123
1362
|
|
|
1124
|
-
|
|
1363
|
+
// Preset chips
|
|
1364
|
+
root.querySelectorAll('.preset-chip').forEach((chip) => {
|
|
1125
1365
|
chip.addEventListener('click', async () => {
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1366
|
+
const model = chip.getAttribute('data-model');
|
|
1367
|
+
if (model) {
|
|
1368
|
+
const modelInput = root.querySelector('#cfg-model');
|
|
1369
|
+
if (modelInput) modelInput.value = model;
|
|
1370
|
+
await this.setConfigValue('model', model);
|
|
1371
|
+
}
|
|
1132
1372
|
});
|
|
1133
1373
|
});
|
|
1134
1374
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
const
|
|
1140
|
-
const
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
if (
|
|
1145
|
-
if (model) await this.setConfigValue('model', model);
|
|
1146
|
-
if (fallback) await this.setConfigValue('fallback_model', fallback);
|
|
1147
|
-
if (tokens) await this.setConfigValue('max_tokens', tokens);
|
|
1148
|
-
if (timeout) await this.setConfigValue('timeout_ms', timeout);
|
|
1375
|
+
// Save config button
|
|
1376
|
+
const saveCfgBtn = root.querySelector('#btn-save-cfg');
|
|
1377
|
+
if (saveCfgBtn) {
|
|
1378
|
+
saveCfgBtn.addEventListener('click', async () => {
|
|
1379
|
+
const prov = root.querySelector('#cfg-provider')?.value;
|
|
1380
|
+
const mod = root.querySelector('#cfg-model')?.value;
|
|
1381
|
+
const tok = root.querySelector('#cfg-tokens')?.value;
|
|
1382
|
+
if (prov) await this.setConfigValue('provider', prov);
|
|
1383
|
+
if (mod) await this.setConfigValue('model', mod);
|
|
1384
|
+
if (tok) await this.setConfigValue('max_tokens', tok);
|
|
1149
1385
|
});
|
|
1150
1386
|
}
|
|
1387
|
+
}
|
|
1151
1388
|
|
|
1152
|
-
|
|
1153
|
-
if (
|
|
1154
|
-
|
|
1155
|
-
|
|
1389
|
+
escape(str) {
|
|
1390
|
+
if (!str) return '';
|
|
1391
|
+
return String(str)
|
|
1392
|
+
.replace(/&/g, '&')
|
|
1393
|
+
.replace(/</g, '<')
|
|
1394
|
+
.replace(/>/g, '>')
|
|
1395
|
+
.replace(/"/g, '"');
|
|
1156
1396
|
}
|
|
1157
1397
|
}
|
|
1158
1398
|
|
|
1159
|
-
if (!customElements.get('tr-openwiki-card')) {
|
|
1399
|
+
if (typeof customElements !== 'undefined' && !customElements.get('tr-openwiki-card')) {
|
|
1160
1400
|
customElements.define('tr-openwiki-card', OpenWikiCard);
|
|
1161
1401
|
}
|
|
1162
|
-
if (!customElements.get('openwiki-card')) {
|
|
1163
|
-
customElements.define('openwiki-card', class extends OpenWikiCard {});
|
|
1164
|
-
}
|