imbecome commited on
Commit
b5974f4
·
verified ·
1 Parent(s): e21a5c9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +152 -48
app.py CHANGED
@@ -7,7 +7,6 @@ from faster_whisper import WhisperModel
7
  from gtts import gTTS
8
 
9
  # --- CONFIG ---
10
- # Gunakan deepseek-r1:1.5b di Hugging Face agar tidak berat/lag
11
  MODEL_STRATEGY = {
12
  "deepseek": [
13
  {"provider": "ollama", "model": "deepseek-r1:1.5b"},
@@ -23,10 +22,9 @@ QUEUE = asyncio.Queue()
23
  CACHE_DIR = "./models_cache"
24
  os.makedirs(CACHE_DIR, exist_ok=True)
25
 
26
- # Load Whisper
27
- whisper_model = WhisperModel("tiny", device="cpu", compute_type="int8", download_root=CACHE_DIR)
28
 
29
- # --- UTILS ---
30
  def remove_file(path: str):
31
  if os.path.exists(path):
32
  os.remove(path)
@@ -37,8 +35,10 @@ async def call_ollama(messages, model):
37
  async with httpx.AsyncClient(timeout=TIMEOUT) as client:
38
  res = await client.post("http://127.0.0.1:11434/api/chat",
39
  json={"model": model, "messages": messages, "stream": False})
40
- return res.json()["message"]["content"] if res.status_code == 200 else None
41
- except: return None
 
 
42
 
43
  async def call_openrouter(messages, model):
44
  keys = os.getenv("OPENROUTER_KEYS", "").split(",")
@@ -78,33 +78,160 @@ async def lifespan(app: FastAPI):
78
  for w in workers: w.cancel()
79
 
80
  app = FastAPI(title="AI Gateway", lifespan=lifespan)
81
- app.add_middleware(
82
- CORSMiddleware,
83
- allow_origins=["*"],
84
- allow_methods=["*"],
85
- allow_headers=["*"],
86
- allow_credentials=True
87
- )
88
-
89
- # --- ENDPOINTS ---
90
 
 
91
  @app.get("/", response_class=HTMLResponse)
92
  async def home():
93
  return """
94
- <body style="background:#0f172a; color:white; font-family:sans-serif; display:flex; flex-direction:column; align-items:center; justify-content:center; height:100vh;">
95
- <h1 style="color:#38bdf8">AI Gateway Running</h1>
96
- <p style="color:#94a3b8">Status: <span style="color:#4ade80">Online</span></p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  </body>
 
98
  """
99
 
 
 
100
  @app.get("/v1/models")
101
  async def list_models():
102
  return {
103
  "object": "list",
104
  "data": [
105
  {"id": "deepseek", "object": "model", "owned_by": "ollama"},
106
- {"id": "gpt", "object": "model", "owned_by": "openrouter"},
107
- {"id": "gemini", "object": "model", "owned_by": "openrouter"},
108
  {"id": "whisper-1", "object": "model", "owned_by": "openai-style"},
109
  {"id": "tts-1", "object": "model", "owned_by": "openai-style"}
110
  ]
@@ -114,17 +241,9 @@ async def list_models():
114
  async def chat(request: Request):
115
  body = await request.json()
116
  model_input = body.get("model", "deepseek")
117
-
118
- alias = "deepseek"
119
- if "gpt" in model_input.lower(): alias = "gpt"
120
- elif "gemini" in model_input.lower(): alias = "gemini"
121
-
122
  future = asyncio.get_event_loop().create_future()
123
- await QUEUE.put((body.get("messages", []), alias, future))
124
  result = await future
125
-
126
- if not result: return JSONResponse({"error": "Failed to get response"}, status_code=500)
127
-
128
  return {
129
  "id": f"chatcmpl-{uuid.uuid4()}",
130
  "object": "chat.completion",
@@ -133,44 +252,29 @@ async def chat(request: Request):
133
  "choices": [{"message": {"role": "assistant", "content": result}, "finish_reason": "stop"}]
134
  }
135
 
136
- # --- AUDIO ENDPOINTS (WHISPER-1) ---
137
  @app.post("/v1/audio/transcriptions")
138
  async def transcribe(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
139
- # Buat file sementara
140
  tmp_path = os.path.join(tempfile.gettempdir(), f"{uuid.uuid4()}_{file.filename}")
141
-
142
  with open(tmp_path, "wb") as buffer:
143
  shutil.copyfileobj(file.file, buffer)
144
-
145
  try:
146
- segments, _ = whisper_model.transcribe(tmp_path)
 
147
  text = " ".join([s.text for s in segments])
148
-
149
- # Hapus file setelah diproses
150
  background_tasks.add_task(remove_file, tmp_path)
151
-
152
  return {"text": text.strip()}
153
  except Exception as e:
154
  remove_file(tmp_path)
155
  return JSONResponse({"error": str(e)}, status_code=500)
156
 
157
- # --- AUDIO ENDPOINTS (TTS-1) ---
158
  @app.post("/v1/audio/speech")
159
  async def tts(request: Request, background_tasks: BackgroundTasks):
160
  body = await request.json()
161
- text = body.get("input", "")
162
-
163
- lang = "id"
164
- voice_input = body.get("voice", "id").lower()
165
- if "en" in voice_input: lang = "en"
166
-
167
  tmp_path = os.path.join(tempfile.gettempdir(), f"{uuid.uuid4()}.mp3")
168
-
169
  try:
170
- tts_engine = gTTS(text=text, lang=lang)
171
- tts_engine.save(tmp_path)
172
  background_tasks.add_task(remove_file, tmp_path)
173
-
174
  return FileResponse(tmp_path, media_type="audio/mpeg", filename="speech.mp3")
175
  except Exception as e:
176
  return JSONResponse({"error": str(e)}, status_code=500)
 
7
  from gtts import gTTS
8
 
9
  # --- CONFIG ---
 
10
  MODEL_STRATEGY = {
11
  "deepseek": [
12
  {"provider": "ollama", "model": "deepseek-r1:1.5b"},
 
22
  CACHE_DIR = "./models_cache"
23
  os.makedirs(CACHE_DIR, exist_ok=True)
24
 
25
+ # UPGRADE: Menggunakan model 'base' untuk akurasi lebih tinggi
26
+ whisper_model = WhisperModel("base", device="cpu", compute_type="int8", download_root=CACHE_DIR)
27
 
 
28
  def remove_file(path: str):
29
  if os.path.exists(path):
30
  os.remove(path)
 
35
  async with httpx.AsyncClient(timeout=TIMEOUT) as client:
36
  res = await client.post("http://127.0.0.1:11434/api/chat",
37
  json={"model": model, "messages": messages, "stream": False})
38
+ if res.status_code == 200:
39
+ return res.json()["message"]["content"]
40
+ except: pass
41
+ return None
42
 
43
  async def call_openrouter(messages, model):
44
  keys = os.getenv("OPENROUTER_KEYS", "").split(",")
 
78
  for w in workers: w.cancel()
79
 
80
  app = FastAPI(title="AI Gateway", lifespan=lifespan)
81
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], allow_credentials=True)
 
 
 
 
 
 
 
 
82
 
83
+ # --- STARTUP UI PLAYGROUND (Premium Look) ---
84
  @app.get("/", response_class=HTMLResponse)
85
  async def home():
86
  return """
87
+ <!DOCTYPE html>
88
+ <html lang="id">
89
+ <head>
90
+ <meta charset="UTF-8">
91
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
92
+ <title>Imbecome AI Infrastructure</title>
93
+ <script src="https://cdn.tailwindcss.com"></script>
94
+ <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;800&display=swap" rel="stylesheet">
95
+ <style>
96
+ body {
97
+ background-color: #020617;
98
+ color: #f8fafc;
99
+ font-family: 'Plus Jakarta Sans', sans-serif;
100
+ background-image: radial-gradient(circle at 50% 0%, #1e1b4b 0%, #020617 70%);
101
+ }
102
+ .glass {
103
+ background: rgba(30, 41, 59, 0.5);
104
+ backdrop-filter: blur(12px);
105
+ border: 1px solid rgba(255, 255, 255, 0.1);
106
+ }
107
+ .neon-border:focus {
108
+ border-color: #38bdf8;
109
+ box-shadow: 0 0 15px rgba(56, 189, 248, 0.3);
110
+ }
111
+ .btn-gradient {
112
+ background: linear-gradient(135deg, #38bdf8 0%, #818cf8 100%);
113
+ transition: all 0.2s;
114
+ }
115
+ .btn-gradient:hover { transform: translateY(-2px); filter: brightness(1.1); }
116
+ .status-dot { width: 8px; height: 8px; background: #4ade80; border-radius: 50%; box-shadow: 0 0 10px #4ade80; }
117
+ </style>
118
+ </head>
119
+ <body class="min-h-screen p-6">
120
+ <div class="max-w-6xl mx-auto">
121
+ <nav class="flex justify-between items-center mb-12 glass p-4 rounded-2xl">
122
+ <div class="flex items-center gap-2">
123
+ <div class="bg-sky-500 w-8 h-8 rounded-lg flex items-center justify-center font-bold">I</div>
124
+ <span class="text-xl font-extrabold tracking-tight">IMBECOME <span class="text-sky-400">AI</span></span>
125
+ </div>
126
+ <div class="flex items-center gap-4 text-sm text-slate-400">
127
+ <div class="status-dot"></div> Gateway Online
128
+ </div>
129
+ </nav>
130
+
131
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
132
+ <!-- Chat Completion Card -->
133
+ <div class="lg:col-span-2 glass rounded-3xl p-8 flex flex-col">
134
+ <div class="flex justify-between items-center mb-6">
135
+ <h3 class="text-xl font-bold flex items-center gap-2">Smart AI Chat</h3>
136
+ <select id="chatModel" class="bg-slate-900 border border-slate-700 text-sm rounded-xl px-3 py-1 outline-none neon-border">
137
+ <option value="deepseek">DeepSeek R1</option>
138
+ <option value="gpt">GPT-4o Mini</option>
139
+ <option value="gemini">Gemini 2.0</option>
140
+ </select>
141
+ </div>
142
+ <div id="chatBox" class="bg-slate-900/50 border border-slate-800 rounded-2xl p-4 mb-4 overflow-y-auto h-[350px] text-sm space-y-4"></div>
143
+ <div class="relative">
144
+ <input id="chatInput" type="text" placeholder="Tanya sesuatu..." class="w-full bg-slate-900 border border-slate-700 rounded-2xl px-5 py-4 pr-16 outline-none neon-border">
145
+ <button onclick="testChat()" id="btnChat" class="absolute right-2 top-2 bottom-2 bg-sky-500 px-4 rounded-xl font-bold btn-gradient">Kirim</button>
146
+ </div>
147
+ </div>
148
+
149
+ <div class="space-y-8">
150
+ <!-- TTS Card -->
151
+ <div class="glass rounded-3xl p-6">
152
+ <h3 class="text-lg font-bold mb-4">Voice Engine (TTS)</h3>
153
+ <textarea id="ttsInput" rows="2" class="w-full bg-slate-900 border border-slate-700 rounded-xl p-3 text-sm outline-none mb-3" placeholder="Teks ke suara..."></textarea>
154
+ <button onclick="testTTS()" id="btnTTS" class="w-full bg-emerald-500 py-3 rounded-xl font-bold btn-gradient">Generate Suara</button>
155
+ <div id="ttsPlayer" class="hidden mt-4">
156
+ <audio id="audioElement" controls class="w-full h-8"></audio>
157
+ </div>
158
+ </div>
159
+
160
+ <!-- Whisper Card -->
161
+ <div class="glass rounded-3xl p-6">
162
+ <h3 class="text-lg font-bold mb-4">Whisper AI (STT)</h3>
163
+ <input type="file" id="whisperFile" class="text-xs text-slate-400 mb-4 w-full">
164
+ <button onclick="testWhisper()" id="btnWhisper" class="w-full bg-purple-600 py-3 rounded-xl font-bold btn-gradient">Transkripsi (Model Base)</button>
165
+ <div id="whisperResult" class="hidden mt-4 p-3 bg-slate-900 rounded-xl border border-slate-800 text-xs text-slate-300 max-h-32 overflow-y-auto"></div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </div>
170
+
171
+ <script>
172
+ async function testChat() {
173
+ const input = document.getElementById('chatInput');
174
+ const box = document.getElementById('chatBox');
175
+ if(!input.value) return;
176
+
177
+ const userDiv = document.createElement('div');
178
+ userDiv.className = 'ml-auto bg-sky-500/20 p-3 rounded-2xl max-w-[80%] text-right border border-sky-500/30';
179
+ userDiv.innerText = input.value;
180
+ box.appendChild(userDiv);
181
+
182
+ const text = input.value;
183
+ input.value = '';
184
+
185
+ const res = await fetch('/v1/chat/completions', {
186
+ method: 'POST',
187
+ headers: {'Content-Type': 'application/json'},
188
+ body: JSON.stringify({ model: document.getElementById('chatModel').value, messages: [{role: 'user', content: text}] })
189
+ });
190
+ const data = await res.json();
191
+
192
+ const botDiv = document.createElement('div');
193
+ botDiv.className = 'mr-auto bg-slate-800 p-3 rounded-2xl max-w-[80%] border border-slate-700';
194
+ botDiv.innerText = data.choices[0].message.content;
195
+ box.appendChild(botDiv);
196
+ box.scrollTop = box.scrollHeight;
197
+ }
198
+
199
+ async function testTTS() {
200
+ const res = await fetch('/v1/audio/speech', {
201
+ method: 'POST',
202
+ headers: {'Content-Type': 'application/json'},
203
+ body: JSON.stringify({ input: document.getElementById('ttsInput').value, voice: 'id' })
204
+ });
205
+ const blob = await res.blob();
206
+ document.getElementById('audioElement').src = URL.createObjectURL(blob);
207
+ document.getElementById('ttsPlayer').classList.remove('hidden');
208
+ }
209
+
210
+ async function testWhisper() {
211
+ const file = document.getElementById('whisperFile').files[0];
212
+ if(!file) return;
213
+ document.getElementById('whisperResult').classList.remove('hidden');
214
+ document.getElementById('whisperResult').innerText = "Sedang memproses (Model Base)...";
215
+
216
+ const fd = new FormData();
217
+ fd.append('file', file);
218
+ const res = await fetch('/v1/audio/transcriptions', { method: 'POST', body: fd });
219
+ const data = await res.json();
220
+ document.getElementById('whisperResult').innerText = data.text || "Gagal transkripsi.";
221
+ }
222
+ </script>
223
  </body>
224
+ </html>
225
  """
226
 
227
+ # --- API ENDPOINTS ---
228
+
229
  @app.get("/v1/models")
230
  async def list_models():
231
  return {
232
  "object": "list",
233
  "data": [
234
  {"id": "deepseek", "object": "model", "owned_by": "ollama"},
 
 
235
  {"id": "whisper-1", "object": "model", "owned_by": "openai-style"},
236
  {"id": "tts-1", "object": "model", "owned_by": "openai-style"}
237
  ]
 
241
  async def chat(request: Request):
242
  body = await request.json()
243
  model_input = body.get("model", "deepseek")
 
 
 
 
 
244
  future = asyncio.get_event_loop().create_future()
245
+ await QUEUE.put((body.get("messages", []), "deepseek", future))
246
  result = await future
 
 
 
247
  return {
248
  "id": f"chatcmpl-{uuid.uuid4()}",
249
  "object": "chat.completion",
 
252
  "choices": [{"message": {"role": "assistant", "content": result}, "finish_reason": "stop"}]
253
  }
254
 
 
255
  @app.post("/v1/audio/transcriptions")
256
  async def transcribe(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
 
257
  tmp_path = os.path.join(tempfile.gettempdir(), f"{uuid.uuid4()}_{file.filename}")
 
258
  with open(tmp_path, "wb") as buffer:
259
  shutil.copyfileobj(file.file, buffer)
 
260
  try:
261
+ # PERBAIKAN: Gunakan beam_size=5 dan vad_filter=True untuk hasil akurat
262
+ segments, _ = whisper_model.transcribe(tmp_path, beam_size=5, language="id", vad_filter=True)
263
  text = " ".join([s.text for s in segments])
 
 
264
  background_tasks.add_task(remove_file, tmp_path)
 
265
  return {"text": text.strip()}
266
  except Exception as e:
267
  remove_file(tmp_path)
268
  return JSONResponse({"error": str(e)}, status_code=500)
269
 
 
270
  @app.post("/v1/audio/speech")
271
  async def tts(request: Request, background_tasks: BackgroundTasks):
272
  body = await request.json()
273
+ text, lang = body.get("input", ""), body.get("voice", "id")
 
 
 
 
 
274
  tmp_path = os.path.join(tempfile.gettempdir(), f"{uuid.uuid4()}.mp3")
 
275
  try:
276
+ gTTS(text=text, lang=lang).save(tmp_path)
 
277
  background_tasks.add_task(remove_file, tmp_path)
 
278
  return FileResponse(tmp_path, media_type="audio/mpeg", filename="speech.mp3")
279
  except Exception as e:
280
  return JSONResponse({"error": str(e)}, status_code=500)