syafiqq02 commited on
Commit
4bfa09f
Β·
1 Parent(s): 0a55b34

commit gradio new

Browse files
Files changed (4) hide show
  1. Berhenti.mp3 +0 -0
  2. Dimulai.mp3 +0 -0
  3. app.py +732 -76
  4. wordlist.lst +0 -0
Berhenti.mp3 ADDED
Binary file (27.2 kB). View file
 
Dimulai.mp3 ADDED
Binary file (26.8 kB). View file
 
app.py CHANGED
@@ -1,128 +1,784 @@
1
  import gradio as gr
 
2
  import os
3
  import requests
 
 
 
 
 
 
 
4
  from dotenv import load_dotenv
5
 
 
 
 
 
 
 
 
 
6
  load_dotenv()
7
  API_TRANSCRIBE = os.getenv("API_TRANSCRIBE")
8
  API_TEXT = os.getenv("API_TEXT")
9
 
10
- # ==== Function for backend calls ====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  def handle_audio(audio_file):
 
12
  if audio_file is None:
13
- return "", "", ""
14
- with open(audio_file, "rb") as f:
15
- files = {"audio": f}
16
- response = requests.post(API_TRANSCRIBE, files=files)
17
- result = response.json()
18
- return result.get("transcription", "-"), result.get("soap_content", "-"), result.get("tags_content", "-")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  def handle_text(dialogue):
 
21
  if not dialogue.strip():
22
- return "", "", ""
23
- response = requests.post(API_TEXT, json={"dialogue": dialogue})
24
- result = response.json()
25
- return dialogue, result.get("soap_content", "-"), result.get("tags_content", "-")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
- # ==== Function to toggle inputs with refresh ====
28
  def toggle_inputs_with_refresh(choice):
29
- # Clear semua output saat dropdown berubah
30
  return (
31
- gr.update(visible=(choice == "Upload Audio"), value=None), # Clear audio upload
32
- gr.update(visible=(choice == "Realtime Recording"), value=None), # Clear audio record
33
- gr.update(visible=(choice == "Input Teks"), value=""), # Clear text input
34
- gr.update(value=""), # Clear transcript output
35
- gr.update(value=""), # Clear SOAP output
36
- gr.update(value="") # Clear tags output
 
 
 
 
 
37
  )
38
 
39
- # ==== Function to clear all data ====
40
  def clear_all_data():
41
  return (
42
- gr.update(value=None), # Clear audio upload
43
- gr.update(value=None), # Clear audio record
44
- gr.update(value=""), # Clear text input
45
- gr.update(value=""), # Clear transcript output
46
- gr.update(value=""), # Clear SOAP output
47
- gr.update(value="") # Clear tags output
 
 
 
 
48
  )
49
 
50
- # ==== Combined processing function ====
51
  def process_data(choice, audio_upload, audio_record, text_input):
 
 
 
 
 
52
  if choice == "Upload Audio":
53
- return handle_audio(audio_upload)
 
 
 
 
 
 
 
 
 
 
54
  elif choice == "Realtime Recording":
55
- return handle_audio(audio_record)
 
 
 
 
 
 
 
 
 
 
56
  elif choice == "Input Teks":
57
- return handle_text(text_input)
 
 
 
 
 
 
 
 
 
 
58
  else:
59
- return "", "", ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
- # ==== UI ====
62
- with gr.Blocks(title="SOAP AI Dropdown Input") as app:
63
- gr.Markdown("## 🩺 SOAP AI β€” Pilih Jenis Input")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
  with gr.Row():
66
- input_choice = gr.Dropdown(
67
- choices=["Upload Audio", "Realtime Recording", "Input Teks"],
68
- value="Upload Audio",
69
- label="Pilih Metode Input",
70
- scale=4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  )
72
- clear_button = gr.Button("πŸ—‘οΈClear all", variant="secondary", size="sm", scale=0.5, min_width=25)
73
-
74
- # Input fields (hidden by default except selected)
75
- # Upload Audio - seperti gambar 1 (drag & drop + upload button)
76
- audio_upload = gr.Audio(
77
- sources=["upload"],
78
- label="πŸ”Š Upload File Audio",
79
- type="filepath",
80
- visible=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  )
82
-
83
- # Realtime Recording - seperti gambar 2 (tombol record + microphone selector)
84
- audio_record = gr.Audio(
85
- sources=["microphone"],
86
- label="πŸ”Š Realtime Recording",
87
- type="filepath",
88
- visible=False
89
  )
90
-
91
- # Text Input
92
- text_input = gr.Textbox(
93
- label="πŸ“ Masukkan Percakapan Dokter-Pasien",
94
- lines=6,
95
- visible=False
 
 
 
 
 
 
 
 
96
  )
97
 
98
- # Tombol proses
99
- process_button = gr.Button("πŸš€ Proses ke SOAP")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
- # Output
102
- transcript_output = gr.Textbox(label="πŸ“ Hasil Transkripsi", lines=3)
103
- soap_output = gr.Textbox(label="πŸ“‹ Ringkasan SOAP", lines=6)
104
- tags_output = gr.Textbox(label="🏷️ Medical Tags", lines=6)
 
 
105
 
106
- # === Events ===
107
- # Event untuk dropdown change - akan clear semua data
108
  input_choice.change(
109
- fn=toggle_inputs_with_refresh,
 
 
 
 
 
 
 
 
 
 
 
110
  inputs=input_choice,
111
- outputs=[audio_upload, audio_record, text_input, transcript_output, soap_output, tags_output]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  )
113
 
114
- # Event untuk tombol clear - akan clear semua data
115
  clear_button.click(
116
  fn=clear_all_data,
117
- outputs=[audio_upload, audio_record, text_input, transcript_output, soap_output, tags_output]
 
 
 
 
 
 
 
 
 
 
 
118
  )
119
 
120
- # Single event handler untuk semua jenis input
121
  process_button.click(
122
  fn=process_data,
123
  inputs=[input_choice, audio_upload, audio_record, text_input],
124
- outputs=[transcript_output, soap_output, tags_output],
125
- show_progress="minimal"
 
 
 
 
 
 
 
126
  )
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  app.launch()
 
1
  import gradio as gr
2
+ import threading
3
  import os
4
  import requests
5
+ import string
6
+ import pygame
7
+ import time
8
+ from pydub import AudioSegment
9
+ from nltk.tokenize import word_tokenize
10
+ import nltk
11
+ from nltk.corpus import words, stopwords
12
  from dotenv import load_dotenv
13
 
14
+ # Initialize pygame mixer for sound playback
15
+ pygame.mixer.init()
16
+
17
+ # Download resource NLTK (hanya sekali)
18
+ nltk.download('punkt')
19
+ nltk.download('words')
20
+ nltk.download('stopwords')
21
+
22
  load_dotenv()
23
  API_TRANSCRIBE = os.getenv("API_TRANSCRIBE")
24
  API_TEXT = os.getenv("API_TEXT")
25
 
26
+ # Path ke file sound yang akan diputar (sesuaikan dengan file Anda)
27
+ NOTIFICATION_SOUND_PATH = "Berhenti.mp3" # Sound ketika recording selesai
28
+ START_RECORDING_SOUND_PATH = "Dimulai.mp3" # Sound ketika mulai recording
29
+
30
+ english_words = set(words.words())
31
+ indonesian_stopwords = set(stopwords.words('indonesian'))
32
+
33
+ def load_indonesian_wordlist(filepath='wordlist.lst'):
34
+ try:
35
+ with open(filepath, encoding='utf-8') as f:
36
+ return set(line.strip().lower() for line in f if line.strip())
37
+ except UnicodeDecodeError:
38
+ try:
39
+ with open(filepath, encoding='latin-1') as f:
40
+ return set(line.strip().lower() for line in f if line.strip())
41
+ except Exception:
42
+ return set()
43
+ except Exception:
44
+ return set()
45
+
46
+ indonesian_words = load_indonesian_wordlist()
47
+ valid_words = english_words.union(indonesian_words)
48
+
49
+ def contains_medical_terms_auto_threshold(text, medical_words):
50
+ tokens = word_tokenize(text.lower())
51
+ tokens = [w.strip(string.punctuation) for w in tokens if w.isalpha()]
52
+ if not tokens:
53
+ return False
54
+ medical_count = sum(1 for w in tokens if w in medical_words)
55
+ ratio = medical_count / len(tokens)
56
+ threshold = 0.4 if len(tokens) <= 5 else 0.1
57
+ return ratio >= threshold
58
+
59
+ medical_words = load_indonesian_wordlist('wordlist.lst')
60
+
61
+ MAX_DURATION_SECONDS = 600
62
+
63
+ def validate_audio_duration(audio_file):
64
+ try:
65
+ audio = AudioSegment.from_file(audio_file)
66
+ duration_sec = len(audio) / 1000.0
67
+ if duration_sec > MAX_DURATION_SECONDS:
68
+ return False, duration_sec
69
+ return True, duration_sec
70
+ except Exception as e:
71
+ return False, -1
72
+
73
+ def play_notification_sound():
74
+ """Function untuk memainkan sound notification ketika recording selesai"""
75
+ try:
76
+ if os.path.exists(NOTIFICATION_SOUND_PATH):
77
+ pygame.mixer.music.load(NOTIFICATION_SOUND_PATH)
78
+ pygame.mixer.music.play()
79
+ print("πŸ”Š Playing completion notification sound...")
80
+ else:
81
+ print(f"⚠️ File completion sound tidak ditemukan: {NOTIFICATION_SOUND_PATH}")
82
+ except Exception as e:
83
+ print(f"❌ Error playing completion sound: {e}")
84
+
85
+ def play_start_recording_sound():
86
+ """Function untuk memainkan sound ketika mulai recording"""
87
+ try:
88
+ if os.path.exists(START_RECORDING_SOUND_PATH):
89
+ # Menggunakan Sound effect untuk play bersamaan tanpa interrupt music
90
+ start_sound = pygame.mixer.Sound(START_RECORDING_SOUND_PATH)
91
+ start_sound.play()
92
+ print("🎡 Playing start recording sound...")
93
+ else:
94
+ print(f"⚠️ File start recording sound tidak ditemukan: {START_RECORDING_SOUND_PATH}")
95
+ except Exception as e:
96
+ print(f"❌ Error playing start recording sound: {e}")
97
+
98
+ def start_recording():
99
+ """Function yang dipanggil ketika tombol record ditekan"""
100
+ print("πŸŽ™οΈ Recording started...")
101
+ # Play start recording sound
102
+ threading.Thread(target=play_start_recording_sound, daemon=True).start()
103
+ return "πŸŽ™οΈ Sedang merekam... Klik stop untuk menyelesaikan"
104
+
105
+ def stop_recording(audio):
106
+ """Function yang dipanggil ketika recording selesai"""
107
+ if audio is not None:
108
+ print("βœ… Recording completed!")
109
+ # Play notification sound when recording is completed
110
+ threading.Thread(target=play_notification_sound, daemon=True).start()
111
+ return "βœ… Recording selesai! Audio siap diproses"
112
+ else:
113
+ print("❌ No audio recorded")
114
+ return "❌ Tidak ada audio yang direkam"
115
+
116
+ def test_microphone():
117
+ """Function untuk test microphone"""
118
+ print("πŸ”§ Testing microphone...")
119
+ return "πŸ”§ Testing microphone... Silakan coba record lagi"
120
+
121
+ def reset_recording_status():
122
+ """Function untuk reset status recording"""
123
+ return "πŸ“± Siap untuk merekam - Klik tombol record"
124
+
125
  def handle_audio(audio_file):
126
+ """Handle audio processing - returns (validation_message, transcript, soap, tags)"""
127
  if audio_file is None:
128
+ return "❌ Tidak ada file audio", "", "", ""
129
+
130
+ valid, duration = validate_audio_duration(audio_file)
131
+ if not valid:
132
+ if duration == -1:
133
+ msg = "⚠️ Gagal memproses file audio."
134
+ else:
135
+ msg = f"⚠️ Durasi rekaman terlalu panjang ({duration:.1f}s). Maksimal {MAX_DURATION_SECONDS}s."
136
+ return msg, "", "", ""
137
+
138
+ try:
139
+ with open(audio_file, "rb") as f:
140
+ files = {"audio": f}
141
+ response = requests.post(API_TRANSCRIBE, files=files)
142
+ result = response.json()
143
+
144
+ transcription = result.get("transcription", "")
145
+ soap_content = result.get("soap_content", "")
146
+ tags_content = result.get("tags_content", "")
147
+
148
+ if not transcription and not soap_content and not tags_content:
149
+ return "⚠️ Tidak ada hasil dari proses audio", "", "", ""
150
+
151
+ return "", transcription, soap_content, tags_content
152
+
153
+ except Exception as e:
154
+ return f"❌ Error processing audio: {str(e)}", "", "", ""
155
 
156
  def handle_text(dialogue):
157
+ """Handle text processing - returns (validation_message, transcript, soap, tags)"""
158
  if not dialogue.strip():
159
+ return "⚠️ Teks tidak boleh kosong", "", "", ""
160
+
161
+ if not contains_medical_terms_auto_threshold(dialogue, medical_words):
162
+ return "⚠️ Teks tidak mengandung istilah medis yang cukup untuk diproses.", "", "", ""
163
+
164
+ try:
165
+ response = requests.post(API_TEXT, json={"dialogue": dialogue})
166
+ result = response.json()
167
+
168
+ soap_content = result.get("soap_content", "")
169
+ tags_content = result.get("tags_content", "")
170
+
171
+ if not soap_content and not tags_content:
172
+ return "⚠️ Tidak ada hasil dari proses teks", "", "", ""
173
+
174
+ return "", dialogue, soap_content, tags_content
175
+
176
+ except Exception as e:
177
+ return f"❌ Error processing text: {str(e)}", "", "", ""
178
 
 
179
  def toggle_inputs_with_refresh(choice):
180
+ # Tampilkan input dan validasi yang sesuai, sembunyikan lainnya
181
  return (
182
+ gr.update(visible=(choice == "Upload Audio"), value=None), # audio upload
183
+ gr.update(visible=(choice == "Realtime Recording"), value=None), # audio record
184
+ gr.update(visible=(choice == "Input Teks"), value=""), # text input
185
+ gr.update(visible=(choice == "Upload Audio")), # validasi upload
186
+ gr.update(visible=(choice == "Realtime Recording")), # validasi realtime
187
+ gr.update(visible=(choice == "Input Teks")), # validasi teks
188
+ gr.update(visible=(choice == "Realtime Recording")), # recording status group
189
+ gr.update(visible=(choice == "Realtime Recording")), # record audio group
190
+ gr.update(value=""), # transcript
191
+ gr.update(value=""), # soap
192
+ gr.update(value=""), # tags
193
  )
194
 
 
195
  def clear_all_data():
196
  return (
197
+ gr.update(value=None), # audio_upload
198
+ gr.update(value=None), # audio_record
199
+ gr.update(value=""), # text_input
200
+ gr.update(value=""), # validation_upload
201
+ gr.update(value=""), # validation_realtime
202
+ gr.update(value=""), # validation_text
203
+ gr.update(value="πŸ“± Siap untuk merekam"), # recording_status
204
+ gr.update(value=""), # transcript_output
205
+ gr.update(value=""), # soap_output
206
+ gr.update(value=""), # tags_output
207
  )
208
 
 
209
  def process_data(choice, audio_upload, audio_record, text_input):
210
+ """
211
+ Process data based on choice and return results in correct order:
212
+ Returns: (validation_upload, validation_realtime, validation_text, transcript, soap, tags)
213
+ """
214
+
215
  if choice == "Upload Audio":
216
+ # Process upload audio
217
+ validation_msg, transcript, soap, tags = handle_audio(audio_upload)
218
+ return (
219
+ validation_msg, # validation_upload
220
+ "", # validation_realtime (empty)
221
+ "", # validation_text (empty)
222
+ transcript, # transcript_output
223
+ soap, # soap_output
224
+ tags # tags_output
225
+ )
226
+
227
  elif choice == "Realtime Recording":
228
+ # Process realtime recording
229
+ validation_msg, transcript, soap, tags = handle_audio(audio_record)
230
+ return (
231
+ "", # validation_upload (empty)
232
+ validation_msg, # validation_realtime
233
+ "", # validation_text (empty)
234
+ transcript, # transcript_output
235
+ soap, # soap_output
236
+ tags # tags_output
237
+ )
238
+
239
  elif choice == "Input Teks":
240
+ # Process text input
241
+ validation_msg, transcript, soap, tags = handle_text(text_input)
242
+ return (
243
+ "", # validation_upload (empty)
244
+ "", # validation_realtime (empty)
245
+ validation_msg, # validation_text
246
+ transcript, # transcript_output (will be same as input for text)
247
+ soap, # soap_output
248
+ tags # tags_output
249
+ )
250
+
251
  else:
252
+ # Default case - clear all
253
+ return ("", "", "", "", "", "")
254
+
255
+ # Custom CSS untuk tampilan modern dengan alignment yang diperbaiki
256
+ modern_css = """
257
+ <style>
258
+ /* Background gradient yang modern */
259
+ .gradio-container {
260
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
261
+ min-height: 100vh;
262
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
263
+ }
264
+
265
+ /* Header styling */
266
+ .main-header {
267
+ text-align: center;
268
+ padding: 2rem 0;
269
+ color: white;
270
+ margin-bottom: 2rem;
271
+ }
272
+
273
+ .main-header h1 {
274
+ font-size: 3rem;
275
+ font-weight: 700;
276
+ margin-bottom: 0.5rem;
277
+ text-shadow: 0 2px 4px rgba(0,0,0,0.3);
278
+ }
279
+
280
+ .main-header p {
281
+ font-size: 1.2rem;
282
+ opacity: 0.9;
283
+ font-weight: 300;
284
+ }
285
+
286
+ /* Card styling */
287
+ .input-card {
288
+ background: rgba(255, 255, 255, 0.95);
289
+ border-radius: 20px;
290
+ padding: 2rem;
291
+ margin: 0.8rem 0;
292
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
293
+ backdrop-filter: blur(10px);
294
+ border: 1px solid rgba(255,255,255,0.2);
295
+ }
296
+
297
+ .output-card {
298
+ background: rgba(255, 255, 255, 0.95);
299
+ border-radius: 20px;
300
+ padding: 2rem;
301
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
302
+ backdrop-filter: blur(10px);
303
+ border: 1px solid rgba(255,255,255,0.2);
304
+ }
305
+
306
+ /* Record audio section with padding */
307
+ .record-audio-section {
308
+ background: rgba(255, 255, 255, 0.95);
309
+ border-radius: 20px;
310
+ padding: 2rem;
311
+ margin: 0.8rem 0;
312
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
313
+ backdrop-filter: blur(10px);
314
+ border: 1px solid rgba(255,255,255,0.2);
315
+ }
316
+
317
+ /* Output header styling - aligned properly */
318
+ .output-header {
319
+ text-align: left;
320
+ margin-bottom: 1.5rem;
321
+ padding-left: 0;
322
+ }
323
+
324
+ .output-header h3 {
325
+ font-size: 1.5rem;
326
+ font-weight: 600;
327
+ color: #2d3436;
328
+ margin: 0;
329
+ display: flex;
330
+ align-items: center;
331
+ }
332
+
333
+ /* Spacing adjustments */
334
+ .input-section {
335
+ margin-bottom: 1rem;
336
+ }
337
+
338
+ .button-section {
339
+ margin-top: 0.5rem;
340
+ margin-bottom: 1rem;
341
+ }
342
+
343
+ /* Updated output section styling with proper alignment */
344
+ .output-section {
345
+ background: rgba(255, 255, 255, 0.98);
346
+ border-radius: 15px;
347
+ padding: 1.5rem;
348
+ margin: 1rem 0 0 0;
349
+ box-shadow: 0 4px 20px rgba(0,0,0,0.08);
350
+ }
351
+
352
+ /* Ensure all output components are aligned consistently */
353
+ .output-container {
354
+ display: flex;
355
+ flex-direction: column;
356
+ gap: 1rem;
357
+ }
358
 
359
+ /* Button styling */
360
+ .record-btn {
361
+ background: linear-gradient(45deg, #ff6b6b, #ee5a24);
362
+ border: none;
363
+ border-radius: 50px;
364
+ padding: 1rem 2rem;
365
+ color: white;
366
+ font-weight: 600;
367
+ font-size: 1.1rem;
368
+ cursor: pointer;
369
+ transition: all 0.3s ease;
370
+ box-shadow: 0 4px 15px rgba(238, 90, 36, 0.4);
371
+ }
372
+
373
+ .record-btn:hover {
374
+ transform: translateY(-2px);
375
+ box-shadow: 0 6px 20px rgba(238, 90, 36, 0.6);
376
+ }
377
+
378
+ .test-btn {
379
+ background: linear-gradient(45deg, #74b9ff, #0984e3);
380
+ border: none;
381
+ border-radius: 50px;
382
+ padding: 0.8rem 1.5rem;
383
+ color: white;
384
+ font-weight: 500;
385
+ cursor: pointer;
386
+ transition: all 0.3s ease;
387
+ }
388
+
389
+ .test-btn:hover {
390
+ transform: translateY(-1px);
391
+ box-shadow: 0 4px 15px rgba(116, 185, 255, 0.4);
392
+ }
393
+
394
+ .reset-btn {
395
+ background: linear-gradient(45deg, #a29bfe, #6c5ce7);
396
+ border: none;
397
+ border-radius: 15px;
398
+ padding: 0.4rem 1rem;
399
+ font-size: 0.8rem;
400
+ color: white;
401
+ font-weight: 500;
402
+ cursor: pointer;
403
+ transition: all 0.3s ease;
404
+ height: 2rem;
405
+ min-width: 80px;
406
+ }
407
+
408
+ .reset-btn:hover {
409
+ transform: translateY(-1px);
410
+ box-shadow: 0 2px 10px rgba(162, 155, 254, 0.4);
411
+ }
412
+
413
+ .process-btn {
414
+ background: linear-gradient(45deg, #00b894, #00cec9);
415
+ border: none;
416
+ border-radius: 50px;
417
+ padding: 1rem 2.5rem;
418
+ color: white;
419
+ font-weight: 600;
420
+ font-size: 1.2rem;
421
+ cursor: pointer;
422
+ transition: all 0.3s ease;
423
+ box-shadow: 0 4px 15px rgba(0, 184, 148, 0.4);
424
+ margin: 1rem 0;
425
+ }
426
+
427
+ .process-btn:hover {
428
+ transform: translateY(-2px);
429
+ box-shadow: 0 6px 20px rgba(0, 184, 148, 0.6);
430
+ }
431
+
432
+ /* Status indicator */
433
+ .status-indicator {
434
+ background: rgba(255, 255, 255, 0.95);
435
+ border: 2px solid #74b9ff;
436
+ color: #2d3436;
437
+ padding: 2rem;
438
+ border-radius: 15px;
439
+ font-weight: 500;
440
+ margin: 1rem 0;
441
+ box-shadow: 0 4px 15px rgba(116, 185, 255, 0.2);
442
+ }
443
+
444
+ /* Input styling */
445
+ .audio-input {
446
+ border: 2px dashed #ddd;
447
+ border-radius: 15px;
448
+ padding: 2rem;
449
+ text-align: center;
450
+ background: rgba(255,255,255,0.5);
451
+ transition: all 0.3s ease;
452
+ }
453
+
454
+ .audio-input:hover {
455
+ border-color: #74b9ff;
456
+ background: rgba(116, 185, 255, 0.1);
457
+ }
458
+
459
+ /* Ensure proper text alignment in outputs */
460
+ .output-section textarea {
461
+ text-align: left;
462
+ vertical-align: top;
463
+ resize: vertical;
464
+ }
465
+
466
+ /* Responsive design */
467
+ @media (max-width: 768px) {
468
+ .main-header h1 {
469
+ font-size: 2rem;
470
+ }
471
+
472
+ .input-card, .output-card, .record-audio-section {
473
+ padding: 2rem;
474
+ }
475
+
476
+ .record-btn, .process-btn {
477
+ width: 100%;
478
+ margin: 0.5rem 0;
479
+ }
480
+
481
+ .reset-btn {
482
+ min-width: 70px;
483
+ font-size: 0.75rem;
484
+ }
485
+ }
486
+
487
+ /* Animation untuk elemen yang muncul */
488
+ @keyframes fadeInUp {
489
+ from {
490
+ opacity: 0;
491
+ transform: translateY(30px);
492
+ }
493
+ to {
494
+ opacity: 1;
495
+ transform: translateY(0);
496
+ }
497
+ }
498
+
499
+ .input-card, .output-card, .record-audio-section {
500
+ animation: fadeInUp 0.6s ease-out;
501
+ }
502
+
503
+ /* Microphone icon animation */
504
+ @keyframes pulse {
505
+ 0% {
506
+ transform: scale(1);
507
+ }
508
+ 50% {
509
+ transform: scale(1.05);
510
+ }
511
+ 100% {
512
+ transform: scale(1);
513
+ }
514
+ }
515
+
516
+ .recording-active {
517
+ animation: pulse 1s infinite;
518
+ }
519
+ </style>
520
+ """
521
+
522
+ # Buat interface dengan theme modern
523
+ with gr.Blocks(
524
+ title="🩺 SOAP AI - Modern Interface",
525
+ css=modern_css,
526
+ theme=gr.themes.Soft(
527
+ primary_hue="blue",
528
+ secondary_hue="cyan",
529
+ neutral_hue="slate",
530
+ font=gr.themes.GoogleFont("Inter")
531
+ )
532
+ ) as app:
533
+
534
+ # Header
535
+ gr.HTML("""
536
+ <div class="main-header">
537
+ <h1>πŸŽ™οΈ Realtime Recording</h1>
538
+ <p>High Quality Audio Recording with Smart Notifications</p>
539
+ </div>
540
+ """)
541
 
542
  with gr.Row():
543
+ with gr.Column(scale=8):
544
+ input_choice = gr.Dropdown(
545
+ choices=["Upload Audio", "Realtime Recording", "Input Teks"],
546
+ value="Realtime Recording",
547
+ label="🎯 Pilih Metode Input",
548
+ container=True,
549
+ elem_classes=["input-dropdown"]
550
+ )
551
+ with gr.Column(scale=2):
552
+ clear_button = gr.Button(
553
+ "πŸ—‘οΈ Clear",
554
+ variant="secondary",
555
+ size="sm",
556
+ elem_classes=["reset-btn"]
557
+ )
558
+
559
+ # Input Section - Upload Audio
560
+ with gr.Group(elem_classes=["input-card"], visible=False) as upload_audio_group:
561
+ gr.HTML("<h3>πŸ“ Upload Audio File</h3>")
562
+
563
+ audio_upload = gr.Audio(
564
+ sources=["upload"],
565
+ label="πŸ“ Upload File Audio",
566
+ type="filepath",
567
+ show_download_button=False,
568
+ show_share_button=False,
569
+ interactive=True,
570
+ elem_classes=["audio-input"]
571
  )
572
+
573
+ # Input Section - Record Audio with proper padding
574
+ with gr.Group(elem_classes=["record-audio-section"], visible=True) as record_audio_group:
575
+ gr.HTML("<h3>🎡 Record Your Audio</h3>")
576
+
577
+ audio_record = gr.Audio(
578
+ sources=["microphone"],
579
+ label="πŸŽ™οΈ Realtime Recording",
580
+ type="filepath",
581
+ show_download_button=True,
582
+ show_share_button=False,
583
+ interactive=True,
584
+ streaming=False,
585
+ elem_classes=["audio-input"]
586
+ )
587
+
588
+ # Input Section - Text Input (without record audio section)
589
+ with gr.Group(elem_classes=["input-card"], visible=False) as text_input_group:
590
+ gr.HTML("<h3>πŸ“ Input Teks</h3>")
591
+
592
+ text_input = gr.Textbox(
593
+ label="πŸ“ Masukkan Percakapan Dokter-Pasien",
594
+ lines=6,
595
+ placeholder="Ketik percakapan antara dokter dan pasien di sini...",
596
+ elem_classes=["text-input"]
597
+ )
598
+
599
+ # Status Section - hanya untuk Realtime Recording
600
+ recording_status_group = gr.Group(elem_classes=["status-indicator"], visible=True)
601
+ with recording_status_group:
602
+ gr.HTML("<h4>πŸ“Š Status Recording</h4>")
603
+ recording_status = gr.Textbox(
604
+ value="Siap untuk merekam",
605
+ interactive=False,
606
+ show_label=False,
607
+ lines=1,
608
+ elem_classes=["status-display"]
609
+ )
610
+
611
+ # Validation Section
612
+ validation_upload = gr.Textbox(
613
+ label="⚠️ Validasi Upload Audio",
614
+ lines=1,
615
+ interactive=False,
616
+ visible=False,
617
+ elem_classes=["validation-msg"]
618
  )
619
+ validation_realtime = gr.Textbox(
620
+ label="⚠️ Validasi Realtime Recording",
621
+ lines=1,
622
+ interactive=False,
623
+ visible=True,
624
+ elem_classes=["validation-msg"]
 
625
  )
626
+ validation_text = gr.Textbox(
627
+ label="⚠️ Validasi Input Teks",
628
+ lines=1,
629
+ interactive=False,
630
+ visible=False,
631
+ elem_classes=["validation-msg"]
632
+ )
633
+
634
+ # Process Button
635
+ process_button = gr.Button(
636
+ "πŸš€ Proses ke SOAP",
637
+ variant="primary",
638
+ size="lg",
639
+ elem_classes=["process-btn"]
640
  )
641
 
642
+ # Output Section with proper alignment - Fixed Layout
643
+ with gr.Group(elem_classes=["output-card"]):
644
+ # Header aligned properly
645
+ with gr.Row():
646
+ with gr.Column():
647
+ gr.HTML('<div class="output-header"><h3>πŸ“‹ Hasil Analisis</h3></div>')
648
+
649
+ # All outputs in aligned container
650
+ with gr.Column(elem_classes=["output-container"]):
651
+ transcript_output = gr.Textbox(
652
+ label="πŸ“ Hasil Transkripsi",
653
+ lines=4,
654
+ elem_classes=["output-section"]
655
+ )
656
+
657
+ soap_output = gr.Textbox(
658
+ label="πŸ“‹ Ringkasan SOAP",
659
+ lines=8,
660
+ elem_classes=["output-section"]
661
+ )
662
+
663
+ tags_output = gr.Textbox(
664
+ label="🏷️ Medical Tags",
665
+ lines=6,
666
+ elem_classes=["output-section"]
667
+ )
668
 
669
+ # Footer
670
+ gr.HTML("""
671
+ <div style="text-align: center; padding: 2rem; color: rgba(255,255,255,0.7);">
672
+ <p>Use via API πŸ”₯ β€’ Built with Gradio πŸš€</p>
673
+ </div>
674
+ """)
675
 
676
+ # Event handlers untuk toggle inputs
 
677
  input_choice.change(
678
+ fn=lambda choice: (
679
+ gr.update(visible=(choice == "Upload Audio")), # upload_audio_group
680
+ gr.update(visible=(choice == "Realtime Recording")), # record_audio_group
681
+ gr.update(visible=(choice == "Input Teks")), # text_input_group
682
+ gr.update(visible=(choice == "Upload Audio")), # validation_upload
683
+ gr.update(visible=(choice == "Realtime Recording")), # validation_realtime
684
+ gr.update(visible=(choice == "Input Teks")), # validation_text
685
+ gr.update(visible=(choice == "Realtime Recording")), # recording_status_group
686
+ gr.update(value=""), # transcript
687
+ gr.update(value=""), # soap
688
+ gr.update(value=""), # tags
689
+ ),
690
  inputs=input_choice,
691
+ outputs=[
692
+ upload_audio_group,
693
+ record_audio_group,
694
+ text_input_group,
695
+ validation_upload,
696
+ validation_realtime,
697
+ validation_text,
698
+ recording_status_group,
699
+ transcript_output,
700
+ soap_output,
701
+ tags_output,
702
+ ],
703
+ )
704
+
705
+ # Event handlers untuk recording
706
+ audio_record.start_recording(
707
+ fn=start_recording,
708
+ outputs=recording_status
709
+ )
710
+
711
+ audio_record.stop_recording(
712
+ fn=stop_recording,
713
+ inputs=audio_record,
714
+ outputs=recording_status
715
  )
716
 
 
717
  clear_button.click(
718
  fn=clear_all_data,
719
+ outputs=[
720
+ audio_upload,
721
+ audio_record,
722
+ text_input,
723
+ validation_upload,
724
+ validation_realtime,
725
+ validation_text,
726
+ recording_status,
727
+ transcript_output,
728
+ soap_output,
729
+ tags_output,
730
+ ],
731
  )
732
 
 
733
  process_button.click(
734
  fn=process_data,
735
  inputs=[input_choice, audio_upload, audio_record, text_input],
736
+ outputs=[
737
+ validation_upload,
738
+ validation_realtime,
739
+ validation_text,
740
+ transcript_output,
741
+ soap_output,
742
+ tags_output,
743
+ ],
744
+ show_progress="minimal",
745
  )
746
 
747
+ # Startup information
748
+ if __name__ == "__main__":
749
+ print("πŸš€ Starting Enhanced SOAP AI Application with Modern UI...")
750
+ print("πŸ“‹ Setup Instructions:")
751
+ print("1. Install dependencies: pip install gradio pygame pydub nltk requests python-dotenv")
752
+ print(f"2. Place your sound files:")
753
+ print(f" - Start recording sound: {START_RECORDING_SOUND_PATH}")
754
+ print(f" - Completion sound: {NOTIFICATION_SOUND_PATH}")
755
+ print("3. Supported sound formats: WAV, MP3, OGG")
756
+ print("4. Make sure wordlist.lst file is available")
757
+ print("5. Set up your .env file with API_TRANSCRIBE and API_TEXT")
758
+ print()
759
+
760
+ # Cek apakah file sound ada
761
+ sounds_status = []
762
+ if os.path.exists(START_RECORDING_SOUND_PATH):
763
+ print(f"βœ… Start recording sound found: {START_RECORDING_SOUND_PATH}")
764
+ sounds_status.append("start")
765
+ else:
766
+ print(f"⚠️ Start recording sound not found: {START_RECORDING_SOUND_PATH}")
767
+
768
+ if os.path.exists(NOTIFICATION_SOUND_PATH):
769
+ print(f"βœ… Completion sound found: {NOTIFICATION_SOUND_PATH}")
770
+ sounds_status.append("completion")
771
+ else:
772
+ print(f"⚠️ Completion sound not found: {NOTIFICATION_SOUND_PATH}")
773
+
774
+ if not sounds_status:
775
+ print("πŸ“ Note: Add sound files to enable audio notifications for realtime recording")
776
+ elif len(sounds_status) == 1:
777
+ print("πŸ“ Note: Add the missing sound file for complete audio experience")
778
+
779
+ print("\n🌐 Application will start at: http://localhost:7860")
780
+ print("πŸŽ™οΈ Make sure to allow microphone access when using Realtime Recording!")
781
+ print("✨ New Modern UI with enhanced visual experience!")
782
+ print()
783
+
784
  app.launch()
wordlist.lst ADDED
The diff for this file is too large to render. See raw diff