Merge branch 'feat/back-to-front' of https://git.pengyejiatu.com/pengyejiatu/IC-Coder-Plugin into feat/back-to-front
This commit is contained in:
@ -31,28 +31,28 @@ export interface IccoderConfig {
|
||||
|
||||
/** 环境配置 */
|
||||
const ENV_CONFIG: Record<Environment, IccoderConfig> = {
|
||||
/** 本地开发环境 */
|
||||
/** 本地开发环境 - 通过 Gateway 路由 */
|
||||
dev: {
|
||||
backendUrl: "http://localhost:2233",
|
||||
backendUrlStrongeLoop: "http://192.168.1.108:2029",
|
||||
backendUrl: "http://localhost:8080/iccoder",
|
||||
backendUrlStrongeLoop: "http://localhost:8080",
|
||||
loginUrl: "http://localhost/login",
|
||||
timeout: 300000,
|
||||
userId: "default-user",
|
||||
serviceTier: "max", // 默认使用 max
|
||||
},
|
||||
/** 测试服务器环境 */
|
||||
/** 测试服务器环境 - 通过 Gateway 路由 */
|
||||
test: {
|
||||
backendUrl: "http://192.168.1.108:2233",
|
||||
backendUrl: "http://192.168.1.108:2029/iccoder",
|
||||
backendUrlStrongeLoop: "http://192.168.1.108:2029",
|
||||
loginUrl: "http://192.168.1.108:2005/login",
|
||||
timeout: 60000,
|
||||
userId: "default-user",
|
||||
serviceTier: "max",
|
||||
},
|
||||
/** 生产环境 */
|
||||
/** 生产环境 - 通过 Gateway 路由 */
|
||||
prod: {
|
||||
backendUrl: "https://api.iccoder.com",
|
||||
backendUrlStrongeLoop: "http://api.iccoder.com:2029",
|
||||
backendUrl: "https://api.iccoder.com/iccoder",
|
||||
backendUrlStrongeLoop: "https://api.iccoder.com",
|
||||
loginUrl: "https://iccoder.com/login",
|
||||
timeout: 60000,
|
||||
userId: "default-user",
|
||||
|
||||
@ -180,3 +180,8 @@ export const userQuestionIconSvg = `<svg t="1767869230062" class="icon" viewBox=
|
||||
* 用户头像图标 SVG
|
||||
*/
|
||||
export const userAvatarIconSvg = `<svg t="1767947405083" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4661" width="16" height="16"><path d="M515.541449 7.082899c-280.359429 0-508.458551 228.120391-508.458551 508.458551s228.120391 508.458551 508.458551 508.458551 508.458551-228.120391 508.458551-508.458551S795.900879 7.082899 515.541449 7.082899zM515.541449 981.864196c-257.132626 0-466.301477-209.190121-466.301477-466.322747 0-257.132626 209.168851-466.322747 466.301477-466.322747s466.301477 209.190121 466.301477 466.322747S772.674075 981.864196 515.541449 981.864196zM614.574414 524.177056 614.574414 524.177056c47.751075-31.96876 79.230625-86.398604 79.230625-148.187857 0-98.437405-79.804915-178.24232-178.24232-178.24232-98.437405 0-178.24232 79.804915-178.24232 178.24232 0 61.810523 31.479551 116.219097 79.251895 148.187857-100.266622 39.519598-171.244501 137.170014-171.244501 251.453545 0 0.23397 0 0.446669 0.02127 0.659369 0 0.04254-0.02127 0.10635-0.02127 0.14889 0 15.612155 12.65563 28.246516 28.267786 28.246516 15.590885 0 21.886796-12.63436 21.886796-28.246516 0-0.340319-0.08508-0.659369-0.10635-1.020958 0.10635-118.005774 102.159649-219.995264 220.207964-219.995264 118.112124 0 220.207964 102.095839 220.207964 220.207964 0 0.14889-1.467628 29.054774 21.971875 29.054774 15.505806 0 28.076356-12.57055 28.076356-28.055086 0-0.06381-0.02127-0.12762-0.02127-0.2127 0-0.25524 0.02127-0.510479 0.02127-0.786989C785.797645 661.34707 714.798496 563.696654 614.574414 524.177056zM515.541449 510.734437c-74.402343 0-134.723968-60.321625-134.723968-134.723968 0-74.423613 60.321625-134.723968 134.723968-134.723968 74.423613 0 134.723968 60.321625 134.723968 134.723968S589.943792 510.734437 515.541449 510.734437z" fill="currentColor" p-id="4662"></path></svg>`;
|
||||
|
||||
/**
|
||||
* 更新阶段图标 SVG
|
||||
*/
|
||||
export const updateStageIconSvg = `<svg t="1768188846282" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7848" width="14" height="14"><path d="M83.712 1024c-0.256 0-0.768 0-1.024-0.256-17.408-0.512-31.488-14.848-31.488-32.512V32.512C51.2 14.592 65.792 0 83.712 0h745.472c17.92 0 32.512 14.592 32.512 32.512v280.576c0 18.432-14.592 33.28-32.512 33.28-1.536 0-3.072 0-4.608-0.256-16.128-2.304-27.648-15.872-27.648-32V77.056c0-6.912-5.632-12.288-12.288-12.288H128.256c-6.912 0-12.288 5.632-12.288 12.288v869.632c0 6.912 5.632 12.288 12.288 12.288h238.08c9.728 0 18.944 4.096 25.344 11.52 6.144 7.168 8.96 16.384 7.68 25.6-2.304 16.128-15.872 27.648-32 27.648H83.712v0.256zM534.784 1024c-6.144 0-12.032-2.816-15.616-7.424-3.84-4.352-5.376-10.496-4.352-16.64l27.648-147.968c0-0.512 0.512-1.024 0.768-1.536L867.84 558.336c11.52-11.264 26.624-17.664 42.24-17.664 16.384 0 31.488 6.4 42.752 17.664l53.76 53.504c23.296 23.04 23.552 60.928 0.768 84.736l-0.768 0.768-323.584 294.912c-2.816 2.56-6.4 4.352-9.984 5.12l-134.656 26.368c-0.512 0-2.048 0.256-3.584 0.256z m95.488-182.528c-1.024 0-1.792 0.256-2.56 1.024L590.848 875.52c-0.512 0.512-1.024 1.28-1.28 2.048l-15.104 81.152c-0.256 1.792 0.768 3.072 0.768 3.072 0.768 0.768 1.792 1.28 2.816 1.28H578.816l73.984-14.336c0.768-0.256 1.28-0.512 1.792-0.768l38.4-34.816c0.768-0.768 1.024-1.536 1.024-2.56s-0.256-2.304-1.024-3.072l-60.416-64.768-0.256-0.256c0-0.512-1.024-1.024-2.048-1.024z m217.088-194.56c-1.024 0-1.792 0.256-2.56 1.024l-172.8 155.392c-0.768 0.768-1.28 1.536-1.28 2.816 0 1.024 0.256 2.048 1.024 2.56l60.16 64.768c0.768 0.768 1.792 1.28 2.816 1.28s1.792-0.256 2.56-1.024l173.568-157.952c0.768-0.768 1.024-1.536 1.024-2.56s-0.256-2.048-1.28-3.072L849.92 647.936c-0.512-0.256-1.536-1.024-2.56-1.024z m101.376 28.928c0.768 0.768 1.536 1.024 2.816 1.024 1.024 0 1.792-0.256 2.56-1.024l16.384-14.848 0.512-0.512c3.072-3.584 2.816-8.704-0.512-12.032L916.48 594.944c-1.536-1.536-4.096-2.56-6.144-2.56-2.56 0-4.864 1.024-6.4 2.56-0.256 0.256-0.512 0.512-0.768 0.512l-17.664 15.872 63.232 64.512z" p-id="7849" fill="#8a8a8a"></path><path d="M212.48 419.584h118.016v39.424H212.48v-39.424z m137.472-118.016h118.016v39.424h-118.016v-39.424z m137.728-118.016h196.608v39.424h-196.608V183.552z m0 0" fill="#8a8a8a" p-id="7850"></path><path d="M664.576 242.688h-157.184c-11.776 0-19.712 7.936-19.712 19.712v98.304h-118.016c-11.776 0-19.712 7.936-19.712 19.712V478.72h-117.76c-11.776 0-19.712 7.936-19.712 19.712v118.016c0 11.776 7.936 19.712 19.712 19.712h432.384c11.776 0 19.712-7.936 19.712-19.712V262.144c-0.256-11.776-7.936-19.456-19.712-19.456zM369.664 576.768h-117.76v-39.424h118.016v39.424z m137.728-118.016h-118.016v-39.424h118.016v39.424z m137.472-117.76h-118.016v-39.424h118.016v39.424z m0 0" fill="#8a8a8a" p-id="7851"></path></svg>`;
|
||||
|
||||
@ -6,6 +6,7 @@ import { ChatHistoryManager } from "./utils/chatHistoryManager";
|
||||
import { ICCoderAuthenticationProvider } from "./services/icCoderAuthProvider";
|
||||
import { VCDFileServer } from "./services/vcdFileServer";
|
||||
import { initUserService } from "./services/userService";
|
||||
import { initCreditsService } from "./services/creditsService";
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
console.log("🎉 IC Coder 插件已激活!");
|
||||
@ -13,6 +14,9 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
// 初始化用户服务
|
||||
initUserService(context);
|
||||
|
||||
// 初始化 Credits 服务
|
||||
initCreditsService(context);
|
||||
|
||||
// 初始化 VCD 文件服务器
|
||||
const vcdFileServer = new VCDFileServer(context.extensionUri);
|
||||
vcdFileServer.start().then((port) => {
|
||||
@ -128,15 +132,18 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
"ic-coder.login",
|
||||
async () => {
|
||||
try {
|
||||
// 检查是否有现有 session
|
||||
const existingSession = await vscode.authentication.getSession("iccoder", [], { createIfNone: false });
|
||||
if (existingSession) {
|
||||
// 有旧 session,使用 forceNewSession 强制创建新 session
|
||||
await vscode.authentication.getSession("iccoder", [], { forceNewSession: true });
|
||||
} else {
|
||||
// 没有旧 session,使用 createIfNone 创建新 session
|
||||
await vscode.authentication.getSession("iccoder", [], { createIfNone: true });
|
||||
// 先清除 session 偏好,避免 VSCode 弹出"账户不一致"确认框
|
||||
try {
|
||||
await vscode.authentication.getSession("iccoder", [], {
|
||||
clearSessionPreference: true,
|
||||
createIfNone: false
|
||||
});
|
||||
} catch {
|
||||
// 忽略错误
|
||||
}
|
||||
|
||||
// 创建新 session
|
||||
await vscode.authentication.getSession("iccoder", [], { createIfNone: true });
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(`登录失败: ${error}`);
|
||||
}
|
||||
|
||||
@ -147,16 +147,20 @@ export async function showICHelperPanel(
|
||||
if (userInfo) {
|
||||
// 使用缓存的用户信息
|
||||
console.log('[ICHelperPanel] 使用缓存的用户信息:', userInfo);
|
||||
console.log('[ICHelperPanel] Credits 余额:', userInfo.credits);
|
||||
const tierIconUrl = getTierIconUri(panel.webview, context, userInfo.membership?.tierCode);
|
||||
panel.webview.postMessage({
|
||||
const messageData = {
|
||||
command: 'updateUserInfo',
|
||||
userInfo: {
|
||||
userId: userInfo.userId,
|
||||
nickname: userInfo.nickname,
|
||||
username: userInfo.username
|
||||
username: userInfo.username,
|
||||
credits: userInfo.credits
|
||||
},
|
||||
tierIconUrl: tierIconUrl
|
||||
});
|
||||
};
|
||||
console.log('[ICHelperPanel] 发送用户信息到前端:', messageData);
|
||||
panel.webview.postMessage(messageData);
|
||||
} else {
|
||||
// 如果没有缓存,从 session 中获取
|
||||
const session = await vscode.authentication.getSession("iccoder", [], {
|
||||
|
||||
@ -22,6 +22,33 @@ let lastUpdateTime: number = 0;
|
||||
/** 缓存有效期(5分钟) */
|
||||
const CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
|
||||
/** ExtensionContext 用于持久化存储 */
|
||||
let extensionContext: vscode.ExtensionContext | null = null;
|
||||
|
||||
/**
|
||||
* 初始化 Credits 服务(设置 context)
|
||||
*/
|
||||
export function initCreditsService(context: vscode.ExtensionContext): void {
|
||||
extensionContext = context;
|
||||
// 从持久化存储加载余额
|
||||
const savedBalance = extensionContext.globalState.get<number>('icCoderCreditsBalance');
|
||||
if (savedBalance !== undefined) {
|
||||
cachedBalance = savedBalance;
|
||||
lastUpdateTime = Date.now();
|
||||
console.log('[CreditsService] 从持久化存储加载余额:', savedBalance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存余额到持久化存储
|
||||
*/
|
||||
async function saveBalance(balance: number): Promise<void> {
|
||||
if (extensionContext) {
|
||||
await extensionContext.globalState.update('icCoderCreditsBalance', balance);
|
||||
console.log('[CreditsService] 余额已保存到持久化存储:', balance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新缓存的余额(从 SSE credit_update 事件调用)
|
||||
*/
|
||||
@ -29,6 +56,10 @@ export function updateCachedBalance(balance: number): void {
|
||||
cachedBalance = balance;
|
||||
lastUpdateTime = Date.now();
|
||||
console.log('[CreditsService] 余额已更新:', balance);
|
||||
// 异步保存到持久化存储
|
||||
saveBalance(balance).catch(err => {
|
||||
console.error('[CreditsService] 保存余额失败:', err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -203,8 +234,11 @@ export async function checkBalanceBeforeSend(): Promise<{
|
||||
/**
|
||||
* 清除缓存(登出时调用)
|
||||
*/
|
||||
export function clearBalanceCache(): void {
|
||||
export async function clearBalanceCache(): Promise<void> {
|
||||
cachedBalance = null;
|
||||
lastUpdateTime = 0;
|
||||
if (extensionContext) {
|
||||
await extensionContext.globalState.update('icCoderCreditsBalance', undefined);
|
||||
}
|
||||
console.log('[CreditsService] 余额缓存已清除');
|
||||
}
|
||||
|
||||
@ -238,6 +238,25 @@ export async function startStreamDialog(
|
||||
res.on('data', (chunk: string) => {
|
||||
if (!controller.aborted) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
@ -8,6 +8,7 @@ import { URL } from 'url';
|
||||
import * as vscode from 'vscode';
|
||||
import { getStrangeLoopApiUrl, getConfig } from '../config/settings';
|
||||
import type { UserInfoResponse, MembershipResponse, MultiMembershipVO, MembershipItemVO } from '../types/api';
|
||||
import { fetchBalance, getCachedBalance } from './creditsService';
|
||||
|
||||
/**
|
||||
* HTTP 请求选项
|
||||
@ -114,6 +115,8 @@ export interface UserInfo {
|
||||
remainingDays?: number;
|
||||
monthlyCredits?: number;
|
||||
};
|
||||
// Credits 余额
|
||||
credits?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -221,12 +224,13 @@ function getHighestTierMembership(allMemberships?: MembershipItemVO[]): Membersh
|
||||
*/
|
||||
export async function onTokenReceived(token: string): Promise<UserInfo | null> {
|
||||
try {
|
||||
console.log('[UserService] Token 已获取,正在获取用户信息和会员信息...');
|
||||
console.log('[UserService] Token 已获取,正在获取用户信息、会员信息和余额...');
|
||||
|
||||
// 并行获取用户信息和会员信息
|
||||
const [userInfo, membershipInfo] = await Promise.all([
|
||||
// 并行获取用户信息、会员信息和余额
|
||||
const [userInfo, membershipInfo, credits] = await Promise.all([
|
||||
getUserInfo(token),
|
||||
getMembershipInfo(token)
|
||||
getMembershipInfo(token),
|
||||
fetchBalance()
|
||||
]);
|
||||
|
||||
if (!userInfo) {
|
||||
@ -234,6 +238,15 @@ export async function onTokenReceived(token: string): Promise<UserInfo | null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 添加 Credits 余额到用户信息
|
||||
console.log('[UserService] 获取到的 Credits 余额:', credits);
|
||||
if (credits !== null) {
|
||||
userInfo.credits = credits;
|
||||
console.log('[UserService] Credits 已添加到用户信息');
|
||||
} else {
|
||||
console.warn('[UserService] Credits 余额为 null,未添加到用户信息');
|
||||
}
|
||||
|
||||
// 打印用户信息到控制台
|
||||
console.log('='.repeat(60));
|
||||
console.log('用户信息详情:');
|
||||
@ -286,6 +299,15 @@ export async function onTokenReceived(token: string): Promise<UserInfo | null> {
|
||||
}
|
||||
}
|
||||
|
||||
// 打印 Credits 余额
|
||||
console.log('');
|
||||
console.log('资源点余额:');
|
||||
if (userInfo.credits !== undefined) {
|
||||
console.log(`当前余额: ${userInfo.credits} Credits`);
|
||||
} else {
|
||||
console.log('当前余额: 未获取到余额信息');
|
||||
}
|
||||
|
||||
console.log('='.repeat(60));
|
||||
|
||||
// 保存到持久化存储
|
||||
@ -329,7 +351,18 @@ export function getCachedUserInfo(): UserInfo | null {
|
||||
console.warn('[UserService] ExtensionContext 未初始化');
|
||||
return null;
|
||||
}
|
||||
return extensionContext.globalState.get<UserInfo>('icCoderUserInfo') || null;
|
||||
const userInfo = extensionContext.globalState.get<UserInfo>('icCoderUserInfo') || null;
|
||||
|
||||
// 从 creditsService 加载余额并合并到用户信息中
|
||||
if (userInfo) {
|
||||
const cachedCredits = getCachedBalance();
|
||||
if (cachedCredits !== null) {
|
||||
userInfo.credits = cachedCredits;
|
||||
console.log('[UserService] 从 creditsService 加载余额:', cachedCredits);
|
||||
}
|
||||
}
|
||||
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -18,7 +18,10 @@ import { ChatHistoryManager } from "./chatHistoryManager";
|
||||
import { dialogManager, DialogSession } from "../services/dialogService";
|
||||
import { userInteractionManager } from "../services/userInteraction";
|
||||
import { healthCheck } from "../services/apiClient";
|
||||
import { checkBalanceBeforeSend } from "../services/creditsService";
|
||||
import {
|
||||
checkBalanceBeforeSend,
|
||||
fetchBalance,
|
||||
} from "../services/creditsService";
|
||||
|
||||
import type { RunMode, ServiceTier } from "../types/api";
|
||||
|
||||
@ -39,7 +42,7 @@ export async function handleUserMessage(
|
||||
text: string,
|
||||
extensionPath?: string,
|
||||
mode?: RunMode,
|
||||
serviceTier?: ServiceTier // 新增:服务等级参数
|
||||
serviceTier?: ServiceTier // 新增:服务等级参数
|
||||
) {
|
||||
console.log("收到用户消息:", text);
|
||||
|
||||
@ -78,7 +81,9 @@ export async function handleUserMessage(
|
||||
"去充值"
|
||||
);
|
||||
if (selection === "去充值") {
|
||||
vscode.env.openExternal(vscode.Uri.parse("https://iccoder.com/recharge"));
|
||||
vscode.env.openExternal(
|
||||
vscode.Uri.parse("https://iccoder.com/memberCenter")
|
||||
);
|
||||
}
|
||||
// 恢复输入状态
|
||||
panel.webview.postMessage({
|
||||
@ -92,7 +97,14 @@ export async function handleUserMessage(
|
||||
// 尝试使用后端服务
|
||||
if (useBackendService && extensionPath) {
|
||||
try {
|
||||
await handleUserMessageWithBackend(panel, text, extensionPath, mode, undefined, serviceTier);
|
||||
await handleUserMessageWithBackend(
|
||||
panel,
|
||||
text,
|
||||
extensionPath,
|
||||
mode,
|
||||
undefined,
|
||||
serviceTier
|
||||
);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error("后端服务不可用:", error);
|
||||
@ -128,7 +140,7 @@ async function handleUserMessageWithBackend(
|
||||
extensionPath: string,
|
||||
mode?: RunMode,
|
||||
reuseTaskId?: string, // 可选,复用现有 taskId(用于 Plan 模式确认后继续执行)
|
||||
serviceTier?: ServiceTier // 新增:服务等级参数
|
||||
serviceTier?: ServiceTier // 新增:服务等级参数
|
||||
): Promise<void> {
|
||||
const historyManager = ChatHistoryManager.getInstance();
|
||||
|
||||
@ -137,10 +149,18 @@ async function handleUserMessageWithBackend(
|
||||
const taskIdToUse = reuseTaskId || historyManager.getCurrentTaskId();
|
||||
|
||||
// 创建会话(dialogManager 会自动处理旧会话的中止)
|
||||
currentSession = dialogManager.createSession(extensionPath, taskIdToUse || undefined);
|
||||
currentSession = dialogManager.createSession(
|
||||
extensionPath,
|
||||
taskIdToUse || undefined
|
||||
);
|
||||
// 保存 taskId 用于后续操作(如压缩)
|
||||
lastTaskId = currentSession.getTaskId();
|
||||
console.log("[MessageHandler] 创建会话: taskId=", lastTaskId, "来源=", taskIdToUse ? "historyManager" : "新生成");
|
||||
console.log(
|
||||
"[MessageHandler] 创建会话: taskId=",
|
||||
lastTaskId,
|
||||
"来源=",
|
||||
taskIdToUse ? "historyManager" : "新生成"
|
||||
);
|
||||
|
||||
// 显示状态栏
|
||||
panel.webview.postMessage({
|
||||
@ -200,6 +220,17 @@ async function handleUserMessageWithBackend(
|
||||
// 最后一次发送完整的段落
|
||||
console.log("[MessageHandler] 对话完成, 段落数:", segments.length);
|
||||
|
||||
// 对话完成后重新获取余额(因为已经消耗了 Credits)
|
||||
try {
|
||||
console.log("[MessageHandler] 对话完成,重新获取余额...");
|
||||
const newBalance = await fetchBalance();
|
||||
if (newBalance !== null) {
|
||||
console.log("[MessageHandler] 余额已更新:", newBalance);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[MessageHandler] 获取余额失败:", error);
|
||||
}
|
||||
|
||||
const result = await panel.webview.postMessage({
|
||||
command: "updateSegments",
|
||||
segments: segments,
|
||||
@ -286,7 +317,7 @@ async function handleUserMessageWithBackend(
|
||||
},
|
||||
},
|
||||
mode,
|
||||
serviceTier // 传递服务等级
|
||||
serviceTier // 传递服务等级
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -369,7 +400,14 @@ export async function handlePlanAction(
|
||||
extensionPath: string,
|
||||
serviceTier?: ServiceTier
|
||||
): Promise<void> {
|
||||
console.log("[handlePlanAction] action:", action, "planTitle:", planTitle, "serviceTier:", serviceTier);
|
||||
console.log(
|
||||
"[handlePlanAction] action:",
|
||||
action,
|
||||
"planTitle:",
|
||||
planTitle,
|
||||
"serviceTier:",
|
||||
serviceTier
|
||||
);
|
||||
|
||||
switch (action) {
|
||||
case "confirm":
|
||||
|
||||
@ -128,10 +128,34 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
|
||||
* 侧边栏视图提供者
|
||||
*/
|
||||
export class ICViewProvider implements vscode.WebviewViewProvider {
|
||||
private _view?: vscode.WebviewView;
|
||||
|
||||
constructor(
|
||||
private readonly extensionUri: vscode.Uri,
|
||||
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)
|
||||
@ -139,24 +163,29 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
|
||||
private async checkLoginStatus(): Promise<boolean> {
|
||||
try {
|
||||
const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false });
|
||||
console.log("[ICViewProvider] 检查登录状态, session:", session ? "存在" : "不存在");
|
||||
if (!session) {
|
||||
return false;
|
||||
}
|
||||
// 检查 token 是否过期
|
||||
const expired = isTokenExpired(session.accessToken);
|
||||
// 如果已过期或无法判断(null),都认为未登录
|
||||
if (expired === true || expired === null) {
|
||||
console.log("Token 已过期或无法判断过期状态");
|
||||
console.log("[ICViewProvider] token 过期检查结果:", expired);
|
||||
// 只有明确过期才认为未登录,无法判断时认为已登录
|
||||
if (expired === true) {
|
||||
console.log("[ICViewProvider] Token 已过期");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log("检查登录状态失败:", error);
|
||||
console.log("[ICViewProvider] 检查登录状态失败:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
resolveWebviewView(webviewView: vscode.WebviewView) {
|
||||
// 保存引用以便后续刷新
|
||||
this._view = webviewView;
|
||||
|
||||
webviewView.webview.options = {
|
||||
enableScripts: true,
|
||||
localResourceRoots: [vscode.Uri.joinPath(this.extensionUri, "media")],
|
||||
|
||||
@ -14,9 +14,7 @@ export function getContextButtonContent(): string {
|
||||
<path d="M469.333333 469.333333V170.666667h85.333334v298.666666h298.666666v85.333334h-298.666666v298.666666h-85.333334v-298.666666H170.666667v-85.333334h298.666666z" fill="#8a8a8a" p-id="4995"></path>
|
||||
</svg>
|
||||
<span class="add-context-label">添加上下文</span>
|
||||
<svg class="dropdown-arrow" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M512 714.666667L213.333333 416l42.666667-42.666667L512 629.333333l256-256 42.666667 42.666667z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
</button>
|
||||
<span class="tooltiptext">添加文件、文件夹、图片或文档作为上下文</span>
|
||||
</div>
|
||||
|
||||
216
src/views/exampleShowcase.ts
Normal file
216
src/views/exampleShowcase.ts
Normal file
@ -0,0 +1,216 @@
|
||||
/**
|
||||
* 获取展示区域的 HTML 内容
|
||||
*/
|
||||
export function getExampleShowcaseContent(): string {
|
||||
return `
|
||||
<div class="example-showcase" id="exampleShowcase">
|
||||
<div class="showcase-title">展示</div>
|
||||
<div class="example-cards">
|
||||
<div class="example-card" onclick="fillExample(0)">
|
||||
<div class="example-icon">📝</div>
|
||||
<div class="example-content">
|
||||
<div class="example-title">代码生成</div>
|
||||
<div class="example-desc">生成一个 8 位全加器的 Verilog 代码</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="example-card" onclick="fillExample(1)">
|
||||
<div class="example-icon">🔍</div>
|
||||
<div class="example-content">
|
||||
<div class="example-title">代码分析</div>
|
||||
<div class="example-desc">分析当前项目中的时序逻辑设计</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="web-link">
|
||||
<a href="https://iccoder.com" target="_blank" class="web-link-button">
|
||||
<span class="link-icon">🌐</span>
|
||||
<span>IC Coder Web端</span>
|
||||
<span class="link-arrow">→</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取展示区域的样式
|
||||
*/
|
||||
export function getExampleShowcaseStyles(): string {
|
||||
return `
|
||||
.example-showcase {
|
||||
margin-top: 24px;
|
||||
padding: 0;
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.example-showcase.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.showcase-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--vscode-foreground);
|
||||
margin-bottom: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.example-cards {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.example-card {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: var(--vscode-input-background);
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.example-card:hover {
|
||||
border-color: var(--vscode-focusBorder);
|
||||
background: var(--vscode-list-hoverBackground);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.example-icon {
|
||||
font-size: 28px;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.example-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.example-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
.example-desc {
|
||||
font-size: 11px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.web-link {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--vscode-panel-border);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.web-link-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 20px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 50%, #a855f7 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.web-link-button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.web-link-button:hover {
|
||||
transform: translateY(-1px);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.link-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.link-arrow {
|
||||
font-size: 16px;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.web-link-button:hover .link-arrow {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取展示区域的脚本
|
||||
*/
|
||||
export function getExampleShowcaseScript(): string {
|
||||
return `
|
||||
// 示例文本数组
|
||||
const exampleTexts = [
|
||||
'生成一个 8 位全加器的 Verilog 代码',
|
||||
'分析当前项目中的时序逻辑设计'
|
||||
];
|
||||
|
||||
// 填充示例到输入框
|
||||
function fillExample(index) {
|
||||
const messageInput = document.getElementById('messageInput');
|
||||
if (messageInput && exampleTexts[index]) {
|
||||
messageInput.value = exampleTexts[index];
|
||||
messageInput.focus();
|
||||
// 触发自动调整高度
|
||||
if (typeof autoResizeTextarea === 'function') {
|
||||
autoResizeTextarea();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监听消息变化,自动隐藏/显示展示区域
|
||||
function updateShowcaseVisibility() {
|
||||
const showcase = document.getElementById('exampleShowcase');
|
||||
if (showcase) {
|
||||
if (hasMessages) {
|
||||
showcase.classList.add('hidden');
|
||||
} else {
|
||||
showcase.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 扩展原有的布局更新函数
|
||||
const originalUpdateInputAreaLayout = updateInputAreaLayout;
|
||||
updateInputAreaLayout = function() {
|
||||
if (originalUpdateInputAreaLayout) {
|
||||
originalUpdateInputAreaLayout();
|
||||
}
|
||||
updateShowcaseVisibility();
|
||||
};
|
||||
`;
|
||||
}
|
||||
@ -29,16 +29,21 @@ import {
|
||||
getOptimizeButtonStyles,
|
||||
getOptimizeButtonScript,
|
||||
} from "./optimizeButton";
|
||||
import {
|
||||
getExampleShowcaseContent,
|
||||
getExampleShowcaseStyles,
|
||||
getExampleShowcaseScript,
|
||||
} from "./exampleShowcase";
|
||||
import { sendIconSvg, stopIconSvg } from "../constants/toolIcons";
|
||||
|
||||
/**
|
||||
* 获取输入区域的 HTML 内容
|
||||
*/
|
||||
export function getInputAreaContent(
|
||||
autoIcon: string = '',
|
||||
liteIcon: string = '',
|
||||
syIcon: string = '',
|
||||
maxIcon: string = ''
|
||||
autoIcon: string = "",
|
||||
liteIcon: string = "",
|
||||
syIcon: string = "",
|
||||
maxIcon: string = ""
|
||||
): string {
|
||||
return `
|
||||
<div class="input-area centered" id="inputArea">
|
||||
@ -71,6 +76,8 @@ export function getInputAreaContent(
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 展示区域:案例和 Web 端链接 -->
|
||||
${getExampleShowcaseContent()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -86,6 +93,7 @@ export function getInputAreaStyles(): string {
|
||||
${getContextDisplayStyles()}
|
||||
${getContextCompressStyles()}
|
||||
${getOptimizeButtonStyles()}
|
||||
${getExampleShowcaseStyles()}
|
||||
.input-area {
|
||||
border-top: 1px solid var(--vscode-panel-border);
|
||||
padding-top: 15px;
|
||||
@ -95,7 +103,7 @@ export function getInputAreaStyles(): string {
|
||||
/* 居中模式:未发起对话时 */
|
||||
.input-area.centered {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
top: 55%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: calc(100% - 40px);
|
||||
@ -292,6 +300,7 @@ export function getInputAreaScript(): string {
|
||||
${getContextDisplayScript()}
|
||||
${getContextCompressScript()}
|
||||
${getOptimizeButtonScript()}
|
||||
${getExampleShowcaseScript()}
|
||||
|
||||
// 对话状态管理
|
||||
let isConversationActive = false;
|
||||
|
||||
@ -24,6 +24,7 @@ import {
|
||||
knowledgeLoadIconSvg,
|
||||
stateTransitionIconSvg,
|
||||
userQuestionIconSvg,
|
||||
updateStageIconSvg,
|
||||
} from "../constants/toolIcons";
|
||||
import {
|
||||
getWaveformPreviewContent,
|
||||
@ -670,6 +671,7 @@ export function getMessageAreaScript(): string {
|
||||
const knowledgeLoadIconSvg = \`${knowledgeLoadIconSvg}\`;
|
||||
const stateTransitionIconSvg = \`${stateTransitionIconSvg}\`;
|
||||
const userQuestionIconSvg = \`${userQuestionIconSvg}\`;
|
||||
const updateStageIconSvg = \`${updateStageIconSvg}\`;
|
||||
|
||||
${getAgentCardScript()}
|
||||
|
||||
@ -724,6 +726,7 @@ export function getMessageAreaScript(): string {
|
||||
'updateNode': fileWriteIconSvg,
|
||||
'addStateTransition': stateTransitionIconSvg,
|
||||
'askUser': userQuestionIconSvg,
|
||||
'updatePhase': updateStageIconSvg,
|
||||
};
|
||||
return iconMap[toolName] || '';
|
||||
}
|
||||
@ -756,6 +759,8 @@ export function getMessageAreaScript(): string {
|
||||
'spawnExplorer': '代码探索',
|
||||
'spawnDebugger': '波形调试',
|
||||
'askUser': '用户提问',
|
||||
'updatePhase': '已更新阶段',
|
||||
'iverilog': '已完成编译',
|
||||
};
|
||||
return toolNameMap[toolName] || toolName;
|
||||
}
|
||||
|
||||
@ -62,9 +62,9 @@ export function getPlanCardStyles(): string {
|
||||
.plan-summary p { margin: 8px 0; }
|
||||
.plan-summary ul, .plan-summary ol {
|
||||
margin: 8px 0;
|
||||
padding-left: 24px;
|
||||
padding-left: 0;
|
||||
}
|
||||
.plan-summary li { margin: 4px 0; }
|
||||
.plan-summary li { margin: 4px 0 4px 27px; }
|
||||
.plan-summary code {
|
||||
background: var(--vscode-textCodeBlock-background);
|
||||
padding: 2px 6px;
|
||||
@ -403,6 +403,12 @@ export function getPlanCardScript(): string {
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
|
||||
// 标题(必须在转义之后、其他处理之前)
|
||||
html = html.replace(/^#### (.+)$/gm, '<h4>$1</h4>');
|
||||
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
||||
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
||||
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
|
||||
|
||||
// 代码块 (\`\`\`code\`\`\`)
|
||||
html = html.replace(/\\x60\\x60\\x60([\\s\\S]*?)\\x60\\x60\\x60/g, '<pre><code>$1</code></pre>');
|
||||
|
||||
@ -428,12 +434,6 @@ export function getPlanCardScript(): string {
|
||||
return table;
|
||||
});
|
||||
|
||||
// 标题
|
||||
html = html.replace(/^#### (.+)$/gm, '<h4>$1</h4>');
|
||||
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
||||
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
||||
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
|
||||
|
||||
// 粗体和斜体
|
||||
html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');
|
||||
html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');
|
||||
|
||||
@ -250,14 +250,27 @@ export function getUserInfoComponentScript(): string {
|
||||
|
||||
// 更新剩余 Credits
|
||||
const creditsDetail = document.getElementById('creditsDetail');
|
||||
console.log('[UserInfoComponent] 更新 Credits 显示');
|
||||
console.log('[UserInfoComponent] currentUserInfo.credits:', currentUserInfo.credits);
|
||||
console.log('[UserInfoComponent] creditsDetail 元素:', creditsDetail);
|
||||
if (creditsDetail) {
|
||||
creditsDetail.textContent = currentUserInfo.credits !== undefined ? currentUserInfo.credits.toString() : '-';
|
||||
const creditsText = currentUserInfo.credits !== undefined ? currentUserInfo.credits.toString() : '-';
|
||||
creditsDetail.textContent = creditsText;
|
||||
console.log('[UserInfoComponent] Credits 已更新为:', creditsText);
|
||||
} else {
|
||||
console.warn('[UserInfoComponent] creditsDetail 元素未找到');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新用户信息显示
|
||||
function updateUserInfoDisplay(userInfo) {
|
||||
currentUserInfo = userInfo;
|
||||
console.log('[UserInfoComponent] 更新用户信息:', userInfo);
|
||||
// 如果下拉面板已打开,立即更新显示
|
||||
const dropdown = document.getElementById('userDetailDropdown');
|
||||
if (dropdown && dropdown.classList.contains('active')) {
|
||||
updateUserDetailModal();
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定下拉面板事件
|
||||
|
||||
@ -174,7 +174,7 @@ export function getWaveformPreviewScript(): string {
|
||||
const content = document.createElement('div');
|
||||
content.className = 'waveform-preview-content';
|
||||
|
||||
const miniViewerId = 'waveform-mini-' + Date.now();
|
||||
const miniViewerId = 'waveform-mini-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
|
||||
const miniViewer = document.createElement('div');
|
||||
miniViewer.id = miniViewerId;
|
||||
miniViewer.className = 'waveform-mini-viewer';
|
||||
|
||||
@ -588,20 +588,25 @@ export function getWebviewContent(
|
||||
case 'updateUserInfo':
|
||||
// 更新用户信息
|
||||
console.log('[WebView] 收到用户信息:', message.userInfo);
|
||||
console.log('[WebView] Credits 字段值:', message.userInfo?.credits);
|
||||
if (message.userInfo) {
|
||||
const userInfoData = {
|
||||
nickname: message.userInfo.nickname || message.userInfo.username || '用户',
|
||||
userId: message.userInfo.userId || message.userInfo.id,
|
||||
tierName: message.userInfo.tierName,
|
||||
tierIconUrl: message.tierIconUrl,
|
||||
registerTime: message.userInfo.registerTime || message.userInfo.createdAt
|
||||
registerTime: message.userInfo.registerTime || message.userInfo.createdAt,
|
||||
credits: message.userInfo.credits
|
||||
};
|
||||
|
||||
console.log('[WebView] 显示用户信息:', userInfoData);
|
||||
console.log('[WebView] userInfoData.credits:', userInfoData.credits);
|
||||
|
||||
// 调用更新用户头像图标按钮的函数
|
||||
if (typeof updateUserAvatarIconButton === 'function') {
|
||||
updateUserAvatarIconButton(userInfoData);
|
||||
} else {
|
||||
console.warn('[WebView] updateUserAvatarIconButton 函数不存在');
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user