@skaile/forge-settings 1.3.1 → 1.3.2
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 +4 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/ForgeProvidersPanel.vue +168 -183
- package/dist/runtime/vue/providers-panel-model.d.ts +10 -0
- package/dist/runtime/vue/providers-panel-model.js +4 -0
- package/package.json +1 -1
- package/src/runtime/components/ForgeProvidersPanel.vue +168 -183
- package/src/runtime/vue/providers-panel-model.ts +14 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.3.2
|
|
4
|
+
|
|
5
|
+
- `<ForgeProvidersPanel>` lists providers as stacked rows instead of a six-column table: name and badge, then one muted line with id, preset and base URL, the credential picker below, and Test / Edit / Delete / Enabled on the right. It fits a settings column without clipping the actions or wrapping the base URL per character. Test ids are unchanged.
|
|
6
|
+
|
|
3
7
|
## 1.3.1
|
|
4
8
|
|
|
5
9
|
- `<ForgeProvidersPanel>` takes an optional `note` prop: a string replaces the line above the provider list, `false` hides it (Add provider stays, right-aligned). Absent keeps today's text, so existing hosts see no change.
|
package/dist/module.json
CHANGED
|
@@ -52,191 +52,176 @@
|
|
|
52
52
|
This registry has no providers yet.
|
|
53
53
|
</div>
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
v-if="keyEditing === row.id"
|
|
109
|
-
:data-testid="`provider-credential-enter-key-${row.id}`"
|
|
110
|
-
class="space-y-1.5 p-2 rounded-lg border border-surface-border bg-surface-ground/50"
|
|
55
|
+
<!-- One stacked row per provider: fits a settings column; a table squeezed the base URL and clipped the actions. -->
|
|
56
|
+
<ul v-else class="rounded-lg border border-surface-border divide-y divide-surface-border text-xs">
|
|
57
|
+
<li
|
|
58
|
+
v-for="row in rows"
|
|
59
|
+
:key="row.id"
|
|
60
|
+
:data-testid="`provider-row-${row.id}`"
|
|
61
|
+
class="p-3 space-y-2"
|
|
62
|
+
:class="row.enabled ? '' : 'opacity-60'"
|
|
63
|
+
>
|
|
64
|
+
<div class="flex items-start gap-3">
|
|
65
|
+
<div class="flex-1 min-w-0">
|
|
66
|
+
<div class="flex items-center gap-1.5 flex-wrap">
|
|
67
|
+
<span class="text-sm font-medium text-foreground">{{ row.name }}</span>
|
|
68
|
+
<UBadge
|
|
69
|
+
:data-testid="`provider-badge-${row.id}`"
|
|
70
|
+
size="sm"
|
|
71
|
+
variant="subtle"
|
|
72
|
+
color="neutral"
|
|
73
|
+
>{{ row.badge }}</UBadge>
|
|
74
|
+
<Icon v-if="row.locked" name="lucide:lock" class="text-foreground-subtle shrink-0" />
|
|
75
|
+
</div>
|
|
76
|
+
<p class="font-mono text-foreground-subtle truncate" :title="row.baseUrl ?? void 0">{{ rowMeta(row) }}</p>
|
|
77
|
+
<p v-if="row.inheritedNote" class="text-foreground-subtle italic">{{ row.inheritedNote }}</p>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="flex items-center gap-1 shrink-0">
|
|
80
|
+
<UButton
|
|
81
|
+
v-if="canTest"
|
|
82
|
+
size="sm"
|
|
83
|
+
variant="ghost"
|
|
84
|
+
:loading="testBusy === row.id"
|
|
85
|
+
:disabled="testBusy !== null"
|
|
86
|
+
:data-testid="`provider-test-${row.id}`"
|
|
87
|
+
@click="testProvider(row)"
|
|
88
|
+
>
|
|
89
|
+
<Icon name="lucide:plug-zap" class="mr-1.5 w-3.5 h-3.5" />Test
|
|
90
|
+
</UButton>
|
|
91
|
+
<UButton
|
|
92
|
+
v-if="row.canReset"
|
|
93
|
+
size="sm"
|
|
94
|
+
variant="ghost"
|
|
95
|
+
:loading="rowBusy === row.id"
|
|
96
|
+
:data-testid="`provider-reset-${row.id}`"
|
|
97
|
+
@click="resetPatch(row)"
|
|
98
|
+
>
|
|
99
|
+
<Icon name="lucide:undo-2" class="mr-1.5 w-3.5 h-3.5" />Reset
|
|
100
|
+
</UButton>
|
|
101
|
+
<template v-if="row.editable">
|
|
102
|
+
<UButton
|
|
103
|
+
size="sm"
|
|
104
|
+
variant="ghost"
|
|
105
|
+
:aria-label="`Edit ${row.id}`"
|
|
106
|
+
:data-testid="`provider-edit-${row.id}`"
|
|
107
|
+
@click="openEdit(row)"
|
|
111
108
|
>
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
autocomplete="off"
|
|
122
|
-
placeholder="Key"
|
|
123
|
-
:data-testid="`provider-key-value-${row.id}`"
|
|
124
|
-
class="w-full bg-surface-ground border-surface-border text-foreground text-xs font-mono"
|
|
125
|
-
/>
|
|
126
|
-
<p v-if="keyError" class="text-error-500">{{ keyError }}</p>
|
|
127
|
-
<div class="flex justify-end gap-1">
|
|
128
|
-
<UButton size="xs" variant="ghost" :data-testid="`provider-key-cancel-${row.id}`" @click="cancelKeyEditor()">Cancel</UButton>
|
|
129
|
-
<UButton
|
|
130
|
-
size="xs"
|
|
131
|
-
color="primary"
|
|
132
|
-
:loading="rowBusy === row.id"
|
|
133
|
-
:data-testid="`provider-key-save-${row.id}`"
|
|
134
|
-
@click="saveKeyEditor()"
|
|
135
|
-
>Save</UButton>
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
|
-
<!-- Inline reference editor -->
|
|
140
|
-
<div
|
|
141
|
-
v-if="refEditing === row.id"
|
|
142
|
-
:data-testid="`provider-credential-ref-${row.id}`"
|
|
143
|
-
class="space-y-1.5 p-2 rounded-lg border border-surface-border bg-surface-ground/50"
|
|
109
|
+
<Icon name="lucide:pencil" class="w-3.5 h-3.5" />
|
|
110
|
+
</UButton>
|
|
111
|
+
<UButton
|
|
112
|
+
size="sm"
|
|
113
|
+
variant="ghost"
|
|
114
|
+
color="error"
|
|
115
|
+
:aria-label="`Delete ${row.id}`"
|
|
116
|
+
:data-testid="`provider-delete-${row.id}`"
|
|
117
|
+
@click="requestDelete(row)"
|
|
144
118
|
>
|
|
145
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
:data-testid="`provider-ref-save-${row.id}`"
|
|
159
|
-
@click="saveRefEditor()"
|
|
160
|
-
>Save</UButton>
|
|
161
|
-
</div>
|
|
162
|
-
</div>
|
|
119
|
+
<Icon name="lucide:trash-2" class="w-3.5 h-3.5" />
|
|
120
|
+
</UButton>
|
|
121
|
+
</template>
|
|
122
|
+
<USwitch
|
|
123
|
+
:model-value="row.enabled"
|
|
124
|
+
:disabled="!row.canToggle || rowBusy === row.id"
|
|
125
|
+
:aria-label="`Enable ${row.id}`"
|
|
126
|
+
:data-testid="`provider-enabled-${row.id}`"
|
|
127
|
+
class="ml-1"
|
|
128
|
+
@update:model-value="setEnabled(row, Boolean($event))"
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
163
132
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
133
|
+
<div class="space-y-1 max-w-md">
|
|
134
|
+
<USelect
|
|
135
|
+
v-if="row.needsCredential"
|
|
136
|
+
:model-value="row.credentialValue"
|
|
137
|
+
:items="row.credentialOptions"
|
|
138
|
+
placeholder="Choose a credential"
|
|
139
|
+
:disabled="!row.canEditCredential || rowBusy === row.id"
|
|
140
|
+
:data-testid="`provider-credential-${row.id}`"
|
|
141
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-xs font-mono"
|
|
142
|
+
@update:model-value="onCredentialSelect(row, String($event ?? ''))"
|
|
143
|
+
/>
|
|
144
|
+
<p :class="row.configured ? 'text-foreground-subtle' : 'text-amber-600 dark:text-amber-400'" class="italic">
|
|
145
|
+
{{ row.status }}
|
|
146
|
+
</p>
|
|
147
|
+
|
|
148
|
+
<!-- Inline key editor -->
|
|
149
|
+
<div
|
|
150
|
+
v-if="keyEditing === row.id"
|
|
151
|
+
:data-testid="`provider-credential-enter-key-${row.id}`"
|
|
152
|
+
class="space-y-1.5 p-2 rounded-lg border border-surface-border bg-surface-ground/50"
|
|
153
|
+
>
|
|
154
|
+
<UInput
|
|
155
|
+
v-model="keyForm.name"
|
|
156
|
+
placeholder="PROVIDER_API_KEY"
|
|
157
|
+
:data-testid="`provider-key-name-${row.id}`"
|
|
158
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-xs font-mono"
|
|
159
|
+
/>
|
|
160
|
+
<UInput
|
|
161
|
+
v-model="keyForm.value"
|
|
162
|
+
type="password"
|
|
163
|
+
autocomplete="off"
|
|
164
|
+
placeholder="Key"
|
|
165
|
+
:data-testid="`provider-key-value-${row.id}`"
|
|
166
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-xs font-mono"
|
|
167
|
+
/>
|
|
168
|
+
<p v-if="keyError" class="text-error-500">{{ keyError }}</p>
|
|
169
|
+
<div class="flex justify-end gap-1">
|
|
170
|
+
<UButton size="xs" variant="ghost" :data-testid="`provider-key-cancel-${row.id}`" @click="cancelKeyEditor()">Cancel</UButton>
|
|
171
|
+
<UButton
|
|
172
|
+
size="xs"
|
|
173
|
+
color="primary"
|
|
174
|
+
:loading="rowBusy === row.id"
|
|
175
|
+
:data-testid="`provider-key-save-${row.id}`"
|
|
176
|
+
@click="saveKeyEditor()"
|
|
177
|
+
>Save</UButton>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<!-- Inline reference editor -->
|
|
182
|
+
<div
|
|
183
|
+
v-if="refEditing === row.id"
|
|
184
|
+
:data-testid="`provider-credential-ref-${row.id}`"
|
|
185
|
+
class="space-y-1.5 p-2 rounded-lg border border-surface-border bg-surface-ground/50"
|
|
186
|
+
>
|
|
187
|
+
<UInput
|
|
188
|
+
v-model="refForm.ref"
|
|
189
|
+
placeholder="keyring:forge/name · cmd:… · platform:…"
|
|
190
|
+
:data-testid="`provider-ref-input-${row.id}`"
|
|
191
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-xs font-mono"
|
|
192
|
+
/>
|
|
193
|
+
<p v-if="refError" class="text-error-500">{{ refError }}</p>
|
|
194
|
+
<div class="flex justify-end gap-1">
|
|
195
|
+
<UButton size="xs" variant="ghost" :data-testid="`provider-ref-cancel-${row.id}`" @click="cancelRefEditor()">Cancel</UButton>
|
|
196
|
+
<UButton
|
|
197
|
+
size="xs"
|
|
198
|
+
color="primary"
|
|
199
|
+
:loading="rowBusy === row.id"
|
|
200
|
+
:data-testid="`provider-ref-save-${row.id}`"
|
|
201
|
+
@click="saveRefEditor()"
|
|
202
|
+
>Save</UButton>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<p
|
|
207
|
+
v-for="notice in row.overrideNotices"
|
|
208
|
+
:key="notice"
|
|
209
|
+
:data-testid="`provider-override-notice-${row.id}`"
|
|
210
|
+
class="text-foreground-subtle"
|
|
211
|
+
>{{ notice }}</p>
|
|
212
|
+
<p
|
|
213
|
+
v-if="testResults[row.id]"
|
|
214
|
+
:data-testid="`provider-test-result-${row.id}`"
|
|
215
|
+
:class="testResults[row.id]?.ok ? 'text-success-500' : 'text-error-500'"
|
|
216
|
+
>{{ testResults[row.id]?.message }}</p>
|
|
217
|
+
<p
|
|
218
|
+
v-if="rowError?.id === row.id"
|
|
219
|
+
:data-testid="`provider-row-error-${row.id}`"
|
|
220
|
+
class="text-error-500"
|
|
221
|
+
>{{ rowError.message }}</p>
|
|
222
|
+
</div>
|
|
223
|
+
</li>
|
|
224
|
+
</ul>
|
|
240
225
|
|
|
241
226
|
<!-- Add / edit dialog -->
|
|
242
227
|
<UModal
|
|
@@ -390,7 +375,7 @@
|
|
|
390
375
|
|
|
391
376
|
<script setup>
|
|
392
377
|
import { computed } from "vue";
|
|
393
|
-
import { providersPanelNote } from "../vue/providers-panel-model";
|
|
378
|
+
import { providersPanelNote, providersPanelRowMeta as rowMeta } from "../vue/providers-panel-model";
|
|
394
379
|
import { useProvidersPanel } from "../vue/useProvidersPanel";
|
|
395
380
|
const props = defineProps({
|
|
396
381
|
client: { type: Object, required: false },
|
|
@@ -149,6 +149,16 @@ export declare const PROVIDER_ID_PATTERN: RegExp;
|
|
|
149
149
|
export declare const DEFAULT_PRESETS_URL = "/api/providers/presets";
|
|
150
150
|
/** The line `ForgeProvidersPanel` shows above its list; `null` = none. */
|
|
151
151
|
export declare function providersPanelNote(note: string | false | undefined, layer: ProvidersPanelLayer): string | null;
|
|
152
|
+
/**
|
|
153
|
+
* The muted line under a provider's name: its id (when the name differs), its
|
|
154
|
+
* preset or surface and its base URL, without repeating a value.
|
|
155
|
+
*/
|
|
156
|
+
export declare function providersPanelRowMeta(row: {
|
|
157
|
+
id: string;
|
|
158
|
+
name: string;
|
|
159
|
+
origin: string;
|
|
160
|
+
baseUrl?: string | null;
|
|
161
|
+
}): string;
|
|
152
162
|
/** An `env:NAME` credential reference; group 1 is the variable name. */
|
|
153
163
|
export declare const ENV_REF_PATTERN: RegExp;
|
|
154
164
|
/** The credential kind a written reference gets when the row states none. */
|
|
@@ -8,6 +8,10 @@ export function providersPanelNote(note, layer) {
|
|
|
8
8
|
if (note !== void 0) return note;
|
|
9
9
|
return layer === "workspace" ? "Entries of this workspace's providers.yml can be changed here; instance entries are inherited. Keys stay in the environment; an entry only references them." : "The instance registry, $FORGE_HOME/.skaile/providers.yml. Keys stay in the environment; an entry only references them.";
|
|
10
10
|
}
|
|
11
|
+
export function providersPanelRowMeta(row) {
|
|
12
|
+
const parts = [row.name !== row.id ? row.id : null, row.origin, row.baseUrl ?? null];
|
|
13
|
+
return [...new Set(parts.filter((part) => !!part))].join(" \xB7 ");
|
|
14
|
+
}
|
|
11
15
|
export const ENV_REF_PATTERN = /^env:(.+)$/;
|
|
12
16
|
export const FALLBACK_CREDENTIAL_KIND = "api_key_header";
|
|
13
17
|
export const ENV_SOURCE_LABEL = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skaile/forge-settings",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "Provider and settings operations for Skaile forge apps: provider registry layers, credential store, env layers, model discovery, the providers panel, and editor / AI SDK model adapters.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "bun@1.3.9",
|
|
@@ -52,191 +52,176 @@
|
|
|
52
52
|
This registry has no providers yet.
|
|
53
53
|
</div>
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
v-if="keyEditing === row.id"
|
|
109
|
-
:data-testid="`provider-credential-enter-key-${row.id}`"
|
|
110
|
-
class="space-y-1.5 p-2 rounded-lg border border-surface-border bg-surface-ground/50"
|
|
55
|
+
<!-- One stacked row per provider: fits a settings column; a table squeezed the base URL and clipped the actions. -->
|
|
56
|
+
<ul v-else class="rounded-lg border border-surface-border divide-y divide-surface-border text-xs">
|
|
57
|
+
<li
|
|
58
|
+
v-for="row in rows"
|
|
59
|
+
:key="row.id"
|
|
60
|
+
:data-testid="`provider-row-${row.id}`"
|
|
61
|
+
class="p-3 space-y-2"
|
|
62
|
+
:class="row.enabled ? '' : 'opacity-60'"
|
|
63
|
+
>
|
|
64
|
+
<div class="flex items-start gap-3">
|
|
65
|
+
<div class="flex-1 min-w-0">
|
|
66
|
+
<div class="flex items-center gap-1.5 flex-wrap">
|
|
67
|
+
<span class="text-sm font-medium text-foreground">{{ row.name }}</span>
|
|
68
|
+
<UBadge
|
|
69
|
+
:data-testid="`provider-badge-${row.id}`"
|
|
70
|
+
size="sm"
|
|
71
|
+
variant="subtle"
|
|
72
|
+
color="neutral"
|
|
73
|
+
>{{ row.badge }}</UBadge>
|
|
74
|
+
<Icon v-if="row.locked" name="lucide:lock" class="text-foreground-subtle shrink-0" />
|
|
75
|
+
</div>
|
|
76
|
+
<p class="font-mono text-foreground-subtle truncate" :title="row.baseUrl ?? undefined">{{ rowMeta(row) }}</p>
|
|
77
|
+
<p v-if="row.inheritedNote" class="text-foreground-subtle italic">{{ row.inheritedNote }}</p>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="flex items-center gap-1 shrink-0">
|
|
80
|
+
<UButton
|
|
81
|
+
v-if="canTest"
|
|
82
|
+
size="sm"
|
|
83
|
+
variant="ghost"
|
|
84
|
+
:loading="testBusy === row.id"
|
|
85
|
+
:disabled="testBusy !== null"
|
|
86
|
+
:data-testid="`provider-test-${row.id}`"
|
|
87
|
+
@click="testProvider(row)"
|
|
88
|
+
>
|
|
89
|
+
<Icon name="lucide:plug-zap" class="mr-1.5 w-3.5 h-3.5" />Test
|
|
90
|
+
</UButton>
|
|
91
|
+
<UButton
|
|
92
|
+
v-if="row.canReset"
|
|
93
|
+
size="sm"
|
|
94
|
+
variant="ghost"
|
|
95
|
+
:loading="rowBusy === row.id"
|
|
96
|
+
:data-testid="`provider-reset-${row.id}`"
|
|
97
|
+
@click="resetPatch(row)"
|
|
98
|
+
>
|
|
99
|
+
<Icon name="lucide:undo-2" class="mr-1.5 w-3.5 h-3.5" />Reset
|
|
100
|
+
</UButton>
|
|
101
|
+
<template v-if="row.editable">
|
|
102
|
+
<UButton
|
|
103
|
+
size="sm"
|
|
104
|
+
variant="ghost"
|
|
105
|
+
:aria-label="`Edit ${row.id}`"
|
|
106
|
+
:data-testid="`provider-edit-${row.id}`"
|
|
107
|
+
@click="openEdit(row)"
|
|
111
108
|
>
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
autocomplete="off"
|
|
122
|
-
placeholder="Key"
|
|
123
|
-
:data-testid="`provider-key-value-${row.id}`"
|
|
124
|
-
class="w-full bg-surface-ground border-surface-border text-foreground text-xs font-mono"
|
|
125
|
-
/>
|
|
126
|
-
<p v-if="keyError" class="text-error-500">{{ keyError }}</p>
|
|
127
|
-
<div class="flex justify-end gap-1">
|
|
128
|
-
<UButton size="xs" variant="ghost" :data-testid="`provider-key-cancel-${row.id}`" @click="cancelKeyEditor()">Cancel</UButton>
|
|
129
|
-
<UButton
|
|
130
|
-
size="xs"
|
|
131
|
-
color="primary"
|
|
132
|
-
:loading="rowBusy === row.id"
|
|
133
|
-
:data-testid="`provider-key-save-${row.id}`"
|
|
134
|
-
@click="saveKeyEditor()"
|
|
135
|
-
>Save</UButton>
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
|
-
<!-- Inline reference editor -->
|
|
140
|
-
<div
|
|
141
|
-
v-if="refEditing === row.id"
|
|
142
|
-
:data-testid="`provider-credential-ref-${row.id}`"
|
|
143
|
-
class="space-y-1.5 p-2 rounded-lg border border-surface-border bg-surface-ground/50"
|
|
109
|
+
<Icon name="lucide:pencil" class="w-3.5 h-3.5" />
|
|
110
|
+
</UButton>
|
|
111
|
+
<UButton
|
|
112
|
+
size="sm"
|
|
113
|
+
variant="ghost"
|
|
114
|
+
color="error"
|
|
115
|
+
:aria-label="`Delete ${row.id}`"
|
|
116
|
+
:data-testid="`provider-delete-${row.id}`"
|
|
117
|
+
@click="requestDelete(row)"
|
|
144
118
|
>
|
|
145
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
:data-testid="`provider-ref-save-${row.id}`"
|
|
159
|
-
@click="saveRefEditor()"
|
|
160
|
-
>Save</UButton>
|
|
161
|
-
</div>
|
|
162
|
-
</div>
|
|
119
|
+
<Icon name="lucide:trash-2" class="w-3.5 h-3.5" />
|
|
120
|
+
</UButton>
|
|
121
|
+
</template>
|
|
122
|
+
<USwitch
|
|
123
|
+
:model-value="row.enabled"
|
|
124
|
+
:disabled="!row.canToggle || rowBusy === row.id"
|
|
125
|
+
:aria-label="`Enable ${row.id}`"
|
|
126
|
+
:data-testid="`provider-enabled-${row.id}`"
|
|
127
|
+
class="ml-1"
|
|
128
|
+
@update:model-value="setEnabled(row, Boolean($event))"
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
163
132
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
133
|
+
<div class="space-y-1 max-w-md">
|
|
134
|
+
<USelect
|
|
135
|
+
v-if="row.needsCredential"
|
|
136
|
+
:model-value="row.credentialValue"
|
|
137
|
+
:items="row.credentialOptions"
|
|
138
|
+
placeholder="Choose a credential"
|
|
139
|
+
:disabled="!row.canEditCredential || rowBusy === row.id"
|
|
140
|
+
:data-testid="`provider-credential-${row.id}`"
|
|
141
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-xs font-mono"
|
|
142
|
+
@update:model-value="onCredentialSelect(row, String($event ?? ''))"
|
|
143
|
+
/>
|
|
144
|
+
<p :class="row.configured ? 'text-foreground-subtle' : 'text-amber-600 dark:text-amber-400'" class="italic">
|
|
145
|
+
{{ row.status }}
|
|
146
|
+
</p>
|
|
147
|
+
|
|
148
|
+
<!-- Inline key editor -->
|
|
149
|
+
<div
|
|
150
|
+
v-if="keyEditing === row.id"
|
|
151
|
+
:data-testid="`provider-credential-enter-key-${row.id}`"
|
|
152
|
+
class="space-y-1.5 p-2 rounded-lg border border-surface-border bg-surface-ground/50"
|
|
153
|
+
>
|
|
154
|
+
<UInput
|
|
155
|
+
v-model="keyForm.name"
|
|
156
|
+
placeholder="PROVIDER_API_KEY"
|
|
157
|
+
:data-testid="`provider-key-name-${row.id}`"
|
|
158
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-xs font-mono"
|
|
159
|
+
/>
|
|
160
|
+
<UInput
|
|
161
|
+
v-model="keyForm.value"
|
|
162
|
+
type="password"
|
|
163
|
+
autocomplete="off"
|
|
164
|
+
placeholder="Key"
|
|
165
|
+
:data-testid="`provider-key-value-${row.id}`"
|
|
166
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-xs font-mono"
|
|
167
|
+
/>
|
|
168
|
+
<p v-if="keyError" class="text-error-500">{{ keyError }}</p>
|
|
169
|
+
<div class="flex justify-end gap-1">
|
|
170
|
+
<UButton size="xs" variant="ghost" :data-testid="`provider-key-cancel-${row.id}`" @click="cancelKeyEditor()">Cancel</UButton>
|
|
171
|
+
<UButton
|
|
172
|
+
size="xs"
|
|
173
|
+
color="primary"
|
|
174
|
+
:loading="rowBusy === row.id"
|
|
175
|
+
:data-testid="`provider-key-save-${row.id}`"
|
|
176
|
+
@click="saveKeyEditor()"
|
|
177
|
+
>Save</UButton>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<!-- Inline reference editor -->
|
|
182
|
+
<div
|
|
183
|
+
v-if="refEditing === row.id"
|
|
184
|
+
:data-testid="`provider-credential-ref-${row.id}`"
|
|
185
|
+
class="space-y-1.5 p-2 rounded-lg border border-surface-border bg-surface-ground/50"
|
|
186
|
+
>
|
|
187
|
+
<UInput
|
|
188
|
+
v-model="refForm.ref"
|
|
189
|
+
placeholder="keyring:forge/name · cmd:… · platform:…"
|
|
190
|
+
:data-testid="`provider-ref-input-${row.id}`"
|
|
191
|
+
class="w-full bg-surface-ground border-surface-border text-foreground text-xs font-mono"
|
|
192
|
+
/>
|
|
193
|
+
<p v-if="refError" class="text-error-500">{{ refError }}</p>
|
|
194
|
+
<div class="flex justify-end gap-1">
|
|
195
|
+
<UButton size="xs" variant="ghost" :data-testid="`provider-ref-cancel-${row.id}`" @click="cancelRefEditor()">Cancel</UButton>
|
|
196
|
+
<UButton
|
|
197
|
+
size="xs"
|
|
198
|
+
color="primary"
|
|
199
|
+
:loading="rowBusy === row.id"
|
|
200
|
+
:data-testid="`provider-ref-save-${row.id}`"
|
|
201
|
+
@click="saveRefEditor()"
|
|
202
|
+
>Save</UButton>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<p
|
|
207
|
+
v-for="notice in row.overrideNotices"
|
|
208
|
+
:key="notice"
|
|
209
|
+
:data-testid="`provider-override-notice-${row.id}`"
|
|
210
|
+
class="text-foreground-subtle"
|
|
211
|
+
>{{ notice }}</p>
|
|
212
|
+
<p
|
|
213
|
+
v-if="testResults[row.id]"
|
|
214
|
+
:data-testid="`provider-test-result-${row.id}`"
|
|
215
|
+
:class="testResults[row.id]?.ok ? 'text-success-500' : 'text-error-500'"
|
|
216
|
+
>{{ testResults[row.id]?.message }}</p>
|
|
217
|
+
<p
|
|
218
|
+
v-if="rowError?.id === row.id"
|
|
219
|
+
:data-testid="`provider-row-error-${row.id}`"
|
|
220
|
+
class="text-error-500"
|
|
221
|
+
>{{ rowError.message }}</p>
|
|
222
|
+
</div>
|
|
223
|
+
</li>
|
|
224
|
+
</ul>
|
|
240
225
|
|
|
241
226
|
<!-- Add / edit dialog -->
|
|
242
227
|
<UModal
|
|
@@ -391,7 +376,7 @@
|
|
|
391
376
|
<script setup lang="ts">
|
|
392
377
|
import { computed } from "vue";
|
|
393
378
|
import type { ProvidersPanelClient } from "../vue/providers-client";
|
|
394
|
-
import { providersPanelNote } from "../vue/providers-panel-model";
|
|
379
|
+
import { providersPanelNote, providersPanelRowMeta as rowMeta } from "../vue/providers-panel-model";
|
|
395
380
|
import { type ProvidersPanelLayer, useProvidersPanel } from "../vue/useProvidersPanel";
|
|
396
381
|
|
|
397
382
|
const props = withDefaults(
|
|
@@ -178,6 +178,20 @@ export function providersPanelNote(
|
|
|
178
178
|
: "The instance registry, $FORGE_HOME/.skaile/providers.yml. Keys stay in the environment; an entry only references them.";
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
/**
|
|
182
|
+
* The muted line under a provider's name: its id (when the name differs), its
|
|
183
|
+
* preset or surface and its base URL, without repeating a value.
|
|
184
|
+
*/
|
|
185
|
+
export function providersPanelRowMeta(row: {
|
|
186
|
+
id: string;
|
|
187
|
+
name: string;
|
|
188
|
+
origin: string;
|
|
189
|
+
baseUrl?: string | null;
|
|
190
|
+
}): string {
|
|
191
|
+
const parts = [row.name !== row.id ? row.id : null, row.origin, row.baseUrl ?? null];
|
|
192
|
+
return [...new Set(parts.filter((part): part is string => !!part))].join(" · ");
|
|
193
|
+
}
|
|
194
|
+
|
|
181
195
|
/** An `env:NAME` credential reference; group 1 is the variable name. */
|
|
182
196
|
export const ENV_REF_PATTERN = /^env:(.+)$/;
|
|
183
197
|
|