- Delete unused OpenAI WAF bypass client block (60+ lines) from both monolith
and microservice llmModerationClient.ts
- Replace OpenAI.Chat.Completions.ChatCompletion with type-only ChatCompletion
import from openai/resources/chat/completions
- All LLM chat calls already go through centralized llmClient.ts helper
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Create llmClient.ts (monolith + microservice) with llmChat(), llmVision(), llmDetectBadwords()
- Refactor llmModerationClient.ts: vision and moderation batch calls use llmClient
- Refactor indonesianTextNormalizer.ts: badword detection uses llmClient
- Remove unused withLlmConcurrency and direct openai.chat.completions.create imports
- All LLM config (model, tokens, concurrency, retry) now maintained in one place
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove Groq and NVIDIA fallback from text moderation pipeline
- Remove GROQ_API_KEY, GROQ_MODERATION_*, NVIDIA_NEMOTRON_* config vars
- Update source enum: remove 'groq' and 'nvidia' from cache/schema types
- Rewrite indonesianTextNormalizer to use Primary AI only (no fallback chain)
- Rewrite remote test to test Primary AI only
- Fix stickerCache executeGet missing empty params array
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add fetchRemoteBranchFromGitHub() to query https://api.github.com/repos/MythEclipse/bete for actual default_branch
- Add resolveBranch() tiered resolver: git CLI → GitHub API → env → error
- Async post-startup branch resolution upgrades version from emergency fallback to real remote branch
- normalizeBranchName() for consistent version key formatting
- Emergency fallback 'v1' logged as ERROR with repo context — not a silent dummy
- Removed static env-first approach; env is now tier 3 override, not tier 1
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Create new migration 0001_add_model_version_to_cache.sql to add model_version column to text_analysis_cache table (default 'v1')
- Add composite index on source + model_version for efficient cache lookups
- Improve git branch detection in textCacheStore.ts:
* Check CACHE_MODEL_VERSION env var first (required in Docker)
* Suppress stderr with stdio pipes to eliminate spurious warnings
* Log ERROR (not warning) when git unavailable AND no env var set
* Only fall back to 'v1' as last resort, preventing silent dummy usage
- Remove unused imports (logModelVersionChange, logCacheInvalidation) and previousVersion variable
- Fixes repeated 'column model_version does not exist' errors on bot startup
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- aiAnalyzer.ts: removed unused logAnalysisSummary and logFalsePositiveDetected
- llmModerationClient.ts: removed unused logVisionAnalysis, logVisionError, logRetryAttempt
These functions are not called in the codebase.
Fix#1 (CRITICAL): Wire message_analyzed through Redis EventBroadcaster
- DG aiAnalyzer.ts: Add broadcastAnalysisCompleted() helper that publishes
to both in-memory WS broadcaster AND Redis EventBroadcaster
- DG bootstrap.ts: Pass eventBroadcaster to startPendingAIAnalysisWorker()
- Fixes broken real-time chain so analysis results appear instantly
Fix#2: Collapsible AI Analysis with Rich Formatting
- MessageCard: AI analysis now collapsible with color-coded severity border
(red/yellow/blue), summary line showing categories + confidence + severity
- Default collapsed for clean, expanded for warn/flagged
Fix#3: Toast Notifications for Flagged Content
- Wrap app in ToastProvider; ModerationAlertListener component listens for
moderation_alert custom events and shows toast with emoji + details
Fix#4: Discord-style Message Grouping
- MessageFeed: Group consecutive messages from same user within 5 min
- MessageCard: Compact variant hides avatar, reduces padding for non-first
Fix#5: Moderation Action Buttons in UI
- BE: POST /api/messages/:id/moderate endpoint + publishCommand() for Redis
- FE: Delete/Warn buttons on flagged/warned cards with confirmation dialog
Fix#6: Search Results Include Full Data
- BE analysis.service.ts: SELECT all 26 message columns instead of just 11
- Search results now render with full MessageCard including images/analysis
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sebelumnya: 'Pesan hanya berisi attachment tanpa teks yang melanggar'
Sekarang: harus deskriptif berdasarkan tipe konten:
- Text only: '[user] membahas tentang <topik>. <konteks>.'
- Image only: 'Gambar berupa <jenis>. Terlihat <isi>.'
- Text+Image: '[user] mengirim <gambar> sambil membahas <topik>.'
Tambahkan contoh baik vs buruk di OUTPUT_INSTRUCTIONS sebagai format wajib.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sebelumnya aturan 'percaya teks terlebih dahulu' membuat model abaikan
deskripsi gambar saat teks kosong. Semua image-only message di-clean.
Fix:
- SYSTEM_RULES: pisah Mode 1 (teks+gambar) dan Mode 2 (hanya gambar)
- Mode 2: deskripsi gambar jadi bukti utama, WAJIB dibaca
- Gambar terminal/chat/editor kode/casual → clean
- Gambar dengan elemen judi NYATA (chip, roulette, odds) → flag
- 2 contoh few-shot baru: terminal clean, situs judi flag
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Root cause: vision model diminta untuk 'flag' dan 'menilai' gambar,
sehingga screenshot terminal/chat biasa diklaim sebagai 'situs perjudian'.
Fix:
- vision prompt: HANYA deskripsi objektif (objek, teks, layout, jenis gambar)
- larang tegas kata 'gambling', 'judi', 'pelanggaran', 'harus dihapus'
- tambah buildGeneralImageVisionPrompt di discord-gateway stickerPrompt
- MEDIA_INSTRUCTIONS: tegaskan batch LLM adalah hakim, vision hanya saksi mata
- deskripsi netral (terminal, chat, editor kode) tidak boleh jadi dasar flag
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
- discord-gateway: add redis command handlers for guilds:list, guilds:text-channels, voice:channels
- backend: replace postgres synthetic names with redis commands to gateway (with fallback)
- frontend: remove guild/channel dropdowns from messages and analytics tabs
- frontend: auto-load all channels from monitor guild via guildId query param
- frontend: show guild name in messages/analytics headers instead of selector
- live tab: keeps guild/channel selectors with real discord names
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
- Check if origin mismatch → rm and re-clone fresh
- Public repo — no auth needed for clone
- docker-compose.yml from repo source of truth
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GITHUB_TOKEN can't auth git over HTTPS on VPS, so revert to inline
compose generation. nginx.conf already baked into proxy image.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace build: blocks with image: from ghcr.io
- Simplify deploy: git clone/pull on VPS, then docker compose pull/up
- Remove SCP step entirely — nginx.conf baked into proxy image
- Rename containers to imphenbot-* to match existing VPS setup
- Fix backend port to 3000 for production
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add proxy to build matrix
- Replace heredoc docker-compose generation with appleboy/scp-action
- Fix nginx upstream backend port to 3000 (matches prod WEBSERVER_PORT)
- Copy docker-compose.yml and nginx.conf from repo via SCP staging
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add nginx service with /api, /ws, and / location routing
- Move traefik labels to proxy service only (port 80)
- Remove traefik labels from backend and frontend services
- Backend routes to :3001 via nginx upstream
- WebSocket upgrade enabled for /ws path
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend (real implementations, no more stubs):
- Redis pub/sub bridge: subscribes to discord-gateway events (message/attachment/voice) and broadcasts to WS clients
- Redis command channel: backend publishes voice/media commands, discord-gateway executes and replies
- Voice service: connectVoice/disconnectVoice/getVoiceStatus via Redis commands with graceful fallback
- Media service: queue/skip/stop/volume via Redis commands, reads status from Redis cache
- Messages repository: ALL 7 methods now use real PostgreSQL queries (findMany, findById, findByChannel, create, update, delete, getAttachmentsByChannel)
- Analytics: period returns {start,end} epoch millis, overview includes hourly/topics/top_users, worst_flags as string[]
- Health check: actually queries SELECT 1 against database
- VoiceStatus type fixed: {connected, activeGuildId, activeChannelId, activeChannelName}
- Guild type: includes icon: string | null
- asyncHandler: accepts Promise<unknown> instead of Promise<void>
Discord Gateway:
- CommandHandler: subscribes to 'backend:command' Redis channel, executes voice/media commands, publishes replies
- Publishes voice:status and media:status to Redis for backend caching
- Shutdown handler updated to close command handler
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace stub analytics.repository.ts with real PostgreSQL queries using pg.Pool
- Add /api/guilds, /api/config, /api/auth/login, /api/ui-state (GET/POST)
- Add /api/review, /api/recordings, /api/analysis/search
- Add /api/messages/:id/reanalyze endpoint
- Add /api/analytics/heatmap and /api/analytics/topics
- Implement media routes (stub responses, backend has no Discord voice client)
- Add WebSocket server at /ws with heartbeat and broadcast functions
- Fix analytics route paths to match frontend contract (dual paths for backward compat)
- Export getPool() from database module for raw SQL queries
- Register all new routers in app.ts
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>