Skip to main content
Glama
controller.go7.36 kB
package system import ( "fmt" "strings" "github.com/charmbracelet/bubbles/viewport" "github.com/standardbeagle/brummer/internal/tui/navigation" ) // SystemMessageMsg represents a system message event type SystemMessageMsg struct { Level string Context string Message string } // View type alias for unread indicators type View = navigation.View // UnreadIndicator tracks unread notifications for a view type UnreadIndicator struct { Count int Severity string Icon string } // Controller manages system messages and panel state type Controller struct { messages []Message maxMessages int expanded bool viewport viewport.Model // Unread indicators management unreadIndicators map[View]UnreadIndicator currentView View // Dimensions from parent width int height int headerHeight int footerHeight int } // NewController creates a new system message controller func NewController(maxMessages int) *Controller { if maxMessages <= 0 { maxMessages = 100 } return &Controller{ messages: make([]Message, 0, maxMessages), maxMessages: maxMessages, viewport: viewport.New(0, 0), unreadIndicators: make(map[View]UnreadIndicator), } } // AddMessage adds a new system message func (c *Controller) AddMessage(level, context, message string) { msg := NewMessage(level, context, message) // Add to the beginning of the list (most recent first) c.messages = append([]Message{msg}, c.messages...) // Keep only the last maxMessages if len(c.messages) > c.maxMessages { c.messages = c.messages[:c.maxMessages] } // Update the viewport content c.updateViewport() } // Clear removes all system messages func (c *Controller) Clear() { c.messages = []Message{} c.expanded = false c.viewport.SetContent("") } // ToggleExpanded toggles the expanded state of the panel func (c *Controller) ToggleExpanded() { if len(c.messages) > 0 { c.expanded = !c.expanded c.updateViewport() } } // IsExpanded returns whether the panel is expanded func (c *Controller) IsExpanded() bool { return c.expanded } // HasMessages returns whether there are any messages func (c *Controller) HasMessages() bool { return len(c.messages) > 0 } // GetMessageCount returns the number of messages func (c *Controller) GetMessageCount() int { return len(c.messages) } // GetMessages returns all messages func (c *Controller) GetMessages() []Message { return c.messages } // UpdateSize updates the controller dimensions func (c *Controller) UpdateSize(width, height, headerHeight, footerHeight int) { c.width = width c.height = height c.headerHeight = headerHeight c.footerHeight = footerHeight c.updateViewport() } // updateViewport updates the viewport with current content func (c *Controller) updateViewport() { // Calculate viewport height height := c.height - c.headerHeight - c.footerHeight if !c.expanded { height = 5 // Show only 5 lines when not expanded } c.viewport.Width = c.width c.viewport.Height = height // Format messages for display content := c.formatMessagesForDisplay() c.viewport.SetContent(content) } // formatMessagesForDisplay formats messages for the panel func (c *Controller) formatMessagesForDisplay() string { if len(c.messages) == 0 { return "No system messages" } var b strings.Builder // Determine how many messages to show messagesToShow := c.messages if !c.expanded && len(c.messages) > 5 { messagesToShow = c.messages[:5] } // Format each message for i, msg := range messagesToShow { if i > 0 { b.WriteString("\n") } // Format timestamp timestamp := msg.Timestamp.Format("15:04:05") // Get icon based on level icon := GetIcon(msg.Level) // Build message line msgLine := fmt.Sprintf("[%s] %s %s: %s", timestamp, icon, msg.Context, msg.Message, ) b.WriteString(msgLine) } // Add count if not showing all messages if !c.expanded && len(c.messages) > 5 { b.WriteString(fmt.Sprintf("\n... and %d more messages (press 'e' to expand, 'm' to clear)", len(c.messages)-5)) } else if len(c.messages) > 0 { // Add clear hint when showing all messages b.WriteString("\n(Press 'm' to clear messages)") } return b.String() } // GetViewport returns the viewport for rendering func (c *Controller) GetViewport() *viewport.Model { return &c.viewport } // SetCurrentView sets the current view for unread indicator management func (c *Controller) SetCurrentView(view View) { c.currentView = view } // UpdateUnreadIndicator updates the unread indicator for a specific view func (c *Controller) UpdateUnreadIndicator(view View, severity string, increment int) { if view == c.currentView { // Don't mark as unread if we're currently viewing this tab return } indicator := c.unreadIndicators[view] indicator.Count += increment // Update severity and icon based on priority if c.shouldUpdateSeverity(indicator.Severity, severity) { indicator.Severity = severity indicator.Icon = GetIcon(severity) } c.unreadIndicators[view] = indicator } // ClearUnreadIndicator clears the unread indicator for a specific view func (c *Controller) ClearUnreadIndicator(view View) { delete(c.unreadIndicators, view) } // shouldUpdateSeverity determines if the new severity is higher priority func (c *Controller) shouldUpdateSeverity(current, new string) bool { priority := map[string]int{ "error": 4, "warning": 3, "success": 2, "info": 1, } return priority[new] > priority[current] } // GetUnreadIndicators returns all unread indicators func (c *Controller) GetUnreadIndicators() map[View]UnreadIndicator { return c.unreadIndicators } // OverlaySystemPanel overlays the system panel on top of the main content func (c *Controller) OverlaySystemPanel(mainContent string) string { // Split main content into lines lines := strings.Split(mainContent, "\n") // Calculate panel height (5 messages + title + border = 8 lines) panelHeight := 8 if c.GetMessageCount() < 5 { panelHeight = c.GetMessageCount() + 3 // messages + title + border } // Position panel at bottom, but above help (2 lines) startLine := len(lines) - panelHeight - 2 if startLine < 0 { startLine = 0 } // Replace the lines with the panel panelContent := c.formatMessagesForDisplay() panelLines := strings.Split(panelContent, "\n") // Add border and title panelWithBorder := []string{ "┌─ System Messages ─────────────────────────────────────────────────┐", } for _, line := range panelLines { // Pad line to fit within border paddedLine := fmt.Sprintf("│ %-70s │", line) if len(line) > 70 { paddedLine = fmt.Sprintf("│ %.67s... │", line) } panelWithBorder = append(panelWithBorder, paddedLine) } panelWithBorder = append(panelWithBorder, "└───────────────────────────────────────────────────────────────────┘") // Replace the appropriate lines result := make([]string, len(lines)) copy(result, lines) for i, panelLine := range panelWithBorder { lineIndex := startLine + i if lineIndex < len(result) { result[lineIndex] = panelLine } } return strings.Join(result, "\n") }

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