Skip to content

Commit a2f1b50

Browse files
committed
Add Freebuff session restart action
1 parent 5fd1dbd commit a2f1b50

2 files changed

Lines changed: 60 additions & 22 deletions

File tree

cli/src/components/session-ended-banner.tsx

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import { useKeyboard } from '@opentui/react'
33
import React, { useCallback, useState } from 'react'
44

55
import { Button } from './button'
6-
import { returnToFreebuffLanding } from '../hooks/use-freebuff-session'
6+
import {
7+
refreshFreebuffSession,
8+
returnToFreebuffLanding,
9+
} from '../hooks/use-freebuff-session'
710
import { useTheme } from '../hooks/use-theme'
811
import { BORDER_CHARS } from '../utils/ui-constants'
912

@@ -25,36 +28,46 @@ export const SessionEndedBanner: React.FC<SessionEndedBannerProps> = ({
2528
isStreaming,
2629
}) => {
2730
const theme = useTheme()
28-
const [rejoining, setRejoining] = useState(false)
31+
const [pendingAction, setPendingAction] = useState<
32+
'waiting-room' | 'same-chat' | null
33+
>(null)
2934

30-
// While a request is still streaming, rejoin is disabled: it would
35+
// While a request is still streaming, restart is disabled: it would
3136
// unmount <Chat> and abort the in-flight agent run. The promise is "we
3237
// let the agent finish" — honoring that means Enter does nothing until
3338
// the stream ends or the user hits Esc.
34-
const canRejoin = !isStreaming && !rejoining
35-
const rejoin = useCallback(() => {
36-
if (!canRejoin) return
37-
setRejoining(true)
39+
const canRestart = !isStreaming && pendingAction === null
40+
const pickNewModel = useCallback(() => {
41+
if (!canRestart) return
42+
setPendingAction('waiting-room')
3843
// Drop back to the landing picker (status: 'none') so the user picks a
3944
// model and hits Enter again to commit, instead of being silently
4045
// re-queued. app.tsx swaps us into <WaitingRoomScreen> on the
41-
// transition, unmounting this banner — no need to clear `rejoining` on
46+
// transition, unmounting this banner — no need to clear the pending state on
4247
// success.
4348
returnToFreebuffLanding({ resetChat: true }).catch(() =>
44-
setRejoining(false),
49+
setPendingAction(null),
4550
)
46-
}, [canRejoin])
51+
}, [canRestart])
52+
53+
const startSameChatSession = useCallback(() => {
54+
if (!canRestart) return
55+
setPendingAction('same-chat')
56+
// Re-POST with the currently selected model and keep the chat/run state
57+
// intact so the next prompt continues the same conversation.
58+
refreshFreebuffSession().catch(() => setPendingAction(null))
59+
}, [canRestart])
4760

4861
useKeyboard(
4962
useCallback(
5063
(key: KeyEvent) => {
51-
if (!canRejoin) return
64+
if (!canRestart) return
5265
if (key.name === 'return' || key.name === 'enter') {
5366
key.preventDefault?.()
54-
rejoin()
67+
startSameChatSession()
5568
}
5669
},
57-
[rejoin, canRejoin],
70+
[startSameChatSession, canRestart],
5871
),
5972
)
6073

@@ -83,14 +96,36 @@ export const SessionEndedBanner: React.FC<SessionEndedBannerProps> = ({
8396
Agent is wrapping up. Rejoin the wait room after it's finished.
8497
</text>
8598
) : (
86-
<Button onClick={rejoin}>
87-
<text
88-
style={{ fg: rejoining ? theme.muted : theme.primary }}
89-
attributes={TextAttributes.BOLD}
90-
>
91-
{rejoining ? 'Rejoining…' : 'Press Enter to rejoin waiting room'}
92-
</text>
93-
</Button>
99+
<box style={{ flexDirection: 'row', gap: 2, flexWrap: 'wrap' }}>
100+
<Button onClick={startSameChatSession}>
101+
<text
102+
style={{
103+
fg:
104+
pendingAction === 'same-chat'
105+
? theme.muted
106+
: theme.primary,
107+
}}
108+
attributes={TextAttributes.BOLD}
109+
>
110+
{pendingAction === 'same-chat'
111+
? 'Starting…'
112+
: 'Press Enter to start new session (same model + chat)'}
113+
</text>
114+
</Button>
115+
<Button onClick={pickNewModel}>
116+
<text
117+
style={{
118+
fg:
119+
pendingAction === 'waiting-room' ? theme.muted : theme.info,
120+
}}
121+
attributes={TextAttributes.BOLD}
122+
>
123+
{pendingAction === 'waiting-room'
124+
? 'Opening model picker…'
125+
: 'Pick a new model'}
126+
</text>
127+
</Button>
128+
</box>
94129
)}
95130
</box>
96131
)

cli/src/hooks/use-send-message.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ export const useSendMessage = ({
138138
setRunState,
139139
setIsRetrying,
140140
} = useChatStore.getState()
141-
const previousRunStateRef = useRef<RunState | null>(null)
141+
const previousRunStateRef = useRef<RunState | null>(
142+
useChatStore.getState().runState,
143+
)
142144
// Memoize stream controller to maintain referential stability across renders
143145
const streamRefsRef = useRef<ReturnType<
144146
typeof createStreamController
@@ -198,6 +200,7 @@ export const useSendMessage = ({
198200

199201
function clearMessages() {
200202
previousRunStateRef.current = null
203+
setRunState(null)
201204
}
202205

203206
const prepareUserMessage = useCallback(

0 commit comments

Comments
 (0)