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:
+990
-15
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 内置工具名称常量
|
||||
* 所有前端代码中使用内置工具名称的地方都应该使用这些常量,而不是硬编码字符串
|
||||
*
|
||||
* 注意:这些常量必须与后端的 internal/mcp/builtin/constants.go 中的常量保持一致
|
||||
*/
|
||||
|
||||
// 内置工具名称常量
|
||||
const BuiltinTools = {
|
||||
// 漏洞管理工具
|
||||
RECORD_VULNERABILITY: 'record_vulnerability',
|
||||
|
||||
// 知识库工具
|
||||
LIST_KNOWLEDGE_RISK_TYPES: 'list_knowledge_risk_types',
|
||||
SEARCH_KNOWLEDGE_BASE: 'search_knowledge_base'
|
||||
};
|
||||
|
||||
// 检查是否是内置工具
|
||||
function isBuiltinTool(toolName) {
|
||||
return Object.values(BuiltinTools).includes(toolName);
|
||||
}
|
||||
|
||||
// 获取所有内置工具名称列表
|
||||
function getAllBuiltinTools() {
|
||||
return Object.values(BuiltinTools);
|
||||
}
|
||||
|
||||
+54
-9
@@ -145,6 +145,9 @@ async function sendMessage() {
|
||||
let mcpExecutionIds = [];
|
||||
|
||||
try {
|
||||
// 获取当前选中的角色(从 roles.js 的函数获取)
|
||||
const roleName = typeof getCurrentRole === 'function' ? getCurrentRole() : '';
|
||||
|
||||
const response = await apiFetch('/api/agent-loop/stream', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -152,7 +155,8 @@ async function sendMessage() {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
message: message,
|
||||
conversationId: currentConversationId
|
||||
conversationId: currentConversationId,
|
||||
role: roleName || undefined
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -252,6 +256,13 @@ if (typeof window !== 'undefined') {
|
||||
}
|
||||
|
||||
function ensureMentionToolsLoaded() {
|
||||
// 检查角色是否改变,如果改变则强制重新加载
|
||||
if (typeof window !== 'undefined' && window._mentionToolsRoleChanged) {
|
||||
mentionToolsLoaded = false;
|
||||
mentionTools = [];
|
||||
delete window._mentionToolsRoleChanged;
|
||||
}
|
||||
|
||||
if (mentionToolsLoaded) {
|
||||
return Promise.resolve(mentionTools);
|
||||
}
|
||||
@@ -282,6 +293,9 @@ async function fetchMentionTools() {
|
||||
const collected = [];
|
||||
|
||||
try {
|
||||
// 获取当前选中的角色(从 roles.js 的函数获取)
|
||||
const roleName = typeof getCurrentRole === 'function' ? getCurrentRole() : '';
|
||||
|
||||
// 同时获取外部MCP列表
|
||||
try {
|
||||
const mcpResponse = await apiFetch('/api/external-mcp');
|
||||
@@ -300,7 +314,13 @@ async function fetchMentionTools() {
|
||||
}
|
||||
|
||||
while (page <= totalPages && page <= 20) {
|
||||
const response = await apiFetch(`/api/config/tools?page=${page}&page_size=${pageSize}`);
|
||||
// 构建API URL,如果指定了角色,添加role查询参数
|
||||
let url = `/api/config/tools?page=${page}&page_size=${pageSize}`;
|
||||
if (roleName && roleName !== '默认') {
|
||||
url += `&role=${encodeURIComponent(roleName)}`;
|
||||
}
|
||||
|
||||
const response = await apiFetch(url);
|
||||
if (!response.ok) {
|
||||
break;
|
||||
}
|
||||
@@ -316,10 +336,20 @@ async function fetchMentionTools() {
|
||||
return;
|
||||
}
|
||||
seen.add(toolKey);
|
||||
|
||||
// 确定工具在当前角色中的启用状态
|
||||
// 如果有 role_enabled 字段,使用它(表示指定了角色)
|
||||
// 否则使用 enabled 字段(表示未指定角色或使用所有工具)
|
||||
let roleEnabled = tool.enabled !== false;
|
||||
if (tool.role_enabled !== undefined && tool.role_enabled !== null) {
|
||||
roleEnabled = tool.role_enabled;
|
||||
}
|
||||
|
||||
collected.push({
|
||||
name: tool.name,
|
||||
description: tool.description || '',
|
||||
enabled: tool.enabled !== false,
|
||||
enabled: tool.enabled !== false, // 工具本身的启用状态
|
||||
roleEnabled: roleEnabled, // 在当前角色中的启用状态
|
||||
isExternal: !!tool.is_external,
|
||||
externalMcp: tool.external_mcp || '',
|
||||
toolKey: toolKey, // 保存唯一标识符
|
||||
@@ -488,6 +518,15 @@ function updateMentionCandidates() {
|
||||
}
|
||||
|
||||
filtered = filtered.slice().sort((a, b) => {
|
||||
// 如果指定了角色,优先显示在当前角色中启用的工具
|
||||
if (a.roleEnabled !== undefined || b.roleEnabled !== undefined) {
|
||||
const aRoleEnabled = a.roleEnabled !== undefined ? a.roleEnabled : a.enabled;
|
||||
const bRoleEnabled = b.roleEnabled !== undefined ? b.roleEnabled : b.enabled;
|
||||
if (aRoleEnabled !== bRoleEnabled) {
|
||||
return aRoleEnabled ? -1 : 1; // 启用的工具排在前面
|
||||
}
|
||||
}
|
||||
|
||||
if (normalizedQuery) {
|
||||
// 精确匹配MCP名称的工具优先显示
|
||||
const aMcpExact = a.externalMcp && a.externalMcp.toLowerCase() === normalizedQuery;
|
||||
@@ -502,8 +541,11 @@ function updateMentionCandidates() {
|
||||
return aStarts ? -1 : 1;
|
||||
}
|
||||
}
|
||||
if (a.enabled !== b.enabled) {
|
||||
return a.enabled ? -1 : 1;
|
||||
// 如果指定了角色,使用 roleEnabled;否则使用 enabled
|
||||
const aEnabled = a.roleEnabled !== undefined ? a.roleEnabled : a.enabled;
|
||||
const bEnabled = b.roleEnabled !== undefined ? b.roleEnabled : b.enabled;
|
||||
if (aEnabled !== bEnabled) {
|
||||
return aEnabled ? -1 : 1;
|
||||
}
|
||||
return a.name.localeCompare(b.name, 'zh-CN');
|
||||
});
|
||||
@@ -545,13 +587,16 @@ function renderMentionSuggestions({ showLoading = false } = {}) {
|
||||
|
||||
const itemsHtml = mentionFilteredTools.map((tool, index) => {
|
||||
const activeClass = index === mentionState.selectedIndex ? 'active' : '';
|
||||
const disabledClass = tool.enabled ? '' : 'disabled';
|
||||
// 如果工具有 roleEnabled 字段(指定了角色),使用它;否则使用 enabled
|
||||
const toolEnabled = tool.roleEnabled !== undefined ? tool.roleEnabled : tool.enabled;
|
||||
const disabledClass = toolEnabled ? '' : 'disabled';
|
||||
const badge = tool.isExternal ? '<span class="mention-item-badge">外部</span>' : '<span class="mention-item-badge internal">内置</span>';
|
||||
const nameHtml = escapeHtml(tool.name);
|
||||
const description = tool.description && tool.description.length > 0 ? escapeHtml(tool.description) : '暂无描述';
|
||||
const descHtml = `<div class="mention-item-desc">${description}</div>`;
|
||||
const statusLabel = tool.enabled ? '可用' : '已禁用';
|
||||
const statusClass = tool.enabled ? 'enabled' : 'disabled';
|
||||
// 根据工具在当前角色中的启用状态显示状态标签
|
||||
const statusLabel = toolEnabled ? '可用' : (tool.roleEnabled !== undefined ? '已禁用(当前角色)' : '已禁用');
|
||||
const statusClass = toolEnabled ? 'enabled' : 'disabled';
|
||||
const originLabel = tool.isExternal
|
||||
? (tool.externalMcp ? `来源:${escapeHtml(tool.externalMcp)}` : '来源:外部MCP')
|
||||
: '来源:内置工具';
|
||||
@@ -1109,7 +1154,7 @@ function renderProcessDetails(messageId, processDetails) {
|
||||
itemTitle = `${statusIcon} 工具 ${escapeHtml(toolName)} 执行${success ? '完成' : '失败'}`;
|
||||
|
||||
// 如果是知识检索工具,添加特殊标记
|
||||
if (toolName === 'search_knowledge_base' && success) {
|
||||
if (toolName === BuiltinTools.SEARCH_KNOWLEDGE_BASE && success) {
|
||||
itemTitle = `📚 ${itemTitle} - 知识检索`;
|
||||
}
|
||||
} else if (eventType === 'knowledge_retrieval') {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+24
-1
@@ -8,7 +8,7 @@ function initRouter() {
|
||||
if (hash) {
|
||||
const hashParts = hash.split('?');
|
||||
const pageId = hashParts[0];
|
||||
if (pageId && ['chat', 'vulnerabilities', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'settings', 'tasks'].includes(pageId)) {
|
||||
if (pageId && ['chat', 'vulnerabilities', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'settings', 'tasks'].includes(pageId)) {
|
||||
switchPage(pageId);
|
||||
|
||||
// 如果是chat页面且带有conversation参数,加载对应对话
|
||||
@@ -94,6 +94,19 @@ function updateNavState(pageId) {
|
||||
knowledgeItem.classList.add('expanded');
|
||||
}
|
||||
|
||||
const submenuItem = document.querySelector(`.nav-submenu-item[data-page="${pageId}"]`);
|
||||
if (submenuItem) {
|
||||
submenuItem.classList.add('active');
|
||||
}
|
||||
} else if (pageId === 'roles-management') {
|
||||
// 角色子菜单项
|
||||
const rolesItem = document.querySelector('.nav-item[data-page="roles"]');
|
||||
if (rolesItem) {
|
||||
rolesItem.classList.add('active');
|
||||
// 展开角色子菜单
|
||||
rolesItem.classList.add('expanded');
|
||||
}
|
||||
|
||||
const submenuItem = document.querySelector(`.nav-submenu-item[data-page="${pageId}"]`);
|
||||
if (submenuItem) {
|
||||
submenuItem.classList.add('active');
|
||||
@@ -239,6 +252,16 @@ function initPage(pageId) {
|
||||
loadConfig(false);
|
||||
}
|
||||
break;
|
||||
case 'roles-management':
|
||||
// 初始化角色管理页面
|
||||
if (typeof loadRoles === 'function') {
|
||||
loadRoles().then(() => {
|
||||
if (typeof renderRolesList === 'function') {
|
||||
renderRolesList();
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 清理其他页面的定时器
|
||||
|
||||
+168
-6
@@ -136,6 +136,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item nav-item-has-submenu" data-page="roles">
|
||||
<div class="nav-item-content" data-title="角色" onclick="toggleSubmenu('roles')">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="12" cy="7" r="4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<span>角色</span>
|
||||
<svg class="submenu-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="nav-submenu">
|
||||
<div class="nav-submenu-item" data-page="roles-management" onclick="switchPage('roles-management')">
|
||||
<span>角色管理</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item" data-page="settings">
|
||||
<div class="nav-item-content" data-title="系统设置" onclick="switchPage('settings')">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -162,7 +179,7 @@
|
||||
</div>
|
||||
<div class="sidebar-content">
|
||||
<!-- 全局搜索 -->
|
||||
<div class="conversation-search-box" style="margin-bottom: 16px;">
|
||||
<div class="conversation-search-box" style="margin-bottom: 16px; margin-top: 16px;">
|
||||
<input type="text" id="conversation-search-input" placeholder="搜索历史记录..."
|
||||
oninput="handleConversationSearch(this.value)"
|
||||
onkeypress="if(event.key === 'Enter') handleConversationSearch(this.value)" />
|
||||
@@ -269,11 +286,37 @@
|
||||
<div id="active-tasks-bar" class="active-tasks-bar"></div>
|
||||
<div id="chat-messages" class="chat-messages"></div>
|
||||
<div class="chat-input-container">
|
||||
<div class="role-selector-wrapper">
|
||||
<button id="role-selector-btn" class="role-selector-btn" onclick="toggleRoleSelectionPanel()" title="选择角色">
|
||||
<span id="role-selector-icon" class="role-selector-icon">🔵</span>
|
||||
<span id="role-selector-text" class="role-selector-text">默认</span>
|
||||
<svg class="role-selector-arrow" width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- 角色选择下拉面板 -->
|
||||
<div id="role-selection-panel" class="role-selection-panel" style="display: none;">
|
||||
<div class="role-selection-panel-header">
|
||||
<h3 class="role-selection-panel-title">选择角色</h3>
|
||||
<button class="role-selection-panel-close" onclick="closeRoleSelectionPanel()" title="关闭">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="role-selection-list" class="role-selection-list-main"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-input-field">
|
||||
<textarea id="chat-input" placeholder="输入测试目标或命令... (输入 @ 选择工具 | Shift+Enter 换行,Enter 发送)" rows="1"></textarea>
|
||||
<div id="mention-suggestions" class="mention-suggestions" role="listbox" aria-label="工具提及候选"></div>
|
||||
</div>
|
||||
<button onclick="sendMessage()">发送</button>
|
||||
<button class="send-btn" onclick="sendMessage()">
|
||||
<span>发送</span>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 12h14M12 5l7 7-7 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -605,6 +648,34 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 角色管理页面 -->
|
||||
<div id="page-roles-management" class="page">
|
||||
<div class="page-header">
|
||||
<h2>角色管理</h2>
|
||||
<div class="page-header-actions">
|
||||
<button class="btn-secondary" onclick="refreshRoles()">刷新</button>
|
||||
<button class="btn-primary" onclick="showAddRoleModal()">添加角色</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-content">
|
||||
<div class="roles-controls">
|
||||
<div class="roles-stats-bar" id="roles-stats">
|
||||
<div class="role-stat-item">
|
||||
<span class="role-stat-label">总角色数</span>
|
||||
<span class="role-stat-value">-</span>
|
||||
</div>
|
||||
<div class="role-stat-item">
|
||||
<span class="role-stat-label">已启用</span>
|
||||
<span class="role-stat-value">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="roles-list" class="roles-list">
|
||||
<div class="loading-spinner">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统设置页面 -->
|
||||
<div id="page-settings" class="page">
|
||||
<div class="page-header">
|
||||
@@ -687,10 +758,6 @@
|
||||
<option value="openai">OpenAI</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="knowledge-embedding-model">模型名称</label>
|
||||
<input type="text" id="knowledge-embedding-model" placeholder="text-embedding-v4" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="knowledge-embedding-base-url">Base URL</label>
|
||||
<input type="text" id="knowledge-embedding-base-url" placeholder="留空则使用OpenAI配置的base_url" />
|
||||
@@ -701,6 +768,10 @@
|
||||
<input type="password" id="knowledge-embedding-api-key" placeholder="留空则使用OpenAI配置的api_key" />
|
||||
<small class="form-hint">留空则使用OpenAI配置的api_key</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="knowledge-embedding-model">模型名称</label>
|
||||
<input type="text" id="knowledge-embedding-model" placeholder="text-embedding-v4" />
|
||||
</div>
|
||||
|
||||
<div class="settings-subsection-header">
|
||||
<h5>检索配置</h5>
|
||||
@@ -1329,6 +1400,96 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 角色选择弹窗 -->
|
||||
<div id="role-select-modal" class="modal">
|
||||
<div class="modal-content role-select-modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>选择角色</h2>
|
||||
<span class="modal-close" onclick="closeRoleSelectModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body role-select-body">
|
||||
<div id="role-select-list" class="role-select-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 角色编辑模态框 -->
|
||||
<div id="role-modal" class="modal">
|
||||
<div class="modal-content" style="max-width: 900px;">
|
||||
<div class="modal-header">
|
||||
<h2 id="role-modal-title">添加角色</h2>
|
||||
<span class="modal-close" onclick="closeRoleModal()">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="role-name">角色名称 <span style="color: red;">*</span></label>
|
||||
<input type="text" id="role-name" placeholder="输入角色名称" required />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="role-description">角色描述</label>
|
||||
<input type="text" id="role-description" placeholder="输入角色描述" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="role-icon">角色图标</label>
|
||||
<input type="text" id="role-icon" placeholder="输入emoji图标,例如: 🏆" maxlength="10" />
|
||||
<small class="form-hint">输入一个emoji作为角色的图标,将显示在角色选择器中。</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="role-user-prompt">用户提示词</label>
|
||||
<textarea id="role-user-prompt" rows="10" placeholder="输入用户提示词,会在用户消息前追加此提示词..."></textarea>
|
||||
<small class="form-hint">此提示词会追加到用户消息前,用于指导AI的行为。注意:这不会修改系统提示词。</small>
|
||||
</div>
|
||||
<div class="form-group" id="role-tools-section">
|
||||
<label>关联的工具(可选)</label>
|
||||
<div id="role-tools-default-hint" class="role-tools-default-hint" style="display: none;">
|
||||
<div class="role-tools-default-info">
|
||||
<span class="role-tools-default-icon">ℹ️</span>
|
||||
<div class="role-tools-default-content">
|
||||
<div class="role-tools-default-title">默认角色使用所有工具</div>
|
||||
<div class="role-tools-default-desc">默认角色会自动使用MCP管理中启用的所有工具,无需单独配置。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="role-tools-controls">
|
||||
<div class="role-tools-actions">
|
||||
<button type="button" class="btn-secondary" onclick="selectAllRoleTools()">全选</button>
|
||||
<button type="button" class="btn-secondary" onclick="deselectAllRoleTools()">全不选</button>
|
||||
<div class="role-tools-search-box">
|
||||
<input type="text" id="role-tools-search" placeholder="搜索工具..."
|
||||
oninput="searchRoleTools(this.value)"
|
||||
onkeypress="if(event.key === 'Enter') searchRoleTools(this.value)" />
|
||||
<button class="role-tools-search-clear" id="role-tools-search-clear"
|
||||
onclick="clearRoleToolsSearch()" style="display: none;" title="清除搜索">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M15 9l-6 6M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="role-tools-stats" class="role-tools-stats"></div>
|
||||
</div>
|
||||
<div id="role-tools-list" class="role-tools-list">
|
||||
<div class="tools-loading">正在加载工具列表...</div>
|
||||
</div>
|
||||
<small class="form-hint">勾选要关联的工具,留空则使用MCP管理中的全部工具配置。</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="role-enabled" class="modern-checkbox" checked />
|
||||
<span class="checkbox-custom"></span>
|
||||
<span class="checkbox-text">启用此角色</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-secondary" onclick="closeRoleModal()">取消</button>
|
||||
<button class="btn-primary" onclick="saveRole()">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/builtin-tools.js"></script>
|
||||
<script src="/static/js/auth.js"></script>
|
||||
<script src="/static/js/router.js"></script>
|
||||
<script src="/static/js/monitor.js"></script>
|
||||
@@ -1337,6 +1498,7 @@
|
||||
<script src="/static/js/knowledge.js"></script>
|
||||
<script src="/static/js/vulnerability.js?v=4"></script>
|
||||
<script src="/static/js/tasks.js"></script>
|
||||
<script src="/static/js/roles.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user