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 {
|
||||||
|
@ -90,8 +90,8 @@ type Model struct {
|
|||||||
|
|
||||||
// UI state
|
// UI state
|
||||||
focus focusState
|
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
|
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
|
messageCache []string // cache of syntax highlighted and wrapped message content
|
||||||
messageOffsets []int
|
messageOffsets []int
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
@ -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