Back to packages list

Vals using hono

Description from the NPM package:
Ultrafast web framework for the Edges

Press to talk, and get a translation!

The app is set up so you can easily have a conversation between two people. The app will translate between the two selected languages, in each voice, as the speakers talk.

Add your OpenAI API Key, and make sure to open in a separate window for Mic to work.

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { Hono } from "npm:hono@3";
import { html } from "npm:hono@3/html";
import { cors } from 'npm:hono/cors';
import { OpenAI } from "npm:openai";
const app = new Hono();
const openai = new OpenAI(Deno.env.get("OPENAI_API_KEY_VOICE"));
class TranscriptionService {
async transcribeAudio(audioFile) {
try {
const transcription = await openai.audio.transcriptions.create({
file: audioFile,
model: "whisper-1",
response_format: "text",
});
return transcription;
} catch (error) {
console.error('OpenAI API error:', error);
throw error;
}
}
}
export const transcriptionService = new TranscriptionService();
app.use('*', cors({
origin: '*',
allowMethods: ['GET', 'POST'],
allowHeaders: ['Content-Type'],
}));
app.get("/", async (c) => {
const languages = [
"Afrikaans", "Arabic", "Armenian", "Azerbaijani", "Belarusian", "Bosnian", "Bulgarian", "Catalan", "Chinese", "Croatian", "Czech", "Danish", "Dutch", "English", "Estonian", "Finnish", "French", "Galician", "German", "Greek", "Hebrew", "Hindi", "Hungari
];
const voices = [
"alloy",
"echo",
"fable",
"onyx",
"nova",
"shimmer"
];
const resHtml = html`
<!DOCTYPE html>
<html>
<head>
<title>Translator</title>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<style>
html {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
@media (max-width: 640px) {
.container {
max-width: 100%;
padding: 1rem;
}
button {
font-size: 1.5rem;
padding: 1rem 2rem;
}
select {
font-size: 1.2rem;
padding: 0.8rem;
}
audio {
width: 100%;
}
}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div class="container mx-auto py-8 px-8" x-data="recordingData">
<h1 class="text-4xl font-bold mb-4">Translator</h1>
<div class="mb-4">
<h2 class="font-bold mb-2">Languages</h2>
<div class="grid grid-cols-2 gap-4" x-data="{
languages: [
'Afrikaans', 'Arabic', 'Armenian', 'Azerbaijani', 'Belarusian', 'Bosnian', 'Bulgarian', 'Catalan', 'Chinese', 'Croatian', 'Czech', 'Danish', 'Dutch', 'English', 'Estonian', 'Finnish', 'French', 'Galician', 'German', 'Greek', 'Hebrew', 'Hind
]
}">
<div>
<select id="language1" name="language1" class="border border-gray-300 rounded px-4 py-2 mb-4 w-full">
<template x-for="lang in languages">
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
import { Hono } from "npm:hono";
const app = new Hono();
app.get("/", (c) => {
return c.json({
name: "Val Town",
actions: [
{
title: "Search Vals",
type: "push",
page: "/search/vals",
},
{
title: "Open Documentation",
type: "open",
url: "https://docs.val.town",
},
],
});
});
app.get("/list", (c) => {
return c.json({}); // page
});
app.post("/command", (c) => {
return c.json({}); // command
});
export default app.fetch;

An http and class wrapper for Vercel's AI SDK

Usage:

  • Groq: https://yawnxyz-ai.web.val.run/generate?prompt="tell me a beer joke"&provider=groq&model=llama3-8b-8192
  • Perplexity: https://yawnxyz-ai.web.val.run/generate?prompt="what's the latest phage directory capsid & tail article about?"&provider=perplexity
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { Hono } from "npm:hono@3";
import { cors } from "npm:hono/cors";
import { openai, createOpenAI } from "npm:@ai-sdk/openai";
import { anthropic } from "npm:@ai-sdk/anthropic";
import { generateText, streamText, generateObject } from "npm:ai";
import { z } from "npm:zod";
const app = new Hono();
app.use('*', cors({
origin: '*',
allowMethods: ['GET', 'POST'],
allowHeaders: ['Content-Type'],
}));
openai.apiKey = Deno.env.get("OPENAI_API_KEY");
anthropic.apiKey = Deno.env.get("ANTHROPIC_API_KEY");
const groq = createOpenAI({
baseURL: 'https://api.groq.com/openai/v1',
apiKey: Deno.env.get("GROQ_API_KEY"),
});
const perplexity = createOpenAI({
apiKey: Deno.env.get("PERPLEXITY_API_KEY") ?? '',
baseURL: 'https://api.perplexity.ai/',
});
class ModelProvider {
async generateResponse(c, provider, model, prompt, maxTokens, streaming, schema, messages, tools) {
if (provider === 'openai') {
return this.generateOpenAIResponse(c, model, prompt, maxTokens, streaming, schema, messages, tools);
} else if (provider === 'anthropic') {
return this.generateAnthropicResponse(c, model, prompt, maxTokens, streaming, schema, messages, tools);
} else if (provider === 'groq') {
return this.generateGroqResponse(c, model, prompt, maxTokens, streaming, schema, messages, tools);
} else if (provider === 'perplexity') {
return this.generatePerplexityResponse(c, model, prompt, maxTokens, streaming, schema, messages, tools);
} else {
throw new Error('Invalid provider');
}
}
async generateOpenAIResponse(c, model, prompt, maxTokens, streaming, schema, messages) {
const options = {
model: openai(model || 'gpt-3.5-turbo'),
max_tokens: maxTokens || 100,
};
if (prompt && messages && messages.length > 0) {
throw new Error('prompt and messages cannot be defined at the same time');
} else if (prompt) {
options.prompt = prompt;
} else if (messages && messages.length > 0) {
options.messages = messages;
}
if (tools) {
options.tools = tools;
}
if (schema) {
const { object } = await generateObject({
...options,
schema: z.object(JSON.parse(schema)),
});
return c.json(object);
} else if (streaming) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
const { textStream } = await streamText(options);
for await (const delta of textStream) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ token: delta })}\n\n`));
}
controller.enqueue(encoder.encode(`data: [DONE]\n\n`));
controller.close();
},
});
return c.body(stream, 200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
} else {
const { text, finishReason, usage } = await generateText(options);
return c.json({ text, finishReason, usage });
}
}
async generateAnthropicResponse(c, model, prompt, maxTokens, streaming, schema, messages) {
if (schema) {
const { object } = await generateObject({
model: anthropic(model || 'claude-3-haiku-20240307'),
prompt: prompt,

A minimal bookmarking tool

This allows you to bookmark links and view them later. Completely powered by ValTown and SQLite.

To set this up for yourself

  1. Fork the val
  2. From your ValTown settings page, add an environment variable named bookmarks_client_id and give it a value (you will be using this for saving)
  3. Add another environment variable named bookmarks_client_secret and give it a value (you will also be using this for saving)
  4. At first, the "bookmarks" table will not exist, so we need to save an article first, which will create the "bookmarks" table
  5. To do this, add a bookmarklet to your browser with this value (replace BOOKMARKS-CLIENT-ID and BOOKMARKS-CLIENT-SECRET with the values you added to the environment variables, and replace BOOKMARKS-URL with your VAL's URL):
javascript:void(open('BOOKMARKS-URL/save?u='+encodeURIComponent(location.href)+'&t='+encodeURIComponent(document.title)+'&id=BOOKMARKS-CLIENT-ID&secret=BOOKMARKS-CLIENT-SECRET', 'Bookmark a link', 'width=400,height=450'))
  1. Click this bookmarklet to bookmark the URL of the current active tab
  2. Go to your VAL URL homepage to see the bookmark

Demo

Here are my bookmarks: https://ramkarthik-bookmark.web.val.run/

Note

Make sure you don't share bookmarks_client_id and bookmarks_client_secret. It is used for authentication before saving a bookmark.

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { sqlite } from "https://esm.town/v/std/sqlite?v=4";
import { Hono } from "npm:hono@3";
const app = new Hono();
app.get("/", async (c) => {
var page = parseInt(c.req.query("page") || "0");
var offset = 0;
if (page && page > 0) {
offset = page * 10;
}
var bookmarks = await sqlite.execute({
sql: "select title, url from bookmarks order by created_at desc limit 10 offset :offset",
args: { offset: offset },
});
var totalBookmarkRows = await sqlite.execute("select count(1) from bookmarks order by created_at desc");
var totalBookmarks = parseInt(totalBookmarkRows.rows[0][0].toString());
var pagesCount = Math.floor(
((totalBookmarks % 10) == 0 && totalBookmarks / 10 > 0)
? ((totalBookmarks / 10) - 1)
: (totalBookmarks / 10),
);
var bookmarksList = "";
for (var i = 0; i < bookmarks.rows.length - 1; i++) {
bookmarksList += "<p>" + (page * 10 + i + 1) + ". <a href=\"" + bookmarks.rows[i][1] + "\">" + bookmarks.rows[i][0]
+ "</a></p>";
}
var pagination = "<div style=\"flex-direction: row; width: 100%;justify-content: space-between;\">";
if (page > 0) {
pagination += "<a href=\"?page=" + (page - 1) + "\"> < prev </a>";
}
if (page < pagesCount) {
pagination += "<a href=\"?page=" + (page + 1) + "\"> next > </a>";
}
pagination += "</div>";
const html = `<html>
<head>
<title>Michael & Kelly Bookmarks</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
</head>
<body style="width:70%; margin-left:15%">
<h1>Michael & Kelly Bookmarks</h1>`
+ bookmarksList
+ pagination + `</body>
</html>`;
return new Response(
html,
{
headers: {
"Content-Type": "text/html",
},
},
);
});
app.get("/save", async (c) => {
const id = c.req.query("id");
const secret = c.req.query("secret");
const title = c.req.query("t");
const url = c.req.query("u");
if (!id && !secret) {
return c.text("Authentication details (ID/Secret) missing!");
}
if (id != Deno.env.get("bookmarks_client_id") || secret != Deno.env.get("bookmarks_client_secret")) {
return c.text("Unauthorized!");
}
if (!url) {
return c.text("URL missing!");
}
const create_table = await sqlite.execute(
"CREATE TABLE IF NOT EXISTS bookmarks (title TEXT, url TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)",
);
const res = await sqlite.execute({
sql: `insert into bookmarks (title, url) values (:title, :url)`,
args: { title: title, url: url },
});
return c.text("Saved!");
});
app.get("/api/bookmarks", async (c) => {
var baseUrl = c.req.url;
var page = parseInt(c.req.query("page") || "0");
var offset = 0;
if (page && page > 0) {
offset = page * 10;
}
var bookmarks = await sqlite.execute({
sql: "select title, url from bookmarks order by created_at desc limit 10 offset :offset",
args: { offset: offset },
});
var totalBookmarkRows = await sqlite.execute("select count(1) from bookmarks order by created_at desc");
var totalBookmarks = parseInt(totalBookmarkRows.rows[0][0].toString());
var pagesCount = Math.floor(
((totalBookmarks % 10) == 0 && totalBookmarks / 10 > 0)
? ((totalBookmarks / 10) - 1)
: (totalBookmarks / 10),
);
let response = {
prev: page > 0 ? baseUrl + "?page=" + (page - 1) : null,
1
2
3
import { Hono } from "npm:hono";
const app = new Hono();
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
51
52
53
54
55
56
import { Hono } from "npm:hono";
import Parser from "npm:rss-parser";
const app = new Hono();
app.get("/", (c) => {
return c.json({
type: "list",
list: {
items: [
{
title: "Search Front Page",
actions: [
{
type: "push",
push: {
page: "/frontpage",
},
},
],
},
],
},
});
});
app.get("/frontpage", async (c) => {
const feed = await new Parser().parseURL(
`https://hnrss.org/frontpage?description=0&count=25`,
);
return c.json({
type: "list",
list: {
items: feed.items?.map((item) => ({
title: item.title || "",
actions: [
{
type: "open",
open: {
url: item.link || "",
},
},
{
type: "copy",
copy: {
text: item.link || "",
},
},
],
})),
},
});
});
export default app.fetch;
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
import { Hono } from "npm:hono";
export function githubExtension({
token: string,
}) {
const app = new Hono();
app.get("/", (c) => {
return c.json({
type: "list",
list: {
items: [
{
"title": "Search Repositories"
}
]
}
})
});
app.get("/repos/search", (c) => {
return {
type: "list",
list: {
items: []
}
}
})
return app.fetch
}

PersonalizationGPT

You are a helpful personalization assistant

Use GPT to return JIT personalization for client side applications.

If you fork this, you'll need to set OPENAI_API_KEY in your Val Town Secrets.

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { Hono } from "npm:hono";
import { OpenAI } from "npm:openai";
const defaultUser = {
name: "Mike W",
age: 31,
// interests: ["books"],
interests: ["rock", "italian"],
};
const limit = 5;
const personalizations = [
{
name: "Here Comes the Sun",
type: "music",
genre: "rock",
description:
"Released in 1969, Here Comes the Sun by The Beatles is a song about hope. We all go through difficult times, difficult moments in our life. And some of these moments feel like they last a long time -- just like the long, cold, lonely winter.",
},
{
genre: "rap",
name: "No More Pain",
type: "music",
description: "Released in 1994, No More Pain is a rap song by American rap producer 2Pac.",
},
{
genre: "country",
name: "Sunrise",
type: "music",
description: "Sunrise is a song by country singer Morgan Wallen",
},
{
name: "Stairway to Heaven",
type: "music",
genre: "rock",
description:
"Stairway to Heaven is a song by the English rock band Led Zeppelin. It was released on 1971 in the United Kingdom.",
},
{
name: "Romeo and Juliet",
type: "book",
genre: "romance",
description:
"Romeo and Juliet is a classic tale of love and conflict. It was written by William Shakespeare in 1596.",
},
{
name: "The Catcher in the Rye",
type: "book",
genre: "romance",
description: "The Catcher in the Rye is a novel by J. D. Salinger, published in 1951.",
},
{
name: "The Godfather",
type: "books",
genre: "crime",
description:
"The Godfather is a 1972 American crime drama film directed by Francis Ford Coppola. The film depicts the Corleone family's history and its roles in the fictional Italian-American crime syndicate.",
},
{
name: "The Lord of the Rings",
type: "books",
genre: "fantasy",
description:
"The Lord of the Rings is an epic high fantasy novel written by English author J. R. R. Tolkien. It was published on 29 June 1954 by Charles",
},
{
name: "Pizza",
type: "food",
genre: "italian",
description:
"Pizza is a dish of Italian origin consisting of a usually round, flattened base of leavened wheat-based dough,",
},
{
name: "Pasta",
type: "food",
genre: "italian",
description: "Pasta is a type of food typically made from durum wheat semolina,",
},
{
name: "Rice",
type: "food",
genre: "asian",
description: "Rice is the seed of the grass species Oryza sativa, commonly known as the rice plant",
},
{
name: "Sushi",
type: "food",
genre: "japanese",
description: "Sushi is a type of Japanese rice and was originally a dish of prepared vinegared rice.",
},
];
type UserObject = typeof defaultUser;
const personalizationGPT = async (user: UserObject) => {
const openai = new OpenAI();
let chatCompletion = await openai.chat.completions.create({
messages: [
{

Date Me Directory

This is entry-point val for the source code for the Date Me Directory. Contributions welcome!

This app uses Hono as the server framework and for JSX.

The vals are stored in Val Town SQLite.

Contributing

Forking this repo should mostly work, except for the sqlite database. You'll need to create the table & populate it with some data. This script should do it, but I think it has a couple bugs. If you're interested in contributing to this project contact me or comment on this val and I'll get it working for ya!

Todos

  • Make the SQLite database forkable and build a widget/workflow for that, ie fix @stevekrouse/dateme_sqlite
  • Require an email (that isn't shared publicly)
    • Verify the email address with a "magic link"
  • Refactor Location to an array of Lat, Lon
    • Geocode all the existing locations
    • Add a geocoder map input to the form
    • Allow selecting multiple location through the form
  • Profile performance & speed up site, possibly add more caching
  • Let people edit their forms
  • Featured profiles
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=50";
import { form } from "https://esm.town/v/stevekrouse/date_me_form";
import browse from "https://esm.town/v/stevekrouse/dateme_browse";
import faq from "https://esm.town/v/stevekrouse/dateme_faq";
import home from "https://esm.town/v/stevekrouse/dateme_home";
import { dateMeRSS } from "https://esm.town/v/stevekrouse/dateMeRSS";
import { Hono } from "npm:hono@3";
const app = new Hono();
app.get("/", home);
app.get("/browse", browse);
app.route("/submit", form);
app.get("/faq", faq);
app.get("/rss.xml", c => dateMeRSS(c.req as unknown as Request));
export default modifyFetchHandler(app.fetch, {
style: `@media (max-width: 500px) {
.github-fork-ribbon {
display: none !important;
}
}`,
val: { handle: "stevekrouse", name: "dateme" },
});

Get common or "dominant" color from an image given a source URL

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/** @jsxImportSource npm:hono@3/jsx */
import { Hono } from "https://esm.sh/hono@3";
const EXAMPLE_URL = "https://letsenhance.io/static/8f5e523ee6b2479e26ecc91b9c25261e/1015f/MainAfter.jpg";
const PERCENT_COVERAGE = 10; // lower is faster, but higher is more accurate
function getPixelIndex(numToRound) {
// Each pixel is 4 units long: r,g,b,a
const remainder = numToRound % 4;
if (remainder == 0) return numToRound;
return numToRound + 4 - remainder;
}
export async function getColor(src: string = EXAMPLE_URL): Promise<string> {
const { default: Jimp } = await import("npm:jimp");
const image = await Jimp.read(src);
const store = {};
const total_pixels = image.bitmap.width * image.bitmap.height;
const coverage = total_pixels * PERCENT_COVERAGE / 100;
const data = image.bitmap.data;
const max_pixel_index = total_pixels - 1;
for (let i = 0; i < coverage; ++i) {
const x = getPixelIndex(Math.floor(Math.random() * max_pixel_index));
const key = `${data[x]},${data[x + 1]},${data[x + 2]}`;
const val = store[key];
store[key] = val ? val + 1 : 1;
}
const rgb_code = Object.keys(store).reduce((a, b) => store[a] > store[b] ? a : b);
return `rgb(${rgb_code})`;
}
const template = (children) => (
<html>
<head>
<title>Dominant Color from Image</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com" />
</head>
<body class="p-20">
<div class="flex flex-col p-10 space-y-10">
<a href="/">
<h1 class="text-5xl font-extrabold hover:underline">Dominant Color from Image</h1>
</a>
<div class="flex flex-col flex-grow justify-center items-center w-full">
{children}
</div>
<a href="https://val.town/v/stevekrouse/getColor" class="hover:underline text-center">
view code
</a>
</div>
</body>
</html>
);
const form = (url) => (
<form
action="/"
class="flex w-full"
>
<input name="url" type="url" value={url} class="border border-black p-4 text-2xl w-full"></input>
<button class="border border-black p-4 text-2xl w-40 hover:bg-black hover:text-white">Get Color</button>
</form>
);
const app = new Hono();
export default app.fetch;
app.get("/", async (c) => {
const url = c.req.query("url");
if (url) {
const color = await getColor(url);
return c.html(template(
<div class="text-3xl space-y-4">
<div class="flex items-center">{form(url)}</div>
<img src={url} />
<div class="flex space-x-2">
<div class="m-auto"></div>
<div class="w-10 h-10" style={{ backgroundColor: color }}></div>{" "}
<span class="text-4xl font-bold">{color}</span>
</div>
</div>,
));
}
else {
return c.html(template(form(EXAMPLE_URL)));
}
});
1
2
3
4
5
6
7
import { Hono } from "npm:hono@4";
import { denoServer } from "https://esm.town/v/pomdtr/deno_server?v=8";
const app = new Hono();
app.get("/", (c) => c.text("Hello from Hono!"));
app.get("/yeah", (c) => c.text("Routing!"));
export default denoServer(app.fetch);
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/** @jsx jsx */
import { Hono } from "npm:hono@3";
import { jsx } from "npm:hono@3/jsx";
import { cors } from 'npm:hono/cors';
import { OpenAI } from "npm:openai";
const app = new Hono();
const openai = new OpenAI();
app.use('*', cors({
origin: '*',
allowMethods: ['GET', 'POST'],
allowHeaders: ['Content-Type'],
}));
// Server-side rendering
app.get("/", async (c) => {
const html = (
<html>
<head>
<title>OpenAI Prompt Example</title>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div class="container mx-auto py-8">
<h1 class="text-4xl font-bold mb-4">OpenAI Prompt Example</h1>
<form action="/prompt" method="GET">
<label for="prompt" class="block mb-2 font-bold">Prompt:</label>
<input type="text" id="prompt" name="prompt" value="Say hello in a creative way" class="border border-gray-300 rounded px-4 py-2 mb-4 w-full" />
<button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">
Submit
</button>
</form>
<div class="mt-4">
<h2 class="text-xl font-bold mb-2">Response:</h2>
<div id="output" class="border border-gray-300 rounded p-4">
{c.req.query('response') || ''}
</div>
</div>
</div>
</body>
</html>
);
return c.html(html);
});
app.get('/prompt', async (c) => {
const prompt = c.req.query('prompt');
try {
const response = await openai.chat.completions.create({
model: "gpt-4",
messages: [{ role: "user", content: prompt }],
max_tokens: 100,
});
const generatedResponse = response.choices[0].message.content;
return c.redirect(`/?response=${encodeURIComponent(generatedResponse)}`);
} catch (error) {
console.error('OpenAI API error:', error);
return c.redirect('/?response=Error%20occurred.');
}
});
export default app.fetch;
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
/** @jsxImportSource npm:hono/jsx */
import { sqlite } from "https://esm.town/v/std/sqlite?v=6";
import { Hono } from "npm:hono";
const app = new Hono();
app.get("/", (c) => {
return c.html(
<html>
<head>
<script type="module" src="https://esm.town/v/pomdtr/libsqlstudio_script"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>,
);
});
app.post("/api/execute", async (c) => {
const res = await sqlite.execute(await c.req.json());
return c.json(res);
});
app.post("/api/batch", async (c) => {
const res = await sqlite.batch(await c.req.json());
return c.json(res);
});
export default app.fetch;

A minimal bookmarking tool

This allows you to bookmark links and view them later. Completely powered by ValTown and SQLite.

To set this up for yourself

  1. Fork the val
  2. From your ValTown settings page, add an environment variable named bookmarks_client_id and give it a value (you will be using this for saving)
  3. Add another environment variable named bookmarks_client_secret and give it a value (you will also be using this for saving)
  4. At first, the "bookmarks" table will not exist, so we need to save an article first, which will create the "bookmarks" table
  5. To do this, add a bookmarklet to your browser with this value (replace BOOKMARKS-CLIENT-ID and BOOKMARKS-CLIENT-SECRET with the values you added to the environment variables, and replace BOOKMARKS-URL with your VAL's URL):
javascript:void(open('BOOKMARKS-URL/save?u='+encodeURIComponent(location.href)+'&t='+encodeURIComponent(document.title)+'&id=BOOKMARKS-CLIENT-ID&secret=BOOKMARKS-CLIENT-SECRET', 'Bookmark a link', 'width=400,height=450'))
  1. Click this bookmarklet to bookmark the URL of the current active tab
  2. Go to your VAL URL homepage to see the bookmark

Demo

Here are my bookmarks: https://ramkarthik-bookmark.web.val.run/

Note

Make sure you don't share bookmarks_client_id and bookmarks_client_secret. It is used for authentication before saving a bookmark.

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import { sqlite } from "https://esm.town/v/std/sqlite?v=4";
import { Hono } from "npm:hono@3";
const app = new Hono();
app.get("/", async (c) => {
var page = parseInt(c.req.query("page") || "0");
var offset = 0;
if (page && page > 0) {
offset = page * 10;
}
var bookmarks = await sqlite.execute({
sql: "select title, url from bookmarks order by created_at desc limit 10 offset :offset",
args: { offset: offset },
});
var totalBookmarkRows = await sqlite.execute("select count(1) from bookmarks order by created_at desc");
var totalBookmarks = parseInt(totalBookmarkRows.rows[0][0].toString());
var pagesCount = Math.floor(
((totalBookmarks % 10) == 0 && totalBookmarks / 10 > 0)
? ((totalBookmarks / 10) - 1)
: (totalBookmarks / 10),
);
var bookmarksList = "";
for (var i = 0; i < bookmarks.rows.length; i++) {
bookmarksList += "<p>" + (page * 10 + i + 1) + ". <a href=\"" + bookmarks.rows[i][1] + "\">" + bookmarks.rows[i][0]
+ "</a></p>";
}
var pagination = "<div style=\"flex-direction: row; width: 100%;justify-content: space-between;\">";
if (page > 0) {
pagination += "<a href=\"?page=" + (page - 1) + "\"> < prev </a>";
}
if (page < pagesCount) {
pagination += "<a href=\"?page=" + (page + 1) + "\"> next > </a>";
}
pagination += "</div>";
const html = `<html>
<head>
<title>My reading list</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
</head>
<body style="width:70%; margin-left:15%">
<h1>Reading list</h1>`
+ bookmarksList
+ pagination + `</body>
</html>`;
return new Response(
html,
{
headers: {
"Content-Type": "text/html",
},
},
);
});
app.get("/save", async (c) => {
const id = c.req.query("id");
const secret = c.req.query("secret");
const title = c.req.query("t");
const url = c.req.query("u");
if (!id && !secret) {
return c.text("Authentication details (ID/Secret) missing!");
}
if (id != Deno.env.get("bookmarks_client_id") || secret != Deno.env.get("bookmarks_client_secret")) {
return c.text("Unauthorized!");
}
if (!url) {
return c.text("URL missing!");
}
const create_table = await sqlite.execute(
"CREATE TABLE IF NOT EXISTS bookmarks (title TEXT, url TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)",
);
const res = await sqlite.execute({
sql: `insert into bookmarks (title, url) values (:title, :url)`,
args: { title: title, url: url },
});
return c.text("Saved!");
});
export default app.fetch;

Create your own Myspace profile, deployed to Val town. https://jdan-myspace.web.val.run

Screenshot 2024-04-27 at 7.20.32 PM.png

Click "..." and select Fork to create your own.

Screenshot 2024-04-27 at 7.18.00 PM.png

From there you can:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { myspaceHtml } from "https://esm.town/v/jdan/myspaceHtml";
import { Hono } from "npm:hono@3";
// TODO: Fetch from key-value
const profile = {
displayName: "Jordan",
seo: {
title: "Jordan Scales | Myspace.com",
},
info: {
imageUrl: "https://jordanscales.com/me.png",
allImagesUrl: "https://instagram.com/jdanscales",
status:
`<span style="color:purple"><em>~*~ do you realize<br>I could have been the one to change your life? - xcx ~*~</em></span>`,
gender: "Male",
age: 31,
location: ["Hoboken, NEW JERSEY", "United States"],
lastLogin: "04/27/2024",
},
contact: {
sendMessage: "mailto:hello@example.com",
forwardToFriend: "https://twitter.com/intent/tweet?url=https%3A%2F%2Fjordanscales.com",
addToFriends: "https://twitter.com/jdan",
addToFavorites: "https://www.val.town/v/jdan/myspace",
},
interests: {
general: "Reading, surfing the web, video games, long walks with friends, bubble tea, programming, chess, art",
music:
"LIGHTS, Daft Punk, Mr. Oizo, The Chemical Brothers, CHVRCHES, Japanese Breakfast, The Prodigy, PVRIS, The Japanese House, Poppy, blink-182, Chrome Sparks, Ashnikko, Rezz, Grimes, Bag Raiders, Kim Petras, Tegan and Sara, Charli XCX, MARINA",
television: " Boardwalk Empire, The Sopranos, The Office, Parks and Recreation, King of Queens, See, Crashing",
books:
"Three Body Problem (Remembrance of Earth's Past trilogy), Alex's Adventures in Numberland, Things to Make and Do in the Fourth Dimension, Ball Lightning",
},
details: {
status: "Single",
hereFor: "Dating, Serious Relationships, Friends, Networking",
hometown: "Middletown, NJ",
sign: "Cancer",
smokeDrink: "No / Sometimes",
occupation: "Webmaster",
},
schools: [
{
details: [
"Stevens Institute of Technology",
"Hoboken, NEW JERSEY",
"Degree: Bachelor's Degree",
"Major: Computer Science",
"Minor: Mathematics, Science and Technology Studies",
],
start: "2010",
end: "2014",
},
{
details: [
"Middletown High School South",
"Middletown, NEW JERSEY",
"President: Computer Club",
],
start: "2006",
end: "2010",
},
],
links: [
{ url: "https://twitter.com/jdan", text: "Twitter" },
{ url: "https://mastodon.xyz/@jordan", text: "Mastodon" },
{ url: "https://github.com/jdan", text: "GitHub" },
{ url: "https://notes.jordanscales.com/", text: "Blog" },
{ url: "https://hash.jordanscales.com/", text: "Hashart" },
],
blog: {
allPostsUrl: "https://notes.jordanscales.com",
posts: [
{
title: "Is this true?",
url: "https://notes.jordanscales.com/is-this-true",
},
{
title: "Operating on Infinite Lists",
url: "https://notes.jordanscales.com/infinite-lists",
},
{
title: "I Peeked Into My Node_Modules Directory And You Won’t Believe What Happened Next",
url: "https://notes.jordanscales.com/node_modules",
},
],
},
blurbs: {
aboutMe:
`My name is Jordan and I'm not sure what to put on my new profile. I'm a software developer based out of Hoboken, New Jersey and I like to
build things that make people smile.<br><br>
I'm currently trying to get better at <a href="https://lichess.org/@/jordanscales">chess</a> and occasionally making
<a href="https://hash.jordanscales.com/">some art</a>.<br><br>
I write the most words on twitter (<a href="https://twitter.com/jdan">@jdan</a>) and <a href="https://notes.jordanscales.com">my blog</a>.<br><br>
`,
whoIdLikeToMeet: "Tom! Thank you for making the Internet truly good for a while. but most of all, Samy is my hero",
},
top8: {