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