1// Package chat provides the chat UI components for displaying and managing
2// conversation messages between users and assistants.
3package chat
4
5import (
6 tea "charm.land/bubbletea/v2"
7 "github.com/charmbracelet/crush/internal/ui/common"
8 "github.com/charmbracelet/crush/internal/ui/list"
9 uv "github.com/charmbracelet/ultraviolet"
10)
11
12// maxTextWidth is the maximum width text messages can be
13const maxTextWidth = 120
14
15// Identifiable is an interface for items that can provide a unique identifier.
16type Identifiable interface {
17 ID() string
18}
19
20// MessageItem represents a [message.Message] item that can be displayed in the
21// UI and be part of a [list.List] identifiable by a unique ID.
22type MessageItem interface {
23 list.Item
24 Identifiable
25}
26
27// Chat represents the chat UI model that handles chat interactions and
28// messages.
29type Chat struct {
30 com *common.Common
31 list *list.List
32}
33
34// NewChat creates a new instance of [Chat] that handles chat interactions and
35// messages.
36func NewChat(com *common.Common) *Chat {
37 l := list.NewList()
38 l.SetGap(1)
39 return &Chat{
40 com: com,
41 list: l,
42 }
43}
44
45// Height returns the height of the chat view port.
46func (m *Chat) Height() int {
47 return m.list.Height()
48}
49
50// Draw renders the chat UI component to the screen and the given area.
51func (m *Chat) Draw(scr uv.Screen, area uv.Rectangle) {
52 uv.NewStyledString(m.list.Render()).Draw(scr, area)
53}
54
55// SetSize sets the size of the chat view port.
56func (m *Chat) SetSize(width, height int) {
57 m.list.SetSize(width, height)
58}
59
60// Len returns the number of items in the chat list.
61func (m *Chat) Len() int {
62 return m.list.Len()
63}
64
65// PrependItems prepends new items to the chat list.
66func (m *Chat) PrependItems(items ...list.Item) {
67 m.list.PrependItems(items...)
68 m.list.ScrollToIndex(0)
69}
70
71// SetMessages sets the chat messages to the provided list of message items.
72func (m *Chat) SetMessages(msgs ...MessageItem) {
73 items := make([]list.Item, len(msgs))
74 for i, msg := range msgs {
75 items[i] = msg
76 }
77 m.list.SetItems(items...)
78 m.list.ScrollToBottom()
79}
80
81// AppendMessages appends a new message item to the chat list.
82func (m *Chat) AppendMessages(msgs ...MessageItem) {
83 items := make([]list.Item, len(msgs))
84 for i, msg := range msgs {
85 items[i] = msg
86 }
87 m.list.AppendItems(items...)
88}
89
90// AppendItems appends new items to the chat list.
91func (m *Chat) AppendItems(items ...list.Item) {
92 m.list.AppendItems(items...)
93 m.list.ScrollToIndex(m.list.Len() - 1)
94}
95
96// Focus sets the focus state of the chat component.
97func (m *Chat) Focus() {
98 m.list.Focus()
99}
100
101// Blur removes the focus state from the chat component.
102func (m *Chat) Blur() {
103 m.list.Blur()
104}
105
106// ScrollToTop scrolls the chat view to the top.
107func (m *Chat) ScrollToTop() {
108 m.list.ScrollToTop()
109}
110
111// ScrollToBottom scrolls the chat view to the bottom.
112func (m *Chat) ScrollToBottom() {
113 m.list.ScrollToBottom()
114}
115
116// ScrollBy scrolls the chat view by the given number of line deltas.
117func (m *Chat) ScrollBy(lines int) {
118 m.list.ScrollBy(lines)
119}
120
121// ScrollToSelected scrolls the chat view to the selected item.
122func (m *Chat) ScrollToSelected() {
123 m.list.ScrollToSelected()
124}
125
126// SelectedItemInView returns whether the selected item is currently in view.
127func (m *Chat) SelectedItemInView() bool {
128 return m.list.SelectedItemInView()
129}
130
131// SetSelected sets the selected message index in the chat list.
132func (m *Chat) SetSelected(index int) {
133 m.list.SetSelected(index)
134}
135
136// SelectPrev selects the previous message in the chat list.
137func (m *Chat) SelectPrev() {
138 m.list.SelectPrev()
139}
140
141// SelectNext selects the next message in the chat list.
142func (m *Chat) SelectNext() {
143 m.list.SelectNext()
144}
145
146// SelectFirst selects the first message in the chat list.
147func (m *Chat) SelectFirst() {
148 m.list.SelectFirst()
149}
150
151// SelectLast selects the last message in the chat list.
152func (m *Chat) SelectLast() {
153 m.list.SelectLast()
154}
155
156// SelectFirstInView selects the first message currently in view.
157func (m *Chat) SelectFirstInView() {
158 m.list.SelectFirstInView()
159}
160
161// SelectLastInView selects the last message currently in view.
162func (m *Chat) SelectLastInView() {
163 m.list.SelectLastInView()
164}
165
166// HandleMouseDown handles mouse down events for the chat component.
167func (m *Chat) HandleMouseDown(x, y int) {
168 m.list.HandleMouseDown(x, y)
169}
170
171// HandleMouseUp handles mouse up events for the chat component.
172func (m *Chat) HandleMouseUp(x, y int) {
173 m.list.HandleMouseUp(x, y)
174}
175
176// HandleMouseDrag handles mouse drag events for the chat component.
177func (m *Chat) HandleMouseDrag(x, y int) {
178 m.list.HandleMouseDrag(x, y)
179}
180
181// HandleKeyPress handles key press events for the currently selected item.
182func (m *Chat) HandleKeyPress(msg tea.KeyPressMsg) bool {
183 return m.list.HandleKeyPress(msg)
184}