scchess commited on
Commit
1a588c5
·
1 Parent(s): fa81066

Run via uvicorn FastAPI+Gradio so /chat stays up for Flutter.

Browse files
Files changed (3) hide show
  1. README.md +3 -2
  2. app.py +50 -33
  3. requirements.txt +1 -0
README.md CHANGED
@@ -13,16 +13,17 @@ pinned: false
13
 
14
  Gradio Space that proxies chat to DeepSeek with per-topic personality prompts.
15
 
16
- ## App endpoints (used by Flutter)
17
 
18
  - `GET /health`
19
  - `GET /personalities`
20
  - `GET /personalities/{id}`
21
  - `POST /chat` — `{ "personalityId", "message", "history?" }`
 
22
 
23
  Public URL: `https://scchess-confidence-api.hf.space`
24
 
25
  ## Secrets
26
 
27
  - `DEEPSEEK_API_KEY` (required)
28
- - `CHAT_API_SECRET` (optional) — if set, non-health routes need header `X-App-Key`
 
13
 
14
  Gradio Space that proxies chat to DeepSeek with per-topic personality prompts.
15
 
16
+ ## Endpoints (Flutter)
17
 
18
  - `GET /health`
19
  - `GET /personalities`
20
  - `GET /personalities/{id}`
21
  - `POST /chat` — `{ "personalityId", "message", "history?" }`
22
+ - UI: `/ui`
23
 
24
  Public URL: `https://scchess-confidence-api.hf.space`
25
 
26
  ## Secrets
27
 
28
  - `DEEPSEEK_API_KEY` (required)
29
+ - `CHAT_API_SECRET` (optional) — non-health API routes need header `X-App-Key`
app.py CHANGED
@@ -9,12 +9,12 @@ from typing import Any
9
 
10
  import gradio as gr
11
  import httpx
12
- from fastapi import HTTPException, Request
13
- from fastapi.responses import JSONResponse
14
 
15
  try:
16
  import spaces
17
- except ImportError: # local runs without ZeroGPU
18
  spaces = None
19
 
20
  PERSONALITIES_PATH = Path(__file__).with_name("personalities.json")
@@ -130,13 +130,7 @@ async def deepseek_chat(
130
  }
131
 
132
 
133
- PERSONALITY_CHOICES = [
134
- (f"{p['name']} ({p['id']})", p["id"]) for p in PERSONALITIES.values()
135
- ]
136
-
137
-
138
- # ZeroGPU Spaces require at least one @spaces.GPU function at import time.
139
- # Chat itself is CPU-only (DeepSeek HTTP proxy); this satisfies the runtime check.
140
  if spaces is not None:
141
 
142
  @spaces.GPU(duration=60)
@@ -144,6 +138,11 @@ if spaces is not None:
144
  return "ok"
145
 
146
 
 
 
 
 
 
147
  async def ui_chat(
148
  personality_id: str,
149
  message: str,
@@ -163,28 +162,13 @@ async def ui_chat(
163
  return history, ""
164
 
165
 
166
- with gr.Blocks(title="Confidence Buddy API") as demo:
167
- gr.Markdown(
168
- "## Confidence Buddy API\n"
169
- "Flutter uses `POST /chat`. This UI is for quick manual checks.\n\n"
170
- f"Personalities loaded: **{len(PERSONALITIES)}**"
171
- )
172
- personality = gr.Dropdown(
173
- choices=PERSONALITY_CHOICES,
174
- value=PERSONALITY_CHOICES[0][1] if PERSONALITY_CHOICES else None,
175
- label="Personality",
176
- )
177
- chatbot = gr.Chatbot(type="messages", height=420)
178
- msg = gr.Textbox(label="Message", placeholder="Type a message…")
179
- clear = gr.Button("Clear")
180
- msg.submit(ui_chat, [personality, msg, chatbot], [chatbot, msg])
181
- clear.click(lambda: ([], ""), outputs=[chatbot, msg])
182
 
183
 
184
- # Flutter-compatible REST routes on the Gradio FastAPI app (Spaces launches `demo`).
185
- @demo.app.middleware("http")
186
  async def optional_app_key(request: Request, call_next):
187
- if request.url.path == "/health":
 
188
  return await call_next(request)
189
  secret = _app_secret()
190
  if secret and request.headers.get("x-app-key") != secret:
@@ -192,17 +176,22 @@ async def optional_app_key(request: Request, call_next):
192
  return await call_next(request)
193
 
194
 
195
- @demo.app.get("/health")
 
 
 
 
 
196
  async def health():
197
  return {"ok": True, "hasApiKey": bool(_api_key())}
198
 
199
 
200
- @demo.app.get("/personalities")
201
  async def personalities_list():
202
  return list_personalities()
203
 
204
 
205
- @demo.app.get("/personalities/{personality_id}")
206
  async def personality_detail(personality_id: str):
207
  personality = get_personality(personality_id)
208
  if not personality:
@@ -217,7 +206,7 @@ async def personality_detail(personality_id: str):
217
  }
218
 
219
 
220
- @demo.app.post("/chat")
221
  async def chat_endpoint(request: Request):
222
  body = await request.json()
223
  try:
@@ -228,3 +217,31 @@ async def chat_endpoint(request: Request):
228
  )
229
  except HTTPException as exc:
230
  return JSONResponse({"error": exc.detail}, status_code=exc.status_code)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  import gradio as gr
11
  import httpx
12
+ from fastapi import FastAPI, HTTPException, Request
13
+ from fastapi.responses import JSONResponse, RedirectResponse
14
 
15
  try:
16
  import spaces
17
+ except ImportError:
18
  spaces = None
19
 
20
  PERSONALITIES_PATH = Path(__file__).with_name("personalities.json")
 
130
  }
131
 
132
 
133
+ # ZeroGPU hardware on this Space requires a @spaces.GPU symbol at import time.
 
 
 
 
 
 
134
  if spaces is not None:
135
 
136
  @spaces.GPU(duration=60)
 
138
  return "ok"
139
 
140
 
141
+ PERSONALITY_CHOICES = [
142
+ (f"{p['name']} ({p['id']})", p["id"]) for p in PERSONALITIES.values()
143
+ ]
144
+
145
+
146
  async def ui_chat(
147
  personality_id: str,
148
  message: str,
 
162
  return history, ""
163
 
164
 
165
+ api = FastAPI()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
 
168
+ @api.middleware("http")
 
169
  async def optional_app_key(request: Request, call_next):
170
+ path = request.url.path
171
+ if path in ("/health", "/", "/ui", "/ui/") or path.startswith("/ui/"):
172
  return await call_next(request)
173
  secret = _app_secret()
174
  if secret and request.headers.get("x-app-key") != secret:
 
176
  return await call_next(request)
177
 
178
 
179
+ @api.get("/")
180
+ async def root():
181
+ return RedirectResponse(url="/ui/")
182
+
183
+
184
+ @api.get("/health")
185
  async def health():
186
  return {"ok": True, "hasApiKey": bool(_api_key())}
187
 
188
 
189
+ @api.get("/personalities")
190
  async def personalities_list():
191
  return list_personalities()
192
 
193
 
194
+ @api.get("/personalities/{personality_id}")
195
  async def personality_detail(personality_id: str):
196
  personality = get_personality(personality_id)
197
  if not personality:
 
206
  }
207
 
208
 
209
+ @api.post("/chat")
210
  async def chat_endpoint(request: Request):
211
  body = await request.json()
212
  try:
 
217
  )
218
  except HTTPException as exc:
219
  return JSONResponse({"error": exc.detail}, status_code=exc.status_code)
220
+
221
+
222
+ with gr.Blocks(title="Confidence Buddy API") as gradio_ui:
223
+ gr.Markdown(
224
+ "## Confidence Buddy API\n"
225
+ "Flutter uses `POST /chat`. This UI is for quick manual checks.\n\n"
226
+ f"Personalities loaded: **{len(PERSONALITIES)}** · "
227
+ f"API key configured: **{bool(_api_key())}**"
228
+ )
229
+ personality = gr.Dropdown(
230
+ choices=PERSONALITY_CHOICES,
231
+ value=PERSONALITY_CHOICES[0][1] if PERSONALITY_CHOICES else None,
232
+ label="Personality",
233
+ )
234
+ chatbot = gr.Chatbot(type="messages", height=420)
235
+ msg = gr.Textbox(label="Message", placeholder="Type a message…")
236
+ clear = gr.Button("Clear")
237
+ msg.submit(ui_chat, [personality, msg, chatbot], [chatbot, msg])
238
+ clear.click(lambda: ([], ""), outputs=[chatbot, msg])
239
+
240
+
241
+ app = gr.mount_gradio_app(api, gradio_ui, path="/ui")
242
+
243
+
244
+ if __name__ == "__main__":
245
+ import uvicorn
246
+
247
+ uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "7860")))
requirements.txt CHANGED
@@ -1,3 +1,4 @@
1
  gradio>=5.0.0,<6
2
  httpx>=0.27.0
3
  spaces>=0.30.0
 
 
1
  gradio>=5.0.0,<6
2
  httpx>=0.27.0
3
  spaces>=0.30.0
4
+ uvicorn>=0.30.0