mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-13 07:36:35 +02:00
Add files via upload
This commit is contained in:
+97
-53
@@ -1,6 +1,8 @@
|
||||
// 角色管理相关功能
|
||||
let currentRole = localStorage.getItem('currentRole') || '';
|
||||
let roles = [];
|
||||
let rolesSearchKeyword = ''; // 角色搜索关键词
|
||||
let rolesSearchTimeout = null; // 搜索防抖定时器
|
||||
let allRoleTools = []; // 存储所有工具列表(用于角色工具选择)
|
||||
let roleToolsPagination = {
|
||||
page: 1,
|
||||
@@ -258,6 +260,7 @@ async function refreshRoles() {
|
||||
}
|
||||
// 始终更新侧边栏角色选择列表
|
||||
renderRoleSelectionSidebar();
|
||||
showNotification('已刷新', 'success');
|
||||
}
|
||||
|
||||
// 渲染角色列表
|
||||
@@ -265,30 +268,25 @@ function renderRolesList() {
|
||||
const rolesList = document.getElementById('roles-list');
|
||||
if (!rolesList) return;
|
||||
|
||||
if (roles.length === 0) {
|
||||
rolesList.innerHTML = '<div class="empty-state">暂无角色</div>';
|
||||
// 过滤角色(根据搜索关键词)
|
||||
let filteredRoles = roles;
|
||||
if (rolesSearchKeyword) {
|
||||
const keyword = rolesSearchKeyword.toLowerCase();
|
||||
filteredRoles = roles.filter(role =>
|
||||
role.name.toLowerCase().includes(keyword) ||
|
||||
(role.description && role.description.toLowerCase().includes(keyword))
|
||||
);
|
||||
}
|
||||
|
||||
if (filteredRoles.length === 0) {
|
||||
rolesList.innerHTML = '<div class="empty-state">' +
|
||||
(rolesSearchKeyword ? '没有找到匹配的角色' : '暂无角色') +
|
||||
'</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新统计
|
||||
const stats = document.getElementById('roles-stats');
|
||||
if (stats) {
|
||||
const totalRoles = roles.length;
|
||||
const enabledRoles = roles.filter(r => r.enabled !== false).length;
|
||||
stats.innerHTML = `
|
||||
<div class="role-stat-item">
|
||||
<span class="role-stat-label">总角色数</span>
|
||||
<span class="role-stat-value">${totalRoles}</span>
|
||||
</div>
|
||||
<div class="role-stat-item">
|
||||
<span class="role-stat-label">已启用</span>
|
||||
<span class="role-stat-value">${enabledRoles}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 对角色进行排序:默认角色第一个,其他按名称排序
|
||||
const sortedRoles = sortRoles(roles);
|
||||
const sortedRoles = sortRoles(filteredRoles);
|
||||
|
||||
rolesList.innerHTML = sortedRoles.map(role => {
|
||||
// 获取角色图标,如果是Unicode转义格式则转换为emoji
|
||||
@@ -307,47 +305,93 @@ function renderRolesList() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取工具列表显示
|
||||
let toolsDisplay = '';
|
||||
let toolsCount = 0;
|
||||
if (role.name === '默认') {
|
||||
toolsDisplay = '使用所有工具';
|
||||
} else if (role.tools && role.tools.length > 0) {
|
||||
toolsCount = role.tools.length;
|
||||
// 显示前5个工具名称
|
||||
const toolNames = role.tools.slice(0, 5).map(tool => {
|
||||
// 如果是外部工具,格式为 external_mcp::tool_name,只显示工具名
|
||||
const toolName = tool.includes('::') ? tool.split('::')[1] : tool;
|
||||
return escapeHtml(toolName);
|
||||
});
|
||||
if (toolsCount <= 5) {
|
||||
toolsDisplay = toolNames.join(', ');
|
||||
} else {
|
||||
toolsDisplay = toolNames.join(', ') + ` 等 ${toolsCount} 个`;
|
||||
}
|
||||
} else if (role.mcps && role.mcps.length > 0) {
|
||||
toolsCount = role.mcps.length;
|
||||
toolsDisplay = `等 ${toolsCount} 个`;
|
||||
} else {
|
||||
toolsDisplay = '使用所有工具';
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="role-item">
|
||||
<div class="role-item-content">
|
||||
<div class="role-item-header">
|
||||
<div class="role-item-name">
|
||||
<span class="role-item-icon" style="margin-right: 8px;">${roleIcon}</span>
|
||||
${escapeHtml(role.name)}
|
||||
</div>
|
||||
<div class="role-item-badge ${role.enabled !== false ? 'enabled' : 'disabled'}">
|
||||
${role.enabled !== false ? '已启用' : '已禁用'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="role-item-description">${escapeHtml(role.description || '无描述')}</div>
|
||||
<div class="role-item-details">
|
||||
<div class="role-item-detail">
|
||||
<span class="role-item-detail-label">用户提示词:</span>
|
||||
<span class="role-item-detail-value">${role.user_prompt ? (role.user_prompt.length > 100 ? escapeHtml(role.user_prompt.substring(0, 100)) + '...' : escapeHtml(role.user_prompt)) : '无'}</span>
|
||||
</div>
|
||||
<div class="role-item-detail">
|
||||
<span class="role-item-detail-label">关联的工具:</span>
|
||||
<span class="role-item-detail-value">${
|
||||
role.name === '默认'
|
||||
? '使用所有工具'
|
||||
: (role.tools && role.tools.length > 0
|
||||
? `${role.tools.length} 个工具`
|
||||
: (role.mcps && role.mcps.length > 0
|
||||
? `${role.mcps.length} 个工具(兼容旧版)`
|
||||
: '使用所有工具'))
|
||||
}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="role-card">
|
||||
<div class="role-card-header">
|
||||
<h3 class="role-card-title">
|
||||
<span class="role-card-icon">${roleIcon}</span>
|
||||
${escapeHtml(role.name)}
|
||||
</h3>
|
||||
<span class="role-card-badge ${role.enabled !== false ? 'enabled' : 'disabled'}">
|
||||
${role.enabled !== false ? '已启用' : '已禁用'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="role-item-actions">
|
||||
<button class="btn-secondary" onclick="editRole('${escapeHtml(role.name)}')">编辑</button>
|
||||
${role.name !== '默认' ? `<button class="btn-secondary btn-danger" onclick="deleteRole('${escapeHtml(role.name)}')">删除</button>` : ''}
|
||||
<div class="role-card-description">${escapeHtml(role.description || '无描述')}</div>
|
||||
<div class="role-card-tools">
|
||||
<span class="role-card-tools-label">工具:</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>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 处理角色搜索输入
|
||||
function handleRolesSearchInput() {
|
||||
clearTimeout(rolesSearchTimeout);
|
||||
rolesSearchTimeout = setTimeout(() => {
|
||||
searchRoles();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// 搜索角色
|
||||
function searchRoles() {
|
||||
const searchInput = document.getElementById('roles-search');
|
||||
if (!searchInput) return;
|
||||
|
||||
rolesSearchKeyword = searchInput.value.trim();
|
||||
const clearBtn = document.getElementById('roles-search-clear');
|
||||
if (clearBtn) {
|
||||
clearBtn.style.display = rolesSearchKeyword ? 'block' : 'none';
|
||||
}
|
||||
|
||||
renderRolesList();
|
||||
}
|
||||
|
||||
// 清除角色搜索
|
||||
function clearRolesSearch() {
|
||||
const searchInput = document.getElementById('roles-search');
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
}
|
||||
rolesSearchKeyword = '';
|
||||
const clearBtn = document.getElementById('roles-search-clear');
|
||||
if (clearBtn) {
|
||||
clearBtn.style.display = 'none';
|
||||
}
|
||||
renderRolesList();
|
||||
}
|
||||
|
||||
// 生成工具唯一标识符(与settings.js中的getToolKey保持一致)
|
||||
function getToolKey(tool) {
|
||||
// 如果是外部工具,使用 external_mcp::tool.name 作为唯一标识符
|
||||
|
||||
@@ -280,6 +280,15 @@ function initPage(pageId) {
|
||||
break;
|
||||
case 'roles-management':
|
||||
// 初始化角色管理页面
|
||||
// 重置搜索UI(变量会在下次搜索时自动更新)
|
||||
const rolesSearchInput = document.getElementById('roles-search');
|
||||
if (rolesSearchInput) {
|
||||
rolesSearchInput.value = '';
|
||||
}
|
||||
const rolesSearchClear = document.getElementById('roles-search-clear');
|
||||
if (rolesSearchClear) {
|
||||
rolesSearchClear.style.display = 'none';
|
||||
}
|
||||
if (typeof loadRoles === 'function') {
|
||||
loadRoles().then(() => {
|
||||
if (typeof renderRolesList === 'function') {
|
||||
@@ -296,6 +305,15 @@ function initPage(pageId) {
|
||||
break;
|
||||
case 'skills-management':
|
||||
// 初始化Skills管理页面
|
||||
// 重置搜索UI(变量会在下次搜索时自动更新)
|
||||
const skillsSearchInput = document.getElementById('skills-search');
|
||||
if (skillsSearchInput) {
|
||||
skillsSearchInput.value = '';
|
||||
}
|
||||
const skillsSearchClear = document.getElementById('skills-search-clear');
|
||||
if (skillsSearchClear) {
|
||||
skillsSearchClear.style.display = 'none';
|
||||
}
|
||||
if (typeof initSkillsPagination === 'function') {
|
||||
initSkillsPagination();
|
||||
}
|
||||
|
||||
+50
-39
@@ -24,7 +24,7 @@ function getSkillsPageSize() {
|
||||
const saved = localStorage.getItem('skillsPageSize');
|
||||
if (saved) {
|
||||
const size = parseInt(saved);
|
||||
if ([10, 20, 50, 100].includes(size)) {
|
||||
if ([20, 50, 100].includes(size)) {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,7 @@ function renderSkillsList() {
|
||||
|
||||
if (filteredSkills.length === 0) {
|
||||
skillsListEl.innerHTML = '<div class="empty-state">' +
|
||||
(skillsSearchKeyword ? '没有找到匹配的skills' : '暂无skills,点击"添加Skill"创建第一个skill') +
|
||||
(skillsSearchKeyword ? '没有找到匹配的skills' : '暂无skills,点击"创建Skill"创建第一个skill') +
|
||||
'</div>';
|
||||
// 搜索时隐藏分页
|
||||
const paginationContainer = document.getElementById('skills-pagination');
|
||||
@@ -105,47 +105,31 @@ function renderSkillsList() {
|
||||
}
|
||||
|
||||
skillsListEl.innerHTML = filteredSkills.map(skill => {
|
||||
const fileSize = skill.file_size || 0;
|
||||
const fileSizeStr = fileSize < 1024 ? fileSize + ' B' :
|
||||
fileSize < 1024 * 1024 ? (fileSize / 1024).toFixed(2) + ' KB' :
|
||||
(fileSize / (1024 * 1024)).toFixed(2) + ' MB';
|
||||
|
||||
return `
|
||||
<div class="skill-item">
|
||||
<div class="skill-item-header">
|
||||
<div class="skill-item-info">
|
||||
<h3 class="skill-item-name">${escapeHtml(skill.name || '')}</h3>
|
||||
${skill.description ? `<p class="skill-item-desc">${escapeHtml(skill.description)}</p>` : ''}
|
||||
</div>
|
||||
<div class="skill-item-actions">
|
||||
<button class="btn-icon" onclick="viewSkill('${escapeHtml(skill.name)}')" title="查看">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-icon" onclick="editSkill('${escapeHtml(skill.name)}')" title="编辑">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-icon btn-danger" onclick="deleteSkill('${escapeHtml(skill.name)}')" title="删除">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="skill-card">
|
||||
<div class="skill-card-header">
|
||||
<h3 class="skill-card-title">${escapeHtml(skill.name || '')}</h3>
|
||||
</div>
|
||||
<div class="skill-item-meta">
|
||||
<span class="skill-meta-item">路径: ${escapeHtml(skill.path || '')}</span>
|
||||
<span class="skill-meta-item">大小: ${fileSizeStr}</span>
|
||||
${skill.mod_time ? `<span class="skill-meta-item">修改时间: ${escapeHtml(skill.mod_time)}</span>` : ''}
|
||||
<div class="skill-card-description">${escapeHtml(skill.description || '无描述')}</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>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// 确保列表容器可以滚动,分页栏可见
|
||||
// 使用 setTimeout 确保 DOM 更新完成后再检查
|
||||
setTimeout(() => {
|
||||
const paginationContainer = document.getElementById('skills-pagination');
|
||||
if (paginationContainer && !skillsSearchKeyword) {
|
||||
// 确保分页栏可见
|
||||
paginationContainer.style.display = 'block';
|
||||
paginationContainer.style.visibility = 'visible';
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// 渲染分页组件(参考MCP管理页面样式)
|
||||
@@ -177,7 +161,6 @@ function renderSkillsPagination() {
|
||||
<label class="pagination-page-size">
|
||||
每页显示
|
||||
<select id="skills-page-size-pagination" onchange="changeSkillsPageSize()">
|
||||
<option value="10" ${pageSize === 10 ? 'selected' : ''}>10</option>
|
||||
<option value="20" ${pageSize === 20 ? 'selected' : ''}>20</option>
|
||||
<option value="50" ${pageSize === 50 ? 'selected' : ''}>50</option>
|
||||
<option value="100" ${pageSize === 100 ? 'selected' : ''}>100</option>
|
||||
@@ -205,6 +188,11 @@ function renderSkillsPagination() {
|
||||
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; // 内容总高度
|
||||
@@ -213,7 +201,7 @@ function renderSkillsPagination() {
|
||||
|
||||
// 如果列表有垂直滚动条,分页组件应该与列表内容区域对齐(clientWidth)
|
||||
// 如果没有滚动条,使用100%宽度
|
||||
if (hasScrollbar) {
|
||||
if (hasScrollbar && listClientWidth > 0) {
|
||||
// 分页组件应该与列表内容区域对齐,不包括滚动条
|
||||
paginationContainer.style.width = `${listClientWidth}px`;
|
||||
} else {
|
||||
@@ -235,6 +223,10 @@ function renderSkillsPagination() {
|
||||
if (skillsList) {
|
||||
resizeObserver.observe(skillsList);
|
||||
}
|
||||
|
||||
// 确保分页容器始终可见(防止被隐藏)
|
||||
paginationContainer.style.display = 'block';
|
||||
paginationContainer.style.visibility = 'visible';
|
||||
}
|
||||
|
||||
// 改变每页显示数量
|
||||
@@ -288,6 +280,10 @@ async function searchSkills() {
|
||||
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(加载所有匹配结果,不分页)
|
||||
@@ -317,6 +313,21 @@ async function searchSkills() {
|
||||
}
|
||||
}
|
||||
|
||||
// 清除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);
|
||||
|
||||
Reference in New Issue
Block a user