File size: 6,476 Bytes
00ace63 029df55 24753ba 9370c0a 24753ba 9370c0a 24753ba b586b7b 24753ba cae6054 24753ba 1e7df8d 9370c0a c2d2189 9370c0a c517565 9370c0a c517565 9370c0a 9534de3 9370c0a c151194 00ace63 24753ba cae6054 24753ba 9370c0a 24753ba cae6054 9370c0a cae6054 9370c0a 00ace63 9370c0a cae6054 9370c0a 24753ba cae6054 00ace63 35f9d6c cae6054 00ace63 cae6054 00ace63 35f9d6c 00ace63 cae6054 00ace63 cae6054 24753ba cae6054 24753ba 00ace63 cae6054 24753ba 1fbd132 00ace63 24753ba cae6054 24753ba cae6054 00ace63 cae6054 00ace63 cae6054 00ace63 cae6054 00ace63 cae6054 00ace63 cae6054 00ace63 cae6054 00ace63 cae6054 00ace63 cae6054 00ace63 cae6054 00ace63 cae6054 00ace63 9370c0a 00ace63 cae6054 00ace63 9370c0a 00ace63 9370c0a 00ace63 b2d8537 00ace63 cae6054 00ace63 cae6054 d9fbc4f 9370c0a cae6054 c151194 33b8aa6 cae6054 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# 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)
|