MME-6 Re-introduce ai infer for recipes upload.

This commit is contained in:
Jesse Brault 2026-02-18 13:02:50 -06:00
parent ef49f55598
commit 907c8c34ad
2 changed files with 66 additions and 22 deletions

View File

@ -16,6 +16,7 @@ import { from, map, switchMap, tap } from 'rxjs';
import { Review } from './steps/review/review';
import { RecipeEditFormSubmitEvent } from '../../shared/components/recipe-edit-form/RecipeEditFormSubmitEvent';
import { QueryClient } from '@tanstack/angular-query-experimental';
import { ToastrService } from 'ngx-toastr';
@Component({
selector: 'app-recipe-upload-page',
@ -37,6 +38,7 @@ export class RecipeUploadPage implements OnInit {
private readonly activatedRoute = inject(ActivatedRoute);
private readonly recipeDraftService = inject(RecipeDraftService);
private readonly queryClient = inject(QueryClient);
private readonly toastrService = inject(ToastrService);
private isValidStep(step: number): boolean {
if (this.model().draft?.lastInference || this.model().draft?.state === 'INFER') {
@ -83,7 +85,7 @@ export class RecipeUploadPage implements OnInit {
switchStep: boolean | RecipeUploadStep = false,
): Promise<void> {
this.model.set(model);
this.includeInfer.set(!!model.draft?.lastInference);
this.includeInfer.set(!!model.draft?.lastInference || model.inProgressStep === RecipeUploadStep.INFER);
if (switchStep === true) {
await this.changeDisplayStep(model.inProgressStep);
} else if (typeof switchStep === 'number') {
@ -126,16 +128,30 @@ export class RecipeUploadPage implements OnInit {
const model = await this.recipeDraftService.createManualDraft();
await this.switchModel(model, true);
} else {
this.model.update((model) => ({
...model,
await this.switchModel(
{
...this.model(),
inputSourceFile: this.sourceFile(),
inProgressStep: RecipeUploadStep.INFER,
}));
await this.changeDisplayStep(RecipeUploadStep.INFER);
this.includeInfer.set(true);
this.recipeDraftService.doInference(this.model()).subscribe((updatedModel) => {
this.model.set(updatedModel);
this.changeDisplayStep(RecipeUploadStep.ENTER_DATA);
},
true,
);
this.recipeDraftService
.startInference(this.model())
.pipe(
tap((updated) => {
this.switchModel(updated, true);
}),
switchMap((model) => this.recipeDraftService.pollUntilInferenceComplete(model)),
)
.subscribe({
next: (inferredModel) => {
this.switchModel(inferredModel, true);
},
error: (e) => {
console.error(e);
this.toastrService.error('Error while extracting recipe data');
},
});
}
}

View File

@ -1,6 +1,6 @@
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { delay, firstValueFrom, map, Observable, of } from 'rxjs';
import { first, firstValueFrom, interval, map, Observable, switchMap, takeWhile } from 'rxjs';
import { RecipeUploadClientModel } from '../client-models/RecipeUploadClientModel';
import { RecipeUploadStep } from '../client-models/RecipeUploadStep';
import { RecipeDraftViewModel } from '../models/RecipeDraftView.model';
@ -122,14 +122,42 @@ export class RecipeDraftService {
return firstValueFrom(this.http.delete<void>(this.endpointService.getUrl('recipeDrafts', [id])));
}
public doInference(model: RecipeUploadClientModel): Observable<RecipeUploadClientModel> {
return of({
public startInference(model: RecipeUploadClientModel): Observable<RecipeUploadClientModel> {
const formData = new FormData();
formData.set('sourceFile', model.inputSourceFile!);
formData.set('sourceFileName', model.inputSourceFile!.name);
return this.http
.post<WithStringDates<RecipeDraftViewModel>>(this.endpointService.getUrl('recipeDrafts', ['ai']), formData)
.pipe(
map((rawDraft) => this.hydrateView(rawDraft)),
map((draft) => ({
...model,
inProgressStep: RecipeUploadStep.INFER,
draft,
})),
);
}
public pollUntilInferenceComplete(
model: RecipeUploadClientModel,
pollInterval: number = 5000,
maxPollCount: number = 12,
): Observable<RecipeUploadClientModel> {
return interval(pollInterval).pipe(
takeWhile((pollCount) => pollCount < maxPollCount),
switchMap(() =>
this.http.get<WithStringDates<RecipeDraftViewModel>>(
this.endpointService.getUrl('recipeDrafts', [model.draft!.id]),
),
),
map((rawDraft) => this.hydrateView(rawDraft)),
first((draft) => draft.state === 'ENTER_DATA'),
map((draft) => ({
...model,
inProgressStep: RecipeUploadStep.ENTER_DATA,
id: 16,
inferredTitle: 'Some recipe',
inferredSlug: 'some-recipe',
inferredText: 'Some text.',
inferredIngredients: [],
}).pipe(delay(5_000));
draft,
})),
);
}
}