| #!/usr/bin/env bash |
| set -euo pipefail |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| ROOT_DIR="$(pwd)" |
| VENV_DIR="$ROOT_DIR/.venv" |
|
|
| echo "==> Preparing virtualenv..." |
| if ! command -v python3 >/dev/null 2>&1; then |
| echo "Python3 is required. Install Python 3.10+ and retry." |
| exit 1 |
| fi |
|
|
| python3 -m venv "$VENV_DIR" |
| |
| source "$VENV_DIR/bin/activate" |
|
|
| pip install --upgrade pip |
|
|
| cat > requirements.txt <<'PYREQ' |
| fastapi==0.95.2 |
| uvicorn[standard]==0.21.1 |
| httpx==0.24.1 |
| python-dotenv==1.0.0 |
| python-multipart==0.0.6 |
| sqlalchemy==2.0.19 |
| pydantic==1.10.12 |
| PYREQ |
|
|
| echo "==> Installing Python dependencies (lightweight)..." |
| pip install -r requirements.txt |
|
|
| |
| cat > app.py <<'PYAPP' |
| import os |
| import json |
| import sqlite3 |
| import urllib.parse |
| from typing import Optional, List, Dict |
| from fastapi import FastAPI, Request, HTTPException, UploadFile, File, Form |
| from pydantic import BaseModel |
| import httpx |
| from starlette.responses import PlainTextResponse, HTMLResponse, Response, FileResponse |
|
|
| |
| HF_API_TOKEN = os.getenv("HF_API_TOKEN", "") |
| HF_MODEL_ID = os.getenv("HF_MODEL_ID", "gpt2") |
| COGNITO_API_KEY = os.getenv("COGNITO_API_KEY", "cognito_default_key_please_change") |
| ALLOW_FETCH = os.getenv("ALLOW_FETCH", "false").lower() == "true" |
| FETCH_BEARER_TOKEN = os.getenv("FETCH_BEARER_TOKEN", "") |
| MODERATION_BLOCKLIST = [s.strip().lower() for s in os.getenv("MODERATION_BLOCKLIST", "hack,steal,illegal").split(",") if s.strip()] |
|
|
| DB_PATH = os.path.join(os.getcwd(), "cognito_sessions.db") |
| UPLOADS_DIR = os.path.join(os.getcwd(), "uploads") |
| os.makedirs(UPLOADS_DIR, exist_ok=True) |
|
|
| app = FastAPI(title="Cognito (Minimal)", docs_url="/docs") |
|
|
| |
| def init_db(): |
| conn = sqlite3.connect(DB_PATH) |
| cur = conn.cursor() |
| cur.execute(""" |
| CREATE TABLE IF NOT EXISTS sessions ( |
| session_id TEXT PRIMARY KEY, |
| messages TEXT |
| ) |
| """) |
| conn.commit() |
| conn.close() |
|
|
| def get_session_messages(session_id: str): |
| conn = sqlite3.connect(DB_PATH) |
| cur = conn.cursor() |
| cur.execute("SELECT messages FROM sessions WHERE session_id = ?", (session_id,)) |
| row = cur.fetchone() |
| conn.close() |
| if row: |
| return json.loads(row[0]) |
| return [] |
|
|
| def save_session_messages(session_id: str, messages): |
| conn = sqlite3.connect(DB_PATH) |
| cur = conn.cursor() |
| cur.execute("INSERT OR REPLACE INTO sessions (session_id, messages) VALUES (?, ?)", (session_id, json.dumps(messages))) |
| conn.commit() |
| conn.close() |
|
|
| init_db() |
|
|
| |
| class ChatRequest(BaseModel): |
| session_id: Optional[str] = None |
| messages: List[Dict] |
| fetch_url: Optional[str] = None |
|
|
| |
| def moderate_messages(messages): |
| txt = " ".join(m.get("content","") for m in messages).lower() |
| for bad in MODERATION_BLOCKLIST: |
| if bad and bad in txt: |
| return False, f"Message blocked by moderation: found banned word '{bad}'" |
| return True, "" |
|
|
| |
| def build_prompt(messages, uploaded_texts=None): |
| prompt = "" |
| if uploaded_texts: |
| prompt += "Context documents:\n" |
| for i, t in enumerate(uploaded_texts): |
| prompt += f"[DOC {i+1}]\n{t}\n\n" |
| prompt += "---\n" |
| for m in messages: |
| role = m.get("role","user") |
| content = m.get("content","") |
| if role == "system": |
| prompt += f"[SYSTEM] {content}\n" |
| elif role == "user": |
| prompt += f"User: {content}\n" |
| else: |
| prompt += f"Assistant: {content}\n" |
| prompt += "\nAssistant:" |
| return prompt |
|
|
| |
| @app.post("/upload") |
| async def upload_file(session_id: Optional[str] = Form(None), file: UploadFile = File(...)): |
| |
| content = await file.read() |
| fname = file.filename |
| safe_name = fname.replace("..","_").replace("/","_") |
| path = os.path.join(UPLOADS_DIR, safe_name) |
| with open(path, "wb") as f: |
| f.write(content) |
| return {"ok": True, "path": path} |
|
|
| |
| @app.get("/cognito-widget.js") |
| def widget_js(): |
| |
| js = """ |
| (function () { |
| if (window.CognitoWidgetInitialized) return; |
| window.CognitoWidgetInitialized = true; |
| const CONFIG = { apiBase: window.COGNITO_API_BASE || "http://localhost:8000", apiKey: window.COGNITO_API_KEY || null, title: "Cognito" }; |
| const css = `.cognito-chat-btn { position: fixed; right: 20px; bottom: 20px; z-index: 100000; background: #0b5fff; color: #fff; border-radius: 28px; padding: 12px 16px; cursor: pointer; } .cognito-panel { position: fixed; right: 20px; bottom: 80px; width: 360px; max-width: 90vw; z-index: 100000; border-radius: 12px; overflow: hidden; background: #fff; display:none; flex-direction:column } .cognito-header{ padding:12px; background:#0b5fff; color:#fff } .cognito-body{ padding:12px; height:300px; overflow:auto; background:#f7f7fb } .cognito-input-row{ display:flex; padding:8px; border-top:1px solid #eee } .cognito-input{ flex:1; padding:8px 10px; border-radius:8px; border:1px solid #ddd } .cognito-send{ margin-left:8px; padding:8px 12px; border-radius:8px; border:none; background:#0b5fff; color:#fff } .cognito-msg{ margin-bottom:8px; padding:8px 10px; border-radius:8px; max-width:85% } .cognito-msg.user{ background:#0b5fff; color:#fff; margin-left:auto } .cognito-msg.bot{ background:#fff; color:#111; border:1px solid #eee }`; |
| const styleEl=document.createElement("style"); styleEl.innerHTML=css; document.head.appendChild(styleEl); |
| const btn=document.createElement("button"); btn.className='cognito-chat-btn'; btn.innerText=CONFIG.title; document.body.appendChild(btn); |
| const panel=document.createElement("div"); panel.className='cognito-panel'; panel.innerHTML='<div class=\"cognito-header\">Cognito</div><div class=\"cognito-body\"><div class=\"cognito-msg bot\">Hello — I am Cognito.</div></div><div class=\"cognito-input-row\"><input class=\"cognito-input\" placeholder=\"Ask Cognito...\" /><button class=\"cognito-send\">Send</button></div>'; document.body.appendChild(panel); |
| btn.addEventListener('click', ()=>{ panel.style.display = panel.style.display === 'flex' ? 'none' : 'flex'; panel.style.flexDirection='column'; }); |
| const bodyEl=panel.querySelector('.cognito-body'); const inputEl=panel.querySelector('.cognito-input'); const sendBtn=panel.querySelector('.cognito-send'); |
| function appendMessage(text, who){ const m=document.createElement('div'); m.className='cognito-msg '+(who==='user'?'user':'bot'); m.innerText=text; bodyEl.appendChild(m); bodyEl.scrollTop = bodyEl.scrollHeight; } |
| async function sendMessage(text){ appendMessage(text,'user'); inputEl.value=''; const payload={ messages: [{role:'user', content:text}], session_id: null }; const headers={'Content-Type':'application/json'}; if(CONFIG.apiKey) headers['x-api-key']=CONFIG.apiKey; try{ const res = await fetch(CONFIG.apiBase + '/chat',{ method:'POST', headers, body: JSON.stringify(payload) }); if(!res.ok){ appendMessage('Error: '+res.status+' '+await res.text(),'bot'); return; } const data = await res.json(); appendMessage(data.reply || JSON.stringify(data), 'bot'); } catch(err){ appendMessage('Network error: '+err.message,'bot'); } } |
| sendBtn.addEventListener('click', ()=>{ const v=inputEl.value.trim(); if(!v) return; sendMessage(v); }); |
| inputEl.addEventListener('keydown', (e)=>{ if(e.key==='Enter'){ e.preventDefault(); sendBtn.click(); } }); |
| window.CognitoWidget = { send: sendMessage, open: () => { panel.style.display='flex'; }, close: () => { panel.style.display='none'; } }; |
| })(); |
| """ |
| return Response(content=js, media_type="application/javascript") |
|
|
| |
| @app.get("/") |
| def index(): |
| content = f"<html><body><h3>Cognito (minimal)</h3><p>Use the widget by embedding <code><script src=\"/cognito-widget.js\"></script></code> and set <code>window.COGNITO_API_BASE</code> & <code>window.COGNITO_API_KEY</code>.</p><p>HF_API_TOKEN set: {bool(HF_API_TOKEN)}</p></body></html>" |
| return HTMLResponse(content=content) |
|
|
| |
| async def fetch_url_with_bearer(url: str): |
| if not ALLOW_FETCH: |
| raise HTTPException(status_code=403, detail="Server is not configured to fetch external pages.") |
| parsed = urllib.parse.urlparse(url) |
| if parsed.scheme not in ("http","https"): |
| raise HTTPException(status_code=400, detail="Invalid URL scheme.") |
| headers = {"User-Agent": "CognitoFetcher/1.0"} |
| if FETCH_BEARER_TOKEN: |
| headers["Authorization"] = f"Bearer {FETCH_BEARER_TOKEN}" |
| async with httpx.AsyncClient(timeout=20.0) as client: |
| r = await client.get(url, headers=headers) |
| if r.status_code >= 400: |
| raise HTTPException(status_code=502, detail=f"Upstream fetch failed: {r.status_code}") |
| |
| return r.text[:20000] |
|
|
| |
| def call_hf_inference(prompt: str): |
| if not HF_API_TOKEN: |
| |
| return "Cognito (offline mode): I received your message. Provide HF_API_TOKEN to enable real model responses." |
| url = f"https://api-inference.huggingface.co/models/{HF_MODEL_ID}" |
| headers = {"Authorization": f"Bearer {HF_API_TOKEN}", "Content-Type": "application/json"} |
| payload = {"inputs": prompt, "options": {"wait_for_model": True}} |
| try: |
| with httpx.Client(timeout=60.0) as client: |
| r = client.post(url, headers=headers, json=payload) |
| r.raise_for_status() |
| out = r.json() |
| |
| if isinstance(out, list) and len(out) > 0 and isinstance(out[0], dict): |
| return out[0].get("generated_text") or str(out) |
| if isinstance(out, dict): |
| return out.get("generated_text") or str(out) |
| return str(out) |
| except Exception as e: |
| return f"Cognito: error calling HF inference: {str(e)}" |
|
|
| |
| @app.post("/chat") |
| async def chat(req: ChatRequest, request: Request): |
| |
| header_key = request.headers.get("x-api-key", "") |
| if header_key != COGNITO_API_KEY: |
| raise HTTPException(status_code=401, detail="Invalid API key.") |
|
|
| |
| ok, reason = moderate_messages(req.messages) |
| if not ok: |
| raise HTTPException(status_code=400, detail=reason) |
|
|
| uploaded_texts = [] |
| |
| if req.session_id: |
| |
| for fname in os.listdir(UPLOADS_DIR): |
| if fname.startswith(req.session_id): |
| path = os.path.join(UPLOADS_DIR, fname) |
| try: |
| with open(path, "r", encoding="utf-8", errors="replace") as f: |
| uploaded_texts.append(f.read()[:5000]) |
| except Exception: |
| pass |
|
|
| |
| fetched = "" |
| if req.fetch_url: |
| fetched = await fetch_url_with_bearer(req.fetch_url) |
|
|
| prompt = build_prompt(req.messages, uploaded_texts + ([fetched] if fetched else [])) |
| reply = call_hf_inference(prompt) |
|
|
| |
| sess_id = req.session_id or "anon" |
| prev = get_session_messages(sess_id) |
| prev.append({"role":"user","content": req.messages[-1].get("content","") if req.messages else ""}) |
| prev.append({"role":"assistant","content": reply}) |
| |
| if len(prev) > 50: |
| prev = prev[-50:] |
| save_session_messages(sess_id, prev) |
|
|
| return {"reply": reply} |
|
|
| |
| @app.get("/health") |
| def health(): |
| return {"status": "ok", "hf_token": bool(HF_API_TOKEN), "allow_fetch": ALLOW_FETCH} |
| PYAPP |
|
|
| |
| cat > cognito-widget.js <<'WIDGET' |
| /* This file is also available at /cognito-widget.js when server is running. |
| The hosted widget expects window.COGNITO_API_BASE and window.COGNITO_API_KEY on the host page. */ |
| (function () { |
| if (window.CognitoWidgetInitialized) return; |
| window.CognitoWidgetInitialized = true; |
| const CONFIG = { apiBase: window.COGNITO_API_BASE || "http://localhost:8000", apiKey: window.COGNITO_API_KEY || null, title: "Cognito" }; |
| const css = `.cognito-chat-btn { position: fixed; right: 20px; bottom: 20px; z-index: 100000; background: |
| const styleEl=document.createElement("style"); styleEl.innerHTML=css; document.head.appendChild(styleEl); |
| const btn=document.createElement("button"); btn.className='cognito-chat-btn'; btn.innerText=CONFIG.title; document.body.appendChild(btn); |
| const panel=document.createElement("div"); panel.className='cognito-panel'; panel.innerHTML='<div class="cognito-header">Cognito</div><div class="cognito-body"><div class="cognito-msg bot">Hello — I am Cognito.</div></div><div class="cognito-input-row"><input class="cognito-input" placeholder="Ask Cognito..." /><button class="cognito-send">Send</button></div>'; document.body.appendChild(panel); |
| btn.addEventListener('click', ()=>{ panel.style.display = panel.style.display === 'flex' ? 'none' : 'flex'; panel.style.flexDirection='column'; }); |
| const bodyEl=panel.querySelector('.cognito-body'); const inputEl=panel.querySelector('.cognito-input'); const sendBtn=panel.querySelector('.cognito-send'); |
| function appendMessage(text, who){ const m=document.createElement('div'); m.className='cognito-msg '+(who==='user'?'user':'bot'); m.innerText=text; bodyEl.appendChild(m); bodyEl.scrollTop = bodyEl.scrollHeight; } |
| async function sendMessage(text){ appendMessage(text,'user'); inputEl.value=''; const payload={ messages: [{role:'user', content:text}], session_id: null }; const headers={'Content-Type':'application/json'}; if(CONFIG.apiKey) headers['x-api-key']=CONFIG.apiKey; try{ const res = await fetch(CONFIG.apiBase + '/chat',{ method:'POST', headers, body: JSON.stringify(payload) }); if(!res.ok){ appendMessage('Error: '+res.status+' '+await res.text(),'bot'); return; } const data = await res.json(); appendMessage(data.reply || JSON.stringify(data), 'bot'); } catch(err){ appendMessage('Network error: '+err.message,'bot'); } } |
| sendBtn.addEventListener('click', ()=>{ const v=inputEl.value.trim(); if(!v) return; sendMessage(v); }); |
| inputEl.addEventListener('keydown', (e)=>{ if(e.key==='Enter'){ e.preventDefault(); sendBtn.click(); } }); |
| window.CognitoWidget = { send: sendMessage, open: () => { panel.style.display='flex'; }, close: () => { panel.style.display='none'; } }; |
| })(); |
| WIDGET |
|
|
| |
| cat > .env <<'ENV' |
| |
| |
| HF_API_TOKEN="" |
| HF_MODEL_ID="gpt2" |
| COGNITO_API_KEY="cognito_default_key_please_change" |
| ALLOW_FETCH="false" |
| FETCH_BEARER_TOKEN="" |
| ENV |
|
|
| echo "==> Starting server (virtualenv active)." |
| echo "API key (for widget) default: cognito_default_key_please_change" |
| echo "If you have a Hugging Face token, export HF_API_TOKEN before running this script for real model responses." |
| echo "Example to run with token: HF_API_TOKEN='hf_xxx' ./setup_and_run.sh" |
|
|
| |
| |
| uvicorn app:app --host 0.0.0.0 --port 8000 --reload |
|
|