TruthCheck Docs
Everything you need to understand, run, and extend TruthCheck v6.1.
What is TruthCheck?
TruthCheck is an AI-powered bilingual fact-checking application. It verifies claims against global evidence using DeepSeek Reasoner and real-time web search (Brave Search primary, DuckDuckGo fallback) — available in English and Spanish.
- ▸Bilingual UI (EN/ES) — full interface and AI output in both languages
- ▸AI Claim Extraction — identifies atomic, verifiable statements from any text
- ▸Parallel Verification — all claims verified simultaneously for minimum wait time
- ▸Progressive Results — cards appear incrementally as each verification completes
- ▸Truth Scoring — 0–5 score with four labels (Likely True, Uncertain, Unverified, Contradicted)
- ▸Source Evidence — up to 4 web search results per claim as clickable source cards
- ▸Real-time Status — progress bar, pending claim feed, elapsed timer, live domain feed
- ▸Copy Result — one-click clipboard export of claim, verdict, reasoning, and sources
- ▸Character Counter — amber/red threshold warnings at 12k/15k chars
- ▸Keyboard Shortcut — Ctrl+Enter / ⌘+Enter to submit
- ▸Dark Mode — follows system preference automatically
- ▸Error Monitoring — Sentry integration for server-side exceptions
Quick Start
Prerequisites
- Node.js 20+
- DeepSeek API key — platform.deepseek.com
- Brave Search API key (optional — required on Vercel/cloud where DuckDuckGo is blocked)
Setup
git clone <repo-url> cd truth-checker npm install # Copy and fill in environment variables cp .env.example .env.local # Required: DEEPSEEK_API_KEY # Optional: BRAVE_SEARCH_API_KEY npm run dev
App available at http://localhost:3000
Common Commands
npm run dev # Start dev server npm run build # Production build npm run start # Start production server npm run lint # ESLint npm test # Run all tests (Vitest) npm run test:watch # Watch mode
Tech Stack
| Layer | Technology |
|---|---|
| Framework | Next.js 16.2.3 (App Router, standalone) |
| AI — Extraction | DeepSeek Chat (deepseek-chat) |
| AI — Reasoning | DeepSeek Reasoner (deepseek-reasoner) |
| Search | Brave Search API (primary) / DuckDuckGo HTML scraper (fallback) |
| Validation | Zod — LLM response schema enforcement |
| Error Monitoring | Sentry (@sentry/nextjs) |
| i18n | Custom lib/i18n.ts + React Context |
| Analytics | Vercel Analytics |
| Testing | Vitest + React Testing Library |
| Styling | Tailwind CSS v4 |
| Icons | Lucide React |
| Reverse Proxy | nginx 1.27 (TLS, rate limiting) |
| Containerization | Docker / Docker Compose |
Security
| Control | Details |
|---|---|
| Rate Limiting | Per-IP sliding window: 5 req/min (/analyze), 10 req/min (/extract), 20 req/min (/verify) |
| Security Headers | CSP, HSTS (1 year + preload), X-Frame-Options: DENY, X-Content-Type-Options, Permissions-Policy |
| Input Validation | 15,000 char limit; claim.id format enforced; empty input rejected |
| Secrets | Hard startup failure if DEEPSEEK_API_KEY is missing — no mock/fallback |
| Network Isolation | Next.js on internal Docker network only; nginx is the sole public entry point |
| TLS | Mozilla Intermediate config — TLS 1.2/1.3, strong ciphers, OCSP stapling |
| Audit Logging | Structured JSON logs on every API request (timestamp, IP, route, payload size, duration, status) |
Architecture
TruthCheck is built on Next.js 16 App Router. All AI logic runs server-side in API routes. The frontend is a single client component (app/page.tsx) that orchestrates the full pipeline.
Analysis Pipeline
- User submits text →
POST /api/extract→ DeepSeek Chat extracts atomic claims - Frontend fans out N parallel
POST /api/verifycalls — one per claim - Each verify call searches Brave Search (DuckDuckGo fallback) → sends claim + evidence to DeepSeek Reasoner
- Results stream in one-by-one; each replaces its skeleton card as it arrives
Key Files
| app/page.tsx | Root client component — UI layout and prop wiring |
| hooks/useAnalyze.ts | handleAnalyze() — pipeline state, fetch orchestration, error handling |
| app/api/extract/route.ts | POST /api/extract — claim extraction via deepseek-chat |
| app/api/verify/route.ts | POST /api/verify — evidence search + reasoning via deepseek-reasoner |
| lib/analyzer.ts | extractClaims() + verifyClaim() — core AI logic with Zod validation |
| lib/search.ts | searchEvidence() — Brave Search primary, DuckDuckGo fallback |
| lib/i18n.ts | Translation dictionary (en/es) + dynamic string helpers |
| lib/i18nContext.tsx | LanguageProvider + useLanguage() hook |
Development Guide
Adding a Translation Key
- Add the English string to
dict.eninlib/i18n.ts - Add the Spanish string to
dict.eswith the same key - Use it in any client component:
const { t } = useLanguage(); t('my_key')
The test suite automatically enforces that dict.en and dict.es have identical keys.
Styling
Tailwind CSS v4. Custom utility classes in app/globals.css:
.glass— glassmorphism with backdrop blur.gradient-text— indigo-to-cyan gradient text.hero-pattern— radial dot grid background
Docker
docker compose up -d --build # Build and start docker compose down # Stop
API Reference
POST/api/extract
Extracts atomic factual claims from a body of text using deepseek-chat.
// Request
{ "text": "...", "language": "en" }
// Response
{ "claims": [{ "id": "claim-0", "text": "...", "originalTextQuote": "..." }] }POST/api/verify
Verifies a single claim via web search + DeepSeek Reasoner. Pass "language": "es" to receive reasoning in Spanish.
// Request
{ "claim": { "id": "claim-0", "text": "...", "originalTextQuote": "..." }, "language": "en" }
// Response
{ "result": { "claimId": "claim-0", "score": 0, "reasoning": "...", "sources": [...] } }Score Interpretation
| Score | Label | Meaning |
|---|---|---|
| 0 | Contradicted | Evidence explicitly contradicts the claim |
| 1 | Unverified | No relevant evidence found |
| 2–3 | Uncertain | Evidence is partial, ambiguous, or inconclusive |
| 4–5 | Likely True | Multiple sources explicitly confirm the claim |