TUI view management and input handling cleanup
This commit is contained in:
parent
24b5cdbbf6
commit
463ca9ef40
@ -40,7 +40,7 @@ func ChatCmd(ctx *lmcli.Context) *cobra.Command {
|
||||
}
|
||||
|
||||
if list {
|
||||
opts = append(opts, tui.WithInitialView(shared.StateConversations))
|
||||
opts = append(opts, tui.WithInitialView(shared.ViewConversations))
|
||||
}
|
||||
|
||||
err = tui.Launch(ctx, opts...)
|
||||
|
@ -4,13 +4,31 @@ import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
type Shared struct {
|
||||
// An analogue to tea.Model with support for checking if the model has been
|
||||
// initialized before
|
||||
type ViewModel interface {
|
||||
Init() tea.Cmd
|
||||
Update(tea.Msg) (ViewModel, tea.Cmd)
|
||||
View() string
|
||||
Initialized() bool // Return whether this view is initialized
|
||||
}
|
||||
|
||||
type ViewState struct {
|
||||
Initialized bool
|
||||
Width int
|
||||
Height int
|
||||
Err error
|
||||
}
|
||||
|
||||
type View int
|
||||
|
||||
const (
|
||||
ViewChat View = iota
|
||||
ViewConversations
|
||||
//StateSettings
|
||||
//StateHelp
|
||||
)
|
||||
|
||||
// a convenience struct for holding rendered content for indiviudal UI
|
||||
// elements
|
||||
type Sections struct {
|
||||
@ -28,6 +46,8 @@ type (
|
||||
MsgViewEnter struct{}
|
||||
// sent when an error occurs
|
||||
MsgError error
|
||||
// sent when the view has handled a key input
|
||||
MsgKeyHandled tea.KeyMsg
|
||||
)
|
||||
|
||||
func ViewEnter() tea.Cmd {
|
||||
@ -36,17 +56,14 @@ func ViewEnter() tea.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
func KeyHandled(key tea.KeyMsg) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return MsgKeyHandled(key)
|
||||
}
|
||||
}
|
||||
|
||||
func WrapError(err error) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
return MsgError(err)
|
||||
}
|
||||
}
|
||||
|
||||
type View int
|
||||
|
||||
const (
|
||||
StateChat View = iota
|
||||
StateConversations
|
||||
//StateSettings
|
||||
//StateHelp
|
||||
)
|
||||
|
108
pkg/tui/tui.go
108
pkg/tui/tui.go
@ -1,11 +1,5 @@
|
||||
package tui
|
||||
|
||||
// The terminal UI for lmcli, launched from the `lmcli chat` command
|
||||
// TODO:
|
||||
// - change model
|
||||
// - rename conversation
|
||||
// - set system prompt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
@ -25,116 +19,80 @@ type LaunchOptions struct {
|
||||
|
||||
type Model struct {
|
||||
App *model.AppModel
|
||||
view shared.View
|
||||
|
||||
// views
|
||||
chat chat.Model
|
||||
conversations conversations.Model
|
||||
activeView shared.View
|
||||
views map[shared.View]shared.ViewModel
|
||||
}
|
||||
|
||||
func initialModel(ctx *lmcli.Context, opts LaunchOptions) Model {
|
||||
m := Model{
|
||||
App: &model.AppModel{
|
||||
sharedData := shared.ViewState{}
|
||||
|
||||
app := &model.AppModel{
|
||||
Ctx: ctx,
|
||||
Conversation: opts.InitialConversation,
|
||||
},
|
||||
view: opts.InitialView,
|
||||
}
|
||||
|
||||
sharedData := shared.Shared{}
|
||||
m := Model{
|
||||
App: app,
|
||||
activeView: opts.InitialView,
|
||||
views: map[shared.View]shared.ViewModel{
|
||||
shared.ViewChat: chat.Chat(app, sharedData),
|
||||
shared.ViewConversations: conversations.Conversations(app, sharedData),
|
||||
},
|
||||
}
|
||||
|
||||
m.chat = chat.Chat(m.App, sharedData)
|
||||
m.conversations = conversations.Conversations(m.App, sharedData)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return tea.Batch(
|
||||
func() tea.Msg {
|
||||
return shared.MsgViewChange(m.view)
|
||||
return shared.MsgViewChange(m.activeView)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (m *Model) handleGlobalInput(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
switch m.view {
|
||||
case shared.StateChat:
|
||||
handled, cmd := m.chat.HandleInput(msg)
|
||||
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)
|
||||
cmds = append(cmds, cmd)
|
||||
if handled {
|
||||
m.conversations, cmd = m.conversations.Update(nil)
|
||||
cmds = append(cmds, cmd)
|
||||
return true, tea.Batch(cmds...)
|
||||
}
|
||||
func (m *Model) handleGlobalInput(msg tea.KeyMsg) tea.Cmd {
|
||||
view, cmd := m.views[m.activeView].Update(msg)
|
||||
m.views[m.activeView] = view
|
||||
if cmd != nil {
|
||||
return cmd
|
||||
}
|
||||
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "ctrl+q":
|
||||
return true, tea.Quit
|
||||
return tea.Quit
|
||||
}
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
handled, cmd := m.handleGlobalInput(msg)
|
||||
if handled {
|
||||
cmd := m.handleGlobalInput(msg)
|
||||
if cmd != nil {
|
||||
return m, cmd
|
||||
}
|
||||
case shared.MsgViewChange:
|
||||
m.view = shared.View(msg)
|
||||
m.activeView = shared.View(msg)
|
||||
view := m.views[m.activeView]
|
||||
|
||||
var cmds []tea.Cmd
|
||||
switch m.view {
|
||||
case shared.StateConversations:
|
||||
if !m.conversations.Initialized {
|
||||
cmds = append(cmds, m.conversations.Init())
|
||||
m.conversations.Initialized = true
|
||||
}
|
||||
case shared.StateChat:
|
||||
if !m.chat.Initialized {
|
||||
cmds = append(cmds, m.chat.Init())
|
||||
m.chat.Initialized = true
|
||||
}
|
||||
if !view.Initialized() {
|
||||
cmds = append(cmds, view.Init())
|
||||
}
|
||||
cmds = append(cmds, tea.WindowSize(), shared.ViewEnter())
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
view, cmd := m.views[m.activeView].Update(msg)
|
||||
m.views[m.activeView] = view
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
switch m.view {
|
||||
case shared.StateConversations:
|
||||
return m.conversations.View()
|
||||
case shared.StateChat:
|
||||
return m.chat.View()
|
||||
}
|
||||
return ""
|
||||
return m.views[m.activeView].View()
|
||||
}
|
||||
|
||||
type LaunchOption func(*LaunchOptions)
|
||||
@ -153,7 +111,7 @@ func WithInitialView(view shared.View) LaunchOption {
|
||||
|
||||
func Launch(ctx *lmcli.Context, options ...LaunchOption) error {
|
||||
opts := &LaunchOptions{
|
||||
InitialView: shared.StateChat,
|
||||
InitialView: shared.ViewChat,
|
||||
}
|
||||
for _, opt := range options {
|
||||
opt(opts)
|
||||
|
@ -74,7 +74,7 @@ const (
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
shared.Shared
|
||||
*shared.ViewState
|
||||
shared.Sections
|
||||
|
||||
// App state
|
||||
@ -108,10 +108,14 @@ type Model struct {
|
||||
elapsed time.Duration
|
||||
}
|
||||
|
||||
func Chat(app *model.AppModel, shared shared.Shared) Model {
|
||||
func (m Model) Initialized() bool {
|
||||
return m.ViewState.Initialized
|
||||
}
|
||||
|
||||
func Chat(app *model.AppModel, shared shared.ViewState) shared.ViewModel {
|
||||
m := Model{
|
||||
App: app,
|
||||
Shared: shared,
|
||||
ViewState: &shared,
|
||||
|
||||
state: idle,
|
||||
persistence: true,
|
||||
@ -169,6 +173,7 @@ func Chat(app *model.AppModel, shared shared.Shared) Model {
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
m.ViewState.Initialized = true
|
||||
return tea.Batch(
|
||||
m.waitForResponseChunk(),
|
||||
)
|
||||
|
@ -11,17 +11,17 @@ import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func (m *Model) HandleInput(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
func (m *Model) handleInput(msg tea.KeyMsg) tea.Cmd {
|
||||
switch m.focus {
|
||||
case focusInput:
|
||||
consumed, cmd := m.handleInputKey(msg)
|
||||
if consumed {
|
||||
return true, cmd
|
||||
cmd := m.handleInputKey(msg)
|
||||
if cmd != nil {
|
||||
return cmd
|
||||
}
|
||||
case focusMessages:
|
||||
consumed, cmd := m.handleMessagesKey(msg)
|
||||
if consumed {
|
||||
return true, cmd
|
||||
cmd := m.handleMessagesKey(msg)
|
||||
if cmd != nil {
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,51 +29,51 @@ func (m *Model) HandleInput(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
case "esc":
|
||||
if m.state == pendingResponse {
|
||||
m.stopSignal <- struct{}{}
|
||||
return true, nil
|
||||
return shared.KeyHandled(msg)
|
||||
}
|
||||
return true, func() tea.Msg {
|
||||
return shared.MsgViewChange(shared.StateConversations)
|
||||
return func() tea.Msg {
|
||||
return shared.MsgViewChange(shared.ViewConversations)
|
||||
}
|
||||
case "ctrl+c":
|
||||
if m.state == pendingResponse {
|
||||
m.stopSignal <- struct{}{}
|
||||
return true, nil
|
||||
return shared.KeyHandled(msg)
|
||||
}
|
||||
case "ctrl+p":
|
||||
m.persistence = !m.persistence
|
||||
return true, nil
|
||||
return shared.KeyHandled(msg)
|
||||
case "ctrl+t":
|
||||
m.showToolResults = !m.showToolResults
|
||||
m.rebuildMessageCache()
|
||||
m.updateContent()
|
||||
return true, nil
|
||||
return shared.KeyHandled(msg)
|
||||
case "ctrl+w":
|
||||
m.wrap = !m.wrap
|
||||
m.rebuildMessageCache()
|
||||
m.updateContent()
|
||||
return true, nil
|
||||
return shared.KeyHandled(msg)
|
||||
}
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleMessagesKey handles input when the messages pane is focused
|
||||
func (m *Model) handleMessagesKey(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
func (m *Model) handleMessagesKey(msg tea.KeyMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "tab", "enter":
|
||||
m.focus = focusInput
|
||||
m.updateContent()
|
||||
m.input.Focus()
|
||||
return true, nil
|
||||
return shared.KeyHandled(msg)
|
||||
case "e":
|
||||
if m.selectedMessage < len(m.App.Messages) {
|
||||
m.editorTarget = selectedMessage
|
||||
return true, tuiutil.OpenTempfileEditor(
|
||||
return tuiutil.OpenTempfileEditor(
|
||||
"message.*.md",
|
||||
m.App.Messages[m.selectedMessage].Content,
|
||||
"# Edit the message below\n",
|
||||
)
|
||||
}
|
||||
return false, nil
|
||||
return nil
|
||||
case "ctrl+k":
|
||||
if m.selectedMessage > 0 && len(m.App.Messages) == len(m.messageOffsets) {
|
||||
m.selectedMessage--
|
||||
@ -81,7 +81,7 @@ func (m *Model) handleMessagesKey(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
offset := m.messageOffsets[m.selectedMessage]
|
||||
tuiutil.ScrollIntoView(&m.content, offset, m.content.Height/2)
|
||||
}
|
||||
return true, nil
|
||||
return shared.KeyHandled(msg)
|
||||
case "ctrl+j":
|
||||
if m.selectedMessage < len(m.App.Messages)-1 && len(m.App.Messages) == len(m.messageOffsets) {
|
||||
m.selectedMessage++
|
||||
@ -89,7 +89,7 @@ func (m *Model) handleMessagesKey(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
offset := m.messageOffsets[m.selectedMessage]
|
||||
tuiutil.ScrollIntoView(&m.content, offset, m.content.Height/2)
|
||||
}
|
||||
return true, nil
|
||||
return shared.KeyHandled(msg)
|
||||
case "ctrl+h", "ctrl+l":
|
||||
dir := model.CyclePrev
|
||||
if msg.String() == "ctrl+l" {
|
||||
@ -102,8 +102,7 @@ func (m *Model) handleMessagesKey(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
} else if m.selectedMessage > 0 {
|
||||
cmd = m.cycleSelectedReply(&m.App.Messages[m.selectedMessage-1], dir)
|
||||
}
|
||||
|
||||
return cmd != nil, cmd
|
||||
return cmd
|
||||
case "ctrl+r":
|
||||
// resubmit the conversation with all messages up until and including the selected message
|
||||
if m.state == idle && m.selectedMessage < len(m.App.Messages) {
|
||||
@ -112,14 +111,14 @@ func (m *Model) handleMessagesKey(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
cmd := m.promptLLM()
|
||||
m.updateContent()
|
||||
m.content.GotoBottom()
|
||||
return true, cmd
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleInputKey handles input when the input textarea is focused
|
||||
func (m *Model) handleInputKey(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
func (m *Model) handleInputKey(msg tea.KeyMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "esc":
|
||||
m.focus = focusMessages
|
||||
@ -132,20 +131,20 @@ func (m *Model) handleInputKey(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
}
|
||||
m.updateContent()
|
||||
m.input.Blur()
|
||||
return true, nil
|
||||
return shared.KeyHandled(msg)
|
||||
case "ctrl+s":
|
||||
// TODO: call a "handleSend" function which returns a tea.Cmd
|
||||
if m.state != idle {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
input := strings.TrimSpace(m.input.Value())
|
||||
if input == "" {
|
||||
return true, nil
|
||||
return shared.KeyHandled(msg)
|
||||
}
|
||||
|
||||
if len(m.App.Messages) > 0 && m.App.Messages[len(m.App.Messages)-1].Role == api.MessageRoleUser {
|
||||
return true, shared.WrapError(fmt.Errorf("Can't reply to a user message"))
|
||||
return shared.WrapError(fmt.Errorf("Can't reply to a user message"))
|
||||
}
|
||||
|
||||
m.addMessage(api.Message{
|
||||
@ -164,11 +163,11 @@ func (m *Model) handleInputKey(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
|
||||
m.updateContent()
|
||||
m.content.GotoBottom()
|
||||
return true, tea.Batch(cmds...)
|
||||
return tea.Batch(cmds...)
|
||||
case "ctrl+e":
|
||||
cmd := tuiutil.OpenTempfileEditor("message.*.md", m.input.Value(), "# Edit your input below\n")
|
||||
m.editorTarget = input
|
||||
return true, cmd
|
||||
return cmd
|
||||
}
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
@ -47,9 +47,17 @@ func (m *Model) updateContent() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
func (m Model) Update(msg tea.Msg) (shared.ViewModel, tea.Cmd) {
|
||||
inputHandled := false
|
||||
|
||||
var cmds []tea.Cmd
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
cmd := m.handleInput(msg)
|
||||
if cmd != nil {
|
||||
inputHandled = true
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
m.Width, m.Height = msg.Width, msg.Height
|
||||
m.content.Width = msg.Width
|
||||
@ -167,7 +175,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
m.updateContent()
|
||||
case msgChatResponseError:
|
||||
m.state = idle
|
||||
m.Shared.Err = error(msg)
|
||||
m.ViewState.Err = error(msg)
|
||||
m.updateContent()
|
||||
case msgToolResults:
|
||||
last := len(m.App.Messages) - 1
|
||||
@ -231,14 +239,16 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
prevInputLineCnt := m.input.LineCount()
|
||||
inputCaptured := false
|
||||
|
||||
if !inputHandled {
|
||||
m.input, cmd = m.input.Update(msg)
|
||||
if cmd != nil {
|
||||
inputCaptured = true
|
||||
inputHandled = true
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
if !inputCaptured {
|
||||
if !inputHandled {
|
||||
m.content, cmd = m.content.Update(msg)
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
@ -285,5 +295,8 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(cmds) > 0 {
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
@ -25,8 +25,9 @@ type (
|
||||
// sent when a conversation is deleted
|
||||
msgConversationDeleted struct{}
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
shared.Shared
|
||||
*shared.ViewState
|
||||
shared.Sections
|
||||
|
||||
App *model.AppModel
|
||||
@ -38,21 +39,25 @@ type Model struct {
|
||||
confirmPrompt bubbles.ConfirmPrompt
|
||||
}
|
||||
|
||||
func Conversations(app *model.AppModel, shared shared.Shared) Model {
|
||||
func Conversations(app *model.AppModel, shared shared.ViewState) Model {
|
||||
m := Model{
|
||||
App: app,
|
||||
Shared: shared,
|
||||
ViewState: &shared,
|
||||
content: viewport.New(0, 0),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Model) HandleInput(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
func (m Model) Initialized() bool {
|
||||
return m.ViewState.Initialized
|
||||
}
|
||||
|
||||
func (m *Model) handleInput(msg tea.KeyMsg) tea.Cmd {
|
||||
if m.confirmPrompt.Focused() {
|
||||
var cmd tea.Cmd
|
||||
m.confirmPrompt, cmd = m.confirmPrompt.Update(msg)
|
||||
if cmd != nil {
|
||||
return true, cmd
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,8 +66,8 @@ func (m *Model) HandleInput(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
if len(m.App.Conversations) > 0 && m.cursor < len(m.App.Conversations) {
|
||||
m.App.Conversation = &m.App.Conversations[m.cursor].Conv
|
||||
m.App.Messages = []api.Message{}
|
||||
return true, func() tea.Msg {
|
||||
return shared.MsgViewChange(shared.StateChat)
|
||||
return func() tea.Msg {
|
||||
return shared.MsgViewChange(shared.ViewChat)
|
||||
}
|
||||
}
|
||||
case "j", "down":
|
||||
@ -81,7 +86,7 @@ func (m *Model) HandleInput(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
m.cursor = len(m.App.Conversations) - 1
|
||||
m.content.GotoBottom()
|
||||
}
|
||||
return true, nil
|
||||
return shared.KeyHandled(msg)
|
||||
case "k", "up":
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
@ -95,7 +100,7 @@ func (m *Model) HandleInput(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
m.cursor = 0
|
||||
m.content.GotoTop()
|
||||
}
|
||||
return true, nil
|
||||
return shared.KeyHandled(msg)
|
||||
case "n":
|
||||
// new conversation
|
||||
case "d":
|
||||
@ -111,7 +116,7 @@ func (m *Model) HandleInput(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
m.confirmPrompt.Style = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("3"))
|
||||
return true, nil
|
||||
return shared.KeyHandled(msg)
|
||||
}
|
||||
case "c":
|
||||
// copy/clone conversation
|
||||
@ -120,16 +125,27 @@ func (m *Model) HandleInput(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
case "shift+r":
|
||||
// show prompt to generate name for conversation
|
||||
}
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
m.ViewState.Initialized = true
|
||||
return m.loadConversations()
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
func (m Model) Update(msg tea.Msg) (shared.ViewModel, tea.Cmd) {
|
||||
isInput := false
|
||||
inputHandled := false
|
||||
|
||||
var cmds []tea.Cmd
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
isInput = true
|
||||
cmd := m.handleInput(msg)
|
||||
if cmd != nil {
|
||||
inputHandled = true
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
case shared.MsgViewEnter:
|
||||
cmds = append(cmds, m.loadConversations())
|
||||
m.content.SetContent(m.renderConversationList())
|
||||
@ -153,11 +169,13 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
cmds = append(cmds, m.loadConversations())
|
||||
}
|
||||
|
||||
if !isInput || !inputHandled {
|
||||
var cmd tea.Cmd
|
||||
m.content, cmd = m.content.Update(msg)
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
if m.Width > 0 {
|
||||
wrap := lipgloss.NewStyle().Width(m.Width)
|
||||
@ -171,7 +189,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
m.content.Height = m.Height - fixedHeight
|
||||
m.Content = m.content.View()
|
||||
}
|
||||
|
||||
if len(cmds) > 0 {
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *Model) loadConversations() tea.Cmd {
|
||||
@ -289,7 +312,7 @@ func (m *Model) renderConversationList() string {
|
||||
sb.WriteRune('\n')
|
||||
}
|
||||
|
||||
tStyle := titleStyle.Copy()
|
||||
tStyle := titleStyle
|
||||
if c.Conv.Title == "" {
|
||||
tStyle = tStyle.Inherit(untitledStyle).SetString("(untitled)")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user