Show description
Interactive ASCII Art Grid
Interactive ASCII Art Grid
Chat with the AI and see its visual response on the grid.
Send
Interactive ASCII Art Grid
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive ASCII Art Grid</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Roboto Mono', monospace;
}
/* Custom styles for the loader */
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Ensure the pre tag maintains its structure */
pre {
white-space: pre;
overflow-x: auto;
}
</style>
</head>
<body class="bg-gray-900 text-gray-200 flex flex-col items-center justify-center min-h-screen p-4">
<div class="w-full max-w-4xl mx-auto">
<h1 class="text-3xl md:text-4xl font-bold text-center text-cyan-400 mb-2">Interactive ASCII Art Grid</h1>
<p class="text-center text-gray-400 mb-4">Chat with the AI and see its visual response on the grid.</p>
<!-- The Grid Display -->
<div class="bg-gray-800 border-2 border-cyan-700 rounded-lg p-2 shadow-lg mb-4">
<pre id="ascii-grid" class="text-center text-lg leading-tight text-green-400"></pre>
</div>
<!-- AI Response and Loader -->
<div class="flex items-center justify-center space-x-4 h-12 mb-4">
<div id="loader" class="loader hidden"></div>
<p id="ai-response" class="text-lg text-yellow-300 text-center"></p>
</div>
<!-- Input Form -->
<form id="prompt-form" class="flex flex-col sm:flex-row gap-2">
<input
type="text"
id="prompt-input"
placeholder="Ask the AI to draw something... (e.g., 'draw a house')"
class="flex-grow bg-gray-700 text-white border border-gray-600 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-cyan-500"
required>
<button
type="submit"
id="send-button"
class="bg-cyan-600 hover:bg-cyan-700 text-white font-bold py-3 px-6 rounded-lg transition-colors duration-300 disabled:bg-gray-500 disabled:cursor-not-allowed">
Send
</button>
</form>
</div>
<script>
// --- CONFIGURATION ---
const GRID_COLS = 50;
const GRID_ROWS = 20;
const CHAR_SET = ['#', '@', '*', '.', ' ']; // The 5 characters the AI can use
const ANIMATION_SPEED_MS = 150; // Speed for animations in milliseconds
// --- DOM ELEMENTS ---
const gridElement = document.getElementById('ascii-grid');
const promptForm = document.getElementById('prompt-form');
const promptInput = document.getElementById('prompt-input');
const sendButton = document.getElementById('send-button');
const loader = document.getElementById('loader');
const aiResponseElement = document.getElementById('ai-response');
// --- STATE ---
let animationInterval = null;
// --- CORE FUNCTIONS ---
/**
* Initializes the grid with empty spaces and renders it.
*/
function initializeGrid() {
const initialGridData = Array(GRID_ROWS).fill().map(() => Array(GRID_COLS).fill(' '));
const welcomeMessage = "Hello! What should I draw for you?".split('');
const startCol = Math.floor((GRID_COLS - welcomeMessage.length) / 2);
const midRow = Math.floor(GRID_ROWS / 2);
welcomeMessage.forEach((char, index) => {
if(startCol + index < GRID_COLS) {
initialGridData[midRow][startCol + index] = char;
}
});
renderGrid(initialGridData);
}
/**
* Renders a 2D array of characters to the <pre> element.
* @param {string[][]} gridData - The 2D array representing the grid.
*/
function renderGrid(gridData) {
if (!gridData || gridData.length === 0) {
console.error("Invalid grid data provided to renderGrid.");
return;
}
const gridString = gridData.map(row => row.join('')).join('\n');
gridElement.textContent = gridString;
}
/**
* Plays an animation by cycling through frames.
* @param {string[][][]} frames - An array of grid data, where each is a frame.
*/
function playAnimation(frames) {
if (animationInterval) {
clearInterval(animationInterval);
}
let currentFrame = 0;
animationInterval = setInterval(() => {
renderGrid(frames[currentFrame]);
currentFrame = (currentFrame + 1) % frames.length;
}, ANIMATION_SPEED_MS);
}
/**
* Clears any ongoing animation.
*/
function stopAnimation() {
if (animationInterval) {
clearInterval(animationInterval);
animationInterval = null;
}
}
/**
* Calls the Gemini API with a structured prompt.
* @param {string} userPrompt - The text from the user.
*/
async function callGeminiAPI(userPrompt) {
// This is the system prompt that tells the AI its role and capabilities.
const systemPrompt = `You are GridAI, a creative AI that responds to prompts by generating ASCII art.
Your capabilities:
1. You can create static images.
2. You can create simple animations (e.g., blinking, pulsing).
3. You can animate the movement of images across the grid.
Constraints:
- Grid dimensions: ${GRID_COLS} columns by ${GRID_ROWS} rows.
- Character set: [${CHAR_SET.map(c => `'${c}'`).join(', ')}]
- Your output MUST be a valid JSON object following the specified schema.
- For animations, provide an array of grids as frames. Keep animations short (2-8 frames).
- For static images, the "animation" array should be empty, and the "grid" field should contain the image.
- To show movement, create multiple frames where the object's position is different in each frame.
- Always provide a brief, friendly text description of what you've created.
- Be creative! Try to make interesting patterns and shapes.
- If you cannot fulfill a request, explain why in the description and return an empty grid.
Example of a static image request: "draw a smiley face"
Example of a simple animation request: "make a blinking star"
Example of a moving animation: "make a ball bounce from left to right"
`;
// Define the JSON schema for the AI's response
const schema = {
type: "OBJECT",
properties: {
"description": { "type": "STRING" },
"grid": {
"type": "ARRAY",
"items": {
"type": "ARRAY",
"items": { "type": "STRING" }
}
},
"animation": {
"type": "ARRAY",
"items": {
"type": "ARRAY",
"items": {
"type": "ARRAY",
"items": { "type": "STRING" }
}
}
}
},
required: ["description", "grid", "animation"]
};
const payload = {
systemInstruction: {
parts: [{ text: systemPrompt }]
},
contents: [
{ role: "user", parts: [{ text: userPrompt }] }
],
generationConfig: {
responseMimeType: "application/json",
responseSchema: schema
}
};
const apiKey = ""; // API key is handled by the environment
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorBody = await response.text();
console.error("API Error Body:", errorBody);
throw new Error(`API Error: ${response.status} ${response.statusText}`);
}
const result = await response.json();
if (result.candidates && result.candidates.length > 0) {
const text = result.candidates[0].content.parts[0].text;
return JSON.parse(text);
} else {
throw new Error("Invalid response structure from API.");
}
} catch (error) {
console.error("Error calling Gemini API:", error);
// Return a structured error response to be displayed on the grid
const errorGrid = Array(GRID_ROWS).fill().map(() => Array(GRID_COLS).fill(' '));
const errorMessage = "Error: Could not generate art.".split('');
const startCol = Math.floor((GRID_COLS - errorMessage.length) / 2);
errorMessage.forEach((char, i) => errorGrid[Math.floor(GRID_ROWS/2)][startCol+i] = char);
return {
description: "Sorry, I encountered an error. Please try again.",
grid: errorGrid,
animation: []
};
}
}
/**
* Handles the form submission event.
*/
async function handleFormSubmit(event) {
event.preventDefault();
const prompt = promptInput.value.trim();
if (!prompt) return;
// --- UI updates for loading state ---
stopAnimation();
sendButton.disabled = true;
loader.classList.remove('hidden');
aiResponseElement.textContent = 'Generating...';
promptInput.value = '';
renderGrid(Array(GRID_ROWS).fill().map(() => Array(GRID_COLS).fill(' '))); // Clear grid
// --- API Call and Rendering ---
const aiArt = await callGeminiAPI(prompt);
loader.classList.add('hidden');
sendButton.disabled = false;
if (aiArt) {
aiResponseElement.textContent = aiArt.description;
if (aiArt.animation && aiArt.animation.length > 0) {
playAnimation(aiArt.animation);
} else if (aiArt.grid && aiArt.grid.length > 0) {
renderGrid(aiArt.grid);
}
} else {
aiResponseElement.textContent = "Something went wrong. Couldn't get a response.";
}
}
// --- EVENT LISTENERS ---
document.addEventListener('DOMContentLoaded', initializeGrid);
promptForm.addEventListener('submit', handleFormSubmit);
</script>
</body>
</html>