diff --git a/mammouth-archiv/README.md b/mammouth-archiv/README.md new file mode 100644 index 0000000..369546b --- /dev/null +++ b/mammouth-archiv/README.md @@ -0,0 +1,18 @@ +# Mammouth-Archiv (2026-07-20) + +**Abo derzeit abgelaufen — API + Bridge-Konfiguration BEHALTEN (Bär-Anweisung).** +Sobald das Abo wieder aktiv ist, liefert die Bridge sofort: sie ist technisch repariert +(gpu-pc-IP, SOCKS5-Tunnel, Passwort-Auth alle gefixt am 2026-07-20). Nur die Antwort +"No active subscription" steht im Weg. + +## Was hier liegt +- `mammouth.py` — die Bridge (Selektor, Chat-Logik, SOCKS5-Proxy-Config) +- `main.py` — Aggregator mit den Mammouth-Routen +- `mammouth-84-modelle.txt` — alle 84 Modellnamen (Bild: flux/gpt-image/nano-banana/recraft/sd, + Video: sora/veo/kling, Sprache: elevenlabs + 7x TTS, Chat bis claude-opus-4.8) + +## Wiederinbetriebnahme +1. Abo bei mammouth.ai reaktivieren, in der gpu-pc-PWA (Firefox, SOCKS5 :1080) einloggen +2. `docker restart keys-bridges` +3. Test: `curl -s -X POST http://127.0.0.1:4003/mammouth/chat -d '{"prompt":"Test"}' -H "Content-Type: application/json"` +4. Modelle sind über keys.consoro.it/v1 als `Mammouth-PWA/` erreichbar diff --git a/mammouth-archiv/main.py b/mammouth-archiv/main.py new file mode 100644 index 0000000..85a7d1d --- /dev/null +++ b/mammouth-archiv/main.py @@ -0,0 +1,554 @@ +""" +keys-bridges — Headless-Chromium PWA-Bridge-Hub. +Endpoints: + GET /healthz → ok + GET /status → login-status aller Bridges + POST /mammouth/chat → {prompt, model?} → {content} + POST /suno/generate → {prompt, instrumental?} → {audio_urls} + POST /gemini/chat → {prompt} → {content} + noVNC läuft separat auf :4004 (über supervisord) +""" +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +from bridges.mammouth import Mammouth +from bridges.suno import Suno +from bridges.gemini import Gemini + +app = FastAPI(title="keys-bridges") + +BRIDGES = {"mammouth": Mammouth(), "suno": Suno(), "gemini": Gemini()} + +class ChatIn(BaseModel): + prompt: str + model: str | None = None + +class SunoIn(BaseModel): + prompt: str + instrumental: bool = False + +@app.get("/healthz") +async def health(): return {"ok": True} + +@app.get("/status") +async def status(): + out = {} + for n, b in BRIDGES.items(): + try: + out[n] = await b.login_status() + except Exception as e: + out[n] = {"error": str(e), "logged_in": False} + return out + +@app.post("/mammouth/chat") +async def mammouth_chat(p: ChatIn): + try: + content = await BRIDGES["mammouth"].chat(p.prompt, p.model or "auto") + return {"content": content, "model": p.model or "auto"} + except Exception as e: + raise HTTPException(500, f"mammouth: {e}") + +@app.post("/gemini/chat") +async def gemini_chat(p: ChatIn): + try: + content = await BRIDGES["gemini"].chat(p.prompt) + return {"content": content} + except Exception as e: + raise HTTPException(500, f"gemini: {e}") + +from bridges.qolaba import Qolaba +BRIDGES["qolaba"] = Qolaba() + +@app.post("/qolaba/chat") +async def qolaba_chat(p: ChatIn): + try: + content = await BRIDGES["qolaba"].chat(p.prompt, p.model or "chat") + return {"content": content} + except Exception as e: + raise HTTPException(500, f"qolaba: {e}") + +from bridges.suno import Suno as _SunoCls +BRIDGES["suno"] = _SunoCls() + +@app.post("/suno/generate") +async def suno_submit(p: SunoIn): + """Async — returns job_id sofort. Poll /suno/status/.""" + job_id = await BRIDGES["suno"].submit_job(p.prompt, p.instrumental) + return {"job_id": job_id, "status_url": f"/suno/status/{job_id}"} + +@app.get("/suno/status/{job_id}") +async def suno_status(job_id: str): + job = BRIDGES["suno"].get_job(job_id) + if not job: raise HTTPException(404, "unknown job_id") + return job + +@app.get("/suno/jobs") +async def suno_jobs(): + return BRIDGES["suno"].list_jobs() + +from bridges.qolaba_api import qolaba_api + +class QolabaAPIIn(BaseModel): + model: str + messages: list + +@app.post("/qolaba/api/chat") +async def qolaba_api_chat(p: QolabaAPIIn): + content = await qolaba_api.chat_via_ui(p.model, p.messages) + return {"content": content, "model": p.model} + + +# --- Nimbus/FuseBase Bridge (Baer 2026-06-24) --- +from bridges.nimbus import Nimbus +BRIDGES["nimbus"] = Nimbus() + +@app.get("/nimbus/status") +async def nimbus_status(): return await BRIDGES["nimbus"].login_status() + +@app.get("/nimbus/text") +async def nimbus_text(path: str = ""): return await BRIDGES["nimbus"].text(path) + +@app.get("/nimbus/workspaces") +async def nimbus_workspaces(): return await BRIDGES["nimbus"].workspaces() + +@app.get("/nimbus/screenshot") +async def nimbus_screenshot(path: str = ""): return await BRIDGES["nimbus"].screenshot(path) + +@app.get("/nimbus/goto") +async def nimbus_goto(path: str = ""): return await BRIDGES["nimbus"].goto(path) + + +# --- OpenAI-compatible adapter (replaces dead code-x86 adapters) --- +import time, uuid +from fastapi import Request +from fastapi.responses import JSONResponse + +BRIDGE_ROUTING = { + "mammouth": "mammouth", + "qolaba": "qolaba", + "gemini": "gemini", +} + +def _openai_response(content: str, model: str) -> dict: + return { + "id": f"chatcmpl-{uuid.uuid4().hex[:12]}", + "object": "chat.completion", + "created": int(time.time()), + "model": model, + "choices": [{ + "index": 0, + "message": {"role": "assistant", "content": content}, + "finish_reason": "stop" + }], + "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0} + } + +@app.post("/v1/chat/completions") +async def openai_chat_completions(request: Request): + body = await request.json() + model = body.get("model", "mammouth/auto") + messages = body.get("messages", []) + prompt = "\n".join( + f"{m.get('role','user')}: {m.get('content','')}" for m in messages + ) if messages else "" + if not prompt and messages: + prompt = messages[-1].get("content", "") + + prefix = model.split("/")[0] if "/" in model else model + sub_model = "/".join(model.split("/")[1:]) if "/" in model else "auto" + + bridge_name = BRIDGE_ROUTING.get(prefix) + if not bridge_name: + return JSONResponse(status_code=400, content={ + "error": {"message": f"Unknown model prefix: {prefix}", "type": "invalid_request_error"} + }) + + bridge = BRIDGES.get(bridge_name) + if not bridge: + return JSONResponse(status_code=503, content={ + "error": {"message": f"Bridge {bridge_name} not loaded", "type": "server_error"} + }) + + try: + if bridge_name == "mammouth": + content = await bridge.chat(prompt, sub_model) + elif bridge_name == "qolaba": + content = await bridge.chat(prompt, sub_model) + elif bridge_name == "gemini": + content = await bridge.chat(prompt) + else: + content = await bridge.chat(prompt) + except Exception as e: + return JSONResponse(status_code=500, content={ + "error": {"message": str(e), "type": "server_error"} + }) + + return _openai_response(content, model) + +@app.get("/v1/models") +async def openai_models(): + model_list = [] + for prefix in BRIDGE_ROUTING: + model_list.append({ + "id": f"{prefix}/auto", + "object": "model", + "created": 1700000000, + "owned_by": "keys-bridges" + }) + return {"object": "list", "data": model_list} + + +# --- Veo3 Video-Generation via Gemini PWA (Baer 2026-07-05) --- +from pydantic import Field + +class Veo3In(BaseModel): + prompt: str + timeout: int = Field(default=300, ge=30, le=600) + +@app.post("/gemini/veo3") +async def gemini_veo3(p: Veo3In): + try: + result = await BRIDGES["gemini"].veo3(p.prompt, p.timeout) + return result + except Exception as e: + raise HTTPException(500, f"veo3: {e}") + +@app.get("/gemini/screenshot") +async def gemini_screenshot(): + try: + path = await BRIDGES["gemini"].screenshot() + return {"screenshot": path} + except Exception as e: + raise HTTPException(500, f"screenshot: {e}") + +@app.get("/gemini/veo3/files") +async def veo3_files(): + from pathlib import Path + d = Path("/tmp/veo3") + if not d.exists(): + return {"files": []} + files = sorted(d.glob("veo3_*.mp4"), key=lambda f: f.stat().st_mtime, reverse=True) + return {"files": [{"path": str(f), "size": f.stat().st_size, "name": f.name} for f in files[:20]]} + +@app.get("/gemini/debug/overlay") +async def gemini_debug_overlay(): + try: + bridge = BRIDGES["gemini"] + p = await bridge.get_page() + result = await p.evaluate("""() => { + const out = {overlay_text: "", buttons: [], dialogs: []}; + const container = document.querySelector(".cdk-overlay-container"); + if (container) { + out.overlay_text = container.innerText.substring(0, 2000); + container.querySelectorAll("button").forEach(b => { + if (b.offsetHeight > 0) out.buttons.push({text: b.innerText.trim(), ariaLabel: b.getAttribute('aria-label') || ''}); + }); + } + document.querySelectorAll("[role=dialog], mat-dialog-container, .mat-mdc-dialog-surface").forEach(d => { + out.dialogs.push(d.innerText.substring(0, 500)); + }); + return out; + }""") + return result + except Exception as e: + return {"error": str(e)} + +@app.get("/gemini/debug/page") +async def gemini_debug_page(): + try: + bridge = BRIDGES["gemini"] + p = await bridge.get_page() + result = await p.evaluate("""() => { + const out = { + url: window.location.href, + title: document.title, + body_text: document.body ? document.body.innerText.substring(0, 3000) : '', + textbox_exists: !!document.querySelector('div.ql-editor[role="textbox"]'), + overlay_visible: false, + video_count: document.querySelectorAll('video').length, + model_response_count: document.querySelectorAll('model-response').length, + message_content_count: document.querySelectorAll('message-content').length, + }; + const ov = document.querySelector('.cdk-overlay-container'); + if (ov && ov.innerText.trim()) { + out.overlay_visible = true; + out.overlay_text = ov.innerText.substring(0, 500); + } + // Check last message content + const msgs = document.querySelectorAll('message-content'); + if (msgs.length > 0) { + out.last_message = msgs[msgs.length - 1].innerText.substring(0, 500); + } + return out; + }""") + return result + except Exception as e: + return {"error": str(e)} + +@app.get("/gemini/debug/video-state") +async def gemini_debug_video_state(): + try: + bridge = BRIDGES["gemini"] + p = await bridge.get_page() + result = await p.evaluate("""() => { + const out = { + url: window.location.href, + jetzt_antworten_found: false, + jetzt_antworten_is_button: false, + video_elements: [], + spinners: [], + progress_indicators: [], + }; + // Check all elements for "Jetzt antworten" + const allElements = document.querySelectorAll('*'); + for (const el of allElements) { + if (el.children.length === 0 || el.tagName === 'BUTTON') { + const t = el.textContent.trim(); + if (t === 'Jetzt antworten' || t === 'Answer now') { + out.jetzt_antworten_found = true; + out.jetzt_antworten_is_button = el.tagName === 'BUTTON' || el.closest('button') !== null; + out.jetzt_antworten_tag = el.tagName; + out.jetzt_antworten_visible = el.offsetHeight > 0; + } + } + // Check for spinners/loading + if (el.getAttribute('role') === 'progressbar' || + el.classList.contains('loading') || + el.classList.contains('spinner') || + el.tagName === 'MAT-SPINNER' || + el.tagName === 'MAT-PROGRESS-SPINNER') { + if (el.offsetHeight > 0) { + out.spinners.push({tag: el.tagName, class: el.className.substring(0,100)}); + } + } + } + // Shadow DOM video check + function findVideos(root) { + root.querySelectorAll('video').forEach(v => { + out.video_elements.push({src: v.src || v.currentSrc || 'no-src', ready: v.readyState}); + }); + root.querySelectorAll('*').forEach(el => { + if (el.shadowRoot) findVideos(el.shadowRoot); + }); + } + findVideos(document); + return out; + }""") + return result + except Exception as e: + return {"error": str(e)} + + +# TEMPORAER (2026-07-20): Selektor-Diagnose fuer mammouth.ai +@app.get("/debug/mammouth-dom") +async def debug_mammouth_dom(): + b = BRIDGES["mammouth"] + p = await b.get_page() + try: + await p.wait_for_load_state("domcontentloaded", timeout=20000) + except Exception: + pass + return await p.evaluate("""() => { + const sel = (q) => Array.from(document.querySelectorAll(q)); + const d = (e) => ({ + tag: e.tagName.toLowerCase(), + platzhalter: e.getAttribute('placeholder'), + rolle: e.getAttribute('role'), + aria: e.getAttribute('aria-label'), + klasse: (e.className || '').toString().slice(0, 80), + id: e.id, + editierbar: e.isContentEditable, + sichtbar: !!(e.offsetParent || e.getClientRects().length) + }); + return { + url: location.href, + titel: document.title, + textareas: sel('textarea').map(d), + editierbare: sel('[contenteditable]').map(d), + eingaben: sel('input').map(d).slice(0, 10), + knoepfe: sel('button').filter(e => e.offsetParent) + .map(e => ({text: (e.innerText||'').trim().slice(0,35), aria: e.getAttribute('aria-label')})) + .slice(0, 15) + }; + }""") + + +# TEMPORAER (2026-07-20): Selektor-Diagnose fuer suno.com +@app.get("/debug/suno-dom") +async def debug_suno_dom(): + b = BRIDGES["suno"] + p = await b.get_page() + try: + await p.wait_for_load_state("domcontentloaded", timeout=20000) + except Exception: + pass + return await p.evaluate("""() => { + const sel = (q) => Array.from(document.querySelectorAll(q)); + const d = (e) => ({ + tag: e.tagName.toLowerCase(), + platzhalter: e.getAttribute('placeholder'), + rolle: e.getAttribute('role'), + aria: e.getAttribute('aria-label'), + klasse: (e.className || '').toString().slice(0, 80), + id: e.id, + editierbar: e.isContentEditable, + sichtbar: !!(e.offsetParent || e.getClientRects().length) + }); + return { + url: location.href, + titel: document.title, + textareas: sel('textarea').map(d), + editierbare: sel('[contenteditable]').map(d), + eingaben: sel('input').map(d).slice(0, 10), + knoepfe: sel('button').filter(e => e.offsetParent) + .map(e => ({text: (e.innerText||'').trim().slice(0,35), aria: e.getAttribute('aria-label')})) + .slice(0, 20) + }; + }""") + + +# TEMPORAER (2026-07-20): Selektor-Diagnose fuer qolaba.ai +@app.get("/debug/qolaba-dom") +async def debug_qolaba_dom(): + b = BRIDGES["qolaba"] + p = await b.get_page() + try: + await p.wait_for_load_state("domcontentloaded", timeout=20000) + except Exception: + pass + return await p.evaluate("""() => { + const sel = (q) => Array.from(document.querySelectorAll(q)); + const d = (e) => ({ + tag: e.tagName.toLowerCase(), + platzhalter: e.getAttribute('placeholder'), + rolle: e.getAttribute('role'), + aria: e.getAttribute('aria-label'), + klasse: (e.className || '').toString().slice(0, 80), + id: e.id, + editierbar: e.isContentEditable, + sichtbar: !!(e.offsetParent || e.getClientRects().length) + }); + return { + url: location.href, + titel: document.title, + textareas: sel('textarea').map(d), + editierbare: sel('[contenteditable]').map(d), + eingaben: sel('input').map(d).slice(0, 10), + knoepfe: sel('button').filter(e => e.offsetParent) + .map(e => ({text: (e.innerText||'').trim().slice(0,35), aria: e.getAttribute('aria-label')})) + .slice(0, 15), + prose_count: sel('main .prose').length, + main_html_len: (document.querySelector('main')||{}).innerHTML ? document.querySelector('main').innerHTML.length : 0 + }; + }""") + + +@app.get("/debug/qolaba-dom2") +async def debug_qolaba_dom2(): + b = BRIDGES["qolaba"] + p = await b.get_page() + return await p.evaluate("""() => { + const all = Array.from(document.querySelectorAll('main *')); + const cand = all.filter(e => { + const cls = (e.className || '').toString().toLowerCase(); + const id = (e.id || '').toLowerCase(); + return /message|markdown|chat|bubble|assistant|response|prose|reply/.test(cls) || + /message|markdown|chat|bubble|assistant|response|prose|reply/.test(id); + }); + const seen = new Set(); + const out = []; + for (const e of cand) { + const key = e.tagName + '|' + (e.className||'').toString().slice(0,100); + if (seen.has(key)) continue; + seen.add(key); + out.push({ + tag: e.tagName.toLowerCase(), + klasse: (e.className||'').toString().slice(0,100), + id: e.id, + textlen: (e.innerText||'').length, + textsnippet: (e.innerText||'').trim().slice(0,80) + }); + if (out.length >= 40) break; + } + return out; + }""") + + +@app.get("/debug/qolaba-send") +async def debug_qolaba_send(): + b = BRIDGES["qolaba"] + p = await b.get_page() + try: + await p.goto("https://www.qolaba.ai/chat", wait_until="domcontentloaded", timeout=20000) + await p.wait_for_timeout(3000) + except Exception: + pass + ta = await p.wait_for_selector('textarea[placeholder*="typing" i]', timeout=20000) + await ta.click(); await ta.fill("Sag in einem Satz: welches Datum ist heute laut deinem Wissen?") + await p.wait_for_timeout(1000) + sub = await p.query_selector('button[type="submit"]') + clicked_via = None + if sub: + await sub.scroll_into_view_if_needed() + await sub.click(force=True) + clicked_via = "submit-button" + else: + await p.keyboard.press("Enter") + clicked_via = "enter-key" + await p.wait_for_timeout(12000) + result = await p.evaluate("""() => { + const all = Array.from(document.querySelectorAll('main *')); + const cand = all.filter(e => { + const cls = (e.className || '').toString().toLowerCase(); + const id = (e.id || '').toLowerCase(); + return /message|markdown|chat|bubble|assistant|response|prose|reply|answer|content|text-/.test(cls) || + /message|markdown|chat|bubble|assistant|response|prose|reply/.test(id); + }); + const seen = new Set(); + const out = []; + for (const e of cand) { + const key = e.tagName + '|' + (e.className||'').toString().slice(0,100); + if (seen.has(key)) continue; + seen.add(key); + const t = (e.innerText||'').trim(); + if (!t) continue; + out.push({ + tag: e.tagName.toLowerCase(), + klasse: (e.className||'').toString().slice(0,120), + id: e.id, + textlen: t.length, + textsnippet: t.slice(0,150) + }); + if (out.length >= 40) break; + } + return out; + }""") + return {"clicked_via": clicked_via, "result": result} + + +@app.get("/debug/qolaba-dom3") +async def debug_qolaba_dom3(): + b = BRIDGES["qolaba"] + p = await b.get_page() + await p.wait_for_timeout(2000) + return await p.evaluate("""() => { + const btns = Array.from(document.querySelectorAll('button')).filter(e=>e.offsetParent).map(e=>({text:(e.innerText||'').trim().slice(0,40), cls:(e.className||'').toString().slice(0,60)})); + const overlays = Array.from(document.querySelectorAll('[class*="cookie" i], [class*="consent" i], [id*="cookie" i], [id*="consent" i], [class*="overlay" i], [class*="modal" i]')).map(e=>({tag:e.tagName, cls:(e.className||'').toString().slice(0,80), visible: !!(e.offsetParent)})); + return {url: location.href, btns, overlays}; + }""") + + +@app.get("/debug/qolaba-dom4") +async def debug_qolaba_dom4(): + b = BRIDGES["qolaba"] + p = await b.get_page() + await p.wait_for_timeout(1500) + return await p.evaluate("""() => { + const btns = Array.from(document.querySelectorAll('button')); + return btns.map(e => ({ + text: (e.innerText||'').trim().slice(0,40), + type: e.getAttribute('type'), + aria: e.getAttribute('aria-label'), + visible: !!(e.offsetParent), + disabled: e.disabled + })); + }""") diff --git a/mammouth-archiv/mammouth-84-modelle.txt b/mammouth-archiv/mammouth-84-modelle.txt new file mode 100644 index 0000000..137f317 --- /dev/null +++ b/mammouth-archiv/mammouth-84-modelle.txt @@ -0,0 +1,84 @@ +Mammouth-PWA/auto +Mammouth-PWA/azure-tts +Mammouth-PWA/claude +Mammouth-PWA/claude-haiku +Mammouth-PWA/claude-haiku-4.5 +Mammouth-PWA/claude-opus +Mammouth-PWA/claude-opus-4.6 +Mammouth-PWA/claude-opus-4.7 +Mammouth-PWA/claude-opus-4.8 +Mammouth-PWA/claude-sonnet +Mammouth-PWA/claude-sonnet-4.5 +Mammouth-PWA/claude-sonnet-4.6 +Mammouth-PWA/deep-research +Mammouth-PWA/deepseek +Mammouth-PWA/deepseek-r1 +Mammouth-PWA/deepseek-v4-flash +Mammouth-PWA/deepseek-v4-pro +Mammouth-PWA/deepthink +Mammouth-PWA/elevenlabs +Mammouth-PWA/expert +Mammouth-PWA/flux +Mammouth-PWA/gemini +Mammouth-PWA/gemini-2.5-flash +Mammouth-PWA/gemini-2.5-pro +Mammouth-PWA/gemini-3-flash +Mammouth-PWA/gemini-3.1-pro +Mammouth-PWA/gemini-3.5-flash +Mammouth-PWA/gemini-flash +Mammouth-PWA/gemini-flash-lite +Mammouth-PWA/gemini-pro +Mammouth-PWA/glm +Mammouth-PWA/glm-5.1 +Mammouth-PWA/google-tts +Mammouth-PWA/gpt +Mammouth-PWA/gpt-4.1 +Mammouth-PWA/gpt-4o +Mammouth-PWA/gpt-5 +Mammouth-PWA/gpt-5.1 +Mammouth-PWA/gpt-5.3 +Mammouth-PWA/gpt-5.4 +Mammouth-PWA/gpt-5.4-mini +Mammouth-PWA/gpt-5.4-nano +Mammouth-PWA/gpt-5.5 +Mammouth-PWA/gpt-image +Mammouth-PWA/gpt-mini +Mammouth-PWA/grok +Mammouth-PWA/grok-4.1-fast +Mammouth-PWA/grok-4.20 +Mammouth-PWA/grok-4.3 +Mammouth-PWA/grok-fast +Mammouth-PWA/grok-imagine +Mammouth-PWA/image +Mammouth-PWA/kimi +Mammouth-PWA/kimi-k2.6 +Mammouth-PWA/kling +Mammouth-PWA/llama +Mammouth-PWA/llama-3.1-8b +Mammouth-PWA/llama-4-maverick +Mammouth-PWA/llama-4-scout +Mammouth-PWA/llama-scout +Mammouth-PWA/mistral +Mammouth-PWA/mistral-medium +Mammouth-PWA/mistral-small +Mammouth-PWA/nano-banana +Mammouth-PWA/openai-tts +Mammouth-PWA/perplexity +Mammouth-PWA/play.ht +Mammouth-PWA/playht +Mammouth-PWA/qwen +Mammouth-PWA/reasoning +Mammouth-PWA/recraft +Mammouth-PWA/sd +Mammouth-PWA/sonar +Mammouth-PWA/sonar-deep-research +Mammouth-PWA/sonar-pro +Mammouth-PWA/sora +Mammouth-PWA/speech +Mammouth-PWA/stable-diffusion +Mammouth-PWA/svg +Mammouth-PWA/thinking +Mammouth-PWA/tts +Mammouth-PWA/tts-1 +Mammouth-PWA/tts-1-hd +Mammouth-PWA/veo \ No newline at end of file diff --git a/mammouth-archiv/mammouth.py b/mammouth-archiv/mammouth.py new file mode 100644 index 0000000..39f6166 --- /dev/null +++ b/mammouth-archiv/mammouth.py @@ -0,0 +1,29 @@ +from .base import PWABridge, best_response + +class Mammouth(PWABridge): + name = "mammouth" + url = "https://mammouth.ai/app" + use_firefox = True + proxy = "socks5://host.docker.internal:1080" + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0" + + async def login_status(self): + p = await self.get_page() + try: + await p.wait_for_load_state("domcontentloaded", timeout=30000) + return {"logged_in": "/login" not in p.url, "url": p.url} + except Exception as e: return {"logged_in": False, "error": str(e)} + + async def chat(self, prompt: str, model: str = "auto") -> str: + async with await self.lock(): + p = await self.get_page() + try: await p.wait_for_load_state("domcontentloaded", timeout=30000) + except Exception: pass + if "/login" in p.url: return "[mammouth login wall — proxy check]" + ta = await p.wait_for_selector('textarea, div[contenteditable="true"]', timeout=30000) + await ta.click(); await ta.fill(prompt); await p.wait_for_timeout(800) + await self.start_capture(p) + await p.keyboard.press("Enter") + await p.wait_for_timeout(18000) + captured = await self.end_capture(p) + return best_response(captured, prompt) or "[no response]"