Back to APIs list

ChatGPT API examples & templates

Use these vals as a playground to view and fork ChatGPT API examples and templates on Val Town. Run any example below or find templates that can be used as a pre-built solution.
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
/** @jsxImportSource npm:hono@3/jsx */
import { fetchText } from "https://esm.town/v/stevekrouse/fetchText";
import { chat } from "https://esm.town/v/stevekrouse/openai";
import cronstrue from "npm:cronstrue";
import { Hono } from "npm:hono@3";
const examples = [
{
user: "website that shows the current time",
content: `/** @jsxImportSource npm:react */
export default function() {
return <h1>{new Date().toLocaleTimeString()}</h1>;
}`,
},
{
user: `A collaborative poem builder.
It stores each line of the poem in sqlite.
It has a textbox that lets anyone input a new line to the poem.`,
content: await fetchText("https://esm.town/v/stevekrouse/poembuilder3?v=4"),
},
{
user: "an app that uses chatgpt to convert natural language to cron syntax",
content: await fetchText("https://esm.town/v/stevekrouse/cron2"),
},
];
const app = new Hono();
export default app.fetch;
app.get("/", async (c) => {
const example = examples[Math.floor(Math.random() * examples.length)];
const description = c.req.query("description") || example.user;
let code = c.req.query("description") ? await compile(description) : example.content;
return c.html(
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com" />
<title>Val Writer</title>
</head>
<body class="flex p-6 flex-col space-y-4 max-w-2xl mx-auto">
<div>
<h1 class="text-3xl">Val Writer</h1>
<p>Compile your prompt into code</p>
</div>
<form class="flex space-x-2" hx-disabled-elt="button">
<textarea
name="description"
required
class="w-full border-2 rounded-lg p-2"
rows={5}
>
{description}
</textarea>
<button class="bg-purple-500 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded disabled:hidden">
Write
</button>
</form>
<div>
<pre>
{code}
</pre>
</div>
</body>
</html>,
);
});
export async function compile(description: string) {
const messages = [
{
role: "system",
content: `You are a programmer.
Convert user requests to code.
Write ONLY Deno TypeScript.
If you use Hono, use \`export default app.fetch;\` to start the server.
Only use web standard fetch.
For storage, use \`import { sqlite } from "https://esm.town/v/std/sqlite?v=5";\`. Rows are arrays, NOT objects.
For notifying the user, use: \`import { email } from "https://esm.town/v/std/email";
await email({ subject: "Subject", text: "Body" });\`
Add extensive comments.`,
},
...examples.flatMap((example) => [, {
role: "user",
content: example.user,
}, {
role: "assistant",
content: example.content,
}]),
{ role: "user", content: description },
];
const { content } = await chat(messages, {
model: "gpt-4o",
max_tokens: 4000,
});
return content;
}

Calorie Count via Photo

Uploads your photo to ChatGPT's new vision model to automatically categorize the food and estimate the calories.

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
/** @jsxImportSource npm:hono@3/jsx */
import { fileToDataURL } from "https://esm.town/v/stevekrouse/fileToDataURL";
import { modifyImage } from "https://esm.town/v/stevekrouse/modifyImage";
import { chat } from "https://esm.town/v/stevekrouse/openai";
import { Hono } from "npm:hono@3";
function esmTown(url) {
return fetch(url, {
headers: {
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.142.86 Safari/537.36",
},
}).then(r => r.text());
}
const app = new Hono();
export default app.fetch;
app.get("/", async (c) =>
c.html(
<html>
<head>
<title>Calorie Estimator</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com" />
</head>
<body class="h-dvh bg-pink-100">
<div class="flex flex-col h-full items-center p-20">
<a href="/">
<h1 class="text-5xl font-extrabold text-pink-900 hover:underline">How many calories?</h1>
</a>
<div class="flex flex-col flex-grow justify-center items-center">
<form
action="/"
target="results"
class="flex flex-col"
method="post"
enctype="multipart/form-data"
>
<div></div>
<label
for="file"
id="upload"
class="px-6 py-4 m-4 text-3xl text-center cursor-pointer border-4 border-pink-400 rounded-xl transition-all bg-pink-300 hover:bg-pink-400 hover:text-pink-900 text-pink-900 shadow-2xl shadow-pink-500/50"
>
Upload photo
</label>
<input
class="hidden"
type="file"
id="file"
accept="image/*"
name="file"
/>
<img class="rounded-md shadow-2xl shadow-pink-500/50" id="preview" />
</form>
<iframe width="300px" height="600px" name="results" class="hidden" id="results" srcdoc="Loading...">
</iframe>
</div>
<a href="https://val.town/v/stevekrouse/calories" class="hover:underline text-pink-700">
view code
</a>
</div>
</body>
<script
dangerouslySetInnerHTML={{
__html: await esmTown("https://esm.town/v/stevekrouse/calories_script"),
}}
type="module"
/>
</html>,
));
app.post("/", async (c) => {
const formData = await c.req.formData();
const file = formData.get("file") as File;
console.log(file);
if (!file || !file.size) {
return c.text("No file uploaded", 400);
}
const estimates = await calorieEstimate(file);
if (estimates.error) {
return c.text(estimates.error, 500);
}
const dataURL = await fileToDataURL(file);
return c.html(
<div>
{estimates.map((estimate) => <p>{estimate.ingredient}: {estimate.calories} calories</p>)}
Total: {estimates.reduce((sum, estimate) => sum + estimate.calories, 0)}
</div>,
);
});
export async function calorieEstimate(file: File) {
const dataURL = await fileToDataURL(file);
try {
const response = await chat([
{
role: "system",
content: `You are an nutritionist.
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
/** @jsxImportSource https://esm.sh/react */
import { renderToString } from "npm:react-dom/server";
export default async function(req: Request) {
return new Response(
renderToString(
<html>
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tahir's TIL</title>
</head>
<body
style={{
maxWidth: "70ch",
padding: "3em 1em",
margin: "auto",
lineHeight: "1.75",
fontSize: "1.25em",
fontFamily: "sans-serif",
}}
>
<h1>Tahir's TIL</h1>
<h3>Design, Web and Data Things</h3>
<div style={{ display: "flex", flexDirection: "column", gap: "15px" }}>
<a href="https://github.com/Little-Languages/quiver" style={itemStyle}>
declarative arrows for the web. ⤵
</a>
<a href="https://github.com/steveruizok/perfect-arrows" style={itemStyle}>
Draw perfect arrows between points and shapes. By the the creator of tldraw.
</a>
<a href="https://news.ycombinator.com/item?id=32972004" style={itemStyle}>
58 bytes of CSS to look great nearly everywhere
</a>
<a href="https://maxbo.me/a-html-file-is-all-you-need" style={itemStyle}>
A HTML file is all you need. Lot's of useful client side JavaScript examples including charts, Python and
SQLite.
</a>
<a href="https://developer.mozilla.org/en-US/blog/color-palettes-css-color-mix/" style={itemStyle}>
color-mix() allows you to mix colours in CSS using 3 colour spaces: srgb, oklab or hsl
</a>
<a href="https://www.val.town/v/tfayyaz/honoJsDialogShowModal" style={itemStyle}>
val.town demo - Hono JS Dialog Show Modal
</a>
<a href="https://www.val.town/v/tfayyaz/honoJsInClientTemplate" style={itemStyle}>
Simple Hono JS val.town Template with client side JavaScript using html Helper
</a>
<a href="https://ourworldindata.org/electric-car-sales" style={itemStyle}>
Over 90% of new cars are electric in Norway + Our World in Data make beautiful charts
</a>
<a
href="https://webkit.org/blog/15131/speedometer-3-0-the-best-way-yet-to-measure-browser-performance/"
style={itemStyle}
>
Speedometer 3.0 - Browser Performance benchmarks by Apple, Google, Mozilla and Microsoft. Includes 4
charting libraries
</a>
<a href="https://www.typewolf.com/site-of-the-day/" style={itemStyle}>
Typewolf Site of the Day. Inspiration for font pairings
</a>
<a href="https://deadsimplesites.com/" style={itemStyle}>DSS Dead Simple Sites.</a>
<a href="https://www.csscade.com/" style={itemStyle}>CSS is awesome - CSSCade</a>
<a href="https://djr.com/job-clarendon" style={itemStyle}>
Job Clarendon is a stunning typeface that pays homage to job printing
</a>
<a href="https://courses.nan.fyi/login" style={itemStyle}>Interactive SVG Animations Course</a>
<a href="https://htmx.org/examples/active-search/" style={itemStyle}>HTMX Active Search</a>
<a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/" style={itemStyle}>
Search and highlight text
</a>
<a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/" style={itemStyle}>
Custom highlight API - Display style and script blocks
</a>
<a href="https://docs.val.town/quickstarts/first-website/" style={itemStyle}>
Building websites with Val.town is fast
</a>
<p>Notes: Asking ChatGPT to explain code is a great and fun way to learn</p>
</div>
</body>
</html>,
),
{
headers: {
"Content-Type": "text/html",
},
},
);
}
const itemStyle = {
padding: "10px",
color: "#222",
// backgroundColor: "#ff748d",
backgroundColor: "rgba(0, 0, 0, 0.02)",
background: "colorMix(in srgb, blue, white 80%)",
borderTop: "2px solid rgba(0, 0, 0, 0.20)",
borderRadius: "2px",
textDecoration: "none",
// boxShadow: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
};
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
/** @jsxImportSource https://esm.sh/react */
import { renderToString } from "npm:react-dom/server";
export default async function(req: Request) {
return new Response(
renderToString(
<html>
<head>
<meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tahir's TIL</title>
</head>
<body
style={{ padding: "30px", width: "300px", margin: "0 auto", fontFamily: "sans-serif", textAlign: "center" }}
>
<script>
console.log('This will run when the page loads');
// Other inline scripts here
</script>
<h1>Tahir's TIL</h1>
<p>Web, design and data tips</p>
<div style={{ display: "flex", flexDirection: "column", gap: "15px" }}>
<a href="https://ourworldindata.org/electric-car-sales" style={itemStyle}>
Over 90% of new cars are electric in Norway + Our World in Data make beautiful charts
</a>
<a
href="https://webkit.org/blog/15131/speedometer-3-0-the-best-way-yet-to-measure-browser-performance/"
style={itemStyle}
>
Speedometer 3.0 - Browser Performance benchmarks by Apple, Google, Mozilla and Microsoft. Includes 4
charting libraries
</a>
<a href="https://www.typewolf.com/site-of-the-day/" style={itemStyle}>
Typewolf Site of the Day. Inspiration for font pairings
</a>
<a href="https://deadsimplesites.com/" style={itemStyle}>DSS Dead Simple Sites.</a>
<a href="https://www.csscade.com/" style={itemStyle}>CSS is awesome - CSSCade</a>
<a href="https://djr.com/job-clarendon" style={itemStyle}>
Job Clarendon is a stunning typeface that pays homage to job printing
</a>
<a href="https://courses.nan.fyi/login" style={itemStyle}>Interactive SVG Animations Course</a>
<a href="https://htmx.org/examples/active-search/" style={itemStyle}>HTMX Active Search</a>
<a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/" style={itemStyle}>
Search and highlight text
</a>
<a href="https://www.bram.us/2024/02/18/custom-highlight-api-for-syntax-highlighting/" style={itemStyle}>
Custom highlight API - Display style and script blocks
</a>
<a href="https://docs.val.town/quickstarts/first-website/" style={itemStyle}>
Building websites with Val.town is fast
</a>
<p>Notes: Asking ChatGPT to explain code is a great and fun way to learn</p>
</div>
</body>
</html>,
),
{
headers: {
"Content-Type": "text/html",
},
},
);
}
const itemStyle = {
padding: "10px",
color: "#222",
// backgroundColor: "#ff748d",
backgroundColor: "rgba(0, 0, 0, 0.02)",
borderRadius: "2px",
textDecoration: "none",
// boxShadow: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
};

CronGPT

This is a minisite to help you create cron expressions, particularly for crons on Val Town. It was inspired by Cron Prompt, but also does the timezone conversion from wherever you are to UTC (typically the server timezone).

Tech

  • Hono for routing (GET / and POST /compile.)
  • Hono JSX
  • HTML (probably overcomplicates things; should remove)
  • @stevekrouse/openai, which is a light wrapper around @std/openai

TODO

  • simplify by removing HTMX (try doing the form as a GET request, manual JS script, or client side react)
  • make the timezone picker better (fewer options, searchable)
  • add a copy button?
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
/** @jsxImportSource https://esm.sh/react@18.2.0 */
import cronstrue from "https://esm.sh/cronstrue";
import React, { useState } from "https://esm.sh/react@18.2.0";
import { chat } from "https://esm.town/v/stevekrouse/openai?v=19";
import react_http from "https://esm.town/v/stevekrouse/react_http?v=6";
export default function(req: Request) {
const url = new URL(req.url);
if (req.method === "GET" && url.pathname === "/") {
return react_http({
component: App,
sourceURL: import.meta.url,
head: `<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com"></script>
<title>CronGPT</title>`,
});
} else if (req.method === "POST" && url.pathname === "/compile") {
return compile(req);
}
return new Response("Not found", { status: 404 });
}
export function App() {
const browserTZ = Intl.DateTimeFormat().resolvedOptions().timeZone;
const [loading, setLoading] = useState(false);
const [cron, setCron] = useState("0 16 * * 1-5");
const onSubmit: React.FormEventHandler<HTMLFormElement> = async function(e) {
e.preventDefault();
setLoading(true);
const cron = await fetch("/compile", {
method: "POST",
body: new FormData(e.target as HTMLFormElement),
}).then((r) => r.json());
setCron(cron);
setLoading(false);
};
return (
<div className="flex p-6 mt-4 flex-col space-y-12 max-w-2xl mx-auto">
<div className="flex flex-col text-center space-y-2">
<h1 className="text-3xl font-bold">CronGPT</h1>
<p className="text-lg">Generate cron expressions with ChatGPT</p>
</div>
<form className="flex flex-col space-y-4" onSubmit={onSubmit}>
<div className="flex flex-col">
<label>Natural language description</label>
<input
name="description"
defaultValue="On weekdays at noon"
required
className="border-2 rounded-lg p-2 text-lg text-center"
/>
</div>
<div className="flex flex-col">
<label>Timezone</label>
<select name="timezone" className=" border-2 rounded-lg text-lg p-2 text-center">
{Intl.supportedValuesOf("timeZone").map((tz) => <option value={tz} selected={tz === browserTZ}>{tz}
</option>)}
</select>
</div>
<button
className="bg-sky-500 hover:bg-sky-700 text-white font-bold py-2 px-4 rounded disabled:bg-gray-500"
disabled={loading}
>
{loading ? "Loading.." : "Compile"}
</button>
</form>
<Cron cron={cron} timezone="America/New_York" />
<div className="text-gray-500 text-center flex flex-col space-y-2">
<div>
Need a place to run cron jobs? Try <Link href="https://val.town">Val Town</Link>
</div>{" "}
<div>
<Link href="https://www.val.town/v/stevekrouse/cron">View source</Link>
{" | "} Inspired by <Link href="https://cronprompt.com/">Cron Prompt</Link>
</div>
</div>
</div>
);
}
const Link = ({ href, children }: { href: string; children?: any }) => (
<a href={href} className="text-blue-500 hover:text-blue-700" target="_blank" rel="noopener noreferrer">{children}</a>
);
function Cron({ cron, timezone }) {
let translation;
try {
translation = cronstrue.toString(cron, { tzOffset: getOffset(timezone), use24HourTimeFormat: false });
} catch (e) {
translation = "Translation error: " + e.message;
}
return (
<div id="cron" className="flex flex-col space-y-4">
<pre className="font-mono text-2xl flex flex-row justify-between w-full border-2 p-2 bg-gray-200">
<div></div>
<div className="flex">
<div className="font-bold select-all">{cron}</div>
</div>
<div className="text-gray-400 font-bold mr-2 select-none">UTC</div>

CronGPT

This is a minisite to help you create cron expressions, particularly for crons on Val Town. It was inspired by Cron Prompt, but also does the timezone conversion from wherever you are to UTC (typically the server timezone).

Tech

  • Hono for routing (GET / and POST /compile.)
  • Hono JSX
  • HTML (probably overcomplicates things; should remove)
  • @stevekrouse/openai, which is a light wrapper around @std/openai
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
/** @jsxImportSource npm:hono@3/jsx */
import { chat } from "https://esm.town/v/stevekrouse/openai";
import cronstrue from "npm:cronstrue";
import { Hono } from "npm:hono@3";
const app = new Hono();
export default app.fetch;
app.get("/", (c) =>
c.html(
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com" />
<title>CronGPT</title>
</head>
<body class="flex p-6 mt-4 flex-col space-y-12 max-w-2xl mx-auto">
<div class="flex flex-col text-center space-y-2">
<h1 class="text-3xl font-bold">CronGPT</h1>
<p class="text-lg">Generate cron expressions with ChatGPT</p>
</div>
<form class="flex flex-col space-y-4">
<div class="flex flex-col">
<label for="description">Natural language description</label>
<input
name="description"
value="On weekdays at noon"
required
class="border-2 rounded-lg p-2 text-lg text-center"
/>
</div>
<div class="flex flex-col">
<label for="description">Timezone</label>
<select name="timezone" class=" border-2 rounded-lg text-lg p-2 text-center">
{Intl.supportedValuesOf("timeZone").map((tz) => (
<option value={tz} id={tz.replace("/", "-")}>{tz}</option>
))}
</select>
</div>
<button class="bg-sky-500 hover:bg-sky-700 text-white font-bold py-2 px-4 rounded disabled:bg-gray-500">
Compile
</button>
</form>
<div>
<Cron cron="0 16 * * 1-5" timezone="America/New_York" />
</div>
<div class="text-gray-500 text-center flex flex-col space-y-2">
<div>
Need a place to run cron jobs? Try <Link href="https://val.town">Val Town</Link>
</div>{" "}
<div>
<Link href="https://www.val.town/v/stevekrouse/cron">View source</Link>
{" | "} Inspired by <Link href="https://cronprompt.com/">Cron Prompt</Link>
</div>
</div>
</body>
<script dangerouslySetInnerHTML={{ __html: `${clientScript}; clientScript()` }} />
</html>,
));
app.post("/compile", async (c) => {
const form = await c.req.formData();
const description = form.get("description") as string;
const timezone = form.get("timezone") as string;
const { content } = await chat([
{
role: "system",
content: `You are an natural language to cron syntax compiler.
Return a cron expression in UTC.
The user is in ${timezone}. The timezone offset is ${getOffset(timezone)}.
Return ONLY the cron expression.`,
},
{ role: "user", content: description },
], {
max_tokens: 10,
});
const cron = content.replace("`", "");
return c.html(<Cron cron={cron} timezone={timezone} />);
});
function clientScript() {
// Set the timezone input
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
(document.querySelector("#" + tz.replace("/", "-")) as HTMLOptionElement).selected = true;
const form = document.querySelector("form") as HTMLFormElement;
const button = document.querySelector("button") as HTMLButtonElement;
form.addEventListener("submit", async (e) => {
e.preventDefault();
button.disabled = true;
button.innerText = "Loading..";
const cron = await fetch("/compile", { method: "POST", body: new FormData(form) }).then((r) => r.text());
button.disabled = false;
button.innerText = "Compile";
(document.getElementById("cron") as HTMLDivElement).innerHTML = cron;
});
}
function Cron({ cron, timezone }) {
let translation;
try {

CronGPT

This is a minisite to help you create cron expressions, particularly for crons on Val Town. It was inspired by Cron Prompt, but also does the timezone conversion from wherever you are to UTC (typically the server timezone).

Tech

  • Hono for routing (GET / and POST /compile.)
  • Hono JSX
  • HTMX (probably overcomplicates things; should remove)
  • @stevekrouse/openai, which is a light wrapper around @std/openai

I'm finding HTMX a bit overpowered for this, so I have two experimental forks without it:

  1. Vanilla client-side JavaScript: @stevekrouse/cron_client_side_script_fork
  2. Client-side ReactJS (no SSR): @stevekrouse/cron_client_react_fork

I think (2) Client-side React without any SSR is the simplest architecture. Maybe will move to that.

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
/** @jsxImportSource npm:hono@3/jsx */
import { chat } from "https://esm.town/v/stevekrouse/openai";
import cronstrue from "npm:cronstrue";
import { Hono } from "npm:hono@3";
const css = `
.htmx-indicator{
display:none;
}
.htmx-request .htmx-indicator{
display:inline;
}
.htmx-request.htmx-indicator{
display:inline;
}`;
const app = new Hono();
export default app.fetch;
app.get("/", (c) =>
c.html(
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com" />
<script
src="https://unpkg.com/htmx.org@1.9.11"
integrity="sha384-0gxUXCCR8yv9FM2b+U3FDbsKthCI66oH5IA9fHppQq9DDMHuMauqq1ZHBpJxQ0J0"
crossorigin="anonymous"
>
</script>
<style dangerouslySetInnerHTML={{ __html: css }} />
<title>CronGPT</title>
</head>
<body class="flex p-6 mt-4 flex-col space-y-12 max-w-2xl mx-auto">
<div class="flex flex-col text-center space-y-2">
<h1 class="text-3xl font-bold">CronGPT</h1>
<p class="text-lg">Generate cron expressions with ChatGPT</p>
</div>
<form hx-post="/compile" hx-target="#cron" class="flex flex-col space-y-4" hx-disabled-elt="button">
<div class="flex flex-col">
<label for="description">Natural language description</label>
<input
name="description"
value="On weekdays at noon"
required
class="border-2 rounded-lg p-2 text-lg text-center"
/>
</div>
<div class="flex flex-col">
<label for="description">Timezone</label>
<select name="timezone" class=" border-2 rounded-lg text-lg p-2 text-center">
{Intl.supportedValuesOf("timeZone").map((tz) => (
<option value={tz} id={tz.replace("/", "-")}>{tz}</option>
))}
</select>
</div>
<script
dangerouslySetInnerHTML={{
__html: `
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone
document.querySelector("#" + tz.replace("/", "-")).selected = true
`,
}}
/>
<button class="bg-sky-500 hover:bg-sky-700 text-white font-bold py-2 px-4 rounded disabled:hidden">
Compile
</button>
<button class="htmx-indicator text-white font-bold py-2 px-4 rounded bg-gray-400" disabled>
Loading...
</button>
</form>
<div hx-swap-oob="true" id="cron">
<Cron cron="0 16 * * 1-5" timezone="America/New_York" />
</div>
<div class="text-gray-500 text-center flex flex-col space-y-2">
<div>
Need a place to run cron jobs? Try <Link href="https://val.town">Val Town</Link>
</div>{" "}
<div>
<Link href="https://www.val.town/v/stevekrouse/cron">View source</Link>
{" | "} Inspired by <Link href="https://cronprompt.com/">Cron Prompt</Link>
</div>
</div>
</body>
</html>,
));
app.post("/compile", async (c) => {
const form = await c.req.formData();
const description = form.get("description") as string;
const timezone = form.get("timezone") as string;
const { content } = await chat([
{
role: "system",
content: `You are an natural language to cron syntax compiler.
Return a cron expression in UTC.
The user is in ${timezone}. The timezone offset is ${getOffset(timezone)}.
Return ONLY the cron expression.`,
},
{ role: "user", content: description },

OpenAI ChatGPT helper function

This val uses your OpenAI token if you have one, and the @std/openai if not, so it provides limited OpenAI usage for free.

Create valimport { chat } from "https://esm.town/v/stevekrouse/openai"; const { content } = await chat("Hello, GPT!"); console.log(content);
Create valimport { chat } from "https://esm.town/v/stevekrouse/openai"; const { content } = await chat( [ { role: "system", content: "You are Alan Kay" }, { role: "user", content: "What is the real computer revolution?"} ], { max_tokens: 50, model: "gpt-4" } ); console.log(content);
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
import type { ChatCompletion, ChatCompletionCreateParamsNonStreaming, Message } from "npm:@types/openai";
async function getOpenAI() {
// if you don't have a key, use our std library version
if (Deno.env.get("OPENAI_API_KEY") === undefined) {
const { OpenAI } = await import("https://esm.town/v/std/openai");
return new OpenAI();
} else {
const { OpenAI } = await import("npm:openai");
return new OpenAI();
}
}
/**
* Initiates a chat conversation with OpenAI's GPT model and retrieves the content of the first response.
* This function can handle both single string inputs and arrays of message objects.
* It supports various GPT models, allowing for flexibility in choosing the model based on the application's needs.
*
* @param {string | Message[]} input - The input message(s) to send to GPT. Can be a single string or an array of message objects.
* @param {object} options - Additional options for the completion request.
* @returns {Promise<string>} A promise that resolves to the content of the first response from the completion.
*/
export async function chat(
input: string | Message[],
options?: Omit<ChatCompletionCreateParamsNonStreaming, "messages">,
): Promise<ChatCompletion & { content: string }> {
const openai = await getOpenAI();
const messages = Array.isArray(input) ? input : [{ role: "user", content: input }];
const createParams: ChatCompletionCreateParamsNonStreaming = {
max_tokens: 30,
model: "gpt-3.5-turbo",
...(options ?? {}),
messages,
};
const completion = await openai.chat.completions.create(createParams);
return { ...completion, content: completion.choices[0].message.content };
}
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
/** @jsxImportSource npm:hono@3/jsx */
import { chat } from "https://esm.town/v/stevekrouse/openai";
import cronstrue from "npm:cronstrue";
import { Hono } from "npm:hono@3";
const app = new Hono();
app.get("/", (c) =>
c.html(
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com" />
<script
src="https://unpkg.com/htmx.org@1.9.11"
integrity="sha384-0gxUXCCR8yv9FM2b+U3FDbsKthCI66oH5IA9fHppQq9DDMHuMauqq1ZHBpJxQ0J0"
crossorigin="anonymous"
>
</script>
<style
dangerouslySetInnerHTML={{
__html: `
.htmx-indicator{
display:none;
}
.htmx-request .htmx-indicator{
display:inline;
}
.htmx-request.htmx-indicator{
display:inline;
}
`,
}}
>
</style>
<title>Cron Compiler</title>
</head>
<body class="flex p-6 flex-col space-y-4 max-w-2xl mx-auto">
<div>
<h1 class="text-3xl">Cron Compiler</h1>
<p>Compile natural language to cron via ChatGPT</p>
</div>
<form hx-post="/compile" hx-target="#cron" class="flex space-x-2" hx-disabled-elt="button">
<input
name="description"
value="On weekdays at noon"
required
class="w-full border-2 rounded-lg p-2"
>
</input>
<input type="hidden" name="timezone" id="timezone"></input>
<script
dangerouslySetInnerHTML={{
__html: `document.querySelector('#timezone').value = Intl.DateTimeFormat().resolvedOptions().timeZone`,
}}
/>
<button class="bg-purple-500 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded disabled:hidden">
Compile
</button>
<button class="htmx-indicator text-white font-bold py-2 px-4 rounded bg-gray-400" disabled>
Loading...
</button>
</form>
<div hx-swap-oob="true" id="cron">
{renderCron("0 16 * * 1-5", "America/New_York")}
</div>
<div>
Inspired by <a href="https://cronprompt.com/" class="text-blue-500 hover:text-blue-700">Cron Prompt</a>.{" "}
<a href="https://www.val.town/v/stevekrouse/cron" class="text-blue-500 hover:text-blue-700">View source</a>.
</div>
</body>
</html>,
));
function renderCron(cron: string, timezone: string) {
return (
<div>
<pre class="font-mono text-xl text-center w-full border-2 p-2 bg-gray-200">{cron}</pre>
<p class="text-center">
{cronstrue.toString(cron, { tzOffset: getOffset(timezone), use24HourTimeFormat: false })}
, {shortenTimezoneString(timezone)}
</p>
</div>
);
}
// convert a timezone string like America/New_York to "EST"
function shortenTimezoneString(timeZone: string) {
const string = new Date().toLocaleString("en-US", { timeZone, timeZoneName: "short" });
return string.split(" ").at(-1);
}
// https://stackoverflow.com/a/68593283
const getOffset = (timeZone: string) => {
const now = new Date();
const utcDate = new Date(now.toLocaleString("en-US", { timeZone: "UTC" }));
const tzDate = new Date(now.toLocaleString("en-US", { timeZone }));
return (tzDate.getTime() - utcDate.getTime()) / (60 * 60 * 1000);
};

Code from https://deno.com/blog/build-image-resizing-api

Useful for compressing an image before sending to chatgpt4v, for example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { ImageMagick, initializeImageMagick, MagickGeometry } from "https://deno.land/x/imagemagick_deno@0.0.14/mod.ts";
export async function modifyImage(
file: File,
params: { width: number; height: number },
) {
const imageBuffer = new Uint8Array(await file.arrayBuffer());
const sizingData = new MagickGeometry(
params.width,
params.height,
);
sizingData.ignoreAspectRatio = params.height > 0 && params.width > 0;
return new Promise<File>((resolve) => {
ImageMagick.read(imageBuffer, (image) => {
image.resize(sizingData);
image.write((data) => resolve(new File([data], file.name, { type: file.type })));
});
});
}

Doesn't seem to work on val.town, should work in principle

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
/** @jsxImportSource npm:hono@3/jsx */
import { fetchText } from "https://esm.town/v/stevekrouse/fetchText";
import { chat } from "https://esm.town/v/stevekrouse/openai";
import cronstrue from "npm:cronstrue";
import { Hono } from "npm:hono@3";
const examples = [
{
user: "website that shows the current time",
content: `/** @jsxImportSource npm:react */
export default function() {
return <h1>{new Date().toLocaleTimeString()}</h1>;
}`,
},
{
user: `A collaborative poem builder.
It stores each line of the poem in sqlite.
It has a textbox that lets anyone input a new line to the poem.`,
content: await fetchText("https://esm.town/v/stevekrouse/poembuilder3?v=4"),
},
{
user: "an app that uses chatgpt to convert natural language to cron syntax",
content: await fetchText("https://esm.town/v/stevekrouse/cron2"),
},
];
const app = new Hono();
export default app.fetch;
app.get("/", async (c) => {
const example = examples[0] // examples[Math.floor(Math.random() * examples.length)];
const description = c.req.query("description") || example.user;
let code = c.req.query("description") ? await compile(description) : example.content;
return c.html(
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com" />
<title>Val Writer</title>
</head>
<body class="flex p-6 flex-col space-y-4 max-w-2xl mx-auto">
<div>
<h1 class="text-3xl">Val Writer</h1>
<p>Compile your prompt into code</p>
</div>
<form class="flex space-x-2" hx-disabled-elt="button">
<textarea
name="description"
required
class="w-full border-2 rounded-lg p-2"
rows={5}
>
{description}
</textarea>
<button class="bg-purple-500 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded disabled:hidden">
Write
</button>
</form>
<div>
<pre>
{code}
</pre>
</div>
</body>
</html>,
);
});
export async function compile(description: string) {
const messages = [
{
role: "system",
content: `You are an expert fullstack developer.
You convert user requests to code.
You write Deno TypeScript.
Reply ONLY with valid Typescript.
Export the fetch function to run the server.
Only use web standard fetch. Export the fetch function to start the server.
Add extensive comments`,
},
...examples.flatMap((example) => [, {
role: "user",
content: example.user,
}, {
role: "assistant",
content: example.content,
}]),
{ role: "user", content: description },
];
const { content } = await chat(messages, {
model: "gpt-4-turbo-2024-04-09",
max_tokens: 4000,
});
return content;
}
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
/** @jsxImportSource npm:hono@3/jsx */
import { chat } from "https://esm.town/v/stevekrouse/openai";
import cronstrue from "npm:cronstrue";
import { Hono } from "npm:hono@3";
const app = new Hono();
export default app.fetch;
app.get("/", async (c) => {
const description = c.req.query("description") || "On weekdays at noon";
const timezone = c.req.query("timezone") || "America/New_York";
const cron = c.req.query("description") ? await compile(description, timezone) : "0 16 * * 1-5";
let translated;
try {
translated = cronstrue.toString(cron, { tzOffset: getOffset(timezone), use24HourTimeFormat: false });
} catch (e)
{
translated = "Error in cron expression " + e.message;
}
return c.html(
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com" />
<title>Cron Compiler</title>
</head>
<body class="flex p-6 flex-col space-y-4 max-w-2xl mx-auto">
<div>
<h1 class="text-3xl">Cron Compiler</h1>
<p>Compile natural language to cron via ChatGPT</p>
</div>
<form class="flex space-x-2" hx-disabled-elt="button">
<input
name="description"
value={description}
required
class="w-full border-2 rounded-lg p-2"
>
</input>
<input type="hidden" name="timezone"></input>
<script
dangerouslySetInnerHTML={{ __html: `document.querySelector("input[name=timezone]").value = "${timezone}"` }}
/>
<button class="bg-purple-500 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded disabled:hidden">
Compile
</button>
</form>
<div>
<pre class="font-mono text-xl text-center w-full border-2 p-2 bg-gray-200">{cron}</pre>
<p class="text-center">
{translated}
, {shortenTimezoneString(timezone)}
</p>
</div>
<div>
Inspired by <a href="https://cronprompt.com/" class="text-blue-500 hover:text-blue-700">Cron Prompt</a>.{" "}
<a href="https://www.val.town/v/stevekrouse/cron" class="text-blue-500 hover:text-blue-700">View source</a>.
</div>
</body>
</html>,
);
});
// convert a timezone string like America/New_York to "EST"
function shortenTimezoneString(timeZone: string) {
const string = new Date().toLocaleString("en-US", { timeZone, timeZoneName: "short" });
return string.split(" ").at(-1);
}
// https://stackoverflow.com/a/68593283
const getOffset = (timeZone: string) => {
const now = new Date();
const utcDate = new Date(now.toLocaleString("en-US", { timeZone: "UTC" }));
const tzDate = new Date(now.toLocaleString("en-US", { timeZone }));
return (tzDate.getTime() - utcDate.getTime()) / (60 * 60 * 1000);
};
export async function compile(description: string, timezone: string) {
const { content } = await chat([
{
role: "system",
content:
"You are a helpful assistant that converts the user's prompt into a valid crontab expression. You only reply with valid crontab expressions.",
},
{ role: "user", content: "On Juneteenth every hour" },
{ role: "assistant", content: "0 * 19 6 * *" },
{ role: "user", content: "At midnight on christmas" },
{ role: "assistant", content: "0 0 25 12 * *" },
{ role: "user", content: prompt },
], {
max_tokens: 10,
});
return content.replace("`", "");
}

OpenAI ChatGPT helper function

This val uses your OpenAI token if you have one, and the @std/openai if not, so it provides limited OpenAI usage for free.

Create valimport { chat } from "https://esm.town/v/stevekrouse/openai"; const { content } = await chat("Hello, GPT!"); console.log(content);
Create valimport { chat } from "https://esm.town/v/stevekrouse/openai"; const { content } = await chat( [ { role: "system", content: "You are Alan Kay" }, { role: "user", content: "What is the real computer revolution?"} ], { max_tokens: 50, model: "gpt-4" } ); console.log(content);
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
import type { ChatCompletion, ChatCompletionCreateParamsNonStreaming, Message } from "npm:@types/openai";
async function getOpenAI() {
// if you don't have a key, use our std library version
if (Deno.env.get("OPENAI_API_KEY") === undefined) {
const { OpenAI } = await import("https://esm.town/v/std/openai");
return new OpenAI();
} else {
const { OpenAI } = await import("npm:openai");
return new OpenAI();
}
}
/**
* Initiates a chat conversation with OpenAI's GPT model and retrieves the content of the first response.
* This function can handle both single string inputs and arrays of message objects.
* It supports various GPT models, allowing for flexibility in choosing the model based on the application's needs.
*
* @param {string | Message[]} input - The input message(s) to send to GPT. Can be a single string or an array of message objects.
* @param {object} options - Additional options for the completion request.
* @returns {Promise<string>} A promise that resolves to the content of the first response from the completion.
*/
export async function chat(
input: string | Message[],
options?: Omit<ChatCompletionCreateParamsNonStreaming, "messages">,
): Promise<ChatCompletion & { content: string }> {
const openai = await getOpenAI();
const messages = Array.isArray(input) ? input : [{ role: "user", content: input }];
const createParams: ChatCompletionCreateParamsNonStreaming = {
max_tokens: 30,
model: "gpt-3.5-turbo",
...(options ?? {}),
messages,
};
const completion = await openai.chat.completions.create(createParams);
return { ...completion, content: completion.choices[0].message.content };
}

Add an email entry option to your static website/blog. Easy peasy. 🚀

newsletter.png

PoV: You just hacked together a portfolio website or launched a blog as a static website. Some people who visit might be interested in hearing more from you. ❤️ But you don't want to get lost building your backend, API, DB or fancy apps like SubstandardStack or MailMachineGun for people to sign up to your newsletter. 😩

All you want is a simple input box on your website - when someone types their email, username or social link in and submits it, you want to be notified.

psst...do you want another one that uses the DB instead of email so you can look up all entries at once? Let me know and I'll get cooking!

Quickstart

Call the val URL with data in the query param userContact . That's it!

// Format
`https://<val_url>?userContact=<mandatory_primary_contact>`

// Examples
`https://dvsj-subscribeToNewsletter.web.val.run?userContact=dav.is@zohomail.in`
`https://dvsj-subscribeToNewsletter.web.val.run?userContact=CatalanCabbage`

Bonus

Have extra data apart from email?

Pass any encoded data in the queryParam userData, will be included in the email. It's optional.

// Format
`https://<val_url>?userContact=<mandatory_primary_contact>&userData=<optional_any_data>`

//Examples
`https://dvsj-subscribeToNewsletter.web.val.run?userContact=dav.is@zohomail.in&userData={"time": "2/2/1969", "twitter": "https://twitter.com/dvsj_in"}`

// Note: All values should be URL encoded. Example:
let userData = {"time": "2/2/1969", "twitter": "https://twitter.com/dvsj_in"}
let encodedUserData = encodeURIComponent(userData) //This should go in the query param

Want bot protection?

Add a simple question to your website, like "okay, so what's one minus one?".
In the val, set isBotProtectionOn = true and botProtectionAnswer="0".
When you call the val, include the encoded user's answer to the bot question as botProtection query param.
Answer will be compared with botProtectionAnswer; if the answer is wrong, the request is rejected.

// Format
`https://<val_url>?userContact=<mandatory_primary_contact>&userData=<optional_any_data>&botProtection=<answer>`

//Examples
`https://dvsj-subscribeToNewsletter.web.val.run?userContact=dav.is@zohomail.in&botProtection=123`

Add it to your website

Want to add it to your site but get a headstart coding it? Use this ChatGPT prompt to get code for your website!

I'm building a simple form submission component. It should a submit button and these 2 input boxes: 
1. "userContact" to get the user's email (mandatory)
2. "userData" to get a custom message from the user (optional)

On clicking the submit button: 
1. Both input values should be encoded using "encodeURIComponent" 
2. A GET URL should be built in this format with query params. Include userData query param only if input is not null or empty.
`https://dvsj-subscribeToNewsletter.web.val.run?userContact=<encodedUserContact>&userData=<encodedUserData>`
3. The GET URL should be called and result printed in the console.

I'm using React, so make it a react component.
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
import { email } from "https://esm.town/v/std/email?v=11";
// You can turn this on if needed. Check the readme!
const isBotProtectionOn = false;
const botProtectionAnswer = "0";
export default async function(req: Request): Promise<Response> {
// Get the data from URL's query params
const url = new URL(req.url);
let botProtectionInput = url.searchParams.get("botProtection");
let userContact = url.searchParams.get("userContact");
let userData = url.searchParams.get("userData");
// Check if query params are valid
const areInputsValid = validateInputs(botProtectionInput, userContact);
if (!areInputsValid.isValid) {
return Response.json({
success: false,
msg: areInputsValid.errorMsg,
});
}
// And send email!
await sendEmail(userContact, userData);
return Response.json({ ok: true });
}
async function sendEmail(userContact, userData) {
userContact = decodeURIComponent(userContact);
// Email body should have userData part only if it's present in query params
let userDataMsg = "";
if (userData != null) {
userData = decodeURIComponent(userData);
userDataMsg = `User data was: ${userData} \n`;
}
// Yay!
const subject = `You've got a new subscriber ${userContact}! 🎉`;
const text = `User with contact ${userContact} has signed up to your newsletter. \n ${userDataMsg} Let's goo! 🥂`;
await email({ subject, text });
}
function validateInputs(botProtectionInput, userContact) {
let isValid = true;
let errorMsg = "";
// Validate bot protection input only if it's turned on
if (isBotProtectionOn) {
botProtectionInput = decodeURIComponent(botProtectionInput);
if (botProtectionInput == null || botProtectionInput != botProtectionAnswer) {
isValid = false;
errorMsg += "botProtection failed, expected *** but answer was " + botProtectionInput + ". Bad bot! ";
}
}
// userContact is mandatory
if (userContact == null || userContact.trim() == "") {
isValid = false;
errorMsg +=
"userContact is missing. It should be a queryParam in the URL, like https://<val_url>?userContact=<user_contact> where user_contact is email, username, social link etc. Eg: https://<val_url>?userContact=dav.is@zohomail.in";
}
return { isValid, errorMsg };
}

OpenAI ChatGPT helper function

This val uses your OpenAI token if you have one, and the @std/openai if not, so it provides limited OpenAI usage for free.

Create valimport { chat } from "https://esm.town/v/stevekrouse/openai"; const { content } = await chat("Hello, GPT!"); console.log(content);
Create valimport { chat } from "https://esm.town/v/stevekrouse/openai"; const { content } = await chat( [ { role: "system", content: "You are Alan Kay" }, { role: "user", content: "What is the real computer revolution?"} ], { max_tokens: 50, model: "gpt-4o" } ); console.log(content);
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
import type { ChatCompletion, ChatCompletionCreateParamsNonStreaming, Message } from "npm:@types/openai";
async function getOpenAI() {
// if you don't have a key, use our std library version
if (Deno.env.get("OPENAI_API_KEY") === undefined) {
const { OpenAI } = await import("https://esm.town/v/std/openai");
return new OpenAI();
} else {
const { OpenAI } = await import("npm:openai");
return new OpenAI();
}
}
/**
* Initiates a chat conversation with OpenAI's GPT model and retrieves the content of the first response.
* This function can handle both single string inputs and arrays of message objects.
* It supports various GPT models, allowing for flexibility in choosing the model based on the application's needs.
*
* @param {string | Message[]} input - The input message(s) to send to GPT. Can be a single string or an array of message objects.
* @param {object} options - Additional options for the completion request.
* @returns {Promise<string>} A promise that resolves to the content of the first response from the completion.
*/
export async function chat(
input: string | Message[],
options?: Omit<ChatCompletionCreateParamsNonStreaming, "messages">,
): Promise<ChatCompletion & { content: string }> {
const openai = await getOpenAI();
const messages = Array.isArray(input) ? input : [{ role: "user", content: input }];
const createParams: ChatCompletionCreateParamsNonStreaming = {
max_tokens: 30,
model: "gpt-3.5-turbo",
...(options ?? {}),
messages,
};
const completion = await openai.chat.completions.create(createParams);
return { ...completion, content: completion.choices[0].message.content };
}