summary history files

internal/model/model.go
package model

import (
	"fmt"
	"strings"

	"github.com/charmbracelet/bubbles/textarea"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/sashabaranov/go-openai"
)

type CommandMsg struct {
	Command Command
}

func NewCommandMsg(c Command) CommandMsg {
	return CommandMsg{Command: c}
}

type errMsg error

type Model struct {
	textarea    textarea.Model
	UserInput   UserInput
	commandMsgs []CommandMsg
	err         error
	Opts
}

type Opts struct {
	ready        bool
	messages     []openai.ChatCompletionMessage
	initialValue string
}

type OptFunc func(*Opts)

func WithChatCompletionMessages(m []openai.ChatCompletionMessage) OptFunc {
	return func(opts *Opts) {
		opts.messages = m
	}
}

func WithInitialValue(s string) OptFunc {
	return func(opts *Opts) {
		opts.initialValue = s
	}
}

func WithReady() OptFunc {
	return func(opts *Opts) {
		opts.ready = true
	}
}

func defaultOpts() Opts {
	return Opts{
		ready:        false,
		messages:     nil,
		initialValue: "",
	}
}

var initialTextAreaHeight = 3

func NewModel(opts ...OptFunc) *Model {
	o := defaultOpts()
	for _, fn := range opts {
		fn(&o)
	}

	t := textarea.New()
	t.Placeholder = "press [tab] to submit prompt"
	t.SetWidth(72)
	t.SetHeight(initialTextAreaHeight)
	t.ShowLineNumbers = false
	t.Focus()

	if o.initialValue != "" {
		t.SetValue(o.initialValue)
	}

	return &Model{
		textarea: t,
		Opts:     o,
	}
}

func (m *Model) SetReady(r bool) {
	m.Opts.ready = r
}

func (m *Model) Ready() bool {
	return m.Opts.ready
}

func (m *Model) Init() tea.Cmd {
	return textarea.Blink
}

func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var cmds []tea.Cmd
	var cmd tea.Cmd

	m.textarea.SetHeight(strings.Count(m.textarea.Value(), "\n") + initialTextAreaHeight)

	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.Type {
		case tea.KeyCtrlC:
			return m, tea.Quit
		case tea.KeyTab:
			m.UserInput = NewUserInput(m.textarea.Value())
			m.SetReady(true)

			for _, command := range m.UserInput.Commands() {
				m.commandMsgs = append(m.commandMsgs, NewCommandMsg(command))
			}

			if len(m.commandMsgs) >= 1 {
				m.SetReady(false)
				return m.Update(m.commandMsgs[0])
			}

			return m, tea.Quit
		default:
			if !m.textarea.Focused() {
				cmd = m.textarea.Focus()
				cmds = append(cmds, cmd)
			}
		}

	case CommandMsg:
		command := msg.Command
		if err := command.Run(m); err != nil {
			fmt.Printf("error: %s", err)
			return m, nil
		}

		m.commandMsgs = m.commandMsgs[1:]

		if len(m.commandMsgs) >= 1 {
			return m.Update(m.commandMsgs[0])
		}

		return m, tea.Quit

	case errMsg:
		m.err = msg
		return m, nil
	}

	m.textarea, cmd = m.textarea.Update(msg)
	cmds = append(cmds, cmd)
	return m, tea.Batch(cmds...)
}

func (m *Model) View() string {
	return fmt.Sprintf(m.textarea.View()) + "\n\n"
}

func (m *Model) Reset() {
	m.textarea.SetValue("")
}

func (m *Model) Messages() []openai.ChatCompletionMessage {
	return m.Opts.messages
}