@osvi-ai/chat 0.0.0-stage → 0.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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -3
  3. package/dist/core.js +10 -0
  4. package/dist/core.js.map +1 -0
  5. package/dist/index-BsRyQwUM.js +1331 -0
  6. package/dist/index-BsRyQwUM.js.map +1 -0
  7. package/dist/types/core/client.d.ts +65 -0
  8. package/dist/types/core/connection.d.ts +78 -0
  9. package/dist/types/core/errors.d.ts +14 -0
  10. package/dist/types/core/http.d.ts +38 -0
  11. package/dist/types/core/index.d.ts +7 -0
  12. package/dist/types/core/interactions.d.ts +50 -0
  13. package/dist/types/core/protocol/envelope.d.ts +2 -0
  14. package/dist/types/core/protocol/readers.d.ts +14 -0
  15. package/dist/types/core/protocol/types.d.ts +118 -0
  16. package/dist/types/core/protocol/validators.d.ts +10 -0
  17. package/dist/types/core/state.d.ts +23 -0
  18. package/dist/types/core/storage.d.ts +10 -0
  19. package/dist/types/core/version.d.ts +1 -0
  20. package/dist/types/ui/define.d.ts +1 -0
  21. package/dist/types/ui/index.d.ts +9 -0
  22. package/dist/types/ui/osvi-chat.d.ts +69 -0
  23. package/dist/types/ui/panel/attrs.d.ts +6 -0
  24. package/dist/types/ui/panel/composer.d.ts +10 -0
  25. package/dist/types/ui/panel/linkify.d.ts +7 -0
  26. package/dist/types/ui/panel/transcript.d.ts +4 -0
  27. package/dist/types/ui/strings.d.ts +27 -0
  28. package/dist/types/ui/styles.d.ts +1 -0
  29. package/dist/types/ui/theme.d.ts +22 -0
  30. package/dist/types/ui/tools/base.d.ts +14 -0
  31. package/dist/types/ui/tools/osvi-buttons.d.ts +10 -0
  32. package/dist/types/ui/tools/osvi-carousel.d.ts +16 -0
  33. package/dist/types/ui/tools/osvi-date-picker.d.ts +17 -0
  34. package/dist/types/widget/config.d.ts +22 -0
  35. package/dist/types/widget/index.d.ts +1 -0
  36. package/dist/types/widget/launcher.d.ts +35 -0
  37. package/dist/types/widget/widget.d.ts +13 -0
  38. package/dist/ui.js +49 -0
  39. package/dist/ui.js.map +1 -0
  40. package/dist/ui.mjs +570 -0
  41. package/dist/ui.mjs.map +1 -0
  42. package/dist/widget.js +59 -0
  43. package/dist/widget.js.map +1 -0
  44. package/package.json +60 -4
package/dist/ui.mjs ADDED
@@ -0,0 +1,570 @@
1
+ import { nothing as d, html as a, LitElement as W, css as X, unsafeCSS as x } from "lit";
2
+ import { property as l, state as b } from "lit/decorators.js";
3
+ import { c as et, i as it, a as st, b as R } from "./index-BsRyQwUM.js";
4
+ import { repeat as nt } from "lit/directives/repeat.js";
5
+ import { live as rt } from "lit/directives/live.js";
6
+ const Y = { primary: "#4f46e5", background: "#ffffff", surface: "#f4f4f5", text: "#18181b", userBubble: "#4f46e5", assistantBubble: "#f4f4f5" }, at = { primary: "#6366f1", background: "#0a0a12", surface: "#1c1c27", text: "#f4f4f5", userBubble: "#6366f1", assistantBubble: "#1f1f2b" }, ot = { none: "0px", sm: "6px", md: "10px", lg: "16px", full: "9999px" }, C = { primary: "--osvi-color-primary", background: "--osvi-color-background", surface: "--osvi-color-surface", text: "--osvi-color-text", userBubble: "--osvi-color-user-bubble", assistantBubble: "--osvi-color-assistant-bubble" }, w = (i) => `var(${C[i]}, ${Y[i]})`;
7
+ function F(i) {
8
+ const t = { ...i?.mode === "dark" ? at : Y, ...i?.colors }, e = {};
9
+ for (const n of Object.keys(C)) e[C[n]] = t[n];
10
+ const s = i?.radius ?? "md";
11
+ return e["--osvi-radius"] = typeof s == "number" ? `${s}px` : ot[s], e;
12
+ }
13
+ function lt(i) {
14
+ if (!i) return {};
15
+ if (i.mode) return F(i);
16
+ const t = F(i), e = {};
17
+ for (const s of Object.keys(i.colors ?? {})) e[C[s]] = t[C[s]];
18
+ return i.radius !== void 0 && (e["--osvi-radius"] = t["--osvi-radius"]), e;
19
+ }
20
+ const M = /* @__PURE__ */ new WeakMap();
21
+ function G(i, t) {
22
+ for (const s of M.get(i) ?? []) i.style.removeProperty(s);
23
+ const e = lt(t);
24
+ for (const [s, n] of Object.entries(e)) i.style.setProperty(s, n);
25
+ M.set(i, Object.keys(e));
26
+ }
27
+ const N = {
28
+ placeholder: "Type your message…",
29
+ send: "Send",
30
+ typing: "Typing…",
31
+ humanJoined: "A team member has joined",
32
+ chooseOption: "Choose an option to continue.",
33
+ typeAnswer: "Type your answer below, then press Send.",
34
+ sending: "Sending your response…",
35
+ pausedForHuman: "Choices are paused while a team member is connected.",
36
+ sendFailed: "Message not sent.",
37
+ retry: "Retry",
38
+ sessionEnded: "Conversation ended",
39
+ reconnecting: "Reconnecting…",
40
+ offline: "You are offline. We will reconnect automatically.",
41
+ unavailable: "This chat is unavailable.",
42
+ originBlocked: "This chat is not enabled for this website.",
43
+ rateLimited: "Too many requests. Please try again in a few minutes.",
44
+ tryAgain: "Try again",
45
+ closeChat: "Close chat",
46
+ poweredBy: "Powered by OSVI",
47
+ newChat: "New chat",
48
+ emptyState: "Send a message to start chatting.",
49
+ defaultTitle: "Chat"
50
+ }, L = /* @__PURE__ */ new Set();
51
+ function Q(i, t) {
52
+ L.has(i) || (L.add(i), console.warn(`[osvi-chat] ${t}`));
53
+ }
54
+ function y(i, t) {
55
+ return i != null && t.includes(i) ? i : void 0;
56
+ }
57
+ function K(i) {
58
+ if (i != null)
59
+ return y(i, ["none", "sm", "md", "lg", "full"]) ?? (/^\d{1,2}(?:px)?$/.test(i) ? Number(i.replace(/px$/, "")) : void 0);
60
+ }
61
+ function H(i) {
62
+ if (i) {
63
+ try {
64
+ const t = new URL(i);
65
+ if (!t.username && !t.password && (t.protocol === "https:" || t.protocol === "http:" && ["localhost", "127.0.0.1"].includes(t.hostname))) return i.replace(/\/+$/, "");
66
+ } catch {
67
+ }
68
+ Q("base-url", "ignored an invalid base-url (https required, http only for localhost)");
69
+ }
70
+ }
71
+ function dt(i) {
72
+ if (i) {
73
+ if (/^data:image\/(png|jpeg|gif|webp);base64,[a-z0-9+/]+={0,2}$/i.test(i)) return i;
74
+ try {
75
+ const t = new URL(i);
76
+ if (t.protocol === "https:" && !t.username && !t.password) return i;
77
+ } catch {
78
+ }
79
+ }
80
+ }
81
+ function J(i, t) {
82
+ if (t) {
83
+ try {
84
+ const e = JSON.parse(t);
85
+ if (e && typeof e == "object" && !Array.isArray(e)) return e;
86
+ } catch {
87
+ }
88
+ Q(i, `ignored invalid JSON in the ${i} attribute`);
89
+ }
90
+ }
91
+ const ct = /https?:\/\/[^\s<>"']+/g, S = (i, t) => i.split(t).length - 1;
92
+ function ht(i) {
93
+ for (; ; ) {
94
+ const t = i[i.length - 1];
95
+ if (t && ".,!?;:".includes(t) || t === ")" && S(i, ")") > S(i, "(") || t === "]" && S(i, "]") > S(i, "[")) {
96
+ i = i.slice(0, -1);
97
+ continue;
98
+ }
99
+ return i;
100
+ }
101
+ }
102
+ function ut(i) {
103
+ const t = [];
104
+ let e = 0;
105
+ for (const s of i.matchAll(ct)) {
106
+ const n = ht(s[0]), r = s.index;
107
+ try {
108
+ const o = new URL(n);
109
+ if (!["http:", "https:"].includes(o.protocol)) continue;
110
+ } catch {
111
+ continue;
112
+ }
113
+ r > e && t.push({ text: i.slice(e, r) }), t.push({ href: n, text: n }), e = r + n.length;
114
+ }
115
+ return e < i.length && t.push({ text: i.slice(e) }), t.length ? t : [{ text: i }];
116
+ }
117
+ function pt(i, t, e, s, n) {
118
+ return a`${i.items.length ? nt(i.items, (r) => r.id, (r) => {
119
+ if (r.kind === "divider") return a`<p class="divider">${t.sessionEnded}</p>`;
120
+ if (r.kind === "message") return a`<article class=${`bubble ${r.role}`}>${r.role === "human" ? a`<small>Team</small>` : d}<p>${ut(r.content).map(($) => "href" in $ ? a`<a href=${$.href} target="_blank" rel="noopener noreferrer">${$.text}</a>` : $.text)}</p>${r.role === "user" && r.status === "failed" ? a`<span>${t.sendFailed}</span><button type="button" @click=${() => n(r.id)}>${t.retry}</button>` : d}</article>`;
121
+ const o = r.interaction, u = o.interaction_id === i.activeInteraction?.interaction_id && o.interaction_status === "pending", _ = !u || i.interactionPending || i.mode === "human";
122
+ switch (o.tool_type) {
123
+ case "ui_buttons":
124
+ return a`<osvi-buttons data-live=${u ? "true" : "false"} .interaction=${o} .disabled=${_} .variant=${e.buttons ?? "chips"} base-url=${s}></osvi-buttons>`;
125
+ case "ui_carousel":
126
+ return a`<osvi-carousel data-live=${u ? "true" : "false"} .interaction=${o} .disabled=${_} .variant=${e.carousel ?? "slider"} base-url=${s}></osvi-carousel>`;
127
+ case "ui_date_picker":
128
+ return a`<osvi-date-picker data-live=${u ? "true" : "false"} .interaction=${o} .disabled=${_} base-url=${s}></osvi-date-picker>`;
129
+ default:
130
+ return a`<p>${o.content}</p>`;
131
+ }
132
+ }) : a`<p>${t.emptyState}</p>`}${i.awaitingReply ? a`<p aria-label=${t.typing}>${t.typing}<span aria-hidden="true"> •••</span></p>` : d}`;
133
+ }
134
+ function ft(i) {
135
+ return a`<div class="composer"><textarea aria-label="Message" placeholder=${i.placeholder} .value=${rt(i.draft)} ?disabled=${i.disabled} @input=${i.input} @keydown=${(t) => {
136
+ t.key === "Enter" && !t.shiftKey && !t.isComposing && !i.disabled && (t.preventDefault(), i.send());
137
+ }}></textarea><button type="button" aria-label=${i.strings.send} ?disabled=${i.disabled || !i.draft.trim()} @click=${i.send}>${i.strings.send}</button></div>`;
138
+ }
139
+ var bt = Object.defineProperty, h = (i, t, e, s) => {
140
+ for (var n = void 0, r = i.length - 1, o; r >= 0; r--)
141
+ (o = i[r]) && (n = o(t, e, n) || n);
142
+ return n && bt(t, e, n), n;
143
+ };
144
+ const z = () => ({ status: "idle", mode: "ai", awaitingReply: !1, items: [], activeInteraction: null, interactionPending: !1, agent: null, error: null }), Z = { fromAttribute: (i) => i !== "false" };
145
+ var f;
146
+ const c = (f = class extends W {
147
+ constructor() {
148
+ super(...arguments), this.agentId = "", this.baseUrl = "", this.client = null, this.theme = {}, this.variants = {}, this.strings = {}, this.poweredBy = !0, this.newChat = !0, this.header = {}, this.closable = !1, this.chatState = z(), this.draft = "", this.busy = !1, this.typed = null, this.ownClient = null, this.boundClient = null, this.ownKey = "", this.generation = 0, this.followBottom = !0, this.focusedInteraction = null, this.send = () => {
149
+ if (this.blocked || !this.draft.trim() || !this.boundClient) return;
150
+ const t = this.boundClient, e = this.draft.trim(), s = this.typed;
151
+ this.draft = "", s && this.restoreDraft(), this.requestUpdate(), this.run(() => s ? t.respond(s.id, { kind: "text", value: e }) : t.send(e));
152
+ }, this.respond = (t) => {
153
+ const e = t.detail, s = this.chatState;
154
+ if (!e || this.busy || s.mode !== "ai" || s.interactionPending || s.activeInteraction?.interaction_status !== "pending" || e.interactionId !== s.activeInteraction.interaction_id) return;
155
+ t.stopPropagation();
156
+ const n = this.boundClient;
157
+ n && this.run(() => n.respond(e.interactionId, e.response));
158
+ }, this.requestInput = (t) => {
159
+ const e = t.detail, s = this.chatState;
160
+ !e || this.busy || s.mode !== "ai" || s.interactionPending || s.activeInteraction?.interaction_status !== "pending" || e.interactionId !== s.activeInteraction.interaction_id || (t.stopPropagation(), this.typed = { id: e.interactionId, placeholder: typeof e.placeholder == "string" ? e.placeholder : void 0, previousDraft: this.typed?.previousDraft ?? this.draft }, this.draft = "", this.requestUpdate(), this.updateComplete.then(() => this.renderRoot.querySelector("textarea")?.focus()));
161
+ };
162
+ }
163
+ connectedCallback() {
164
+ super.connectedCallback(), this.syncClient();
165
+ }
166
+ disconnectedCallback() {
167
+ super.disconnectedCallback(), this.releaseClient();
168
+ }
169
+ releaseClient() {
170
+ ++this.generation, this.unsubscribe?.(), this.unsubscribe = void 0, this.boundClient = null;
171
+ const t = this.ownClient;
172
+ this.ownClient = null, this.ownKey = "", t?.destroy(), this.busy = !1, this.typed = null, this.focusedInteraction = null, this.requestUpdate();
173
+ }
174
+ syncClient() {
175
+ if (!this.isConnected || !this.client && !this.agentId && !this.boundClient) return;
176
+ const t = JSON.stringify([this.agentId, H(this.baseUrl)]);
177
+ if ((this.client ? this.boundClient === this.client && !this.ownClient : this.ownClient && t === this.ownKey) || (this.releaseClient(), this.chatState = z(), !this.client && !this.agentId)) return;
178
+ const e = !this.client, s = this.client ?? f.clientFactory({ agentId: this.agentId, baseUrl: H(this.baseUrl) });
179
+ this.boundClient = s, e && (this.ownClient = s, this.ownKey = t);
180
+ const n = this.generation;
181
+ this.chatState = s.getState(), this.requestUpdate(), this.unsubscribe = s.subscribe((r) => {
182
+ this.generation !== n || this.boundClient !== s || (this.chatState = r, this.requestUpdate(), this.typed && (r.mode === "human" || r.activeInteraction?.interaction_id !== this.typed.id || r.activeInteraction.interaction_status !== "pending") && this.restoreDraft());
183
+ }), e && this.run(() => s.start());
184
+ }
185
+ willUpdate() {
186
+ this.syncClient(), G(this, this.mergedTheme());
187
+ }
188
+ mergedTheme() {
189
+ const t = J("theme", this.themeAttribute), e = {}, s = t?.colors;
190
+ if (s && typeof s == "object" && !Array.isArray(s))
191
+ for (const o of ["primary", "background", "surface", "text", "userBubble", "assistantBubble"]) {
192
+ const u = s[o];
193
+ typeof u == "string" && (e[o] = u);
194
+ }
195
+ typeof this.primaryColor == "string" && (e.primary = this.primaryColor);
196
+ const n = K(this.radius) ?? (typeof t?.radius == "number" && Number.isInteger(t.radius) && t.radius >= 0 && t.radius <= 99 ? t.radius : typeof t?.radius == "string" ? K(t.radius) : void 0), r = y(this.mode, ["light", "dark"]) ?? y(typeof t?.mode == "string" ? t.mode : void 0, ["light", "dark"]);
197
+ return { ...this.theme, colors: { ...this.theme?.colors, ...e }, ...n === void 0 ? {} : { radius: n }, ...r === void 0 ? {} : { mode: r } };
198
+ }
199
+ get labels() {
200
+ const t = { ...N };
201
+ for (const e of [this.strings, J("strings", this.stringsAttribute)])
202
+ for (const s of Object.keys(N)) typeof e?.[s] == "string" && (t[s] = e[s]);
203
+ return t;
204
+ }
205
+ get blocked() {
206
+ const t = this.chatState;
207
+ return !this.boundClient || this.busy || t.status === "connecting" || t.status === "closed" && !!t.error && !t.error.retryable || t.mode === "ai" && (t.interactionPending || t.activeInteraction?.interaction_status === "pending" && !this.typed);
208
+ }
209
+ get sessionBusy() {
210
+ return this.busy || this.chatState.status === "connecting" || this.chatState.interactionPending || this.chatState.items.some((t) => t.kind === "message" && t.status === "sending");
211
+ }
212
+ async run(t) {
213
+ if (this.busy || !this.boundClient) return;
214
+ const e = this.generation;
215
+ this.busy = !0, this.requestUpdate();
216
+ try {
217
+ await t();
218
+ } catch {
219
+ } finally {
220
+ this.generation === e && (this.busy = !1, this.requestUpdate());
221
+ }
222
+ }
223
+ restoreDraft() {
224
+ this.typed && (this.draft = this.typed.previousDraft, this.typed = null, this.requestUpdate());
225
+ }
226
+ async focusActiveInteraction() {
227
+ if (await this.updateComplete, !this.isConnected || this.chatState.activeInteraction?.interaction_status !== "pending") return;
228
+ const t = this.renderRoot.querySelector('[data-live="true"]');
229
+ if (!t) return;
230
+ const e = this.chatState.activeInteraction.interaction_id;
231
+ await t.updateComplete, !(!this.isConnected || this.chatState.activeInteraction?.interaction_id !== e) && t.shadowRoot?.querySelector('button:not(:disabled),input:not(:disabled),select:not(:disabled),textarea:not(:disabled),[tabindex="0"]')?.focus();
232
+ }
233
+ updated() {
234
+ const t = this.renderRoot.querySelector(".transcript");
235
+ t && this.followBottom && (t.scrollTop = t.scrollHeight);
236
+ const e = this.chatState.activeInteraction?.interaction_status === "pending" ? this.chatState.activeInteraction.interaction_id : null;
237
+ e !== this.focusedInteraction && (this.focusedInteraction = e, e && this.isConnected && !this.hidden && document.visibilityState !== "hidden" && this.focusActiveInteraction());
238
+ }
239
+ statusText(t) {
240
+ const e = this.chatState;
241
+ return e.error?.code === "origin_not_allowed" ? t.originBlocked : e.error && ["agent_inactive", "agent_archived", "agent_not_found"].includes(e.error.code) ? t.unavailable : e.error?.code === "rate_limited" ? t.rateLimited : e.error?.code === "token_failed" ? e.error.message : e.status === "recovering" ? t.reconnecting : e.status === "offline" ? t.offline : e.interactionPending || this.busy && this.typed ? t.sending : e.activeInteraction?.interaction_status === "pending" ? e.mode === "human" ? t.pausedForHuman : this.typed ? t.typeAnswer : t.chooseOption : e.status === "closed" ? t.sessionEnded : "";
242
+ }
243
+ render() {
244
+ const t = this.labels, e = this.chatState, s = this.boundClient, n = dt(this.headerAvatar ?? this.header.avatarUrl), r = { ...this.variants, ...y(this.buttons, ["chips", "list"]) ? { buttons: y(this.buttons, ["chips", "list"]) } : {}, ...y(this.carousel, ["slider", "list"]) ? { carousel: y(this.carousel, ["slider", "list"]) } : {} };
245
+ return a`<header>${n ? a`<img src=${n} alt="" referrerpolicy="no-referrer">` : d}<div class="heading"><h2>${this.headerTitle ?? this.header.title ?? e.agent?.name ?? t.defaultTitle}</h2>${this.headerSubtitle ?? this.header.subtitle ? a`<p>${this.headerSubtitle ?? this.header.subtitle}</p>` : d}${e.mode === "human" ? a`<small>${t.humanJoined}</small>` : d}</div>
246
+ ${this.newChat ? a`<button type="button" ?disabled=${!s || this.sessionBusy} @click=${() => s && !this.sessionBusy && void this.run(() => s.newSession())}>${t.newChat}</button>` : d}
247
+ ${this.closable ? a`<button type="button" aria-label=${t.closeChat} @click=${() => this.dispatchEvent(new CustomEvent("osvi-close", { bubbles: !0, composed: !0 }))}>×</button>` : d}</header>
248
+ <div class="transcript" role="log" aria-live="polite" aria-label="Conversation" @scroll=${(o) => {
249
+ const u = o.currentTarget;
250
+ this.followBottom = u.scrollHeight - u.scrollTop - u.clientHeight <= 80;
251
+ }} @osvi-respond=${this.respond} @osvi-request-input=${this.requestInput}>${pt(e, t, r, s?.resolveUrl("") ?? "", (o) => {
252
+ s && this.run(() => s.retry(o));
253
+ })}</div>
254
+ <div class="status" role="status">${this.statusText(t)}${e.error?.retryable && (e.status === "idle" || e.status === "closed") ? a`<button type="button" ?disabled=${this.busy || !s} @click=${() => s && void this.run(() => s.start())}>${t.tryAgain}</button>` : d}</div>
255
+ ${ft({ strings: t, draft: this.draft, placeholder: this.typed?.placeholder ?? t.placeholder, disabled: this.blocked, input: (o) => {
256
+ this.draft = o.target.value, this.requestUpdate();
257
+ }, send: this.send })}
258
+ ${this.poweredBy ? a`<footer><a href="https://osvi.ai" target="_blank" rel="noopener noreferrer">${t.poweredBy}</a></footer>` : d}`;
259
+ }
260
+ }, f.clientFactory = et, f.properties = { themeAttribute: { attribute: "theme" }, stringsAttribute: { attribute: "strings" } }, f.styles = X`
261
+ :host { display:flex; flex-direction:column; height:100%; min-height:260px; color:var(--osvi-color-text,#18181b); background:var(--osvi-color-background,#fff); font:14px/1.5 system-ui,sans-serif; border-radius:var(--osvi-radius,10px); overflow:hidden; }
262
+ header { display:flex; align-items:center; gap:10px; padding:14px; border-bottom:1px solid var(--osvi-color-surface,#f4f4f5); } header img { width:36px; height:36px; border-radius:50%; } .heading { flex:1; } h2,p { margin:0; } h2 { font-size:16px; } small { display:block; }
263
+ .transcript { flex:1; overflow:auto; padding:16px; min-height:0; } .bubble { width:fit-content; max-width:85%; padding:10px 14px; margin:8px 0; border-radius:var(--osvi-radius,10px); background:var(--osvi-color-assistant-bubble,#f4f4f5); overflow-wrap:anywhere; white-space:pre-wrap; } .user { margin-left:auto; color:#fff; background:var(--osvi-color-user-bubble,#4f46e5); } .bubble a { color:inherit; } .divider { text-align:center; } .status { padding:0 14px; } .composer { display:flex; gap:8px; padding:14px; } textarea { flex:1; resize:vertical; min-width:0; } button,textarea { font:inherit; } button { cursor:pointer; } button:disabled { cursor:default; } footer { padding:0 14px 10px; text-align:center; } footer a { color:inherit; } osvi-buttons,osvi-carousel,osvi-date-picker { display:block; margin:8px 0; }
264
+ `, f);
265
+ h([
266
+ l({ attribute: "agent-id" })
267
+ ], c.prototype, "agentId");
268
+ h([
269
+ l({ attribute: "base-url" })
270
+ ], c.prototype, "baseUrl");
271
+ h([
272
+ l({ attribute: !1 })
273
+ ], c.prototype, "client");
274
+ h([
275
+ l({ attribute: !1 })
276
+ ], c.prototype, "theme");
277
+ h([
278
+ l({ attribute: !1 })
279
+ ], c.prototype, "variants");
280
+ h([
281
+ l({ attribute: !1 })
282
+ ], c.prototype, "strings");
283
+ h([
284
+ l({ attribute: "powered-by", converter: Z })
285
+ ], c.prototype, "poweredBy");
286
+ h([
287
+ l({ attribute: "new-chat", converter: Z })
288
+ ], c.prototype, "newChat");
289
+ h([
290
+ l({ attribute: !1 })
291
+ ], c.prototype, "header");
292
+ h([
293
+ l({ type: Boolean })
294
+ ], c.prototype, "closable");
295
+ h([
296
+ l({ attribute: "header-title" })
297
+ ], c.prototype, "headerTitle");
298
+ h([
299
+ l({ attribute: "header-subtitle" })
300
+ ], c.prototype, "headerSubtitle");
301
+ h([
302
+ l({ attribute: "header-avatar" })
303
+ ], c.prototype, "headerAvatar");
304
+ h([
305
+ l({ attribute: "primary-color" })
306
+ ], c.prototype, "primaryColor");
307
+ h([
308
+ l()
309
+ ], c.prototype, "radius");
310
+ h([
311
+ l()
312
+ ], c.prototype, "mode");
313
+ h([
314
+ l()
315
+ ], c.prototype, "buttons");
316
+ h([
317
+ l()
318
+ ], c.prototype, "carousel");
319
+ h([
320
+ b()
321
+ ], c.prototype, "chatState");
322
+ h([
323
+ b()
324
+ ], c.prototype, "draft");
325
+ h([
326
+ b()
327
+ ], c.prototype, "busy");
328
+ h([
329
+ b()
330
+ ], c.prototype, "typed");
331
+ let vt = c;
332
+ function A(i, t) {
333
+ typeof customElements > "u" || customElements.get(i) || customElements.define(i, t);
334
+ }
335
+ const B = X`
336
+ :host { display: block; font: inherit; color: ${x(w("text"))}; background: ${x(w("background"))}; }
337
+ * { box-sizing: border-box; }
338
+ button { font: inherit; color: inherit; background: ${x(w("surface"))}; border: 1px solid currentColor; border-radius: var(--osvi-radius, 10px); padding: 10px 14px; cursor: pointer; }
339
+ button:focus-visible { outline: 3px solid ${x(w("primary"))}; outline-offset: 3px; }
340
+ button:disabled { opacity: .5; cursor: default; }
341
+ button[aria-pressed="true"] { outline: 2px solid ${x(w("primary"))}; }
342
+ small { display: block; }
343
+ .chips { display: flex; flex-wrap: wrap; gap: 8px; }
344
+ .list { display: grid; gap: 6px; }
345
+ .list button { width: 100%; text-align: left; }
346
+ .card { background: ${x(w("surface"))}; border-radius: var(--osvi-radius, 10px); padding: 12px; }
347
+ .image { aspect-ratio: 16 / 9; display: grid; place-items: center; overflow: hidden; }
348
+ .image img { width: 100%; height: 100%; object-fit: cover; }
349
+ .navigation { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: 10px 0; }
350
+ .days, .weekdays { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; text-align: center; }
351
+ .days button { padding: 8px 0; }
352
+ `;
353
+ var yt = Object.defineProperty, P = (i, t, e, s) => {
354
+ for (var n = void 0, r = i.length - 1, o; r >= 0; r--)
355
+ (o = i[r]) && (n = o(t, e, n) || n);
356
+ return n && yt(t, e, n), n;
357
+ };
358
+ class m extends W {
359
+ constructor() {
360
+ super(...arguments), this.interaction = null, this.disabled = !1, this.baseUrl = "";
361
+ }
362
+ get live() {
363
+ return !this.disabled && this.interaction?.interaction_status === "pending";
364
+ }
365
+ get valid() {
366
+ const t = this.interaction;
367
+ return !!t && t.tool_type === this.toolType && it(t, t.presentation);
368
+ }
369
+ respond(t) {
370
+ !this.interaction || !this.live || !this.valid || this.dispatchEvent(new CustomEvent("osvi-respond", { bubbles: !0, composed: !0, detail: { interactionId: this.interaction.interaction_id, response: t } }));
371
+ }
372
+ updated(t) {
373
+ t.has("theme") && G(this, this.theme);
374
+ }
375
+ }
376
+ P([
377
+ l({ attribute: !1 })
378
+ ], m.prototype, "interaction");
379
+ P([
380
+ l({ type: Boolean })
381
+ ], m.prototype, "disabled");
382
+ P([
383
+ l({ attribute: !1 })
384
+ ], m.prototype, "theme");
385
+ P([
386
+ l({ attribute: "base-url" })
387
+ ], m.prototype, "baseUrl");
388
+ var gt = Object.defineProperty, mt = (i, t, e, s) => {
389
+ for (var n = void 0, r = i.length - 1, o; r >= 0; r--)
390
+ (o = i[r]) && (n = o(t, e, n) || n);
391
+ return n && gt(t, e, n), n;
392
+ };
393
+ const E = class E extends m {
394
+ constructor() {
395
+ super(...arguments), this.toolType = "ui_buttons", this.variant = "chips";
396
+ }
397
+ render() {
398
+ const t = this.interaction;
399
+ if (!t) return d;
400
+ if (!this.valid) return a`<p>${t.content}</p>`;
401
+ const e = t.presentation, s = t.response_payload?.kind === "text" ? t.response_payload.value : e.options.find((n) => n.id === (t.response_payload?.kind === "selection" ? t.response_payload.value : t.selected_option_id))?.label;
402
+ return a`<p>${e.label}</p>${e.description ? a`<p>${e.description}</p>` : d}
403
+ ${this.live ? a`<div role="group" aria-label="Options" class=${this.variant} @keydown=${this.onKey}>
404
+ ${e.options.map((n) => a`<button type="button" @click=${() => this.pick(n.id, n.allow_input, n.input_placeholder)}><span>${n.label}</span>${n.description ? a`<small>${n.description}</small>` : d}</button>`)}
405
+ </div>` : s ? a`<p>${s}</p>` : d}`;
406
+ }
407
+ pick(t, e, s) {
408
+ !this.live || !this.valid || !this.interaction || (this.interaction.schema_version === 2 && e ? this.dispatchEvent(new CustomEvent("osvi-request-input", { bubbles: !0, composed: !0, detail: { interactionId: this.interaction.interaction_id, placeholder: s } })) : this.respond({ kind: "selection", value: t }));
409
+ }
410
+ onKey(t) {
411
+ if (!["ArrowDown", "ArrowUp", "ArrowRight", "ArrowLeft"].includes(t.key)) return;
412
+ const e = [...this.renderRoot.querySelectorAll("button")], s = e.indexOf(this.renderRoot.activeElement), n = t.key === "ArrowDown" || t.key === "ArrowRight" ? 1 : -1;
413
+ e[(s + n + e.length) % e.length]?.focus(), t.preventDefault();
414
+ }
415
+ };
416
+ E.styles = B;
417
+ let I = E;
418
+ mt([
419
+ l()
420
+ ], I.prototype, "variant");
421
+ var $t = Object.defineProperty, q = (i, t, e, s) => {
422
+ for (var n = void 0, r = i.length - 1, o; r >= 0; r--)
423
+ (o = i[r]) && (n = o(t, e, n) || n);
424
+ return n && $t(t, e, n), n;
425
+ };
426
+ const O = class O extends m {
427
+ constructor() {
428
+ super(...arguments), this.toolType = "ui_carousel", this.variant = "slider", this.index = 0, this.imageFailed = /* @__PURE__ */ new Set(), this.suppressPointerClick = !1;
429
+ }
430
+ willUpdate(t) {
431
+ t.has("interaction") && (this.index = 0, this.imageFailed = /* @__PURE__ */ new Set(), this.pointerStart = void 0, this.suppressPointerClick = !1);
432
+ }
433
+ render() {
434
+ const t = this.interaction;
435
+ if (!t) return d;
436
+ if (!this.valid) return a`<p>${t.content}</p>`;
437
+ const e = t.presentation, s = e.items.find((n) => n.id === (t.response_payload?.kind === "selection" ? t.response_payload.value : t.selected_option_id));
438
+ return a`<p>${e.label}</p>${e.description ? a`<p>${e.description}</p>` : d}
439
+ ${this.live ? this.variant === "list" ? a`<div class="list">${e.items.map((n, r) => this.card(n, r))}</div>` : a`<div @pointerdown=${(n) => {
440
+ this.pointerStart = n.clientX, this.suppressPointerClick = !1;
441
+ }} @pointerup=${(n) => {
442
+ this.pointerStart !== void 0 && Math.abs(n.clientX - this.pointerStart) > 40 && (this.suppressPointerClick = !0, this.navigate(n.clientX < this.pointerStart ? 1 : -1, e.items.length)), this.pointerStart = void 0;
443
+ }} @pointercancel=${() => {
444
+ this.pointerStart = void 0;
445
+ }}>${this.card(e.items[this.index], this.index)}</div>
446
+ <div class="navigation"><button type="button" aria-label="Previous card" ?disabled=${this.index === 0} @click=${() => this.navigate(-1, e.items.length)}>Previous</button>
447
+ <span aria-live="polite">${this.index + 1} of ${e.items.length}</span>
448
+ <button type="button" aria-label="Next card" ?disabled=${this.index === e.items.length - 1} @click=${() => this.navigate(1, e.items.length)}>Next</button></div>` : s ? a`<p>${s.title}</p>` : d}`;
449
+ }
450
+ navigate(t, e) {
451
+ this.index = Math.max(0, Math.min(e - 1, this.index + t));
452
+ }
453
+ select(t, e) {
454
+ if (this.suppressPointerClick && t.detail !== 0) {
455
+ this.suppressPointerClick = !1, t.preventDefault(), t.stopPropagation();
456
+ return;
457
+ }
458
+ this.respond({ kind: "selection", value: e });
459
+ }
460
+ card(t, e) {
461
+ const s = t.image;
462
+ return a`<article class="card">${s && st(s.url) ? a`<div class="image">${this.imageFailed.has(e) ? a`<span role="img" aria-label="Image unavailable">Image unavailable</span>` : a`<img src=${this.baseUrl.replace(/\/$/, "") + s.url} alt=${s.alt} referrerpolicy="no-referrer" loading="lazy" @error=${() => {
463
+ this.imageFailed = /* @__PURE__ */ new Set([...this.imageFailed, e]);
464
+ }}>`}</div>` : d}
465
+ <h3>${t.title}</h3>${t.description ? a`<p>${t.description}</p>` : d}
466
+ <button type="button" @click=${(n) => this.select(n, t.id)}>${t.action_label ?? "Select"}</button></article>`;
467
+ }
468
+ };
469
+ O.styles = B;
470
+ let k = O;
471
+ q([
472
+ l()
473
+ ], k.prototype, "variant");
474
+ q([
475
+ b()
476
+ ], k.prototype, "index");
477
+ q([
478
+ b()
479
+ ], k.prototype, "imageFailed");
480
+ var xt = Object.defineProperty, T = (i, t, e, s) => {
481
+ for (var n = void 0, r = i.length - 1, o; r >= 0; r--)
482
+ (o = i[r]) && (n = o(t, e, n) || n);
483
+ return n && xt(t, e, n), n;
484
+ };
485
+ const tt = (i) => `${String(i.getFullYear()).padStart(4, "0")}-${String(i.getMonth() + 1).padStart(2, "0")}-${String(i.getDate()).padStart(2, "0")}`, D = () => tt(/* @__PURE__ */ new Date()), V = (i) => {
486
+ const t = /* @__PURE__ */ new Date(0);
487
+ return t.setHours(0, 0, 0, 0), t.setFullYear(Number(i.slice(0, 4)), Number(i.slice(5, 7)) - 1, Number(i.slice(8, 10))), t;
488
+ }, v = (i) => i.slice(0, 7), j = class j extends m {
489
+ constructor() {
490
+ super(...arguments), this.toolType = "ui_date_picker", this.variant = "grid", this.month = v(D()), this.start = "", this.end = "";
491
+ }
492
+ earliest(t) {
493
+ return t.min_date && t.min_date > D() ? t.min_date : D();
494
+ }
495
+ willUpdate(t) {
496
+ t.has("interaction") && (this.start = "", this.end = "", this.valid && (this.month = v(this.earliest(this.interaction.presentation))));
497
+ }
498
+ render() {
499
+ const t = this.interaction;
500
+ if (!t) return d;
501
+ if (!this.valid) return a`<p>${t.content}</p>`;
502
+ const e = t.presentation, s = t.response_payload, n = s?.kind === "date_range" ? `${s.value.start_date} – ${s.value.end_date}` : s?.kind === "date" || s?.kind === "text" ? s.value : t.selected_option_id;
503
+ if (!this.live) return a`<p>${e.label}</p>${e.description ? a`<p>${e.description}</p>` : d}${n ? a`<p>${n}</p>` : d}`;
504
+ const r = V(`${this.month}-01`), o = new Date(r);
505
+ o.setMonth(o.getMonth() + 1, 0);
506
+ const u = this.earliest(e), _ = Array.from({ length: o.getDate() }, (p, U) => `${this.month}-${String(U + 1).padStart(2, "0")}`), $ = e.selection_mode === "range" ? this.start ? this.end ? `${this.start} – ${this.end}` : `Check-in: ${this.start}. Choose check-out.` : "Choose check-in, then check-out." : this.start;
507
+ return a`<p>${e.label}</p>${e.description ? a`<p>${e.description}</p>` : d}
508
+ <div class="navigation"><button type="button" aria-label="Previous month" ?disabled=${this.month <= v(u)} @click=${() => this.moveMonth(-1, e)}>Previous</button>
509
+ <span aria-live="polite">${r.toLocaleDateString(void 0, { month: "long", year: "numeric" })}</span>
510
+ <button type="button" aria-label="Next month" ?disabled=${!!e.max_date && this.month >= v(e.max_date)} @click=${() => this.moveMonth(1, e)}>Next</button></div>
511
+ <div class="weekdays" aria-hidden="true">${["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map((p) => a`<span>${p}</span>`)}</div>
512
+ <div class="days" role="group" aria-label="Choose date" @keydown=${this.onKey}>
513
+ ${Array.from({ length: r.getDay() }, () => a`<span></span>`)}
514
+ ${_.map((p, U) => a`<button type="button" aria-label=${p} aria-pressed=${p === this.start || p === this.end} ?disabled=${p < u || !!e.max_date && p > e.max_date} @click=${() => this.pick(p, e)}>${U + 1}</button>`)}
515
+ </div><p aria-live="polite">${$}</p>
516
+ <button type="button" ?disabled=${!this.start || e.selection_mode === "range" && (!this.end || this.start >= this.end)} @click=${() => this.confirm(e)}>${e.confirm_label ?? "Confirm date"}</button>`;
517
+ }
518
+ moveMonth(t, e) {
519
+ const s = V(`${this.month}-01`);
520
+ s.setMonth(s.getMonth() + t);
521
+ const n = v(tt(s));
522
+ n >= v(this.earliest(e)) && (!e.max_date || n <= v(e.max_date)) && (this.month = n);
523
+ }
524
+ pick(t, e) {
525
+ !this.live || !this.valid || !R(t) || t < this.earliest(e) || e.max_date && t > e.max_date || (e.selection_mode === "single" || !this.start || this.end || t <= this.start ? (this.start = t, this.end = "") : this.end = t);
526
+ }
527
+ confirm(t) {
528
+ !R(this.start) || this.start < this.earliest(t) || t.max_date && this.start > t.max_date || (t.selection_mode === "single" ? this.respond({ kind: "date", value: this.start }) : R(this.end) && this.start < this.end && (!t.max_date || this.end <= t.max_date) && this.respond({ kind: "date_range", value: { start_date: this.start, end_date: this.end } }));
529
+ }
530
+ onKey(t) {
531
+ const s = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[t.key];
532
+ if (s === void 0) return;
533
+ const n = [...this.renderRoot.querySelectorAll(".days button")], r = n.indexOf(this.renderRoot.activeElement);
534
+ if (r < 0) return;
535
+ let o = r + s;
536
+ for (; o >= 0 && o < n.length && n[o]?.disabled; ) o += s > 0 ? 1 : -1;
537
+ n[o]?.focus(), t.preventDefault();
538
+ }
539
+ };
540
+ j.styles = B;
541
+ let g = j;
542
+ T([
543
+ l()
544
+ ], g.prototype, "variant");
545
+ T([
546
+ b()
547
+ ], g.prototype, "month");
548
+ T([
549
+ b()
550
+ ], g.prototype, "start");
551
+ T([
552
+ b()
553
+ ], g.prototype, "end");
554
+ A("osvi-chat", vt);
555
+ A("osvi-buttons", I);
556
+ A("osvi-carousel", k);
557
+ A("osvi-date-picker", g);
558
+ export {
559
+ N as DEFAULT_STRINGS,
560
+ I as OsviButtons,
561
+ k as OsviCarousel,
562
+ vt as OsviChat,
563
+ g as OsviDatePicker,
564
+ G as applyTheme,
565
+ w as cssVar,
566
+ A as defineOnce,
567
+ lt as explicitThemeVariables,
568
+ F as themeVariables
569
+ };
570
+ //# sourceMappingURL=ui.mjs.map