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:
@@ -1401,8 +1401,8 @@ export default function AdminPage() {
|
|||||||
<p className="text-warm-brown-light text-sm italic font-lora">Keine Beiträge.</p>
|
<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)
|
// Show all contribution types
|
||||||
let filtered = timelineContributions.filter(c => c.type !== 'timeline')
|
let filtered = [...timelineContributions]
|
||||||
if (contributionFilter === 'review') {
|
if (contributionFilter === 'review') {
|
||||||
filtered = filtered.filter(c => c.status === 'pending' || c.status === 'flagged')
|
filtered = filtered.filter(c => c.status === 'pending' || c.status === 'flagged')
|
||||||
} else if (contributionFilter !== 'all') {
|
} else if (contributionFilter !== 'all') {
|
||||||
|
|||||||
@@ -38,46 +38,45 @@ async function moderateWithAI(contributionId: number, content: string) {
|
|||||||
if (foundBadWord) {
|
if (foundBadWord) {
|
||||||
console.log(`[AI-Mod] ⚠️ INSTANT FLAG: "${foundBadWord}" detected!`)
|
console.log(`[AI-Mod] ⚠️ INSTANT FLAG: "${foundBadWord}" detected!`)
|
||||||
const db = getDb()
|
const db = getDb()
|
||||||
const flagResult = db.prepare(`
|
db.prepare(`
|
||||||
UPDATE contributions
|
UPDATE contributions
|
||||||
SET status = 'flagged', moderation_reason = ?
|
SET status = 'flagged', moderation_reason = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`).run(`Unangemessene Sprache: "${foundBadWord}"`, contributionId)
|
`).run(`Unangemessene Sprache: "${foundBadWord}"`, contributionId)
|
||||||
console.log(`[AI-Mod] ✅ FLAGGED ${contributionId} instantly:`, flagResult.changes, 'rows')
|
console.log(`[AI-Mod] ✅ FLAGGED ${contributionId} instantly`)
|
||||||
return // Done, no AI needed
|
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...`)
|
console.log(`[AI-Mod] No bad words, asking AI...`)
|
||||||
|
|
||||||
try {
|
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}"
|
"${content}"
|
||||||
|
|
||||||
ERLAUBT: Liebe, Vermissen, Trauer
|
ERLAUBT: Liebe, Vermissen, Trauer, Erinnerungen, persönliche Geschichten, Beileidsbekundungen
|
||||||
VERBOTEN: Beleidigungen, Spam, Hassrede
|
VERBOTEN: Beleidigungen, Spam, Hassrede, Werbung, völlig zusammenhanglose oder sinnlose Texte ohne Bezug
|
||||||
|
|
||||||
Antworte NUR mit JSON (keine Erklärung):
|
|
||||||
{"appropriate": true} oder {"appropriate": false, "reason": "..."}
|
|
||||||
|
|
||||||
|
Antworte NUR mit JSON:
|
||||||
|
{"appropriate": true} oder {"appropriate": false, "reason": "kurze Begründung"}
|
||||||
JSON:`
|
JSON:`
|
||||||
|
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
setTimeout(() => controller.abort(), 10000) // 10sec timeout
|
setTimeout(() => controller.abort(), 15000)
|
||||||
|
|
||||||
const res = await fetch('http://localhost:11434/api/generate', {
|
const res = await fetch('http://localhost:11434/api/generate', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: 'qwen3:4b',
|
model: 'llama3.2:latest',
|
||||||
prompt,
|
prompt,
|
||||||
stream: false,
|
stream: false,
|
||||||
options: {
|
options: {
|
||||||
temperature: 0.1,
|
temperature: 0.1,
|
||||||
num_predict: 50,
|
num_predict: 60,
|
||||||
num_ctx: 256
|
num_ctx: 512
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -102,22 +101,25 @@ JSON:`
|
|||||||
console.error(`[AI-Mod] Parse error:`, e)
|
console.error(`[AI-Mod] Parse error:`, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update DB if inappropriate
|
// Flag if inappropriate
|
||||||
if (result && result.appropriate === false) {
|
if (result && result.appropriate === false) {
|
||||||
const db = getDb()
|
const db = getDb()
|
||||||
const updateResult = db.prepare(`
|
db.prepare(`
|
||||||
UPDATE contributions
|
UPDATE contributions
|
||||||
SET status = 'flagged', moderation_reason = ?
|
SET status = 'flagged', moderation_reason = ?
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`).run(result.reason || 'KI-Warnung', contributionId)
|
`).run(result.reason || 'KI-Warnung', contributionId)
|
||||||
|
console.log(`[AI-Mod] ⚠️ FLAGGED ${contributionId}: ${result.reason}`)
|
||||||
console.log(`[AI-Mod] ✅ FLAGGED ${contributionId}:`, updateResult)
|
|
||||||
} else {
|
} else {
|
||||||
console.log(`[AI-Mod] ✅ Passed ${contributionId}`)
|
console.log(`[AI-Mod] ✅ Passed ${contributionId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
} 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()
|
const db = getDb()
|
||||||
|
|
||||||
// 1. Check bad words instantly
|
// 1. Check bad words instantly - clean content is auto-approved
|
||||||
const badWordCheck = hasBadWords(content + ' ' + (title || ''))
|
const badWordCheck = hasBadWords(content + ' ' + (title || ''))
|
||||||
const initialStatus = badWordCheck.flag ? 'flagged' : 'pending'
|
const initialStatus = badWordCheck.flag ? 'flagged' : 'approved'
|
||||||
const moderationReason = badWordCheck.flag ? badWordCheck.reason : null
|
const moderationReason = badWordCheck.flag ? badWordCheck.reason : null
|
||||||
|
|
||||||
// 2. Insert contribution
|
// 2. Insert contribution
|
||||||
|
|||||||
@@ -218,6 +218,9 @@ export default async function HomePage() {
|
|||||||
{/* Photo Upload */}
|
{/* Photo Upload */}
|
||||||
<PhotoUploadSection />
|
<PhotoUploadSection />
|
||||||
|
|
||||||
|
{/* Family Upload */}
|
||||||
|
<FamilyUploadSection />
|
||||||
|
|
||||||
{/* Memories */}
|
{/* Memories */}
|
||||||
<section id="erinnerungen">
|
<section id="erinnerungen">
|
||||||
<MemorySection memories={combinedMemories} />
|
<MemorySection memories={combinedMemories} />
|
||||||
|
|||||||
Reference in New Issue
Block a user