Skip to content

Commit 2dd1f1a

Browse files
author
echoVic
committed
feat(model-config): 添加模型配置向导组件及支持自定义HTTP头
重构模型配置向导,支持从80+ Provider中选择并配置API Key和模型 添加对自定义HTTP头的支持,优化Anthropic和OpenAI服务配置 新增models.dev服务集成,提供Provider和模型列表 重构OAuth登录流程,支持Antigravity和Copilot 更新VSCode配置和依赖版本
1 parent b39d743 commit 2dd1f1a

19 files changed

Lines changed: 1655 additions & 1271 deletions

.vscode/extensions.json

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
{
2-
"recommendations": ["biomejs.biome", "bradlc.vscode-tailwindcss"],
3-
"unwantedRecommendations": [
4-
"esbenp.prettier-vscode",
5-
"dbaeumer.vscode-eslint",
6-
"rvest.vs-code-prettier-eslint"
7-
]
2+
"recommendations": ["biomejs.biome"],
3+
"unwantedRecommendations": ["dbaeumer.vscode-eslint", "rvest.vs-code-prettier-eslint"]
84
}

.vscode/settings.json

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
11
{
2-
"editor.formatOnSave": true,
3-
"editor.defaultFormatter": "biomejs.biome",
2+
"files.autoSave": "afterDelay",
3+
"eslint.format.enable": false,
4+
"eslint.validate": [],
5+
"eslint.probe": [],
46
"editor.codeActionsOnSave": {
5-
"quickfix.biome": "explicit",
6-
"source.organizeImports.biome": "explicit"
7+
"source.addMissingImports": "explicit",
8+
"source.organizeImports": "explicit",
9+
"source.fixAll.eslint": "never"
710
},
8-
"eslint.enable": false,
9-
"prettier.enable": false,
10-
"typescript.preferences.includePackageJsonAutoImports": "on",
11-
"typescript.suggest.autoImports": true,
12-
"files.associations": {
13-
"*.ts": "typescript"
14-
},
15-
"[typescript]": {
11+
"[javascript]": {
1612
"editor.defaultFormatter": "biomejs.biome"
1713
},
18-
"[json]": {
14+
"[javascriptreact]": {
1915
"editor.defaultFormatter": "biomejs.biome"
2016
},
21-
"[javascript]": {
17+
"[typescript]": {
2218
"editor.defaultFormatter": "biomejs.biome"
2319
},
2420
"[typescriptreact]": {
2521
"editor.defaultFormatter": "biomejs.biome"
2622
},
27-
"[javascriptreact]": {
23+
"[json]": {
24+
"editor.defaultFormatter": "biomejs.biome"
25+
},
26+
"[jsonc]": {
2827
"editor.defaultFormatter": "biomejs.biome"
2928
}
3029
}

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
2+
"$schema": "https://biomejs.dev/schemas/2.3.9/schema.json",
33
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
44
"files": {
55
"ignoreUnknown": false,

src/services/AnthropicChatService.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,13 @@ export class AnthropicChatService implements IChatService {
142142
baseURL: config.baseUrl || undefined, // Anthropic 默认使用官方 API
143143
timeout: config.timeout ?? 180000,
144144
maxRetries: 3,
145+
defaultHeaders: config.customHeaders,
145146
});
146147

148+
if (config.customHeaders && Object.keys(config.customHeaders).length > 0) {
149+
_logger.debug('🔧 [AnthropicChatService] Custom headers configured:', Object.keys(config.customHeaders));
150+
}
151+
147152
_logger.debug('✅ [AnthropicChatService] Initialized successfully');
148153
}
149154

@@ -652,6 +657,7 @@ export class AnthropicChatService implements IChatService {
652657
baseURL: this.config.baseUrl || undefined,
653658
timeout: this.config.timeout ?? 180000,
654659
maxRetries: 3,
660+
defaultHeaders: this.config.customHeaders,
655661
});
656662

657663
_logger.debug('✅ [AnthropicChatService] Configuration updated successfully');

src/services/ChatServiceInterface.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { isBuiltinApiKey } from '../config/builtinModels.js';
1111
import type { ProviderType } from '../config/types.js';
1212
import { createLogger, LogCategory } from '../logging/Logger.js';
1313
import type { MessageRole } from '../store/types.js';
14+
import { getProviderHeaders } from '../ui/components/model-config/types.js';
1415
import { AnthropicChatService } from './AnthropicChatService.js';
1516
import { AntigravityChatService } from './AntigravityChatService.js';
1617
import { AzureOpenAIChatService } from './AzureOpenAIChatService.js';
@@ -72,6 +73,8 @@ export interface ChatConfig {
7273
timeout?: number;
7374
apiVersion?: string; // GPT OpenAI Platform 专用:API 版本(如 '2024-03-01-preview')
7475
supportsThinking?: boolean; // 是否支持 thinking 模式(DeepSeek Reasoner 等)
76+
customHeaders?: Record<string, string>; // Provider 特定的自定义 HTTP Headers
77+
providerId?: string; // models.dev 中的 Provider ID(用于获取特定配置)
7578
}
7679

7780
/**
@@ -183,6 +186,21 @@ export async function createChatServiceAsync(
183186
resolvedConfig = { ...config, apiKey: realApiKey };
184187
}
185188

189+
// 自动注入 Provider 特定的 Headers
190+
if (resolvedConfig.providerId) {
191+
const providerHeaders = getProviderHeaders(resolvedConfig.providerId);
192+
if (Object.keys(providerHeaders).length > 0) {
193+
resolvedConfig = {
194+
...resolvedConfig,
195+
customHeaders: {
196+
...providerHeaders,
197+
...resolvedConfig.customHeaders, // 用户配置优先
198+
},
199+
};
200+
logger.debug(`🔧 注入 ${resolvedConfig.providerId} 特定 headers:`, Object.keys(providerHeaders));
201+
}
202+
}
203+
186204
return createChatServiceInternal(resolvedConfig);
187205
}
188206

src/services/ModelsDevService.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* ModelsDevService - models.dev API 服务
3+
* 获取 80+ LLM Provider 及其内置模型列表
4+
*/
5+
6+
import { createLogger, LogCategory } from '../logging/Logger.js';
7+
import type {
8+
ModelOption,
9+
ModelsDevData,
10+
ProviderOption
11+
} from '../ui/components/model-config/types.js';
12+
import {
13+
DEFAULT_BASE_URLS,
14+
getBladeProvider,
15+
POPULAR_PROVIDERS,
16+
PROVIDER_ICONS,
17+
} from '../ui/components/model-config/types.js';
18+
19+
const logger = createLogger(LogCategory.SERVICE);
20+
const MODELS_DEV_API = 'https://models.dev/api.json';
21+
const CACHE_TTL = 1000 * 60 * 60; // 1 hour
22+
23+
interface CacheEntry {
24+
data: ModelsDevData;
25+
timestamp: number;
26+
}
27+
28+
let cache: CacheEntry | null = null;
29+
30+
export const fetchModelsDevData = async (): Promise<ModelsDevData> => {
31+
if (cache && Date.now() - cache.timestamp < CACHE_TTL) {
32+
return cache.data;
33+
}
34+
35+
try {
36+
logger.info('📡 Fetching models.dev data...');
37+
const response = await fetch(MODELS_DEV_API, {
38+
headers: { 'User-Agent': 'Blade-CLI/1.0' },
39+
});
40+
41+
if (!response.ok) {
42+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
43+
}
44+
45+
const data = (await response.json()) as ModelsDevData;
46+
cache = { data, timestamp: Date.now() };
47+
logger.info(`✅ Loaded ${Object.keys(data).length} providers from models.dev`);
48+
return data;
49+
} catch (error) {
50+
logger.error('❌ Failed to fetch models.dev data:', error);
51+
throw error;
52+
}
53+
};
54+
55+
export const getProviders = async (): Promise<ProviderOption[]> => {
56+
const data = await fetchModelsDevData();
57+
const providers: ProviderOption[] = [];
58+
59+
for (const [id, provider] of Object.entries(data)) {
60+
const modelCount = Object.keys(provider.models || {}).length;
61+
if (modelCount === 0) continue;
62+
63+
providers.push({
64+
id,
65+
name: provider.name || id,
66+
icon: PROVIDER_ICONS[id] || PROVIDER_ICONS.default,
67+
description: `${modelCount} 个模型`,
68+
isOAuth: false,
69+
envVars: provider.env || [],
70+
docUrl: provider.doc,
71+
defaultBaseUrl: DEFAULT_BASE_URLS[id],
72+
bladeProvider: getBladeProvider(id),
73+
});
74+
}
75+
76+
return providers.sort((a, b) => {
77+
const aPopular = POPULAR_PROVIDERS.indexOf(a.id as (typeof POPULAR_PROVIDERS)[number]);
78+
const bPopular = POPULAR_PROVIDERS.indexOf(b.id as (typeof POPULAR_PROVIDERS)[number]);
79+
if (aPopular !== -1 && bPopular !== -1) return aPopular - bPopular;
80+
if (aPopular !== -1) return -1;
81+
if (bPopular !== -1) return 1;
82+
return a.name.localeCompare(b.name);
83+
});
84+
};
85+
86+
export const getModelsForProvider = async (providerId: string): Promise<ModelOption[]> => {
87+
const data = await fetchModelsDevData();
88+
const provider = data[providerId];
89+
90+
if (!provider?.models) {
91+
logger.warn(`⚠️ No models found for provider: ${providerId}`);
92+
return [];
93+
}
94+
95+
return Object.values(provider.models).map((model) => ({
96+
id: model.id,
97+
name: model.name || model.id,
98+
contextWindow: model.limit?.context,
99+
maxOutput: model.limit?.output,
100+
inputCost: model.cost?.input,
101+
outputCost: model.cost?.output,
102+
}));
103+
};
104+
105+

src/services/OpenAIChatService.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,13 @@ export class OpenAIChatService implements IChatService {
258258
baseURL: config.baseUrl, // OpenAI SDK 使用 baseURL
259259
timeout: config.timeout ?? 180000, // 180秒超时(长上下文场景需要更长时间)
260260
maxRetries: 3,
261+
defaultHeaders: config.customHeaders,
261262
});
262263

264+
if (config.customHeaders && Object.keys(config.customHeaders).length > 0) {
265+
_logger.debug('🔧 [ChatService] Custom headers configured:', Object.keys(config.customHeaders));
266+
}
267+
263268
_logger.debug('✅ [ChatService] ChatService initialized successfully');
264269
}
265270

@@ -669,6 +674,7 @@ export class OpenAIChatService implements IChatService {
669674
baseURL: this.config.baseUrl, // OpenAI SDK 使用 baseURL
670675
timeout: this.config.timeout ?? 180000, // 180秒超时(长上下文场景需要更长时间)
671676
maxRetries: 2, // 2次重试,平衡稳定性和响应速度
677+
defaultHeaders: this.config.customHeaders,
672678
});
673679

674680
_logger.debug('✅ [ChatService] Configuration updated successfully');

src/ui/App.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ function initializeStoreState(config: RuntimeConfig): void {
7272
// 如果没有设置当前模型,使用第一个内置模型(GLM)
7373
if (!config.currentModelId) {
7474
config.currentModelId = getBuiltinModelId();
75-
console.log('✨ 已自动配置内置免费模型: GLM-4.7');
7675
}
7776

7877
// 设置配置(使用 config slice)

src/ui/components/BladeInterface.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import { HooksManager } from './HooksManager.js';
4141
import { InputArea } from './InputArea.js';
4242
import { LoadingIndicator } from './LoadingIndicator.js';
4343
import { MessageArea } from './MessageArea.js';
44-
import { ModelConfigWizard } from './ModelConfigWizard.js';
44+
import { ModelConfigWizard } from './model-config/index.js';
4545
import { ModelSelector } from './ModelSelector.js';
4646
import { PermissionsManager } from './PermissionsManager.js';
4747
import { PluginsManager } from './PluginsManager.js';

0 commit comments

Comments
 (0)