From 11adaf1b4e76bb19ec6796ad7bb6afd0720d7e35 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Apr 2026 02:48:07 +0000 Subject: [PATCH 1/2] Add web-based HTML UI with Flask backend Adds a browser-based interface for the multi-AI article scoring system, with dark mode Apple-style design, real-time SSE progress updates, AI model status cards, and full pipeline visualization. Works in demo mode out of the box (no API keys needed). https://claude.ai/code/session_018yqjBvzXGHi6VbxRbSJt9w --- requirements.txt | 3 + templates/index.html | 645 +++++++++++++++++++++++++++++++++++++++++++ web_app.py | 174 ++++++++++++ 3 files changed, 822 insertions(+) create mode 100644 templates/index.html create mode 100644 web_app.py diff --git a/requirements.txt b/requirements.txt index d8852f3..7e78062 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,6 @@ Pillow>=10.0.0 # Logo display in GUI gspread>=5.10.0 # Google Sheets API google-auth>=2.22.0 # Google authentication rich>=13.0.0 + +# Web UI +flask>=3.0.0 diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..62065fe --- /dev/null +++ b/templates/index.html @@ -0,0 +1,645 @@ + + + + + +News Intelligence System + + + +
+
+

News Intelligence System

+

Multi-AI Article Scoring — 5 models, 1 consensus

+
+ + +
+
News SourceFetch articles
+ +
Health CheckVerify APIs
+ +
AI Scoring5 models
+ +
Peer ReviewCross-check
+ +
Fact CheckPerplexity
+ +
ConsensusFinal score
+
+ + +
Select an Article to Analyze
+
+ + + + + +
+
+
Starting analysis...
+
+ + +
AI Models
+
+
ChatGPT
Idle
+
Claude
Idle
+
Gemini
Idle
+
Grok
Idle
+
Perplexity
Idle
+
+ + +
+ +
+
--
+
AI Radar Score (out of 10)
+
+ Confidence: -- + Variance: -- + Action: -- +
+
+ + +
+
+

Individual Scores

+
+
+
+

Peer Review

+ + + +
AI 1ScoreAI 2ScoreResult
+
+
+ +
+
+

Fact Check

+
+
+
+

Key Insights

+
+
+
+ + +
+

AI Rationales

+
+
+
+
+ + + + diff --git a/web_app.py b/web_app.py new file mode 100644 index 0000000..a3157fd --- /dev/null +++ b/web_app.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +""" +News Intelligence System - Web UI +Flask-based web interface for multi-AI article scoring. + +Run: python web_app.py +Then open http://localhost:5000 +""" + +import json +import os +import time +import queue +import threading +from flask import Flask, render_template, jsonify, request, Response + +# Reuse data from demo.py +from demo import SAMPLE_ARTICLES, SIMULATED_SCORES, AI_MODELS + +app = Flask(__name__) + +# Global queue for SSE events per analysis session +analysis_events = {} + + +def _send_event(session_id, event_type, data): + """Push an SSE event to the session queue.""" + if session_id in analysis_events: + analysis_events[session_id].put({"event": event_type, "data": data}) + + +def run_demo_analysis(session_id, article_index): + """Run a simulated analysis pipeline with progress events.""" + article = SAMPLE_ARTICLES[article_index] + _send_event(session_id, "step", {"step": "health_check", "message": "Checking AI engines..."}) + + # Step 0: Health check + for model, config in AI_MODELS.items(): + time.sleep(0.3) + _send_event(session_id, "health", { + "model": model, + "status": "online", + "latency": 42 + hash(model) % 50, + }) + _send_event(session_id, "step", {"step": "health_check_done", "message": "All 5 AI engines operational"}) + time.sleep(0.3) + + # Step 1: Individual scoring + _send_event(session_id, "step", {"step": "scoring", "message": "AI models scoring article..."}) + for model, data in SIMULATED_SCORES.items(): + time.sleep(0.5) + _send_event(session_id, "score", { + "model": model, + "score": data["score"], + "reasoning": data["reasoning"], + "strengths": data["strengths"], + "concerns": data["concerns"], + }) + time.sleep(0.3) + + # Step 2: Peer review + _send_event(session_id, "step", {"step": "peer_review", "message": "Peer review in progress..."}) + pairs = [ + ("ChatGPT", "Perplexity", "AGREEMENT"), + ("Claude", "Grok", "AGREEMENT"), + ("Gemini", "Claude", "DISAGREEMENT"), + ] + for ai1, ai2, result in pairs: + time.sleep(0.4) + _send_event(session_id, "peer", { + "ai1": ai1, + "ai2": ai2, + "score1": SIMULATED_SCORES[ai1]["score"], + "score2": SIMULATED_SCORES[ai2]["score"], + "result": result, + }) + time.sleep(0.3) + + # Step 3: Fact check + _send_event(session_id, "step", {"step": "fact_check", "message": "Perplexity fact-checking claims..."}) + time.sleep(0.5) + fact_checks = [ + {"claim": "15-20% improvement on reasoning tests", "verdict": "VERIFIED", "details": "Matches published benchmarks"}, + {"claim": "Human-level reasoning", "verdict": "PARTIAL", "details": "True for narrow tasks, contested for general"}, + ] + _send_event(session_id, "facts", {"checks": fact_checks}) + time.sleep(0.3) + + # Step 4: Consensus + _send_event(session_id, "step", {"step": "consensus", "message": "Calculating consensus score..."}) + time.sleep(0.5) + scores = [d["score"] for d in SIMULATED_SCORES.values()] + avg_score = sum(scores) / len(scores) + _send_event(session_id, "consensus", { + "score": round(avg_score, 1), + "confidence": "HIGH", + "variance": 0.8, + "recommendation": "SHARE WITH CONTEXT", + }) + time.sleep(0.3) + + # Step 5: Insights + _send_event(session_id, "step", {"step": "insights", "message": "Generating insights..."}) + time.sleep(0.3) + insights = [ + {"category": "FACTUAL ACCURACY", "rating": "High", "details": "Claims verified against published sources"}, + {"category": "SOURCE QUALITY", "rating": "Excellent", "details": f"{article['source']} is Tier-1 publication"}, + {"category": "HEADLINE CONCERN", "rating": "Moderate", "details": "'Human-level' framing debated"}, + {"category": "RECOMMENDATION", "rating": "Share with context", "details": "Good for professional network"}, + ] + _send_event(session_id, "insights", {"insights": insights}) + + # Done + _send_event(session_id, "step", {"step": "done", "message": "Analysis complete"}) + _send_event(session_id, "done", {}) + + +@app.route("/") +def index(): + return render_template("index.html") + + +@app.route("/api/articles") +def get_articles(): + return jsonify(SAMPLE_ARTICLES) + + +@app.route("/api/analyze", methods=["POST"]) +def start_analysis(): + data = request.get_json() + article_index = data.get("article_index", 0) + session_id = f"session_{int(time.time() * 1000)}" + analysis_events[session_id] = queue.Queue() + + thread = threading.Thread(target=run_demo_analysis, args=(session_id, article_index), daemon=True) + thread.start() + + return jsonify({"session_id": session_id}) + + +@app.route("/api/analyze/stream") +def stream_analysis(): + session_id = request.args.get("session_id") + if not session_id or session_id not in analysis_events: + return Response("Invalid session", status=400) + + def generate(): + q = analysis_events[session_id] + while True: + try: + event = q.get(timeout=30) + yield f"event: {event['event']}\ndata: {json.dumps(event['data'])}\n\n" + if event["event"] == "done": + break + except queue.Empty: + yield "event: ping\ndata: {}\n\n" + # Cleanup + analysis_events.pop(session_id, None) + + return Response(generate(), mimetype="text/event-stream", headers={ + "Cache-Control": "no-cache", + "X-Accel-Buffering": "no", + }) + + +@app.route("/api/sample-report") +def sample_report(): + report_path = os.path.join(os.path.dirname(__file__), "sample_output", "analysis_report.json") + with open(report_path) as f: + return jsonify(json.load(f)) + + +if __name__ == "__main__": + app.run(debug=True, host="0.0.0.0", port=5000) From f2b11aa912ed68eb4efea88c8c04cc7a7fb58d7a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 12:09:38 +0000 Subject: [PATCH 2/2] Fix lint and security issues in web_app.py - Format with black and isort for CI compliance - Use env var for debug mode instead of hardcoded True (CodeQL fix) https://claude.ai/code/session_018yqjBvzXGHi6VbxRbSJt9w --- web_app.py | 143 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 99 insertions(+), 44 deletions(-) diff --git a/web_app.py b/web_app.py index a3157fd..a8e12fa 100644 --- a/web_app.py +++ b/web_app.py @@ -9,13 +9,14 @@ import json import os -import time import queue import threading -from flask import Flask, render_template, jsonify, request, Response +import time + +from flask import Flask, Response, jsonify, render_template, request # Reuse data from demo.py -from demo import SAMPLE_ARTICLES, SIMULATED_SCORES, AI_MODELS +from demo import AI_MODELS, SAMPLE_ARTICLES, SIMULATED_SCORES app = Flask(__name__) @@ -37,29 +38,41 @@ def run_demo_analysis(session_id, article_index): # Step 0: Health check for model, config in AI_MODELS.items(): time.sleep(0.3) - _send_event(session_id, "health", { - "model": model, - "status": "online", - "latency": 42 + hash(model) % 50, - }) - _send_event(session_id, "step", {"step": "health_check_done", "message": "All 5 AI engines operational"}) + _send_event( + session_id, + "health", + { + "model": model, + "status": "online", + "latency": 42 + hash(model) % 50, + }, + ) + _send_event( + session_id, "step", {"step": "health_check_done", "message": "All 5 AI engines operational"} + ) time.sleep(0.3) # Step 1: Individual scoring _send_event(session_id, "step", {"step": "scoring", "message": "AI models scoring article..."}) for model, data in SIMULATED_SCORES.items(): time.sleep(0.5) - _send_event(session_id, "score", { - "model": model, - "score": data["score"], - "reasoning": data["reasoning"], - "strengths": data["strengths"], - "concerns": data["concerns"], - }) + _send_event( + session_id, + "score", + { + "model": model, + "score": data["score"], + "reasoning": data["reasoning"], + "strengths": data["strengths"], + "concerns": data["concerns"], + }, + ) time.sleep(0.3) # Step 2: Peer review - _send_event(session_id, "step", {"step": "peer_review", "message": "Peer review in progress..."}) + _send_event( + session_id, "step", {"step": "peer_review", "message": "Peer review in progress..."} + ) pairs = [ ("ChatGPT", "Perplexity", "AGREEMENT"), ("Claude", "Grok", "AGREEMENT"), @@ -67,46 +80,82 @@ def run_demo_analysis(session_id, article_index): ] for ai1, ai2, result in pairs: time.sleep(0.4) - _send_event(session_id, "peer", { - "ai1": ai1, - "ai2": ai2, - "score1": SIMULATED_SCORES[ai1]["score"], - "score2": SIMULATED_SCORES[ai2]["score"], - "result": result, - }) + _send_event( + session_id, + "peer", + { + "ai1": ai1, + "ai2": ai2, + "score1": SIMULATED_SCORES[ai1]["score"], + "score2": SIMULATED_SCORES[ai2]["score"], + "result": result, + }, + ) time.sleep(0.3) # Step 3: Fact check - _send_event(session_id, "step", {"step": "fact_check", "message": "Perplexity fact-checking claims..."}) + _send_event( + session_id, "step", {"step": "fact_check", "message": "Perplexity fact-checking claims..."} + ) time.sleep(0.5) fact_checks = [ - {"claim": "15-20% improvement on reasoning tests", "verdict": "VERIFIED", "details": "Matches published benchmarks"}, - {"claim": "Human-level reasoning", "verdict": "PARTIAL", "details": "True for narrow tasks, contested for general"}, + { + "claim": "15-20% improvement on reasoning tests", + "verdict": "VERIFIED", + "details": "Matches published benchmarks", + }, + { + "claim": "Human-level reasoning", + "verdict": "PARTIAL", + "details": "True for narrow tasks, contested for general", + }, ] _send_event(session_id, "facts", {"checks": fact_checks}) time.sleep(0.3) # Step 4: Consensus - _send_event(session_id, "step", {"step": "consensus", "message": "Calculating consensus score..."}) + _send_event( + session_id, "step", {"step": "consensus", "message": "Calculating consensus score..."} + ) time.sleep(0.5) scores = [d["score"] for d in SIMULATED_SCORES.values()] avg_score = sum(scores) / len(scores) - _send_event(session_id, "consensus", { - "score": round(avg_score, 1), - "confidence": "HIGH", - "variance": 0.8, - "recommendation": "SHARE WITH CONTEXT", - }) + _send_event( + session_id, + "consensus", + { + "score": round(avg_score, 1), + "confidence": "HIGH", + "variance": 0.8, + "recommendation": "SHARE WITH CONTEXT", + }, + ) time.sleep(0.3) # Step 5: Insights _send_event(session_id, "step", {"step": "insights", "message": "Generating insights..."}) time.sleep(0.3) insights = [ - {"category": "FACTUAL ACCURACY", "rating": "High", "details": "Claims verified against published sources"}, - {"category": "SOURCE QUALITY", "rating": "Excellent", "details": f"{article['source']} is Tier-1 publication"}, - {"category": "HEADLINE CONCERN", "rating": "Moderate", "details": "'Human-level' framing debated"}, - {"category": "RECOMMENDATION", "rating": "Share with context", "details": "Good for professional network"}, + { + "category": "FACTUAL ACCURACY", + "rating": "High", + "details": "Claims verified against published sources", + }, + { + "category": "SOURCE QUALITY", + "rating": "Excellent", + "details": f"{article['source']} is Tier-1 publication", + }, + { + "category": "HEADLINE CONCERN", + "rating": "Moderate", + "details": "'Human-level' framing debated", + }, + { + "category": "RECOMMENDATION", + "rating": "Share with context", + "details": "Good for professional network", + }, ] _send_event(session_id, "insights", {"insights": insights}) @@ -132,7 +181,9 @@ def start_analysis(): session_id = f"session_{int(time.time() * 1000)}" analysis_events[session_id] = queue.Queue() - thread = threading.Thread(target=run_demo_analysis, args=(session_id, article_index), daemon=True) + thread = threading.Thread( + target=run_demo_analysis, args=(session_id, article_index), daemon=True + ) thread.start() return jsonify({"session_id": session_id}) @@ -157,10 +208,14 @@ def generate(): # Cleanup analysis_events.pop(session_id, None) - return Response(generate(), mimetype="text/event-stream", headers={ - "Cache-Control": "no-cache", - "X-Accel-Buffering": "no", - }) + return Response( + generate(), + mimetype="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "X-Accel-Buffering": "no", + }, + ) @app.route("/api/sample-report") @@ -171,4 +226,4 @@ def sample_report(): if __name__ == "__main__": - app.run(debug=True, host="0.0.0.0", port=5000) + app.run(debug=os.environ.get("FLASK_DEBUG", "0") == "1", host="0.0.0.0", port=5000)