From b3f6ecb1bca12f4ef4422f3b4c8f964bcfa953b8 Mon Sep 17 00:00:00 2001 From: st-server Date: Tue, 6 Jan 2026 15:26:14 +0100 Subject: [PATCH] Initial implementation of the chat application with MariaDB and ArangoDB integration, including Docker setup and web interface. --- .gitignore | 2 + api/Dockerfile | 6 +++ api/app.py | 70 ++++++++++++++++++++++++++++++ api/requirements.txt | 4 ++ docker-compose.yml | 96 +++++++++++++++++++++++++++++++++++++++++ initdb/init.sql | 15 +++++++ web/Dockerfile | 2 + web/index.html | 17 ++++++++ web/scripts.js | 12 ++++++ web/style.css | 4 ++ worker/Dockerfile | 6 +++ worker/requirements.txt | 4 ++ worker/worker.py | 58 +++++++++++++++++++++++++ 13 files changed, 296 insertions(+) create mode 100644 .gitignore create mode 100644 api/Dockerfile create mode 100644 api/app.py create mode 100644 api/requirements.txt create mode 100644 docker-compose.yml create mode 100644 initdb/init.sql create mode 100644 web/Dockerfile create mode 100644 web/index.html create mode 100644 web/scripts.js create mode 100644 web/style.css create mode 100644 worker/Dockerfile create mode 100644 worker/requirements.txt create mode 100644 worker/worker.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4aed35b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +data/arangodb/ +data/mariadb/ diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000..bd171e5 --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.11-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY app.py . +CMD ["python", "app.py"] diff --git a/api/app.py b/api/app.py new file mode 100644 index 0000000..24e3008 --- /dev/null +++ b/api/app.py @@ -0,0 +1,70 @@ +from flask import Flask, request, jsonify +import mysql.connector +import requests +import os +from arango import ArangoClient + +app = Flask(__name__) + +# MariaDB Verbindung +def get_db(): + return mysql.connector.connect( + host=os.environ['DB_HOST'], + user=os.environ['DB_USER'], + password=os.environ['DB_PASSWORD'], + database=os.environ['DB_NAME'] + ) + +# ArangoDB Verbindung +def get_arango(): + client = ArangoClient(hosts=os.environ['ARANGO_URL']) + sys_db = client.db('_system', username=os.environ['ARANGO_USER'], password=os.environ['ARANGO_PASSWORD']) + if not sys_db.has_database('llm_facts'): + sys_db.create_database('llm_facts') + db = client.db('llm_facts', username=os.environ['ARANGO_USER'], password=os.environ['ARANGO_PASSWORD']) + if not db.has_collection('facts'): + db.create_collection('facts') + return db.collection('facts') + +# Skill-Trigger +def extract_fact(text): + triggers = ["merke dir", "notiere", "speichere"] + for t in triggers: + if text.lower().startswith(t): + fact = text[len(t):].strip() + return fact + return None + +@app.route("/chat", methods=["POST"]) +def chat(): + data = request.json + project = data.get("project") + message = data.get("message") + + # Lernbefehl + fact = extract_fact(message) + if fact: + arango = get_arango() + arango.insert({ + "_raw": message, + "_source": project, + "content": fact, + "tags": ["skill:merke"], + "relations": [] + }) + return jsonify({"reply": f"Fakt gespeichert: {fact}"}) + + # Chat speichern in MariaDB + db = get_db() + cursor = db.cursor() + cursor.execute("INSERT INTO chats (project, user_input) VALUES (%s,%s)", (project, message)) + db.commit() + cursor.close() + db.close() + + # Anfrage an LLM + resp = requests.post(f"{os.environ['LM_API_URL']}/generate", json={"prompt": message}) + return jsonify({"reply": resp.json()}) + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000) diff --git a/api/requirements.txt b/api/requirements.txt new file mode 100644 index 0000000..ccd4a16 --- /dev/null +++ b/api/requirements.txt @@ -0,0 +1,4 @@ +flask +mysql-connector-python +requests +python-arango diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c905c84 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,96 @@ +services: + myki-mariadb: + image: mariadb:11 + container_name: myki-mariadb + restart: always + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: llm_projects + MYSQL_USER: llmuser + MYSQL_PASSWORD: llmpassword + ports: + - "3306:3306" + volumes: + - ./data/mariadb:/var/lib/mysql + - ./initdb:/docker-entrypoint-initdb.d # SQL-Dateien werden hier automatisch beim ersten Start ausgeführt + + myki-phpmyadmin: + image: phpmyadmin/phpmyadmin + container_name: myki-phpmyadmin + restart: always + environment: + PMA_HOST: mariadb + PMA_USER: llmuser + PMA_PASSWORD: llmpassword + ports: + - "8081:80" + depends_on: + - myki-mariadb + + myki-arangodb: + image: arangodb:3.12 + container_name: myki-arangodb + restart: always + environment: + ARANGO_ROOT_PASSWORD: rootpassword + ports: + - "8529:8529" + volumes: + - ./data/arangodb:/var/lib/arangodb3 + - ./initdb-arango:/docker-entrypoint-initdb.d # Initialisierung + +# myki-lmstudio: +# image: ghcr.io/nomic-ai/ministral-3-14b-instruct-2512 +# container_name: myki-lmstudio +# environment: +# LMSTUDIO_API: "1" +# ports: +# - "8080:8080" +# volumes: +# - ./lmstudio/models:/models + + myki-api: + build: ./api + container_name: myki-api + depends_on: + - myki-mariadb + - myki-arangodb + #- myki-lmstudio + environment: + DB_HOST: mariadb + DB_USER: llmuser + DB_PASSWORD: llmpassword + DB_NAME: llm_projects + #LM_API_URL: http://lmstudio:8080 + LM_API_URL: http://host.docker.internal:1234 + ARANGO_URL: http://arangodb:8529 + ARANGO_USER: root + ARANGO_PASSWORD: rootpassword + ports: + - "5000:5000" + + myki-worker: + build: ./worker + container_name: myki-worker + depends_on: + - myki-api + - myki-mariadb + - myki-arangodb + #- lmstudio + environment: + DB_HOST: mariadb + DB_USER: llmuser + DB_PASSWORD: llmpassword + DB_NAME: llm_projects + LM_API_URL: http://lmstudio:8080 + ARANGO_URL: http://arangodb:8529 + ARANGO_USER: root + ARANGO_PASSWORD: rootpassword + + web: + build: ./web + container_name: llm_web + depends_on: + - myki-api + ports: + - "3001:3000" diff --git a/initdb/init.sql b/initdb/init.sql new file mode 100644 index 0000000..883ce0d --- /dev/null +++ b/initdb/init.sql @@ -0,0 +1,15 @@ +CREATE TABLE chats ( + id INT AUTO_INCREMENT PRIMARY KEY, + project VARCHAR(255), + user_input TEXT, + llm_output TEXT, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE feedback ( + id INT AUTO_INCREMENT PRIMARY KEY, + chat_id INT, + rating ENUM('positive','negative'), + comment TEXT, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 0000000..6e8acef --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx:alpine +COPY . /usr/share/nginx/html diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..1840a44 --- /dev/null +++ b/web/index.html @@ -0,0 +1,17 @@ + + + + +Projekt-LLM Chat + + + +
+ +
+ + +
+ + + diff --git a/web/scripts.js b/web/scripts.js new file mode 100644 index 0000000..7d9a318 --- /dev/null +++ b/web/scripts.js @@ -0,0 +1,12 @@ +async function sendMessage() { + const project = document.getElementById('project').value; + const message = document.getElementById('message').value; + const res = await fetch('http://localhost:5000/chat', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({project, message}) + }); + const data = await res.json(); + const messagesDiv = document.getElementById('messages'); + messagesDiv.innerHTML += `

Du: ${message}

LLM: ${data.reply}

`; +} diff --git a/web/style.css b/web/style.css new file mode 100644 index 0000000..c859149 --- /dev/null +++ b/web/style.css @@ -0,0 +1,4 @@ +#chat-container { width: 500px; margin: auto; } +#messages { border: 1px solid #ccc; height: 400px; overflow-y: scroll; padding: 5px; margin-bottom: 5px; } +input { width: 80%; margin-bottom: 5px; } +button { width: 18%; } diff --git a/worker/Dockerfile b/worker/Dockerfile new file mode 100644 index 0000000..7bf1623 --- /dev/null +++ b/worker/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.11-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY worker.py . +CMD ["python", "worker.py"] diff --git a/worker/requirements.txt b/worker/requirements.txt new file mode 100644 index 0000000..fa0b1ea --- /dev/null +++ b/worker/requirements.txt @@ -0,0 +1,4 @@ +mysql-connector-python +requests +python-arango +schedule diff --git a/worker/worker.py b/worker/worker.py new file mode 100644 index 0000000..1bc27e0 --- /dev/null +++ b/worker/worker.py @@ -0,0 +1,58 @@ +import os +import mysql.connector +import requests +from arango import ArangoClient +import schedule +import time + +# MariaDB +def get_db(): + return mysql.connector.connect( + host=os.environ['DB_HOST'], + user=os.environ['DB_USER'], + password=os.environ['DB_PASSWORD'], + database=os.environ['DB_NAME'] + ) + +# ArangoDB +def get_arango(): + client = ArangoClient(hosts=os.environ['ARANGO_URL']) + db = client.db('llm_facts', username=os.environ['ARANGO_USER'], password=os.environ['ARANGO_PASSWORD']) + return db.collection('facts') + +# Analysiere neue Chats und überführe sie in ArangoDB +def analyze_chats(): + db = get_db() + cursor = db.cursor(dictionary=True) + cursor.execute("SELECT * FROM chats WHERE llm_output IS NULL") + chats = cursor.fetchall() + + arango = get_arango() + + for chat in chats: + # Anfrage an LLM zur Analyse / Fakt-Extraktion + resp = requests.post(f"{os.environ['LM_API_URL']}/generate", json={"prompt": f"Extrahiere Fakten und Kontext aus diesem Text:\n{chat['user_input']}"}) + fact = resp.json() + + # Speichern in ArangoDB + arango.insert({ + "_raw": chat['user_input'], + "_source": chat['project'], + "content": fact, + "tags": ["auto"], + "relations": [] + }) + + # Update llm_output in MariaDB + cursor.execute("UPDATE chats SET llm_output=%s WHERE id=%s", (fact, chat['id'])) + + db.commit() + cursor.close() + db.close() + +# Alle 30 Sekunden ausführen +schedule.every(30).seconds.do(analyze_chats) + +while True: + schedule.run_pending() + time.sleep(1)