import gradio as gr import threading import os import requests import string import time from pydub import AudioSegment from nltk.tokenize import word_tokenize import nltk from nltk.corpus import words, stopwords from dotenv import load_dotenv # Download resource NLTK (hanya sekali) nltk.download('punkt') nltk.download('words') nltk.download('stopwords') load_dotenv() API_TRANSCRIBE = os.getenv("API_TRANSCRIBE") API_TEXT = os.getenv("API_TEXT") english_words = set(words.words()) indonesian_stopwords = set(stopwords.words('indonesian')) def load_indonesian_wordlist(filepath='wordlist.lst'): try: with open(filepath, encoding='utf-8') as f: return set(line.strip().lower() for line in f if line.strip()) except UnicodeDecodeError: try: with open(filepath, encoding='latin-1') as f: return set(line.strip().lower() for line in f if line.strip()) except Exception: return set() except Exception: return set() indonesian_words = load_indonesian_wordlist() valid_words = english_words.union(indonesian_words) def contains_medical_terms_auto_threshold(text, medical_words): tokens = word_tokenize(text.lower()) tokens = [w.strip(string.punctuation) for w in tokens if w.isalpha()] if not tokens: return False medical_count = sum(1 for w in tokens if w in medical_words) ratio = medical_count / len(tokens) threshold = 0.4 if len(tokens) <= 5 else 0.1 return ratio >= threshold medical_words = load_indonesian_wordlist('wordlist.lst') MAX_DURATION_SECONDS = 600 def validate_audio_duration(audio_file): try: audio = AudioSegment.from_file(audio_file) duration_sec = len(audio) / 1000.0 if duration_sec > MAX_DURATION_SECONDS: return False, duration_sec return True, duration_sec except Exception as e: return False, -1 def start_recording(): """Function yang dipanggil ketika tombol record ditekan""" print("šŸŽ™ļø Recording started...") return ( "šŸŽ™ļø Sedang merekam... Klik stop untuk menyelesaikan", gr.update(elem_classes=["record-audio-section", "recording-active"]) # Add red border ) def stop_recording(audio): """Function yang dipanggil ketika recording selesai""" if audio is not None: print("āœ… Recording completed!") return ( "āœ… Recording selesai! Audio siap diproses", gr.update(elem_classes=["record-audio-section", "recording-completed"]) # Add blue border ) else: print("āŒ No audio recorded") return ( "āŒ Tidak ada audio yang direkam", gr.update(elem_classes=["record-audio-section"]) # Reset to default ) def test_microphone(): """Function untuk test microphone""" print("šŸ”§ Testing microphone...") return "šŸ”§ Testing microphone... Silakan coba record lagi" def reset_recording_status(): """Function untuk reset status recording""" return ( "šŸ“± Siap untuk merekam - Klik tombol record", gr.update(elem_classes=["record-audio-section"]) # Reset to default ) def handle_audio(audio_file): """Handle audio processing - returns (validation_message, transcript, soap, tags)""" if audio_file is None: return "āŒ Tidak ada file audio", "", "", "" valid, duration = validate_audio_duration(audio_file) if not valid: if duration == -1: msg = "āš ļø Gagal memproses file audio." else: msg = f"āš ļø Durasi rekaman terlalu panjang ({duration:.1f}s). Maksimal {MAX_DURATION_SECONDS}s." return msg, "", "", "" try: with open(audio_file, "rb") as f: files = {"audio": f} response = requests.post(API_TRANSCRIBE, files=files) result = response.json() transcription = result.get("transcription", "") soap_content = result.get("soap_content", "") tags_content = result.get("tags_content", "") if not transcription and not soap_content and not tags_content: return "āš ļø Tidak ada hasil dari proses audio", "", "", "" return "", transcription, soap_content, tags_content except Exception as e: return f"āŒ Error processing audio: {str(e)}", "", "", "" def handle_text(dialogue): """Handle text processing - returns (validation_message, transcript, soap, tags)""" if not dialogue.strip(): return "āš ļø Teks tidak boleh kosong", "", "", "" if not contains_medical_terms_auto_threshold(dialogue, medical_words): return "āš ļø Teks tidak mengandung istilah medis yang cukup untuk diproses.", "", "", "" try: response = requests.post(API_TEXT, json={"dialogue": dialogue}) result = response.json() soap_content = result.get("soap_content", "") tags_content = result.get("tags_content", "") if not soap_content and not tags_content: return "āš ļø Tidak ada hasil dari proses teks", "", "", "" return "", dialogue, soap_content, tags_content except Exception as e: return f"āŒ Error processing text: {str(e)}", "", "", "" def toggle_inputs_with_refresh(choice): # Tampilkan input dan validasi yang sesuai, sembunyikan lainnya return ( gr.update(visible=(choice == "Upload Audio"), value=None), # audio upload gr.update(visible=(choice == "Realtime Recording"), value=None), # audio record gr.update(visible=(choice == "Input Teks"), value=""), # text input gr.update(visible=(choice == "Upload Audio")), # validasi upload gr.update(visible=(choice == "Realtime Recording")), # validasi realtime gr.update(visible=(choice == "Input Teks")), # validasi teks gr.update(visible=(choice == "Realtime Recording")), # recording status group gr.update(visible=(choice == "Realtime Recording"), elem_classes=["record-audio-section"]), # record audio group with reset border gr.update(value=""), # transcript gr.update(value=""), # soap gr.update(value=""), # tags ) def clear_all_data(): return ( gr.update(value=None), # audio_upload gr.update(value=None), # audio_record gr.update(value=""), # text_input gr.update(value=""), # validation_upload gr.update(value=""), # validation_realtime gr.update(value=""), # validation_text gr.update(value="šŸ“± Siap untuk merekam"), # recording_status gr.update(value=""), # transcript_output gr.update(value=""), # soap_output gr.update(value=""), # tags_output gr.update(elem_classes=["record-audio-section"]), # reset record audio group border ) def process_data(choice, audio_upload, audio_record, text_input): """ Process data based on choice and return results in correct order: Returns: (validation_upload, validation_realtime, validation_text, transcript, soap, tags) """ if choice == "Upload Audio": # Process upload audio validation_msg, transcript, soap, tags = handle_audio(audio_upload) return ( validation_msg, # validation_upload "", # validation_realtime (empty) "", # validation_text (empty) transcript, # transcript_output soap, # soap_output tags # tags_output ) elif choice == "Realtime Recording": # Process realtime recording validation_msg, transcript, soap, tags = handle_audio(audio_record) return ( "", # validation_upload (empty) validation_msg, # validation_realtime "", # validation_text (empty) transcript, # transcript_output soap, # soap_output tags # tags_output ) elif choice == "Input Teks": # Process text input validation_msg, transcript, soap, tags = handle_text(text_input) return ( "", # validation_upload (empty) "", # validation_realtime (empty) validation_msg, # validation_text transcript, # transcript_output (will be same as input for text) soap, # soap_output tags # tags_output ) else: # Default case - clear all return ("", "", "", "", "", "") # Custom CSS untuk tampilan modern dengan alignment yang diperbaiki dan border dinamis modern_css = """ """ # Buat interface dengan theme modern with gr.Blocks( title="🩺 SOAP AI - Modern Interface", css=modern_css, theme=gr.themes.Soft( primary_hue="blue", secondary_hue="cyan", neutral_hue="slate", font=gr.themes.GoogleFont("Inter") ) ) as app: # Header gr.HTML("""

šŸŽ™ļø Realtime Recording

High Quality Audio Recording with Smart Visual Feedback

""") with gr.Row(): with gr.Column(scale=8): input_choice = gr.Dropdown( choices=["Upload Audio", "Realtime Recording", "Input Teks"], value="Realtime Recording", label="šŸŽÆ Pilih Metode Input", container=True, elem_classes=["input-dropdown"] ) with gr.Column(scale=2): clear_button = gr.Button( "šŸ—‘ļø Clear", variant="secondary", size="sm", elem_classes=["reset-btn"] ) # Input Section - Upload Audio with gr.Group(elem_classes=["input-card"], visible=False) as upload_audio_group: gr.HTML("

šŸ“ Upload Audio File

") audio_upload = gr.Audio( sources=["upload"], label="šŸ“ Upload File Audio", type="filepath", show_download_button=False, show_share_button=False, interactive=True, elem_classes=["audio-input"] ) # Input Section - Record Audio with proper padding and dynamic border record_audio_group = gr.Group(elem_classes=["record-audio-section"], visible=True) with record_audio_group: gr.HTML("

šŸŽµ Record Your Audio

") audio_record = gr.Audio( sources=["microphone"], label="šŸŽ™ļø Realtime Recording", type="filepath", show_download_button=True, show_share_button=False, interactive=True, streaming=False, elem_classes=["audio-input"] ) # Input Section - Text Input (without record audio section) with gr.Group(elem_classes=["input-card"], visible=False) as text_input_group: gr.HTML("

šŸ“ Input Teks

") text_input = gr.Textbox( label="šŸ“ Masukkan Percakapan Dokter-Pasien", lines=6, placeholder="Ketik percakapan antara dokter dan pasien di sini...", elem_classes=["text-input"] ) # Status Section - hanya untuk Realtime Recording recording_status_group = gr.Group(elem_classes=["status-indicator"], visible=True) with recording_status_group: gr.HTML("

šŸ“Š Status Recording

") recording_status = gr.Textbox( value="Siap untuk merekam", interactive=False, show_label=False, lines=1, elem_classes=["status-display"] ) # Validation Section validation_upload = gr.Textbox( label="āš ļø Validasi Upload Audio", lines=1, interactive=False, visible=False, elem_classes=["validation-msg"] ) validation_realtime = gr.Textbox( label="āš ļø Validasi Realtime Recording", lines=1, interactive=False, visible=True, elem_classes=["validation-msg"] ) validation_text = gr.Textbox( label="āš ļø Validasi Input Teks", lines=1, interactive=False, visible=False, elem_classes=["validation-msg"] ) # Process Button process_button = gr.Button( "šŸš€ Proses ke SOAP", variant="primary", size="lg", elem_classes=["process-btn"] ) # Output Section with proper alignment - Fixed Layout with gr.Group(elem_classes=["output-card"]): # Header aligned properly with gr.Row(): with gr.Column(): gr.HTML('

šŸ“‹ Hasil Analisis

') # All outputs in aligned container with gr.Column(elem_classes=["output-container"]): transcript_output = gr.Textbox( label="šŸ“ Hasil Transkripsi", lines=4, elem_classes=["output-section"] ) soap_output = gr.Textbox( label="šŸ“‹ Ringkasan SOAP", lines=8, elem_classes=["output-section"] ) tags_output = gr.Textbox( label="šŸ·ļø Medical Tags", lines=6, elem_classes=["output-section"] ) # Footer gr.HTML("""

Use via API šŸ”„ • Built with Gradio šŸš€

""") # Event handlers untuk toggle inputs input_choice.change( fn=lambda choice: ( gr.update(visible=(choice == "Upload Audio")), # upload_audio_group gr.update(visible=(choice == "Realtime Recording"), elem_classes=["record-audio-section"]), # record_audio_group with reset border gr.update(visible=(choice == "Input Teks")), # text_input_group gr.update(visible=(choice == "Upload Audio")), # validation_upload gr.update(visible=(choice == "Realtime Recording")), # validation_realtime gr.update(visible=(choice == "Input Teks")), # validation_text gr.update(visible=(choice == "Realtime Recording")), # recording_status_group gr.update(value=""), # transcript gr.update(value=""), # soap gr.update(value=""), # tags ), inputs=input_choice, outputs=[ upload_audio_group, record_audio_group, text_input_group, validation_upload, validation_realtime, validation_text, recording_status_group, transcript_output, soap_output, tags_output, ], ) # Event handlers untuk recording dengan border dynamics audio_record.start_recording( fn=start_recording, outputs=[recording_status, record_audio_group] ) audio_record.stop_recording( fn=stop_recording, inputs=audio_record, outputs=[recording_status, record_audio_group] ) clear_button.click( fn=clear_all_data, outputs=[ audio_upload, audio_record, text_input, validation_upload, validation_realtime, validation_text, recording_status, transcript_output, soap_output, tags_output, record_audio_group, # Reset border when clearing ], ) process_button.click( fn=process_data, inputs=[input_choice, audio_upload, audio_record, text_input], outputs=[ validation_upload, validation_realtime, validation_text, transcript_output, soap_output, tags_output, ], show_progress="minimal", ) # Startup information if __name__ == "__main__": print("šŸš€ Starting Enhanced SOAP AI Application...") print("šŸ“‹ Setup Instructions:") print("1. Install dependencies: pip install gradio pydub nltk requests python-dotenv") print("2. Make sure wordlist.lst file is available") print("3. Set up your .env file with API_TRANSCRIBE and API_TEXT") print() print("\n🌐 Application will start at: http://localhost:7860") print("šŸŽ™ļø Make sure to allow microphone access when using Realtime Recording!") print("šŸ“Š Visual feedback provided through dynamic border colors during recording") print() app.launch()