completed demo. need to start making it production ready
This commit is contained in:
parent
eb6a3e04ab
commit
e7b4eaac0b
44
app.js
44
app.js
@ -2,17 +2,61 @@ const express = require('express');
|
||||
const multer = require('multer');
|
||||
const axios = require('axios');
|
||||
const fs = require('fs'); // Add this line to import the fs module
|
||||
const mime = require('mime-types');
|
||||
const app = express();
|
||||
const port = 3000;
|
||||
|
||||
const upload = multer({ dest: 'uploads/' });
|
||||
|
||||
const GROQ_API_KEY = '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';
|
||||
const model = 'llama3-8b-8192';
|
||||
|
||||
app.use(express.static("static"));
|
||||
app.use(express.json());
|
||||
|
||||
// Middleware to serve static files with correct MIME type
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(__dirname + '/static/index.html');
|
||||
});
|
||||
|
||||
// Serve the script.js file with the correct MIME type
|
||||
app.get('/script.js', (req, res) => {
|
||||
|
||||
const filePath = __dirname + '/static/script.js';
|
||||
|
||||
res.set('Content-Type', 'application/javascript');
|
||||
|
||||
res.sendFile(filePath);
|
||||
|
||||
});
|
||||
|
||||
app.post('/completions', async (req, res) => {
|
||||
try {
|
||||
const query = req.body.query;
|
||||
if (!query) {
|
||||
return res.status(400).json({ error: 'Prompt is required' });
|
||||
}
|
||||
|
||||
const data = {
|
||||
messages: [{ role: 'user', content: query }],
|
||||
model,
|
||||
};
|
||||
|
||||
const config = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${GROQ_API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await axios.post('https://api.groq.com/openai/v1/chat/completions', data, config);
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: 'Error completing prompt' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/upload', upload.single('image'), async (req, res) => {
|
||||
try {
|
||||
|
@ -1,176 +1,19 @@
|
||||
<!-- <!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Upload Image</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="file" id="imageInput" accept="image/*" required />
|
||||
<button onclick="uploadImage()">Upload Image</button> <br /><br />
|
||||
<div id="editable-classes"></div>
|
||||
<button id="add-class-btn">Add Class</button>
|
||||
<div id="suggested-classes"></div>
|
||||
</body>
|
||||
</html> -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<title>Fridge2Fit</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Fridge2Fit is an AI-powered recipe generator that helps individuals with obesity take control of their diet. Simply upload a picture of your fridge contents, and our algorithm will suggest healthy recipes tailored to your ingredients. Our goal is to empower users to make informed food choices, reduce food waste, and promote a healthier lifestyle."
|
||||
/>
|
||||
<meta
|
||||
name="keywords"
|
||||
content="ai artificial intelligence fridge fit food fat obesity health healthy"
|
||||
/>
|
||||
|
||||
<link href="/dist.css" rel="stylesheet" />
|
||||
<script src="/load.js" defer></script>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Upload Image</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="file" id="imageInput" accept="image/*" required=""> <button onclick="uploadImage()" id="upload-image-btn">Upload Image</button>
|
||||
<br><br>
|
||||
<div id="editable-classes"></div>
|
||||
<button id="add-class-btn">Add Class</button>
|
||||
<div id="suggested-classes"></div>
|
||||
<br>
|
||||
|
||||
<body
|
||||
class="leading-normal tracking-normal text-indigo-400 m-6 bg-cover bg-fixed overflow-hidden"
|
||||
style="background-image: url('header.png')"
|
||||
>
|
||||
<div class="h-full">
|
||||
<!--Nav-->
|
||||
<div class="w-full container mx-auto">
|
||||
<div class="w-full flex items-center justify-between">
|
||||
<a
|
||||
class="flex items-center text-indigo-400 no-underline hover:no-underline font-bold text-2xl lg:text-4xl"
|
||||
href="#"
|
||||
>
|
||||
Fridge<span
|
||||
class="bg-clip-text text-transparent bg-gradient-to-r from-green-400 via-pink-500 to-purple-500"
|
||||
>2Fit</span
|
||||
>
|
||||
</a>
|
||||
|
||||
<div class="flex w-1/2 justify-end content-center">
|
||||
<a
|
||||
class="inline-block text-blue-300 no-underline hover:text-pink-500 hover:text-underline text-center h-10 p-2 md:h-auto md:p-4 transform hover:scale-125 duration-300 ease-in-out"
|
||||
href="https://twitter.com/intent/tweet?url=#"
|
||||
>
|
||||
<svg
|
||||
class="fill-current h-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<path
|
||||
d="M30.063 7.313c-.813 1.125-1.75 2.125-2.875 2.938v.75c0 1.563-.188 3.125-.688 4.625a15.088 15.088 0 0 1-2.063 4.438c-.875 1.438-2 2.688-3.25 3.813a15.015 15.015 0 0 1-4.625 2.563c-1.813.688-3.75 1-5.75 1-3.25 0-6.188-.875-8.875-2.625.438.063.875.125 1.375.125 2.688 0 5.063-.875 7.188-2.5-1.25 0-2.375-.375-3.375-1.125s-1.688-1.688-2.063-2.875c.438.063.813.125 1.125.125.5 0 1-.063 1.5-.25-1.313-.25-2.438-.938-3.313-1.938a5.673 5.673 0 0 1-1.313-3.688v-.063c.813.438 1.688.688 2.625.688a5.228 5.228 0 0 1-1.875-2c-.5-.875-.688-1.813-.688-2.75 0-1.063.25-2.063.75-2.938 1.438 1.75 3.188 3.188 5.25 4.25s4.313 1.688 6.688 1.813a5.579 5.579 0 0 1 1.5-5.438c1.125-1.125 2.5-1.688 4.125-1.688s3.063.625 4.188 1.813a11.48 11.48 0 0 0 3.688-1.375c-.438 1.375-1.313 2.438-2.563 3.188 1.125-.125 2.188-.438 3.313-.875z"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
class="inline-block text-blue-300 no-underline hover:text-pink-500 hover:text-underline text-center h-10 p-2 md:h-auto md:p-4 transform hover:scale-125 duration-300 ease-in-out"
|
||||
href="https://www.facebook.com/sharer/sharer.php?u=#"
|
||||
>
|
||||
<svg
|
||||
class="fill-current h-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<path
|
||||
d="M19 6h5V0h-5c-3.86 0-7 3.14-7 7v3H8v6h4v16h6V16h5l1-6h-6V7c0-.542.458-1 1-1z"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Main-->
|
||||
<div
|
||||
class="container pt-24 md:pt-36 mx-auto flex flex-wrap flex-col md:flex-row items-center"
|
||||
>
|
||||
<!--Left Col-->
|
||||
<div
|
||||
class="flex flex-col w-full xl:w-2/5 justify-center lg:items-start overflow-y-hidden"
|
||||
>
|
||||
<h1
|
||||
class="my-4 text-3xl md:text-5xl text-white opacity-75 font-bold leading-tight text-center md:text-left"
|
||||
>
|
||||
Main
|
||||
<span
|
||||
class="bg-clip-text text-transparent bg-gradient-to-r from-green-400 via-pink-500 to-purple-500"
|
||||
>
|
||||
Hero Message
|
||||
</span>
|
||||
to sell yourself!
|
||||
</h1>
|
||||
<p
|
||||
class="leading-normal text-base md:text-2xl mb-8 text-center md:text-left"
|
||||
>
|
||||
An AI-powered recipe generator that helps individuals
|
||||
with obesity take control of their diet.
|
||||
</p>
|
||||
|
||||
<form
|
||||
class="bg-gray-800 opacity-75 w-full shadow-lg rounded-lg px-8 pt-6 pb-8 mb-4 h-64"
|
||||
>
|
||||
<button
|
||||
class="text-4xl w-full h-full bg-gradient-to-r from-purple-800 to-green-500 hover:from-pink-500 hover:to-green-500 text-white font-bold py-2 px-4 rounded-3xl focus:ring transform transition hover:scale-105 duration-300 ease-in-out flex items-center"
|
||||
onclick="document.getElementById('imageInput').click();"
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
id="imageInput"
|
||||
accept="image/*"
|
||||
required
|
||||
hidden
|
||||
/>
|
||||
<img
|
||||
src="upload.svg"
|
||||
class="h-1/2 pr-8 brightness-150"
|
||||
/>
|
||||
Upload your fridge
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!--Right Col-->
|
||||
<div class="w-full xl:w-3/5 p-12 overflow-hidden">
|
||||
<img
|
||||
class="mx-auto w-full md:w-4/5 transform -rotate-6 transition hover:scale-105 duration-700 ease-in-out hover:rotate-6"
|
||||
src="macbook.svg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto md:pt-16">
|
||||
<p
|
||||
class="text-blue-400 font-bold pb-8 lg:pb-6 text-center"
|
||||
></p>
|
||||
<div
|
||||
class="flex w-full justify-center md:justify-start pb-24 lg:pb-0 fade-in"
|
||||
>
|
||||
<img
|
||||
class="h-12 pr-12 transform hover:scale-125 duration-300 ease-in-out"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
class="h-12 transform hover:scale-125 duration-300 ease-in-out"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Footer-->
|
||||
<div
|
||||
class="w-full pt-16 pb-6 text-sm text-center md:text-left fade-in"
|
||||
>
|
||||
<a
|
||||
class="text-gray-500 no-underline hover:no-underline"
|
||||
href="#"
|
||||
>© Fridge2Fit 2024</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="generateIngredients()">Generate Ingredients</button>
|
||||
<script src="/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
187
static/script.js
Normal file
187
static/script.js
Normal file
@ -0,0 +1,187 @@
|
||||
const editableClassesDiv = document.getElementById("editable-classes");
|
||||
const suggestedClassesDiv = document.getElementById("suggested-classes");
|
||||
const addClassBtn = document.getElementById("add-class-btn");
|
||||
const uploadImageBtn = document.getElementById("upload-image-btn");
|
||||
|
||||
const uploadImage = async () => {
|
||||
const input = document.getElementById('imageInput');
|
||||
const file = input.files[0];
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
|
||||
try {
|
||||
uploadImageBtn.disabled = true;
|
||||
const response = await fetch('../upload', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to upload image');
|
||||
}
|
||||
|
||||
const jsonResponse = await response.json();
|
||||
|
||||
const classes = jsonResponse.predictions;
|
||||
|
||||
// Remove all underscores
|
||||
|
||||
classes.forEach(str => {
|
||||
str.class = str.class.replace(/_/g, ' ');
|
||||
});
|
||||
|
||||
// Create input fields for classes with confidence >= 0.5
|
||||
|
||||
classes.filter((cls) => cls.confidence >= 0.5).forEach((cls) => {
|
||||
addInputField(cls.class);
|
||||
|
||||
});
|
||||
|
||||
// Create buttons for classes with confidence < 0.5
|
||||
|
||||
classes.filter((cls) => cls.confidence < 0.5).forEach((cls) => {
|
||||
addSuggestion(cls.class);
|
||||
});
|
||||
addSuggestion("salt and pepper");
|
||||
|
||||
|
||||
// Add event listener to "Add Class" button
|
||||
addClassBtn.onclick = () => {
|
||||
addInputField("");
|
||||
};
|
||||
|
||||
} catch (exception) {
|
||||
uploadImageBtn.disabled = false;
|
||||
console.log(exception);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function addInputField(content) {
|
||||
const inputField = document.createElement("input");
|
||||
inputField.type = "text";
|
||||
inputField.value = content;
|
||||
|
||||
const closeButton = document.createElement("button");
|
||||
closeButton.style.border = "none";
|
||||
closeButton.style.background = "transparent";
|
||||
closeButton.style.cursor = "pointer";
|
||||
closeButton.innerHTML = "x";
|
||||
|
||||
closeButton.addEventListener("click", () => {
|
||||
editableClassesDiv.removeChild(inputField);
|
||||
editableClassesDiv.removeChild(br);
|
||||
editableClassesDiv.removeChild(closeButton);
|
||||
});
|
||||
|
||||
editableClassesDiv.appendChild(inputField);
|
||||
editableClassesDiv.appendChild(closeButton);
|
||||
const br = document.createElement("br");
|
||||
editableClassesDiv.appendChild(br);
|
||||
}
|
||||
|
||||
function addSuggestion(content) {
|
||||
const button = document.createElement("button");
|
||||
button.textContent = content;
|
||||
button.onclick = () => {
|
||||
addInputField(content);
|
||||
button.remove();
|
||||
};
|
||||
suggestedClassesDiv.appendChild(button);
|
||||
}
|
||||
|
||||
async function createInference(query) {
|
||||
try {
|
||||
const response = await fetch("/completions", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const content = data.choices[0].message.content;
|
||||
return content;
|
||||
} catch (error) {
|
||||
return `<p>Error: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function generateIngredients() {
|
||||
// Get all input elements inside the div
|
||||
const inputs = editableClassesDiv.querySelectorAll("input");
|
||||
|
||||
// Initialize an empty string to hold the input values
|
||||
let values = "";
|
||||
|
||||
// Check if there are any input elements
|
||||
if (inputs.length === 0) {
|
||||
return; // Exit the function silently if there are no input elements
|
||||
}
|
||||
|
||||
// Loop through the input elements and add their values to the string
|
||||
inputs.forEach((input) => {
|
||||
if (values !== "") {
|
||||
values += ", ";
|
||||
}
|
||||
values += input.value;
|
||||
});
|
||||
|
||||
// Alert the input values
|
||||
const output = await createInference("Make 4 recipes based on the following exclusive ingredients:" + values);
|
||||
|
||||
// Create a new div element to hold the output
|
||||
const outputDiv = document.createElement("div");
|
||||
outputDiv.innerHTML = format(output);
|
||||
console.log(output);
|
||||
|
||||
// Add the output div to the page
|
||||
document.body.appendChild(outputDiv);
|
||||
}
|
||||
|
||||
function format(markdown) {
|
||||
// Replace newlines with br tags
|
||||
var formatted = markdown.replace(/\n/g, '<br>');
|
||||
|
||||
// Replace bold with b tags
|
||||
const boldRegex = /\*\*(.+?)\*\*/g;
|
||||
const boldMatches = formatted.match(boldRegex);
|
||||
|
||||
if (boldMatches) {
|
||||
boldMatches.forEach(match => {
|
||||
const boldText = match.replace(boldRegex, '<b>$1</b>');
|
||||
formatted = formatted.replace(match, boldText);
|
||||
});
|
||||
}
|
||||
|
||||
// Replace italics with i tags
|
||||
const italicRegex = /\*(.+?)\*/g;
|
||||
const italicMatches = formatted.match(italicRegex);
|
||||
|
||||
if (italicMatches) {
|
||||
italicMatches.forEach(match => {
|
||||
const italicText = match.replace(italicRegex, '<i>$1</i>');
|
||||
formatted = formatted.replace(match, italicText);
|
||||
});
|
||||
}
|
||||
|
||||
// Replace underline with u tags
|
||||
const underlineRegex = /__(.+?)__/g;
|
||||
const underlineMatches = formatted.match(underlineRegex);
|
||||
|
||||
if (underlineMatches) {
|
||||
underlineMatches.forEach(match => {
|
||||
const underlineText = match.replace(underlineRegex, '<u>$1</u>');
|
||||
formatted = formatted.replace(match, underlineText);
|
||||
});
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
Loading…
Reference in New Issue
Block a user