1package chat
2
3import (
4 "fmt"
5 "path/filepath"
6 "strings"
7
8 "charm.land/lipgloss/v2"
9 "github.com/charmbracelet/crush/internal/message"
10 "github.com/charmbracelet/crush/internal/ui/common"
11 "github.com/charmbracelet/crush/internal/ui/styles"
12 "github.com/charmbracelet/x/ansi"
13)
14
15type UserMessageItem struct {
16 id string
17 content string
18 attachments []message.BinaryContent
19 sty *styles.Styles
20}
21
22func NewUserMessage(id, content string, attachments []message.BinaryContent, sty *styles.Styles) *UserMessageItem {
23 return &UserMessageItem{
24 id: id,
25 content: content,
26 attachments: attachments,
27 sty: sty,
28 }
29}
30
31// ID implements Identifiable.
32func (m *UserMessageItem) ID() string {
33 return m.id
34}
35
36// FocusStyle returns the focus style.
37func (m *UserMessageItem) FocusStyle() lipgloss.Style {
38 return m.sty.Chat.Message.UserFocused
39}
40
41// BlurStyle returns the blur style.
42func (m *UserMessageItem) BlurStyle() lipgloss.Style {
43 return m.sty.Chat.Message.UserBlurred
44}
45
46// HighlightStyle returns the highlight style.
47func (m *UserMessageItem) HighlightStyle() lipgloss.Style {
48 return m.sty.TextSelection
49}
50
51// Render implements MessageItem.
52func (m *UserMessageItem) Render(width int) string {
53 cappedWidth := min(width, maxTextWidth)
54 renderer := common.MarkdownRenderer(m.sty, cappedWidth)
55 result, err := renderer.Render(m.content)
56 var rendered string
57 if err != nil {
58 rendered = m.content
59 } else {
60 rendered = strings.TrimSuffix(result, "\n")
61 }
62
63 if len(m.attachments) > 0 {
64 attachmentsStr := m.renderAttachments(cappedWidth)
65 rendered = strings.Join([]string{rendered, "", attachmentsStr}, "\n")
66 }
67 return rendered
68}
69
70// renderAttachments renders attachments with wrapping if they exceed the width.
71func (m *UserMessageItem) renderAttachments(width int) string {
72 const maxFilenameWidth = 10
73
74 attachments := make([]string, len(m.attachments))
75 for i, attachment := range m.attachments {
76 filename := filepath.Base(attachment.Path)
77 attachments[i] = m.sty.Chat.Message.Attachment.Render(fmt.Sprintf(
78 " %s %s ",
79 styles.DocumentIcon,
80 ansi.Truncate(filename, maxFilenameWidth, "..."),
81 ))
82 }
83
84 // Wrap attachments into lines that fit within the width.
85 var lines []string
86 var currentLine []string
87 currentWidth := 0
88
89 for _, att := range attachments {
90 attWidth := lipgloss.Width(att)
91 sepWidth := 1
92 if len(currentLine) == 0 {
93 sepWidth = 0
94 }
95
96 if currentWidth+sepWidth+attWidth > width && len(currentLine) > 0 {
97 lines = append(lines, strings.Join(currentLine, " "))
98 currentLine = []string{att}
99 currentWidth = attWidth
100 } else {
101 currentLine = append(currentLine, att)
102 currentWidth += sepWidth + attWidth
103 }
104 }
105
106 if len(currentLine) > 0 {
107 lines = append(lines, strings.Join(currentLine, " "))
108 }
109
110 return strings.Join(lines, "\n")
111}