diff --git a/.github/workflows/azure-webapps-node.yml b/.github/workflows/azure-webapps-node.yml new file mode 100644 index 0000000..642d476 --- /dev/null +++ b/.github/workflows/azure-webapps-node.yml @@ -0,0 +1,84 @@ +# This workflow will build and push a node.js application to an Azure Web App when a commit is pushed to your default branch. +# +# This workflow assumes you have already created the target Azure App Service web app. +# For instructions see https://docs.microsoft.com/en-us/azure/app-service/quickstart-nodejs?tabs=linux&pivots=development-environment-cli +# +# To configure this workflow: +# +# 1. Download the Publish Profile for your Azure Web App. You can download this file from the Overview page of your Web App in the Azure Portal. +# For more information: https://docs.microsoft.com/en-us/azure/app-service/deploy-github-actions?tabs=applevel#generate-deployment-credentials +# +# 2. Create a secret in your repository named AZURE_WEBAPP_PUBLISH_PROFILE, paste the publish profile contents as the value of the secret. +# For instructions on obtaining the publish profile see: https://docs.microsoft.com/azure/app-service/deploy-github-actions#configure-the-github-secret +# +# 3. Change the value for the AZURE_WEBAPP_NAME. Optionally, change the AZURE_WEBAPP_PACKAGE_PATH and NODE_VERSION environment variables below. +# +# For more information on GitHub Actions for Azure: https://github.com/Azure/Actions +# For more information on the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy +# For more samples to get started with GitHub Action workflows to deploy to Azure: https://github.com/Azure/actions-workflow-samples + + + + + + + + + +name: Build and Deploy Node.js App to Azure + +on: + push: + branches: [ "master" ] + workflow_dispatch: + +env: + AZURE_WEBAPP_NAME: recipe-for-success + AZURE_WEBAPP_PACKAGE_PATH: '.' + NODE_VERSION: '14.x' + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + - name: Install Dependencies + run: npm install + - name: Build Application + run: npm run build --if-present + - name: Test Application + run: npm run test --if-present + - name: Upload artifact for deployment job + uses: actions/upload-artifact@v3 + with: + name: node-app + path: . + + deploy: + permissions: + contents: none + runs-on: ubuntu-latest + needs: build + steps: + - name: Download artifact from build job + uses: actions/download-artifact@v3 + with: + name: node-app + - name: 'Login via Azure CLI' + uses: azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + - name: 'Deploy to Azure WebApp' + uses: azure/webapps-deploy@v2 + with: + app-name: ${{ env.AZURE_WEBAPP_NAME }} + publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }} + package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }} diff --git a/.gitignore b/.gitignore index 97aca2e..6ed48a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ .env -node_modules \ No newline at end of file +node_modules diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2955010 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/server.js" + } + ] +} \ No newline at end of file diff --git a/controllers/email.js b/controllers/email.js new file mode 100644 index 0000000..4213ebe --- /dev/null +++ b/controllers/email.js @@ -0,0 +1,81 @@ +const nodemailer = require("nodemailer"); +const recipeFromStream = require("./stream.js"); +const path = require("path"); + + +async function processEmail(req, res) { + console.log(`is picked up ${req.body.pictureSectionText}`) + let recipe = recipeFromStream.getStreamRecipe(); + // let recipe = recipeFromStream.getStreamRecipe() !== "" ? recipeFromStream.getStreamRecipe() : req.body.pictureSectionText; + // let recipe = req.body.pictureSectionText !== "" ? req.body.pictureSectionText : recipeFromStream.getStreamRecipe() ; + let url = recipeFromStream.getUrl(); + console.log(`email.js file ${url}`) + var transporter = nodemailer.createTransport({ + service: process.env.service, + auth: { + user: process.env.from, + pass: process.env.third_party_app_password, + }, + }); + + const emailDocument = ` + + + + + + ${recipe} +
+ Embedded image: +
+ + + + `; + + // if (recipe !== "") { + var mailOptions = { + from: process.env.from, + to: req.query.user_email_address, + subject: " Your recipe from recipe-for-success dynamic app", + text: recipe, + html: emailDocument, + attachments: [ + { + filename: "url_folder.txt", + path: path.join(__dirname, "../public/url_folder/url_folder.txt"), + cid: "url", + }, + ], + }; + // } else { + // console.log("doubleResponse is not defined yet."); + // } + + transporter.sendMail(mailOptions, function (error, info) { + if (error) { + res.status(500).json({emailStatus: info.response}); + console.log(error); + } else { + res.status(250).json({emailStatus: info.response}); + console.log("Email sent: " + info.response); + } + }); + + console.log(`user recipe ${recipe}`); + recipe = "Empty string"; + console.log(recipe) + + } + + module.exports = { + processEmail + } \ No newline at end of file diff --git a/controllers/email_picture_section.js b/controllers/email_picture_section.js new file mode 100644 index 0000000..e8620ce --- /dev/null +++ b/controllers/email_picture_section.js @@ -0,0 +1,80 @@ +const nodemailer = require("nodemailer"); +const path = require("path"); +const recipeFromStream = require("./stream.js"); + + +async function processEmail(req, res) { + console.log(`is picked up ${req.body.pictureTextSection}`) + const {user_email_address} = req.query; + + let recipe = req.body.pictureTextSection; + // let recipe = recipeFromStream.getStreamRecipe() !== "" ? recipeFromStream.getStreamRecipe() : req.body.pictureSectionText; + // let recipe = req.body.pictureSectionText !== "" ? req.body.pictureSectionText : recipeFromStream.getStreamRecipe() ; + let url = recipeFromStream.getUrl(); + console.log(`email.js file ${url}`) + var transporter = nodemailer.createTransport({ + service: process.env.service, + auth: { + user: process.env.from, + pass: process.env.third_party_app_password, + }, + }); + + const emailDocument = ` + + + + + + ${recipe} +
+ + + `; + + if (recipe !== "") { + var mailOptions = { + from: process.env.from, + to: user_email_address, + subject: " Your recipe from recipe-for-success dynamic app", + text: recipe, + html: emailDocument, + attachments: [ + { + filename: "url_folder.txt", + path: path.join(__dirname, "../public/url_folder/url_folder.txt"), + + }, + ], + }; + } else { + console.log("doubleResponse is not defined yet."); + } + + transporter.sendMail(mailOptions, function (error, info) { + if (error) { + res.status(500).json({emailStatus: info.response}); + console.log(error); + } else { + res.status(250).json({emailStatus: info.response}); + console.log("Email sent: " + info.response); + } + }); + + // console.log(`user recipe ${recipe}`); + // recipe = "Empty string"; + // console.log(recipe) + + } + + module.exports = { + processEmail + } \ No newline at end of file diff --git a/controllers/recipes.js b/controllers/recipes.js new file mode 100644 index 0000000..e8c51be --- /dev/null +++ b/controllers/recipes.js @@ -0,0 +1,118 @@ + +const path = require("path"); +const fs = require("fs"); +const { OpenAI } = require("openai"); +require("dotenv").config(); +let recipe = "Recipe:" +let url + +console.log(` server.js file value of recipe is: ${recipe}`) + + +const openai = new OpenAI({ + apiKey: process.env.openaiAPI, + }); + + + +// async function processEmail(req, res) { +// var transporter = nodemailer.createTransport({ +// service: process.env.service, +// auth: { +// user: process.env.from, +// pass: process.env.third_party_app_password, +// }, +// }); + +// const emailDocument = ` +// +// +// +// +// +// ${recipe} +//
+// Embedded image: +//
+// +// +// +// `; + +// if (recipe !== "") { +// var mailOptions = { +// from: process.env.from, +// to: req.query.user_email_address, +// subject: "Your recipe from recipe-for-success dynamic app", +// text: recipe, +// html: emailDocument, +// attachments: [ +// { +// filename: "url_folder.txt", +// path: path.join(__dirname, "../public/url_folder/url_folder.txt"), +// cid: "url", +// }, +// ], +// }; +// } else { +// console.log("doubleResponse is not defined yet."); +// } + +// transporter.sendMail(mailOptions, function (error, info) { +// if (error) { +// console.log(error); +// } else { +// console.log("Email sent: " + info.response); +// } +// }); +// } + + async function processUpload(req, res) { + const picture = req.body.image; + console.log({ josue_upload: picture }); + // res.status(200).json({ message: `variable ${JSON.stringify(picture)} received` }); + + + + const response = await openai.chat.completions.create({ + model: "gpt-4o", + messages: [ + { + role: "user", + content: [ + { type: "text", text: "What can I cook with these ingredients?" }, + { + type: "image_url", + image_url: { + url: picture, + }, + }, + ], + }, + ], + }); + + recipe = response.choices[0].message.content + + console.log(`your recipe is ${recipe}`); + res.send(response.choices[0]); + + recipe = "Recipe:" + + + } + + + + +module.exports = { + processUpload, +} diff --git a/controllers/stream.js b/controllers/stream.js new file mode 100644 index 0000000..2e67293 --- /dev/null +++ b/controllers/stream.js @@ -0,0 +1,150 @@ +const path = require("path"); +const fs = require("fs"); +const { OpenAI } = require("openai"); + +require("dotenv").config(); +let streamRecipe = ""; // Reset for each request +let initialUrl = ""; + +const openai = new OpenAI({ + apiKey: process.env.openaiAPI, +}); + +function resetStreamRecipe() { + streamRecipe = "Recipe:"; +} + +function getStreamRecipe(newContent) { + if (newContent) { + streamRecipe += newContent; + } + return streamRecipe; +} + +function getUrl(newContent) { + if (newContent) { + initialUrl = newContent; + } + return initialUrl; +} + +async function processStream(req, res) { + resetStreamRecipe(); // Reset for each new request to avoid concatenation of previous recipes + + try { + res.setHeader("Content-Type", "text/event-stream"); + res.setHeader("Cache-Control", "no-cache"); + res.setHeader("Connection", "keep-alive"); + + const { + recipe_country_of_origin, + is_lactose_intolerant, + is_vegan, + what_are_user_other_dietary_requirements, + } = req.query; + + if (!recipe_country_of_origin) { + res.status(400).send("Missing recipe_country_of_origin query parameter"); + return; + } + + let prompt = generateRecipePrompt( + recipe_country_of_origin, + is_lactose_intolerant, + is_vegan, + what_are_user_other_dietary_requirements + ); + + // Max limit on prompt length + const maxAllowedLength = 4096; + if (prompt.length > maxAllowedLength) { + prompt = prompt.slice(0, maxAllowedLength); + } + + const stream = await openai.chat.completions.create({ + messages: [ + { + role: "user", + content: `${prompt}`, + }, + ], + model: "gpt-3.5-turbo", + max_tokens: 2000, + stream: true, + }); + + for await (const chunk of stream) { + const finishReason = chunk.choices[0].finish_reason; + + if (finishReason === "stop") { + break; + } + + const message = chunk.choices[0]?.delta?.content || ""; + const messageJSON = JSON.stringify({ message }); + res.write(`data: ${messageJSON}\n\n`); + getStreamRecipe(message); + } + + const imagePromise = openai.images + .generate({ + model: "dall-e-3", + prompt: `${streamRecipe}`, + n: 1, + size: "1024x1024", + }) + .then((image) => { + const messageJSON = JSON.stringify({ image }); + res.write(`data: ${messageJSON}\n\n`); + const folderPath = path.join(__dirname, "../public/url_folder"); + let url = image.data[0].url; + getUrl(url); + const filePath = path.join(folderPath, "../url_folder.txt"); + fs.writeFileSync(filePath, url); + }); + + const audioPromise = openai.audio.speech + .create({ + model: "tts-1", + voice: "alloy", + input: `${streamRecipe}`, + }) + .then(async (mp3) => { + const buffer = Buffer.from(await mp3.arrayBuffer()); + const speechFile = path.resolve("./speech.mp3"); + await fs.promises.writeFile(speechFile, buffer); + const messageJSON = JSON.stringify({ audio: buffer.toString("base64") }); + res.write(`data: ${messageJSON}\n\n`); + }); + + return Promise.all([imagePromise, audioPromise]).then(() => { + const messageJSON = JSON.stringify({ message: "stop" }); + res.write(`data: ${messageJSON}\n\n`); + res.end(); + }); + } catch (error) { + if (error.code === "invalid_api_key") { + const errorMessage = error.code; + const errorJSON = JSON.stringify({ errorMessage }); + res.write(`data: ${errorJSON}\n\n`); + console.error("Invalid API key provided. Please check your API key."); + } else { + console.error("An error occurred:", error.message); + } + } +} + +module.exports = { + processStream, + getStreamRecipe, + getUrl, +}; + +function generateRecipePrompt( + recipe_country_of_origin, + is_lactose_intolerant, + is_vegan, + what_are_user_other_dietary_requirements +) { + return `Provide a recipe for a dish from ${recipe_country_of_origin}, taking into account the fact that I'm ${is_lactose_intolerant === "true" ? "lactose intolerant" : "not lactose intolerant"}, ${is_vegan === "true" ? "vegan" : "not vegan"} and ${what_are_user_other_dietary_requirements === "" ? "I have no other dietary requirements" : what_are_user_other_dietary_requirements}.`; +} diff --git a/controllers/stream.test.js b/controllers/stream.test.js new file mode 100644 index 0000000..4bc4572 --- /dev/null +++ b/controllers/stream.test.js @@ -0,0 +1,28 @@ +const assert = require("assert"); +const generateRecipePrompt = require("/stream.js"); + +// Test case 1: All parameters are provided +assert.strictEqual( + generateRecipePrompt("Italy", "true", "false", "no dietary requirements"), + "Provide a recipe for a dish from Italy, taking into account the fact that I'm lactose intolerant not vegan and no dietary requirements" +); + +// Test case 2: No dietary requirements +assert.strictEqual( + generateRecipePrompt("Mexico", "false", "true", ""), + "Provide a recipe for a dish from Mexico, taking into account the fact that I'm not lactose intolerant or vegan and I have no other dietary requirements" +); + +// Test case 3: Only country of origin is provided +assert.strictEqual( + generateRecipePrompt("India", "false", "false", ""), + "Provide a recipe for a dish from India, taking into account the fact that I'm not lactose intolerant not vegan and I have no other dietary requirements" +); + +// Test case 4: All parameters are empty +assert.strictEqual( + generateRecipePrompt("", "", "", ""), + "Provide a recipe for a dish from , taking into account the fact that I'm not lactose intolerant not vegan and I have no other dietary requirements" +); + +console.log("All test cases passed!"); diff --git a/package-lock.json b/package-lock.json index 561d128..68f8248 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,7 +4,9 @@ "requires": true, "packages": { "": { + "name": "recipe-for-success", "dependencies": { + "body-parser": "^1.20.2", "dotenv": "^16.4.5", "express": "^4.18.3", "node": "^21.7.1", @@ -155,12 +157,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -261,9 +263,9 @@ } }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -409,16 +411,16 @@ } }, "node_modules/express": { - "version": "4.18.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", - "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -450,9 +452,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -992,9 +994,9 @@ } }, "node_modules/npm": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.4.0.tgz", - "integrity": "sha512-RS7Mx0OVfXlOcQLRePuDIYdFCVBPCNapWHplDK+mh7GDdP/Tvor4ocuybRRPSvfcRb2vjRJt1fHCqw3cr8qACQ==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.8.0.tgz", + "integrity": "sha512-wh93uRczgp7HDnPMiLXcCkv2hagdJS0zJ9KT/31d0FoXP02+qgN2AOwpaW85fxRWkinl2rELfPw+CjBXW48/jQ==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -1003,6 +1005,7 @@ "@npmcli/map-workspaces", "@npmcli/package-json", "@npmcli/promise-spawn", + "@npmcli/redact", "@npmcli/run-script", "@sigstore/tuf", "abbrev", @@ -1011,8 +1014,6 @@ "chalk", "ci-info", "cli-columns", - "cli-table3", - "columnify", "fastest-levenshtein", "fs-minipass", "glob", @@ -1048,7 +1049,6 @@ "npm-profile", "npm-registry-fetch", "npm-user-validate", - "npmlog", "p-map", "pacote", "parse-conflict-json", @@ -1069,73 +1069,71 @@ ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^7.2.1", - "@npmcli/config": "^8.0.2", - "@npmcli/fs": "^3.1.0", - "@npmcli/map-workspaces": "^3.0.4", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.1", - "@npmcli/run-script": "^7.0.4", - "@sigstore/tuf": "^2.3.0", + "@npmcli/arborist": "^7.5.2", + "@npmcli/config": "^8.3.2", + "@npmcli/fs": "^3.1.1", + "@npmcli/map-workspaces": "^3.0.6", + "@npmcli/package-json": "^5.1.0", + "@npmcli/promise-spawn": "^7.0.2", + "@npmcli/redact": "^2.0.0", + "@npmcli/run-script": "^8.1.0", + "@sigstore/tuf": "^2.3.3", "abbrev": "^2.0.0", "archy": "~1.0.0", - "cacache": "^18.0.2", + "cacache": "^18.0.3", "chalk": "^5.3.0", "ci-info": "^4.0.0", "cli-columns": "^4.0.0", - "cli-table3": "^0.6.3", - "columnify": "^1.6.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", - "glob": "^10.3.10", + "glob": "^10.3.15", "graceful-fs": "^4.2.11", - "hosted-git-info": "^7.0.1", - "ini": "^4.1.1", - "init-package-json": "^6.0.0", - "is-cidr": "^5.0.3", - "json-parse-even-better-errors": "^3.0.1", - "libnpmaccess": "^8.0.1", - "libnpmdiff": "^6.0.3", - "libnpmexec": "^7.0.4", - "libnpmfund": "^5.0.1", - "libnpmhook": "^10.0.0", - "libnpmorg": "^6.0.1", - "libnpmpack": "^6.0.3", - "libnpmpublish": "^9.0.2", - "libnpmsearch": "^7.0.0", - "libnpmteam": "^6.0.0", - "libnpmversion": "^5.0.1", - "make-fetch-happen": "^13.0.0", - "minimatch": "^9.0.3", - "minipass": "^7.0.4", + "hosted-git-info": "^7.0.2", + "ini": "^4.1.2", + "init-package-json": "^6.0.3", + "is-cidr": "^5.0.5", + "json-parse-even-better-errors": "^3.0.2", + "libnpmaccess": "^8.0.6", + "libnpmdiff": "^6.1.2", + "libnpmexec": "^8.1.1", + "libnpmfund": "^5.0.10", + "libnpmhook": "^10.0.5", + "libnpmorg": "^6.0.6", + "libnpmpack": "^7.0.2", + "libnpmpublish": "^9.0.8", + "libnpmsearch": "^7.0.5", + "libnpmteam": "^6.0.5", + "libnpmversion": "^6.0.2", + "make-fetch-happen": "^13.0.1", + "minimatch": "^9.0.4", + "minipass": "^7.1.1", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^10.0.1", - "nopt": "^7.2.0", - "normalize-package-data": "^6.0.0", + "node-gyp": "^10.1.0", + "nopt": "^7.2.1", + "normalize-package-data": "^6.0.1", "npm-audit-report": "^5.0.0", "npm-install-checks": "^6.3.0", - "npm-package-arg": "^11.0.1", - "npm-pick-manifest": "^9.0.0", - "npm-profile": "^9.0.0", - "npm-registry-fetch": "^16.1.0", - "npm-user-validate": "^2.0.0", - "npmlog": "^7.0.1", + "npm-package-arg": "^11.0.2", + "npm-pick-manifest": "^9.0.1", + "npm-profile": "^10.0.0", + "npm-registry-fetch": "^17.0.1", + "npm-user-validate": "^2.0.1", "p-map": "^4.0.0", - "pacote": "^17.0.6", + "pacote": "^18.0.6", "parse-conflict-json": "^3.0.1", - "proc-log": "^3.0.0", + "proc-log": "^4.2.0", "qrcode-terminal": "^0.12.0", - "read": "^2.1.0", - "semver": "^7.5.4", - "spdx-expression-parse": "^3.0.1", - "ssri": "^10.0.5", + "read": "^3.0.1", + "semver": "^7.6.2", + "spdx-expression-parse": "^4.0.0", + "ssri": "^10.0.6", "supports-color": "^9.4.0", - "tar": "^6.2.0", + "tar": "^6.2.1", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", "treeverse": "^3.0.0", - "validate-npm-package-name": "^5.0.0", + "validate-npm-package-name": "^5.0.1", "which": "^4.0.0", "write-file-atomic": "^5.0.1" }, @@ -1147,15 +1145,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm/node_modules/@colors/colors": { - "version": "1.5.0", - "inBundle": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/npm/node_modules/@isaacs/cliui": { "version": "8.0.2", "inBundle": true, @@ -1224,7 +1213,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/agent": { - "version": "2.2.0", + "version": "2.2.2", "inBundle": true, "license": "ISC", "dependencies": { @@ -1232,48 +1221,50 @@ "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.1" + "socks-proxy-agent": "^8.0.3" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "7.3.1", + "version": "7.5.2", "inBundle": true, "license": "ISC", "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^3.1.0", - "@npmcli/installed-package-contents": "^2.0.2", + "@npmcli/fs": "^3.1.1", + "@npmcli/installed-package-contents": "^2.1.0", "@npmcli/map-workspaces": "^3.0.2", - "@npmcli/metavuln-calculator": "^7.0.0", + "@npmcli/metavuln-calculator": "^7.1.1", "@npmcli/name-from-folder": "^2.0.0", "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/query": "^3.0.1", - "@npmcli/run-script": "^7.0.2", - "bin-links": "^4.0.1", - "cacache": "^18.0.0", + "@npmcli/package-json": "^5.1.0", + "@npmcli/query": "^3.1.0", + "@npmcli/redact": "^2.0.0", + "@npmcli/run-script": "^8.1.0", + "bin-links": "^4.0.4", + "cacache": "^18.0.3", "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^7.0.1", - "json-parse-even-better-errors": "^3.0.0", + "hosted-git-info": "^7.0.2", + "json-parse-even-better-errors": "^3.0.2", "json-stringify-nice": "^1.1.4", - "minimatch": "^9.0.0", - "nopt": "^7.0.0", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^7.2.1", "npm-install-checks": "^6.2.0", - "npm-package-arg": "^11.0.1", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "npmlog": "^7.0.1", - "pacote": "^17.0.4", + "npm-package-arg": "^11.0.2", + "npm-pick-manifest": "^9.0.1", + "npm-registry-fetch": "^17.0.1", + "pacote": "^18.0.6", "parse-conflict-json": "^3.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.2.0", + "proggy": "^2.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "read-package-json-fast": "^3.0.2", "semver": "^7.3.7", - "ssri": "^10.0.5", + "ssri": "^10.0.6", "treeverse": "^3.0.0", "walk-up-path": "^3.0.1" }, @@ -1285,15 +1276,15 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "8.1.0", + "version": "8.3.2", "inBundle": true, "license": "ISC", "dependencies": { "@npmcli/map-workspaces": "^3.0.2", "ci-info": "^4.0.0", - "ini": "^4.1.0", - "nopt": "^7.0.0", - "proc-log": "^3.0.0", + "ini": "^4.1.2", + "nopt": "^7.2.1", + "proc-log": "^4.2.0", "read-package-json-fast": "^3.0.2", "semver": "^7.3.5", "walk-up-path": "^3.0.1" @@ -1302,33 +1293,8 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/@npmcli/disparity-colors": { - "version": "3.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "ansi-styles": "^4.3.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/disparity-colors/node_modules/ansi-styles": { - "version": "4.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/npm/node_modules/@npmcli/fs": { - "version": "3.1.0", + "version": "3.1.1", "inBundle": true, "license": "ISC", "dependencies": { @@ -1339,14 +1305,14 @@ } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "5.0.4", + "version": "5.0.7", "inBundle": true, "license": "ISC", "dependencies": { "@npmcli/promise-spawn": "^7.0.0", "lru-cache": "^10.0.1", "npm-pick-manifest": "^9.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.0.0", "promise-inflight": "^1.0.1", "promise-retry": "^2.0.1", "semver": "^7.3.5", @@ -1357,7 +1323,7 @@ } }, "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "2.0.2", + "version": "2.1.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -1365,14 +1331,14 @@ "npm-normalize-package-bin": "^3.0.0" }, "bin": { - "installed-package-contents": "lib/index.js" + "installed-package-contents": "bin/index.js" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "3.0.4", + "version": "3.0.6", "inBundle": true, "license": "ISC", "dependencies": { @@ -1386,13 +1352,14 @@ } }, "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "7.0.0", + "version": "7.1.1", "inBundle": true, "license": "ISC", "dependencies": { "cacache": "^18.0.0", "json-parse-even-better-errors": "^3.0.0", - "pacote": "^17.0.0", + "pacote": "^18.0.0", + "proc-log": "^4.1.0", "semver": "^7.3.5" }, "engines": { @@ -1416,7 +1383,7 @@ } }, "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "5.0.0", + "version": "5.1.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -1425,7 +1392,7 @@ "hosted-git-info": "^7.0.0", "json-parse-even-better-errors": "^3.0.0", "normalize-package-data": "^6.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.0.0", "semver": "^7.5.3" }, "engines": { @@ -1433,7 +1400,7 @@ } }, "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "7.0.1", + "version": "7.0.2", "inBundle": true, "license": "ISC", "dependencies": { @@ -1444,7 +1411,7 @@ } }, "node_modules/npm/node_modules/@npmcli/query": { - "version": "3.0.1", + "version": "3.1.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -1454,8 +1421,16 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "7.0.4", + "version": "8.1.0", "inBundle": true, "license": "ISC", "dependencies": { @@ -1463,6 +1438,7 @@ "@npmcli/package-json": "^5.0.0", "@npmcli/promise-spawn": "^7.0.0", "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", "which": "^4.0.0" }, "engines": { @@ -1479,18 +1455,18 @@ } }, "node_modules/npm/node_modules/@sigstore/bundle": { - "version": "2.1.1", + "version": "2.3.1", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.1" + "@sigstore/protobuf-specs": "^0.3.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@sigstore/core": { - "version": "0.2.0", + "version": "1.1.0", "inBundle": true, "license": "Apache-2.0", "engines": { @@ -1498,47 +1474,49 @@ } }, "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.2.1", + "version": "0.3.2", "inBundle": true, "license": "Apache-2.0", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@sigstore/sign": { - "version": "2.2.1", + "version": "2.3.1", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.1.1", - "@sigstore/core": "^0.2.0", - "@sigstore/protobuf-specs": "^0.2.1", - "make-fetch-happen": "^13.0.0" + "@sigstore/bundle": "^2.3.0", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.1", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "2.3.0", + "version": "2.3.3", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.1", - "tuf-js": "^2.2.0" + "@sigstore/protobuf-specs": "^0.3.0", + "tuf-js": "^2.2.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@sigstore/verify": { - "version": "0.1.0", + "version": "1.2.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.1.1", - "@sigstore/core": "^0.2.0", - "@sigstore/protobuf-specs": "^0.2.1" + "@sigstore/bundle": "^2.3.1", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -1553,12 +1531,12 @@ } }, "node_modules/npm/node_modules/@tufjs/models": { - "version": "2.0.0", + "version": "2.0.1", "inBundle": true, "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.3" + "minimatch": "^9.0.4" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -1573,7 +1551,7 @@ } }, "node_modules/npm/node_modules/agent-base": { - "version": "7.1.0", + "version": "7.1.1", "inBundle": true, "license": "MIT", "dependencies": { @@ -1624,21 +1602,13 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/are-we-there-yet": { - "version": "4.0.2", - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/npm/node_modules/balanced-match": { "version": "1.0.2", "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/bin-links": { - "version": "4.0.3", + "version": "4.0.4", "inBundle": true, "license": "ISC", "dependencies": { @@ -1652,11 +1622,14 @@ } }, "node_modules/npm/node_modules/binary-extensions": { - "version": "2.2.0", + "version": "2.3.0", "inBundle": true, "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm/node_modules/brace-expansion": { @@ -1667,16 +1640,8 @@ "balanced-match": "^1.0.0" } }, - "node_modules/npm/node_modules/builtins": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "semver": "^7.0.0" - } - }, "node_modules/npm/node_modules/cacache": { - "version": "18.0.2", + "version": "18.0.3", "inBundle": true, "license": "ISC", "dependencies": { @@ -1731,7 +1696,7 @@ } }, "node_modules/npm/node_modules/cidr-regex": { - "version": "4.0.3", + "version": "4.0.5", "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -1761,30 +1726,8 @@ "node": ">= 10" } }, - "node_modules/npm/node_modules/cli-table3": { - "version": "0.6.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/npm/node_modules/clone": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/npm/node_modules/cmd-shim": { - "version": "6.0.2", + "version": "6.0.3", "inBundle": true, "license": "ISC", "engines": { @@ -1807,36 +1750,11 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/color-support": { - "version": "1.1.3", - "inBundle": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/npm/node_modules/columnify": { - "version": "1.6.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "strip-ansi": "^6.0.1", - "wcwidth": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/npm/node_modules/common-ancestor-path": { "version": "1.0.1", "inBundle": true, "license": "ISC" }, - "node_modules/npm/node_modules/console-control-strings": { - "version": "1.1.0", - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/cross-spawn": { "version": "7.0.3", "inBundle": true, @@ -1896,19 +1814,8 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/defaults": { - "version": "1.0.4", - "inBundle": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm/node_modules/diff": { - "version": "5.1.0", + "version": "5.2.0", "inBundle": true, "license": "BSD-3-Clause", "engines": { @@ -1994,40 +1901,22 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/npm/node_modules/gauge": { - "version": "5.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^4.0.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/npm/node_modules/glob": { - "version": "10.3.10", + "version": "10.3.15", "inBundle": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.11.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2038,13 +1927,8 @@ "inBundle": true, "license": "ISC" }, - "node_modules/npm/node_modules/has-unicode": { - "version": "2.0.1", - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/hasown": { - "version": "2.0.0", + "version": "2.0.2", "inBundle": true, "license": "MIT", "dependencies": { @@ -2055,7 +1939,7 @@ } }, "node_modules/npm/node_modules/hosted-git-info": { - "version": "7.0.1", + "version": "7.0.2", "inBundle": true, "license": "ISC", "dependencies": { @@ -2071,7 +1955,7 @@ "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/http-proxy-agent": { - "version": "7.0.0", + "version": "7.0.2", "inBundle": true, "license": "MIT", "dependencies": { @@ -2083,7 +1967,7 @@ } }, "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.2", + "version": "7.0.4", "inBundle": true, "license": "MIT", "dependencies": { @@ -2107,7 +1991,7 @@ } }, "node_modules/npm/node_modules/ignore-walk": { - "version": "6.0.4", + "version": "6.0.5", "inBundle": true, "license": "ISC", "dependencies": { @@ -2134,7 +2018,7 @@ } }, "node_modules/npm/node_modules/ini": { - "version": "4.1.1", + "version": "4.1.2", "inBundle": true, "license": "ISC", "engines": { @@ -2142,14 +2026,14 @@ } }, "node_modules/npm/node_modules/init-package-json": { - "version": "6.0.0", + "version": "6.0.3", "inBundle": true, "license": "ISC", "dependencies": { + "@npmcli/package-json": "^5.0.0", "npm-package-arg": "^11.0.0", "promzard": "^1.0.0", - "read": "^2.0.0", - "read-package-json": "^7.0.0", + "read": "^3.0.1", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "^5.0.0" @@ -2158,10 +2042,17 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/ip": { - "version": "2.0.0", + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", "inBundle": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } }, "node_modules/npm/node_modules/ip-regex": { "version": "5.0.0", @@ -2175,11 +2066,11 @@ } }, "node_modules/npm/node_modules/is-cidr": { - "version": "5.0.3", + "version": "5.0.5", "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "cidr-regex": "4.0.3" + "cidr-regex": "^4.0.4" }, "engines": { "node": ">=14" @@ -2231,8 +2122,13 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "inBundle": true, + "license": "MIT" + }, "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "3.0.1", + "version": "3.0.2", "inBundle": true, "license": "MIT", "engines": { @@ -2266,49 +2162,47 @@ "license": "MIT" }, "node_modules/npm/node_modules/libnpmaccess": { - "version": "8.0.2", + "version": "8.0.6", "inBundle": true, "license": "ISC", "dependencies": { - "npm-package-arg": "^11.0.1", - "npm-registry-fetch": "^16.0.0" + "npm-package-arg": "^11.0.2", + "npm-registry-fetch": "^17.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "6.0.6", + "version": "6.1.2", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.2.1", - "@npmcli/disparity-colors": "^3.0.0", - "@npmcli/installed-package-contents": "^2.0.2", - "binary-extensions": "^2.2.0", + "@npmcli/arborist": "^7.5.2", + "@npmcli/installed-package-contents": "^2.1.0", + "binary-extensions": "^2.3.0", "diff": "^5.1.0", - "minimatch": "^9.0.0", - "npm-package-arg": "^11.0.1", - "pacote": "^17.0.4", - "tar": "^6.2.0" + "minimatch": "^9.0.4", + "npm-package-arg": "^11.0.2", + "pacote": "^18.0.6", + "tar": "^6.2.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "7.0.7", + "version": "8.1.1", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.2.1", - "@npmcli/run-script": "^7.0.2", + "@npmcli/arborist": "^7.5.2", + "@npmcli/run-script": "^8.1.0", "ci-info": "^4.0.0", - "npm-package-arg": "^11.0.1", - "npmlog": "^7.0.1", - "pacote": "^17.0.4", - "proc-log": "^3.0.0", - "read": "^2.0.0", + "npm-package-arg": "^11.0.2", + "pacote": "^18.0.6", + "proc-log": "^4.2.0", + "read": "^3.0.1", "read-package-json-fast": "^3.0.2", "semver": "^7.3.7", "walk-up-path": "^3.0.1" @@ -2318,104 +2212,104 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "5.0.4", + "version": "5.0.10", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.2.1" + "@npmcli/arborist": "^7.5.2" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmhook": { - "version": "10.0.1", + "version": "10.0.5", "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^16.0.0" + "npm-registry-fetch": "^17.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmorg": { - "version": "6.0.2", + "version": "6.0.6", "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^16.0.0" + "npm-registry-fetch": "^17.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "6.0.6", + "version": "7.0.2", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.2.1", - "@npmcli/run-script": "^7.0.2", - "npm-package-arg": "^11.0.1", - "pacote": "^17.0.4" + "@npmcli/arborist": "^7.5.2", + "@npmcli/run-script": "^8.1.0", + "npm-package-arg": "^11.0.2", + "pacote": "^18.0.6" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmpublish": { - "version": "9.0.4", + "version": "9.0.8", "inBundle": true, "license": "ISC", "dependencies": { "ci-info": "^4.0.0", - "normalize-package-data": "^6.0.0", - "npm-package-arg": "^11.0.1", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0", + "normalize-package-data": "^6.0.1", + "npm-package-arg": "^11.0.2", + "npm-registry-fetch": "^17.0.1", + "proc-log": "^4.2.0", "semver": "^7.3.7", "sigstore": "^2.2.0", - "ssri": "^10.0.5" + "ssri": "^10.0.6" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmsearch": { - "version": "7.0.1", + "version": "7.0.5", "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^16.0.0" + "npm-registry-fetch": "^17.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmteam": { - "version": "6.0.1", + "version": "6.0.5", "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^16.0.0" + "npm-registry-fetch": "^17.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmversion": { - "version": "5.0.2", + "version": "6.0.2", "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.3", - "@npmcli/run-script": "^7.0.2", - "json-parse-even-better-errors": "^3.0.0", - "proc-log": "^3.0.0", + "@npmcli/git": "^5.0.7", + "@npmcli/run-script": "^8.1.0", + "json-parse-even-better-errors": "^3.0.2", + "proc-log": "^4.2.0", "semver": "^7.3.7" }, "engines": { @@ -2423,7 +2317,7 @@ } }, "node_modules/npm/node_modules/lru-cache": { - "version": "10.1.0", + "version": "10.2.2", "inBundle": true, "license": "ISC", "engines": { @@ -2431,7 +2325,7 @@ } }, "node_modules/npm/node_modules/make-fetch-happen": { - "version": "13.0.0", + "version": "13.0.1", "inBundle": true, "license": "ISC", "dependencies": { @@ -2444,6 +2338,7 @@ "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", + "proc-log": "^4.2.0", "promise-retry": "^2.0.1", "ssri": "^10.0.0" }, @@ -2452,7 +2347,7 @@ } }, "node_modules/npm/node_modules/minimatch": { - "version": "9.0.3", + "version": "9.0.4", "inBundle": true, "license": "ISC", "dependencies": { @@ -2466,7 +2361,7 @@ } }, "node_modules/npm/node_modules/minipass": { - "version": "7.0.4", + "version": "7.1.1", "inBundle": true, "license": "ISC", "engines": { @@ -2485,7 +2380,7 @@ } }, "node_modules/npm/node_modules/minipass-fetch": { - "version": "3.0.4", + "version": "3.0.5", "inBundle": true, "license": "MIT", "dependencies": { @@ -2642,7 +2537,7 @@ } }, "node_modules/npm/node_modules/node-gyp": { - "version": "10.0.1", + "version": "10.1.0", "inBundle": true, "license": "MIT", "dependencies": { @@ -2664,8 +2559,16 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/npm/node_modules/node-gyp/node_modules/proc-log": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/npm/node_modules/nopt": { - "version": "7.2.0", + "version": "7.2.1", "inBundle": true, "license": "ISC", "dependencies": { @@ -2679,7 +2582,7 @@ } }, "node_modules/npm/node_modules/normalize-package-data": { - "version": "6.0.0", + "version": "6.0.1", "inBundle": true, "license": "BSD-2-Clause", "dependencies": { @@ -2701,7 +2604,7 @@ } }, "node_modules/npm/node_modules/npm-bundled": { - "version": "3.0.0", + "version": "3.0.1", "inBundle": true, "license": "ISC", "dependencies": { @@ -2731,12 +2634,12 @@ } }, "node_modules/npm/node_modules/npm-package-arg": { - "version": "11.0.1", + "version": "11.0.2", "inBundle": true, "license": "ISC", "dependencies": { "hosted-git-info": "^7.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" }, @@ -2756,7 +2659,7 @@ } }, "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "9.0.0", + "version": "9.0.1", "inBundle": true, "license": "ISC", "dependencies": { @@ -2770,56 +2673,43 @@ } }, "node_modules/npm/node_modules/npm-profile": { - "version": "9.0.0", + "version": "10.0.0", "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0" + "npm-registry-fetch": "^17.0.1", + "proc-log": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=18.0.0" } }, "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "16.1.0", + "version": "17.0.1", "inBundle": true, "license": "ISC", "dependencies": { + "@npmcli/redact": "^2.0.0", "make-fetch-happen": "^13.0.0", "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", "minipass-json-stream": "^1.0.1", "minizlib": "^2.1.2", "npm-package-arg": "^11.0.0", - "proc-log": "^3.0.0" + "proc-log": "^4.0.0" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/npm-user-validate": { - "version": "2.0.0", + "version": "2.0.1", "inBundle": true, "license": "BSD-2-Clause", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/npmlog": { - "version": "7.0.1", - "inBundle": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^4.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^5.0.0", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/npm/node_modules/p-map": { "version": "4.0.0", "inBundle": true, @@ -2835,31 +2725,30 @@ } }, "node_modules/npm/node_modules/pacote": { - "version": "17.0.6", + "version": "18.0.6", "inBundle": true, "license": "ISC", "dependencies": { "@npmcli/git": "^5.0.0", "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/package-json": "^5.1.0", "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^7.0.0", + "@npmcli/run-script": "^8.0.0", "cacache": "^18.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^11.0.0", "npm-packlist": "^8.0.0", "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", "promise-retry": "^2.0.1", - "read-package-json": "^7.0.0", - "read-package-json-fast": "^3.0.0", "sigstore": "^2.2.0", "ssri": "^10.0.0", "tar": "^6.1.11" }, "bin": { - "pacote": "lib/bin.js" + "pacote": "bin/index.js" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -2887,22 +2776,22 @@ } }, "node_modules/npm/node_modules/path-scurry": { - "version": "1.10.1", + "version": "1.11.1", "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "6.0.15", + "version": "6.0.16", "inBundle": true, "license": "MIT", "dependencies": { @@ -2914,7 +2803,15 @@ } }, "node_modules/npm/node_modules/proc-log": { - "version": "3.0.0", + "version": "4.2.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/proggy": { + "version": "2.0.0", "inBundle": true, "license": "ISC", "engines": { @@ -2955,11 +2852,11 @@ } }, "node_modules/npm/node_modules/promzard": { - "version": "1.0.0", + "version": "1.0.2", "inBundle": true, "license": "ISC", "dependencies": { - "read": "^2.0.0" + "read": "^3.0.1" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -2973,11 +2870,11 @@ } }, "node_modules/npm/node_modules/read": { - "version": "2.1.0", + "version": "3.0.1", "inBundle": true, "license": "ISC", "dependencies": { - "mute-stream": "~1.0.0" + "mute-stream": "^1.0.0" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -2991,20 +2888,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/read-package-json": { - "version": "7.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, "node_modules/npm/node_modules/read-package-json-fast": { "version": "3.0.2", "inBundle": true, @@ -3032,12 +2915,9 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.5.4", + "version": "7.6.2", "inBundle": true, "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -3045,22 +2925,6 @@ "node": ">=10" } }, - "node_modules/npm/node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/set-blocking": { - "version": "2.0.0", - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/shebang-command": { "version": "2.0.0", "inBundle": true, @@ -3092,16 +2956,16 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "2.2.0", + "version": "2.3.0", "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.1.1", - "@sigstore/core": "^0.2.0", - "@sigstore/protobuf-specs": "^0.2.1", - "@sigstore/sign": "^2.2.1", - "@sigstore/tuf": "^2.3.0", - "@sigstore/verify": "^0.1.0" + "@sigstore/bundle": "^2.3.1", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.1", + "@sigstore/sign": "^2.3.0", + "@sigstore/tuf": "^2.3.1", + "@sigstore/verify": "^1.2.0" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -3117,24 +2981,24 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.7.1", + "version": "2.8.3", "inBundle": true, "license": "MIT", "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.2", + "version": "8.0.3", "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.1", "debug": "^4.3.4", "socks": "^2.7.1" }, @@ -3151,13 +3015,22 @@ "spdx-license-ids": "^3.0.0" } }, + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.3.0", + "version": "2.5.0", "inBundle": true, "license": "CC-BY-3.0" }, "node_modules/npm/node_modules/spdx-expression-parse": { - "version": "3.0.1", + "version": "4.0.0", "inBundle": true, "license": "MIT", "dependencies": { @@ -3166,12 +3039,17 @@ } }, "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.16", + "version": "3.0.17", "inBundle": true, "license": "CC0-1.0" }, + "node_modules/npm/node_modules/sprintf-js": { + "version": "1.1.3", + "inBundle": true, + "license": "BSD-3-Clause" + }, "node_modules/npm/node_modules/ssri": { - "version": "10.0.5", + "version": "10.0.6", "inBundle": true, "license": "ISC", "dependencies": { @@ -3243,7 +3121,7 @@ } }, "node_modules/npm/node_modules/tar": { - "version": "6.2.0", + "version": "6.2.1", "inBundle": true, "license": "ISC", "dependencies": { @@ -3307,13 +3185,13 @@ } }, "node_modules/npm/node_modules/tuf-js": { - "version": "2.2.0", + "version": "2.2.1", "inBundle": true, "license": "MIT", "dependencies": { - "@tufjs/models": "2.0.0", + "@tufjs/models": "2.0.1", "debug": "^4.3.4", - "make-fetch-happen": "^13.0.0" + "make-fetch-happen": "^13.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -3355,13 +3233,19 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "5.0.0", + "version": "5.0.1", "inBundle": true, "license": "ISC", - "dependencies": { - "builtins": "^5.0.0" - }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -3371,14 +3255,6 @@ "inBundle": true, "license": "ISC" }, - "node_modules/npm/node_modules/wcwidth": { - "version": "1.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, "node_modules/npm/node_modules/which": { "version": "4.0.0", "inBundle": true, @@ -3401,14 +3277,6 @@ "node": ">=16" } }, - "node_modules/npm/node_modules/wide-align": { - "version": "1.1.5", - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/npm/node_modules/wrap-ansi": { "version": "8.1.0", "inBundle": true, diff --git a/package.json b/package.json index 4b7104e..48506e8 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "devStart": "nodemon server.js" }, "dependencies": { + "body-parser": "^1.20.2", "dotenv": "^16.4.5", "express": "^4.18.3", "node": "^21.7.1", diff --git a/public/.deployment b/public/.deployment new file mode 100644 index 0000000..6278331 --- /dev/null +++ b/public/.deployment @@ -0,0 +1,2 @@ +[config] +SCM_DO_BUILD_DURING_DEPLOYMENT=true \ No newline at end of file diff --git a/public/canvas.js b/public/canvas.js new file mode 100644 index 0000000..139597f --- /dev/null +++ b/public/canvas.js @@ -0,0 +1,2 @@ + + diff --git a/public/image/Designer_5.png b/public/image/Designer_5.png new file mode 100644 index 0000000..dd4fa3f Binary files /dev/null and b/public/image/Designer_5.png differ diff --git a/public/image/Designer_6.png b/public/image/Designer_6.png new file mode 100644 index 0000000..9000994 Binary files /dev/null and b/public/image/Designer_6.png differ diff --git a/public/image/Designer_7.png b/public/image/Designer_7.png new file mode 100644 index 0000000..b460809 Binary files /dev/null and b/public/image/Designer_7.png differ diff --git a/public/image/Designer_8.png b/public/image/Designer_8.png new file mode 100644 index 0000000..f33d07d Binary files /dev/null and b/public/image/Designer_8.png differ diff --git a/public/index.css b/public/index.css index 3cda152..718c6b4 100644 --- a/public/index.css +++ b/public/index.css @@ -7,26 +7,82 @@ :root { --green: rgb(183, 235, 183); --black: rgb(67, 63, 63); + --dark-grey: rgb(45, 45, 45); + --light-grey: rgb(200, 200, 200); } +body.dark-mode { + --green: var(--dark-grey); + --black: var(--light-grey); + background-color: var(--green); + color: var(--black); +} + +.recording, +.main-element, +.recipe-section-headline, +.picture-section-headline, +.allergies, +.cusine-options button, +.want-another-recipe, +button, +.take-picture, +.send-to-user-inbox-btn, +.send-to-user-inbox, +.try-again-btn, +.gpt-response, +.slider, +input:checked+.slider { + background-color: var(--green); + color: var(--black); +} + +body.dark-mode .recording, +body.dark-mode .main-element, +body.dark-mode .recipe-section-headline, +body.dark-mode .picture-section-headline, +body.dark-mode .allergies, +body.dark-mode .cusine-options button, +body.dark-mode .want-another-recipe, +body.dark-mode button, +body.dark-mode .take-picture, +body.dark-mode .send-to-user-inbox-btn, +body.dark-mode .send-to-user-inbox, +body.dark-mode .try-again-btn, +body.dark-mode .gpt-response, +body.dark-mode .slider, +body.dark-mode input:checked+.slider { + background-color: var(--dark-grey); + color: var(--light-grey); +} + +body.dark-mode .slider:before { + background-color: var(--light-grey); +} + + + + + .parent-container { height: 100vh; width: 100vw; } .recording { - /* position: fixed; */ width: 50%; height: fit-content; - display: flex; justify-content: space-evenly; + align-items: center; flex-direction: row; margin: auto; + background-color: var(--green); + border-radius: 5px; + padding: 10px; + display: none; } - - .speed-wrapper { display: flex; } @@ -42,7 +98,8 @@ width: 100%; max-width: 500px; border: black 3px solid; - border-radius: 5%; + border-top-left-radius: 5px; + border-top-right-radius: 5px; background-image: url("./image/Designer_2.png"); background-image: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url("./image/Designer_2.png"); @@ -51,18 +108,45 @@ position: absolute; top: 50%; left: 50%; - transform: translate(-50%,-50%); + transform: translate(-50%, -50%); margin: 0 auto; } +.parent-container { + position: relative; +} + +.wrapper { + width: 100%; + /* Match the width of the parent */ + max-width: 500px; + /* Match the max-width of .main-element */ + height: 200vh; + overflow-y: scroll; + display: flex; + /* border: 10px blue solid; */ + flex-direction: column; + justify-content: center; + align-items: center; + margin: 0 auto; + +} + +.recipe-section { + height: 100vh; +} + + +.ingredient { + text-align: center; +} + .preserve-line-breaks { white-space: pre-line; /* text-align: left; */ } -.parent-container { - position: relative; -} + main { z-index: 1; @@ -75,9 +159,9 @@ main { z-index: 0; height: 605px; width: 420px; - top: 405px; + top: 360px; left: 50%; - transform: translate(-50%,-50%); + transform: translate(-50%, -50%); } @@ -90,10 +174,10 @@ main { justify-content: center; } */ -.fa-microphone { +/* .fa-microphone { height: 30px; width: 30px; -} +} */ #background-img { @@ -104,17 +188,21 @@ main { transform: translate(-50%, -50%); width: 100%; height: 100%; - max-width: 500px; + max-width: 500px; min-height: 100vh; border-radius: 5%; - border: 2px solid black; + border: 2px solid black; background-image: url("./image/Designer_2.png"); background-size: cover; background-position: center; + } -h1 { + +.headline, +.recipe-section-headline, +.picture-section-headline { /* text-wrap: balance; */ text-align: center; background-color: var(--green); @@ -142,8 +230,11 @@ h1 { margin-top: 80px; } + + .want-another-recipe, -button { +button, +.take-picture { text-transform: uppercase; height: 40px; margin-right: 10px; @@ -153,7 +244,12 @@ button { transition: background-color 0.5s ease; } -.send-to-user-inbox-btn { +/* .take-picture { + display: hidden; +} */ + +.send-to-user-inbox-btn, +.send-to-user-inbox { text-transform: uppercase; height: 20px; margin-right: 10px; @@ -164,17 +260,24 @@ button { } .send-recipe-to-user-inbox, -.want-another-recipe { +.email-recipe, +.want-another-recipe, +.previous-page { width: 170px; margin: auto; display: none; } -.email-section { +.email-section, +.picture-email-section { width: 170px; display: none; grid-template-columns: 2fr 1fr; - margin:auto; + margin: auto; +} + +.picture-email-section { + margin-top: -21px; } @@ -185,7 +288,7 @@ button { border-radius: 5px; background-color: var(--green); transition: background-color 0.5s ease; - display: block; + display: none; margin: auto; margin-top: 20px; width: fit-content; @@ -203,16 +306,17 @@ button { transition: background-color 0.5s ease; margin-top: 260px; margin-bottom: 20px; - display: none; + /* display: none; */ + display: hidden; } /* The switch - the box around the slider */ .switch { position: relative; - display: block; + display: block; width: 60px; height: 34px; - margin: 10px auto; + margin: 10px auto; } @@ -247,16 +351,16 @@ button { transition: 0.4s; } -input:checked + .slider { +input:checked+.slider { background-color: var(--black); transition: background-color 0.5s ease; } -input:focus + .slider { +input:focus+.slider { box-shadow: 0 0 1px var(--green); } -input:checked + .slider:before { +input:checked+.slider:before { -webkit-transform: translateX(26px); -ms-transform: translateX(26px); transform: translateX(26px); @@ -273,10 +377,21 @@ input:checked + .slider:before { /* Loading indicator */ -#loading-container { - display: none; +/* #loading-container { */ +/* display: none; */ +/* justify-content: center; + align-items: center; margin-bottom: 1em; -} + min-height: 100vh; + width: 100%; + max-width: 500px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); */ +/* } */ + + #loading-indicator { border: 5px solid transparent; border-top: 5px solid rgb(73, 180, 87); @@ -285,7 +400,7 @@ input:checked + .slider:before { width: 60px; height: 60px; animation: spin 1.6s linear infinite; - margin: 20px auto; + margin: auto; } #loading-text { @@ -309,13 +424,252 @@ input:checked + .slider:before { 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } } + @media only screen and (max-width: 768px) { .main-element { - width: 100%; /* Adjusted width for smaller screens */ + width: 100%; + /* Adjusted width for smaller screens */ max-width: none; } +} + + + + + + +/* picture-section*/ + + +.take-picture { + width: 170px; + margin-top: -24px; +} + +.background-img-picture-section { + z-index: -2; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 100%; + height: 100%; + max-width: 500px; + min-height: 100vh; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + border: 2px solid rgb(54, 49, 49); + background-image: url("./image/Designer_8.png"); + background-size: cover; + background-position: center; +} + + + + + +.chat-gpt-vision-text { + height: fit-content; + /* overflow: scroll; */ + overflow-y: scroll; + text-align: center; + background-color: var(--green); + border-radius: 5px; + margin-left: 10px; + margin-right: 10px; + transition: background-color 0.5s ease; + margin-bottom: 20px; + margin-top: -71px; + display: hidden; +} + + +.want-to-take-a-picture { + position: absolute; + bottom: 265px; +} + +.picture-section { + align-items: center; + position: relative; + height: 100vh; + width: 501px; + /* border: 1px black solid; */ + display: flex; + flex-direction: column; + row-gap: 20px; + overflow: hidden; +} + +.video-btn-canvas { + display: none; + flex-direction: column; + justify-content: center; + align-items: center; + row-gap: 10px; +} + +.video { + height: 300px; + width: 300px; + margin-top: -50px; + /* border: 1px solid; + border-radius: 5px; */ +} + +.canvas { + height: 300px; + width: 300px; + border-radius: 5px; +} + +.previous-page { + margin-top: 44px; +} + + + +.container { + width: 100%; + max-width: 500px; + height: 100%; + position: relative; + margin: 0 auto; +} + +.menu-icon { + height: 3rem; + width: 3rem; + position: fixed; + top: 1rem; + right: 1rem; + display: flex; + flex-direction: column; + justify-content: space-evenly; + align-items: center; + cursor: pointer; + z-index: 200; + left: auto; + margin-right: calc(50% - 250px); + background-color: transparent; + color: black; +} + +.menu-icon .line { + width: 3rem; + height: 0.2rem; + background-color: black; + transition: transform 0.3s; +} + +.menu-icon.change .line-1 { + transform: rotateZ(45deg) translate(0.7rem, -0.1rem); +} + +.menu-icon.change .line-2 { + transform: rotateZ(-45deg) translate(0.7rem, -0.1rem); +} + + +.line { + width: 3rem; + height: 0.2rem; + background-color: #bbb; + transition: transform 0.3s; +} + +.change .line-1 { + transform: rotateZ(45deg) translate(0.7rem, -0.1rem); +} + +.change .line-2 { + transform: rotateZ(-45deg) translate(0.7rem, -0.1rem); +} + +.navigation { + position: fixed; + top: 5px; + left: 56%; + transform: translateX(-50%); + width: 100%; + max-width: 340px; + height: 100vh; + /* Updated height to match the wrapper/container */ + background-color: var(--green); + z-index: 100; + display: none; + align-items: center; + padding-left: 1rem; + border-radius: 1px; + border: black 3px solid; + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +.change .navigation { + display: flex; +} + +.wrapper { + width: 100%; + max-width: 500px; + height: 200vh; + overflow-y: scroll; + display: flex; + /* border: 10px blue solid; */ + flex-direction: column; + justify-content: center; + align-items: center; + margin: 0 auto; +} + +.nav-items { + display: flex; + flex-direction: column; + opacity: 1; + visibility: visible; +} + +.nav-items a { + font-size: 1.5rem; + font-weight: 300; + text-transform: uppercase; + letter-spacing: 0.2rem; + color: var(--black); + margin: 1rem 0; + transition: color 0.3s, text-decoration 0.3s; +} + +.nav-items a:hover { + color: darkgreen; + text-decoration: underline; +} + +/* Media Queries */ +@media only screen and (max-width: 768px) { + .menu-icon { + right: 2rem; + margin-right: 0; + } + + .navigation { + left: 56%; + transform: translateX(-50%) translateY(-20px); + width: 100%; + max-width: 340px; + } +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.fade-in { + animation: fadeIn 1s ease-in; } \ No newline at end of file diff --git a/public/index.html b/public/index.html index 8c032c0..3fea77f 100644 --- a/public/index.html +++ b/public/index.html @@ -18,17 +18,36 @@ /> + +
+
+
+
+ + + +
+
+

- No idea what to cook? No worries recipe for success app does the - thinking for you. + Unsure What to Cook? Let Recipe for Success Inspire Your Next Meal! +

Indicate any dietary requirements @@ -94,32 +113,62 @@

Random -
-
-

Creating Recipe...

-
- + - +

+ +
+
+ + + + +
+ + +
+
+ + + +
+
+

Creating audio and image...

+
+ > + +
- - - +
+

You've got some ingredients but no idea what do do with them. Take a picture and we will cook something up

+ +
+ + + + +

+ + +
+ +
+ + + + + + + + + +
+ +
+ + + + + diff --git a/public/index.js b/public/index.js index 88cc063..f5ceca0 100644 --- a/public/index.js +++ b/public/index.js @@ -1,116 +1,68 @@ -const mainElement = document.querySelector(".main-element"); -const test = document.querySelector(".test"); -const backgroundImg = document.querySelector("#background-img"); -const gptResponseElement = document.querySelector(".gpt-response"); -const headline = document.querySelector(".headline"); -const lactoseIntolerant = document.querySelector("#lactose-intolerant"); -const vegan = document.querySelector("#vegan"); -const loadingContainer = document.querySelector("#loading-container"); -const allergies = document.querySelector(".allergies"); -const darkLightButton = document.querySelector(".dark-light-button"); -const userWantAnotherRecipe = document.querySelector(".want-another-recipe"); -const tryAgainBtn = document.querySelector(".try-again-btn"); -const recipeButtons = document.querySelectorAll(".recipe-button"); -const sendRecipeToUserInboxBtn = document.querySelector( - ".send-recipe-to-user-inbox" -); -const userEmail = document.querySelector("#user-email"); -const sendEmailButton = document.querySelector(".send-email-btn"); -const emailSection = document.querySelector(".email-section"); -const paperPlane = document.querySelector(".fa-paper-plane"); -const sendToUserInboxBtn = document.querySelector(".send-to-user-inbox-btn"); -console.log(sendToUserInboxBtn); - -const dietaryRequirements = Array.from( - document.querySelectorAll(".dietary-requirements") -); -const otherDietaryRequirements = document.querySelector( - "#other-dietary-requirements" -); -const userText = document.querySelector("#user-text"); -let textContent; -let imageUrl; -let isLactoseIntolerant; -let dishOriginCountry; -let currentChar; - -const defaultRecipe = ` -Apologies, but our AI Recipe-Making expert is unavailable. Please try again later. In the meantime, please find one of our favourite recipes below. - -

Ingredients

-
    -
  • 1 tbsp olive oil
  • -
  • 4 rashers smoked streaky bacon, finely chopped
  • -
  • 2 medium onions, finely chopped
  • -
  • 2 carrots, trimmed and finely chopped
  • -
  • 2 celery sticks, finely chopped
  • -
  • 2 garlic cloves finely chopped
  • -
  • 2-3 sprigs rosemary leaves picked and finely chopped
  • -
  • 500g beef mince
  • -
- -

For the Bolognese Sauce

-
    -
  • 2 x 400g tins plum tomatoes
  • -
  • Small pack basil leaves picked, ¾ finely chopped and the rest left whole for garnish
  • -
  • 1 tsp dried oregano
  • -
  • 2 fresh bay leaves
  • -
  • 2 tbsp tomato purée
  • -
  • 1 beef stock cube
  • -
  • 1 red chilli deseeded and finely chopped (optional)
  • -
  • 125ml red wine
  • -
  • 6 cherry tomatoes sliced in half
  • -
- -

To Season and Serve

-
    -
  • 75g parmesan grated, plus extra to serve
  • -
  • 400g spaghetti
  • -
  • Crusty bread to serve (optional)
  • -
- -

Method

-
    -
  1. Put a large saucepan on a medium heat and add 1 tbsp olive oil.
  2. -
  3. Add 4 finely chopped bacon rashers and fry for 10 mins until golden and crisp.
  4. -
  5. Reduce the heat and add the 2 onions, 2 carrots, 2 celery sticks, 2 garlic cloves and the leaves from 2-3 sprigs rosemary, all finely chopped, then fry for 10 mins. Stir the veg often until it softens.
  6. -
  7. Increase the heat to medium-high, add 500g beef mince and cook stirring for 3-4 mins until the meat is browned all over.
  8. -
  9. Add 2 tins plum tomatoes, the finely chopped leaves from ¾ small pack basil, 1 tsp dried oregano, 2 bay leaves, 2 tbsp tomato purée, 1 beef stock cube, 1 deseeded and finely chopped red chilli (if using), 125ml red wine and 6 halved cherry tomatoes. Stir with a wooden spoon, breaking up the plum tomatoes.
  10. -
  11. Bring to the boil, reduce to a gentle simmer and cover with a lid. Cook for 1 hr 15 mins stirring occasionally, until you have a rich, thick sauce.
  12. -
  13. Add the 75g grated parmesan, check the seasoning and stir.
  14. -
  15. When the bolognese is nearly finished, cook 400g spaghetti following the pack instructions.
  16. -
  17. Drain the spaghetti and either stir into the bolognese sauce, or serve the sauce on top. Serve with more grated parmesan, the remaining basil leaves and crusty bread, if you like.
  18. -
-`; -let errorMessage = ` - - ${defaultRecipe} - `; - -tryAgainBtn.style.display = "none"; - - - -sendToUserInboxBtn.addEventListener("click", () => { - if (userEmail.value !== "") { - alert("an email has been sent to your inbox"); - } -}) - -function createQuery(myObject) { - let esc = encodeURIComponent; - let query = Object.keys(myObject) - .map((k) => esc(k) + "=" + esc(myObject[k])) - .join("&"); - return query; -} +import { + defaultRecipe, + createQuery, + displayElements, + displayElementsFlex, + displayElementsGrid, + removeElements, + emptyTheElement, + resetCheckedStateToFalse, + playAudio, + pauseAudio, + stopAudio, + createAudio, + createUserRecipe, + audioElement, + alert_message, +} from "./js_utilities/functions_and_variables.js"; + +import { + mainElement, + backgroundImg, + gptResponseElement, + headline, + loadingContainer, + allergies, + darkLightButton, + userWantAnotherRecipe, + tryAgainBtn, + recipeButtons, + sendRecipeToUserInboxBtn, + recording, + userEmail, + emailSection, + sendToUserInboxBtn, + dietaryRequirements, + otherDietaryRequirements, + userText, + video, + canvas, + takePicture, + context, + chatGptVisionText, + videoBtnCanvas, + pictureSectionHeadline, + wantToTakeAPicture, + emailRecipe, + pictureEmailSection, + previousPage, + sendToUserInbox, + wrapper, +} from "./js_utilities/query_selector.js"; + +let currentCameraIndex = 0; +const switchCameraButton = document.getElementById("switchCamera"); +let emailObject; + +wantToTakeAPicture.addEventListener("click", () => { + removeElements([pictureSectionHeadline, wantToTakeAPicture]); + displayElementsFlex([videoBtnCanvas]); + console.log("Picture taken"); +}); -function loopOverArrayOfElements(array, display) { - array.forEach((elememt) => { - elememt.style.display = display; - elememt.style.transition = "all 2s"; - }); -} +takePicture.addEventListener("click", () => { + console.log("take a picture"); +}); otherDietaryRequirements.addEventListener("click", () => { if (otherDietaryRequirements.checked) { @@ -120,236 +72,271 @@ otherDietaryRequirements.addEventListener("click", () => { } }); -function displayElements(array) { - loopOverArrayOfElements(array, "block"); -} - -function displayElementsGrid(array) { - loopOverArrayOfElements(array, "grid"); -} - -function removeElements(array) { - loopOverArrayOfElements(array, "none"); -} - -function emptyTheElement(elememt) { - elememt.innerHTML = ""; -} +emailRecipe.addEventListener("click", () => { + displayElementsGrid([pictureEmailSection]); + removeElements([emailRecipe]); +}); sendRecipeToUserInboxBtn.addEventListener("click", () => { + console.log("Email to user"); displayElementsGrid([emailSection]); removeElements([sendRecipeToUserInboxBtn]); }); -function resetCheckedStateToFalse(array) { - array.forEach((requirement) => { - if (requirement.checked) { - requirement.checked = false; - } - }); -} - -userWantAnotherRecipe.addEventListener("click", () => { - displayElements([headline, allergies, ...recipeButtons, mainElement]); - removeElements([userText, emailSection]); - emptyTheElement(gptResponseElement); - resetCheckedStateToFalse(dietaryRequirements); - userText.value = ""; +previousPage.addEventListener("click", () => { + removeElements([ + videoBtnCanvas, + pictureEmailSection, + previousPage, + emailRecipe, + ]); + displayElements([pictureSectionHeadline, wantToTakeAPicture]); + emptyTheElement(chatGptVisionText); }); tryAgainBtn.addEventListener("click", () => { - displayElements([headline, allergies, ...recipeButtons]); + console.log("Try again"); + displayElements([headline, allergies, ...recipeButtons, mainElement]); removeElements([gptResponseElement, tryAgainBtn]); emptyTheElement(gptResponseElement); }); darkLightButton.addEventListener("change", () => { - let color = darkLightButton.checked - ? "rgb(67, 63, 63)" - : "rgb(183, 235, 183)"; - [ - gptResponseElement, - lactoseIntolerant, - allergies, - headline, - userWantAnotherRecipe, - sendRecipeToUserInboxBtn, - tryAgainBtn, - ...recipeButtons, - ].forEach((element) => { - element.style.setProperty("--green", color); - element.style.transition = "background-color 0.5s ease"; + document.body.classList.toggle("dark-mode", darkLightButton.checked); +}); + +const user_email_elememts = [...userEmail]; +user_email_elememts.forEach((element) => { + element.addEventListener("input", (e) => { + emailObject = { + [element.name]: element.value, + }; + console.log(e.target.value); }); }); -sendToUserInboxBtn.addEventListener("click", () => { - let emailOBject = { - [userEmail.name]: userEmail.value, - }; - fetch("/server.js", { +console.log(emailObject); + +sendToUserInbox.addEventListener("click", () => { + fetch(`/email_picture_section?${createQuery(emailObject)}`, { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify(emailOBject), - }) - .then((response) => response.json()) - .then((data) => { - console.log("Response for user email", data); - }) - .catch((error) => { - console.error("Error", error); - }); + body: JSON.stringify({ pictureTextSection: chatGptVisionText.textContent }), + }).then((response) => { + if (response.ok) { + console.log("image posted"); + alert(`${alert_message}`); + return response.json(); + } else { + throw new Error("Failed to post image"); + } + }); +}); - fetch(`/email?${createQuery(emailOBject)}`) +sendToUserInboxBtn.addEventListener("click", () => { + console.log(emailObject); + fetch(`/email?${createQuery(emailObject)}`) .then((response) => response.json()) .then((data) => { - console.log({ data }, { emailQuery }); + if (data.emailStatus === "250 OK , completed") { + alert(`${alert_message}`); + } else { + alert("Invalid email address, try again"); + } }) .catch((error) => console.error("Error:", error)); }); +console.log(emailObject); + recipeButtons.forEach((button) => { console.log(userText.value); button.addEventListener("click", async () => { - recipeTextLoaded = false; - recipeImageLoaded = false; - - let userRecipe = { - [button.name]: button.value, - array: [...dietaryRequirements, ...[userText]], - I_do_not_eat: userText.placeholder, - loopOverArray: function () { - this.array.forEach((dietaryRequirement) => { - this[dietaryRequirement.name] = dietaryRequirement.checked; - if (dietaryRequirement.value !== "on") { - this[dietaryRequirement.name] = dietaryRequirement.value; - } - }); - }, - }; - - userRecipe.loopOverArray(); - console.log(userRecipe); - - dishOriginCountry = button.value; // needed ?∫ displayElements([loadingContainer]); - gptResponseElement.innerHTML = ""; - fetch("/server.js", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(userRecipe), - }) - .then((response) => response.json()) - .then((data) => { - console.log("Response from the back-end", data); - }) - .catch((error) => { - console.error("Error", error); - }); - fetch(`/openai?${createQuery(userRecipe)}`) - .then((response) => response.json()) - .then((data) => { - // CREATE IMAGE PROMISE - const imagePromise = new Promise((resolve) => { - console.log("image begin"); - - // Wait for background image to be loaded - backgroundImg.addEventListener("load", () => { - resolve(); - mainElement.style.display = "none"; - - console.log("image end"); - }); + removeElements([mainElement]); + const userRecipe = createUserRecipe(button, dietaryRequirements, userText); + console.log(userRecipe); - // Set background image - imageUrl = data.image.data[0].url; - backgroundImg.src = imageUrl; + const data = {}; + + const eventSource = new EventSource(`/stream?${createQuery(userRecipe)}`); + + eventSource.onmessage = async function (event) { + let eventData = JSON.parse(event.data); + if (eventData.message) { + if (eventData.message === "stop") { + eventSource.close(); + return; + } + displayElements([gptResponseElement]); + gptResponseElement.textContent += eventData.message; + return; + } else if (eventData.errorMessage === "invalid_api_key") { + eventSource.close(); + console.log(eventData.errorMessage); + displayElements([gptResponseElement, tryAgainBtn]); + removeElements([loadingContainer]); + gptResponseElement.innerHTML = defaultRecipe; + return; + } + + if (eventData.audio) { + data.audio = eventData.audio; + } + if (eventData.image) { + data.image = eventData.image; + } + + console.log("data.audio", eventData.audio); + console.log("data.image", eventData.image); + + if (data.audio && data.image) { + createImage(data); + + const { speedBtn, speechBtns } = createTextToSpeech(data); + + userWantAnotherRecipe.addEventListener("click", () => { + displayElements([headline, allergies, ...recipeButtons, mainElement]); + removeElements([userText, emailSection, recording]); + emptyTheElement(gptResponseElement); + resetCheckedStateToFalse(dietaryRequirements); + userText.value = ""; + data.audio = ""; + stopAudio(audioElement); }); - // Update text contennt once image is loaded - Promise.all([imagePromise]) - .then(() => { - console.log("image loaded:", Promise.all.status); - textContent = data.text.choices[0].message.content; - gptResponseElement.innerHTML = ` -
- - - -
- - -
-
- ${textContent}`; - removeElements([headline, allergies, ...recipeButtons]); - displayElements([ - userWantAnotherRecipe, - gptResponseElement, - sendRecipeToUserInboxBtn, - ]); - - const utterance = new SpeechSynthesisUtterance(); - - const speechBtns = Array.from( - document.querySelectorAll(".fa-solid") - ); - const speedBtn = document.querySelector("#speed"); - - console.log(speechBtns); - - function readRecipe(recipe) { - if (speechSynthesis.paused && speechSynthesis.speaking) { - return speechSynthesis.resume(); - } - if (speechSynthesis.speaking) return; - utterance.text = recipe; - utterance.rate = speedBtn.value || 1; - speechSynthesis.speak(utterance); - } - function pauseReading() { - if (speechSynthesis.speaking) speechSynthesis.pause(); - } + speedBtn.addEventListener("change", () => { + audioElement.playbackRate = speedBtn.value || 1; + }); - function stopREeading() { - speechSynthesis.resume(); - speechSynthesis.cancel(); + speechBtns.forEach((speechBtn) => { + speechBtn.addEventListener("click", () => { + const btnName = speechBtn.getAttribute("name"); + if (btnName === "microphone") { + playAudio(audioElement); + } else if (btnName === "pause") { + pauseAudio(audioElement); + } else if (btnName === "stop") { + stopAudio(audioElement); } - - speechBtns.forEach((speechBtn) => { - speechBtn.addEventListener("click", () => { - const btnName = speechBtn.getAttribute("name"); - if (btnName === "microphone") { - console.log(btnName); - readRecipe(`${textContent}`); - } else if (btnName === "pause") { - pauseReading(); - } else if (btnName === "stop") { - stopREeading(); - } - }); - }); - }) - .catch((error) => { - console.error("Error:", error); - gptResponseElement.innerHTML = `${errorMessage}`; - removeElements([headline, allergies, ...recipeButtons]); - displayElements([tryAgainBtn, gptResponseElement]); - }) - .finally(() => { - //HIDES LOADING WHETHER OR NOT IT FAILS - loadingContainer.style.display = "none"; - console.log("All promises have been settled"); }); - }) - .catch((error) => { - console.error("Error:", error); - gptResponseElement.innerHTML = `${errorMessage}`; - removeElements([headline, allergies, ...recipeButtons]); - displayElements([tryAgainBtn, gptResponseElement]); - }); + }); + } + }; }); }); + +function createImage(param) { + removeElements([loadingContainer]); + const imageUrl = param.image.data[0].url; + backgroundImg.src = imageUrl; + return backgroundImg; +} + +function createTextToSpeech(param) { + displayElementsFlex([recording]); + displayElements([sendRecipeToUserInboxBtn, userWantAnotherRecipe]); + const speechBtns = Array.from(document.querySelectorAll(".fa-solid")); + const speedBtn = document.querySelector("#speed"); + audioElement.src = createAudio(param.audio); + audioElement.stop = function () { + this.pause(); + this.currentTime = 0; + }; + return { speedBtn, speechBtns }; +} + +// Picture section +async function getVideoDevices() { + const devices = await navigator.mediaDevices.enumerateDevices(); + return devices.filter((device) => device.kind === "videoinput"); +} + +async function startCamera(deviceId) { + const constraints = { + audio: false, + video: { + deviceId: deviceId ? { exact: deviceId } : undefined, + width: { min: 1024, ideal: 1280, max: 1920 }, + height: { min: 576, ideal: 720, max: 1080 }, + }, + }; + + try { + const stream = await navigator.mediaDevices.getUserMedia(constraints); + video.srcObject = stream; + await video.play(); + } catch (error) { + console.error("Error accessing camera:", error); + } +} + +async function initializeCamera() { + const videoDevices = await getVideoDevices(); + + if (videoDevices.length > 1) { + switchCameraButton.style.display = "block"; + switchCameraButton.addEventListener("click", () => { + currentCameraIndex = (currentCameraIndex + 1) % videoDevices.length; + startCamera(videoDevices[currentCameraIndex].deviceId); + }); + } else { + switchCameraButton.style.display = "none"; + } + + // Start with the rear camera if available + const rearCameraDevice = videoDevices.find( + (device) => + device.label.toLowerCase().includes("back") || + device.label.toLowerCase().includes("rear") + ); + startCamera( + rearCameraDevice ? rearCameraDevice.deviceId : videoDevices[0].deviceId + ); +} + +initializeCamera(); + +function capturePhoto() { + context.drawImage(video, 0, 0, 400, 100); +} + +takePicture.addEventListener("click", () => { + capturePhoto(); + const imageData = canvas.toDataURL("image/png"); + console.log("Captured photo:", imageData); + + fetch("/upload", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ image: imageData }), + }) + .then((response) => { + if (response.ok) { + console.log("Image uploaded successfully"); + return response.json(); + } else { + throw new Error("Failed to upload image"); + } + }) + .then((data) => { + const chatGptVisionResponse = data.message.content; + chatGptVisionText.textContent = chatGptVisionResponse; + displayElements([emailRecipe, previousPage]); + displayElementsGrid([pictureEmailSection]); + }) + .catch((error) => { + console.error("Error", error); + }); +}); + +// Menu icon toggle +const menuIcon = document.querySelector(".menu-icon"); + +menuIcon.addEventListener("click", () => { + wrapper.classList.toggle("change"); +}); diff --git a/public/js_utilities/functions_and_variables.js b/public/js_utilities/functions_and_variables.js new file mode 100644 index 0000000..e06e5f2 --- /dev/null +++ b/public/js_utilities/functions_and_variables.js @@ -0,0 +1,171 @@ +// variables + +const CACHE_NAME_URL = "image-cache-v1"; +const CACHE_NAME_AUDIO = "audio-cache-v2"; +let audioElement = new Audio(); +let alert_message = "an email has been sent your inbox" + + + + + + + +const defaultRecipe = ` +Apologies, but our AI Recipe-Making expert is unavailable. Please try again later. In the meantime, please find one of our favourite recipes below. + +

Ingredients

+
    +
  • 1 tbsp olive oil
  • +
  • 4 rashers smoked streaky bacon, finely chopped
  • +
  • 2 medium onions, finely chopped
  • +
  • 2 carrots, trimmed and finely chopped
  • +
  • 2 celery sticks, finely chopped
  • +
  • 2 garlic cloves finely chopped
  • +
  • 2-3 sprigs rosemary leaves picked and finely chopped
  • +
  • 500g beef mince
  • +
+ +

For the Bolognese Sauce

+
    +
  • 2 x 400g tins plum tomatoes
  • +
  • Small pack basil leaves picked, ¾ finely chopped and the rest left whole for garnish
  • +
  • 1 tsp dried oregano
  • +
  • 2 fresh bay leaves
  • +
  • 2 tbsp tomato purée
  • +
  • 1 beef stock cube
  • +
  • 1 red chilli deseeded and finely chopped (optional)
  • +
  • 125ml red wine
  • +
  • 6 cherry tomatoes sliced in half
  • +
+ +

To Season and Serve

+
    +
  • 75g parmesan grated, plus extra to serve
  • +
  • 400g spaghetti
  • +
  • Crusty bread to serve (optional)
  • +
+ +

Method

+
    +
  1. Put a large saucepan on a medium heat and add 1 tbsp olive oil.
  2. +
  3. Add 4 finely chopped bacon rashers and fry for 10 mins until golden and crisp.
  4. +
  5. Reduce the heat and add the 2 onions, 2 carrots, 2 celery sticks, 2 garlic cloves and the leaves from 2-3 sprigs rosemary, all finely chopped, then fry for 10 mins. Stir the veg often until it softens.
  6. +
  7. Increase the heat to medium-high, add 500g beef mince and cook stirring for 3-4 mins until the meat is browned all over.
  8. +
  9. Add 2 tins plum tomatoes, the finely chopped leaves from ¾ small pack basil, 1 tsp dried oregano, 2 bay leaves, 2 tbsp tomato purée, 1 beef stock cube, 1 deseeded and finely chopped red chilli (if using), 125ml red wine and 6 halved cherry tomatoes. Stir with a wooden spoon, breaking up the plum tomatoes.
  10. +
  11. Bring to the boil, reduce to a gentle simmer and cover with a lid. Cook for 1 hr 15 mins stirring occasionally, until you have a rich, thick sauce.
  12. +
  13. Add the 75g grated parmesan, check the seasoning and stir.
  14. +
  15. When the bolognese is nearly finished, cook 400g spaghetti following the pack instructions.
  16. +
  17. Drain the spaghetti and either stir into the bolognese sauce, or serve the sauce on top. Serve with more grated parmesan, the remaining basil leaves and crusty bread, if you like.
  18. +
+`; + + + + +// functions + +function playAudio(element) { + element.play(); +} + +function pauseAudio(element) { + element.pause(); +} + +function stopAudio(element) { + element.stop(); +} + +function createQuery(myObject) { + let esc = encodeURIComponent; + let query = Object.keys(myObject) + .map((k) => esc(k) + "=" + esc(myObject[k])) + .join("&"); + return query; +} + +function createUserRecipe(button, dietaryRequirements, userText) { + let recipe = { + [button.name]: button.value, + array: [...dietaryRequirements, ...[userText]], + I_do_not_eat: userText.placeholder + }; + + recipe.array.forEach((dietaryRequirement) => { + recipe[dietaryRequirement.name] = dietaryRequirement.checked; + if (dietaryRequirement.value !== "on") { + recipe[dietaryRequirement.name] = dietaryRequirement.value; + } + }); + + return recipe; +} + +function loopOverArrayOfElements(array, display) { + array.forEach((element) => { + if (element) { // Check if the element is not null + element.style.display = display; + element.style.transition = "all 2s"; + } + }); +} + + + + + + + +function displayElements(array) { + loopOverArrayOfElements(array, "block"); +} + +function displayElementsFlex(array) { + loopOverArrayOfElements(array, "flex"); +} + +function displayElementsGrid(array) { + loopOverArrayOfElements(array, "grid"); +} + +function removeElements(array) { + loopOverArrayOfElements(array, "none"); +} + +function emptyTheElement(element) { + if (element) { // Check if the element is not null + element.innerHTML = ""; + } +} + +function resetCheckedStateToFalse(array) { + array.forEach((requirement) => { + if (requirement && requirement.checked) { // Check if the requirement is not null and checked + requirement.checked = false; + } + }); +} + +function createAudio(data) { + const binaryData = atob(data); + const audioData = new Uint8Array(binaryData.length); + for (let i = 0; i < binaryData.length; i++) { + audioData[i] = binaryData.charCodeAt(i); + } + const audioBlob = new Blob([audioData], { type: "audio/mpeg" }); + return URL.createObjectURL(audioBlob); +} + + // Function to cache the image URL/AUDIO (without fetching the image) + async function cacheData(data, chache_name, type_of_data) { + const cache = await caches.open(chache_name); + const response = new Response( + JSON.stringify({ data, timeStamp: Date.now }) + ); + await cache.put(`last-generated-${type_of_data}`, response); + } + + + +export { defaultRecipe, CACHE_NAME_URL, audioElement, CACHE_NAME_AUDIO, alert_message, cacheData, createQuery, displayElements, displayElementsFlex, displayElementsGrid, removeElements, emptyTheElement, resetCheckedStateToFalse, playAudio, pauseAudio, stopAudio, createAudio, createUserRecipe } \ No newline at end of file diff --git a/public/js_utilities/query_selector.js b/public/js_utilities/query_selector.js new file mode 100644 index 0000000..dec588f --- /dev/null +++ b/public/js_utilities/query_selector.js @@ -0,0 +1,94 @@ +const emailRecipe = document.querySelector(".email-recipe"); +const wrapper = document.querySelector(".wrapper"); +const emailUserRecipeSection = document.querySelector(".user-email-recipe-section"); +const sendToUserInbox = document.querySelector(".send-to-user-inbox"); +const previousPage = document.querySelector(".previous-page"); +const pictureEmailSection = document.querySelector(".picture-email-section"); +const mainElement = document.querySelector(".main-element"); +const pictureSectionHeadline = document.querySelector(".picture-section-headline"); +const pictureSection = document.querySelector(".picture-section") +const takePicture = document.querySelector(".take-picture"); +const wantToTakeAPicture = document.querySelector(".want-to-take-a-picture"); +const videoBtnCanvas = document.querySelector(".video-btn-canvas"); +const chatGptVisionText = document.querySelector(".chat-gpt-vision-text"); +const video = document.querySelector(".video"); +const canvas = document.querySelector(".canvas"); +const context = canvas.getContext('2d'); +const backgroundImg = document.querySelector("#background-img"); +const gptResponseElement = document.querySelector(".gpt-response"); +const headline = document.querySelector(".headline"); +const lactoseIntolerant = document.querySelector("#lactose-intolerant"); +const loadingContainer = document.querySelector("#loading-container"); +const allergies = document.querySelector(".allergies"); +const darkLightButton = document.querySelector(".dark-light-button"); +const userWantAnotherRecipe = document.querySelector(".want-another-recipe"); +const tryAgainBtn = document.querySelector(".try-again-btn"); +const recipeButtons = document.querySelectorAll(".recipe-button"); +const sendRecipeToUserInboxBtn = document.querySelector( + ".send-recipe-to-user-inbox" +); +const loadingText = document.querySelector("#loading-text"); +const recording = document.querySelector(".recording"); +const userEmail = document.querySelectorAll(".user-email"); +const emailSection = document.querySelector(".email-section"); +const sendToUserInboxBtn = document.querySelector(".send-to-user-inbox-btn"); +const dietaryRequirements = Array.from( + document.querySelectorAll(".dietary-requirements") +); +const otherDietaryRequirements = document.querySelector( + "#other-dietary-requirements" +); +const userText = document.querySelector("#user-text"); +const constraint = { + audio: false, + video: { + width: {min: 1024, ideal: 1280, max: 1920}, + height: {min: 576, ideal: 720, max: 1080}, + facingMode: { ideal: 'environment' } // tells the browser to prefer the rear camera if available. + + +} +} + + +export { + mainElement, + backgroundImg, + gptResponseElement, + headline, + lactoseIntolerant, + loadingContainer, + allergies, + darkLightButton, + userWantAnotherRecipe, + tryAgainBtn, + recipeButtons, + sendRecipeToUserInboxBtn, + loadingText, + recording, + userEmail, + emailSection, + sendToUserInboxBtn, + dietaryRequirements, + otherDietaryRequirements, + userText, + pictureSection, + video, + canvas, + takePicture, + context, + constraint, + chatGptVisionText, + videoBtnCanvas, + pictureSectionHeadline, + wantToTakeAPicture, + emailRecipe, + pictureEmailSection, + previousPage, + sendToUserInbox, + emailUserRecipeSection, + wrapper +}; + + + diff --git a/public/url_folder.txt b/public/url_folder.txt new file mode 100644 index 0000000..40b2ee8 --- /dev/null +++ b/public/url_folder.txt @@ -0,0 +1 @@ +https://oaidalleapiprodscus.blob.core.windows.net/private/org-nYbqgo3O0LNnYYAoKAmApBfx/user-58Je7efi0iy880e2UYCdYpBm/img-ovb7qD0yHMLLx0Ooe4iCpSOE.png?st=2024-08-03T13%3A22%3A53Z&se=2024-08-03T15%3A22%3A53Z&sp=r&sv=2023-11-03&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2024-08-03T04%3A05%3A06Z&ske=2024-08-04T04%3A05%3A06Z&sks=b&skv=2023-11-03&sig=bDE%2Bp86dJ7jZ6nydnnnKPumw/XA0M1VaghM8Bm5XY5E%3D \ No newline at end of file diff --git a/public/url_folder/url_folder.txt b/public/url_folder/url_folder.txt new file mode 100644 index 0000000..49e2a3c --- /dev/null +++ b/public/url_folder/url_folder.txt @@ -0,0 +1 @@ +https://oaidalleapiprodscus.blob.core.windows.net/private/org-nYbqgo3O0LNnYYAoKAmApBfx/user-58Je7efi0iy880e2UYCdYpBm/img-C0V1XKTeR1cGXus4r6pj0Ws5.png?st=2024-06-22T15%3A24%3A09Z&se=2024-06-22T17%3A24%3A09Z&sp=r&sv=2023-11-03&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2024-06-22T13%3A05%3A11Z&ske=2024-06-23T13%3A05%3A11Z&sks=b&skv=2023-11-03&sig=8MWx63QT8zhEBhb7RNKIdO42DpIiaIAD26WVSeywswk%3D \ No newline at end of file diff --git a/readMe.txt b/readMe.txt new file mode 100644 index 0000000..e69de29 diff --git a/server.js b/server.js index 3cf8b2a..223c30e 100644 --- a/server.js +++ b/server.js @@ -1,115 +1,17 @@ const express = require("express"); -const path = require('path'); -var nodemailer = require("nodemailer"); -require("dotenv").config(); -const { OpenAI } = require("openai"); const app = express(); -const bodyParser = require("body-parser"); -app.use(bodyParser.json()); -app.post("/server.js", (req, res) => { - const dishCountry = req.body.recipe_country_of_origin; - const isUserLactoseIntolerant = req.body.is_lactose_intolerant; - res.json({ - message: `Variables ${dishCountry} and ${isUserLactoseIntolerant} received successfully`, - }); -}); -const openai = new OpenAI({ - apiKey: process.env.openaiAPI, -}); - -let doubleResponse; - -app.get("/email", async (req, res) => { - var transporter = nodemailer.createTransport({ - service: process.env.service, - auth: { - user: process.env.from, - pass: process.env.third_party_app_password, - }, - }); - - if (doubleResponse && doubleResponse.text && doubleResponse.text.choices) { - var mailOptions = { - from: process.env.from, - to: req.query.user_email_address, - subject: "Your recipe from recipe-for-success dynamic app", - text: doubleResponse.text.choices[0].message.content, - }; - } else { - console.log("doubleResponse is not defined yet."); - } - - transporter.sendMail(mailOptions, function (error, info) { - if (error) { - console.log(error); - } else { - console.log("Email sent: " + info.response); - } - }); -}); - -app.get("/openai", async (req, res) => { - const { - recipe_country_of_origin, - is_lactose_intolerant, - is_vegan, - what_are_user_other_dietary_requirements, - } = req.query; - try { - console.log( - { recipe_country_of_origin }, - { is_lactose_intolerant }, - { is_vegan }, - { what_are_user_other_dietary_requirements } - ); - - const prompt = `Provide a recipe for a dish from ${recipe_country_of_origin}, taking into account the fact that I'm ${ - is_lactose_intolerant === "true" - ? "lactose intolerant" - : "not lactose intolerant" - } ${is_vegan === "true" ? "vegan" : "not vegan"} and ${ - what_are_user_other_dietary_requirements === "" - ? "I have no other dietary requirements" - : what_are_user_other_dietary_requirements - } `; - - console.log(prompt); - - const completion = await openai.chat.completions.create({ - messages: [ - { - role: "user", - content: `${prompt}`, - }, - ], - model: "gpt-3.5-turbo", - max_tokens: 2000, - }); - const imageResponse = await openai.images.generate({ - model: "dall-e-3", - prompt: `${prompt}`, - n: 1, - size: "1024x1024", - }); - - doubleResponse = { - text: completion, - image: imageResponse, - }; - res.json(doubleResponse); - } catch (error) { - console.error("An error occurred:", error.message); - res.status(500).json({ error: error.message }); - } -}); - -// const dishCountry = req.body.recipe_country_of_origin; -// const isUserLactoseIntolerant = req.body.is_lactose_intolerant; - -// res.json({ -// message: `Variables ${dishCountry} and ${isUserLactoseIntolerant} received successfully`, -// }); -// }); +app.use(express.json()); +const path = require("path"); +const recipeContoller = require("./controllers/recipes.js"); +const streamController = require("./controllers/stream.js"); +const emailController = require("./controllers/email.js"); +const emailPictureSectionController = require("./controllers/email_picture_section") + +app.get("/stream", streamController.processStream); +app.get("/email", emailController.processEmail); +app.post("/upload", recipeContoller.processUpload); +app.post("/email", emailController.processEmail); +app.post("/email_picture_section", emailPictureSectionController.processEmail) app.use(express.static(path.join(__dirname, "public"))); const port = process.env.PORT || 3000; diff --git a/speech.mp3 b/speech.mp3 new file mode 100644 index 0000000..acc7046 Binary files /dev/null and b/speech.mp3 differ diff --git a/writeMe.txt b/writeMe.txt new file mode 100644 index 0000000..83b39a2 --- /dev/null +++ b/writeMe.txt @@ -0,0 +1,331 @@ + +K +ung + P +ao + T +of +u + + + +Ingredients +: + +- + +1 + block + of + extra +-f +irm + tofu +, + drained + and + pressed + + +- + +1 +/ +4 + cup + soy + sauce + + +- + +3 + cloves + garlic +, + minced + + +- + +1 + tablespoon + fresh + ginger +, + minced + + +- + +2 + tablespoons + ho +isin + sauce + + +- + +1 + tablespoon + rice + vinegar + + +- + +1 + tablespoon + sesame + oil + + +- + +1 + tablespoon + corn +st +arch + + +- + +1 +/ +2 + cup + water + + +- + +1 +/ +2 + cup + uns +alted + peanuts + + +- + +3 +- +4 + green + onions +, + chopped + + +- + +1 + red + bell + pepper +, + sliced + + +- + +1 + green + bell + pepper +, + sliced + + +- + +1 + teaspoon + crushed + red + pepper + flakes + ( +optional +) + +- + Cook +ed + white + rice +, + for + serving + + + +Instructions +: + +1 +. + Cut + the + tofu + into + +1 +-inch + cubes + and + place + in + a + bowl +. + In + a + separate + small + bowl +, + whisk + together + soy + sauce +, + garlic +, + ginger +, + ho +isin + sauce +, + rice + vinegar +, + sesame + oil +, + and + corn +st +arch +. + Pour + this + mixture + over + the + tofu + and + let + mar +inate + for + at + least + +30 + minutes +. + +2 +. + Heat + a + large + skillet + over + medium + heat + and + add + the + mar +inated + tofu + cubes +, + along + with + the + marin +ade +. + Cook + until + the + tofu + is + golden + brown + on + all + sides +. + +3 +. + Add + water +, + peanuts +, + green + onions +, + bell + peppers +, + and + red + pepper + flakes + to + the + skillet +. + Stir + to + combine + and + cook + for + an + additional + +5 +- +7 + minutes +, + or + until + the + vegetables + are + cooked + to + your + liking +. + +4 +. + Serve + the + k +ung + p +ao + tofu + over + cooked + white + rice + and + enjoy +! +