Skip to content

Commit c9c7434

Browse files
authored
Luftbnb bookings and playlist dreams (#111)
* add very simple bookings to luftbnb * playlists for trips being suggested as dream * terrible hack to open dream in new tab
1 parent e25b57b commit c9c7434

File tree

6 files changed

+175
-49
lines changed

6 files changed

+175
-49
lines changed

typescript/packages/lookslike-high-level/src/data.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import { luftBnBSearch } from "./recipes/luft-bnb-search.js";
99
import { ticket } from "./recipes/ticket.js";
1010
import { routine } from "./recipes/routine.js";
1111

12-
import "./recipes/todo-list-as-task.js"; // Necessary, so that suggestions are indexed.
12+
// Necessary, so that suggestions are indexed.
13+
import "./recipes/todo-list-as-task.js";
14+
import "./recipes/playlist.js";
1315

1416
export const dataGems = state<Gem[]>([]);
1517

@@ -30,6 +32,7 @@ addGems([
3032
}),
3133
ticket({
3234
title: "Reservation for 'Counterstrike the Musical'",
35+
show: "Counterstrike the Musical",
3336
date: "2021-07-07",
3437
location: "New York",
3538
}),
@@ -85,3 +88,10 @@ function getFridayAndMondayDateStrings() {
8588
endDate: formatDate(followingMonday),
8689
};
8790
}
91+
92+
// Terrible hack to open a saga from a recipe
93+
let openSagaOpener: (saga: Gem) => void = () => {};
94+
export const openSaga = (saga: Gem) => openSagaOpener(saga);
95+
openSaga.set = (opener: (saga: Gem) => void) => {
96+
openSagaOpener = opener;
97+
};
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
export { components } from "@commontools/common-ui";
22
import { CommonWindowManager } from "./components/window-manager.js";
33
export { components as myComponents } from "./components.js";
4-
import { dataGems, recipes } from "./data.js";
4+
import { dataGems, recipes, openSaga } from "./data.js";
55
import { home } from "./recipes/home.js";
66

77
document.addEventListener("DOMContentLoaded", () => {
88
const windowManager = document.getElementById(
9-
"window-manager",
9+
"window-manager"
1010
)! as CommonWindowManager;
11+
openSaga.set(windowManager.openSaga.bind(windowManager));
1112
windowManager.openSaga(home({ sagas: dataGems, recipes }));
1213
});

typescript/packages/lookslike-high-level/src/recipes/annotation.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { tags } from "@commontools/common-ui";
22
import { signal, stream } from "@commontools/common-frp";
3-
import { dataGems } from "../data.js";
3+
import { dataGems, openSaga } from "../data.js";
44
import {
55
recipe,
66
Recipe,
@@ -13,7 +13,7 @@ import {
1313
} from "../recipe.js";
1414
import { effect } from "@commontools/common-frp/signal";
1515
import { suggestionClient } from "../llm-client.js";
16-
const { include } = tags;
16+
const { include, div } = tags;
1717
const { state, computed, isSignal } = signal;
1818
const { subject } = stream;
1919

@@ -63,14 +63,19 @@ export const annotation = recipe(
6363
});
6464

6565
const UI = computed(
66-
[suggestion, acceptedSuggestion],
67-
(suggestion, acceptedSuggestion) => {
66+
[suggestion, acceptedSuggestion, target],
67+
(suggestion, acceptedSuggestion, target) => {
6868
if (acceptedSuggestion) {
6969
const acceptedRecipe = acceptedSuggestion.recipe;
7070
const accepted = acceptedRecipe({
7171
...data,
7272
...acceptedSuggestion.boundGems,
7373
});
74+
// HACK: -1 is home screen and so let's open a new tab
75+
if (target === -1) {
76+
openSaga(accepted);
77+
return div({});
78+
}
7479
return include({ content: accepted.UI });
7580
} else if (suggestion) {
7681
return tags.suggestions({

typescript/packages/lookslike-high-level/src/recipes/home.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { signal } from "@commontools/common-frp";
33
import { recipe, Gem, ID } from "../recipe.js";
44
import { sagaLink } from "../components/saga-link.js";
55
import { recipeLink } from "../components/recipe-link.js";
6+
import { annotation } from "../components/annotation.js";
67
const { binding, repeat } = view;
78
const { vstack } = tags;
89

@@ -34,6 +35,11 @@ export const home = recipe("home screen", ({ sagas, recipes }) => {
3435
{},
3536
repeat(recipesWithIDs, recipeLink({ recipe: binding("recipe") }))
3637
),
38+
annotation({
39+
query: "dream fun things to explore",
40+
target: -1,
41+
data: { sagas, recipes },
42+
}),
3743
]),
3844
};
3945
});

typescript/packages/lookslike-high-level/src/recipes/luft-bnb-search.ts

Lines changed: 71 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { signal, stream, Sendable } from "@commontools/common-frp";
33
import { generateData } from "@commontools/llm-client";
44
import { Gem, recipe, NAME, addSuggestion, description } from "../recipe.js";
55
import { sagaLink } from "../components/saga-link.js";
6+
import { addGems } from "../data.js";
67
import { mockResultClient } from "../llm-client.js";
78
const { binding, repeat } = view;
89
const { vstack, hstack, div, commonInput, button, input, include } = tags;
@@ -55,17 +56,21 @@ export const luftBnBSearch = recipe(
5556
);
5657

5758
const book = subject<{ id: string }>();
59+
const booking = state<Gem | undefined>(undefined);
5860
book.sink({
59-
send: ({ id }) =>
60-
console.log(
61-
"Booked",
62-
places.get().find((place) => place.id === id)
63-
),
61+
send: ({ id }) => {
62+
const place = places.get().find((place) => place.id === id);
63+
const newBooking = luftBnBBooking({ place, startDate, endDate });
64+
addGems([newBooking]);
65+
booking.send(newBooking);
66+
console.log("Booked", place);
67+
},
6468
});
6569

6670
const summaryUI = computed(
67-
[places, startDate, endDate],
68-
(places: LuftBnBPlace[], startDate, endDate) => {
71+
[booking, places, startDate, endDate],
72+
(booking, places: LuftBnBPlace[], startDate, endDate) => {
73+
if (booking) return booking.UI;
6974
if (!places.length) return div({}, ["Searching..."]);
7075
const place = places[0];
7176
return vstack({}, [
@@ -79,47 +84,55 @@ export const luftBnBSearch = recipe(
7984
);
8085

8186
return {
82-
UI: vstack({}, [
83-
hstack({}, [
84-
input({
85-
type: "date",
86-
value: startDate,
87-
placeholder: "Type of place",
88-
"@common-input#value": startDate,
89-
}),
90-
input({
91-
type: "date",
92-
value: endDate,
93-
placeholder: "Type of place",
94-
"@common-input#value": endDate,
95-
}),
96-
]),
97-
commonInput({
98-
value: location,
99-
placeholder: "Location",
100-
"@common-input#value": location,
101-
}),
102-
button({ "@click": search }, ["Search"]),
103-
vstack(
104-
{},
105-
repeat(
106-
summaries,
107-
vstack({}, [
108-
div({}, binding("name")),
109-
div({}, binding("description")),
110-
div({}, binding("location")),
111-
div({}, binding("rating")),
112-
include({ content: binding("annotationUI") }),
113-
button({ "@click": book, id: binding("id") }, binding("bookFor")),
87+
UI: computed([booking], (booking) =>
88+
booking
89+
? include({ content: booking.UI })
90+
: vstack({}, [
91+
hstack({}, [
92+
input({
93+
type: "date",
94+
value: startDate,
95+
placeholder: "Type of place",
96+
"@common-input#value": startDate,
97+
}),
98+
input({
99+
type: "date",
100+
value: endDate,
101+
placeholder: "Type of place",
102+
"@common-input#value": endDate,
103+
}),
104+
]),
105+
commonInput({
106+
value: location,
107+
placeholder: "Location",
108+
"@common-input#value": location,
109+
}),
110+
button({ "@click": search }, ["Search"]),
111+
vstack(
112+
{},
113+
repeat(
114+
summaries,
115+
vstack({}, [
116+
div({}, binding("name")),
117+
div({}, binding("description")),
118+
div({}, binding("location")),
119+
div({}, binding("rating")),
120+
include({ content: binding("annotationUI") }),
121+
button(
122+
{ "@click": book, id: binding("id") },
123+
binding("bookFor")
124+
),
125+
])
126+
)
127+
),
114128
])
115-
)
116-
),
117-
]),
129+
),
118130
summaryUI: summaryUI,
119131
startDate,
120132
endDate,
121133
location,
122134
places,
135+
booking,
123136
[NAME]: computed(
124137
[location, startDate, endDate],
125138
(location, startDate: string, endDate: string) =>
@@ -129,6 +142,22 @@ export const luftBnBSearch = recipe(
129142
}
130143
);
131144

145+
export const luftBnBBooking = recipe(
146+
"booking",
147+
({ place, startDate, endDate }) => {
148+
const text = computed(
149+
[place, startDate, endDate],
150+
(place: LuftBnBPlace, startDate, endDate) =>
151+
`Booked ${place.title} LuftBnB from ${startDate} to ${endDate} for $${place.pricePerNight} per night`
152+
);
153+
const name = computed(
154+
[place],
155+
(place: LuftBnBPlace) => `Booking for LuftBnB in ${place.location}`
156+
);
157+
return { UI: div({}, text), [NAME]: name, place, startDate, endDate };
158+
}
159+
);
160+
132161
async function performLuftBnBSearch(location: string): Promise<LuftBnBPlace[]> {
133162
if (!location) return [];
134163
const result = (await generateData(
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { view, tags } from "@commontools/common-ui";
2+
import { state, computed, effect } from "@commontools/common-frp/signal";
3+
import { generateData } from "@commontools/llm-client";
4+
import { mockResultClient } from "../llm-client.js";
5+
import { recipe, Gem, NAME, addSuggestion, description } from "../recipe.js";
6+
const { repeat } = view;
7+
const { vstack, hstack, div, commonInput, button, input, include } = tags;
8+
9+
interface Playlist {
10+
title: string;
11+
songs: string[];
12+
}
13+
14+
export const playlistForTrip = recipe(
15+
"playlist for trip",
16+
({ ticket, booking }) => {
17+
const playlist = state<Playlist>({ title: "", songs: [] });
18+
const name = computed([playlist, ticket], (playlist, ticket: Gem) =>
19+
playlist.title
20+
? playlist.title
21+
: `Creating playlist for ${ticket.show.get()}`
22+
);
23+
24+
effect([ticket, booking], (ticket: Gem, booking: Gem) => {
25+
if (!ticket || !booking) return;
26+
const result = generateData(
27+
mockResultClient,
28+
`Create a playlist in anticipation of a trip to see ${ticket.show.get()}`,
29+
{},
30+
{
31+
type: "object",
32+
properties: {
33+
title: {
34+
type: "string",
35+
title: "Title of the playlist",
36+
},
37+
songs: {
38+
type: "array",
39+
title: "Songs",
40+
items: {
41+
type: "string",
42+
},
43+
description: "10 songs to listen to on the way to the show",
44+
},
45+
},
46+
}
47+
);
48+
49+
result.then((data) => playlist.send(data as Playlist));
50+
});
51+
return {
52+
UI: computed([playlist], (playlist: Playlist) =>
53+
vstack({}, [
54+
playlist.title,
55+
vstack(
56+
{},
57+
repeat(state(playlist.songs), (song: string) => div({}, [song]))
58+
),
59+
])
60+
),
61+
[NAME]: name,
62+
playlist,
63+
};
64+
}
65+
);
66+
67+
addSuggestion({
68+
description: description`Make a playlist for ${"ticket"}`,
69+
recipe: playlistForTrip,
70+
bindings: { sagas: "sagas" },
71+
dataGems: {
72+
ticket: "ticket",
73+
booking: "booking",
74+
},
75+
});

0 commit comments

Comments
 (0)