user.go

  1package chat
  2
  3import (
  4	"strings"
  5
  6	tea "charm.land/bubbletea/v2"
  7	"charm.land/lipgloss/v2"
  8	"github.com/charmbracelet/crush/internal/message"
  9	"github.com/charmbracelet/crush/internal/ui/attachments"
 10	"github.com/charmbracelet/crush/internal/ui/common"
 11	"github.com/charmbracelet/crush/internal/ui/styles"
 12)
 13
 14// UserMessageItem represents a user message in the chat UI.
 15type UserMessageItem struct {
 16	*highlightableMessageItem
 17	*cachedMessageItem
 18	*focusableMessageItem
 19
 20	attachments *attachments.Renderer
 21	message     *message.Message
 22	sty         *styles.Styles
 23}
 24
 25// NewUserMessageItem creates a new UserMessageItem.
 26func NewUserMessageItem(sty *styles.Styles, message *message.Message, attachments *attachments.Renderer) MessageItem {
 27	return &UserMessageItem{
 28		highlightableMessageItem: defaultHighlighter(sty),
 29		cachedMessageItem:        &cachedMessageItem{},
 30		focusableMessageItem:     &focusableMessageItem{},
 31		attachments:              attachments,
 32		message:                  message,
 33		sty:                      sty,
 34	}
 35}
 36
 37// RawRender implements [MessageItem].
 38func (m *UserMessageItem) RawRender(width int) string {
 39	cappedWidth := cappedMessageWidth(width)
 40
 41	content, height, ok := m.getCachedRender(cappedWidth)
 42	// cache hit
 43	if ok {
 44		return m.renderHighlighted(content, cappedWidth, height)
 45	}
 46
 47	renderer := common.MarkdownRenderer(m.sty, cappedWidth)
 48
 49	msgContent := strings.TrimSpace(m.message.Content().Text)
 50	result, err := renderer.Render(msgContent)
 51	if err != nil {
 52		content = msgContent
 53	} else {
 54		content = strings.TrimSuffix(result, "\n")
 55	}
 56
 57	if len(m.message.BinaryContent()) > 0 {
 58		attachmentsStr := m.renderAttachments(cappedWidth)
 59		if content == "" {
 60			content = attachmentsStr
 61		} else {
 62			content = strings.Join([]string{content, "", attachmentsStr}, "\n")
 63		}
 64	}
 65
 66	height = lipgloss.Height(content)
 67	m.setCachedRender(content, cappedWidth, height)
 68	return m.renderHighlighted(content, cappedWidth, height)
 69}
 70
 71// Render implements MessageItem.
 72func (m *UserMessageItem) Render(width int) string {
 73	style := m.sty.Chat.Message.UserBlurred
 74	if m.focused {
 75		style = m.sty.Chat.Message.UserFocused
 76	}
 77	return style.Render(m.RawRender(width))
 78}
 79
 80// ID implements MessageItem.
 81func (m *UserMessageItem) ID() string {
 82	return m.message.ID
 83}
 84
 85// renderAttachments renders attachments.
 86func (m *UserMessageItem) renderAttachments(width int) string {
 87	var attachments []message.Attachment
 88	for _, at := range m.message.BinaryContent() {
 89		attachments = append(attachments, message.Attachment{
 90			FileName: at.Path,
 91			MimeType: at.MIMEType,
 92		})
 93	}
 94	return m.attachments.Render(attachments, false, width)
 95}
 96
 97// HandleKeyEvent implements KeyEventHandler.
 98func (m *UserMessageItem) HandleKeyEvent(key tea.KeyMsg) (bool, tea.Cmd) {
 99	if k := key.String(); k == "c" || k == "y" {
100		text := m.message.Content().Text
101		return true, common.CopyToClipboard(text, "Message copied to clipboard")
102	}
103	return false, nil
104}