chatbot / app.py
Nguyen5's picture
commit
e6bb64c
# app.py – Prüfungsrechts-Chatbot (RAG + Sprache, UI kiểu ChatGPT)
import gradio as gr
from gradio_pdf import PDF
from load_documents import load_all_documents
from split_documents import split_documents
from vectorstore import build_vectorstore
from retriever import get_retriever
from llm import load_llm
from rag_pipeline import answer
from speech_io import transcribe_audio, synthesize_speech
# =====================================================
# INITIALISIERUNG (global)
# =====================================================
print("📚 Lade Dokumente…")
docs = load_all_documents()
print("🔪 Splitte Dokumente…")
chunks = split_documents(docs)
print("🔍 Erstelle VectorStore…")
vs = build_vectorstore(chunks)
print("🔎 Erzeuge Retriever…")
retriever = get_retriever(vs)
print("🤖 Lade LLM…")
llm = load_llm()
# =====================================================
# Quellen formatieren – Markdown für Chat
# =====================================================
def format_sources(src):
if not src:
return ""
out = ["", "## 📚 Quellen"]
for s in src:
line = f"- [{s['source']}]({s['url']})"
if s.get("page") is not None:
line += f" (Seite {s['page']})"
out.append(line)
return "\n".join(out)
# =====================================================
# CORE CHAT-FUNKTION (MultimodalTextbox: Text + Audio)
# =====================================================
def chat_fn(message, history):
"""
message: dict {"text": str, "files": [...]} von gr.MultimodalTextbox
history: Liste von OpenAI-ähnlichen Messages (role, content)
"""
# 1) Text + evtl. Audio aus message holen
if isinstance(message, dict):
text = (message.get("text") or "").strip()
files = message.get("files") or []
else:
text = str(message or "").strip()
files = []
# Audio-Datei (vom Mikrofon) herausziehen
audio_path = None
for f in files:
# gr.MultimodalTextbox liefert i.d.R. Dict mit "path"
if isinstance(f, dict):
path = f.get("path")
else:
path = f
if isinstance(path, str) and path:
audio_path = path
break
# Wenn Audio vorhanden: transkribieren
if audio_path:
spoken = transcribe_audio(audio_path)
if text:
text = (text + " " + spoken).strip()
else:
text = spoken
if not text:
# Nichts zu tun
return history, None, {"text": "", "files": []}
# 2) RAG-Antwort berechnen
ans, sources = answer(text, retriever, llm)
bot_msg = ans + format_sources(sources)
# 3) History aktualisieren (ChatGPT-Style)
history = history + [
{"role": "user", "content": text},
{"role": "assistant", "content": bot_msg},
]
# 4) TTS für Antwort
tts_audio = synthesize_speech(bot_msg)
# 5) Input-Feld leeren
cleared_input = {"text": "", "files": []}
return history, tts_audio, cleared_input
# =====================================================
# LAST ANSWER → TTS (für Button "Antwort erneut vorlesen")
# =====================================================
def read_last_answer(history):
if not history:
return None
for msg in reversed(history):
if msg.get("role") == "assistant":
return synthesize_speech(msg.get("content", ""))
return None
# =====================================================
# UI – GRADIO
# =====================================================
with gr.Blocks(title="Prüfungsrechts-Chatbot (RAG + Sprache)") as demo:
gr.Markdown("# 🧑‍⚖️ Prüfungsrechts-Chatbot")
gr.Markdown(
"Dieser Chatbot beantwortet Fragen **ausschließlich** aus der "
"Prüfungsordnung (PDF) und dem Hochschulgesetz NRW. "
"Du kannst Text eingeben oder direkt ins Mikrofon sprechen."
)
with gr.Row():
# ===================== LINKER TEIL: Chat =====================
with gr.Column(scale=2):
chatbot = gr.Chatbot(
label="Chat",
height=500,
)
# Audio-Ausgabe (TTS)
voice_out = gr.Audio(label="Vorgelesene Antwort", type="numpy")
# Multimodal-Textbox mit Mikrofon in der Leiste
chat_input = gr.MultimodalTextbox(
label=None,
placeholder="Stelle deine Frage zum Prüfungsrecht … oder sprich ins Mikrofon",
show_label=False,
sources=["microphone"], # nur Mikrofon (kein Upload nötig)
file_types=["audio"],
max_lines=6,
)
# Senden bei Enter / Klick auf Icon
chat_input.submit(
chat_fn,
[chat_input, chatbot],
[chatbot, voice_out, chat_input],
)
send_btn = gr.Button("Senden")
send_btn.click(
chat_fn,
[chat_input, chatbot],
[chatbot, voice_out, chat_input],
)
# Button: Antwort erneut vorlesen
read_btn = gr.Button("🔁 Antwort erneut vorlesen")
read_btn.click(
read_last_answer,
[chatbot],
[voice_out],
)
# Chat löschen
clear_btn = gr.Button("Chat zurücksetzen")
clear_btn.click(
lambda: ([], None, {"text": "", "files": []}),
None,
[chatbot, voice_out, chat_input],
)
# ===================== RECHTER TEIL: Viewer =====================
with gr.Column(scale=1):
# PDF-URL aus metadata holen
pdf_meta = next(d.metadata for d in docs if d.metadata["type"] == "pdf")
gr.Markdown("### 📄 Prüfungsordnung (PDF)")
PDF(pdf_meta["pdf_url"], height=350)
# HG-Viewer-URL (hg_clean.html aus Supabase Storage)
hg_meta = next(d.metadata for d in docs if d.metadata["type"] == "hg")
hg_url = hg_meta["viewer_url"].split("#")[0]
gr.Markdown("### 📘 Hochschulgesetz NRW (Viewer)")
gr.HTML(
f'<iframe src="{hg_url}" '
'style="width:100%;height:350px;border:none;"></iframe>'
)
if __name__ == "__main__":
demo.queue().launch(ssr_mode=False, show_error=True)