Nguyen5 commited on
Commit
b8e573b
·
1 Parent(s): ea9c422
Files changed (1) hide show
  1. app.py +357 -167
app.py CHANGED
@@ -1,8 +1,9 @@
1
- # app.py – Prüfungsrechts-Chatbot (RAG + Sprache) UI kiểu ChatGPT
2
-
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
@@ -12,13 +13,12 @@ from llm import load_llm
12
  from rag_pipeline import answer
13
  from speech_io import transcribe_audio, synthesize_speech
14
 
15
-
16
- ASR_LANGUAGE_HINT = os.getenv("ASR_LANGUAGE", "de")
17
-
18
 
19
  # =====================================================
20
- # INITIALISIERUNG
21
  # =====================================================
 
22
  print("📚 Lade Dokumente…")
23
  docs = load_all_documents()
24
 
@@ -34,227 +34,417 @@ retriever = get_retriever(vs)
34
  print("🤖 Lade LLM…")
35
  llm = load_llm()
36
 
 
37
  pdf_meta = next(d.metadata for d in docs if d.metadata.get("type") == "pdf")
38
  hg_meta = next(d.metadata for d in docs if d.metadata.get("type") == "hg")
39
  hg_url = hg_meta.get("viewer_url")
40
 
41
-
42
  # =====================================================
43
- # Quellen formatieren
44
  # =====================================================
45
  def format_sources(src):
46
  if not src:
47
  return ""
48
- lines = ["", "## 📚 Quellen"]
 
 
49
  for s in src:
50
- url = s["url"]
51
- line = f"- [{s['source']}]({url})"
52
  if s.get("page") is not None:
53
  line += f" (Seite {s['page']})"
54
- lines.append(line)
55
- return "\n".join(lines)
 
56
 
57
 
58
  # =====================================================
59
- # Chat-Funktion
60
  # =====================================================
61
  def chat_fn(text_input, audio_path, history):
 
 
 
 
 
62
  text = (text_input or "").strip()
63
 
64
- # Audio Text
65
  if audio_path:
66
- spoken = transcribe_audio(audio_path)
67
  if text:
68
  text = (text + " " + spoken).strip()
69
  else:
70
  text = spoken
71
 
72
  if not text:
73
- return history, None, "", None
74
-
75
- # RAG Antwort
 
 
 
 
 
 
 
 
 
 
 
 
76
  ans, sources = answer(text, retriever, llm)
77
  bot_msg = ans + format_sources(sources)
78
-
79
- # Neues Chat-Element
80
- history = history + [
81
- {"role": "user", "content": text},
82
- {"role": "assistant", "content": bot_msg},
83
- ]
84
-
85
- # TTS
86
- tts = synthesize_speech(bot_msg)
87
-
88
- return history, tts, "", None
 
 
 
 
 
 
 
 
 
 
89
 
90
 
91
  # =====================================================
92
- # Button: Antwort erneut vorlesen
93
  # =====================================================
94
  def read_last_answer(history):
95
  if not history:
96
  return None
97
- for msg in reversed(history):
98
- if msg["role"] == "assistant":
99
- return synthesize_speech(msg["content"])
100
- return None
101
-
102
-
103
- # =====================================================
104
- # CSS – UI giống ChatGPT
105
- # =====================================================
106
- CUSTOM_CSS = """
107
- <style>
108
-
109
- /* === Hintergrund wie ChatGPT === */
110
- body {
111
- background-color: #f0f0f0 !important;
112
- }
113
-
114
- /* === Zentralisiertes Chat-Fenster === */
115
- .gradio-container {
116
- max-width: 820px !important;
117
- margin: 0 auto !important;
118
- padding-bottom: 120px !important;
119
- }
120
-
121
- /* === ChatGPT Bubble-Style === */
122
- .chatbot .message.user {
123
- background-color: white !important;
124
- color: black !important;
125
- border-radius: 14px !important;
126
- padding: 12px 16px !important;
127
- margin-bottom: 10px !important;
128
- max-width: 85% !important;
129
- }
130
-
131
- .chatbot .message.assistant {
132
- background-color: #e6e6e6 !important;
133
- color: black !important;
134
- border-radius: 14px !important;
135
- padding: 12px 16px !important;
136
- margin-bottom: 10px !important;
137
- max-width: 85% !important;
138
- }
139
-
140
- /* === Input Bar giống ChatGPT === */
141
- #input-bar {
142
- position: fixed !important;
143
- bottom: 0;
144
- left: 0;
145
- right: 0;
146
- background: white;
147
- padding: 14px 20px;
148
- box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
149
- display: flex;
150
- gap: 12px;
151
- align-items: center;
152
- z-index: 999;
153
- }
154
-
155
- #input-text textarea {
156
- min-height: 52px !important;
157
- resize: none !important;
158
- }
159
-
160
- /* Send Button giống ChatGPT */
161
- .send-btn {
162
- background-color: #0b57d0 !important;
163
- color: white !important;
164
- border-radius: 8px !important;
165
- height: 52px !important;
166
- }
167
 
168
- /* Microphone Button Rund */
169
- .mic-btn button {
170
- border-radius: 50% !important;
171
- width: 52px !important;
172
- height: 52px !important;
173
- }
174
 
175
- </style>
176
- """
177
 
178
 
179
  # =====================================================
180
- # UI – Gradio Blocks
181
  # =====================================================
182
- with gr.Blocks(title="Prüfungsrechts-Chatbot") as demo:
183
-
184
- # CSS injizieren
185
- gr.HTML(CUSTOM_CSS)
186
-
187
- # Titel
188
- gr.Markdown("<h1 style='text-align:center;'>🧑‍⚖️ Prüfungsrechts-Chatbot</h1>")
189
- gr.Markdown(
190
- "<p style='text-align:center;'>Ein Chatbot speziell für Prüfungsordnung & Hochschulgesetz NRW – mit Spracheingabe wie ChatGPT.</p>"
191
- )
192
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  chatbot = gr.Chatbot(
194
- label="Chat",
195
  elem_classes=["chatbot"],
196
- height=600,
197
  show_label=False,
 
 
 
 
 
 
 
 
198
  )
199
-
200
- voice_out = gr.Audio(type="numpy", visible=False)
201
-
202
- # === ChatGPT Input Bar ===
203
- with gr.Row(elem_id="input-bar"):
204
- chat_text = gr.Textbox(
205
- placeholder="Frage eingeben oder Mikrofon benutzen…",
206
- label=None,
207
- elem_id="input-text",
208
- lines=1,
209
- scale=10,
210
  )
211
-
212
- chat_audio = gr.Audio(
213
- sources=["microphone"],
214
- type="filepath",
215
- label=None,
216
- elem_classes=["mic-btn"],
217
- scale=1,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  )
219
-
220
- send_btn = gr.Button("Senden", elem_classes=["send-btn"], scale=2)
221
-
222
- # Events – Text + Audio
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  chat_text.submit(
224
  chat_fn,
225
  [chat_text, chat_audio, chatbot],
226
- [chatbot, voice_out, chat_text, chat_audio],
227
  )
 
 
228
  chat_audio.change(
229
  chat_fn,
230
  [chat_text, chat_audio, chatbot],
231
- [chatbot, voice_out, chat_text, chat_audio],
232
  )
 
 
233
  send_btn.click(
234
  chat_fn,
235
  [chat_text, chat_audio, chatbot],
236
- [chatbot, voice_out, chat_text, chat_audio],
237
  )
238
-
239
- # Antwort wiederholen
240
- gr.Button("🔁 Antwort erneut vorlesen").click(
241
- read_last_answer, [chatbot], [voice_out]
 
 
 
 
 
 
 
 
 
242
  )
243
 
244
- # Quellen-Accordion
245
- with gr.Accordion("📄 Quellen & Dokumente", open=False):
246
- gr.Markdown("### Prüfungsordnung (PDF)")
247
- PDF(pdf_meta["pdf_url"], height=250)
248
-
249
- gr.Markdown("### Hochschulgesetz NRW")
250
- if isinstance(hg_url, str) and hg_url.startswith("http"):
251
- gr.Markdown(f"[Im Viewer öffnen]({hg_url})")
252
- else:
253
- gr.Markdown("Kein Viewer verfügbar.")
254
-
255
-
256
- # =====================================================
257
- # Start
258
- # =====================================================
259
  if __name__ == "__main__":
260
  demo.queue().launch(ssr_mode=False, show_error=True)
 
1
+ # app.py – Prüfungsrechts-Chatbot (RAG + Sprache, UI kiểu ChatGPT)
2
+ #
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
 
13
  from rag_pipeline import answer
14
  from speech_io import transcribe_audio, synthesize_speech
15
 
16
+ ASR_LANGUAGE_HINT = os.getenv("ASR_LANGUAGE", "de") # set to "auto" for detection, or e.g. "en"
 
 
17
 
18
  # =====================================================
19
+ # INITIALISIERUNG (global)
20
  # =====================================================
21
+
22
  print("📚 Lade Dokumente…")
23
  docs = load_all_documents()
24
 
 
34
  print("🤖 Lade LLM…")
35
  llm = load_llm()
36
 
37
+ # Dokument-Metadaten für UI
38
  pdf_meta = next(d.metadata for d in docs if d.metadata.get("type") == "pdf")
39
  hg_meta = next(d.metadata for d in docs if d.metadata.get("type") == "hg")
40
  hg_url = hg_meta.get("viewer_url")
41
 
 
42
  # =====================================================
43
+ # Quellen formatieren – Markdown für Chat
44
  # =====================================================
45
  def format_sources(src):
46
  if not src:
47
  return ""
48
+
49
+ out = ["", "## 📚 Quellen"]
50
+
51
  for s in src:
52
+ line = f"- [{s['source']}]({s['url']})"
 
53
  if s.get("page") is not None:
54
  line += f" (Seite {s['page']})"
55
+ out.append(line)
56
+
57
+ return "\n".join(out)
58
 
59
 
60
  # =====================================================
61
+ # CORE CHAT-FUNKTION (Text + separates Mikro-Audio)
62
  # =====================================================
63
  def chat_fn(text_input, audio_path, history):
64
+ """
65
+ text_input: Textbox-Inhalt (str)
66
+ audio_path: Pfad zu WAV/FLAC vom Mikro (gr.Audio, type="filepath")
67
+ history: Liste von OpenAI-ähnlichen Messages (role, content)
68
+ """
69
  text = (text_input or "").strip()
70
 
71
+ # Wenn Audio vorhanden: transkribieren
72
  if audio_path:
73
+ spoken = transcribe_audio(audio_path, language=ASR_LANGUAGE_HINT)
74
  if text:
75
  text = (text + " " + spoken).strip()
76
  else:
77
  text = spoken
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
  # =====================================================
121
+ # LAST ANSWER → TTS (für Button "Antwort erneut vorlesen")
122
  # =====================================================
123
  def read_last_answer(history):
124
  if not history:
125
  return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
+ for msg in reversed(history):
128
+ if msg.get("role") == "assistant":
129
+ return synthesize_speech(msg.get("content", ""))
 
 
 
130
 
131
+ return None
 
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)