@curless/hotel-storefront 0.46.0 → 0.47.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aggregate/hotels.d.ts.map +1 -1
- package/dist/aggregate/hotels.js +6 -1
- package/dist/aggregate/hotels.js.map +1 -1
- package/dist/card.generated.d.ts +1 -1
- package/dist/card.generated.js +2 -2
- package/dist/card.generated.js.map +1 -1
- package/dist/demo-hotels.d.ts.map +1 -1
- package/dist/demo-hotels.js +15 -6
- package/dist/demo-hotels.js.map +1 -1
- package/dist/huazhu/backend.d.ts.map +1 -1
- package/dist/huazhu/backend.js +54 -2
- package/dist/huazhu/backend.js.map +1 -1
- package/dist/huazhu/catalog.d.ts +9 -2
- package/dist/huazhu/catalog.d.ts.map +1 -1
- package/dist/huazhu/catalog.js +65 -5
- package/dist/huazhu/catalog.js.map +1 -1
- package/dist/huazhu/english.d.ts +8 -0
- package/dist/huazhu/english.d.ts.map +1 -1
- package/dist/huazhu/english.js +295 -2
- package/dist/huazhu/english.js.map +1 -1
- package/dist/huazhu/sellable.d.ts +12 -0
- package/dist/huazhu/sellable.d.ts.map +1 -1
- package/dist/huazhu/sellable.js +15 -0
- package/dist/huazhu/sellable.js.map +1 -1
- package/dist/names.d.ts +22 -0
- package/dist/names.d.ts.map +1 -1
- package/dist/names.js +43 -0
- package/dist/names.js.map +1 -1
- package/dist/room-filters.d.ts +37 -7
- package/dist/room-filters.d.ts.map +1 -1
- package/dist/room-filters.js +82 -21
- package/dist/room-filters.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.js +2 -2
- package/dist/tools.d.ts +14 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +111 -8
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -4,7 +4,7 @@ import { FACILITIES } from './facilities.js';
|
|
|
4
4
|
import { DESCRIPTION_MAX, POLICY_MAX, guestText, hotelText } from './fence.js';
|
|
5
5
|
import { clockOf, minutesOf, startProblem, stayWords, windowWords } from './hourly.js';
|
|
6
6
|
import { amountsIn, formatMoney, keepingAmounts, money, multiplyMoney, toMinor } from './money.js';
|
|
7
|
-
import { roomNamesOf, withTraditional } from './names.js';
|
|
7
|
+
import { namedExactly, roomNamesOf, withTraditional } from './names.js';
|
|
8
8
|
import { MAX_COMPARED, MAX_CONS, MAX_DAYS, MAX_ITEMS_PER_DAY, MAX_MOMENTS, MAX_PROS, buildItinerary, candidatesFor, compareHotels, namesMoreThanOne, } from './present.js';
|
|
9
9
|
import { regionOf } from './regions.js';
|
|
10
10
|
import { activeKeysOf, byRoomType, facetsOf, keyWords, matchesTerms, optionsOf, roomTermsOf, termWords, } from './room-filters.js';
|
|
@@ -339,7 +339,7 @@ const allTools = (n, v) => {
|
|
|
339
339
|
},
|
|
340
340
|
{
|
|
341
341
|
name: n.getHotel,
|
|
342
|
-
description: `One ${v.noun} in full: description, amenities, address, and check-in and check-out times where the ${v.noun} publishes them. Given \`checkIn\` and \`checkOut\` it also returns the room types that can be booked for those nights, one line per type at its lowest rate: the nightly rate, the stay total including taxes and fees, whether it can be cancelled free and until when, and how many rates it has. \`rooms\` leaves out the types that have fewer than that many left. \`bed\`, \`breakfast\` and \`
|
|
342
|
+
description: `One ${v.noun} in full: description, amenities, address, and check-in and check-out times where the ${v.noun} publishes them. Given \`checkIn\` and \`checkOut\` it also returns the room types that can be booked for those nights, one line per type at its lowest rate: the nightly rate, the stay total including taxes and fees, whether it can be cancelled free and until when, and how many rates it has. \`rooms\` leaves out the types that have fewer than that many left. \`bed\`, \`breakfast\`, \`freeCancellation\`, \`window\`, \`payment\` and \`family\` keep only the rates that meet all of those given; the card still holds every room, with the given filters selected. Without dates it returns the ${v.noun} only, because a room has no price until the nights are known. The ${v.noun} is drawn as a card for the guest to look at.${v.hints.hotel ? ` ${v.hints.hotel}` : ''}`,
|
|
343
343
|
annotations: reads(`View one ${catalogue(v, true)}`, open),
|
|
344
344
|
inputSchema: {
|
|
345
345
|
type: 'object',
|
|
@@ -368,6 +368,19 @@ const allTools = (n, v) => {
|
|
|
368
368
|
type: 'boolean',
|
|
369
369
|
description: 'With dates: only rooms that can be cancelled free',
|
|
370
370
|
},
|
|
371
|
+
window: {
|
|
372
|
+
type: 'boolean',
|
|
373
|
+
description: 'With dates: only rooms with a window (not a partial or an inner one)',
|
|
374
|
+
},
|
|
375
|
+
payment: {
|
|
376
|
+
type: 'string',
|
|
377
|
+
enum: ['at-hotel', 'online'],
|
|
378
|
+
description: 'With dates: only rooms paid at the hotel (`at-hotel`) or in advance online (`online`)',
|
|
379
|
+
},
|
|
380
|
+
family: {
|
|
381
|
+
type: 'boolean',
|
|
382
|
+
description: 'With dates: only rooms that sleep 3 or more',
|
|
383
|
+
},
|
|
371
384
|
},
|
|
372
385
|
required: ['hotel'],
|
|
373
386
|
},
|
|
@@ -923,6 +936,8 @@ const CANCEL_SCHEMA = {
|
|
|
923
936
|
demoHold: BOOL,
|
|
924
937
|
addOnsFree: BOOL,
|
|
925
938
|
fee: STR,
|
|
939
|
+
/** What the booking being cancelled came to, taxes in - the fee is read against it. */
|
|
940
|
+
bookingTotal: STR,
|
|
926
941
|
refund: STR,
|
|
927
942
|
rule: STR,
|
|
928
943
|
freeUntil: STR,
|
|
@@ -1050,12 +1065,22 @@ const HOTEL_PAGE = {
|
|
|
1050
1065
|
dates: ARR,
|
|
1051
1066
|
/**
|
|
1052
1067
|
* The room filters this hotel's rooms offer, each with how many rooms (rates)
|
|
1053
|
-
* it keeps - `{ key
|
|
1054
|
-
*
|
|
1068
|
+
* it keeps - `{ key, count }`, `key` one of `breakfast`, `king`, `twin`,
|
|
1069
|
+
* `freeCancellation` (always here, `count` 0 where no room has it) and
|
|
1070
|
+
* `window`, `payAtHotel`, `payOnline`, `family` (here only where some rooms
|
|
1071
|
+
* have it and some do not).
|
|
1055
1072
|
*/
|
|
1056
1073
|
roomFilters: ARR,
|
|
1057
1074
|
/** The filters the call asked for, as the same keys - the card opens with these selected. */
|
|
1058
1075
|
activeFilters: STRS,
|
|
1076
|
+
/**
|
|
1077
|
+
* Room types with no room to book for these nights (none left, or fewer
|
|
1078
|
+
* than the rooms asked for): `{ id, name, names?, sleeps, ..., left? }`, no
|
|
1079
|
+
* price. The card draws them muted at the end of the list, so a type the
|
|
1080
|
+
* guest saw is said to be sold out and not left out. Absent where there are
|
|
1081
|
+
* none - and never in what the assistant is told.
|
|
1082
|
+
*/
|
|
1083
|
+
soldOut: ARR,
|
|
1059
1084
|
};
|
|
1060
1085
|
export const buildOutputSchemas = (n) => ({
|
|
1061
1086
|
[n.listHotels]: {
|
|
@@ -1173,6 +1198,8 @@ export const buildOutputSchemas = (n) => ({
|
|
|
1173
1198
|
accessibility: BOOL,
|
|
1174
1199
|
/** One per stay, when the trip held several. */
|
|
1175
1200
|
bookings: ARR,
|
|
1201
|
+
/** What all of `bookings` come to, where they are in one currency (the card's first screen). */
|
|
1202
|
+
totalAll: STR,
|
|
1176
1203
|
/**
|
|
1177
1204
|
* The stays that could not be booked when the others were - each with
|
|
1178
1205
|
* why, still in the trip, and what could be booked instead (robin,
|
|
@@ -1457,6 +1484,10 @@ const holderOf = (deps, supplier) => {
|
|
|
1457
1484
|
const bookingForCard = (b) => ({
|
|
1458
1485
|
...b,
|
|
1459
1486
|
quote: quoteForCard(b.quote),
|
|
1487
|
+
// What a cancelled booking was charged to cancel, as text like every amount the
|
|
1488
|
+
// card is given: "Cancelled" in My bookings says it beside the struck-out total
|
|
1489
|
+
// (r10 #21). `cancellationFee` stays the money it was.
|
|
1490
|
+
...(b.cancellationFee ? { cancellationFeeText: formatMoney(b.cancellationFee) } : {}),
|
|
1460
1491
|
...(b.payment
|
|
1461
1492
|
? {
|
|
1462
1493
|
payment: {
|
|
@@ -1720,6 +1751,9 @@ const quoteForCard = (quote, groupOf) => ({
|
|
|
1720
1751
|
...(l.ordered !== undefined ? { ordered: l.ordered } : {}),
|
|
1721
1752
|
unit: formatMoney(l.unit),
|
|
1722
1753
|
total: formatMoney(l.total),
|
|
1754
|
+
// The line with its taxes in, where the backend knows them per line: what the
|
|
1755
|
+
// review and the trip show, as the hotel's page does (r10 #23).
|
|
1756
|
+
...(l.totalWithTaxes ? { totalWithTaxes: formatMoney(l.totalWithTaxes) } : {}),
|
|
1723
1757
|
refundable: l.refundable,
|
|
1724
1758
|
freeCancellationUntil: l.freeCancellationUntil,
|
|
1725
1759
|
// The deadline on the hotel's clock, where the backend knows it - the
|
|
@@ -2068,7 +2102,7 @@ when, enough, roomsWanted, terms) => {
|
|
|
2068
2102
|
...roomTypeLines(rs, roomsWanted, terms),
|
|
2069
2103
|
...(byRoomType(rs).some((t) => t.rooms.length > 1)
|
|
2070
2104
|
? [
|
|
2071
|
-
'A type with several rates is priced from its cheapest one; bed, breakfast and
|
|
2105
|
+
'A type with several rates is priced from its cheapest one; bed, breakfast, freeCancellation, window, payment and family narrow to the others (the card lists every rate).',
|
|
2072
2106
|
]
|
|
2073
2107
|
: []),
|
|
2074
2108
|
];
|
|
@@ -2221,6 +2255,52 @@ const dateFlex = (dates, stay) => {
|
|
|
2221
2255
|
text: `Arriving ${beats.checkIn} instead is ${formatMoney(saved)} less for ${sameNights}.`,
|
|
2222
2256
|
};
|
|
2223
2257
|
};
|
|
2258
|
+
/**
|
|
2259
|
+
* The room types a guest can no longer book on these nights - for the card to
|
|
2260
|
+
* say so, and not leave them out (r10 #10: a type the guest saw earlier
|
|
2261
|
+
* vanished from the list, and the heading counted one fewer, without a word).
|
|
2262
|
+
*
|
|
2263
|
+
* ⚠️ AN ADDITIVE FIELD, NEVER THE TEXT. What the assistant is told is the rooms
|
|
2264
|
+
* that can be booked, as before; this is only what the card draws, muted, at
|
|
2265
|
+
* the end of the list. A type is listed when
|
|
2266
|
+
* - the backend sends it with none left (`HotelDetail.soldOut`, or a room with
|
|
2267
|
+
* `roomsLeft: 0`), or
|
|
2268
|
+
* - it has some left, but fewer than the rooms asked for (`left`): a guest who
|
|
2269
|
+
* asked for two rooms and was shown no type with one left was not told why.
|
|
2270
|
+
* Not a type that sleeps fewer than the party (the backend never sends it), and
|
|
2271
|
+
* not a type any of whose rates can be booked. One entry a type, with the facts
|
|
2272
|
+
* the card says of a room and no price.
|
|
2273
|
+
*/
|
|
2274
|
+
const soldOutTypesOf = (detail, enough, wanted) => {
|
|
2275
|
+
const key = (r) => r.roomType?.id ?? r.id;
|
|
2276
|
+
const open = new Set(enough.map(key));
|
|
2277
|
+
const types = new Map();
|
|
2278
|
+
for (const r of [...(detail.rooms ?? []), ...(detail.soldOut ?? [])]) {
|
|
2279
|
+
if (r.hourly || open.has(key(r)) || r.roomsLeft >= wanted)
|
|
2280
|
+
continue;
|
|
2281
|
+
const seen = types.get(key(r));
|
|
2282
|
+
if (seen)
|
|
2283
|
+
seen.left = Math.max(seen.left, r.roomsLeft);
|
|
2284
|
+
else
|
|
2285
|
+
types.set(key(r), { room: r, left: r.roomsLeft });
|
|
2286
|
+
}
|
|
2287
|
+
return [...types.entries()].map(([id, { room: r, left }]) => ({
|
|
2288
|
+
id,
|
|
2289
|
+
name: r.roomType?.name ?? r.name,
|
|
2290
|
+
...((r.roomType?.names ?? r.names) ? { names: r.roomType?.names ?? r.names } : {}),
|
|
2291
|
+
sleeps: r.sleeps,
|
|
2292
|
+
...(r.maxChildren ? { maxChildren: r.maxChildren } : {}),
|
|
2293
|
+
...(r.sizeSqm ? { sizeSqm: r.sizeSqm } : {}),
|
|
2294
|
+
...(r.beds ? { beds: r.beds } : {}),
|
|
2295
|
+
...(r.bedTypes ? { bedTypes: r.bedTypes } : {}),
|
|
2296
|
+
...(r.view ? { view: r.view } : {}),
|
|
2297
|
+
...(r.window ? { window: r.window } : {}),
|
|
2298
|
+
...(r.floor ? { floor: r.floor } : {}),
|
|
2299
|
+
...(r.imageUrl ? { imageUrl: r.imageUrl } : {}),
|
|
2300
|
+
// Some left, but not as many as were asked for.
|
|
2301
|
+
...(left > 0 ? { left } : {}),
|
|
2302
|
+
}));
|
|
2303
|
+
};
|
|
2224
2304
|
const identity = (deps, ctx, key, args = {}) => {
|
|
2225
2305
|
const email = str(args.email);
|
|
2226
2306
|
const member = deps.members.read(key);
|
|
@@ -3329,6 +3409,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3329
3409
|
// with what is true of each (`facets`), so a chip switched on or off on the
|
|
3330
3410
|
// card needs no call to the server.
|
|
3331
3411
|
const terms = roomTermsOf(args);
|
|
3412
|
+
const soldOut = soldOutTypesOf(detail, enough, roomsWanted);
|
|
3332
3413
|
const rooms = enough.map((r) => ({
|
|
3333
3414
|
...r,
|
|
3334
3415
|
facets: facetsOf(r),
|
|
@@ -3366,6 +3447,8 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3366
3447
|
rooms_wanted: roomsWanted,
|
|
3367
3448
|
roomFilters: optionsOf(enough),
|
|
3368
3449
|
activeFilters: activeKeysOf(terms),
|
|
3450
|
+
// Types with no room to book for these nights, said and not left out.
|
|
3451
|
+
...(byTheHour || !soldOut.length ? {} : { soldOut }),
|
|
3369
3452
|
...flex.structured,
|
|
3370
3453
|
},
|
|
3371
3454
|
text: [
|
|
@@ -3659,7 +3742,10 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3659
3742
|
const today = (startTime || !asStay(args)
|
|
3660
3743
|
? deps.backend.hourlyRooms?.(hotelId, guestsOf(args), who)
|
|
3661
3744
|
: undefined);
|
|
3662
|
-
const byTheHour = async () =>
|
|
3745
|
+
const byTheHour = async () => {
|
|
3746
|
+
const sold = ((await today)?.rooms ?? []).filter((r) => r.hourly);
|
|
3747
|
+
return sold.find((r) => r.id === roomRef) ?? namedExactly(sold, roomRef);
|
|
3748
|
+
};
|
|
3663
3749
|
if (startTime && deps.backend.hourlyRooms) {
|
|
3664
3750
|
const hourlyRoom = await byTheHour();
|
|
3665
3751
|
if (!hourlyRoom?.hourly) {
|
|
@@ -3766,7 +3852,11 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3766
3852
|
text: dated?.noRooms ?? 'Those nights cannot be booked here.',
|
|
3767
3853
|
};
|
|
3768
3854
|
}
|
|
3769
|
-
|
|
3855
|
+
// ⚠️ BY THE OFFER'S WHOLE NAME, in any language it has: a guest who read
|
|
3856
|
+
// 「豪华大床房 · 双早 · 不可取消」 on the card says that, and it is the same offer
|
|
3857
|
+
// as "Deluxe King Room · Breakfast for 2 · Non-refundable". Never a part of
|
|
3858
|
+
// it, and never a room type's bare name when it has several rates.
|
|
3859
|
+
const room = dated.rooms.find((r) => r.id === roomRef) ?? namedExactly(dated.rooms, roomRef);
|
|
3770
3860
|
if (!room) {
|
|
3771
3861
|
return {
|
|
3772
3862
|
structured: {
|
|
@@ -3784,7 +3874,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3784
3874
|
hotel: { id: detail.hotel.id, name: detail.hotel.name },
|
|
3785
3875
|
stay,
|
|
3786
3876
|
},
|
|
3787
|
-
text: `${detail.hotel.name} has no room type by that name for those nights. It has: ${dated.rooms.map((r) => r.name).join(', ')}.`,
|
|
3877
|
+
text: `${detail.hotel.name} has no room type by that name for those nights. It has: ${dated.rooms.map((r) => (/\p{Script=Han}/u.test(roomRef) && r.names?.zh ? `${r.name} (${r.names.zh})` : r.name)).join(', ')}.`,
|
|
3788
3878
|
};
|
|
3789
3879
|
}
|
|
3790
3880
|
deps.trips.set(key, {
|
|
@@ -4018,6 +4108,17 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
4018
4108
|
: {}),
|
|
4019
4109
|
paid: false,
|
|
4020
4110
|
...(remembered ? { remembered: remembered.kept } : {}),
|
|
4111
|
+
// What they come to together, said by the server like every figure on
|
|
4112
|
+
// the card - never added up there (r10, #15). Only in one currency.
|
|
4113
|
+
...(several &&
|
|
4114
|
+
booked.every(({ booking }) => booking.quote.total.currency === first.booking.quote.total.currency)
|
|
4115
|
+
? {
|
|
4116
|
+
totalAll: formatMoney({
|
|
4117
|
+
minor: booked.reduce((n, { booking }) => n + booking.quote.total.minor, 0),
|
|
4118
|
+
currency: first.booking.quote.total.currency,
|
|
4119
|
+
}),
|
|
4120
|
+
}
|
|
4121
|
+
: {}),
|
|
4021
4122
|
holdUntil: first.booking.holdUntil,
|
|
4022
4123
|
total: formatMoney(first.booking.quote.total),
|
|
4023
4124
|
...(first.asked.length ? { requested: first.asked } : {}),
|
|
@@ -4437,6 +4538,8 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
4437
4538
|
: {}),
|
|
4438
4539
|
...ownDemo,
|
|
4439
4540
|
fee: formatMoney(preview.fee),
|
|
4541
|
+
// What the booking came to, so the fee is read against it (r10 #22).
|
|
4542
|
+
...(booked ? { bookingTotal: formatMoney(booked.quote.total) } : {}),
|
|
4440
4543
|
// ⚠️ NO "REFUND": nothing was ever paid here - a demonstration takes no
|
|
4441
4544
|
// money and a hotel's own booking is paid at the hotel - so a figure
|
|
4442
4545
|
// called a refund is money that is not coming back (2026-10-05 audit, P3).
|