Skip to main content
Glama
conversation_formatter.py11.5 kB
"""Conversation Formatter - Handles different conversation display formats""" import re from enum import Enum from PySide2 import QtCore, QtWidgets class ConversationFormat(Enum): """Available conversation formats.""" PLAIN_TEXT = "plain" MARKDOWN = "markdown" HTML = "html" RICH_TEXT = "rich" class ConversationFormatter: """Formats conversation messages in different styles.""" def __init__(self, format_type: ConversationFormat = ConversationFormat.RICH_TEXT): self.format_type = format_type def format_message(self, sender: str, message: str, timestamp: str = None) -> str: """Format a message based on the current format type.""" if self.format_type == ConversationFormat.PLAIN_TEXT: return self._format_plain_text(sender, message, timestamp) elif self.format_type == ConversationFormat.MARKDOWN: return self._format_markdown(sender, message, timestamp) elif self.format_type == ConversationFormat.HTML: return self._format_html(sender, message, timestamp) else: # RICH_TEXT return self._format_rich_text(sender, message, timestamp) def _format_plain_text(self, sender: str, message: str, timestamp: str) -> str: """Format as plain text.""" if timestamp: return f"[{timestamp}] {sender}: {message}\n" else: return f"{sender}: {message}\n" def _format_markdown(self, sender: str, message: str, timestamp: str) -> str: """Format as markdown.""" # Convert common markdown patterns formatted_message = message # Bold formatted_message = re.sub(r"\*\*(.*?)\*\*", r"<b>\1</b>", formatted_message) # Italic formatted_message = re.sub(r"\*(.*?)\*", r"<i>\1</i>", formatted_message) # Code blocks formatted_message = re.sub( r"```(.*?)```", r"<pre>\1</pre>", formatted_message, flags=re.DOTALL ) # Inline code formatted_message = re.sub(r"`(.*?)`", r"<code>\1</code>", formatted_message) # Headers formatted_message = re.sub( r"^# (.*?)$", r"<h1>\1</h1>", formatted_message, flags=re.MULTILINE ) formatted_message = re.sub( r"^## (.*?)$", r"<h2>\1</h2>", formatted_message, flags=re.MULTILINE ) formatted_message = re.sub( r"^### (.*?)$", r"<h3>\1</h3>", formatted_message, flags=re.MULTILINE ) # Lists formatted_message = re.sub( r"^- (.*?)$", r"• \1", formatted_message, flags=re.MULTILINE ) formatted_message = re.sub( r"^\* (.*?)$", r"• \1", formatted_message, flags=re.MULTILINE ) formatted_message = re.sub( r"^\d+\. (.*?)$", r"⦿ \1", formatted_message, flags=re.MULTILINE ) # Line breaks formatted_message = formatted_message.replace("\n", "<br>") if timestamp: header = ( f'<span style="color: #888; font-size: 10px;">[{timestamp}]</span> ' ) else: header = "" sender_style = self._get_sender_style(sender) return f'{header}<span style="{sender_style}">{sender}:</span><br>{formatted_message}<br><br>' def _format_html(self, sender: str, message: str, timestamp: str) -> str: """Format as HTML.""" # Escape HTML characters message = ( message.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;") ) message = message.replace("\n", "<br>") if timestamp: header = ( f'<span style="color: #888; font-size: 10px;">[{timestamp}]</span> ' ) else: header = "" sender_style = self._get_sender_style(sender) return f'{header}<span style="{sender_style}">{sender}:</span><br>{message}<br><br>' def _format_rich_text(self, sender: str, message: str, timestamp: str) -> str: """Format as rich text with full styling.""" # Process markdown-like syntax formatted_message = self._process_rich_text_formatting(message) if timestamp: header = ( f'<span style="color: #888; font-size: 10px;">[{timestamp}]</span> ' ) else: header = "" sender_style = self._get_sender_style(sender) message_style = self._get_message_style(sender) # Create message block with proper styling return f""" <div style="margin: 5px 0; padding: 8px; background-color: {self._get_background_color(sender)}; border-radius: 8px;"> {header}<span style="{sender_style}">{sender}</span><br> <div style="{message_style}">{formatted_message}</div> </div> """ def _process_rich_text_formatting(self, text: str) -> str: """Process rich text formatting with enhanced styles.""" # Bold text = re.sub(r"\*\*(.*?)\*\*", r"<b>\1</b>", text) # Italic text = re.sub(r"\*(.*?)\*", r"<i>\1</i>", text) # Code blocks with syntax highlighting simulation def format_code_block(match): code = match.group(1).strip() return f'<pre style="background-color: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace; overflow-x: auto;">{code}</pre>' text = re.sub(r"```(.*?)```", format_code_block, text, flags=re.DOTALL) # Inline code text = re.sub( r"`(.*?)`", r'<code style="background-color: #f0f0f0; padding: 2px 4px; border-radius: 3px; font-family: monospace;">\1</code>', text, ) # Headers text = re.sub( r"^# (.*?)$", r'<h1 style="color: #333; margin: 10px 0 5px 0;">\1</h1>', text, flags=re.MULTILINE, ) text = re.sub( r"^## (.*?)$", r'<h2 style="color: #444; margin: 8px 0 4px 0;">\1</h2>', text, flags=re.MULTILINE, ) text = re.sub( r"^### (.*?)$", r'<h3 style="color: #555; margin: 6px 0 3px 0;">\1</h3>', text, flags=re.MULTILINE, ) # Lists with better styling text = re.sub( r"^- (.*?)$", r'<span style="color: #2196F3;">•</span> \1', text, flags=re.MULTILINE, ) text = re.sub( r"^\* (.*?)$", r'<span style="color: #2196F3;">•</span> \1', text, flags=re.MULTILINE, ) text = re.sub( r"^(\d+)\. (.*?)$", r'<span style="color: #2196F3;">\1.</span> \2', text, flags=re.MULTILINE, ) # Links (basic support) text = re.sub( r"\[([^\]]+)\]\(([^\)]+)\)", r'<a href="\2" style="color: #2196F3; text-decoration: underline;">\1</a>', text, ) # Line breaks text = text.replace("\n", "<br>") return text def _get_sender_style(self, sender: str) -> str: """Get style for sender name.""" if sender.lower() == "you": return "font-weight: bold; color: #2196F3; font-size: 12px;" elif sender.lower() in ["ai", "assistant", "claude", "gpt", "gemini"]: return "font-weight: bold; color: #4CAF50; font-size: 12px;" elif sender.lower() == "system": return "font-weight: bold; color: #FF9800; font-size: 12px;" else: return "font-weight: bold; color: #666; font-size: 12px;" def _get_message_style(self, sender: str) -> str: """Get style for message content.""" return "margin-top: 4px; font-size: 11px; line-height: 1.5;" def _get_background_color(self, sender: str) -> str: """Get background color for message block.""" if sender.lower() == "you": return "#e3f2fd" elif sender.lower() in ["ai", "assistant", "claude", "gpt", "gemini"]: return "#e8f5e9" elif sender.lower() == "system": return "#fff3e0" else: return "#f5f5f5" def export_conversation( self, messages: list, format_type: ConversationFormat = None ) -> str: """Export entire conversation in specified format.""" if format_type: original_format = self.format_type self.format_type = format_type exported = "" if self.format_type == ConversationFormat.MARKDOWN: exported = "# Conversation Export\n\n" elif self.format_type == ConversationFormat.HTML: exported = """ <html> <head> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; } .message { margin: 10px 0; padding: 10px; border-radius: 8px; } .user { background-color: #e3f2fd; } .ai { background-color: #e8f5e9; } .system { background-color: #fff3e0; } </style> </head> <body> <h1>Conversation Export</h1> """ for msg in messages: sender = msg.get("sender", "Unknown") content = msg.get("message", "") timestamp = msg.get("timestamp", "") if self.format_type == ConversationFormat.HTML: css_class = ( "user" if sender.lower() == "you" else "ai" if sender.lower() in ["ai", "assistant"] else "system" ) exported += f'<div class="message {css_class}">' exported += self.format_message(sender, content, timestamp) if self.format_type == ConversationFormat.HTML: exported += "</div>" if self.format_type == ConversationFormat.HTML: exported += "</body></html>" if format_type: self.format_type = original_format return exported class FormatSelectionWidget(QtWidgets.QWidget): """Widget for selecting conversation format.""" format_changed = QtCore.Signal(ConversationFormat) def __init__(self, parent=None): super().__init__(parent) self.current_format = ConversationFormat.RICH_TEXT self._setup_ui() def _setup_ui(self): """Setup the UI.""" layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(QtWidgets.QLabel("Format:")) self.format_combo = QtWidgets.QComboBox() self.format_combo.addItem("Rich Text", ConversationFormat.RICH_TEXT) self.format_combo.addItem("Plain Text", ConversationFormat.PLAIN_TEXT) self.format_combo.addItem("Markdown", ConversationFormat.MARKDOWN) self.format_combo.addItem("HTML", ConversationFormat.HTML) self.format_combo.currentIndexChanged.connect(self._on_format_changed) layout.addWidget(self.format_combo) layout.addStretch() def _on_format_changed(self, index): """Handle format change.""" self.current_format = self.format_combo.itemData(index) self.format_changed.emit(self.current_format) def get_current_format(self) -> ConversationFormat: """Get current selected format.""" return self.current_format

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jango-blockchained/mcp-freecad'

If you have feedback or need assistance with the MCP directory API, please join our Discord server