@@ -11,6 +11,39 @@ interface TerminalRendererProps {
1111 displayMode ?: FileDisplayMode ;
1212}
1313
14+ /**
15+ * URL regex pattern to detect URLs in text
16+ */
17+ const URL_REGEX = / ( h t t p s ? : \/ \/ [ ^ \s < > " { } | \\ ^ ` \[ \] ] + ) / g;
18+
19+ /**
20+ * Convert text with URLs to JSX with clickable links
21+ */
22+ function linkifyText ( text : string ) : React . ReactNode {
23+ if ( ! text || typeof text !== 'string' ) {
24+ return text ;
25+ }
26+
27+ const parts = text . split ( URL_REGEX ) ;
28+ return parts . map ( ( part , index ) => {
29+ if ( URL_REGEX . test ( part ) ) {
30+ return (
31+ < a
32+ key = { index }
33+ href = { part }
34+ target = "_blank"
35+ rel = "noopener noreferrer"
36+ className = "text-blue-400 hover:text-blue-300 underline cursor-pointer"
37+ onClick = { ( e ) => e . stopPropagation ( ) }
38+ >
39+ { part }
40+ </ a >
41+ ) ;
42+ }
43+ return part ;
44+ } ) ;
45+ }
46+
1447/**
1548 * Format tool arguments as JSON string
1649 */
@@ -21,6 +54,44 @@ function formatArguments(args: Record<string, any>): string {
2154 return JSON . stringify ( args , null , 2 ) ;
2255}
2356
57+ /**
58+ * Custom CodeHighlight wrapper that makes URLs clickable in JSON
59+ */
60+ function CodeHighlightWithLinks ( { code, language } : { code : string ; language : string } ) {
61+ const [ processedCode , setProcessedCode ] = React . useState < string > ( code ) ;
62+
63+ React . useEffect ( ( ) => {
64+ if ( language === 'json' ) {
65+ // Replace URLs in JSON strings with clickable links
66+ const urlRegex = / " ( h t t p s ? : \/ \/ [ ^ " \s ] + ) " / g;
67+ const linkedCode = code . replace ( urlRegex , ( match , url ) => {
68+ return `"<span class='json-url-link text-blue-400 hover:text-blue-300 underline cursor-pointer' data-url='${ url } '>${ url } </span>"` ;
69+ } ) ;
70+ setProcessedCode ( linkedCode ) ;
71+ } else {
72+ setProcessedCode ( code ) ;
73+ }
74+ } , [ code , language ] ) ;
75+
76+ const handleClick = React . useCallback ( ( e : React . MouseEvent ) => {
77+ const target = e . target as HTMLElement ;
78+ if ( target . classList . contains ( 'json-url-link' ) ) {
79+ e . preventDefault ( ) ;
80+ e . stopPropagation ( ) ;
81+ const url = target . getAttribute ( 'data-url' ) ;
82+ if ( url ) {
83+ window . open ( url , '_blank' , 'noopener,noreferrer' ) ;
84+ }
85+ }
86+ } , [ ] ) ;
87+
88+ return (
89+ < div onClick = { handleClick } >
90+ < CodeHighlight code = { processedCode } language = { language } />
91+ </ div >
92+ ) ;
93+ }
94+
2495/**
2596 * Format tool output as JSON string when applicable
2697 */
@@ -139,14 +210,14 @@ export const TerminalRenderer: React.FC<TerminalRendererProps> = ({
139210 { /* Arguments section */ }
140211 { argumentsJson && (
141212 < div className = "p-3 pb-0 bg-[#121212] rounded m-3 mb-0 max-h-[40vh] overflow-auto" >
142- < CodeHighlight code = { argumentsJson } language = "json" />
213+ < CodeHighlightWithLinks code = { argumentsJson } language = "json" />
143214 </ div >
144215 ) }
145216
146217 { /* Output section */ }
147218 { output && (
148219 < div className = "p-3 bg-[#121212] rounded m-3 max-h-[80vh] overflow-auto" >
149- < CodeHighlight code = { output } language = "json" />
220+ < CodeHighlightWithLinks code = { output } language = "json" />
150221 </ div >
151222 ) }
152223 </ div >
0 commit comments