From ea455cfa81df3fa2accbe203e56f29ea77e6eca0 Mon Sep 17 00:00:00 2001 From: awdev Date: Tue, 7 May 2024 19:00:56 -0700 Subject: [PATCH] Bin Development 2024-05-02 (#1190) * added caching for images (need to fix) * added part fetching (needs fixing) * Add more clicky buttons * Add endpoint for project suggestions * Improve project idea prompt * Add project idea section --------- Co-authored-by: Max Wofford --- package.json | 1 + pages/api/bin/openai.js | 40 +++++ public/bin/landing-new/gambling.js | 267 ++++++++--------------------- public/bin/landing-new/index.html | 22 ++- public/bin/landing-new/style.css | 27 +++ public/bin/selector/index.html | 44 +---- public/bin/selector/script.js | 95 ++++++++-- public/bin/selector/style.css | 2 +- yarn.lock | 115 ++++++++++++- 9 files changed, 358 insertions(+), 255 deletions(-) create mode 100644 pages/api/bin/openai.js diff --git a/package.json b/package.json index 07dd8a65..0c393a52 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "next": "^12.3.1", "next-transpile-modules": "^10.0.1", "nextjs-current-url": "^1.0.3", + "openai": "^4.42.0", "pcb-stackup": "^4.2.8", "react": "^17.0.2", "react-before-after-slider-component": "^1.1.8", diff --git a/pages/api/bin/openai.js b/pages/api/bin/openai.js new file mode 100644 index 00000000..d3374612 --- /dev/null +++ b/pages/api/bin/openai.js @@ -0,0 +1,40 @@ +import OpenAI from 'openai'; + +const generateProjectIdea = async (parts) => { + + let prompt = `I'm running a hardware program around the raspberry pi pico w where high schoolers will build a simple project using the following parts. Please propose a simple project in 1-2 sentences to use as a prompt for the high schoolers to build with: + +` + parts.forEach((part) => { + prompt += `- ${part}\n` + }) + + prompt += ` + The project should only involve household items like lamps. The project should only use sensors provided, and use those sensors for their intended use. For example, an accelerometer cannot be used to measure humidity or tilt.` + + // expects OPENAI_API_KEY + const openai = new OpenAI(); + const chatCompletion = await openai.chat.completions.create({ + messages: [{ role: 'user', content: prompt }], + model: 'gpt-3.5-turbo', + }); + + return chatCompletion.choices[0].message.content +} + +export default async function handler(req, res) { + const requestedParts = req.body.parts + + const availablePartsReq = await fetch('https://hackclub.com/api/bin/wokwi/parts') + const availableParts = await availablePartsReq.json() + + // check that the requested parts are in the available parts + const parts = requestedParts.map((requestedPart) => { + return availableParts.find((availablePart) => availablePart.wokwiName === requestedPart)?.name + }) + + const recommendation = await generateProjectIdea(parts) + + res.send({recommendation, parts}) +} + diff --git a/public/bin/landing-new/gambling.js b/public/bin/landing-new/gambling.js index 33444f89..d5a78fba 100644 --- a/public/bin/landing-new/gambling.js +++ b/public/bin/landing-new/gambling.js @@ -1,7 +1,7 @@ var fetchedParts; +var selectedParts = [] var rolled = false; async function fetchParts() { - /* const response = await fetch('https://hackclub.com/api/bin/wokwi/parts/'); if (!response.ok) { throw new Error('Network response was not ok.'); @@ -9,192 +9,19 @@ async function fetchParts() { data = await response.json(); data = removeItemByAttribute(data, "type", "Microprocessor"); - console.log(data)*/ - data = [ - { - "name": "Motion Sensor", - "flavorText": "Detects movement.", - "type": "Component", - "wokwiName": "wokwi-pir-motion-sensor", - "wokwiXOffset": 89 - }, - { - "name": "Temperature Sensor", - "flavorText": "Temp checker!", - "type": "Component", - "wokwiName": "board-ds18b20", - "wokwiXOffset": 32.88 - }, - { - "name": "Clock (RTC)", - "flavorText": "It's a clock!", - "type": "Component", - "wokwiName": "wokwi-ds1307", - "wokwiXOffset": 99 - }, - { - "name": "Buzzer", - "flavorText": "Make noise!", - "type": "Component", - "wokwiName": "wokwi-buzzer", - "notes (internal)": "Double check if active or passive", - "wokwiXOffset": 69 - }, - { - "name": "Humidity", - "flavorText": "Moisture monitor", - "imageUrl": "https://cloud-q01dbzfhj-hack-club-bot.vercel.app/1humidity.png", - "type": "Component", - "wokwiName": "wokwi-dht22", - "notes (internal)": "We actually send the dht11", - "wokwiXOffset": 56 - }, - { - "name": "Rotary Encoder", - "flavorText": "Detect spinning!", - "type": "Component", - "wokwiName": "wokwi-ky-040", - "wokwiXOffset": 120 - }, - { - "name": "Shift Register", - "flavorText": "Switch inputs!", - "type": "Component", - "wokwiName": "wokwi-74hc595", - "wokwiXOffset": 76 - }, - { - "name": "Range finder", - "flavorText": "Measure distance!", - "type": "Component", - "wokwiName": "wokwi-hc-sr04", - "wokwiXOffset": 168.7 - }, - { - "name": "Keypad", - "flavorText": "Dial a number!", - "type": "Component", - "wokwiName": "wokwi-membrane-keypad", - "wokwiXOffset": 264.8 - }, - { - "name": "Accelerometer", - "flavorText": "Speedchecker!", - "type": "Component", - "wokwiName": "wokwi-mpu6050", - "wokwiXOffset": 82 - }, - { - "name": "Neopixel LED", - "flavorText": "Technicolor!", - "type": "Component", - "wokwiName": "wokwi-neopixel", - "wokwiXOffset": 21 - }, - { - "name": "LED", - "flavorText": "It's lit!", - "imageUrl": "https://cloud-q01dbzfhj-hack-club-bot.vercel.app/4led.png", - "type": "Component", - "wokwiName": "wokwi-led", - "wokwiXOffset": 24 - }, - { - "name": "Stepper Motor", - "flavorText": "It spins!", - "type": "Component", - "wokwiName": "wokwi-stepper-motor,wokwi-a4988", - "wokwiXOffset": 162 - }, - { - "name": "Slider", - "flavorText": "A sliding input!", - "type": "Component", - "wokwiName": "wokwi-slide-potentiometer", - "wokwiXOffset": 210.2 - }, - { - "name": "Thermistor", - "flavorText": "Temperature checker!", - "type": "Component", - "wokwiName": "wokwi-ntc-temperature-sensor", - "wokwiXOffset": 139 - }, - { - "name": "Relay", - "flavorText": "Turn things on and off!", - "imageUrl": "https://cloud-q01dbzfhj-hack-club-bot.vercel.app/0relay.png", - "type": "Component", - "wokwiName": "wokwi-relay-module", - "wokwiXOffset": 130 - }, - { - "name": "LCD", - "flavorText": "Display text!", - "type": "Component", - "wokwiName": "wokwi-lcd1602", - "wokwiXOffset": 303.2 - }, - { - "name": "Servo", - "flavorText": "Move stuff", - "type": "Component", - "wokwiName": "wokwi-servo", - "wokwiXOffset": 178.2 - }, - { - "name": "Joystick", - "flavorText": "It's a joystick!", - "type": "Component", - "wokwiName": "wokwi-analog-joystick", - "wokwiXOffset": 98 - }, - { - "name": "Potentiometer", - "flavorText": "It's a dial!", - "type": "Component", - "wokwiName": "wokwi-potentiometer", - "wokwiXOffset": 76.6 - }, - { - "name": "Multicolor LED", - "flavorText": "Now, in color!", - "type": "Component", - "wokwiName": "wokwi-rgb-led", - "wokwiXOffset": 30 - }, - { - "name": "Photoresistor", - "flavorText": "Detect light!", - "type": "Component", - "wokwiName": "wokwi-photoresistor-sensor", - "wokwiXOffset": 173.6 - }, - { - "name": "Button", - "flavorText": "Bop it!", - "type": "Component", - "wokwiName": "wokwi-pushbutton", - "wokwiXOffset": 67 - }, - { - "name": "IR Reciever", - "flavorText": "Detect Infrared!", - "imageUrl": "https://cloud-q01dbzfhj-hack-club-bot.vercel.app/2ir.png", - "type": "Component", - "wokwiName": "wokwi-ir-receiver", - "wokwiXOffset": 62 - }, - { - "name": "LED Matrix", - "flavorText": "Display stuff!", - "type": "Component", - "wokwiName": "wokwi-max7219-matrix", - "wokwiXOffset": 340 - } - ] + console.log(data) return data } +async function preloadImage(item) { + let response = await fetch(item.imageUrl); + let blob = response.blob(); + return blob +} +async function saveImageToCache(item) { + const image = await preloadImage(item) + const blob = URL.createObjectURL(image) + localStorage.setItem(item.wokwiName, blob); +} function removeItemByAttribute(arr, attr, value) { return arr.filter(item => item[attr] !== value); } @@ -252,23 +79,79 @@ function rollParts() { addComponentsToPage(data) } rolled = true - let results = {} - let counter = 0 + document.querySelector(".gambling-build").classList.remove("disabled") + let results = [] document.querySelectorAll(".gambling-item-wrapper").forEach((element) => { let randomThingy = getRandomInt(fetchedParts.length - 1) let spinnerImage = element.childNodes[2].childNodes[0] let partTitle = element.childNodes[2].childNodes[1] let flavorText = element.childNodes[2].childNodes[2] let result = fetchedParts[randomThingy] - spinnerImage.src = (result.imageURL == "" || result.imageURL == undefined) ? "https://awdev.codes/images/ww.gif" : result.imageURL + //spinnerImage.src = (result.imageUrl == "" || result.imageUrl == undefined) ? "https://awdev.codes/images/ww.gif" : result.imageUrl + spinnerImage.src = (result.imageUrl == "" || result.imageUrl == undefined) ? localStorage.getItem("wokwi-pedro") : localStorage.getItem(result.wokwiName) partTitle.innerText = result.name; flavorText.innerText = result.flavorText; - results[counter] = result.wokwiName - counter++; + results.push(result.wokwiName) }) - console.log(results) + selectedParts = results } + +async function generateBuildLink(e) { + if (!rolled) { + return + } + e.classList.add("disabled") + e.classList.add("loading") + const payload = { + parts: selectedParts + }; + + try { + const response = await fetch('/api/bin/wokwi/new/', { + mode: 'cors', + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload) + }) + if (!response.ok) { + throw new Error('Network response was not ok'); + } + const json = await response.json() + const shareLink = json.shareLink + + // window.open(shareLink, '_blank').focus() + } catch (error) { + console.error('Error:', error) + // e.classList.add("error") + } + e.classList.remove("disabled") + e.classList.remove("loading") +} window.addEventListener("load", (e) => { - fetchParts().then(parts => { fetchedParts = parts }); + fetchParts().then(parts => { + fetchedParts = parts; + fetchedParts.forEach(part => { + if (!(part.imageUrl == undefined)) { + console.log(part.wokwiName) + saveImageToCache(part); + } + }) + saveImageToCache({ wokwiName: "wokwi-pedro", imageUrl: "https://awdev.codes/images/ww.gif" }) + }); + + document.querySelector("#generate-project-idea").addEventListener("click", async (e) => { + document.querySelector('#project-idea').innerText = "Thinking..." + const res = await fetch('/api/bin/openai/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ parts: selectedParts }) + }) + const json = await res.json() + document.querySelector('#project-idea').innerText = json.recommendation + }) }) \ No newline at end of file diff --git a/public/bin/landing-new/index.html b/public/bin/landing-new/index.html index 29237413..5677945a 100644 --- a/public/bin/landing-new/index.html +++ b/public/bin/landing-new/index.html @@ -60,10 +60,26 @@
- + - - + + +
+ +
+
+

What are we building today?

+ +

💡 Need an idea? Click the raccoon!

+
+
+ +

(It doesn't know much about electronics, but it'll try its best.)

+
+

🗑️

+