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	content, height, ok := m.getCachedRender(width)
 40	// cache hit
 41	if ok {
 42		return m.renderHighlighted(content, width, height)
 43	}
 44
 45	renderer := common.MarkdownRenderer(m.sty, width)
 46
 47	msgContent := strings.TrimSpace(m.message.Content().Text)
 48	result, err := renderer.Render(msgContent)
 49	if err != nil {
 50		content = msgContent
 51	} else {
 52		content = strings.TrimSuffix(result, "\n")
 53	}
 54
 55	if len(m.message.BinaryContent()) > 0 {
 56		attachmentsStr := m.renderAttachments(width)
 57		if content == "" {
 58			content = attachmentsStr
 59		} else {
 60			content = strings.Join([]string{content, "", attachmentsStr}, "\n")
 61		}
 62	}
 63
 64	height = lipgloss.Height(content)
 65	m.setCachedRender(content, width, height)
 66	return m.renderHighlighted(content, width, height)
 67}
 68
 69// Render implements MessageItem.
 70func (m *UserMessageItem) Render(width int) string {
 71	style := m.sty.Chat.Message.UserBlurred
 72	if m.focused {
 73		style = m.sty.Chat.Message.UserFocused
 74	}
 75	return style.Render(m.RawRender(width))
 76}
 77
 78// ID implements MessageItem.
 79func (m *UserMessageItem) ID() string {
 80	return m.message.ID
 81}
 82
 83// renderAttachments renders attachments.
 84func (m *UserMessageItem) renderAttachments(width int) string {
 85	var attachments []message.Attachment
 86	for _, at := range m.message.BinaryContent() {
 87		attachments = append(attachments, message.Attachment{
 88			FileName: at.Path,
 89			MimeType: at.MIMEType,
 90		})
 91	}
 92	return m.attachments.Render(attachments, false, width)
 93}
 94
 95// HandleKeyEvent implements KeyEventHandler.
 96func (m *UserMessageItem) HandleKeyEvent(key tea.KeyMsg) (bool, tea.Cmd) {
 97	if k := key.String(); k == "c" || k == "y" {
 98		text := m.message.Content().Text
 99		return true, common.CopyToClipboard(text, "Message copied to clipboard")
100	}
101	return false, nil
102}