Files
jarvis-ai/jarvis.py

212 lines
7.9 KiB
Python

import os
import re
import sqlite3
import asyncio
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
# --- PFADE & SETUP ---
BASE_DIR = Path(__file__).resolve().parent
CONFIG_DIR = BASE_DIR / "config"
DATA_DIR = BASE_DIR / "data"
WORKSPACE_DIR = BASE_DIR / "workspace"
ENV_FILE = CONFIG_DIR / ".env"
load_dotenv(ENV_FILE)
DB_PATH = DATA_DIR / "cluster.db"
NOTES_FILE = WORKSPACE_DIR / "NOTIZEN.md"
TODO_FILE = WORKSPACE_DIR / "TODO.md"
WEB_USER_NAME = os.getenv("WEB_USER_NAME", "Meik")
# Ordner & Dateien anlegen
for d in [WORKSPACE_DIR, DATA_DIR, CONFIG_DIR]:
d.mkdir(parents=True, exist_ok=True)
for f in [NOTES_FILE, TODO_FILE]:
if not f.exists():
f.write_text(f"# {f.name}\nHier fängt dein Gedächtnis an, J.A.R.V.I.S.\n", encoding="utf-8")
# --- KI KONFIGURATION ---
AI_PROVIDER = os.getenv("AI_PROVIDER", "google").lower()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
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")
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")
# --- DATENBANK ---
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.execute('''
CREATE TABLE IF NOT EXISTS nodes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT, ip TEXT UNIQUE, user TEXT, sudo_password TEXT,
os TEXT DEFAULT 'Unbekannt', arch TEXT DEFAULT 'Unbekannt',
docker_installed INTEGER DEFAULT 0, status TEXT
)
''')
conn.commit()
conn.close()
init_db()
def get_db():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def get_system_prompt():
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."
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))
conn = get_db()
nodes = conn.execute('SELECT * FROM nodes').fetchall()
conn.close()
node_info = ""
for n in nodes:
node_info += f"- Name: {n['name']}, IP: {n['ip']}, User: {n['user']}\n"
return prompt.replace("{node_info}", node_info)
# --- KI KOMMUNIKATION ---
async def get_ai_response(user_msg, system_prompt, history_list):
try:
if AI_PROVIDER in ["openai", "ollama", "nvidia"]:
messages = [{"role": "system", "content": system_prompt}] + history_list
if AI_PROVIDER == "ollama":
url = OLLAMA_BASE_URL if OLLAMA_BASE_URL.endswith('/v1') else OLLAMA_BASE_URL.rstrip('/') + '/v1'
key, model_to_use = "ollama", OLLAMA_MODEL
elif AI_PROVIDER == "nvidia":
url, key, model_to_use = "https://integrate.api.nvidia.com/v1", NVIDIA_API_KEY, NVIDIA_MODEL
else:
url, key, model_to_use = None, OPENAI_API_KEY, OPENAI_MODEL
client = openai.AsyncOpenAI(base_url=url, api_key=key)
response = await client.chat.completions.create(model=model_to_use, messages=messages)
return response.choices[0].message.content
elif AI_PROVIDER == "google":
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),
history=google_history
)
return chat.send_message(user_msg).text
except Exception as e:
return f"Fehler bei der KI-Anfrage: {e}"
# --- 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}")
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)
stdout, _ = await proc.communicate()
output = stdout.decode('utf-8', errors='ignore').strip()
print(f"💻 Output:\n{output or '✅ Erfolgreich ausgeführt.'}\n")
return output
except Exception as e:
err = f"❌ Fehler: {e}"
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 ---
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("====================================================")
chat_history = []
while True:
user_msg = await listen_to_user()
if user_msg.lower().strip() in ['exit', 'quit']:
print("J.A.R.V.I.S. geht offline. Auf Wiedersehen!")
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()
ai_response = await get_ai_response(user_msg, system_prompt, chat_history)
# XML-Befehle extrahieren und aus dem sichtbaren Text entfernen
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})
if commands:
for target, cmd in commands:
output = await run_task(target.strip(), cmd.strip())
sys_now = datetime.now().strftime("%d.%m.%Y %H:%M")
chat_history.append({
"role": "user",
"content": f"[SYSTEM] Befehl '{cmd}' auf {target} abgeschlossen. Output:\n{output}",
"timestamp": sys_now
})
if len(chat_history) > 20:
chat_history = chat_history[-20:]
if __name__ == "__main__":
try:
asyncio.run(main_chat_loop())
except KeyboardInterrupt:
print("\nJ.A.R.V.I.S. hart beendet.")