Skip to content

Commit 413ff1f

Browse files
committed
feat(cli): add message-block-store and block-margins utilities
Add zustand store for managing message block context and callbacks. Add block-margins utility for consistent spacing calculations.
1 parent f36bb00 commit 413ff1f

File tree

2 files changed

+152
-0
lines changed

2 files changed

+152
-0
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { create } from 'zustand'
2+
import { immer } from 'zustand/middleware/immer'
3+
4+
import type { ChatMessage } from '../types/chat'
5+
import type { ChatTheme } from '../types/theme-system'
6+
import type { MarkdownPalette } from '../utils/markdown-renderer'
7+
8+
/**
9+
* Context values that are updated by the Chat component and consumed by
10+
* message rendering components (MessageWithAgents, AgentMessage, etc).
11+
*/
12+
export interface MessageBlockContext {
13+
/** Active chat theme (colors, etc). */
14+
theme: ChatTheme | null
15+
/** Palette for markdown rendering. Null until Chat component initializes it. */
16+
markdownPalette: MarkdownPalette | null
17+
/** Message tree mapping parent message ID -> child agent messages. */
18+
messageTree: Map<string, ChatMessage[]> | null
19+
/** Whether the main agent is currently waiting for a response. */
20+
isWaitingForResponse: boolean
21+
/** Timer start time for the main agent stream, used for UI timers. */
22+
timerStartTime: number | null
23+
/** Available width for rendering message content. */
24+
availableWidth: number
25+
}
26+
27+
/**
28+
* Stable callback functions for message block interactions.
29+
* These are set by the Chat component and consumed by message blocks.
30+
*/
31+
export interface MessageBlockCallbacks {
32+
onToggleCollapsed: (id: string) => void
33+
onBuildFast: () => void
34+
onBuildMax: () => void
35+
onFeedback: (
36+
messageId: string,
37+
options?: {
38+
category?: string
39+
footerMessage?: string
40+
errors?: Array<{ id: string; message: string }>
41+
},
42+
) => void
43+
onCloseFeedback: () => void
44+
}
45+
46+
interface MessageBlockStoreState {
47+
context: MessageBlockContext
48+
callbacks: MessageBlockCallbacks
49+
}
50+
51+
interface MessageBlockStoreActions {
52+
/**
53+
* Batch update context values. Pass only the values you want to update.
54+
*
55+
* This is called from the Chat component whenever any of the dependent
56+
* values (theme, markdownPalette, messageTree, etc) change.
57+
*/
58+
setContext: (context: Partial<MessageBlockContext>) => void
59+
/**
60+
* Replace all callbacks at once. These are typically stable functions set
61+
* up once when the Chat component mounts.
62+
*/
63+
setCallbacks: (callbacks: MessageBlockCallbacks) => void
64+
/**
65+
* Reset the store to its initial state. Primarily used by tests.
66+
*/
67+
reset: () => void
68+
}
69+
70+
type MessageBlockStore = MessageBlockStoreState & MessageBlockStoreActions
71+
72+
const noop = () => {}
73+
const noopFeedback: MessageBlockCallbacks['onFeedback'] = () => {}
74+
75+
const initialContext: MessageBlockContext = {
76+
theme: null,
77+
markdownPalette: null,
78+
messageTree: null,
79+
isWaitingForResponse: false,
80+
timerStartTime: null,
81+
availableWidth: 80,
82+
}
83+
84+
const initialCallbacks: MessageBlockCallbacks = {
85+
onToggleCollapsed: noop,
86+
onBuildFast: noop,
87+
onBuildMax: noop,
88+
onFeedback: noopFeedback,
89+
onCloseFeedback: noop,
90+
}
91+
92+
const initialState: MessageBlockStoreState = {
93+
context: initialContext,
94+
callbacks: initialCallbacks,
95+
}
96+
97+
export const useMessageBlockStore = create<MessageBlockStore>()(
98+
immer((set) => ({
99+
...initialState,
100+
101+
setContext: (updates) =>
102+
set((state) => {
103+
state.context = { ...state.context, ...updates }
104+
}),
105+
106+
setCallbacks: (callbacks) =>
107+
set((state) => {
108+
state.callbacks = callbacks
109+
}),
110+
111+
reset: () =>
112+
set((state) => {
113+
state.context = { ...initialContext }
114+
state.callbacks = { ...initialCallbacks }
115+
}),
116+
})),
117+
)

cli/src/utils/block-margins.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { ContentBlock, TextContentBlock } from '../types/chat'
2+
3+
/**
4+
* Margin calculation result for a content block.
5+
*/
6+
export interface BlockMargins {
7+
marginTop: number
8+
marginBottom: number
9+
}
10+
11+
/** Extracts margins for a text block, suppressing top margin after tool/agent blocks. */
12+
export function extractTextBlockMargins(
13+
block: TextContentBlock,
14+
prevBlock: ContentBlock | null,
15+
): BlockMargins {
16+
const prevBlockSuppressesMargin =
17+
prevBlock !== null &&
18+
(prevBlock.type === 'tool' || prevBlock.type === 'agent')
19+
20+
const marginTop = prevBlockSuppressesMargin ? 0 : (block.marginTop ?? 0)
21+
const marginBottom = block.marginBottom ?? 0
22+
23+
return { marginTop, marginBottom }
24+
}
25+
26+
/** Extracts margins for an HTML block using explicit values without context adjustments. */
27+
export function extractHtmlBlockMargins(block: {
28+
marginTop?: number
29+
marginBottom?: number
30+
}): BlockMargins {
31+
return {
32+
marginTop: block.marginTop ?? 0,
33+
marginBottom: block.marginBottom ?? 0,
34+
}
35+
}

0 commit comments

Comments
 (0)