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}