diff --git a/src/views/agentCard.ts b/src/views/agentCard.ts new file mode 100644 index 0000000..1515750 --- /dev/null +++ b/src/views/agentCard.ts @@ -0,0 +1,311 @@ +/** + * 智能体卡片组件 + * + * 功能说明: + * - 显示子智能体的执行过程 + * - 支持展开/收起步骤详情 + * - 显示执行状态和统计信息 + */ + +import type { + AgentStartEvent, + AgentProgressEvent, + AgentCompleteEvent, + AgentErrorEvent +} from '../types/api'; + +/** + * 获取智能体卡片样式 + */ +export function getAgentCardStyles(): string { + return ` + .agent-card { + background: var(--vscode-editor-background); + border: 1px solid var(--vscode-input-border); + border-radius: 8px; + margin: 10px 0; + overflow: hidden; + } + .agent-card-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px 12px; + background: var(--vscode-sideBar-background); + cursor: pointer; + user-select: none; + } + .agent-card-header:hover { + background: var(--vscode-list-hoverBackground); + } + .agent-card-title { + display: flex; + align-items: center; + gap: 8px; + font-weight: 500; + } + .agent-card-icon { + font-size: 16px; + } + .agent-card-status { + font-size: 12px; + padding: 2px 6px; + border-radius: 4px; + } + .agent-card-status.running { + background: var(--vscode-inputValidation-infoBackground); + color: var(--vscode-inputValidation-infoForeground); + } + .agent-card-status.completed { + background: var(--vscode-testing-iconPassed); + color: white; + } + .agent-card-status.error { + background: var(--vscode-testing-iconFailed); + color: white; + } + .agent-card-toggle { + font-size: 12px; + color: var(--vscode-descriptionForeground); + } + .agent-card-body { + padding: 12px; + border-top: 1px solid var(--vscode-input-border); + } + .agent-card-body.collapsed { + display: none; + } + .agent-card-instruction { + font-size: 13px; + color: var(--vscode-descriptionForeground); + margin-bottom: 10px; + padding-bottom: 10px; + border-bottom: 1px dashed var(--vscode-input-border); + } + .agent-steps { + font-size: 13px; + } + .agent-step { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 6px 0; + border-left: 2px solid var(--vscode-input-border); + padding-left: 12px; + margin-left: 6px; + } + .agent-step:last-child { + border-left-color: transparent; + } + .agent-step-icon { + flex-shrink: 0; + } + .agent-step-content { + flex: 1; + min-width: 0; + } + .agent-step-name { + font-weight: 500; + } + .agent-step-result { + font-size: 12px; + color: var(--vscode-descriptionForeground); + margin-top: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .agent-card-summary { + font-size: 13px; + padding: 8px 12px; + background: var(--vscode-sideBar-background); + border-top: 1px solid var(--vscode-input-border); + } + .agent-card-error { + color: var(--vscode-errorForeground); + padding: 8px 12px; + background: var(--vscode-inputValidation-errorBackground); + } + `; +} + +/** + * 渲染智能体卡片(启动状态) + */ +export function renderAgentCardStart(event: AgentStartEvent): string { + return ` +
+
+
+ 🤖 + ${event.agentName} + 执行中 +
+ +
+
+
指令:${escapeHtml(event.instruction)}
+
+ +
+
+
+ `; +} + +/** + * 渲染步骤项(进行中) + */ +export function renderAgentStepRunning(event: AgentProgressEvent): string { + const inputStr = event.toolInput ? JSON.stringify(event.toolInput) : ''; + const shortInput = inputStr.length > 50 ? inputStr.substring(0, 50) + '...' : inputStr; + + return ` +
+ 🔄 +
+
${event.toolName}
+
${escapeHtml(shortInput)}
+
+
+ `; +} + +/** + * 更新步骤项(完成) + */ +export function getStepCompleteUpdate(event: AgentProgressEvent): { icon: string; result: string } { + const result = event.toolResult || ''; + const shortResult = result.length > 80 ? result.substring(0, 80) + '...' : result; + return { + icon: event.status === 'completed' ? '✅' : '❌', + result: shortResult + }; +} + +/** + * 获取智能体卡片脚本 + */ +export function getAgentCardScript(): string { + return ` + // 智能体状态存储 + const agentStates = {}; + + // 切换智能体卡片展开/收起 + function toggleAgentCard(agentId) { + const body = document.getElementById('agent-body-' + agentId); + const header = body?.previousElementSibling; + const toggle = header?.querySelector('.agent-card-toggle'); + + if (body && toggle) { + body.classList.toggle('collapsed'); + toggle.textContent = body.classList.contains('collapsed') ? '▶' : '▼'; + } + } + + // 处理智能体启动事件 + function handleAgentStart(event) { + agentStates[event.agentId] = { + status: 'running', + steps: [] + }; + + // 在当前消息中添加智能体卡片 + const currentMessage = document.querySelector('.bot-message:last-child .message-content'); + if (currentMessage) { + currentMessage.insertAdjacentHTML('beforeend', renderAgentCardStart(event)); + } + } + + // 处理智能体进度事件 + function handleAgentProgress(event) { + const stepsContainer = document.getElementById('agent-steps-' + event.agentId); + if (!stepsContainer) return; + + if (event.status === 'running') { + // 添加新步骤 + stepsContainer.insertAdjacentHTML('beforeend', renderAgentStepRunning(event)); + } else if (event.status === 'completed') { + // 更新步骤状态 + const stepEl = document.getElementById('agent-step-' + event.agentId + '-' + event.step); + if (stepEl) { + const iconEl = stepEl.querySelector('.agent-step-icon'); + const resultEl = stepEl.querySelector('.agent-step-result'); + if (iconEl) iconEl.textContent = '✅'; + if (resultEl) { + const result = event.toolResult || ''; + resultEl.textContent = result.length > 80 ? result.substring(0, 80) + '...' : result; + } + } + } + } + + // 处理智能体完成事件 + function handleAgentComplete(event) { + const card = document.getElementById('agent-' + event.agentId); + if (!card) return; + + // 更新状态 + const statusEl = card.querySelector('.agent-card-status'); + if (statusEl) { + statusEl.className = 'agent-card-status completed'; + statusEl.textContent = '完成'; + } + + // 添加摘要 + const body = card.querySelector('.agent-card-body'); + if (body) { + body.insertAdjacentHTML('beforeend', + '
' + escapeHtml(event.summary) + '
' + ); + } + + // 自动收起 + body?.classList.add('collapsed'); + const toggle = card.querySelector('.agent-card-toggle'); + if (toggle) toggle.textContent = '▶'; + } + + // 处理智能体错误事件 + function handleAgentError(event) { + const card = document.getElementById('agent-' + event.agentId); + if (!card) return; + + // 更新状态 + const statusEl = card.querySelector('.agent-card-status'); + if (statusEl) { + statusEl.className = 'agent-card-status error'; + statusEl.textContent = '错误'; + } + + // 添加错误信息 + const body = card.querySelector('.agent-card-body'); + if (body) { + body.insertAdjacentHTML('beforeend', + '
错误:' + escapeHtml(event.error) + '
' + ); + } + } + + // HTML 转义 + function escapeHtml(text) { + if (!text) return ''; + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + `; +} + +/** + * HTML 转义(服务端使用) + */ +function escapeHtml(text: string): string { + if (!text) return ''; + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} diff --git a/src/views/webviewContent.ts b/src/views/webviewContent.ts index c3e321a..740d484 100644 --- a/src/views/webviewContent.ts +++ b/src/views/webviewContent.ts @@ -17,6 +17,10 @@ import { getMessageAreaStyles, getMessageAreaScript, } from "./messageArea"; +import { + getAgentCardStyles, + getAgentCardScript, +} from "./agentCard"; /** * 获取 WebView 面板的 HTML 内容 */ @@ -65,6 +69,7 @@ export function getWebviewContent(iconUri?: string): string { padding: 0 20px 20px 20px; } ${getMessageAreaStyles()} + ${getAgentCardStyles()} ${getWaveformPreviewContent()} ${getConversationHistoryBarStyles()} ${getInputAreaStyles()} @@ -531,6 +536,7 @@ export function getWebviewContent(iconUri?: string): string { }); ${getMessageAreaScript()} + ${getAgentCardScript()} ${getWaveformPreviewScript()} ${getConversationHistoryBarScript()} ${getInputAreaScript()}