From b524ce68eab63852aa2ec3ce5d3134ea4e4032f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Fri, 9 Jan 2026 21:16:38 +0800 Subject: [PATCH] Add files via upload --- web/static/js/chat.js | 20 ++++++- web/static/js/settings.js | 114 +++++++++++++++++++++++++------------- 2 files changed, 93 insertions(+), 41 deletions(-) diff --git a/web/static/js/chat.js b/web/static/js/chat.js index 8c8d193c..41761178 100644 --- a/web/static/js/chat.js +++ b/web/static/js/chat.js @@ -254,6 +254,16 @@ function ensureMentionToolsLoaded() { return mentionToolsLoadingPromise; } +// 生成工具的唯一标识符,用于区分同名但来源不同的工具 +function getToolKeyForMention(tool) { + // 如果是外部工具,使用 external_mcp::tool.name 作为唯一标识 + // 如果是内部工具,使用 tool.name 作为标识 + if (tool.is_external && tool.external_mcp) { + return `${tool.external_mcp}::${tool.name}`; + } + return tool.name; +} + async function fetchMentionTools() { const pageSize = 100; let page = 1; @@ -287,16 +297,22 @@ async function fetchMentionTools() { const result = await response.json(); const tools = Array.isArray(result.tools) ? result.tools : []; tools.forEach(tool => { - if (!tool || !tool.name || seen.has(tool.name)) { + if (!tool || !tool.name) { return; } - seen.add(tool.name); + // 使用唯一标识符来去重,而不是只使用工具名称 + const toolKey = getToolKeyForMention(tool); + if (seen.has(toolKey)) { + return; + } + seen.add(toolKey); collected.push({ name: tool.name, description: tool.description || '', enabled: tool.enabled !== false, isExternal: !!tool.is_external, externalMcp: tool.external_mcp || '', + toolKey: toolKey, // 保存唯一标识符 }); }); totalPages = result.total_pages || 1; diff --git a/web/static/js/settings.js b/web/static/js/settings.js index 74237f9a..27aa5b7d 100644 --- a/web/static/js/settings.js +++ b/web/static/js/settings.js @@ -2,8 +2,18 @@ let currentConfig = null; let allTools = []; // 全局工具状态映射,用于保存用户在所有页面的修改 -// key: tool.name, value: { enabled: boolean, is_external: boolean, external_mcp: string } +// key: 唯一工具标识符(toolKey),value: { enabled: boolean, is_external: boolean, external_mcp: string } let toolStateMap = new Map(); + +// 生成工具的唯一标识符,用于区分同名但来源不同的工具 +function getToolKey(tool) { + // 如果是外部工具,使用 external_mcp::tool.name 作为唯一标识 + // 如果是内部工具,使用 tool.name 作为标识 + if (tool.is_external && tool.external_mcp) { + return `${tool.external_mcp}::${tool.name}`; + } + return tool.name; +} // 从localStorage读取每页显示数量,默认为20 const getToolsPageSize = () => { const saved = localStorage.getItem('toolsPageSize'); @@ -199,11 +209,13 @@ async function loadToolsList(page = 1, searchKeyword = '') { // 初始化工具状态映射(如果工具不在映射中,使用服务器返回的状态) allTools.forEach(tool => { - if (!toolStateMap.has(tool.name)) { - toolStateMap.set(tool.name, { + const toolKey = getToolKey(tool); + if (!toolStateMap.has(toolKey)) { + toolStateMap.set(toolKey, { enabled: tool.enabled, is_external: tool.is_external || false, - external_mcp: tool.external_mcp || '' + external_mcp: tool.external_mcp || '', + name: tool.name // 保存原始工具名称 }); } }); @@ -223,14 +235,16 @@ async function loadToolsList(page = 1, searchKeyword = '') { function saveCurrentPageToolStates() { document.querySelectorAll('#tools-list .tool-item').forEach(item => { const checkbox = item.querySelector('input[type="checkbox"]'); + const toolKey = item.dataset.toolKey; // 使用唯一标识符 const toolName = item.dataset.toolName; const isExternal = item.dataset.isExternal === 'true'; const externalMcp = item.dataset.externalMcp || ''; - if (toolName && checkbox) { - toolStateMap.set(toolName, { + if (toolKey && checkbox) { + toolStateMap.set(toolKey, { enabled: checkbox.checked, is_external: isExternal, - external_mcp: externalMcp + external_mcp: externalMcp, + name: toolName // 保存原始工具名称 }); } }); @@ -283,14 +297,16 @@ function renderToolsList() { } allTools.forEach(tool => { + const toolKey = getToolKey(tool); // 生成唯一标识符 const toolItem = document.createElement('div'); toolItem.className = 'tool-item'; + toolItem.dataset.toolKey = toolKey; // 保存唯一标识符 toolItem.dataset.toolName = tool.name; // 保存原始工具名称 toolItem.dataset.isExternal = tool.is_external ? 'true' : 'false'; toolItem.dataset.externalMcp = tool.external_mcp || ''; // 从全局状态映射获取工具状态,如果不存在则使用服务器返回的状态 - const toolState = toolStateMap.get(tool.name) || { + const toolState = toolStateMap.get(toolKey) || { enabled: tool.enabled, is_external: tool.is_external || false, external_mcp: tool.external_mcp || '' @@ -298,15 +314,18 @@ function renderToolsList() { // 外部工具标签,显示来源信息 let externalBadge = ''; - if (toolState.is_external) { - const externalMcpName = toolState.external_mcp || ''; + if (toolState.is_external || tool.is_external) { + const externalMcpName = toolState.external_mcp || tool.external_mcp || ''; const badgeText = externalMcpName ? `外部 (${escapeHtml(externalMcpName)})` : '外部'; const badgeTitle = externalMcpName ? `外部MCP工具 - 来源:${escapeHtml(externalMcpName)}` : '外部MCP工具'; externalBadge = `${badgeText}`; } + // 生成唯一的checkbox id,使用工具唯一标识符 + const checkboxId = `tool-${escapeHtml(toolKey).replace(/::/g, '--')}`; + toolItem.innerHTML = ` - +
${escapeHtml(tool.name)} @@ -376,16 +395,18 @@ function renderToolsPagination() { } // 处理工具checkbox状态变化 -function handleToolCheckboxChange(toolName, enabled) { +function handleToolCheckboxChange(toolKey, enabled) { // 更新全局状态映射 - const toolItem = document.querySelector(`.tool-item[data-tool-name="${toolName}"]`); + const toolItem = document.querySelector(`.tool-item[data-tool-key="${toolKey}"]`); if (toolItem) { + const toolName = toolItem.dataset.toolName; const isExternal = toolItem.dataset.isExternal === 'true'; const externalMcp = toolItem.dataset.externalMcp || ''; - toolStateMap.set(toolName, { + toolStateMap.set(toolKey, { enabled: enabled, is_external: isExternal, - external_mcp: externalMcp + external_mcp: externalMcp, + name: toolName // 保存原始工具名称 }); } updateToolsStats(); @@ -398,14 +419,16 @@ function selectAllTools() { // 更新全局状态映射 const toolItem = checkbox.closest('.tool-item'); if (toolItem) { + const toolKey = toolItem.dataset.toolKey; const toolName = toolItem.dataset.toolName; const isExternal = toolItem.dataset.isExternal === 'true'; const externalMcp = toolItem.dataset.externalMcp || ''; - if (toolName) { - toolStateMap.set(toolName, { + if (toolKey) { + toolStateMap.set(toolKey, { enabled: true, is_external: isExternal, - external_mcp: externalMcp + external_mcp: externalMcp, + name: toolName // 保存原始工具名称 }); } } @@ -420,14 +443,16 @@ function deselectAllTools() { // 更新全局状态映射 const toolItem = checkbox.closest('.tool-item'); if (toolItem) { + const toolKey = toolItem.dataset.toolKey; const toolName = toolItem.dataset.toolName; const isExternal = toolItem.dataset.isExternal === 'true'; const externalMcp = toolItem.dataset.externalMcp || ''; - if (toolName) { - toolStateMap.set(toolName, { + if (toolKey) { + toolStateMap.set(toolKey, { enabled: false, is_external: isExternal, - external_mcp: externalMcp + external_mcp: externalMcp, + name: toolName // 保存原始工具名称 }); } } @@ -484,11 +509,13 @@ async function updateToolsStats() { totalTools = allTools.length; totalEnabled = allTools.filter(tool => { // 优先使用全局状态映射,否则使用checkbox状态,最后使用服务器返回的状态 - const savedState = toolStateMap.get(tool.name); + const toolKey = getToolKey(tool); + const savedState = toolStateMap.get(toolKey); if (savedState !== undefined) { return savedState.enabled; } - const checkbox = document.getElementById(`tool-${tool.name}`); + const checkboxId = `tool-${toolKey.replace(/::/g, '--')}`; + const checkbox = document.getElementById(checkboxId); return checkbox ? checkbox.checked : tool.enabled; }).length; } else { @@ -498,16 +525,18 @@ async function updateToolsStats() { // 从当前页的checkbox获取状态(如果全局映射中没有) allTools.forEach(tool => { - const savedState = toolStateMap.get(tool.name); + const toolKey = getToolKey(tool); + const savedState = toolStateMap.get(toolKey); if (savedState !== undefined) { - localStateMap.set(tool.name, savedState.enabled); + localStateMap.set(toolKey, savedState.enabled); } else { - const checkbox = document.getElementById(`tool-${tool.name}`); + const checkboxId = `tool-${toolKey.replace(/::/g, '--')}`; + const checkbox = document.getElementById(checkboxId); if (checkbox) { - localStateMap.set(tool.name, checkbox.checked); + localStateMap.set(toolKey, checkbox.checked); } else { // 如果checkbox不存在(不在当前页),使用工具原始状态 - localStateMap.set(tool.name, tool.enabled); + localStateMap.set(toolKey, tool.enabled); } } }); @@ -527,9 +556,10 @@ async function updateToolsStats() { const pageResult = await pageResponse.json(); pageResult.tools.forEach(tool => { // 优先使用全局状态映射,否则使用服务器返回的状态 - if (!localStateMap.has(tool.name)) { - const savedState = toolStateMap.get(tool.name); - localStateMap.set(tool.name, savedState ? savedState.enabled : tool.enabled); + const toolKey = getToolKey(tool); + if (!localStateMap.has(toolKey)) { + const savedState = toolStateMap.get(toolKey); + localStateMap.set(toolKey, savedState ? savedState.enabled : tool.enabled); } }); @@ -665,8 +695,9 @@ async function applySettings() { // 将工具添加到映射中 // 优先使用全局状态映射中的状态(用户修改过的),否则使用服务器返回的状态 pageResult.tools.forEach(tool => { - const savedState = toolStateMap.get(tool.name); - allToolsMap.set(tool.name, { + const toolKey = getToolKey(tool); + const savedState = toolStateMap.get(toolKey); + allToolsMap.set(toolKey, { name: tool.name, enabled: savedState ? savedState.enabled : tool.enabled, is_external: savedState ? savedState.is_external : (tool.is_external || false), @@ -683,7 +714,7 @@ async function applySettings() { } // 将所有工具添加到配置中 - allToolsMap.forEach(tool => { + allToolsMap.forEach((tool, toolKey) => { config.tools.push({ name: tool.name, enabled: tool.enabled, @@ -694,7 +725,9 @@ async function applySettings() { } catch (error) { console.warn('获取所有工具列表失败,仅使用全局状态映射', error); // 如果获取失败,使用全局状态映射 - toolStateMap.forEach((toolData, toolName) => { + toolStateMap.forEach((toolData, toolKey) => { + // toolData.name 保存了原始工具名称 + const toolName = toolData.name || toolKey.split('::').pop(); config.tools.push({ name: toolName, enabled: toolData.enabled, @@ -777,8 +810,9 @@ async function saveToolsConfig() { // 将工具添加到映射中 pageResult.tools.forEach(tool => { - const savedState = toolStateMap.get(tool.name); - allToolsMap.set(tool.name, { + const toolKey = getToolKey(tool); + const savedState = toolStateMap.get(toolKey); + allToolsMap.set(toolKey, { name: tool.name, enabled: savedState ? savedState.enabled : tool.enabled, is_external: savedState ? savedState.is_external : (tool.is_external || false), @@ -795,7 +829,7 @@ async function saveToolsConfig() { } // 将所有工具添加到配置中 - allToolsMap.forEach(tool => { + allToolsMap.forEach((tool, toolKey) => { config.tools.push({ name: tool.name, enabled: tool.enabled, @@ -806,7 +840,9 @@ async function saveToolsConfig() { } catch (error) { console.warn('获取所有工具列表失败,仅使用全局状态映射', error); // 如果获取失败,使用全局状态映射 - toolStateMap.forEach((toolData, toolName) => { + toolStateMap.forEach((toolData, toolKey) => { + // toolData.name 保存了原始工具名称 + const toolName = toolData.name || toolKey.split('::').pop(); config.tools.push({ name: toolName, enabled: toolData.enabled,