3Ask me about r's in strawberry
4
5
6
7
3This is a lightweight Blob Admin interface to view and debug your Blob data.
4
5
6
7## Installation
1# Blob Storage - [Docs β](https://docs.val.town/std/blob)
2
3Val Town comes with blob storage built-in. It allows for storing any data, like text, JSON, or images. You can access it via [`std/blob`](https://www.val.town/v/std/blob).
4
5Blob storage is scoped globally to your account. If you set a blob in one val, you can retrieve it by the same key in another val. It's backed by Cloudflare R2.
5/**
6 * Provides functions for interacting with your account's blob storage.
7 * Blobs can store any data type (text, JSON, images, etc.) and allow
8 * retrieval across different vals using the same key.
9 * ([Docs β](https://docs.val.town/std/blob))
3View and interact with your Val Town SQLite data. It's based off Steve's excellent [SQLite Admin](https://www.val.town/v/stevekrouse/sqlite_admin?v=46) val, adding the ability to run SQLite queries directly in the interface. This new version has a revised UI and that's heavily inspired by [LibSQL Studio](https://github.com/invisal/libsql-studio) by [invisal](https://github.com/invisal). This is now more an SPA, with tables, queries and results showing up on the same page.
4
5
6
7## Install
3This is a lightweight Blob Admin interface to view and debug your Blob data.
4
5
6
7## Installation
3This is a lightweight Blob Admin interface to view and debug your Blob data.
4
5
6
7## Installation
1import { generateImageSummary } from "https://esm.town/v/jackd/generateImageSummary";
2import { email } from "https://esm.town/v/std/email";
3
6
7 if (e.attachments.length === 0) {
8 await sendReply(e.from, "No attachment found", "Please send an email with a food image attachment.");
9 return;
10 }
11
12 const attachment = e.attachments[0];
13 if (!attachment.type.startsWith("image/")) {
14 await sendReply(e.from, "Invalid attachment", "Please send a food image as an attachment.");
15 return;
16 }
17
18 try {
19 const observations = await generateImageSummary(attachment);
20 const htmlContent = formatEmailContent(observations);
21 await sendReply(e.from, "Screenshot Analysis", htmlContent);
24 let errorMessage = "An error occurred while processing your screenshot. ";
25 if (error instanceof RangeError) {
26 errorMessage += "The image file might be too large. Please try with a smaller image.";
27 } else {
28 errorMessage += "Please try again later.";
39 return `
40<div style="font-family: Arial, sans-serif; color: rgb(51,51,51); line-height: 1.5; padding: 12px;">
41 <h2 style="color: rgb(51,51,51); margin-bottom: 12px;">Analysis of Your Image</h2>
42 <ul style="margin: 0; padding-left: 24px;">
43 ${listItems}
2import { fileToDataURL } from "https://esm.town/v/stevekrouse/fileToDataURL";
3
4export async function generateImageSummary(image: File): Promise<string[]> {
5 const openai = new OpenAI();
6 const dataURL = await fileToDataURL(image);
7 const response = await openai.chat.completions.create({
8 model: "gpt-4-vision-preview",
11 role: "system",
12 content: `
13You are an assistant that analyzes images and returns exactly 3 key observations.
14Return your response as a JSON array of exactly 3 strings.
15Each string should be a clear, concise observation about the most important or notable elements in the image.
16Do not include bullet points, numbers, or any formatting - just the plain text observations.
17
28 role: "user",
29 content: [{
30 type: "image_url",
31 image_url: {
32 url: dataURL,
33 },
41 const content = response.choices[0].message.content;
42 if (!content) {
43 throw new Error("Unable to generate image analysis.");
44 }
45
55 } catch (error) {
56 console.error("Error parsing AI response:", error);
57 throw new Error("Failed to parse image analysis response");
58 }
59}
25 transparent: true,
26 sizeAttenuation: true,
27 map: new THREE.TextureLoader().load('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oMCRUiMrIBQVkAAACdSURBVBjTXY69DcJAEESD2YrgcB9HdEAHJHSAaIBWXAsuXOLshPTwJ0GIAhB/HRABCZjZkW5XelrpVeMhIh5TSt854x0/MDXe8Zt/4GeMcV6W5XNd131K6Qg0xnj0fT+XUm4ppTPQMzAQ0fO+BZrn+QHshRA+bdseqpI+wLcs3/e+9JMRUK21G5nXOb/jpa7rZwixfudSisGHrgPvTlKI/AKLe0YbZiEBRQAAAABJRU5ErkJggg==')
28 });
29
51 blending: THREE.AdditiveBlending,
52 sizeAttenuation: true,
53 map: new THREE.TextureLoader().load('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oMCRUiMrIBQVkAAACdSURBVBjTXY69DcJAEESD2YrgcB9HdEAHJHSAaIBWXAsuXOLshPTwJ0GIAhB/HRABCZjZkW5XelrpVeMhIh5TSt854x0/MDXe8Zt/4GeMcV6W5XNd131K6Qg0xnj0fT+XUm4ppTPQMzAQ0fO+BZrn+QHshRA+bdseqpI+wLcs3/e+9JMRUK21G5nXOb/jpa7rZwixfudSisGHrgPvTlKI/AKLe0YbZiEBRQAAAABJRU5ErkJggg==')
54 });
55
83 blending: THREE.AdditiveBlending,
84 sizeAttenuation: true,
85 map: new THREE.TextureLoader().load('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oMCRUiMrIBQVkAAACdSURBVBjTXY69DcJAEESD2YrgcB9HdEAHJHSAaIBWXAsuXOLshPTwJ0GIAhB/HRABCZjZkW5XelrpVeMhIh5TSt854x0/MDXe8Zt/4GeMcV6W5XNd131K6Qg0xnj0fT+XUm4ppTPQMzAQ0fO+BZrn+QHshRA+bdseqpI+wLcs3/e+9JMRUK21G5nXOb/jpa7rZwixfudSisGHrgPvTlKI/AKLe0YbZiEBRQAAAABJRU5ErkJggg==')
86 });
87