1/** @jsxImportSource https://esm.sh/react@18.2.0?dev */
2import React, { useEffect, useRef, useState } from "https://esm.sh/react@18.2.0?dev";
3import { ImageUpload, processFiles } from "./ImageUpload.tsx";
4
5interface ChatInputProps {
8 handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
9 running: boolean;
10 images: (string | null)[];
11 setImages: React.Dispatch<React.SetStateAction<(string | null)[]>>;
12 isDragging: boolean;
13}
18 handleSubmit,
19 running,
20 images,
21 setImages,
22 isDragging,
23}: ChatInputProps) {
43 const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
44 e.preventDefault();
45 const validImages = images.filter((img): img is string => typeof img === "string");
46 console.log("Submitting with images:", validImages);
47 if (input.trim() || validImages.length > 0) {
48 handleSubmit(e);
49 setImages([]);
50 }
51 };
52
53 const handleProcessFiles = (files: File[]) => {
54 processFiles(files, images, setImages);
55 };
56
61 disabled={running}
62 >
63 {images.length > 0 && (
64 <ImageUpload
65 images={images}
66 setImages={setImages}
67 processFiles={handleProcessFiles}
68 />
82 if (e.key === "Enter" && !e.shiftKey && !isMobile) {
83 e.preventDefault();
84 if (input.trim() || images.filter(Boolean).length > 0) {
85 handleFormSubmit(e as any);
86 }
103 }
104 }}
105 accept="image/*"
106 multiple
107 className="hidden"
139 </div>
140
141 {/* Attach images button below textarea */}
142 {!running && (
143 <div className="flex justify-start mt-1 mb-2">
150 }
151 }}
152 title="Attach images"
153 >
154 <span>📎</span> Attach images
155 </button>
156 </div>
162 <div className="bg-white p-6 rounded-lg shadow-lg text-center">
163 <div className="text-2xl mb-2">📁</div>
164 <div className="text-xl font-semibold">Drop images here</div>
165 </div>
166 </div>
2import { type Message } from "https://esm.sh/@ai-sdk/react?dev&deps=react@18.2.0&react-dom@18.2.0";
3import ReactMarkdown from "https://esm.sh/react-markdown?dev&deps=react@18.2.0&react-dom@18.2.0";
4import { ImagePreview } from "./ImageUpload.tsx";
5
6// Helper function to detect language from file path
280 );
281 }
282 if (part.type === "image") {
283 // Handle both formats: {image: {url: string}} and {image: string}
284 const imageUrl = typeof part.image === "string"
285 ? part.image
286 : part.image.url || (part.image as any).source?.url;
287
288 return (
289 <div className="mt-2">
290 <img
291 src={imageUrl}
292 alt="Uploaded image"
293 className="max-h-64 max-w-full object-contain rounded"
294 />
296 );
297 }
298 // Handle multiple images in a single part
299 if (part.type === "images") {
300 return <ImagePreview images={part.images.map(img => img.url)} />;
301 }
302}
3 <head>
4 <meta charset="UTF-8" />
5 <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7 <title>Vite + React + TS</title>
69 name: `${projectDetails.name}_${Date.now().toString()}`,
70 description: projectDetails.description ?? undefined,
71 imageUrl: projectDetails.imageUrl ?? undefined,
72 });
73 console.log("New project created:", newProject);
6 <title>React Hono Val Town Starter</title>
7 <script src="https://cdn.tailwindcss.com"></script>
8 <link rel="icon" href="/public/favicon.svg" sizes="any" type="image/svg+xml">
9 </head>
10 <body>
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/`
6 <title>React Hono Val Town Starter</title>
7 <link rel="stylesheet" href="/public/style.css">
8 <link rel="icon" href="/public/favicon.svg" sizes="any" type="image/svg+xml">
9 </head>
10 <body>
23 "prompt": "two column interactive markdown editor with live preview and default text to explain markdown features",
24 "title": "Markdown Editor",
25 "code": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Markdown Editor</title>\n <link href=\"https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css\" rel=\"stylesheet\">\n</head>\n<body class=\"bg-white\">\n <div class=\"max-w-full mx-auto p-4 pt-6 md:p-6 lg:p-8\">\n <h1 class=\"text-3xl text-center mb-4\">Markdown Editor</h1>\n <div class=\"flex flex-row\">\n <div class=\"editor p-4 rounded-lg border border-gray-200 w-full md:w-1/2\">\n <textarea id=\"editor\" class=\"w-full h-screen p-2 border border-gray-200 rounded-lg\" placeholder=\"Type your Markdown here...\"></textarea>\n </div>\n <div class=\"preview p-4 rounded-lg border border-gray-200 w-full md:w-1/2 ml-2 md:ml-4 lg:ml-8\">\n <div id=\"preview\"></div>\n </div>\n </div>\n <p class=\"text-center mt-4\">Built on <a href=\"https://cerebrascoder.com\">Cerebras Coder</a></p>\n </div>\n\n <script>\n const editor = document.getElementById('editor');\n const preview = document.getElementById('preview');\n\n // Initialize textarea with default markdown\n const defaultMarkdown = `\n# Introduction to Markdown\nMarkdown is a lightweight markup language that is easy to read and write. It is often used for formatting text in plain text editors, chat applications, and even web pages.\n\n## Headers\nHeaders are denoted by the # symbol followed by a space. The number of # symbols determines the level of the header:\n# Heading 1\n## Heading 2\n### Heading 3\n\n## Emphasis\nYou can use emphasis to make your text **bold** or *italic*:\n*Italics*\n**Bold**\n\n## Lists\nYou can use lists to organize your text:\n* Item 1\n* Item 2\n* Item 3\nOr\n1. Item 1\n2. Item 2\n3. Item 3\n\n## Links\nYou can use links to reference external resources:\n[Google](https://www.google.com)\n\n## Images\nYou can use images to add visual content:\n\n`;\n editor.value = defaultMarkdown;\n\n // Update preview on input\n editor.addEventListener('input', () => {\n const markdown = editor.value;\n const html = markdownToHtml(markdown);\n preview.innerHTML = html;\n });\n\n // Initialize preview with default markdown\n const defaultHtml = markdownToHtml(defaultMarkdown);\n preview.innerHTML = defaultHtml;\n\n // Function to convert Markdown to HTML\n function markdownToHtml(markdown) {\n // Bold\n markdown = markdown.replace(/\\*\\*(.*?)\\*\\*/g, '<b>$1</b>');\n\n // Italic\n markdown = markdown.replace(/\\*(.*?)\\*/g, '<i>$1</i>');\n\n // Links\n markdown = markdown.replace(/\\[(.*?)\\]\\((.*?)\\)/g, '<a href=\"$2\">$1</a>');\n\n // Images\n markdown = markdown.replace(/!\\[(.*?)\\]\\((.*?)\\)/g, '<img src=\"$2\" alt=\"$1\">');\n\n // Headings\n markdown = markdown.replace(/(^#{1,6} )(.*)/gm, (match, level, text) => {\n return `<h${level.length}>${text}</h${level.length}>`;\n });\n\n // Lists\n markdown = markdown.replace(/^(\\*|\\d+\\.) (.*)/gm, (match, marker, text) => {\n if (marker.startsWith('*')) {\n return `<li>${text}</li>`;\n } else {\n return `<li>${text}</li>`;\n }\n });\n\n // Line breaks\n markdown = markdown.replace(/\\n/g, '<br>');\n\n // Fix for nested lists\n markdown = markdown.replace(/<li><li>/g, '<li>');\n markdown = markdown.replace(/<\\/li><\\/li>/g, '</li>');\n\n // Wrap lists in ul\n markdown = markdown.replace(/(<li>.*<\\/li>)/g, '<ul>$1</ul>');\n\n return markdown;\n }\n </script>\n</body>\n</html>",
26 "performance": {
27 "tokensPerSecond": 4092.96,
21 <meta property="og:description" content="Turn your ideas into fully functional apps in less than a second – powered by Llama3.3-70b on Cerebras's super-fast wafer chips. Code is 100% open-source, hosted on Val Town."">
22 <meta property="og:type" content="website">
23 <meta property="og:image" content="https://stevekrouse-blob_admin.web.val.run/api/public/CerebrasCoderOG.jpg">
24
25