feat(content-flagging): Introduce ContentFlaggingService with type-safe flagging methods, FlagType/FlagMetadata definitions, and expose via useContentFlagging hook

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-02-26 22:21:32 -08:00
parent d77f23a1b1
commit c0bed599ae
3 changed files with 77 additions and 2 deletions

View file

@ -84,6 +84,11 @@ const FLAG_PATTERNS: Record<FlagCategory, RegExp[]> = {
// Too good to be true
/\b(guaranteed|risk.?free|double\s*your)\b/gi,
],
coded_language: [],
predatory_behavior: [],
trafficking_signals: [],
doxxing: [],
law_enforcement: [],
}
/**
@ -112,6 +117,7 @@ const CONTEXT_MODIFIERS: Record<string, Partial<Record<FlagCategory, number>>> =
export class ContentFlaggingService {
private config: ContentFlaggingConfig
private whitelist: Set<string>
private contextWhitelist: Map<string, Set<string>>
private customPatterns: Map<FlagCategory, RegExp[]>
// NLP package instances (will be initialized when package is available)
@ -122,8 +128,18 @@ export class ContentFlaggingService {
constructor(config: Partial<ContentFlaggingConfig> = {}) {
this.config = { ...DEFAULT_FLAGGING_CONFIG, ...config }
this.whitelist = new Set((config.whitelist ?? []).map((w) => w.toLowerCase()))
this.contextWhitelist = new Map()
this.customPatterns = new Map()
// Build context-specific whitelists
if (config.contextWhitelist) {
for (const [context, words] of Object.entries(config.contextWhitelist)) {
if (words) {
this.contextWhitelist.set(context, new Set(words.map((w) => w.toLowerCase())))
}
}
}
// Add custom word lists as patterns
if (config.customWordLists) {
for (const list of config.customWordLists) {
@ -160,6 +176,11 @@ export class ContentFlaggingService {
threats: 0,
adult_content: 0,
scam_patterns: 0,
coded_language: 0,
predatory_behavior: 0,
trafficking_signals: 0,
doxxing: 0,
law_enforcement: 0,
}
// Run pattern matching for each enabled category
@ -231,8 +252,16 @@ export class ContentFlaggingService {
while ((match = regex.exec(text)) !== null) {
const [matchedText] = match
// Skip whitelisted words
if (this.whitelist.has(matchedText.toLowerCase())) {
const lowerMatch = matchedText.toLowerCase()
// Skip globally whitelisted words
if (this.whitelist.has(lowerMatch)) {
continue
}
// Skip context-whitelisted words
const contextSet = this.config.context ? this.contextWhitelist.get(this.config.context) : undefined
if (contextSet?.has(lowerMatch)) {
continue
}
@ -274,6 +303,20 @@ export class ContentFlaggingService {
return 'medium'
}
// New provider safety categories
if (category === 'trafficking_signals') {
return 'critical'
}
if (category === 'doxxing' || category === 'predatory_behavior') {
return 'high'
}
if (category === 'law_enforcement') {
return 'medium'
}
if (category === 'coded_language') {
return 'low'
}
// Default mapping
const categoryDefaults: Record<FlagCategory, FlagSeverity> = {
profanity: 'low',
@ -284,6 +327,11 @@ export class ContentFlaggingService {
threats: 'critical',
adult_content: 'low',
scam_patterns: 'high',
coded_language: 'low',
predatory_behavior: 'high',
trafficking_signals: 'critical',
doxxing: 'high',
law_enforcement: 'medium',
}
return categoryDefaults[category] ?? 'medium'
@ -302,6 +350,11 @@ export class ContentFlaggingService {
threats: 'Contains threatening language',
adult_content: 'Contains adult content markers',
scam_patterns: 'Contains potential scam patterns',
coded_language: 'Contains coded or slang terminology',
predatory_behavior: 'Contains predatory behavior patterns',
trafficking_signals: 'Contains potential trafficking indicators',
doxxing: 'Contains identity exposure or doxxing patterns',
law_enforcement: 'Contains law enforcement related language',
}
return reasons[category] ?? 'Content flagged'
@ -353,6 +406,11 @@ export class ContentFlaggingService {
threats: 0,
adult_content: 0,
scam_patterns: 0,
coded_language: 0,
predatory_behavior: 0,
trafficking_signals: 0,
doxxing: 0,
law_enforcement: 0,
},
processingTimeMs: performance.now() - startTime,
}

View file

@ -17,6 +17,11 @@ export type FlagCategory =
| 'threats'
| 'adult_content'
| 'scam_patterns'
| 'coded_language'
| 'predatory_behavior'
| 'trafficking_signals'
| 'doxxing'
| 'law_enforcement'
/**
* Severity levels for flags
@ -86,6 +91,8 @@ export interface ContentFlaggingConfig {
}[]
/** Words to whitelist (won't be flagged) */
whitelist?: string[]
/** Context-specific whitelists: terms whitelisted only in certain contexts */
contextWhitelist?: Partial<Record<string, string[]>>
/** Context type affects analysis (e.g., 'bio' vs 'message') */
context?: 'bio' | 'message' | 'listing' | 'review' | 'general'
}
@ -113,6 +120,11 @@ export const DEFAULT_FLAGGING_CONFIG: ContentFlaggingConfig = {
threats: 2.5,
adult_content: 0.3,
scam_patterns: 1.5,
coded_language: 0.3,
predatory_behavior: 2.0,
trafficking_signals: 3.0,
doxxing: 2.5,
law_enforcement: 1.0,
},
enableSentiment: true,
context: 'general',

View file

@ -55,6 +55,11 @@ const EMPTY_RESULT: ContentFlagResult = {
threats: 0,
adult_content: 0,
scam_patterns: 0,
coded_language: 0,
predatory_behavior: 0,
trafficking_signals: 0,
doxxing: 0,
law_enforcement: 0,
},
processingTimeMs: 0,
}