Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@
## Future Tasks

- [ ] Generate additional details (ingredients, origin, calories, taste) then display them in a modal when a user clicks a menu item
- [ ] After upload, do a nice scroll to the loading state when it's loading
- [ ] Better account for errors if it crashes (or if menu is too big). Also warn users it can take up to 60 seconds
- [ ] Make the "use our example" link show a lot of custom menus in different languages, maybe in a modal
- [ ] Iterate on the image prompt to make the images more realistic
- [ ] Try out using Flux Dev instead of Flux Schnell
- [ ] Add some tags as well (like spicy, vegetarian, vegan, etc.) to make the UI better
- [ ] Add filters for those tags to be able to filter by them for food restrictions
19 changes: 10 additions & 9 deletions app/api/parseMenu/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,16 @@ export async function POST(request: Request) {
// Create an array of promises for parallel image generation
const imagePromises = menuItemsJSON.map(async (item: any) => {
console.log("processing image for:", item.name);
const response = await together.images.create({
prompt: `A picture of food for a menu, hyper realistic, highly detailed, ${item.name}, ${item.description}.`,
model: "black-forest-labs/FLUX.1-schnell",
width: 1024,
height: 768,
steps: 5,
// @ts-expect-error - this is not typed in the API
response_format: "base64",
});
const response = await together.images.create({
prompt: `A professional food photography shot of ${item.name}, ultra realistic, high-end restaurant presentation, studio lighting, highly detailed, professional food styling, garnished, on a elegant plate, dramatic lighting, shallow depth of field, commercial photography quality. ${item.description}`,
model: "black-forest-labs/FLUX.1-dev", // Using the dev model for potentially better quality
width: 1024,
height: 768,
steps: 5,
// @ts-expect-error - this is not typed in the API
response_format: "base64",
});

item.menuImage = response.data[0];
return item;
});
Expand Down
68 changes: 45 additions & 23 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useS3Upload } from "next-s3-upload";
import { useState } from "react";
import { useState, useRef } from "react";
import Dropzone from "react-dropzone";
import { PhotoIcon, MagnifyingGlassIcon } from "@heroicons/react/20/solid";
import { Input } from "@/components/ui/input";
Expand All @@ -26,11 +26,25 @@ export default function Home() {
>("initial");
const [parsedMenu, setParsedMenu] = useState<MenuItem[]>([]);
const [searchTerm, setSearchTerm] = useState("");
const [isLoading, setIsLoading] = useState(false);

const loadingRef = useRef<HTMLDivElement>(null);

const scrollToLoading = () => {
loadingRef.current?.scrollIntoView({
behavior: "smooth",
block: "center",
});
};

const handleFileChange = async (file: File) => {
const objectUrl = URL.createObjectURL(file);
setStatus("uploading");
setMenuUrl(objectUrl);

setIsLoading(true);
scrollToLoading();

const { url } = await uploadToS3(file);
setMenuUrl(url);
setStatus("parsing");
Expand All @@ -46,15 +60,20 @@ export default function Home() {
console.log({ json });

setStatus("created");
setIsLoading(false);
setParsedMenu(json.menu);
};

const handleSampleImage = async () => {
setStatus("parsing");
setIsLoading(true);
scrollToLoading();

setMenuUrl(italianMenuUrl);
await new Promise((resolve) => setTimeout(resolve, 3000));

setStatus("created");
setIsLoading(false);
setParsedMenu(italianParsedMenu);
};

Expand Down Expand Up @@ -82,7 +101,7 @@ export default function Home() {
</h1>
</div>
<div className="max-w-3xl text-center mx-auto">
<p className="mb-8 text-lg text-gray-500 text-balance ">
<p className="mb-8 text-lg text-gray-500 text-balance">
Take a picture of your menu and get pictures of each dish so you can
better decide what to order.
</p>
Expand Down Expand Up @@ -136,7 +155,7 @@ export default function Home() {
)}

{menuUrl && (
<div className="my-10 mx-auto flex flex-col items-center">
<div className="my-10 mx-auto flex flex-col items-center">
<Image
width={1024}
height={768}
Expand All @@ -147,29 +166,32 @@ export default function Home() {
</div>
)}

{status === "parsing" && (
<div className="mt-10 flex flex-col items-center">
<div className="flex items-center space-x-4 mb-6">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" />
<p className="text-lg text-gray-600">
Creating your visual menu...
</p>
</div>
<div className="w-full max-w-2xl space-y-4">
<div className="h-8 bg-gray-200 rounded-lg animate-pulse" />
<div className="grid grid-cols-3 gap-4">
{[...Array(6)].map((_, i) => (
<div key={i} className="space-y-2">
<div className="h-32 bg-gray-200 rounded-lg animate-pulse" />
<div className="h-4 bg-gray-200 rounded animate-pulse" />
<div className="h-4 w-2/3 bg-gray-200 rounded animate-pulse" />
</div>
))}
<div ref={loadingRef}>
{isLoading && (
<div className="mt-10 flex flex-col items-center">
<div className="flex items-center space-x-4 mb-6">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-gray-200 border-t-blue-500" />
<p className="text-lg text-gray-600">
Creating your visual menu...
</p>
</div>
<div className="w-full max-w-2xl space-y-4">
<div className="h-8 bg-gray-200 rounded-lg animate-pulse" />
<div className="grid grid-cols-3 gap-4">
{[...Array(6)].map((_, i) => (
<div key={i} className="space-y-2">
<div className="h-32 bg-gray-200 rounded-lg animate-pulse" />
<div className="h-4 bg-gray-200 rounded animate-pulse" />
<div className="h-4 w-2/3 bg-gray-200 rounded animate-pulse" />
</div>
))}
</div>
</div>
</div>
</div>
)}
)}
</div>
</div>

{parsedMenu.length > 0 && (
<div className="mt-10">
<h2 className="text-4xl font-bold mb-5">
Expand Down