0% found this document useful (0 votes)
7 views35 pages

04

The document is a React component that generates story scenes and images using AI models, specifically leveraging Gemini and Imagen APIs. It includes state management for user inputs, loading indicators, and error handling, while dynamically loading necessary libraries. The component allows users to input a story description, specify the number of scenes, and adjust the aspect ratio for generated content.

Uploaded by

mnp7646
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views35 pages

04

The document is a React component that generates story scenes and images using AI models, specifically leveraging Gemini and Imagen APIs. It includes state management for user inputs, loading indicators, and error handling, while dynamically loading necessary libraries. The component allows users to input a story description, specify the number of scenes, and adjust the aspect ratio for generated content.

Uploaded by

mnp7646
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

import React, { useState, useEffect } from 'react';

const App = () => {

const [storyDescription, setStoryDescription] = useState('');

const [numScenes, setNumScenes] = useState(3);

const [aspectRatio, setAspectRatio] = useState('16:9'); // Default aspect ratio

const [generatedContent, setGeneratedContent] = useState([]);

const [isLoading, setIsLoading] = useState(false); // ‫لحالة التحميل الكلية‬

const [errorMessage, setErrorMessage] = useState('');

// New state to track if external libraries are loaded

const [areLibsLoaded, setAreLibsLoaded] = useState(false);

// API keys provided by the user are kept here as requested.

// In a production environment, it's generally recommended to handle API keys more securely

// (e.g., through environment variables or a backend proxy) rather than hardcoding them in
client-side code).

// ‫ يجب تأمين مفاتيح الـ‬:‫ مالحظة هامة‬API ‫!هذه في بيئة إنتاجية‬

const geminiApiKey = "AIzaSyCZXqFKSKsP0rGS5HBwGX_bYYXYehCo0qI";

const imagenApiKey = "AIzaSyCZXqFKSKsP0rGS5HBwGX_bYYXYehCo0qI";

// Use useEffect to dynamically load JSZip and FileSaver

useEffect(() => {

const loadScript = (id, src, onloadCallback) => {

if ([Link](id)) {

onloadCallback(); // If already exists, call callback immediately

return;
}

const script = [Link]('script');

[Link] = src;

[Link] = id;

[Link] = onloadCallback;

[Link] = () => [Link](`Failed to load script: ${src}`);

[Link](script);

};

let jszipLoaded = false;

let fileSaverLoaded = false;

const checkAllLoaded = () => {

if (jszipLoaded && fileSaverLoaded) {

setAreLibsLoaded(true);

};

loadScript(

'jszip-cdn',

'[Link]

() => {

jszipLoaded = true;

checkAllLoaded();

);
loadScript(

'file-saver-cdn',

'[Link]

() => {

fileSaverLoaded = true;

checkAllLoaded();

);

}, []); // Run once on mount

/**

* Displays a temporary message to the user, typically for errors or confirmations.

* @param {string} message - The message to display.

*/

const showMessage = (message) => {

setErrorMessage(message);

setTimeout(() => setErrorMessage(''), 5000);

};

/**

* Parses the structured output from Gemini for a single scene.

* @param {string} outputBlock - The raw text block for a single scene from Gemini.

* @returns {object} An object containing parsed scene details.

*/

const parseGeminiSceneOutput = (outputBlock) => {


const extractBlock = (key) => {

// Adjusted regex to handle more general block endings or end of string

const match = [Link](new RegExp(`${key}:([\\s\\S]*?)(?=(SCENE_DESCRIPTION:|


MALLOW_CLOTHING:|MALLOW_ACCESSORIES:|MALLOW_FACE_STYLE:|
OPTIONAL_SECONDARY_CHARACTER_BLOCK:|ENVIRONMENT_BLOCK:|---|$))`, 'i'));

return match ? match[1].trim() : '';

};

return {

sceneDesc: extractBlock('SCENE_DESCRIPTION'),

mallowClothing: extractBlock('MALLOW_CLOTHING'),

mallowAccessories: extractBlock('MALLOW_ACCESSORIES'),

mallowFaceStyle: extractBlock('MALLOW_FACE_STYLE'),

optionalCharBlock: extractBlock('OPTIONAL_SECONDARY_CHARACTER_BLOCK'),

envBlock: extractBlock('ENVIRONMENT_BLOCK'),

};

};

/**

* Generates the story scenes and corresponding images using AI models.

*/

const generateStory = async () => {

if (![Link]()) {

showMessage('‫الرجاء إدخال وصف للقصة‬.');

return;

const parsedNumScenes = parseInt(numScenes, 10);


if (isNaN(parsedNumScenes) || parsedNumScenes < 1 || parsedNumScenes > 10) {

showMessage('10 ‫ و‬1 ‫الرجاء إدخال عدد مشاهد بين‬.');

return;

setIsLoading(true);

setGeneratedContent([]);

setErrorMessage('');

try {

// Step 1: Generate scene descriptions using Gemini

// ‫ نطلب من‬Gemini ‫توليد كل جزء من المشهد بشكل منفصل لتسهيل التعديل‬

const sceneDescriptionsPrompt = `Given the humorous and comedic story: '$


{storyDescription}', break this down into ${parsedNumScenes} sequential, cinematic scene
descriptions. Each scene should logically progress the narrative. For each scene, provide the
following structured blocks. Ensure Mallow's core appearance (ginger cat, fluffy, orange tabby
fur, large round green eyes) remains consistent, but other details can change per scene as
described.

**Output Format for EACH Scene (separate each full scene block with '---'):**

SCENE_DESCRIPTION: [Detailed description of Mallow's action, main focus, and specific


immediate environment.]

MALLOW_CLOTHING: [Description of Mallow's clothing for this scene, e.g., 'yellow t-shirt
with "Malik Kids" logo'.]

MALLOW_ACCESSORIES: [Description of Mallow's accessories for this scene, e.g., 'small


brown backpack, red superhero cape'. If none, state 'None'.]

MALLOW_FACE_STYLE: [Mallow's specific facial expression/emotional state for this scene,


e.g., 'amazed, wide-eyed, jaw dropped'.]

OPTIONAL_SECONDARY_CHARACTER_BLOCK:
- Character Name: [Name (e.g., Sparkle)]

- Species: [Species (e.g., Firefly)]

- Size: [Size (e.g., Small, match Mallow's paw)]

- Physique: [Physique (e.g., Slim, green, jelly-like)]

- Color: [Color (e.g., Emerald green with glowing wings)]

- Eye Color: [Eye Color (e.g., Bright, large, black, saucer-like)]

- Expression: [Expression (e.g., Curious, slightly confused)]

- Accessories: [Accessories (e.g., Tiny spacesuit helmet, tiny crown)]

- Positioning: [Positioning relative to Mallow (e.g., Hovering gently near Mallow's nose,
perched on Mallow's head)]

ENVIRONMENT_BLOCK:

- Location Type: [Type of location (e.g., Enchanted Forest Clearing, bustling city street)]

- Key Elements: [Key visual elements (e.g., bioluminescent mushrooms, ancient trees,
skyscrapers, hot dog stand)]

- Time of Day: [Time of day (e.g., Twilight, mid-afternoon, rainy night)]

- Lighting: [Lighting (e.g., Soft ambient glow from flora, harsh neon lights, stormy dim light)]

- Weather: [Weather (e.g., Clear, calm, rainy, foggy)]

- Mood: [Overall mood (e.g., Magical, wondrous, exciting, mysterious)]

---`;

const geminiApiUrl = `[Link]


2.0-flash:generateContent?key=${geminiApiKey}`;

const geminiPayload = {

contents: [{ role: "user", parts: [{ text: sceneDescriptionsPrompt }] }]

};
const geminiResponse = await fetch(geminiApiUrl, {

method: 'POST',

headers: { 'Content-Type': 'application/json' },

body: [Link](geminiPayload)

});

if (![Link]) {

throw new Error(`Gemini API error: ${[Link]}`);

const geminiResult = await [Link]();

const rawSceneOutputs = geminiResult?.candidates?.[0]?.content?.parts?.


[0]?.text?.split('---').filter(Boolean) || [];

if ([Link] === 0) {

showMessage(' ‫ يرجى محاولة وصف‬.‫لم يتمكن الذكاء االصطناعي من توليد أوصاف المشاهد‬
‫قصة مختلفة أو أعد المحاولة‬.');

setIsLoading(false);

return;

const sceneDataExtracted = [Link](parseGeminiSceneOutput).filter(item =>


[Link]);

if ([Link] === 0) {

showMessage('‫ يرجى محاولة وصف‬.‫لم يتمكن الذكاء االصطناعي من تحليل أوصاف المشاهد‬
‫قصة مختلفة أو أعد المحاولة‬.');
setIsLoading(false);

return;

const [placeholderWidth, placeholderHeight] = aspectRatio === '16:9' ? [1280, 720] : [720,


1280];

for (let i = 0; i < [Link]; i++) {

const { sceneDesc, mallowClothing, mallowAccessories, mallowFaceStyle,


optionalCharBlock, envBlock } = sceneDataExtracted[i];

const tempContent = {

sceneText: sceneDesc,

editablePrompt: sceneDesc, // for the main scene description

editableClothingText: mallowClothing,

editableAccessoriesText: mallowAccessories,

editableFaceStyleText: mallowFaceStyle,

editableOptionalCharacterText: optionalCharBlock,

editableEnvironmentText: envBlock,

isLoading: true,

placeholderWidth,

placeholderHeight,

imageUrl: '',

motionPrompt: ''

};

setGeneratedContent(prev => [...prev, tempContent]);


const fullPromptData = await generateImageAndMotionPrompt(

sceneDesc,

optionalCharBlock,

envBlock,

mallowClothing,

mallowAccessories,

mallowFaceStyle,

aspectRatio

);

setGeneratedContent(prevContent => {

const updated = [...prevContent];

updated[i] = { ...tempContent, ...fullPromptData, isLoading: false };

return updated;

});

} catch (error) {

[Link]("Generation error:", error);

showMessage(`‫حدث خطأ أثناء التوليد‬: ${[Link]}. ‫يرجى المحاولة مرة أخرى‬.`);

setGeneratedContent(prevContent => [Link](item => ({ ...item, isLoading:


false })));

} finally {

setIsLoading(false);

};
/**

* Helper function to generate image and motion prompt for a single scene.

* ‫اآلن تستقبل جميع كتل الوصف القابلة للتعديل‬.

* @param {string} sceneDescription - ‫الوصف المحدد للمشهد‬.

* @param {string} optionalCharacterBlock - )‫كتلة وصف الشخصية الثانوية (إذا وجدت‬.

* @param {string} environmentBlock - ‫كتلة وصف البيئة للمشهد‬.

* @param {string} clothingText - ‫وصف مالبس مالو‬.

* @param {string} accessoriesText - ‫وصف اكسسوارات مالو‬.

* @param {string} faceStyleText - ‫وصف تعابير وجه مالو‬.

* @param {string} selectedAspectRatio - ‫نسبة العرض إلى االرتفاع‬.

* @returns {Promise<{imageUrl: string, motionPrompt: string}>}

*/

const generateImageAndMotionPrompt = async (sceneDescription, optionalCharacterBlock,


environmentBlock, clothingText, accessoriesText, faceStyleText, selectedAspectRatio) => {

// ‫ تحديد‬Aspect Ratio ‫للنص‬

const aspectRatioText = selectedAspectRatio === '16:9' ? 'cinematic 16:9 horizontal frame' :


'cinematic 9:16 vertical frame';

const [fallbackWidth, fallbackHeight] = selectedAspectRatio === '16:9' ? [1280, 720] : [720,


1280];

// **** ‫( قالب البرومبت الرئيسي‬Fixed) ****

// ‫الحظ كيف تم دمج وصف الشخصية والدعائم الثابتة هنا‬.

// ‫ يتم استبدال‬SCENE_DESCRIPTION ‫ و‬OPTIONAL_SECONDARY_CHARACTER_BLOCK ‫و‬


ENVIRONMENT_BLOCK ‫ديناميكًيا‬.

const basePromptTemplate = `ultra-realistic 3D digital illustration, ${aspectRatioText}, warm


color palette, soft lighting, masterpiece quality, 8K resolution, shallow depth of field, clean
background, no text in image
Scene Composition:

${sceneDescription}

Mallow the cat is always fully visible in the frame (full-body), facing camera or interacting with
objects. If other characters appear, they must be proportionally accurate, placed at specified
positions, and NOT overlap or distort Mallow. Background must match the environmental
context precisely.

Character Specifications (Fixed Elements - to maintain general consistency):

- Character Name: Mallow

- Species: Cat

- Size: Small

- Physique: slightly leaner, fluffy // Changed to be slightly leaner but still fluffy

- Fur Color: Orange tabby with soft stripes

- Eye Color: Large, round, green

- Motion Style: Can be standing, walking, jumping, riding, swimming depending on the scene,
but always grounded and natural

- Face Features: cute, chubby, rounded, realistic, highly detailed, photorealistic cat face, with
wide open expressive green eyes, small open mouth, natural fur texture, consistent look across
all images // Added specific details from the reference image

Dynamic Character Specifications (Editable per scene):

- Expression Style: ${faceStyleText || 'Childlike, expressive (match with scene emotion)'}

- Clothing: ${clothingText || 'No specific clothing'}

- Accessories: ${accessoriesText || 'No specific accessories'}

Optional Secondary Character Block:

${optionalCharacterBlock || 'None'}
Environment Block:

${environmentBlock}

Camera and Technical Settings (Fixed):

- Camera Angle: Eye-level or slightly low-angle if heroic, always cinematic

- Focus: Sharp focus on Mallow and foreground characters/objects

- Depth of Field: Shallow (background blurred, focus on characters)

- Color Grading: Warm and vibrant, child-friendly

- Aspect Ratio: ${selectedAspectRatio}

- Style: clean and emotional, ultra-professional

Negative Prompts (MANDATORY for all scenes):

mutated, deformed, ugly, cropped, low-res, extra limbs, extra tails, missing limbs, blurry, bad
anatomy, fused characters, text, logos not matching description, duplicate characters, cartoon,
anime, drawing, sketch, illustration, low poly, pixel art, childish, abstract, non-realistic`;

const imagenApiUrl = `[Link]


3.0-generate-002:predict?key=${imagenApiKey}`;

const imagenPayload = {

instances: { prompt: basePromptTemplate }, // ‫نرسل القالب الكامل هنا‬

parameters: { sampleCount: 1, aspectRatio: selectedAspectRatio }

};

let imageUrl;

try {

const imagenResponse = await fetch(imagenApiUrl, {


method: 'POST',

headers: { 'Content-Type': 'application/json' },

body: [Link](imagenPayload)

});

if (![Link]) {

throw new Error(`Imagen API error: ${[Link]}`);

const imagenResult = await [Link]();

imageUrl = imagenResult?.predictions?.[0]?.bytesBase64Encoded

? `data:image/png;base64,${[Link][0].bytesBase64Encoded}`

: `[Link]
text=No+Image+Generated`;

} catch (imagenError) {

[Link]("Error generating image with Imagen:", imagenError);

imageUrl = `[Link]
text=Image+API+Failed`;

showMessage(`‫فشل توليد الصورة‬: ${[Link]}`);

let motionPrompt = 'No specific motion prompt generated.';

// ‫اقتراح الحركة ال يزال يعتمد فقط على وصف المشهد‬

const motionPromptGenPrompt = `Based on the following ultra-realistic scene description of


a cat: '${sceneDescription}', suggest a concise, engaging motion prompt suitable for a video
generation AI like PixVerse. Make it short and descriptive, only the motion prompt itself.`;
const geminiMotionApiUrl =
`[Link]
key=${geminiApiKey}`;

const geminiMotionPayload = {

contents: [{ role: "user", parts: [{ text: motionPromptGenPrompt }] }]

};

try {

const geminiMotionResponse = await fetch(geminiMotionApiUrl, {

method: 'POST',

headers: { 'Content-Type': 'application/json' },

body: [Link](geminiMotionPayload)

});

if (![Link]) {

throw new Error(`Gemini Motion API error: ${[Link]}`);

const geminiMotionResult = await [Link]();

motionPrompt = geminiMotionResult?.candidates?.[0]?.content?.parts?.[0]?.text?.trim() ||
motionPrompt;

} catch (geminiMotionError) {

[Link]("Error generating motion prompt with Gemini:", geminiMotionError);

showMessage(`‫فشل توليد اقتراح الحركة‬: ${[Link]}`);

return { imageUrl, motionPrompt };


};

/**

* Handles downloading a single image.

* @param {string} imageUrl - The base64 encoded image URL.

* @param {number} sceneIndex - The index of the scene (for file naming).

*/

const handleDownloadImage = async (imageUrl, sceneIndex) => {

try {

const response = await fetch(imageUrl);

const blob = await [Link]();

const url = [Link](blob);

const a = [Link]('a');

[Link] = url;

[Link] = `scene_${sceneIndex + 1}_image.png`;

[Link](a);

[Link]();

[Link](a);

[Link](url);

} catch (error) {

[Link]("Failed to download image:", error);

showMessage("‫فشل تنزيل الصورة‬.");

};

/**
* Handles downloading all generated images as a single ZIP file.

* ‫ تستخدم‬JSZip ‫ و‬FileSaver ‫ المتاحتين عالمًيا عبر‬CDN

*/

const handleDownloadAllImagesAsZip = async () => {

if ([Link] === 0) {

showMessage('‫ال توجد صور لتنزيلها‬.');

return;

// ‫التحقق من توفر المكتبات العالمية‬

if (!areLibsLoaded || typeof [Link] === 'undefined' || typeof [Link] ===


'undefined') {

showMessage(' ‫ يرجى االنتظار قليًال أو إعادة تحميل‬.‫ مكتبات التنزيل غير متوفرة بعد‬،‫عذرًا‬
‫الصفحة‬.');

[Link]("JSZip or FileSaver are not loaded in the window object, despite useEffect
attempt.");

return;

setIsLoading(true); // ‫تعيين حالة التحميل عند بدء عملية الضغط‬

showMessage(')‫جاري إعداد الصور للتنزيل (ملف مضغوط‬...');

const zip = new [Link](); // ‫استخدام الكائن العالمي‬

try {

for (let i = 0; i < [Link]; i++) {

const item = generatedContent[i];

if ([Link] && ![Link]) {


// ‫ جلب الصورة كـ‬Blob ‫ وإضافتها إلى ملف‬ZIP

const response = await fetch([Link]);

const blob = await [Link]();

[Link](`scene_${i + 1}_image.png`, blob);

const content = await [Link]({ type: "blob" });

[Link](content, "story_images.zip"); // ‫استخدام الكائن العالمي‬

showMessage('‫;)'!تم تنزيل جميع الصور بنجاح كملف مضغوط‬

} catch (error) {

[Link]("‫فشل تنزيل جميع الصور كملف مضغوط‬:", error);

showMessage(`‫فشل تنزيل جميع الصور كملف مضغوط‬: ${[Link]}.`);

} finally {

setIsLoading(false); // ‫إيقاف حالة التحميل بعد االنتهاء‬

};

/**

* Regenerates the image for a specific scene with a modified prompt.

* @param {number} sceneIndex - The index of the scene to regenerate.

*/

const regenerateSceneImage = async (sceneIndex) => {

const sceneToRegenerate = generatedContent[sceneIndex];

if (!sceneToRegenerate || ![Link]()) {

showMessage('‫الرجاء إدخال برومبت صالح إلعادة التوليد‬.');


return;

setGeneratedContent(prevContent => {

const updated = [...prevContent];

updated[sceneIndex] = { ...updated[sceneIndex], isLoading: true };

return updated;

});

try {

const newSceneData = await generateImageAndMotionPrompt(

[Link],

[Link],

[Link],

[Link],

[Link],

[Link],

aspectRatio

);

setGeneratedContent(prevContent => {

const updated = [...prevContent];

updated[sceneIndex] = {

...updated[sceneIndex],

imageUrl: [Link],

motionPrompt: [Link],
isLoading: false,

};

return updated;

});

showMessage(`‫ تم إعادة توليد المشهد‬${sceneIndex + 1} ‫;)`!بنجاح‬

} catch (error) {

[Link](`Error regenerating scene ${sceneIndex + 1}:`, error);

showMessage(`‫ فشل إعادة توليد المشهد‬${sceneIndex + 1}: ${[Link]}`);

setGeneratedContent(prevContent => {

const updated = [...prevContent];

updated[sceneIndex] = { ...updated[sceneIndex], isLoading: false };

return updated;

});

};

/**

* Handles copying text to clipboard.

* @param {string} textToCopy - The text to be copied.

*/

const handleCopyText = async (textToCopy) => {

try {

await [Link](textToCopy);

showMessage("‫;)"!تم نسخ االقتراح بنجاح‬

} catch (err) {

[Link]('Failed to copy text: ', err);


showMessage("‫فشل نسخ االقتراح‬.");

};

/**

* Handles downloading all motion prompts as a text file.

*/

const handleDownloadAllMotionPromptsAsText = () => {

if ([Link] === 0) {

showMessage('‫ال توجد برومبتات حركة لتنزيلها‬.');

return;

if (!areLibsLoaded || typeof [Link] === 'undefined') {

showMessage(' ‫ يرجى االنتظار قليًال أو إعادة تحميل‬.‫ مكتبة التنزيل غير متوفرة بعد‬،‫عذرًا‬
‫الصفحة‬.');

[Link]("FileSaver is not loaded in the window object.");

return;

const allMotionPrompts = [Link]((item, index) => `‫ المشهد‬${index + 1}: $


{[Link]}`).join('\n\n');

const blob = new Blob([allMotionPrompts], { type: "text/plain;charset=utf-8" });

try {

[Link](blob, "motion_prompts.txt");

showMessage('‫;)'!تم تنزيل جميع برومبتات الحركة بنجاح كملف نصي‬

} catch (error) {
[Link]("‫فشل تنزيل برومبتات الحركة‬:", error);

showMessage(`‫فشل تنزيل برومبتات الحركة‬: ${[Link]}.`);

};

return (

<>

{/* Tailwind CSS CDN script and Google Fonts link are kept as they are commonly used and
less problematic */}

<script src="[Link]

<link href="[Link]
family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />

<div className="min-h-screen bg-gradient-to-br from-purple-100 to-indigo-100 flex flex-col


items-center p-4 sm:p-6 lg:p-8 font-inter text-gray-800">

<div className="bg-white p-6 rounded-xl shadow-2xl w-full max-w-lg sm:max-w-2xl


lg:max-w-3xl border border-indigo-200">

<h1 className="text-3xl sm:text-4xl font-extrabold mb-6 text-center text-indigo-700 drop-


shadow-sm">

AI Story & Image Generator

</h1>

<div className="mb-6">

<label htmlFor="storyDescription" className="block text-lg font-semibold text-gray-700


mb-2">

‫وصف القصة‬

</label>

<textarea
id="storyDescription"

value={storyDescription}

onChange={e => setStoryDescription([Link])}

placeholder=" :‫ على سبيل المثال‬.‫اكتب وصًفا مفصًال لقصة قطة الزنجبيل الساحرة هنا‬
‫ صف‬.‫ تكتشف دراجة نارية صغيرة وتقرر الذهاب في مغامرة‬،‫'قطة زنجبيل مرحة ُتدعى لوز‬
‫ بما في ذلك أي شخصيات ثانوية أو تفاصيل بيئية متغيرة‬،‫المشاهد بالتفصيل‬.'"

rows={5}

className="w-full p-3 border border-gray-300 rounded-lg focus:ring-4 focus:ring-


indigo-300 focus:border-indigo-500 transition duration-200 ease-in-out resize-y shadow-sm"

aria-label="Story description"

/>

</div>

<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-6">

<div>

<label htmlFor="numScenes" className="block text-lg font-semibold text-gray-700 mb-


2">

‫عدد المشاهد‬

</label>

<input

id="numScenes"

type="number"

min={1}

max={10}

value={numScenes}

onChange={e => setNumScenes(parseInt([Link], 10))}

className="w-full p-3 border border-gray-300 rounded-lg focus:ring-4 focus:ring-


indigo-300 focus:border-indigo-500 transition duration-200 ease-in-out shadow-sm"
aria-label="Number of scenes"

/>

</div>

<div>

<label htmlFor="aspectRatio" className="block text-lg font-semibold text-gray-700 mb-


2">

‫نسبة العرض إلى االرتفاع للصورة‬

</label>

<select

id="aspectRatio"

value={aspectRatio}

onChange={e => setAspectRatio([Link])}

className="w-full p-3 border border-gray-300 rounded-lg focus:ring-4 focus:ring-


indigo-300 focus:border-indigo-500 transition duration-200 ease-in-out shadow-sm bg-white"

aria-label="Image aspect ratio"

>

<option value="16:9">16:9 (‫<)أفقي‬/option>

<option value="9:16">9:16 (‫<)عمودي‬/option>

</select>

</div>

</div>

<button

onClick={generateStory}

disabled={isLoading}
className={`w-full py-3 px-6 rounded-lg text-white font-bold text-lg shadow-lg transform
transition duration-300 ease-in-out focus:outline-none focus:ring-4 focus:ring-indigo-400
focus:ring-offset-2

${isLoading ? 'bg-indigo-400 cursor-not-allowed' : 'bg-indigo-600 hover:bg-indigo-700


active:bg-indigo-800 hover:scale-105'}`}

aria-live="polite"

>

{isLoading ? (

<span className="flex items-center justify-center">

<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"


xmlns="[Link] fill="none" viewBox="0 0 24 24">

<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"


strokeWidth="4"></circle>

<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0


5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3
7.938l3-2.647z"></path>

</svg>

‫جاري التوليد‬...

</span>

) : '‫}'توليد القصة والصور‬

</button>

{errorMessage && (

<div

className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg


relative mt-4 shadow-md"

role="alert"

>

<strong className="font-bold">‫<!خطأ‬/strong>
<span className="block sm:inline ml-2">{errorMessage}</span>

</div>

)}

</div>

{[Link] > 0 && (

<div className="mt-8 w-full max-w-lg sm:max-w-2xl lg:max-w-3xl flex flex-col sm:flex-row


justify-center gap-4">

<button

onClick={handleDownloadAllImagesAsZip}

disabled={isLoading || !areLibsLoaded}

className={`flex-1 py-3 px-6 rounded-lg text-white font-bold text-lg shadow-lg


transform transition duration-300 ease-in-out focus:outline-none focus:ring-4 focus:ring-green-
400 focus:ring-offset-2

${isLoading || !areLibsLoaded ? 'bg-green-400 cursor-not-allowed' : 'bg-green-600


hover:bg-green-700 active:bg-green-800 hover:scale-105'}`}

>

‫( تنزيل جميع الصور‬ZIP)

</button>

<button

onClick={handleDownloadAllMotionPromptsAsText} // Changed to new download


function

disabled={isLoading || !areLibsLoaded} // Disable if loading or libs not loaded

className={`flex-1 py-3 px-6 rounded-lg text-white font-bold text-lg shadow-lg


transform transition duration-300 ease-in-out focus:outline-none focus:ring-4 focus:ring-blue-
400 focus:ring-offset-2

${isLoading || !areLibsLoaded ? 'bg-blue-400 cursor-not-allowed' : 'bg-blue-600


hover:bg-blue-700 active:bg-blue-800 hover:scale-105'}`}

>
{/* Changed icon to download icon */}

<svg xmlns="[Link] className="h-5 w-5 inline-block mr-2"


viewBox="0 0 20 20" fill="currentColor">

<path fillRule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-


1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414
1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clipRule="evenodd" />

</svg>

‫تنزيل جميع برومبتات الحركة‬

</button>

</div>

)}

{[Link] > 0 && (

<div className="mt-12 w-full max-w-4xl grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2


xl:grid-cols-3 gap-8">

{[Link]((item, idx) => (

<div key={idx} className="bg-white p-5 rounded-xl shadow-lg border border-gray-200


transform hover:scale-103 transition duration-300 ease-in-out">

<h3 className="text-xl font-bold text-indigo-700 mb-3">

‫{ المشهد‬idx + 1}

</h3>

{[Link] ? (

<div className={`flex items-center justify-center rounded-lg bg-gray-100 text-gray-


500 text-sm animate-pulse

${aspectRatio === '16:9' ? 'h-48 w-full' : 'h-64 w-full'}`}>

<svg className="animate-spin h-8 w-8 text-indigo-500"


xmlns="[Link] fill="none" viewBox="0 0 24 24">

<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"


strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0
0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3
7.938l3-2.647z"></path>

</svg>

<span className="ml-2">‫جاري تحميل الصورة‬...</span>

</div>

):(

<img

src={[Link]}

alt={`Scene ${idx + 1}`}

className="w-full h-auto rounded-lg object-cover mb-4 shadow-md border border-


gray-100"

onError={(e) => {

[Link] = null;

const [width, height] = [Link] && [Link] ?


[[Link], [Link]] : (aspectRatio === '16:9' ? [1280, 720] : [720,
1280]);

[Link] = `[Link]
text=Image+Load+Failed`;

}}

/>

)}

<button

onClick={() => handleDownloadImage([Link], idx)}

disabled={[Link]}

className={`w-full bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4


rounded-lg flex items-center justify-center space-x-2 transition duration-200 ease-in-out
shadow-md hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-
offset-2 mb-3

${[Link] ? 'opacity-50 cursor-not-allowed' : ''}`}

>

<svg xmlns="[Link] className="h-5 w-5" viewBox="0 0 20


20" fill="currentColor">

<path fillRule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-


1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414
1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clipRule="evenodd" />

</svg>

<span>‫<تنزيل الصورة‬/span>

</button>

{/* Editable Scene Prompt */}

<label htmlFor={`editablePrompt-${idx}`} className="block text-sm font-semibold text-


gray-700 mb-1 mt-4">

‫تعديل وصف المشهد‬:

</label>

<textarea

id={`editablePrompt-${idx}`}

value={[Link]}

onChange={(e) => {

setGeneratedContent(prevContent => {

const updated = [...prevContent];

updated[idx] = { ...updated[idx], editablePrompt: [Link] };

return updated;

});

}}
rows={3}

className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-


indigo-300 focus:border-indigo-500 transition duration-200 ease-in-out resize-y text-sm mb-2"

aria-label={`Editable prompt for scene ${idx + 1}`}

/>

{/* Editable Mallow Clothing */}

<label htmlFor={`editableClothing-${idx}`} className="block text-sm font-semibold


text-gray-700 mb-1 mt-2">

‫تعديل مالبس مالو‬:

</label>

<textarea

id={`editableClothing-${idx}`}

value={[Link]}

onChange={(e) => {

setGeneratedContent(prevContent => {

const updated = [...prevContent];

updated[idx] = { ...updated[idx], editableClothingText: [Link] };

return updated;

});

}}

rows={2}

className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-


indigo-300 focus:border-indigo-500 transition duration-200 ease-in-out resize-y text-sm mb-2"

placeholder="‫مثال‬: yellow t-shirt with 'Malik Kids' logo"

aria-label={`Editable Mallow clothing for scene ${idx + 1}`}

/>
{/* Editable Mallow Accessories */}

<label htmlFor={`editableAccessories-${idx}`} className="block text-sm font-semibold


text-gray-700 mb-1 mt-2">

‫تعديل إكسسوارات مالو‬:

</label>

<textarea

id={`editableAccessories-${idx}`}

value={[Link]}

onChange={(e) => {

setGeneratedContent(prevContent => {

const updated = [...prevContent];

updated[idx] = { ...updated[idx], editableAccessoriesText: [Link] };

return updated;

});

}}

rows={2}

className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-


indigo-300 focus:border-indigo-500 transition duration-200 ease-in-out resize-y text-sm mb-2"

placeholder="‫مثال‬: small brown backpack, bright red superhero cape"

aria-label={`Editable Mallow accessories for scene ${idx + 1}`}

/>

{/* Editable Mallow Face Style */}

<label htmlFor={`editableFaceStyle-${idx}`} className="block text-sm font-semibold


text-gray-700 mb-1 mt-2">

‫تعديل تعبيرات وجه مالو‬:


</label>

<textarea

id={`editableFaceStyle-${idx}`}

value={[Link]}

onChange={(e) => {

setGeneratedContent(prevContent => {

const updated = [...prevContent];

updated[idx] = { ...updated[idx], editableFaceStyleText: [Link] };

return updated;

});

}}

rows={2}

className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-


indigo-300 focus:border-indigo-500 transition duration-200 ease-in-out resize-y text-sm mb-2"

placeholder="‫مثال‬: wide-eyed, jaw dropped, slightly comically terrified"

aria-label={`Editable Mallow face style for scene ${idx + 1}`}

/>

{/* Editable Optional Secondary Character Block */}

<label htmlFor={`optionalCharPrompt-${idx}`} className="block text-sm font-semibold


text-gray-700 mb-1 mt-2">

)‫تعديل وصف الشخصية الثانوية (اختياري‬:

</label>

<textarea

id={`optionalCharPrompt-${idx}`}

value={[Link]}

onChange={(e) => {
setGeneratedContent(prevContent => {

const updated = [...prevContent];

updated[idx] = { ...updated[idx], editableOptionalCharacterText: [Link] };

return updated;

});

}}

rows={3}

className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-


indigo-300 focus:border-indigo-500 transition duration-200 ease-in-out resize-y text-sm mb-2"

placeholder="‫ مثال‬،‫إذا وجدت‬: - Character Name: Sparkle..."

aria-label={`Editable optional character prompt for scene ${idx + 1}`}

/>

{/* Editable Environment Block */}

<label htmlFor={`envPrompt-${idx}`} className="block text-sm font-semibold text-


gray-700 mb-1 mt-2">

‫تعديل وصف البيئة‬:

</label>

<textarea

id={`envPrompt-${idx}`}

value={[Link]}

onChange={(e) => {

setGeneratedContent(prevContent => {

const updated = [...prevContent];

updated[idx] = { ...updated[idx], editableEnvironmentText: [Link] };

return updated;

});
}}

rows={3}

className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-


indigo-300 focus:border-indigo-500 transition duration-200 ease-in-out resize-y text-sm mb-2"

placeholder="‫مثال‬: - Location Type: Enchanted Forest Clearing..."

aria-label={`Editable environment prompt for scene ${idx + 1}`}

/>

<button

onClick={() => regenerateSceneImage(idx)}

disabled={[Link] || ![Link]()}

className={`w-full py-2 px-4 rounded-lg text-white font-bold text-sm shadow-md


transform transition duration-300 ease-in-out focus:outline-none focus:ring-2 focus:ring-purple-
400 focus:ring-offset-2

${[Link] || ![Link]() ? 'bg-purple-400 cursor-not-allowed'


: 'bg-purple-600 hover:bg-purple-700 active:bg-purple-800'}`}

>

{[Link] ? (

<span className="flex items-center justify-center">

<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white"


xmlns="[Link] fill="none" viewBox="0 0 24 24">

<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor"


strokeWidth="4"></circle>

<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373


0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3
7.938l3-2.647z"></path>

</svg>

‫جاري إعادة التوليد‬...

</span>
) : '‫}'إعادة توليد الصورة‬

</button>

<p className="text-gray-700 text-sm mb-2 mt-4">

<strong className="font-semibold text-indigo-600">‫اقتراح الحركة‬:</strong>


{[Link]}

</p>

<button

onClick={() => handleCopyText([Link])}

className="w-full bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4


rounded-lg flex items-center justify-center space-x-2 transition duration-200 ease-in-out
shadow-md hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-
offset-2"

>

<svg xmlns="[Link] className="h-5 w-5" viewBox="0 0 20


20" fill="currentColor">

<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />

<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2H6zM5


9.586V15h10V9.586l-1-1V6H6v2.586L5 9.586z" />

</svg>

<span>‫<نسخ اقتراح الحركة‬/span>

</button>

</div>

))}

</div>

)}

</div>
</>

);

};

export default App;

You might also like