aifastdb 3.7.6 → 3.8.6-mac.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,27 +17,27 @@ const crud_table_1 = require("./crud-table");
17
17
  * 生成完整的 HTML 页面
18
18
  */
19
19
  function renderPage(data) {
20
- return `<!DOCTYPE html>
21
- <html lang="zh-CN">
22
- <head>
23
- <meta charset="UTF-8">
24
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
25
- <title>${data.title} - AiFastDb Admin</title>
26
- <style>${styles_1.adminStyles}</style>
27
- </head>
28
- <body>
29
- <div class="admin-layout">
30
- ${renderSidebar(data.currentPage, data.version || '1.2.1')}
31
- <div class="admin-main">
32
- ${renderHeader(data.user)}
33
- <main class="admin-content">
34
- ${data.content}
35
- </main>
36
- ${renderFooter(data.version || '1.2.1', data.uptime || '0s')}
37
- </div>
38
- </div>
39
- <script>${getClientScript()}</script>
40
- </body>
20
+ return `<!DOCTYPE html>
21
+ <html lang="zh-CN">
22
+ <head>
23
+ <meta charset="UTF-8">
24
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
25
+ <title>${data.title} - AiFastDb Admin</title>
26
+ <style>${styles_1.adminStyles}</style>
27
+ </head>
28
+ <body>
29
+ <div class="admin-layout">
30
+ ${renderSidebar(data.currentPage, data.version || '1.2.1')}
31
+ <div class="admin-main">
32
+ ${renderHeader(data.user)}
33
+ <main class="admin-content">
34
+ ${data.content}
35
+ </main>
36
+ ${renderFooter(data.version || '1.2.1', data.uptime || '0s')}
37
+ </div>
38
+ </div>
39
+ <script>${getClientScript()}</script>
40
+ </body>
41
41
  </html>`;
42
42
  }
43
43
  /**
@@ -52,284 +52,284 @@ function renderSidebar(currentPage, version) {
52
52
  { id: 'monitoring', icon: 'monitoring', label: '系统监控', href: '/admin/monitoring' },
53
53
  { id: 'users', icon: 'users', label: '用户管理', href: '/admin/users' },
54
54
  ];
55
- return `
56
- <aside class="admin-sidebar">
57
- <div class="sidebar-logo">
58
- ${(0, icons_1.icon)('logo')}
59
- <div>
60
- <h1>AiFastDb</h1>
61
- <span>v${version}</span>
62
- </div>
63
- </div>
64
- <nav class="sidebar-nav">
65
- <div class="nav-section">
66
- <div class="nav-section-title">主菜单</div>
67
- ${navItems.map(item => `
68
- <a href="${item.href}" class="nav-item ${currentPage === item.id ? 'active' : ''}">
69
- ${(0, icons_1.icon)(item.icon)}
70
- <span>${item.label}</span>
71
- </a>
72
- `).join('')}
73
- </div>
74
- <div class="nav-section">
75
- <div class="nav-section-title">设置</div>
76
- <a href="/admin/settings" class="nav-item ${currentPage === 'settings' ? 'active' : ''}">
77
- ${(0, icons_1.icon)('settings')}
78
- <span>系统设置</span>
79
- </a>
80
- </div>
81
- </nav>
82
- </aside>
55
+ return `
56
+ <aside class="admin-sidebar">
57
+ <div class="sidebar-logo">
58
+ ${(0, icons_1.icon)('logo')}
59
+ <div>
60
+ <h1>AiFastDb</h1>
61
+ <span>v${version}</span>
62
+ </div>
63
+ </div>
64
+ <nav class="sidebar-nav">
65
+ <div class="nav-section">
66
+ <div class="nav-section-title">主菜单</div>
67
+ ${navItems.map(item => `
68
+ <a href="${item.href}" class="nav-item ${currentPage === item.id ? 'active' : ''}">
69
+ ${(0, icons_1.icon)(item.icon)}
70
+ <span>${item.label}</span>
71
+ </a>
72
+ `).join('')}
73
+ </div>
74
+ <div class="nav-section">
75
+ <div class="nav-section-title">设置</div>
76
+ <a href="/admin/settings" class="nav-item ${currentPage === 'settings' ? 'active' : ''}">
77
+ ${(0, icons_1.icon)('settings')}
78
+ <span>系统设置</span>
79
+ </a>
80
+ </div>
81
+ </nav>
82
+ </aside>
83
83
  `;
84
84
  }
85
85
  /**
86
86
  * 渲染顶部栏
87
87
  */
88
88
  function renderHeader(user) {
89
- return `
90
- <header class="admin-header">
91
- <div class="search-box">
92
- ${(0, icons_1.icon)('search')}
93
- <input type="text" placeholder="搜索文档、集合..." id="global-search">
94
- </div>
95
- <div class="flex items-center gap-4">
96
- ${user ? `
97
- <div class="flex items-center gap-2">
98
- <span class="text-secondary">${user.username}</span>
99
- <span class="badge badge-info">${user.role}</span>
100
- </div>
101
- <button class="btn btn-secondary btn-sm" onclick="logout()">
102
- ${(0, icons_1.icon)('logout')}
103
- <span>退出</span>
104
- </button>
105
- ` : ''}
106
- </div>
107
- </header>
89
+ return `
90
+ <header class="admin-header">
91
+ <div class="search-box">
92
+ ${(0, icons_1.icon)('search')}
93
+ <input type="text" placeholder="搜索文档、集合..." id="global-search">
94
+ </div>
95
+ <div class="flex items-center gap-4">
96
+ ${user ? `
97
+ <div class="flex items-center gap-2">
98
+ <span class="text-secondary">${user.username}</span>
99
+ <span class="badge badge-info">${user.role}</span>
100
+ </div>
101
+ <button class="btn btn-secondary btn-sm" onclick="logout()">
102
+ ${(0, icons_1.icon)('logout')}
103
+ <span>退出</span>
104
+ </button>
105
+ ` : ''}
106
+ </div>
107
+ </header>
108
108
  `;
109
109
  }
110
110
  /**
111
111
  * 渲染底部栏
112
112
  */
113
113
  function renderFooter(version, uptime) {
114
- return `
115
- <footer class="admin-footer">
116
- <span>AiFastDb v${version}</span>
117
- <span>运行时间: ${uptime}</span>
118
- <span id="health-status">
119
- ${(0, icons_1.icon)('checkCircle')}
120
- <span class="text-success">系统正常</span>
121
- </span>
122
- </footer>
114
+ return `
115
+ <footer class="admin-footer">
116
+ <span>AiFastDb v${version}</span>
117
+ <span>运行时间: ${uptime}</span>
118
+ <span id="health-status">
119
+ ${(0, icons_1.icon)('checkCircle')}
120
+ <span class="text-success">系统正常</span>
121
+ </span>
122
+ </footer>
123
123
  `;
124
124
  }
125
125
  /**
126
126
  * 仪表盘页面内容
127
127
  */
128
128
  function renderDashboard(stats) {
129
- return `
130
- <h1 class="mb-4">仪表盘</h1>
131
-
132
- <div class="stats-grid">
133
- <div class="stat-card">
134
- <div class="stat-icon primary">${(0, icons_1.icon)('documents')}</div>
135
- <div class="stat-content">
136
- <h3>${stats.documentCount.toLocaleString()}</h3>
137
- <p>文档总数</p>
138
- </div>
139
- </div>
140
- <div class="stat-card">
141
- <div class="stat-icon success">${(0, icons_1.icon)('collections')}</div>
142
- <div class="stat-content">
143
- <h3>${stats.collectionCount}</h3>
144
- <p>集合数量</p>
145
- </div>
146
- </div>
147
- <div class="stat-card">
148
- <div class="stat-icon warning">${(0, icons_1.icon)('memory')}</div>
149
- <div class="stat-content">
150
- <h3>${stats.memoryUsed}</h3>
151
- <p>内存使用</p>
152
- </div>
153
- </div>
154
- <div class="stat-card">
155
- <div class="stat-icon info">${(0, icons_1.icon)('clock')}</div>
156
- <div class="stat-content">
157
- <h3>${stats.uptime}</h3>
158
- <p>运行时间</p>
159
- </div>
160
- </div>
161
- </div>
162
-
163
- <div class="grid-2">
164
- <div class="card">
165
- <div class="card-header">
166
- <h2 class="card-title">集合概览</h2>
167
- <a href="/admin/collections" class="btn btn-secondary btn-sm">查看全部</a>
168
- </div>
169
- <div class="table-container">
170
- <table class="table">
171
- <thead>
172
- <tr>
173
- <th>名称</th>
174
- <th>文档数</th>
175
- <th>向量数</th>
176
- <th>操作</th>
177
- </tr>
178
- </thead>
179
- <tbody>
180
- ${stats.collections.length > 0 ? stats.collections.map(col => `
181
- <tr>
182
- <td><a href="/admin/documents?collection=${col.name}">${col.name}</a></td>
183
- <td>${col.documentCount.toLocaleString()}</td>
184
- <td>${col.vectorCount.toLocaleString()}</td>
185
- <td>
186
- <button class="btn btn-secondary btn-sm btn-icon" onclick="viewCollection('${col.name}')">
187
- ${(0, icons_1.icon)('eye')}
188
- </button>
189
- </td>
190
- </tr>
191
- `).join('') : `
192
- <tr>
193
- <td colspan="4" class="text-center text-muted">暂无集合</td>
194
- </tr>
195
- `}
196
- </tbody>
197
- </table>
198
- </div>
199
- </div>
200
-
201
- <div class="card">
202
- <div class="card-header">
203
- <h2 class="card-title">最近文档</h2>
204
- <a href="/admin/documents" class="btn btn-secondary btn-sm">查看全部</a>
205
- </div>
206
- <div class="table-container">
207
- <table class="table">
208
- <thead>
209
- <tr>
210
- <th>内容</th>
211
- <th>集合</th>
212
- <th>时间</th>
213
- </tr>
214
- </thead>
215
- <tbody>
216
- ${stats.recentDocs.length > 0 ? stats.recentDocs.map(doc => `
217
- <tr>
218
- <td class="truncate" style="max-width: 200px;">${escapeHtml(doc.content)}</td>
219
- <td><span class="badge badge-info">${doc.collection}</span></td>
220
- <td class="text-muted">${formatTime(doc.createdAt)}</td>
221
- </tr>
222
- `).join('') : `
223
- <tr>
224
- <td colspan="3" class="text-center text-muted">暂无文档</td>
225
- </tr>
226
- `}
227
- </tbody>
228
- </table>
229
- </div>
230
- </div>
231
- </div>
129
+ return `
130
+ <h1 class="mb-4">仪表盘</h1>
131
+
132
+ <div class="stats-grid">
133
+ <div class="stat-card">
134
+ <div class="stat-icon primary">${(0, icons_1.icon)('documents')}</div>
135
+ <div class="stat-content">
136
+ <h3>${stats.documentCount.toLocaleString()}</h3>
137
+ <p>文档总数</p>
138
+ </div>
139
+ </div>
140
+ <div class="stat-card">
141
+ <div class="stat-icon success">${(0, icons_1.icon)('collections')}</div>
142
+ <div class="stat-content">
143
+ <h3>${stats.collectionCount}</h3>
144
+ <p>集合数量</p>
145
+ </div>
146
+ </div>
147
+ <div class="stat-card">
148
+ <div class="stat-icon warning">${(0, icons_1.icon)('memory')}</div>
149
+ <div class="stat-content">
150
+ <h3>${stats.memoryUsed}</h3>
151
+ <p>内存使用</p>
152
+ </div>
153
+ </div>
154
+ <div class="stat-card">
155
+ <div class="stat-icon info">${(0, icons_1.icon)('clock')}</div>
156
+ <div class="stat-content">
157
+ <h3>${stats.uptime}</h3>
158
+ <p>运行时间</p>
159
+ </div>
160
+ </div>
161
+ </div>
162
+
163
+ <div class="grid-2">
164
+ <div class="card">
165
+ <div class="card-header">
166
+ <h2 class="card-title">集合概览</h2>
167
+ <a href="/admin/collections" class="btn btn-secondary btn-sm">查看全部</a>
168
+ </div>
169
+ <div class="table-container">
170
+ <table class="table">
171
+ <thead>
172
+ <tr>
173
+ <th>名称</th>
174
+ <th>文档数</th>
175
+ <th>向量数</th>
176
+ <th>操作</th>
177
+ </tr>
178
+ </thead>
179
+ <tbody>
180
+ ${stats.collections.length > 0 ? stats.collections.map(col => `
181
+ <tr>
182
+ <td><a href="/admin/documents?collection=${col.name}">${col.name}</a></td>
183
+ <td>${col.documentCount.toLocaleString()}</td>
184
+ <td>${col.vectorCount.toLocaleString()}</td>
185
+ <td>
186
+ <button class="btn btn-secondary btn-sm btn-icon" onclick="viewCollection('${col.name}')">
187
+ ${(0, icons_1.icon)('eye')}
188
+ </button>
189
+ </td>
190
+ </tr>
191
+ `).join('') : `
192
+ <tr>
193
+ <td colspan="4" class="text-center text-muted">暂无集合</td>
194
+ </tr>
195
+ `}
196
+ </tbody>
197
+ </table>
198
+ </div>
199
+ </div>
200
+
201
+ <div class="card">
202
+ <div class="card-header">
203
+ <h2 class="card-title">最近文档</h2>
204
+ <a href="/admin/documents" class="btn btn-secondary btn-sm">查看全部</a>
205
+ </div>
206
+ <div class="table-container">
207
+ <table class="table">
208
+ <thead>
209
+ <tr>
210
+ <th>内容</th>
211
+ <th>集合</th>
212
+ <th>时间</th>
213
+ </tr>
214
+ </thead>
215
+ <tbody>
216
+ ${stats.recentDocs.length > 0 ? stats.recentDocs.map(doc => `
217
+ <tr>
218
+ <td class="truncate" style="max-width: 200px;">${escapeHtml(doc.content)}</td>
219
+ <td><span class="badge badge-info">${doc.collection}</span></td>
220
+ <td class="text-muted">${formatTime(doc.createdAt)}</td>
221
+ </tr>
222
+ `).join('') : `
223
+ <tr>
224
+ <td colspan="3" class="text-center text-muted">暂无文档</td>
225
+ </tr>
226
+ `}
227
+ </tbody>
228
+ </table>
229
+ </div>
230
+ </div>
231
+ </div>
232
232
  `;
233
233
  }
234
234
  /**
235
235
  * 集合管理页面内容
236
236
  */
237
237
  function renderCollections(collections) {
238
- return `
239
- <div class="flex justify-between items-center mb-4">
240
- <h1>集合管理</h1>
241
- <button class="btn btn-primary" onclick="showCreateCollectionModal()">
242
- ${(0, icons_1.icon)('plus')}
243
- <span>新建集合</span>
244
- </button>
245
- </div>
246
-
247
- <div class="card">
248
- <div class="table-container">
249
- <table class="table">
250
- <thead>
251
- <tr>
252
- <th>名称</th>
253
- <th>描述</th>
254
- <th>维度</th>
255
- <th>文档数</th>
256
- <th>向量数</th>
257
- <th>创建时间</th>
258
- <th>操作</th>
259
- </tr>
260
- </thead>
261
- <tbody>
262
- ${collections.length > 0 ? collections.map(col => `
263
- <tr>
264
- <td><strong>${col.name}</strong></td>
265
- <td class="text-muted">${col.description || '-'}</td>
266
- <td>${col.dimension}</td>
267
- <td>${col.documentCount.toLocaleString()}</td>
268
- <td>${col.vectorCount.toLocaleString()}</td>
269
- <td class="text-muted">${formatTime(col.createdAt)}</td>
270
- <td>
271
- <div class="flex gap-2">
272
- <a href="/admin/documents?collection=${col.name}" class="btn btn-secondary btn-sm btn-icon" title="浏览文档">
273
- ${(0, icons_1.icon)('eye')}
274
- </a>
275
- ${col.name !== 'default' ? `
276
- <button class="btn btn-danger btn-sm btn-icon" onclick="confirmDeleteCollection('${col.name}')" title="删除">
277
- ${(0, icons_1.icon)('trash')}
278
- </button>
279
- ` : ''}
280
- </div>
281
- </td>
282
- </tr>
283
- `).join('') : `
284
- <tr>
285
- <td colspan="7">
286
- <div class="empty-state">
287
- ${(0, icons_1.icon)('empty')}
288
- <h3>暂无集合</h3>
289
- <p>点击上方按钮创建第一个集合</p>
290
- </div>
291
- </td>
292
- </tr>
293
- `}
294
- </tbody>
295
- </table>
296
- </div>
297
- </div>
298
-
299
- <!-- 创建集合模态框 -->
300
- <div class="modal-overlay" id="create-collection-modal">
301
- <div class="modal">
302
- <div class="modal-header">
303
- <h2>新建集合</h2>
304
- <button class="modal-close" onclick="closeModal('create-collection-modal')">
305
- ${(0, icons_1.icon)('close')}
306
- </button>
307
- </div>
308
- <div class="modal-body">
309
- <form id="create-collection-form" onsubmit="createCollection(event)">
310
- <div class="form-group">
311
- <label class="form-label">集合名称 *</label>
312
- <input type="text" class="form-input" name="name" required pattern="[a-zA-Z0-9_-]+"
313
- placeholder="例如: articles, users">
314
- <small class="text-muted">只能包含字母、数字、下划线和横线</small>
315
- </div>
316
- <div class="form-group">
317
- <label class="form-label">向量维度 *</label>
318
- <input type="number" class="form-input" name="dimension" required min="1" max="4096" value="1536"
319
- placeholder="例如: 1536 (OpenAI), 768 (BERT)">
320
- </div>
321
- <div class="form-group">
322
- <label class="form-label">描述</label>
323
- <textarea class="form-textarea" name="description" placeholder="可选的集合描述"></textarea>
324
- </div>
325
- </form>
326
- </div>
327
- <div class="modal-footer">
328
- <button class="btn btn-secondary" onclick="closeModal('create-collection-modal')">取消</button>
329
- <button class="btn btn-primary" type="submit" form="create-collection-form">创建</button>
330
- </div>
331
- </div>
332
- </div>
238
+ return `
239
+ <div class="flex justify-between items-center mb-4">
240
+ <h1>集合管理</h1>
241
+ <button class="btn btn-primary" onclick="showCreateCollectionModal()">
242
+ ${(0, icons_1.icon)('plus')}
243
+ <span>新建集合</span>
244
+ </button>
245
+ </div>
246
+
247
+ <div class="card">
248
+ <div class="table-container">
249
+ <table class="table">
250
+ <thead>
251
+ <tr>
252
+ <th>名称</th>
253
+ <th>描述</th>
254
+ <th>维度</th>
255
+ <th>文档数</th>
256
+ <th>向量数</th>
257
+ <th>创建时间</th>
258
+ <th>操作</th>
259
+ </tr>
260
+ </thead>
261
+ <tbody>
262
+ ${collections.length > 0 ? collections.map(col => `
263
+ <tr>
264
+ <td><strong>${col.name}</strong></td>
265
+ <td class="text-muted">${col.description || '-'}</td>
266
+ <td>${col.dimension}</td>
267
+ <td>${col.documentCount.toLocaleString()}</td>
268
+ <td>${col.vectorCount.toLocaleString()}</td>
269
+ <td class="text-muted">${formatTime(col.createdAt)}</td>
270
+ <td>
271
+ <div class="flex gap-2">
272
+ <a href="/admin/documents?collection=${col.name}" class="btn btn-secondary btn-sm btn-icon" title="浏览文档">
273
+ ${(0, icons_1.icon)('eye')}
274
+ </a>
275
+ ${col.name !== 'default' ? `
276
+ <button class="btn btn-danger btn-sm btn-icon" onclick="confirmDeleteCollection('${col.name}')" title="删除">
277
+ ${(0, icons_1.icon)('trash')}
278
+ </button>
279
+ ` : ''}
280
+ </div>
281
+ </td>
282
+ </tr>
283
+ `).join('') : `
284
+ <tr>
285
+ <td colspan="7">
286
+ <div class="empty-state">
287
+ ${(0, icons_1.icon)('empty')}
288
+ <h3>暂无集合</h3>
289
+ <p>点击上方按钮创建第一个集合</p>
290
+ </div>
291
+ </td>
292
+ </tr>
293
+ `}
294
+ </tbody>
295
+ </table>
296
+ </div>
297
+ </div>
298
+
299
+ <!-- 创建集合模态框 -->
300
+ <div class="modal-overlay" id="create-collection-modal">
301
+ <div class="modal">
302
+ <div class="modal-header">
303
+ <h2>新建集合</h2>
304
+ <button class="modal-close" onclick="closeModal('create-collection-modal')">
305
+ ${(0, icons_1.icon)('close')}
306
+ </button>
307
+ </div>
308
+ <div class="modal-body">
309
+ <form id="create-collection-form" onsubmit="createCollection(event)">
310
+ <div class="form-group">
311
+ <label class="form-label">集合名称 *</label>
312
+ <input type="text" class="form-input" name="name" required pattern="[a-zA-Z0-9_-]+"
313
+ placeholder="例如: articles, users">
314
+ <small class="text-muted">只能包含字母、数字、下划线和横线</small>
315
+ </div>
316
+ <div class="form-group">
317
+ <label class="form-label">向量维度 *</label>
318
+ <input type="number" class="form-input" name="dimension" required min="1" max="4096" value="1536"
319
+ placeholder="例如: 1536 (OpenAI), 768 (BERT)">
320
+ </div>
321
+ <div class="form-group">
322
+ <label class="form-label">描述</label>
323
+ <textarea class="form-textarea" name="description" placeholder="可选的集合描述"></textarea>
324
+ </div>
325
+ </form>
326
+ </div>
327
+ <div class="modal-footer">
328
+ <button class="btn btn-secondary" onclick="closeModal('create-collection-modal')">取消</button>
329
+ <button class="btn btn-primary" type="submit" form="create-collection-form">创建</button>
330
+ </div>
331
+ </div>
332
+ </div>
333
333
  `;
334
334
  }
335
335
  /**
@@ -337,199 +337,199 @@ function renderCollections(collections) {
337
337
  */
338
338
  function renderDocuments(data) {
339
339
  const totalPages = Math.ceil(data.pagination.total / data.pagination.pageSize);
340
- return `
341
- <div class="flex justify-between items-center mb-4">
342
- <h1>文档浏览</h1>
343
- <div class="flex gap-2">
344
- <select class="form-select" style="width: auto;" onchange="changeCollection(this.value)">
345
- ${data.collections.map(col => `
346
- <option value="${col}" ${col === data.collection ? 'selected' : ''}>${col}</option>
347
- `).join('')}
348
- </select>
349
- <button class="btn btn-secondary" onclick="refreshDocuments()">
350
- ${(0, icons_1.icon)('refresh')}
351
- </button>
352
- <button class="btn btn-primary" onclick="exportDocuments()">
353
- ${(0, icons_1.icon)('download')}
354
- <span>导出</span>
355
- </button>
356
- </div>
357
- </div>
358
-
359
- <div class="card mb-4">
360
- <div class="flex gap-4 items-center">
361
- <div class="search-box" style="flex: 1;">
362
- ${(0, icons_1.icon)('search')}
363
- <input type="text" placeholder="搜索文档内容..." id="doc-search" onkeyup="searchDocuments(this.value)">
364
- </div>
365
- <select class="form-select" style="width: auto;" id="tag-filter" onchange="filterByTag(this.value)">
366
- <option value="">所有标签</option>
367
- </select>
368
- </div>
369
- </div>
370
-
371
- <div class="card">
372
- <div class="table-container">
373
- <table class="table">
374
- <thead>
375
- <tr>
376
- <th style="width: 80px;">ID</th>
377
- <th>内容</th>
378
- <th>标签</th>
379
- <th>重要性</th>
380
- <th>创建时间</th>
381
- <th style="width: 100px;">操作</th>
382
- </tr>
383
- </thead>
384
- <tbody id="documents-tbody">
385
- ${data.documents.length > 0 ? data.documents.map(doc => `
386
- <tr>
387
- <td class="truncate" style="max-width: 80px;" title="${doc.id}">${doc.id.slice(0, 8)}...</td>
388
- <td class="truncate" style="max-width: 300px;">${escapeHtml(doc.content)}</td>
389
- <td>
390
- ${doc.tags.map(tag => `<span class="badge badge-info">${tag}</span>`).join(' ')}
391
- </td>
392
- <td>
393
- <div class="flex items-center gap-2">
394
- <div style="width: 60px; height: 6px; background: var(--bg-dark); border-radius: 3px;">
395
- <div style="width: ${doc.importance * 100}%; height: 100%; background: var(--primary); border-radius: 3px;"></div>
396
- </div>
397
- <span class="text-muted">${(doc.importance * 100).toFixed(0)}%</span>
398
- </div>
399
- </td>
400
- <td class="text-muted">${formatTime(doc.createdAt)}</td>
401
- <td>
402
- <div class="flex gap-2">
403
- <button class="btn btn-secondary btn-sm btn-icon" onclick="viewDocument('${doc.id}')" title="查看详情">
404
- ${(0, icons_1.icon)('eye')}
405
- </button>
406
- <button class="btn btn-danger btn-sm btn-icon" onclick="confirmDeleteDocument('${doc.id}')" title="删除">
407
- ${(0, icons_1.icon)('trash')}
408
- </button>
409
- </div>
410
- </td>
411
- </tr>
412
- `).join('') : `
413
- <tr>
414
- <td colspan="6">
415
- <div class="empty-state">
416
- ${(0, icons_1.icon)('empty')}
417
- <h3>暂无文档</h3>
418
- <p>该集合中还没有任何文档</p>
419
- </div>
420
- </td>
421
- </tr>
422
- `}
423
- </tbody>
424
- </table>
425
- </div>
426
-
427
- ${data.pagination.total > data.pagination.pageSize ? `
428
- <div class="flex justify-between items-center mt-4">
429
- <span class="pagination-info">
430
- 共 ${data.pagination.total} 条,第 ${data.pagination.page} / ${totalPages} 页
431
- </span>
432
- <div class="pagination">
433
- <button class="pagination-btn" onclick="goToPage(${data.pagination.page - 1})" ${data.pagination.page <= 1 ? 'disabled' : ''}>
434
- ${(0, icons_1.icon)('chevronLeft')}
435
- </button>
436
- ${generatePaginationButtons(data.pagination.page, totalPages)}
437
- <button class="pagination-btn" onclick="goToPage(${data.pagination.page + 1})" ${data.pagination.page >= totalPages ? 'disabled' : ''}>
438
- ${(0, icons_1.icon)('chevronRight')}
439
- </button>
440
- </div>
441
- </div>
442
- ` : ''}
443
- </div>
444
-
445
- <!-- 文档详情模态框 -->
446
- <div class="modal-overlay" id="document-detail-modal">
447
- <div class="modal" style="max-width: 700px;">
448
- <div class="modal-header">
449
- <h2>文档详情</h2>
450
- <button class="modal-close" onclick="closeModal('document-detail-modal')">
451
- ${(0, icons_1.icon)('close')}
452
- </button>
453
- </div>
454
- <div class="modal-body" id="document-detail-content">
455
- <!-- 内容由 JS 动态填充 -->
456
- </div>
457
- <div class="modal-footer">
458
- <button class="btn btn-secondary" onclick="closeModal('document-detail-modal')">关闭</button>
459
- </div>
460
- </div>
461
- </div>
340
+ return `
341
+ <div class="flex justify-between items-center mb-4">
342
+ <h1>文档浏览</h1>
343
+ <div class="flex gap-2">
344
+ <select class="form-select" style="width: auto;" onchange="changeCollection(this.value)">
345
+ ${data.collections.map(col => `
346
+ <option value="${col}" ${col === data.collection ? 'selected' : ''}>${col}</option>
347
+ `).join('')}
348
+ </select>
349
+ <button class="btn btn-secondary" onclick="refreshDocuments()">
350
+ ${(0, icons_1.icon)('refresh')}
351
+ </button>
352
+ <button class="btn btn-primary" onclick="exportDocuments()">
353
+ ${(0, icons_1.icon)('download')}
354
+ <span>导出</span>
355
+ </button>
356
+ </div>
357
+ </div>
358
+
359
+ <div class="card mb-4">
360
+ <div class="flex gap-4 items-center">
361
+ <div class="search-box" style="flex: 1;">
362
+ ${(0, icons_1.icon)('search')}
363
+ <input type="text" placeholder="搜索文档内容..." id="doc-search" onkeyup="searchDocuments(this.value)">
364
+ </div>
365
+ <select class="form-select" style="width: auto;" id="tag-filter" onchange="filterByTag(this.value)">
366
+ <option value="">所有标签</option>
367
+ </select>
368
+ </div>
369
+ </div>
370
+
371
+ <div class="card">
372
+ <div class="table-container">
373
+ <table class="table">
374
+ <thead>
375
+ <tr>
376
+ <th style="width: 80px;">ID</th>
377
+ <th>内容</th>
378
+ <th>标签</th>
379
+ <th>重要性</th>
380
+ <th>创建时间</th>
381
+ <th style="width: 100px;">操作</th>
382
+ </tr>
383
+ </thead>
384
+ <tbody id="documents-tbody">
385
+ ${data.documents.length > 0 ? data.documents.map(doc => `
386
+ <tr>
387
+ <td class="truncate" style="max-width: 80px;" title="${doc.id}">${doc.id.slice(0, 8)}...</td>
388
+ <td class="truncate" style="max-width: 300px;">${escapeHtml(doc.content)}</td>
389
+ <td>
390
+ ${doc.tags.map(tag => `<span class="badge badge-info">${tag}</span>`).join(' ')}
391
+ </td>
392
+ <td>
393
+ <div class="flex items-center gap-2">
394
+ <div style="width: 60px; height: 6px; background: var(--bg-dark); border-radius: 3px;">
395
+ <div style="width: ${doc.importance * 100}%; height: 100%; background: var(--primary); border-radius: 3px;"></div>
396
+ </div>
397
+ <span class="text-muted">${(doc.importance * 100).toFixed(0)}%</span>
398
+ </div>
399
+ </td>
400
+ <td class="text-muted">${formatTime(doc.createdAt)}</td>
401
+ <td>
402
+ <div class="flex gap-2">
403
+ <button class="btn btn-secondary btn-sm btn-icon" onclick="viewDocument('${doc.id}')" title="查看详情">
404
+ ${(0, icons_1.icon)('eye')}
405
+ </button>
406
+ <button class="btn btn-danger btn-sm btn-icon" onclick="confirmDeleteDocument('${doc.id}')" title="删除">
407
+ ${(0, icons_1.icon)('trash')}
408
+ </button>
409
+ </div>
410
+ </td>
411
+ </tr>
412
+ `).join('') : `
413
+ <tr>
414
+ <td colspan="6">
415
+ <div class="empty-state">
416
+ ${(0, icons_1.icon)('empty')}
417
+ <h3>暂无文档</h3>
418
+ <p>该集合中还没有任何文档</p>
419
+ </div>
420
+ </td>
421
+ </tr>
422
+ `}
423
+ </tbody>
424
+ </table>
425
+ </div>
426
+
427
+ ${data.pagination.total > data.pagination.pageSize ? `
428
+ <div class="flex justify-between items-center mt-4">
429
+ <span class="pagination-info">
430
+ 共 ${data.pagination.total} 条,第 ${data.pagination.page} / ${totalPages} 页
431
+ </span>
432
+ <div class="pagination">
433
+ <button class="pagination-btn" onclick="goToPage(${data.pagination.page - 1})" ${data.pagination.page <= 1 ? 'disabled' : ''}>
434
+ ${(0, icons_1.icon)('chevronLeft')}
435
+ </button>
436
+ ${generatePaginationButtons(data.pagination.page, totalPages)}
437
+ <button class="pagination-btn" onclick="goToPage(${data.pagination.page + 1})" ${data.pagination.page >= totalPages ? 'disabled' : ''}>
438
+ ${(0, icons_1.icon)('chevronRight')}
439
+ </button>
440
+ </div>
441
+ </div>
442
+ ` : ''}
443
+ </div>
444
+
445
+ <!-- 文档详情模态框 -->
446
+ <div class="modal-overlay" id="document-detail-modal">
447
+ <div class="modal" style="max-width: 700px;">
448
+ <div class="modal-header">
449
+ <h2>文档详情</h2>
450
+ <button class="modal-close" onclick="closeModal('document-detail-modal')">
451
+ ${(0, icons_1.icon)('close')}
452
+ </button>
453
+ </div>
454
+ <div class="modal-body" id="document-detail-content">
455
+ <!-- 内容由 JS 动态填充 -->
456
+ </div>
457
+ <div class="modal-footer">
458
+ <button class="btn btn-secondary" onclick="closeModal('document-detail-modal')">关闭</button>
459
+ </div>
460
+ </div>
461
+ </div>
462
462
  `;
463
463
  }
464
464
  /**
465
465
  * 搜索测试页面内容
466
466
  */
467
467
  function renderSearch(collections) {
468
- return `
469
- <h1 class="mb-4">搜索测试</h1>
470
-
471
- <div class="grid-2">
472
- <div class="card">
473
- <h2 class="card-title mb-4">搜索参数</h2>
474
- <form id="search-form" onsubmit="performSearch(event)">
475
- <div class="form-group">
476
- <label class="form-label">目标集合</label>
477
- <select class="form-select" name="collection">
478
- <option value="">所有集合</option>
479
- ${collections.map(col => `<option value="${col}">${col}</option>`).join('')}
480
- </select>
481
- </div>
482
- <div class="form-group">
483
- <label class="form-label">搜索查询 *</label>
484
- <textarea class="form-textarea" name="query" required placeholder="输入搜索文本..."></textarea>
485
- </div>
486
- <div class="form-group">
487
- <label class="form-label">向量 (可选)</label>
488
- <textarea class="form-textarea" name="vector" placeholder="粘贴 embedding 向量 JSON 数组,例如: [0.1, 0.2, ...]"
489
- style="font-family: monospace; font-size: 12px;"></textarea>
490
- </div>
491
- <div class="grid-2">
492
- <div class="form-group">
493
- <label class="form-label">结果数量</label>
494
- <input type="number" class="form-input" name="limit" value="10" min="1" max="100">
495
- </div>
496
- <div class="form-group">
497
- <label class="form-label">最低重要性</label>
498
- <input type="number" class="form-input" name="minImportance" value="0" min="0" max="1" step="0.1">
499
- </div>
500
- </div>
501
- <div class="form-group">
502
- <label class="form-label">标签过滤</label>
503
- <input type="text" class="form-input" name="tags" placeholder="多个标签用逗号分隔">
504
- </div>
505
- <button type="submit" class="btn btn-primary" style="width: 100%;">
506
- ${(0, icons_1.icon)('search')}
507
- <span>执行搜索</span>
508
- </button>
509
- </form>
510
- </div>
511
-
512
- <div class="card">
513
- <div class="card-header">
514
- <h2 class="card-title">搜索结果</h2>
515
- <span class="text-muted" id="search-time"></span>
516
- </div>
517
- <div id="search-results">
518
- <div class="empty-state">
519
- ${(0, icons_1.icon)('search')}
520
- <h3>等待搜索</h3>
521
- <p>在左侧输入搜索条件并点击执行</p>
522
- </div>
523
- </div>
524
- </div>
525
- </div>
526
-
527
- <div class="card mt-4">
528
- <h2 class="card-title mb-4">搜索历史</h2>
529
- <div id="search-history">
530
- <p class="text-muted text-center">暂无搜索历史</p>
531
- </div>
532
- </div>
468
+ return `
469
+ <h1 class="mb-4">搜索测试</h1>
470
+
471
+ <div class="grid-2">
472
+ <div class="card">
473
+ <h2 class="card-title mb-4">搜索参数</h2>
474
+ <form id="search-form" onsubmit="performSearch(event)">
475
+ <div class="form-group">
476
+ <label class="form-label">目标集合</label>
477
+ <select class="form-select" name="collection">
478
+ <option value="">所有集合</option>
479
+ ${collections.map(col => `<option value="${col}">${col}</option>`).join('')}
480
+ </select>
481
+ </div>
482
+ <div class="form-group">
483
+ <label class="form-label">搜索查询 *</label>
484
+ <textarea class="form-textarea" name="query" required placeholder="输入搜索文本..."></textarea>
485
+ </div>
486
+ <div class="form-group">
487
+ <label class="form-label">向量 (可选)</label>
488
+ <textarea class="form-textarea" name="vector" placeholder="粘贴 embedding 向量 JSON 数组,例如: [0.1, 0.2, ...]"
489
+ style="font-family: monospace; font-size: 12px;"></textarea>
490
+ </div>
491
+ <div class="grid-2">
492
+ <div class="form-group">
493
+ <label class="form-label">结果数量</label>
494
+ <input type="number" class="form-input" name="limit" value="10" min="1" max="100">
495
+ </div>
496
+ <div class="form-group">
497
+ <label class="form-label">最低重要性</label>
498
+ <input type="number" class="form-input" name="minImportance" value="0" min="0" max="1" step="0.1">
499
+ </div>
500
+ </div>
501
+ <div class="form-group">
502
+ <label class="form-label">标签过滤</label>
503
+ <input type="text" class="form-input" name="tags" placeholder="多个标签用逗号分隔">
504
+ </div>
505
+ <button type="submit" class="btn btn-primary" style="width: 100%;">
506
+ ${(0, icons_1.icon)('search')}
507
+ <span>执行搜索</span>
508
+ </button>
509
+ </form>
510
+ </div>
511
+
512
+ <div class="card">
513
+ <div class="card-header">
514
+ <h2 class="card-title">搜索结果</h2>
515
+ <span class="text-muted" id="search-time"></span>
516
+ </div>
517
+ <div id="search-results">
518
+ <div class="empty-state">
519
+ ${(0, icons_1.icon)('search')}
520
+ <h3>等待搜索</h3>
521
+ <p>在左侧输入搜索条件并点击执行</p>
522
+ </div>
523
+ </div>
524
+ </div>
525
+ </div>
526
+
527
+ <div class="card mt-4">
528
+ <h2 class="card-title mb-4">搜索历史</h2>
529
+ <div id="search-history">
530
+ <p class="text-muted text-center">暂无搜索历史</p>
531
+ </div>
532
+ </div>
533
533
  `;
534
534
  }
535
535
  /**
@@ -537,86 +537,86 @@ function renderSearch(collections) {
537
537
  */
538
538
  function renderMonitoring(metrics) {
539
539
  const memoryPercent = ((metrics.memory.heapUsed / metrics.memory.heapTotal) * 100).toFixed(1);
540
- return `
541
- <div class="flex justify-between items-center mb-4">
542
- <h1>系统监控</h1>
543
- <button class="btn btn-secondary" onclick="refreshMetrics()">
544
- ${(0, icons_1.icon)('refresh')}
545
- <span>刷新</span>
546
- </button>
547
- </div>
548
-
549
- <div class="stats-grid">
550
- <div class="stat-card">
551
- <div class="stat-icon warning">${(0, icons_1.icon)('memory')}</div>
552
- <div class="stat-content">
553
- <h3>${formatBytes(metrics.memory.heapUsed)}</h3>
554
- <p>堆内存使用</p>
555
- <div class="stat-trend">
556
- 总计 ${formatBytes(metrics.memory.heapTotal)} (${memoryPercent}%)
557
- </div>
558
- </div>
559
- </div>
560
- <div class="stat-card">
561
- <div class="stat-icon info">${(0, icons_1.icon)('server')}</div>
562
- <div class="stat-content">
563
- <h3>${formatBytes(metrics.memory.rss)}</h3>
564
- <p>RSS 内存</p>
565
- </div>
566
- </div>
567
- <div class="stat-card">
568
- <div class="stat-icon success">${(0, icons_1.icon)('clock')}</div>
569
- <div class="stat-content">
570
- <h3>${formatUptime(metrics.uptime)}</h3>
571
- <p>运行时间</p>
572
- </div>
573
- </div>
574
- <div class="stat-card">
575
- <div class="stat-icon primary">${(0, icons_1.icon)('database')}</div>
576
- <div class="stat-content">
577
- <h3>${metrics.queries?.total || 0}</h3>
578
- <p>查询总数</p>
579
- <div class="stat-trend">
580
- 平均延迟 ${metrics.queries?.avgLatency || 0}ms
581
- </div>
582
- </div>
583
- </div>
584
- </div>
585
-
586
- <div class="grid-2">
587
- <div class="card">
588
- <h2 class="card-title mb-4">内存使用趋势</h2>
589
- <canvas id="memory-chart" height="200"></canvas>
590
- </div>
591
- <div class="card">
592
- <h2 class="card-title mb-4">查询延迟分布</h2>
593
- <canvas id="latency-chart" height="200"></canvas>
594
- </div>
595
- </div>
596
-
597
- <div class="card mt-4">
598
- <h2 class="card-title mb-4">系统信息</h2>
599
- <div class="table-container">
600
- <table class="table">
601
- <tbody>
602
- <tr><td class="text-muted">Node.js 版本</td><td>${process.version}</td></tr>
603
- <tr><td class="text-muted">平台</td><td>${process.platform}</td></tr>
604
- <tr><td class="text-muted">架构</td><td>${process.arch}</td></tr>
605
- <tr><td class="text-muted">进程 PID</td><td>${process.pid}</td></tr>
606
- <tr><td class="text-muted">外部内存</td><td>${formatBytes(metrics.memory.external)}</td></tr>
607
- </tbody>
608
- </table>
609
- </div>
610
- </div>
611
-
612
- <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
613
- <script>
614
- // 初始化图表
615
- document.addEventListener('DOMContentLoaded', () => {
616
- initCharts();
617
- setInterval(refreshMetrics, 5000);
618
- });
619
- </script>
540
+ return `
541
+ <div class="flex justify-between items-center mb-4">
542
+ <h1>系统监控</h1>
543
+ <button class="btn btn-secondary" onclick="refreshMetrics()">
544
+ ${(0, icons_1.icon)('refresh')}
545
+ <span>刷新</span>
546
+ </button>
547
+ </div>
548
+
549
+ <div class="stats-grid">
550
+ <div class="stat-card">
551
+ <div class="stat-icon warning">${(0, icons_1.icon)('memory')}</div>
552
+ <div class="stat-content">
553
+ <h3>${formatBytes(metrics.memory.heapUsed)}</h3>
554
+ <p>堆内存使用</p>
555
+ <div class="stat-trend">
556
+ 总计 ${formatBytes(metrics.memory.heapTotal)} (${memoryPercent}%)
557
+ </div>
558
+ </div>
559
+ </div>
560
+ <div class="stat-card">
561
+ <div class="stat-icon info">${(0, icons_1.icon)('server')}</div>
562
+ <div class="stat-content">
563
+ <h3>${formatBytes(metrics.memory.rss)}</h3>
564
+ <p>RSS 内存</p>
565
+ </div>
566
+ </div>
567
+ <div class="stat-card">
568
+ <div class="stat-icon success">${(0, icons_1.icon)('clock')}</div>
569
+ <div class="stat-content">
570
+ <h3>${formatUptime(metrics.uptime)}</h3>
571
+ <p>运行时间</p>
572
+ </div>
573
+ </div>
574
+ <div class="stat-card">
575
+ <div class="stat-icon primary">${(0, icons_1.icon)('database')}</div>
576
+ <div class="stat-content">
577
+ <h3>${metrics.queries?.total || 0}</h3>
578
+ <p>查询总数</p>
579
+ <div class="stat-trend">
580
+ 平均延迟 ${metrics.queries?.avgLatency || 0}ms
581
+ </div>
582
+ </div>
583
+ </div>
584
+ </div>
585
+
586
+ <div class="grid-2">
587
+ <div class="card">
588
+ <h2 class="card-title mb-4">内存使用趋势</h2>
589
+ <canvas id="memory-chart" height="200"></canvas>
590
+ </div>
591
+ <div class="card">
592
+ <h2 class="card-title mb-4">查询延迟分布</h2>
593
+ <canvas id="latency-chart" height="200"></canvas>
594
+ </div>
595
+ </div>
596
+
597
+ <div class="card mt-4">
598
+ <h2 class="card-title mb-4">系统信息</h2>
599
+ <div class="table-container">
600
+ <table class="table">
601
+ <tbody>
602
+ <tr><td class="text-muted">Node.js 版本</td><td>${process.version}</td></tr>
603
+ <tr><td class="text-muted">平台</td><td>${process.platform}</td></tr>
604
+ <tr><td class="text-muted">架构</td><td>${process.arch}</td></tr>
605
+ <tr><td class="text-muted">进程 PID</td><td>${process.pid}</td></tr>
606
+ <tr><td class="text-muted">外部内存</td><td>${formatBytes(metrics.memory.external)}</td></tr>
607
+ </tbody>
608
+ </table>
609
+ </div>
610
+ </div>
611
+
612
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
613
+ <script>
614
+ // 初始化图表
615
+ document.addEventListener('DOMContentLoaded', () => {
616
+ initCharts();
617
+ setInterval(refreshMetrics, 5000);
618
+ });
619
+ </script>
620
620
  `;
621
621
  }
622
622
  /**
@@ -743,10 +743,10 @@ function generatePaginationButtons(current, total) {
743
743
  start = Math.max(1, end - maxButtons + 1);
744
744
  }
745
745
  for (let i = start; i <= end; i++) {
746
- buttons.push(`
747
- <button class="pagination-btn ${i === current ? 'active' : ''}" onclick="goToPage(${i})">
748
- ${i}
749
- </button>
746
+ buttons.push(`
747
+ <button class="pagination-btn ${i === current ? 'active' : ''}" onclick="goToPage(${i})">
748
+ ${i}
749
+ </button>
750
750
  `);
751
751
  }
752
752
  return buttons.join('');
@@ -755,326 +755,326 @@ function generatePaginationButtons(current, total) {
755
755
  * 客户端 JavaScript
756
756
  */
757
757
  function getClientScript() {
758
- return `
759
- // 全局状态
760
- let currentCollection = new URLSearchParams(window.location.search).get('collection') || 'default';
761
- let currentPage = 1;
762
-
763
- // 模态框控制
764
- function showModal(id) {
765
- document.getElementById(id).classList.add('active');
766
- }
767
-
768
- function closeModal(id) {
769
- document.getElementById(id).classList.remove('active');
770
- }
771
-
772
- // 集合操作
773
- function showCreateCollectionModal() {
774
- showModal('create-collection-modal');
775
- }
776
-
777
- async function createCollection(e) {
778
- e.preventDefault();
779
- const form = e.target;
780
- const data = {
781
- name: form.name.value,
782
- dimension: parseInt(form.dimension.value),
783
- description: form.description.value
784
- };
785
-
786
- try {
787
- const res = await fetch('/api/v1/admin/collections', {
788
- method: 'POST',
789
- headers: { 'Content-Type': 'application/json' },
790
- body: JSON.stringify(data)
791
- });
792
-
793
- if (res.ok) {
794
- closeModal('create-collection-modal');
795
- location.reload();
796
- } else {
797
- const err = await res.json();
798
- alert('创建失败: ' + (err.message || '未知错误'));
799
- }
800
- } catch (err) {
801
- alert('请求失败: ' + err.message);
802
- }
803
- }
804
-
805
- async function confirmDeleteCollection(name) {
806
- if (confirm('确定要删除集合 "' + name + '" 吗?此操作不可恢复!')) {
807
- try {
808
- const res = await fetch('/api/v1/admin/collections/' + name, {
809
- method: 'DELETE'
810
- });
811
- if (res.ok) {
812
- location.reload();
813
- } else {
814
- const err = await res.json();
815
- alert('删除失败: ' + (err.message || '未知错误'));
816
- }
817
- } catch (err) {
818
- alert('请求失败: ' + err.message);
819
- }
820
- }
821
- }
822
-
823
- // 文档操作
824
- function changeCollection(name) {
825
- window.location.href = '/admin/documents?collection=' + name;
826
- }
827
-
828
- function goToPage(page) {
829
- const params = new URLSearchParams(window.location.search);
830
- params.set('page', page);
831
- window.location.search = params.toString();
832
- }
833
-
834
- async function viewDocument(id) {
835
- try {
836
- const res = await fetch('/api/v1/get/' + id + '?collection=' + currentCollection);
837
- if (res.ok) {
838
- const data = await res.json();
839
- document.getElementById('document-detail-content').innerHTML =
840
- '<pre class="code-block">' + syntaxHighlight(JSON.stringify(data.data, null, 2)) + '</pre>';
841
- showModal('document-detail-modal');
842
- }
843
- } catch (err) {
844
- alert('获取文档失败: ' + err.message);
845
- }
846
- }
847
-
848
- async function confirmDeleteDocument(id) {
849
- if (confirm('确定要删除此文档吗?')) {
850
- try {
851
- const res = await fetch('/api/v1/forget/' + id + '?collection=' + currentCollection, {
852
- method: 'DELETE'
853
- });
854
- if (res.ok) {
855
- location.reload();
856
- }
857
- } catch (err) {
858
- alert('删除失败: ' + err.message);
859
- }
860
- }
861
- }
862
-
863
- function refreshDocuments() {
864
- location.reload();
865
- }
866
-
867
- function exportDocuments() {
868
- window.open('/api/v1/admin/export?collection=' + currentCollection, '_blank');
869
- }
870
-
871
- // 搜索
872
- async function performSearch(e) {
873
- e.preventDefault();
874
- const form = e.target;
875
- const startTime = Date.now();
876
-
877
- const body = {
878
- query: form.query.value,
879
- limit: parseInt(form.limit.value) || 10,
880
- collection: form.collection.value || undefined
881
- };
882
-
883
- if (form.vector.value) {
884
- try {
885
- body.vector = JSON.parse(form.vector.value);
886
- } catch (err) {
887
- alert('向量格式错误,请输入有效的 JSON 数组');
888
- return;
889
- }
890
- }
891
-
892
- if (form.minImportance.value > 0) {
893
- body.filters = { minImportance: parseFloat(form.minImportance.value) };
894
- }
895
-
896
- if (form.tags.value) {
897
- body.filters = body.filters || {};
898
- body.filters.tags = form.tags.value.split(',').map(t => t.trim());
899
- }
900
-
901
- try {
902
- document.getElementById('search-results').innerHTML = '<div class="loading"><div class="spinner"></div></div>';
903
-
904
- const res = await fetch('/api/v1/recall', {
905
- method: 'POST',
906
- headers: { 'Content-Type': 'application/json' },
907
- body: JSON.stringify(body)
908
- });
909
-
910
- const elapsed = Date.now() - startTime;
911
- document.getElementById('search-time').textContent = elapsed + 'ms';
912
-
913
- if (res.ok) {
914
- const data = await res.json();
915
- renderSearchResults(data.data || []);
916
- addToSearchHistory(form.query.value, data.data?.length || 0);
917
- } else {
918
- document.getElementById('search-results').innerHTML =
919
- '<div class="alert alert-danger">搜索失败</div>';
920
- }
921
- } catch (err) {
922
- document.getElementById('search-results').innerHTML =
923
- '<div class="alert alert-danger">请求失败: ' + err.message + '</div>';
924
- }
925
- }
926
-
927
- function renderSearchResults(results) {
928
- if (results.length === 0) {
929
- document.getElementById('search-results').innerHTML =
930
- '<div class="empty-state"><h3>无匹配结果</h3></div>';
931
- return;
932
- }
933
-
934
- const html = results.map((r, i) => \`
935
- <div class="card mb-2" style="padding: 12px;">
936
- <div class="flex justify-between items-center mb-2">
937
- <span class="badge badge-info">#\${i + 1}</span>
938
- <span class="text-muted">相似度: \${(r.score * 100).toFixed(1)}%</span>
939
- </div>
940
- <p style="margin: 0;">\${escapeHtml(r.node.content)}</p>
941
- <div class="flex gap-2 mt-2">
942
- \${r.node.tags.map(t => '<span class="badge badge-secondary">' + t + '</span>').join('')}
943
- </div>
944
- </div>
945
- \`).join('');
946
-
947
- document.getElementById('search-results').innerHTML = html;
948
- }
949
-
950
- function addToSearchHistory(query, resultCount) {
951
- // 简单的历史记录(实际应该存 localStorage)
952
- const history = document.getElementById('search-history');
953
- const item = document.createElement('div');
954
- item.className = 'flex justify-between items-center mb-2';
955
- item.innerHTML = \`
956
- <span>\${escapeHtml(query)}</span>
957
- <span class="text-muted">\${resultCount} 条结果</span>
958
- \`;
959
- if (history.querySelector('p')) history.innerHTML = '';
960
- history.insertBefore(item, history.firstChild);
961
- }
962
-
963
- // 监控
964
- async function refreshMetrics() {
965
- try {
966
- const res = await fetch('/api/v1/health/detailed');
967
- if (res.ok) {
968
- const data = await res.json();
969
- // 更新页面数据
970
- console.log('Metrics refreshed:', data);
971
- }
972
- } catch (err) {
973
- console.error('Failed to refresh metrics:', err);
974
- }
975
- }
976
-
977
- function initCharts() {
978
- if (typeof Chart === 'undefined') return;
979
-
980
- const memoryCtx = document.getElementById('memory-chart');
981
- const latencyCtx = document.getElementById('latency-chart');
982
-
983
- if (memoryCtx) {
984
- new Chart(memoryCtx, {
985
- type: 'line',
986
- data: {
987
- labels: ['5m', '4m', '3m', '2m', '1m', 'now'],
988
- datasets: [{
989
- label: '堆内存',
990
- data: [65, 70, 68, 72, 75, 78],
991
- borderColor: '#6366f1',
992
- tension: 0.3
993
- }]
994
- },
995
- options: {
996
- responsive: true,
997
- plugins: { legend: { display: false } }
998
- }
999
- });
1000
- }
1001
-
1002
- if (latencyCtx) {
1003
- new Chart(latencyCtx, {
1004
- type: 'bar',
1005
- data: {
1006
- labels: ['<10ms', '10-50ms', '50-100ms', '100-500ms', '>500ms'],
1007
- datasets: [{
1008
- label: '查询数',
1009
- data: [120, 80, 30, 10, 2],
1010
- backgroundColor: '#22c55e'
1011
- }]
1012
- },
1013
- options: {
1014
- responsive: true,
1015
- plugins: { legend: { display: false } }
1016
- }
1017
- });
1018
- }
1019
- }
1020
-
1021
- // 用户管理
1022
- function showCreateUserModal() {
1023
- // TODO: 实现用户创建模态框
1024
- alert('功能开发中');
1025
- }
1026
-
1027
- function editUser(id) {
1028
- alert('功能开发中');
1029
- }
1030
-
1031
- function confirmDeleteUser(id) {
1032
- if (confirm('确定要删除此用户吗?')) {
1033
- alert('功能开发中');
1034
- }
1035
- }
1036
-
1037
- // 登出
1038
- async function logout() {
1039
- try {
1040
- await fetch('/api/v1/auth/logout', { method: 'POST' });
1041
- window.location.href = '/admin/login';
1042
- } catch (err) {
1043
- alert('登出失败');
1044
- }
1045
- }
1046
-
1047
- // JSON 语法高亮
1048
- function syntaxHighlight(json) {
1049
- return json.replace(/("(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\"])*"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g, function (match) {
1050
- let cls = 'json-number';
1051
- if (/^"/.test(match)) {
1052
- if (/:$/.test(match)) {
1053
- cls = 'json-key';
1054
- match = match.slice(0, -1);
1055
- return '<span class="' + cls + '">' + match + '</span>:';
1056
- } else {
1057
- cls = 'json-string';
1058
- }
1059
- } else if (/true|false/.test(match)) {
1060
- cls = 'json-boolean';
1061
- } else if (/null/.test(match)) {
1062
- cls = 'json-null';
1063
- }
1064
- return '<span class="' + cls + '">' + match + '</span>';
1065
- });
1066
- }
1067
-
1068
- function escapeHtml(text) {
1069
- const div = document.createElement('div');
1070
- div.textContent = text;
1071
- return div.innerHTML;
1072
- }
1073
-
1074
- // ========================================
1075
- // CrudTable 通用组件脚本
1076
- // ========================================
1077
- ${(0, crud_table_1.getCrudTableScript)()}
758
+ return `
759
+ // 全局状态
760
+ let currentCollection = new URLSearchParams(window.location.search).get('collection') || 'default';
761
+ let currentPage = 1;
762
+
763
+ // 模态框控制
764
+ function showModal(id) {
765
+ document.getElementById(id).classList.add('active');
766
+ }
767
+
768
+ function closeModal(id) {
769
+ document.getElementById(id).classList.remove('active');
770
+ }
771
+
772
+ // 集合操作
773
+ function showCreateCollectionModal() {
774
+ showModal('create-collection-modal');
775
+ }
776
+
777
+ async function createCollection(e) {
778
+ e.preventDefault();
779
+ const form = e.target;
780
+ const data = {
781
+ name: form.name.value,
782
+ dimension: parseInt(form.dimension.value),
783
+ description: form.description.value
784
+ };
785
+
786
+ try {
787
+ const res = await fetch('/api/v1/admin/collections', {
788
+ method: 'POST',
789
+ headers: { 'Content-Type': 'application/json' },
790
+ body: JSON.stringify(data)
791
+ });
792
+
793
+ if (res.ok) {
794
+ closeModal('create-collection-modal');
795
+ location.reload();
796
+ } else {
797
+ const err = await res.json();
798
+ alert('创建失败: ' + (err.message || '未知错误'));
799
+ }
800
+ } catch (err) {
801
+ alert('请求失败: ' + err.message);
802
+ }
803
+ }
804
+
805
+ async function confirmDeleteCollection(name) {
806
+ if (confirm('确定要删除集合 "' + name + '" 吗?此操作不可恢复!')) {
807
+ try {
808
+ const res = await fetch('/api/v1/admin/collections/' + name, {
809
+ method: 'DELETE'
810
+ });
811
+ if (res.ok) {
812
+ location.reload();
813
+ } else {
814
+ const err = await res.json();
815
+ alert('删除失败: ' + (err.message || '未知错误'));
816
+ }
817
+ } catch (err) {
818
+ alert('请求失败: ' + err.message);
819
+ }
820
+ }
821
+ }
822
+
823
+ // 文档操作
824
+ function changeCollection(name) {
825
+ window.location.href = '/admin/documents?collection=' + name;
826
+ }
827
+
828
+ function goToPage(page) {
829
+ const params = new URLSearchParams(window.location.search);
830
+ params.set('page', page);
831
+ window.location.search = params.toString();
832
+ }
833
+
834
+ async function viewDocument(id) {
835
+ try {
836
+ const res = await fetch('/api/v1/get/' + id + '?collection=' + currentCollection);
837
+ if (res.ok) {
838
+ const data = await res.json();
839
+ document.getElementById('document-detail-content').innerHTML =
840
+ '<pre class="code-block">' + syntaxHighlight(JSON.stringify(data.data, null, 2)) + '</pre>';
841
+ showModal('document-detail-modal');
842
+ }
843
+ } catch (err) {
844
+ alert('获取文档失败: ' + err.message);
845
+ }
846
+ }
847
+
848
+ async function confirmDeleteDocument(id) {
849
+ if (confirm('确定要删除此文档吗?')) {
850
+ try {
851
+ const res = await fetch('/api/v1/forget/' + id + '?collection=' + currentCollection, {
852
+ method: 'DELETE'
853
+ });
854
+ if (res.ok) {
855
+ location.reload();
856
+ }
857
+ } catch (err) {
858
+ alert('删除失败: ' + err.message);
859
+ }
860
+ }
861
+ }
862
+
863
+ function refreshDocuments() {
864
+ location.reload();
865
+ }
866
+
867
+ function exportDocuments() {
868
+ window.open('/api/v1/admin/export?collection=' + currentCollection, '_blank');
869
+ }
870
+
871
+ // 搜索
872
+ async function performSearch(e) {
873
+ e.preventDefault();
874
+ const form = e.target;
875
+ const startTime = Date.now();
876
+
877
+ const body = {
878
+ query: form.query.value,
879
+ limit: parseInt(form.limit.value) || 10,
880
+ collection: form.collection.value || undefined
881
+ };
882
+
883
+ if (form.vector.value) {
884
+ try {
885
+ body.vector = JSON.parse(form.vector.value);
886
+ } catch (err) {
887
+ alert('向量格式错误,请输入有效的 JSON 数组');
888
+ return;
889
+ }
890
+ }
891
+
892
+ if (form.minImportance.value > 0) {
893
+ body.filters = { minImportance: parseFloat(form.minImportance.value) };
894
+ }
895
+
896
+ if (form.tags.value) {
897
+ body.filters = body.filters || {};
898
+ body.filters.tags = form.tags.value.split(',').map(t => t.trim());
899
+ }
900
+
901
+ try {
902
+ document.getElementById('search-results').innerHTML = '<div class="loading"><div class="spinner"></div></div>';
903
+
904
+ const res = await fetch('/api/v1/recall', {
905
+ method: 'POST',
906
+ headers: { 'Content-Type': 'application/json' },
907
+ body: JSON.stringify(body)
908
+ });
909
+
910
+ const elapsed = Date.now() - startTime;
911
+ document.getElementById('search-time').textContent = elapsed + 'ms';
912
+
913
+ if (res.ok) {
914
+ const data = await res.json();
915
+ renderSearchResults(data.data || []);
916
+ addToSearchHistory(form.query.value, data.data?.length || 0);
917
+ } else {
918
+ document.getElementById('search-results').innerHTML =
919
+ '<div class="alert alert-danger">搜索失败</div>';
920
+ }
921
+ } catch (err) {
922
+ document.getElementById('search-results').innerHTML =
923
+ '<div class="alert alert-danger">请求失败: ' + err.message + '</div>';
924
+ }
925
+ }
926
+
927
+ function renderSearchResults(results) {
928
+ if (results.length === 0) {
929
+ document.getElementById('search-results').innerHTML =
930
+ '<div class="empty-state"><h3>无匹配结果</h3></div>';
931
+ return;
932
+ }
933
+
934
+ const html = results.map((r, i) => \`
935
+ <div class="card mb-2" style="padding: 12px;">
936
+ <div class="flex justify-between items-center mb-2">
937
+ <span class="badge badge-info">#\${i + 1}</span>
938
+ <span class="text-muted">相似度: \${(r.score * 100).toFixed(1)}%</span>
939
+ </div>
940
+ <p style="margin: 0;">\${escapeHtml(r.node.content)}</p>
941
+ <div class="flex gap-2 mt-2">
942
+ \${r.node.tags.map(t => '<span class="badge badge-secondary">' + t + '</span>').join('')}
943
+ </div>
944
+ </div>
945
+ \`).join('');
946
+
947
+ document.getElementById('search-results').innerHTML = html;
948
+ }
949
+
950
+ function addToSearchHistory(query, resultCount) {
951
+ // 简单的历史记录(实际应该存 localStorage)
952
+ const history = document.getElementById('search-history');
953
+ const item = document.createElement('div');
954
+ item.className = 'flex justify-between items-center mb-2';
955
+ item.innerHTML = \`
956
+ <span>\${escapeHtml(query)}</span>
957
+ <span class="text-muted">\${resultCount} 条结果</span>
958
+ \`;
959
+ if (history.querySelector('p')) history.innerHTML = '';
960
+ history.insertBefore(item, history.firstChild);
961
+ }
962
+
963
+ // 监控
964
+ async function refreshMetrics() {
965
+ try {
966
+ const res = await fetch('/api/v1/health/detailed');
967
+ if (res.ok) {
968
+ const data = await res.json();
969
+ // 更新页面数据
970
+ console.log('Metrics refreshed:', data);
971
+ }
972
+ } catch (err) {
973
+ console.error('Failed to refresh metrics:', err);
974
+ }
975
+ }
976
+
977
+ function initCharts() {
978
+ if (typeof Chart === 'undefined') return;
979
+
980
+ const memoryCtx = document.getElementById('memory-chart');
981
+ const latencyCtx = document.getElementById('latency-chart');
982
+
983
+ if (memoryCtx) {
984
+ new Chart(memoryCtx, {
985
+ type: 'line',
986
+ data: {
987
+ labels: ['5m', '4m', '3m', '2m', '1m', 'now'],
988
+ datasets: [{
989
+ label: '堆内存',
990
+ data: [65, 70, 68, 72, 75, 78],
991
+ borderColor: '#6366f1',
992
+ tension: 0.3
993
+ }]
994
+ },
995
+ options: {
996
+ responsive: true,
997
+ plugins: { legend: { display: false } }
998
+ }
999
+ });
1000
+ }
1001
+
1002
+ if (latencyCtx) {
1003
+ new Chart(latencyCtx, {
1004
+ type: 'bar',
1005
+ data: {
1006
+ labels: ['<10ms', '10-50ms', '50-100ms', '100-500ms', '>500ms'],
1007
+ datasets: [{
1008
+ label: '查询数',
1009
+ data: [120, 80, 30, 10, 2],
1010
+ backgroundColor: '#22c55e'
1011
+ }]
1012
+ },
1013
+ options: {
1014
+ responsive: true,
1015
+ plugins: { legend: { display: false } }
1016
+ }
1017
+ });
1018
+ }
1019
+ }
1020
+
1021
+ // 用户管理
1022
+ function showCreateUserModal() {
1023
+ // TODO: 实现用户创建模态框
1024
+ alert('功能开发中');
1025
+ }
1026
+
1027
+ function editUser(id) {
1028
+ alert('功能开发中');
1029
+ }
1030
+
1031
+ function confirmDeleteUser(id) {
1032
+ if (confirm('确定要删除此用户吗?')) {
1033
+ alert('功能开发中');
1034
+ }
1035
+ }
1036
+
1037
+ // 登出
1038
+ async function logout() {
1039
+ try {
1040
+ await fetch('/api/v1/auth/logout', { method: 'POST' });
1041
+ window.location.href = '/admin/login';
1042
+ } catch (err) {
1043
+ alert('登出失败');
1044
+ }
1045
+ }
1046
+
1047
+ // JSON 语法高亮
1048
+ function syntaxHighlight(json) {
1049
+ return json.replace(/("(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\"])*"(\\s*:)?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)/g, function (match) {
1050
+ let cls = 'json-number';
1051
+ if (/^"/.test(match)) {
1052
+ if (/:$/.test(match)) {
1053
+ cls = 'json-key';
1054
+ match = match.slice(0, -1);
1055
+ return '<span class="' + cls + '">' + match + '</span>:';
1056
+ } else {
1057
+ cls = 'json-string';
1058
+ }
1059
+ } else if (/true|false/.test(match)) {
1060
+ cls = 'json-boolean';
1061
+ } else if (/null/.test(match)) {
1062
+ cls = 'json-null';
1063
+ }
1064
+ return '<span class="' + cls + '">' + match + '</span>';
1065
+ });
1066
+ }
1067
+
1068
+ function escapeHtml(text) {
1069
+ const div = document.createElement('div');
1070
+ div.textContent = text;
1071
+ return div.innerHTML;
1072
+ }
1073
+
1074
+ // ========================================
1075
+ // CrudTable 通用组件脚本
1076
+ // ========================================
1077
+ ${(0, crud_table_1.getCrudTableScript)()}
1078
1078
  `;
1079
1079
  }
1080
1080
  //# sourceMappingURL=templates.js.map