mirror of
https://github.com/sst/opencode.git
synced 2025-09-09 22:46:18 +00:00
wip: refactoring tui
This commit is contained in:
parent
fce9e79d38
commit
26606ccbf7
3 changed files with 198 additions and 267 deletions
|
@ -11,7 +11,6 @@ import (
|
||||||
"github.com/charmbracelet/x/ansi"
|
"github.com/charmbracelet/x/ansi"
|
||||||
"github.com/sst/opencode/internal/config"
|
"github.com/sst/opencode/internal/config"
|
||||||
"github.com/sst/opencode/internal/diff"
|
"github.com/sst/opencode/internal/diff"
|
||||||
"github.com/sst/opencode/internal/llm/models"
|
|
||||||
"github.com/sst/opencode/internal/llm/tools"
|
"github.com/sst/opencode/internal/llm/tools"
|
||||||
"github.com/sst/opencode/internal/message"
|
"github.com/sst/opencode/internal/message"
|
||||||
"github.com/sst/opencode/internal/tui/styles"
|
"github.com/sst/opencode/internal/tui/styles"
|
||||||
|
@ -22,64 +21,25 @@ import (
|
||||||
type uiMessageType int
|
type uiMessageType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
userMessageType uiMessageType = iota
|
|
||||||
assistantMessageType
|
|
||||||
toolMessageType
|
|
||||||
|
|
||||||
maxResultHeight = 10
|
maxResultHeight = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
type uiMessage struct {
|
func toMarkdown(content string, width int) string {
|
||||||
ID string
|
|
||||||
messageType uiMessageType
|
|
||||||
content string
|
|
||||||
}
|
|
||||||
|
|
||||||
func toMarkdown(content string, focused bool, width int) string {
|
|
||||||
r := styles.GetMarkdownRenderer(width)
|
r := styles.GetMarkdownRenderer(width)
|
||||||
rendered, _ := r.Render(content)
|
rendered, _ := r.Render(content)
|
||||||
return rendered
|
return strings.TrimSuffix(rendered, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderMessage(msg string, isUser bool, isFocused bool, width int, info ...string) string {
|
func renderUserMessage(msg client.MessageInfo, width int) string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
|
|
||||||
style := styles.BaseStyle().
|
style := styles.BaseStyle().
|
||||||
// Width(width - 1).
|
|
||||||
BorderLeft(true).
|
BorderLeft(true).
|
||||||
Foreground(t.TextMuted()).
|
Foreground(t.TextMuted()).
|
||||||
BorderForeground(t.Primary()).
|
BorderForeground(t.Secondary()).
|
||||||
BorderStyle(lipgloss.ThickBorder())
|
BorderStyle(lipgloss.ThickBorder())
|
||||||
|
|
||||||
if isUser {
|
|
||||||
style = style.BorderForeground(t.Secondary())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply markdown formatting and handle background color
|
|
||||||
parts := []string{
|
|
||||||
styles.ForceReplaceBackgroundWithLipgloss(toMarkdown(msg, isFocused, width), t.Background()),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove newline at the end
|
|
||||||
parts[0] = strings.TrimSuffix(parts[0], "\n")
|
|
||||||
if len(info) > 0 {
|
|
||||||
parts = append(parts, info...)
|
|
||||||
}
|
|
||||||
|
|
||||||
rendered := style.Render(
|
|
||||||
lipgloss.JoinVertical(
|
|
||||||
lipgloss.Left,
|
|
||||||
parts...,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
return rendered
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderUserMessage(msg client.MessageInfo, isFocused bool, width int) uiMessage {
|
|
||||||
// var styledAttachments []string
|
|
||||||
t := theme.CurrentTheme()
|
|
||||||
baseStyle := styles.BaseStyle()
|
baseStyle := styles.BaseStyle()
|
||||||
|
// var styledAttachments []string
|
||||||
// attachmentStyles := baseStyle.
|
// attachmentStyles := baseStyle.
|
||||||
// MarginLeft(1).
|
// MarginLeft(1).
|
||||||
// Background(t.TextMuted()).
|
// Background(t.TextMuted()).
|
||||||
|
@ -95,16 +55,12 @@ func renderUserMessage(msg client.MessageInfo, isFocused bool, width int) uiMess
|
||||||
// styledAttachments = append(styledAttachments, attachmentStyles.Render(filename))
|
// styledAttachments = append(styledAttachments, attachmentStyles.Render(filename))
|
||||||
// }
|
// }
|
||||||
|
|
||||||
info := []string{}
|
|
||||||
|
|
||||||
// Add timestamp info
|
// Add timestamp info
|
||||||
timestamp := time.UnixMilli(int64(msg.Metadata.Time.Created)).Local().Format("02 Jan 2006 03:04 PM")
|
timestamp := time.UnixMilli(int64(msg.Metadata.Time.Created)).Local().Format("02 Jan 2006 03:04 PM")
|
||||||
username, _ := config.GetUsername()
|
username, _ := config.GetUsername()
|
||||||
info = append(info, baseStyle.
|
info := baseStyle.
|
||||||
Width(width-1).
|
|
||||||
Foreground(t.TextMuted()).
|
Foreground(t.TextMuted()).
|
||||||
Render(fmt.Sprintf(" %s (%s)", username, timestamp)),
|
Render(fmt.Sprintf(" %s (%s)", username, timestamp))
|
||||||
)
|
|
||||||
|
|
||||||
content := ""
|
content := ""
|
||||||
// if len(styledAttachments) > 0 {
|
// if len(styledAttachments) > 0 {
|
||||||
|
@ -120,125 +76,175 @@ func renderUserMessage(msg client.MessageInfo, isFocused bool, width int) uiMess
|
||||||
switch part.(type) {
|
switch part.(type) {
|
||||||
case client.MessagePartText:
|
case client.MessagePartText:
|
||||||
textPart := part.(client.MessagePartText)
|
textPart := part.(client.MessagePartText)
|
||||||
content = renderMessage(textPart.Text, true, isFocused, width, info...)
|
text := toMarkdown(textPart.Text, width)
|
||||||
|
content = style.Render(lipgloss.JoinVertical(lipgloss.Left, text, info))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// content = renderMessage(msg.Parts, true, isFocused, width, info...)
|
|
||||||
|
|
||||||
userMsg := uiMessage{
|
return content
|
||||||
ID: msg.Id,
|
}
|
||||||
messageType: userMessageType,
|
|
||||||
content: content,
|
func convertToMap(input *any) (map[string]any, bool) {
|
||||||
}
|
if input == nil {
|
||||||
return userMsg
|
return nil, false // Handle nil pointer
|
||||||
|
}
|
||||||
|
value := *input // Dereference the pointer to get the interface value
|
||||||
|
m, ok := value.(map[string]any) // Type assertion
|
||||||
|
return m, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns multiple uiMessages because of the tool calls
|
|
||||||
func renderAssistantMessage(
|
func renderAssistantMessage(
|
||||||
msg message.Message,
|
msg client.MessageInfo,
|
||||||
msgIndex int,
|
|
||||||
allMessages []message.Message, // we need this to get tool results and the user message
|
|
||||||
messagesService message.Service, // We need this to get the task tool messages
|
|
||||||
focusedUIMessageId string,
|
|
||||||
width int,
|
width int,
|
||||||
position int,
|
|
||||||
showToolMessages bool,
|
showToolMessages bool,
|
||||||
) []uiMessage {
|
) string {
|
||||||
messages := []uiMessage{}
|
|
||||||
content := strings.TrimSpace(msg.Content().String())
|
|
||||||
thinking := msg.IsThinking()
|
|
||||||
thinkingContent := msg.ReasoningContent().Thinking
|
|
||||||
finished := msg.IsFinished()
|
|
||||||
finishData := msg.FinishPart()
|
|
||||||
info := []string{}
|
|
||||||
|
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
baseStyle := styles.BaseStyle()
|
style := styles.BaseStyle().
|
||||||
|
BorderLeft(true).
|
||||||
// Always add timestamp info
|
|
||||||
timestamp := msg.CreatedAt.Local().Format("02 Jan 2006 03:04 PM")
|
|
||||||
modelName := "Assistant"
|
|
||||||
if msg.Model != "" {
|
|
||||||
modelName = models.SupportedModels[msg.Model].Name
|
|
||||||
}
|
|
||||||
|
|
||||||
info = append(info, baseStyle.
|
|
||||||
Width(width-1).
|
|
||||||
Foreground(t.TextMuted()).
|
Foreground(t.TextMuted()).
|
||||||
Render(fmt.Sprintf(" %s (%s)", modelName, timestamp)),
|
BorderForeground(t.Primary()).
|
||||||
)
|
BorderStyle(lipgloss.ThickBorder())
|
||||||
|
toolStyle := styles.BaseStyle().
|
||||||
|
BorderLeft(true).
|
||||||
|
Foreground(t.TextMuted()).
|
||||||
|
BorderForeground(t.TextMuted()).
|
||||||
|
BorderStyle(lipgloss.ThickBorder())
|
||||||
|
|
||||||
if finished {
|
baseStyle := styles.BaseStyle()
|
||||||
// Add finish info if available
|
messages := []string{}
|
||||||
switch finishData.Reason {
|
|
||||||
case message.FinishReasonCanceled:
|
// content := strings.TrimSpace(msg.Content().String())
|
||||||
info = append(info, baseStyle.
|
// thinking := msg.IsThinking()
|
||||||
Width(width-1).
|
// thinkingContent := msg.ReasoningContent().Thinking
|
||||||
Foreground(t.Warning()).
|
// finished := msg.IsFinished()
|
||||||
Render("(canceled)"),
|
// finishData := msg.FinishPart()
|
||||||
)
|
|
||||||
case message.FinishReasonError:
|
// Add timestamp info
|
||||||
info = append(info, baseStyle.
|
timestamp := time.UnixMilli(int64(msg.Metadata.Time.Created)).Local().Format("02 Jan 2006 03:04 PM")
|
||||||
Width(width-1).
|
modelName := msg.Metadata.Assistant.ModelID
|
||||||
Foreground(t.Error()).
|
info := baseStyle.
|
||||||
Render("(error)"),
|
Foreground(t.TextMuted()).
|
||||||
)
|
Render(fmt.Sprintf(" %s (%s)", modelName, timestamp))
|
||||||
case message.FinishReasonPermissionDenied:
|
|
||||||
info = append(info, baseStyle.
|
for _, p := range msg.Parts {
|
||||||
Width(width-1).
|
part, err := p.ValueByDiscriminator()
|
||||||
Foreground(t.Info()).
|
if err != nil {
|
||||||
Render("(permission denied)"),
|
continue //TODO: handle error?
|
||||||
)
|
}
|
||||||
|
|
||||||
|
switch part.(type) {
|
||||||
|
case client.MessagePartText:
|
||||||
|
textPart := part.(client.MessagePartText)
|
||||||
|
text := toMarkdown(textPart.Text, width)
|
||||||
|
content := style.Render(lipgloss.JoinVertical(lipgloss.Left, text, info))
|
||||||
|
messages = append(messages, content)
|
||||||
|
|
||||||
|
case client.MessagePartToolInvocation:
|
||||||
|
if !showToolMessages {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
toolInvocationPart := part.(client.MessagePartToolInvocation)
|
||||||
|
toolInvocation, _ := toolInvocationPart.ToolInvocation.ValueByDiscriminator()
|
||||||
|
switch toolInvocation.(type) {
|
||||||
|
case client.MessageToolInvocationToolCall:
|
||||||
|
toolCall := toolInvocation.(client.MessageToolInvocationToolCall)
|
||||||
|
toolName := toolName(toolCall.ToolName)
|
||||||
|
var toolArgs []string
|
||||||
|
toolMap, _ := convertToMap(toolCall.Args)
|
||||||
|
for _, arg := range toolMap {
|
||||||
|
toolArgs = append(toolArgs, fmt.Sprintf("%v", arg))
|
||||||
|
}
|
||||||
|
params := renderParams(width-lipgloss.Width(toolName)-1, toolArgs...)
|
||||||
|
title := styles.Padded().Render(fmt.Sprintf("%s: %s", toolName, params))
|
||||||
|
|
||||||
|
content := toolStyle.Render(lipgloss.JoinVertical(lipgloss.Left,
|
||||||
|
title,
|
||||||
|
" In progress...",
|
||||||
|
))
|
||||||
|
messages = append(messages, content)
|
||||||
|
|
||||||
|
case client.MessageToolInvocationToolResult:
|
||||||
|
toolInvocationResult := toolInvocation.(client.MessageToolInvocationToolResult)
|
||||||
|
toolName := toolName(toolInvocationResult.ToolName)
|
||||||
|
var toolArgs []string
|
||||||
|
toolMap, _ := convertToMap(toolInvocationResult.Args)
|
||||||
|
for _, arg := range toolMap {
|
||||||
|
toolArgs = append(toolArgs, fmt.Sprintf("%v", arg))
|
||||||
|
}
|
||||||
|
result := truncateHeight(strings.TrimSpace(toolInvocationResult.Result), 10)
|
||||||
|
params := renderParams(width-lipgloss.Width(toolName)-1, toolArgs...)
|
||||||
|
title := styles.Padded().Render(fmt.Sprintf("%s: %s", toolName, params))
|
||||||
|
|
||||||
|
markdown := toMarkdown(result, width)
|
||||||
|
|
||||||
|
content := toolStyle.Render(lipgloss.JoinVertical(lipgloss.Left,
|
||||||
|
title,
|
||||||
|
markdown,
|
||||||
|
))
|
||||||
|
messages = append(messages, content)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if content != "" || (finished && finishData.Reason == message.FinishReasonEndTurn) {
|
// if finished {
|
||||||
if content == "" {
|
// // Add finish info if available
|
||||||
content = "*Finished without output*"
|
// switch finishData.Reason {
|
||||||
}
|
// case message.FinishReasonCanceled:
|
||||||
|
// info = append(info, baseStyle.
|
||||||
|
// Width(width-1).
|
||||||
|
// Foreground(t.Warning()).
|
||||||
|
// Render("(canceled)"),
|
||||||
|
// )
|
||||||
|
// case message.FinishReasonError:
|
||||||
|
// info = append(info, baseStyle.
|
||||||
|
// Width(width-1).
|
||||||
|
// Foreground(t.Error()).
|
||||||
|
// Render("(error)"),
|
||||||
|
// )
|
||||||
|
// case message.FinishReasonPermissionDenied:
|
||||||
|
// info = append(info, baseStyle.
|
||||||
|
// Width(width-1).
|
||||||
|
// Foreground(t.Info()).
|
||||||
|
// Render("(permission denied)"),
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
content = renderMessage(content, false, true, width, info...)
|
// if content != "" || (finished && finishData.Reason == message.FinishReasonEndTurn) {
|
||||||
messages = append(messages, uiMessage{
|
// if content == "" {
|
||||||
ID: msg.ID,
|
// content = "*Finished without output*"
|
||||||
messageType: assistantMessageType,
|
// }
|
||||||
// position: position,
|
//
|
||||||
// height: lipgloss.Height(content),
|
// content = renderMessage(content, false, width, info...)
|
||||||
content: content,
|
// messages = append(messages, content)
|
||||||
})
|
// // position += messages[0].height
|
||||||
// position += messages[0].height
|
// position++ // for the space
|
||||||
position++ // for the space
|
// } else if thinking && thinkingContent != "" {
|
||||||
} else if thinking && thinkingContent != "" {
|
// // Render the thinking content with timestamp
|
||||||
// Render the thinking content with timestamp
|
// content = renderMessage(thinkingContent, false, width, info...)
|
||||||
content = renderMessage(thinkingContent, false, msg.ID == focusedUIMessageId, width, info...)
|
// messages = append(messages, content)
|
||||||
messages = append(messages, uiMessage{
|
// position += lipgloss.Height(content)
|
||||||
ID: msg.ID,
|
// position++ // for the space
|
||||||
messageType: assistantMessageType,
|
// }
|
||||||
// position: position,
|
|
||||||
// height: lipgloss.Height(content),
|
|
||||||
content: content,
|
|
||||||
})
|
|
||||||
position += lipgloss.Height(content)
|
|
||||||
position++ // for the space
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only render tool messages if they should be shown
|
// Only render tool messages if they should be shown
|
||||||
if showToolMessages {
|
if showToolMessages {
|
||||||
for i, toolCall := range msg.ToolCalls() {
|
// for i, toolCall := range msg.ToolCalls() {
|
||||||
toolCallContent := renderToolMessage(
|
// toolCallContent := renderToolMessage(
|
||||||
toolCall,
|
// toolCall,
|
||||||
allMessages,
|
// allMessages,
|
||||||
messagesService,
|
// messagesService,
|
||||||
focusedUIMessageId,
|
// focusedUIMessageId,
|
||||||
false,
|
// false,
|
||||||
width,
|
// width,
|
||||||
i+1,
|
// i+1,
|
||||||
)
|
// )
|
||||||
messages = append(messages, toolCallContent)
|
// messages = append(messages, toolCallContent)
|
||||||
// position += toolCallContent.height
|
// }
|
||||||
position++ // for the space
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return messages
|
|
||||||
|
return strings.Join(messages, "\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func findToolResponse(toolCallID string, futureMessages []message.Message) *message.ToolResult {
|
func findToolResponse(toolCallID string, futureMessages []message.Message) *message.ToolResult {
|
||||||
|
@ -497,7 +503,7 @@ func renderToolResponse(toolCall message.ToolCall, response message.ToolResult,
|
||||||
case tools.BashToolName:
|
case tools.BashToolName:
|
||||||
resultContent = fmt.Sprintf("```bash\n%s\n```", resultContent)
|
resultContent = fmt.Sprintf("```bash\n%s\n```", resultContent)
|
||||||
return styles.ForceReplaceBackgroundWithLipgloss(
|
return styles.ForceReplaceBackgroundWithLipgloss(
|
||||||
toMarkdown(resultContent, true, width),
|
toMarkdown(resultContent, width),
|
||||||
t.Background(),
|
t.Background(),
|
||||||
)
|
)
|
||||||
case tools.EditToolName:
|
case tools.EditToolName:
|
||||||
|
@ -517,7 +523,7 @@ func renderToolResponse(toolCall message.ToolCall, response message.ToolResult,
|
||||||
}
|
}
|
||||||
resultContent = fmt.Sprintf("```%s\n%s\n```", mdFormat, resultContent)
|
resultContent = fmt.Sprintf("```%s\n%s\n```", mdFormat, resultContent)
|
||||||
return styles.ForceReplaceBackgroundWithLipgloss(
|
return styles.ForceReplaceBackgroundWithLipgloss(
|
||||||
toMarkdown(resultContent, true, width),
|
toMarkdown(resultContent, width),
|
||||||
t.Background(),
|
t.Background(),
|
||||||
)
|
)
|
||||||
case tools.GlobToolName:
|
case tools.GlobToolName:
|
||||||
|
@ -537,7 +543,7 @@ func renderToolResponse(toolCall message.ToolCall, response message.ToolResult,
|
||||||
}
|
}
|
||||||
resultContent = fmt.Sprintf("```%s\n%s\n```", ext, truncateHeight(metadata.Content, maxResultHeight))
|
resultContent = fmt.Sprintf("```%s\n%s\n```", ext, truncateHeight(metadata.Content, maxResultHeight))
|
||||||
return styles.ForceReplaceBackgroundWithLipgloss(
|
return styles.ForceReplaceBackgroundWithLipgloss(
|
||||||
toMarkdown(resultContent, true, width),
|
toMarkdown(resultContent, width),
|
||||||
t.Background(),
|
t.Background(),
|
||||||
)
|
)
|
||||||
case tools.WriteToolName:
|
case tools.WriteToolName:
|
||||||
|
@ -553,7 +559,7 @@ func renderToolResponse(toolCall message.ToolCall, response message.ToolResult,
|
||||||
}
|
}
|
||||||
resultContent = fmt.Sprintf("```%s\n%s\n```", ext, truncateHeight(params.Content, maxResultHeight))
|
resultContent = fmt.Sprintf("```%s\n%s\n```", ext, truncateHeight(params.Content, maxResultHeight))
|
||||||
return styles.ForceReplaceBackgroundWithLipgloss(
|
return styles.ForceReplaceBackgroundWithLipgloss(
|
||||||
toMarkdown(resultContent, true, width),
|
toMarkdown(resultContent, width),
|
||||||
t.Background(),
|
t.Background(),
|
||||||
)
|
)
|
||||||
case tools.BatchToolName:
|
case tools.BatchToolName:
|
||||||
|
@ -591,7 +597,7 @@ func renderToolResponse(toolCall message.ToolCall, response message.ToolResult,
|
||||||
default:
|
default:
|
||||||
resultContent = fmt.Sprintf("```text\n%s\n```", resultContent)
|
resultContent = fmt.Sprintf("```text\n%s\n```", resultContent)
|
||||||
return styles.ForceReplaceBackgroundWithLipgloss(
|
return styles.ForceReplaceBackgroundWithLipgloss(
|
||||||
toMarkdown(resultContent, true, width),
|
toMarkdown(resultContent, width),
|
||||||
t.Background(),
|
t.Background(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -605,7 +611,7 @@ func renderToolMessage(
|
||||||
nested bool,
|
nested bool,
|
||||||
width int,
|
width int,
|
||||||
position int,
|
position int,
|
||||||
) uiMessage {
|
) string {
|
||||||
if nested {
|
if nested {
|
||||||
width = width - 3
|
width = width - 3
|
||||||
}
|
}
|
||||||
|
@ -634,13 +640,7 @@ func renderToolMessage(
|
||||||
Render(fmt.Sprintf("%s", toolAction))
|
Render(fmt.Sprintf("%s", toolAction))
|
||||||
|
|
||||||
content := style.Render(lipgloss.JoinHorizontal(lipgloss.Left, toolNameText, progressText))
|
content := style.Render(lipgloss.JoinHorizontal(lipgloss.Left, toolNameText, progressText))
|
||||||
toolMsg := uiMessage{
|
return content
|
||||||
messageType: toolMessageType,
|
|
||||||
// position: position,
|
|
||||||
// height: lipgloss.Height(content),
|
|
||||||
content: content,
|
|
||||||
}
|
|
||||||
return toolMsg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
params := renderToolParams(width-1-lipgloss.Width(toolNameText), toolCall)
|
params := renderToolParams(width-1-lipgloss.Width(toolNameText), toolCall)
|
||||||
|
@ -702,11 +702,5 @@ func renderToolMessage(
|
||||||
parts...,
|
parts...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
toolMsg := uiMessage{
|
return content
|
||||||
messageType: toolMessageType,
|
|
||||||
// position: position,
|
|
||||||
// height: lipgloss.Height(content),
|
|
||||||
content: content,
|
|
||||||
}
|
|
||||||
return toolMsg
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package chat
|
package chat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/key"
|
"github.com/charmbracelet/bubbles/key"
|
||||||
|
@ -20,18 +18,11 @@ import (
|
||||||
"github.com/sst/opencode/pkg/client"
|
"github.com/sst/opencode/pkg/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type cacheItem struct {
|
|
||||||
width int
|
|
||||||
content []uiMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
type messagesCmp struct {
|
type messagesCmp struct {
|
||||||
app *app.App
|
app *app.App
|
||||||
width, height int
|
width, height int
|
||||||
viewport viewport.Model
|
viewport viewport.Model
|
||||||
uiMessages []uiMessage
|
|
||||||
currentMsgID string
|
currentMsgID string
|
||||||
cachedContent map[string]cacheItem
|
|
||||||
spinner spinner.Model
|
spinner spinner.Model
|
||||||
rendering bool
|
rendering bool
|
||||||
attachments viewport.Model
|
attachments viewport.Model
|
||||||
|
@ -71,17 +62,17 @@ func (m *messagesCmp) Init() tea.Cmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m *messagesCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
m.renderView()
|
// m.renderView()
|
||||||
|
|
||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case dialog.ThemeChangedMsg:
|
case dialog.ThemeChangedMsg:
|
||||||
m.rerender()
|
m.renderView()
|
||||||
return m, nil
|
return m, nil
|
||||||
case ToggleToolMessagesMsg:
|
case ToggleToolMessagesMsg:
|
||||||
m.showToolMessages = !m.showToolMessages
|
m.showToolMessages = !m.showToolMessages
|
||||||
// Clear the cache to force re-rendering of all messages
|
// Clear the cache to force re-rendering of all messages
|
||||||
m.cachedContent = make(map[string]cacheItem)
|
// m.cachedContent = make(map[string]cacheItem)
|
||||||
m.renderView()
|
m.renderView()
|
||||||
return m, nil
|
return m, nil
|
||||||
case state.SessionSelectedMsg:
|
case state.SessionSelectedMsg:
|
||||||
|
@ -171,93 +162,43 @@ func (m *messagesCmp) IsAgentWorking() bool {
|
||||||
return m.app.PrimaryAgentOLD.IsSessionBusy(m.app.CurrentSessionOLD.ID)
|
return m.app.PrimaryAgentOLD.IsSessionBusy(m.app.CurrentSessionOLD.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatTimeDifference(unixTime1, unixTime2 int64) string {
|
|
||||||
diffSeconds := float64(math.Abs(float64(unixTime2 - unixTime1)))
|
|
||||||
|
|
||||||
if diffSeconds < 60 {
|
|
||||||
return fmt.Sprintf("%.1fs", diffSeconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
minutes := int(diffSeconds / 60)
|
|
||||||
seconds := int(diffSeconds) % 60
|
|
||||||
return fmt.Sprintf("%dm%ds", minutes, seconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *messagesCmp) renderView() {
|
func (m *messagesCmp) renderView() {
|
||||||
m.uiMessages = make([]uiMessage, 0)
|
|
||||||
baseStyle := styles.BaseStyle()
|
|
||||||
|
|
||||||
if m.width == 0 {
|
if m.width == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t := theme.CurrentTheme()
|
||||||
|
messages := make([]string, 0)
|
||||||
for _, msg := range m.app.Messages {
|
for _, msg := range m.app.Messages {
|
||||||
switch msg.Role {
|
switch msg.Role {
|
||||||
case client.User:
|
case client.User:
|
||||||
if cache, ok := m.cachedContent[msg.Id]; ok && cache.width == m.width {
|
content := renderUserMessage(
|
||||||
m.uiMessages = append(m.uiMessages, cache.content...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
userMsg := renderUserMessage(
|
|
||||||
msg,
|
msg,
|
||||||
msg.Id == m.currentMsgID,
|
|
||||||
m.width,
|
m.width,
|
||||||
)
|
)
|
||||||
m.uiMessages = append(m.uiMessages, userMsg)
|
messages = append(messages, content+"\n")
|
||||||
m.cachedContent[msg.Id] = cacheItem{
|
|
||||||
width: m.width,
|
|
||||||
content: []uiMessage{userMsg},
|
|
||||||
}
|
|
||||||
// pos += userMsg.height + 1 // + 1 for spacing
|
|
||||||
case client.Assistant:
|
case client.Assistant:
|
||||||
if cache, ok := m.cachedContent[msg.Id]; ok && cache.width == m.width {
|
content := renderAssistantMessage(
|
||||||
m.uiMessages = append(m.uiMessages, cache.content...)
|
msg,
|
||||||
continue
|
m.width,
|
||||||
}
|
m.showToolMessages,
|
||||||
// assistantMessages := renderAssistantMessage(
|
)
|
||||||
// msg,
|
messages = append(messages, content+"\n")
|
||||||
// inx,
|
|
||||||
// m.app.Messages,
|
|
||||||
// m.app.MessagesOLD,
|
|
||||||
// m.currentMsgID,
|
|
||||||
// m.width,
|
|
||||||
// pos,
|
|
||||||
// m.showToolMessages,
|
|
||||||
// )
|
|
||||||
// for _, msg := range assistantMessages {
|
|
||||||
// m.uiMessages = append(m.uiMessages, msg)
|
|
||||||
// // pos += msg.height + 1 // + 1 for spacing
|
|
||||||
// }
|
|
||||||
// m.cachedContent[msg.Id] = cacheItem{
|
|
||||||
// width: m.width,
|
|
||||||
// content: assistantMessages,
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messages := make([]string, 0)
|
m.viewport.SetContent(
|
||||||
for _, v := range m.uiMessages {
|
styles.ForceReplaceBackgroundWithLipgloss(
|
||||||
messages = append(messages, lipgloss.JoinVertical(lipgloss.Left, v.content),
|
styles.BaseStyle().
|
||||||
baseStyle.
|
|
||||||
Width(m.width).
|
Width(m.width).
|
||||||
Render(
|
Render(
|
||||||
"",
|
lipgloss.JoinVertical(
|
||||||
|
lipgloss.Top,
|
||||||
|
messages...,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
t.Background(),
|
||||||
}
|
),
|
||||||
|
|
||||||
// temp, _ := json.MarshalIndent(m.app.State, "", " ")
|
|
||||||
|
|
||||||
m.viewport.SetContent(
|
|
||||||
baseStyle.
|
|
||||||
Width(m.width).
|
|
||||||
Render(
|
|
||||||
// string(temp),
|
|
||||||
lipgloss.JoinVertical(
|
|
||||||
lipgloss.Top,
|
|
||||||
messages...,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,13 +357,6 @@ func (m *messagesCmp) initialScreen() string {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *messagesCmp) rerender() {
|
|
||||||
for _, msg := range m.app.Messages {
|
|
||||||
delete(m.cachedContent, msg.Id)
|
|
||||||
}
|
|
||||||
m.renderView()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *messagesCmp) SetSize(width, height int) tea.Cmd {
|
func (m *messagesCmp) SetSize(width, height int) tea.Cmd {
|
||||||
if m.width == width && m.height == height {
|
if m.width == width && m.height == height {
|
||||||
return nil
|
return nil
|
||||||
|
@ -433,7 +367,7 @@ func (m *messagesCmp) SetSize(width, height int) tea.Cmd {
|
||||||
m.viewport.Height = height - 2
|
m.viewport.Height = height - 2
|
||||||
m.attachments.Width = width + 40
|
m.attachments.Width = width + 40
|
||||||
m.attachments.Height = 3
|
m.attachments.Height = 3
|
||||||
m.rerender()
|
m.renderView()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,7 +387,7 @@ func (m *messagesCmp) Reload(session *session.Session) tea.Cmd {
|
||||||
if len(m.app.Messages) > 0 {
|
if len(m.app.Messages) > 0 {
|
||||||
m.currentMsgID = m.app.Messages[len(m.app.Messages)-1].Id
|
m.currentMsgID = m.app.Messages[len(m.app.Messages)-1].Id
|
||||||
}
|
}
|
||||||
delete(m.cachedContent, m.currentMsgID)
|
// delete(m.cachedContent, m.currentMsgID)
|
||||||
m.rendering = true
|
m.rendering = true
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
m.renderView()
|
m.renderView()
|
||||||
|
@ -476,18 +410,19 @@ func NewMessagesCmp(app *app.App) tea.Model {
|
||||||
FPS: time.Second / 3,
|
FPS: time.Second / 3,
|
||||||
}
|
}
|
||||||
s := spinner.New(spinner.WithSpinner(customSpinner))
|
s := spinner.New(spinner.WithSpinner(customSpinner))
|
||||||
|
|
||||||
vp := viewport.New(0, 0)
|
vp := viewport.New(0, 0)
|
||||||
attachmets := viewport.New(0, 0)
|
attachments := viewport.New(0, 0)
|
||||||
vp.KeyMap.PageUp = messageKeys.PageUp
|
vp.KeyMap.PageUp = messageKeys.PageUp
|
||||||
vp.KeyMap.PageDown = messageKeys.PageDown
|
vp.KeyMap.PageDown = messageKeys.PageDown
|
||||||
vp.KeyMap.HalfPageUp = messageKeys.HalfPageUp
|
vp.KeyMap.HalfPageUp = messageKeys.HalfPageUp
|
||||||
vp.KeyMap.HalfPageDown = messageKeys.HalfPageDown
|
vp.KeyMap.HalfPageDown = messageKeys.HalfPageDown
|
||||||
|
|
||||||
return &messagesCmp{
|
return &messagesCmp{
|
||||||
app: app,
|
app: app,
|
||||||
cachedContent: make(map[string]cacheItem),
|
|
||||||
viewport: vp,
|
viewport: vp,
|
||||||
spinner: s,
|
spinner: s,
|
||||||
attachments: attachmets,
|
attachments: attachments,
|
||||||
showToolMessages: true,
|
showToolMessages: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -285,7 +285,8 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
a.app.Session = &sessionInfo
|
a.app.Session = &sessionInfo
|
||||||
}
|
}
|
||||||
return a, nil
|
|
||||||
|
return a.updateAllPages(state.StateUpdatedMsg{State: a.app.State})
|
||||||
}
|
}
|
||||||
|
|
||||||
if parts[0] == "session" && parts[1] == "message" {
|
if parts[0] == "session" && parts[1] == "message" {
|
||||||
|
@ -303,7 +304,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
if m.Id == messageId {
|
if m.Id == messageId {
|
||||||
a.app.Messages[i] = message
|
a.app.Messages[i] = message
|
||||||
slog.Debug("Updated message", "message", message)
|
slog.Debug("Updated message", "message", message)
|
||||||
return a, nil
|
return a.updateAllPages(state.StateUpdatedMsg{State: a.app.State})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,7 +317,8 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
// a.app.CurrentSession.Cost += message.Cost
|
// a.app.CurrentSession.Cost += message.Cost
|
||||||
// a.app.CurrentSession.UpdatedAt = message.CreatedAt
|
// a.app.CurrentSession.UpdatedAt = message.CreatedAt
|
||||||
}
|
}
|
||||||
return a, nil
|
|
||||||
|
return a.updateAllPages(state.StateUpdatedMsg{State: a.app.State})
|
||||||
}
|
}
|
||||||
|
|
||||||
// log key and content
|
// log key and content
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue