mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-23 16:09:44 +02:00
Add files via upload
This commit is contained in:
+11
-6
@@ -61,18 +61,23 @@
|
||||
const isFormControl = (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA');
|
||||
const attrList = el.getAttribute('data-i18n-attr');
|
||||
const text = i18next.t(key);
|
||||
|
||||
// 仅当未使用 data-i18n-attr 时才替换元素文本内容(否则会覆盖卡片内的数字、子节点等)
|
||||
// input/textarea:永不设置 textContent(会变成 value),只更新属性
|
||||
if (!attrList && !skipText && !isFormControl && text && typeof text === 'string') {
|
||||
// 仅当元素无子元素(仅文本或空)时才替换文本,避免覆盖卡片内的数字、子节点等;input/textarea 永不设置 textContent
|
||||
const hasNoElementChildren = !el.querySelector('*');
|
||||
if (!skipText && !isFormControl && hasNoElementChildren && text && typeof text === 'string') {
|
||||
el.textContent = text;
|
||||
}
|
||||
|
||||
if (attrList) {
|
||||
const titleKey = el.getAttribute('data-i18n-title');
|
||||
attrList.split(',').map(function (s) { return s.trim(); }).forEach(function (attr) {
|
||||
if (!attr) return;
|
||||
if (text && typeof text === 'string') {
|
||||
el.setAttribute(attr, text);
|
||||
var val = text;
|
||||
if (attr === 'title' && titleKey) {
|
||||
var titleText = i18next.t(titleKey);
|
||||
if (titleText && typeof titleText === 'string') val = titleText;
|
||||
}
|
||||
if (val && typeof val === 'string') {
|
||||
el.setAttribute(attr, val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+39
-36
@@ -1,4 +1,7 @@
|
||||
// 角色管理相关功能
|
||||
function _t(key, opts) {
|
||||
return typeof window.t === 'function' ? window.t(key, opts) : key;
|
||||
}
|
||||
let currentRole = localStorage.getItem('currentRole') || '';
|
||||
let roles = [];
|
||||
let rolesSearchKeyword = ''; // 角色搜索关键词
|
||||
@@ -54,7 +57,7 @@ async function loadRoles() {
|
||||
return roles;
|
||||
} catch (error) {
|
||||
console.error('加载角色失败:', error);
|
||||
showNotification('加载角色失败: ' + error.message, 'error');
|
||||
showNotification(_t('roles.loadFailed') + ': ' + error.message, 'error');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -167,9 +170,9 @@ function renderRoleSelectionSidebar() {
|
||||
const icon = getRoleIcon(role);
|
||||
|
||||
// 处理默认角色的描述
|
||||
let description = role.description || '暂无描述';
|
||||
let description = role.description || _t('roles.noDescription');
|
||||
if (isDefaultRole && !role.description) {
|
||||
description = '默认角色,不额外携带用户提示词,使用默认MCP';
|
||||
description = _t('roles.defaultRoleDescription');
|
||||
}
|
||||
|
||||
roleItem.innerHTML = `
|
||||
@@ -282,7 +285,7 @@ function renderRolesList() {
|
||||
|
||||
if (filteredRoles.length === 0) {
|
||||
rolesList.innerHTML = '<div class="empty-state">' +
|
||||
(rolesSearchKeyword ? '没有找到匹配的角色' : '暂无角色') +
|
||||
(rolesSearchKeyword ? _t('roles.noMatchingRoles') : _t('roles.noRoles')) +
|
||||
'</div>';
|
||||
return;
|
||||
}
|
||||
@@ -312,7 +315,7 @@ function renderRolesList() {
|
||||
let toolsDisplay = '';
|
||||
let toolsCount = 0;
|
||||
if (role.name === '默认') {
|
||||
toolsDisplay = '使用所有工具';
|
||||
toolsDisplay = _t('roleModal.usingAllTools');
|
||||
} else if (role.tools && role.tools.length > 0) {
|
||||
toolsCount = role.tools.length;
|
||||
// 显示前5个工具名称
|
||||
@@ -324,13 +327,13 @@ function renderRolesList() {
|
||||
if (toolsCount <= 5) {
|
||||
toolsDisplay = toolNames.join(', ');
|
||||
} else {
|
||||
toolsDisplay = toolNames.join(', ') + ` 等 ${toolsCount} 个`;
|
||||
toolsDisplay = toolNames.join(', ') + _t('roleModal.andNMore', { count: toolsCount });
|
||||
}
|
||||
} else if (role.mcps && role.mcps.length > 0) {
|
||||
toolsCount = role.mcps.length;
|
||||
toolsDisplay = `等 ${toolsCount} 个`;
|
||||
toolsDisplay = _t('roleModal.andNMore', { count: toolsCount });
|
||||
} else {
|
||||
toolsDisplay = '使用所有工具';
|
||||
toolsDisplay = _t('roleModal.usingAllTools');
|
||||
}
|
||||
|
||||
return `
|
||||
@@ -341,17 +344,17 @@ function renderRolesList() {
|
||||
${escapeHtml(role.name)}
|
||||
</h3>
|
||||
<span class="role-card-badge ${role.enabled !== false ? 'enabled' : 'disabled'}">
|
||||
${role.enabled !== false ? '已启用' : '已禁用'}
|
||||
${role.enabled !== false ? _t('roles.enabled') : _t('roles.disabled')}
|
||||
</span>
|
||||
</div>
|
||||
<div class="role-card-description">${escapeHtml(role.description || '无描述')}</div>
|
||||
<div class="role-card-description">${escapeHtml(role.description || _t('roles.noDescriptionShort'))}</div>
|
||||
<div class="role-card-tools">
|
||||
<span class="role-card-tools-label">工具:</span>
|
||||
<span class="role-card-tools-label">${_t('roleModal.toolsLabel')}</span>
|
||||
<span class="role-card-tools-value">${toolsDisplay}</span>
|
||||
</div>
|
||||
<div class="role-card-actions">
|
||||
<button class="btn-secondary btn-small" onclick="editRole('${escapeHtml(role.name)}')">编辑</button>
|
||||
${role.name !== '默认' ? `<button class="btn-secondary btn-small btn-danger" onclick="deleteRole('${escapeHtml(role.name)}')">删除</button>` : ''}
|
||||
<button class="btn-secondary btn-small" onclick="editRole('${escapeHtml(role.name)}')">${_t('common.edit')}</button>
|
||||
${role.name !== '默认' ? `<button class="btn-secondary btn-small btn-danger" onclick="deleteRole('${escapeHtml(role.name)}')">${_t('common.delete')}</button>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -503,7 +506,7 @@ async function loadRoleTools(page = 1, searchKeyword = '') {
|
||||
console.error('加载工具列表失败:', error);
|
||||
const toolsList = document.getElementById('role-tools-list');
|
||||
if (toolsList) {
|
||||
toolsList.innerHTML = `<div class="tools-error">加载工具列表失败: ${escapeHtml(error.message)}</div>`;
|
||||
toolsList.innerHTML = `<div class="tools-error">${_t('roleModal.loadToolsFailed')}: ${escapeHtml(error.message)}</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -521,7 +524,7 @@ function renderRoleToolsList() {
|
||||
listContainer.innerHTML = '';
|
||||
|
||||
if (allRoleTools.length === 0) {
|
||||
listContainer.innerHTML = '<div class="tools-empty">暂无工具</div>';
|
||||
listContainer.innerHTML = '<div class="tools-empty">' + _t('roleModal.noTools') + '</div>';
|
||||
toolsList.appendChild(listContainer);
|
||||
return;
|
||||
}
|
||||
@@ -594,16 +597,16 @@ function renderRoleToolsPagination() {
|
||||
const startItem = (page - 1) * roleToolsPagination.pageSize + 1;
|
||||
const endItem = Math.min(page * roleToolsPagination.pageSize, total);
|
||||
|
||||
const paginationShowText = _t('roleModal.paginationShow', { start: startItem, end: endItem, total: total }) +
|
||||
(roleToolsSearchKeyword ? _t('roleModal.paginationSearch', { keyword: roleToolsSearchKeyword }) : '');
|
||||
pagination.innerHTML = `
|
||||
<div class="pagination-info">
|
||||
显示 ${startItem}-${endItem} / 共 ${total} 个工具${roleToolsSearchKeyword ? ` (搜索: "${escapeHtml(roleToolsSearchKeyword)}")` : ''}
|
||||
</div>
|
||||
<div class="pagination-info">${paginationShowText}</div>
|
||||
<div class="pagination-controls">
|
||||
<button class="btn-secondary" onclick="loadRoleTools(1, '${escapeHtml(roleToolsSearchKeyword)}')" ${page === 1 ? 'disabled' : ''}>首页</button>
|
||||
<button class="btn-secondary" onclick="loadRoleTools(${page - 1}, '${escapeHtml(roleToolsSearchKeyword)}')" ${page === 1 ? 'disabled' : ''}>上一页</button>
|
||||
<span class="pagination-page">第 ${page} / ${totalPages} 页</span>
|
||||
<button class="btn-secondary" onclick="loadRoleTools(${page + 1}, '${escapeHtml(roleToolsSearchKeyword)}')" ${page === totalPages ? 'disabled' : ''}>下一页</button>
|
||||
<button class="btn-secondary" onclick="loadRoleTools(${totalPages}, '${escapeHtml(roleToolsSearchKeyword)}')" ${page === totalPages ? 'disabled' : ''}>末页</button>
|
||||
<button class="btn-secondary" onclick="loadRoleTools(1, '${escapeHtml(roleToolsSearchKeyword)}')" ${page === 1 ? 'disabled' : ''}>${_t('roleModal.firstPage')}</button>
|
||||
<button class="btn-secondary" onclick="loadRoleTools(${page - 1}, '${escapeHtml(roleToolsSearchKeyword)}')" ${page === 1 ? 'disabled' : ''}>${_t('roleModal.prevPage')}</button>
|
||||
<span class="pagination-page">${_t('roleModal.pageOf', { page: page, total: totalPages })}</span>
|
||||
<button class="btn-secondary" onclick="loadRoleTools(${page + 1}, '${escapeHtml(roleToolsSearchKeyword)}')" ${page === totalPages ? 'disabled' : ''}>${_t('roleModal.nextPage')}</button>
|
||||
<button class="btn-secondary" onclick="loadRoleTools(${totalPages}, '${escapeHtml(roleToolsSearchKeyword)}')" ${page === totalPages ? 'disabled' : ''}>${_t('roleModal.lastPage')}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -727,8 +730,8 @@ function updateRoleToolsStats() {
|
||||
// 总工具数(所有工具,包括已启用和未启用的)
|
||||
const totalTools = roleToolsPagination.total || 0;
|
||||
statsEl.innerHTML = `
|
||||
<span title="当前页选中的工具数">✅ 当前页已选中: <strong>${currentPageEnabled}</strong> / ${currentPageTotal}</span>
|
||||
<span title="所有已启用工具中选中的工具总数(基于MCP管理)">📊 总计已选中: <strong>${totalEnabled}</strong> / ${totalTools} <em>(使用所有已启用工具)</em></span>
|
||||
<span title="${_t('roleModal.currentPageSelectedTitle')}">✅ ${_t('roleModal.currentPageSelected', { current: currentPageEnabled, total: currentPageTotal })}</span>
|
||||
<span title="${_t('roleModal.totalSelectedTitle')}">📊 ${_t('roleModal.totalSelected', { current: totalEnabled, total: totalTools })} <em>${_t('roleModal.usingAllEnabledTools')}</em></span>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
@@ -779,8 +782,8 @@ function updateRoleToolsStats() {
|
||||
const totalTools = roleToolsPagination.total || 0;
|
||||
|
||||
statsEl.innerHTML = `
|
||||
<span title="当前页选中的工具数(只统计已启用的工具)">✅ 当前页已选中: <strong>${currentPageEnabled}</strong> / ${currentPageTotal}</span>
|
||||
<span title="角色已关联的工具总数(基于角色实际配置)">📊 总计已选中: <strong>${totalSelected}</strong> / ${totalTools}</span>
|
||||
<span title="${_t('roleModal.currentPageSelectedTitle')}">✅ ${_t('roleModal.currentPageSelected', { current: currentPageEnabled, total: currentPageTotal })}</span>
|
||||
<span title="${_t('roleModal.totalSelectedTitle')}">📊 ${_t('roleModal.totalSelected', { current: totalSelected, total: totalTools })}</span>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -838,7 +841,7 @@ async function showAddRoleModal() {
|
||||
const modal = document.getElementById('role-modal');
|
||||
if (!modal) return;
|
||||
|
||||
document.getElementById('role-modal-title').textContent = '添加角色';
|
||||
document.getElementById('role-modal-title').textContent = _t('roleModal.addRole');
|
||||
document.getElementById('role-name').value = '';
|
||||
document.getElementById('role-name').disabled = false;
|
||||
document.getElementById('role-description').value = '';
|
||||
@@ -918,14 +921,14 @@ async function showAddRoleModal() {
|
||||
async function editRole(roleName) {
|
||||
const role = roles.find(r => r.name === roleName);
|
||||
if (!role) {
|
||||
showNotification('角色不存在', 'error');
|
||||
showNotification(_t('roleModal.roleNotFound'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = document.getElementById('role-modal');
|
||||
if (!modal) return;
|
||||
|
||||
document.getElementById('role-modal-title').textContent = '编辑角色';
|
||||
document.getElementById('role-modal-title').textContent = _t('roleModal.editRole');
|
||||
document.getElementById('role-name').value = role.name;
|
||||
document.getElementById('role-name').disabled = true; // 编辑时不允许修改名称
|
||||
document.getElementById('role-description').value = role.description || '';
|
||||
@@ -1186,7 +1189,7 @@ async function loadAllToolsToStateMap() {
|
||||
async function saveRole() {
|
||||
const name = document.getElementById('role-name').value.trim();
|
||||
if (!name) {
|
||||
showNotification('角色名称不能为空', 'error');
|
||||
showNotification(_t('roleModal.roleNameRequired'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1227,7 +1230,7 @@ async function saveRole() {
|
||||
// 如果是首次添加角色且没有选择工具,默认使用全部工具
|
||||
if (isFirstUserRole && allSelectedTools.length === 0) {
|
||||
roleUsesAllTools = true;
|
||||
showNotification('检测到这是首次添加角色且未选择工具,将默认使用全部工具', 'info');
|
||||
showNotification(_t('roleModal.firstRoleNoToolsHint'), 'info');
|
||||
} else if (roleUsesAllTools) {
|
||||
// 如果当前使用所有工具,需要检查用户是否取消了一些工具
|
||||
// 检查状态映射中是否有未选中的已启用工具
|
||||
@@ -1358,7 +1361,7 @@ async function saveRole() {
|
||||
// 删除角色
|
||||
async function deleteRole(roleName) {
|
||||
if (roleName === '默认') {
|
||||
showNotification('不能删除默认角色', 'error');
|
||||
showNotification(_t('roleModal.cannotDeleteDefaultRole'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1469,7 +1472,7 @@ async function loadRoleSkills() {
|
||||
allRoleSkills = [];
|
||||
const skillsList = document.getElementById('role-skills-list');
|
||||
if (skillsList) {
|
||||
skillsList.innerHTML = '<div class="skills-error">加载skills列表失败: ' + error.message + '</div>';
|
||||
skillsList.innerHTML = '<div class="skills-error">' + _t('roleModal.loadSkillsFailed') + ': ' + error.message + '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1490,7 +1493,7 @@ function renderRoleSkills() {
|
||||
|
||||
if (filteredSkills.length === 0) {
|
||||
skillsList.innerHTML = '<div class="skills-empty">' +
|
||||
(roleSkillsSearchKeyword ? '没有找到匹配的skills' : '暂无可用skills') +
|
||||
(roleSkillsSearchKeyword ? _t('roleModal.noMatchingSkills') : _t('roleModal.noSkillsAvailable')) +
|
||||
'</div>';
|
||||
updateRoleSkillsStats();
|
||||
return;
|
||||
@@ -1596,7 +1599,7 @@ function updateRoleSkillsStats() {
|
||||
filteredSkills.includes(skill)
|
||||
).length;
|
||||
|
||||
statsEl.textContent = `已选择 ${selectedCount} / ${filteredSkills.length}`;
|
||||
statsEl.textContent = _t('roleModal.skillsSelectedCount', { count: selectedCount, total: filteredSkills.length });
|
||||
}
|
||||
|
||||
// HTML转义函数
|
||||
|
||||
+17
-15
@@ -1367,7 +1367,7 @@ function formatExternalMCPJSON() {
|
||||
try {
|
||||
const jsonStr = jsonTextarea.value.trim();
|
||||
if (!jsonStr) {
|
||||
errorDiv.textContent = 'JSON不能为空';
|
||||
errorDiv.textContent = (typeof window.t === 'function' ? window.t('mcp.jsonEmpty') : 'JSON不能为空');
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
return;
|
||||
@@ -1379,7 +1379,7 @@ function formatExternalMCPJSON() {
|
||||
errorDiv.style.display = 'none';
|
||||
jsonTextarea.classList.remove('error');
|
||||
} catch (error) {
|
||||
errorDiv.textContent = 'JSON格式错误: ' + error.message;
|
||||
errorDiv.textContent = (typeof window.t === 'function' ? window.t('mcp.jsonError') : 'JSON格式错误') + ': ' + error.message;
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
}
|
||||
@@ -1387,6 +1387,7 @@ function formatExternalMCPJSON() {
|
||||
|
||||
// 加载示例
|
||||
function loadExternalMCPExample() {
|
||||
const desc = (typeof window.t === 'function' ? window.t('externalMcpModal.exampleDescription') : '示例描述');
|
||||
const example = {
|
||||
"hexstrike-ai": {
|
||||
command: "python3",
|
||||
@@ -1395,7 +1396,7 @@ function loadExternalMCPExample() {
|
||||
"--server",
|
||||
"http://example.com"
|
||||
],
|
||||
description: "示例描述",
|
||||
description: desc,
|
||||
timeout: 300
|
||||
},
|
||||
"cyberstrike-ai-http": {
|
||||
@@ -1420,7 +1421,7 @@ async function saveExternalMCP() {
|
||||
const errorDiv = document.getElementById('external-mcp-json-error');
|
||||
|
||||
if (!jsonStr) {
|
||||
errorDiv.textContent = 'JSON配置不能为空';
|
||||
errorDiv.textContent = (typeof window.t === 'function' ? window.t('mcp.jsonEmpty') : 'JSON不能为空');
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
jsonTextarea.focus();
|
||||
@@ -1431,16 +1432,17 @@ async function saveExternalMCP() {
|
||||
try {
|
||||
configObj = JSON.parse(jsonStr);
|
||||
} catch (error) {
|
||||
errorDiv.textContent = 'JSON格式错误: ' + error.message;
|
||||
errorDiv.textContent = (typeof window.t === 'function' ? window.t('mcp.jsonError') : 'JSON格式错误') + ': ' + error.message;
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
jsonTextarea.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const t = (typeof window.t === 'function' ? window.t : function (k, opts) { return k; });
|
||||
// 验证必须是对象格式
|
||||
if (typeof configObj !== 'object' || Array.isArray(configObj) || configObj === null) {
|
||||
errorDiv.textContent = '配置错误: 必须是JSON对象格式,key为配置名称,value为配置内容';
|
||||
errorDiv.textContent = t('mcp.configMustBeObject');
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
return;
|
||||
@@ -1449,7 +1451,7 @@ async function saveExternalMCP() {
|
||||
// 获取所有配置名称
|
||||
const names = Object.keys(configObj);
|
||||
if (names.length === 0) {
|
||||
errorDiv.textContent = '配置错误: 至少需要一个配置项';
|
||||
errorDiv.textContent = t('mcp.configNeedOne');
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
return;
|
||||
@@ -1458,7 +1460,7 @@ async function saveExternalMCP() {
|
||||
// 验证每个配置
|
||||
for (const name of names) {
|
||||
if (!name || name.trim() === '') {
|
||||
errorDiv.textContent = '配置错误: 配置名称不能为空';
|
||||
errorDiv.textContent = t('mcp.configNameEmpty');
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
return;
|
||||
@@ -1466,7 +1468,7 @@ async function saveExternalMCP() {
|
||||
|
||||
const config = configObj[name];
|
||||
if (typeof config !== 'object' || Array.isArray(config) || config === null) {
|
||||
errorDiv.textContent = `配置错误: "${name}" 的配置必须是对象`;
|
||||
errorDiv.textContent = t('mcp.configMustBeObj', { name: name });
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
return;
|
||||
@@ -1478,28 +1480,28 @@ async function saveExternalMCP() {
|
||||
// 验证配置内容
|
||||
const transport = config.transport || (config.command ? 'stdio' : config.url ? 'http' : '');
|
||||
if (!transport) {
|
||||
errorDiv.textContent = `配置错误: "${name}" 需要指定command(stdio模式)或url(http/sse模式)`;
|
||||
errorDiv.textContent = t('mcp.configNeedCommand', { name: name });
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (transport === 'stdio' && !config.command) {
|
||||
errorDiv.textContent = `配置错误: "${name}" stdio模式需要command字段`;
|
||||
errorDiv.textContent = t('mcp.configStdioNeedCommand', { name: name });
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (transport === 'http' && !config.url) {
|
||||
errorDiv.textContent = `配置错误: "${name}" http模式需要url字段`;
|
||||
errorDiv.textContent = t('mcp.configHttpNeedUrl', { name: name });
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (transport === 'sse' && !config.url) {
|
||||
errorDiv.textContent = `配置错误: "${name}" sse模式需要url字段`;
|
||||
errorDiv.textContent = t('mcp.configSseNeedUrl', { name: name });
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
return;
|
||||
@@ -1514,7 +1516,7 @@ async function saveExternalMCP() {
|
||||
// 如果是编辑模式,只更新当前编辑的配置
|
||||
if (currentEditingMCPName) {
|
||||
if (!configObj[currentEditingMCPName]) {
|
||||
errorDiv.textContent = `配置错误: 编辑模式下,JSON必须包含配置名称 "${currentEditingMCPName}"`;
|
||||
errorDiv.textContent = (typeof window.t === 'function' ? window.t('mcp.configEditMustContainName', { name: currentEditingMCPName }) : '配置错误: 编辑模式下,JSON必须包含配置名称 "' + currentEditingMCPName + '"');
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
return;
|
||||
@@ -1561,7 +1563,7 @@ async function saveExternalMCP() {
|
||||
alert(typeof window.t === 'function' ? window.t('mcp.saveSuccess') : '保存成功');
|
||||
} catch (error) {
|
||||
console.error('保存外部MCP失败:', error);
|
||||
errorDiv.textContent = '保存失败: ' + error.message;
|
||||
errorDiv.textContent = (typeof window.t === 'function' ? window.t('mcp.operationFailed') : '保存失败') + ': ' + error.message;
|
||||
errorDiv.style.display = 'block';
|
||||
jsonTextarea.classList.add('error');
|
||||
}
|
||||
|
||||
+94
-66
@@ -1,4 +1,7 @@
|
||||
// Skills管理相关功能
|
||||
function _t(key, opts) {
|
||||
return typeof window.t === 'function' ? window.t(key, opts) : key;
|
||||
}
|
||||
let skillsList = [];
|
||||
let currentEditingSkillName = null;
|
||||
let isSavingSkill = false; // 防止重复提交
|
||||
@@ -65,7 +68,7 @@ async function loadSkills(page = 1, pageSize = null) {
|
||||
|
||||
const response = await apiFetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error('获取skills列表失败');
|
||||
throw new Error(_t('skills.loadListFailed'));
|
||||
}
|
||||
const data = await response.json();
|
||||
skillsList = data.skills || [];
|
||||
@@ -76,10 +79,10 @@ async function loadSkills(page = 1, pageSize = null) {
|
||||
updateSkillsManagementStats();
|
||||
} catch (error) {
|
||||
console.error('加载skills列表失败:', error);
|
||||
showNotification('加载skills列表失败: ' + error.message, 'error');
|
||||
showNotification(_t('skills.loadListFailed') + ': ' + error.message, 'error');
|
||||
const skillsListEl = document.getElementById('skills-list');
|
||||
if (skillsListEl) {
|
||||
skillsListEl.innerHTML = '<div class="empty-state">加载失败: ' + error.message + '</div>';
|
||||
skillsListEl.innerHTML = '<div class="empty-state">' + _t('skills.loadFailedShort') + ': ' + escapeHtml(error.message) + '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,7 +97,7 @@ function renderSkillsList() {
|
||||
|
||||
if (filteredSkills.length === 0) {
|
||||
skillsListEl.innerHTML = '<div class="empty-state">' +
|
||||
(skillsSearchKeyword ? '没有找到匹配的skills' : '暂无skills,点击"创建Skill"创建第一个skill') +
|
||||
(skillsSearchKeyword ? _t('skills.noMatch') : _t('skills.noSkills')) +
|
||||
'</div>';
|
||||
// 搜索时隐藏分页
|
||||
const paginationContainer = document.getElementById('skills-pagination');
|
||||
@@ -109,12 +112,12 @@ function renderSkillsList() {
|
||||
<div class="skill-card">
|
||||
<div class="skill-card-header">
|
||||
<h3 class="skill-card-title">${escapeHtml(skill.name || '')}</h3>
|
||||
<div class="skill-card-description">${escapeHtml(skill.description || '无描述')}</div>
|
||||
<div class="skill-card-description">${escapeHtml(skill.description || _t('skills.noDescription'))}</div>
|
||||
</div>
|
||||
<div class="skill-card-actions">
|
||||
<button class="btn-secondary btn-small" onclick="viewSkill('${escapeHtml(skill.name)}')">查看</button>
|
||||
<button class="btn-secondary btn-small" onclick="editSkill('${escapeHtml(skill.name)}')">编辑</button>
|
||||
<button class="btn-secondary btn-small btn-danger" onclick="deleteSkill('${escapeHtml(skill.name)}')">删除</button>
|
||||
<button class="btn-secondary btn-small" onclick="viewSkill('${escapeHtml(skill.name)}')">${_t('common.view')}</button>
|
||||
<button class="btn-secondary btn-small" onclick="editSkill('${escapeHtml(skill.name)}')">${_t('common.edit')}</button>
|
||||
<button class="btn-secondary btn-small btn-danger" onclick="deleteSkill('${escapeHtml(skill.name)}')">${_t('common.delete')}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -154,12 +157,19 @@ function renderSkillsPagination() {
|
||||
|
||||
let paginationHTML = '<div class="pagination">';
|
||||
|
||||
const paginationShowText = _t('skillsPage.paginationShow', { start, end, total });
|
||||
const perPageLabelText = _t('skillsPage.perPageLabel');
|
||||
const firstPageText = _t('skillsPage.firstPage');
|
||||
const prevPageText = _t('skillsPage.prevPage');
|
||||
const pageOfText = _t('skillsPage.pageOf', { current: currentPage, total: totalPages || 1 });
|
||||
const nextPageText = _t('skillsPage.nextPage');
|
||||
const lastPageText = _t('skillsPage.lastPage');
|
||||
// 左侧:显示范围信息和每页数量选择器(参考MCP样式)
|
||||
paginationHTML += `
|
||||
<div class="pagination-info">
|
||||
<span>显示 ${start}-${end} / 共 ${total} 条</span>
|
||||
<span>${escapeHtml(paginationShowText)}</span>
|
||||
<label class="pagination-page-size">
|
||||
每页显示
|
||||
${escapeHtml(perPageLabelText)}
|
||||
<select id="skills-page-size-pagination" onchange="changeSkillsPageSize()">
|
||||
<option value="10" ${pageSize === 10 ? 'selected' : ''}>10</option>
|
||||
<option value="20" ${pageSize === 20 ? 'selected' : ''}>20</option>
|
||||
@@ -173,11 +183,11 @@ function renderSkillsPagination() {
|
||||
// 右侧:分页按钮(参考MCP样式:首页、上一页、第X/Y页、下一页、末页)
|
||||
paginationHTML += `
|
||||
<div class="pagination-controls">
|
||||
<button class="btn-secondary" onclick="loadSkills(1, ${pageSize})" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>首页</button>
|
||||
<button class="btn-secondary" onclick="loadSkills(${currentPage - 1}, ${pageSize})" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>上一页</button>
|
||||
<span class="pagination-page">第 ${currentPage} / ${totalPages || 1} 页</span>
|
||||
<button class="btn-secondary" onclick="loadSkills(${currentPage + 1}, ${pageSize})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>下一页</button>
|
||||
<button class="btn-secondary" onclick="loadSkills(${totalPages || 1}, ${pageSize})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>末页</button>
|
||||
<button class="btn-secondary" onclick="loadSkills(1, ${pageSize})" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>${escapeHtml(firstPageText)}</button>
|
||||
<button class="btn-secondary" onclick="loadSkills(${currentPage - 1}, ${pageSize})" ${currentPage === 1 || total === 0 ? 'disabled' : ''}>${escapeHtml(prevPageText)}</button>
|
||||
<span class="pagination-page">${escapeHtml(pageOfText)}</span>
|
||||
<button class="btn-secondary" onclick="loadSkills(${currentPage + 1}, ${pageSize})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>${escapeHtml(nextPageText)}</button>
|
||||
<button class="btn-secondary" onclick="loadSkills(${totalPages || 1}, ${pageSize})" ${currentPage >= totalPages || total === 0 ? 'disabled' : ''}>${escapeHtml(lastPageText)}</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -291,7 +301,7 @@ async function searchSkills() {
|
||||
try {
|
||||
const response = await apiFetch(`/api/skills?search=${encodeURIComponent(skillsSearchKeyword)}&limit=10000&offset=0`);
|
||||
if (!response.ok) {
|
||||
throw new Error('获取skills列表失败');
|
||||
throw new Error(_t('skills.loadListFailed'));
|
||||
}
|
||||
const data = await response.json();
|
||||
skillsList = data.skills || [];
|
||||
@@ -306,7 +316,7 @@ async function searchSkills() {
|
||||
updateSkillsManagementStats();
|
||||
} catch (error) {
|
||||
console.error('搜索skills失败:', error);
|
||||
showNotification('搜索失败: ' + error.message, 'error');
|
||||
showNotification(_t('skills.searchFailed') + ': ' + error.message, 'error');
|
||||
}
|
||||
} else {
|
||||
// 没有搜索关键词时,恢复分页加载
|
||||
@@ -332,7 +342,7 @@ function clearSkillsSearch() {
|
||||
// 刷新skills
|
||||
async function refreshSkills() {
|
||||
await loadSkills(skillsPagination.currentPage, skillsPagination.pageSize);
|
||||
showNotification('已刷新', 'success');
|
||||
showNotification(_t('skills.refreshed'), 'success');
|
||||
}
|
||||
|
||||
// 显示添加skill模态框
|
||||
@@ -340,7 +350,7 @@ function showAddSkillModal() {
|
||||
const modal = document.getElementById('skill-modal');
|
||||
if (!modal) return;
|
||||
|
||||
document.getElementById('skill-modal-title').textContent = '添加Skill';
|
||||
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 = '';
|
||||
@@ -354,7 +364,7 @@ async function editSkill(skillName) {
|
||||
try {
|
||||
const response = await apiFetch(`/api/skills/${encodeURIComponent(skillName)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('获取skill详情失败');
|
||||
throw new Error(_t('skills.loadDetailFailed'));
|
||||
}
|
||||
const data = await response.json();
|
||||
const skill = data.skill;
|
||||
@@ -362,7 +372,7 @@ async function editSkill(skillName) {
|
||||
const modal = document.getElementById('skill-modal');
|
||||
if (!modal) return;
|
||||
|
||||
document.getElementById('skill-modal-title').textContent = '编辑Skill';
|
||||
document.getElementById('skill-modal-title').textContent = _t('skills.editSkill');
|
||||
document.getElementById('skill-name').value = skill.name;
|
||||
document.getElementById('skill-name').disabled = true; // 编辑时不允许修改名称
|
||||
document.getElementById('skill-description').value = skill.description || '';
|
||||
@@ -372,7 +382,7 @@ async function editSkill(skillName) {
|
||||
modal.style.display = 'flex';
|
||||
} catch (error) {
|
||||
console.error('加载skill详情失败:', error);
|
||||
showNotification('加载skill详情失败: ' + error.message, 'error');
|
||||
showNotification(_t('skills.loadDetailFailed') + ': ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +391,7 @@ async function viewSkill(skillName) {
|
||||
try {
|
||||
const response = await apiFetch(`/api/skills/${encodeURIComponent(skillName)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('获取skill详情失败');
|
||||
throw new Error(_t('skills.loadDetailFailed'));
|
||||
}
|
||||
const data = await response.json();
|
||||
const skill = data.skill;
|
||||
@@ -390,22 +400,29 @@ async function viewSkill(skillName) {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'modal';
|
||||
modal.id = 'skill-view-modal';
|
||||
const viewTitle = _t('skills.viewSkillTitle', { name: skill.name });
|
||||
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');
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content" style="max-width: 900px; max-height: 90vh;">
|
||||
<div class="modal-header">
|
||||
<h2>查看Skill: ${escapeHtml(skill.name)}</h2>
|
||||
<h2>${escapeHtml(viewTitle)}</h2>
|
||||
<span class="modal-close" onclick="closeSkillViewModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body" style="overflow-y: auto; max-height: calc(90vh - 120px);">
|
||||
${skill.description ? `<div style="margin-bottom: 16px;"><strong>描述:</strong> ${escapeHtml(skill.description)}</div>` : ''}
|
||||
<div style="margin-bottom: 8px;"><strong>路径:</strong> ${escapeHtml(skill.path || '')}</div>
|
||||
<div style="margin-bottom: 16px;"><strong>修改时间:</strong> ${escapeHtml(skill.mod_time || '')}</div>
|
||||
<div style="margin-bottom: 8px;"><strong>内容:</strong></div>
|
||||
${skill.description ? `<div style="margin-bottom: 16px;"><strong>${escapeHtml(descLabel)}</strong> ${escapeHtml(skill.description)}</div>` : ''}
|
||||
<div style="margin-bottom: 8px;"><strong>${escapeHtml(pathLabel)}</strong> ${escapeHtml(skill.path || '')}</div>
|
||||
<div style="margin-bottom: 16px;"><strong>${escapeHtml(modTimeLabel)}</strong> ${escapeHtml(skill.mod_time || '')}</div>
|
||||
<div style="margin-bottom: 8px;"><strong>${escapeHtml(contentLabel)}</strong></div>
|
||||
<pre style="background: #f5f5f5; padding: 16px; border-radius: 4px; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;">${escapeHtml(skill.content || '')}</pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-secondary" onclick="closeSkillViewModal()">关闭</button>
|
||||
<button class="btn-primary" onclick="editSkill('${escapeHtml(skill.name)}'); closeSkillViewModal();">编辑</button>
|
||||
<button class="btn-secondary" onclick="closeSkillViewModal()">${escapeHtml(closeBtn)}</button>
|
||||
<button class="btn-primary" onclick="editSkill('${escapeHtml(skill.name)}'); closeSkillViewModal();">${escapeHtml(editBtn)}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -413,7 +430,7 @@ async function viewSkill(skillName) {
|
||||
modal.style.display = 'flex';
|
||||
} catch (error) {
|
||||
console.error('查看skill失败:', error);
|
||||
showNotification('查看skill失败: ' + error.message, 'error');
|
||||
showNotification(_t('skills.viewFailed') + ': ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,18 +460,18 @@ async function saveSkill() {
|
||||
const content = document.getElementById('skill-content').value.trim();
|
||||
|
||||
if (!name) {
|
||||
showNotification('skill名称不能为空', 'error');
|
||||
showNotification(_t('skills.nameRequired'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
showNotification('skill内容不能为空', 'error');
|
||||
showNotification(_t('skills.contentRequired'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证skill名称
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
||||
showNotification('skill名称只能包含字母、数字、连字符和下划线', 'error');
|
||||
showNotification(_t('skills.nameInvalid'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -462,7 +479,7 @@ async function saveSkill() {
|
||||
const saveBtn = document.querySelector('#skill-modal .btn-primary');
|
||||
if (saveBtn) {
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.textContent = '保存中...';
|
||||
saveBtn.textContent = _t('skills.saving');
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -484,20 +501,20 @@ async function saveSkill() {
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || '保存skill失败');
|
||||
throw new Error(error.error || _t('skills.saveFailed'));
|
||||
}
|
||||
|
||||
showNotification(isEdit ? 'skill已更新' : 'skill已创建', 'success');
|
||||
showNotification(isEdit ? _t('skills.saveSuccess') : _t('skills.createdSuccess'), 'success');
|
||||
closeSkillModal();
|
||||
await loadSkills(skillsPagination.currentPage, skillsPagination.pageSize);
|
||||
} catch (error) {
|
||||
console.error('保存skill失败:', error);
|
||||
showNotification('保存skill失败: ' + error.message, 'error');
|
||||
showNotification(_t('skills.saveFailed') + ': ' + error.message, 'error');
|
||||
} finally {
|
||||
isSavingSkill = false;
|
||||
if (saveBtn) {
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.textContent = '保存';
|
||||
saveBtn.textContent = _t('common.save');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -518,10 +535,10 @@ async function deleteSkill(skillName) {
|
||||
}
|
||||
|
||||
// 构建确认消息
|
||||
let confirmMessage = `确定要删除skill "${skillName}" 吗?此操作不可恢复。`;
|
||||
let confirmMessage = _t('skills.deleteConfirm', { name: skillName });
|
||||
if (boundRoles.length > 0) {
|
||||
const rolesList = boundRoles.join('、');
|
||||
confirmMessage = `确定要删除skill "${skillName}" 吗?\n\n⚠️ 该skill当前已被以下 ${boundRoles.length} 个角色绑定:\n${rolesList}\n\n删除后,系统将自动从这些角色中移除该skill的绑定。\n\n此操作不可恢复,是否继续?`;
|
||||
confirmMessage = _t('skills.deleteConfirmWithRoles', { name: skillName, count: boundRoles.length, roles: rolesList });
|
||||
}
|
||||
|
||||
if (!confirm(confirmMessage)) {
|
||||
@@ -535,14 +552,14 @@ async function deleteSkill(skillName) {
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || '删除skill失败');
|
||||
throw new Error(error.error || _t('skills.deleteFailed'));
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
let successMessage = 'skill已删除';
|
||||
let successMessage = _t('skills.deleteSuccess');
|
||||
if (data.affected_roles && data.affected_roles.length > 0) {
|
||||
const rolesList = data.affected_roles.join('、');
|
||||
successMessage = `skill已删除,已自动从 ${data.affected_roles.length} 个角色中移除绑定:${rolesList}`;
|
||||
successMessage = _t('skills.deleteSuccessWithRoles', { count: data.affected_roles.length, roles: rolesList });
|
||||
}
|
||||
showNotification(successMessage, 'success');
|
||||
|
||||
@@ -554,7 +571,7 @@ async function deleteSkill(skillName) {
|
||||
await loadSkills(pageToLoad, skillsPagination.pageSize);
|
||||
} catch (error) {
|
||||
console.error('删除skill失败:', error);
|
||||
showNotification('删除skill失败: ' + error.message, 'error');
|
||||
showNotification(_t('skills.deleteFailed') + ': ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,7 +582,7 @@ async function loadSkillsMonitor() {
|
||||
try {
|
||||
const response = await apiFetch('/api/skills/stats');
|
||||
if (!response.ok) {
|
||||
throw new Error('获取skills统计信息失败');
|
||||
throw new Error(_t('skills.loadStatsFailed'));
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
@@ -581,14 +598,14 @@ async function loadSkillsMonitor() {
|
||||
renderSkillsMonitor();
|
||||
} catch (error) {
|
||||
console.error('加载skills监控数据失败:', error);
|
||||
showNotification('加载skills监控数据失败: ' + error.message, 'error');
|
||||
showNotification(_t('skills.loadStatsFailed') + ': ' + error.message, 'error');
|
||||
const statsEl = document.getElementById('skills-stats');
|
||||
if (statsEl) {
|
||||
statsEl.innerHTML = '<div class="monitor-error">无法加载统计信息:' + escapeHtml(error.message) + '</div>';
|
||||
statsEl.innerHTML = '<div class="monitor-error">' + _t('skills.loadStatsErrorShort') + ': ' + escapeHtml(error.message) + '</div>';
|
||||
}
|
||||
const monitorListEl = document.getElementById('skills-monitor-list');
|
||||
if (monitorListEl) {
|
||||
monitorListEl.innerHTML = '<div class="monitor-error">无法加载调用统计:' + escapeHtml(error.message) + '</div>';
|
||||
monitorListEl.innerHTML = '<div class="monitor-error">' + _t('skills.loadCallStatsError') + ': ' + escapeHtml(error.message) + '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -604,23 +621,23 @@ function renderSkillsMonitor() {
|
||||
|
||||
statsEl.innerHTML = `
|
||||
<div class="monitor-stat-card">
|
||||
<div class="monitor-stat-label">总Skills数</div>
|
||||
<div class="monitor-stat-label">${_t('skills.totalSkillsCount')}</div>
|
||||
<div class="monitor-stat-value">${skillsStats.total}</div>
|
||||
</div>
|
||||
<div class="monitor-stat-card">
|
||||
<div class="monitor-stat-label">总调用次数</div>
|
||||
<div class="monitor-stat-label">${_t('skills.totalCallsCount')}</div>
|
||||
<div class="monitor-stat-value">${skillsStats.totalCalls}</div>
|
||||
</div>
|
||||
<div class="monitor-stat-card">
|
||||
<div class="monitor-stat-label">成功调用</div>
|
||||
<div class="monitor-stat-label">${_t('skills.successfulCalls')}</div>
|
||||
<div class="monitor-stat-value" style="color: #28a745;">${skillsStats.totalSuccess}</div>
|
||||
</div>
|
||||
<div class="monitor-stat-card">
|
||||
<div class="monitor-stat-label">失败调用</div>
|
||||
<div class="monitor-stat-label">${_t('skills.failedCalls')}</div>
|
||||
<div class="monitor-stat-value" style="color: #dc3545;">${skillsStats.totalFailed}</div>
|
||||
</div>
|
||||
<div class="monitor-stat-card">
|
||||
<div class="monitor-stat-label">成功率</div>
|
||||
<div class="monitor-stat-label">${_t('skills.successRate')}</div>
|
||||
<div class="monitor-stat-value">${successRate}%</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -634,7 +651,7 @@ function renderSkillsMonitor() {
|
||||
|
||||
// 如果没有统计数据,显示空状态
|
||||
if (stats.length === 0) {
|
||||
monitorListEl.innerHTML = '<div class="monitor-empty">暂无Skills调用记录</div>';
|
||||
monitorListEl.innerHTML = '<div class="monitor-empty">' + _t('skills.noCallRecords') + '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -652,12 +669,12 @@ function renderSkillsMonitor() {
|
||||
<table class="monitor-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: left !important;">Skill名称</th>
|
||||
<th style="text-align: center;">总调用</th>
|
||||
<th style="text-align: center;">成功</th>
|
||||
<th style="text-align: center;">失败</th>
|
||||
<th style="text-align: center;">成功率</th>
|
||||
<th style="text-align: left;">最后调用时间</th>
|
||||
<th style="text-align: left !important;">${_t('skills.skillName')}</th>
|
||||
<th style="text-align: center;">${_t('skills.totalCalls')}</th>
|
||||
<th style="text-align: center;">${_t('skills.success')}</th>
|
||||
<th style="text-align: center;">${_t('skills.failure')}</th>
|
||||
<th style="text-align: center;">${_t('skills.successRate')}</th>
|
||||
<th style="text-align: left;">${_t('skills.lastCallTime')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -687,12 +704,12 @@ function renderSkillsMonitor() {
|
||||
// 刷新skills监控
|
||||
async function refreshSkillsMonitor() {
|
||||
await loadSkillsMonitor();
|
||||
showNotification('已刷新', 'success');
|
||||
showNotification(_t('skills.refreshed'), 'success');
|
||||
}
|
||||
|
||||
// 清空skills统计数据
|
||||
async function clearSkillsStats() {
|
||||
if (!confirm('确定要清空所有Skills统计数据吗?此操作不可恢复。')) {
|
||||
if (!confirm(_t('skills.clearStatsConfirm'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -703,15 +720,15 @@ async function clearSkillsStats() {
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || '清空统计数据失败');
|
||||
throw new Error(error.error || _t('skills.clearStatsFailed'));
|
||||
}
|
||||
|
||||
showNotification('已清空所有Skills统计数据', 'success');
|
||||
showNotification(_t('skills.statsCleared'), 'success');
|
||||
// 重新加载统计数据
|
||||
await loadSkillsMonitor();
|
||||
} catch (error) {
|
||||
console.error('清空统计数据失败:', error);
|
||||
showNotification('清空统计数据失败: ' + error.message, 'error');
|
||||
showNotification(_t('skills.clearStatsFailed') + ': ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -722,3 +739,14 @@ function escapeHtml(text) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user