@skaile/forge-settings 1.3.2 → 1.4.0
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 +19 -0
- package/README.md +20 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/ForgeProvidersPanel.d.vue.ts +13 -0
- package/dist/runtime/components/ForgeProvidersPanel.vue +461 -78
- package/dist/runtime/components/ForgeProvidersPanel.vue.d.ts +13 -0
- package/dist/runtime/server/harness-settings.d.ts +50 -12
- package/dist/runtime/server/harness-settings.js +93 -9
- package/dist/runtime/server/index.d.ts +2 -0
- package/dist/runtime/server/index.js +2 -0
- package/dist/runtime/server/provider-service.d.ts +26 -2
- package/dist/runtime/server/provider-service.js +16 -1
- package/dist/runtime/server/provider-write.d.ts +25 -9
- package/dist/runtime/server/provider-write.js +72 -4
- package/dist/runtime/shared/data-zone.d.ts +66 -0
- package/dist/runtime/shared/data-zone.js +79 -0
- package/dist/runtime/shared/harness-providers.d.ts +41 -2
- package/dist/runtime/shared/harness-providers.js +31 -8
- package/dist/runtime/shared/harnesses.d.ts +7 -0
- package/dist/runtime/shared/harnesses.js +18 -0
- package/dist/runtime/shared/provider-accounts.d.ts +215 -0
- package/dist/runtime/shared/provider-accounts.js +255 -0
- package/dist/runtime/shared/provider-registry.d.ts +38 -0
- package/dist/runtime/shared/provider-registry.js +16 -0
- package/dist/runtime/shared/settings-types.d.ts +7 -0
- package/dist/runtime/vue/index.d.ts +2 -0
- package/dist/runtime/vue/index.js +2 -0
- package/dist/runtime/vue/providers-panel-model.d.ts +98 -5
- package/dist/runtime/vue/providers-panel-model.js +13 -1
- package/dist/runtime/vue/useProvidersPanel.d.ts +66 -5
- package/dist/runtime/vue/useProvidersPanel.js +417 -29
- package/package.json +2 -2
- package/src/runtime/components/ForgeProvidersPanel.vue +477 -78
- package/src/runtime/server/harness-settings.ts +165 -21
- package/src/runtime/server/index.ts +2 -0
- package/src/runtime/server/provider-service.ts +53 -1
- package/src/runtime/server/provider-write.ts +129 -15
- package/src/runtime/shared/data-zone.ts +172 -0
- package/src/runtime/shared/harness-providers.ts +77 -6
- package/src/runtime/shared/harnesses.ts +25 -0
- package/src/runtime/shared/provider-accounts.ts +526 -0
- package/src/runtime/shared/provider-registry.ts +58 -0
- package/src/runtime/shared/settings-types.ts +7 -0
- package/src/runtime/vue/index.ts +2 -0
- package/src/runtime/vue/providers-panel-model.ts +123 -5
- package/src/runtime/vue/useProvidersPanel.ts +586 -37
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type WritableComputedRef,
|
|
25
25
|
watch,
|
|
26
26
|
} from "vue";
|
|
27
|
+
import { DATA_ZONE_LABELS, dataZoneRefusal, entryDataZone } from "../shared/data-zone";
|
|
27
28
|
import {
|
|
28
29
|
type EnvLayerId,
|
|
29
30
|
type EnvSettingsDocument,
|
|
@@ -31,11 +32,32 @@ import {
|
|
|
31
32
|
type EnvVarView,
|
|
32
33
|
validateEnvVarName,
|
|
33
34
|
} from "../shared/env-vars";
|
|
35
|
+
import { isPlainObject } from "../shared/guards";
|
|
36
|
+
import {
|
|
37
|
+
AZURE_OPENAI_DEFAULT_API_VERSION,
|
|
38
|
+
AZURE_OPENAI_PRESET,
|
|
39
|
+
azureAccountEntries,
|
|
40
|
+
credentialExpiry,
|
|
41
|
+
defaultParamValue,
|
|
42
|
+
EXPERT_PARAMS,
|
|
43
|
+
FOUNDRY_PRESET,
|
|
44
|
+
missingRequiredParams,
|
|
45
|
+
PROVIDER_PARAM_LABELS,
|
|
46
|
+
type ProviderSignInCredential,
|
|
47
|
+
type ProviderSignInId,
|
|
48
|
+
type ProviderSignInOption,
|
|
49
|
+
presetParamKeys,
|
|
50
|
+
presetSignInOptions,
|
|
51
|
+
providerAccountLine,
|
|
52
|
+
signInCredential,
|
|
53
|
+
signInOptionOf,
|
|
54
|
+
} from "../shared/provider-accounts";
|
|
34
55
|
import {
|
|
35
56
|
AI_PROVIDER_SURFACES,
|
|
36
57
|
type AIProviderCredentialKind,
|
|
37
58
|
type AIProviderKind,
|
|
38
59
|
type AIProviderSurface,
|
|
60
|
+
type DataZone,
|
|
39
61
|
validateCredentialRef,
|
|
40
62
|
} from "../shared/provider-registry";
|
|
41
63
|
import {
|
|
@@ -44,6 +66,7 @@ import {
|
|
|
44
66
|
urlProvidersClient,
|
|
45
67
|
} from "./providers-client";
|
|
46
68
|
import {
|
|
69
|
+
AZURE_ACCOUNT,
|
|
47
70
|
CREDENTIAL_ENTER_KEY,
|
|
48
71
|
CREDENTIAL_OTHER_REF,
|
|
49
72
|
CUSTOM_PRESET,
|
|
@@ -54,29 +77,56 @@ import {
|
|
|
54
77
|
nonEnvSourceLabel,
|
|
55
78
|
OVERRIDES_INSTANCE_NOTICE,
|
|
56
79
|
PROVIDER_ID_PATTERN,
|
|
80
|
+
type ProvidersPanelAzureForm,
|
|
57
81
|
type ProvidersPanelCredentialOption,
|
|
82
|
+
type ProvidersPanelDialogStep,
|
|
58
83
|
type ProvidersPanelEmit,
|
|
59
84
|
type ProvidersPanelEntry,
|
|
60
85
|
type ProvidersPanelForm,
|
|
86
|
+
type ProvidersPanelModelsForm,
|
|
87
|
+
type ProvidersPanelModelsMode,
|
|
61
88
|
type ProvidersPanelPreset,
|
|
62
89
|
type ProvidersPanelProps,
|
|
63
90
|
type ProvidersPanelRow,
|
|
64
91
|
type ProvidersPanelRowState,
|
|
92
|
+
providerModelChoices,
|
|
93
|
+
providerModelsChip,
|
|
65
94
|
requestErrorMessage,
|
|
66
95
|
} from "./providers-panel-model";
|
|
67
96
|
|
|
68
97
|
export type {
|
|
98
|
+
ProvidersPanelAzureForm,
|
|
69
99
|
ProvidersPanelCredentialOption,
|
|
100
|
+
ProvidersPanelDialogStep,
|
|
70
101
|
ProvidersPanelEmit,
|
|
71
102
|
ProvidersPanelEntry,
|
|
72
103
|
ProvidersPanelForm,
|
|
73
104
|
ProvidersPanelLayer,
|
|
105
|
+
ProvidersPanelModelsForm,
|
|
106
|
+
ProvidersPanelModelsMode,
|
|
74
107
|
ProvidersPanelPreset,
|
|
75
108
|
ProvidersPanelProps,
|
|
76
109
|
ProvidersPanelRow,
|
|
77
110
|
ProvidersPanelRowState,
|
|
78
111
|
} from "./providers-panel-model";
|
|
79
|
-
export {
|
|
112
|
+
export {
|
|
113
|
+
AZURE_ACCOUNT,
|
|
114
|
+
CREDENTIAL_ENTER_KEY,
|
|
115
|
+
CREDENTIAL_OTHER_REF,
|
|
116
|
+
CUSTOM_PRESET,
|
|
117
|
+
providerModelChoices,
|
|
118
|
+
providerModelsChip,
|
|
119
|
+
} from "./providers-panel-model";
|
|
120
|
+
|
|
121
|
+
/** One parameter field of the add/edit dialog. */
|
|
122
|
+
export interface ProvidersPanelParamField {
|
|
123
|
+
key: string;
|
|
124
|
+
label: string;
|
|
125
|
+
/** The preset requires it. */
|
|
126
|
+
required: boolean;
|
|
127
|
+
/** Editable; expert-only parameters are read-only outside expert mode. */
|
|
128
|
+
editable: boolean;
|
|
129
|
+
}
|
|
80
130
|
|
|
81
131
|
/** What both `ForgeProvidersPanel` variants bind to. */
|
|
82
132
|
export interface ProvidersPanelState {
|
|
@@ -104,14 +154,48 @@ export interface ProvidersPanelState {
|
|
|
104
154
|
baseUrlPlaceholder: ComputedRef<string>;
|
|
105
155
|
/** "Set value now" is offered: the scope has an `.env` route. */
|
|
106
156
|
canSetValue: ComputedRef<boolean>;
|
|
107
|
-
/** The dialog shows the env name and secret fields:
|
|
157
|
+
/** The dialog shows the env name and secret fields: the chosen sign-in takes a key. */
|
|
108
158
|
showCredentialFields: ComputedRef<boolean>;
|
|
109
159
|
idError: ComputedRef<string | null>;
|
|
160
|
+
/** Open the add dialog: on the library, or on `presetId`'s account step. */
|
|
110
161
|
openAdd: (presetId?: string) => void;
|
|
111
162
|
openEdit: (row: ProvidersPanelEntry) => void;
|
|
112
163
|
selectPreset: (value: string) => void;
|
|
164
|
+
/** Create (Add) or save (Edit). Adding with an explicit client then tests what it created. */
|
|
113
165
|
saveProvider: () => Promise<void>;
|
|
114
166
|
|
|
167
|
+
// Add flow: library → account (sign-in, fields) → done (test)
|
|
168
|
+
dialogStep: Ref<ProvidersPanelDialogStep>;
|
|
169
|
+
/** The library's presets, catalog order; the Azure OpenAI and Foundry presets sit behind the Azure card. */
|
|
170
|
+
libraryPresets: ComputedRef<ProvidersPanelPreset[]>;
|
|
171
|
+
/** Pick a library card: a preset id, {@link CUSTOM_PRESET} or {@link AZURE_ACCOUNT}. */
|
|
172
|
+
chooseLibrary: (value: string) => void;
|
|
173
|
+
/** From the account step of an add back to the library. */
|
|
174
|
+
backToLibrary: () => void;
|
|
175
|
+
/** The account step is the Microsoft Azure card. */
|
|
176
|
+
isAzureAccount: ComputedRef<boolean>;
|
|
177
|
+
/** "How do you sign in?": the kinds the chosen preset accepts (the reference form only in expert mode). */
|
|
178
|
+
signInOptions: ComputedRef<ProviderSignInOption[]>;
|
|
179
|
+
signInOption: ComputedRef<ProviderSignInOption | undefined>;
|
|
180
|
+
selectSignIn: (id: ProviderSignInId) => void;
|
|
181
|
+
/** The sign-in step is shown; editing an entry whose credential none of the options expresses keeps it as is. */
|
|
182
|
+
showSignIn: ComputedRef<boolean>;
|
|
183
|
+
/** The reference field is shown: the chosen sign-in is a typed reference. */
|
|
184
|
+
showReferenceField: ComputedRef<boolean>;
|
|
185
|
+
/** The variable a blank key name falls back to: the preset's (the Azure card: Azure OpenAI's). */
|
|
186
|
+
defaultEnvName: ComputedRef<string | undefined>;
|
|
187
|
+
/** Parameter fields of the chosen preset (required ones, plus any the edited entry holds). */
|
|
188
|
+
paramFields: ComputedRef<ProvidersPanelParamField[]>;
|
|
189
|
+
azureForm: ProvidersPanelAzureForm;
|
|
190
|
+
addDeploymentRow: () => void;
|
|
191
|
+
removeDeploymentRow: (index: number) => void;
|
|
192
|
+
/** Ids the last add created; the done step lists them with their test results. */
|
|
193
|
+
createdIds: Ref<string[]>;
|
|
194
|
+
/** Close the dialog from the done step. */
|
|
195
|
+
finishDialog: () => void;
|
|
196
|
+
/** "Renew" on a row: re-open its sign-in (the edit dialog, else the row's key or reference editor). */
|
|
197
|
+
openSignIn: (row: ProvidersPanelRow) => void;
|
|
198
|
+
|
|
115
199
|
deleteTarget: Ref<ProvidersPanelEntry | null>;
|
|
116
200
|
/** The confirmation dialog's `v-model`; closing it cancels. */
|
|
117
201
|
deleteOpen: WritableComputedRef<boolean>;
|
|
@@ -134,6 +218,8 @@ export interface ProvidersPanelState {
|
|
|
134
218
|
setEnabled: (row: ProvidersPanelRow, enabled: boolean) => Promise<void>;
|
|
135
219
|
/** Delete the workspace patch of an inherited entry. */
|
|
136
220
|
resetPatch: (row: ProvidersPanelRow) => Promise<void>;
|
|
221
|
+
/** "EU processing is on for this account": writes the entry's `eu_processing`. */
|
|
222
|
+
setEuProcessing: (row: ProvidersPanelRow, on: boolean) => Promise<void>;
|
|
137
223
|
/** Row id with a write in flight. */
|
|
138
224
|
rowBusy: Ref<string | null>;
|
|
139
225
|
rowError: Ref<{ id: string; message: string } | null>;
|
|
@@ -143,7 +229,7 @@ export interface ProvidersPanelState {
|
|
|
143
229
|
testBusy: Ref<string | null>;
|
|
144
230
|
/** The last test result per row id. */
|
|
145
231
|
testResults: Record<string, ProvidersPanelTestResult>;
|
|
146
|
-
testProvider: (row: ProvidersPanelEntry) => Promise<void>;
|
|
232
|
+
testProvider: (row: Pick<ProvidersPanelEntry, "id">) => Promise<void>;
|
|
147
233
|
|
|
148
234
|
// Inline key editor
|
|
149
235
|
/** "Enter a key…" is offered: the scope has an editable `.env` route. */
|
|
@@ -165,6 +251,25 @@ export interface ProvidersPanelState {
|
|
|
165
251
|
cancelRefEditor: () => void;
|
|
166
252
|
saveRefEditor: () => Promise<void>;
|
|
167
253
|
|
|
254
|
+
// "Models allowed here"
|
|
255
|
+
/** Row id whose model filter is open. */
|
|
256
|
+
modelsEditing: Ref<string | null>;
|
|
257
|
+
modelsForm: ProvidersPanelModelsForm;
|
|
258
|
+
/** The models offered: the entry's `model_map` ids, else what `client.models` lists. */
|
|
259
|
+
modelsKnown: Ref<string[]>;
|
|
260
|
+
modelsLoading: Ref<boolean>;
|
|
261
|
+
/** Why the entry's models could not be listed (the checklist then holds only allowed ids). */
|
|
262
|
+
modelsListMessage: Ref<string | null>;
|
|
263
|
+
/** The checklist after the search. */
|
|
264
|
+
modelChoices: ComputedRef<string[]>;
|
|
265
|
+
modelsError: Ref<string | null>;
|
|
266
|
+
openModelsEditor: (row: ProvidersPanelRow) => Promise<void>;
|
|
267
|
+
setModelsMode: (mode: ProvidersPanelModelsMode) => void;
|
|
268
|
+
toggleModel: (id: string, on: boolean) => void;
|
|
269
|
+
cancelModelsEditor: () => void;
|
|
270
|
+
/** Write `allowed_models`: `null` for All models, else the ticked ids in checklist order. */
|
|
271
|
+
saveModelsEditor: () => Promise<void>;
|
|
272
|
+
|
|
168
273
|
// Environment
|
|
169
274
|
envDocument: Ref<EnvSettingsDocument | null>;
|
|
170
275
|
/** The environment document says the caller may write its layer. */
|
|
@@ -360,11 +465,20 @@ export function useProvidersPanel(
|
|
|
360
465
|
return notices;
|
|
361
466
|
}
|
|
362
467
|
|
|
468
|
+
/** The muted note of a row the host's data-zone rule keeps out; `null` when it does not. */
|
|
469
|
+
function blockedNoteOf(zone: DataZone, euProcessing: boolean | undefined): string | null {
|
|
470
|
+
const rule = props.dataZoneRule;
|
|
471
|
+
if (!rule || rule === "any") return null;
|
|
472
|
+
return dataZoneRefusal(rule, zone, euProcessing) ?? null;
|
|
473
|
+
}
|
|
474
|
+
|
|
363
475
|
function buildRow(entry: ProvidersPanelEntry): ProvidersPanelRow {
|
|
364
476
|
const state = stateOf(entry);
|
|
365
477
|
const writable = !props.readOnly && !entry.locked;
|
|
366
478
|
const needsCredential = entry.credentialKind !== "none";
|
|
367
479
|
const envName = envNameOfRef(entry.credentialRef);
|
|
480
|
+
const zone = entry.dataZone ?? entryDataZone({ preset: entry.preset });
|
|
481
|
+
const isModelEntry = entry.kind === "llm" || entry.kind === "embed";
|
|
368
482
|
return {
|
|
369
483
|
...entry,
|
|
370
484
|
state,
|
|
@@ -385,6 +499,15 @@ export function useProvidersPanel(
|
|
|
385
499
|
origin: entry.preset ?? entry.surface ?? entry.kind,
|
|
386
500
|
status: statusOf(entry, needsCredential),
|
|
387
501
|
overrideNotices: overrideNoticesOf(entry),
|
|
502
|
+
accountLine: providerAccountLine(entry, presets.value),
|
|
503
|
+
expiry: needsCredential ? credentialExpiry(entry.credentialExpiresAt) : null,
|
|
504
|
+
canRenew: needsCredential && writable,
|
|
505
|
+
zone,
|
|
506
|
+
zoneLabel: DATA_ZONE_LABELS[zone],
|
|
507
|
+
canSetEuProcessing: zone === "eu_option" && writable && state === "own",
|
|
508
|
+
blockedNote: blockedNoteOf(zone, entry.euProcessing),
|
|
509
|
+
modelsChip: isModelEntry ? providerModelsChip(entry.allowedModels) : null,
|
|
510
|
+
canFilterModels: isModelEntry && writable && state === "own",
|
|
388
511
|
};
|
|
389
512
|
}
|
|
390
513
|
|
|
@@ -395,15 +518,36 @@ export function useProvidersPanel(
|
|
|
395
518
|
const dialogVisible = ref(false);
|
|
396
519
|
/** The row being edited; `null` while adding. */
|
|
397
520
|
const editing = ref<ProvidersPanelEntry | null>(null);
|
|
398
|
-
const form = reactive<ProvidersPanelForm>({ ...EMPTY_FORM });
|
|
521
|
+
const form = reactive<ProvidersPanelForm>({ ...EMPTY_FORM, params: {} });
|
|
399
522
|
const dialogError = ref<string | null>(null);
|
|
400
523
|
const saving = ref(false);
|
|
524
|
+
const dialogStep = ref<ProvidersPanelDialogStep>("account");
|
|
525
|
+
const createdIds = ref<string[]>([]);
|
|
526
|
+
const azureForm = reactive<ProvidersPanelAzureForm>(emptyAzureForm());
|
|
527
|
+
|
|
528
|
+
/** A fresh Azure card: Azure OpenAI chosen, one blank deployment row, the default `api_version`. */
|
|
529
|
+
function emptyAzureForm(): ProvidersPanelAzureForm {
|
|
530
|
+
return {
|
|
531
|
+
resource: "",
|
|
532
|
+
useOpenAI: true,
|
|
533
|
+
useFoundry: false,
|
|
534
|
+
deployments: [{ deployment: "", model: "" }],
|
|
535
|
+
apiVersion: AZURE_OPENAI_DEFAULT_API_VERSION,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
401
538
|
|
|
402
539
|
const presetOptions = computed(() => [
|
|
403
540
|
...presets.value.map((preset) => ({ label: preset.label, value: preset.id })),
|
|
404
541
|
{ label: "Custom", value: CUSTOM_PRESET },
|
|
405
542
|
]);
|
|
406
543
|
|
|
544
|
+
/** Azure OpenAI and Claude on Foundry are added through the Microsoft Azure card. */
|
|
545
|
+
const libraryPresets = computed(() =>
|
|
546
|
+
presets.value.filter(
|
|
547
|
+
(preset) => preset.id !== AZURE_OPENAI_PRESET && preset.id !== FOUNDRY_PRESET,
|
|
548
|
+
),
|
|
549
|
+
);
|
|
550
|
+
|
|
407
551
|
const surfaceOptions = AI_PROVIDER_SURFACES.map((surface) => ({
|
|
408
552
|
label: surface,
|
|
409
553
|
value: surface,
|
|
@@ -413,6 +557,8 @@ export function useProvidersPanel(
|
|
|
413
557
|
|
|
414
558
|
const isCustom = computed(() => form.preset === CUSTOM_PRESET);
|
|
415
559
|
|
|
560
|
+
const isAzureAccount = computed(() => form.preset === AZURE_ACCOUNT);
|
|
561
|
+
|
|
416
562
|
/** Kind of a custom entry: the edited one's, else `llm`. */
|
|
417
563
|
const customKind = computed<AIProviderKind>(() => editing.value?.kind ?? "llm");
|
|
418
564
|
|
|
@@ -428,8 +574,83 @@ export function useProvidersPanel(
|
|
|
428
574
|
/** Whether "Set value now" is offered: the scope has an `.env` route. */
|
|
429
575
|
const canSetValue = computed(() => environmentOn.value);
|
|
430
576
|
|
|
431
|
-
/** The
|
|
432
|
-
const
|
|
577
|
+
/** The preset whose sign-in kinds the Azure card offers: Azure OpenAI's, Foundry's when only that is used. */
|
|
578
|
+
const azureSignInPreset = computed<Pick<ProvidersPanelPreset, "credential" | "transport">>(() => {
|
|
579
|
+
const openai = presets.value.find((p) => p.id === AZURE_OPENAI_PRESET);
|
|
580
|
+
const foundry = presets.value.find((p) => p.id === FOUNDRY_PRESET);
|
|
581
|
+
const chosen = !azureForm.useOpenAI && azureForm.useFoundry ? foundry : openai;
|
|
582
|
+
return (
|
|
583
|
+
chosen ??
|
|
584
|
+
openai ??
|
|
585
|
+
foundry ?? {
|
|
586
|
+
credential: { kind: "api_key_header", env: "AZURE_OPENAI_API_KEY" },
|
|
587
|
+
transport: "azure-openai",
|
|
588
|
+
}
|
|
589
|
+
);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
/** The sign-in options the account step offers. */
|
|
593
|
+
const signInOptions = computed<ProviderSignInOption[]>(() => {
|
|
594
|
+
const expert = props.expert === true;
|
|
595
|
+
if (isAzureAccount.value) return presetSignInOptions(azureSignInPreset.value, { expert });
|
|
596
|
+
if (!form.preset) return [];
|
|
597
|
+
return presetSignInOptions(isCustom.value ? null : (selectedPreset.value ?? null), { expert });
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
const signInOption = computed(() => signInOptions.value.find((o) => o.id === form.signIn));
|
|
601
|
+
|
|
602
|
+
/** The sign-in option the edited entry's stored credential corresponds to. */
|
|
603
|
+
const storedSignIn = computed(() => {
|
|
604
|
+
const row = editing.value;
|
|
605
|
+
if (!row) return undefined;
|
|
606
|
+
const kind = row.credentialKind ?? selectedPreset.value?.credential.kind;
|
|
607
|
+
return signInOptionOf({ kind, ref: row.credentialRef }, signInOptions.value);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Editing an entry whose stored credential none of the options expresses
|
|
612
|
+
* (an `oauth` credential, or a `keyring:` reference outside expert mode)
|
|
613
|
+
* keeps it as is; the row's picker still changes it.
|
|
614
|
+
*/
|
|
615
|
+
const showSignIn = computed(() => {
|
|
616
|
+
const row = editing.value;
|
|
617
|
+
if (!row) return signInOptions.value.length > 0;
|
|
618
|
+
const option = storedSignIn.value;
|
|
619
|
+
const kind = row.credentialKind ?? selectedPreset.value?.credential.kind;
|
|
620
|
+
if (!option || (kind && option.credentialKind !== kind)) return false;
|
|
621
|
+
const ref = row.credentialRef;
|
|
622
|
+
if (!ref) return option.fields !== "reference";
|
|
623
|
+
return option.fields === (envNameOfRef(ref) ? "key" : "reference");
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
/** The dialog collects a key (env name, optional value) for the chosen sign-in. */
|
|
627
|
+
const showCredentialFields = computed(
|
|
628
|
+
() => showSignIn.value && signInOption.value?.fields === "key",
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
const showReferenceField = computed(
|
|
632
|
+
() => showSignIn.value && signInOption.value?.fields === "reference",
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
/** The conventional variable a blank key name falls back to. */
|
|
636
|
+
const defaultEnvName = computed(() =>
|
|
637
|
+
isAzureAccount.value
|
|
638
|
+
? azureSignInPreset.value.credential.env
|
|
639
|
+
: selectedPreset.value?.credential.env,
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
const paramFields = computed<ProvidersPanelParamField[]>(() => {
|
|
643
|
+
if (isCustom.value || isAzureAccount.value) return [];
|
|
644
|
+
const preset = selectedPreset.value;
|
|
645
|
+
const kept = editing.value && editing.value.preset === form.preset ? editing.value : null;
|
|
646
|
+
const required = new Set(preset?.required_params ?? []);
|
|
647
|
+
return presetParamKeys(preset, kept?.transportParams).map((key) => ({
|
|
648
|
+
key,
|
|
649
|
+
label: PROVIDER_PARAM_LABELS[key] ?? key,
|
|
650
|
+
required: required.has(key),
|
|
651
|
+
editable: props.expert === true || !EXPERT_PARAMS.includes(key),
|
|
652
|
+
}));
|
|
653
|
+
});
|
|
433
654
|
|
|
434
655
|
const idError = computed(() => {
|
|
435
656
|
const id = form.id.trim();
|
|
@@ -438,28 +659,74 @@ export function useProvidersPanel(
|
|
|
438
659
|
: null;
|
|
439
660
|
});
|
|
440
661
|
|
|
441
|
-
const dialogTitle = computed(() =>
|
|
442
|
-
editing.value
|
|
443
|
-
|
|
662
|
+
const dialogTitle = computed(() => {
|
|
663
|
+
if (editing.value) return `Edit provider — ${editing.value.id}`;
|
|
664
|
+
if (dialogStep.value === "done") return "Provider added";
|
|
665
|
+
if (dialogStep.value === "account" && isAzureAccount.value) return "Add Microsoft Azure";
|
|
666
|
+
return "Add provider";
|
|
667
|
+
});
|
|
444
668
|
|
|
445
669
|
function resetDialog(values: Partial<ProvidersPanelForm>, row: ProvidersPanelEntry | null) {
|
|
446
670
|
Object.assign(form, EMPTY_FORM, values);
|
|
671
|
+
form.params = { ...(values.params ?? {}) };
|
|
672
|
+
Object.assign(azureForm, emptyAzureForm());
|
|
447
673
|
editing.value = row;
|
|
448
674
|
dialogError.value = null;
|
|
675
|
+
createdIds.value = [];
|
|
676
|
+
dialogStep.value = "account";
|
|
449
677
|
dialogVisible.value = true;
|
|
450
678
|
}
|
|
451
679
|
|
|
452
|
-
/**
|
|
680
|
+
/** Pick the first sign-in option when the current one is not offered. */
|
|
681
|
+
function settleSignIn() {
|
|
682
|
+
if (!signInOptions.value.some((o) => o.id === form.signIn)) {
|
|
683
|
+
form.signIn = signInOptions.value[0]?.id ?? "";
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/** Open the add dialog: on the library, or straight on `presetId`'s account step. */
|
|
453
688
|
function openAdd(presetId?: string) {
|
|
454
689
|
if (props.readOnly) return;
|
|
455
690
|
resetDialog({}, null);
|
|
456
|
-
if (presetId)
|
|
691
|
+
if (presetId) chooseLibrary(presetId);
|
|
692
|
+
else dialogStep.value = "library";
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function chooseLibrary(value: string) {
|
|
696
|
+
if (props.readOnly || editing.value) return;
|
|
697
|
+
dialogError.value = null;
|
|
698
|
+
if (value === AZURE_ACCOUNT) {
|
|
699
|
+
form.preset = AZURE_ACCOUNT;
|
|
700
|
+
form.id = "";
|
|
701
|
+
form.envName = "";
|
|
702
|
+
form.params = {};
|
|
703
|
+
} else {
|
|
704
|
+
if (form.preset === AZURE_ACCOUNT) form.preset = "";
|
|
705
|
+
selectPreset(value);
|
|
706
|
+
}
|
|
707
|
+
settleSignIn();
|
|
708
|
+
dialogStep.value = "account";
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function backToLibrary() {
|
|
712
|
+
if (editing.value) return;
|
|
713
|
+
dialogError.value = null;
|
|
714
|
+
dialogStep.value = "library";
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function selectSignIn(id: ProviderSignInId) {
|
|
718
|
+
if (signInOptions.value.some((o) => o.id === id)) form.signIn = id;
|
|
457
719
|
}
|
|
458
720
|
|
|
459
721
|
/** Open the dialog on an entry this scope's document defines; its id stays fixed. */
|
|
460
722
|
function openEdit(row: ProvidersPanelEntry) {
|
|
461
723
|
if (!isEditable(row)) return;
|
|
462
724
|
const preset = presets.value.find((p) => p.id === row.preset);
|
|
725
|
+
const ref = row.credentialRef;
|
|
726
|
+
const params: Record<string, string> = {};
|
|
727
|
+
for (const key of presetParamKeys(preset, row.transportParams)) {
|
|
728
|
+
params[key] = row.transportParams?.[key] ?? defaultParamValue(preset?.id, key);
|
|
729
|
+
}
|
|
463
730
|
resetDialog(
|
|
464
731
|
{
|
|
465
732
|
preset: preset ? preset.id : CUSTOM_PRESET,
|
|
@@ -467,15 +734,19 @@ export function useProvidersPanel(
|
|
|
467
734
|
surface: row.surface ?? "",
|
|
468
735
|
// The preset's own endpoint shows as the placeholder, not as a pinned value.
|
|
469
736
|
baseUrl: row.baseUrl && row.baseUrl !== preset?.base_url ? row.baseUrl : "",
|
|
737
|
+
envName: envNameOfRef(ref) ?? "",
|
|
738
|
+
credentialRef: ref && !envNameOfRef(ref) ? ref : "",
|
|
739
|
+
params,
|
|
470
740
|
},
|
|
471
741
|
row,
|
|
472
742
|
);
|
|
743
|
+
form.signIn = storedSignIn.value?.id ?? signInOptions.value[0]?.id ?? "";
|
|
473
744
|
}
|
|
474
745
|
|
|
475
746
|
/**
|
|
476
747
|
* Switch the preset. Fields still holding the previous preset's defaults
|
|
477
|
-
* (the id while adding, the env var, a custom surface
|
|
478
|
-
* anything the user typed is kept.
|
|
748
|
+
* (the id while adding, the env var, a custom surface, the parameters)
|
|
749
|
+
* follow the new one; anything the user typed is kept.
|
|
479
750
|
*/
|
|
480
751
|
function selectPreset(value: string) {
|
|
481
752
|
const previous = selectedPreset.value;
|
|
@@ -485,6 +756,50 @@ export function useProvidersPanel(
|
|
|
485
756
|
if (form.envName === "" || form.envName === previous?.credential.env)
|
|
486
757
|
form.envName = next?.credential.env ?? "";
|
|
487
758
|
if (value === CUSTOM_PRESET && form.surface === "") form.surface = previous?.surface ?? "";
|
|
759
|
+
const params: Record<string, string> = {};
|
|
760
|
+
for (const key of presetParamKeys(next)) {
|
|
761
|
+
const typed = form.params[key]?.trim();
|
|
762
|
+
params[key] =
|
|
763
|
+
typed && typed !== defaultParamValue(previous?.id, key)
|
|
764
|
+
? typed
|
|
765
|
+
: defaultParamValue(next?.id, key);
|
|
766
|
+
}
|
|
767
|
+
form.params = params;
|
|
768
|
+
settleSignIn();
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function addDeploymentRow() {
|
|
772
|
+
azureForm.deployments.push({ deployment: "", model: "" });
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function removeDeploymentRow(index: number) {
|
|
776
|
+
azureForm.deployments.splice(index, 1);
|
|
777
|
+
if (azureForm.deployments.length === 0) addDeploymentRow();
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/** The credential the chosen sign-in writes. */
|
|
781
|
+
function dialogCredential(): ProviderSignInCredential {
|
|
782
|
+
const option = signInOption.value;
|
|
783
|
+
if (!option) return { ok: false, error: "Choose how you sign in." };
|
|
784
|
+
return signInCredential(
|
|
785
|
+
option,
|
|
786
|
+
{ envName: form.envName, ref: form.credentialRef },
|
|
787
|
+
{ defaultEnv: defaultEnvName.value },
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/** Why the key fields cannot be saved, or `null`. */
|
|
792
|
+
function keyFieldsRefusal(): string | null {
|
|
793
|
+
if (!showCredentialFields.value) return null;
|
|
794
|
+
const envName = form.envName.trim();
|
|
795
|
+
if (envName) {
|
|
796
|
+
const refusal = validateEnvVarName(envName);
|
|
797
|
+
if (refusal) return refusal;
|
|
798
|
+
}
|
|
799
|
+
if (form.secret && !envName && !defaultEnvName.value) {
|
|
800
|
+
return "Name the environment variable the value is stored in.";
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
488
803
|
}
|
|
489
804
|
|
|
490
805
|
/** Why the form cannot be saved, or `null`. */
|
|
@@ -494,13 +809,17 @@ export function useProvidersPanel(
|
|
|
494
809
|
if (idError.value) return idError.value;
|
|
495
810
|
if (showSurface.value && !form.surface) return "Choose the surface a custom provider speaks.";
|
|
496
811
|
if (isCustom.value && !form.baseUrl.trim()) return "A custom provider needs a base URL.";
|
|
497
|
-
if (
|
|
498
|
-
const
|
|
499
|
-
if (
|
|
500
|
-
|
|
501
|
-
if (refusal) return refusal;
|
|
812
|
+
if (!isCustom.value && selectedPreset.value) {
|
|
813
|
+
const missing = missingRequiredParams(selectedPreset.value, form.params)[0];
|
|
814
|
+
if (missing) {
|
|
815
|
+
return `Enter the ${(PROVIDER_PARAM_LABELS[missing] ?? missing).toLowerCase()} (${missing}).`;
|
|
502
816
|
}
|
|
503
|
-
|
|
817
|
+
}
|
|
818
|
+
if (showSignIn.value) {
|
|
819
|
+
const keyRefusal = keyFieldsRefusal();
|
|
820
|
+
if (keyRefusal) return keyRefusal;
|
|
821
|
+
const credential = dialogCredential();
|
|
822
|
+
if (!credential.ok) return credential.error;
|
|
504
823
|
}
|
|
505
824
|
return null;
|
|
506
825
|
}
|
|
@@ -508,7 +827,8 @@ export function useProvidersPanel(
|
|
|
508
827
|
/**
|
|
509
828
|
* The `PUT ${providersWriteUrl}/:id` body. The write replaces every editable
|
|
510
829
|
* field, so an edit starts from the defining document's values the dialog
|
|
511
|
-
* does not show (`layerFields`)
|
|
830
|
+
* does not show (`layerFields`). An edit whose credential the sign-in step
|
|
831
|
+
* cannot express keeps the stored one.
|
|
512
832
|
*/
|
|
513
833
|
function buildBody(): Record<string, unknown> {
|
|
514
834
|
const row = editing.value;
|
|
@@ -526,43 +846,131 @@ export function useProvidersPanel(
|
|
|
526
846
|
}
|
|
527
847
|
const baseUrl = form.baseUrl.trim();
|
|
528
848
|
if (baseUrl) body.base_url = baseUrl;
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
849
|
+
|
|
850
|
+
const fields = paramFields.value;
|
|
851
|
+
if (fields.length > 0) {
|
|
852
|
+
const params: Record<string, unknown> =
|
|
853
|
+
kept && isPlainObject(body.params) ? { ...body.params } : {};
|
|
854
|
+
for (const { key } of fields) {
|
|
855
|
+
const value = form.params[key]?.trim();
|
|
856
|
+
if (value) params[key] = value;
|
|
857
|
+
else delete params[key];
|
|
858
|
+
}
|
|
859
|
+
body.params = params;
|
|
860
|
+
} else if (!kept) {
|
|
861
|
+
// Another preset (or custom): the previous preset's parameters do not carry over.
|
|
862
|
+
delete body.params;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
if (showSignIn.value) {
|
|
866
|
+
const credential = dialogCredential();
|
|
867
|
+
if (credential.ok && credential.credential) body.credential = credential.credential;
|
|
868
|
+
} else if (row?.credentialRef) {
|
|
869
|
+
const credentialKind: AIProviderCredentialKind =
|
|
870
|
+
kept?.credentialKind ?? (isCustom.value ? "bearer" : (preset?.credential.kind ?? "bearer"));
|
|
871
|
+
body.credential = { kind: credentialKind, ref: row.credentialRef };
|
|
872
|
+
} else if (kept?.credentialKind) {
|
|
873
|
+
body.credential = { kind: kept.credentialKind };
|
|
874
|
+
}
|
|
534
875
|
return body;
|
|
535
876
|
}
|
|
536
877
|
|
|
878
|
+
/** Write a "Set value now" secret to the scope's `.env` first, under the key name (else the preset's). */
|
|
879
|
+
async function writeDialogSecret() {
|
|
880
|
+
const name = form.envName.trim() || defaultEnvName.value || "";
|
|
881
|
+
if (!showCredentialFields.value || !form.secret || !environmentOn.value || !name) return;
|
|
882
|
+
const update: EnvSettingsUpdate = { set: { [name]: form.secret }, unset: [] };
|
|
883
|
+
await client().updateEnvironment(update);
|
|
884
|
+
form.secret = "";
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
/**
|
|
888
|
+
* After an add: with an explicit client, test every created entry and show
|
|
889
|
+
* the results on the done step; otherwise close the dialog.
|
|
890
|
+
*/
|
|
891
|
+
async function finishAdd(ids: string[]) {
|
|
892
|
+
createdIds.value = ids;
|
|
893
|
+
await afterWrite();
|
|
894
|
+
if (!canTest.value) {
|
|
895
|
+
dialogVisible.value = false;
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
dialogStep.value = "done";
|
|
899
|
+
for (const id of ids) await testProvider({ id });
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/** Save the Azure card: the secret first, then each entry; ids written before a refusal are named. */
|
|
903
|
+
async function saveAzureAccount() {
|
|
904
|
+
const keyRefusal = keyFieldsRefusal();
|
|
905
|
+
const credential = dialogCredential();
|
|
906
|
+
const drafts = keyRefusal
|
|
907
|
+
? { ok: false as const, error: keyRefusal }
|
|
908
|
+
: !credential.ok
|
|
909
|
+
? credential
|
|
910
|
+
: azureAccountEntries({
|
|
911
|
+
resource: azureForm.resource,
|
|
912
|
+
credential: credential.credential,
|
|
913
|
+
openai: azureForm.useOpenAI
|
|
914
|
+
? { deployments: azureForm.deployments, apiVersion: azureForm.apiVersion }
|
|
915
|
+
: undefined,
|
|
916
|
+
foundry: azureForm.useFoundry ? { deployments: [] } : undefined,
|
|
917
|
+
takenIds: providers.value.map((entry) => entry.id),
|
|
918
|
+
});
|
|
919
|
+
if (!drafts.ok) {
|
|
920
|
+
dialogError.value = drafts.error;
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
saving.value = true;
|
|
924
|
+
dialogError.value = null;
|
|
925
|
+
const created: string[] = [];
|
|
926
|
+
try {
|
|
927
|
+
await writeDialogSecret();
|
|
928
|
+
for (const draft of drafts.entries) {
|
|
929
|
+
await client().upsert(draft.id, draft.body);
|
|
930
|
+
created.push(draft.id);
|
|
931
|
+
}
|
|
932
|
+
await finishAdd(created);
|
|
933
|
+
} catch (err) {
|
|
934
|
+
const message = requestErrorMessage(err, "Could not add the Azure account.");
|
|
935
|
+
dialogError.value =
|
|
936
|
+
created.length > 0 ? `${message} (created: ${created.join(", ")})` : message;
|
|
937
|
+
if (created.length > 0) await afterWrite();
|
|
938
|
+
} finally {
|
|
939
|
+
saving.value = false;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
537
943
|
/**
|
|
538
944
|
* Save the dialog. A "Set value now" secret is written first, through the
|
|
539
945
|
* scope's `.env` route as `NAME=value`; only then is the entry — carrying
|
|
540
946
|
* the `env:NAME` reference — written. Refusals from either request stay
|
|
541
|
-
* inline in the dialog.
|
|
947
|
+
* inline in the dialog. The Azure card writes up to two entries.
|
|
542
948
|
*/
|
|
543
949
|
async function saveProvider() {
|
|
544
950
|
if (props.readOnly) return;
|
|
951
|
+
if (isAzureAccount.value && !editing.value) {
|
|
952
|
+
await saveAzureAccount();
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
545
955
|
const refusal = formRefusal();
|
|
546
956
|
if (refusal) {
|
|
547
957
|
dialogError.value = refusal;
|
|
548
958
|
return;
|
|
549
959
|
}
|
|
550
960
|
const id = form.id.trim();
|
|
961
|
+
const adding = editing.value === null;
|
|
551
962
|
saving.value = true;
|
|
552
963
|
dialogError.value = null;
|
|
553
964
|
try {
|
|
554
|
-
|
|
555
|
-
const update: EnvSettingsUpdate = {
|
|
556
|
-
set: { [form.envName.trim()]: form.secret },
|
|
557
|
-
unset: [],
|
|
558
|
-
};
|
|
559
|
-
await client().updateEnvironment(update);
|
|
560
|
-
form.secret = "";
|
|
561
|
-
}
|
|
965
|
+
await writeDialogSecret();
|
|
562
966
|
await client().upsert(id, buildBody());
|
|
563
|
-
dialogVisible.value = false;
|
|
564
967
|
editing.value = null;
|
|
565
|
-
|
|
968
|
+
if (adding) {
|
|
969
|
+
await finishAdd([id]);
|
|
970
|
+
} else {
|
|
971
|
+
dialogVisible.value = false;
|
|
972
|
+
await afterWrite();
|
|
973
|
+
}
|
|
566
974
|
} catch (err) {
|
|
567
975
|
dialogError.value = requestErrorMessage(err, "Could not save the provider.");
|
|
568
976
|
} finally {
|
|
@@ -570,6 +978,20 @@ export function useProvidersPanel(
|
|
|
570
978
|
}
|
|
571
979
|
}
|
|
572
980
|
|
|
981
|
+
function finishDialog() {
|
|
982
|
+
dialogVisible.value = false;
|
|
983
|
+
createdIds.value = [];
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/** "Renew": the edit dialog's sign-in for an own entry, else the row's key or reference editor. */
|
|
987
|
+
function openSignIn(row: ProvidersPanelRow) {
|
|
988
|
+
if (!row.canRenew) return;
|
|
989
|
+
if (isEditable(row)) openEdit(row);
|
|
990
|
+
else if (canEnterKey.value && (!row.credentialRef || envNameOfRef(row.credentialRef))) {
|
|
991
|
+
openKeyEditor(row);
|
|
992
|
+
} else openRefEditor(row);
|
|
993
|
+
}
|
|
994
|
+
|
|
573
995
|
// ── Delete ─────────────────────────────────────────────────────────────────
|
|
574
996
|
|
|
575
997
|
const deleteTarget = ref<ProvidersPanelEntry | null>(null);
|
|
@@ -734,6 +1156,100 @@ export function useProvidersPanel(
|
|
|
734
1156
|
);
|
|
735
1157
|
}
|
|
736
1158
|
|
|
1159
|
+
async function setEuProcessing(row: ProvidersPanelRow, on: boolean) {
|
|
1160
|
+
if (!row.canSetEuProcessing || row.euProcessing === on) return;
|
|
1161
|
+
await runRowWrite(
|
|
1162
|
+
row.id,
|
|
1163
|
+
() => client().upsert(row.id, { ...ownBody(row), eu_processing: on }),
|
|
1164
|
+
`Could not change EU processing of ${row.id}.`,
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// ── Models allowed here ────────────────────────────────────────────────────
|
|
1169
|
+
|
|
1170
|
+
const modelsEditing = ref<string | null>(null);
|
|
1171
|
+
const modelsForm = reactive<ProvidersPanelModelsForm>({ mode: "all", selected: [], query: "" });
|
|
1172
|
+
const modelsKnown = ref<string[]>([]);
|
|
1173
|
+
const modelsLoading = ref(false);
|
|
1174
|
+
const modelsListMessage = ref<string | null>(null);
|
|
1175
|
+
const modelsError = ref<string | null>(null);
|
|
1176
|
+
|
|
1177
|
+
const modelChoices = computed(() =>
|
|
1178
|
+
providerModelChoices(modelsKnown.value, modelsForm.selected, modelsForm.query),
|
|
1179
|
+
);
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Open the row's model filter: its `allowed_models` ticked, the checklist
|
|
1183
|
+
* from its declared `model_map` ids, else from the client's model listing
|
|
1184
|
+
* (`client.models`, the entry's discovery).
|
|
1185
|
+
*/
|
|
1186
|
+
async function openModelsEditor(row: ProvidersPanelRow) {
|
|
1187
|
+
if (!row.canFilterModels) return;
|
|
1188
|
+
modelsEditing.value = row.id;
|
|
1189
|
+
modelsForm.mode = row.allowedModels === undefined ? "all" : "only";
|
|
1190
|
+
modelsForm.selected = [...(row.allowedModels ?? [])];
|
|
1191
|
+
modelsForm.query = "";
|
|
1192
|
+
modelsError.value = null;
|
|
1193
|
+
modelsListMessage.value = null;
|
|
1194
|
+
if (row.modelIds && row.modelIds.length > 0) {
|
|
1195
|
+
modelsKnown.value = [...row.modelIds];
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
modelsKnown.value = [];
|
|
1199
|
+
modelsLoading.value = true;
|
|
1200
|
+
try {
|
|
1201
|
+
const listed = await client().models(row.id);
|
|
1202
|
+
if (modelsEditing.value !== row.id) return;
|
|
1203
|
+
modelsKnown.value = listed.models.map((model) => model.id);
|
|
1204
|
+
if (listed.models.length === 0) {
|
|
1205
|
+
modelsListMessage.value = listed.message ?? "This provider lists no models.";
|
|
1206
|
+
}
|
|
1207
|
+
} catch (err) {
|
|
1208
|
+
if (modelsEditing.value === row.id) {
|
|
1209
|
+
modelsListMessage.value = requestErrorMessage(
|
|
1210
|
+
err,
|
|
1211
|
+
`Could not list the models of ${row.id}.`,
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
} finally {
|
|
1215
|
+
modelsLoading.value = false;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function setModelsMode(mode: ProvidersPanelModelsMode) {
|
|
1220
|
+
modelsForm.mode = mode;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
function toggleModel(id: string, on: boolean) {
|
|
1224
|
+
const has = modelsForm.selected.includes(id);
|
|
1225
|
+
if (on && !has) modelsForm.selected = [...modelsForm.selected, id];
|
|
1226
|
+
if (!on && has) modelsForm.selected = modelsForm.selected.filter((model) => model !== id);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function cancelModelsEditor() {
|
|
1230
|
+
modelsEditing.value = null;
|
|
1231
|
+
modelsError.value = null;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
async function saveModelsEditor() {
|
|
1235
|
+
const row = rows.value.find((r) => r.id === modelsEditing.value);
|
|
1236
|
+
if (!row?.canFilterModels) return;
|
|
1237
|
+
const order = providerModelChoices(modelsKnown.value, modelsForm.selected);
|
|
1238
|
+
const allowed =
|
|
1239
|
+
modelsForm.mode === "all" ? null : order.filter((id) => modelsForm.selected.includes(id));
|
|
1240
|
+
rowBusy.value = row.id;
|
|
1241
|
+
modelsError.value = null;
|
|
1242
|
+
try {
|
|
1243
|
+
await client().upsert(row.id, { ...ownBody(row), allowed_models: allowed });
|
|
1244
|
+
modelsEditing.value = null;
|
|
1245
|
+
await afterWrite();
|
|
1246
|
+
} catch (err) {
|
|
1247
|
+
modelsError.value = requestErrorMessage(err, `Could not save the models of ${row.id}.`);
|
|
1248
|
+
} finally {
|
|
1249
|
+
rowBusy.value = null;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
737
1253
|
// ── Connection test ────────────────────────────────────────────────────────
|
|
738
1254
|
|
|
739
1255
|
/** Only an explicit client is known to serve the test; the URL routes are a host's concern. A read-only panel offers none. */
|
|
@@ -742,7 +1258,7 @@ export function useProvidersPanel(
|
|
|
742
1258
|
const testResults = reactive<Record<string, ProvidersPanelTestResult>>({});
|
|
743
1259
|
|
|
744
1260
|
/** Ask the client whether the entry's credential works; the answer shows in its row. */
|
|
745
|
-
async function testProvider(row: ProvidersPanelEntry) {
|
|
1261
|
+
async function testProvider(row: Pick<ProvidersPanelEntry, "id">) {
|
|
746
1262
|
if (!props.client || props.readOnly || testBusy.value !== null) return;
|
|
747
1263
|
testBusy.value = row.id;
|
|
748
1264
|
delete testResults[row.id];
|
|
@@ -925,6 +1441,25 @@ export function useProvidersPanel(
|
|
|
925
1441
|
openEdit,
|
|
926
1442
|
selectPreset,
|
|
927
1443
|
saveProvider,
|
|
1444
|
+
// add flow
|
|
1445
|
+
dialogStep,
|
|
1446
|
+
libraryPresets,
|
|
1447
|
+
chooseLibrary,
|
|
1448
|
+
backToLibrary,
|
|
1449
|
+
isAzureAccount,
|
|
1450
|
+
signInOptions,
|
|
1451
|
+
signInOption,
|
|
1452
|
+
selectSignIn,
|
|
1453
|
+
showSignIn,
|
|
1454
|
+
showReferenceField,
|
|
1455
|
+
paramFields,
|
|
1456
|
+
azureForm,
|
|
1457
|
+
addDeploymentRow,
|
|
1458
|
+
removeDeploymentRow,
|
|
1459
|
+
createdIds,
|
|
1460
|
+
finishDialog,
|
|
1461
|
+
defaultEnvName,
|
|
1462
|
+
openSignIn,
|
|
928
1463
|
// delete
|
|
929
1464
|
deleteTarget,
|
|
930
1465
|
deleteOpen,
|
|
@@ -942,6 +1477,7 @@ export function useProvidersPanel(
|
|
|
942
1477
|
setCredentialEnv,
|
|
943
1478
|
setEnabled,
|
|
944
1479
|
resetPatch,
|
|
1480
|
+
setEuProcessing,
|
|
945
1481
|
rowBusy,
|
|
946
1482
|
rowError,
|
|
947
1483
|
canTest,
|
|
@@ -963,6 +1499,19 @@ export function useProvidersPanel(
|
|
|
963
1499
|
openRefEditor,
|
|
964
1500
|
cancelRefEditor,
|
|
965
1501
|
saveRefEditor,
|
|
1502
|
+
// models allowed here
|
|
1503
|
+
modelsEditing,
|
|
1504
|
+
modelsForm,
|
|
1505
|
+
modelsKnown,
|
|
1506
|
+
modelsLoading,
|
|
1507
|
+
modelsListMessage,
|
|
1508
|
+
modelChoices,
|
|
1509
|
+
modelsError,
|
|
1510
|
+
openModelsEditor,
|
|
1511
|
+
setModelsMode,
|
|
1512
|
+
toggleModel,
|
|
1513
|
+
cancelModelsEditor,
|
|
1514
|
+
saveModelsEditor,
|
|
966
1515
|
// environment
|
|
967
1516
|
envDocument,
|
|
968
1517
|
envEditable,
|