feat: 工具列表独立为工具面板模态框
- Header 新增 🔧 工具按钮,点击打开工具面板
- 新增 tools-modal.ts 组件
- 工具面板以卡片网格展示全部 12 个工具
- 每个工具显示名称、描述、自动/需确认标签
- run_command 开关内嵌在工具卡片中
- 设置面板简化为「查看全部工具」按钮
- 设置面板保留 run_command 开关同步
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* ToolsModal - 工具面板模态框
|
||||
* 展示所有可用工具的详细信息
|
||||
*/
|
||||
|
||||
import { state, KEYS } from '../state/state.js';
|
||||
import { setToolEnabled } from '../services/tool-registry.js';
|
||||
import { showToast } from './toast.js';
|
||||
import { logInfo } from '../services/log-service.js';
|
||||
import type { ChatDB } from '../types.js';
|
||||
|
||||
let modalEl: HTMLElement;
|
||||
|
||||
export function initToolsModal(): void {
|
||||
modalEl = document.querySelector('#toolsModal')!;
|
||||
|
||||
// Header 按钮
|
||||
document.querySelector('#btnTools')!.addEventListener('click', openToolsModal);
|
||||
document.querySelector('#btnCloseTools')!.addEventListener('click', closeToolsModal);
|
||||
modalEl.addEventListener('click', (e) => {
|
||||
if (e.target === modalEl) closeToolsModal();
|
||||
});
|
||||
|
||||
// 设置中"查看全部工具"按钮
|
||||
document.querySelector('#btnOpenToolsFromSettings')?.addEventListener('click', () => {
|
||||
(document.querySelector('#settingsModal') as HTMLElement).style.display = 'none';
|
||||
openToolsModal();
|
||||
});
|
||||
|
||||
// run_command 开关(模态框中)
|
||||
const toggle2 = document.querySelector('#toggleRunCommand2') as HTMLInputElement;
|
||||
if (toggle2) {
|
||||
toggle2.addEventListener('change', async () => {
|
||||
const db = state.get<ChatDB | null>(KEYS.DB);
|
||||
state.set('runCommandEnabled', toggle2.checked);
|
||||
if (db) await db.saveSetting('runCommandEnabled', toggle2.checked);
|
||||
setToolEnabled('run_command', toggle2.checked);
|
||||
|
||||
// 同步设置面板的开关
|
||||
const toggle1 = document.querySelector('#toggleRunCommand') as HTMLInputElement;
|
||||
if (toggle1) toggle1.checked = toggle2.checked;
|
||||
|
||||
if (toggle2.checked) {
|
||||
showToast('命令执行已开启(每次执行需确认)', 'info');
|
||||
logInfo('命令执行已开启');
|
||||
} else {
|
||||
showToast('命令执行已关闭', 'info');
|
||||
logInfo('命令执行已关闭');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function openToolsModal(): void {
|
||||
// 同步 run_command 开关状态
|
||||
const runCmdEnabled = state.get<boolean>('runCommandEnabled', true);
|
||||
const toggle2 = document.querySelector('#toggleRunCommand2') as HTMLInputElement;
|
||||
if (toggle2) toggle2.checked = runCmdEnabled;
|
||||
|
||||
modalEl.style.display = '';
|
||||
}
|
||||
|
||||
export function closeToolsModal(): void {
|
||||
modalEl.style.display = 'none';
|
||||
}
|
||||
+83
-16
@@ -52,6 +52,11 @@
|
||||
<line x1="9" y1="21" x2="15" y2="21"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="btnTools" title="工具面板">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="icon-btn" id="btnHistory" title="历史记录">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>
|
||||
@@ -270,20 +275,9 @@
|
||||
</label>
|
||||
</label>
|
||||
<p class="text-muted" style="font-size:11px;margin-bottom:8px;">启用后,AI 可以在对话中主动调用本地工具(读写文件、搜索、创建目录等)。需要模型支持 Tool Calling。</p>
|
||||
<div class="tool-settings" id="toolSettings">
|
||||
<div class="tool-list">
|
||||
<div class="tool-item"><span>📄 读取文件</span><span class="text-muted">自动</span></div>
|
||||
<div class="tool-item"><span>✏️ 写入文件</span><span class="text-muted">需确认</span></div>
|
||||
<div class="tool-item"><span>📁 列出目录</span><span class="text-muted">自动</span></div>
|
||||
<div class="tool-item"><span>🔍 搜索文件</span><span class="text-muted">自动</span></div>
|
||||
<div class="tool-item"><span>📂 创建目录</span><span class="text-muted">需确认</span></div>
|
||||
<div class="tool-item"><span>🗑️ 删除文件</span><span class="text-muted">需确认</span></div>
|
||||
<div class="tool-item"><span>📦 移动文件</span><span class="text-muted">需确认</span></div>
|
||||
<div class="tool-item"><span>📋 复制文件</span><span class="text-muted">需确认</span></div>
|
||||
<div class="tool-item"><span>🌐 网页抓取</span><span class="text-muted">自动</span></div>
|
||||
<div class="tool-item"><span>➕ 追加文件</span><span class="text-muted">需确认</span></div>
|
||||
<div class="tool-item"><span>✂️ 编辑文件</span><span class="text-muted">需确认</span></div>
|
||||
<div class="tool-item">
|
||||
<div style="display:flex;gap:8px;align-items:center;">
|
||||
<button class="btn btn-sm btn-outline" id="btnOpenToolsFromSettings">🔧 查看全部工具(12 个)</button>
|
||||
<div class="tool-item" style="flex:1;margin:0;">
|
||||
<span>💻 执行命令</span>
|
||||
<label class="toggle-label" style="margin:0;">
|
||||
<input type="checkbox" id="toggleRunCommand">
|
||||
@@ -291,8 +285,7 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted" style="font-size:11px;margin-top:8px;">⚠️ 高风险操作(写入/删除/命令执行)会弹出确认对话框。系统关键路径(/etc, /sys, ~/.ssh 等)已自动屏蔽。</p>
|
||||
</div>
|
||||
<p class="text-muted" style="font-size:11px;margin-top:8px;">⚠️ 高风险操作会弹出确认对话框。系统关键路径(/etc, /sys, ~/.ssh 等)已自动屏蔽。</p>
|
||||
</div>
|
||||
<div class="setting-group">
|
||||
<label class="setting-label">🖥️ 工作空间目录</label>
|
||||
@@ -387,6 +380,80 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════ 工具面板模态框 ═══════════════ -->
|
||||
<div class="modal-overlay" id="toolsModal" style="display:none;">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h3>🔧 工具面板(12 个)</h3>
|
||||
<button class="icon-btn" id="btnCloseTools">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="text-muted" style="font-size:12px;margin-bottom:12px;">启用工具调用后,AI 可在对话中自主调用以下工具。高风险操作需用户确认。</p>
|
||||
<div class="tools-grid">
|
||||
<div class="tool-card">
|
||||
<div class="tool-card-header"><span class="tool-card-icon">📄</span><span class="tool-card-name">read_file</span><span class="tool-card-badge auto">自动</span></div>
|
||||
<div class="tool-card-desc">读取文件内容,支持指定行范围</div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-card-header"><span class="tool-card-icon">✏️</span><span class="tool-card-name">write_file</span><span class="tool-card-badge confirm">需确认</span></div>
|
||||
<div class="tool-card-desc">写入文件,自动创建父目录</div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-card-header"><span class="tool-card-icon">📁</span><span class="tool-card-name">list_directory</span><span class="tool-card-badge auto">自动</span></div>
|
||||
<div class="tool-card-desc">列出目录内容,支持递归和隐藏文件</div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-card-header"><span class="tool-card-icon">🔍</span><span class="tool-card-name">search_files</span><span class="tool-card-badge auto">自动</span></div>
|
||||
<div class="tool-card-desc">按文件名或内容搜索文件</div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-card-header"><span class="tool-card-icon">📂</span><span class="tool-card-name">create_directory</span><span class="tool-card-badge confirm">需确认</span></div>
|
||||
<div class="tool-card-desc">创建目录,自动创建父级</div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-card-header"><span class="tool-card-icon">🗑️</span><span class="tool-card-name">delete_file</span><span class="tool-card-badge confirm">需确认</span></div>
|
||||
<div class="tool-card-desc">删除文件或目录</div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-card-header"><span class="tool-card-icon">📦</span><span class="tool-card-name">move_file</span><span class="tool-card-badge confirm">需确认</span></div>
|
||||
<div class="tool-card-desc">移动或重命名文件/目录</div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-card-header"><span class="tool-card-icon">📋</span><span class="tool-card-name">copy_file</span><span class="tool-card-badge confirm">需确认</span></div>
|
||||
<div class="tool-card-desc">复制文件或目录到新位置</div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-card-header"><span class="tool-card-icon">🌐</span><span class="tool-card-name">web_fetch</span><span class="tool-card-badge auto">自动</span></div>
|
||||
<div class="tool-card-desc">抓取网页内容,自动提取文本</div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-card-header"><span class="tool-card-icon">➕</span><span class="tool-card-name">append_file</span><span class="tool-card-badge confirm">需确认</span></div>
|
||||
<div class="tool-card-desc">追加内容到文件末尾</div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-card-header"><span class="tool-card-icon">✂️</span><span class="tool-card-name">edit_file</span><span class="tool-card-badge confirm">需确认</span></div>
|
||||
<div class="tool-card-desc">文件内查找替换,支持全部替换</div>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<div class="tool-card-header"><span class="tool-card-icon">💻</span><span class="tool-card-name">run_command</span><span class="tool-card-badge confirm">需确认</span></div>
|
||||
<div class="tool-card-desc">执行 shell 命令,无超时限制</div>
|
||||
<div style="margin-top:6px;display:flex;align-items:center;gap:8px;">
|
||||
<label class="toggle-label" style="margin:0;font-size:11px;">
|
||||
<input type="checkbox" id="toggleRunCommand2">
|
||||
<span class="toggle-slider"></span>
|
||||
<span style="margin-left:4px;">启用命令执行</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════ 图片预览 Lightbox ═══════════════ -->
|
||||
<div class="lightbox-overlay" id="lightbox" style="display:none;">
|
||||
<button class="lightbox-close" id="lightboxClose">✕</button>
|
||||
|
||||
@@ -20,6 +20,7 @@ import { initInputArea } from './components/input-area.js';
|
||||
import { initSettingsModal, closeSettingsModal } from './components/settings-modal.js';
|
||||
import { initHistoryModal, closeHistoryModal } from './components/history-modal.js';
|
||||
import { initMemoryModal } from './components/memory-modal.js';
|
||||
import { initToolsModal, closeToolsModal } from './components/tools-modal.js';
|
||||
import { initMemoryManager } from './services/memory-manager.js';
|
||||
import { setToolEnabled } from './services/tool-registry.js';
|
||||
import { initToolConfirmModal } from './components/tool-confirm-modal.js';
|
||||
@@ -205,6 +206,7 @@ async function init(): Promise<void> {
|
||||
initHelpModal();
|
||||
initToolConfirmModal();
|
||||
initMemoryModal();
|
||||
initToolsModal();
|
||||
initWorkspacePanel();
|
||||
setupDesktopIntegration();
|
||||
|
||||
@@ -286,6 +288,7 @@ function bindGlobalEvents(): void {
|
||||
closeSettingsModal();
|
||||
closeHistoryModal();
|
||||
closeLightbox();
|
||||
closeToolsModal();
|
||||
(document.querySelector('#helpModal') as HTMLElement).style.display = 'none';
|
||||
(document.querySelector('#memoryModal') as HTMLElement).style.display = 'none';
|
||||
}
|
||||
|
||||
@@ -2869,3 +2869,76 @@ html, body {
|
||||
}
|
||||
|
||||
工作空间面板已改为 flex 子元素,自然占据宽度 */
|
||||
|
||||
/* ═══════════════ 工具面板模态框 ═══════════════ */
|
||||
|
||||
#toolsModal .modal {
|
||||
max-width: 720px;
|
||||
width: 95vw;
|
||||
max-height: 85vh;
|
||||
}
|
||||
|
||||
.tools-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tool-card {
|
||||
padding: 10px 14px;
|
||||
background: var(--bg-layer);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-control);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.tool-card:hover {
|
||||
border-color: var(--border-strong);
|
||||
background: var(--bg-layer-alt);
|
||||
}
|
||||
|
||||
.tool-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.tool-card-icon {
|
||||
font-size: 15px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tool-card-name {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.tool-card-badge {
|
||||
margin-left: auto;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tool-card-badge.auto {
|
||||
color: var(--success);
|
||||
background: var(--success-bg);
|
||||
border: 1px solid rgba(108, 203, 95, 0.15);
|
||||
}
|
||||
|
||||
.tool-card-badge.confirm {
|
||||
color: var(--caution);
|
||||
background: var(--caution-bg);
|
||||
border: 1px solid rgba(252, 225, 0, 0.15);
|
||||
}
|
||||
|
||||
.tool-card-desc {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user