Files
IC-Coder-Plugin/src/views/webviewContent.ts

992 lines
33 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
getWaveformPreviewContent,
getWaveformPreviewScript,
} from "./waveformPreviewContent";
import {
getConversationHistoryBarContent,
getConversationHistoryBarStyles,
getConversationHistoryBarScript,
} from "./conversationHistoryBar";
import {
getInputAreaContent,
getInputAreaStyles,
getInputAreaScript,
} from "./inputArea";
import {
getMessageAreaContent,
getMessageAreaStyles,
getMessageAreaScript,
} from "./messageArea";
import { getAgentCardStyles, getAgentCardScript } from "./agentCard";
import {
getProgressBarContent,
getProgressBarStyles,
getProgressBarScript,
} from "./progressBar";
import { getHighlightJsLinks } from "../components/codeHighlight";
import { getCurrentEnv } from "../config/settings";
import { taskCompleteIconSvg } from "../constants/toolIcons";
import {
getInvitationModalContent,
getInvitationModalStyles,
getInvitationModalScript,
} from "./invitationModal";
import {
getWelcomeModalContent,
getWelcomeModalStyles,
getWelcomeModalScript,
} from "./welcomeModal";
import {
getNdtWelcomeModalContent,
getNdtWelcomeModalStyles,
getNdtWelcomeModalScript,
} from "./ndtWelcomeModal";
import {
getExpiredModalContent,
getExpiredModalStyles,
getExpiredModalScript,
} from "./expiredModal";
/**
* 获取 WebView 面板的 HTML 内容
*/
export function getWebviewContent(
iconUri?: string,
autoIconUri?: string,
liteIconUri?: string,
syIconUri?: string,
maxIconUri?: string,
qrCodeUri?: string,
logoUri?: string,
): string {
// 获取当前环境,只在 dev 和 test 环境下显示快速操作按钮
const currentEnv = getCurrentEnv();
const showQuickActions = currentEnv === "dev" || currentEnv === "test";
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IC Coder</title>
${getHighlightJsLinks()}
<style>
body {
font-family: var(--vscode-font-family);
background: var(--vscode-editor-background);
color: var(--vscode-foreground);
margin: 0;
padding: 0;
height: 100vh;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.header {
text-align: center;
padding: 20px;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 1;
}
.header.hidden {
display: none;
}
.header h1 {
background: linear-gradient(to right, #4A9EFF, #7CB8FF, #A8D0FF);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0 0 8px 0;
}
.chat-container {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
padding: 0 20px 20px 20px;
}
${getMessageAreaStyles()}
${getAgentCardStyles()}
${getWaveformPreviewContent()}
${getConversationHistoryBarStyles()}
${getProgressBarStyles()}
${getInputAreaStyles()}
${getInvitationModalStyles()}
${getWelcomeModalStyles()}
${getNdtWelcomeModalStyles()}
${getExpiredModalStyles()}
.file-editor-section {
margin-bottom: 15px;
padding: 15px;
background: var(--vscode-input-background);
border: 1px solid var(--vscode-input-border);
border-radius: 8px;
display: none;
flex-shrink: 0;
}
.file-editor-section.active {
display: block;
}
.file-editor-section h3 {
margin: 0 0 10px 0;
color: var(--vscode-button-background);
}
.file-editor-textarea {
width: 100%;
min-height: 300px;
padding: 10px;
background: var(--vscode-editor-background);
color: var(--vscode-editor-foreground);
border: 1px solid var(--vscode-input-border);
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 12px;
resize: vertical;
}
.editor-actions {
display: flex;
gap: 10px;
margin-top: 10px;
}
/* 流式消息样式 */
.streaming .message-content {
border-right: 2px solid var(--vscode-focusBorder);
animation: blink 1s infinite;
}
@keyframes blink {
0%, 50% { border-color: var(--vscode-focusBorder); }
51%, 100% { border-color: transparent; }
}
/* 加载指示器样式 */
.loading-message {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
color: var(--vscode-descriptionForeground);
}
.loading-dots {
display: flex;
gap: 4px;
}
.loading-dots span {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--vscode-focusBorder);
animation: loadingDot 1.4s infinite ease-in-out;
}
.loading-dots span:nth-child(1) { animation-delay: 0s; }
.loading-dots span:nth-child(2) { animation-delay: 0.2s; }
.loading-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes loadingDot {
0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; }
40% { transform: scale(1); opacity: 1; }
}
.loading-text {
font-size: 13px;
}
/* 工具状态样式 */
.tool-status {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
margin: 4px 0;
font-size: 12px;
border-radius: 6px;
background: var(--vscode-textBlockQuote-background);
}
.tool-status.tool-start {
border-left: 3px solid var(--vscode-charts-blue);
}
.tool-status.tool-complete {
border-left: 3px solid var(--vscode-charts-green);
}
.tool-status.tool-error {
border-left: 3px solid var(--vscode-charts-red);
}
.tool-icon {
font-size: 14px;
}
.tool-name {
font-weight: 500;
color: var(--vscode-foreground);
}
.tool-status-text {
color: var(--vscode-descriptionForeground);
}
.tool-detail {
margin-top: 4px;
font-size: 11px;
color: var(--vscode-descriptionForeground);
white-space: pre-wrap;
max-height: 100px;
overflow-y: auto;
}
/* 用户问题样式 */
.question-message {
padding: 16px;
}
.question-text {
margin-bottom: 12px;
font-weight: 500;
}
.question-options {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.question-option {
padding: 8px 16px;
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
border: 1px solid var(--vscode-button-border);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.question-option:hover {
background: var(--vscode-button-secondaryHoverBackground);
}
.question-option.selected {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
}
.question-message.answered .question-option:not(.selected) {
opacity: 0.5;
pointer-events: none;
}
.custom-input-container {
display: flex;
gap: 8px;
width: 100%;
margin-top: 8px;
}
.custom-input {
flex: 1;
padding: 8px 12px;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px solid var(--vscode-input-border);
border-radius: 6px;
font-size: 13px;
}
.custom-submit {
padding: 8px 16px;
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
border: none;
border-radius: 6px;
cursor: pointer;
}
.custom-submit:hover {
background: var(--vscode-button-hoverBackground);
}
.question-message.answered .custom-input-container {
display: none;
}
/* 分段消息样式 */
.segmented-message {
padding: 0;
}
.message-segment {
padding: 10px 0;
}
.segment-text {
line-height: 1.6;
font-size:0.9rem;
color: var(--vscode-foreground);
letter-spacing: 0.5px;
}
.segment-tool {
background: var(--vscode-textBlockQuote-background);
border-radius: 6px;
margin: 8px 0;
padding: 10px 14px;
}
.tool-segment-header {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.tool-segment-icon {
font-size: 14px;
}
.tool-segment-name {
font-weight: 500;
color: var(--vscode-foreground);
}
.tool-segment-result {
font-size: 12px;
color: var(--vscode-descriptionForeground);
padding-left: 22px;
}
.segment-tool.tool-success {
border-left: 3px solid var(--vscode-charts-green);
}
.segment-tool.tool-error {
border-left: 3px solid var(--vscode-charts-red);
}
.segment-tool.tool-running {
border-left: 3px solid var(--vscode-charts-blue);
}
.segment-question {
background: var(--vscode-textBlockQuote-background);
border-radius: 6px;
margin: 8px 0;
padding: 12px 35px;
border-left: 3px solid var(--vscode-charts-orange);
}
.question-segment .question-text {
margin-bottom: 8px;
font-weight: 500;
}
.question-segment .question-options {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.question-opt {
padding: 4px 10px;
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
border-radius: 4px;
font-size: 12px;
}
/* 状态栏样式 */
.status-bar {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: var(--vscode-textBlockQuote-background);
border-radius: 6px;
margin: 8px 0;
font-size: 13px;
color: var(--vscode-descriptionForeground);
}
.status-bar #statusText {
background: linear-gradient(90deg,
var(--vscode-descriptionForeground) 0%,
var(--vscode-foreground) 50%,
var(--vscode-descriptionForeground) 100%);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: textShimmer 2s linear infinite;
}
@keyframes textShimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--vscode-charts-blue);
animation: statusPulse 1.5s ease-in-out infinite;
box-shadow: 0 0 8px currentColor;
}
@keyframes statusPulse {
0%, 100% { opacity: 1; transform: scale(1.2); }
50% { opacity: 0.3; transform: scale(0.6); }
}
.status-bar.working .status-indicator {
background: var(--vscode-charts-orange);
}
.status-bar.success .status-indicator {
background: var(--vscode-charts-green);
animation: none;
}
.status-bar.error .status-indicator {
background: var(--vscode-charts-red);
animation: none;
}
/* 快捷操作按钮样式 */
.quick-actions {
display: flex;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.quick-btn {
padding: 8px 16px;
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
border: 1px solid var(--vscode-button-border);
border-radius: 6px;
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
}
.quick-btn:hover {
background: var(--vscode-button-secondaryHoverBackground);
}
/* 响应式调整 */
@media (max-height: 600px) {
.header {
/* 使用 clamp 动态调整内边距: 最小值 5px, 理想值 2vh, 最大值 20px */
padding: clamp(5px, 2vh, 20px) 20px;
flex: 0 0 auto;
min-height: auto;
}
.header img {
/* 使用 clamp 动态调整图片高度: 最小值 40px, 理想值 10vh, 最大值 60px */
max-height: clamp(40px, 10vh, 60px) !important;
}
.header p {
/* 使用 clamp 动态调整字体大小 */
font-size: clamp(12px, 2.5vh, 14px) !important;
margin-top: clamp(4px, 1.5vh, 8px) !important;
line-height: 1.2 !important;
margin-bottom: clamp(4px, 1.5vh, 8px) !important;
}
.quick-actions {
margin-bottom: 5px;
gap: 5px;
}
.quick-btn {
padding: 4px 8px;
font-size: 12px;
}
.chat-container {
padding: 0 10px 10px 10px;
}
}
/* 高度极小时隐藏描述文本 */
@media (max-height: 450px) {
.header p {
display: none !important;
}
.header {
padding: 4px;
}
.quick-actions {
margin-bottom: 4px;
}
}
@media (max-width: 480px) {
.header h1 {
font-size: 24px;
}
.header p {
font-size: 14px;
}
.quick-actions {
justify-content: center;
}
.chat-container {
padding: 0 10px 10px 10px;
}
}
</style>
</head>
<body>
${getConversationHistoryBarContent()}
${getProgressBarContent()}
${getInvitationModalContent(qrCodeUri, logoUri)}
${getWelcomeModalContent(logoUri)}
${getNdtWelcomeModalContent(logoUri)}
${getExpiredModalContent(logoUri)}
<div class="header">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="${logoUri}" alt="IC Coder" style="max-width: 100%; height: auto; max-height: 80px;" />
</div>
<p style="font-size: 16px; margin-top: 12px; line-height: 1.5;">
The <span style="background: linear-gradient(to right, #42bcff, #4A9EFF); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: bold;">Agentic AI</span> Verilog Coding Platform,
<span style="display: block; margin-top: 8px;">将芯片设计与验证的效率提升至少20倍</span>
</p>
</div>
<div class="chat-container">
${getMessageAreaContent()}
<!-- 状态栏 -->
<div id="statusBar" class="status-bar" style="display: none;">
<div class="status-indicator"></div>
<span id="statusText">思考中...</span>
</div>
${
showQuickActions
? `<div class="quick-actions">
<button class="quick-btn" onclick="quickAction('counter')">生成计数器</button>
<button class="quick-btn" onclick="quickAction('fsm')">生成状态机</button>
<button class="quick-btn" onclick="quickAction('testbench')">生成测试平台</button>
<button class="quick-btn" onclick="quickAction('explore')">知识探索</button>
</div>`
: ""
}
${getInputAreaContent(autoIconUri, liteIconUri, syIconUri, maxIconUri)}
</div>
<script>
console.log('[WebView] 脚本开始执行');
const vscode = acquireVsCodeApi();
window.vscode = vscode; // 确保全局可访问
console.log('[WebView] vscode API 已获取');
const messageInput = document.getElementById('messageInput');
const modeSelect = document.getElementById('modeSelect');
const messagesEl = document.getElementById('messages');
// 图标常量
const taskCompleteIconSvg = ${JSON.stringify(taskCompleteIconSvg)};
// 全局变量
let currentStreamingMessage = null;
let loadingIndicator = null;
let currentSegmentedMessage = null; // 当前分段消息容器
// 设置二维码图片
const feedbackQRCodeImage = document.getElementById('feedbackQRCodeImage');
if (feedbackQRCodeImage && '${qrCodeUri}') {
feedbackQRCodeImage.src = '${qrCodeUri}';
}
// ========== 模式选择器脚本(直接内联,避免模板字符串嵌套问题)==========
let currentMode = 'agent';
function toggleModeDropdown() {
const modeSelectEl = document.getElementById('modeSelect');
const modelSelectEl = document.getElementById('modelSelect');
if (modeSelectEl) {
modeSelectEl.classList.toggle('active');
if (modelSelectEl) {
modelSelectEl.classList.remove('active');
}
}
}
function selectMode(value, label) {
currentMode = value;
const modeValue = document.getElementById('modeValue');
const modeTooltip = document.getElementById('modeTooltip');
if (modeValue) {
modeValue.textContent = label;
}
if (modeTooltip) {
const tooltipMap = {
'plan': 'plan模式',
'ask': 'ask模式',
'agent': 'agent模式'
};
modeTooltip.textContent = tooltipMap[value] || '切换模式';
}
const options = document.querySelectorAll('.mode-option');
options.forEach(option => {
if (option.getAttribute('data-value') === value) {
option.classList.add('selected');
} else {
option.classList.remove('selected');
}
});
const modeSelectEl = document.getElementById('modeSelect');
if (modeSelectEl) {
modeSelectEl.classList.remove('active');
}
}
function getCurrentMode() {
return currentMode;
}
document.addEventListener('click', (event) => {
const modeSelectEl = document.getElementById('modeSelect');
if (modeSelectEl && !modeSelectEl.contains(event.target)) {
modeSelectEl.classList.remove('active');
}
});
// ========== 模式选择器脚本结束 ==========
function quickAction(type) {
const questions = {
counter: '生成一个4位同步计数器',
fsm: '生成一个状态机',
testbench: '生成测试平台',
explore: '请启动知识探索智能体,分析当前项目结构'
};
if (questions[type]) {
messageInput.value = questions[type];
sendMessage();
}
}
// 检查 header 显示状态
function checkHeaderVisibility() {
const header = document.querySelector('.header');
const messages = document.getElementById('messages');
if (header && messages) {
if (messages.children.length > 0) {
header.classList.add('hidden');
} else {
header.classList.remove('hidden');
}
}
}
// 监听来自插件的消息
window.addEventListener('message', event => {
const message = event.data;
console.log('[WebView] 收到消息:', message.command, message);
switch (message.command) {
case 'receiveMessage':
// 接收完整消息
addMessage(message.text, 'bot');
break;
case 'updateStreamingMessage':
// 更新流式消息
updateOrCreateStreamingMessage(message.text);
break;
case 'updateSegments':
// 实时更新分段消息(按后端返回顺序)
console.log('[WebView] 实时更新段落, segments:', message.segments);
updateSegmentsRealtime(message.segments, message.isComplete);
// 如果对话完成,恢复发送按钮状态
if (message.isComplete && typeof setSendButtonState === 'function') {
setSendButtonState(false);
}
break;
case 'receiveSegments':
// 接收分段消息(兼容旧代码)
console.log('[WebView] 调用 renderSegments, segments:', message.segments);
renderSegments(message.segments);
break;
case 'toolStart':
// 工具开始执行
addToolStatus(message.toolName, 'start');
break;
case 'toolComplete':
// 工具执行完成
addToolStatus(message.toolName, 'complete', message.result);
break;
case 'toolError':
// 工具执行错误
addToolStatus(message.toolName, 'error', message.error);
break;
case 'updateStatus':
// 更新状态栏
const statusBar = document.getElementById('statusBar');
const statusText = document.getElementById('statusText');
if (statusBar && statusText) {
statusBar.style.display = 'flex';
statusText.textContent = message.text;
statusBar.className = 'status-bar ' + (message.type || '');
}
break;
case 'hideStatus':
// 隐藏状态栏
const statusBarHide = document.getElementById('statusBar');
if (statusBarHide) {
statusBarHide.style.display = 'none';
}
break;
case 'updateUserInfo':
// 更新用户信息
console.log('[WebView] 收到用户信息:', message.userInfo);
console.log('[WebView] Credits 字段值:', message.userInfo?.credits);
if (message.userInfo) {
const userInfoData = {
nickname: message.userInfo.nickname || message.userInfo.username || '用户',
userId: message.userInfo.userId || message.userInfo.id,
tierName: message.userInfo.tierName,
tierIconUrl: message.tierIconUrl,
registerTime: message.userInfo.registerTime || message.userInfo.createdAt,
credits: message.userInfo.credits,
membership: message.userInfo.membership
};
console.log('[WebView] 显示用户信息:', userInfoData);
console.log('[WebView] userInfoData.credits:', userInfoData.credits);
console.log('[WebView] userInfoData.membership:', userInfoData.membership);
// 调用更新用户头像图标按钮的函数
if (typeof updateUserAvatarIconButton === 'function') {
updateUserAvatarIconButton(userInfoData);
} else {
console.warn('[WebView] updateUserAvatarIconButton 函数不存在');
}
}
break;
case 'personalRulesLoaded':
// 加载个人规则数据
if (typeof loadPersonalRules === 'function') {
loadPersonalRules(message.data);
}
break;
case 'autoSendMessage':
// 自动发送待发送的消息(登录后)
console.log('[WebView] 自动发送待发送消息:', message.text);
const inputElement = document.getElementById('userInput');
if (inputElement) {
inputElement.value = message.text;
// 触发发送
if (typeof sendMessage === 'function') {
sendMessage();
}
}
break;
case 'showFeedbackQRCode':
// 显示用户反馈二维码弹窗
console.log('[WebView] 显示用户反馈二维码弹窗');
if (typeof showFeedbackQRCode === 'function') {
showFeedbackQRCode();
}
break;
case 'resetSegmentedMessage':
// 重置分段消息容器(停止对话时调用)
console.log('[WebView] 重置分段消息容器');
currentSegmentedMessage = null;
break;
case 'contextUsage':
// 更新上下文使用量显示
if (typeof updateContextDisplay === 'function') {
updateContextDisplay(message.currentTokens, message.maxTokens);
}
break;
case 'hideLoading':
// 隐藏加载指示器
hideLoadingIndicator();
break;
case 'taskComplete':
// 显示任务完成提示
const taskDiv = document.createElement('div');
taskDiv.className = 'message bot-message';
const taskActionsDiv = document.createElement('div');
taskActionsDiv.className = 'message-actions';
const taskMessageContent = document.createElement('span');
taskMessageContent.innerHTML = taskCompleteIconSvg + ' 任务完成';
const taskCopyBtn = document.createElement('button');
taskCopyBtn.className = 'action-btn';
taskCopyBtn.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M761.088 715.3152a38.7072 38.7072 0 0 1 0-77.4144 37.4272 37.4272 0 0 0 37.4272-37.4272V265.0112a37.4272 37.4272 0 0 0-37.4272-37.4272H425.6256a37.4272 37.4272 0 0 0-37.4272 37.4272 38.7072 38.7072 0 1 1-77.4144 0 115.0976 115.0976 0 0 1 114.8416-114.8416h335.4624a115.0976 115.0976 0 0 1 114.8416 114.8416v335.4624a115.0976 115.0976 0 0 1-114.8416 114.8416z" fill="currentColor"/><path d="M589.4656 883.0976H268.1856a121.1392 121.1392 0 0 1-121.2928-121.2928v-322.56a121.1392 121.1392 0 0 1 121.2928-121.344h321.28a121.1392 121.1392 0 0 1 121.2928 121.2928v322.56c1.28 67.1232-54.1696 121.344-121.2928 121.344zM268.1856 395.3152a43.52 43.52 0 0 0-43.8784 43.8784v322.56a43.52 43.52 0 0 0 43.8784 43.8784h321.28a43.52 43.52 0 0 0 43.8784-43.8784v-322.56a43.52 43.52 0 0 0-43.8784-43.8784z" fill="currentColor"/></svg><span class="action-tooltip">复制</span>\`;
taskCopyBtn.onclick = () => {
// 获取前一个 AI 消息的内容
const prevMessage = taskDiv.previousElementSibling;
if (prevMessage && prevMessage.classList.contains('bot-message')) {
const textContent = prevMessage.textContent || '';
copyMessage(textContent, taskCopyBtn);
}
};
const taskLikeBtn = document.createElement('button');
taskLikeBtn.className = 'action-btn';
taskLikeBtn.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M923.5 411.2c-28.6-33.9-72.1-53-116.4-51.1h-68c6.4-31.6 10.1-63.9 11.2-96v-0.8c-0.5-60.9-18.7-112-51.2-144-22.6-22.2-50.8-33.7-81.5-33.3-38.3 0-69.1 11.5-91.7 34.2-26.5 26.5-39.9 66.8-39.8 119.6 0.1 40.1-19.4 83.4-52.1 115.9-32 31.8-71.7 49.3-111.8 49.3H295.6c-3 0-6 0.3-8.9 0.8v-1.2H140.8c-39.7 0-72.2 32.5-72.2 72.2v392.9c0 39.7 32.5 72.2 72.2 72.2h146.8v-0.6c2.9 0.4 5.9 0.7 8.9 0.7h464.7c33.3-0.8 65.6-13 91.1-34.4s43.1-51.1 49.6-83.8l52.3-289.1c9.4-43.4-2.1-89.6-30.7-123.5zM147.7 843.7v-344c0-9 7.3-16.3 16.3-16.3h70.4V860H164c-9 0-16.3-7.3-16.3-16.3z m726.4-324.9l-0.2 0.6-51.7 290.3c-6.7 29.1-32.3 50.2-62.2 51.3l-4.9 0.2-0.4 0.3h-440V486h7.3c61.4 0 121-25.7 168.1-72.4 48.6-48.2 76.5-111.7 76.5-174.2-0.1-31.5 4.9-51.8 15.3-62.2 7.4-7.4 18.7-10.8 35.8-10.8h0.2c9-0.1 17.4 3.6 24.9 11 16.3 16.2 25.7 47.3 25.8 85.4-1.2 41.8-7.9 83.3-19.9 123.4l-21.6 54.3h181.5c24.5-0.6 48.2 8.9 65.1 26.1 16.9 17.2 25.3 40.8 23 64.9z" fill="currentColor"/></svg><span class="action-tooltip">点赞</span>\`;
taskLikeBtn.onclick = () => toggleLike(taskLikeBtn);
const taskDislikeBtn = document.createElement('button');
taskDislikeBtn.className = 'action-btn';
taskDislikeBtn.innerHTML = \`<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M360 640c60.992 40.107 88 87.381 88 149.333 0 5.611 0.427 12.864 1.579 21.462a174.933 174.933 0 0 0 11.2 42.666c19.584 48.192 60.864 83.008 119.018 85.12 28.843 2.56 60.886-3.584 91.414-25.045 58.709-41.237 81.706-117.248 69.973-230.87h48.15V640v42.667c17.173 0 38.4-2.475 61.823-10.667 66.304-23.125 107.627-84.117 84.928-162.816l-53.674-254.55a213.333 213.333 0 0 0-208.747-169.3H207.019a85.333 85.333 0 0 0-85.078 78.783l-29.546 384A85.333 85.333 0 0 0 177.493 640H360z m-61.333-109.333v24H177.493l29.526-384h91.648v360z m85.333-360h289.664a128 128 0 0 1 125.227 101.589l54.442 258.07c21.334 67.007-64 67.007-64 67.007H640c64 277.334-54.613 256-54.613 256-52.054 0-52.054-64-52.054-64 0-92.8-43.264-167.082-129.77-222.805A42.667 42.667 0 0 1 384 530.667v-360z" fill="currentColor"/></svg><span class="action-tooltip">点踩</span>\`;
taskDislikeBtn.onclick = () => toggleDislike(taskDislikeBtn);
taskActionsDiv.appendChild(taskMessageContent);
taskActionsDiv.appendChild(taskCopyBtn);
taskActionsDiv.appendChild(taskLikeBtn);
taskActionsDiv.appendChild(taskDislikeBtn);
taskDiv.appendChild(taskActionsDiv);
messagesEl.appendChild(taskDiv);
messagesEl.scrollTop = messagesEl.scrollHeight;
break;
case 'taskCompleteHistory':
// 历史记录不显示任务完成提示
break;
case 'workspaceStatus':
// 更新工作区状态
if (typeof hasWorkspace !== 'undefined') {
hasWorkspace = message.hasWorkspace;
console.log('[WebView] 工作区状态:', hasWorkspace);
// 如果有待发送的示例,且工作区存在,则发送
if (hasWorkspace && typeof pendingExampleIndex !== 'undefined' && pendingExampleIndex >= 0) {
if (typeof doSendExample === 'function') {
doSendExample(pendingExampleIndex);
pendingExampleIndex = -1; // 重置
}
}
}
break;
case 'vcdInfo':
// 渲染迷你波形预览信息
try {
if (message.containerId && typeof renderWaveformInfo === 'function') {
renderWaveformInfo(message.containerId, message.vcdInfo || {});
}
} catch (e) {
console.warn('[WebView] 渲染波形信息失败:', e);
}
break;
case 'vcdGenerated':
// VCD 文件生成成功,添加消息并附带波形预览
addMessage(message.text, 'bot');
try {
if (message.vcdFilePath) {
const lastMsg = messagesEl ? messagesEl.lastElementChild : null;
if (lastMsg && typeof addWaveformPreviewToMessage === 'function') {
addWaveformPreviewToMessage(lastMsg, message.vcdFilePath, message.fileName || 'waveform.vcd');
}
}
} catch (e) {
console.warn('[WebView] 添加波形预览失败:', e);
}
break;
case 'fileContent':
// 文件内容
addMessage('文件内容:\\n' + message.content, 'bot');
break;
case 'fileError':
// 文件错误
addMessage('❌ ' + message.error, 'bot');
break;
case 'showQuestion':
// 显示用户问题
showQuestion(message.askId, message.question, message.options);
break;
case 'conversationHistory':
// 渲染会话历史列表(支持分页)
renderConversationHistory({
items: message.items || [],
total: message.total || 0,
hasMore: message.hasMore || false
});
break;
case 'clearChat':
// 清空聊天界面
const messagesContainer = document.getElementById('messages');
if (messagesContainer) {
messagesContainer.innerHTML = '';
}
// 重置输入框布局到居中
if (typeof window.resetInputAreaLayout === 'function') {
window.resetInputAreaLayout();
}
break;
case 'addUserMessage':
// 添加用户消息
if (message.text) {
addMessage(message.text, 'user');
}
// 检查并更新输入框布局
if (typeof window.checkMessagesAndUpdateLayout === 'function') {
window.checkMessagesAndUpdateLayout();
}
break;
case 'addAiMessage':
// 添加AI消息
if (message.text) {
addMessage(message.text, 'bot');
}
// 检查并更新输入框布局
if (typeof window.checkMessagesAndUpdateLayout === 'function') {
window.checkMessagesAndUpdateLayout();
}
break;
case 'switchMode':
// 切换运行模式Plan 确认后自动切换到 Agent
if (message.mode && typeof selectMode === 'function') {
const labelMap = {
'plan': 'Plan',
'ask': 'Ask',
'agent': 'Agent',
'auto': 'Auto'
};
selectMode(message.mode, labelMap[message.mode] || message.mode);
console.log('[WebView] 模式已切换到:', message.mode);
}
break;
case 'addMessage':
// 添加消息(通用)
if (message.text && message.sender) {
addMessage(message.text, message.sender);
}
break;
case 'optimizeResult':
// 处理提示词优化结果
if (typeof handleOptimizeResult === 'function') {
handleOptimizeResult(message.success, message.optimizedPrompt, message.error);
}
break;
case 'showChanges':
// 显示代码变更
if (typeof showChangesPanel === 'function') {
showChangesPanel(message.changes);
}
break;
case 'changeAccepted':
// 变更已采纳
if (typeof handleChangeAccepted === 'function') {
handleChangeAccepted(message.changeId, message.success, message.error);
}
break;
case 'changeRejected':
// 变更已拒绝
if (typeof handleChangeRejected === 'function') {
handleChangeRejected(message.changeId, message.success, message.error);
}
break;
default:
console.log('[WebView] 未处理的消息类型:', message.command);
}
});
${getMessageAreaScript()}
${getAgentCardScript()}
${getWaveformPreviewScript()}
${getConversationHistoryBarScript()}
${getProgressBarScript()}
${getInputAreaScript()}
${getInvitationModalScript()}
${getWelcomeModalScript()}
${getNdtWelcomeModalScript()}
${getExpiredModalScript()}
</script></body>
</html>`;
}