992 lines
33 KiB
TypeScript
992 lines
33 KiB
TypeScript
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>`;
|
||
}
|