+
+
@@ -2170,14 +2370,27 @@ class TSPDashboard {
async searchKnowledge() {
const query = document.getElementById('knowledge-search').value.trim();
if (!query) {
- this.loadKnowledge();
+ // Task 8.1: 清空搜索时恢复当前租户的分页列表
+ if (this.knowledgeCurrentTenantId) {
+ this.loadKnowledgeTenantDetail(this.knowledgeCurrentTenantId);
+ } else {
+ this.loadKnowledge();
+ }
return;
}
try {
- const response = await fetch(`/api/knowledge/search?q=${encodeURIComponent(query)}`);
+ // Task 8.1: 搜索时附加 tenant_id 参数
+ let url = `/api/knowledge/search?q=${encodeURIComponent(query)}`;
+ if (this.knowledgeCurrentTenantId) {
+ url += `&tenant_id=${encodeURIComponent(this.knowledgeCurrentTenantId)}`;
+ }
+ const response = await fetch(url);
const results = await response.json();
this.updateKnowledgeDisplay(results);
+ // 搜索结果不显示分页
+ const paginationEl = document.getElementById('knowledge-pagination');
+ if (paginationEl) paginationEl.innerHTML = '';
} catch (error) {
console.error('搜索知识库失败:', error);
}
@@ -2195,17 +2408,22 @@ class TSPDashboard {
}
try {
+ // Task 7.3: 添加知识条目时自动设置 tenant_id
+ const body = {
+ question,
+ answer,
+ category,
+ confidence_score: confidence
+ };
+ if (this.knowledgeCurrentTenantId) {
+ body.tenant_id = this.knowledgeCurrentTenantId;
+ }
const response = await fetch('/api/knowledge', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
- body: JSON.stringify({
- question,
- answer,
- category,
- confidence_score: confidence
- })
+ body: JSON.stringify(body)
});
const data = await response.json();
@@ -2213,7 +2431,12 @@ class TSPDashboard {
this.showNotification('知识添加成功', 'success');
bootstrap.Modal.getInstance(document.getElementById('addKnowledgeModal')).hide();
document.getElementById('knowledge-form').reset();
- this.loadKnowledge();
+ // Task 7.3: 刷新当前视图
+ if (this.knowledgeCurrentTenantId) {
+ this.loadKnowledgeTenantDetail(this.knowledgeCurrentTenantId, this.paginationConfig.currentKnowledgePage);
+ } else {
+ this.loadKnowledgeTenantList();
+ }
} else {
this.showNotification('添加知识失败', 'error');
}
@@ -2293,7 +2516,7 @@ class TSPDashboard {
this.showNotification(`文件处理成功,生成了 ${data.knowledge_count || 0} 条知识`, 'success');
bootstrap.Modal.getInstance(document.getElementById('uploadFileModal')).hide();
document.getElementById('file-upload-form').reset();
- this.loadKnowledge();
+ this.refreshKnowledgeCurrentView();
} else {
this.showNotification(data.error || '文件处理失败', 'error');
}
@@ -2321,7 +2544,7 @@ class TSPDashboard {
const data = await response.json();
if (data.success) {
this.showNotification('知识库验证成功', 'success');
- this.loadKnowledge(this.paginationConfig.currentKnowledgePage);
+ this.refreshKnowledgeCurrentView();
} else {
this.showNotification('知识库验证失败', 'error');
}
@@ -2343,7 +2566,7 @@ class TSPDashboard {
const data = await response.json();
if (data.success) {
this.showNotification('取消验证成功', 'success');
- this.loadKnowledge(this.paginationConfig.currentKnowledgePage);
+ this.refreshKnowledgeCurrentView();
} else {
this.showNotification('取消验证失败', 'error');
}
@@ -2366,7 +2589,19 @@ class TSPDashboard {
const data = await response.json();
if (data.success) {
this.showNotification('知识库条目已删除', 'success');
- this.loadKnowledge(this.paginationConfig.currentKnowledgePage);
+
+ // Task 7.3: 删除后检查是否所有条目已删除,若是则返回租户列表
+ if (this.knowledgeCurrentTenantId) {
+ const checkResp = await fetch(`/api/knowledge?tenant_id=${encodeURIComponent(this.knowledgeCurrentTenantId)}&page=1&per_page=1`);
+ const checkData = await checkResp.json();
+ if (checkData.total === 0) {
+ this.showNotification('该租户下已无知识条目,返回租户列表', 'info');
+ this.loadKnowledgeTenantList();
+ return;
+ }
+ }
+
+ this.refreshKnowledgeCurrentView();
} else {
this.showNotification('知识库删除失败', 'error');
}
@@ -2391,12 +2626,21 @@ class TSPDashboard {
updateBatchDeleteKnowledgeButton() {
const selectedCheckboxes = document.querySelectorAll('.knowledge-checkbox:checked');
const batchDeleteBtn = document.getElementById('batch-delete-knowledge');
+ const batchVerifyBtn = document.getElementById('batch-verify-knowledge');
+ const batchUnverifyBtn = document.getElementById('batch-unverify-knowledge');
+ const hasSelection = selectedCheckboxes.length > 0;
if (batchDeleteBtn) {
- batchDeleteBtn.disabled = selectedCheckboxes.length === 0;
- batchDeleteBtn.textContent = selectedCheckboxes.length > 0
- ? `批量删除 (${selectedCheckboxes.length})`
- : '批量删除';
+ batchDeleteBtn.disabled = !hasSelection;
+ batchDeleteBtn.innerHTML = hasSelection
+ ? `
批量删除 (${selectedCheckboxes.length})`
+ : '
批量删除';
+ }
+ if (batchVerifyBtn) {
+ batchVerifyBtn.disabled = !hasSelection;
+ }
+ if (batchUnverifyBtn) {
+ batchUnverifyBtn.disabled = !hasSelection;
}
}
@@ -2429,9 +2673,21 @@ class TSPDashboard {
// 清除缓存并强制刷新
this.cache.delete('knowledge');
- await this.loadKnowledge();
+
+ // Task 7.3: 删除后检查是否所有条目已删除,若是则返回租户列表
+ if (this.knowledgeCurrentTenantId) {
+ const checkResp = await fetch(`/api/knowledge?tenant_id=${encodeURIComponent(this.knowledgeCurrentTenantId)}&page=1&per_page=1`);
+ const checkData = await checkResp.json();
+ if (checkData.total === 0) {
+ this.showNotification('该租户下已无知识条目,返回租户列表', 'info');
+ this.loadKnowledgeTenantList();
+ return;
+ }
+ }
+
+ await this.refreshKnowledgeCurrentView();
- // 重置批量删除按钮状态
+ // 重置批量操作按钮状态
this.updateBatchDeleteKnowledgeButton();
} else {
this.showNotification(data.error || '批量删除失败', 'error');
@@ -2442,6 +2698,68 @@ class TSPDashboard {
}
}
+ // Task 7.3: 批量验证知识条目
+ async batchVerifyKnowledge() {
+ const selectedCheckboxes = document.querySelectorAll('.knowledge-checkbox:checked');
+ const selectedIds = Array.from(selectedCheckboxes).map(cb => parseInt(cb.value));
+
+ if (selectedIds.length === 0) {
+ this.showNotification('请选择要验证的知识库条目', 'warning');
+ return;
+ }
+
+ try {
+ const response = await fetch('/api/knowledge/batch_verify', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ ids: selectedIds })
+ });
+
+ const data = await response.json();
+ if (data.success) {
+ this.showNotification(data.message || '批量验证成功', 'success');
+ await this.refreshKnowledgeCurrentView();
+ this.updateBatchDeleteKnowledgeButton();
+ } else {
+ this.showNotification(data.error || '批量验证失败', 'error');
+ }
+ } catch (error) {
+ console.error('批量验证知识库条目失败:', error);
+ this.showNotification('批量验证知识库条目失败', 'error');
+ }
+ }
+
+ // Task 7.3: 批量取消验证知识条目
+ async batchUnverifyKnowledge() {
+ const selectedCheckboxes = document.querySelectorAll('.knowledge-checkbox:checked');
+ const selectedIds = Array.from(selectedCheckboxes).map(cb => parseInt(cb.value));
+
+ if (selectedIds.length === 0) {
+ this.showNotification('请选择要取消验证的知识库条目', 'warning');
+ return;
+ }
+
+ try {
+ const response = await fetch('/api/knowledge/batch_unverify', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ ids: selectedIds })
+ });
+
+ const data = await response.json();
+ if (data.success) {
+ this.showNotification(data.message || '批量取消验证成功', 'success');
+ await this.refreshKnowledgeCurrentView();
+ this.updateBatchDeleteKnowledgeButton();
+ } else {
+ this.showNotification(data.error || '批量取消验证失败', 'error');
+ }
+ } catch (error) {
+ console.error('批量取消验证知识库条目失败:', error);
+ this.showNotification('批量取消验证知识库条目失败', 'error');
+ }
+ }
+
// 工单管理
async loadWorkOrders(page = 1, forceRefresh = false) {
this.paginationConfig.currentWorkOrderPage = page;
@@ -3206,6 +3524,369 @@ class TSPDashboard {
}
}
+ // 对话历史租户列表视图
+ async loadConversationTenantList() {
+ this.conversationCurrentTenantId = null;
+ this.renderConversationBreadcrumb(null);
+
+ // 加载全局统计
+ this.loadConversationStats(null);
+
+ // 显示租户列表容器,隐藏详情容器
+ const tenantListEl = document.getElementById('conversation-tenant-list');
+ const tenantDetailEl = document.getElementById('conversation-tenant-detail');
+
+ if (tenantListEl) tenantListEl.style.display = '';
+ if (tenantDetailEl) tenantDetailEl.style.display = 'none';
+
+ // 显示加载中 spinner
+ if (tenantListEl) {
+ tenantListEl.innerHTML = '
';
+ }
+
+ try {
+ const response = await fetch('/api/conversations/tenants');
+ const tenants = await response.json();
+
+ if (!Array.isArray(tenants) || tenants.length === 0) {
+ if (tenantListEl) {
+ tenantListEl.innerHTML = '
';
+ }
+ return;
+ }
+
+ const cardsHtml = tenants.map(t => {
+ const lastActive = t.last_active_time ? new Date(t.last_active_time).toLocaleString() : '无';
+ return `
+
+
+
+
${t.tenant_id}
+
+
+ 会话总数
+
${t.session_count}
+
+
+ 消息总数
+
${t.message_count}
+
+
+ 活跃会话
+
${t.active_session_count}
+
+
+
+ 最近活跃: ${lastActive}
+
+
+
+
+ `;
+ }).join('');
+
+ if (tenantListEl) {
+ tenantListEl.innerHTML = cardsHtml;
+ }
+ } catch (error) {
+ console.error('加载对话租户列表失败:', error);
+ if (tenantListEl) {
+ tenantListEl.innerHTML = '
加载失败
';
+ }
+ this.showNotification('加载对话租户列表失败', 'error');
+ }
+ }
+
+ // Task 7.1: 加载对话历史租户详情视图
+ async loadConversationTenantDetail(tenantId, page = 1) {
+ this.conversationCurrentTenantId = tenantId;
+ this.paginationConfig.currentConversationPage = page;
+
+ // 隐藏租户列表,显示详情容器
+ const tenantListEl = document.getElementById('conversation-tenant-list');
+ const tenantDetailEl = document.getElementById('conversation-tenant-detail');
+
+ if (tenantListEl) tenantListEl.style.display = 'none';
+ if (tenantDetailEl) tenantDetailEl.style.display = '';
+
+ // 渲染面包屑(如果 renderConversationBreadcrumb 已实现)
+ if (typeof this.renderConversationBreadcrumb === 'function') {
+ this.renderConversationBreadcrumb(tenantId);
+ }
+
+ // 加载租户级统计(如果 loadConversationStats 已实现)
+ if (typeof this.loadConversationStats === 'function') {
+ this.loadConversationStats(tenantId);
+ }
+
+ // 显示加载中 spinner
+ const sessionListEl = document.getElementById('conversation-session-list');
+ if (sessionListEl) {
+ sessionListEl.innerHTML = '
';
+ }
+
+ try {
+ const perPage = this.getPageSize('conversation-session-pagination');
+ const statusFilter = document.getElementById('conversation-status-filter')?.value || '';
+ const dateFilter = document.getElementById('conversation-detail-date-filter')?.value || '';
+
+ let url = `/api/conversations/sessions?tenant_id=${encodeURIComponent(tenantId)}&page=${page}&per_page=${perPage}`;
+ if (statusFilter) url += `&status=${encodeURIComponent(statusFilter)}`;
+ if (dateFilter) url += `&date_filter=${encodeURIComponent(dateFilter)}`;
+
+ const response = await fetch(url);
+ const data = await response.json();
+
+ if (data.sessions) {
+ this.renderConversationSessionTable(data.sessions, tenantId);
+ this.updateConversationTenantPagination(data);
+ } else {
+ if (sessionListEl) {
+ sessionListEl.innerHTML = '
';
+ }
+ }
+ } catch (error) {
+ console.error('加载租户会话列表失败:', error);
+ if (sessionListEl) {
+ sessionListEl.innerHTML = '
加载失败
';
+ }
+ this.showNotification('加载租户会话列表失败', 'error');
+ }
+ }
+
+ // Task 7.1: 渲染会话表格
+ renderConversationSessionTable(sessions, tenantId) {
+ const sessionListEl = document.getElementById('conversation-session-list');
+ if (!sessionListEl) return;
+
+ if (!sessions || sessions.length === 0) {
+ sessionListEl.innerHTML = '
';
+ return;
+ }
+
+ const statusBadge = (status) => {
+ if (status === 'active') return '
活跃';
+ if (status === 'ended') return '
已结束';
+ return `
${status || '未知'}`;
+ };
+
+ const sourceBadge = (source) => {
+ if (source === 'feishu') return '
飞书';
+ if (source === 'websocket') return '
WebSocket';
+ if (source === 'api') return '
API';
+ return `
${source || '未知'}`;
+ };
+
+ const formatTime = (isoStr) => {
+ if (!isoStr) return '-';
+ return new Date(isoStr).toLocaleString();
+ };
+
+ const rowsHtml = sessions.map(s => `
+
+ | ${s.title || s.session_id || '-'} |
+ ${s.message_count || 0} |
+ ${statusBadge(s.status)} |
+ ${sourceBadge(s.source)} |
+ ${formatTime(s.created_at)} |
+ ${formatTime(s.updated_at)} |
+
+
+ |
+
+ `).join('');
+
+ sessionListEl.innerHTML = `
+
+
+
+
+ | 会话标题 |
+ 消息数 |
+ 状态 |
+ 来源 |
+ 创建时间 |
+ 更新时间 |
+ 操作 |
+
+
+
+ ${rowsHtml}
+
+
+
+ `;
+ }
+
+ // Task 7.1: 查看会话消息详情
+ async viewSessionMessages(sessionId, sessionTitle) {
+ try {
+ const response = await fetch(`/api/conversations/sessions/${encodeURIComponent(sessionId)}`);
+ const data = await response.json();
+
+ if (data.success && data.messages) {
+ // 更新面包屑到第三层(如果已实现)
+ if (typeof this.renderConversationBreadcrumb === 'function') {
+ this.renderConversationBreadcrumb(this.conversationCurrentTenantId, sessionTitle || sessionId);
+ }
+ this.showSessionMessagesModal(data.session, data.messages);
+ } else {
+ throw new Error(data.error || '获取会话消息失败');
+ }
+ } catch (error) {
+ console.error('获取会话消息失败:', error);
+ this.showNotification('获取会话消息失败: ' + error.message, 'error');
+ }
+ }
+
+ // Task 7.1: 显示会话消息模态框
+ showSessionMessagesModal(session, messages) {
+ const messagesHtml = messages.map(msg => `
+
+
+
用户:
+
${msg.user_message || ''}
+
+
+
助手:
+
${msg.assistant_response || ''}
+
+
+
+ ${msg.timestamp ? new Date(msg.timestamp).toLocaleString() : ''}
+ ${msg.response_time ? ` | 响应: ${msg.response_time}s` : ''}
+ ${msg.confidence_score ? ` | 置信度: ${Math.round(msg.confidence_score * 100)}%` : ''}
+
+
+
+ `).join('');
+
+ const modalHtml = `
+
+ `;
+
+ // 移除已存在的模态框
+ const existingModal = document.getElementById('sessionMessagesModal');
+ if (existingModal) existingModal.remove();
+
+ document.body.insertAdjacentHTML('beforeend', modalHtml);
+ const modal = new bootstrap.Modal(document.getElementById('sessionMessagesModal'));
+ modal.show();
+
+ // 模态框关闭后清理
+ document.getElementById('sessionMessagesModal').addEventListener('hidden.bs.modal', function () {
+ this.remove();
+ });
+ }
+
+ // Task 7.1: 对话租户详情分页
+ updateConversationTenantPagination(data) {
+ this.createPaginationComponent(data, 'conversation-session-pagination', 'loadConversationTenantDetailPage', '条会话');
+ }
+
+ // Task 7.2: 面包屑导航
+ renderConversationBreadcrumb(tenantId, sessionTitle) {
+ const breadcrumbEl = document.getElementById('conversation-breadcrumb');
+ if (!breadcrumbEl) return;
+
+ if (!tenantId) {
+ breadcrumbEl.innerHTML = '';
+ return;
+ }
+
+ if (!sessionTitle) {
+ // 租户详情视图: "对话历史 > {tenant_id}"
+ breadcrumbEl.innerHTML = `
+
+ `;
+ } else {
+ // 消息详情视图: "对话历史 > {tenant_id} > {session_title}"
+ breadcrumbEl.innerHTML = `
+
+ `;
+ }
+
+ this.conversationCurrentTenantId = tenantId;
+ }
+
+ // Task 7.1: 应用对话筛选条件
+ applyConversationFilters() {
+ if (this.conversationCurrentTenantId) {
+ this.loadConversationTenantDetail(this.conversationCurrentTenantId, 1);
+ }
+ }
+
+ // Task 7.3: 删除会话并处理空租户自动返回
+ async deleteConversationSession(sessionId) {
+ if (!confirm('确定要删除这个会话及其所有消息吗?')) return;
+ try {
+ const response = await fetch(`/api/conversations/sessions/${encodeURIComponent(sessionId)}`, { method: 'DELETE' });
+ const data = await response.json();
+ if (data.success) {
+ this.showNotification('会话已删除', 'success');
+ // 刷新当前租户详情视图并检查是否还有剩余会话
+ if (this.conversationCurrentTenantId) {
+ const tenantId = this.conversationCurrentTenantId;
+ const perPage = this.getPageSize('conversation-session-pagination');
+ const checkUrl = `/api/conversations/sessions?tenant_id=${encodeURIComponent(tenantId)}&page=1&per_page=${perPage}`;
+ const checkResp = await fetch(checkUrl);
+ const checkData = await checkResp.json();
+
+ if (!checkData.sessions || checkData.sessions.length === 0 || checkData.total === 0) {
+ // 该租户下已无会话,自动返回租户列表视图
+ this.loadConversationTenantList();
+ } else {
+ // 仍有会话,刷新当前详情视图(调整页码避免越界)
+ const maxPage = checkData.total_pages || 1;
+ const currentPage = Math.min(this.paginationConfig.currentConversationPage, maxPage);
+ await this.loadConversationTenantDetail(tenantId, currentPage);
+ }
+ }
+ } else {
+ throw new Error(data.error || '删除失败');
+ }
+ } catch (error) {
+ console.error('删除会话失败:', error);
+ this.showNotification('删除会话失败: ' + error.message, 'error');
+ }
+ }
+
// 对话历史管理
async loadConversationHistory(page = 1) {
try {
@@ -3307,12 +3988,44 @@ class TSPDashboard {
document.getElementById('conversation-active-users').textContent = stats.active_users || 0;
}
+ // Task 8.3: 加载对话统计(支持租户级别)
+ async loadConversationStats(tenantId) {
+ try {
+ let url = '/api/conversations/analytics';
+ if (tenantId) {
+ url += `?tenant_id=${encodeURIComponent(tenantId)}`;
+ }
+ const response = await fetch(url);
+ const data = await response.json();
+
+ const analytics = data.analytics || {};
+ const convStats = analytics.conversations || {};
+
+ const totalEl = document.getElementById('conversation-total');
+ const todayEl = document.getElementById('conversation-today');
+ const avgResponseEl = document.getElementById('conversation-avg-response');
+ const activeUsersEl = document.getElementById('conversation-active-users');
+
+ if (totalEl) totalEl.textContent = convStats.total || 0;
+ if (todayEl) todayEl.textContent = convStats.today || 0;
+ if (avgResponseEl) avgResponseEl.textContent = `${Math.round(convStats.avg_response_time || 0)}ms`;
+ if (activeUsersEl) activeUsersEl.textContent = convStats.active_users || 0;
+ } catch (error) {
+ console.error('加载对话统计失败:', error);
+ }
+ }
+
async refreshConversationHistory() {
// 先尝试触发一次合并迁移(幂等,重复调用也安全)
try {
await fetch('/api/conversations/migrate-merge', { method: 'POST' });
} catch (e) { /* 忽略迁移失败 */ }
- await this.loadConversationHistory();
+ // 根据当前视图状态刷新:租户详情视图或租户列表视图
+ if (this.conversationCurrentTenantId) {
+ await this.loadConversationTenantDetail(this.conversationCurrentTenantId, this.paginationConfig.currentConversationPage);
+ } else {
+ await this.loadConversationTenantList();
+ }
this.showNotification('对话历史已刷新', 'success');
}
@@ -3422,10 +4135,44 @@ class TSPDashboard {
}
async filterConversations() {
- const search = document.getElementById('conversation-search').value;
- const userFilter = document.getElementById('conversation-user-filter').value;
- const dateFilter = document.getElementById('conversation-date-filter').value;
-
+ const search = document.getElementById('conversation-search').value.trim();
+ const userFilter = document.getElementById('conversation-user-filter')?.value || '';
+ const dateFilter = document.getElementById('conversation-date-filter')?.value || '';
+
+ // Task 8.1: 在 Tenant_Detail_View 中搜索时自动附加 tenant_id 参数
+ if (this.conversationCurrentTenantId) {
+ if (!search) {
+ // 清空搜索时恢复当前租户的完整分页列表
+ this.loadConversationTenantDetail(this.conversationCurrentTenantId);
+ return;
+ }
+ try {
+ const perPage = this.getPageSize('conversation-session-pagination');
+ let url = `/api/conversations/sessions?search=${encodeURIComponent(search)}&tenant_id=${encodeURIComponent(this.conversationCurrentTenantId)}&page=1&per_page=${perPage}`;
+ if (dateFilter) url += `&date_filter=${encodeURIComponent(dateFilter)}`;
+
+ const response = await fetch(url);
+ const data = await response.json();
+
+ if (data.sessions) {
+ this.renderConversationSessionTable(data.sessions, this.conversationCurrentTenantId);
+ this.updateConversationTenantPagination(data);
+ } else {
+ const sessionListEl = document.getElementById('conversation-session-list');
+ if (sessionListEl) {
+ sessionListEl.innerHTML = '
';
+ }
+ const paginationEl = document.getElementById('conversation-session-pagination');
+ if (paginationEl) paginationEl.innerHTML = '';
+ }
+ } catch (error) {
+ console.error('搜索租户会话失败:', error);
+ this.showNotification('搜索会话失败: ' + error.message, 'error');
+ }
+ return;
+ }
+
+ // 非租户详情视图:保持原有行为
try {
const params = new URLSearchParams();
if (search) params.append('search', search);
@@ -5181,9 +5928,24 @@ class TSPDashboard {
// 系统设置
async loadSettings() {
try {
- const response = await fetch('/api/settings');
- const settings = await response.json();
+ const [settingsResp, configResp] = await Promise.all([
+ fetch('/api/settings'),
+ fetch('/api/runtime-config'),
+ ]);
+ const settings = await settingsResp.json();
this.updateSettingsDisplay(settings);
+
+ // 加载运行时配置(租户、模型等)
+ const config = await configResp.json();
+ if (config.success) {
+ const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val || '-'; };
+ set('setting-tenant-id', config.tenant_id);
+ set('setting-llm-provider', config.llm?.provider);
+ set('setting-llm-model', config.llm?.model);
+ set('setting-llm-base-url', config.llm?.base_url);
+ set('setting-embedding-status', config.embedding?.enabled ? `启用 (${config.embedding.model})` : '禁用');
+ set('setting-redis-status', config.redis?.enabled ? `启用 (${config.redis.host})` : '禁用');
+ }
} catch (error) {
console.error('加载设置失败:', error);
}
diff --git a/src/web/templates/dashboard.html b/src/web/templates/dashboard.html
index 3fcdacf..bfd03d5 100644
--- a/src/web/templates/dashboard.html
+++ b/src/web/templates/dashboard.html
@@ -662,19 +662,22 @@