## 功能概述 - 用户首次使用需验证邀请码才能发起对话 - 在输入框聚焦和点击示例时触发验证检查 - 使用弹窗形式展示邀请码输入界面,包含企业端用户提示和微信二维码 ## 主要变更 ### 新增文件 - `services/invitationService.ts`: 邀请码验证服务,处理验证逻辑和状态管理 - `views/invitationModal.ts`: 邀请码验证弹窗组件(HTML/CSS/JS) - `docs/invitation-code-design.md`: 邀请码功能设计文档 ### 修改文件 - `extension.ts`: 添加更换邀请码命令,退出登录时清除验证状态 - `panels/ICHelperPanel.ts`: 添加邀请码验证状态检查和验证消息处理 - `services/apiClient.ts`: 添加邀请码验证接口调用 - `types/api.ts`: 添加邀请码相关类型定义 - `views/inputArea.ts`: 输入框聚焦时触发邀请码验证检查 - `views/exampleShowcase.ts`: 点击示例时先检查邀请码验证状态 - `views/webviewContent.ts`: 集成邀请码弹窗到主界面 ## 技术实现 - 验证状态保存在 ExtensionContext.globalState 中 - 使用后端接口 POST /api/invitation/verify 进行验证 - 弹窗样式适配 VS Code 主题 - 支持回车键提交验证
287 lines
6.8 KiB
TypeScript
287 lines
6.8 KiB
TypeScript
/**
|
||
* API 客户端
|
||
* 封装与后端的 HTTP 通信
|
||
*/
|
||
import * as vscode from 'vscode';
|
||
import * as https from 'https';
|
||
import * as http from 'http';
|
||
import { URL } from 'url';
|
||
import { getApiUrl, getConfig } from '../config/settings';
|
||
import type { ToolCallResult, AnswerRequest, ToolResultResponse, AnswerResponse, ToolConfirmResponse, UserInfoResponse, InvitationVerifyRequest, InvitationVerifyResponse, InvitationStatusResponse } from '../types/api';
|
||
|
||
/**
|
||
* HTTP 请求选项
|
||
*/
|
||
interface RequestOptions {
|
||
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||
headers?: Record<string, string>;
|
||
body?: unknown;
|
||
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 请求
|
||
*/
|
||
async function request<T>(path: string, options: RequestOptions): Promise<T> {
|
||
const url = new URL(getApiUrl(path));
|
||
const { timeout } = getConfig();
|
||
|
||
// 自动获取 Token
|
||
const token = await getAuthToken();
|
||
|
||
const isHttps = url.protocol === 'https:';
|
||
const httpModule = isHttps ? https : http;
|
||
|
||
const requestOptions: http.RequestOptions = {
|
||
hostname: url.hostname,
|
||
port: url.port || (isHttps ? 443 : 80),
|
||
path: url.pathname + url.search,
|
||
method: options.method,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
||
...options.headers
|
||
},
|
||
timeout: options.timeout || timeout
|
||
};
|
||
|
||
return new Promise((resolve, reject) => {
|
||
const req = httpModule.request(requestOptions, (res) => {
|
||
let data = '';
|
||
|
||
res.on('data', (chunk) => {
|
||
data += chunk;
|
||
});
|
||
|
||
res.on('end', () => {
|
||
try {
|
||
const json = JSON.parse(data);
|
||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||
resolve(json as T);
|
||
} else {
|
||
reject(new Error(json.error || json.message || `HTTP ${res.statusCode}`));
|
||
}
|
||
} catch (e) {
|
||
reject(new Error(`解析响应失败: ${data}`));
|
||
}
|
||
});
|
||
});
|
||
|
||
req.on('error', (error) => {
|
||
reject(error);
|
||
});
|
||
|
||
req.on('timeout', () => {
|
||
req.destroy();
|
||
reject(new Error('请求超时'));
|
||
});
|
||
|
||
if (options.body) {
|
||
req.write(JSON.stringify(options.body));
|
||
}
|
||
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 提交工具执行结果
|
||
* POST /api/tool/result
|
||
*/
|
||
export async function submitToolResult(result: ToolCallResult): Promise<ToolResultResponse> {
|
||
console.log(`[API] 提交工具结果: callId=${result.id}`);
|
||
return request<ToolResultResponse>('/api/tool/result', {
|
||
method: 'POST',
|
||
body: result
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 提交用户回答
|
||
* POST /api/task/answer
|
||
*/
|
||
export async function submitAnswer(answer: AnswerRequest): Promise<AnswerResponse> {
|
||
console.log(`[API] 提交用户回答: askId=${answer.askId}`);
|
||
return request<AnswerResponse>('/api/task/answer', {
|
||
method: 'POST',
|
||
body: answer
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 提交工具确认响应(Ask 模式)
|
||
* POST /api/tool/confirm
|
||
*/
|
||
export async function submitToolConfirm(response: ToolConfirmResponse): Promise<ToolResultResponse> {
|
||
console.log(`[API] 提交工具确认: confirmId=${response.confirmId}, approved=${response.approved}`);
|
||
return request<ToolResultResponse>('/api/tool/confirm', {
|
||
method: 'POST',
|
||
body: response
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 健康检查
|
||
* GET /api/dialog/health
|
||
*/
|
||
export async function healthCheck(): Promise<{ status: string }> {
|
||
return request<{ status: string }>('/api/dialog/health', {
|
||
method: 'GET',
|
||
timeout: 5000
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 停止对话请求
|
||
*/
|
||
export interface StopDialogRequest {
|
||
taskId: string;
|
||
}
|
||
|
||
/**
|
||
* 停止对话响应
|
||
*/
|
||
export interface StopDialogResponse {
|
||
success: boolean;
|
||
taskId: string;
|
||
message?: string;
|
||
error?: string;
|
||
}
|
||
|
||
/**
|
||
* 停止对话
|
||
* POST /api/dialog/stop
|
||
*/
|
||
export async function stopDialog(taskId: string): Promise<StopDialogResponse> {
|
||
console.log(`[API] 停止对话: taskId=${taskId}`);
|
||
return request<StopDialogResponse>('/api/dialog/stop', {
|
||
method: 'POST',
|
||
body: { taskId }
|
||
});
|
||
}
|
||
|
||
/** 压缩对话响应 */
|
||
export interface CompactDialogResponse {
|
||
success: boolean;
|
||
taskId: string;
|
||
message?: string;
|
||
error?: string;
|
||
}
|
||
|
||
/**
|
||
* 手动压缩对话历史
|
||
* POST /api/dialog/compact
|
||
*/
|
||
export async function compactDialog(taskId: string): Promise<CompactDialogResponse> {
|
||
console.log(`[API] 压缩对话: taskId=${taskId}`);
|
||
return request<CompactDialogResponse>('/api/dialog/compact', {
|
||
method: 'POST',
|
||
body: { taskId }
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 创建成功的工具结果
|
||
*/
|
||
export function createSuccessResult(id: number, text: string): ToolCallResult {
|
||
return {
|
||
jsonrpc: '2.0',
|
||
id,
|
||
result: {
|
||
content: [{ type: 'text', text }],
|
||
isError: false
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 创建业务错误的工具结果(如编译失败)
|
||
*/
|
||
export function createBusinessErrorResult(id: number, errorMessage: string): ToolCallResult {
|
||
return {
|
||
jsonrpc: '2.0',
|
||
id,
|
||
result: {
|
||
content: [{ type: 'text', text: errorMessage }],
|
||
isError: true
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 创建系统错误的工具结果
|
||
*/
|
||
export function createSystemErrorResult(id: number, code: number, message: string): ToolCallResult {
|
||
return {
|
||
jsonrpc: '2.0',
|
||
id,
|
||
error: { code, message }
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取用户信息
|
||
* GET /system/user/getInfo
|
||
*/
|
||
export async function getUserInfo(): Promise<UserInfoResponse> {
|
||
console.log('[API] 获取用户信息');
|
||
return request<UserInfoResponse>('/system/user/getInfo', {
|
||
method: 'GET'
|
||
});
|
||
}
|
||
|
||
/** 余额查询响应 */
|
||
export interface CreditBalanceResponse {
|
||
success: boolean;
|
||
balance?: number;
|
||
error?: string;
|
||
}
|
||
|
||
/**
|
||
* 查询用户资源点余额
|
||
* GET /api/dialog/balance?userId=xxx
|
||
*/
|
||
export async function getCreditBalance(userId: string): Promise<CreditBalanceResponse> {
|
||
console.log('[API] 查询余额: userId=', userId);
|
||
return request<CreditBalanceResponse>(`/api/dialog/balance?userId=${userId}`, {
|
||
method: 'GET',
|
||
timeout: 5000
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 验证邀请码
|
||
* POST /api/invitation/verify
|
||
*/
|
||
export async function verifyInvitationCode(code: string): Promise<InvitationVerifyResponse> {
|
||
console.log('[API] 验证邀请码');
|
||
const body: InvitationVerifyRequest = { code };
|
||
return request<InvitationVerifyResponse>('/api/invitation/verify', {
|
||
method: 'POST',
|
||
body
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 查询邀请码验证状态
|
||
* GET /api/invitation/status
|
||
*/
|
||
export async function checkInvitationStatus(): Promise<InvitationStatusResponse> {
|
||
console.log('[API] 查询邀请码验证状态');
|
||
return request<InvitationStatusResponse>('/api/invitation/status', {
|
||
method: 'GET'
|
||
});
|
||
}
|