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() }