Sanyam400 commited on
Commit
76b1985
Β·
verified Β·
1 Parent(s): 8dc579c

Update app/main.py

Browse files
Files changed (1) hide show
  1. app/main.py +90 -87
app/main.py CHANGED
@@ -1,74 +1,52 @@
1
  import os, json, traceback
2
  from pathlib import Path
3
- from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
4
  from fastapi.responses import StreamingResponse, HTMLResponse, JSONResponse
5
  from fastapi.staticfiles import StaticFiles
6
  from fastapi.middleware.cors import CORSMiddleware
7
- from fastapi.exception_handlers import http_exception_handler
8
- from starlette.exceptions import HTTPException as StarletteHTTPException
9
  import config as cfg
10
  from agent_system import orchestrator
11
  from sandbox import pip_install
12
 
13
- app = FastAPI(title="PraisonChat", version="5.0.0")
14
- app.add_middleware(CORSMiddleware, allow_origins=["*"],
15
- allow_methods=["*"], allow_headers=["*"])
16
 
17
  STATIC_DIR = Path(__file__).parent / "static"
18
  app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
19
 
20
 
21
- # ── ALWAYS return JSON errors, never HTML ─────────────────────
 
22
  @app.exception_handler(StarletteHTTPException)
23
- async def json_http_exception(request, exc):
24
- return JSONResponse(
25
- status_code=exc.status_code,
26
- content={"ok": False, "detail": str(exc.detail)}
27
- )
28
 
29
  @app.exception_handler(Exception)
30
- async def json_generic_exception(request, exc):
31
- tb = traceback.format_exc()
32
- print(f"[SERVER] Unhandled: {exc}\n{tb}")
33
- return JSONResponse(
34
- status_code=500,
35
- content={"ok": False, "detail": str(exc)}
36
- )
37
 
38
 
39
- # ── Pages ──────────────────────────────────────────────────────
40
  @app.get("/", response_class=HTMLResponse)
41
  async def root():
42
  return HTMLResponse((STATIC_DIR / "index.html").read_text(encoding="utf-8"))
43
 
44
 
45
- # ── System ─────────────────────────────────────────────────────
46
  @app.get("/api/health")
47
  def health():
48
- return {"ok": True, "version": "5.0.0",
49
- "longcat_key": bool(cfg.get_longcat_key()),
50
- "telegram": bool(cfg.get_telegram_token())}
 
51
 
52
  @app.get("/api/models")
53
  def models():
54
- return {"models": [
55
  {"id":"LongCat-Flash-Lite", "name":"LongCat Flash Lite", "context":"320K","speed":"⚑ Fastest","quota":"50M/day"},
56
  {"id":"LongCat-Flash-Chat", "name":"LongCat Flash Chat", "context":"256K","speed":"πŸš€ Fast", "quota":"500K/day"},
57
  {"id":"LongCat-Flash-Thinking-2601", "name":"LongCat Flash Thinking", "context":"256K","speed":"🧠 Deep", "quota":"500K/day"},
58
  ]}
59
 
60
- @app.get("/api/builtin-tools")
61
- def builtin_tools():
62
- return {"tools":[
63
- {"name":"datetime", "description":"Real system date & time", "icon":"πŸ•"},
64
- {"name":"web_search", "description":"Real DuckDuckGo search", "icon":"πŸ”"},
65
- {"name":"fetch_page", "description":"Real webpage content", "icon":"🌐"},
66
- {"name":"run_python", "description":"Real Python code execution", "icon":"🐍"},
67
- {"name":"voice", "description":"Real MP3 audio via gTTS", "icon":"πŸ”Š"},
68
- {"name":"math", "description":"Real math & calculations", "icon":"πŸ”’"},
69
- {"name":"qr_code", "description":"QR code generation", "icon":"πŸ“±"},
70
- {"name":"charts", "description":"Charts & graphs via matplotlib","icon":"πŸ“Š"},
71
- ]}
72
 
73
  @app.get("/api/memory")
74
  def get_memory():
@@ -76,14 +54,14 @@ def get_memory():
76
  return get_all_for_api()
77
 
78
  @app.delete("/api/memory/{key}")
79
- def delete_memory(key: str):
80
  from memory import MEM_DIR
81
  safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in key)
82
  path = MEM_DIR / f"{safe}.md"
83
  if path.exists():
84
  path.unlink()
85
- return {"ok": True}
86
- return {"ok": False, "detail": "Not found"}
87
 
88
  @app.get("/api/skills")
89
  def get_skills():
@@ -91,13 +69,12 @@ def get_skills():
91
  skills = list_skills()
92
  for s in skills:
93
  s["code"] = load_skill(s["name"])[:800]
94
- return {"skills": skills}
95
 
96
  @app.delete("/api/skills/{name}")
97
- def delete_skill_route(name: str):
98
  from memory import delete_skill
99
- ok = delete_skill(name)
100
- return {"ok": ok}
101
 
102
 
103
  # ── Chat ───────────────────────────────────────────────────────
@@ -106,20 +83,19 @@ async def chat(request: Request):
106
  try:
107
  body = await request.json()
108
  except Exception:
109
- return JSONResponse(status_code=400,
110
- content={"ok":False,"detail":"Invalid JSON body"})
111
 
112
  messages = body.get("messages", [])
113
- api_key = body.get("api_key","").strip() or cfg.get_longcat_key()
114
- model = body.get("model","LongCat-Flash-Lite")
115
 
116
  if not api_key:
117
  return JSONResponse(status_code=400,
118
- content={"ok":False,"detail":"LongCat API key required. Add it in Settings."})
119
  if not messages:
120
- return JSONResponse(status_code=400,
121
- content={"ok":False,"detail":"No messages provided"})
122
 
 
123
  if api_key != cfg.get_longcat_key(): cfg.set_longcat_key(api_key)
124
  if model != cfg.get_model(): cfg.set_model(model)
125
 
@@ -127,10 +103,12 @@ async def chat(request: Request):
127
  history = messages[:-1]
128
 
129
  async def stream():
130
- async for chunk in orchestrator.stream_response(
131
- user_message, history, api_key, model
132
- ):
133
- yield f"data: {chunk}\n\n"
 
 
134
 
135
  return StreamingResponse(stream(), media_type="text/event-stream",
136
  headers={"X-Accel-Buffering":"no","Cache-Control":"no-cache","Connection":"keep-alive"})
@@ -141,15 +119,15 @@ async def chat(request: Request):
141
  async def install_package(request: Request):
142
  try:
143
  body = await request.json()
144
- except Exception:
145
- return JSONResponse(status_code=400, content={"ok":False,"detail":"Invalid JSON"})
146
- packages = body.get("packages", [])
147
- if not packages:
148
- return JSONResponse(status_code=400, content={"ok":False,"detail":"No packages"})
149
- import asyncio
150
- loop = asyncio.get_event_loop()
151
- ok, msg = await loop.run_in_executor(None, pip_install, packages)
152
- return {"ok": ok, "message": msg}
153
 
154
 
155
  # ── Telegram ───────────────────────────────────────────────────
@@ -159,24 +137,25 @@ async def telegram_webhook(request: Request, background_tasks: BackgroundTasks):
159
  try:
160
  update = await request.json()
161
  except Exception:
162
- return JSONResponse({"ok": True})
163
 
164
- print(f"[WEBHOOK] {str(update)[:150]}")
165
  api_key = cfg.get_longcat_key()
166
  model = cfg.get_model()
167
 
168
  if not api_key:
169
- msg = update.get("message", {})
170
- chat_id = msg.get("chat", {}).get("id")
171
  if chat_id:
172
  from telegram_bot import send_message
173
  await send_message(chat_id,
174
- "⚠️ No API key saved. Open the web UI β†’ Settings β†’ save your LongCat key, "
175
- "then re-connect Telegram.")
176
- return JSONResponse({"ok": True})
 
177
 
178
  background_tasks.add_task(handle_update, update, api_key, model)
179
- return JSONResponse({"ok": True})
180
 
181
 
182
  @app.post("/api/telegram/setup")
@@ -199,38 +178,56 @@ async def telegram_setup(request: Request):
199
  return JSONResponse(status_code=400,
200
  content={"ok":False,"detail":"No Telegram bot token provided"})
201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  try:
203
- from telegram_bot import get_bot_info, set_webhook
204
  bot = await get_bot_info()
205
  if not bot.get("ok"):
206
  return JSONResponse(status_code=400,
207
- content={"ok":False,"detail":f"Invalid bot token: {bot.get('description','')}"})
208
 
209
  webhook_result = {}
210
  if base_url:
211
  webhook_result = await set_webhook(base_url)
212
 
213
- return JSONResponse({"ok":True,
214
- "bot":bot.get("result",{}),
215
- "webhook":webhook_result})
216
  except Exception as e:
217
- return JSONResponse(status_code=500,
218
- content={"ok":False,"detail":str(e)})
219
 
220
 
221
  @app.get("/api/telegram/status")
222
  async def telegram_status():
223
  try:
224
  token = cfg.get_telegram_token()
 
 
 
 
 
225
  if not token:
226
  return {"connected":False,"message":"No bot token set"}
227
- from telegram_bot import get_bot_info, get_webhook_info
 
 
 
 
 
228
  bot = await get_bot_info()
229
  hook = await get_webhook_info()
230
- return {"connected":bot.get("ok",False),
231
- "bot":bot.get("result",{}),
232
- "webhook":hook.get("result",{}),
233
- "longcat_key_set":bool(cfg.get_longcat_key())}
234
  except Exception as e:
235
  return {"connected":False,"message":str(e)}
236
 
@@ -238,9 +235,15 @@ async def telegram_status():
238
  @app.delete("/api/telegram/disconnect")
239
  async def telegram_disconnect():
240
  try:
241
- from telegram_bot import delete_webhook
242
- result = await delete_webhook()
 
 
 
 
 
243
  cfg.set("telegram_token","")
244
  return {"ok":True,"result":result}
245
  except Exception as e:
246
- return JSONResponse(status_code=500, content={"ok":False,"detail":str(e)})
 
 
1
  import os, json, traceback
2
  from pathlib import Path
3
+ from fastapi import FastAPI, Request, BackgroundTasks
4
  from fastapi.responses import StreamingResponse, HTMLResponse, JSONResponse
5
  from fastapi.staticfiles import StaticFiles
6
  from fastapi.middleware.cors import CORSMiddleware
 
 
7
  import config as cfg
8
  from agent_system import orchestrator
9
  from sandbox import pip_install
10
 
11
+ app = FastAPI(title="PraisonChat", version="6.0.0")
12
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
 
13
 
14
  STATIC_DIR = Path(__file__).parent / "static"
15
  app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
16
 
17
 
18
+ # ── Always return JSON errors, never HTML ─────────────────────
19
+ from starlette.exceptions import HTTPException as StarletteHTTPException
20
  @app.exception_handler(StarletteHTTPException)
21
+ async def json_http_handler(request, exc):
22
+ return JSONResponse(status_code=exc.status_code, content={"ok":False,"detail":str(exc.detail)})
 
 
 
23
 
24
  @app.exception_handler(Exception)
25
+ async def json_generic_handler(request, exc):
26
+ print(f"[SERVER] {exc}\n{traceback.format_exc()}")
27
+ return JSONResponse(status_code=500, content={"ok":False,"detail":str(exc)})
 
 
 
 
28
 
29
 
 
30
  @app.get("/", response_class=HTMLResponse)
31
  async def root():
32
  return HTMLResponse((STATIC_DIR / "index.html").read_text(encoding="utf-8"))
33
 
34
 
 
35
  @app.get("/api/health")
36
  def health():
37
+ return {"ok":True,"version":"6.0.0",
38
+ "longcat_key":bool(cfg.get_longcat_key()),
39
+ "telegram":bool(cfg.get_telegram_token())}
40
+
41
 
42
  @app.get("/api/models")
43
  def models():
44
+ return {"models":[
45
  {"id":"LongCat-Flash-Lite", "name":"LongCat Flash Lite", "context":"320K","speed":"⚑ Fastest","quota":"50M/day"},
46
  {"id":"LongCat-Flash-Chat", "name":"LongCat Flash Chat", "context":"256K","speed":"πŸš€ Fast", "quota":"500K/day"},
47
  {"id":"LongCat-Flash-Thinking-2601", "name":"LongCat Flash Thinking", "context":"256K","speed":"🧠 Deep", "quota":"500K/day"},
48
  ]}
49
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  @app.get("/api/memory")
52
  def get_memory():
 
54
  return get_all_for_api()
55
 
56
  @app.delete("/api/memory/{key}")
57
+ def del_memory(key: str):
58
  from memory import MEM_DIR
59
  safe = "".join(c if c.isalnum() or c in "-_" else "_" for c in key)
60
  path = MEM_DIR / f"{safe}.md"
61
  if path.exists():
62
  path.unlink()
63
+ return {"ok":True}
64
+ return {"ok":False,"detail":"Not found"}
65
 
66
  @app.get("/api/skills")
67
  def get_skills():
 
69
  skills = list_skills()
70
  for s in skills:
71
  s["code"] = load_skill(s["name"])[:800]
72
+ return {"skills":skills}
73
 
74
  @app.delete("/api/skills/{name}")
75
+ def del_skill(name: str):
76
  from memory import delete_skill
77
+ return {"ok": delete_skill(name)}
 
78
 
79
 
80
  # ── Chat ───────────────────────────────────────────────────────
 
83
  try:
84
  body = await request.json()
85
  except Exception:
86
+ return JSONResponse(status_code=400, content={"ok":False,"detail":"Invalid JSON body"})
 
87
 
88
  messages = body.get("messages", [])
89
+ api_key = (body.get("api_key") or "").strip() or cfg.get_longcat_key()
90
+ model = body.get("model", "LongCat-Flash-Lite")
91
 
92
  if not api_key:
93
  return JSONResponse(status_code=400,
94
+ content={"ok":False,"detail":"LongCat API key required. Go to Settings (βš™οΈ) and paste your key."})
95
  if not messages:
96
+ return JSONResponse(status_code=400, content={"ok":False,"detail":"No messages"})
 
97
 
98
+ # Save for Telegram
99
  if api_key != cfg.get_longcat_key(): cfg.set_longcat_key(api_key)
100
  if model != cfg.get_model(): cfg.set_model(model)
101
 
 
103
  history = messages[:-1]
104
 
105
  async def stream():
106
+ try:
107
+ async for chunk in orchestrator.stream_response(user_message, history, api_key, model):
108
+ yield f"data: {chunk}\n\n"
109
+ except Exception as e:
110
+ err = json.dumps({"type":"error","message":str(e)})
111
+ yield f"data: {err}\n\n"
112
 
113
  return StreamingResponse(stream(), media_type="text/event-stream",
114
  headers={"X-Accel-Buffering":"no","Cache-Control":"no-cache","Connection":"keep-alive"})
 
119
  async def install_package(request: Request):
120
  try:
121
  body = await request.json()
122
+ packages = body.get("packages", [])
123
+ if not packages:
124
+ return JSONResponse(status_code=400, content={"ok":False,"detail":"No packages"})
125
+ import asyncio
126
+ loop = asyncio.get_event_loop()
127
+ ok, msg = await loop.run_in_executor(None, pip_install, packages)
128
+ return {"ok":ok,"message":msg}
129
+ except Exception as e:
130
+ return JSONResponse(status_code=500, content={"ok":False,"detail":str(e)})
131
 
132
 
133
  # ── Telegram ───────────────────────────────────────────────────
 
137
  try:
138
  update = await request.json()
139
  except Exception:
140
+ return JSONResponse({"ok":True})
141
 
142
+ print(f"[WEBHOOK] {str(update)[:120]}")
143
  api_key = cfg.get_longcat_key()
144
  model = cfg.get_model()
145
 
146
  if not api_key:
147
+ msg = update.get("message",{})
148
+ chat_id = msg.get("chat",{}).get("id")
149
  if chat_id:
150
  from telegram_bot import send_message
151
  await send_message(chat_id,
152
+ "⚠️ No LongCat API key saved!\n\n"
153
+ "Open the web app β†’ Settings (βš™οΈ) β†’ paste your LongCat key β†’ Save.\n"
154
+ "Or set LONGCAT_API_KEY as a HuggingFace Space Secret.")
155
+ return JSONResponse({"ok":True})
156
 
157
  background_tasks.add_task(handle_update, update, api_key, model)
158
+ return JSONResponse({"ok":True})
159
 
160
 
161
  @app.post("/api/telegram/setup")
 
178
  return JSONResponse(status_code=400,
179
  content={"ok":False,"detail":"No Telegram bot token provided"})
180
 
181
+ # Check DNS first
182
+ from telegram_bot import check_telegram_reachable, get_bot_info, set_webhook
183
+ import asyncio
184
+ loop = asyncio.get_event_loop()
185
+ reachable, dns_msg = await loop.run_in_executor(None, check_telegram_reachable)
186
+ if not reachable:
187
+ return JSONResponse(status_code=503, content={
188
+ "ok": False,
189
+ "detail": f"Cannot reach Telegram API from this server. {dns_msg}\n\n"
190
+ f"Solution: Set TELEGRAM_BOT_TOKEN as a HuggingFace Space Secret "
191
+ f"(Settings β†’ Variables and Secrets). The bot will still receive "
192
+ f"messages via webhook even if this setup fails."
193
+ })
194
+
195
  try:
 
196
  bot = await get_bot_info()
197
  if not bot.get("ok"):
198
  return JSONResponse(status_code=400,
199
+ content={"ok":False,"detail":f"Invalid bot token: {bot.get('description','check your token')}"})
200
 
201
  webhook_result = {}
202
  if base_url:
203
  webhook_result = await set_webhook(base_url)
204
 
205
+ return JSONResponse({"ok":True,"bot":bot.get("result",{}),"webhook":webhook_result})
 
 
206
  except Exception as e:
207
+ return JSONResponse(status_code=500, content={"ok":False,"detail":str(e)})
 
208
 
209
 
210
  @app.get("/api/telegram/status")
211
  async def telegram_status():
212
  try:
213
  token = cfg.get_telegram_token()
214
+ # Also check env var
215
+ env_token = os.environ.get("TELEGRAM_BOT_TOKEN","")
216
+ if env_token and not token:
217
+ cfg.set_telegram_token(env_token)
218
+ token = env_token
219
  if not token:
220
  return {"connected":False,"message":"No bot token set"}
221
+ from telegram_bot import check_telegram_reachable, get_bot_info, get_webhook_info
222
+ import asyncio
223
+ loop = asyncio.get_event_loop()
224
+ reachable, _ = await loop.run_in_executor(None, check_telegram_reachable)
225
+ if not reachable:
226
+ return {"connected":False,"message":"Token set but api.telegram.org unreachable from server. Use HF Space Secret TELEGRAM_BOT_TOKEN.","token_set":True}
227
  bot = await get_bot_info()
228
  hook = await get_webhook_info()
229
+ return {"connected":bot.get("ok",False),"bot":bot.get("result",{}),
230
+ "webhook":hook.get("result",{}),"longcat_key_set":bool(cfg.get_longcat_key())}
 
 
231
  except Exception as e:
232
  return {"connected":False,"message":str(e)}
233
 
 
235
  @app.delete("/api/telegram/disconnect")
236
  async def telegram_disconnect():
237
  try:
238
+ from telegram_bot import delete_webhook, check_telegram_reachable
239
+ import asyncio
240
+ loop = asyncio.get_event_loop()
241
+ reachable, _ = await loop.run_in_executor(None, check_telegram_reachable)
242
+ result = {}
243
+ if reachable:
244
+ result = await delete_webhook()
245
  cfg.set("telegram_token","")
246
  return {"ok":True,"result":result}
247
  except Exception as e:
248
+ cfg.set("telegram_token","")
249
+ return {"ok":True}