Skip to content

Instantly share code, notes, and snippets.

@simonw
Created May 7, 2025 21:56
Show Gist options
  • Save simonw/6363ace77bbac08c6ad05857b3bd9ad2 to your computer and use it in GitHub Desktop.
Save simonw/6363ace77bbac08c6ad05857b3bd9ad2 to your computer and use it in GitHub Desktop.

Gemini Image JSON tool

Run against Gemini 2.5 Pro Preview 05-06 in AI Studio.

Prompt

Build a single page HTML + JavaScript (no React, no dependencies) mobile-friendly UI that lets me paste in JSON like this:

{
  "candidates": [
    {
      "content": {
        "parts": [
          {
            "text": "Okay, I will generate a 3D rendered image of a pink cartoon pig wearing a dark blue top hat and small, white feathered wings. It will be flying above a brightly colored, utopian futuristic city with sleek, rounded buildings interspersed with lush green parks and flying vehicles.\n\n"
          },
          {
            "inlineData": {
              "mimeType": "image/png",
              "data": "iVBORw0KGgoAAAANSUhEU...gxRAAAAAElFTkSuQmCC"
            }
          }
        ],
        "role": "model"
      },
      "finishReason": "STOP",
      "index": 0
    }
  ],
  "usageMetadata": {
    "promptTokenCount": 33,
    "candidatesTokenCount": 1345,
    "totalTokenCount": 1378,
    "promptTokensDetails": [
      {
        "modality": "TEXT",
        "tokenCount": 33
      }
    ],
    "candidatesTokensDetails": [
      {
        "modality": "IMAGE",
        "tokenCount": 1290
      }
    ]
  },
  "modelVersion": "gemini-2.0-flash-preview-image-generation"
}

And renders that in a nice way, with those base64 images rendered on the page (max width 100%) and the JSON usage metadata pretty-printed at the bottom

Response

Okay, here's a single HTML file with embedded JavaScript and CSS that achieves this.

Key features:

  1. Mobile-Friendly: Uses viewport meta tag and basic responsive CSS.
  2. JSON Input: A textarea for pasting the JSON.
  3. Processing Button: Triggers the parsing and rendering.
  4. Content Rendering:
    • Extracts text from parts.text and displays it as paragraphs.
    • Extracts base64 image data from parts.inlineData (if mimeType is image/*), creates an <img> tag, and displays it with max-width: 100%.
  5. Usage Metadata: Pretty-prints the usageMetadata object at the bottom.
  6. Error Handling: Basic error message if JSON is invalid.
  7. No Dependencies: Pure HTML, CSS, and JavaScript.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSON Image & Text Renderer</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            margin: 0;
            padding: 15px;
            background-color: #f4f4f4;
            color: #333;
            line-height: 1.6;
        }
        .container {
            max-width: 800px;
            margin: 20px auto;
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0,0,0,0.1);
        }
        h1, h2 {
            color: #333;
            border-bottom: 1px solid #eee;
            padding-bottom: 10px;
        }
        textarea {
            width: 100%;
            min-height: 200px;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            box-sizing: border-box;
            margin-bottom: 10px;
            font-family: monospace;
            font-size: 0.9em;
        }
        button {
            background-color: #007bff;
            color: white;
            padding: 10px 15px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 1em;
            margin-bottom: 20px;
            display: block;
            width: 100%;
        }
        button:hover {
            background-color: #0056b3;
        }
        #output img {
            max-width: 100%;
            height: auto;
            display: block;
            margin-top: 10px;
            margin-bottom: 10px;
            border-radius: 4px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        #output p {
            margin-top: 0;
            margin-bottom: 1em;
            white-space: pre-wrap; /* Preserve line breaks from JSON text */
        }
        #metadata pre {
            background-color: #e9ecef;
            padding: 15px;
            border-radius: 4px;
            overflow-x: auto;
            font-size: 0.85em;
            white-space: pre-wrap;
            word-break: break-all;
        }
        .error {
            color: red;
            font-weight: bold;
            margin-bottom: 10px;
        }
        .section-title {
            margin-top: 30px;
            font-size: 1.2em;
            color: #555;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>JSON Content Renderer</h1>

        <label for="jsonInput">Paste JSON here:</label>
        <textarea id="jsonInput" placeholder="Paste your JSON data here..."></textarea>
        <button id="processButton">Process JSON</button>

        <div id="errorDisplay" class="error"></div>

        <div id="output">
            <!-- Rendered content will go here -->
        </div>

        <div id="metadata">
            <!-- Usage metadata will go here -->
        </div>
    </div>

    <script>
        document.getElementById('processButton').addEventListener('click', () => {
            const jsonString = document.getElementById('jsonInput').value;
            const outputDiv = document.getElementById('output');
            const metadataDiv = document.getElementById('metadata');
            const errorDiv = document.getElementById('errorDisplay');

            // Clear previous output and errors
            outputDiv.innerHTML = '';
            metadataDiv.innerHTML = '';
            errorDiv.textContent = '';

            if (!jsonString.trim()) {
                errorDiv.textContent = 'Please paste some JSON data.';
                return;
            }

            let jsonData;
            try {
                jsonData = JSON.parse(jsonString);
            } catch (e) {
                errorDiv.textContent = `Error parsing JSON: ${e.message}`;
                return;
            }

            // Render content from candidates
            if (jsonData.candidates && Array.isArray(jsonData.candidates)) {
                const candidatesTitle = document.createElement('h2');
                candidatesTitle.textContent = 'Rendered Content';
                candidatesTitle.classList.add('section-title');
                outputDiv.appendChild(candidatesTitle);

                jsonData.candidates.forEach((candidate, index) => {
                    if (candidate.content && candidate.content.parts && Array.isArray(candidate.content.parts)) {
                        const candidateHeader = document.createElement('h3');
                        candidateHeader.textContent = `Candidate ${index + 1}`;
                        candidateHeader.style.fontSize = '1.1em';
                        candidateHeader.style.marginTop = '20px';
                        candidateHeader.style.color = '#444';
                        outputDiv.appendChild(candidateHeader);

                        candidate.content.parts.forEach(part => {
                            if (part.text) {
                                const p = document.createElement('p');
                                p.textContent = part.text;
                                outputDiv.appendChild(p);
                            } else if (part.inlineData && part.inlineData.mimeType && part.inlineData.mimeType.startsWith('image/')) {
                                const img = document.createElement('img');
                                img.src = `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`;
                                img.alt = 'Rendered Image';
                                outputDiv.appendChild(img);
                            }
                        });
                    }
                });
            } else {
                console.warn("No 'candidates' array found or it's not an array.");
            }

            // Render usage metadata
            if (jsonData.usageMetadata) {
                const metadataTitle = document.createElement('h2');
                metadataTitle.textContent = 'Usage Metadata';
                metadataTitle.classList.add('section-title');
                metadataDiv.appendChild(metadataTitle);

                const pre = document.createElement('pre');
                pre.textContent = JSON.stringify(jsonData.usageMetadata, null, 2); // Pretty print
                metadataDiv.appendChild(pre);
            } else {
                console.warn("No 'usageMetadata' object found.");
            }

             // Render modelVersion if present
            if (jsonData.modelVersion) {
                const modelVersionTitle = document.createElement('h2');
                modelVersionTitle.textContent = 'Model Version';
                modelVersionTitle.classList.add('section-title');
                metadataDiv.appendChild(modelVersionTitle); // Appending to metadataDiv for consistency

                const p = document.createElement('p');
                p.textContent = jsonData.modelVersion;
                p.style.backgroundColor = '#e9ecef';
                p.style.padding = '10px';
                p.style.borderRadius = '4px';
                p.style.fontFamily = 'monospace';
                metadataDiv.appendChild(p);
            }
        });

        // For easy testing, pre-fill with example JSON if textarea is empty
        (function addSampleData() {
            const textarea = document.getElementById('jsonInput');
            if (!textarea.value.trim()) {
                textarea.value = `{
  "candidates": [
    {
      "content": {
        "parts": [
          {
            "text": "Okay, I will generate a 3D rendered image of a pink cartoon pig wearing a dark blue top hat and small, white feathered wings. It will be flying above a brightly colored, utopian futuristic city with sleek, rounded buildings interspersed with lush green parks and flying vehicles.\\n\\n"
          },
          {
            "inlineData": {
              "mimeType": "image/png",
              "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
            }
          },
          {
            "text": "Here is another text part after the image."
          }
        ],
        "role": "model"
      },
      "finishReason": "STOP",
      "index": 0
    },
    {
      "content": {
        "parts": [
          {
            "text": "This is a second candidate, perhaps an alternative generation."
          }
        ],
        "role": "model"
      },
      "finishReason": "STOP",
      "index": 1
    }
  ],
  "usageMetadata": {
    "promptTokenCount": 33,
    "candidatesTokenCount": 1345,
    "totalTokenCount": 1378,
    "promptTokensDetails": [
      {
        "modality": "TEXT",
        "tokenCount": 33
      }
    ],
    "candidatesTokensDetails": [
      {
        "modality": "IMAGE",
        "tokenCount": 1290
      }
    ]
  },
  "modelVersion": "gemini-2.0-flash-preview-image-generation"
}`;
            // A tiny 1x1 red pixel base64 for the example
            const sampleBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
            // Replace the placeholder in the sample JSON with a real, but tiny, image.
            // Your example data was truncated, so I'm using a very small one.
            // For a real test, paste your full base64 string.
            textarea.value = textarea.value.replace("iVBORw0KGgoAAAANSUhEU...gxRAAAAAElFTkSuQmCC", sampleBase64);
            }
        })();

    </script>
</body>
</html>

How to use:

  1. Save the code above as an .html file (e.g., json_renderer.html).
  2. Open this file in any modern web browser (Chrome, Firefox, Safari, Edge).
  3. The textarea will be pre-filled with a modified version of your example JSON (I used a tiny 1x1 red pixel for the base64 image placeholder because your example data was truncated. Replace this with your full valid base64 string for a proper test.).
  4. Paste your own JSON into the textarea.
  5. Click the "Process JSON" button.
  6. The page will display:
    • Any text found in parts.text.
    • Any images found in parts.inlineData (rendered directly).
    • The usageMetadata object, pretty-printed.
    • The modelVersion string.

This solution provides a clean, functional UI for your specific use case without any external dependencies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
OSZAR »