44
55const 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+
726function 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) {
3251function 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
5771function 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 ( / < s t r o n g > / g, '' ) . replace ( / < \/ s t r o n g > / 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+
143199document . 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+ } ) ;
0 commit comments