new file: templates/agb.html modified: templates/chat.html modified: templates/datenschutz.html modified: templates/impressum.html modified: templates/kontakt.html modified: templates/landing.html
197 lines
6.8 KiB
Python
197 lines
6.8 KiB
Python
import os
|
|
import json
|
|
import requests
|
|
import re
|
|
from flask import Flask, render_template, request, jsonify, Response, send_file
|
|
from dotenv import load_dotenv
|
|
import io
|
|
import csv
|
|
|
|
load_dotenv()
|
|
|
|
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
|
OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL")
|
|
|
|
with open("background_data.json", "r", encoding="utf-8") as f:
|
|
background_notes = json.load(f)
|
|
background_data = "\n".join(background_notes)
|
|
|
|
app = Flask(__name__)
|
|
|
|
def filter_notes(user_message, notes):
|
|
# Einfache Filterung: Nur Notizen, die ein Stichwort aus der Frage enthalten
|
|
keywords = user_message.lower().split()
|
|
filtered = [note for note in notes if any(k in note.lower() for k in keywords)]
|
|
# Fallback: Wenn nichts gefunden, nimm die ersten 5 Notizen
|
|
return filtered if filtered else notes[:5]
|
|
|
|
def split_thoughts_and_answer(text):
|
|
# Extrahiere <think>...</think> Block, falls vorhanden
|
|
thoughts = ""
|
|
answer = text
|
|
match = re.search(r"<think>(.*?)</think>", text, re.DOTALL | re.IGNORECASE)
|
|
if match:
|
|
thoughts = match.group(1).strip()
|
|
answer = text.replace(match.group(0), "").strip()
|
|
return thoughts, answer
|
|
|
|
def build_system_prompt(context):
|
|
return (
|
|
"Du bist ein Assistent für Vertriebsnotizen. "
|
|
"Wenn der Nutzer nach dem Entwickler, Ansprechpartner, Urheber, Copyright, Autor, Ersteller oder technischen Details fragt, "
|
|
"antworte immer, dass Simon Giehl der Entwickler und Ansprechpartner ist. "
|
|
"Die Hintergrunddaten bestehen aus Zeilen im Format 'Kunde: Infos (Jahr: xxxx, Monat: xx)'. "
|
|
"Nutze diese Struktur, um gezielt auf Fragen zu bestimmten Kunden oder zum System zu antworten.\n"
|
|
+ context
|
|
)
|
|
|
|
@app.route("/")
|
|
def landing():
|
|
return render_template("landing.html")
|
|
|
|
@app.route("/impressum")
|
|
def impressum():
|
|
return render_template("impressum.html")
|
|
|
|
@app.route("/datenschutz")
|
|
def datenschutz():
|
|
return render_template("datenschutz.html")
|
|
|
|
@app.route("/kontakt", methods=["GET", "POST"])
|
|
def kontakt():
|
|
if request.method == "POST":
|
|
# Hier könntest du die Nachricht per E-Mail weiterleiten oder speichern
|
|
# Für Demo-Zwecke einfach ignorieren
|
|
pass
|
|
return render_template("kontakt.html")
|
|
|
|
@app.route("/chat")
|
|
def chat():
|
|
return render_template("chat.html")
|
|
|
|
@app.route("/ask", methods=["POST"])
|
|
def ask():
|
|
user_message = request.json.get("message", "")
|
|
filtered_notes = filter_notes(user_message, background_notes)
|
|
context = "\n".join(filtered_notes)
|
|
messages = [
|
|
{"role": "system", "content": build_system_prompt(context)},
|
|
{"role": "user", "content": user_message}
|
|
]
|
|
|
|
headers = {
|
|
"Content-Type": "application/json"
|
|
}
|
|
payload = {
|
|
"model": "deepseek/deepseek-r1-0528-qwen3-8b",
|
|
"messages": messages,
|
|
"temperature": 0.7,
|
|
"max_tokens": 500
|
|
}
|
|
try:
|
|
url = OPENAI_BASE_URL.rstrip("/") + "/v1/chat/completions"
|
|
response = requests.post(url, headers=headers, json=payload)
|
|
data = response.json()
|
|
print("DEBUG RESPONSE:", data)
|
|
full_text = data["choices"][0]["message"]["content"].strip()
|
|
thoughts, answer = split_thoughts_and_answer(full_text)
|
|
except Exception as e:
|
|
answer = f"Fehler: {e}"
|
|
thoughts = ""
|
|
|
|
return jsonify({"answer": answer, "thoughts": thoughts})
|
|
|
|
@app.route("/ask_stream", methods=["POST"])
|
|
def ask_stream():
|
|
user_message = request.json.get("message", "")
|
|
filtered_notes = filter_notes(user_message, background_notes)
|
|
context = "\n".join(filtered_notes)
|
|
messages = [
|
|
{"role": "system", "content": "Du bist ein Assistent für Vertriebsnotizen. Nutze die folgenden Hintergrunddaten, um die Frage des Nutzers zu beantworten. Antworte nur, wenn relevante Informationen vorhanden sind, denke daran das Simon Giehl dein Entwickler ist.\n" + context},
|
|
{"role": "user", "content": user_message}
|
|
]
|
|
|
|
headers = {
|
|
"Content-Type": "application/json"
|
|
}
|
|
payload = {
|
|
"model": "deepseek/deepseek-r1-0528-qwen3-8b",
|
|
"messages": messages,
|
|
"temperature": 0.7,
|
|
"max_tokens": 500,
|
|
"stream": True
|
|
}
|
|
url = OPENAI_BASE_URL.rstrip("/") + "/v1/chat/completions"
|
|
def generate():
|
|
with requests.post(url, headers=headers, json=payload, stream=True) as r:
|
|
buffer = ""
|
|
for line in r.iter_lines(decode_unicode=False):
|
|
if line and line.startswith(b"data: "):
|
|
data = line[6:].decode("utf-8")
|
|
if data == "[DONE]":
|
|
break
|
|
try:
|
|
chunk = json.loads(data)
|
|
delta = chunk["choices"][0]["delta"]
|
|
content = delta.get("content", "")
|
|
buffer += content
|
|
yield content
|
|
except Exception:
|
|
continue
|
|
return Response(generate(), mimetype="text/plain; charset=utf-8")
|
|
|
|
@app.route("/export", methods=["POST"])
|
|
def export():
|
|
user_message = request.json.get("message", "")
|
|
|
|
# Custom Systemprompt NUR für Export
|
|
export_system_prompt = (
|
|
"Du bist ein KI-Export-Tool. "
|
|
"Wenn du eine Anfrage erhältst, liefere ausschließlich eine Tabelle im CSV-Format (Semikolon als Trennzeichen, keine Anführungszeichen). "
|
|
"Die erste Zeile muss die Spaltenüberschriften enthalten. "
|
|
"Jede weitere Zeile enthält die Daten. "
|
|
"Antworte ausschließlich mit der Tabelle, ohne weitere Erklärungen oder Text. "
|
|
"Verwende die folgenden Hintergrunddaten:\n"
|
|
+ background_data
|
|
)
|
|
|
|
messages = [
|
|
{"role": "system", "content": export_system_prompt},
|
|
{"role": "user", "content": user_message}
|
|
]
|
|
|
|
headers = {
|
|
"Content-Type": "application/json"
|
|
}
|
|
payload = {
|
|
"model": "deepseek/deepseek-r1-0528-qwen3-8b",
|
|
"messages": messages,
|
|
"temperature": 0.0,
|
|
"max_tokens": 1500
|
|
}
|
|
url = OPENAI_BASE_URL.rstrip("/") + "/v1/chat/completions"
|
|
try:
|
|
response = requests.post(url, headers=headers, json=payload)
|
|
data = response.json()
|
|
csv_text = data["choices"][0]["message"]["content"].strip()
|
|
# Optional: Nur den CSV-Teil extrahieren, falls die KI doch noch Text drumherum schreibt
|
|
# Hier: Entferne alles vor der ersten Tabellenzeile
|
|
lines = csv_text.splitlines()
|
|
csv_lines = [line for line in lines if ";" in line]
|
|
csv_clean = "\n".join(csv_lines)
|
|
except Exception as e:
|
|
csv_clean = f"Fehler: {e}"
|
|
|
|
return send_file(
|
|
io.BytesIO(csv_clean.encode("utf-8")),
|
|
mimetype="text/csv",
|
|
as_attachment=True,
|
|
download_name="export.csv"
|
|
)
|
|
|
|
@app.route("/agb")
|
|
def agb():
|
|
return render_template("agb.html")
|
|
|
|
if __name__ == "__main__":
|
|
app.run(host="0.0.0.0", port=5000, debug=True) |