@@ -24,6 +24,7 @@ interface ToolRenderContext {
2424 hasCodeTool : boolean ;
2525 hasComponentExample : boolean ;
2626 hasInstallation : boolean ;
27+ hasReactTypeTable : boolean ;
2728 hasRelatedLinks : boolean ;
2829 relatedLinkUrls : string [ ] ;
2930}
@@ -52,6 +53,50 @@ function getRelatedLinksFromOutput(output: unknown): Array<{ title: string; url:
5253 . filter ( ( link ) : link is { title : string ; url : string } => link !== null ) ;
5354}
5455
56+ function getReactTypeTableRowsFromOutput ( output : unknown ) : Array < {
57+ name : string ;
58+ type : string ;
59+ required : boolean ;
60+ description : string ;
61+ defaultValue : string | null ;
62+ } > {
63+ const safeOutput = getRecord ( output ) ;
64+ const rows = safeOutput . rows ;
65+ if ( ! Array . isArray ( rows ) ) return [ ] ;
66+
67+ return rows
68+ . map ( ( row ) => {
69+ const safeRow = getRecord ( row ) ;
70+
71+ if (
72+ typeof safeRow . name !== "string" ||
73+ typeof safeRow . type !== "string" ||
74+ typeof safeRow . required !== "boolean"
75+ ) {
76+ return null ;
77+ }
78+
79+ return {
80+ name : safeRow . name ,
81+ type : safeRow . type ,
82+ required : safeRow . required ,
83+ description : typeof safeRow . description === "string" ? safeRow . description : "" ,
84+ defaultValue : typeof safeRow . defaultValue === "string" ? safeRow . defaultValue : null ,
85+ } ;
86+ } )
87+ . filter (
88+ (
89+ row ,
90+ ) : row is {
91+ name : string ;
92+ type : string ;
93+ required : boolean ;
94+ description : string ;
95+ defaultValue : string | null ;
96+ } => row !== null ,
97+ ) ;
98+ }
99+
55100function getToolCopyText ( toolName : string , input : unknown , output : unknown ) : string [ ] {
56101 const lines : string [ ] = [ ] ;
57102 const safeInput = getRecord ( input ) ;
@@ -88,6 +133,21 @@ function getToolCopyText(toolName: string, input: unknown, output: unknown): str
88133 }
89134 }
90135
136+ if ( toolName === "showReactTypeTable" ) {
137+ const rows = getReactTypeTableRowsFromOutput ( output ) ;
138+ if ( rows . length > 0 ) {
139+ lines . push ( "## Props" ) ;
140+ lines . push (
141+ ...rows . map ( ( row ) => {
142+ const requiredText = row . required ? " (required)" : "" ;
143+ const defaultText = row . defaultValue ? ` (default: ${ row . defaultValue } )` : "" ;
144+ const descriptionText = row . description ? ` - ${ row . description } ` : "" ;
145+ return `- ${ row . name } ${ requiredText } : ${ row . type } ${ defaultText } ${ descriptionText } ` ;
146+ } ) ,
147+ ) ;
148+ }
149+ }
150+
91151 if ( toolName === "findRelatedLinks" ) {
92152 const links = getRelatedLinksFromOutput ( output ) ;
93153 if ( links . length > 0 ) {
@@ -134,6 +194,7 @@ function getToolRenderContext(message: UIMessage): ToolRenderContext {
134194 hasCodeTool : false ,
135195 hasComponentExample : false ,
136196 hasInstallation : false ,
197+ hasReactTypeTable : false ,
137198 hasRelatedLinks : false ,
138199 relatedLinkUrls : [ ] ,
139200 } ;
@@ -143,6 +204,7 @@ function getToolRenderContext(message: UIMessage): ToolRenderContext {
143204 if ( part . toolName === "showCodeBlock" ) context . hasCodeTool = true ;
144205 if ( part . toolName === "showComponentExample" ) context . hasComponentExample = true ;
145206 if ( part . toolName === "showInstallation" ) context . hasInstallation = true ;
207+ if ( part . toolName === "showReactTypeTable" ) context . hasReactTypeTable = true ;
146208 if ( part . toolName === "findRelatedLinks" ) {
147209 context . hasRelatedLinks = true ;
148210 context . relatedLinkUrls . push (
@@ -161,6 +223,7 @@ function getToolRenderContext(message: UIMessage): ToolRenderContext {
161223 if ( toolName === "showCodeBlock" ) context . hasCodeTool = true ;
162224 if ( toolName === "showComponentExample" ) context . hasComponentExample = true ;
163225 if ( toolName === "showInstallation" ) context . hasInstallation = true ;
226+ if ( toolName === "showReactTypeTable" ) context . hasReactTypeTable = true ;
164227 if ( toolName === "findRelatedLinks" ) {
165228 context . hasRelatedLinks = true ;
166229 context . relatedLinkUrls . push (
@@ -174,63 +237,149 @@ function getToolRenderContext(message: UIMessage): ToolRenderContext {
174237 return context ;
175238}
176239
177- function isRedundantTextForTools ( text : string , toolContext : ToolRenderContext ) : boolean {
178- const trimmed = text . trim ( ) ;
179- if ( ! trimmed ) return true ;
240+ function isCoveredRelatedUrl ( urlInText : string , relatedLinkUrls : string [ ] ) : boolean {
241+ return relatedLinkUrls . some (
242+ ( toolUrl ) => toolUrl . includes ( urlInText ) || urlInText . includes ( toolUrl ) ,
243+ ) ;
244+ }
245+
246+ function sanitizeTextForTools ( text : string , toolContext : ToolRenderContext ) : string {
247+ let sanitized = text ;
248+
249+ if ( ! sanitized . trim ( ) ) {
250+ return "" ;
251+ }
180252
181253 if (
182- ( toolContext . hasCodeTool || toolContext . hasComponentExample || toolContext . hasInstallation ) &&
183- / ` ` ` / . test ( trimmed )
254+ ( toolContext . hasCodeTool ||
255+ toolContext . hasComponentExample ||
256+ toolContext . hasInstallation ||
257+ toolContext . hasReactTypeTable ) &&
258+ / ` ` ` / . test ( sanitized )
184259 ) {
185- return true ;
260+ return "" ;
186261 }
187262
188263 if ( toolContext . hasInstallation ) {
189- if ( / @ s e e d - d e s i g n \/ c l i @ l a t e s t a d d / . test ( trimmed ) ) {
190- return true ;
264+ sanitized = sanitized
265+ . replace ( / ^ # { 1 , 6 } \s * ( i n s t a l l a t i o n | i n s t a l l | 설 치 ) \s * $ / gim, "" )
266+ . replace ( / ^ .* @ s e e d - d e s i g n \/ c l i @ l a t e s t a d d .* $ / gim, "" )
267+ . replace ( / ^ .* ( r u n t h i s c o m m a n d | t o i n s t a l l | 설 치 .* 명 령 어 | 설 치 방 법 ) .* $ / gim, "" ) ;
268+ }
269+
270+ if ( toolContext . hasComponentExample ) {
271+ sanitized = sanitized
272+ . replace ( / ^ # { 1 , 6 } \s * ( p r e v i e w | 미 리 보 기 | e x a m p l e | 사 용 예 시 ) \s * $ / gim, "" )
273+ . replace (
274+ / ^ .* ( h e r e i s a p r e v i e w | p r e v i e w o f t h e | c o m p o n e n t p r e v i e w | 컴 포 넌 트 미 리 보 기 | 사 용 예 제 | 아 래 는 .* 예 제 ) .* $ / gim,
275+ "" ,
276+ ) ;
277+ }
278+
279+ if ( toolContext . hasReactTypeTable ) {
280+ const propsListPatterns = [
281+ / ^ # { 1 , 6 } \s * p r o p s \s * $ / gim,
282+ / ^ .* ( 주 요 \s * p r o p s | p r o p s 는 다 음 과 같 습 니 다 | p r o p t a b l e | p r o p s t a b l e | 타 입 테 이 블 | 프 로 퍼 티 목 록 ) .* $ / gim,
283+ / ^ \s * [ - * ] \s * \* \* [ ^ * ] + \* \* .* $ / gim,
284+ ] ;
285+
286+ for ( const pattern of propsListPatterns ) {
287+ sanitized = sanitized . replace ( pattern , "" ) ;
191288 }
192- if (
193- trimmed . length < 220 &&
194- / ( r u n t h i s c o m m a n d | t o i n s t a l l | 설 치 .* 명 령 어 | 설 치 방 법 ) / i. test ( trimmed )
195- ) {
196- return true ;
289+
290+ if ( / \b ( p r o p s ? | 프 로 퍼 티 | 속 성 ) \b / i. test ( sanitized ) && / \| \s * u n d e f i n e d / . test ( sanitized ) ) {
291+ return "" ;
197292 }
198293 }
199294
200- if ( toolContext . hasComponentExample ) {
201- if (
202- trimmed . length < 220 &&
203- / ( h e r e i s a p r e v i e w | p r e v i e w o f t h e | c o m p o n e n t p r e v i e w | 컴 포 넌 트 미 리 보 기 | 사 용 예 제 | 아 래 는 .* 예 제 ) / i. test (
204- trimmed ,
205- )
206- ) {
207- return true ;
295+ if ( toolContext . hasRelatedLinks ) {
296+ sanitized = sanitized
297+ . replace ( / ^ # { 1 , 6 } \s * ( r e l a t e d l i n k s ? | 관 련 문 서 링 크 | 관 련 된 링 크 ) \s * $ / gim, "" )
298+ . replace ( / ^ .* ( f o r m o r e d e t a i l e d i n f o r m a t i o n | 자 세 한 정 보 ) .* $ / gim, "" ) ;
299+
300+ const sanitizedLines = sanitized
301+ . split ( "\n" )
302+ . filter ( ( line ) => {
303+ const trimmedLine = line . trim ( ) ;
304+ if ( ! trimmedLine ) return true ;
305+
306+ const markdownLinkMatch = trimmedLine . match ( / \[ .* ?\] \( ( h t t p s ? : \/ \/ [ ^ ) ] + ) \) / ) ;
307+ if ( markdownLinkMatch ) {
308+ return ! isCoveredRelatedUrl ( markdownLinkMatch [ 1 ] , toolContext . relatedLinkUrls ) ;
309+ }
310+
311+ const urlMatches = trimmedLine . match ( / h t t p s ? : \/ \/ [ ^ \s ) \] ] + / g) ?? [ ] ;
312+ if ( urlMatches . length === 0 ) return true ;
313+
314+ return ! urlMatches . every ( ( urlInText ) =>
315+ isCoveredRelatedUrl ( urlInText , toolContext . relatedLinkUrls ) ,
316+ ) ;
317+ } )
318+ . join ( "\n" ) ;
319+
320+ if ( sanitizedLines . trim ( ) . length === 0 ) {
321+ return "" ;
208322 }
323+
324+ sanitized = sanitizedLines ;
325+ }
326+
327+ sanitized = sanitized
328+ . split ( "\n" )
329+ . map ( ( line ) => line . replace ( / \s + $ / g, "" ) )
330+ . join ( "\n" )
331+ . replace ( / \n { 3 , } / g, "\n\n" )
332+ . trim ( ) ;
333+
334+ if ( ! sanitized ) {
335+ return "" ;
336+ }
337+
338+ if (
339+ toolContext . hasInstallation &&
340+ sanitized . length < 220 &&
341+ / ( r u n t h i s c o m m a n d | t o i n s t a l l | 설 치 .* 명 령 어 | 설 치 방 법 ) / i. test ( sanitized )
342+ ) {
343+ return "" ;
344+ }
345+
346+ if (
347+ toolContext . hasComponentExample &&
348+ sanitized . length < 220 &&
349+ / ( h e r e i s a p r e v i e w | p r e v i e w o f t h e | c o m p o n e n t p r e v i e w | 컴 포 넌 트 미 리 보 기 | 사 용 예 제 | 아 래 는 .* 예 제 ) / i. test (
350+ sanitized ,
351+ )
352+ ) {
353+ return "" ;
354+ }
355+
356+ if (
357+ toolContext . hasRelatedLinks &&
358+ / ( r e l a t e d l i n k s ? | 관 련 문 서 링 크 | 관 련 된 링 크 | f o r m o r e d e t a i l e d i n f o r m a t i o n | 자 세 한 정 보 ) / i. test (
359+ sanitized ,
360+ )
361+ ) {
362+ return "" ;
363+ }
364+
365+ if (
366+ toolContext . hasReactTypeTable &&
367+ / ( 주 요 \s * p r o p s | p r o p s 는 다 음 과 같 습 니 다 | p r o p t a b l e | p r o p s t a b l e | 타 입 테 이 블 ) / i. test ( sanitized )
368+ ) {
369+ return "" ;
209370 }
210371
211372 if ( toolContext . hasRelatedLinks ) {
373+ const urlMatches = sanitized . match ( / h t t p s ? : \/ \/ [ ^ \s ) \] ] + / g) ?? [ ] ;
212374 if (
213- / ( r e l a t e d l i n k s ? | 관 련 문 서 링 크 | 관 련 된 링 크 | f o r m o r e d e t a i l e d i n f o r m a t i o n | 자 세 한 정 보 ) / i. test (
214- trimmed ,
215- )
375+ urlMatches . length > 0 &&
376+ urlMatches . every ( ( urlInText ) => isCoveredRelatedUrl ( urlInText , toolContext . relatedLinkUrls ) )
216377 ) {
217- return true ;
218- }
219-
220- const urlMatches = trimmed . match ( / h t t p s ? : \/ \/ [ ^ \s ) \] ] + / g) ?? [ ] ;
221- if ( urlMatches . length > 0 ) {
222- const allCoveredByTool = urlMatches . every ( ( urlInText ) =>
223- toolContext . relatedLinkUrls . some (
224- ( toolUrl ) => toolUrl . includes ( urlInText ) || urlInText . includes ( toolUrl ) ,
225- ) ,
226- ) ;
227- if ( allCoveredByTool ) {
228- return true ;
229- }
378+ return "" ;
230379 }
231380 }
232381
233- return false ;
382+ return sanitized ;
234383}
235384
236385export function ChatMessage ( { message } : { message : UIMessage } ) {
@@ -241,6 +390,7 @@ export function ChatMessage({ message }: { message: UIMessage }) {
241390 hasCodeTool : false ,
242391 hasComponentExample : false ,
243392 hasInstallation : false ,
393+ hasReactTypeTable : false ,
244394 hasRelatedLinks : false ,
245395 relatedLinkUrls : [ ] ,
246396 }
@@ -272,7 +422,7 @@ export function ChatMessage({ message }: { message: UIMessage }) {
272422
273423 return (
274424 < div className = { `flex ${ isUser ? "justify-end" : "justify-start" } ` } >
275- < div className = "max-w-[90%]" >
425+ < div className = { isUser ? "max-w-[90%]" : "min-w-[85%] max-w-[90%]" } >
276426 < div
277427 className = { `${
278428 isUser
@@ -293,7 +443,8 @@ export function ChatMessage({ message }: { message: UIMessage }) {
293443 ! isUser &&
294444 ( toolContext . hasCodeTool ||
295445 toolContext . hasComponentExample ||
296- toolContext . hasInstallation )
446+ toolContext . hasInstallation ||
447+ toolContext . hasReactTypeTable )
297448 ) {
298449 return null ;
299450 }
@@ -309,7 +460,11 @@ export function ChatMessage({ message }: { message: UIMessage }) {
309460 return null ;
310461 }
311462
312- if ( ! isUser && isRedundantTextForTools ( segment . text , toolContext ) ) {
463+ const visibleText = ! isUser
464+ ? sanitizeTextForTools ( segment . text , toolContext )
465+ : segment . text ;
466+
467+ if ( ! visibleText ) {
313468 return null ;
314469 }
315470
@@ -322,7 +477,7 @@ export function ChatMessage({ message }: { message: UIMessage }) {
322477 : "prose prose-sm dark:prose-invert max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0"
323478 } `}
324479 >
325- { segment . text }
480+ { visibleText }
326481 </ div >
327482 ) ;
328483 } ) }
@@ -388,7 +543,7 @@ export function ChatMessage({ message }: { message: UIMessage }) {
388543 initial = { { opacity : 0 } }
389544 animate = { { opacity : 1 } }
390545 exit = { { opacity : 0 } }
391- transition = { { duration : 0.15 } }
546+ transition = { { duration : 0.1 } }
392547 className = "inline-flex"
393548 >
394549 < Icon svg = { < IconCheckmarkCircleLine /> } />
@@ -399,7 +554,7 @@ export function ChatMessage({ message }: { message: UIMessage }) {
399554 initial = { { opacity : 0 } }
400555 animate = { { opacity : 1 } }
401556 exit = { { opacity : 0 } }
402- transition = { { duration : 0.15 } }
557+ transition = { { duration : 0.1 } }
403558 className = "inline-flex"
404559 >
405560 < Icon svg = { < IconSquare2StackedLine /> } />
0 commit comments