chat.go

  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}