@nextclaw/ui 0.9.6 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -2
- package/dist/assets/ChannelsList-CIMYaIji.js +1 -0
- package/dist/assets/{ChatPage-DM1ewbWf.js → ChatPage-B5UpeEIp.js} +2 -2
- package/dist/assets/{DocBrowser-BLv77lJ0.js → DocBrowser-BJ610SPa.js} +1 -1
- package/dist/assets/{LogoBadge-D7j1al-w.js → LogoBadge-BKq1GKWP.js} +1 -1
- package/dist/assets/MarketplacePage-Bs3sLsgx.js +49 -0
- package/dist/assets/{McpMarketplacePage-DpMjaD3m.js → McpMarketplacePage-BWTguHCs.js} +2 -2
- package/dist/assets/ModelConfig-B-oTP-Bc.js +1 -0
- package/dist/assets/ProvidersList-r7bD0-R0.js +1 -0
- package/dist/assets/RemoteAccessPage-D7On6waK.js +1 -0
- package/dist/assets/{RuntimeConfig-BbX4yFKy.js → RuntimeConfig-C11xVxH9.js} +1 -1
- package/dist/assets/{SearchConfig-BmmmeyJd.js → SearchConfig-BVZdCxiM.js} +1 -1
- package/dist/assets/{SecretsConfig-CWG8J01H.js → SecretsConfig-DuEDdC3X.js} +2 -2
- package/dist/assets/SessionsConfig-Y-Blf_-K.js +2 -0
- package/dist/assets/{chat-message-CGXiVhyN.js → chat-message-B6VCCEXF.js} +1 -1
- package/dist/assets/index-DfEAJJsA.css +1 -0
- package/dist/assets/index-DvA7S11O.js +8 -0
- package/dist/assets/{label-CCSffS1D.js → label-DzwitL78.js} +1 -1
- package/dist/assets/{page-layout-ud8wZ8gX.js → page-layout-DEq5N_8L.js} +1 -1
- package/dist/assets/popover-CY54V8F6.js +1 -0
- package/dist/assets/provider-models-BOeNnjk9.js +1 -0
- package/dist/assets/{security-config-DJJUCMov.js → security-config-CgbYP57d.js} +1 -1
- package/dist/assets/skeleton-zjQZMWu9.js +1 -0
- package/dist/assets/{status-dot-Fz9-eKsl.js → status-dot-CU_P0tvO.js} +1 -1
- package/dist/assets/{switch-B-_SrMSL.js → switch-PvjTvlcs.js} +1 -1
- package/dist/assets/{tabs-custom-6Tm1ZHfS.js → tabs-custom-Bke5J9ny.js} +1 -1
- package/dist/assets/useConfirmDialog-8tzzp_oW.js +1 -0
- package/dist/assets/vendor-CmQZsDAE.js +436 -0
- package/dist/index.html +3 -3
- package/package.json +4 -4
- package/src/App.tsx +36 -39
- package/src/account/components/account-panel.tsx +93 -0
- package/src/account/managers/account.manager.ts +179 -0
- package/src/account/stores/account.store.ts +68 -0
- package/src/api/types.ts +2 -0
- package/src/app-query-client.ts +10 -0
- package/src/components/config/ProviderForm.tsx +91 -641
- package/src/components/config/ProvidersList.tsx +10 -5
- package/src/components/config/provider-advanced-settings-section.tsx +92 -0
- package/src/components/config/provider-auth-section.tsx +113 -0
- package/src/components/config/provider-enabled-field.tsx +20 -0
- package/src/components/config/provider-form-support.ts +344 -0
- package/src/components/config/provider-models-section.tsx +198 -0
- package/src/components/config/provider-pill-selector.tsx +39 -0
- package/src/components/config/provider-status-badge.tsx +21 -0
- package/src/components/layout/Sidebar.tsx +26 -0
- package/src/components/remote/RemoteAccessPage.tsx +162 -442
- package/src/hooks/useRemoteAccess.ts +7 -6
- package/src/lib/i18n.remote.ts +108 -4
- package/src/lib/provider-models.ts +2 -2
- package/src/presenter/app-presenter-context.tsx +20 -0
- package/src/presenter/app.presenter.ts +12 -0
- package/src/remote/managers/remote-access.manager.ts +196 -0
- package/src/remote/remote-access.query.ts +78 -0
- package/src/remote/stores/remote-access.store.ts +44 -0
- package/dist/assets/ChannelsList-Byfj2R01.js +0 -1
- package/dist/assets/MarketplacePage-DuskLKYh.js +0 -49
- package/dist/assets/ModelConfig-ubaecweS.js +0 -1
- package/dist/assets/ProvidersList-w8MJH2LI.js +0 -1
- package/dist/assets/RemoteAccessPage-D79_5Kbn.js +0 -1
- package/dist/assets/SessionsConfig-D-vg_Lgv.js +0 -2
- package/dist/assets/index-COrhpAdh.css +0 -1
- package/dist/assets/index-CeRbsQ90.js +0 -8
- package/dist/assets/index-Ct7FQpxN.js +0 -1
- package/dist/assets/popover-Bfoe6YBX.js +0 -1
- package/dist/assets/provider-models-D3B_xWXx.js +0 -1
- package/dist/assets/skeleton-IOOTmHzP.js +0 -1
- package/dist/assets/useConfirmDialog-BeOW2bOI.js +0 -5
- package/dist/assets/vendor-CwsIoNvJ.js +0 -442
|
@@ -14,374 +14,45 @@ import {
|
|
|
14
14
|
import { Button } from '@/components/ui/button';
|
|
15
15
|
import { Input } from '@/components/ui/input';
|
|
16
16
|
import { Label } from '@/components/ui/label';
|
|
17
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
18
|
-
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
|
19
17
|
import { MaskedInput } from '@/components/common/MaskedInput';
|
|
20
|
-
import { KeyValueEditor } from '@/components/common/KeyValueEditor';
|
|
21
|
-
import { StatusDot } from '@/components/ui/status-dot';
|
|
22
18
|
import { getLanguage, t } from '@/lib/i18n';
|
|
23
19
|
import { hintForPath } from '@/lib/config-hints';
|
|
24
|
-
import type {
|
|
25
|
-
import { CircleDotDashed,
|
|
20
|
+
import type { ProviderConfigUpdate, ProviderConnectionTestRequest, ThinkingLevel } from '@/api/types';
|
|
21
|
+
import { CircleDotDashed, Trash2 } from 'lucide-react';
|
|
26
22
|
import { toast } from 'sonner';
|
|
27
23
|
import { CONFIG_DETAIL_CARD_CLASS, CONFIG_EMPTY_DETAIL_CARD_CLASS } from './config-layout';
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
import { ProviderAdvancedSettingsSection } from './provider-advanced-settings-section';
|
|
25
|
+
import { ProviderAuthSection } from './provider-auth-section';
|
|
26
|
+
import { ProviderEnabledField } from './provider-enabled-field';
|
|
27
|
+
import {
|
|
28
|
+
applyEnabledPatch,
|
|
29
|
+
EMPTY_PROVIDER_CONFIG,
|
|
30
|
+
formatThinkingLevelLabel,
|
|
31
|
+
headersEqual,
|
|
32
|
+
modelListsEqual,
|
|
33
|
+
modelThinkingEqual,
|
|
34
|
+
normalizeHeaders,
|
|
35
|
+
normalizeModelList,
|
|
36
|
+
normalizeModelThinkingConfig,
|
|
37
|
+
normalizeModelThinkingForModels,
|
|
38
|
+
resolveEditableModels,
|
|
39
|
+
resolvePreferredAuthMethodId,
|
|
40
|
+
serializeModelsForSave,
|
|
41
|
+
shouldUsePillSelector,
|
|
42
|
+
THINKING_LEVELS,
|
|
43
|
+
toProviderLocalModelId,
|
|
44
|
+
type ModelThinkingConfig,
|
|
45
|
+
type WireApiType
|
|
46
|
+
} from './provider-form-support';
|
|
47
|
+
import { ProviderModelsSection } from './provider-models-section';
|
|
48
|
+
import type { PillSelectOption } from './provider-pill-selector';
|
|
49
|
+
import { ProviderStatusBadge } from './provider-status-badge';
|
|
31
50
|
|
|
32
51
|
type ProviderFormProps = {
|
|
33
52
|
providerName?: string;
|
|
34
53
|
onProviderDeleted?: (providerName: string) => void;
|
|
35
54
|
};
|
|
36
55
|
|
|
37
|
-
type ProviderAuthMethodOption = {
|
|
38
|
-
id: string;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
type PillSelectOption = {
|
|
42
|
-
value: string;
|
|
43
|
-
label: string;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const EMPTY_PROVIDER_CONFIG: ProviderConfigView = {
|
|
47
|
-
displayName: '',
|
|
48
|
-
apiKeySet: false,
|
|
49
|
-
apiKeyMasked: undefined,
|
|
50
|
-
apiBase: null,
|
|
51
|
-
extraHeaders: null,
|
|
52
|
-
wireApi: null,
|
|
53
|
-
models: [],
|
|
54
|
-
modelThinking: {}
|
|
55
|
-
};
|
|
56
|
-
const THINKING_LEVELS: ThinkingLevel[] = ['off', 'minimal', 'low', 'medium', 'high', 'adaptive', 'xhigh'];
|
|
57
|
-
const THINKING_LEVEL_SET = new Set<string>(THINKING_LEVELS);
|
|
58
|
-
|
|
59
|
-
function normalizeHeaders(input: Record<string, string> | null | undefined): Record<string, string> | null {
|
|
60
|
-
if (!input) {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
const entries = Object.entries(input)
|
|
64
|
-
.map(([key, value]) => [key.trim(), value] as const)
|
|
65
|
-
.filter(([key]) => key.length > 0);
|
|
66
|
-
if (entries.length === 0) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
return Object.fromEntries(entries);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function headersEqual(
|
|
73
|
-
left: Record<string, string> | null | undefined,
|
|
74
|
-
right: Record<string, string> | null | undefined
|
|
75
|
-
): boolean {
|
|
76
|
-
const a = normalizeHeaders(left);
|
|
77
|
-
const b = normalizeHeaders(right);
|
|
78
|
-
if (a === null && b === null) {
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
if (!a || !b) {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
const aEntries = Object.entries(a).sort(([ak], [bk]) => ak.localeCompare(bk));
|
|
85
|
-
const bEntries = Object.entries(b).sort(([ak], [bk]) => ak.localeCompare(bk));
|
|
86
|
-
if (aEntries.length !== bEntries.length) {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
return aEntries.every(([key, value], index) => key === bEntries[index][0] && value === bEntries[index][1]);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function normalizeModelList(input: string[] | null | undefined): string[] {
|
|
93
|
-
if (!input || input.length === 0) {
|
|
94
|
-
return [];
|
|
95
|
-
}
|
|
96
|
-
const deduped = new Set<string>();
|
|
97
|
-
for (const item of input) {
|
|
98
|
-
const trimmed = item.trim();
|
|
99
|
-
if (trimmed) {
|
|
100
|
-
deduped.add(trimmed);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return [...deduped];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function stripProviderPrefix(model: string, prefix: string): string {
|
|
107
|
-
const trimmed = model.trim();
|
|
108
|
-
if (!trimmed || !prefix.trim()) {
|
|
109
|
-
return trimmed;
|
|
110
|
-
}
|
|
111
|
-
const fullPrefix = `${prefix.trim()}/`;
|
|
112
|
-
if (trimmed.startsWith(fullPrefix)) {
|
|
113
|
-
return trimmed.slice(fullPrefix.length);
|
|
114
|
-
}
|
|
115
|
-
return trimmed;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function toProviderLocalModelId(model: string, aliases: string[]): string {
|
|
119
|
-
let normalized = model.trim();
|
|
120
|
-
if (!normalized) {
|
|
121
|
-
return '';
|
|
122
|
-
}
|
|
123
|
-
for (const alias of aliases) {
|
|
124
|
-
const cleanAlias = alias.trim();
|
|
125
|
-
if (!cleanAlias) {
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
normalized = stripProviderPrefix(normalized, cleanAlias);
|
|
129
|
-
}
|
|
130
|
-
return normalized.trim();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function modelListsEqual(left: string[], right: string[]): boolean {
|
|
134
|
-
if (left.length !== right.length) {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
return left.every((item, index) => item === right[index]);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function mergeModelLists(base: string[], extra: string[]): string[] {
|
|
141
|
-
const merged = [...base];
|
|
142
|
-
for (const item of extra) {
|
|
143
|
-
if (!merged.includes(item)) {
|
|
144
|
-
merged.push(item);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return merged;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function resolveEditableModels(defaultModels: string[], savedModels: string[]): string[] {
|
|
151
|
-
if (savedModels.length === 0) {
|
|
152
|
-
return defaultModels;
|
|
153
|
-
}
|
|
154
|
-
const looksLikeLegacyCustomList = savedModels.every((model) => !defaultModels.includes(model));
|
|
155
|
-
if (looksLikeLegacyCustomList) {
|
|
156
|
-
return mergeModelLists(defaultModels, savedModels);
|
|
157
|
-
}
|
|
158
|
-
return savedModels;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function serializeModelsForSave(models: string[], defaultModels: string[]): string[] {
|
|
162
|
-
if (modelListsEqual(models, defaultModels)) {
|
|
163
|
-
return [];
|
|
164
|
-
}
|
|
165
|
-
return models;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function parseThinkingLevel(value: unknown): ThinkingLevel | null {
|
|
169
|
-
if (typeof value !== 'string') {
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
const normalized = value.trim().toLowerCase();
|
|
173
|
-
if (!normalized) {
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
return THINKING_LEVEL_SET.has(normalized) ? (normalized as ThinkingLevel) : null;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function normalizeThinkingLevels(values: unknown): ThinkingLevel[] {
|
|
180
|
-
if (!Array.isArray(values)) {
|
|
181
|
-
return [];
|
|
182
|
-
}
|
|
183
|
-
const deduped: ThinkingLevel[] = [];
|
|
184
|
-
for (const value of values) {
|
|
185
|
-
const level = parseThinkingLevel(value);
|
|
186
|
-
if (!level || deduped.includes(level)) {
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
deduped.push(level);
|
|
190
|
-
}
|
|
191
|
-
return deduped;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function normalizeModelThinkingConfig(
|
|
195
|
-
input: ProviderConfigView['modelThinking'],
|
|
196
|
-
aliases: string[]
|
|
197
|
-
): ModelThinkingConfig {
|
|
198
|
-
if (!input) {
|
|
199
|
-
return {};
|
|
200
|
-
}
|
|
201
|
-
const normalized: ModelThinkingConfig = {};
|
|
202
|
-
for (const [rawModel, rawValue] of Object.entries(input)) {
|
|
203
|
-
const model = toProviderLocalModelId(rawModel, aliases);
|
|
204
|
-
if (!model) {
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
const supported = normalizeThinkingLevels(rawValue?.supported);
|
|
208
|
-
if (supported.length === 0) {
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
const defaultLevel = parseThinkingLevel(rawValue?.default);
|
|
212
|
-
normalized[model] =
|
|
213
|
-
defaultLevel && supported.includes(defaultLevel)
|
|
214
|
-
? { supported, default: defaultLevel }
|
|
215
|
-
: { supported };
|
|
216
|
-
}
|
|
217
|
-
return normalized;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function normalizeModelThinkingForModels(modelThinking: ModelThinkingConfig, models: string[]): ModelThinkingConfig {
|
|
221
|
-
const modelSet = new Set(models.map((item) => item.trim()).filter(Boolean));
|
|
222
|
-
const normalized: ModelThinkingConfig = {};
|
|
223
|
-
for (const [model, entry] of Object.entries(modelThinking)) {
|
|
224
|
-
if (!modelSet.has(model)) {
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
const supported = normalizeThinkingLevels(entry.supported);
|
|
228
|
-
if (supported.length === 0) {
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
const defaultLevel = parseThinkingLevel(entry.default);
|
|
232
|
-
normalized[model] =
|
|
233
|
-
defaultLevel && supported.includes(defaultLevel)
|
|
234
|
-
? { supported, default: defaultLevel }
|
|
235
|
-
: { supported };
|
|
236
|
-
}
|
|
237
|
-
return normalized;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function modelThinkingEqual(left: ModelThinkingConfig, right: ModelThinkingConfig): boolean {
|
|
241
|
-
const leftKeys = Object.keys(left).sort();
|
|
242
|
-
const rightKeys = Object.keys(right).sort();
|
|
243
|
-
if (leftKeys.length !== rightKeys.length) {
|
|
244
|
-
return false;
|
|
245
|
-
}
|
|
246
|
-
for (let index = 0; index < leftKeys.length; index += 1) {
|
|
247
|
-
const key = leftKeys[index];
|
|
248
|
-
if (key !== rightKeys[index]) {
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
251
|
-
const leftEntry = left[key];
|
|
252
|
-
const rightEntry = right[key];
|
|
253
|
-
if (!leftEntry || !rightEntry) {
|
|
254
|
-
return false;
|
|
255
|
-
}
|
|
256
|
-
const leftSupported = [...leftEntry.supported].sort();
|
|
257
|
-
const rightSupported = [...rightEntry.supported].sort();
|
|
258
|
-
if (!modelListsEqual(leftSupported, rightSupported)) {
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
if ((leftEntry.default ?? null) !== (rightEntry.default ?? null)) {
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
return true;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function formatThinkingLevelLabel(level: ThinkingLevel): string {
|
|
269
|
-
if (level === 'off') {
|
|
270
|
-
return t('chatThinkingLevelOff');
|
|
271
|
-
}
|
|
272
|
-
if (level === 'minimal') {
|
|
273
|
-
return t('chatThinkingLevelMinimal');
|
|
274
|
-
}
|
|
275
|
-
if (level === 'low') {
|
|
276
|
-
return t('chatThinkingLevelLow');
|
|
277
|
-
}
|
|
278
|
-
if (level === 'medium') {
|
|
279
|
-
return t('chatThinkingLevelMedium');
|
|
280
|
-
}
|
|
281
|
-
if (level === 'high') {
|
|
282
|
-
return t('chatThinkingLevelHigh');
|
|
283
|
-
}
|
|
284
|
-
if (level === 'adaptive') {
|
|
285
|
-
return t('chatThinkingLevelAdaptive');
|
|
286
|
-
}
|
|
287
|
-
return t('chatThinkingLevelXhigh');
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function resolvePreferredAuthMethodId(params: {
|
|
291
|
-
providerName?: string;
|
|
292
|
-
methods: ProviderAuthMethodOption[];
|
|
293
|
-
defaultMethodId?: string;
|
|
294
|
-
language: 'zh' | 'en';
|
|
295
|
-
}): string {
|
|
296
|
-
const { providerName, methods, defaultMethodId, language } = params;
|
|
297
|
-
if (methods.length === 0) {
|
|
298
|
-
return '';
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const methodIdMap = new Map<string, string>();
|
|
302
|
-
for (const method of methods) {
|
|
303
|
-
const methodId = method.id.trim();
|
|
304
|
-
if (methodId) {
|
|
305
|
-
methodIdMap.set(methodId.toLowerCase(), methodId);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const pick = (...candidates: string[]): string | undefined => {
|
|
310
|
-
for (const candidate of candidates) {
|
|
311
|
-
const resolved = methodIdMap.get(candidate.toLowerCase());
|
|
312
|
-
if (resolved) {
|
|
313
|
-
return resolved;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
return undefined;
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
const normalizedDefault = defaultMethodId?.trim();
|
|
320
|
-
if (providerName === 'minimax-portal') {
|
|
321
|
-
if (language === 'zh') {
|
|
322
|
-
return pick('cn', 'china-mainland') ?? pick(normalizedDefault ?? '') ?? methods[0]?.id ?? '';
|
|
323
|
-
}
|
|
324
|
-
if (language === 'en') {
|
|
325
|
-
return pick('global', 'intl', 'international') ?? pick(normalizedDefault ?? '') ?? methods[0]?.id ?? '';
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (normalizedDefault) {
|
|
330
|
-
const matchedDefault = pick(normalizedDefault);
|
|
331
|
-
if (matchedDefault) {
|
|
332
|
-
return matchedDefault;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (language === 'zh') {
|
|
337
|
-
return pick('cn') ?? methods[0]?.id ?? '';
|
|
338
|
-
}
|
|
339
|
-
if (language === 'en') {
|
|
340
|
-
return pick('global') ?? methods[0]?.id ?? '';
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
return methods[0]?.id ?? '';
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function shouldUsePillSelector(params: {
|
|
347
|
-
required: boolean;
|
|
348
|
-
hasDefault: boolean;
|
|
349
|
-
optionCount: number;
|
|
350
|
-
}): boolean {
|
|
351
|
-
return params.required && params.hasDefault && params.optionCount > 1 && params.optionCount <= 3;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function PillSelector(props: {
|
|
355
|
-
value: string;
|
|
356
|
-
onChange: (value: string) => void;
|
|
357
|
-
options: PillSelectOption[];
|
|
358
|
-
}) {
|
|
359
|
-
const { value, onChange, options } = props;
|
|
360
|
-
|
|
361
|
-
return (
|
|
362
|
-
<div className="flex flex-wrap gap-2">
|
|
363
|
-
{options.map((option) => {
|
|
364
|
-
const selected = option.value === value;
|
|
365
|
-
return (
|
|
366
|
-
<button
|
|
367
|
-
key={option.value}
|
|
368
|
-
type="button"
|
|
369
|
-
onClick={() => onChange(option.value)}
|
|
370
|
-
aria-pressed={selected}
|
|
371
|
-
className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
|
|
372
|
-
selected
|
|
373
|
-
? 'border-primary bg-primary text-white shadow-sm'
|
|
374
|
-
: 'border-gray-200 bg-white text-gray-700 hover:border-primary/40 hover:text-primary'
|
|
375
|
-
}`}
|
|
376
|
-
>
|
|
377
|
-
{option.label}
|
|
378
|
-
</button>
|
|
379
|
-
);
|
|
380
|
-
})}
|
|
381
|
-
</div>
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
56
|
export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormProps) {
|
|
386
57
|
const queryClient = useQueryClient();
|
|
387
58
|
const { data: config } = useConfig();
|
|
@@ -395,6 +66,7 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
395
66
|
const importProviderAuthFromCli = useImportProviderAuthFromCli();
|
|
396
67
|
|
|
397
68
|
const [apiKey, setApiKey] = useState('');
|
|
69
|
+
const [enabled, setEnabled] = useState(true);
|
|
398
70
|
const [apiBase, setApiBase] = useState('');
|
|
399
71
|
const [extraHeaders, setExtraHeaders] = useState<Record<string, string> | null>(null);
|
|
400
72
|
const [wireApi, setWireApi] = useState<WireApiType>('auto');
|
|
@@ -422,6 +94,7 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
422
94
|
const defaultDisplayName = providerSpec?.displayName || providerName || '';
|
|
423
95
|
const currentDisplayName = (resolvedProviderConfig.displayName || '').trim();
|
|
424
96
|
const effectiveDisplayName = currentDisplayName || defaultDisplayName;
|
|
97
|
+
const currentEnabled = resolvedProviderConfig.enabled !== false;
|
|
425
98
|
|
|
426
99
|
const providerTitle = providerDisplayName.trim() || effectiveDisplayName || providerName || t('providersSelectPlaceholder');
|
|
427
100
|
const providerModelPrefix = providerSpec?.modelPrefix || providerName || '';
|
|
@@ -575,6 +248,7 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
575
248
|
useEffect(() => {
|
|
576
249
|
if (!providerName) {
|
|
577
250
|
setApiKey('');
|
|
251
|
+
setEnabled(true);
|
|
578
252
|
setApiBase('');
|
|
579
253
|
setExtraHeaders(null);
|
|
580
254
|
setWireApi('auto');
|
|
@@ -590,6 +264,7 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
590
264
|
}
|
|
591
265
|
|
|
592
266
|
setApiKey('');
|
|
267
|
+
setEnabled(currentEnabled);
|
|
593
268
|
setApiBase(currentApiBase);
|
|
594
269
|
setExtraHeaders(resolvedProviderConfig.extraHeaders || null);
|
|
595
270
|
setWireApi(currentWireApi);
|
|
@@ -604,6 +279,7 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
604
279
|
}, [
|
|
605
280
|
providerName,
|
|
606
281
|
currentApiBase,
|
|
282
|
+
currentEnabled,
|
|
607
283
|
resolvedProviderConfig.extraHeaders,
|
|
608
284
|
currentWireApi,
|
|
609
285
|
currentEditableModels,
|
|
@@ -635,6 +311,7 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
635
311
|
|
|
636
312
|
return (
|
|
637
313
|
apiKeyChanged ||
|
|
314
|
+
enabled !== currentEnabled ||
|
|
638
315
|
apiBaseChanged ||
|
|
639
316
|
headersChanged ||
|
|
640
317
|
wireApiChanged ||
|
|
@@ -648,6 +325,8 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
648
325
|
providerDisplayName,
|
|
649
326
|
effectiveDisplayName,
|
|
650
327
|
apiKey,
|
|
328
|
+
enabled,
|
|
329
|
+
currentEnabled,
|
|
651
330
|
apiBase,
|
|
652
331
|
currentApiBase,
|
|
653
332
|
extraHeaders,
|
|
@@ -733,6 +412,7 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
733
412
|
if (trimmedApiKey.length > 0) {
|
|
734
413
|
payload.apiKey = trimmedApiKey;
|
|
735
414
|
}
|
|
415
|
+
applyEnabledPatch(payload, enabled, currentEnabled);
|
|
736
416
|
|
|
737
417
|
if (trimmedApiBase !== currentApiBase.trim()) {
|
|
738
418
|
payload.apiBase = trimmedApiBase.length > 0 && trimmedApiBase !== defaultApiBase ? trimmedApiBase : null;
|
|
@@ -868,8 +548,6 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
868
548
|
);
|
|
869
549
|
}
|
|
870
550
|
|
|
871
|
-
const statusLabel = resolvedProviderConfig.apiKeySet ? t('statusReady') : t('statusSetup');
|
|
872
|
-
|
|
873
551
|
return (
|
|
874
552
|
<div className={CONFIG_DETAIL_CARD_CLASS}>
|
|
875
553
|
<div className="border-b border-gray-100 px-6 py-4">
|
|
@@ -887,13 +565,15 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
887
565
|
<Trash2 className="h-4 w-4" />
|
|
888
566
|
</button>
|
|
889
567
|
)}
|
|
890
|
-
<
|
|
568
|
+
<ProviderStatusBadge enabled={currentEnabled} apiKeySet={resolvedProviderConfig.apiKeySet} />
|
|
891
569
|
</div>
|
|
892
570
|
</div>
|
|
893
571
|
</div>
|
|
894
572
|
|
|
895
573
|
<form onSubmit={handleSubmit} className="flex min-h-0 flex-1 flex-col">
|
|
896
574
|
<div className="min-h-0 flex-1 space-y-5 overflow-y-auto px-6 py-5">
|
|
575
|
+
<ProviderEnabledField enabled={enabled} onChange={setEnabled} />
|
|
576
|
+
|
|
897
577
|
{isCustomProvider && (
|
|
898
578
|
<div className="space-y-2">
|
|
899
579
|
<Label htmlFor="providerDisplayName" className="text-sm font-medium text-gray-900">
|
|
@@ -926,76 +606,22 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
926
606
|
<p className="text-xs text-gray-500">{t('leaveBlankToKeepUnchanged')}</p>
|
|
927
607
|
</div>
|
|
928
608
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
/>
|
|
946
|
-
) : (
|
|
947
|
-
<Select value={resolvedAuthMethodId} onValueChange={setAuthMethodId}>
|
|
948
|
-
<SelectTrigger className="h-8 rounded-lg bg-white">
|
|
949
|
-
<SelectValue placeholder={t('providerAuthMethodPlaceholder')} />
|
|
950
|
-
</SelectTrigger>
|
|
951
|
-
<SelectContent>
|
|
952
|
-
{providerAuthMethodOptions.map((method) => (
|
|
953
|
-
<SelectItem key={method.value} value={method.value}>
|
|
954
|
-
{method.label}
|
|
955
|
-
</SelectItem>
|
|
956
|
-
))}
|
|
957
|
-
</SelectContent>
|
|
958
|
-
</Select>
|
|
959
|
-
)}
|
|
960
|
-
{selectedAuthMethodHint ? (
|
|
961
|
-
<p className="text-xs text-gray-500">{selectedAuthMethodHint}</p>
|
|
962
|
-
) : null}
|
|
963
|
-
</div>
|
|
964
|
-
) : null}
|
|
965
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
966
|
-
<Button
|
|
967
|
-
type="button"
|
|
968
|
-
variant="outline"
|
|
969
|
-
size="sm"
|
|
970
|
-
onClick={handleStartProviderAuth}
|
|
971
|
-
disabled={startProviderAuth.isPending || Boolean(authSessionId)}
|
|
972
|
-
>
|
|
973
|
-
{startProviderAuth.isPending
|
|
974
|
-
? t('providerAuthStarting')
|
|
975
|
-
: authSessionId
|
|
976
|
-
? t('providerAuthAuthorizing')
|
|
977
|
-
: t('providerAuthAuthorizeInBrowser')}
|
|
978
|
-
</Button>
|
|
979
|
-
{providerAuth.supportsCliImport ? (
|
|
980
|
-
<Button
|
|
981
|
-
type="button"
|
|
982
|
-
variant="outline"
|
|
983
|
-
size="sm"
|
|
984
|
-
onClick={handleImportProviderAuthFromCli}
|
|
985
|
-
disabled={importProviderAuthFromCli.isPending}
|
|
986
|
-
>
|
|
987
|
-
{importProviderAuthFromCli.isPending ? t('providerAuthImporting') : t('providerAuthImportFromCli')}
|
|
988
|
-
</Button>
|
|
989
|
-
) : null}
|
|
990
|
-
{authSessionId ? (
|
|
991
|
-
<span className="text-xs text-gray-500">{t('providerAuthSessionLabel')}: {authSessionId.slice(0, 8)}…</span>
|
|
992
|
-
) : null}
|
|
993
|
-
</div>
|
|
994
|
-
{authStatusMessage ? (
|
|
995
|
-
<p className="text-xs text-gray-600">{authStatusMessage}</p>
|
|
996
|
-
) : null}
|
|
997
|
-
</div>
|
|
998
|
-
)}
|
|
609
|
+
<ProviderAuthSection
|
|
610
|
+
providerAuth={providerAuth}
|
|
611
|
+
providerAuthNote={providerAuthNote}
|
|
612
|
+
providerAuthMethodOptions={providerAuthMethodOptions}
|
|
613
|
+
providerAuthMethodsCount={providerAuthMethods.length}
|
|
614
|
+
selectedAuthMethodHint={selectedAuthMethodHint}
|
|
615
|
+
shouldUseAuthMethodPills={shouldUseAuthMethodPills}
|
|
616
|
+
resolvedAuthMethodId={resolvedAuthMethodId}
|
|
617
|
+
onAuthMethodChange={setAuthMethodId}
|
|
618
|
+
onStartProviderAuth={handleStartProviderAuth}
|
|
619
|
+
onImportProviderAuthFromCli={handleImportProviderAuthFromCli}
|
|
620
|
+
startPending={startProviderAuth.isPending}
|
|
621
|
+
importPending={importProviderAuthFromCli.isPending}
|
|
622
|
+
authSessionId={authSessionId}
|
|
623
|
+
authStatusMessage={authStatusMessage}
|
|
624
|
+
/>
|
|
999
625
|
|
|
1000
626
|
<div className="space-y-2">
|
|
1001
627
|
<Label htmlFor="apiBase" className="text-sm font-medium text-gray-900">
|
|
@@ -1012,217 +638,41 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
|
|
|
1012
638
|
<p className="text-xs text-gray-500">{apiBaseHelpText}</p>
|
|
1013
639
|
</div>
|
|
1014
640
|
|
|
1015
|
-
<
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
)
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
{
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
/>
|
|
1051
|
-
<Button type="button" size="sm" onClick={handleAddModel} disabled={modelDraft.trim().length === 0}>
|
|
1052
|
-
{t('add')}
|
|
1053
|
-
</Button>
|
|
1054
|
-
<Button type="button" size="sm" variant="ghost" onClick={() => { setShowModelInput(false); setModelDraft(''); }}>
|
|
1055
|
-
<X className="h-4 w-4" />
|
|
1056
|
-
</Button>
|
|
1057
|
-
</div>
|
|
1058
|
-
)}
|
|
1059
|
-
|
|
1060
|
-
{models.length === 0 ? (
|
|
1061
|
-
<div className="rounded-xl border border-dashed border-gray-200 bg-gray-50 px-4 py-6 text-center">
|
|
1062
|
-
<p className="text-sm text-gray-500">{t('providerModelsEmptyShort')}</p>
|
|
1063
|
-
{!showModelInput && (
|
|
1064
|
-
<button
|
|
1065
|
-
type="button"
|
|
1066
|
-
onClick={() => setShowModelInput(true)}
|
|
1067
|
-
className="mt-2 text-sm text-primary hover:text-primary/80 font-medium"
|
|
1068
|
-
>
|
|
1069
|
-
{t('providerAddFirstModel')}
|
|
1070
|
-
</button>
|
|
1071
|
-
)}
|
|
1072
|
-
</div>
|
|
1073
|
-
) : (
|
|
1074
|
-
<div className="flex flex-wrap gap-2">
|
|
1075
|
-
{models.map((modelName) => {
|
|
1076
|
-
const thinkingEntry = modelThinking[modelName];
|
|
1077
|
-
const supportedLevels = thinkingEntry?.supported ?? [];
|
|
1078
|
-
const defaultThinkingLevel = thinkingEntry?.default ?? null;
|
|
1079
|
-
return (
|
|
1080
|
-
<div
|
|
1081
|
-
key={modelName}
|
|
1082
|
-
className="group inline-flex max-w-full items-center gap-1 rounded-full border border-gray-200 bg-white px-3 py-1.5"
|
|
1083
|
-
>
|
|
1084
|
-
<span className="max-w-[140px] truncate text-sm text-gray-800 sm:max-w-[220px]">{modelName}</span>
|
|
1085
|
-
<Popover>
|
|
1086
|
-
<PopoverTrigger asChild>
|
|
1087
|
-
<button
|
|
1088
|
-
type="button"
|
|
1089
|
-
className="inline-flex h-5 w-5 items-center justify-center rounded-full text-gray-400 transition-opacity hover:bg-gray-100 hover:text-gray-600 opacity-100 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
|
|
1090
|
-
aria-label={t('providerModelThinkingTitle')}
|
|
1091
|
-
title={t('providerModelThinkingTitle')}
|
|
1092
|
-
>
|
|
1093
|
-
<Settings2 className="h-3 w-3" />
|
|
1094
|
-
</button>
|
|
1095
|
-
</PopoverTrigger>
|
|
1096
|
-
<PopoverContent className="w-80 space-y-3">
|
|
1097
|
-
<div className="space-y-1">
|
|
1098
|
-
<p className="text-xs font-semibold text-gray-800">{t('providerModelThinkingTitle')}</p>
|
|
1099
|
-
<p className="text-xs text-gray-500">{t('providerModelThinkingHint')}</p>
|
|
1100
|
-
</div>
|
|
1101
|
-
<div className="flex flex-wrap gap-1.5">
|
|
1102
|
-
{THINKING_LEVELS.map((level) => {
|
|
1103
|
-
const selected = supportedLevels.includes(level);
|
|
1104
|
-
return (
|
|
1105
|
-
<button
|
|
1106
|
-
key={level}
|
|
1107
|
-
type="button"
|
|
1108
|
-
onClick={() => toggleModelThinkingLevel(modelName, level)}
|
|
1109
|
-
className={`rounded-full border px-2.5 py-1 text-xs font-medium transition-colors ${
|
|
1110
|
-
selected
|
|
1111
|
-
? 'border-primary bg-primary text-white'
|
|
1112
|
-
: 'border-gray-200 bg-white text-gray-600 hover:border-primary/40 hover:text-primary'
|
|
1113
|
-
}`}
|
|
1114
|
-
>
|
|
1115
|
-
{formatThinkingLevelLabel(level)}
|
|
1116
|
-
</button>
|
|
1117
|
-
);
|
|
1118
|
-
})}
|
|
1119
|
-
</div>
|
|
1120
|
-
<div className="space-y-1.5">
|
|
1121
|
-
<Label className="text-xs font-medium text-gray-700">{t('providerModelThinkingDefault')}</Label>
|
|
1122
|
-
<Select
|
|
1123
|
-
value={defaultThinkingLevel ?? '__none__'}
|
|
1124
|
-
onValueChange={(value) =>
|
|
1125
|
-
setModelThinkingDefault(
|
|
1126
|
-
modelName,
|
|
1127
|
-
value === '__none__' ? null : (value as ThinkingLevel)
|
|
1128
|
-
)
|
|
1129
|
-
}
|
|
1130
|
-
disabled={supportedLevels.length === 0}
|
|
1131
|
-
>
|
|
1132
|
-
<SelectTrigger className="h-8 rounded-lg bg-white text-xs">
|
|
1133
|
-
<SelectValue />
|
|
1134
|
-
</SelectTrigger>
|
|
1135
|
-
<SelectContent>
|
|
1136
|
-
<SelectItem value="__none__">{t('providerModelThinkingDefaultNone')}</SelectItem>
|
|
1137
|
-
{supportedLevels.map((level) => (
|
|
1138
|
-
<SelectItem key={level} value={level}>
|
|
1139
|
-
{formatThinkingLevelLabel(level)}
|
|
1140
|
-
</SelectItem>
|
|
1141
|
-
))}
|
|
1142
|
-
</SelectContent>
|
|
1143
|
-
</Select>
|
|
1144
|
-
{supportedLevels.length === 0 ? (
|
|
1145
|
-
<p className="text-xs text-gray-500">{t('providerModelThinkingNoSupported')}</p>
|
|
1146
|
-
) : null}
|
|
1147
|
-
</div>
|
|
1148
|
-
</PopoverContent>
|
|
1149
|
-
</Popover>
|
|
1150
|
-
<button
|
|
1151
|
-
type="button"
|
|
1152
|
-
onClick={() => {
|
|
1153
|
-
setModels((prev) => prev.filter((name) => name !== modelName));
|
|
1154
|
-
setModelThinking((prev) => {
|
|
1155
|
-
const next = { ...prev };
|
|
1156
|
-
delete next[modelName];
|
|
1157
|
-
return next;
|
|
1158
|
-
});
|
|
1159
|
-
}}
|
|
1160
|
-
className="inline-flex h-5 w-5 items-center justify-center rounded-full text-gray-400 transition-opacity hover:bg-gray-100 hover:text-gray-600 opacity-100 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
|
|
1161
|
-
aria-label={t('remove')}
|
|
1162
|
-
>
|
|
1163
|
-
<X className="h-3 w-3" />
|
|
1164
|
-
</button>
|
|
1165
|
-
</div>
|
|
1166
|
-
);
|
|
1167
|
-
})}
|
|
1168
|
-
</div>
|
|
1169
|
-
)}
|
|
1170
|
-
</div>
|
|
1171
|
-
|
|
1172
|
-
{/* Advanced Settings - Collapsible */}
|
|
1173
|
-
<div className="border-t border-gray-100 pt-4">
|
|
1174
|
-
<button
|
|
1175
|
-
type="button"
|
|
1176
|
-
onClick={() => setShowAdvanced(!showAdvanced)}
|
|
1177
|
-
className="flex w-full items-center justify-between text-sm text-gray-600 hover:text-gray-900 transition-colors"
|
|
1178
|
-
>
|
|
1179
|
-
<span className="flex items-center gap-1.5">
|
|
1180
|
-
<Settings2 className="h-3.5 w-3.5" />
|
|
1181
|
-
{t('providerAdvancedSettings')}
|
|
1182
|
-
</span>
|
|
1183
|
-
<ChevronDown className={`h-4 w-4 transition-transform ${showAdvanced ? 'rotate-180' : ''}`} />
|
|
1184
|
-
</button>
|
|
1185
|
-
|
|
1186
|
-
{showAdvanced && (
|
|
1187
|
-
<div className="mt-4 space-y-5">
|
|
1188
|
-
{providerSpec.supportsWireApi && (
|
|
1189
|
-
<div className="space-y-2">
|
|
1190
|
-
<Label htmlFor="wireApi" className="text-sm font-medium text-gray-900">
|
|
1191
|
-
{wireApiHint?.label ?? t('wireApi')}
|
|
1192
|
-
</Label>
|
|
1193
|
-
{shouldUseWireApiPills ? (
|
|
1194
|
-
<PillSelector
|
|
1195
|
-
value={wireApi}
|
|
1196
|
-
onChange={(v) => setWireApi(v as WireApiType)}
|
|
1197
|
-
options={wireApiSelectOptions}
|
|
1198
|
-
/>
|
|
1199
|
-
) : (
|
|
1200
|
-
<Select value={wireApi} onValueChange={(v) => setWireApi(v as WireApiType)}>
|
|
1201
|
-
<SelectTrigger className="rounded-xl">
|
|
1202
|
-
<SelectValue />
|
|
1203
|
-
</SelectTrigger>
|
|
1204
|
-
<SelectContent>
|
|
1205
|
-
{wireApiSelectOptions.map((option) => (
|
|
1206
|
-
<SelectItem key={option.value} value={option.value}>
|
|
1207
|
-
{option.label}
|
|
1208
|
-
</SelectItem>
|
|
1209
|
-
))}
|
|
1210
|
-
</SelectContent>
|
|
1211
|
-
</Select>
|
|
1212
|
-
)}
|
|
1213
|
-
</div>
|
|
1214
|
-
)}
|
|
1215
|
-
|
|
1216
|
-
<div className="space-y-2">
|
|
1217
|
-
<Label className="text-sm font-medium text-gray-900">
|
|
1218
|
-
{extraHeadersHint?.label ?? t('extraHeaders')}
|
|
1219
|
-
</Label>
|
|
1220
|
-
<KeyValueEditor value={extraHeaders} onChange={setExtraHeaders} />
|
|
1221
|
-
<p className="text-xs text-gray-500">{t('providerExtraHeadersHelpShort')}</p>
|
|
1222
|
-
</div>
|
|
1223
|
-
</div>
|
|
1224
|
-
)}
|
|
1225
|
-
</div>
|
|
641
|
+
<ProviderModelsSection
|
|
642
|
+
models={models}
|
|
643
|
+
modelThinking={modelThinking}
|
|
644
|
+
modelDraft={modelDraft}
|
|
645
|
+
showModelInput={showModelInput}
|
|
646
|
+
onModelDraftChange={setModelDraft}
|
|
647
|
+
onShowModelInputChange={setShowModelInput}
|
|
648
|
+
onAddModel={handleAddModel}
|
|
649
|
+
onRemoveModel={(modelName) => {
|
|
650
|
+
setModels((prev) => prev.filter((name) => name !== modelName));
|
|
651
|
+
setModelThinking((prev) => {
|
|
652
|
+
const next = { ...prev };
|
|
653
|
+
delete next[modelName];
|
|
654
|
+
return next;
|
|
655
|
+
});
|
|
656
|
+
}}
|
|
657
|
+
onToggleModelThinkingLevel={toggleModelThinkingLevel}
|
|
658
|
+
onSetModelThinkingDefault={setModelThinkingDefault}
|
|
659
|
+
thinkingLevels={THINKING_LEVELS}
|
|
660
|
+
formatThinkingLevelLabel={formatThinkingLevelLabel}
|
|
661
|
+
/>
|
|
662
|
+
|
|
663
|
+
<ProviderAdvancedSettingsSection
|
|
664
|
+
showAdvanced={showAdvanced}
|
|
665
|
+
onShowAdvancedChange={setShowAdvanced}
|
|
666
|
+
supportsWireApi={Boolean(providerSpec.supportsWireApi)}
|
|
667
|
+
wireApiLabel={wireApiHint?.label ?? t('wireApi')}
|
|
668
|
+
wireApi={wireApi}
|
|
669
|
+
onWireApiChange={setWireApi}
|
|
670
|
+
shouldUseWireApiPills={shouldUseWireApiPills}
|
|
671
|
+
wireApiSelectOptions={wireApiSelectOptions}
|
|
672
|
+
extraHeadersLabel={extraHeadersHint?.label ?? t('extraHeaders')}
|
|
673
|
+
extraHeaders={extraHeaders}
|
|
674
|
+
onExtraHeadersChange={setExtraHeaders}
|
|
675
|
+
/>
|
|
1226
676
|
</div>
|
|
1227
677
|
|
|
1228
678
|
<div className="flex items-center justify-between border-t border-gray-100 px-6 py-4">
|