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; | |