Calorie Count via Photo

Uploads your photo to ChatGPT's new vision model to automatically categorize the food and estimate the calories.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @jsxImportSource npm:hono@3/jsx */
import { fileToDataURL } from "https://esm.town/v/stevekrouse/fileToDataURL";
import { modifyImage } from "https://esm.town/v/stevekrouse/modifyImage";
import { chat } from "https://esm.town/v/stevekrouse/openai";
import { Hono } from "npm:hono@3";
function esmTown(url) {
return fetch(url, {
headers: {
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.142.86 Safari/537.36",
},
}).then(r => r.text());
}
const app = new Hono();
export default app.fetch;
app.get("/", async (c) =>
c.html(
<html>
<head>
<title>Calorie Estimator</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com" />
</head>
<body class="h-dvh bg-pink-100">
<div class="flex flex-col h-full items-center p-20">
<a href="/">
<h1 class="text-5xl font-extrabold text-pink-900 hover:underline">How many calories?</h1>
</a>
<div class="flex flex-col flex-grow justify-center items-center">
<form
action="/"
target="results"
class="flex flex-col"
method="post"
enctype="multipart/form-data"
>
<div></div>
<label
for="file"
id="upload"
class="px-6 py-4 m-4 text-3xl text-center cursor-pointer border-4 border-pink-400 rounded-xl transition-all bg-pink-300 hover:bg-pink-400 hover:text-pink-900 text-pink-900 shadow-2xl shadow-pink-500/50"
>
Upload photo
</label>
<input
class="hidden"
type="file"
id="file"
accept="image/*"
name="file"
/>
<img class="rounded-md shadow-2xl shadow-pink-500/50" id="preview" />
</form>
<iframe width="200px" name="results" class="hidden" id="results" srcdoc="Loading..."></iframe>
</div>
<a href="https://val.town/v/stevekrouse/calories" class="hover:underline text-pink-700">
view code
</a>
</div>
</body>
<script
dangerouslySetInnerHTML={{
__html: await esmTown("https://esm.town/v/stevekrouse/calories_script"),
}}
type="module"
/>
</html>,
));
app.post("/", async (c) => {
const formData = await c.req.formData();
const file = formData.get("file") as File;
console.log(file);
if (!file || !file.size) {
return c.text("No file uploaded", 400);
}
const estimates = await calorieEstimate(file);
if (estimates.error) {
return c.text(estimates.error, 500);
}
const dataURL = await fileToDataURL(file);
return c.html(
<div>
{estimates.map((estimate) => <p>{estimate.ingredient}: {estimate.calories} calories</p>)}
Total: {estimates.reduce((sum, estimate) => sum + estimate.calories, 0)}
</div>,
);
});
export async function calorieEstimate(file: File) {
const dataURL = await fileToDataURL(file);
try {
const response = await chat([
{
role: "system",
content: `You are an nutritionist.
Estimate the calories.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { fileToDataURL } from "https://esm.town/v/stevekrouse/fileToDataURL?v=1";
import { convertBlobToDataURL } from "https://esm.town/v/vladimyr/dataURL";
const file = new File(["testing stuff"], "test.txt", { type: "text/plain" });
const dataURL1 = await fileToDataURL(file);
const dataURL2 = await convertBlobToDataURL(file);
console.assert(dataURL1 === dataURL2);
console.log(dataURL1);
const resp = await fetch(dataURL1);
const text = await resp.text();
console.assert(resp.headers.get("content-type") === "text/plain");
console.assert(text === "testing stuff");
console.log(text);
1
Next