返回 DeepSeek-Reasonix
chat_tui_maintenance.go
根目录 / internal / cli / chat_tui_maintenance.go
1 package cli
2
3 import (
4 tea "charm.land/bubbletea/v2"
5 "context"
6 "fmt"
7 "reasonix/internal/control"
8 "reasonix/internal/event"
9 "reasonix/internal/i18n"
10 "strings"
11 )
12
13 func (m chatTUI) maintenanceCancellable() bool {
14 if m.maintenance == nil || m.ctrl == nil {
15 return false
16 }
17 switch strings.ToLower(strings.TrimSpace(m.maintenance.Activity)) {
18 case "", "starting", "running":
19 return true
20 default:
21 return false
22 }
23 }
24
25 func (m chatTUI) runningWorkingLine(cancelRequested, styled bool) string {
26 if m.state != tuiRunning {
27 if m.maintenance == nil {
28 return ""
29 }
30 var label string
31 switch strings.ToLower(strings.TrimSpace(m.maintenance.Activity)) {
32 case "cancelling":
33 label = i18n.M.CompactionStopping
34 case "finalizing":
35 label = i18n.M.CompactionSaving
36 case "recovery_required":
37 label = i18n.M.CompactionRecoveryRequired
38 default:
39 label = i18n.M.CompactionWorking
40 }
41 return fmt.Sprintf(" %s %s", m.spinner.View(), label)
42 }
43 if m.retryAttempt > 0 && !cancelRequested {
44 if line, ok := m.waitingRecoveryLine(); ok {
45 return line
46 }
47
48 return fmt.Sprintf(" "+i18n.M.ChatStatusRetryingFmt, m.spinner.View(), m.retryAttempt, m.retryMax)
49 }
50
51 var working string
52 if cancelRequested {
53 working = fmt.Sprintf(" "+i18n.M.ChatStatusCancellingFmt, m.spinner.View(), m.elapsed)
54 } else {
55 phaseLabel := m.readStatusLabel
56 if phaseLabel == "" {
57 phaseLabel = turnPhaseStatusLabel(m.turnPhase)
58 }
59 if phaseLabel != "" {
60 working = fmt.Sprintf(" %s %s · %ds", m.spinner.View(), phaseLabel, m.elapsed)
61 } else {
62 working = fmt.Sprintf(" "+i18n.M.ChatStatusThinkingFmt, m.spinner.View(), m.elapsed)
63 }
64 }
65 if m.turnTokens > 0 {
66 working += " · ↓" + shortTokens(m.turnTokens)
67 }
68 if n := m.inboxQueuedCount(); n > 0 {
69 var queued string
70 if n == 1 {
71 queued = " · ✎ 1 in inbox"
72 } else {
73 queued = fmt.Sprintf(" · ✎ %d in inbox", n)
74 }
75 if m.inboxSnap().Paused {
76 queued += " (paused)"
77 }
78 if styled {
79 working += dim(queued)
80 } else {
81 working += queued
82 }
83 }
84 return working
85 }
86
87 func (m *chatTUI) runCompactCommand(input, typedCmd string) tea.Cmd {
88 m.echoLocalCommand(input)
89 // Register maintenance before returning so Esc and new input see its owner.
90 // The worker runs in the background; trailing text remains summary guidance.
91 if submitter, ok := m.ctrl.(interface {
92 SubmitDisplayWithResult(display, input string) control.SubmitResult
93 }); ok {
94 result := submitter.SubmitDisplayWithResult(input, input)
95 if result.OperationID != "" {
96 op := &event.SessionOperationInfo{
97 OperationID: result.OperationID,
98 Kind: "compact",
99 Activity: "running",
100 Status: "running",
101 }
102 m.maintenance = op
103 m.renderSessionOperation(op)
104 }
105 return nil
106 }
107
108 // Compatibility path for older SessionAPI implementations. Keep an
109 // explicit maintenance placeholder so Esc preserves the draft while the
110 // blocking Compact call runs as a Bubble Tea command.
111 focus := strings.TrimSpace(strings.TrimPrefix(input, typedCmd))
112 m.maintenance = &event.SessionOperationInfo{Kind: "compact", Activity: "starting", Status: "running"}
113 m.compactCompatibilityPending = true
114 m.compactLifecycleObserved = false
115 return func() tea.Msg { return compactDoneMsg{err: m.ctrl.Compact(context.Background(), focus)} }
116 }
117
118 func (m *chatTUI) stopMaintenance() {
119 m.ctrl.Cancel()
120 updated := *m.maintenance
121 updated.Activity = "cancelling"
122 updated.Status = "cancelling"
123 m.maintenance = &updated
124 m.renderSessionOperation(&updated)
125 }
126
126 lines GO