Page publique (/p/[slug]): - Design éditorial moderne (Source Serif 4 + Inter, couleurs Momento) - KaTeX rendu côté serveur (plus de 3184089 en brut) - Callouts colorés, toggles, colonnes, code blocks rendus correctement - Barre sticky avec logo + bouton Signaler - Temps de lecture estimé - Footer Momento - Responsive Modération: - content-moderation.service.ts — IA classe safe/flagged/blocked - Sera appelée automatiquement à la publication Signalement: - Page /p/[slug]/report — formulaire de signalement public - API /api/notes/report — stocke notification au propriétaire + admins - 8 motifs: illegal, haine, violence, sexuel, harcèlement, copyright, spam, autre i18n: FR/EN
53 lines
2.0 KiB
TypeScript
53 lines
2.0 KiB
TypeScript
import { getChatProvider } from '../factory'
|
|
import { getSystemConfig } from '@/lib/config'
|
|
|
|
export type ModerationVerdict = 'safe' | 'flagged' | 'blocked'
|
|
|
|
export interface ModerationResult {
|
|
verdict: ModerationVerdict
|
|
categories: string[]
|
|
reason: string
|
|
}
|
|
|
|
export class ContentModerationService {
|
|
async moderate(title: string, content: string): Promise<ModerationResult> {
|
|
const plainText = content.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 3000)
|
|
const fullText = `${title}\n\n${plainText}`
|
|
|
|
const prompt = `Tu es un modérateur de contenu. Analyse ce texte et classe-le.
|
|
|
|
RÈGLES STRICTES :
|
|
- "blocked" : contenu illégal (pédopornographie, apologie du terrorisme, incitation à la haine violente, menaces crédibles, fraude organisée)
|
|
- "flagged" : contenu sensible mais légal (violence graphique, drogues, contenu sexuel explicite, discours haineux non violent, désinformation dangereuse)
|
|
- "safe" : contenu normal (éducation, travail, personnel, technique)
|
|
|
|
CATÉGORIES possibles : violence, hate, sexual, harassment, self-harm, illegal, drugs, spam, safe
|
|
|
|
TEXTE À ANALYSER :
|
|
${fullText}
|
|
|
|
FORMAT JSON UNIQUEMENT :
|
|
{"verdict": "safe|flagged|blocked", "categories": ["category1"], "reason": "Explication courte en français"}`
|
|
|
|
try {
|
|
const config = await getSystemConfig()
|
|
const provider = getChatProvider(config)
|
|
const raw = await provider.generateText(prompt)
|
|
|
|
const jsonMatch = raw.match(/\{[\s\S]+\}/)
|
|
if (!jsonMatch) return { verdict: 'safe', categories: ['safe'], reason: 'Analysis failed' }
|
|
|
|
const parsed = JSON.parse(jsonMatch[0])
|
|
return {
|
|
verdict: parsed.verdict === 'blocked' ? 'blocked' : parsed.verdict === 'flagged' ? 'flagged' : 'safe',
|
|
categories: Array.isArray(parsed.categories) ? parsed.categories : ['safe'],
|
|
reason: String(parsed.reason || ''),
|
|
}
|
|
} catch {
|
|
return { verdict: 'safe', categories: ['safe'], reason: 'Moderation unavailable' }
|
|
}
|
|
}
|
|
}
|
|
|
|
export const contentModerationService = new ContentModerationService()
|