@v-office/website-chat 1.1.2 → 1.2.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.
- package/README.md +2 -2
- package/dist/{catalog-y3xQ2jXW.js → catalog-ClcCUjf7.js} +13 -13
- package/dist/catalog.js +1 -1
- package/dist/{icons-DIqOmKoe.js → icons-BK5Lk0cP.js} +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +175 -74
- package/dist/{panel-BsocDe9I.js → panel-CfS3SuRX.js} +301 -146
- package/instructions/CHANGELOG.md +2 -0
- package/instructions/MIGRATION.md +2 -0
- package/instructions/versions/1.1.3/CHANGELOG.md +18 -0
- package/instructions/versions/1.1.3/MIGRATION.md +9 -0
- package/instructions/versions/1.2.0/CHANGELOG.md +45 -0
- package/instructions/versions/1.2.0/MIGRATION.md +15 -0
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as SESSION_TTL_S, r as MAX_MESSAGE_CHARS } from "./protocol-BuNqZAMt.js";
|
|
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-
|
|
3
|
-
import { r as chatCatalog, u as statePath } from "./catalog-
|
|
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-BK5Lk0cP.js";
|
|
3
|
+
import { r as chatCatalog, u as statePath } from "./catalog-ClcCUjf7.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";
|
|
@@ -23,6 +23,14 @@ const stayHref = (page, stay, searchPath) => {
|
|
|
23
23
|
const bookingHref = (bookingPage, stay) => withQuery(bookingPage, [...stayPairs(stay), ...(stay.services ?? []).filter((service) => service.count > 0).map((service) => ["service", `${service.id}:${service.count}`])]);
|
|
24
24
|
//#endregion
|
|
25
25
|
//#region src/map.tsx
|
|
26
|
+
/** MapLibre and its worker, loaded on the first tap. MapLibre finds its worker beside its own module
|
|
27
|
+
* by default; bundled, the module is a hashed chunk with nothing beside it, so `?worker&url` has the
|
|
28
|
+
* site's bundler emit the worker as one self-contained file (MapLibre's own Vite setup), and
|
|
29
|
+
* `setWorkerUrl` points MapLibre at it before the first map reads it. */
|
|
30
|
+
const loadMapLibre = () => Promise.all([import("maplibre-gl"), import("maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url")]).then(([library, worker]) => {
|
|
31
|
+
library.setWorkerUrl(worker.default);
|
|
32
|
+
return library;
|
|
33
|
+
});
|
|
26
34
|
/** What the style's sources ask to be credited, with their links, as MapLibre's attribution control
|
|
27
35
|
* would show it (OpenStreetMap's to its copyright page). */
|
|
28
36
|
const Attribution = () => /* @__PURE__ */ jsxs("p", {
|
|
@@ -61,10 +69,8 @@ const MapCard = ({ rental, label, notice, load, children }) => {
|
|
|
61
69
|
if (!shown || !element || !location) return;
|
|
62
70
|
let removed = false;
|
|
63
71
|
let map;
|
|
64
|
-
|
|
72
|
+
loadMapLibre().then(({ Map, Marker }) => {
|
|
65
73
|
if (removed) return;
|
|
66
|
-
const library = module;
|
|
67
|
-
const { Map, Marker } = library.default ?? library;
|
|
68
74
|
const center = [location.lng, location.lat];
|
|
69
75
|
const created = new Map({
|
|
70
76
|
container: element,
|
|
@@ -124,26 +130,39 @@ const useStay = (stayId, rentalId) => {
|
|
|
124
130
|
};
|
|
125
131
|
const useRental = (rentalId) => useStateValue(statePath("rentals", rentalId)) ?? null;
|
|
126
132
|
const day = (value) => /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
|
|
133
|
+
/** The locale dates are formatted in: British English ("13 May"), as the replies write dates and as
|
|
134
|
+
* European visitors read them, never the US order "5/13" that a bare "en" gives. */
|
|
135
|
+
const dateLocale = (locale) => locale === "en" ? "en-GB" : "de-DE";
|
|
127
136
|
/** "12.–19. Juli 2027" from two `YYYY-MM-DD` days. */
|
|
128
|
-
const period = (stay, locale) => new Intl.DateTimeFormat(locale, {
|
|
137
|
+
const period = (stay, locale) => new Intl.DateTimeFormat(dateLocale(locale), {
|
|
129
138
|
day: "numeric",
|
|
130
139
|
month: "short",
|
|
131
140
|
year: "numeric",
|
|
132
141
|
timeZone: "UTC"
|
|
133
142
|
}).formatRange(day(stay.start), day(stay.end));
|
|
134
|
-
/** "14.–21. Juli": the short form
|
|
135
|
-
const shortPeriod = (stay, locale) =>
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
/** "14.–21. Juli", "26. Dez.–2. Jan.": the short form, without years even over New Year. */
|
|
144
|
+
const shortPeriod = (stay, locale) => {
|
|
145
|
+
const short = new Intl.DateTimeFormat(dateLocale(locale), {
|
|
146
|
+
day: "numeric",
|
|
147
|
+
month: "short",
|
|
148
|
+
timeZone: "UTC"
|
|
149
|
+
});
|
|
150
|
+
return stay.start.slice(0, 4) === stay.end.slice(0, 4) ? short.formatRange(day(stay.start), day(stay.end)) : `${short.format(day(stay.start))}–${short.format(day(stay.end))}`;
|
|
151
|
+
};
|
|
152
|
+
/** "1 Kind", "2 Kinder": a count in its plural form, its number and noun joined by a no-break space,
|
|
153
|
+
* so a narrow card never ends a line on the "2". */
|
|
154
|
+
const counted = (forms, n, locale) => (forms[new Intl.PluralRules(locale).select(n)] ?? forms.other).replace("{n} ", `${n}\u00a0`).replace("{n}", String(n));
|
|
155
|
+
/** "2 Schlafzimmer" from the PMS, its number and noun joined by a no-break space as in `counted`. */
|
|
156
|
+
const keepCount = (fact) => fact.replace(/^(\d+) /, "$1\xA0");
|
|
142
157
|
/** A review count as the PMS formats it ("36", or "1 Bewertungen" on v10), as the number it states. */
|
|
143
158
|
const reviewCount = (count) => Number(count.replace(/\D/g, ""));
|
|
144
159
|
const partyText = (stay, copy, locale) => {
|
|
145
160
|
const children = stay.party.children + stay.party.babies;
|
|
146
|
-
return [
|
|
161
|
+
return [
|
|
162
|
+
counted(copy.adults, stay.party.adults, locale),
|
|
163
|
+
...children ? [counted(copy.children, children, locale)] : [],
|
|
164
|
+
...stay.party.pets ? [counted(copy.pets, stay.party.pets, locale)] : []
|
|
165
|
+
].join(", ");
|
|
147
166
|
};
|
|
148
167
|
/** A bookable stay goes to the rental's booking page with everything chosen, where the site has one;
|
|
149
168
|
* otherwise to the rental page (or the search) with the stay filled in. */
|
|
@@ -168,12 +187,25 @@ const ReplyLink = ({ href, className, children }) => {
|
|
|
168
187
|
children
|
|
169
188
|
});
|
|
170
189
|
};
|
|
190
|
+
/** What a booking link is for, for a screen reader: in a links list, eight "Buchen" say nothing. */
|
|
191
|
+
const LinkTarget = ({ rentalId, stay }) => {
|
|
192
|
+
const { locale } = useChat();
|
|
193
|
+
const rental = useRental(rentalId);
|
|
194
|
+
if (!rental) return null;
|
|
195
|
+
return /* @__PURE__ */ jsx("span", {
|
|
196
|
+
className: "sr-only",
|
|
197
|
+
children: `, ${rental.name}${stay?.bookable ? `, ${shortPeriod(stay, locale)}` : ""}`
|
|
198
|
+
});
|
|
199
|
+
};
|
|
171
200
|
const BookLink = ({ rentalId, stay }) => {
|
|
172
201
|
const { copy } = useChat();
|
|
173
|
-
return /* @__PURE__ */
|
|
202
|
+
return /* @__PURE__ */ jsxs(ReplyLink, {
|
|
174
203
|
className: "button",
|
|
175
204
|
href: useBookHref(rentalId, stay),
|
|
176
|
-
children: stay?.bookable ? copy.bookStay : copy.viewRental
|
|
205
|
+
children: [stay?.bookable ? copy.bookStay : copy.viewRental, /* @__PURE__ */ jsx(LinkTarget, {
|
|
206
|
+
rentalId,
|
|
207
|
+
stay
|
|
208
|
+
})]
|
|
177
209
|
});
|
|
178
210
|
};
|
|
179
211
|
/** A row that snaps item by item, with arrow buttons and "n / total"; swipe, scroll or keys move it. */
|
|
@@ -214,7 +246,7 @@ const Snapper = ({ count, className, label, children }) => {
|
|
|
214
246
|
"aria-label": copy.previous,
|
|
215
247
|
disabled: index === 0,
|
|
216
248
|
onClick: () => go(-1),
|
|
217
|
-
children: "
|
|
249
|
+
children: /* @__PURE__ */ jsx(Chevron, { side: "prev" })
|
|
218
250
|
}),
|
|
219
251
|
/* @__PURE__ */ jsxs("span", {
|
|
220
252
|
className: "muted",
|
|
@@ -229,7 +261,7 @@ const Snapper = ({ count, className, label, children }) => {
|
|
|
229
261
|
"aria-label": copy.next,
|
|
230
262
|
disabled: index >= count - 1,
|
|
231
263
|
onClick: () => go(1),
|
|
232
|
-
children: "
|
|
264
|
+
children: /* @__PURE__ */ jsx(Chevron, { side: "next" })
|
|
233
265
|
})
|
|
234
266
|
]
|
|
235
267
|
})]
|
|
@@ -295,7 +327,6 @@ const Gallery = ({ images, name }) => {
|
|
|
295
327
|
}),
|
|
296
328
|
/* @__PURE__ */ jsx("span", {
|
|
297
329
|
className: "sr-only",
|
|
298
|
-
"aria-live": "polite",
|
|
299
330
|
children: `${copy.photo} ${Math.min(index + 1, images.length)} / ${images.length}`
|
|
300
331
|
})
|
|
301
332
|
] })]
|
|
@@ -325,17 +356,21 @@ const Wishes = ({ wishes }) => {
|
|
|
325
356
|
children: wishes.map((wish) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx(Check, {}), wish] }, wish))
|
|
326
357
|
});
|
|
327
358
|
};
|
|
328
|
-
/** "7 Nächte ·
|
|
359
|
+
/** "7 Nächte · 6.–13. Mai": a stay's length and dates, short. */
|
|
329
360
|
const stayMeta = (stay, copy, locale) => {
|
|
330
361
|
const nights = Math.round((day(stay.end).getTime() - day(stay.start).getTime()) / 864e5);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
362
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
363
|
+
counted(copy.nights, nights, locale),
|
|
364
|
+
" ·",
|
|
365
|
+
" ",
|
|
366
|
+
/* @__PURE__ */ jsx("span", {
|
|
367
|
+
className: "period",
|
|
368
|
+
children: shortPeriod(stay, locale)
|
|
369
|
+
})
|
|
370
|
+
] });
|
|
337
371
|
};
|
|
338
|
-
/** A card's foot: the stay's total
|
|
372
|
+
/** A card's foot: the stay's total, its length and dates and the party it is for, and the way to
|
|
373
|
+
* book or to the rental. */
|
|
339
374
|
const CardFoot = ({ rentalId, stay }) => {
|
|
340
375
|
const { copy, locale } = useChat();
|
|
341
376
|
const href = useBookHref(rentalId, stay);
|
|
@@ -344,19 +379,29 @@ const CardFoot = ({ rentalId, stay }) => {
|
|
|
344
379
|
children: [stay && /* @__PURE__ */ jsx("div", {
|
|
345
380
|
className: "foot-price",
|
|
346
381
|
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
382
|
className: "foot-meta",
|
|
354
|
-
children:
|
|
355
|
-
})
|
|
356
|
-
|
|
383
|
+
children: copy.notBookable
|
|
384
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
385
|
+
stay.total && /* @__PURE__ */ jsx("span", {
|
|
386
|
+
className: "price",
|
|
387
|
+
children: stay.total
|
|
388
|
+
}),
|
|
389
|
+
/* @__PURE__ */ jsx("span", {
|
|
390
|
+
className: "foot-meta",
|
|
391
|
+
children: stayMeta(stay, copy, locale)
|
|
392
|
+
}),
|
|
393
|
+
/* @__PURE__ */ jsx("span", {
|
|
394
|
+
className: "foot-meta",
|
|
395
|
+
children: partyText(stay, copy, locale)
|
|
396
|
+
})
|
|
397
|
+
] })
|
|
398
|
+
}), /* @__PURE__ */ jsxs(ReplyLink, {
|
|
357
399
|
className: "button compact",
|
|
358
400
|
href,
|
|
359
|
-
children: stay?.bookable ? copy.book : copy.view
|
|
401
|
+
children: [stay?.bookable ? copy.book : copy.view, /* @__PURE__ */ jsx(LinkTarget, {
|
|
402
|
+
rentalId,
|
|
403
|
+
stay
|
|
404
|
+
})]
|
|
360
405
|
})]
|
|
361
406
|
});
|
|
362
407
|
};
|
|
@@ -392,7 +437,7 @@ const Card = ({ rentalId, stayId }) => {
|
|
|
392
437
|
}),
|
|
393
438
|
rental.highlights.length > 0 && /* @__PURE__ */ jsx("p", {
|
|
394
439
|
className: "specs",
|
|
395
|
-
children: rental.highlights.slice(0, 3).join(" · ")
|
|
440
|
+
children: rental.highlights.slice(0, 3).map(keepCount).join(" · ")
|
|
396
441
|
}),
|
|
397
442
|
/* @__PURE__ */ jsx(Wishes, { wishes })
|
|
398
443
|
]
|
|
@@ -430,17 +475,17 @@ const Calendar = ({ rentalId, stayId }) => {
|
|
|
430
475
|
const [shown, setShown] = useState(() => Math.max(0, stay ? months.indexOf(stay.start.slice(0, 7)) : 0));
|
|
431
476
|
if (!calendar || months.length === 0) return null;
|
|
432
477
|
const month = months[Math.min(shown, months.length - 1)];
|
|
433
|
-
const weekdays = Array.from({ length: 7 }, (_, i) => new Intl.DateTimeFormat(locale, {
|
|
478
|
+
const weekdays = Array.from({ length: 7 }, (_, i) => new Intl.DateTimeFormat(dateLocale(locale), {
|
|
434
479
|
weekday: "short",
|
|
435
480
|
timeZone: "UTC"
|
|
436
481
|
}).format(day(`2024-01-0${i + 1}`)));
|
|
437
|
-
const title = new Intl.DateTimeFormat(locale, {
|
|
482
|
+
const title = new Intl.DateTimeFormat(dateLocale(locale), {
|
|
438
483
|
month: "long",
|
|
439
484
|
year: "numeric",
|
|
440
485
|
timeZone: "UTC"
|
|
441
486
|
}).format(day(`${month}-01`));
|
|
442
487
|
const inStay = (date) => Boolean(stay && date >= stay.start && date <= stay.end);
|
|
443
|
-
const fullDate = new Intl.DateTimeFormat(locale, {
|
|
488
|
+
const fullDate = new Intl.DateTimeFormat(dateLocale(locale), {
|
|
444
489
|
weekday: "long",
|
|
445
490
|
day: "numeric",
|
|
446
491
|
month: "long",
|
|
@@ -462,7 +507,7 @@ const Calendar = ({ rentalId, stayId }) => {
|
|
|
462
507
|
"aria-label": copy.previous,
|
|
463
508
|
disabled: shown === 0,
|
|
464
509
|
onClick: () => setShown(shown - 1),
|
|
465
|
-
children: "
|
|
510
|
+
children: /* @__PURE__ */ jsx(Chevron, { side: "prev" })
|
|
466
511
|
}),
|
|
467
512
|
/* @__PURE__ */ jsxs("span", { children: [title, rental ? ` · ${rental.name}` : ""] }),
|
|
468
513
|
/* @__PURE__ */ jsx("button", {
|
|
@@ -470,7 +515,7 @@ const Calendar = ({ rentalId, stayId }) => {
|
|
|
470
515
|
"aria-label": copy.next,
|
|
471
516
|
disabled: shown >= months.length - 1,
|
|
472
517
|
onClick: () => setShown(shown + 1),
|
|
473
|
-
children: "
|
|
518
|
+
children: /* @__PURE__ */ jsx(Chevron, { side: "next" })
|
|
474
519
|
})
|
|
475
520
|
]
|
|
476
521
|
}),
|
|
@@ -517,6 +562,8 @@ const CompareColumn = ({ rentalId, stayId }) => {
|
|
|
517
562
|
const stay = useStay(stayId, rentalId);
|
|
518
563
|
const href = useBookHref(rentalId, stay);
|
|
519
564
|
if (!rental) return null;
|
|
565
|
+
const rooms = rental.rooms.slice(0, 3);
|
|
566
|
+
const highlights = rental.highlights.filter((highlight) => !rooms.includes(highlight));
|
|
520
567
|
return /* @__PURE__ */ jsxs("div", {
|
|
521
568
|
className: "compare-col",
|
|
522
569
|
children: [
|
|
@@ -525,12 +572,17 @@ const CompareColumn = ({ rentalId, stayId }) => {
|
|
|
525
572
|
/* @__PURE__ */ jsx("dt", { children: copy.compareRating }),
|
|
526
573
|
/* @__PURE__ */ jsx("dd", { children: rental.rating ? `★ ${rental.rating.value} (${counted(copy.reviews, reviewCount(rental.rating.count), locale)})` : "–" }),
|
|
527
574
|
/* @__PURE__ */ jsx("dt", { children: copy.compareRooms }),
|
|
528
|
-
/* @__PURE__ */ jsx("dd", { children:
|
|
575
|
+
/* @__PURE__ */ jsx("dd", { children: rooms.map(keepCount).join(", ") || "–" }),
|
|
529
576
|
/* @__PURE__ */ jsx("dt", { children: copy.compareHighlights }),
|
|
530
|
-
/* @__PURE__ */ jsx("dd", { children:
|
|
577
|
+
/* @__PURE__ */ jsx("dd", { children: highlights.slice(0, 4).map(keepCount).join(", ") || "–" }),
|
|
531
578
|
stay && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("dt", { children: copy.compareStay }), /* @__PURE__ */ jsxs("dd", { children: [
|
|
532
579
|
shortPeriod(stay, locale),
|
|
533
580
|
/* @__PURE__ */ jsx("br", {}),
|
|
581
|
+
/* @__PURE__ */ jsx("span", {
|
|
582
|
+
className: "muted",
|
|
583
|
+
children: partyText(stay, copy, locale)
|
|
584
|
+
}),
|
|
585
|
+
/* @__PURE__ */ jsx("br", {}),
|
|
534
586
|
!stay.bookable ? /* @__PURE__ */ jsx("span", {
|
|
535
587
|
className: "muted",
|
|
536
588
|
children: copy.notBookable
|
|
@@ -540,23 +592,24 @@ const CompareColumn = ({ rentalId, stayId }) => {
|
|
|
540
592
|
})
|
|
541
593
|
] })] })
|
|
542
594
|
] }),
|
|
543
|
-
/* @__PURE__ */
|
|
595
|
+
/* @__PURE__ */ jsxs(ReplyLink, {
|
|
544
596
|
className: "button",
|
|
545
597
|
href,
|
|
546
|
-
children: stay?.bookable ? copy.
|
|
598
|
+
children: [stay?.bookable ? copy.book : copy.view, /* @__PURE__ */ jsx(LinkTarget, {
|
|
599
|
+
rentalId,
|
|
600
|
+
stay
|
|
601
|
+
})]
|
|
547
602
|
})
|
|
548
603
|
]
|
|
549
604
|
});
|
|
550
605
|
};
|
|
551
|
-
const AlternativeChip = ({
|
|
606
|
+
const AlternativeChip = ({ stay }) => {
|
|
552
607
|
const { locale } = useChat();
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
if (!stay?.bookable) return null;
|
|
556
|
-
return /* @__PURE__ */ jsxs(ReplyLink, {
|
|
608
|
+
const href = useBookHref(stay.rentalId, stay);
|
|
609
|
+
return /* @__PURE__ */ jsx(ReplyLink, {
|
|
557
610
|
className: "chip-link",
|
|
558
611
|
href,
|
|
559
|
-
children: [/* @__PURE__ */ jsx("strong", { children: shortPeriod(stay, locale) }), stay.total ? ` · ${stay.total}` : ""]
|
|
612
|
+
children: /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsx("strong", { children: shortPeriod(stay, locale) }), stay.total ? ` · ${stay.total}` : ""] })
|
|
560
613
|
});
|
|
561
614
|
};
|
|
562
615
|
/** One arrival of a price calendar: its bar, the PMS's total, the day; tapping asks for that stay. */
|
|
@@ -565,10 +618,8 @@ const PriceColumn = ({ rentalId, option, asked, cheapest }) => {
|
|
|
565
618
|
const rental = useRental(rentalId);
|
|
566
619
|
const stay = useStay(option.stayId, rentalId);
|
|
567
620
|
const free = Boolean(stay?.bookable && stay.total);
|
|
568
|
-
const arrival = new Intl.DateTimeFormat(locale, {
|
|
569
|
-
|
|
570
|
-
day: "numeric",
|
|
571
|
-
month: "numeric",
|
|
621
|
+
const arrival = (options) => new Intl.DateTimeFormat(dateLocale(locale), {
|
|
622
|
+
...options,
|
|
572
623
|
timeZone: "UTC"
|
|
573
624
|
}).format(day(option.start));
|
|
574
625
|
const dates = period(option, locale);
|
|
@@ -600,18 +651,45 @@ const PriceColumn = ({ rentalId, option, asked, cheapest }) => {
|
|
|
600
651
|
"aria-hidden": "true",
|
|
601
652
|
children: free ? stay.total : copy.closed
|
|
602
653
|
}),
|
|
603
|
-
/* @__PURE__ */
|
|
654
|
+
/* @__PURE__ */ jsxs("span", {
|
|
604
655
|
className: "muted",
|
|
605
656
|
"aria-hidden": "true",
|
|
606
|
-
children:
|
|
657
|
+
children: [
|
|
658
|
+
arrival({ weekday: "short" }),
|
|
659
|
+
/* @__PURE__ */ jsx("br", {}),
|
|
660
|
+
arrival({
|
|
661
|
+
day: "numeric",
|
|
662
|
+
month: "short"
|
|
663
|
+
})
|
|
664
|
+
]
|
|
607
665
|
})
|
|
608
666
|
]
|
|
609
667
|
}) });
|
|
610
668
|
};
|
|
669
|
+
/** Which ends of a scrolled row have more beyond them, for the fade there. */
|
|
670
|
+
const hiddenEdges = (row) => {
|
|
671
|
+
const start = row.scrollLeft > 1;
|
|
672
|
+
const end = row.scrollLeft + row.clientWidth < row.scrollWidth - 1;
|
|
673
|
+
return start && end ? "both" : start ? "start" : end ? "end" : void 0;
|
|
674
|
+
};
|
|
611
675
|
const PriceCalendarView = ({ rentalId }) => {
|
|
612
676
|
const { copy, locale } = useChat();
|
|
613
677
|
const rental = useRental(rentalId);
|
|
614
678
|
const calendar = useStateValue(statePath("priceCalendars", rentalId)) ?? null;
|
|
679
|
+
const row = useRef(null);
|
|
680
|
+
const [fade, setFade] = useState();
|
|
681
|
+
useEffect(() => {
|
|
682
|
+
const element = row.current;
|
|
683
|
+
if (!element || !calendar) return;
|
|
684
|
+
const cheapest = calendar.options.findIndex((option) => option.stayId !== null && option.stayId === calendar.cheapest);
|
|
685
|
+
const asked = calendar.options.findIndex((option) => option.start === calendar.asked);
|
|
686
|
+
const column = element.children[cheapest >= 0 ? cheapest : asked];
|
|
687
|
+
if (column) {
|
|
688
|
+
const end = column.offsetLeft + column.offsetWidth;
|
|
689
|
+
if (end > element.clientWidth) element.scrollLeft = end - element.clientWidth;
|
|
690
|
+
}
|
|
691
|
+
setFade(hiddenEdges(element));
|
|
692
|
+
}, [calendar]);
|
|
615
693
|
if (!calendar || calendar.options.length === 0) return null;
|
|
616
694
|
return /* @__PURE__ */ jsx("section", {
|
|
617
695
|
className: "card price-calendar",
|
|
@@ -628,7 +706,10 @@ const PriceCalendarView = ({ rentalId }) => {
|
|
|
628
706
|
children: [counted(copy.priceCalendarNights, calendar.nights, locale), rental ? ` · ${rental.name}` : ""]
|
|
629
707
|
}),
|
|
630
708
|
/* @__PURE__ */ jsx("ul", {
|
|
709
|
+
ref: row,
|
|
631
710
|
className: "price-columns",
|
|
711
|
+
"data-fade": fade,
|
|
712
|
+
onScroll: (event) => setFade(hiddenEdges(event.currentTarget)),
|
|
632
713
|
children: calendar.options.map((option) => /* @__PURE__ */ jsx(PriceColumn, {
|
|
633
714
|
rentalId,
|
|
634
715
|
option,
|
|
@@ -749,7 +830,7 @@ const { registry } = defineRegistry(chatCatalog, { components: {
|
|
|
749
830
|
return /* @__PURE__ */ jsx(Snapper, {
|
|
750
831
|
count: props.items.length,
|
|
751
832
|
className: "carousel",
|
|
752
|
-
label: copy.
|
|
833
|
+
label: copy.rentalsFound,
|
|
753
834
|
children: props.items.map((item) => /* @__PURE__ */ jsx(Card, {
|
|
754
835
|
rentalId: item.rentalId,
|
|
755
836
|
stayId: item.stayId
|
|
@@ -757,21 +838,17 @@ const { registry } = defineRegistry(chatCatalog, { components: {
|
|
|
757
838
|
});
|
|
758
839
|
},
|
|
759
840
|
StayQuote: ({ props }) => {
|
|
760
|
-
const { copy, locale } = useChat();
|
|
761
841
|
const stay = useStay(props.stayId);
|
|
762
842
|
const rental = useRental(stay?.rentalId ?? "");
|
|
763
843
|
if (!stay) return null;
|
|
764
844
|
return /* @__PURE__ */ jsxs("article", {
|
|
765
845
|
className: "card stay-card",
|
|
766
|
-
children: [/* @__PURE__ */
|
|
846
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
767
847
|
className: "card-body",
|
|
768
|
-
children:
|
|
848
|
+
children: rental && /* @__PURE__ */ jsx("h3", {
|
|
769
849
|
className: "card-title",
|
|
770
850
|
children: rental.name
|
|
771
|
-
})
|
|
772
|
-
className: "specs",
|
|
773
|
-
children: partyText(stay, copy, locale)
|
|
774
|
-
})]
|
|
851
|
+
})
|
|
775
852
|
}), /* @__PURE__ */ jsx(CardFoot, {
|
|
776
853
|
rentalId: stay.rentalId,
|
|
777
854
|
stay
|
|
@@ -779,18 +856,28 @@ const { registry } = defineRegistry(chatCatalog, { components: {
|
|
|
779
856
|
});
|
|
780
857
|
},
|
|
781
858
|
PriceBreakdown: ({ props }) => {
|
|
782
|
-
const { copy } = useChat();
|
|
859
|
+
const { copy, locale } = useChat();
|
|
783
860
|
const stay = useStay(props.stayId);
|
|
861
|
+
const rental = useRental(stay?.rentalId ?? "");
|
|
784
862
|
if (!stay?.bookable || !stay.total) return null;
|
|
863
|
+
const label = rental ? `${copy.priceBreakdown} · ${rental.name}` : copy.priceBreakdown;
|
|
785
864
|
return /* @__PURE__ */ jsx("section", {
|
|
786
865
|
className: "card breakdown",
|
|
787
|
-
"aria-label":
|
|
866
|
+
"aria-label": label,
|
|
788
867
|
children: /* @__PURE__ */ jsxs("div", {
|
|
789
868
|
className: "card-body",
|
|
790
869
|
children: [
|
|
791
870
|
/* @__PURE__ */ jsx("span", {
|
|
792
871
|
className: "muted",
|
|
793
|
-
children:
|
|
872
|
+
children: label
|
|
873
|
+
}),
|
|
874
|
+
/* @__PURE__ */ jsxs("span", {
|
|
875
|
+
className: "foot-meta",
|
|
876
|
+
children: [
|
|
877
|
+
stayMeta(stay, copy, locale),
|
|
878
|
+
" · ",
|
|
879
|
+
partyText(stay, copy, locale)
|
|
880
|
+
]
|
|
794
881
|
}),
|
|
795
882
|
stay.lines.map((line) => /* @__PURE__ */ jsxs("div", {
|
|
796
883
|
className: "line",
|
|
@@ -806,14 +893,26 @@ const { registry } = defineRegistry(chatCatalog, { components: {
|
|
|
806
893
|
},
|
|
807
894
|
AlternativeStays: ({ props }) => {
|
|
808
895
|
const { copy } = useChat();
|
|
809
|
-
|
|
896
|
+
const rental = useRental(props.rentalId);
|
|
897
|
+
const stays = useStateValue("/stays") ?? {};
|
|
898
|
+
const bookable = props.stayIds.flatMap((stayId) => {
|
|
899
|
+
const stay = stays[stayId];
|
|
900
|
+
return stay?.rentalId === props.rentalId && stay.bookable ? [stay] : [];
|
|
901
|
+
});
|
|
902
|
+
if (bookable.length === 0) return null;
|
|
903
|
+
const label = rental ? `${copy.otherDates} · ${rental.name}` : copy.otherDates;
|
|
904
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
810
905
|
className: "alternatives",
|
|
811
906
|
role: "group",
|
|
812
|
-
"aria-label":
|
|
813
|
-
children:
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
907
|
+
"aria-label": label,
|
|
908
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
909
|
+
className: "alt-caption",
|
|
910
|
+
"aria-hidden": "true",
|
|
911
|
+
children: label
|
|
912
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
913
|
+
className: "alt-chips",
|
|
914
|
+
children: bookable.map((stay) => /* @__PURE__ */ jsx(AlternativeChip, { stay }, stay.id))
|
|
915
|
+
})]
|
|
817
916
|
});
|
|
818
917
|
},
|
|
819
918
|
AvailabilityCalendar: ({ props }) => /* @__PURE__ */ jsx(Calendar, {
|
|
@@ -854,12 +953,12 @@ const { registry } = defineRegistry(chatCatalog, { components: {
|
|
|
854
953
|
if (!send) return null;
|
|
855
954
|
return /* @__PURE__ */ jsx("div", {
|
|
856
955
|
className: "quick-replies",
|
|
857
|
-
children: props.options.map((option) => /* @__PURE__ */
|
|
956
|
+
children: props.options.map((option) => /* @__PURE__ */ jsx("button", {
|
|
858
957
|
type: "button",
|
|
859
958
|
className: "chip",
|
|
860
959
|
disabled: busy,
|
|
861
960
|
onClick: () => send(option),
|
|
862
|
-
children:
|
|
961
|
+
children: option
|
|
863
962
|
}, option))
|
|
864
963
|
});
|
|
865
964
|
}
|
|
@@ -945,19 +1044,45 @@ const Consent = ({ copy, pending, onAccept, buttonRef }) => /* @__PURE__ */ jsxs
|
|
|
945
1044
|
})
|
|
946
1045
|
]
|
|
947
1046
|
});
|
|
1047
|
+
/** Shown until the reply's text: a search takes seconds, and three dots alone read as nothing.
|
|
1048
|
+
* Hidden from a screen reader: inside the busy log it may go unannounced, so the panel's own status,
|
|
1049
|
+
* outside the log, says it. */
|
|
948
1050
|
const Thinking = ({ label }) => /* @__PURE__ */ jsxs("div", {
|
|
949
1051
|
className: "thinking",
|
|
950
|
-
|
|
1052
|
+
"aria-hidden": "true",
|
|
951
1053
|
children: [
|
|
952
1054
|
/* @__PURE__ */ jsx("i", {}),
|
|
953
1055
|
/* @__PURE__ */ jsx("i", {}),
|
|
954
1056
|
/* @__PURE__ */ jsx("i", {}),
|
|
955
1057
|
/* @__PURE__ */ jsx("span", {
|
|
956
|
-
className: "
|
|
1058
|
+
className: "thinking-label",
|
|
957
1059
|
children: label
|
|
958
1060
|
})
|
|
959
1061
|
]
|
|
960
1062
|
});
|
|
1063
|
+
/** Why the chat cannot answer, as its reply to the visitor's message, and where to go instead. */
|
|
1064
|
+
const Fallback = ({ reason, copy, searchPath, contactPath }) => /* @__PURE__ */ jsxs("div", {
|
|
1065
|
+
className: "fallback",
|
|
1066
|
+
role: "status",
|
|
1067
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
1068
|
+
className: "message",
|
|
1069
|
+
"data-role": "assistant",
|
|
1070
|
+
children: reason === "busy" ? copy.busy : copy.unavailable
|
|
1071
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1072
|
+
className: "fallback-links",
|
|
1073
|
+
children: [/* @__PURE__ */ jsx("a", {
|
|
1074
|
+
className: "button",
|
|
1075
|
+
href: searchPath,
|
|
1076
|
+
children: copy.searchLink
|
|
1077
|
+
}), /* @__PURE__ */ jsx("a", {
|
|
1078
|
+
className: "link",
|
|
1079
|
+
href: contactPath,
|
|
1080
|
+
children: copy.contactLink
|
|
1081
|
+
})]
|
|
1082
|
+
})]
|
|
1083
|
+
});
|
|
1084
|
+
/** A refusal that a second try can get past: the message goes back into the field to send again. */
|
|
1085
|
+
const RETRYABLE = /* @__PURE__ */ new Set(["busy", "error"]);
|
|
961
1086
|
function Panel(props) {
|
|
962
1087
|
const { copy, endpoint, open, pending, onSent, onStarted, onClose } = props;
|
|
963
1088
|
const stored = useMemo(() => loadConversation(endpoint), [endpoint]);
|
|
@@ -968,19 +1093,39 @@ function Panel(props) {
|
|
|
968
1093
|
const [unavailable, setUnavailable] = useState(null);
|
|
969
1094
|
const session = useRef(stored?.sessionId ?? null);
|
|
970
1095
|
const abort = useRef(null);
|
|
971
|
-
const
|
|
1096
|
+
const list = useRef(null);
|
|
1097
|
+
const following = useRef(true);
|
|
1098
|
+
const lastRole = useRef(stored?.messages.at(-1)?.role);
|
|
972
1099
|
const start = useRef(null);
|
|
973
1100
|
const composer = useRef(null);
|
|
974
1101
|
const panel = useRef(null);
|
|
975
1102
|
const lastActivity = useRef(stored?.savedAt ?? Date.now());
|
|
976
1103
|
const hasMessages = messages.length > 0;
|
|
1104
|
+
const last = messages.at(-1);
|
|
1105
|
+
const waiting = streaming && !unavailable && (last?.role === "visitor" || !last?.parts.some((part) => part.kind === "text"));
|
|
977
1106
|
const expired = () => Date.now() - lastActivity.current >= SESSION_TTL_S * 1e3;
|
|
978
1107
|
useEffect(() => () => abort.current?.abort(), []);
|
|
979
1108
|
useEffect(() => {
|
|
980
|
-
|
|
981
|
-
|
|
1109
|
+
const role = messages.at(-1)?.role;
|
|
1110
|
+
const replyStarts = role === "assistant" && lastRole.current === "visitor";
|
|
1111
|
+
lastRole.current = role;
|
|
1112
|
+
const element = list.current;
|
|
1113
|
+
if (!open || !element) return;
|
|
1114
|
+
const turnTop = ([...element.querySelectorAll(":scope > .turn")].at(-1)?.offsetTop ?? 0) - 12;
|
|
1115
|
+
if (replyStarts) element.scrollTop = turnTop;
|
|
1116
|
+
else if (role === "assistant" && following.current) element.scrollTop = Math.max(element.scrollTop, Math.min(element.scrollHeight - element.clientHeight, turnTop));
|
|
1117
|
+
else if (role === "visitor" || following.current) element.scrollTop = element.scrollHeight;
|
|
1118
|
+
}, [
|
|
1119
|
+
messages,
|
|
1120
|
+
open,
|
|
1121
|
+
waiting,
|
|
1122
|
+
unavailable
|
|
1123
|
+
]);
|
|
982
1124
|
useEffect(() => {
|
|
983
|
-
if (open)
|
|
1125
|
+
if (!open) return;
|
|
1126
|
+
const touch = window.matchMedia?.("(pointer: coarse)").matches;
|
|
1127
|
+
if (accepted && hasMessages && touch) panel.current?.focus();
|
|
1128
|
+
else (accepted ? composer : start).current?.focus();
|
|
984
1129
|
}, [accepted, open]);
|
|
985
1130
|
useEffect(() => {
|
|
986
1131
|
onStarted(hasMessages);
|
|
@@ -1006,16 +1151,21 @@ function Panel(props) {
|
|
|
1006
1151
|
clearConversation(endpoint);
|
|
1007
1152
|
}
|
|
1008
1153
|
setDraft("");
|
|
1154
|
+
const restoreDraft = () => setDraft((current) => current || message);
|
|
1009
1155
|
setUnavailable(null);
|
|
1010
1156
|
setStreaming(true);
|
|
1011
|
-
setMessages((current) =>
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1157
|
+
setMessages((current) => {
|
|
1158
|
+
const before = fresh ? [] : current;
|
|
1159
|
+
const last = before.at(-1);
|
|
1160
|
+
return [...last?.role === "visitor" && last.unconfirmed && last.parts[0]?.kind === "text" && last.parts[0].text === message ? before.slice(0, -1) : before, {
|
|
1161
|
+
role: "visitor",
|
|
1162
|
+
parts: [{
|
|
1163
|
+
kind: "text",
|
|
1164
|
+
text: message
|
|
1165
|
+
}],
|
|
1166
|
+
unconfirmed: true
|
|
1167
|
+
}];
|
|
1168
|
+
});
|
|
1019
1169
|
const sentAt = Date.now();
|
|
1020
1170
|
const controller = new AbortController();
|
|
1021
1171
|
abort.current = controller;
|
|
@@ -1036,14 +1186,19 @@ function Panel(props) {
|
|
|
1036
1186
|
kind: "ui",
|
|
1037
1187
|
spec: chatEvent.spec
|
|
1038
1188
|
}));
|
|
1039
|
-
else if (chatEvent.type === "unavailable")
|
|
1040
|
-
|
|
1189
|
+
else if (chatEvent.type === "unavailable") {
|
|
1190
|
+
setUnavailable(chatEvent.reason);
|
|
1191
|
+
if (RETRYABLE.has(chatEvent.reason)) restoreDraft();
|
|
1192
|
+
} else if (chatEvent.type === "done") {
|
|
1041
1193
|
lastActivity.current = sentAt;
|
|
1042
1194
|
setMessages(confirmed);
|
|
1043
1195
|
}
|
|
1044
1196
|
}, controller.signal);
|
|
1045
1197
|
} catch {
|
|
1046
|
-
if (!controller.signal.aborted)
|
|
1198
|
+
if (!controller.signal.aborted) {
|
|
1199
|
+
setUnavailable("error");
|
|
1200
|
+
restoreDraft();
|
|
1201
|
+
}
|
|
1047
1202
|
} finally {
|
|
1048
1203
|
if (abort.current === controller) setStreaming(false);
|
|
1049
1204
|
}
|
|
@@ -1088,7 +1243,8 @@ function Panel(props) {
|
|
|
1088
1243
|
const focusable = [...element.querySelectorAll("a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled])")];
|
|
1089
1244
|
const active = element.getRootNode().activeElement;
|
|
1090
1245
|
const edge = event.shiftKey ? focusable[0] : focusable.at(-1);
|
|
1091
|
-
|
|
1246
|
+
const outside = !element.contains(active) || event.shiftKey && active === element;
|
|
1247
|
+
if (focusable.length === 0 || active === edge || outside) {
|
|
1092
1248
|
event.preventDefault();
|
|
1093
1249
|
(event.shiftKey ? focusable.at(-1) : focusable[0])?.focus();
|
|
1094
1250
|
}
|
|
@@ -1097,7 +1253,6 @@ function Panel(props) {
|
|
|
1097
1253
|
event.preventDefault();
|
|
1098
1254
|
submit(draft);
|
|
1099
1255
|
};
|
|
1100
|
-
const waiting = streaming && messages.at(-1)?.role === "visitor";
|
|
1101
1256
|
return /* @__PURE__ */ jsxs(ChatContext.Provider, {
|
|
1102
1257
|
value: {
|
|
1103
1258
|
...props,
|
|
@@ -1115,6 +1270,7 @@ function Panel(props) {
|
|
|
1115
1270
|
"aria-modal": "true",
|
|
1116
1271
|
"aria-label": copy.title,
|
|
1117
1272
|
ref: panel,
|
|
1273
|
+
tabIndex: -1,
|
|
1118
1274
|
hidden: !open,
|
|
1119
1275
|
inert: !open,
|
|
1120
1276
|
onKeyDown: (event) => {
|
|
@@ -1150,50 +1306,50 @@ function Panel(props) {
|
|
|
1150
1306
|
className: "notice",
|
|
1151
1307
|
children: copy.notice
|
|
1152
1308
|
}),
|
|
1153
|
-
accepted ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1309
|
+
accepted ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1310
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1311
|
+
className: "messages",
|
|
1312
|
+
role: "log",
|
|
1313
|
+
"aria-busy": streaming,
|
|
1314
|
+
ref: list,
|
|
1315
|
+
onScroll: (event) => {
|
|
1316
|
+
const element = event.currentTarget;
|
|
1317
|
+
following.current = element.scrollHeight - element.scrollTop - element.clientHeight < 80;
|
|
1318
|
+
},
|
|
1319
|
+
children: [
|
|
1320
|
+
/* @__PURE__ */ jsx("div", {
|
|
1165
1321
|
className: "message",
|
|
1166
|
-
"data-role":
|
|
1167
|
-
children:
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
!hasMessages && copy.suggestions.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
1322
|
+
"data-role": "assistant",
|
|
1323
|
+
children: copy.greeting
|
|
1324
|
+
}),
|
|
1325
|
+
messages.map((message, index) => /* @__PURE__ */ jsxs("div", {
|
|
1326
|
+
className: message.role === "assistant" ? "ui" : "turn",
|
|
1327
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
1328
|
+
className: "sr-only",
|
|
1329
|
+
children: [message.role === "assistant" ? copy.title : copy.yourMessage, ":"]
|
|
1330
|
+
}), message.parts.map((part, partIndex) => part.kind === "text" ? /* @__PURE__ */ jsx("div", {
|
|
1331
|
+
className: "message",
|
|
1332
|
+
"data-role": message.role,
|
|
1333
|
+
children: message.role === "assistant" ? /* @__PURE__ */ jsx(RichText, { text: part.text }) : part.text
|
|
1334
|
+
}, partIndex) : /* @__PURE__ */ jsx(SpecView, { spec: part.spec }, partIndex))]
|
|
1335
|
+
}, index)),
|
|
1336
|
+
unavailable && /* @__PURE__ */ jsx(Fallback, {
|
|
1337
|
+
reason: unavailable,
|
|
1338
|
+
copy,
|
|
1339
|
+
searchPath: props.searchPath,
|
|
1340
|
+
contactPath: props.contactPath
|
|
1341
|
+
}),
|
|
1342
|
+
waiting && /* @__PURE__ */ jsx(Thinking, { label: copy.thinking })
|
|
1343
|
+
]
|
|
1344
|
+
}),
|
|
1345
|
+
/* @__PURE__ */ jsx("p", {
|
|
1346
|
+
className: "sr-only",
|
|
1347
|
+
role: "status",
|
|
1348
|
+
children: waiting ? copy.thinking : ""
|
|
1349
|
+
}),
|
|
1350
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1351
|
+
className: "dock",
|
|
1352
|
+
children: [!hasMessages && copy.suggestions.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
1197
1353
|
className: "suggestions",
|
|
1198
1354
|
children: copy.suggestions.map((suggestion) => /* @__PURE__ */ jsxs("button", {
|
|
1199
1355
|
type: "button",
|
|
@@ -1201,8 +1357,7 @@ function Panel(props) {
|
|
|
1201
1357
|
onClick: () => void submit(suggestion),
|
|
1202
1358
|
children: [/* @__PURE__ */ jsx(TopicIcon, { text: suggestion }), suggestion]
|
|
1203
1359
|
}, suggestion))
|
|
1204
|
-
}),
|
|
1205
|
-
/* @__PURE__ */ jsxs("form", {
|
|
1360
|
+
}), /* @__PURE__ */ jsxs("form", {
|
|
1206
1361
|
className: "composer",
|
|
1207
1362
|
onSubmit: send,
|
|
1208
1363
|
children: [/* @__PURE__ */ jsx("textarea", {
|
|
@@ -1225,9 +1380,9 @@ function Panel(props) {
|
|
|
1225
1380
|
disabled: streaming || !draft.trim(),
|
|
1226
1381
|
children: /* @__PURE__ */ jsx(Arrow, {})
|
|
1227
1382
|
})]
|
|
1228
|
-
})
|
|
1229
|
-
|
|
1230
|
-
|
|
1383
|
+
})]
|
|
1384
|
+
})
|
|
1385
|
+
] }) : /* @__PURE__ */ jsx(Consent, {
|
|
1231
1386
|
copy,
|
|
1232
1387
|
pending,
|
|
1233
1388
|
onAccept: () => setAccepted(true),
|