Skip to content

Commit 170afb9

Browse files
authored
Merge branch 'release-1.0' into docs-bedtime-storyteller
2 parents 1f33010 + 726530c commit 170afb9

File tree

27 files changed

+387
-143
lines changed

27 files changed

+387
-143
lines changed

examples/bedtime-story-teller/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,4 @@ socket.on('stream_end', () => {
166166
storyResponse.innerHTML = storyBuffer; // Final render
167167
document.getElementById('loading-spinner').style.display = 'none';
168168
});
169-
```
169+
```

examples/bedtime-story-teller/assets/app.js

Lines changed: 170 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,61 @@
1-
// SPDX-FileCopyrightText: Copyright (C) 2025 ARDUINO SA <http://www.arduino.cc>
1+
// SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
22
//
33
// SPDX-License-Identifier: MPL-2.0
44

55
const socket = io(`http://${window.location.host}`);
66

7+
let generateStoryButtonOriginalHTML = ''; // To store the original content of the generate story button
8+
let storyBuffer = '';
9+
710

811

912
function initSocketIO() {
10-
socket.on('response', (data) => {
11-
document.getElementById('story-container').style.display = 'flex';
12-
const storyResponse = document.getElementById('story-response');
13-
storyResponse.textContent = data;
14-
document.getElementById('loading-spinner').style.display = 'none';
15-
const clearStoryButton = document.getElementById('clear-story-button');
16-
clearStoryButton.style.display = 'block';
17-
clearStoryButton.disabled = false;
13+
socket.on('prompt', (data) => {
14+
const promptContainer = document.getElementById('prompt-container');
15+
const promptDisplay = document.getElementById('prompt-display');
16+
promptDisplay.innerHTML = data;
17+
promptContainer.style.display = 'block';
1818
});
19+
20+
socket.on('response', (data) => {
21+
22+
document.getElementById('story-container').style.display = 'flex';
23+
24+
storyBuffer += data;
25+
26+
});
27+
28+
29+
30+
socket.on('stream_end', () => {
31+
32+
const storyResponse = document.getElementById('story-response');
33+
34+
storyResponse.innerHTML = storyBuffer;
35+
36+
37+
38+
document.getElementById('loading-spinner').style.display = 'none';
39+
40+
const clearStoryButton = document.getElementById('clear-story-button');
41+
42+
clearStoryButton.style.display = 'block';
43+
44+
clearStoryButton.disabled = false;
45+
46+
47+
48+
const generateStoryButton = document.querySelector('.generate-story-button');
49+
50+
if (generateStoryButton) {
51+
52+
generateStoryButton.disabled = false;
53+
54+
generateStoryButton.innerHTML = generateStoryButtonOriginalHTML; // Restore original content
55+
56+
}
57+
58+
});
1959
}
2060

2161
function unlockAndOpenNext(currentContainer) {
@@ -33,6 +73,12 @@ function unlockAndOpenNext(currentContainer) {
3373
}
3474
}
3575

76+
function getRandomElement(elements) {
77+
if (elements.length === 0) return null;
78+
const randomIndex = Math.floor(Math.random() * elements.length);
79+
return elements[randomIndex];
80+
}
81+
3682
function setupChipSelection(container) {
3783
const chips = container.querySelectorAll('.chip');
3884
const selectedValue = container.querySelector('.selected-value');
@@ -49,6 +95,13 @@ function setupChipSelection(container) {
4995
if (!alreadySelected) {
5096
unlockAndOpenNext(container);
5197
}
98+
99+
// Collapse the current container
100+
const content = container.querySelector('.parameter-content');
101+
const arrow = container.querySelector('.arrow-icon');
102+
content.style.display = 'none';
103+
arrow.classList.remove('rotated');
104+
52105
});
53106
});
54107
}
@@ -129,6 +182,17 @@ function checkCharactersAndUnlockNext(charactersContainer) {
129182
}
130183

131184
function gatherDataAndGenerateStory() {
185+
document.querySelectorAll('.parameter-container').forEach(container => {
186+
const content = container.querySelector('.parameter-content');
187+
if (content && content.style.display === 'block') {
188+
content.style.display = 'none';
189+
const arrow = container.querySelector('.arrow-icon');
190+
if (arrow) {
191+
arrow.classList.remove('rotated');
192+
}
193+
}
194+
});
195+
132196
const age = document.querySelector('.parameter-container:nth-child(1) .chip.selected')?.textContent.trim() || 'any';
133197
const theme = document.querySelector('.parameter-container:nth-child(2) .chip.selected')?.textContent.trim() || 'any';
134198
const storyTypeContainer = document.querySelector('.parameter-container:nth-child(3)');
@@ -168,9 +232,20 @@ function generateStory(data) {
168232
document.querySelector('.story-output-placeholder').style.display = 'none';
169233
const responseArea = document.getElementById('story-response-area');
170234
responseArea.style.display = 'flex';
235+
document.getElementById('prompt-container').style.display = 'none';
236+
document.getElementById('prompt-display').textContent = '';
171237
document.getElementById('story-container').style.display = 'none';
172-
document.getElementById('loading-spinner').style.display = 'block';
173-
document.getElementById('story-response').textContent = '';
238+
document.getElementById('story-response').innerHTML = ''; // Use innerHTML to clear
239+
storyBuffer = ''; // Reset buffer
240+
document.getElementById('loading-spinner').style.display = 'block'; // Show the general loading spinner
241+
242+
const generateStoryButton = document.querySelector('.generate-story-button');
243+
if (generateStoryButton) {
244+
generateStoryButton.disabled = true;
245+
// Append the spinner instead of replacing innerHTML
246+
generateStoryButton.innerHTML += '<div class="button-spinner spinner"></div>';
247+
}
248+
174249
document.getElementById('clear-story-button').style.display = 'none';
175250
socket.emit('generate_story', data);
176251
}
@@ -223,10 +298,12 @@ function resetStoryView() {
223298
}
224299
}
225300

226-
// Hide "Generate story" button
301+
// Restore "Generate story" button to original state
227302
const generateStoryButton = document.querySelector('.generate-story-button');
228303
if (generateStoryButton) {
229-
generateStoryButton.style.display = 'none';
304+
generateStoryButton.style.display = 'none'; // Keep hidden if no chars, will be set to flex by checkCharactersAndUnlockNext
305+
generateStoryButton.disabled = false;
306+
generateStoryButton.innerHTML = generateStoryButtonOriginalHTML;
230307
}
231308

232309
// Reset parameter containers state
@@ -240,7 +317,9 @@ function resetStoryView() {
240317
arrow.classList.add('rotated');
241318
container.classList.remove('disabled');
242319
} else {
243-
container.classList.add('disabled');
320+
if (container.id !== 'prompt-container') {
321+
container.classList.add('disabled');
322+
}
244323
content.style.display = 'none';
245324
arrow.classList.remove('rotated');
246325
}
@@ -250,6 +329,11 @@ function resetStoryView() {
250329
document.addEventListener('DOMContentLoaded', () => {
251330
initSocketIO();
252331

332+
const generateStoryButton = document.querySelector('.generate-story-button');
333+
if (generateStoryButton) {
334+
generateStoryButtonOriginalHTML = generateStoryButton.innerHTML; // Store original content
335+
}
336+
253337
const parameterContainers = document.querySelectorAll('.parameter-container');
254338

255339
parameterContainers.forEach((container, index) => {
@@ -259,7 +343,9 @@ document.addEventListener('DOMContentLoaded', () => {
259343
content.style.display = 'block';
260344
arrow.classList.add('rotated');
261345
} else {
262-
container.classList.add('disabled');
346+
if (container.id !== 'prompt-container') {
347+
container.classList.add('disabled');
348+
}
263349
}
264350
});
265351

@@ -360,18 +446,77 @@ document.addEventListener('DOMContentLoaded', () => {
360446
});
361447

362448
document.getElementById('copy-story-button').addEventListener('click', () => {
363-
const storyText = document.getElementById('story-response').textContent;
364-
navigator.clipboard.writeText(storyText).then(() => {
365-
const copyButton = document.getElementById('copy-story-button');
366-
const originalHTML = copyButton.innerHTML;
449+
const storyText = document.getElementById('story-response').innerText;
450+
const copyButton = document.getElementById('copy-story-button');
451+
const originalHTML = copyButton.innerHTML;
452+
const textarea = document.createElement('textarea');
453+
textarea.value = storyText;
454+
document.body.appendChild(textarea);
455+
textarea.select();
456+
try {
457+
document.execCommand('copy');
367458
copyButton.textContent = 'Copied!';
368459
copyButton.disabled = true;
369-
setTimeout(() => {
370-
copyButton.innerHTML = originalHTML;
371-
copyButton.disabled = false;
372-
}, 2000);
373-
}, (err) => {
460+
} catch (err) {
374461
console.error('Could not copy text: ', err);
375-
});
462+
}
463+
document.body.removeChild(textarea);
464+
465+
setTimeout(() => {
466+
copyButton.innerHTML = originalHTML;
467+
copyButton.disabled = false;
468+
}, 2000);
469+
});
470+
471+
document.getElementById('generate-randomly-button').addEventListener('click', () => {
472+
// Age
473+
const ageChips = document.querySelectorAll('.parameter-container:nth-child(1) .chip');
474+
const randomAgeChip = getRandomElement(ageChips);
475+
const age = randomAgeChip ? randomAgeChip.textContent.trim() : 'any';
476+
477+
// Theme
478+
const themeChips = document.querySelectorAll('.parameter-container:nth-child(2) .chip');
479+
const randomThemeChip = getRandomElement(themeChips);
480+
const theme = randomThemeChip ? randomThemeChip.textContent.trim() : 'any';
481+
482+
// Story Type
483+
const storyTypeContainer = document.querySelector('.parameter-container:nth-child(3)');
484+
485+
// Tone
486+
const toneChips = storyTypeContainer.querySelectorAll('.story-type-paragraph:nth-child(1) .chip');
487+
const randomToneChip = getRandomElement(toneChips);
488+
const tone = randomToneChip ? randomToneChip.textContent.trim() : 'any';
489+
490+
// Ending type
491+
const endingTypeChips = storyTypeContainer.querySelectorAll('.story-type-paragraph:nth-child(2) .chip');
492+
const randomEndingTypeChip = getRandomElement(endingTypeChips);
493+
const endingType = randomEndingTypeChip ? randomEndingTypeChip.textContent.trim() : 'any';
494+
495+
// Narrative structure
496+
const narrativeStructureChips = storyTypeContainer.querySelectorAll('.story-type-paragraph:nth-child(3) .chip');
497+
const randomNarrativeStructureChip = getRandomElement(narrativeStructureChips);
498+
const narrativeStructure = randomNarrativeStructureChip ? randomNarrativeStructureChip.textContent.trim() : 'any';
499+
500+
// Duration
501+
const durationChips = storyTypeContainer.querySelectorAll('.story-type-paragraph:nth-child(4) .chip');
502+
const randomDurationChip = getRandomElement(durationChips);
503+
const duration = randomDurationChip ? randomDurationChip.textContent.trim() : 'any';
504+
505+
// Characters and Other will be empty for random generation.
506+
const characters = [];
507+
const other = '';
508+
509+
const storyData = {
510+
age,
511+
theme,
512+
tone,
513+
endingType,
514+
narrativeStructure,
515+
duration,
516+
characters,
517+
other,
518+
};
519+
520+
generateStory(storyData);
376521
});
377522
});

examples/bedtime-story-teller/assets/index.html

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!--
2-
SPDX-FileCopyrightText: Copyright (C) 2025 ARDUINO SA <http://www.arduino.cc>
2+
SPDX-FileCopyrightText: Copyright (C) ARDUINO SRL (http://www.arduino.cc)
33
44
SPDX-License-Identifier: MPL-2.0
55
-->
@@ -71,7 +71,7 @@ <h3 class="parameter-title">Theme</h3>
7171
<div class="parameter-container">
7272
<div class="parameter-header">
7373
<h3 class="parameter-title">Story type</h3>
74-
<span class="optional-text">(optional)</span>
74+
<span class="optional-text story-type-selections">(optional)</span>
7575
<img src="./img/arrow-right.svg" class="arrow-icon" alt="arrow icon">
7676
</div>
7777
<div class="parameter-content">
@@ -172,9 +172,20 @@ <h3 class="parameter-title">Other</h3>
172172
<div class="story-output-container">
173173
<div class="story-output-placeholder">
174174
<p>Set parameters to generate story</p>
175+
<p>or</p>
176+
<button id="generate-randomly-button" class="generate-randomly-button">Generate Randomly</button>
175177
</div>
176178
<div id="story-response-area" style="display: none;">
177179
<div id="loading-spinner" class="spinner" style="display: none;"></div>
180+
<div class="parameter-container" id="prompt-container" style="display: none;">
181+
<div class="parameter-header">
182+
<h3 class="parameter-title">Prompt</h3>
183+
<img src="./img/arrow-right.svg" class="arrow-icon" alt="arrow icon">
184+
</div>
185+
<div class="parameter-content">
186+
<div id="prompt-display" class="response-content"></div>
187+
</div>
188+
</div>
178189
<div class="response-container" id="story-container" style="display: none;">
179190
<div class="response-header">
180191
<h3 class="response-title">Story</h3>
@@ -196,12 +207,18 @@ <h3 class="response-title">Story</h3>
196207
<div id="new-story-modal" class="modal" style="display: none;">
197208
<div class="modal-content">
198209
<div class="modal-header">
199-
<h2>New Story</h2>
210+
<h2 class="modal-body-title">New Story</h2>
200211
<span class="close-button">&times;</span>
201212
</div>
202213
<div class="modal-body">
203214
<p>Creating a new story will delete the existing one, Are you sure? This action cannot be undone.</p>
204215
</div>
216+
<div class="modal-body">
217+
<p>Are you sure?</p>
218+
</div>
219+
<div class="modal-body-small">
220+
<p>This action cannot be undone.</p>
221+
</div>
205222
<div class="modal-footer">
206223
<button id="confirm-new-story-button" class="primary-button">Create new story</button>
207224
</div>

0 commit comments

Comments
 (0)