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";
|
type Environment = "dev" | "test" | "prod";
|
||||||
|
|
||||||
/** 当前环境 - 修改这里切换环境 */
|
/** 当前环境 - 修改这里切换环境 */
|
||||||
const CURRENT_ENV: Environment = "dev";
|
const CURRENT_ENV: Environment = "test";
|
||||||
|
|
||||||
/** 配置项接口 */
|
/** 配置项接口 */
|
||||||
export interface IccoderConfig {
|
export interface IccoderConfig {
|
||||||
@ -36,7 +36,7 @@ const ENV_CONFIG: Record<Environment, IccoderConfig> = {
|
|||||||
},
|
},
|
||||||
/** 生产环境 */
|
/** 生产环境 */
|
||||||
prod: {
|
prod: {
|
||||||
backendUrl: "https://api.iccoder.com", // TODO: 替换为实际生产地址
|
backendUrl: "https://api.iccoder.com", // TODO: 替换为实际生产地址
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
userId: "default-user",
|
userId: "default-user",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -60,7 +60,10 @@ export async function showICHelperPanel(
|
|||||||
{
|
{
|
||||||
enableScripts: true,
|
enableScripts: true,
|
||||||
retainContextWhenHidden: 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")
|
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内容
|
// 设置HTML内容
|
||||||
panel.webview.html = getWebviewContent(iconUri.toString());
|
panel.webview.html = getWebviewContent(
|
||||||
|
iconUri.toString(),
|
||||||
|
autoIconUri.toString(),
|
||||||
|
liteIconUri.toString(),
|
||||||
|
syIconUri.toString(),
|
||||||
|
maxIconUri.toString()
|
||||||
|
);
|
||||||
|
|
||||||
// 处理消息
|
// 处理消息
|
||||||
panel.webview.onDidReceiveMessage(
|
panel.webview.onDidReceiveMessage(
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { dialogManager, DialogSession } from "../services/dialogService";
|
|||||||
import { userInteractionManager } from "../services/userInteraction";
|
import { userInteractionManager } from "../services/userInteraction";
|
||||||
import { healthCheck } from "../services/apiClient";
|
import { healthCheck } from "../services/apiClient";
|
||||||
|
|
||||||
import type { RunMode } from '../types/api';
|
import type { RunMode } from "../types/api";
|
||||||
|
|
||||||
/** 是否使用后端服务(可通过配置控制) */
|
/** 是否使用后端服务(可通过配置控制) */
|
||||||
let useBackendService = true;
|
let useBackendService = true;
|
||||||
@ -35,7 +35,7 @@ let pendingPlanExecution: {
|
|||||||
panel: vscode.WebviewPanel;
|
panel: vscode.WebviewPanel;
|
||||||
planTitle: string;
|
planTitle: string;
|
||||||
extensionPath: string;
|
extensionPath: string;
|
||||||
taskId: string; // 保存 taskId 以便复用
|
taskId: string; // 保存 taskId 以便复用
|
||||||
} | null = null;
|
} | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,7 +48,7 @@ export function setPendingPlanExecution(
|
|||||||
taskId: string
|
taskId: string
|
||||||
): void {
|
): void {
|
||||||
pendingPlanExecution = { panel, planTitle, extensionPath, taskId };
|
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);
|
await handleUserMessageWithBackend(panel, text, extensionPath, mode);
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("后端服务不可用,回退到本地模式:", error);
|
console.error("后端服务不可用:", error);
|
||||||
// 后端不可用时,使用本地模拟回复
|
panel.webview.postMessage({
|
||||||
|
command: "updateStatus",
|
||||||
|
text: "后端服务不可用",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
// 恢复输入状态
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "updateSegments",
|
||||||
|
segments: [],
|
||||||
|
isComplete: true,
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 本地模拟回复(后端不可用时的 fallback)
|
// 如果没有 extensionPath,显示错误
|
||||||
console.log("使用本地模拟回复");
|
panel.webview.postMessage({
|
||||||
const reply = getMockReply(text);
|
command: "updateStatus",
|
||||||
|
text: "无法处理消息:缺少必要参数",
|
||||||
// 记录AI回复到历史(允许失败)
|
type: "error",
|
||||||
try {
|
});
|
||||||
const historyManager = ChatHistoryManager.getInstance();
|
|
||||||
await historyManager.addAiMessage(reply);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("记录AI回复历史失败:", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
panel.webview.postMessage({
|
|
||||||
command: "receiveMessage",
|
|
||||||
text: reply,
|
|
||||||
});
|
|
||||||
}, 500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -126,7 +125,7 @@ async function handleUserMessageWithBackend(
|
|||||||
text: string,
|
text: string,
|
||||||
extensionPath: string,
|
extensionPath: string,
|
||||||
mode?: RunMode,
|
mode?: RunMode,
|
||||||
reuseTaskId?: string // 可选,复用现有 taskId(用于 Plan 模式确认后继续执行)
|
reuseTaskId?: string // 可选,复用现有 taskId(用于 Plan 模式确认后继续执行)
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// 创建或复用会话
|
// 创建或复用会话
|
||||||
if (!currentSession || !currentSession.active) {
|
if (!currentSession || !currentSession.active) {
|
||||||
@ -134,7 +133,7 @@ async function handleUserMessageWithBackend(
|
|||||||
// 保存 taskId 用于后续操作(如压缩)
|
// 保存 taskId 用于后续操作(如压缩)
|
||||||
lastTaskId = currentSession.getTaskId();
|
lastTaskId = currentSession.getTaskId();
|
||||||
if (reuseTaskId) {
|
if (reuseTaskId) {
|
||||||
console.log('[MessageHandler] 复用 taskId 创建会话:', reuseTaskId);
|
console.log("[MessageHandler] 复用 taskId 创建会话:", reuseTaskId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,127 +147,150 @@ async function handleUserMessageWithBackend(
|
|||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
currentSession!.sendMessage(text, {
|
currentSession!.sendMessage(
|
||||||
onText: (fullText, isStreaming) => {
|
text,
|
||||||
// 不再单独处理文本,统一通过 onSegmentUpdate 处理
|
{
|
||||||
|
onText: (fullText, isStreaming) => {
|
||||||
|
// 不再单独处理文本,统一通过 onSegmentUpdate 处理
|
||||||
|
},
|
||||||
|
|
||||||
|
onSegmentUpdate: (segments) => {
|
||||||
|
// 实时发送段落更新,按后端返回顺序展示
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "updateSegments",
|
||||||
|
segments: segments,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onToolStart: (toolName) => {
|
||||||
|
// 更新状态栏
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "updateStatus",
|
||||||
|
text: `正在执行 ${toolName}...`,
|
||||||
|
type: "working",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onToolComplete: (toolName, result) => {
|
||||||
|
// 工具完成,不需要单独处理,通过 onSegmentUpdate 统一更新
|
||||||
|
},
|
||||||
|
|
||||||
|
onToolError: (toolName, error) => {
|
||||||
|
// 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新
|
||||||
|
},
|
||||||
|
|
||||||
|
onQuestion: (askId, question, options) => {
|
||||||
|
// 只更新状态栏,问题显示由 onSegmentUpdate 统一处理
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "updateStatus",
|
||||||
|
text: "等待用户回答...",
|
||||||
|
type: "working",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onComplete: async (segments) => {
|
||||||
|
// 隐藏状态栏
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "hideStatus",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 最后一次发送完整的段落
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 保存完整的 segments 到历史记录
|
||||||
|
try {
|
||||||
|
// 将完整的 segments 保存到一条 AI 消息中
|
||||||
|
// 这样加载时可以完整还原对话样式
|
||||||
|
const textContent = segments
|
||||||
|
.filter((s) => s.type === "text" && s.content)
|
||||||
|
.map((s) => s.content)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
await historyManager.addAiMessage(textContent, undefined, segments);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("保存AI响应历史失败:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有待执行的计划(Plan 模式确认后自动执行)
|
||||||
|
if (pendingPlanExecution) {
|
||||||
|
const {
|
||||||
|
panel: execPanel,
|
||||||
|
planTitle,
|
||||||
|
extensionPath: execPath,
|
||||||
|
taskId: reuseTaskId,
|
||||||
|
} = pendingPlanExecution;
|
||||||
|
pendingPlanExecution = null;
|
||||||
|
console.log(
|
||||||
|
"[MessageHandler] 自动执行计划:",
|
||||||
|
planTitle,
|
||||||
|
"复用 taskId:",
|
||||||
|
reuseTaskId
|
||||||
|
);
|
||||||
|
|
||||||
|
// 延迟一小段时间确保当前对话完全结束
|
||||||
|
setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
// 复用 taskId 创建新会话,确保知识图谱数据不丢失
|
||||||
|
await handleUserMessageWithBackend(
|
||||||
|
execPanel,
|
||||||
|
`请按照刚才的计划执行:${planTitle}`,
|
||||||
|
execPath,
|
||||||
|
"agent",
|
||||||
|
reuseTaskId // 复用 Plan 模式的 taskId
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[MessageHandler] 自动执行计划失败:", err);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
|
||||||
|
onError: (message) => {
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "hideLoading",
|
||||||
|
});
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "receiveMessage",
|
||||||
|
text: `❌ 错误: ${message}`,
|
||||||
|
});
|
||||||
|
// 恢复输入状态
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "updateSegments",
|
||||||
|
segments: [],
|
||||||
|
isComplete: true,
|
||||||
|
});
|
||||||
|
reject(new Error(message));
|
||||||
|
},
|
||||||
|
|
||||||
|
onNotification: (message) => {
|
||||||
|
vscode.window.showInformationMessage(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
onContextUsage: (data) => {
|
||||||
|
// 发送上下文使用量到 WebView
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "contextUsage",
|
||||||
|
currentTokens: data.currentTokens,
|
||||||
|
maxTokens: data.maxTokens,
|
||||||
|
percentage: data.percentage,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
mode
|
||||||
onSegmentUpdate: (segments) => {
|
);
|
||||||
// 实时发送段落更新,按后端返回顺序展示
|
|
||||||
panel.webview.postMessage({
|
|
||||||
command: "updateSegments",
|
|
||||||
segments: segments,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onToolStart: (toolName) => {
|
|
||||||
// 更新状态栏
|
|
||||||
panel.webview.postMessage({
|
|
||||||
command: "updateStatus",
|
|
||||||
text: `正在执行 ${toolName}...`,
|
|
||||||
type: "working",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onToolComplete: (toolName, result) => {
|
|
||||||
// 工具完成,不需要单独处理,通过 onSegmentUpdate 统一更新
|
|
||||||
},
|
|
||||||
|
|
||||||
onToolError: (toolName, error) => {
|
|
||||||
// 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新
|
|
||||||
},
|
|
||||||
|
|
||||||
onQuestion: (askId, question, options) => {
|
|
||||||
// 只更新状态栏,问题显示由 onSegmentUpdate 统一处理
|
|
||||||
panel.webview.postMessage({
|
|
||||||
command: "updateStatus",
|
|
||||||
text: "等待用户回答...",
|
|
||||||
type: "working",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onComplete: async (segments) => {
|
|
||||||
// 隐藏状态栏
|
|
||||||
panel.webview.postMessage({
|
|
||||||
command: "hideStatus",
|
|
||||||
});
|
|
||||||
|
|
||||||
// 最后一次发送完整的段落
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 保存完整的 segments 到历史记录
|
|
||||||
try {
|
|
||||||
// 将完整的 segments 保存到一条 AI 消息中
|
|
||||||
// 这样加载时可以完整还原对话样式
|
|
||||||
const textContent = segments
|
|
||||||
.filter(s => s.type === 'text' && s.content)
|
|
||||||
.map(s => s.content)
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
await historyManager.addAiMessage(textContent, undefined, segments);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn("保存AI响应历史失败:", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否有待执行的计划(Plan 模式确认后自动执行)
|
|
||||||
if (pendingPlanExecution) {
|
|
||||||
const { panel: execPanel, planTitle, extensionPath: execPath, taskId: reuseTaskId } = pendingPlanExecution;
|
|
||||||
pendingPlanExecution = null;
|
|
||||||
console.log('[MessageHandler] 自动执行计划:', planTitle, '复用 taskId:', reuseTaskId);
|
|
||||||
|
|
||||||
// 延迟一小段时间确保当前对话完全结束
|
|
||||||
setTimeout(async () => {
|
|
||||||
try {
|
|
||||||
// 复用 taskId 创建新会话,确保知识图谱数据不丢失
|
|
||||||
await handleUserMessageWithBackend(
|
|
||||||
execPanel,
|
|
||||||
`请按照刚才的计划执行:${planTitle}`,
|
|
||||||
execPath,
|
|
||||||
'agent',
|
|
||||||
reuseTaskId // 复用 Plan 模式的 taskId
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[MessageHandler] 自动执行计划失败:', err);
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
},
|
|
||||||
|
|
||||||
onError: (message) => {
|
|
||||||
panel.webview.postMessage({
|
|
||||||
command: "hideLoading",
|
|
||||||
});
|
|
||||||
panel.webview.postMessage({
|
|
||||||
command: "receiveMessage",
|
|
||||||
text: `❌ 错误: ${message}`,
|
|
||||||
});
|
|
||||||
reject(new Error(message));
|
|
||||||
},
|
|
||||||
|
|
||||||
onNotification: (message) => {
|
|
||||||
vscode.window.showInformationMessage(message);
|
|
||||||
},
|
|
||||||
|
|
||||||
onContextUsage: (data) => {
|
|
||||||
// 发送上下文使用量到 WebView
|
|
||||||
panel.webview.postMessage({
|
|
||||||
command: 'contextUsage',
|
|
||||||
currentTokens: data.currentTokens,
|
|
||||||
maxTokens: data.maxTokens,
|
|
||||||
percentage: data.percentage
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}, mode);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,16 +318,16 @@ export async function abortCurrentDialog(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
const historyManager = ChatHistoryManager.getInstance();
|
const historyManager = ChatHistoryManager.getInstance();
|
||||||
const textContent = segments
|
const textContent = segments
|
||||||
.filter(s => s.type === 'text' && s.content)
|
.filter((s) => s.type === "text" && s.content)
|
||||||
.map(s => s.content)
|
.map((s) => s.content)
|
||||||
.join('\n');
|
.join("\n");
|
||||||
|
|
||||||
// 添加中止标记
|
// 添加中止标记
|
||||||
const abortedContent = textContent + '\n\n[对话已被用户中止]';
|
const abortedContent = textContent + "\n\n[对话已被用户中止]";
|
||||||
await historyManager.addAiMessage(abortedContent, undefined, segments);
|
await historyManager.addAiMessage(abortedContent, undefined, segments);
|
||||||
console.log('[MessageHandler] 已保存中止前的对话内容');
|
console.log("[MessageHandler] 已保存中止前的对话内容");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[MessageHandler] 保存中止对话失败:', error);
|
console.warn("[MessageHandler] 保存中止对话失败:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -313,8 +335,8 @@ export async function abortCurrentDialog(): Promise<void> {
|
|||||||
// 通知 WebView 重置分段消息容器
|
// 通知 WebView 重置分段消息容器
|
||||||
const panel = userInteractionManager.getWebviewPanel();
|
const panel = userInteractionManager.getWebviewPanel();
|
||||||
if (panel) {
|
if (panel) {
|
||||||
panel.webview.postMessage({ command: 'resetSegmentedMessage' });
|
panel.webview.postMessage({ command: "resetSegmentedMessage" });
|
||||||
console.log('[MessageHandler] 已发送重置分段消息命令');
|
console.log("[MessageHandler] 已发送重置分段消息命令");
|
||||||
}
|
}
|
||||||
|
|
||||||
dialogManager.abortCurrentSession();
|
dialogManager.abortCurrentSession();
|
||||||
@ -333,7 +355,7 @@ export function getCurrentTaskId(): string | null {
|
|||||||
*/
|
*/
|
||||||
export function setLastTaskId(taskId: string): void {
|
export function setLastTaskId(taskId: string): void {
|
||||||
lastTaskId = taskId;
|
lastTaskId = taskId;
|
||||||
console.log('[MessageHandler] 设置 lastTaskId:', taskId);
|
console.log("[MessageHandler] 设置 lastTaskId:", taskId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -349,52 +371,52 @@ export async function handlePlanAction(
|
|||||||
planTitle: string,
|
planTitle: string,
|
||||||
extensionPath: string
|
extensionPath: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
console.log('[handlePlanAction] action:', action, 'planTitle:', planTitle);
|
console.log("[handlePlanAction] action:", action, "planTitle:", planTitle);
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'confirm':
|
case "confirm":
|
||||||
// 确认执行:切换到 Agent 模式并发送执行消息
|
// 确认执行:切换到 Agent 模式并发送执行消息
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: 'switchMode',
|
command: "switchMode",
|
||||||
mode: 'agent'
|
mode: "agent",
|
||||||
});
|
});
|
||||||
// 发送执行消息
|
// 发送执行消息
|
||||||
await handleUserMessage(
|
await handleUserMessage(
|
||||||
panel,
|
panel,
|
||||||
`请按照刚才的计划执行:${planTitle}`,
|
`请按照刚才的计划执行:${planTitle}`,
|
||||||
extensionPath,
|
extensionPath,
|
||||||
'agent'
|
"agent"
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'modify':
|
case "modify":
|
||||||
// 修改计划:提示用户输入修改建议
|
// 修改计划:提示用户输入修改建议
|
||||||
const modification = await vscode.window.showInputBox({
|
const modification = await vscode.window.showInputBox({
|
||||||
prompt: '请输入您对计划的修改建议',
|
prompt: "请输入您对计划的修改建议",
|
||||||
placeHolder: '例如:第2步需要先检查文件是否存在...',
|
placeHolder: "例如:第2步需要先检查文件是否存在...",
|
||||||
ignoreFocusOut: true
|
ignoreFocusOut: true,
|
||||||
});
|
});
|
||||||
if (modification) {
|
if (modification) {
|
||||||
await handleUserMessage(
|
await handleUserMessage(
|
||||||
panel,
|
panel,
|
||||||
`请根据以下建议修改计划:${modification}`,
|
`请根据以下建议修改计划:${modification}`,
|
||||||
extensionPath,
|
extensionPath,
|
||||||
'plan'
|
"plan"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'cancel':
|
case "cancel":
|
||||||
// 取消计划:通知用户
|
// 取消计划:通知用户
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: 'addMessage',
|
command: "addMessage",
|
||||||
text: '计划已取消。',
|
text: "计划已取消。",
|
||||||
sender: 'bot'
|
sender: "bot",
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
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(优先匹配,避免被修改匹配)
|
// 匹配重命名文件:将 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) {
|
if (renameMatch) {
|
||||||
const oldPath = renameMatch[1].trim();
|
const oldPath = renameMatch[1].trim();
|
||||||
const newPath = renameMatch[2].trim();
|
const newPath = renameMatch[2].trim();
|
||||||
@ -448,7 +472,9 @@ function parseFileOperation(text: string): {
|
|||||||
// 格式1: 在 xxx.ts 中将 "aaa" 替换为 "bbb"
|
// 格式1: 在 xxx.ts 中将 "aaa" 替换为 "bbb"
|
||||||
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
|
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
|
||||||
// 格式3: 将 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) {
|
if (replaceMatch1) {
|
||||||
const filePath = replaceMatch1[1].trim();
|
const filePath = replaceMatch1[1].trim();
|
||||||
const searchText = replaceMatch1[2].trim();
|
const searchText = replaceMatch1[2].trim();
|
||||||
@ -462,7 +488,9 @@ function parseFileOperation(text: string): {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
|
// 格式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) {
|
if (replaceMatch2) {
|
||||||
const filePath = replaceMatch2[1].trim();
|
const filePath = replaceMatch2[1].trim();
|
||||||
const searchText = replaceMatch2[2].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) {
|
if (!projectCheck.hasTestbench) {
|
||||||
errorMsg += "• ❌ 缺少 testbench 文件\n";
|
errorMsg += "• ❌ 缺少 testbench 文件\n";
|
||||||
errorMsg += "\n提示: testbench 文件应包含 $dumpfile 和 $dumpvars 语句来生成 VCD 文件。\n";
|
errorMsg +=
|
||||||
|
"\n提示: testbench 文件应包含 $dumpfile 和 $dumpvars 语句来生成 VCD 文件。\n";
|
||||||
} else {
|
} else {
|
||||||
errorMsg += `• ✅ Testbench: ${projectCheck.testbenchFile}\n`;
|
errorMsg += `• ✅ Testbench: ${projectCheck.testbenchFile}\n`;
|
||||||
}
|
}
|
||||||
@ -961,9 +955,7 @@ async function handleVCDGeneration(
|
|||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
});
|
});
|
||||||
|
|
||||||
vscode.window.showInformationMessage(
|
vscode.window.showInformationMessage(`VCD 文件生成成功: ${fileName}`);
|
||||||
`VCD 文件生成成功: ${fileName}`
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
panel.webview.postMessage({
|
panel.webview.postMessage({
|
||||||
command: "receiveMessage",
|
command: "receiveMessage",
|
||||||
|
|||||||
@ -24,7 +24,10 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
|
|||||||
{
|
{
|
||||||
enableScripts: true,
|
enableScripts: true,
|
||||||
retainContextWhenHidden: 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(
|
const iconUri = panel.webview.asWebviewUri(
|
||||||
vscode.Uri.joinPath(context.extensionUri, "media", "icon.png")
|
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内容
|
// 设置HTML内容
|
||||||
panel.webview.html = getWebviewContent(iconUri.toString());
|
panel.webview.html = getWebviewContent(
|
||||||
|
iconUri.toString(),
|
||||||
|
autoIconUri.toString(),
|
||||||
|
liteIconUri.toString(),
|
||||||
|
syIconUri.toString(),
|
||||||
|
maxIconUri.toString()
|
||||||
|
);
|
||||||
|
|
||||||
// 处理消息
|
// 处理消息
|
||||||
panel.webview.onDidReceiveMessage(
|
panel.webview.onDidReceiveMessage(
|
||||||
|
|||||||
@ -29,7 +29,12 @@ import { sendIconSvg, stopIconSvg } from "../constants/toolIcons";
|
|||||||
/**
|
/**
|
||||||
* 获取输入区域的 HTML 内容
|
* 获取输入区域的 HTML 内容
|
||||||
*/
|
*/
|
||||||
export function getInputAreaContent(): string {
|
export function getInputAreaContent(
|
||||||
|
autoIcon: string = '',
|
||||||
|
liteIcon: string = '',
|
||||||
|
syIcon: string = '',
|
||||||
|
maxIcon: string = ''
|
||||||
|
): string {
|
||||||
return `
|
return `
|
||||||
<div class="input-area">
|
<div class="input-area">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@ -40,13 +45,13 @@ export function getInputAreaContent(): string {
|
|||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
id="messageInput"
|
id="messageInput"
|
||||||
placeholder="输入您的问题..."
|
placeholder="输入您的问题,按 Enter 发送,Shift + Enter 换行..."
|
||||||
onkeydown="if(event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); sendMessage(); }"
|
onkeydown="if(event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); sendMessage(); }"
|
||||||
></textarea>
|
></textarea>
|
||||||
<div class="input-bottom-row">
|
<div class="input-bottom-row">
|
||||||
<div class="mode-selector">
|
<div class="mode-selector">
|
||||||
${getModeSelectorContent()}
|
${getModeSelectorContent()}
|
||||||
${getModelSelectorContent()}
|
${getModelSelectorContent(autoIcon, liteIcon, syIcon, maxIcon)}
|
||||||
</div>
|
</div>
|
||||||
<div class="input-actions">
|
<div class="input-actions">
|
||||||
${getContextCompressContent()}
|
${getContextCompressContent()}
|
||||||
@ -197,6 +202,11 @@ export function getInputAreaStyles(): string {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
textarea:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: rgba(128, 128, 128, 0.1);
|
||||||
|
}
|
||||||
/* 简洁的滚动条样式 */
|
/* 简洁的滚动条样式 */
|
||||||
textarea::-webkit-scrollbar {
|
textarea::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
@ -300,11 +310,17 @@ export function getInputAreaScript(): string {
|
|||||||
sendIconContainer.style.display = 'none';
|
sendIconContainer.style.display = 'none';
|
||||||
stopIconContainer.style.display = 'block';
|
stopIconContainer.style.display = 'block';
|
||||||
isConversationActive = true;
|
isConversationActive = true;
|
||||||
|
// 禁用输入框
|
||||||
|
messageInput.disabled = true;
|
||||||
|
messageInput.placeholder = '正在处理中,请稍候...';
|
||||||
} else {
|
} else {
|
||||||
sendButton.classList.remove('sending');
|
sendButton.classList.remove('sending');
|
||||||
sendIconContainer.style.display = 'block';
|
sendIconContainer.style.display = 'block';
|
||||||
stopIconContainer.style.display = 'none';
|
stopIconContainer.style.display = 'none';
|
||||||
isConversationActive = false;
|
isConversationActive = false;
|
||||||
|
// 启用输入框
|
||||||
|
messageInput.disabled = false;
|
||||||
|
messageInput.placeholder = '输入您的问题,按 Enter 发送,Shift + Enter 换行...';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,6 +340,11 @@ export function getInputAreaScript(): string {
|
|||||||
const text = messageInput.value.trim();
|
const text = messageInput.value.trim();
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
|
|
||||||
|
// 如果正在对话中,阻止发送新消息
|
||||||
|
if (isConversationActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 检查工作区状态
|
// 检查工作区状态
|
||||||
if (!hasWorkspace) {
|
if (!hasWorkspace) {
|
||||||
// 如果没有工作区,阻止发送并清空输入框
|
// 如果没有工作区,阻止发送并清空输入框
|
||||||
|
|||||||
@ -1097,9 +1097,11 @@ export function getMessageAreaScript(): string {
|
|||||||
console.log('[WebView] 对话完成,添加操作按钮');
|
console.log('[WebView] 对话完成,添加操作按钮');
|
||||||
const actionsDiv = document.createElement('div');
|
const actionsDiv = document.createElement('div');
|
||||||
actionsDiv.className = 'message-actions';
|
actionsDiv.className = 'message-actions';
|
||||||
|
|
||||||
|
// 复制按钮
|
||||||
const copyBtn = document.createElement('button');
|
const copyBtn = document.createElement('button');
|
||||||
copyBtn.className = 'action-btn';
|
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 = () => {
|
copyBtn.onclick = () => {
|
||||||
const textContent = segments
|
const textContent = segments
|
||||||
.filter(s => s.type === 'text' && s.content)
|
.filter(s => s.type === 'text' && s.content)
|
||||||
@ -1107,7 +1109,22 @@ export function getMessageAreaScript(): string {
|
|||||||
.join('\\n');
|
.join('\\n');
|
||||||
copyMessage(textContent, copyBtn);
|
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(copyBtn);
|
||||||
|
actionsDiv.appendChild(likeBtn);
|
||||||
|
actionsDiv.appendChild(dislikeBtn);
|
||||||
currentSegmentedMessage.appendChild(actionsDiv);
|
currentSegmentedMessage.appendChild(actionsDiv);
|
||||||
|
|
||||||
// 重置当前分段消息容器
|
// 重置当前分段消息容器
|
||||||
@ -1241,9 +1258,11 @@ export function getMessageAreaScript(): string {
|
|||||||
// 添加操作按钮
|
// 添加操作按钮
|
||||||
const actionsDiv = document.createElement('div');
|
const actionsDiv = document.createElement('div');
|
||||||
actionsDiv.className = 'message-actions';
|
actionsDiv.className = 'message-actions';
|
||||||
|
|
||||||
|
// 复制按钮
|
||||||
const copyBtn = document.createElement('button');
|
const copyBtn = document.createElement('button');
|
||||||
copyBtn.className = 'action-btn';
|
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 = () => {
|
copyBtn.onclick = () => {
|
||||||
const textContent = segments
|
const textContent = segments
|
||||||
.filter(s => s.type === 'text' && s.content)
|
.filter(s => s.type === 'text' && s.content)
|
||||||
@ -1251,7 +1270,22 @@ export function getMessageAreaScript(): string {
|
|||||||
.join('\\n');
|
.join('\\n');
|
||||||
copyMessage(textContent, copyBtn);
|
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(copyBtn);
|
||||||
|
actionsDiv.appendChild(likeBtn);
|
||||||
|
actionsDiv.appendChild(dislikeBtn);
|
||||||
container.appendChild(actionsDiv);
|
container.appendChild(actionsDiv);
|
||||||
|
|
||||||
messagesEl.appendChild(container);
|
messagesEl.appendChild(container);
|
||||||
|
|||||||
@ -5,7 +5,12 @@
|
|||||||
/**
|
/**
|
||||||
* 获取模型选择器的 HTML 内容
|
* 获取模型选择器的 HTML 内容
|
||||||
*/
|
*/
|
||||||
export function getModelSelectorContent(): string {
|
export function getModelSelectorContent(
|
||||||
|
autoIcon: string = '',
|
||||||
|
liteIcon: string = '',
|
||||||
|
syIcon: string = '',
|
||||||
|
maxIcon: string = ''
|
||||||
|
): string {
|
||||||
return `
|
return `
|
||||||
<!-- 模型选择 -->
|
<!-- 模型选择 -->
|
||||||
<div class="tooltip">
|
<div class="tooltip">
|
||||||
@ -17,10 +22,22 @@ export function getModelSelectorContent(): string {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="select-dropdown" id="modelDropdown">
|
<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')">
|
||||||
<div class="select-option selected" data-value="auto" data-tooltip="自动选择最佳模型" onclick="selectModel('auto', 'Auto')">Auto</div>
|
${autoIcon ? `<img src="${autoIcon}" class="model-icon" alt="Auto">` : ''}
|
||||||
<div class="select-option" data-value="syntaxic" data-tooltip="语法分析和代码理解" onclick="selectModel('syntaxic', 'Syntaxic')">Syntaxic</div>
|
<span class="option-label">Auto</span>
|
||||||
<div class="select-option" data-value="max" data-tooltip="最强性能,复杂任务" onclick="selectModel('max', 'Max')">Max</div>
|
</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>
|
</div>
|
||||||
<!-- 模型选择器的 tooltip 容器 -->
|
<!-- 模型选择器的 tooltip 容器 -->
|
||||||
<div id="modelTooltip" class="model-tooltip"></div>
|
<div id="modelTooltip" class="model-tooltip"></div>
|
||||||
@ -88,10 +105,12 @@ export function getModelSelectorStyles(): string {
|
|||||||
#modelDropdown .select-option {
|
#modelDropdown .select-option {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
font-size: 12px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.2s ease;
|
transition: background 0.2s ease;
|
||||||
white-space: nowrap;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
}
|
}
|
||||||
#modelDropdown .select-option:hover {
|
#modelDropdown .select-option:hover {
|
||||||
background: rgba(128, 128, 128, 0.3);
|
background: rgba(128, 128, 128, 0.3);
|
||||||
@ -100,6 +119,17 @@ export function getModelSelectorStyles(): string {
|
|||||||
background: rgba(128, 128, 128, 0.5);
|
background: rgba(128, 128, 128, 0.5);
|
||||||
color: var(--vscode-foreground);
|
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 样式 */
|
/* 模型选择器的 tooltip 样式 */
|
||||||
.model-tooltip {
|
.model-tooltip {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@ -17,14 +17,17 @@ import {
|
|||||||
getMessageAreaStyles,
|
getMessageAreaStyles,
|
||||||
getMessageAreaScript,
|
getMessageAreaScript,
|
||||||
} from "./messageArea";
|
} from "./messageArea";
|
||||||
import {
|
import { getAgentCardStyles, getAgentCardScript } from "./agentCard";
|
||||||
getAgentCardStyles,
|
|
||||||
getAgentCardScript,
|
|
||||||
} from "./agentCard";
|
|
||||||
/**
|
/**
|
||||||
* 获取 WebView 面板的 HTML 内容
|
* 获取 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>
|
return `<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
@ -394,14 +397,14 @@ export function getWebviewContent(iconUri?: string): string {
|
|||||||
<span id="statusText">思考中...</span>
|
<span id="statusText">思考中...</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="quick-actions">
|
<!-- <div class="quick-actions">
|
||||||
<button class="quick-btn" onclick="quickAction('counter')">生成计数器</button>
|
<button class="quick-btn" onclick="quickAction('counter')">生成计数器</button>
|
||||||
<button class="quick-btn" onclick="quickAction('fsm')">生成状态机</button>
|
<button class="quick-btn" onclick="quickAction('fsm')">生成状态机</button>
|
||||||
<button class="quick-btn" onclick="quickAction('testbench')">生成测试平台</button>
|
<button class="quick-btn" onclick="quickAction('testbench')">生成测试平台</button>
|
||||||
<button class="quick-btn" onclick="quickAction('explore')">知识探索</button>
|
<button class="quick-btn" onclick="quickAction('explore')">知识探索</button>
|
||||||
</div>
|
</div> -->
|
||||||
|
|
||||||
${getInputAreaContent()}
|
${getInputAreaContent(autoIconUri, liteIconUri, syIconUri, maxIconUri)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
Reference in New Issue
Block a user