@customize-agent/knowledge 1.0.1
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/dist/chunking/text-chunker.d.ts +24 -0
- package/dist/chunking/text-chunker.js +90 -0
- package/dist/classification/classifier.d.ts +12 -0
- package/dist/classification/classifier.js +95 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +40 -0
- package/dist/core/change-tracker.d.ts +10 -0
- package/dist/core/change-tracker.js +66 -0
- package/dist/core/file-scanner.d.ts +8 -0
- package/dist/core/file-scanner.js +32 -0
- package/dist/core/index-state-store.d.ts +103 -0
- package/dist/core/index-state-store.js +439 -0
- package/dist/core/knowledge-base-manager.d.ts +76 -0
- package/dist/core/knowledge-base-manager.js +300 -0
- package/dist/core/multi-project-manager.d.ts +30 -0
- package/dist/core/multi-project-manager.js +163 -0
- package/dist/core/project-config.d.ts +9 -0
- package/dist/core/project-config.js +73 -0
- package/dist/core/project-id.d.ts +1 -0
- package/dist/core/project-id.js +8 -0
- package/dist/core/project-registry.d.ts +10 -0
- package/dist/core/project-registry.js +70 -0
- package/dist/dedup/dedup-engine.d.ts +20 -0
- package/dist/dedup/dedup-engine.js +78 -0
- package/dist/dedup/relationship-detector.d.ts +10 -0
- package/dist/dedup/relationship-detector.js +84 -0
- package/dist/embedding/embedding-provider.d.ts +15 -0
- package/dist/embedding/embedding-provider.js +31 -0
- package/dist/extraction/content-extractor.d.ts +36 -0
- package/dist/extraction/content-extractor.js +655 -0
- package/dist/extraction/external-extractor.d.ts +63 -0
- package/dist/extraction/external-extractor.js +139 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +22 -0
- package/dist/search/federation-search.d.ts +41 -0
- package/dist/search/federation-search.js +102 -0
- package/dist/server/dashboard-client.d.ts +2 -0
- package/dist/server/dashboard-client.js +396 -0
- package/dist/server/dashboard-i18n.d.ts +112 -0
- package/dist/server/dashboard-i18n.js +220 -0
- package/dist/server/dashboard-page.d.ts +6 -0
- package/dist/server/dashboard-page.js +138 -0
- package/dist/server/dashboard-server.d.ts +13 -0
- package/dist/server/dashboard-server.js +225 -0
- package/dist/server/dashboard-styles.d.ts +1 -0
- package/dist/server/dashboard-styles.js +152 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.js +1 -0
- package/dist/vector/chroma-store.d.ts +39 -0
- package/dist/vector/chroma-store.js +131 -0
- package/dist/vector/collection-manager.d.ts +16 -0
- package/dist/vector/collection-manager.js +77 -0
- package/dist/vector/types.d.ts +33 -0
- package/dist/vector/types.js +1 -0
- package/dist/vector/vector-indexer.d.ts +18 -0
- package/dist/vector/vector-indexer.js +61 -0
- package/package.json +45 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
export function renderDashboardClient(messages) {
|
|
2
|
+
return `
|
|
3
|
+
const M = ${JSON.stringify(messages)};
|
|
4
|
+
let allKbFiles = [];
|
|
5
|
+
function formatText(value) {
|
|
6
|
+
if (value == null) return '';
|
|
7
|
+
if (Array.isArray(value)) return value.map(formatText).filter(Boolean).join(', ');
|
|
8
|
+
if (typeof value === 'object') return Object.entries(value).map(([key, val]) => key + '=' + formatText(val)).join(', ');
|
|
9
|
+
return String(value);
|
|
10
|
+
}
|
|
11
|
+
function switchLanguage(locale) {
|
|
12
|
+
const url = new URL(window.location.href);
|
|
13
|
+
url.searchParams.set('lang', locale);
|
|
14
|
+
window.location.href = url.toString();
|
|
15
|
+
}
|
|
16
|
+
async function withButtonLoading(button, task) {
|
|
17
|
+
const target = button || null;
|
|
18
|
+
const canDisable = target && 'disabled' in target;
|
|
19
|
+
if (target) {
|
|
20
|
+
if (canDisable) target.disabled = true;
|
|
21
|
+
target.classList.add('loading');
|
|
22
|
+
target.setAttribute('aria-busy', 'true');
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return await task();
|
|
26
|
+
} finally {
|
|
27
|
+
if (target) {
|
|
28
|
+
if ('disabled' in target) target.disabled = false;
|
|
29
|
+
target.classList.remove('loading');
|
|
30
|
+
if (canReplaceText) target.textContent = original;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function api(path, init) {
|
|
35
|
+
const res = await fetch(path, init);
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
const text = await res.text();
|
|
38
|
+
try {
|
|
39
|
+
const body = JSON.parse(text);
|
|
40
|
+
throw new Error(body.error || body.message || text);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error instanceof SyntaxError) throw new Error(text);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return res.json();
|
|
47
|
+
}
|
|
48
|
+
function showPage(page) {
|
|
49
|
+
document.querySelectorAll('.page').forEach(el => el.classList.toggle('active', el.id === 'page-' + page));
|
|
50
|
+
document.querySelectorAll('.tab').forEach(el => el.classList.toggle('active', el.dataset.page === page));
|
|
51
|
+
}
|
|
52
|
+
async function load() {
|
|
53
|
+
await Promise.all([
|
|
54
|
+
safeLoad(loadStats, 'stats'),
|
|
55
|
+
safeLoad(loadConfig, 'config'),
|
|
56
|
+
safeLoad(loadProjects, 'projects'),
|
|
57
|
+
safeLoad(loadCapabilities, 'capabilities'),
|
|
58
|
+
safeLoad(loadRelationships, 'relationships'),
|
|
59
|
+
safeLoad(loadFiles, 'filesPanel'),
|
|
60
|
+
safeLoad(loadIgnoreRules, 'ignorePanel'),
|
|
61
|
+
safeLoad(loadDuplicates, 'duplicates'),
|
|
62
|
+
]);
|
|
63
|
+
enhanceCustomSelects();
|
|
64
|
+
}
|
|
65
|
+
async function safeLoad(fn, targetId) {
|
|
66
|
+
try {
|
|
67
|
+
await fn();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
const el = document.getElementById(targetId);
|
|
70
|
+
if (el) el.innerHTML = '<div class="meta">' + M.loadFailed + ': ' + escapeHtml(error.message || String(error)) + '</div>';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function loadStats() {
|
|
74
|
+
const data = await api('/api/kb/stats');
|
|
75
|
+
document.getElementById('stats').innerHTML = renderStats(M.project, data.project) + renderStats(M.global, data.global);
|
|
76
|
+
}
|
|
77
|
+
function renderStats(title, stats) {
|
|
78
|
+
return '<h3>' + escapeHtml(title) + '</h3>' +
|
|
79
|
+
'<div class="stat-grid">' +
|
|
80
|
+
'<div class="metric"><span>' + M.files + '</span><b>' + stats.fileCount + '</b></div>' +
|
|
81
|
+
'<div class="metric"><span>' + M.chunks + '</span><b>' + stats.chunkCount + '</b></div>' +
|
|
82
|
+
'<div class="metric"><span>' + M.size + '</span><b>' + formatBytes(stats.totalSizeBytes) + '</b></div>' +
|
|
83
|
+
'</div>' +
|
|
84
|
+
'<div class="meta">' + escapeHtml(stats.kbPath) + '</div>';
|
|
85
|
+
}
|
|
86
|
+
async function loadConfig() {
|
|
87
|
+
const data = await api('/api/kb/config');
|
|
88
|
+
document.getElementById('config').innerHTML =
|
|
89
|
+
'<div class="config-row"><b>' + M.projectId + '</b><span>' + escapeHtml(data.projectId) + '</span></div>' +
|
|
90
|
+
'<div class="config-row"><b>' + M.path + '</b><span>' + escapeHtml(data.kbPath) + '</span></div>';
|
|
91
|
+
const categoryEl = document.getElementById('categoryDirs');
|
|
92
|
+
if (categoryEl) {
|
|
93
|
+
const dirs = data.categoryDirs || {};
|
|
94
|
+
categoryEl.innerHTML = Object.entries(dirs).map(([category, dir]) => '<div class="config-row"><b>' + categoryLabel(category) + '</b><span>' + escapeHtml(dir) + '</span></div>').join('') || '<div class="meta">' + M.noCategoryDirs + '</div>';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function loadProjects() {
|
|
98
|
+
const data = await api('/api/kb/projects');
|
|
99
|
+
document.getElementById('projects').innerHTML = data.projects.length
|
|
100
|
+
? data.projects.map(p => '<div class="card"><div class="path">' + escapeHtml(p.projectName || p.projectId) + '</div><div class="meta">' + escapeHtml(p.projectRoot) + '</div><span class="badge ok">' + p.fileCount + ' files</span><span class="badge">' + p.chunkCount + ' chunks</span></div>').join('')
|
|
101
|
+
: '<div class="meta">' + M.noProjects + '</div>';
|
|
102
|
+
}
|
|
103
|
+
async function loadCapabilities() {
|
|
104
|
+
const data = await api('/api/kb/capabilities');
|
|
105
|
+
const builtIns = data.capabilities.map(item => '<div class="card"><div class="path">' + escapeHtml(formatText(item.category) + ' / ' + formatText(item.format)) + '</div><div><span class="badge ok">' + M.vectorizable + '</span><span class="badge ' + (item.builtInTool ? 'ok' : 'warn') + '">' + M.builtInTool + ': ' + (item.builtInTool ? M.yes : M.plugin) + '</span></div><div class="meta">' + M.extraction + ': ' + escapeHtml(formatText(item.extraction)) + '</div>' + (item.note ? '<div class="meta">' + escapeHtml(formatText(item.note)) + '</div>' : '') + '</div>').join('');
|
|
106
|
+
const plugins = (data.externalExtractors || []).map(item => '<div class="card"><div class="path">' + M.plugin + ': ' + escapeHtml(formatText(item.name)) + '</div><span class="badge ' + (item.available ? 'ok' : 'warn') + '">' + (item.available ? M.available : M.unavailable) + '</span><div class="meta">' + escapeHtml([item.category, (item.formats || []).join('/'), (item.extensions || []).join('/')].filter(Boolean).map(formatText).join(' · ')) + '</div></div>').join('');
|
|
107
|
+
document.getElementById('capabilities').innerHTML = builtIns + (plugins ? '<h3>' + M.externalPlugins + '</h3>' + plugins : '');
|
|
108
|
+
}
|
|
109
|
+
async function search(button) {
|
|
110
|
+
const q = document.getElementById('query').value.trim();
|
|
111
|
+
const scope = document.getElementById('scope').value;
|
|
112
|
+
if (!q) return;
|
|
113
|
+
await withButtonLoading(button, async () => {
|
|
114
|
+
try {
|
|
115
|
+
const data = await api('/api/kb/search?q=' + encodeURIComponent(q) + '&scope=' + scope);
|
|
116
|
+
document.getElementById('results').innerHTML = data.results.length
|
|
117
|
+
? data.results.map(r => '<div class="card"><div class="path">' + escapeHtml(formatText(r.filePath)) + '</div><div class="meta"><span class="badge">' + M.scope + ': ' + formatText(r.scope) + '</span><span class="badge ok">' + M.score + ': ' + Number(r.score).toFixed(3) + '</span><span class="badge">' + escapeHtml(formatText(r.collection)) + '</span></div><pre>' + escapeHtml(formatText(r.content).slice(0, 700)) + '</pre></div>').join('')
|
|
118
|
+
: '<div class="meta">' + M.noResults + '</div>';
|
|
119
|
+
} catch (error) {
|
|
120
|
+
document.getElementById('results').innerHTML = '<div class="meta">' + M.failed + ': ' + escapeHtml(error.message || String(error)) + '</div>';
|
|
121
|
+
}
|
|
122
|
+
}, M.loading);
|
|
123
|
+
}
|
|
124
|
+
async function syncNow(button) {
|
|
125
|
+
await withButtonLoading(button, async () => {
|
|
126
|
+
await api('/api/kb/reindex', { method:'POST' });
|
|
127
|
+
await load();
|
|
128
|
+
}, M.loading);
|
|
129
|
+
}
|
|
130
|
+
async function uploadFiles() {
|
|
131
|
+
const input = document.getElementById('uploadFile');
|
|
132
|
+
const files = Array.from(input.files || []);
|
|
133
|
+
if (files.length === 0) return;
|
|
134
|
+
const uploadCard = document.querySelector('.upload-card');
|
|
135
|
+
await withButtonLoading(uploadCard, async () => {
|
|
136
|
+
await runOp(async () => {
|
|
137
|
+
let uploaded = 0;
|
|
138
|
+
for (const file of files) {
|
|
139
|
+
const contentBase64 = await readFileAsBase64(file);
|
|
140
|
+
await api('/api/kb/upload', { method:'POST', body: JSON.stringify({ fileName: file.name, contentBase64 }) });
|
|
141
|
+
uploaded++;
|
|
142
|
+
}
|
|
143
|
+
return M.uploadSuccess + ': ' + uploaded + ' ' + M.files;
|
|
144
|
+
});
|
|
145
|
+
input.value = '';
|
|
146
|
+
}, M.loading);
|
|
147
|
+
}
|
|
148
|
+
async function runOp(operation) {
|
|
149
|
+
setStatus('opsStatus', M.loading);
|
|
150
|
+
try {
|
|
151
|
+
const message = await operation();
|
|
152
|
+
await load();
|
|
153
|
+
setStatus('opsStatus', message || M.ok, true);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
await load();
|
|
156
|
+
setStatus('opsStatus', M.failed + ': ' + error.message, false, true);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function loadRelationships() {
|
|
160
|
+
const data = await api('/api/kb/relationships');
|
|
161
|
+
const relationships = data.relationships || [];
|
|
162
|
+
document.getElementById('relationships').innerHTML = relationships.length
|
|
163
|
+
? relationships.slice(0, 80).map(r => '<div class="card"><div><span class="badge ok">' + escapeHtml(r.relationshipType) + '</span><span class="badge">' + Number(r.confidence).toFixed(2) + '</span></div><div class="meta">' + escapeHtml(r.sourceFile) + ' → ' + escapeHtml(r.targetFile) + '</div><div class="meta">' + escapeHtml(r.detail || '') + '</div></div>').join('')
|
|
164
|
+
: '<div class="meta">' + M.noRelationships + '</div>';
|
|
165
|
+
renderRelationshipSummary(relationships);
|
|
166
|
+
}
|
|
167
|
+
function renderRelationshipSummary(relationships) {
|
|
168
|
+
const counts = new Map();
|
|
169
|
+
for (const item of relationships) counts.set(item.relationshipType, (counts.get(item.relationshipType) || 0) + 1);
|
|
170
|
+
document.getElementById('relationshipSummary').innerHTML = counts.size
|
|
171
|
+
? Array.from(counts.entries()).map(([type, count]) => '<div class="stat"><span>' + escapeHtml(type) + '</span><b>' + count + '</b></div>').join('')
|
|
172
|
+
: '<div class="meta">' + M.noRelationships + '</div>';
|
|
173
|
+
}
|
|
174
|
+
async function loadFiles() {
|
|
175
|
+
const data = await api('/api/kb/files');
|
|
176
|
+
allKbFiles = data.files || [];
|
|
177
|
+
renderFilesSyncStatus(data.sync);
|
|
178
|
+
renderFileFilters();
|
|
179
|
+
renderFiles();
|
|
180
|
+
await loadFailedFiles();
|
|
181
|
+
}
|
|
182
|
+
async function loadFailedFiles() {
|
|
183
|
+
const panel = document.getElementById('failedFilesPanel');
|
|
184
|
+
if (!panel) return;
|
|
185
|
+
const data = await api('/api/kb/failed-files');
|
|
186
|
+
const failed = data.failedFiles || [];
|
|
187
|
+
panel.innerHTML = failed.length ? failed.map(item => '<div class="file-item failed-file"><div class="file-main"><div class="path">' + escapeHtml(item.file.relativePath) + '</div><div class="meta">' + M.reason + ': ' + escapeHtml(formatText(item.reason)) + '</div><div class="meta">' + escapeHtml(formatText(item.file.format)) + ' · ' + formatBytes(item.file.fileSize) + '</div></div><div class="file-actions"><button onclick="retryFailedFile(this.dataset.path, this)" data-path="' + escapeAttr(item.file.relativePath) + '">' + M.retryIndex + '</button><button class="danger" onclick="deleteFileWithModal(this.dataset.path, this)" data-path="' + escapeAttr(item.file.relativePath) + '">' + M.delete + '</button></div></div>').join('') : '<div class="meta">' + M.noFailedFiles + '</div>';
|
|
188
|
+
}
|
|
189
|
+
async function retryFailedFile(filePath, button) {
|
|
190
|
+
await withButtonLoading(button, async () => {
|
|
191
|
+
await runOp(async () => {
|
|
192
|
+
await api('/api/kb/failed-files/' + encodeURIComponent(filePath), { method:'POST' });
|
|
193
|
+
return M.retryStarted;
|
|
194
|
+
});
|
|
195
|
+
await loadFiles();
|
|
196
|
+
}, M.loading);
|
|
197
|
+
}
|
|
198
|
+
function renderFilesSyncStatus(sync) {
|
|
199
|
+
const el = document.getElementById('filesSyncStatus');
|
|
200
|
+
if (!el || !sync) return;
|
|
201
|
+
const changed = (sync.newFiles?.length || 0) + (sync.modifiedFiles?.length || 0) + (sync.deletedFiles?.length || 0);
|
|
202
|
+
const skipped = sync.skippedFiles?.length || 0;
|
|
203
|
+
if (skipped > 0) {
|
|
204
|
+
el.className = 'status error';
|
|
205
|
+
el.textContent = M.filesSyncSkipped.replace('{count}', skipped);
|
|
206
|
+
} else if (changed > 0) {
|
|
207
|
+
el.className = 'status success';
|
|
208
|
+
el.textContent = M.filesSyncChanged.replace('{count}', changed);
|
|
209
|
+
} else {
|
|
210
|
+
el.className = 'status success';
|
|
211
|
+
el.textContent = M.filesSyncUnchanged;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function renderFileFilters() {
|
|
215
|
+
document.querySelectorAll('section.select-host-open').forEach(el => el.classList.remove('select-host-open'));
|
|
216
|
+
document.querySelectorAll('.custom-select').forEach(el => el.remove());
|
|
217
|
+
document.querySelectorAll('select.select-native-hidden').forEach(el => { el.classList.remove('select-native-hidden'); delete el.dataset.enhanced; });
|
|
218
|
+
const typeEl = document.getElementById('fileTypeFilter');
|
|
219
|
+
const folderEl = document.getElementById('folderFilter');
|
|
220
|
+
if (!typeEl || !folderEl) return;
|
|
221
|
+
const currentType = typeEl.value || 'all';
|
|
222
|
+
const currentFolder = folderEl.value || 'all';
|
|
223
|
+
const types = [...new Set(allKbFiles.map(file => file.category || 'other'))].sort();
|
|
224
|
+
const folders = [...new Set(allKbFiles.map(file => firstFolder(file.relativePath)))].sort();
|
|
225
|
+
typeEl.innerHTML = '<option value="all">' + M.allTypes + '</option>' + types.map(type => '<option value="' + escapeAttr(type) + '">' + categoryLabel(type) + '</option>').join('');
|
|
226
|
+
folderEl.innerHTML = '<option value="all">' + M.allFolders + '</option>' + folders.map(folder => '<option value="' + escapeAttr(folder) + '">' + escapeHtml(folder) + '</option>').join('');
|
|
227
|
+
typeEl.value = types.includes(currentType) ? currentType : 'all';
|
|
228
|
+
folderEl.value = folders.includes(currentFolder) ? currentFolder : 'all';
|
|
229
|
+
enhanceCustomSelects();
|
|
230
|
+
}
|
|
231
|
+
function renderFiles() {
|
|
232
|
+
const type = document.getElementById('fileTypeFilter')?.value || 'all';
|
|
233
|
+
const folder = document.getElementById('folderFilter')?.value || 'all';
|
|
234
|
+
const files = allKbFiles.filter(file => (type === 'all' || file.category === type) && (folder === 'all' || firstFolder(file.relativePath) === folder));
|
|
235
|
+
const groups = files.reduce((acc, file) => {
|
|
236
|
+
const key = file.category || 'other';
|
|
237
|
+
(acc[key] ||= []).push(file);
|
|
238
|
+
return acc;
|
|
239
|
+
}, {});
|
|
240
|
+
const entries = Object.entries(groups);
|
|
241
|
+
document.getElementById('filesPanel').innerHTML = entries.length ? entries.map(([category, items]) => '<details class="file-group" open><summary>' + categoryLabel(category) + ' <span>' + items.length + '</span></summary>' + items.map(file => '<div class="file-item"><div class="file-main"><div class="path">' + escapeHtml(file.relativePath) + '</div><div class="meta">' + escapeHtml(formatText(file.format)) + ' · ' + formatBytes(file.fileSize) + ' · ' + escapeHtml(firstFolder(file.relativePath)) + '</div></div><div class="file-actions"><button onclick="quickTag(this.dataset.path, this)" data-path="' + escapeAttr(file.relativePath) + '">' + M.tag + '</button><button onclick="ignoreSimilar(this.dataset.path, this)" data-path="' + escapeAttr(file.relativePath) + '">' + M.ignoreSimilar + '</button><button class="danger" onclick="deleteFileWithModal(this.dataset.path, this)" data-path="' + escapeAttr(file.relativePath) + '">' + M.delete + '</button></div></div>').join('') + '</details>').join('') : '<div class="meta">' + M.noFilteredFiles + '</div>';
|
|
242
|
+
}
|
|
243
|
+
function firstFolder(relativePath) {
|
|
244
|
+
return String(relativePath || '').split('/')[0] || M.rootFolder;
|
|
245
|
+
}
|
|
246
|
+
async function deleteFileWithModal(filePath, button) {
|
|
247
|
+
const ok = await openModal({ title: M.deleteConfirmTitle, message: M.deleteConfirmMessage + '\\n' + filePath, confirmText: M.delete, danger: true });
|
|
248
|
+
if (!ok) return;
|
|
249
|
+
await withButtonLoading(button, async () => {
|
|
250
|
+
await runOp(async () => api('/api/kb/files/' + encodeURIComponent(filePath), { method:'DELETE' }));
|
|
251
|
+
await loadFiles();
|
|
252
|
+
}, M.loading);
|
|
253
|
+
}
|
|
254
|
+
async function quickTag(filePath, button) {
|
|
255
|
+
const value = await openModal({ title: M.tag, message: M.tagPrompt, inputValue: M.defaultTag, confirmText: M.confirm });
|
|
256
|
+
if (!value) return;
|
|
257
|
+
const tags = String(value).split(',').map(t => t.trim()).filter(Boolean);
|
|
258
|
+
await withButtonLoading(button, async () => runOp(async () => api('/api/kb/tags', { method:'POST', body: JSON.stringify({ filePath, tags }) })), M.loading);
|
|
259
|
+
}
|
|
260
|
+
async function ignoreSimilar(filePath, button) {
|
|
261
|
+
const ext = filePath.includes('.') ? '*.' + filePath.split('.').pop() : filePath;
|
|
262
|
+
const pattern = await openModal({ title: M.ignore, message: M.ignorePrompt, inputValue: '**/' + ext, confirmText: M.confirm });
|
|
263
|
+
if (!pattern) return;
|
|
264
|
+
await withButtonLoading(button, async () => runOp(async () => api('/api/kb/ignore', { method:'POST', body: JSON.stringify({ pattern }) })), M.loading);
|
|
265
|
+
}
|
|
266
|
+
function categoryLabel(category) {
|
|
267
|
+
return M.categoryLabels?.[category] || category;
|
|
268
|
+
}
|
|
269
|
+
async function loadTags() {
|
|
270
|
+
const data = await api('/api/kb/tags');
|
|
271
|
+
const tags = data.tags || [];
|
|
272
|
+
document.getElementById('tagsPanel').innerHTML = tags.length
|
|
273
|
+
? tags.slice(0, 100).map(t => '<div class="card"><span class="badge ok">' + escapeHtml(t.tag) + '</span><div class="meta">' + escapeHtml(t.filePath) + '</div></div>').join('')
|
|
274
|
+
: '<div class="meta">' + M.noTags + '</div>';
|
|
275
|
+
}
|
|
276
|
+
async function loadIgnoreRules() {
|
|
277
|
+
const data = await api('/api/kb/ignore');
|
|
278
|
+
const rules = data.rules || [];
|
|
279
|
+
document.getElementById('ignorePanel').innerHTML = rules.length
|
|
280
|
+
? rules.map(rule => '<div class="card"><span class="badge ' + (rule.enabled ? 'ok' : 'warn') + '">' + (rule.enabled ? M.enabled : M.disabled) + '</span><div class="path">' + escapeHtml(rule.pattern) + '</div></div>').join('')
|
|
281
|
+
: '<div class="meta">' + M.noIgnoreRules + '</div>';
|
|
282
|
+
}
|
|
283
|
+
async function loadDuplicates() {
|
|
284
|
+
const data = await api('/api/kb/duplicates');
|
|
285
|
+
const duplicates = data.duplicates || [];
|
|
286
|
+
document.getElementById('duplicates').innerHTML = duplicates.length
|
|
287
|
+
? duplicates.slice(0, 50).map(d => '<div class="card"><div class="path">' + escapeHtml(d.contentHash.slice(0, 16)) + '</div>' + d.files.map(f => '<div class="meta">' + escapeHtml(f.projectId + ': ' + f.relativePath) + '</div>').join('') + '</div>').join('')
|
|
288
|
+
: '<div class="meta">' + M.noDuplicates + '</div>';
|
|
289
|
+
}
|
|
290
|
+
function openModal(options) {
|
|
291
|
+
return new Promise(resolve => {
|
|
292
|
+
const backdrop = document.getElementById('modalBackdrop');
|
|
293
|
+
const title = document.getElementById('modalTitle');
|
|
294
|
+
const message = document.getElementById('modalMessage');
|
|
295
|
+
const cancel = document.getElementById('modalCancel');
|
|
296
|
+
const confirm = document.getElementById('modalConfirm');
|
|
297
|
+
title.textContent = options.title || '';
|
|
298
|
+
message.textContent = options.message || '';
|
|
299
|
+
message.querySelectorAll('input').forEach(input => input.remove());
|
|
300
|
+
const input = options.inputValue != null ? document.createElement('input') : undefined;
|
|
301
|
+
if (input) {
|
|
302
|
+
input.className = 'modal-input';
|
|
303
|
+
input.value = options.inputValue;
|
|
304
|
+
message.after(input);
|
|
305
|
+
}
|
|
306
|
+
cancel.textContent = M.cancel;
|
|
307
|
+
confirm.textContent = options.confirmText || M.confirm;
|
|
308
|
+
confirm.classList.toggle('danger', Boolean(options.danger));
|
|
309
|
+
const cleanup = value => {
|
|
310
|
+
backdrop.hidden = true;
|
|
311
|
+
cancel.onclick = null;
|
|
312
|
+
confirm.onclick = null;
|
|
313
|
+
input?.remove();
|
|
314
|
+
resolve(value);
|
|
315
|
+
};
|
|
316
|
+
cancel.onclick = () => cleanup(false);
|
|
317
|
+
confirm.onclick = () => cleanup(input ? input.value : true);
|
|
318
|
+
backdrop.hidden = false;
|
|
319
|
+
input?.focus();
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
function readFileAsBase64(file) {
|
|
323
|
+
return new Promise((resolve, reject) => {
|
|
324
|
+
const reader = new FileReader();
|
|
325
|
+
reader.onload = () => resolve(String(reader.result).split(',')[1] || '');
|
|
326
|
+
reader.onerror = () => reject(reader.error);
|
|
327
|
+
reader.readAsDataURL(file);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
function setStatus(id, text, ok, error) {
|
|
331
|
+
const el = document.getElementById(id);
|
|
332
|
+
if (!el) return;
|
|
333
|
+
el.className = error ? 'status error' : ok ? 'status ok' : 'status';
|
|
334
|
+
el.textContent = text;
|
|
335
|
+
}
|
|
336
|
+
function enhanceCustomSelects() {
|
|
337
|
+
document.querySelectorAll('select').forEach(select => {
|
|
338
|
+
if (select.classList.contains('native-select') || select.classList.contains('select-native-hidden') || select.dataset.enhanced === 'true') return;
|
|
339
|
+
select.dataset.enhanced = 'true';
|
|
340
|
+
select.classList.add('select-native-hidden');
|
|
341
|
+
const wrapper = document.createElement('div');
|
|
342
|
+
wrapper.className = 'custom-select';
|
|
343
|
+
const trigger = document.createElement('button');
|
|
344
|
+
trigger.type = 'button';
|
|
345
|
+
trigger.className = 'custom-select-trigger';
|
|
346
|
+
const menu = document.createElement('div');
|
|
347
|
+
menu.className = 'custom-select-menu';
|
|
348
|
+
wrapper.append(trigger, menu);
|
|
349
|
+
select.insertAdjacentElement('afterend', wrapper);
|
|
350
|
+
const sync = () => {
|
|
351
|
+
const selected = select.options[select.selectedIndex];
|
|
352
|
+
trigger.textContent = selected ? selected.textContent : '';
|
|
353
|
+
menu.innerHTML = '';
|
|
354
|
+
Array.from(select.options).forEach(option => {
|
|
355
|
+
const button = document.createElement('button');
|
|
356
|
+
button.type = 'button';
|
|
357
|
+
button.className = 'custom-select-option' + (option.value === select.value ? ' active' : '');
|
|
358
|
+
button.textContent = option.textContent;
|
|
359
|
+
button.onclick = () => {
|
|
360
|
+
select.value = option.value;
|
|
361
|
+
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
362
|
+
wrapper.classList.remove('open');
|
|
363
|
+
wrapper.closest('section')?.classList.remove('select-host-open');
|
|
364
|
+
sync();
|
|
365
|
+
};
|
|
366
|
+
menu.appendChild(button);
|
|
367
|
+
});
|
|
368
|
+
};
|
|
369
|
+
trigger.onclick = event => {
|
|
370
|
+
event.stopPropagation();
|
|
371
|
+
document.querySelectorAll('.custom-select.open').forEach(el => { if (el !== wrapper) { el.classList.remove('open'); el.closest('section')?.classList.remove('select-host-open'); } });
|
|
372
|
+
const isOpen = wrapper.classList.toggle('open');
|
|
373
|
+
wrapper.closest('section')?.classList.toggle('select-host-open', isOpen);
|
|
374
|
+
};
|
|
375
|
+
select.addEventListener('change', sync);
|
|
376
|
+
sync();
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
document.addEventListener('click', () => document.querySelectorAll('.custom-select.open').forEach(el => { el.classList.remove('open'); el.closest('section')?.classList.remove('select-host-open'); }));
|
|
380
|
+
function formatBytes(bytes) {
|
|
381
|
+
if (!bytes) return '0 B';
|
|
382
|
+
const units = ['B','KB','MB','GB'];
|
|
383
|
+
let value = Number(bytes);
|
|
384
|
+
let unit = 0;
|
|
385
|
+
while (value >= 1024 && unit < units.length - 1) { value /= 1024; unit++; }
|
|
386
|
+
return value.toFixed(unit === 0 ? 0 : 1) + ' ' + units[unit];
|
|
387
|
+
}
|
|
388
|
+
function escapeHtml(s) {
|
|
389
|
+
return String(s).replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c]));
|
|
390
|
+
}
|
|
391
|
+
function escapeAttr(s) {
|
|
392
|
+
return escapeHtml(s).replace(/'/g, ''');
|
|
393
|
+
}
|
|
394
|
+
load().catch(err => document.body.insertAdjacentHTML('beforeend', '<pre>' + escapeHtml(String(err)) + '</pre>'));
|
|
395
|
+
`;
|
|
396
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export type DashboardLocale = 'zh-CN' | 'en-US';
|
|
2
|
+
export interface DashboardMessages {
|
|
3
|
+
title: string;
|
|
4
|
+
subtitle: string;
|
|
5
|
+
reindex: string;
|
|
6
|
+
refresh: string;
|
|
7
|
+
stats: string;
|
|
8
|
+
projects: string;
|
|
9
|
+
fileOps: string;
|
|
10
|
+
search: string;
|
|
11
|
+
relationships: string;
|
|
12
|
+
relationshipSummary: string;
|
|
13
|
+
duplicates: string;
|
|
14
|
+
tags: string;
|
|
15
|
+
ignoreRules: string;
|
|
16
|
+
config: string;
|
|
17
|
+
capabilities: string;
|
|
18
|
+
pluginGuide: string;
|
|
19
|
+
vectorizable: string;
|
|
20
|
+
builtInTool: string;
|
|
21
|
+
extraction: string;
|
|
22
|
+
sourceFilePath: string;
|
|
23
|
+
targetRelativePath: string;
|
|
24
|
+
addFile: string;
|
|
25
|
+
relativePath: string;
|
|
26
|
+
remove: string;
|
|
27
|
+
filePath: string;
|
|
28
|
+
tagsPlaceholder: string;
|
|
29
|
+
tag: string;
|
|
30
|
+
ignorePattern: string;
|
|
31
|
+
ignore: string;
|
|
32
|
+
ignoreSimilar: string;
|
|
33
|
+
searchPlaceholder: string;
|
|
34
|
+
loading: string;
|
|
35
|
+
noProjects: string;
|
|
36
|
+
noResults: string;
|
|
37
|
+
noRelationships: string;
|
|
38
|
+
noDuplicates: string;
|
|
39
|
+
noTags: string;
|
|
40
|
+
noIgnoreRules: string;
|
|
41
|
+
project: string;
|
|
42
|
+
global: string;
|
|
43
|
+
files: string;
|
|
44
|
+
chunks: string;
|
|
45
|
+
size: string;
|
|
46
|
+
path: string;
|
|
47
|
+
scope: string;
|
|
48
|
+
score: string;
|
|
49
|
+
updated: string;
|
|
50
|
+
ok: string;
|
|
51
|
+
failed: string;
|
|
52
|
+
overviewTab: string;
|
|
53
|
+
filesTab: string;
|
|
54
|
+
settingsTab: string;
|
|
55
|
+
searchHelp: string;
|
|
56
|
+
scopeLabel: string;
|
|
57
|
+
scopeAll: string;
|
|
58
|
+
scopeProject: string;
|
|
59
|
+
scopeGlobal: string;
|
|
60
|
+
uploadTitle: string;
|
|
61
|
+
uploadHelp: string;
|
|
62
|
+
uploadChoose: string;
|
|
63
|
+
uploadSupport: string;
|
|
64
|
+
filesTitle: string;
|
|
65
|
+
filesHelp: string;
|
|
66
|
+
fileType: string;
|
|
67
|
+
allTypes: string;
|
|
68
|
+
folderDir: string;
|
|
69
|
+
allFolders: string;
|
|
70
|
+
categoryDirs: string;
|
|
71
|
+
categoryDirsHelp: string;
|
|
72
|
+
projectId: string;
|
|
73
|
+
noCategoryDirs: string;
|
|
74
|
+
loadFailed: string;
|
|
75
|
+
uploadSuccess: string;
|
|
76
|
+
filesSyncSkipped: string;
|
|
77
|
+
filesSyncChanged: string;
|
|
78
|
+
filesSyncUnchanged: string;
|
|
79
|
+
noFilteredFiles: string;
|
|
80
|
+
rootFolder: string;
|
|
81
|
+
confirmRemove: string;
|
|
82
|
+
tagPrompt: string;
|
|
83
|
+
defaultTag: string;
|
|
84
|
+
ignorePrompt: string;
|
|
85
|
+
enabled: string;
|
|
86
|
+
disabled: string;
|
|
87
|
+
yes: string;
|
|
88
|
+
plugin: string;
|
|
89
|
+
externalPlugins: string;
|
|
90
|
+
available: string;
|
|
91
|
+
unavailable: string;
|
|
92
|
+
syncNow: string;
|
|
93
|
+
language: string;
|
|
94
|
+
delete: string;
|
|
95
|
+
cancel: string;
|
|
96
|
+
confirm: string;
|
|
97
|
+
retryIndex: string;
|
|
98
|
+
failedFilesTitle: string;
|
|
99
|
+
failedFilesHelp: string;
|
|
100
|
+
noFailedFiles: string;
|
|
101
|
+
reason: string;
|
|
102
|
+
operation: string;
|
|
103
|
+
deleteConfirmTitle: string;
|
|
104
|
+
deleteConfirmMessage: string;
|
|
105
|
+
retryStarted: string;
|
|
106
|
+
categoryLabels: Record<string, string>;
|
|
107
|
+
}
|
|
108
|
+
export declare const DASHBOARD_MESSAGES: Record<DashboardLocale, DashboardMessages>;
|
|
109
|
+
export declare function resolveDashboardMessages(locale: string | undefined): {
|
|
110
|
+
locale: DashboardLocale;
|
|
111
|
+
messages: DashboardMessages;
|
|
112
|
+
};
|