import modal import gradio as gr import asyncio import os import logging from agents.modal_orchestrator import ModalMovieSearchOrchestrator # Настройка логирования logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Инициализация Modal App app = modal.App("movie-plot-search") # Глобальная инициализация оркестратора orchestrator = ModalMovieSearchOrchestrator() async def chat_interface(message: str, history: list) -> tuple: """ Основной интерфейс чата с Modal агентами """ try: logger.info(f"Processing user message: {message[:50]}...") # Вызов оркестратора result = await orchestrator.process_user_input(message) logger.info(f"RESULT: {result}") status = result.get("status") response_parts = [] # Логика формирования ответа if status == "insufficient_length": response_parts += [ "**❗ Editor Feedback:**", result.get("message", ""), "\n---\nPlot description is too short (min 50 words). Please expand and try again." ] elif status == "search_completed": response_parts.append("**✅ Search completed!**") if (result.get("improved_plot") and result.get("improved_plot") != result.get("original_plot")): response_parts.append(f"**📝 Improved plot:** {result.get('improved_plot')}") if result.get("movie_overview"): response_parts.append(f"\n**🎬 Generated movie overview:**\n{result.get('movie_overview')}") response_parts.append("\n" + "=" * 60) response_parts.append("**🎯 EXPERT SYSTEM RECOMMENDATIONS**") response_parts.append("=" * 60) recommendations = result.get("recommendations", "") if isinstance(recommendations, dict): recommendations = recommendations.get("explanations", str(recommendations)) if isinstance(recommendations, str) and recommendations: response_parts.append(recommendations) else: response_parts.append("No recommendations were generated.") # Метрики response_parts.append("\n" + "=" * 60) response_parts.append("**📊 PERFORMANCE METRICS**") response_parts.append("=" * 60) metrics = result.get("performance_metrics", {}) if metrics: response_parts.append(f"🚀 **GPU Used:** {'✅ Yes' if metrics.get('using_gpu', False) else '❌ No'}") response_parts.append(f"⚡ **Search Time:** {metrics.get('search_time', 0):.3f}s") response_parts.append(f"🎬 **Movies Analyzed:** {result.get('total_analyzed', 0)}") response_parts.append("\n" + "=" * 60) response_parts.append("**🔄 Ready for the next search!**") elif status == "suggestion": response_parts.append("**💡 AI Plot Suggestion**") response_parts.append(result.get("message", "")) elif status == "awaiting_custom_plot": response_parts.append("**📝 Custom Plot Mode Activated**") response_parts.append(result.get("message", "")) elif status == "custom_plot_too_short": response_parts.append(result.get("message", "Your plot is too short.")) elif status == "custom_plot_rejected": response_parts.append(result.get("message", "Your plot doesn't meet requirements.")) elif status == "end_session": response_parts.append(result.get("message", "Thank you for using Movie Plot Search!")) elif status == "error": response_parts.append("**❌ System Error occurred:**") response_parts.append(result.get("message", "Unknown error")) else: response_parts.append(f"⚠️ Unhandled status: {status}") # Сборка ответа assistant_reply = "\n".join(response_parts) new_history = history + [ {"role": "user", "content": message}, {"role": "assistant", "content": assistant_reply} ] return new_history, "" except Exception as e: logger.error(f"Error in chat interface: {e}") new_history = history + [ {"role": "user", "content": message}, {"role": "assistant", "content": f"**❌ Unexpected error:** {e}"} ] return new_history, "" def reset_chat(): logger.info("Resetting chat session") orchestrator.reset_conversation() return [], "" def get_session_info(): try: summary = orchestrator.get_conversation_summary() return f"""**Hybrid Session Info:** - ID: {summary.get('session_id', 'N/A')} - Step: {summary.get('current_step', 'N/A')} - Has Plot: {'✅' if summary.get('has_plot', False) else '❌'} - Has Recommendations: {'✅' if summary.get('has_recommendations', False) else '❌'} - Total Results: {summary.get('total_search_results', 0)} """ except Exception as e: return f"Error getting session info: {e}" def force_refresh_session_info(): return get_session_info() # --- 3. Создание интерфейса (Глобально!) --- with gr.Blocks(title="🎬 Movie Plot Search") as demo: gr.Markdown(""" # 🎬 Movie Plot Search Engine **🏗️ Architecture:** 🖥️ **UI**: Hugging Face Spaces (Gradio); ⚡ **Agents**: Running on Modal Cloud (Serverless GPU); 🤖 **LLM**: Nebius AI Studio API (Llama-3.3-70B-Instruct). ****How it works:**** *Describe a story, dream, or vibe (50+ words) in English. 5 AI Agents will collaborate to find 3 semantic matches.* """) with gr.Row(): with gr.Column(scale=4): # Простой конструктор для максимальной совместимости chatbot = gr.Chatbot( value=[], height=600, label="🎬 Conversation with AI Agents", # type='messages' ) msg = gr.Textbox( placeholder="Describe a movie plot (50-100 words in English)...", label="Your message", lines=3, max_lines=5 ) with gr.Row(): submit_btn = gr.Button("🚀 Submit", variant="primary", scale=2) clear_btn = gr.Button("🔄 Clear Chat", scale=1) with gr.Column(scale=1): gr.Markdown("### 🔍 How to use:\n1. Describe the plot (50+ words)\n2. Wait for agents to analyze\n3. Get expert recommendations") session_info = gr.Textbox( label="Session Info", value=get_session_info(), interactive=False, lines=5 ) refresh_btn = gr.Button("🔄 Refresh Info", size="sm") # Обработчики submit_btn.click(chat_interface, [msg, chatbot], [chatbot, msg]).then(get_session_info, None, session_info) msg.submit(chat_interface, [msg, chatbot], [chatbot, msg]).then(get_session_info, None, session_info) clear_btn.click(reset_chat, None, [chatbot, msg]) refresh_btn.click(force_refresh_session_info, None, session_info) # --- 4. Запуск (С ПРАВИЛЬНЫМИ ПАРАМЕТРАМИ) --- if __name__ == "__main__": # ВАЖНО: 0.0.0.0 делает приложение видимым для Hugging Face # demo.launch(server_name="0.0.0.0", server_port=7860) demo.launch()