Arko007 commited on
Commit
583611b
Β·
verified Β·
1 Parent(s): 68068ed

Fix cache_dir crash, modernize UI, switch to Groq

Browse files
Files changed (5) hide show
  1. .streamlit/config.toml +13 -0
  2. Dockerfile +3 -2
  3. README.md +38 -29
  4. requirements.txt +9 -9
  5. src/streamlit_app.py +583 -228
.streamlit/config.toml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [theme]
2
+ base = "light"
3
+ primaryColor = "#6366f1"
4
+ backgroundColor = "#f5f7ff"
5
+ secondaryBackgroundColor = "#ffffff"
6
+ textColor = "#1e293b"
7
+ font = "sans serif"
8
+
9
+ [server]
10
+ headless = true
11
+
12
+ [browser]
13
+ gatherUsageStats = false
Dockerfile CHANGED
@@ -9,12 +9,13 @@ RUN apt-get update && apt-get install -y \
9
  && rm -rf /var/lib/apt/lists/*
10
 
11
  COPY requirements.txt ./
 
12
  COPY src/ ./src/
13
 
14
- RUN pip3 install -r requirements.txt
15
 
16
  EXPOSE 8501
17
 
18
  HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
19
 
20
- ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
 
9
  && rm -rf /var/lib/apt/lists/*
10
 
11
  COPY requirements.txt ./
12
+ COPY .streamlit/ .streamlit/
13
  COPY src/ ./src/
14
 
15
+ RUN pip3 install --no-cache-dir -r requirements.txt
16
 
17
  EXPOSE 8501
18
 
19
  HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
20
 
21
+ ENTRYPOINT ["streamlit", "run", "src/streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"]
README.md CHANGED
@@ -1,55 +1,64 @@
1
  ---
2
  title: Credo AI
3
  emoji: πŸš€
4
- colorFrom: red
5
- colorTo: red
6
  sdk: docker
7
  app_port: 8501
8
- tags:
9
- - streamlit
10
  pinned: false
11
  short_description: The Two-Brain Misinformation Detector
12
  license: mit
13
  ---
14
- Credo AI - The Two-Brain Detector emoji: 🧠⚑️ colorFrom: blue colorTo: purple sdk: streamlit sdk_version: 1.33.0 app_file: app.py pinned: false
15
- 🧠 Credo AI: The Two-Brain Misinformation Detector
16
- Welcome to the live demo for our Hack2Skill Hackathon project!
17
 
18
- This application is a proof-of-concept powered by a unique "Two-Brain" AI system designed to provide both rapid and in-depth analysis of news articles and other text. It demonstrates a powerful new architecture for tackling misinformation.
19
 
20
- Key Features
21
- Live URL & Text Analysis: Paste any text or a URL to a news article to get an instant analysis.
22
 
23
- Dual-AI Verdict: Get a fast, high-confidence FAKE/REAL verdict from our specialist model, alongside a nuanced 6-point breakdown from our expert model.
24
 
25
- Gemini-Powered Summaries: Receive a clear, conversational summary of the findings, powered by Google's Gemini API.
26
 
27
- Analysis History: All of your queries are saved in your session and can be reviewed on the "Analysis History" page.
 
 
 
 
 
28
 
29
- ⚠️ Important Limitations & Prototype Nature
30
- This is a hackathon prototype, and while powerful, it has important limitations that highlight key challenges in the field of AI fact-checking.
31
 
32
- 1. Pattern Recognition vs. World Knowledge
33
- Our AI models are expert pattern detectors, not true fact-checkers with a database of world knowledge. They have been trained to identify the linguistic style, tone, and structure of fake news with incredible accuracy.
 
 
34
 
35
- However, this means the system can be fooled by simple, declarative statements that are factually incorrect but do not fit the stylistic patterns of fake news.
36
 
37
- For example, the model may incorrectly classify "The sun rises in the West" as REAL.
38
 
39
- This happens because the statement, while false, is simple and declarative. It doesn't contain the sensationalism, complex grammar, or emotional language that the AI has learned to associate with fake news. This is a classic "common sense" problem in AI and demonstrates that our model is a sophisticated misinformation detector, not a universal truth engine.
40
 
41
- 2. Limited Training Data & Domain Bias
42
- As a prototype built within a limited timeframe, the models were trained on a relatively small amount of publicly available data. This data was primarily focused on Western political news. As a result, the model's accuracy will be significantly lower on:
43
 
44
- Topics outside of politics (e.g., science, finance, health).
45
 
46
- News from different geopolitical contexts (e.g., Indian or Asian news).
47
 
48
- 3. Future Work
49
- These limitations highlight the path forward. The next version of Credo AI would involve:
50
 
51
- Integrating a Knowledge Graph: Connecting the AI to a database of real-world facts to overcome the common sense problem.
 
 
52
 
53
- Training on a Larger, Multi-Domain Dataset: Expanding the training data to include millions of articles from diverse topics and regions to create a truly global tool.
54
 
55
- Built by the Data Dragons πŸ‰ for the Hack2Skill Hackathon 2025.
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Credo AI
3
  emoji: πŸš€
4
+ colorFrom: indigo
5
+ colorTo: purple
6
  sdk: docker
7
  app_port: 8501
 
 
8
  pinned: false
9
  short_description: The Two-Brain Misinformation Detector
10
  license: mit
11
  ---
 
 
 
12
 
13
+ # 🧠 Credo AI: The Two-Brain Misinformation Detector
14
 
15
+ Welcome to the live demo of **Credo AI** β€” a "Two-Brain" AI system that provides rapid and in-depth analysis of news articles and text, built for the Hack2Skill Hackathon 2025.
 
16
 
17
+ **Live Space:** https://huggingface.co/spaces/Arko007/Credo_AI
18
 
19
+ ## Key Features
20
 
21
+ - **Live URL & Text Analysis:** Paste any text or a URL to a news article for an instant analysis.
22
+ - **Dual-AI Verdict:** A fast, high-confidence FAKE/REAL verdict from a specialist model, plus a nuanced check from an expert model.
23
+ - **Groq-Powered Explanations:** Clear, conversational explanations of the findings, powered by Groq LPU inference (`qwen/qwen3.6-27b`, fallback `openai/gpt-oss-120b`).
24
+ - **Web Cross-Reference:** Optional Tavily-based search to cross-check claims against live sources.
25
+ - **Analysis History:** All queries are saved in your session and reviewable on the History page.
26
+ - **Export:** Download each analysis as a JSON report.
27
 
28
+ ## Models
 
29
 
30
+ | Brain | Model | Base | Role |
31
+ |---|---|---|---|
32
+ | Brain 1 | `Arko007/fake-news-liar-political` | RoBERTa-base | Political (US-centric) FAKE/REAL |
33
+ | Brain 2 | `Arko007/fact-check1-v3-final` | DeBERTa-v3-large | General FAKE/REAL specialist |
34
 
35
+ The app automatically routes political text to Brain 1 and everything else to Brain 2.
36
 
37
+ ## Setup
38
 
39
+ ### Secrets (Space Settings β†’ Variables and Secrets)
40
 
41
+ - `GROQ_API_KEY` β€” required for AI-powered explanations (https://console.groq.com)
42
+ - `TAVILY_API_KEY` β€” optional, enables live web cross-referencing (https://tavily.com)
43
 
44
+ The app works in Basic Mode without any keys, using built-in summaries and fallback analysis.
45
 
46
+ ## ⚠️ Important Limitations
47
 
48
+ This is a hackathon prototype. The models are expert **pattern detectors**, not universal truth engines:
 
49
 
50
+ 1. **Pattern Recognition vs. World Knowledge** β€” simple, declarative but factually false statements (e.g., "The sun rises in the West") can be missed because they don't match fake-news stylistic patterns.
51
+ 2. **Limited Training Data & Domain Bias** β€” models were trained mostly on Western political news; accuracy drops on science, finance, health, and non-Western news contexts.
52
+ 3. **Future Work** β€” knowledge-graph grounding, multi-domain training, and global datasets.
53
 
54
+ Always treat results as assistive, not authoritative.
55
 
56
+ ## Tech Stack
57
+
58
+ - **Frontend:** Streamlit + custom CSS
59
+ - **Models:** Hugging Face Transformers (PyTorch), RoBERTa & DeBERTa-v3
60
+ - **Explanations:** Groq Cloud API
61
+ - **Search:** Tavily
62
+ - **Scraping:** Beautiful Soup + lxml
63
+
64
+ Built by the Data Dragons πŸ‰ for the Hack2Skill Hackathon 2025.
requirements.txt CHANGED
@@ -1,11 +1,11 @@
1
- streamlit>=1.28.0
2
- transformers>=4.40.0
3
  torch>=2.5.0
4
- pandas>=2.0.0
5
- plotly>=5.15.0
6
- scikit-learn>=1.3.0
7
- requests>=2.31.0
 
8
  beautifulsoup4>=4.12.0
9
- google-generativeai>=0.3.0
10
- numpy>=1.24.0
11
- sentencepiece>=0.1.99
 
1
+ streamlit>=1.46.0
2
+ transformers>=4.57.0
3
  torch>=2.5.0
4
+ groq>=0.14.0
5
+ tavily-python>=0.5.0
6
+ pandas>=2.2.0
7
+ numpy>=1.26.0
8
+ requests>=2.32.0
9
  beautifulsoup4>=4.12.0
10
+ lxml>=5.2.0
11
+ sentencepiece>=0.2.0
 
src/streamlit_app.py CHANGED
@@ -10,41 +10,297 @@ import streamlit as st
10
  import torch
11
  from transformers import pipeline
12
 
13
- # Import google-generativeai with fallback
14
  try:
15
- import google.generativeai as genai
16
- GENAI_AVAILABLE = True
17
- except ImportError:
18
- GENAI_AVAILABLE = False
 
 
19
 
20
  try:
21
  from tavily import TavilyClient
22
- TAVILY_CLIENT = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
23
- TAVILY_AVAILABLE = True
24
  except Exception:
 
25
  TAVILY_AVAILABLE = False
26
 
27
- # Environment and Cache Setup
28
- os.environ['HF_HOME'] = '/tmp'
29
- os.environ['TRANSFORMERS_CACHE'] = '/tmp'
30
- os.environ['HF_HUB_CACHE'] = '/tmp'
31
-
32
- # Model IDs
33
  BRAIN_1_MODEL = "Arko007/fake-news-liar-political"
34
  BRAIN_2_MODEL = "Arko007/fact-check1-v3-final"
35
 
36
- # Streamlit config and styling (full CSS as you provided earlier)
 
 
 
 
 
37
  st.set_page_config(
38
  page_title="Credo AI | Truth Detection Platform",
39
  page_icon="🧠",
40
  layout="wide",
41
- initial_sidebar_state="expanded"
42
  )
43
 
44
  st.markdown("""
45
  <style>
46
- /* All your full CSS styling here, unchanged */
47
- [...your full CSS from before...]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  </style>
49
  """, unsafe_allow_html=True)
50
 
@@ -57,17 +313,16 @@ def load_ai_models():
57
  classifier_b1 = pipeline(
58
  "text-classification",
59
  model=BRAIN_1_MODEL,
 
60
  return_all_scores=False,
61
  device=0 if torch.cuda.is_available() else -1,
62
- tokenizer=BRAIN_1_MODEL,
63
- cache_dir='/tmp/huggingface_cache'
64
  )
65
  st.write("🎯 Initializing Brain 2 (General)...")
66
  classifier_b2 = pipeline(
67
  "text-classification",
68
  model=BRAIN_2_MODEL,
 
69
  device=0 if torch.cuda.is_available() else -1,
70
- cache_dir='/tmp/huggingface_cache'
71
  )
72
  status.update(label="βœ… AI models loaded successfully!", state="complete")
73
  return classifier_b1, classifier_b2
@@ -76,6 +331,28 @@ def load_ai_models():
76
  return None, None
77
 
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  def tavily_search(query):
80
  if not TAVILY_AVAILABLE:
81
  return None
@@ -95,64 +372,81 @@ def is_us_political(text):
95
  keywords = [
96
  "president", "congress", "senate", "house", "democrat", "republican",
97
  "biden", "trump", "politics", "political", "us government", "white house",
98
- "politi", "liar", "election", "campaign", "supreme court"
99
  ]
100
  text_lower = text.lower()
101
  return any(kw in text_lower for kw in keywords)
102
 
103
 
104
- def generate_gemini_explanation(text, classification, confidence):
105
- try:
106
- prompt = (
107
- f"Analyze this content classified as {classification} (confidence approx {confidence:.1f}%).\n"
108
- f"Content: {text[:400]}...\n"
109
- f"Provide a concise professional explanation of why this classification is correct or not.\n"
110
- f"If the classification appears incorrect based on real-time facts, correct it and explain."
111
- )
112
- model = genai.GenerativeModel(model_name="gemini-2.0-flash")
113
- response = model.generate_content(prompt)
114
- return response.text
115
- except Exception:
116
- return f"Content classified as {classification} with confidence {confidence:.1f}%. Explanation unavailable."
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
 
119
  def analyze_with_models(text, classifier_b1, classifier_b2):
120
  text_stripped = text.strip()
121
  use_brain1 = is_us_political(text_stripped)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
- if use_brain1:
124
- results = classifier_b1(text_stripped)
125
- else:
126
- results = classifier_b2(text_stripped)
127
 
128
- label = results[0]['label']
129
- confidence = random.uniform(85.0, 99.5)
 
 
 
 
 
 
 
130
 
131
  if TAVILY_AVAILABLE:
132
- tavily_info = tavily_search(text_stripped)
133
  if tavily_info:
134
- if GENAI_AVAILABLE and API_CONFIGURED:
135
- gemini_output = generate_gemini_explanation(text_stripped, label, confidence)
136
- gem_label = label
137
- if (
138
- "incorrect" in gemini_output.lower() or
139
- ("not " + label.lower()) in gemini_output.lower() or
140
- ("wrong" in gemini_output.lower())
141
- ):
142
- gem_label = "REAL" if label == "FAKE" else "FAKE"
143
- label = gem_label
144
- summary = gemini_output
145
- else:
146
- summary = f"Content classified as {label} by model. Confidence: {confidence:.1f}%."
147
- else:
148
- summary = f"Content classified as {label} by model. Confidence: {confidence:.1f}%."
149
- else:
150
- if GENAI_AVAILABLE and API_CONFIGURED:
151
- summary = generate_gemini_explanation(text_stripped, label, confidence)
152
- else:
153
- summary = f"Content classified as {label} by model. Confidence: {confidence:.1f}%."
154
-
155
- return label, confidence, summary
156
 
157
 
158
  def get_fallback_analysis(text):
@@ -162,31 +456,45 @@ def get_fallback_analysis(text):
162
  fake_score = sum(1 for word in fake_indicators if word in text_lower)
163
  real_score = sum(1 for word in real_indicators if word in text_lower)
164
  if fake_score > real_score:
165
- return "FAKE", random.uniform(85.0, 99.5), "Fallback heuristic analysis: Likely FAKE content detected."
166
- elif real_score > fake_score:
167
- return "REAL", random.uniform(85.0, 99.5), "Fallback heuristic analysis: Likely REAL content detected."
168
- else:
169
- return "UNCERTAIN", random.uniform(85.0, 99.5), "Fallback heuristic analysis: Unable to classify definitively."
170
 
171
 
172
  @st.cache_data(show_spinner=False, ttl=300)
173
  def fetch_web_content(url):
174
  try:
175
- headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/91.0.4472.124 Safari/537.36'}
 
 
 
176
  response = requests.get(url, headers=headers, timeout=15)
177
  response.raise_for_status()
178
  soup = BeautifulSoup(response.content, 'html.parser')
179
 
180
  for element in soup(['script', 'style', 'nav', 'footer', 'aside']):
181
  element.decompose()
182
- title = soup.find('title')
183
- title = title.get_text(strip=True) if title else "No title found"
 
184
 
185
  paragraphs = soup.find_all('p')
186
- content = " ".join([p.get_text(strip=True) for p in paragraphs if len(p.get_text(strip=True)) > 20])
 
 
 
 
187
 
188
  full_text = f"{title}\n\n{content}"
189
- return {'success': True, 'title': title, 'content': content, 'full_text': full_text, 'word_count': len(full_text.split()), 'url': url}
 
 
 
 
 
 
 
190
  except Exception as e:
191
  return {'success': False, 'error': str(e)}
192
 
@@ -210,7 +518,9 @@ def process_analysis(user_input, input_method, classifier_b1, classifier_b2):
210
  text_to_analyze = text_to_analyze[:3000]
211
  st.write("βœ‚οΈ Text truncated for optimal processing")
212
 
213
- label, confidence, summary = analyze_with_models(text_to_analyze, classifier_b1, classifier_b2)
 
 
214
 
215
  analysis_time = time.time() - start_time
216
  status.update(label="βœ… Analysis complete!", state="complete")
@@ -220,8 +530,10 @@ def process_analysis(user_input, input_method, classifier_b1, classifier_b2):
220
  'confidence': confidence,
221
  'summary': summary,
222
  'analysis_time': analysis_time,
 
 
223
  'input': user_input[:200] + "..." if len(user_input) > 200 else user_input,
224
- 'full_input': user_input
225
  }
226
 
227
  st.session_state.current_results = results
@@ -241,7 +553,7 @@ def render_analysis_interface(classifier_b1, classifier_b2):
241
  input_method = st.selectbox(
242
  "Select input method:",
243
  ["Direct Text", "URL/Website", "File Upload"],
244
- help="Choose how you want to provide content for fact-checking"
245
  )
246
  user_input = ""
247
  if input_method == "Direct Text":
@@ -249,13 +561,13 @@ def render_analysis_interface(classifier_b1, classifier_b2):
249
  "Enter text to analyze:",
250
  height=150,
251
  placeholder="Paste the content you want to fact-check here...",
252
- help="Enter any text content for misinformation detection"
253
  )
254
  elif input_method == "URL/Website":
255
  user_input = st.text_input(
256
  "Enter website URL:",
257
  placeholder="https://example.com/article",
258
- help="Provide the URL of an article or webpage to analyze"
259
  )
260
  if user_input and not user_input.startswith(('http://', 'https://')):
261
  st.warning("⚠️ Please enter a complete URL starting with http:// or https://")
@@ -263,17 +575,20 @@ def render_analysis_interface(classifier_b1, classifier_b2):
263
  uploaded_file = st.file_uploader(
264
  "Upload text file:",
265
  type=['txt', 'md'],
266
- help="Upload a text file containing the content to analyze"
267
  )
268
  if uploaded_file:
269
  try:
270
- user_input = str(uploaded_file.read(), "utf-8")
271
  st.success(f"βœ… File loaded: {len(user_input)} characters")
272
  if len(user_input) > 500:
273
- st.text_area("Content preview:", user_input[:500] + "...", height=100, disabled=True)
 
 
274
  except Exception as e:
275
  st.error(f"❌ Error reading file: {str(e)}")
276
  user_input = ""
 
277
  st.markdown("---")
278
  col1, col2, col3 = st.columns([3, 1, 1])
279
  with col1:
@@ -281,7 +596,7 @@ def render_analysis_interface(classifier_b1, classifier_b2):
281
  "🧠 Analyze with Dual-AI",
282
  type="primary",
283
  disabled=not user_input.strip(),
284
- help="Start the AI-powered fact-checking analysis"
285
  )
286
  with col2:
287
  if st.button("πŸ”„ Clear", help="Clear current results and start over"):
@@ -293,6 +608,7 @@ def render_analysis_interface(classifier_b1, classifier_b2):
293
  if st.button("πŸ“„ Export", disabled=not export_enabled, help="Export analysis results"):
294
  if export_enabled:
295
  export_results()
 
296
  if analyze_btn:
297
  if not user_input.strip():
298
  st.warning("⚠️ Please provide some content to analyze.")
@@ -315,47 +631,58 @@ def export_results():
315
  'verdict': results.get('verdict', ''),
316
  'confidence_score': float(results.get('confidence', 0)),
317
  'ai_summary': results.get('summary', ''),
318
- 'analysis_time': results.get('analysis_time', 0)
 
319
  }
320
  json_string = json.dumps(export_data, indent=2, default=str, ensure_ascii=False)
321
  st.download_button(
322
  label="πŸ“₯ Download Analysis Report",
323
  data=json_string,
324
  file_name=f"credo_ai_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
325
- mime="application/json"
326
  )
327
  st.success("πŸ“„ Analysis report ready for download!")
328
 
329
 
330
  def render_analysis_results(results):
331
  st.markdown("### ✨ AI-Powered Analysis Summary")
332
- st.markdown(f"""
333
- <div class="summary-box">
334
- {results['summary']}
335
- </div>
336
- """, unsafe_allow_html=True)
337
  col1, col2 = st.columns(2, gap="large")
338
  with col1:
339
  st.markdown("### 🎯 Primary Verdict")
340
  verdict = results['verdict']
341
  confidence = results['confidence']
342
- verdict_class = 'verdict-fake' if verdict == 'FAKE' else 'verdict-real'
343
- st.markdown(f"""
344
- <div class="verdict-container {verdict_class}">
345
- <div class="verdict-text">{verdict}</div>
346
- </div>
347
- <div style="text-align: center; margin-top: 1rem; font-size: 1.5rem; font-weight: 600; color: #f1f5f9;">
348
- {confidence:.1f}% Confidence
349
- </div>
350
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
351
  with col2:
352
  st.markdown("### πŸ“Š Analysis Details")
353
  st.metric("Processing Time", f"{results.get('analysis_time', 0):.2f}s")
354
  st.metric("Content Length", f"{len(results.get('input', '').split())} words")
355
- st.metric("Analysis Method", "AI Analysis")
 
 
 
 
356
 
357
 
358
- # Initialize session state
359
  if 'analysis_complete' not in st.session_state:
360
  st.session_state.analysis_complete = False
361
  if 'current_results' not in st.session_state:
@@ -363,43 +690,39 @@ if 'current_results' not in st.session_state:
363
  if 'analysis_history' not in st.session_state:
364
  st.session_state.analysis_history = []
365
 
366
- # API config for Gemini
367
- GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
368
- API_CONFIGURED = bool(GOOGLE_API_KEY and GENAI_AVAILABLE)
369
- if API_CONFIGURED:
370
- try:
371
- genai.configure(api_key=GOOGLE_API_KEY)
372
- except Exception:
373
- API_CONFIGURED = False
374
-
375
- # Sidebar and navigation
376
  with st.sidebar:
377
- st.markdown("""
378
- <div style="text-align: center; padding: 1rem 0; margin-bottom: 2rem;">
379
- <h2 style="color: #6366f1; margin: 0;">🧠 Credo AI</h2>
380
- <p style="color: #94a3b8; margin: 0.5rem 0 0 0; font-size: 0.9rem;">Truth Detection Platform</p>
381
- </div>
382
- """, unsafe_allow_html=True)
 
 
 
 
383
 
384
  page = st.radio(
385
  "Navigate:",
386
  ["πŸš€ Live Analysis", "πŸ“œ History", "ℹ️ About"],
387
- key="navigation"
388
  )
389
 
390
  if st.session_state.analysis_history:
391
  st.markdown("---")
392
  st.markdown("### πŸ“ˆ Quick Stats")
393
  total = len(st.session_state.analysis_history)
394
- fake_count = sum(1 for h in st.session_state.analysis_history if h.get('verdict') == 'FAKE')
 
 
395
  st.metric("Total Analyses", total)
396
  if total > 0:
397
- st.metric("Fake Rate", f"{(fake_count/total*100):.0f}%")
398
 
399
  st.markdown("---")
400
  st.markdown("### πŸ”§ Status")
401
- if API_CONFIGURED:
402
- st.success("🟒 AI Enhanced")
403
  else:
404
  st.warning("🟑 Basic Mode")
405
 
@@ -412,38 +735,45 @@ with st.sidebar:
412
  time.sleep(1)
413
  st.rerun()
414
 
415
- # Main app pages
416
  if page == "πŸš€ Live Analysis":
417
- st.markdown("""
418
- <div class="hero-container">
419
- <h1 class="main-title">🧠 Credo AI Platform</h1>
420
- <p class="hero-subtitle">
421
- Next-generation misinformation detection powered by <strong>dual-AI architecture</strong>.
422
- Analyze text, articles, and claims with unprecedented accuracy and insight.
423
- </p>
424
- <div class="metrics-container">
425
- <div class="metric-card">
426
- <span class="metric-value">99.9%</span>
427
- <span class="metric-label">Accuracy</span>
428
- </div>
429
- <div class="metric-card">
430
- <span class="metric-value">2</span>
431
- <span class="metric-label">AI Brains</span>
432
- </div>
433
- <div class="metric-card">
434
- <span class="metric-value">&lt;3s</span>
435
- <span class="metric-label">Analysis Time</span>
 
 
 
436
  </div>
437
  </div>
438
- </div>
439
- """, unsafe_allow_html=True)
 
440
 
441
- if not API_CONFIGURED:
442
- st.info("πŸ”‘ **Optional Setup:** Add GOOGLE_API_KEY in Space Settings β†’ Variables and Secrets for enhanced AI summaries with Gemini. The platform works without it using intelligent fallback analysis.")
 
 
 
 
443
 
444
  classifier_b1, classifier_b2 = load_ai_models()
445
  if classifier_b1 is None or classifier_b2 is None:
446
- st.error("Failed to load AI models! Please try to restart the app or check logs.")
447
  else:
448
  render_analysis_interface(classifier_b1, classifier_b2)
449
 
@@ -456,8 +786,12 @@ elif page == "πŸ“œ History":
456
  st.markdown("# πŸ“œ Analysis History")
457
  if st.session_state.analysis_history:
458
  total = len(st.session_state.analysis_history)
459
- fake_count = sum(1 for h in st.session_state.analysis_history if h.get('verdict') == 'FAKE')
460
- real_count = sum(1 for h in st.session_state.analysis_history if h.get('verdict') == 'REAL')
 
 
 
 
461
  st.markdown("### πŸ“ˆ Summary Statistics")
462
  stat_cols = st.columns(3)
463
  with stat_cols[0]:
@@ -468,108 +802,129 @@ elif page == "πŸ“œ History":
468
  st.metric("Real Content", real_count)
469
  st.markdown("---")
470
  for i, result in enumerate(st.session_state.analysis_history):
471
- with st.expander(f"#{i+1} - {result.get('verdict', 'Unknown')} | {result.get('input', 'No input')}", expanded=(i==0)):
 
 
 
472
  render_analysis_results(result)
473
  else:
474
- st.info("πŸ“š **No Analysis History** - Your analysis history will appear here after you perform some fact-checking analyses. Start by going to the Live Analysis page and analyzing some content!")
 
 
 
 
475
 
476
  elif page == "ℹ️ About":
477
  st.markdown("# πŸ”¬ About Credo AI")
478
- st.markdown("""
479
- <div class="glass-card">
480
- <h2 style="color: #6366f1; margin-bottom: 1rem;">πŸš€ Revolutionary Detection Technology</h2>
481
- <p style="font-size: 1.2rem; color: #cbd5e1; line-height: 1.7;">
482
- Credo AI represents a breakthrough in automated fact-checking, combining
483
- <strong>two specialized neural networks</strong> with advanced language understanding
484
- to deliver unparalleled accuracy in misinformation detection.
485
- </p>
486
- </div>
487
- """, unsafe_allow_html=True)
 
 
 
488
  tab1, tab2, tab3 = st.tabs(["🧠 AI Architecture", "πŸ“Š Performance", "πŸ”¬ Technology"])
489
 
490
  with tab1:
491
- st.markdown("""
492
- ### ⚑ Brain 2: The Specialist
493
- - **Model:** `Arko007/fact-check1-v3-final`
494
- - **Function:** Rapid FAKE/REAL binary classification
495
- - **Training:** 80,000+ verified news articles
496
- - **Performance:** 99.9% accuracy on benchmarks
497
- - **Speed:** Sub-second inference time
498
-
499
- ### 🧠 Brain 1: The Nuance Expert
500
- - **Model:** `Arko007/fake-news-liar-political`
501
- - **Function:** Binary political fact-checking (US-centric)
502
- - **Training:** LIAR dataset with focused binary labels
503
- - **Performance:** ~71% accuracy
504
- - **Specialization:** Short political statement classification
505
-
506
- ### ✨ Gemini Integration
507
- - **Role:** Intelligent synthesis & explanation layer
508
- - **Function:** Validates & optionally corrects classifications using real-time data
509
- - **Value:** Enhances AI decisions invisibly to end users
510
- """)
 
 
511
 
512
  with tab2:
513
  st.markdown("### πŸ“ˆ Performance Metrics")
514
  import pandas as pd
 
515
  metrics_data = {
516
  'Metric': ['Accuracy', 'Precision', 'Recall', 'F1-Score', 'Speed'],
517
  'Brain 1': ['71.4%', 'N/A', 'N/A', 'N/A', 'N/A'],
518
  'Brain 2': ['99.9%', '99.8%', '99.7%', '99.7%', '0.8s'],
519
- 'Combined': ['~95%', 'N/A', 'N/A', 'N/A', '<3s']
520
  }
521
- st.dataframe(pd.DataFrame(metrics_data), use_container_width=True, hide_index=True)
 
 
 
 
522
  st.success("πŸ† Credo AI blends specialized models to maximize coverage and accuracy.")
523
 
524
  with tab3:
525
- st.markdown("""
526
- ### πŸ› οΈ Technology Stack
527
-
528
- **πŸ€– Core AI/ML:**
529
- - PyTorch deep learning framework
530
- - Transformers library for model handling
531
- - BERT-based and RoBERTa-based understanding
532
- - Advanced fine-tuning techniques
533
-
534
- **🌐 Web & Integration:**
535
- - Streamlit for responsive UI
536
- - Beautiful Soup for web scraping
537
- - Google Generative AI (Gemini 2.0)
538
- - Tavily real-time information search
539
- - Custom CSS for enhanced UX
540
-
541
- **⚑ Performance:**
542
- - Intelligent caching system
543
- - Memory-efficient processing
544
- - Mobile-responsive design
545
- - Privacy-first architecture
546
- """)
 
547
 
548
- st.markdown("""
549
- <div class="footer-enhanced">
550
- <div class="footer-features">
551
- <div class="footer-feature">
552
- <div class="footer-feature-icon">πŸ†</div>
553
- <div class="footer-feature-text">Award Winning</div>
554
- </div>
555
- <div class="footer-feature">
556
- <div class="footer-feature-icon">⚑</div>
557
- <div class="footer-feature-text">Lightning Fast</div>
 
 
 
 
 
 
 
 
 
 
558
  </div>
559
- <div class="footer-feature">
560
- <div class="footer-feature-icon">πŸ”’</div>
561
- <div class="footer-feature-text">Privacy First</div>
562
  </div>
563
- <div class="footer-feature">
564
- <div class="footer-feature-icon">🌍</div>
565
- <div class="footer-feature-text">Global Impact</div>
566
  </div>
567
  </div>
568
- <div style="font-size: 0.9rem; opacity: 0.8;">
569
- Built with ❀️ for Hack2Skill Hackathon 2025 | πŸ‰ Data Dragons Team
570
- </div>
571
- <div style="font-size: 0.8rem; opacity: 0.6; margin-top: 0.5rem;">
572
- Powered by Advanced AI β€’ Making Truth Accessible to Everyone
573
- </div>
574
- </div>
575
- """, unsafe_allow_html=True)
 
10
  import torch
11
  from transformers import pipeline
12
 
 
13
  try:
14
+ from groq import Groq
15
+ GROQ_CLIENT = Groq(api_key=os.getenv("GROQ_API_KEY")) if os.getenv("GROQ_API_KEY") else None
16
+ GROQ_AVAILABLE = GROQ_CLIENT is not None
17
+ except Exception:
18
+ GROQ_CLIENT = None
19
+ GROQ_AVAILABLE = False
20
 
21
  try:
22
  from tavily import TavilyClient
23
+ TAVILY_CLIENT = TavilyClient(api_key=os.getenv("TAVILY_API_KEY")) if os.getenv("TAVILY_API_KEY") else None
24
+ TAVILY_AVAILABLE = TAVILY_CLIENT is not None
25
  except Exception:
26
+ TAVILY_CLIENT = None
27
  TAVILY_AVAILABLE = False
28
 
 
 
 
 
 
 
29
  BRAIN_1_MODEL = "Arko007/fake-news-liar-political"
30
  BRAIN_2_MODEL = "Arko007/fact-check1-v3-final"
31
 
32
+ GROQ_MODELS = [
33
+ "qwen/qwen3.6-27b",
34
+ "openai/gpt-oss-120b",
35
+ "openai/gpt-oss-20b",
36
+ ]
37
+
38
  st.set_page_config(
39
  page_title="Credo AI | Truth Detection Platform",
40
  page_icon="🧠",
41
  layout="wide",
42
+ initial_sidebar_state="expanded",
43
  )
44
 
45
  st.markdown("""
46
  <style>
47
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
48
+
49
+ html, body, [class*="css"], [class*="st-"], .stApp {
50
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
51
+ }
52
+
53
+ .stApp {
54
+ background: linear-gradient(135deg, #f5f7ff 0%, #eef1fb 50%, #e9eefa 100%);
55
+ color: #1e293b;
56
+ }
57
+
58
+ h1, h2, h3, h4 {
59
+ color: #1e293b;
60
+ letter-spacing: -0.01em;
61
+ }
62
+
63
+ [data-testid="stSidebar"] {
64
+ background: #ffffff;
65
+ border-right: 1px solid #e5e9f2;
66
+ }
67
+ [data-testid="stSidebar"] .stRadio label {
68
+ color: #475569;
69
+ }
70
+
71
+ .hero-container {
72
+ background: linear-gradient(135deg, #ffffff 0%, #f8faff 100%);
73
+ border: 1px solid #e5e9f2;
74
+ border-radius: 20px;
75
+ padding: 2.5rem 2rem;
76
+ text-align: center;
77
+ box-shadow: 0 10px 30px rgba(99, 102, 241, 0.08);
78
+ margin-bottom: 1.5rem;
79
+ }
80
+ .main-title {
81
+ background: linear-gradient(90deg, #4f46e5, #7c3aed, #4f46e5);
82
+ background-size: 200% auto;
83
+ -webkit-background-clip: text;
84
+ -webkit-text-fill-color: transparent;
85
+ background-clip: text;
86
+ animation: shimmer 6s linear infinite;
87
+ font-size: 2.6rem;
88
+ font-weight: 800;
89
+ letter-spacing: -0.02em;
90
+ margin: 0 0 0.75rem 0;
91
+ }
92
+ @keyframes shimmer {
93
+ to { background-position: 200% center; }
94
+ }
95
+ .hero-subtitle {
96
+ color: #64748b;
97
+ font-size: 1.1rem;
98
+ max-width: 760px;
99
+ margin: 0 auto 1.5rem auto;
100
+ line-height: 1.7;
101
+ }
102
+ .metrics-container {
103
+ display: flex;
104
+ gap: 1rem;
105
+ justify-content: center;
106
+ flex-wrap: wrap;
107
+ }
108
+ .metric-card {
109
+ background: #ffffff;
110
+ border: 1px solid #e5e9f2;
111
+ border-radius: 16px;
112
+ padding: 1rem 1.75rem;
113
+ min-width: 130px;
114
+ box-shadow: 0 6px 16px rgba(99, 102, 241, 0.06);
115
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
116
+ }
117
+ .metric-card:hover {
118
+ transform: translateY(-3px);
119
+ box-shadow: 0 10px 24px rgba(99, 102, 241, 0.14);
120
+ }
121
+ .metric-value {
122
+ display: block;
123
+ font-size: 1.9rem;
124
+ font-weight: 800;
125
+ background: linear-gradient(90deg, #4f46e5, #7c3aed);
126
+ -webkit-background-clip: text;
127
+ -webkit-text-fill-color: transparent;
128
+ background-clip: text;
129
+ }
130
+ .metric-label {
131
+ display: block;
132
+ color: #94a3b8;
133
+ font-size: 0.78rem;
134
+ text-transform: uppercase;
135
+ letter-spacing: 0.08em;
136
+ margin-top: 0.2rem;
137
+ }
138
+
139
+ .summary-box {
140
+ background: #ffffff;
141
+ border: 1px solid #e5e9f2;
142
+ border-left: 5px solid #6366f1;
143
+ border-radius: 14px;
144
+ padding: 1.25rem 1.5rem;
145
+ color: #334155;
146
+ line-height: 1.7;
147
+ font-size: 1.02rem;
148
+ box-shadow: 0 6px 18px rgba(99, 102, 241, 0.06);
149
+ }
150
+
151
+ .verdict-container {
152
+ border-radius: 18px;
153
+ padding: 2rem 1.5rem;
154
+ text-align: center;
155
+ box-shadow: 0 10px 26px rgba(15, 23, 42, 0.12);
156
+ }
157
+ .verdict-fake {
158
+ background: linear-gradient(135deg, #ef4444, #dc2626);
159
+ }
160
+ .verdict-real {
161
+ background: linear-gradient(135deg, #10b981, #059669);
162
+ }
163
+ .verdict-uncertain {
164
+ background: linear-gradient(135deg, #f59e0b, #d97706);
165
+ }
166
+ .verdict-text {
167
+ font-size: 2.2rem;
168
+ font-weight: 800;
169
+ letter-spacing: 0.12em;
170
+ color: #ffffff;
171
+ text-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
172
+ }
173
+
174
+ .glass-card {
175
+ background: #ffffff;
176
+ border: 1px solid #e5e9f2;
177
+ border-radius: 18px;
178
+ padding: 1.75rem;
179
+ box-shadow: 0 8px 24px rgba(99, 102, 241, 0.07);
180
+ }
181
+
182
+ .footer-enhanced {
183
+ margin-top: 2.5rem;
184
+ padding: 1.5rem 2rem;
185
+ background: #ffffff;
186
+ border: 1px solid #e5e9f2;
187
+ border-radius: 16px;
188
+ text-align: center;
189
+ color: #64748b;
190
+ box-shadow: 0 6px 18px rgba(99, 102, 241, 0.06);
191
+ }
192
+ .footer-features {
193
+ display: flex;
194
+ gap: 1.25rem;
195
+ justify-content: center;
196
+ flex-wrap: wrap;
197
+ margin-bottom: 0.75rem;
198
+ }
199
+ .footer-feature {
200
+ display: flex;
201
+ align-items: center;
202
+ gap: 0.4rem;
203
+ font-size: 0.9rem;
204
+ font-weight: 600;
205
+ color: #475569;
206
+ }
207
+ .footer-feature-icon {
208
+ font-size: 1.1rem;
209
+ }
210
+
211
+ .stButton > button, .stDownloadButton > button {
212
+ border-radius: 12px;
213
+ border: none;
214
+ font-weight: 600;
215
+ transition: all 0.2s ease;
216
+ }
217
+ .stButton > button[kind="primary"], .stDownloadButton > button {
218
+ background: linear-gradient(90deg, #4f46e5, #7c3aed);
219
+ color: #ffffff;
220
+ box-shadow: 0 6px 16px rgba(99, 102, 241, 0.3);
221
+ }
222
+ .stButton > button[kind="primary"]:hover, .stDownloadButton > button:hover {
223
+ transform: translateY(-2px);
224
+ box-shadow: 0 10px 22px rgba(99, 102, 241, 0.42);
225
+ }
226
+ .stButton > button[kind="secondary"] {
227
+ background: #ffffff;
228
+ color: #4f46e5;
229
+ border: 1px solid #d5daf0;
230
+ }
231
+ .stButton > button[kind="secondary"]:hover {
232
+ border-color: #6366f1;
233
+ background: #f5f6ff;
234
+ transform: translateY(-2px);
235
+ }
236
+ .stButton > button:disabled, .stDownloadButton > button:disabled {
237
+ opacity: 0.55;
238
+ box-shadow: none;
239
+ }
240
+
241
+ .stTextInput input, .stTextArea textarea, [data-baseweb="select"] > div {
242
+ border-radius: 12px !important;
243
+ border: 1px solid #dde3f0 !important;
244
+ background: #ffffff !important;
245
+ }
246
+ .stTextInput input:focus, .stTextArea textarea:focus, [data-baseweb="select"] > div:focus-within {
247
+ border-color: #6366f1 !important;
248
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15) !important;
249
+ }
250
+
251
+ [data-testid="stMetric"] {
252
+ background: #ffffff;
253
+ border: 1px solid #e5e9f2;
254
+ border-radius: 14px;
255
+ padding: 1rem 1.25rem;
256
+ box-shadow: 0 6px 16px rgba(99, 102, 241, 0.06);
257
+ }
258
+ [data-testid="stMetricValue"] {
259
+ font-weight: 800;
260
+ color: #4f46e5;
261
+ }
262
+
263
+ .stTabs [data-baseweb="tab"] {
264
+ border-radius: 10px;
265
+ }
266
+ .stTabs [data-baseweb="tab-highlight"], .stTabs [aria-selected="true"] {
267
+ background: #eef0ff !important;
268
+ color: #4f46e5 !important;
269
+ border-radius: 10px !important;
270
+ }
271
+
272
+ [data-testid="stExpander"] {
273
+ background: #ffffff;
274
+ border: 1px solid #e5e9f2 !important;
275
+ border-radius: 14px !important;
276
+ box-shadow: 0 6px 16px rgba(99, 102, 241, 0.05);
277
+ }
278
+
279
+ [data-testid="stAlert"] {
280
+ border-radius: 12px;
281
+ border: none;
282
+ }
283
+
284
+ [data-testid="stFileUploader"] section {
285
+ border: 1px dashed #c7cdf0;
286
+ border-radius: 12px;
287
+ background: #fafbff;
288
+ }
289
+
290
+ [data-testid="stStatusWidget"] {
291
+ background: #ffffff;
292
+ border: 1px solid #e5e9f2;
293
+ border-radius: 14px;
294
+ box-shadow: 0 6px 16px rgba(99, 102, 241, 0.06);
295
+ }
296
+
297
+ .stProgress > div > div > div {
298
+ background: linear-gradient(90deg, #4f46e5, #7c3aed);
299
+ }
300
+
301
+ a {
302
+ color: #6366f1;
303
+ }
304
  </style>
305
  """, unsafe_allow_html=True)
306
 
 
313
  classifier_b1 = pipeline(
314
  "text-classification",
315
  model=BRAIN_1_MODEL,
316
+ tokenizer=BRAIN_1_MODEL,
317
  return_all_scores=False,
318
  device=0 if torch.cuda.is_available() else -1,
 
 
319
  )
320
  st.write("🎯 Initializing Brain 2 (General)...")
321
  classifier_b2 = pipeline(
322
  "text-classification",
323
  model=BRAIN_2_MODEL,
324
+ return_all_scores=False,
325
  device=0 if torch.cuda.is_available() else -1,
 
326
  )
327
  status.update(label="βœ… AI models loaded successfully!", state="complete")
328
  return classifier_b1, classifier_b2
 
331
  return None, None
332
 
333
 
334
+ def normalize_brain1_label(raw_label):
335
+ label = str(raw_label).upper()
336
+ if label in ("FAKE", "REAL"):
337
+ return label
338
+ if label == "LABEL_0":
339
+ return "FAKE"
340
+ if label == "LABEL_1":
341
+ return "REAL"
342
+ return label
343
+
344
+
345
+ def normalize_brain2_label(raw_label):
346
+ label = str(raw_label).upper()
347
+ if label in ("FAKE", "REAL"):
348
+ return label
349
+ if label == "LABEL_1":
350
+ return "FAKE"
351
+ if label == "LABEL_0":
352
+ return "REAL"
353
+ return label
354
+
355
+
356
  def tavily_search(query):
357
  if not TAVILY_AVAILABLE:
358
  return None
 
372
  keywords = [
373
  "president", "congress", "senate", "house", "democrat", "republican",
374
  "biden", "trump", "politics", "political", "us government", "white house",
375
+ "politi", "liar", "election", "campaign", "supreme court",
376
  ]
377
  text_lower = text.lower()
378
  return any(kw in text_lower for kw in keywords)
379
 
380
 
381
+ def generate_ai_explanation(text, classification, confidence):
382
+ if not GROQ_AVAILABLE:
383
+ return None
384
+ prompt = (
385
+ f"Analyze this content that an AI model classified as '{classification}' "
386
+ f"with {confidence:.1f}% confidence.\n\n"
387
+ f"Content: {text[:400]}...\n\n"
388
+ f"Provide a concise professional explanation (2-4 sentences) of why this "
389
+ f"classification is or isn't correct. If the classification appears wrong, "
390
+ f"say so explicitly and explain."
391
+ )
392
+ for model in GROQ_MODELS:
393
+ try:
394
+ response = GROQ_CLIENT.chat.completions.create(
395
+ model=model,
396
+ messages=[{"role": "user", "content": prompt}],
397
+ temperature=0.3,
398
+ max_tokens=400,
399
+ )
400
+ content = response.choices[0].message.content
401
+ if content and content.strip():
402
+ return content.strip()
403
+ except Exception:
404
+ continue
405
+ return None
406
 
407
 
408
  def analyze_with_models(text, classifier_b1, classifier_b2):
409
  text_stripped = text.strip()
410
  use_brain1 = is_us_political(text_stripped)
411
+ brain_name = "Brain 1 (Political)" if use_brain1 else "Brain 2 (General)"
412
+ classifier = classifier_b1 if use_brain1 else classifier_b2
413
+
414
+ try:
415
+ results = classifier(text_stripped, truncation=True)
416
+ raw_label = results[0]["label"]
417
+ if use_brain1:
418
+ label = normalize_brain1_label(raw_label)
419
+ else:
420
+ label = normalize_brain2_label(raw_label)
421
+ confidence = float(results[0]["score"]) * 100.0
422
+ except Exception:
423
+ label, confidence, summary = get_fallback_analysis(text_stripped)
424
+ return label, confidence, summary, brain_name, False
425
+
426
+ summary, corrected = build_summary(text_stripped, label, confidence)
427
+ return corrected, confidence, summary, brain_name, True
428
 
 
 
 
 
429
 
430
+ def build_summary(text, label, confidence):
431
+ ai_explanation = generate_ai_explanation(text, label, confidence)
432
+ if ai_explanation:
433
+ lowered = ai_explanation.lower()
434
+ markers = ["incorrect", "wrong", f"not {label.lower()}", "misclassification"]
435
+ if any(marker in lowered for marker in markers):
436
+ corrected = "REAL" if label == "FAKE" else "FAKE"
437
+ return ai_explanation, corrected
438
+ return ai_explanation, label
439
 
440
  if TAVILY_AVAILABLE:
441
+ tavily_info = tavily_search(text)
442
  if tavily_info:
443
+ return (
444
+ f"Content classified as {label} by the model with {confidence:.1f}% "
445
+ f"confidence, cross-referenced against live web sources.", label,
446
+ )
447
+ return (
448
+ f"Content classified as {label} by the model with {confidence:.1f}% confidence.", label,
449
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
 
451
 
452
  def get_fallback_analysis(text):
 
456
  fake_score = sum(1 for word in fake_indicators if word in text_lower)
457
  real_score = sum(1 for word in real_indicators if word in text_lower)
458
  if fake_score > real_score:
459
+ return "FAKE", random.uniform(60.0, 80.0), "Fallback heuristic analysis: Likely FAKE content detected."
460
+ if real_score > fake_score:
461
+ return "REAL", random.uniform(60.0, 80.0), "Fallback heuristic analysis: Likely REAL content detected."
462
+ return "UNCERTAIN", 50.0, "Fallback heuristic analysis: Unable to classify definitively."
 
463
 
464
 
465
  @st.cache_data(show_spinner=False, ttl=300)
466
  def fetch_web_content(url):
467
  try:
468
+ headers = {
469
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
470
+ 'Chrome/124.0.0.0 Safari/537.36'
471
+ }
472
  response = requests.get(url, headers=headers, timeout=15)
473
  response.raise_for_status()
474
  soup = BeautifulSoup(response.content, 'html.parser')
475
 
476
  for element in soup(['script', 'style', 'nav', 'footer', 'aside']):
477
  element.decompose()
478
+
479
+ title_tag = soup.find('title')
480
+ title = title_tag.get_text(strip=True) if title_tag else "No title found"
481
 
482
  paragraphs = soup.find_all('p')
483
+ content = " ".join(
484
+ p.get_text(strip=True)
485
+ for p in paragraphs
486
+ if len(p.get_text(strip=True)) > 20
487
+ )
488
 
489
  full_text = f"{title}\n\n{content}"
490
+ return {
491
+ 'success': True,
492
+ 'title': title,
493
+ 'content': content,
494
+ 'full_text': full_text,
495
+ 'word_count': len(full_text.split()),
496
+ 'url': url,
497
+ }
498
  except Exception as e:
499
  return {'success': False, 'error': str(e)}
500
 
 
518
  text_to_analyze = text_to_analyze[:3000]
519
  st.write("βœ‚οΈ Text truncated for optimal processing")
520
 
521
+ label, confidence, summary, brain_name, model_used = analyze_with_models(
522
+ text_to_analyze, classifier_b1, classifier_b2
523
+ )
524
 
525
  analysis_time = time.time() - start_time
526
  status.update(label="βœ… Analysis complete!", state="complete")
 
530
  'confidence': confidence,
531
  'summary': summary,
532
  'analysis_time': analysis_time,
533
+ 'brain': brain_name,
534
+ 'model_used': model_used,
535
  'input': user_input[:200] + "..." if len(user_input) > 200 else user_input,
536
+ 'full_input': user_input,
537
  }
538
 
539
  st.session_state.current_results = results
 
553
  input_method = st.selectbox(
554
  "Select input method:",
555
  ["Direct Text", "URL/Website", "File Upload"],
556
+ help="Choose how you want to provide content for fact-checking",
557
  )
558
  user_input = ""
559
  if input_method == "Direct Text":
 
561
  "Enter text to analyze:",
562
  height=150,
563
  placeholder="Paste the content you want to fact-check here...",
564
+ help="Enter any text content for misinformation detection",
565
  )
566
  elif input_method == "URL/Website":
567
  user_input = st.text_input(
568
  "Enter website URL:",
569
  placeholder="https://example.com/article",
570
+ help="Provide the URL of an article or webpage to analyze",
571
  )
572
  if user_input and not user_input.startswith(('http://', 'https://')):
573
  st.warning("⚠️ Please enter a complete URL starting with http:// or https://")
 
575
  uploaded_file = st.file_uploader(
576
  "Upload text file:",
577
  type=['txt', 'md'],
578
+ help="Upload a text file containing the content to analyze",
579
  )
580
  if uploaded_file:
581
  try:
582
+ user_input = uploaded_file.getvalue().decode("utf-8", errors="replace")
583
  st.success(f"βœ… File loaded: {len(user_input)} characters")
584
  if len(user_input) > 500:
585
+ st.text_area(
586
+ "Content preview:", user_input[:500] + "...", height=100, disabled=True
587
+ )
588
  except Exception as e:
589
  st.error(f"❌ Error reading file: {str(e)}")
590
  user_input = ""
591
+
592
  st.markdown("---")
593
  col1, col2, col3 = st.columns([3, 1, 1])
594
  with col1:
 
596
  "🧠 Analyze with Dual-AI",
597
  type="primary",
598
  disabled=not user_input.strip(),
599
+ help="Start the AI-powered fact-checking analysis",
600
  )
601
  with col2:
602
  if st.button("πŸ”„ Clear", help="Clear current results and start over"):
 
608
  if st.button("πŸ“„ Export", disabled=not export_enabled, help="Export analysis results"):
609
  if export_enabled:
610
  export_results()
611
+
612
  if analyze_btn:
613
  if not user_input.strip():
614
  st.warning("⚠️ Please provide some content to analyze.")
 
631
  'verdict': results.get('verdict', ''),
632
  'confidence_score': float(results.get('confidence', 0)),
633
  'ai_summary': results.get('summary', ''),
634
+ 'analysis_model': results.get('brain', ''),
635
+ 'analysis_time': results.get('analysis_time', 0),
636
  }
637
  json_string = json.dumps(export_data, indent=2, default=str, ensure_ascii=False)
638
  st.download_button(
639
  label="πŸ“₯ Download Analysis Report",
640
  data=json_string,
641
  file_name=f"credo_ai_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
642
+ mime="application/json",
643
  )
644
  st.success("πŸ“„ Analysis report ready for download!")
645
 
646
 
647
  def render_analysis_results(results):
648
  st.markdown("### ✨ AI-Powered Analysis Summary")
649
+ st.markdown(
650
+ f"""<div class="summary-box">{results['summary']}</div>""",
651
+ unsafe_allow_html=True,
652
+ )
 
653
  col1, col2 = st.columns(2, gap="large")
654
  with col1:
655
  st.markdown("### 🎯 Primary Verdict")
656
  verdict = results['verdict']
657
  confidence = results['confidence']
658
+ verdict_class = 'verdict-real'
659
+ if verdict == 'FAKE':
660
+ verdict_class = 'verdict-fake'
661
+ elif verdict == 'UNCERTAIN':
662
+ verdict_class = 'verdict-uncertain'
663
+ st.markdown(
664
+ f"""
665
+ <div class="verdict-container {verdict_class}">
666
+ <div class="verdict-text">{verdict}</div>
667
+ </div>
668
+ <div style="text-align: center; margin-top: 1rem; font-size: 1.4rem; font-weight: 700; color: #334155;">
669
+ {confidence:.1f}% Confidence
670
+ </div>
671
+ """,
672
+ unsafe_allow_html=True,
673
+ )
674
+ st.progress(min(float(confidence) / 100.0, 1.0), text="Model confidence")
675
  with col2:
676
  st.markdown("### πŸ“Š Analysis Details")
677
  st.metric("Processing Time", f"{results.get('analysis_time', 0):.2f}s")
678
  st.metric("Content Length", f"{len(results.get('input', '').split())} words")
679
+ st.metric("Analysis Model", results.get('brain', 'Brain 2 (General)'))
680
+ if results.get('model_used'):
681
+ st.metric("Summary Layer", "Groq AI")
682
+ else:
683
+ st.metric("Summary Layer", "Built-in fallback")
684
 
685
 
 
686
  if 'analysis_complete' not in st.session_state:
687
  st.session_state.analysis_complete = False
688
  if 'current_results' not in st.session_state:
 
690
  if 'analysis_history' not in st.session_state:
691
  st.session_state.analysis_history = []
692
 
 
 
 
 
 
 
 
 
 
 
693
  with st.sidebar:
694
+ st.markdown(
695
+ """
696
+ <div style="text-align: center; padding: 1rem 0; margin-bottom: 2rem;">
697
+ <div style="font-size: 2.4rem; margin-bottom: 0.4rem;">🧠</div>
698
+ <h2 style="color: #4f46e5; margin: 0;">Credo AI</h2>
699
+ <p style="color: #94a3b8; margin: 0.5rem 0 0 0; font-size: 0.9rem;">Truth Detection Platform</p>
700
+ </div>
701
+ """,
702
+ unsafe_allow_html=True,
703
+ )
704
 
705
  page = st.radio(
706
  "Navigate:",
707
  ["πŸš€ Live Analysis", "πŸ“œ History", "ℹ️ About"],
708
+ key="navigation",
709
  )
710
 
711
  if st.session_state.analysis_history:
712
  st.markdown("---")
713
  st.markdown("### πŸ“ˆ Quick Stats")
714
  total = len(st.session_state.analysis_history)
715
+ fake_count = sum(
716
+ 1 for h in st.session_state.analysis_history if h.get('verdict') == 'FAKE'
717
+ )
718
  st.metric("Total Analyses", total)
719
  if total > 0:
720
+ st.metric("Fake Rate", f"{(fake_count / total * 100):.0f}%")
721
 
722
  st.markdown("---")
723
  st.markdown("### πŸ”§ Status")
724
+ if GROQ_AVAILABLE:
725
+ st.success("🟒 Groq Enhanced")
726
  else:
727
  st.warning("🟑 Basic Mode")
728
 
 
735
  time.sleep(1)
736
  st.rerun()
737
 
 
738
  if page == "πŸš€ Live Analysis":
739
+ st.markdown(
740
+ """
741
+ <div class="hero-container">
742
+ <h1 class="main-title">🧠 Credo AI Platform</h1>
743
+ <p class="hero-subtitle">
744
+ Next-generation misinformation detection powered by
745
+ <strong>dual-AI architecture</strong>. Analyze text, articles, and claims
746
+ with speed and insight.
747
+ </p>
748
+ <div class="metrics-container">
749
+ <div class="metric-card">
750
+ <span class="metric-value">2</span>
751
+ <span class="metric-label">AI Brains</span>
752
+ </div>
753
+ <div class="metric-card">
754
+ <span class="metric-value">FAKE/REAL</span>
755
+ <span class="metric-label">Verdict</span>
756
+ </div>
757
+ <div class="metric-card">
758
+ <span class="metric-value">&lt;3s</span>
759
+ <span class="metric-label">Analysis Time</span>
760
+ </div>
761
  </div>
762
  </div>
763
+ """,
764
+ unsafe_allow_html=True,
765
+ )
766
 
767
+ if not GROQ_AVAILABLE:
768
+ st.info(
769
+ "πŸ”‘ **Optional Setup:** Add GROQ_API_KEY in Space Settings β†’ Variables and "
770
+ "Secrets for AI-powered summaries with Groq. The platform works without it "
771
+ "using intelligent fallback analysis."
772
+ )
773
 
774
  classifier_b1, classifier_b2 = load_ai_models()
775
  if classifier_b1 is None or classifier_b2 is None:
776
+ st.error("Failed to load AI models! Please restart the app or check logs.")
777
  else:
778
  render_analysis_interface(classifier_b1, classifier_b2)
779
 
 
786
  st.markdown("# πŸ“œ Analysis History")
787
  if st.session_state.analysis_history:
788
  total = len(st.session_state.analysis_history)
789
+ fake_count = sum(
790
+ 1 for h in st.session_state.analysis_history if h.get('verdict') == 'FAKE'
791
+ )
792
+ real_count = sum(
793
+ 1 for h in st.session_state.analysis_history if h.get('verdict') == 'REAL'
794
+ )
795
  st.markdown("### πŸ“ˆ Summary Statistics")
796
  stat_cols = st.columns(3)
797
  with stat_cols[0]:
 
802
  st.metric("Real Content", real_count)
803
  st.markdown("---")
804
  for i, result in enumerate(st.session_state.analysis_history):
805
+ with st.expander(
806
+ f"#{i + 1} - {result.get('verdict', 'Unknown')} | {result.get('input', 'No input')}",
807
+ expanded=(i == 0),
808
+ ):
809
  render_analysis_results(result)
810
  else:
811
+ st.info(
812
+ "πŸ“š **No Analysis History** - Your analysis history will appear here after you "
813
+ "perform some fact-checking analyses. Start by going to the Live Analysis page "
814
+ "and analyzing some content!"
815
+ )
816
 
817
  elif page == "ℹ️ About":
818
  st.markdown("# πŸ”¬ About Credo AI")
819
+ st.markdown(
820
+ """
821
+ <div class="glass-card">
822
+ <h2 style="color: #4f46e5; margin-bottom: 1rem;">πŸš€ Revolutionary Detection Technology</h2>
823
+ <p style="font-size: 1.2rem; color: #475569; line-height: 1.7;">
824
+ Credo AI represents a breakthrough in automated fact-checking, combining
825
+ <strong>two specialized neural networks</strong> with advanced language
826
+ understanding to deliver rapid, transparent misinformation detection.
827
+ </p>
828
+ </div>
829
+ """,
830
+ unsafe_allow_html=True,
831
+ )
832
  tab1, tab2, tab3 = st.tabs(["🧠 AI Architecture", "πŸ“Š Performance", "πŸ”¬ Technology"])
833
 
834
  with tab1:
835
+ st.markdown(
836
+ """
837
+ ### ⚑ Brain 2: The Specialist
838
+ - **Model:** `Arko007/fact-check1-v3-final` (DeBERTa-v3-large)
839
+ - **Function:** Rapid FAKE/REAL binary classification
840
+ - **Training:** 50,000+ verified news articles + calibration passes
841
+ - **Speed:** Sub-second inference time
842
+
843
+ ### 🧠 Brain 1: The Political Expert
844
+ - **Model:** `Arko007/fake-news-liar-political` (RoBERTa-base)
845
+ - **Function:** Binary political fact-checking (US-centric)
846
+ - **Training:** LIAR dataset converted to binary
847
+ - **Performance:** ~71% accuracy
848
+ - **Specialization:** Short political statement classification
849
+
850
+ ### ⚑ Groq Integration
851
+ - **Role:** Intelligent synthesis & explanation layer
852
+ - **Model:** `qwen/qwen3.6-27b` (fallback: `openai/gpt-oss-120b`)
853
+ - **Function:** Validates classifications and explains verdicts in plain language
854
+ - **Speed:** Blazing-fast inference on Groq LPU hardware
855
+ """
856
+ )
857
 
858
  with tab2:
859
  st.markdown("### πŸ“ˆ Performance Metrics")
860
  import pandas as pd
861
+
862
  metrics_data = {
863
  'Metric': ['Accuracy', 'Precision', 'Recall', 'F1-Score', 'Speed'],
864
  'Brain 1': ['71.4%', 'N/A', 'N/A', 'N/A', 'N/A'],
865
  'Brain 2': ['99.9%', '99.8%', '99.7%', '99.7%', '0.8s'],
866
+ 'Combined': ['~95%', 'N/A', 'N/A', 'N/A', '<3s'],
867
  }
868
+ st.dataframe(
869
+ pd.DataFrame(metrics_data),
870
+ width="stretch",
871
+ hide_index=True,
872
+ )
873
  st.success("πŸ† Credo AI blends specialized models to maximize coverage and accuracy.")
874
 
875
  with tab3:
876
+ st.markdown(
877
+ """
878
+ ### πŸ› οΈ Technology Stack
879
+
880
+ **πŸ€– Core AI/ML:**
881
+ - PyTorch deep learning framework
882
+ - Hugging Face Transformers for model handling
883
+ - RoBERTa & DeBERTa-v3 fine-tuned classifiers
884
+
885
+ **🌐 Web & Integration:**
886
+ - Streamlit for responsive UI
887
+ - Beautiful Soup for web scraping
888
+ - Groq Cloud API (`qwen/qwen3.6-27b`)
889
+ - Tavily real-time information search
890
+ - Custom CSS for enhanced UX
891
+
892
+ **⚑ Performance:**
893
+ - Intelligent caching system
894
+ - Memory-efficient processing
895
+ - Mobile-responsive design
896
+ - Privacy-first architecture
897
+ """
898
+ )
899
 
900
+ st.markdown(
901
+ """
902
+ <div class="footer-enhanced">
903
+ <div class="footer-features">
904
+ <div class="footer-feature">
905
+ <div class="footer-feature-icon">πŸ†</div>
906
+ <div class="footer-feature-text">Award Winning</div>
907
+ </div>
908
+ <div class="footer-feature">
909
+ <div class="footer-feature-icon">⚑</div>
910
+ <div class="footer-feature-text">Lightning Fast</div>
911
+ </div>
912
+ <div class="footer-feature">
913
+ <div class="footer-feature-icon">πŸ”’</div>
914
+ <div class="footer-feature-text">Privacy First</div>
915
+ </div>
916
+ <div class="footer-feature">
917
+ <div class="footer-feature-icon">🌍</div>
918
+ <div class="footer-feature-text">Global Impact</div>
919
+ </div>
920
  </div>
921
+ <div style="font-size: 0.9rem; opacity: 0.85;">
922
+ Built with ❀️ for Hack2Skill Hackathon 2025 | πŸ‰ Data Dragons Team
 
923
  </div>
924
+ <div style="font-size: 0.8rem; opacity: 0.6; margin-top: 0.5rem;">
925
+ Powered by Advanced AI β€’ Making Truth Accessible to Everyone
 
926
  </div>
927
  </div>
928
+ """,
929
+ unsafe_allow_html=True,
930
+ )