功能: - 激活码管理 (Pro/Auto 两种类型) - 账号池管理 - 设备绑定记录 - 使用日志 - 搜索/筛选功能 - 禁用/启用功能 (支持退款参考) - 全局设置 (换号间隔、额度消耗等) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
957 lines
30 KiB
JavaScript
957 lines
30 KiB
JavaScript
'use strict';
|
|
|
|
// ============================================
|
|
// CursorPro Webview Provider - 反混淆版本
|
|
// ============================================
|
|
|
|
const vscode = require('vscode');
|
|
const client = require('../api/client');
|
|
const account = require('../utils/account');
|
|
const extension = require('../extension');
|
|
|
|
/**
|
|
* CursorPro Webview Provider
|
|
* 处理侧边栏 webview 的显示和交互
|
|
*/
|
|
class CursorProProvider {
|
|
constructor(extensionUri, context) {
|
|
this._extensionUri = extensionUri;
|
|
this._context = context;
|
|
this._view = undefined;
|
|
}
|
|
|
|
/**
|
|
* 解析 webview 视图
|
|
*/
|
|
resolveWebviewView(webviewView, context, token) {
|
|
this._view = webviewView;
|
|
|
|
webviewView.webview.options = {
|
|
enableScripts: true,
|
|
localResourceRoots: [this._extensionUri]
|
|
};
|
|
|
|
// 设置 HTML 内容
|
|
webviewView.webview.html = this._getHtmlContent(webviewView.webview);
|
|
|
|
// 处理来自 webview 的消息
|
|
webviewView.webview.onDidReceiveMessage(async (message) => {
|
|
await this._handleMessage(message);
|
|
});
|
|
|
|
// 监听在线状态变化
|
|
client.onOnlineStatusChange((isOnline) => {
|
|
this._postMessage({
|
|
type: 'onlineStatus',
|
|
isOnline: isOnline
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 发送消息到 webview
|
|
*/
|
|
_postMessage(message) {
|
|
if (this._view) {
|
|
this._view.webview.postMessage(message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 处理来自 webview 的消息
|
|
*/
|
|
async _handleMessage(message) {
|
|
const { type, data } = message;
|
|
|
|
try {
|
|
switch (type) {
|
|
case 'verifyKey':
|
|
await this._handleVerifyKey(data);
|
|
break;
|
|
|
|
case 'switchAccount':
|
|
await this._handleSwitchAccount(data);
|
|
break;
|
|
|
|
case 'getSeamlessStatus':
|
|
await this._handleGetSeamlessStatus();
|
|
break;
|
|
|
|
case 'getSeamlessConfig':
|
|
await this._handleGetSeamlessConfig();
|
|
break;
|
|
|
|
case 'updateSeamlessConfig':
|
|
await this._handleUpdateSeamlessConfig(data);
|
|
break;
|
|
|
|
case 'injectSeamless':
|
|
await this._handleInjectSeamless(data);
|
|
break;
|
|
|
|
case 'restoreSeamless':
|
|
await this._handleRestoreSeamless();
|
|
break;
|
|
|
|
case 'getSeamlessAccounts':
|
|
await this._handleGetSeamlessAccounts();
|
|
break;
|
|
|
|
case 'syncSeamlessAccounts':
|
|
await this._handleSyncSeamlessAccounts(data);
|
|
break;
|
|
|
|
case 'switchSeamlessToken':
|
|
await this._handleSwitchSeamlessToken(data);
|
|
break;
|
|
|
|
case 'getProxyConfig':
|
|
await this._handleGetProxyConfig();
|
|
break;
|
|
|
|
case 'updateProxyConfig':
|
|
await this._handleUpdateProxyConfig(data);
|
|
break;
|
|
|
|
case 'checkVersion':
|
|
await this._handleCheckVersion();
|
|
break;
|
|
|
|
case 'openExternal':
|
|
vscode.env.openExternal(vscode.Uri.parse(data.url));
|
|
break;
|
|
|
|
case 'showMessage':
|
|
this._showMessage(data.messageType, data.message);
|
|
break;
|
|
|
|
case 'getStoredKey':
|
|
await this._handleGetStoredKey();
|
|
break;
|
|
|
|
case 'logout':
|
|
await this._handleLogout();
|
|
break;
|
|
|
|
default:
|
|
console.warn('[CursorPro] 未知消息类型:', type);
|
|
}
|
|
} catch (error) {
|
|
console.error('[CursorPro] 处理消息失败:', error);
|
|
this._postMessage({
|
|
type: 'error',
|
|
error: error.message || '操作失败'
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 验证 Key
|
|
*/
|
|
async _handleVerifyKey(data) {
|
|
const { key } = data;
|
|
extension.log('开始验证 Key...');
|
|
|
|
const result = await client.verifyKey(key);
|
|
|
|
if (result.success) {
|
|
// 保存 key 到全局状态
|
|
await this._context.globalState.update('cursorpro.key', key);
|
|
|
|
// 写入账号数据到本地
|
|
if (result.data) {
|
|
const writeResult = await account.writeAccountToLocal(result.data);
|
|
if (writeResult) {
|
|
extension.showStatusBar();
|
|
extension.updateUsageStatusBar(
|
|
result.data.requestCount || 0,
|
|
result.data.usageAmount || 0
|
|
);
|
|
|
|
// 提示重启
|
|
await account.promptRestartCursor('账号切换成功,需要重启 Cursor 生效');
|
|
}
|
|
}
|
|
}
|
|
|
|
this._postMessage({
|
|
type: 'verifyKeyResult',
|
|
result: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 切换账号
|
|
*/
|
|
async _handleSwitchAccount(data) {
|
|
const { key } = data;
|
|
extension.log('开始切换账号...');
|
|
|
|
const result = await client.switchAccount(key);
|
|
|
|
if (result.success && result.data) {
|
|
const writeResult = await account.writeAccountToLocal(result.data);
|
|
if (writeResult) {
|
|
extension.updateUsageStatusBar(
|
|
result.data.requestCount || 0,
|
|
result.data.usageAmount || 0
|
|
);
|
|
await account.promptRestartCursor('账号切换成功,需要重启 Cursor 生效');
|
|
}
|
|
}
|
|
|
|
this._postMessage({
|
|
type: 'switchAccountResult',
|
|
result: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 获取无缝模式状态
|
|
*/
|
|
async _handleGetSeamlessStatus() {
|
|
const result = await client.getSeamlessStatus();
|
|
this._postMessage({
|
|
type: 'seamlessStatusResult',
|
|
result: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 获取无缝配置
|
|
*/
|
|
async _handleGetSeamlessConfig() {
|
|
const result = await client.getSeamlessConfig();
|
|
this._postMessage({
|
|
type: 'seamlessConfigResult',
|
|
result: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 更新无缝配置
|
|
*/
|
|
async _handleUpdateSeamlessConfig(data) {
|
|
const result = await client.updateSeamlessConfig(data);
|
|
this._postMessage({
|
|
type: 'updateSeamlessConfigResult',
|
|
result: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 注入无缝模式
|
|
*/
|
|
async _handleInjectSeamless(data) {
|
|
const { apiUrl, userKey } = data;
|
|
const result = await client.injectSeamless(apiUrl, userKey);
|
|
|
|
if (result.success && result.data) {
|
|
const writeResult = await account.writeAccountToLocal(result.data);
|
|
if (writeResult) {
|
|
await account.promptRestartCursor('无缝模式注入成功,需要重启 Cursor 生效');
|
|
}
|
|
}
|
|
|
|
this._postMessage({
|
|
type: 'injectSeamlessResult',
|
|
result: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 恢复无缝模式
|
|
*/
|
|
async _handleRestoreSeamless() {
|
|
const result = await client.restoreSeamless();
|
|
|
|
if (result.success) {
|
|
await account.promptRestartCursor('已恢复默认设置,需要重启 Cursor 生效');
|
|
}
|
|
|
|
this._postMessage({
|
|
type: 'restoreSeamlessResult',
|
|
result: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 获取无缝账号列表
|
|
*/
|
|
async _handleGetSeamlessAccounts() {
|
|
const result = await client.getSeamlessAccounts();
|
|
this._postMessage({
|
|
type: 'seamlessAccountsResult',
|
|
result: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 同步无缝账号
|
|
*/
|
|
async _handleSyncSeamlessAccounts(data) {
|
|
const result = await client.syncSeamlessAccounts(data.accounts);
|
|
this._postMessage({
|
|
type: 'syncSeamlessAccountsResult',
|
|
result: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 切换无缝 Token
|
|
*/
|
|
async _handleSwitchSeamlessToken(data) {
|
|
const { userKey } = data;
|
|
const result = await client.switchSeamlessToken(userKey);
|
|
|
|
if (result.success && result.data) {
|
|
const writeResult = await account.writeAccountToLocal(result.data);
|
|
if (writeResult) {
|
|
extension.updateUsageStatusBar(
|
|
result.data.requestCount || 0,
|
|
result.data.usageAmount || 0
|
|
);
|
|
await account.promptRestartCursor('Token 切换成功,需要重启 Cursor 生效');
|
|
}
|
|
}
|
|
|
|
this._postMessage({
|
|
type: 'switchSeamlessTokenResult',
|
|
result: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 获取代理配置
|
|
*/
|
|
async _handleGetProxyConfig() {
|
|
const result = await client.getProxyConfig();
|
|
this._postMessage({
|
|
type: 'proxyConfigResult',
|
|
result: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 更新代理配置
|
|
*/
|
|
async _handleUpdateProxyConfig(data) {
|
|
const { isEnabled, proxyUrl } = data;
|
|
const result = await client.updateProxyConfig(isEnabled, proxyUrl);
|
|
this._postMessage({
|
|
type: 'updateProxyConfigResult',
|
|
result: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 检查版本
|
|
*/
|
|
async _handleCheckVersion() {
|
|
const result = await client.getLatestVersion();
|
|
this._postMessage({
|
|
type: 'versionResult',
|
|
result: result
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 获取存储的 Key
|
|
*/
|
|
async _handleGetStoredKey() {
|
|
const key = this._context.globalState.get('cursorpro.key');
|
|
this._postMessage({
|
|
type: 'storedKeyResult',
|
|
key: key || null
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 登出
|
|
*/
|
|
async _handleLogout() {
|
|
await this._context.globalState.update('cursorpro.key', undefined);
|
|
extension.hideStatusBar();
|
|
this._postMessage({
|
|
type: 'logoutResult',
|
|
success: true
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 显示消息
|
|
*/
|
|
_showMessage(messageType, message) {
|
|
switch (messageType) {
|
|
case 'info':
|
|
vscode.window.showInformationMessage(message);
|
|
break;
|
|
case 'warning':
|
|
vscode.window.showWarningMessage(message);
|
|
break;
|
|
case 'error':
|
|
vscode.window.showErrorMessage(message);
|
|
break;
|
|
default:
|
|
vscode.window.showInformationMessage(message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 生成 Webview HTML 内容
|
|
*/
|
|
_getHtmlContent(webview) {
|
|
const styleUri = webview.asWebviewUri(
|
|
vscode.Uri.joinPath(this._extensionUri, 'media', 'style.css')
|
|
);
|
|
const scriptUri = webview.asWebviewUri(
|
|
vscode.Uri.joinPath(this._extensionUri, 'media', 'main.js')
|
|
);
|
|
const nonce = this._getNonce();
|
|
|
|
return `<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource} 'unsafe-inline'; script-src 'nonce-${nonce}';">
|
|
<title>CursorPro</title>
|
|
<style>
|
|
:root {
|
|
--container-padding: 16px;
|
|
--input-padding: 8px 12px;
|
|
--border-radius: 6px;
|
|
}
|
|
|
|
body {
|
|
padding: var(--container-padding);
|
|
font-family: var(--vscode-font-family);
|
|
font-size: var(--vscode-font-size);
|
|
color: var(--vscode-foreground);
|
|
}
|
|
|
|
.section {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
margin-bottom: 12px;
|
|
color: var(--vscode-foreground);
|
|
}
|
|
|
|
input[type="text"],
|
|
input[type="password"] {
|
|
width: 100%;
|
|
padding: var(--input-padding);
|
|
border: 1px solid var(--vscode-input-border);
|
|
background: var(--vscode-input-background);
|
|
color: var(--vscode-input-foreground);
|
|
border-radius: var(--border-radius);
|
|
box-sizing: border-box;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
input:focus {
|
|
outline: 1px solid var(--vscode-focusBorder);
|
|
}
|
|
|
|
button {
|
|
width: 100%;
|
|
padding: 10px 16px;
|
|
border: none;
|
|
border-radius: var(--border-radius);
|
|
background: var(--vscode-button-background);
|
|
color: var(--vscode-button-foreground);
|
|
cursor: pointer;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
button:hover {
|
|
background: var(--vscode-button-hoverBackground);
|
|
}
|
|
|
|
button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
button.secondary {
|
|
background: var(--vscode-button-secondaryBackground);
|
|
color: var(--vscode-button-secondaryForeground);
|
|
}
|
|
|
|
button.secondary:hover {
|
|
background: var(--vscode-button-secondaryHoverBackground);
|
|
}
|
|
|
|
.status {
|
|
padding: 12px;
|
|
border-radius: var(--border-radius);
|
|
margin-bottom: 12px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.status.online {
|
|
background: rgba(40, 167, 69, 0.1);
|
|
border: 1px solid rgba(40, 167, 69, 0.3);
|
|
color: #28a745;
|
|
}
|
|
|
|
.status.offline {
|
|
background: rgba(220, 53, 69, 0.1);
|
|
border: 1px solid rgba(220, 53, 69, 0.3);
|
|
color: #dc3545;
|
|
}
|
|
|
|
.info-box {
|
|
padding: 12px;
|
|
background: var(--vscode-textBlockQuote-background);
|
|
border-left: 3px solid var(--vscode-textLink-foreground);
|
|
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
|
margin-bottom: 12px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.usage-stats {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 12px;
|
|
background: var(--vscode-editor-background);
|
|
border: 1px solid var(--vscode-panel-border);
|
|
border-radius: var(--border-radius);
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.usage-stat {
|
|
text-align: center;
|
|
}
|
|
|
|
.usage-stat-value {
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
color: var(--vscode-textLink-foreground);
|
|
}
|
|
|
|
.usage-stat-label {
|
|
font-size: 11px;
|
|
color: var(--vscode-descriptionForeground);
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.account-card {
|
|
padding: 12px;
|
|
background: var(--vscode-editor-background);
|
|
border: 1px solid var(--vscode-panel-border);
|
|
border-radius: var(--border-radius);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.account-email {
|
|
font-weight: 500;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.account-type {
|
|
font-size: 11px;
|
|
color: var(--vscode-descriptionForeground);
|
|
}
|
|
|
|
.hidden {
|
|
display: none;
|
|
}
|
|
|
|
.loading {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px;
|
|
}
|
|
|
|
.spinner {
|
|
width: 20px;
|
|
height: 20px;
|
|
border: 2px solid var(--vscode-foreground);
|
|
border-top-color: transparent;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
.tabs {
|
|
display: flex;
|
|
border-bottom: 1px solid var(--vscode-panel-border);
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.tab {
|
|
padding: 8px 16px;
|
|
cursor: pointer;
|
|
border-bottom: 2px solid transparent;
|
|
color: var(--vscode-descriptionForeground);
|
|
}
|
|
|
|
.tab:hover {
|
|
color: var(--vscode-foreground);
|
|
}
|
|
|
|
.tab.active {
|
|
color: var(--vscode-textLink-foreground);
|
|
border-bottom-color: var(--vscode-textLink-foreground);
|
|
}
|
|
|
|
.tab-content {
|
|
display: none;
|
|
}
|
|
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
.toggle {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 8px 0;
|
|
}
|
|
|
|
.toggle-switch {
|
|
position: relative;
|
|
width: 40px;
|
|
height: 20px;
|
|
background: var(--vscode-input-background);
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.toggle-switch.active {
|
|
background: var(--vscode-textLink-foreground);
|
|
}
|
|
|
|
.toggle-switch::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 2px;
|
|
left: 2px;
|
|
width: 16px;
|
|
height: 16px;
|
|
background: white;
|
|
border-radius: 50%;
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.toggle-switch.active::after {
|
|
transform: translateX(20px);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<!-- 连接状态 -->
|
|
<div id="connection-status" class="status online">
|
|
<span id="status-icon">●</span>
|
|
<span id="status-text">已连接</span>
|
|
</div>
|
|
|
|
<!-- 标签页 -->
|
|
<div class="tabs">
|
|
<div class="tab active" data-tab="main">主页</div>
|
|
<div class="tab" data-tab="seamless">无缝模式</div>
|
|
<div class="tab" data-tab="settings">设置</div>
|
|
</div>
|
|
|
|
<!-- 主页内容 -->
|
|
<div id="tab-main" class="tab-content active">
|
|
<!-- 登录区域 -->
|
|
<div id="login-section" class="section">
|
|
<div class="section-title">激活 CursorPro</div>
|
|
<input type="password" id="key-input" placeholder="请输入您的激活码">
|
|
<button id="verify-btn">验证激活码</button>
|
|
</div>
|
|
|
|
<!-- 已登录区域 -->
|
|
<div id="logged-in-section" class="section hidden">
|
|
<div class="section-title">使用统计</div>
|
|
<div class="usage-stats">
|
|
<div class="usage-stat">
|
|
<div class="usage-stat-value" id="request-count">0</div>
|
|
<div class="usage-stat-label">请求次数</div>
|
|
</div>
|
|
<div class="usage-stat">
|
|
<div class="usage-stat-value" id="usage-amount">$0.00</div>
|
|
<div class="usage-stat-label">已用额度</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="account-card">
|
|
<div class="account-email" id="account-email">-</div>
|
|
<div class="account-type" id="account-type">-</div>
|
|
</div>
|
|
|
|
<button id="switch-btn" class="secondary">切换账号</button>
|
|
<button id="logout-btn" class="secondary">退出登录</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 无缝模式内容 -->
|
|
<div id="tab-seamless" class="tab-content">
|
|
<div class="section">
|
|
<div class="section-title">无缝模式</div>
|
|
<div class="info-box">
|
|
无缝模式允许您在多个账号之间自动切换,实现不间断使用。
|
|
</div>
|
|
|
|
<div class="toggle">
|
|
<span>启用无缝模式</span>
|
|
<div id="seamless-toggle" class="toggle-switch"></div>
|
|
</div>
|
|
|
|
<div id="seamless-accounts" class="hidden">
|
|
<div class="section-title" style="margin-top: 16px;">账号池</div>
|
|
<div id="accounts-list"></div>
|
|
<button id="sync-accounts-btn" class="secondary">同步账号</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 设置内容 -->
|
|
<div id="tab-settings" class="tab-content">
|
|
<div class="section">
|
|
<div class="section-title">代理设置</div>
|
|
<div class="toggle">
|
|
<span>启用代理</span>
|
|
<div id="proxy-toggle" class="toggle-switch"></div>
|
|
</div>
|
|
<input type="text" id="proxy-url" placeholder="代理地址 (如: http://127.0.0.1:7890)" class="hidden">
|
|
<button id="save-proxy-btn" class="secondary hidden">保存代理设置</button>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="section-title">关于</div>
|
|
<div class="info-box">
|
|
<strong>CursorPro</strong><br>
|
|
版本: <span id="version">0.4.5</span><br>
|
|
<a href="#" id="check-update-link">检查更新</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script nonce="${nonce}">
|
|
(function() {
|
|
const vscode = acquireVsCodeApi();
|
|
|
|
// 元素引用
|
|
const elements = {
|
|
connectionStatus: document.getElementById('connection-status'),
|
|
statusText: document.getElementById('status-text'),
|
|
keyInput: document.getElementById('key-input'),
|
|
verifyBtn: document.getElementById('verify-btn'),
|
|
loginSection: document.getElementById('login-section'),
|
|
loggedInSection: document.getElementById('logged-in-section'),
|
|
requestCount: document.getElementById('request-count'),
|
|
usageAmount: document.getElementById('usage-amount'),
|
|
accountEmail: document.getElementById('account-email'),
|
|
accountType: document.getElementById('account-type'),
|
|
switchBtn: document.getElementById('switch-btn'),
|
|
logoutBtn: document.getElementById('logout-btn'),
|
|
seamlessToggle: document.getElementById('seamless-toggle'),
|
|
seamlessAccounts: document.getElementById('seamless-accounts'),
|
|
accountsList: document.getElementById('accounts-list'),
|
|
proxyToggle: document.getElementById('proxy-toggle'),
|
|
proxyUrl: document.getElementById('proxy-url'),
|
|
saveProxyBtn: document.getElementById('save-proxy-btn')
|
|
};
|
|
|
|
// 标签页切换
|
|
document.querySelectorAll('.tab').forEach(tab => {
|
|
tab.addEventListener('click', () => {
|
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
|
tab.classList.add('active');
|
|
document.getElementById('tab-' + tab.dataset.tab).classList.add('active');
|
|
});
|
|
});
|
|
|
|
// 验证按钮点击
|
|
elements.verifyBtn.addEventListener('click', () => {
|
|
const key = elements.keyInput.value.trim();
|
|
if (!key) {
|
|
vscode.postMessage({ type: 'showMessage', data: { messageType: 'warning', message: '请输入激活码' }});
|
|
return;
|
|
}
|
|
elements.verifyBtn.disabled = true;
|
|
elements.verifyBtn.textContent = '验证中...';
|
|
vscode.postMessage({ type: 'verifyKey', data: { key } });
|
|
});
|
|
|
|
// 切换账号按钮
|
|
elements.switchBtn.addEventListener('click', () => {
|
|
vscode.postMessage({ type: 'getStoredKey' });
|
|
});
|
|
|
|
// 退出登录按钮
|
|
elements.logoutBtn.addEventListener('click', () => {
|
|
vscode.postMessage({ type: 'logout' });
|
|
});
|
|
|
|
// 无缝模式开关
|
|
elements.seamlessToggle.addEventListener('click', () => {
|
|
elements.seamlessToggle.classList.toggle('active');
|
|
const isEnabled = elements.seamlessToggle.classList.contains('active');
|
|
elements.seamlessAccounts.classList.toggle('hidden', !isEnabled);
|
|
vscode.postMessage({ type: 'updateSeamlessConfig', data: { enabled: isEnabled }});
|
|
});
|
|
|
|
// 代理开关
|
|
elements.proxyToggle.addEventListener('click', () => {
|
|
elements.proxyToggle.classList.toggle('active');
|
|
const isEnabled = elements.proxyToggle.classList.contains('active');
|
|
elements.proxyUrl.classList.toggle('hidden', !isEnabled);
|
|
elements.saveProxyBtn.classList.toggle('hidden', !isEnabled);
|
|
});
|
|
|
|
// 保存代理设置
|
|
elements.saveProxyBtn.addEventListener('click', () => {
|
|
const isEnabled = elements.proxyToggle.classList.contains('active');
|
|
const proxyUrl = elements.proxyUrl.value.trim();
|
|
vscode.postMessage({ type: 'updateProxyConfig', data: { isEnabled, proxyUrl }});
|
|
});
|
|
|
|
// 处理来自扩展的消息
|
|
window.addEventListener('message', event => {
|
|
const message = event.data;
|
|
|
|
switch (message.type) {
|
|
case 'onlineStatus':
|
|
updateConnectionStatus(message.isOnline);
|
|
break;
|
|
|
|
case 'verifyKeyResult':
|
|
handleVerifyResult(message.result);
|
|
break;
|
|
|
|
case 'storedKeyResult':
|
|
if (message.key) {
|
|
vscode.postMessage({ type: 'switchAccount', data: { key: message.key }});
|
|
}
|
|
break;
|
|
|
|
case 'switchAccountResult':
|
|
handleSwitchResult(message.result);
|
|
break;
|
|
|
|
case 'logoutResult':
|
|
handleLogout();
|
|
break;
|
|
|
|
case 'seamlessConfigResult':
|
|
handleSeamlessConfig(message.result);
|
|
break;
|
|
|
|
case 'proxyConfigResult':
|
|
handleProxyConfig(message.result);
|
|
break;
|
|
|
|
case 'error':
|
|
vscode.postMessage({ type: 'showMessage', data: { messageType: 'error', message: message.error }});
|
|
break;
|
|
}
|
|
});
|
|
|
|
function updateConnectionStatus(isOnline) {
|
|
elements.connectionStatus.className = 'status ' + (isOnline ? 'online' : 'offline');
|
|
elements.statusText.textContent = isOnline ? '已连接' : '连接断开';
|
|
}
|
|
|
|
function handleVerifyResult(result) {
|
|
elements.verifyBtn.disabled = false;
|
|
elements.verifyBtn.textContent = '验证激活码';
|
|
|
|
if (result.success) {
|
|
showLoggedIn(result.data);
|
|
vscode.postMessage({ type: 'showMessage', data: { messageType: 'info', message: '激活成功!' }});
|
|
} else {
|
|
vscode.postMessage({ type: 'showMessage', data: { messageType: 'error', message: result.message || '验证失败' }});
|
|
}
|
|
}
|
|
|
|
function handleSwitchResult(result) {
|
|
if (result.success) {
|
|
showLoggedIn(result.data);
|
|
vscode.postMessage({ type: 'showMessage', data: { messageType: 'info', message: '切换成功!' }});
|
|
} else {
|
|
vscode.postMessage({ type: 'showMessage', data: { messageType: 'error', message: result.message || '切换失败' }});
|
|
}
|
|
}
|
|
|
|
function showLoggedIn(data) {
|
|
elements.loginSection.classList.add('hidden');
|
|
elements.loggedInSection.classList.remove('hidden');
|
|
|
|
if (data) {
|
|
elements.requestCount.textContent = data.requestCount || 0;
|
|
elements.usageAmount.textContent = '$' + (data.usageAmount || 0).toFixed(2);
|
|
elements.accountEmail.textContent = data.email || '-';
|
|
elements.accountType.textContent = data.membership_type || 'Free';
|
|
}
|
|
}
|
|
|
|
function handleLogout() {
|
|
elements.loginSection.classList.remove('hidden');
|
|
elements.loggedInSection.classList.add('hidden');
|
|
elements.keyInput.value = '';
|
|
}
|
|
|
|
function handleSeamlessConfig(result) {
|
|
if (result.success && result.data) {
|
|
if (result.data.enabled) {
|
|
elements.seamlessToggle.classList.add('active');
|
|
elements.seamlessAccounts.classList.remove('hidden');
|
|
}
|
|
}
|
|
}
|
|
|
|
function handleProxyConfig(result) {
|
|
if (result.success && result.data) {
|
|
if (result.data.is_enabled) {
|
|
elements.proxyToggle.classList.add('active');
|
|
elements.proxyUrl.classList.remove('hidden');
|
|
elements.saveProxyBtn.classList.remove('hidden');
|
|
elements.proxyUrl.value = result.data.proxy_url || '';
|
|
}
|
|
}
|
|
}
|
|
|
|
// 初始化:获取存储的 key
|
|
vscode.postMessage({ type: 'getStoredKey' });
|
|
vscode.postMessage({ type: 'getSeamlessConfig' });
|
|
vscode.postMessage({ type: 'getProxyConfig' });
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
}
|
|
|
|
/**
|
|
* 生成随机 nonce
|
|
*/
|
|
_getNonce() {
|
|
let text = '';
|
|
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
for (let i = 0; i < 32; i++) {
|
|
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
|
}
|
|
return text;
|
|
}
|
|
}
|
|
|
|
exports.CursorProProvider = CursorProProvider;
|