Skip to content

Commit e204bd4

Browse files
authored
Ellyse/webread pattern (#1825)
* uses web-read api to get website data * simplify by remove interface and inlining, also removed typing as Cell in lift so that we dont have to call get() * run map on the pizza list to display them individually * added jsdocs on what this pattern tries to show * parse out the date for pizzas and display them * parse out date and pizza individually * removed unneeded index for map
1 parent 2144f25 commit e204bd4

File tree

1 file changed

+145
-0
lines changed

1 file changed

+145
-0
lines changed

packages/patterns/cheeseboard.tsx

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/// <cts-enable />
2+
import {
3+
Cell,
4+
cell,
5+
Default,
6+
derive,
7+
fetchData,
8+
h,
9+
handler,
10+
ifElse,
11+
lift,
12+
NAME,
13+
recipe,
14+
UI,
15+
} from "commontools";
16+
17+
/**
18+
* Fetch the Cheeseboard pizza schedule via Toolshed's web-read endpoint and
19+
* display a list of pizza descriptions inside the charm.
20+
*
21+
* Uses: fetchData, lift, map built-in, toolshed web-read endpoint
22+
*/
23+
const DATE_LINE_REGEX = /^[A-Z][a-z]{2}\s+[A-Z][a-z]{2}\s+\d{1,2}$/;
24+
25+
type CheeseboardEntry = [date: string, pizza: string];
26+
27+
/** Extract pizza descriptions from a web-read content blob. */
28+
function extractPizzas(content: string): CheeseboardEntry[] {
29+
const normalized = content.replace(/\r\n/g, "\n");
30+
const lines = normalized.split("\n");
31+
const pizzas: CheeseboardEntry[] = [];
32+
33+
for (let i = 0; i < lines.length; i++) {
34+
const dateLine = lines[i].trim();
35+
if (!DATE_LINE_REGEX.test(dateLine)) {
36+
continue;
37+
}
38+
39+
let cursor = i + 1;
40+
while (cursor < lines.length && lines[cursor].trim() === "") {
41+
cursor++;
42+
}
43+
44+
if (lines[cursor]?.trim() !== "### Pizza") {
45+
continue;
46+
}
47+
48+
cursor++;
49+
while (cursor < lines.length && lines[cursor].trim() === "") {
50+
cursor++;
51+
}
52+
53+
const descriptionLines: string[] = [];
54+
for (; cursor < lines.length; cursor++) {
55+
const current = lines[cursor].trim();
56+
if (
57+
current === "" ||
58+
current.startsWith("### ") ||
59+
DATE_LINE_REGEX.test(current)
60+
) {
61+
break;
62+
}
63+
descriptionLines.push(current);
64+
}
65+
66+
if (descriptionLines.length > 0) {
67+
pizzas.push([
68+
dateLine,
69+
descriptionLines.join(" "),
70+
]);
71+
}
72+
}
73+
74+
return pizzas;
75+
}
76+
77+
/** Shape of the Toolshed web-read response we care about. */
78+
type WebReadResult = {
79+
content: string;
80+
metadata: {
81+
title?: string;
82+
author?: string;
83+
date?: string;
84+
word_count: number;
85+
};
86+
};
87+
88+
/** Reactive system will call this lift when the fetched data
89+
is updated. it also allows us to call our pure function
90+
`extractPizzas` and return the results
91+
*/
92+
const createPizzaListCell = lift<{ result: WebReadResult }, CheeseboardEntry[]>(
93+
({ result }) => {
94+
return extractPizzas(result?.content ?? "");
95+
},
96+
);
97+
98+
export default recipe("Cheeseboard", () => {
99+
const cheeseBoardUrl =
100+
"https://cheeseboardcollective.coop/home/pizza/pizza-schedule/";
101+
const { result, pending, error } = fetchData<WebReadResult>({
102+
url: "/api/agent-tools/web-read",
103+
mode: "json",
104+
options: {
105+
method: "POST",
106+
headers: {
107+
"Content-Type": "application/json",
108+
},
109+
body: {
110+
url: cheeseBoardUrl,
111+
max_tokens: 4000,
112+
},
113+
},
114+
});
115+
116+
const pizzaList = createPizzaListCell({ result });
117+
118+
return {
119+
[NAME]: "Cheeseboard",
120+
[UI]: (
121+
<div>
122+
<h2>Cheeseboard</h2>
123+
<p>
124+
<a
125+
href={cheeseBoardUrl}
126+
target="_blank"
127+
rel="noopener noreferrer"
128+
>
129+
{cheeseBoardUrl}
130+
</a>
131+
</p>
132+
<div>
133+
<h3>Pizza list</h3>
134+
<ul>
135+
{pizzaList.map(([date, pizza]) => (
136+
<li>
137+
{date}: {pizza}
138+
</li>
139+
))}
140+
</ul>
141+
</div>
142+
</div>
143+
),
144+
};
145+
});

0 commit comments

Comments
 (0)