2
0

user.go 3.0 KB

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