1package logs
2
3import (
4 "fmt"
5 "strings"
6 "time"
7
8 "github.com/charmbracelet/bubbles/key"
9 "github.com/charmbracelet/bubbles/viewport"
10 tea "github.com/charmbracelet/bubbletea"
11 "github.com/charmbracelet/lipgloss"
12 "github.com/kujtimiihoxha/opencode/internal/logging"
13 "github.com/kujtimiihoxha/opencode/internal/tui/layout"
14 "github.com/kujtimiihoxha/opencode/internal/tui/styles"
15)
16
17type DetailComponent interface {
18 tea.Model
19 layout.Sizeable
20 layout.Bindings
21}
22
23type detailCmp struct {
24 width, height int
25 currentLog logging.LogMessage
26 viewport viewport.Model
27}
28
29func (i *detailCmp) Init() tea.Cmd {
30 messages := logging.List()
31 if len(messages) == 0 {
32 return nil
33 }
34 i.currentLog = messages[0]
35 return nil
36}
37
38func (i *detailCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
39 switch msg := msg.(type) {
40 case selectedLogMsg:
41 if msg.ID != i.currentLog.ID {
42 i.currentLog = logging.LogMessage(msg)
43 i.updateContent()
44 }
45 }
46
47 return i, nil
48}
49
50func (i *detailCmp) updateContent() {
51 var content strings.Builder
52
53 // Format the header with timestamp and level
54 timeStyle := lipgloss.NewStyle().Foreground(styles.SubText0)
55 levelStyle := getLevelStyle(i.currentLog.Level)
56
57 header := lipgloss.JoinHorizontal(
58 lipgloss.Center,
59 timeStyle.Render(i.currentLog.Time.Format(time.RFC3339)),
60 " ",
61 levelStyle.Render(i.currentLog.Level),
62 )
63
64 content.WriteString(lipgloss.NewStyle().Bold(true).Render(header))
65 content.WriteString("\n\n")
66
67 // Message with styling
68 messageStyle := lipgloss.NewStyle().Bold(true).Foreground(styles.Text)
69 content.WriteString(messageStyle.Render("Message:"))
70 content.WriteString("\n")
71 content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(i.currentLog.Message))
72 content.WriteString("\n\n")
73
74 // Attributes section
75 if len(i.currentLog.Attributes) > 0 {
76 attrHeaderStyle := lipgloss.NewStyle().Bold(true).Foreground(styles.Text)
77 content.WriteString(attrHeaderStyle.Render("Attributes:"))
78 content.WriteString("\n")
79
80 // Create a table-like display for attributes
81 keyStyle := lipgloss.NewStyle().Foreground(styles.Primary).Bold(true)
82 valueStyle := lipgloss.NewStyle().Foreground(styles.Text)
83
84 for _, attr := range i.currentLog.Attributes {
85 attrLine := fmt.Sprintf("%s: %s",
86 keyStyle.Render(attr.Key),
87 valueStyle.Render(attr.Value),
88 )
89 content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(attrLine))
90 content.WriteString("\n")
91 }
92 }
93
94 i.viewport.SetContent(content.String())
95}
96
97func getLevelStyle(level string) lipgloss.Style {
98 style := lipgloss.NewStyle().Bold(true)
99
100 switch strings.ToLower(level) {
101 case "info":
102 return style.Foreground(styles.Blue)
103 case "warn", "warning":
104 return style.Foreground(styles.Warning)
105 case "error", "err":
106 return style.Foreground(styles.Error)
107 case "debug":
108 return style.Foreground(styles.Green)
109 default:
110 return style.Foreground(styles.Text)
111 }
112}
113
114func (i *detailCmp) View() string {
115 return styles.ForceReplaceBackgroundWithLipgloss(i.viewport.View(), styles.Background)
116}
117
118func (i *detailCmp) GetSize() (int, int) {
119 return i.width, i.height
120}
121
122func (i *detailCmp) SetSize(width int, height int) tea.Cmd {
123 i.width = width
124 i.height = height
125 i.viewport.Width = i.width
126 i.viewport.Height = i.height
127 i.updateContent()
128 return nil
129}
130
131func (i *detailCmp) BindingKeys() []key.Binding {
132 return layout.KeyMapToSlice(i.viewport.KeyMap)
133}
134
135func NewLogsDetails() DetailComponent {
136 return &detailCmp{
137 viewport: viewport.New(0, 0),
138 }
139}