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}