From a79c75513575ad4d019ea534fa39ff1f2742e352 Mon Sep 17 00:00:00 2001 From: DHARAVATH SATVIK <96382122+Gitcoder12@users.noreply.github.com> Date: Thu, 9 Jul 2026 19:35:27 +0530 Subject: [PATCH] Build React frontend for TruthStream --- .gitignore | 14 + README.md | 343 +++++++++++--------- frontend/README.md | 32 ++ frontend/eslint.config.js | 26 ++ frontend/index.html | 13 + frontend/package.json | 34 ++ frontend/postcss.config.js | 6 + frontend/src/App.tsx | 57 ++++ frontend/src/components/ConfidenceMeter.tsx | 31 ++ frontend/src/components/EvidenceCard.tsx | 34 ++ frontend/src/components/Layout.tsx | 48 +++ frontend/src/components/Link.tsx | 23 ++ frontend/src/components/ResultPanel.tsx | 56 ++++ frontend/src/lib/api.ts | 25 ++ frontend/src/lib/format.ts | 18 + frontend/src/lib/storage.ts | 44 +++ frontend/src/main.tsx | 10 + frontend/src/pages/About.tsx | 31 ++ frontend/src/pages/History.tsx | 44 +++ frontend/src/pages/Home.tsx | 47 +++ frontend/src/pages/Settings.tsx | 53 +++ frontend/src/pages/Verify.tsx | 87 +++++ frontend/src/styles.css | 20 ++ frontend/src/types/api.ts | 57 ++++ frontend/tailwind.config.js | 19 ++ frontend/tsconfig.json | 21 ++ frontend/tsconfig.node.json | 9 + frontend/vite.config.ts | 16 + pyproject.toml | 41 +++ src/truthstream/__init__.py | 5 + src/truthstream/api.py | 51 +++ src/truthstream/cli.py | 44 +++ src/truthstream/evidence.py | 131 ++++++++ src/truthstream/extraction.py | 53 +++ src/truthstream/models.py | 109 +++++++ src/truthstream/pipeline.py | 43 +++ src/truthstream/providers.py | 226 +++++++++++++ src/truthstream/scoring.py | 67 ++++ src/truthstream/settings.py | 27 ++ tests/test_api.py | 61 ++++ tests/test_pipeline.py | 22 ++ tests/test_providers.py | 119 +++++++ 42 files changed, 2059 insertions(+), 158 deletions(-) create mode 100644 .gitignore create mode 100644 frontend/README.md create mode 100644 frontend/eslint.config.js create mode 100644 frontend/index.html create mode 100644 frontend/package.json create mode 100644 frontend/postcss.config.js create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/components/ConfidenceMeter.tsx create mode 100644 frontend/src/components/EvidenceCard.tsx create mode 100644 frontend/src/components/Layout.tsx create mode 100644 frontend/src/components/Link.tsx create mode 100644 frontend/src/components/ResultPanel.tsx create mode 100644 frontend/src/lib/api.ts create mode 100644 frontend/src/lib/format.ts create mode 100644 frontend/src/lib/storage.ts create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/pages/About.tsx create mode 100644 frontend/src/pages/History.tsx create mode 100644 frontend/src/pages/Home.tsx create mode 100644 frontend/src/pages/Settings.tsx create mode 100644 frontend/src/pages/Verify.tsx create mode 100644 frontend/src/styles.css create mode 100644 frontend/src/types/api.ts create mode 100644 frontend/tailwind.config.js create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 pyproject.toml create mode 100644 src/truthstream/__init__.py create mode 100644 src/truthstream/api.py create mode 100644 src/truthstream/cli.py create mode 100644 src/truthstream/evidence.py create mode 100644 src/truthstream/extraction.py create mode 100644 src/truthstream/models.py create mode 100644 src/truthstream/pipeline.py create mode 100644 src/truthstream/providers.py create mode 100644 src/truthstream/scoring.py create mode 100644 src/truthstream/settings.py create mode 100644 tests/test_api.py create mode 100644 tests/test_pipeline.py create mode 100644 tests/test_providers.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b289079 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +__pycache__/ +*.py[cod] +.pytest_cache/ +.ruff_cache/ +.venv/ +dist/ +build/ +*.egg-info/ +# Frontend +frontend/node_modules/ +frontend/dist/ +frontend/*.tsbuildinfo +frontend/vite.config.js +frontend/vite.config.d.ts diff --git a/README.md b/README.md index ac9d1e4..298121a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ **Real-time claim verification and evidence analysis for streaming information.** -TruthStream continuously monitors incoming information streams, extracts factual claims, gathers supporting and contradicting evidence from trusted sources, and generates confidence scores with transparent explanations. +TruthStream continuously monitors incoming information streams, extracts factual claims, +gathers supporting and contradicting evidence from trusted sources, and generates confidence +scores with transparent explanations. Instead of asking: @@ -12,180 +14,205 @@ TruthStream asks: > "What evidence supports this claim, and how strong is that evidence?" ---- +## Current State -## Why TruthStream? - -Information spreads faster than verification. - -News articles, social media posts, blogs, and public announcements can reach millions of people before fact-checkers have time to investigate. - -TruthStream helps bridge this gap by providing automated, evidence-based verification for information as it appears. - ---- - -## Features - -### Real-Time Stream Processing - -Monitor live information sources including: - -* News feeds -* Social media streams -* Public announcements -* Government releases -* Research publications - -### Claim Extraction - -Automatically identifies factual claims from incoming text using Natural Language Processing (NLP). - -### Evidence Retrieval - -Searches trusted sources for supporting and contradicting information. +TruthStream is now an MVP product foundation. It includes: -### Confidence Scoring +- A deterministic claim extraction pipeline. +- A pluggable evidence retrieval interface with demo and real provider modes. +- Real evidence providers for Wikipedia, Crossref, and OpenAlex with in-memory request caching. +- Evidence-weighted confidence scoring with structured evidence summaries. +- A FastAPI service with `/health`, `/verify`, and `/verify/batch` endpoints. +- A CLI for local claim verification with JSON, stdin, and pretty-print output modes. +- A React + TypeScript + Tailwind frontend with Home, Verify, History, About, and Settings pages. +- Automated tests for the core pipeline and API. -Assigns a confidence score based on: +The MVP intentionally avoids external model or search dependencies so it can run locally and in CI. +Future implementations can replace the extractor and retriever with NLP models, search APIs, +stream processors, and persistent storage without changing the public API contract. -* Source credibility -* Evidence consistency -* Number of supporting sources -* Number of contradicting sources -* Information recency - -### Explainable Results - -Provides transparent reasoning instead of a simple true/false label. - -Example: - -Claim: -"Country X has banned AI systems." - -Result: - -* Confidence Score: 12% -* Supporting Sources: 0 -* Contradicting Sources: 5 -* Status: Likely False - -Reason: -No official government announcement found. Multiple trusted news organizations report no such policy. - -### Analytics Dashboard - -Visualize: +## Why TruthStream? -* Trending claims -* Verification status -* Evidence graphs -* Misinformation spikes -* Source reliability metrics +Information spreads faster than verification. ---- +News articles, social media posts, blogs, and public announcements can reach millions of people +before fact-checkers have time to investigate. TruthStream helps bridge this gap by providing +automated, evidence-based verification for information as it appears. -## System Architecture +## MVP Architecture -Incoming Stream -↓ +```text +Incoming Text + ↓ Claim Extraction -↓ + ↓ Evidence Retrieval -↓ -Source Evaluation -↓ + ↓ Confidence Scoring -↓ + ↓ Explanation Generation -↓ -Dashboard & Alerts - ---- - -## Use Cases - -### Misinformation Detection - -Identify potentially misleading claims before they spread widely. - -### Journalism - -Assist reporters with rapid claim verification. - -### Research - -Study information propagation and verification dynamics. - -### Public Transparency - -Provide evidence-based verification of public statements and announcements. - -### Open Source Intelligence (OSINT) - -Track and evaluate claims across multiple public information channels. - ---- - -## Technology Stack - -Frontend: - -* React -* TypeScript -* Tailwind CSS - -Backend: - -* Python -* FastAPI - -Data Streaming: - -* Apache Kafka -* Apache Flink - -AI & NLP: - -* DeepSeek/OpenAI -* Sentence Transformers -* Named Entity Recognition -* Claim Extraction Models - -Storage: - -* PostgreSQL -* Redis - -Visualization: - -* Chart.js -* D3.js - ---- - -## Future Roadmap - -* Multi-language verification -* Real-time misinformation alerts -* Knowledge graph integration -* Cross-source contradiction detection -* Source credibility learning system -* Public API -* Browser extension -* Research paper support - ---- + ↓ +API / CLI Output +``` -## Research Vision +Core modules: -TruthStream is not designed to determine absolute truth. +- `truthstream.extraction`: deterministic claim and entity extraction heuristics. +- `truthstream.evidence`: retriever protocols, composite retrieval, and demo fallback retriever. +- `truthstream.providers`: cached Wikipedia, Crossref, and OpenAlex provider integrations. +- `truthstream.settings`: runtime selection for demo or real evidence modes. +- `truthstream.scoring`: source-credibility and recency weighted confidence scoring. +- `truthstream.pipeline`: orchestration layer that composes extraction, retrieval, scoring, and explanation. +- `truthstream.api`: FastAPI application. +- `truthstream.cli`: command-line entrypoint. -Its goal is to provide transparent, evidence-based confidence assessments by continuously analyzing information from multiple independent sources. +## Quickstart -The focus is on verifiability, evidence strength, and explainability rather than binary judgments. +### 1. Create a virtual environment ---- +```bash +python -m venv .venv +source .venv/bin/activate +``` + +### 2. Install dependencies + +```bash +pip install -e '.[dev]' +``` + +### 3. Run tests + +```bash +pytest +ruff check . +``` + +### 4. Run the API + +```bash +uvicorn truthstream.api:app --reload +``` + +By default the API uses deterministic demo evidence for offline development. To enable real +Wikipedia, Crossref, and OpenAlex providers, set: + +```bash +TRUTHSTREAM_EVIDENCE_MODE=real uvicorn truthstream.api:app --reload +``` + +Then verify text: + +```bash +curl -X POST http://127.0.0.1:8000/verify \ + -H 'Content-Type: application/json' \ + -d '{"text":"Country X has banned AI systems."}' +``` + +Batch verification is also available: + +```bash +curl -X POST http://127.0.0.1:8000/verify/batch \ + -H 'Content-Type: application/json' \ + -d '[{"text":"Country X has banned AI systems."},{"text":"The moon has a new public library."}]' +``` + +### 5. Run the frontend + +```bash +cd frontend +npm install +npm run dev +``` + +Open http://127.0.0.1:5173. The Vite dev server proxies `/api` requests to the FastAPI backend by default. + +### 6. Run the CLI + +```bash +truthstream "Country X has banned AI systems." +truthstream --pretty "Country X has banned AI systems." +truthstream --evidence real --pretty "Recent research on multilingual misinformation is rising." +echo "Country X has banned AI systems." | truthstream --pretty +``` + +## Frontend + +The frontend lives in `frontend/` and is built with React, TypeScript, Vite, and Tailwind CSS. +It connects to the existing backend API instead of replacing it. The Verify page posts claims to +`/verify`, displays loading and error states, renders a confidence meter, separates supporting, +contradicting, and neutral evidence, links to sources, and stores optional local history. + +## Evidence Provider System + +TruthStream now separates retrieval orchestration from source-specific integrations: + +- `EvidenceProvider` defines the provider contract. +- `CachedEvidenceProvider` adds TTL caching around any provider. +- `CompositeEvidenceRetriever` queries multiple providers and keeps working if one provider fails. +- `WikipediaEvidenceProvider` retrieves encyclopedia search results. +- `CrossrefEvidenceProvider` retrieves scholarly work metadata. +- `OpenAlexEvidenceProvider` retrieves open scholarly graph results. + +Remote provider evidence is returned as neutral evidence candidates. This keeps retrieval +deterministic and leaves support/contradiction classification to the verification/ranking layer. +The original in-memory retriever remains available for tests, demos, and offline development. + +## Example API Response + +```json +[ + { + "claim": { + "text": "Country X has banned AI systems.", + "entities": ["Country X", "AI"] + }, + "confidence_score": 0.0, + "status": "likely_false", + "supporting_evidence": [], + "contradicting_evidence": [ + { + "source": { + "name": "Official Government Registry", + "url": null, + "credibility": 0.95 + }, + "title": "No nationwide AI ban has been issued", + "snippet": "The registry lists AI safety guidance but no blanket ban on AI systems.", + "stance": "contradicts", + "recency_score": 1.0 + } + ], + "neutral_evidence": [], + "explanation": "Confidence is 0% with stronger contradicting evidence, based on 0 supporting item(s) from 0 source(s) and 2 contradicting item(s) from 2 source(s).", + "evidence_summary": { + "supporting_count": 0, + "contradicting_count": 2, + "neutral_count": 0, + "supporting_weight": 0, + "contradicting_weight": 1.724 + } + } +] +``` + +## Long-Term Vision + +TruthStream is not designed to determine absolute truth. Its goal is to provide transparent, +evidence-based confidence assessments by continuously analyzing information from multiple +independent sources. + +Planned capabilities include: + +- Multi-language verification. +- Real-time misinformation alerts. +- Stream ingestion using Kafka or equivalent event queues. +- Knowledge graph integration. +- Cross-source contradiction detection. +- Source credibility learning. +- Public API and dashboard. +- Browser extension. +- Research paper support. ## License diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..fad2d5e --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,32 @@ +# TruthStream Frontend + +React + TypeScript + Tailwind website for the TruthStream verification API. + +## Local development + +From the repository root, start the backend: + +```bash +uvicorn truthstream.api:app --reload +``` + +In a second terminal: + +```bash +cd frontend +npm install +npm run dev +``` + +Open http://127.0.0.1:5173. + +The frontend defaults to the Vite `/api` proxy, which forwards requests to `http://127.0.0.1:8000`. +You can change the API base URL in the Settings page. + +## Pages + +- Home: product landing page. +- Verify: claim input, loading state, confidence meter, evidence cards, and errors. +- History: locally saved verification results. +- About: explanation of the verification flow. +- Settings: API base URL, dark mode, and local history preferences. diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..4816410 --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,26 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { ignores: ['dist'] }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + }, + } +); diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..3a6c67a --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + TruthStream + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..bffa3cd --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,34 @@ +{ + "name": "truthstream-frontend", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "tsc -b && vite build", + "preview": "vite preview --host 0.0.0.0", + "lint": "eslint ." + }, + "dependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.0.5", + "typescript": "^5.7.2", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "lucide-react": "^0.468.0" + }, + "devDependencies": { + "@eslint/js": "^9.17.0", + "@types/react": "^19.0.2", + "@types/react-dom": "^19.0.2", + "@types/node": "^22.10.2", + "autoprefixer": "^10.4.20", + "eslint": "^9.17.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.14.0", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", + "typescript-eslint": "^8.18.2" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..cd54b4b --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,57 @@ +import { useEffect, useMemo, useState } from 'react'; +import { Layout } from './components/Layout'; +import { About } from './pages/About'; +import { History } from './pages/History'; +import { Home } from './pages/Home'; +import { Settings } from './pages/Settings'; +import { Verify } from './pages/Verify'; +import { clearHistory, loadHistory, loadSettings, saveHistory, saveSettings } from './lib/storage'; +import type { HistoryItem, Settings as SettingsType } from './types/api'; + +type Page = 'home' | 'verify' | 'history' | 'about' | 'settings'; + +export function App() { + const [page, setPage] = useState('home'); + const [settings, setSettings] = useState(() => loadSettings()); + const [history, setHistory] = useState(() => loadHistory()); + + useEffect(() => { + const handler = (event: Event) => { + const nextPage = (event as CustomEvent).detail; + setPage(nextPage); + }; + window.addEventListener('truthstream:navigate', handler); + return () => window.removeEventListener('truthstream:navigate', handler); + }, []); + + useEffect(() => { + document.documentElement.classList.toggle('dark', settings.darkMode); + saveSettings(settings); + }, [settings]); + + useEffect(() => { + saveHistory(history); + }, [history]); + + const content = useMemo(() => { + switch (page) { + case 'verify': + return ( + setHistory((items) => [item, ...items].slice(0, 25))} + settings={settings} + /> + ); + case 'history': + return { clearHistory(); setHistory([]); }} />; + case 'about': + return ; + case 'settings': + return ; + default: + return setPage('verify')} />; + } + }, [history, page, settings]); + + return {content}; +} diff --git a/frontend/src/components/ConfidenceMeter.tsx b/frontend/src/components/ConfidenceMeter.tsx new file mode 100644 index 0000000..7639122 --- /dev/null +++ b/frontend/src/components/ConfidenceMeter.tsx @@ -0,0 +1,31 @@ +import type { VerificationStatus } from '../types/api'; +import { statusLabel, statusTone } from '../lib/format'; + +interface ConfidenceMeterProps { + confidence: number; + status: VerificationStatus; +} + +export function ConfidenceMeter({ confidence, status }: ConfidenceMeterProps) { + const percent = Math.round(confidence * 100); + + return ( +
+
+
+

Confidence

+

{percent}%

+
+ + {statusLabel(status)} + +
+
+
+
+
+ ); +} diff --git a/frontend/src/components/EvidenceCard.tsx b/frontend/src/components/EvidenceCard.tsx new file mode 100644 index 0000000..f5d0d01 --- /dev/null +++ b/frontend/src/components/EvidenceCard.tsx @@ -0,0 +1,34 @@ +import type { Evidence } from '../types/api'; + +interface EvidenceCardProps { + evidence: Evidence; +} + +export function EvidenceCard({ evidence }: EvidenceCardProps) { + const tone = + evidence.stance === 'supports' + ? 'border-emerald-500/40 bg-emerald-500/10' + : evidence.stance === 'contradicts' + ? 'border-rose-500/40 bg-rose-500/10' + : 'border-sky-500/30 bg-sky-500/10'; + + return ( +
+
+

{evidence.title}

+ + {evidence.stance} + +
+

{evidence.snippet}

+
+ {evidence.source.name} · credibility {Math.round(evidence.source.credibility * 100)}% + {evidence.source.url ? ( + + Open source ↗ + + ) : null} +
+
+ ); +} diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx new file mode 100644 index 0000000..11aca10 --- /dev/null +++ b/frontend/src/components/Layout.tsx @@ -0,0 +1,48 @@ +import type { ReactNode } from 'react'; +import { Link } from './Link'; + +interface LayoutProps { + activePage: string; + children: ReactNode; +} + +const navItems = [ + { id: 'home', label: 'Home' }, + { id: 'verify', label: 'Verify' }, + { id: 'history', label: 'History' }, + { id: 'about', label: 'About' }, + { id: 'settings', label: 'Settings' }, +]; + +export function Layout({ activePage, children }: LayoutProps) { + return ( +
+
+
+ +
+
{children}
+
+ ); +} diff --git a/frontend/src/components/Link.tsx b/frontend/src/components/Link.tsx new file mode 100644 index 0000000..5e28691 --- /dev/null +++ b/frontend/src/components/Link.tsx @@ -0,0 +1,23 @@ +import type { ReactNode } from 'react'; + +interface LinkProps { + active: boolean; + children: ReactNode; + page: string; +} + +export function Link({ active, children, page }: LinkProps) { + return ( + + ); +} diff --git a/frontend/src/components/ResultPanel.tsx b/frontend/src/components/ResultPanel.tsx new file mode 100644 index 0000000..d93726a --- /dev/null +++ b/frontend/src/components/ResultPanel.tsx @@ -0,0 +1,56 @@ +import { ConfidenceMeter } from './ConfidenceMeter'; +import { EvidenceCard } from './EvidenceCard'; +import type { VerificationResult } from '../types/api'; + +interface ResultPanelProps { + result: VerificationResult; +} + +export function ResultPanel({ result }: ResultPanelProps) { + const groups = [ + { title: 'Supporting evidence', items: result.supporting_evidence }, + { title: 'Contradicting evidence', items: result.contradicting_evidence }, + { title: 'Neutral evidence', items: result.neutral_evidence }, + ]; + + return ( +
+ +
+

Claim

+

{result.claim.text}

+

{result.explanation}

+
+ + + +
+
+ {groups.map((group) => ( +
+

{group.title}

+ {group.items.length ? ( +
+ {group.items.map((item, index) => ( + + ))} +
+ ) : ( +

+ No {group.title.toLowerCase()} found for this claim. +

+ )} +
+ ))} +
+ ); +} + +function Metric({ label, value }: { label: string; value: number }) { + return ( +
+

{label}

+

{value}

+
+ ); +} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts new file mode 100644 index 0000000..35363df --- /dev/null +++ b/frontend/src/lib/api.ts @@ -0,0 +1,25 @@ +import type { VerificationResult } from '../types/api'; + +export async function verifyClaim(apiBaseUrl: string, text: string): Promise { + const response = await fetch(`${apiBaseUrl}/verify`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ text }), + }); + + if (!response.ok) { + const details = await safeError(response); + throw new Error(details || `Verification failed with status ${response.status}`); + } + + return (await response.json()) as VerificationResult[]; +} + +async function safeError(response: Response): Promise { + try { + const body = await response.json(); + return typeof body.detail === 'string' ? body.detail : null; + } catch { + return null; + } +} diff --git a/frontend/src/lib/format.ts b/frontend/src/lib/format.ts new file mode 100644 index 0000000..9c6e054 --- /dev/null +++ b/frontend/src/lib/format.ts @@ -0,0 +1,18 @@ +import type { VerificationStatus } from '../types/api'; + +export function statusLabel(status: VerificationStatus): string { + return status.replace(/_/g, ' ').replace(/^\w/, (match: string) => match.toUpperCase()); +} + +export function statusTone(status: VerificationStatus): string { + switch (status) { + case 'likely_true': + return 'text-emerald-400 bg-emerald-500/10 ring-emerald-500/30'; + case 'likely_false': + return 'text-rose-400 bg-rose-500/10 ring-rose-500/30'; + case 'mixed': + return 'text-amber-300 bg-amber-500/10 ring-amber-500/30'; + default: + return 'text-sky-300 bg-sky-500/10 ring-sky-500/30'; + } +} diff --git a/frontend/src/lib/storage.ts b/frontend/src/lib/storage.ts new file mode 100644 index 0000000..8bda5ba --- /dev/null +++ b/frontend/src/lib/storage.ts @@ -0,0 +1,44 @@ +import type { HistoryItem, Settings } from '../types/api'; + +const SETTINGS_KEY = 'truthstream.settings'; +const HISTORY_KEY = 'truthstream.history'; + +export const defaultSettings: Settings = { + apiBaseUrl: '/api', + darkMode: true, + saveHistory: true, +}; + +export function loadSettings(): Settings { + const raw = localStorage.getItem(SETTINGS_KEY); + if (!raw) return defaultSettings; + + try { + return { ...defaultSettings, ...JSON.parse(raw) }; + } catch { + return defaultSettings; + } +} + +export function saveSettings(settings: Settings): void { + localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)); +} + +export function loadHistory(): HistoryItem[] { + const raw = localStorage.getItem(HISTORY_KEY); + if (!raw) return []; + + try { + return JSON.parse(raw) as HistoryItem[]; + } catch { + return []; + } +} + +export function saveHistory(history: HistoryItem[]): void { + localStorage.setItem(HISTORY_KEY, JSON.stringify(history.slice(0, 25))); +} + +export function clearHistory(): void { + localStorage.removeItem(HISTORY_KEY); +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..ed0dc63 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { App } from './App'; +import './styles.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/frontend/src/pages/About.tsx b/frontend/src/pages/About.tsx new file mode 100644 index 0000000..37a2b65 --- /dev/null +++ b/frontend/src/pages/About.tsx @@ -0,0 +1,31 @@ +export function About() { + return ( +
+
+

About

+

TruthStream explains evidence instead of pretending to know absolute truth.

+

+ The goal is to help journalists, researchers, and curious readers rapidly inspect what sources say about a claim. +

+
+
+ {[ + ['1', 'Extract claims', 'The backend identifies factual claims from submitted text.'], + ['2', 'Retrieve evidence', 'Evidence providers collect supporting, contradicting, or neutral source material.'], + ['3', 'Score confidence', 'The verification engine weighs source credibility, recency, and stance.'], + ['4', 'Show the work', 'The UI displays confidence, evidence cards, source links, and explanations.'], + ].map(([step, title, body]) => ( +
+
+ {step} +
+

{title}

+

{body}

+
+
+
+ ))} +
+
+ ); +} diff --git a/frontend/src/pages/History.tsx b/frontend/src/pages/History.tsx new file mode 100644 index 0000000..7558f82 --- /dev/null +++ b/frontend/src/pages/History.tsx @@ -0,0 +1,44 @@ +import { ResultPanel } from '../components/ResultPanel'; +import type { HistoryItem } from '../types/api'; + +interface HistoryProps { + history: HistoryItem[]; + onClear: () => void; +} + +export function History({ history, onClear }: HistoryProps) { + return ( +
+
+
+

History

+

Recent verifications

+
+ +
+ {history.length ? ( +
+ {history.map((item) => ( +
+
+

{new Date(item.createdAt).toLocaleString()}

+

{item.claimText}

+
+ {item.results.map((result, index) => ( + + ))} +
+ ))} +
+ ) : ( +
+

🕓

+

No saved verifications yet.

+

Run a claim from the Verify page to build your local history.

+
+ )} +
+ ); +} diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx new file mode 100644 index 0000000..72e8195 --- /dev/null +++ b/frontend/src/pages/Home.tsx @@ -0,0 +1,47 @@ +interface HomeProps { + onVerify: () => void; +} + +export function Home({ onVerify }: HomeProps) { + return ( +
+
+

+ Evidence-centered verification for fast-moving information +

+

+ Verify claims with sources, confidence, and context. +

+

+ TruthStream turns a claim into a transparent evidence report. It shows what supports the claim, what contradicts it, and how confident the system is. +

+
+ + +
+
+
+
+

Try typing

+

“India banned AI”

+
+
+ {[ + ['Evidence', '3 sources'], + ['Confidence', '72%'], + ['Status', 'Mixed'], + ].map(([label, value]) => ( +
+

{label}

+

{value}

+
+ ))} +
+
+
+ ); +} diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx new file mode 100644 index 0000000..d1ebc69 --- /dev/null +++ b/frontend/src/pages/Settings.tsx @@ -0,0 +1,53 @@ +import type { Settings as SettingsType } from '../types/api'; + +interface SettingsProps { + settings: SettingsType; + onSettings: (settings: SettingsType) => void; +} + +export function Settings({ settings, onSettings }: SettingsProps) { + return ( +
+
+

Settings

+

Configure your TruthStream workspace.

+
+
+ + onSettings({ ...settings, apiBaseUrl: event.target.value })} + value={settings.apiBaseUrl} + /> +

Use /api with the Vite proxy, or point directly to your FastAPI server.

+
+ onSettings({ ...settings, darkMode: checked })} + /> + onSettings({ ...settings, saveHistory: checked })} + /> +
+ ); +} + +function Toggle({ checked, description, label, onChange }: { checked: boolean; description: string; label: string; onChange: (checked: boolean) => void }) { + return ( +
+
+

{label}

+

{description}

+
+ +
+ ); +} diff --git a/frontend/src/pages/Verify.tsx b/frontend/src/pages/Verify.tsx new file mode 100644 index 0000000..10dc623 --- /dev/null +++ b/frontend/src/pages/Verify.tsx @@ -0,0 +1,87 @@ +import { useState } from 'react'; +import { verifyClaim } from '../lib/api'; +import type { HistoryItem, Settings, VerificationResult } from '../types/api'; +import { ResultPanel } from '../components/ResultPanel'; + +interface VerifyProps { + settings: Settings; + onHistoryItem: (item: HistoryItem) => void; +} + +export function Verify({ settings, onHistoryItem }: VerifyProps) { + const [text, setText] = useState(''); + const [results, setResults] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + async function submit() { + if (!text.trim()) { + setError('Enter a claim before verifying.'); + return; + } + + setLoading(true); + setError(null); + try { + const nextResults = await verifyClaim(settings.apiBaseUrl, text.trim()); + setResults(nextResults); + if (settings.saveHistory) { + onHistoryItem({ + id: crypto.randomUUID(), + claimText: text.trim(), + createdAt: new Date().toISOString(), + results: nextResults, + }); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Verification failed.'); + } finally { + setLoading(false); + } + } + + return ( +
+
+

Verify

+

What should we check?

+