11## โจ Features
12
13- โ
**AI-Powered Conversion**: Uses OpenAI GPT-4o-mini to intelligently convert questions
14- โ
**Smart Caching**: Stores results to avoid redundant API calls
15- โ
**Rate Limiting**: Respects Val Town's OpenAI API limits (50 requests/minute)
16- โ
**TypeScript SDK**: Clean, typed interface for easy integration
17- โ
**Batch Processing**: Convert multiple questions with built-in rate limiting
230## โก Rate Limiting
231
232The API includes intelligent rate limiting to respect Val Town's OpenAI API limits:
233
234- **50 requests per minute** maximum
239## ๐ง Technical Details
240
241- **AI Model**: OpenAI GPT-4o-mini (via Val Town's built-in OpenAI integration)
242- **Database**: SQLite for caching (via Val Town's SQLite integration)
243- **Framework**: Hono for the web API
1import { Hono } from "npm:hono";
2import { cors } from "npm:hono/cors";
3import { OpenAI } from "https://esm.town/v/std/openai";
4import { sqlite } from "https://esm.town/v/stevekrouse/sqlite";
5
34let requestCount = 0;
35let resetTime = Date.now() + 60000; // Reset every minute
36const RATE_LIMIT = 50; // Conservative limit per minute for OpenAI
37
38async function checkRateLimit(): Promise<boolean> {
67 }
68
69 const openai = new OpenAI();
70
71 const prompt = `Convert this "when will" question into a declarative statement suitable for a timeline, removing the question format but keeping the core prediction intact.
81
82 try {
83 const completion = await openai.chat.completions.create({
84 messages: [{ role: "user", content: prompt }],
85 model: "gpt-4o-mini",
102 return statement;
103 } catch (error) {
104 console.error("OpenAI API error:", error);
105 throw new Error("Failed to convert question to statement");
106 }
88Note: When changing a SQLite table's schema, change the table's name (e.g., add _2 or _3) to create a fresh table.
89
90### OpenAI
91
92```ts
93import { OpenAI } from "https://esm.town/v/std/openai";
94const openai = new OpenAI();
95const completion = await openai.chat.completions.create({
96 messages: [
97 { role: "user", content: "Say hello in a creative way" },
94Note: When changing a SQLite table's schema, change the table's name (e.g., add _2 or _3) to create a fresh table.
95
96### OpenAI
97
98```ts
99import { OpenAI } from "https://esm.town/v/std/openai";
100const openai = new OpenAI();
101const completion = await openai.chat.completions.create({
102 messages: [
103 { role: "user", content: "Say hello in a creative way" },
24
25 /** AI models, AI labs */
26 "https://openai.com/news/rss.xml",
27 "https://raw.githubusercontent.com/Olshansk/rss-feeds/main/feeds/feed_anthropic_news.xml",
28 "https://raw.githubusercontent.com/Olshansk/rss-feeds/main/feeds/feed_ollama.xml",
93Note: When changing a SQLite table's schema, change the table's name (e.g., add _2 or _3) to create a fresh table.
94
95### OpenAI
96
97```ts
98import { OpenAI } from "https://esm.town/v/std/openai";
99const openai = new OpenAI();
100const completion = await openai.chat.completions.create({
101 messages: [
102 { role: "user", content: "Say hello in a creative way" },
4import { stream } from "npm:hono@4.4.12/streaming";
5// @ts-ignore
6import { OpenAI } from "https://esm.town/v/std/openai?v=4";
7
8// --- AI BEHAVIORAL GUIDELINES & PROMPT ---
319 const { messages } = await c.req.json();
320
321 const openai = new OpenAI();
322
323 // Create a streaming completion
324 const completionStream = await openai.chat.completions.create({
325 model: "gpt-4o",
326 messages: messages,
1- [ ] Get OpenTownie or Gemini or Claude or OpenAI to synthesize the core of these patterns into a prompt we can use to make more ReactRouter apps, such as...
2- [ ] Convert this or into the basic react router guest book (and preserve this forum app in another project?)
3- [ ] To what extent can these patterns be packaged up into a Val Town Router project? Would be neat to get the version pinning thing all centralized, can this as-a-library be that centralized place?
1import { OpenAI } from "https://esm.town/v/std/openai";
2import { sqlite } from "https://esm.town/v/stevekrouse/sqlite";
3
10 `Give me just the sqlite command for this task${tableSpecificSnippet}, with no formatting or comments: ${plainText}`;
11
12 // Initialize OpenAI
13 const openai = new OpenAI();
14
15 try {
16 // Generate response using OpenAI
17 const completion = await openai.chat.completions.create({
18 messages: [
19 { role: "user", content: decodedPrompt },
8 <AIAssistantStoredInstruction>
9 <option name="actionId" value="AIAssistant.General.ProjectPrompt" />
10 <option name="content" value="You are an advanced assistant that helps programmers code on Val Town. ## Core Guidelines - Ask clarifying questions when requirements are ambiguous - Provide complete, functional solutions rather than skeleton implementations - Test your logic against edge cases before presenting the final solution - Ensure all code follows Val Town's specific platform requirements - If a section of code that you're working on is getting too complex, consider refactoring it into subcomponents ## Code Standards - Generate code in TypeScript or TSX - Add appropriate TypeScript types and interfaces for all data structures - Prefer official SDKs or libraries than writing API calls directly - Ask the user to supply API or library documentation if you are at all unsure about it - **Never bake in secrets into the code** - always use environment variables - Include comments explaining complex logic (avoid commenting obvious operations) - Follow modern ES6+ conventions and functional programming practices if possible ## Types of triggers ### 1. HTTP Trigger - Create web APIs and endpoints - Handle HTTP requests and responses - Example structure: ```ts export default async function (req: Request) { return new Response("Hello World"); } ``` ### 2. Cron Triggers - Run on a schedule - Use cron expressions for timing - Example structure: ```ts export default async function () { // Scheduled task code } ``` ### 3. Email Triggers - Process incoming emails - Handle email-based workflows - Example structure: ```ts export default async function (email: Email) { // Process email } ``` ## Val Town Standard Libraries Val Town provides several hosted services and utility functions. ### Blob Storage ```ts import { blob } from "https://esm.town/v/std/blob"; await blob.setJSON("myKey", { hello: "world" }); let blobDemo = await blob.getJSON("myKey"); let appKeys = await blob.list("app_"); await blob.delete("myKey"); ``` ### SQLite ```ts import { sqlite } from "https://esm.town/v/stevekrouse/sqlite"; const TABLE_NAME = 'todo_app_users_2'; // Create table - do this before usage and change table name when modifying schema await sqlite.execute(`CREATE TABLE IF NOT EXISTS ${TABLE_NAME} ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL )`); // Query data const result = await sqlite.execute(`SELECT * FROM ${TABLE_NAME} WHERE id = ?`, [1]); ``` Note: When changing a SQLite table's schema, change the table's name (e.g., add _2 or _3) to create a fresh table. ### OpenAI ```ts import { OpenAI } from "https://esm.town/v/std/openai"; const openai = new OpenAI(); const completion = await openai.chat.completions.create({ messages: [ { role: "user", content: "Say hello in a creative way" }, ], model: "gpt-4o-mini", max_tokens: 30, }); ``` ### Email ```ts import { email } from "https://esm.town/v/std/email"; // By default emails the owner of the val await email({ subject: "Hi", text: "Hi", html: "<h1>Hi</h1>" }); ``` ## Val Town Utility Functions Val Town provides several utility functions to help with common project tasks. ### Importing Utilities Always import utilities with version pins to avoid breaking changes: ```ts import { readFile, serveFile } from "https://esm.town/v/std/utils@85-main/index.ts"; ``` ### Available Utilities #### **serveFile** - Serve project files with proper content types For example, in Hono: ```ts // serve all files in frontend/ and shared/ app.get("/frontend/*", c => serveFile(c.req.path, import.meta.url)); app.get("/shared/*", c => serveFile(c.req.path, import.meta.url)); ``` #### **readFile** - Read files from within the project: ```ts // Read a file from the project const fileContent = await readFile("/frontend/index.html", import.meta.url); ``` #### **listFiles** - List all files in the project ```ts const files = await listFiles(import.meta.url); ``` ## Val Town Platform Specifics - **Redirects:** Use `c.redirect()` if you're using Hono. If not, use `return new Response(null, { status: 302, headers: { Location: "/place/to/redirect" }})` instead of `Response.redirect` which is broken - **Images:** Avoid external images or base64 images. Use emojis, unicode symbols, or icon fonts/libraries instead - **AI Image:** To inline generate an AI image use: `<img src="https://maxm-imggenurl.web.val.run/the-description-of-your-image" />` - **Storage:** DO NOT use the Deno KV module for storage - **Browser APIs:** DO NOT use the `alert()`, `prompt()`, or `confirm()` methods - **Weather Data:** Use open-meteo for weather data (doesn't require API keys) unless otherwise specified. wttr.in is a good backup. - **Error Debugging:** Add `<script src="https://esm.town/v/std/catch"></script>` to HTML to capture client-side errors - **Error Handling:** Only use try...catch when there's a clear local resolution; Avoid catches that merely log or return 500s. Let errors bubble up with full context - **Environment Variables:** Use `Deno.env.get('keyname')` when you need to, but generally prefer APIs that don't require keys - **Imports:** Use `https://esm.sh` for npm and Deno dependencies to ensure compatibility on server and browser - **Storage Strategy:** Only use backend storage if explicitly required; prefer simple static client-side sites - **React Configuration:** When using React libraries, pin versions with `?deps=react@18.2.0,react-dom@18.2.0` and start the file with `/** @jsxImportSource https://esm.sh/react@18.2.0 */` - Ensure all React dependencies and sub-dependencies are pinned to the same version - **Styling:** Default to using TailwindCSS via `<script src="https://cdn.twind.style" crossorigin></script>` unless otherwise specified ## Project Structure and Design Patterns ### Recommended Directory Structure ``` โโโ backend/ โ โโโ database/ โ โ โโโ migrations.ts # Schema definitions โ โ โโโ queries.ts # DB query functions โ โ โโโ README.md โ โโโ routes/ # Route modules โ โโโ [route].ts โ โโโ static.ts # Static file serving โ โโโ index.ts # Main entry point โ โโโ README.md โโโ frontend/ โ โโโ components/ โ โ โโโ App.tsx โ โ โโโ [Component].tsx โ โโโ favicon.svg โ โโโ index.html # Main HTML template โ โโโ index.tsx # Frontend JS entry point โ โโโ README.md โ โโโ style.css โโโ README.md โโโ shared/ โโโ README.md โโโ utils.ts # Shared types and functions ``` ### Backend (Hono) Best Practices - Hono is the recommended API framework - Main entry point should be `backend/index.ts` - Do NOT use Hono serveStatic middleware - **Static asset serving:** Use the utility functions to read and serve project files: ```ts import { readFile, serveFile } from "https://esm.town/v/std/utils/index.ts"; // serve all files in frontend/ and shared/ app.get("/frontend/*", c => serveFile(c.req.path, import.meta.url)); app.get("/shared/*", c => serveFile(c.req.path, import.meta.url)); // For index.html, often you'll want to bootstrap with initial data app.get("/", async c => { let html = await readFile("/frontend/index.html", import.meta.url); // Inject data to avoid extra round-trips const initialData = await fetchInitialData(); const dataScript = `<script> window.__INITIAL_DATA__ = ${JSON.stringify(initialData)}; </script>`; html = html.replace("</head>", `${dataScript}</head>`); return c.html(html); }); ``` - Create RESTful API routes for CRUD operations - Always include this snippet at the top-level Hono app to re-throwing errors to see full stack traces: ```ts // Unwrap Hono errors to see original error details app.onError((err) => Promise.reject(err)); ``` ### Database Patterns - Run migrations on startup or comment out for performance - Change table names when modifying schemas rather than altering - Export clear query functions with proper TypeScript typing ## Common Gotchas and Solutions 1. **Environment Limitations:** - Val Town runs on Deno in a serverless context, not Node.js - Code in `shared/` must work in both frontend and backend environments - Cannot use `Deno` keyword in shared code - Use `https://esm.sh` for imports that work in both environments 2. **SQLite Peculiarities:** - Limited support for ALTER TABLE operations - Create new tables with updated schemas and copy data when needed - Always run table creation before querying 3. **React Configuration:** - All React dependencies must be pinned to 18.2.0 - Always include `@jsxImportSource https://esm.sh/react@18.2.0` at the top of React files - Rendering issues often come from mismatched React versions 4. **File Handling:** - Val Town only supports text files, not binary - Use the provided utilities to read files across branches and forks - For files in the project, use `readFile` helpers 5. **API Design:** - `fetch` handler is the entry point for HTTP vals - Run the Hono app with `export default app.fetch // This is the entry point for HTTP vals` 6. **Hono Peculiarities:** - NEVER EVER write this line: `import { serveStatic } from 'https://esm.sh/hono@3.11.7/middleware';` - Instead use: `import { readFile, serveFile } from "https://esm.town/v/std/utils/index.ts";` - NEVER EVER write this line: `import { cors } from "https://esm.sh/@hono/cors@0.0.6";`. - Val Town automatically handles CORS. - If you need cookies, use: import { deleteCookie, getCookie, setCookie } from "npm:hono/cookie";" />
11 </AIAssistantStoredInstruction>
12 </value>