mendeld commited on
Commit
adb1ebb
·
verified ·
1 Parent(s): cde39d8

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -354
app.py DELETED
@@ -1,354 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- import secrets
5
- from pathlib import Path
6
- from types import SimpleNamespace
7
- from typing import Any
8
-
9
- # ZeroGPU: import spaces before GPU-sensitive modules.
10
- try:
11
- import spaces
12
- except ImportError:
13
- class _SpacesFallback:
14
- @staticmethod
15
- def GPU(*args, **kwargs):
16
- def deco(fn):
17
- return fn
18
- return deco
19
- spaces = _SpacesFallback()
20
-
21
- import gradio as gr
22
- import uvicorn
23
- from fastapi import FastAPI, Request, HTTPException
24
- from fastapi.responses import JSONResponse, FileResponse, HTMLResponse
25
- from fastapi.middleware.cors import CORSMiddleware
26
- from pydantic import BaseModel, Field
27
- from starlette.middleware.base import BaseHTTPMiddleware
28
- from starlette.middleware.sessions import SessionMiddleware
29
-
30
- from bh56_core import (
31
- BH56_VERSION, STORE, LIVE_LOG, admin_auth, admin_role,
32
- build_location, geocode_city, timezone_for_coordinates,
33
- )
34
- from bh56_ui import (
35
- BASE_CSS,
36
- PAGE_JS,
37
- build_admin_ui,
38
- clean_history,
39
- prepare_message,
40
- generate_answer,
41
- )
42
- from ac_frontend import ac_frontend_html, admin_login_html
43
-
44
- os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False")
45
- os.environ.setdefault("HF_HUB_DISABLE_TELEMETRY", "1")
46
- os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
47
-
48
- SESSION_SECRET = os.getenv("BH56_SESSION_SECRET", "") or secrets.token_urlsafe(48)
49
-
50
- app = FastAPI(
51
- title=f"Artificial Chachma (AC) {BH56_VERSION}",
52
- docs_url=None,
53
- redoc_url=None,
54
- openapi_url=None,
55
- )
56
- app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET, https_only=True, same_site="lax", max_age=1800)
57
-
58
- allowed_origins = [
59
- x.strip() for x in os.getenv(
60
- "BH56_CORS_ORIGINS",
61
- "https://5bcheder.com,https://www.5bcheder.com",
62
- ).split(",") if x.strip()
63
- ]
64
- app.add_middleware(
65
- CORSMiddleware,
66
- allow_origins=allowed_origins,
67
- allow_credentials=True,
68
- allow_methods=["GET", "POST"],
69
- allow_headers=["Content-Type", "Authorization", "X-Requested-With"],
70
- )
71
-
72
-
73
- class SecurityHeadersMiddleware(BaseHTTPMiddleware):
74
- async def dispatch(self, request: Request, call_next):
75
- response = await call_next(request)
76
- frame_ancestors = os.getenv(
77
- "BH56_FRAME_ANCESTORS",
78
- "'self' https://huggingface.co https://*.huggingface.co https://5bcheder.com https://www.5bcheder.com https://*.hf.space",
79
- )
80
- response.headers["X-Content-Type-Options"] = "nosniff"
81
- response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
82
- response.headers["Permissions-Policy"] = "geolocation=(self), microphone=(self), camera=()"
83
- response.headers["Content-Security-Policy"] = f"frame-ancestors {frame_ancestors};"
84
- response.headers["Cache-Control"] = "no-store" if request.url.path.startswith("/admin") else "private, max-age=0"
85
- return response
86
-
87
-
88
- app.add_middleware(SecurityHeadersMiddleware)
89
-
90
- ICON_PATH = Path(__file__).with_name("ac_app_icon.png")
91
-
92
-
93
- @app.get("/ac_app_icon.png", include_in_schema=False)
94
- def ac_app_icon():
95
- return FileResponse(str(ICON_PATH), media_type="image/png", headers={"Cache-Control":"public, max-age=86400"})
96
-
97
-
98
- @app.get("/health")
99
- def health():
100
- return JSONResponse({
101
- "ok": True,
102
- "name": "Artificial Chachma (AC)",
103
- "version": BH56_VERSION,
104
- "persistent_storage": STORE.persistent_enabled,
105
- "frontend": "custom-fixed-shell",
106
- })
107
-
108
-
109
- def _session_id(request: Request) -> str:
110
- sid = request.session.get("ac_sid")
111
- if not isinstance(sid, str) or len(sid) < 12:
112
- sid = secrets.token_urlsafe(16)
113
- request.session["ac_sid"] = sid
114
- return sid
115
-
116
-
117
- class ChatPayload(BaseModel):
118
- message: str = Field(min_length=1, max_length=12000)
119
- history: list[dict[str, Any]] = Field(default_factory=list)
120
- mode: str = "Binah"
121
- answer_length: str = "Normal"
122
- study_mode: str = "Explain"
123
- location: dict[str, Any] = Field(default_factory=dict)
124
- announce_location: bool = False
125
- location_attempted: bool = False
126
-
127
-
128
- class LocationSearchPayload(BaseModel):
129
- query: str = Field(min_length=1, max_length=150)
130
-
131
-
132
- def _location_dict_from_payload(raw: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
133
- """Turn a browser/manual location payload into server-side location state.
134
-
135
- Exact browser coordinates are used only for this request. They are not added
136
- to chat history, browser chat archives, LIVE_LOG, or the public response.
137
- """
138
- if not isinstance(raw, dict):
139
- return {}, {}
140
- source = str(raw.get("source") or "").strip().lower()
141
- try:
142
- lat = float(raw.get("latitude"))
143
- lon = float(raw.get("longitude"))
144
- except Exception:
145
- return {}, {}
146
- if not (-90 <= lat <= 90 and -180 <= lon <= 180):
147
- return {}, {}
148
-
149
- if source == "manual":
150
- label = str(raw.get("label") or "").strip()[:220]
151
- cc = str(raw.get("country_code") or "").strip().upper()[:3]
152
- if not label:
153
- return {}, {}
154
- tz = timezone_for_coordinates(lat, lon)
155
- loc = build_location(lat, lon, tz, "manual city", label, cc)
156
- kind = "manual"
157
- else:
158
- browser_tz = str(raw.get("timezone") or "").strip()[:80]
159
- try:
160
- accuracy = max(0.0, float(raw.get("accuracy") or 999999))
161
- except Exception:
162
- accuracy = 999999.0
163
- # Reduced-precision browser locations can be several kilometers wide.
164
- # Do not describe them as exact.
165
- method = "browser geolocation" if accuracy <= 1500 else "approximate browser geolocation"
166
- loc = build_location(lat, lon, browser_tz, method)
167
- kind = "exact" if loc.exact else "approximate"
168
-
169
- if loc.latitude is None:
170
- return {}, {}
171
- state = {
172
- "latitude": loc.latitude, "longitude": loc.longitude,
173
- "timezone_name": loc.timezone_name, "label": loc.label,
174
- "method": loc.method, "country_code": loc.country_code, "exact": loc.exact,
175
- }
176
- public = {"label": loc.label, "kind": kind, "method": loc.method}
177
- return state, public
178
-
179
-
180
- @app.get("/", response_class=HTMLResponse, include_in_schema=False)
181
- def home():
182
- return HTMLResponse(ac_frontend_html(is_admin=False))
183
-
184
-
185
- @app.post("/api/location/search", include_in_schema=False)
186
- def api_location_search(payload: LocationSearchPayload):
187
- rows = geocode_city(payload.query)
188
- # Manual locations are city/place-center coordinates, not a user's precise
189
- # device coordinates, so returning them to the browser is safe and lets the
190
- # selected city be reused locally.
191
- return {
192
- "results": [{
193
- "label": str(r.get("label") or "")[:240],
194
- "latitude": float(r.get("lat")),
195
- "longitude": float(r.get("lon")),
196
- "country_code": str(r.get("country_code") or "").upper()[:3],
197
- } for r in rows[:6]]
198
- }
199
-
200
-
201
- @app.post("/api/chat", include_in_schema=False)
202
- def api_chat(payload: ChatPayload, request: Request):
203
- history = clean_history(payload.history)[-60:]
204
- loc_dict, public_loc = _location_dict_from_payload(payload.location)
205
- prepared, _, browser_history, _, _ = prepare_message(
206
- payload.message,
207
- history,
208
- history,
209
- "Saved",
210
- loc_dict,
211
- )
212
- fake_request = SimpleNamespace(
213
- session_hash=_session_id(request),
214
- username="",
215
- )
216
- result = generate_answer(
217
- prepared,
218
- browser_history,
219
- "Saved",
220
- loc_dict,
221
- payload.mode if payload.mode in {"Chachma", "Binah", "Daas"} else "Binah",
222
- payload.answer_length if payload.answer_length in {"Short", "Normal", "Detailed"} else "Normal",
223
- payload.study_mode if payload.study_mode in {"Explain", "Hint first", "Quiz me", "Study mode"} else "Explain",
224
- fake_request,
225
- )
226
- new_history, _, _, status, details = result
227
- cleaned = clean_history(new_history)
228
-
229
- # The first local answer tells the student what AC is using without ever
230
- # exposing coordinates. If it looks wrong, Manual Location is one click away.
231
- if payload.announce_location and public_loc and cleaned and cleaned[-1].get("role") == "assistant":
232
- label = str(public_loc.get("label") or "Location")
233
- kind = str(public_loc.get("kind") or "approximate")
234
- if kind == "exact":
235
- lead = f"📍 **Exact location: {label}.**"
236
- elif kind == "manual":
237
- lead = f"📍 **Manual location: {label}.**"
238
- else:
239
- lead = f"📍 **Approximate location: {label}.**"
240
- lead += " If this isn't correct, use **Manual Location** in the sidebar."
241
- cleaned[-1]["content"] = lead + "\n\n" + str(cleaned[-1].get("content") or "")
242
-
243
- return {
244
- "history": cleaned,
245
- "status": str(status or ""),
246
- "details": str(details or ""),
247
- "location": public_loc,
248
- "location_attempted": bool(payload.location_attempted),
249
- }
250
-
251
-
252
- class AdminLoginPayload(BaseModel):
253
- username: str = Field(min_length=1, max_length=120)
254
- password: str = Field(min_length=1, max_length=300)
255
-
256
-
257
- @app.post("/api/admin/login", include_in_schema=False)
258
- def admin_login(payload: AdminLoginPayload, request: Request):
259
- if not admin_auth(payload.username, payload.password):
260
- raise HTTPException(status_code=401, detail="Incorrect username or password.")
261
- request.session["admin_user"] = payload.username.strip()
262
- return {"ok": True}
263
-
264
-
265
- def _admin_from_session(request: Request) -> tuple[str, str]:
266
- user = str(request.session.get("admin_user") or "")
267
- role = admin_role(user)
268
- if role not in {"main", "admin"}:
269
- request.session.pop("admin_user", None)
270
- raise HTTPException(status_code=401, detail="Admin authentication required.")
271
- return user, role
272
-
273
-
274
- @app.get("/admin", response_class=HTMLResponse, include_in_schema=False)
275
- def admin_home(request: Request):
276
- try:
277
- user, role = _admin_from_session(request)
278
- except HTTPException:
279
- return HTMLResponse(admin_login_html())
280
- return HTMLResponse(ac_frontend_html(is_admin=True, username=user, role=role))
281
-
282
-
283
- @app.get("/api/admin/dashboard", include_in_schema=False)
284
- def api_admin_dashboard(request: Request):
285
- user, role = _admin_from_session(request)
286
- cfg = STORE.config()
287
- live = LIVE_LOG.snapshot(100)
288
- return {
289
- "user": user,
290
- "role": "Head Admin" if role == "main" else "Admin",
291
- "version": BH56_VERSION,
292
- "live_requests": len(live),
293
- "emergency_pause": bool(cfg.get("paused")),
294
- "persistent_storage": bool(STORE.persistent_enabled),
295
- }
296
-
297
-
298
- @app.get("/api/admin/chats", include_in_schema=False)
299
- def api_admin_chats(request: Request):
300
- user, role = _admin_from_session(request)
301
- rows = LIVE_LOG.snapshot(200)
302
- if role != "main":
303
- rows = [r for r in rows if r.get("role", "student") == "student"]
304
- rows = list(reversed(rows[-100:]))
305
- return {
306
- "chats": [{
307
- "time": r.get("time", ""),
308
- "session": r.get("session", ""),
309
- "role": r.get("role", "student"),
310
- "mode": r.get("mode", ""),
311
- "user": r.get("user", ""),
312
- "response": r.get("response", ""),
313
- } for r in rows]
314
- }
315
-
316
-
317
- @app.post("/api/admin/logout", include_in_schema=False)
318
- def api_admin_logout(request: Request):
319
- request.session.pop("admin_user", None)
320
- return {"ok": True}
321
-
322
-
323
- # Keep the existing full Gradio admin console available for advanced controls.
324
- # It is NOT the visible student/admin shell, so its page sizing cannot make the
325
- # Hugging Face App tab grow.
326
- admin_ui = build_admin_ui()
327
- app = gr.mount_gradio_app(
328
- app,
329
- admin_ui,
330
- path="/admin-console",
331
- auth=admin_auth,
332
- auth_message="AC Admin — authorized administrators only.",
333
- footer_links=[],
334
- show_error=False,
335
- max_file_size="25mb",
336
- enable_monitoring=False,
337
- mcp_server=False,
338
- ssr_mode=False,
339
- pwa=False,
340
- theme=gr.themes.Base(),
341
- css=BASE_CSS,
342
- js=PAGE_JS,
343
- )
344
-
345
- try:
346
- import spaces.zero as _spaces_zero
347
- if hasattr(_spaces_zero, "startup"):
348
- _spaces_zero.startup()
349
- except Exception as exc:
350
- print(f"[AC ZeroGPU] startup report note: {exc}")
351
-
352
- if __name__ == "__main__":
353
- port = int(os.getenv("GRADIO_SERVER_PORT", os.getenv("PORT", "7860")))
354
- uvicorn.run(app, host="0.0.0.0", port=port, log_level="info")