// 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 `
`;
}).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 = `
${sumSkill.version ? `
${escapeHtml(_t('skills.versionLabel'))} ${escapeHtml(sumSkill.version)}
` : ''}
${sumSkill.description ? `
${escapeHtml(descLabel)} ${escapeHtml(sumSkill.description)}
` : ''}
${scriptsBlock}
${escapeHtml(pathLabel)} ${escapeHtml(sumSkill.path || '')}
${escapeHtml(modTimeLabel)} ${escapeHtml(sumSkill.mod_time || '')}
${escapeHtml(contentLabel)} ${escapeHtml(_t('skills.summaryHint'))}
${escapeHtml(sumSkill.content || '')}
`;
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 = `
| ${_t('skills.skillName')} |
${_t('skills.totalCalls')} |
${_t('skills.success')} |
${_t('skills.failure')} |
${_t('skills.successRate')} |
${_t('skills.lastCallTime')} |
${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 `
| ${escapeHtml(stat.skill_name || '')} |
${totalCalls} |
${successCalls} |
${failedCalls} |
${successRate}% |
${escapeHtml(lastCallTime)} |
`;
}).join('')}
`;
}
// 刷新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();
});