sqliteExplorerAppREADME.md1 match
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.
45
67## Install
egoBoostermain.tsx15 matches
2* This ego booster app takes a selfie, sends it to GPT-4o-mini for analysis,
3* and streams funny, specific compliments about the user's appearance.
4* We use the WebRTC API for camera access, the OpenAI API for image analysis,
5* and server-sent events for real-time streaming of compliments.
6*/
73const context = canvasRef.current.getContext('2d');
74if (context) {
75context.drawImage(videoRef.current, 0, 0, canvasRef.current.width, canvasRef.current.height);
76console.log("Selfie captured on canvas");
77try {
78const blob = await new Promise<Blob | null>((resolve) => canvasRef.current!.toBlob(resolve, 'image/jpeg'));
79if (blob) {
80console.log("Blob created, size:", blob.size);
81const formData = new FormData();
82formData.append('image', blob, 'selfie.jpg');
83
84console.log("Sending request to /analyze");
112} else {
113console.log("Failed to create blob");
114throw new Error("Failed to create image blob");
115}
116} catch (error) {
177console.log("Handling POST request to /analyze");
178const formData = await request.formData();
179const image = formData.get('image') as File;
180181if (!image) {
182console.log("No image provided in request");
183return new Response('No image provided', { status: 400 });
184} else {
185console.log("Image received, size:", image.size);
186}
187192async start(controller) {
193try {
194console.log("Starting to process image");
195const arrayBuffer = await image.arrayBuffer();
196const base64Image = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
197198console.log("Sending request to OpenAI");
202{
203role: "system",
204content: "You are a hilarious and overly enthusiastic ego booster. Your job is to provide 5 funny, specific, and over-the-top compliments about the person's appearance in the image. Be creative, exaggerate, and make it entertaining! Keep each compliment short and punchy, around 10-15 words max. Use lots of emojis and fun language. Respond in markdown format, with each compliment as a separate bullet point."
205},
206{
208content: [
209{ type: "text", text: "Analyze this selfie and give me 5 short, funny, specific compliments about my appearance:" },
210{ type: "image_url", image_url: { url: `data:image/jpeg;base64,${base64Image}` } }
211]
212}
password_authREADME.md1 match
3Protect your vals behind a password. Use session cookies to persist authentication.
45
67## Demo
218functions where possible. Unless specified, don't add error handling,
219make sure that errors bubble up to the caller.
220Avoid external images or base64 images, use emojis, unicode symtols, or icon fonts/libraries instead, unless that's
221not practical for the user's request (e.g. if they ask for a particular animated gif).
222If the user asks for something that requires persistence, use the Val Town Blob storage API, unless
280functions where possible. Unless specified, don't add error handling,
281make sure that errors bubble up to the caller.
282Avoid external images or base64 images, use emojis, unicode symtols, or icon fonts/libraries instead, unless that's
283not practical for the user's request (e.g. if they ask for a particular animated gif).
284If the user asks for something that requires persistence, use the Val Town Blob storage API, unless
315---
316
317Val Town comes with blob storage built-in. It allows for storing any data: text, JSON, images. You can access it via [\`std/blob\`](https://www.val.town/v/std/blob).
318
319Blob 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.
eldestBronzePtarmiganmain.tsx37 matches
1/**
2* This dating app allows users to upload photos of their bedside table and phone case.
3* It uses Val Town's SQLite for data persistence and blob storage for image uploads.
4* The app includes a simple matching algorithm based on common items in bedside tables.
5*/
12const [user, setUser] = useState(null);
13const [matches, setMatches] = useState([]);
14const [bedsideImage, setBedsideImage] = useState(null);
15const [phoneCaseImage, setPhoneCaseImage] = useState(null);
1617useEffect(() => {
25const userData = await response.json();
26setUser(userData);
27setBedsideImage(userData.bedsideImage);
28setPhoneCaseImage(userData.phoneCaseImage);
29}
30};
38};
3940const handleImageUpload = async (event, type) => {
41const file = event.target.files[0];
42const formData = new FormData();
43formData.append('image', file);
44formData.append('type', type);
4552const result = await response.json();
53if (type === 'bedside') {
54setBedsideImage(result.imageUrl);
55} else {
56setPhoneCaseImage(result.imageUrl);
57}
58}
65<div>
66<h2>Welcome, {user.name}!</h2>
67<div className="image-upload">
68<h3>Your Bedside Table</h3>
69{bedsideImage ? (
70<img src={bedsideImage} alt="Bedside Table" className="uploaded-image" />
71) : (
72<input type="file" onChange={(e) => handleImageUpload(e, 'bedside')} />
73)}
74</div>
75<div className="image-upload">
76<h3>Your Phone Case</h3>
77{phoneCaseImage ? (
78<img src={phoneCaseImage} alt="Phone Case" className="uploaded-image" />
79) : (
80<input type="file" onChange={(e) => handleImageUpload(e, 'phonecase')} />
81)}
82</div>
86<div key={match.id} className="match">
87<h4>{match.name}</h4>
88<img src={match.bedsideImage} alt="Bedside Table" className="match-image" />
89<img src={match.phoneCaseImage} alt="Phone Case" className="match-image" />
90</div>
91))}
118id INTEGER PRIMARY KEY AUTOINCREMENT,
119name TEXT NOT NULL,
120bedsideImage TEXT,
121phoneCaseImage TEXT
122)
123`);
131id: 1,
132name: "John Doe",
133bedsideImage: "https://maxm-imggenurl.web.val.run/a-simple-bedside-table-with-a-lamp-and-book",
134phoneCaseImage: "https://maxm-imggenurl.web.val.run/a-plain-black-phone-case"
135};
136return new Response(JSON.stringify(user), { headers: { 'Content-Type': 'application/json' } });
140// In a real app, you'd implement a matching algorithm. Here we're returning dummy data.
141const matches = [
142{ id: 2, name: "Jane Smith", bedsideImage: "https://maxm-imggenurl.web.val.run/a-bedside-table-with-plants-and-a-clock", phoneCaseImage: "https://maxm-imggenurl.web.val.run/a-colorful-floral-phone-case" },
143{ id: 3, name: "Bob Johnson", bedsideImage: "https://maxm-imggenurl.web.val.run/a-messy-bedside-table-with-many-gadgets", phoneCaseImage: "https://maxm-imggenurl.web.val.run/a-rugged-outdoor-phone-case" }
144];
145return new Response(JSON.stringify(matches), { headers: { 'Content-Type': 'application/json' } });
148if (path === '/upload' && request.method === 'POST') {
149const formData = await request.formData();
150const image = formData.get('image') as File;
151const type = formData.get('type') as string;
152153if (image && type) {
154const imageBuffer = await image.arrayBuffer();
155const imageKey = `${KEY}_${type}_${Date.now()}`;
156await blob.set(imageKey, imageBuffer);
157const imageUrl = `https://val.town/v/${KEY}/blobs/${imageKey}`;
158159// Update user's image in the database
160await sqlite.execute(`
161UPDATE ${KEY}_users
162SET ${type === 'bedside' ? 'bedsideImage' : 'phoneCaseImage'} = ?
163WHERE id = 1
164`, [imageUrl]);
165166return new Response(JSON.stringify({ imageUrl }), { headers: { 'Content-Type': 'application/json' } });
167}
168206}
207208.image-upload {
209margin-bottom: 20px;
210}
211212.uploaded-image, .match-image {
213max-width: 200px;
214max-height: 200px;
1interface WikipediaImage {
2source: string;
3width: number;
9pageId: number;
10extract: string;
11image?: WikipediaImage;
12url: string;
13}
1415export const fetchWikipediaImage = async (request: Request): Promise<Response> => {
16const url = new URL(request.url);
17const pageName = url.searchParams.get("title");
30return formatResponse(pageData, format);
31} catch (error) {
32console.error("Error fetching Wikipedia image:", error);
33return new Response("An error occurred while fetching the image", { status: 500 });
34}
35};
63apiUrl.searchParams.append("action", "query");
64apiUrl.searchParams.append("titles", searchResult.title);
65apiUrl.searchParams.append("prop", "pageimages|extracts|info|images|pageprops");
66apiUrl.searchParams.append("piprop", "original|name");
67apiUrl.searchParams.append("pithumbsize", "1000");
89const page = pages[0] as any;
9091const image = await getImageFromPage(page);
9293return {
95pageId: page.pageid,
96extract: page.extract || "No description available.",
97image: image,
98url: page.fullurl,
99};
100}
101102async function getImageFromPage(page: any): Promise<WikipediaImage | undefined> {
103console.log("Page data:", JSON.stringify(page, null, 2));
104105// Check for page_image in pageprops (this is usually the main image)
106if (page.pageprops && page.pageprops.page_image) {
107const imageData = await getImageInfo(page.pageprops.page_image);
108if (imageData) return imageData;
109}
110111// If no main image found, try the original image
112if (page.original) {
113return {
118}
119120// If still no image, try the images array
121if (page.images && page.images.length > 0) {
122for (const image of page.images) {
123const imageData = await getImageInfo(image.title);
124if (imageData) return imageData;
125}
126}
127128console.log("No image found for the page.");
129return undefined;
130}
131132async function getImageInfo(filename: string): Promise<WikipediaImage | undefined> {
133// Remove 'File:' prefix if it exists
134filename = filename.replace(/^File:/, "");
137apiUrl.searchParams.append("action", "query");
138apiUrl.searchParams.append("titles", `File:${filename}`);
139apiUrl.searchParams.append("prop", "imageinfo");
140apiUrl.searchParams.append("iiprop", "url|size");
141apiUrl.searchParams.append("format", "json");
144const data = await response.json();
145146console.log("Image info response:", JSON.stringify(data, null, 2));
147148const pages = Object.values(data.query.pages);
149if (pages.length > 0) {
150const page = pages[0] as any;
151if (page.imageinfo && page.imageinfo[0]) {
152const imageInfo = page.imageinfo[0];
153return {
154source: imageInfo.url,
155width: imageInfo.width,
156height: imageInfo.height,
157};
158}
159}
160161console.log("No image info found for:", filename);
162return undefined;
163}
168): Response {
169const responseData = {
170imageUrl: pageData.image ? pageData.image.source : "No image available",
171pageTitle: pageData.title,
172pageUrl: pageData.url,
186<h2>${responseData.pageTitle}</h2>
187${
188responseData.imageUrl !== "No image available"
189? `<img src="${responseData.imageUrl}" alt="${responseData.pageTitle}" style="max-width: 100%; height: auto;"/>`
190: "<p>No image available</p>"
191}
192<p>${responseData.extract}</p>
198});
199default:
200return new Response(responseData.imageUrl, {
201headers: { "Content-Type": "text/plain" },
202});
22borderWindowOuter: `inset -1px -1px ${colors.windowFrame}, inset 1px 1px ${colors.buttonFace}`,
23minimizeIcon:
24"data:image/svg+xml;charset=utf-8,%3Csvg width='6' height='2' viewBox='0 0 6 2' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Crect width='6' height='2' fill='black'/%3E %3C/svg%3E",
25maximizeIcon:
26"data:image/svg+xml;charset=utf-8,%3Csvg width='9' height='8' viewBox='0 0 9 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 2V7V8H1H8H9V7V2V0H8H1H0V2ZM8 7V2H1V7H8Z' fill='black'/%3E %3C/svg%3E",
27closeIcon:
28"data:image/svg+xml;charset=utf-8,%3Csvg width='8' height='7' viewBox='0 0 8 7' fill='none' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0H1H2V1H3V2H4H5V1H6V0H7H8V1H7V2H6V3H5V4H6V5H7V6H8V7H7H6V6H5V5H4H3V6H2V7H1H0V6H1V5H2V4H3V3H2V2H1V1H0V0Z' fill='black'/%3E %3C/svg%3E",
29elementSpacing: 8,
30};
122<TitleBarControl
123style={{
124backgroundImage: `url(${tokens.minimizeIcon})`,
125backgroundRepeat: "no-repeat",
126// // Only a single value is supported
139<TitleBarControl
140style={{
141backgroundImage: `url(${tokens.maximizeIcon})`,
142backgroundRepeat: "no-repeat",
143// backgroundPosition: "top 2px left 3x",
155marginLeft={2}
156style={{
157backgroundImage: `url(${tokens.closeIcon})`,
158backgroundRepeat: "no-repeat",
159// backgroundPosition: "top 3px left 4px",
285286return c.body(svg, 200, {
287"Content-Type": "image/svg+xml",
288});
289});
modifyImageREADME.md2 matches
1Code from https://deno.com/blog/build-image-resizing-api
23Useful for compressing an image before sending to chatgpt4v, for example
modifyImagemain.tsx6 matches
1import { ImageMagick, initializeImageMagick, MagickGeometry } from "https://deno.land/x/imagemagick_deno@0.0.14/mod.ts";
23export async function modifyImage(
4file: File,
5params: { width: number; height: number },
6) {
7const imageBuffer = new Uint8Array(await file.arrayBuffer());
8const sizingData = new MagickGeometry(
9params.width,
12sizingData.ignoreAspectRatio = params.height > 0 && params.width > 0;
13return new Promise<File>((resolve) => {
14ImageMagick.read(imageBuffer, (image) => {
15image.resize(sizingData);
16image.write((data) => resolve(new File([data], file.name, { type: file.type })));
17});
18});
thomasResumeHandlermain.tsx4 matches
16<meta name="viewport" content="width=device-width, initial-scale=1.0">
17<title>hello, resume</title>
18<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text y='50%' font-size='24' text-anchor='middle' x='50%' dy='.3em'>📄</text></svg>">
19<style>
20${helloResume}
29}
3031// Add OG image URL and custom title to the config
32const ogImageUrl = 'https://tseeley.com/images/ufoog.JPG'; // Replace with your actual OG image URL
33const customTitle = `resume ✦ thomas seeley`; // You can customize this as needed
3435const resumeHTML = renderResume({
36...config,
37ogImageUrl,
38customTitle
39});