Skip to content

Commit 60754b3

Browse files
authored
Merge pull request #109 from AOSSIE-Org/google-custom-search-engine
Fact check with google custom search engine api
2 parents 1ae7e6b + e1dbc2d commit 60754b3

3 files changed

Lines changed: 108 additions & 56 deletions

File tree

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,19 @@
1-
from serpapi import GoogleSearch
1+
import requests
2+
from dotenv import load_dotenv
23
import os
34

5+
load_dotenv()
46

5-
def search_with_serpapi(query, max_results=1):
6-
api_key = os.getenv("SERPAPI_KEY")
7-
if not api_key:
8-
raise ValueError("SERPAPI_KEY not set in environment")
9-
10-
params = {
11-
"engine": "google",
12-
"q": query,
13-
"api_key": api_key,
14-
"num": max_results,
15-
}
16-
17-
search = GoogleSearch(params)
18-
results = search.get_dict()
19-
organic = results.get("organic_results", [])
7+
GOOGLE_SEARCH = os.getenv("SEARCH_KEY")
208

9+
def search_google(query):
10+
results = requests.get(f"https://www.googleapis.com/customsearch/v1?key={GOOGLE_SEARCH}&cx=f637ab77b5d8b4a3c&q={query}")
11+
res = results.json()
12+
first = {}
13+
first["title"] = res["items"][0]["title"]
14+
first["link"] = res["items"][0]["link"]
15+
first["snippet"] = res["items"][0]["snippet"]
16+
2117
return [
22-
{
23-
"title": r.get("title", ""),
24-
"snippet": r.get("snippet", ""),
25-
"link": r.get("link", ""),
26-
}
27-
for r in organic
28-
]
18+
first,
19+
]

backend/app/utils/fact_check_utils.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from app.modules.facts_check.web_search import search_with_serpapi
1+
from app.modules.facts_check.web_search import search_google
22
from app.modules.facts_check.llm_processing import (
33
run_claim_extractor_sdk,
44
run_fact_verifier_sdk
@@ -28,7 +28,7 @@ def run_fact_check_pipeline(state):
2828
for claim in claims:
2929
print(f"\n🔍 Searching for claim: {claim}")
3030
try:
31-
results = search_with_serpapi(claim, max_results=1)
31+
results = search_google(claim)
3232
if results:
3333
results[0]["claim"] = claim
3434
search_results.append(results[0])
@@ -37,7 +37,6 @@ def run_fact_check_pipeline(state):
3737
print(f"⚠️ No search result for: {claim}")
3838
except Exception as e:
3939
print(f"❌ Search failed for: {claim} -> {e}")
40-
time.sleep(5) # ⏱️ Gentle delay to avoid DuckDuckGo ratelimit
4140

4241
if not search_results:
4342
return [], "All claim searches failed or returned no results."

frontend/app/analyze/results/page.tsx

Lines changed: 92 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"use client"
22

33
import type React from "react"
4-
import { useState, useEffect } from "react"
4+
import { useState, useEffect, useRef } from "react"
5+
import { useRouter } from "next/navigation"
56
import Link from "next/link"
67
import { ArrowLeft, Globe, MessageSquare, Send, ThumbsDown, ThumbsUp, Menu, Link as LinkIcon } from "lucide-react"
78
import { Button } from "@/components/ui/button"
@@ -18,6 +19,8 @@ import BiasMeter from "@/components/bias-meter"
1819
*/
1920
export default function AnalyzePage() {
2021
const [analysisData, setAnalysisData] = useState<any>(null)
22+
const router = useRouter()
23+
const isRedirecting = useRef(false);
2124
const [activeTab, setActiveTab] = useState("summary")
2225
const [message, setMessage] = useState("")
2326
const [isLoading, setIsLoading] = useState(true)
@@ -38,6 +41,41 @@ export default function AnalyzePage() {
3841
return () => clearTimeout(timer)
3942
}, [])
4043

44+
45+
useEffect(() => {
46+
47+
if (isRedirecting.current) {
48+
return;
49+
}
50+
51+
const timer = setTimeout(() => setIsLoading(false), 1500);
52+
const storedData = sessionStorage.getItem("analysisResult");
53+
54+
if (storedData) {
55+
const parsedData = JSON.parse(storedData);
56+
const requiredFields = ['cleaned_text', 'facts', 'sentiment', 'perspective', 'score'];
57+
const isDataValid = requiredFields.every(field => parsedData[field] !== undefined && parsedData[field] !== null);
58+
59+
if (isDataValid) {
60+
setAnalysisData(parsedData);
61+
} else {
62+
console.warn("Incomplete analysis data. Redirecting...");
63+
64+
65+
isRedirecting.current = true;
66+
router.push("/analyze");
67+
}
68+
} else {
69+
console.warn("No analysis result found. Redirecting...");
70+
71+
isRedirecting.current = true;
72+
router.push("/analyze");
73+
}
74+
75+
return () => clearTimeout(timer);
76+
}, [router]);
77+
78+
4179
const handleSendMessage = (e: React.FormEvent) => {
4280
e.preventDefault()
4381
if (!message.trim()) return
@@ -57,7 +95,7 @@ export default function AnalyzePage() {
5795
)
5896
}
5997

60-
const { cleaned_text, facts, sentiment, perspective, score } = analysisData
98+
const { cleaned_text, facts=[], sentiment, perspective, score } = analysisData
6199

62100
return (
63101
<div className="flex flex-col min-h-screen">
@@ -92,36 +130,60 @@ export default function AnalyzePage() {
92130
</TabsContent>
93131

94132
<TabsContent value="perspectives">
95-
<div className="space-y-4">
96-
<h2 className="text-xl font-semibold">Counter-Perspective</h2>
97-
<p className="italic">"{perspective.perspective}"</p>
98-
<h3 className="font-medium">Reasoning:</h3>
99-
<p>{perspective.reasoning}</p>
100-
</div>
101-
</TabsContent>
133+
{perspective ? (
134+
<div className="space-y-4">
135+
<h2 className="text-xl font-semibold">Counter-Perspective</h2>
136+
<p className="italic">"{perspective.perspective}"</p>
137+
<h3 className="font-medium">Reasoning:</h3>
138+
<p>{perspective.reasoning}</p>
139+
</div>
140+
) : (
141+
<div className="text-muted-foreground p-4">
142+
No counter-perspective was generated for this content.
143+
</div>
144+
)}
145+
</TabsContent>
102146

103147
<TabsContent value="facts">
104-
<div className="space-y-4">
105-
{facts.map((fact: any, idx: number) => (
106-
<Card key={idx} className="border">
107-
<CardHeader>
108-
<div className="flex justify-between items-center">
109-
<CardTitle>{fact.original_claim}</CardTitle>
110-
<Badge variant={fact.verdict === 'True' ? 'success' : fact.verdict === 'False' ? 'destructive' : 'warning'}>
111-
{fact.verdict}
112-
</Badge>
113-
</div>
114-
</CardHeader>
115-
<CardContent>
116-
<p className="mb-2">{fact.explanation}</p>
117-
<Link href={fact.source_link} target="_blank" className="flex items-center text-sm hover:underline">
118-
<LinkIcon className="mr-1 h-4 w-4" /> Source
119-
</Link>
120-
</CardContent>
121-
</Card>
122-
))}
123-
</div>
124-
</TabsContent>
148+
<div className="space-y-4">
149+
{facts.length > 0 ? (
150+
facts.map((fact: any, idx: number) => (
151+
<Card key={idx} className="border">
152+
<CardHeader>
153+
<div className="flex justify-between items-center">
154+
<CardTitle>{fact.original_claim}</CardTitle>
155+
<Badge
156+
variant={
157+
fact.verdict === 'True'
158+
? 'success'
159+
: fact.verdict === 'False'
160+
? 'destructive'
161+
: 'warning'
162+
}
163+
>
164+
{fact.verdict}
165+
</Badge>
166+
</div>
167+
</CardHeader>
168+
<CardContent>
169+
<p className="mb-2">{fact.explanation}</p>
170+
<Link
171+
href={fact.source_link}
172+
target="_blank"
173+
className="flex items-center text-sm hover:underline"
174+
>
175+
<LinkIcon className="mr-1 h-4 w-4" /> Source
176+
</Link>
177+
</CardContent>
178+
</Card>
179+
))
180+
) : (
181+
<div className="text-muted-foreground p-4">
182+
No specific claims were identified for fact-checking in this content.
183+
</div>
184+
)}
185+
</div>
186+
</TabsContent>
125187
</Tabs>
126188
</div>
127189

0 commit comments

Comments
 (0)