meals-made-easy-app/src/app/pages/recipe-upload-page/recipe-upload-page.ts
2026-01-11 13:32:46 -06:00

107 lines
3.7 KiB
TypeScript

import { Component, inject, signal } from '@angular/core';
import {
FormBuilder,
FormControl,
FormGroup,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
import { SseClient } from 'ngx-sse-client';
import { Spinner } from '../../shared/components/spinner/spinner';
@Component({
selector: 'app-recipe-upload-page',
imports: [ReactiveFormsModule, Spinner],
templateUrl: './recipe-upload-page.html',
styleUrl: './recipe-upload-page.css',
})
export class RecipeUploadPage {
private readonly sseClient = inject(SseClient);
private readonly formBuilder = inject(FormBuilder);
protected readonly sourceRecipeImage = signal<string | null>(null);
protected readonly inferenceInProgress = signal(false);
protected readonly recipeUploadForm = this.formBuilder.group({
file: this.formBuilder.control<File | null>(null, [Validators.required]),
});
protected readonly recipeForm = new FormGroup({
title: new FormControl('', [Validators.required]),
recipeText: new FormControl('', Validators.required),
});
protected onClear() {
this.recipeUploadForm.reset();
this.sourceRecipeImage.set(null);
}
protected onFileChange(event: Event) {
const fileInput = event.target as HTMLInputElement;
if (fileInput.files && fileInput.files.length) {
const file = fileInput.files[0];
this.recipeUploadForm.controls.file.setValue(file);
this.recipeUploadForm.controls.file.markAsTouched();
this.recipeUploadForm.controls.file.updateValueAndValidity();
// set source image
this.sourceRecipeImage.set(URL.createObjectURL(file));
}
}
protected onFileSubmit() {
const rawValue = this.recipeUploadForm.getRawValue();
this.inferenceInProgress.set(true);
// upload form data
const formData = new FormData();
formData.append('recipeImageFile', rawValue.file!, rawValue.file!.name);
this.sseClient
.stream(
`http://localhost:8080/inferences/recipe-extract-stream`,
{
keepAlive: false,
reconnectionDelay: 1000,
responseType: 'event',
},
{
body: formData,
},
'PUT',
)
.subscribe({
next: (event) => {
if (event.type === 'error') {
const errorEvent = event as ErrorEvent;
console.error(errorEvent.error, errorEvent.message);
} else {
const messageEvent = event as MessageEvent;
const data: { delta: string } = JSON.parse(messageEvent.data);
this.recipeForm.patchValue({
recipeText: this.recipeForm.value.recipeText + data.delta,
});
// must do this so we auto-resize the textarea
document
.getElementById('recipe-text')
?.dispatchEvent(new Event('input', { bubbles: true }));
}
},
complete: () => {
this.inferenceInProgress.set(false);
},
});
}
protected onRecipeSubmit() {
console.log(this.recipeForm.value);
}
protected onRecipeTextChange(event: Event) {
const textarea = event.target as HTMLTextAreaElement;
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
}
}