fix: auto-approve clean content, fix AI moderation, show all contributions

- Auto-approve: clean submissions now default to 'approved' (not 'pending')
- Switch AI model from qwen3:4b to llama3.2 (qwen3 writes to thinking field, response always empty)
- AI now correctly flags spam, nonsense, and irrelevant content
- Admin: show all contribution types including timeline (was filtering them out)
- Add FamilyUploadSection to public page (was imported but never rendered)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
denshooter
2026-02-18 12:32:22 +01:00
parent a34d406375
commit 2fb6dd7279
3 changed files with 28 additions and 23 deletions
+2 -2
View File
@@ -1401,8 +1401,8 @@ export default function AdminPage() {
<p className="text-warm-brown-light text-sm italic font-lora">Keine Beiträge.</p>
) : (
(() => {
// Filter contributions - exclude timeline type (those go to timeline section)
let filtered = timelineContributions.filter(c => c.type !== 'timeline')
// Show all contribution types
let filtered = [...timelineContributions]
if (contributionFilter === 'review') {
filtered = filtered.filter(c => c.status === 'pending' || c.status === 'flagged')
} else if (contributionFilter !== 'all') {
+23 -21
View File
@@ -38,46 +38,45 @@ async function moderateWithAI(contributionId: number, content: string) {
if (foundBadWord) {
console.log(`[AI-Mod] ⚠️ INSTANT FLAG: "${foundBadWord}" detected!`)
const db = getDb()
const flagResult = db.prepare(`
db.prepare(`
UPDATE contributions
SET status = 'flagged', moderation_reason = ?
WHERE id = ?
`).run(`Unangemessene Sprache: "${foundBadWord}"`, contributionId)
console.log(`[AI-Mod] ✅ FLAGGED ${contributionId} instantly:`, flagResult.changes, 'rows')
return // Done, no AI needed
console.log(`[AI-Mod] ✅ FLAGGED ${contributionId} instantly`)
return
}
// Step 2: AI check for subtle issues
// Step 2: AI check for subtle issues (irrelevant content, hidden insults etc.)
console.log(`[AI-Mod] No bad words, asking AI...`)
try {
const prompt = `Ist dieser Text unangemessen für eine Gedenkseite?
const prompt = `Ist dieser Text angemessen für eine Gedenkseite einer verstorbenen Großmutter?
"${content}"
ERLAUBT: Liebe, Vermissen, Trauer
VERBOTEN: Beleidigungen, Spam, Hassrede
Antworte NUR mit JSON (keine Erklärung):
{"appropriate": true} oder {"appropriate": false, "reason": "..."}
ERLAUBT: Liebe, Vermissen, Trauer, Erinnerungen, persönliche Geschichten, Beileidsbekundungen
VERBOTEN: Beleidigungen, Spam, Hassrede, Werbung, völlig zusammenhanglose oder sinnlose Texte ohne Bezug
Antworte NUR mit JSON:
{"appropriate": true} oder {"appropriate": false, "reason": "kurze Begründung"}
JSON:`
const controller = new AbortController()
setTimeout(() => controller.abort(), 10000) // 10sec timeout
setTimeout(() => controller.abort(), 15000)
const res = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
signal: controller.signal,
body: JSON.stringify({
model: 'qwen3:4b',
model: 'llama3.2:latest',
prompt,
stream: false,
options: {
temperature: 0.1,
num_predict: 50,
num_ctx: 256
num_predict: 60,
num_ctx: 512
}
})
})
@@ -102,22 +101,25 @@ JSON:`
console.error(`[AI-Mod] Parse error:`, e)
}
// Update DB if inappropriate
// Flag if inappropriate
if (result && result.appropriate === false) {
const db = getDb()
const updateResult = db.prepare(`
db.prepare(`
UPDATE contributions
SET status = 'flagged', moderation_reason = ?
WHERE id = ?
`).run(result.reason || 'KI-Warnung', contributionId)
console.log(`[AI-Mod] ✅ FLAGGED ${contributionId}:`, updateResult)
console.log(`[AI-Mod] ⚠️ FLAGGED ${contributionId}: ${result.reason}`)
} else {
console.log(`[AI-Mod] ✅ Passed ${contributionId}`)
}
} catch (error: any) {
console.error(`[AI-Mod] Error for ${contributionId}:`, error.message)
if (error.name === 'AbortError') {
console.warn(`[AI-Mod] Timeout for ${contributionId}`)
} else {
console.error(`[AI-Mod] Error for ${contributionId}:`, error.message)
}
}
}
@@ -135,9 +137,9 @@ export async function POST(request: Request) {
const db = getDb()
// 1. Check bad words instantly
// 1. Check bad words instantly - clean content is auto-approved
const badWordCheck = hasBadWords(content + ' ' + (title || ''))
const initialStatus = badWordCheck.flag ? 'flagged' : 'pending'
const initialStatus = badWordCheck.flag ? 'flagged' : 'approved'
const moderationReason = badWordCheck.flag ? badWordCheck.reason : null
// 2. Insert contribution
+3
View File
@@ -218,6 +218,9 @@ export default async function HomePage() {
{/* Photo Upload */}
<PhotoUploadSection />
{/* Family Upload */}
<FamilyUploadSection />
{/* Memories */}
<section id="erinnerungen">
<MemorySection memories={combinedMemories} />