[dyad] Improved schema display - wrote 2 file(s)
This commit is contained in:
146
src/components/pretty-schema-display.tsx
Normal file
146
src/components/pretty-schema-display.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Link as LinkIcon,
|
||||
Type,
|
||||
Image as ImageIcon,
|
||||
User,
|
||||
Building,
|
||||
Calendar,
|
||||
HelpCircle,
|
||||
Hash,
|
||||
Text,
|
||||
} from "lucide-react";
|
||||
|
||||
const keyMappings: { [key: string]: string } = {
|
||||
"@type": "Type",
|
||||
"@context": "Context",
|
||||
"@id": "ID",
|
||||
name: "Name",
|
||||
headline: "Headline",
|
||||
description: "Description",
|
||||
author: "Author",
|
||||
publisher: "Publisher",
|
||||
mainEntityOfPage: "Main Page",
|
||||
image: "Image",
|
||||
datePublished: "Date Published",
|
||||
dateModified: "Date Modified",
|
||||
acceptedAnswer: "Answer",
|
||||
mainEntity: "Main Content",
|
||||
url: "URL",
|
||||
text: "Text",
|
||||
question: "Question",
|
||||
answer: "Answer",
|
||||
logo: "Logo",
|
||||
telephone: "Telephone",
|
||||
email: "Email",
|
||||
};
|
||||
|
||||
const keyIcons: { [key: string]: React.ElementType } = {
|
||||
"@type": Type,
|
||||
"@id": Hash,
|
||||
url: LinkIcon,
|
||||
image: ImageIcon,
|
||||
logo: ImageIcon,
|
||||
author: User,
|
||||
publisher: Building,
|
||||
datePublished: Calendar,
|
||||
dateModified: Calendar,
|
||||
question: HelpCircle,
|
||||
text: Text,
|
||||
};
|
||||
|
||||
const renderValue = (value: any): React.ReactNode => {
|
||||
if (typeof value === "string") {
|
||||
if (value.startsWith("http://") || value.startsWith("https://")) {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
return (
|
||||
<a
|
||||
href={value}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 hover:underline break-all inline-flex items-center gap-1.5"
|
||||
>
|
||||
<LinkIcon className="h-3 w-3 flex-shrink-0" />
|
||||
<span>
|
||||
{url.hostname}
|
||||
{url.pathname.length > 1 ? "/..." : ""}
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
} catch (e) {
|
||||
return <span className="break-all">{value}</span>;
|
||||
}
|
||||
}
|
||||
return <span className="break-words">{value}</span>;
|
||||
}
|
||||
|
||||
if (typeof value === "object" && value !== null) {
|
||||
if (Array.isArray(value)) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2 pl-4 border-l ml-2 mt-2">
|
||||
{value.map((item, index) => (
|
||||
<div key={index}>{renderValue(item)}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <SchemaObjectRenderer data={value} isNested={true} />;
|
||||
}
|
||||
|
||||
return <span>{String(value)}</span>;
|
||||
};
|
||||
|
||||
const SchemaObjectRenderer = ({
|
||||
data,
|
||||
isNested = false,
|
||||
}: {
|
||||
data: any;
|
||||
isNested?: boolean;
|
||||
}) => {
|
||||
const content = (
|
||||
<div className="space-y-3">
|
||||
{Object.entries(data).map(([key, value]) => {
|
||||
if (key === "@context") return null;
|
||||
const label =
|
||||
keyMappings[key] || key.charAt(0).toUpperCase() + key.slice(1);
|
||||
const Icon = keyIcons[key];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className="flex flex-col sm:flex-row sm:items-start gap-1 sm:gap-2"
|
||||
>
|
||||
<div className="flex items-center gap-2 font-semibold text-sm text-muted-foreground w-full sm:w-40 flex-shrink-0">
|
||||
{Icon && <Icon className="h-4 w-4" />}
|
||||
<span>{label}:</span>
|
||||
</div>
|
||||
<div className="flex-grow text-sm text-foreground pl-6 sm:pl-0">
|
||||
{key === "@type" ? (
|
||||
<Badge variant="outline">{value as string}</Badge>
|
||||
) : (
|
||||
renderValue(value)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (isNested) {
|
||||
return (
|
||||
<div className="p-3 border rounded-md bg-background/50">{content}</div>
|
||||
);
|
||||
}
|
||||
|
||||
return content;
|
||||
};
|
||||
|
||||
export function PrettySchemaDisplay({ schema }: { schema: any }) {
|
||||
return <SchemaObjectRenderer data={schema} />;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { CopyButton } from "./copy-button";
|
||||
import { PrettySchemaDisplay } from "./pretty-schema-display";
|
||||
|
||||
interface SchemaDisplayProps {
|
||||
schemas: any[];
|
||||
@@ -14,27 +15,43 @@ interface SchemaDisplayProps {
|
||||
|
||||
export function SchemaDisplay({ schemas }: SchemaDisplayProps) {
|
||||
return (
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
{schemas.map((schema, index) => {
|
||||
const schemaType = schema["@type"] || `Schema Block ${index + 1}`;
|
||||
const schemaJson = JSON.stringify(schema, null, 2);
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center p-4 bg-muted/50 rounded-lg">
|
||||
<p className="text-sm font-medium">
|
||||
Found {schemas.length} schema block{schemas.length > 1 ? "s" : ""} on
|
||||
this page.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Expand to see details.</p>
|
||||
</div>
|
||||
<Accordion type="single" collapsible className="w-full space-y-4">
|
||||
{schemas.map((schema, index) => {
|
||||
const schemaType = schema["@type"] || `Schema Block ${index + 1}`;
|
||||
const schemaJson = JSON.stringify(schema, null, 2);
|
||||
|
||||
return (
|
||||
<AccordionItem value={`item-${index}`} key={index}>
|
||||
<AccordionTrigger>{schemaType}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="relative bg-muted/50 p-4 rounded-md">
|
||||
<div className="absolute top-2 right-2">
|
||||
<CopyButton textToCopy={schemaJson} />
|
||||
return (
|
||||
<AccordionItem
|
||||
value={`item-${index}`}
|
||||
key={index}
|
||||
className="border rounded-lg bg-background shadow-sm"
|
||||
>
|
||||
<AccordionTrigger className="px-6 hover:no-underline">
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<span className="font-semibold">{schemaType}</span>
|
||||
<div className="flex items-center gap-2 mr-2">
|
||||
<span className="text-xs text-muted-foreground hidden sm:inline">
|
||||
Copy Raw JSON
|
||||
</span>
|
||||
<CopyButton textToCopy={schemaJson} />
|
||||
</div>
|
||||
</div>
|
||||
<pre className="text-sm overflow-x-auto">
|
||||
<code>{schemaJson}</code>
|
||||
</pre>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
);
|
||||
})}
|
||||
</Accordion>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="p-6 pt-0">
|
||||
<PrettySchemaDisplay schema={schema} />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
);
|
||||
})}
|
||||
</Accordion>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user