From 6e2ef55ceef266cac0fbe707205ed35f22b8ed71 Mon Sep 17 00:00:00 2001 From: denshooter Date: Sat, 21 Feb 2026 20:37:14 +0100 Subject: [PATCH] Fix candle layout, relax AI moderation, fix ReadableStream error - Candles: clean grid layout with consistent sizing, no overlapping - AI moderation: much more lenient prompt - short descriptions, dates, locations are all valid memorial content - Fix ReadableStream error by clearing abort timeout after response Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/app/api/contributions/route.ts | 25 ++++++++++--- src/app/api/moderate/route.ts | 22 +++++------ src/components/CandleSection.tsx | 59 ++++++++++++++---------------- 3 files changed, 58 insertions(+), 48 deletions(-) diff --git a/src/app/api/contributions/route.ts b/src/app/api/contributions/route.ts index db1e317..d065138 100644 --- a/src/app/api/contributions/route.ts +++ b/src/app/api/contributions/route.ts @@ -51,19 +51,32 @@ async function moderateWithAI(contributionId: number, content: string) { console.log(`[AI-Mod] No bad words, asking AI...`) try { - const prompt = `Ist dieser Text angemessen für eine Gedenkseite einer verstorbenen Großmutter? + const prompt = `Du prüfst Beiträge für eine Gedenkseite einer verstorbenen Großmutter (Maria Malejka). -"${content}" +Text: "${content}" -ERLAUBT: Liebe, Vermissen, Trauer, Erinnerungen, persönliche Geschichten, Beileidsbekundungen -VERBOTEN: Beleidigungen, Spam, Hassrede, Werbung, völlig zusammenhanglose oder sinnlose Texte ohne Bezug +WICHTIG: Sei SEHR großzügig! Die meisten Beiträge sind von Familienmitgliedern und Freunden. + +ERLAUBT (immer appropriate=true): +- Kurze Beschreibungen wie "Hochzeit", "Geburtstag", "Urlaub in..." - das sind Erinnerungen! +- Namen, Orte, Daten - das sind Zeitstrahl-Einträge +- Alles was eine Erinnerung, ein Ereignis oder ein Lebensmoment sein könnte +- Persönliche Geschichten, Beileidsbekundungen, Liebe, Vermissen, Trauer +- Auch sehr kurze Texte oder einzelne Wörter sind OK wenn sie ein Ereignis beschreiben + +NUR VERBOTEN (appropriate=false): +- Beleidigungen, Hassrede, Schimpfwörter +- Offensichtlicher Spam oder Werbung mit Links +- Komplett sinnloser Text (zufällige Buchstaben, Tastatur-Spam) + +Im Zweifel: appropriate=true Antworte NUR mit JSON: {"appropriate": true} oder {"appropriate": false, "reason": "kurze Begründung"} JSON:` const controller = new AbortController() - setTimeout(() => controller.abort(), 15000) + const timeout = setTimeout(() => controller.abort(), 15000) const ollamaUrl = process.env.OLLAMA_URL || 'http://localhost:11434' const res = await fetch(`${ollamaUrl}/api/generate`, { @@ -83,11 +96,13 @@ JSON:` }) if (!res.ok) { + clearTimeout(timeout) console.warn(`[AI-Mod] Ollama error: ${res.status}`) return } const data = await res.json() + clearTimeout(timeout) const responseText = (data.response || '').trim() console.log(`[AI-Mod] Response: "${responseText}"`) diff --git a/src/app/api/moderate/route.ts b/src/app/api/moderate/route.ts index 5b2abca..4efdd87 100644 --- a/src/app/api/moderate/route.ts +++ b/src/app/api/moderate/route.ts @@ -21,23 +21,23 @@ export async function POST(req: NextRequest) { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'llama3.2:1b', - prompt: `Du bist ein Moderator für eine Gedenkseite. Prüfe, ob dieser Beitrag angemessen ist. + prompt: `Du prüfst Beiträge für eine Gedenkseite einer verstorbenen Großmutter (Maria Malejka). Sei SEHR großzügig! Name: ${name || 'Anonym'} Titel: ${title || 'Kein Titel'} Text: "${text}" -Unangemessen sind: -- Spam, Werbung, Links zu Produkten -- Beleidigungen, Hassrede -- Völlig irrelevanter Inhalt -- Unseriöse oder respektlose Inhalte +ERLAUBT (immer appropriate=true): +- Kurze Beschreibungen, Namen, Orte, Daten - das sind Erinnerungen! +- Persönliche Geschichten, Beileidsbekundungen, Liebe, Vermissen, Trauer +- Auch sehr kurze oder allgemeine Texte sind OK -Angemessen sind: -- Erinnerungen, Anekdoten -- Kondolenzen, Beileidsbekundungen -- Persönliche Geschichten -- Emotionale oder traurige Texte +NUR VERBOTEN (appropriate=false): +- Beleidigungen, Hassrede, Schimpfwörter +- Offensichtlicher Spam oder Werbung mit Links +- Komplett sinnloser Text (Tastatur-Spam) + +Im Zweifel: appropriate=true Antworte NUR mit einem JSON-Objekt: { diff --git a/src/components/CandleSection.tsx b/src/components/CandleSection.tsx index afc3e88..9193bef 100644 --- a/src/components/CandleSection.tsx +++ b/src/components/CandleSection.tsx @@ -89,23 +89,20 @@ function CandleFlame({ size = 1, delay = 0 }: { size?: number; delay?: number }) } function SingleCandle({ candle, index }: { candle: CandleData; index: number }) { - // Generate consistent but varied properties based on candle ID - const seed = candle.id * 7919 // Prime number for good distribution - const sizeVariant = ((seed % 5) / 4) * 0.6 + 0.7 // 0.7 to 1.3 - const heightVariant = ((seed % 7) / 6) * 0.5 + 0.75 // 0.75 to 1.25 - const hueShift = (seed % 20) - 10 // -10 to +10 hue shift - const brightnessShift = ((seed % 15) - 7) / 100 // -0.07 to +0.08 brightness - const rotation = ((seed % 11) - 5) / 2 // -2.5 to +2.5 degrees + const seed = candle.id * 7919 + const heightVariant = ((seed % 7) / 6) * 0.3 + 0.85 // 0.85 to 1.15 (subtler) + const hueShift = (seed % 14) - 7 // -7 to +7 + const brightnessShift = ((seed % 11) - 5) / 100 + const rotation = ((seed % 7) - 3) / 3 // -1 to +1 degrees (subtler) - // Calculate burn-down based on age const createdTime = new Date(candle.created_at + 'Z').getTime() const now = Date.now() const ageInHours = (now - createdTime) / (1000 * 60 * 60) - const burnProgress = Math.min(ageInHours / 24, 0.4) // Burns down max 40% over 24 hours + const burnProgress = Math.min(ageInHours / 24, 0.4) - const candleHeight = 80 * heightVariant * (1 - burnProgress) - const candleWidth = 36 * sizeVariant - const flameSize = 1.0 * sizeVariant + const candleHeight = 72 * heightVariant * (1 - burnProgress) + const candleWidth = 32 + const flameSize = 0.95 const delay = (index % 7) * 0.15 return ( @@ -114,12 +111,12 @@ function SingleCandle({ candle, index }: { candle: CandleData; index: number }) animate={{ opacity: 1, y: 0, scale: 1 }} transition={{ duration: 0.6, delay: Math.min(index * 0.08, 1.2) }} style={{ - display: 'inline-flex', + display: 'flex', flexDirection: 'column', alignItems: 'center', transform: `rotate(${rotation}deg)`, filter: `brightness(${1 + brightnessShift})`, - marginLeft: index > 0 ? '-8px' : '0', // Slight overlap for natural clustering + width: 72, }} className="group relative" > @@ -141,7 +138,7 @@ function SingleCandle({ candle, index }: { candle: CandleData; index: number })
- {/* Wax drips - more visible */} + {/* Wax drips */}
- {/* Additional smaller drip for realism */}
@@ -217,20 +213,19 @@ function SingleCandle({ candle, index }: { candle: CandleData; index: number }) {/* Name Label */}

{candle.name}

@@ -512,7 +507,7 @@ export default function CandleSection() { {/* Candle Grid - with better spacing for many candles */} {candles.length > 0 && ( -

+
{candles.map((candle, i) => ( ))}