feat(assistant): select provider, model and vault credential
Lets the assistant panel pick the provider and model for a session, and choose or create the vault credential an external provider needs. Status messages for the credential endpoints come from the shared CREDENTIAL_ERROR_MESSAGES map, so the assistant and the vault report 400/401/409 the same way. Also moves the remaining Italian UI strings to English, matching the rest of the application. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7e9b483882
commit
4a40a8aa2a
|
|
@ -104,6 +104,7 @@ describe('AssistantCallService', () => {
|
|||
const selection = {
|
||||
provider: 'OpenAI',
|
||||
model: 'gpt-oss:20b',
|
||||
credentialId: 'credential-1',
|
||||
phaseModels: { planningModel: 'planning-model' }
|
||||
};
|
||||
const actionRequests = [
|
||||
|
|
|
|||
|
|
@ -74,8 +74,13 @@ function mapAssistantConfig(raw: unknown): AssistantConfig {
|
|||
return {
|
||||
defaultProvider: String(value['defaultProvider'] ?? value['provider'] ?? ''),
|
||||
defaultModel: String(value['defaultModel'] ?? ''),
|
||||
availableProvidersRetrieverUrl: String(value['availableProvidersRetrieverUrl'] ?? ''),
|
||||
availableModelsRetrieverUrl: String(value['availableModelsRetrieverUrlTemplate'] ?? value['availableModelsRetrieverUrl'] ?? ''),
|
||||
availableProvidersRetrieverUrl: String(value['providersRetrieverUrl'] ?? value['availableProvidersRetrieverUrl'] ?? ''),
|
||||
availableModelsRetrieverUrl: String(
|
||||
value['modelsRetrieverUrlTemplate']
|
||||
?? value['availableModelsRetrieverUrlTemplate']
|
||||
?? value['availableModelsRetrieverUrl']
|
||||
?? ''
|
||||
),
|
||||
defaultPhaseModels: mapPhaseModels(value['defaultPhaseModels'])
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -237,6 +237,55 @@
|
|||
gap: 10px;
|
||||
}
|
||||
|
||||
.assistant-credential-picker,
|
||||
.assistant-credentials-list,
|
||||
.assistant-credential-form {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.assistant-add-credential {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.assistant-credential-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px;
|
||||
border: 1px solid #dbe4ee;
|
||||
border-radius: 10px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.assistant-credential-item strong,
|
||||
.assistant-credential-item p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.assistant-credential-item strong {
|
||||
color: #0f172a;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.assistant-credential-item p {
|
||||
margin-top: 3px;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.assistant-credential-inactive {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.assistant-credential-actions {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.assistant-llm-settings .assistant-copy {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
|
@ -482,4 +531,12 @@
|
|||
.assistant-phase-fields {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.assistant-credential-item {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.assistant-credential-actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@
|
|||
<section class="assistant-card assistant-llm-settings">
|
||||
<div class="assistant-starters-head">
|
||||
<div>
|
||||
<p class="assistant-label">Configurazione assistente</p>
|
||||
<p class="assistant-copy">Le credenziali dei provider sono gestite dal backend.</p>
|
||||
<p class="assistant-label">Assistant model</p>
|
||||
<p class="assistant-copy">Provider credentials are managed by the backend.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -51,11 +51,11 @@
|
|||
[checked]="useDefaultConfiguration()"
|
||||
[disabled]="configurationLocked() || assistantBusy()"
|
||||
(change)="setUseDefaultConfiguration($event.checked)">
|
||||
Usa configurazione predefinita
|
||||
Use default configuration
|
||||
</mat-checkbox>
|
||||
|
||||
@if (configurationLocked()) {
|
||||
<p class="assistant-meta">La configurazione LLM è fissata per questa sessione.</p>
|
||||
<p class="assistant-meta">The LLM configuration is locked for this session.</p>
|
||||
}
|
||||
|
||||
@if (!useDefaultConfiguration()) {
|
||||
|
|
@ -70,12 +70,12 @@
|
|||
<mat-option [value]="provider">{{ provider }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
@if (providersLoading()) { <mat-hint>Caricamento provider...</mat-hint> }
|
||||
@if (providersLoading()) { <mat-hint>Loading providers...</mat-hint> }
|
||||
@if (providersError()) { <mat-error>{{ providersError() }}</mat-error> }
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Modello</mat-label>
|
||||
<mat-label>Model</mat-label>
|
||||
<mat-select
|
||||
[value]="selectedModel()"
|
||||
[disabled]="configurationLocked() || assistantBusy() || !selectedProvider() || modelsLoading()"
|
||||
|
|
@ -84,13 +84,36 @@
|
|||
<mat-option [value]="model">{{ model }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
@if (modelsLoading()) { <mat-hint>Caricamento modelli...</mat-hint> }
|
||||
@if (modelsLoading()) { <mat-hint>Loading models...</mat-hint> }
|
||||
@if (modelsError()) { <mat-error>{{ modelsError() }}</mat-error> }
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@if (providerNeedsCredential()) {
|
||||
<div class="assistant-credential-picker">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Credential</mat-label>
|
||||
<mat-select
|
||||
[value]="selectedCredentialId()"
|
||||
[disabled]="configurationLocked() || assistantBusy() || credentialsLoading()"
|
||||
(selectionChange)="selectCredential($event.value)">
|
||||
@for (credential of compatibleCredentials(); track credential.id) {
|
||||
<mat-option [value]="credential.id">{{ credential.label }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
@if (credentialsLoading()) { <mat-hint>Loading credentials...</mat-hint> }
|
||||
@if (credentialsError()) { <mat-error>{{ credentialsError() }}</mat-error> }
|
||||
</mat-form-field>
|
||||
@if (!credentialsLoading() && !compatibleCredentials().length) {
|
||||
<button type="button" mat-stroked-button class="assistant-add-credential" [disabled]="configurationLocked() || assistantBusy()" (click)="openCredentialForm()">
|
||||
Add credential
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<button type="button" class="assistant-advanced-toggle" [disabled]="configurationLocked() || assistantBusy()" (click)="toggleAdvancedModels()">
|
||||
{{ advancedModelsOpen() ? 'Nascondi' : 'Mostra' }} modelli per fase (opzionale)
|
||||
{{ advancedModelsOpen() ? 'Hide' : 'Show' }} per-phase models (optional)
|
||||
</button>
|
||||
|
||||
@if (advancedModelsOpen()) {
|
||||
|
|
@ -98,21 +121,21 @@
|
|||
<mat-form-field appearance="outline">
|
||||
<mat-label>Planning</mat-label>
|
||||
<mat-select [value]="phaseModels()?.planningModel ?? ''" [disabled]="configurationLocked() || assistantBusy() || !models().length" (selectionChange)="setPhaseModel('planningModel', $event.value)">
|
||||
<mat-option value="">Modello principale</mat-option>
|
||||
<mat-option value="">Main model</mat-option>
|
||||
@for (model of models(); track model) { <mat-option [value]="model">{{ model }}</mat-option> }
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>JSON</mat-label>
|
||||
<mat-select [value]="phaseModels()?.jsonModel ?? ''" [disabled]="configurationLocked() || assistantBusy() || !models().length" (selectionChange)="setPhaseModel('jsonModel', $event.value)">
|
||||
<mat-option value="">Modello principale</mat-option>
|
||||
<mat-option value="">Main model</mat-option>
|
||||
@for (model of models(); track model) { <mat-option [value]="model">{{ model }}</mat-option> }
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Repair</mat-label>
|
||||
<mat-select [value]="phaseModels()?.repairModel ?? ''" [disabled]="configurationLocked() || assistantBusy() || !models().length" (selectionChange)="setPhaseModel('repairModel', $event.value)">
|
||||
<mat-option value="">Modello principale</mat-option>
|
||||
<mat-option value="">Main model</mat-option>
|
||||
@for (model of models(); track model) { <mat-option [value]="model">{{ model }}</mat-option> }
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
|
@ -121,6 +144,75 @@
|
|||
}
|
||||
</section>
|
||||
|
||||
<section class="assistant-card assistant-credentials">
|
||||
<div class="assistant-starters-head">
|
||||
<div>
|
||||
<p class="assistant-label">Provider credentials</p>
|
||||
<p class="assistant-copy">Keys are stored in the vault and are never displayed.</p>
|
||||
</div>
|
||||
<button type="button" class="assistant-section-toggle" (click)="toggleCredentialsPanel()" [attr.aria-label]="credentialsPanelOpen() ? 'Collapse credentials' : 'Expand credentials'">
|
||||
{{ credentialsPanelOpen() ? '−' : '+' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (credentialsPanelOpen()) {
|
||||
<div class="assistant-credentials-list">
|
||||
@if (credentialsLoading()) {
|
||||
<p class="assistant-meta">Loading credentials...</p>
|
||||
} @else if (!credentials().length) {
|
||||
<p class="assistant-meta">No credentials saved yet.</p>
|
||||
} @else {
|
||||
@for (credential of credentials(); track credential.id) {
|
||||
<article class="assistant-credential-item" [class.assistant-credential-inactive]="!credential.active">
|
||||
<div>
|
||||
<strong>{{ credential.label }}</strong>
|
||||
<p>{{ credential.provider }} · {{ credential.active ? 'Active' : 'Inactive' }}</p>
|
||||
@if (credential.description) { <p>{{ credential.description }}</p> }
|
||||
<p>Key configured · last used: {{ credential.lastUsedAt || 'never' }}</p>
|
||||
</div>
|
||||
<div class="assistant-credential-actions">
|
||||
<button type="button" mat-stroked-button (click)="editCredential(credential)">Edit / rotate</button>
|
||||
<button type="button" mat-stroked-button [disabled]="credentialSaving()" (click)="setCredentialActive(credential, !credential.active)">
|
||||
{{ credential.active ? 'Disable' : 'Enable' }}
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (!credentialFormOpen()) {
|
||||
<button type="button" mat-stroked-button class="assistant-add-credential" (click)="openCredentialForm()">Add credential</button>
|
||||
} @else {
|
||||
<form class="assistant-credential-form" (ngSubmit)="saveCredential()">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Label</mat-label>
|
||||
<input matInput [ngModel]="credentialLabel()" (ngModelChange)="credentialLabel.set($event)" name="credentialLabel" required />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Provider</mat-label>
|
||||
<mat-select [value]="credentialProvider()" [disabled]="!!editingCredentialId()" (selectionChange)="credentialProvider.set($event.value)" name="credentialProvider" required>
|
||||
@for (provider of providers(); track provider) { <mat-option [value]="provider">{{ provider }}</mat-option> }
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Description (optional)</mat-label>
|
||||
<input matInput [ngModel]="credentialDescription()" (ngModelChange)="credentialDescription.set($event)" name="credentialDescription" />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>{{ editingCredentialId() ? 'New value (to rotate)' : 'API key' }}</mat-label>
|
||||
<input matInput type="password" autocomplete="off" [ngModel]="credentialValue()" (ngModelChange)="credentialValue.set($event)" name="credentialValue" [required]="!editingCredentialId()" />
|
||||
<mat-hint>{{ editingCredentialId() ? 'Leave empty to keep the current key.' : 'The value will not be shown again after saving.' }}</mat-hint>
|
||||
</mat-form-field>
|
||||
@if (credentialsError()) { <p class="assistant-error">{{ credentialsError() }}</p> }
|
||||
<button type="submit" mat-flat-button class="assistant-send" [disabled]="credentialSaving() || !credentialLabel().trim() || !credentialProvider().trim() || (!editingCredentialId() && !credentialValue().trim())">
|
||||
{{ credentialSaving() ? 'Saving...' : editingCredentialId() ? 'Save changes' : 'Save credential' }}
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
</section>
|
||||
|
||||
@if (assistantErrorMessage()) {
|
||||
<section class="assistant-card assistant-error-card">
|
||||
<p class="assistant-error-message">{{ assistantErrorMessage() }}</p>
|
||||
|
|
|
|||
|
|
@ -14,10 +14,12 @@ import {
|
|||
AssistantDraftPayload,
|
||||
AssistantFlowActionResult,
|
||||
AssistantLlmSelection,
|
||||
AssistantSessionState
|
||||
AssistantSessionState,
|
||||
VaultSecret
|
||||
} from '@models/assistant';
|
||||
import { Flow } from '@models/flow';
|
||||
import { AssistantService } from '@services/assistant/assistant';
|
||||
import { CREDENTIAL_ERROR_MESSAGES, VaultService } from '@services/vault/vault';
|
||||
import { Authorization } from '@services/authorization/authorization';
|
||||
import { AssistantSessionStore } from '@stores/assistant-session-store';
|
||||
import { EditorStateHolder } from '@stores/flow-editor';
|
||||
|
|
@ -35,6 +37,7 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
@ViewChild('assistantScroll') assistantScrollElement?: ElementRef<HTMLDivElement>;
|
||||
|
||||
private readonly assistant = inject(AssistantService);
|
||||
private readonly vault = inject(VaultService);
|
||||
private readonly editorState = inject(EditorStateHolder);
|
||||
private readonly authorization = inject(Authorization);
|
||||
private readonly sessionStore = inject(AssistantSessionStore);
|
||||
|
|
@ -66,6 +69,18 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
readonly useDefaultConfiguration = signal(true);
|
||||
readonly selectedProvider = signal('');
|
||||
readonly phaseModels = signal<AssistantLlmSelection['phaseModels']>(undefined);
|
||||
readonly credentials = signal<VaultSecret[]>([]);
|
||||
readonly credentialsLoading = signal(false);
|
||||
readonly credentialsError = signal<string | null>(null);
|
||||
readonly selectedCredentialId = signal('');
|
||||
readonly credentialsPanelOpen = signal(false);
|
||||
readonly credentialFormOpen = signal(false);
|
||||
readonly credentialSaving = signal(false);
|
||||
readonly editingCredentialId = signal<string | null>(null);
|
||||
readonly credentialLabel = signal('');
|
||||
readonly credentialProvider = signal('');
|
||||
readonly credentialDescription = signal('');
|
||||
readonly credentialValue = signal('');
|
||||
readonly advancedModelsOpen = signal(false);
|
||||
readonly modelPickerOpen = signal(false);
|
||||
readonly quickPromptsOpen = signal(true);
|
||||
|
|
@ -189,8 +204,22 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
readonly currentFlow = this.editorState.currentFlow;
|
||||
readonly currentDraft = computed(() => this.sessionState()?.currentDraftFlow ?? null);
|
||||
readonly configurationLocked = computed(() => !!this.sessionState()?.id);
|
||||
readonly providerNeedsCredential = computed(() =>
|
||||
!!this.selectedProvider().trim() && !this.isInternalProvider(this.selectedProvider())
|
||||
);
|
||||
readonly compatibleCredentials = computed(() => {
|
||||
const provider = this.selectedProvider().trim().toLowerCase();
|
||||
if (!provider) return [];
|
||||
return this.credentials().filter((credential) =>
|
||||
credential.active && credential.provider.trim().toLowerCase() === provider
|
||||
);
|
||||
});
|
||||
readonly customConfigurationValid = computed(() =>
|
||||
!!this.selectedProvider().trim() && !!this.selectedModel().trim()
|
||||
!!this.selectedProvider().trim()
|
||||
&& !!this.selectedModel().trim()
|
||||
&& (!this.providerNeedsCredential() || this.compatibleCredentials().some(
|
||||
(credential) => credential.id === this.selectedCredentialId()
|
||||
))
|
||||
);
|
||||
readonly configurationValid = computed(() =>
|
||||
this.useDefaultConfiguration() || this.customConfigurationValid()
|
||||
|
|
@ -260,12 +289,99 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
if (this.configurationLocked()) return;
|
||||
this.selectedProvider.set(provider);
|
||||
this.selectedModel.set('');
|
||||
this.selectedCredentialId.set('');
|
||||
this.models.set([]);
|
||||
this.modelsError.set(null);
|
||||
if (provider) void this.loadModels(provider);
|
||||
if (provider) void this.loadCredentials();
|
||||
this.persistSnapshot();
|
||||
}
|
||||
|
||||
selectCredential(credentialId: string) {
|
||||
if (this.configurationLocked()) return;
|
||||
this.selectedCredentialId.set(credentialId);
|
||||
}
|
||||
|
||||
toggleCredentialsPanel() {
|
||||
this.credentialsPanelOpen.update((open) => !open);
|
||||
if (this.credentialsPanelOpen()) {
|
||||
void this.loadCredentials();
|
||||
void this.loadProviders();
|
||||
}
|
||||
}
|
||||
|
||||
openCredentialForm(provider = this.selectedProvider()) {
|
||||
if (this.configurationLocked()) return;
|
||||
this.editingCredentialId.set(null);
|
||||
this.credentialLabel.set('');
|
||||
this.credentialProvider.set(provider);
|
||||
this.credentialDescription.set('');
|
||||
this.credentialValue.set('');
|
||||
this.credentialsError.set(null);
|
||||
this.credentialsPanelOpen.set(true);
|
||||
this.credentialFormOpen.set(true);
|
||||
}
|
||||
|
||||
editCredential(credential: VaultSecret) {
|
||||
this.editingCredentialId.set(credential.id);
|
||||
this.credentialLabel.set(credential.label);
|
||||
this.credentialProvider.set(credential.provider);
|
||||
this.credentialDescription.set(credential.description ?? '');
|
||||
this.credentialValue.set('');
|
||||
this.credentialsError.set(null);
|
||||
this.credentialsPanelOpen.set(true);
|
||||
this.credentialFormOpen.set(true);
|
||||
}
|
||||
|
||||
saveCredential() {
|
||||
const label = this.credentialLabel().trim();
|
||||
const provider = this.credentialProvider().trim();
|
||||
const description = this.credentialDescription().trim();
|
||||
const value = this.credentialValue();
|
||||
const editingId = this.editingCredentialId();
|
||||
if (!label || !provider || (!editingId && !value.trim()) || this.credentialSaving()) return;
|
||||
|
||||
this.credentialSaving.set(true);
|
||||
this.credentialsError.set(null);
|
||||
const request = editingId
|
||||
? this.vault.updateSecret(editingId, {
|
||||
label,
|
||||
description: description || undefined,
|
||||
...(value.trim() ? { value } : {})
|
||||
})
|
||||
: this.vault.createSecret({ label, provider, description: description || undefined, value });
|
||||
|
||||
request.pipe(finalize(() => {
|
||||
this.credentialSaving.set(false);
|
||||
this.credentialValue.set('');
|
||||
})).subscribe({
|
||||
next: (credential) => {
|
||||
this.credentialFormOpen.set(false);
|
||||
this.editingCredentialId.set(null);
|
||||
if (credential.active && this.sameProvider(credential.provider, this.selectedProvider())) {
|
||||
this.selectedCredentialId.set(credential.id);
|
||||
}
|
||||
void this.loadCredentials();
|
||||
},
|
||||
error: (err) => this.credentialsError.set(this.backendErrorMessage(err))
|
||||
});
|
||||
}
|
||||
|
||||
setCredentialActive(credential: VaultSecret, active: boolean) {
|
||||
if (this.credentialSaving()) return;
|
||||
this.credentialSaving.set(true);
|
||||
this.credentialsError.set(null);
|
||||
this.vault.updateSecret(credential.id, { active }).pipe(
|
||||
finalize(() => this.credentialSaving.set(false))
|
||||
).subscribe({
|
||||
next: () => {
|
||||
if (!active && this.selectedCredentialId() === credential.id) this.selectedCredentialId.set('');
|
||||
void this.loadCredentials();
|
||||
},
|
||||
error: (err) => this.credentialsError.set(this.backendErrorMessage(err))
|
||||
});
|
||||
}
|
||||
|
||||
setPhaseModel(phase: keyof NonNullable<AssistantLlmSelection['phaseModels']>, model: string) {
|
||||
if (this.configurationLocked()) return;
|
||||
this.phaseModels.update((current) => ({ ...current, [phase]: model || undefined }));
|
||||
|
|
@ -354,6 +470,7 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
|
||||
const request = {
|
||||
userPrompt: normalizedContent,
|
||||
maxRepairAttempts: 2,
|
||||
...(this.llmSelection() ? { llmSelection: this.llmSelection() } : {}),
|
||||
...(intent === 'draft' ? {} : { flow: this.assistantFlowForRequest() }),
|
||||
...(intent === 'fix' ? { validationErrors: this.sessionState()?.lastValidationErrors ?? [] } : {})
|
||||
|
|
@ -391,6 +508,7 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
next: (config) => {
|
||||
this.assistantConfig.set(config);
|
||||
this.initializeConfiguration(config);
|
||||
void this.loadCredentials();
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Assistant config loading failed', err);
|
||||
|
|
@ -439,6 +557,24 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
private loadCredentials() {
|
||||
this.credentialsLoading.set(true);
|
||||
this.credentialsError.set(null);
|
||||
this.vault.listSecrets().pipe(
|
||||
take(1),
|
||||
finalize(() => this.credentialsLoading.set(false))
|
||||
).subscribe({
|
||||
next: (credentials) => {
|
||||
this.credentials.set(credentials);
|
||||
const selectedId = this.selectedCredentialId();
|
||||
if (selectedId && !credentials.some((credential) => credential.id === selectedId && credential.active)) {
|
||||
this.selectedCredentialId.set('');
|
||||
}
|
||||
},
|
||||
error: (err) => this.credentialsError.set(this.backendErrorMessage(err))
|
||||
});
|
||||
}
|
||||
|
||||
private llmSelection(): AssistantLlmSelection | undefined {
|
||||
if (this.useDefaultConfiguration()) return undefined;
|
||||
const provider = this.selectedProvider().trim();
|
||||
|
|
@ -448,7 +584,13 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
const populatedPhases = phaseModels && Object.values(phaseModels).some(Boolean)
|
||||
? phaseModels
|
||||
: undefined;
|
||||
return { provider, model, ...(populatedPhases ? { phaseModels: populatedPhases } : {}) };
|
||||
const credentialId = this.selectedCredentialId();
|
||||
return {
|
||||
provider,
|
||||
model,
|
||||
...(this.providerNeedsCredential() && credentialId ? { credentialId } : {}),
|
||||
...(populatedPhases ? { phaseModels: populatedPhases } : {})
|
||||
};
|
||||
}
|
||||
|
||||
private sessionRequest() {
|
||||
|
|
@ -501,7 +643,7 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
private backendErrorMessage(error: unknown): string {
|
||||
const value = error as { error?: unknown; message?: unknown };
|
||||
const value = error as { error?: unknown; message?: unknown; status?: unknown };
|
||||
const payload = value?.error;
|
||||
if (typeof payload === 'string' && payload.trim()) return payload;
|
||||
if (payload && typeof payload === 'object') {
|
||||
|
|
@ -510,10 +652,20 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
if (typeof body[key] === 'string' && body[key].trim()) return body[key] as string;
|
||||
}
|
||||
}
|
||||
const statusMessage = CREDENTIAL_ERROR_MESSAGES[Number(value?.status)];
|
||||
if (statusMessage) return statusMessage;
|
||||
if (typeof value?.message === 'string' && value.message.trim()) return value.message;
|
||||
return FlowAssistant.STANDARD_ASSISTANT_ERROR;
|
||||
}
|
||||
|
||||
private isInternalProvider(provider: string): boolean {
|
||||
return provider.trim().toLowerCase() === 'internalollama';
|
||||
}
|
||||
|
||||
private sameProvider(left: string, right: string): boolean {
|
||||
return left.trim().toLowerCase() === right.trim().toLowerCase();
|
||||
}
|
||||
|
||||
private async openSession(
|
||||
promptToSend?: string,
|
||||
flowKey = this.activeFlowKey ?? this.resolveFlowKey(this.currentFlow()?.id ?? null)
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ export class TitleToolbar {
|
|||
},
|
||||
error: err => {
|
||||
console.error('Save failed', err);
|
||||
this.showSnackbar(err instanceof Error ? err.message : 'Errore durante il salvataggio', 'error');
|
||||
this.showSnackbar(err instanceof Error ? err.message : 'Unable to save the flow', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -399,7 +399,7 @@ export class TitleToolbar {
|
|||
error: (err) => {
|
||||
this.executeLoading.set(false);
|
||||
console.error('Create execution failed', err);
|
||||
this.showSnackbar('Errore durante la creazione dell\'esecuzione', 'error');
|
||||
this.showSnackbar('Unable to create the execution', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue