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 <max@maxwofford.com>
This commit is contained in:
awdev 2024-05-07 19:00:56 -07:00 committed by GitHub
parent 457d5b2607
commit ea455cfa81
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 358 additions and 255 deletions

View file

@ -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",

40
pages/api/bin/openai.js Normal file
View file

@ -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})
}

View file

@ -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
}
window.addEventListener("load", (e) => {
fetchParts().then(parts => { fetchedParts = parts });
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;
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
})
})

View file

@ -60,10 +60,26 @@
</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>

View file

@ -4,6 +4,20 @@
box-sizing: border-box;
}
.container {
margin: auto;
padding: 0 20px;
max-width: 52em;
}
.hoverable {
transition: 500ms transform;
}
.hoverable:hover {
transform: scale(1.05);
}
.flex-lb {
width: 100%;
display: block;
@ -336,3 +350,16 @@
font-size: 20px;
}
}
.disabled {
cursor: not-allowed !important;
}
.loading {
cursor: wait !important;
}
#generate-project-idea {
cursor: pointer;
}

View file

@ -15,49 +15,7 @@
<h1 class="selector-title">The Bin</h1>
<button class="selector-continue">Let's Build!<img src="../icons/arrow.svg"></button>
</div>
<div class="selector-main">
<div part_name="relay" class="selector-item">
<img class="selector-image" src="../parts/relay.png">
<div class="selector-item-name">Relay</div>
<div class="selector-item-description">On/Off Switch</div>
</div>
<div part_name="laser" class="selector-item">
<img class="selector-image" src="../parts/laser.png">
<div class="selector-item-name">Laser</div>
<div class="selector-item-description">Shoots Light</div>
</div>
<div part_name="led" class="selector-item">
<img class="selector-image" src="../parts/led.png">
<div class="selector-item-name">LED</div>
<div class="selector-item-description">Shines light</div>
</div>
<div part_name="humidity" class="selector-item">
<img class="selector-image" src="../parts/humidity.png">
<div class="selector-item-name">Humidity</div>
<div class="selector-item-description">Measures humidity</div>
</div>
<span class="flex-lb"></span>
<div part_name="mic" class="selector-item">
<img class="selector-image" src="../parts/mic-right.png">
<div class="selector-item-name">Mic</div>
<div class="selector-item-description">Records sounds</div>
</div>
<div part_name="mic" class="selector-item">
<img class="selector-image" src="../parts/mic.png">
<div class="selector-item-name">Mic</div>
<div class="selector-item-description">Records sounds</div>
</div>
<div part_name="led" class="selector-item">
<img class="selector-image" src="../parts/led.png">
<div class="selector-item-name">LED</div>
<div class="selector-item-description">Shines Light</div>
</div>
<div part_name="relay" class="selector-item">
<img class="selector-image" src="../parts/relay.png">
<div class="selector-item-name">Relay</div>
<div class="selector-item-description">On/Off Switch</div>
</div>
</div>
<div class="selector-main"></div>
<span class="selector-number"></span>
</div>
<script src="./script.js"></script>

View file

@ -1,24 +1,36 @@
items = document.querySelectorAll(".selector-item")
var fetchedParts;
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();
items.forEach(item => {
item.addEventListener("click", () => {
let isSelected = item.className.includes("selected")
if (isSelected) {
item.classList.remove("selected")
} else {
if (getSelectedItems().length < 3) {
item.classList.add("selected")
data = removeItemByAttribute(data, "type", "Microprocessor");
console.log(data)
return data
}
function removeItemByAttribute(arr, attr, value) {
return arr.filter(item => item[attr] !== value);
}
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);
addPartToPage(item)
}
recalculateSelected();
})
});
function getSelectedItems() {
return document.querySelectorAll(".selected")
}
function recalculateSelected() {
let numSelectedItems = getSelectedItems().length
let selections = []
items = document.querySelectorAll(".selector-item")
document.querySelector(".selector-number").innerText = `${3 - numSelectedItems} choices remaining.`
if (3 - numSelectedItems == 0) {
items.forEach(item => {
@ -42,5 +54,58 @@ function recalculateSelected() {
return numSelectedItems
}
function addPartToPage(part) {
/*
<div part_name="mic" class="selector-item">
<img class="selector-image" src="../parts/mic.png">
<div class="selector-item-name">Mic</div>
<div class="selector-item-description">Records sounds</div>
</div>
*/
let selectorItem = document.createElement("div")
selectorItem.setAttribute("part_name", part.wokwiName)
selectorItem.className = "selector-item"
let selectorImage = document.createElement("img")
selectorImage.src = localStorage.getItem(part.wokwiName)
selectorImage.className = "selector-image"
selectorItem.appendChild(selectorImage)
let selectorItemName = document.createElement("div")
selectorItemName.innerText = part.name
selectorItemName.className = "selector-item-name"
selectorItem.appendChild(selectorItemName)
let selectorItemDesc = document.createElement("div")
selectorItemDesc.innerText = part.flavorText
selectorItemDesc.className = "selector-item-description"
selectorItem.appendChild(selectorItemDesc)
document.getElementsByClassName("selector-main")[0].appendChild(selectorItem)
selectorItem.addEventListener("click", () => {
let isSelected = selectorItem.className.includes("selected")
if (isSelected) {
selectorItem.classList.remove("selected")
} else {
if (getSelectedItems().length < 3) {
selectorItem.classList.add("selected")
}
}
recalculateSelected();
})
}
window.addEventListener("load", (e) => {
recalculateSelected();
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" })
});
})

View file

@ -72,7 +72,7 @@
}
.selector-item-name {
font-size: 45px;
font-size: 35px;
font-weight: bold;
}

115
yarn.lock
View file

@ -2448,6 +2448,14 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433"
integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==
"@types/node-fetch@^2.6.4":
version "2.6.11"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24"
integrity sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==
dependencies:
"@types/node" "*"
form-data "^4.0.0"
"@types/node@*":
version "17.0.23"
resolved "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz"
@ -2463,6 +2471,13 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.40.tgz#968d64746d20cac747a18ca982c0f1fe518c031c"
integrity sha512-+yno3ItTEwGxXiS/75Q/aHaa5srkpnJaH+kdkTVJ3DtJEwv92itpKbxU+FjPoh2m/5G9zmUQfrL4A4C13c+iGA==
"@types/node@^18.11.18":
version "18.19.32"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.32.tgz#96e4c80dca0ccf48505add2a399f36465955e0be"
integrity sha512-2bkg93YBSDKk8DLmmHnmj/Rwr18TLx7/n+I23BigFwgexUJoMHZOd8X1OFxuF/W3NN0S2W2E5sVabI5CPinNvA==
dependencies:
undici-types "~5.26.4"
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz"
@ -2590,6 +2605,13 @@
dependencies:
tslib "^2.3.0"
abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
dependencies:
event-target-shim "^5.0.0"
acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
@ -2605,6 +2627,13 @@ add@^2.0.6:
resolved "https://registry.npmjs.org/add/-/add-2.0.6.tgz"
integrity sha512-j5QzrmsokwWWp6kUcJQySpbG+xfOBqqKnup3OIk1pz+kB/80SLorZ9V8zHFLO92Lcd+hbvq8bT+zOGoPkmBV0Q==
agentkeepalive@^4.2.1:
version "4.5.0"
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923"
integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==
dependencies:
humanize-ms "^1.2.1"
airtable-plus@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/airtable-plus/-/airtable-plus-1.0.4.tgz"
@ -4370,6 +4399,11 @@ etag@1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
event-target-shim@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
eventemitter3@^4.0.1:
version "4.0.7"
resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz"
@ -4544,6 +4578,11 @@ forever-agent@~0.6.1:
resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data-encoder@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040"
integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz"
@ -4562,6 +4601,14 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
formdata-node@^4.3.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/formdata-node/-/formdata-node-4.4.1.tgz#23f6a5cb9cb55315912cbec4ff7b0f59bbd191e2"
integrity sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==
dependencies:
node-domexception "1.0.0"
web-streams-polyfill "4.0.0-beta.3"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
@ -5324,6 +5371,13 @@ https-browserify@1.0.0, https-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==
humanize-ms@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==
dependencies:
ms "^2.0.0"
iconv-lite@0.4.19:
version "0.4.19"
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz"
@ -6571,7 +6625,7 @@ ms@2.1.2:
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@^2.1.1:
ms@^2.0.0, ms@^2.1.1:
version "2.1.3"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@ -6723,11 +6777,23 @@ nextjs-current-url@^1.0.3:
next "^13.4.7"
react "^18.2.0"
node-domexception@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
node-fetch@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-fetch@^2.6.7:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"
node-html-parser@1.4.9:
version "1.4.9"
resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-1.4.9.tgz#3c8f6cac46479fae5800725edb532e9ae8fd816c"
@ -6898,6 +6964,20 @@ open@^8.4.0:
is-docker "^2.1.1"
is-wsl "^2.2.0"
openai@^4.42.0:
version "4.42.0"
resolved "https://registry.yarnpkg.com/openai/-/openai-4.42.0.tgz#8e6517839dcfbde848b981965989ccd2a4d70b1c"
integrity sha512-xbiQQ2YNqdkE6cHqeWKa7lsAvdYfgp84XiNFOVkAMa6+9KpmOL4hCWCRR6e6I/clpaens/T9XeLVtyC5StXoRw==
dependencies:
"@types/node" "^18.11.18"
"@types/node-fetch" "^2.6.4"
abort-controller "^3.0.0"
agentkeepalive "^4.2.1"
form-data-encoder "1.7.2"
formdata-node "^4.3.2"
node-fetch "^2.6.7"
web-streams-polyfill "^3.2.1"
optimism@^0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.18.0.tgz#e7bb38b24715f3fdad8a9a7fc18e999144bbfa63"
@ -8616,6 +8696,11 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
trim-lines@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"
@ -8788,6 +8873,11 @@ unbox-primitive@^1.0.2:
has-symbols "^1.0.3"
which-boxed-primitive "^1.0.2"
undici-types@~5.26.4:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
unherit@^1.0.4:
version "1.1.3"
resolved "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz"
@ -9284,6 +9374,21 @@ web-namespaces@^2.0.0:
resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692"
integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==
web-streams-polyfill@4.0.0-beta.3:
version "4.0.0-beta.3"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38"
integrity sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==
web-streams-polyfill@^3.2.1:
version "3.3.3"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b"
integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
@ -9306,6 +9411,14 @@ whats-that-gerber@^4.2.7:
dependencies:
xtend "^4.0.2"
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
whatwg-url@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"