Skip to content

Commit d79e529

Browse files
committed
Polish welcome gate
1 parent 4ae84ef commit d79e529

8 files changed

Lines changed: 810 additions & 114 deletions

cmd/chat_commands_session.go

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package cmd
22

33
import (
4-
"context"
54
"fmt"
65
"math/rand"
76
"os"
@@ -55,28 +54,30 @@ func (m *chatModel) handleSessionCommand(cmd string, parts []string, text string
5554
return m, tea.Quit
5655

5756
case "/clear":
57+
if m.manualCompacting {
58+
return m.cancelManualCompact("Compaction cancelled.")
59+
}
5860
// Cancel any running /loop goroutine.
5961
if m.loopCancel != nil {
6062
m.loopCancel()
6163
m.loopCancel = nil
6264
}
63-
m.messages = nil
64-
m.messages = append(m.messages, displayMsg{role: "system", content: "Conversation cleared."})
65+
m.messages = []displayMsg{{role: "system", content: "Conversation cleared."}}
66+
m.viewDirty = true
67+
m.autoScroll = false
6568
return m, nil
6669

6770
case "/compact":
68-
before := m.session.MessageCount()
69-
strat, tokBefore, tokAfter, err := m.session.CompactConversation(context.Background())
70-
after := m.session.MessageCount()
71-
msg := fmt.Sprintf("Compacted (%s): %d → %d messages, ~%dk → ~%dk tokens", strat, before, after, tokBefore/1000, tokAfter/1000)
72-
if err != nil {
73-
msg = fmt.Sprintf("Compacted with fallback: %d → %d messages", before, after)
71+
if m.manualCompacting {
72+
return m.cancelManualCompact("Compaction cancelled.")
7473
}
75-
m.messages = append(m.messages, displayMsg{role: "system", content: msg})
76-
m.compacting = false
77-
m.brailleSpinner.SetLabel(m.spinnerVerb)
78-
m.invalidateConnStatus()
79-
return m, nil
74+
if m.waiting {
75+
m.messages = append(m.messages, displayMsg{role: "system", content: "Wait for the current response to finish, then run /compact."})
76+
m.viewDirty = true
77+
m.updateViewportContent()
78+
return m, nil
79+
}
80+
return m.startManualCompact()
8081

8182
case "/history":
8283
entries, err := session.List()
@@ -105,7 +106,7 @@ func (m *chatModel) handleSessionCommand(cmd string, parts []string, text string
105106
return m, nil
106107
}
107108
m.sessionID = s.ID
108-
m.messages = nil
109+
m.messages = []displayMsg{{role: "welcome", content: m.welcomeCache}}
109110
var msgs []client.EyrieMessage
110111
for _, sm := range s.Messages {
111112
em := client.EyrieMessage{Role: sm.Role, Content: sm.Content}
@@ -123,6 +124,8 @@ func (m *chatModel) handleSessionCommand(cmd string, parts []string, text string
123124
}
124125
m.session.LoadMessages(msgs)
125126
m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Recovered: %s\nSession %s ready (%d msgs)", note, s.ID, len(s.Messages))})
127+
m.viewDirty = true
128+
m.autoScroll = false
126129
return m, nil
127130
}
128131
// List candidates
@@ -151,7 +154,7 @@ func (m *chatModel) handleSessionCommand(cmd string, parts []string, text string
151154
return m, nil
152155
}
153156
m.sessionID = saved.ID
154-
m.messages = nil
157+
m.messages = []displayMsg{{role: "welcome", content: m.welcomeCache}}
155158
var msgs []client.EyrieMessage
156159
for _, sm := range saved.Messages {
157160
em := client.EyrieMessage{Role: sm.Role, Content: sm.Content}
@@ -169,6 +172,8 @@ func (m *chatModel) handleSessionCommand(cmd string, parts []string, text string
169172
}
170173
m.session.LoadMessages(msgs)
171174
m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Resumed session %s", saved.ID)})
175+
m.viewDirty = true
176+
m.autoScroll = false
172177
return m, nil
173178

174179
case "/fork":

cmd/chat_layout.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package cmd
2+
3+
import (
4+
"strings"
5+
)
6+
7+
const minChatViewportLines = 4
8+
9+
// fixedWelcomeLineCount reserves room for the branded welcome pane above chat.
10+
func (m chatModel) fixedWelcomeLineCount() int {
11+
if strings.TrimSpace(m.welcomeCache) == "" || m.onWelcomeGate() {
12+
return 0
13+
}
14+
lines := strings.Split(strings.TrimRight(m.welcomeCache, "\n"), "\n")
15+
count := len(lines)
16+
if m.height <= 0 {
17+
return count
18+
}
19+
max := m.height - m.chatBottomBarLines() - minChatViewportLines - 1
20+
if max < 0 {
21+
max = 0
22+
}
23+
if count > max {
24+
count = max
25+
}
26+
return count
27+
}
28+
29+
// renderFixedWelcomePane draws the welcome screen above the scrollable chat viewport.
30+
func (m chatModel) renderFixedWelcomePane(width int) string {
31+
if m.fixedWelcomeLineCount() == 0 {
32+
return ""
33+
}
34+
lines := strings.Split(strings.TrimRight(m.welcomeCache, "\n"), "\n")
35+
max := m.fixedWelcomeLineCount()
36+
if len(lines) > max {
37+
lines = lines[:max]
38+
}
39+
return strings.Join(lines, "\n")
40+
}
41+
42+
// withSyncedLayout returns m with viewport size reserved for welcome + bottom chrome.
43+
func (m chatModel) withSyncedLayout() chatModel {
44+
if m.height <= 0 {
45+
return m
46+
}
47+
bottomH := m.chatBottomBarLines()
48+
welcomeH := m.fixedWelcomeLineCount()
49+
vpH := m.height - bottomH - welcomeH
50+
if welcomeH > 0 {
51+
vpH--
52+
}
53+
if m.onWelcomeGate() {
54+
vpH = minChatViewportLines
55+
}
56+
if vpH < minChatViewportLines {
57+
vpH = minChatViewportLines
58+
}
59+
if m.viewport.Height != vpH {
60+
m.viewport.Height = vpH
61+
}
62+
w := m.width
63+
if w <= 0 {
64+
w = 80
65+
}
66+
vpW := m.chatViewportWidth(w)
67+
if m.viewport.Width != vpW {
68+
m.viewport.Width = vpW
69+
}
70+
return m
71+
}
72+
73+
// measureInputBoxLines returns rendered input box height (borders + content).
74+
func (m chatModel) measureInputBoxLines(footerW int) int {
75+
view := m.input.View()
76+
if m.useConfigInput {
77+
view = m.configInput.View()
78+
}
79+
box := inputBorderStyle.Width(footerW).Render(view)
80+
lines := strings.Split(strings.TrimRight(box, "\n"), "\n")
81+
if len(lines) == 0 {
82+
return 3
83+
}
84+
return len(lines)
85+
}

0 commit comments

Comments
 (0)