compact.go

  1package compact
  2
  3import (
  4	"context"
  5
  6	"github.com/charmbracelet/bubbles/v2/key"
  7	tea "github.com/charmbracelet/bubbletea/v2"
  8	"github.com/charmbracelet/lipgloss/v2"
  9
 10	"github.com/charmbracelet/crush/internal/agent"
 11	"github.com/charmbracelet/crush/internal/tui/components/core"
 12	"github.com/charmbracelet/crush/internal/tui/components/dialogs"
 13	"github.com/charmbracelet/crush/internal/tui/styles"
 14	"github.com/charmbracelet/crush/internal/tui/util"
 15)
 16
 17const CompactDialogID dialogs.DialogID = "compact"
 18
 19// CompactDialog interface for the session compact dialog
 20type CompactDialog interface {
 21	dialogs.DialogModel
 22}
 23
 24type compactDialogCmp struct {
 25	wWidth, wHeight int
 26	width, height   int
 27	selected        int
 28	keyMap          KeyMap
 29	sessionID       string
 30	progress        string
 31	agent           agent.Coordinator
 32	noAsk           bool // If true, skip confirmation dialog
 33}
 34
 35// NewCompactDialogCmp creates a new session compact dialog
 36func NewCompactDialogCmp(agent agent.Coordinator, sessionID string, noAsk bool) CompactDialog {
 37	return &compactDialogCmp{
 38		sessionID: sessionID,
 39		keyMap:    DefaultKeyMap(),
 40		selected:  0,
 41		agent:     agent,
 42		noAsk:     noAsk,
 43	}
 44}
 45
 46func (c *compactDialogCmp) Init() tea.Cmd {
 47	if c.noAsk {
 48		// If noAsk is true, skip confirmation and start compaction immediately
 49		c.agent.Summarize(context.Background(), c.sessionID)
 50		return util.CmdHandler(dialogs.CloseDialogMsg{})
 51	}
 52	return nil
 53}
 54
 55func (c *compactDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 56	switch msg := msg.(type) {
 57	case tea.WindowSizeMsg:
 58		c.wWidth = msg.Width
 59		c.wHeight = msg.Height
 60		cmd := c.SetSize()
 61		return c, cmd
 62	case tea.KeyPressMsg:
 63		switch {
 64		case key.Matches(msg, c.keyMap.ChangeSelection):
 65			c.selected = (c.selected + 1) % 2
 66			return c, nil
 67		case key.Matches(msg, c.keyMap.Select):
 68			if c.selected == 0 {
 69				c.agent.Summarize(context.Background(), c.sessionID)
 70				return c, util.CmdHandler(dialogs.CloseDialogMsg{})
 71			} else {
 72				return c, util.CmdHandler(dialogs.CloseDialogMsg{})
 73			}
 74		case key.Matches(msg, c.keyMap.Y):
 75			c.agent.Summarize(context.Background(), c.sessionID)
 76			return c, util.CmdHandler(dialogs.CloseDialogMsg{})
 77		case key.Matches(msg, c.keyMap.N):
 78			return c, util.CmdHandler(dialogs.CloseDialogMsg{})
 79		case key.Matches(msg, c.keyMap.Close):
 80			return c, util.CmdHandler(dialogs.CloseDialogMsg{})
 81		}
 82	}
 83	return c, nil
 84}
 85
 86func (c *compactDialogCmp) renderButtons() string {
 87	t := styles.CurrentTheme()
 88	baseStyle := t.S().Base
 89
 90	buttons := []core.ButtonOpts{
 91		{
 92			Text:           "Yes",
 93			UnderlineIndex: 0, // "Y"
 94			Selected:       c.selected == 0,
 95		},
 96		{
 97			Text:           "No",
 98			UnderlineIndex: 0, // "N"
 99			Selected:       c.selected == 1,
100		},
101	}
102
103	content := core.SelectableButtons(buttons, "  ")
104
105	return baseStyle.AlignHorizontal(lipgloss.Right).Width(c.width - 4).Render(content)
106}
107
108func (c *compactDialogCmp) render() string {
109	t := styles.CurrentTheme()
110	baseStyle := t.S().Base
111
112	title := "Compact Session"
113	titleView := core.Title(title, c.width-4)
114	explanation := t.S().Text.
115		Width(c.width - 4).
116		Render("This will summarize the current session and reset the context. The conversation history will be condensed into a summary to free up context space while preserving important information.")
117
118	question := t.S().Text.
119		Width(c.width - 4).
120		Render("Do you want to continue?")
121
122	content := baseStyle.Render(lipgloss.JoinVertical(
123		lipgloss.Left,
124		explanation,
125		"",
126		question,
127	))
128
129	buttons := c.renderButtons()
130	dialogContent := lipgloss.JoinVertical(
131		lipgloss.Top,
132		titleView,
133		"",
134		content,
135		"",
136		buttons,
137		"",
138	)
139
140	return baseStyle.
141		Padding(0, 1).
142		Border(lipgloss.RoundedBorder()).
143		BorderForeground(t.BorderFocus).
144		Width(c.width).
145		Render(dialogContent)
146}
147
148func (c *compactDialogCmp) View() string {
149	return c.render()
150}
151
152// SetSize sets the size of the component.
153func (c *compactDialogCmp) SetSize() tea.Cmd {
154	c.width = min(90, c.wWidth)
155	c.height = min(15, c.wHeight)
156	return nil
157}
158
159func (c *compactDialogCmp) Position() (int, int) {
160	row := (c.wHeight / 2) - (c.height / 2)
161	col := (c.wWidth / 2) - (c.width / 2)
162	return row, col
163}
164
165// ID implements CompactDialog.
166func (c *compactDialogCmp) ID() dialogs.DialogID {
167	return CompactDialogID
168}