Skip to content

Commit e0d81a4

Browse files
authored
Merge pull request #1461 from krissetto/show-tool-complete-in-collapsed-reasoning
fade out competed tool calls in collapsed reasoning blocks
2 parents 440bfab + 5a7e916 commit e0d81a4

File tree

4 files changed

+391
-22
lines changed

4 files changed

+391
-22
lines changed

pkg/tui/components/messages/messages.go

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ type model struct {
8181

8282
// Height tracking system fields
8383
scrollOffset int // Current scroll position in lines
84+
bottomSlack int // Extra blank lines added after content shrinks
8485
rendered string // Complete rendered content string
8586
renderedItems map[int]renderedItem // Cache of rendered items with positions
8687
totalHeight int // Total height of all content in lines
@@ -185,6 +186,9 @@ func (m *model) Update(msg tea.Msg) (layout.Model, tea.Cmd) {
185186
m.invalidateAllItems()
186187
return m, nil
187188

189+
case reasoningblock.BlockMsg:
190+
return m.forwardToReasoningBlock(msg.GetBlockID(), msg)
191+
188192
case tea.KeyPressMsg:
189193
return m.handleKeyPress(msg)
190194
}
@@ -220,6 +224,7 @@ func (m *model) handleMouseClick(msg tea.MouseClickMsg) (layout.Model, tea.Cmd)
220224
if block.IsToggleLine(localLine) {
221225
block.Toggle()
222226
m.userHasScrolled = true // Prevent auto-scroll jump
227+
m.bottomSlack = 0
223228
m.invalidateItem(msgIdx)
224229
return m, nil
225230
}
@@ -310,12 +315,14 @@ func (m *model) handleMouseWheel(msg tea.MouseWheelMsg) (layout.Model, tea.Cmd)
310315
case "wheelup":
311316
if m.scrollOffset > 0 {
312317
m.userHasScrolled = true
318+
m.bottomSlack = 0
313319
for range mouseScrollAmount {
314320
m.setScrollOffset(m.scrollOffset - defaultScrollAmount)
315321
}
316322
}
317323
case "wheeldown":
318324
m.userHasScrolled = true
325+
m.bottomSlack = 0
319326
for range mouseScrollAmount {
320327
m.setScrollOffset(m.scrollOffset + defaultScrollAmount)
321328
}
@@ -374,23 +381,39 @@ func (m *model) View() string {
374381
}
375382

376383
prevTotalHeight := m.totalHeight
384+
prevScrollableHeight := m.totalHeight + m.bottomSlack
377385
m.ensureAllItemsRendered()
378386

379387
if m.totalHeight == 0 {
380388
return ""
381389
}
382390

383-
// Calculate viewport bounds
384-
maxScrollOffset := max(0, m.totalHeight-m.height)
391+
if m.userHasScrolled {
392+
m.bottomSlack = 0
393+
} else {
394+
delta := m.totalHeight - prevTotalHeight
395+
if delta < 0 {
396+
m.bottomSlack += -delta
397+
} else if delta > 0 && m.bottomSlack > 0 {
398+
consume := min(delta, m.bottomSlack)
399+
m.bottomSlack -= consume
400+
}
401+
}
402+
403+
scrollableHeight := m.totalHeight + m.bottomSlack
404+
maxScrollOffset := max(0, scrollableHeight-m.height)
385405

386-
// Auto-scroll if content grew and user hasn't manually scrolled
387-
if !m.userHasScrolled && m.totalHeight > prevTotalHeight {
406+
// Auto-scroll when content grows beyond any slack.
407+
if !m.userHasScrolled && scrollableHeight > prevScrollableHeight {
388408
m.scrollOffset = maxScrollOffset
389409
} else {
390410
m.scrollOffset = max(0, min(m.scrollOffset, maxScrollOffset))
391411
}
392412

393413
lines := strings.Split(m.rendered, "\n")
414+
if m.bottomSlack > 0 {
415+
lines = append(lines, make([]string, m.bottomSlack)...)
416+
}
394417
if len(lines) == 0 {
395418
return ""
396419
}
@@ -519,12 +542,14 @@ const defaultScrollAmount = 1
519542
func (m *model) scrollUp() {
520543
if m.scrollOffset > 0 {
521544
m.userHasScrolled = true
545+
m.bottomSlack = 0
522546
m.setScrollOffset(max(0, m.scrollOffset-defaultScrollAmount))
523547
}
524548
}
525549

526550
func (m *model) scrollDown() {
527551
m.userHasScrolled = true
552+
m.bottomSlack = 0
528553
m.setScrollOffset(m.scrollOffset + defaultScrollAmount)
529554
if m.isAtBottom() {
530555
m.userHasScrolled = false
@@ -533,11 +558,13 @@ func (m *model) scrollDown() {
533558

534559
func (m *model) scrollPageUp() {
535560
m.userHasScrolled = true
561+
m.bottomSlack = 0
536562
m.setScrollOffset(max(0, m.scrollOffset-m.height))
537563
}
538564

539565
func (m *model) scrollPageDown() {
540566
m.userHasScrolled = true
567+
m.bottomSlack = 0
541568
m.setScrollOffset(m.scrollOffset + m.height)
542569
if m.isAtBottom() {
543570
m.userHasScrolled = false
@@ -546,6 +573,7 @@ func (m *model) scrollPageDown() {
546573

547574
func (m *model) scrollToTop() {
548575
m.userHasScrolled = true
576+
m.bottomSlack = 0
549577
m.setScrollOffset(0)
550578
}
551579

@@ -555,7 +583,7 @@ func (m *model) scrollToBottom() {
555583
}
556584

557585
func (m *model) setScrollOffset(offset int) {
558-
maxOffset := max(0, m.totalHeight-m.height)
586+
maxOffset := max(0, m.totalScrollableHeight()-m.height)
559587
m.scrollOffset = max(0, min(offset, maxOffset))
560588
m.scrollbar.SetScrollOffset(m.scrollOffset)
561589
}
@@ -564,7 +592,7 @@ func (m *model) isAtBottom() bool {
564592
if len(m.messages) == 0 {
565593
return true
566594
}
567-
maxScrollOffset := max(0, m.totalHeight-m.height)
595+
maxScrollOffset := max(0, m.totalScrollableHeight()-m.height)
568596
return m.scrollOffset >= maxScrollOffset
569597
}
570598

@@ -657,9 +685,11 @@ func (m *model) scrollToSelectedMessage() {
657685
// Scroll to make the selected message visible
658686
if startLine < m.scrollOffset {
659687
m.userHasScrolled = true
688+
m.bottomSlack = 0
660689
m.setScrollOffset(startLine)
661690
} else if endLine > m.scrollOffset+m.height {
662691
m.userHasScrolled = true
692+
m.bottomSlack = 0
663693
m.setScrollOffset(endLine - m.height)
664694
}
665695
}
@@ -782,6 +812,21 @@ func (m *model) invalidateAllItems() {
782812
m.renderDirty = true
783813
}
784814

815+
// forwardToReasoningBlock finds the reasoning block with the given ID and forwards the message to it.
816+
func (m *model) forwardToReasoningBlock(blockID string, msg tea.Msg) (layout.Model, tea.Cmd) {
817+
for i, tuiMsg := range m.messages {
818+
if tuiMsg.Type == types.MessageTypeAssistantReasoningBlock {
819+
if block, ok := m.views[i].(*reasoningblock.Model); ok && block.ID() == blockID {
820+
updatedView, cmd := m.views[i].Update(msg)
821+
m.views[i] = updatedView
822+
m.invalidateItem(i)
823+
return m, cmd
824+
}
825+
}
826+
}
827+
return m, nil
828+
}
829+
785830
// Message management methods
786831
func (m *model) AddUserMessage(content string) tea.Cmd {
787832
return m.addMessage(types.User(content))
@@ -913,6 +958,7 @@ func (m *model) LoadFromSession(sess *session.Session) tea.Cmd {
913958
m.rendered = ""
914959
m.scrollOffset = 0
915960
m.totalHeight = 0
961+
m.bottomSlack = 0
916962
m.selectedMessageIndex = -1
917963

918964
var cmds []tea.Cmd
@@ -1189,6 +1235,10 @@ func (m *model) contentWidth() int {
11891235
return m.width - 2
11901236
}
11911237

1238+
func (m *model) totalScrollableHeight() int {
1239+
return m.totalHeight + m.bottomSlack
1240+
}
1241+
11921242
// Helper methods
11931243
func (m *model) createToolCallView(msg *types.Message) layout.Model {
11941244
view := tool.New(msg, m.sessionState)
@@ -1258,6 +1308,8 @@ func (m *model) isMouseOnScrollbar(x, y int) bool {
12581308
func (m *model) handleScrollbarUpdate(msg tea.Msg) (layout.Model, tea.Cmd) {
12591309
sb, cmd := m.scrollbar.Update(msg)
12601310
m.scrollbar = sb
1311+
m.userHasScrolled = true
1312+
m.bottomSlack = 0
12611313
m.scrollOffset = m.scrollbar.GetScrollOffset()
12621314
return m, cmd
12631315
}

0 commit comments

Comments
 (0)