on main help sign in
chore: initial logs changes
commit3b840ada2e1c35d2831df7e49571ad5d8269bd64 authorKujtim Hoxha <kujtimii.h@gmail.com> dateMon 9 Jun 2025, 15:57 (1 year ago) parentf44035e
12 files changed+270−114
internal/config/init.go+2−2 internal/tui/components/chat/editor/editor.go−1 internal/tui/components/core/helpers.go+13−13 internal/tui/components/dialogs/compact/compact.go−1 internal/tui/components/dialogs/compact/keys.go+1−1 internal/tui/components/logs/details.go+66−25 internal/tui/components/logs/table.go+111−44 internal/tui/page/chat/chat.go−2 internal/tui/page/logs/keys.go+37 internal/tui/page/logs.go → internal/tui/page/logs/logs.go+30−18 internal/tui/tui.go+4−4 todos.md+6−3
internal/config/init.go+2−2
@@ -63,13 +63,13 @@ func crushMdExists(dir string) (bool, error) {
6363 if entry.IsDir() {
6464 continue
6565 }
66−
66+
6767 name := strings.ToLower(entry.Name())
6868 if name == "crush.md" {
6969 return true, nil
7070 }
7171 }
72−
72+
7373 return false, nil
7474}
7575
internal/tui/components/chat/editor/editor.go−1
@@ -360,7 +360,6 @@ func (c *editorCmp) Blur() tea.Cmd {
360360
361361// Focus implements Container.
362362func (c *editorCmp) Focus() tea.Cmd {
363− logging.Info("Focusing editor textarea")
364363 return c.textarea.Focus()
365364}
366365
internal/tui/components/core/helpers.go+13−13
@@ -78,39 +78,39 @@ func Status(ops StatusOpts, width int) string {
7878}
7979
8080type ButtonOpts struct {
81− Text string
82− UnderlineIndex int // Index of character to underline (0-based)
83− Selected bool // Whether this button is selected
81+ Text string
82+ UnderlineIndex int // Index of character to underline (0-based)
83+ Selected bool // Whether this button is selected
8484}
8585
8686// SelectableButton creates a button with an underlined character and selection state
8787func SelectableButton(opts ButtonOpts) string {
8888 t := styles.CurrentTheme()
89−
89+
9090 // Base style for the button
9191 buttonStyle := t.S().Text
92−
92+
9393 // Apply selection styling
9494 if opts.Selected {
9595 buttonStyle = buttonStyle.Foreground(t.White).Background(t.Secondary)
9696 } else {
9797 buttonStyle = buttonStyle.Background(t.BgSubtle)
9898 }
99−
99+
100100 // Create the button text with underlined character
101101 text := opts.Text
102102 if opts.UnderlineIndex >= 0 && opts.UnderlineIndex < len(text) {
103103 before := text[:opts.UnderlineIndex]
104104 underlined := text[opts.UnderlineIndex : opts.UnderlineIndex+1]
105105 after := text[opts.UnderlineIndex+1:]
106−
107− message := buttonStyle.Render(before) +
108− buttonStyle.Underline(true).Render(underlined) +
106+
107+ message := buttonStyle.Render(before) +
108+ buttonStyle.Underline(true).Render(underlined) +
109109 buttonStyle.Render(after)
110−
110+
111111 return buttonStyle.Padding(0, 2).Render(message)
112112 }
113−
113+
114114 // Fallback if no underline index specified
115115 return buttonStyle.Padding(0, 2).Render(text)
116116}
@@ -120,7 +120,7 @@ func SelectableButtons(buttons []ButtonOpts, spacing string) string {
120120 if spacing == "" {
121121 spacing = " "
122122 }
123−
123+
124124 var parts []string
125125 for i, button := range buttons {
126126 parts = append(parts, SelectableButton(button))
@@ -128,6 +128,6 @@ func SelectableButtons(buttons []ButtonOpts, spacing string) string {
128128 parts = append(parts, spacing)
129129 }
130130 }
131−
131+
132132 return lipgloss.JoinHorizontal(lipgloss.Left, parts...)
133133}
internal/tui/components/dialogs/compact/compact.go−1
@@ -263,4 +263,3 @@ func (c *compactDialogCmp) Position() (int, int) {
263263func (c *compactDialogCmp) ID() dialogs.DialogID {
264264 return CompactDialogID
265265}
266−
internal/tui/components/dialogs/compact/keys.go+1−1
@@ -58,4 +58,4 @@ func (k KeyMap) ShortHelp() []key.Binding {
5858 k.Select,
5959 k.Close,
6060 }
61−}
61+}
internal/tui/components/logs/details.go+66−25
@@ -52,43 +52,55 @@ func (i *detailCmp) updateContent() {
5252 var content strings.Builder
5353 t := styles.CurrentTheme()
5454
55− // Format the header with timestamp and level
56− timeStyle := t.S().Muted
55+ if i.currentLog.ID == "" {
56+ content.WriteString(t.S().Muted.Render("No log selected"))
57+ i.viewport.SetContent(content.String())
58+ return
59+ }
60+
61+ // Level badge with background color
5762 levelStyle := getLevelStyle(i.currentLog.Level)
63+ levelBadge := levelStyle.Padding(0, 1).Render(strings.ToUpper(i.currentLog.Level))
5864
65+ // Timestamp with relative time
66+ timeStr := i.currentLog.Time.Format("2006-01-05 15:04:05 UTC")
67+ relativeTime := getRelativeTime(i.currentLog.Time)
68+ timeStyle := t.S().Muted
69+
70+ // Header line
5971 header := lipgloss.JoinHorizontal(
60− lipgloss.Center,
61− timeStyle.Render(i.currentLog.Time.Format(time.RFC3339)),
62− " ",
63− levelStyle.Render(i.currentLog.Level),
72+ lipgloss.Left,
73+ timeStr,
74+ " ",
75+ timeStyle.Render(relativeTime),
6476 )
6577
66− content.WriteString(lipgloss.NewStyle().Bold(true).Render(header))
78+ content.WriteString(levelBadge)
79+ content.WriteString("\n\n")
80+ content.WriteString(header)
6781 content.WriteString("\n\n")
6882
69− // Message with styling
70− messageStyle := t.S().Text.Bold(true)
71− content.WriteString(messageStyle.Render("Message:"))
83+ // Message section
84+ messageHeaderStyle := t.S().Base.Foreground(t.Blue).Bold(true)
85+ content.WriteString(messageHeaderStyle.Render("Message"))
7286 content.WriteString("\n")
73− content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(i.currentLog.Message))
87+ content.WriteString(i.currentLog.Message)
7488 content.WriteString("\n\n")
7589
7690 // Attributes section
7791 if len(i.currentLog.Attributes) > 0 {
78− attrHeaderStyle := t.S().Text.Bold(true)
79− content.WriteString(attrHeaderStyle.Render("Attributes:"))
92+ attrHeaderStyle := t.S().Base.Foreground(t.Blue).Bold(true)
93+ content.WriteString(attrHeaderStyle.Render("Attributes"))
8094 content.WriteString("\n")
8195
82− // Create a table-like display for attributes
83− keyStyle := t.S().Base.Foreground(t.Primary).Bold(true)
84− valueStyle := t.S().Text
85−
8696 for _, attr := range i.currentLog.Attributes {
97+ keyStyle := t.S().Base.Foreground(t.Accent)
98+ valueStyle := t.S().Text
8799 attrLine := fmt.Sprintf("%s: %s",
88100 keyStyle.Render(attr.Key),
89101 valueStyle.Render(attr.Value),
90102 )
91− content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(attrLine))
103+ content.WriteString(attrLine)
92104 content.WriteString("\n")
93105 }
94106 }
@@ -102,20 +114,48 @@ func getLevelStyle(level string) lipgloss.Style {
102114
103115 switch strings.ToLower(level) {
104116 case "info":
105− return style.Foreground(t.Info)
117+ return style.Foreground(t.White).Background(t.Info)
106118 case "warn", "warning":
107− return style.Foreground(t.Warning)
119+ return style.Foreground(t.White).Background(t.Warning)
108120 case "error", "err":
109− return style.Foreground(t.Error)
121+ return style.Foreground(t.White).Background(t.Error)
110122 case "debug":
111− return style.Foreground(t.Success)
123+ return style.Foreground(t.White).Background(t.Success)
124+ case "fatal":
125+ return style.Foreground(t.White).Background(t.Error)
112126 default:
113127 return style.Foreground(t.FgBase)
114128 }
115129}
116130
131+func getRelativeTime(logTime time.Time) string {
132+ now := time.Now()
133+ diff := now.Sub(logTime)
134+
135+ if diff < time.Minute {
136+ return fmt.Sprintf("%ds ago", int(diff.Seconds()))
137+ } else if diff < time.Hour {
138+ return fmt.Sprintf("%dm ago", int(diff.Minutes()))
139+ } else if diff < 24*time.Hour {
140+ return fmt.Sprintf("%dh ago", int(diff.Hours()))
141+ } else if diff < 30*24*time.Hour {
142+ return fmt.Sprintf("%dd ago", int(diff.Hours()/24))
143+ } else if diff < 365*24*time.Hour {
144+ return fmt.Sprintf("%dmo ago", int(diff.Hours()/(24*30)))
145+ } else {
146+ return fmt.Sprintf("%dy ago", int(diff.Hours()/(24*365)))
147+ }
148+}
149+
117150func (i *detailCmp) View() tea.View {
118− return tea.NewView(i.viewport.View())
151+ t := styles.CurrentTheme()
152+ style := t.S().Base.
153+ BorderStyle(lipgloss.RoundedBorder()).
154+ BorderForeground(t.BorderFocus).
155+ Width(i.width - 2). // Adjust width for border
156+ Height(i.height - 2). // Adjust height for border
157+ Padding(1)
158+ return tea.NewView(style.Render(i.viewport.View()))
119159}
120160
121161func (i *detailCmp) GetSize() (int, int) {
@@ -123,10 +163,11 @@ func (i *detailCmp) GetSize() (int, int) {
123163}
124164
125165func (i *detailCmp) SetSize(width int, height int) tea.Cmd {
166+ logging.Info("Setting size for detail component", "width", width, "height", height)
126167 i.width = width
127168 i.height = height
128− i.viewport.SetWidth(i.width)
129− i.viewport.SetHeight(i.height)
169+ i.viewport.SetWidth(i.width - 4)
170+ i.viewport.SetHeight(i.height - 4)
130171 i.updateContent()
131172 return nil
132173}
internal/tui/components/logs/table.go+111−44
@@ -1,8 +1,9 @@
11package logs
22
33import (
4− "encoding/json"
4+ "fmt"
55 "slices"
6+ "strings"
67
78 "github.com/charmbracelet/bubbles/v2/key"
89 "github.com/charmbracelet/bubbles/v2/table"
@@ -12,6 +13,7 @@ import (
1213 "github.com/charmbracelet/crush/internal/tui/layout"
1314 "github.com/charmbracelet/crush/internal/tui/styles"
1415 "github.com/charmbracelet/crush/internal/tui/util"
16+ "github.com/charmbracelet/lipgloss/v2"
1517)
1618
1719type TableComponent interface {
@@ -22,48 +24,80 @@ type TableComponent interface {
2224
2325type tableCmp struct {
2426 table table.Model
27+ logs []logging.LogMessage
2528}
2629
2730type selectedLogMsg logging.LogMessage
2831
2932func (i *tableCmp) Init() tea.Cmd {
33+ i.logs = logging.List()
3034 i.setRows()
3135 return nil
3236}
3337
3438func (i *tableCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
3539 var cmds []tea.Cmd
36− switch msg.(type) {
40+ switch msg := msg.(type) {
3741 case pubsub.Event[logging.LogMessage]:
38− i.setRows()
39− return i, nil
42+ return i, func() tea.Msg {
43+ if msg.Type == pubsub.CreatedEvent {
44+ rows := i.table.Rows()
45+ for _, row := range rows {
46+ if row[1] == msg.Payload.ID {
47+ return nil // If the log already exists, do not add it again
48+ }
49+ }
50+ i.logs = append(i.logs, msg.Payload)
51+ i.table.SetRows(
52+ append(
53+ []table.Row{
54+ logToRow(msg.Payload),
55+ },
56+ i.table.Rows()...,
57+ ),
58+ )
59+ }
60+ return selectedLogMsg(msg.Payload)
61+ }
4062 }
41− prevSelectedRow := i.table.SelectedRow()
4263 t, cmd := i.table.Update(msg)
4364 cmds = append(cmds, cmd)
4465 i.table = t
45− selectedRow := i.table.SelectedRow()
46− if selectedRow != nil {
47− if prevSelectedRow == nil || selectedRow[0] == prevSelectedRow[0] {
48− var log logging.LogMessage
49− for _, row := range logging.List() {
50− if row.ID == selectedRow[0] {
51− log = row
52− break
53− }
54− }
55− if log.ID != "" {
56− cmds = append(cmds, util.CmdHandler(selectedLogMsg(log)))
66+
67+ cmds = append(cmds, func() tea.Msg {
68+ for _, log := range logging.List() {
69+ if log.ID == i.table.SelectedRow()[1] {
70+ // If the selected row matches the log ID, return the selected log message
71+ return selectedLogMsg(log)
5772 }
5873 }
59− }
74+ return nil
75+ })
6076 return i, tea.Batch(cmds...)
6177}
6278
6379func (i *tableCmp) View() tea.View {
6480 t := styles.CurrentTheme()
6581 defaultStyles := table.DefaultStyles()
66− defaultStyles.Selected = defaultStyles.Selected.Foreground(t.Primary)
82+
83+ // Header styling
84+ defaultStyles.Header = defaultStyles.Header.
85+ Foreground(t.Primary).
86+ Bold(true).
87+ BorderStyle(lipgloss.NormalBorder()).
88+ BorderBottom(true).
89+ BorderForeground(t.Border)
90+
91+ // Selected row styling
92+ defaultStyles.Selected = defaultStyles.Selected.
93+ Foreground(t.FgSelected).
94+ Background(t.Primary).
95+ Bold(false)
96+
97+ // Cell styling
98+ defaultStyles.Cell = defaultStyles.Cell.
99+ Foreground(t.FgBase)
100+
67101 i.table.SetStyles(defaultStyles)
68102 return tea.NewView(i.table.View())
69103}
@@ -75,12 +109,30 @@ func (i *tableCmp) GetSize() (int, int) {
75109func (i *tableCmp) SetSize(width int, height int) tea.Cmd {
76110 i.table.SetWidth(width)
77111 i.table.SetHeight(height)
78− cloumns := i.table.Columns()
79− for i, col := range cloumns {
80− col.Width = (width / len(cloumns)) - 2
81− cloumns[i] = col
82− }
83− i.table.SetColumns(cloumns)
112+
113+ columnWidth := (width - 10) / 4
114+ i.table.SetColumns([]table.Column{
115+ {
116+ Title: "Level",
117+ Width: 10,
118+ },
119+ {
120+ Title: "ID",
121+ Width: columnWidth,
122+ },
123+ {
124+ Title: "Time",
125+ Width: columnWidth,
126+ },
127+ {
128+ Title: "Message",
129+ Width: columnWidth,
130+ },
131+ {
132+ Title: "Attributes",
133+ Width: columnWidth,
134+ },
135+ })
84136 return nil
85137}
86138
@@ -91,39 +143,54 @@ func (i *tableCmp) BindingKeys() []key.Binding {
91143func (i *tableCmp) setRows() {
92144 rows := []table.Row{}
93145
94− logs := logging.List()
95− slices.SortFunc(logs, func(a, b logging.LogMessage) int {
146+ slices.SortFunc(i.logs, func(a, b logging.LogMessage) int {
96147 if a.Time.Before(b.Time) {
97− return 1
148+ return -1
98149 }
99150 if a.Time.After(b.Time) {
100− return -1
151+ return 1
101152 }
102153 return 0
103154 })
104155
105− for _, log := range logs {
106− bm, _ := json.Marshal(log.Attributes)
156+ for _, log := range i.logs {
157+ rows = append(rows, logToRow(log))
158+ }
159+ i.table.SetRows(rows)
160+}
107161
108− row := table.Row{
109− log.ID,
110− log.Time.Format("15:04:05"),
111− log.Level,
112− log.Message,
113− string(bm),
162+func logToRow(log logging.LogMessage) table.Row {
163+ // Format attributes as JSON string
164+ var attrStr string
165+ if len(log.Attributes) > 0 {
166+ var parts []string
167+ for _, attr := range log.Attributes {
168+ parts = append(parts, fmt.Sprintf(`{"Key":"%s","Value":"%s"}`, attr.Key, attr.Value))
114169 }
115− rows = append(rows, row)
170+ attrStr = "[" + strings.Join(parts, ",") + "]"
171+ }
172+
173+ // Format time with relative time
174+ timeStr := log.Time.Format("2006-01-05 15:04:05 UTC")
175+ relativeTime := getRelativeTime(log.Time)
176+ fullTimeStr := timeStr + " " + relativeTime
177+
178+ return table.Row{
179+ strings.ToUpper(log.Level),
180+ log.ID,
181+ fullTimeStr,
182+ log.Message,
183+ attrStr,
116184 }
117− i.table.SetRows(rows)
118185}
119186
120187func NewLogsTable() TableComponent {
121188 columns := []table.Column{
122− {Title: "ID", Width: 4},
123− {Title: "Time", Width: 4},
124− {Title: "Level", Width: 10},
125− {Title: "Message", Width: 10},
126− {Title: "Attributes", Width: 10},
189+ {Title: "Level"},
190+ {Title: "ID"},
191+ {Title: "Time"},
192+ {Title: "Message"},
193+ {Title: "Attributes"},
127194 }
128195
129196 tableModel := table.New(
internal/tui/page/chat/chat.go−2
@@ -8,7 +8,6 @@ import (
88 "github.com/charmbracelet/crush/internal/app"
99 "github.com/charmbracelet/crush/internal/config"
1010 "github.com/charmbracelet/crush/internal/llm/models"
11− "github.com/charmbracelet/crush/internal/logging"
1211 "github.com/charmbracelet/crush/internal/message"
1312 "github.com/charmbracelet/crush/internal/session"
1413 "github.com/charmbracelet/crush/internal/tui/components/chat"
@@ -99,7 +98,6 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
9998 return p, util.ReportWarn("File attachments are not supported by the current model: " + string(selectedModelID))
10099 }
101100 case key.Matches(msg, p.keyMap.Tab):
102− logging.Info("Tab key pressed, toggling chat focus")
103101 if p.session.ID == "" {
104102 return p, nil
105103 }
internal/tui/page/logs/keys.gonew file+37
@@ -0,0 +1,37 @@
1+package logs
2+
3+import (
4+ "github.com/charmbracelet/bubbles/v2/key"
5+ "github.com/charmbracelet/crush/internal/tui/layout"
6+)
7+
8+type KeyMap struct {
9+ Back key.Binding
10+}
11+
12+func DefaultKeyMap() KeyMap {
13+ return KeyMap{
14+ Back: key.NewBinding(
15+ key.WithKeys("esc", "backspace"),
16+ key.WithHelp("esc/backspace", "back to chat"),
17+ ),
18+ }
19+}
20+
21+// FullHelp implements help.KeyMap.
22+func (k KeyMap) FullHelp() [][]key.Binding {
23+ m := [][]key.Binding{}
24+ slice := layout.KeyMapToSlice(k)
25+ for i := 0; i < len(slice); i += 4 {
26+ end := min(i+4, len(slice))
27+ m = append(m, slice[i:end])
28+ }
29+ return m
30+}
31+
32+// ShortHelp implements help.KeyMap.
33+func (k KeyMap) ShortHelp() []key.Binding {
34+ return []key.Binding{
35+ k.Back,
36+ }
37+}
internal/tui/page/logs.go → internal/tui/page/logs/logs.gorenamed+30−18
@@ -1,26 +1,30 @@
1−package page
1+package logs
22
33import (
44 "github.com/charmbracelet/bubbles/v2/key"
55 tea "github.com/charmbracelet/bubbletea/v2"
6− "github.com/charmbracelet/crush/internal/tui/components/logs"
6+ "github.com/charmbracelet/crush/internal/tui/components/core"
7+ logsComponents "github.com/charmbracelet/crush/internal/tui/components/logs"
78 "github.com/charmbracelet/crush/internal/tui/layout"
9+ "github.com/charmbracelet/crush/internal/tui/page"
10+ "github.com/charmbracelet/crush/internal/tui/page/chat"
811 "github.com/charmbracelet/crush/internal/tui/styles"
912 "github.com/charmbracelet/crush/internal/tui/util"
1013 "github.com/charmbracelet/lipgloss/v2"
1114)
1215
13−var LogsPage PageID = "logs"
16+var LogsPage page.PageID = "logs"
1417
1518type LogPage interface {
1619 util.Model
1720 layout.Sizeable
18− layout.Bindings
1921}
22+
2023type logsPage struct {
2124 width, height int
22− table layout.Container
23− details layout.Container
25+ table logsComponents.TableComponent
26+ details logsComponents.DetailComponent
27+ keyMap KeyMap
2428}
2529
2630func (p *logsPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@@ -30,34 +34,39 @@ func (p *logsPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
3034 p.width = msg.Width
3135 p.height = msg.Height
3236 return p, p.SetSize(msg.Width, msg.Height)
37+ case tea.KeyMsg:
38+ switch {
39+ case key.Matches(msg, p.keyMap.Back):
40+ return p, util.CmdHandler(page.PageChangeMsg{ID: chat.ChatPage})
41+ }
3342 }
3443
3544 table, cmd := p.table.Update(msg)
3645 cmds = append(cmds, cmd)
37− p.table = table.(layout.Container)
46+ p.table = table.(logsComponents.TableComponent)
3847 details, cmd := p.details.Update(msg)
3948 cmds = append(cmds, cmd)
40− p.details = details.(layout.Container)
49+ p.details = details.(logsComponents.DetailComponent)
4150
4251 return p, tea.Batch(cmds...)
4352}
4453
4554func (p *logsPage) View() tea.View {
46− style := styles.CurrentTheme().S().Base.Width(p.width).Height(p.height)
55+ baseStyle := styles.CurrentTheme().S().Base
56+ style := baseStyle.Width(p.width).Height(p.height).Padding(1)
57+ title := core.Title("Logs", p.width-2)
58+
4759 return tea.NewView(
4860 style.Render(
4961 lipgloss.JoinVertical(lipgloss.Top,
50− p.table.View().String(),
62+ title,
5163 p.details.View().String(),
64+ p.table.View().String(),
5265 ),
5366 ),
5467 )
5568}
5669
57−func (p *logsPage) BindingKeys() []key.Binding {
58− return p.table.BindingKeys()
59−}
60−
6170// GetSize implements LogPage.
6271func (p *logsPage) GetSize() (int, int) {
6372 return p.width, p.height
@@ -67,9 +76,11 @@ func (p *logsPage) GetSize() (int, int) {
6776func (p *logsPage) SetSize(width int, height int) tea.Cmd {
6877 p.width = width
6978 p.height = height
79+ availableHeight := height - 2 // Padding for top and bottom
80+ availableHeight -= 1 // title height
7081 return tea.Batch(
71− p.table.SetSize(width, height/2),
72− p.details.SetSize(width, height/2),
82+ p.table.SetSize(width-2, availableHeight/2),
83+ p.details.SetSize(width-2, availableHeight/2),
7384 )
7485}
7586
@@ -82,7 +93,8 @@ func (p *logsPage) Init() tea.Cmd {
8293
8394func NewLogsPage() LogPage {
8495 return &logsPage{
85− table: layout.NewContainer(logs.NewLogsTable(), layout.WithBorderAll()),
86− details: layout.NewContainer(logs.NewLogsDetails(), layout.WithBorderAll()),
96+ details: logsComponents.NewLogsDetails(),
97+ table: logsComponents.NewLogsTable(),
98+ keyMap: DefaultKeyMap(),
8799 }
88100}
internal/tui/tui.go+4−4
@@ -27,6 +27,7 @@ import (
2727 "github.com/charmbracelet/crush/internal/tui/layout"
2828 "github.com/charmbracelet/crush/internal/tui/page"
2929 "github.com/charmbracelet/crush/internal/tui/page/chat"
30+ "github.com/charmbracelet/crush/internal/tui/page/logs"
3031 "github.com/charmbracelet/crush/internal/tui/styles"
3132 "github.com/charmbracelet/crush/internal/tui/util"
3233 "github.com/charmbracelet/lipgloss/v2"
@@ -137,7 +138,7 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
137138 cmds = append(cmds, statusCmd)
138139
139140 // If the current page is logs, update the logs view
140− if a.currentPage == page.LogsPage {
141+ if a.currentPage == logs.LogsPage {
141142 updated, pageCmd := a.pages[a.currentPage].Update(msg)
142143 a.pages[a.currentPage] = updated.(util.Model)
143144 cmds = append(cmds, pageCmd)
@@ -328,7 +329,7 @@ func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
328329 return tea.Sequence(cmds...)
329330 // Page navigation
330331 case key.Matches(msg, a.keyMap.Logs):
331− return a.moveToPage(page.LogsPage)
332+ return a.moveToPage(logs.LogsPage)
332333
333334 default:
334335 if a.dialog.HasDialogs() {
@@ -379,7 +380,6 @@ func (a *appModel) View() tea.View {
379380 lipgloss.NewLayer(appView),
380381 }
381382 if a.dialog.HasDialogs() {
382− logging.Info("Rendering dialogs")
383383 layers = append(
384384 layers,
385385 a.dialog.GetLayers()...,
@@ -424,7 +424,7 @@ func New(app *app.App) tea.Model {
424424
425425 pages: map[page.PageID]util.Model{
426426 chat.ChatPage: chat.NewChatPage(app),
427− page.LogsPage: page.NewLogsPage(),
427+ logs.LogsPage: logs.NewLogsPage(),
428428 },
429429
430430 dialog: dialogs.NewDialogCmp(),
todos.md+6−3
@@ -11,9 +11,9 @@
1111
1212- [x] Cleanup Commands
1313- [x] Sessions dialog
14−- [ ] Models
15−- [~] Move sessions and model dialog to the commands
16−- [ ] Add sessions shortuct
14+- [x] Models
15+- [x] Move sessions and model dialog to the commands
16+- [x] Add sessions shortuct
1717- [ ] Add all posible actions to the commands
1818
1919## Investigate
@@ -24,3 +24,6 @@
2424## Messages
2525
2626- [ ] Fix issue with numbers (padding)
27+- [ ] Run tools in parallel and add the responses in parallel
28+- [ ] Handle parallel permission calls
29+- [ ] Weird behavior sometimes the message does not update