|
|
import gradio as gr |
|
|
import asyncio |
|
|
import logging |
|
|
from pathlib import Path |
|
|
|
|
|
import sys |
|
|
import os |
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), 'src')) |
|
|
|
|
|
|
|
|
logging.basicConfig( |
|
|
level=logging.INFO, |
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' |
|
|
) |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
try: |
|
|
from src.agent import safe_run_agent_streaming, safe_run_agent, clear_memory |
|
|
from src.data_loaders import process_uploaded_file |
|
|
from src.utils import initialize_knowledge_base |
|
|
except ImportError as e: |
|
|
logger.error(f"Failed to import required modules: {e}") |
|
|
raise |
|
|
|
|
|
|
|
|
logger.info("Initializing knowledge base...") |
|
|
try: |
|
|
knowledge_base = initialize_knowledge_base() |
|
|
if knowledge_base: |
|
|
logger.info("Knowledge base initialized successfully") |
|
|
else: |
|
|
logger.warning("Knowledge base initialization failed - some features may be limited") |
|
|
except Exception as e: |
|
|
logger.error(f"Knowledge base initialization error: {e}") |
|
|
knowledge_base = None |
|
|
|
|
|
|
|
|
processed_docs = [] |
|
|
|
|
|
async def chat_function_streaming(message: str, history: list): |
|
|
"""Process user message through the agent with streaming and better error handling""" |
|
|
if not message or not message.strip(): |
|
|
history.append([message, "عذراً، لم أتلقَ أي سؤال. يرجى إدخال سؤالك أو طلبك."]) |
|
|
yield history, "" |
|
|
return |
|
|
|
|
|
|
|
|
history.append([message, ""]) |
|
|
|
|
|
try: |
|
|
|
|
|
message_to_agent = message |
|
|
if processed_docs: |
|
|
str_processed_docs = "\n".join([ |
|
|
f"{doc.page_content}\n{doc.metadata}" |
|
|
for doc in processed_docs |
|
|
]) |
|
|
message_to_agent = f"{message}\n\nThis is Information you can use:\n\n{str_processed_docs}" |
|
|
|
|
|
|
|
|
accumulated_response = "" |
|
|
async for chunk in safe_run_agent_streaming(message_to_agent): |
|
|
accumulated_response += chunk |
|
|
history[-1][1] = accumulated_response |
|
|
yield history, "" |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error in chat_function_streaming: {e}") |
|
|
history[-1][1] = f"عذراً، حدث خطأ: {str(e)}" |
|
|
yield history, "" |
|
|
|
|
|
def upload_and_process_file(file) -> str: |
|
|
"""Process uploaded file and add to knowledge base""" |
|
|
global processed_docs |
|
|
|
|
|
if file is None: |
|
|
return "لم يتم رفع أي ملف" |
|
|
|
|
|
try: |
|
|
|
|
|
file_path = Path(file) |
|
|
|
|
|
|
|
|
allowed_extensions = {'.pdf', '.txt', '.docx', '.doc'} |
|
|
if file_path.suffix.lower() not in allowed_extensions: |
|
|
return f"نوع الملف غير مدعوم: {file_path.suffix}. الأنواع المدعومة: {', '.join(allowed_extensions)}" |
|
|
|
|
|
|
|
|
file_size = file_path.stat().st_size |
|
|
if file_size > 10 * 1024 * 1024: |
|
|
return "الملف كبير جداً. الحد الأقصى 10 ميجابايت." |
|
|
|
|
|
|
|
|
new_documents = process_uploaded_file(file_path) |
|
|
|
|
|
if new_documents: |
|
|
|
|
|
processed_docs.extend(new_documents) |
|
|
return f"تم معالجة الملف '{file_path.name}' بنجاح. تمت إضافة {len(new_documents)} وثيقة." |
|
|
else: |
|
|
return f"لم يتم العثور على محتوى قابل للمعالجة في الملف '{file_path.name}'" |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Error processing file {file}: {e}") |
|
|
return f"خطأ في معالجة الملف '{file}': {str(e)}" |
|
|
|
|
|
def clear_chat_memory_and_history(): |
|
|
"""Clear the conversation memory, processed documents, chat history, and upload status""" |
|
|
global processed_docs |
|
|
|
|
|
try: |
|
|
clear_memory() |
|
|
processed_docs = [] |
|
|
logger.info("Successfully cleared chat memory and history") |
|
|
|
|
|
return [], "تم مسح الذاكرة بنجاح. بدأت محادثة جديدة!", "" |
|
|
except Exception as e: |
|
|
logger.error(f"Error clearing memory: {e}") |
|
|
return [], f"خطأ في مسح الذاكرة: {str(e)}", "" |
|
|
|
|
|
def validate_startup(): |
|
|
"""Validate system before launching""" |
|
|
required_env_vars = ["OPENAI_API_KEY"] |
|
|
missing_vars = [var for var in required_env_vars if not os.getenv(var)] |
|
|
|
|
|
if missing_vars: |
|
|
error_msg = f"Missing required environment variables: {', '.join(missing_vars)}" |
|
|
logger.error(error_msg) |
|
|
raise ValueError(error_msg) |
|
|
|
|
|
logger.info("Startup validation passed") |
|
|
|
|
|
|
|
|
def chat_function_wrapper(message, history): |
|
|
"""Wrapper to run the async streaming function""" |
|
|
try: |
|
|
|
|
|
loop = asyncio.new_event_loop() |
|
|
asyncio.set_event_loop(loop) |
|
|
|
|
|
try: |
|
|
|
|
|
async_gen = chat_function_streaming(message, history) |
|
|
|
|
|
|
|
|
while True: |
|
|
try: |
|
|
result = loop.run_until_complete(async_gen.__anext__()) |
|
|
yield result |
|
|
except StopAsyncIteration: |
|
|
break |
|
|
|
|
|
finally: |
|
|
loop.close() |
|
|
|
|
|
except Exception as e: |
|
|
|
|
|
try: |
|
|
|
|
|
if processed_docs: |
|
|
|
|
|
str_processed_docs = "\n".join([ |
|
|
f"{doc.page_content}\n{doc.metadata}" |
|
|
for doc in processed_docs |
|
|
]) |
|
|
message_to_agent = f"{message}\n\nThis is Information you can use:\n\n{str_processed_docs}" |
|
|
|
|
|
response = asyncio.run(safe_run_agent(message_to_agent)) |
|
|
else: |
|
|
|
|
|
response = asyncio.run(safe_run_agent(message)) |
|
|
|
|
|
|
|
|
history.append([message, response]) |
|
|
yield history, "" |
|
|
except Exception as fallback_error: |
|
|
history.append([message, f"Error: {str(fallback_error)}"]) |
|
|
yield history, "" |
|
|
|
|
|
def create_interface(): |
|
|
"""Create and configure the Gradio interface""" |
|
|
|
|
|
|
|
|
custom_css = """ |
|
|
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); |
|
|
|
|
|
/* Global font */ |
|
|
* { |
|
|
font-family: 'Roboto', sans-serif; |
|
|
} |
|
|
|
|
|
/* Better RTL support for Arabic */ |
|
|
.rtl { |
|
|
direction: rtl; |
|
|
text-align: right; |
|
|
} |
|
|
|
|
|
/* Apply Roboto font only to English content explicitly if needed */ |
|
|
:lang(en) { |
|
|
font-family: 'Roboto', sans-serif; |
|
|
} |
|
|
|
|
|
/* Responsive design for small screens */ |
|
|
@media (max-width: 768px) { |
|
|
.gradio-row { |
|
|
flex-direction: column !important; |
|
|
} |
|
|
} |
|
|
|
|
|
/* 👇 Control font size in the input Textbox */ |
|
|
textarea { |
|
|
font-size: 18px !important; |
|
|
} |
|
|
|
|
|
/* 👇 Control font size in the Chatbot messages */ |
|
|
.message, .message-user, .message-ai { |
|
|
font-size: 18px !important; |
|
|
line-height: 1.6; |
|
|
} |
|
|
|
|
|
/* 👇 Optional: Adjust file upload input and other text areas */ |
|
|
input[type="file"], .gr-textbox, .gr-textbox textarea { |
|
|
font-size: 16px !important; |
|
|
} |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
with gr.Blocks( |
|
|
title="الشفاء الرقمية للرعاية الصحية - المساعد الطبي", |
|
|
css=custom_css, |
|
|
theme=gr.themes.Soft() |
|
|
) as interface: |
|
|
|
|
|
|
|
|
gr.Markdown( |
|
|
""" |
|
|
<div style="text-align: center; direction: rtl;"> |
|
|
|
|
|
# 🏥 الشفاء الرقمية للرعاية الصحية - المساعد الطبي |
|
|
|
|
|
### اطرح أسئلة طبية أو ارفع وثائق للحصول على مساعدة |
|
|
|
|
|
</div> |
|
|
""", |
|
|
elem_classes="rtl" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=3, min_width=400): |
|
|
chatbot = gr.Chatbot( |
|
|
label="💬 محادثة المساعد الطبي", |
|
|
height=600, |
|
|
show_label=True, |
|
|
container=True, |
|
|
bubble_full_width=False, |
|
|
rtl=True |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
msg = gr.Textbox( |
|
|
label="رسالتك", |
|
|
placeholder="اطرح سؤالاً طبياً باللغة العربية أو الإنجليزية...", |
|
|
scale=4, |
|
|
container=False, |
|
|
rtl=True |
|
|
) |
|
|
submit_btn = gr.Button("إرسال", variant="primary", scale=1) |
|
|
|
|
|
gr.Markdown( |
|
|
""" |
|
|
<div style="text-align: center; direction: rtl; color: #666; margin-top: 10px;"> |
|
|
<em> |
|
|
ملاحظة: هذا مشروع تجريبي شخصي لاغراض تعليمية فقط. |
|
|
<br> |
|
|
لإنشاء مشروع مماثل، يمكنكم التواصل مع المطور: |
|
|
<br> |
|
|
<a href="mailto:moazeldsoky8@gmail.com">Email</a> | <a href="https://github.com/MoazEldsouky">GitHub</a> | <a href="https://www.linkedin.com/in/moaz-eldesouky-762288251/">LinkedIn</a> |
|
|
<br> |
|
|
WhatsApp: +201096448317 |
|
|
<br> |
|
|
في حالات الطوارئ، اتصل بـ 997 فوراً. |
|
|
تنويه: تم استخدام OpenAI API مجاني من GitHub، وهو محدود بـ 50 Request يوميًا. |
|
|
</em> |
|
|
</div> |
|
|
""", |
|
|
elem_classes="rtl" |
|
|
) |
|
|
|
|
|
with gr.Column(scale=2, min_width=300): |
|
|
|
|
|
gr.Markdown("### 📁 رفع الوثائق", elem_classes="rtl") |
|
|
file_upload = gr.File( |
|
|
label="ارفع وثيقة طبية", |
|
|
file_types=[".pdf", ".txt", ".docx", ".doc"], |
|
|
type="filepath" |
|
|
) |
|
|
upload_status = gr.Textbox( |
|
|
label="حالة الرفع", |
|
|
interactive=False, |
|
|
max_lines=3, |
|
|
rtl=True |
|
|
) |
|
|
|
|
|
|
|
|
gr.Markdown("### 🧠 إدارة الذاكرة", elem_classes="rtl") |
|
|
clear_btn = gr.Button( |
|
|
"🗑️ مسح الذاكرة وبدء محادثة جديدة", |
|
|
variant="secondary", |
|
|
size="lg" |
|
|
) |
|
|
clear_status = gr.Textbox( |
|
|
label="حالة المسح", |
|
|
interactive=False, |
|
|
rtl=True |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Accordion("ℹ️ حول مساعد الشفاء الرقمي", open=False): |
|
|
gr.Markdown( |
|
|
""" |
|
|
<div style="direction: rtl; text-align: right;"> |
|
|
|
|
|
**الميزات:** |
|
|
- معلومات وإرشادات طبية |
|
|
- مساعدة في حجز المواعيد |
|
|
- دعم تحليل الوثائق |
|
|
- دعم ثنائي اللغة (العربية/الإنجليزية) |
|
|
- متاح 24/7 لخدمتكم |
|
|
|
|
|
**مهم:** |
|
|
- هذا ليس بديلاً عن المشورة الطبية المهنية |
|
|
- في حالات الطوارئ، اتصل دائماً بـ 997 |
|
|
- الاستجابات مولدة بالذكاء الاصطناعي ويجب التحقق منها مع المختصين الطبيين |
|
|
|
|
|
**معلومات الاتصال:** |
|
|
- الطوارئ: 997 |
|
|
- خدمة العملاء: متوفرة على مدار الساعة |
|
|
- الموقع الإلكتروني: www.alshifadigital.com |
|
|
- الهاتف: 9200-000-000 (متوفر خلال ساعات العمل الرسمية) |
|
|
|
|
|
</div> |
|
|
""", |
|
|
elem_classes="rtl" |
|
|
) |
|
|
|
|
|
|
|
|
def submit_message(message, history): |
|
|
"""Handle message submission""" |
|
|
if message.strip(): |
|
|
yield from chat_function_wrapper(message, history) |
|
|
|
|
|
|
|
|
msg.submit( |
|
|
submit_message, |
|
|
inputs=[msg, chatbot], |
|
|
outputs=[chatbot, msg] |
|
|
) |
|
|
|
|
|
submit_btn.click( |
|
|
submit_message, |
|
|
inputs=[msg, chatbot], |
|
|
outputs=[chatbot, msg] |
|
|
) |
|
|
|
|
|
|
|
|
file_upload.upload( |
|
|
upload_and_process_file, |
|
|
inputs=file_upload, |
|
|
outputs=upload_status |
|
|
) |
|
|
|
|
|
|
|
|
clear_btn.click( |
|
|
clear_chat_memory_and_history, |
|
|
inputs=[], |
|
|
outputs=[chatbot, clear_status, upload_status] |
|
|
) |
|
|
|
|
|
return interface |
|
|
|
|
|
def launch_gradio(): |
|
|
"""Launch Gradio with startup validation""" |
|
|
try: |
|
|
validate_startup() |
|
|
logger.info("Starting Gradio interface...") |
|
|
|
|
|
interface = create_interface() |
|
|
|
|
|
interface.launch( |
|
|
server_name="0.0.0.0", |
|
|
server_port=int(os.getenv("PORT", 7860)), |
|
|
share=bool(os.getenv("GRADIO_SHARE", False)), |
|
|
debug=bool(os.getenv("DEBUG", False)), |
|
|
show_error=True, |
|
|
quiet=False, |
|
|
inbrowser=True, |
|
|
favicon_path=None, |
|
|
ssl_verify=False, |
|
|
app_kwargs={} |
|
|
) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"Failed to launch Gradio: {e}") |
|
|
raise |
|
|
|
|
|
if __name__ == "__main__": |
|
|
launch_gradio() |