Skip to content

Commit a0a3305

Browse files
committed
luftbnb almost working again
1 parent 902c252 commit a0a3305

File tree

1 file changed

+101
-125
lines changed

1 file changed

+101
-125
lines changed

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

Lines changed: 101 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,52 @@ import {
44
lift,
55
handler,
66
str,
7-
generateData,
7+
generateText,
88
UI,
99
NAME,
1010
} from "@commontools/common-builder";
1111
import { addSuggestion, description } from "../suggestions.js";
1212
import { launch, ID } from "../data.js";
13+
import { z } from 'zod';
14+
import zodToJsonSchema from 'zod-to-json-schema';
1315

14-
export interface LuftBnBPlace {
15-
// Schema for a place
16-
id: string;
17-
title: string;
18-
host: string;
19-
location: string;
20-
propertyType: "Apartment" | "House" | "Room";
21-
pricePerNight: number;
22-
numberOfGuests: number;
23-
latitude: number;
24-
longitude: number;
25-
rating: number;
26-
annotationUI: any;
27-
}
16+
const LuftBnBPlace = z.object({
17+
id: z.string().describe('Unique identifier for the listing'),
18+
title: z.string().describe('Title of the listing'),
19+
host: z.string().describe('Host of the listing'),
20+
location: z.string().describe('Street corner, Neighborhood and city of the listing'),
21+
propertyType: z.enum(['Apartment', 'House', 'Room']),
22+
pricePerNight: z.number().min(0),
23+
numberOfGuests: z.number().int().min(1),
24+
latitude: z.number(),
25+
longitude: z.number(),
26+
rating: z.number().min(0).max(5).describe('Average rating of the listing'),
27+
annotationUI: z.string().describe('empty string - do not add anything here'),
28+
});
29+
30+
type LuftBnBPlace = z.infer<typeof LuftBnBPlace>;
31+
32+
const LuftBnBPlaces = z.array(LuftBnBPlace);
33+
34+
const jsonSchema = JSON.stringify(zodToJsonSchema(LuftBnBPlaces), null, 2);
35+
36+
const grabPlaces = lift<{ result: string }, LuftBnBPlace[]>(({ result }) => {
37+
if (!result) {
38+
return [];
39+
}
40+
const jsonMatch = result.match(/```json\n([\s\S]+?)```/);
41+
if (!jsonMatch) {
42+
console.error("No JSON found in text:", result);
43+
return [];
44+
}
45+
let rawData = JSON.parse(jsonMatch[1]);
46+
let parsedData = z.array(LuftBnBPlace).safeParse(rawData);
47+
if (!parsedData.success) {
48+
console.error("Invalid JSON:", parsedData.error);
49+
return [];
50+
}
51+
return parsedData.data;
52+
});
2853

2954
const copy = lift(({ value }: { value: string }) => value);
3055

@@ -75,67 +100,9 @@ const makeBooking = handler<
75100
});
76101
});
77102

78-
const buildQuery = lift(({ location }) => ({
79-
messages: [`generate 10 places for private home short-term rentals in ${location}`, '```json\n'],
80-
result: [],
81-
schema: {
82-
type: "array",
83-
items: {
84-
type: "object",
85-
properties: {
86-
id: {
87-
type: "string",
88-
description: "Unique identifier for the listing",
89-
},
90-
title: {
91-
type: "string",
92-
description: "Title of the listing",
93-
},
94-
host: {
95-
type: "string",
96-
description: "Host of the listing",
97-
},
98-
location: {
99-
type: "string",
100-
description: "Street corner, Neighborhood and city of the listing",
101-
},
102-
propertyType: {
103-
type: "string",
104-
enum: ["Apartment", "House", "Room"],
105-
},
106-
pricePerNight: {
107-
type: "number",
108-
minimum: 0,
109-
},
110-
numberOfGuests: {
111-
type: "integer",
112-
minimum: 1,
113-
},
114-
latitude: {
115-
type: "number",
116-
},
117-
longitude: {
118-
type: "number",
119-
},
120-
rating: {
121-
type: "number",
122-
minimum: 0,
123-
maximum: 5,
124-
description: "Average rating of the listing",
125-
},
126-
},
127-
required: [
128-
"id",
129-
"title",
130-
"host",
131-
"location",
132-
"propertyType",
133-
"pricePerNight",
134-
"numberOfGuests",
135-
"imageUrl",
136-
],
137-
},
138-
},
103+
const buildQuery = lift(({ location, startDate, endDate }) => ({
104+
messages: [`generate 10 places for private home short-term rentals in ${location} between ${startDate} and ${endDate}`, '```json\n['],
105+
system: `Generate a list of places in json format\n\n<schema>${jsonSchema}</schema>`,
139106
}));
140107

141108
export const luftBnBSearch = recipe<{
@@ -158,9 +125,7 @@ export const luftBnBSearch = recipe<{
158125
const endDateUI = copy({ value: endDate });
159126
const locationUI = copy({ value: location });
160127

161-
const { result: places } = generateData<LuftBnBPlace[]>(
162-
buildQuery({ location })
163-
);
128+
const places = grabPlaces(generateText(buildQuery({ location, startDate, endDate })));
164129

165130
return {
166131
[UI]: html`
@@ -186,18 +151,18 @@ export const luftBnBSearch = recipe<{
186151
></common-input>
187152
<common-button
188153
onclick=${handleSearchClick({
189-
startDate,
190-
endDate,
191-
location,
192-
startDateUI,
193-
endDateUI,
194-
locationUI,
195-
})}
154+
startDate,
155+
endDate,
156+
location,
157+
startDateUI,
158+
endDateUI,
159+
locationUI,
160+
})}
196161
>Search</common-button
197162
>
198163
<common-vstack gap="md">
199164
${places.map(
200-
(place) => html`
165+
(place) => html`
201166
<common-vstack gap="xs">
202167
<div>${place.title}</div>
203168
<div>
@@ -212,9 +177,8 @@ export const luftBnBSearch = recipe<{
212177
Book for $${place.pricePerNight} per night
213178
</common-button>
214179
${place.annotationUI}
215-
</common-vstack>
216-
`
217-
)}
180+
</common-vstack>`
181+
)}
218182
</common-vstack>
219183
</common-vstack>
220184
`,
@@ -255,11 +219,9 @@ const computeBookingDatesFromEvent = lift(({ date }) => {
255219

256220
const describeFirstResult = lift(({ places, startDate, endDate }) => {
257221
return places && places.length
258-
? `${places[0].propertyType} ${startDate}-${endDate} in ${
259-
places[0].location
260-
}. ${"⭐".repeat(Math.round(places[0].rating))} (${places[0].rating}). $${
261-
places[0].pricePerNight
262-
} per night`
222+
? `${places[0].propertyType} ${startDate}-${endDate} in ${places[0].location
223+
}. ${"⭐".repeat(Math.round(places[0].rating))} (${places[0].rating}). $${places[0].pricePerNight
224+
} per night`
263225
: "Searching...";
264226
});
265227

@@ -298,42 +260,57 @@ addSuggestion({
298260
},
299261
});
300262

263+
264+
const NearbyPlace = z.object({
265+
id: z.string().describe('Unique identifier for the listing'),
266+
name: z.string().describe('Name of the place'),
267+
location: z.string().describe(`Street corner, Neighborhood and city`),
268+
walkingDistance: z.number().describe('Walking distance in minutes'),
269+
});
270+
271+
type NearbyPlace = z.infer<typeof NearbyPlace>;
272+
273+
const NearbyPlaces = z.array(NearbyPlace);
274+
type NearbyPlaces = z.infer<typeof NearbyPlace>;
275+
301276
const generateNearbyPlaceQuery = lift(({ routine, places }) => {
302277
const locationType = routine.locations[0] ?? "coffee shop";
303278

304279
const initialData = places.map((place: LuftBnBPlace) => ({
305280
location: place.location,
306281
}));
307282

308-
return {
309-
messages: [`generate ${initialData.length} ${locationType} with pun names`, '```json\n'],
310-
initialData,
311-
schema: {
312-
type: "array",
313-
items: {
314-
type: "object",
315-
properties: {
316-
id: {
317-
type: "string",
318-
description: "Unique identifier for the listing",
319-
},
320-
name: {
321-
type: "string",
322-
description: `Name of the ${locationType}`,
323-
},
324-
location: {
325-
type: "string",
326-
description:
327-
"Street corner, Neighborhood and city of the ${locationType}",
328-
},
329-
walkingDistance: {
330-
type: "number",
331-
description: "Walking distance in minutes",
332-
},
333-
},
334-
},
335-
},
283+
284+
const jsonSchema = JSON.stringify(zodToJsonSchema(NearbyPlaces), null, 2);
285+
286+
let r = {
287+
messages: [`generate ${initialData.length} ${locationType} with pun names`,
288+
'```json\n['],
289+
system: `Generate a list of ${locationType} places in json format\n\n<schema>${jsonSchema}</schema>`,
290+
stop: '```',
336291
};
292+
console.log(JSON.stringify(r, null, 2));
293+
return r;
294+
});
295+
296+
// FIXME(ja): validate that the recommendations work here...
297+
const grabNearbyPlaces = lift<{ result: string }, NearbyPlaces>(({ result }) => {
298+
if (!result) {
299+
return [];
300+
}
301+
const jsonMatch = result.match(/```json\n([\s\S]+?)```/);
302+
if (!jsonMatch) {
303+
console.error("No JSON found in text:", result);
304+
return [];
305+
}
306+
307+
let rawData = JSON.parse(jsonMatch[1]);
308+
let parsedData = z.array(NearbyPlace).safeParse(rawData);
309+
if (!parsedData.success) {
310+
console.error("Invalid JSON:", parsedData.error);
311+
return [];
312+
}
313+
return parsedData.data;
337314
});
338315

339316
// NOTE: This writes results into `places`
@@ -352,9 +329,8 @@ const nearbyPlacesForRoutine = recipe<{
352329
routine: { locations: string[] };
353330
places: LuftBnBPlace[];
354331
}>("annotate places for routine", ({ routine, places }) => {
355-
const query = generateNearbyPlaceQuery({ routine, places });
356332

357-
const { result: nearbyPlaces } = generateData(query);
333+
const nearbyPlaces = grabNearbyPlaces(generateText(generateNearbyPlaceQuery({ routine, places })));
358334

359335
annotatePlacesWithNearbyPlaces({ nearbyPlaces, places });
360336

0 commit comments

Comments
 (0)