mothy-08 commited on
Commit ·
1d1b426
1
Parent(s): aaa4ec9
Added a frontend/Chrome Extension
Browse files- README.md +12 -9
- api/config.py +2 -1
- api/crawler.py +1 -1
- api/server.py +4 -4
- api/vectorstore.py +1 -1
- chrome-extension/.DS_Store +0 -0
- chrome-extension/icons/icon128.png +0 -0
- chrome-extension/icons/icon16.png +0 -0
- chrome-extension/icons/icon32.png +0 -0
- chrome-extension/icons/icon48.png +0 -0
- chrome-extension/manifest.json +22 -0
- chrome-extension/popup.css +186 -0
- chrome-extension/popup.html +56 -0
- chrome-extension/popup.js +162 -0
- requirements.txt +0 -45
README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
#
|
| 2 |
|
| 3 |
A cloud-native, multi-tenant AI chatbot that instantly learns the content of any website and answers questions in real-time. Built for the Cloud Computing final project.
|
| 4 |
|
|
@@ -26,14 +26,17 @@ A cloud-native, multi-tenant AI chatbot that instantly learns the content of any
|
|
| 26 |
## 📂 Project Structure
|
| 27 |
|
| 28 |
```bash
|
| 29 |
-
|
| 30 |
-
├──
|
| 31 |
-
├──
|
| 32 |
-
├──
|
| 33 |
-
├──
|
| 34 |
-
├──
|
| 35 |
-
├──
|
|
|
|
|
|
|
|
|
|
| 36 |
│ ├── manifest.json
|
| 37 |
│ ├── popup.html
|
| 38 |
│ └── popup.js
|
| 39 |
-
└──
|
|
|
|
| 1 |
+
# 🤖 RAGBot
|
| 2 |
|
| 3 |
A cloud-native, multi-tenant AI chatbot that instantly learns the content of any website and answers questions in real-time. Built for the Cloud Computing final project.
|
| 4 |
|
|
|
|
| 26 |
## 📂 Project Structure
|
| 27 |
|
| 28 |
```bash
|
| 29 |
+
ragbot/
|
| 30 |
+
├── api/ # 🐍 Backend Logic
|
| 31 |
+
│ ├── server.py # Main FastAPI entry point
|
| 32 |
+
│ ├── crawler.py # Logic for sitemap parsing & scraping
|
| 33 |
+
│ ├── vectorstore.py # Pinecone batching & management
|
| 34 |
+
│ ├── config.py # Environment & Logging setup
|
| 35 |
+
│ ├── utils.py # Security & URL validation
|
| 36 |
+
│ ├── schemas.py # Pydantic data models
|
| 37 |
+
│ └── requirements.txt # Python Dependencies
|
| 38 |
+
├── extension/ # 🧩 Frontend Client
|
| 39 |
│ ├── manifest.json
|
| 40 |
│ ├── popup.html
|
| 41 |
│ └── popup.js
|
| 42 |
+
└── README.md # Documentation
|
api/config.py
CHANGED
|
@@ -2,7 +2,7 @@ import os
|
|
| 2 |
import sys
|
| 3 |
import logging
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
-
from vectorstore import VectorStoreManager
|
| 6 |
import google.generativeai as genai
|
| 7 |
|
| 8 |
logging.basicConfig(
|
|
@@ -38,6 +38,7 @@ try:
|
|
| 38 |
genai.configure(api_key=GOOGLE_API_KEY) # type: ignore
|
| 39 |
flash = genai.GenerativeModel("gemini-2.0-flash") # type: ignore
|
| 40 |
|
|
|
|
| 41 |
except Exception as e:
|
| 42 |
logger.critical(f"Startup Failure: {e}")
|
| 43 |
sys.exit(1)
|
|
|
|
| 2 |
import sys
|
| 3 |
import logging
|
| 4 |
from dotenv import load_dotenv
|
| 5 |
+
from api.vectorstore import VectorStoreManager
|
| 6 |
import google.generativeai as genai
|
| 7 |
|
| 8 |
logging.basicConfig(
|
|
|
|
| 38 |
genai.configure(api_key=GOOGLE_API_KEY) # type: ignore
|
| 39 |
flash = genai.GenerativeModel("gemini-2.0-flash") # type: ignore
|
| 40 |
|
| 41 |
+
logger.info("✅ System initialized: Pinecone & Gemini ready.")
|
| 42 |
except Exception as e:
|
| 43 |
logger.critical(f"Startup Failure: {e}")
|
| 44 |
sys.exit(1)
|
api/crawler.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import time
|
| 2 |
import trafilatura
|
| 3 |
from trafilatura.sitemaps import sitemap_search
|
| 4 |
-
from utils import logger, is_valid_url
|
| 5 |
|
| 6 |
|
| 7 |
def smart_chunk(text: str, chunk_size=500) -> list[str]:
|
|
|
|
| 1 |
import time
|
| 2 |
import trafilatura
|
| 3 |
from trafilatura.sitemaps import sitemap_search
|
| 4 |
+
from api.utils import logger, is_valid_url
|
| 5 |
|
| 6 |
|
| 7 |
def smart_chunk(text: str, chunk_size=500) -> list[str]:
|
api/server.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
| 1 |
from fastapi import FastAPI, BackgroundTasks, HTTPException
|
| 2 |
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
|
| 4 |
-
from utils import get_namespace_id, logger
|
| 5 |
-
from crawler import crawl_website
|
| 6 |
-
from schemas import ChatRequest, IngestRequest
|
| 7 |
-
from config import vs, flash
|
| 8 |
|
| 9 |
app = FastAPI(title="RAG Chatbot API", version="1.0")
|
| 10 |
|
|
|
|
| 1 |
from fastapi import FastAPI, BackgroundTasks, HTTPException
|
| 2 |
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
|
| 4 |
+
from api.utils import get_namespace_id, logger
|
| 5 |
+
from api.crawler import crawl_website
|
| 6 |
+
from api.schemas import ChatRequest, IngestRequest
|
| 7 |
+
from api.config import vs, flash
|
| 8 |
|
| 9 |
app = FastAPI(title="RAG Chatbot API", version="1.0")
|
| 10 |
|
api/vectorstore.py
CHANGED
|
@@ -2,7 +2,7 @@ from typing import Any
|
|
| 2 |
import uuid
|
| 3 |
from pinecone import Pinecone, ServerlessSpec
|
| 4 |
from sentence_transformers import SentenceTransformer
|
| 5 |
-
from utils import logger
|
| 6 |
|
| 7 |
|
| 8 |
class VectorStoreManager:
|
|
|
|
| 2 |
import uuid
|
| 3 |
from pinecone import Pinecone, ServerlessSpec
|
| 4 |
from sentence_transformers import SentenceTransformer
|
| 5 |
+
from api.utils import logger
|
| 6 |
|
| 7 |
|
| 8 |
class VectorStoreManager:
|
chrome-extension/.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
chrome-extension/icons/icon128.png
ADDED
|
|
chrome-extension/icons/icon16.png
ADDED
|
|
chrome-extension/icons/icon32.png
ADDED
|
|
chrome-extension/icons/icon48.png
ADDED
|
|
chrome-extension/manifest.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Ragbot",
|
| 3 |
+
"description": "Chat with any website using RAG.",
|
| 4 |
+
"version": "1.0",
|
| 5 |
+
"manifest_version": 3,
|
| 6 |
+
"action": {
|
| 7 |
+
"default_popup": "popup.html",
|
| 8 |
+
"default_icon": {
|
| 9 |
+
"16": "icons/icon16.png",
|
| 10 |
+
"48": "icons/icon48.png",
|
| 11 |
+
"128": "icons/icon128.png"
|
| 12 |
+
}
|
| 13 |
+
},
|
| 14 |
+
"icons": {
|
| 15 |
+
"16": "icons/icon16.png",
|
| 16 |
+
"32": "icons/icon32.png",
|
| 17 |
+
"48": "icons/icon48.png",
|
| 18 |
+
"128": "icons/icon128.png"
|
| 19 |
+
},
|
| 20 |
+
"permissions": ["activeTab", "scripting"],
|
| 21 |
+
"host_permissions": ["http://localhost:8000/*"]
|
| 22 |
+
}
|
chrome-extension/popup.css
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
* {
|
| 2 |
+
box-sizing: border-box;
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
body {
|
| 6 |
+
width: 360px;
|
| 7 |
+
height: 500px;
|
| 8 |
+
font-family:
|
| 9 |
+
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial,
|
| 10 |
+
sans-serif;
|
| 11 |
+
margin: 0;
|
| 12 |
+
padding: 0;
|
| 13 |
+
background-color: #ffffff;
|
| 14 |
+
color: #1a1a1a;
|
| 15 |
+
display: flex;
|
| 16 |
+
flex-direction: column;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
header {
|
| 20 |
+
padding: 15px;
|
| 21 |
+
border-bottom: 1px solid #eaeaea;
|
| 22 |
+
display: flex;
|
| 23 |
+
align-items: center;
|
| 24 |
+
justify-content: space-between;
|
| 25 |
+
background: #fafafa;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
h1 {
|
| 29 |
+
margin: 0;
|
| 30 |
+
font-size: 16px;
|
| 31 |
+
font-weight: 600;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.badge {
|
| 35 |
+
font-size: 10px;
|
| 36 |
+
padding: 2px 6px;
|
| 37 |
+
border-radius: 4px;
|
| 38 |
+
background: #eee;
|
| 39 |
+
color: #666;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
main {
|
| 43 |
+
flex: 1;
|
| 44 |
+
overflow: hidden;
|
| 45 |
+
display: flex;
|
| 46 |
+
flex-direction: column;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.view {
|
| 50 |
+
padding: 20px;
|
| 51 |
+
flex: 1;
|
| 52 |
+
display: flex;
|
| 53 |
+
flex-direction: column;
|
| 54 |
+
align-items: center;
|
| 55 |
+
justify-content: center;
|
| 56 |
+
text-align: center;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.hidden {
|
| 60 |
+
display: none !important;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
.icon-large {
|
| 64 |
+
font-size: 48px;
|
| 65 |
+
margin-bottom: 15px;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
h2 {
|
| 69 |
+
margin: 0 0 10px 0;
|
| 70 |
+
font-size: 18px;
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
p {
|
| 74 |
+
margin: 0 0 20px 0;
|
| 75 |
+
font-size: 13px;
|
| 76 |
+
color: #666;
|
| 77 |
+
line-height: 1.4;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
button {
|
| 81 |
+
background: #000;
|
| 82 |
+
color: #fff;
|
| 83 |
+
border: none;
|
| 84 |
+
padding: 10px 20px;
|
| 85 |
+
border-radius: 6px;
|
| 86 |
+
font-size: 14px;
|
| 87 |
+
font-weight: 500;
|
| 88 |
+
cursor: pointer;
|
| 89 |
+
transition: opacity 0.2s;
|
| 90 |
+
width: 100%;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
button:hover {
|
| 94 |
+
opacity: 0.8;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
button:disabled {
|
| 98 |
+
background: #ccc;
|
| 99 |
+
cursor: not-allowed;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
#chat-view {
|
| 103 |
+
justify-content: flex-start;
|
| 104 |
+
padding: 0;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
#messages {
|
| 108 |
+
flex: 1;
|
| 109 |
+
width: 100%;
|
| 110 |
+
overflow-y: auto;
|
| 111 |
+
padding: 15px;
|
| 112 |
+
display: flex;
|
| 113 |
+
flex-direction: column;
|
| 114 |
+
gap: 10px;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
.msg {
|
| 118 |
+
max-width: 85%;
|
| 119 |
+
padding: 8px 12px;
|
| 120 |
+
border-radius: 12px;
|
| 121 |
+
font-size: 13px;
|
| 122 |
+
line-height: 1.4;
|
| 123 |
+
word-wrap: break-word;
|
| 124 |
+
}
|
| 125 |
+
.msg.user {
|
| 126 |
+
align-self: flex-end;
|
| 127 |
+
background: #000;
|
| 128 |
+
color: #fff;
|
| 129 |
+
border-bottom-right-radius: 2px;
|
| 130 |
+
}
|
| 131 |
+
.msg.bot {
|
| 132 |
+
align-self: flex-start;
|
| 133 |
+
background: #f0f0f0;
|
| 134 |
+
color: #000;
|
| 135 |
+
border-bottom-left-radius: 2px;
|
| 136 |
+
}
|
| 137 |
+
.msg.error {
|
| 138 |
+
align-self: center;
|
| 139 |
+
background: #ffebee;
|
| 140 |
+
color: #c62828;
|
| 141 |
+
font-size: 12px;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
.input-area {
|
| 145 |
+
width: 100%;
|
| 146 |
+
padding: 15px;
|
| 147 |
+
border-top: 1px solid #eaeaea;
|
| 148 |
+
display: flex;
|
| 149 |
+
gap: 10px;
|
| 150 |
+
background: #fff;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
input {
|
| 154 |
+
flex: 1;
|
| 155 |
+
padding: 10px;
|
| 156 |
+
border: 1px solid #ddd;
|
| 157 |
+
border-radius: 6px;
|
| 158 |
+
outline: none;
|
| 159 |
+
font-size: 14px;
|
| 160 |
+
}
|
| 161 |
+
input:focus {
|
| 162 |
+
border-color: #000;
|
| 163 |
+
}
|
| 164 |
+
.send-btn {
|
| 165 |
+
width: auto;
|
| 166 |
+
padding: 0 15px;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
.loader {
|
| 170 |
+
border: 3px solid #f3f3f3;
|
| 171 |
+
border-top: 3px solid #000;
|
| 172 |
+
border-radius: 50%;
|
| 173 |
+
width: 20px;
|
| 174 |
+
height: 20px;
|
| 175 |
+
animation: spin 1s linear infinite;
|
| 176 |
+
display: inline-block;
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
@keyframes spin {
|
| 180 |
+
0% {
|
| 181 |
+
transform: rotate(0deg);
|
| 182 |
+
}
|
| 183 |
+
100% {
|
| 184 |
+
transform: rotate(360deg);
|
| 185 |
+
}
|
| 186 |
+
}
|
chrome-extension/popup.html
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>RAG Bot</title>
|
| 7 |
+
<link rel="stylesheet" href="popup.css" />
|
| 8 |
+
<script src="popup.js" defer></script>
|
| 9 |
+
</head>
|
| 10 |
+
<body>
|
| 11 |
+
<header>
|
| 12 |
+
<h1>RAG Bot</h1>
|
| 13 |
+
<span id="domain-badge" class="badge">Checking...</span>
|
| 14 |
+
</header>
|
| 15 |
+
|
| 16 |
+
<main>
|
| 17 |
+
<div id="loading-view" class="view">
|
| 18 |
+
<div class="loader"></div>
|
| 19 |
+
<p>Connecting to Brain...</p>
|
| 20 |
+
</div>
|
| 21 |
+
|
| 22 |
+
<!-- Train View -->
|
| 23 |
+
<div id="train-view" class="view hidden">
|
| 24 |
+
<div class="icon-large">🧠</div>
|
| 25 |
+
<h2>New Knowledge</h2>
|
| 26 |
+
<p>
|
| 27 |
+
I haven't read this website yet.<br />Train me to answer questions
|
| 28 |
+
about it.
|
| 29 |
+
</p>
|
| 30 |
+
<button id="train-btn">Train on this Page</button>
|
| 31 |
+
<p id="train-status" class="hidden">
|
| 32 |
+
<span class="loader"></span>
|
| 33 |
+
Processing...
|
| 34 |
+
</p>
|
| 35 |
+
</div>
|
| 36 |
+
|
| 37 |
+
<!-- Chat View -->
|
| 38 |
+
<div id="chat-view" class="view hidden">
|
| 39 |
+
<div id="messages">
|
| 40 |
+
<div class="msg bot">Hello! Ask me anything about this page.</div>
|
| 41 |
+
</div>
|
| 42 |
+
<div class="input-area">
|
| 43 |
+
<input
|
| 44 |
+
type="text"
|
| 45 |
+
id="user-input"
|
| 46 |
+
placeholder="Ask a question..."
|
| 47 |
+
autocomplete="off"
|
| 48 |
+
/>
|
| 49 |
+
<button id="send-btn" class="send-btn">↑</button>
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
+
</main>
|
| 53 |
+
|
| 54 |
+
<!-- The Logic -->
|
| 55 |
+
</body>
|
| 56 |
+
</html>
|
chrome-extension/popup.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Config
|
| 2 |
+
const API_BASE = "http://localhost:8000";
|
| 3 |
+
|
| 4 |
+
// DOM Elements
|
| 5 |
+
const views = {
|
| 6 |
+
loading: document.getElementById("loading-view"),
|
| 7 |
+
train: document.getElementById("train-view"),
|
| 8 |
+
chat: document.getElementById("chat-view"),
|
| 9 |
+
};
|
| 10 |
+
const els = {
|
| 11 |
+
domainBadge: document.getElementById("domain-badge"),
|
| 12 |
+
trainBtn: document.getElementById("train-btn"),
|
| 13 |
+
trainStatus: document.getElementById("train-status"),
|
| 14 |
+
messages: document.getElementById("messages"),
|
| 15 |
+
input: document.getElementById("user-input"),
|
| 16 |
+
sendBtn: document.getElementById("send-btn"),
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
// State
|
| 20 |
+
let currentUrl = "";
|
| 21 |
+
|
| 22 |
+
// --- Initialization ---
|
| 23 |
+
|
| 24 |
+
document.addEventListener("DOMContentLoaded", async () => {
|
| 25 |
+
try {
|
| 26 |
+
const tab = await getCurrentTab();
|
| 27 |
+
currentUrl = tab.url;
|
| 28 |
+
|
| 29 |
+
// Update Badge with Domain
|
| 30 |
+
const urlObj = new URL(currentUrl);
|
| 31 |
+
els.domainBadge.textContent = urlObj.hostname;
|
| 32 |
+
|
| 33 |
+
// Check Backend
|
| 34 |
+
await checkIndexStatus(currentUrl);
|
| 35 |
+
} catch (err) {
|
| 36 |
+
showError("Error: " + err.message);
|
| 37 |
+
}
|
| 38 |
+
});
|
| 39 |
+
|
| 40 |
+
// --- Core Logic ---
|
| 41 |
+
|
| 42 |
+
async function checkIndexStatus(url) {
|
| 43 |
+
showView("loading");
|
| 44 |
+
|
| 45 |
+
try {
|
| 46 |
+
const res = await fetch(`${API_BASE}/check`, {
|
| 47 |
+
method: "POST",
|
| 48 |
+
headers: { "Content-Type": "application/json" },
|
| 49 |
+
body: JSON.stringify({ url: url }),
|
| 50 |
+
});
|
| 51 |
+
|
| 52 |
+
const data = await res.json();
|
| 53 |
+
|
| 54 |
+
if (data.exists) {
|
| 55 |
+
showView("chat");
|
| 56 |
+
} else {
|
| 57 |
+
showView("train");
|
| 58 |
+
}
|
| 59 |
+
} catch (err) {
|
| 60 |
+
showError("Backend Offline. Is server.py running?");
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
async function startTraining() {
|
| 65 |
+
els.trainBtn.disabled = true;
|
| 66 |
+
els.trainStatus.classList.remove("hidden");
|
| 67 |
+
|
| 68 |
+
try {
|
| 69 |
+
const res = await fetch(`${API_BASE}/ingest`, {
|
| 70 |
+
method: "POST",
|
| 71 |
+
headers: { "Content-Type": "application/json" },
|
| 72 |
+
body: JSON.stringify({ url: currentUrl }),
|
| 73 |
+
});
|
| 74 |
+
|
| 75 |
+
const data = await res.json();
|
| 76 |
+
|
| 77 |
+
// Since ingestion is background, we wait a bit then switch to chat
|
| 78 |
+
// In a real app, we would poll status. Here we fake a 2s delay.
|
| 79 |
+
setTimeout(() => {
|
| 80 |
+
showView("chat");
|
| 81 |
+
addMessage(
|
| 82 |
+
"bot",
|
| 83 |
+
"I've started reading this site! You can ask me questions now. (Note: It might take a minute to finish reading everything).",
|
| 84 |
+
);
|
| 85 |
+
}, 2000);
|
| 86 |
+
} catch (err) {
|
| 87 |
+
els.trainBtn.disabled = false;
|
| 88 |
+
els.trainStatus.classList.add("hidden");
|
| 89 |
+
alert("Training failed: " + err.message);
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
async function sendMessage() {
|
| 94 |
+
const text = els.input.value.trim();
|
| 95 |
+
if (!text) return;
|
| 96 |
+
|
| 97 |
+
// UI Updates
|
| 98 |
+
addMessage("user", text);
|
| 99 |
+
els.input.value = "";
|
| 100 |
+
els.input.disabled = true;
|
| 101 |
+
els.sendBtn.disabled = true;
|
| 102 |
+
|
| 103 |
+
try {
|
| 104 |
+
const res = await fetch(`${API_BASE}/chat`, {
|
| 105 |
+
method: "POST",
|
| 106 |
+
headers: { "Content-Type": "application/json" },
|
| 107 |
+
body: JSON.stringify({
|
| 108 |
+
message: text,
|
| 109 |
+
url: currentUrl,
|
| 110 |
+
}),
|
| 111 |
+
});
|
| 112 |
+
|
| 113 |
+
if (!res.ok) throw new Error("API Error");
|
| 114 |
+
|
| 115 |
+
const data = await res.json();
|
| 116 |
+
addMessage("bot", data.answer);
|
| 117 |
+
} catch (err) {
|
| 118 |
+
addMessage("error", "Failed to get response. Try again.");
|
| 119 |
+
} finally {
|
| 120 |
+
els.input.disabled = false;
|
| 121 |
+
els.sendBtn.disabled = false;
|
| 122 |
+
els.input.focus();
|
| 123 |
+
}
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
// --- Utilities ---
|
| 127 |
+
|
| 128 |
+
function getCurrentTab() {
|
| 129 |
+
return new Promise((resolve, reject) => {
|
| 130 |
+
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
| 131 |
+
if (tabs[0]) resolve(tabs[0]);
|
| 132 |
+
else reject(new Error("No active tab"));
|
| 133 |
+
});
|
| 134 |
+
});
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
function showView(name) {
|
| 138 |
+
Object.values(views).forEach((el) => el.classList.add("hidden"));
|
| 139 |
+
views[name].classList.remove("hidden");
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
function addMessage(type, text) {
|
| 143 |
+
const div = document.createElement("div");
|
| 144 |
+
div.className = `msg ${type}`;
|
| 145 |
+
div.innerText = text; // Safe text insertion
|
| 146 |
+
els.messages.appendChild(div);
|
| 147 |
+
els.messages.scrollTop = els.messages.scrollHeight;
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
function showError(msg) {
|
| 151 |
+
document.body.innerHTML = `<div style="padding:20px; color:red; text-align:center;">${msg}</div>`;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
// --- Event Listeners ---
|
| 155 |
+
|
| 156 |
+
els.trainBtn.addEventListener("click", startTraining);
|
| 157 |
+
|
| 158 |
+
els.sendBtn.addEventListener("click", sendMessage);
|
| 159 |
+
|
| 160 |
+
els.input.addEventListener("keypress", (e) => {
|
| 161 |
+
if (e.key === "Enter") sendMessage();
|
| 162 |
+
});
|
requirements.txt
CHANGED
|
@@ -1,11 +1,7 @@
|
|
| 1 |
-
altair==5.5.0
|
| 2 |
annotated-doc==0.0.4
|
| 3 |
annotated-types==0.7.0
|
| 4 |
anyio==4.11.0
|
| 5 |
-
attrs==25.4.0
|
| 6 |
babel==2.17.0
|
| 7 |
-
beautifulsoup4==4.14.2
|
| 8 |
-
blinker==1.9.0
|
| 9 |
cachetools==6.2.2
|
| 10 |
certifi==2025.11.12
|
| 11 |
charset-normalizer==3.4.4
|
|
@@ -13,10 +9,6 @@ click==8.3.1
|
|
| 13 |
courlan==1.3.2
|
| 14 |
dateparser==1.2.2
|
| 15 |
fastapi==0.122.0
|
| 16 |
-
filelock==3.20.0
|
| 17 |
-
fsspec==2025.10.0
|
| 18 |
-
gitdb==4.0.12
|
| 19 |
-
GitPython==3.1.45
|
| 20 |
google-ai-generativelanguage==0.6.15
|
| 21 |
google-api-core==2.28.1
|
| 22 |
google-api-python-client==2.187.0
|
|
@@ -27,74 +19,37 @@ googleapis-common-protos==1.72.0
|
|
| 27 |
grpcio==1.76.0
|
| 28 |
grpcio-status==1.71.2
|
| 29 |
h11==0.16.0
|
| 30 |
-
hf-xet==1.2.0
|
| 31 |
htmldate==1.9.4
|
| 32 |
httplib2==0.31.0
|
| 33 |
-
huggingface-hub==0.36.0
|
| 34 |
idna==3.11
|
| 35 |
-
Jinja2==3.1.6
|
| 36 |
-
joblib==1.5.2
|
| 37 |
-
jsonschema==4.25.1
|
| 38 |
-
jsonschema-specifications==2025.9.1
|
| 39 |
jusText==3.0.2
|
| 40 |
lxml==6.0.2
|
| 41 |
lxml_html_clean==0.4.3
|
| 42 |
-
MarkupSafe==3.0.3
|
| 43 |
-
mpmath==1.3.0
|
| 44 |
-
narwhals==2.12.0
|
| 45 |
-
networkx==3.6
|
| 46 |
-
numpy==2.3.5
|
| 47 |
orjson==3.11.4
|
| 48 |
packaging==24.2
|
| 49 |
-
pandas==2.3.3
|
| 50 |
-
pillow==12.0.0
|
| 51 |
pinecone==8.0.0
|
| 52 |
-
pinecone-client==6.0.0
|
| 53 |
pinecone-plugin-assistant==3.0.1
|
| 54 |
pinecone-plugin-interface==0.0.7
|
| 55 |
proto-plus==1.26.1
|
| 56 |
protobuf==5.29.5
|
| 57 |
-
pyarrow==21.0.0
|
| 58 |
pyasn1==0.6.1
|
| 59 |
pyasn1_modules==0.4.2
|
| 60 |
pydantic==2.12.4
|
| 61 |
pydantic_core==2.41.5
|
| 62 |
-
pydeck==0.9.1
|
| 63 |
pyparsing==3.2.5
|
| 64 |
python-dateutil==2.9.0.post0
|
| 65 |
-
python-dotenv==1.2.1
|
| 66 |
pytz==2025.2
|
| 67 |
-
PyYAML==6.0.3
|
| 68 |
-
referencing==0.37.0
|
| 69 |
regex==2025.11.3
|
| 70 |
requests==2.32.5
|
| 71 |
-
rpds-py==0.29.0
|
| 72 |
rsa==4.9.1
|
| 73 |
-
safetensors==0.7.0
|
| 74 |
-
scikit-learn==1.7.2
|
| 75 |
-
scipy==1.16.3
|
| 76 |
-
sentence-transformers==5.1.2
|
| 77 |
-
setuptools==80.9.0
|
| 78 |
six==1.17.0
|
| 79 |
-
smmap==5.0.2
|
| 80 |
sniffio==1.3.1
|
| 81 |
-
soupsieve==2.8
|
| 82 |
starlette==0.50.0
|
| 83 |
-
streamlit==1.51.0
|
| 84 |
-
sympy==1.14.0
|
| 85 |
-
tenacity==9.1.2
|
| 86 |
-
threadpoolctl==3.6.0
|
| 87 |
tld==0.13.1
|
| 88 |
-
tokenizers==0.22.1
|
| 89 |
-
toml==0.10.2
|
| 90 |
-
torch==2.9.1
|
| 91 |
-
tornado==6.5.2
|
| 92 |
tqdm==4.67.1
|
| 93 |
trafilatura==2.0.0
|
| 94 |
-
transformers==4.57.2
|
| 95 |
typing-inspection==0.4.2
|
| 96 |
typing_extensions==4.15.0
|
| 97 |
-
tzdata==2025.2
|
| 98 |
tzlocal==5.3.1
|
| 99 |
uritemplate==4.2.0
|
| 100 |
urllib3==2.5.0
|
|
|
|
|
|
|
| 1 |
annotated-doc==0.0.4
|
| 2 |
annotated-types==0.7.0
|
| 3 |
anyio==4.11.0
|
|
|
|
| 4 |
babel==2.17.0
|
|
|
|
|
|
|
| 5 |
cachetools==6.2.2
|
| 6 |
certifi==2025.11.12
|
| 7 |
charset-normalizer==3.4.4
|
|
|
|
| 9 |
courlan==1.3.2
|
| 10 |
dateparser==1.2.2
|
| 11 |
fastapi==0.122.0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
google-ai-generativelanguage==0.6.15
|
| 13 |
google-api-core==2.28.1
|
| 14 |
google-api-python-client==2.187.0
|
|
|
|
| 19 |
grpcio==1.76.0
|
| 20 |
grpcio-status==1.71.2
|
| 21 |
h11==0.16.0
|
|
|
|
| 22 |
htmldate==1.9.4
|
| 23 |
httplib2==0.31.0
|
|
|
|
| 24 |
idna==3.11
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
jusText==3.0.2
|
| 26 |
lxml==6.0.2
|
| 27 |
lxml_html_clean==0.4.3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
orjson==3.11.4
|
| 29 |
packaging==24.2
|
|
|
|
|
|
|
| 30 |
pinecone==8.0.0
|
|
|
|
| 31 |
pinecone-plugin-assistant==3.0.1
|
| 32 |
pinecone-plugin-interface==0.0.7
|
| 33 |
proto-plus==1.26.1
|
| 34 |
protobuf==5.29.5
|
|
|
|
| 35 |
pyasn1==0.6.1
|
| 36 |
pyasn1_modules==0.4.2
|
| 37 |
pydantic==2.12.4
|
| 38 |
pydantic_core==2.41.5
|
|
|
|
| 39 |
pyparsing==3.2.5
|
| 40 |
python-dateutil==2.9.0.post0
|
|
|
|
| 41 |
pytz==2025.2
|
|
|
|
|
|
|
| 42 |
regex==2025.11.3
|
| 43 |
requests==2.32.5
|
|
|
|
| 44 |
rsa==4.9.1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
six==1.17.0
|
|
|
|
| 46 |
sniffio==1.3.1
|
|
|
|
| 47 |
starlette==0.50.0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
tld==0.13.1
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
tqdm==4.67.1
|
| 50 |
trafilatura==2.0.0
|
|
|
|
| 51 |
typing-inspection==0.4.2
|
| 52 |
typing_extensions==4.15.0
|
|
|
|
| 53 |
tzlocal==5.3.1
|
| 54 |
uritemplate==4.2.0
|
| 55 |
urllib3==2.5.0
|