diff --git a/backend/app/modules/bias_detection/__init__.py b/backend/app/modules/bias_detection/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/app/modules/bias_detection/check_bias.py b/backend/app/modules/bias_detection/check_bias.py new file mode 100644 index 00000000..b4b39801 --- /dev/null +++ b/backend/app/modules/bias_detection/check_bias.py @@ -0,0 +1,57 @@ +import os +from groq import Groq +from dotenv import load_dotenv +import json + +load_dotenv() + +client = Groq(api_key=os.getenv("GROQ_API_KEY")) + + +def check_bias(text): + try: + print(text) + print(json.dumps(text)) + + if not text: + raise ValueError("Missing or empty 'cleaned_text'") + + chat_completion = client.chat.completions.create( + messages=[ + { + "role": "system", + "content": ( + "You are an assistant that checks " + "if given article is biased and give" + "score to each based on biasness where 0 is lowest bias and 100 is highest bias" + "Only return a number between 0 to 100 base on bias." + "only return Number No Text" + ), + }, + { + "role": "user", + "content": ( + "Give bias score to the following article " + f"\n\n{text}" + ), + }, + ], + model="gemma2-9b-it", + temperature=0.3, + max_tokens=512, + ) + + bias_score = chat_completion.choices[0].message.content.strip() + + return { + "bias_score": bias_score, + "status": "success", + } + + except Exception as e: + print(f"Error in bias_detection: {e}") + return { + "status": "error", + "error_from": "bias_detection", + "message": str(e), + } diff --git a/backend/app/routes/routes.py b/backend/app/routes/routes.py index c443e83c..9df55a2a 100644 --- a/backend/app/routes/routes.py +++ b/backend/app/routes/routes.py @@ -2,11 +2,12 @@ from pydantic import BaseModel from app.modules.pipeline import run_scraper_pipeline from app.modules.pipeline import run_langgraph_workflow +from app.modules.bias_detection.check_bias import check_bias +import asyncio import json router = APIRouter() - class URlRequest(BaseModel): url: str @@ -15,10 +16,18 @@ class URlRequest(BaseModel): async def home(): return {"message": "Perspective API is live!"} +@router.post("/bias") +async def bias_detection(request: URlRequest): + content = await asyncio.to_thread(run_scraper_pipeline,(request.url)) + bias_score = await asyncio.to_thread(check_bias,(content)) + print(bias_score) + return bias_score + + @router.post("/process") async def run_pipelines(request: URlRequest): - article_text = run_scraper_pipeline(request.url) + article_text = await asyncio.to_thread(run_scraper_pipeline,(request.url)) print(json.dumps(article_text, indent=2)) - data = run_langgraph_workflow(article_text) + data = await asyncio.to_thread(run_langgraph_workflow,(article_text)) return data diff --git a/frontend/app/analyze/loading/page.tsx b/frontend/app/analyze/loading/page.tsx index 055a1a08..1d55ace3 100644 --- a/frontend/app/analyze/loading/page.tsx +++ b/frontend/app/analyze/loading/page.tsx @@ -1,12 +1,20 @@ -"use client" +"use client"; -import { useEffect, useState } from "react" -import { useRouter } from "next/navigation" -import { Card } from "@/components/ui/card" -import { Badge } from "@/components/ui/badge" -import { Globe, Brain, Shield, CheckCircle, Database, Sparkles, Zap } from "lucide-react" -import ThemeToggle from "@/components/theme-toggle" -import axios from "axios" +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import { Card } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { + Globe, + Brain, + Shield, + CheckCircle, + Database, + Sparkles, + Zap, +} from "lucide-react"; +import ThemeToggle from "@/components/theme-toggle"; +import axios from "axios"; /** * Displays a multi-step animated loading and progress interface for the article analysis workflow. @@ -16,10 +24,10 @@ import axios from "axios" * @remark This component manages its own navigation and redirects based on session state. */ export default function LoadingPage() { - const [currentStep, setCurrentStep] = useState(0) - const [progress, setProgress] = useState(0) - const [articleUrl, setArticleUrl] = useState("") - const router = useRouter() + const [currentStep, setCurrentStep] = useState(0); + const [progress, setProgress] = useState(0); + const [articleUrl, setArticleUrl] = useState(""); + const router = useRouter(); const steps = [ { @@ -52,67 +60,88 @@ export default function LoadingPage() { description: "Creating balanced alternative viewpoints", color: "from-pink-500 to-rose-500", }, - ] + ]; useEffect(() => { - const runAnalysis = async () => { - const storedUrl = sessionStorage.getItem("articleUrl") - if (storedUrl) { - setArticleUrl(storedUrl) + const runAnalysis = async () => { + const storedUrl = sessionStorage.getItem("articleUrl"); + if (storedUrl) { + setArticleUrl(storedUrl); - try { - const res = await axios.post("https://Thunder1245-perspective-backend.hf.space/api/process", { - url: storedUrl, - }) + try { + const [processRes, biasRes] = await Promise.all([ + axios.post( + "https://Thunder1245-perspective-backend.hf.space/api/process", + { + url: storedUrl, + } + ), + axios.post( + "http://Thunder1245-perspective-backend.hf.space/api/bias", + { + url: storedUrl, + } + ), + ]); - // Save response to sessionStorage - sessionStorage.setItem("analysisResult", JSON.stringify(res.data)) - // optional logging - console.log("Analysis result saved") - console.log(res) - } catch (err) { - console.error("Failed to process article:", err) - router.push("/analyze") // fallback in case of error - return - } + sessionStorage.setItem("BiasScore", JSON.stringify(biasRes.data)); - // Progress and step simulation - const stepInterval = setInterval(() => { - setCurrentStep((prev) => { - if (prev < steps.length - 1) { - return prev + 1 - } else { - clearInterval(stepInterval) - setTimeout(() => { - router.push("/analyze/results") - }, 2000) - return prev - } - }) - }, 2000) + console.log("Bias score saved"); + console.log(biasRes); - const progressInterval = setInterval(() => { - setProgress((prev) => { - if (prev < 100) { - return prev + 1 - } - return prev - }) - }, 100) + // Save response to sessionStorage + sessionStorage.setItem( + "analysisResult", + JSON.stringify(processRes.data) + ); - return () => { - clearInterval(stepInterval) - clearInterval(progressInterval) - } - } else { - router.push("/analyze") - } - } + console.log("Analysis result saved"); + console.log(processRes); + + + // optional logging + } catch (err) { + console.error("Failed to process article:", err); + router.push("/analyze"); // fallback in case of error + return; + } + + // Progress and step simulation + const stepInterval = setInterval(() => { + setCurrentStep((prev) => { + if (prev < steps.length - 1) { + return prev + 1; + } else { + clearInterval(stepInterval); + setTimeout(() => { + router.push("/analyze/results"); + }, 2000); + return prev; + } + }); + }, 2000); - runAnalysis() -}, [router]) + const progressInterval = setInterval(() => { + setProgress((prev) => { + if (prev < 100) { + return prev + 1; + } + return prev; + }); + }, 100); + return () => { + clearInterval(stepInterval); + clearInterval(progressInterval); + }; + } else { + router.push("/analyze"); + } + }; + + runAnalysis(); + }, [router]); return (
@@ -162,8 +191,12 @@ export default function LoadingPage() { {/* Article URL Display */}
-

Processing:

-

{articleUrl}

+

+ Processing: +

+

+ {articleUrl} +

{/* Progress Bar */} @@ -171,7 +204,9 @@ export default function LoadingPage() {
@@ -190,8 +225,8 @@ export default function LoadingPage() { index === currentStep ? "bg-white dark:bg-slate-800 shadow-2xl scale-105 ring-2 ring-blue-500/50" : index < currentStep - ? "bg-white/80 dark:bg-slate-800/80 shadow-lg opacity-75" - : "bg-white/40 dark:bg-slate-800/40 shadow-md opacity-50" + ? "bg-white/80 dark:bg-slate-800/80 shadow-lg opacity-75" + : "bg-white/40 dark:bg-slate-800/40 shadow-md opacity-50" }`} >
@@ -200,8 +235,8 @@ export default function LoadingPage() { index === currentStep ? `bg-gradient-to-br ${step.color} animate-pulse shadow-lg` : index < currentStep - ? "bg-gradient-to-br from-emerald-500 to-teal-500 shadow-md" - : "bg-slate-200 dark:bg-slate-700" + ? "bg-gradient-to-br from-emerald-500 to-teal-500 shadow-md" + : "bg-slate-200 dark:bg-slate-700" }`} > {index < currentStep ? ( @@ -221,13 +256,15 @@ export default function LoadingPage() { index === currentStep ? "text-blue-600 dark:text-blue-400" : index < currentStep - ? "text-emerald-600 dark:text-emerald-400" - : "text-slate-500 dark:text-slate-400" + ? "text-emerald-600 dark:text-emerald-400" + : "text-slate-500 dark:text-slate-400" }`} > {step.title} -

{step.description}

+

+ {step.description} +

{index === currentStep && (
@@ -262,5 +299,5 @@ export default function LoadingPage() {
- ) + ); } diff --git a/frontend/app/analyze/page.tsx b/frontend/app/analyze/page.tsx index 541b0dd2..c86c6c9e 100644 --- a/frontend/app/analyze/page.tsx +++ b/frontend/app/analyze/page.tsx @@ -1,15 +1,29 @@ -"use client" +"use client"; -import type React from "react" +import type React from "react"; -import { useState } from "react" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Badge } from "@/components/ui/badge" -import { Globe, ArrowRight, Link, Sparkles, Shield, Brain, CheckCircle } from "lucide-react" -import { useRouter } from "next/navigation" -import ThemeToggle from "@/components/theme-toggle" +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { + Globe, + ArrowRight, + Link, + Sparkles, + Shield, + Brain, + CheckCircle, +} from "lucide-react"; +import { useRouter } from "next/navigation"; +import ThemeToggle from "@/components/theme-toggle"; /** * Renders the main page for submitting an article URL to initiate AI-powered analysis. @@ -17,36 +31,36 @@ import ThemeToggle from "@/components/theme-toggle" * Provides a user interface for entering and validating an article URL, displays real-time feedback on URL validity, and enables users to trigger analysis. Features include a branded header, a hero section, a URL input card with validation, a grid highlighting analysis capabilities, and example article URLs for quick testing. On valid submission, the URL is stored in sessionStorage and the user is navigated to a loading page for further processing. */ export default function AnalyzePage() { - const [url, setUrl] = useState("") - const [isValidUrl, setIsValidUrl] = useState(false) - const router = useRouter() + const [url, setUrl] = useState(""); + const [isValidUrl, setIsValidUrl] = useState(false); + const router = useRouter(); const validateUrl = (inputUrl: string) => { try { - new URL(inputUrl) - setIsValidUrl(true) + new URL(inputUrl); + setIsValidUrl(true); } catch { - setIsValidUrl(false) + setIsValidUrl(false); } - } + }; const handleUrlChange = (e: React.ChangeEvent) => { - const inputUrl = e.target.value - setUrl(inputUrl) + const inputUrl = e.target.value; + setUrl(inputUrl); if (inputUrl.length > 0) { - validateUrl(inputUrl) + validateUrl(inputUrl); } else { - setIsValidUrl(false) + setIsValidUrl(false); } - } + }; const handleAnalyze = () => { if (isValidUrl && url) { // Store the URL in sessionStorage to pass to loading page - sessionStorage.setItem("articleUrl", url) - router.push("/analyze/loading") + sessionStorage.setItem("articleUrl", url); + router.push("/analyze/loading"); } - } + }; const features = [ { @@ -64,7 +78,7 @@ export default function AnalyzePage() { title: "Fact Verification", description: "Cross-references claims with reliable sources", }, - ] + ]; return (
@@ -109,8 +123,8 @@ export default function AnalyzePage() {

- Paste the URL of any online article and get AI-powered bias detection, fact-checking, and alternative - perspectives in seconds. + Paste the URL of any online article and get AI-powered bias + detection, fact-checking, and alternative perspectives in seconds.

@@ -121,7 +135,8 @@ export default function AnalyzePage() { Enter Article URL - Provide the link to the article you want to analyze for bias and alternative perspectives + Provide the link to the article you want to analyze for bias and + alternative perspectives @@ -157,7 +172,9 @@ export default function AnalyzePage() {
{url && !isValidUrl && ( -

Please enter a valid URL

+

+ Please enter a valid URL +

)} @@ -204,8 +221,8 @@ export default function AnalyzePage() {