{placeholder}
" in html_content: 110 | html_content = html_content.replace(f"{placeholder}
", details_element) 111 | else: 112 | # If we can't find the exact placeholder, try a more aggressive approach 113 | template_logger.info(f"Placeholder {placeholder} not found in exact form, trying regex") 114 | pattern = re.compile(fr'{placeholder}|\s*{placeholder}\s*
', re.IGNORECASE) 115 | html_content = pattern.sub(details_element, html_content) 116 | 117 | template_logger.info(f"Final HTML after restoring think blocks: {html_content[:100]}...") 118 | return html_content 119 | 120 | app.jinja_env.filters['markdown'] = custom_markdown_filter 121 | 122 | # Initialize database extension 123 | db.init_app(app) 124 | 125 | # Register routes 126 | from .routes import register_routes 127 | register_routes(app) 128 | 129 | logger.info( 130 | f"Web UI initialized successfully on {WEB_UI_HOST}:{WEB_UI_PORT}") 131 | return app 132 | except Exception as e: 133 | logger.error(f"Failed to initialize web UI: {e}") 134 | return None 135 | -------------------------------------------------------------------------------- /src/mcp_perplexity/web/database_extension.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, List, Dict 2 | from quart import Quart 3 | from ..database import DatabaseManager, Chat, Message 4 | from datetime import datetime 5 | 6 | 7 | class Database: 8 | def __init__(self, app: Optional[Quart] = None): 9 | self._database_manager: Optional[DatabaseManager] = None 10 | if app is not None: 11 | self.init_app(app) 12 | 13 | def init_app(self, app: Quart): 14 | self._database_manager = DatabaseManager() 15 | 16 | # Register extension with Quart 17 | if not hasattr(app, 'extensions'): 18 | app.extensions = {} 19 | app.extensions['database'] = self 20 | 21 | @property 22 | def database_manager(self) -> DatabaseManager: 23 | if self._database_manager is None: 24 | raise RuntimeError( 25 | "Database extension not initialized. " 26 | "Did you forget to call init_app()?") 27 | return self._database_manager 28 | 29 | def get_all_chats(self, page: int = 1, per_page: int = 50) -> Dict: 30 | """Get all chats with pagination and convert them to dictionaries to avoid session issues. 31 | 32 | Args: 33 | page: The page number (1-based) 34 | per_page: Number of items per page (default 50) 35 | 36 | Returns: 37 | Dict containing chats and pagination info 38 | """ 39 | with self.database_manager.get_session() as session: 40 | # Calculate offset 41 | offset = (page - 1) * per_page 42 | 43 | # Get total count 44 | total = session.query(Chat).count() 45 | 46 | # Get paginated chats 47 | chats = session.query(Chat).order_by(Chat.created_at.desc())\ 48 | .offset(offset).limit(per_page).all() 49 | 50 | # Convert to dictionaries while session is still open 51 | return { 52 | 'chats': [{ 53 | 'id': chat.id, 54 | 'title': chat.title, 55 | 'created_at': chat.created_at 56 | } for chat in chats], 57 | 'pagination': { 58 | 'page': page, 59 | 'per_page': per_page, 60 | 'total': total, 61 | 'pages': (total + per_page - 1) // per_page 62 | } 63 | } 64 | 65 | def get_chat(self, chat_id: str) -> Optional[Dict]: 66 | """Get a chat and convert it to a dictionary to avoid session issues.""" 67 | with self.database_manager.get_session() as session: 68 | chat = session.query(Chat).filter(Chat.id == chat_id).first() 69 | if not chat: 70 | return None 71 | # Convert to dictionary while session is still open 72 | return { 73 | 'id': chat.id, 74 | 'title': chat.title, 75 | 'created_at': chat.created_at 76 | } 77 | 78 | def get_chat_messages(self, chat_id: str) -> List[Dict]: 79 | """Get chat messages and convert them to dictionaries to avoid session issues.""" 80 | with self.database_manager.get_session() as session: 81 | messages = session.query(Message).filter( 82 | Message.chat_id == chat_id 83 | ).order_by(Message.timestamp.asc()).all() 84 | # Convert to dictionaries while session is still open 85 | return [{ 86 | 'id': msg.id, 87 | 'role': msg.role, 88 | 'content': msg.content, 89 | 'timestamp': msg.timestamp 90 | } for msg in messages] 91 | 92 | def delete_chat(self, chat_id: str) -> None: 93 | """Delete a chat and all its messages.""" 94 | with self.database_manager.get_session() as session: 95 | chat = session.query(Chat).filter(Chat.id == chat_id).first() 96 | if chat: 97 | session.delete(chat) 98 | session.commit() 99 | 100 | 101 | # Create the extension instance 102 | db = Database() 103 | -------------------------------------------------------------------------------- /src/mcp_perplexity/web/routes.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from quart import render_template, jsonify, request 4 | from .database_extension import db 5 | 6 | # Setup logging 7 | logger = logging.getLogger(__name__) 8 | 9 | # Only enable logging if DEBUG_LOGS is set to true 10 | if os.getenv('DEBUG_LOGS', 'false').lower() == 'true': 11 | logger.setLevel(logging.INFO) 12 | else: 13 | logger.setLevel(logging.CRITICAL) # Effectively disable logging 14 | 15 | 16 | def register_routes(app): 17 | @app.route('/') 18 | async def index(): 19 | try: 20 | page = request.args.get('page', 1, type=int) 21 | chats = db.get_all_chats(page=page) 22 | return await render_template('index.html', chats=chats) 23 | except Exception as e: 24 | logger.error(f"Error in index route: {e}") 25 | return await render_template('error.html', error="Failed to load chats"), 500 26 | 27 | @app.route('/chat/68 | Showing 69 | {{ ((chats['pagination']['page'] - 1) * chats['pagination']['per_page']) + 1 }} 70 | to 71 | {{ min(chats['pagination']['page'] * chats['pagination']['per_page'], chats['pagination']['total']) }} 72 | of 73 | {{ chats['pagination']['total'] }} 74 | results 75 |
76 |14 | Created {{ chat['created_at'].strftime('%Y-%m-%d %H:%M:%S') }} 15 |
16 |17 | {{ error }} 18 |
19 | 22 |12 | Your conversations with Perplexity AI 13 |
14 |