Skip to content

Adding error screen BEN-1077 #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions app/api/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,16 @@ export async function POST(request: NextRequest) {
// }
// }

const { sbxId, template, url, allFiles } = await createSandbox({ files: repairedFiles });
const sandboxResult = await createSandbox({ files: repairedFiles });

// Return the results to the client
return NextResponse.json({
originalFiles: generatedFiles,
repairedFiles: allFiles, // Use the allFiles from the sandbox
buildOutput: `Sandbox created with template: ${template}, ID: ${sbxId}`,
previewUrl: url,
repairedFiles: sandboxResult.allFiles, // Use the allFiles from the sandbox
buildOutput: `Sandbox created with template: ${sandboxResult.template}, ID: ${sandboxResult.sbxId}`,
previewUrl: sandboxResult.url,
buildErrors: sandboxResult.buildErrors,
hasErrors: sandboxResult.hasErrors,
});
} catch (error) {
console.error('Error generating app:', error);
Expand Down
10 changes: 10 additions & 0 deletions app/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ type GenerationResult = {
originalFiles?: z.infer<typeof benchifyFileSchema>;
buildOutput: string;
previewUrl: string;
buildErrors?: Array<{
type: 'typescript' | 'build' | 'runtime';
message: string;
file?: string;
line?: number;
column?: number;
}>;
hasErrors?: boolean;
};

export default function ChatPage() {
Expand Down Expand Up @@ -103,6 +111,8 @@ export default function ChatPage() {
code={result?.repairedFiles || result?.originalFiles || []}
isGenerating={isGenerating}
prompt={initialPrompt}
buildErrors={result?.buildErrors}
hasErrors={result?.hasErrors}
/>
</div>
</div>
Expand Down
117 changes: 117 additions & 0 deletions components/ui-builder/error-display.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
'use client';

import { AlertCircle, Code, FileX, Terminal } from 'lucide-react';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { ScrollArea } from '@/components/ui/scroll-area';

interface BuildError {
type: 'typescript' | 'build' | 'runtime';
message: string;
file?: string;
line?: number;
column?: number;
}

interface ErrorDisplayProps {
errors: BuildError[];
}

export function ErrorDisplay({ errors }: ErrorDisplayProps) {
const getErrorIcon = (type: BuildError['type']) => {
switch (type) {
case 'typescript':
return <Code className="h-4 w-4" />;
case 'build':
return <Terminal className="h-4 w-4" />;
case 'runtime':
return <AlertCircle className="h-4 w-4" />;
default:
return <FileX className="h-4 w-4" />;
}
};

const getErrorColor = (type: BuildError['type']) => {
switch (type) {
case 'typescript':
return 'bg-blue-500/10 text-blue-700 dark:text-blue-400';
case 'build':
return 'bg-orange-500/10 text-orange-700 dark:text-orange-400';
case 'runtime':
return 'bg-red-500/10 text-red-700 dark:text-red-400';
default:
return 'bg-gray-500/10 text-gray-700 dark:text-gray-400';
}
};

const groupedErrors = errors.reduce((acc, error) => {
if (!acc[error.type]) {
acc[error.type] = [];
}
acc[error.type].push(error);
return acc;
}, {} as Record<string, BuildError[]>);

return (
<div className="w-full h-full flex items-center justify-center rounded-md border bg-background p-6">
<div className="w-full max-w-2xl">
<div className="text-center mb-6">
<AlertCircle className="h-12 w-12 text-destructive mx-auto mb-4" />
<h3 className="text-lg font-semibold mb-2">Build Errors Detected</h3>
<p className="text-muted-foreground text-sm">
Your project has some issues that need to be fixed before it can run properly.
</p>
</div>

<ScrollArea className="max-h-96">
<div className="space-y-4">
{Object.entries(groupedErrors).map(([type, typeErrors]) => (
<div key={type} className="space-y-2">
<div className="flex items-center gap-2 mb-3">
{getErrorIcon(type as BuildError['type'])}
<h4 className="font-medium capitalize">{type} Errors</h4>
<Badge variant="secondary" className="text-xs">
{typeErrors.length}
</Badge>
</div>

{typeErrors.map((error, index) => (
<Alert key={index} className="border-l-4 border-destructive">
<AlertTitle className="flex items-center gap-2 text-sm">
<Badge
variant="outline"
className={`text-xs ${getErrorColor(error.type)}`}
>
{error.type}
</Badge>
{error.file && (
<span className="text-muted-foreground">
{error.file}
{error.line && `:${error.line}`}
{error.column && `:${error.column}`}
</span>
)}
</AlertTitle>
<AlertDescription className="mt-2 text-sm font-mono bg-muted/50 p-2 rounded text-wrap break-words">
{error.message}
</AlertDescription>
</Alert>
))}
</div>
))}
</div>
</ScrollArea>

<div className="mt-6 p-4 bg-muted/50 rounded-lg">
<h4 className="text-sm font-medium mb-2">💡 Common Solutions:</h4>
<ul className="text-sm text-muted-foreground space-y-1">
<li>• Check for missing imports or typos in component names</li>
<li>• Verify that all props are properly typed</li>
<li>• Make sure all dependencies are correctly installed</li>
<li>• Try regenerating the component with more specific requirements</li>
</ul>
</div>
</div>
</div>
);
}
27 changes: 24 additions & 3 deletions components/ui-builder/preview-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { benchifyFileSchema } from "@/lib/schemas";
import { z } from "zod";
import { CodeEditor } from "./code-editor";
import { DownloadButton } from "./download-button";
import { ErrorDisplay } from "./error-display";

interface Step {
id: string;
Expand Down Expand Up @@ -36,14 +37,31 @@ const GENERATION_STEPS: Step[] = [
},
];

interface BuildError {
type: 'typescript' | 'build' | 'runtime';
message: string;
file?: string;
line?: number;
column?: number;
}

interface PreviewCardProps {
previewUrl?: string;
code: z.infer<typeof benchifyFileSchema>;
isGenerating?: boolean;
prompt?: string;
buildErrors?: BuildError[];
hasErrors?: boolean;
}

export function PreviewCard({ previewUrl, code, isGenerating = false, prompt }: PreviewCardProps) {
export function PreviewCard({
previewUrl,
code,
isGenerating = false,
prompt,
buildErrors = [],
hasErrors = false
}: PreviewCardProps) {
const files = code || [];
const [currentStep, setCurrentStep] = useState(0);

Expand Down Expand Up @@ -111,8 +129,8 @@ export function PreviewCard({ previewUrl, code, isGenerating = false, prompt }:
</div>
<div className="flex-1 min-w-0">
<p className={`text-sm font-medium ${isCompleted ? 'text-green-700 dark:text-green-400' :
isCurrent ? 'text-primary' :
'text-muted-foreground'
isCurrent ? 'text-primary' :
'text-muted-foreground'
}`}>
{step.label}
</p>
Expand All @@ -127,6 +145,9 @@ export function PreviewCard({ previewUrl, code, isGenerating = false, prompt }:
</CardContent>
</Card>
</div>
) : hasErrors && buildErrors.length > 0 ? (
// Show build errors if there are any
<ErrorDisplay errors={buildErrors} />
) : previewUrl ? (
// Show the actual preview iframe when ready
<div className="w-full h-full overflow-hidden rounded-md border bg-background">
Expand Down
46 changes: 46 additions & 0 deletions components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const badgeVariants = cva(
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary:
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)

function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "span"

return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}

export { Badge, badgeVariants }
Loading