Sonnet-WebSearch "Lösung suchen" für kaputte Quellen
- POST /health/search-fix/{source_id}: Sonnet recherchiert Alternativen
- Button "Lösung suchen" bei Erreichbarkeits-Fehlern im Health-Tab
- Gefundene Lösungen werden automatisch als Vorschläge gespeichert
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -516,3 +516,134 @@ async def run_health_check_now(
|
||||
"issues": result["issues"],
|
||||
"suggestions": suggestion_count,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@router.post("/health/search-fix/{source_id}")
|
||||
async def search_fix_for_source(
|
||||
source_id: int,
|
||||
admin: dict = Depends(get_current_admin),
|
||||
db: aiosqlite.Connection = Depends(db_dependency),
|
||||
):
|
||||
"""Sonnet mit WebSearch nach Lösung für eine kaputte Quelle suchen lassen."""
|
||||
import json as _json
|
||||
|
||||
cursor = await db.execute(
|
||||
"SELECT id, name, url, domain, source_type, category FROM sources WHERE id = ?",
|
||||
(source_id,),
|
||||
)
|
||||
source = await cursor.fetchone()
|
||||
if not source:
|
||||
raise HTTPException(status_code=404, detail="Quelle nicht gefunden")
|
||||
|
||||
source = dict(source)
|
||||
|
||||
# Health-Check-Probleme für diese Quelle laden
|
||||
cursor = await db.execute(
|
||||
"SELECT check_type, status, message FROM source_health_checks WHERE source_id = ?",
|
||||
(source_id,),
|
||||
)
|
||||
issues = [dict(row) for row in await cursor.fetchall()]
|
||||
issues_text = "\n".join(f"- {i['check_type']}: {i['status']} - {i['message']}" for i in issues)
|
||||
|
||||
prompt = f"""Du bist ein OSINT-Analyst. Folgende Quelle ist nicht mehr erreichbar:
|
||||
|
||||
Name: {source['name']}
|
||||
URL: {source['url'] or 'keine'}
|
||||
Domain: {source['domain'] or 'unbekannt'}
|
||||
Typ: {source['source_type']}
|
||||
Kategorie: {source['category']}
|
||||
|
||||
Probleme:
|
||||
{issues_text}
|
||||
|
||||
Aufgabe: Suche im Internet nach funktionierenden Alternativen für diese Quelle.
|
||||
- Finde konkrete RSS-Feed-URLs die tatsächlich funktionieren
|
||||
- Prüfe ob es alternative Zugangswege gibt (andere Subdomains, Feed-Aggregatoren, alternative URLs)
|
||||
- Gibt es eine Lösung oder ist die Quelle nur noch per WebSearch erreichbar?
|
||||
|
||||
Antworte NUR mit einem JSON-Objekt:
|
||||
{{
|
||||
"fixable": true/false,
|
||||
"solutions": [
|
||||
{{
|
||||
"type": "replace_url|add_feed|deactivate",
|
||||
"name": "Anzeigename",
|
||||
"url": "https://...",
|
||||
"description": "Kurze Begründung"
|
||||
}}
|
||||
],
|
||||
"summary": "Zusammenfassung in 1-2 Sätzen"
|
||||
}}
|
||||
|
||||
Nur das JSON, kein anderer Text."""
|
||||
|
||||
sys.path.insert(0, "/home/claude-dev/AegisSight-Monitor/src")
|
||||
from agents.claude_client import call_claude
|
||||
|
||||
try:
|
||||
response, usage = await call_claude(prompt, tools="WebSearch,WebFetch")
|
||||
|
||||
import re
|
||||
json_match = re.search(r'\{.*\}', response, re.DOTALL)
|
||||
if json_match:
|
||||
result = _json.loads(json_match.group(0))
|
||||
else:
|
||||
result = {"fixable": False, "solutions": [], "summary": response[:500]}
|
||||
|
||||
# Lösungen als Vorschläge speichern
|
||||
await db.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS source_suggestions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
suggestion_type TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
source_id INTEGER REFERENCES sources(id) ON DELETE SET NULL,
|
||||
suggested_data TEXT,
|
||||
priority TEXT DEFAULT 'medium',
|
||||
status TEXT DEFAULT 'pending',
|
||||
reviewed_at TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
""")
|
||||
|
||||
for sol in result.get("solutions", []):
|
||||
sol_type = sol.get("type", "add_feed")
|
||||
suggestion_type = {
|
||||
"replace_url": "fix_url",
|
||||
"add_feed": "add_source",
|
||||
"deactivate": "deactivate_source",
|
||||
}.get(sol_type, "add_source")
|
||||
|
||||
title = f"{source['name']}: {sol.get('description', sol_type)[:80]}"
|
||||
|
||||
# Duplikat-Check
|
||||
cursor = await db.execute(
|
||||
"SELECT id FROM source_suggestions WHERE title = ? AND status = 'pending'",
|
||||
(title,),
|
||||
)
|
||||
if await cursor.fetchone():
|
||||
continue
|
||||
|
||||
data = _json.dumps({
|
||||
"name": sol.get("name", source["name"]),
|
||||
"url": sol.get("url", ""),
|
||||
"domain": source["domain"] or "",
|
||||
"category": source["category"],
|
||||
}, ensure_ascii=False)
|
||||
|
||||
await db.execute(
|
||||
"INSERT INTO source_suggestions "
|
||||
"(suggestion_type, title, description, source_id, suggested_data, priority, status) "
|
||||
"VALUES (?, ?, ?, ?, ?, 'high', 'pending')",
|
||||
(suggestion_type, title, sol.get("description", ""), source_id, data),
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
|
||||
result["cost_usd"] = usage.cost_usd
|
||||
result["tokens"] = {"input": usage.input_tokens, "output": usage.output_tokens}
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Recherche fehlgeschlagen: {e}")
|
||||
|
||||
@@ -161,7 +161,7 @@ function renderHealthDashboard() {
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Quelle</th><th>Domain</th><th>Typ</th><th>Status</th><th>Details</th></tr>
|
||||
<tr><th>Quelle</th><th>Domain</th><th>Typ</th><th>Status</th><th>Details</th><th>Aktionen</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${issues
|
||||
@@ -172,7 +172,8 @@ function renderHealthDashboard() {
|
||||
<td class="text-secondary">${esc(c.domain || "")}</td>
|
||||
<td>${CHECK_TYPE_LABELS[c.check_type] || c.check_type}</td>
|
||||
<td><span class="badge badge-health-${c.status}">${c.status === "error" ? "Fehler" : "Warnung"}</span></td>
|
||||
<td class="text-secondary" style="max-width:300px;">${esc(c.message)}</td>
|
||||
<td class="text-secondary" style="max-width:250px;">${esc(c.message)}</td>
|
||||
<td>${c.status === "error" && c.check_type === "reachability" ? \`<button class="btn btn-secondary btn-small" onclick="searchFix(\${c.source_id}, '\${esc(c.name)}')">Lösung suchen</button>\` : ""}</td>
|
||||
</tr>`,
|
||||
)
|
||||
.join("")}
|
||||
@@ -257,3 +258,32 @@ function formatDate(dateStr) {
|
||||
return dateStr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Sonnet-Recherche für kaputte Quelle ---
|
||||
async function searchFix(sourceId, sourceName) {
|
||||
if (!confirm(`Sonnet mit WebSearch nach einer Lösung für "${sourceName}" suchen lassen?\n\nDas nutzt Kontingent vom Max-Abo (~$3-4).`)) return;
|
||||
|
||||
const btn = event.target;
|
||||
btn.disabled = true;
|
||||
btn.textContent = "Sucht...";
|
||||
|
||||
try {
|
||||
const result = await API.post("/api/sources/health/search-fix/" + sourceId);
|
||||
|
||||
let msg = result.summary || "Keine Zusammenfassung";
|
||||
if (result.solutions && result.solutions.length > 0) {
|
||||
msg += "\n\nGefundene Lösungen als Vorschläge gespeichert.";
|
||||
}
|
||||
if (result.cost_usd) {
|
||||
msg += `\n\nKosten: $${result.cost_usd.toFixed(2)}`;
|
||||
}
|
||||
alert(msg);
|
||||
loadHealthData();
|
||||
} catch (err) {
|
||||
alert("Fehler: " + err.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = "Lösung suchen";
|
||||
}
|
||||
}
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren