- 设置面板:三步引导 / AI 配置 / API Key / 规则管理 / 连接测试 - Provider 重构:统一 OpenAICompatibleProvider 基类,新增 Gemini/Claude/混元/智谱等 - 审查面板 Webview:三 Tab、统计卡片、问题列表、postMessage 通信 - esbuild 构建脚本 + 生产打包 + PMD 下载 - 保存文件自动静态分析(500ms debounce)
205 lines
5.0 KiB
Markdown
205 lines
5.0 KiB
Markdown
# 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`
|
|
|
|
```typescript
|
|
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`
|
|
|
|
```typescript
|
|
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`
|
|
|
|
```typescript
|
|
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`
|
|
|
|
```typescript
|
|
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` 通过
|