@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
|
@@ -71,10 +71,54 @@
|
|
|
71
71
|
variant="subtle"
|
|
72
72
|
color="neutral"
|
|
73
73
|
>{{ row.badge }}</UBadge>
|
|
74
|
+
<UBadge
|
|
75
|
+
:data-testid="`provider-zone-${row.id}`"
|
|
76
|
+
size="sm"
|
|
77
|
+
variant="outline"
|
|
78
|
+
:color="row.zone === 'transfer' ? 'warning' : 'neutral'"
|
|
79
|
+
:title="`Data zone: ${row.zoneLabel}`"
|
|
80
|
+
>{{ row.zoneLabel }}</UBadge>
|
|
81
|
+
<button
|
|
82
|
+
v-if="row.modelsChip && row.canFilterModels"
|
|
83
|
+
type="button"
|
|
84
|
+
class="rounded-md border border-surface-border px-1.5 py-0.5 text-[10px] text-foreground-muted hover:border-primary-500"
|
|
85
|
+
:data-testid="`provider-models-chip-${row.id}`"
|
|
86
|
+
@click="openModelsEditor(row)"
|
|
87
|
+
>{{ row.modelsChip }}</button>
|
|
88
|
+
<UBadge
|
|
89
|
+
v-else-if="row.modelsChip"
|
|
90
|
+
:data-testid="`provider-models-chip-${row.id}`"
|
|
91
|
+
size="sm"
|
|
92
|
+
variant="subtle"
|
|
93
|
+
color="neutral"
|
|
94
|
+
>{{ row.modelsChip }}</UBadge>
|
|
74
95
|
<Icon v-if="row.locked" name="lucide:lock" class="text-foreground-subtle shrink-0" />
|
|
75
96
|
</div>
|
|
76
97
|
<p class="font-mono text-foreground-subtle truncate" :title="row.baseUrl ?? undefined">{{ rowMeta(row) }}</p>
|
|
98
|
+
<p v-if="row.accountLine" :data-testid="`provider-account-${row.id}`" class="text-foreground-muted">{{ row.accountLine }}</p>
|
|
77
99
|
<p v-if="row.inheritedNote" class="text-foreground-subtle italic">{{ row.inheritedNote }}</p>
|
|
100
|
+
<p
|
|
101
|
+
v-if="row.blockedNote"
|
|
102
|
+
:data-testid="`provider-blocked-${row.id}`"
|
|
103
|
+
class="text-foreground-subtle italic"
|
|
104
|
+
>{{ row.blockedNote }}</p>
|
|
105
|
+
<p
|
|
106
|
+
v-if="row.expiry"
|
|
107
|
+
:data-testid="`provider-expiry-${row.id}`"
|
|
108
|
+
class="flex items-center gap-1"
|
|
109
|
+
:class="row.expiry.expired || row.expiry.days < 7 ? 'text-amber-600 dark:text-amber-400' : 'text-foreground-subtle'"
|
|
110
|
+
>
|
|
111
|
+
{{ row.expiry.text }}
|
|
112
|
+
<template v-if="row.canRenew">
|
|
113
|
+
·
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
class="underline hover:text-foreground"
|
|
117
|
+
:data-testid="`provider-renew-${row.id}`"
|
|
118
|
+
@click="openSignIn(row)"
|
|
119
|
+
>Renew</button>
|
|
120
|
+
</template>
|
|
121
|
+
</p>
|
|
78
122
|
</div>
|
|
79
123
|
<div class="flex items-center gap-1 shrink-0">
|
|
80
124
|
<UButton
|
|
@@ -145,6 +189,16 @@
|
|
|
145
189
|
{{ row.status }}
|
|
146
190
|
</p>
|
|
147
191
|
|
|
192
|
+
<USwitch
|
|
193
|
+
v-if="row.zone === 'eu_option'"
|
|
194
|
+
:model-value="row.euProcessing === true"
|
|
195
|
+
:disabled="!row.canSetEuProcessing || rowBusy === row.id"
|
|
196
|
+
label="EU processing is on for this account"
|
|
197
|
+
size="xs"
|
|
198
|
+
:data-testid="`provider-eu-processing-${row.id}`"
|
|
199
|
+
@update:model-value="setEuProcessing(row, Boolean($event))"
|
|
200
|
+
/>
|
|
201
|
+
|
|
148
202
|
<!-- Inline key editor -->
|
|
149
203
|
<div
|
|
150
204
|
v-if="keyEditing === row.id"
|
|
@@ -203,6 +257,67 @@
|
|
|
203
257
|
</div>
|
|
204
258
|
</div>
|
|
205
259
|
|
|
260
|
+
<!-- Models allowed here -->
|
|
261
|
+
<div
|
|
262
|
+
v-if="modelsEditing === row.id"
|
|
263
|
+
:data-testid="`provider-models-${row.id}`"
|
|
264
|
+
class="space-y-1.5 p-2 rounded-lg border border-surface-border bg-surface-ground/50"
|
|
265
|
+
>
|
|
266
|
+
<p class="font-medium text-foreground">Models allowed here</p>
|
|
267
|
+
<URadioGroup
|
|
268
|
+
:model-value="modelsForm.mode"
|
|
269
|
+
:items="MODELS_MODE_ITEMS"
|
|
270
|
+
orientation="horizontal"
|
|
271
|
+
size="xs"
|
|
272
|
+
:data-testid="`provider-models-mode-${row.id}`"
|
|
273
|
+
@update:model-value="setModelsMode($event as ProvidersPanelModelsMode)"
|
|
274
|
+
/>
|
|
275
|
+
<template v-if="modelsForm.mode === 'only'">
|
|
276
|
+
<UInput
|
|
277
|
+
v-model="modelsForm.query"
|
|
278
|
+
placeholder="Search models"
|
|
279
|
+
size="xs"
|
|
280
|
+
:data-testid="`provider-models-search-${row.id}`"
|
|
281
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-xs"
|
|
282
|
+
/>
|
|
283
|
+
<p v-if="modelsLoading" class="text-foreground-subtle italic">Listing models…</p>
|
|
284
|
+
<p
|
|
285
|
+
v-if="modelsListMessage"
|
|
286
|
+
:data-testid="`provider-models-message-${row.id}`"
|
|
287
|
+
class="text-foreground-subtle italic"
|
|
288
|
+
>{{ modelsListMessage }}</p>
|
|
289
|
+
<div class="max-h-48 overflow-y-auto space-y-1">
|
|
290
|
+
<UCheckbox
|
|
291
|
+
v-for="modelId in modelChoices"
|
|
292
|
+
:key="modelId"
|
|
293
|
+
:model-value="modelsForm.selected.includes(modelId)"
|
|
294
|
+
:label="modelId"
|
|
295
|
+
size="xs"
|
|
296
|
+
:data-testid="`provider-model-${row.id}-${modelId}`"
|
|
297
|
+
@update:model-value="toggleModel(modelId, Boolean($event))"
|
|
298
|
+
/>
|
|
299
|
+
</div>
|
|
300
|
+
<p class="text-foreground-subtle">
|
|
301
|
+
{{ modelsForm.selected.length === 0 ? "No model ticked: this provider will run no model." : `${modelsForm.selected.length} ticked.` }}
|
|
302
|
+
</p>
|
|
303
|
+
</template>
|
|
304
|
+
<p
|
|
305
|
+
v-if="modelsError"
|
|
306
|
+
:data-testid="`provider-models-error-${row.id}`"
|
|
307
|
+
class="text-error-500"
|
|
308
|
+
>{{ modelsError }}</p>
|
|
309
|
+
<div class="flex justify-end gap-1">
|
|
310
|
+
<UButton size="xs" variant="ghost" :data-testid="`provider-models-cancel-${row.id}`" @click="cancelModelsEditor()">Cancel</UButton>
|
|
311
|
+
<UButton
|
|
312
|
+
size="xs"
|
|
313
|
+
color="primary"
|
|
314
|
+
:loading="rowBusy === row.id"
|
|
315
|
+
:data-testid="`provider-models-save-${row.id}`"
|
|
316
|
+
@click="saveModelsEditor()"
|
|
317
|
+
>Save</UButton>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
|
|
206
321
|
<p
|
|
207
322
|
v-for="notice in row.overrideNotices"
|
|
208
323
|
:key="notice"
|
|
@@ -228,7 +343,7 @@
|
|
|
228
343
|
:open="dialogVisible"
|
|
229
344
|
:title="dialogTitle"
|
|
230
345
|
:ui="{
|
|
231
|
-
content: 'bg-surface-card border border-surface-border sm:max-w-[
|
|
346
|
+
content: 'bg-surface-card border border-surface-border sm:max-w-[520px]',
|
|
232
347
|
header: 'bg-surface-card border-b border-surface-border text-foreground',
|
|
233
348
|
body: 'bg-surface-card p-5',
|
|
234
349
|
footer: 'bg-surface-ground/50 border-t border-surface-border p-3 justify-end',
|
|
@@ -237,104 +352,323 @@
|
|
|
237
352
|
>
|
|
238
353
|
<template #body>
|
|
239
354
|
<div data-testid="provider-dialog" class="space-y-4">
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
class="
|
|
250
|
-
|
|
251
|
-
|
|
355
|
+
<!-- Step 1: library -->
|
|
356
|
+
<div v-if="dialogStep === 'library'" data-testid="provider-library" class="space-y-3">
|
|
357
|
+
<p class="text-xs text-foreground-muted">Which provider do you want to add?</p>
|
|
358
|
+
<button
|
|
359
|
+
type="button"
|
|
360
|
+
data-testid="provider-library-azure"
|
|
361
|
+
class="w-full text-left p-3 rounded-lg border border-surface-border hover:border-primary-500 hover:bg-primary-500/5 transition-colors"
|
|
362
|
+
@click="chooseLibrary(AZURE_ACCOUNT)"
|
|
363
|
+
>
|
|
364
|
+
<span class="flex items-center gap-2">
|
|
365
|
+
<Icon name="lucide:cloud" class="text-primary-500 shrink-0" />
|
|
366
|
+
<span class="text-sm font-medium text-foreground">Microsoft Azure</span>
|
|
367
|
+
</span>
|
|
368
|
+
<span class="block text-xs text-foreground-muted mt-1">
|
|
369
|
+
Azure OpenAI and Claude on Foundry from one Azure resource, with Entra ID or an API key.
|
|
370
|
+
</span>
|
|
371
|
+
</button>
|
|
372
|
+
<div class="grid grid-cols-2 gap-2 max-h-72 overflow-y-auto">
|
|
373
|
+
<button
|
|
374
|
+
v-for="preset in libraryPresets"
|
|
375
|
+
:key="preset.id"
|
|
376
|
+
type="button"
|
|
377
|
+
:data-testid="`provider-library-${preset.id}`"
|
|
378
|
+
class="text-left p-2 rounded-lg border border-surface-border hover:border-primary-500 hover:bg-primary-500/5 transition-colors"
|
|
379
|
+
@click="chooseLibrary(preset.id)"
|
|
380
|
+
>
|
|
381
|
+
<span class="block text-xs font-medium text-foreground truncate">{{ preset.label }}</span>
|
|
382
|
+
<span class="block text-[10px] text-foreground-subtle">{{ libraryHint(preset) }}</span>
|
|
383
|
+
</button>
|
|
384
|
+
<button
|
|
385
|
+
type="button"
|
|
386
|
+
data-testid="provider-library-custom"
|
|
387
|
+
class="text-left p-2 rounded-lg border border-dashed border-surface-border hover:border-primary-500 transition-colors"
|
|
388
|
+
@click="chooseLibrary(CUSTOM_PRESET)"
|
|
389
|
+
>
|
|
390
|
+
<span class="block text-xs font-medium text-foreground">Custom</span>
|
|
391
|
+
<span class="block text-[10px] text-foreground-subtle">Any endpoint, by base URL</span>
|
|
392
|
+
</button>
|
|
393
|
+
</div>
|
|
252
394
|
</div>
|
|
253
395
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
396
|
+
<!-- Step 2: account -->
|
|
397
|
+
<template v-else-if="dialogStep === 'account'">
|
|
398
|
+
<!-- Microsoft Azure account -->
|
|
399
|
+
<template v-if="isAzureAccount">
|
|
400
|
+
<div class="flex flex-col gap-1">
|
|
401
|
+
<label class="text-xs text-foreground-muted font-medium">
|
|
402
|
+
Resource name <span class="text-error-500">*</span>
|
|
403
|
+
</label>
|
|
404
|
+
<UInput
|
|
405
|
+
v-model="azureForm.resource"
|
|
406
|
+
placeholder="contoso-ai"
|
|
407
|
+
data-testid="provider-azure-resource"
|
|
408
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-sm font-mono"
|
|
409
|
+
/>
|
|
410
|
+
<p class="text-[10px] text-foreground-subtle">
|
|
411
|
+
The <code class="font-mono">{{ azureForm.resource.trim() || 'name' }}</code> of
|
|
412
|
+
<code class="font-mono">{{ azureForm.resource.trim() || 'name' }}.openai.azure.com</code>; shared by every entry this account creates.
|
|
413
|
+
</p>
|
|
414
|
+
</div>
|
|
266
415
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
416
|
+
<div class="space-y-2">
|
|
417
|
+
<p class="text-xs text-foreground-muted font-medium">What do you want to use?</p>
|
|
418
|
+
<UCheckbox
|
|
419
|
+
v-model="azureForm.useOpenAI"
|
|
420
|
+
label="Azure OpenAI"
|
|
421
|
+
description="GPT models you deployed on the resource."
|
|
422
|
+
data-testid="provider-azure-openai"
|
|
423
|
+
/>
|
|
424
|
+
<div v-if="azureForm.useOpenAI" class="ml-6 space-y-1.5" data-testid="provider-azure-deployments">
|
|
425
|
+
<p class="text-[10px] text-foreground-subtle">Deployment name → model id the pickers show (blank: the deployment name).</p>
|
|
426
|
+
<div
|
|
427
|
+
v-for="(row, index) in azureForm.deployments"
|
|
428
|
+
:key="index"
|
|
429
|
+
class="flex items-center gap-1.5"
|
|
430
|
+
>
|
|
431
|
+
<UInput
|
|
432
|
+
v-model="row.deployment"
|
|
433
|
+
placeholder="gpt-5-eu"
|
|
434
|
+
:aria-label="`Deployment ${index + 1}`"
|
|
435
|
+
:data-testid="`provider-azure-deployment-${index}`"
|
|
436
|
+
class="flex-1 bg-surface-ground border-surface-border text-foreground text-xs font-mono"
|
|
437
|
+
/>
|
|
438
|
+
<Icon name="lucide:arrow-right" class="text-foreground-subtle shrink-0" />
|
|
439
|
+
<UInput
|
|
440
|
+
v-model="row.model"
|
|
441
|
+
placeholder="gpt-5"
|
|
442
|
+
:aria-label="`Model id ${index + 1}`"
|
|
443
|
+
:data-testid="`provider-azure-model-${index}`"
|
|
444
|
+
class="flex-1 bg-surface-ground border-surface-border text-foreground text-xs font-mono"
|
|
445
|
+
/>
|
|
446
|
+
<UButton
|
|
447
|
+
size="xs"
|
|
448
|
+
variant="ghost"
|
|
449
|
+
:aria-label="`Remove deployment ${index + 1}`"
|
|
450
|
+
:data-testid="`provider-azure-deployment-remove-${index}`"
|
|
451
|
+
@click="removeDeploymentRow(index)"
|
|
452
|
+
>
|
|
453
|
+
<Icon name="lucide:x" class="w-3.5 h-3.5" />
|
|
454
|
+
</UButton>
|
|
455
|
+
</div>
|
|
456
|
+
<UButton size="xs" variant="ghost" data-testid="provider-azure-deployment-add" @click="addDeploymentRow()">
|
|
457
|
+
<Icon name="lucide:plus" class="mr-1.5" />Add deployment
|
|
458
|
+
</UButton>
|
|
459
|
+
<div class="flex flex-col gap-1">
|
|
460
|
+
<label class="text-xs text-foreground-muted font-medium">API version</label>
|
|
461
|
+
<UInput
|
|
462
|
+
v-model="azureForm.apiVersion"
|
|
463
|
+
:disabled="!props.expert"
|
|
464
|
+
data-testid="provider-azure-api-version"
|
|
465
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-xs font-mono"
|
|
466
|
+
/>
|
|
467
|
+
<p v-if="!props.expert" class="text-[10px] text-foreground-subtle">Editable in expert mode.</p>
|
|
468
|
+
</div>
|
|
469
|
+
</div>
|
|
470
|
+
<UCheckbox
|
|
471
|
+
v-model="azureForm.useFoundry"
|
|
472
|
+
label="Claude on Foundry"
|
|
473
|
+
description="Anthropic models deployed through Azure AI Foundry on the same resource."
|
|
474
|
+
data-testid="provider-azure-foundry"
|
|
475
|
+
/>
|
|
476
|
+
</div>
|
|
477
|
+
</template>
|
|
282
478
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
479
|
+
<template v-else>
|
|
480
|
+
<div class="flex flex-col gap-1">
|
|
481
|
+
<label class="text-xs text-foreground-muted font-medium">
|
|
482
|
+
Preset <span class="text-error-500">*</span>
|
|
483
|
+
</label>
|
|
484
|
+
<USelect
|
|
485
|
+
:model-value="form.preset"
|
|
486
|
+
:items="presetOptions"
|
|
487
|
+
placeholder="Choose a preset"
|
|
488
|
+
data-testid="provider-preset"
|
|
489
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-sm"
|
|
490
|
+
@update:model-value="selectPreset(String($event ?? ''))"
|
|
491
|
+
/>
|
|
492
|
+
</div>
|
|
294
493
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
</
|
|
494
|
+
<div v-if="showSurface" class="flex flex-col gap-1">
|
|
495
|
+
<label class="text-xs text-foreground-muted font-medium">
|
|
496
|
+
Surface <span class="text-error-500">*</span>
|
|
497
|
+
</label>
|
|
498
|
+
<USelect
|
|
499
|
+
v-model="form.surface"
|
|
500
|
+
:items="surfaceOptions"
|
|
501
|
+
placeholder="Wire protocol"
|
|
502
|
+
data-testid="provider-surface"
|
|
503
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-sm"
|
|
504
|
+
/>
|
|
505
|
+
</div>
|
|
506
|
+
|
|
507
|
+
<div class="flex flex-col gap-1">
|
|
508
|
+
<label class="text-xs text-foreground-muted font-medium">
|
|
509
|
+
ID <span class="text-error-500">*</span>
|
|
510
|
+
</label>
|
|
511
|
+
<UInput
|
|
512
|
+
v-model="form.id"
|
|
513
|
+
:disabled="editing !== null"
|
|
514
|
+
placeholder="openrouter"
|
|
515
|
+
data-testid="provider-id"
|
|
516
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-sm font-mono"
|
|
517
|
+
:class="editing !== null ? 'opacity-60 cursor-not-allowed' : ''"
|
|
518
|
+
/>
|
|
519
|
+
<p v-if="idError" class="text-xs text-error-500">{{ idError }}</p>
|
|
520
|
+
<p v-else class="text-[10px] text-foreground-subtle">The left half of a model selector: <code class="font-mono"><id>/<model></code>.</p>
|
|
521
|
+
</div>
|
|
522
|
+
|
|
523
|
+
<div class="flex flex-col gap-1">
|
|
524
|
+
<label class="text-xs text-foreground-muted font-medium">
|
|
525
|
+
Base URL <span v-if="isCustom" class="text-error-500">*</span>
|
|
526
|
+
</label>
|
|
527
|
+
<UInput
|
|
528
|
+
v-model="form.baseUrl"
|
|
529
|
+
:placeholder="baseUrlPlaceholder"
|
|
530
|
+
data-testid="provider-base-url"
|
|
531
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-sm font-mono"
|
|
532
|
+
/>
|
|
533
|
+
</div>
|
|
534
|
+
|
|
535
|
+
<div v-for="field in paramFields" :key="field.key" class="flex flex-col gap-1">
|
|
536
|
+
<label class="text-xs text-foreground-muted font-medium">
|
|
537
|
+
{{ field.label }} <span v-if="field.required" class="text-error-500">*</span>
|
|
538
|
+
</label>
|
|
539
|
+
<UInput
|
|
540
|
+
v-model="form.params[field.key]"
|
|
541
|
+
:disabled="!field.editable"
|
|
542
|
+
:data-testid="`provider-param-${field.key}`"
|
|
543
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-sm font-mono"
|
|
544
|
+
/>
|
|
545
|
+
<p v-if="!field.editable" class="text-[10px] text-foreground-subtle">Editable in expert mode.</p>
|
|
546
|
+
</div>
|
|
547
|
+
</template>
|
|
548
|
+
|
|
549
|
+
<!-- How do you sign in? -->
|
|
550
|
+
<div v-if="showSignIn" data-testid="provider-signin" class="space-y-1.5">
|
|
551
|
+
<p class="text-xs text-foreground-muted font-medium">How do you sign in?</p>
|
|
552
|
+
<div class="flex flex-wrap gap-1.5" role="radiogroup" aria-label="How do you sign in?">
|
|
553
|
+
<UButton
|
|
554
|
+
v-for="option in signInOptions"
|
|
555
|
+
:key="option.id"
|
|
556
|
+
size="xs"
|
|
557
|
+
role="radio"
|
|
558
|
+
:aria-checked="form.signIn === option.id"
|
|
559
|
+
:variant="form.signIn === option.id ? 'solid' : 'outline'"
|
|
560
|
+
:color="form.signIn === option.id ? 'primary' : 'neutral'"
|
|
561
|
+
:data-testid="`provider-signin-${option.id}`"
|
|
562
|
+
@click="selectSignIn(option.id)"
|
|
563
|
+
>{{ option.label }}</UButton>
|
|
564
|
+
</div>
|
|
565
|
+
<p v-if="signInOption" class="text-[10px] text-foreground-subtle leading-relaxed">{{ signInOption.description }}</p>
|
|
307
566
|
</div>
|
|
308
567
|
|
|
309
|
-
<
|
|
310
|
-
<
|
|
568
|
+
<template v-if="showCredentialFields">
|
|
569
|
+
<div class="flex flex-col gap-1">
|
|
570
|
+
<label class="text-xs text-foreground-muted font-medium">Credential environment variable</label>
|
|
571
|
+
<UInput
|
|
572
|
+
v-model="form.envName"
|
|
573
|
+
:placeholder="defaultEnvPlaceholder"
|
|
574
|
+
data-testid="provider-credential-env"
|
|
575
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-sm font-mono"
|
|
576
|
+
/>
|
|
577
|
+
<p class="text-[10px] text-foreground-subtle">
|
|
578
|
+
Stored as <code class="font-mono">env:{{ form.envName.trim() || defaultEnvPlaceholder }}</code> — the key itself stays in the environment.
|
|
579
|
+
</p>
|
|
580
|
+
</div>
|
|
581
|
+
|
|
582
|
+
<div v-if="canSetValue" class="flex flex-col gap-1">
|
|
583
|
+
<label class="text-xs text-foreground-muted font-medium">Set value now</label>
|
|
584
|
+
<UInput
|
|
585
|
+
v-model="form.secret"
|
|
586
|
+
type="password"
|
|
587
|
+
autocomplete="off"
|
|
588
|
+
placeholder="Optional — written to this scope's .env"
|
|
589
|
+
data-testid="provider-set-value"
|
|
590
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-sm font-mono"
|
|
591
|
+
/>
|
|
592
|
+
</div>
|
|
593
|
+
</template>
|
|
594
|
+
|
|
595
|
+
<div v-if="showReferenceField" class="flex flex-col gap-1">
|
|
596
|
+
<label class="text-xs text-foreground-muted font-medium">
|
|
597
|
+
Credential reference <span class="text-error-500">*</span>
|
|
598
|
+
</label>
|
|
311
599
|
<UInput
|
|
312
|
-
v-model="form.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
placeholder="Optional — written to this scope's .env"
|
|
316
|
-
data-testid="provider-set-value"
|
|
600
|
+
v-model="form.credentialRef"
|
|
601
|
+
placeholder="env:NAME · keyring:forge/name · cmd:… · platform:…"
|
|
602
|
+
data-testid="provider-dialog-credential-ref"
|
|
317
603
|
class="w-full bg-surface-ground border-surface-border text-foreground text-sm font-mono"
|
|
318
604
|
/>
|
|
319
605
|
</div>
|
|
320
606
|
</template>
|
|
321
607
|
|
|
608
|
+
<!-- Step 3: done, the created entries tested -->
|
|
609
|
+
<div v-else data-testid="provider-done" class="space-y-2">
|
|
610
|
+
<p class="text-xs text-foreground-muted">
|
|
611
|
+
{{ createdIds.length === 1 ? 'Added' : `Added ${createdIds.length} providers` }}. Testing the sign-in:
|
|
612
|
+
</p>
|
|
613
|
+
<div
|
|
614
|
+
v-for="id in createdIds"
|
|
615
|
+
:key="id"
|
|
616
|
+
:data-testid="`provider-created-${id}`"
|
|
617
|
+
class="flex items-start gap-2 p-2 rounded-lg border border-surface-border text-xs"
|
|
618
|
+
>
|
|
619
|
+
<span class="font-mono text-foreground shrink-0">{{ id }}</span>
|
|
620
|
+
<span v-if="testBusy === id" class="text-foreground-subtle italic">testing…</span>
|
|
621
|
+
<span
|
|
622
|
+
v-else-if="testResults[id]"
|
|
623
|
+
:data-testid="`provider-created-result-${id}`"
|
|
624
|
+
:class="testResults[id]?.ok ? 'text-success-500' : 'text-error-500'"
|
|
625
|
+
>{{ testResults[id]?.message }}</span>
|
|
626
|
+
<UButton
|
|
627
|
+
v-if="testBusy !== id"
|
|
628
|
+
size="xs"
|
|
629
|
+
variant="ghost"
|
|
630
|
+
class="ml-auto"
|
|
631
|
+
:disabled="testBusy !== null"
|
|
632
|
+
:data-testid="`provider-created-test-${id}`"
|
|
633
|
+
@click="testProvider({ id })"
|
|
634
|
+
>Test again</UButton>
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
|
|
322
638
|
<p v-if="dialogError" data-testid="provider-dialog-error" class="text-sm text-error-500">{{ dialogError }}</p>
|
|
323
639
|
</div>
|
|
324
640
|
</template>
|
|
325
641
|
|
|
326
642
|
<template #footer>
|
|
327
643
|
<div class="flex gap-2 justify-end w-full">
|
|
328
|
-
<UButton size="sm" variant="ghost" @click="dialogVisible = false">Cancel</UButton>
|
|
329
644
|
<UButton
|
|
645
|
+
v-if="!editing && dialogStep === 'account'"
|
|
330
646
|
size="sm"
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
@click="
|
|
647
|
+
variant="ghost"
|
|
648
|
+
class="mr-auto"
|
|
649
|
+
data-testid="provider-back"
|
|
650
|
+
@click="backToLibrary()"
|
|
335
651
|
>
|
|
336
|
-
<Icon name="lucide:
|
|
652
|
+
<Icon name="lucide:arrow-left" class="mr-1.5" />Back
|
|
337
653
|
</UButton>
|
|
654
|
+
<template v-if="dialogStep === 'done'">
|
|
655
|
+
<UButton size="sm" color="primary" data-testid="provider-done-close" @click="finishDialog()">
|
|
656
|
+
<Icon name="lucide:check" class="mr-1.5" />Done
|
|
657
|
+
</UButton>
|
|
658
|
+
</template>
|
|
659
|
+
<template v-else>
|
|
660
|
+
<UButton size="sm" variant="ghost" @click="dialogVisible = false">Cancel</UButton>
|
|
661
|
+
<UButton
|
|
662
|
+
v-if="dialogStep === 'account'"
|
|
663
|
+
size="sm"
|
|
664
|
+
color="primary"
|
|
665
|
+
data-testid="provider-save"
|
|
666
|
+
:loading="saving"
|
|
667
|
+
@click="saveProvider"
|
|
668
|
+
>
|
|
669
|
+
<Icon name="lucide:check" class="mr-1.5" />{{ editing ? 'Save' : isAzureAccount ? 'Add account' : 'Add' }}
|
|
670
|
+
</UButton>
|
|
671
|
+
</template>
|
|
338
672
|
</div>
|
|
339
673
|
</template>
|
|
340
674
|
</UModal>
|
|
@@ -375,8 +709,16 @@
|
|
|
375
709
|
|
|
376
710
|
<script setup lang="ts">
|
|
377
711
|
import { computed } from "vue";
|
|
712
|
+
import type { DataZoneRule } from "../shared/provider-registry";
|
|
378
713
|
import type { ProvidersPanelClient } from "../vue/providers-client";
|
|
379
|
-
import {
|
|
714
|
+
import {
|
|
715
|
+
AZURE_ACCOUNT,
|
|
716
|
+
CUSTOM_PRESET,
|
|
717
|
+
type ProvidersPanelModelsMode,
|
|
718
|
+
type ProvidersPanelPreset,
|
|
719
|
+
providersPanelNote,
|
|
720
|
+
providersPanelRowMeta as rowMeta,
|
|
721
|
+
} from "../vue/providers-panel-model";
|
|
380
722
|
import { type ProvidersPanelLayer, useProvidersPanel } from "../vue/useProvidersPanel";
|
|
381
723
|
|
|
382
724
|
const props = withDefaults(
|
|
@@ -402,13 +744,40 @@ const props = withDefaults(
|
|
|
402
744
|
readOnly?: boolean;
|
|
403
745
|
/** The line above the list. Absent: the default text for `layer`; a string replaces it; `false` hides it. */
|
|
404
746
|
note?: string | false;
|
|
747
|
+
/**
|
|
748
|
+
* Expert mode: the sign-in step also offers the raw reference form
|
|
749
|
+
* (`env:` / `keyring:` / `cmd:` / `platform:`), and expert-only
|
|
750
|
+
* parameters (`api_version`) become editable.
|
|
751
|
+
*/
|
|
752
|
+
expert?: boolean;
|
|
753
|
+
/**
|
|
754
|
+
* The host's data-zone rule: rows outside it show a muted note naming the
|
|
755
|
+
* rule (`provider-blocked-<id>`). Absent or `any`: none.
|
|
756
|
+
*/
|
|
757
|
+
dataZoneRule?: DataZoneRule;
|
|
405
758
|
}>(),
|
|
406
759
|
// Without a default Vue casts an absent boolean to `false`; the panel tells absent from `false`.
|
|
407
|
-
{ environmentEnabled: undefined, note: undefined },
|
|
760
|
+
{ environmentEnabled: undefined, note: undefined, expert: false },
|
|
408
761
|
);
|
|
409
762
|
|
|
410
763
|
const noteText = computed(() => providersPanelNote(props.note, props.layer));
|
|
411
764
|
|
|
765
|
+
const MODELS_MODE_ITEMS: Array<{ label: string; value: ProvidersPanelModelsMode }> = [
|
|
766
|
+
{ label: "All models", value: "all" },
|
|
767
|
+
{ label: "Only these", value: "only" },
|
|
768
|
+
];
|
|
769
|
+
|
|
770
|
+
/** The library card's second line: what the preset serves. */
|
|
771
|
+
function libraryHint(preset: ProvidersPanelPreset): string {
|
|
772
|
+
if (preset.credential.kind === "none") return "Local, no sign-in";
|
|
773
|
+
if (preset.kind === "stt") return "Speech to text";
|
|
774
|
+
if (preset.kind === "tts") return "Text to speech";
|
|
775
|
+
if (preset.kind === "embed") return "Embeddings";
|
|
776
|
+
return "Chat models";
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const defaultEnvPlaceholder = computed(() => defaultEnvName.value ?? "PROVIDER_API_KEY");
|
|
780
|
+
|
|
412
781
|
const emit = defineEmits<{
|
|
413
782
|
/** After every successful `PUT`/`DELETE`. */
|
|
414
783
|
(event: "changed"): void;
|
|
@@ -431,6 +800,24 @@ const {
|
|
|
431
800
|
baseUrlPlaceholder,
|
|
432
801
|
canSetValue,
|
|
433
802
|
showCredentialFields,
|
|
803
|
+
defaultEnvName,
|
|
804
|
+
dialogStep,
|
|
805
|
+
libraryPresets,
|
|
806
|
+
chooseLibrary,
|
|
807
|
+
backToLibrary,
|
|
808
|
+
isAzureAccount,
|
|
809
|
+
signInOptions,
|
|
810
|
+
signInOption,
|
|
811
|
+
selectSignIn,
|
|
812
|
+
showSignIn,
|
|
813
|
+
showReferenceField,
|
|
814
|
+
paramFields,
|
|
815
|
+
azureForm,
|
|
816
|
+
addDeploymentRow,
|
|
817
|
+
removeDeploymentRow,
|
|
818
|
+
createdIds,
|
|
819
|
+
finishDialog,
|
|
820
|
+
openSignIn,
|
|
434
821
|
rowBusy,
|
|
435
822
|
rowError,
|
|
436
823
|
canTest,
|
|
@@ -440,6 +827,18 @@ const {
|
|
|
440
827
|
onCredentialSelect,
|
|
441
828
|
setEnabled,
|
|
442
829
|
resetPatch,
|
|
830
|
+
setEuProcessing,
|
|
831
|
+
modelsEditing,
|
|
832
|
+
modelsForm,
|
|
833
|
+
modelsLoading,
|
|
834
|
+
modelsListMessage,
|
|
835
|
+
modelChoices,
|
|
836
|
+
modelsError,
|
|
837
|
+
openModelsEditor,
|
|
838
|
+
setModelsMode,
|
|
839
|
+
toggleModel,
|
|
840
|
+
cancelModelsEditor,
|
|
841
|
+
saveModelsEditor,
|
|
443
842
|
keyEditing,
|
|
444
843
|
keyForm,
|
|
445
844
|
keyError,
|