Skip to content

Commit 6a1b0c1

Browse files
authored
Add hotel search tool. (#300)
1 parent 2b5ae6d commit 6a1b0c1

File tree

2 files changed

+247
-0
lines changed

2 files changed

+247
-0
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright 2025 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:dart_schema_builder/dart_schema_builder.dart';
6+
import 'package:flutter_genui/flutter_genui.dart';
7+
8+
class HotelSearchResult {
9+
final List<HotelListing> listings;
10+
11+
HotelSearchResult({required this.listings});
12+
13+
static HotelSearchResult fromJson(JsonMap json) {
14+
return HotelSearchResult(
15+
listings: (json['listings'] as List)
16+
.map((e) => HotelListing.fromJson(e as JsonMap))
17+
.toList(),
18+
);
19+
}
20+
21+
JsonMap toJson() {
22+
return {'listings': listings.map((e) => e.toJson()).toList()};
23+
}
24+
}
25+
26+
class HotelListing {
27+
final String name;
28+
final String location;
29+
final double pricePerNight;
30+
final List<String> images;
31+
final String listingId;
32+
33+
HotelListing({
34+
required this.name,
35+
required this.location,
36+
required this.pricePerNight,
37+
required this.listingId,
38+
required this.images,
39+
});
40+
41+
static HotelListing fromJson(JsonMap json) {
42+
return HotelListing(
43+
name: json['name'] as String,
44+
location: json['location'] as String,
45+
pricePerNight: (json['pricePerNight'] as num).toDouble(),
46+
images: List<String>.from(json['images'] as List),
47+
listingId: json['listingId'] as String,
48+
);
49+
}
50+
51+
JsonMap toJson() {
52+
return {
53+
'name': name,
54+
'location': location,
55+
'pricePerNight': pricePerNight,
56+
'images': images,
57+
'listingId': listingId,
58+
};
59+
}
60+
}
61+
62+
class HotelSearch {
63+
final String query;
64+
final DateTime checkIn;
65+
final DateTime checkOut;
66+
final int guests;
67+
68+
HotelSearch({
69+
required this.query,
70+
required this.checkIn,
71+
required this.checkOut,
72+
required this.guests,
73+
});
74+
75+
static HotelSearch fromJson(JsonMap json) {
76+
return HotelSearch(
77+
query: json['query'] as String,
78+
checkIn: DateTime.parse(json['checkIn'] as String),
79+
checkOut: DateTime.parse(json['checkOut'] as String),
80+
guests: json['guests'] as int,
81+
);
82+
}
83+
84+
JsonMap toJson() {
85+
return {
86+
'query': query,
87+
'checkIn': checkIn.toIso8601String(),
88+
'checkOut': checkOut.toIso8601String(),
89+
'guests': guests,
90+
};
91+
}
92+
}
93+
94+
/// An [AiTool] for listing hotels.
95+
class ListHotelsTool extends AiTool<Map<String, Object?>> {
96+
/// Creates a [ListHotelsTool].
97+
ListHotelsTool({required this.onListHotels})
98+
: super(
99+
name: 'listHotels',
100+
description: 'Lists hotels based on the provided criteria.',
101+
parameters: S.object(
102+
properties: {
103+
'query': S.string(
104+
description: 'The search query, e.g., "hotels in Paris".',
105+
),
106+
'checkIn': S.string(
107+
description: 'The check-in date in ISO 8601 format (YYYY-MM-DD).',
108+
format: 'date',
109+
),
110+
'checkOut': S.string(
111+
description:
112+
'The check-out date in ISO 8601 format (YYYY-MM-DD).',
113+
format: 'date',
114+
),
115+
'guests': S.integer(
116+
description: 'The number of guests.',
117+
minimum: 1,
118+
),
119+
},
120+
required: ['query', 'checkIn', 'checkOut', 'guests'],
121+
),
122+
);
123+
124+
/// The callback to invoke when searching hotels.
125+
final HotelSearchResult Function(HotelSearch search) onListHotels;
126+
127+
@override
128+
Future<JsonMap> invoke(JsonMap args) async {
129+
final search = HotelSearch.fromJson(args);
130+
return onListHotels(search).toJson();
131+
}
132+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright 2025 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:travel_app/src/tools/list_hotels_tool.dart';
7+
8+
void main() {
9+
group('ListHotelsTool', () {
10+
group('HotelListing', () {
11+
test('fromJson and toJson', () {
12+
final json = {
13+
'name': 'The Grand Hotel',
14+
'location': 'New York, NY',
15+
'pricePerNight': 299.99,
16+
'images': ['image1.jpg', 'image2.jpg'],
17+
'listingId': '12345',
18+
};
19+
20+
final listing = HotelListing.fromJson(json);
21+
22+
expect(listing.name, 'The Grand Hotel');
23+
expect(listing.location, 'New York, NY');
24+
expect(listing.pricePerNight, 299.99);
25+
expect(listing.images, ['image1.jpg', 'image2.jpg']);
26+
expect(listing.listingId, '12345');
27+
28+
expect(listing.toJson(), json);
29+
});
30+
});
31+
32+
group('HotelSearchResult', () {
33+
test('fromJson and toJson', () {
34+
final json = {
35+
'listings': [
36+
{
37+
'name': 'The Grand Hotel',
38+
'location': 'New York, NY',
39+
'pricePerNight': 299.99,
40+
'images': ['image1.jpg', 'image2.jpg'],
41+
'listingId': '12345',
42+
},
43+
],
44+
};
45+
46+
final searchResult = HotelSearchResult.fromJson(json);
47+
48+
expect(searchResult.listings.length, 1);
49+
expect(searchResult.listings.first.name, 'The Grand Hotel');
50+
51+
expect(searchResult.toJson(), json);
52+
});
53+
});
54+
55+
group('HotelSearch', () {
56+
test('fromJson and toJson', () {
57+
final checkIn = DateTime(2025, 10, 01);
58+
final checkOut = DateTime(2025, 10, 05);
59+
60+
final json = {
61+
'query': 'hotels in New York',
62+
'checkIn': checkIn.toIso8601String(),
63+
'checkOut': checkOut.toIso8601String(),
64+
'guests': 2,
65+
};
66+
67+
final search = HotelSearch.fromJson(json);
68+
69+
expect(search.query, 'hotels in New York');
70+
expect(search.checkIn, checkIn);
71+
expect(search.checkOut, checkOut);
72+
expect(search.guests, 2);
73+
74+
expect(search.toJson(), json);
75+
});
76+
});
77+
78+
group('ListHotelsTool', () {
79+
test('invoke calls onListHotels and returns correct JSON', () async {
80+
final searchResult = HotelSearchResult(
81+
listings: [
82+
HotelListing(
83+
name: 'The Grand Hotel',
84+
location: 'New York, NY',
85+
pricePerNight: 299.99,
86+
images: ['image1.jpg', 'image2.jpg'],
87+
listingId: '12345',
88+
),
89+
],
90+
);
91+
92+
final tool = ListHotelsTool(
93+
onListHotels: (search) {
94+
expect(search.query, 'hotels in New York');
95+
expect(search.checkIn, DateTime.parse('2025-10-01T00:00:00.000'));
96+
expect(search.checkOut, DateTime.parse('2025-10-05T00:00:00.000'));
97+
expect(search.guests, 2);
98+
return searchResult;
99+
},
100+
);
101+
102+
final args = {
103+
'query': 'hotels in New York',
104+
'checkIn': '2025-10-01',
105+
'checkOut': '2025-10-05',
106+
'guests': 2,
107+
};
108+
109+
final result = await tool.invoke(args);
110+
111+
expect(result, searchResult.toJson());
112+
});
113+
});
114+
});
115+
}

0 commit comments

Comments
 (0)