Skip to content

Commit bd86ff6

Browse files
Wing900claude
andcommitted
feat: 支持自定义模型列表
添加自定义模型输入框,与 API 获取的模型合并显示 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 534fb71 commit bd86ff6

File tree

5 files changed

+41
-8
lines changed

5 files changed

+41
-8
lines changed

components/settings/AdvancedSettings.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ export const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({ settings, on
2323
onSettingsChange({ apiKey: keys });
2424
};
2525

26+
const handleCustomModelsChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
27+
onSettingsChange({ customModels: e.target.value });
28+
};
29+
2630
const llmProviderOptions: SelectOption[] = [
2731
{ value: 'gemini', label: 'Google Gemini' },
2832
{ value: 'openai', label: 'OpenAI' },
@@ -86,6 +90,19 @@ export const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({ settings, on
8690
</SettingsItem>
8791
)}
8892

93+
{/* 自定义模型列表 */}
94+
{visibleIds.has('customModels') && (
95+
<SettingsItem label={t('customModels')} description={t('customModelsDesc')}>
96+
<textarea
97+
value={settings.customModels || ''}
98+
onChange={handleCustomModelsChange}
99+
placeholder={t('customModelsPlaceholder')}
100+
className="input-glass max-w-60 min-h-24"
101+
rows={3}
102+
/>
103+
</SettingsItem>
104+
)}
105+
89106
{visibleIds.has('temperature') && (
90107
<SettingsItem label={t('temperature')} description={t('temperatureDesc')}>
91108
<div className="flex items-center gap-4 w-60">

contexts/locales/en.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ export const en = {
8181
apiBaseUrlDesc: 'Optional. Use a proxy or a different API endpoint.',
8282
useCustomApi: 'Use Custom API Configuration',
8383
useCustomApiDesc: 'Override environment variables and use custom API URL and Key.',
84+
customModels: 'Custom Model List',
85+
customModelsDesc: 'Add custom models (comma or newline separated), will be merged with fetched models.',
86+
customModelsPlaceholder: 'e.g., gemini-2.0-flash-exp, gpt-4-turbo',
8487
llmProvider: 'Model Service Provider',
8588
llmProviderDesc: 'Choose the backend large language model service for chat.',
8689
langDetectModel: 'Language Detection Model',

contexts/locales/zh.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ export const zh = {
8181
apiBaseUrlDesc: '可选。使用代理或不同的 API 端点。',
8282
useCustomApi: '使用自定义 API 配置',
8383
useCustomApiDesc: '覆盖环境变量,使用自定义的 API 地址和密钥。',
84+
customModels: '自定义模型列表',
85+
customModelsDesc: '添加自定义模型(逗号或换行分隔),将与获取的模型合并。',
86+
customModelsPlaceholder: '例如:gemini-2.0-flash-exp, gpt-4-turbo',
8487
llmProvider: '模型服务商',
8588
llmProviderDesc: '选择用于聊天的后端大语言模型服务。',
8689
langDetectModel: '语言检测模型',

hooks/useSettings.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -159,28 +159,37 @@ export const useSettings = () => {
159159

160160
if (actualApiKey) {
161161
const llmService = createLLMService(settings);
162-
llmService.getAvailableModels(actualApiKey, actualApiBaseUrl).then(models => {
163-
if (!models || models.length === 0) return;
164-
setAvailableModels(models);
162+
llmService.getAvailableModels(actualApiKey, actualApiBaseUrl).then(fetchedModels => {
163+
// 解析用户自定义的模型列表
164+
const customModelsList = settings.customModels
165+
? settings.customModels.split(/[\n,]+/).map(m => m.trim()).filter(Boolean)
166+
: [];
167+
168+
// 合并 fetch 到的模型和用户自定义的模型(去重)
169+
const allModels = [...new Set([...fetchedModels, ...customModelsList])];
170+
171+
if (allModels.length === 0) return;
172+
173+
setAvailableModels(allModels);
165174
setSettings(current => {
166175
const newDefaults: Partial<Settings> = {};
167-
if (!models.includes(current.defaultModel)) {
168-
newDefaults.defaultModel = models[0] || '';
176+
if (!allModels.includes(current.defaultModel)) {
177+
newDefaults.defaultModel = allModels[0] || '';
169178
}
170179
// 标题生成模型逻辑:优先使用环境变量,否则取列表最后一位
171180
const envTitleModel = process.env.TITLE_MODEL_NAME?.trim();
172181
if (envTitleModel) {
173182
// 环境变量有配置,使用环境变量
174183
newDefaults.titleGenerationModel = envTitleModel;
175-
} else if (!models.includes(current.titleGenerationModel)) {
184+
} else if (!allModels.includes(current.titleGenerationModel)) {
176185
// 环境变量没有配置,取列表最后一位
177-
newDefaults.titleGenerationModel = models[models.length - 1] || '';
186+
newDefaults.titleGenerationModel = allModels[allModels.length - 1] || '';
178187
}
179188
return Object.keys(newDefaults).length > 0 ? { ...current, ...newDefaults } : current;
180189
});
181190
});
182191
}
183-
}, [isStorageLoaded, settings.apiKey, settings.apiBaseUrl, settings.llmProvider, settings.useCustomApi]);
192+
}, [isStorageLoaded, settings.apiKey, settings.apiBaseUrl, settings.llmProvider, settings.useCustomApi, settings.customModels]);
184193

185194
return { settings, setSettings, availableModels, isStorageLoaded };
186195
};

types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export interface Settings {
9090
enableSearch: boolean;
9191
apiBaseUrl?: string;
9292
useCustomApi?: boolean; // 是否使用自定义 API 配置(包括 URL 和 Key,覆盖环境变量)
93+
customModels?: string; // 用户自定义的模型列表(逗号或换行分隔)
9394
temperature?: number;
9495
maxOutputTokens?: number;
9596
contextLength?: number;

0 commit comments

Comments
 (0)