|
1 | | -import React from 'react'; |
| 1 | +import React, { useRef, useState, useCallback } from 'react'; |
2 | 2 | import { motion } from 'framer-motion'; |
3 | | -import { FiTerminal, FiClock, FiPlay, FiCheckCircle, FiXCircle } from 'react-icons/fi'; |
4 | | -import { JsonRenderer } from '@/common/components/JsonRenderer'; |
| 3 | +import { FiTerminal, FiClock, FiPlay, FiCheckCircle, FiXCircle, FiCopy, FiCheck } from 'react-icons/fi'; |
| 4 | +import { JsonRenderer, JsonRendererRef } from '@/common/components/JsonRenderer'; |
5 | 5 | import { RawToolMapping } from '@/common/state/atoms/rawEvents'; |
6 | 6 | import { formatTimestamp } from '@/common/utils/formatters'; |
7 | 7 |
|
8 | 8 | interface RawModeRendererProps { |
9 | 9 | toolMapping: RawToolMapping; |
10 | 10 | } |
11 | 11 |
|
| 12 | +// Copy button component |
| 13 | +const CopyButton: React.FC<{ |
| 14 | + jsonRef: React.RefObject<JsonRendererRef>; |
| 15 | + title: string; |
| 16 | +}> = ({ jsonRef, title }) => { |
| 17 | + const [copied, setCopied] = useState(false); |
| 18 | + |
| 19 | + const handleCopy = useCallback(async () => { |
| 20 | + try { |
| 21 | + const jsonString = jsonRef.current?.copyAll(); |
| 22 | + if (jsonString) { |
| 23 | + await navigator.clipboard.writeText(jsonString); |
| 24 | + setCopied(true); |
| 25 | + setTimeout(() => setCopied(false), 1500); |
| 26 | + } |
| 27 | + } catch (error) { |
| 28 | + console.error('Failed to copy JSON:', error); |
| 29 | + } |
| 30 | + }, [jsonRef]); |
| 31 | + |
| 32 | + return ( |
| 33 | + <motion.button |
| 34 | + whileHover={{ scale: 1.05 }} |
| 35 | + whileTap={{ scale: 0.95 }} |
| 36 | + onClick={handleCopy} |
| 37 | + className="p-1.5 rounded-md hover:bg-slate-200 dark:hover:bg-slate-700 transition-all opacity-0 group-hover:opacity-100" |
| 38 | + title={title} |
| 39 | + > |
| 40 | + {copied ? ( |
| 41 | + <FiCheck size={12} className="text-green-500" /> |
| 42 | + ) : ( |
| 43 | + <FiCopy size={12} className="text-slate-400" /> |
| 44 | + )} |
| 45 | + </motion.button> |
| 46 | + ); |
| 47 | +}; |
| 48 | + |
12 | 49 | export const RawModeRenderer: React.FC<RawModeRendererProps> = ({ toolMapping }) => { |
13 | 50 | const { toolCall, toolResult } = toolMapping; |
| 51 | + |
| 52 | + // Refs for JsonRenderer components |
| 53 | + const parametersRef = useRef<JsonRendererRef>(null); |
| 54 | + const responseRef = useRef<JsonRendererRef>(null); |
| 55 | + const metadataRef = useRef<JsonRendererRef>(null); |
14 | 56 |
|
15 | 57 | return ( |
16 | 58 | <div className="space-y-3 mt-3"> |
@@ -50,12 +92,13 @@ export const RawModeRenderer: React.FC<RawModeRendererProps> = ({ toolMapping }) |
50 | 92 | </div> |
51 | 93 | </div> |
52 | 94 | {toolCall.arguments && ( |
53 | | - <div> |
54 | | - <div className="text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2"> |
55 | | - Parameters |
| 95 | + <div className="group"> |
| 96 | + <div className="text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2 flex items-center justify-between"> |
| 97 | + <span>Parameters</span> |
| 98 | + <CopyButton jsonRef={parametersRef} title="Copy parameters JSON" /> |
56 | 99 | </div> |
57 | 100 | <div className="bg-white/60 dark:bg-slate-800/60 rounded-lg p-3 border border-slate-200/40 dark:border-slate-700/40"> |
58 | | - <JsonRenderer data={toolCall.arguments} emptyMessage="No parameters provided" /> |
| 101 | + <JsonRenderer ref={parametersRef} data={toolCall.arguments} emptyMessage="No parameters provided" /> |
59 | 102 | </div> |
60 | 103 | </div> |
61 | 104 | )} |
@@ -134,21 +177,23 @@ export const RawModeRenderer: React.FC<RawModeRendererProps> = ({ toolMapping }) |
134 | 177 | </div> |
135 | 178 | </div> |
136 | 179 | )} |
137 | | - <div> |
138 | | - <div className="text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2"> |
139 | | - Response |
| 180 | + <div className="group"> |
| 181 | + <div className="text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2 flex items-center justify-between"> |
| 182 | + <span>Response</span> |
| 183 | + <CopyButton jsonRef={responseRef} title="Copy response JSON" /> |
140 | 184 | </div> |
141 | 185 | <div className="bg-white/60 dark:bg-slate-800/60 rounded-lg p-3 border border-slate-200/40 dark:border-slate-700/40"> |
142 | | - <JsonRenderer data={toolResult.content} emptyMessage="No response data" /> |
| 186 | + <JsonRenderer ref={responseRef} data={toolResult.content} emptyMessage="No response data" /> |
143 | 187 | </div> |
144 | 188 | </div> |
145 | 189 | {toolResult._extra && ( |
146 | | - <div> |
147 | | - <div className="text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2"> |
148 | | - Metadata |
| 190 | + <div className="group"> |
| 191 | + <div className="text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2 flex items-center justify-between"> |
| 192 | + <span>Metadata</span> |
| 193 | + <CopyButton jsonRef={metadataRef} title="Copy metadata JSON" /> |
149 | 194 | </div> |
150 | 195 | <div className="bg-white/60 dark:bg-slate-800/60 rounded-lg p-3 border border-slate-200/40 dark:border-slate-700/40"> |
151 | | - <JsonRenderer data={toolResult._extra} emptyMessage="No metadata" /> |
| 196 | + <JsonRenderer ref={metadataRef} data={toolResult._extra} emptyMessage="No metadata" /> |
152 | 197 | </div> |
153 | 198 | </div> |
154 | 199 | )} |
|
0 commit comments