Introcuced a Free LLM for easier food input

This commit is contained in:
2026-07-30 09:06:25 +02:00
parent 5196ff0194
commit c40a142da2
8 changed files with 272 additions and 7 deletions
@@ -29,11 +29,51 @@ export default function ProductNotFoundForm({
const [mealType, setMealType] = useState(initialMealType || defaultMealType())
const [error, setError] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const [description, setDescription] = useState('')
const [isEstimating, setIsEstimating] = useState(false)
const [estimateError, setEstimateError] = useState('')
const [wasEstimated, setWasEstimated] = useState(false)
function optionalDecimal(value) {
return value === '' ? undefined : parseDecimal(value)
}
function formatEstimate(value) {
return String(Math.round(value * 10) / 10)
}
async function handleEstimate() {
if (description.trim().length < 2) return
setEstimateError('')
setIsEstimating(true)
try {
const { data } = await apiClient.post('/api/products/estimate', {
description: description.trim(),
})
setName(data.name)
setCalories(formatEstimate(data.calories))
setCarbs(formatEstimate(data.carbs))
setProtein(formatEstimate(data.protein))
setFat(formatEstimate(data.fat))
setSugar(formatEstimate(data.sugar))
setFiber(formatEstimate(data.fiber))
setSaturatedFat(formatEstimate(data.saturated_fat))
setSalt(formatEstimate(data.salt))
if (mode === 'log') {
setAmountG(formatEstimate(data.portion_g))
}
setWasEstimated(true)
} catch (err) {
setEstimateError(
err.response?.status === 503
? 'KI-Schätzung gerade nicht verfügbar.'
: 'Schätzung fehlgeschlagen. Bitte erneut versuchen.',
)
} finally {
setIsEstimating(false)
}
}
async function handleSubmit(e) {
e.preventDefault()
setError('')
@@ -72,7 +112,7 @@ export default function ProductNotFoundForm({
return (
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/50 sm:items-center">
<div className="w-full max-w-sm rounded-t-2xl bg-white p-6 sm:rounded-2xl dark:bg-gray-800">
<div className="max-h-[90vh] w-full max-w-sm overflow-y-auto rounded-t-2xl bg-white p-6 sm:rounded-2xl dark:bg-gray-800">
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-50">
{barcode ? 'Produkt nicht gefunden' : 'Neues Produkt anlegen'}
</h2>
@@ -82,6 +122,42 @@ export default function ProductNotFoundForm({
: 'Bitte Nährwerte pro 100g eintragen'}
</p>
<div className="mb-4 rounded-lg border border-dashed border-gray-300 p-3 dark:border-gray-600">
<label className={labelClass}>Nährwerte per KI schätzen (optional)</label>
<div className="flex gap-2">
<input
type="text"
value={description}
onChange={(e) => setDescription(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault()
handleEstimate()
}
}}
placeholder="z. B. Döner mit allem"
disabled={isEstimating}
className="min-w-0 flex-1 rounded-lg border border-gray-300 px-3 py-2 text-base text-gray-900 focus:border-emerald-500 focus:outline-none disabled:opacity-60 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-50"
/>
<button
type="button"
onClick={handleEstimate}
disabled={isEstimating || description.trim().length < 2}
className="shrink-0 rounded-lg bg-emerald-600 px-3 py-2 text-sm font-medium text-white active:bg-emerald-700 disabled:opacity-60"
>
{isEstimating ? '…' : 'Schätzen'}
</button>
</div>
{estimateError && (
<p className="mt-2 text-sm text-red-600 dark:text-red-400">{estimateError}</p>
)}
{wasEstimated && !estimateError && (
<p className="mt-2 text-xs text-amber-700 dark:text-amber-400">
Geschätzte Werte — bitte prüfen und bei Bedarf korrigieren.
</p>
)}
</div>
<form onSubmit={handleSubmit} className="space-y-3">
<div>
<label className={labelClass}>Name</label>