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 os
import re
import sqlite3
import asyncio import asyncio
import sqlite3
import re
from datetime import datetime
from pathlib import Path
import subprocess import subprocess
import openai import openai
from google import genai from google import genai
from google.genai import types from google.genai import types
from datetime import datetime
from pathlib import Path
from dotenv import load_dotenv 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 --- # --- PFADE & SETUP ---
BASE_DIR = Path(__file__).resolve().parent BASE_DIR = Path(__file__).resolve().parent
CONFIG_DIR = BASE_DIR / "config" ROOT_DIR = BASE_DIR.parent
DATA_DIR = BASE_DIR / "data" CONFIG_DIR = ROOT_DIR / "config"
WORKSPACE_DIR = BASE_DIR / "workspace" DATA_DIR = ROOT_DIR / "data"
WORKSPACE_DIR = ROOT_DIR / "workspace"
ENV_FILE = CONFIG_DIR / ".env" ENV_FILE = CONFIG_DIR / ".env"
load_dotenv(ENV_FILE) load_dotenv(ENV_FILE)
DB_PATH = DATA_DIR / "cluster.db" DB_PATH = DATA_DIR / "cluster.db"
PROMPT_FILE = CONFIG_DIR / "system_prompt.txt"
NOTES_FILE = WORKSPACE_DIR / "NOTIZEN.md" NOTES_FILE = WORKSPACE_DIR / "NOTIZEN.md"
TODO_FILE = WORKSPACE_DIR / "TODO.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 # Ordner & Dateien anlegen
for d in [WORKSPACE_DIR, DATA_DIR, CONFIG_DIR]: 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", "") NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY", "")
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://127.0.0.1:11434/v1") 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") OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o")
OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "llama3") OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "llama3")
NVIDIA_MODEL = os.getenv("NVIDIA_MODEL", "moonshotai/kimi-k2.5") NVIDIA_MODEL = os.getenv("NVIDIA_MODEL", "moonshotai/kimi-k2.5")
@@ -66,14 +78,14 @@ def get_db():
conn.row_factory = sqlite3.Row conn.row_factory = sqlite3.Row
return conn return conn
def get_system_prompt(): def get_system_prompt(current_user=WEB_USER_NAME):
prompt_path = CONFIG_DIR / "system_prompt.txt" 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("{user_name}", current_user)
prompt = prompt.replace("{workspace_dir}", str(WORKSPACE_DIR))
prompt = prompt.replace("{notes_file}", str(NOTES_FILE))
prompt = prompt.replace("{todo_file}", str(TODO_FILE))
conn = get_db() conn = get_db()
nodes = conn.execute('SELECT * FROM nodes').fetchall() nodes = conn.execute('SELECT * FROM nodes').fetchall()
@@ -81,9 +93,15 @@ def get_system_prompt():
node_info = "" node_info = ""
for n in nodes: 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 --- # --- KI KOMMUNIKATION ---
async def get_ai_response(user_msg, system_prompt, history_list): 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 return response.choices[0].message.content
elif AI_PROVIDER == "google": elif AI_PROVIDER == "google":
if not GOOGLE_API_KEY: return "Fehler: GOOGLE_API_KEY fehlt!"
client = genai.Client(api_key=GOOGLE_API_KEY) client = genai.Client(api_key=GOOGLE_API_KEY)
google_history = [ google_history = [
types.Content(role="user" if msg["role"] == "user" else "model", parts=[types.Part.from_text(text=msg["content"])]) types.Content(role="user" if msg["role"] == "user" else "model", parts=[types.Part.from_text(text=msg["content"])])
for msg in history_list[:-1] for msg in history_list[:-1]
] ]
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),
@@ -117,84 +138,84 @@ async def get_ai_response(user_msg, system_prompt, history_list):
return chat.send_message(user_msg).text return chat.send_message(user_msg).text
except Exception as e: 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) --- # --- SSH BEFEHLSAUSFÜHRUNG ---
async def run_task(target, cmd): async def run_remote_task(target, cmd):
print(f"\n⚙️ J.A.R.V.I.S. führt aus auf [{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: try:
if target.lower() == "localhost" or target == "127.0.0.1": proc = await asyncio.create_subprocess_shell(
# Lokale Ausführung (für Notizen, Workspace etc.) f"ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR {n['user']}@{n['ip']} '{cmd}'",
proc = await asyncio.create_subprocess_shell(cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) stdout=asyncio.subprocess.PIPE,
else: stderr=asyncio.subprocess.STDOUT
# 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)
stdout, _ = await proc.communicate() stdout, _ = await proc.communicate()
output = stdout.decode('utf-8', errors='ignore').strip() 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 return output
except Exception as e: except Exception as e:
err = f"❌ Fehler: {e}" err = f"{Color.ERROR}❌ Fehler: {e}{Color.RESET}"
print(err) print(err)
return err return err
# --- MODULARE I/O SCHNITTSTELLEN (Für spätere Sprachsteuerung) --- # --- HAUPT-TERMINAL-LOOP ---
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 ---
async def main_chat_loop(): async def main_chat_loop():
print("====================================================") # Bunter Boot-Screen
print("🤖 J.A.R.V.I.S. Terminal Interface geladen.") print(f"{Color.SYSTEM}===================================================={Color.RESET}")
print(f"Provider: {AI_PROVIDER.upper()}") print(f"{Color.BOLD}{Color.JARVIS}🤖 J.A.R.V.I.S. Terminal Interface geladen.{Color.RESET}")
print("Tippe 'exit', um zu beenden.") print(f"{Color.SYSTEM}Provider: {AI_PROVIDER.upper()}")
print("====================================================") print(f"Tippe 'exit' oder 'quit' um zu beenden.{Color.RESET}")
print(f"{Color.SYSTEM}===================================================={Color.RESET}\n")
chat_history = [] chat_history = []
system_prompt = get_system_prompt()
while True: 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']: 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 break
if not user_msg.strip(): if not user_msg.strip():
continue continue
now = datetime.now().strftime("%d.%m.%Y %H:%M") now = datetime.now().strftime("%d.%m.%Y %H:%M")
chat_history.append({"role": "user", "content": user_msg, "timestamp": now}) 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) 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) 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: # Ladezeile säubern und KI-Antwort ausgeben (Grün)
await speak_to_user(clean_msg) print("\r" + " " * 30 + "\r", end="")
chat_history.append({"role": "assistant", "content": clean_msg, "timestamp": now}) 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: if commands:
for target, cmd in 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") sys_now = datetime.now().strftime("%d.%m.%Y %H:%M")
chat_history.append({ chat_history.append({
"role": "user", "role": "user",
@@ -202,6 +223,7 @@ async def main_chat_loop():
"timestamp": sys_now "timestamp": sys_now
}) })
# RAM-Schutz: Historie nicht ins Unendliche wachsen lassen
if len(chat_history) > 20: if len(chat_history) > 20:
chat_history = chat_history[-20:] chat_history = chat_history[-20:]
@@ -209,4 +231,4 @@ if __name__ == "__main__":
try: try:
asyncio.run(main_chat_loop()) asyncio.run(main_chat_loop())
except KeyboardInterrupt: 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}")