prelington commited on
Commit
3ae707c
·
verified ·
1 Parent(s): 6fc36ee

Update setup_and_run.sh

Browse files
Files changed (1) hide show
  1. setup_and_run.sh +289 -23
setup_and_run.sh CHANGED
@@ -1,45 +1,311 @@
1
  #!/usr/bin/env bash
2
  set -euo pipefail
3
 
4
- echo "==> Setting up Python environment and dependencies..."
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- # check for python3
7
- if ! command -v python3 &> /dev/null; then
8
- echo "Python3 not found. Please install Python3.11+"
9
- exit 1
 
 
 
10
  fi
11
 
12
- # create virtual environment
13
- python3 -m venv .venv
14
- source .venv/bin/activate
15
 
16
- # upgrade pip
17
  pip install --upgrade pip
18
 
19
- # install requirements
20
- cat > requirements.txt <<EOF
21
  fastapi==0.95.2
22
  uvicorn[standard]==0.21.1
23
  httpx==0.24.1
24
  python-dotenv==1.0.0
25
- transformers==4.36.0
26
- torch==2.2.0
27
  python-multipart==0.0.6
28
  sqlalchemy==2.0.19
29
  pydantic==1.10.12
30
- EOF
31
 
 
32
  pip install -r requirements.txt
33
 
34
- echo "==> Creating default environment variables..."
35
- export HF_API_TOKEN=${HF_API_TOKEN:-"hf_demo_token_replace_me"}
36
- export HF_MODEL_ID=${HF_MODEL_ID:-"gpt2"}
37
- export RUN_MODE=${RUN_MODE:-"inference_api"}
38
- export COGNITO_API_KEY=${COGNITO_API_KEY:-"supersecretkey123"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
- echo "==> Downloading server files..."
41
- curl -fsSL https://raw.githubusercontent.com/yourusername/cognito/main/app.py -o app.py
42
- curl -fsSL https://raw.githubusercontent.com/yourusername/cognito/main/cognito-widget.js -o cognito-widget.js
 
43
 
44
- echo "==> Starting Cognito server..."
 
45
  uvicorn app:app --host 0.0.0.0 --port 8000 --reload
 
1
  #!/usr/bin/env bash
2
  set -euo pipefail
3
 
4
+ # --------------------
5
+ # Minimal zero-edit installer + runner for Cognito
6
+ # --------------------
7
+ # Usage:
8
+ # ./setup_and_run.sh
9
+ # Optional env overrides:
10
+ # HF_API_TOKEN - your Hugging Face inference API token (if omitted, server runs offline fallback)
11
+ # HF_MODEL_ID - model id for HF inference (default: gpt2)
12
+ # COGNITO_API_KEY - API key used by the widget to call /chat (default provided)
13
+ # ALLOW_FETCH - "true" to allow server-side fetches (default false)
14
+ # FETCH_BEARER_TOKEN - optional bearer token used by server to fetch authorized pages (if you provide)
15
+ #
16
+ # Note: Do NOT use this to bypass site protections. Only fetch pages you own/are authorized to access.
17
 
18
+ ROOT_DIR="$(pwd)"
19
+ VENV_DIR="$ROOT_DIR/.venv"
20
+
21
+ echo "==> Preparing virtualenv..."
22
+ if ! command -v python3 >/dev/null 2>&1; then
23
+ echo "Python3 is required. Install Python 3.10+ and retry."
24
+ exit 1
25
  fi
26
 
27
+ python3 -m venv "$VENV_DIR"
28
+ # shellcheck disable=SC1091
29
+ source "$VENV_DIR/bin/activate"
30
 
 
31
  pip install --upgrade pip
32
 
33
+ cat > requirements.txt <<'PYREQ'
 
34
  fastapi==0.95.2
35
  uvicorn[standard]==0.21.1
36
  httpx==0.24.1
37
  python-dotenv==1.0.0
 
 
38
  python-multipart==0.0.6
39
  sqlalchemy==2.0.19
40
  pydantic==1.10.12
41
+ PYREQ
42
 
43
+ echo "==> Installing Python dependencies (lightweight)..."
44
  pip install -r requirements.txt
45
 
46
+ # Write app.py
47
+ cat > app.py <<'PYAPP'
48
+ import os
49
+ import json
50
+ import sqlite3
51
+ import urllib.parse
52
+ from typing import Optional, List, Dict
53
+ from fastapi import FastAPI, Request, HTTPException, UploadFile, File, Form
54
+ from pydantic import BaseModel
55
+ import httpx
56
+ from starlette.responses import PlainTextResponse, HTMLResponse, Response, FileResponse
57
+
58
+ # Config (can be overridden by environment variables)
59
+ HF_API_TOKEN = os.getenv("HF_API_TOKEN", "")
60
+ HF_MODEL_ID = os.getenv("HF_MODEL_ID", "gpt2")
61
+ COGNITO_API_KEY = os.getenv("COGNITO_API_KEY", "cognito_default_key_please_change")
62
+ ALLOW_FETCH = os.getenv("ALLOW_FETCH", "false").lower() == "true"
63
+ FETCH_BEARER_TOKEN = os.getenv("FETCH_BEARER_TOKEN", "")
64
+ MODERATION_BLOCKLIST = [s.strip().lower() for s in os.getenv("MODERATION_BLOCKLIST", "hack,steal,illegal").split(",") if s.strip()]
65
+
66
+ DB_PATH = os.path.join(os.getcwd(), "cognito_sessions.db")
67
+ UPLOADS_DIR = os.path.join(os.getcwd(), "uploads")
68
+ os.makedirs(UPLOADS_DIR, exist_ok=True)
69
+
70
+ app = FastAPI(title="Cognito (Minimal)", docs_url="/docs")
71
+
72
+ # --- DB helpers (SQLite simple storage) ---
73
+ def init_db():
74
+ conn = sqlite3.connect(DB_PATH)
75
+ cur = conn.cursor()
76
+ cur.execute("""
77
+ CREATE TABLE IF NOT EXISTS sessions (
78
+ session_id TEXT PRIMARY KEY,
79
+ messages TEXT
80
+ )
81
+ """)
82
+ conn.commit()
83
+ conn.close()
84
+
85
+ def get_session_messages(session_id: str):
86
+ conn = sqlite3.connect(DB_PATH)
87
+ cur = conn.cursor()
88
+ cur.execute("SELECT messages FROM sessions WHERE session_id = ?", (session_id,))
89
+ row = cur.fetchone()
90
+ conn.close()
91
+ if row:
92
+ return json.loads(row[0])
93
+ return []
94
+
95
+ def save_session_messages(session_id: str, messages):
96
+ conn = sqlite3.connect(DB_PATH)
97
+ cur = conn.cursor()
98
+ cur.execute("INSERT OR REPLACE INTO sessions (session_id, messages) VALUES (?, ?)", (session_id, json.dumps(messages)))
99
+ conn.commit()
100
+ conn.close()
101
+
102
+ init_db()
103
+
104
+ # --- Request models ---
105
+ class ChatRequest(BaseModel):
106
+ session_id: Optional[str] = None
107
+ messages: List[Dict] # [{"role":"user","content":"..."}]
108
+ fetch_url: Optional[str] = None
109
+
110
+ # --- Moderation ---
111
+ def moderate_messages(messages):
112
+ txt = " ".join(m.get("content","") for m in messages).lower()
113
+ for bad in MODERATION_BLOCKLIST:
114
+ if bad and bad in txt:
115
+ return False, f"Message blocked by moderation: found banned word '{bad}'"
116
+ return True, ""
117
+
118
+ # --- Helper: simple prompt builder ---
119
+ def build_prompt(messages, uploaded_texts=None):
120
+ prompt = ""
121
+ if uploaded_texts:
122
+ prompt += "Context documents:\n"
123
+ for i, t in enumerate(uploaded_texts):
124
+ prompt += f"[DOC {i+1}]\n{t}\n\n"
125
+ prompt += "---\n"
126
+ for m in messages:
127
+ role = m.get("role","user")
128
+ content = m.get("content","")
129
+ if role == "system":
130
+ prompt += f"[SYSTEM] {content}\n"
131
+ elif role == "user":
132
+ prompt += f"User: {content}\n"
133
+ else:
134
+ prompt += f"Assistant: {content}\n"
135
+ prompt += "\nAssistant:"
136
+ return prompt
137
+
138
+ # --- File uploads endpoint ---
139
+ @app.post("/upload")
140
+ async def upload_file(session_id: Optional[str] = Form(None), file: UploadFile = File(...)):
141
+ # Stores plain text/markdown files into uploads/, returns file id
142
+ content = await file.read()
143
+ fname = file.filename
144
+ safe_name = fname.replace("..","_").replace("/","_")
145
+ path = os.path.join(UPLOADS_DIR, safe_name)
146
+ with open(path, "wb") as f:
147
+ f.write(content)
148
+ return {"ok": True, "path": path}
149
+
150
+ # --- Serve widget JS ---
151
+ @app.get("/cognito-widget.js")
152
+ def widget_js():
153
+ # This simple widget matches what's described in README and expects window.COGNITO_API_BASE & API key set on host page.
154
+ js = """
155
+ (function () {
156
+ if (window.CognitoWidgetInitialized) return;
157
+ window.CognitoWidgetInitialized = true;
158
+ const CONFIG = { apiBase: window.COGNITO_API_BASE || "http://localhost:8000", apiKey: window.COGNITO_API_KEY || null, title: "Cognito" };
159
+ 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 }`;
160
+ const styleEl=document.createElement("style"); styleEl.innerHTML=css; document.head.appendChild(styleEl);
161
+ const btn=document.createElement("button"); btn.className='cognito-chat-btn'; btn.innerText=CONFIG.title; document.body.appendChild(btn);
162
+ 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);
163
+ btn.addEventListener('click', ()=>{ panel.style.display = panel.style.display === 'flex' ? 'none' : 'flex'; panel.style.flexDirection='column'; });
164
+ const bodyEl=panel.querySelector('.cognito-body'); const inputEl=panel.querySelector('.cognito-input'); const sendBtn=panel.querySelector('.cognito-send');
165
+ 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; }
166
+ 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'); } }
167
+ sendBtn.addEventListener('click', ()=>{ const v=inputEl.value.trim(); if(!v) return; sendMessage(v); });
168
+ inputEl.addEventListener('keydown', (e)=>{ if(e.key==='Enter'){ e.preventDefault(); sendBtn.click(); } });
169
+ window.CognitoWidget = { send: sendMessage, open: () => { panel.style.display='flex'; }, close: () => { panel.style.display='none'; } };
170
+ })();
171
+ """
172
+ return Response(content=js, media_type="application/javascript")
173
+
174
+ # --- Status root ---
175
+ @app.get("/")
176
+ def index():
177
+ content = f"<html><body><h3>Cognito (minimal)</h3><p>Use the widget by embedding <code>&lt;script src=\"/cognito-widget.js\"&gt;&lt;/script&gt;</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>"
178
+ return HTMLResponse(content=content)
179
+
180
+ # --- Helper: server-side fetch (ONLY if ALLOW_FETCH enabled) ---
181
+ async def fetch_url_with_bearer(url: str):
182
+ if not ALLOW_FETCH:
183
+ raise HTTPException(status_code=403, detail="Server is not configured to fetch external pages.")
184
+ parsed = urllib.parse.urlparse(url)
185
+ if parsed.scheme not in ("http","https"):
186
+ raise HTTPException(status_code=400, detail="Invalid URL scheme.")
187
+ headers = {"User-Agent": "CognitoFetcher/1.0"}
188
+ if FETCH_BEARER_TOKEN:
189
+ headers["Authorization"] = f"Bearer {FETCH_BEARER_TOKEN}"
190
+ async with httpx.AsyncClient(timeout=20.0) as client:
191
+ r = await client.get(url, headers=headers)
192
+ if r.status_code >= 400:
193
+ raise HTTPException(status_code=502, detail=f"Upstream fetch failed: {r.status_code}")
194
+ # return text limited to first N chars
195
+ return r.text[:20000]
196
+
197
+ # --- HF inference call (if token provided) ---
198
+ def call_hf_inference(prompt: str):
199
+ if not HF_API_TOKEN:
200
+ # fallback: simple deterministic reply
201
+ return "Cognito (offline mode): I received your message. Provide HF_API_TOKEN to enable real model responses."
202
+ url = f"https://api-inference.huggingface.co/models/{HF_MODEL_ID}"
203
+ headers = {"Authorization": f"Bearer {HF_API_TOKEN}", "Content-Type": "application/json"}
204
+ payload = {"inputs": prompt, "options": {"wait_for_model": True}}
205
+ try:
206
+ with httpx.Client(timeout=60.0) as client:
207
+ r = client.post(url, headers=headers, json=payload)
208
+ r.raise_for_status()
209
+ out = r.json()
210
+ # attempt to extract text
211
+ if isinstance(out, list) and len(out) > 0 and isinstance(out[0], dict):
212
+ return out[0].get("generated_text") or str(out)
213
+ if isinstance(out, dict):
214
+ return out.get("generated_text") or str(out)
215
+ return str(out)
216
+ except Exception as e:
217
+ return f"Cognito: error calling HF inference: {str(e)}"
218
+
219
+ # --- /chat endpoint ---
220
+ @app.post("/chat")
221
+ async def chat(req: ChatRequest, request: Request):
222
+ # API key check
223
+ header_key = request.headers.get("x-api-key", "")
224
+ if header_key != COGNITO_API_KEY:
225
+ raise HTTPException(status_code=401, detail="Invalid API key.")
226
+
227
+ # moderation
228
+ ok, reason = moderate_messages(req.messages)
229
+ if not ok:
230
+ raise HTTPException(status_code=400, detail=reason)
231
+
232
+ uploaded_texts = []
233
+ # If session has uploaded files, read small files from uploads/
234
+ if req.session_id:
235
+ # naive: read any files that start with session_id in uploads folder
236
+ for fname in os.listdir(UPLOADS_DIR):
237
+ if fname.startswith(req.session_id):
238
+ path = os.path.join(UPLOADS_DIR, fname)
239
+ try:
240
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
241
+ uploaded_texts.append(f.read()[:5000])
242
+ except Exception:
243
+ pass
244
+
245
+ # optional fetch
246
+ fetched = ""
247
+ if req.fetch_url:
248
+ fetched = await fetch_url_with_bearer(req.fetch_url)
249
+
250
+ prompt = build_prompt(req.messages, uploaded_texts + ([fetched] if fetched else []))
251
+ reply = call_hf_inference(prompt)
252
+
253
+ # save to session
254
+ sess_id = req.session_id or "anon"
255
+ prev = get_session_messages(sess_id)
256
+ prev.append({"role":"user","content": req.messages[-1].get("content","") if req.messages else ""})
257
+ prev.append({"role":"assistant","content": reply})
258
+ # cap history size
259
+ if len(prev) > 50:
260
+ prev = prev[-50:]
261
+ save_session_messages(sess_id, prev)
262
+
263
+ return {"reply": reply}
264
+
265
+ # --- simple health route ---
266
+ @app.get("/health")
267
+ def health():
268
+ return {"status": "ok", "hf_token": bool(HF_API_TOKEN), "allow_fetch": ALLOW_FETCH}
269
+ PYAPP
270
+
271
+ # Write cognito-widget.js (also for convenience if user wants standalone file)
272
+ cat > cognito-widget.js <<'WIDGET'
273
+ /* This file is also available at /cognito-widget.js when server is running.
274
+ The hosted widget expects window.COGNITO_API_BASE and window.COGNITO_API_KEY on the host page. */
275
+ (function () {
276
+ if (window.CognitoWidgetInitialized) return;
277
+ window.CognitoWidgetInitialized = true;
278
+ const CONFIG = { apiBase: window.COGNITO_API_BASE || "http://localhost:8000", apiKey: window.COGNITO_API_KEY || null, title: "Cognito" };
279
+ 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 }`;
280
+ const styleEl=document.createElement("style"); styleEl.innerHTML=css; document.head.appendChild(styleEl);
281
+ const btn=document.createElement("button"); btn.className='cognito-chat-btn'; btn.innerText=CONFIG.title; document.body.appendChild(btn);
282
+ 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);
283
+ btn.addEventListener('click', ()=>{ panel.style.display = panel.style.display === 'flex' ? 'none' : 'flex'; panel.style.flexDirection='column'; });
284
+ const bodyEl=panel.querySelector('.cognito-body'); const inputEl=panel.querySelector('.cognito-input'); const sendBtn=panel.querySelector('.cognito-send');
285
+ 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; }
286
+ 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'); } }
287
+ sendBtn.addEventListener('click', ()=>{ const v=inputEl.value.trim(); if(!v) return; sendMessage(v); });
288
+ inputEl.addEventListener('keydown', (e)=>{ if(e.key==='Enter'){ e.preventDefault(); sendBtn.click(); } });
289
+ window.CognitoWidget = { send: sendMessage, open: () => { panel.style.display='flex'; }, close: () => { panel.style.display='none'; } };
290
+ })();
291
+ WIDGET
292
+
293
+ # Provide default .env file
294
+ cat > .env <<'ENV'
295
+ # Cognito minimal .env - automatically used by setup script defaults if not provided
296
+ # Replace HF_API_TOKEN with a valid Hugging Face Inference API token to enable model responses.
297
+ HF_API_TOKEN=""
298
+ HF_MODEL_ID="gpt2"
299
+ COGNITO_API_KEY="cognito_default_key_please_change"
300
+ ALLOW_FETCH="false"
301
+ FETCH_BEARER_TOKEN=""
302
+ ENV
303
 
304
+ echo "==> Starting server (virtualenv active)."
305
+ echo "API key (for widget) default: cognito_default_key_please_change"
306
+ echo "If you have a Hugging Face token, export HF_API_TOKEN before running this script for real model responses."
307
+ echo "Example to run with token: HF_API_TOKEN='hf_xxx' ./setup_and_run.sh"
308
 
309
+ # Start uvicorn
310
+ # shellcheck disable=SC2086
311
  uvicorn app:app --host 0.0.0.0 --port 8000 --reload