33 extractTextFromReactNodeNormal ,
44 extractTextFromReactNodeWeb ,
55 parseClickableText ,
6- transformLink ,
6+ transformLink
77} from '@/lib/clickable-results'
88import { useThread } from '@/lib/hooks/use-thread'
99import { cn } from '@/lib/utils'
@@ -15,7 +15,7 @@ export function ClickableText({
1515 isListItem,
1616 sendMessageFromResponse,
1717 webSearchResults = [ ] ,
18- onReferenceFound,
18+ onReferenceFound
1919} : ClickableTextProps ) {
2020 const { webSearch } = useThread ( )
2121
@@ -26,33 +26,77 @@ export function ClickableText({
2626 const createClickHandler = ( text : string ) => ( ) => {
2727 if ( sendMessageFromResponse && text . trim ( ) ) {
2828 const cleanedText = cleanClickableText ( text )
29- sendMessageFromResponse ( `Explain more in-depth and in detail about ${ cleanedText } ` )
30- // sendMessageFromResponse(`Tell me more about ${cleanedText}`)
29+ sendMessageFromResponse (
30+ `Explain more in-depth and in detail about ${ cleanedText } `
31+ )
32+ }
33+ }
34+
35+ const processNestedContent = ( content : React . ReactNode ) : React . ReactNode => {
36+ if ( React . isValidElement ( content ) ) {
37+ if ( content . type === 'ul' || content . type === 'ol' ) {
38+ return React . cloneElement ( content , {
39+ ...content . props ,
40+ className : cn (
41+ content . props . className ,
42+ 'mt-2 ml-4' ,
43+ content . type === 'ul' ? 'list-disc' : 'list-decimal' ,
44+ 'nested-list'
45+ ) ,
46+ children : React . Children . map ( content . props . children , child =>
47+ processNestedContent ( child )
48+ )
49+ } )
50+ }
51+
52+ if ( content . type === 'li' ) {
53+ const hasNestedList = React . Children . toArray (
54+ content . props . children
55+ ) . some (
56+ child =>
57+ React . isValidElement ( child ) &&
58+ ( child . type === 'ul' || child . type === 'ol' )
59+ )
60+
61+ return React . cloneElement ( content , {
62+ ...content . props ,
63+ className : cn (
64+ content . props . className ,
65+ 'ml-4' ,
66+ hasNestedList && 'mt-2'
67+ ) ,
68+ children : processNestedContent ( content . props . children )
69+ } )
70+ }
3171 }
72+
73+ return content
3274 }
3375
3476 const processLink = ( linkElement : React . ReactElement ) => {
3577 const href = linkElement . props . href
36- // Buscar la referencia correspondiente
37- const reference = webSearchResults . find ( ( result ) => result . url === href )
78+ const reference = webSearchResults . find ( result => result . url === href )
3879
3980 if ( reference && onReferenceFound ) {
4081 onReferenceFound ( reference )
41- return null // Remover el link inline
82+ return null
4283 }
4384
44- return linkElement // Mantener links que no son de búsqueda web
85+ return linkElement
4586 }
4687
4788 const renderClickableContent = ( clickableText : string , restText : string ) => (
4889 < >
4990 < span
50- className = { cn ( 'cursor-pointer hover:underline bg-transparent border-none p-0 m-0' , isListItem ? 'text-blue-500' : 'text-link' ) }
51- // biome-ignore lint/a11y/useSemanticElements: it is required to have a span to not break the text
91+ className = { cn (
92+ 'cursor-pointer hover:underline bg-transparent border-none p-0 m-0' ,
93+ isListItem ? 'text-blue-500' : 'text-link'
94+ ) }
95+ // biome-ignore lint/a11y/useSemanticElements: <explanation>
5296 role = "button"
5397 tabIndex = { 0 }
5498 onClick = { createClickHandler ( clickableText ) }
55- onKeyUp = { ( e ) => e . key === 'Enter' && createClickHandler ( clickableText ) ( ) }
99+ onKeyUp = { e => e . key === 'Enter' && createClickHandler ( clickableText ) ( ) }
56100 >
57101 { clickableText }
58102 </ span >
@@ -63,23 +107,32 @@ export function ClickableText({
63107 if ( Array . isArray ( extractedContent ) ) {
64108 return extractedContent . map ( ( content , index ) => {
65109 if ( React . isValidElement ( content ) ) {
110+ if ( content . type === 'ul' || content . type === 'ol' ) {
111+ return processNestedContent ( content )
112+ }
113+
66114 if ( content . type === 'a' && webSearch ) {
67115 return processLink ( content )
68116 }
69- // Manejo de elementos strong
117+
70118 if ( content . type === 'strong' ) {
71119 const strongContent = extractTextFromReactNodeNormal (
72- ( content . props as { children : React . ReactNode } ) . children ,
120+ ( content . props as { children : React . ReactNode } ) . children
121+ )
122+ const { clickableText, restText } = parseClickableText (
123+ strongContent + ':'
73124 )
74- const { clickableText, restText } = parseClickableText ( strongContent + ':' )
75125
76126 if ( clickableText . trim ( ) ) {
77127 return (
78128 < button
79- key = { `clickable-${ index } ` }
129+ key = { `clickable-${
130+ // biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
131+ index
132+ } `}
80133 className = { cn (
81134 'cursor-pointer hover:underline' ,
82- isListItem ? 'text-blue-500' : 'text-link' ,
135+ isListItem ? 'text-blue-500' : 'text-link'
83136 ) }
84137 onClick = { createClickHandler ( clickableText ) }
85138 type = "button"
@@ -92,19 +145,19 @@ export function ClickableText({
92145 return content
93146 }
94147
95- // Manejo de links cuando webSearch está activo
96148 if ( content . type === 'a' && webSearch ) {
97149 const parentContext = extractedContent
98150 . filter (
99- ( item ) =>
100- typeof item === 'string' || ( React . isValidElement ( item ) && item . type === 'strong' ) ,
151+ item =>
152+ typeof item === 'string' ||
153+ ( React . isValidElement ( item ) && item . type === 'strong' )
101154 )
102- . map ( ( item ) =>
155+ . map ( item =>
103156 typeof item === 'string'
104157 ? item
105158 : extractTextFromReactNodeNormal (
106- ( item . props as { children : React . ReactNode } ) . children ,
107- ) ,
159+ ( item . props as { children : React . ReactNode } ) . children
160+ )
108161 )
109162 . join ( ' ' )
110163 return transformLink ( content , parentContext )
@@ -120,7 +173,12 @@ export function ClickableText({
120173 }
121174
122175 return (
123- < React . Fragment key = { `clickable-${ index } ` } >
176+ < React . Fragment
177+ key = { `clickable-${
178+ // biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
179+ index
180+ } `}
181+ >
124182 { renderClickableContent ( clickableText , restText ) }
125183 </ React . Fragment >
126184 )
@@ -131,7 +189,9 @@ export function ClickableText({
131189 return extractedContent
132190 }
133191
134- const { clickableText, restText } = parseClickableText ( String ( extractedContent ) )
192+ const { clickableText, restText } = parseClickableText (
193+ String ( extractedContent )
194+ )
135195
136196 if ( ! clickableText . trim ( ) ) {
137197 return < > { extractedContent } </ >
0 commit comments