- 设置面板:三步引导 / AI 配置 / API Key / 规则管理 / 连接测试 - Provider 重构:统一 OpenAICompatibleProvider 基类,新增 Gemini/Claude/混元/智谱等 - 审查面板 Webview:三 Tab、统计卡片、问题列表、postMessage 通信 - esbuild 构建脚本 + 生产打包 + PMD 下载 - 保存文件自动静态分析(500ms debounce)
5.0 KiB
5.0 KiB
Step 09 — Phase 4.1: AI Provider 基础设施
依赖: Step 01(配置模块)
参考设计: §5.2
目标
实现 AI Provider 策略模式基础设施:抽象基类、2 个 Provider 实现、工厂函数。
新建文件
| # | 文件 | 说明 |
|---|---|---|
| 1 | src/ai/providers/base.ts |
AIProvider 抽象基类 + ChatOptions |
| 2 | src/ai/providers/deepseek.ts |
DeepSeekProvider |
| 3 | src/ai/providers/openai.ts |
OpenAIProvider |
| 4 | src/ai/factory.ts |
createProvider() 工厂 |
1. src/ai/providers/base.ts
export interface ChatOptions {
model: string;
temperature: number;
timeoutMs: number;
}
export abstract class AIProvider {
abstract id: string;
abstract name: string;
constructor(
protected apiKey: string,
protected baseUrl: string
) {}
abstract chat(
systemPrompt: string,
userPrompt: string,
options: ChatOptions
): Promise<string>;
}
2. src/ai/providers/deepseek.ts
import { AIProvider, ChatOptions } from './base';
export class DeepSeekProvider extends AIProvider {
id = 'deepseek';
name = 'DeepSeek';
async chat(systemPrompt: string, userPrompt: string, options: ChatOptions): Promise<string> {
const url = `${this.baseUrl}/chat/completions`;
const body = JSON.stringify({
model: options.model,
temperature: options.temperature,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt },
],
});
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.apiKey}`,
},
body,
signal: controller.signal,
});
if (!response.ok) {
const errorText = await response.text();
if (response.status === 401) {
throw new Error('API Key 无效,请重新设置');
}
throw new Error(`API 请求失败 (${response.status}): ${errorText}`);
}
const data = await response.json() as {
choices: Array<{ message: { content: string } }>;
};
return data.choices[0]?.message?.content ?? '';
} finally {
clearTimeout(timeout);
}
}
}
3. src/ai/providers/openai.ts
import { AIProvider, ChatOptions } from './base';
export class OpenAIProvider extends AIProvider {
id = 'openai';
name = 'OpenAI';
async chat(systemPrompt: string, userPrompt: string, options: ChatOptions): Promise<string> {
const url = `${this.baseUrl}/chat/completions`;
const body = JSON.stringify({
model: options.model,
temperature: options.temperature,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt },
],
});
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.apiKey}`,
},
body,
signal: controller.signal,
});
if (!response.ok) {
const errorText = await response.text();
if (response.status === 401) {
throw new Error('API Key 无效,请重新设置');
}
throw new Error(`API 请求失败 (${response.status}): ${errorText}`);
}
const data = await response.json() as {
choices: Array<{ message: { content: string } }>;
};
return data.choices[0]?.message?.content ?? '';
} finally {
clearTimeout(timeout);
}
}
}
4. src/ai/factory.ts
import { AIProvider } from './providers/base';
import { DeepSeekProvider } from './providers/deepseek';
import { OpenAIProvider } from './providers/openai';
type ProviderConstructor = new (apiKey: string, baseUrl: string) => AIProvider;
const registry: Record<string, ProviderConstructor> = {
deepseek: DeepSeekProvider,
openai: OpenAIProvider,
};
export function createProvider(providerId: string, apiKey: string, baseUrl: string): AIProvider {
const Cls = registry[providerId];
if (!Cls) {
throw new Error(`未知的 Provider: ${providerId}`);
}
return new Cls(apiKey, baseUrl);
}
export function getProviderIds(): string[] {
return Object.keys(registry);
}
关键逻辑
- Provider 统一实现
chat(systemPrompt, userPrompt, options): Promise<string> - 支持 AbortController 超时控制
- HTTP 401 → 抛出 "API Key 无效" 错误
- 工厂函数通过注册表字符串查找,便于添加新的 Provider
- 两个 Provider 实现几乎相同(都是 openai 兼容 API),可考虑后续合并
验收
- 4 个文件创建完成
npm run compile通过npm run lint通过