diff --git a/.gitignore b/.gitignore index 9e1d25d..32f227b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ wheels/ # Virtual environments .venv - # Custom *_data/ *.epub diff --git a/server.py b/server.py index 9c870dc..019c493 100644 --- a/server.py +++ b/server.py @@ -2,17 +2,51 @@ import pickle from functools import lru_cache from typing import Optional - +from groq import Groq +from pydantic import BaseModel +import dotenv +dotenv.load_dotenv() from fastapi import FastAPI, Request, HTTPException from fastapi.responses import HTMLResponse, FileResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates +from fastapi.responses import StreamingResponse + from reader3 import Book, BookMetadata, ChapterContent, TOCEntry app = FastAPI() templates = Jinja2Templates(directory="templates") +class ChatRequest(BaseModel): + message: str + +client = Groq(api_key=os.getenv("GROQ_KEY")) + +@app.post("/chat-stream") +async def chat_stream(req: ChatRequest): + def generate(): + stream = client.chat.completions.create( + model="llama-3.1-8b-instant", + stream=True, + messages=[{"role": "user", "content": req.message}], + ) + + for chunk in stream: + if chunk.choices and chunk.choices[0].delta: + delta = chunk.choices[0].delta.content + + if delta: + # Groq may return list or string + if isinstance(delta, list): + for item in delta: + if "text" in item: + yield item["text"] + else: + yield delta + + return StreamingResponse(generate(), media_type="text/plain") + # Where are the book folders located? BOOKS_DIR = "." diff --git a/templates/reader.html b/templates/reader.html index c012edc..69e7968 100644 --- a/templates/reader.html +++ b/templates/reader.html @@ -35,7 +35,84 @@ .nav-btn { text-decoration: none; color: #3498db; font-weight: bold; padding: 10px 20px; border: 1px solid #3498db; border-radius: 4px; transition: all 0.2s; } .nav-btn:hover { background: #3498db; color: white; } .nav-btn.disabled { opacity: 0.5; pointer-events: none; border-color: #ccc; color: #ccc; } + #chatbox { + position: fixed; + bottom: 20px; + right: 20px; + width: 320px; + background: #ffffffee; + backdrop-filter: saturate(180%) blur(20px); + border: 1px solid #e5e5e5; + border-radius: 14px; + box-shadow: 0 10px 25px rgba(0,0,0,0.08); + overflow: hidden; + } + + #chat-header { + padding: 12px 16px; + font-size: 15px; + font-weight: 600; + color: #111; + border-bottom: 1px solid #eaeaea; + } + + #messages { + height: 230px; + overflow-y: auto; + padding: 12px; + font-size: 14px; + color: #222; + } + + .bubble-user { + background: #007aff; + color: white; + padding: 8px 12px; + border-radius: 14px; + margin: 8px 0; + max-width: 80%; + margin-left: auto; + display: inline-block; + } + + .bubble-bot { + background: #f2f2f7; + color: #333; + padding: 8px 12px; + border-radius: 14px; + margin: 8px 0; + max-width: 80%; + display: inline-block; + } + + #chat-input-area { + display: flex; + gap: 8px; + padding: 10px; + border-top: 1px solid #eaeaea; + background: white; + } + + #chatInput { + flex: 1; + padding: 10px; + border-radius: 12px; + border: 1px solid #ccc; + font-size: 14px; + } + + #sendBtn { + padding: 10px 14px; + background: #007aff; + color: white; + border: none; + border-radius: 12px; + cursor: pointer; + font-size: 14px; + transition: 0.2s; + } + #sendBtn:hover { background: #0063d6; } @@ -120,8 +197,62 @@ +
+
Chat with Groq
+ +
+ +
+ + +
+
- + \ No newline at end of file