diff options
| author | Kujtim Hoxha <[email protected]> | 2025-04-09 19:07:39 +0200 |
|---|---|---|
| committer | Kujtim Hoxha <[email protected]> | 2025-04-09 19:07:39 +0200 |
| commit | d39d52d95d6aaab67fb3a17efb9ed62cc290e72f (patch) | |
| tree | 940bad6f0c847ceb213e5fc684b6d87cbf9d6996 /internal/tui/components | |
| parent | 0d8d324ac6e640b95f4f2f62fd189399a959319a (diff) | |
| download | opencode-d39d52d95d6aaab67fb3a17efb9ed62cc290e72f.tar.gz opencode-d39d52d95d6aaab67fb3a17efb9ed62cc290e72f.zip | |
finish logs page
Diffstat (limited to 'internal/tui/components')
| -rw-r--r-- | internal/tui/components/core/status.go | 24 | ||||
| -rw-r--r-- | internal/tui/components/logs/details.go | 176 | ||||
| -rw-r--r-- | internal/tui/components/logs/table.go | 38 |
3 files changed, 218 insertions, 20 deletions
diff --git a/internal/tui/components/core/status.go b/internal/tui/components/core/status.go index 83ef07fe3..6c4dda0a4 100644 --- a/internal/tui/components/core/status.go +++ b/internal/tui/components/core/status.go @@ -7,7 +7,6 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/kujtimiihoxha/termai/internal/config" "github.com/kujtimiihoxha/termai/internal/llm/models" - "github.com/kujtimiihoxha/termai/internal/pubsub" "github.com/kujtimiihoxha/termai/internal/tui/styles" "github.com/kujtimiihoxha/termai/internal/tui/util" "github.com/kujtimiihoxha/termai/internal/version" @@ -20,8 +19,8 @@ type statusCmp struct { } // clearMessageCmd is a command that clears status messages after a timeout -func (m statusCmp) clearMessageCmd() tea.Cmd { - return tea.Tick(m.messageTTL, func(time.Time) tea.Msg { +func (m statusCmp) clearMessageCmd(ttl time.Duration) tea.Cmd { + return tea.Tick(ttl, func(time.Time) tea.Msg { return util.ClearStatusMsg{} }) } @@ -34,13 +33,14 @@ func (m statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: m.width = msg.Width - return m, m.clearMessageCmd() - case pubsub.Event[util.InfoMsg]: - m.info = &msg.Payload - return m, m.clearMessageCmd() + return m, nil case util.InfoMsg: m.info = &msg - return m, m.clearMessageCmd() + ttl := msg.TTL + if ttl == 0 { + ttl = m.messageTTL + } + return m, m.clearMessageCmd(ttl) case util.ClearStatusMsg: m.info = nil } @@ -66,7 +66,13 @@ func (m statusCmp) View() string { case util.InfoTypeError: infoStyle = infoStyle.Background(styles.Red) } - status += infoStyle.Render(m.info.Msg) + // Truncate message if it's longer than available width + msg := m.info.Msg + availWidth := m.availableFooterMsgWidth() - 3 // Account for ellipsis + if len(msg) > availWidth && availWidth > 0 { + msg = msg[:availWidth] + "..." + } + status += infoStyle.Render(msg) } else { status += styles.Padded. Foreground(styles.Base). diff --git a/internal/tui/components/logs/details.go b/internal/tui/components/logs/details.go new file mode 100644 index 000000000..e02dfd9cc --- /dev/null +++ b/internal/tui/components/logs/details.go @@ -0,0 +1,176 @@ +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/kujtimiihoxha/termai/internal/logging" + "github.com/kujtimiihoxha/termai/internal/tui/layout" + "github.com/kujtimiihoxha/termai/internal/tui/styles" +) + +type DetailComponent interface { + tea.Model + layout.Focusable + layout.Sizeable + layout.Bindings + layout.Bordered +} + +type detailCmp struct { + width, height int + focused bool + currentLog logging.LogMessage + viewport viewport.Model +} + +func (i *detailCmp) Init() tea.Cmd { + messages := logging.Get().List() + if len(messages) == 0 { + return nil + } + i.currentLog = messages[0] + return nil +} + +func (i *detailCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var ( + cmd tea.Cmd + cmds []tea.Cmd + ) + + switch msg := msg.(type) { + case selectedLogMsg: + if msg.ID != i.currentLog.ID { + i.currentLog = logging.LogMessage(msg) + i.updateContent() + } + } + + if i.focused { + i.viewport, cmd = i.viewport.Update(msg) + cmds = append(cmds, cmd) + } + + return i, tea.Batch(cmds...) +} + +func (i *detailCmp) updateContent() { + var content strings.Builder + + // Format the header with timestamp and level + timeStyle := lipgloss.NewStyle().Foreground(styles.SubText0) + 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(styles.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(styles.Text) + content.WriteString(attrHeaderStyle.Render("Attributes:")) + content.WriteString("\n") + + // Create a table-like display for attributes + keyStyle := lipgloss.NewStyle().Foreground(styles.Primary).Bold(true) + valueStyle := lipgloss.NewStyle().Foreground(styles.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) + + switch strings.ToLower(level) { + case "info": + return style.Foreground(styles.Blue) + case "warn", "warning": + return style.Foreground(styles.Warning) + case "error", "err": + return style.Foreground(styles.Error) + case "debug": + return style.Foreground(styles.Green) + default: + return style.Foreground(styles.Text) + } +} + +func (i *detailCmp) View() string { + return i.viewport.View() +} + +func (i *detailCmp) Blur() tea.Cmd { + i.focused = false + return nil +} + +func (i *detailCmp) Focus() tea.Cmd { + i.focused = true + return nil +} + +func (i *detailCmp) IsFocused() bool { + return i.focused +} + +func (i *detailCmp) GetSize() (int, int) { + return i.width, i.height +} + +func (i *detailCmp) SetSize(width int, height int) { + i.width = width + i.height = height + i.viewport.Width = i.width + i.viewport.Height = i.height + i.updateContent() +} + +func (i *detailCmp) BindingKeys() []key.Binding { + return []key.Binding{ + i.viewport.KeyMap.PageDown, + i.viewport.KeyMap.PageUp, + i.viewport.KeyMap.HalfPageDown, + i.viewport.KeyMap.HalfPageUp, + } +} + +func (i *detailCmp) BorderText() map[layout.BorderPosition]string { + return map[layout.BorderPosition]string{ + layout.TopLeftBorder: "Log Details", + } +} + +func NewLogsDetails() DetailComponent { + return &detailCmp{ + viewport: viewport.New(0, 0), + } +} diff --git a/internal/tui/components/logs/table.go b/internal/tui/components/logs/table.go index c268fc686..e84b4149c 100644 --- a/internal/tui/components/logs/table.go +++ b/internal/tui/components/logs/table.go @@ -11,6 +11,7 @@ import ( "github.com/kujtimiihoxha/termai/internal/pubsub" "github.com/kujtimiihoxha/termai/internal/tui/layout" "github.com/kujtimiihoxha/termai/internal/tui/styles" + "github.com/kujtimiihoxha/termai/internal/tui/util" ) type TableComponent interface { @@ -26,29 +27,42 @@ type tableCmp struct { table table.Model } +type selectedLogMsg logging.LogMessage + func (i *tableCmp) Init() tea.Cmd { i.setRows() return nil } func (i *tableCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd if i.table.Focused() { - switch msg := msg.(type) { - case pubsub.Event[logging.Message]: + switch msg.(type) { + case pubsub.Event[logging.LogMessage]: i.setRows() return i, nil - case tea.KeyMsg: - if msg.String() == "ctrl+s" { - logger.Info("Saving logs...", - "rows", len(i.table.Rows()), - ) - } } + prevSelectedRow := i.table.SelectedRow() t, cmd := i.table.Update(msg) + cmds = append(cmds, cmd) i.table = t - return i, cmd + selectedRow := i.table.SelectedRow() + if selectedRow != nil { + if prevSelectedRow == nil || selectedRow[0] == prevSelectedRow[0] { + var log logging.LogMessage + for _, row := range logging.Get().List() { + if row.ID == selectedRow[0] { + log = row + break + } + } + if log.ID != "" { + cmds = append(cmds, util.CmdHandler(selectedLogMsg(log))) + } + } + } } - return i, nil + return i, tea.Batch(cmds...) } func (i *tableCmp) View() string { @@ -92,7 +106,7 @@ func (i *tableCmp) setRows() { rows := []table.Row{} logs := logger.List() - slices.SortFunc(logs, func(a, b logging.Message) int { + slices.SortFunc(logs, func(a, b logging.LogMessage) int { if a.Time.Before(b.Time) { return 1 } @@ -106,6 +120,7 @@ func (i *tableCmp) setRows() { bm, _ := json.Marshal(log.Attributes) row := table.Row{ + log.ID, log.Time.Format("15:04:05"), log.Level, log.Message, @@ -118,6 +133,7 @@ func (i *tableCmp) setRows() { func NewLogsTable() TableComponent { columns := []table.Column{ + {Title: "ID", Width: 4}, {Title: "Time", Width: 4}, {Title: "Level", Width: 10}, {Title: "Message", Width: 10}, |
