Avatar

@nbbaier

11 likes103 public vals
Joined January 12, 2023
Valin' up a storm

Val Town AI Readme Writer

This val provides a class ReadmeWriter for generating readmes for vals with OpenAI. It can both draft readmes and update them directly

PRs welcome! See Todos below for some ideas I have.

Usage

To draft a readme for a given code, use the draftReadme method:

import { ReadmeWriter } from "https://esm.town/v/nbbaier/readmeGPT";

const readmeWriter = new ReadmeWriter({});
const val = "https://www.val.town/v/:username/:valname";

const generatedReadme = await readmeWriter.draftReadme(val);

To write and update a readme for a given code, use the writeReadme method:

import { ReadmeWriter } from "https://esm.town/v/nbbaier/readmeGPT";

const readmeWriter = new ReadmeWriter({});
const val = "https://www.val.town/v/:username/:valname";

const successMessage = await readmeWriter.writeReadme(val);

API Reference

Class: ReadmeWriter

The ReadmeWriter class represents a utility for generating and updating README files.

Constructor

Creates an instance of the ReadmeWriter class.

Parameters:
  • model (optional): The model to be used for generating the readme. Defaults to "gpt-3.5-turbo".
  • apiKey (optional): An OpenAI API key. Defaults to Deno.env.get("OPENAI_API_KEY").

Methods

  • draftReadme(val: string): Promise<string>: Generates a readme for the given val.

    • Parameters:

      • val: URL of the code repository.
    • Returns:

      • A promise that resolves to the generated readme.
  • writeReadme(val: string): Promise<string>: Generates and updates a readme for the given val.

    • Parameters:

      • val: URL of the code repository.
    • Returns:

      • A promise that resolves to a success message if the update is successful.

Todos

  • Additional options to pass to the OpenAI model
  • Ability to pass more instructions to the prompt to modify how the readme is constructed
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import { type WriterOptions } from "https://esm.town/v/nbbaier/WriterOptions";
import { fetch } from "https://esm.town/v/std/fetch?v=4";
import OpenAI, { type ClientOptions } from "npm:openai";
export class ReadmeWriter {
model: string;
openai: OpenAI;
apiKey: string;
valtownKey: string;
constructor(options: WriterOptions) {
const { model, ...openaiOptions } = options;
this.model = model ? model : "gpt-3.5-turbo";
this.openai = new OpenAI(openaiOptions);
this.valtownKey = Deno.env.get("valtown");
}
private createPrompt(code: string, userPrompt?: string) {
return `
You are an AI assistant that writes documentation for code. You output readmes
in GitHub flavored markdown. Usage sections should include a single code snippet
that a user can copy and paste. Never return anything other than documentation for
the code you are provided.
${userPrompt}
Take the below code and return a markdown readme:
${code}
`;
}
private async getVal(username: string, valName: string) {
try {
const res = await fetch(`https://api.val.town/v1/alias/${username}/${valName}`, {
method: "GET",
headers: {
"accept": "*/*",
"Content-Type": "application/json",
"Authorization": `Bearer ${this.valtownKey}`,
},
});
const { id, code } = await res.json();
return { id, code };
} catch (error) {
throw new Error("Error getting val code: " + error.message);
}
}
private async performOpenAICall(prompt: string) {
try {
Fork
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import { swaggerUI } from "npm:@hono/swagger-ui";
import { z } from "npm:@hono/zod-openapi";
import { createRoute, OpenAPIHono } from "npm:@hono/zod-openapi";
import { html } from "npm:hono/html";
const ParamsSchema = z.object({
id: z
.string()
.min(3)
.openapi({
param: {
name: "id",
in: "path",
},
example: "1212121",
}),
});
const UserSchema = z
.object({
id: z.string().openapi({
example: "123",
}),
name: z.string().openapi({
example: "John Doe",
}),
age: z.number().openapi({
example: 42,
}),
})
.openapi("User");
const route = createRoute({
method: "get",
path: "/users/{id}",
request: {
params: ParamsSchema,
},
responses: {
200: {
content: {
"application/json": {
schema: UserSchema,
},
},
description: "Retrieve the user",
},
},
});

SQLite Explorer

View and interact with your Val Town SQLite data. It's based off Steve's excellent SQLite Admin val, adding the ability to run SQLite queries directly in the interface. This new version has a revised UI and that's heavily inspired by LibSQL Studio by invisal. This is now more an SPA, with tables, queries and results showing up on the same page.

image.webp

Install

Install the latest stable version (v37) by forking this val that imports & exports it in your account:

Install v37

Authentication

SQLite Explorer basic authentication with your Val Town API Token as the password (leave the username field blank).

Todos / Plans

  • fix wonky sidebar separator height problem
  • improve error handling
  • improve table formatting
  • make result tables scrollable
  • add codemirror
  • add loading indication to the run button (initial version shipped)
  • add ability to favorite queries
  • add saving of last query run for a table (started)
  • add visible output for non-query statements
  • add schema viewing
  • add export to SQL, CSV, and JSON (CSV and JSON helper functions written in this val)
  • add refresh to table list sidebar after CREATE/DROP/ALTER statements
  • add automatic execution of initial select query on double click
  • add listener for cmd+enter to submit query
  • add views to the sidebar
Readme
Fork
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/** @jsxImportSource https://esm.sh/hono@latest/jsx **/
import { resetStyle } from "https://esm.town/v/nbbaier/resetStyle";
import {
EditorSection,
MockTable,
Separator,
Table,
TablesList,
} from "https://esm.town/v/nbbaier/sqliteStudioComponents";
import { RunSVG, TableSVG } from "https://esm.town/v/nbbaier/sqliteStudioSVGs";
import { sqliteStyle } from "https://esm.town/v/nbbaier/sqliteStyle";
import { basicAuth } from "https://esm.town/v/pomdtr/basicAuth";
import { sqlite } from "https://esm.town/v/std/sqlite";
import { Hono } from "npm:hono";
import type { FC } from "npm:hono/jsx";
import { jsxRenderer } from "npm:hono/jsx-renderer";
const HTML: FC = ({ children }) => {
return (
<html>
<head>
<title>SQLite Explorer</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap"
rel="stylesheet"
/>
<style
dangerouslySetInnerHTML={{ __html: resetStyle }}
/>
<style
dangerouslySetInnerHTML={{ __html: sqliteStyle }}
/>
<script src="https://unpkg.com/htmx.org@1.9.9/dist/htmx.min.js"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.12"></script>
<script type="module" src="https://esm.town/v/nbbaier/resizeScript" />
<script type="module" src="https://esm.town/v/nbbaier/tableSelectScript" />
</head>
<body _="
on keydown[event.metaKey and key is 'Enter'] log 'command + enter' then send submitForm to #sql-editor
">
<div class="root-container">
<header>
<h1>sqlite explorer</h1>
</header>
{children}
</div>
</body>
</html>

SQLite Dump Util

A utility function that generates SQL statements to dump the data and schema of tables in a SQLite database.

Usage

This example specifically dumps the users table and logs the output:

import { sqliteDump } from "https://esm.town/v/nbbaier/sqliteDump";

const dump = await sqliteDump(["users"]);
console.log(dump)

You can optionally specify a callback to handle the result. The example below dumps the users table and emails it using std/email.

import { email } from "https://esm.town/v/std/email";
import { sqliteDump } from "https://esm.town/v/nbbaier/sqliteDump";

await sqliteDump(["users"], async (res) => {
  await email({ text: res });
});

Function Signature

function sqliteDump(tables?: string[], callback?: ((result: string) => string | void | Promise<void>) | undefined): Promise<string | void>

Parameters

  • tables: (Optional) Array of table names to include in the dump. If not provided, all tables will be included.
  • callback: (Optional) An (potentially async) callback function to process the dump result. The callback receives the dump string as its argument.
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import { email } from "https://esm.town/v/std/email";
import { sqlite } from "https://esm.town/v/std/sqlite";
function generateInsertStatements(
tableName: string,
columns: string[],
rows: string[][],
): string[] {
const columnNames = columns.join(", ");
let insertStatements = [];
for (const row of rows) {
const values = row.map(v => `'${typeof v === "string" ? v.replace(/'/g, "%27") : v}'`).join(", ");
const insertStatement = `INSERT INTO ${tableName} (${columnNames}) VALUES (${values});`;
insertStatements.push(insertStatement);
}
return insertStatements;
}
export async function sqliteDump(
tables?: string[],
callback?: (result: string) => string | void | Promise<void>,
) {
const dumpTables = tables !== undefined
? tables
: ((
await sqlite.execute(
`select name, type from sqlite_schema where type = 'table'`,
)
).rows.map((row) => row[0]) as string[]);
let statements: string[] = [];
for (const table of dumpTables) {
const schemaQuery = await sqlite.execute(
`SELECT name, sql FROM sqlite_schema WHERE name = '${table}'`,
);
const tableSchema = schemaQuery.rows[0][1] as string;
const createStatement = tableSchema.replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS");
const { columns, rows } = await sqlite.execute(`select * from ${table} where id = 89`);
const insertStatements = generateInsertStatements(table, columns, rows);
statements.push(createStatement, ...insertStatements);
}
if (callback === undefined) {
callback = (result) => {

Return a paginated response

A helper function to take an array and return a paginated response. This is useful when defining one's own folders for pomdtr's vscode extension.

Usage:

const data = [...]

export default async function(req: Request): Promise<Response> {
  return paginatedResponse(req, data);
}

For demo usage in the context of the vscode extension see this val.

Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export async function paginatedResponse(req: Request, data: any[], defaultLimit: number = 20): Promise<Response> {
const url = new URL(req.url);
const searchParams = Object.fromEntries(url.searchParams.entries());
const limit = parseInt(searchParams.limit) || defaultLimit;
const offset = parseInt(searchParams.offset) || 0;
const dataSubset = data.slice(offset, offset + limit);
const nextPageOffset = offset + limit;
const nextPageUrl = new URL(url.toString());
nextPageUrl.searchParams.set("offset", nextPageOffset.toString());
const res = {
data: dataSubset,
links: {
self: url,
next: nextPageOffset < data.length ? nextPageUrl.toString() : null,
},
};
return Response.json(res);
}

Val Town Idea List Aggregator

This val serves as an aggregator for all vals with the tag // @vtIdeas in their code. To create your own idea list, simple fork this template val and follow the instructions there (instructions can also be found on the the aggregator homepage)

Feature Wish List

Here's some stuff that could be added to this val:

  • Pull in the actual lists of ideas from the various aggregated readmes and collect them in a (sortable?) table
  • A community list of val ideas, submittable to by form
  • Instead of a link list of links, make each Idea List a card, with revealable content
Readme
Fork
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import findIdeaLists from "https://esm.town/v/nbbaier/findIdeaLists";
import { ideaList } from "https://esm.town/v/nbbaier/ideaList";
import { api } from "https://esm.town/v/pomdtr/api";
import { extractMetadata } from "https://esm.town/v/pomdtr/extractMetadata";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { gfm } from "https://esm.town/v/pomdtr/gfm";
import { html } from "https://esm.town/v/stevekrouse/html?v=5";
import { Hono } from "npm:hono";
const app = new Hono();
const homepage = extractValInfo(import.meta.url);
const ideaLists = (await findIdeaLists()).map(val => {
const valName = val.name;
const author = val.author.username.replace("@", "");
const title = extractMetadata("title", val.code);
return { valName, author, title };
});
app.get("/", async c => {
const items = ideaLists.map(val => {
const { valName, author, title } = val;
return `- [${title}](/ideas/${author}.${valName}) by [${author}](https://www.val.town/u/${author})`;
});
const reservedVals = ideaLists.map(val => {
const { valName, author, title } = val;
return `[\`${valName}\`](https://www.val.town/v/${author}/${valName})`;
});
const markdown = `# Val Town Idea Aggregator
This val serves as n aggegrator for personal lists of ideas people have for what they
want to build on val town (or anything else having to do with Val Town, I suppose).
## Ideas
${items.join("\n")}
## Starting your own list
To create your own collection and add to this list, do the following:
1. Fork [this template val](https://www.val.town/v/nbbaier/vtIdeasTemplate)
2. Add the following metadata to the top of your freshly forked val's code
\`\`\`
// @vtIdeas
// @title <insert a nice title>

Create an API from a lowdb blob

This val exports a function that takes a lowdb instance and returns a Hono router that can be used to interact with the data. This is the beginning of an implementation of something like json-server for Val Town.

The resulting server also comes with a frontend at /. The code for the frontend can be found here.

See this val for an example.

Things I'd like to implement

  • All HTTP methods
  • Custom route definitions (like in json-server)
  • Custom frontends
  • Filtering, sorting, pagination, etc
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/** @jsxImportSource https://esm.sh/hono@3.9.2/jsx **/
import { Homepage } from "https://esm.town/v/nbbaier/dbToAPIFrontend";
import { checkResource, getResources, type Options, validate } from "https://esm.town/v/nbbaier/dbToApiHelpers";
import { Hono } from "npm:hono";
// TODO: implement options
export async function createServer(db, options: Options & { auth?: { username: string; password: string } } = {}) {
const app = new Hono();
if (options.auth) {
app.use(
"*",
);
}
// Make sure our db contains "data"
if (!validate(db)) {
app.get("/", c => c.text("Wrong data shape"));
return app;
}
// Extract information about the resources in the db
const resources = getResources(db);
// Get / => returns home
app.get("/", c => c.html(<Homepage resources={resources} />));
// GET /db => returns all db data
app.get("/db", c => c.json(db.data));
// GET /:resource => if :resource is valid, returns db.data[resource]
app.get("/:resource", c => {
const { resource } = c.req.param();
const queries = c.req.queries();
if (!checkResource(resource, resources)) {
return c.text(`The requested resource (${resource}) is not available`);
}
let q;
let chain = db.data[resource];
if (queries.q) {
q = queries.q[0];
chain = chain.filter(obj => {
obj.name === q;
});
}
Readme
Fork
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { Hono } from "npm:hono";
import { PDFDocument, rgb, StandardFonts } from "npm:pdf-lib";
const pdfDoc = await PDFDocument.create();
const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman);
const fontSize = 30;
const page = pdfDoc.addPage();
const { width, height } = page.getSize();
page.drawText("Creating PDFs in JavaScript is awesome!", {
x: 50,
y: height - 4 * fontSize,
size: fontSize,
font: timesRomanFont,
color: rgb(0, 0.53, 0.71),
});
const pdfBytes = await pdfDoc.save();
const blob = new Blob([pdfBytes], { type: "application/pdf" });
export function servePDF(req: Request) {
const app = new Hono();
app.get("/", (c) => {
return new Response(blob);
});
app.get("/download", (c) => {
c.header("Content-Type", "application/pdf");
c.header("Content-Disposition", "attachment; filename=cool.pdf");
return c.stream(async (stream) => {
await stream.write(pdfBytes);
});
});
return app.fetch(req);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { email } from "https://esm.town/v/std/email?v=9";
import { PDFDocument } from "npm:pdf-lib";
// PDF Creation
const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage();
page.drawText("You can create PDFs!");
const pdf = await pdfDoc.saveAsBase64();
export const sendPDF2 = (async () => {
return await email({
text: "hello from sendPDF2",
attachments: [{ content: btoa(pdf), filename: "file.pdf", type: "application/pdf" }],
});
})();
1