diff --git a/src/services/sseHandler.ts b/src/services/sseHandler.ts index b6aba4b..d150c1f 100644 --- a/src/services/sseHandler.ts +++ b/src/services/sseHandler.ts @@ -13,6 +13,8 @@ import type { SSEEventType, TextDeltaEvent, ToolCallRequest, + ToolConfirmEvent, + PlanConfirmEvent, AskUserEvent, CompleteEvent, ErrorEvent, @@ -36,6 +38,10 @@ export interface SSECallbacks { onTextDelta?: (data: TextDeltaEvent) => void; /** 收到工具调用请求 */ onToolCall?: (data: ToolCallRequest) => void; + /** 收到工具确认请求(Ask 模式) */ + onToolConfirm?: (data: ToolConfirmEvent) => void; + /** 收到计划确认请求(Plan 模式) */ + onPlanConfirm?: (data: PlanConfirmEvent) => void; /** 工具开始执行 */ onToolStart?: (data: ToolStartEvent) => void; /** 工具执行完成 */ @@ -136,7 +142,7 @@ export async function startStreamDialog( const body = JSON.stringify(request); - console.log(`[SSE] 开始流式对话: taskId=${request.taskId}, url=${urlString}`); + console.log(`[SSE] 开始流式对话: taskId=${request.taskId}, mode=${request.mode}, url=${urlString}`); return new Promise((resolve, reject) => { const options: http.RequestOptions = { @@ -268,6 +274,12 @@ function dispatchEvent( case 'tool_call': callbacks.onToolCall?.(data as ToolCallRequest); break; + case 'tool_confirm': + callbacks.onToolConfirm?.(data as ToolConfirmEvent); + break; + case 'plan_confirm': + callbacks.onPlanConfirm?.(data as PlanConfirmEvent); + break; case 'tool_start': callbacks.onToolStart?.(data as ToolStartEvent); break; diff --git a/src/views/messageArea.ts b/src/views/messageArea.ts index 89f2d39..635f8d2 100644 --- a/src/views/messageArea.ts +++ b/src/views/messageArea.ts @@ -611,6 +611,88 @@ export function getMessageAreaStyles(): string { text-align: center; } + /* 计划卡片样式 */ + .segment-plan { + margin: 8px 0; + } + .plan-card { + border: 1px solid var(--vscode-input-border); + border-radius: 8px; + overflow: hidden; + background: var(--vscode-editor-background); + } + .plan-header { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + background: var(--vscode-sideBar-background); + border-bottom: 1px solid var(--vscode-input-border); + } + .plan-icon { + font-size: 18px; + } + .plan-title { + font-weight: 600; + font-size: 14px; + } + .plan-body { + padding: 12px; + } + .plan-summary { + color: var(--vscode-descriptionForeground); + margin-bottom: 10px; + font-size: 13px; + } + .plan-steps { + font-size: 13px; + } + .plan-step { + padding: 6px 8px; + margin-bottom: 4px; + background: var(--vscode-list-hoverBackground); + border-radius: 4px; + } + .plan-step:last-child { + margin-bottom: 0; + } + .step-num { + color: var(--vscode-textLink-foreground); + font-weight: 500; + margin-right: 4px; + } + .plan-actions { + display: flex; + gap: 8px; + padding: 12px; + border-top: 1px solid var(--vscode-input-border); + background: var(--vscode-sideBar-background); + } + .plan-btn { + padding: 6px 16px; + border-radius: 4px; + border: none; + cursor: pointer; + font-size: 12px; + font-weight: 500; + } + .plan-btn-confirm { + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + } + .plan-btn-confirm:hover { + background: var(--vscode-button-hoverBackground); + } + .plan-btn-modify { + background: var(--vscode-input-background); + color: var(--vscode-foreground); + border: 1px solid var(--vscode-input-border); + } + .plan-btn-cancel { + background: transparent; + color: var(--vscode-descriptionForeground); + } + ${getWaveformPreviewContent()} `; } @@ -1034,6 +1116,93 @@ export function getMessageAreaScript(): string { container.scrollTop = container.scrollHeight; } }, 0); + } else if (segment.type === 'plan') { + // 计划卡片渲染(类似 askUser) + segmentDiv.className += ' segment-plan'; + + // 检查是否已回答 + const isAnswered = answeredQuestions.has(segment.askId); + const selectedAnswer = answeredQuestions.get(segment.askId); + + if (isAnswered) { + segmentDiv.classList.add('answered'); + } + + const stepsHtml = (segment.planSteps || []).map((step, i) => + \`