File size: 17,179 Bytes
66dbebd
 
 
ae20ff2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66dbebd
c5e8f57
 
 
ae20ff2
c5e8f57
 
 
 
ae20ff2
c5e8f57
66dbebd
c5e8f57
 
 
 
 
66dbebd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7c65f3f
66dbebd
c5e8f57
66dbebd
 
 
 
 
 
 
 
 
 
 
c5e8f57
66dbebd
 
 
 
 
 
 
c5e8f57
66dbebd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f96d28a
 
 
 
 
 
 
 
 
c5e8f57
 
66dbebd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ae20ff2
7c65f3f
ae20ff2
 
7c65f3f
ae20ff2
 
7c65f3f
ae20ff2
 
 
7c65f3f
ae20ff2
7c65f3f
 
 
 
 
 
 
 
 
 
ae20ff2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7c65f3f
ae20ff2
7c65f3f
ae20ff2
7c65f3f
 
 
 
ae20ff2
7c65f3f
 
 
 
c5e8f57
ae20ff2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c5e8f57
 
 
 
 
 
 
 
 
 
ae20ff2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66dbebd
ae20ff2
 
 
 
c5e8f57
66dbebd
ae20ff2
 
 
c5e8f57
ae20ff2
 
 
c5e8f57
 
 
 
 
66dbebd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# app.py - Mobile-First Implementation
import gradio as gr
import uuid
import logging
import traceback
from typing import Optional, Tuple, List, Dict, Any
import os

# Configure comprehensive logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(),
        logging.FileHandler('app.log')
    ]
)
logger = logging.getLogger(__name__)

# Try to import orchestration components
orchestrator = None
orchestrator_available = False

try:
    logger.info("Attempting to import orchestration components...")
    import sys
    sys.path.insert(0, '.')
    sys.path.insert(0, 'src')
    
    from src.agents.intent_agent import create_intent_agent
    from src.agents.synthesis_agent import create_synthesis_agent
    from src.agents.safety_agent import create_safety_agent
    from llm_router import LLMRouter
    from orchestrator_engine import MVPOrchestrator
    from context_manager import EfficientContextManager
    from config import settings
    
    logger.info("βœ“ Successfully imported orchestration components")
    orchestrator_available = True
except ImportError as e:
    logger.warning(f"Could not import orchestration components: {e}")
    logger.info("Will use placeholder mode")

try:
    from spaces import GPU
    SPACES_GPU_AVAILABLE = True
    logger.info("HF Spaces GPU available")
except ImportError:
    # Not running on HF Spaces or spaces module not available
    SPACES_GPU_AVAILABLE = False
    GPU = None
    logger.info("Running without HF Spaces GPU")

def create_mobile_optimized_interface():
    """Create the mobile-optimized Gradio interface and return demo with components"""
    
    # Store components for wiring
    interface_components = {}
    
    with gr.Blocks(
        title="AI Research Assistant MVP",
        theme=gr.themes.Soft(
            primary_hue="blue",
            secondary_hue="gray",
            font=("Inter", "system-ui", "sans-serif")
        ),
        css="""
        /* Mobile-first responsive CSS */
        .mobile-container {
            max-width: 100vw;
            margin: 0 auto;
            padding: 0 12px;
        }
        
        /* Touch-friendly button sizing */
        .gradio-button {
            min-height: 44px !important;
            min-width: 44px !important;
            font-size: 16px !important; /* Prevents zoom on iOS */
        }
        
        /* Mobile-optimized chat interface */
        .chatbot-container {
            height: 60vh !important;
            max-height: 60vh !important;
            overflow-y: auto !important;
            -webkit-overflow-scrolling: touch !important;
        }
        
        /* Mobile input enhancements */
        .textbox-input {
            font-size: 16px !important; /* Prevents zoom */
            min-height: 44px !important;
            padding: 12px !important;
        }
        
        /* Responsive grid adjustments */
        @media (max-width: 768px) {
            .gradio-row {
                flex-direction: column !important;
                gap: 8px !important;
            }
            
            .gradio-column {
                width: 100% !important;
            }
            
            .chatbot-container {
                height: 50vh !important;
            }
        }
        
        /* Dark mode support */
        @media (prefers-color-scheme: dark) {
            body {
                background: #1a1a1a;
                color: #ffffff;
            }
        }
        
        /* Hide scrollbars but maintain functionality */
        .chatbot-container::-webkit-scrollbar {
            width: 4px;
        }
        
        /* Loading states */
        .loading-indicator {
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 20px;
        }
        
        /* Mobile menu enhancements */
        .accordion-content {
            max-height: 200px !important;
            overflow-y: auto !important;
        }
        """
    ) as demo:
        
        # Session Management (Mobile-Optimized)
        with gr.Column(elem_classes="mobile-container"):
            gr.Markdown("""
            # 🧠 Research Assistant
            *Academic AI with transparent reasoning*
            """)
            
            # Session Header Bar (Mobile-Friendly)
            with gr.Row():
                session_info = gr.Textbox(
                    label="Session ID",
                    value=str(uuid.uuid4())[:8],  # Shortened for mobile
                    max_lines=1,
                    show_label=False,
                    container=False,
                    scale=3
                )
                
                new_session_btn = gr.Button(
                    "πŸ”„ New",
                    size="sm",
                    variant="secondary",
                    scale=1,
                    min_width=60
                )
                
                menu_toggle = gr.Button(
                    "βš™οΈ",
                    size="sm",
                    variant="secondary",
                    scale=1,
                    min_width=60
                )
            
            # Main Chat Area (Mobile-Optimized)
            with gr.Tabs() as main_tabs:
                with gr.TabItem("πŸ’¬ Chat", id="chat_tab"):
                    chatbot = gr.Chatbot(
                        label="",
                        show_label=False,
                        height="60vh",
                        elem_classes="chatbot-container",
                        type="messages"
                    )
                    interface_components['chatbot'] = chatbot
                    
                    # Mobile Input Area
                    with gr.Row():
                        message_input = gr.Textbox(
                            placeholder="Ask me anything...",
                            show_label=False,
                            max_lines=3,
                            container=False,
                            scale=4,
                            autofocus=True
                        )
                        interface_components['message_input'] = message_input
                        
                        send_btn = gr.Button(
                            "↑ Send",
                            variant="primary",
                            scale=1,
                            min_width=80
                        )
                        interface_components['send_btn'] = send_btn
                
                # Technical Details Tab (Collapsible for Mobile)
                with gr.TabItem("πŸ” Details", id="details_tab"):
                    with gr.Accordion("Reasoning Chain", open=False):
                        reasoning_display = gr.JSON(
                            label="",
                            show_label=False
                        )
                    
                    with gr.Accordion("Agent Performance", open=False):
                        performance_display = gr.JSON(
                            label="",
                            show_label=False
                        )
                    
                    with gr.Accordion("Session Context", open=False):
                        context_display = gr.JSON(
                            label="",
                            show_label=False
                        )
            
            # Mobile Bottom Navigation
            with gr.Row(visible=False, elem_id="mobile_nav") as mobile_navigation:
                chat_nav_btn = gr.Button("πŸ’¬ Chat", variant="secondary", size="sm", min_width=0)
                details_nav_btn = gr.Button("πŸ” Details", variant="secondary", size="sm", min_width=0)
                settings_nav_btn = gr.Button("βš™οΈ Settings", variant="secondary", size="sm", min_width=0)
        
        # Settings Panel (Modal for Mobile)
        with gr.Column(visible=False, elem_id="settings_panel") as settings:
            with gr.Accordion("Display Options", open=True):
                show_reasoning = gr.Checkbox(
                    label="Show reasoning chain",
                    value=True,
                    info="Display step-by-step reasoning"
                )
                
                show_agent_trace = gr.Checkbox(
                    label="Show agent execution trace",
                    value=False,
                    info="Display which agents processed your request"
                )
                
                compact_mode = gr.Checkbox(
                    label="Compact mode",
                    value=False,
                    info="Optimize for smaller screens"
                )
            
            with gr.Accordion("Performance Options", open=False):
                response_speed = gr.Radio(
                    choices=["Fast", "Balanced", "Thorough"],
                    value="Balanced",
                    label="Response Speed Preference"
                )
                
                cache_enabled = gr.Checkbox(
                    label="Enable context caching",
                    value=True,
                    info="Faster responses using session memory"
                )
            
            gr.Button("Save Preferences", variant="primary")
        
        # Wire up the submit handler INSIDE the gr.Blocks context
        if 'send_btn' in interface_components and 'message_input' in interface_components and 'chatbot' in interface_components:
            # Connect the submit handler with the GPU-decorated function
            interface_components['send_btn'].click(
                fn=chat_handler_fn,
                inputs=[interface_components['message_input'], interface_components['chatbot']],
                outputs=[interface_components['chatbot'], interface_components['message_input']]
            )
    
    return demo, interface_components

def setup_event_handlers(demo, event_handlers):
    """Setup event handlers for the interface"""
    
    # Find components by their labels or types
    components = {}
    for block in demo.blocks:
        if hasattr(block, 'label'):
            if block.label == 'Session ID':
                components['session_info'] = block
            elif hasattr(block, 'value') and 'session' in str(block.value).lower():
                components['session_id'] = block
    
    # Setup message submission handler
    try:
        # This is a simplified version - you'll need to adapt based on your actual component structure
        if hasattr(demo, 'submit'):
            demo.submit(
                fn=event_handlers.handle_message_submit,
                inputs=[components.get('message_input'), components.get('chatbot')],
                outputs=[components.get('message_input'), components.get('chatbot')]
            )
    except Exception as e:
        print(f"Could not setup event handlers: {e}")
        # Fallback to basic functionality
    
    return demo

async def process_message_async(message: str, history: Optional[List], session_id: str) -> Tuple[List, str]:
    """
    Process message with full orchestration system
    Returns (updated_history, empty_string)
    """
    global orchestrator
    
    try:
        logger.info(f"Processing message: {message[:100]}")
        logger.info(f"Session ID: {session_id}")
        
        if not message or not message.strip():
            logger.debug("Empty message received")
            return history if history else [], ""
        
        if history is None:
            history = []
        
        new_history = list(history) if isinstance(history, list) else []
        
        # Add user message
        new_history.append({"role": "user", "content": message.strip()})
        
        # Try to use orchestrator if available
        if orchestrator is not None:
            try:
                logger.info("Attempting full orchestration...")
                # Use orchestrator to process
                result = await orchestrator.process_request(
                    session_id=session_id,
                    user_input=message.strip()
                )
                
                # Extract response from result
                response = result.get('response', result.get('final_response', str(result)))
                logger.info(f"Orchestrator returned response: {response[:100]}")
                
            except Exception as orch_error:
                logger.error(f"Orchestrator error: {orch_error}", exc_info=True)
                response = f"[Orchestrator Error] {str(orch_error)}"
        else:
            # Fallback placeholder
            logger.info("Using placeholder response")
            response = f"I received your message: {message}\n\nThis is a placeholder response. The orchestrator system is {'' if orchestrator_available else 'not'} available."
        
        # Add assistant response
        new_history.append({"role": "assistant", "content": response})
        logger.info("Message processing complete")
        
        return new_history, ""
        
    except Exception as e:
        logger.error(f"Error in process_message_async: {e}", exc_info=True)
        error_history = list(history) if history else []
        error_history.append({"role": "user", "content": message})
        error_history.append({"role": "assistant", "content": f"I encountered an error: {str(e)}"})
        return error_history, ""

def process_message(message: str, history: Optional[List]) -> Tuple[List, str]:
    """
    Synchronous wrapper for async processing
    """
    import asyncio
    
    # Generate a session ID
    session_id = str(uuid.uuid4())[:8]
    
    try:
        # Run async processing
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        result = loop.run_until_complete(process_message_async(message, history, session_id))
        return result
    except Exception as e:
        logger.error(f"Error in process_message: {e}", exc_info=True)
        error_history = list(history) if history else []
        error_history.append({"role": "user", "content": message})
        error_history.append({"role": "assistant", "content": f"Error: {str(e)}"})
        return error_history, ""

# Decorate the chat handler with GPU if available
if SPACES_GPU_AVAILABLE and GPU is not None:
    @GPU  # This decorator is detected by HF Spaces for ZeroGPU allocation
    def gpu_chat_handler(message, history):
        """Handle chat messages with GPU support"""
        return process_message(message, history)
    chat_handler_fn = gpu_chat_handler
else:
    chat_handler_fn = process_message

# Initialize orchestrator on module load
def initialize_orchestrator():
    """Initialize the orchestration system with logging"""
    global orchestrator
    
    if not orchestrator_available:
        logger.info("Orchestrator components not available, skipping initialization")
        return
    
    try:
        logger.info("=" * 60)
        logger.info("INITIALIZING ORCHESTRATION SYSTEM")
        logger.info("=" * 60)
        
        # Get HF token
        hf_token = os.getenv('HF_TOKEN', '')
        if not hf_token:
            logger.warning("HF_TOKEN not found in environment")
        
        # Initialize LLM Router
        logger.info("Step 1/6: Initializing LLM Router...")
        llm_router = LLMRouter(hf_token)
        logger.info("βœ“ LLM Router initialized")
        
        # Initialize Agents
        logger.info("Step 2/6: Initializing Agents...")
        agents = {
            'intent_recognition': create_intent_agent(llm_router),
            'response_synthesis': create_synthesis_agent(llm_router),
            'safety_check': create_safety_agent(llm_router)
        }
        logger.info(f"βœ“ Initialized {len(agents)} agents")
        
        # Initialize Context Manager
        logger.info("Step 3/6: Initializing Context Manager...")
        context_manager = EfficientContextManager()
        logger.info("βœ“ Context Manager initialized")
        
        # Initialize Orchestrator
        logger.info("Step 4/6: Initializing Orchestrator...")
        orchestrator = MVPOrchestrator(llm_router, context_manager, agents)
        logger.info("βœ“ Orchestrator initialized")
        
        logger.info("=" * 60)
        logger.info("ORCHESTRATION SYSTEM READY")
        logger.info("=" * 60)
        
    except Exception as e:
        logger.error(f"Failed to initialize orchestrator: {e}", exc_info=True)
        orchestrator = None

# Try to initialize orchestrator
initialize_orchestrator()

if __name__ == "__main__":
    logger.info("=" * 60)
    logger.info("STARTING APP")
    logger.info("=" * 60)
    
    demo, components = create_mobile_optimized_interface()
    
    logger.info("βœ“ Interface created")
    logger.info(f"Orchestrator available: {orchestrator is not None}")
    
    # Launch the app
    logger.info("=" * 60)
    logger.info("LAUNCHING GRADIO APP")
    logger.info("=" * 60)
    demo.launch(
        server_name="0.0.0.0",
        server_port=7860,
        share=False
    )