parthmax24 commited on
Commit
c83cd7e
·
0 Parent(s):

initial commit

Browse files
.gitignore ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / cache
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.so
7
+ #assets
8
+ assets/
9
+ # Virtual environment
10
+ venv/
11
+ .env/
12
+ .venv/
13
+ ENV/
14
+ env/
15
+ *.env
16
+
17
+ # Environment & secrets
18
+ .env
19
+ *.env.*
20
+ *.secret
21
+ secrets/
22
+
23
+ # PyInstaller
24
+ *.spec
25
+
26
+ # Log files
27
+ *.log
28
+ logs/
29
+
30
+ # Unit test / coverage
31
+ .coverage
32
+ .cache/
33
+ .pytest_cache/
34
+ nosetests.xml
35
+ coverage.xml
36
+ *.cover
37
+ .hypothesis/
38
+
39
+ # IDEs and editors
40
+ .vscode/
41
+ .idea/
42
+ *.sublime-project
43
+ *.sublime-workspace
44
+
45
+ # OS-generated files
46
+ .DS_Store
47
+ Thumbs.db
48
+ ehthumbs.db
49
+ desktop.ini
50
+
51
+ # Python distribution
52
+ build/
53
+ dist/
54
+ *.egg-info/
55
+ *.egg
56
+
README.md ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # Falcon: Fake News Analysis and Language Comprehension for Online Neutrality
3
+
4
+ **Falcon** is an advanced Gen AI-powered system designed to analyze, verify, and classify online claims to promote digital content neutrality. Leveraging state-of-the-art language models, it classifies user-submitted text, detects tone and intent, verifies facts using multiple data sources, and generates an informed verdict about claim credibility.
5
+
6
+ ---
7
+
8
+ ## Features
9
+
10
+ * **Claim Classification**: Classifies input text into categories like Factual Claim, Opinion, Irrelevant Talk, or Vague/Incomplete.
11
+ * **Tone and Intent Detection**: Analyzes the emotional tone and intent behind the claim (e.g., neutral, persuasive, humorous).
12
+ * **Fact Verification**: Verifies factual claims by searching reliable sources like Google (via Serper API) and Wikipedia.
13
+ * **Verdict Generation**: Produces a clear verdict on the claim’s truthfulness based on gathered evidence and reasoning.
14
+ * **Fallback Language Model**: Uses OpenAI’s GPT-4 as the primary LLM and automatically switches to **Deepseek** as a fallback model to ensure high availability and robustness.
15
+ * **Simple Web Interface**: Easy-to-use frontend using HTML, CSS, and JavaScript for seamless user interaction.
16
+ * **FastAPI Backend**: Fast, lightweight API server handling all processing with asynchronous endpoints.
17
+
18
+ ---
19
+
20
+ ## Tech Stack
21
+
22
+ * **Backend**: FastAPI (Python)
23
+ * **Frontend**: HTML, CSS, JavaScript
24
+ * **Language Models**: OpenAI GPT-4 (primary), Deepseek (fallback)
25
+ * **APIs**: Serper API (Google Search), Wikipedia API
26
+ * **Prompt Engineering**: Carefully crafted templates for accurate and reliable results
27
+ * **Deployment**: Docker containerization (optional)
28
+
29
+ ---
30
+
31
+ ## Project Structure
32
+
33
+ ```
34
+ falcon/
35
+ ├── backend/
36
+ │ ├── api/
37
+ │ │ ├── claims.py # Claim classification logic
38
+ │ │ ├── fact_check.py # Fact verification logic
39
+ │ │ ├── tone_intent.py # Tone and intent detection logic
40
+ │ ├── langchain_tools.py # LangChain prompt templates and LLM wrappers
41
+ │ ├── config.py # API keys and config variables
42
+ │ └── main.py # FastAPI app entry point
43
+ ├── frontend/
44
+ │ ├── index.html # Main UI page
45
+ │ ├── assets/ # CSS, JS, images
46
+ ├── Dockerfile # Docker container definition (optional)
47
+ └── README.md # This file
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Setup Instructions
53
+
54
+ ### Prerequisites
55
+
56
+ * Python 3.9+
57
+ * FastAPI
58
+ * Uvicorn (ASGI server)
59
+ * OpenAI API key
60
+ * Serper API key
61
+ * Deepseek API key (for fallback)
62
+
63
+ ### Installation
64
+
65
+ 1. Clone the repository:
66
+
67
+ ```bash
68
+ git clone https://github.com/yourusername/falcon.git
69
+ cd falcon
70
+ ```
71
+
72
+ 2. Create and activate a virtual environment:
73
+
74
+ ```bash
75
+ python -m venv venv
76
+ source venv/bin/activate # On Windows: venv\Scripts\activate
77
+ ```
78
+
79
+ 3. Install dependencies:
80
+
81
+ ```bash
82
+ pip install -r requirements.txt
83
+ ```
84
+
85
+ 4. Configure API keys in `backend/config.py`:
86
+
87
+ ```python
88
+ OPENAI_API_KEY = "your_openai_api_key"
89
+ SERPER_API_KEY = "your_serper_api_key"
90
+ DEEPSEEK_API_KEY = "your_deepseek_api_key"
91
+ ```
92
+
93
+ 5. Run the FastAPI server:
94
+
95
+ ```bash
96
+ uvicorn backend.main:app --port 8000
97
+ ```
98
+
99
+ 6. Open your browser and visit `http://localhost:8000` to use the Falcon app.
100
+
101
+ ---
102
+
103
+ ## Usage
104
+
105
+ * Enter a claim or statement in the input form.
106
+ * The system classifies the claim, detects tone and intent, performs fact verification if applicable, and returns a detailed verdict with supporting evidence.
107
+ * The fallback model Deepseek is automatically used if GPT-4 is unavailable, ensuring consistent performance.
108
+
backend/api/claims.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain.prompts import PromptTemplate
2
+ from backend.langchain_tools import llm, deepseek_tool
3
+ from langchain_core.runnables import RunnableSequence
4
+ import json
5
+ import re
6
+
7
+ # Prompt Template
8
+ claim_classification_prompt = PromptTemplate.from_template("""
9
+ You are an expert language analyst trained to classify text-based user claims. Your task is to analyze a given piece of text and classify it into one of the following precise categories, based on meaning, structure, and intention:
10
+
11
+ Classify the following text into one of these categories:
12
+ 1. Factual Claim – A statement that can be verified or disproven using evidence.
13
+ 2. Opinion – A personal belief or viewpoint that cannot be objectively proven.
14
+ 3. Misleading Claim – A statement that could mislead or distort facts.
15
+ 4. Exaggeration – A statement that overstates facts or makes things seem more dramatic.
16
+ 5. Factoid – A trivial, unverifiable claim that seems factual but lacks evidence.
17
+ 6. Question – A statement framed as a question, seeking information.
18
+ 7. Joke/Hyperbole – A statement made in jest or exaggeration, not to be taken literally.
19
+ 8. Testimonial/Personal Experience – A personal account or anecdote.
20
+ 9. Propaganda/Manipulative Claim – A claim designed to manipulate public opinion.
21
+
22
+ Text: "{claim}"
23
+
24
+ Respond in JSON format:
25
+ {{
26
+ "category": "<one of the above categories>",
27
+ "reasoning": "<brief justification>"
28
+ }}
29
+ """)
30
+
31
+ # Convert template to runnable chain
32
+ claim_chain: RunnableSequence = claim_classification_prompt | llm
33
+
34
+ # Store the prompt template string for fallback use
35
+ prompt_template_str = claim_classification_prompt.template
36
+
37
+ # Classification Function with fallback to DeepSeek-V3
38
+ def classify_claim(claim_text: str) -> dict:
39
+ try:
40
+ # Try using the primary LLM model (OpenAI)
41
+ result = claim_chain.invoke({"claim": claim_text})
42
+ classification = json.loads(result.content.strip())
43
+
44
+ if 'category' not in classification or 'reasoning' not in classification:
45
+ raise ValueError("Invalid classification format received from the model.")
46
+
47
+ return classification
48
+
49
+ except Exception as e:
50
+ # If LLM fails, use DeepSeek-V3 as a fallback
51
+ try:
52
+ print(f"Error with LLM: {e}. Falling back to DeepSeek-V3.")
53
+
54
+ # Manually construct prompt text
55
+ deepseek_prompt = prompt_template_str.format(claim=claim_text)
56
+
57
+ # Call DeepSeek with input key "input"
58
+ deepseek_result = deepseek_tool.invoke({"input": deepseek_prompt})
59
+ print("Raw DeepSeek Output:", deepseek_result)
60
+
61
+ # Remove markdown-style code block if present
62
+ cleaned_output = re.sub(r"```(?:json)?\s*([\s\S]*?)\s*```", r"\1", deepseek_result.strip())
63
+
64
+ # Parse cleaned JSON
65
+ deepseek_classification = json.loads(cleaned_output)
66
+
67
+ if 'category' not in deepseek_classification or 'reasoning' not in deepseek_classification:
68
+ raise ValueError("Invalid classification format received from DeepSeek-V3.")
69
+
70
+ return deepseek_classification
71
+
72
+ except Exception as fallback_e:
73
+ return {"error": f"An error occurred with both LLM and DeepSeek-V3: {str(fallback_e)}"}
74
+
75
+ # Example usage
76
+ if __name__ == "__main__":
77
+ claim = "modi is prime minister"
78
+ result = classify_claim(claim)
79
+ print(result)
backend/api/fact_check.py ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import requests
4
+ from dotenv import load_dotenv
5
+ from backend.langchain_tools import llm, deepseek_tool
6
+ import re
7
+ from backend.api.claims import classify_claim
8
+ from backend.api.tone_intent import detect_tone_and_intent
9
+ from langchain.prompts import PromptTemplate
10
+ from langchain_core.runnables import RunnableSequence
11
+ import wikipedia
12
+ import logging
13
+ from bs4 import BeautifulSoup
14
+ from sumy.parsers.plaintext import PlaintextParser
15
+ from sumy.nlp.tokenizers import Tokenizer
16
+ from sumy.summarizers.lsa import LsaSummarizer
17
+ import nltk
18
+
19
+ load_dotenv()
20
+
21
+ SERPER_API_KEY = os.getenv("SERPER_API_KEY")
22
+ USER_AGENT = {"User-Agent": "Mozilla/5.0"}
23
+
24
+ # Initialising requests session to reuse connections
25
+ session = requests.Session()
26
+ session.headers.update(USER_AGENT)
27
+
28
+ # nltk data for tokenization once
29
+ nltk.download('punkt')
30
+
31
+ logging.basicConfig(level=logging.INFO)
32
+
33
+ fact_check_prompt = PromptTemplate.from_template("""
34
+ You are an expert fact-checker. Your task is to determine whether the following claim is true or false based on the information available from external sources (Wikipedia and Google search). The claim has already been classified into categories and analyzed for tone/intent.
35
+
36
+ Claim: "{claim}"
37
+ Classification: "{classification}"
38
+ Tone: "{tone}"
39
+ Intent: "{intent}"
40
+ Wikipedia Evidence: "{wikipedia_evidence}"
41
+ Serper Evidence: "{serper_evidence}"
42
+
43
+ Fact-checking Task:
44
+ - Based on the evidence from Wikipedia and Serper search results, classify the claim as:
45
+ 1. True
46
+ 2. False
47
+ 3. Uncertain (when there is insufficient evidence)
48
+
49
+ Respond in JSON format:
50
+ {{
51
+ "claim": "{claim}",
52
+ "classification": "{classification}",
53
+ "tone": "{tone}",
54
+ "intent": "{intent}",
55
+ "fact_check_result": "<True/False/Uncertain/Misleading/Exaggerated/Partially True/Inconclusive:: >",
56
+ "evidence": "<evidence supporting or contradicting the claim>",
57
+ "sources": [
58
+ {{
59
+ "source": "Wikipedia",
60
+ "url": "<url>"
61
+ }},
62
+ {{
63
+ "source": "Search result",
64
+ "url": "<serper_url>"
65
+ }},
66
+ {{
67
+ "source": "LLM",
68
+ "url": "<llm_inferred_url>"
69
+ }},
70
+ ...
71
+ ],
72
+ "reasoning": "<reasoning for the fact-checking result>"
73
+ }}
74
+ """)
75
+
76
+ fact_check_chain = fact_check_prompt | llm
77
+
78
+ # Wikipedia Search Function
79
+ def search_wikipedia(query):
80
+ try:
81
+ results = wikipedia.search(query, results=5)
82
+ if not results:
83
+ return {"error": "No results found on Wikipedia."}
84
+
85
+ summaries = []
86
+ for title in results:
87
+ try:
88
+ page = wikipedia.page(title)
89
+ summaries.append({
90
+ "title": title,
91
+ "summary": page.summary,
92
+ "url": page.url
93
+ })
94
+ except wikipedia.exceptions.DisambiguationError as e:
95
+ summaries.append({"error": f"Disambiguation: {str(e)}"})
96
+ except Exception as e:
97
+ summaries.append({"error": str(e)})
98
+ return summaries
99
+ except Exception as e:
100
+ return {"error": str(e)}
101
+
102
+ # Fetch search results from Serper API
103
+ def fetch_search_results(query, sentences_count=3):
104
+ url = "https://google.serper.dev/search"
105
+ headers = {"X-API-KEY": SERPER_API_KEY}
106
+ data = {"q": query, "num": 10}
107
+
108
+ try:
109
+ response = session.post(url, json=data, headers=headers)
110
+ response.raise_for_status()
111
+ results = response.json()
112
+
113
+ if not results.get("organic"):
114
+ return "Error: No search results found."
115
+
116
+ sources = []
117
+ for result in results.get("organic", [])[:5]:
118
+ source_url = result.get("url") or result.get("link")
119
+ sources.append(source_url)
120
+
121
+ summary = fetch_and_summarize(sources[0], sentences_count)
122
+ return summary, sources
123
+ except requests.exceptions.RequestException as e:
124
+ return f"Error fetching search results: {e}"
125
+
126
+ # Summarize content from a Webpage
127
+ def fetch_and_summarize(url, sentences_count=3):
128
+ try:
129
+ response = session.get(url, timeout=10)
130
+ response.raise_for_status()
131
+ soup = BeautifulSoup(response.text, "html.parser")
132
+ paragraphs = soup.find_all("p")
133
+ text = "\n".join([p.get_text() for p in paragraphs])
134
+
135
+ if not text.strip():
136
+ return "Error: No readable content found on the page."
137
+
138
+ return summarize_text(text, sentences_count)
139
+ except requests.exceptions.RequestException as e:
140
+ return f"Error: Failed to fetch webpage ({str(e)})"
141
+ except Exception as e:
142
+ return f"Error: {str(e)}"
143
+
144
+ # Summarize text using LsaSummarizer
145
+ def summarize_text(text, sentences_count=3):
146
+ if not text.strip():
147
+ return "Error: No text provided for summarization."
148
+
149
+ parser = PlaintextParser.from_string(text, Tokenizer("english"))
150
+ summarizer = LsaSummarizer()
151
+ summary = summarizer(parser.document, sentences_count)
152
+
153
+ return " ".join(str(sentence) for sentence in summary)
154
+
155
+
156
+
157
+ def fact_check_claim(claim_text: str) -> dict:
158
+ try:
159
+ classification = classify_claim(claim_text)
160
+ tone_intent = detect_tone_and_intent(claim_text)
161
+
162
+ classification_type = classification.get("category", "Unknown")
163
+ tone = tone_intent.get("tone", "Unknown")
164
+ intent = tone_intent.get("intent", "Unknown")
165
+
166
+ wikipedia_results = search_wikipedia(claim_text)
167
+ serper_summary, serper_sources = fetch_search_results(claim_text)
168
+
169
+ wikipedia_evidence = " ".join([r["summary"] for r in wikipedia_results if "summary" in r])
170
+
171
+ sources = []
172
+
173
+ for r in wikipedia_results:
174
+ if "url" in r:
175
+ sources.append({"source": "Wikipedia", "url": r["url"]})
176
+ for url in serper_sources:
177
+ sources.append({"source": "Serper", "url": url})
178
+
179
+ try:
180
+ fact_check_result = fact_check_chain.invoke({
181
+ "claim": claim_text,
182
+ "classification": classification_type,
183
+ "tone": tone,
184
+ "intent": intent,
185
+ "wikipedia_evidence": wikipedia_evidence,
186
+ "serper_evidence": serper_summary
187
+ })
188
+
189
+ result = json.loads(fact_check_result.content.strip())
190
+ result["sources"] = sources
191
+ return result
192
+
193
+ except Exception as primary_error:
194
+ logging.warning(f"Primary LLM failed: {primary_error}. Falling back to DeepSeek.")
195
+
196
+ deepseek_prompt = fact_check_prompt.template.format(
197
+ claim=claim_text,
198
+ classification=classification_type,
199
+ tone=tone,
200
+ intent=intent,
201
+ wikipedia_evidence=wikipedia_evidence,
202
+ serper_evidence=serper_summary
203
+ )
204
+
205
+ deepseek_result = deepseek_tool.invoke({"input": deepseek_prompt})
206
+ logging.info(f"Raw DeepSeek Output: {deepseek_result}")
207
+
208
+ cleaned_output = re.sub(r"```(?:json)?\s*([\s\S]*?)\s*```", r"\1", deepseek_result.strip())
209
+
210
+ result = json.loads(cleaned_output)
211
+ result["sources"] = sources
212
+ return result
213
+
214
+ except Exception as final_error:
215
+ logging.error(f"Total failure in fact_check_claim: {final_error}")
216
+ return {"error": f"Fact-checking failed due to: {str(final_error)}"}
backend/api/tone_intent.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain.prompts import PromptTemplate
2
+ from backend.langchain_tools import llm, deepseek_tool
3
+ from langchain_core.runnables import RunnableSequence
4
+ import json
5
+ import logging
6
+ import re
7
+
8
+ logging.basicConfig(level=logging.INFO)
9
+
10
+ # Prompt Template
11
+ tone_intent_prompt = PromptTemplate.from_template("""
12
+ You are an expert language analyst trained to detect the tone and intent of a given piece of text. Your task is to analyze the text and determine both its tone and intent from the following predefined categories:
13
+
14
+ Tone Categories:
15
+ 1. Neutral
16
+ 2. Sarcastic
17
+ 3. Clickbait
18
+ 4. Propaganda
19
+ 5. Aggressive / Toxic
20
+ 6. Persuasive
21
+ 7. Humorous
22
+
23
+ Intent Categories:
24
+ 1. To Inform
25
+ 2. To Persuade
26
+ 3. To Deceive
27
+ 4. To Express Emotion
28
+ 5. To Incite Hate
29
+ 6. To Promote Agenda
30
+
31
+ Text: "{text}"
32
+
33
+ Respond in JSON format:
34
+ {{
35
+ "tone": "<one of: Neutral, Sarcastic, Clickbait, Propaganda, Aggressive / Toxic, Persuasive, Humorous>",
36
+ "intent": "<one of: To Inform, To Persuade, To Deceive, To Express Emotion, To Incite Hate, To Promote Agenda>",
37
+ "reasoning": "<brief justification for tone and intent>"
38
+ }}
39
+ """)
40
+
41
+ # RunnableSequence
42
+ tone_intent_chain = tone_intent_prompt | llm
43
+
44
+ # Store the prompt string for fallback
45
+ prompt_template_str = tone_intent_prompt.template
46
+
47
+ # Detection function with fallback to DeepSeek-V3
48
+ def detect_tone_and_intent(text: str) -> dict:
49
+ try:
50
+ result = tone_intent_chain.invoke({"text": text})
51
+ detection = json.loads(result.content.strip())
52
+
53
+ if 'tone' not in detection or 'intent' not in detection or 'reasoning' not in detection:
54
+ logging.error(f"Unexpected response format: {result.content.strip()}")
55
+ return {"error": "Response format is incorrect. Missing required fields."}
56
+
57
+ return detection
58
+
59
+ except Exception as e:
60
+ logging.error(f"Error with primary model (LLM): {str(e)}. Falling back to DeepSeek-V3.")
61
+
62
+ try:
63
+ # Create prompt manually for DeepSeek
64
+ deepseek_prompt = prompt_template_str.format(text=text)
65
+
66
+ deepseek_result = deepseek_tool.invoke({"input": deepseek_prompt})
67
+ logging.info(f"Raw DeepSeek Output: {deepseek_result}")
68
+
69
+ cleaned_output = re.sub(r"```(?:json)?\s*([\s\S]*?)\s*```", r"\1", deepseek_result.strip())
70
+
71
+ deepseek_detection = json.loads(cleaned_output)
72
+
73
+ if 'tone' not in deepseek_detection or 'intent' not in deepseek_detection or 'reasoning' not in deepseek_detection:
74
+ logging.error(f"Unexpected format from DeepSeek-V3: {cleaned_output}")
75
+ return {"error": "Response format from DeepSeek-V3 is incorrect. Missing required fields."}
76
+
77
+ return deepseek_detection
78
+
79
+ except Exception as fallback_e:
80
+ logging.error(f"Error with both LLM and DeepSeek-V3: {str(fallback_e)}")
81
+ return {"error": f"An error occurred with both LLM and DeepSeek-V3: {str(fallback_e)}"}
82
+
backend/api/verdict.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from backend.api.claims import classify_claim
2
+ from backend.api.tone_intent import detect_tone_and_intent
3
+ from backend.api.fact_check import fact_check_claim
4
+ import json
5
+
6
+ # final verdict function
7
+ def get_final_verdict(claim_text: str) -> dict:
8
+ try:
9
+ # Classify the claim
10
+ classification = classify_claim(claim_text)
11
+ classification_type = classification.get("category", "Unknown")
12
+
13
+ # If the claim is not a factual claim, return an early verdict
14
+ if classification_type != "Factual Claim":
15
+ return {
16
+ "claim": claim_text,
17
+ "classification": classification_type,
18
+ "verdict": "Claim is not factual.",
19
+ "reasoning": "The claim does not fall under factual category. It is an opinion, irrelevant, or vague."
20
+ }
21
+
22
+ # Detect the tone of the claim
23
+ tone_intent = detect_tone_and_intent(claim_text)
24
+ tone = tone_intent.get("tone", "Unknown")
25
+
26
+ # If the tone is positive (Neutral, Persuasive, Humorous), proceed to fact-check
27
+ if tone in ["Neutral", "Persuasive", "Humorous"]:
28
+ fact_check_result = fact_check_claim(claim_text)
29
+
30
+ # Fact-checking results
31
+ verdict = fact_check_result.get("fact_check_result", "Unknown")
32
+ evidence = fact_check_result.get("evidence", "No evidence available.")
33
+ sources = fact_check_result.get("sources", [])
34
+ reasoning = fact_check_result.get("reasoning", "No reasoning available.")
35
+
36
+ return {
37
+ "claim": claim_text,
38
+ "classification": classification_type,
39
+ "tone": tone,
40
+ "fact_check_result": verdict,
41
+ "evidence": evidence,
42
+ "sources": sources,
43
+ "reasoning": reasoning
44
+ }
45
+ else:
46
+ return {
47
+ "claim": claim_text,
48
+ "classification": classification_type,
49
+ "tone": tone,
50
+ "verdict": "Claim has a non-positive tone.",
51
+ "reasoning": "The claim's tone is considered negative or misleading, hence not processed for fact-checking."
52
+ }
53
+
54
+ except Exception as e:
55
+ return {"error": f"An error occurred while processing the claim: {str(e)}"}
56
+
backend/config.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ load_dotenv()
5
+
6
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
7
+ SERPER_API_KEY = os.getenv("SERPER_API_KEY")
8
+ TOGETHER_API_KEY = os.getenv("TOGETHER_API_KEY")
9
+
10
+ if not OPENAI_API_KEY:
11
+ raise ValueError("OPENAI_API_KEY is not set in environment variables or .env file")
12
+ if not SERPER_API_KEY:
13
+ raise ValueError("SERPER_API_KEY is not set in environment variables or .env file")
14
+ if not TOGETHER_API_KEY:
15
+ raise ValueError("TOGETHER_API_KEY is not set in environment variables or .env file")
16
+
17
+
backend/langchain_tools.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_openai import ChatOpenAI
2
+ from langchain.agents import Tool
3
+ from langchain_community.utilities import WikipediaAPIWrapper
4
+ import requests
5
+ import os
6
+ from backend.config import OPENAI_API_KEY, SERPER_API_KEY
7
+
8
+ llm = ChatOpenAI(
9
+ model="gpt-3.5-turbo",
10
+ temperature=0,
11
+ openai_api_key=OPENAI_API_KEY
12
+ )
13
+
14
+
15
+ import wikipedia
16
+ from langchain.tools import Tool
17
+
18
+ def search_wikipedia(query):
19
+ try:
20
+ results = wikipedia.search(query, results=10) # Fetch top 10 results
21
+ if not results:
22
+ return {"error": "No results found on Wikipedia."}
23
+
24
+ summaries = []
25
+ for title in results:
26
+ try:
27
+ page = wikipedia.page(title)
28
+ summary = page.summary
29
+ url = page.url
30
+ summaries.append({"title": title, "summary": summary, "url": url})
31
+ except wikipedia.exceptions.DisambiguationError as e:
32
+ summaries.append({"error": f"Disambiguation: {str(e)}"})
33
+ except wikipedia.exceptions.HTTPTimeoutError:
34
+ summaries.append({"error": "Wikipedia request timed out."})
35
+ except Exception as e:
36
+ summaries.append({"error": str(e)})
37
+ return summaries
38
+ except Exception as e:
39
+ return {"error": str(e)}
40
+
41
+ # Define the Wikipedia Tool
42
+ wiki_tool = Tool.from_function(
43
+ func=search_wikipedia,
44
+ name="WikipediaAPI",
45
+ description="Fetch summaries from Wikipedia based on search query"
46
+ )
47
+
48
+
49
+ from langchain.tools import Tool
50
+
51
+ def fetch_search_results(query):
52
+ url = "https://google.serper.dev/search"
53
+ headers = {
54
+ "X-API-KEY": SERPER_API_KEY,
55
+ "Content-Type": "application/json"
56
+ }
57
+
58
+ data = {
59
+ "q": query,
60
+ "num": 10
61
+ }
62
+
63
+ try:
64
+ response = requests.post(url, json=data, headers=headers)
65
+ if response.status_code == 200:
66
+ return response.json()
67
+ else:
68
+ return {"error": f"Serper API error: {response.status_code}"}
69
+ except requests.exceptions.RequestException as e:
70
+ return {"error": f"Request failed: {str(e)}"}
71
+
72
+ # Define the Serper Tool
73
+ serper_tool = Tool.from_function(
74
+ func=fetch_search_results,
75
+ name="SerperAPI",
76
+ description="Search the web using the Serper API"
77
+ )
78
+
79
+
80
+ from together import Together
81
+ from backend.config import TOGETHER_API_KEY
82
+
83
+ client = Together(api_key=TOGETHER_API_KEY)
84
+
85
+ def deepseek_v3_query(query):
86
+ try:
87
+ response = client.chat.completions.create(
88
+ model="deepseek-ai/DeepSeek-V3", # DeepSeek-V3 model from Together
89
+ messages=[{"role": "user", "content": query}],
90
+ stream=True
91
+ )
92
+
93
+ result = ""
94
+ for token in response:
95
+ if hasattr(token, 'choices'):
96
+ result += token.choices[0].delta.content
97
+ return result
98
+ except Exception as e:
99
+ return {"error": f"DeepSeek-V3 API error: {str(e)}"}
100
+
101
+ # Define the DeepSeek-V3 Tool
102
+ deepseek_tool = Tool.from_function(
103
+ func=deepseek_v3_query,
104
+ name="DeepSeekV3",
105
+ description="Query DeepSeek-V3 AI model from Together for advanced text completions"
106
+ )
107
+
108
+
109
+ # 5. Group tools together
110
+ search_tools = [wiki_tool, serper_tool, deepseek_tool]
backend/langchain_tools1.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_openai import ChatOpenAI # Corrected import
2
+ from langchain.agents import Tool
3
+ from langchain_community.utilities import WikipediaAPIWrapper
4
+ import requests
5
+ import os
6
+ from backend.config import OPENAI_API_KEY, SERPER_API_KEY
7
+
8
+ # 1. Setup OpenAI Chat Model (using GPT-4)
9
+ llm = ChatOpenAI(
10
+ model="gpt-3.5-turbo",
11
+ temperature=0,
12
+ openai_api_key=OPENAI_API_KEY
13
+ )
14
+
15
+ # 2. Wikipedia Tool for Fact-Checking
16
+ import wikipedia
17
+ from langchain.tools import Tool
18
+
19
+ def search_wikipedia(query):
20
+ try:
21
+ results = wikipedia.search(query, results=10) # Fetch top 10 results
22
+ if not results:
23
+ return {"error": "No results found on Wikipedia."}
24
+
25
+ summaries = []
26
+ for title in results:
27
+ try:
28
+ page = wikipedia.page(title)
29
+ summary = page.summary
30
+ url = page.url
31
+ summaries.append({"title": title, "summary": summary, "url": url})
32
+ except wikipedia.exceptions.DisambiguationError as e:
33
+ summaries.append({"error": f"Disambiguation: {str(e)}"})
34
+ except wikipedia.exceptions.HTTPTimeoutError:
35
+ summaries.append({"error": "Wikipedia request timed out."})
36
+ except Exception as e:
37
+ summaries.append({"error": str(e)})
38
+ return summaries
39
+ except Exception as e:
40
+ return {"error": str(e)}
41
+
42
+ # Define the Wikipedia Tool
43
+ wiki_tool = Tool.from_function(
44
+ func=search_wikipedia,
45
+ name="WikipediaAPI",
46
+ description="Fetch summaries from Wikipedia based on search query"
47
+ )
48
+
49
+
50
+ # 3. Serper Search Tool (Google-like Search)
51
+ from langchain.tools import Tool
52
+
53
+ def fetch_search_results(query):
54
+ url = "https://google.serper.dev/search"
55
+ headers = {
56
+ "X-API-KEY": SERPER_API_KEY,
57
+ "Content-Type": "application/json"
58
+ }
59
+
60
+ data = {
61
+ "q": query,
62
+ "num": 10 # Get 10 results
63
+ }
64
+
65
+ try:
66
+ response = requests.post(url, json=data, headers=headers)
67
+ if response.status_code == 200:
68
+ return response.json()
69
+ else:
70
+ return {"error": f"Serper API error: {response.status_code}"}
71
+ except requests.exceptions.RequestException as e:
72
+ return {"error": f"Request failed: {str(e)}"}
73
+
74
+ # Define the Serper Tool
75
+ serper_tool = Tool.from_function(
76
+ func=fetch_search_results,
77
+ name="SerperAPI",
78
+ description="Search the web using the Serper API"
79
+ )
80
+
81
+
82
+ # 4. DeepSeek-V3 Tool (Integration with Together API)
83
+ from together import Together
84
+ from backend.config import TOGETHER_API_KEY # Ensure you have this in your .env or config file
85
+
86
+ # Initialize Together client with the API key
87
+ client = Together(api_key=TOGETHER_API_KEY)
88
+
89
+ def deepseek_v3_query(query):
90
+ try:
91
+ response = client.chat.completions.create(
92
+ model="deepseek-ai/DeepSeek-V3", # DeepSeek-V3 model from Together
93
+ messages=[{"role": "user", "content": query}],
94
+ stream=True
95
+ )
96
+
97
+ result = ""
98
+ for token in response:
99
+ if hasattr(token, 'choices'):
100
+ result += token.choices[0].delta.content
101
+ return result
102
+ except Exception as e:
103
+ return {"error": f"DeepSeek-V3 API error: {str(e)}"}
104
+
105
+ # Define the DeepSeek-V3 Tool
106
+ deepseek_tool = Tool.from_function(
107
+ func=deepseek_v3_query,
108
+ name="DeepSeekV3",
109
+ description="Query DeepSeek-V3 AI model from Together for advanced text completions"
110
+ )
111
+
112
+
113
+ # 5. Group tools together
114
+ search_tools = [wiki_tool, serper_tool, deepseek_tool]
backend/main.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from fastapi import FastAPI, Form, Request
3
+ from fastapi.responses import HTMLResponse
4
+ from fastapi.templating import Jinja2Templates
5
+ from backend.api.fact_check import fact_check_claim
6
+ from backend.api.claims import classify_claim
7
+ from backend.api.tone_intent import detect_tone_and_intent
8
+ import json
9
+
10
+ app = FastAPI()
11
+
12
+
13
+
14
+ # Jinja2 templates for rendering HTML pages
15
+ templates = Jinja2Templates(directory="frontend")
16
+ from pathlib import Path
17
+ from fastapi.staticfiles import StaticFiles
18
+
19
+ BASE_DIR = Path(__file__).resolve().parent
20
+ STATIC_DIR = BASE_DIR.parent / "frontend" / "assets"
21
+ app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
22
+
23
+
24
+ @app.get("/", response_class=HTMLResponse)
25
+ async def read_root(request: Request):
26
+ return templates.TemplateResponse("index.html", {"request": request})
27
+
28
+ @app.post("/process_claim")
29
+ async def process_claim(claim: str = Form(...)):
30
+ # Classify the claim
31
+ classification = classify_claim(claim)
32
+ classification_type = classification.get("category", "Unknown")
33
+
34
+ # Detect the tone and intent of the claim
35
+ tone_intent = detect_tone_and_intent(claim)
36
+ tone = tone_intent.get("tone", "Unknown")
37
+ intent = tone_intent.get("intent", "Unknown")
38
+
39
+ # fact-check the claim if it's factual and tone is positive
40
+ if classification_type in ["Factual Claim", "Misleading Claim", "Factoid"] and tone in ["Neutral", "Persuasive", "Humorous"]:
41
+ fact_check_result = fact_check_claim(claim)
42
+ verdict = fact_check_result.get("fact_check_result", "Unknown")
43
+ evidence = fact_check_result.get("evidence", "No evidence available.")
44
+ sources = fact_check_result.get("sources", [])
45
+ reasoning = fact_check_result.get("reasoning", "No reasoning available.")
46
+ else:
47
+ verdict = "Not Fact-Checked"
48
+ evidence = "Claim is either not factual or has a non-positive tone."
49
+ sources = []
50
+ reasoning = "The claim either isn’t factual or has a misleading tone, hence skipped from fact-checking."
51
+
52
+ return {
53
+ "claim": claim,
54
+ "classification": classification_type,
55
+ "tone": tone,
56
+ "intent": intent,
57
+ "fact_check_result": verdict,
58
+ "evidence": evidence,
59
+ "sources": sources,
60
+ "reasoning": reasoning
61
+ }
frontend/index.html ADDED
@@ -0,0 +1,802 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <html lang="en">
2
+
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="description"
6
+ content="FALCON is an AI-powered tool for verifying the authenticity of claims. Check if a claim is factual, misleading, or opinion-based using trusted data sources.">
7
+ <meta name="keywords"
8
+ content="AI fact-checking, claim verification, fake news detection, NLP models, fact-checking tool, verify claims">
9
+
10
+ <meta content="width=device-width, initial-scale=1" name="viewport" />
11
+ <title>FALCON - Fake news Analysis and Language Comprehension for Online Neutrality </title>
12
+ <script src="https://cdn.tailwindcss.com"></script>
13
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet" />
14
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet" />
15
+ <style>
16
+ html {
17
+ scroll-behavior: smooth;
18
+ }
19
+
20
+ body {
21
+ font-family: "Inter", sans-serif;
22
+ background-color: #0c1a36;
23
+ /* dark blue background */
24
+ color: #ffffff;
25
+ /* white text */
26
+ min-height: 100vh;
27
+ display: flex;
28
+ flex-direction: column;
29
+ }
30
+
31
+ /* Fade-in animation */
32
+ .fade-in {
33
+ animation: fadeIn 0.8s ease forwards;
34
+ opacity: 0;
35
+ }
36
+
37
+ @keyframes fadeIn {
38
+ to {
39
+ opacity: 1;
40
+ }
41
+ }
42
+
43
+ /* Stylish button hover and focus */
44
+ button,
45
+ a.button-link {
46
+ position: relative;
47
+ overflow: hidden;
48
+ transition: color 0.3s ease;
49
+ z-index: 0;
50
+ }
51
+
52
+ button::before,
53
+ a.button-link::before {
54
+ content: "";
55
+ position: absolute;
56
+ top: 50%;
57
+ left: 50%;
58
+ width: 0;
59
+ height: 300%;
60
+ background: #2563eb;
61
+ transform: translate(-50%, -50%) rotate(45deg);
62
+ transition: width 0.4s ease;
63
+ z-index: -1;
64
+ border-radius: 12px;
65
+ }
66
+
67
+ button:hover::before,
68
+ button:focus::before,
69
+ a.button-link:hover::before,
70
+ a.button-link:focus::before {
71
+ width: 250%;
72
+ }
73
+
74
+ button:hover,
75
+ button:focus,
76
+ a.button-link:hover,
77
+ a.button-link:focus {
78
+ color: #dbeafe;
79
+ outline: none;
80
+ box-shadow: 0 0 12px #3b82f6;
81
+ }
82
+
83
+ /* Input and textarea focus glow */
84
+ input:focus,
85
+ textarea:focus {
86
+ box-shadow: 0 0 8px #3b82f6;
87
+ border-color: #3b82f6;
88
+ background-color: #142a5c;
89
+ color: #ffffff;
90
+ }
91
+
92
+ /* Scrollbar styling for result panel */
93
+ .result-scrollbar::-webkit-scrollbar {
94
+ height: 6px;
95
+ }
96
+
97
+ .result-scrollbar::-webkit-scrollbar-track {
98
+ background: #142a5c;
99
+ border-radius: 3px;
100
+ }
101
+
102
+ .result-scrollbar::-webkit-scrollbar-thumb {
103
+ background: #3b82f6;
104
+ border-radius: 3px;
105
+ }
106
+
107
+ /* Override Tailwind default whites for backgrounds and borders */
108
+ header,
109
+ footer,
110
+ section.bg-white {
111
+ background-color: #142a5c;
112
+ /* darker blue slate */
113
+ }
114
+
115
+ header {
116
+ box-shadow: 0 2px 15px rgb(59 130 246 / 0.6);
117
+ }
118
+
119
+ footer {
120
+ border-top-color: #1e3a8a;
121
+ }
122
+
123
+ section.bg-white {
124
+ box-shadow: 0 4px 25px rgb(59 130 246 / 0.6);
125
+ }
126
+
127
+ input,
128
+ button,
129
+ textarea {
130
+ background-color: #0c1a36;
131
+ border-color: #3b82f6;
132
+ color: #ffffff;
133
+ }
134
+
135
+ input::placeholder,
136
+ textarea::placeholder {
137
+ color: #a5b4fc;
138
+ }
139
+
140
+ /* Result section styled like other sections */
141
+ #result {
142
+ background-color: #142a5c;
143
+ border-radius: 1rem;
144
+ box-shadow: 0 4px 25px rgb(59 130 246 / 0.6);
145
+ padding: 2.5rem 3rem;
146
+ max-width: 920px;
147
+ margin-left: auto;
148
+ margin-right: auto;
149
+ opacity: 0;
150
+ pointer-events: none;
151
+ transition: opacity 0.5s ease;
152
+ color: #dbeafe;
153
+ font-family: "Inter", sans-serif;
154
+ }
155
+
156
+ #result.visible {
157
+ opacity: 1;
158
+ pointer-events: auto;
159
+ }
160
+
161
+ #result h2 {
162
+ font-size: 2rem;
163
+ font-weight: 700;
164
+ margin-bottom: 1.5rem;
165
+ color: #bfdbfe;
166
+ text-align: center;
167
+ user-select: none;
168
+ }
169
+
170
+ #result p {
171
+ font-size: 1rem;
172
+ line-height: 1.6;
173
+ margin-bottom: 1rem;
174
+ user-select: text;
175
+ }
176
+
177
+ #result p span.font-semibold {
178
+ color: #93c5fd;
179
+ font-weight: 600;
180
+ }
181
+
182
+ /* Link styling in result */
183
+ #sources a {
184
+ color: #bfdbfe;
185
+ font-weight: 600;
186
+ transition: color 0.3s ease;
187
+ }
188
+
189
+ #sources a:hover,
190
+ #sources a:focus {
191
+ color: #60a5fa;
192
+ outline: none;
193
+ text-decoration: underline;
194
+ }
195
+
196
+ /* Header nav link underline on hover with animation */
197
+ nav a {
198
+ position: relative;
199
+ padding-bottom: 0.25rem;
200
+ color: #ffffff;
201
+ font-weight: 600;
202
+ transition: color 0.3s ease;
203
+ }
204
+
205
+ nav a::after {
206
+ content: "";
207
+ position: absolute;
208
+ width: 0%;
209
+ height: 3px;
210
+ bottom: 0;
211
+ left: 0;
212
+ background: linear-gradient(90deg, #3b82f6, #60a5fa);
213
+ transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
214
+ border-radius: 3px;
215
+ }
216
+
217
+ nav a:hover,
218
+ nav a:focus {
219
+ color: #60a5fa;
220
+ outline: none;
221
+ }
222
+
223
+ nav a:hover::after,
224
+ nav a:focus::after {
225
+ width: 100%;
226
+ }
227
+
228
+ /* Loading animation - rotating rings */
229
+ #loadingScreen {
230
+ background-color: #0c1a36;
231
+ display: flex;
232
+ justify-content: center;
233
+ align-items: center;
234
+ z-index: 50;
235
+ }
236
+
237
+ .loader {
238
+ position: relative;
239
+ width: 64px;
240
+ height: 64px;
241
+ animation: rotate 1.5s linear infinite;
242
+ }
243
+
244
+ .loader div {
245
+ position: absolute;
246
+ border: 4px solid transparent;
247
+ border-top-color: #3b82f6;
248
+ border-radius: 50%;
249
+ animation: spin 1s ease-in-out infinite;
250
+ }
251
+
252
+ .loader div:nth-child(1) {
253
+ width: 64px;
254
+ height: 64px;
255
+ top: 0;
256
+ left: 0;
257
+ animation-delay: -0.2s;
258
+ }
259
+
260
+ .loader div:nth-child(2) {
261
+ width: 48px;
262
+ height: 48px;
263
+ top: 8px;
264
+ left: 8px;
265
+ border-top-color: #60a5fa;
266
+ animation-delay: -0.4s;
267
+ }
268
+
269
+ .loader div:nth-child(3) {
270
+ width: 32px;
271
+ height: 32px;
272
+ top: 16px;
273
+ left: 16px;
274
+ border-top-color: #93c5fd;
275
+ animation-delay: -0.6s;
276
+ }
277
+
278
+ @keyframes rotate {
279
+ 100% {
280
+ transform: rotate(360deg);
281
+ }
282
+ }
283
+
284
+ @keyframes spin {
285
+
286
+ 0%,
287
+ 100% {
288
+ transform: rotate(0deg);
289
+ }
290
+
291
+ 50% {
292
+ transform: rotate(180deg);
293
+ }
294
+ }
295
+
296
+ /* Form error styling */
297
+ #error {
298
+ color: #f87171;
299
+ font-weight: 600;
300
+ user-select: none;
301
+ }
302
+
303
+ /* Input and button width */
304
+ #formSection form input[type="text"] {
305
+ max-width: 900px;
306
+ width: 100%;
307
+ }
308
+
309
+ #formSection form button {
310
+ max-width: 900px;
311
+ width: 100%;
312
+ }
313
+
314
+ #formSection {
315
+ max-width: 920px;
316
+ }
317
+
318
+ /* Responsive adjustments */
319
+ @media (max-width: 1024px) {
320
+ #formSection {
321
+ max-width: 100%;
322
+ padding-left: 1.5rem;
323
+ padding-right: 1.5rem;
324
+ }
325
+
326
+ #result {
327
+ max-width: 100%;
328
+ padding-left: 1.5rem;
329
+ padding-right: 1.5rem;
330
+ }
331
+ }
332
+
333
+ /* Loading overlay for analyze button */
334
+ #analyzeLoadingOverlay {
335
+ position: absolute;
336
+ inset: 0;
337
+ background: rgba(14, 42, 94, 0.7);
338
+ border-radius: 0.5rem;
339
+ display: flex;
340
+ justify-content: center;
341
+ align-items: center;
342
+ pointer-events: none;
343
+ opacity: 0;
344
+ transition: opacity 0.3s ease;
345
+ z-index: 10;
346
+ }
347
+
348
+ #analyzeLoadingOverlay.visible {
349
+ opacity: 1;
350
+ pointer-events: auto;
351
+ }
352
+
353
+ /* Spinner for analyze button */
354
+ .btn-spinner {
355
+ border: 3px solid transparent;
356
+ border-top-color: #60a5fa;
357
+ border-radius: 50%;
358
+ width: 24px;
359
+ height: 24px;
360
+ animation: spinBtn 1s linear infinite;
361
+ }
362
+
363
+ @keyframes spinBtn {
364
+ 0% {
365
+ transform: rotate(0deg);
366
+ }
367
+
368
+ 100% {
369
+ transform: rotate(360deg);
370
+ }
371
+ }
372
+
373
+ /* Position relative for button container */
374
+ .button-container {
375
+ position: relative;
376
+ display: inline-block;
377
+ width: 100%;
378
+ }
379
+ </style>
380
+ </head>
381
+
382
+ <body class="flex flex-col min-h-screen">
383
+ <!-- Loading Screen -->
384
+ <div aria-label="Loading animation" class="fixed inset-0 flex justify-center items-center" id="loadingScreen">
385
+ <div class="loader" aria-hidden="true">
386
+ <div></div>
387
+ <div></div>
388
+ <div></div>
389
+ </div>
390
+ </div>
391
+
392
+ <!-- Header -->
393
+ <header aria-label="Primary Navigation" class="w-full bg-[#142a5c] shadow-md sticky top-0 z-40" role="banner">
394
+ <nav aria-label="Main navigation"
395
+ class="max-w-7xl mx-auto px-6 sm:px-8 lg:px-12 flex items-center justify-between h-18">
396
+ <a aria-label="FALCON Home"
397
+ class="flex items-center space-x-3 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
398
+ href="#home">
399
+ <img alt="FALCON logo "
400
+ class="w-50 h-50 rounded-md" height="64" loading="lazy" src="/static/falcon-logo.svg" width="100" />
401
+
402
+ <span class="text-3xl font-extrabold select-none text-white tracking-wide">FALCON</span>
403
+ </a>
404
+ <button aria-controls="primary-navigation" aria-expanded="false"
405
+ class="sm:hidden text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
406
+ id="navToggle">
407
+ <svg aria-hidden="true" class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"
408
+ xmlns="http://www.w3.org/2000/svg">
409
+ <path d="M4 8h16M4 16h16" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></path>
410
+ </svg>
411
+ <span class="sr-only">Toggle navigation menu</span>
412
+ </button>
413
+ <ul class="hidden sm:flex space-x-10 font-medium text-lg" id="primary-navigation">
414
+ <li>
415
+ <a class="hover:text-[#60a5fa] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 rounded"
416
+ href="#home">Home</a>
417
+ </li>
418
+ <li>
419
+ <a class="hover:text-[#60a5fa] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 rounded"
420
+ href="#about">About Us</a>
421
+ </li>
422
+ <li>
423
+ <a class="hover:text-[#60a5fa] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 rounded"
424
+ href="#contact">Contact Us</a>
425
+ </li>
426
+ </ul>
427
+ </nav>
428
+ <!-- Mobile menu -->
429
+ <div aria-label="Mobile navigation menu" class="sm:hidden bg-[#142a5c] border-t border-blue-900 hidden"
430
+ id="mobileMenu" role="menu">
431
+ <ul class="flex flex-col space-y-2 px-6 py-4 font-medium text-lg">
432
+ <li>
433
+ <a class="block py-2 hover:text-[#60a5fa] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 rounded"
434
+ href="#home" role="menuitem">Home</a>
435
+ </li>
436
+ <li>
437
+ <a class="block py-2 hover:text-[#60a5fa] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 rounded"
438
+ href="#about" role="menuitem">About Us</a>
439
+ </li>
440
+ <li>
441
+ <a class="block py-2 hover:text-[#60a5fa] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 rounded"
442
+ href="#contact" role="menuitem">Contact Us</a>
443
+ </li>
444
+ </ul>
445
+ </div>
446
+ </header>
447
+
448
+ <!-- Main Content -->
449
+ <main class="flex-grow max-w-7xl mx-auto px-6 sm:px-8 lg:px-12 py-12 fade-in" id="mainContent" tabindex="-1">
450
+ <!-- Home Section -->
451
+ <section id="home" class="mb-20">
452
+ <div class="bg-[#142a5c] rounded-xl shadow-lg p-10 flex flex-col items-center text-center space-y-6">
453
+ <h1 class="text-5xl font-extrabold select-none leading-tight text-white drop-shadow-lg">
454
+ FALCON : AI-Powered Claim Fact-Checker
455
+ </h1>
456
+ <p class="max-w-3xl text-lg text-blue-200">
457
+ Instantly verify the accuracy of claims with FALCON, an advanced AI-powered fact-checking tool.
458
+ Powered by NLP and trusted data sources, FALCON helps detect factual, misleading, or opinion-based
459
+ content with ease.
460
+ </p><img alt="FALCON logo"
461
+ class=" rounded-md" height="64" loading="lazy" src="/static/falcon-logo.svg" width="200" />
462
+ <a href="#formSection"
463
+ class="button-link inline-block bg-transparent border-2 border-[#3b82f6] text-white font-semibold rounded-md py-3 px-8 transition focus:outline-none focus:ring-4 focus:ring-[#2563eb]"
464
+ title="Start fact-checking claims now">
465
+ Start Fact-Checking
466
+ </a>
467
+ </div>
468
+ </section>
469
+
470
+
471
+ <!-- Form Section -->
472
+ <section id="formSection" aria-labelledby="formTitle"
473
+ class="bg-[#142a5c] rounded-xl shadow-lg p-8 sm:p-10 mx-auto mb-20 max-w-[920px]">
474
+ <h2 class="sr-only" id="formTitle">Check your claim</h2>
475
+ <form class="space-y-6" id="claimForm" novalidate>
476
+ <label class="block text-white font-semibold text-lg mb-2" for="claim">Enter a Claim (max 350
477
+ characters):</label>
478
+ <input aria-describedby="charCount"
479
+ class="w-full rounded-md border border-[#3b82f6] px-4 py-3 text-white placeholder-blue-300 focus:outline-none focus:ring-2 focus:ring-[#60a5fa] focus:border-[#60a5fa] transition"
480
+ id="claim" maxlength="350" name="claim" placeholder="Type your claim here..." required type="text"
481
+ spellcheck="false" autocomplete="off" />
482
+ <p aria-atomic="true" aria-live="polite" class="text-sm text-blue-300 select-none" id="charCount">
483
+ 350 characters remaining
484
+ </p>
485
+ <div class="button-container">
486
+ <button aria-label="Check Claim"
487
+ class="button-link w-full bg-transparent border-2 border-[#3b82f6] text-white font-semibold rounded-md py-3 transition focus:outline-none focus:ring-4 focus:ring-[#2563eb] flex justify-center items-center space-x-3"
488
+ type="submit">
489
+ <span>Check Claim</span>
490
+ </button>
491
+ <div id="analyzeLoadingOverlay" aria-hidden="true" class="rounded-md">
492
+ <div class="btn-spinner" aria-label="Loading"></div>
493
+ </div>
494
+ </div>
495
+ </form>
496
+ </section>
497
+
498
+ <!-- Result Section -->
499
+ <section aria-atomic="true" aria-live="polite" id="result" tabindex="0" aria-label="Fact check result output"
500
+ class="mb-20">
501
+ <h2>Result</h2>
502
+ <div class="space-y-4">
503
+ <p><span class="font-semibold">Claim:</span> <span id="claimText"></span></p>
504
+ <p><span class="font-semibold">Classification:</span> <span id="classification"></span></p>
505
+ <p><span class="font-semibold">Tone:</span> <span id="tone"></span></p>
506
+ <p><span class="font-semibold">Intent:</span> <span id="intent"></span></p>
507
+ <p><span class="font-semibold">Fact-Check Result:</span> <span id="factCheckResult"></span></p>
508
+ <p><span class="font-semibold">Evidence:</span> <span id="evidence"></span></p>
509
+ <p><span class="font-semibold">Sources:</span> <span id="sources" class="underline"></span></p>
510
+ <p><span class="font-semibold">Reasoning:</span> <span id="reasoning"></span></p>
511
+ </div>
512
+ </section>
513
+
514
+ <section id="about" class="bg-[#142a5c] rounded-xl shadow-lg p-10 max-w-4xl mx-auto mb-20"
515
+ aria-labelledby="aboutTitle">
516
+ <h2 id="aboutTitle" class="text-3xl font-semibold mb-6 text-center select-none text-white drop-shadow-md">
517
+ About Us</h2>
518
+ <div class="flex flex-col sm:flex-row sm:space-x-10 items-center sm:items-start">
519
+ <!-- Icons & Short Descriptions -->
520
+ <div class="flex space-x-6 mb-8 sm:mb-0 justify-center sm:justify-start w-full sm:w-auto">
521
+ <div class="flex flex-col items-center space-y-3 text-white drop-shadow-md max-w-xs">
522
+ <i class="fas fa-robot fa-3x"></i>
523
+ <p class="text-center">AI-powered fake news detection</p>
524
+ </div>
525
+ <div class="flex flex-col items-center space-y-3 text-white drop-shadow-md max-w-xs">
526
+ <i class="fas fa-shield-alt fa-3x"></i>
527
+ <p class="text-center">Reliable, trustworthy fact-checking</p>
528
+ </div>
529
+ <div class="flex flex-col items-center space-y-3 text-white drop-shadow-md max-w-xs">
530
+ <i class="fas fa-users fa-3x"></i>
531
+ <p class="text-center">Committed to transparency & accuracy</p>
532
+ </div>
533
+ </div>
534
+ <!-- Short Description Text -->
535
+ <div class="text-blue-100 max-w-xl text-center sm:text-left">
536
+ <p>
537
+ FALCON is an AI-powered fact-checking platform designed to quickly verify claims, detect
538
+ misinformation,
539
+ and provide users with reliable results. It uses AI-driven analysis to classify claims, assess
540
+ tone and intent,
541
+ and fact-check the information based on trusted sources.
542
+ </p>
543
+ <p class="mt-4">
544
+ Our goal is to empower users with accurate, timely information, helping to build a more
545
+ informed, responsible society
546
+ by combating fake news and misinformation effectively.
547
+ </p>
548
+ <p class="mt-4">
549
+ FALCON is developed by <strong>Saksham Pathak</strong>, a student currently pursuing his
550
+ Master's degree at IIIT Lucknow.
551
+ </p>
552
+ </div>
553
+
554
+
555
+ </div>
556
+ </section>
557
+
558
+
559
+
560
+
561
+ <!-- Contact Us Section -->
562
+ <section id="contact" class="bg-[#142a5c] rounded-xl shadow-lg p-10 max-w-3xl mx-auto mb-20"
563
+ aria-labelledby="contactTitle">
564
+ <h2 id="contactTitle" class="text-3xl font-semibold mb-6 text-center select-none text-white drop-shadow-md">
565
+ Contact Us
566
+ </h2>
567
+ <form id="contactForm" class="space-y-6" novalidate>
568
+ <div>
569
+ <label for="name" class="block mb-2 font-semibold text-white">Name</label>
570
+ <input type="text" id="name" name="name" placeholder="Your name" required
571
+ class="w-full rounded-md border border-[#3b82f6] px-4 py-3 text-white placeholder-blue-300 focus:outline-none focus:ring-2 focus:ring-[#60a5fa] focus:border-[#60a5fa] transition"
572
+ spellcheck="false" autocomplete="off" />
573
+ </div>
574
+ <div>
575
+ <label for="email" class="block mb-2 font-semibold text-white">Email</label>
576
+ <input type="email" id="email" name="email" placeholder="yourname@gmail.com" required
577
+ class="w-full rounded-md border border-[#3b82f6] px-4 py-3 text-white placeholder-blue-300 focus:outline-none focus:ring-2 focus:ring-[#60a5fa] focus:border-[#60a5fa] transition"
578
+ spellcheck="false" autocomplete="off" />
579
+ </div>
580
+ <div>
581
+ <label for="message" class="block mb-2 font-semibold text-white">Message</label>
582
+ <textarea id="message" name="message" rows="4" placeholder="Your message" required
583
+ class="w-full rounded-md border border-[#3b82f6] px-4 py-3 text-white placeholder-blue-300 focus:outline-none focus:ring-2 focus:ring-[#60a5fa] focus:border-[#60a5fa] transition resize-none"
584
+ spellcheck="false"></textarea>
585
+ </div>
586
+ <button type="submit"
587
+ class="button-link w-full bg-transparent border-2 border-[#3b82f6] text-white font-semibold rounded-md py-3 transition focus:outline-none focus:ring-4 focus:ring-[#2563eb]">
588
+ Send Message
589
+ </button>
590
+ </form>
591
+ <div class="mt-8 text-center text-blue-300 select-none drop-shadow-md">
592
+ <p>If you have any questions or inquiries about FALCON or need assistance with using the platform, feel
593
+ free to reach out to us.</p>
594
+ <p class="mt-4">Email: <a href="mailto:msa24021@iiit.ac.in"
595
+ class="text-[#60a5fa]">msa24021@iiit.ac.in</a></p>
596
+ <p>Location: Saksham Pathak, IIIT Lucknow, India</p>
597
+ </div>
598
+ </section>
599
+ </main>
600
+
601
+ <!-- Footer -->
602
+ <footer class="bg-[#142a5c] border-t border-blue-900 py-8 select-none" role="contentinfo">
603
+ <div
604
+ class="max-w-7xl mx-auto px-6 sm:px-8 lg:px-12 flex flex-col sm:flex-row justify-between items-center space-y-4 sm:space-y-0">
605
+ <p class="text-blue-300 text-sm drop-shadow-md">© FALCON : AI-Powered Fake News Detector </p>
606
+ <div class="flex space-x-6 text-blue-300 hover:text-[#60a5fa] transition-colors drop-shadow-md">
607
+ <a aria-label="LinkedIn"
608
+ class="text-xl focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 rounded"
609
+ href="https://www.linkedin.com/in/sakshampathak" rel="noopener noreferrer" target="_blank">
610
+ <i class="fab fa-linkedin-in"></i>
611
+ </a>
612
+ <a aria-label="GitHub"
613
+ class="text-xl focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 rounded"
614
+ href="https://github.com/parthmax2" rel="noopener noreferrer" target="_blank"><i
615
+ class="fab fa-github"></i></a>
616
+ <a aria-label="Twitter"
617
+ class="text-xl focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 rounded"
618
+ href="https://twitter.com/Parthamx__" rel="noopener noreferrer" target="_blank"><i
619
+ class="fab fa-twitter"></i></a>
620
+ <a aria-label="Instagram"
621
+ class="text-xl focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 rounded"
622
+ href="https://instagram.com/parthmax_" rel="noopener noreferrer" target="_blank"><i
623
+ class="fab fa-instagram"></i></a>
624
+ </div>
625
+ </div>
626
+ <div class="text-blue-200 text-sm mt-4 text-center">
627
+ <p>Developed by <a href="https://instagram.com/parthmax_"
628
+ class="hover:text-[#60a5fa]"><strong>Parthamx</strong></a></p>
629
+ </div>
630
+
631
+
632
+ <p class="text-blue-300 text-sm mt-4 text-center">Follow us on social media for the latest updates!</p>
633
+
634
+ </footer>
635
+
636
+
637
+
638
+
639
+ <script>
640
+ // Mobile nav toggle
641
+ const navToggle = document.getElementById("navToggle");
642
+ const mobileMenu = document.getElementById("mobileMenu");
643
+ navToggle.addEventListener("click", () => {
644
+ const expanded =
645
+ navToggle.getAttribute("aria-expanded") === "true" || false;
646
+ navToggle.setAttribute("aria-expanded", !expanded);
647
+ mobileMenu.classList.toggle("hidden");
648
+ });
649
+
650
+ // Loading screen fade out after page load
651
+ window.addEventListener("load", () => {
652
+ const loadingScreen = document.getElementById("loadingScreen");
653
+ setTimeout(() => {
654
+ loadingScreen.style.opacity = "0";
655
+ loadingScreen.style.transition = "opacity 0.5s ease";
656
+ setTimeout(() => {
657
+ loadingScreen.style.display = "none";
658
+ // Focus main content for accessibility
659
+ document.getElementById("mainContent").focus();
660
+ }, 500);
661
+ }, 1200);
662
+ });
663
+
664
+ // Character count update
665
+ const claimInput = document.getElementById("claim");
666
+ const charCount = document.getElementById("charCount");
667
+ claimInput.addEventListener("input", () => {
668
+ const length = claimInput.value.length;
669
+ const remaining = 350 - length;
670
+ charCount.textContent = remaining + " characters remaining";
671
+ if (remaining <= 0) {
672
+ charCount.classList.add("text-red-600");
673
+ charCount.classList.remove("text-blue-300");
674
+ } else {
675
+ charCount.classList.remove("text-red-600");
676
+ charCount.classList.add("text-blue-300");
677
+ }
678
+ });
679
+
680
+ // Form submission and result display with loading overlay on button
681
+ const form = document.getElementById("claimForm");
682
+ const resultSection = document.getElementById("result");
683
+ const errorDiv = document.createElement("div");
684
+ errorDiv.id = "error";
685
+ errorDiv.setAttribute("role", "alert");
686
+ errorDiv.setAttribute("aria-live", "assertive");
687
+ errorDiv.className =
688
+ "mt-6 max-w-3xl mx-auto font-semibold text-center select-none text-red-600";
689
+ form.after(errorDiv);
690
+ errorDiv.style.display = "none";
691
+
692
+ const analyzeLoadingOverlay = document.getElementById("analyzeLoadingOverlay");
693
+ const submitBtn = form.querySelector("button[type=submit]");
694
+
695
+ form.addEventListener("submit", async (e) => {
696
+ e.preventDefault();
697
+
698
+ const claimText = claimInput.value.trim();
699
+
700
+ if (claimText.length === 0) {
701
+ errorDiv.textContent = "Please enter a claim.";
702
+ errorDiv.style.display = "block";
703
+ resultSection.classList.remove("visible");
704
+ return;
705
+ }
706
+
707
+ if (claimText.length > 350) {
708
+ errorDiv.textContent = "Claim exceeds 350 characters!";
709
+ errorDiv.style.display = "block";
710
+ resultSection.classList.remove("visible");
711
+ return;
712
+ }
713
+
714
+ errorDiv.style.display = "none";
715
+
716
+ // Show loading overlay on button
717
+ submitBtn.disabled = true;
718
+ analyzeLoadingOverlay.classList.add("visible");
719
+ submitBtn.querySelector("span").textContent = "Checking...";
720
+
721
+ try {
722
+ const response = await fetch("/process_claim", {
723
+ method: "POST",
724
+ body: new URLSearchParams({ claim: claimText }),
725
+ headers: {
726
+ "Content-Type": "application/x-www-form-urlencoded",
727
+ "ngrok-skip-browser-warning": "true"
728
+ },
729
+ });
730
+
731
+ if (!response.ok) {
732
+ throw new Error("Network response was not ok");
733
+ }
734
+
735
+ const data = await response.json();
736
+
737
+ if (data.error) {
738
+ errorDiv.textContent = data.error;
739
+ errorDiv.style.display = "block";
740
+ resultSection.classList.remove("visible");
741
+ } else {
742
+ errorDiv.style.display = "none";
743
+
744
+ // Populate result fields
745
+ document.getElementById("claimText").textContent = data.claim || "N/A";
746
+ document.getElementById("classification").textContent =
747
+ data.classification || "N/A";
748
+ document.getElementById("tone").textContent = data.tone || "N/A";
749
+ document.getElementById("intent").textContent = data.intent || "N/A";
750
+ document.getElementById("factCheckResult").textContent =
751
+ data.fact_check_result || "N/A";
752
+ document.getElementById("evidence").textContent = data.evidence || "N/A";
753
+
754
+ if (Array.isArray(data.sources) && data.sources.length > 0) {
755
+ const sourcesContainer = document.getElementById("sources");
756
+ sourcesContainer.innerHTML = "";
757
+ data.sources.forEach((s, index) => {
758
+ const a = document.createElement("a");
759
+ a.href = s.url;
760
+ a.target = "_blank";
761
+ a.rel = "noopener noreferrer";
762
+ a.className = "hover:text-[#60a5fa] focus:outline-none focus:ring-2 focus:ring-indigo-500 rounded";
763
+ a.textContent = s.source;
764
+ sourcesContainer.appendChild(a);
765
+ if (index < data.sources.length - 1) {
766
+ sourcesContainer.appendChild(document.createTextNode(", "));
767
+ }
768
+ });
769
+ } else {
770
+ document.getElementById("sources").textContent = "N/A";
771
+ }
772
+
773
+ document.getElementById("reasoning").textContent = data.reasoning || "N/A";
774
+
775
+ // Show result with fade-in
776
+ resultSection.classList.add("visible");
777
+ resultSection.focus();
778
+ }
779
+ } catch (error) {
780
+ errorDiv.textContent = "An error occurred. Please try again later.";
781
+ errorDiv.style.display = "block";
782
+ resultSection.classList.remove("visible");
783
+ } finally {
784
+ submitBtn.disabled = false;
785
+ analyzeLoadingOverlay.classList.remove("visible");
786
+ submitBtn.querySelector("span").textContent = "Check Claim";
787
+ }
788
+ });
789
+
790
+ // Contact form submission (dummy)
791
+ const contactForm = document.getElementById("contactForm");
792
+ contactForm.addEventListener("submit", (e) => {
793
+ e.preventDefault();
794
+ alert(
795
+ "Thank you for reaching out! We will contact you soon. "
796
+ );
797
+ contactForm.reset();
798
+ });
799
+ </script>
800
+ </body>
801
+
802
+ </html>
requirements.txt ADDED
Binary file (2.62 kB). View file
 
test.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from backend.api.fact_check import fact_check_claim
2
+
3
+
4
+ if __name__ == "__main__":
5
+ sample_text = "Modi is a great leader who changed India forever!"
6
+ result = fact_check_claim(sample_text)
7
+ print(result)