169 lines
6.6 KiB
Python
Executable File
169 lines
6.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
WEVIA Web Chat API — Playwright headless multi-chat ILLIMITÉ
|
|
Port: 8902 — Same architecture as deepseek-web-api.py (port 8901)
|
|
"""
|
|
import json, os, time, threading, traceback
|
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
|
|
PORT = 8902
|
|
SESSION_DIR = '/opt/weval-l99/webchat-session'
|
|
LOCK = threading.Lock()
|
|
|
|
browser = None
|
|
context = None
|
|
pages = {}
|
|
ready = False
|
|
last_error = ''
|
|
|
|
SERVICES = {
|
|
"copilot": {"url": "https://copilot.microsoft.com", "input": "textarea#userInput,textarea", "wait": 10},
|
|
"huggingchat": {"url": "https://huggingface.co/chat", "input": "textarea[placeholder]", "wait": 12},
|
|
"duckduckgo": {"url": "https://duck.ai", "input": "textarea", "wait": 15},
|
|
"qwen": {"url": "https://chat.qwen.ai", "input": "textarea,div[contenteditable]", "wait": 12},
|
|
"lechat": {"url": "https://chat.mistral.ai", "input": "textarea", "wait": 12},
|
|
"perplexity": {"url": "https://perplexity.ai", "input": "textarea[placeholder],textarea", "wait": 12},
|
|
"meta": {"url": "https://www.meta.ai", "input": "div[contenteditable],textarea", "wait": 10},
|
|
"deepseek": {"url": "https://chat.deepseek.com", "input": "textarea", "wait": 15},
|
|
"claude": {"url": "https://claude.ai/new", "input": "div[contenteditable]", "wait": 15},
|
|
}
|
|
|
|
def init_browser():
|
|
global browser, context, ready, last_error
|
|
try:
|
|
from playwright.sync_api import sync_playwright
|
|
pw = sync_playwright().start()
|
|
browser = pw.chromium.launch(
|
|
headless=True,
|
|
args=['--no-sandbox', '--disable-gpu', '--disable-blink-features=AutomationControlled']
|
|
)
|
|
os.makedirs(SESSION_DIR, exist_ok=True)
|
|
context = browser.new_context(
|
|
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
|
)
|
|
ready = True
|
|
print(f"[WEVIA WebChat] Browser ready")
|
|
except Exception as e:
|
|
last_error = str(e)
|
|
print(f"[ERR] {e}")
|
|
|
|
def chat(service, message):
|
|
with LOCK:
|
|
if not ready:
|
|
return {"error": "not ready", "detail": last_error}
|
|
cfg = SERVICES.get(service)
|
|
if not cfg:
|
|
return {"error": f"unknown: {service}", "available": list(SERVICES.keys())}
|
|
try:
|
|
# Open page if needed
|
|
if service not in pages or pages[service].is_closed():
|
|
p = context.new_page()
|
|
p.goto(cfg["url"], timeout=30000, wait_until="domcontentloaded")
|
|
time.sleep(3)
|
|
pages[service] = p
|
|
|
|
page = pages[service]
|
|
|
|
# Find textarea
|
|
ta = page.query_selector(cfg["input"])
|
|
if not ta:
|
|
ta = page.query_selector("textarea")
|
|
if not ta:
|
|
return {"error": "no input", "url": page.url, "title": page.title()}
|
|
|
|
# Count existing messages before
|
|
before = len(page.query_selector_all("[class*='message'],[class*='response'],[class*='prose'],.group"))
|
|
|
|
# Type + submit
|
|
ta.fill("")
|
|
ta.type(message, delay=30)
|
|
time.sleep(0.3)
|
|
ta.press("Enter")
|
|
|
|
# Wait for new content
|
|
time.sleep(cfg["wait"])
|
|
|
|
# Try to get the last response
|
|
# Generic: get all text blocks and take the last substantial one
|
|
selectors = [
|
|
"[class*='message']", "[class*='response']", "[class*='prose']",
|
|
"[class*='answer']", "[class*='reply']", "[class*='content']",
|
|
".group", "article", "main p"
|
|
]
|
|
best = ""
|
|
for sel in selectors:
|
|
els = page.query_selector_all(sel)
|
|
if els:
|
|
for el in reversed(els):
|
|
txt = el.inner_text().strip()
|
|
if len(txt) > len(best) and txt != message:
|
|
best = txt
|
|
if len(best) > 50:
|
|
break
|
|
|
|
if len(best) > 10:
|
|
return {
|
|
"content": best[:3000],
|
|
"provider": f"WEVIA WebChat ({service})",
|
|
"model": service,
|
|
"cost": "0EUR UNLIMITED",
|
|
}
|
|
|
|
# Last resort: full page text
|
|
body = page.inner_text("body")
|
|
lines = [l.strip() for l in body.split("\n") if len(l.strip()) > 20 and l.strip() != message]
|
|
if lines:
|
|
return {
|
|
"content": "\n".join(lines[-5:]),
|
|
"provider": f"WEVIA WebChat ({service})",
|
|
"model": service,
|
|
"cost": "0EUR",
|
|
}
|
|
|
|
return {"error": "no response detected", "url": page.url}
|
|
except Exception as e:
|
|
# Reset page on error
|
|
if service in pages:
|
|
try: pages[service].close()
|
|
except: pass
|
|
del pages[service]
|
|
return {"error": str(e)}
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def do_GET(self):
|
|
self.send_response(200)
|
|
self.send_header('Content-Type', 'application/json')
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps({
|
|
"service": "WEVIA WebChat Multi UNLIMITED",
|
|
"port": PORT, "ready": ready,
|
|
"services": list(SERVICES.keys()),
|
|
"active": list(pages.keys()),
|
|
"unlimited": True
|
|
}).encode())
|
|
|
|
def do_POST(self):
|
|
body = json.loads(self.rfile.read(int(self.headers.get('Content-Length', 0)))) if int(self.headers.get('Content-Length', 0)) > 0 else {}
|
|
svc = body.get('service', body.get('model', '')).replace('web-', '')
|
|
msg = body.get('message', body.get('prompt', ''))
|
|
result = chat(svc, msg) if msg and svc else {"error": "need service+message"}
|
|
self.send_response(200)
|
|
self.send_header('Content-Type', 'application/json')
|
|
self.send_header('Access-Control-Allow-Origin', '*')
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps(result).encode())
|
|
|
|
def do_OPTIONS(self):
|
|
self.send_response(200)
|
|
self.send_header('Access-Control-Allow-Origin', '*')
|
|
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
|
self.end_headers()
|
|
|
|
def log_message(self, *a): pass
|
|
|
|
if __name__ == '__main__':
|
|
print(f"[WEVIA WebChat] Init browser...")
|
|
init_browser() # MAIN THREAD — same as DS Web
|
|
print(f"[WEVIA WebChat] Serving on :{PORT}")
|
|
HTTPServer(('0.0.0.0', PORT), Handler).serve_forever()
|