11import { TextAttributes , type BorderCharacters } from '@opentui/core'
22import React , { type ReactNode } from 'react'
33
4- const borderCharsWithoutVertical : BorderCharacters = {
5- topLeft : '┌ ' ,
6- topRight : '┐ ' ,
7- bottomLeft : '└ ' ,
8- bottomRight : '┘ ' ,
4+ const containerBorderChars : BorderCharacters = {
5+ topLeft : '╭ ' ,
6+ topRight : '╮ ' ,
7+ bottomLeft : '╰ ' ,
8+ bottomRight : '╯ ' ,
99 horizontal : '─' ,
10- vertical : ' ' ,
11- topT : ' ' ,
12- bottomT : ' ' ,
13- leftT : ' ' ,
14- rightT : ' ' ,
15- cross : ' ' ,
10+ vertical : '│ ' ,
11+ topT : '┬ ' ,
12+ bottomT : '┴ ' ,
13+ leftT : '├ ' ,
14+ rightT : '┤ ' ,
15+ cross : '┼ ' ,
1616}
1717
1818import type { ChatTheme } from '../utils/theme-system'
@@ -25,9 +25,12 @@ interface BranchItemProps {
2525 agentId ?: string
2626 isCollapsed : boolean
2727 isStreaming : boolean
28- branchChar : string
2928 streamingPreview : string
3029 finishedPreview : string
30+ availableWidth : number
31+ statusLabel ?: string
32+ statusColor ?: string
33+ statusIndicator ?: string
3134 theme : ChatTheme
3235 onToggle : ( ) => void
3336}
@@ -39,13 +42,15 @@ export const BranchItem = ({
3942 agentId,
4043 isCollapsed,
4144 isStreaming,
42- branchChar,
4345 streamingPreview,
4446 finishedPreview,
47+ availableWidth,
48+ statusLabel,
49+ statusColor,
50+ statusIndicator = '●' ,
4551 theme,
4652 onToggle,
4753} : BranchItemProps ) => {
48- const cornerColor = theme . agentPrefix
4954 const isExpanded = ! isCollapsed
5055 const toggleFrameColor = isExpanded
5156 ? theme . agentToggleExpandedBg
@@ -57,6 +62,16 @@ export const BranchItem = ({
5762 const toggleLabel = `${ isCollapsed ? '▸' : '▾' } `
5863 const collapseButtonFrame = theme . agentToggleExpandedBg
5964 const collapseButtonText = collapseButtonFrame
65+ const separatorColor = theme . agentResponseCount
66+ const innerContentWidth = Math . max ( 0 , Math . floor ( availableWidth ) - 4 )
67+ const horizontalLine =
68+ innerContentWidth > 0 ? '─' . repeat ( innerContentWidth ) : ''
69+ const statusText =
70+ statusLabel && statusLabel . length > 0
71+ ? statusIndicator === '✓'
72+ ? `${ statusLabel } ${ statusIndicator } `
73+ : `${ statusIndicator } ${ statusLabel } `
74+ : null
6075
6176 const isTextRenderable = ( value : ReactNode ) : boolean => {
6277 if ( value === null || value === undefined || typeof value === 'boolean' ) {
@@ -151,80 +166,142 @@ export const BranchItem = ({
151166 flexShrink : 0 ,
152167 marginTop : 1 ,
153168 marginBottom : 0 ,
169+ width : '100%' ,
154170 } }
155171 >
156- < box style = { { flexDirection : 'column' , gap : 0 } } >
157- < RaisedPill
158- segments = { [
159- { text : toggleLabel , fg : toggleIconColor } ,
160- {
161- text : name ,
162- fg : toggleLabelColor ,
163- attr : isExpanded ? TextAttributes . BOLD : undefined ,
164- } ,
165- ] }
166- frameColor = { toggleFrameColor }
167- textColor = { toggleLabelColor }
168- onPress = { onToggle }
169- style = { { alignSelf : 'flex-start' } }
170- />
171- < box style = { { flexShrink : 1 , marginBottom : 0 } } >
172- { isStreaming && isCollapsed && streamingPreview && (
173- < text
174- key = "streaming-preview"
175- fg = { theme . agentText }
176- attributes = { TextAttributes . ITALIC }
177- >
178- { streamingPreview }
172+ < box
173+ border
174+ borderStyle = "single"
175+ borderColor = { toggleFrameColor }
176+ customBorderChars = { containerBorderChars }
177+ style = { {
178+ flexDirection : 'column' ,
179+ gap : 0 ,
180+ paddingLeft : 0 ,
181+ paddingRight : 0 ,
182+ paddingTop : 0 ,
183+ paddingBottom : 0 ,
184+ width : '100%' ,
185+ } }
186+ >
187+ { prompt ? (
188+ < box
189+ style = { {
190+ flexDirection : 'column' ,
191+ gap : 0 ,
192+ paddingLeft : 1 ,
193+ paddingRight : 1 ,
194+ paddingTop : 0 ,
195+ paddingBottom : 0 ,
196+ width : '100%' ,
197+ } }
198+ >
199+ < text fg = { theme . agentToggleHeaderText } > Prompt</ text >
200+ < text fg = { theme . agentText } style = { { wrapMode : 'word' } } >
201+ { prompt }
179202 </ text >
180- ) }
181- { ! isStreaming && isCollapsed && finishedPreview && (
182- < text
183- key = "finished-preview"
184- fg = { theme . agentResponseCount }
185- attributes = { TextAttributes . ITALIC }
203+ </ box >
204+ ) : null }
205+ < box
206+ style = { {
207+ flexDirection : 'row' ,
208+ alignItems : 'center' ,
209+ paddingLeft : 1 ,
210+ paddingRight : 1 ,
211+ paddingTop : 0 ,
212+ paddingBottom : 0 ,
213+ width : '100%' ,
214+ } }
215+ onMouseDown = { onToggle }
216+ >
217+ < text style = { { wrapMode : 'none' } } >
218+ < span fg = { toggleIconColor } > { toggleLabel } </ span >
219+ < span
220+ fg = { toggleLabelColor }
221+ attributes = { isExpanded ? TextAttributes . BOLD : undefined }
186222 >
187- { finishedPreview }
188- </ text >
189- ) }
190- { ! isCollapsed && (
191- < box style = { { flexDirection : 'column' , gap : 0 } } >
192- { content && (
223+ { name }
224+ </ span >
225+ { statusText ? (
226+ < span
227+ fg = { statusColor ?? theme . agentResponseCount }
228+ attributes = { TextAttributes . DIM }
229+ >
230+ { ` ${ statusText } ` }
231+ </ span >
232+ ) : null }
233+ </ text >
234+ </ box >
235+
236+ { isCollapsed ? (
237+ ( isStreaming && streamingPreview ) || ( ! isStreaming && finishedPreview ) ? (
238+ < box
239+ style = { {
240+ paddingLeft : 1 ,
241+ paddingRight : 1 ,
242+ paddingTop : 0 ,
243+ paddingBottom : 1 ,
244+ } }
245+ >
246+ < text
247+ fg = { isStreaming ? theme . agentText : theme . agentResponseCount }
248+ attributes = { TextAttributes . ITALIC }
249+ >
250+ { isStreaming ? streamingPreview : finishedPreview }
251+ </ text >
252+ </ box >
253+ ) : null
254+ ) : (
255+ < >
256+ { horizontalLine && (
257+ < box style = { { paddingLeft : 1 , paddingRight : 1 } } >
258+ < text style = { { wrapMode : 'none' } } >
259+ < span fg = { separatorColor } > { horizontalLine } </ span >
260+ </ text >
261+ </ box >
262+ ) }
263+ < box
264+ style = { {
265+ flexDirection : 'column' ,
266+ gap : 0 ,
267+ paddingLeft : 1 ,
268+ paddingRight : 1 ,
269+ paddingTop : 0 ,
270+ paddingBottom : 0 ,
271+ } }
272+ >
273+ { prompt && (
193274 < box
194- border
195- borderStyle = "single"
196- borderColor = { cornerColor }
197- customBorderChars = { borderCharsWithoutVertical }
198275 style = { {
199276 flexDirection : 'column' ,
200277 gap : 0 ,
201- paddingLeft : 1 ,
202- paddingRight : 1 ,
203- paddingTop : 0 ,
204- paddingBottom : 0 ,
278+ marginBottom : content ? 1 : 0 ,
205279 } }
206280 >
207- { prompt && (
208- < box style = { { flexDirection : 'column' , gap : 0 } } >
209- < text fg = { theme . agentToggleHeaderText } > Prompt</ text >
210- < text fg = { theme . agentText } > { prompt } </ text >
211- < text > </ text >
212- < text fg = { theme . agentToggleHeaderText } > Response</ text >
213- </ box >
281+ < text fg = { theme . agentToggleHeaderText } > Prompt</ text >
282+ < text fg = { theme . agentText } style = { { wrapMode : 'word' } } >
283+ { prompt }
284+ </ text >
285+ { content && (
286+ < text fg = { theme . agentToggleHeaderText } style = { { marginTop : 1 } } >
287+ Response
288+ </ text >
214289 ) }
215- { renderExpandedContent ( content ) }
216290 </ box >
217291 ) }
218- < RaisedPill
219- segments = { [ { text : 'Collapse' , fg : collapseButtonText } ] }
220- frameColor = { collapseButtonFrame }
221- textColor = { collapseButtonText }
222- onPress = { onToggle }
223- style = { { alignSelf : 'flex-end' } }
224- />
292+ { renderExpandedContent ( content ) }
293+ < box style = { { alignSelf : 'flex-end' , marginTop : content ? 0 : 1 } } >
294+ < RaisedPill
295+ segments = { [ { text : 'Collapse' , fg : collapseButtonText } ] }
296+ frameColor = { collapseButtonFrame }
297+ textColor = { collapseButtonText }
298+ padding = { 0 }
299+ onPress = { onToggle }
300+ />
301+ </ box >
225302 </ box >
226- ) }
227- </ box >
303+ </ >
304+ ) }
228305 </ box >
229306 </ box >
230307 )
0 commit comments