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)