@v-office/website-chat 0.0.0 → 1.0.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 +53 -1
- package/dist/catalog-RXKmEmMW.js +183 -0
- package/dist/catalog.d.ts +303 -0
- package/dist/catalog.js +3 -0
- package/dist/icons-B5PAY3aU.js +145 -0
- package/dist/index.d.ts +111 -0
- package/dist/index.js +577 -0
- package/dist/panel-C0Bb7ln-.js +1161 -0
- package/dist/protocol-Bmb_uxVQ.d.ts +46 -0
- package/dist/protocol-BuNqZAMt.js +12 -0
- package/instructions/CHANGELOG.md +8 -0
- package/instructions/MIGRATION.md +8 -0
- package/instructions/versions/1.0.0/CHANGELOG.md +37 -0
- package/instructions/versions/1.0.0/MIGRATION.md +51 -0
- package/instructions/versions/1.0.1/CHANGELOG.md +14 -0
- package/instructions/versions/1.0.1/MIGRATION.md +9 -0
- package/package.json +65 -6
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { n as CHAT_PATH } from "./protocol-BuNqZAMt.js";
|
|
2
|
+
import { jsx } 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, in the primary colour's circle. */
|
|
134
|
+
const Spark = () => /* @__PURE__ */ jsx("span", {
|
|
135
|
+
className: "spark",
|
|
136
|
+
children: /* @__PURE__ */ jsx(Svg, {
|
|
137
|
+
size: 18,
|
|
138
|
+
children: /* @__PURE__ */ jsx("path", { d: "M12 3v4M12 17v4M3 12h4M17 12h4M6.3 6.3l2.5 2.5M15.2 15.2l2.5 2.5M6.3 17.7l2.5-2.5M15.2 8.8l2.5-2.5" })
|
|
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
|
+
//#endregion
|
|
145
|
+
export { clearConversation as a, chatAvailable as c, Spark as i, sendMessage as l, Cross as n, loadConversation as o, Restart as r, saveConversation as s, Arrow as t };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { a as ChatLocale, t as CHAT_LOCALES } from "./protocol-Bmb_uxVQ.js";
|
|
2
|
+
//#region src/copy.d.ts
|
|
3
|
+
interface ChatCopy {
|
|
4
|
+
/** The entry pill's invitation, under the title; on wide screens the pill's field says it. */
|
|
5
|
+
readonly open: string;
|
|
6
|
+
/** The entry pill once a conversation exists. */
|
|
7
|
+
readonly resume: string;
|
|
8
|
+
readonly close: string;
|
|
9
|
+
/** The chat's name: over the entry pill and in the panel's head. */
|
|
10
|
+
readonly title: string;
|
|
11
|
+
readonly newConversation: string;
|
|
12
|
+
/** The AI notice under the title (AI Act transparency); have its wording checked for the site. */
|
|
13
|
+
readonly notice: string;
|
|
14
|
+
/** Shown before the first message. */
|
|
15
|
+
readonly greeting: string;
|
|
16
|
+
/** Tappable first messages, before the visitor wrote anything. */
|
|
17
|
+
readonly suggestions: readonly string[];
|
|
18
|
+
readonly placeholder: string;
|
|
19
|
+
readonly send: string;
|
|
20
|
+
/** Read out while a reply is on its way. */
|
|
21
|
+
readonly thinking: string;
|
|
22
|
+
/** Over the message typed into the entry pill, at the start step that sends it. */
|
|
23
|
+
readonly yourMessage: string;
|
|
24
|
+
/** The consent step: what turning the chat on means. */
|
|
25
|
+
readonly consent: string;
|
|
26
|
+
readonly consentAccept: string;
|
|
27
|
+
readonly unavailable: string;
|
|
28
|
+
readonly busy: string;
|
|
29
|
+
readonly searchLink: string;
|
|
30
|
+
readonly contactLink: string;
|
|
31
|
+
readonly bookStay: string;
|
|
32
|
+
readonly viewRental: string;
|
|
33
|
+
readonly total: string;
|
|
34
|
+
readonly notBookable: string;
|
|
35
|
+
readonly photo: string;
|
|
36
|
+
readonly previous: string;
|
|
37
|
+
readonly next: string;
|
|
38
|
+
/** `{n}` reviews, per plural form. */
|
|
39
|
+
readonly reviews: Readonly<Partial<Record<Intl.LDMLPluralRule, string>>> & {
|
|
40
|
+
readonly other: string;
|
|
41
|
+
};
|
|
42
|
+
readonly priceBreakdown: string;
|
|
43
|
+
readonly otherDates: string;
|
|
44
|
+
readonly free: string;
|
|
45
|
+
readonly booked: string;
|
|
46
|
+
/** A day that is neither free nor taken: past, beyond the bookable range, or no arrival. */
|
|
47
|
+
readonly closed: string;
|
|
48
|
+
readonly yourStay: string;
|
|
49
|
+
readonly compareRooms: string;
|
|
50
|
+
readonly compareHighlights: string;
|
|
51
|
+
readonly compareRating: string;
|
|
52
|
+
readonly compareStay: string;
|
|
53
|
+
readonly priceCalendar: string;
|
|
54
|
+
/** Under the price calendar's title: what each column's price is. */
|
|
55
|
+
readonly priceCalendarNights: Readonly<Partial<Record<Intl.LDMLPluralRule, string>>> & {
|
|
56
|
+
readonly other: string;
|
|
57
|
+
};
|
|
58
|
+
readonly cheapest: string;
|
|
59
|
+
readonly reviewsTitle: string;
|
|
60
|
+
/** Over the wishes a found stay matches. */
|
|
61
|
+
readonly matchesWishes: string;
|
|
62
|
+
readonly mapLoad: string;
|
|
63
|
+
/** Before the map loads: that loading it sends the visitor's IP address to the tile host (the
|
|
64
|
+
* platform's map gate says the same, website-components `map/copy.ts`). */
|
|
65
|
+
readonly mapNotice: string;
|
|
66
|
+
/** The map's label for a screen reader; `{name}` is the rental's. */
|
|
67
|
+
readonly mapLabel: string;
|
|
68
|
+
/** A count's words by plural category (`Intl.PluralRules`); `{n}` is the count. */
|
|
69
|
+
readonly adults: Readonly<Partial<Record<Intl.LDMLPluralRule, string>>> & {
|
|
70
|
+
readonly other: string;
|
|
71
|
+
};
|
|
72
|
+
readonly children: Readonly<Partial<Record<Intl.LDMLPluralRule, string>>> & {
|
|
73
|
+
readonly other: string;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export declare const DEFAULT_COPY: Readonly<Record<ChatLocale, ChatCopy>>;
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/theme.d.ts
|
|
79
|
+
/** The curated looks over one layout: `classic` (soft shadows, medium corners), `soft` (round corners,
|
|
80
|
+
* lighter shadows), `minimal` (borders instead of shadows, small corners). */
|
|
81
|
+
export declare const CHAT_THEMES: readonly ["classic", "soft", "minimal"];
|
|
82
|
+
type ChatTheme = (typeof CHAT_THEMES)[number];
|
|
83
|
+
/** A site's colours, any CSS colour. `primary` fills buttons, the send button and the chat's mark, and
|
|
84
|
+
* runs around the entry pill; `secondary` tints the visitor's messages and the suggestions. */
|
|
85
|
+
interface ChatColors {
|
|
86
|
+
readonly primary?: string;
|
|
87
|
+
readonly secondary?: string;
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/WebsiteChat.d.ts
|
|
91
|
+
interface WebsiteChatProps {
|
|
92
|
+
/** The chat service's URL for this environment: AI Studio's builds set `PUBLIC_CHAT_ENDPOINT` to it.
|
|
93
|
+
* Empty (a build outside AI Studio), the widget asks nothing and renders nothing. */
|
|
94
|
+
readonly endpoint: string;
|
|
95
|
+
readonly locale: ChatLocale;
|
|
96
|
+
/** The site's search route, the fallback for a rental without a page and when the chat is down. */
|
|
97
|
+
readonly searchPath: string;
|
|
98
|
+
/** The site's contact page: the chat links to it and has no form of its own. */
|
|
99
|
+
readonly contactPath: string;
|
|
100
|
+
readonly theme?: ChatTheme;
|
|
101
|
+
readonly colors?: ChatColors;
|
|
102
|
+
readonly position?: "right" | "left";
|
|
103
|
+
/** Overrides of single fields of `DEFAULT_COPY` (strings, the `suggestions` list, plural forms). */
|
|
104
|
+
readonly copy?: Partial<ChatCopy>;
|
|
105
|
+
}
|
|
106
|
+
/** The chat widget. */
|
|
107
|
+
type WebsiteChatComponent = (props: WebsiteChatProps) => React.JSX.Element | null;
|
|
108
|
+
/** See {@link WebsiteChatComponent}. */
|
|
109
|
+
export declare const WebsiteChat: WebsiteChatComponent;
|
|
110
|
+
//#endregion
|
|
111
|
+
export { CHAT_LOCALES, type ChatColors, type ChatCopy, type ChatLocale, type ChatTheme, type WebsiteChatComponent, type WebsiteChatProps };
|