Mammouth-Archiv: API+Konfig behalten trotz abgelaufenem Abo (Baer-Anweisung)
This commit is contained in:
parent
b44f1d6ad9
commit
069e90b8d6
18
mammouth-archiv/README.md
Normal file
18
mammouth-archiv/README.md
Normal file
|
|
@ -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/<name>` erreichbar
|
||||||
554
mammouth-archiv/main.py
Normal file
554
mammouth-archiv/main.py
Normal file
|
|
@ -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/<id>."""
|
||||||
|
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
|
||||||
|
}));
|
||||||
|
}""")
|
||||||
84
mammouth-archiv/mammouth-84-modelle.txt
Normal file
84
mammouth-archiv/mammouth-84-modelle.txt
Normal file
|
|
@ -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
|
||||||
29
mammouth-archiv/mammouth.py
Normal file
29
mammouth-archiv/mammouth.py
Normal file
|
|
@ -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]"
|
||||||
Loading…
Reference in a new issue