12 total_price: number;
13 inference_price: number;
14 total_images: number;
15 used_inference_data?: boolean;
16}
44 <th>Total Price</th>
45 <th>Inference Price</th>
46 <th>Images</th>
47 </tr>
48 </thead>
59 <td class="price">${formatPrice(row.total_price)} ${row.used_inference_data ? '<span class="badge badge-info" title="Using inference data">I</span>' : ''}</td>
60 <td class="price">${formatPrice(row.inference_price || 0)}</td>
61 <td>${formatNumber(row.total_images)}</td>
62 </tr>
63 `).join("")}
178
179- **Redirects:** Use `return new Response(null, { status: 302, headers: { Location: "/place/to/redirect" }})` instead of `Response.redirect` which is broken
180- **Images:** Avoid external images or base64 images. Use emojis, unicode symbols, or icon fonts/libraries instead
181- **AI Image:** To inline generate an AI image use: `<img src="https://maxm-imggenurl.web.val.run/the-description-of-your-image" />`
182- **Storage:** DO NOT use the Deno KV module for storage
183- **Browser APIs:** DO NOT use the `alert()`, `prompt()`, or `confirm()` methods
10import { useCreditBalance } from "../hooks/useCreditBalance.tsx";
11import { Messages } from "./Messages.tsx";
12import { InputBox, ImageDropContainer } from "./InputBox.tsx";
13import { PreviewFrame } from "./PreviewFrame.tsx";
14import { BranchSelect } from "./BranchSelect.tsx";
68 refetch: () => void;
69}) {
70 const [images, setImages] = useState<(string|null)[]>([]);
71 const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
72 const { audio, user } = useContext(AppContext);
88 branchId,
89 selectedFiles,
90 images,
91 soundEnabled: audio,
92 });
137
138 return (
139 <ImageDropContainer
140 running={running}
141 images={images}
142 setImages={setImages}>
143 <div className="single-column-container">
144 <div className="single-sticky-header">
148 rel="norefferer"
149 className="block-link text-link lockup">
150 {project.imageUrl ? (
151 <img src={project.imageUrl} className="image-thumbnail" />
152 ) : user?.profileImageUrl ? (
153 <img
154 src={user.profileImageUrl}
155 className="avatar"
156 alt={user.username}
159 />
160 ) : (
161 <div className="image-placeholder" />
162 )}
163 <div>{project.name}</div>
211 onSubmit={e => {
212 handleSubmit(e);
213 setImages([]);
214 }}
215 onCancel={handleStop}
216 running={running}
217 error={error}
218 images={images}
219 setImages={setImages}
220 />
221 )}
223 </div>
224 </div>
225 </ImageDropContainer>
226 );
227}
11
12 <!-- reference the webpage's favicon. note: currently only svg is supported in val town files -->
13 <link rel="icon" href="/favicon.svg" sizes="any" type="image/svg+xml">
14
15 <!-- import the webpage's javascript file -->
11
12 <!-- reference the webpage's favicon. note: currently only svg is supported in val town files -->
13 <link rel="icon" href="/favicon.svg" sizes="any" type="image/svg+xml">
14
15 <!-- import the webpage's javascript file -->
11
12 <!-- reference the webpage's favicon. note: currently only svg is supported in val town files -->
13 <link rel="icon" href="/favicon.svg" sizes="any" type="image/svg+xml">
14
15 <!-- import the webpage's javascript file -->
29 }
30
31 const { messages, project, branchId, anthropicApiKey, selectedFiles, images } = await c.req.json();
32
33 // do we want to allow user-provided tokens still
55 branch_id: branchId,
56 val_id: project.id,
57 num_images: images?.length || 0,
58 model,
59 });
87 townie_usage_id: rowid,
88 townie_our_api_token: our_api_token,
89 townie_num_images: images?.length || 0,
90 townie_selected_files_count: selectedFiles?.length || 0,
91 },
105 let coreMessages = convertToCoreMessages(messages);
106
107 // If there are images, we need to add them to the last user message
108 if (images && Array.isArray(images) && images.length > 0) {
109 // Find the last user message
110 const lastUserMessageIndex = coreMessages.findIndex(
128 };
129
130 // Add each image to the content array using the correct ImagePart format
131 for (const image of images) {
132 if (image && image.url) {
133 // Extract mime type from data URL if available
134 let mimeType = undefined;
135 if (image.url.startsWith("data:")) {
136 const matches = image.url.match(/^data:([^;]+);/);
137 if (matches && matches.length > 1) {
138 mimeType = matches[1];
141
142 newUserMessage.content.push({
143 type: "image",
144 image: image.url,
145 mimeType,
146 });
3const ai = new GoogleGenAI({ apiKey: "AIzaSyDcuswpF8sAUBVTvrUlnpADXF4gHjTTAxA" });
4
5const _prompt = `Parse the image and extract the question and options`;
6
7export const questionPrompt = async (image: string, mimeType = "image/jpeg", prompt = _prompt) =>
8 await ai.models.generateContent({
9 model: "gemini-2.0-flash",
11 {
12 inlineData: {
13 data: image,
14 mimeType,
15 },
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/`