Fix create-with-AI minimize/resume regression from cancellable-call stub
hasCancellableCall() was hardcoded to false (from an earlier commit that gutted the real async call tracking), which silently blocked minimizeCreateWithAi() from ever minimizing an in-progress AI flow creation into the floating banner, and blocked closeCreateWithAi() from prompting to cancel a running request. restoreSessionForFlow() also never resumed polling for a call that was still QUEUED/RUNNING when its snapshot was saved (e.g. after minimizing and reopening, or a page reload), so a resumed session lost live progress updates and could get stuck showing a stale phase. Restores real hasCancellableCall()/cancelActiveCall() backed by the submitMessage/getCall/cancelCall flow, resumes polling on restore for an active call, and refreshes the session from the backend otherwise. Adds getSession() across the assistant call service stack to support this.
This commit is contained in:
parent
38b01b2bee
commit
3906bf8bd5
|
|
@ -17,6 +17,8 @@ export abstract class AssistantCallServiceBase {
|
|||
|
||||
abstract createSession(request: AssistantSessionRequest): Observable<AssistantSessionState>;
|
||||
|
||||
abstract getSession(sessionId: string): Observable<AssistantSessionState>;
|
||||
|
||||
abstract submitMessage(sessionId: string, request: AssistantSessionMessageRequest): Observable<AssistantCallAccepted>;
|
||||
|
||||
abstract getCall(callId: string): Observable<AssistantCallState>;
|
||||
|
|
|
|||
|
|
@ -10,12 +10,13 @@ import {
|
|||
AssistantSessionState
|
||||
} from '@models/assistant';
|
||||
import { FlowData } from '@models/flow';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { Observable, of, throwError } from 'rxjs';
|
||||
import { AssistantCallServiceBase } from './assistant-call.base';
|
||||
|
||||
export class AssistantCallServiceFake extends AssistantCallServiceBase {
|
||||
private readonly models = ['llama3.1:8b', 'qwen2.5:7b', 'mistral:7b'];
|
||||
private readonly providers = ['InternalOllama', 'OpenAI'];
|
||||
private readonly sessions = new Map<string, AssistantSessionState>();
|
||||
private readonly calls = new Map<string, { sessionId: string; intent: AssistantIntent; result: AssistantFlowActionResult }>();
|
||||
|
||||
override getConfig(): Observable<AssistantConfig> {
|
||||
|
|
@ -52,14 +53,46 @@ export class AssistantCallServiceFake extends AssistantCallServiceBase {
|
|||
lastValidationErrors: [],
|
||||
lastCallId: null
|
||||
};
|
||||
this.sessions.set(session.id, session);
|
||||
return of(structuredClone(session));
|
||||
}
|
||||
|
||||
override getSession(sessionId: string): Observable<AssistantSessionState> {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) {
|
||||
return throwError(() => new Error('Assistant session not found'));
|
||||
}
|
||||
return of(structuredClone(session));
|
||||
}
|
||||
|
||||
override submitMessage(sessionId: string, request: AssistantSessionMessageRequest): Observable<AssistantCallAccepted> {
|
||||
const intent = this.inferIntent(request.message, request.flow);
|
||||
const result = this.buildActionResult(intent, request);
|
||||
const session = this.sessions.get(sessionId);
|
||||
const flow = request.flow ?? session?.currentFlow ?? undefined;
|
||||
const intent = this.inferIntent(request.message, flow);
|
||||
const result = this.buildActionResult(intent, { ...request, flow });
|
||||
const callId = crypto.randomUUID();
|
||||
this.calls.set(callId, { sessionId, intent, result });
|
||||
|
||||
if (session) {
|
||||
session.messages = [
|
||||
...session.messages,
|
||||
{ id: crypto.randomUUID(), role: 'user', content: request.message },
|
||||
{
|
||||
id: crypto.randomUUID(),
|
||||
role: 'assistant',
|
||||
content: result.message,
|
||||
warnings: result.warnings,
|
||||
validationErrors: result.validationErrors
|
||||
}
|
||||
];
|
||||
session.lastCallId = callId;
|
||||
if (result.flow) {
|
||||
session.currentFlow = result.flow;
|
||||
session.currentDraftFlow = result.flow;
|
||||
}
|
||||
session.lastValidationErrors = result.validationErrors;
|
||||
}
|
||||
|
||||
return of({ sessionId, callId });
|
||||
}
|
||||
|
||||
|
|
@ -91,6 +124,13 @@ export class AssistantCallServiceFake extends AssistantCallServiceBase {
|
|||
|
||||
override cancelCall(callId: string): Observable<AssistantCallState> {
|
||||
const call = this.calls.get(callId);
|
||||
const session = call ? this.sessions.get(call.sessionId) : undefined;
|
||||
if (session) {
|
||||
session.messages = [
|
||||
...session.messages,
|
||||
{ id: crypto.randomUUID(), role: 'assistant', content: 'Assistant request cancelled.' }
|
||||
];
|
||||
}
|
||||
return of({
|
||||
id: callId,
|
||||
sessionId: call?.sessionId ?? '',
|
||||
|
|
|
|||
|
|
@ -45,6 +45,12 @@ export class AssistantCallService extends AssistantCallServiceBase {
|
|||
.pipe(map((raw) => mapAssistantSessionState(raw)));
|
||||
}
|
||||
|
||||
override getSession(sessionId: string): Observable<AssistantSessionState> {
|
||||
return this.http
|
||||
.get<unknown>(`${environment.apiUrl}/assistant/sessions/${sessionId}`)
|
||||
.pipe(map((raw) => mapAssistantSessionState(raw)));
|
||||
}
|
||||
|
||||
override submitMessage(sessionId: string, request: AssistantSessionMessageRequest): Observable<AssistantCallAccepted> {
|
||||
return this.http
|
||||
.post<unknown>(`${environment.apiUrl}/assistant/sessions/${sessionId}/messages`, request)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ export class AssistantService {
|
|||
return this.assistantCall.createSession(request);
|
||||
}
|
||||
|
||||
getSession(sessionId: string) {
|
||||
return this.assistantCall.getSession(sessionId);
|
||||
}
|
||||
|
||||
submitMessage(sessionId: string, request: AssistantSessionMessageRequest) {
|
||||
return this.assistantCall.submitMessage(sessionId, request);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -427,12 +427,40 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
=== this.canonicalAssistantErrorContent(FlowAssistant.STANDARD_ASSISTANT_ERROR);
|
||||
}
|
||||
|
||||
hasCancellableCall(): boolean { return false; }
|
||||
hasCancellableCall(): boolean {
|
||||
return this.isActiveCall(this.currentCall());
|
||||
}
|
||||
|
||||
async cancelActiveCall(): Promise<boolean> {
|
||||
const call = this.currentCall();
|
||||
if (!this.isActiveCall(call)) {
|
||||
this.requestPending.set(false);
|
||||
this.createPromptSubmitted.set(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
this.stopPolling();
|
||||
this.requestPending.set(false);
|
||||
this.createPromptSubmitted.set(false);
|
||||
return true;
|
||||
|
||||
try {
|
||||
const cancelledCall = await firstValueFrom(this.assistant.cancelCall(call.id).pipe(take(1)));
|
||||
this.currentCall.set(cancelledCall);
|
||||
this.createPromptSubmitted.set(false);
|
||||
this.persistSnapshot();
|
||||
|
||||
const session = await firstValueFrom(this.assistant.getSession(cancelledCall.sessionId || call.sessionId).pipe(take(1)));
|
||||
this.applySessionState(session, { syncDraftToEditor: false });
|
||||
this.currentCall.set(null);
|
||||
this.persistSnapshot();
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('Assistant call cancel failed', err);
|
||||
this.currentCall.set(call);
|
||||
this.beginPolling(call.id);
|
||||
this.assistantErrorMessage.set('Unable to cancel the assistant request.');
|
||||
this.persistSnapshot();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
clearActiveSnapshot() {
|
||||
|
|
@ -892,23 +920,43 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
this.assistantErrorMessage.set(snapshot.assistantErrorMessage);
|
||||
this.lastFailedPrompt.set(snapshot.lastFailedPrompt);
|
||||
this.lastSubmittedPrompt.set(snapshot.lastSubmittedPrompt);
|
||||
this.createPromptSubmitted.set(false);
|
||||
this.createPromptSubmitted.set(this.isCreateModal() && this.isActiveCall(snapshot.currentCall));
|
||||
this.useDefaultConfiguration.set(snapshot.useDefaultConfiguration);
|
||||
this.selectedProvider.set(snapshot.selectedProvider);
|
||||
this.selectedModel.set(snapshot.selectedModel);
|
||||
this.phaseModels.set(snapshot.phaseModels);
|
||||
this.advancedModelsOpen.set(snapshot.advancedModelsOpen);
|
||||
if (snapshot.sessionState) {
|
||||
this.sessionState.set(snapshot.sessionState);
|
||||
} else {
|
||||
this.sessionState.set(null);
|
||||
}
|
||||
this.sessionState.set(snapshot.sessionState ?? null);
|
||||
|
||||
this.sessionLoading.set(false);
|
||||
if (!this.useDefaultConfiguration() && this.selectedProvider()) {
|
||||
void this.loadProviders();
|
||||
void this.loadModels(this.selectedProvider());
|
||||
}
|
||||
|
||||
if (this.isActiveCall(snapshot.currentCall)) {
|
||||
this.sessionLoading.set(false);
|
||||
this.beginPolling(snapshot.currentCall!.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = snapshot.sessionId ?? snapshot.sessionState?.id ?? null;
|
||||
if (!sessionId) {
|
||||
this.sessionLoading.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.sessionLoading.set(true);
|
||||
this.assistant.getSession(sessionId).pipe(
|
||||
take(1),
|
||||
finalize(() => this.sessionLoading.set(false))
|
||||
).subscribe({
|
||||
next: (session) => {
|
||||
this.applySessionState(session, { clearLocalMessages: false, syncDraftToEditor: false });
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Assistant session refresh failed', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private resolveFlowKey(flowId: string | null | undefined): string {
|
||||
|
|
@ -990,6 +1038,10 @@ export class FlowAssistant implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
private isActiveCall(call: AssistantCallState | null): call is AssistantCallState {
|
||||
return !!call && (call.status === 'QUEUED' || call.status === 'RUNNING');
|
||||
}
|
||||
|
||||
private hasMeaningfulFlow(flow: Flow | null): boolean {
|
||||
if (!flow) return false;
|
||||
const data = flow.data;
|
||||
|
|
|
|||
Loading…
Reference in New Issue