Skip to content

Commit

Permalink
feat(internal/ui): enhance pager with stopwatch and AI message buffer
Browse files Browse the repository at this point in the history
  • Loading branch information
nullswan committed Oct 5, 2024
1 parent 06feb40 commit 58f4670
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 18 deletions.
22 changes: 18 additions & 4 deletions internal/ui/model.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package ui

import (
"time"

"github.com/charmbracelet/bubbles/stopwatch"
"github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/viewport"
"github.com/charmbracelet/glamour"
Expand All @@ -9,6 +12,13 @@ import (
tea "github.com/charmbracelet/bubbletea"
)

const (
stopwatchIntval = time.Millisecond * 100
loadingMessage = "Loading..."
aiPrefix = "AI: "
humanPrefix = "You: "
)

// Define the model struct
type model struct {
// textInput is the text input component.
Expand All @@ -17,10 +27,13 @@ type model struct {

// Pager is the pager component.
// The pager is used to display the text received.
pagerContent string
pager viewport.Model
pagerRenderer *glamour.TermRenderer
ready bool
pagerContent string
pager viewport.Model
pagerRenderer *glamour.TermRenderer
ready bool
pagerStopwatch stopwatch.Model
pagerAIBuffer string
pagerAIRenderedBuffer string

// commandChannel is the channel where the user input is sent.
commandChannel chan string
Expand All @@ -45,6 +58,7 @@ func NewModel(inputChan chan string) model {
pager: viewport.Model{},
pagerRenderer: renderer,
ready: false,
pagerStopwatch: stopwatch.NewWithInterval(stopwatchIntval),
commandChannel: inputChan,
humanStyle: lipgloss.NewStyle().
Foreground(lipgloss.Color("#FF00FF")),
Expand Down
51 changes: 41 additions & 10 deletions internal/ui/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.textArea.Blur()
}

// If the text area is empty, do nothing
if m.textArea.Value() == "" {
return m, nil
}

// Send the command
m.commandChannel <- m.textArea.Value()
m.textArea.Reset()

cmds = append(cmds, m.pagerStopwatch.Reset())
cmds = append(cmds, m.pagerStopwatch.Start())
case tea.KeyCtrlC:
return m, tea.Quit
default:
Expand All @@ -53,25 +58,51 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.pager.Width = msg.Width
m.pager.Height = msg.Height - verticalMarginHeight
}

case PagerMsg:
str, err := m.pagerRenderer.Render(msg.String())
if err != nil {
return m, nil
}

// Add the message to the pager content
if msg.From == Human {
m.pagerContent += m.humanStyle.Render("You:") + "\n" + str + "\n"
} else {
m.pagerContent += m.aiStyle.Render("AI:") + "\n" + str + "\n"
str, err := m.pagerRenderer.Render(msg.String())
if err != nil {
return m, nil
}

m.pagerContent += m.humanStyle.Render(humanPrefix) + "\n" + str + "\n"
} else if msg.From == AI {
if msg.String() == "" {
// The AI stopped talking, so stop the stopwatch
cmds = append(cmds, m.pagerStopwatch.Stop())
m.pagerAIRenderedBuffer = ""
m.pagerAIBuffer = ""
} else {
// The AI is talking
m.pagerAIBuffer += msg.String()

if m.pagerAIRenderedBuffer == "" {
m.pagerContent += m.aiStyle.Render(aiPrefix) + "\n"
} else {
m.pagerContent = m.pagerContent[:len(m.pagerContent)-len(m.pagerAIRenderedBuffer)]
}

newContent, err := m.pagerRenderer.Render(m.pagerAIBuffer)
if err != nil {
return m, nil
}

m.pagerAIRenderedBuffer = newContent
m.pagerContent += newContent
}
}

// Update the pager content
m.pager.SetContent(m.pagerContent)
return m, nil
m.pager.GotoBottom()

return m, tea.Batch(cmds...)
}

m.pagerStopwatch, cmd = m.pagerStopwatch.Update(msg)
cmds = append(cmds, cmd)

// Update the text area
m.textArea, cmd = m.textArea.Update(msg)
cmds = append(cmds, cmd)
Expand Down
41 changes: 37 additions & 4 deletions internal/ui/view.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,49 @@
package ui

import "fmt"
import (
"fmt"
"time"
)

func (m model) View() string {
if !m.ready {
return "Loading..."
return loadingMessage
}

return fmt.Sprintf(
pager := fmt.Sprintf(
"%s\n%s\n%s",
m.headerView(),
m.pager.View(),
m.footerView(),
) + "\n\nInput (type or use voice):\n" + m.textArea.View()
)

var textAreaHeader string
if m.pagerStopwatch.Running() {
elapsedTime := formatDuration(m.pagerStopwatch.Elapsed())
renderedTime := m.aiStyle.Render(elapsedTime)
textAreaHeader = fmt.Sprintf(
"\n\nInput (type or use voice): -- Generating for [%s], submit to cancel\n",
renderedTime,
)
} else {
textAreaHeader = "\n\nInput (type or use voice):\n"
}

textArea := m.textArea.View()

return pager + textAreaHeader + textArea
}

func formatDuration(d time.Duration) string {
// Limit to 60 seconds
if d > 60*time.Second {
d = 60 * time.Second
}

// Round to nearest intval
d = d.Round(stopwatchIntval)

// Format to ensure one decimal place
seconds := d.Seconds()
return fmt.Sprintf("%.1fs", seconds)
}

0 comments on commit 58f4670

Please sign in to comment.