source/main.py aktualisiert

This commit is contained in:
2026-03-09 14:16:05 +00:00
parent a4b3d58d76
commit b5f6ac1701

View File

@@ -63,7 +63,10 @@ templates = Jinja2Templates(directory=BASE_DIR / "templates")
app.mount("/static", StaticFiles(directory=BASE_DIR / "static"), name="static")
SSH_KEY = os.path.expanduser("~/.ssh/id_rsa")
chat_history = []
# Ein Dictionary für verschiedene Chat-Historien
chat_histories = {
"private": [] # Hier landen Webchat UND private Telegram-Nachrichten
}
# KI KONFIGURATION
AI_PROVIDER = os.getenv("AI_PROVIDER", "google").lower()
@@ -136,17 +139,15 @@ def get_system_prompt(current_user=WEB_USER_NAME):
# --- KI FUNKTIONEN ---
async def get_ai_response(user_input, system_prompt):
global chat_history
now = datetime.now().strftime("%d.%m.%Y %H:%M")
chat_history.append({"role": "user", "content": user_input, "timestamp": now})
chat_history = chat_history[-30:]
async def get_ai_response(user_msg, system_prompt, history_list):
ai_msg = ""
try:
# Kombinierte Logik für OpenAI, Ollama und NVIDIA (alle nutzen das OpenAI SDK)
if AI_PROVIDER in ["openai", "ollama", "nvidia"]:
messages = [{"role": "system", "content": system_prompt}] + chat_history
# WICHTIG: Wir nutzen die übergebene 'history_list', NICHT starr "private"
# Da der Aufrufer (Telegram/Web) die aktuelle Frage schon hinzugefügt hat, ist sie hier schon drin.
messages = [{"role": "system", "content": system_prompt}] + history_list
if AI_PROVIDER == "ollama":
url = OLLAMA_BASE_URL
@@ -163,7 +164,6 @@ async def get_ai_response(user_input, system_prompt):
key = OPENAI_API_KEY
model_to_use = OPENAI_MODEL
# WICHTIG: Hier .AsyncOpenAI nutzen, da die Funktion async ist
client = openai.AsyncOpenAI(base_url=url, api_key=key)
response = await client.chat.completions.create(
model=model_to_use,
@@ -172,41 +172,36 @@ async def get_ai_response(user_input, system_prompt):
ai_msg = response.choices[0].message.content
elif AI_PROVIDER == "google":
# Für Google Gemini
if not GOOGLE_API_KEY:
return "Fehler: GOOGLE_API_KEY fehlt in der .env Datei!"
client = genai.Client(api_key=GOOGLE_API_KEY)
# Wir müssen unser Array in das spezielle Google-Format umwandeln
google_history = []
# Alle Nachrichten AUSSER der allerletzten (die aktuelle User-Frage) in die History packen
for msg in chat_history[:-1]:
# Alle Nachrichten AUSSER der allerletzten (das ist die aktuelle Frage von gerade eben)
for msg in history_list[:-1]:
role = "user" if msg["role"] == "user" else "model"
google_history.append(
types.Content(role=role, parts=[types.Part.from_text(text=msg["content"])])
)
# Chat MIT dem übersetzten Gedächtnis starten
chat = client.chats.create(
model=GOOGLE_MODEL,
config=types.GenerateContentConfig(system_instruction=system_prompt),
history=google_history
)
# Jetzt erst die neue Nachricht an den Chat mit Gedächtnis schicken
response = chat.send_message(user_input)
# Sende die aktuelle Nachricht an den Chat
response = chat.send_message(user_msg)
ai_msg = response.text
except Exception as e:
ai_msg = f"Fehler bei der KI-Anfrage: {e}"
print(f"KI Fehler: {e}")
# 3. Die Antwort der KI ebenfalls ins Gedächtnis aufnehmen
now = datetime.now().strftime("%d.%m.%Y %H:%M")
chat_history.append({"role": "assistant", "content": ai_msg, "timestamp": now})
# Wir geben nur den Text zurück. Das Speichern der KI-Antwort in die Historie
# erledigt der Aufrufer (Web-Chat Endpoint oder Telegram Funktion)!
return ai_msg
async def handle_telegram_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
@@ -216,37 +211,54 @@ async def handle_telegram_message(update: Update, context: ContextTypes.DEFAULT_
chat_type = update.effective_chat.type
user_msg = update.message.text
user_id = str(update.message.from_user.id)
chat_id = str(update.effective_chat.id)
# 1. Gruppen-Logik: Nur reagieren, wenn der Bot @erwähnt wird
# 1. Weiche für Gruppen-Logik & Historien-Zuordnung
if chat_type in ['group', 'supergroup']:
if bot_username not in user_msg:
return # Bot wurde nicht erwähnt, Nachricht ignorieren
# Den @Namen aus dem Text entfernen, damit die KI nicht verwirrt wird
# Den @Namen aus dem Text entfernen
user_msg = user_msg.replace(bot_username, "").strip()
# Jede Gruppe bekommt ihr komplett eigenes Gedächtnis
history_key = chat_id
else:
# Im Einzelchat kann optional weiterhin nur der Admin zugelassen werden.
# Wenn du willst, dass auch andere den Bot privat nutzen können, entferne diesen Block:
# Sicherheitscheck im Einzelchat
if user_id != ALLOWED_ID:
await update.message.reply_text("Zugriff auf den privaten Chat verweigert. 🔒")
return
# Tipp-Status anzeigen
await update.message.reply_chat_action(action="typing")
# Privat-Chats teilen sich das Gedächtnis mit dem Webchat
history_key = "private"
# Sicherstellen, dass die Liste für diesen Chat existiert
if history_key not in chat_histories:
chat_histories[history_key] = []
current_history = chat_histories[history_key]
# Nutzer-Nachricht mit Zeitstempel in die RICHTIGE Historie aufnehmen
now = datetime.now().strftime("%d.%m.%Y %H:%M")
current_history.append({"role": "user", "content": user_msg, "timestamp": now})
# 2. KI fragen
# Vorname des Telegram-Nutzers auslesen
sender_name = update.message.from_user.first_name
# Tipp-Status anzeigen
await update.message.reply_chat_action(action="typing")
# 2. KI fragen (und den dynamischen Namen übergeben!)
ai_response = await get_ai_response(user_msg, get_system_prompt(sender_name))
# 2. KI fragen (Wir übergeben die spezifische Historie!)
# HINWEIS: Stelle sicher, dass deine get_ai_response Funktion die current_history auch annimmt.
ai_response = await get_ai_response(user_msg, get_system_prompt(sender_name), current_history)
commands = re.findall(r'<EXECUTE target="(.*?)">(.*?)</EXECUTE>', ai_response, re.I | re.S)
clean_msg = re.sub(r'<EXECUTE.*?>.*?</EXECUTE>', '', ai_response, flags=re.I | re.S).strip()
# KI-Antwort ebenfalls in die Historie speichern
now = datetime.now().strftime("%d.%m.%Y %H:%M")
current_history.append({"role": "assistant", "content": clean_msg, "timestamp": now})
# KI Text-Antwort senden
if clean_msg:
await update.message.reply_text(clean_msg)
@@ -277,13 +289,19 @@ async def handle_telegram_message(update: Update, context: ContextTypes.DEFAULT_
result_text = output[:4000] if output else "✅ Befehl ohne Output ausgeführt."
await update.message.reply_text(f"💻 **Output von {n['name']}:**\n```\n{result_text}\n```", parse_mode='Markdown')
# System-Output auch in die RICHTIGE Historie packen, damit die KI weiß, was passiert ist
now = datetime.now().strftime("%d.%m.%Y %H:%M")
chat_history.append({"role": "user", "content": f"[SYSTEM] Befehl '{cmd}' auf {target} fertig:\n{result_text}", "timestamp": now})
current_history.append({"role": "user", "content": f"[SYSTEM] Befehl '{cmd}' auf {target} fertig:\n{result_text}", "timestamp": now})
except Exception as e:
await update.message.reply_text(f"❌ Fehler bei der Ausführung: {e}")
else:
await update.message.reply_text(f"⚠️ Node '{target}' nicht in der Datenbank gefunden.")
# Optionaler Bonus: Halte die Historie sauber (z.B. max. 15 Nachrichten), damit der RAM nicht überläuft
if len(current_history) > 15:
chat_histories[history_key] = current_history[-15:]
# --- FASTAPI LIFESPAN EVENTS (Bot starten/stoppen) ---
@app.on_event("startup")
async def startup_event():
@@ -599,37 +617,80 @@ async def terminal_websocket(websocket: WebSocket, ip: str):
@app.websocket("/ws/chat")
async def chat_endpoint(websocket: WebSocket):
await websocket.accept()
# Sicherstellen, dass die private History existiert
if "private" not in chat_histories:
chat_histories["private"] = []
try:
while True:
user_msg = await websocket.receive_text()
ai_response = await get_ai_response(user_msg, get_system_prompt())
# 1. User-Nachricht in Historie speichern
now = datetime.now().strftime("%d.%m.%Y %H:%M")
chat_histories["private"].append({"role": "user", "content": user_msg, "timestamp": now})
# 2. KI fragen (mit der Historie!)
ai_response = await get_ai_response(user_msg, get_system_prompt(), chat_histories["private"])
commands = re.findall(r'<EXECUTE target="(.*?)">(.*?)</EXECUTE>', ai_response, re.I | re.S)
clean_msg = re.sub(r'<EXECUTE.*?>.*?</EXECUTE>', '', ai_response, flags=re.I | re.S).strip()
if clean_msg: await websocket.send_text(clean_msg)
# 3. KI-Antwort in Historie speichern
if clean_msg:
now = datetime.now().strftime("%d.%m.%Y %H:%M")
chat_histories["private"].append({"role": "assistant", "content": clean_msg, "timestamp": now})
await websocket.send_text(clean_msg)
# Begrenzung der Historie
if len(chat_histories["private"]) > 30:
chat_histories["private"] = chat_histories["private"][-30:]
if commands:
tasks = []
for target, cmd in commands:
conn = get_db(); n = conn.execute('SELECT * FROM nodes WHERE ip=? OR name=?', (target.strip(), target.strip())).fetchone(); conn.close()
if n: tasks.append(run_remote_task(n['ip'], n['user'], cmd.strip()))
conn = get_db()
n = conn.execute('SELECT * FROM nodes WHERE ip=? OR name=?', (target.strip(), target.strip())).fetchone()
conn.close()
if n:
# Wir übergeben hier die History, damit run_remote_task weiß, wo er loggen soll
tasks.append(run_remote_task(n['ip'], n['user'], cmd.strip(), "private"))
if tasks:
await websocket.send_text(" *Führe Befehle aus...*")
await asyncio.gather(*tasks)
summary = await get_ai_response("Zusammenfassung der Ergebnisse?", get_system_prompt())
# Nochmal KI fragen für Zusammenfassung (optional)
summary = await get_ai_response("Zusammenfassung der Ergebnisse?", get_system_prompt(), chat_histories["private"])
await websocket.send_text(f"--- Info ---\n{summary}")
except: pass
except WebSocketDisconnect:
pass
except Exception as e:
print(f"Webchat Fehler: {e}")
async def run_remote_task(ip, user, cmd):
async def run_remote_task(ip, user, cmd, history_key="private"): # history_key als Parameter
await manager.broadcast(f"🚀 Task: {cmd} auf {ip}")
proc = await asyncio.create_subprocess_shell(f"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null {user}@{ip} '{cmd}'", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT)
proc = await asyncio.create_subprocess_shell(
f"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null {user}@{ip} '{cmd}'",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT
)
full_output = ""
while True:
line = await proc.stdout.readline()
if not line: break
out = line.decode('utf-8', errors='ignore').strip()
if out: await manager.broadcast(f"🛠️ {out}"); full_output += out + "\n"
if out:
await manager.broadcast(f"🛠️ {out}")
full_output += out + "\n"
await proc.wait()
now = datetime.now().strftime("%d.%m.%Y %H:%M")
chat_history.append({"role": "user", "content": f"[SYSTEM] Befehl '{cmd}' auf {ip} fertig:\n{full_output or 'Kein Output'}", "timestamp": now})
# Jetzt wird in die richtige Historie geschrieben!
if history_key in chat_histories:
chat_histories[history_key].append({
"role": "user",
"content": f"[SYSTEM] Befehl '{cmd}' auf {ip} fertig:\n{full_output or 'Kein Output'}",
"timestamp": now
})
# --- Settings API ---
@app.get("/api/settings")