lmcli/pkg/tui/conversations.go

190 lines
4.7 KiB
Go
Raw Normal View History

package tui
import (
"fmt"
"slices"
"strings"
"time"
models "git.mlow.ca/mlow/lmcli/pkg/lmcli/model"
"git.mlow.ca/mlow/lmcli/pkg/util"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type (
// send when conversation list is loaded
msgConversationsLoaded []models.Conversation
)
func (m *model) handleConversationsInput(msg tea.KeyMsg) tea.Cmd {
switch msg.String() {
case "enter":
m.state = stateChat
m.updateContent()
// load selected conversation and switch state
case "n":
// new conversation
case "d":
// show prompt to delete conversation
case "c":
// copy/clone conversation
case "r":
// show prompt to rename conversation
case "shift+r":
// show prompt to generate name for conversation
}
return nil
}
func (m *model) handleConversationsUpdate(msg tea.Msg) []tea.Cmd {
var cmds []tea.Cmd
switch msg := msg.(type) {
case msgConversationsLoaded:
m.conversations = msg
m.content.SetContent(m.renderConversationList())
}
var cmd tea.Cmd
m.content, cmd = m.content.Update(msg)
if cmd != nil {
cmds = append(cmds, cmd)
}
if m.width > 0 {
m.views.header = m.headerView()
m.views.footer = m.footerView()
m.views.error = m.errorView()
fixedHeight := height(m.views.header) + height(m.views.error) + height(m.views.footer)
m.content.Height = m.height - fixedHeight
m.views.content = m.content.View()
}
return cmds
}
func (m *model) loadConversations() tea.Cmd {
return func() tea.Msg {
c, err := m.ctx.Store.Conversations()
if err != nil {
return msgError(fmt.Errorf("Could not load conversations: %v", err))
}
return msgConversationsLoaded(c)
}
}
func (m *model) loadConversationLastestReplies(conversations []models.Conversation) tea.Cmd {
return func() tea.Msg {
//lastMessage, err := m.ctx.Store.LastMessage(&conversation)
return nil
}
}
func (m *model) setConversations(conversations []models.Conversation) {
}
func (m *model) renderConversationList() string {
sb := &strings.Builder{}
type AgeGroup struct {
name string
cutoff time.Duration
}
type ConversationLine struct {
id uint
short string
title string
elapsed string
lastReplyAge time.Duration
}
now := time.Now()
midnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
dayOfWeek := int(now.Weekday())
categories := []AgeGroup{
{"Today", now.Sub(midnight)},
{"Yesterday", now.Sub(midnight.AddDate(0, 0, -1))},
{"This week", now.Sub(midnight.AddDate(0, 0, -dayOfWeek))},
{"Last week", now.Sub(midnight.AddDate(0, 0, -(dayOfWeek + 7)))},
{"This month", now.Sub(monthStart)},
{"Last month", now.Sub(monthStart.AddDate(0, -1, 0))},
{"2 Months ago", now.Sub(monthStart.AddDate(0, -2, 0))},
{"3 Months ago", now.Sub(monthStart.AddDate(0, -3, 0))},
{"4 Months ago", now.Sub(monthStart.AddDate(0, -4, 0))},
{"5 Months ago", now.Sub(monthStart.AddDate(0, -5, 0))},
{"6 Months ago", now.Sub(monthStart.AddDate(0, -6, 0))},
{"Older", now.Sub(time.Time{})},
}
categorized := map[string][]ConversationLine{}
for _, conversation := range m.conversations {
lastMessage, err := m.ctx.Store.LastMessage(&conversation)
if lastMessage == nil || err != nil {
continue
}
messageAge := now.Sub(lastMessage.CreatedAt)
var category string
for _, c := range categories {
if messageAge < c.cutoff {
category = c.name
break
}
}
categorized[category] = append(
categorized[category],
ConversationLine{
id: conversation.ID,
title: conversation.Title,
short: conversation.ShortName.String,
lastReplyAge: messageAge,
},
)
}
// TODO: pick nice color
categoryStyle := lipgloss.NewStyle().
MarginBottom(1).
Foreground(lipgloss.Color("170")).
PaddingLeft(1).
Bold(true)
conversationHeadingStyle := lipgloss.NewStyle().
MarginBottom(1).
PaddingLeft(2)
for _, category := range categories {
conversationLines, ok := categorized[category.name]
if !ok {
continue
}
slices.SortFunc(conversationLines, func(a, b ConversationLine) int {
return int(a.lastReplyAge - b.lastReplyAge)
})
ageStyle := lipgloss.NewStyle().Faint(true)
titleStyle := lipgloss.NewStyle().Bold(true)
untitledStyle := titleStyle.Copy().Italic(true).SetString("(untitled)")
fmt.Fprintf(sb, "%s\n", categoryStyle.Render(category.name))
for _, c := range conversationLines {
tstyle := titleStyle
if c.title == "" {
tstyle = untitledStyle
}
heading := fmt.Sprintf(
"%s\n%s",
tstyle.Render(c.title),
ageStyle.Render(util.HumanTimeElapsedSince(c.lastReplyAge)),
)
sb.WriteString(conversationHeadingStyle.Render(heading))
sb.WriteRune('\n')
}
}
return sb.String()
}