@@ -66,6 +66,7 @@ export function MethodView({
6666 const [ running , setRunning ] = useState ( false ) ;
6767 const [ error , setError ] = useState ( "" ) ;
6868 const [ tab , setTab ] = useState < "example" | "output" > ( "example" ) ;
69+ const [ copied , setCopied ] = useState ( false ) ;
6970 const callAbortRef = useRef < ( ( reason : string ) => void ) | null > ( null ) ;
7071 const cancelRunRef = useRef < ( ( ) => void ) | null > ( null ) ;
7172
@@ -150,6 +151,20 @@ export function MethodView({
150151 callAbortRef . current ?.( "Call aborted" ) ;
151152 } ;
152153
154+ const handleCopyLogs = async ( ) => {
155+ const text = [ ...logs . map ( ( entry ) => entry . text ) , ...( error ? [ error ] : [ ] ) ]
156+ . join ( "\n" )
157+ . trim ( ) ;
158+ if ( ! text ) return ;
159+ try {
160+ await navigator . clipboard . writeText ( text ) ;
161+ setCopied ( true ) ;
162+ setTimeout ( ( ) => setCopied ( false ) , 1500 ) ;
163+ } catch {
164+ /* clipboard unavailable */
165+ }
166+ } ;
167+
153168 const kind = methodInfo ?. type ?? "unary" ;
154169
155170 const status : Status = error
@@ -305,41 +320,81 @@ export function MethodView({
305320 </ >
306321 ) : (
307322 < div className = "console console--inline" data-status = { status } >
308- { error ? (
309- < div
310- className = "console__body console__body--error"
311- data-testid = "error-display"
312- >
313- { error }
314- { isHostMissingError ( error ) && (
315- < div className = "console__cta" >
316- < a
317- className = "open-in-dotli"
318- href = { hostedPlaygroundUrl ( service , method ) }
319- target = "_blank"
320- rel = "noreferrer"
321- title = "Open this example in the host-backed playground"
323+ { logs . length > 0 || error ? (
324+ < >
325+ < button
326+ type = "button"
327+ className = "console__copy"
328+ data-copied = { copied }
329+ onClick = { handleCopyLogs }
330+ aria-label = "Copy output to clipboard"
331+ title = "Copy output"
332+ >
333+ { copied ? (
334+ < svg
335+ viewBox = "0 0 24 24"
336+ fill = "none"
337+ stroke = "currentColor"
338+ strokeWidth = "2"
339+ strokeLinecap = "round"
340+ strokeLinejoin = "round"
341+ aria-hidden
342+ >
343+ < path d = "M20 6 9 17l-5-5" />
344+ </ svg >
345+ ) : (
346+ < svg
347+ viewBox = "0 0 24 24"
348+ fill = "none"
349+ stroke = "currentColor"
350+ strokeWidth = "2"
351+ strokeLinecap = "round"
352+ strokeLinejoin = "round"
353+ aria-hidden
322354 >
323- Run in hosted playground ↗
324- </ a >
355+ < rect x = "8" y = "8" width = "14" height = "14" rx = "2" ry = "2" />
356+ < path d = "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
357+ </ svg >
358+ ) }
359+ </ button >
360+ { logs . length > 0 && (
361+ < div className = "console__body" data-testid = "stream-log" >
362+ { logs . map ( ( entry , i ) => (
363+ < div
364+ key = { i }
365+ className = { `console__entry console__entry--${ entry . level } ` }
366+ data-testid = "stream-entry"
367+ >
368+ < span className = "console__entry-i" >
369+ { String ( i + 1 ) . padStart ( 2 , "0" ) }
370+ </ span >
371+ < span className = "console__entry-body" > { entry . text } </ span >
372+ </ div >
373+ ) ) }
325374 </ div >
326375 ) }
327- </ div >
328- ) : logs . length > 0 ? (
329- < div className = "console__body" data-testid = "stream-log" >
330- { logs . map ( ( entry , i ) => (
376+ { error && (
331377 < div
332- key = { i }
333- className = { `console__entry console__entry--${ entry . level } ` }
334- data-testid = "stream-entry"
378+ className = "console__body console__body--error"
379+ data-testid = "error-display"
335380 >
336- < span className = "console__entry-i" >
337- { String ( i + 1 ) . padStart ( 2 , "0" ) }
338- </ span >
339- < span className = "console__entry-body" > { entry . text } </ span >
381+ { error }
382+ { isHostMissingError ( error ) && (
383+ < div className = "console__cta" >
384+ < a
385+ className = "open-in-dotli"
386+ href = { hostedPlaygroundUrl ( service , method ) }
387+ target = "_blank"
388+ rel = "noreferrer"
389+ title = "Open this example in the host-backed playground"
390+ >
391+ Run in hosted playground ↗
392+ </ a >
393+ </ div >
394+ ) }
340395 </ div >
341- ) ) }
342- </ div >
396+ ) }
397+ </ >
343398 ) : (
344399 < div className = "console__body console__body--empty" >
345400 { ! runnable
0 commit comments