diff --git a/source/main.py b/source/main.py
index 5325fc7..05eb125 100644
--- a/source/main.py
+++ b/source/main.py
@@ -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
+
+ # Privat-Chats teilen sich das Gedächtnis mit dem Webchat
+ history_key = "private"
- # Tipp-Status anzeigen
- await update.message.reply_chat_action(action="typing")
+ # 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'(.*?)', ai_response, re.I | re.S)
clean_msg = re.sub(r'.*?', '', 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'(.*?)', ai_response, re.I | re.S)
clean_msg = re.sub(r'.*?', '', 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")