async function run()

in src/gif_maker/index.ts [100:241]


async function run(value: string) {
  if (framesContainer) framesContainer.textContent = '';
  if (resultContainer) resultContainer.textContent = '';
  resultContainer?.classList.remove('appear');
  switchTab('frames');
  if (resultContainer) resultContainer.style.display = 'none';

  updateStatus('Generating frames...');
  if (generateButton) {
    generateButton.disabled = true;
    generateButton.classList.add('loading');
  }

  try {
    const expandPromptResponse = await ai.models.generateContent({
      model: 'gemini-2.0-flash',
      contents: value,
      config: {
        temperature: 1,
        systemInstruction: `**Generate simple, animated doodle GIFs on white from user input, prioritizing key visual identifiers in an animated doodle style with ethical considerations.**
**Core GIF:** Doodle/cartoonish (simple lines, stylized forms, no photorealism), subtle looping motion (primary subject(s) only: wiggle, shimmer, etc.), white background, lighthearted/positive tone (playful, avoids trivializing serious subjects), uses specified colors (unless monochrome/outline requested).
**Input Analysis:** Identify subject (type, specificity), prioritize visual attributes (hair C/T, skin tone neutrally if discernible/needed, clothes C/P, accessories C, facial hair type, other distinct features neutrally for people; breed, fur C/P for animals; key parts, colors for objects), extract text (content, style hints described, display as requested: speech bubble [format: 'Speech bubble says "[Text]" is persistent.'], caption/title [format: 'with the [title/caption] "[Text]" [position]'], or text-as-subject [format: 'the word "[Text]" in [style/color description]']), note style modifiers (e.g., "pencil sketch," "monochrome"), and action (usually "subtle motion"). If the subject or description is too vague, add specific characteristics to make it more unique and detailed.
**Prompt Template:** "[Style Descriptor(s)] [Subject Description with Specificity, Attributes, Colors, Skin Tone if applicable] [Text Component if applicable and NOT speech bubble]. [Speech Bubble Component if applicable]"
**Template Notes:** '[Style Descriptor(s)]' includes "cartoonish" or "doodle style" (especially for people) plus any user-requested modifiers. '[Subject Description...]' combines all relevant subject and attribute details. '[Text Component...]' is for captions, titles, or text-as-subject only. '[Speech Bubble Component...]' is for speech bubbles only (mutually exclusive with Text Component).
**Key Constraints:** No racial labels. Neutral skin tone descriptors when included. Cartoonish/doodle style always implied, especially for people. One text display method only.`,
      },
    });

    const prompt = `A doodle animation on a white background of ${expandPromptResponse.text}. Subtle motion but nothing else moves.`;
    const style = `Simple, vibrant, varied-colored doodle/hand-drawn sketch`;

    const response = await ai.models.generateContentStream({
      model: 'gemini-2.0-flash-exp',
      contents: `Generate at least 10 square, white-background doodle animation frames with smooth, fluid, vibrantly colored motion depicting ${prompt}.

*Mandatory Requirements (Compacted):**

**Style:** ${style}.
**Background:** Plain solid white (no background colors/elements). Absolutely no black background.
**Content & Motion:** Clearly depict **{{prompt}}** action with colored, moving subject (no static images). If there's an action specified, it should be the main difference between frames.
**Frame Count:** At least 5 frames showing continuous progression and at most 10 frames.
**Format:** Square image (1:1 aspect ratio).
**Cropping:** Absolutely no black bars/letterboxing; colorful doodle fully visible against white.
**Output:** Actual image files for a smooth, colorful doodle-style GIF on a white background. Make sure every frame is different enough from the previous one.`,
      config: {
        temperature: 1,
        responseModalities: [Modality.IMAGE, Modality.TEXT],
      },
    });
    const images = [];
    let frameCount = 0;

    for await (const chunk of response) {
      if (chunk.candidates && chunk.candidates[0].content?.parts) {
        for (const part of chunk.candidates[0].content.parts) {
          if (part.inlineData && framesContainer) {
            frameCount++;
            updateStatus(`Generated frame ${frameCount}`);

            // Create a frame element for our UI
            const frameElement = document.createElement('div');
            frameElement.className = 'frame';

            // Create and add the frame number
            const frameNumber = document.createElement('div');
            frameNumber.className = 'frame-number';
            frameNumber.textContent = frameCount.toString();
            frameElement.appendChild(frameNumber);

            // Create the image as in the original
            const src = `data:image/png;base64,${part.inlineData.data}`;
            const img = document.createElement('img');
            img.width = 1024;
            img.height = 1024;
            img.src = src;

            // Add it to our frame element
            frameElement.appendChild(img);
            framesContainer.appendChild(frameElement);

            // Store URL for GIF creation
            images.push(src);

            // Animate the frame appearance
            setTimeout(() => {
              frameElement.classList.add('appear');
            }, 50);
          }
        }
      }
    }

    if (frameCount < 2) {
      updateStatus('Failed to generate any frames. Try another prompt.');
      return false;
    }

    // Update status
    updateStatus('Creating GIF...');

    // Create the GIF just like in the original
    const img = await createGifFromPngs(images);
    img.className = 'result-image';

    // Clear and add to result container
    if (resultContainer) {
      resultContainer.appendChild(img);

      // Add download button
      const downloadButton = document.createElement('button');
      downloadButton.className = 'download-button';
      const icon = document.createElement('i');
      icon.className = 'fas fa-download';
      downloadButton.appendChild(icon);
      downloadButton.onclick = () => {
        const a = document.createElement('a') as HTMLAnchorElement;
        a.href = img.src;
        a.download = 'magical-animation.gif';
        a.click();
      };
      resultContainer.appendChild(downloadButton);

      switchTab('output');
      setTimeout(() => {
        resultContainer.classList.add('appear');
        generationContainer.scrollIntoView({behavior: 'smooth'});
      }, 50);
    }

    updateStatus('Done!');
  } catch (error) {
    console.error('Error generating animation:', error);
    updateStatus('Error generating animation');
    return false;
  } finally {
    if (generateButton) {
      generateButton.disabled = false;
      generateButton.classList.remove('loading');
    }
  }
  return true;
}