stevensDemoindex.html3 matches
10href="/public/favicon.svg"
11sizes="any"
12type="image/svg+xml"
13/>
14<link rel="preconnect" href="https://fonts.googleapis.com" />
36height: 100%;
37font-family: "Pixelify Sans", sans-serif;
38image-rendering: pixelated;
39}
40body::before {
50/* For pixel art aesthetic */
51* {
52image-rendering: pixelated;
53}
54</style>
stevensDemohandleUSPSEmail.ts12 matches
12}
1314type ImageSummary = {
15sender: string;
16recipient: (typeof RECIPIENTS)[number] | "both" | "other";
22anthropic: Anthropic,
23htmlContent: string,
24imageSummaries: ImageSummary[]
25) {
26try {
36text: `Analyze the following content from an email and provide a response as a JSON blob (only JSON, no other text) with two parts.
3738The 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.
3940Your response should include:
66And here is info about the mail pieces:
6768${JSON.stringify(imageSummaries)}`,
69},
70],
95const anthropic = new Anthropic({ apiKey });
9697// Process each image attachment serially
98const summaries = [];
99for (const [index, attachment] of e.attachments.entries()) {
100try {
101const imageData = await attachment.arrayBuffer();
102const base64Image = btoa(
103String.fromCharCode(...new Uint8Array(imageData))
104);
105112content: [
113{
114type: "image",
115source: {
116type: "base64",
117media_type: attachment.type,
118data: base64Image,
119},
120},
148summaries.push(parsedResponse);
149} catch (error) {
150console.error(`Image analysis error:`, error);
151summaries.push({
152sender: "Error",
153recipient: "Error",
154type: "error",
155notes: `Image ${index + 1} Analysis Failed: ${error.message}`,
156});
157}
stevensDemo.cursorrules2 matches
187## Val Town Platform Specifics
188- **Redirects:** Use `return new Response(null, { status: 302, headers: { Location: "/place/to/redirect" }})` instead of `Response.redirect` which is broken
189- **Images:** Avoid external images or base64 images. Use emojis, unicode symbols, or icon fonts/libraries instead
190- For AI-generated images, use: `https://maxm-imggenurl.web.val.run/the-description-of-your-image`
191- **Storage:** DO NOT use the Deno KV module for storage
192- **Browser APIs:** DO NOT use the `alert()`, `prompt()`, or `confirm()` methods
stevensDemoassets.ts6 matches
2// Background
3BACKGROUND:
4"https://imagedelivery.net/iHX6Ovru0O7AjmyT5yZRoA/8b501664-722e-4be8-cf71-83aab7756e00/public",
56// Stevens
7STEVENS_FRONT:
8"https://imagedelivery.net/iHX6Ovru0O7AjmyT5yZRoA/8b8432bb-add2-44ad-bb12-44b8ea215500/public",
9STEVENS_BACK:
10"https://imagedelivery.net/iHX6Ovru0O7AjmyT5yZRoA/e28da8ab-7710-4b82-8e32-8fdf65c2ed00/public",
11STEVENS_WALKING:
12"https://imagedelivery.net/iHX6Ovru0O7AjmyT5yZRoA/bd7b9997-09b2-4b35-6eb9-9975a85bb700/public",
1314// Mailman
15MAILMAN_STANDING:
16"https://imagedelivery.net/iHX6Ovru0O7AjmyT5yZRoA/20a6493d-cc31-475e-aa83-ac97d317e400/public",
17MAILMAN_WALKING:
18"https://imagedelivery.net/iHX6Ovru0O7AjmyT5yZRoA/61604576-8a83-4d85-d5e4-8e8e26641700/public",
19};
20
stevensDemoApp.tsx25 matches
82const [cookieAndTeaMode, setCookieAndTeaMode] = useState(false);
8384// Fetch images from backend instead of blob storage directly
85useEffect(() => {
86// Set default background color in case image doesn't load
87if (document.body) {
88document.body.style.backgroundColor = "#2D1700"; // Dark brown leather color
89}
9091// Fetch avatar image
92fetch("/api/images/stevens.jpg")
93.then((response) => {
94if (response.ok) return response.blob();
95throw new Error("Failed to load avatar image");
96})
97.then((imageBlob) => {
98const url = URL.createObjectURL(imageBlob);
99setAvatarUrl(url);
100})
104105// Fetch wood background
106fetch("/api/images/wood.jpg")
107.then((response) => {
108if (response.ok) return response.blob();
109throw new Error("Failed to load wood background");
110})
111.then((imageBlob) => {
112const url = URL.createObjectURL(imageBlob);
113setWoodUrl(url);
114115// Apply wood background to body
116if (document.body) {
117document.body.style.backgroundImage = `url(${url})`;
118}
119})
362return {
363position: SCENE_ELEMENTS.DESK_SITTING,
364image: ASSETS.STEVENS_FRONT,
365highlightElement: SCENE_ELEMENTS.DESK,
366animationClass: "no-animation",
372return {
373position: SCENE_ELEMENTS.DESK_SITTING,
374image: ASSETS.STEVENS_FRONT,
375highlightElement: null,
376animationClass: "no-animation",
387y: SCENE_ELEMENTS.MAILBOX.y - 20,
388},
389image: ASSETS.STEVENS_BACK,
390highlightElement: SCENE_ELEMENTS.MAILBOX,
391animationClass: "walk-to-mailbox",
398y: SCENE_ELEMENTS.CALENDAR.y + 30,
399},
400image: ASSETS.STEVENS_BACK,
401highlightElement: SCENE_ELEMENTS.CALENDAR,
402animationClass: "walk-to-calendar",
409y: SCENE_ELEMENTS.TELEGRAM.y + 10,
410},
411image: ASSETS.STEVENS_BACK,
412highlightElement: SCENE_ELEMENTS.TELEGRAM,
413animationClass: "walk-to-telegram",
417return {
418position: SCENE_ELEMENTS.OUTSIDE,
419image: ASSETS.STEVENS_FRONT,
420highlightElement: null,
421animationClass: "walk-to-outside",
425return {
426position: SCENE_ELEMENTS.DESK_SITTING,
427image: ASSETS.STEVENS_FRONT,
428highlightElement: SCENE_ELEMENTS.DESK,
429animationClass: "walk-to-desk",
623box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.4),
6240 3px 8px rgba(0, 0, 0, 0.5);
625image-rendering: pixelated;
626cursor: pointer;
627transition: transform 0.2s;
634.notebook-pages {
635background-color: #f8f1e0;
636background-image: linear-gradient(#d6c6a5 1px, transparent 1px);
637background-size: 100% 16px;
638box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.3);
639image-rendering: pixelated;
640}
641652653.pixel-character {
654image-rendering: pixelated;
655position: absolute;
656transition: left 0.7s ease-in-out, top 0.7s ease-in-out;
763className="w-[512px] h-[512px] mx-auto relative"
764style={{
765backgroundImage: `url(${ASSETS.BACKGROUND})`,
766backgroundSize: "cover",
767backgroundPosition: "center",
768imageRendering: "pixelated",
769}}
770>
799{/* Stevens character */}
800<img
801src={stevensState.image}
802alt="Stevens"
803className={`pixel-character ${
hono_html_Appsindex.html73 matches
100<!-- Controls on the right -->
101<div id="controlsContainer" class="w-[300px] min-w-[300px] p-5 overflow-y-auto border-l-4 border-accent bg-base-100 text-base-content flex flex-col gap-5">
102<div id="pdfImageControls" style="display: none;" class="flex flex-col gap-4">
103<h4 class="font-bold uppercase mb-2">Image Controls:</h4>
104<button
105class="btn btn-success w-full"
106id="downloadZipButton"
107onclick="downloadAllPdfImagesAsZip()"
108>
109Download All Images as ZIP
110</button>
111112<!-- Image Buttons -->
113<div id="imageButtonsContainer" class="flex flex-col gap-3">
114<button class="btn btn-info w-full" onclick="copyAllPdfImagesToClipboard()">
115Copy All Images to Clipboard
116</button>
117<button class="btn btn-warning w-full" onclick="downloadAllPdfImagesAsZip()">
118Download All Images
119</button>
120</div>
124<!-- Main Content -->
125<div id="mainContentContainer" class="flex-1 p-5 overflow-y-auto bg-base-100 text-base-content flex flex-wrap gap-8 items-start">
126<div id="pdfImagesContainer">
127<!-- Images will be inserted here -->
128</div>
129</div>
148<script src="https://cdn.jsdelivr.net/npm/tesseract.js@4.0.3/dist/tesseract.min.js"></script>
149150<!-- JSZip library for zipping images -->
151<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
152340const totalPages = loadedPdf.numPages
341342// Clear any previously displayed images
343const pdfImagesContainer = document.getElementById(
344'pdfImagesContainer',
345)
346pdfImagesContainer.innerHTML = ''
347348// Retrieve the current or selected week number
377378// Convert the canvas to a JPG data URL
379const jpgDataUrl = tempCanvas.toDataURL('image/jpeg', 1.0)
380381// Perform OCR on the image using Tesseract.js
382const ocrResult = await Tesseract.recognize(tempCanvas, 'eng', {
383logger: (message) => console.log(message),
397)
398399// Build the image container
400const singleImageContainer = document.createElement('div')
401singleImageContainer.className = 'singleImageContainer card bg-base-200 shadow-md p-4 flex flex-col items-center w-64 min-w-56 max-w-xs mb-5'
402403// Add the rendered image
404const renderedImg = document.createElement('img')
405renderedImg.src = jpgDataUrl
407renderedImg.className = 'max-w-full h-auto object-contain rounded-box border border-base-300 bg-base-100'
408409// Create a container for the image buttons
410const singleImageButtonsDiv = document.createElement('div')
411singleImageButtonsDiv.className = 'singleImageButtons flex flex-wrap gap-2 mt-3 justify-center'
412413// Copy Image Button
414const copyImageButton = document.createElement('button')
415copyImageButton.className = 'btn btn-info'
416copyImageButton.textContent = 'Copy'
417copyImageButton.onclick = () =>
418copySingleImageToClipboard(renderedImg)
419420// Download Image Button
421const downloadImageButton = document.createElement('button')
422downloadImageButton.className = 'btn btn-warning'
423downloadImageButton.textContent = 'Download'
424// Construct filename based on name (if found) or page number
425let fileNameToUse = `week_${finalWeekNumber}-page_${pageIndex}.jpg`
427fileNameToUse = `week_${finalWeekNumber}-${extractedPersonName}.jpg`
428}
429downloadImageButton.setAttribute('data-filename', fileNameToUse)
430downloadImageButton.onclick = () =>
431downloadSingleImage(jpgDataUrl, fileNameToUse)
432433singleImageButtonsDiv.appendChild(copyImageButton)
434singleImageButtonsDiv.appendChild(downloadImageButton)
435436// Optional button: Copy Pay Amount
441copyPayAmountButton.onclick = () =>
442copyTextToClipboard(extractedPayAmount)
443singleImageButtonsDiv.appendChild(copyPayAmountButton)
444}
445446// Append everything to the container
447singleImageContainer.appendChild(renderedImg)
448singleImageContainer.appendChild(singleImageButtonsDiv)
449pdfImagesContainer.appendChild(singleImageContainer)
450}
451452// Show image controls
453document.getElementById('pdfImageControls').style.display = 'block'
454} catch (error) {
455console.error('Error loading PDF:', error)
511}
512513function copySingleImageToClipboard(imageElement) {
514fetch(imageElement.src)
515.then((response) => response.blob())
516.then((blob) => {
518navigator.clipboard.write([clipboardItem]).then(
519() => {
520alert('Image copied to clipboard!')
521},
522(error) => {
523alert('Failed to copy image: ' + error)
524},
525)
527}
528529function downloadSingleImage(dataUrl, fileName) {
530const hiddenLink = document.createElement('a')
531hiddenLink.href = dataUrl
536}
537538async function copyAllPdfImagesToClipboard() {
539const allImages = document.querySelectorAll('#pdfImagesContainer img')
540if (allImages.length === 0) {
541alert('No images to copy.')
542return
543}
544545try {
546for (let imageElem of allImages) {
547const imageResponse = await fetch(imageElem.src)
548const imageBlob = await imageResponse.blob()
549const clipboardItem = new ClipboardItem({
550[imageBlob.type]: imageBlob,
551})
552await navigator.clipboard.write([clipboardItem])
553}
554alert('All images copied to clipboard!')
555} catch (error) {
556console.error('Error copying images:', error)
557alert(
558'Failed to copy all images. Your browser may not support this feature.',
559)
560}
561}
562563async function downloadAllPdfImagesAsZip() {
564const allImages = document.querySelectorAll('#pdfImagesContainer img')
565if (allImages.length === 0) {
566alert('No images to download.')
567return
568}
576document.getElementById('currentWeekNumberText').textContent
577578for (let [index, imgElement] of allImages.entries()) {
579const imageResponse = await fetch(imgElement.src)
580const imageBlob = await imageResponse.blob()
581const imageBuffer = await imageBlob.arrayBuffer()
582583// Retrieve the filename from the corresponding download button
589`week-${finalWeekNumber}-page_${index + 1}.jpg`
590591zipArchive.file(fileName, imageBuffer)
592}
593594zipArchive.generateAsync({ type: 'blob' }).then((zipFileBlob) => {
595saveAs(zipFileBlob, `week-${finalWeekNumber}-images.zip`)
596})
597}
hono_html_Appsindex.html80 matches
199}
200201#imageButtonsContainer {
202margin-top: 20px;
203}
204205#imageButtonsContainer .btn {
206width: 48%;
207display: inline-block;
209}
210211#imageButtonsContainer .btn:last-child {
212margin-right: 0;
213}
214215.singleImageContainer {
216margin-bottom: 20px;
217text-align: center;
221}
222223.singleImageContainer img {
224max-width: 100%;
225height: auto;
229}
230231.singleImageButtons {
232margin-top: 10px;
233}
234235.singleImageButtons button {
236margin: 5px;
237font-size: 0.9em;
337<!-- Controls on the right -->
338<div id="controlsContainer">
339<div id="pdfImageControls" style="display: none;">
340<h4>Image Controls:</h4>
341<button
342class="btn btn-success mt-2"
343id="downloadZipButton"
344onclick="downloadAllPdfImagesAsZip()"
345>
346Download All Images as ZIP
347</button>
348349<!-- Image Buttons -->
350<div id="imageButtonsContainer">
351<button class="btn btn-info" onclick="copyAllPdfImagesToClipboard()">
352Copy All Images to Clipboard
353</button>
354<button class="btn btn-warning" onclick="downloadAllPdfImagesAsZip()">
355Download All Images
356</button>
357</div>
361<!-- Main Content -->
362<div id="mainContentContainer">
363<div id="pdfImagesContainer">
364<!-- Images will be inserted here -->
365</div>
366</div>
387<script src="https://cdn.jsdelivr.net/npm/tesseract.js@4.0.3/dist/tesseract.min.js"></script>
388389<!-- JSZip library for zipping images -->
390<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
391579const totalPages = loadedPdf.numPages
580581// Clear any previously displayed images
582const pdfImagesContainer = document.getElementById(
583'pdfImagesContainer',
584)
585pdfImagesContainer.innerHTML = ''
586587// Retrieve the current or selected week number
616617// Convert the canvas to a JPG data URL
618const jpgDataUrl = tempCanvas.toDataURL('image/jpeg', 1.0)
619620// Perform OCR on the image using Tesseract.js
621const ocrResult = await Tesseract.recognize(tempCanvas, 'eng', {
622logger: (message) => console.log(message),
636)
637638// Build the image container
639const singleImageContainer = document.createElement('div')
640singleImageContainer.className = 'singleImageContainer'
641642// Add the rendered image
643const renderedImg = document.createElement('img')
644renderedImg.src = jpgDataUrl
645renderedImg.alt = `Page ${pageIndex}`
646647// Create a container for the image buttons
648const singleImageButtonsDiv = document.createElement('div')
649singleImageButtonsDiv.className = 'singleImageButtons'
650651// Copy Image Button
652const copyImageButton = document.createElement('button')
653copyImageButton.className = 'btn btn-info'
654copyImageButton.textContent = 'Copy'
655copyImageButton.onclick = () =>
656copySingleImageToClipboard(renderedImg)
657658// Download Image Button
659const downloadImageButton = document.createElement('button')
660downloadImageButton.className = 'btn btn-warning'
661downloadImageButton.textContent = 'Download'
662// Construct filename based on name (if found) or page number
663let fileNameToUse = `week_${finalWeekNumber}-page_${pageIndex}.jpg`
665fileNameToUse = `week_${finalWeekNumber}-${extractedPersonName}.jpg`
666}
667downloadImageButton.setAttribute('data-filename', fileNameToUse)
668downloadImageButton.onclick = () =>
669downloadSingleImage(jpgDataUrl, fileNameToUse)
670671singleImageButtonsDiv.appendChild(copyImageButton)
672singleImageButtonsDiv.appendChild(downloadImageButton)
673674// Optional button: Copy Pay Amount
679copyPayAmountButton.onclick = () =>
680copyTextToClipboard(extractedPayAmount)
681singleImageButtonsDiv.appendChild(copyPayAmountButton)
682}
683684// Append everything to the container
685singleImageContainer.appendChild(renderedImg)
686singleImageContainer.appendChild(singleImageButtonsDiv)
687pdfImagesContainer.appendChild(singleImageContainer)
688}
689690// Show image controls
691document.getElementById('pdfImageControls').style.display = 'block'
692} catch (error) {
693console.error('Error loading PDF:', error)
749}
750751function copySingleImageToClipboard(imageElement) {
752fetch(imageElement.src)
753.then((response) => response.blob())
754.then((blob) => {
756navigator.clipboard.write([clipboardItem]).then(
757() => {
758alert('Image copied to clipboard!')
759},
760(error) => {
761alert('Failed to copy image: ' + error)
762},
763)
765}
766767function downloadSingleImage(dataUrl, fileName) {
768const hiddenLink = document.createElement('a')
769hiddenLink.href = dataUrl
774}
775776async function copyAllPdfImagesToClipboard() {
777const allImages = document.querySelectorAll('#pdfImagesContainer img')
778if (allImages.length === 0) {
779alert('No images to copy.')
780return
781}
782783try {
784for (let imageElem of allImages) {
785const imageResponse = await fetch(imageElem.src)
786const imageBlob = await imageResponse.blob()
787const clipboardItem = new ClipboardItem({
788[imageBlob.type]: imageBlob,
789})
790await navigator.clipboard.write([clipboardItem])
791}
792alert('All images copied to clipboard!')
793} catch (error) {
794console.error('Error copying images:', error)
795alert(
796'Failed to copy all images. Your browser may not support this feature.',
797)
798}
799}
800801async function downloadAllPdfImagesAsZip() {
802const allImages = document.querySelectorAll('#pdfImagesContainer img')
803if (allImages.length === 0) {
804alert('No images to download.')
805return
806}
814document.getElementById('currentWeekNumberText').textContent
815816for (let [index, imgElement] of allImages.entries()) {
817const imageResponse = await fetch(imgElement.src)
818const imageBlob = await imageResponse.blob()
819const imageBuffer = await imageBlob.arrayBuffer()
820821// Retrieve the filename from the corresponding download button
827`week-${finalWeekNumber}-page_${index + 1}.jpg`
828829zipArchive.file(fileName, imageBuffer)
830}
831832zipArchive.generateAsync({ type: 'blob' }).then((zipFileBlob) => {
833saveAs(zipFileBlob, `week-${finalWeekNumber}-images.zip`)
834})
835}
stevensDemoREADME.md1 match
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:
45
67For 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.
stevensDemoREADME.md1 match
21## `favicon.svg`
2223As 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/).
2425## `components/`
stevensDemoindex.ts15 matches
73});
7475// --- Blob Image Serving Routes ---
7677// GET /api/images/:filename - Serve images from blob storage
78app.get("/api/images/:filename", async (c) => {
79const filename = c.req.param("filename");
8081try {
82// Get image data from blob storage
83const imageData = await blob.get(filename);
8485if (!imageData) {
86return c.json({ error: "Image not found" }, 404);
87}
8890let contentType = "application/octet-stream"; // Default
91if (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) {
92contentType = "image/jpeg";
93} else if (filename.endsWith(".png")) {
94contentType = "image/png";
95} else if (filename.endsWith(".gif")) {
96contentType = "image/gif";
97} else if (filename.endsWith(".svg")) {
98contentType = "image/svg+xml";
99}
100101// Return the image with appropriate headers
102return new Response(imageData, {
103headers: {
104"Content-Type": contentType,
107});
108} catch (error) {
109console.error(`Error serving image ${filename}:`, error);
110return c.json(
111{ error: "Failed to load image", details: error.message },
112500,
113);