# app.py — FastAPI-only (no WebSockets/SSE) import os from typing import List, Optional, Tuple from fastapi import FastAPI from fastapi.responses import HTMLResponse, JSONResponse from pydantic import BaseModel, Field from openai import OpenAI # --- Your prompts (edit these files in /prompts) --- from prompts.initial_prompt import INITIAL_PROMPT from prompts.main_prompt import MAIN_PROMPT # Read key from Hugging Face Secret (Settings → Variables and secrets) OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") if not OPENAI_API_KEY: print("WARNING: OPENAI_API_KEY secret is not set. Set it in the Space settings.") client = OpenAI(api_key=OPENAI_API_KEY) # ---------- Models for API I/O ---------- class ChatIn(BaseModel): message: str = Field("", description="The latest user message.") # Optional: carry conversation history as a list of [user, assistant] turns history: Optional[List[Tuple[str, str]]] = Field( default=None, description="Optional history as list of (user, assistant) tuples." ) model: str = Field(default="gpt-4o-mini") temperature: float = Field(default=0.7) top_p: float = Field(default=0.95) max_tokens: int = Field(default=600) class ChatOut(BaseModel): reply: str # ---------- Core non-streaming OpenAI call (server-side) ---------- def generate_reply( user_message: str, history: Optional[List[Tuple[str, str]]] = None, model: str = "gpt-4o-mini", temperature: float = 0.7, top_p: float = 0.95, max_tokens: int = 600, ) -> str: """ Builds messages as: system: MAIN_PROMPT assistant: INITIAL_PROMPT (so users see your intro) ... then optional history [(user, assistant), ...] user: latest message """ messages = [ {"role": "system", "content": MAIN_PROMPT}, {"role": "assistant", "content": INITIAL_PROMPT}, ] if history: for u, a in history: if u: messages.append({"role": "user", "content": u}) if a: messages.append({"role": "assistant", "content": a}) messages.append({"role": "user", "content": user_message or ""}) try: resp = client.chat.completions.create( model=model, messages=messages, temperature=temperature, top_p=top_p, max_tokens=max_tokens, ) return resp.choices[0].message.content except Exception as e: # Return the error to the UI return f"Error: {e}" # ---------- FastAPI app + routes ---------- app = FastAPI(title="Module Chat — Basic Mode") @app.get("/health") async def health(): return {"status": "ok"} @app.post("/api/chat", response_model=ChatOut) async def api_chat(payload: ChatIn): reply = generate_reply( user_message=payload.message, history=payload.history, model=payload.model, temperature=payload.temperature, top_p=payload.top_p, max_tokens=payload.max_tokens, ) return ChatOut(reply=reply) # Single-page minimal UI that uses ONLY plain HTTPS (no WebSockets) INDEX_HTML = f""" Module Chat — Basic Mode

Module Chat — Basic Mode

This version uses only normal HTTPS requests (works even on strict campus Wi-Fi).
Assistant: {INITIAL_PROMPT}

Reply

Tip: Press Ctrl/Cmd+Enter to send.

""" @app.get("/", response_class=HTMLResponse) async def root(): return INDEX_HTML