jarvis.py aktualisiert

This commit is contained in:
2026-05-22 20:27:26 +00:00
parent 60a8e1f104
commit 300960f5ce

150
jarvis.py
View File

@@ -1,29 +1,41 @@
import os
import re
import sqlite3
import asyncio
import sqlite3
import re
from datetime import datetime
from pathlib import Path
import subprocess
import openai
from google import genai
from google.genai import types
from datetime import datetime
from pathlib import Path
from dotenv import load_dotenv
# --- FARBEN FÜR DAS TERMINAL ---
class Color:
USER = '\033[94m' # Blau
JARVIS = '\033[92m' # Grün
SYSTEM = '\033[93m' # Gelb
EXEC = '\033[95m' # Magenta für SSH
ERROR = '\033[91m' # Rot
RESET = '\033[0m' # Zurücksetzen
BOLD = '\033[1m'
# --- PFADE & SETUP ---
BASE_DIR = Path(__file__).resolve().parent
CONFIG_DIR = BASE_DIR / "config"
DATA_DIR = BASE_DIR / "data"
WORKSPACE_DIR = BASE_DIR / "workspace"
ROOT_DIR = BASE_DIR.parent
CONFIG_DIR = ROOT_DIR / "config"
DATA_DIR = ROOT_DIR / "data"
WORKSPACE_DIR = ROOT_DIR / "workspace"
ENV_FILE = CONFIG_DIR / ".env"
load_dotenv(ENV_FILE)
DB_PATH = DATA_DIR / "cluster.db"
PROMPT_FILE = CONFIG_DIR / "system_prompt.txt"
NOTES_FILE = WORKSPACE_DIR / "NOTIZEN.md"
TODO_FILE = WORKSPACE_DIR / "TODO.md"
WEB_USER_NAME = os.getenv("WEB_USER_NAME", "Meik")
WEB_USER_NAME = os.getenv("WEB_USER_NAME", "Admin")
# Ordner & Dateien anlegen
for d in [WORKSPACE_DIR, DATA_DIR, CONFIG_DIR]:
@@ -40,7 +52,7 @@ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "")
NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY", "")
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://127.0.0.1:11434/v1")
GOOGLE_MODEL = os.getenv("GOOGLE_MODEL", "gemini-2.5-flash")
GOOGLE_MODEL = os.getenv("GOOGLE_MODEL", "gemini-2.0-flash")
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o")
OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "llama3")
NVIDIA_MODEL = os.getenv("NVIDIA_MODEL", "moonshotai/kimi-k2.5")
@@ -66,14 +78,14 @@ def get_db():
conn.row_factory = sqlite3.Row
return conn
def get_system_prompt():
def get_system_prompt(current_user=WEB_USER_NAME):
prompt_path = CONFIG_DIR / "system_prompt.txt"
prompt = prompt_path.read_text(encoding="utf-8") if prompt_path.exists() else f"Hallo {WEB_USER_NAME}, ich bin J.A.R.V.I.S."
if prompt_path.exists():
prompt = prompt_path.read_text(encoding="utf-8")
else:
prompt = f"Hallo {current_user}, ich bin J.A.R.V.I.S., dein lokaler Systemassistent."
prompt = prompt.replace("{user_name}", WEB_USER_NAME)
prompt = prompt.replace("{workspace_dir}", str(WORKSPACE_DIR))
prompt = prompt.replace("{notes_file}", str(NOTES_FILE))
prompt = prompt.replace("{todo_file}", str(TODO_FILE))
prompt = prompt.replace("{user_name}", current_user)
conn = get_db()
nodes = conn.execute('SELECT * FROM nodes').fetchall()
@@ -81,9 +93,15 @@ def get_system_prompt():
node_info = ""
for n in nodes:
node_info += f"- Name: {n['name']}, IP: {n['ip']}, User: {n['user']}\n"
docker_str = "Ja" if n['docker_installed'] else "Nein"
node_info += f"- Name: {n['name']}, IP: {n['ip']}, User: {n['user']}, OS: {n['os']}, Arch: {n['arch']}, Docker: {docker_str}\n"
return prompt.replace("{node_info}", node_info)
prompt = prompt.replace("{node_info}", node_info)
prompt = prompt.replace("{workspace_dir}", str(WORKSPACE_DIR))
prompt = prompt.replace("{notes_file}", str(NOTES_FILE))
prompt = prompt.replace("{todo_file}", str(TODO_FILE))
return prompt
# --- KI KOMMUNIKATION ---
async def get_ai_response(user_msg, system_prompt, history_list):
@@ -104,11 +122,14 @@ async def get_ai_response(user_msg, system_prompt, history_list):
return response.choices[0].message.content
elif AI_PROVIDER == "google":
if not GOOGLE_API_KEY: return "Fehler: GOOGLE_API_KEY fehlt!"
client = genai.Client(api_key=GOOGLE_API_KEY)
google_history = [
types.Content(role="user" if msg["role"] == "user" else "model", parts=[types.Part.from_text(text=msg["content"])])
for msg in history_list[:-1]
]
chat = client.chats.create(
model=GOOGLE_MODEL,
config=types.GenerateContentConfig(system_instruction=system_prompt),
@@ -117,84 +138,84 @@ async def get_ai_response(user_msg, system_prompt, history_list):
return chat.send_message(user_msg).text
except Exception as e:
return f"Fehler bei der KI-Anfrage: {e}"
return f"{Color.ERROR}Fehler bei der KI-Anfrage: {e}{Color.RESET}"
# --- BEFEHLSAUSFÜHRUNG (Lokal & Remote) ---
async def run_task(target, cmd):
print(f"\n⚙️ J.A.R.V.I.S. führt aus auf [{target}]: {cmd}")
# --- SSH BEFEHLSAUSFÜHRUNG ---
async def run_remote_task(target, cmd):
print(f"\n{Color.EXEC}🚀 J.A.R.V.I.S. führt aus auf {target}:{Color.RESET}\n> {cmd}\n")
conn = get_db()
n = conn.execute('SELECT * FROM nodes WHERE ip=? OR name=?', (target, target)).fetchone()
conn.close()
if not n:
msg = f"{Color.ERROR}⚠️ Node '{target}' nicht in der Datenbank gefunden.{Color.RESET}"
print(msg)
return msg
try:
if target.lower() == "localhost" or target == "127.0.0.1":
# Lokale Ausführung (für Notizen, Workspace etc.)
proc = await asyncio.create_subprocess_shell(cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT)
else:
# Remote Ausführung via SSH
conn = get_db()
n = conn.execute('SELECT * FROM nodes WHERE ip=? OR name=?', (target, target)).fetchone()
conn.close()
if not n:
return f"⚠️ Node '{target}' nicht in der DB gefunden."
ssh_cmd = f"ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR {n['user']}@{n['ip']} '{cmd}'"
proc = await asyncio.create_subprocess_shell(ssh_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT)
proc = await asyncio.create_subprocess_shell(
f"ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR {n['user']}@{n['ip']} '{cmd}'",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT
)
stdout, _ = await proc.communicate()
output = stdout.decode('utf-8', errors='ignore').strip()
print(f"💻 Output:\n{output or '✅ Erfolgreich ausgeführt.'}\n")
print(f"{Color.EXEC}💻 Output:\n{output or '✅ Ohne Output ausgeführt.'}{Color.RESET}\n")
return output
except Exception as e:
err = f"❌ Fehler: {e}"
err = f"{Color.ERROR}❌ Fehler: {e}{Color.RESET}"
print(err)
return err
# --- MODULARE I/O SCHNITTSTELLEN (Für spätere Sprachsteuerung) ---
async def listen_to_user():
# Später: Hier Mikrofon-Aufnahme und Vosk-Transkription einbauen
return await asyncio.to_thread(input, "\nDu: ")
async def speak_to_user(text):
# Später: Hier Piper TTS einbauen, um den Text vorzulesen
print(f"\n🤖 J.A.R.V.I.S.:\n{text}")
# --- HAUPT-LOOP ---
# --- HAUPT-TERMINAL-LOOP ---
async def main_chat_loop():
print("====================================================")
print("🤖 J.A.R.V.I.S. Terminal Interface geladen.")
print(f"Provider: {AI_PROVIDER.upper()}")
print("Tippe 'exit', um zu beenden.")
print("====================================================")
# Bunter Boot-Screen
print(f"{Color.SYSTEM}===================================================={Color.RESET}")
print(f"{Color.BOLD}{Color.JARVIS}🤖 J.A.R.V.I.S. Terminal Interface geladen.{Color.RESET}")
print(f"{Color.SYSTEM}Provider: {AI_PROVIDER.upper()}")
print(f"Tippe 'exit' oder 'quit' um zu beenden.{Color.RESET}")
print(f"{Color.SYSTEM}===================================================={Color.RESET}\n")
chat_history = []
system_prompt = get_system_prompt()
while True:
user_msg = await listen_to_user()
# User Eingabe abwarten (Blau und Fett)
user_msg = await asyncio.to_thread(input, f"\n{Color.BOLD}{Color.USER}Du: {Color.RESET}")
if user_msg.lower().strip() in ['exit', 'quit']:
print("J.A.R.V.I.S. geht offline. Auf Wiedersehen!")
print(f"{Color.SYSTEM}J.A.R.V.I.S. wird beendet. Auf Wiedersehen!{Color.RESET}")
break
if not user_msg.strip():
continue
now = datetime.now().strftime("%d.%m.%Y %H:%M")
chat_history.append({"role": "user", "content": user_msg, "timestamp": now})
print("...", end="\r") # Kleiner Ladeindikator
system_prompt = get_system_prompt()
# Ladeindikator
print(f"{Color.SYSTEM}J.A.R.V.I.S. verarbeitet...{Color.RESET}", end="\r")
# KI antworten lassen
ai_response = await get_ai_response(user_msg, system_prompt, chat_history)
# XML-Befehle extrahieren und aus dem sichtbaren Text entfernen
# Nach <EXECUTE> Tags suchen
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 speak_to_user(clean_msg)
chat_history.append({"role": "assistant", "content": clean_msg, "timestamp": now})
# Ladezeile säubern und KI-Antwort ausgeben (Grün)
print("\r" + " " * 30 + "\r", end="")
print(f"{Color.BOLD}{Color.JARVIS}🤖 J.A.R.V.I.S.:{Color.RESET}")
print(f"{Color.JARVIS}{clean_msg}{Color.RESET}")
chat_history.append({"role": "assistant", "content": clean_msg, "timestamp": now})
# Ausführung der gefundenen Befehle
if commands:
for target, cmd in commands:
output = await run_task(target.strip(), cmd.strip())
output = await run_remote_task(target.strip(), cmd.strip())
# System-Output ebenfalls in die Historie packen
sys_now = datetime.now().strftime("%d.%m.%Y %H:%M")
chat_history.append({
"role": "user",
@@ -202,6 +223,7 @@ async def main_chat_loop():
"timestamp": sys_now
})
# RAM-Schutz: Historie nicht ins Unendliche wachsen lassen
if len(chat_history) > 20:
chat_history = chat_history[-20:]
@@ -209,4 +231,4 @@ if __name__ == "__main__":
try:
asyncio.run(main_chat_loop())
except KeyboardInterrupt:
print("\nJ.A.R.V.I.S. hart beendet.")
print(f"\n{Color.ERROR}J.A.R.V.I.S. hart beendet.{Color.RESET}")