Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
151 changes: 105 additions & 46 deletions svg-feedback/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 = `
Expand All @@ -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) {
Expand All @@ -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) {
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 Down Expand Up @@ -294,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 @@ -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
Expand All @@ -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?.()) {
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() {
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() {
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 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', () => {
Expand All @@ -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>
Expand Down
40 changes: 38 additions & 2 deletions svg-feedback/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,31 @@ button:disabled {
cursor: not-allowed;
}

#preview {
width: 100%;
aspect-ratio: 1;
border: 1px solid #ccc;
margin-bottom: 1rem;
overflow: hidden;
}

#history {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin-top: 5px;
gap: 10px;
margin-top: 20px;
overflow: hidden;
}

.history-item {
border: 1px solid #ccc;
padding: 5px;
cursor: pointer;
overflow: hidden;
}

.history-item > div {
overflow: hidden;
}

.history-item {
Expand Down Expand Up @@ -215,3 +235,19 @@ input[type="range"] {
font-family: monospace;
margin-left: 10px;
}

textarea {
width: 100%;
margin-bottom: 1rem;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
font-family: monospace;
}

#initial-svg {
font-size: 0.9em;
background-color: #1a1a1a;
border-color: #333;
color: #aaa;
}
Loading