Spaces:
Running
Running
import os | |
import json | |
import re | |
import gradio as gr | |
from google import genai | |
from google.genai import types | |
import asyncio | |
from datasets import load_dataset, DatasetDict, Dataset | |
from huggingface_hub import login | |
import datetime | |
# Authenticate with HF token | |
hf_token = os.getenv("HF_TOKEN") | |
login(token=hf_token) | |
dataset_name = "spriambada3/ehealth_transcribe" | |
def init_dataset(): | |
try: | |
dataset = load_dataset(dataset_name) | |
except Exception as e: | |
print(e) | |
dataset = DatasetDict( | |
{"data": Dataset.from_dict({"logintime": [], "email": [], "wa": []})} | |
) | |
print("init dataset result ") | |
print(dataset) | |
return dataset | |
def add_user(dataset, email, wa): | |
new_data = { | |
"logintime": datetime.datetime.now(), | |
"email": email, | |
"wa": wa, | |
} | |
dataset["data"] = dataset["data"].add_item(new_data) | |
dataset.push_to_hub(dataset_name) # Save to HF Hub | |
print("add data successful") | |
def audio_from_bytes(audio_file_path: str): | |
"""Converts an audio file into Gemini-compatible format.""" | |
try: | |
with open(audio_file_path, "rb") as f: | |
audio_data = f.read() | |
mime_type = "audio/mp3" # Adjust based on your audio type | |
return types.Part.from_bytes(data=audio_data, mime_type=mime_type) | |
except FileNotFoundError: | |
return "Error: Audio file not found!" | |
except Exception as e: | |
return f"An error occurred: {e}" | |
def transcribe_and_summarize(audio_file, session): | |
"""Processes audio with Gemini API and returns a SOAP summary.""" | |
if audio_file is None: | |
return "No audio file uploaded." | |
# Ensure API Key is set | |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") | |
if not GEMINI_API_KEY: | |
return "Error: GEMINI_API_KEY environment variable is missing." | |
asyncio.set_event_loop(asyncio.new_event_loop()) | |
client = genai.Client(api_key=GEMINI_API_KEY) | |
model = "gemini-2.0-flash" | |
# Prepare the request | |
contents = [ | |
types.Content( | |
role="user", | |
parts=[ | |
types.Part.from_text( | |
text="""Anda adalah asisten medis yang membantu dokter dalam menyusun catatan medis dalam bentuk paragraf menggunakan bahasa Indonesia. | |
Buat ringkasan SOAP berdasarkan percakapan dokter dan pasien dalam format berikut: | |
Subjective: | |
ICD10: | |
Objective: | |
Assessment: | |
Plan: | |
Identifikasi dan berikan saran dalam bahasa Indonesia tindakan logis selanjutnya dalam format: | |
ICD10: | |
Obat: | |
Laboratorium: | |
Radiologi: | |
""" | |
), | |
audio_from_bytes(audio_file), | |
], | |
) | |
] | |
generate_content_config = types.GenerateContentConfig( | |
temperature=0, | |
top_p=0.95, | |
top_k=40, | |
max_output_tokens=8192, | |
response_mime_type="text/plain", | |
) | |
# Process the audio | |
response_text = "" | |
for chunk in client.models.generate_content_stream( | |
model=model, | |
contents=contents, | |
config=generate_content_config, | |
): | |
response_text += chunk.text | |
counter_display, session = click_button(session) | |
return response_text, counter_display, session | |
DATA_FILE = "user_data.json" | |
def load_user_data(): | |
"""Load user data from JSON file.""" | |
if os.path.exists(DATA_FILE): | |
with open(DATA_FILE, "r") as file: | |
return json.load(file) | |
return {} # Return empty dictionary if no data | |
def save_user_data(username, email): | |
"""Save user data to JSON file with default counter = 10 if new.""" | |
data = load_user_data() | |
if username not in data: # New user | |
data[username] = {"email": email, "counter": 10} # Set default counter to 10 | |
with open(DATA_FILE, "w") as file: | |
json.dump(data, file, indent=4) | |
wa = username | |
dataset = init_dataset() | |
add_user(dataset, email, wa) | |
return data | |
def is_valid_wa(username): | |
"""Check if username is all numbers, at least 11 characters, and starts with 08 or 62.""" | |
return re.fullmatch(r"^(08|62)\d{9,}$", username) is not None | |
def is_valid_email(email): | |
"""Check if email format is valid.""" | |
return re.fullmatch(r"^[\w\.-]+@[\w\.-]+\.\w+$", email) is not None | |
def login(username, email, session): | |
"""Handles user login or registration with validation.""" | |
if not is_valid_wa(username): | |
return ( | |
"❌ Invalid WA! Nomor WA minimal 12 digits dimulai '08' atau '62'.", | |
session, | |
gr.update(visible=True), | |
gr.update(visible=False), | |
) | |
if not is_valid_email(email): | |
return ( | |
"❌ Invalid email format! Please enter a valid email.", | |
session, | |
gr.update(visible=True), | |
gr.update(visible=False), | |
) | |
data = save_user_data(username, email) # Save or retrieve user data | |
session["username"] = username | |
session["counter"] = data[username]["counter"] | |
return "", session, gr.update(visible=False), gr.update(visible=True) | |
def click_button(session): | |
"""Decrease counter on button click.""" | |
err = f"⚠️ Quota habis. Silahkan mengunjungi https://ehealth.co.id atau WA 6285777779926 untuk menambah kuota" | |
if session["counter"] > 0: | |
session["counter"] -= 1 | |
# Update the user data in JSON file | |
data = load_user_data() | |
data[session["username"]]["counter"] = session["counter"] | |
with open(DATA_FILE, "w") as file: | |
json.dump(data, file, indent=4) | |
if session["counter"] == 0: | |
return (err, session) | |
return f"Quota: {session['counter']}", session | |
else: | |
return (err, session) | |
# Gradio Interface | |
with gr.Blocks(theme=gr.themes.Default()) as demo: | |
session = gr.State({"username": None, "counter": 0}) # Manage session state | |
# Login Section | |
login_block = gr.Column(visible=True) | |
with login_block: | |
gr.HTML( | |
""" | |
<div style="text-align: center;"> | |
<a href="https://youtube.com/shorts/DmiVhj9ROag?si=j5Opmjny3kNdLlrf" target="_blank" style="font-size: 20px;"> | |
<strong>Klik disini untuk Demo Video <img src="https://upload.wikimedia.org/wikipedia/commons/b/b8/YouTube_Logo_2017.svg" alt="YouTube" width="100"><br></strong> | |
</a> | |
</div> | |
""" | |
) | |
email_input = gr.Textbox(label="Email") | |
username_input = gr.Textbox(label="WA", type="password") # Hide input | |
login_button = gr.Button("🔑 Login / Register") | |
gr.Markdown( | |
"""### dengan login, saya menyetujui ketentuan penggunaan data perusahaan https://eHealth.co.id dan tidak akan menuntut eHealth.co.id dalam uji coba gratis AI Transkripsi Medis ini | |
seluruh data yang saya sediakan adalah data yang benar dan tidak melanggar hukum | |
saya memahami bahwa tidak ada data suara maupun tulisan medis yang akan disimpan oleh eHealth.co.id, namun perusahaan tidak dapat menjamin perlakuan data penyedia model AI (OpenAI, DeepSeek, Google, Mistral, dll.) | |
### setelah quota habis, saya dapat menambah quota dengan mengunjungi https://ehealth.co.id atau WA 6285777779926""" | |
) | |
output_text = gr.Textbox(label="Status", interactive=False) | |
# Main User Interface (After Login) | |
user_block = gr.Column(visible=False) | |
with user_block: | |
counter_display = gr.Textbox(label="Status Message", interactive=False) | |
gr.Interface( | |
fn=transcribe_and_summarize, | |
inputs=[gr.Audio(type="filepath", sources="microphone"), session], | |
outputs=["text", counter_display, session], | |
description="Halo, pastikan HP/Laptop memiliki microphone untuk merekam percakapan dokter-pasien menjadi rekam medis SOAP. Akun berlangganan https://ehealth.co.id dapat terintegrasi SATUSEHAT & BPJS secara otomatis", | |
allow_flagging="never", | |
theme="light", | |
) | |
use_case_description = gr.Markdown( | |
""" | |
Selain Rekam Medis Pasien, dokumen lain yang dapat digitalisasi: | |
- Surgery Notes atau Catatan Tindakan Lain | |
- Inform Concern (Dokter dan Keluarga Pasien/Pasien) | |
- Counseling | |
- Nursing reports | |
- Clinical documentation | |
- Continue Care Document (untuk RS dokumentasi pemberian obat, infus, dll).""" | |
) | |
# Login button action | |
login_button.click( | |
login, | |
[username_input, email_input, session], | |
[output_text, session, login_block, user_block], | |
trigger_mode="once", | |
) | |
demo.launch(allowed_paths=["./images/eHwhite.png", "eHwhite.png", "./images/pp.png"]) | |