Assistant: gestione cancel, status e refactor chiamate (AssistantService, AssistantCall)

This commit is contained in:
Lucio Lelii 2026-05-18 12:35:21 +02:00
parent 73312f82c0
commit df26fdc666
6 changed files with 67 additions and 7 deletions

View File

@ -46,7 +46,7 @@ export type AssistantConfig = {
availableModelsRetrieverUrl: string;
};
export type AssistantCallStatus = 'QUEUED' | 'RUNNING' | 'COMPLETED' | 'FAILED';
export type AssistantCallStatus = 'QUEUED' | 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED';
export type AssistantCallPhase =
| 'queued'
@ -58,7 +58,8 @@ export type AssistantCallPhase =
| 'fixing'
| 'explaining'
| 'completed'
| 'failed';
| 'failed'
| 'cancelled';
export type AssistantCallState = {
id: string;

View File

@ -19,5 +19,7 @@ export abstract class AssistantCallServiceBase {
abstract getCall(callId: string): Observable<AssistantCallState>;
abstract cancelCall(callId: string): Observable<AssistantCallState>;
abstract getSession(sessionId: string): Observable<AssistantSessionState>;
}

View File

@ -20,6 +20,7 @@ type FakeCallRecord = {
phases: AssistantCallPhase[];
completed: boolean;
failed: boolean;
cancelled: boolean;
};
export class AssistantCallServiceFake extends AssistantCallServiceBase {
@ -83,7 +84,8 @@ export class AssistantCallServiceFake extends AssistantCallServiceBase {
phaseIndex: 0,
phases,
completed: false,
failed: false
failed: false,
cancelled: false
});
session.lastCallId = callId;
@ -96,7 +98,7 @@ export class AssistantCallServiceFake extends AssistantCallServiceBase {
throw new Error(`Assistant call ${callId} not found`);
}
if (!call.completed && !call.failed) {
if (!call.completed && !call.failed && !call.cancelled) {
if (call.phaseIndex < call.phases.length - 1) {
call.phaseIndex += 1;
} else {
@ -109,23 +111,44 @@ export class AssistantCallServiceFake extends AssistantCallServiceBase {
? 'completed'
: call.failed
? 'failed'
: call.phases[call.phaseIndex];
: call.cancelled
? 'cancelled'
: call.phases[call.phaseIndex];
return of({
id: call.id,
sessionId: call.sessionId,
status: call.failed
? 'FAILED'
: call.cancelled
? 'CANCELLED'
: call.completed
? 'COMPLETED'
: call.phaseIndex === 0
? 'QUEUED'
: 'RUNNING',
phase,
progressMessage: call.cancelled ? 'Assistant request cancelled' : undefined,
errorMessage: call.failed ? 'Fake assistant call failed.' : undefined
});
}
override cancelCall(callId: string): Observable<AssistantCallState> {
const call = this.calls.get(callId);
if (!call) {
throw new Error(`Assistant call ${callId} not found`);
}
call.cancelled = true;
return of({
id: call.id,
sessionId: call.sessionId,
status: 'CANCELLED',
phase: 'cancelled',
progressMessage: 'Assistant request cancelled'
});
}
override getSession(sessionId: string): Observable<AssistantSessionState> {
const session = this.sessions.get(sessionId);
if (!session) {

View File

@ -55,6 +55,13 @@ export class AssistantCallService extends AssistantCallServiceBase {
.pipe(map((raw) => mapAssistantCallState(raw)));
}
override cancelCall(callId: string): Observable<AssistantCallState> {
const encodedId = encodeURIComponent(callId);
return this.http
.put<unknown>(`${environment.apiUrl}/assistant/calls/${encodedId}/cancel`, {})
.pipe(map((raw) => mapAssistantCallState(raw)));
}
override getSession(sessionId: string): Observable<AssistantSessionState> {
const encodedId = encodeURIComponent(sessionId);
return this.http
@ -222,7 +229,7 @@ function mapValidationIssues(raw: unknown): AssistantValidationIssue[] {
function mapCallStatus(raw: unknown): AssistantCallStatus {
const normalized = typeof raw === 'string' ? raw.toUpperCase() : '';
if (normalized === 'QUEUED' || normalized === 'RUNNING' || normalized === 'FAILED') return normalized;
if (normalized === 'QUEUED' || normalized === 'RUNNING' || normalized === 'FAILED' || normalized === 'CANCELLED') return normalized;
return 'COMPLETED';
}
@ -239,6 +246,7 @@ function mapCallPhase(raw: unknown): AssistantCallPhase {
case 'explaining':
case 'completed':
case 'failed':
case 'cancelled':
return normalized;
default:
return 'queued';

View File

@ -29,6 +29,10 @@ export class AssistantService {
return this.assistantCall.getCall(callId);
}
cancelCall(callId: string) {
return this.assistantCall.cancelCall(callId);
}
getSession(sessionId: string) {
return this.assistantCall.getSession(sessionId);
}

View File

@ -10,10 +10,14 @@ export type AssistantSessionSnapshot = {
localMessages: AssistantChatMessage[];
currentCall: AssistantCallState | null;
sessionState: AssistantSessionState | null;
assistantErrorMessage: string | null;
lastFailedPrompt: string | null;
lastSubmittedPrompt: string;
};
@Injectable({ providedIn: 'root' })
export class AssistantSessionStore {
static readonly CREATE_MODAL_FLOW_KEY = '__assistant:create-modal';
private static readonly NO_FLOW_KEY = '__assistant:no-flow__';
private static readonly STORAGE_KEY = 'assistant-session-store:v1';
private static readonly STORAGE_TARGETS: Array<'localStorage' | 'sessionStorage'> = ['localStorage', 'sessionStorage'];
@ -32,6 +36,10 @@ export class AssistantSessionStore {
return snapshot ? structuredClone(snapshot) : null;
}
hasSnapshot(flowKey: string): boolean {
return this.snapshots.has(flowKey);
}
setSnapshot(flowKey: string, snapshot: AssistantSessionSnapshot) {
this.snapshots.set(flowKey, structuredClone(snapshot));
this.persistToStorage();
@ -44,6 +52,11 @@ export class AssistantSessionStore {
this.persistToStorage();
}
clearSnapshot(flowKey: string) {
if (!this.snapshots.delete(flowKey)) return;
this.persistToStorage();
}
private hydrateFromStorage() {
const raw = this.readStoredPayload();
if (!raw) return;
@ -83,7 +96,16 @@ export class AssistantSessionStore {
: null,
sessionState: snapshot['sessionState'] && typeof snapshot['sessionState'] === 'object'
? this.normalizeSessionState(snapshot['sessionState'])
: null
: null,
assistantErrorMessage: typeof snapshot['assistantErrorMessage'] === 'string'
? snapshot['assistantErrorMessage']
: null,
lastFailedPrompt: typeof snapshot['lastFailedPrompt'] === 'string'
? snapshot['lastFailedPrompt']
: null,
lastSubmittedPrompt: typeof snapshot['lastSubmittedPrompt'] === 'string'
? snapshot['lastSubmittedPrompt']
: ''
};
}