Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1960adb
Add prompt guessing game initial structure
mentatbot[bot] Jan 16, 2025
8e9cb95
Implement prompt guessing game functionality
mentatbot[bot] Jan 16, 2025
d7f7765
Add game instructions and hint system
mentatbot[bot] Jan 16, 2025
18a094c
Fix build configuration and directory structure
mentatbot[bot] Jan 16, 2025
68be43a
Update deploy-apps.yml
voodoohop Jan 17, 2025
38eaf60
Update utils.js
voodoohop Jan 17, 2025
78b55d4
Update vite.config.js
voodoohop Jan 17, 2025
ce4f3f8
Merge branch 'main' into mentat-140-1-prompt-guessing-game
voodoohop Jan 17, 2025
978a7ee
Update vite.config.js
voodoohop Jan 17, 2025
775d517
Update vite.config.js
voodoohop Jan 17, 2025
2046ac5
Update vite.config.js
voodoohop Jan 17, 2025
bc87a8c
Crop
voodoohop Jan 17, 2025
81f1450
Add svg feedback experiment
voodoohop Jan 17, 2025
b4c9f32
Update index.html
voodoohop Jan 17, 2025
4c9c876
Update index.html
voodoohop Jan 17, 2025
7123bda
Update index.html
voodoohop Jan 17, 2025
2fcdbc2
Update index.html
voodoohop Jan 17, 2025
68b6bf3
Merge branch 'main' into mentat-140-1-prompt-guessing-game
voodoohop Jan 17, 2025
3177b83
Update index.html
voodoohop Jan 17, 2025
6caa377
Merge branch 'main' into mentat-140-1-prompt-guessing-game
voodoohop Jan 17, 2025
7e993fd
Beautiful version
voodoohop Jan 17, 2025
c15e53d
Update index.html
voodoohop Jan 17, 2025
54a62c2
Merge branch 'main' into mentat-140-1-prompt-guessing-game
voodoohop Jan 17, 2025
55df13c
Update index.html
voodoohop Jan 17, 2025
aeca570
initial svg state
voodoohop Jan 17, 2025
47cf8fb
save preset too
voodoohop Jan 17, 2025
8bc7de5
Update index.html
voodoohop Jan 17, 2025
8f9b7ca
Update index.html
voodoohop Jan 17, 2025
4f91539
Merge branch 'main' into mentat-140-1-prompt-guessing-game
voodoohop Jan 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 161 additions & 39 deletions svg-feedback/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand All @@ -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>
Expand All @@ -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
Expand Down Expand Up @@ -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())
Expand All @@ -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 = `
Expand All @@ -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) {
Expand All @@ -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) {
Copy link
Contributor

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:

function animate(currentTime) {
    if (!lastFrameTime) lastFrameTime = currentTime;
    const elapsed = currentTime - lastFrameTime;
    // ... rest of the animation logic
}

updateFrame(frameIndex);
frameIndex = (frameIndex + 1) % frames.length;
updateFrame(frameIndex);
lastFrameTime = currentTime;
}

Expand All @@ -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}`;
Expand Down Expand Up @@ -273,6 +312,8 @@ <h2>History</h2>
throw new Error('Incomplete SVG content');
}

console.log(`Response character count: ${text.length}`);

return svgContent;

} catch (error) {
Expand All @@ -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
Expand All @@ -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?.()) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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 <svg>, it will still be accepted. Consider adding more validation like checking for required SVG attributes (xmlns, viewBox) and basic structure validity.

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() {
Expand All @@ -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() {
Copy link
Contributor

Choose a reason for hiding this comment

The 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 textContent instead of direct HTML insertion and validate the preset name format.

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>
Loading