-
Notifications
You must be signed in to change notification settings - Fork 665
Expand file tree
/
Copy pathprovider-presets.tsx
More file actions
211 lines (192 loc) · 7.96 KB
/
Copy pathprovider-presets.tsx
File metadata and controls
211 lines (192 loc) · 7.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
"use client";
import type { ReactNode } from "react";
import { HardDrives } from "@/components/ui/icon";
import type { ApiProvider } from "@/types";
import { VENDOR_PRESETS, resolveProviderPresetIdentity } from "@/lib/provider-catalog";
import type { VendorPreset } from "@/lib/provider-catalog";
import { getProviderIconKey, type ProviderIconKey } from "@/lib/provider-icon-rule";
import Anthropic from "@lobehub/icons/es/Anthropic";
import OpenRouter from "@lobehub/icons/es/OpenRouter";
import Zhipu from "@lobehub/icons/es/Zhipu";
import Kimi from "@lobehub/icons/es/Kimi";
import Moonshot from "@lobehub/icons/es/Moonshot";
import Minimax from "@lobehub/icons/es/Minimax";
import Cline from "@lobehub/icons/es/Cline";
import OpenCode from "@lobehub/icons/es/OpenCode";
import Aws from "@lobehub/icons/es/Aws";
import Bedrock from "@lobehub/icons/es/Bedrock";
import Google from "@lobehub/icons/es/Google";
import Volcengine from "@lobehub/icons/es/Volcengine";
import DeepSeek from "@lobehub/icons/es/DeepSeek";
import Bailian from "@lobehub/icons/es/Bailian";
import XiaomiMiMo from "@lobehub/icons/es/XiaomiMiMo";
import Ollama from "@lobehub/icons/es/Ollama";
import OpenAI from "@lobehub/icons/es/OpenAI";
import XAI from "@lobehub/icons/es/XAI";
// Requesty has no brand icon in @lobehub/icons yet, so mirror the approach the
// model-selector already uses for the `requesty` provider and render its logo
// from models.dev. Same `size` prop shape as the lobehub icon components so the
// two icon maps below can treat it uniformly.
function RequestyIcon({ size = 18 }: { size?: number }): ReactNode {
return (
<img
alt="Requesty"
src="https://models.dev/logos/requesty.svg"
width={size}
height={size}
className="dark:invert"
/>
);
}
// ---------------------------------------------------------------------------
// Brand icon resolver
// ---------------------------------------------------------------------------
/**
* React node for a brand icon. Pure rule lives in
* `src/lib/provider-icon-rule.ts` (unit-testable without React); this
* thin wrapper just maps the rule's string key to a JSX component.
*/
const ICON_BY_KEY: Record<ProviderIconKey, ReactNode> = {
openrouter: <OpenRouter size={18} />,
requesty: <RequestyIcon size={18} />,
zhipu: <Zhipu size={18} />,
kimi: <Kimi size={18} />,
moonshot: <Moonshot size={18} />,
minimax: <Minimax size={18} />,
volcengine: <Volcengine size={18} />,
bailian: <Bailian size={18} />,
"xiaomi-mimo": <XiaomiMiMo size={18} />,
ollama: <Ollama size={18} />,
openai: <OpenAI size={18} />,
xai: <XAI size={18} />,
deepseek: <DeepSeek size={18} />,
bedrock: <Bedrock size={18} />,
google: <Google size={18} />,
aws: <Aws size={18} />,
anthropic: <Anthropic size={18} />,
cline: <Cline size={18} />,
opencode: <OpenCode size={18} />,
default: <HardDrives size={18} className="text-muted-foreground" />,
};
/** Map a provider name / base_url to a brand icon */
export function getProviderIcon(name: string, baseUrl: string): ReactNode {
return ICON_BY_KEY[getProviderIconKey(name, baseUrl)];
}
// ---------------------------------------------------------------------------
// Quick-add preset definitions — generated from VENDOR_PRESETS (single source of truth)
// ---------------------------------------------------------------------------
export interface QuickPreset {
key: string;
name: string;
description: string;
descriptionZh: string;
icon: ReactNode;
provider_type: string;
protocol: string;
/** Auth style from catalog — frontend should use this instead of inferring from extra_env */
authStyle: string;
base_url: string;
extra_env: string;
fields: ("name" | "api_key" | "base_url" | "extra_env" | "model_names" | "model_mapping")[];
category?: "chat" | "media";
/** Provider meta info from catalog (for user guidance) */
meta?: VendorPreset['meta'];
/** Catalog default model id — used to pre-fill the model_names input so a
* preset that requires a user-specified model (e.g. MiMo) shows its current
* default instead of an empty box with an unrelated placeholder. */
defaultModelId?: string;
}
/** Map iconKey from VENDOR_PRESETS to React icon component */
function resolveIcon(iconKey: string): ReactNode {
const ICON_MAP: Record<string, ReactNode> = {
anthropic: <Anthropic size={18} />,
openrouter: <OpenRouter size={18} />,
requesty: <RequestyIcon size={18} />,
zhipu: <Zhipu size={18} />,
kimi: <Kimi size={18} />,
moonshot: <Moonshot size={18} />,
minimax: <Minimax size={18} />,
bedrock: <Bedrock size={18} />,
google: <Google size={18} />,
volcengine: <Volcengine size={18} />,
bailian: <Bailian size={18} />,
'xiaomi-mimo': <XiaomiMiMo size={18} />,
ollama: <Ollama size={18} />,
openai: <OpenAI size={18} />,
xai: <XAI size={18} />,
deepseek: <DeepSeek size={18} />,
cline: <Cline size={18} />,
opencode: <OpenCode size={18} />,
server: <HardDrives size={18} className="text-muted-foreground" />,
};
return ICON_MAP[iconKey] || <HardDrives size={18} className="text-muted-foreground" />;
}
/** Convert a VendorPreset to the frontend QuickPreset format */
function toQuickPreset(vp: VendorPreset): QuickPreset {
return {
key: vp.key,
name: vp.name,
description: vp.description,
descriptionZh: vp.descriptionZh,
icon: resolveIcon(vp.iconKey),
provider_type: vp.protocol === 'openrouter' ? 'openrouter'
: vp.protocol === 'bedrock' ? 'bedrock'
: vp.protocol === 'vertex' ? 'vertex'
: vp.protocol === 'gemini-image' ? 'gemini-image'
: vp.protocol === 'openai-image' ? 'openai-image'
: vp.protocol === 'openai-compatible' ? 'openai-compatible'
: vp.protocol === 'xai' ? 'xai'
: 'anthropic',
protocol: vp.protocol,
authStyle: vp.authStyle,
base_url: vp.baseUrl,
extra_env: JSON.stringify(vp.defaultEnvOverrides),
fields: vp.fields as QuickPreset['fields'],
category: vp.category,
meta: vp.meta,
defaultModelId: vp.defaultRoleModels?.default
?? vp.defaultModels?.[0]?.upstreamModelId
?? vp.defaultModels?.[0]?.modelId,
};
}
export const QUICK_PRESETS: QuickPreset[] = VENDOR_PRESETS.map(toQuickPreset);
// ---------------------------------------------------------------------------
// Gemini image model definitions
// ---------------------------------------------------------------------------
export const GEMINI_IMAGE_MODELS = [
{ value: 'gemini-3.1-flash-image-preview', label: 'Nano Banana 2' },
{ value: 'gemini-3-pro-image-preview', label: 'Nano Banana Pro' },
{ value: 'gemini-2.5-flash-image', label: 'Nano Banana' },
];
export const DEFAULT_GEMINI_IMAGE_MODEL = 'gemini-3.1-flash-image-preview';
export const OPENAI_IMAGE_MODELS = [
{ value: 'gpt-image-2', label: 'GPT Image 2' },
{ value: 'gpt-image-1.5', label: 'GPT Image 1.5' },
{ value: 'gpt-image-1', label: 'GPT Image 1' },
{ value: 'gpt-image-1-mini', label: 'GPT Image 1 Mini' },
];
export const DEFAULT_OPENAI_IMAGE_MODEL = 'gpt-image-2';
export function getGeminiImageModel(provider: ApiProvider): string {
try {
const env = JSON.parse(provider.extra_env || '{}');
return env.GEMINI_IMAGE_MODEL || DEFAULT_GEMINI_IMAGE_MODEL;
} catch {
return DEFAULT_GEMINI_IMAGE_MODEL;
}
}
export function getOpenAIImageModel(provider: ApiProvider): string {
try {
const env = JSON.parse(provider.extra_env || '{}');
return env.OPENAI_IMAGE_MODEL || DEFAULT_OPENAI_IMAGE_MODEL;
} catch {
return DEFAULT_OPENAI_IMAGE_MODEL;
}
}
// ---------------------------------------------------------------------------
// Preset matcher — find which quick preset a provider was created from
// ---------------------------------------------------------------------------
export function findMatchingPreset(provider: ApiProvider): QuickPreset | undefined {
const resolution = resolveProviderPresetIdentity(provider);
if (resolution.status !== 'resolved') return undefined;
return QUICK_PRESETS.find(p => p.key === resolution.preset.key);
}