Spaces:
Running
Running
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 "π΄ Recording..." | |
def stop_recording(audio): | |
"""Function yang dipanggil ketika recording selesai""" | |
if audio is not None: | |
print("β Recording completed!") | |
return "β Ready to process" | |
else: | |
print("β No audio recorded") | |
return "βͺ Ready to record" | |
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 "βͺ Ready to record" | |
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(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="βͺ Ready to record"), # recording_status | |
gr.update(value=""), # transcript_output | |
gr.update(value=""), # soap_output | |
gr.update(value=""), # tags_output | |
) | |
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 ("", "", "", "", "", "") | |
# Buat interface dengan tampilan default Gradio | |
with gr.Blocks(title="π©Ί SOAP AI") as app: | |
# Header | |
gr.Markdown("# ποΈ SOAP AI - Medical Transcription & Analysis") | |
gr.Markdown("Aplikasi untuk mengkonversi percakapan dokter-pasien menjadi format SOAP") | |
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" | |
) | |
with gr.Column(scale=2): | |
clear_button = gr.Button("ποΈ Clear", variant="secondary") | |
# Input Section - Upload Audio | |
with gr.Group(visible=False) as upload_audio_group: | |
gr.Markdown("### π Upload Audio File") | |
audio_upload = gr.Audio( | |
sources=["upload"], | |
label="π Upload File Audio", | |
type="filepath" | |
) | |
# Input Section - Record Audio | |
with gr.Group(visible=True) as record_audio_group: | |
gr.Markdown("### π΅ Record Your Audio") | |
recording_status = gr.Textbox( | |
value="βͺ Ready to record", | |
interactive=False, | |
show_label=False, | |
lines=1 | |
) | |
audio_record = gr.Audio( | |
sources=["microphone"], | |
label="ποΈ Realtime Recording", | |
type="filepath" | |
) | |
# Input Section - Text Input | |
with gr.Group(visible=False) as text_input_group: | |
gr.Markdown("### π Input Teks") | |
text_input = gr.Textbox( | |
label="π Masukkan Percakapan Dokter-Pasien", | |
lines=6, | |
placeholder="Ketik percakapan antara dokter dan pasien di sini..." | |
) | |
# Validation Section | |
validation_upload = gr.Textbox( | |
label="β οΈ Validasi Upload Audio", | |
lines=1, | |
interactive=False, | |
visible=False | |
) | |
validation_realtime = gr.Textbox( | |
label="β οΈ Validasi Realtime Recording", | |
lines=1, | |
interactive=False, | |
visible=True | |
) | |
validation_text = gr.Textbox( | |
label="β οΈ Validasi Input Teks", | |
lines=1, | |
interactive=False, | |
visible=False | |
) | |
# Process Button | |
process_button = gr.Button("π Proses ke SOAP", variant="primary", size="lg") | |
# Output Section | |
gr.Markdown("## π Hasil Analisis") | |
transcript_output = gr.Textbox( | |
label="π Hasil Transkripsi", | |
lines=4 | |
) | |
soap_output = gr.Textbox( | |
label="π Ringkasan SOAP", | |
lines=8 | |
) | |
tags_output = gr.Textbox( | |
label="π·οΈ Medical Tags", | |
lines=6 | |
) | |
# Event handlers untuk toggle inputs | |
input_choice.change( | |
fn=lambda choice: ( | |
gr.update(visible=(choice == "Upload Audio")), | |
gr.update(visible=(choice == "Realtime Recording")), | |
gr.update(visible=(choice == "Input Teks")), | |
gr.update(visible=(choice == "Upload Audio")), | |
gr.update(visible=(choice == "Realtime Recording")), | |
gr.update(visible=(choice == "Input Teks")), | |
gr.update(value=""), | |
gr.update(value=""), | |
gr.update(value="") | |
), | |
inputs=input_choice, | |
outputs=[ | |
upload_audio_group, | |
record_audio_group, | |
text_input_group, | |
validation_upload, | |
validation_realtime, | |
validation_text, | |
transcript_output, | |
soap_output, | |
tags_output | |
] | |
) | |
# Event handlers untuk recording | |
audio_record.start_recording( | |
fn=start_recording, | |
outputs=recording_status | |
) | |
audio_record.stop_recording( | |
fn=stop_recording, | |
inputs=audio_record, | |
outputs=recording_status | |
) | |
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 | |
] | |
) | |
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 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() | |
app.launch() |