Spaces:
Running
Running
| """ | |
| LifeFlow AI - Main Application (Fixed Output Mismatch) | |
| โ ไฟฎๅพฉ Step 4 ๅๅณๅผๆธ้ไธๅน้ ๅฐ่ด็ AttributeError | |
| โ ็ขบไฟ outputs ๆธ ๅฎๅ ๅซๆๆ 7 ๅๅ ไปถ | |
| """ | |
| import gradio as gr | |
| import inspect | |
| from datetime import datetime | |
| from ui.theme import get_enhanced_css | |
| from ui.components.header import create_header | |
| from ui.components.progress_stepper import create_progress_stepper, update_stepper | |
| from ui.components.input_form import create_input_form, toggle_location_inputs | |
| from ui.components.modals import create_settings_modal, create_doc_modal | |
| from ui.renderers import ( | |
| create_agent_dashboard, | |
| create_summary_card, | |
| create_task_card | |
| ) | |
| from core.session import UserSession | |
| from services.planner_service import PlannerService | |
| from core.visualizers import create_animated_map | |
| class LifeFlowAI: | |
| def __init__(self): | |
| self.service = PlannerService() | |
| def _get_dashboard_html(self, active_agent: str = None, status: str = "idle", message: str = "Waiting") -> str: | |
| agents = ['team', 'scout', 'optimizer', 'navigator', 'weatherman', 'presenter'] | |
| status_dict = {a: {'status': 'idle', 'message': 'Standby'} for a in agents} | |
| if active_agent in status_dict: | |
| status_dict[active_agent] = {'status': status, 'message': message} | |
| if active_agent != 'team' and status == 'working': | |
| status_dict['team'] = {'status': 'working', 'message': f'Monitoring {active_agent}...'} | |
| return create_agent_dashboard(status_dict) | |
| def _check_api_status(self, session_data): | |
| session = UserSession.from_dict(session_data) | |
| has_gemini = bool(session.custom_settings.get("gemini_api_key")) | |
| has_google = bool(session.custom_settings.get("google_maps_api_key")) | |
| msg = "โ System Ready" if (has_gemini and has_google) else "โ ๏ธ Missing API Keys" | |
| return msg | |
| def _get_gradio_chat_history(self, session): | |
| history = [] | |
| for msg in session.chat_history: | |
| history.append({"role": msg["role"], "content": msg["message"]}) | |
| return history | |
| # ================= Event Wrappers ================= | |
| def analyze_wrapper(self, user_input, auto_loc, lat, lon, session_data): | |
| session = UserSession.from_dict(session_data) | |
| iterator = self.service.run_step1_analysis(user_input, auto_loc, lat, lon, session) | |
| for event in iterator: | |
| evt_type = event.get("type") | |
| current_session = event.get("session", session) | |
| if evt_type == "error": | |
| yield ( | |
| gr.update(), # step1 | |
| gr.update(), # step2 | |
| gr.update(), # step3 | |
| gr.HTML(f"<div style='color:red'>{event.get('message')}</div>"), # s1_stream | |
| gr.update(), # task_list | |
| gr.update(), # task_summary | |
| gr.update(), # chatbot | |
| current_session.to_dict() # state | |
| ) | |
| return | |
| if evt_type == "stream": | |
| yield ( | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| event.get("stream_text", ""), | |
| gr.update(), | |
| gr.update(), | |
| gr.update(), | |
| current_session.to_dict() | |
| ) | |
| elif evt_type == "complete": | |
| tasks_html = self.service.generate_task_list_html(current_session) | |
| date_str = datetime.now().strftime("%Y-%m-%d") | |
| loc_str = "Selected Location" | |
| if current_session.lat and current_session.lng: | |
| loc_str = f"{current_session.lat:.2f}, {current_session.lng:.2f}" | |
| summary_html = create_summary_card(len(current_session.task_list), event.get("high_priority", 0), event.get("total_time", 0), location=loc_str, date=date_str) | |
| chat_history = self._get_gradio_chat_history(current_session) | |
| if not chat_history: | |
| chat_history = [ | |
| {"role": "assistant", "content": event.get('stream_text', "")}, | |
| {"role": "assistant", "content": "Hi! I'm LifeFlow. Tell me if you want to change priorities, add stops, or adjust times."}] | |
| current_session.chat_history.append({"role": "assistant", "message": "Hi! I'm LifeFlow...", "time": ""}) | |
| yield ( | |
| gr.update(visible=False), # Hide S1 | |
| gr.update(visible=True), # Show S2 | |
| gr.update(visible=False), # Hide S3 | |
| "", # Clear Stream | |
| gr.HTML(tasks_html), | |
| gr.HTML(summary_html), | |
| chat_history, | |
| current_session.to_dict() | |
| ) | |
| def chat_wrapper(self, msg, session_data): | |
| session = UserSession.from_dict(session_data) | |
| iterator = self.service.modify_task_chat(msg, session) | |
| for event in iterator: | |
| sess = event.get("session", session) | |
| tasks_html = self.service.generate_task_list_html(sess) | |
| summary_html = event.get("summary_html", gr.HTML()) | |
| gradio_history = self._get_gradio_chat_history(sess) | |
| yield (gradio_history, tasks_html, summary_html, sess.to_dict()) | |
| def step3_wrapper(self, session_data): | |
| session = UserSession.from_dict(session_data) | |
| # Init variables | |
| log_content = "" | |
| report_content = "" | |
| tasks_html = self.service.generate_task_list_html(session) | |
| init_dashboard = self._get_dashboard_html('team', 'working', 'Initializing...') | |
| # HTML Content (No Indentation) | |
| loading_html = inspect.cleandoc(""" | |
| <div style="text-align: center; padding: 60px 20px; color: #64748b;"> | |
| <div style="font-size: 48px; margin-bottom: 20px; animation: pulse 1.5s infinite;">๐ง </div> | |
| <div style="font-size: 1.2rem; font-weight: 600; color: #334155;">AI Team is analyzing your request...</div> | |
| <div style="font-size: 0.9rem; margin-top: 8px;">Checking routes, weather, and optimizing schedule.</div> | |
| </div> | |
| <style>@keyframes pulse { 0% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.1); opacity: 0.7; } 100% { transform: scale(1); opacity: 1; } }</style> | |
| """) | |
| init_log = '<div style="padding: 10px; color: #94a3b8; font-style: italic;">Waiting for agents...</div>' | |
| yield (init_dashboard, init_log, loading_html, tasks_html, session.to_dict()) | |
| iterator = self.service.run_step3_team(session) | |
| for event in iterator: | |
| sess = event.get("session", session) | |
| evt_type = event.get("type") | |
| if evt_type == "report_stream": | |
| report_content = event.get("content", "") | |
| if evt_type == "reasoning_update": | |
| agent, status, msg = event.get("agent_status") | |
| time_str = datetime.now().strftime('%H:%M:%S') | |
| log_entry = f""" | |
| <div style="margin-bottom: 8px; border-left: 3px solid #6366f1; padding-left: 10px;"> | |
| <div style="font-size: 0.75rem; color: #94a3b8;">{time_str} โข {agent.upper()}</div> | |
| <div style="color: #334155; font-size: 0.9rem;">{msg}</div> | |
| </div> | |
| """ | |
| log_content = log_entry + log_content | |
| dashboard_html = self._get_dashboard_html(agent, status, msg) | |
| log_html = f'<div style="height: 500px; overflow-y: auto; padding: 10px; background: #fff;">{log_content}</div>' | |
| current_report = report_content + "\n\n" if report_content else loading_html | |
| yield (dashboard_html, log_html, current_report, tasks_html, sess.to_dict()) | |
| if evt_type == "complete": | |
| final_report = event.get("report_html", report_content) | |
| final_log = f""" | |
| <div style="margin-bottom: 8px; border-left: 3px solid #10b981; padding-left: 10px; background: #ecfdf5; padding: 10px;"> | |
| <div style="font-weight: bold; color: #059669;">โ Planning Completed</div> | |
| </div> | |
| {log_content} | |
| """ | |
| final_log_html = f'<div style="height: 500px; overflow-y: auto; padding: 10px; background: #fff;">{final_log}</div>' | |
| yield ( | |
| self._get_dashboard_html('team', 'complete', 'Planning Finished'), | |
| final_log_html, | |
| final_report, | |
| tasks_html, | |
| sess.to_dict() | |
| ) | |
| def step4_wrapper(self, session_data): | |
| session = UserSession.from_dict(session_data) | |
| result = self.service.run_step4_finalize(session) | |
| # ๐ฅ Wrapper ๅๅณ 7 ๅๅผ | |
| if result['type'] == 'success': | |
| return ( | |
| gr.update(visible=False), # 1. Step 3 Hide | |
| gr.update(visible=True), # 2. Step 4 Show | |
| result['summary_tab_html'], # 3. Summary Tab HTML | |
| result['report_md'], # 4. Report Tab Markdown | |
| result['task_list_html'], # 5. Task List HTML | |
| result['map_fig'], # 6. Map Plot | |
| session.to_dict() # 7. Session State | |
| ) | |
| else: | |
| return ( | |
| gr.update(visible=True), gr.update(visible=False), | |
| "", "", "", None, | |
| session.to_dict() | |
| ) | |
| # ================= Main UI Builder ================= | |
| def build_interface(self): | |
| with gr.Blocks(title="LifeFlow AI", css=".gradio-container {max-width: 100% !important; padding: 0;}") as demo: | |
| gr.HTML(get_enhanced_css()) | |
| session_state = gr.State(UserSession().to_dict()) | |
| with gr.Column(elem_classes="step-container"): | |
| home_btn, theme_btn, settings_btn, doc_btn = create_header() | |
| stepper = create_progress_stepper(1) | |
| status_bar = gr.Markdown("Ready", visible=False) | |
| # STEP 1 | |
| with gr.Group(visible=True, elem_classes="step-container centered-input-container") as step1_container: | |
| (input_area, s1_stream_output, user_input, auto_loc, loc_group, lat_in, lon_in, analyze_btn) = create_input_form("") | |
| # STEP 2 | |
| with gr.Group(visible=False, elem_classes="step-container") as step2_container: | |
| gr.Markdown("### โ Review & Refine Tasks") | |
| with gr.Row(elem_classes="step2-split-view"): | |
| with gr.Column(scale=1): | |
| with gr.Group(elem_classes="panel-container"): | |
| gr.HTML('<div class="panel-header">๐ Your Tasks</div>') | |
| with gr.Group(elem_classes="scrollable-content"): | |
| task_summary_box = gr.HTML() | |
| task_list_box = gr.HTML() | |
| with gr.Column(scale=1): | |
| with gr.Group(elem_classes="panel-container chat-panel-native"): | |
| chatbot = gr.Chatbot(label="AI Assistant", type="messages", height=540, elem_classes="native-chatbot", bubble_full_width=False) | |
| with gr.Row(elem_classes="chat-input-row"): | |
| chat_input = gr.Textbox(show_label=False, placeholder="Type to modify tasks...", container=False, scale=5, autofocus=True) | |
| chat_send = gr.Button("โค", variant="primary", scale=1, min_width=50) | |
| with gr.Row(elem_classes="action-footer"): | |
| back_btn = gr.Button("โ Back", variant="secondary", scale=1) | |
| plan_btn = gr.Button("๐ Start Planning", variant="primary", scale=2) | |
| # STEP 3 | |
| with gr.Group(visible=False, elem_classes="step-container") as step3_container: | |
| gr.Markdown("### ๐ค AI Team Operations") | |
| with gr.Group(elem_classes="agent-dashboard-container"): | |
| agent_dashboard = gr.HTML(value=self._get_dashboard_html()) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| with gr.Tabs(): | |
| with gr.Tab("๐ Full Report"): | |
| with gr.Group(elem_classes="live-report-wrapper"): | |
| live_report_md = gr.Markdown() | |
| with gr.Tab("๐ Task List"): | |
| with gr.Group(elem_classes="panel-container"): | |
| with gr.Group(elem_classes="scrollable-content"): | |
| task_list_s3 = gr.HTML() | |
| with gr.Column(scale=1): | |
| gr.Markdown("### โก Activity Log") | |
| with gr.Group(elem_classes="panel-container"): | |
| planning_log = gr.HTML(value="Waiting...") | |
| # STEP 4 | |
| with gr.Group(visible=False, elem_classes="step-container") as step4_container: | |
| with gr.Row(): | |
| # Left: Tabs (Summary / Report / Tasks) | |
| with gr.Column(scale=1, elem_classes="split-left-panel"): | |
| with gr.Tabs(): | |
| with gr.Tab("๐ Summary"): | |
| summary_tab_output = gr.HTML() | |
| with gr.Tab("๐ Full Report"): | |
| report_tab_output = gr.Markdown() | |
| with gr.Tab("๐ Task List"): | |
| task_list_tab_output = gr.HTML() | |
| # Right: Hero Map | |
| with gr.Column(scale=2, elem_classes="split-right-panel"): | |
| map_view = gr.Plot(label="Route Map", show_label=False) | |
| # Modals & Events | |
| (settings_modal, g_key, w_key, gem_key, model_sel, close_set, save_set, set_stat) = create_settings_modal() | |
| doc_modal, close_doc_btn = create_doc_modal() | |
| settings_btn.click(fn=lambda: gr.update(visible=True), outputs=[settings_modal]) | |
| close_set.click(fn=lambda: gr.update(visible=False), outputs=[settings_modal]) | |
| doc_btn.click(fn=lambda: gr.update(visible=True), outputs=[doc_modal]) | |
| close_doc_btn.click(fn=lambda: gr.update(visible=False), outputs=[doc_modal]) | |
| theme_btn.click(fn=None, js="() => { document.querySelector('.gradio-container').classList.toggle('theme-dark'); }") | |
| analyze_event = analyze_btn.click( | |
| fn=self.analyze_wrapper, | |
| inputs=[user_input, auto_loc, lat_in, lon_in, session_state], | |
| outputs=[step1_container, step2_container, step3_container, s1_stream_output, task_list_box, task_summary_box, chatbot, session_state] | |
| ).then(fn=lambda: update_stepper(2), outputs=[stepper]) | |
| chat_send.click(fn=self.chat_wrapper, inputs=[chat_input, session_state], outputs=[chatbot, task_list_box, task_summary_box, session_state]).then(fn=lambda: "", outputs=[chat_input]) | |
| chat_input.submit(fn=self.chat_wrapper, inputs=[chat_input, session_state], outputs=[chatbot, task_list_box, task_summary_box, session_state]).then(fn=lambda: "", outputs=[chat_input]) | |
| step3_start = plan_btn.click( | |
| fn=lambda: (gr.update(visible=False), gr.update(visible=True), update_stepper(3)), | |
| outputs=[step2_container, step3_container, stepper] | |
| ).then( | |
| fn=self.step3_wrapper, | |
| inputs=[session_state], | |
| outputs=[agent_dashboard, planning_log, live_report_md, task_list_s3, session_state], | |
| show_progress="hidden" | |
| ) | |
| step3_start.then( | |
| fn=self.step4_wrapper, | |
| inputs=[session_state], | |
| # ๐ฅ๐ฅ๐ฅ ้้ตไฟฎๆญฃ๏ผ้่ฃกๅๅบไบ 7 ๅ Outputs๏ผๅฟ ้ ๅฐๆ Wrapper ๅๅณ็ 7 ๅๅผ | |
| outputs=[ | |
| step3_container, # 1. Hide Step 3 | |
| step4_container, # 2. Show Step 4 | |
| summary_tab_output, # 3. Summary Tab | |
| report_tab_output, # 4. Report Tab | |
| task_list_tab_output, # 5. Task List Tab | |
| map_view, # 6. Map | |
| session_state # 7. State | |
| ] | |
| ).then(fn=lambda: update_stepper(4), outputs=[stepper]) | |
| def reset_all(session_data): | |
| old_session = UserSession.from_dict(session_data) | |
| new_session = UserSession() | |
| new_session.custom_settings = old_session.custom_settings | |
| return (gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), update_stepper(1), new_session.to_dict(), "") | |
| home_btn.click(fn=reset_all, inputs=[session_state], outputs=[step1_container, step2_container, step3_container, step4_container, stepper, session_state, user_input]) | |
| back_btn.click(fn=reset_all, inputs=[session_state], outputs=[step1_container, step2_container, step3_container, step4_container, stepper, session_state, user_input]) | |
| save_set.click(fn=self.save_settings, inputs=[g_key, w_key, gem_key, model_sel, session_state], outputs=[set_stat, session_state, status_bar]) | |
| auto_loc.change(fn=toggle_location_inputs, inputs=auto_loc, outputs=loc_group) | |
| demo.load(fn=self._check_api_status, inputs=[session_state], outputs=[status_bar]) | |
| return demo | |
| def save_settings(self, g, w, gem, m, s_data): | |
| sess = UserSession.from_dict(s_data) | |
| sess.custom_settings.update({'google_maps_api_key': g, 'openweather_api_key': w, 'gemini_api_key': gem, 'model': m}) | |
| return "โ Saved", sess.to_dict(), "Settings Updated" | |
| def main(): | |
| app = LifeFlowAI() | |
| demo = app.build_interface() | |
| demo.launch(server_name="0.0.0.0", server_port=8080, share=True, show_error=True) | |
| if __name__ == "__main__": | |
| main() |