Compare commits
10 Commits
f933d84cd1
...
acf3f9ff37
| Author | SHA1 | Date | |
|---|---|---|---|
| acf3f9ff37 | |||
| c27b08cccf | |||
| 9fc3c9f056 | |||
| 60d8eaf0eb | |||
| df6f983e83 | |||
| acf60f2a17 | |||
| c2936395d9 | |||
| 8762eacb3e | |||
| 3d535fd3e1 | |||
| ecdbe0bdc0 |
BIN
src/assets/model/Auto.png
Normal file
BIN
src/assets/model/Auto.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src/assets/model/Max.png
Normal file
BIN
src/assets/model/Max.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/assets/model/Sy.png
Normal file
BIN
src/assets/model/Sy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/model/lite.png
Normal file
BIN
src/assets/model/lite.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
@ -8,7 +8,7 @@ import * as vscode from "vscode";
|
||||
type Environment = "dev" | "test" | "prod";
|
||||
|
||||
/** 当前环境 - 修改这里切换环境 */
|
||||
const CURRENT_ENV: Environment = "dev";
|
||||
const CURRENT_ENV: Environment = "test";
|
||||
|
||||
/** 配置项接口 */
|
||||
export interface IccoderConfig {
|
||||
|
||||
@ -60,7 +60,10 @@ export async function showICHelperPanel(
|
||||
{
|
||||
enableScripts: true,
|
||||
retainContextWhenHidden: true,
|
||||
localResourceRoots: [vscode.Uri.joinPath(context.extensionUri, "media")],
|
||||
localResourceRoots: [
|
||||
vscode.Uri.joinPath(context.extensionUri, "media"),
|
||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets")
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
@ -82,8 +85,28 @@ export async function showICHelperPanel(
|
||||
vscode.Uri.joinPath(context.extensionUri, "media", "icon.png")
|
||||
);
|
||||
|
||||
// 获取模型图标URI
|
||||
const autoIconUri = panel.webview.asWebviewUri(
|
||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Auto.png")
|
||||
);
|
||||
const liteIconUri = panel.webview.asWebviewUri(
|
||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "lite.png")
|
||||
);
|
||||
const syIconUri = panel.webview.asWebviewUri(
|
||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Sy.png")
|
||||
);
|
||||
const maxIconUri = panel.webview.asWebviewUri(
|
||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Max.png")
|
||||
);
|
||||
|
||||
// 设置HTML内容
|
||||
panel.webview.html = getWebviewContent(iconUri.toString());
|
||||
panel.webview.html = getWebviewContent(
|
||||
iconUri.toString(),
|
||||
autoIconUri.toString(),
|
||||
liteIconUri.toString(),
|
||||
syIconUri.toString(),
|
||||
maxIconUri.toString()
|
||||
);
|
||||
|
||||
// 处理消息
|
||||
panel.webview.onDidReceiveMessage(
|
||||
|
||||
@ -19,7 +19,7 @@ import { dialogManager, DialogSession } from "../services/dialogService";
|
||||
import { userInteractionManager } from "../services/userInteraction";
|
||||
import { healthCheck } from "../services/apiClient";
|
||||
|
||||
import type { RunMode } from '../types/api';
|
||||
import type { RunMode } from "../types/api";
|
||||
|
||||
/** 是否使用后端服务(可通过配置控制) */
|
||||
let useBackendService = true;
|
||||
@ -48,7 +48,7 @@ export function setPendingPlanExecution(
|
||||
taskId: string
|
||||
): void {
|
||||
pendingPlanExecution = { panel, planTitle, extensionPath, taskId };
|
||||
console.log('[MessageHandler] 设置待执行计划:', planTitle, 'taskId:', taskId);
|
||||
console.log("[MessageHandler] 设置待执行计划:", planTitle, "taskId:", taskId);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,29 +93,28 @@ export async function handleUserMessage(
|
||||
await handleUserMessageWithBackend(panel, text, extensionPath, mode);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error("后端服务不可用,回退到本地模式:", error);
|
||||
// 后端不可用时,使用本地模拟回复
|
||||
}
|
||||
}
|
||||
|
||||
// 本地模拟回复(后端不可用时的 fallback)
|
||||
console.log("使用本地模拟回复");
|
||||
const reply = getMockReply(text);
|
||||
|
||||
// 记录AI回复到历史(允许失败)
|
||||
try {
|
||||
const historyManager = ChatHistoryManager.getInstance();
|
||||
await historyManager.addAiMessage(reply);
|
||||
} catch (error) {
|
||||
console.warn("记录AI回复历史失败:", error);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
console.error("后端服务不可用:", error);
|
||||
panel.webview.postMessage({
|
||||
command: "receiveMessage",
|
||||
text: reply,
|
||||
command: "updateStatus",
|
||||
text: "后端服务不可用",
|
||||
type: "error",
|
||||
});
|
||||
// 恢复输入状态
|
||||
panel.webview.postMessage({
|
||||
command: "updateSegments",
|
||||
segments: [],
|
||||
isComplete: true,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有 extensionPath,显示错误
|
||||
panel.webview.postMessage({
|
||||
command: "updateStatus",
|
||||
text: "无法处理消息:缺少必要参数",
|
||||
type: "error",
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,7 +133,7 @@ async function handleUserMessageWithBackend(
|
||||
// 保存 taskId 用于后续操作(如压缩)
|
||||
lastTaskId = currentSession.getTaskId();
|
||||
if (reuseTaskId) {
|
||||
console.log('[MessageHandler] 复用 taskId 创建会话:', reuseTaskId);
|
||||
console.log("[MessageHandler] 复用 taskId 创建会话:", reuseTaskId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +147,9 @@ async function handleUserMessageWithBackend(
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
currentSession!.sendMessage(text, {
|
||||
currentSession!.sendMessage(
|
||||
text,
|
||||
{
|
||||
onText: (fullText, isStreaming) => {
|
||||
// 不再单独处理文本,统一通过 onSegmentUpdate 处理
|
||||
},
|
||||
@ -194,24 +195,27 @@ async function handleUserMessageWithBackend(
|
||||
});
|
||||
|
||||
// 最后一次发送完整的段落
|
||||
console.log('[MessageHandler] 对话完成, 段落数:', segments.length);
|
||||
console.log('[MessageHandler] segments 内容:', JSON.stringify(segments));
|
||||
console.log("[MessageHandler] 对话完成, 段落数:", segments.length);
|
||||
console.log(
|
||||
"[MessageHandler] segments 内容:",
|
||||
JSON.stringify(segments)
|
||||
);
|
||||
|
||||
const result = await panel.webview.postMessage({
|
||||
command: "updateSegments",
|
||||
segments: segments,
|
||||
isComplete: true,
|
||||
});
|
||||
console.log('[MessageHandler] postMessage 返回值:', result);
|
||||
console.log("[MessageHandler] postMessage 返回值:", result);
|
||||
|
||||
// 保存完整的 segments 到历史记录
|
||||
try {
|
||||
// 将完整的 segments 保存到一条 AI 消息中
|
||||
// 这样加载时可以完整还原对话样式
|
||||
const textContent = segments
|
||||
.filter(s => s.type === 'text' && s.content)
|
||||
.map(s => s.content)
|
||||
.join('\n');
|
||||
.filter((s) => s.type === "text" && s.content)
|
||||
.map((s) => s.content)
|
||||
.join("\n");
|
||||
|
||||
await historyManager.addAiMessage(textContent, undefined, segments);
|
||||
} catch (error) {
|
||||
@ -220,9 +224,19 @@ async function handleUserMessageWithBackend(
|
||||
|
||||
// 检查是否有待执行的计划(Plan 模式确认后自动执行)
|
||||
if (pendingPlanExecution) {
|
||||
const { panel: execPanel, planTitle, extensionPath: execPath, taskId: reuseTaskId } = pendingPlanExecution;
|
||||
const {
|
||||
panel: execPanel,
|
||||
planTitle,
|
||||
extensionPath: execPath,
|
||||
taskId: reuseTaskId,
|
||||
} = pendingPlanExecution;
|
||||
pendingPlanExecution = null;
|
||||
console.log('[MessageHandler] 自动执行计划:', planTitle, '复用 taskId:', reuseTaskId);
|
||||
console.log(
|
||||
"[MessageHandler] 自动执行计划:",
|
||||
planTitle,
|
||||
"复用 taskId:",
|
||||
reuseTaskId
|
||||
);
|
||||
|
||||
// 延迟一小段时间确保当前对话完全结束
|
||||
setTimeout(async () => {
|
||||
@ -232,11 +246,11 @@ async function handleUserMessageWithBackend(
|
||||
execPanel,
|
||||
`请按照刚才的计划执行:${planTitle}`,
|
||||
execPath,
|
||||
'agent',
|
||||
"agent",
|
||||
reuseTaskId // 复用 Plan 模式的 taskId
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('[MessageHandler] 自动执行计划失败:', err);
|
||||
console.error("[MessageHandler] 自动执行计划失败:", err);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
@ -252,6 +266,12 @@ async function handleUserMessageWithBackend(
|
||||
command: "receiveMessage",
|
||||
text: `❌ 错误: ${message}`,
|
||||
});
|
||||
// 恢复输入状态
|
||||
panel.webview.postMessage({
|
||||
command: "updateSegments",
|
||||
segments: [],
|
||||
isComplete: true,
|
||||
});
|
||||
reject(new Error(message));
|
||||
},
|
||||
|
||||
@ -262,13 +282,15 @@ async function handleUserMessageWithBackend(
|
||||
onContextUsage: (data) => {
|
||||
// 发送上下文使用量到 WebView
|
||||
panel.webview.postMessage({
|
||||
command: 'contextUsage',
|
||||
command: "contextUsage",
|
||||
currentTokens: data.currentTokens,
|
||||
maxTokens: data.maxTokens,
|
||||
percentage: data.percentage
|
||||
percentage: data.percentage,
|
||||
});
|
||||
},
|
||||
}, mode);
|
||||
},
|
||||
mode
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -296,16 +318,16 @@ export async function abortCurrentDialog(): Promise<void> {
|
||||
try {
|
||||
const historyManager = ChatHistoryManager.getInstance();
|
||||
const textContent = segments
|
||||
.filter(s => s.type === 'text' && s.content)
|
||||
.map(s => s.content)
|
||||
.join('\n');
|
||||
.filter((s) => s.type === "text" && s.content)
|
||||
.map((s) => s.content)
|
||||
.join("\n");
|
||||
|
||||
// 添加中止标记
|
||||
const abortedContent = textContent + '\n\n[对话已被用户中止]';
|
||||
const abortedContent = textContent + "\n\n[对话已被用户中止]";
|
||||
await historyManager.addAiMessage(abortedContent, undefined, segments);
|
||||
console.log('[MessageHandler] 已保存中止前的对话内容');
|
||||
console.log("[MessageHandler] 已保存中止前的对话内容");
|
||||
} catch (error) {
|
||||
console.warn('[MessageHandler] 保存中止对话失败:', error);
|
||||
console.warn("[MessageHandler] 保存中止对话失败:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -313,8 +335,8 @@ export async function abortCurrentDialog(): Promise<void> {
|
||||
// 通知 WebView 重置分段消息容器
|
||||
const panel = userInteractionManager.getWebviewPanel();
|
||||
if (panel) {
|
||||
panel.webview.postMessage({ command: 'resetSegmentedMessage' });
|
||||
console.log('[MessageHandler] 已发送重置分段消息命令');
|
||||
panel.webview.postMessage({ command: "resetSegmentedMessage" });
|
||||
console.log("[MessageHandler] 已发送重置分段消息命令");
|
||||
}
|
||||
|
||||
dialogManager.abortCurrentSession();
|
||||
@ -333,7 +355,7 @@ export function getCurrentTaskId(): string | null {
|
||||
*/
|
||||
export function setLastTaskId(taskId: string): void {
|
||||
lastTaskId = taskId;
|
||||
console.log('[MessageHandler] 设置 lastTaskId:', taskId);
|
||||
console.log("[MessageHandler] 设置 lastTaskId:", taskId);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -349,52 +371,52 @@ export async function handlePlanAction(
|
||||
planTitle: string,
|
||||
extensionPath: string
|
||||
): Promise<void> {
|
||||
console.log('[handlePlanAction] action:', action, 'planTitle:', planTitle);
|
||||
console.log("[handlePlanAction] action:", action, "planTitle:", planTitle);
|
||||
|
||||
switch (action) {
|
||||
case 'confirm':
|
||||
case "confirm":
|
||||
// 确认执行:切换到 Agent 模式并发送执行消息
|
||||
panel.webview.postMessage({
|
||||
command: 'switchMode',
|
||||
mode: 'agent'
|
||||
command: "switchMode",
|
||||
mode: "agent",
|
||||
});
|
||||
// 发送执行消息
|
||||
await handleUserMessage(
|
||||
panel,
|
||||
`请按照刚才的计划执行:${planTitle}`,
|
||||
extensionPath,
|
||||
'agent'
|
||||
"agent"
|
||||
);
|
||||
break;
|
||||
|
||||
case 'modify':
|
||||
case "modify":
|
||||
// 修改计划:提示用户输入修改建议
|
||||
const modification = await vscode.window.showInputBox({
|
||||
prompt: '请输入您对计划的修改建议',
|
||||
placeHolder: '例如:第2步需要先检查文件是否存在...',
|
||||
ignoreFocusOut: true
|
||||
prompt: "请输入您对计划的修改建议",
|
||||
placeHolder: "例如:第2步需要先检查文件是否存在...",
|
||||
ignoreFocusOut: true,
|
||||
});
|
||||
if (modification) {
|
||||
await handleUserMessage(
|
||||
panel,
|
||||
`请根据以下建议修改计划:${modification}`,
|
||||
extensionPath,
|
||||
'plan'
|
||||
"plan"
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'cancel':
|
||||
case "cancel":
|
||||
// 取消计划:通知用户
|
||||
panel.webview.postMessage({
|
||||
command: 'addMessage',
|
||||
text: '计划已取消。',
|
||||
sender: 'bot'
|
||||
command: "addMessage",
|
||||
text: "计划已取消。",
|
||||
sender: "bot",
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('[handlePlanAction] 未知操作:', action);
|
||||
console.warn("[handlePlanAction] 未知操作:", action);
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,7 +455,9 @@ function parseFileOperation(text: string): {
|
||||
}
|
||||
|
||||
// 匹配重命名文件:将 xxx.ts 重命名为 yyy.ts 或 把 xxx.ts 改名为 yyy.ts(优先匹配,避免被修改匹配)
|
||||
const renameMatch = lowerText.match(/(?:将|把)\s*(.+?\.\w+)\s*(?:重命名|改名)\s*(?:为|成)\s*(.+?\.\w+)/);
|
||||
const renameMatch = lowerText.match(
|
||||
/(?:将|把)\s*(.+?\.\w+)\s*(?:重命名|改名)\s*(?:为|成)\s*(.+?\.\w+)/
|
||||
);
|
||||
if (renameMatch) {
|
||||
const oldPath = renameMatch[1].trim();
|
||||
const newPath = renameMatch[2].trim();
|
||||
@ -448,7 +472,9 @@ function parseFileOperation(text: string): {
|
||||
// 格式1: 在 xxx.ts 中将 "aaa" 替换为 "bbb"
|
||||
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
|
||||
// 格式3: 将 xxx.ts 文件 'aaa' 替换为 'bbb'
|
||||
const replaceMatch1 = lowerText.match(/在\s*(.+?\.\w+)\s*(?:文件)?中?\s*(?:将|把)\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/);
|
||||
const replaceMatch1 = lowerText.match(
|
||||
/在\s*(.+?\.\w+)\s*(?:文件)?中?\s*(?:将|把)\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/
|
||||
);
|
||||
if (replaceMatch1) {
|
||||
const filePath = replaceMatch1[1].trim();
|
||||
const searchText = replaceMatch1[2].trim();
|
||||
@ -462,7 +488,9 @@ function parseFileOperation(text: string): {
|
||||
}
|
||||
|
||||
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
|
||||
const replaceMatch2 = lowerText.match(/(?:将|把)\s*(.+?\.\w+)\s*(?:文件)?\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/);
|
||||
const replaceMatch2 = lowerText.match(
|
||||
/(?:将|把)\s*(.+?\.\w+)\s*(?:文件)?\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/
|
||||
);
|
||||
if (replaceMatch2) {
|
||||
const filePath = replaceMatch2[1].trim();
|
||||
const searchText = replaceMatch2[2].trim();
|
||||
@ -790,41 +818,6 @@ export async function handleReplaceInFile(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模拟回复
|
||||
*/
|
||||
function getMockReply(question: string): string {
|
||||
const replies = [
|
||||
`已收到您的问题:"${question}"
|
||||
|
||||
这是一个演示版本,实际需要连接AI服务。
|
||||
|
||||
示例回复:这是一个计数器模板:
|
||||
\`\`\`verilog
|
||||
module counter (
|
||||
input clk,
|
||||
input rst_n,
|
||||
output reg [3:0] count
|
||||
);
|
||||
always @(posedge clk or negedge rst_n) begin
|
||||
if (!rst_n) count <= 0;
|
||||
else count <= count + 1;
|
||||
end
|
||||
endmodule
|
||||
\`\`\``,
|
||||
|
||||
`感谢提问!关于"${question}",在真实版本中我会:
|
||||
1. 分析您的代码上下文
|
||||
2. 提供优化建议
|
||||
3. 生成完整代码
|
||||
4. 解释设计原理
|
||||
|
||||
当前是演示版,请点击侧边栏按钮快速生成代码。`,
|
||||
];
|
||||
|
||||
return replies[Math.floor(Math.random() * replies.length)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 将代码插入到编辑器
|
||||
*/
|
||||
@ -917,7 +910,8 @@ async function handleVCDGeneration(
|
||||
|
||||
if (!projectCheck.hasTestbench) {
|
||||
errorMsg += "• ❌ 缺少 testbench 文件\n";
|
||||
errorMsg += "\n提示: testbench 文件应包含 $dumpfile 和 $dumpvars 语句来生成 VCD 文件。\n";
|
||||
errorMsg +=
|
||||
"\n提示: testbench 文件应包含 $dumpfile 和 $dumpvars 语句来生成 VCD 文件。\n";
|
||||
} else {
|
||||
errorMsg += `• ✅ Testbench: ${projectCheck.testbenchFile}\n`;
|
||||
}
|
||||
@ -961,9 +955,7 @@ async function handleVCDGeneration(
|
||||
fileName: fileName,
|
||||
});
|
||||
|
||||
vscode.window.showInformationMessage(
|
||||
`VCD 文件生成成功: ${fileName}`
|
||||
);
|
||||
vscode.window.showInformationMessage(`VCD 文件生成成功: ${fileName}`);
|
||||
} else {
|
||||
panel.webview.postMessage({
|
||||
command: "receiveMessage",
|
||||
|
||||
@ -24,7 +24,10 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
|
||||
{
|
||||
enableScripts: true,
|
||||
retainContextWhenHidden: true,
|
||||
localResourceRoots: [vscode.Uri.joinPath(context.extensionUri, "media")],
|
||||
localResourceRoots: [
|
||||
vscode.Uri.joinPath(context.extensionUri, "media"),
|
||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets")
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
@ -39,8 +42,29 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
|
||||
const iconUri = panel.webview.asWebviewUri(
|
||||
vscode.Uri.joinPath(context.extensionUri, "media", "icon.png")
|
||||
);
|
||||
|
||||
// 获取模型图标URI
|
||||
const autoIconUri = panel.webview.asWebviewUri(
|
||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Auto.png")
|
||||
);
|
||||
const liteIconUri = panel.webview.asWebviewUri(
|
||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "lite.png")
|
||||
);
|
||||
const syIconUri = panel.webview.asWebviewUri(
|
||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Sy.png")
|
||||
);
|
||||
const maxIconUri = panel.webview.asWebviewUri(
|
||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Max.png")
|
||||
);
|
||||
|
||||
// 设置HTML内容
|
||||
panel.webview.html = getWebviewContent(iconUri.toString());
|
||||
panel.webview.html = getWebviewContent(
|
||||
iconUri.toString(),
|
||||
autoIconUri.toString(),
|
||||
liteIconUri.toString(),
|
||||
syIconUri.toString(),
|
||||
maxIconUri.toString()
|
||||
);
|
||||
|
||||
// 处理消息
|
||||
panel.webview.onDidReceiveMessage(
|
||||
|
||||
@ -29,7 +29,12 @@ import { sendIconSvg, stopIconSvg } from "../constants/toolIcons";
|
||||
/**
|
||||
* 获取输入区域的 HTML 内容
|
||||
*/
|
||||
export function getInputAreaContent(): string {
|
||||
export function getInputAreaContent(
|
||||
autoIcon: string = '',
|
||||
liteIcon: string = '',
|
||||
syIcon: string = '',
|
||||
maxIcon: string = ''
|
||||
): string {
|
||||
return `
|
||||
<div class="input-area">
|
||||
<div class="input-group">
|
||||
@ -40,13 +45,13 @@ export function getInputAreaContent(): string {
|
||||
</div>
|
||||
<textarea
|
||||
id="messageInput"
|
||||
placeholder="输入您的问题..."
|
||||
placeholder="输入您的问题,按 Enter 发送,Shift + Enter 换行..."
|
||||
onkeydown="if(event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); sendMessage(); }"
|
||||
></textarea>
|
||||
<div class="input-bottom-row">
|
||||
<div class="mode-selector">
|
||||
${getModeSelectorContent()}
|
||||
${getModelSelectorContent()}
|
||||
${getModelSelectorContent(autoIcon, liteIcon, syIcon, maxIcon)}
|
||||
</div>
|
||||
<div class="input-actions">
|
||||
${getContextCompressContent()}
|
||||
@ -197,6 +202,11 @@ export function getInputAreaStyles(): string {
|
||||
overflow-y: auto;
|
||||
line-height: 1.5;
|
||||
}
|
||||
textarea:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background: rgba(128, 128, 128, 0.1);
|
||||
}
|
||||
/* 简洁的滚动条样式 */
|
||||
textarea::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
@ -300,11 +310,17 @@ export function getInputAreaScript(): string {
|
||||
sendIconContainer.style.display = 'none';
|
||||
stopIconContainer.style.display = 'block';
|
||||
isConversationActive = true;
|
||||
// 禁用输入框
|
||||
messageInput.disabled = true;
|
||||
messageInput.placeholder = '正在处理中,请稍候...';
|
||||
} else {
|
||||
sendButton.classList.remove('sending');
|
||||
sendIconContainer.style.display = 'block';
|
||||
stopIconContainer.style.display = 'none';
|
||||
isConversationActive = false;
|
||||
// 启用输入框
|
||||
messageInput.disabled = false;
|
||||
messageInput.placeholder = '输入您的问题,按 Enter 发送,Shift + Enter 换行...';
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,6 +340,11 @@ export function getInputAreaScript(): string {
|
||||
const text = messageInput.value.trim();
|
||||
if (!text) return;
|
||||
|
||||
// 如果正在对话中,阻止发送新消息
|
||||
if (isConversationActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查工作区状态
|
||||
if (!hasWorkspace) {
|
||||
// 如果没有工作区,阻止发送并清空输入框
|
||||
|
||||
@ -1097,9 +1097,11 @@ export function getMessageAreaScript(): string {
|
||||
console.log('[WebView] 对话完成,添加操作按钮');
|
||||
const actionsDiv = document.createElement('div');
|
||||
actionsDiv.className = 'message-actions';
|
||||
|
||||
// 复制按钮
|
||||
const copyBtn = document.createElement('button');
|
||||
copyBtn.className = 'action-btn';
|
||||
copyBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
||||
copyBtn.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>\`;
|
||||
copyBtn.onclick = () => {
|
||||
const textContent = segments
|
||||
.filter(s => s.type === 'text' && s.content)
|
||||
@ -1107,7 +1109,22 @@ export function getMessageAreaScript(): string {
|
||||
.join('\\n');
|
||||
copyMessage(textContent, copyBtn);
|
||||
};
|
||||
|
||||
// 点赞按钮
|
||||
const likeBtn = document.createElement('button');
|
||||
likeBtn.className = 'action-btn';
|
||||
likeBtn.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>\`;
|
||||
likeBtn.onclick = () => toggleLike(likeBtn);
|
||||
|
||||
// 点踩按钮
|
||||
const dislikeBtn = document.createElement('button');
|
||||
dislikeBtn.className = 'action-btn';
|
||||
dislikeBtn.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>\`;
|
||||
dislikeBtn.onclick = () => toggleDislike(dislikeBtn);
|
||||
|
||||
actionsDiv.appendChild(copyBtn);
|
||||
actionsDiv.appendChild(likeBtn);
|
||||
actionsDiv.appendChild(dislikeBtn);
|
||||
currentSegmentedMessage.appendChild(actionsDiv);
|
||||
|
||||
// 重置当前分段消息容器
|
||||
@ -1241,9 +1258,11 @@ export function getMessageAreaScript(): string {
|
||||
// 添加操作按钮
|
||||
const actionsDiv = document.createElement('div');
|
||||
actionsDiv.className = 'message-actions';
|
||||
|
||||
// 复制按钮
|
||||
const copyBtn = document.createElement('button');
|
||||
copyBtn.className = 'action-btn';
|
||||
copyBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
||||
copyBtn.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>\`;
|
||||
copyBtn.onclick = () => {
|
||||
const textContent = segments
|
||||
.filter(s => s.type === 'text' && s.content)
|
||||
@ -1251,7 +1270,22 @@ export function getMessageAreaScript(): string {
|
||||
.join('\\n');
|
||||
copyMessage(textContent, copyBtn);
|
||||
};
|
||||
|
||||
// 点赞按钮
|
||||
const likeBtn = document.createElement('button');
|
||||
likeBtn.className = 'action-btn';
|
||||
likeBtn.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>\`;
|
||||
likeBtn.onclick = () => toggleLike(likeBtn);
|
||||
|
||||
// 点踩按钮
|
||||
const dislikeBtn = document.createElement('button');
|
||||
dislikeBtn.className = 'action-btn';
|
||||
dislikeBtn.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>\`;
|
||||
dislikeBtn.onclick = () => toggleDislike(dislikeBtn);
|
||||
|
||||
actionsDiv.appendChild(copyBtn);
|
||||
actionsDiv.appendChild(likeBtn);
|
||||
actionsDiv.appendChild(dislikeBtn);
|
||||
container.appendChild(actionsDiv);
|
||||
|
||||
messagesEl.appendChild(container);
|
||||
|
||||
@ -5,7 +5,12 @@
|
||||
/**
|
||||
* 获取模型选择器的 HTML 内容
|
||||
*/
|
||||
export function getModelSelectorContent(): string {
|
||||
export function getModelSelectorContent(
|
||||
autoIcon: string = '',
|
||||
liteIcon: string = '',
|
||||
syIcon: string = '',
|
||||
maxIcon: string = ''
|
||||
): string {
|
||||
return `
|
||||
<!-- 模型选择 -->
|
||||
<div class="tooltip">
|
||||
@ -17,10 +22,22 @@ export function getModelSelectorContent(): string {
|
||||
</svg>
|
||||
</div>
|
||||
<div class="select-dropdown" id="modelDropdown">
|
||||
<div class="select-option" data-value="lite" data-tooltip="快速响应,适合简单任务" onclick="selectModel('lite', 'Lite')">Lite</div>
|
||||
<div class="select-option selected" data-value="auto" data-tooltip="自动选择最佳模型" onclick="selectModel('auto', 'Auto')">Auto</div>
|
||||
<div class="select-option" data-value="syntaxic" data-tooltip="语法分析和代码理解" onclick="selectModel('syntaxic', 'Syntaxic')">Syntaxic</div>
|
||||
<div class="select-option" data-value="max" data-tooltip="最强性能,复杂任务" onclick="selectModel('max', 'Max')">Max</div>
|
||||
<div class="select-option selected" data-value="auto" data-tooltip="自动选择最佳模型" onclick="selectModel('auto', 'Auto')">
|
||||
${autoIcon ? `<img src="${autoIcon}" class="model-icon" alt="Auto">` : ''}
|
||||
<span class="option-label">Auto</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="lite" data-tooltip="快速响应,适合简单任务" onclick="selectModel('lite', 'Lite')">
|
||||
${liteIcon ? `<img src="${liteIcon}" class="model-icon" alt="Lite">` : ''}
|
||||
<span class="option-label">Lite</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="syntaxic" data-tooltip="语法分析和代码理解" onclick="selectModel('syntaxic', 'Syntaxic')">
|
||||
${syIcon ? `<img src="${syIcon}" class="model-icon" alt="Syntaxic">` : ''}
|
||||
<span class="option-label">Syntaxic</span>
|
||||
</div>
|
||||
<div class="select-option" data-value="max" data-tooltip="最强性能,复杂任务" onclick="selectModel('max', 'Max')">
|
||||
${maxIcon ? `<img src="${maxIcon}" class="model-icon" alt="Max">` : ''}
|
||||
<span class="option-label">Max</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 模型选择器的 tooltip 容器 -->
|
||||
<div id="modelTooltip" class="model-tooltip"></div>
|
||||
@ -88,10 +105,12 @@ export function getModelSelectorStyles(): string {
|
||||
#modelDropdown .select-option {
|
||||
position: relative;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
#modelDropdown .select-option:hover {
|
||||
background: rgba(128, 128, 128, 0.3);
|
||||
@ -100,6 +119,17 @@ export function getModelSelectorStyles(): string {
|
||||
background: rgba(128, 128, 128, 0.5);
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
.model-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
object-fit: contain;
|
||||
}
|
||||
.option-label {
|
||||
font-size: 12px;
|
||||
color: var(--vscode-foreground);
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* 模型选择器的 tooltip 样式 */
|
||||
.model-tooltip {
|
||||
position: fixed;
|
||||
|
||||
@ -17,14 +17,17 @@ import {
|
||||
getMessageAreaStyles,
|
||||
getMessageAreaScript,
|
||||
} from "./messageArea";
|
||||
import {
|
||||
getAgentCardStyles,
|
||||
getAgentCardScript,
|
||||
} from "./agentCard";
|
||||
import { getAgentCardStyles, getAgentCardScript } from "./agentCard";
|
||||
/**
|
||||
* 获取 WebView 面板的 HTML 内容
|
||||
*/
|
||||
export function getWebviewContent(iconUri?: string): string {
|
||||
export function getWebviewContent(
|
||||
iconUri?: string,
|
||||
autoIconUri?: string,
|
||||
liteIconUri?: string,
|
||||
syIconUri?: string,
|
||||
maxIconUri?: string
|
||||
): string {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
@ -394,14 +397,14 @@ export function getWebviewContent(iconUri?: string): string {
|
||||
<span id="statusText">思考中...</span>
|
||||
</div>
|
||||
|
||||
<div class="quick-actions">
|
||||
<!-- <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>
|
||||
</div> -->
|
||||
|
||||
${getInputAreaContent()}
|
||||
${getInputAreaContent(autoIconUri, liteIconUri, syIconUri, maxIconUri)}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
Reference in New Issue
Block a user