-
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 all 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 |
|---|---|---|
|
|
@@ -27,8 +27,11 @@ <h1>LLM SVG Art Evolution</h1> | |
| </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> | ||
|
|
||
|
|
@@ -75,11 +78,12 @@ <h2>History</h2> | |
| 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') | ||
| }; | ||
|
|
||
| // Load saved presets from localStorage | ||
| const savedPresets = JSON.parse(localStorage.getItem('savedPresets') || '{}'); | ||
| const savedPresets = JSON.parse(localStorage.getItem('savedPresets2') || '{}'); | ||
|
|
||
| // Base presets combined with saved presets | ||
| const presets = { | ||
|
|
@@ -164,10 +168,11 @@ <h2>History</h2> | |
| let isRunning = false; | ||
| let frames = []; | ||
| let frameIndex = 0; | ||
| let currentSeed; | ||
| let initialSeed; | ||
| let currentSeed = 42; | ||
| let initialSeed = 42; | ||
| let lastFrameTime = 0; | ||
| let animationFrame = null; | ||
| let currentState = null; | ||
|
|
||
| function createEmptyCanvas() { | ||
| elements.preview.innerHTML = ` | ||
|
|
@@ -181,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) { | ||
|
|
@@ -209,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; | ||
| } | ||
|
|
||
|
|
@@ -294,6 +312,8 @@ <h2>History</h2> | |
| throw new Error('Incomplete SVG content'); | ||
| } | ||
|
|
||
| console.log(`Response character count: ${text.length}`); | ||
|
|
||
| return svgContent; | ||
|
|
||
| } catch (error) { | ||
|
|
@@ -313,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 | ||
|
|
@@ -333,98 +353,134 @@ <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; | ||
| initialSeed = parseInt(elements.seed.value); | ||
| currentState = null; | ||
|
|
||
| // Reset seed to initial value | ||
| currentSeed = initialSeed; | ||
| elements.frameCounter.textContent = 'Frame 0/0'; | ||
| evolve(); | ||
|
|
||
| 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() { | ||
| isRunning = false; | ||
| elements.start.disabled = false; | ||
| elements.stop.disabled = true; | ||
| currentSeed = initialSeed; | ||
| elements.seed.value = initialSeed; | ||
| if (animationFrame) { | ||
| cancelAnimationFrame(animationFrame); | ||
| animationFrame = null; | ||
| } | ||
| // Reset seed to initial value | ||
| currentSeed = initialSeed; | ||
| } | ||
|
|
||
| function getFirstFourWords(str) { | ||
| return str.split(/\s+/).slice(0, 4).join(' ').toLowerCase().replace(/[^\w\s-]/g, ''); | ||
| 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 prompt = elements.basePrompt.value; | ||
| const temperature = parseFloat(elements.temperature.value); | ||
| const key = getFirstFourWords(prompt); | ||
| const defaultName = getFirstThreeWords(elements.basePrompt.value); | ||
| const name = prompt('Enter a name for this preset:', defaultName); | ||
| if (!name) return; | ||
|
|
||
| const savedPresets = JSON.parse(localStorage.getItem('savedPresets') || '{}'); | ||
| savedPresets[key] = { | ||
| prompt, | ||
| temperature, | ||
| name: key | ||
| const preset = { | ||
| name, | ||
| prompt: elements.basePrompt.value, | ||
| temperature: elements.temperature.value, | ||
| initialSvg: elements.initialSvg.value, | ||
| model: elements.modelSelect.value, | ||
| seed: currentSeed | ||
| }; | ||
|
|
||
| localStorage.setItem('savedPresets', JSON.stringify(savedPresets)); | ||
| 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="${key}"]`)) { | ||
| if (!elements.presetSelect.querySelector(`option[value="${name}"]`)) { | ||
| const option = document.createElement('option'); | ||
| option.value = key; | ||
| option.textContent = key; | ||
| option.value = name; | ||
| option.textContent = name; | ||
| elements.presetSelect.appendChild(option); | ||
| } | ||
|
|
||
| elements.presetSelect.value = key; | ||
| 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 | ||
| document.addEventListener('DOMContentLoaded', () => { | ||
|
|
@@ -438,6 +494,9 @@ <h2>History</h2> | |
| 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> | ||
|
|
||
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: