AI backed processing
This commit is contained in:
@@ -15,4 +15,12 @@ export class OcrService {
|
||||
formData.append('file', file);
|
||||
return this.http.post(`${this.apiUrl}/extract`, formData);
|
||||
}
|
||||
|
||||
extractWithAI(text: string, filePath: string | null, modelType: string): Observable<any> {
|
||||
return this.http.post(`http://localhost:8000/api/extract/ai`, { text, file_path: filePath, model_type: modelType });
|
||||
}
|
||||
|
||||
saveDocument(data: any): Observable<any> {
|
||||
return this.http.post(`http://localhost:8000/api/documents/save`, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@ import { FileUploadModule } from 'primeng/fileupload';
|
||||
import { ProgressBarModule } from 'primeng/progressbar';
|
||||
import { InputTextareaModule } from 'primeng/inputtextarea';
|
||||
import { ToastModule } from 'primeng/toast';
|
||||
import { ButtonModule } from 'primeng/button';
|
||||
import { TableModule } from 'primeng/table';
|
||||
import { CardModule } from 'primeng/card';
|
||||
import { RadioButtonModule } from 'primeng/radiobutton';
|
||||
import { InputTextModule } from 'primeng/inputtext';
|
||||
|
||||
@Component({
|
||||
selector: 'app-ocr',
|
||||
@@ -19,16 +24,18 @@ import { ToastModule } from 'primeng/toast';
|
||||
FileUploadModule,
|
||||
ProgressBarModule,
|
||||
InputTextareaModule,
|
||||
ToastModule
|
||||
ToastModule,
|
||||
ButtonModule,
|
||||
TableModule,
|
||||
CardModule,
|
||||
RadioButtonModule,
|
||||
InputTextModule
|
||||
],
|
||||
providers: [MessageService],
|
||||
template: `
|
||||
<div class="card">
|
||||
<h2>OCR Extraction</h2>
|
||||
<!--
|
||||
Note: "customUpload" mode in PrimeNG FileUpload requires "uploadHandler".
|
||||
"mode='advanced'" gives the sleek UI.
|
||||
-->
|
||||
|
||||
<p-fileUpload mode="advanced"
|
||||
chooseLabel="Select PDF or Image"
|
||||
uploadLabel="Extract Text"
|
||||
@@ -44,15 +51,120 @@ import { ToastModule } from 'primeng/toast';
|
||||
<p-progressBar mode="indeterminate" [style]="{'height': '6px'}"></p-progressBar>
|
||||
</div>
|
||||
|
||||
<div class="mt-4" *ngIf="extractedText !== null">
|
||||
<h3>Extracted Text Result:</h3>
|
||||
<textarea pInputTextarea
|
||||
[autoResize]="true"
|
||||
[(ngModel)]="extractedText"
|
||||
readonly
|
||||
class="w-full"
|
||||
style="min-height: 300px; width: 100%; border-color: #d1d5db; font-family: monospace;">
|
||||
</textarea>
|
||||
<div class="mt-4 grid" *ngIf="extractedText !== null">
|
||||
<div class="col-12 md:col-6">
|
||||
<h3>Extracted Text Result:</h3>
|
||||
<textarea pInputTextarea
|
||||
[autoResize]="true"
|
||||
[(ngModel)]="extractedText"
|
||||
readonly
|
||||
class="w-full"
|
||||
style="min-height: 300px; width: 100%; border-color: #d1d5db; font-family: monospace;">
|
||||
</textarea>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="flex flex-column gap-2 mb-3">
|
||||
<label>AI Analysis Mode:</label>
|
||||
<div class="flex align-items-center">
|
||||
<p-radioButton name="model" value="text" [(ngModel)]="modelType" inputId="mod1"></p-radioButton>
|
||||
<label for="mod1" class="ml-2">Text Analysis (Fast - Gemma)</label>
|
||||
</div>
|
||||
<div class="flex align-items-center">
|
||||
<p-radioButton name="model" value="vision" [(ngModel)]="modelType" inputId="mod2"></p-radioButton>
|
||||
<label for="mod2" class="ml-2">Vision Analysis (Accurate - Qwen)</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p-button label="Process with AI"
|
||||
icon="pi pi-bolt"
|
||||
[loading]="aiLoading"
|
||||
(onClick)="processWithAI()">
|
||||
</p-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6" *ngIf="aiResult">
|
||||
<div class="flex justify-content-between align-items-center">
|
||||
<h3>AI Analysis Result:</h3>
|
||||
<p-button label="Save & Verify" icon="pi pi-check" styleClass="p-button-success" [loading]="saveLoading" (onClick)="saveDocument()"></p-button>
|
||||
</div>
|
||||
|
||||
<p-card class="mb-3">
|
||||
<div class="grid">
|
||||
<div class="col-6">
|
||||
<label class="block text-sm font-bold mb-1">Vendor</label>
|
||||
<input pInputText [(ngModel)]="aiResult.vendor_name" class="w-full" />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="block text-sm font-bold mb-1">Date</label>
|
||||
<input pInputText [(ngModel)]="aiResult.date" class="w-full" />
|
||||
</div>
|
||||
<div class="col-6 mt-2">
|
||||
<label class="block text-sm font-bold mb-1">Invoice #</label>
|
||||
<input pInputText [(ngModel)]="aiResult.invoice_number" class="w-full" />
|
||||
</div>
|
||||
<div class="col-6 mt-2">
|
||||
<label class="block text-sm font-bold mb-1">Total</label>
|
||||
<input pInputText [(ngModel)]="aiResult.total_amount" class="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</p-card>
|
||||
|
||||
<p-table [value]="aiResult.line_items" styleClass="p-datatable-sm" [scrollable]="true" scrollHeight="200px">
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th>Qty</th>
|
||||
<th>Price</th>
|
||||
<th>Total</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="body" let-item>
|
||||
<tr>
|
||||
<td pEditableColumn>
|
||||
<p-cellEditor>
|
||||
<ng-template pTemplate="input">
|
||||
<input pInputText type="text" [(ngModel)]="item.description">
|
||||
</ng-template>
|
||||
<ng-template pTemplate="output">
|
||||
{{item.description}}
|
||||
</ng-template>
|
||||
</p-cellEditor>
|
||||
</td>
|
||||
<td pEditableColumn>
|
||||
<p-cellEditor>
|
||||
<ng-template pTemplate="input">
|
||||
<input pInputText type="text" [(ngModel)]="item.quantity">
|
||||
</ng-template>
|
||||
<ng-template pTemplate="output">
|
||||
{{item.quantity}}
|
||||
</ng-template>
|
||||
</p-cellEditor>
|
||||
</td>
|
||||
<td pEditableColumn>
|
||||
<p-cellEditor>
|
||||
<ng-template pTemplate="input">
|
||||
<input pInputText type="text" [(ngModel)]="item.unit_price">
|
||||
</ng-template>
|
||||
<ng-template pTemplate="output">
|
||||
{{item.unit_price}}
|
||||
</ng-template>
|
||||
</p-cellEditor>
|
||||
</td>
|
||||
<td pEditableColumn>
|
||||
<p-cellEditor>
|
||||
<ng-template pTemplate="input">
|
||||
<input pInputText type="text" [(ngModel)]="item.total">
|
||||
</ng-template>
|
||||
<ng-template pTemplate="output">
|
||||
{{item.total}}
|
||||
</ng-template>
|
||||
</p-cellEditor>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
||||
</div>
|
||||
</div>
|
||||
<p-toast></p-toast>
|
||||
</div>
|
||||
@@ -65,16 +177,27 @@ import { ToastModule } from 'primeng/toast';
|
||||
export class OcrComponent {
|
||||
extractedText: string | null = null;
|
||||
loading: boolean = false;
|
||||
|
||||
aiLoading: boolean = false;
|
||||
saveLoading: boolean = false;
|
||||
aiResult: any = null;
|
||||
|
||||
// Hybrid AI Props
|
||||
modelType: string = 'text';
|
||||
filePath: string | null = null;
|
||||
|
||||
constructor(private ocrService: OcrService, private messageService: MessageService) {}
|
||||
|
||||
onUpload(event: any) {
|
||||
this.loading = true;
|
||||
this.aiResult = null; // Reset AI result on new upload
|
||||
this.filePath = null;
|
||||
const file = event.files[0];
|
||||
|
||||
this.ocrService.extractText(file).subscribe({
|
||||
next: (res) => {
|
||||
this.extractedText = res.text;
|
||||
this.filePath = res.file_path;
|
||||
this.loading = false;
|
||||
this.messageService.add({severity:'success', summary:'Success', detail:'Text Extracted Successfully'});
|
||||
},
|
||||
@@ -88,5 +211,50 @@ export class OcrComponent {
|
||||
|
||||
onClear() {
|
||||
this.extractedText = null;
|
||||
this.aiResult = null;
|
||||
this.filePath = null;
|
||||
}
|
||||
|
||||
processWithAI() {
|
||||
if (!this.extractedText) return;
|
||||
|
||||
this.aiLoading = true;
|
||||
// Pass text, filePath, and modelType
|
||||
this.ocrService.extractWithAI(this.extractedText, this.filePath, this.modelType).subscribe({
|
||||
next: (res) => {
|
||||
this.aiResult = res;
|
||||
this.aiLoading = false;
|
||||
this.messageService.add({severity:'success', summary:'AI Processing Complete', detail:'Data Extracted'});
|
||||
},
|
||||
error: (err) => {
|
||||
console.error(err);
|
||||
this.aiLoading = false;
|
||||
this.messageService.add({severity:'error', summary:'AI Error', detail:'Could not process with AI'});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
saveDocument() {
|
||||
if (!this.aiResult || !this.filePath) return;
|
||||
|
||||
this.saveLoading = true;
|
||||
const payload = {
|
||||
vendor_name: this.aiResult.vendor_name || 'Unknown Vendor',
|
||||
file_path: this.filePath,
|
||||
model_type: this.modelType,
|
||||
data: this.aiResult
|
||||
};
|
||||
|
||||
this.ocrService.saveDocument(payload).subscribe({
|
||||
next: (res) => {
|
||||
this.saveLoading = false;
|
||||
this.messageService.add({severity:'success', summary:'Saved & Verified', detail:'Document and rules saved'});
|
||||
},
|
||||
error: (err) => {
|
||||
console.error(err);
|
||||
this.saveLoading = false;
|
||||
this.messageService.add({severity:'error', summary:'Save Error', detail:'Failed to save document'});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user