@v-office/website-chat 1.0.1 → 1.1.0

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.
@@ -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 clearConversation, l as sendMessage, n as Cross, o as loadConversation, r as Restart, s as saveConversation, t as Arrow } from "./icons-B5PAY3aU.js";
3
- import { c as statePath, r as chatCatalog } from "./catalog-RXKmEmMW.js";
2
+ import { a as Restart, c as clearConversation, f as sendMessage, i as Cross, l as loadConversation, n as Check, r as Chevron, s as TopicIcon, t as Arrow, u as saveConversation } from "./icons-B4hI9ipR.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
- if (!stay) return path;
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
- const StayLine = ({ stay }) => {
146
- const { copy, locale } = useChat();
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,
@@ -248,35 +234,83 @@ const Snapper = ({ count, className, label, children }) => {
248
234
  })]
249
235
  });
250
236
  };
237
+ /** A rental's photos, swiped or stepped through with the arrows a pointer brings up; the dots say
238
+ * which one shows. */
251
239
  const Gallery = ({ images, name }) => {
252
240
  const { copy } = useChat();
241
+ const row = useRef(null);
242
+ const [index, setIndex] = useState(0);
253
243
  if (images.length === 0) return null;
254
- return /* @__PURE__ */ jsx(Snapper, {
255
- count: images.length,
256
- className: "gallery",
257
- label: name,
258
- children: images.map((image, index) => /* @__PURE__ */ jsx("img", {
259
- src: image.src,
260
- alt: image.alt || `${name}, ${copy.photo} ${index + 1}`,
261
- loading: "lazy"
262
- }, image.src))
244
+ const go = (to) => {
245
+ const element = row.current;
246
+ if (!element) return;
247
+ const still = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
248
+ element.scrollTo({
249
+ left: to * element.clientWidth,
250
+ behavior: still ? "auto" : "smooth"
251
+ });
252
+ };
253
+ const many = images.length > 1;
254
+ return /* @__PURE__ */ jsxs("div", {
255
+ className: "gallery-wrap",
256
+ role: "group",
257
+ "aria-roledescription": "carousel",
258
+ "aria-label": name,
259
+ children: [/* @__PURE__ */ jsx("div", {
260
+ ref: row,
261
+ className: "gallery",
262
+ onScroll: (event) => {
263
+ const element = event.currentTarget;
264
+ setIndex(Math.round(element.scrollLeft / Math.max(1, element.clientWidth)));
265
+ },
266
+ children: images.map((image, imageIndex) => /* @__PURE__ */ jsx("img", {
267
+ src: image.src,
268
+ alt: image.alt || `${name}, ${copy.photo} ${imageIndex + 1}`,
269
+ loading: "lazy"
270
+ }, image.src))
271
+ }), many && /* @__PURE__ */ jsxs(Fragment, { children: [
272
+ /* @__PURE__ */ jsx("button", {
273
+ type: "button",
274
+ className: "gallery-arrow",
275
+ "data-side": "prev",
276
+ "aria-label": copy.previous,
277
+ disabled: index === 0,
278
+ onClick: () => go(index - 1),
279
+ children: /* @__PURE__ */ jsx(Chevron, { side: "prev" })
280
+ }),
281
+ /* @__PURE__ */ jsx("button", {
282
+ type: "button",
283
+ className: "gallery-arrow",
284
+ "data-side": "next",
285
+ "aria-label": copy.next,
286
+ disabled: index >= images.length - 1,
287
+ onClick: () => go(index + 1),
288
+ children: /* @__PURE__ */ jsx(Chevron, { side: "next" })
289
+ }),
290
+ /* @__PURE__ */ jsx("span", {
291
+ className: "dots",
292
+ "aria-hidden": "true",
293
+ children: images.map((image, imageIndex) => /* @__PURE__ */ jsx("i", { "data-current": imageIndex === index || void 0 }, image.src))
294
+ }),
295
+ /* @__PURE__ */ jsx("span", {
296
+ className: "sr-only",
297
+ "aria-live": "polite",
298
+ children: `${copy.photo} ${Math.min(index + 1, images.length)} / ${images.length}`
299
+ })
300
+ ] })]
263
301
  });
264
302
  };
303
+ /** The PMS's score as a chip; its number of reviews is read out, not shown. */
265
304
  const Rating = ({ rental }) => {
266
305
  const { copy, locale } = useChat();
267
306
  if (!rental.rating) return null;
268
307
  const { value, count } = rental.rating;
269
- return /* @__PURE__ */ jsxs("span", {
270
- className: "muted",
271
- children: [
272
- /* @__PURE__ */ jsxs("span", {
273
- className: "rating",
274
- children: ["★ ", value]
275
- }),
276
- " (",
277
- counted(copy.reviews, reviewCount(count), locale),
278
- ")"
279
- ]
308
+ return /* @__PURE__ */ jsx("span", {
309
+ className: "score-chip",
310
+ role: "img",
311
+ title: counted(copy.reviews, reviewCount(count), locale),
312
+ "aria-label": `${value}, ${counted(copy.reviews, reviewCount(count), locale)}`,
313
+ children: value
280
314
  });
281
315
  };
282
316
  /** The visitor's wishes the latest search that found the rental applied, which it therefore has. */
@@ -287,7 +321,42 @@ const Wishes = ({ wishes }) => {
287
321
  return /* @__PURE__ */ jsx("ul", {
288
322
  className: "wishes",
289
323
  "aria-label": copy.matchesWishes,
290
- children: wishes.map((wish) => /* @__PURE__ */ jsx("li", { children: wish }, wish))
324
+ children: wishes.map((wish) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx(Check, {}), wish] }, wish))
325
+ });
326
+ };
327
+ /** "7 Nächte · 26.12.–2.1.": a stay's length and dates, short. */
328
+ const stayMeta = (stay, copy, locale) => {
329
+ const nights = Math.round((day(stay.end).getTime() - day(stay.start).getTime()) / 864e5);
330
+ const short = new Intl.DateTimeFormat(locale, {
331
+ day: "numeric",
332
+ month: "numeric",
333
+ timeZone: "UTC"
334
+ });
335
+ return `${counted(copy.nights, nights, locale)} · ${short.format(day(stay.start))}–${short.format(day(stay.end))}`;
336
+ };
337
+ /** A card's foot: the stay's total and its length, and the way to book or to the rental. */
338
+ const CardFoot = ({ rentalId, stay }) => {
339
+ const { copy, locale } = useChat();
340
+ const href = useBookHref(rentalId, stay);
341
+ return /* @__PURE__ */ jsxs("div", {
342
+ className: "card-foot",
343
+ children: [stay && /* @__PURE__ */ jsxs("div", {
344
+ className: "foot-price",
345
+ children: [!stay.bookable ? /* @__PURE__ */ jsx("span", {
346
+ className: "muted",
347
+ children: copy.notBookable
348
+ }) : stay.total && /* @__PURE__ */ jsx("span", {
349
+ className: "price",
350
+ children: stay.total
351
+ }), /* @__PURE__ */ jsx("span", {
352
+ className: "foot-meta",
353
+ children: stayMeta(stay, copy, locale)
354
+ })]
355
+ }), /* @__PURE__ */ jsx(ReplyLink, {
356
+ className: "button compact",
357
+ href,
358
+ children: stay?.bookable ? copy.book : copy.view
359
+ })]
291
360
  });
292
361
  };
293
362
  const Card = ({ rentalId, stayId }) => {
@@ -297,41 +366,45 @@ const Card = ({ rentalId, stayId }) => {
297
366
  const wishes = useWishes(rentalId);
298
367
  if (!rental) return null;
299
368
  return /* @__PURE__ */ jsxs("article", {
300
- className: "card",
301
- children: [/* @__PURE__ */ jsxs("div", {
302
- className: "card-media",
303
- children: [/* @__PURE__ */ jsx(Gallery, {
304
- images: rental.images,
305
- name: rental.name
306
- }), wishes.length > 0 ? /* @__PURE__ */ jsx("span", {
307
- className: "badge",
308
- "aria-hidden": "true",
309
- children: copy.matchesWishes
310
- }) : null]
311
- }), /* @__PURE__ */ jsxs("div", {
312
- className: "card-body",
313
- children: [
314
- /* @__PURE__ */ jsx("h3", {
315
- className: "card-title",
316
- children: rental.name
317
- }),
318
- /* @__PURE__ */ jsx(Wishes, { wishes }),
319
- rental.place && /* @__PURE__ */ jsx("span", {
320
- className: "muted",
321
- children: rental.place
322
- }),
323
- /* @__PURE__ */ jsx(Rating, { rental }),
324
- rental.highlights.length > 0 && /* @__PURE__ */ jsx("span", {
325
- className: "muted",
326
- children: rental.highlights.slice(0, 3).join(" · ")
327
- }),
328
- stay && /* @__PURE__ */ jsx(StayLine, { stay }),
329
- /* @__PURE__ */ jsx(BookLink, {
330
- rentalId: rental.id,
331
- stay
332
- })
333
- ]
334
- })]
369
+ className: "card rental-card",
370
+ children: [
371
+ /* @__PURE__ */ jsxs("div", {
372
+ className: "card-media",
373
+ children: [/* @__PURE__ */ jsx(Gallery, {
374
+ images: rental.images,
375
+ name: rental.name
376
+ }), wishes.length > 0 ? /* @__PURE__ */ jsx("span", {
377
+ className: "badge",
378
+ "aria-hidden": "true",
379
+ children: copy.matchBadge
380
+ }) : null]
381
+ }),
382
+ /* @__PURE__ */ jsxs("div", {
383
+ className: "card-body",
384
+ children: [
385
+ /* @__PURE__ */ jsxs("div", {
386
+ className: "card-meta",
387
+ children: [rental.place && /* @__PURE__ */ jsx("span", {
388
+ className: "place",
389
+ children: rental.place
390
+ }), /* @__PURE__ */ jsx(Rating, { rental })]
391
+ }),
392
+ /* @__PURE__ */ jsx("h3", {
393
+ className: "card-title",
394
+ children: rental.name
395
+ }),
396
+ rental.highlights.length > 0 && /* @__PURE__ */ jsx("p", {
397
+ className: "specs",
398
+ children: rental.highlights.slice(0, 3).join(" · ")
399
+ }),
400
+ /* @__PURE__ */ jsx(Wishes, { wishes })
401
+ ]
402
+ }),
403
+ /* @__PURE__ */ jsx(CardFoot, {
404
+ rentalId: rental.id,
405
+ stay
406
+ })
407
+ ]
335
408
  });
336
409
  };
337
410
  /** Monday-first weeks of one month, as `YYYY-MM-DD` days and `null` padding. */
@@ -687,25 +760,25 @@ const { registry } = defineRegistry(chatCatalog, { components: {
687
760
  });
688
761
  },
689
762
  StayQuote: ({ props }) => {
763
+ const { copy, locale } = useChat();
690
764
  const stay = useStay(props.stayId);
691
765
  const rental = useRental(stay?.rentalId ?? "");
692
766
  if (!stay) return null;
693
- return /* @__PURE__ */ jsx("article", {
694
- className: "card",
695
- children: /* @__PURE__ */ jsxs("div", {
767
+ return /* @__PURE__ */ jsxs("article", {
768
+ className: "card stay-card",
769
+ children: [/* @__PURE__ */ jsxs("div", {
696
770
  className: "card-body",
697
- children: [
698
- rental && /* @__PURE__ */ jsx("h3", {
699
- className: "card-title",
700
- children: rental.name
701
- }),
702
- /* @__PURE__ */ jsx(StayLine, { stay }),
703
- /* @__PURE__ */ jsx(BookLink, {
704
- rentalId: stay.rentalId,
705
- stay
706
- })
707
- ]
708
- })
771
+ children: [rental && /* @__PURE__ */ jsx("h3", {
772
+ className: "card-title",
773
+ children: rental.name
774
+ }), /* @__PURE__ */ jsx("p", {
775
+ className: "specs",
776
+ children: partyText(stay, copy, locale)
777
+ })]
778
+ }), /* @__PURE__ */ jsx(CardFoot, {
779
+ rentalId: stay.rentalId,
780
+ stay
781
+ })]
709
782
  });
710
783
  },
711
784
  PriceBreakdown: ({ props }) => {
@@ -784,12 +857,12 @@ const { registry } = defineRegistry(chatCatalog, { components: {
784
857
  if (!send) return null;
785
858
  return /* @__PURE__ */ jsx("div", {
786
859
  className: "quick-replies",
787
- children: props.options.map((option) => /* @__PURE__ */ jsx("button", {
860
+ children: props.options.map((option) => /* @__PURE__ */ jsxs("button", {
788
861
  type: "button",
789
862
  className: "chip",
790
863
  disabled: busy,
791
864
  onClick: () => send(option),
792
- children: option
865
+ children: [/* @__PURE__ */ jsx(TopicIcon, { text: option }), option]
793
866
  }, option))
794
867
  });
795
868
  }
@@ -837,6 +910,9 @@ const lockPageScroll = () => {
837
910
  root.setAttribute(SCROLL_LOCK, "");
838
911
  return () => root.removeAttribute(SCROLL_LOCK);
839
912
  };
913
+ /** The assistant's text, its `**bold**` (a rental's name, the dates) as strong text: nothing else
914
+ * is read as markup, so the model's text never becomes HTML. */
915
+ const RichText = ({ text }) => /* @__PURE__ */ jsx(Fragment, { children: text.split(/\*\*(.+?)\*\*/g).map((piece, index) => index % 2 === 1 ? /* @__PURE__ */ jsx("strong", { children: piece }, index) : piece) });
840
916
  const SpecView = ({ spec }) => /* @__PURE__ */ jsx(JSONUIProvider, {
841
917
  registry,
842
918
  initialState: spec.state ?? {},
@@ -848,6 +924,11 @@ const SpecView = ({ spec }) => /* @__PURE__ */ jsx(JSONUIProvider, {
848
924
  const Consent = ({ copy, pending, onAccept, buttonRef }) => /* @__PURE__ */ jsxs("div", {
849
925
  className: "consent",
850
926
  children: [
927
+ /* @__PURE__ */ jsx("p", {
928
+ className: "message",
929
+ "data-role": "assistant",
930
+ children: copy.greeting
931
+ }),
851
932
  pending && /* @__PURE__ */ jsxs("figure", {
852
933
  className: "pending",
853
934
  children: [/* @__PURE__ */ jsx("figcaption", {
@@ -855,13 +936,15 @@ const Consent = ({ copy, pending, onAccept, buttonRef }) => /* @__PURE__ */ jsxs
855
936
  children: copy.yourMessage
856
937
  }), /* @__PURE__ */ jsx("p", { children: pending })]
857
938
  }),
858
- /* @__PURE__ */ jsx("p", { children: copy.consent }),
859
- /* @__PURE__ */ jsx("button", {
860
- ref: buttonRef,
861
- type: "button",
862
- className: "button",
863
- onClick: onAccept,
864
- children: copy.consentAccept
939
+ /* @__PURE__ */ jsxs("div", {
940
+ className: "consent-card",
941
+ children: [/* @__PURE__ */ jsx("p", { children: copy.consent }), /* @__PURE__ */ jsx("button", {
942
+ ref: buttonRef,
943
+ type: "button",
944
+ className: "button compact",
945
+ onClick: onAccept,
946
+ children: copy.consentAccept
947
+ })]
865
948
  })
866
949
  ]
867
950
  });
@@ -1084,7 +1167,7 @@ function Panel(props) {
1084
1167
  children: message.parts.map((part, partIndex) => part.kind === "text" ? /* @__PURE__ */ jsx("div", {
1085
1168
  className: "message",
1086
1169
  "data-role": message.role,
1087
- children: part.text
1170
+ children: message.role === "assistant" ? /* @__PURE__ */ jsx(RichText, { text: part.text }) : part.text
1088
1171
  }, partIndex) : /* @__PURE__ */ jsx(SpecView, { spec: part.spec }, partIndex))
1089
1172
  }, index)),
1090
1173
  waiting && /* @__PURE__ */ jsx(Thinking, { label: copy.thinking }),
@@ -1115,11 +1198,11 @@ function Panel(props) {
1115
1198
  }),
1116
1199
  !hasMessages && copy.suggestions.length > 0 && /* @__PURE__ */ jsx("div", {
1117
1200
  className: "suggestions",
1118
- children: copy.suggestions.map((suggestion) => /* @__PURE__ */ jsx("button", {
1201
+ children: copy.suggestions.map((suggestion) => /* @__PURE__ */ jsxs("button", {
1119
1202
  type: "button",
1120
1203
  className: "suggestion",
1121
1204
  onClick: () => void submit(suggestion),
1122
- children: suggestion
1205
+ children: [/* @__PURE__ */ jsx(TopicIcon, { text: suggestion }), suggestion]
1123
1206
  }, suggestion))
1124
1207
  }),
1125
1208
  /* @__PURE__ */ jsxs("form", {
@@ -4,5 +4,6 @@
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.0/CHANGELOG.md`: release notes for 1.1.0
7
8
  - `versions/1.0.1/CHANGELOG.md`: release notes for 1.0.1
8
9
  - `versions/1.0.0/CHANGELOG.md`: release notes for 1.0.0
@@ -4,5 +4,6 @@
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.0/MIGRATION.md`: Migration: 1.0.1 to 1.1.0
7
8
  - `versions/1.0.1/MIGRATION.md`: Migration: 1.0.0 to 1.0.1
8
9
  - `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
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v-office/website-chat",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
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,7 +34,7 @@
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.1.1",
37
+ "@v-office/website-headless": "1.2.0",
38
38
  "@v-office/website-sdk": "2.34.0",
39
39
  "jsdom": "30.0.1",
40
40
  "oxfmt": "0.67.0",