@@ -4,27 +4,52 @@ import {
44 lift ,
55 handler ,
66 str ,
7- generateData ,
7+ generateText ,
88 UI ,
99 NAME ,
1010} from "@commontools/common-builder" ;
1111import { addSuggestion , description } from "../suggestions.js" ;
1212import { 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 ( / ` ` ` j s o n \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
2954const 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
141108export 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
256220const 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+
301276const 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 ( / ` ` ` j s o n \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