@skaile/forge-settings 1.3.3 → 2.0.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 +40 -0
- package/README.md +52 -9
- package/dist/module.json +1 -1
- package/dist/runtime/ai-sdk/index.d.ts +33 -26
- package/dist/runtime/ai-sdk/index.js +34 -16
- 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/editor/index.d.ts +55 -16
- package/dist/runtime/editor/index.js +82 -40
- package/dist/runtime/model/selector.d.ts +16 -0
- package/dist/runtime/model/selector.js +12 -0
- package/dist/runtime/server/credential-store.d.ts +16 -4
- package/dist/runtime/server/credential-store.js +3 -4
- package/dist/runtime/server/env-layers.d.ts +1 -1
- package/dist/runtime/server/env-layers.js +3 -3
- package/dist/runtime/server/index.d.ts +9 -8
- package/dist/runtime/server/index.js +3 -3
- package/dist/runtime/server/provider-discovery.d.ts +28 -8
- package/dist/runtime/server/provider-discovery.js +57 -33
- package/dist/runtime/server/provider-layers.d.ts +1 -1
- package/dist/runtime/server/provider-layers.js +3 -5
- 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 +26 -10
- package/dist/runtime/server/provider-write.js +75 -7
- package/dist/runtime/server/voice-provider.d.ts +61 -0
- package/dist/runtime/server/voice-provider.js +31 -0
- package/dist/runtime/shared/data-zone.d.ts +68 -0
- package/dist/runtime/shared/data-zone.js +79 -0
- package/dist/runtime/shared/harness-providers.d.ts +42 -3
- 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 +19 -182
- package/dist/runtime/shared/provider-registry.js +25 -65
- 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/dist/runtime/{server → workspaces}/harness-settings.d.ts +51 -13
- package/dist/runtime/{server → workspaces}/harness-settings.js +93 -9
- package/dist/runtime/workspaces/index.d.ts +16 -0
- package/dist/runtime/workspaces/index.js +5 -0
- package/dist/runtime/workspaces/language-model.d.ts +45 -0
- package/dist/runtime/workspaces/language-model.js +26 -0
- package/dist/runtime/workspaces/model-endpoint.d.ts +38 -0
- package/dist/runtime/workspaces/model-endpoint.js +54 -0
- package/dist/runtime/workspaces/provider-settings.d.ts +35 -0
- package/dist/runtime/{server → workspaces}/provider-settings.js +0 -31
- package/dist/runtime/{server → workspaces}/settings-defaults.d.ts +1 -1
- package/dist/runtime/{shared → workspaces}/settings-types.d.ts +11 -4
- package/package.json +11 -2
- package/src/runtime/ai-sdk/index.ts +88 -47
- package/src/runtime/components/ForgeProvidersPanel.vue +477 -78
- package/src/runtime/editor/index.ts +158 -63
- package/src/runtime/model/selector.ts +31 -0
- package/src/runtime/server/credential-store.ts +20 -10
- package/src/runtime/server/env-layers.ts +4 -4
- package/src/runtime/server/index.ts +9 -8
- package/src/runtime/server/provider-discovery.ts +114 -48
- package/src/runtime/server/provider-layers.ts +6 -6
- package/src/runtime/server/provider-service.ts +53 -1
- package/src/runtime/server/provider-write.ts +132 -18
- package/src/runtime/server/voice-provider.ts +110 -0
- package/src/runtime/shared/data-zone.ts +179 -0
- package/src/runtime/shared/harness-providers.ts +78 -7
- package/src/runtime/shared/harnesses.ts +25 -0
- package/src/runtime/shared/provider-accounts.ts +526 -0
- package/src/runtime/shared/provider-registry.ts +82 -256
- package/src/runtime/vue/index.ts +2 -0
- package/src/runtime/vue/providers-panel-model.ts +123 -5
- package/src/runtime/vue/useProvidersPanel.ts +589 -37
- package/src/runtime/{server → workspaces}/harness-settings.ts +166 -22
- package/src/runtime/workspaces/index.ts +17 -0
- package/src/runtime/workspaces/language-model.ts +82 -0
- package/src/runtime/workspaces/model-endpoint.ts +100 -0
- package/src/runtime/workspaces/provider-settings.ts +96 -0
- package/src/runtime/{server → workspaces}/settings-defaults.ts +1 -1
- package/src/runtime/{shared → workspaces}/settings-types.ts +11 -4
- package/dist/runtime/server/provider-settings.d.ts +0 -78
- package/src/runtime/server/provider-settings.ts +0 -192
- /package/dist/runtime/{server → workspaces}/settings-defaults.js +0 -0
- /package/dist/runtime/{shared → workspaces}/settings-types.js +0 -0
|
@@ -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,9 +32,30 @@ 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,
|
|
58
|
+
type AIProviderDataZone,
|
|
37
59
|
type AIProviderKind,
|
|
38
60
|
type AIProviderSurface,
|
|
39
61
|
validateCredentialRef,
|
|
@@ -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,23 @@ 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(
|
|
470
|
+
zone: AIProviderDataZone,
|
|
471
|
+
euProcessing: boolean | undefined,
|
|
472
|
+
): string | null {
|
|
473
|
+
const rule = props.dataZoneRule;
|
|
474
|
+
if (!rule || rule === "any") return null;
|
|
475
|
+
return dataZoneRefusal(rule, zone, euProcessing) ?? null;
|
|
476
|
+
}
|
|
477
|
+
|
|
363
478
|
function buildRow(entry: ProvidersPanelEntry): ProvidersPanelRow {
|
|
364
479
|
const state = stateOf(entry);
|
|
365
480
|
const writable = !props.readOnly && !entry.locked;
|
|
366
481
|
const needsCredential = entry.credentialKind !== "none";
|
|
367
482
|
const envName = envNameOfRef(entry.credentialRef);
|
|
483
|
+
const zone = entry.dataZone ?? entryDataZone({ preset: entry.preset });
|
|
484
|
+
const isModelEntry = entry.kind === "llm" || entry.kind === "embed";
|
|
368
485
|
return {
|
|
369
486
|
...entry,
|
|
370
487
|
state,
|
|
@@ -385,6 +502,15 @@ export function useProvidersPanel(
|
|
|
385
502
|
origin: entry.preset ?? entry.surface ?? entry.kind,
|
|
386
503
|
status: statusOf(entry, needsCredential),
|
|
387
504
|
overrideNotices: overrideNoticesOf(entry),
|
|
505
|
+
accountLine: providerAccountLine(entry, presets.value),
|
|
506
|
+
expiry: needsCredential ? credentialExpiry(entry.credentialExpiresAt) : null,
|
|
507
|
+
canRenew: needsCredential && writable,
|
|
508
|
+
zone,
|
|
509
|
+
zoneLabel: DATA_ZONE_LABELS[zone],
|
|
510
|
+
canSetEuProcessing: zone === "eu_option" && writable && state === "own",
|
|
511
|
+
blockedNote: blockedNoteOf(zone, entry.euProcessing),
|
|
512
|
+
modelsChip: isModelEntry ? providerModelsChip(entry.allowedModels) : null,
|
|
513
|
+
canFilterModels: isModelEntry && writable && state === "own",
|
|
388
514
|
};
|
|
389
515
|
}
|
|
390
516
|
|
|
@@ -395,15 +521,36 @@ export function useProvidersPanel(
|
|
|
395
521
|
const dialogVisible = ref(false);
|
|
396
522
|
/** The row being edited; `null` while adding. */
|
|
397
523
|
const editing = ref<ProvidersPanelEntry | null>(null);
|
|
398
|
-
const form = reactive<ProvidersPanelForm>({ ...EMPTY_FORM });
|
|
524
|
+
const form = reactive<ProvidersPanelForm>({ ...EMPTY_FORM, params: {} });
|
|
399
525
|
const dialogError = ref<string | null>(null);
|
|
400
526
|
const saving = ref(false);
|
|
527
|
+
const dialogStep = ref<ProvidersPanelDialogStep>("account");
|
|
528
|
+
const createdIds = ref<string[]>([]);
|
|
529
|
+
const azureForm = reactive<ProvidersPanelAzureForm>(emptyAzureForm());
|
|
530
|
+
|
|
531
|
+
/** A fresh Azure card: Azure OpenAI chosen, one blank deployment row, the default `api_version`. */
|
|
532
|
+
function emptyAzureForm(): ProvidersPanelAzureForm {
|
|
533
|
+
return {
|
|
534
|
+
resource: "",
|
|
535
|
+
useOpenAI: true,
|
|
536
|
+
useFoundry: false,
|
|
537
|
+
deployments: [{ deployment: "", model: "" }],
|
|
538
|
+
apiVersion: AZURE_OPENAI_DEFAULT_API_VERSION,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
401
541
|
|
|
402
542
|
const presetOptions = computed(() => [
|
|
403
543
|
...presets.value.map((preset) => ({ label: preset.label, value: preset.id })),
|
|
404
544
|
{ label: "Custom", value: CUSTOM_PRESET },
|
|
405
545
|
]);
|
|
406
546
|
|
|
547
|
+
/** Azure OpenAI and Claude on Foundry are added through the Microsoft Azure card. */
|
|
548
|
+
const libraryPresets = computed(() =>
|
|
549
|
+
presets.value.filter(
|
|
550
|
+
(preset) => preset.id !== AZURE_OPENAI_PRESET && preset.id !== FOUNDRY_PRESET,
|
|
551
|
+
),
|
|
552
|
+
);
|
|
553
|
+
|
|
407
554
|
const surfaceOptions = AI_PROVIDER_SURFACES.map((surface) => ({
|
|
408
555
|
label: surface,
|
|
409
556
|
value: surface,
|
|
@@ -413,6 +560,8 @@ export function useProvidersPanel(
|
|
|
413
560
|
|
|
414
561
|
const isCustom = computed(() => form.preset === CUSTOM_PRESET);
|
|
415
562
|
|
|
563
|
+
const isAzureAccount = computed(() => form.preset === AZURE_ACCOUNT);
|
|
564
|
+
|
|
416
565
|
/** Kind of a custom entry: the edited one's, else `llm`. */
|
|
417
566
|
const customKind = computed<AIProviderKind>(() => editing.value?.kind ?? "llm");
|
|
418
567
|
|
|
@@ -428,8 +577,83 @@ export function useProvidersPanel(
|
|
|
428
577
|
/** Whether "Set value now" is offered: the scope has an `.env` route. */
|
|
429
578
|
const canSetValue = computed(() => environmentOn.value);
|
|
430
579
|
|
|
431
|
-
/** The
|
|
432
|
-
const
|
|
580
|
+
/** The preset whose sign-in kinds the Azure card offers: Azure OpenAI's, Foundry's when only that is used. */
|
|
581
|
+
const azureSignInPreset = computed<Pick<ProvidersPanelPreset, "credential" | "transport">>(() => {
|
|
582
|
+
const openai = presets.value.find((p) => p.id === AZURE_OPENAI_PRESET);
|
|
583
|
+
const foundry = presets.value.find((p) => p.id === FOUNDRY_PRESET);
|
|
584
|
+
const chosen = !azureForm.useOpenAI && azureForm.useFoundry ? foundry : openai;
|
|
585
|
+
return (
|
|
586
|
+
chosen ??
|
|
587
|
+
openai ??
|
|
588
|
+
foundry ?? {
|
|
589
|
+
credential: { kind: "api_key_header", env: "AZURE_OPENAI_API_KEY" },
|
|
590
|
+
transport: "azure-openai",
|
|
591
|
+
}
|
|
592
|
+
);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
/** The sign-in options the account step offers. */
|
|
596
|
+
const signInOptions = computed<ProviderSignInOption[]>(() => {
|
|
597
|
+
const expert = props.expert === true;
|
|
598
|
+
if (isAzureAccount.value) return presetSignInOptions(azureSignInPreset.value, { expert });
|
|
599
|
+
if (!form.preset) return [];
|
|
600
|
+
return presetSignInOptions(isCustom.value ? null : (selectedPreset.value ?? null), { expert });
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
const signInOption = computed(() => signInOptions.value.find((o) => o.id === form.signIn));
|
|
604
|
+
|
|
605
|
+
/** The sign-in option the edited entry's stored credential corresponds to. */
|
|
606
|
+
const storedSignIn = computed(() => {
|
|
607
|
+
const row = editing.value;
|
|
608
|
+
if (!row) return undefined;
|
|
609
|
+
const kind = row.credentialKind ?? selectedPreset.value?.credential.kind;
|
|
610
|
+
return signInOptionOf({ kind, ref: row.credentialRef }, signInOptions.value);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Editing an entry whose stored credential none of the options expresses
|
|
615
|
+
* (an `oauth` credential, or a `keyring:` reference outside expert mode)
|
|
616
|
+
* keeps it as is; the row's picker still changes it.
|
|
617
|
+
*/
|
|
618
|
+
const showSignIn = computed(() => {
|
|
619
|
+
const row = editing.value;
|
|
620
|
+
if (!row) return signInOptions.value.length > 0;
|
|
621
|
+
const option = storedSignIn.value;
|
|
622
|
+
const kind = row.credentialKind ?? selectedPreset.value?.credential.kind;
|
|
623
|
+
if (!option || (kind && option.credentialKind !== kind)) return false;
|
|
624
|
+
const ref = row.credentialRef;
|
|
625
|
+
if (!ref) return option.fields !== "reference";
|
|
626
|
+
return option.fields === (envNameOfRef(ref) ? "key" : "reference");
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
/** The dialog collects a key (env name, optional value) for the chosen sign-in. */
|
|
630
|
+
const showCredentialFields = computed(
|
|
631
|
+
() => showSignIn.value && signInOption.value?.fields === "key",
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
const showReferenceField = computed(
|
|
635
|
+
() => showSignIn.value && signInOption.value?.fields === "reference",
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
/** The conventional variable a blank key name falls back to. */
|
|
639
|
+
const defaultEnvName = computed(() =>
|
|
640
|
+
isAzureAccount.value
|
|
641
|
+
? azureSignInPreset.value.credential.env
|
|
642
|
+
: selectedPreset.value?.credential.env,
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
const paramFields = computed<ProvidersPanelParamField[]>(() => {
|
|
646
|
+
if (isCustom.value || isAzureAccount.value) return [];
|
|
647
|
+
const preset = selectedPreset.value;
|
|
648
|
+
const kept = editing.value && editing.value.preset === form.preset ? editing.value : null;
|
|
649
|
+
const required = new Set(preset?.required_params ?? []);
|
|
650
|
+
return presetParamKeys(preset, kept?.transportParams).map((key) => ({
|
|
651
|
+
key,
|
|
652
|
+
label: PROVIDER_PARAM_LABELS[key] ?? key,
|
|
653
|
+
required: required.has(key),
|
|
654
|
+
editable: props.expert === true || !EXPERT_PARAMS.includes(key),
|
|
655
|
+
}));
|
|
656
|
+
});
|
|
433
657
|
|
|
434
658
|
const idError = computed(() => {
|
|
435
659
|
const id = form.id.trim();
|
|
@@ -438,28 +662,74 @@ export function useProvidersPanel(
|
|
|
438
662
|
: null;
|
|
439
663
|
});
|
|
440
664
|
|
|
441
|
-
const dialogTitle = computed(() =>
|
|
442
|
-
editing.value
|
|
443
|
-
|
|
665
|
+
const dialogTitle = computed(() => {
|
|
666
|
+
if (editing.value) return `Edit provider — ${editing.value.id}`;
|
|
667
|
+
if (dialogStep.value === "done") return "Provider added";
|
|
668
|
+
if (dialogStep.value === "account" && isAzureAccount.value) return "Add Microsoft Azure";
|
|
669
|
+
return "Add provider";
|
|
670
|
+
});
|
|
444
671
|
|
|
445
672
|
function resetDialog(values: Partial<ProvidersPanelForm>, row: ProvidersPanelEntry | null) {
|
|
446
673
|
Object.assign(form, EMPTY_FORM, values);
|
|
674
|
+
form.params = { ...(values.params ?? {}) };
|
|
675
|
+
Object.assign(azureForm, emptyAzureForm());
|
|
447
676
|
editing.value = row;
|
|
448
677
|
dialogError.value = null;
|
|
678
|
+
createdIds.value = [];
|
|
679
|
+
dialogStep.value = "account";
|
|
449
680
|
dialogVisible.value = true;
|
|
450
681
|
}
|
|
451
682
|
|
|
452
|
-
/**
|
|
683
|
+
/** Pick the first sign-in option when the current one is not offered. */
|
|
684
|
+
function settleSignIn() {
|
|
685
|
+
if (!signInOptions.value.some((o) => o.id === form.signIn)) {
|
|
686
|
+
form.signIn = signInOptions.value[0]?.id ?? "";
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/** Open the add dialog: on the library, or straight on `presetId`'s account step. */
|
|
453
691
|
function openAdd(presetId?: string) {
|
|
454
692
|
if (props.readOnly) return;
|
|
455
693
|
resetDialog({}, null);
|
|
456
|
-
if (presetId)
|
|
694
|
+
if (presetId) chooseLibrary(presetId);
|
|
695
|
+
else dialogStep.value = "library";
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function chooseLibrary(value: string) {
|
|
699
|
+
if (props.readOnly || editing.value) return;
|
|
700
|
+
dialogError.value = null;
|
|
701
|
+
if (value === AZURE_ACCOUNT) {
|
|
702
|
+
form.preset = AZURE_ACCOUNT;
|
|
703
|
+
form.id = "";
|
|
704
|
+
form.envName = "";
|
|
705
|
+
form.params = {};
|
|
706
|
+
} else {
|
|
707
|
+
if (form.preset === AZURE_ACCOUNT) form.preset = "";
|
|
708
|
+
selectPreset(value);
|
|
709
|
+
}
|
|
710
|
+
settleSignIn();
|
|
711
|
+
dialogStep.value = "account";
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function backToLibrary() {
|
|
715
|
+
if (editing.value) return;
|
|
716
|
+
dialogError.value = null;
|
|
717
|
+
dialogStep.value = "library";
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function selectSignIn(id: ProviderSignInId) {
|
|
721
|
+
if (signInOptions.value.some((o) => o.id === id)) form.signIn = id;
|
|
457
722
|
}
|
|
458
723
|
|
|
459
724
|
/** Open the dialog on an entry this scope's document defines; its id stays fixed. */
|
|
460
725
|
function openEdit(row: ProvidersPanelEntry) {
|
|
461
726
|
if (!isEditable(row)) return;
|
|
462
727
|
const preset = presets.value.find((p) => p.id === row.preset);
|
|
728
|
+
const ref = row.credentialRef;
|
|
729
|
+
const params: Record<string, string> = {};
|
|
730
|
+
for (const key of presetParamKeys(preset, row.transportParams)) {
|
|
731
|
+
params[key] = row.transportParams?.[key] ?? defaultParamValue(preset?.id, key);
|
|
732
|
+
}
|
|
463
733
|
resetDialog(
|
|
464
734
|
{
|
|
465
735
|
preset: preset ? preset.id : CUSTOM_PRESET,
|
|
@@ -467,15 +737,19 @@ export function useProvidersPanel(
|
|
|
467
737
|
surface: row.surface ?? "",
|
|
468
738
|
// The preset's own endpoint shows as the placeholder, not as a pinned value.
|
|
469
739
|
baseUrl: row.baseUrl && row.baseUrl !== preset?.base_url ? row.baseUrl : "",
|
|
740
|
+
envName: envNameOfRef(ref) ?? "",
|
|
741
|
+
credentialRef: ref && !envNameOfRef(ref) ? ref : "",
|
|
742
|
+
params,
|
|
470
743
|
},
|
|
471
744
|
row,
|
|
472
745
|
);
|
|
746
|
+
form.signIn = storedSignIn.value?.id ?? signInOptions.value[0]?.id ?? "";
|
|
473
747
|
}
|
|
474
748
|
|
|
475
749
|
/**
|
|
476
750
|
* 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.
|
|
751
|
+
* (the id while adding, the env var, a custom surface, the parameters)
|
|
752
|
+
* follow the new one; anything the user typed is kept.
|
|
479
753
|
*/
|
|
480
754
|
function selectPreset(value: string) {
|
|
481
755
|
const previous = selectedPreset.value;
|
|
@@ -485,6 +759,50 @@ export function useProvidersPanel(
|
|
|
485
759
|
if (form.envName === "" || form.envName === previous?.credential.env)
|
|
486
760
|
form.envName = next?.credential.env ?? "";
|
|
487
761
|
if (value === CUSTOM_PRESET && form.surface === "") form.surface = previous?.surface ?? "";
|
|
762
|
+
const params: Record<string, string> = {};
|
|
763
|
+
for (const key of presetParamKeys(next)) {
|
|
764
|
+
const typed = form.params[key]?.trim();
|
|
765
|
+
params[key] =
|
|
766
|
+
typed && typed !== defaultParamValue(previous?.id, key)
|
|
767
|
+
? typed
|
|
768
|
+
: defaultParamValue(next?.id, key);
|
|
769
|
+
}
|
|
770
|
+
form.params = params;
|
|
771
|
+
settleSignIn();
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function addDeploymentRow() {
|
|
775
|
+
azureForm.deployments.push({ deployment: "", model: "" });
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function removeDeploymentRow(index: number) {
|
|
779
|
+
azureForm.deployments.splice(index, 1);
|
|
780
|
+
if (azureForm.deployments.length === 0) addDeploymentRow();
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/** The credential the chosen sign-in writes. */
|
|
784
|
+
function dialogCredential(): ProviderSignInCredential {
|
|
785
|
+
const option = signInOption.value;
|
|
786
|
+
if (!option) return { ok: false, error: "Choose how you sign in." };
|
|
787
|
+
return signInCredential(
|
|
788
|
+
option,
|
|
789
|
+
{ envName: form.envName, ref: form.credentialRef },
|
|
790
|
+
{ defaultEnv: defaultEnvName.value },
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/** Why the key fields cannot be saved, or `null`. */
|
|
795
|
+
function keyFieldsRefusal(): string | null {
|
|
796
|
+
if (!showCredentialFields.value) return null;
|
|
797
|
+
const envName = form.envName.trim();
|
|
798
|
+
if (envName) {
|
|
799
|
+
const refusal = validateEnvVarName(envName);
|
|
800
|
+
if (refusal) return refusal;
|
|
801
|
+
}
|
|
802
|
+
if (form.secret && !envName && !defaultEnvName.value) {
|
|
803
|
+
return "Name the environment variable the value is stored in.";
|
|
804
|
+
}
|
|
805
|
+
return null;
|
|
488
806
|
}
|
|
489
807
|
|
|
490
808
|
/** Why the form cannot be saved, or `null`. */
|
|
@@ -494,13 +812,17 @@ export function useProvidersPanel(
|
|
|
494
812
|
if (idError.value) return idError.value;
|
|
495
813
|
if (showSurface.value && !form.surface) return "Choose the surface a custom provider speaks.";
|
|
496
814
|
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;
|
|
815
|
+
if (!isCustom.value && selectedPreset.value) {
|
|
816
|
+
const missing = missingRequiredParams(selectedPreset.value, form.params)[0];
|
|
817
|
+
if (missing) {
|
|
818
|
+
return `Enter the ${(PROVIDER_PARAM_LABELS[missing] ?? missing).toLowerCase()} (${missing}).`;
|
|
502
819
|
}
|
|
503
|
-
|
|
820
|
+
}
|
|
821
|
+
if (showSignIn.value) {
|
|
822
|
+
const keyRefusal = keyFieldsRefusal();
|
|
823
|
+
if (keyRefusal) return keyRefusal;
|
|
824
|
+
const credential = dialogCredential();
|
|
825
|
+
if (!credential.ok) return credential.error;
|
|
504
826
|
}
|
|
505
827
|
return null;
|
|
506
828
|
}
|
|
@@ -508,7 +830,8 @@ export function useProvidersPanel(
|
|
|
508
830
|
/**
|
|
509
831
|
* The `PUT ${providersWriteUrl}/:id` body. The write replaces every editable
|
|
510
832
|
* field, so an edit starts from the defining document's values the dialog
|
|
511
|
-
* does not show (`layerFields`)
|
|
833
|
+
* does not show (`layerFields`). An edit whose credential the sign-in step
|
|
834
|
+
* cannot express keeps the stored one.
|
|
512
835
|
*/
|
|
513
836
|
function buildBody(): Record<string, unknown> {
|
|
514
837
|
const row = editing.value;
|
|
@@ -526,43 +849,131 @@ export function useProvidersPanel(
|
|
|
526
849
|
}
|
|
527
850
|
const baseUrl = form.baseUrl.trim();
|
|
528
851
|
if (baseUrl) body.base_url = baseUrl;
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
852
|
+
|
|
853
|
+
const fields = paramFields.value;
|
|
854
|
+
if (fields.length > 0) {
|
|
855
|
+
const params: Record<string, unknown> =
|
|
856
|
+
kept && isPlainObject(body.params) ? { ...body.params } : {};
|
|
857
|
+
for (const { key } of fields) {
|
|
858
|
+
const value = form.params[key]?.trim();
|
|
859
|
+
if (value) params[key] = value;
|
|
860
|
+
else delete params[key];
|
|
861
|
+
}
|
|
862
|
+
body.params = params;
|
|
863
|
+
} else if (!kept) {
|
|
864
|
+
// Another preset (or custom): the previous preset's parameters do not carry over.
|
|
865
|
+
delete body.params;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (showSignIn.value) {
|
|
869
|
+
const credential = dialogCredential();
|
|
870
|
+
if (credential.ok && credential.credential) body.credential = credential.credential;
|
|
871
|
+
} else if (row?.credentialRef) {
|
|
872
|
+
const credentialKind: AIProviderCredentialKind =
|
|
873
|
+
kept?.credentialKind ?? (isCustom.value ? "bearer" : (preset?.credential.kind ?? "bearer"));
|
|
874
|
+
body.credential = { kind: credentialKind, ref: row.credentialRef };
|
|
875
|
+
} else if (kept?.credentialKind) {
|
|
876
|
+
body.credential = { kind: kept.credentialKind };
|
|
877
|
+
}
|
|
534
878
|
return body;
|
|
535
879
|
}
|
|
536
880
|
|
|
881
|
+
/** Write a "Set value now" secret to the scope's `.env` first, under the key name (else the preset's). */
|
|
882
|
+
async function writeDialogSecret() {
|
|
883
|
+
const name = form.envName.trim() || defaultEnvName.value || "";
|
|
884
|
+
if (!showCredentialFields.value || !form.secret || !environmentOn.value || !name) return;
|
|
885
|
+
const update: EnvSettingsUpdate = { set: { [name]: form.secret }, unset: [] };
|
|
886
|
+
await client().updateEnvironment(update);
|
|
887
|
+
form.secret = "";
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* After an add: with an explicit client, test every created entry and show
|
|
892
|
+
* the results on the done step; otherwise close the dialog.
|
|
893
|
+
*/
|
|
894
|
+
async function finishAdd(ids: string[]) {
|
|
895
|
+
createdIds.value = ids;
|
|
896
|
+
await afterWrite();
|
|
897
|
+
if (!canTest.value) {
|
|
898
|
+
dialogVisible.value = false;
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
dialogStep.value = "done";
|
|
902
|
+
for (const id of ids) await testProvider({ id });
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/** Save the Azure card: the secret first, then each entry; ids written before a refusal are named. */
|
|
906
|
+
async function saveAzureAccount() {
|
|
907
|
+
const keyRefusal = keyFieldsRefusal();
|
|
908
|
+
const credential = dialogCredential();
|
|
909
|
+
const drafts = keyRefusal
|
|
910
|
+
? { ok: false as const, error: keyRefusal }
|
|
911
|
+
: !credential.ok
|
|
912
|
+
? credential
|
|
913
|
+
: azureAccountEntries({
|
|
914
|
+
resource: azureForm.resource,
|
|
915
|
+
credential: credential.credential,
|
|
916
|
+
openai: azureForm.useOpenAI
|
|
917
|
+
? { deployments: azureForm.deployments, apiVersion: azureForm.apiVersion }
|
|
918
|
+
: undefined,
|
|
919
|
+
foundry: azureForm.useFoundry ? { deployments: [] } : undefined,
|
|
920
|
+
takenIds: providers.value.map((entry) => entry.id),
|
|
921
|
+
});
|
|
922
|
+
if (!drafts.ok) {
|
|
923
|
+
dialogError.value = drafts.error;
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
saving.value = true;
|
|
927
|
+
dialogError.value = null;
|
|
928
|
+
const created: string[] = [];
|
|
929
|
+
try {
|
|
930
|
+
await writeDialogSecret();
|
|
931
|
+
for (const draft of drafts.entries) {
|
|
932
|
+
await client().upsert(draft.id, draft.body);
|
|
933
|
+
created.push(draft.id);
|
|
934
|
+
}
|
|
935
|
+
await finishAdd(created);
|
|
936
|
+
} catch (err) {
|
|
937
|
+
const message = requestErrorMessage(err, "Could not add the Azure account.");
|
|
938
|
+
dialogError.value =
|
|
939
|
+
created.length > 0 ? `${message} (created: ${created.join(", ")})` : message;
|
|
940
|
+
if (created.length > 0) await afterWrite();
|
|
941
|
+
} finally {
|
|
942
|
+
saving.value = false;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
537
946
|
/**
|
|
538
947
|
* Save the dialog. A "Set value now" secret is written first, through the
|
|
539
948
|
* scope's `.env` route as `NAME=value`; only then is the entry — carrying
|
|
540
949
|
* the `env:NAME` reference — written. Refusals from either request stay
|
|
541
|
-
* inline in the dialog.
|
|
950
|
+
* inline in the dialog. The Azure card writes up to two entries.
|
|
542
951
|
*/
|
|
543
952
|
async function saveProvider() {
|
|
544
953
|
if (props.readOnly) return;
|
|
954
|
+
if (isAzureAccount.value && !editing.value) {
|
|
955
|
+
await saveAzureAccount();
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
545
958
|
const refusal = formRefusal();
|
|
546
959
|
if (refusal) {
|
|
547
960
|
dialogError.value = refusal;
|
|
548
961
|
return;
|
|
549
962
|
}
|
|
550
963
|
const id = form.id.trim();
|
|
964
|
+
const adding = editing.value === null;
|
|
551
965
|
saving.value = true;
|
|
552
966
|
dialogError.value = null;
|
|
553
967
|
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
|
-
}
|
|
968
|
+
await writeDialogSecret();
|
|
562
969
|
await client().upsert(id, buildBody());
|
|
563
|
-
dialogVisible.value = false;
|
|
564
970
|
editing.value = null;
|
|
565
|
-
|
|
971
|
+
if (adding) {
|
|
972
|
+
await finishAdd([id]);
|
|
973
|
+
} else {
|
|
974
|
+
dialogVisible.value = false;
|
|
975
|
+
await afterWrite();
|
|
976
|
+
}
|
|
566
977
|
} catch (err) {
|
|
567
978
|
dialogError.value = requestErrorMessage(err, "Could not save the provider.");
|
|
568
979
|
} finally {
|
|
@@ -570,6 +981,20 @@ export function useProvidersPanel(
|
|
|
570
981
|
}
|
|
571
982
|
}
|
|
572
983
|
|
|
984
|
+
function finishDialog() {
|
|
985
|
+
dialogVisible.value = false;
|
|
986
|
+
createdIds.value = [];
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/** "Renew": the edit dialog's sign-in for an own entry, else the row's key or reference editor. */
|
|
990
|
+
function openSignIn(row: ProvidersPanelRow) {
|
|
991
|
+
if (!row.canRenew) return;
|
|
992
|
+
if (isEditable(row)) openEdit(row);
|
|
993
|
+
else if (canEnterKey.value && (!row.credentialRef || envNameOfRef(row.credentialRef))) {
|
|
994
|
+
openKeyEditor(row);
|
|
995
|
+
} else openRefEditor(row);
|
|
996
|
+
}
|
|
997
|
+
|
|
573
998
|
// ── Delete ─────────────────────────────────────────────────────────────────
|
|
574
999
|
|
|
575
1000
|
const deleteTarget = ref<ProvidersPanelEntry | null>(null);
|
|
@@ -734,6 +1159,100 @@ export function useProvidersPanel(
|
|
|
734
1159
|
);
|
|
735
1160
|
}
|
|
736
1161
|
|
|
1162
|
+
async function setEuProcessing(row: ProvidersPanelRow, on: boolean) {
|
|
1163
|
+
if (!row.canSetEuProcessing || row.euProcessing === on) return;
|
|
1164
|
+
await runRowWrite(
|
|
1165
|
+
row.id,
|
|
1166
|
+
() => client().upsert(row.id, { ...ownBody(row), eu_processing: on }),
|
|
1167
|
+
`Could not change EU processing of ${row.id}.`,
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// ── Models allowed here ────────────────────────────────────────────────────
|
|
1172
|
+
|
|
1173
|
+
const modelsEditing = ref<string | null>(null);
|
|
1174
|
+
const modelsForm = reactive<ProvidersPanelModelsForm>({ mode: "all", selected: [], query: "" });
|
|
1175
|
+
const modelsKnown = ref<string[]>([]);
|
|
1176
|
+
const modelsLoading = ref(false);
|
|
1177
|
+
const modelsListMessage = ref<string | null>(null);
|
|
1178
|
+
const modelsError = ref<string | null>(null);
|
|
1179
|
+
|
|
1180
|
+
const modelChoices = computed(() =>
|
|
1181
|
+
providerModelChoices(modelsKnown.value, modelsForm.selected, modelsForm.query),
|
|
1182
|
+
);
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Open the row's model filter: its `allowed_models` ticked, the checklist
|
|
1186
|
+
* from its declared `model_map` ids, else from the client's model listing
|
|
1187
|
+
* (`client.models`, the entry's discovery).
|
|
1188
|
+
*/
|
|
1189
|
+
async function openModelsEditor(row: ProvidersPanelRow) {
|
|
1190
|
+
if (!row.canFilterModels) return;
|
|
1191
|
+
modelsEditing.value = row.id;
|
|
1192
|
+
modelsForm.mode = row.allowedModels === undefined ? "all" : "only";
|
|
1193
|
+
modelsForm.selected = [...(row.allowedModels ?? [])];
|
|
1194
|
+
modelsForm.query = "";
|
|
1195
|
+
modelsError.value = null;
|
|
1196
|
+
modelsListMessage.value = null;
|
|
1197
|
+
if (row.modelIds && row.modelIds.length > 0) {
|
|
1198
|
+
modelsKnown.value = [...row.modelIds];
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
modelsKnown.value = [];
|
|
1202
|
+
modelsLoading.value = true;
|
|
1203
|
+
try {
|
|
1204
|
+
const listed = await client().models(row.id);
|
|
1205
|
+
if (modelsEditing.value !== row.id) return;
|
|
1206
|
+
modelsKnown.value = listed.models.map((model) => model.id);
|
|
1207
|
+
if (listed.models.length === 0) {
|
|
1208
|
+
modelsListMessage.value = listed.message ?? "This provider lists no models.";
|
|
1209
|
+
}
|
|
1210
|
+
} catch (err) {
|
|
1211
|
+
if (modelsEditing.value === row.id) {
|
|
1212
|
+
modelsListMessage.value = requestErrorMessage(
|
|
1213
|
+
err,
|
|
1214
|
+
`Could not list the models of ${row.id}.`,
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
} finally {
|
|
1218
|
+
modelsLoading.value = false;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
function setModelsMode(mode: ProvidersPanelModelsMode) {
|
|
1223
|
+
modelsForm.mode = mode;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
function toggleModel(id: string, on: boolean) {
|
|
1227
|
+
const has = modelsForm.selected.includes(id);
|
|
1228
|
+
if (on && !has) modelsForm.selected = [...modelsForm.selected, id];
|
|
1229
|
+
if (!on && has) modelsForm.selected = modelsForm.selected.filter((model) => model !== id);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function cancelModelsEditor() {
|
|
1233
|
+
modelsEditing.value = null;
|
|
1234
|
+
modelsError.value = null;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
async function saveModelsEditor() {
|
|
1238
|
+
const row = rows.value.find((r) => r.id === modelsEditing.value);
|
|
1239
|
+
if (!row?.canFilterModels) return;
|
|
1240
|
+
const order = providerModelChoices(modelsKnown.value, modelsForm.selected);
|
|
1241
|
+
const allowed =
|
|
1242
|
+
modelsForm.mode === "all" ? null : order.filter((id) => modelsForm.selected.includes(id));
|
|
1243
|
+
rowBusy.value = row.id;
|
|
1244
|
+
modelsError.value = null;
|
|
1245
|
+
try {
|
|
1246
|
+
await client().upsert(row.id, { ...ownBody(row), allowed_models: allowed });
|
|
1247
|
+
modelsEditing.value = null;
|
|
1248
|
+
await afterWrite();
|
|
1249
|
+
} catch (err) {
|
|
1250
|
+
modelsError.value = requestErrorMessage(err, `Could not save the models of ${row.id}.`);
|
|
1251
|
+
} finally {
|
|
1252
|
+
rowBusy.value = null;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
737
1256
|
// ── Connection test ────────────────────────────────────────────────────────
|
|
738
1257
|
|
|
739
1258
|
/** 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 +1261,7 @@ export function useProvidersPanel(
|
|
|
742
1261
|
const testResults = reactive<Record<string, ProvidersPanelTestResult>>({});
|
|
743
1262
|
|
|
744
1263
|
/** Ask the client whether the entry's credential works; the answer shows in its row. */
|
|
745
|
-
async function testProvider(row: ProvidersPanelEntry) {
|
|
1264
|
+
async function testProvider(row: Pick<ProvidersPanelEntry, "id">) {
|
|
746
1265
|
if (!props.client || props.readOnly || testBusy.value !== null) return;
|
|
747
1266
|
testBusy.value = row.id;
|
|
748
1267
|
delete testResults[row.id];
|
|
@@ -925,6 +1444,25 @@ export function useProvidersPanel(
|
|
|
925
1444
|
openEdit,
|
|
926
1445
|
selectPreset,
|
|
927
1446
|
saveProvider,
|
|
1447
|
+
// add flow
|
|
1448
|
+
dialogStep,
|
|
1449
|
+
libraryPresets,
|
|
1450
|
+
chooseLibrary,
|
|
1451
|
+
backToLibrary,
|
|
1452
|
+
isAzureAccount,
|
|
1453
|
+
signInOptions,
|
|
1454
|
+
signInOption,
|
|
1455
|
+
selectSignIn,
|
|
1456
|
+
showSignIn,
|
|
1457
|
+
showReferenceField,
|
|
1458
|
+
paramFields,
|
|
1459
|
+
azureForm,
|
|
1460
|
+
addDeploymentRow,
|
|
1461
|
+
removeDeploymentRow,
|
|
1462
|
+
createdIds,
|
|
1463
|
+
finishDialog,
|
|
1464
|
+
defaultEnvName,
|
|
1465
|
+
openSignIn,
|
|
928
1466
|
// delete
|
|
929
1467
|
deleteTarget,
|
|
930
1468
|
deleteOpen,
|
|
@@ -942,6 +1480,7 @@ export function useProvidersPanel(
|
|
|
942
1480
|
setCredentialEnv,
|
|
943
1481
|
setEnabled,
|
|
944
1482
|
resetPatch,
|
|
1483
|
+
setEuProcessing,
|
|
945
1484
|
rowBusy,
|
|
946
1485
|
rowError,
|
|
947
1486
|
canTest,
|
|
@@ -963,6 +1502,19 @@ export function useProvidersPanel(
|
|
|
963
1502
|
openRefEditor,
|
|
964
1503
|
cancelRefEditor,
|
|
965
1504
|
saveRefEditor,
|
|
1505
|
+
// models allowed here
|
|
1506
|
+
modelsEditing,
|
|
1507
|
+
modelsForm,
|
|
1508
|
+
modelsKnown,
|
|
1509
|
+
modelsLoading,
|
|
1510
|
+
modelsListMessage,
|
|
1511
|
+
modelChoices,
|
|
1512
|
+
modelsError,
|
|
1513
|
+
openModelsEditor,
|
|
1514
|
+
setModelsMode,
|
|
1515
|
+
toggleModel,
|
|
1516
|
+
cancelModelsEditor,
|
|
1517
|
+
saveModelsEditor,
|
|
966
1518
|
// environment
|
|
967
1519
|
envDocument,
|
|
968
1520
|
envEditable,
|