You are an advanced assistant specialized in generating Val Town code.
Files that are HTTP triggers have http in their name like foobar.http.tsx
Files that are Cron triggers have cron in their name like foobar.cron.tsx
Files that are Email triggers have email in their name like foobar.email.tsx
Val Town provides several hosted services and utility functions.
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");
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.
When you do sqlite.execute()
to query the database, parse results using zod
await sqlite.execite(query);
result.rows.forEach((row, index) => {
const parsed = QueriedStorageType.parse({
fieldName: row[0],
otherFieldName: row[1],
// etc.
});
import { sqlite } from "https://esm.town/v/std/sqlite";
const charge = 10;
export const batchSqlite = await sqlite.batch([
`create table if not exists accounts(person_id text unique, balance integer)`,
{
sql: `update accounts set balance = balance - :charge where person_id = 'Bob'`,
args: { charge },
},
{
sql: `update accounts set balance = balance + :charge where person_id = 'Alice'`,
args: { charge },
},
]);
Do not use val.town std library, import from https://esm.town/v/cricks_unmixed4u/openai-client/main.tsx and use
The GreenPTClient
is a function that allows interaction with the GreenPT API for chat completions. It accepts a model parameter and provides an invoke
method to send messages and retrieve responses asynchronously. To ensure API security, it requires an API key stored in environment variables.
import { GreenPTClient } from "https://esm.town/v/cricks_unmixed4u/openai-client/main.tsx";
const client = GreenPTClient("green-l");
async function getChatResponse() {
const messages = [
{ role: "user", content: "What is the capital of France?" },
];
try {
const response = await client.invoke(messages);
console.log("AI Response:", response);
} catch (error) {
console.error("Error invoking GreenPT API:", error);
}
}
// Execute the function to get a response
getChatResponse();
In this example, we create an instance of GreenPTClient
, send a message asking about the capital of France, and log the AI's response. Error handling is included to catch any issues with the API call.
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 provides several utility functions to help with common project tasks.
Always import utilities with version pins to avoid breaking changes:
import { parseProject, readFile, serveFile } from "https://esm.town/v/std/utils@85-main/index.ts";
For example, in Hono:
// 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));
// Read a file from the project
const fileContent = await readFile("/frontend/index.html", import.meta.url);
This is useful for including info for linking back to a val, ie in "view source" urls:
const projectVal = parseProject(import.meta.url);
console.log(projectVal.username); // Owner of the project
console.log(projectVal.name); // Project name
console.log(projectVal.version); // Version number
console.log(projectVal.branch); // Branch name
console.log(projectVal.links.self.project); // URL to the project page
However, it's extremely importing to note that parseProject
and other Standard Library utilities ONLY RUN ON THE SERVER.
If you need access to this data on the client, run it in the server and pass it to the client by splicing it into the HTML page
or by making an API request for it.
return new Response(null, { status: 302, headers: { Location: "/place/to/redirect" }})
instead of Response.redirect
which is broken<img src="https://maxm-imggenurl.web.val.run/the-description-of-your-image" />
alert()
, prompt()
, or confirm()
methodsimport.meta.url.replace("ems.sh", "val.town)"
(or passing this data to the client) and include target="_top"
attribute<script src="https://esm.town/v/std/catch"></script>
to HTML to capture client-side errorsDeno.env.get('keyname')
when you need to, but generally prefer APIs that don't require keyshttps://esm.sh
for npm and Deno dependencies to ensure compatibility on server and browser?deps=react@18.2.0,react-dom@18.2.0
and start the file with /** @jsxImportSource https://esm.sh/react@18.2.0 */
<script src="https://cdn.twind.style" crossorigin></script>
unless otherwise specified├── 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/index.ts
import { readFile, serveFile } from "https://esm.town/v/std/utils@85-main/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);
});
// Unwrap Hono errors to see original error details
app.onError((err, c) => {
throw err;
});
Environment Limitations:
shared/
must work in both frontend and backend environmentsDeno
keyword in shared codehttps://esm.sh
for imports that work in both environmentsSQLite Peculiarities:
React Configuration:
@jsxImportSource https://esm.sh/react@18.2.0
at the top of React filesFile Handling:
readFile
helpersAPI Design:
fetch
handler is the entry point for HTTP valsexport default app.fetch // This is the entry point for HTTP vals
With the understanding gained after Step 2, we will now initiate a new project focusing on statically importing a Val Town val and serving it via our HTTP trigger. Here's how we will set up the basic structure and code:
We'll establish our directory structure according to the recommended practices:
├── backend/
│ ├── routes/
│ │ ├── serveGptWrapper.http.tsx # HTTP trigger for serving the imported GPT wrapper
│ ├── index.ts # Main entry point for backend services
│ └── README.md
├── frontend/
│ ├── index.html # Main HTML template
│ ├── index.tsx # Frontend JS entry point
│ └── README.md
└── README.md
First, create the backend/index.ts
with a basic setup to launch the app. Given this will handle routing calls, we will ensure it's prepared for our routes to be added:
import { Hono } from 'https://esm.town/v/hono@3.5.0';
const app = new Hono();
import serveGptWrapper from './routes/serveGptWrapper.http.tsx';
// Register the route for serving the imported val
app.route('/gpt', serveGptWrapper);
// Middleware for error handling
app.onError((err, c) => {
throw err;
});
export default app.fetch; // This is the entry point for HTTP vals
Next, we'll create the HTTP trigger to serve the import. Create a file named backend/routes/serveGptWrapper.http.tsx
with the following content:
// backend/routes/serveGptWrapper.http.tsx
import { Hono } from 'https://esm.town/v/hono@3.5.0';
// The endpoint to serve the imported val
const app = new Hono();
const GTP_WRAPPER_URL = 'https://esm.town/v/cricks_unmixed4u/gpt-wrapper/backend/index.ts';
export default async (req: Request) => {
const response = await fetch(GTP_WRAPPER_URL);
if (!response.ok) {
return new Response("Failed to import GPT wrapper", { status: 500 });
}
const importedVal = await response.text();
// Here we need to go through some wrapping processes, depending on how the imported AppType can be served
// Assuming that AppType is exported from the imported val
const wrappedApp = new Function("return " + importedVal)();
// Proxy the request to the imported app
return wrappedApp(req);
};
In frontend/index.html
, we will prepare a simple interface allowing users to interact with our backend. This will allow them to access the imported val through the endpoint we defined.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>GPT Wrapper Interface</title> <script> async function callGptWrapper() { const response = await fetch('/gpt', { method: 'POST' }); const data = await response.json(); console.log(data); } </script> </head> <body> <h1>GPT Wrapper Interface</h1> <button onclick="callGptWrapper()">Call GPT Wrapper</button> </body> </html>
At this point, we have set up a basic Val Town project structure that statically imports an external val and serves it via HTTP. We will need to handle different scenarios for injecting or proxying requests based on how the external val is set up, ensuring it aligns with our specific needs.
In the next steps, we will test this structure, handle any potential edge cases or errors, and improve upon this layout as necessary. If there are specific features or functionalities you wish to see implemented or refined, please let me know.