# Project export: Verity

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: CruzHacks 2025
- Tagline: We built a tool that listens to speech, detects factual claims, and quickly generates fact-check reports with credible sources—bridging the gap between spoken information and verified truth.
- Devpost: https://devpost.com/software/verity-0vjidn
- GitHub: https://github.com/0xcolinhenderson/cruzhacks2025
- Team: 3 GitHub contributor(s) — Abj1-sc (20 commits), Colin Henderson (17 commits), notpr (1 commits)

## Devpost submission (written by the team)

### Inspiration

The Internet has become the backbone of modern society, and is used to share information and other things to everyone. Now more than ever, the Internet has become an invaluable resource for almost everything. With the increase in reliance on the Internet, there has also been an alarming increase in the spread of misinformation. We were inspired to build our project because we noticed how much misinformation has entered our daily lives in things like social media, news articles, and sometimes even informational websites. Our goal is help everyone figure out for themselves what the truth is.

### What it does

Our tool helps users reveal the truth in their conversations. It records and transcribes speech in real time. Any sentence can be clicked and automatically fact-checked against multiple reputable sources, all of which are conveniently linked in a quick AI report for transparency.

### How we built it

Our project is built off of Next.js infrastructure alongside a Flask RESTful API server. Our backend utilizes a queue system to handle requests, which query Gemini API alongside a local retriever model using Langchain & Ollama libraries. We source our information through Wikipedia’s API, as it gives easily obtainable sourcing and straight-to-the-point information.

### Challenges we ran into

We overcame many challenges during the development process, the most difficult being optimizing the retrieval model and the entire pipeline for latency.

### Accomplishments we're proud of

We are proud of the cohesive application that we've built, linking together a powerful backend, responsive and easy to use UI, all while leveraging Gemini and Ollama. It is a fully working project and the first step towards an important goal.

### What we learned

We learned a great deal about the science behind RAG (Retrieval Augmented Generation), pipelining, and model optimization when working with webpage datasets. Additionally, our use of Gemini API taught us how to optimize our prompts for the best straightforward responses. Some of us were not familiar with React & Next, and learned about important React fundamentals (such as useState, references, and routing).

### What's next

The path to bringing Verity to everyone will include extensive testing and fine tuning, user testing, and lots of sleepless nights trying to fix bugs. Our goal is to continue development of Verity to improve its performance, add new features, and most importantly scale it to a production version that we everyone can use.

## README (from the GitHub repository)

# Verity - Realtime Fact-Checking & Conversational Analysis

Verity combats misinformation through realtime retreival of sourced data, which is analyzed and applied to specific claims made during a conversation. The platform encourages research and sourcing claims, improving conversation productiveness and establishing baseline truths to arguments.

With Verity, users can get a realtime transcription of their conversation, analyze specific claims made, and get personalized feedback regardinging the validity of claims.


## 📸 Screenshots

![Homepage](public/screenshots/homepage.png)
_Home Page_

![Analysis](public/screenshots/analysis.png)
_Interface for live conversational analysis_


## 🚀 Getting Started

### Prerequisites

- Node.js (v15.1.5 or higher)
- npm or yarn
- Python 3.7

### Installation

1. Clone the repository:

   ```bash
   git clone https://github.com/yourusername/speedfill.git
   cd cruzhacks2025
   cd frontend
   ```

2. Install dependencies:

   ```bash
   npm install
   # or
   yarn install
   ```

3. Create a [\\.env\\.local](.env.local) file in the root directory and add your API keys:

   ```
   GEMINI_API_KEY = "YOUR KEY"
   GOOGLESEARCH_API_KEY = "YOUR KEY"
   ```

4. Run the development server:

   ```bash
   npm run dev
   # or
   yarn dev
   ```

5. Open [http://localhost:3000](http://localhost:3000) in your browser to view the application.

## 🛠️ Technology Stack

- **Frontend:** React.js, Next.js
- **AI Integration:** Google Gemini AI API, Ollama+Langchain Retreiver Models (nomic-embed-text)
- **Backend API** Flask
- **Languages** Python, TypeScript, HTML/CSS



## 📜 License

This project is licensed under the MIT License - see the [`LICENSE`](LICENSE) file for details.


## Detected evidence (automated analysis)

Indexed codebase: 24 recognized source files, 56 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- Flask (technology) — claimed on Devpost, not found in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- LangChain (technology) — claimed on Devpost, not found in the code
- Ollama (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (29 of 29)

```
backend/ai_pipeline.py
backend/detect_claim.py
backend/server.py
backend/speech_to_text.py
backend/test.py
backend/vector.py
frontend/.gitignore
frontend/app/about/page.tsx
frontend/app/components/Button.module.css
frontend/app/components/Button.tsx
frontend/app/components/FactCheckBox.module.css
frontend/app/components/FactCheckBox.tsx
frontend/app/components/Navbar.module.css
frontend/app/components/Navbar.tsx
frontend/app/components/Transcription.module.css
frontend/app/components/Transcription.tsx
frontend/app/globals.css
frontend/app/layout.tsx
frontend/app/page.module.css
frontend/app/page.tsx
frontend/app/session/page.tsx
frontend/eslint.config.mjs
frontend/global.d.ts
frontend/next.config.ts
frontend/package.json
frontend/README.md
frontend/tsconfig.json
LICENSE
README.md
```

### Dependencies

- frontend/package.json: @eslint/eslintrc@^3, @types/node@^20, @types/react@^19, @types/react-dom@^19, eslint@^9, eslint-config-next@15.3.0, next@15.3.0, react@^19.0.0, react-dom@^19.0.0, typescript@^5, whatamesh@^0.2.0

### Recent commits (newest first)

- Update README.md
- changed header in main page
- dogp3
- Page animations and cosmetics update
- dogp2
- Added About Page
- Merge branch 'main' of https://github.com/0xcolinhenderson/cruzhacks2025
- dog piss
- move schreenshots
- Update README.md
- Add files via upload
- Delete .DS_Store
- Delete STTandWS directory
- small changes
- minor ui tweaking
- Modify backend to optimize response times
- Restructure Navigation and change cosmetics
- Merge branch 'main' of https://github.com/0xcolinhenderson/cruzhacks2025
- json formatting for llm output
- Restructure Navigation and change cosmetics

## Key source files (fetched from GitHub, selected and truncated for size)

### frontend/package.json

```
{
  "name": "factchecker",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "15.3.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "whatamesh": "^0.2.0"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "15.3.0",
    "typescript": "^5"
  }
}

```

### backend/server.py

```python
from flask import Flask, request, jsonify
from flask_cors import CORS
import threading
import uuid
import time
from queue import Queue
from ai_pipeline import verify_claim

app = Flask(__name__)
CORS(app)

task_queue = Queue()
task_status = {}

state = {
    "output": "",
    "running": False,
    "complete": False,
    "failed": False
}

def worker():
    while True:
        task_id, input_data = task_queue.get()
        task_status[task_id] = {"status": "running", "result": None}
        try:
            result = verify_claim(input_data)
            task_status[task_id] = {"status": "done", "result": result}
        except Exception as e:
            task_status[task_id] = {"status": "error", "result": str(e)}
        finally:
            task_queue.task_done()

threading.Thread(target=worker, daemon=True).start()

@app.route('/queue_claim', methods=['POST'])
def queue_claim():
    claim = request.json.get("claim")
    if not claim or claim == "":
        return jsonify({"error": "No sentence provided"}), 400

    task_id = str(uuid.uuid4())
    task_status[task_id] = {"status": "pending", "result": None}
    task_queue.put((task_id, claim))
    return jsonify({"result": task_id})

@app.route('/poll/<task_id>', methods=['GET'])
def poll(task_id):
    if task_id not in task_status:
        return jsonify({"error": "Task ID not found"}), 404
    return jsonify({"task_id": task_id, **task_status[task_id]})

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

```

### frontend/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({
  subsets: ["latin"],
  weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"],
  variable: "--font-inter",
});

export const metadata: Metadata = {
  title: "Fact Checking App",
  description: "Submission for CruzHacks 2025",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" className={inter.variable}>
      <body>{children}</body>
    </html>
  );
}

```

### frontend/app/page.tsx

```typescript
"use client";

import { Gradient } from "whatamesh";
import { useEffect } from "react";
import styles from "./page.module.css";
import Navbar from "./components/Navbar";
export default function BlankPage() {
  useEffect(() => {
    const gradient = new Gradient();
    gradient.initGradient("#gradient-canvas");
  }, []);

  const routes = [
    { name: "Home", path: "/" },
    { name: "About", path: "/about" },
    {
      name: "Github",
      path: "https://github.com/0xcolinhenderson/cruzhacks2025",
      icon: "/github.svg",
    },
    { name: "Create Session", path: "/session", isMain: true },
  ];

  return (
    <div className={styles.page}>
      <canvas id="gradient-canvas" className={styles.canvas}></canvas>
      <Navbar routes={routes} />
      <main className={styles.main}>
        <div className={styles.contentContainer}>
          <div className={styles.titleContainer}>
            <h1 className={styles.title}>
              Realtime Conversational Fact-Checking
            </h1>
            <h2 className={styles.subtitle}>
              Generate realtime analysis & transcription of conversations to
              combat misinformation. Created for UCSC CruzHacks 2025.
            </h2>
          </div>
        </div>
      </main>
      <div className={styles.mainContent}>
        <div className={styles.feature}>
          <img src="/analysis.JPG" alt="Analysis" />
          <h3 className={styles.featureTitle}>
            Realtime Conversational Analysis
          </h3>
          <p>
            Analyze conversations in real-time to detect misinformation and
            ensure factual accuracy.
          </p>
        </div>
        <div className={styles.feature}>
          <img src="/stt.JPG" alt="STT" />
          <h3 className={styles.featureTitle}>Speech-to-Text</h3>
          <p>
            Convert spoken words into text dynamically with advanced speech
            recognition
          </p>
        </div>
        <div className={styles.feature}>
          <img src="/other.JPG" alt="Verify Claims" />
          <h3 className={styles.featureTitle}>
            Transparent AI & Sources
          </h3>
          <p>
            Verify claims using AI-powered tools and trusted sources. Sources are linked for transparency.
          </p>
        </div>
      </div>
    </div>
  );
}

```

### frontend/app/session/page.tsx

```typescript
"use client";

import { Gradient } from "whatamesh";
import { useEffect, useState } from "react";
import Navbar from "../components/Navbar";
import Transcription from "../components/Transcription";
import FactCheckBox from "../components/FactCheckBox";
import styles from "../page.module.css";

interface FactCheckData {
  claim: string;
  isFact: boolean;
  description: string;
  sources: string[];
  timestamp: string;
}

export default function Home() {
  const [factChecks, setFactChecks] = useState<FactCheckData[]>([]);
  const [autoMode, setAutoMode] = useState(false);

  const handleAutoModeChange = (isAuto: boolean) => {
    setAutoMode(isAuto);
  };

  useEffect(() => {
    const gradient = new Gradient();
    gradient.initGradient("#gradient-canvas");
  }, []);

  const routes = [
    { name: "Home", path: "/" },
    { name: "About", path: "/about" },
    {
      name: "Github",
      path: "https://github.com/0xcolinhenderson/cruzhacks2025",
      icon: "/github.svg",
    },
    { name: "Create Session", path: "/session", isMain: true },
  ];

  const addFactCheck = ({
    claim,
    isFact,
    description,
    sources,
    timestamp,
  }: {
    claim: string;
    isFact: boolean;
    description: string;
    sources: string[];
    timestamp: string;
  }) => {
    setFactChecks((prev) => [
      ...prev,
      { claim, isFact, description, sources, timestamp },
    ]);
  };

  return (
    <div className={styles.page}>
      <canvas id="gradient-canvas" className={styles.canvas}></canvas>
      <Navbar routes={routes} />
      <main className={styles.main}>
        <div className={styles.contentContainer}>
          <FactCheckBox
            factChecks={factChecks}
            onAutoModeChange={handleAutoModeChange}
          />
          <Transcription addFactCheck={addFactCheck} autoMode={autoMode} />
        </div>
      </main>
    </div>
  );
}

```

### frontend/app/about/page.tsx

```typescript
"use client";

import { Gradient } from "whatamesh";
import { useEffect } from "react";
import styles from "../page.module.css";
import Navbar from "../components/Navbar";

export default function AboutPage() {
  useEffect(() => {
    const gradient = new Gradient();
    gradient.initGradient("#gradient-canvas");
  }, []);

  const routes = [
    { name: "Home", path: "/" },
    { name: "About", path: "/about" },
    {
      name: "Github",
      path: "https://github.com/0xcolinhenderson/cruzhacks2025",
      icon: "/github.svg",
    },
    { name: "Create Session", path: "/session", isMain: true },
  ];

  return (
    <div className={styles.page}>
      <canvas id="gradient-canvas" className={styles.canvas}></canvas>
      <Navbar routes={routes} />
      <main className={styles.main}>
        <div className={`${styles.contentContainer} ${styles.horizontalSlide}`}>
          <div className={styles.header}>
            <h1 className={styles.title}>About</h1>
          </div>
          <div className={styles.tiles}>
            <div className={styles.tile}>
              <h2>Inspiration</h2>
              <p>
                With the increase in reliance on the Internet, there has also been an alarming increase in the spread of misinformation. We were inspired to build our project because we noticed how much misinformation has entered our daily lives in things like social media, news articles, and sometimes even informational websites. Our goal is help everyone figure out for themselves what the truth is.
              </p>
            </div>
            <div className={styles.tile}>
              <h2>What it does</h2>
              <p>
                Our tool helps users reveal the truth in their conversations. It records and transcribes speech in real time. Any sentence can be clicked and automatically fact-checked against multiple reputable sources, all of which are conveniently linked in a quick AI report for transparency.
              </p>
            </div>
            <div className={styles.tile}>
              <h2>How we built it</h2>
              <p>
                Our project is built off of Next.js infrastructure alongside a Flask RESTful API server. Our backend utilizes a queue system to handle requests, which query Gemini API alongside a local retriever model using Langchain & Ollama libraries. We source our information through Wikipedia’s API, as it gives easily obtainable sourcing and straight-to-the-point information.
              </p>
            </div>
            <div className={styles.tile}>
              <h2>Built With</h2>
              <ul>
                <li>Flask</li>
                <li>Google Gemini API</li>
                <li>Langchain</li>
                <li>Next.js</li>
                <li>Ollama</li>
                <li>Python</li>
                <li>React</li>
                <li>Typescript</li>
                <li>Web Speech API</li>
                <li>Wikipedia API</li>
              </ul>
            </div>
            <div className={`${styles.tile} ${styles.tileCreatedBy}`}>
              <p>
                Created by: Ayush Bandopadhyay, Colin Henderson, Preston Clayton
              </p>
            </div>
          </div>
        </div>
      </main>
    </div>
  );
}

```

### frontend/next.config.ts

```typescript
import type { NextConfig } from "next";

const nextConfig = {
  reactStrictMode: false,
};

export default nextConfig;

```

### backend/speech_to_text.py

```python
from RealtimeSTT import AudioToTextRecorder
from detect_claims import handle_text, context

if __name__ == '__main__':
    recorder = AudioToTextRecorder()

    while True:
        handle_text(recorder.text(), context)
```

### frontend/global.d.ts

```typescript
interface Window {
  SpeechRecognition: typeof SpeechRecognition;
  webkitSpeechRecognition: typeof SpeechRecognition;
}

type SpeechRecognition = typeof window.SpeechRecognition;
type SpeechRecognitionEvent = Event & {
  results: SpeechRecognitionResultList;
  resultIndex: number;
};
type SpeechRecognitionErrorEvent = Event & {
  error: string;
};

```

### backend/vector.py

```python
from langchain_chroma import Chroma
from langchain_ollama import OllamaEmbeddings
import os
import shutil

def retriever(query, documents):
    """
    Embed and store documents in a Chroma vector store, then retrieve relevant chunks.
    """
    
    embeddings = OllamaEmbeddings(model="nomic-embed-text")

    vector_store = Chroma(
        collection_name="wikipedia_articles",
        persist_directory=None,
        embedding_function=embeddings
    )

    vector_store.add_documents(documents=documents)

    retriever = vector_store.as_retriever(search_kwargs={"k": 5})
    retrieved_docs = retriever.invoke(query)


    source_titles = set()
    for doc in retrieved_docs:
        url = doc.metadata.get("source", "Unknown")
        source_titles.add(url)

    context = "\n".join([doc.page_content for doc in retrieved_docs])
    return context, list(source_titles)

```

[13 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]