Nguyen5 commited on
Commit
00ace63
·
1 Parent(s): 8f9d0de
Files changed (1) hide show
  1. app.py +100 -86
app.py CHANGED
@@ -1,18 +1,7 @@
1
- # app.py – Prüfungsrechts-Chatbot (RAG + Sprachmodus)
2
- # Version 26.11 – ohne Modi, stabil für Text + Voice
3
 
4
  import gradio as gr
5
  from gradio_pdf import PDF
6
- from huggingface_hub import hf_hub_download
7
-
8
- # from load_documents import load_documents, DATASET, PDF_FILE, HTML_FILE
9
- # from split_documents import split_documents
10
- # from vectorstore import build_vectorstore
11
- # from retriever import get_retriever
12
- # from llm import load_llm
13
- # from rag_pipeline import answer, PDF_BASE_URL, LAW_URL
14
-
15
- # from speech_io import transcribe_audio, synthesize_speech
16
 
17
  from load_documents import load_all_documents
18
  from split_documents import split_documents
@@ -41,10 +30,10 @@ retriever = get_retriever(vs)
41
  print("🤖 Lade LLM…")
42
  llm = load_llm()
43
 
 
44
  # =====================================================
45
  # Quellen formatieren – Markdown für Chat
46
  # =====================================================
47
-
48
  def format_sources(src):
49
  if not src:
50
  return ""
@@ -53,140 +42,165 @@ def format_sources(src):
53
 
54
  for s in src:
55
  line = f"- [{s['source']}]({s['url']})"
56
- if s.get("page"):
57
  line += f" (Seite {s['page']})"
58
  out.append(line)
59
 
60
  return "\n".join(out)
61
 
62
- # ===================== TEXT CHAT =====================
63
-
64
- def chatbot_text(msg, history):
65
- if not msg:
66
- return history, ""
67
-
68
- ans, sources = answer(msg, retriever, llm)
69
- history.append({"role": "user", "content": msg})
70
- history.append({"role": "assistant", "content": ans + format_sources(sources)})
71
- return history, ""
72
-
73
- # ===================== VOICE CHAT =====================
74
-
75
- def chatbot_voice(audio, history):
76
- text = transcribe_audio(audio)
77
- history.append({"role": "user", "content": text})
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  ans, sources = answer(text, retriever, llm)
80
  bot_msg = ans + format_sources(sources)
81
 
82
- history.append({"role": "assistant", "content": bot_msg})
 
 
 
 
83
 
 
84
  tts_audio = synthesize_speech(bot_msg)
85
- return history, tts_audio, ""
 
 
 
 
86
 
87
 
88
  # =====================================================
89
- # LAST ANSWER → TTS
90
  # =====================================================
91
-
92
  def read_last_answer(history):
93
  if not history:
94
  return None
95
 
96
  for msg in reversed(history):
97
- if msg["role"] == "assistant":
98
- return synthesize_speech(msg["content"])
99
 
100
  return None
101
 
 
102
  # =====================================================
103
  # UI – GRADIO
104
  # =====================================================
105
-
106
  with gr.Blocks(title="Prüfungsrechts-Chatbot (RAG + Sprache)") as demo:
107
  gr.Markdown("# 🧑‍⚖️ Prüfungsrechts-Chatbot")
108
  gr.Markdown(
109
  "Dieser Chatbot beantwortet Fragen **ausschließlich** aus der "
110
- "Prüfungsordnung (PDF) und dem Hochschulgesetz NRW (Website). "
111
  "Du kannst Text eingeben oder direkt ins Mikrofon sprechen."
112
  )
113
 
114
  with gr.Row():
 
115
  with gr.Column(scale=2):
116
- chatbot = gr.Chatbot(label="Chat", height=500)
117
-
118
- msg = gr.Textbox(
119
- label="Frage eingeben",
120
- placeholder="Stelle deine Frage zum Prüfungsrecht …",
121
  )
122
 
123
- # TEXT SENDEN
124
- msg.submit(
125
- chatbot_text,
126
- [msg, chatbot],
127
- [chatbot, msg]
128
- )
129
 
130
- send_btn = gr.Button("Senden (Text)")
131
- send_btn.click(
132
- chatbot_text,
133
- [msg, chatbot],
134
- [chatbot, msg]
 
 
 
135
  )
136
 
137
- # SPRACHEINGABE
138
- gr.Markdown("### 🎙️ Spracheingabe")
139
- voice_in = gr.Audio(sources=["microphone"], type="filepath")
140
- voice_out = gr.Audio(label="Vorgelesene Antwort", type="numpy")
 
 
141
 
142
- voice_btn = gr.Button("Sprechen & senden")
143
- voice_btn.click(
144
- chatbot_voice,
145
- [voice_in, chatbot],
146
- [chatbot, voice_out, msg]
147
  )
148
 
 
149
  read_btn = gr.Button("🔁 Antwort erneut vorlesen")
150
  read_btn.click(
151
  read_last_answer,
152
  [chatbot],
153
- [voice_out]
154
  )
155
 
 
156
  clear_btn = gr.Button("Chat zurücksetzen")
157
- clear_btn.click(lambda: [], None, chatbot)
158
-
159
- # =====================
160
- # RECHTE SPALTE: Viewer
161
- # =====================
162
-
163
- # with gr.Column(scale=1):
164
- # gr.Markdown("### 📄 Prüfungsordnung (PDF)")
165
- # PDF(_pdf_path, height=350)
166
-
167
- # gr.Markdown("### 📘 Hochschulgesetz NRW (Website)")
168
- # gr.HTML(
169
- # f'<iframe src="{LAW_URL}" style="width:100%;height:350px;border:none;"></iframe>'
170
- # )
171
 
 
172
  with gr.Column(scale=1):
173
- gr.Markdown("### 📄 Prüfungsordnung (PDF)")
174
- # PDF đã được load_documents cung cấp pdf_url — dùng metadata trực tiếp
175
  pdf_meta = next(d.metadata for d in docs if d.metadata["type"] == "pdf")
 
176
  PDF(pdf_meta["pdf_url"], height=350)
177
 
178
- gr.Markdown("### 📘 Hochschulgesetz NRW")
179
  hg_meta = next(d.metadata for d in docs if d.metadata["type"] == "hg")
180
- # hg_view_url = hg_meta["viewer_url"].split("#")[0]
181
 
182
- hg_url = hg_meta["viewer_url"]
183
  gr.HTML(
184
  f'<iframe src="{hg_url}" '
185
  'style="width:100%;height:350px;border:none;"></iframe>'
186
  )
187
 
188
-
189
  if __name__ == "__main__":
190
  demo.queue().launch(ssr_mode=False, show_error=True)
191
-
192
-
 
1
+ # app.py – Prüfungsrechts-Chatbot (RAG + Sprache, UI kiểu ChatGPT)
 
2
 
3
  import gradio as gr
4
  from gradio_pdf import PDF
 
 
 
 
 
 
 
 
 
 
5
 
6
  from load_documents import load_all_documents
7
  from split_documents import split_documents
 
30
  print("🤖 Lade LLM…")
31
  llm = load_llm()
32
 
33
+
34
  # =====================================================
35
  # Quellen formatieren – Markdown für Chat
36
  # =====================================================
 
37
  def format_sources(src):
38
  if not src:
39
  return ""
 
42
 
43
  for s in src:
44
  line = f"- [{s['source']}]({s['url']})"
45
+ if s.get("page") is not None:
46
  line += f" (Seite {s['page']})"
47
  out.append(line)
48
 
49
  return "\n".join(out)
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ # =====================================================
53
+ # CORE CHAT-FUNKTION (MultimodalTextbox: Text + Audio)
54
+ # =====================================================
55
+ def chat_fn(message, history):
56
+ """
57
+ message: dict {"text": str, "files": [...]} von gr.MultimodalTextbox
58
+ history: Liste von OpenAI-ähnlichen Messages (role, content)
59
+ """
60
+ # 1) Text + evtl. Audio aus message holen
61
+ if isinstance(message, dict):
62
+ text = (message.get("text") or "").strip()
63
+ files = message.get("files") or []
64
+ else:
65
+ text = str(message or "").strip()
66
+ files = []
67
+
68
+ # Audio-Datei (vom Mikrofon) herausziehen
69
+ audio_path = None
70
+ for f in files:
71
+ # gr.MultimodalTextbox liefert i.d.R. Dict mit "path"
72
+ if isinstance(f, dict):
73
+ path = f.get("path")
74
+ else:
75
+ path = f
76
+ if isinstance(path, str) and path:
77
+ audio_path = path
78
+ break
79
+
80
+ # Wenn Audio vorhanden: transkribieren
81
+ if audio_path:
82
+ spoken = transcribe_audio(audio_path)
83
+ if text:
84
+ text = (text + " " + spoken).strip()
85
+ else:
86
+ text = spoken
87
+
88
+ if not text:
89
+ # Nichts zu tun
90
+ return history, None, {"text": "", "files": []}
91
+
92
+ # 2) RAG-Antwort berechnen
93
  ans, sources = answer(text, retriever, llm)
94
  bot_msg = ans + format_sources(sources)
95
 
96
+ # 3) History aktualisieren (ChatGPT-Style)
97
+ history = history + [
98
+ {"role": "user", "content": text},
99
+ {"role": "assistant", "content": bot_msg},
100
+ ]
101
 
102
+ # 4) TTS für Antwort
103
  tts_audio = synthesize_speech(bot_msg)
104
+
105
+ # 5) Input-Feld leeren
106
+ cleared_input = {"text": "", "files": []}
107
+
108
+ return history, tts_audio, cleared_input
109
 
110
 
111
  # =====================================================
112
+ # LAST ANSWER → TTS (für Button "Antwort erneut vorlesen")
113
  # =====================================================
 
114
  def read_last_answer(history):
115
  if not history:
116
  return None
117
 
118
  for msg in reversed(history):
119
+ if msg.get("role") == "assistant":
120
+ return synthesize_speech(msg.get("content", ""))
121
 
122
  return None
123
 
124
+
125
  # =====================================================
126
  # UI – GRADIO
127
  # =====================================================
 
128
  with gr.Blocks(title="Prüfungsrechts-Chatbot (RAG + Sprache)") as demo:
129
  gr.Markdown("# 🧑‍⚖️ Prüfungsrechts-Chatbot")
130
  gr.Markdown(
131
  "Dieser Chatbot beantwortet Fragen **ausschließlich** aus der "
132
+ "Prüfungsordnung (PDF) und dem Hochschulgesetz NRW. "
133
  "Du kannst Text eingeben oder direkt ins Mikrofon sprechen."
134
  )
135
 
136
  with gr.Row():
137
+ # ===================== LINKER TEIL: Chat =====================
138
  with gr.Column(scale=2):
139
+ chatbot = gr.Chatbot(
140
+ label="Chat",
141
+ height=500,
142
+ type="messages", # nutzt role/content-Struktur
 
143
  )
144
 
145
+ # Audio-Ausgabe (TTS)
146
+ voice_out = gr.Audio(label="Vorgelesene Antwort", type="numpy")
 
 
 
 
147
 
148
+ # Multimodal-Textbox mit Mikrofon in der Leiste
149
+ chat_input = gr.MultimodalTextbox(
150
+ label=None,
151
+ placeholder="Stelle deine Frage zum Prüfungsrecht … oder sprich ins Mikrofon",
152
+ show_label=False,
153
+ sources=["microphone"], # nur Mikrofon (kein Upload nötig)
154
+ file_types=["audio"],
155
+ max_lines=6,
156
  )
157
 
158
+ # Senden bei Enter / Klick auf Icon
159
+ chat_input.submit(
160
+ chat_fn,
161
+ [chat_input, chatbot],
162
+ [chatbot, voice_out, chat_input],
163
+ )
164
 
165
+ send_btn = gr.Button("Senden")
166
+ send_btn.click(
167
+ chat_fn,
168
+ [chat_input, chatbot],
169
+ [chatbot, voice_out, chat_input],
170
  )
171
 
172
+ # Button: Antwort erneut vorlesen
173
  read_btn = gr.Button("🔁 Antwort erneut vorlesen")
174
  read_btn.click(
175
  read_last_answer,
176
  [chatbot],
177
+ [voice_out],
178
  )
179
 
180
+ # Chat löschen
181
  clear_btn = gr.Button("Chat zurücksetzen")
182
+ clear_btn.click(
183
+ lambda: ([], None, {"text": "", "files": []}),
184
+ None,
185
+ [chatbot, voice_out, chat_input],
186
+ )
 
 
 
 
 
 
 
 
 
187
 
188
+ # ===================== RECHTER TEIL: Viewer =====================
189
  with gr.Column(scale=1):
190
+ # PDF-URL aus metadata holen
 
191
  pdf_meta = next(d.metadata for d in docs if d.metadata["type"] == "pdf")
192
+ gr.Markdown("### 📄 Prüfungsordnung (PDF)")
193
  PDF(pdf_meta["pdf_url"], height=350)
194
 
195
+ # HG-Viewer-URL (hg_clean.html aus Supabase Storage)
196
  hg_meta = next(d.metadata for d in docs if d.metadata["type"] == "hg")
197
+ hg_url = hg_meta["viewer_url"].split("#")[0]
198
 
199
+ gr.Markdown("### 📘 Hochschulgesetz NRW (Viewer)")
200
  gr.HTML(
201
  f'<iframe src="{hg_url}" '
202
  'style="width:100%;height:350px;border:none;"></iframe>'
203
  )
204
 
 
205
  if __name__ == "__main__":
206
  demo.queue().launch(ssr_mode=False, show_error=True)