// Skills管理相关功能 function _t(key, opts) { return typeof window.t === 'function' ? window.t(key, opts) : key; } let skillsList = []; let currentEditingSkillName = null; let skillModalAddMode = true; let skillActivePath = 'SKILL.md'; let skillFileDirty = false; let skillPackageFiles = []; let skillModalControlsWired = false; let isSavingSkill = false; // 防止重复提交 let skillsSearchKeyword = ''; let skillsSearchTimeout = null; // 搜索防抖定时器 let skillsAutoRefreshTimer = null; let isAutoRefreshingSkills = false; const SKILLS_AUTO_REFRESH_INTERVAL_MS = 5000; let skillsPagination = { currentPage: 1, pageSize: 20, // 每页20条(默认值,实际从localStorage读取) total: 0 }; let skillsStats = { total: 0, totalCalls: 0, totalSuccess: 0, totalFailed: 0, skillsDir: '', stats: [] }; function isSkillsManagementPageActive() { const page = document.getElementById('page-skills-management'); return !!(page && page.classList.contains('active')); } function shouldSkipSkillsAutoRefresh() { if (isSavingSkill || currentEditingSkillName) { return true; } const modal = document.getElementById('skill-modal'); if (modal && modal.style.display === 'flex') { return true; } const searchInput = document.getElementById('skills-search'); if (skillsSearchKeyword || (searchInput && searchInput.value.trim())) { return true; } return false; } function startSkillsAutoRefresh() { if (skillsAutoRefreshTimer) return; skillsAutoRefreshTimer = setInterval(async () => { if (!isSkillsManagementPageActive() || shouldSkipSkillsAutoRefresh()) { return; } if (isAutoRefreshingSkills) { return; } isAutoRefreshingSkills = true; try { await loadSkills(skillsPagination.currentPage, skillsPagination.pageSize); } finally { isAutoRefreshingSkills = false; } }, SKILLS_AUTO_REFRESH_INTERVAL_MS); } // 获取保存的每页显示数量 function getSkillsPageSize() { try { const saved = localStorage.getItem('skillsPageSize'); if (saved) { const size = parseInt(saved); if ([10, 20, 50, 100].includes(size)) { return size; } } } catch (e) { console.warn('无法从localStorage读取分页设置:', e); } return 20; // 默认20 } // 初始化分页设置 function initSkillsPagination() { const savedPageSize = getSkillsPageSize(); skillsPagination.pageSize = savedPageSize; } // 加载skills列表(支持分页) async function loadSkills(page = 1, pageSize = null) { try { // 如果没有指定pageSize,使用保存的值或默认值 if (pageSize === null) { pageSize = getSkillsPageSize(); } // 更新分页状态(确保使用正确的pageSize) skillsPagination.currentPage = page; skillsPagination.pageSize = pageSize; // 清空搜索关键词(正常分页加载时) skillsSearchKeyword = ''; const searchInput = document.getElementById('skills-search'); if (searchInput) { searchInput.value = ''; } // 构建URL(支持分页) const offset = (page - 1) * pageSize; const url = `/api/skills?limit=${pageSize}&offset=${offset}`; const response = await apiFetch(url); if (!response.ok) { throw new Error(_t('skills.loadListFailed')); } const data = await response.json(); skillsList = data.skills || []; skillsPagination.total = data.total || 0; renderSkillsList(); renderSkillsPagination(); updateSkillsManagementStats(); } catch (error) { console.error('加载skills列表失败:', error); showNotification(_t('skills.loadListFailed') + ': ' + error.message, 'error'); const skillsListEl = document.getElementById('skills-list'); if (skillsListEl) { skillsListEl.innerHTML = '
' + _t('skills.loadFailedShort') + ': ' + escapeHtml(error.message) + '
'; } } } // 渲染skills列表 function renderSkillsList() { const skillsListEl = document.getElementById('skills-list'); if (!skillsListEl) return; // 后端已经完成搜索过滤,直接使用skillsList const filteredSkills = skillsList; if (filteredSkills.length === 0) { skillsListEl.innerHTML = '
' + (skillsSearchKeyword ? _t('skills.noMatch') : _t('skills.noSkills')) + '
'; // 搜索时隐藏分页 const paginationContainer = document.getElementById('skills-pagination'); if (paginationContainer) { paginationContainer.innerHTML = ''; } return; } skillsListEl.innerHTML = filteredSkills.map(skill => { const sid = skill.id || skill.name || ''; const ver = skill.version ? _t('skills.cardVersion', { version: skill.version }) : ''; const sc = typeof skill.script_count === 'number' && skill.script_count > 0 ? _t('skills.cardScripts', { count: skill.script_count }) : ''; const fc = typeof skill.file_count === 'number' && skill.file_count > 0 ? _t('skills.cardFiles', { count: skill.file_count }) : ''; const meta = [ver, fc, sc].filter(Boolean).join(' · '); return `

${escapeHtml(skill.name || sid)}

${meta ? `
${escapeHtml(meta)}
` : ''}
${escapeHtml(skill.description || _t('skills.noDescription'))}
`; }).join(''); skillsListEl.querySelectorAll('[data-skill-view]').forEach(btn => { btn.addEventListener('click', () => viewSkill(btn.getAttribute('data-skill-view'))); }); skillsListEl.querySelectorAll('[data-skill-edit]').forEach(btn => { btn.addEventListener('click', () => editSkill(btn.getAttribute('data-skill-edit'))); }); skillsListEl.querySelectorAll('[data-skill-delete]').forEach(btn => { btn.addEventListener('click', () => deleteSkill(btn.getAttribute('data-skill-delete'))); }); // 确保列表容器可以滚动,分页栏可见 // 使用 setTimeout 确保 DOM 更新完成后再检查 setTimeout(() => { const paginationContainer = document.getElementById('skills-pagination'); if (paginationContainer && !skillsSearchKeyword) { // 确保分页栏可见 paginationContainer.style.display = 'block'; paginationContainer.style.visibility = 'visible'; } }, 0); } // 渲染分页组件(参考MCP管理页面样式) function renderSkillsPagination() { const paginationContainer = document.getElementById('skills-pagination'); if (!paginationContainer) return; const total = skillsPagination.total; const pageSize = skillsPagination.pageSize; const currentPage = skillsPagination.currentPage; const totalPages = Math.ceil(total / pageSize); // 即使只有一页也显示分页信息(参考MCP样式) if (total === 0) { paginationContainer.innerHTML = ''; return; } // 计算显示范围 const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1; const end = total === 0 ? 0 : Math.min(currentPage * pageSize, total); let paginationHTML = ''; paginationContainer.innerHTML = paginationHTML; // 确保分页组件与列表内容区域对齐(不包括滚动条) function alignPaginationWidth() { const skillsList = document.getElementById('skills-list'); if (skillsList && paginationContainer) { // 确保分页容器始终可见 paginationContainer.style.display = ''; paginationContainer.style.visibility = 'visible'; paginationContainer.style.opacity = '1'; // 获取列表的实际内容宽度(不包括滚动条) const listClientWidth = skillsList.clientWidth; // 可视区域宽度(不包括滚动条) const listScrollHeight = skillsList.scrollHeight; // 内容总高度 const listClientHeight = skillsList.clientHeight; // 可视区域高度 const hasScrollbar = listScrollHeight > listClientHeight; // 如果列表有垂直滚动条,分页组件应该与列表内容区域对齐(clientWidth) // 如果没有滚动条,使用100%宽度 if (hasScrollbar && listClientWidth > 0) { // 分页组件应该与列表内容区域对齐,不包括滚动条 paginationContainer.style.width = `${listClientWidth}px`; } else { // 如果没有滚动条,使用100%宽度 paginationContainer.style.width = '100%'; } } } // 立即执行一次 alignPaginationWidth(); // 监听窗口大小变化和列表内容变化 const resizeObserver = new ResizeObserver(() => { alignPaginationWidth(); }); const skillsList = document.getElementById('skills-list'); if (skillsList) { resizeObserver.observe(skillsList); } // 确保分页容器始终可见(防止被隐藏) paginationContainer.style.display = 'block'; paginationContainer.style.visibility = 'visible'; } // 改变每页显示数量 async function changeSkillsPageSize() { const pageSizeSelect = document.getElementById('skills-page-size-pagination'); if (!pageSizeSelect) return; const newPageSize = parseInt(pageSizeSelect.value); if (isNaN(newPageSize) || newPageSize <= 0) return; // 保存到localStorage try { localStorage.setItem('skillsPageSize', newPageSize.toString()); } catch (e) { console.warn('无法保存分页设置到localStorage:', e); } // 更新分页状态 skillsPagination.pageSize = newPageSize; // 重新计算当前页(确保不超出范围) const totalPages = Math.ceil(skillsPagination.total / newPageSize); const currentPage = Math.min(skillsPagination.currentPage, totalPages || 1); skillsPagination.currentPage = currentPage; // 重新加载数据 await loadSkills(currentPage, newPageSize); } // 更新skills管理统计信息 function updateSkillsManagementStats() { const statsEl = document.getElementById('skills-management-stats'); if (!statsEl) return; const totalEl = statsEl.querySelector('.skill-stat-value'); if (totalEl) { totalEl.textContent = skillsPagination.total; } } // 搜索skills function handleSkillsSearchInput() { clearTimeout(skillsSearchTimeout); skillsSearchTimeout = setTimeout(() => { searchSkills(); }, 300); } async function searchSkills() { const searchInput = document.getElementById('skills-search'); if (!searchInput) return; skillsSearchKeyword = searchInput.value.trim(); const clearBtn = document.getElementById('skills-search-clear'); if (clearBtn) { clearBtn.style.display = skillsSearchKeyword ? 'block' : 'none'; } if (skillsSearchKeyword) { // 有搜索关键词时,使用后端搜索API(加载所有匹配结果,不分页) try { const response = await apiFetch(`/api/skills?search=${encodeURIComponent(skillsSearchKeyword)}&limit=10000&offset=0`); if (!response.ok) { throw new Error(_t('skills.loadListFailed')); } const data = await response.json(); skillsList = data.skills || []; skillsPagination.total = data.total || 0; renderSkillsList(); // 搜索时隐藏分页 const paginationContainer = document.getElementById('skills-pagination'); if (paginationContainer) { paginationContainer.innerHTML = ''; } // 更新统计信息(显示搜索结果数量) updateSkillsManagementStats(); } catch (error) { console.error('搜索skills失败:', error); showNotification(_t('skills.searchFailed') + ': ' + error.message, 'error'); } } else { // 没有搜索关键词时,恢复分页加载 await loadSkills(1, skillsPagination.pageSize); } } // 清除skills搜索 function clearSkillsSearch() { const searchInput = document.getElementById('skills-search'); if (searchInput) { searchInput.value = ''; } skillsSearchKeyword = ''; const clearBtn = document.getElementById('skills-search-clear'); if (clearBtn) { clearBtn.style.display = 'none'; } // 恢复分页加载 loadSkills(1, skillsPagination.pageSize); } // 刷新skills async function refreshSkills() { await loadSkills(skillsPagination.currentPage, skillsPagination.pageSize); showNotification(_t('skills.refreshed'), 'success'); } // 显示添加skill模态框 function wireSkillModalOnce() { if (skillModalControlsWired) return; skillModalControlsWired = true; const addTa = document.getElementById('skill-content-add'); const edTa = document.getElementById('skill-content'); if (addTa) addTa.addEventListener('input', () => { if (skillModalAddMode) skillFileDirty = true; }); if (edTa) edTa.addEventListener('input', () => { if (!skillModalAddMode) skillFileDirty = true; }); const nb = document.getElementById('skill-new-file-btn'); if (nb) { nb.addEventListener('click', () => { if (!currentEditingSkillName) return; const inp = document.getElementById('skill-new-file-path'); const p = (inp && inp.value || '').trim(); if (!p) { showNotification(_t('skillModal.newFilePathRequired'), 'error'); return; } if (p.includes('..') || p.startsWith('/')) { showNotification(_t('skillModal.newFilePathInvalid'), 'error'); return; } selectSkillPackageFile(currentEditingSkillName, p, { force: true, freshContent: '' }); if (inp) inp.value = ''; }); } } function showAddSkillModal() { wireSkillModalOnce(); const modal = document.getElementById('skill-modal'); if (!modal) return; skillModalAddMode = true; skillFileDirty = false; skillActivePath = 'SKILL.md'; skillPackageFiles = []; const pkg = document.getElementById('skill-package-editor'); const addEd = document.getElementById('skill-add-editor'); if (pkg) pkg.style.display = 'none'; if (addEd) addEd.style.display = 'block'; document.getElementById('skill-modal-title').textContent = _t('skills.addSkill'); document.getElementById('skill-name').value = ''; document.getElementById('skill-name').disabled = false; document.getElementById('skill-description').value = ''; const addTa = document.getElementById('skill-content-add'); if (addTa) addTa.value = ''; modal.style.display = 'flex'; } function renderSkillPackageTree() { const el = document.getElementById('skill-package-tree'); if (!el) return; const rows = (skillPackageFiles || []).filter(f => f.path && f.path !== '.').sort((a, b) => String(a.path).localeCompare(String(b.path))); if (rows.length === 0) { el.innerHTML = '
' + escapeHtml(_t('skillModal.noPackageFiles')) + '
'; return; } el.innerHTML = rows.map(f => { const path = f.path || ''; if (f.is_dir) { return `
${escapeHtml(path)}/
`; } const sel = path === skillActivePath ? 'font-weight:600;background:rgba(99,102,241,0.12);' : ''; return `
${escapeHtml(path)}
`; }).join(''); el.querySelectorAll('[data-skill-tree-path]').forEach(node => { node.addEventListener('click', () => { const p = node.getAttribute('data-skill-tree-path'); if (p) selectSkillPackageFile(currentEditingSkillName, p, {}); }); }); } async function selectSkillPackageFile(skillId, path, opts) { const force = opts && opts.force; const freshContent = opts && Object.prototype.hasOwnProperty.call(opts, 'freshContent') ? opts.freshContent : null; if (!force && skillFileDirty) { if (!confirm(_t('skillModal.unsavedSwitch'))) { return; } } skillActivePath = path; const label = document.getElementById('skill-active-path'); if (label) label.textContent = path; const hint = document.getElementById('skill-body-hint-edit'); if (hint) hint.style.display = path === 'SKILL.md' ? 'block' : 'none'; const ta = document.getElementById('skill-content'); if (!ta) return; if (freshContent !== null) { ta.value = freshContent; skillFileDirty = true; renderSkillPackageTree(); return; } try { if (path === 'SKILL.md') { const response = await apiFetch(`/api/skills/${encodeURIComponent(skillId)}?depth=full`); if (!response.ok) throw new Error(_t('skills.loadDetailFailed')); const data = await response.json(); const skill = data.skill; ta.value = skill && skill.content != null ? skill.content : ''; } else { const response = await apiFetch(`/api/skills/${encodeURIComponent(skillId)}/file?path=${encodeURIComponent(path)}`); if (!response.ok) throw new Error(_t('skills.loadDetailFailed')); const data = await response.json(); ta.value = data.content != null ? data.content : ''; } skillFileDirty = false; renderSkillPackageTree(); } catch (e) { console.error(e); showNotification(_t('skills.loadDetailFailed') + ': ' + e.message, 'error'); } } // 编辑skill async function editSkill(skillId) { wireSkillModalOnce(); try { const [detailRes, filesRes] = await Promise.all([ apiFetch(`/api/skills/${encodeURIComponent(skillId)}?depth=full`), apiFetch(`/api/skills/${encodeURIComponent(skillId)}/files`) ]); if (!detailRes.ok) { throw new Error(_t('skills.loadDetailFailed')); } const data = await detailRes.json(); const skill = data.skill; const modal = document.getElementById('skill-modal'); if (!modal) return; skillModalAddMode = false; skillFileDirty = false; skillActivePath = 'SKILL.md'; const pkg = document.getElementById('skill-package-editor'); const addEd = document.getElementById('skill-add-editor'); if (pkg) pkg.style.display = 'block'; if (addEd) addEd.style.display = 'none'; document.getElementById('skill-modal-title').textContent = _t('skills.editSkill'); document.getElementById('skill-name').value = skill.id || skillId; document.getElementById('skill-name').disabled = true; document.getElementById('skill-description').value = skill.description || ''; if (filesRes.ok) { const fd = await filesRes.json(); skillPackageFiles = fd.files || []; } else { skillPackageFiles = []; } renderSkillPackageTree(); const ta = document.getElementById('skill-content'); if (ta) ta.value = skill.content || ''; const hint = document.getElementById('skill-body-hint-edit'); if (hint) hint.style.display = 'block'; currentEditingSkillName = skillId; modal.style.display = 'flex'; } catch (error) { console.error('加载skill详情失败:', error); showNotification(_t('skills.loadDetailFailed') + ': ' + error.message, 'error'); } } // 查看 skill:先摘要再按需拉全文(与多代理 Eino skill 渐进披露思路一致) async function viewSkill(skillId) { try { const sumRes = await apiFetch(`/api/skills/${encodeURIComponent(skillId)}?depth=summary`); if (!sumRes.ok) { throw new Error(_t('skills.loadDetailFailed')); } const sumData = await sumRes.json(); const sumSkill = sumData.skill; const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'skill-view-modal'; const viewTitle = _t('skills.viewSkillTitle', { name: sumSkill.name || skillId }); const descLabel = _t('skills.descriptionLabel'); const pathLabel = _t('skills.pathLabel'); const modTimeLabel = _t('skills.modTimeLabel'); const contentLabel = _t('skills.contentLabel'); const closeBtn = _t('common.close'); const editBtn = _t('common.edit'); const loadFullLabel = _t('skills.loadFullBody'); const scriptsLabel = _t('skills.scriptsHeading'); let scriptsBlock = ''; if (Array.isArray(sumSkill.scripts) && sumSkill.scripts.length > 0) { const lines = sumSkill.scripts.map(s => { const rel = escapeHtml(s.rel_path || s.RelPath || ''); const dn = escapeHtml(s.description || s.Description || ''); return `
  • ${rel}${dn ? ' — ' + dn : ''}
  • `; }).join(''); scriptsBlock = `
    ${escapeHtml(scriptsLabel)}
    `; } modal.innerHTML = ` `; document.body.appendChild(modal); modal.style.display = 'flex'; const close = () => closeSkillViewModal(); modal.querySelectorAll('[data-skill-view-close]').forEach(el => el.addEventListener('click', close)); modal.querySelector('[data-skill-view-edit]').addEventListener('click', () => { close(); editSkill(skillId); }); modal.querySelector('[data-skill-load-full]').addEventListener('click', async () => { const pre = modal.querySelector('#skill-view-body'); const btn = modal.querySelector('[data-skill-load-full]'); if (!pre || !btn) return; btn.disabled = true; try { const fullRes = await apiFetch(`/api/skills/${encodeURIComponent(skillId)}?depth=full`); if (!fullRes.ok) throw new Error(_t('skills.loadDetailFailed')); const fullData = await fullRes.json(); pre.textContent = fullData.skill && fullData.skill.content != null ? fullData.skill.content : ''; } catch (e) { showNotification(_t('skills.loadFullFailed') + ': ' + e.message, 'error'); } finally { btn.disabled = false; } }); } catch (error) { console.error('查看skill失败:', error); showNotification(_t('skills.viewFailed') + ': ' + error.message, 'error'); } } // 关闭查看模态框 function closeSkillViewModal() { const modal = document.getElementById('skill-view-modal'); if (modal) { modal.remove(); } } // 关闭skill模态框 function closeSkillModal() { const modal = document.getElementById('skill-modal'); if (modal) { modal.style.display = 'none'; currentEditingSkillName = null; skillModalAddMode = true; skillFileDirty = false; skillPackageFiles = []; skillActivePath = 'SKILL.md'; } } // 保存skill async function saveSkill() { if (isSavingSkill) return; const name = document.getElementById('skill-name').value.trim(); const description = document.getElementById('skill-description').value.trim(); if (!name) { showNotification(_t('skills.nameRequired'), 'error'); return; } if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) { showNotification(_t('skills.nameInvalid'), 'error'); return; } if (skillModalAddMode || !currentEditingSkillName) { if (!description) { showNotification(_t('skills.descriptionRequired'), 'error'); return; } const content = (document.getElementById('skill-content-add') || {}).value; const body = (content || '').trim(); if (!body) { showNotification(_t('skills.contentRequired'), 'error'); return; } } isSavingSkill = true; const saveBtn = document.querySelector('#skill-modal .btn-primary'); if (saveBtn) { saveBtn.disabled = true; saveBtn.textContent = _t('skills.saving'); } try { if (skillModalAddMode || !currentEditingSkillName) { const content = (document.getElementById('skill-content-add') || {}).value; const body = (content || '').trim(); const response = await apiFetch('/api/skills', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, description, content: body }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || _t('skills.saveFailed')); } showNotification(_t('skills.createdSuccess'), 'success'); closeSkillModal(); await loadSkills(skillsPagination.currentPage, skillsPagination.pageSize); return; } const path = skillActivePath || 'SKILL.md'; const ta = document.getElementById('skill-content'); const raw = ta ? ta.value : ''; if (path === 'SKILL.md') { if (!raw.trim()) { showNotification(_t('skills.contentRequired'), 'error'); return; } const response = await apiFetch(`/api/skills/${encodeURIComponent(currentEditingSkillName)}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ description: description, content: raw.trim() }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || _t('skills.saveFailed')); } } else { const response = await apiFetch(`/api/skills/${encodeURIComponent(currentEditingSkillName)}/file`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: path, content: raw }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || _t('skills.saveFailed')); } } skillFileDirty = false; showNotification(_t('skills.saveSuccess'), 'success'); const filesRes = await apiFetch(`/api/skills/${encodeURIComponent(currentEditingSkillName)}/files`); if (filesRes.ok) { const fd = await filesRes.json(); skillPackageFiles = fd.files || []; renderSkillPackageTree(); } await loadSkills(skillsPagination.currentPage, skillsPagination.pageSize); } catch (error) { console.error('保存skill失败:', error); showNotification(_t('skills.saveFailed') + ': ' + error.message, 'error'); } finally { isSavingSkill = false; if (saveBtn) { saveBtn.disabled = false; saveBtn.textContent = _t('common.save'); } } } // 删除skill async function deleteSkill(skillName) { // 先检查是否有角色绑定了该skill let boundRoles = []; try { const checkResponse = await apiFetch(`/api/skills/${encodeURIComponent(skillName)}/bound-roles`); if (checkResponse.ok) { const checkData = await checkResponse.json(); boundRoles = checkData.bound_roles || []; } } catch (error) { console.warn('检查skill绑定失败:', error); // 如果检查失败,继续执行删除流程 } // 构建确认消息 let confirmMessage = _t('skills.deleteConfirm', { name: skillName }); if (boundRoles.length > 0) { const rolesList = boundRoles.join('、'); confirmMessage = _t('skills.deleteConfirmWithRoles', { name: skillName, count: boundRoles.length, roles: rolesList }); } if (!confirm(confirmMessage)) { return; } try { const response = await apiFetch(`/api/skills/${encodeURIComponent(skillName)}`, { method: 'DELETE' }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || _t('skills.deleteFailed')); } const data = await response.json(); let successMessage = _t('skills.deleteSuccess'); if (data.affected_roles && data.affected_roles.length > 0) { const rolesList = data.affected_roles.join('、'); successMessage = _t('skills.deleteSuccessWithRoles', { count: data.affected_roles.length, roles: rolesList }); } showNotification(successMessage, 'success'); // 如果当前页没有数据了,回到上一页 const currentPage = skillsPagination.currentPage; const totalAfterDelete = skillsPagination.total - 1; const totalPages = Math.ceil(totalAfterDelete / skillsPagination.pageSize); const pageToLoad = currentPage > totalPages && totalPages > 0 ? totalPages : currentPage; await loadSkills(pageToLoad, skillsPagination.pageSize); } catch (error) { console.error('删除skill失败:', error); showNotification(_t('skills.deleteFailed') + ': ' + error.message, 'error'); } } // ==================== Skills状态监控相关函数 ==================== // 加载skills监控数据 async function loadSkillsMonitor() { try { const response = await apiFetch('/api/skills/stats'); if (!response.ok) { throw new Error(_t('skills.loadStatsFailed')); } const data = await response.json(); skillsStats = { total: data.total_skills || 0, totalCalls: data.total_calls || 0, totalSuccess: data.total_success || 0, totalFailed: data.total_failed || 0, skillsDir: data.skills_dir || '', stats: data.stats || [] }; renderSkillsMonitor(); } catch (error) { console.error('加载skills监控数据失败:', error); showNotification(_t('skills.loadStatsFailed') + ': ' + error.message, 'error'); const statsEl = document.getElementById('skills-stats'); if (statsEl) { statsEl.innerHTML = '
    ' + _t('skills.loadStatsErrorShort') + ': ' + escapeHtml(error.message) + '
    '; } const monitorListEl = document.getElementById('skills-monitor-list'); if (monitorListEl) { monitorListEl.innerHTML = '
    ' + _t('skills.loadCallStatsError') + ': ' + escapeHtml(error.message) + '
    '; } } } // 渲染skills监控页面 function renderSkillsMonitor() { // 渲染总体统计 const statsEl = document.getElementById('skills-stats'); if (statsEl) { const successRate = skillsStats.totalCalls > 0 ? ((skillsStats.totalSuccess / skillsStats.totalCalls) * 100).toFixed(1) : '0.0'; statsEl.innerHTML = `
    ${_t('skills.totalSkillsCount')}
    ${skillsStats.total}
    ${_t('skills.totalCallsCount')}
    ${skillsStats.totalCalls}
    ${_t('skills.successfulCalls')}
    ${skillsStats.totalSuccess}
    ${_t('skills.failedCalls')}
    ${skillsStats.totalFailed}
    ${_t('skills.successRate')}
    ${successRate}%
    `; } // 渲染调用统计表格 const monitorListEl = document.getElementById('skills-monitor-list'); if (!monitorListEl) return; const stats = skillsStats.stats || []; // 如果没有统计数据,显示空状态 if (stats.length === 0) { monitorListEl.innerHTML = '
    ' + _t('skills.noCallRecords') + '
    '; return; } // 按调用次数排序(降序),如果调用次数相同,按名称排序 const sortedStats = [...stats].sort((a, b) => { const callsA = b.total_calls || 0; const callsB = a.total_calls || 0; if (callsA !== callsB) { return callsA - callsB; } return (a.skill_name || '').localeCompare(b.skill_name || ''); }); monitorListEl.innerHTML = ` ${sortedStats.map(stat => { const totalCalls = stat.total_calls || 0; const successCalls = stat.success_calls || 0; const failedCalls = stat.failed_calls || 0; const successRate = totalCalls > 0 ? ((successCalls / totalCalls) * 100).toFixed(1) : '0.0'; const lastCallTime = stat.last_call_time && stat.last_call_time !== '-' ? stat.last_call_time : '-'; return ` `; }).join('')}
    ${_t('skills.skillName')} ${_t('skills.totalCalls')} ${_t('skills.success')} ${_t('skills.failure')} ${_t('skills.successRate')} ${_t('skills.lastCallTime')}
    ${escapeHtml(stat.skill_name || '')} ${totalCalls} ${successCalls} ${failedCalls} ${successRate}% ${escapeHtml(lastCallTime)}
    `; } // 刷新skills监控 async function refreshSkillsMonitor() { await loadSkillsMonitor(); showNotification(_t('skills.refreshed'), 'success'); } // 清空skills统计数据 async function clearSkillsStats() { if (!confirm(_t('skills.clearStatsConfirm'))) { return; } try { const response = await apiFetch('/api/skills/stats', { method: 'DELETE' }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || _t('skills.clearStatsFailed')); } showNotification(_t('skills.statsCleared'), 'success'); // 重新加载统计数据 await loadSkillsMonitor(); } catch (error) { console.error('清空统计数据失败:', error); showNotification(_t('skills.clearStatsFailed') + ': ' + error.message, 'error'); } } // HTML转义函数 function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // 语言切换时重新渲染当前页(技能列表与分页使用 _t,需随语言更新) document.addEventListener('languagechange', function () { const page = document.getElementById('page-skills-management'); if (page && page.classList.contains('active')) { renderSkillsList(); if (!skillsSearchKeyword) { renderSkillsPagination(); } } const pkg = document.getElementById('skill-package-editor'); if (pkg && pkg.style.display !== 'none' && currentEditingSkillName) { renderSkillPackageTree(); } }); document.addEventListener('DOMContentLoaded', function () { startSkillsAutoRefresh(); });