@@ -0,0 +1,266 @@
6+ "github.com/charmbracelet/bubbles/v2/key"
7+ tea "github.com/charmbracelet/bubbletea/v2"
8+ "github.com/charmbracelet/lipgloss/v2"
10+ "github.com/charmbracelet/crush/internal/llm/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"
17+const CompactDialogID dialogs.DialogID = "compact"
19+// CompactDialog interface for the session compact dialog
20+type CompactDialog interface {
24+type compactDialogCmp struct {
33+ noAsk bool // If true, skip confirmation dialog
39+ stateConfirm compactState = iota
44+// NewCompactDialogCmp creates a new session compact dialog
45+func NewCompactDialogCmp(agent agent.Service, sessionID string, noAsk bool) CompactDialog {
46+ return &compactDialogCmp{
47+ sessionID: sessionID,
48+ keyMap: DefaultKeyMap(),
56+func (c *compactDialogCmp) Init() tea.Cmd {
58+ // If noAsk is true, skip confirmation and start compaction immediately
59+ return c.startCompaction()
64+func (c *compactDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
65+ switch msg := msg.(type) {
66+ case tea.WindowSizeMsg:
68+ c.wHeight = msg.Height
72+ case tea.KeyPressMsg:
76+ case key.Matches(msg, c.keyMap.ChangeSelection):
77+ c.selected = (c.selected + 1) % 2
79+ case key.Matches(msg, c.keyMap.Select):
81+ return c, c.startCompaction()
83+ return c, util.CmdHandler(dialogs.CloseDialogMsg{})
85+ case key.Matches(msg, c.keyMap.Y):
86+ return c, c.startCompaction()
87+ case key.Matches(msg, c.keyMap.N):
88+ return c, util.CmdHandler(dialogs.CloseDialogMsg{})
89+ case key.Matches(msg, c.keyMap.Close):
90+ return c, util.CmdHandler(dialogs.CloseDialogMsg{})
92+ case stateCompacting:
94+ case key.Matches(msg, c.keyMap.Close):
95+ return c, util.CmdHandler(dialogs.CloseDialogMsg{})
99+ case key.Matches(msg, c.keyMap.Select):
100+ return c, util.CmdHandler(dialogs.CloseDialogMsg{})
101+ case key.Matches(msg, c.keyMap.Close):
102+ return c, util.CmdHandler(dialogs.CloseDialogMsg{})
106+ case agent.AgentEvent:
107+ if msg.Type == agent.AgentEventTypeSummarize {
108+ if msg.Error != nil {
109+ c.state = stateError
110+ c.progress = "Error: " + msg.Error.Error()
111+ } else if msg.Done {
112+ return c, util.CmdHandler(
113+ dialogs.CloseDialogMsg{},
116+ c.progress = msg.Progress
125+func (c *compactDialogCmp) startCompaction() tea.Cmd {
126+ c.state = stateCompacting
127+ c.progress = "Starting summarization..."
128+ return func() tea.Msg {
129+ err := c.agent.Summarize(context.Background(), c.sessionID)
131+ c.state = stateError
132+ c.progress = "Error: " + err.Error()
138+func (c *compactDialogCmp) renderButtons() string {
139+ t := styles.CurrentTheme()
140+ baseStyle := t.S().Base
142+ buttons := []core.ButtonOpts{
145+ UnderlineIndex: 0, // "Y"
146+ Selected: c.selected == 0,
150+ UnderlineIndex: 0, // "N"
151+ Selected: c.selected == 1,
155+ content := core.SelectableButtons(buttons, " ")
157+ return baseStyle.AlignHorizontal(lipgloss.Right).Width(c.width - 4).Render(content)
160+func (c *compactDialogCmp) renderContent() string {
161+ t := styles.CurrentTheme()
162+ baseStyle := t.S().Base
166+ explanation := t.S().Text.
168+ 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.")
170+ question := t.S().Text.
172+ Render("Do you want to continue?")
174+ return baseStyle.Render(lipgloss.JoinVertical(
180+ case stateCompacting:
181+ return baseStyle.Render(lipgloss.JoinVertical(
188+ return baseStyle.Render(lipgloss.JoinVertical(
192+ "Press Enter to close",
198+func (c *compactDialogCmp) render() string {
199+ t := styles.CurrentTheme()
200+ baseStyle := t.S().Base
205+ title = "Compact Session"
206+ case stateCompacting:
207+ title = "Compacting Session"
209+ title = "Compact Failed"
212+ titleView := core.Title(title, c.width-4)
213+ content := c.renderContent()
215+ var dialogContent string
216+ if c.state == stateConfirm {
217+ buttons := c.renderButtons()
218+ dialogContent = lipgloss.JoinVertical(
228+ dialogContent = lipgloss.JoinVertical(
239+ Border(lipgloss.RoundedBorder()).
240+ BorderForeground(t.BorderFocus).
242+ Render(dialogContent)
245+func (c *compactDialogCmp) View() tea.View {
246+ return tea.NewView(c.render())
249+// SetSize sets the size of the component.
250+func (c *compactDialogCmp) SetSize() tea.Cmd {
251+ c.width = min(90, c.wWidth)
252+ c.height = min(15, c.wHeight)
256+func (c *compactDialogCmp) Position() (int, int) {
257+ row := (c.wHeight / 2) - (c.height / 2)
258+ col := (c.wWidth / 2) - (c.width / 2)
262+// ID implements CompactDialog.
263+func (c *compactDialogCmp) ID() dialogs.DialogID {
264+ return CompactDialogID