Skip to content

Commit

Permalink
Bin Development 2024-05-02 (#1190)
Browse files Browse the repository at this point in the history
* 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 <max@maxwofford.com>
  • Loading branch information
aaronw-dev and maxwofford committed May 8, 2024
1 parent 457d5b2 commit ea455cf
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 255 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
40 changes: 40 additions & 0 deletions pages/api/bin/openai.js
Original file line number Diff line number Diff line change
@@ -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})
}

267 changes: 75 additions & 192 deletions public/bin/landing-new/gambling.js
Original file line number Diff line number Diff line change
@@ -1,200 +1,27 @@
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.');
}
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);
}
Expand Down Expand Up @@ -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
})
})
22 changes: 19 additions & 3 deletions public/bin/landing-new/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,26 @@ <h2 class="gambling-subheader">Let's get you your parts!</h2>
</div>
</div>
<div class="gambling-controls">
<button onclick="rollParts()" class="gambling-roll">Roll!</button>
<button onclick="rollParts()" class="gambling-roll hoverable">Roll!</button>
<span class="flex-lb"></span>
<button onclick="location.href='../selector'" class="gambling-select">Manual Selection</button>
<button onclick="" class="gambling-build">Continue<img src="../icons/arrow.svg"></button>
<button onclick="location.href='../selector/index.html'" class="gambling-select hoverable">Manual
Selection</button>
<button onclick="generateBuildLink(this)" class="gambling-build hoverable disabled">Continue<img
src="../icons/arrow.svg"></button>
</div>
</section>
<section class="project-idea-section section">
<div class="container">
<h1>What are we building today?</h1>
<textarea id="project-name" placeholder="I'm going to build a..."></textarea>
<h2>💡 Need an idea? Click the raccoon!</h2>
<div style="display: flex;">
<div>
<img src="../images/idea.png" style="margin: 0 auto; display: inline; max-width: 10em" id="generate-project-idea">
<p><em>(It doesn't know much about electronics, but it'll try its best.)</em></p>
</div>
<p id="project-idea">🗑️</p>
</div>
</div>
</section>
<footer>
Expand Down
Loading

0 comments on commit ea455cf

Please sign in to comment.