Nguyen5 commited on
Commit
11e64e1
·
1 Parent(s): b8e573b
Files changed (1) hide show
  1. app.py +95 -334
app.py CHANGED
@@ -3,7 +3,6 @@
3
  import os
4
  import gradio as gr
5
  from gradio_pdf import PDF
6
- import time
7
 
8
  from load_documents import load_all_documents
9
  from split_documents import split_documents
@@ -78,43 +77,23 @@ def chat_fn(text_input, audio_path, history):
78
 
79
  if not text:
80
  # Nichts zu tun
81
- yield history, None, "", None
82
- return
83
-
84
- # User-Nachricht zur History hinzufügen
85
- user_message = {"role": "user", "content": text}
86
-
87
- # Sofortige Anzeige der User-Nachricht
88
- history.append(user_message)
89
- yield history, None, "", None, gr.update(interactive=False), gr.update(interactive=False)
90
-
91
- # Kurze Verzögerung für Realismus
92
- time.sleep(0.1)
93
-
94
  # 2) RAG-Antwort berechnen
95
  ans, sources = answer(text, retriever, llm)
96
  bot_msg = ans + format_sources(sources)
97
-
98
- # Antwort stückweise zeigen (Streaming-Effekt)
99
- assistant_message = {"role": "assistant", "content": ""}
100
- history.append(assistant_message)
101
-
102
- # Simuliere Streaming-Effekt
103
- words = bot_msg.split()
104
- for i in range(0, len(words), 3):
105
- chunk = " ".join(words[i:i+3])
106
- assistant_message["content"] += chunk + " "
107
- yield history, None, "", None, gr.update(interactive=False), gr.update(interactive=False)
108
- time.sleep(0.05)
109
-
110
- # Vollständige Antwort setzen
111
- assistant_message["content"] = bot_msg
112
-
113
  # 4) TTS für Antwort
114
  tts_audio = synthesize_speech(bot_msg)
115
-
116
  # 5) Input-Felder leeren
117
- yield history, tts_audio, "", None, gr.update(interactive=True), gr.update(interactive=True)
118
 
119
 
120
  # =====================================================
@@ -132,319 +111,101 @@ def read_last_answer(history):
132
 
133
 
134
  # =====================================================
135
- # UI – GRADIO (ChatGPT Style)
136
  # =====================================================
137
- with gr.Blocks(
138
- title="Prüfungsrechts-Chatbot",
139
- theme=gr.themes.Soft(
140
- primary_hue="blue",
141
- secondary_hue="gray",
142
- neutral_hue="gray",
143
- radius_size=gr.themes.sizes.radius_md,
144
- font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
145
- ),
146
- css="""
147
- :root {
148
- --primary: #10a37f;
149
- --primary-dark: #0d8c6d;
150
- }
151
-
152
- .gradio-container {
153
- max-width: 900px !important;
154
- margin: 0 auto !important;
155
- padding: 20px !important;
156
- height: 100vh !important;
157
- }
158
-
159
- .chatbot {
160
- min-height: 500px;
161
- border: 1px solid #e5e7eb !important;
162
- border-radius: 12px !important;
163
- background: white !important;
164
- box-shadow: 0 1px 3px rgba(0,0,0,0.1) !important;
165
- }
166
-
167
- .chatbot .user, .chatbot .assistant {
168
- padding: 20px 24px !important;
169
- border-bottom: 1px solid #f3f4f6 !important;
170
- }
171
-
172
- .chatbot .user {
173
- background: #f9fafb !important;
174
- }
175
-
176
- .chatbot .assistant {
177
- background: white !important;
178
- }
179
-
180
- .input-container {
181
- position: sticky !important;
182
- bottom: 20px !important;
183
- background: white !important;
184
- border: 1px solid #e5e7eb !important;
185
- border-radius: 12px !important;
186
- padding: 4px !important;
187
- box-shadow: 0 4px 12px rgba(0,0,0,0.1) !important;
188
- margin-top: 20px !important;
189
- }
190
-
191
- .textbox-container {
192
- border: none !important;
193
- box-shadow: none !important;
194
- background: transparent !important;
195
- }
196
-
197
- .textbox-container textarea {
198
- font-size: 16px !important;
199
- line-height: 1.5 !important;
200
- padding: 12px 16px !important;
201
- border: none !important;
202
- background: transparent !important;
203
- resize: none !important;
204
- min-height: 56px !important;
205
- max-height: 200px !important;
206
- }
207
-
208
- .send-button {
209
- background: var(--primary) !important;
210
- color: white !important;
211
- border: none !important;
212
- border-radius: 8px !important;
213
- padding: 8px 16px !important;
214
- height: 40px !important;
215
- font-weight: 500 !important;
216
- }
217
-
218
- .send-button:hover {
219
- background: var(--primary-dark) !important;
220
- transform: translateY(-1px) !important;
221
- }
222
-
223
- .audio-button {
224
- background: white !important;
225
- border: 1px solid #d1d5db !important;
226
- border-radius: 8px !important;
227
- padding: 8px !important;
228
- height: 40px !important;
229
- width: 40px !important;
230
- }
231
-
232
- .audio-button:hover {
233
- background: #f9fafb !important;
234
- }
235
-
236
- .secondary-button {
237
- background: white !important;
238
- border: 1px solid #d1d5db !important;
239
- color: #374151 !important;
240
- border-radius: 8px !important;
241
- padding: 8px 16px !important;
242
- font-weight: 500 !important;
243
- }
244
-
245
- .secondary-button:hover {
246
- background: #f9fafb !important;
247
- }
248
-
249
- .header {
250
- text-align: center !important;
251
- margin-bottom: 24px !important;
252
- }
253
-
254
- .logo {
255
- font-size: 32px !important;
256
- margin-bottom: 8px !important;
257
- }
258
-
259
- .subtitle {
260
- color: #6b7280 !important;
261
- font-size: 14px !important;
262
- margin-bottom: 8px !important;
263
- }
264
-
265
- .warning-box {
266
- background: #fef3c7 !important;
267
- border: 1px solid #fbbf24 !important;
268
- border-radius: 8px !important;
269
- padding: 12px 16px !important;
270
- margin: 16px 0 !important;
271
- color: #92400e !important;
272
- font-size: 14px !important;
273
- }
274
-
275
- .footer {
276
- margin-top: 24px !important;
277
- padding-top: 16px !important;
278
- border-top: 1px solid #e5e7eb !important;
279
- color: #6b7280 !important;
280
- font-size: 12px !important;
281
- text-align: center !important;
282
- }
283
-
284
- .tts-container {
285
- margin-top: 12px !important;
286
- }
287
-
288
- .document-section {
289
- margin-top: 24px !important;
290
- }
291
- """
292
- ) as demo:
293
-
294
- # Header mit Logo und Beschreibung
295
- with gr.Column(elem_classes=["header"]):
296
- gr.HTML("""
297
- <div class="logo">🧑‍⚖️</div>
298
- <h1 style="margin: 0; font-size: 28px; font-weight: 600;">Prüfungsrechts-Chatbot</h1>
299
- <div class="subtitle">Intelligenter Assistent für Prüfungsordnung und Hochschulgesetz NRW</div>
300
- """)
301
-
302
- # Warning Box
303
- gr.HTML("""
304
- <div class="warning-box">
305
- ⚠️ Dieser Chatbot beantwortet Fragen <strong>ausschließlich</strong> aus der
306
- Prüfungsordnung (PDF) und dem Hochschulgesetz NRW.
307
- </div>
308
- """)
309
-
310
- # Haupt-Chat-Bereich
311
- chatbot = gr.Chatbot(
312
- label="",
313
- elem_classes=["chatbot"],
314
- height=500,
315
- show_label=False,
316
- avatar_images=(None, "🧑‍⚖️"),
317
- bubble_full_width=False,
318
- latex_delimiters=[
319
- {"left": "$$", "right": "$$", "display": True},
320
- {"left": "$", "right": "$", "display": False}
321
- ],
322
- render_markdown=True,
323
- show_copy_button=True
324
  )
325
-
326
- # TTS-Audio-Ausgabe
327
- with gr.Row(elem_classes=["tts-container"]):
328
- voice_out = gr.Audio(
329
- label="Vorgelesene Antwort",
330
- type="numpy",
331
- interactive=False,
332
- show_label=True,
333
- visible=True
334
  )
335
-
336
- # ChatGPT-ähnliche Eingabezeile
337
- with gr.Row(elem_classes=["input-container"]):
338
- with gr.Column(scale=1, min_width=50):
 
 
 
 
 
 
 
 
 
 
 
339
  chat_audio = gr.Audio(
340
- label="",
341
  sources=["microphone"],
342
  type="filepath",
343
  interactive=True,
 
344
  show_label=False,
345
- elem_classes=["audio-button"],
346
- show_download_button=False
347
  )
348
-
349
- with gr.Column(scale=9, elem_classes=["textbox-container"]):
350
- chat_text = gr.Textbox(
351
- label="",
352
- placeholder="Stelle deine Frage hier... (Drücke Enter zum Senden, Shift+Enter für neue Zeile)",
353
- lines=1,
354
- max_lines=5,
355
- autofocus=True,
356
- show_label=False,
357
- elem_id="chat-input"
358
- )
359
-
360
- with gr.Column(scale=1, min_width=80):
361
- send_btn = gr.Button(
362
- "Senden",
363
- variant="primary",
364
- elem_classes=["send-button"]
365
- )
366
-
367
- # Control Buttons
368
- with gr.Row():
369
- read_btn = gr.Button(
370
- "🔁 Antwort vorlesen",
371
- elem_classes=["secondary-button"],
372
- size="sm"
373
  )
374
- clear_btn = gr.Button(
375
- "🗑️ Chat löschen",
376
- elem_classes=["secondary-button"],
377
- size="sm"
 
378
  )
379
-
380
- # Dokumente in Accordion
381
- with gr.Accordion("📚 Quellen & Dokumente", open=False):
382
- with gr.Tabs():
383
- with gr.TabItem("📄 Prüfungsordnung"):
384
- PDF(pdf_meta["pdf_url"], height=350)
385
-
386
- with gr.TabItem("📘 Hochschulgesetz NRW"):
387
- if isinstance(hg_url, str) and hg_url.startswith("http"):
388
- gr.HTML(f"""
389
- <div style="padding: 20px;">
390
- <p style="margin-bottom: 16px;">Das Hochschulgesetz NRW kann online eingesehen werden:</p>
391
- <a href='{hg_url}' target='_blank' style='
392
- display: inline-block;
393
- padding: 12px 24px;
394
- background: #10a37f;
395
- color: white;
396
- text-decoration: none;
397
- border-radius: 8px;
398
- font-weight: 500;
399
- '>📖 Im Viewer öffnen</a>
400
- </div>
401
- """)
402
- else:
403
- gr.Markdown("Viewer-Link nicht verfügbar.")
404
-
405
- # Footer
406
- gr.HTML("""
407
- <div class="footer">
408
- <p>Prüfungsrechts-Chatbot v1.0 • Powered by RAG & LLM • Nur für informative Zwecke</p>
409
- <p style="font-size: 11px; opacity: 0.7;">Hinweis: Rechtsverbindliche Auskünfte erhalten Sie von offiziellen Stellen.</p>
410
- </div>
411
- """)
412
-
413
- # Event Handlers
414
- # Text senden mit Enter
415
- chat_text.submit(
416
- chat_fn,
417
- [chat_text, chat_audio, chatbot],
418
- [chatbot, voice_out, chat_text, chat_audio, chat_text, send_btn],
419
- )
420
-
421
- # Audio ändern (nach Aufnahme)
422
- chat_audio.change(
423
- chat_fn,
424
- [chat_text, chat_audio, chatbot],
425
- [chatbot, voice_out, chat_text, chat_audio, chat_text, send_btn],
426
- )
427
-
428
- # Senden-Button
429
- send_btn.click(
430
- chat_fn,
431
- [chat_text, chat_audio, chatbot],
432
- [chatbot, voice_out, chat_text, chat_audio, chat_text, send_btn],
433
- )
434
-
435
- # Antwort erneut vorlesen
436
- read_btn.click(
437
- read_last_answer,
438
- [chatbot],
439
- [voice_out],
440
- )
441
-
442
- # Chat löschen
443
- clear_btn.click(
444
- lambda: ([], None, "", None),
445
- None,
446
- [chatbot, voice_out, chat_text, chat_audio],
447
- )
448
 
449
  if __name__ == "__main__":
450
  demo.queue().launch(ssr_mode=False, show_error=True)
 
3
  import os
4
  import gradio as gr
5
  from gradio_pdf import PDF
 
6
 
7
  from load_documents import load_all_documents
8
  from split_documents import split_documents
 
77
 
78
  if not text:
79
  # Nichts zu tun
80
+ return history, None, "", None
81
+
 
 
 
 
 
 
 
 
 
 
 
82
  # 2) RAG-Antwort berechnen
83
  ans, sources = answer(text, retriever, llm)
84
  bot_msg = ans + format_sources(sources)
85
+
86
+ # 3) History aktualisieren (ChatGPT-Style)
87
+ history = history + [
88
+ {"role": "user", "content": text},
89
+ {"role": "assistant", "content": bot_msg},
90
+ ]
91
+
 
 
 
 
 
 
 
 
 
92
  # 4) TTS für Antwort
93
  tts_audio = synthesize_speech(bot_msg)
94
+
95
  # 5) Input-Felder leeren
96
+ return history, tts_audio, "", None
97
 
98
 
99
  # =====================================================
 
111
 
112
 
113
  # =====================================================
114
+ # UI – GRADIO
115
  # =====================================================
116
+ with gr.Blocks(title="Prüfungsrechts-Chatbot (RAG + Sprache)") as demo:
117
+ # Leichtes Styling: zentriert, schmale Breite, kompakte Input-Zeile
118
+ gr.HTML(
119
+ """
120
+ <style>
121
+ html, body {height: auto !important; overflow-y: auto !important;}
122
+ .gradio-container {max-width: 960px; margin: 0 auto; padding: 12px;}
123
+ #chat-input-row {align-items: center; gap: 8px;}
124
+ #chat-textbox textarea {min-height: 56px;}
125
+ </style>
126
+ """
127
+ )
128
+ gr.Markdown("# 🧑‍⚖️ Prüfungsrechts-Chatbot")
129
+ gr.Markdown(
130
+ "Dieser Chatbot beantwortet Fragen **ausschließlich** aus der "
131
+ "Prüfungsordnung (PDF) und dem Hochschulgesetz NRW. "
132
+ "Du kannst Text eingeben oder direkt ins Mikrofon sprechen."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  )
134
+
135
+ # Einspaltiges Layout, alles untereinander (verhindert abgeschnittene Bereiche)
136
+ with gr.Column():
137
+ chatbot = gr.Chatbot(
138
+ label="Chat",
139
+ height=280,
 
 
 
140
  )
141
+
142
+ # Audio-Ausgabe (TTS)
143
+ voice_out = gr.Audio(label="Vorgelesene Antwort", type="numpy", interactive=False)
144
+
145
+ # Eingabezeile à la ChatGPT: Text + Mikro + Senden
146
+ with gr.Row(elem_id="chat-input-row"):
147
+ chat_text = gr.Textbox(
148
+ elem_id="chat-textbox",
149
+ label=None,
150
+ placeholder="Schreibe deine Frage oder klicke das Mikro und sprich. Enter sendet.",
151
+ lines=2,
152
+ max_lines=4,
153
+ autofocus=True,
154
+ scale=8,
155
+ )
156
  chat_audio = gr.Audio(
157
+ label="🎤",
158
  sources=["microphone"],
159
  type="filepath",
160
  interactive=True,
161
+ scale=1,
162
  show_label=False,
 
 
163
  )
164
+ send_btn = gr.Button("Senden", elem_classes=["compact-btn"], scale=1)
165
+
166
+ # Senden bei Enter
167
+ chat_text.submit(
168
+ chat_fn,
169
+ [chat_text, chat_audio, chatbot],
170
+ [chatbot, voice_out, chat_text, chat_audio],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  )
172
+ # Auto-submit sobald eine Aufnahme fertig ist (Text + Audio werden kombiniert)
173
+ chat_audio.change(
174
+ chat_fn,
175
+ [chat_text, chat_audio, chatbot],
176
+ [chatbot, voice_out, chat_text, chat_audio],
177
  )
178
+ send_btn.click(
179
+ chat_fn,
180
+ [chat_text, chat_audio, chatbot],
181
+ [chatbot, voice_out, chat_text, chat_audio],
182
+ )
183
+
184
+ # Button: Antwort erneut vorlesen
185
+ read_btn = gr.Button("🔁 Antwort erneut vorlesen")
186
+ read_btn.click(
187
+ read_last_answer,
188
+ [chatbot],
189
+ [voice_out],
190
+ )
191
+
192
+ # Chat löschen
193
+ clear_btn = gr.Button("Chat zurücksetzen")
194
+ clear_btn.click(
195
+ lambda: ([], None, "", None),
196
+ None,
197
+ [chatbot, voice_out, chat_text, chat_audio],
198
+ )
199
+
200
+ # Quellen & Dokumente kompakt unterhalb
201
+ with gr.Accordion("Quellen & Dokumente", open=False):
202
+ gr.Markdown("### 📄 Prüfungsordnung (PDF)")
203
+ PDF(pdf_meta["pdf_url"], height=250)
204
+ gr.Markdown("### 📘 Hochschulgesetz NRW")
205
+ if isinstance(hg_url, str) and hg_url.startswith("http"):
206
+ gr.Markdown(f"[Im Viewer öffnen]({hg_url})")
207
+ else:
208
+ gr.Markdown("Viewer-Link nicht verfügbar.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
 
210
  if __name__ == "__main__":
211
  demo.queue().launch(ssr_mode=False, show_error=True)