feat(T08): Bildretusche via Flux.1-Pro + Preisvergleich + Retouch UI with before/after slider

This commit is contained in:
2026-07-17 09:31:28 +02:00
parent 5cc9f8e9a9
commit f1d6de0e47
16 changed files with 2278 additions and 30 deletions
+93
View File
@@ -0,0 +1,93 @@
import { apiFetch, type PaginatedResponse } from './api';
export interface RetouchResultResponse {
id: string;
vehicle_id?: string | null;
original_file_path: string;
original_file_name: string;
mime_type: string;
retouched_file_path?: string | null;
status: 'pending' | 'processing' | 'completed' | 'failed';
error_message?: string | null;
created_at?: string;
updated_at?: string;
}
export interface RetouchProcessResponse {
message: string;
retouch_id: string;
status: string;
}
export interface ComparableListing {
title: string;
make: string;
model: string;
year?: number | null;
price: number;
mileage_km?: number | null;
location?: string | null;
url?: string | null;
source: string;
}
export interface PriceCompareResponse {
vehicle_id: string;
comparable_listings: ComparableListing[];
average_price: number | null;
listing_count: number;
}
export async function uploadRetouchImage(
file: File,
vehicleId?: string
): Promise<RetouchProcessResponse> {
const formData = new FormData();
formData.append('file', file);
if (vehicleId) {
formData.append('vehicle_id', vehicleId);
}
const token = typeof window !== 'undefined' ? localStorage.getItem('access_token') : null;
const headers: Record<string, string> = {};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1';
const response = await fetch(`${API_BASE_URL}/retouch/process`, {
method: 'POST',
headers,
body: formData,
});
if (!response.ok) {
const err = await response.json().catch(() => ({ error: { code: 'UNKNOWN', message: 'Upload failed' } }));
throw err;
}
return response.json();
}
export async function getRetouchResult(id: string): Promise<RetouchResultResponse> {
return apiFetch<RetouchResultResponse>(`/retouch/results/${id}`);
}
export async function listRetouchResults(
vehicleId?: string,
page = 1,
pageSize = 20
): Promise<PaginatedResponse<RetouchResultResponse>> {
const params = new URLSearchParams();
if (vehicleId) params.set('vehicle_id', vehicleId);
params.set('page', String(page));
params.set('page_size', String(pageSize));
return apiFetch<PaginatedResponse<RetouchResultResponse>>(`/retouch/results?${params.toString()}`);
}
export async function comparePrices(vehicleId: string): Promise<PriceCompareResponse> {
return apiFetch<PriceCompareResponse>('/retouch/price-compare', {
method: 'POST',
body: JSON.stringify({ vehicle_id: vehicleId }),
});
}