Spaces:
Running
Running
| import os | |
| import time | |
| import base64 | |
| import random | |
| import json | |
| import requests | |
| from datetime import datetime, timedelta, timezone | |
| from flask import Flask, request, jsonify, Response | |
| app = Flask(__name__) | |
| app.secret_key = os.getenv("FLASK_SECRET_KEY") | |
| # ==== API KEYS ==== | |
| GROQ_API_KEY_1 = os.getenv("GROQ_API_KEY_1") | |
| GROQ_API_KEY_2 = os.getenv("GROQ_API_KEY_2") | |
| GROQ_API_KEY_3 = os.getenv("GROQ_API_KEY_3") | |
| SERPAPI_KEY = os.getenv("SERPAPI_KEY") | |
| # ==== URL ==== | |
| GROQ_URL_CHAT = "https://api.groq.com/openai/v1/chat/completions" | |
| GROQ_URL_TTS = "https://api.groq.com/openai/v1/audio/speech" | |
| GROQ_URL_STT = "https://api.groq.com/openai/v1/audio/transcriptions" | |
| # ==== SYSTEM PROMPT ==== | |
| SYSTEM_PROMPT = ( | |
| "You are Talk GTE β a friendly AI assistant created by Vibow AI. " | |
| "GTE means Generative Text Expert in Vibow AI. " | |
| "Vibow AI created in 29 June 2025 and Talk GTE created in 23 October 2025. " | |
| "Talk GTE have approximately 1 trillion parameters. " | |
| "Stay positive, kind, and expert. " | |
| "Always capitalize the first letter of sentences. " | |
| "If the user requests code, always use triple backticks (```). " | |
| "Be concise, neutral, and accurate. " | |
| "Sometimes use emoji but relevant." | |
| ) | |
| # ========================= | |
| # π€ STT | |
| # ========================= | |
| def transcribe_audio(file_path: str) -> str: | |
| try: | |
| headers = {"Authorization": f"Bearer {GROQ_API_KEY_2}"} | |
| files = { | |
| "file": (os.path.basename(file_path), open(file_path, "rb"), "audio/wav"), | |
| "model": (None, "whisper-large-v3-turbo"), | |
| } | |
| res = requests.post(GROQ_URL_STT, headers=headers, files=files, timeout=60) | |
| res.raise_for_status() | |
| return res.json().get("text", "") | |
| except Exception as e: | |
| print(f"[STT Error] {e}") | |
| return "" | |
| finally: | |
| if os.path.exists(file_path): | |
| os.remove(file_path) | |
| # ========================= | |
| # π TTS | |
| # ========================= | |
| def text_to_speech(text: str) -> bytes: | |
| try: | |
| headers = {"Authorization": f"Bearer {GROQ_API_KEY_3}"} | |
| data = {"model": "playai-tts", "voice": "Celeste-PlayAI", "input": text} | |
| res = requests.post(GROQ_URL_TTS, headers=headers, json=data, timeout=60) | |
| if res.status_code != 200: | |
| print(f"[TTS Error] {res.text}") | |
| return b"" | |
| return res.content | |
| except Exception as e: | |
| print(f"[TTS Exception] {e}") | |
| return b"" | |
| # ========================= | |
| # π SerpAPI with 3 images | |
| # ========================= | |
| def serpapi_search(query: str, location=None, num_results=3): | |
| url = "https://serpapi.com/search.json" | |
| params = { | |
| "q": query, | |
| "location": location or "", | |
| "engine": "google", | |
| "api_key": SERPAPI_KEY, | |
| "num": num_results | |
| } | |
| try: | |
| r = requests.get(url, params=params, timeout=10) | |
| r.raise_for_status() | |
| data = r.json() | |
| results = [] | |
| images = [] | |
| if "organic_results" in data: | |
| for item in data["organic_results"][:num_results]: | |
| results.append({ | |
| "title": item.get("title", ""), | |
| "snippet": item.get("snippet", ""), | |
| "link": item.get("link", "") | |
| }) | |
| if "images_results" in data: | |
| for img in data["images_results"][:3]: | |
| images.append(img.get("thumbnail", "")) | |
| return {"results": results, "images": images} | |
| except Exception as e: | |
| print(f"[SerpAPI Error] {e}") | |
| return {"results": [], "images": []} | |
| # ========================= | |
| # π¬ Stream Chat | |
| # ========================= | |
| def stream_chat(prompt: str, history=None): | |
| wib = timezone(timedelta(hours=7)) | |
| now = datetime.now(wib) | |
| sys_prompt = SYSTEM_PROMPT + f"\nCurrent time: {now.strftime('%A, %d %B %Y β %H:%M:%S WIB')}." | |
| messages = [{"role": "system", "content": sys_prompt}] | |
| if history: | |
| messages += history | |
| messages.append({"role": "user", "content": prompt}) | |
| payload = { | |
| "model": "moonshotai/kimi-k2-instruct-0905", | |
| "messages": messages, | |
| "temperature": 0.7, | |
| "max_tokens": 3600, | |
| "stream": True, | |
| } | |
| headers = {"Authorization": f"Bearer {GROQ_API_KEY_1}"} | |
| with requests.post(GROQ_URL_CHAT, headers=headers, json=payload, stream=True) as r: | |
| for line in r.iter_lines(): | |
| if not line: | |
| continue | |
| line = line.decode() | |
| if line.startswith("data: "): | |
| data = line[6:] | |
| if data == "[DONE]": | |
| break | |
| try: | |
| delta = json.loads(data)["choices"][0]["delta"].get("content", "") | |
| if delta: | |
| yield delta | |
| except: | |
| continue | |
| # ========================= | |
| # π Chat Endpoint (Text + Voice) | |
| # ========================= | |
| def chat(): | |
| # π€ Voice Mode | |
| if "audio" in request.files: | |
| audio = request.files["audio"] | |
| temp = f"/tmp/{time.time()}_{random.randint(1000,9999)}.wav" | |
| audio.save(temp) | |
| user_text = transcribe_audio(temp) | |
| if not user_text: | |
| return jsonify({"error": "Failed STT"}), 500 | |
| keywords = ["hotel", "mall", "resort", "villa", "tempat wisata"] | |
| if any(k in user_text.lower() for k in keywords): | |
| serp = serpapi_search(user_text) | |
| text = "\n".join([f"{r['title']} β {r['snippet']} β {r['link']}" for r in serp["results"]]) | |
| imgs = " ".join(serp["images"]) | |
| user_text = f"{user_text}\n\nGoogle Results:\n{text}\n\nImages:\n{imgs}\n\nExplain & recommend." | |
| ai = "".join(chunk for chunk in stream_chat(user_text)) | |
| audio_bytes = text_to_speech(ai) | |
| return jsonify({ | |
| "mode": "voice", | |
| "transcript": user_text, | |
| "reply_text": ai, | |
| "audio_base64": "data:audio/mp3;base64," + base64.b64encode(audio_bytes).decode() | |
| }) | |
| # π¬ Text Mode | |
| data = request.get_json(force=True) | |
| prompt = data.get("prompt", "") | |
| history = data.get("history", []) | |
| keywords = ["hotel", "mall", "resort", "villa", "tempat wisata"] | |
| if any(k in prompt.lower() for k in keywords): | |
| serp = serpapi_search(prompt) | |
| text = "\n".join([f"{r['title']} β {r['snippet']} β {r['link']}" for r in serp["results"]]) | |
| imgs = " ".join(serp["images"]) | |
| prompt = f"{prompt}\n\nGoogle Results:\n{text}\n\nImages:\n{imgs}\n\nExplain & recommend." | |
| def generate(): | |
| for chunk in stream_chat(prompt, history): | |
| yield chunk | |
| return Response(generate(), mimetype="text/plain") | |
| # ========================= | |
| # βΆοΈ Run | |
| # ========================= | |
| if __name__ == "__main__": | |
| print("π Vibow Talk GTE Server Running") | |
| app.run(host="0.0.0.0", port=7860, debug=True, threaded=True) |