From b0a1299e0be79aa77488f38fae52a3cb583389d4 Mon Sep 17 00:00:00 2001 From: Matt Low Date: Sun, 12 Nov 2023 07:19:45 +0000 Subject: [PATCH] Implement `lmcli ls` --- pkg/cli/cmd.go | 71 +++++++++++++++++++++++++++++++++++++++++++++--- pkg/cli/store.go | 8 ++++++ pkg/cli/util.go | 43 +++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 4 deletions(-) diff --git a/pkg/cli/cmd.go b/pkg/cli/cmd.go index bdcc946..157e99e 100644 --- a/pkg/cli/cmd.go +++ b/pkg/cli/cmd.go @@ -3,6 +3,7 @@ package cli import ( "fmt" "strings" + "time" "github.com/spf13/cobra" ) @@ -24,17 +25,21 @@ var lsCmd = &cobra.Command{ Short: "List existing conversations", Long: `List all existing conversations in descending order of recent activity.`, Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Listing conversations...") - // Example output, asterisk to indicate current converation + conversations, err := store.GetConversations() + if err != nil { + fmt.Println("Could not fetch conversations.") + return + } - // $ lm ls + // Example output + // $ lmcli ls // last hour: // 98sg - 12 minutes ago - Project discussion // last day: // tj3l - 10 hours ago - Deep learning concepts // last week: // bwfm - 2 days ago - Machine learning study - // * 8n3h - 3 days ago - Weekend plans + // 8n3h - 3 days ago - Weekend plans // f3n7 - 6 days ago - CLI development // last month: // 5hn2 - 8 days ago - Book club discussion @@ -45,6 +50,63 @@ var lsCmd = &cobra.Command{ // g8d9 - 3 months ago - History book club // 4lk3 - 4 months ago - Local events and meetups // 43jn - 6 months ago - Mobile photography techniques + + now := time.Now() + categories := []string{ + "recent", + "last hour", + "last day", + "last week", + "last month", + "last 6 months", + "older", + } + categorized := map[string][]string{} + + for _, conversation := range conversations { + lastMessage, err := store.GetLastMessage(&conversation) + if lastMessage == nil || err != nil { + continue + } + messageAge := now.Sub(lastMessage.CreatedAt) + + var category string + switch { + case messageAge <= 10*time.Minute: + category = "recent" + case messageAge <= time.Hour: + category = "last hour" + case messageAge <= 24*time.Hour: + category = "last day" + case messageAge <= 7*24*time.Hour: + category = "last week" + case messageAge <= 30*24*time.Hour: + category = "last month" + case messageAge <= 6*30*24*time.Hour: // Approximate as 6 months + category = "last 6 months" + default: + category = "older" + } + + formatted := fmt.Sprintf( + "%s - %s - %s", + conversation.ShortName.String, + humanTimeElapsedSince(messageAge), + conversation.Title, + ) + categorized[category] = append(categorized[category], formatted) + } + + for _, category := range categories { + conversations, ok := categorized[category] + if !ok { + continue + } + fmt.Printf("%s:\n", category) + for _, conv := range conversations { + fmt.Printf(" %s\n", conv) + } + } }, } @@ -174,6 +236,7 @@ var promptCmd = &cobra.Command{ func NewRootCmd() *cobra.Command { rootCmd.AddCommand( + lsCmd, newCmd, promptCmd, ) diff --git a/pkg/cli/store.go b/pkg/cli/store.go index 3e468e7..2bce9f4 100644 --- a/pkg/cli/store.go +++ b/pkg/cli/store.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "time" sqids "github.com/sqids/sqids-go" "gorm.io/driver/sqlite" @@ -22,6 +23,7 @@ type Message struct { Conversation Conversation OriginalContent string Role string // 'user' or 'assistant' + CreatedAt time.Time } type Conversation struct { @@ -98,3 +100,9 @@ func (s *Store) GetMessages(conversation *Conversation) ([]Message, error) { err := s.db.Where("conversation_id = ?", conversation.ID).Find(&messages).Error return messages, err } + +func (s *Store) GetLastMessage(conversation *Conversation) (*Message, error) { + var message Message + err := s.db.Where("conversation_id = ?", conversation.ID).Last(&message).Error + return &message, err +} diff --git a/pkg/cli/util.go b/pkg/cli/util.go index 3619880..6157c48 100644 --- a/pkg/cli/util.go +++ b/pkg/cli/util.go @@ -1,9 +1,11 @@ package cli import ( + "fmt" "os" "os/exec" "strings" + "time" ) // InputFromEditor retrieves user input by opening an editor (one specified by @@ -48,3 +50,44 @@ func InputFromEditor(placeholder string, pattern string) (string, error) { return strings.Trim(content, "\n \t"), nil } + +// humanTimeElapsedSince returns a human-friendly representation of the given time +// duration. +func humanTimeElapsedSince(d time.Duration) string { + seconds := d.Seconds() + minutes := seconds / 60 + hours := minutes / 60 + days := hours / 24 + weeks := days / 7 + months := days / 30 + years := days / 365 + + switch { + case seconds < 60: + return "seconds ago" + case minutes < 2: + return "1 minute ago" + case minutes < 60: + return fmt.Sprintf("%d minutes ago", int64(minutes)) + case hours < 2: + return "1 hour ago" + case hours < 24: + return fmt.Sprintf("%d hours ago", int64(hours)) + case days < 2: + return "1 day ago" + case days < 7: + return fmt.Sprintf("%d days ago", int64(days)) + case weeks < 2: + return "1 week ago" + case weeks <= 4: + return fmt.Sprintf("%d weeks ago", int64(weeks)) + case months < 2: + return "1 month ago" + case months < 12: + return fmt.Sprintf("%d months ago", int64(months)) + case years < 2: + return "1 year ago" + default: + return fmt.Sprintf("%d years ago", int64(years)) + } +}