Skip to content

Commit d8d508a

Browse files
committed
Add mode divider
1 parent 9f87259 commit d8d508a

File tree

6 files changed

+113
-1
lines changed

6 files changed

+113
-1
lines changed

cli/src/chat.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ export const App = ({
122122
toggleAgentMode,
123123
hasReceivedPlanResponse,
124124
setHasReceivedPlanResponse,
125+
lastMessageMode,
126+
setLastMessageMode,
125127
resetChatStore,
126128
} = useChatStore(
127129
useShallow((store) => ({
@@ -152,6 +154,8 @@ export const App = ({
152154
toggleAgentMode: store.toggleAgentMode,
153155
hasReceivedPlanResponse: store.hasReceivedPlanResponse,
154156
setHasReceivedPlanResponse: store.setHasReceivedPlanResponse,
157+
lastMessageMode: store.lastMessageMode,
158+
setLastMessageMode: store.setLastMessageMode,
155159
resetChatStore: store.reset,
156160
})),
157161
)
@@ -615,6 +619,8 @@ export const App = ({
615619
availableWidth: separatorWidth,
616620
onTimerEvent: handleTimerEvent,
617621
setHasReceivedPlanResponse,
622+
lastMessageMode,
623+
setLastMessageMode,
618624
})
619625

620626
sendMessageRef.current = sendMessage
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { TextAttributes } from '@opentui/core'
2+
import React from 'react'
3+
import stringWidth from 'string-width'
4+
5+
import { useTheme } from '../hooks/use-theme'
6+
7+
interface ModeDividerProps {
8+
mode: string
9+
width: number
10+
}
11+
12+
export const ModeDivider = ({ mode, width }: ModeDividerProps) => {
13+
const theme = useTheme()
14+
15+
const label = ` ${mode} `
16+
const labelWidth = stringWidth(label)
17+
const lineWidth = Math.max(0, Math.floor((width - labelWidth) / 2))
18+
const leftLine = '─'.repeat(lineWidth)
19+
const rightLine = '─'.repeat(Math.max(0, width - lineWidth - labelWidth))
20+
21+
return (
22+
<box
23+
style={{
24+
width: '100%',
25+
flexDirection: 'row',
26+
justifyContent: 'center',
27+
paddingTop: 1,
28+
paddingBottom: 1,
29+
}}
30+
>
31+
<text style={{ wrapMode: 'none' }}>
32+
<span fg={theme.border}>{leftLine}</span>
33+
<span fg={theme.foreground} attributes={TextAttributes.BOLD}>
34+
{label}
35+
</span>
36+
<span fg={theme.border}>{rightLine}</span>
37+
</text>
38+
</box>
39+
)
40+
}

cli/src/hooks/use-message-renderer.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useMemo, type ReactNode } from 'react'
33
import React from 'react'
44

55
import { MessageBlock } from '../components/message-block'
6+
import { ModeDivider } from '../components/mode-divider'
67
import {
78
renderMarkdown,
89
hasMarkdown,
@@ -271,6 +272,22 @@ export const useMessageRenderer = (
271272
const isAi = message.variant === 'ai'
272273
const isUser = message.variant === 'user'
273274
const isError = message.variant === 'error'
275+
276+
// Check if this is a mode divider message
277+
if (
278+
message.blocks &&
279+
message.blocks.length === 1 &&
280+
message.blocks[0].type === 'mode-divider'
281+
) {
282+
const dividerBlock = message.blocks[0]
283+
return (
284+
<ModeDivider
285+
key={message.id}
286+
mode={dividerBlock.mode}
287+
width={availableWidth}
288+
/>
289+
)
290+
}
274291
const lineColor = isError ? 'red' : isAi ? theme.aiLine : theme.userLine
275292
const textColor = isError
276293
? theme.foreground

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

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type { ChatMessage, ContentBlock, ToolContentBlock } from '../types/chat'
1515
import type { SendMessageFn } from '../types/contracts/send-message'
1616
import type { ParamsOf } from '../types/function-params'
1717
import type { SetElement } from '../types/utils'
18+
import type { AgentMode } from '../utils/constants'
1819
import type { AgentDefinition, ToolName } from '@codebuff/sdk'
1920
import type { SetStateAction } from 'react'
2021

@@ -172,6 +173,8 @@ interface UseSendMessageOptions {
172173
availableWidth?: number
173174
onTimerEvent?: (event: SendMessageTimerEvent) => void
174175
setHasReceivedPlanResponse: (value: boolean) => void
176+
lastMessageMode: AgentMode | null
177+
setLastMessageMode: (mode: AgentMode | null) => void
175178
}
176179

177180
export const useSendMessage = ({
@@ -201,6 +204,8 @@ export const useSendMessage = ({
201204
availableWidth = 80,
202205
onTimerEvent = () => {},
203206
setHasReceivedPlanResponse,
207+
lastMessageMode,
208+
setLastMessageMode,
204209
}: UseSendMessageOptions): {
205210
sendMessage: SendMessageFn
206211
clearMessages: () => void
@@ -351,6 +356,10 @@ export const useSendMessage = ({
351356
// This is computed efficiently in the Zustand store
352357
const previousToggleIds = allToggleIds
353358

359+
// Check if mode changed and insert divider if needed
360+
// Also show divider on first message (when lastMessageMode is null)
361+
const shouldInsertDivider = lastMessageMode === null || lastMessageMode !== agentMode
362+
354363
// Add user message to UI first
355364
const userMessage: ChatMessage = {
356365
id: `user-${Date.now()}`,
@@ -360,7 +369,27 @@ export const useSendMessage = ({
360369
}
361370

362371
applyMessageUpdate((prev) => {
363-
let newMessages = [...prev, userMessage]
372+
let newMessages = [...prev]
373+
374+
// Insert mode divider if mode changed
375+
if (shouldInsertDivider) {
376+
const dividerMessage: ChatMessage = {
377+
id: `divider-${Date.now()}`,
378+
variant: 'ai',
379+
content: '',
380+
blocks: [
381+
{
382+
type: 'mode-divider',
383+
mode: agentMode,
384+
},
385+
],
386+
timestamp: formatTimestamp(),
387+
}
388+
newMessages.push(dividerMessage)
389+
}
390+
391+
newMessages.push(userMessage)
392+
364393
if (postUserMessage) {
365394
newMessages = postUserMessage(newMessages)
366395
}
@@ -369,6 +398,10 @@ export const useSendMessage = ({
369398
}
370399
return newMessages
371400
})
401+
402+
// Update last message mode
403+
setLastMessageMode(agentMode)
404+
372405
await yieldToEventLoop()
373406

374407
// Auto-collapse previous message toggles to minimize clutter.
@@ -1488,6 +1521,8 @@ export const useSendMessage = ({
14881521
scrollToLatest,
14891522
availableWidth,
14901523
setHasReceivedPlanResponse,
1524+
lastMessageMode,
1525+
setLastMessageMode,
14911526
],
14921527
)
14931528

cli/src/state/chat-store.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type ChatStoreState = {
1919
agentSelectedIndex: number
2020
agentMode: AgentMode
2121
hasReceivedPlanResponse: boolean
22+
lastMessageMode: AgentMode | null
2223
}
2324

2425
type ChatStoreActions = {
@@ -46,6 +47,7 @@ type ChatStoreActions = {
4647
setAgentMode: (mode: AgentMode) => void
4748
toggleAgentMode: () => void
4849
setHasReceivedPlanResponse: (value: boolean) => void
50+
setLastMessageMode: (mode: AgentMode | null) => void
4951
reset: () => void
5052
}
5153

@@ -67,6 +69,7 @@ const initialState: ChatStoreState = {
6769
agentSelectedIndex: 0,
6870
agentMode: 'FAST',
6971
hasReceivedPlanResponse: false,
72+
lastMessageMode: null,
7073
}
7174

7275
export const useChatStore = create<ChatStore>()(
@@ -158,6 +161,11 @@ export const useChatStore = create<ChatStore>()(
158161
state.hasReceivedPlanResponse = value
159162
}),
160163

164+
setLastMessageMode: (mode) =>
165+
set((state) => {
166+
state.lastMessageMode = mode
167+
}),
168+
161169
reset: () =>
162170
set((state) => {
163171
state.messages = initialState.messages.slice()
@@ -173,6 +181,7 @@ export const useChatStore = create<ChatStore>()(
173181
state.agentSelectedIndex = initialState.agentSelectedIndex
174182
state.agentMode = initialState.agentMode
175183
state.hasReceivedPlanResponse = initialState.hasReceivedPlanResponse
184+
state.lastMessageMode = initialState.lastMessageMode
176185
}),
177186
})),
178187
)

cli/src/types/chat.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,15 @@ export type AgentListContentBlock = {
4444
agents: Array<{ id: string; displayName: string }>
4545
agentsDir: string
4646
}
47+
export type ModeDividerContentBlock = {
48+
type: 'mode-divider'
49+
mode: string
50+
}
4751
export type ContentBlock =
4852
| AgentContentBlock
4953
| AgentListContentBlock
5054
| HtmlContentBlock
55+
| ModeDividerContentBlock
5156
| TextContentBlock
5257
| ToolContentBlock
5358

0 commit comments

Comments
 (0)