Large refactor - it compiles!
This refactor splits out all conversation concerns into a new `conversation` package. There is now a split between `conversation` and `api`s representation of `Message`, the latter storing the minimum information required for interaction with LLM providers. There is necessary conversation between the two when making LLM calls.
This commit is contained in:
@@ -6,30 +6,30 @@ import (
|
||||
|
||||
"git.mlow.ca/mlow/lmcli/pkg/agents"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/api/provider"
|
||||
cmdutil "git.mlow.ca/mlow/lmcli/pkg/cmd/util"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/conversation"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/lmcli"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/provider"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
type LoadedConversation struct {
|
||||
Conv api.Conversation
|
||||
LastReply api.Message
|
||||
Conv conversation.Conversation
|
||||
LastReply conversation.Message
|
||||
}
|
||||
|
||||
type AppModel struct {
|
||||
Ctx *lmcli.Context
|
||||
Conversations []LoadedConversation
|
||||
Conversation *api.Conversation
|
||||
RootMessages []api.Message
|
||||
Messages []api.Message
|
||||
Conversation *conversation.Conversation
|
||||
Messages []conversation.Message
|
||||
Model string
|
||||
ProviderName string
|
||||
Provider provider.ChatCompletionProvider
|
||||
Agent *lmcli.Agent
|
||||
Agent *lmcli.Agent
|
||||
}
|
||||
|
||||
func NewAppModel(ctx *lmcli.Context, initialConversation *api.Conversation) *AppModel {
|
||||
func NewAppModel(ctx *lmcli.Context, initialConversation *conversation.Conversation) *AppModel {
|
||||
app := &AppModel{
|
||||
Ctx: ctx,
|
||||
Conversation: initialConversation,
|
||||
@@ -67,8 +67,7 @@ const (
|
||||
|
||||
func (m *AppModel) ClearConversation() {
|
||||
m.Conversation = nil
|
||||
m.Messages = []api.Message{}
|
||||
m.RootMessages = []api.Message{}
|
||||
m.Messages = []conversation.Message{}
|
||||
}
|
||||
|
||||
func (m *AppModel) ApplySystemPrompt() {
|
||||
@@ -81,7 +80,7 @@ func (m *AppModel) ApplySystemPrompt() {
|
||||
system = m.Ctx.DefaultSystemPrompt()
|
||||
}
|
||||
if system != "" {
|
||||
m.Messages = api.ApplySystemPrompt(m.Messages, system, false)
|
||||
m.Messages = conversation.ApplySystemPrompt(m.Messages, system, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +90,7 @@ func (m *AppModel) NewConversation() {
|
||||
}
|
||||
|
||||
func (m *AppModel) LoadConversations() (error, []LoadedConversation) {
|
||||
messages, err := m.Ctx.Store.LatestConversationMessages()
|
||||
messages, err := m.Ctx.Conversations.LatestConversationMessages()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not load conversations: %v", err), nil
|
||||
}
|
||||
@@ -106,42 +105,34 @@ func (m *AppModel) LoadConversations() (error, []LoadedConversation) {
|
||||
return nil, conversations
|
||||
}
|
||||
|
||||
func (a *AppModel) LoadConversationRootMessages() ([]api.Message, error) {
|
||||
messages, err := a.Ctx.Store.RootMessages(a.Conversation.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not load conversation root messages: %v %v", a.Conversation.SelectedRoot, err)
|
||||
}
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
func (a *AppModel) LoadConversationMessages() ([]api.Message, error) {
|
||||
messages, err := a.Ctx.Store.PathToLeaf(a.Conversation.SelectedRoot)
|
||||
func (a *AppModel) LoadConversationMessages() ([]conversation.Message, error) {
|
||||
messages, err := a.Ctx.Conversations.PathToLeaf(a.Conversation.SelectedRoot)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not load conversation messages: %v %v", a.Conversation.SelectedRoot, err)
|
||||
}
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
func (a *AppModel) GenerateConversationTitle(messages []api.Message) (string, error) {
|
||||
func (a *AppModel) GenerateConversationTitle(messages []conversation.Message) (string, error) {
|
||||
return cmdutil.GenerateTitle(a.Ctx, messages)
|
||||
}
|
||||
|
||||
func (a *AppModel) UpdateConversationTitle(conversation *api.Conversation) error {
|
||||
return a.Ctx.Store.UpdateConversation(conversation)
|
||||
func (a *AppModel) UpdateConversationTitle(conversation *conversation.Conversation) error {
|
||||
return a.Ctx.Conversations.UpdateConversation(conversation)
|
||||
}
|
||||
|
||||
func (a *AppModel) CloneMessage(message api.Message, selected bool) (*api.Message, error) {
|
||||
msg, _, err := a.Ctx.Store.CloneBranch(message)
|
||||
func (a *AppModel) CloneMessage(message conversation.Message, selected bool) (*conversation.Message, error) {
|
||||
msg, _, err := a.Ctx.Conversations.CloneBranch(message)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not clone message: %v", err)
|
||||
}
|
||||
if selected {
|
||||
if msg.Parent == nil {
|
||||
msg.Conversation.SelectedRoot = msg
|
||||
err = a.Ctx.Store.UpdateConversation(msg.Conversation)
|
||||
err = a.Ctx.Conversations.UpdateConversation(msg.Conversation)
|
||||
} else {
|
||||
msg.Parent.SelectedReply = msg
|
||||
err = a.Ctx.Store.UpdateMessage(msg.Parent)
|
||||
err = a.Ctx.Conversations.UpdateMessage(msg.Parent)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not update selected message: %v", err)
|
||||
@@ -150,11 +141,11 @@ func (a *AppModel) CloneMessage(message api.Message, selected bool) (*api.Messag
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (a *AppModel) UpdateMessageContent(message *api.Message) error {
|
||||
return a.Ctx.Store.UpdateMessage(message)
|
||||
func (a *AppModel) UpdateMessageContent(message *conversation.Message) error {
|
||||
return a.Ctx.Conversations.UpdateMessage(message)
|
||||
}
|
||||
|
||||
func cycleSelectedMessage(selected *api.Message, choices []api.Message, dir MessageCycleDirection) (*api.Message, error) {
|
||||
func cycleSelectedMessage(selected *conversation.Message, choices []conversation.Message, dir MessageCycleDirection) (*conversation.Message, error) {
|
||||
currentIndex := -1
|
||||
for i, reply := range choices {
|
||||
if reply.ID == selected.ID {
|
||||
@@ -176,25 +167,25 @@ func cycleSelectedMessage(selected *api.Message, choices []api.Message, dir Mess
|
||||
return &choices[next], nil
|
||||
}
|
||||
|
||||
func (a *AppModel) CycleSelectedRoot(conv *api.Conversation, rootMessages []api.Message, dir MessageCycleDirection) (*api.Message, error) {
|
||||
if len(rootMessages) < 2 {
|
||||
func (a *AppModel) CycleSelectedRoot(conv *conversation.Conversation, dir MessageCycleDirection) (*conversation.Message, error) {
|
||||
if len(conv.RootMessages) < 2 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nextRoot, err := cycleSelectedMessage(conv.SelectedRoot, rootMessages, dir)
|
||||
nextRoot, err := cycleSelectedMessage(conv.SelectedRoot, conv.RootMessages, dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conv.SelectedRoot = nextRoot
|
||||
err = a.Ctx.Store.UpdateConversation(conv)
|
||||
err = a.Ctx.Conversations.UpdateConversation(conv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not update conversation SelectedRoot: %v", err)
|
||||
}
|
||||
return nextRoot, nil
|
||||
}
|
||||
|
||||
func (a *AppModel) CycleSelectedReply(message *api.Message, dir MessageCycleDirection) (*api.Message, error) {
|
||||
func (a *AppModel) CycleSelectedReply(message *conversation.Message, dir MessageCycleDirection) (*conversation.Message, error) {
|
||||
if len(message.Replies) < 2 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -205,17 +196,17 @@ func (a *AppModel) CycleSelectedReply(message *api.Message, dir MessageCycleDire
|
||||
}
|
||||
|
||||
message.SelectedReply = nextReply
|
||||
err = a.Ctx.Store.UpdateMessage(message)
|
||||
err = a.Ctx.Conversations.UpdateMessage(message)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not update message SelectedReply: %v", err)
|
||||
}
|
||||
return nextReply, nil
|
||||
}
|
||||
|
||||
func (a *AppModel) PersistConversation(conversation *api.Conversation, messages []api.Message) (*api.Conversation, []api.Message, error) {
|
||||
func (a *AppModel) PersistConversation(conversation *conversation.Conversation, messages []conversation.Message) (*conversation.Conversation, []conversation.Message, error) {
|
||||
var err error
|
||||
if conversation == nil || conversation.ID == 0 {
|
||||
conversation, messages, err = a.Ctx.Store.StartConversation(messages...)
|
||||
conversation, messages, err = a.Ctx.Conversations.StartConversation(messages...)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Could not start new conversation: %v", err)
|
||||
}
|
||||
@@ -224,12 +215,12 @@ func (a *AppModel) PersistConversation(conversation *api.Conversation, messages
|
||||
|
||||
for i := range messages {
|
||||
if messages[i].ID > 0 {
|
||||
err := a.Ctx.Store.UpdateMessage(&messages[i])
|
||||
err := a.Ctx.Conversations.UpdateMessage(&messages[i])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
} else if i > 0 {
|
||||
saved, err := a.Ctx.Store.Reply(&messages[i-1], messages[i])
|
||||
saved, err := a.Ctx.Conversations.Reply(&messages[i-1], messages[i])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -251,10 +242,10 @@ func (a *AppModel) ExecuteToolCalls(toolCalls []api.ToolCall) ([]api.ToolResult,
|
||||
}
|
||||
|
||||
func (a *AppModel) Prompt(
|
||||
messages []api.Message,
|
||||
messages []conversation.Message,
|
||||
chatReplyChunks chan provider.Chunk,
|
||||
stopSignal chan struct{},
|
||||
) (*api.Message, error) {
|
||||
) (*conversation.Message, error) {
|
||||
model, _, p, err := a.Ctx.GetModelProvider(a.Model, a.ProviderName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -280,11 +271,14 @@ func (a *AppModel) Prompt(
|
||||
}()
|
||||
|
||||
msg, err := p.CreateChatCompletionStream(
|
||||
ctx, params, messages, chatReplyChunks,
|
||||
ctx, params, conversation.MessagesToAPI(messages), chatReplyChunks,
|
||||
)
|
||||
|
||||
if msg != nil {
|
||||
msg := conversation.MessageFromAPI(*msg)
|
||||
msg.Metadata.GenerationProvider = &a.ProviderName
|
||||
msg.Metadata.GenerationModel = &a.Model
|
||||
return &msg, err
|
||||
}
|
||||
return msg, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package tui
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/conversation"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/lmcli"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/tui/model"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/tui/shared"
|
||||
@@ -130,13 +130,13 @@ func (m *Model) View() string {
|
||||
}
|
||||
|
||||
type LaunchOptions struct {
|
||||
InitialConversation *api.Conversation
|
||||
InitialConversation *conversation.Conversation
|
||||
InitialView shared.View
|
||||
}
|
||||
|
||||
type LaunchOption func(*LaunchOptions)
|
||||
|
||||
func WithInitialConversation(conv *api.Conversation) LaunchOption {
|
||||
func WithInitialConversation(conv *conversation.Conversation) LaunchOption {
|
||||
return func(opts *LaunchOptions) {
|
||||
opts.InitialConversation = conv
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import (
|
||||
"time"
|
||||
|
||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/api/provider"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/provider"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/conversation"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/tui/model"
|
||||
"github.com/charmbracelet/bubbles/cursor"
|
||||
"github.com/charmbracelet/bubbles/spinner"
|
||||
@@ -20,14 +21,12 @@ type (
|
||||
msgConversationTitleGenerated string
|
||||
// sent when the conversation has been persisted, triggers a reload of contents
|
||||
msgConversationPersisted struct {
|
||||
isNew bool
|
||||
conversation *api.Conversation
|
||||
messages []api.Message
|
||||
conversation *conversation.Conversation
|
||||
messages []conversation.Message
|
||||
}
|
||||
// sent when a conversation's messages are laoded
|
||||
msgConversationMessagesLoaded struct {
|
||||
messages []api.Message
|
||||
rootMessages []api.Message
|
||||
messages []conversation.Message
|
||||
}
|
||||
// a special case of common.MsgError that stops the response waiting animation
|
||||
msgChatResponseError struct {
|
||||
@@ -36,19 +35,19 @@ type (
|
||||
// sent on each chunk received from LLM
|
||||
msgChatResponseChunk provider.Chunk
|
||||
// sent on each completed reply
|
||||
msgChatResponse *api.Message
|
||||
msgChatResponse *conversation.Message
|
||||
// sent when the response is canceled
|
||||
msgChatResponseCanceled struct{}
|
||||
// sent when results from a tool call are returned
|
||||
msgToolResults []api.ToolResult
|
||||
// sent when the given message is made the new selected reply of its parent
|
||||
msgSelectedReplyCycled *api.Message
|
||||
msgSelectedReplyCycled *conversation.Message
|
||||
// sent when the given message is made the new selected root of the current conversation
|
||||
msgSelectedRootCycled *api.Message
|
||||
msgSelectedRootCycled *conversation.Message
|
||||
// sent when a message's contents are updated and saved
|
||||
msgMessageUpdated *api.Message
|
||||
msgMessageUpdated *conversation.Message
|
||||
// sent when a message is cloned, with the cloned message
|
||||
msgMessageCloned *api.Message
|
||||
msgMessageCloned *conversation.Message
|
||||
)
|
||||
|
||||
type focusState int
|
||||
@@ -84,7 +83,7 @@ type Model struct {
|
||||
selectedMessage int
|
||||
editorTarget editorTarget
|
||||
stopSignal chan struct{}
|
||||
replyChan chan api.Message
|
||||
replyChan chan conversation.Message
|
||||
chatReplyChunks chan provider.Chunk
|
||||
persistence bool // whether we will save new messages in the conversation
|
||||
|
||||
@@ -137,7 +136,7 @@ func Chat(app *model.AppModel) *Model {
|
||||
persistence: true,
|
||||
|
||||
stopSignal: make(chan struct{}),
|
||||
replyChan: make(chan api.Message),
|
||||
replyChan: make(chan conversation.Message),
|
||||
chatReplyChunks: make(chan provider.Chunk),
|
||||
|
||||
wrap: true,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/conversation"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/tui/model"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/tui/shared"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
@@ -21,13 +22,7 @@ func (m *Model) loadConversationMessages() tea.Cmd {
|
||||
if err != nil {
|
||||
return shared.AsMsgError(err)
|
||||
}
|
||||
rootMessages, err := m.App.LoadConversationRootMessages()
|
||||
if err != nil {
|
||||
return shared.AsMsgError(err)
|
||||
}
|
||||
return msgConversationMessagesLoaded{
|
||||
messages, rootMessages,
|
||||
}
|
||||
return msgConversationMessagesLoaded{messages}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +36,7 @@ func (m *Model) generateConversationTitle() tea.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) updateConversationTitle(conversation *api.Conversation) tea.Cmd {
|
||||
func (m *Model) updateConversationTitle(conversation *conversation.Conversation) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
err := m.App.UpdateConversationTitle(conversation)
|
||||
if err != nil {
|
||||
@@ -51,7 +46,7 @@ func (m *Model) updateConversationTitle(conversation *api.Conversation) tea.Cmd
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) cloneMessage(message api.Message, selected bool) tea.Cmd {
|
||||
func (m *Model) cloneMessage(message conversation.Message, selected bool) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
msg, err := m.App.CloneMessage(message, selected)
|
||||
if err != nil {
|
||||
@@ -61,7 +56,7 @@ func (m *Model) cloneMessage(message api.Message, selected bool) tea.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) updateMessageContent(message *api.Message) tea.Cmd {
|
||||
func (m *Model) updateMessageContent(message *conversation.Message) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
err := m.App.UpdateMessageContent(message)
|
||||
if err != nil {
|
||||
@@ -71,14 +66,13 @@ func (m *Model) updateMessageContent(message *api.Message) tea.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) cycleSelectedRoot(conv *api.Conversation, dir model.MessageCycleDirection) tea.Cmd {
|
||||
if len(m.App.RootMessages) < 2 {
|
||||
|
||||
func (m *Model) cycleSelectedRoot(conv *conversation.Conversation, dir model.MessageCycleDirection) tea.Cmd {
|
||||
if len(conv.RootMessages) < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return func() tea.Msg {
|
||||
nextRoot, err := m.App.CycleSelectedRoot(conv, m.App.RootMessages, dir)
|
||||
nextRoot, err := m.App.CycleSelectedRoot(conv, dir)
|
||||
if err != nil {
|
||||
return shared.WrapError(err)
|
||||
}
|
||||
@@ -86,7 +80,7 @@ func (m *Model) cycleSelectedRoot(conv *api.Conversation, dir model.MessageCycle
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) cycleSelectedReply(message *api.Message, dir model.MessageCycleDirection) tea.Cmd {
|
||||
func (m *Model) cycleSelectedReply(message *conversation.Message, dir model.MessageCycleDirection) tea.Cmd {
|
||||
if len(message.Replies) < 2 {
|
||||
return nil
|
||||
}
|
||||
@@ -106,7 +100,7 @@ func (m *Model) persistConversation() tea.Cmd {
|
||||
if err != nil {
|
||||
return shared.AsMsgError(err)
|
||||
}
|
||||
return msgConversationPersisted{conversation.ID == 0, conversation, messages}
|
||||
return msgConversationPersisted{conversation, messages}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/conversation"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/tui/model"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/tui/shared"
|
||||
tuiutil "git.mlow.ca/mlow/lmcli/pkg/tui/util"
|
||||
@@ -70,12 +71,12 @@ func (m *Model) handleInput(msg tea.KeyMsg) tea.Cmd {
|
||||
}
|
||||
|
||||
func (m *Model) scrollSelection(dir int) {
|
||||
if m.selectedMessage + dir < 0 || m.selectedMessage + dir >= len(m.App.Messages) {
|
||||
if m.selectedMessage+dir < 0 || m.selectedMessage+dir >= len(m.App.Messages) {
|
||||
return
|
||||
}
|
||||
|
||||
newIdx := m.selectedMessage
|
||||
for i := newIdx + dir; i >= 0 && i < len(m.App.Messages); i += dir{
|
||||
for i := newIdx + dir; i >= 0 && i < len(m.App.Messages); i += dir {
|
||||
if !m.showDetails && m.App.Messages[i].Role.IsSystem() {
|
||||
continue
|
||||
}
|
||||
@@ -175,7 +176,7 @@ func (m *Model) handleInputKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return shared.WrapError(fmt.Errorf("Can't reply to a user message"))
|
||||
}
|
||||
|
||||
m.addMessage(api.Message{
|
||||
m.addMessage(conversation.Message{
|
||||
Role: api.MessageRoleUser,
|
||||
Content: input,
|
||||
})
|
||||
|
||||
@@ -5,13 +5,14 @@ import (
|
||||
"time"
|
||||
|
||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/conversation"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/tui/shared"
|
||||
tuiutil "git.mlow.ca/mlow/lmcli/pkg/tui/util"
|
||||
"github.com/charmbracelet/bubbles/cursor"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func (m *Model) setMessage(i int, msg api.Message) {
|
||||
func (m *Model) setMessage(i int, msg conversation.Message) {
|
||||
if i >= len(m.App.Messages) {
|
||||
panic("i out of range")
|
||||
}
|
||||
@@ -19,7 +20,7 @@ func (m *Model) setMessage(i int, msg api.Message) {
|
||||
m.messageCache[i] = m.renderMessage(i)
|
||||
}
|
||||
|
||||
func (m *Model) addMessage(msg api.Message) {
|
||||
func (m *Model) addMessage(msg conversation.Message) {
|
||||
m.App.Messages = append(m.App.Messages, msg)
|
||||
m.messageCache = append(m.messageCache, m.renderMessage(len(m.App.Messages)-1))
|
||||
}
|
||||
@@ -95,7 +96,6 @@ func (m *Model) Update(msg tea.Msg) (shared.ViewModel, tea.Cmd) {
|
||||
}
|
||||
}
|
||||
case msgConversationMessagesLoaded:
|
||||
m.App.RootMessages = msg.rootMessages
|
||||
m.App.Messages = msg.messages
|
||||
if m.selectedMessage == -1 {
|
||||
m.selectedMessage = len(msg.messages) - 1
|
||||
@@ -117,7 +117,7 @@ func (m *Model) Update(msg tea.Msg) (shared.ViewModel, tea.Cmd) {
|
||||
m.setMessageContents(last, m.App.Messages[last].Content+msg.Content)
|
||||
} else {
|
||||
// use chunk in a new message
|
||||
m.addMessage(api.Message{
|
||||
m.addMessage(conversation.Message{
|
||||
Role: api.MessageRoleAssistant,
|
||||
Content: msg.Content,
|
||||
})
|
||||
@@ -133,7 +133,7 @@ func (m *Model) Update(msg tea.Msg) (shared.ViewModel, tea.Cmd) {
|
||||
case msgChatResponse:
|
||||
m.state = idle
|
||||
|
||||
reply := (*api.Message)(msg)
|
||||
reply := (*conversation.Message)(msg)
|
||||
reply.Content = strings.TrimSpace(reply.Content)
|
||||
|
||||
last := len(m.App.Messages) - 1
|
||||
@@ -181,9 +181,9 @@ func (m *Model) Update(msg tea.Msg) (shared.ViewModel, tea.Cmd) {
|
||||
panic("Previous message not a tool call, unexpected")
|
||||
}
|
||||
|
||||
m.addMessage(api.Message{
|
||||
m.addMessage(conversation.Message{
|
||||
Role: api.MessageRoleToolResult,
|
||||
ToolResults: api.ToolResults(msg),
|
||||
ToolResults: conversation.ToolResults(msg),
|
||||
})
|
||||
|
||||
if m.persistence {
|
||||
@@ -207,15 +207,11 @@ func (m *Model) Update(msg tea.Msg) (shared.ViewModel, tea.Cmd) {
|
||||
case msgConversationPersisted:
|
||||
m.App.Conversation = msg.conversation
|
||||
m.App.Messages = msg.messages
|
||||
if msg.isNew {
|
||||
m.App.RootMessages = []api.Message{m.App.Messages[0]}
|
||||
}
|
||||
m.rebuildMessageCache()
|
||||
m.updateContent()
|
||||
case msgMessageCloned:
|
||||
if msg.Parent == nil {
|
||||
m.App.Conversation = msg.Conversation
|
||||
m.App.RootMessages = append(m.App.RootMessages, *msg)
|
||||
}
|
||||
cmds = append(cmds, m.loadConversationMessages())
|
||||
case msgSelectedRootCycled, msgSelectedReplyCycled, msgMessageUpdated:
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/conversation"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/tui/styles"
|
||||
tuiutil "git.mlow.ca/mlow/lmcli/pkg/tui/util"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
@@ -44,7 +45,7 @@ var (
|
||||
footerStyle = lipgloss.NewStyle().Padding(0, 1)
|
||||
)
|
||||
|
||||
func (m *Model) renderMessageHeading(i int, message *api.Message) string {
|
||||
func (m *Model) renderMessageHeading(i int, message *conversation.Message) string {
|
||||
friendly := message.Role.FriendlyRole()
|
||||
style := systemStyle
|
||||
|
||||
@@ -70,15 +71,15 @@ func (m *Model) renderMessageHeading(i int, message *api.Message) string {
|
||||
prefix = " "
|
||||
}
|
||||
|
||||
if i == 0 && len(m.App.RootMessages) > 1 && m.App.Conversation.SelectedRootID != nil {
|
||||
if i == 0 && len(m.App.Conversation.RootMessages) > 1 && m.App.Conversation.SelectedRootID != nil {
|
||||
selectedRootIndex := 0
|
||||
for j, reply := range m.App.RootMessages {
|
||||
for j, reply := range m.App.Conversation.RootMessages {
|
||||
if reply.ID == *m.App.Conversation.SelectedRootID {
|
||||
selectedRootIndex = j
|
||||
break
|
||||
}
|
||||
}
|
||||
suffix += faintStyle.Render(fmt.Sprintf(" <%d/%d>", selectedRootIndex+1, len(m.App.RootMessages)))
|
||||
suffix += faintStyle.Render(fmt.Sprintf(" <%d/%d>", selectedRootIndex+1, len(m.App.Conversation.RootMessages)))
|
||||
}
|
||||
if i > 0 && len(m.App.Messages[i-1].Replies) > 1 {
|
||||
// Find the selected reply index
|
||||
@@ -230,9 +231,9 @@ func (m *Model) conversationMessagesView() string {
|
||||
|
||||
// Render a placeholder for the incoming assistant reply
|
||||
if m.state == pendingResponse && m.App.Messages[len(m.App.Messages)-1].Role != api.MessageRoleAssistant {
|
||||
heading := m.renderMessageHeading(-1, &api.Message{
|
||||
heading := m.renderMessageHeading(-1, &conversation.Message{
|
||||
Role: api.MessageRoleAssistant,
|
||||
Metadata: api.MessageMeta{
|
||||
Metadata: conversation.MessageMeta{
|
||||
GenerationModel: &m.App.Model,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.mlow.ca/mlow/lmcli/pkg/api"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/conversation"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/tui/bubbles"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/tui/model"
|
||||
"git.mlow.ca/mlow/lmcli/pkg/tui/shared"
|
||||
@@ -21,7 +21,7 @@ type (
|
||||
// sent when conversation list is loaded
|
||||
msgConversationsLoaded ([]model.LoadedConversation)
|
||||
// sent when a conversation is selected
|
||||
msgConversationSelected api.Conversation
|
||||
msgConversationSelected conversation.Conversation
|
||||
// sent when a conversation is deleted
|
||||
msgConversationDeleted struct{}
|
||||
)
|
||||
@@ -154,7 +154,7 @@ func (m *Model) Update(msg tea.Msg) (shared.ViewModel, tea.Cmd) {
|
||||
case bubbles.MsgConfirmPromptAnswered:
|
||||
m.confirmPrompt.Blur()
|
||||
if msg.Value {
|
||||
conv, ok := msg.Payload.(api.Conversation)
|
||||
conv, ok := msg.Payload.(conversation.Conversation)
|
||||
if ok {
|
||||
cmds = append(cmds, m.deleteConversation(conv))
|
||||
}
|
||||
@@ -188,9 +188,9 @@ func (m *Model) loadConversations() tea.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) deleteConversation(conv api.Conversation) tea.Cmd {
|
||||
func (m *Model) deleteConversation(conv conversation.Conversation) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
err := m.App.Ctx.Store.DeleteConversation(&conv)
|
||||
err := m.App.Ctx.Conversations.DeleteConversation(&conv)
|
||||
if err != nil {
|
||||
return shared.AsMsgError(fmt.Errorf("Could not delete conversation: %v", err))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user