Back to packages list

Vals using cronstrue

Description from the NPM package:
Convert cron expressions into human readable descriptions

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 {
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);
};

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
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-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("`", "");
}

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 },
1
Next