lmcli/pkg/tui/tui.go

162 lines
3.5 KiB
Go
Raw Normal View History

2024-03-12 01:10:54 -06:00
package tui
// The terminal UI for lmcli, launched from the `lmcli chat` command
// TODO:
2024-03-14 00:01:16 -06:00
// - change model
// - rename conversation
// - set system prompt
2024-03-12 01:10:54 -06:00
import (
"fmt"
"git.mlow.ca/mlow/lmcli/pkg/api"
2024-03-12 01:10:54 -06:00
"git.mlow.ca/mlow/lmcli/pkg/lmcli"
"git.mlow.ca/mlow/lmcli/pkg/tui/model"
"git.mlow.ca/mlow/lmcli/pkg/tui/shared"
"git.mlow.ca/mlow/lmcli/pkg/tui/views/chat"
"git.mlow.ca/mlow/lmcli/pkg/tui/views/conversations"
2024-03-12 01:10:54 -06:00
tea "github.com/charmbracelet/bubbletea"
)
type LaunchOptions struct {
InitialConversation *api.Conversation
InitialView shared.View
}
type Model struct {
App *model.AppModel
view shared.View
chat chat.Model
conversations conversations.Model
Width int
Height int
}
2024-03-12 01:10:54 -06:00
func initialModel(ctx *lmcli.Context, opts LaunchOptions) Model {
m := Model{
App: &model.AppModel{
Ctx: ctx,
Conversation: opts.InitialConversation,
},
view: opts.InitialView,
}
sharedData := shared.Shared{}
m.chat = chat.Chat(m.App, sharedData)
m.conversations = conversations.Conversations(m.App, sharedData)
return m
2024-03-29 14:43:19 -06:00
}
2024-03-12 01:10:54 -06:00
func (m Model) Init() tea.Cmd {
return tea.Batch(
m.conversations.Init(),
m.chat.Init(),
func() tea.Msg {
return shared.MsgViewChange(m.view)
},
)
2024-03-12 01:10:54 -06:00
}
func (m *Model) handleGlobalInput(msg tea.KeyMsg) (bool, tea.Cmd) {
2024-03-31 19:06:13 -06:00
var cmds []tea.Cmd
switch m.view {
case shared.StateChat:
handled, cmd := m.chat.HandleInput(msg)
2024-03-31 19:06:13 -06:00
cmds = append(cmds, cmd)
if handled {
m.chat, cmd = m.chat.Update(nil)
cmds = append(cmds, cmd)
return true, tea.Batch(cmds...)
}
case shared.StateConversations:
handled, cmd := m.conversations.HandleInput(msg)
2024-03-31 19:06:13 -06:00
cmds = append(cmds, cmd)
if handled {
m.conversations, cmd = m.conversations.Update(nil)
cmds = append(cmds, cmd)
return true, tea.Batch(cmds...)
}
}
2024-03-31 19:06:13 -06:00
switch msg.String() {
case "ctrl+c", "ctrl+q":
return true, tea.Quit
}
return false, nil
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
2024-03-31 19:06:13 -06:00
handled, cmd := m.handleGlobalInput(msg)
if handled {
return m, cmd
}
case shared.MsgViewChange:
m.view = shared.View(msg)
switch m.view {
case shared.StateChat:
2024-05-30 01:18:31 -06:00
m.chat.HandleResize(m.Width, m.Height)
case shared.StateConversations:
2024-05-30 01:18:31 -06:00
m.conversations.HandleResize(m.Width, m.Height)
}
return m, func() tea.Msg { return shared.MsgViewEnter(struct{}{}) }
case tea.WindowSizeMsg:
2024-05-30 01:18:31 -06:00
m.Width, m.Height = msg.Width, msg.Height
}
var cmd tea.Cmd
switch m.view {
case shared.StateConversations:
m.conversations, cmd = m.conversations.Update(msg)
case shared.StateChat:
m.chat, cmd = m.chat.Update(msg)
}
if cmd != nil {
cmds = append(cmds, cmd)
}
2024-03-13 23:58:31 -06:00
return m, tea.Batch(cmds...)
2024-03-12 01:10:54 -06:00
}
func (m Model) View() string {
switch m.view {
case shared.StateConversations:
2024-05-30 01:04:55 -06:00
return m.conversations.View()
case shared.StateChat:
2024-05-30 01:04:55 -06:00
return m.chat.View()
2024-03-13 20:54:25 -06:00
}
2024-05-30 01:18:31 -06:00
return ""
2024-03-12 01:10:54 -06:00
}
type LaunchOption func(*LaunchOptions)
func WithInitialConversation(conv *api.Conversation) LaunchOption {
return func(opts *LaunchOptions) {
opts.InitialConversation = conv
}
}
func WithInitialView(view shared.View) LaunchOption {
return func(opts *LaunchOptions) {
opts.InitialView = view
}
}
func Launch(ctx *lmcli.Context, options ...LaunchOption) error {
opts := &LaunchOptions{
InitialView: shared.StateChat,
}
for _, opt := range options {
opt(opts)
}
program := tea.NewProgram(initialModel(ctx, *opts), tea.WithAltScreen())
if _, err := program.Run(); err != nil {
2024-03-12 01:10:54 -06:00
return fmt.Errorf("Error running program: %v", err)
}
return nil
}