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,
|
getMessageAreaStyles,
|
||||||
getMessageAreaScript,
|
getMessageAreaScript,
|
||||||
} from "./messageArea";
|
} from "./messageArea";
|
||||||
|
import {
|
||||||
|
getAgentCardStyles,
|
||||||
|
getAgentCardScript,
|
||||||
|
} from "./agentCard";
|
||||||
/**
|
/**
|
||||||
* 获取 WebView 面板的 HTML 内容
|
* 获取 WebView 面板的 HTML 内容
|
||||||
*/
|
*/
|
||||||
@ -65,6 +69,7 @@ export function getWebviewContent(iconUri?: string): string {
|
|||||||
padding: 0 20px 20px 20px;
|
padding: 0 20px 20px 20px;
|
||||||
}
|
}
|
||||||
${getMessageAreaStyles()}
|
${getMessageAreaStyles()}
|
||||||
|
${getAgentCardStyles()}
|
||||||
${getWaveformPreviewContent()}
|
${getWaveformPreviewContent()}
|
||||||
${getConversationHistoryBarStyles()}
|
${getConversationHistoryBarStyles()}
|
||||||
${getInputAreaStyles()}
|
${getInputAreaStyles()}
|
||||||
@ -531,6 +536,7 @@ export function getWebviewContent(iconUri?: string): string {
|
|||||||
});
|
});
|
||||||
|
|
||||||
${getMessageAreaScript()}
|
${getMessageAreaScript()}
|
||||||
|
${getAgentCardScript()}
|
||||||
${getWaveformPreviewScript()}
|
${getWaveformPreviewScript()}
|
||||||
${getConversationHistoryBarScript()}
|
${getConversationHistoryBarScript()}
|
||||||
${getInputAreaScript()}
|
${getInputAreaScript()}
|
||||||
|
|||||||
Reference in New Issue
Block a user