119 lines
4.2 KiB
Python
119 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Basti Voice Chat Server — serves the web UI and proxies to Hermes API."""
|
|
|
|
import http.server
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.request
|
|
import urllib.error
|
|
import uuid
|
|
import socketserver
|
|
from pathlib import Path
|
|
from urllib.parse import urlparse, parse_qs
|
|
|
|
PORT = int(os.environ.get("VOICE_CHAT_PORT", "8891"))
|
|
API_PORT = 8642
|
|
API_KEY = "866c20bd9c34d92bd34f1bf21cff6f52c40840da8ec6c562afac22dfc3f9ac8e"
|
|
STATIC_DIR = Path(__file__).parent
|
|
|
|
# Session history (in-memory, simple)
|
|
sessions = {}
|
|
|
|
class VoiceChatHandler(http.server.SimpleHTTPRequestHandler):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, directory=str(STATIC_DIR), **kwargs)
|
|
|
|
def do_POST(self):
|
|
if self.path == "/api/chat":
|
|
self.handle_chat()
|
|
elif self.path == "/api/chat/save":
|
|
self.handle_save()
|
|
else:
|
|
self.send_error(404)
|
|
|
|
def handle_chat(self):
|
|
content_len = int(self.headers.get("Content-Length", 0))
|
|
body = self.rfile.read(content_len)
|
|
try:
|
|
data = json.loads(body)
|
|
user_text = data.get("text", "").strip()
|
|
except:
|
|
self.send_error(400, "Invalid JSON")
|
|
return
|
|
|
|
if not user_text:
|
|
self.send_error(400, "Missing 'text' field")
|
|
return
|
|
|
|
# Forward to Hermes API server
|
|
api_url = f"http://localhost:{API_PORT}/v1/chat/completions"
|
|
api_body = json.dumps({
|
|
"model": "hermes",
|
|
"messages": [
|
|
{"role": "system", "content": "Du bist Basti, Chef vom Dienst der digitalen Redaktion von brettspiel-news.de. Antworte auf Deutsch, freundlich und hilfreich. Halte deine Antworten kurz — maximal 3-4 Sätze. Dein Chef Daniel spricht mit dir."},
|
|
{"role": "user", "content": user_text}
|
|
],
|
|
"max_tokens": 300,
|
|
"temperature": 0.7
|
|
}).encode("utf-8")
|
|
|
|
req = urllib.request.Request(api_url, data=api_body, headers={
|
|
"Content-Type": "application/json",
|
|
"Authorization": f"Bearer {API_KEY}"
|
|
})
|
|
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=45) as resp:
|
|
result = json.loads(resp.read().decode())
|
|
reply = result.get("choices", [{}])[0].get("message", {}).get("content", "")
|
|
if not reply:
|
|
reply = "Hmm, da kam nichts zurück. Versuch's nochmal?"
|
|
except urllib.error.HTTPError as e:
|
|
err_body = e.read().decode() if e.fp else str(e)
|
|
reply = f"API-Fehler {e.code}: {err_body[:200]}"
|
|
except Exception as e:
|
|
reply = f"Server-Fehler: {str(e)[:200]}"
|
|
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.send_header("Access-Control-Allow-Origin", "*")
|
|
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps({"response": reply}).encode())
|
|
|
|
def handle_save(self):
|
|
"""Save message pair for session continuity."""
|
|
content_len = int(self.headers.get("Content-Length", 0))
|
|
body = self.rfile.read(content_len)
|
|
try:
|
|
data = json.loads(body)
|
|
except:
|
|
self.send_error(400)
|
|
return
|
|
|
|
# Simple append to history (not used by API currently)
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps({"ok": True}).encode())
|
|
|
|
def do_OPTIONS(self):
|
|
self.send_response(200)
|
|
self.send_header("Access-Control-Allow-Origin", "*")
|
|
self.send_header("Access-Control-Allow-Methods", "POST, OPTIONS")
|
|
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
|
self.end_headers()
|
|
|
|
def log_message(self, format, *args):
|
|
# Quiet logging
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
with socketserver.TCPServer(("0.0.0.0", PORT), VoiceChatHandler) as httpd:
|
|
print(f"🎤 Basti Voice Chat running on http://0.0.0.0:{PORT}")
|
|
print(f" External: http://185.162.249.159:{PORT}")
|
|
sys.stdout.flush()
|
|
httpd.serve_forever()
|