import sys import os import pandas as pd import pdfplumber import json import gradio as gr from typing import List from concurrent.futures import ThreadPoolExecutor, as_completed import hashlib import shutil # ✅ Fix: Add src to Python path sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))) # ✅ Persist model cache to Hugging Face Space's /data directory model_cache_dir = "/data/txagent_models" os.makedirs(model_cache_dir, exist_ok=True) os.environ["TRANSFORMERS_CACHE"] = model_cache_dir os.environ["HF_HOME"] = model_cache_dir from txagent.txagent import TxAgent def sanitize_utf8(text: str) -> str: return text.encode("utf-8", "ignore").decode("utf-8") def file_hash(path): with open(path, "rb") as f: return hashlib.md5(f.read()).hexdigest() def convert_file_to_json(file_path: str, file_type: str) -> str: try: cache_dir = "/data/cache" os.makedirs(cache_dir, exist_ok=True) h = file_hash(file_path) cache_path = os.path.join(cache_dir, f"{h}.json") if os.path.exists(cache_path): return open(cache_path, "r", encoding="utf-8").read() if file_type == "csv": df = pd.read_csv(file_path, encoding_errors="replace", header=None, dtype=str, skip_blank_lines=False, on_bad_lines="skip") elif file_type in ["xls", "xlsx"]: try: df = pd.read_excel(file_path, engine="openpyxl", header=None, dtype=str) except: df = pd.read_excel(file_path, engine="xlrd", header=None, dtype=str) elif file_type == "pdf": with pdfplumber.open(file_path) as pdf: text = "\n".join([page.extract_text() or "" for page in pdf.pages]) result = json.dumps({"filename": os.path.basename(file_path), "content": text.strip()}) open(cache_path, "w", encoding="utf-8").write(result) return result else: return json.dumps({"error": f"Unsupported file type: {file_type}"}) if df is None or df.empty: return json.dumps({"warning": f"No data extracted from: {file_path}"}) df = df.fillna("") content = df.astype(str).values.tolist() result = json.dumps({"filename": os.path.basename(file_path), "rows": content}) open(cache_path, "w", encoding="utf-8").write(result) return result except Exception as e: return json.dumps({"error": f"Error reading {os.path.basename(file_path)}: {str(e)}"}) def create_ui(agent: TxAgent): with gr.Blocks(theme=gr.themes.Soft()) as demo: gr.Markdown("

📋 CPS: Clinical Patient Support System

") chatbot = gr.Chatbot(label="CPS Assistant", height=600, type="messages") file_upload = gr.File( label="Upload Medical File", file_types=[".pdf", ".txt", ".docx", ".jpg", ".png", ".csv", ".xls", ".xlsx"], file_count="multiple" ) message_input = gr.Textbox(placeholder="Ask a biomedical question or just upload the files...", show_label=False) send_button = gr.Button("Send", variant="primary") conversation_state = gr.State([]) def handle_chat(message: str, history: list, conversation: list, uploaded_files: list, progress=gr.Progress()): try: history.append({"role": "user", "content": message}) history.append({"role": "assistant", "content": "⏳ Processing your request..."}) yield history extracted_text = "" if uploaded_files and isinstance(uploaded_files, list): for file in uploaded_files: if not hasattr(file, 'name'): continue path = file.name ext = path.split(".")[-1].lower() json_text = convert_file_to_json(path, ext) extracted_text += sanitize_utf8(json_text) + "\n" # Only final chunk will be passed (no split or loop) context = ( "You are an expert clinical AI assistant. Review this patient's history, medications, and notes, and ONLY provide a final answer summarizing what the doctor might have missed." ) chunked_prompt = f"{context}\n\n--- Patient Record ---\n{extracted_text}\n\n[Final Analysis]" generator = agent.run_gradio_chat( message=chunked_prompt, history=[], temperature=0.3, max_new_tokens=1024, max_token=8192, call_agent=False, conversation=conversation, uploaded_files=uploaded_files, max_round=30 ) final_response = "" for update in generator: if update is None: continue if isinstance(update, str): final_response += update elif isinstance(update, list): for msg in update: if hasattr(msg, 'content'): final_response += msg.content history[-1] = {"role": "assistant", "content": final_response.strip() or "❌ No response."} yield history except Exception as chat_error: print(f"Chat handling error: {chat_error}") history[-1] = {"role": "assistant", "content": "❌ An error occurred while processing your request."} yield history inputs = [message_input, chatbot, conversation_state, file_upload] send_button.click(fn=handle_chat, inputs=inputs, outputs=chatbot) message_input.submit(fn=handle_chat, inputs=inputs, outputs=chatbot) gr.Examples([ ["Upload your medical form and ask what the doctor might've missed."], ["This patient was treated with antibiotics for UTI. What else should we check?"], ["Is there anything abnormal in the attached blood work report?"] ], inputs=message_input) return demo