Compare commits
36 Commits
58113fb109
...
50eacdafde
| Author | SHA1 | Date | |
|---|---|---|---|
| 50eacdafde | |||
| d90cca7cef | |||
| 5347425327 | |||
| 28d93c7e75 | |||
| 5339212de9 | |||
| 73a1510de4 | |||
| 606f757699 | |||
| 342bf22f3f | |||
| d2ec73f796 | |||
| c9f597beec | |||
| e9a201ef01 | |||
| 77a89847cb | |||
| c14b7f4dbc | |||
| 64724bf48c | |||
| c9e160f2ef | |||
| 3a19cc638f | |||
| a2e8e74572 | |||
| ad96743fad | |||
| 95b1bd7678 | |||
| 94b6fb056f | |||
| a24fd71636 | |||
| 7d1b8f7e26 | |||
| 5753e120ba | |||
| f55a5bfbcb | |||
| 83b706d5be | |||
| b9e63bc9a9 | |||
| ef0c8748f7 | |||
| 430a2c4062 | |||
| f5bd35c71a | |||
| f958683f53 | |||
| 21a8abd5cf | |||
| 4b2da8244f | |||
| c571cd9137 | |||
| 8cf0e32184 | |||
| 1cbd0c5fe7 | |||
| 72a84ed9e2 |
24
CHANGELOG.md
@ -1,9 +1,23 @@
|
|||||||
# Change Log
|
# 更新日志
|
||||||
|
|
||||||
All notable changes to the "ic-coder" extension will be documented in this file.
|
所有重要的项目变更都将记录在此文件中。
|
||||||
|
|
||||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
## [1.0.2] - 2026-01-13
|
||||||
|
|
||||||
## [Unreleased]
|
IC Coder插件端正式发布。
|
||||||
|
|
||||||
- Initial release
|
IC Coder 插件端是一个专为 FPGA 开发设计的 VS Code 扩展,提供 AI 驱动的智能辅助功能。
|
||||||
|
|
||||||
|
主要功能:
|
||||||
|
- VCD波形解析
|
||||||
|
- 自动生成完整工程
|
||||||
|
- 自动仿真
|
||||||
|
- 自主代码迭代
|
||||||
|
- 智能匹配最优模型
|
||||||
|
- 多线程任务处理
|
||||||
|
- 实时跟随
|
||||||
|
- 丰富的上下文工具
|
||||||
|
- 全双工交互
|
||||||
|
- 多层次安全保障
|
||||||
|
- 自动搭建电路架构
|
||||||
|
- 多平台支持
|
||||||
|
|||||||
@ -67,6 +67,11 @@
|
|||||||
CO03l8nmFBBTNPDg7lN9a9fYwDdgsRIDVDwTrx6Esggi6HnzmrMTJQQJ99BLACAAAAAAAAAAAAAGAZDOVVyT
|
CO03l8nmFBBTNPDg7lN9a9fYwDdgsRIDVDwTrx6Esggi6HnzmrMTJQQJ99BLACAAAAAAAAAAAAAGAZDOVVyT
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
//蔡工的token
|
||||||
|
6CB3tOZPiwNi6rrOuFHMe6QzrVWBnajW5fJsNgCWu8jtERUCCRnJJQQJ99CAACAAAAAAAAAAAAASAZDO3FnY
|
||||||
|
```
|
||||||
|
|
||||||
### 3. 创建发布者账号
|
### 3. 创建发布者账号
|
||||||
|
|
||||||
发布者账号是你在 VS Code 市场的身份标识。
|
发布者账号是你在 VS Code 市场的身份标识。
|
||||||
|
|||||||
51
README.md
@ -1,24 +1,45 @@
|
|||||||
# IC Coder Plugin
|
## 什么是 IC Coder
|
||||||
|
|
||||||
IC Coder 是一个面向 Verilog/FPGA 开发的智能辅助插件。
|
**IC Coder** 是一款 **专注于真实 FPGA 研发的 Verilog 智能体编程平台**。我们立志于用 AI 重塑 FPGA 研发效率,让 FPGA 开发者们,都能享受到 AI 发展所带来的科技福利!目标成为全球最好用的 **LLM 生成 Verilog**的平台!
|
||||||
|
|
||||||
## 功能特性
|
从 WEB 端到插件端,IC Coder 智能体架构完成了**全新升级**,采用当前主流的**层级架构**设计,这种高内聚、低耦合的架构特性,不仅支持更多功能扩展,更预留了充足的迭代空间。当前,插件端拥有了调用本地工具的能力,不再是单纯代码生成的智能体,升级为拥有**语法校验、波形逻辑检查**等工具的**全流程 Verilog 编程智能体平台**,给用户带来更沉浸的**Vibe Verilog Coding**体验。
|
||||||
|
|
||||||
- Verilog 代码智能生成
|
## 输入需求 对话补充需求
|
||||||
- 文件操作支持(创建、读取、修改、删除)
|
|
||||||
- 集成 iverilog 仿真工具
|
|
||||||
- VCD 波形文件生成
|
|
||||||
- 智能对话助手
|
|
||||||
|
|
||||||
## 使用说明
|
**无需**输入完整需求,放心交给智能体补充完善。
|
||||||
|
|
||||||
安装插件后,点击侧边栏的 IC Coder 图标即可开始使用。
|
## Plan 模型下确认设计文档
|
||||||
|
|
||||||
## 系统要求
|
**确定**好用户需求以及相关参数后,整理并输出一份 FPGA 开发**设计文档**。Plan 模式下用户可以**进一步**与 IC Coder 沟通需求,或**直接修改**设计文档。
|
||||||
|
|
||||||
- VS Code 1.107.0 或更高版本
|
## 自动搭建电路架构
|
||||||
- 插件已内置 iverilog 工具(Windows 平台)
|
|
||||||
|
|
||||||
## 许可证
|
根据需求自动搭建电路架构,并将电路信号关系结构化
|
||||||
|
|
||||||
MIT
|
## 自动仿真
|
||||||
|
|
||||||
|
自主搭建 Testbench 仿真平台,自动运行仿真生成波形
|
||||||
|
|
||||||
|
## 实时跟随
|
||||||
|
|
||||||
|
实时展示全流程执行细节,与智能体协同随时反馈,让 AI 开发更清晰、高效
|
||||||
|
|
||||||
|
## VCD 波形解析
|
||||||
|
|
||||||
|
自动解析 VCD 波形文件,自动根据需求,检查是否存在逻辑错误
|
||||||
|
|
||||||
|
## 自主代码迭代
|
||||||
|
|
||||||
|
根据波形解析结果,自动对代码进行优化,然后重新仿真并解析波形,如此迭代,直到仿真无误
|
||||||
|
|
||||||
|
## 多层次安全保障
|
||||||
|
|
||||||
|
默认本地存储与云端即时加密保障隐私,真正做到了代码全链路加密传输、云端零存储
|
||||||
|
|
||||||
|
## 反馈
|
||||||
|
|
||||||
|
无论是想与我们深入交流还是遇到任何问题,欢迎您[进入社区](https://iccoder.com:888/community)与我们联系
|
||||||
|
|
||||||
|
## 服务条款和隐私协议
|
||||||
|
|
||||||
|
请阅读我们的[服务条款](https://iccoder.com:888/guides/legal/terms-of-service)和[隐私协议](https://iccoder.com:888/guides/legal/privacy-policy)了解更多细节。
|
||||||
|
|||||||
BIN
media/description/auto-build-architecture-copy.png
Normal file
|
After Width: | Height: | Size: 168 KiB |
BIN
media/description/auto-build-architecture.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
media/description/auto-simulation-1.png
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
media/description/auto-simulation-2.png
Normal file
|
After Width: | Height: | Size: 251 KiB |
BIN
media/description/auto-simulation-3.png
Normal file
|
After Width: | Height: | Size: 266 KiB |
BIN
media/description/input-requirement-1.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
media/description/input-requirement-2.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
media/description/input-requirement-3.png
Normal file
|
After Width: | Height: | Size: 242 KiB |
BIN
media/description/plan-design-doc-1.png
Normal file
|
After Width: | Height: | Size: 349 KiB |
BIN
media/description/plan-design-doc-2.png
Normal file
|
After Width: | Height: | Size: 302 KiB |
BIN
media/description/real-time-follow-1.png
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
media/description/real-time-follow-2.png
Normal file
|
After Width: | Height: | Size: 305 KiB |
BIN
media/description/real-time-follow-3.png
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
media/description/real-time-follow-4.png
Normal file
|
After Width: | Height: | Size: 209 KiB |
14
package.json
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "iccoder",
|
"name": "iccoder",
|
||||||
"displayName": "IC Coder",
|
"displayName": "IC Coder: Agentic Verilog Platform",
|
||||||
"description": "Agentic Verilog Coding Platform for Real-World FPGAs",
|
"description": "Agentic Verilog Coding Platform for Real-World FPGAs",
|
||||||
"version": "0.0.2",
|
"version": "1.0.2",
|
||||||
"publisher": "ICCoder",
|
"publisher": "ICCoderAgenticVerilogPlatform",
|
||||||
"engines": {
|
"engines": {
|
||||||
"vscode": "^1.80.0"
|
"vscode": "^1.80.0"
|
||||||
},
|
},
|
||||||
@ -21,6 +21,10 @@
|
|||||||
"assistant"
|
"assistant"
|
||||||
],
|
],
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.pengyejiatu.com/pengyejiatu/IC-Coder-Plugin.git"
|
||||||
|
},
|
||||||
"activationEvents": [
|
"activationEvents": [
|
||||||
"onCommand:ic-coder.openPanel",
|
"onCommand:ic-coder.openPanel",
|
||||||
"onView:ic-coder-sidebar",
|
"onView:ic-coder-sidebar",
|
||||||
@ -114,7 +118,9 @@
|
|||||||
"dist",
|
"dist",
|
||||||
"media",
|
"media",
|
||||||
"tools",
|
"tools",
|
||||||
"src/assets"
|
"src/assets",
|
||||||
|
"LICENSE",
|
||||||
|
"CHANGELOG.md"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wavedrom/doppler": "^1.14.0",
|
"@wavedrom/doppler": "^1.14.0",
|
||||||
|
|||||||
BIN
src/assets/QRCode/wx.png
Normal file
|
After Width: | Height: | Size: 119 KiB |
@ -8,7 +8,7 @@ import * as vscode from "vscode";
|
|||||||
type Environment = "dev" | "test" | "prod";
|
type Environment = "dev" | "test" | "prod";
|
||||||
|
|
||||||
/** 当前环境 - 修改这里切换环境 */
|
/** 当前环境 - 修改这里切换环境 */
|
||||||
const CURRENT_ENV: Environment = "test";
|
const CURRENT_ENV: Environment = "prod";
|
||||||
|
|
||||||
/** 服务等级类型 */
|
/** 服务等级类型 */
|
||||||
export type ServiceTier = "lite" | "syntaxic" | "max" | "auto";
|
export type ServiceTier = "lite" | "syntaxic" | "max" | "auto";
|
||||||
@ -31,28 +31,28 @@ export interface IccoderConfig {
|
|||||||
|
|
||||||
/** 环境配置 */
|
/** 环境配置 */
|
||||||
const ENV_CONFIG: Record<Environment, IccoderConfig> = {
|
const ENV_CONFIG: Record<Environment, IccoderConfig> = {
|
||||||
/** 本地开发环境 */
|
/** 本地开发环境 - 通过 Gateway 路由 */
|
||||||
dev: {
|
dev: {
|
||||||
backendUrl: "http://localhost:2233",
|
backendUrl: "http://localhost:8080/iccoder",
|
||||||
backendUrlStrongeLoop: "http://192.168.1.108:2029",
|
backendUrlStrongeLoop: "http://localhost:8080",
|
||||||
loginUrl: "http://localhost/login",
|
loginUrl: "http://localhost/login",
|
||||||
timeout: 300000,
|
timeout: 300000,
|
||||||
userId: "default-user",
|
userId: "default-user",
|
||||||
serviceTier: "max", // 默认使用 max
|
serviceTier: "max", // 默认使用 max
|
||||||
},
|
},
|
||||||
/** 测试服务器环境 */
|
/** 测试服务器环境 - 通过 Gateway 路由 */
|
||||||
test: {
|
test: {
|
||||||
backendUrl: "http://192.168.1.108:2233",
|
backendUrl: "http://192.168.1.108:2029/iccoder",
|
||||||
backendUrlStrongeLoop: "http://192.168.1.108:2029",
|
backendUrlStrongeLoop: "http://192.168.1.108:2029",
|
||||||
loginUrl: "http://192.168.1.108:2005/login",
|
loginUrl: "http://192.168.1.108:2005/login",
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
userId: "default-user",
|
userId: "default-user",
|
||||||
serviceTier: "max",
|
serviceTier: "max",
|
||||||
},
|
},
|
||||||
/** 生产环境 */
|
/** 生产环境 - 通过 Gateway 路由 */
|
||||||
prod: {
|
prod: {
|
||||||
backendUrl: "https://api.iccoder.com",
|
backendUrl: "https://api.iccoder.com",
|
||||||
backendUrlStrongeLoop: "http://api.iccoder.com:2029",
|
backendUrlStrongeLoop: "http://192.168.1.115:2029",
|
||||||
loginUrl: "https://iccoder.com/login",
|
loginUrl: "https://iccoder.com/login",
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
userId: "default-user",
|
userId: "default-user",
|
||||||
|
|||||||
@ -132,6 +132,17 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
"ic-coder.login",
|
"ic-coder.login",
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
|
// 先清除 session 偏好,避免 VSCode 弹出"账户不一致"确认框
|
||||||
|
try {
|
||||||
|
await vscode.authentication.getSession("iccoder", [], {
|
||||||
|
clearSessionPreference: true,
|
||||||
|
createIfNone: false
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// 忽略错误
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新 session
|
||||||
await vscode.authentication.getSession("iccoder", [], { createIfNone: true });
|
await vscode.authentication.getSession("iccoder", [], { createIfNone: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
vscode.window.showErrorMessage(`登录失败: ${error}`);
|
vscode.window.showErrorMessage(`登录失败: ${error}`);
|
||||||
@ -146,12 +157,8 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
try {
|
try {
|
||||||
const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false });
|
const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false });
|
||||||
if (session) {
|
if (session) {
|
||||||
// 通过创建新会话并清除偏好来实现登出
|
// 调用 authProvider 的 removeSession 方法
|
||||||
await vscode.authentication.getSession("iccoder", [], {
|
await authProvider.removeSession(session.id);
|
||||||
clearSessionPreference: true,
|
|
||||||
forceNewSession: true
|
|
||||||
});
|
|
||||||
vscode.window.showInformationMessage("已退出登录");
|
|
||||||
} else {
|
} else {
|
||||||
vscode.window.showInformationMessage("当前未登录");
|
vscode.window.showInformationMessage("当前未登录");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
handleReplaceInFile,
|
handleReplaceInFile,
|
||||||
handleUserAnswer,
|
handleUserAnswer,
|
||||||
abortCurrentDialog,
|
abortCurrentDialog,
|
||||||
|
handleOptimizePrompt,
|
||||||
handlePlanAction,
|
handlePlanAction,
|
||||||
getCurrentTaskId,
|
getCurrentTaskId,
|
||||||
setLastTaskId,
|
setLastTaskId,
|
||||||
@ -130,13 +131,19 @@ export async function showICHelperPanel(
|
|||||||
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Max.png")
|
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Max.png")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 获取二维码图片URI
|
||||||
|
const qrCodeUri = panel.webview.asWebviewUri(
|
||||||
|
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "QRCode", "wx.png")
|
||||||
|
);
|
||||||
|
|
||||||
// 设置HTML内容
|
// 设置HTML内容
|
||||||
panel.webview.html = getWebviewContent(
|
panel.webview.html = getWebviewContent(
|
||||||
iconUri.toString(),
|
iconUri.toString(),
|
||||||
autoIconUri.toString(),
|
autoIconUri.toString(),
|
||||||
liteIconUri.toString(),
|
liteIconUri.toString(),
|
||||||
syIconUri.toString(),
|
syIconUri.toString(),
|
||||||
maxIconUri.toString()
|
maxIconUri.toString(),
|
||||||
|
qrCodeUri.toString()
|
||||||
);
|
);
|
||||||
|
|
||||||
// 获取并发送用户信息到 webview
|
// 获取并发送用户信息到 webview
|
||||||
@ -155,7 +162,8 @@ export async function showICHelperPanel(
|
|||||||
userId: userInfo.userId,
|
userId: userInfo.userId,
|
||||||
nickname: userInfo.nickname,
|
nickname: userInfo.nickname,
|
||||||
username: userInfo.username,
|
username: userInfo.username,
|
||||||
credits: userInfo.credits
|
credits: userInfo.credits,
|
||||||
|
membership: userInfo.membership
|
||||||
},
|
},
|
||||||
tierIconUrl: tierIconUrl
|
tierIconUrl: tierIconUrl
|
||||||
};
|
};
|
||||||
@ -328,6 +336,35 @@ export async function showICHelperPanel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "optimizePrompt":
|
||||||
|
if (typeof message.prompt === "string") {
|
||||||
|
void handleOptimizePrompt(panel, message.prompt);
|
||||||
|
} else {
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "optimizeResult",
|
||||||
|
success: false,
|
||||||
|
error: "提示词为空或格式错误",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "logout":
|
||||||
|
// 退出登录
|
||||||
|
vscode.commands.executeCommand("ic-coder.logout");
|
||||||
|
break;
|
||||||
|
case "openICCoder":
|
||||||
|
// 跳转到 IC Coder 官网
|
||||||
|
vscode.env.openExternal(vscode.Uri.parse("https://www.iccoder.com"));
|
||||||
|
break;
|
||||||
|
case "openUserManual":
|
||||||
|
// 打开用户手册
|
||||||
|
vscode.env.openExternal(vscode.Uri.parse("https://www.iccoder.com"));
|
||||||
|
break;
|
||||||
|
case "openUserFeedback":
|
||||||
|
// 打开用户反馈二维码弹窗
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "showFeedbackQRCode"
|
||||||
|
});
|
||||||
|
break;
|
||||||
// 处理计划操作(只做模式切换,响应已通过 submitAnswer 发送)
|
// 处理计划操作(只做模式切换,响应已通过 submitAnswer 发送)
|
||||||
case "planAction":
|
case "planAction":
|
||||||
if (message.action === "confirm") {
|
if (message.action === "confirm") {
|
||||||
@ -475,6 +512,20 @@ export async function showICHelperPanel(
|
|||||||
hasWorkspace: hasWorkspace,
|
hasWorkspace: hasWorkspace,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "openExternalUrl":
|
||||||
|
// 打开外部链接
|
||||||
|
if (message.url) {
|
||||||
|
vscode.env.openExternal(vscode.Uri.parse(message.url));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "openICCoder":
|
||||||
|
// 打开 IC Coder 官网
|
||||||
|
vscode.env.openExternal(vscode.Uri.parse('https://www.iccoder.com'));
|
||||||
|
break;
|
||||||
|
case "logout":
|
||||||
|
// 退出登录(前端已有确认对话框)
|
||||||
|
vscode.commands.executeCommand('iccoder.logout');
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
* API 客户端
|
* API 客户端
|
||||||
* 封装与后端的 HTTP 通信
|
* 封装与后端的 HTTP 通信
|
||||||
*/
|
*/
|
||||||
|
import * as vscode from 'vscode';
|
||||||
import * as https from 'https';
|
import * as https from 'https';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
@ -18,6 +19,18 @@ interface RequestOptions {
|
|||||||
timeout?: number;
|
timeout?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录的 Token
|
||||||
|
*/
|
||||||
|
async function getAuthToken(): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const session = await vscode.authentication.getSession('iccoder', [], { silent: true });
|
||||||
|
return session?.accessToken;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送 HTTP 请求
|
* 发送 HTTP 请求
|
||||||
*/
|
*/
|
||||||
@ -25,6 +38,9 @@ async function request<T>(path: string, options: RequestOptions): Promise<T> {
|
|||||||
const url = new URL(getApiUrl(path));
|
const url = new URL(getApiUrl(path));
|
||||||
const { timeout } = getConfig();
|
const { timeout } = getConfig();
|
||||||
|
|
||||||
|
// 自动获取 Token
|
||||||
|
const token = await getAuthToken();
|
||||||
|
|
||||||
const isHttps = url.protocol === 'https:';
|
const isHttps = url.protocol === 'https:';
|
||||||
const httpModule = isHttps ? https : http;
|
const httpModule = isHttps ? https : http;
|
||||||
|
|
||||||
@ -35,6 +51,7 @@ async function request<T>(path: string, options: RequestOptions): Promise<T> {
|
|||||||
method: options.method,
|
method: options.method,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
||||||
...options.headers
|
...options.headers
|
||||||
},
|
},
|
||||||
timeout: options.timeout || timeout
|
timeout: options.timeout || timeout
|
||||||
|
|||||||
@ -100,7 +100,18 @@ export async function fetchBalance(): Promise<number | null> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = session.accessToken;
|
return await fetchBalanceWithToken(session.accessToken);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[CreditsService] 查询余额异常:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用指定 token 查询余额(登录过程中使用)
|
||||||
|
*/
|
||||||
|
export async function fetchBalanceWithToken(token: string): Promise<number | null> {
|
||||||
|
try {
|
||||||
console.log('[CreditsService] 开始查询余额,token 长度:', token.length);
|
console.log('[CreditsService] 开始查询余额,token 长度:', token.length);
|
||||||
|
|
||||||
// 直接调用 StrangeLoop 的 /api/credit/balance 接口
|
// 直接调用 StrangeLoop 的 /api/credit/balance 接口
|
||||||
|
|||||||
@ -29,7 +29,7 @@ import type {
|
|||||||
} from "../types/api";
|
} from "../types/api";
|
||||||
import { submitToolConfirm, submitAnswer, stopDialog } from "./apiClient";
|
import { submitToolConfirm, submitAnswer, stopDialog } from "./apiClient";
|
||||||
import { ChatHistoryManager } from "../utils/chatHistoryManager";
|
import { ChatHistoryManager } from "../utils/chatHistoryManager";
|
||||||
import { getUserIdFromToken } from "../utils/jwtUtils";
|
import { getUserIdFromToken, isTokenExpired } from "../utils/jwtUtils";
|
||||||
import { updateCachedBalance } from "./creditsService";
|
import { updateCachedBalance } from "./creditsService";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -418,8 +418,9 @@ export class DialogSession {
|
|||||||
|
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
|
|
||||||
// 从登录 session 获取真实 userId
|
// 从登录 session 获取真实 userId 和 token
|
||||||
let userId = config.userId; // 默认值
|
let userId = config.userId; // 默认值
|
||||||
|
let token: string | undefined;
|
||||||
try {
|
try {
|
||||||
console.log("[DialogSession] 尝试获取登录 session...");
|
console.log("[DialogSession] 尝试获取登录 session...");
|
||||||
const session = await vscode.authentication.getSession("iccoder", [], {
|
const session = await vscode.authentication.getSession("iccoder", [], {
|
||||||
@ -434,6 +435,22 @@ export class DialogSession {
|
|||||||
"[DialogSession] accessToken 长度:",
|
"[DialogSession] accessToken 长度:",
|
||||||
session.accessToken.length
|
session.accessToken.length
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 检测 token 是否过期
|
||||||
|
const expired = isTokenExpired(session.accessToken);
|
||||||
|
if (expired === true) {
|
||||||
|
console.error("[DialogSession] token 已过期,需要重新登录");
|
||||||
|
vscode.window
|
||||||
|
.showErrorMessage("登录已过期,请重新登录", "重新登录")
|
||||||
|
.then((selection) => {
|
||||||
|
if (selection === "重新登录") {
|
||||||
|
vscode.commands.executeCommand("iccoder.login");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
throw new Error("登录已过期,请重新登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
token = session.accessToken; // 保存 token 用于扣费
|
||||||
const parsedUserId = getUserIdFromToken(session.accessToken);
|
const parsedUserId = getUserIdFromToken(session.accessToken);
|
||||||
console.log("[DialogSession] 解析的 userId:", parsedUserId);
|
console.log("[DialogSession] 解析的 userId:", parsedUserId);
|
||||||
if (parsedUserId) {
|
if (parsedUserId) {
|
||||||
@ -475,6 +492,7 @@ export class DialogSession {
|
|||||||
userId,
|
userId,
|
||||||
mode: mode || "agent",
|
mode: mode || "agent",
|
||||||
serviceTier: serviceTier || config.serviceTier, // 优先使用传入的参数
|
serviceTier: serviceTier || config.serviceTier, // 优先使用传入的参数
|
||||||
|
token, // JWT token 用于扣费
|
||||||
compactedData: compactedData || undefined,
|
compactedData: compactedData || undefined,
|
||||||
newMessages: newMessages.length > 0 ? newMessages : undefined,
|
newMessages: newMessages.length > 0 ? newMessages : undefined,
|
||||||
knowledgeData: knowledgeData || undefined,
|
knowledgeData: knowledgeData || undefined,
|
||||||
@ -861,6 +879,23 @@ export class DialogSession {
|
|||||||
|
|
||||||
onError: (data) => {
|
onError: (data) => {
|
||||||
this.isActive = false;
|
this.isActive = false;
|
||||||
|
|
||||||
|
// 检测登录状态过期(只弹一次窗,不再传递错误)
|
||||||
|
if (
|
||||||
|
data.message.includes("LOGIN_EXPIRED") ||
|
||||||
|
data.message.includes("登录状态已过期")
|
||||||
|
) {
|
||||||
|
vscode.window
|
||||||
|
.showErrorMessage("登录状态已过期,请重新登录", "重新登录")
|
||||||
|
.then((selection) => {
|
||||||
|
if (selection === "重新登录") {
|
||||||
|
vscode.commands.executeCommand("ic-coder.login");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 登录过期错误已处理,不再传递给外部
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
callbacks.onError?.(data.message);
|
callbacks.onError?.(data.message);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -981,7 +1016,7 @@ export class DialogSession {
|
|||||||
if (selection === "去充值") {
|
if (selection === "去充值") {
|
||||||
// 打开充值页面
|
// 打开充值页面
|
||||||
vscode.env.openExternal(
|
vscode.env.openExternal(
|
||||||
vscode.Uri.parse("https://iccoder.com/memberCenter")
|
vscode.Uri.parse("https://iccoder.com/recharge")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1068,7 +1103,15 @@ export class DialogSession {
|
|||||||
selected?: string[],
|
selected?: string[],
|
||||||
customInput?: string
|
customInput?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await userInteractionManager.receiveAnswer(askId, selected, customInput);
|
// 直接调用 receiveAnswer,传递 taskId 作为 fallbackTaskId
|
||||||
|
// 如果 pendingQuestions 中有问题,走正常流程
|
||||||
|
// 如果没有,receiveAnswer 会使用 fallbackTaskId 直接发送到后端
|
||||||
|
await userInteractionManager.receiveAnswer(
|
||||||
|
askId,
|
||||||
|
selected,
|
||||||
|
customInput,
|
||||||
|
this.taskId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,8 +24,23 @@ export class ICCoderAuthenticationProvider
|
|||||||
private _sessions: vscode.AuthenticationSession[] = [];
|
private _sessions: vscode.AuthenticationSession[] = [];
|
||||||
|
|
||||||
constructor(private readonly context: vscode.ExtensionContext) {
|
constructor(private readonly context: vscode.ExtensionContext) {
|
||||||
// 从存储中恢复会话
|
// 从存储中恢复会话(同步执行)
|
||||||
this.loadSessions();
|
this.loadSessionsSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从存储中加载会话(同步版本)
|
||||||
|
*/
|
||||||
|
private loadSessionsSync(): void {
|
||||||
|
const storedSessions = this.context.globalState.get<
|
||||||
|
vscode.AuthenticationSession[]
|
||||||
|
>("icCoderSessions", []);
|
||||||
|
this._sessions = storedSessions;
|
||||||
|
console.log("[AuthProvider] 同步加载 sessions, 数量:", this._sessions.length);
|
||||||
|
if (this._sessions.length > 0) {
|
||||||
|
console.log("[AuthProvider] Session ID:", this._sessions[0].id);
|
||||||
|
console.log("[AuthProvider] Account:", this._sessions[0].account.label);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,7 +57,9 @@ export class ICCoderAuthenticationProvider
|
|||||||
* 保存会话到存储
|
* 保存会话到存储
|
||||||
*/
|
*/
|
||||||
private async saveSessions(): Promise<void> {
|
private async saveSessions(): Promise<void> {
|
||||||
|
console.log("[AuthProvider] 保存 sessions, 数量:", this._sessions.length);
|
||||||
await this.context.globalState.update("icCoderSessions", this._sessions);
|
await this.context.globalState.update("icCoderSessions", this._sessions);
|
||||||
|
console.log("[AuthProvider] sessions 已保存到 globalState");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,6 +68,7 @@ export class ICCoderAuthenticationProvider
|
|||||||
async getSessions(
|
async getSessions(
|
||||||
scopes?: readonly string[]
|
scopes?: readonly string[]
|
||||||
): Promise<vscode.AuthenticationSession[]> {
|
): Promise<vscode.AuthenticationSession[]> {
|
||||||
|
console.log("[AuthProvider] getSessions 被调用, 当前 sessions 数量:", this._sessions.length);
|
||||||
return [...this._sessions];
|
return [...this._sessions];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +79,20 @@ export class ICCoderAuthenticationProvider
|
|||||||
scopes: readonly string[]
|
scopes: readonly string[]
|
||||||
): Promise<vscode.AuthenticationSession> {
|
): Promise<vscode.AuthenticationSession> {
|
||||||
try {
|
try {
|
||||||
|
// 先删除旧的 session(静默删除,不弹窗、不重载窗口)
|
||||||
|
if (this._sessions.length > 0) {
|
||||||
|
const oldSession = this._sessions[0];
|
||||||
|
this._sessions = [];
|
||||||
|
await this.saveSessions();
|
||||||
|
await clearUserInfo();
|
||||||
|
this._onDidChangeSessions.fire({
|
||||||
|
added: [],
|
||||||
|
removed: [oldSession],
|
||||||
|
changed: [],
|
||||||
|
});
|
||||||
|
console.log("🔄 已清除旧的 session");
|
||||||
|
}
|
||||||
|
|
||||||
const token = await this.login();
|
const token = await this.login();
|
||||||
|
|
||||||
// 获取到 token 后立即调用用户信息接口
|
// 获取到 token 后立即调用用户信息接口
|
||||||
|
|||||||
103
src/services/promptOptimizeService.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* 提示词优化服务
|
||||||
|
* 调用后端 API 优化用户输入的提示词
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as https from 'https';
|
||||||
|
import * as http from 'http';
|
||||||
|
import { URL } from 'url';
|
||||||
|
import { getApiUrl } from '../config/settings';
|
||||||
|
|
||||||
|
/** 优化响应类型 */
|
||||||
|
interface OptimizeResponse {
|
||||||
|
success: boolean;
|
||||||
|
optimizedPrompt?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优化提示词
|
||||||
|
* @param prompt 原始提示词
|
||||||
|
* @returns 优化后的提示词
|
||||||
|
*/
|
||||||
|
export async function optimizePrompt(prompt: string): Promise<string> {
|
||||||
|
// 获取 JWT token
|
||||||
|
const session = await vscode.authentication.getSession('iccoder', [], { silent: true });
|
||||||
|
if (!session?.accessToken) {
|
||||||
|
throw new Error('未登录,请先登录');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await callOptimizeApi(prompt, session.accessToken);
|
||||||
|
|
||||||
|
if (response.success && response.optimizedPrompt) {
|
||||||
|
return response.optimizedPrompt;
|
||||||
|
} else {
|
||||||
|
throw new Error(response.error || '优化失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用后端优化 API
|
||||||
|
*/
|
||||||
|
async function callOptimizeApi(prompt: string, token: string): Promise<OptimizeResponse> {
|
||||||
|
const urlStr = getApiUrl('/api/prompt/optimize');
|
||||||
|
const url = new URL(urlStr);
|
||||||
|
|
||||||
|
const isHttps = url.protocol === 'https:';
|
||||||
|
const httpModule = isHttps ? https : http;
|
||||||
|
|
||||||
|
const body = JSON.stringify({ prompt });
|
||||||
|
|
||||||
|
const requestOptions: http.RequestOptions = {
|
||||||
|
hostname: url.hostname,
|
||||||
|
port: url.port || (isHttps ? 443 : 80),
|
||||||
|
path: url.pathname + url.search,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Content-Length': Buffer.byteLength(body),
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
timeout: 30000
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = httpModule.request(requestOptions, (res) => {
|
||||||
|
let data = '';
|
||||||
|
|
||||||
|
res.on('data', (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('end', () => {
|
||||||
|
console.log('[PromptOptimize] 响应状态码:', res.statusCode);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(data);
|
||||||
|
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||||||
|
resolve(json as OptimizeResponse);
|
||||||
|
} else if (res.statusCode === 401 || res.statusCode === 403) {
|
||||||
|
resolve({ success: false, error: '登录已过期,请重新登录' });
|
||||||
|
} else {
|
||||||
|
resolve({ success: false, error: json.error || json.message || `HTTP ${res.statusCode}` });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
resolve({ success: false, error: `解析响应失败: ${data}` });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', (error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('timeout', () => {
|
||||||
|
req.destroy();
|
||||||
|
reject(new Error('请求超时'));
|
||||||
|
});
|
||||||
|
|
||||||
|
req.write(body);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -173,7 +173,8 @@ export async function startStreamDialog(
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'text/event-stream',
|
'Accept': 'text/event-stream',
|
||||||
'Cache-Control': 'no-cache',
|
'Cache-Control': 'no-cache',
|
||||||
'Content-Length': Buffer.byteLength(body)
|
'Content-Length': Buffer.byteLength(body),
|
||||||
|
...(request.token ? { 'Authorization': `Bearer ${request.token}` } : {})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -183,9 +184,20 @@ export async function startStreamDialog(
|
|||||||
let errorBody = '';
|
let errorBody = '';
|
||||||
res.on('data', chunk => errorBody += chunk);
|
res.on('data', chunk => errorBody += chunk);
|
||||||
res.on('end', () => {
|
res.on('end', () => {
|
||||||
const error = new Error(`SSE 连接失败: ${res.statusCode} ${errorBody}`);
|
// 检测是否是登录状态过期
|
||||||
callbacks.onError?.({ message: error.message });
|
const isLoginExpired = errorBody.includes('登录状态已过期') ||
|
||||||
reject(error);
|
errorBody.includes('token') && errorBody.includes('过期') ||
|
||||||
|
res.statusCode === 401;
|
||||||
|
|
||||||
|
if (isLoginExpired) {
|
||||||
|
const error = new Error('LOGIN_EXPIRED:登录状态已过期,请重新登录');
|
||||||
|
callbacks.onError?.({ message: error.message });
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
const error = new Error(`SSE 连接失败: ${res.statusCode} ${errorBody}`);
|
||||||
|
callbacks.onError?.({ message: error.message });
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -226,6 +238,25 @@ export async function startStreamDialog(
|
|||||||
res.on('data', (chunk: string) => {
|
res.on('data', (chunk: string) => {
|
||||||
if (!controller.aborted) {
|
if (!controller.aborted) {
|
||||||
console.log('[SSE] 收到原始数据块:', chunk.substring(0, 200));
|
console.log('[SSE] 收到原始数据块:', chunk.substring(0, 200));
|
||||||
|
|
||||||
|
// 检查是否是业务错误码(Gateway 返回 HTTP 200 但响应体是错误 JSON)
|
||||||
|
try {
|
||||||
|
const trimmed = chunk.trim();
|
||||||
|
if (trimmed.startsWith('{') && trimmed.includes('"code"')) {
|
||||||
|
const json = JSON.parse(trimmed);
|
||||||
|
if (json.code === 401 || json.msg?.includes('登录状态已过期')) {
|
||||||
|
console.log('[SSE] 检测到登录过期业务错误');
|
||||||
|
const error = new Error('LOGIN_EXPIRED:登录状态已过期,请重新登录');
|
||||||
|
callbacks.onError?.({ message: error.message });
|
||||||
|
controller.abort();
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 不是 JSON 格式,继续正常处理
|
||||||
|
}
|
||||||
|
|
||||||
parser.feed(chunk);
|
parser.feed(chunk);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -82,21 +82,28 @@ export class UserInteractionManager {
|
|||||||
* @param askId 问题ID
|
* @param askId 问题ID
|
||||||
* @param selected 选中的选项
|
* @param selected 选中的选项
|
||||||
* @param customInput 自定义输入
|
* @param customInput 自定义输入
|
||||||
|
* @param fallbackTaskId 当问题不存在时使用的 taskId(用于直接发送到后端)
|
||||||
*/
|
*/
|
||||||
async receiveAnswer(
|
async receiveAnswer(
|
||||||
askId: string,
|
askId: string,
|
||||||
selected?: string[],
|
selected?: string[],
|
||||||
customInput?: string
|
customInput?: string,
|
||||||
|
fallbackTaskId?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const pending = this.pendingQuestions.get(askId);
|
const pending = this.pendingQuestions.get(askId);
|
||||||
|
const answer = customInput || selected?.join(', ') || '';
|
||||||
|
|
||||||
if (!pending) {
|
if (!pending) {
|
||||||
console.warn(`[UserInteraction] 问题不存在或已超时: askId=${askId}`);
|
// 问题不存在(可能是页面刷新或会话切换后),尝试直接发送到后端
|
||||||
|
if (fallbackTaskId) {
|
||||||
|
console.log(`[UserInteraction] 问题不在 pendingQuestions 中,直接发送到后端: askId=${askId}, taskId=${fallbackTaskId}`);
|
||||||
|
await this.submitUserAnswer(askId, fallbackTaskId, answer);
|
||||||
|
} else {
|
||||||
|
console.warn(`[UserInteraction] 问题不存在且无 fallbackTaskId: askId=${askId}`);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建答案
|
|
||||||
const answer = customInput || selected?.join(', ') || '';
|
|
||||||
|
|
||||||
console.log(`[UserInteraction] 收到用户回答: askId=${askId}, answer=${answer}`);
|
console.log(`[UserInteraction] 收到用户回答: askId=${askId}, answer=${answer}`);
|
||||||
|
|
||||||
// 移除待处理问题
|
// 移除待处理问题
|
||||||
@ -173,6 +180,13 @@ export class UserInteractionManager {
|
|||||||
hasPendingQuestions(): boolean {
|
hasPendingQuestions(): boolean {
|
||||||
return this.pendingQuestions.size > 0;
|
return this.pendingQuestions.size > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查特定问题是否存在
|
||||||
|
*/
|
||||||
|
hasPendingQuestion(askId: string): boolean {
|
||||||
|
return this.pendingQuestions.has(askId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全局实例
|
// 全局实例
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { URL } from 'url';
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { getStrangeLoopApiUrl, getConfig } from '../config/settings';
|
import { getStrangeLoopApiUrl, getConfig } from '../config/settings';
|
||||||
import type { UserInfoResponse, MembershipResponse, MultiMembershipVO, MembershipItemVO } from '../types/api';
|
import type { UserInfoResponse, MembershipResponse, MultiMembershipVO, MembershipItemVO } from '../types/api';
|
||||||
import { fetchBalance, getCachedBalance } from './creditsService';
|
import { fetchBalanceWithToken, getCachedBalance } from './creditsService';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP 请求选项
|
* HTTP 请求选项
|
||||||
@ -230,7 +230,7 @@ export async function onTokenReceived(token: string): Promise<UserInfo | null> {
|
|||||||
const [userInfo, membershipInfo, credits] = await Promise.all([
|
const [userInfo, membershipInfo, credits] = await Promise.all([
|
||||||
getUserInfo(token),
|
getUserInfo(token),
|
||||||
getMembershipInfo(token),
|
getMembershipInfo(token),
|
||||||
fetchBalance()
|
fetchBalanceWithToken(token)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!userInfo) {
|
if (!userInfo) {
|
||||||
|
|||||||
@ -40,6 +40,8 @@ export interface DialogRequest {
|
|||||||
mode: RunMode;
|
mode: RunMode;
|
||||||
/** 服务等级 */
|
/** 服务等级 */
|
||||||
serviceTier?: ServiceTier;
|
serviceTier?: ServiceTier;
|
||||||
|
/** JWT Token(用于认证和扣费) */
|
||||||
|
token?: string;
|
||||||
/** 压缩后的记忆数据(用于后端重启后恢复) */
|
/** 压缩后的记忆数据(用于后端重启后恢复) */
|
||||||
compactedData?: CompactedMemory;
|
compactedData?: CompactedMemory;
|
||||||
/** 压缩后产生的新消息 */
|
/** 压缩后产生的新消息 */
|
||||||
|
|||||||
@ -71,3 +71,31 @@ export function getUserIdFromToken(token: string): string | null {
|
|||||||
console.warn('[JWT] payload 中没有 user_id, userId 或 sub 字段');
|
console.warn('[JWT] payload 中没有 user_id, userId 或 sub 字段');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测 JWT token 是否已过期
|
||||||
|
* @param token JWT token
|
||||||
|
* @param bufferSeconds 提前多少秒判定为过期(默认60秒)
|
||||||
|
* @returns true 表示已过期,false 表示未过期,null 表示无法判断
|
||||||
|
*/
|
||||||
|
export function isTokenExpired(token: string, bufferSeconds: number = 60): boolean | null {
|
||||||
|
const payload = parseJwtPayload(token);
|
||||||
|
if (!payload) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.exp === undefined) {
|
||||||
|
console.warn('[JWT] payload 中没有 exp 字段,无法判断过期');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
const expTime = payload.exp - bufferSeconds;
|
||||||
|
const isExpired = now >= expTime;
|
||||||
|
|
||||||
|
if (isExpired) {
|
||||||
|
console.warn('[JWT] token 已过期,exp:', payload.exp, '当前:', now);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isExpired;
|
||||||
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import {
|
|||||||
checkBalanceBeforeSend,
|
checkBalanceBeforeSend,
|
||||||
fetchBalance,
|
fetchBalance,
|
||||||
} from "../services/creditsService";
|
} from "../services/creditsService";
|
||||||
|
import { optimizePrompt } from "../services/promptOptimizeService";
|
||||||
|
|
||||||
import type { RunMode, ServiceTier } from "../types/api";
|
import type { RunMode, ServiceTier } from "../types/api";
|
||||||
|
|
||||||
@ -1031,3 +1032,35 @@ async function handleVCDGeneration(
|
|||||||
vscode.window.showErrorMessage(errorMsg);
|
vscode.window.showErrorMessage(errorMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理提示词优化请求
|
||||||
|
*/
|
||||||
|
export async function handleOptimizePrompt(
|
||||||
|
panel: vscode.WebviewPanel,
|
||||||
|
prompt: string
|
||||||
|
): Promise<void> {
|
||||||
|
console.log("[MessageHandler] ========== 收到提示词优化请求 ==========");
|
||||||
|
console.log("[MessageHandler] prompt:", prompt);
|
||||||
|
console.log("[MessageHandler] prompt 长度:", prompt?.length);
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log("[MessageHandler] 开始调用 optimizePrompt...");
|
||||||
|
const optimized = await optimizePrompt(prompt);
|
||||||
|
console.log("[MessageHandler] 优化成功,结果:", optimized);
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "optimizeResult",
|
||||||
|
success: true,
|
||||||
|
optimizedPrompt: optimized,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const errorMsg = error instanceof Error ? error.message : "优化失败";
|
||||||
|
console.error("[MessageHandler] 提示词优化失败:", errorMsg);
|
||||||
|
panel.webview.postMessage({
|
||||||
|
command: "optimizeResult",
|
||||||
|
success: false,
|
||||||
|
error: errorMsg,
|
||||||
|
});
|
||||||
|
vscode.window.showErrorMessage(`提示词优化失败: ${errorMsg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import { getWebviewContent } from "./webviewContent";
|
import { getWebviewContent } from "./webviewContent";
|
||||||
|
import { isTokenExpired } from "../utils/jwtUtils";
|
||||||
import {
|
import {
|
||||||
handleUserMessage,
|
handleUserMessage,
|
||||||
insertCodeToEditor,
|
insertCodeToEditor,
|
||||||
@ -10,6 +11,7 @@ import {
|
|||||||
handleReplaceInFile,
|
handleReplaceInFile,
|
||||||
handleUserAnswer,
|
handleUserAnswer,
|
||||||
abortCurrentDialog,
|
abortCurrentDialog,
|
||||||
|
handleOptimizePrompt,
|
||||||
} from "../utils/messageHandler";
|
} from "../utils/messageHandler";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,6 +71,9 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
|
|||||||
// 处理消息
|
// 处理消息
|
||||||
panel.webview.onDidReceiveMessage(
|
panel.webview.onDidReceiveMessage(
|
||||||
(message) => {
|
(message) => {
|
||||||
|
console.log("[ICViewProvider] ====== 收到 WebView 消息 ======");
|
||||||
|
console.log("[ICViewProvider] command:", message.command);
|
||||||
|
console.log("[ICViewProvider] 完整消息:", JSON.stringify(message));
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
case "sendMessage":
|
case "sendMessage":
|
||||||
handleUserMessage(panel, message.text, context.extensionPath, message.mode);
|
handleUserMessage(panel, message.text, context.extensionPath, message.mode);
|
||||||
@ -104,6 +109,9 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
|
|||||||
case "showInfo":
|
case "showInfo":
|
||||||
vscode.window.showInformationMessage(message.text);
|
vscode.window.showInformationMessage(message.text);
|
||||||
break;
|
break;
|
||||||
|
case "showWarning":
|
||||||
|
vscode.window.showWarningMessage(message.message);
|
||||||
|
break;
|
||||||
// 新增:处理用户回答
|
// 新增:处理用户回答
|
||||||
case "submitAnswer":
|
case "submitAnswer":
|
||||||
handleUserAnswer(
|
handleUserAnswer(
|
||||||
@ -116,6 +124,10 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
|
|||||||
case "abortDialog":
|
case "abortDialog":
|
||||||
void abortCurrentDialog();
|
void abortCurrentDialog();
|
||||||
break;
|
break;
|
||||||
|
// 新增:优化提示词
|
||||||
|
case "optimizePrompt":
|
||||||
|
handleOptimizePrompt(panel, message.prompt);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
@ -127,10 +139,34 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
|
|||||||
* 侧边栏视图提供者
|
* 侧边栏视图提供者
|
||||||
*/
|
*/
|
||||||
export class ICViewProvider implements vscode.WebviewViewProvider {
|
export class ICViewProvider implements vscode.WebviewViewProvider {
|
||||||
|
private _view?: vscode.WebviewView;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly extensionUri: vscode.Uri,
|
private readonly extensionUri: vscode.Uri,
|
||||||
private readonly context: vscode.ExtensionContext
|
private readonly context: vscode.ExtensionContext
|
||||||
) {}
|
) {
|
||||||
|
// 监听认证状态变化
|
||||||
|
this.context.subscriptions.push(
|
||||||
|
vscode.authentication.onDidChangeSessions((e) => {
|
||||||
|
if (e.provider.id === "iccoder") {
|
||||||
|
this.refreshLoginStatus();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新登录状态并更新视图
|
||||||
|
*/
|
||||||
|
private async refreshLoginStatus(): Promise<void> {
|
||||||
|
if (this._view) {
|
||||||
|
const isLoggedIn = await this.checkLoginStatus();
|
||||||
|
this._view.webview.html = this.getWebviewContent(
|
||||||
|
this._view.webview,
|
||||||
|
isLoggedIn
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查登录状态(使用 Authentication API)
|
* 检查登录状态(使用 Authentication API)
|
||||||
@ -138,14 +174,29 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
|
|||||||
private async checkLoginStatus(): Promise<boolean> {
|
private async checkLoginStatus(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false });
|
const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false });
|
||||||
return !!session;
|
console.log("[ICViewProvider] 检查登录状态, session:", session ? "存在" : "不存在");
|
||||||
|
if (!session) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 检查 token 是否过期
|
||||||
|
const expired = isTokenExpired(session.accessToken);
|
||||||
|
console.log("[ICViewProvider] token 过期检查结果:", expired);
|
||||||
|
// 只有明确过期才认为未登录,无法判断时认为已登录
|
||||||
|
if (expired === true) {
|
||||||
|
console.log("[ICViewProvider] Token 已过期");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("检查登录状态失败:", error);
|
console.log("[ICViewProvider] 检查登录状态失败:", error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveWebviewView(webviewView: vscode.WebviewView) {
|
resolveWebviewView(webviewView: vscode.WebviewView) {
|
||||||
|
// 保存引用以便后续刷新
|
||||||
|
this._view = webviewView;
|
||||||
|
|
||||||
webviewView.webview.options = {
|
webviewView.webview.options = {
|
||||||
enableScripts: true,
|
enableScripts: true,
|
||||||
localResourceRoots: [vscode.Uri.joinPath(this.extensionUri, "media")],
|
localResourceRoots: [vscode.Uri.joinPath(this.extensionUri, "media")],
|
||||||
@ -166,6 +217,17 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
|
|||||||
vscode.commands.executeCommand("ic-coder.openChat");
|
vscode.commands.executeCommand("ic-coder.openChat");
|
||||||
} else if (message.command === "login") {
|
} else if (message.command === "login") {
|
||||||
vscode.commands.executeCommand("ic-coder.login");
|
vscode.commands.executeCommand("ic-coder.login");
|
||||||
|
} else if (message.command === "logout") {
|
||||||
|
// 退出登录(前端已有确认对话框)
|
||||||
|
vscode.commands.executeCommand('iccoder.logout');
|
||||||
|
} else if (message.command === "openICCoder") {
|
||||||
|
// 打开 IC Coder 官网
|
||||||
|
vscode.env.openExternal(vscode.Uri.parse('https://www.iccoder.com'));
|
||||||
|
} else if (message.command === "openExternalUrl") {
|
||||||
|
// 打开外部链接
|
||||||
|
if (message.url) {
|
||||||
|
vscode.env.openExternal(vscode.Uri.parse(message.url));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
|
|||||||
@ -3,7 +3,21 @@ import {
|
|||||||
getUserInfoComponentStyles,
|
getUserInfoComponentStyles,
|
||||||
getUserInfoComponentScript,
|
getUserInfoComponentScript,
|
||||||
} from "./userInfoComponent";
|
} from "./userInfoComponent";
|
||||||
import { userAvatarIconSvg } from "../constants/toolIcons";
|
import {
|
||||||
|
getMoreOptionsComponentContent,
|
||||||
|
getMoreOptionsComponentStyles,
|
||||||
|
getMoreOptionsComponentScript,
|
||||||
|
} from "./moreOptionsComponent";
|
||||||
|
import {
|
||||||
|
getSettingsComponentContent,
|
||||||
|
getSettingsComponentStyles,
|
||||||
|
getSettingsComponentScript,
|
||||||
|
} from "./settingsComponent";
|
||||||
|
import {
|
||||||
|
userAvatarIconSvg,
|
||||||
|
moreIconSvg,
|
||||||
|
setting,
|
||||||
|
} from "../constants/toolIcons";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取会话历史栏的 HTML 内容
|
* 获取会话历史栏的 HTML 内容
|
||||||
@ -39,8 +53,23 @@ export function getConversationHistoryBarContent(): string {
|
|||||||
</button>
|
</button>
|
||||||
${getUserInfoComponentContent()}
|
${getUserInfoComponentContent()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class='setting'>
|
||||||
|
<button class="setting-btn" title="设置" onclick="openSettingsModal()">
|
||||||
|
${setting}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='more-container'>
|
||||||
|
<button class="more-button" title="更多选项" onclick="toggleMoreOptionsDropdown()">
|
||||||
|
${moreIconSvg}
|
||||||
|
</button>
|
||||||
|
${getMoreOptionsComponentContent()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
${getSettingsComponentContent()}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,8 +105,8 @@ export function getConversationHistoryBarStyles(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.user-avatar-icon-button {
|
.user-avatar-icon-button {
|
||||||
width: 36px;
|
width: 30px;
|
||||||
height: 36px;
|
height: 30px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--vscode-foreground);
|
color: var(--vscode-foreground);
|
||||||
@ -111,6 +140,72 @@ export function getConversationHistoryBarStyles(): string {
|
|||||||
|
|
||||||
${getUserInfoComponentStyles()}
|
${getUserInfoComponentStyles()}
|
||||||
|
|
||||||
|
${getSettingsComponentStyles()}
|
||||||
|
|
||||||
|
.setting {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-btn {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-btn:hover {
|
||||||
|
background: var(--vscode-toolbar-hoverBackground);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-button {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-button:hover {
|
||||||
|
background: var(--vscode-toolbar-hoverBackground);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-button:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-button.active {
|
||||||
|
background: var(--vscode-toolbar-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
${getMoreOptionsComponentStyles()}
|
||||||
|
|
||||||
.history-dropdown-button {
|
.history-dropdown-button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -219,8 +314,8 @@ export function getConversationHistoryBarStyles(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.new-conversation-button {
|
.new-conversation-button {
|
||||||
width: 36px;
|
width: 30px;
|
||||||
height: 36px;
|
height: 30px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--vscode-foreground);
|
color: var(--vscode-foreground);
|
||||||
@ -275,6 +370,10 @@ export function getConversationHistoryBarScript(): string {
|
|||||||
return `
|
return `
|
||||||
${getUserInfoComponentScript()}
|
${getUserInfoComponentScript()}
|
||||||
|
|
||||||
|
${getMoreOptionsComponentScript()}
|
||||||
|
|
||||||
|
${getSettingsComponentScript()}
|
||||||
|
|
||||||
// 更新用户头像图标按钮显示
|
// 更新用户头像图标按钮显示
|
||||||
function updateUserAvatarIconButton(userInfo) {
|
function updateUserAvatarIconButton(userInfo) {
|
||||||
const userAvatarIconButton = document.getElementById('userAvatarIconButton');
|
const userAvatarIconButton = document.getElementById('userAvatarIconButton');
|
||||||
|
|||||||
@ -4,21 +4,33 @@
|
|||||||
export function getExampleShowcaseContent(): string {
|
export function getExampleShowcaseContent(): string {
|
||||||
return `
|
return `
|
||||||
<div class="example-showcase" id="exampleShowcase">
|
<div class="example-showcase" id="exampleShowcase">
|
||||||
<div class="showcase-title">展示</div>
|
<div class="showcase-title">示例</div>
|
||||||
<div class="example-cards">
|
<div class="example-cards">
|
||||||
<div class="example-card" onclick="fillExample(0)">
|
<div class="example-card" onclick="sendExample(0)">
|
||||||
<div class="example-icon">📝</div>
|
<div class="example-icon">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V20C4 20.5304 4.21071 21.0391 4.58579 21.4142C4.96086 21.7893 5.46957 22 6 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M16 13H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M16 17H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M10 9H9H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
<div class="example-content">
|
<div class="example-content">
|
||||||
<div class="example-title">代码生成</div>
|
<div class="example-title">生成一个SPI控制器</div>
|
||||||
<div class="example-desc">生成一个 8 位全加器的 Verilog 代码</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="example-card" onclick="fillExample(1)">
|
<div class="example-card" onclick="sendExample(1)">
|
||||||
<div class="example-icon">🔍</div>
|
<div class="example-icon">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
<div class="example-content">
|
<div class="example-content">
|
||||||
<div class="example-title">代码分析</div>
|
<div class="example-title">生成一个GMII接口的以太网UDP通信模块</div>
|
||||||
<div class="example-desc">分析当前项目中的时序逻辑设计</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -71,26 +83,50 @@ export function getExampleShowcaseStyles(): string {
|
|||||||
background: var(--vscode-input-background);
|
background: var(--vscode-input-background);
|
||||||
border: 1px solid var(--vscode-input-border);
|
border: 1px solid var(--vscode-input-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 14px;
|
padding: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(135deg, rgba(79, 172, 254, 0.1) 0%, rgba(0, 242, 254, 0.1) 50%, rgba(168, 85, 247, 0.1) 100%);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-card:hover::before {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.example-card:hover {
|
.example-card:hover {
|
||||||
border-color: var(--vscode-focusBorder);
|
border-color: var(--vscode-focusBorder);
|
||||||
background: var(--vscode-list-hoverBackground);
|
transform: translateY(-3px);
|
||||||
transform: translateY(-2px);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.example-icon {
|
.example-icon {
|
||||||
font-size: 28px;
|
|
||||||
line-height: 1;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-icon svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.example-content {
|
.example-content {
|
||||||
@ -99,12 +135,23 @@ export function getExampleShowcaseStyles(): string {
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.example-title {
|
.example-title {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--vscode-foreground);
|
color: var(--vscode-foreground);
|
||||||
|
line-height: 1.4;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-card:hover .example-title {
|
||||||
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 50%, #a855f7 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.example-desc {
|
.example-desc {
|
||||||
@ -175,20 +222,41 @@ export function getExampleShowcaseScript(): string {
|
|||||||
return `
|
return `
|
||||||
// 示例文本数组
|
// 示例文本数组
|
||||||
const exampleTexts = [
|
const exampleTexts = [
|
||||||
'生成一个 8 位全加器的 Verilog 代码',
|
'生成一个SPI控制器',
|
||||||
'分析当前项目中的时序逻辑设计'
|
'生成一个GMII接口的以太网UDP通信模块'
|
||||||
];
|
];
|
||||||
|
|
||||||
// 填充示例到输入框
|
// 存储待发送的示例索引
|
||||||
function fillExample(index) {
|
let pendingExampleIndex = -1;
|
||||||
|
|
||||||
|
// 直接发送示例消息
|
||||||
|
function sendExample(index) {
|
||||||
|
// 先检查工作区
|
||||||
|
pendingExampleIndex = index;
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'checkWorkspace'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实际发送示例消息
|
||||||
|
function doSendExample(index) {
|
||||||
const messageInput = document.getElementById('messageInput');
|
const messageInput = document.getElementById('messageInput');
|
||||||
|
const sendButton = document.getElementById('sendButton');
|
||||||
|
|
||||||
if (messageInput && exampleTexts[index]) {
|
if (messageInput && exampleTexts[index]) {
|
||||||
messageInput.value = exampleTexts[index];
|
messageInput.value = exampleTexts[index];
|
||||||
messageInput.focus();
|
|
||||||
// 触发自动调整高度
|
// 触发自动调整高度
|
||||||
if (typeof autoResizeTextarea === 'function') {
|
if (typeof autoResizeTextarea === 'function') {
|
||||||
autoResizeTextarea();
|
autoResizeTextarea();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 直接触发发送
|
||||||
|
if (sendButton && typeof sendButton.click === 'function') {
|
||||||
|
sendButton.click();
|
||||||
|
} else if (typeof sendMessage === 'function') {
|
||||||
|
sendMessage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
327
src/views/generalSettingsComponent.ts
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
/**
|
||||||
|
* 获取通用设置组件的 HTML 内容
|
||||||
|
*/
|
||||||
|
export function getGeneralSettingsComponentContent(): string {
|
||||||
|
return `
|
||||||
|
<div class="general-settings">
|
||||||
|
<h3 class="settings-section-title">通用设置</h3>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<div class="settings-item">
|
||||||
|
<div class="settings-item-header">
|
||||||
|
<label class="settings-item-label">主题</label>
|
||||||
|
<span class="settings-item-description">选择界面主题</span>
|
||||||
|
</div>
|
||||||
|
<select class="settings-select" id="themeSelect">
|
||||||
|
<option value="auto">跟随系统</option>
|
||||||
|
<option value="light">浅色</option>
|
||||||
|
<option value="dark">深色</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-item">
|
||||||
|
<div class="settings-item-header">
|
||||||
|
<label class="settings-item-label">语言</label>
|
||||||
|
<span class="settings-item-description">选择界面语言</span>
|
||||||
|
</div>
|
||||||
|
<select class="settings-select" id="languageSelect">
|
||||||
|
<option value="zh-CN">简体中文</option>
|
||||||
|
<option value="en-US">English</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-item">
|
||||||
|
<div class="settings-item-header">
|
||||||
|
<label class="settings-item-label">自动保存</label>
|
||||||
|
<span class="settings-item-description">自动保存会话历史</span>
|
||||||
|
</div>
|
||||||
|
<label class="settings-switch">
|
||||||
|
<input type="checkbox" id="autoSaveCheckbox" checked>
|
||||||
|
<span class="settings-switch-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-item">
|
||||||
|
<div class="settings-item-header">
|
||||||
|
<label class="settings-item-label">显示时间戳</label>
|
||||||
|
<span class="settings-item-description">在消息中显示时间戳</span>
|
||||||
|
</div>
|
||||||
|
<label class="settings-switch">
|
||||||
|
<input type="checkbox" id="showTimestampCheckbox">
|
||||||
|
<span class="settings-switch-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<h4 class="settings-subsection-title">编辑器设置</h4>
|
||||||
|
|
||||||
|
<div class="settings-item">
|
||||||
|
<div class="settings-item-header">
|
||||||
|
<label class="settings-item-label">字体大小</label>
|
||||||
|
<span class="settings-item-description">设置编辑器字体大小</span>
|
||||||
|
</div>
|
||||||
|
<input type="number" class="settings-input" id="fontSizeInput" value="14" min="10" max="24">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-item">
|
||||||
|
<div class="settings-item-header">
|
||||||
|
<label class="settings-item-label">代码高亮</label>
|
||||||
|
<span class="settings-item-description">启用代码语法高亮</span>
|
||||||
|
</div>
|
||||||
|
<label class="settings-switch">
|
||||||
|
<input type="checkbox" id="syntaxHighlightCheckbox" checked>
|
||||||
|
<span class="settings-switch-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-actions">
|
||||||
|
<button class="settings-button settings-button-primary" onclick="saveGeneralSettings()">
|
||||||
|
保存设置
|
||||||
|
</button>
|
||||||
|
<button class="settings-button settings-button-secondary" onclick="resetGeneralSettings()">
|
||||||
|
重置为默认
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取通用设置组件的 CSS 样式
|
||||||
|
*/
|
||||||
|
export function getGeneralSettingsComponentStyles(): string {
|
||||||
|
return `
|
||||||
|
.general-settings {
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section-title {
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-section {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-subsection-title {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid var(--vscode-panel-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-item-header {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-item-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-item-description {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-select {
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: var(--vscode-input-background);
|
||||||
|
color: var(--vscode-input-foreground);
|
||||||
|
border: 1px solid var(--vscode-input-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-select:focus {
|
||||||
|
border-color: var(--vscode-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-input {
|
||||||
|
width: 80px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: var(--vscode-input-background);
|
||||||
|
color: var(--vscode-input-foreground);
|
||||||
|
border: 1px solid var(--vscode-input-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-input:focus {
|
||||||
|
border-color: var(--vscode-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 44px;
|
||||||
|
height: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-switch-slider {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: var(--vscode-input-background);
|
||||||
|
border: 1px solid var(--vscode-input-border);
|
||||||
|
border-radius: 24px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-switch-slider:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
left: 3px;
|
||||||
|
bottom: 3px;
|
||||||
|
background: var(--vscode-foreground);
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-switch input:checked + .settings-switch-slider {
|
||||||
|
background: var(--vscode-button-background);
|
||||||
|
border-color: var(--vscode-button-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-switch input:checked + .settings-switch-slider:before {
|
||||||
|
transform: translateX(20px);
|
||||||
|
background: var(--vscode-button-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 24px;
|
||||||
|
padding-top: 24px;
|
||||||
|
border-top: 1px solid var(--vscode-panel-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-button {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-button-primary {
|
||||||
|
background: var(--vscode-button-background);
|
||||||
|
color: var(--vscode-button-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-button-primary:hover {
|
||||||
|
background: var(--vscode-button-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-button-secondary {
|
||||||
|
background: var(--vscode-button-secondaryBackground);
|
||||||
|
color: var(--vscode-button-secondaryForeground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-button-secondary:hover {
|
||||||
|
background: var(--vscode-button-secondaryHoverBackground);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取通用设置组件的 JavaScript 脚本
|
||||||
|
*/
|
||||||
|
export function getGeneralSettingsComponentScript(): string {
|
||||||
|
return `
|
||||||
|
// 保存通用设置
|
||||||
|
function saveGeneralSettings() {
|
||||||
|
const settings = {
|
||||||
|
theme: document.getElementById('themeSelect').value,
|
||||||
|
language: document.getElementById('languageSelect').value,
|
||||||
|
autoSave: document.getElementById('autoSaveCheckbox').checked,
|
||||||
|
showTimestamp: document.getElementById('showTimestampCheckbox').checked,
|
||||||
|
fontSize: document.getElementById('fontSizeInput').value,
|
||||||
|
syntaxHighlight: document.getElementById('syntaxHighlightCheckbox').checked,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 发送消息到扩展
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'saveGeneralSettings',
|
||||||
|
settings: settings
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示保存成功提示
|
||||||
|
console.log('通用设置已保存', settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置通用设置
|
||||||
|
function resetGeneralSettings() {
|
||||||
|
document.getElementById('themeSelect').value = 'auto';
|
||||||
|
document.getElementById('languageSelect').value = 'zh-CN';
|
||||||
|
document.getElementById('autoSaveCheckbox').checked = true;
|
||||||
|
document.getElementById('showTimestampCheckbox').checked = false;
|
||||||
|
document.getElementById('fontSizeInput').value = '14';
|
||||||
|
document.getElementById('syntaxHighlightCheckbox').checked = true;
|
||||||
|
|
||||||
|
console.log('通用设置已重置为默认值');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载通用设置
|
||||||
|
function loadGeneralSettings(settings) {
|
||||||
|
if (!settings) return;
|
||||||
|
|
||||||
|
if (settings.theme) {
|
||||||
|
document.getElementById('themeSelect').value = settings.theme;
|
||||||
|
}
|
||||||
|
if (settings.language) {
|
||||||
|
document.getElementById('languageSelect').value = settings.language;
|
||||||
|
}
|
||||||
|
if (settings.autoSave !== undefined) {
|
||||||
|
document.getElementById('autoSaveCheckbox').checked = settings.autoSave;
|
||||||
|
}
|
||||||
|
if (settings.showTimestamp !== undefined) {
|
||||||
|
document.getElementById('showTimestampCheckbox').checked = settings.showTimestamp;
|
||||||
|
}
|
||||||
|
if (settings.fontSize) {
|
||||||
|
document.getElementById('fontSizeInput').value = settings.fontSize;
|
||||||
|
}
|
||||||
|
if (settings.syntaxHighlight !== undefined) {
|
||||||
|
document.getElementById('syntaxHighlightCheckbox').checked = settings.syntaxHighlight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
@ -103,7 +103,7 @@ export function getInputAreaStyles(): string {
|
|||||||
/* 居中模式:未发起对话时 */
|
/* 居中模式:未发起对话时 */
|
||||||
.input-area.centered {
|
.input-area.centered {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 55%;
|
top: 60%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
width: calc(100% - 40px);
|
width: calc(100% - 40px);
|
||||||
@ -300,7 +300,6 @@ export function getInputAreaScript(): string {
|
|||||||
${getContextDisplayScript()}
|
${getContextDisplayScript()}
|
||||||
${getContextCompressScript()}
|
${getContextCompressScript()}
|
||||||
${getOptimizeButtonScript()}
|
${getOptimizeButtonScript()}
|
||||||
${getExampleShowcaseScript()}
|
|
||||||
|
|
||||||
// 对话状态管理
|
// 对话状态管理
|
||||||
let isConversationActive = false;
|
let isConversationActive = false;
|
||||||
@ -310,6 +309,8 @@ export function getInputAreaScript(): string {
|
|||||||
let hasCheckedWorkspace = false; // 是否已经检测过工作区
|
let hasCheckedWorkspace = false; // 是否已经检测过工作区
|
||||||
let hasWorkspace = true; // 工作区状态
|
let hasWorkspace = true; // 工作区状态
|
||||||
|
|
||||||
|
${getExampleShowcaseScript()}
|
||||||
|
|
||||||
// 切换输入框布局模式
|
// 切换输入框布局模式
|
||||||
function updateInputAreaLayout() {
|
function updateInputAreaLayout() {
|
||||||
const inputArea = document.getElementById('inputArea');
|
const inputArea = document.getElementById('inputArea');
|
||||||
|
|||||||
394
src/views/moreOptionsComponent.ts
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
/**
|
||||||
|
* 更多选项组件
|
||||||
|
* 包含用户手册和用户反馈入口
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取更多选项组件的 HTML 内容
|
||||||
|
*/
|
||||||
|
export function getMoreOptionsComponentContent(): string {
|
||||||
|
return `
|
||||||
|
<div class="more-options-wrapper">
|
||||||
|
<!-- 更多选项下拉面板 -->
|
||||||
|
<div class="more-options-dropdown" id="moreOptionsDropdown">
|
||||||
|
<div class="more-options-content">
|
||||||
|
<div class="more-options-header">
|
||||||
|
<span class="more-options-title">更多选项</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="more-options-body">
|
||||||
|
<div class="more-option-item" id="userManualOption">
|
||||||
|
<div class="option-icon">
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="option-text">
|
||||||
|
<div class="option-label">用户手册</div>
|
||||||
|
<div class="option-desc">查看使用文档和帮助</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="more-option-item" id="userFeedbackOption">
|
||||||
|
<div class="option-icon">
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 12h-2v-2h2v2zm0-4h-2V6h2v4z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="option-text">
|
||||||
|
<div class="option-label">用户反馈</div>
|
||||||
|
<div class="option-desc">提交问题和建议</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户反馈二维码弹窗 -->
|
||||||
|
<div class="feedback-qrcode-modal" id="feedbackQRCodeModal">
|
||||||
|
<div class="feedback-qrcode-overlay" onclick="closeFeedbackQRCode()"></div>
|
||||||
|
<div class="feedback-qrcode-content">
|
||||||
|
<div class="feedback-qrcode-header">
|
||||||
|
<span class="feedback-qrcode-title">用户反馈</span>
|
||||||
|
<button class="feedback-qrcode-close" onclick="closeFeedbackQRCode()">
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="feedback-qrcode-body">
|
||||||
|
<img class="feedback-qrcode-image" id="feedbackQRCodeImage" alt="微信二维码" />
|
||||||
|
<p class="feedback-qrcode-text">扫描二维码添加微信反馈</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取更多选项组件的 CSS 样式
|
||||||
|
*/
|
||||||
|
export function getMoreOptionsComponentStyles(): string {
|
||||||
|
return `
|
||||||
|
.more-options-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 更多选项下拉面板 */
|
||||||
|
.more-options-dropdown {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 8px);
|
||||||
|
right: 0;
|
||||||
|
z-index: 10000;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-options-dropdown.active {
|
||||||
|
display: block;
|
||||||
|
animation: dropdownSlideIn 0.15s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dropdownSlideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-options-content {
|
||||||
|
background: var(--vscode-dropdown-background);
|
||||||
|
border: 1px solid var(--vscode-dropdown-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-options-header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-options-body {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-option-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-option-item:hover {
|
||||||
|
background: var(--vscode-list-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-option-item:active {
|
||||||
|
background: var(--vscode-list-activeSelectionBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-icon svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-desc {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用户反馈二维码弹窗 */
|
||||||
|
.feedback-qrcode-modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 20000;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-qrcode-modal.active {
|
||||||
|
display: flex;
|
||||||
|
animation: fadeIn 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-qrcode-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-qrcode-content {
|
||||||
|
position: relative;
|
||||||
|
background: var(--vscode-editor-background);
|
||||||
|
border: 1px solid var(--vscode-widget-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||||
|
max-width: 400px;
|
||||||
|
width: 90%;
|
||||||
|
animation: slideUp 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-qrcode-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid var(--vscode-widget-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-qrcode-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-qrcode-close {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-qrcode-close:hover {
|
||||||
|
background: var(--vscode-toolbar-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-qrcode-close svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-qrcode-body {
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-qrcode-image {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
border: 1px solid var(--vscode-widget-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback-qrcode-text {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取更多选项组件的 JavaScript 脚本
|
||||||
|
*/
|
||||||
|
export function getMoreOptionsComponentScript(): string {
|
||||||
|
return `
|
||||||
|
// 切换更多选项下拉面板
|
||||||
|
function toggleMoreOptionsDropdown() {
|
||||||
|
const dropdown = document.getElementById('moreOptionsDropdown');
|
||||||
|
const moreButton = document.querySelector('.more-button');
|
||||||
|
|
||||||
|
if (dropdown) {
|
||||||
|
const isActive = dropdown.classList.contains('active');
|
||||||
|
if (isActive) {
|
||||||
|
dropdown.classList.remove('active');
|
||||||
|
if (moreButton) {
|
||||||
|
moreButton.classList.remove('active');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dropdown.classList.add('active');
|
||||||
|
if (moreButton) {
|
||||||
|
moreButton.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭更多选项下拉面板
|
||||||
|
function closeMoreOptionsDropdown() {
|
||||||
|
const dropdown = document.getElementById('moreOptionsDropdown');
|
||||||
|
const moreButton = document.querySelector('.more-button');
|
||||||
|
|
||||||
|
if (dropdown) {
|
||||||
|
dropdown.classList.remove('active');
|
||||||
|
}
|
||||||
|
if (moreButton) {
|
||||||
|
moreButton.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开用户手册
|
||||||
|
function openUserManual() {
|
||||||
|
console.log('打开用户手册');
|
||||||
|
vscode.postMessage({ command: 'openUserManual' });
|
||||||
|
closeMoreOptionsDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开用户反馈
|
||||||
|
function openUserFeedback() {
|
||||||
|
console.log('打开用户反馈');
|
||||||
|
vscode.postMessage({ command: 'openUserFeedback' });
|
||||||
|
closeMoreOptionsDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示用户反馈二维码弹窗
|
||||||
|
function showFeedbackQRCode() {
|
||||||
|
const modal = document.getElementById('feedbackQRCodeModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭用户反馈二维码弹窗
|
||||||
|
function closeFeedbackQRCode() {
|
||||||
|
const modal = document.getElementById('feedbackQRCodeModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定更多选项事件
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// 绑定用户手册选项
|
||||||
|
const userManualOption = document.getElementById('userManualOption');
|
||||||
|
if (userManualOption) {
|
||||||
|
userManualOption.addEventListener('click', openUserManual);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定用户反馈选项
|
||||||
|
const userFeedbackOption = document.getElementById('userFeedbackOption');
|
||||||
|
if (userFeedbackOption) {
|
||||||
|
userFeedbackOption.addEventListener('click', openUserFeedback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击页面其他地方关闭下拉面板
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
const dropdown = document.getElementById('moreOptionsDropdown');
|
||||||
|
const moreButton = document.querySelector('.more-button');
|
||||||
|
const moreContainer = document.querySelector('.more-container');
|
||||||
|
|
||||||
|
if (dropdown && dropdown.classList.contains('active')) {
|
||||||
|
// 如果点击的不是更多按钮和下拉面板内容,则关闭
|
||||||
|
if (!moreContainer?.contains(e.target)) {
|
||||||
|
closeMoreOptionsDropdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 阻止下拉面板内容点击事件冒泡
|
||||||
|
const dropdownContent = document.querySelector('.more-options-content');
|
||||||
|
if (dropdownContent) {
|
||||||
|
dropdownContent.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
}
|
||||||
@ -60,35 +60,97 @@ export function getOptimizeButtonScript(): string {
|
|||||||
return `
|
return `
|
||||||
let isOptimized = false; // 标记是否已优化
|
let isOptimized = false; // 标记是否已优化
|
||||||
let originalText = ''; // 保存原始文本用于撤回
|
let originalText = ''; // 保存原始文本用于撤回
|
||||||
|
let isOptimizing = false; // 标记是否正在优化中
|
||||||
|
|
||||||
function handleOptimize() {
|
function handleOptimize() {
|
||||||
|
console.log('[Optimize] handleOptimize 被调用');
|
||||||
|
console.log('[Optimize] isOptimizing:', isOptimizing);
|
||||||
|
console.log('[Optimize] isOptimized:', isOptimized);
|
||||||
|
console.log('[Optimize] messageInput:', messageInput);
|
||||||
|
|
||||||
|
if (isOptimizing) {
|
||||||
|
console.log('[Optimize] 正在优化中,忽略点击');
|
||||||
|
return; // 正在优化中,忽略点击
|
||||||
|
}
|
||||||
|
|
||||||
if (isOptimized) {
|
if (isOptimized) {
|
||||||
// 撤回操作
|
// 撤回操作
|
||||||
|
console.log('[Optimize] 执行撤回操作');
|
||||||
messageInput.value = originalText;
|
messageInput.value = originalText;
|
||||||
resetOptimizeButton();
|
resetOptimizeButton();
|
||||||
} else {
|
} else {
|
||||||
// 优化操作
|
// 优化操作
|
||||||
|
const currentText = messageInput.value.trim();
|
||||||
|
console.log('[Optimize] 当前输入内容:', currentText);
|
||||||
|
console.log('[Optimize] 内容长度:', currentText.length);
|
||||||
|
|
||||||
|
if (!currentText) {
|
||||||
|
console.log('[Optimize] 输入框为空,不执行优化');
|
||||||
|
return; // 输入框为空,不执行优化
|
||||||
|
}
|
||||||
|
|
||||||
originalText = messageInput.value; // 保存原始文本
|
originalText = messageInput.value; // 保存原始文本
|
||||||
|
isOptimizing = true;
|
||||||
|
console.log('[Optimize] 开始优化,显示加载状态');
|
||||||
|
|
||||||
// 使用死数据替换输入框内容
|
// 显示加载状态
|
||||||
const optimizedTexts = [
|
showOptimizeLoading();
|
||||||
'请帮我优化这段代码,提高性能和可读性',
|
|
||||||
'请分析这个问题并给出最佳解决方案',
|
|
||||||
'请帮我重构这段代码,使其更加简洁高效',
|
|
||||||
'请检查代码中的潜在问题并提供改进建议'
|
|
||||||
];
|
|
||||||
const randomText = optimizedTexts[Math.floor(Math.random() * optimizedTexts.length)];
|
|
||||||
messageInput.value = randomText;
|
|
||||||
|
|
||||||
// 切换到撤回状态
|
// 发送优化请求到扩展
|
||||||
isOptimized = true;
|
console.log('[Optimize] 发送 optimizePrompt 消息');
|
||||||
updateOptimizeButton();
|
vscode.postMessage({
|
||||||
|
command: 'optimizePrompt',
|
||||||
|
prompt: currentText
|
||||||
|
});
|
||||||
|
console.log('[Optimize] postMessage 已发送');
|
||||||
}
|
}
|
||||||
|
|
||||||
messageInput.focus();
|
messageInput.focus();
|
||||||
autoResizeTextarea();
|
autoResizeTextarea();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理优化结果
|
||||||
|
function handleOptimizeResult(success, optimizedPrompt, error) {
|
||||||
|
isOptimizing = false;
|
||||||
|
hideOptimizeLoading();
|
||||||
|
|
||||||
|
if (success && optimizedPrompt) {
|
||||||
|
messageInput.value = optimizedPrompt;
|
||||||
|
isOptimized = true;
|
||||||
|
updateOptimizeButton();
|
||||||
|
} else {
|
||||||
|
// 优化失败,恢复原始文本
|
||||||
|
messageInput.value = originalText;
|
||||||
|
console.error('优化失败:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
messageInput.focus();
|
||||||
|
autoResizeTextarea();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showOptimizeLoading() {
|
||||||
|
const optimizeButton = document.getElementById('optimizeButton');
|
||||||
|
const optimizeIcon = document.getElementById('optimizeIcon');
|
||||||
|
if (optimizeButton && optimizeIcon) {
|
||||||
|
optimizeButton.disabled = true;
|
||||||
|
optimizeButton.style.opacity = '0.5';
|
||||||
|
// 显示加载动画
|
||||||
|
optimizeIcon.innerHTML = '<circle cx="512" cy="512" r="400" fill="none" stroke="#409eff" stroke-width="60" stroke-dasharray="1200" stroke-dashoffset="0"><animateTransform attributeName="transform" type="rotate" from="0 512 512" to="360 512 512" dur="1s" repeatCount="indefinite"/></circle>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideOptimizeLoading() {
|
||||||
|
const optimizeButton = document.getElementById('optimizeButton');
|
||||||
|
if (optimizeButton) {
|
||||||
|
optimizeButton.disabled = false;
|
||||||
|
optimizeButton.style.opacity = '1';
|
||||||
|
}
|
||||||
|
// 恢复图标会在 updateOptimizeButton 或 resetOptimizeButton 中处理
|
||||||
|
if (!isOptimized) {
|
||||||
|
resetOptimizeButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateOptimizeButton() {
|
function updateOptimizeButton() {
|
||||||
const optimizeIcon = document.getElementById('optimizeIcon');
|
const optimizeIcon = document.getElementById('optimizeIcon');
|
||||||
const optimizeTooltip = document.getElementById('optimizeTooltip');
|
const optimizeTooltip = document.getElementById('optimizeTooltip');
|
||||||
|
|||||||
@ -150,24 +150,50 @@ export function getPlanCardStyles(): string {
|
|||||||
.plan-actions {
|
.plan-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 12px;
|
||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
border-top: 1px solid var(--vscode-input-border);
|
border-top: 1px solid var(--vscode-input-border);
|
||||||
background: var(--vscode-sideBar-background);
|
background: var(--vscode-sideBar-background);
|
||||||
}
|
}
|
||||||
.plan-actions .question-options {
|
.plan-input-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.plan-input {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: var(--vscode-input-background);
|
||||||
|
color: var(--vscode-input-foreground);
|
||||||
|
border: 1px solid var(--vscode-input-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.plan-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--vscode-focusBorder);
|
||||||
|
}
|
||||||
|
.plan-btn-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
.plan-btn {
|
.plan-btn {
|
||||||
padding: 8px 18px;
|
padding: 8px 20px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
.plan-btn-submit {
|
||||||
|
background: var(--vscode-input-background);
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
border: 1px solid var(--vscode-input-border);
|
||||||
|
}
|
||||||
|
.plan-btn-submit:hover {
|
||||||
|
background: var(--vscode-list-hoverBackground);
|
||||||
|
}
|
||||||
.plan-btn-confirm {
|
.plan-btn-confirm {
|
||||||
background: var(--vscode-button-background);
|
background: var(--vscode-button-background);
|
||||||
color: var(--vscode-button-foreground);
|
color: var(--vscode-button-foreground);
|
||||||
@ -175,41 +201,26 @@ export function getPlanCardStyles(): string {
|
|||||||
.plan-btn-confirm:hover {
|
.plan-btn-confirm:hover {
|
||||||
background: var(--vscode-button-hoverBackground);
|
background: var(--vscode-button-hoverBackground);
|
||||||
}
|
}
|
||||||
.plan-btn-modify {
|
|
||||||
background: var(--vscode-input-background);
|
|
||||||
color: var(--vscode-foreground);
|
|
||||||
border: 1px solid var(--vscode-input-border);
|
|
||||||
}
|
|
||||||
.plan-btn-cancel {
|
.plan-btn-cancel {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--vscode-descriptionForeground);
|
color: var(--vscode-descriptionForeground);
|
||||||
}
|
|
||||||
.plan-actions .custom-input-container {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.plan-actions .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: 1px solid var(--vscode-input-border);
|
||||||
border-radius: 4px;
|
}
|
||||||
|
.plan-btn-cancel:hover {
|
||||||
|
background: var(--vscode-list-hoverBackground);
|
||||||
|
}
|
||||||
|
.plan-answered {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid var(--vscode-input-border);
|
||||||
|
background: var(--vscode-sideBar-background);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
.plan-actions .custom-submit {
|
.answered-label {
|
||||||
padding: 8px 18px;
|
color: var(--vscode-descriptionForeground);
|
||||||
background: var(--vscode-button-background);
|
|
||||||
color: var(--vscode-button-foreground);
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
}
|
||||||
.plan-actions .custom-submit:hover {
|
.answered-value {
|
||||||
background: var(--vscode-button-hoverBackground);
|
color: var(--vscode-textLink-foreground);
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 阶段进度条样式 */
|
/* 阶段进度条样式 */
|
||||||
@ -597,16 +608,17 @@ export function getPlanCardScript(): string {
|
|||||||
// 兼容旧格式:渲染步骤列表
|
// 兼容旧格式:渲染步骤列表
|
||||||
const stepsHtml = !hasPhases ? renderPlanSteps(segment.planSteps || []) : '';
|
const stepsHtml = !hasPhases ? renderPlanSteps(segment.planSteps || []) : '';
|
||||||
|
|
||||||
// 选项按钮
|
|
||||||
const options = ['确认执行', '修改计划', '取消'];
|
|
||||||
const optionsHtml = options.map(opt => {
|
|
||||||
const isSelected = isAnswered && opt === selectedAnswer;
|
|
||||||
return \`<button class="question-option\${isSelected ? ' selected' : ''}" data-option="\${opt}">\${opt}</button>\`;
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
// 渲染 Markdown 格式的摘要
|
// 渲染 Markdown 格式的摘要
|
||||||
const summaryHtml = renderPlanMarkdown(segment.planSummary || '');
|
const summaryHtml = renderPlanMarkdown(segment.planSummary || '');
|
||||||
|
|
||||||
|
// 已回答时显示用户的选择
|
||||||
|
const answeredHtml = isAnswered ? \`
|
||||||
|
<div class="plan-answered">
|
||||||
|
<span class="answered-label">已回复:</span>
|
||||||
|
<span class="answered-value">\${selectedAnswer}</span>
|
||||||
|
</div>
|
||||||
|
\` : '';
|
||||||
|
|
||||||
segmentDiv.innerHTML = \`
|
segmentDiv.innerHTML = \`
|
||||||
<div class="plan-card">
|
<div class="plan-card">
|
||||||
<div class="plan-header">
|
<div class="plan-header">
|
||||||
@ -618,59 +630,72 @@ export function getPlanCardScript(): string {
|
|||||||
<div class="plan-summary">\${summaryHtml}</div>
|
<div class="plan-summary">\${summaryHtml}</div>
|
||||||
\${hasPhases ? \`<div class="plan-phases">\${phasesHtml}</div>\` : \`<div class="plan-steps">\${stepsHtml}</div>\`}
|
\${hasPhases ? \`<div class="plan-phases">\${phasesHtml}</div>\` : \`<div class="plan-steps">\${stepsHtml}</div>\`}
|
||||||
</div>
|
</div>
|
||||||
<div class="plan-actions">
|
<div class="plan-actions" data-ask-id="\${segment.askId}" style="display: \${isAnswered ? 'none' : 'flex'};">
|
||||||
<div class="question-options" data-ask-id="\${segment.askId}">\${optionsHtml}</div>
|
<div class="plan-input-row">
|
||||||
<div class="custom-input-container" style="display: \${isAnswered ? 'none' : 'flex'};">
|
<input type="text" class="plan-input" placeholder="输入修改建议..." />
|
||||||
<input type="text" class="custom-input" placeholder="输入修改建议..." />
|
<button class="plan-btn plan-btn-submit">提交修改</button>
|
||||||
<button class="custom-submit">提交</button>
|
</div>
|
||||||
|
<div class="plan-btn-row">
|
||||||
|
<button class="plan-btn plan-btn-confirm">确认执行</button>
|
||||||
|
<button class="plan-btn plan-btn-cancel">取消</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
\${answeredHtml}
|
||||||
</div>
|
</div>
|
||||||
\`;
|
\`;
|
||||||
|
|
||||||
// 只在未回答时添加事件监听
|
// 只在未回答时添加事件监听
|
||||||
if (!isAnswered) {
|
if (!isAnswered) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const optionButtons = segmentDiv.querySelectorAll('.question-option');
|
const submitBtn = segmentDiv.querySelector('.plan-btn-submit');
|
||||||
optionButtons.forEach(btn => {
|
const confirmBtn = segmentDiv.querySelector('.plan-btn-confirm');
|
||||||
btn.addEventListener('click', function() {
|
const cancelBtn = segmentDiv.querySelector('.plan-btn-cancel');
|
||||||
const option = this.getAttribute('data-option');
|
const planInput = segmentDiv.querySelector('.plan-input');
|
||||||
// 发送答案到后端
|
|
||||||
handleQuestionAnswerInSegment(segment.askId, option, segmentDiv);
|
|
||||||
// 同时发送 planAction 用于模式切换
|
|
||||||
const actionMap = {
|
|
||||||
'确认执行': 'confirm',
|
|
||||||
'修改计划': 'modify',
|
|
||||||
'取消': 'cancel'
|
|
||||||
};
|
|
||||||
vscode.postMessage({
|
|
||||||
command: 'planAction',
|
|
||||||
action: actionMap[option] || option,
|
|
||||||
planTitle: segment.planTitle,
|
|
||||||
model: getCurrentModel()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const submitBtn = segmentDiv.querySelector('.custom-submit');
|
// 提交修改按钮
|
||||||
const customInput = segmentDiv.querySelector('.custom-input');
|
if (submitBtn && planInput) {
|
||||||
if (submitBtn && customInput) {
|
|
||||||
submitBtn.addEventListener('click', function() {
|
submitBtn.addEventListener('click', function() {
|
||||||
const customValue = customInput.value.trim();
|
const inputValue = planInput.value.trim();
|
||||||
if (customValue) {
|
if (inputValue) {
|
||||||
handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv);
|
handleQuestionAnswerInSegment(segment.askId, inputValue, segmentDiv);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
customInput.addEventListener('keypress', function(e) {
|
// 回车键提交修改
|
||||||
|
planInput.addEventListener('keypress', function(e) {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
const customValue = customInput.value.trim();
|
const inputValue = planInput.value.trim();
|
||||||
if (customValue) {
|
if (inputValue) {
|
||||||
handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv);
|
handleQuestionAnswerInSegment(segment.askId, inputValue, segmentDiv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确认执行按钮
|
||||||
|
if (confirmBtn) {
|
||||||
|
confirmBtn.addEventListener('click', function() {
|
||||||
|
handleQuestionAnswerInSegment(segment.askId, '确认执行', segmentDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消按钮 - 直接中止对话,不发送给智能体
|
||||||
|
if (cancelBtn) {
|
||||||
|
cancelBtn.addEventListener('click', function() {
|
||||||
|
// 标记问题已回答
|
||||||
|
answeredQuestions.set(segment.askId, '取消');
|
||||||
|
segmentDiv.classList.add('answered');
|
||||||
|
|
||||||
|
// 隐藏操作按钮
|
||||||
|
const actionsDiv = segmentDiv.querySelector('.plan-actions');
|
||||||
|
if (actionsDiv) {
|
||||||
|
actionsDiv.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送中止对话命令
|
||||||
|
vscode.postMessage({ command: 'abortDialog' });
|
||||||
|
});
|
||||||
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -703,30 +728,65 @@ export function getPlanCardScript(): string {
|
|||||||
<div class="plan-summary">\${summaryHtml}</div>
|
<div class="plan-summary">\${summaryHtml}</div>
|
||||||
\${hasPhases ? \`<div class="plan-phases">\${phasesHtml}</div>\` : \`<div class="plan-steps">\${stepsHtml}</div>\`}
|
\${hasPhases ? \`<div class="plan-phases">\${phasesHtml}</div>\` : \`<div class="plan-steps">\${stepsHtml}</div>\`}
|
||||||
</div>
|
</div>
|
||||||
<div class="plan-actions">
|
<div class="plan-actions" data-ask-id="\${segment.askId}">
|
||||||
<button class="plan-btn plan-btn-confirm" data-action="confirm">确认执行</button>
|
<div class="plan-input-row">
|
||||||
<button class="plan-btn plan-btn-modify" data-action="modify">修改计划</button>
|
<input type="text" class="plan-input" placeholder="输入修改建议..." />
|
||||||
<button class="plan-btn plan-btn-cancel" data-action="cancel">取消</button>
|
<button class="plan-btn plan-btn-submit">提交修改</button>
|
||||||
|
</div>
|
||||||
|
<div class="plan-btn-row">
|
||||||
|
<button class="plan-btn plan-btn-confirm">确认执行</button>
|
||||||
|
<button class="plan-btn plan-btn-cancel">取消</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
\`;
|
\`;
|
||||||
|
|
||||||
// 绑定按钮事件
|
// 绑定按钮事件(静态渲染时也需要能响应)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const planCard = segmentDiv.querySelector('.plan-card');
|
const submitBtn = segmentDiv.querySelector('.plan-btn-submit');
|
||||||
if (planCard) {
|
const confirmBtn = segmentDiv.querySelector('.plan-btn-confirm');
|
||||||
planCard.querySelectorAll('.plan-btn').forEach(btn => {
|
const cancelBtn = segmentDiv.querySelector('.plan-btn-cancel');
|
||||||
btn.addEventListener('click', (e) => {
|
const planInput = segmentDiv.querySelector('.plan-input');
|
||||||
const action = e.currentTarget?.dataset?.action;
|
|
||||||
|
// 提交修改按钮
|
||||||
|
if (submitBtn && planInput) {
|
||||||
|
submitBtn.addEventListener('click', function() {
|
||||||
|
const inputValue = planInput.value.trim();
|
||||||
|
if (inputValue) {
|
||||||
vscode.postMessage({
|
vscode.postMessage({
|
||||||
command: 'planAction',
|
command: 'submitAnswer',
|
||||||
action: action,
|
askId: segment.askId,
|
||||||
planTitle: segment.planTitle,
|
selected: [inputValue],
|
||||||
model: getCurrentModel()
|
customInput: inputValue
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认执行按钮
|
||||||
|
if (confirmBtn) {
|
||||||
|
confirmBtn.addEventListener('click', function() {
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'submitAnswer',
|
||||||
|
askId: segment.askId,
|
||||||
|
selected: ['确认执行'],
|
||||||
|
customInput: '确认执行'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 取消按钮 - 直接中止对话
|
||||||
|
if (cancelBtn) {
|
||||||
|
cancelBtn.addEventListener('click', function() {
|
||||||
|
// 隐藏操作按钮
|
||||||
|
const actionsDiv = segmentDiv.querySelector('.plan-actions');
|
||||||
|
if (actionsDiv) {
|
||||||
|
actionsDiv.style.display = 'none';
|
||||||
|
}
|
||||||
|
// 发送中止对话命令
|
||||||
|
vscode.postMessage({ command: 'abortDialog' });
|
||||||
|
});
|
||||||
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
177
src/views/rulesSettingsComponent.ts
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
/**
|
||||||
|
* 获取规则设置组件的 HTML 内容
|
||||||
|
*/
|
||||||
|
export function getRulesSettingsComponentContent(): string {
|
||||||
|
return `
|
||||||
|
<div class="rules-settings">
|
||||||
|
<h3 class="settings-section-title">规则设置</h3>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<div class="settings-item">
|
||||||
|
<div class="settings-item-header">
|
||||||
|
<label class="settings-item-label">启用自定义规则</label>
|
||||||
|
<span class="settings-item-description">使用自定义规则来控制 AI 行为</span>
|
||||||
|
</div>
|
||||||
|
<label class="settings-switch">
|
||||||
|
<input type="checkbox" id="enableCustomRulesCheckbox" checked>
|
||||||
|
<span class="settings-switch-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<h4 class="settings-subsection-title">系统规则</h4>
|
||||||
|
<div class="rules-textarea-container">
|
||||||
|
<textarea
|
||||||
|
class="rules-textarea"
|
||||||
|
id="systemRulesTextarea"
|
||||||
|
placeholder="在此输入系统规则,例如: - 始终使用中文回复 - 代码注释要详细 - 遵循项目编码规范"
|
||||||
|
rows="8"
|
||||||
|
></textarea>
|
||||||
|
<div class="rules-textarea-hint">
|
||||||
|
系统规则会在每次对话开始时应用
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<h4 class="settings-subsection-title">代码生成规则</h4>
|
||||||
|
<div class="rules-textarea-container">
|
||||||
|
<textarea
|
||||||
|
class="rules-textarea"
|
||||||
|
id="codeRulesTextarea"
|
||||||
|
placeholder="在此输入代码生成规则,例如: - 使用 TypeScript 严格模式 - 函数命名使用驼峰命名法 - 添加必要的错误处理"
|
||||||
|
rows="8"
|
||||||
|
></textarea>
|
||||||
|
<div class="rules-textarea-hint">
|
||||||
|
这些规则会在生成代码时应用
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<h4 class="settings-subsection-title">Verilog 规则</h4>
|
||||||
|
<div class="rules-textarea-container">
|
||||||
|
<textarea
|
||||||
|
class="rules-textarea"
|
||||||
|
id="verilogRulesTextarea"
|
||||||
|
placeholder="在此输入 Verilog 代码规则,例如: - 使用非阻塞赋值 (<=) 在时序逻辑中 - 模块命名使用小写加下划线 - 添加详细的端口注释"
|
||||||
|
rows="8"
|
||||||
|
></textarea>
|
||||||
|
<div class="rules-textarea-hint">
|
||||||
|
这些规则会在生成 Verilog 代码时应用
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-actions">
|
||||||
|
<button class="settings-button settings-button-primary" onclick="saveRulesSettings()">
|
||||||
|
保存规则
|
||||||
|
</button>
|
||||||
|
<button class="settings-button settings-button-secondary" onclick="resetRulesSettings()">
|
||||||
|
重置为默认
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取规则设置组件的 CSS 样式
|
||||||
|
*/
|
||||||
|
export function getRulesSettingsComponentStyles(): string {
|
||||||
|
return `
|
||||||
|
.rules-settings {
|
||||||
|
max-width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-textarea-container {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--vscode-input-background);
|
||||||
|
color: var(--vscode-input-foreground);
|
||||||
|
border: 1px solid var(--vscode-input-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: var(--vscode-editor-font-family);
|
||||||
|
line-height: 1.5;
|
||||||
|
resize: vertical;
|
||||||
|
outline: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-textarea:focus {
|
||||||
|
border-color: var(--vscode-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-textarea::placeholder {
|
||||||
|
color: var(--vscode-input-placeholderForeground);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules-textarea-hint {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--vscode-descriptionForeground);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取规则设置组件的 JavaScript 脚本
|
||||||
|
*/
|
||||||
|
export function getRulesSettingsComponentScript(): string {
|
||||||
|
return `
|
||||||
|
// 保存规则设置
|
||||||
|
function saveRulesSettings() {
|
||||||
|
const settings = {
|
||||||
|
enableCustomRules: document.getElementById('enableCustomRulesCheckbox').checked,
|
||||||
|
systemRules: document.getElementById('systemRulesTextarea').value,
|
||||||
|
codeRules: document.getElementById('codeRulesTextarea').value,
|
||||||
|
verilogRules: document.getElementById('verilogRulesTextarea').value,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 发送消息到扩展
|
||||||
|
vscode.postMessage({
|
||||||
|
command: 'saveRulesSettings',
|
||||||
|
settings: settings
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示保存成功提示
|
||||||
|
console.log('规则设置已保存', settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置规则设置
|
||||||
|
function resetRulesSettings() {
|
||||||
|
document.getElementById('enableCustomRulesCheckbox').checked = true;
|
||||||
|
document.getElementById('systemRulesTextarea').value = '';
|
||||||
|
document.getElementById('codeRulesTextarea').value = '';
|
||||||
|
document.getElementById('verilogRulesTextarea').value = '';
|
||||||
|
|
||||||
|
console.log('规则设置已重置为默认值');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载规则设置
|
||||||
|
function loadRulesSettings(settings) {
|
||||||
|
if (!settings) return;
|
||||||
|
|
||||||
|
if (settings.enableCustomRules !== undefined) {
|
||||||
|
document.getElementById('enableCustomRulesCheckbox').checked = settings.enableCustomRules;
|
||||||
|
}
|
||||||
|
if (settings.systemRules) {
|
||||||
|
document.getElementById('systemRulesTextarea').value = settings.systemRules;
|
||||||
|
}
|
||||||
|
if (settings.codeRules) {
|
||||||
|
document.getElementById('codeRulesTextarea').value = settings.codeRules;
|
||||||
|
}
|
||||||
|
if (settings.verilogRules) {
|
||||||
|
document.getElementById('verilogRulesTextarea').value = settings.verilogRules;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
248
src/views/settingsComponent.ts
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
import {
|
||||||
|
getGeneralSettingsComponentContent,
|
||||||
|
getGeneralSettingsComponentStyles,
|
||||||
|
getGeneralSettingsComponentScript,
|
||||||
|
} from "./generalSettingsComponent";
|
||||||
|
import {
|
||||||
|
getRulesSettingsComponentContent,
|
||||||
|
getRulesSettingsComponentStyles,
|
||||||
|
getRulesSettingsComponentScript,
|
||||||
|
} from "./rulesSettingsComponent";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设置面板的 HTML 内容
|
||||||
|
*/
|
||||||
|
export function getSettingsComponentContent(): string {
|
||||||
|
return `
|
||||||
|
<div class="settings-modal" id="settingsModal">
|
||||||
|
<div class="settings-modal-overlay" onclick="closeSettingsModal()"></div>
|
||||||
|
<div class="settings-modal-content">
|
||||||
|
<div class="settings-modal-header">
|
||||||
|
<h2 class="settings-modal-title">设置</h2>
|
||||||
|
<button class="settings-modal-close" onclick="closeSettingsModal()" title="关闭">
|
||||||
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-modal-body">
|
||||||
|
<div class="settings-nav">
|
||||||
|
<button class="settings-nav-item active" data-tab="general" onclick="switchSettingsTab('general')">
|
||||||
|
通用
|
||||||
|
</button>
|
||||||
|
<button class="settings-nav-item" data-tab="rules" onclick="switchSettingsTab('rules')">
|
||||||
|
规则
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-content">
|
||||||
|
<div class="settings-tab-content active" id="generalSettings">
|
||||||
|
${getGeneralSettingsComponentContent()}
|
||||||
|
</div>
|
||||||
|
<div class="settings-tab-content" id="rulesSettings">
|
||||||
|
${getRulesSettingsComponentContent()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设置面板的 CSS 样式
|
||||||
|
*/
|
||||||
|
export function getSettingsComponentStyles(): string {
|
||||||
|
return `
|
||||||
|
.settings-modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal.active {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-content {
|
||||||
|
position: relative;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 800px;
|
||||||
|
height: 80%;
|
||||||
|
max-height: 600px;
|
||||||
|
background: var(--vscode-editor-background);
|
||||||
|
border: 1px solid var(--vscode-panel-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid var(--vscode-panel-border);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-close {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-close:hover {
|
||||||
|
background: var(--vscode-toolbar-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-close svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-body {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-nav {
|
||||||
|
width: 180px;
|
||||||
|
padding: 16px 0;
|
||||||
|
border-right: 1px solid var(--vscode-panel-border);
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-nav-item {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-nav-item:hover {
|
||||||
|
background: var(--vscode-list-hoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-nav-item.active {
|
||||||
|
background: var(--vscode-list-activeSelectionBackground);
|
||||||
|
color: var(--vscode-list-activeSelectionForeground);
|
||||||
|
border-left-color: var(--vscode-focusBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tab-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-tab-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
${getGeneralSettingsComponentStyles()}
|
||||||
|
${getRulesSettingsComponentStyles()}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设置面板的 JavaScript 脚本
|
||||||
|
*/
|
||||||
|
export function getSettingsComponentScript(): string {
|
||||||
|
return `
|
||||||
|
${getGeneralSettingsComponentScript()}
|
||||||
|
${getRulesSettingsComponentScript()}
|
||||||
|
|
||||||
|
// 打开设置面板
|
||||||
|
function openSettingsModal() {
|
||||||
|
const modal = document.getElementById('settingsModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭设置面板
|
||||||
|
function closeSettingsModal() {
|
||||||
|
const modal = document.getElementById('settingsModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换设置标签页
|
||||||
|
function switchSettingsTab(tabName) {
|
||||||
|
// 更新导航项状态
|
||||||
|
const navItems = document.querySelectorAll('.settings-nav-item');
|
||||||
|
navItems.forEach(item => {
|
||||||
|
if (item.dataset.tab === tabName) {
|
||||||
|
item.classList.add('active');
|
||||||
|
} else {
|
||||||
|
item.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新内容区域
|
||||||
|
const tabContents = document.querySelectorAll('.settings-tab-content');
|
||||||
|
tabContents.forEach(content => {
|
||||||
|
if (content.id === tabName + 'Settings') {
|
||||||
|
content.classList.add('active');
|
||||||
|
} else {
|
||||||
|
content.classList.remove('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阻止点击模态框内容时关闭
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
const modalContent = document.querySelector('.settings-modal-content');
|
||||||
|
if (modalContent && modalContent.contains(event.target)) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
}
|
||||||
@ -14,14 +14,20 @@ export function getUserInfoComponentContent(): string {
|
|||||||
<div class="user-detail-dropdown" id="userDetailDropdown">
|
<div class="user-detail-dropdown" id="userDetailDropdown">
|
||||||
<div class="user-detail-content">
|
<div class="user-detail-content">
|
||||||
<div class="user-detail-header">
|
<div class="user-detail-header">
|
||||||
<div class="user-avatar-small">
|
<div class="user-info-row">
|
||||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<div class="user-avatar-small clickable" id="userAvatarClickable">
|
||||||
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" fill="currentColor"/>
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
</svg>
|
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="user-name-tier">
|
||||||
|
<div class="user-detail-name clickable" id="userDetailName">加载中...</div>
|
||||||
|
<img class="tier-icon-inline" id="tierIconInline" style="display: none;" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-name-tier">
|
<!-- 升级到Pro按钮 (仅BASIC会员显示) -->
|
||||||
<div class="user-detail-name" id="userDetailName">加载中...</div>
|
<div class="upgrade-pro-wrapper" id="upgradeProWrapper" style="display: none;">
|
||||||
<img class="tier-icon-inline" id="tierIconInline" style="display: none;" />
|
<button class="upgrade-pro-btn" id="upgradeProBtn">升级到 Pro</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -30,10 +36,31 @@ export function getUserInfoComponentContent(): string {
|
|||||||
<span class="detail-label">剩余 Credits</span>
|
<span class="detail-label">剩余 Credits</span>
|
||||||
<span class="detail-value" id="creditsDetail">-</span>
|
<span class="detail-value" id="creditsDetail">-</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="user-detail-item logout-item" id="logoutItem">
|
||||||
|
<span class="detail-label">账户管理</span>
|
||||||
|
<span class="detail-value logout-link">退出登录</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 退出登录确认对话框 -->
|
||||||
|
<div class="logout-confirm-modal" id="logoutConfirmModal">
|
||||||
|
<div class="logout-confirm-overlay"></div>
|
||||||
|
<div class="logout-confirm-content">
|
||||||
|
<div class="logout-confirm-header">
|
||||||
|
<h3>确认退出</h3>
|
||||||
|
</div>
|
||||||
|
<div class="logout-confirm-body">
|
||||||
|
<p>确定要退出登录吗?</p>
|
||||||
|
</div>
|
||||||
|
<div class="logout-confirm-footer">
|
||||||
|
<button class="logout-confirm-btn logout-cancel-btn" id="logoutCancelBtn">取消</button>
|
||||||
|
<button class="logout-confirm-btn logout-ok-btn" id="logoutOkBtn">确定</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,12 +111,18 @@ export function getUserInfoComponentStyles(): string {
|
|||||||
.user-detail-header {
|
.user-detail-header {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
background: linear-gradient(135deg, rgba(0, 122, 204, 0.1) 0%, rgba(88, 166, 255, 0.05) 100%);
|
background: linear-gradient(135deg, rgba(0, 122, 204, 0.1) 0%, rgba(88, 166, 255, 0.05) 100%);
|
||||||
border-bottom: 1px solid var(--vscode-widget-border);
|
border-bottom: 1px solid var(--vscode-widget-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-info-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.user-avatar-small {
|
.user-avatar-small {
|
||||||
width: 26px;
|
width: 26px;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
@ -100,6 +133,16 @@ export function getUserInfoComponentStyles(): string {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-shadow: 0 2px 8px rgba(0, 122, 204, 0.3);
|
box-shadow: 0 2px 8px rgba(0, 122, 204, 0.3);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar-small.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar-small.clickable:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 122, 204, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-avatar-small svg {
|
.user-avatar-small svg {
|
||||||
@ -119,6 +162,16 @@ export function getUserInfoComponentStyles(): string {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--vscode-foreground);
|
color: var(--vscode-foreground);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-detail-name.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-detail-name.clickable:hover {
|
||||||
|
color: #007acc;
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tier-icon-inline {
|
.tier-icon-inline {
|
||||||
@ -152,6 +205,19 @@ export function getUserInfoComponentStyles(): string {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logout-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-item:hover {
|
||||||
|
background: var(--vscode-list-hoverBackground);
|
||||||
|
border-color: rgba(204, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-item:hover .logout-link {
|
||||||
|
color: #f48771;
|
||||||
|
}
|
||||||
|
|
||||||
.detail-label {
|
.detail-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@ -168,6 +234,11 @@ export function getUserInfoComponentStyles(): string {
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logout-link {
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
.tier-icon-large {
|
.tier-icon-large {
|
||||||
height: 20px;
|
height: 20px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
@ -180,6 +251,136 @@ export function getUserInfoComponentStyles(): string {
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.upgrade-pro-wrapper {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgrade-pro-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: linear-gradient(135deg, #007acc 0%, #58a6ff 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 122, 204, 0.2);
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgrade-pro-btn:hover {
|
||||||
|
background: linear-gradient(135deg, #0098ff 0%, #6bb6ff 100%);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 122, 204, 0.4);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgrade-pro-btn:active {
|
||||||
|
transform: translateY(0) scale(0.98);
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 122, 204, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 退出登录确认对话框 */
|
||||||
|
.logout-confirm-modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 20000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-confirm-modal.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-confirm-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-confirm-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: var(--vscode-editor-background);
|
||||||
|
border: 1px solid var(--vscode-widget-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
min-width: 320px;
|
||||||
|
max-width: 400px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-confirm-header {
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid var(--vscode-widget-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-confirm-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-confirm-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-confirm-body p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-confirm-footer {
|
||||||
|
padding: 12px 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
border-top: 1px solid var(--vscode-widget-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-confirm-btn {
|
||||||
|
padding: 6px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-cancel-btn {
|
||||||
|
background: var(--vscode-button-secondaryBackground);
|
||||||
|
color: var(--vscode-button-secondaryForeground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-cancel-btn:hover {
|
||||||
|
background: var(--vscode-button-secondaryHoverBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-ok-btn {
|
||||||
|
background: #f48771;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-ok-btn:hover {
|
||||||
|
background: #e67361;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-confirm-btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,6 +415,44 @@ export function getUserInfoComponentScript(): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
function logout() {
|
||||||
|
console.log("显示退出登录确认对话框");
|
||||||
|
// 显示确认对话框
|
||||||
|
const modal = document.getElementById('logoutConfirmModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认退出登录
|
||||||
|
function confirmLogout() {
|
||||||
|
console.log("确认退出登录");
|
||||||
|
vscode.postMessage({ command: 'logout' });
|
||||||
|
// 关闭确认对话框
|
||||||
|
closeLogoutConfirmModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭退出登录确认对话框
|
||||||
|
function closeLogoutConfirmModal() {
|
||||||
|
const modal = document.getElementById('logoutConfirmModal');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳转到 IC Coder 官网
|
||||||
|
function openICCoder() {
|
||||||
|
console.log("跳转到 IC Coder 官网");
|
||||||
|
vscode.postMessage({ command: 'openICCoder' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 升级到Pro
|
||||||
|
function upgradeToPro() {
|
||||||
|
console.log("升级到 Pro - 跳转到 IC Coder 官网");
|
||||||
|
vscode.postMessage({ command: 'openExternalUrl', url: 'https://www.iccoder.com' });
|
||||||
|
}
|
||||||
|
|
||||||
// 关闭用户详情下拉面板
|
// 关闭用户详情下拉面板
|
||||||
function closeUserDetailModal() {
|
function closeUserDetailModal() {
|
||||||
const dropdown = document.getElementById('userDetailDropdown');
|
const dropdown = document.getElementById('userDetailDropdown');
|
||||||
@ -260,6 +499,19 @@ export function getUserInfoComponentScript(): string {
|
|||||||
} else {
|
} else {
|
||||||
console.warn('[UserInfoComponent] creditsDetail 元素未找到');
|
console.warn('[UserInfoComponent] creditsDetail 元素未找到');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示或隐藏升级到Pro按钮 (仅BASIC会员显示)
|
||||||
|
const upgradeProWrapper = document.getElementById('upgradeProWrapper');
|
||||||
|
const tierCode = currentUserInfo.membership?.tierCode;
|
||||||
|
if (upgradeProWrapper) {
|
||||||
|
if (tierCode === 'BASIC') {
|
||||||
|
upgradeProWrapper.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
upgradeProWrapper.style.display = 'none';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('[UserInfoComponent] upgradeProWrapper 元素未找到');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新用户信息显示
|
// 更新用户信息显示
|
||||||
@ -275,6 +527,66 @@ export function getUserInfoComponentScript(): string {
|
|||||||
|
|
||||||
// 绑定下拉面板事件
|
// 绑定下拉面板事件
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// 绑定退出登录卡片点击事件
|
||||||
|
const logoutItem = document.getElementById('logoutItem');
|
||||||
|
if (logoutItem) {
|
||||||
|
logoutItem.addEventListener('click', () => {
|
||||||
|
logout();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定退出登录确认对话框按钮
|
||||||
|
const logoutOkBtn = document.getElementById('logoutOkBtn');
|
||||||
|
if (logoutOkBtn) {
|
||||||
|
logoutOkBtn.addEventListener('click', () => {
|
||||||
|
confirmLogout();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const logoutCancelBtn = document.getElementById('logoutCancelBtn');
|
||||||
|
if (logoutCancelBtn) {
|
||||||
|
logoutCancelBtn.addEventListener('click', () => {
|
||||||
|
closeLogoutConfirmModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击遮罩层关闭对话框
|
||||||
|
const logoutConfirmModal = document.getElementById('logoutConfirmModal');
|
||||||
|
if (logoutConfirmModal) {
|
||||||
|
const overlay = logoutConfirmModal.querySelector('.logout-confirm-overlay');
|
||||||
|
if (overlay) {
|
||||||
|
overlay.addEventListener('click', () => {
|
||||||
|
closeLogoutConfirmModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定升级到Pro按钮
|
||||||
|
const upgradeProBtn = document.getElementById('upgradeProBtn');
|
||||||
|
if (upgradeProBtn) {
|
||||||
|
upgradeProBtn.addEventListener('click', () => {
|
||||||
|
upgradeToPro();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定头像点击事件
|
||||||
|
const userAvatar = document.getElementById('userAvatarClickable');
|
||||||
|
if (userAvatar) {
|
||||||
|
userAvatar.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
openICCoder();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定用户名点击事件
|
||||||
|
const userName = document.getElementById('userDetailName');
|
||||||
|
if (userName) {
|
||||||
|
userName.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
openICCoder();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 点击页面其他地方关闭下拉面板
|
// 点击页面其他地方关闭下拉面板
|
||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
const dropdown = document.getElementById('userDetailDropdown');
|
const dropdown = document.getElementById('userDetailDropdown');
|
||||||
|
|||||||
@ -33,7 +33,8 @@ export function getWebviewContent(
|
|||||||
autoIconUri?: string,
|
autoIconUri?: string,
|
||||||
liteIconUri?: string,
|
liteIconUri?: string,
|
||||||
syIconUri?: string,
|
syIconUri?: string,
|
||||||
maxIconUri?: string
|
maxIconUri?: string,
|
||||||
|
qrCodeUri?: string
|
||||||
): string {
|
): string {
|
||||||
// 获取当前环境,只在 dev 和 test 环境下显示快速操作按钮
|
// 获取当前环境,只在 dev 和 test 环境下显示快速操作按钮
|
||||||
const currentEnv = getCurrentEnv();
|
const currentEnv = getCurrentEnv();
|
||||||
@ -72,7 +73,10 @@ export function getWebviewContent(
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.header h1 {
|
.header h1 {
|
||||||
color: var(--vscode-button-background);
|
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;
|
margin: 0 0 8px 0;
|
||||||
}
|
}
|
||||||
.chat-container {
|
.chat-container {
|
||||||
@ -395,11 +399,11 @@ export function getWebviewContent(
|
|||||||
${getConversationHistoryBarContent()}
|
${getConversationHistoryBarContent()}
|
||||||
${getProgressBarContent()}
|
${getProgressBarContent()}
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div style="display: flex; align-items: center; justify-content: center; gap: 10px;">
|
<div style="display: flex; align-items: center; justify-content: center; gap: 15px;">
|
||||||
<img src="${iconUri}" alt="IC Coder" style="width: 28px; height: 28px;" />
|
<img src="${iconUri}" alt="IC Coder" style="width: 48px; height: 48px;" />
|
||||||
<h1 style="margin: 0;">IC Coder</h1>
|
<h1 style="margin: 0; font-size: 36px;">IC Coder</h1>
|
||||||
</div>
|
</div>
|
||||||
<p>专注于真实FPGA研发的Verilog智能体编程平台</p>
|
<p style="font-size: 16px; margin-top: 12px;">专注于真实FPGA研发的Verilog智能体编程平台</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chat-container">
|
<div class="chat-container">
|
||||||
@ -428,6 +432,7 @@ export function getWebviewContent(
|
|||||||
<script>
|
<script>
|
||||||
console.log('[WebView] 脚本开始执行');
|
console.log('[WebView] 脚本开始执行');
|
||||||
const vscode = acquireVsCodeApi();
|
const vscode = acquireVsCodeApi();
|
||||||
|
window.vscode = vscode; // 确保全局可访问
|
||||||
console.log('[WebView] vscode API 已获取');
|
console.log('[WebView] vscode API 已获取');
|
||||||
const messageInput = document.getElementById('messageInput');
|
const messageInput = document.getElementById('messageInput');
|
||||||
const modeSelect = document.getElementById('modeSelect');
|
const modeSelect = document.getElementById('modeSelect');
|
||||||
@ -438,6 +443,12 @@ export function getWebviewContent(
|
|||||||
let loadingIndicator = null;
|
let loadingIndicator = null;
|
||||||
let currentSegmentedMessage = null; // 当前分段消息容器
|
let currentSegmentedMessage = null; // 当前分段消息容器
|
||||||
|
|
||||||
|
// 设置二维码图片
|
||||||
|
const feedbackQRCodeImage = document.getElementById('feedbackQRCodeImage');
|
||||||
|
if (feedbackQRCodeImage && '${qrCodeUri}') {
|
||||||
|
feedbackQRCodeImage.src = '${qrCodeUri}';
|
||||||
|
}
|
||||||
|
|
||||||
// ========== 模式选择器脚本(直接内联,避免模板字符串嵌套问题)==========
|
// ========== 模式选择器脚本(直接内联,避免模板字符串嵌套问题)==========
|
||||||
let currentMode = 'agent';
|
let currentMode = 'agent';
|
||||||
|
|
||||||
@ -596,11 +607,13 @@ export function getWebviewContent(
|
|||||||
tierName: message.userInfo.tierName,
|
tierName: message.userInfo.tierName,
|
||||||
tierIconUrl: message.tierIconUrl,
|
tierIconUrl: message.tierIconUrl,
|
||||||
registerTime: message.userInfo.registerTime || message.userInfo.createdAt,
|
registerTime: message.userInfo.registerTime || message.userInfo.createdAt,
|
||||||
credits: message.userInfo.credits
|
credits: message.userInfo.credits,
|
||||||
|
membership: message.userInfo.membership
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[WebView] 显示用户信息:', userInfoData);
|
console.log('[WebView] 显示用户信息:', userInfoData);
|
||||||
console.log('[WebView] userInfoData.credits:', userInfoData.credits);
|
console.log('[WebView] userInfoData.credits:', userInfoData.credits);
|
||||||
|
console.log('[WebView] userInfoData.membership:', userInfoData.membership);
|
||||||
|
|
||||||
// 调用更新用户头像图标按钮的函数
|
// 调用更新用户头像图标按钮的函数
|
||||||
if (typeof updateUserAvatarIconButton === 'function') {
|
if (typeof updateUserAvatarIconButton === 'function') {
|
||||||
@ -611,6 +624,14 @@ export function getWebviewContent(
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'showFeedbackQRCode':
|
||||||
|
// 显示用户反馈二维码弹窗
|
||||||
|
console.log('[WebView] 显示用户反馈二维码弹窗');
|
||||||
|
if (typeof showFeedbackQRCode === 'function') {
|
||||||
|
showFeedbackQRCode();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'resetSegmentedMessage':
|
case 'resetSegmentedMessage':
|
||||||
// 重置分段消息容器(停止对话时调用)
|
// 重置分段消息容器(停止对话时调用)
|
||||||
console.log('[WebView] 重置分段消息容器');
|
console.log('[WebView] 重置分段消息容器');
|
||||||
@ -634,6 +655,14 @@ export function getWebviewContent(
|
|||||||
if (typeof hasWorkspace !== 'undefined') {
|
if (typeof hasWorkspace !== 'undefined') {
|
||||||
hasWorkspace = message.hasWorkspace;
|
hasWorkspace = message.hasWorkspace;
|
||||||
console.log('[WebView] 工作区状态:', hasWorkspace);
|
console.log('[WebView] 工作区状态:', hasWorkspace);
|
||||||
|
|
||||||
|
// 如果有待发送的示例,且工作区存在,则发送
|
||||||
|
if (hasWorkspace && typeof pendingExampleIndex !== 'undefined' && pendingExampleIndex >= 0) {
|
||||||
|
if (typeof doSendExample === 'function') {
|
||||||
|
doSendExample(pendingExampleIndex);
|
||||||
|
pendingExampleIndex = -1; // 重置
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -742,6 +771,13 @@ export function getWebviewContent(
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'optimizeResult':
|
||||||
|
// 处理提示词优化结果
|
||||||
|
if (typeof handleOptimizeResult === 'function') {
|
||||||
|
handleOptimizeResult(message.success, message.optimizedPrompt, message.error);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.log('[WebView] 未处理的消息类型:', message.command);
|
console.log('[WebView] 未处理的消息类型:', message.command);
|
||||||
}
|
}
|
||||||
|
|||||||