-
Notifications
You must be signed in to change notification settings - Fork 1
initil svg state #148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
initil svg state #148
Changes from 28 commits
1960adb
8e9cb95
d7f7765
18a094c
68be43a
38eaf60
78b55d4
ce4f3f8
978a7ee
775d517
2046ac5
bc87a8c
81f1450
b4c9f32
4c9c876
7123bda
2fcdbc2
68b6bf3
3177b83
6caa377
7e993fd
c15e53d
54a62c2
55df13c
aeca570
47cf8fb
8bc7de5
8f9b7ca
4f91539
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,11 +23,15 @@ <h1>LLM SVG Art Evolution</h1> | |
| <option value="neural">Neural Gardens</option> | ||
| <option value="meditation">Inner Landscapes</option> | ||
| <option value="emergence">Emergent Behaviors</option> | ||
| <option value="digital-nature">Digital Nature</option> | ||
| </select> | ||
|
|
||
| <label for="base-prompt">Animation Prompt:</label> | ||
| <textarea id="base-prompt" rows="4"></textarea> | ||
| <textarea id="base-prompt" rows="5" placeholder="Enter your base prompt here"></textarea> | ||
|
|
||
| <label for="initial-svg">Initial SVG (optional):</label> | ||
| <textarea id="initial-svg" rows="3" placeholder="Enter initial SVG code here (optional)"></textarea> | ||
|
|
||
| <label for="model-select">Model:</label> | ||
| <select id="model-select"></select> | ||
|
|
||
|
|
@@ -49,8 +53,9 @@ <h1>LLM SVG Art Evolution</h1> | |
| </div> | ||
|
|
||
| <div class="controls"> | ||
| <button id="startBtn">Start Evolution</button> | ||
| <button id="stopBtn" disabled>Stop Evolution</button> | ||
| <button id="start" onclick="startEvolution()">Start Evolution</button> | ||
| <button id="stop" onclick="stopEvolution()" disabled>Stop</button> | ||
| <button id="save-preset" onclick="saveCurrentPreset()">Save Preset</button> | ||
| <span id="frame-counter" class="frame-counter">Frame 0/0</span> | ||
| </div> | ||
| </div> | ||
|
|
@@ -69,15 +74,24 @@ <h2>History</h2> | |
| temperatureValue: document.getElementById('temperature-value'), | ||
| seed: document.getElementById('seed'), | ||
| preview: document.getElementById('preview-window'), | ||
| start: document.getElementById('startBtn'), | ||
| stop: document.getElementById('stopBtn'), | ||
| start: document.getElementById('start'), | ||
| stop: document.getElementById('stop'), | ||
| speedSlider: document.getElementById('speed-slider'), | ||
| frameCounter: document.getElementById('frame-counter'), | ||
| history: document.getElementById('history') | ||
| history: document.getElementById('history'), | ||
| initialSvg: document.getElementById('initial-svg') | ||
| }; | ||
|
|
||
| // Presets for SVG generation | ||
| // Load saved presets from localStorage | ||
| const savedPresets = JSON.parse(localStorage.getItem('savedPresets2') || '{}'); | ||
|
|
||
| // Base presets combined with saved presets | ||
| const presets = { | ||
| 'neural': { | ||
| name: 'Neural Gardens', | ||
| prompt: "Create an abstract garden of neural networks. Use flowing lines and organic shapes to represent neurons, synapses, and the flow of information. Incorporate subtle color transitions and pulsing animations to suggest the dynamic nature of neural processing.", | ||
| temperature: 0.8 | ||
| }, | ||
| biomorphic: { | ||
| prompt: "Create a living system of biomorphic forms inspired by Ernst Haeckel's scientific illustrations. Design intricate organic shapes that grow, divide, and evolve. Use animated transformations to suggest cellular processes and biological rhythms. Include delicate details and symmetrical patterns.", | ||
| temperature: 0.9 | ||
|
|
@@ -114,20 +128,29 @@ <h2>History</h2> | |
| prompt: "Design an abstract representation of cosmic architecture - galaxy formations, gravitational lensing, and spacetime fabric. Create animated elements suggesting orbital motion and celestial mechanics. Use deep space colors and geometric patterns inspired by astronomical phenomena.", | ||
| temperature: 0.85 | ||
| }, | ||
| neural: { | ||
| prompt: "Visualize a garden of artificial neural networks as living, breathing entities. Create organic-mechanical hybrid forms with animated synaptic connections and information flows. Show learning and adaptation through evolving patterns and emergent behaviors.", | ||
| temperature: 0.9 | ||
| }, | ||
| meditation: { | ||
| prompt: "Generate abstract visualizations of inner mental states and consciousness. Create flowing mandalas and organic patterns that pulse with meditative rhythms. Use subtle animations and transitions to induce contemplative states. Incorporate sacred geometry and archetypal forms.", | ||
| temperature: 0.8 | ||
| }, | ||
| emergence: { | ||
| prompt: "Design a complex system showing emergent behavior from simple rules. Create animated patterns that suggest flocking birds, forming crystals, or growing cities. Use mathematical principles to generate organic complexity. Show how individual elements combine to create larger organized structures.", | ||
| temperature: 0.85 | ||
| }, | ||
| 'digital-nature': { | ||
| prompt: `Create a mesmerizing fusion of nature and technology: a fractal tree growing from a digital seedling, | ||
| with branches that transform into circuitboard pathways lit by electric blue lightning. | ||
| Matrix-style digital rain falls in the background, but using varying shades of green and cyan. | ||
| The tree should grow slowly and majestically, | ||
| while the lightning pulses should be subtle and ethereal. The digital rain should fall at different speeds, | ||
| creating layers of depth. Color palette should focus on deep greens, electric blues, and cyan accents, | ||
| with occasional golden sparks where the lightning meets the tree branches.`, | ||
| temperature: 0.9 | ||
| } | ||
| }; | ||
|
|
||
| // Add saved presets to base presets | ||
| Object.assign(presets, savedPresets); | ||
|
|
||
| // Fetch available models | ||
| fetch('https://text.pollinations.ai/models') | ||
| .then(r => r.json()) | ||
|
|
@@ -145,9 +168,11 @@ <h2>History</h2> | |
| let isRunning = false; | ||
| let frames = []; | ||
| let frameIndex = 0; | ||
| let currentSeed; | ||
| let currentSeed = 42; | ||
| let initialSeed = 42; | ||
| let lastFrameTime = 0; | ||
| let animationFrame = null; | ||
| let currentState = null; | ||
|
|
||
| function createEmptyCanvas() { | ||
| elements.preview.innerHTML = ` | ||
|
|
@@ -161,8 +186,22 @@ <h2>History</h2> | |
| } | ||
|
|
||
| function extractSvgContent(text) { | ||
| const svgMatch = text.match(/```(?:svg|xml)\n([\s\S]*?)\n```/); | ||
| return svgMatch ? svgMatch[1].trim() : null; | ||
| // Try to match SVG with language specifier | ||
| let svgMatch = text.match(/```(?:svg|xml)\n([\s\S]*?)\n```/); | ||
| if (svgMatch) return svgMatch[1].trim(); | ||
|
|
||
| // Try to match SVG without language specifier | ||
| svgMatch = text.match(/```([\s\S]*?)```/); | ||
| if (svgMatch && svgMatch[1].trim().startsWith('<svg')) { | ||
| return svgMatch[1].trim(); | ||
| } | ||
|
|
||
| // If the text itself starts with <svg, return it directly | ||
| if (text.trim().startsWith('<svg')) { | ||
| return text.trim(); | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| function updateFrame(currentFrame) { | ||
|
|
@@ -189,15 +228,14 @@ <h2>History</h2> | |
| } | ||
|
|
||
| lastFrameTime = performance.now(); | ||
| frameIndex = 0; | ||
|
|
||
| function animate(currentTime) { | ||
| const frameDelay = parseInt(elements.speedSlider.value); | ||
| const elapsed = currentTime - lastFrameTime; | ||
|
|
||
| if (elapsed >= frameDelay && frames.length > 0) { | ||
| updateFrame(frameIndex); | ||
| frameIndex = (frameIndex + 1) % frames.length; | ||
| updateFrame(frameIndex); | ||
| lastFrameTime = currentTime; | ||
| } | ||
|
|
||
|
|
@@ -219,13 +257,14 @@ <h2>History</h2> | |
|
|
||
| const systemPrompt = `You are an animated SVG art generator. Create SVG code that fits within a 800x600 viewBox. | ||
| Follow these rules: | ||
| 0. Your code should be inspired by the demoscene. Self-containted. Small. Re-using elements and groups creatively. | ||
| 1. Always start with <?xml version="1.0" encoding="UTF-8"?> and proper SVG tags | ||
| 2. Dont modify too many things at once if receiving a previous state | ||
| 3. Include style definitions in a <defs> section | ||
| 4. Always ensure the SVG is complete and properly closed | ||
| 5. If evolving from a previous state, introduce only gradual changes! | ||
| 6. Return ONLY the SVG code wrapped in \`\`\`svg code blocks | ||
| 7. use animations abundantly | ||
| 7. animations should be slow and fluid | ||
| 8. please add directions for the next evolution as explanation underneath the svg | ||
|
|
||
| Creative Direction: ${prompt}`; | ||
|
|
@@ -273,6 +312,8 @@ <h2>History</h2> | |
| throw new Error('Incomplete SVG content'); | ||
| } | ||
|
|
||
| console.log(`Response character count: ${text.length}`); | ||
|
|
||
| return svgContent; | ||
|
|
||
| } catch (error) { | ||
|
|
@@ -292,14 +333,14 @@ <h2>History</h2> | |
| const basePrompt = elements.basePrompt.value || preset.prompt; | ||
|
|
||
| try { | ||
| const currentState = frames.length > 0 ? frames[frames.length - 1] : null; | ||
| const evolutionPrompt = currentState | ||
| ? `Evolve this SVG art while maintaining some consistency with the previous state. ${basePrompt}` | ||
| : basePrompt; | ||
|
|
||
| const svgContent = await generateText(evolutionPrompt, currentState); | ||
|
|
||
| if (svgContent) { | ||
| currentState = svgContent; | ||
| frames.push(svgContent); | ||
|
|
||
| // Add to history | ||
|
|
@@ -312,42 +353,60 @@ <h2>History</h2> | |
| `; | ||
| elements.history.appendChild(historyItem); | ||
|
|
||
| // If this is the first frame, start the animation | ||
| // Update frame index to show the latest frame | ||
| frameIndex = frames.length - 1; | ||
| updateFrame(frameIndex); | ||
|
|
||
| // Start animation if it's not already running | ||
| if (frames.length === 1) { | ||
| startPreviewAnimation(); | ||
| } | ||
|
|
||
| // Keep evolving | ||
| if (isRunning) { | ||
| setTimeout(evolve, 1500); | ||
| } | ||
| } else { | ||
| console.error('Failed to generate SVG content'); | ||
| if (isRunning) { | ||
| currentSeed++; | ||
| elements.seed.value = currentSeed; | ||
| setTimeout(evolve, 1500); | ||
| } | ||
| } | ||
| } catch (error) { | ||
| console.error('Evolution error:', error); | ||
| if (isRunning) { | ||
| currentSeed++; | ||
| elements.seed.value = currentSeed; | ||
| setTimeout(evolve, 1500); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function startEvolution() { | ||
| async function startEvolution() { | ||
| if (isRunning) return; | ||
|
|
||
| isRunning = true; | ||
| elements.start.disabled = true; | ||
| elements.stop.disabled = false; | ||
|
|
||
| frames = []; | ||
| frameIndex = 0; | ||
| currentSeed = parseInt(elements.seed.value); | ||
| elements.frameCounter.textContent = 'Frame 0/0'; | ||
| evolve(); | ||
| currentState = null; | ||
|
|
||
| // Reset seed to initial value | ||
| currentSeed = initialSeed; | ||
|
|
||
| if (elements.initialSvg.value?.trim?.()) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The initial SVG validation could be more robust. Currently, if the SVG content is invalid but starts with |
||
| const prompt = elements.basePrompt.value; | ||
| const initialSvg = elements.initialSvg.value.trim() + `\n\nPrompt: ${prompt}`; | ||
| try { | ||
| const svgContent = extractSvgContent(initialSvg); | ||
| if (svgContent) { | ||
| currentState = svgContent; | ||
| frames.push(svgContent); | ||
| updateFrame(frameIndex); | ||
| } | ||
| } catch (error) { | ||
| console.error('Invalid initial SVG:', error); | ||
| } | ||
| } | ||
|
|
||
| await evolve(); | ||
| } | ||
|
|
||
| function stopEvolution() { | ||
|
|
@@ -356,26 +415,89 @@ <h2>History</h2> | |
| elements.stop.disabled = true; | ||
| if (animationFrame) { | ||
| cancelAnimationFrame(animationFrame); | ||
| animationFrame = null; | ||
| } | ||
| // Reset seed to initial value | ||
| currentSeed = initialSeed; | ||
| } | ||
|
|
||
| function getFirstThreeWords(str) { | ||
| return str.trim().split(/\s+/).slice(0, 3).join(' '); | ||
| } | ||
|
|
||
| function saveCurrentPreset() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The preset name is used directly in the DOM without sanitization. While not a critical issue in this context since it's local storage, it's good practice to sanitize user input before using it in HTML. Consider using |
||
| const defaultName = getFirstThreeWords(elements.basePrompt.value); | ||
| const name = prompt('Enter a name for this preset:', defaultName); | ||
| if (!name) return; | ||
|
|
||
| const preset = { | ||
| name, | ||
| prompt: elements.basePrompt.value, | ||
| temperature: elements.temperature.value, | ||
| initialSvg: elements.initialSvg.value, | ||
| model: elements.modelSelect.value, | ||
| seed: currentSeed | ||
| }; | ||
|
|
||
| const savedPresets = JSON.parse(localStorage.getItem('savedPresets2') || '{}'); | ||
| savedPresets[name] = preset; | ||
|
|
||
| localStorage.setItem('savedPresets2', JSON.stringify(savedPresets)); | ||
|
|
||
| // Add to select if not exists | ||
| if (!elements.presetSelect.querySelector(`option[value="${name}"]`)) { | ||
| const option = document.createElement('option'); | ||
| option.value = name; | ||
| option.textContent = name; | ||
| elements.presetSelect.appendChild(option); | ||
| } | ||
|
|
||
| elements.presetSelect.value = name; | ||
| } | ||
|
|
||
| elements.presetSelect.addEventListener('change', () => { | ||
| const preset = { ...presets[elements.presetSelect.value] }; | ||
| console.log('Loading preset:', preset); | ||
| elements.basePrompt.value = preset.prompt; | ||
| elements.temperature.value = preset.temperature; | ||
| elements.temperatureValue.textContent = preset.temperature; | ||
| if (preset.initialSvg) { | ||
| elements.initialSvg.value = preset.initialSvg; | ||
| } | ||
| if (preset.model) { | ||
| elements.modelSelect.value = preset.model; | ||
| } | ||
| if (preset.seed) { | ||
| currentSeed = preset.seed; | ||
| initialSeed = preset.seed; | ||
| } else { | ||
| currentSeed = 42; | ||
| initialSeed = 42; | ||
| } | ||
| }); | ||
|
|
||
| // Event Listeners | ||
| elements.start.addEventListener('click', startEvolution); | ||
| elements.stop.addEventListener('click', stopEvolution); | ||
| elements.temperature.addEventListener('input', (e) => { | ||
| elements.temperatureValue.textContent = e.target.value; | ||
| }); | ||
| elements.presetSelect.addEventListener('change', (e) => { | ||
| const preset = presets[e.target.value]; | ||
| elements.basePrompt.value = preset.prompt; | ||
| elements.temperature.value = preset.temperature; | ||
| elements.temperatureValue.textContent = preset.temperature; | ||
| }); | ||
|
|
||
| // Initialize | ||
| createEmptyCanvas(); | ||
| elements.basePrompt.value = presets[elements.presetSelect.value].prompt; | ||
| document.addEventListener('DOMContentLoaded', () => { | ||
| // Add saved presets to select | ||
| Object.entries(savedPresets).forEach(([key, preset]) => { | ||
| const option = document.createElement('option'); | ||
| option.value = key; | ||
| option.textContent = preset.name; | ||
| elements.presetSelect.appendChild(option); | ||
| }); | ||
| createEmptyCanvas(); | ||
| elements.presetSelect.value = 'digital-nature'; | ||
| elements.basePrompt.value = presets['digital-nature'].prompt; | ||
| if (presets['digital-nature'].initialSvg) { | ||
| elements.initialSvg.value = presets['digital-nature'].initialSvg; | ||
| } | ||
| }); | ||
| </script> | ||
| </body> | ||
| </html> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The animation frame timing could lead to inconsistent frame rates when browser tab is inactive or system is under load. Consider using
requestAnimationFrame's timestamp parameter more effectively by tracking time deltas between frames. Here's how: