Skip to content

Commit 25df599

Browse files
committed
update UI
1 parent fe67714 commit 25df599

File tree

4 files changed

+350
-75
lines changed

4 files changed

+350
-75
lines changed

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

Lines changed: 118 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,32 @@
44

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

7+
function generateRandomTestStory() {
8+
document.querySelector('.story-output-placeholder').style.display = 'none';
9+
const responseArea = document.getElementById('story-response-area');
10+
responseArea.style.display = 'flex';
11+
document.getElementById('prompt-container').style.display = 'none';
12+
document.getElementById('story-container').style.display = 'none';
13+
document.getElementById('loading-spinner').style.display = 'block';
14+
document.getElementById('story-response').textContent = '';
15+
document.getElementById('clear-story-button').style.display = 'none';
16+
setTimeout(() => {
17+
const randomStory = `Once upon a time, in a land far, far away, there lived a brave ${Math.random() > 0.5 ? 'knight' : 'princess'}. They embarked on a quest to find a magical ${Math.random() > 0.5 ? 'dragon' : 'unicorn'} and save their kingdom from a wicked ${Math.random() > 0.5 ? 'sorcerer' : 'giant'}. After many adventures and challenges, they succeeded and lived happily ever after. The end.`;
18+
document.getElementById('story-container').style.display = 'flex';
19+
const storyResponse = document.getElementById('story-response');
20+
storyResponse.textContent += randomStory;
21+
document.getElementById('loading-spinner').style.display = 'none';
22+
document.getElementById('clear-story-button').style.display = 'block';
23+
}, 1500);
24+
}
25+
726
function initSocketIO() {
827
socket.on('response', (data) => {
9-
const responseBox = document.getElementById('promptResponse');
10-
responseBox.textContent += data;
11-
responseBox.style.display = 'block';
12-
document.getElementById('loadingSpinner').style.display = 'none';
13-
document.getElementById('clearStoryButton').disabled = false;
28+
document.getElementById('story-container').style.display = 'flex';
29+
const storyResponse = document.getElementById('story-response');
30+
storyResponse.textContent += data;
31+
document.getElementById('loading-spinner').style.display = 'none';
32+
document.getElementById('clear-story-button').style.display = 'block';
1433
});
1534
}
1635

@@ -32,21 +51,16 @@ function unlockAndOpenNext(currentContainer) {
3251
function setupChipSelection(container) {
3352
const chips = container.querySelectorAll('.chip');
3453
const selectedValue = container.querySelector('.selected-value');
35-
3654
chips.forEach(chip => {
3755
chip.addEventListener('click', (event) => {
3856
event.stopPropagation();
39-
4057
const alreadySelected = chip.classList.contains('selected');
41-
4258
chips.forEach(c => c.classList.remove('selected'));
4359
chip.classList.add('selected');
44-
4560
if (selectedValue) {
4661
selectedValue.innerHTML = chip.innerHTML;
4762
selectedValue.style.display = 'inline-flex';
4863
}
49-
5064
if (!alreadySelected) {
5165
unlockAndOpenNext(container);
5266
}
@@ -56,21 +70,15 @@ function setupChipSelection(container) {
5670

5771
function setupStoryTypeSelection(container) {
5872
const paragraphs = container.querySelectorAll('.story-type-paragraph');
59-
6073
paragraphs.forEach(paragraph => {
6174
const chips = paragraph.querySelectorAll('.chip');
6275
chips.forEach(chip => {
6376
chip.addEventListener('click', (event) => {
6477
event.stopPropagation();
65-
66-
// Allow only one selection per paragraph
6778
const paragraphChips = paragraph.querySelectorAll('.chip');
6879
paragraphChips.forEach(c => c.classList.remove('selected'));
6980
chip.classList.add('selected');
70-
7181
updateStoryTypeHeader(container);
72-
73-
// Check if all subcategories have a selection
7482
const selectedChips = container.querySelectorAll('.chip.selected');
7583
if (selectedChips.length === paragraphs.length) {
7684
unlockAndOpenNext(container);
@@ -85,14 +93,11 @@ function updateStoryTypeHeader(container) {
8593
const selectedChips = container.querySelectorAll('.chip.selected');
8694
const content = container.querySelector('.parameter-content');
8795
const isOpen = content.style.display === 'block';
88-
89-
optionalText.innerHTML = ''; // Clear previous content
90-
96+
optionalText.innerHTML = '';
9197
if (selectedChips.length === 0) {
9298
optionalText.textContent = '(optional)';
9399
return;
94100
}
95-
96101
if (isOpen) {
97102
Array.from(selectedChips).forEach(chip => {
98103
const pill = document.createElement('span');
@@ -108,12 +113,11 @@ function updateStoryTypeHeader(container) {
108113
pill.innerHTML = chip.innerHTML;
109114
optionalText.appendChild(pill);
110115
});
111-
112116
const remaining = selectedChips.length - 2;
113117
if (remaining > 0) {
114118
const plusSpan = document.createElement('span');
115119
plusSpan.className = 'plus-x';
116-
plusSpan.style.display = 'inline-block'; // make it visible
120+
plusSpan.style.display = 'inline-block';
117121
plusSpan.textContent = `+${remaining}`;
118122
optionalText.appendChild(plusSpan);
119123
}
@@ -130,7 +134,6 @@ function checkCharactersAndUnlockNext(charactersContainer) {
130134
atLeastOneCharacterEntered = true;
131135
}
132136
});
133-
134137
const generateButton = document.querySelector('.generate-story-button');
135138
if (atLeastOneCharacterEntered) {
136139
unlockAndOpenNext(charactersContainer);
@@ -140,14 +143,66 @@ function checkCharactersAndUnlockNext(charactersContainer) {
140143
}
141144
}
142145

146+
function gatherDataAndGenerateStory() {
147+
const age = document.querySelector('.parameter-container:nth-child(1) .chip.selected')?.textContent.trim() || 'any';
148+
const theme = document.querySelector('.parameter-container:nth-child(2) .chip.selected')?.textContent.trim() || 'any';
149+
const storyTypeContainer = document.querySelector('.parameter-container:nth-child(3)');
150+
const tone = storyTypeContainer.querySelector('.story-type-paragraph:nth-child(1) .chip.selected')?.textContent.trim() || 'any';
151+
const endingType = storyTypeContainer.querySelector('.story-type-paragraph:nth-child(2) .chip.selected')?.textContent.trim() || 'any';
152+
const narrativeStructure = storyTypeContainer.querySelector('.story-type-paragraph:nth-child(3) .chip.selected')?.textContent.trim() || 'any';
153+
const duration = storyTypeContainer.querySelector('.story-type-paragraph:nth-child(4) .chip.selected')?.textContent.trim() || 'any';
154+
const characters = [];
155+
const characterGroups = document.querySelectorAll('.character-input-group');
156+
characterGroups.forEach(group => {
157+
const name = group.querySelector('.character-name').value.trim();
158+
const role = group.querySelector('.character-role').value;
159+
const description = group.querySelector('.character-description').value.trim();
160+
if (name && role) {
161+
characters.push({ name, role, description });
162+
}
163+
});
164+
const protagonist = characters.find(c => c.role === 'protagonist');
165+
const helper = characters.find(c => c.role === 'positive-helper');
166+
const antagonist = characters.find(c => c.role === 'antagonist');
167+
const other = document.querySelector('.other-textarea').value.trim();
168+
const formattedPrompt = `As a parent who loves to read bedtime stories to my <strong>${age}</strong> year old child, I need a delightful and age-appropriate story about an <strong>${protagonist ? protagonist.description : ''}</strong>, <strong>${protagonist ? protagonist.name : 'a character'}</strong> accompanied by his <strong>${helper ? helper.description : ''}</strong> helper <strong>${helper ? helper.name : 'a friend'}</strong> who will have to face the <strong>${antagonist ? antagonist.description : ''}</strong> antagonist <strong>${antagonist ? antagonist.name : 'a villain'}</strong>. The story type is <strong>${theme}</strong>. The tone should be <strong>${tone}</strong>. The format should be a narrative-style story with a clear beginning, middle, and end, allowing for a smooth and engaging reading experience. The objective is to entertain and soothe the child before bedtime. Provide a brief introduction to set the scene and introduce the main character. The scope should revolve around the topic: managing emotions and conflicts. The length should be approximately <strong>${duration}</strong>. Please ensure the story has a <strong>${narrativeStructure}</strong> narrative structure, leaving the child with a sense of <strong>${endingType}</strong>. The language should be easy to understand and suitable for my child's age comprehension.
169+
${other ? `
170+
171+
Other on optional stuff for the story: <strong>${other}</strong>` : ''}`;
172+
document.getElementById('prompt-display').innerHTML = formattedPrompt;
173+
document.getElementById('prompt-container').style.display = 'flex';
174+
const rawPrompt = formattedPrompt.replace(/<strong>/g, '').replace(/<\/strong>/g, '');
175+
generateStory(rawPrompt);
176+
}
177+
178+
function generateStory(msg) {
179+
document.querySelector('.story-output-placeholder').style.display = 'none';
180+
const responseArea = document.getElementById('story-response-area');
181+
responseArea.style.display = 'flex';
182+
document.getElementById('story-container').style.display = 'none';
183+
document.getElementById('loading-spinner').style.display = 'block';
184+
document.getElementById('story-response').textContent = '';
185+
document.getElementById('clear-story-button').style.display = 'none';
186+
socket.emit('generate_story', msg);
187+
}
188+
189+
function resetStoryView() {
190+
document.querySelector('.story-output-placeholder').style.display = 'flex';
191+
const responseArea = document.getElementById('story-response-area');
192+
responseArea.style.display = 'none';
193+
document.getElementById('prompt-container').style.display = 'none';
194+
document.getElementById('story-container').style.display = 'none';
195+
document.getElementById('prompt-display').innerHTML = '';
196+
document.getElementById('story-response').textContent = '';
197+
}
198+
143199
document.addEventListener('DOMContentLoaded', () => {
144200
initSocketIO();
145201

146202
const parameterContainers = document.querySelectorAll('.parameter-container');
147203

148-
// Initial setup for sequential containers
149204
parameterContainers.forEach((container, index) => {
150-
if (index === 0) { // First container (Age)
205+
if (index === 0) {
151206
const content = container.querySelector('.parameter-content');
152207
const arrow = container.querySelector('.arrow-icon');
153208
content.style.display = 'block';
@@ -160,35 +215,29 @@ document.addEventListener('DOMContentLoaded', () => {
160215
parameterContainers.forEach(container => {
161216
const title = container.querySelector('.parameter-title').textContent;
162217
const header = container.querySelector('.parameter-header');
163-
164218
header.addEventListener('click', () => {
165219
if (container.classList.contains('disabled')) return;
166-
167220
const content = container.querySelector('.parameter-content');
168221
const arrow = container.querySelector('.arrow-icon');
169-
170222
arrow.classList.toggle('rotated');
171223
if (content.style.display === 'block') {
172224
content.style.display = 'none';
173225
} else {
174226
content.style.display = 'block';
175227
}
176-
177228
if (title === 'Story type') {
178229
updateStoryTypeHeader(container);
179230
} else if (title === 'Other') {
180231
const textarea = container.querySelector('.other-textarea');
181232
const charCounter = container.querySelector('.char-counter');
182233
const maxLength = textarea.maxLength;
183-
184234
textarea.addEventListener('input', () => {
185235
const currentLength = textarea.value.length;
186236
charCounter.textContent = `${currentLength} / ${maxLength}`;
187237
});
188238
}
189239
});
190240

191-
// Setup interaction listeners for unlocking the next container
192241
if (title === 'Story type') {
193242
setupStoryTypeSelection(container);
194243
} else if (title === 'Characters') {
@@ -209,72 +258,69 @@ document.addEventListener('DOMContentLoaded', () => {
209258
const addCharacterButton = document.querySelector('.add-character-button');
210259
const charactersList = document.querySelector('.characters-list');
211260
const characterInputGroup = document.querySelector('.character-input-group');
212-
213261
addCharacterButton.addEventListener('click', () => {
214262
const characterGroups = document.querySelectorAll('.character-input-group');
215263
if (characterGroups.length < 5) {
216264
const newCharacterGroup = characterInputGroup.cloneNode(true);
217265
newCharacterGroup.querySelector('.character-name').value = '';
218266
newCharacterGroup.querySelector('.character-role').selectedIndex = 0;
219267
newCharacterGroup.querySelector('.character-description').value = '';
220-
221268
const deleteButton = newCharacterGroup.querySelector('.delete-character-button');
222269
deleteButton.style.display = 'block';
223270
deleteButton.addEventListener('click', () => {
224271
newCharacterGroup.remove();
225272
if (document.querySelectorAll('.character-input-group').length < 5) {
226273
addCharacterButton.style.display = 'block';
227274
}
275+
checkCharactersAndUnlockNext(document.querySelector('.parameter-container:nth-child(4)'));
228276
});
229-
230277
charactersList.appendChild(newCharacterGroup);
231-
232278
if (document.querySelectorAll('.character-input-group').length === 5) {
233279
addCharacterButton.style.display = 'none';
234280
}
235281
}
236282
});
237-
});
238283

239-
function generateStory(msg) {
240-
document.getElementById('sendStoryButton').disabled = true;
241-
document.getElementById('storyInput').disabled = true;
242-
document.getElementById('loadingSpinner').style.display = 'inline-block';
243-
socket.emit('generate_story', msg);
244-
}
284+
document.querySelector('.generate-story-button').addEventListener('click', gatherDataAndGenerateStory);
285+
document.querySelector('.generate-randomly-button').addEventListener('click', generateRandomTestStory);
286+
287+
const modal = document.getElementById('new-story-modal');
288+
const clearButton = document.getElementById('clear-story-button');
289+
const closeButton = document.querySelector('.close-button');
290+
const confirmButton = document.getElementById('confirm-new-story-button');
291+
292+
clearButton.addEventListener('click', () => {
293+
modal.style.display = 'flex';
294+
});
245295

246-
function resetUI() {
247-
// This function might need to be updated to reset the sequential locking
248-
document.getElementById('storyInput').value = '';
249-
document.getElementById('promptResponse').style.display = 'none';
250-
document.getElementById('promptResponse').scrollTop = 0;
251-
document.getElementById('promptResponse').textContent = '';
252-
document.getElementById('sendStoryButton').disabled = false;
253-
document.getElementById('storyInput').disabled = false;
296+
closeButton.addEventListener('click', () => {
297+
modal.style.display = 'none';
298+
});
254299

255-
// Reset sequential containers
256-
const parameterContainers = document.querySelectorAll('.parameter-container');
257-
parameterContainers.forEach((container, index) => {
258-
// Close content and un-rotate arrow
259-
const content = container.querySelector('.parameter-content');
260-
const arrow = container.querySelector('.arrow-icon');
261-
content.style.display = 'none';
262-
arrow.classList.remove('rotated');
300+
confirmButton.addEventListener('click', () => {
301+
resetStoryView();
302+
modal.style.display = 'none';
303+
});
263304

264-
// Reset selected chips
265-
container.querySelectorAll('.chip.selected').forEach(c => c.classList.remove('selected'));
266-
const selectedValue = container.querySelector('.selected-value');
267-
if (selectedValue) {
268-
selectedValue.textContent = '';
269-
selectedValue.style.display = 'none';
305+
window.addEventListener('click', (event) => {
306+
if (event.target == modal) {
307+
modal.style.display = 'none';
270308
}
309+
});
271310

272-
if (index === 0) { // First container (Age)
273-
container.classList.remove('disabled');
274-
content.style.display = 'block';
275-
arrow.classList.add('rotated');
276-
} else {
277-
container.classList.add('disabled');
278-
}
311+
document.getElementById('copy-story-button').addEventListener('click', () => {
312+
const storyText = document.getElementById('story-response').textContent;
313+
navigator.clipboard.writeText(storyText).then(() => {
314+
const copyButton = document.getElementById('copy-story-button');
315+
const originalHTML = copyButton.innerHTML;
316+
copyButton.textContent = 'Copied!';
317+
copyButton.disabled = true;
318+
setTimeout(() => {
319+
copyButton.innerHTML = originalHTML;
320+
copyButton.disabled = false;
321+
}, 2000);
322+
}, (err) => {
323+
console.error('Could not copy text: ', err);
324+
});
279325
});
280-
}
326+
});
Lines changed: 3 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)