import gradio as gr from .components import ( create_header, create_chatbot, create_input_row, create_clear_button, create_top_header, create_login_form, ) from .handlers import ( setup_handlers, handle_login, handle_register, handle_logout, handle_load_history ) from utils.helpers import convert_chatbot_messages_to_list from auth.db import init_db import modelscope_studio.components.antdx as antdx import modelscope_studio.components.base as ms import modelscope_studio.components.antd as antd init_db() theme = gr.themes.Soft() def create_auth(session, chat_column, chatbot): with gr.Column() as auth_block: with gr.Row(): with gr.Column(scale=8): create_header() with gr.Column(scale=2): top_row, welcome_text, login_btn, logout_btn = create_top_header() login_form, username, password, login_submit, register_btn, back_btn, status = create_login_form() # Sự kiện: bấm nút đăng nhập → hiện form, ẩn khu vực chat login_btn.click( lambda: ( gr.update(visible=False), # Ẩn nút đăng nhập gr.update(visible=True), # Hiện form đăng nhập gr.update(visible=False), # Ẩn khu vực gr.update(value="", visible=False) # Ẩn message ), None, [login_btn, login_form, chat_column, welcome_text] ) # Sự kiện: bấm nút đăng xuất → xóa session, ẩn lời chào def logout_handler(): msg = handle_logout(session) return ( msg, gr.update(visible=True), # Hiện nút đăng nhập gr.update(visible=False), # Ẩn nút đăng xuất gr.update(value="", visible=True), # Hiện message gr.update(visible=True), # Hiện khu vực chat gr.update(value=[]) # Reset chatbot ) logout_btn.click( logout_handler, None, [status, login_btn, logout_btn, welcome_text, chat_column, chatbot] ) # Sự kiện: bấm nút trở lại → ẩn lời chào def return_handler(): return ( "", gr.update(visible=True), # Hiện nút đăng nhập gr.update(visible=False), # Ẩn login form gr.update(visible=True), # Hiện khu vực chat gr.update(value="", visible=True) # Hiện message ) back_btn.click( return_handler, None, [status, login_btn, login_form, chat_column, welcome_text] ) # Sự kiện: xác thực đăng nhập def login_submit_handler(u, p): success, msg, username = handle_login(u, p, session) if success: welcome = f"👋 Xin chào, {username.title()}!" history = handle_load_history(username) return ( msg, gr.update(visible=True), # Hiện chat_column gr.update(visible=False), # Ẩn form đăng nhập gr.update(visible=True), # Hiện nút đăng xuất gr.update(visible=False), # Ẩn nút đăng nhập gr.update(value=welcome, visible=True), # Hiện lời chào gr.update(value=history) ) else: return ( msg, gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(value="", visible=True), gr.update(value=[]) ) login_submit.click( login_submit_handler, inputs=[username, password], outputs=[status, chat_column, login_form, logout_btn, login_btn, welcome_text, chatbot] ) # Sự kiện: đăng ký tài khoản def register_handler(u, p): success, msg = handle_register(u, p, session) return msg register_btn.click( register_handler, inputs=[username, password], outputs=[status] ) return { "auth_block": auth_block, "top_row": top_row, "login_form": login_form, "welcome_text": welcome_text, "login_btn": login_btn, "logout_btn": logout_btn, "username": username, "password": password, "login_submit": login_submit, "register_btn": register_btn, "status": status } def create_chat_column(session): """Tạo giao diện chatbot""" with gr.Column(visible=True) as chat_column: with gr.Column(elem_classes=["tab-container"]): username = session.value.get("user") # Chatbot with clear badge overlay with gr.Group(elem_classes=["chatbot-wrapper"]): chatbot = create_chatbot() clear_btn = create_clear_button() # File display (1 line above input) file_display = gr.Markdown( "", elem_classes=["file-display-compact"], visible=False) # Input row with textbox and submit button row, message, submit_btn = create_input_row() # Audio input component for speech-to-text audio_input = gr.Audio( sources=["microphone"], type="filepath", label="🎤 Nhập bằng giọng nói", waveform_options=gr.WaveformOptions( show_recording_waveform=False, ), ) clear_btn = create_clear_button() setup_handlers(message, chatbot, submit_btn, clear_btn, session, audio_input=audio_input) return chat_column, chatbot def build_layout(): """Build the Gradio UI layout for the healthcare AI assistant with modelscope_studio components.""" with gr.Blocks( theme=theme, css=""" .tab-title { font-size: 1.2rem; font-weight: 600; } .tab-subtitle { font-size: 0.9rem; color: #555; margin-top: 0.3rem; } .tab-container { border-radius: 10px; } .general-tab { background-color: #e6f7ff; border: 1px solid #91caff; } .nutrition-tab { background-color: #f6ffed; border: 1px solid #b7eb8f; } .exercise-tab { background-color: #fff7e6; border: 1px solid #ffd591; } .mental-tab { background-color: #f9f0ff; border: 1px solid #d3adf7; } .featured-tip { background-color: rgba(255, 255, 255, 0.7); padding: 10px; border-radius: 8px; margin-bottom: 15px; border-left: 4px solid #4b8bf4; } .tab-selected { border-bottom: 3px solid #1890ff !important; font-weight: bold; } .tab-header { margin-bottom: 12px; } .tab-container button::after { bottom: -20px; } .action-buttons { display: flex; gap: 10px; } .build-layout-container { display: flex; flex-direction: column; } .auth-block { order: 1; } .chat-block { order: 2; } .login-form-container { max-width: 70%; margin: auto; } .login-input label span { background-color: unset } .login-buttons { display: flex; justify-content: space-between; margin-top: 15px; } .login-btn, .register-btn { width: 48%; padding: 10px; font-weight: bold; border-radius: 8px; } .login-status textarea { margin-top: 15px; background-color: #fffbe6; border-left: 4px solid #faad14; border-radius: 8px; padding: 10px; font-size: 0.95rem; color: #333; } .file-display-compact { font-size: 0.85rem; color: #666; padding: 4px 8px; background-color: #f5f5f5; border-radius: 4px; margin: 0 0 8px 0; } .input-container { position: relative; } .message-input textarea { padding-right: 45px !important; } .small-button { max-width: 200px; } /* Speech input styling */ #speech-input-panel { background-color: #f0f7ff; border: 2px solid #1890ff; border-radius: 8px; padding: 15px; margin-top: 10px; } .speech-audio-input { border: 2px dashed #1890ff; border-radius: 8px; padding: 10px; } .speech-status { background-color: #e6f7ff; border-left: 4px solid #1890ff; } .speech-transcription { background-color: #fafafa; border: 1px solid #d9d9d9; border-radius: 4px; } /* Chatbot wrapper with badge overlay */ .chatbot-wrapper { position: relative !important; background: transparent !important; border: none !important; padding: 0 !important; } .clear-badge { position: absolute !important; top: 8px !important; right: 8px !important; z-index: 10 !important; font-size: 0.75rem !important; padding: 4px 8px !important; min-width: auto !important; height: auto !important; background-color: rgba(239, 68, 68, 0.9) !important; border: none !important; border-radius: 4px !important; color: white !important; cursor: pointer !important; } .clear-badge:hover { background-color: rgba(220, 38, 38, 1) !important; } /* Input block with inline button */ .input-block { background-color: #27272A; border-radius: 8px; padding: 12px; } .input-block > div { background: transparent !important; } .input-label { font-size: 0.75rem !important; color: #a1a1aa !important; margin-bottom: 5px !important; background-color: #27272A !important; } .input-label p { margin: 0 !important; font-size: 0.75rem !important; } .input-row { gap: 0 !important; align-items: center !important;; } .input-row button { min-width: 60px !important; height: 40px !important; margin: 0 !important; border-radius: 0 6px 6px 0 !important; font-size: 1rem; } .message-input { height: 40px !important; background: transparent !important; } .message-input textarea { height: 40px !important; min-height: 40px !important; max-height: 40px !important; border-radius: 6px 0 0 6px !important; box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3) !important; background-color: #1a1a1c !important; border: none !important; color: #ffffff !important; padding: 10px 12px !important; } .message-input textarea::placeholder { color: #a1a1aa !important; } /* Hide Gradio processing time display */ .progress-text { display: none !important; } footer { display: none !important; } """ ) as demo, ms.Application(), antdx.XProvider(): session = gr.State({"user": None, "history": []}) with antd.Flex(vertical=True, gap="middle"): chat_column, chatbot = create_chat_column(session) chat_column.elem_classes = ["chat-block"] auth_ui = create_auth(session, chat_column, chatbot) auth_ui["auth_block"].elem_classes = ["auth-block"] return demo