@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
package/README.md
CHANGED
|
@@ -28,16 +28,21 @@ Render `WebsiteChat` once in the layout as a client island; see `instructions/ve
|
|
|
28
28
|
| `locale` | yes | `"de"` or `"en"` |
|
|
29
29
|
| `searchPath` | yes | the site's search, for a rental without a page and when the chat is down |
|
|
30
30
|
| `contactPath` | yes | the site's contact page |
|
|
31
|
-
| `theme` | no | `classic` (default), `soft` or `minimal
|
|
32
|
-
| `colors` | no | `{ primary, secondary }`, any CSS colour
|
|
31
|
+
| `theme` | no | `classic` (default), `soft` or `minimal`: shadows or borders, how round |
|
|
32
|
+
| `colors` | no | `{ primary, secondary, accent }`, any CSS colour, over the site's tokens |
|
|
33
33
|
| `position` | no | the entry pill and the panel on the `right` (default) or `left` |
|
|
34
34
|
| `copy` | no | overrides of single fields of `DEFAULT_COPY` (strings, `suggestions`) |
|
|
35
35
|
|
|
36
36
|
The chat stays hidden until an agent switches it on for the website in AI Studio.
|
|
37
37
|
|
|
38
|
+
It takes its colours and corners from the site's shadcn tokens (`--primary`,
|
|
39
|
+
`--primary-foreground`, `--background`, `--foreground`, `--muted`, `--muted-foreground`, `--border`,
|
|
40
|
+
`--radius`), dark mode included, and its own values where a site has none. A site whose corners
|
|
41
|
+
are square (`--radius: 0`) gets square buttons too.
|
|
42
|
+
|
|
38
43
|
A site with a sticky header sets `--chat-top` (its height) on any element around the widget, such as
|
|
39
44
|
`:root`: the dimmed page and the panel start below it. `--chat-width` widens the panel on wide screens
|
|
40
|
-
(default `
|
|
45
|
+
(default `700px`).
|
|
41
46
|
|
|
42
47
|
The service places these components in replies: `RentalCard` (photo gallery, rating, the wishes the
|
|
43
48
|
search that found it applied), `RentalCarousel`, `Compare`, `StayQuote`, `PriceBreakdown`,
|
|
@@ -137,16 +137,23 @@ const versionOf = (version) => {
|
|
|
137
137
|
Number(match[3])
|
|
138
138
|
] : null;
|
|
139
139
|
};
|
|
140
|
-
/**
|
|
141
|
-
|
|
140
|
+
/** Whether a widget of `clientVersion` is `since` or newer within the same major; an unreadable
|
|
141
|
+
* version is neither. */
|
|
142
|
+
const isAtLeast = (clientVersion, since) => {
|
|
142
143
|
const client = versionOf(clientVersion);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (since[0] !== client[0]) return false;
|
|
147
|
-
return since[1] < client[1] || since[1] === client[1] && since[2] <= client[2];
|
|
148
|
-
});
|
|
144
|
+
const from = versionOf(since);
|
|
145
|
+
if (!client || from[0] !== client[0]) return false;
|
|
146
|
+
return from[1] < client[1] || from[1] === client[1] && from[2] <= client[2];
|
|
149
147
|
};
|
|
148
|
+
/** See {@link ComponentsFor}. */
|
|
149
|
+
const componentsFor = (clientVersion) => PLACEABLE_COMPONENTS.filter((name) => isAtLeast(clientVersion, COMPONENT_SINCE[name]));
|
|
150
|
+
/** Whether the widget shows the assistant's `**bold**` as bold text (1.1.0 and later); an older
|
|
151
|
+
* one would show the asterisks, so the service asks for none there. */
|
|
152
|
+
const rendersBold = (clientVersion) => isAtLeast(clientVersion, "1.1.0");
|
|
153
|
+
/** Whether the widget's booking button opens the rental's booking page with the rate and extras the
|
|
154
|
+
* visitor chose in the chat (1.1.0 and later); an older one opens the rental page and loses them, so
|
|
155
|
+
* the service puts no stay together there. */
|
|
156
|
+
const handsOverStays = (clientVersion) => isAtLeast(clientVersion, "1.1.0");
|
|
150
157
|
/** See {@link ParseItem}. */
|
|
151
158
|
const parseItem = (item, allowed) => {
|
|
152
159
|
if (typeof item !== "object" || item === null) return null;
|
|
@@ -180,4 +187,4 @@ const chatSpec = (items, state) => {
|
|
|
180
187
|
};
|
|
181
188
|
};
|
|
182
189
|
//#endregion
|
|
183
|
-
export { componentsFor as a,
|
|
190
|
+
export { componentsFor as a, rendersBold as c, chatSpec as i, stateKey as l, PLACEABLE_COMPONENTS as n, handsOverStays as o, chatCatalog as r, parseItem as s, COMPONENT_SINCE as t, statePath as u };
|
package/dist/catalog.d.ts
CHANGED
|
@@ -51,6 +51,15 @@ interface ChatStay {
|
|
|
51
51
|
readonly label: string;
|
|
52
52
|
readonly amount: string;
|
|
53
53
|
}[];
|
|
54
|
+
/** The rate the visitor chose in the chat (`?policy=` on the way to booking); absent for the
|
|
55
|
+
* PMS's pre-selection. */
|
|
56
|
+
readonly policyId?: string | null;
|
|
57
|
+
/** The extras the visitor chose in the chat, by the PMS's service id, as the quote priced them
|
|
58
|
+
* (`?service=<id>:<count>` on the way to booking). */
|
|
59
|
+
readonly services?: readonly {
|
|
60
|
+
readonly id: string;
|
|
61
|
+
readonly count: number;
|
|
62
|
+
}[];
|
|
54
63
|
}
|
|
55
64
|
/** A day of a rental's calendar: bookable, taken, or outside what can be booked (past, closed). */
|
|
56
65
|
type ChatDayStatus = "free" | "booked" | "closed";
|
|
@@ -109,6 +118,9 @@ interface ChatState {
|
|
|
109
118
|
readonly stays: Readonly<Record<string, ChatStay>>;
|
|
110
119
|
/** A rental's page on this website in the visitor's language; absent without one. */
|
|
111
120
|
readonly pages: Readonly<Record<string, string>>;
|
|
121
|
+
/** A rental's booking page on this website, where a stay is booked with everything the chat put
|
|
122
|
+
* together; absent without one (the stay then goes to the rental page). */
|
|
123
|
+
readonly bookingPages?: Readonly<Record<string, string>>;
|
|
112
124
|
/** A rental's calendar, by rental id; only for rentals whose calendar was asked for. */
|
|
113
125
|
readonly availability: Readonly<Record<string, ChatCalendar>>;
|
|
114
126
|
/** A rental's price calendar, by rental id (PriceCalendar). */
|
|
@@ -295,6 +307,13 @@ type ParseItem = (item: unknown, allowed: readonly PlaceableComponent[]) => Chat
|
|
|
295
307
|
type ChatSpec = (items: readonly ChatItem[], state: ChatState) => Spec;
|
|
296
308
|
/** See {@link ComponentsFor}. */
|
|
297
309
|
export declare const componentsFor: ComponentsFor;
|
|
310
|
+
/** Whether the widget shows the assistant's `**bold**` as bold text (1.1.0 and later); an older
|
|
311
|
+
* one would show the asterisks, so the service asks for none there. */
|
|
312
|
+
export declare const rendersBold: (clientVersion: string) => boolean;
|
|
313
|
+
/** Whether the widget's booking button opens the rental's booking page with the rate and extras the
|
|
314
|
+
* visitor chose in the chat (1.1.0 and later); an older one opens the rental page and loses them, so
|
|
315
|
+
* the service puts no stay together there. */
|
|
316
|
+
export declare const handsOverStays: (clientVersion: string) => boolean;
|
|
298
317
|
/** See {@link ParseItem}. */
|
|
299
318
|
export declare const parseItem: ParseItem;
|
|
300
319
|
/** See {@link ChatSpec}. */
|
package/dist/catalog.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { i as SESSION_TTL_S, n as CHAT_PATH, r as MAX_MESSAGE_CHARS, t as CHAT_LOCALES } from "./protocol-BuNqZAMt.js";
|
|
2
|
-
import { a as componentsFor, c as
|
|
3
|
-
export { CHAT_LOCALES, CHAT_PATH, COMPONENT_SINCE, MAX_MESSAGE_CHARS, PLACEABLE_COMPONENTS, SESSION_TTL_S, chatCatalog, chatSpec, componentsFor, parseItem, stateKey, statePath };
|
|
2
|
+
import { a as componentsFor, c as rendersBold, i as chatSpec, l as stateKey, n as PLACEABLE_COMPONENTS, o as handsOverStays, r as chatCatalog, s as parseItem, t as COMPONENT_SINCE, u as statePath } from "./catalog-y3xQ2jXW.js";
|
|
3
|
+
export { CHAT_LOCALES, CHAT_PATH, COMPONENT_SINCE, MAX_MESSAGE_CHARS, PLACEABLE_COMPONENTS, SESSION_TTL_S, chatCatalog, chatSpec, componentsFor, handsOverStays, parseItem, rendersBold, stateKey, statePath };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { n as CHAT_PATH } from "./protocol-BuNqZAMt.js";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/client.ts
|
|
4
|
+
const url = (endpoint) => `${endpoint.replace(/\/+$/, "")}${CHAT_PATH}`;
|
|
5
|
+
/** See {@link ChatAvailable}. */
|
|
6
|
+
const chatAvailable = async (endpoint, signal) => {
|
|
7
|
+
if (!endpoint) return false;
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch(url(endpoint), signal ? { signal } : {});
|
|
10
|
+
if (!res.ok) return false;
|
|
11
|
+
return (await res.json()).available === true;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
/** See {@link ParseEvents}. Only `data:` lines matter: the event's type travels in its JSON. */
|
|
17
|
+
const parseEvents = (buffer) => {
|
|
18
|
+
const blocks = buffer.split("\n\n");
|
|
19
|
+
const rest = blocks.pop() ?? "";
|
|
20
|
+
const events = [];
|
|
21
|
+
for (const block of blocks) {
|
|
22
|
+
const data = block.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
|
|
23
|
+
if (!data) continue;
|
|
24
|
+
try {
|
|
25
|
+
events.push(JSON.parse(data));
|
|
26
|
+
} catch {}
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
events,
|
|
30
|
+
rest
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
/** See {@link SendMessage}. */
|
|
34
|
+
const sendMessage = async (endpoint, request, onEvent, signal) => {
|
|
35
|
+
const res = await fetch(url(endpoint), {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: {
|
|
38
|
+
"content-type": "application/json",
|
|
39
|
+
accept: "text/event-stream"
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify(request),
|
|
42
|
+
...signal ? { signal } : {}
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok || !res.body) {
|
|
45
|
+
onEvent({
|
|
46
|
+
type: "unavailable",
|
|
47
|
+
reason: res.status === 429 ? "limit" : res.status === 409 ? "busy" : "error"
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
52
|
+
let buffer = "";
|
|
53
|
+
let ended = false;
|
|
54
|
+
for (;;) {
|
|
55
|
+
const { done, value } = await reader.read();
|
|
56
|
+
if (done) break;
|
|
57
|
+
const parsed = parseEvents(buffer + value);
|
|
58
|
+
buffer = parsed.rest;
|
|
59
|
+
for (const event of parsed.events) {
|
|
60
|
+
if (event.type === "done" || event.type === "unavailable") ended = true;
|
|
61
|
+
onEvent(event);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!ended) onEvent({
|
|
65
|
+
type: "unavailable",
|
|
66
|
+
reason: "error"
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/conversation.ts
|
|
71
|
+
const keyOf = (endpoint) => `v-office-chat:${endpoint}`;
|
|
72
|
+
/** sessionStorage throws where a browser forbids it (some private modes): the chat then lives in the
|
|
73
|
+
* page only, as before. */
|
|
74
|
+
const storage = () => {
|
|
75
|
+
try {
|
|
76
|
+
return globalThis.sessionStorage ?? null;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/** See {@link LoadConversation}. */
|
|
82
|
+
const loadConversation = (endpoint, now = Date.now()) => {
|
|
83
|
+
try {
|
|
84
|
+
const raw = storage()?.getItem(keyOf(endpoint));
|
|
85
|
+
if (!raw) return null;
|
|
86
|
+
const stored = JSON.parse(raw);
|
|
87
|
+
if (!Array.isArray(stored.messages) || stored.messages.length === 0) return null;
|
|
88
|
+
if (!(now - stored.savedAt < 72e5)) {
|
|
89
|
+
storage()?.removeItem(keyOf(endpoint));
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
sessionId: stored.sessionId ?? null,
|
|
94
|
+
messages: stored.messages,
|
|
95
|
+
savedAt: stored.savedAt
|
|
96
|
+
};
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
/** See {@link SaveConversation}. */
|
|
102
|
+
const saveConversation = (endpoint, conversation, savedAt = Date.now()) => {
|
|
103
|
+
try {
|
|
104
|
+
storage()?.setItem(keyOf(endpoint), JSON.stringify({
|
|
105
|
+
...conversation,
|
|
106
|
+
savedAt
|
|
107
|
+
}));
|
|
108
|
+
} catch {
|
|
109
|
+
clearConversation(endpoint);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
/** See {@link ClearConversation}. */
|
|
113
|
+
const clearConversation = (endpoint) => {
|
|
114
|
+
try {
|
|
115
|
+
storage()?.removeItem(keyOf(endpoint));
|
|
116
|
+
} catch {}
|
|
117
|
+
};
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/icons.tsx
|
|
120
|
+
const Svg = ({ children, size = 16 }) => /* @__PURE__ */ jsx("svg", {
|
|
121
|
+
width: size,
|
|
122
|
+
height: size,
|
|
123
|
+
viewBox: "0 0 24 24",
|
|
124
|
+
fill: "none",
|
|
125
|
+
stroke: "currentColor",
|
|
126
|
+
strokeWidth: 2.2,
|
|
127
|
+
strokeLinecap: "round",
|
|
128
|
+
strokeLinejoin: "round",
|
|
129
|
+
"aria-hidden": "true",
|
|
130
|
+
focusable: "false",
|
|
131
|
+
children
|
|
132
|
+
});
|
|
133
|
+
/** The chat's mark, two sparkles in the accent colour's circle. */
|
|
134
|
+
const Spark = () => /* @__PURE__ */ jsx("span", {
|
|
135
|
+
className: "spark",
|
|
136
|
+
children: /* @__PURE__ */ jsxs(Svg, {
|
|
137
|
+
size: 18,
|
|
138
|
+
children: [/* @__PURE__ */ jsx("path", { d: "M10 4c.5 4 2 5.5 6 6-4 .5-5.5 2-6 6-.5-4-2-5.5-6-6 4-.5 5.5-2 6-6Z" }), /* @__PURE__ */ jsx("path", { d: "M18 14c.3 2 1 2.7 3 3-2 .3-2.7 1-3 3-.3-2-1-2.7-3-3 2-.3 2.7-1 3-3Z" })]
|
|
139
|
+
})
|
|
140
|
+
});
|
|
141
|
+
const Arrow = () => /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M5 12h14M13 6l6 6-6 6" }) });
|
|
142
|
+
const Restart = () => /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 1 0 3-6.7L3 8M3 3v5h5" }) });
|
|
143
|
+
const Cross = () => /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6L6 18" }) });
|
|
144
|
+
const Chevron = ({ side }) => /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("path", { d: side === "prev" ? "M15 18l-6-6 6-6" : "M9 6l6 6-6 6" }) });
|
|
145
|
+
const Check = () => /* @__PURE__ */ jsx(Svg, {
|
|
146
|
+
size: 14,
|
|
147
|
+
children: /* @__PURE__ */ jsx("path", { d: "M5 12.5l4.5 4.5L19 7.5" })
|
|
148
|
+
});
|
|
149
|
+
/** In a photo's place on a card without photos. */
|
|
150
|
+
const House = () => /* @__PURE__ */ jsx(Svg, {
|
|
151
|
+
size: 32,
|
|
152
|
+
children: /* @__PURE__ */ jsx("path", { d: "M3 10.5 12 3l9 7.5M5 9v11h14V9M10 20v-6h4v6" })
|
|
153
|
+
});
|
|
154
|
+
/** What a suggestion is about, read from its words (German and English), for its icon. */
|
|
155
|
+
const TOPICS = [
|
|
156
|
+
{
|
|
157
|
+
topic: "date",
|
|
158
|
+
words: /\bwann\b|termin|datum|anreise|\bwhen\b|\bdates?\b/
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
topic: "book",
|
|
162
|
+
words: /buch|\bbook/
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
topic: "price",
|
|
166
|
+
words: /preis|kost|günstig|teuer|budget|€|price|cost|cheap/
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
topic: "pet",
|
|
170
|
+
words: /hund|katze|haustier|\b(?:dogs?|cats?|pets?)\b/
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
topic: "people",
|
|
174
|
+
words: /famili|kinder|zu zweit|personen|erwachsene|gruppe|family|kids|people|adults|couple/
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
topic: "date",
|
|
178
|
+
words: /wann|termin|datum|woche|nächte|nacht|ferien|silvester|ostern|sommer|herbst|winter|frühling|januar|februar|märz|april|mai|juni|juli|august|september|oktober|november|dezember|when|date|week|night|holiday/
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
topic: "place",
|
|
182
|
+
words: /strand|lage|meer|see|wo |nähe|entfern|beach|where|sea|location/
|
|
183
|
+
}
|
|
184
|
+
];
|
|
185
|
+
const TOPIC_PATHS = {
|
|
186
|
+
book: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("rect", {
|
|
187
|
+
x: "5",
|
|
188
|
+
y: "10.5",
|
|
189
|
+
width: "14",
|
|
190
|
+
height: "10",
|
|
191
|
+
rx: "2"
|
|
192
|
+
}), /* @__PURE__ */ jsx("path", { d: "M8.5 10.5V7.5a3.5 3.5 0 0 1 7 0v3" })] }),
|
|
193
|
+
price: /* @__PURE__ */ jsx("path", { d: "M17 6.5A6.5 6.5 0 1 0 17 17.5M4 10.5h9M4 13.5h9" }),
|
|
194
|
+
pet: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
195
|
+
/* @__PURE__ */ jsx("circle", {
|
|
196
|
+
cx: "6",
|
|
197
|
+
cy: "10",
|
|
198
|
+
r: "1.6"
|
|
199
|
+
}),
|
|
200
|
+
/* @__PURE__ */ jsx("circle", {
|
|
201
|
+
cx: "10",
|
|
202
|
+
cy: "6",
|
|
203
|
+
r: "1.6"
|
|
204
|
+
}),
|
|
205
|
+
/* @__PURE__ */ jsx("circle", {
|
|
206
|
+
cx: "14",
|
|
207
|
+
cy: "6",
|
|
208
|
+
r: "1.6"
|
|
209
|
+
}),
|
|
210
|
+
/* @__PURE__ */ jsx("circle", {
|
|
211
|
+
cx: "18",
|
|
212
|
+
cy: "10",
|
|
213
|
+
r: "1.6"
|
|
214
|
+
}),
|
|
215
|
+
/* @__PURE__ */ jsx("path", { d: "M12 12c-3 0-5 3-5 5.2 0 1.4 1.3 2 2.6 1.6 1.5-.5 3.3-.5 4.8 0 1.3.4 2.6-.2 2.6-1.6C17 15 15 12 12 12Z" })
|
|
216
|
+
] }),
|
|
217
|
+
people: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("circle", {
|
|
218
|
+
cx: "9",
|
|
219
|
+
cy: "8",
|
|
220
|
+
r: "3"
|
|
221
|
+
}), /* @__PURE__ */ jsx("path", { d: "M3.5 19c.6-3 2.8-5 5.5-5s4.9 2 5.5 5M15.5 5.5a3 3 0 0 1 0 5.6M17.5 14.3c1.6.7 2.7 2.4 3 4.7" })] }),
|
|
222
|
+
date: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("rect", {
|
|
223
|
+
x: "4",
|
|
224
|
+
y: "5.5",
|
|
225
|
+
width: "16",
|
|
226
|
+
height: "14.5",
|
|
227
|
+
rx: "2"
|
|
228
|
+
}), /* @__PURE__ */ jsx("path", { d: "M4 10h16M8.5 3.5v4M15.5 3.5v4" })] }),
|
|
229
|
+
place: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("path", { d: "M12 21s-6.5-5.6-6.5-11a6.5 6.5 0 0 1 13 0c0 5.4-6.5 11-6.5 11Z" }), /* @__PURE__ */ jsx("circle", {
|
|
230
|
+
cx: "12",
|
|
231
|
+
cy: "10",
|
|
232
|
+
r: "2.3"
|
|
233
|
+
})] }),
|
|
234
|
+
other: /* @__PURE__ */ jsx("path", { d: "M5 5.5h14a1.5 1.5 0 0 1 1.5 1.5v8a1.5 1.5 0 0 1-1.5 1.5h-7l-4.5 3.5v-3.5H5A1.5 1.5 0 0 1 3.5 15V7A1.5 1.5 0 0 1 5 5.5Z" })
|
|
235
|
+
};
|
|
236
|
+
/** A suggestion's icon: the first topic its words name, else a speech bubble. */
|
|
237
|
+
const TopicIcon = ({ text }) => {
|
|
238
|
+
const words = text.toLowerCase();
|
|
239
|
+
const topic = TOPICS.find((candidate) => candidate.words.test(words))?.topic ?? "other";
|
|
240
|
+
return /* @__PURE__ */ jsx(Svg, { children: TOPIC_PATHS[topic] });
|
|
241
|
+
};
|
|
242
|
+
//#endregion
|
|
243
|
+
export { House as a, TopicIcon as c, saveConversation as d, chatAvailable as f, Cross as i, clearConversation as l, Check as n, Restart as o, sendMessage as p, Chevron as r, Spark as s, Arrow as t, loadConversation as u };
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,16 @@ interface ChatCopy {
|
|
|
30
30
|
readonly contactLink: string;
|
|
31
31
|
readonly bookStay: string;
|
|
32
32
|
readonly viewRental: string;
|
|
33
|
+
/** A card's short button: book its stay, or see the rental without one. */
|
|
34
|
+
readonly book: string;
|
|
35
|
+
readonly view: string;
|
|
36
|
+
/** @deprecated Not shown since 1.1.1: a card lists the wishes it matches instead. Kept so a site's
|
|
37
|
+
* `copy` that sets it still type-checks. */
|
|
38
|
+
readonly matchBadge: string;
|
|
39
|
+
/** A stay's length on a card; `{n}` is the count. */
|
|
40
|
+
readonly nights: Readonly<Partial<Record<Intl.LDMLPluralRule, string>>> & {
|
|
41
|
+
readonly other: string;
|
|
42
|
+
};
|
|
33
43
|
readonly total: string;
|
|
34
44
|
readonly notBookable: string;
|
|
35
45
|
readonly photo: string;
|
|
@@ -76,15 +86,20 @@ interface ChatCopy {
|
|
|
76
86
|
export declare const DEFAULT_COPY: Readonly<Record<ChatLocale, ChatCopy>>;
|
|
77
87
|
//#endregion
|
|
78
88
|
//#region src/theme.d.ts
|
|
79
|
-
/** The curated looks over one layout
|
|
80
|
-
* lighter shadows), `minimal` (borders instead of shadows,
|
|
89
|
+
/** The curated looks over one layout, each relative to the site's corner radius: `classic` (soft
|
|
90
|
+
* shadows), `soft` (rounder corners, lighter shadows), `minimal` (borders instead of shadows, tighter
|
|
91
|
+
* corners). */
|
|
81
92
|
export declare const CHAT_THEMES: readonly ["classic", "soft", "minimal"];
|
|
82
93
|
type ChatTheme = (typeof CHAT_THEMES)[number];
|
|
83
|
-
/**
|
|
84
|
-
*
|
|
94
|
+
/** Colours for the chat, any CSS colour, over the ones it takes from the site's tokens. `primary`
|
|
95
|
+
* fills buttons, the send button and the chat's mark, and runs around the entry pill; `secondary`
|
|
96
|
+
* tints the visitor's messages and the suggestions. */
|
|
85
97
|
interface ChatColors {
|
|
86
98
|
readonly primary?: string;
|
|
87
99
|
readonly secondary?: string;
|
|
100
|
+
/** The small marks: the chat's name and sparkle, a card's place, the icons, the ring round the
|
|
101
|
+
* entry pill. The primary when unset. */
|
|
102
|
+
readonly accent?: string;
|
|
88
103
|
}
|
|
89
104
|
//#endregion
|
|
90
105
|
//#region src/WebsiteChat.d.ts
|