details.go 3.5 KB

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