source/main.py aktualisiert
This commit is contained in:
139
source/main.py
139
source/main.py
@@ -63,7 +63,10 @@ templates = Jinja2Templates(directory=BASE_DIR / "templates")
|
|||||||
app.mount("/static", StaticFiles(directory=BASE_DIR / "static"), name="static")
|
app.mount("/static", StaticFiles(directory=BASE_DIR / "static"), name="static")
|
||||||
|
|
||||||
SSH_KEY = os.path.expanduser("~/.ssh/id_rsa")
|
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
|
# KI KONFIGURATION
|
||||||
AI_PROVIDER = os.getenv("AI_PROVIDER", "google").lower()
|
AI_PROVIDER = os.getenv("AI_PROVIDER", "google").lower()
|
||||||
@@ -136,17 +139,15 @@ def get_system_prompt(current_user=WEB_USER_NAME):
|
|||||||
|
|
||||||
# --- KI FUNKTIONEN ---
|
# --- KI FUNKTIONEN ---
|
||||||
|
|
||||||
async def get_ai_response(user_input, system_prompt):
|
async def get_ai_response(user_msg, system_prompt, history_list):
|
||||||
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:]
|
|
||||||
ai_msg = ""
|
ai_msg = ""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Kombinierte Logik für OpenAI, Ollama und NVIDIA (alle nutzen das OpenAI SDK)
|
# Kombinierte Logik für OpenAI, Ollama und NVIDIA (alle nutzen das OpenAI SDK)
|
||||||
if AI_PROVIDER in ["openai", "ollama", "nvidia"]:
|
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":
|
if AI_PROVIDER == "ollama":
|
||||||
url = OLLAMA_BASE_URL
|
url = OLLAMA_BASE_URL
|
||||||
@@ -163,7 +164,6 @@ async def get_ai_response(user_input, system_prompt):
|
|||||||
key = OPENAI_API_KEY
|
key = OPENAI_API_KEY
|
||||||
model_to_use = OPENAI_MODEL
|
model_to_use = OPENAI_MODEL
|
||||||
|
|
||||||
# WICHTIG: Hier .AsyncOpenAI nutzen, da die Funktion async ist
|
|
||||||
client = openai.AsyncOpenAI(base_url=url, api_key=key)
|
client = openai.AsyncOpenAI(base_url=url, api_key=key)
|
||||||
response = await client.chat.completions.create(
|
response = await client.chat.completions.create(
|
||||||
model=model_to_use,
|
model=model_to_use,
|
||||||
@@ -172,41 +172,36 @@ async def get_ai_response(user_input, system_prompt):
|
|||||||
ai_msg = response.choices[0].message.content
|
ai_msg = response.choices[0].message.content
|
||||||
|
|
||||||
elif AI_PROVIDER == "google":
|
elif AI_PROVIDER == "google":
|
||||||
# Für Google Gemini
|
|
||||||
if not GOOGLE_API_KEY:
|
if not GOOGLE_API_KEY:
|
||||||
return "Fehler: GOOGLE_API_KEY fehlt in der .env Datei!"
|
return "Fehler: GOOGLE_API_KEY fehlt in der .env Datei!"
|
||||||
|
|
||||||
client = genai.Client(api_key=GOOGLE_API_KEY)
|
client = genai.Client(api_key=GOOGLE_API_KEY)
|
||||||
|
|
||||||
# Wir müssen unser Array in das spezielle Google-Format umwandeln
|
|
||||||
google_history = []
|
google_history = []
|
||||||
|
|
||||||
# Alle Nachrichten AUSSER der allerletzten (die aktuelle User-Frage) in die History packen
|
# Alle Nachrichten AUSSER der allerletzten (das ist die aktuelle Frage von gerade eben)
|
||||||
for msg in chat_history[:-1]:
|
for msg in history_list[:-1]:
|
||||||
role = "user" if msg["role"] == "user" else "model"
|
role = "user" if msg["role"] == "user" else "model"
|
||||||
google_history.append(
|
google_history.append(
|
||||||
types.Content(role=role, parts=[types.Part.from_text(text=msg["content"])])
|
types.Content(role=role, parts=[types.Part.from_text(text=msg["content"])])
|
||||||
)
|
)
|
||||||
|
|
||||||
# Chat MIT dem übersetzten Gedächtnis starten
|
|
||||||
chat = client.chats.create(
|
chat = client.chats.create(
|
||||||
model=GOOGLE_MODEL,
|
model=GOOGLE_MODEL,
|
||||||
config=types.GenerateContentConfig(system_instruction=system_prompt),
|
config=types.GenerateContentConfig(system_instruction=system_prompt),
|
||||||
history=google_history
|
history=google_history
|
||||||
)
|
)
|
||||||
|
|
||||||
# Jetzt erst die neue Nachricht an den Chat mit Gedächtnis schicken
|
# Sende die aktuelle Nachricht an den Chat
|
||||||
response = chat.send_message(user_input)
|
response = chat.send_message(user_msg)
|
||||||
ai_msg = response.text
|
ai_msg = response.text
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
ai_msg = f"Fehler bei der KI-Anfrage: {e}"
|
ai_msg = f"Fehler bei der KI-Anfrage: {e}"
|
||||||
print(f"KI Fehler: {e}")
|
print(f"KI Fehler: {e}")
|
||||||
|
|
||||||
# 3. Die Antwort der KI ebenfalls ins Gedächtnis aufnehmen
|
# Wir geben nur den Text zurück. Das Speichern der KI-Antwort in die Historie
|
||||||
now = datetime.now().strftime("%d.%m.%Y %H:%M")
|
# erledigt der Aufrufer (Web-Chat Endpoint oder Telegram Funktion)!
|
||||||
chat_history.append({"role": "assistant", "content": ai_msg, "timestamp": now})
|
|
||||||
|
|
||||||
return ai_msg
|
return ai_msg
|
||||||
|
|
||||||
async def handle_telegram_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
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
|
chat_type = update.effective_chat.type
|
||||||
user_msg = update.message.text
|
user_msg = update.message.text
|
||||||
user_id = str(update.message.from_user.id)
|
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 chat_type in ['group', 'supergroup']:
|
||||||
if bot_username not in user_msg:
|
if bot_username not in user_msg:
|
||||||
return # Bot wurde nicht erwähnt, Nachricht ignorieren
|
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()
|
user_msg = user_msg.replace(bot_username, "").strip()
|
||||||
|
|
||||||
|
# Jede Gruppe bekommt ihr komplett eigenes Gedächtnis
|
||||||
|
history_key = chat_id
|
||||||
else:
|
else:
|
||||||
# Im Einzelchat kann optional weiterhin nur der Admin zugelassen werden.
|
# Sicherheitscheck im Einzelchat
|
||||||
# Wenn du willst, dass auch andere den Bot privat nutzen können, entferne diesen Block:
|
|
||||||
if user_id != ALLOWED_ID:
|
if user_id != ALLOWED_ID:
|
||||||
await update.message.reply_text("Zugriff auf den privaten Chat verweigert. 🔒")
|
await update.message.reply_text("Zugriff auf den privaten Chat verweigert. 🔒")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Tipp-Status anzeigen
|
# Privat-Chats teilen sich das Gedächtnis mit dem Webchat
|
||||||
await update.message.reply_chat_action(action="typing")
|
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
|
# Vorname des Telegram-Nutzers auslesen
|
||||||
sender_name = update.message.from_user.first_name
|
sender_name = update.message.from_user.first_name
|
||||||
|
|
||||||
# Tipp-Status anzeigen
|
# Tipp-Status anzeigen
|
||||||
await update.message.reply_chat_action(action="typing")
|
await update.message.reply_chat_action(action="typing")
|
||||||
|
|
||||||
# 2. KI fragen (und den dynamischen Namen übergeben!)
|
# 2. KI fragen (Wir übergeben die spezifische Historie!)
|
||||||
ai_response = await get_ai_response(user_msg, get_system_prompt(sender_name))
|
# 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)
|
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()
|
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
|
# KI Text-Antwort senden
|
||||||
if clean_msg:
|
if clean_msg:
|
||||||
await update.message.reply_text(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."
|
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')
|
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")
|
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:
|
except Exception as e:
|
||||||
await update.message.reply_text(f"❌ Fehler bei der Ausführung: {e}")
|
await update.message.reply_text(f"❌ Fehler bei der Ausführung: {e}")
|
||||||
else:
|
else:
|
||||||
await update.message.reply_text(f"⚠️ Node '{target}' nicht in der Datenbank gefunden.")
|
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) ---
|
# --- FASTAPI LIFESPAN EVENTS (Bot starten/stoppen) ---
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
async def startup_event():
|
async def startup_event():
|
||||||
@@ -599,37 +617,80 @@ async def terminal_websocket(websocket: WebSocket, ip: str):
|
|||||||
@app.websocket("/ws/chat")
|
@app.websocket("/ws/chat")
|
||||||
async def chat_endpoint(websocket: WebSocket):
|
async def chat_endpoint(websocket: WebSocket):
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
|
# Sicherstellen, dass die private History existiert
|
||||||
|
if "private" not in chat_histories:
|
||||||
|
chat_histories["private"] = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
user_msg = await websocket.receive_text()
|
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)
|
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()
|
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:
|
if commands:
|
||||||
tasks = []
|
tasks = []
|
||||||
for target, cmd in commands:
|
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()
|
conn = get_db()
|
||||||
if n: tasks.append(run_remote_task(n['ip'], n['user'], cmd.strip()))
|
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:
|
if tasks:
|
||||||
await websocket.send_text("ℹ️ *Führe Befehle aus...*")
|
await websocket.send_text("ℹ️ *Führe Befehle aus...*")
|
||||||
await asyncio.gather(*tasks)
|
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}")
|
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}")
|
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 = ""
|
full_output = ""
|
||||||
while True:
|
while True:
|
||||||
line = await proc.stdout.readline()
|
line = await proc.stdout.readline()
|
||||||
if not line: break
|
if not line: break
|
||||||
out = line.decode('utf-8', errors='ignore').strip()
|
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()
|
await proc.wait()
|
||||||
|
|
||||||
now = datetime.now().strftime("%d.%m.%Y %H:%M")
|
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 ---
|
# --- Settings API ---
|
||||||
@app.get("/api/settings")
|
@app.get("/api/settings")
|
||||||
|
|||||||
Reference in New Issue
Block a user