Spaces:
Running
Running
| import type React from "react"; | |
| import { useState } from "react"; | |
| type JsonValue = string | number | boolean | null | JsonObject | JsonArray; | |
| type JsonObject = { [key: string]: JsonValue }; | |
| type JsonArray = JsonValue[]; | |
| const isJsonString = (str: string): boolean => { | |
| if (typeof str !== "string") return false; | |
| const trimmed = str.trim(); | |
| // Check if it looks like JSON (starts with { or [) | |
| if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return false; | |
| try { | |
| const parsed = JSON.parse(trimmed); | |
| // Ensure it's actually an object or array, not just a primitive | |
| console.log("JSON parse successful:", parsed); | |
| return typeof parsed === "object" && parsed !== null; | |
| } catch (error) { | |
| console.log("JSON parse failed:", error); | |
| return false; | |
| } | |
| }; | |
| const JsonCollapsible: React.FC<{ data: JsonValue; isString?: boolean }> = ({ | |
| data, | |
| isString = false, | |
| }) => { | |
| const [isCollapsed, setIsCollapsed] = useState(true); | |
| let jsonData: JsonValue; | |
| try { | |
| jsonData = isString ? JSON.parse(data as string) : data; | |
| } catch { | |
| // If parsing fails, display as string | |
| return ( | |
| <pre className="text-sm text-gray-300 whitespace-pre-wrap overflow-auto"> | |
| {String(data)} | |
| </pre> | |
| ); | |
| } | |
| const isObject = typeof jsonData === "object" && jsonData !== null; | |
| if (!isObject) { | |
| return <span className="text-gray-300">{JSON.stringify(jsonData)}</span>; | |
| } | |
| const keys = Object.keys(jsonData as JsonObject); | |
| const preview = Array.isArray(jsonData) | |
| ? `[${jsonData.length} items]` | |
| : `{${keys.length} keys}`; | |
| return ( | |
| <div className="json-collapsible"> | |
| <button | |
| onClick={() => setIsCollapsed(!isCollapsed)} | |
| className="flex items-center gap-1 text-blue-400 hover:text-blue-300 transition-colors text-sm font-mono" | |
| > | |
| <span | |
| className="transform transition-transform duration-200" | |
| style={{ | |
| transform: isCollapsed ? "rotate(-90deg)" : "rotate(0deg)", | |
| }} | |
| > | |
| ▼ | |
| </span> | |
| {isCollapsed ? preview : Array.isArray(jsonData) ? "[" : "{"} | |
| </button> | |
| {!isCollapsed && ( | |
| <div className="ml-4 mt-1"> | |
| <pre className="text-sm text-gray-300 whitespace-pre-wrap overflow-auto"> | |
| {JSON.stringify(jsonData, null, 2)} | |
| </pre> | |
| <div className="text-blue-400 font-mono text-sm"> | |
| {Array.isArray(jsonData) ? "]" : "}"} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| const ResultBlock: React.FC<{ error?: string; result?: unknown }> = ({ | |
| error, | |
| result, | |
| }) => { | |
| console.log("ResultBlock component rendered with:", { | |
| error, | |
| result, | |
| type: typeof result, | |
| }); | |
| const renderContent = () => { | |
| console.log("ResultBlock Debug:", { | |
| result, | |
| type: typeof result, | |
| isString: typeof result === "string", | |
| isArray: Array.isArray(result), | |
| startsWithBracket: | |
| typeof result === "string" && result.trim().startsWith("["), | |
| isJsonString: typeof result === "string" ? isJsonString(result) : false, | |
| }); | |
| // Handle objects and arrays directly | |
| if (typeof result === "object" && result !== null) { | |
| console.log("Rendering as object/array with JsonCollapsible"); | |
| return <JsonCollapsible data={result as JsonValue} />; | |
| } | |
| // Handle string that might be JSON | |
| if (typeof result === "string") { | |
| const trimmed = result.trim(); | |
| if (isJsonString(trimmed)) { | |
| console.log("Rendering string as JSON with JsonCollapsible"); | |
| return <JsonCollapsible data={trimmed} isString={true} />; | |
| } | |
| } | |
| // Fallback to plain text display | |
| console.log("Rendering as plain text fallback"); | |
| return ( | |
| <pre className="text-sm text-gray-300 whitespace-pre-wrap overflow-auto"> | |
| {String(result)} | |
| </pre> | |
| ); | |
| }; | |
| return ( | |
| <div | |
| className={ | |
| error | |
| ? "bg-red-900 border border-red-600 rounded p-3" | |
| : "bg-gray-700 border border-gray-600 rounded p-3" | |
| } | |
| > | |
| {error ? <p className="text-red-300 text-sm">Error: {error}</p> : null} | |
| <div className="mt-2">{renderContent()}</div> | |
| </div> | |
| ); | |
| }; | |
| export default ResultBlock; | |