79 "slug": "fal",
80 "link": "/blog/fal",
81 "description": "Bringing lightning fast AI image generation to Val Town",
82 "pubDate": "Thu, 31 Oct 2024 00:00:00 GMT",
83 "author": "Steve Krouse",
13 {
14 headers: {
15 "Content-Type": "image/svg+xml",
16 },
17 },
40- Item 3
41
42### Images
43
44Images can be included using markdown syntax.
45
46## Conclusion
6---
7
8
9
10We migrated our blog to Val Town 🥳
79## A fast migration
80
81How did we migrate 65 blog posts – with images, videos, and intricate formatting and styles – to an entirely new blog in a single day?
82
83We didn't. We left them where they are, and proxy to them.
9## Example
10This val tracks mentions of "Val Town" and related terms, excluding noise like retweets and irrelevant accounts. Notifications are sent to a Discord webhook but can be easily reconfigured for other platforms.
11<img src="https://imagedelivery.net/iHX6Ovru0O7AjmyT5yZRoA/85912106-f625-443e-5321-6e2699453200/public" width="500"/>
12To see exactly how we use this template at Val Town: https://www.val.town/x/stevekrouse/twitterAlert
13
16### 1. Fork this Val
17To use this template, fork this val on the top right corner of this page.
18
19
20### 2. View Source Code
21<em>The `CODE` box shows you the the full source code of this val, you may need to scroll down to see it.</em>
22
23
24### 3. Customize Query
3It's common to have code and types that are needed on both the frontend and the backend. It's important that you write this code in a particularly defensive way because it's limited by what both environments support:
4
5
6
7For example, you *cannot* use the `Deno` keyword. For imports, you can't use `npm:` specifiers, so we reccomend `https://esm.sh` because it works on the server & client. You *can* use TypeScript because that is transpiled in `/backend/index.ts` for the frontend. Most code that works on the frontend tends to work in Deno, because Deno is designed to support "web-standards", but there are definitely edge cases to look out for.
21## `favicon.svg`
22
23As of this writing Val Town only supports text files, which is why the favicon is an SVG and not an .ico or any other binary image format. If you need binary file storage, check out [Blob Storage](https://docs.val.town/std/blob/).
24
25## `components/`
73});
74
75// --- Blob Image Serving Routes ---
76
77// GET /api/images/:filename - Serve images from blob storage
78app.get("/api/images/:filename", async (c) => {
79 const filename = c.req.param("filename");
80
81 try {
82 // Get image data from blob storage
83 const imageData = await blob.get(filename);
84
85 if (!imageData) {
86 return c.json({ error: "Image not found" }, 404);
87 }
88
90 let contentType = "application/octet-stream"; // Default
91 if (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) {
92 contentType = "image/jpeg";
93 } else if (filename.endsWith(".png")) {
94 contentType = "image/png";
95 } else if (filename.endsWith(".gif")) {
96 contentType = "image/gif";
97 } else if (filename.endsWith(".svg")) {
98 contentType = "image/svg+xml";
99 }
100
101 // Return the image with appropriate headers
102 return new Response(imageData, {
103 headers: {
104 "Content-Type": contentType,
107 });
108 } catch (error) {
109 console.error(`Error serving image ${filename}:`, error);
110 return c.json(
111 { error: "Failed to load image", details: error.message },
112 500,
113 );
10 href="/public/favicon.svg"
11 sizes="any"
12 type="image/svg+xml"
13 />
14 <link rel="preconnect" href="https://fonts.googleapis.com" />
36 height: 100%;
37 font-family: "Pixelify Sans", sans-serif;
38 image-rendering: pixelated;
39 }
40 body::before {
50 /* For pixel art aesthetic */
51 * {
52 image-rendering: pixelated;
53 }
54 </style>
12}
13
14type ImageSummary = {
15 sender: string;
16 recipient: (typeof RECIPIENTS)[number] | "both" | "other";
22 anthropic: Anthropic,
23 htmlContent: string,
24 imageSummaries: ImageSummary[]
25) {
26 try {
36 text: `Analyze the following content from an email and provide a response as a JSON blob (only JSON, no other text) with two parts.
37
38 The email is from the USPS showing mail I'm receiving. Metadata about packages is stored directly in the email. Info about mail pieces is in images, so I've included summaries of those as well.
39
40 Your response should include:
66 And here is info about the mail pieces:
67
68 ${JSON.stringify(imageSummaries)}`,
69 },
70 ],
95 const anthropic = new Anthropic({ apiKey });
96
97 // Process each image attachment serially
98 const summaries = [];
99 for (const [index, attachment] of e.attachments.entries()) {
100 try {
101 const imageData = await attachment.arrayBuffer();
102 const base64Image = btoa(
103 String.fromCharCode(...new Uint8Array(imageData))
104 );
105
112 content: [
113 {
114 type: "image",
115 source: {
116 type: "base64",
117 media_type: attachment.type,
118 data: base64Image,
119 },
120 },
148 summaries.push(parsedResponse);
149 } catch (error) {
150 console.error(`Image analysis error:`, error);
151 summaries.push({
152 sender: "Error",
153 recipient: "Error",
154 type: "error",
155 notes: `Image ${index + 1} Analysis Failed: ${error.message}`,
156 });
157 }