Viability for this? Touch screen login/out project

Wow my 100 random Aussie names (GPT-3.5) includes 79 members of the Wilson clan.
No Singhs or Patels, "Sophia Nguyen" is the token South East Asian.
Racial bias in AI! Who'd have thought it?

2 Likes

Nothing new under the sun I'm afraid - especially when it comes to LLM's

OK, so here is a very simple flow to create a master membership list with 100 members complete with joining date and membership number :sunglasses::

[{"id":"30c20c7cdb30a0be","type":"inject","z":"44ceec9d5e933a9b","name":"100 Names","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[\"Oliver Smith\",\"Charlotte Johnson\",\"William Brown\",\"Amelia Jones\",\"Jack Taylor\",\"Ava Williams\",\"Thomas White\",\"Mia Martin\",\"Henry Thompson\",\"Isla Harris\",\"Liam Lee\",\"Sophie Walker\",\"James Hall\",\"Grace Clark\",\"Samuel Allen\",\"Emily Young\",\"Lucas King\",\"Zoe Wright\",\"Alexander Scott\",\"Chloe Green\",\"Ethan Adams\",\"Ruby Baker\",\"Noah Roberts\",\"Harper Mitchell\",\"Mason Lewis\",\"Ella Campbell\",\"Charlie Turner\",\"Lily Hill\",\"Jacob Phillips\",\"Ivy Parker\",\"Harrison Evans\",\"Sienna Edwards\",\"Sebastian Collins\",\"Zara Murphy\",\"Aiden Ward\",\"Georgia Cox\",\"Oscar Rogers\",\"Matilda Bailey\",\"Daniel Cooper\",\"Evelyn Richardson\",\"Isaac Kelly\",\"Willow Bennett\",\"Leo Wood\",\"Sophia Barnes\",\"Harvey Russell\",\"Evie Gray\",\"Elijah Carter\",\"Madison Hughes\",\"Matthew Watson\",\"Aria Price\",\"Benjamin Bell\",\"Layla Brooks\",\"Joshua Patterson\",\"Abigail Simmons\",\"Dylan Howard\",\"Hannah Fisher\",\"Archie Reed\",\"Mila Butler\",\"Nathan Foster\",\"Scarlett Murray\",\"Ryan Grant\",\"Lucy Bryant\",\"Carter Hayes\",\"Poppy Long\",\"Gabriel Hughes\",\"Ivy Johnston\",\"Christian Fox\",\"Alice Bryant\",\"Luke Wells\",\"Zoe Richardson\",\"Jonathan Brooks\",\"Madison Simpson\",\"Connor Chapman\",\"Lilly Spencer\",\"Michael Walton\",\"Ellie Lawson\",\"Cameron Abbott\",\"Maya Ward\",\"Hunter Freeman\",\"Violet McDonald\",\"Jackson Harper\",\"Audrey Osborne\",\"Elliot Goodwin\",\"Bella Walsh\",\"Aaron Owen\",\"Molly Newton\",\"Cooper Andrews\",\"Holly Fuller\",\"Joseph Harvey\",\"Piper Morgan\",\"Austin Wallace\",\"Maddison Ellis\",\"Blake Cooper\",\"Eloise Jordan\",\"Xavier Perry\",\"Isabelle Russell\",\"Chase Hughes\",\"Penelope Walsh\",\"George Hunt\",\"Alyssa Hamilton\",\"Sebastian Murray\",\"Stella Foster\"]","payloadType":"json","x":330,"y":620,"wires":[["0744d5b8a2915619"]],"info":"ChatGPT Prompt:\r\n\r\nPlease generate me a list of 100 random people names (given-name family-name) that would be found in Australia. Return the list as a JavaScript array of strings where each entry is in the format given-name space family-name."},{"id":"0744d5b8a2915619","type":"function","z":"44ceec9d5e933a9b","name":"Create master members list","func":"function getRandomFiveDigitString() {\n    // Generate a random number between 10000 and 99999\n    const randomNumber = Math.floor(10000 + Math.random() * 90000);\n    // Convert the number to a string and return it\n    return randomNumber.toString();\n}\n\nfunction getRandomDateString() {\n    // Define the start and end dates\n    const startDate = new Date('2015-01-01');\n    const endDate = new Date('2024-04-05');\n\n    // Generate a random timestamp between the start and end dates\n    const randomTimestamp = startDate.getTime() + Math.random() * (endDate.getTime() - startDate.getTime());\n\n    // Create a new date object from the random timestamp\n    const randomDate = new Date(randomTimestamp);\n\n    // Format the date as YYYY-MM-DD\n    const year = randomDate.getFullYear();\n    const month = String(randomDate.getMonth() + 1).padStart(2, '0'); // Months are zero-indexed\n    const day = String(randomDate.getDate()).padStart(2, '0');\n\n    // Return the formatted date string\n    return `${year}-${month}-${day}`;\n}\n\n// const members = flow.get('members') ?? {}\nconst members = {}\n\nconst names = msg.payload\n\nnames.forEach( (name) => {\n    const id = getRandomFiveDigitString()\n    // Assuming input name list represents actual people\n    // Adding member id to key to ensure it is unique\n    members[`${name} ${id}`] = {\n        'joined': getRandomDateString(),\n        'membershipId': id,\n    }\n})\n\nflow.set('members', members)","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":600,"y":620,"wires":[[]]}]

image

And here is a very simple example flow that signs in or out a random person:

[{"id":"4dab9561ef0452e2","type":"inject","z":"44ceec9d5e933a9b","name":"Entry","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"entry","payloadType":"str","x":290,"y":700,"wires":[["459806e4342483eb"]]},{"id":"fc81aa89b3fc12d1","type":"inject","z":"44ceec9d5e933a9b","name":"Exit","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"exit","payloadType":"str","x":290,"y":740,"wires":[["459806e4342483eb"]]},{"id":"459806e4342483eb","type":"function","z":"44ceec9d5e933a9b","name":"Sign in or out a random person","func":"function getRandomArrayEntry(arr) {\n    // Generate a random index based on the array's length\n    const randomIndex = Math.floor(Math.random() * arr.length);\n    // Return the array entry at the random index\n    return arr[randomIndex];\n}\n\nconst members = flow.get('members')\nconst today = flow.get('today') ?? {} // will be auto-created\nconst inOrOut = msg.payload\n\n// Get the full list of members but only their keys\nconst arrayMemberKeys = Object.keys(members)\n// Chose a random person\nconst randomPerson = getRandomArrayEntry(arrayMemberKeys)\n// If this person not yet in today's data, create them\nif (!today[randomPerson]) today[randomPerson] = {}\n// Set them either in or out\ntoday[randomPerson][inOrOut] = (new Date()).toISOString()\n\n// Save today's data\nflow.set('today', today)\n// Some info on what happened\nmsg.payload = {\n    who: randomPerson,\n    what: inOrOut,\n}\nreturn msg","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":530,"y":720,"wires":[["8a1dc150d853110c"]]},{"id":"8a1dc150d853110c","type":"debug","z":"44ceec9d5e933a9b","name":"Who?","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":750,"y":720,"wires":[]}]

image

2 Likes

Yeah ok, fair enough.

But all I am wanting to go into the node (flow) at this point is the person's name.
And deals with if is the first or second time the name was received.
If the first time, marks them as "IN" and if it is the second time marks them as "OUT".

This part builds the ..... object and spits out the object for further processing. (Display and formatting).

I thought it was you who asked me about chatGPT in post ... 67/8 - as I have never used it before a few posts later.

(Why can't I give you more than one like?) :wink:

First flow: FANTASTIC. Saves me a lot of head work to create a members list.

That is great.

(It's early morning here, I have about 45 minutes, then I'm outa here for abut 4/5 hours.)

The only.... PROBLEM with the Sign in/out script.
I'll redact that as it isn't fair.

(Though I guess if I look more at it, I can separate that into TWO parts and maybe answer my own question)

I guess you did it this way as it is not easy to put all the inject nodes for all the members.

When I get back I'll see if I can make it into TWO function nodes.
The first will be to get a random person who's signed in and pass the name on.
The second one will do the toggling of their status.

I'll now post to the topic in general.

Ok, where we are at:

I'll say KIND OF DONE to indicate this problem has been addressed and the mechanics are shown and so at this point is (Probably) good enough to not need more work AT THIS POINT.
That isn't to say work is needed. But until more things get applied, it is not known by me.

The steps - as I'm calling them - aren't particularly in any order. They are maybe more POINTS.
Sorry, I only thought of this AFTER getting to step 4.

Step 1 - the user database is got from the remote machine and loaded as `members
(KINDA DONE)

Step 2 - as members come in / go home, their status in today is created/updated to show they are here or have gone home / left. (KIND OF DONE)

Step 3 - the today object needs to be handled by code so it makes buttons for all the given members in attendance TODAY is shown.
The button will include extra data to indicate they have gone home.
I'm guessing this will be done in ui-builder?

Step 4 - A .... Co ordinator button. This flags the person who is the go to guy for that day.
This information will be displayed on their name button. Text and maybe colour.
I'm guessing this will be done in ui-builder?

Step 5 - daily (after hours) the daily attendance list is exported to a file. For now local. Ultimately may be remote. Unsure at this stage.
Also a list of people who have not gone home is created.

Step 6 - Two buttons to control how the name list is displayed.
Order arrived
Alphabetical
Not sure this is needed. Depending if it is needed. But shouldn't be a hard to do.

Thanks VERY MUCH for the help so far.

@TotallyInformation - of course we have a node to help - node-red-node-data-generator (node) - Node-RED

2 Likes

Darn it Dave, how did I forget that! :grinning: Still, it was a useful experiment with ChatGPT and I don't think that your excellent node can give locality-specific name lists?!

Probably more random than @jbudd’s list though!
(Edit) the mock data the node uses is in a file. Eg source here dummy-json/lib/mockdata.js at master Ā· webroo/dummy-json Ā· GitHub, so you could of course create a more locale specific version

It's a fun exercise to be sure.

In fact, so much fun that I knocked up a much more complete example! :grinning:

I won't share it though as I don't want to spoil both your fun and you learning. :rofl:

But it is there if you want it at some point. Not complete by any means but it does the basics and sends the data back to Node-RED so you have today's data both in the browser and in node-red. It uses UIBUILDER - of course! And uses the uibuilder cache to ensure that a (re)load of the page gets the latest data.

image

(green is in, horrid pink is out, the search input is dynamic).

Wow, that looks good.

I'm not sure if the dynamic search is good.

But.... :wink:

So there is the search bar at the top.
So that searches the entire list? - guessing.

My idea is that the Today's screen will be broken up .... something like this:
How it is going, that may be the main screen anyway.
Others for house work may exist.

The yellow is kind of fixed / non editable
It shows the time/date (not really needed, but just I think it would be nice to see.

the coordinator is filled in when that person logs in.
There will be another button I have to work out where it is.
Maybe beside the names when you see the list.
There may be a flag that also only allows those with that position to click the button.

The scrolling list is the cumulative list of people's names as they log in.
(may have an optional sort to toggle between time/alphabetical listing.)
All the names are buttons and if you press it there, it then marks you as gone home.
I'm not sure it needs to be that wide, but maybe 2 columns of names would be possible.

The other red box/area is where you have the 26 letters for the name.
Yeah, a search may be nice, but it may be a touch screen rather than keyboard.
NOT SURE at this point.

You press / click A and all the names starting with A are shown in that area.
All buttons that then mark you are in when you press them.

This layout may not be the best, but for now.....
Maybe put the alphabet as 26 buttons along the top.
But that has the problem of where to then put the list of names.
So maybe the column on either side would be better.

Just mentioning.
The Today box/area would show the person's name - if/when they sign in.
Then when the go home: yes change colour and put the time they left.
So the buttons would maybe be better if they had 2 lines.
The person's name and the time they left on the second line.

There also may be the option so if you accidentally press the wrong name in the big list, you have 10 seconds to press it again and remain marked as being here.
People have fit fingers some times. :wink:
10 seconds being nominal/editable via the config screen. Or maybe not.

Is it time I start to work on this page?
I know you can't know what I know and/or where I'm at with it all.
It is just I still don't really understand all the mechanics of what/how to do it.
And is it worth me building a test case on THIS machine which doesn't have ui_builder.
(Just saying as most of what we've done in the last couple of days has been on THIS machine as it is/was basic stuff and the ui_builder wasn't used.

I was going to implement an on-screen keyboard but the newer API requires an HTTPS connection which I don't have on my dev system.

Yes, I take the master object and extract the keys using const members = Object.keys(master) which gives an array of person identifiers. As you type, that is fed into a filter members.filter( m => (m.toLowerCase()).includes(val.toLowerCase())). Where val is the search field value.

Seems reasonable.

With your search keyboard though, I recommend trying to make it qwerty as people are more used to that rather than alphabetic.

If you want to get fancy - and avoid too much messing with permissions on your in/out page, you might consider using a Telegram bot for coordinators :grinning: Or maybe consider having a completely separate page (easy to do with UIBUILDER!) that only coordinators have access to (maybe via their phones?) - much easier.

Well, all I can say is - if you don't have a go, you never will understand, right? :wink:

I did my little Proof of Concept because it was an interesting task. It resulted in a simple flow and , for the front-end, 2 CSS definitions, 5 lines of HTML, and about 70 lines of JS (though that includes blank lines and comments). And I did use ChatGPT to help write a couple of functions which made things rather quicker.

I think that demonstrates sufficiently that UIBUILDER can do what you want.

Whether you want to use it is obviously up to you. :slight_smile:

Julian,

The difference/problem is I don't know of these commands, so can't think to use them.

I admit there is more I don't know I don't know than there is that I know I don't know.

Though

I am going to sit down and try to get my head around some of it.

Yeah, but the age of most of these lot, qwerty is not really much different to alphabetical.
(Age) :frowning:

Yes but that is yet another whole world of which I have ABS(0) knowledge.
Another option is the coordinator field is a button.
They tap on it and then get a drop down list of names. They pick theirs and all is good.

And I do understand/agree.

But it kind of arcs back to my first point.

Honestly: I don't know how I know good I am with NR.
I feel only JUST confident to help people and try things, but with no base line of knowing all the commands at my disposal - to say - I don't understand the mechanics of how I would do it.

Yes, ok, when I first started with NR - what 8 years ago? - I knew nothing of how to use it and all that.
But at that point I was playing (to badly use the word) with the basic inbuilt nodes to do somewhat basic stuff. Bells and whistles if you will.
How I ever got from there to here (which is honestly unknown relative to the greater NR world) is also a mystery to me.

Would any of the example scripts be a starting point?

So, reading what you said:
2 CSS definitions, 5 lines of HTML and 70 lines of JS.

Ok, I can maybe handle the JS lines.
5 Lines of HTML. That will set up the page parts/sections (define the page parts)....
CSS. Well, ok, 2 lines. Can't be beyond me.

But I have very little understanding of the interworkings of these things.

Yes, I would like the challenge of doing it myself but I need to know/understand the foundations on which it will be built.
That not being how it works. I believe I do have a good grasp of that.
But how I think and the code have little in common. I don't know how it gets from the concept stage to the code stage.

Sorry, I'll shut up.
Um, some other things in life hit the fan and I am going to be away for about 3 - 4 days.
No 'puta. :frowning:
I'll give chatGPT a go but again: As good as it is, how I ask questions - the vagueness of them - is it smart enough to understand them? :wink:

OK, "break it down and look it up" is my mantra. I have to do this all the time as I can never remember things. I had to look up both the filter and includes functions for example.

And don't forget that you can ask ChatGPT to explain something to you which is sometimes helpful.

In this case:

  • Object.keys(objectVar) means "just give me an array containing the keys of a JavaScript object variable. You need an array so that you can filter it.
  • members.filter - hopefully fairly self-explanatory. Even though the way filter works might not be obvious. Basically, you give it a function (I used an "arrow" function here).
  • The bit with all the brackets is simply saying "test each element of the members array and see if the member key contains what I typed into the search input box".

That's true for all of us. Recognising it can be rather daunting. However, I refer you back to the mantra at the start of this post. :wink: Though that mantra may seem trivial, it is actually at the heart of any problem solving. And it greatly helps people get beyond that very natural feeling of helplessness that we all sometimes feel.

:+1:

Or an opportunity! :slight_smile:

For where you are at, I would really recommend that you create a second page for the coordinators. This keeps the problems separate from each other and simplifies each one.

Yes, but you really don't want that option appearing to members checking in/out, someone is bound to abuse it sooner or later. That means you would need to start worrying about logins far too soon. Put it on a separate page and give coordinators the link. That is likely to be secure enough for now.

I've spent all of my professional life being "one page ahead in the manual"! (or one search ahead these days). None of us have the full picture - especially when we don't do stuff full-time.

But logic is logic. Regardless of the tech used to deliver it. Break down the problem into little bits you can get your head around and work through each bit slowly linking things back together.

That's why my initial quick example was to simulate user actions in Node-RED. It gave me a better understanding of the data flow needed and so enabled me to start on an actual web page.

OK, so here is my example. Contains the flow I used and the front-end code. Even though what I've done doesn't quite match your requirements, it will hopefully give you a bit of a bootstrap up.

[{"id":"c480c15e8ae54ca2","type":"group","z":"44ceec9d5e933a9b","name":"Base Configuration \\n ","style":{"fill":"#bfdbef","fill-opacity":"0.12","label":true,"color":"#000000"},"nodes":["65ec86be3f5d8aed","bdd19c31149a953e","1276c925f77d592d","becd7b4e9df4cc2e","e1b8e420fb3ceba9","1fa1e50ec490537d","b00996156cb9e85e","1206478c462c5604","70dcf9c03798d318","9111efb2f00b1562","017718debee9dfd1","a02081fdfbb3f8f7","d0f1e332f11932a7","67eafc92ed9e5efd","cc9c724a5e199043","40f19743af440271","8a14206863d3b649","ff8803e82ae8e0bb"],"x":84,"y":821,"w":1320,"h":480},{"id":"65ec86be3f5d8aed","type":"debug","z":"44ceec9d5e933a9b","d":true,"g":"c480c15e8ae54ca2","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1075,"y":1100,"wires":[],"l":false},{"id":"bdd19c31149a953e","type":"uibuilder","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","name":"","topic":"","url":"sign-in","okToGo":true,"fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"templateFolder":"blank","extTemplate":"","showfolder":false,"reload":true,"sourceFolder":"src","deployedVersion":"7.0.0","showMsgUib":true,"title":"","descr":"","editurl":"vscode://file/src/uibRoot/sign-in/?windowId=_blank","x":810,"y":1140,"wires":[["70dcf9c03798d318","9111efb2f00b1562"],["e1b8e420fb3ceba9","017718debee9dfd1"]]},{"id":"1276c925f77d592d","type":"uib-cache","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","cacheall":false,"cacheKey":"topic","newcache":true,"num":1,"storeName":"default","name":"","storeContext":"context","varName":"uib_cache","x":520,"y":1140,"wires":[["bdd19c31149a953e"]]},{"id":"becd7b4e9df4cc2e","type":"debug","z":"44ceec9d5e933a9b","d":true,"g":"c480c15e8ae54ca2","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1075,"y":1180,"wires":[],"l":false},{"id":"e1b8e420fb3ceba9","type":"link out","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","name":"uib-ctrl-out","mode":"link","links":["1fa1e50ec490537d"],"x":935,"y":1180,"wires":[]},{"id":"1fa1e50ec490537d","type":"link in","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","name":"uib-in-cached","links":["e1b8e420fb3ceba9","40f19743af440271"],"x":335,"y":1180,"wires":[["1276c925f77d592d"]]},{"id":"b00996156cb9e85e","type":"link in","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","name":"uib-in-nocached","links":["bdfc9a577a74bb7c"],"x":615,"y":1180,"wires":[["bdd19c31149a953e"]]},{"id":"1206478c462c5604","type":"inject","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","name":"Clear Cache","props":[{"p":"uibuilderCtrl","v":"clear cache","vt":"str"},{"p":"cacheControl","v":"CLEAR","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":280,"y":1140,"wires":[["1276c925f77d592d"]]},{"id":"70dcf9c03798d318","type":"link out","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","name":"link out 135","mode":"link","links":["67eafc92ed9e5efd"],"x":935,"y":1100,"wires":[]},{"id":"9111efb2f00b1562","type":"junction","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","x":970,"y":1120,"wires":[["65ec86be3f5d8aed"]]},{"id":"017718debee9dfd1","type":"junction","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","x":970,"y":1160,"wires":[["becd7b4e9df4cc2e"]]},{"id":"a02081fdfbb3f8f7","type":"change","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","name":"Members List","rules":[{"t":"set","p":"payload","pt":"msg","to":"members","tot":"flow"},{"t":"set","p":"topic","pt":"msg","to":"full-members-list","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":270,"y":1080,"wires":[["1276c925f77d592d"]]},{"id":"d0f1e332f11932a7","type":"inject","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","name":"Update members list to cache","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":145,"y":1080,"wires":[["a02081fdfbb3f8f7"]],"l":false},{"id":"67eafc92ed9e5efd","type":"link in","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","name":"Upd today list","links":["70dcf9c03798d318"],"x":235,"y":1260,"wires":[["cc9c724a5e199043"]]},{"id":"cc9c724a5e199043","type":"function","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","name":"Update Today & send to cache","func":"if (msg.topic === 'person-status-change') {\n    const today = flow.get('today') ?? {}\n    // Create the person if they don't exist today\n    if (!today[msg.key]) today[msg.key] = {}\n    // Merge the new record with the flow var version\n    today[msg.key] = {...today[msg.key], ...msg.record}\n    flow.set('today', today)\n\n    // Send the today list to the cache\n    // So new/reloaded browser tabs get the latest data\n    return {\n        topic: 'today-cache',\n        payload: today,\n    }\n}","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":1260,"wires":[["40f19743af440271"]]},{"id":"40f19743af440271","type":"link out","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","name":"today-to-cache","mode":"link","links":["1fa1e50ec490537d"],"x":615,"y":1260,"wires":[]},{"id":"8a14206863d3b649","type":"group","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","name":"Data setup \\n ","style":{"fill":"#ffffff","fill-opacity":"0.22","label":true,"color":"#000000"},"nodes":["30c20c7cdb30a0be","0744d5b8a2915619"],"x":114,"y":863,"w":532,"h":98},{"id":"30c20c7cdb30a0be","type":"inject","z":"44ceec9d5e933a9b","g":"8a14206863d3b649","name":"100 Names","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[\"Oliver Smith\",\"Charlotte Johnson\",\"William Brown\",\"Amelia Jones\",\"Jack Taylor\",\"Ava Williams\",\"Thomas White\",\"Mia Martin\",\"Henry Thompson\",\"Isla Harris\",\"Liam Lee\",\"Sophie Walker\",\"James Hall\",\"Grace Clark\",\"Samuel Allen\",\"Emily Young\",\"Lucas King\",\"Zoe Wright\",\"Alexander Scott\",\"Chloe Green\",\"Ethan Adams\",\"Ruby Baker\",\"Noah Roberts\",\"Harper Mitchell\",\"Mason Lewis\",\"Ella Campbell\",\"Charlie Turner\",\"Lily Hill\",\"Jacob Phillips\",\"Ivy Parker\",\"Harrison Evans\",\"Sienna Edwards\",\"Sebastian Collins\",\"Zara Murphy\",\"Aiden Ward\",\"Georgia Cox\",\"Oscar Rogers\",\"Matilda Bailey\",\"Daniel Cooper\",\"Evelyn Richardson\",\"Isaac Kelly\",\"Willow Bennett\",\"Leo Wood\",\"Sophia Barnes\",\"Harvey Russell\",\"Evie Gray\",\"Elijah Carter\",\"Madison Hughes\",\"Matthew Watson\",\"Aria Price\",\"Benjamin Bell\",\"Layla Brooks\",\"Joshua Patterson\",\"Abigail Simmons\",\"Dylan Howard\",\"Hannah Fisher\",\"Archie Reed\",\"Mila Butler\",\"Nathan Foster\",\"Scarlett Murray\",\"Ryan Grant\",\"Lucy Bryant\",\"Carter Hayes\",\"Poppy Long\",\"Gabriel Hughes\",\"Ivy Johnston\",\"Christian Fox\",\"Alice Bryant\",\"Luke Wells\",\"Zoe Richardson\",\"Jonathan Brooks\",\"Madison Simpson\",\"Connor Chapman\",\"Lilly Spencer\",\"Michael Walton\",\"Ellie Lawson\",\"Cameron Abbott\",\"Maya Ward\",\"Hunter Freeman\",\"Violet McDonald\",\"Jackson Harper\",\"Audrey Osborne\",\"Elliot Goodwin\",\"Bella Walsh\",\"Aaron Owen\",\"Molly Newton\",\"Cooper Andrews\",\"Holly Fuller\",\"Joseph Harvey\",\"Piper Morgan\",\"Austin Wallace\",\"Maddison Ellis\",\"Blake Cooper\",\"Eloise Jordan\",\"Xavier Perry\",\"Isabelle Russell\",\"Chase Hughes\",\"Penelope Walsh\",\"George Hunt\",\"Alyssa Hamilton\",\"Sebastian Murray\",\"Stella Foster\"]","payloadType":"json","x":230,"y":920,"wires":[["0744d5b8a2915619"]],"info":"ChatGPT Prompt:\r\n\r\nPlease generate me a list of 100 random people names (given-name family-name) that would be found in Australia. Return the list as a JavaScript array of strings where each entry is in the format given-name space family-name."},{"id":"0744d5b8a2915619","type":"function","z":"44ceec9d5e933a9b","g":"8a14206863d3b649","name":"Create master members list","func":"function getRandomFiveDigitString() {\n    // Generate a random number between 10000 and 99999\n    const randomNumber = Math.floor(10000 + Math.random() * 90000);\n    // Convert the number to a string and return it\n    return randomNumber.toString();\n}\n\nfunction getRandomDateString() {\n    // Define the start and end dates\n    const startDate = new Date('2015-01-01');\n    const endDate = new Date('2024-04-05');\n\n    // Generate a random timestamp between the start and end dates\n    const randomTimestamp = startDate.getTime() + Math.random() * (endDate.getTime() - startDate.getTime());\n\n    // Create a new date object from the random timestamp\n    const randomDate = new Date(randomTimestamp);\n\n    // Format the date as YYYY-MM-DD\n    const year = randomDate.getFullYear();\n    const month = String(randomDate.getMonth() + 1).padStart(2, '0'); // Months are zero-indexed\n    const day = String(randomDate.getDate()).padStart(2, '0');\n\n    // Return the formatted date string\n    return `${year}-${month}-${day}`;\n}\n\n// const members = flow.get('members') ?? {}\nconst members = {}\n\nconst names = msg.payload\n\nnames.forEach( (name) => {\n    const id = getRandomFiveDigitString()\n    // Assuming input name list represents actual people\n    // Adding member id to key to ensure it is unique\n    members[`${name} ${id}`] = {\n        'name': name,\n        'joined': getRandomDateString(),\n        'membershipId': id,\n    }\n})\n\nflow.set('members', members)","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":500,"y":920,"wires":[[]]},{"id":"ff8803e82ae8e0bb","type":"group","z":"44ceec9d5e933a9b","g":"c480c15e8ae54ca2","name":"Run this to update the front-end code files. Remember to change the uib-save node to point to your uibuilder node \\n ","style":{"fill":"#ffffff","fill-opacity":"0.31","label":true,"color":"#000000"},"nodes":["e50dc2ddd7b025a6","a477de9427090857","ca627c6704404be9","f197cb5ccc5f5177","96925ec19f0f5c11","3028a39579ad0503","98930d16c52dc719","f636f59656d8b9db"],"x":664,"y":863,"w":714,"h":178},{"id":"e50dc2ddd7b025a6","type":"inject","z":"44ceec9d5e933a9b","g":"ff8803e82ae8e0bb","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":725,"y":960,"wires":[["f197cb5ccc5f5177","96925ec19f0f5c11","98930d16c52dc719"]],"l":false},{"id":"a477de9427090857","type":"template","z":"44ceec9d5e933a9b","g":"ff8803e82ae8e0bb","name":"","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<!doctype html>\n<html lang=\"en\">\n\n<head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"icon\" href=\"../uibuilder/images/node-blue.ico\">\n\n    <title>Members In/Out Register - Node-RED UIBUILDER</title>\n    <meta name=\"description\" content=\"Node-RED UIBUILDER - Members In/Out Register\">\n\n    <!-- Your own CSS (defaults to loading uibuilders css)-->\n    <link type=\"text/css\" rel=\"stylesheet\" href=\"./index.css\" media=\"all\">\n\n    <!-- #region Supporting Scripts. These MUST be in the right order. Note no leading / -->\n    <script defer src=\"../uibuilder/uibuilder.iife.min.js\"></script>\n    <script defer src=\"./index.js\"></script>\n    <!-- #endregion -->\n\n</head>\n\n<body class=\"uib\">\n\n    <h1 class=\"with-subtitle\">Members In/Out Register</h1>\n    <div role=\"doc-subtitle\">Using Node-RED and the UIBUILDER IIFE library.</div>\n\n    <div>\n        <label for=\"search\">Search: </label>\n        <input id=\"search\" type=\"text\" placeholder=\"Enter first characters of name to search for\" style=\"width: 50%;\" oninput=\"doSearch(event)\">\n    </div>\n\n    <ul id=\"results\"></ul>\n\n</body>\n\n</html>","output":"str","x":1040,"y":920,"wires":[["ca627c6704404be9"]]},{"id":"ca627c6704404be9","type":"uib-save","z":"44ceec9d5e933a9b","g":"ff8803e82ae8e0bb","url":"sign-in","uibId":"bdd19c31149a953e","folder":"src","fname":"","createFolder":false,"reload":false,"usePageName":false,"encoding":"utf8","mode":438,"name":"","topic":"","x":1190,"y":960,"wires":[]},{"id":"f197cb5ccc5f5177","type":"change","z":"44ceec9d5e933a9b","g":"ff8803e82ae8e0bb","name":"index.html","rules":[{"t":"set","p":"fname","pt":"msg","to":"index.html","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":850,"y":920,"wires":[["a477de9427090857"]]},{"id":"96925ec19f0f5c11","type":"change","z":"44ceec9d5e933a9b","g":"ff8803e82ae8e0bb","name":"index.js","rules":[{"t":"set","p":"fname","pt":"msg","to":"index.js","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":840,"y":960,"wires":[["3028a39579ad0503"]]},{"id":"3028a39579ad0503","type":"template","z":"44ceec9d5e933a9b","g":"ff8803e82ae8e0bb","name":"","field":"payload","fieldType":"msg","format":"javascript","syntax":"mustache","template":"let fullMembersList = {}\nlet members = []\nlet today = {}\n\nfunction doInOut(event) {\n    const key = event.target.value\n    // If the person not yet seen today, create them an entry\n    if (!today[key]) today[key] = { status: 'out' }\n    // Toggle their status\n    if (today[key].status === 'out') {\n        today[key].status = 'in'\n        today[key].inTime = new Date()\n    } else {\n        today[key].status = 'out'\n        today[key].outTime = new Date()\n    }\n    // send today update to node-red\n    uibuilder.send({\n        topic: 'person-status-change',\n        key: key,\n        record: today[key],\n    })\n    // Toggle the button class\n    const id = key.replace(/ /g, '_')\n    const btn = $(`#${id}`)\n    btn.setAttribute('class', today[key].status)\n    // console.log('btn click: ', key, id, today[key].status, btn)\n}\n\n// Listen for incoming messages from Node-RED and action\n// Actually, you only need to send the list of member keys, not the full thing.\nuibuilder.onTopic('full-members-list', (msg) => {\n    fullMembersList = msg.payload\n    members = Object.keys(fullMembersList)\n    console.log('full-members-list received')\n})\n\nuibuilder.onTopic('today-cache', (msg) => {\n    today = msg.payload\n    members = Object.keys(fullMembersList)\n    console.log('today-list received')\n})\n\nfunction doSearch(event) {\n    if (fullMembersList.length < 1) {\n        alert('Members list not yet received')\n        return\n    }\n    if (members.length < 1) members = Object.keys(fullMembersList)\n\n    // console.log('input', event.data, event.target.value, event)\n    const val = event.target.value\n    let result\n    if (val.length >= 1) {\n        // Get a reference to the results list\n        const ul = $('#results')\n        // Clear out all of the results\n        ul.innerHTML = ''\n        // Search for members starting with the types letters\n        // result = members.filter( m => m.startsWith(val))\n        result = members.filter(m => (m.toLowerCase()).includes(val.toLowerCase()))\n        console.log(`${result.length} names found`)\n        // Loop through the results adding list entries\n        result.forEach(r => {\n            const li = document.createElement('li')\n            const id = r.replace(/ /g, '_')\n            let status\n            if (today[r]) {\n                status = today[r].status\n            } else {\n                status = 'out'\n            }\n            li.innerHTML = `<button id=\"${id}\" onclick=\"doInOut(event)\" class=\"${status}\" value=\"${r}\" data-name=\"${fullMembersList[r].name}\">${r}</button>`\n            ul.appendChild(li)\n        })\n    }\n}\n","output":"str","x":1040,"y":960,"wires":[["ca627c6704404be9"]]},{"id":"98930d16c52dc719","type":"change","z":"44ceec9d5e933a9b","g":"ff8803e82ae8e0bb","name":"index.css","rules":[{"t":"set","p":"fname","pt":"msg","to":"index.css","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":840,"y":1000,"wires":[["f636f59656d8b9db"]]},{"id":"f636f59656d8b9db","type":"template","z":"44ceec9d5e933a9b","g":"ff8803e82ae8e0bb","name":"","field":"payload","fieldType":"msg","format":"css","syntax":"mustache","template":"/* Load defaults from `<userDir>/node_modules/node-red-contrib-uibuilder/front-end/uib-brand.min.css`\n * This version auto-adjusts for light/dark browser settings but might not be as complete.\n */\n@import url(\"../uibuilder/uib-brand.min.css\");\n\n/* OR, load the defaults from the older `<userDir>/node_modules/node-red-contrib-uibuilder/front-end/uib-styles.css` */\n/* @import url(\"../uibuilder/uib-styles.css\"); */\n\n.in {\n    color: hsl(120, 100%, 50%);\n}\n\n.out {\n    color: hsl(290, 100%, 80%);\n}","output":"str","x":1040,"y":1000,"wires":[["ca627c6704404be9"]]}]
1 Like

Thanks.

I'll import it - but not today - and look at it tomorrow I hope.

There may be a delay on my replying - as mentioned.

Seems I may have to get a chatGPT longin. As I guess that give you more control of questions. (Like history?)

1 Like

Yes, it does. There are others as well, I occasionally use Claude, Gemini, Perplexity.ai, or even Microsoft Bing CoPilot. But I find ChatGPT is usually good enough. I may ask the others if ChatGPT gives me a weird or incorrect answer.

I'd heard of bing and coPilot but none of the others. But didn't know for what they were meant.

Wow, I am living under a rock WRT that stuff.

Andrew, This is why I just copied your actual forum post into chatGPT. It did a pretty good job of answering it. Remember you can ask follow-up questions as well.

1 Like

Yeah, ok.

As much as I don't mind trying things, but sometimes if I change too many t hings, it doesn't help me.

And I have only now touched on chatGPT (or any other LLM AI.)

I'll see what Julian's code does for me but it may not be until I get back from this pending trip.

WRT follow up questions.... I'm guessing that is only if you have an account?

Isn't that the definition of being an Australian?! :rofl:

I can't remember I'm afraid, I signed up when it was in beta so I could try it early - as it impacts on my professional work as well.

An account doesn't cost anything anyway.