| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- package logs
- import (
- "fmt"
- "strings"
- "time"
- "github.com/charmbracelet/bubbles/key"
- "github.com/charmbracelet/bubbles/viewport"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
- "github.com/opencode-ai/opencode/internal/logging"
- "github.com/opencode-ai/opencode/internal/tui/layout"
- "github.com/opencode-ai/opencode/internal/tui/styles"
- "github.com/opencode-ai/opencode/internal/tui/theme"
- )
- type DetailComponent interface {
- tea.Model
- layout.Sizeable
- layout.Bindings
- }
- type detailCmp struct {
- width, height int
- currentLog logging.LogMessage
- viewport viewport.Model
- }
- func (i *detailCmp) Init() tea.Cmd {
- messages := logging.List()
- if len(messages) == 0 {
- return nil
- }
- i.currentLog = messages[0]
- return nil
- }
- func (i *detailCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- switch msg := msg.(type) {
- case selectedLogMsg:
- if msg.ID != i.currentLog.ID {
- i.currentLog = logging.LogMessage(msg)
- i.updateContent()
- }
- }
- return i, nil
- }
- func (i *detailCmp) updateContent() {
- var content strings.Builder
- t := theme.CurrentTheme()
- // Format the header with timestamp and level
- timeStyle := lipgloss.NewStyle().Foreground(t.TextMuted())
- levelStyle := getLevelStyle(i.currentLog.Level)
- header := lipgloss.JoinHorizontal(
- lipgloss.Center,
- timeStyle.Render(i.currentLog.Time.Format(time.RFC3339)),
- " ",
- levelStyle.Render(i.currentLog.Level),
- )
- content.WriteString(lipgloss.NewStyle().Bold(true).Render(header))
- content.WriteString("\n\n")
- // Message with styling
- messageStyle := lipgloss.NewStyle().Bold(true).Foreground(t.Text())
- content.WriteString(messageStyle.Render("Message:"))
- content.WriteString("\n")
- content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(i.currentLog.Message))
- content.WriteString("\n\n")
- // Attributes section
- if len(i.currentLog.Attributes) > 0 {
- attrHeaderStyle := lipgloss.NewStyle().Bold(true).Foreground(t.Text())
- content.WriteString(attrHeaderStyle.Render("Attributes:"))
- content.WriteString("\n")
- // Create a table-like display for attributes
- keyStyle := lipgloss.NewStyle().Foreground(t.Primary()).Bold(true)
- valueStyle := lipgloss.NewStyle().Foreground(t.Text())
- for _, attr := range i.currentLog.Attributes {
- attrLine := fmt.Sprintf("%s: %s",
- keyStyle.Render(attr.Key),
- valueStyle.Render(attr.Value),
- )
- content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(attrLine))
- content.WriteString("\n")
- }
- }
- i.viewport.SetContent(content.String())
- }
- func getLevelStyle(level string) lipgloss.Style {
- style := lipgloss.NewStyle().Bold(true)
- t := theme.CurrentTheme()
-
- switch strings.ToLower(level) {
- case "info":
- return style.Foreground(t.Info())
- case "warn", "warning":
- return style.Foreground(t.Warning())
- case "error", "err":
- return style.Foreground(t.Error())
- case "debug":
- return style.Foreground(t.Success())
- default:
- return style.Foreground(t.Text())
- }
- }
- func (i *detailCmp) View() string {
- t := theme.CurrentTheme()
- return styles.ForceReplaceBackgroundWithLipgloss(i.viewport.View(), t.Background())
- }
- func (i *detailCmp) GetSize() (int, int) {
- return i.width, i.height
- }
- func (i *detailCmp) SetSize(width int, height int) tea.Cmd {
- i.width = width
- i.height = height
- i.viewport.Width = i.width
- i.viewport.Height = i.height
- i.updateContent()
- return nil
- }
- func (i *detailCmp) BindingKeys() []key.Binding {
- return layout.KeyMapToSlice(i.viewport.KeyMap)
- }
- func NewLogsDetails() DetailComponent {
- return &detailCmp{
- viewport: viewport.New(0, 0),
- }
- }
|