Antaram commited on
Commit
21edb5c
·
verified ·
1 Parent(s): c7a5b65

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +163 -109
main.py CHANGED
@@ -2,18 +2,23 @@ import uuid
2
  import json
3
  import asyncio
4
  import httpx
 
 
5
  from pathlib import Path
 
6
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request, UploadFile, File, HTTPException
7
- from fastapi.responses import HTMLResponse
8
  from fastapi.staticfiles import StaticFiles
9
  from fastapi.templating import Jinja2Templates
10
  from fastapi.middleware.cors import CORSMiddleware
 
11
 
12
- app = FastAPI(title="Antaram Chat AI")
13
 
14
  # --- Configuration ---
15
  AI_URL = "https://sarveshpatel-unsloth0-6bqwen.hf.space/chat/raw"
16
  MAX_TOKENS = 30000
 
17
 
18
  # CORS
19
  app.add_middleware(
@@ -32,163 +37,212 @@ UPLOAD_DIR.mkdir(exist_ok=True)
32
  templates = Jinja2Templates(directory="templates")
33
  app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
34
 
35
- # State
36
- active_rooms = {} # {room_id: [websocket_objects]}
37
- room_files = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  # --- Routes ---
40
 
41
  @app.get("/", response_class=HTMLResponse)
42
  async def home(request: Request):
43
- """Home page"""
44
  return templates.TemplateResponse("index.html", {"request": request, "room_id": None})
45
 
46
  @app.get("/room/{room_id}", response_class=HTMLResponse)
47
  async def dynamic_room(request: Request, room_id: str):
48
- """Dynamic route for sharing links directly"""
49
  return templates.TemplateResponse("index.html", {"request": request, "room_id": room_id.upper()})
50
 
51
  @app.post("/create-room")
52
  async def create_room():
53
  room_id = str(uuid.uuid4())[:8].upper()
54
- active_rooms[room_id] = []
55
- room_files[room_id] = []
56
  return {"room_id": room_id, "success": True}
57
 
58
  @app.post("/upload-file/{room_id}")
59
  async def upload_file(room_id: str, file: UploadFile = File(...)):
60
- # Allow upload if room is active OR just created
61
- if room_id not in room_files:
62
- if room_id in active_rooms:
63
- room_files[room_id] = []
64
- else:
65
- # Auto-create entry if connecting via file upload first (rare edge case)
66
- room_files[room_id] = []
67
-
68
  file_ext = Path(file.filename).suffix
69
  unique_name = f"{uuid.uuid4().hex}{file_ext}"
70
  file_path = UPLOAD_DIR / unique_name
71
-
72
  content = await file.read()
73
  with open(file_path, "wb") as f:
74
  f.write(content)
75
 
76
- file_info = {
77
- "filename": unique_name,
78
- "original_name": file.filename,
79
- "file_type": file.content_type or "application/octet-stream",
80
- "size": len(content),
81
- "file_url": f"/uploads/{unique_name}"
82
- }
83
- room_files[room_id].append(file_info)
84
- return {"success": True, "file_info": file_info}
85
-
86
- # --- AI Logic ---
87
-
88
- async def stream_antaram_ai(room_id: str, prompt: str):
89
- """Streams AI response to a specific room"""
90
-
91
- # 1. Notify room that AI is thinking
92
- await broadcast_to_room(room_id, json.dumps({
93
- "type": "ai_start",
94
- "username": "Antaram AI"
95
- }))
96
-
97
- full_response = ""
98
-
99
- try:
100
- async with httpx.AsyncClient(timeout=120) as client:
101
- # Removing the trigger word for cleaner prompt
102
- clean_prompt = prompt.replace("@antaram.ai", "").strip()
103
-
104
- async with client.stream("POST", AI_URL, json={"prompt": clean_prompt, "max_tokens": MAX_TOKENS}) as response:
105
- async for chunk in response.aiter_text():
106
- # Strip <think> tags as requested
107
- clean_chunk = chunk.replace("<think>", "").replace("</think>", "")
108
- if clean_chunk:
109
- full_response += clean_chunk
110
- # Stream chunk to WebSocket
111
- await broadcast_to_room(room_id, json.dumps({
112
- "type": "ai_chunk",
113
- "username": "Antaram AI",
114
- "chunk": clean_chunk
115
- }))
116
- except Exception as e:
117
- await broadcast_to_room(room_id, json.dumps({
118
- "type": "error",
119
- "message": f"AI Error: {str(e)}"
120
- }))
121
-
122
- # 2. Notify finished
123
- await broadcast_to_room(room_id, json.dumps({
124
- "type": "ai_end",
125
- "username": "Antaram AI"
126
- }))
127
 
128
  # --- WebSocket ---
129
 
130
  @app.websocket("/ws/{room_id}")
131
  async def websocket_endpoint(websocket: WebSocket, room_id: str):
132
  room_id = room_id.upper()
 
133
 
134
- # Init room if accessed via direct link and not created yet
135
  if room_id not in active_rooms:
136
  active_rooms[room_id] = []
137
- room_files[room_id] = []
138
-
139
- await websocket.accept()
140
  active_rooms[room_id].append(websocket)
141
 
142
- # Notify Join
143
- await broadcast_to_room(room_id, json.dumps({
144
- "type": "system",
145
- "message": "User joined.",
146
- "userCount": len(active_rooms[room_id])
147
- }))
148
 
149
  try:
150
  while True:
151
  data = await websocket.receive_text()
152
- message_data = json.loads(data)
153
 
154
- user_text = message_data.get("text", "")
155
- username = message_data.get("username", "Anonymous")
156
 
157
- # 1. Broadcast User Message Immediately
158
- await broadcast_to_room(room_id, json.dumps({
159
- "type": message_data.get("type", "message"),
160
- "username": username,
161
- "text": user_text,
162
- "file": message_data.get("file", None)
163
- }))
164
-
165
- # 2. Check for AI Trigger
166
- if "@antaram.ai" in user_text.lower():
167
- # Start AI processing in background (non-blocking)
168
- asyncio.create_task(stream_antaram_ai(room_id, user_text))
169
-
170
- except WebSocketDisconnect:
171
- if room_id in active_rooms and websocket in active_rooms[room_id]:
172
- active_rooms[room_id].remove(websocket)
173
- # Cleanup if empty
174
- if not active_rooms[room_id]:
175
- # Optional: Keep room alive for a bit or delete
176
- pass
177
- else:
178
  await broadcast_to_room(room_id, json.dumps({
179
- "type": "system",
180
- "message": "User left.",
181
- "userCount": len(active_rooms[room_id])
182
  }))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
  async def broadcast_to_room(room_id: str, message: str):
185
  if room_id in active_rooms:
186
- # Create a copy of the list to iterate safely
187
  for connection in list(active_rooms[room_id]):
188
  try:
189
  await connection.send_text(message)
190
- except RuntimeError:
191
- # Connection likely closed
192
  pass
193
 
194
  if __name__ == "__main__":
 
2
  import json
3
  import asyncio
4
  import httpx
5
+ import sqlite3
6
+ import time
7
  from pathlib import Path
8
+ from datetime import datetime
9
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request, UploadFile, File, HTTPException
10
+ from fastapi.responses import HTMLResponse, JSONResponse
11
  from fastapi.staticfiles import StaticFiles
12
  from fastapi.templating import Jinja2Templates
13
  from fastapi.middleware.cors import CORSMiddleware
14
+ from typing import List, Dict, Set
15
 
16
+ app = FastAPI(title="Antaram Chat AI Pro")
17
 
18
  # --- Configuration ---
19
  AI_URL = "https://sarveshpatel-unsloth0-6bqwen.hf.space/chat/raw"
20
  MAX_TOKENS = 30000
21
+ SYSTEM_PROMPT = "You are Antaram AI. Answer concisely, accurately, and professionally. Use emojis very sparingly (max 1 per response). Do not hallucinate."
22
 
23
  # CORS
24
  app.add_middleware(
 
37
  templates = Jinja2Templates(directory="templates")
38
  app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
39
 
40
+ # --- Database Setup (SQLite) ---
41
+ DB_PATH = "chat.db"
42
+
43
+ def init_db():
44
+ with sqlite3.connect(DB_PATH) as conn:
45
+ conn.execute("""
46
+ CREATE TABLE IF NOT EXISTS messages (
47
+ id TEXT PRIMARY KEY,
48
+ room_id TEXT,
49
+ username TEXT,
50
+ content TEXT,
51
+ file_url TEXT,
52
+ file_type TEXT,
53
+ reply_to_id TEXT,
54
+ reply_content TEXT,
55
+ timestamp REAL
56
+ )
57
+ """)
58
+ conn.commit()
59
+
60
+ init_db()
61
+
62
+ # --- State ---
63
+ active_rooms: Dict[str, List[WebSocket]] = {}
64
+ active_users: Dict[str, Set[str]] = {} # room_id -> set of usernames
65
+
66
+ # --- Helpers ---
67
+
68
+ def save_message(room_id, username, content, file_data=None, reply_to_id=None, reply_content=None):
69
+ msg_id = str(uuid.uuid4())
70
+ ts = time.time()
71
+ file_url = file_data['file_url'] if file_data else None
72
+ file_type = file_data['file_type'] if file_data else None
73
+
74
+ with sqlite3.connect(DB_PATH) as conn:
75
+ conn.execute(
76
+ "INSERT INTO messages VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
77
+ (msg_id, room_id, username, content, file_url, file_type, reply_to_id, reply_content, ts)
78
+ )
79
+ return {
80
+ "id": msg_id, "username": username, "text": content,
81
+ "file": file_data, "reply_to": reply_to_id, "reply_content": reply_content,
82
+ "timestamp": ts, "type": "message"
83
+ }
84
+
85
+ def get_history(room_id, limit=50):
86
+ with sqlite3.connect(DB_PATH) as conn:
87
+ conn.row_factory = sqlite3.Row
88
+ cursor = conn.execute(
89
+ "SELECT * FROM messages WHERE room_id = ? ORDER BY timestamp ASC LIMIT ?",
90
+ (room_id, limit)
91
+ )
92
+ rows = cursor.fetchall()
93
+
94
+ history = []
95
+ for row in rows:
96
+ file_data = None
97
+ if row['file_url']:
98
+ file_data = {"file_url": row['file_url'], "file_type": row['file_type'], "original_name": "File"}
99
+
100
+ history.append({
101
+ "type": "message",
102
+ "id": row['id'],
103
+ "username": row['username'],
104
+ "text": row['content'],
105
+ "file": file_data,
106
+ "reply_to": row['reply_to_id'],
107
+ "reply_content": row['reply_content'],
108
+ "timestamp": row['timestamp']
109
+ })
110
+ return history
111
+
112
+ # --- AI Logic ---
113
+
114
+ async def stream_antaram_ai(room_id: str, prompt: str, context_msg: str = None):
115
+ """Streams AI response. If context_msg is provided (reply), it's added to prompt."""
116
+
117
+ # 1. Notify Start
118
+ await broadcast_to_room(room_id, json.dumps({"type": "ai_start", "username": "Antaram AI"}))
119
+
120
+ final_prompt = f"{SYSTEM_PROMPT}\n\n"
121
+ if context_msg:
122
+ final_prompt += f"Context (User replied to this): {context_msg}\n"
123
+
124
+ clean_user_prompt = prompt.replace("@antaram.ai", "").strip()
125
+ final_prompt += f"User Query: {clean_user_prompt}"
126
+
127
+ full_response = ""
128
+
129
+ try:
130
+ async with httpx.AsyncClient(timeout=60) as client:
131
+ async with client.stream("POST", AI_URL, json={"prompt": final_prompt, "max_tokens": MAX_TOKENS}) as response:
132
+ async for chunk in response.aiter_text():
133
+ clean_chunk = chunk.replace("<think>", "").replace("</think>", "")
134
+ if clean_chunk:
135
+ full_response += clean_chunk
136
+ await broadcast_to_room(room_id, json.dumps({
137
+ "type": "ai_chunk", "chunk": clean_chunk
138
+ }))
139
+ except Exception as e:
140
+ await broadcast_to_room(room_id, json.dumps({"type": "error", "message": "AI Unreachable"}))
141
+
142
+ # 2. Notify End & Save AI response to DB
143
+ save_message(room_id, "Antaram AI", full_response)
144
+ await broadcast_to_room(room_id, json.dumps({"type": "ai_end"}))
145
 
146
  # --- Routes ---
147
 
148
  @app.get("/", response_class=HTMLResponse)
149
  async def home(request: Request):
 
150
  return templates.TemplateResponse("index.html", {"request": request, "room_id": None})
151
 
152
  @app.get("/room/{room_id}", response_class=HTMLResponse)
153
  async def dynamic_room(request: Request, room_id: str):
 
154
  return templates.TemplateResponse("index.html", {"request": request, "room_id": room_id.upper()})
155
 
156
  @app.post("/create-room")
157
  async def create_room():
158
  room_id = str(uuid.uuid4())[:8].upper()
 
 
159
  return {"room_id": room_id, "success": True}
160
 
161
  @app.post("/upload-file/{room_id}")
162
  async def upload_file(room_id: str, file: UploadFile = File(...)):
 
 
 
 
 
 
 
 
163
  file_ext = Path(file.filename).suffix
164
  unique_name = f"{uuid.uuid4().hex}{file_ext}"
165
  file_path = UPLOAD_DIR / unique_name
 
166
  content = await file.read()
167
  with open(file_path, "wb") as f:
168
  f.write(content)
169
 
170
+ return {"success": True, "file_info": {
171
+ "file_url": f"/uploads/{unique_name}",
172
+ "file_type": file.content_type,
173
+ "original_name": file.filename
174
+ }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
  # --- WebSocket ---
177
 
178
  @app.websocket("/ws/{room_id}")
179
  async def websocket_endpoint(websocket: WebSocket, room_id: str):
180
  room_id = room_id.upper()
181
+ await websocket.accept()
182
 
 
183
  if room_id not in active_rooms:
184
  active_rooms[room_id] = []
185
+ active_users[room_id] = set()
186
+
 
187
  active_rooms[room_id].append(websocket)
188
 
189
+ # 1. Send History First
190
+ history = get_history(room_id)
191
+ await websocket.send_text(json.dumps({"type": "history", "data": history}))
 
 
 
192
 
193
  try:
194
  while True:
195
  data = await websocket.receive_text()
196
+ msg_data = json.loads(data)
197
 
198
+ msg_type = msg_data.get("type")
199
+ username = msg_data.get("username", "Guest")
200
 
201
+ # Handle Join (to update user list)
202
+ if msg_type == "join":
203
+ active_users[room_id].add(username)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  await broadcast_to_room(room_id, json.dumps({
205
+ "type": "system",
206
+ "message": f"{username} joined.",
207
+ "users": list(active_users[room_id])
208
  }))
209
+ continue
210
+
211
+ # Handle Message
212
+ if msg_type == "message":
213
+ text = msg_data.get("text", "")
214
+ reply_to = msg_data.get("reply_to")
215
+ reply_content = msg_data.get("reply_content")
216
+
217
+ # Save to DB
218
+ saved_msg = save_message(room_id, username, text, msg_data.get("file"), reply_to, reply_content)
219
+
220
+ # Broadcast
221
+ await broadcast_to_room(room_id, json.dumps(saved_msg))
222
+
223
+ # Check AI
224
+ if "@antaram.ai" in text.lower():
225
+ # If user is replying to a message AND mentioning AI, pass that context
226
+ asyncio.create_task(stream_antaram_ai(room_id, text, reply_content))
227
+
228
+ except WebSocketDisconnect:
229
+ if room_id in active_rooms:
230
+ if websocket in active_rooms[room_id]:
231
+ active_rooms[room_id].remove(websocket)
232
+ # We don't remove user immediately from set to keep history context,
233
+ # or you can remove if you want strict online status.
234
+ await broadcast_to_room(room_id, json.dumps({
235
+ "type": "system",
236
+ "message": "User left.",
237
+ "users": list(active_users[room_id])
238
+ }))
239
 
240
  async def broadcast_to_room(room_id: str, message: str):
241
  if room_id in active_rooms:
 
242
  for connection in list(active_rooms[room_id]):
243
  try:
244
  await connection.send_text(message)
245
+ except:
 
246
  pass
247
 
248
  if __name__ == "__main__":