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(null); protected readonly inferenceInProgress = signal(false); protected readonly recipeUploadForm = this.formBuilder.group({ file: this.formBuilder.control(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'; } }