| import React, { useState, useEffect } from "react"; | |
| import { | |
| Box, | |
| Grid, | |
| Card, | |
| CardContent, | |
| Typography, | |
| CircularProgress, | |
| Chip, | |
| Stack, | |
| } from "@mui/material"; | |
| import { | |
| Palette as PaletteIcon, | |
| Person as PersonIcon, | |
| Category as CategoryIcon, | |
| AccessTime as AccessTimeIcon, | |
| } from "@mui/icons-material"; | |
| import { storyApi, universeApi } from "../utils/api"; | |
| const UniverseCard = ({ universe, imagePrompt }) => { | |
| const [imageUrl, setImageUrl] = useState(null); | |
| const [isLoading, setIsLoading] = useState(true); | |
| useEffect(() => { | |
| const generateImage = async () => { | |
| try { | |
| const result = await storyApi.generateImage(imagePrompt, 512, 512); | |
| if (result && result.success) { | |
| setImageUrl(`data:image/png;base64,${result.image_base64}`); | |
| } | |
| } catch (error) { | |
| console.error("Error generating image:", error); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| generateImage(); | |
| }, [imagePrompt]); | |
| return ( | |
| <Card sx={{ height: "100%", display: "flex", flexDirection: "column" }}> | |
| <Box sx={{ position: "relative", paddingTop: "100%" }}> | |
| {isLoading ? ( | |
| <Box | |
| sx={{ | |
| position: "absolute", | |
| top: 0, | |
| left: 0, | |
| right: 0, | |
| bottom: 0, | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "center", | |
| backgroundColor: "rgba(0, 0, 0, 0.1)", | |
| }} | |
| > | |
| <CircularProgress /> | |
| </Box> | |
| ) : ( | |
| <Box | |
| component="img" | |
| src={imageUrl} | |
| alt="Universe preview" | |
| sx={{ | |
| position: "absolute", | |
| top: 0, | |
| left: 0, | |
| width: "100%", | |
| height: "100%", | |
| objectFit: "cover", | |
| }} | |
| /> | |
| )} | |
| </Box> | |
| <CardContent sx={{ py: 1.5, px: 2, "&:last-child": { pb: 1.5 } }}> | |
| <Stack spacing={0.5}> | |
| <Stack direction="row" spacing={1} alignItems="center"> | |
| <PaletteIcon fontSize="small" color="primary" /> | |
| <Typography variant="subtitle2" sx={{ fontSize: "0.875rem" }}> | |
| {universe.style.name} | |
| </Typography> | |
| </Stack> | |
| {universe.style.selected_artist && ( | |
| <Stack direction="row" spacing={1} alignItems="center"> | |
| <PersonIcon fontSize="small" color="primary" /> | |
| <Typography variant="body2" sx={{ fontSize: "0.8rem" }}> | |
| {universe.style.selected_artist} | |
| </Typography> | |
| </Stack> | |
| )} | |
| <Stack direction="row" spacing={1} alignItems="center"> | |
| <CategoryIcon fontSize="small" color="primary" /> | |
| <Typography variant="body2" sx={{ fontSize: "0.8rem" }}> | |
| {universe.genre} | |
| </Typography> | |
| </Stack> | |
| <Stack direction="row" spacing={1} alignItems="center"> | |
| <AccessTimeIcon fontSize="small" color="primary" /> | |
| <Typography variant="body2" sx={{ fontSize: "0.8rem" }}> | |
| {universe.epoch} | |
| </Typography> | |
| </Stack> | |
| </Stack> | |
| </CardContent> | |
| </Card> | |
| ); | |
| }; | |
| export function Universe() { | |
| const [universes, setUniverses] = useState([]); | |
| const [isLoading, setIsLoading] = useState(true); | |
| useEffect(() => { | |
| const generateUniverses = async () => { | |
| try { | |
| const generatedUniverses = await Promise.all( | |
| Array(6) | |
| .fill() | |
| .map(async () => { | |
| const universe = await universeApi.generate(); | |
| return { | |
| ...universe, | |
| imagePrompt: `${ | |
| universe.style.selected_artist || | |
| universe.style.references[0].artist | |
| } style, epic wide shot of a detailed scene -- A dramatic establishing shot of a ${universe.genre.toLowerCase()} world in ${ | |
| universe.epoch | |
| }, with rich atmosphere and dynamic composition. The scene should reflect the essence of ${ | |
| universe.style.name | |
| } visual style, with appropriate lighting and mood. In the scene, Sarah is a young woman in her late twenties with short dark hair, wearing a mysterious amulet around her neck. Her blue eyes hide untold secrets.`, | |
| }; | |
| }) | |
| ); | |
| setUniverses(generatedUniverses); | |
| } catch (error) { | |
| console.error("Error generating universes:", error); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| generateUniverses(); | |
| }, []); | |
| if (isLoading) { | |
| return ( | |
| <Box | |
| sx={{ | |
| display: "flex", | |
| alignItems: "center", | |
| justifyContent: "center", | |
| minHeight: "100vh", | |
| }} | |
| > | |
| <CircularProgress /> | |
| </Box> | |
| ); | |
| } | |
| return ( | |
| <Box | |
| sx={{ | |
| height: "100vh", | |
| display: "flex", | |
| flexDirection: "column", | |
| backgroundColor: "background.default", | |
| }} | |
| > | |
| <Box sx={{ p: 3, pb: 2 }}> | |
| <Typography variant="h4">Univers Parallèles</Typography> | |
| </Box> | |
| <Box | |
| sx={{ | |
| flex: 1, | |
| overflow: "auto", | |
| px: 3, | |
| pb: 3, | |
| }} | |
| > | |
| <Grid container spacing={3}> | |
| {universes.map((universe, index) => ( | |
| <Grid item xs={12} sm={6} md={4} key={index}> | |
| <UniverseCard | |
| universe={universe} | |
| imagePrompt={universe.imagePrompt} | |
| /> | |
| </Grid> | |
| ))} | |
| </Grid> | |
| </Box> | |
| </Box> | |
| ); | |
| } | |
| export default Universe; | |