@skaile/forge-settings 1.2.2 → 1.3.1
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 +10 -0
- package/README.md +31 -2
- package/dist/module.d.mts +4 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -0
- package/dist/runtime/components/ForgeModelRolesPanel.d.vue.ts +34 -0
- package/dist/runtime/components/ForgeModelRolesPanel.vue +250 -0
- package/dist/runtime/components/ForgeModelRolesPanel.vue.d.ts +34 -0
- package/dist/runtime/components/ForgeProvidersPanel.d.vue.ts +18 -5
- package/dist/runtime/components/ForgeProvidersPanel.vue +32 -7
- package/dist/runtime/components/ForgeProvidersPanel.vue.d.ts +18 -5
- package/dist/runtime/server/index.d.ts +1 -0
- package/dist/runtime/server/index.js +1 -0
- package/dist/runtime/shared/model-roles.d.ts +99 -0
- package/dist/runtime/shared/model-roles.js +144 -0
- package/dist/runtime/vue/index.d.ts +3 -0
- package/dist/runtime/vue/index.js +3 -0
- package/dist/runtime/vue/model-roles-panel.d.ts +70 -0
- package/dist/runtime/vue/model-roles-panel.js +54 -0
- package/dist/runtime/vue/providers-client.d.ts +67 -0
- package/dist/runtime/vue/providers-client.js +28 -0
- package/dist/runtime/vue/providers-panel-model.d.ts +24 -4
- package/dist/runtime/vue/providers-panel-model.js +5 -0
- package/dist/runtime/vue/useProvidersPanel.d.ts +10 -2
- package/dist/runtime/vue/useProvidersPanel.js +67 -43
- package/package.json +1 -1
- package/src/runtime/components/ForgeModelRolesPanel.vue +312 -0
- package/src/runtime/components/ForgeProvidersPanel.vue +55 -20
- package/src/runtime/server/index.ts +1 -0
- package/src/runtime/shared/model-roles.ts +283 -0
- package/src/runtime/vue/index.ts +3 -0
- package/src/runtime/vue/model-roles-panel.ts +140 -0
- package/src/runtime/vue/providers-client.ts +100 -0
- package/src/runtime/vue/providers-panel-model.ts +34 -4
- package/src/runtime/vue/useProvidersPanel.ts +89 -45
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
* value.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { $fetch } from "ofetch";
|
|
18
17
|
import {
|
|
19
18
|
type ComputedRef,
|
|
20
19
|
computed,
|
|
@@ -39,11 +38,15 @@ import {
|
|
|
39
38
|
type AIProviderSurface,
|
|
40
39
|
validateCredentialRef,
|
|
41
40
|
} from "../shared/provider-registry";
|
|
41
|
+
import {
|
|
42
|
+
type ProvidersPanelClient,
|
|
43
|
+
type ProvidersPanelTestResult,
|
|
44
|
+
urlProvidersClient,
|
|
45
|
+
} from "./providers-client";
|
|
42
46
|
import {
|
|
43
47
|
CREDENTIAL_ENTER_KEY,
|
|
44
48
|
CREDENTIAL_OTHER_REF,
|
|
45
49
|
CUSTOM_PRESET,
|
|
46
|
-
DEFAULT_PRESETS_URL,
|
|
47
50
|
EMPTY_FORM,
|
|
48
51
|
ENV_SOURCE_LABEL,
|
|
49
52
|
envNameOfRef,
|
|
@@ -52,8 +55,8 @@ import {
|
|
|
52
55
|
OVERRIDES_INSTANCE_NOTICE,
|
|
53
56
|
PROVIDER_ID_PATTERN,
|
|
54
57
|
type ProvidersPanelCredentialOption,
|
|
55
|
-
type ProvidersPanelEntry,
|
|
56
58
|
type ProvidersPanelEmit,
|
|
59
|
+
type ProvidersPanelEntry,
|
|
57
60
|
type ProvidersPanelForm,
|
|
58
61
|
type ProvidersPanelPreset,
|
|
59
62
|
type ProvidersPanelProps,
|
|
@@ -62,7 +65,6 @@ import {
|
|
|
62
65
|
requestErrorMessage,
|
|
63
66
|
} from "./providers-panel-model";
|
|
64
67
|
|
|
65
|
-
export { CREDENTIAL_ENTER_KEY, CREDENTIAL_OTHER_REF, CUSTOM_PRESET } from "./providers-panel-model";
|
|
66
68
|
export type {
|
|
67
69
|
ProvidersPanelCredentialOption,
|
|
68
70
|
ProvidersPanelEmit,
|
|
@@ -74,6 +76,7 @@ export type {
|
|
|
74
76
|
ProvidersPanelRow,
|
|
75
77
|
ProvidersPanelRowState,
|
|
76
78
|
} from "./providers-panel-model";
|
|
79
|
+
export { CREDENTIAL_ENTER_KEY, CREDENTIAL_OTHER_REF, CUSTOM_PRESET } from "./providers-panel-model";
|
|
77
80
|
|
|
78
81
|
/** What both `ForgeProvidersPanel` variants bind to. */
|
|
79
82
|
export interface ProvidersPanelState {
|
|
@@ -134,6 +137,13 @@ export interface ProvidersPanelState {
|
|
|
134
137
|
/** Row id with a write in flight. */
|
|
135
138
|
rowBusy: Ref<string | null>;
|
|
136
139
|
rowError: Ref<{ id: string; message: string } | null>;
|
|
140
|
+
/** The panel was given an explicit client: rows offer a connection test. */
|
|
141
|
+
canTest: ComputedRef<boolean>;
|
|
142
|
+
/** Row id with a test in flight. */
|
|
143
|
+
testBusy: Ref<string | null>;
|
|
144
|
+
/** The last test result per row id. */
|
|
145
|
+
testResults: Record<string, ProvidersPanelTestResult>;
|
|
146
|
+
testProvider: (row: ProvidersPanelEntry) => Promise<void>;
|
|
137
147
|
|
|
138
148
|
// Inline key editor
|
|
139
149
|
/** "Enter a key…" is offered: the scope has an editable `.env` route. */
|
|
@@ -180,16 +190,37 @@ export function useProvidersPanel(
|
|
|
180
190
|
const loadError = ref<string | null>(null);
|
|
181
191
|
const envError = ref<string | null>(null);
|
|
182
192
|
|
|
193
|
+
/** The explicit client, else the URL props. Throws when neither is given. */
|
|
194
|
+
function client(): ProvidersPanelClient {
|
|
195
|
+
if (props.client) return props.client;
|
|
196
|
+
if (!props.providersUrl || !props.providersWriteUrl) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
"ForgeProvidersPanel needs a `client`, or `providersUrl` and `providersWriteUrl`.",
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return urlProvidersClient({
|
|
202
|
+
providersUrl: props.providersUrl,
|
|
203
|
+
providersWriteUrl: props.providersWriteUrl,
|
|
204
|
+
presetsUrl: props.presetsUrl,
|
|
205
|
+
environmentUrl: props.environmentUrl,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** The scope's environment is read and written: `environmentEnabled`, else a client, else `environmentUrl`. */
|
|
210
|
+
const environmentOn = computed(
|
|
211
|
+
() => props.environmentEnabled ?? (props.client ? true : Boolean(props.environmentUrl)),
|
|
212
|
+
);
|
|
213
|
+
|
|
183
214
|
// ── Loading ────────────────────────────────────────────────────────────────
|
|
184
215
|
|
|
185
216
|
/** Read the scope's `.env` document. A failure shows in the disclosure and blocks nothing else. */
|
|
186
217
|
async function loadEnvironment() {
|
|
187
|
-
if (!
|
|
218
|
+
if (!environmentOn.value) {
|
|
188
219
|
envDocument.value = null;
|
|
189
220
|
return;
|
|
190
221
|
}
|
|
191
222
|
try {
|
|
192
|
-
envDocument.value =
|
|
223
|
+
envDocument.value = await client().environment();
|
|
193
224
|
envError.value = null;
|
|
194
225
|
} catch (err) {
|
|
195
226
|
envError.value = requestErrorMessage(err, "Could not load the environment variables.");
|
|
@@ -201,11 +232,8 @@ export function useProvidersPanel(
|
|
|
201
232
|
loading.value = true;
|
|
202
233
|
loadError.value = null;
|
|
203
234
|
try {
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
$fetch(`${props.providersWriteUrl}/detected`) as Promise<ProvidersPanelPreset[]>,
|
|
207
|
-
loadEnvironment(),
|
|
208
|
-
]);
|
|
235
|
+
const api = client();
|
|
236
|
+
const [list, found] = await Promise.all([api.list(), api.detected(), loadEnvironment()]);
|
|
209
237
|
providers.value = Array.isArray(list) ? list : [];
|
|
210
238
|
detected.value = Array.isArray(found) ? found : [];
|
|
211
239
|
} catch (err) {
|
|
@@ -217,9 +245,7 @@ export function useProvidersPanel(
|
|
|
217
245
|
|
|
218
246
|
async function loadPresets() {
|
|
219
247
|
try {
|
|
220
|
-
const list =
|
|
221
|
-
props.presetsUrl ?? DEFAULT_PRESETS_URL,
|
|
222
|
-
)) as ProvidersPanelPreset[];
|
|
248
|
+
const list = await client().presets();
|
|
223
249
|
presets.value = Array.isArray(list) ? list : [];
|
|
224
250
|
} catch (err) {
|
|
225
251
|
loadError.value = requestErrorMessage(err, "Could not load the provider presets.");
|
|
@@ -228,6 +254,8 @@ export function useProvidersPanel(
|
|
|
228
254
|
|
|
229
255
|
/** After every successful write: reload list, banner and environment, tell the host. */
|
|
230
256
|
async function afterWrite() {
|
|
257
|
+
// A test result describes the credential as it was; a write may have changed or removed it.
|
|
258
|
+
for (const key of Object.keys(testResults)) delete testResults[key];
|
|
231
259
|
await refresh();
|
|
232
260
|
emit("changed");
|
|
233
261
|
}
|
|
@@ -238,7 +266,7 @@ export function useProvidersPanel(
|
|
|
238
266
|
});
|
|
239
267
|
|
|
240
268
|
watch(
|
|
241
|
-
() => [props.providersUrl, props.providersWriteUrl, props.environmentUrl],
|
|
269
|
+
() => [props.providersUrl, props.providersWriteUrl, props.environmentUrl, props.client],
|
|
242
270
|
() => void refresh(),
|
|
243
271
|
);
|
|
244
272
|
|
|
@@ -252,9 +280,7 @@ export function useProvidersPanel(
|
|
|
252
280
|
|
|
253
281
|
const envEditable = computed(() => envDocument.value?.editable === true);
|
|
254
282
|
|
|
255
|
-
const canEnterKey = computed(
|
|
256
|
-
() => Boolean(props.environmentUrl) && envDocument.value?.editable !== false,
|
|
257
|
-
);
|
|
283
|
+
const canEnterKey = computed(() => environmentOn.value && envDocument.value?.editable !== false);
|
|
258
284
|
|
|
259
285
|
/** Where an entry lives relative to this scope's own document. */
|
|
260
286
|
function stateOf(entry: Pick<ProvidersPanelEntry, "layers">): ProvidersPanelRowState {
|
|
@@ -400,7 +426,7 @@ export function useProvidersPanel(
|
|
|
400
426
|
);
|
|
401
427
|
|
|
402
428
|
/** Whether "Set value now" is offered: the scope has an `.env` route. */
|
|
403
|
-
const canSetValue = computed(() =>
|
|
429
|
+
const canSetValue = computed(() => environmentOn.value);
|
|
404
430
|
|
|
405
431
|
/** The dialog collects a credential for a new entry only. */
|
|
406
432
|
const showCredentialFields = computed(() => editing.value === null);
|
|
@@ -525,18 +551,15 @@ export function useProvidersPanel(
|
|
|
525
551
|
saving.value = true;
|
|
526
552
|
dialogError.value = null;
|
|
527
553
|
try {
|
|
528
|
-
if (showCredentialFields.value && form.secret &&
|
|
554
|
+
if (showCredentialFields.value && form.secret && environmentOn.value) {
|
|
529
555
|
const update: EnvSettingsUpdate = {
|
|
530
556
|
set: { [form.envName.trim()]: form.secret },
|
|
531
557
|
unset: [],
|
|
532
558
|
};
|
|
533
|
-
await
|
|
559
|
+
await client().updateEnvironment(update);
|
|
534
560
|
form.secret = "";
|
|
535
561
|
}
|
|
536
|
-
await
|
|
537
|
-
method: "PUT",
|
|
538
|
-
body: buildBody(),
|
|
539
|
-
});
|
|
562
|
+
await client().upsert(id, buildBody());
|
|
540
563
|
dialogVisible.value = false;
|
|
541
564
|
editing.value = null;
|
|
542
565
|
await afterWrite();
|
|
@@ -573,9 +596,7 @@ export function useProvidersPanel(
|
|
|
573
596
|
deleting.value = true;
|
|
574
597
|
deleteError.value = null;
|
|
575
598
|
try {
|
|
576
|
-
await
|
|
577
|
-
method: "DELETE",
|
|
578
|
-
});
|
|
599
|
+
await client().remove(target.id);
|
|
579
600
|
deleteTarget.value = null;
|
|
580
601
|
await afterWrite();
|
|
581
602
|
} catch (err) {
|
|
@@ -609,12 +630,9 @@ export function useProvidersPanel(
|
|
|
609
630
|
detectedBusy.value = preset.id;
|
|
610
631
|
detectedError.value = null;
|
|
611
632
|
try {
|
|
612
|
-
await
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
preset: preset.id,
|
|
616
|
-
credential: { kind: preset.credential.kind, ref: `env:${envName}` },
|
|
617
|
-
},
|
|
633
|
+
await client().upsert(id, {
|
|
634
|
+
preset: preset.id,
|
|
635
|
+
credential: { kind: preset.credential.kind, ref: `env:${envName}` },
|
|
618
636
|
});
|
|
619
637
|
await afterWrite();
|
|
620
638
|
} catch (err) {
|
|
@@ -664,10 +682,7 @@ export function useProvidersPanel(
|
|
|
664
682
|
change: { credential?: { kind: AIProviderCredentialKind; ref: string }; enabled?: boolean },
|
|
665
683
|
) {
|
|
666
684
|
const body = row.state === "own" ? { ...ownBody(row), ...change } : change;
|
|
667
|
-
await
|
|
668
|
-
method: "PUT",
|
|
669
|
-
body,
|
|
670
|
-
});
|
|
685
|
+
await client().upsert(row.id, body);
|
|
671
686
|
}
|
|
672
687
|
|
|
673
688
|
function credentialFor(row: ProvidersPanelRow, ref: string) {
|
|
@@ -713,14 +728,39 @@ export function useProvidersPanel(
|
|
|
713
728
|
await runRowWrite(
|
|
714
729
|
row.id,
|
|
715
730
|
async () => {
|
|
716
|
-
await
|
|
717
|
-
method: "DELETE",
|
|
718
|
-
});
|
|
731
|
+
await client().remove(row.id);
|
|
719
732
|
},
|
|
720
733
|
`Could not reset ${row.id}.`,
|
|
721
734
|
);
|
|
722
735
|
}
|
|
723
736
|
|
|
737
|
+
// ── Connection test ────────────────────────────────────────────────────────
|
|
738
|
+
|
|
739
|
+
/** Only an explicit client is known to serve the test; the URL routes are a host's concern. A read-only panel offers none. */
|
|
740
|
+
const canTest = computed(() => Boolean(props.client) && !props.readOnly);
|
|
741
|
+
const testBusy = ref<string | null>(null);
|
|
742
|
+
const testResults = reactive<Record<string, ProvidersPanelTestResult>>({});
|
|
743
|
+
|
|
744
|
+
/** Ask the client whether the entry's credential works; the answer shows in its row. */
|
|
745
|
+
async function testProvider(row: ProvidersPanelEntry) {
|
|
746
|
+
if (!props.client || props.readOnly || testBusy.value !== null) return;
|
|
747
|
+
testBusy.value = row.id;
|
|
748
|
+
delete testResults[row.id];
|
|
749
|
+
try {
|
|
750
|
+
testResults[row.id] = await props.client.test(row.id);
|
|
751
|
+
} catch (err) {
|
|
752
|
+
testResults[row.id] = {
|
|
753
|
+
ok: false,
|
|
754
|
+
message: requestErrorMessage(
|
|
755
|
+
err,
|
|
756
|
+
err instanceof Error && err.message ? err.message : `Could not test ${row.id}.`,
|
|
757
|
+
),
|
|
758
|
+
};
|
|
759
|
+
} finally {
|
|
760
|
+
testBusy.value = null;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
724
764
|
// ── Inline key editor ──────────────────────────────────────────────────────
|
|
725
765
|
|
|
726
766
|
const keyEditing = ref<string | null>(null);
|
|
@@ -761,7 +801,7 @@ export function useProvidersPanel(
|
|
|
761
801
|
*/
|
|
762
802
|
async function saveKeyEditor() {
|
|
763
803
|
const row = rows.value.find((r) => r.id === keyEditing.value);
|
|
764
|
-
if (!row?.canEditCredential || !
|
|
804
|
+
if (!row?.canEditCredential || !environmentOn.value) return;
|
|
765
805
|
const name = keyForm.name.trim();
|
|
766
806
|
const refusal = validateEnvVarName(name);
|
|
767
807
|
if (refusal) {
|
|
@@ -777,7 +817,7 @@ export function useProvidersPanel(
|
|
|
777
817
|
let wroteEnv = false;
|
|
778
818
|
try {
|
|
779
819
|
const update: EnvSettingsUpdate = { set: { [name]: keyForm.value }, unset: [] };
|
|
780
|
-
await
|
|
820
|
+
await client().updateEnvironment(update);
|
|
781
821
|
wroteEnv = true;
|
|
782
822
|
const ref = `env:${name}`;
|
|
783
823
|
if (row.credentialRef !== ref) {
|
|
@@ -837,11 +877,11 @@ export function useProvidersPanel(
|
|
|
837
877
|
|
|
838
878
|
/** Write one change to the scope's `.env`, then reload and tell the host. */
|
|
839
879
|
async function writeEnvironment(update: EnvSettingsUpdate, fallback: string): Promise<boolean> {
|
|
840
|
-
if (!
|
|
880
|
+
if (!environmentOn.value || !envEditable.value) return false;
|
|
841
881
|
envBusy.value = true;
|
|
842
882
|
envError.value = null;
|
|
843
883
|
try {
|
|
844
|
-
await
|
|
884
|
+
await client().updateEnvironment(update);
|
|
845
885
|
} catch (err) {
|
|
846
886
|
envError.value = requestErrorMessage(err, fallback);
|
|
847
887
|
return false;
|
|
@@ -904,6 +944,10 @@ export function useProvidersPanel(
|
|
|
904
944
|
resetPatch,
|
|
905
945
|
rowBusy,
|
|
906
946
|
rowError,
|
|
947
|
+
canTest,
|
|
948
|
+
testBusy,
|
|
949
|
+
testResults,
|
|
950
|
+
testProvider,
|
|
907
951
|
// key editor
|
|
908
952
|
canEnterKey,
|
|
909
953
|
keyEditing,
|