# Project export: @groq is this true

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: Cal Hacks 12.0
- Tagline: Misinformation, AI-generated content, and ulterior motives. The unholy trinity of modern-day struggles on the internet. This extension is your all in one tool to stay sure of what you see.
- Devpost: https://devpost.com/software/groq-is-this-true
- GitHub: https://github.com/tomayyeung/groq-is-this-true
- Video: https://www.youtube.com/embed/rZTSPu-qyRA?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 4 GitHub contributor(s) — George Scholefield (15 commits), nishoof (11 commits), Thomas Yeung (11 commits), Leo Zhang (4 commits)

## Devpost submission (written by the team)

### Inspiration

Everyone on the internet today knows the struggle of staying rooted in the truth, but nobody ends up taking the extra time to verify or fact-check everything they see online. We wanted a browser extension that would allow people to check what they see online quickly and easily, so everyone could be more encouraged to double-check what they react to.

### What it does

Our extension allows you to highlight any text and immediately get deep research about what you read, complete with sources. Additionally, you can open the extension to get access to four buttons. One button checks the entire article to see if the general information is correct. Another button allows you to take a screenshot of an image to see if it is AI-generated. Yet another button allows you to scan the article you are looking at to see if it was written by AI. Finally, the last button gives you information about what you are reading, letting you know if it is biased and how generally credible its reputation is.

### How we built it

We built this extension using the standard Chrome extension framework using JavaScript and HTML, using Git for version control. We incorporated multiple APIs into the extension: Groq, Sapling, and Sightengine.

## README (from the GitHub repository)

# groq-is-this-true

A Chrome extension for fact-checking text and detecting AI-generated images.

## Features

### 📝 Fact Checking
- Select text on any webpage to fact-check it using Groq AI
- Get instant verification with sources

### 🖼️ AI Image Detection
- Capture and analyze screenshots to detect AI-generated images
- Uses Sightengine's AI detection API
- Interactive cropper to select specific areas of the page
- Visual confidence indicators

## Setup

### 1. Install Dependencies
```bash
npm install
```

### 2. Build the Extension
```bash
npm run build
```

### 3. Configure API Keys
1. Load the extension in Chrome (Developer mode)
2. Click on the extension icon and go to Options
3. Enter your API credentials:
   - **GROQ API Key**: Get from [console.groq.com](https://console.groq.com)
   - **Sightengine API User & Secret**: Get from [sightengine.com](https://sightengine.com)

## Usage

### Fact Check Text
1. Select any text on a webpage
2. Click the extension icon
3. Click "📝 Fact Check" button
4. View the fact-check results and sources

### Detect AI Images
1. Click the extension icon
2. Click "🖼️ AI Detect" button
3. Click "📸 Capture Screenshot"
4. Draw a rectangle around the image you want to analyze
5. View the AI detection results with confidence score

## How It Works

### Screenshot Capture Flow
1. User clicks "Capture Screenshot" in the popup
2. Content script (`selection.js`) creates an overlay with crosshair cursor
3. User draws a rectangle to select the area
4. Screenshot is captured using `chrome.tabs.captureVisibleTab`
5. Image is cropped to the selected area
6. Cropped image is sent to Sightengine API for AI detection
7. Results are displayed in the popup

### Technologies Used
- **Groq AI**: For fact-checking text content
- **Sightengine API**: For AI-generated image detection
- **Cropper.js**: For interactive screenshot selection
- **Chrome Extension APIs**: For browser integration

## Development Ideas

- agentic
  - automatically scans the site when you open it without us clicking the button
  - only alerts when there's misinformation
  - pro plan?
- website bias checker
  - there are some tools out there that can tell you how biased a website is / what their media bias is
  - https://www.allsides.com/media-bias/media-bias-chart
  - can try to find an api? if not, just hard code some shit
  - cache results?
- edit dom
  - when we find misinformation in the text, highlight it in red

## License

MIT


## Detected evidence (automated analysis)

Indexed codebase: 13 recognized source files, 46 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (17 of 17)

```
.gitignore
background.js
cropper-handler.js
groq.js
manifest.json
news-bias.json
options/options.html
options/options.js
package.json
popup.html
popup/popup.css
popup/popup.html
popup/popup.js
README.md
sapling.js
selection.js
SETUP_GUIDE.md
```

### Dependencies

- package.json: esbuild@^0.25.11, groq-sdk@latest

### Recent commits (newest first)

- should be good now
- Merge branch 'main' of https://github.com/tomayyeung/groq-is-this-true
- Added some better visuals
- merging bias detection with text detection
- ai text detection, we now return a probability
- bias detection done
- Everything is good up until the response we get
- almost at full functionality for ai text
- Last button getting closer
- refactor + ui
- added the new button
- functioning fact check on full page
- groq full page functionality
- refactor: organize background.js, add some comments
- Merge branch 'ai-image-detection'
- remove sources idea from readme
- handle no sources
- console.log
- @groq is this true? icon
- @groq, is this true?

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

### SETUP_GUIDE.md

```markdown
# Setup & Usage Guide

## Quick Start

### 1. Get Your API Keys

#### Groq API Key (for fact-checking)
1. Visit [console.groq.com](https://console.groq.com)
2. Sign up or log in
3. Navigate to API Keys section
4. Create a new API key
5. Copy the key (starts with `gsk_...`)

#### Sightengine API Credentials (for AI image detection)
1. Visit [sightengine.com](https://sightengine.com)
2. Sign up for a free account
3. Go to your dashboard
4. Find your **API User** and **API Secret**
5. Copy both values

### 2. Install the Extension

1. Open Chrome and go to `chrome://extensions/`
2. Enable "Developer mode" (toggle in top right)
3. Click "Load unpacked"
4. Select the `groq-is-this-true` folder
5. The extension icon should appear in your toolbar

### 3. Configure API Keys

1. Click the extension icon
2. Click on "Options" or right-click the icon → "Options"
3. Enter your API credentials:
   - GROQ API Key
   - Sightengine API User
   - Sightengine API Secret
4. Click "Save All Settings"

## Usage

### Feature 1: Fact Check Text

1. **Select text** on any webpage
2. **Click the extension icon**
3. The "Fact Check" tab should be active by default
4. View the fact-check results and sources

### Feature 2: Detect AI-Generated Images

1. **Click the extension icon**
2. **Click "🖼️ AI Detect" button** to switch to AI detection mode
3. **Click "📸 Capture Screenshot"** button
4. Your screen will show a semi-transparent overlay
5. **Click and drag** to draw a rectangle around the image you want to analyze
6. **Release the mouse** to capture
7. The extension will analyze the selected area and show:
   - Whether it's AI-generated or real
   - Confidence percentage
   - AI probability score

### Tips for AI Detection

- ✅ **Select clear images**: The detection works best on clear, high-quality images
- ✅ **Select the whole image**: Make sure to include the entire image in your selection
- ✅ **Press ESC to cancel**: If you want to start over, press the Escape key
- ⚠️ **API limits**: Free Sightengine accounts have usage limits (check their pricing)

## Troubleshooting

### "API key not configured" Error
- Go to extension Options and make sure you've saved your API keys
- Make sure the keys are correct and haven't expired

### "Error: no response" in Fact Check
- Check your internet connection
- Verify your Groq API key is valid
- Make sure you have API credits/quota remaining

### AI Detection Not Working
- Verify both Sightengine API User and Secret are configured
- Check that you have remaining API quota on your Sightengine account
- Make sure the selected area is at least 10x10 pixels

### Screenshot Overlay Stuck
- Press the **Escape (ESC)** key to cancel and remove the overlay
- Reload the page if needed

## Privacy & Security Notes

- 🔒 API keys are stored locally in Chrome's sync storage
- 🔒 Text and images are sent to third-party APIs (Groq and Sightengine)
- 🔒 For production use, consider using a backend proxy instead of client-side API keys
- 📊 Chec
[truncated — 559 more characters]
```

### package.json

```
{
  "name": "groq-is-this-true",
  "scripts": {
    "build": "npm run build:background && npm run build:cropper",
    "build:background": "esbuild background.js --bundle --outfile=dist/background.js --format=esm",
    "build:cropper": "esbuild cropper-handler.js --bundle --outfile=dist/cropper-handler.js --format=iife"
  },
  "dependencies": {
    "groq-sdk": "latest"
  },
  "devDependencies": {
    "esbuild": "^0.25.11"
  }
}

```

### sapling.js

```javascript
function extractArticleText() {
  let paragraphs = [];

  // Prefer <article> content if it exists
  const article = document.querySelector("article");
  if (article) {
    paragraphs = Array.from(article.querySelectorAll("p"));
  } else {
    // Fallback: get all <p> tags in the document
    paragraphs = Array.from(document.querySelectorAll("p"));
  }

  // Extract text and clean it up
  const text = paragraphs
    .map(p => p.innerText.trim())
    .filter(t => t.length > 0)
    .join("\n\n"); // Separate paragraphs clearly

  return text;
}

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === "extractText") {
    const articleText = extractArticleText();
    sendResponse({ text: articleText });
  }
});
```

### selection.js

```javascript
// Listen for getSelection request from popup
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === "getSelection") {
    const selectedText = window.getSelection()?.toString() || "";
    const currentHost = window.location.hostname;
    const currentUrl = window.location.href;
    sendResponse({ selectedText, currentHost, currentUrl });
  }
  return true; // keep channel open for async response
});

let popup = null;

function removePopup() {
  if (popup) {
    popup.remove();
    popup = null
  }
}
document.addEventListener("mouseup", (event) => {
  const selection = window.getSelection();
  const selectedText = selection.toString().trim();
  if (selectedText && !popup) {
    console.log("Final selected text:", selectedText);
    const range = selection.getRangeAt(0);
    const rect = range.getBoundingClientRect();

    popup = document.createElement("div");
    popup.innerText = "@groq, is this true?";
    popup.className = "popup"
    popup.style.position = "fixed";
    popup.style.left = `${rect.left + rect.width / 2}px`;
    popup.style.top = `${rect.top - 30}px`;
    popup.style.transform = "translateX(-50%)";
    popup.style.background = "#222";
    popup.style.color = "white";
    popup.style.padding = "6px 10px";
    popup.style.borderRadius = "6px";
    popup.style.fontSize = "14px";
    popup.style.boxShadow = "0 2px 6px rgba(0,0,0,0.3)";
    popup.style.cursor = "pointer";
    popup.style.zIndex = "999999";
    popup.style.userSelect = "none";

    document.body.appendChild(popup);

    popup.addEventListener("click", function () {
      chrome.runtime.sendMessage({ action: "open_extension_popup" });
      removePopup();
    });
  } else if (popup && !popup.contains(event.target)) {
    console.log("there is a popup!");
    removePopup();
  }

});

```

### groq.js

```javascript
import Groq from "groq-sdk";

export async function factCheck(statement, apiKey, currentUrl, currentHost) {
  if (!apiKey) {
    throw new Error(
      "No GROQ API key provided."
    );
  }

  const groq = new Groq({
    apiKey,
    dangerouslyAllowBrowser: true,
  });

  const messages = statement ?
    // highlighted text
    [
      {
        role: "system",
        content:
          `You are a fact-checker. Evaluate whether the given statement is true or false. Provide a brief response in 3 sentences or less. Do not use sources from the site ${currentHost}. Do not use markdown. If you didn't find any sources agreeing or disagreeing with the statement, then just give me the brief response. If you found sources, then insert the sources after your brief response. Separate the brief response and the list of sources with the delimiter '^^^^^'. Separate sources with '^^^'. Sources should just be links, don't include any other text.`,
      },
      {
        role: "user",
        content: statement,
      },
    ] :
    // no highlighted text, evaulate the article
    [
      {
        role: "user",
        content:
          `You are a fact-checker. Evaluate whether the content of the page ${currentUrl} is true or false. Provide a brief response in 3 sentences or less. Do not use sources from the site ${currentHost}. Do not use markdown. If you didn't find any sources agreeing or disagreeing, then just give me the brief response. If you found sources, then insert the sources after your brief response. Separate the brief response and the list of sources with the delimiter '^^^^^'. Separate sources with '^^^'. Sources should just be links, don't include any other text.`,
      },
    ];
    
  const completion = await groq.chat.completions.create({
    model: "groq/compound",
    messages: messages,
  });

  return completion.choices[0]?.message?.content;
}
```

### popup.html

```html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <title>Groq</title>
  <style>
    body {
      font-family: sans-serif;
      padding: 10px;
      width: 300px;
    }

    #result {
      margin-top: 10px;
      padding: 8px;
      background: #f0f0f0;
      border-radius: 6px;
      min-height: 50px;
    }

    .action-buttons {
      display: flex;
      gap: 10px;
      margin-bottom: 15px;
    }

    .action-btn {
      flex: 1;
      padding: 8px;
      border: none;
      border-radius: 6px;
      cursor: pointer;
      font-size: 14px;
      font-weight: 500;
    }

    .fact-check-btn {
      background: #4CAF50;
      color: white;
    }

    .ai-detect-btn {
      background: #2196F3;
      color: white;
    }

    .ai-text-detect-btn {
      background: #f32121;
      color: white;
    }

    .action-btn:hover {
      opacity: 0.9;
    }

    .action-btn:active {
      transform: scale(0.98);
    }

    .content-section {
      display: none;
    }

    .content-section.active {
      display: block;
    }

    #ai-result {
      margin-top: 10px;
      padding: 8px;
      background: #f0f0f0;
      border-radius: 6px;
      min-height: 50px;
    }
  </style>
</head>

<body>
  <div class="action-buttons">
    <button class="action-btn fact-check-btn" id="fact-check-mode">📝 Fact Check</button>
    <button class="action-btn ai-detect-btn" id="ai-detect-mode">🖼️ AI Detect</button>
    <button class="action-btn ai-text-detect-btn" id="ai-text-detect-mode">✏️ Detect AI Text</button>
  </div>

  <div id="fact-check-section" class="content-section active">
    <h3>Groq says:</h3>
    <h4 id="current-url"></h4>
    <div id="info">Loading result...</div>
    <div id="sources-label"></div>
    <ul id="sources"></ul>
  </div>

  <div id="ai-detect-section" class="content-section">
    <h3>AI Image Detection:</h3>
    <button id="capture-screenshot" class="action-btn ai-detect-btn" style="width: 100%; margin-bottom: 10px;">
      📸 Capture Screenshot
    </button>
    <div id="ai-result">Click "Capture Screenshot" to analyze an image</div>
  </div>

  <div id="raw-result"></div>

  <div id="ai-text-detect-section" class="content-section">
    <h3>AI Text Detection:</h3>
    <h4 id="current-url"></h4>
    <div id="ai-text-result">Loading result...</div>
  </div>

  <script src="popup.js"></script>
</body>

</html>
```

### cropper-handler.js

```javascript
let selectionOverlay = null;
let selectionBox = null;
let startX, startY;
let isSelecting = false;

// Listen for messages to start cropping
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === "startScreenshotCapture") {
    initializeSelectionMode();
    sendResponse({ success: true });
  }
  return true;
});

function initializeSelectionMode() {
  // Create semi-transparent overlay
  selectionOverlay = document.createElement('div');
  selectionOverlay.id = 'groq-selection-overlay';
  selectionOverlay.style.cssText = `
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.3);
    z-index: 2147483647;
    cursor: crosshair;
  `;

  // Create selection box
  selectionBox = document.createElement('div');
  selectionBox.id = 'groq-selection-box';
  selectionBox.style.cssText = `
    position: fixed;
    border: 2px solid #2196F3;
    background: rgba(33, 150, 243, 0.1);
    box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.3);
    z-index: 2147483648;
    display: none;
    pointer-events: none;
  `;

  // Create instruction text
  const instructionText = document.createElement('div');
  instructionText.id = 'groq-instruction-text';
  instructionText.style.cssText = `
    position: fixed;
    top: 20px;
    left: 50%;
    transform: translateX(-50%);
    background: #2196F3;
    color: white;
    padding: 12px 24px;
    border-radius: 8px;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-size: 14px;
    z-index: 2147483649;
    box-shadow: 0 4px 12px rgba(0,0,0,0.3);
    pointer-events: none;
  `;
  instructionText.textContent = '🖼️ Click and drag to select an area • Press ESC to cancel';

  document.body.appendChild(selectionOverlay);
  document.body.appendChild(selectionBox);
  document.body.appendChild(instructionText);

  // Event listeners
  selectionOverlay.addEventListener('mousedown', onMouseDown);
  document.addEventListener('mousemove', onMouseMove);
  document.addEventListener('mouseup', onMouseUp);
  document.addEventListener('keydown', onKeyDown);
}

function onMouseDown(e) {
  e.preventDefault();
  isSelecting = true;
  startX = e.clientX;
  startY = e.clientY;
  
  selectionBox.style.left = startX + 'px';
  selectionBox.style.top = startY + 'px';
  selectionBox.style.width = '0px';
  selectionBox.style.height = '0px';
  selectionBox.style.display = 'block';
}

function onMouseMove(e) {
  if (!isSelecting) return;
  
  const currentX = e.clientX;
  const currentY = e.clientY;
  
  const width = Math.abs(currentX - startX);
  const height = Math.abs(currentY - startY);
  const left = Math.min(currentX, startX);
  const top = Math.min(currentY, startY);
  
  selectionBox.style.width = width + 'px';
  selectionBox.style.height = height + 'px';
  selectionBox.style.left = left + 'px';
  selectionBox.style.top = top + 'px';
}

function onMouseUp(e) {
  if (!isSelecting) return;
  isSelecting = false;
  
  const rect = {
    left: parseInt(selectionBox.style.left),
    top: parseInt(selectionBox.style.top),
    width: parseInt(selectionBox.style.width),
    height: parseInt(selectionBox.style.height)
  };
  
  // Minimum selection size - automatically capture if valid
  if (rect.width > 20 && rect.height > 20) {
    captureSelectedArea(rect);
  } else {
    cleanupSelectionMode();
  }
}

function onKeyDown(e) {
  if (e.key === 'Escape') {
    cleanupSelectionMode();
  }
}

function captureSelectedArea(rect) {
  // Hide UI elements temporarily
  const overlay = document.getElementById('groq-selection-overlay');
  const box = document.getElementById('groq-selection-box');
  const instruction = document.getElementById('groq-instruction-text');
  
  if (overlay) overlay.style.display = 'none';
  if (box) box.style.display = 'none';
  if (instruction) instruction.style.display = 'none';
  
  // Small delay to let the DOM update
  setTimeout(() => {
    // Send message to background to capture the visible tab
    chrome.runtime.sendMessage({ 
      action: "captureAndCrop",
      rect: {
        x: rect.left,
        y: rect.top,
        width: rect.width,
        height: rect.height,
        devicePixelRatio: window.devicePixelRatio || 1
      }
    }, (response) => {
      cleanupSelectionMode();
      
      if (response?.error) {
        alert('Error capturing screenshot: ' + response.error);
      }
      // Popup will be reopened automatically by background script after storing results
    });
  }, 100);
}

function cleanupSelectionMode() {
  if (selectionOverlay) {
    selectionOverlay.removeEventListener('mousedown', onMouseDown);
    selectionOverlay.remove();
    selectionOverlay = null;
  }
  if (selectionBox) {
    selectionBox.remove();
    selectionBox = null;
  }
  const instruction = document.getElementById('groq-instruction-text');
  if (instruction) {
    instruction.remove();
  }
  
  document.removeEventListener('mousemove', onMouseMove);
  document.removeEventListener('mouseup', onMouseUp);
  document.removeEventListener('keydown', onKeyDown);
  isSelecting = false;
}

```

### background.js

```javascript
import { factCheck } from './groq.js';

// Listener
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action === "performFactCheck") {
    performFactCheck(message, sendResponse);
  } else if (message.action === "captureAndCrop") {
    captureAndCrop(message, sendResponse);
  } else if (message.action === "open_extension_popup") {
    openExtensionPopup();
  }
  return true; // keep channel open for async response
});

// Called for message action "performFactCheck". Message should have text and currentUrl. Will call sendResponse with result (string) or error.
function performFactCheck(message, sendResponse) {
  // Retrieve the stored API key from chrome.storage before making the call.
  chrome.storage.sync.get(["groqKey"], (items) => {
    const apiKey = items.groqKey;
    if (!apiKey) {
      sendResponse({ error: "GROQ API key not configured. Open extension options to set the key." });
      return;
    }

    factCheck(message.text, apiKey, message.currentUrl, message.currentHost)
      .then(result => sendResponse({ result }))
      .catch(error => sendResponse({ error: error.message }));
  });
}

// Called for message action "captureAndCrop". Message should have rect. Will call sendResponse with success (boolean) or error (with a message). 
function captureAndCrop(message, sendResponse) {
  // Capture the visible tab
  chrome.tabs.captureVisibleTab(null, { format: 'png' }, (dataUrl) => {
    if (chrome.runtime.lastError) {
      sendResponse({ error: chrome.runtime.lastError.message });
      return;
    }

    // Crop the image based on the selection rect
    cropImage(dataUrl, message.rect).then(croppedDataUrl => {
      // Send to Sightengine API for AI detection
      detectAIImage(croppedDataUrl).then(result => {
        const resultData = {
          action: "aiDetectionResult",
          result: result.type
        };
        // Store in chrome.storage.local first for popup persistence
        chrome.storage.local.set({ pendingAIResult: resultData }, () => {
          // Wait a moment to ensure storage is written, then reopen popup
          setTimeout(() => {
            chrome.action.openPopup(() => {
              if (chrome.runtime.lastError) {
                console.log('Could not open popup:', chrome.runtime.lastError);
              }
            });
          }, 100);
        });
        sendResponse({ success: true });
      }).catch(error => {
        const errorData = {
          action: "aiDetectionResult",
          error: error.message
        };
        // Store error in chrome.storage.local first
        chrome.storage.local.set({ pendingAIResult: errorData }, () => {
          // Wait a moment to ensure storage is written, then reopen popup
          setTimeout(() => {
            chrome.action.openPopup(() => {
              if (chrome.runtime.lastError) {
                console.log('Could not open popup:', chrome.runtime.lastError);
              }
            });
          }, 100);
        });
        sendResponse({ error: error.message });
      });
    }).catch(error => {
      sendResponse({ error: error.message });
    });
  });
}

// Called for message action "open_extension_popup". Used by popup to open the extension when the user clicks the tooltip.
function openExtensionPopup() {
  // Programmatically open the extension's popup
  if (chrome.action.openPopup) {
    chrome.action.openPopup();
  } else {
    console.warn("chrome.action.openPopup not available in this Chrome version or context.");
  }
}

// Helper function for captureAndCrop() to crop the image
function cropImage(dataUrl, rect) {
  return new Promise((resolve, reject) => {
    // Create an ImageBitmap from the data URL
    fetch(dataUrl)
      .then(res => res.blob())
      .then(blob => createImageBitmap(blob))
      .then(imageBitmap => {
        const canvas = new OffscreenCanvas(rect.width, rect.height);
        const ctx = canvas.getContext('2d');

        // Account for device pixel ratio
        const dpr = rect.devicePixelRatio || 1;

        ctx.drawImage(
          imageBitmap,
          rect.x * dpr,
          rect.y * dpr,
          rect.width * dpr,
          rect.height * dpr,
          0,
          0,
          rect.width,
          rect.height
        );

        return canvas.convertToBlob({ type: 'image/png' });
      })
      .then(blob => {
        const reader = new FileReader();
        reader.onloadend = () => resolve(reader.result);
        reader.readAsDataURL(blob);
      })
      .catch(reject);
  });
}

// Helper function for captureAndCrop() to detect AI-generated image using Sightengine API
async function detectAIImage(imageDataUrl) {
  return new Promise((resolve, reject) => {
    chrome.storage.sync.get(["sightengineUser", "sightengineSecret"], async (items) => {
      const apiUser = items.sightengineUser;
      const apiSecret = items.sightengineSecret;

      if (!apiUser || !apiSecret) {
        reject(new Error("Sightengine API credentials not configured. Open extension options to set them."));
        return;
      }

      try {
        // Convert data URL to blob
        const blob = await (await fetch(imageDataUrl)).blob();

        // Create form data
        const formData = new FormData();
        formData.append('media', blob, 'screenshot.png');
        formData.append('models', 'genai');
        formData.append('api_user', apiUser);
        formData.append('api_secret', apiSecret);

        // Send to Sightengine API
        const response = await fetch('https://api.sightengine.com/1.0/check.json', {
          method: 'POST',
          body: formData
        });

        if (!response.ok) {
          throw new Error(`API request failed: ${response.status}`);
        }

        const result = await response.json();

        if (result.status === 'success') {
          resolve(result);
        } else {
          reject(new Error(result.error?.message || 'API request failed'));
        }
      } catch (
[truncated — 56 more characters]
```

### options/options.js

```javascript
const groqKeyInput = document.getElementById('groqKey');
const sightengineUserInput = document.getElementById('sightengineUser');
const sightengineSecretInput = document.getElementById('sightengineSecret');
const saplingAPIKeyInput = document.getElementById('saplingAPIKey');
const saveBtn = document.getElementById('save');
const status = document.getElementById('status');

// Load saved keys on open
chrome.storage.sync.get(['groqKey', 'sightengineUser', 'sightengineSecret', 'saplingAPIKey'], (items) => {
  if (items.groqKey) groqKeyInput.value = items.groqKey;
  if (items.sightengineUser) sightengineUserInput.value = items.sightengineUser;
  if (items.sightengineSecret) sightengineSecretInput.value = items.sightengineSecret;
  if (items.saplingAPIKey) saplingAPIKeyInput.value = items.saplingAPIKey;
});

saveBtn.addEventListener('click', () => {
  const groqKey = groqKeyInput.value.trim();
  const sightengineUser = sightengineUserInput.value.trim();
  const sightengineSecret = sightengineSecretInput.value.trim();
  const saplingAPIKey = saplingAPIKeyInput.value.trim();

  if (!groqKey && !sightengineUser && !sightengineSecret && !saplingAPIKey) {
    status.textContent = 'Please enter at least one API credential.';
    status.classList.add('error');
    return;
  }

  const settings = {};
  if (groqKey) settings.groqKey = groqKey;
  if (sightengineUser) settings.sightengineUser = sightengineUser;
  if (sightengineSecret) settings.sightengineSecret = sightengineSecret;
  if (saplingAPIKey) settings.saplingAPIKey = saplingAPIKey;

  chrome.storage.sync.set(settings, () => {
    status.textContent = 'Settings saved successfully!';
    status.classList.remove('error');
    setTimeout(() => (status.textContent = ''), 3000);
  });
});

```

### popup/popup.html

```html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <title>Groq</title>
  <link rel="stylesheet" href="./popup.css">
  <link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&display=swap" rel="stylesheet">
</head>

<body>
  <img id="mascot" src="../imgs/happy.png" alt="happy" width="100">

  <div class="action-buttons">
    <button class="action-btn fact-check-btn" id="fact-check-mode">📝<span class="tooltip-text">Fact Check</span></button>
    <button class="action-btn ai-detect-btn" id="ai-detect-mode">🖼️<span class="tooltip-text">AI Image Detect</span></button>
    <button class="action-btn ai-text-detect-btn" id="ai-text-detect-mode">✏️<span class="tooltip-text">Detect AI Text</span></button>
    <button class="action-btn" id="bias-check-mode" style="background: #9c27b0; color: white;">⚖️<span class="tooltip-text">Bias Check</span></button>
  </div>

  <div class="action-result">
    <div id="fact-check-section" class="content-section active">
      <h3>Groq says:</h3>
      <!-- <h4 id="current-url"></h4> -->
      <div id="info">Loading result...</div>
      <div id="sources-label"></div>
      <ul id="sources"></ul>
    </div>

    <div id="ai-detect-section" class="content-section">
      <h3>AI Image Detection:</h3>
      <button id="capture-screenshot" class="action-btn ai-detect-btn" style="width: 100%; margin-bottom: 10px;">
        📸 Capture Screenshot
      </button>
      <div id="ai-result">Click "Capture Screenshot" to analyze an image</div>
    </div>

    <div id="ai-text-detect-section" class="content-section">
      <h3>AI Text Detection:</h3>
      <button id="check-for-ai-text" class="action-btn ai-text-detect-btn" style="width: 100%; margin-bottom: 10px;">
        🔍 Check for AI
      </button>
      <div id="ai-text-result"></div>
    </div>

    <div id="bias-check-section" class="content-section">
      <h3>Source Bias Analysis:</h3>
      <div id="bias-info" style="margin-top: 10px; padding: 10px; border-radius: 6px;">
        <div id="bias-details">Loading bias information...</div>
      </div>
    </div>
  </div>

  <script src="popup.js"></script>
</body>

</html>
```

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