Display generation model in message header and other tweaks
Adjusted `ctrl+t` in chat view to toggle `showDetails` which toggles the display of system messages, message metadata (generation model), and tool call details Modified message selection update logic to skip messages that aren't shown
This commit is contained in:
parent
5d13c3e056
commit
bb48bc9abd
@ -71,14 +71,22 @@ func ApplySystemPrompt(m []Message, system string, force bool) []Message {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MessageRole) IsAssistant() bool {
|
||||
switch *m {
|
||||
func (m MessageRole) IsAssistant() bool {
|
||||
switch m {
|
||||
case MessageRoleAssistant, MessageRoleToolCall:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m MessageRole) IsSystem() bool {
|
||||
switch m {
|
||||
case MessageRoleSystem:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FriendlyRole returns a human friendly signifier for the message's role.
|
||||
func (m MessageRole) FriendlyRole() string {
|
||||
switch m {
|
||||
|
@ -89,11 +89,11 @@ type Model struct {
|
||||
persistence bool // whether we will save new messages in the conversation
|
||||
|
||||
// UI state
|
||||
focus focusState
|
||||
wrap bool // whether message content is wrapped to viewport width
|
||||
showToolResults bool // whether tool calls and results are shown
|
||||
messageCache []string // cache of syntax highlighted and wrapped message content
|
||||
messageOffsets []int
|
||||
focus focusState
|
||||
showDetails bool // whether various details are shown in the UI (e.g. system prompt, tool calls/results, message metadata)
|
||||
wrap bool // whether message content is wrapped to viewport width
|
||||
messageCache []string // cache of syntax highlighted and wrapped message content
|
||||
messageOffsets []int
|
||||
|
||||
// ui elements
|
||||
content viewport.Model
|
||||
|
@ -12,17 +12,6 @@ import (
|
||||
)
|
||||
|
||||
func (m *Model) handleInput(msg tea.KeyMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
case "ctrl+g":
|
||||
if m.state == pendingResponse {
|
||||
m.stopSignal <- struct{}{}
|
||||
return shared.KeyHandled(msg)
|
||||
}
|
||||
return func() tea.Msg {
|
||||
return shared.MsgViewChange(shared.ViewSettings)
|
||||
}
|
||||
}
|
||||
|
||||
switch m.focus {
|
||||
case focusInput:
|
||||
cmd := m.handleInputKey(msg)
|
||||
@ -50,11 +39,19 @@ func (m *Model) handleInput(msg tea.KeyMsg) tea.Cmd {
|
||||
m.stopSignal <- struct{}{}
|
||||
return shared.KeyHandled(msg)
|
||||
}
|
||||
case "ctrl+g":
|
||||
if m.state == pendingResponse {
|
||||
m.stopSignal <- struct{}{}
|
||||
return shared.KeyHandled(msg)
|
||||
}
|
||||
return func() tea.Msg {
|
||||
return shared.MsgViewChange(shared.ViewSettings)
|
||||
}
|
||||
case "ctrl+p":
|
||||
m.persistence = !m.persistence
|
||||
return shared.KeyHandled(msg)
|
||||
case "ctrl+t":
|
||||
m.showToolResults = !m.showToolResults
|
||||
m.showDetails = !m.showDetails
|
||||
m.rebuildMessageCache()
|
||||
m.updateContent()
|
||||
return shared.KeyHandled(msg)
|
||||
@ -72,6 +69,27 @@ func (m *Model) handleInput(msg tea.KeyMsg) tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Model) scrollSelection(dir int) {
|
||||
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{
|
||||
if !m.showDetails && m.App.Messages[i].Role.IsSystem() {
|
||||
continue
|
||||
}
|
||||
newIdx = i
|
||||
break
|
||||
}
|
||||
if newIdx != m.selectedMessage {
|
||||
m.selectedMessage = newIdx
|
||||
m.updateContent()
|
||||
yOffset := m.messageOffsets[m.selectedMessage]
|
||||
tuiutil.ScrollIntoView(&m.content, yOffset, m.content.Height/2)
|
||||
}
|
||||
}
|
||||
|
||||
// handleMessagesKey handles input when the messages pane is focused
|
||||
func (m *Model) handleMessagesKey(msg tea.KeyMsg) tea.Cmd {
|
||||
switch msg.String() {
|
||||
@ -90,25 +108,19 @@ func (m *Model) handleMessagesKey(msg tea.KeyMsg) tea.Cmd {
|
||||
)
|
||||
}
|
||||
return nil
|
||||
case "ctrl+k":
|
||||
if m.selectedMessage > 0 && len(m.App.Messages) == len(m.messageOffsets) {
|
||||
m.selectedMessage--
|
||||
m.updateContent()
|
||||
offset := m.messageOffsets[m.selectedMessage]
|
||||
tuiutil.ScrollIntoView(&m.content, offset, m.content.Height/2)
|
||||
case "ctrl+k", "ctrl+up":
|
||||
if m.selectedMessage > 0 {
|
||||
m.scrollSelection(-1)
|
||||
}
|
||||
return shared.KeyHandled(msg)
|
||||
case "ctrl+j":
|
||||
if m.selectedMessage < len(m.App.Messages)-1 && len(m.App.Messages) == len(m.messageOffsets) {
|
||||
m.selectedMessage++
|
||||
m.updateContent()
|
||||
offset := m.messageOffsets[m.selectedMessage]
|
||||
tuiutil.ScrollIntoView(&m.content, offset, m.content.Height/2)
|
||||
case "ctrl+j", "ctrl+down":
|
||||
if m.selectedMessage < len(m.App.Messages)-1 {
|
||||
m.scrollSelection(1)
|
||||
}
|
||||
return shared.KeyHandled(msg)
|
||||
case "ctrl+h", "ctrl+l":
|
||||
case "ctrl+h", "ctrl+left", "ctrl+l", "ctrl+right":
|
||||
dir := model.CyclePrev
|
||||
if msg.String() == "ctrl+l" {
|
||||
if msg.String() == "ctrl+l" || msg.String() == "ctrl+right" {
|
||||
dir = model.CycleNext
|
||||
}
|
||||
|
||||
@ -120,7 +132,7 @@ func (m *Model) handleMessagesKey(msg tea.KeyMsg) tea.Cmd {
|
||||
}
|
||||
return cmd
|
||||
case "ctrl+r":
|
||||
// resubmit the conversation with all messages up until and including the selected message
|
||||
// prompt the model with all messages up to and including the selected message
|
||||
if m.state == idle && m.selectedMessage < len(m.App.Messages) {
|
||||
m.App.Messages = m.App.Messages[:m.selectedMessage+1]
|
||||
m.messageCache = m.messageCache[:m.selectedMessage+1]
|
||||
@ -130,7 +142,7 @@ func (m *Model) handleMessagesKey(msg tea.KeyMsg) tea.Cmd {
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleInputKey handles input when the input textarea is focused
|
||||
|
@ -16,14 +16,19 @@ import (
|
||||
|
||||
// styles
|
||||
var (
|
||||
boldStyle = lipgloss.NewStyle().Bold(true)
|
||||
faintStyle = lipgloss.NewStyle().Faint(true)
|
||||
boldFaintStyle = lipgloss.NewStyle().Faint(true).Bold(true)
|
||||
|
||||
messageHeadingStyle = lipgloss.NewStyle().
|
||||
MarginTop(1).
|
||||
MarginBottom(1).
|
||||
Bold(true)
|
||||
MarginBottom(1)
|
||||
|
||||
userStyle = lipgloss.NewStyle().Faint(true).Foreground(lipgloss.Color("10"))
|
||||
userStyle = boldFaintStyle.Foreground(lipgloss.Color("10"))
|
||||
|
||||
assistantStyle = lipgloss.NewStyle().Faint(true).Foreground(lipgloss.Color("12"))
|
||||
assistantStyle = boldFaintStyle.Foreground(lipgloss.Color("12"))
|
||||
|
||||
systemStyle = boldStyle.Foreground(lipgloss.Color("8"))
|
||||
|
||||
messageStyle = lipgloss.NewStyle().
|
||||
PaddingLeft(2).
|
||||
@ -40,13 +45,10 @@ var (
|
||||
)
|
||||
|
||||
func (m *Model) renderMessageHeading(i int, message *api.Message) string {
|
||||
icon := ""
|
||||
friendly := message.Role.FriendlyRole()
|
||||
style := lipgloss.NewStyle().Faint(true).Bold(true)
|
||||
style := systemStyle
|
||||
|
||||
switch message.Role {
|
||||
case api.MessageRoleSystem:
|
||||
icon = "⚙️"
|
||||
case api.MessageRoleUser:
|
||||
style = userStyle
|
||||
case api.MessageRoleAssistant:
|
||||
@ -54,16 +56,19 @@ func (m *Model) renderMessageHeading(i int, message *api.Message) string {
|
||||
case api.MessageRoleToolCall:
|
||||
style = assistantStyle
|
||||
friendly = api.MessageRoleAssistant.FriendlyRole()
|
||||
case api.MessageRoleSystem:
|
||||
case api.MessageRoleToolResult:
|
||||
icon = "🔧"
|
||||
}
|
||||
|
||||
user := style.Render(icon + friendly)
|
||||
user := style.Render(friendly)
|
||||
|
||||
var prefix string
|
||||
var suffix string
|
||||
var prefix, suffix string
|
||||
|
||||
faint := lipgloss.NewStyle().Faint(true)
|
||||
if i == m.selectedMessage {
|
||||
prefix = "> "
|
||||
} else {
|
||||
prefix = " "
|
||||
}
|
||||
|
||||
if i == 0 && len(m.App.RootMessages) > 1 && m.App.Conversation.SelectedRootID != nil {
|
||||
selectedRootIndex := 0
|
||||
@ -73,7 +78,7 @@ func (m *Model) renderMessageHeading(i int, message *api.Message) string {
|
||||
break
|
||||
}
|
||||
}
|
||||
suffix += faint.Render(fmt.Sprintf(" <%d/%d>", selectedRootIndex+1, len(m.App.RootMessages)))
|
||||
suffix += faintStyle.Render(fmt.Sprintf(" <%d/%d>", selectedRootIndex+1, len(m.App.RootMessages)))
|
||||
}
|
||||
if i > 0 && len(m.App.Messages[i-1].Replies) > 1 {
|
||||
// Find the selected reply index
|
||||
@ -84,20 +89,22 @@ func (m *Model) renderMessageHeading(i int, message *api.Message) string {
|
||||
break
|
||||
}
|
||||
}
|
||||
suffix += faint.Render(fmt.Sprintf(" <%d/%d>", selectedReplyIndex+1, len(m.App.Messages[i-1].Replies)))
|
||||
}
|
||||
|
||||
if i == m.selectedMessage {
|
||||
prefix = "> "
|
||||
} else {
|
||||
prefix = " "
|
||||
suffix += faintStyle.Render(fmt.Sprintf(" <%d/%d>", selectedReplyIndex+1, len(m.App.Messages[i-1].Replies)))
|
||||
}
|
||||
|
||||
if message.ID == 0 {
|
||||
suffix += faint.Render(" (not saved)")
|
||||
suffix += faintStyle.Render(" (not saved)")
|
||||
}
|
||||
|
||||
return messageHeadingStyle.Render(prefix + user + suffix)
|
||||
heading := prefix + user + suffix
|
||||
|
||||
if message.Metadata.GenerationModel != nil && m.showDetails {
|
||||
heading += faintStyle.Render(
|
||||
fmt.Sprintf(" | %s", *message.Metadata.GenerationModel),
|
||||
)
|
||||
}
|
||||
|
||||
return messageHeadingStyle.Render(heading)
|
||||
}
|
||||
|
||||
// renderMessages renders the message at the given index as it should be shown
|
||||
@ -143,7 +150,7 @@ func (m *Model) renderMessage(i int) string {
|
||||
|
||||
var toolResults []renderedResult
|
||||
for _, result := range msg.ToolResults {
|
||||
if m.showToolResults {
|
||||
if m.showDetails {
|
||||
var jsonResult interface{}
|
||||
err := json.Unmarshal([]byte(result.Result), &jsonResult)
|
||||
if err != nil {
|
||||
@ -206,6 +213,10 @@ func (m *Model) conversationMessagesView() string {
|
||||
for i, message := range m.App.Messages {
|
||||
m.messageOffsets[i] = lineCnt
|
||||
|
||||
if !m.showDetails && message.Role.IsSystem() {
|
||||
continue
|
||||
}
|
||||
|
||||
heading := m.renderMessageHeading(i, &message)
|
||||
sb.WriteString(heading)
|
||||
sb.WriteString("\n")
|
||||
@ -221,6 +232,9 @@ func (m *Model) conversationMessagesView() string {
|
||||
if m.state == pendingResponse && m.App.Messages[len(m.App.Messages)-1].Role != api.MessageRoleAssistant {
|
||||
heading := m.renderMessageHeading(-1, &api.Message{
|
||||
Role: api.MessageRoleAssistant,
|
||||
Metadata: api.MessageMeta {
|
||||
GenerationModel: &m.App.Model,
|
||||
},
|
||||
})
|
||||
sb.WriteString(heading)
|
||||
sb.WriteString("\n")
|
||||
|
Loading…
Reference in New Issue
Block a user