Replace yourself with AI: NodeRed Bedtime story from ChatGPT

You now can have unlimited bedtime stories with your child as a main character!

Simply replace your child’s name and gender in the prompt and link this flow to a button, and presto “computer mom” is telling your child a wonderful bed time story.
It is build with local AI in mind which may not be as fast as real chatGPT, some “character” is added to explain the potentially long pause while the story is lovingly inferred by your GPU’s

Requires musicassistant, and it’s custom integration to play the resulting tts wav’s in order
also Requires @runvnc/node-red-chatgpt-model and either paid openai api access or your selfhosted localai

[{"id":"46af7614e31af962","type":"group","z":"b590a025ab486f8d","name":"Bedtime Story","style":{"label":true},"nodes":["91158efa5cfbfdf6","a774bcd4fec3f947","64a5ce9606d0fc68","cc8779a49fb777a4","0f83af7fb1a5be20","17a5524330e77255","ecb7a4c48dd28c7b","075b108bdec12b2f","089698c1db81bdbe","e5870c997cb9da2b","91f9e0eda9fec5bc","9d852481a01d89bd","cc71c775adebd1c5","9bf7e7b03ed25e2b","12dc680fb7630f9e","dbedd5997a4f033b","ce832c039570995b","d4c114efd8a86967","0b8df92787aa466b"],"x":34,"y":873,"w":2672,"h":294},{"id":"91158efa5cfbfdf6","type":"inject","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":1000,"wires":[["a774bcd4fec3f947"]]},{"id":"a774bcd4fec3f947","type":"junction","z":"b590a025ab486f8d","g":"46af7614e31af962","x":220,"y":1060,"wires":[["cc8779a49fb777a4"]]},{"id":"64a5ce9606d0fc68","type":"ha-button","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"","version":0,"debugenabled":false,"outputs":1,"entityConfig":"06dd97284b4bf2b4","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"},{"property":"data","propertyType":"msg","value":"","valueType":"entity"}],"x":110,"y":1100,"wires":[["a774bcd4fec3f947"]]},{"id":"cc8779a49fb777a4","type":"change","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"","rules":[{"t":"set","p":"mediaplayer","pt":"msg","to":"aroom","tot":"str"},{"t":"set","p":"splitLength","pt":"msg","to":"4","tot":"num"},{"t":"set","p":"enqueue","pt":"msg","to":"next","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":1060,"wires":[["91f9e0eda9fec5bc"]]},{"id":"0f83af7fb1a5be20","type":"function","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"Split at .","func":"// Function to estimate syllable count in a word\nfunction estimateSyllables(word) {\n    word = word.toLowerCase().replace(/[^a-z]/g, ''); // Convert to lowercase and remove non-alphabetic characters\n    if (word.length <= 3) return 1; // For short words, assume 1 syllable\n    return word.replace(/(?:[^laeiouy]es|ed|[^laeiouy]e)$/, '')\n        .replace(/^y/, '')\n        .match(/[aeiouy]{1,2}/g).length;\n}\n\nfunction cleanInput(input) {\n    // Remove newlines, quotes, parentheses, and other non-punctuation characters\n    // that are not part of a word\n    input = input.replace(/\\n'\"|(?<=\\s|^)[\\n'\"]|[\\n\\(\\)\\[\\]\\{\\}\\+\\=\\-\\_\\*\\&\\^\\%\\$\\#\\@\\~\\`\\|\\\\\\/\\;\\:\\<\\>]+/g, ' ');\n\n    // Remove uncommon characters (e.g., control characters)\n    input = input.replace(/[^\\x20-\\x7E]/g, '');\n\n    // Replace all double quotes with a single space\n    input = input.replace(/\"/g, ' ');\n\n    return input;\n}\n\n// Check if msg.payload is a string\nif (typeof msg.payload === 'string') {\n    // Remove all non-alphanumeric characters except periods\n    //var cleanedPayload = msg.payload.replace(/[^a-zA-Z0-9. ]/g, '');\n\n    // Configure split length (number of sentences between splits)\n    var splitLength = msg.splitLength || 1; // Default is to split every sentence\n\n    var cleanedInput = cleanInput(msg.payload)\n\n    // Split the cleaned payload at each \". \" or other terminating punctuation\n    var sentences = cleanedInput.split(/(\\. |\\! |\\? |: |;)/);\n\n    // Initialize total delay to 0\n    var totalDelay = 0;\n\n    // Loop through each group of sentences\n    for (var i = 0; i < sentences.length; i += splitLength * 2) {\n        // Count syllables in the group of sentences\n        var syllableCount = 0;\n\n        // Loop through each sentence in the group\n        for (var j = i; j < i + splitLength * 2 && j < sentences.length; j += 2) {\n            // Calculate syllable count for each word in the sentence\n            var sentence = sentences[j] + (sentences[j + 1] || ''); // Include the punctuation\n            var words = sentence.match(/\\S+/g) || [];\n\n            for (var k = 0; k < words.length; k++) {\n                syllableCount += estimateSyllables(words[k]);\n            }\n        }\n\n        // Create a new message object for the group of sentences\n        var newMsg = {\n            payload: sentences.slice(i, i + splitLength * 2).join(''), // Join sentences without adding separators\n            //delay: totalDelay, // Set msg.delay based on accumulated delay\n            // reset: i === 0 // Set reset to true for the first message\n        };\n\n        // Add the current delay to the total delay for the next message\n        totalDelay += syllableCount * 260;\n\n        // If it's the second message, add an additional 1500 to the total delay\n        if (i === 2 * splitLength) {\n            totalDelay += 800;\n        }\n\n        // Copy other properties from the original message to the new message\n        for (var prop in msg) {\n            if (prop !== 'payload') {\n                newMsg[prop] = msg[prop];\n            }\n        }\n\n        // Set the last message enqueue to play\n        if (i + splitLength * 2 >= sentences.length) {\n            newMsg.complete = \"true\";\n        }\n\n        // Send the new message\n        node.send(newMsg);\n    }\n} else {\n    // If msg.payload is not a string, just pass the original message\n    node.send(msg);\n}","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1680,"y":940,"wires":[["17a5524330e77255"]]},{"id":"17a5524330e77255","type":"ha-api","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"","server":"36876d8a.1b2422","version":1,"debugenabled":false,"protocol":"http","method":"post","path":"tts_get_url","data":"{\"message\": \"{{{payload}}}\", \"engine_id\": \"tts.piper\", \"cache\": true, \"time_memory\": 900}","dataType":"json","responseType":"json","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"results"}],"x":1830,"y":940,"wires":[["ecb7a4c48dd28c7b"]]},{"id":"ecb7a4c48dd28c7b","type":"join","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":1970,"y":940,"wires":[["075b108bdec12b2f"]]},{"id":"075b108bdec12b2f","type":"function","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"function 4","func":"// Check if msg.payload is an array\nif (Array.isArray(msg.payload)) {\n    // Set the base URL\n    var url = \"http://homeassistant:8123\";\n\n    // Extract the \"path\" property from each object in the array and prepend the base URL\n    var paths = msg.payload.map(function(obj) {\n        return url + obj.path;\n    });\n\n    // Convert the extracted paths to a JSON-formatted string\n    var jsonString = JSON.stringify(paths);\n\n    // Create a new message object with the JSON string\n    var newMsg = {\n        payload: jsonString\n    };\n\n    // Copy original message properties.\n    for (var prop in msg) {\n        if (prop !== 'payload') {\n            newMsg[prop] = msg[prop];\n        }\n    }\n    // Send the new message\n    return newMsg;\n} else {\n    // If msg.payload is not an array, just pass the original message\n    return msg;\n}\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":2140,"y":940,"wires":[["089698c1db81bdbe"]]},{"id":"089698c1db81bdbe","type":"api-call-service","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"","server":"36876d8a.1b2422","version":5,"debugenabled":false,"domain":"mass","service":"play_media","areaId":[],"deviceId":[],"entityId":["media_player.mass_{{mediaplayer}}"],"data":"{\"media_id\": {{{payload}}}, \"media_type\": \"track\", \"enqueue\": \"replace\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":2370,"y":940,"wires":[["e5870c997cb9da2b"]]},{"id":"e5870c997cb9da2b","type":"api-call-service","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"set sonos vol","server":"36876d8a.1b2422","version":5,"debugenabled":false,"domain":"media_player","service":"volume_set","areaId":[],"deviceId":["6f4c328d2469e250a054e70211ea9cc7"],"entityId":[],"data":"{\"volume_level\": 0.70}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[{"property":"asdf","propertyType":"msg","value":"","valueType":"data"}],"queue":"none","x":2610,"y":940,"wires":[[]]},{"id":"91f9e0eda9fec5bc","type":"change","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"As the character of a 1950's housewife, your young son has asked you to tell him another bedtime story. In a single brief sentence Tell him you would be happy to imagine one just for him, you just need a few minutes to think and ask him to be patient, trailing off saying a variation of \"hmmm let me think\"","tot":"str"},{"t":"set","p":"temperature","pt":"msg","to":"1.0","tot":"str"},{"t":"set","p":"topic","pt":"msg","to":"completion","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":760,"y":1060,"wires":[["5beeb3e0592a1d3b"]]},{"id":"9d852481a01d89bd","type":"group","z":"b590a025ab486f8d","g":"46af7614e31af962","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["f3a6f4bcc8b4b186","5beeb3e0592a1d3b"],"x":654,"y":1019,"w":392,"h":122},{"id":"f3a6f4bcc8b4b186","type":"catch","z":"b590a025ab486f8d","g":"9d852481a01d89bd","name":"","scope":"group","uncaught":false,"x":750,"y":1100,"wires":[["91f9e0eda9fec5bc"]]},{"id":"5beeb3e0592a1d3b","type":"chatgpt-model","z":"b590a025ab486f8d","g":"9d852481a01d89bd","name":"","topic":"turbo","BaseUrl":"http://docker-dmz.0xcbf.net:3180","x":940,"y":1060,"wires":[["ce832c039570995b","d4c114efd8a86967"]]},{"id":"cc71c775adebd1c5","type":"api-call-service","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"","server":"36876d8a.1b2422","version":5,"debugenabled":false,"domain":"tts","service":"speak","areaId":[],"deviceId":[],"entityId":["tts.piper"],"data":"{\"message\": \"{{{payload}}}\", \"media_player_entity_id\": \"media_player.{{mediaplayer}}\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1260,"y":1100,"wires":[[]]},{"id":"9bf7e7b03ed25e2b","type":"api-call-service","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"","server":"36876d8a.1b2422","version":5,"debugenabled":false,"domain":"tts","service":"speak","areaId":[],"deviceId":[],"entityId":["tts.piper"],"data":"{\"message\": \"{{{payload}}}\", \"media_player_entity_id\": \"media_player.{{mediaplayer}}\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":2320,"y":1080,"wires":[[]]},{"id":"12dc680fb7630f9e","type":"group","z":"b590a025ab486f8d","g":"46af7614e31af962","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["1277df29b027428d","c2bfc45d1a69f124","ad50a87f6529b41d"],"x":1614,"y":999,"w":432,"h":122},{"id":"1277df29b027428d","type":"catch","z":"b590a025ab486f8d","g":"12dc680fb7630f9e","name":"","scope":"group","uncaught":false,"x":1710,"y":1040,"wires":[["ad50a87f6529b41d"]]},{"id":"c2bfc45d1a69f124","type":"chatgpt","z":"b590a025ab486f8d","g":"12dc680fb7630f9e","name":"","topic":"turbo","BaseUrl":"http://docker-dmz.0xcbf.net:3180","x":1960,"y":1080,"wires":[["dbedd5997a4f033b"]]},{"id":"ad50a87f6529b41d","type":"change","z":"b590a025ab486f8d","g":"12dc680fb7630f9e","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"As the character of a 1950's housewife, within two sentences exclaim you've thought of the perfect story the user will love and ask if the user is comfortable in bed..","tot":"str"},{"t":"set","p":"temperature","pt":"msg","to":"1.0","tot":"str"},{"t":"set","p":"topic","pt":"msg","to":"completion","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1780,"y":1080,"wires":[["c2bfc45d1a69f124"]]},{"id":"dbedd5997a4f033b","type":"function","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"function 7","func":"function cleanInput(input) {\n    // Remove newlines, quotes, parentheses, and other non-punctuation characters\n    // that are not part of a word\n    input = input.replace(/\\n'\"|(?<=\\s|^)[\\n'\"]|[\\n\\(\\)\\[\\]\\{\\}\\+\\=\\-\\_\\*\\&\\^\\%\\$\\#\\@\\~\\`\\|\\\\\\/\\;\\:\\<\\>]+/g, ' ');\n\n    // Remove uncommon characters (e.g., control characters)\n    input = input.replace(/[^\\x20-\\x7E]/g, '');\n\n    // Replace all double quotes with a single space\n    input = input.replace(/\"/g, ' ');\n\n    return input;\n}\n\nmsg.payload = cleanInput(msg.payload)\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":2120,"y":1080,"wires":[["9bf7e7b03ed25e2b"]]},{"id":"ce832c039570995b","type":"function","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"function 8","func":"function cleanInput(input) {\n    // Remove newlines, quotes, parentheses, and other non-punctuation characters\n    // that are not part of a word\n    input = input.replace(/\\n'\"|(?<=\\s|^)[\\n'\"]|[\\n\\(\\)\\[\\]\\{\\}\\+\\=\\-\\_\\*\\&\\^\\%\\$\\#\\@\\~\\`\\|\\\\\\/\\;\\:\\<\\>]+/g, ' ');\n\n    // Remove uncommon characters (e.g., control characters)\n    input = input.replace(/[^\\x20-\\x7E]/g, '');\n\n    // Replace all double quotes with a single space\n    input = input.replace(/\"/g, ' ');\n\n    return input;\n}\n\nmsg.payload = cleanInput(msg.payload)\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1120,"y":1100,"wires":[["cc71c775adebd1c5"]]},{"id":"d4c114efd8a86967","type":"change","z":"b590a025ab486f8d","g":"46af7614e31af962","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"You are a  loving 1950's house wife. Create a bedtime story for your young SON/DAUGHTER, CHILDSNAME, a bed time story. Draw inspiration from classic childrens stories or create a new unheard story with the following rules: Craft stories with a soothing and comforting tone suitable for a young BOY/GIRL, keeping in mind the bedtime setting. Stories should be 10 paragraphs long to ensure a comprehensive and engaging narrative. Use single quotes for encapsulating dialogue. Ensure that the stories involve CHILDSNAME, or his short name CHILDSNAME, as a character that interacts with the story's main character or companion, bringing depth to the narrative. Infuse each story with a unique theme, magical, or christian element, contributing to the enchanting and imaginative atmosphere. Include moments of fear and bravery while having a moral lesson. Finally, wish CHILDSNAME good night.","tot":"str"},{"t":"set","p":"temperature","pt":"msg","to":"1.0","tot":"str"},{"t":"set","p":"topic","pt":"msg","to":"completion","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1220,"y":940,"wires":[["83e149eeb517bc99"]]},{"id":"0b8df92787aa466b","type":"group","z":"b590a025ab486f8d","g":"46af7614e31af962","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["116ce9e67e8b196f","83e149eeb517bc99"],"x":1054,"y":899,"w":472,"h":142},{"id":"116ce9e67e8b196f","type":"catch","z":"b590a025ab486f8d","g":"0b8df92787aa466b","name":"","scope":"group","uncaught":false,"x":1150,"y":1000,"wires":[["d4c114efd8a86967"]]},{"id":"83e149eeb517bc99","type":"chatgpt-model","z":"b590a025ab486f8d","g":"0b8df92787aa466b","name":"","topic":"turbo","BaseUrl":"http://docker-dmz.0xcbf.net:3180","x":1420,"y":940,"wires":[["0f83af7fb1a5be20","ad50a87f6529b41d"]]},{"id":"06dd97284b4bf2b4","type":"ha-entity-config","server":"36876d8a.1b2422","deviceConfig":"","name":"","version":"6","entityType":"button","haConfig":[{"property":"name","value":"Story Time"},{"property":"icon","value":""},{"property":"entity_category","value":""},{"property":"entity_picture","value":""},{"property":"device_class","value":""}],"resend":false,"debugEnabled":false},{"id":"36876d8a.1b2422","type":"server","name":"Home Assistant","version":5,"addon":false,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":true,"heartbeatInterval":"30","areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m","enableGlobalContextStore":true}]

On a technical level, the function nodes chop up the long string returned by the chatgpt api into smaller chunks to prevent wyoming piper from timing out, then it sends them in order to musicassistant as a playlist.
There is logic built in if you would like to explore using this without music assistant that estimates the time to “speak” each segment based on sylables and time in ms to say a single sylable so each section could be delayed until after the previous message was played by the speaker. Ultimately I abandoned this approach, but it’s left as a mesg property if useful.
In addition to replacing placeholders in ALLCAPS such as CHILDSNAME, also set the config msg’s in the change nodes to your preference.
You may also customize the prompt to your liking, however the style is explictly written to guide mixtral8x7b into responding appropriately.

TEST before it reads to your children!

So cool, thx for the entry! Would you mind sharing a demo? Thx a lot!

it’s a free world of course, but I wouldn’t want to use this. Telling bedtime stories is (to me) a moment of parent-child bonding. Treating it as a chore that you prefer to have done by a computer would, in my view, send a message to my child that I don’t want to give.

3 Likes

There are some great compromises that use the best of AI but also keep you engaged. My little one gives the story ideas for stories to AI and gets to see her imagination flushed out. I have a model trained with her interests and friends/pets.

We don’t use this for bedtime though that is book reading time. This is more for fun through the day.

We also create coloring pages using her wild ideas.

The results of the contest are out!
They may be of interest to you :wink:
Have a look!