feat: 添加智能体卡片组件
- 新建 agentCard.ts 智能体卡片UI组件 - webviewContent.ts 集成样式和脚本
This commit is contained in:
311
src/views/agentCard.ts
Normal file
311
src/views/agentCard.ts
Normal file
@ -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 `
|
||||
<div class="agent-card" id="agent-${event.agentId}">
|
||||
<div class="agent-card-header" onclick="toggleAgentCard('${event.agentId}')">
|
||||
<div class="agent-card-title">
|
||||
<span class="agent-card-icon">🤖</span>
|
||||
<span>${event.agentName}</span>
|
||||
<span class="agent-card-status running">执行中</span>
|
||||
</div>
|
||||
<span class="agent-card-toggle">▼</span>
|
||||
</div>
|
||||
<div class="agent-card-body" id="agent-body-${event.agentId}">
|
||||
<div class="agent-card-instruction">指令:${escapeHtml(event.instruction)}</div>
|
||||
<div class="agent-steps" id="agent-steps-${event.agentId}">
|
||||
<!-- 步骤将动态添加 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染步骤项(进行中)
|
||||
*/
|
||||
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 `
|
||||
<div class="agent-step" id="agent-step-${event.agentId}-${event.step}">
|
||||
<span class="agent-step-icon">🔄</span>
|
||||
<div class="agent-step-content">
|
||||
<div class="agent-step-name">${event.toolName}</div>
|
||||
<div class="agent-step-result">${escapeHtml(shortInput)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新步骤项(完成)
|
||||
*/
|
||||
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',
|
||||
'<div class="agent-card-summary">' + escapeHtml(event.summary) + '</div>'
|
||||
);
|
||||
}
|
||||
|
||||
// 自动收起
|
||||
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',
|
||||
'<div class="agent-card-error">错误:' + escapeHtml(event.error) + '</div>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
@ -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()}
|
||||
|
||||
Reference in New Issue
Block a user