@@ -354,12 +355,14 @@
@if (authorizationErrorFor(entry); as authorizationError) {
{{ authorizationError }}
}
- @if (!llmCredentialLoadingFor(entry) && !llmCredentialOptionsFor(entry).length && !llmCredentialErrorFor(entry)) {
-
No compatible credentials available.
+ @if (credentialFormError(); as formError) {
+
{{ formError }}
}
@if (!llmCredentialLoadingFor(entry)) {
-
+
}
@if (editingAuthorizationKeys()[entry.requirement.key]) {
@@ -368,31 +371,6 @@
}
- @if (credentialFormOpen()) {
-
- }
>({});
/** Satisfied requirements the user reopened to pick a different credential. */
readonly editingAuthorizationKeys = signal>({});
- readonly credentialFormOpen = signal(false);
- readonly credentialFormProvider = signal('');
- readonly credentialFormLabel = signal('');
- readonly credentialFormDescription = signal('');
- readonly credentialFormValue = signal('');
readonly credentialFormSaving = signal(false);
readonly credentialFormError = signal(null);
readonly outputPreviewModal = signal(null);
@@ -267,7 +262,6 @@ export class TaskExecutionViewerComponent implements OnDestroy {
this.llmCredentialErrors.set({});
this.editingAuthorizationKeys.set({});
this.requestedCredentialProviders.clear();
- this.credentialFormOpen.set(false);
this.credentialFormError.set(null);
this.loadLlmProviderCapabilities();
this.activeAsideTab.set('inputs');
@@ -1023,37 +1017,53 @@ export class TaskExecutionViewerComponent implements OnDestroy {
this.submitAuthorization(entry.requirement);
}
- openVaultCredentialForm(provider: string) {
+ /**
+ * A modal, like the copilot's: the form used to render further down the aside, so on a long run
+ * pressing Add credential scrolled nothing into view and looked like it did nothing.
+ */
+ async openVaultCredentialForm(provider: string) {
this.credentialFormError.set(null);
- this.credentialFormProvider.set(provider);
- this.credentialFormLabel.set('');
- this.credentialFormDescription.set('');
- this.credentialFormValue.set('');
- this.credentialFormOpen.set(true);
+
+ // The provider is fixed by the requirement being answered, so it is shown rather than chosen.
+ const fields: NodeSettingField[] = [
+ { key: 'provider', label: 'Provider', type: 'display' },
+ { key: 'label', label: 'Label', type: 'text', required: true, autofocus: true },
+ { key: 'description', label: 'Description (optional)', type: 'text' },
+ {
+ key: 'value',
+ label: 'API key',
+ type: 'password',
+ required: true,
+ tip: 'The value will not be shown after saving.'
+ }
+ ];
+
+ const result = await this.settingsDialog.open({
+ title: `Add ${provider} credential`,
+ fields,
+ initial: { provider, label: '', description: '', value: '' }
+ });
+ if (!result) return;
+
+ this.saveExecutionCredential(
+ provider,
+ String(result['label'] ?? '').trim(),
+ String(result['description'] ?? '').trim(),
+ String(result['value'] ?? '')
+ );
}
- closeVaultCredentialForm() {
- this.credentialFormOpen.set(false);
- this.credentialFormValue.set('');
- this.credentialFormError.set(null);
- }
-
- saveExecutionCredential() {
- const provider = this.credentialFormProvider().trim();
- const label = this.credentialFormLabel().trim();
- const value = this.credentialFormValue();
+ private saveExecutionCredential(provider: string, label: string, description: string, value: string) {
if (!provider || !label || !value.trim() || this.credentialFormSaving()) return;
this.credentialFormSaving.set(true);
this.vaultService.createSecret({
provider,
label,
- description: this.credentialFormDescription().trim() || undefined,
+ description: description || undefined,
value
}).pipe(take(1)).subscribe({
next: (credential) => {
this.credentialFormSaving.set(false);
- this.credentialFormValue.set('');
- this.credentialFormOpen.set(false);
this.credentialFormError.set(null);
this.reloadLlmCredentials(provider);
@@ -1067,7 +1077,6 @@ export class TaskExecutionViewerComponent implements OnDestroy {
},
error: (error) => {
this.credentialFormSaving.set(false);
- this.credentialFormValue.set('');
this.credentialFormError.set(this.executionErrorMessage(error));
}
});