@v-office/website-chat 1.0.1 → 1.1.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/README.md +8 -3
- package/dist/{catalog-RXKmEmMW.js → catalog-y3xQ2jXW.js} +16 -9
- package/dist/catalog.d.ts +19 -0
- package/dist/catalog.js +2 -2
- package/dist/icons-DIqOmKoe.js +243 -0
- package/dist/index.d.ts +19 -4
- package/dist/index.js +225 -84
- package/dist/{panel-C0Bb7ln-.js → panel-BsocDe9I.js} +195 -115
- package/instructions/CHANGELOG.md +2 -0
- package/instructions/MIGRATION.md +2 -0
- package/instructions/versions/1.1.0/CHANGELOG.md +52 -0
- package/instructions/versions/1.1.0/MIGRATION.md +22 -0
- package/instructions/versions/1.1.1/CHANGELOG.md +26 -0
- package/instructions/versions/1.1.1/MIGRATION.md +13 -0
- package/package.json +3 -3
- package/dist/icons-B5PAY3aU.js +0 -145
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import { i as SESSION_TTL_S, r as MAX_MESSAGE_CHARS } from "./protocol-BuNqZAMt.js";
|
|
2
|
-
import { a as
|
|
3
|
-
import {
|
|
2
|
+
import { a as House, c as TopicIcon, d as saveConversation, i as Cross, l as clearConversation, n as Check, o as Restart, p as sendMessage, r as Chevron, t as Arrow, u as loadConversation } from "./icons-DIqOmKoe.js";
|
|
3
|
+
import { r as chatCatalog, u as statePath } from "./catalog-y3xQ2jXW.js";
|
|
4
4
|
import { createContext, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { JSONUIProvider, Renderer, defineRegistry, useStateValue } from "@json-render/react";
|
|
7
7
|
import { DEFAULT_MAP_STYLE_URL, partyPairs } from "@v-office/website-headless";
|
|
8
8
|
//#region src/links.ts
|
|
9
|
+
const withQuery = (path, pairs) => `${path}${path.includes("?") ? "&" : "?"}${new URLSearchParams(pairs)}`;
|
|
10
|
+
/** The stay's dates, party and chosen rate, as the rental page and the checkout both read them. */
|
|
11
|
+
const stayPairs = (stay) => [
|
|
12
|
+
["start", stay.start],
|
|
13
|
+
["end", stay.end],
|
|
14
|
+
...partyPairs(stay.party, "pets").map(([key, value]) => [key, value]),
|
|
15
|
+
...stay.policyId ? [["policy", stay.policyId]] : []
|
|
16
|
+
];
|
|
9
17
|
/** See {@link StayHref}. */
|
|
10
18
|
const stayHref = (page, stay, searchPath) => {
|
|
11
19
|
const path = page ?? searchPath;
|
|
12
|
-
|
|
13
|
-
const pairs = [
|
|
14
|
-
["start", stay.start],
|
|
15
|
-
["end", stay.end],
|
|
16
|
-
...partyPairs(stay.party, "pets").map(([key, value]) => [key, value])
|
|
17
|
-
];
|
|
18
|
-
const query = new URLSearchParams(pairs);
|
|
19
|
-
return `${path}${path.includes("?") ? "&" : "?"}${query}`;
|
|
20
|
+
return stay ? withQuery(path, stayPairs(stay)) : path;
|
|
20
21
|
};
|
|
22
|
+
/** See {@link BookingHref}. */
|
|
23
|
+
const bookingHref = (bookingPage, stay) => withQuery(bookingPage, [...stayPairs(stay), ...(stay.services ?? []).filter((service) => service.count > 0).map((service) => ["service", `${service.id}:${service.count}`])]);
|
|
21
24
|
//#endregion
|
|
22
25
|
//#region src/map.tsx
|
|
23
26
|
/** What the style's sources ask to be credited, with their links, as MapLibre's attribution control
|
|
@@ -142,30 +145,13 @@ const partyText = (stay, copy, locale) => {
|
|
|
142
145
|
const children = stay.party.children + stay.party.babies;
|
|
143
146
|
return [counted(copy.adults, stay.party.adults, locale), ...children ? [counted(copy.children, children, locale)] : []].join(", ");
|
|
144
147
|
};
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
|
|
148
|
-
className: "muted",
|
|
149
|
-
children: [
|
|
150
|
-
period(stay, locale),
|
|
151
|
-
" · ",
|
|
152
|
-
partyText(stay, copy, locale)
|
|
153
|
-
]
|
|
154
|
-
}), !stay.bookable ? /* @__PURE__ */ jsx("span", {
|
|
155
|
-
className: "muted",
|
|
156
|
-
children: copy.notBookable
|
|
157
|
-
}) : stay.total && /* @__PURE__ */ jsxs("span", { children: [
|
|
158
|
-
copy.total,
|
|
159
|
-
": ",
|
|
160
|
-
/* @__PURE__ */ jsx("span", {
|
|
161
|
-
className: "price",
|
|
162
|
-
children: stay.total
|
|
163
|
-
})
|
|
164
|
-
] })] });
|
|
165
|
-
};
|
|
148
|
+
/** A bookable stay goes to the rental's booking page with everything chosen, where the site has one;
|
|
149
|
+
* otherwise to the rental page (or the search) with the stay filled in. */
|
|
166
150
|
const useBookHref = (rentalId, stay) => {
|
|
167
151
|
const { searchPath } = useChat();
|
|
168
152
|
const page = useStateValue(statePath("pages", rentalId)) ?? null;
|
|
153
|
+
const booking = useStateValue(statePath("bookingPages", rentalId)) ?? null;
|
|
154
|
+
if (stay?.bookable && booking) return bookingHref(booking, stay);
|
|
169
155
|
return stayHref(page, stay?.bookable ? stay : null, searchPath);
|
|
170
156
|
};
|
|
171
157
|
/** A link out of the chat. It waits while a reply streams: leaving the page then aborts the turn,
|
|
@@ -200,9 +186,10 @@ const Snapper = ({ count, className, label, children }) => {
|
|
|
200
186
|
const element = row.current;
|
|
201
187
|
if (!element) return;
|
|
202
188
|
const still = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
203
|
-
const
|
|
204
|
-
element.
|
|
205
|
-
|
|
189
|
+
const [first, second] = element.children;
|
|
190
|
+
const card = first && second ? second.offsetLeft - first.offsetLeft : element.clientWidth;
|
|
191
|
+
element.scrollBy({
|
|
192
|
+
left: step * card,
|
|
206
193
|
behavior: still ? "auto" : "smooth"
|
|
207
194
|
});
|
|
208
195
|
};
|
|
@@ -248,35 +235,83 @@ const Snapper = ({ count, className, label, children }) => {
|
|
|
248
235
|
})]
|
|
249
236
|
});
|
|
250
237
|
};
|
|
238
|
+
/** A rental's photos, swiped or stepped through with the arrows a pointer brings up; the dots say
|
|
239
|
+
* which one shows. */
|
|
251
240
|
const Gallery = ({ images, name }) => {
|
|
252
241
|
const { copy } = useChat();
|
|
242
|
+
const row = useRef(null);
|
|
243
|
+
const [index, setIndex] = useState(0);
|
|
253
244
|
if (images.length === 0) return null;
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
245
|
+
const go = (to) => {
|
|
246
|
+
const element = row.current;
|
|
247
|
+
if (!element) return;
|
|
248
|
+
const still = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
249
|
+
element.scrollTo({
|
|
250
|
+
left: to * element.clientWidth,
|
|
251
|
+
behavior: still ? "auto" : "smooth"
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
const many = images.length > 1;
|
|
255
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
256
|
+
className: "gallery-wrap",
|
|
257
|
+
role: "group",
|
|
258
|
+
"aria-roledescription": "carousel",
|
|
259
|
+
"aria-label": name,
|
|
260
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
261
|
+
ref: row,
|
|
262
|
+
className: "gallery",
|
|
263
|
+
onScroll: (event) => {
|
|
264
|
+
const element = event.currentTarget;
|
|
265
|
+
setIndex(Math.round(element.scrollLeft / Math.max(1, element.clientWidth)));
|
|
266
|
+
},
|
|
267
|
+
children: images.map((image, imageIndex) => /* @__PURE__ */ jsx("img", {
|
|
268
|
+
src: image.src,
|
|
269
|
+
alt: image.alt || `${name}, ${copy.photo} ${imageIndex + 1}`,
|
|
270
|
+
loading: "lazy"
|
|
271
|
+
}, image.src))
|
|
272
|
+
}), many && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
273
|
+
/* @__PURE__ */ jsx("button", {
|
|
274
|
+
type: "button",
|
|
275
|
+
className: "gallery-arrow",
|
|
276
|
+
"data-side": "prev",
|
|
277
|
+
"aria-label": copy.previous,
|
|
278
|
+
disabled: index === 0,
|
|
279
|
+
onClick: () => go(index - 1),
|
|
280
|
+
children: /* @__PURE__ */ jsx(Chevron, { side: "prev" })
|
|
281
|
+
}),
|
|
282
|
+
/* @__PURE__ */ jsx("button", {
|
|
283
|
+
type: "button",
|
|
284
|
+
className: "gallery-arrow",
|
|
285
|
+
"data-side": "next",
|
|
286
|
+
"aria-label": copy.next,
|
|
287
|
+
disabled: index >= images.length - 1,
|
|
288
|
+
onClick: () => go(index + 1),
|
|
289
|
+
children: /* @__PURE__ */ jsx(Chevron, { side: "next" })
|
|
290
|
+
}),
|
|
291
|
+
/* @__PURE__ */ jsx("span", {
|
|
292
|
+
className: "dots",
|
|
293
|
+
"aria-hidden": "true",
|
|
294
|
+
children: images.map((image, imageIndex) => /* @__PURE__ */ jsx("i", { "data-current": imageIndex === index || void 0 }, image.src))
|
|
295
|
+
}),
|
|
296
|
+
/* @__PURE__ */ jsx("span", {
|
|
297
|
+
className: "sr-only",
|
|
298
|
+
"aria-live": "polite",
|
|
299
|
+
children: `${copy.photo} ${Math.min(index + 1, images.length)} / ${images.length}`
|
|
300
|
+
})
|
|
301
|
+
] })]
|
|
263
302
|
});
|
|
264
303
|
};
|
|
304
|
+
/** The PMS's score as a chip; its number of reviews is read out, not shown. */
|
|
265
305
|
const Rating = ({ rental }) => {
|
|
266
306
|
const { copy, locale } = useChat();
|
|
267
307
|
if (!rental.rating) return null;
|
|
268
308
|
const { value, count } = rental.rating;
|
|
269
|
-
return /* @__PURE__ */
|
|
270
|
-
className: "
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}),
|
|
276
|
-
" (",
|
|
277
|
-
counted(copy.reviews, reviewCount(count), locale),
|
|
278
|
-
")"
|
|
279
|
-
]
|
|
309
|
+
return /* @__PURE__ */ jsx("span", {
|
|
310
|
+
className: "score-chip",
|
|
311
|
+
role: "img",
|
|
312
|
+
title: counted(copy.reviews, reviewCount(count), locale),
|
|
313
|
+
"aria-label": `${value}, ${counted(copy.reviews, reviewCount(count), locale)}`,
|
|
314
|
+
children: value
|
|
280
315
|
});
|
|
281
316
|
};
|
|
282
317
|
/** The visitor's wishes the latest search that found the rental applied, which it therefore has. */
|
|
@@ -287,51 +322,86 @@ const Wishes = ({ wishes }) => {
|
|
|
287
322
|
return /* @__PURE__ */ jsx("ul", {
|
|
288
323
|
className: "wishes",
|
|
289
324
|
"aria-label": copy.matchesWishes,
|
|
290
|
-
children: wishes.map((wish) => /* @__PURE__ */
|
|
325
|
+
children: wishes.map((wish) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx(Check, {}), wish] }, wish))
|
|
326
|
+
});
|
|
327
|
+
};
|
|
328
|
+
/** "7 Nächte · 26.12.–2.1.": a stay's length and dates, short. */
|
|
329
|
+
const stayMeta = (stay, copy, locale) => {
|
|
330
|
+
const nights = Math.round((day(stay.end).getTime() - day(stay.start).getTime()) / 864e5);
|
|
331
|
+
const short = new Intl.DateTimeFormat(locale, {
|
|
332
|
+
day: "numeric",
|
|
333
|
+
month: "numeric",
|
|
334
|
+
timeZone: "UTC"
|
|
335
|
+
});
|
|
336
|
+
return `${counted(copy.nights, nights, locale)} · ${short.format(day(stay.start))}–${short.format(day(stay.end))}`;
|
|
337
|
+
};
|
|
338
|
+
/** A card's foot: the stay's total and its length, and the way to book or to the rental. */
|
|
339
|
+
const CardFoot = ({ rentalId, stay }) => {
|
|
340
|
+
const { copy, locale } = useChat();
|
|
341
|
+
const href = useBookHref(rentalId, stay);
|
|
342
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
343
|
+
className: "card-foot",
|
|
344
|
+
children: [stay && /* @__PURE__ */ jsx("div", {
|
|
345
|
+
className: "foot-price",
|
|
346
|
+
children: !stay.bookable ? /* @__PURE__ */ jsx("span", {
|
|
347
|
+
className: "muted",
|
|
348
|
+
children: copy.notBookable
|
|
349
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [stay.total && /* @__PURE__ */ jsx("span", {
|
|
350
|
+
className: "price",
|
|
351
|
+
children: stay.total
|
|
352
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
353
|
+
className: "foot-meta",
|
|
354
|
+
children: stayMeta(stay, copy, locale)
|
|
355
|
+
})] })
|
|
356
|
+
}), /* @__PURE__ */ jsx(ReplyLink, {
|
|
357
|
+
className: "button compact",
|
|
358
|
+
href,
|
|
359
|
+
children: stay?.bookable ? copy.book : copy.view
|
|
360
|
+
})]
|
|
291
361
|
});
|
|
292
362
|
};
|
|
293
363
|
const Card = ({ rentalId, stayId }) => {
|
|
294
|
-
const { copy } = useChat();
|
|
295
364
|
const rental = useRental(rentalId);
|
|
296
365
|
const stay = useStay(stayId, rentalId);
|
|
297
366
|
const wishes = useWishes(rentalId);
|
|
298
367
|
if (!rental) return null;
|
|
299
368
|
return /* @__PURE__ */ jsxs("article", {
|
|
300
|
-
className: "card",
|
|
301
|
-
children: [
|
|
302
|
-
|
|
303
|
-
children: [/* @__PURE__ */ jsx(Gallery, {
|
|
369
|
+
className: "card rental-card",
|
|
370
|
+
children: [
|
|
371
|
+
rental.images.length > 0 ? /* @__PURE__ */ jsx(Gallery, {
|
|
304
372
|
images: rental.images,
|
|
305
373
|
name: rental.name
|
|
306
|
-
})
|
|
307
|
-
className: "
|
|
374
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
375
|
+
className: "no-photo",
|
|
308
376
|
"aria-hidden": "true",
|
|
309
|
-
children:
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
377
|
+
children: /* @__PURE__ */ jsx(House, {})
|
|
378
|
+
}),
|
|
379
|
+
/* @__PURE__ */ jsxs("div", {
|
|
380
|
+
className: "card-body",
|
|
381
|
+
children: [
|
|
382
|
+
/* @__PURE__ */ jsxs("div", {
|
|
383
|
+
className: "card-meta",
|
|
384
|
+
children: [rental.place && /* @__PURE__ */ jsx("span", {
|
|
385
|
+
className: "place",
|
|
386
|
+
children: rental.place
|
|
387
|
+
}), /* @__PURE__ */ jsx(Rating, { rental })]
|
|
388
|
+
}),
|
|
389
|
+
/* @__PURE__ */ jsx("h3", {
|
|
390
|
+
className: "card-title",
|
|
391
|
+
children: rental.name
|
|
392
|
+
}),
|
|
393
|
+
rental.highlights.length > 0 && /* @__PURE__ */ jsx("p", {
|
|
394
|
+
className: "specs",
|
|
395
|
+
children: rental.highlights.slice(0, 3).join(" · ")
|
|
396
|
+
}),
|
|
397
|
+
/* @__PURE__ */ jsx(Wishes, { wishes })
|
|
398
|
+
]
|
|
399
|
+
}),
|
|
400
|
+
/* @__PURE__ */ jsx(CardFoot, {
|
|
401
|
+
rentalId: rental.id,
|
|
402
|
+
stay
|
|
403
|
+
})
|
|
404
|
+
]
|
|
335
405
|
});
|
|
336
406
|
};
|
|
337
407
|
/** Monday-first weeks of one month, as `YYYY-MM-DD` days and `null` padding. */
|
|
@@ -687,25 +757,25 @@ const { registry } = defineRegistry(chatCatalog, { components: {
|
|
|
687
757
|
});
|
|
688
758
|
},
|
|
689
759
|
StayQuote: ({ props }) => {
|
|
760
|
+
const { copy, locale } = useChat();
|
|
690
761
|
const stay = useStay(props.stayId);
|
|
691
762
|
const rental = useRental(stay?.rentalId ?? "");
|
|
692
763
|
if (!stay) return null;
|
|
693
|
-
return /* @__PURE__ */
|
|
694
|
-
className: "card",
|
|
695
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
764
|
+
return /* @__PURE__ */ jsxs("article", {
|
|
765
|
+
className: "card stay-card",
|
|
766
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
696
767
|
className: "card-body",
|
|
697
|
-
children: [
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
})
|
|
768
|
+
children: [rental && /* @__PURE__ */ jsx("h3", {
|
|
769
|
+
className: "card-title",
|
|
770
|
+
children: rental.name
|
|
771
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
772
|
+
className: "specs",
|
|
773
|
+
children: partyText(stay, copy, locale)
|
|
774
|
+
})]
|
|
775
|
+
}), /* @__PURE__ */ jsx(CardFoot, {
|
|
776
|
+
rentalId: stay.rentalId,
|
|
777
|
+
stay
|
|
778
|
+
})]
|
|
709
779
|
});
|
|
710
780
|
},
|
|
711
781
|
PriceBreakdown: ({ props }) => {
|
|
@@ -784,12 +854,12 @@ const { registry } = defineRegistry(chatCatalog, { components: {
|
|
|
784
854
|
if (!send) return null;
|
|
785
855
|
return /* @__PURE__ */ jsx("div", {
|
|
786
856
|
className: "quick-replies",
|
|
787
|
-
children: props.options.map((option) => /* @__PURE__ */
|
|
857
|
+
children: props.options.map((option) => /* @__PURE__ */ jsxs("button", {
|
|
788
858
|
type: "button",
|
|
789
859
|
className: "chip",
|
|
790
860
|
disabled: busy,
|
|
791
861
|
onClick: () => send(option),
|
|
792
|
-
children: option
|
|
862
|
+
children: [/* @__PURE__ */ jsx(TopicIcon, { text: option }), option]
|
|
793
863
|
}, option))
|
|
794
864
|
});
|
|
795
865
|
}
|
|
@@ -837,6 +907,9 @@ const lockPageScroll = () => {
|
|
|
837
907
|
root.setAttribute(SCROLL_LOCK, "");
|
|
838
908
|
return () => root.removeAttribute(SCROLL_LOCK);
|
|
839
909
|
};
|
|
910
|
+
/** The assistant's text, its `**bold**` (a rental's name, the dates) as strong text: nothing else
|
|
911
|
+
* is read as markup, so the model's text never becomes HTML. */
|
|
912
|
+
const RichText = ({ text }) => /* @__PURE__ */ jsx(Fragment, { children: text.split(/\*\*(.+?)\*\*/g).map((piece, index) => index % 2 === 1 ? /* @__PURE__ */ jsx("strong", { children: piece }, index) : piece) });
|
|
840
913
|
const SpecView = ({ spec }) => /* @__PURE__ */ jsx(JSONUIProvider, {
|
|
841
914
|
registry,
|
|
842
915
|
initialState: spec.state ?? {},
|
|
@@ -848,6 +921,11 @@ const SpecView = ({ spec }) => /* @__PURE__ */ jsx(JSONUIProvider, {
|
|
|
848
921
|
const Consent = ({ copy, pending, onAccept, buttonRef }) => /* @__PURE__ */ jsxs("div", {
|
|
849
922
|
className: "consent",
|
|
850
923
|
children: [
|
|
924
|
+
/* @__PURE__ */ jsx("p", {
|
|
925
|
+
className: "message",
|
|
926
|
+
"data-role": "assistant",
|
|
927
|
+
children: copy.greeting
|
|
928
|
+
}),
|
|
851
929
|
pending && /* @__PURE__ */ jsxs("figure", {
|
|
852
930
|
className: "pending",
|
|
853
931
|
children: [/* @__PURE__ */ jsx("figcaption", {
|
|
@@ -855,13 +933,15 @@ const Consent = ({ copy, pending, onAccept, buttonRef }) => /* @__PURE__ */ jsxs
|
|
|
855
933
|
children: copy.yourMessage
|
|
856
934
|
}), /* @__PURE__ */ jsx("p", { children: pending })]
|
|
857
935
|
}),
|
|
858
|
-
/* @__PURE__ */
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
936
|
+
/* @__PURE__ */ jsxs("div", {
|
|
937
|
+
className: "consent-card",
|
|
938
|
+
children: [/* @__PURE__ */ jsx("p", { children: copy.consent }), /* @__PURE__ */ jsx("button", {
|
|
939
|
+
ref: buttonRef,
|
|
940
|
+
type: "button",
|
|
941
|
+
className: "button compact",
|
|
942
|
+
onClick: onAccept,
|
|
943
|
+
children: copy.consentAccept
|
|
944
|
+
})]
|
|
865
945
|
})
|
|
866
946
|
]
|
|
867
947
|
});
|
|
@@ -1084,7 +1164,7 @@ function Panel(props) {
|
|
|
1084
1164
|
children: message.parts.map((part, partIndex) => part.kind === "text" ? /* @__PURE__ */ jsx("div", {
|
|
1085
1165
|
className: "message",
|
|
1086
1166
|
"data-role": message.role,
|
|
1087
|
-
children: part.text
|
|
1167
|
+
children: message.role === "assistant" ? /* @__PURE__ */ jsx(RichText, { text: part.text }) : part.text
|
|
1088
1168
|
}, partIndex) : /* @__PURE__ */ jsx(SpecView, { spec: part.spec }, partIndex))
|
|
1089
1169
|
}, index)),
|
|
1090
1170
|
waiting && /* @__PURE__ */ jsx(Thinking, { label: copy.thinking }),
|
|
@@ -1115,11 +1195,11 @@ function Panel(props) {
|
|
|
1115
1195
|
}),
|
|
1116
1196
|
!hasMessages && copy.suggestions.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
1117
1197
|
className: "suggestions",
|
|
1118
|
-
children: copy.suggestions.map((suggestion) => /* @__PURE__ */
|
|
1198
|
+
children: copy.suggestions.map((suggestion) => /* @__PURE__ */ jsxs("button", {
|
|
1119
1199
|
type: "button",
|
|
1120
1200
|
className: "suggestion",
|
|
1121
1201
|
onClick: () => void submit(suggestion),
|
|
1122
|
-
children: suggestion
|
|
1202
|
+
children: [/* @__PURE__ */ jsx(TopicIcon, { text: suggestion }), suggestion]
|
|
1123
1203
|
}, suggestion))
|
|
1124
1204
|
}),
|
|
1125
1205
|
/* @__PURE__ */ jsxs("form", {
|
|
@@ -4,5 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Release notes of `@v-office/website-chat`, newest first. Each version has its own directory.
|
|
6
6
|
|
|
7
|
+
- `versions/1.1.1/CHANGELOG.md`: release notes for 1.1.1
|
|
8
|
+
- `versions/1.1.0/CHANGELOG.md`: release notes for 1.1.0
|
|
7
9
|
- `versions/1.0.1/CHANGELOG.md`: release notes for 1.0.1
|
|
8
10
|
- `versions/1.0.0/CHANGELOG.md`: release notes for 1.0.0
|
|
@@ -4,5 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Migration guides of `@v-office/website-chat`, newest first. Apply them in order when skipping versions.
|
|
6
6
|
|
|
7
|
+
- `versions/1.1.1/MIGRATION.md`: Migration: 1.1.0 to 1.1.1
|
|
8
|
+
- `versions/1.1.0/MIGRATION.md`: Migration: 1.0.1 to 1.1.0
|
|
7
9
|
- `versions/1.0.1/MIGRATION.md`: Migration: 1.0.0 to 1.0.1
|
|
8
10
|
- `versions/1.0.0/MIGRATION.md`: Migration: 1.0.0
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog: 1.1.0
|
|
2
|
+
|
|
3
|
+
The chat looks like the site it is on and like AI Studio's booking assistant prototype. It takes
|
|
4
|
+
its colours and corners from the site's shadcn tokens, which every AI Studio template defines,
|
|
5
|
+
instead of its own blue, and has calmer cards, a two-tone brand and a friendlier start. Nothing to
|
|
6
|
+
change on a site.
|
|
7
|
+
|
|
8
|
+
## Added
|
|
9
|
+
|
|
10
|
+
- `colors.accent`: the colour of the small marks (the chat's name and sparkle, a card's place, the
|
|
11
|
+
icons, the ring round the entry pill). The site's primary when unset; a site with a second brand
|
|
12
|
+
colour sets it here.
|
|
13
|
+
- Suggestions and quick replies carry an icon for their topic (dates, prices, pets, people,
|
|
14
|
+
places, booking).
|
|
15
|
+
- The assistant's **bold** (a rental's name, the dates) shows as bold text; nothing else in its
|
|
16
|
+
text is read as markup.
|
|
17
|
+
- A stay can carry the rate and extras the visitor chose in the chat (`ChatStay.policyId`,
|
|
18
|
+
`ChatStay.services`), and the state the rental's booking page (`ChatState.bookingPages`). A
|
|
19
|
+
bookable stay's booking button then opens the booking page with the dates, the party, the rate
|
|
20
|
+
and the extras (`?policy=`, `?service=<id>:<count>`); the visitor enters their details there.
|
|
21
|
+
Without a booking page the button opens the rental page as before.
|
|
22
|
+
|
|
23
|
+
## Changed
|
|
24
|
+
|
|
25
|
+
- Colours come from the site: `--primary` and `--primary-foreground` for buttons and the chat's
|
|
26
|
+
mark, `--background`, `--foreground`, `--muted`, `--muted-foreground` and `--border` for the
|
|
27
|
+
panel and its text. The visitor's messages, suggestions and chips are the site's primary,
|
|
28
|
+
lightened. A site in dark mode gets a dark chat.
|
|
29
|
+
- Corners come from the site's `--radius`; the themes scale it (`classic` 1.6×, `soft` 2.4×,
|
|
30
|
+
`minimal` 0.6×). Buttons and chips stay pills unless the site's corners are square
|
|
31
|
+
(`--radius: 0`); `minimal` has no pills.
|
|
32
|
+
- The calendar's free days, the price bars and the map pin use the site's colours.
|
|
33
|
+
- Without the tokens the chat looks as in 1.0.
|
|
34
|
+
- Rental cards: the place and the rating on top, the name larger, the facts and the wishes as
|
|
35
|
+
chips, then the stay's total with "7 Nächte · 26.12.–2.1." and a short "Buchen" button below a
|
|
36
|
+
line. Photos show dots, and arrows under a pointer, instead of "1 / 3".
|
|
37
|
+
- A stay quote has the same foot as a card.
|
|
38
|
+
- The panel's head has no line under it and a filled close button; the AI notice is one quiet line.
|
|
39
|
+
- A warmer greeting, and the start step as a card under it with a compact "Chat starten".
|
|
40
|
+
- The panel is 700px wide on wide screens (`--chat-width` still sets it), the assistant's text a
|
|
41
|
+
little larger.
|
|
42
|
+
|
|
43
|
+
## Fixed
|
|
44
|
+
|
|
45
|
+
- A site whose tokens are HSL triples (`221 83% 53%`, an older shadcn setup) keeps the chat's own
|
|
46
|
+
colours for them instead of getting colourless buttons.
|
|
47
|
+
|
|
48
|
+
## Unchanged
|
|
49
|
+
|
|
50
|
+
- `colors={{ primary, secondary }}` still overrides, with white text on `primary` and dark text on
|
|
51
|
+
`secondary` as before (not the site's foregrounds, which suit the site's own colours). `theme`
|
|
52
|
+
takes the same values, and every other prop is as before.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Migration: 1.0.1 to 1.1.0
|
|
2
|
+
|
|
3
|
+
No changes required: the chat takes the site's design tokens by itself.
|
|
4
|
+
|
|
5
|
+
## 1. Upgrade
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add --save-exact @v-office/website-chat@1.1.0
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 2. Check `colors` (optional)
|
|
12
|
+
|
|
13
|
+
A site that passed `colors` to match its brand can drop it: the chat now reads the same colour from
|
|
14
|
+
`--primary`. Keep it only where the chat should differ from the site's buttons.
|
|
15
|
+
|
|
16
|
+
## 3. A second brand colour (optional)
|
|
17
|
+
|
|
18
|
+
A site whose design has a second colour for small accents (labels, icons) passes it:
|
|
19
|
+
|
|
20
|
+
```astro
|
|
21
|
+
<WebsiteChat client:idle ... colors={{ accent: "var(--color-accent)" }} />
|
|
22
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog: 1.1.1
|
|
2
|
+
|
|
3
|
+
Calmer search results and one surface around the message field. Nothing to change on a site.
|
|
4
|
+
|
|
5
|
+
## Changed
|
|
6
|
+
|
|
7
|
+
- Search results (`RentalCarousel`) are one row on wide screens too: two cards side by side and
|
|
8
|
+
the next one peeking in, with arrows that move one card at a time. Two results fill the row and
|
|
9
|
+
have no arrows. On a phone the row shows one card with "1 / 4" as before.
|
|
10
|
+
- The area around the message field has the message area's background, so the conversation and
|
|
11
|
+
the field sit on one surface.
|
|
12
|
+
- A card no longer carries the "Passt genau" badge on its photo: the wishes it matches are ticked
|
|
13
|
+
under its name, as before.
|
|
14
|
+
- A card whose stay cannot be booked says only that ("Für diesen Zeitraum nicht buchbar"),
|
|
15
|
+
without the nights and dates of the stay asked for.
|
|
16
|
+
|
|
17
|
+
## Fixed
|
|
18
|
+
|
|
19
|
+
- In the row of search results, a card without photos holds the photo's place, so its text lines
|
|
20
|
+
up with the cards next to it. On its own it still starts with its text.
|
|
21
|
+
- The cards' shadows are no longer cut off under the row of search results.
|
|
22
|
+
|
|
23
|
+
## Unchanged
|
|
24
|
+
|
|
25
|
+
- Props, themes and colours are as before. `copy.matchBadge` is still accepted, so a site that
|
|
26
|
+
sets it keeps compiling; it is no longer shown.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Migration: 1.1.0 to 1.1.1
|
|
2
|
+
|
|
3
|
+
No changes required.
|
|
4
|
+
|
|
5
|
+
## 1. Upgrade
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add --save-exact @v-office/website-chat@1.1.1
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 2. `copy.matchBadge` (optional)
|
|
12
|
+
|
|
13
|
+
A site that sets `copy={{ matchBadge: … }}` can drop it: the badge is no longer shown.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@v-office/website-chat",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "The AI chat of v-office websites: a launcher and a lazily loaded chat panel in a Shadow DOM, talking to the AI Studio chat service, and the json-render catalog (./catalog) the service places its rental cards, stays and links from. Styled through CSS custom properties and curated template themes; consumers need no Tailwind",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"@types/node": "26.5.1",
|
|
35
35
|
"@types/react": "19.3.0",
|
|
36
36
|
"@types/react-dom": "19.3.0",
|
|
37
|
-
"@v-office/website-headless": "1.
|
|
38
|
-
"@v-office/website-sdk": "2.
|
|
37
|
+
"@v-office/website-headless": "1.5.0",
|
|
38
|
+
"@v-office/website-sdk": "3.2.0",
|
|
39
39
|
"jsdom": "30.0.1",
|
|
40
40
|
"oxfmt": "0.67.0",
|
|
41
41
|
"oxlint": "1.82.0",
|