Skip to main content
Glama
overlay_renderer.go7.16 kB
package system import ( "fmt" "strings" "github.com/charmbracelet/lipgloss" ) // OverlayRenderer handles rendering system messages as overlays using Lipgloss type OverlayRenderer struct { controller *Controller // Styles borderStyle lipgloss.Style titleStyle lipgloss.Style messageStyle lipgloss.Style errorStyle lipgloss.Style warnStyle lipgloss.Style infoStyle lipgloss.Style } // NewOverlayRenderer creates a new overlay renderer func NewOverlayRenderer(controller *Controller) *OverlayRenderer { r := &OverlayRenderer{ controller: controller, } r.initStyles() return r } // initStyles initializes all Lipgloss styles func (r *OverlayRenderer) initStyles() { // Border style for the overlay panel with background r.borderStyle = lipgloss.NewStyle(). BorderStyle(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color("240")). Background(lipgloss.Color("235")). // Dark background to ensure visibility Padding(1, 2) // Title style r.titleStyle = lipgloss.NewStyle(). Bold(true). Foreground(lipgloss.Color("226")). MarginBottom(1) // Message styles by level r.errorStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("196")) r.warnStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("214")) r.infoStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("245")) r.messageStyle = lipgloss.NewStyle() } // RenderOverlay renders the system messages as an overlay on the main content func (r *OverlayRenderer) RenderOverlay(mainContent string, width, height int) string { if !r.controller.HasMessages() { return mainContent } // Create the panel content panel := r.renderPanel(width) panelLines := strings.Split(panel, "\n") panelHeight := len(panelLines) // Split main content into lines mainLines := strings.Split(mainContent, "\n") // Calculate position for bottom-right placement panelWidth := r.getPanelWidth(width) startRow := height - panelHeight - 2 // Leave some margin from bottom if startRow < 0 { startRow = 0 } startCol := width - panelWidth - 2 // Leave some margin from right if startCol < 0 { startCol = 0 } // Ensure we have enough lines in main content for len(mainLines) < height { mainLines = append(mainLines, strings.Repeat(" ", width)) } // Overlay the panel onto the main content for i, panelLine := range panelLines { targetRow := startRow + i if targetRow >= 0 && targetRow < len(mainLines) { line := mainLines[targetRow] // Ensure line is long enough if len(line) < startCol { line = line + strings.Repeat(" ", startCol-len(line)) } // Replace the portion of the line with the panel content if startCol > 0 { // Keep the left part of the original line beforePanel := line if len(beforePanel) > startCol { beforePanel = beforePanel[:startCol] } else { beforePanel = beforePanel + strings.Repeat(" ", startCol-len(beforePanel)) } mainLines[targetRow] = beforePanel + panelLine } else { mainLines[targetRow] = panelLine } } } return strings.Join(mainLines, "\n") } // renderPanel renders the system message panel func (r *OverlayRenderer) renderPanel(maxWidth int) string { // Get messages to display messages := r.controller.GetMessages() if len(messages) == 0 { return "" } // Title title := r.titleStyle.Render("System Messages") // Render messages var messageLines []string for _, msg := range messages { messageLines = append(messageLines, r.renderMessage(msg)) } // Join all messages content := lipgloss.JoinVertical( lipgloss.Left, title, strings.Join(messageLines, "\n"), ) // Apply border and padding panelWidth := r.getPanelWidth(maxWidth) panel := r.borderStyle. Width(panelWidth - 4). // Account for border and padding Render(content) return panel } // renderMessage renders a single message with appropriate styling func (r *OverlayRenderer) renderMessage(msg Message) string { // Choose style based on level var style lipgloss.Style switch msg.Level { case "ERROR": style = r.errorStyle case "WARN": style = r.warnStyle case "INFO": style = r.infoStyle default: style = r.messageStyle } // Format message with timestamp and context timestamp := msg.Timestamp.Format("15:04:05") prefix := fmt.Sprintf("[%s] %s:", timestamp, msg.Context) // Combine prefix and message fullMessage := fmt.Sprintf("%s %s", prefix, msg.Message) // Truncate if too long maxLength := 60 if len(fullMessage) > maxLength { fullMessage = fullMessage[:maxLength-3] + "..." } return style.Render(fullMessage) } // getPanelWidth calculates the appropriate panel width func (r *OverlayRenderer) getPanelWidth(screenWidth int) int { // Use 1/3 of screen width, with min/max bounds panelWidth := screenWidth / 3 minWidth := 40 maxWidth := 80 if panelWidth < minWidth { panelWidth = minWidth } if panelWidth > maxWidth { panelWidth = maxWidth } // Don't exceed screen width if panelWidth > screenWidth-4 { panelWidth = screenWidth - 4 } return panelWidth } // RenderFullScreen renders the system messages in full screen mode func (r *OverlayRenderer) RenderFullScreen(width, height int) string { if !r.controller.HasMessages() { noMessagesStyle := lipgloss.NewStyle(). Foreground(lipgloss.Color("245")). Align(lipgloss.Center, lipgloss.Center). Width(width). Height(height) return noMessagesStyle.Render("No system messages") } // Get all messages messages := r.controller.GetMessages() // Title title := r.titleStyle.Render(fmt.Sprintf("System Messages (%d total)", len(messages))) // Render messages with scrolling handled by viewport in controller var messageLines []string for _, msg := range messages { messageLines = append(messageLines, r.renderFullMessage(msg)) } // Create container for full screen containerStyle := lipgloss.NewStyle(). Width(width). Height(height). Padding(1, 2) // Create separator with lipgloss border separatorStyle := lipgloss.NewStyle(). Width(width - 4). BorderStyle(lipgloss.NormalBorder()). BorderTop(true). BorderBottom(false). BorderLeft(false). BorderRight(false). BorderForeground(lipgloss.Color("240")) // Join content content := lipgloss.JoinVertical( lipgloss.Left, title, separatorStyle.Render(""), strings.Join(messageLines, "\n"), ) return containerStyle.Render(content) } // renderFullMessage renders a message with full details for full screen view func (r *OverlayRenderer) renderFullMessage(msg Message) string { // Choose style based on level var levelStyle lipgloss.Style switch msg.Level { case "ERROR": levelStyle = r.errorStyle case "WARN": levelStyle = r.warnStyle case "INFO": levelStyle = r.infoStyle default: levelStyle = r.messageStyle } // Format message parts timestamp := msg.Timestamp.Format("2006-01-02 15:04:05") level := levelStyle.Render(fmt.Sprintf("[%-5s]", msg.Level)) context := lipgloss.NewStyle().Bold(true).Render(msg.Context) // Combine parts header := fmt.Sprintf("%s %s %s", timestamp, level, context) message := " " + msg.Message return lipgloss.JoinVertical( lipgloss.Left, header, message, ) }

Latest Blog Posts

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/standardbeagle/brummer'

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