mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
116 lines
3.7 KiB
TypeScript
116 lines
3.7 KiB
TypeScript
'use client';
|
||
|
||
import { useEffect, useState } from 'react';
|
||
import { type BuildAppResult, getRecentApps } from '~/lib/persistence/apps';
|
||
import styles from './ExampleLibraryApps.module.scss';
|
||
import { importChat } from '~/lib/persistence/useChatHistory';
|
||
|
||
const formatDate = (date: Date) => {
|
||
return new Intl.DateTimeFormat('en-US', {
|
||
month: 'short',
|
||
day: 'numeric',
|
||
year: 'numeric',
|
||
hour: 'numeric',
|
||
minute: 'numeric',
|
||
}).format(date);
|
||
};
|
||
|
||
export const ExampleLibraryApps = () => {
|
||
const [numApps, setNumApps] = useState<number>(6);
|
||
const [apps, setApps] = useState<BuildAppResult[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
useEffect(() => {
|
||
async function fetchRecentApps() {
|
||
try {
|
||
setLoading(true);
|
||
const recentApps = await getRecentApps(numApps);
|
||
setApps(recentApps);
|
||
setError(null);
|
||
} catch (err) {
|
||
console.error('Failed to fetch recent apps:', err);
|
||
setError('Failed to load recent apps');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
|
||
if (apps.length < numApps) {
|
||
fetchRecentApps();
|
||
}
|
||
}, [numApps]);
|
||
|
||
if (error) {
|
||
return <div className={styles.error}>{error}</div>;
|
||
}
|
||
|
||
if (apps.length === 0) {
|
||
if (loading) {
|
||
return <div className={styles.loading}>Loading recent apps...</div>;
|
||
}
|
||
return <div className={styles.empty}>No recent apps found</div>;
|
||
}
|
||
|
||
const displayApps = apps.slice(0, numApps);
|
||
|
||
return (
|
||
<div className={styles.container}>
|
||
<div className={styles.grid}>
|
||
{displayApps.map((app) => (
|
||
<div
|
||
key={app.appId}
|
||
className={`${styles.appItem} ${!app.outcome.testsPassed ? styles.appItemError : ''}`}
|
||
onClick={() => {
|
||
importChat(
|
||
app.title ?? 'Untitled App',
|
||
app.messages.filter((msg) => {
|
||
// Workaround an issue where the messages in the database include images
|
||
// (used to generate the screenshots).
|
||
if (msg.role == 'assistant' && msg.type == 'image') {
|
||
return false;
|
||
}
|
||
return true;
|
||
}),
|
||
);
|
||
}}
|
||
>
|
||
{app.imageDataURL ? (
|
||
<img src={app.imageDataURL} alt={app.title || 'App preview'} className={styles.previewImage} />
|
||
) : (
|
||
<div className={styles.placeholderImage}>{app.title || 'No preview'}</div>
|
||
)}
|
||
<div className={styles.appTitle}>{app.title || 'Untitled App'}</div>
|
||
<div className={styles.hoverOverlay}>
|
||
<div className={styles.hoverContent}>
|
||
<div className={styles.hoverInfo}>
|
||
<div>
|
||
Created at {formatDate(new Date(app.createdAt))} in {Math.round(app.elapsedMinutes)} minutes
|
||
</div>
|
||
<div>
|
||
{app.totalPeanuts} peanuts{app.outcome.hasDatabase ? ' (has database)' : ''}
|
||
</div>
|
||
{!app.outcome.testsPassed && <div className={styles.warningText}>⚠️ Not all tests are passing</div>}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
{loading && <div className={styles.loading}>Loading recent apps...</div>}
|
||
{!loading && (
|
||
<div className={styles.buttonContainer}>
|
||
<button
|
||
className={styles.loadMoreButton}
|
||
onClick={() => {
|
||
setNumApps((prev) => prev + 12);
|
||
}}
|
||
>
|
||
Load More
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|