feat: 设置面板 + Provider 扩展 + 构建脚本

- 设置面板:三步引导 / AI 配置 / API Key / 规则管理 / 连接测试
- Provider 重构:统一 OpenAICompatibleProvider 基类,新增 Gemini/Claude/混元/智谱等
- 审查面板 Webview:三 Tab、统计卡片、问题列表、postMessage 通信
- esbuild 构建脚本 + 生产打包 + PMD 下载
- 保存文件自动静态分析(500ms debounce)
This commit is contained in:
范智鹏
2026-07-16 22:20:30 +08:00
parent 1144c9b5db
commit 805737fcfc
27 changed files with 1130 additions and 481 deletions
@@ -243,7 +243,7 @@ export abstract class AIProvider {
constructor(
protected apiKey: string,
protected endpoint: string
protected baseUrl: string
) {}
abstract chat(
@@ -258,15 +258,15 @@ export abstract class AIProvider {
```typescript
// factory.ts
const registry: Record<string, new (apiKey: string, endpoint: string) => AIProvider> = {
const registry: Record<string, new (apiKey: string, baseUrl: string) => AIProvider> = {
deepseek: DeepSeekProvider,
openai: OpenAIProvider,
};
export function createProvider(providerId: string, apiKey: string, endpoint: string): AIProvider {
export function createProvider(providerId: string, apiKey: string, baseUrl: string): AIProvider {
const Cls = registry[providerId];
if (!Cls) throw new Error(`Unknown provider: ${providerId}`);
return new Cls(apiKey, endpoint);
return new Cls(apiKey, baseUrl);
}
```
@@ -471,7 +471,7 @@ interface AIFinding {
|--------|------|--------|------|
| `vscode-code-reviewer.ai.provider` | enum | `deepseek` | 模型提供商:`deepseek` / `openai` |
| `vscode-code-reviewer.ai.model` | string | `deepseek-chat` | 模型名称 |
| `vscode-code-reviewer.ai.endpoint` | string | `https://api.deepseek.com/v1` | API 端点 |
| `vscode-code-reviewer.ai.baseUrl` | string | `https://api.deepseek.com/v1` | API Base URL |
| `vscode-code-reviewer.ai.temperature` | number | 0.2 | AI 温度参数(建议 0.1-0.3 |
| `vscode-code-reviewer.ai.timeout` | number | 300 | AI 请求超时(秒) |
| `vscode-code-reviewer.ai.outputLanguage` | string | `zh-CN` | 输出语言:`zh-CN` / `en` / `ja` |
@@ -975,7 +975,7 @@ rules:
```
src/config/
├── index.ts # 统一导出
├── ai.ts # AI 配置(provider/model/endpoint/temperature/timeout/outputLanguage
├── ai.ts # AI 配置(provider/model/baseUrl/temperature/timeout/outputLanguage
├── linter.ts # Linter 配置(linters.<language>、PMD 路径)
├── fixer.ts # 修复器配置(contextLines
└── secret.ts # API Key SecretStorageget/set/delete/isConfigured
@@ -1047,7 +1047,7 @@ src/config/
| 快速开始 | 三步引导(圆形序号,完成变紫色) | — |
| 审核引擎 | 三个模块标签(紫/琥珀/绿圆点) | — |
| AI 模型配置 | 卡片:提供商下拉框 + 模型下拉框 + 状态标签 | `ai.provider` / `ai.model` |
| API Key | 卡片:密码输入框 + Base URL + 状态标签 | SecretStorage / `ai.endpoint` |
| API Key | 卡片:密码输入框 + Base URL + 状态标签 | SecretStorage / `ai.baseUrl` |
| 输出语言 | 下拉框 | `ai.outputLanguage` |
| 自定义规则 | 卡片:开关列表 + 添加行 | `.code-review/config.yaml` |
@@ -48,7 +48,7 @@ Phase 4 内部必须按 4.1 → 4.2 → 4.3 → 4.4 → 4.5 → 4.6 顺序,Fix
|------|------|------|
| `src/types.ts` | 新建 | `LinterDiagnostic``AdapterResult``LinterAdapter` 等公共类型 |
| `src/config/index.ts` | 新建 | 统一导出 |
| `src/config/ai.ts` | 新建 | AI 配置 getterprovider/model/endpoint/temperature/timeout/outputLanguage |
| `src/config/ai.ts` | 新建 | AI 配置 getterprovider/model/baseUrl/temperature/timeout/outputLanguage |
| `src/config/linter.ts` | 新建 | Linter 配置 getterlinters.xxx 语言-linter 映射、PMD 路径) |
| `src/config/fixer.ts` | 新建 | 修复器配置(contextLines |
| `src/config/secret.ts` | 新建 | API Key SecretStorageget/set/delete/isConfigured |
@@ -91,7 +91,7 @@ export interface LinterAdapter {
|------|--------|------|--------|
| ai | `ai.provider` | enum | `deepseek` |
| ai | `ai.model` | string | `deepseek-chat` |
| ai | `ai.endpoint` | string | `https://api.deepseek.com/v1` |
| ai | `ai.baseUrl` | string | `https://api.deepseek.com/v1` |
| ai | `ai.temperature` | number | 0.2 |
| ai | `ai.timeout` | number | 300 |
| ai | `ai.outputLanguage` | string | `zh-CN` |
@@ -270,7 +270,7 @@ jars/pmd/
| `src/ai/providers/base.ts` | 新建 | `AIProvider` 抽象基类 + `ChatOptions` 接口 |
| `src/ai/providers/deepseek.ts` | 新建 | `DeepSeekProvider extends AIProvider` |
| `src/ai/providers/openai.ts` | 新建 | `OpenAIProvider extends AIProvider` |
| `src/ai/factory.ts` | 新建 | `createProvider(providerId, apiKey, endpoint)` 工厂函数 |
| `src/ai/factory.ts` | 新建 | `createProvider(providerId, apiKey, baseUrl)` 工厂函数 |
**Provider 接口**:
```typescript
@@ -283,7 +283,7 @@ interface ChatOptions {
abstract class AIProvider {
abstract id: string;
abstract name: string;
constructor(protected apiKey: string, protected endpoint: string) {}
constructor(protected apiKey: string, protected baseUrl: string) {}
abstract chat(systemPrompt: string, userPrompt: string, options: ChatOptions): Promise<string>;
}
```
@@ -499,7 +499,7 @@ applyFix() 应用修复到编辑器(单条 / 批量倒序)
| 快速开始 | 三步引导(①②③ 圆形序号,完成变紫色) | 实时状态 |
| 审核引擎 | 三个模块标签(紫/琥珀/绿圆点) | 静态 |
| AI 模型配置 | 提供商下拉框 + 模型下拉框 + 状态标签 | `ai.provider` / `ai.model` |
| API Key | 密码输入框 + Base URL + 状态标签 | SecretStorage / `ai.endpoint` |
| API Key | 密码输入框 + Base URL + 状态标签 | SecretStorage / `ai.baseUrl` |
| 输出语言 | 下拉框 | `ai.outputLanguage` |
| 自定义规则 | 开关列表 + 添加行 | `.code-review/config.yaml` |
@@ -61,7 +61,7 @@ import * as vscode from 'vscode';
export interface AIConfig {
provider: string;
model: string;
endpoint: string;
baseUrl: string;
temperature: number;
timeout: number;
outputLanguage: string;
@@ -72,7 +72,7 @@ export function getAIConfig(): AIConfig {
return {
provider: config.get<string>('ai.provider', 'deepseek'),
model: config.get<string>('ai.model', 'deepseek-chat'),
endpoint: config.get<string>('ai.endpoint', 'https://api.deepseek.com/v1'),
baseUrl: config.get<string>('ai.baseUrl', 'https://api.deepseek.com/v1'),
temperature: config.get<number>('ai.temperature', 0.2),
timeout: config.get<number>('ai.timeout', 300),
outputLanguage: config.get<string>('ai.outputLanguage', 'zh-CN'),
@@ -86,7 +86,7 @@ export function getAIConfig(): AIConfig {
|-----|------|--------|
| `ai.provider` | enum | `deepseek` |
| `ai.model` | string | `deepseek-chat` |
| `ai.endpoint` | string | `https://api.deepseek.com/v1` |
| `ai.baseUrl` | string | `https://api.deepseek.com/v1` |
| `ai.temperature` | number | 0.2 |
| `ai.timeout` | number | 300 |
| `ai.outputLanguage` | string | `zh-CN` |
@@ -33,7 +33,7 @@ export abstract class AIProvider {
constructor(
protected apiKey: string,
protected endpoint: string
protected baseUrl: string
) {}
abstract chat(
@@ -56,7 +56,7 @@ export class DeepSeekProvider extends AIProvider {
name = 'DeepSeek';
async chat(systemPrompt: string, userPrompt: string, options: ChatOptions): Promise<string> {
const url = `${this.endpoint}/chat/completions`;
const url = `${this.baseUrl}/chat/completions`;
const body = JSON.stringify({
model: options.model,
@@ -112,7 +112,7 @@ export class OpenAIProvider extends AIProvider {
name = 'OpenAI';
async chat(systemPrompt: string, userPrompt: string, options: ChatOptions): Promise<string> {
const url = `${this.endpoint}/chat/completions`;
const url = `${this.baseUrl}/chat/completions`;
const body = JSON.stringify({
model: options.model,
@@ -165,19 +165,19 @@ import { AIProvider } from './providers/base';
import { DeepSeekProvider } from './providers/deepseek';
import { OpenAIProvider } from './providers/openai';
type ProviderConstructor = new (apiKey: string, endpoint: string) => AIProvider;
type ProviderConstructor = new (apiKey: string, baseUrl: string) => AIProvider;
const registry: Record<string, ProviderConstructor> = {
deepseek: DeepSeekProvider,
openai: OpenAIProvider,
};
export function createProvider(providerId: string, apiKey: string, endpoint: string): AIProvider {
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, endpoint);
return new Cls(apiKey, baseUrl);
}
export function getProviderIds(): string[] {
@@ -142,7 +142,7 @@ export async function runAIReview(
let provider: AIProvider;
try {
provider = createProvider(config.provider, apiKey, config.endpoint);
provider = createProvider(config.provider, apiKey, config.baseUrl);
} catch (err) {
return {
customRuleResults: [],
@@ -343,7 +343,7 @@ context.subscriptions.push(
cancellable: false,
}, async () => {
try {
const provider = createProvider(config.provider, apiKey, config.endpoint);
const provider = createProvider(config.provider, apiKey, config.baseUrl);
await provider.chat('回复 ok', 'ping', {
model: config.model,
temperature: 0,