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/lmcli"
|
2024-05-30 00:44:40 -06:00
|
|
|
"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"
|
|
|
|
)
|
|
|
|
|
2024-05-30 00:44:40 -06:00
|
|
|
// Application model
|
|
|
|
type Model struct {
|
|
|
|
shared.State
|
2024-03-29 18:41:12 -06:00
|
|
|
|
2024-05-30 00:44:40 -06:00
|
|
|
state shared.View
|
|
|
|
chat chat.Model
|
|
|
|
conversations conversations.Model
|
2024-03-31 17:51:45 -06:00
|
|
|
}
|
2024-03-12 01:10:54 -06:00
|
|
|
|
2024-05-30 00:44:40 -06:00
|
|
|
func initialModel(ctx *lmcli.Context, values shared.Values) Model {
|
|
|
|
m := Model{
|
|
|
|
State: shared.State{
|
|
|
|
Ctx: ctx,
|
|
|
|
Values: &values,
|
2024-03-31 17:51:45 -06:00
|
|
|
},
|
2024-03-30 20:03:53 -06:00
|
|
|
}
|
2024-05-30 00:44:40 -06:00
|
|
|
|
|
|
|
m.state = shared.StateChat
|
|
|
|
m.chat = chat.Chat(m.State)
|
|
|
|
m.conversations = conversations.Conversations(m.State)
|
2024-03-30 20:03:53 -06:00
|
|
|
return m
|
2024-03-29 14:43:19 -06:00
|
|
|
}
|
2024-03-12 01:10:54 -06:00
|
|
|
|
2024-05-30 00:44:40 -06:00
|
|
|
func (m Model) Init() tea.Cmd {
|
2024-04-01 11:03:49 -06:00
|
|
|
return tea.Batch(
|
|
|
|
m.conversations.Init(),
|
|
|
|
m.chat.Init(),
|
|
|
|
func() tea.Msg {
|
2024-05-30 00:44:40 -06:00
|
|
|
return shared.MsgViewChange(m.state)
|
2024-04-01 11:03:49 -06:00
|
|
|
},
|
|
|
|
)
|
2024-03-12 01:10:54 -06:00
|
|
|
}
|
|
|
|
|
2024-05-30 00:44:40 -06:00
|
|
|
func (m *Model) handleGlobalInput(msg tea.KeyMsg) (bool, tea.Cmd) {
|
2024-03-31 19:06:13 -06:00
|
|
|
// delegate input to the active child state first, only handling it at the
|
|
|
|
// global level if the child state does not
|
|
|
|
var cmds []tea.Cmd
|
|
|
|
switch m.state {
|
2024-05-30 00:44:40 -06:00
|
|
|
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...)
|
2024-03-29 18:41:12 -06:00
|
|
|
}
|
2024-05-30 00:44:40 -06:00
|
|
|
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-29 18:41:12 -06:00
|
|
|
}
|
|
|
|
}
|
2024-03-31 19:06:13 -06:00
|
|
|
switch msg.String() {
|
|
|
|
case "ctrl+c", "ctrl+q":
|
|
|
|
return true, tea.Quit
|
|
|
|
}
|
|
|
|
return false, nil
|
2024-03-29 18:41:12 -06:00
|
|
|
}
|
|
|
|
|
2024-05-30 00:44:40 -06:00
|
|
|
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
2024-03-29 18:41:12 -06:00
|
|
|
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 {
|
2024-03-29 18:41:12 -06:00
|
|
|
return m, cmd
|
|
|
|
}
|
2024-05-30 00:44:40 -06:00
|
|
|
case shared.MsgViewChange:
|
|
|
|
m.state = shared.View(msg)
|
2024-05-07 02:07:48 -06:00
|
|
|
switch m.state {
|
2024-05-30 00:44:40 -06:00
|
|
|
case shared.StateChat:
|
2024-05-30 01:18:31 -06:00
|
|
|
m.chat.HandleResize(m.Width, m.Height)
|
2024-05-30 00:44:40 -06:00
|
|
|
case shared.StateConversations:
|
2024-05-30 01:18:31 -06:00
|
|
|
m.conversations.HandleResize(m.Width, m.Height)
|
2024-04-01 11:05:36 -06:00
|
|
|
}
|
2024-05-30 00:44:40 -06:00
|
|
|
return m, func() tea.Msg { return shared.MsgViewEnter(struct{}{}) }
|
2024-03-29 18:41:12 -06:00
|
|
|
case tea.WindowSizeMsg:
|
2024-05-30 01:18:31 -06:00
|
|
|
m.Width, m.Height = msg.Width, msg.Height
|
2024-03-29 18:41:12 -06:00
|
|
|
}
|
|
|
|
|
2024-03-31 17:51:45 -06:00
|
|
|
var cmd tea.Cmd
|
2024-03-29 18:41:12 -06:00
|
|
|
switch m.state {
|
2024-05-30 00:44:40 -06:00
|
|
|
case shared.StateConversations:
|
2024-03-31 17:51:45 -06:00
|
|
|
m.conversations, cmd = m.conversations.Update(msg)
|
2024-05-30 00:44:40 -06:00
|
|
|
case shared.StateChat:
|
2024-03-31 17:51:45 -06:00
|
|
|
m.chat, cmd = m.chat.Update(msg)
|
|
|
|
}
|
|
|
|
if cmd != nil {
|
|
|
|
cmds = append(cmds, cmd)
|
2024-03-29 18:41:12 -06:00
|
|
|
}
|
|
|
|
|
2024-03-13 23:58:31 -06:00
|
|
|
return m, tea.Batch(cmds...)
|
2024-03-12 01:10:54 -06:00
|
|
|
}
|
|
|
|
|
2024-05-30 00:44:40 -06:00
|
|
|
func (m Model) View() string {
|
2024-03-29 18:41:12 -06:00
|
|
|
switch m.state {
|
2024-05-30 00:44:40 -06:00
|
|
|
case shared.StateConversations:
|
2024-05-30 01:04:55 -06:00
|
|
|
return m.conversations.View()
|
2024-05-30 00:44:40 -06:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
func Launch(ctx *lmcli.Context, convShortname string) error {
|
2024-05-30 00:44:40 -06:00
|
|
|
p := tea.NewProgram(initialModel(ctx, shared.Values{ConvShortname: convShortname}), tea.WithAltScreen())
|
2024-03-12 01:10:54 -06:00
|
|
|
if _, err := p.Run(); err != nil {
|
|
|
|
return fmt.Errorf("Error running program: %v", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|