@wix/web5-embed 0.1.2
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/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/featureToggles.d.ts +11 -0
- package/dist/focusShortcut.d.ts +17 -0
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +393 -0
- package/dist/index.js.map +1 -0
- package/dist/loader-register.d.ts +52 -0
- package/dist/loader-types.d.ts +59 -0
- package/dist/promptInputTokens.d.ts +24 -0
- package/dist/script-tag.d.ts +20 -0
- package/dist/shared.d.ts +82 -0
- package/dist/staticVersions.d.ts +12 -0
- package/dist/types.d.ts +46 -0
- package/dist/widget-hooks/collapseController.d.ts +29 -0
- package/dist/widget-hooks/inputHeightSync.d.ts +24 -0
- package/dist/widget-hooks/submitFlow.d.ts +15 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wix.com Ltd.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# @wix/web5-embed
|
|
2
|
+
|
|
3
|
+
SDK primitives for embedding Wix Web5 conversational UI on host pages.
|
|
4
|
+
|
|
5
|
+
This package provides the **shared infrastructure** used by per-client embed
|
|
6
|
+
bundles (e.g. `@wix/w5-client-circana`'s embed bundle). It does not render a
|
|
7
|
+
widget on its own. Each client owns its own widget DOM, styles, and defaults
|
|
8
|
+
and composes them with the SDK primitives below.
|
|
9
|
+
|
|
10
|
+
## Distribution model
|
|
11
|
+
|
|
12
|
+
Per-client IIFE bundles ship to a CloudFront-backed CDN that's keyed by the
|
|
13
|
+
client's Wix MSID and supports `/production/` and `/latest/` aliases. Host
|
|
14
|
+
pages embed the bundle via a single URL that never changes:
|
|
15
|
+
|
|
16
|
+
```html
|
|
17
|
+
<script src="https://d13cktzsgh0qls.cloudfront.net/<MSID>/latest/user-query.iife.js"
|
|
18
|
+
data-target="#my-app"
|
|
19
|
+
data-base-url="/web5"
|
|
20
|
+
></script>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The Wix Web5 CLI scaffolds and deploys the per-client bundle, replacing
|
|
24
|
+
`<MSID>` with the customer's real MSID. No bootstrap loader, no version
|
|
25
|
+
pinning in URLs — `/production/` resolves to the latest deployed version on
|
|
26
|
+
each page load.
|
|
27
|
+
|
|
28
|
+
## What's inside
|
|
29
|
+
|
|
30
|
+
- **Focus-shortcut coordinator** (`getFocusShortcutCoordinator`) — cross-bundle
|
|
31
|
+
singleton that handles `/` and Cmd/Ctrl+K shortcuts so multiple Web5
|
|
32
|
+
widgets on the same page don't fight for the keyboard.
|
|
33
|
+
- **Design-token loader** (`attachPromptInputTokensStylesheet`) — fetches
|
|
34
|
+
`prompt-input-tokens.css` from a versioned CDN (parastorage by default,
|
|
35
|
+
which does *not* support `/production/`) and attaches it to a widget's
|
|
36
|
+
shadow root. Resolves the version via `static-versions`.
|
|
37
|
+
- **Static-versions helpers** (`fetchStaticVersions`, `findVersion`,
|
|
38
|
+
`getCachedVersions`, `cacheVersions`, `DEFAULT_STATIC_VERSIONS_URL`) — thin
|
|
39
|
+
wrappers over the artifact registry endpoint with an in-memory cache.
|
|
40
|
+
Used internally by the token loader; available for clients with similar
|
|
41
|
+
needs.
|
|
42
|
+
- **Script-tag config parsing** (`readBaseConfigFromScript`,
|
|
43
|
+
`readBottomConfigFromScript`, `resolveTarget`, `shouldInit`,
|
|
44
|
+
`shouldInitForPath`) — shared logic for turning host-page
|
|
45
|
+
`<script data-*>` attributes into a typed config object.
|
|
46
|
+
- **Widget hooks** (`createInputHeightSync`, `createCollapseController`,
|
|
47
|
+
`createSubmitFlow`) — small composable utilities each widget can wire in
|
|
48
|
+
without inheriting from a base class.
|
|
49
|
+
|
|
50
|
+
## Defaults assume Wix infra
|
|
51
|
+
|
|
52
|
+
The SDK is published to public npm so external partners can integrate, but
|
|
53
|
+
the defaults call Wix endpoints:
|
|
54
|
+
|
|
55
|
+
| Hook | Default | Override option |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `fetchStaticVersions(url?)` | `https://www.wixapis.com/web50-server/static-versions` | first arg `url` |
|
|
58
|
+
| `attachPromptInputTokensStylesheet(shadow, opts?)` | `static.parastorage.com/services/web50-server-ui/<v>/prompt-input-tokens.css`, artifact `com.wixpress.web50-server-ui` | `opts.staticVersionsUrl`, `opts.tokensCdnBase`, `opts.tokensArtifactId`, `opts.tokensFileName`, `opts.fallbackVersion` |
|
|
59
|
+
|
|
60
|
+
If you're running outside Wix infrastructure, override every hook above
|
|
61
|
+
with your own endpoints.
|
|
62
|
+
|
|
63
|
+
## Shadow DOM is required
|
|
64
|
+
|
|
65
|
+
Per-client widgets must render into a shadow root. The SDK helpers assume a
|
|
66
|
+
shadow root for token-stylesheet attachment and CSS isolation; widgets that
|
|
67
|
+
mount into the regular DOM will leak styles into the host page and may pick
|
|
68
|
+
up unintended host-page CSS.
|
|
69
|
+
|
|
70
|
+
## Quick start (minimal client widget)
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// in your per-client `main.ts`
|
|
74
|
+
import {
|
|
75
|
+
attachPromptInputTokensStylesheet,
|
|
76
|
+
createSubmitFlow,
|
|
77
|
+
getFocusShortcutCoordinator,
|
|
78
|
+
resolveTarget,
|
|
79
|
+
readBaseConfigFromScript,
|
|
80
|
+
shouldInit,
|
|
81
|
+
} from '@wix/web5-embed';
|
|
82
|
+
|
|
83
|
+
const config = readBaseConfigFromScript(document.currentScript);
|
|
84
|
+
if (!shouldInit(config.requireParam)) {
|
|
85
|
+
// host page didn't opt in
|
|
86
|
+
} else {
|
|
87
|
+
const host = resolveTarget(config.target);
|
|
88
|
+
const shadow = host.attachShadow({ mode: 'open' });
|
|
89
|
+
shadow.innerHTML = `<style>/* your branded styles */</style><textarea></textarea>`;
|
|
90
|
+
void attachPromptInputTokensStylesheet(shadow);
|
|
91
|
+
const submit = createSubmitFlow({ baseUrl: config.baseUrl ?? 'https://my-host/web5/v2' });
|
|
92
|
+
// ... wire up textarea + submit
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For a complete reference, see Circana's implementation in
|
|
97
|
+
`packages/w5-client-circana/src/web5-embed/`.
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT — see `LICENSE`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type EmbedFeatureToggleStatus = 'SUCCESSFUL' | 'MISSING_SEED' | 'DID_NOT_FIND_ELIGIBLE_FILTER' | 'ERROR';
|
|
2
|
+
export interface EmbedFeatureToggleResult {
|
|
3
|
+
id: string;
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
status: EmbedFeatureToggleStatus;
|
|
6
|
+
}
|
|
7
|
+
export interface FeatureToggleReader {
|
|
8
|
+
isEnabled: (id: string) => boolean;
|
|
9
|
+
get: (id: string) => EmbedFeatureToggleResult | undefined;
|
|
10
|
+
}
|
|
11
|
+
export declare function createFeatureToggleReader(featureToggles: Record<string, EmbedFeatureToggleResult> | undefined): FeatureToggleReader;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-bundle singleton that coordinates "/" and Cmd/Ctrl+K focus shortcuts
|
|
3
|
+
* across multiple Web5 widgets on the same host page.
|
|
4
|
+
*
|
|
5
|
+
* Stored on `window` under a global key so widgets from different bundles
|
|
6
|
+
* (legacy embed-input + new per-client embed) can share one keyboard listener.
|
|
7
|
+
*/
|
|
8
|
+
export interface FocusShortcutTarget {
|
|
9
|
+
focus(previousActive: HTMLElement | null): void;
|
|
10
|
+
isVisible(): boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface FocusShortcutCoordinator {
|
|
13
|
+
register(target: FocusShortcutTarget): void;
|
|
14
|
+
unregister(target: FocusShortcutTarget): void;
|
|
15
|
+
}
|
|
16
|
+
export declare function getFocusShortcutCoordinator(): FocusShortcutCoordinator;
|
|
17
|
+
export declare function __resetFocusShortcutCoordinatorForTests(): void;
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f="__web5InputFocusShortcut__",U='input, textarea, [contenteditable=""], [contenteditable="true"]',x='[role="dialog"][aria-modal="true"]';function V(e){if(e.hasAttribute("inert"))return!1;if(typeof e.checkVisibility=="function")return e.checkVisibility({checkOpacity:!0,checkVisibilityCSS:!0});const t=getComputedStyle(e);return!(t.visibility==="hidden"||t.display==="none"||parseFloat(t.opacity)===0)}function M(e){const t=e.composedPath();for(const n of t)if(n instanceof Element&&typeof n.matches=="function"&&n.matches(U))return!0;return!1}function O(){const e=window;if(e[f])return e[f];const t=[];let n=!1;const r=i=>{if(i.defaultPrevented||i.isComposing)return;const c=i.key==="/"&&!i.metaKey&&!i.ctrlKey&&!i.altKey&&!i.shiftKey,l=(i.metaKey||i.ctrlKey)&&!i.altKey&&i.key.toLowerCase()==="k";if(!c&&!l||c&&M(i))return;const m=document.querySelectorAll(x);for(const u of m)if(V(u))return;const p=t.find(u=>u.isVisible());if(!p)return;i.preventDefault();const d=document.activeElement instanceof HTMLElement?document.activeElement:null;p.focus(d)},o=()=>{n||(window.addEventListener("keydown",r),n=!0)},s=()=>{!n||t.length>0||(window.removeEventListener("keydown",r),n=!1)},a={register(i){t.includes(i)||t.push(i),o()},unregister(i){const c=t.indexOf(i);c>=0&&t.splice(c,1),s()},__teardown(){t.length=0,n&&(window.removeEventListener("keydown",r),n=!1)}};return e[f]=a,a}function H(){const e=window;e[f]?.__teardown(),delete e[f]}const g="https://www.wixapis.com/web50-server/static-versions",b="__web5_static_versions";async function y(e=g){const t=await fetch(e,{method:"GET",headers:{Accept:"application/json"},mode:"cors",credentials:"omit"});if(!t.ok)throw new Error(`[Web5Embed] static-versions returned ${t.status}`);return t.json()}function w(e,t){const n=e.find(r=>r.artifactId===t);if(!n)throw new Error(`[Web5Embed] artifact ${t} not found in static-versions`);return n.version}function C(){return window[b]}function T(e){window[b]=e}const N={staticVersionsUrl:"https://www.wixapis.com/web50-server/static-versions",tokensCdnBase:"https://static.parastorage.com/services/web50-server-ui",tokensArtifactId:"com.wixpress.web50-server-ui",tokensFileName:"prompt-input-tokens.css",fallbackVersion:"1.459.0"};function I(e){return{...N,...e??{}}}function E(e,t){const{tokensCdnBase:n,tokensFileName:r}=I(t);return`${n}/${e}/${r}`}async function v(e){const t=I(e);try{const n=C();if(n)return w(n,t.tokensArtifactId);const r=await y(t.staticVersionsUrl);return T(r),w(r,t.tokensArtifactId)}catch(n){return console.warn("[Web5Embed] Failed to resolve prompt token version, using fallback:",n),t.fallbackVersion}}async function S(e){const t=await v(e);return E(t,e)}async function $(e,t){if(e.querySelector('link[data-web5-prompt-input-tokens="true"]'))return;const r=document.createElement("link");r.rel="stylesheet",r.dataset.web5PromptInputTokens="true",r.href=await S(t),e.prepend(r)}const D=`
|
|
2
|
+
<svg class="web5-submit-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
3
|
+
<path d="M13.0306 7.53114C12.9609 7.60106 12.8781 7.65654 12.787 7.69439C12.6958 7.73224 12.5981 7.75173 12.4993 7.75173C12.4006 7.75173 12.3029 7.73224 12.2117 7.69439C12.1206 7.65654 12.0378 7.60106 11.9681 7.53114L8.74997 4.31301V13.5005C8.74997 13.6994 8.67095 13.8902 8.5303 14.0308C8.38965 14.1715 8.19888 14.2505 7.99997 14.2505C7.80106 14.2505 7.61029 14.1715 7.46964 14.0308C7.32899 13.8902 7.24997 13.6994 7.24997 13.5005V4.31301L4.0306 7.53114C3.8897 7.67203 3.69861 7.75119 3.49935 7.75119C3.30009 7.75119 3.10899 7.67203 2.9681 7.53114C2.8272 7.39024 2.74805 7.19915 2.74805 6.99989C2.74805 6.80063 2.8272 6.60953 2.9681 6.46864L7.4681 1.96864C7.53778 1.89872 7.62057 1.84324 7.71173 1.80539C7.8029 1.76753 7.90064 1.74805 7.99935 1.74805C8.09806 1.74805 8.1958 1.76753 8.28696 1.80539C8.37813 1.84324 8.46092 1.89872 8.5306 1.96864L13.0306 6.46864C13.1005 6.53832 13.156 6.62111 13.1938 6.71227C13.2317 6.80344 13.2512 6.90118 13.2512 6.99989C13.2512 7.0986 13.2317 7.19634 13.1938 7.2875C13.156 7.37867 13.1005 7.46146 13.0306 7.53114Z" fill="currentColor" />
|
|
4
|
+
</svg>
|
|
5
|
+
`;function k(e){return`${e}?initialQuestion=`}function W(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}const B="/_api/page-enrichment/v1/page-meta-data/chips",_=5e3,R="https://www.wixapis.com/wix-assistant-web5/v1/turn",K="https://www.wixapis.com/wix-assistant-web5/v1/conversation",h="pdpChips";async function j(e,t={}){if(t.conversation?.web5Id)return A({...t.conversation,timeoutMs:t.timeoutMs,fetchImpl:t.fetchImpl});const n=t.endpoint??B,r=t.timeoutMs??_,o=L(t.fetchImpl),s=new AbortController,a=setTimeout(()=>s.abort(),r);try{const i=await o(`${n}?url=${encodeURIComponent(e)}`,{signal:s.signal});if(!i.ok)throw new Error(`HTTP ${i.status}`);return(await i.json()).chips?.map(l=>l.label).filter(Boolean)??[]}finally{clearTimeout(a)}}async function A(e){const n=(e.api??"turn")==="conversation",r=e.endpoint??(n?K:R),o=e.timeoutMs??_,s=L(e.fetchImpl),a=new AbortController,i=setTimeout(()=>a.abort(),o);try{const c=e.handle??"<unknown>",m=`Tell me about the ${e.entityType??"product"} with handle "${c}" from the user's current page.`,p=n?{...e.chatId?{chatId:e.chatId}:{},query:m,additionalData:{...e.mode?{mode:e.mode}:{},...e.sessionId?{sessionId:`${e.sessionId}-${h}`}:{},placement:h,...e.web5Id?{predecessorTriggerId:e.web5Id}:{}}}:{text:m,...e.sessionId?{sessionId:e.sessionId}:{},...e.web5Id?{parentMessageId:e.web5Id}:{},placement:h},d=await s(r,{method:"POST",headers:{"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(p),signal:a.signal});if(!d.ok)throw new Error(`HTTP ${d.status}`);const u=await d.text(),P=n?J(u):q(u);return Y(P)}finally{clearTimeout(i)}}function L(e){return e||(typeof window<"u"?fetch.bind(window):fetch)}function q(e){let t=e.trim();if(!t)return"";t.endsWith(",")&&(t=t.slice(0,-1)),t.startsWith("[")||(t=`[${t}`),t.endsWith("]")||(t+="]");try{const n=JSON.parse(t);if(!Array.isArray(n))return"";const r=n.find(o=>typeof o?.completed?.markdown=="string")?.completed?.markdown;return r||n.map(o=>typeof o?.markdown=="string"?o.markdown:"").join("")}catch{return""}}function J(e){let t=e.trim();if(!t)return"";t.endsWith(",")&&(t=t.slice(0,-1)),t.startsWith("[")||(t=`[${t}`),t.endsWith("]")||(t+="]");try{const n=JSON.parse(t);return Array.isArray(n)?n.map(r=>typeof r?.markdown=="string"?r.markdown:"").join(""):""}catch{return""}}function Y(e){const t=e.trim().replace(/^```(?:json)?\s*/i,"").replace(/\s*```$/,"").trim();if(!t)return[];try{return JSON.parse(t).chips?.map(r=>r.label).filter(Boolean)??[]}catch{return[]}}function G(e){if(e instanceof HTMLElement)return e;if(typeof e=="string"){const r=document.querySelector(e);if(!r)throw new Error(`[Web5Embed] Target element not found: ${e}`);return r}const t=document.currentScript,n=document.createElement("div");return t?.parentElement?t.parentElement.insertBefore(n,t):document.body.appendChild(n),n}function Z(e){return e?new URLSearchParams(window.location.search).has(e):!0}function Q(e,t=[]){return!(Array.isArray(e)?e:e!=null?[e]:t).includes(window.location.pathname)}function F(e){if(!e)return{};const t={},n=e.getAttribute("data-target");n&&(t.target=n);const r=e.getAttribute("data-placeholder");r&&(t.placeholder=r);const o=e.getAttribute("data-base-url");o&&(t.baseUrl=o);const s=e.getAttribute("data-require-param");s&&(t.requireParam=s);const a=e.getAttribute("data-suggestions");if(a)try{const i=JSON.parse(a);Array.isArray(i)&&i.length&&(t.suggestions=i)}catch{console.warn("[Web5Embed] Invalid data-suggestions JSON, ignoring.")}return t}function X(e){const t=F(e);if(!e)return t;const n=e.getAttribute("data-visibility-mode");(n==="always"||n==="after-target")&&(t.visibilityMode=n);const r=e.getAttribute("data-show-after-target");r&&(t.showAfterTarget=r);const o=e.getAttribute("data-exclude-paths");return o&&(t.excludePaths=o.includes(",")?o.split(",").map(s=>s.trim()):o),t}function z(e){if(typeof window>"u")return;const t=window.__web5Loader__;if(!t){console.warn(`[Web5Embed] window.__web5Loader__ not present; widget "${e.widgetId}" will not mount`);return}const n=async r=>e.mount(r,e.parseOptions(r.options));t.register(e.widgetId,n)}function ee(e){return(t,n)=>{const r=n.variant;if(!r||r==="default")return e.default(t,n);const o=e.variants?.[r];return o?o(t,n):(console.warn(`[Web5Embed] unknown variant "${r}" — falling back to default`),e.default(t,n))}}function te(e){const t=n=>e?.[n];return{isEnabled:n=>{const r=t(n);return r?.status==="SUCCESSFUL"&&r.enabled===!0},get:t}}function ne(e){const{input:t,wrapper:n,multilineClass:r,maxHeightPx:o,multilineThresholdMultiplier:s=1,fallbackLineHeightPx:a=32}=e;return{sync(){if(!n)return;const i=window.getComputedStyle(t),c=Number.parseFloat(i.lineHeight)||a;t.style.height="auto";const l=t.value.trim().length===0?c:Math.min(Math.max(t.scrollHeight,c),o);t.style.height=`${l}px`,n.classList.toggle(r,l>c*s)}}}const re=400;function oe(e={}){const t=e.thresholdPx??re;let n=!1;function r(){const o=document.documentElement,s=window.scrollY||o.scrollTop||0,a=s+window.innerHeight,i=o.scrollHeight-a;return o.scrollHeight>window.innerHeight+t&&s>0&&i<=t}return{evaluate(o){return!o.isCoordinatorVisible()||!r()?(n=!1,!1):o.hasFocusInside()||n?o.isCollapsed():!0},markManuallyExpanded(){n=!0},resetManualExpand(){n=!1}}}function ie(e){if(!e.baseUrl)throw new Error("[Web5Embed] createSubmitFlow requires a non-empty baseUrl.");const t=k(e.baseUrl),n=e.windowTarget??"_self";return{open(r){return window.open(t+encodeURIComponent(r),n),!0}}}exports.DEFAULT_STATIC_VERSIONS_URL=g;exports.__resetFocusShortcutCoordinatorForTests=H;exports.attachPromptInputTokensStylesheet=$;exports.buildUrl=k;exports.cacheVersions=T;exports.createCollapseController=oe;exports.createFeatureToggleReader=te;exports.createInputHeightSync=ne;exports.createSubmitFlow=ie;exports.createVariantMount=ee;exports.escapeHtml=W;exports.fetchChipSuggestions=j;exports.fetchContextualChips=A;exports.fetchStaticVersions=y;exports.findVersion=w;exports.getCachedVersions=C;exports.getFocusShortcutCoordinator=O;exports.getPromptInputTokensUrl=E;exports.readBaseConfigFromScript=F;exports.readBottomConfigFromScript=X;exports.registerWeb5Widget=z;exports.resolvePromptInputTokensUrl=S;exports.resolvePromptInputTokensVersion=v;exports.resolveTarget=G;exports.sendIconSvg=D;exports.shouldInit=Z;exports.shouldInitForPath=Q;
|
|
6
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/focusShortcut.ts","../src/staticVersions.ts","../src/promptInputTokens.ts","../src/shared.ts","../src/script-tag.ts","../src/loader-register.ts","../src/featureToggles.ts","../src/widget-hooks/inputHeightSync.ts","../src/widget-hooks/collapseController.ts","../src/widget-hooks/submitFlow.ts"],"sourcesContent":["/**\n * Cross-bundle singleton that coordinates \"/\" and Cmd/Ctrl+K focus shortcuts\n * across multiple Web5 widgets on the same host page.\n *\n * Stored on `window` under a global key so widgets from different bundles\n * (legacy embed-input + new per-client embed) can share one keyboard listener.\n */\n\nconst GLOBAL_KEY = '__web5InputFocusShortcut__' as const;\nconst EDITABLE_SELECTOR =\n 'input, textarea, [contenteditable=\"\"], [contenteditable=\"true\"]';\nconst MODAL_SELECTOR = '[role=\"dialog\"][aria-modal=\"true\"]';\n\nexport interface FocusShortcutTarget {\n focus(previousActive: HTMLElement | null): void;\n isVisible(): boolean;\n}\n\nexport interface FocusShortcutCoordinator {\n register(target: FocusShortcutTarget): void;\n unregister(target: FocusShortcutTarget): void;\n}\n\ninterface InternalCoordinator extends FocusShortcutCoordinator {\n __teardown(): void;\n}\n\ninterface CoordinatorWindow {\n [GLOBAL_KEY]?: InternalCoordinator;\n}\n\nfunction isModalVisible(modal: Element): boolean {\n if (modal.hasAttribute('inert')) return false;\n if (typeof (modal as HTMLElement).checkVisibility === 'function') {\n return (modal as HTMLElement).checkVisibility({\n checkOpacity: true,\n checkVisibilityCSS: true,\n });\n }\n const cs = getComputedStyle(modal as HTMLElement);\n if (\n cs.visibility === 'hidden' ||\n cs.display === 'none' ||\n parseFloat(cs.opacity) === 0\n ) {\n return false;\n }\n return true;\n}\n\nfunction pathContainsEditable(event: KeyboardEvent): boolean {\n const path = event.composedPath();\n for (const node of path) {\n if (\n node instanceof Element &&\n typeof node.matches === 'function' &&\n node.matches(EDITABLE_SELECTOR)\n ) {\n return true;\n }\n }\n return false;\n}\n\nexport function getFocusShortcutCoordinator(): FocusShortcutCoordinator {\n const w = window as unknown as CoordinatorWindow;\n if (w[GLOBAL_KEY]) return w[GLOBAL_KEY]!;\n\n const targets: FocusShortcutTarget[] = [];\n let listenerAttached = false;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.defaultPrevented || e.isComposing) return;\n\n const isSlash =\n e.key === '/' &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.altKey &&\n !e.shiftKey;\n const isCmdK =\n (e.metaKey || e.ctrlKey) &&\n !e.altKey &&\n e.key.toLowerCase() === 'k';\n\n if (!isSlash && !isCmdK) return;\n\n if (isSlash && pathContainsEditable(e)) return;\n\n const modals = document.querySelectorAll(MODAL_SELECTOR);\n for (const modal of modals) {\n if (isModalVisible(modal)) return;\n }\n\n const target = targets.find((t) => t.isVisible());\n if (!target) return;\n\n e.preventDefault();\n const previousActive =\n document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null;\n target.focus(previousActive);\n };\n\n const ensureListener = () => {\n if (listenerAttached) return;\n window.addEventListener('keydown', handleKeyDown);\n listenerAttached = true;\n };\n\n const dropListenerIfEmpty = () => {\n if (!listenerAttached || targets.length > 0) return;\n window.removeEventListener('keydown', handleKeyDown);\n listenerAttached = false;\n };\n\n const coordinator: InternalCoordinator = {\n register(target) {\n if (!targets.includes(target)) targets.push(target);\n ensureListener();\n },\n unregister(target) {\n const index = targets.indexOf(target);\n if (index >= 0) targets.splice(index, 1);\n dropListenerIfEmpty();\n },\n __teardown() {\n targets.length = 0;\n if (listenerAttached) {\n window.removeEventListener('keydown', handleKeyDown);\n listenerAttached = false;\n }\n },\n };\n\n w[GLOBAL_KEY] = coordinator;\n return coordinator;\n}\n\nexport function __resetFocusShortcutCoordinatorForTests(): void {\n const w = window as unknown as CoordinatorWindow;\n w[GLOBAL_KEY]?.__teardown();\n delete w[GLOBAL_KEY];\n}\n","/**\n * Resolves artifact versions from a remote `static-versions` JSON endpoint.\n *\n * The default URL points at Wix infra (`wixapis.com`), matching legacy\n * embed-input behaviour. Non-Wix consumers pass their own URL.\n */\n\nimport type { ArtifactVersion } from './types';\n\nexport const DEFAULT_STATIC_VERSIONS_URL =\n 'https://www.wixapis.com/web50-server/static-versions';\n\nconst CACHE_KEY = '__web5_static_versions';\n\ninterface CacheWindow {\n [CACHE_KEY]?: ArtifactVersion[];\n}\n\nexport async function fetchStaticVersions(\n url: string = DEFAULT_STATIC_VERSIONS_URL,\n): Promise<ArtifactVersion[]> {\n const res = await fetch(url, {\n method: 'GET',\n headers: { Accept: 'application/json' },\n mode: 'cors',\n credentials: 'omit',\n });\n if (!res.ok) {\n throw new Error(`[Web5Embed] static-versions returned ${res.status}`);\n }\n return res.json();\n}\n\nexport function findVersion(\n versions: ArtifactVersion[],\n artifactId: string,\n): string {\n const entry = versions.find((v) => v.artifactId === artifactId);\n if (!entry) {\n throw new Error(\n `[Web5Embed] artifact ${artifactId} not found in static-versions`,\n );\n }\n return entry.version;\n}\n\nexport function getCachedVersions(): ArtifactVersion[] | undefined {\n return (window as unknown as CacheWindow)[CACHE_KEY];\n}\n\nexport function cacheVersions(versions: ArtifactVersion[]): void {\n (window as unknown as CacheWindow)[CACHE_KEY] = versions;\n}\n","/**\n * Resolves and attaches the `prompt-input-tokens.css` design-token sheet to a\n * widget's shadow root. The sheet is owned by `web50-server-ui` and provides\n * the `--pi-*` CSS variables consumed by widget styles.\n *\n * Defaults match Wix infra; consumers can fully override for non-Wix\n * environments (e.g. self-hosted CSS in CI / partner deployments).\n */\n\nimport {\n cacheVersions,\n fetchStaticVersions,\n findVersion,\n getCachedVersions,\n} from './staticVersions';\n\nexport interface PromptInputTokensOptions {\n /** Static-versions endpoint to resolve the version from. */\n staticVersionsUrl?: string;\n /** CDN base where the tokens artifact is published. */\n tokensCdnBase?: string;\n /** Tokens artifact ID looked up in `static-versions`. */\n tokensArtifactId?: string;\n /** File name of the tokens stylesheet. */\n tokensFileName?: string;\n /** Version used when resolution fails. */\n fallbackVersion?: string;\n}\n\nconst DEFAULTS: Required<PromptInputTokensOptions> = {\n staticVersionsUrl: 'https://www.wixapis.com/web50-server/static-versions',\n tokensCdnBase: 'https://static.parastorage.com/services/web50-server-ui',\n tokensArtifactId: 'com.wixpress.web50-server-ui',\n tokensFileName: 'prompt-input-tokens.css',\n fallbackVersion: '1.459.0',\n};\n\nfunction resolveOptions(\n options?: PromptInputTokensOptions,\n): Required<PromptInputTokensOptions> {\n return { ...DEFAULTS, ...(options ?? {}) };\n}\n\nexport function getPromptInputTokensUrl(\n version: string,\n options?: PromptInputTokensOptions,\n): string {\n const { tokensCdnBase, tokensFileName } = resolveOptions(options);\n return `${tokensCdnBase}/${version}/${tokensFileName}`;\n}\n\nexport async function resolvePromptInputTokensVersion(\n options?: PromptInputTokensOptions,\n): Promise<string> {\n const opts = resolveOptions(options);\n try {\n const cached = getCachedVersions();\n if (cached) return findVersion(cached, opts.tokensArtifactId);\n\n const versions = await fetchStaticVersions(opts.staticVersionsUrl);\n cacheVersions(versions);\n return findVersion(versions, opts.tokensArtifactId);\n } catch (err) {\n console.warn(\n '[Web5Embed] Failed to resolve prompt token version, using fallback:',\n err,\n );\n return opts.fallbackVersion;\n }\n}\n\nexport async function resolvePromptInputTokensUrl(\n options?: PromptInputTokensOptions,\n): Promise<string> {\n const version = await resolvePromptInputTokensVersion(options);\n return getPromptInputTokensUrl(version, options);\n}\n\nexport async function attachPromptInputTokensStylesheet(\n shadowRoot: ShadowRoot,\n options?: PromptInputTokensOptions,\n): Promise<void> {\n const existing = shadowRoot.querySelector<HTMLLinkElement>(\n 'link[data-web5-prompt-input-tokens=\"true\"]',\n );\n if (existing) return;\n\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.dataset.web5PromptInputTokens = 'true';\n link.href = await resolvePromptInputTokensUrl(options);\n shadowRoot.prepend(link);\n}\n","/**\n * Shared utilities for widget rendering and chip-suggestion fetching.\n *\n * The default Wix `DEFAULT_BASE_URL` from legacy embed-input is removed —\n * each consumer must supply its own `baseUrl`.\n */\n\nexport interface ChipsApiResponse {\n chips?: { label: string }[];\n}\n\n/** Default arrow-up SVG used as the submit/chip icon. */\nexport const sendIconSvg = `\n <svg class=\"web5-submit-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M13.0306 7.53114C12.9609 7.60106 12.8781 7.65654 12.787 7.69439C12.6958 7.73224 12.5981 7.75173 12.4993 7.75173C12.4006 7.75173 12.3029 7.73224 12.2117 7.69439C12.1206 7.65654 12.0378 7.60106 11.9681 7.53114L8.74997 4.31301V13.5005C8.74997 13.6994 8.67095 13.8902 8.5303 14.0308C8.38965 14.1715 8.19888 14.2505 7.99997 14.2505C7.80106 14.2505 7.61029 14.1715 7.46964 14.0308C7.32899 13.8902 7.24997 13.6994 7.24997 13.5005V4.31301L4.0306 7.53114C3.8897 7.67203 3.69861 7.75119 3.49935 7.75119C3.30009 7.75119 3.10899 7.67203 2.9681 7.53114C2.8272 7.39024 2.74805 7.19915 2.74805 6.99989C2.74805 6.80063 2.8272 6.60953 2.9681 6.46864L7.4681 1.96864C7.53778 1.89872 7.62057 1.84324 7.71173 1.80539C7.8029 1.76753 7.90064 1.74805 7.99935 1.74805C8.09806 1.74805 8.1958 1.76753 8.28696 1.80539C8.37813 1.84324 8.46092 1.89872 8.5306 1.96864L13.0306 6.46864C13.1005 6.53832 13.156 6.62111 13.1938 6.71227C13.2317 6.80344 13.2512 6.90118 13.2512 6.99989C13.2512 7.0986 13.2317 7.19634 13.1938 7.2875C13.156 7.37867 13.1005 7.46146 13.0306 7.53114Z\" fill=\"currentColor\" />\n </svg>\n`;\n\nexport function buildUrl(base: string): string {\n return `${base}?initialQuestion=`;\n}\n\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Conversation identity + context for the warm (conversation-aware) chips\n * path. `web5-embed` is a dependency-light SDK with no `@wix/web5-core`\n * dependency, so the caller (the per-client widget, which already imports\n * web5-core) injects the identity (`chatId` / `sessionId` / `mode`) it\n * generates with `getChatId` / `getOrCreateSessionId` / `resolveConversationMode`.\n */\nexport interface ContextualChipsContext {\n /** Conversation trigger id from the page URL (`web5_id`). Maps to `Turn`'s\n * `parentMessageId` / `Conversation`'s `predecessorTriggerId`. */\n web5Id: string;\n /**\n * Product handle (entity id) for the current page. Composed into the query\n * the same way `pdpFraming` does (`composeQuery`) so the BE resolves the\n * product from the handle, not the URL. Falls back to `<unknown>` if absent.\n */\n handle?: string;\n /** Entity type used in the query phrasing (default `product`). */\n entityType?: string;\n /** Session id (Turn `session_id` / Conversation `additionalData.sessionId`). */\n sessionId?: string;\n /**\n * Which BE API serves the warm chips (B2B-4121, DL #110). `'turn'` uses the\n * lock-free `Turn` RPC; `'conversation'` uses `/v1/conversation`, which runs\n * the same prompt as the streamed page. The widget picks the endpoint.\n */\n api?: 'turn' | 'conversation';\n /** Conversation wire mode (e.g. `markdown_history_v3`). Conversation API only. */\n mode?: string;\n /** Chat id for the Conversation request body. Conversation API only. */\n chatId?: string;\n /** Override the endpoint (tests). */\n endpoint?: string;\n}\n\nexport interface FetchChipSuggestionsOptions {\n /** Endpoint that accepts `?url=...` and returns ChipsApiResponse. */\n endpoint?: string;\n /** Abort timeout in ms. */\n timeoutMs?: number;\n /** Override `fetch` (tests). */\n fetchImpl?: typeof fetch;\n /**\n * When set, the page is \"warm\" (the visitor arrived from a Web5 turn,\n * `?web5_id=` present) and chips are generated conversation-aware via the\n * `/conversation` endpoint instead of the URL-only page-enrichment endpoint\n * (B2B-4121).\n */\n conversation?: ContextualChipsContext;\n}\n\nexport interface FetchContextualChipsOptions extends ContextualChipsContext {\n /** Abort timeout in ms. */\n timeoutMs?: number;\n /** Override `fetch` (tests). */\n fetchImpl?: typeof fetch;\n}\n\nconst DEFAULT_CHIPS_ENDPOINT =\n '/_api/page-enrichment/v1/page-meta-data/chips';\nconst DEFAULT_CHIPS_TIMEOUT_MS = 5000;\n\n/**\n * The `wix-assistant-web5` `Turn` endpoint (DL #110). Unlike `/v1/conversation`,\n * `Turn` takes no per-chat lock, so chips run in parallel with the placement\n * answer without \"conversation is busy\" — no client-side serialization needed.\n * Hand-rolled here (not via the ambassador npm client) to keep the thin\n * input-widget bundle light, the same reason `placementFetch` hand-rolls.\n */\nconst DEFAULT_TURN_ENDPOINT =\n 'https://www.wixapis.com/wix-assistant-web5/v1/turn';\n\n/**\n * The `Conversation` endpoint — used for warm chips so they run the same\n * prompt as the streamed page. Same shape the placements POST (see\n * `embed-placement/src/placementFetch.ts`).\n */\nconst DEFAULT_CONVERSATION_ENDPOINT =\n 'https://www.wixapis.com/wix-assistant-web5/v1/conversation';\n\n/** Placement key the BE resolves the chips prompt by (B2B-4121). */\nconst PDP_CHIPS_PLACEMENT = 'pdpChips';\n\nexport async function fetchChipSuggestions(\n pageUrl: string,\n options: FetchChipSuggestionsOptions = {},\n): Promise<string[]> {\n // Warm page: generate conversation-aware chips from the prompt (B2B-4121).\n if (options.conversation?.web5Id) {\n return fetchContextualChips({\n ...options.conversation,\n timeoutMs: options.timeoutMs,\n fetchImpl: options.fetchImpl,\n });\n }\n\n const endpoint = options.endpoint ?? DEFAULT_CHIPS_ENDPOINT;\n const timeoutMs = options.timeoutMs ?? DEFAULT_CHIPS_TIMEOUT_MS;\n const fetchImpl = resolveFetch(options.fetchImpl);\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetchImpl(\n `${endpoint}?url=${encodeURIComponent(pageUrl)}`,\n { signal: controller.signal },\n );\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data: ChipsApiResponse = await response.json();\n return data.chips?.map((chip) => chip.label).filter(Boolean) ?? [];\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Generate conversation-aware chip suggestions for the warm path (B2B-4121).\n *\n * POSTs the same `/conversation` shape the placements use (see\n * `embed-placement/src/placementFetch.ts`), tagged `additionalData.placement =\n * 'pdpChips'` so the BE resolves the chips prompt by key and links the turn to\n * the prior conversation via `predecessorTriggerId`. The prompt is authored to\n * return JSON in the `ChipsApiResponse` shape, so the labels parse the same way\n * as the URL-only endpoint.\n */\nexport async function fetchContextualChips(\n options: FetchContextualChipsOptions,\n): Promise<string[]> {\n const api = options.api ?? 'turn';\n const conversationApi = api === 'conversation';\n const endpoint =\n options.endpoint ??\n (conversationApi ? DEFAULT_CONVERSATION_ENDPOINT : DEFAULT_TURN_ENDPOINT);\n const timeoutMs = options.timeoutMs ?? DEFAULT_CHIPS_TIMEOUT_MS;\n const fetchImpl = resolveFetch(options.fetchImpl);\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const handle = options.handle ?? '<unknown>';\n const entityType = options.entityType ?? 'product';\n // Product identity by handle (like `pdpFraming`'s composeQuery) so the BE\n // resolves the product from the query; `placement: 'pdpChips'` lets it\n // resolve the chips prompt by key. The conversation context (`web5_id`)\n // rides as Turn's `parentMessageId` or Conversation's `predecessorTriggerId`.\n const query = `Tell me about the ${entityType} with handle \"${handle}\" from the user's current page.`;\n\n const body = conversationApi\n ? {\n // `/v1/conversation` shape — same as the placements POST.\n ...(options.chatId ? { chatId: options.chatId } : {}),\n query,\n additionalData: {\n ...(options.mode ? { mode: options.mode } : {}),\n // Per-placement running-context key, like the placements use.\n ...(options.sessionId\n ? { sessionId: `${options.sessionId}-${PDP_CHIPS_PLACEMENT}` }\n : {}),\n placement: PDP_CHIPS_PLACEMENT,\n ...(options.web5Id\n ? { predecessorTriggerId: options.web5Id }\n : {}),\n },\n }\n : {\n // `TurnRequest` shape (DL #110) — lock-free.\n text: query,\n ...(options.sessionId ? { sessionId: options.sessionId } : {}),\n ...(options.web5Id ? { parentMessageId: options.web5Id } : {}),\n placement: PDP_CHIPS_PLACEMENT,\n };\n\n const response = await fetchImpl(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n // Chips are short — read the stream to completion, then parse the JSON the\n // prompt emitted out of the accumulated markdown (each API streams its own\n // response shape).\n const text = await response.text();\n const markdown = conversationApi\n ? accumulateConversationMarkdown(text)\n : accumulateTurnMarkdown(text);\n return parseChipsFromJson(markdown);\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\nfunction resolveFetch(override?: typeof fetch): typeof fetch {\n if (override) return override;\n return typeof window !== 'undefined' ? fetch.bind(window) : fetch;\n}\n\n/**\n * Concatenate the markdown out of a `Turn` JSON-array stream. Each\n * `TurnResponse` is a oneof: `markdown` (a delta) or `completed.markdown` (the\n * full final text). Prefer `completed.markdown` when present, else join the\n * deltas. (`turn`/`event` variants carry no markdown.) Tolerant enough to parse\n * the completed body in one shot, like `placementFetch`'s array parser.\n */\nfunction accumulateTurnMarkdown(streamText: string): string {\n let candidate = streamText.trim();\n if (!candidate) return '';\n if (candidate.endsWith(',')) candidate = candidate.slice(0, -1);\n if (!candidate.startsWith('[')) candidate = `[${candidate}`;\n if (!candidate.endsWith(']')) candidate += ']';\n try {\n const parsed = JSON.parse(candidate) as {\n markdown?: string;\n completed?: { markdown?: string };\n }[];\n if (!Array.isArray(parsed)) return '';\n const completed = parsed.find(\n (item) => typeof item?.completed?.markdown === 'string',\n )?.completed?.markdown;\n if (completed) return completed;\n return parsed\n .map((item) => (typeof item?.markdown === 'string' ? item.markdown : ''))\n .join('');\n } catch {\n return '';\n }\n}\n\n/**\n * Concatenate the markdown out of a `Conversation` JSON-array stream — items\n * are `ConversationResponse`s whose `markdown` field is a delta (the placements'\n * `placementFetch` accumulates the same way). Tolerant of a partial/closing\n * array so the completed body parses in one shot.\n */\nfunction accumulateConversationMarkdown(streamText: string): string {\n let candidate = streamText.trim();\n if (!candidate) return '';\n if (candidate.endsWith(',')) candidate = candidate.slice(0, -1);\n if (!candidate.startsWith('[')) candidate = `[${candidate}`;\n if (!candidate.endsWith(']')) candidate += ']';\n try {\n const parsed = JSON.parse(candidate) as { markdown?: string }[];\n if (!Array.isArray(parsed)) return '';\n return parsed\n .map((item) => (typeof item?.markdown === 'string' ? item.markdown : ''))\n .join('');\n } catch {\n return '';\n }\n}\n\n/**\n * Parse chip labels from the prompt's JSON output (the `ChipsApiResponse`\n * shape), tolerating an optional ```json code fence.\n */\nfunction parseChipsFromJson(markdown: string): string[] {\n const unfenced = markdown\n .trim()\n .replace(/^```(?:json)?\\s*/i, '')\n .replace(/\\s*```$/, '')\n .trim();\n if (!unfenced) return [];\n try {\n const data: ChipsApiResponse = JSON.parse(unfenced);\n return data.chips?.map((chip) => chip.label).filter(Boolean) ?? [];\n } catch {\n return [];\n }\n}\n","/**\n * Helpers shared by every per-client embed `main.ts` for parsing host-page\n * script-tag attributes and gating widget initialization.\n */\n\nimport type { Web5EmbedBaseConfig, Web5EmbedConfig } from './types';\n\nexport function resolveTarget(\n target: string | HTMLElement | undefined,\n): HTMLElement {\n if (target instanceof HTMLElement) return target;\n if (typeof target === 'string') {\n const el = document.querySelector<HTMLElement>(target);\n if (!el) {\n throw new Error(`[Web5Embed] Target element not found: ${target}`);\n }\n return el;\n }\n\n // No target specified — create a container next to the script tag.\n const script = document.currentScript;\n const container = document.createElement('div');\n if (script?.parentElement) {\n script.parentElement.insertBefore(container, script);\n } else {\n document.body.appendChild(container);\n }\n return container;\n}\n\nexport function shouldInit(requireParam?: string): boolean {\n if (!requireParam) return true;\n const params = new URLSearchParams(window.location.search);\n return params.has(requireParam);\n}\n\nexport function shouldInitForPath(\n excludePaths: string | string[] | undefined,\n defaultExcludePaths: string[] = [],\n): boolean {\n const paths = Array.isArray(excludePaths)\n ? excludePaths\n : excludePaths != null\n ? [excludePaths]\n : defaultExcludePaths;\n return !paths.includes(window.location.pathname);\n}\n\n/**\n * Reads the standard `Web5EmbedBaseConfig` data-attributes from a script tag.\n * Per-client `main.ts` files extend this with their own widget-specific\n * attributes (visibility mode, copy props, etc.) by reading additional\n * attributes from the same script.\n */\nexport function readBaseConfigFromScript(\n script: HTMLOrSVGScriptElement | null,\n): Web5EmbedBaseConfig {\n if (!script) return {};\n\n const config: Web5EmbedBaseConfig = {};\n\n const target = script.getAttribute('data-target');\n if (target) config.target = target;\n\n const placeholder = script.getAttribute('data-placeholder');\n if (placeholder) config.placeholder = placeholder;\n\n const baseUrl = script.getAttribute('data-base-url');\n if (baseUrl) config.baseUrl = baseUrl;\n\n const requireParam = script.getAttribute('data-require-param');\n if (requireParam) config.requireParam = requireParam;\n\n const suggestionsRaw = script.getAttribute('data-suggestions');\n if (suggestionsRaw) {\n try {\n const parsed = JSON.parse(suggestionsRaw);\n if (Array.isArray(parsed) && parsed.length) {\n config.suggestions = parsed;\n }\n } catch {\n console.warn('[Web5Embed] Invalid data-suggestions JSON, ignoring.');\n }\n }\n\n return config;\n}\n\n/**\n * Reads the bottom-floating widget's extended config (visibility mode,\n * showAfterTarget, excludePaths) on top of the base config.\n */\nexport function readBottomConfigFromScript(\n script: HTMLOrSVGScriptElement | null,\n): Web5EmbedConfig {\n const config: Web5EmbedConfig = readBaseConfigFromScript(script);\n if (!script) return config;\n\n const visibilityMode = script.getAttribute('data-visibility-mode');\n if (visibilityMode === 'always' || visibilityMode === 'after-target') {\n config.visibilityMode = visibilityMode;\n }\n\n const showAfterTarget = script.getAttribute('data-show-after-target');\n if (showAfterTarget) config.showAfterTarget = showAfterTarget;\n\n const excludePathsRaw = script.getAttribute('data-exclude-paths');\n if (excludePathsRaw) {\n config.excludePaths = excludePathsRaw.includes(',')\n ? excludePathsRaw.split(',').map((p) => p.trim())\n : excludePathsRaw;\n }\n\n return config;\n}\n","/**\n * `registerWeb5Widget` — thin helper that per-client `main.ts` shims use to\n * register a widget against `window.__web5Loader__` (DL #089). Centralizes the\n * \"register-or-warn\" boilerplate and the `ctx.options → typed config` shape\n * coercion so each per-client entry file is a 5-line shim.\n *\n * `createVariantMount` — small dispatch helper for per-client bundles that\n * expose more than one rendering variant of the same widget id (e.g. circana's\n * default bottom-floating input + a homepage hero variant). The SDK does not\n * enumerate variant ids; each client owns its own.\n */\n\nimport type { LoaderContext, WidgetInit, WidgetInstance } from './loader-types';\n\nexport interface RegisterWidgetOptions<TConfig> {\n /**\n * The widget id under which to register. Must match a key in\n * `Configuration.widgetBundles` on the BE / mock config.\n */\n widgetId: string;\n /**\n * Cast / parse the loader's raw `ctx.options` into the client's typed\n * config. The loader passes `spec.options` through verbatim, so this is\n * usually a near-no-op cast.\n */\n parseOptions: (options: Record<string, unknown>) => TConfig;\n /**\n * Mount the widget. Return value supplies the lifecycle hooks the loader\n * uses on SPA route changes. May return synchronously or as a promise —\n * the `WidgetInit` wrapper awaits either form.\n */\n mount: (\n ctx: LoaderContext,\n config: TConfig,\n ) => WidgetInstance | Promise<WidgetInstance>;\n}\n\nexport function registerWeb5Widget<TConfig>(\n opts: RegisterWidgetOptions<TConfig>,\n): void {\n if (typeof window === 'undefined') return;\n const loader = window.__web5Loader__;\n if (!loader) {\n console.warn(\n `[Web5Embed] window.__web5Loader__ not present; widget \"${opts.widgetId}\" will not mount`,\n );\n return;\n }\n const init: WidgetInit = async (ctx) =>\n opts.mount(ctx, opts.parseOptions(ctx.options));\n loader.register(opts.widgetId, init);\n}\n\nexport type VariantMount<TConfig extends { variant?: string }> = (\n ctx: LoaderContext,\n config: TConfig,\n) => WidgetInstance | Promise<WidgetInstance>;\n\nexport interface VariantMountOptions<TConfig extends { variant?: string }> {\n /** Mount used when `config.variant` is undefined, `'default'`, or unknown. */\n default: VariantMount<TConfig>;\n /** Optional named variants, keyed by the string value of `config.variant`. */\n variants?: Record<string, VariantMount<TConfig>>;\n}\n\n/**\n * Build a mount function that dispatches on `config.variant`. Falls back to\n * `default` (with a console warning) when an unknown variant id is supplied —\n * the loader's route rule is the source of truth, and unknown variants\n * typically indicate a config/bundle version mismatch.\n */\nexport function createVariantMount<TConfig extends { variant?: string }>(\n opts: VariantMountOptions<TConfig>,\n): VariantMount<TConfig> {\n return (ctx, config) => {\n const id = config.variant;\n if (!id || id === 'default') {\n return opts.default(ctx, config);\n }\n const handler = opts.variants?.[id];\n if (handler) {\n return handler(ctx, config);\n }\n console.warn(\n `[Web5Embed] unknown variant \"${id}\" — falling back to default`,\n );\n return opts.default(ctx, config);\n };\n}\n","export type EmbedFeatureToggleStatus =\n | 'SUCCESSFUL'\n | 'MISSING_SEED'\n | 'DID_NOT_FIND_ELIGIBLE_FILTER'\n | 'ERROR';\n\nexport interface EmbedFeatureToggleResult {\n id: string;\n enabled: boolean;\n status: EmbedFeatureToggleStatus;\n}\n\nexport interface FeatureToggleReader {\n isEnabled: (id: string) => boolean;\n get: (id: string) => EmbedFeatureToggleResult | undefined;\n}\n\nexport function createFeatureToggleReader(\n featureToggles: Record<string, EmbedFeatureToggleResult> | undefined,\n): FeatureToggleReader {\n const get = (id: string): EmbedFeatureToggleResult | undefined =>\n featureToggles?.[id];\n\n return {\n isEnabled: (id) => {\n const result = get(id);\n return result?.status === 'SUCCESSFUL' && result.enabled === true;\n },\n get,\n };\n}\n","/**\n * Auto-resizing textarea height with a `multiline` class toggle on a wrapper.\n *\n * Pattern: textarea grows from `lineHeight` up to `maxHeightPx`. When growth\n * happens the wrapper element receives `multilineClass` so callers can\n * adjust padding/border-radius for the multi-line state.\n */\n\nexport interface InputHeightSyncOptions {\n input: HTMLTextAreaElement;\n /** Element that should toggle the `multiline` class. */\n wrapper: Element | null;\n /** Class added/removed on `wrapper` when input grows past one line. */\n multilineClass: string;\n /** Max height in px the input is allowed to grow to. */\n maxHeightPx: number;\n /** Multiplier applied to lineHeight to decide when to flip to multiline. Default 1. */\n multilineThresholdMultiplier?: number;\n /** Fallback lineHeight if computed style is unparsable. */\n fallbackLineHeightPx?: number;\n}\n\nexport interface InputHeightSync {\n sync(): void;\n}\n\nexport function createInputHeightSync(\n options: InputHeightSyncOptions,\n): InputHeightSync {\n const {\n input,\n wrapper,\n multilineClass,\n maxHeightPx,\n multilineThresholdMultiplier = 1,\n fallbackLineHeightPx = 32,\n } = options;\n\n return {\n sync() {\n if (!wrapper) return;\n\n const computed = window.getComputedStyle(input);\n const lineHeight =\n Number.parseFloat(computed.lineHeight) || fallbackLineHeightPx;\n\n input.style.height = 'auto';\n const nextHeight =\n input.value.trim().length === 0\n ? lineHeight\n : Math.min(Math.max(input.scrollHeight, lineHeight), maxHeightPx);\n input.style.height = `${nextHeight}px`;\n wrapper.classList.toggle(\n multilineClass,\n nextHeight > lineHeight * multilineThresholdMultiplier,\n );\n },\n };\n}\n","/**\n * Collapse controller for a bottom-fixed prompt widget.\n *\n * Tracks the scroll position relative to the bottom of the document and\n * collapses the widget when within `thresholdPx` of the page bottom — but\n * only if the widget itself doesn't currently hold focus and hasn't already\n * been manually expanded inside the bottom zone.\n *\n * Caller is responsible for binding scroll/resize listeners; this controller\n * just exposes pure state transitions.\n */\n\nexport interface CollapseControllerOptions {\n /** Distance in px from the bottom of the document at which to collapse. */\n thresholdPx?: number;\n}\n\nexport interface CollapseControllerState {\n isCollapsed(): boolean;\n isCoordinatorVisible(): boolean;\n hasFocusInside(): boolean;\n}\n\nexport interface CollapseController {\n /** Should the widget collapse right now, given current state? */\n evaluate(state: CollapseControllerState): boolean;\n /** Mark that the user has expanded the widget while in the bottom zone. */\n markManuallyExpanded(): void;\n /** Reset manual-expand memory (e.g. when leaving the bottom zone). */\n resetManualExpand(): void;\n}\n\nconst DEFAULT_THRESHOLD_PX = 400;\n\nexport function createCollapseController(\n options: CollapseControllerOptions = {},\n): CollapseController {\n const threshold = options.thresholdPx ?? DEFAULT_THRESHOLD_PX;\n let manuallyExpanded = false;\n\n function isBottomZoneVisible(): boolean {\n const doc = document.documentElement;\n const scrollTop = window.scrollY || doc.scrollTop || 0;\n const viewportBottom = scrollTop + window.innerHeight;\n const distanceToBottom = doc.scrollHeight - viewportBottom;\n const hasEnoughContent =\n doc.scrollHeight > window.innerHeight + threshold;\n return (\n hasEnoughContent && scrollTop > 0 && distanceToBottom <= threshold\n );\n }\n\n return {\n evaluate(state) {\n if (!state.isCoordinatorVisible()) {\n manuallyExpanded = false;\n return false;\n }\n\n if (!isBottomZoneVisible()) {\n manuallyExpanded = false;\n return false;\n }\n\n if (state.hasFocusInside()) {\n // Preserve current state; caller should not re-collapse while focused.\n return state.isCollapsed();\n }\n\n if (manuallyExpanded) {\n return state.isCollapsed();\n }\n\n return true;\n },\n markManuallyExpanded() {\n manuallyExpanded = true;\n },\n resetManualExpand() {\n manuallyExpanded = false;\n },\n };\n}\n","/**\n * Submit-flow helper: navigates the host page to `<baseUrl>?initialQuestion=<value>`.\n *\n * Pure side-effect function; pulled out so per-client widgets share the same\n * URL-build + window.open contract.\n */\n\nimport { buildUrl } from '../shared';\n\nexport interface SubmitFlowOptions {\n baseUrl: string;\n /** Window target for the navigation. Defaults to `_self`. */\n windowTarget?: string;\n}\n\nexport interface SubmitFlow {\n open(value: string): boolean;\n}\n\nexport function createSubmitFlow(options: SubmitFlowOptions): SubmitFlow {\n if (!options.baseUrl) {\n throw new Error(\n '[Web5Embed] createSubmitFlow requires a non-empty baseUrl.',\n );\n }\n const prefix = buildUrl(options.baseUrl);\n const target = options.windowTarget ?? '_self';\n\n return {\n open(value) {\n window.open(prefix + encodeURIComponent(value), target);\n return true;\n },\n };\n}\n"],"names":["GLOBAL_KEY","EDITABLE_SELECTOR","MODAL_SELECTOR","isModalVisible","modal","cs","pathContainsEditable","event","path","node","getFocusShortcutCoordinator","w","targets","listenerAttached","handleKeyDown","e","isSlash","isCmdK","modals","target","t","previousActive","ensureListener","dropListenerIfEmpty","coordinator","index","__resetFocusShortcutCoordinatorForTests","DEFAULT_STATIC_VERSIONS_URL","CACHE_KEY","fetchStaticVersions","url","res","findVersion","versions","artifactId","entry","v","getCachedVersions","cacheVersions","DEFAULTS","resolveOptions","options","getPromptInputTokensUrl","version","tokensCdnBase","tokensFileName","resolvePromptInputTokensVersion","opts","cached","err","resolvePromptInputTokensUrl","attachPromptInputTokensStylesheet","shadowRoot","link","sendIconSvg","buildUrl","base","escapeHtml","value","DEFAULT_CHIPS_ENDPOINT","DEFAULT_CHIPS_TIMEOUT_MS","DEFAULT_TURN_ENDPOINT","DEFAULT_CONVERSATION_ENDPOINT","PDP_CHIPS_PLACEMENT","fetchChipSuggestions","pageUrl","fetchContextualChips","endpoint","timeoutMs","fetchImpl","resolveFetch","controller","timeoutId","response","chip","conversationApi","handle","query","body","text","markdown","accumulateConversationMarkdown","accumulateTurnMarkdown","parseChipsFromJson","override","streamText","candidate","parsed","completed","item","unfenced","resolveTarget","el","script","container","shouldInit","requireParam","shouldInitForPath","excludePaths","defaultExcludePaths","readBaseConfigFromScript","config","placeholder","baseUrl","suggestionsRaw","readBottomConfigFromScript","visibilityMode","showAfterTarget","excludePathsRaw","p","registerWeb5Widget","loader","init","ctx","createVariantMount","id","handler","createFeatureToggleReader","featureToggles","get","result","createInputHeightSync","input","wrapper","multilineClass","maxHeightPx","multilineThresholdMultiplier","fallbackLineHeightPx","computed","lineHeight","nextHeight","DEFAULT_THRESHOLD_PX","createCollapseController","threshold","manuallyExpanded","isBottomZoneVisible","doc","scrollTop","viewportBottom","distanceToBottom","state","createSubmitFlow","prefix"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wix/web5-embed — public API barrier.
|
|
3
|
+
*
|
|
4
|
+
* Anything exported here becomes part of the npm package's contract with
|
|
5
|
+
* external consumers. Add carefully; bumping a major version becomes
|
|
6
|
+
* meaningful once external partners adopt the SDK.
|
|
7
|
+
*/
|
|
8
|
+
export type { ArtifactVersion, Web5EmbedBaseConfig, Web5EmbedConfig, } from './types';
|
|
9
|
+
export { getFocusShortcutCoordinator, __resetFocusShortcutCoordinatorForTests, type FocusShortcutCoordinator, type FocusShortcutTarget, } from './focusShortcut';
|
|
10
|
+
export { fetchStaticVersions, findVersion, getCachedVersions, cacheVersions, DEFAULT_STATIC_VERSIONS_URL, } from './staticVersions';
|
|
11
|
+
export { attachPromptInputTokensStylesheet, getPromptInputTokensUrl, resolvePromptInputTokensUrl, resolvePromptInputTokensVersion, type PromptInputTokensOptions, } from './promptInputTokens';
|
|
12
|
+
export { buildUrl, escapeHtml, fetchChipSuggestions, fetchContextualChips, sendIconSvg, type ChipsApiResponse, type ContextualChipsContext, type FetchChipSuggestionsOptions, type FetchContextualChipsOptions, } from './shared';
|
|
13
|
+
/**
|
|
14
|
+
* Script-tag config-reading primitives — kept for standalone consumers that
|
|
15
|
+
* mount a widget directly from a `<script>` tag without the unified loader.
|
|
16
|
+
* Per-client `main.ts` files in this monorepo no longer use these; they
|
|
17
|
+
* register against `window.__web5Loader__` via `registerWeb5Widget` instead.
|
|
18
|
+
*/
|
|
19
|
+
export { resolveTarget, shouldInit, shouldInitForPath, readBaseConfigFromScript, readBottomConfigFromScript, } from './script-tag';
|
|
20
|
+
export { registerWeb5Widget, createVariantMount, type RegisterWidgetOptions, type VariantMount, type VariantMountOptions, } from './loader-register';
|
|
21
|
+
export { createFeatureToggleReader, type EmbedFeatureToggleResult, type EmbedFeatureToggleStatus, type FeatureToggleReader, } from './featureToggles';
|
|
22
|
+
export type { EmbedIdentity, LoaderContext, LoaderRegistry, WidgetInit, WidgetInstance, } from './loader-types';
|
|
23
|
+
export { createInputHeightSync, type InputHeightSync, type InputHeightSyncOptions, } from './widget-hooks/inputHeightSync';
|
|
24
|
+
export { createCollapseController, type CollapseController, type CollapseControllerOptions, type CollapseControllerState, } from './widget-hooks/collapseController';
|
|
25
|
+
export { createSubmitFlow, type SubmitFlow, type SubmitFlowOptions, } from './widget-hooks/submitFlow';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
const f = "__web5InputFocusShortcut__", v = 'input, textarea, [contenteditable=""], [contenteditable="true"]', T = '[role="dialog"][aria-modal="true"]';
|
|
2
|
+
function I(t) {
|
|
3
|
+
if (t.hasAttribute("inert")) return !1;
|
|
4
|
+
if (typeof t.checkVisibility == "function")
|
|
5
|
+
return t.checkVisibility({
|
|
6
|
+
checkOpacity: !0,
|
|
7
|
+
checkVisibilityCSS: !0
|
|
8
|
+
});
|
|
9
|
+
const e = getComputedStyle(t);
|
|
10
|
+
return !(e.visibility === "hidden" || e.display === "none" || parseFloat(e.opacity) === 0);
|
|
11
|
+
}
|
|
12
|
+
function k(t) {
|
|
13
|
+
const e = t.composedPath();
|
|
14
|
+
for (const n of e)
|
|
15
|
+
if (n instanceof Element && typeof n.matches == "function" && n.matches(v))
|
|
16
|
+
return !0;
|
|
17
|
+
return !1;
|
|
18
|
+
}
|
|
19
|
+
function K() {
|
|
20
|
+
const t = window;
|
|
21
|
+
if (t[f]) return t[f];
|
|
22
|
+
const e = [];
|
|
23
|
+
let n = !1;
|
|
24
|
+
const r = (i) => {
|
|
25
|
+
if (i.defaultPrevented || i.isComposing) return;
|
|
26
|
+
const c = i.key === "/" && !i.metaKey && !i.ctrlKey && !i.altKey && !i.shiftKey, l = (i.metaKey || i.ctrlKey) && !i.altKey && i.key.toLowerCase() === "k";
|
|
27
|
+
if (!c && !l || c && k(i)) return;
|
|
28
|
+
const m = document.querySelectorAll(T);
|
|
29
|
+
for (const u of m)
|
|
30
|
+
if (I(u)) return;
|
|
31
|
+
const w = e.find((u) => u.isVisible());
|
|
32
|
+
if (!w) return;
|
|
33
|
+
i.preventDefault();
|
|
34
|
+
const d = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
35
|
+
w.focus(d);
|
|
36
|
+
}, o = () => {
|
|
37
|
+
n || (window.addEventListener("keydown", r), n = !0);
|
|
38
|
+
}, s = () => {
|
|
39
|
+
!n || e.length > 0 || (window.removeEventListener("keydown", r), n = !1);
|
|
40
|
+
}, a = {
|
|
41
|
+
register(i) {
|
|
42
|
+
e.includes(i) || e.push(i), o();
|
|
43
|
+
},
|
|
44
|
+
unregister(i) {
|
|
45
|
+
const c = e.indexOf(i);
|
|
46
|
+
c >= 0 && e.splice(c, 1), s();
|
|
47
|
+
},
|
|
48
|
+
__teardown() {
|
|
49
|
+
e.length = 0, n && (window.removeEventListener("keydown", r), n = !1);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
return t[f] = a, a;
|
|
53
|
+
}
|
|
54
|
+
function q() {
|
|
55
|
+
const t = window;
|
|
56
|
+
t[f]?.__teardown(), delete t[f];
|
|
57
|
+
}
|
|
58
|
+
const A = "https://www.wixapis.com/web50-server/static-versions", g = "__web5_static_versions";
|
|
59
|
+
async function S(t = A) {
|
|
60
|
+
const e = await fetch(t, {
|
|
61
|
+
method: "GET",
|
|
62
|
+
headers: { Accept: "application/json" },
|
|
63
|
+
mode: "cors",
|
|
64
|
+
credentials: "omit"
|
|
65
|
+
});
|
|
66
|
+
if (!e.ok)
|
|
67
|
+
throw new Error(`[Web5Embed] static-versions returned ${e.status}`);
|
|
68
|
+
return e.json();
|
|
69
|
+
}
|
|
70
|
+
function h(t, e) {
|
|
71
|
+
const n = t.find((r) => r.artifactId === e);
|
|
72
|
+
if (!n)
|
|
73
|
+
throw new Error(
|
|
74
|
+
`[Web5Embed] artifact ${e} not found in static-versions`
|
|
75
|
+
);
|
|
76
|
+
return n.version;
|
|
77
|
+
}
|
|
78
|
+
function _() {
|
|
79
|
+
return window[g];
|
|
80
|
+
}
|
|
81
|
+
function L(t) {
|
|
82
|
+
window[g] = t;
|
|
83
|
+
}
|
|
84
|
+
const x = {
|
|
85
|
+
staticVersionsUrl: "https://www.wixapis.com/web50-server/static-versions",
|
|
86
|
+
tokensCdnBase: "https://static.parastorage.com/services/web50-server-ui",
|
|
87
|
+
tokensArtifactId: "com.wixpress.web50-server-ui",
|
|
88
|
+
tokensFileName: "prompt-input-tokens.css",
|
|
89
|
+
fallbackVersion: "1.459.0"
|
|
90
|
+
};
|
|
91
|
+
function b(t) {
|
|
92
|
+
return { ...x, ...t ?? {} };
|
|
93
|
+
}
|
|
94
|
+
function F(t, e) {
|
|
95
|
+
const { tokensCdnBase: n, tokensFileName: r } = b(e);
|
|
96
|
+
return `${n}/${t}/${r}`;
|
|
97
|
+
}
|
|
98
|
+
async function M(t) {
|
|
99
|
+
const e = b(t);
|
|
100
|
+
try {
|
|
101
|
+
const n = _();
|
|
102
|
+
if (n) return h(n, e.tokensArtifactId);
|
|
103
|
+
const r = await S(e.staticVersionsUrl);
|
|
104
|
+
return L(r), h(r, e.tokensArtifactId);
|
|
105
|
+
} catch (n) {
|
|
106
|
+
return console.warn(
|
|
107
|
+
"[Web5Embed] Failed to resolve prompt token version, using fallback:",
|
|
108
|
+
n
|
|
109
|
+
), e.fallbackVersion;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function P(t) {
|
|
113
|
+
const e = await M(t);
|
|
114
|
+
return F(e, t);
|
|
115
|
+
}
|
|
116
|
+
async function j(t, e) {
|
|
117
|
+
if (t.querySelector(
|
|
118
|
+
'link[data-web5-prompt-input-tokens="true"]'
|
|
119
|
+
)) return;
|
|
120
|
+
const r = document.createElement("link");
|
|
121
|
+
r.rel = "stylesheet", r.dataset.web5PromptInputTokens = "true", r.href = await P(e), t.prepend(r);
|
|
122
|
+
}
|
|
123
|
+
const J = `
|
|
124
|
+
<svg class="web5-submit-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
|
125
|
+
<path d="M13.0306 7.53114C12.9609 7.60106 12.8781 7.65654 12.787 7.69439C12.6958 7.73224 12.5981 7.75173 12.4993 7.75173C12.4006 7.75173 12.3029 7.73224 12.2117 7.69439C12.1206 7.65654 12.0378 7.60106 11.9681 7.53114L8.74997 4.31301V13.5005C8.74997 13.6994 8.67095 13.8902 8.5303 14.0308C8.38965 14.1715 8.19888 14.2505 7.99997 14.2505C7.80106 14.2505 7.61029 14.1715 7.46964 14.0308C7.32899 13.8902 7.24997 13.6994 7.24997 13.5005V4.31301L4.0306 7.53114C3.8897 7.67203 3.69861 7.75119 3.49935 7.75119C3.30009 7.75119 3.10899 7.67203 2.9681 7.53114C2.8272 7.39024 2.74805 7.19915 2.74805 6.99989C2.74805 6.80063 2.8272 6.60953 2.9681 6.46864L7.4681 1.96864C7.53778 1.89872 7.62057 1.84324 7.71173 1.80539C7.8029 1.76753 7.90064 1.74805 7.99935 1.74805C8.09806 1.74805 8.1958 1.76753 8.28696 1.80539C8.37813 1.84324 8.46092 1.89872 8.5306 1.96864L13.0306 6.46864C13.1005 6.53832 13.156 6.62111 13.1938 6.71227C13.2317 6.80344 13.2512 6.90118 13.2512 6.99989C13.2512 7.0986 13.2317 7.19634 13.1938 7.2875C13.156 7.37867 13.1005 7.46146 13.0306 7.53114Z" fill="currentColor" />
|
|
126
|
+
</svg>
|
|
127
|
+
`;
|
|
128
|
+
function U(t) {
|
|
129
|
+
return `${t}?initialQuestion=`;
|
|
130
|
+
}
|
|
131
|
+
function Y(t) {
|
|
132
|
+
return t.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
133
|
+
}
|
|
134
|
+
const O = "/_api/page-enrichment/v1/page-meta-data/chips", y = 5e3, H = "https://www.wixapis.com/wix-assistant-web5/v1/turn", V = "https://www.wixapis.com/wix-assistant-web5/v1/conversation", p = "pdpChips";
|
|
135
|
+
async function G(t, e = {}) {
|
|
136
|
+
if (e.conversation?.web5Id)
|
|
137
|
+
return $({
|
|
138
|
+
...e.conversation,
|
|
139
|
+
timeoutMs: e.timeoutMs,
|
|
140
|
+
fetchImpl: e.fetchImpl
|
|
141
|
+
});
|
|
142
|
+
const n = e.endpoint ?? O, r = e.timeoutMs ?? y, o = C(e.fetchImpl), s = new AbortController(), a = setTimeout(() => s.abort(), r);
|
|
143
|
+
try {
|
|
144
|
+
const i = await o(
|
|
145
|
+
`${n}?url=${encodeURIComponent(t)}`,
|
|
146
|
+
{ signal: s.signal }
|
|
147
|
+
);
|
|
148
|
+
if (!i.ok)
|
|
149
|
+
throw new Error(`HTTP ${i.status}`);
|
|
150
|
+
return (await i.json()).chips?.map((l) => l.label).filter(Boolean) ?? [];
|
|
151
|
+
} finally {
|
|
152
|
+
clearTimeout(a);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function $(t) {
|
|
156
|
+
const n = (t.api ?? "turn") === "conversation", r = t.endpoint ?? (n ? V : H), o = t.timeoutMs ?? y, s = C(t.fetchImpl), a = new AbortController(), i = setTimeout(() => a.abort(), o);
|
|
157
|
+
try {
|
|
158
|
+
const c = t.handle ?? "<unknown>", m = `Tell me about the ${t.entityType ?? "product"} with handle "${c}" from the user's current page.`, w = n ? {
|
|
159
|
+
// `/v1/conversation` shape — same as the placements POST.
|
|
160
|
+
...t.chatId ? { chatId: t.chatId } : {},
|
|
161
|
+
query: m,
|
|
162
|
+
additionalData: {
|
|
163
|
+
...t.mode ? { mode: t.mode } : {},
|
|
164
|
+
// Per-placement running-context key, like the placements use.
|
|
165
|
+
...t.sessionId ? { sessionId: `${t.sessionId}-${p}` } : {},
|
|
166
|
+
placement: p,
|
|
167
|
+
...t.web5Id ? { predecessorTriggerId: t.web5Id } : {}
|
|
168
|
+
}
|
|
169
|
+
} : {
|
|
170
|
+
// `TurnRequest` shape (DL #110) — lock-free.
|
|
171
|
+
text: m,
|
|
172
|
+
...t.sessionId ? { sessionId: t.sessionId } : {},
|
|
173
|
+
...t.web5Id ? { parentMessageId: t.web5Id } : {},
|
|
174
|
+
placement: p
|
|
175
|
+
}, d = await s(r, {
|
|
176
|
+
method: "POST",
|
|
177
|
+
headers: {
|
|
178
|
+
"Content-Type": "application/json",
|
|
179
|
+
Accept: "text/event-stream"
|
|
180
|
+
},
|
|
181
|
+
body: JSON.stringify(w),
|
|
182
|
+
signal: a.signal
|
|
183
|
+
});
|
|
184
|
+
if (!d.ok)
|
|
185
|
+
throw new Error(`HTTP ${d.status}`);
|
|
186
|
+
const u = await d.text(), E = n ? D(u) : N(u);
|
|
187
|
+
return W(E);
|
|
188
|
+
} finally {
|
|
189
|
+
clearTimeout(i);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function C(t) {
|
|
193
|
+
return t || (typeof window < "u" ? fetch.bind(window) : fetch);
|
|
194
|
+
}
|
|
195
|
+
function N(t) {
|
|
196
|
+
let e = t.trim();
|
|
197
|
+
if (!e) return "";
|
|
198
|
+
e.endsWith(",") && (e = e.slice(0, -1)), e.startsWith("[") || (e = `[${e}`), e.endsWith("]") || (e += "]");
|
|
199
|
+
try {
|
|
200
|
+
const n = JSON.parse(e);
|
|
201
|
+
if (!Array.isArray(n)) return "";
|
|
202
|
+
const r = n.find(
|
|
203
|
+
(o) => typeof o?.completed?.markdown == "string"
|
|
204
|
+
)?.completed?.markdown;
|
|
205
|
+
return r || n.map((o) => typeof o?.markdown == "string" ? o.markdown : "").join("");
|
|
206
|
+
} catch {
|
|
207
|
+
return "";
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function D(t) {
|
|
211
|
+
let e = t.trim();
|
|
212
|
+
if (!e) return "";
|
|
213
|
+
e.endsWith(",") && (e = e.slice(0, -1)), e.startsWith("[") || (e = `[${e}`), e.endsWith("]") || (e += "]");
|
|
214
|
+
try {
|
|
215
|
+
const n = JSON.parse(e);
|
|
216
|
+
return Array.isArray(n) ? n.map((r) => typeof r?.markdown == "string" ? r.markdown : "").join("") : "";
|
|
217
|
+
} catch {
|
|
218
|
+
return "";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function W(t) {
|
|
222
|
+
const e = t.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim();
|
|
223
|
+
if (!e) return [];
|
|
224
|
+
try {
|
|
225
|
+
return JSON.parse(e).chips?.map((r) => r.label).filter(Boolean) ?? [];
|
|
226
|
+
} catch {
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function Z(t) {
|
|
231
|
+
if (t instanceof HTMLElement) return t;
|
|
232
|
+
if (typeof t == "string") {
|
|
233
|
+
const r = document.querySelector(t);
|
|
234
|
+
if (!r)
|
|
235
|
+
throw new Error(`[Web5Embed] Target element not found: ${t}`);
|
|
236
|
+
return r;
|
|
237
|
+
}
|
|
238
|
+
const e = document.currentScript, n = document.createElement("div");
|
|
239
|
+
return e?.parentElement ? e.parentElement.insertBefore(n, e) : document.body.appendChild(n), n;
|
|
240
|
+
}
|
|
241
|
+
function Q(t) {
|
|
242
|
+
return t ? new URLSearchParams(window.location.search).has(t) : !0;
|
|
243
|
+
}
|
|
244
|
+
function X(t, e = []) {
|
|
245
|
+
return !(Array.isArray(t) ? t : t != null ? [t] : e).includes(window.location.pathname);
|
|
246
|
+
}
|
|
247
|
+
function B(t) {
|
|
248
|
+
if (!t) return {};
|
|
249
|
+
const e = {}, n = t.getAttribute("data-target");
|
|
250
|
+
n && (e.target = n);
|
|
251
|
+
const r = t.getAttribute("data-placeholder");
|
|
252
|
+
r && (e.placeholder = r);
|
|
253
|
+
const o = t.getAttribute("data-base-url");
|
|
254
|
+
o && (e.baseUrl = o);
|
|
255
|
+
const s = t.getAttribute("data-require-param");
|
|
256
|
+
s && (e.requireParam = s);
|
|
257
|
+
const a = t.getAttribute("data-suggestions");
|
|
258
|
+
if (a)
|
|
259
|
+
try {
|
|
260
|
+
const i = JSON.parse(a);
|
|
261
|
+
Array.isArray(i) && i.length && (e.suggestions = i);
|
|
262
|
+
} catch {
|
|
263
|
+
console.warn("[Web5Embed] Invalid data-suggestions JSON, ignoring.");
|
|
264
|
+
}
|
|
265
|
+
return e;
|
|
266
|
+
}
|
|
267
|
+
function z(t) {
|
|
268
|
+
const e = B(t);
|
|
269
|
+
if (!t) return e;
|
|
270
|
+
const n = t.getAttribute("data-visibility-mode");
|
|
271
|
+
(n === "always" || n === "after-target") && (e.visibilityMode = n);
|
|
272
|
+
const r = t.getAttribute("data-show-after-target");
|
|
273
|
+
r && (e.showAfterTarget = r);
|
|
274
|
+
const o = t.getAttribute("data-exclude-paths");
|
|
275
|
+
return o && (e.excludePaths = o.includes(",") ? o.split(",").map((s) => s.trim()) : o), e;
|
|
276
|
+
}
|
|
277
|
+
function tt(t) {
|
|
278
|
+
if (typeof window > "u") return;
|
|
279
|
+
const e = window.__web5Loader__;
|
|
280
|
+
if (!e) {
|
|
281
|
+
console.warn(
|
|
282
|
+
`[Web5Embed] window.__web5Loader__ not present; widget "${t.widgetId}" will not mount`
|
|
283
|
+
);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const n = async (r) => t.mount(r, t.parseOptions(r.options));
|
|
287
|
+
e.register(t.widgetId, n);
|
|
288
|
+
}
|
|
289
|
+
function et(t) {
|
|
290
|
+
return (e, n) => {
|
|
291
|
+
const r = n.variant;
|
|
292
|
+
if (!r || r === "default")
|
|
293
|
+
return t.default(e, n);
|
|
294
|
+
const o = t.variants?.[r];
|
|
295
|
+
return o ? o(e, n) : (console.warn(
|
|
296
|
+
`[Web5Embed] unknown variant "${r}" — falling back to default`
|
|
297
|
+
), t.default(e, n));
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function nt(t) {
|
|
301
|
+
const e = (n) => t?.[n];
|
|
302
|
+
return {
|
|
303
|
+
isEnabled: (n) => {
|
|
304
|
+
const r = e(n);
|
|
305
|
+
return r?.status === "SUCCESSFUL" && r.enabled === !0;
|
|
306
|
+
},
|
|
307
|
+
get: e
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function rt(t) {
|
|
311
|
+
const {
|
|
312
|
+
input: e,
|
|
313
|
+
wrapper: n,
|
|
314
|
+
multilineClass: r,
|
|
315
|
+
maxHeightPx: o,
|
|
316
|
+
multilineThresholdMultiplier: s = 1,
|
|
317
|
+
fallbackLineHeightPx: a = 32
|
|
318
|
+
} = t;
|
|
319
|
+
return {
|
|
320
|
+
sync() {
|
|
321
|
+
if (!n) return;
|
|
322
|
+
const i = window.getComputedStyle(e), c = Number.parseFloat(i.lineHeight) || a;
|
|
323
|
+
e.style.height = "auto";
|
|
324
|
+
const l = e.value.trim().length === 0 ? c : Math.min(Math.max(e.scrollHeight, c), o);
|
|
325
|
+
e.style.height = `${l}px`, n.classList.toggle(
|
|
326
|
+
r,
|
|
327
|
+
l > c * s
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
const R = 400;
|
|
333
|
+
function ot(t = {}) {
|
|
334
|
+
const e = t.thresholdPx ?? R;
|
|
335
|
+
let n = !1;
|
|
336
|
+
function r() {
|
|
337
|
+
const o = document.documentElement, s = window.scrollY || o.scrollTop || 0, a = s + window.innerHeight, i = o.scrollHeight - a;
|
|
338
|
+
return o.scrollHeight > window.innerHeight + e && s > 0 && i <= e;
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
evaluate(o) {
|
|
342
|
+
return !o.isCoordinatorVisible() || !r() ? (n = !1, !1) : o.hasFocusInside() || n ? o.isCollapsed() : !0;
|
|
343
|
+
},
|
|
344
|
+
markManuallyExpanded() {
|
|
345
|
+
n = !0;
|
|
346
|
+
},
|
|
347
|
+
resetManualExpand() {
|
|
348
|
+
n = !1;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function it(t) {
|
|
353
|
+
if (!t.baseUrl)
|
|
354
|
+
throw new Error(
|
|
355
|
+
"[Web5Embed] createSubmitFlow requires a non-empty baseUrl."
|
|
356
|
+
);
|
|
357
|
+
const e = U(t.baseUrl), n = t.windowTarget ?? "_self";
|
|
358
|
+
return {
|
|
359
|
+
open(r) {
|
|
360
|
+
return window.open(e + encodeURIComponent(r), n), !0;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
export {
|
|
365
|
+
A as DEFAULT_STATIC_VERSIONS_URL,
|
|
366
|
+
q as __resetFocusShortcutCoordinatorForTests,
|
|
367
|
+
j as attachPromptInputTokensStylesheet,
|
|
368
|
+
U as buildUrl,
|
|
369
|
+
L as cacheVersions,
|
|
370
|
+
ot as createCollapseController,
|
|
371
|
+
nt as createFeatureToggleReader,
|
|
372
|
+
rt as createInputHeightSync,
|
|
373
|
+
it as createSubmitFlow,
|
|
374
|
+
et as createVariantMount,
|
|
375
|
+
Y as escapeHtml,
|
|
376
|
+
G as fetchChipSuggestions,
|
|
377
|
+
$ as fetchContextualChips,
|
|
378
|
+
S as fetchStaticVersions,
|
|
379
|
+
h as findVersion,
|
|
380
|
+
_ as getCachedVersions,
|
|
381
|
+
K as getFocusShortcutCoordinator,
|
|
382
|
+
F as getPromptInputTokensUrl,
|
|
383
|
+
B as readBaseConfigFromScript,
|
|
384
|
+
z as readBottomConfigFromScript,
|
|
385
|
+
tt as registerWeb5Widget,
|
|
386
|
+
P as resolvePromptInputTokensUrl,
|
|
387
|
+
M as resolvePromptInputTokensVersion,
|
|
388
|
+
Z as resolveTarget,
|
|
389
|
+
J as sendIconSvg,
|
|
390
|
+
Q as shouldInit,
|
|
391
|
+
X as shouldInitForPath
|
|
392
|
+
};
|
|
393
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/focusShortcut.ts","../src/staticVersions.ts","../src/promptInputTokens.ts","../src/shared.ts","../src/script-tag.ts","../src/loader-register.ts","../src/featureToggles.ts","../src/widget-hooks/inputHeightSync.ts","../src/widget-hooks/collapseController.ts","../src/widget-hooks/submitFlow.ts"],"sourcesContent":["/**\n * Cross-bundle singleton that coordinates \"/\" and Cmd/Ctrl+K focus shortcuts\n * across multiple Web5 widgets on the same host page.\n *\n * Stored on `window` under a global key so widgets from different bundles\n * (legacy embed-input + new per-client embed) can share one keyboard listener.\n */\n\nconst GLOBAL_KEY = '__web5InputFocusShortcut__' as const;\nconst EDITABLE_SELECTOR =\n 'input, textarea, [contenteditable=\"\"], [contenteditable=\"true\"]';\nconst MODAL_SELECTOR = '[role=\"dialog\"][aria-modal=\"true\"]';\n\nexport interface FocusShortcutTarget {\n focus(previousActive: HTMLElement | null): void;\n isVisible(): boolean;\n}\n\nexport interface FocusShortcutCoordinator {\n register(target: FocusShortcutTarget): void;\n unregister(target: FocusShortcutTarget): void;\n}\n\ninterface InternalCoordinator extends FocusShortcutCoordinator {\n __teardown(): void;\n}\n\ninterface CoordinatorWindow {\n [GLOBAL_KEY]?: InternalCoordinator;\n}\n\nfunction isModalVisible(modal: Element): boolean {\n if (modal.hasAttribute('inert')) return false;\n if (typeof (modal as HTMLElement).checkVisibility === 'function') {\n return (modal as HTMLElement).checkVisibility({\n checkOpacity: true,\n checkVisibilityCSS: true,\n });\n }\n const cs = getComputedStyle(modal as HTMLElement);\n if (\n cs.visibility === 'hidden' ||\n cs.display === 'none' ||\n parseFloat(cs.opacity) === 0\n ) {\n return false;\n }\n return true;\n}\n\nfunction pathContainsEditable(event: KeyboardEvent): boolean {\n const path = event.composedPath();\n for (const node of path) {\n if (\n node instanceof Element &&\n typeof node.matches === 'function' &&\n node.matches(EDITABLE_SELECTOR)\n ) {\n return true;\n }\n }\n return false;\n}\n\nexport function getFocusShortcutCoordinator(): FocusShortcutCoordinator {\n const w = window as unknown as CoordinatorWindow;\n if (w[GLOBAL_KEY]) return w[GLOBAL_KEY]!;\n\n const targets: FocusShortcutTarget[] = [];\n let listenerAttached = false;\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.defaultPrevented || e.isComposing) return;\n\n const isSlash =\n e.key === '/' &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.altKey &&\n !e.shiftKey;\n const isCmdK =\n (e.metaKey || e.ctrlKey) &&\n !e.altKey &&\n e.key.toLowerCase() === 'k';\n\n if (!isSlash && !isCmdK) return;\n\n if (isSlash && pathContainsEditable(e)) return;\n\n const modals = document.querySelectorAll(MODAL_SELECTOR);\n for (const modal of modals) {\n if (isModalVisible(modal)) return;\n }\n\n const target = targets.find((t) => t.isVisible());\n if (!target) return;\n\n e.preventDefault();\n const previousActive =\n document.activeElement instanceof HTMLElement\n ? document.activeElement\n : null;\n target.focus(previousActive);\n };\n\n const ensureListener = () => {\n if (listenerAttached) return;\n window.addEventListener('keydown', handleKeyDown);\n listenerAttached = true;\n };\n\n const dropListenerIfEmpty = () => {\n if (!listenerAttached || targets.length > 0) return;\n window.removeEventListener('keydown', handleKeyDown);\n listenerAttached = false;\n };\n\n const coordinator: InternalCoordinator = {\n register(target) {\n if (!targets.includes(target)) targets.push(target);\n ensureListener();\n },\n unregister(target) {\n const index = targets.indexOf(target);\n if (index >= 0) targets.splice(index, 1);\n dropListenerIfEmpty();\n },\n __teardown() {\n targets.length = 0;\n if (listenerAttached) {\n window.removeEventListener('keydown', handleKeyDown);\n listenerAttached = false;\n }\n },\n };\n\n w[GLOBAL_KEY] = coordinator;\n return coordinator;\n}\n\nexport function __resetFocusShortcutCoordinatorForTests(): void {\n const w = window as unknown as CoordinatorWindow;\n w[GLOBAL_KEY]?.__teardown();\n delete w[GLOBAL_KEY];\n}\n","/**\n * Resolves artifact versions from a remote `static-versions` JSON endpoint.\n *\n * The default URL points at Wix infra (`wixapis.com`), matching legacy\n * embed-input behaviour. Non-Wix consumers pass their own URL.\n */\n\nimport type { ArtifactVersion } from './types';\n\nexport const DEFAULT_STATIC_VERSIONS_URL =\n 'https://www.wixapis.com/web50-server/static-versions';\n\nconst CACHE_KEY = '__web5_static_versions';\n\ninterface CacheWindow {\n [CACHE_KEY]?: ArtifactVersion[];\n}\n\nexport async function fetchStaticVersions(\n url: string = DEFAULT_STATIC_VERSIONS_URL,\n): Promise<ArtifactVersion[]> {\n const res = await fetch(url, {\n method: 'GET',\n headers: { Accept: 'application/json' },\n mode: 'cors',\n credentials: 'omit',\n });\n if (!res.ok) {\n throw new Error(`[Web5Embed] static-versions returned ${res.status}`);\n }\n return res.json();\n}\n\nexport function findVersion(\n versions: ArtifactVersion[],\n artifactId: string,\n): string {\n const entry = versions.find((v) => v.artifactId === artifactId);\n if (!entry) {\n throw new Error(\n `[Web5Embed] artifact ${artifactId} not found in static-versions`,\n );\n }\n return entry.version;\n}\n\nexport function getCachedVersions(): ArtifactVersion[] | undefined {\n return (window as unknown as CacheWindow)[CACHE_KEY];\n}\n\nexport function cacheVersions(versions: ArtifactVersion[]): void {\n (window as unknown as CacheWindow)[CACHE_KEY] = versions;\n}\n","/**\n * Resolves and attaches the `prompt-input-tokens.css` design-token sheet to a\n * widget's shadow root. The sheet is owned by `web50-server-ui` and provides\n * the `--pi-*` CSS variables consumed by widget styles.\n *\n * Defaults match Wix infra; consumers can fully override for non-Wix\n * environments (e.g. self-hosted CSS in CI / partner deployments).\n */\n\nimport {\n cacheVersions,\n fetchStaticVersions,\n findVersion,\n getCachedVersions,\n} from './staticVersions';\n\nexport interface PromptInputTokensOptions {\n /** Static-versions endpoint to resolve the version from. */\n staticVersionsUrl?: string;\n /** CDN base where the tokens artifact is published. */\n tokensCdnBase?: string;\n /** Tokens artifact ID looked up in `static-versions`. */\n tokensArtifactId?: string;\n /** File name of the tokens stylesheet. */\n tokensFileName?: string;\n /** Version used when resolution fails. */\n fallbackVersion?: string;\n}\n\nconst DEFAULTS: Required<PromptInputTokensOptions> = {\n staticVersionsUrl: 'https://www.wixapis.com/web50-server/static-versions',\n tokensCdnBase: 'https://static.parastorage.com/services/web50-server-ui',\n tokensArtifactId: 'com.wixpress.web50-server-ui',\n tokensFileName: 'prompt-input-tokens.css',\n fallbackVersion: '1.459.0',\n};\n\nfunction resolveOptions(\n options?: PromptInputTokensOptions,\n): Required<PromptInputTokensOptions> {\n return { ...DEFAULTS, ...(options ?? {}) };\n}\n\nexport function getPromptInputTokensUrl(\n version: string,\n options?: PromptInputTokensOptions,\n): string {\n const { tokensCdnBase, tokensFileName } = resolveOptions(options);\n return `${tokensCdnBase}/${version}/${tokensFileName}`;\n}\n\nexport async function resolvePromptInputTokensVersion(\n options?: PromptInputTokensOptions,\n): Promise<string> {\n const opts = resolveOptions(options);\n try {\n const cached = getCachedVersions();\n if (cached) return findVersion(cached, opts.tokensArtifactId);\n\n const versions = await fetchStaticVersions(opts.staticVersionsUrl);\n cacheVersions(versions);\n return findVersion(versions, opts.tokensArtifactId);\n } catch (err) {\n console.warn(\n '[Web5Embed] Failed to resolve prompt token version, using fallback:',\n err,\n );\n return opts.fallbackVersion;\n }\n}\n\nexport async function resolvePromptInputTokensUrl(\n options?: PromptInputTokensOptions,\n): Promise<string> {\n const version = await resolvePromptInputTokensVersion(options);\n return getPromptInputTokensUrl(version, options);\n}\n\nexport async function attachPromptInputTokensStylesheet(\n shadowRoot: ShadowRoot,\n options?: PromptInputTokensOptions,\n): Promise<void> {\n const existing = shadowRoot.querySelector<HTMLLinkElement>(\n 'link[data-web5-prompt-input-tokens=\"true\"]',\n );\n if (existing) return;\n\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.dataset.web5PromptInputTokens = 'true';\n link.href = await resolvePromptInputTokensUrl(options);\n shadowRoot.prepend(link);\n}\n","/**\n * Shared utilities for widget rendering and chip-suggestion fetching.\n *\n * The default Wix `DEFAULT_BASE_URL` from legacy embed-input is removed —\n * each consumer must supply its own `baseUrl`.\n */\n\nexport interface ChipsApiResponse {\n chips?: { label: string }[];\n}\n\n/** Default arrow-up SVG used as the submit/chip icon. */\nexport const sendIconSvg = `\n <svg class=\"web5-submit-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M13.0306 7.53114C12.9609 7.60106 12.8781 7.65654 12.787 7.69439C12.6958 7.73224 12.5981 7.75173 12.4993 7.75173C12.4006 7.75173 12.3029 7.73224 12.2117 7.69439C12.1206 7.65654 12.0378 7.60106 11.9681 7.53114L8.74997 4.31301V13.5005C8.74997 13.6994 8.67095 13.8902 8.5303 14.0308C8.38965 14.1715 8.19888 14.2505 7.99997 14.2505C7.80106 14.2505 7.61029 14.1715 7.46964 14.0308C7.32899 13.8902 7.24997 13.6994 7.24997 13.5005V4.31301L4.0306 7.53114C3.8897 7.67203 3.69861 7.75119 3.49935 7.75119C3.30009 7.75119 3.10899 7.67203 2.9681 7.53114C2.8272 7.39024 2.74805 7.19915 2.74805 6.99989C2.74805 6.80063 2.8272 6.60953 2.9681 6.46864L7.4681 1.96864C7.53778 1.89872 7.62057 1.84324 7.71173 1.80539C7.8029 1.76753 7.90064 1.74805 7.99935 1.74805C8.09806 1.74805 8.1958 1.76753 8.28696 1.80539C8.37813 1.84324 8.46092 1.89872 8.5306 1.96864L13.0306 6.46864C13.1005 6.53832 13.156 6.62111 13.1938 6.71227C13.2317 6.80344 13.2512 6.90118 13.2512 6.99989C13.2512 7.0986 13.2317 7.19634 13.1938 7.2875C13.156 7.37867 13.1005 7.46146 13.0306 7.53114Z\" fill=\"currentColor\" />\n </svg>\n`;\n\nexport function buildUrl(base: string): string {\n return `${base}?initialQuestion=`;\n}\n\nexport function escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Conversation identity + context for the warm (conversation-aware) chips\n * path. `web5-embed` is a dependency-light SDK with no `@wix/web5-core`\n * dependency, so the caller (the per-client widget, which already imports\n * web5-core) injects the identity (`chatId` / `sessionId` / `mode`) it\n * generates with `getChatId` / `getOrCreateSessionId` / `resolveConversationMode`.\n */\nexport interface ContextualChipsContext {\n /** Conversation trigger id from the page URL (`web5_id`). Maps to `Turn`'s\n * `parentMessageId` / `Conversation`'s `predecessorTriggerId`. */\n web5Id: string;\n /**\n * Product handle (entity id) for the current page. Composed into the query\n * the same way `pdpFraming` does (`composeQuery`) so the BE resolves the\n * product from the handle, not the URL. Falls back to `<unknown>` if absent.\n */\n handle?: string;\n /** Entity type used in the query phrasing (default `product`). */\n entityType?: string;\n /** Session id (Turn `session_id` / Conversation `additionalData.sessionId`). */\n sessionId?: string;\n /**\n * Which BE API serves the warm chips (B2B-4121, DL #110). `'turn'` uses the\n * lock-free `Turn` RPC; `'conversation'` uses `/v1/conversation`, which runs\n * the same prompt as the streamed page. The widget picks the endpoint.\n */\n api?: 'turn' | 'conversation';\n /** Conversation wire mode (e.g. `markdown_history_v3`). Conversation API only. */\n mode?: string;\n /** Chat id for the Conversation request body. Conversation API only. */\n chatId?: string;\n /** Override the endpoint (tests). */\n endpoint?: string;\n}\n\nexport interface FetchChipSuggestionsOptions {\n /** Endpoint that accepts `?url=...` and returns ChipsApiResponse. */\n endpoint?: string;\n /** Abort timeout in ms. */\n timeoutMs?: number;\n /** Override `fetch` (tests). */\n fetchImpl?: typeof fetch;\n /**\n * When set, the page is \"warm\" (the visitor arrived from a Web5 turn,\n * `?web5_id=` present) and chips are generated conversation-aware via the\n * `/conversation` endpoint instead of the URL-only page-enrichment endpoint\n * (B2B-4121).\n */\n conversation?: ContextualChipsContext;\n}\n\nexport interface FetchContextualChipsOptions extends ContextualChipsContext {\n /** Abort timeout in ms. */\n timeoutMs?: number;\n /** Override `fetch` (tests). */\n fetchImpl?: typeof fetch;\n}\n\nconst DEFAULT_CHIPS_ENDPOINT =\n '/_api/page-enrichment/v1/page-meta-data/chips';\nconst DEFAULT_CHIPS_TIMEOUT_MS = 5000;\n\n/**\n * The `wix-assistant-web5` `Turn` endpoint (DL #110). Unlike `/v1/conversation`,\n * `Turn` takes no per-chat lock, so chips run in parallel with the placement\n * answer without \"conversation is busy\" — no client-side serialization needed.\n * Hand-rolled here (not via the ambassador npm client) to keep the thin\n * input-widget bundle light, the same reason `placementFetch` hand-rolls.\n */\nconst DEFAULT_TURN_ENDPOINT =\n 'https://www.wixapis.com/wix-assistant-web5/v1/turn';\n\n/**\n * The `Conversation` endpoint — used for warm chips so they run the same\n * prompt as the streamed page. Same shape the placements POST (see\n * `embed-placement/src/placementFetch.ts`).\n */\nconst DEFAULT_CONVERSATION_ENDPOINT =\n 'https://www.wixapis.com/wix-assistant-web5/v1/conversation';\n\n/** Placement key the BE resolves the chips prompt by (B2B-4121). */\nconst PDP_CHIPS_PLACEMENT = 'pdpChips';\n\nexport async function fetchChipSuggestions(\n pageUrl: string,\n options: FetchChipSuggestionsOptions = {},\n): Promise<string[]> {\n // Warm page: generate conversation-aware chips from the prompt (B2B-4121).\n if (options.conversation?.web5Id) {\n return fetchContextualChips({\n ...options.conversation,\n timeoutMs: options.timeoutMs,\n fetchImpl: options.fetchImpl,\n });\n }\n\n const endpoint = options.endpoint ?? DEFAULT_CHIPS_ENDPOINT;\n const timeoutMs = options.timeoutMs ?? DEFAULT_CHIPS_TIMEOUT_MS;\n const fetchImpl = resolveFetch(options.fetchImpl);\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetchImpl(\n `${endpoint}?url=${encodeURIComponent(pageUrl)}`,\n { signal: controller.signal },\n );\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data: ChipsApiResponse = await response.json();\n return data.chips?.map((chip) => chip.label).filter(Boolean) ?? [];\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\n/**\n * Generate conversation-aware chip suggestions for the warm path (B2B-4121).\n *\n * POSTs the same `/conversation` shape the placements use (see\n * `embed-placement/src/placementFetch.ts`), tagged `additionalData.placement =\n * 'pdpChips'` so the BE resolves the chips prompt by key and links the turn to\n * the prior conversation via `predecessorTriggerId`. The prompt is authored to\n * return JSON in the `ChipsApiResponse` shape, so the labels parse the same way\n * as the URL-only endpoint.\n */\nexport async function fetchContextualChips(\n options: FetchContextualChipsOptions,\n): Promise<string[]> {\n const api = options.api ?? 'turn';\n const conversationApi = api === 'conversation';\n const endpoint =\n options.endpoint ??\n (conversationApi ? DEFAULT_CONVERSATION_ENDPOINT : DEFAULT_TURN_ENDPOINT);\n const timeoutMs = options.timeoutMs ?? DEFAULT_CHIPS_TIMEOUT_MS;\n const fetchImpl = resolveFetch(options.fetchImpl);\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const handle = options.handle ?? '<unknown>';\n const entityType = options.entityType ?? 'product';\n // Product identity by handle (like `pdpFraming`'s composeQuery) so the BE\n // resolves the product from the query; `placement: 'pdpChips'` lets it\n // resolve the chips prompt by key. The conversation context (`web5_id`)\n // rides as Turn's `parentMessageId` or Conversation's `predecessorTriggerId`.\n const query = `Tell me about the ${entityType} with handle \"${handle}\" from the user's current page.`;\n\n const body = conversationApi\n ? {\n // `/v1/conversation` shape — same as the placements POST.\n ...(options.chatId ? { chatId: options.chatId } : {}),\n query,\n additionalData: {\n ...(options.mode ? { mode: options.mode } : {}),\n // Per-placement running-context key, like the placements use.\n ...(options.sessionId\n ? { sessionId: `${options.sessionId}-${PDP_CHIPS_PLACEMENT}` }\n : {}),\n placement: PDP_CHIPS_PLACEMENT,\n ...(options.web5Id\n ? { predecessorTriggerId: options.web5Id }\n : {}),\n },\n }\n : {\n // `TurnRequest` shape (DL #110) — lock-free.\n text: query,\n ...(options.sessionId ? { sessionId: options.sessionId } : {}),\n ...(options.web5Id ? { parentMessageId: options.web5Id } : {}),\n placement: PDP_CHIPS_PLACEMENT,\n };\n\n const response = await fetchImpl(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n // Chips are short — read the stream to completion, then parse the JSON the\n // prompt emitted out of the accumulated markdown (each API streams its own\n // response shape).\n const text = await response.text();\n const markdown = conversationApi\n ? accumulateConversationMarkdown(text)\n : accumulateTurnMarkdown(text);\n return parseChipsFromJson(markdown);\n } finally {\n clearTimeout(timeoutId);\n }\n}\n\nfunction resolveFetch(override?: typeof fetch): typeof fetch {\n if (override) return override;\n return typeof window !== 'undefined' ? fetch.bind(window) : fetch;\n}\n\n/**\n * Concatenate the markdown out of a `Turn` JSON-array stream. Each\n * `TurnResponse` is a oneof: `markdown` (a delta) or `completed.markdown` (the\n * full final text). Prefer `completed.markdown` when present, else join the\n * deltas. (`turn`/`event` variants carry no markdown.) Tolerant enough to parse\n * the completed body in one shot, like `placementFetch`'s array parser.\n */\nfunction accumulateTurnMarkdown(streamText: string): string {\n let candidate = streamText.trim();\n if (!candidate) return '';\n if (candidate.endsWith(',')) candidate = candidate.slice(0, -1);\n if (!candidate.startsWith('[')) candidate = `[${candidate}`;\n if (!candidate.endsWith(']')) candidate += ']';\n try {\n const parsed = JSON.parse(candidate) as {\n markdown?: string;\n completed?: { markdown?: string };\n }[];\n if (!Array.isArray(parsed)) return '';\n const completed = parsed.find(\n (item) => typeof item?.completed?.markdown === 'string',\n )?.completed?.markdown;\n if (completed) return completed;\n return parsed\n .map((item) => (typeof item?.markdown === 'string' ? item.markdown : ''))\n .join('');\n } catch {\n return '';\n }\n}\n\n/**\n * Concatenate the markdown out of a `Conversation` JSON-array stream — items\n * are `ConversationResponse`s whose `markdown` field is a delta (the placements'\n * `placementFetch` accumulates the same way). Tolerant of a partial/closing\n * array so the completed body parses in one shot.\n */\nfunction accumulateConversationMarkdown(streamText: string): string {\n let candidate = streamText.trim();\n if (!candidate) return '';\n if (candidate.endsWith(',')) candidate = candidate.slice(0, -1);\n if (!candidate.startsWith('[')) candidate = `[${candidate}`;\n if (!candidate.endsWith(']')) candidate += ']';\n try {\n const parsed = JSON.parse(candidate) as { markdown?: string }[];\n if (!Array.isArray(parsed)) return '';\n return parsed\n .map((item) => (typeof item?.markdown === 'string' ? item.markdown : ''))\n .join('');\n } catch {\n return '';\n }\n}\n\n/**\n * Parse chip labels from the prompt's JSON output (the `ChipsApiResponse`\n * shape), tolerating an optional ```json code fence.\n */\nfunction parseChipsFromJson(markdown: string): string[] {\n const unfenced = markdown\n .trim()\n .replace(/^```(?:json)?\\s*/i, '')\n .replace(/\\s*```$/, '')\n .trim();\n if (!unfenced) return [];\n try {\n const data: ChipsApiResponse = JSON.parse(unfenced);\n return data.chips?.map((chip) => chip.label).filter(Boolean) ?? [];\n } catch {\n return [];\n }\n}\n","/**\n * Helpers shared by every per-client embed `main.ts` for parsing host-page\n * script-tag attributes and gating widget initialization.\n */\n\nimport type { Web5EmbedBaseConfig, Web5EmbedConfig } from './types';\n\nexport function resolveTarget(\n target: string | HTMLElement | undefined,\n): HTMLElement {\n if (target instanceof HTMLElement) return target;\n if (typeof target === 'string') {\n const el = document.querySelector<HTMLElement>(target);\n if (!el) {\n throw new Error(`[Web5Embed] Target element not found: ${target}`);\n }\n return el;\n }\n\n // No target specified — create a container next to the script tag.\n const script = document.currentScript;\n const container = document.createElement('div');\n if (script?.parentElement) {\n script.parentElement.insertBefore(container, script);\n } else {\n document.body.appendChild(container);\n }\n return container;\n}\n\nexport function shouldInit(requireParam?: string): boolean {\n if (!requireParam) return true;\n const params = new URLSearchParams(window.location.search);\n return params.has(requireParam);\n}\n\nexport function shouldInitForPath(\n excludePaths: string | string[] | undefined,\n defaultExcludePaths: string[] = [],\n): boolean {\n const paths = Array.isArray(excludePaths)\n ? excludePaths\n : excludePaths != null\n ? [excludePaths]\n : defaultExcludePaths;\n return !paths.includes(window.location.pathname);\n}\n\n/**\n * Reads the standard `Web5EmbedBaseConfig` data-attributes from a script tag.\n * Per-client `main.ts` files extend this with their own widget-specific\n * attributes (visibility mode, copy props, etc.) by reading additional\n * attributes from the same script.\n */\nexport function readBaseConfigFromScript(\n script: HTMLOrSVGScriptElement | null,\n): Web5EmbedBaseConfig {\n if (!script) return {};\n\n const config: Web5EmbedBaseConfig = {};\n\n const target = script.getAttribute('data-target');\n if (target) config.target = target;\n\n const placeholder = script.getAttribute('data-placeholder');\n if (placeholder) config.placeholder = placeholder;\n\n const baseUrl = script.getAttribute('data-base-url');\n if (baseUrl) config.baseUrl = baseUrl;\n\n const requireParam = script.getAttribute('data-require-param');\n if (requireParam) config.requireParam = requireParam;\n\n const suggestionsRaw = script.getAttribute('data-suggestions');\n if (suggestionsRaw) {\n try {\n const parsed = JSON.parse(suggestionsRaw);\n if (Array.isArray(parsed) && parsed.length) {\n config.suggestions = parsed;\n }\n } catch {\n console.warn('[Web5Embed] Invalid data-suggestions JSON, ignoring.');\n }\n }\n\n return config;\n}\n\n/**\n * Reads the bottom-floating widget's extended config (visibility mode,\n * showAfterTarget, excludePaths) on top of the base config.\n */\nexport function readBottomConfigFromScript(\n script: HTMLOrSVGScriptElement | null,\n): Web5EmbedConfig {\n const config: Web5EmbedConfig = readBaseConfigFromScript(script);\n if (!script) return config;\n\n const visibilityMode = script.getAttribute('data-visibility-mode');\n if (visibilityMode === 'always' || visibilityMode === 'after-target') {\n config.visibilityMode = visibilityMode;\n }\n\n const showAfterTarget = script.getAttribute('data-show-after-target');\n if (showAfterTarget) config.showAfterTarget = showAfterTarget;\n\n const excludePathsRaw = script.getAttribute('data-exclude-paths');\n if (excludePathsRaw) {\n config.excludePaths = excludePathsRaw.includes(',')\n ? excludePathsRaw.split(',').map((p) => p.trim())\n : excludePathsRaw;\n }\n\n return config;\n}\n","/**\n * `registerWeb5Widget` — thin helper that per-client `main.ts` shims use to\n * register a widget against `window.__web5Loader__` (DL #089). Centralizes the\n * \"register-or-warn\" boilerplate and the `ctx.options → typed config` shape\n * coercion so each per-client entry file is a 5-line shim.\n *\n * `createVariantMount` — small dispatch helper for per-client bundles that\n * expose more than one rendering variant of the same widget id (e.g. circana's\n * default bottom-floating input + a homepage hero variant). The SDK does not\n * enumerate variant ids; each client owns its own.\n */\n\nimport type { LoaderContext, WidgetInit, WidgetInstance } from './loader-types';\n\nexport interface RegisterWidgetOptions<TConfig> {\n /**\n * The widget id under which to register. Must match a key in\n * `Configuration.widgetBundles` on the BE / mock config.\n */\n widgetId: string;\n /**\n * Cast / parse the loader's raw `ctx.options` into the client's typed\n * config. The loader passes `spec.options` through verbatim, so this is\n * usually a near-no-op cast.\n */\n parseOptions: (options: Record<string, unknown>) => TConfig;\n /**\n * Mount the widget. Return value supplies the lifecycle hooks the loader\n * uses on SPA route changes. May return synchronously or as a promise —\n * the `WidgetInit` wrapper awaits either form.\n */\n mount: (\n ctx: LoaderContext,\n config: TConfig,\n ) => WidgetInstance | Promise<WidgetInstance>;\n}\n\nexport function registerWeb5Widget<TConfig>(\n opts: RegisterWidgetOptions<TConfig>,\n): void {\n if (typeof window === 'undefined') return;\n const loader = window.__web5Loader__;\n if (!loader) {\n console.warn(\n `[Web5Embed] window.__web5Loader__ not present; widget \"${opts.widgetId}\" will not mount`,\n );\n return;\n }\n const init: WidgetInit = async (ctx) =>\n opts.mount(ctx, opts.parseOptions(ctx.options));\n loader.register(opts.widgetId, init);\n}\n\nexport type VariantMount<TConfig extends { variant?: string }> = (\n ctx: LoaderContext,\n config: TConfig,\n) => WidgetInstance | Promise<WidgetInstance>;\n\nexport interface VariantMountOptions<TConfig extends { variant?: string }> {\n /** Mount used when `config.variant` is undefined, `'default'`, or unknown. */\n default: VariantMount<TConfig>;\n /** Optional named variants, keyed by the string value of `config.variant`. */\n variants?: Record<string, VariantMount<TConfig>>;\n}\n\n/**\n * Build a mount function that dispatches on `config.variant`. Falls back to\n * `default` (with a console warning) when an unknown variant id is supplied —\n * the loader's route rule is the source of truth, and unknown variants\n * typically indicate a config/bundle version mismatch.\n */\nexport function createVariantMount<TConfig extends { variant?: string }>(\n opts: VariantMountOptions<TConfig>,\n): VariantMount<TConfig> {\n return (ctx, config) => {\n const id = config.variant;\n if (!id || id === 'default') {\n return opts.default(ctx, config);\n }\n const handler = opts.variants?.[id];\n if (handler) {\n return handler(ctx, config);\n }\n console.warn(\n `[Web5Embed] unknown variant \"${id}\" — falling back to default`,\n );\n return opts.default(ctx, config);\n };\n}\n","export type EmbedFeatureToggleStatus =\n | 'SUCCESSFUL'\n | 'MISSING_SEED'\n | 'DID_NOT_FIND_ELIGIBLE_FILTER'\n | 'ERROR';\n\nexport interface EmbedFeatureToggleResult {\n id: string;\n enabled: boolean;\n status: EmbedFeatureToggleStatus;\n}\n\nexport interface FeatureToggleReader {\n isEnabled: (id: string) => boolean;\n get: (id: string) => EmbedFeatureToggleResult | undefined;\n}\n\nexport function createFeatureToggleReader(\n featureToggles: Record<string, EmbedFeatureToggleResult> | undefined,\n): FeatureToggleReader {\n const get = (id: string): EmbedFeatureToggleResult | undefined =>\n featureToggles?.[id];\n\n return {\n isEnabled: (id) => {\n const result = get(id);\n return result?.status === 'SUCCESSFUL' && result.enabled === true;\n },\n get,\n };\n}\n","/**\n * Auto-resizing textarea height with a `multiline` class toggle on a wrapper.\n *\n * Pattern: textarea grows from `lineHeight` up to `maxHeightPx`. When growth\n * happens the wrapper element receives `multilineClass` so callers can\n * adjust padding/border-radius for the multi-line state.\n */\n\nexport interface InputHeightSyncOptions {\n input: HTMLTextAreaElement;\n /** Element that should toggle the `multiline` class. */\n wrapper: Element | null;\n /** Class added/removed on `wrapper` when input grows past one line. */\n multilineClass: string;\n /** Max height in px the input is allowed to grow to. */\n maxHeightPx: number;\n /** Multiplier applied to lineHeight to decide when to flip to multiline. Default 1. */\n multilineThresholdMultiplier?: number;\n /** Fallback lineHeight if computed style is unparsable. */\n fallbackLineHeightPx?: number;\n}\n\nexport interface InputHeightSync {\n sync(): void;\n}\n\nexport function createInputHeightSync(\n options: InputHeightSyncOptions,\n): InputHeightSync {\n const {\n input,\n wrapper,\n multilineClass,\n maxHeightPx,\n multilineThresholdMultiplier = 1,\n fallbackLineHeightPx = 32,\n } = options;\n\n return {\n sync() {\n if (!wrapper) return;\n\n const computed = window.getComputedStyle(input);\n const lineHeight =\n Number.parseFloat(computed.lineHeight) || fallbackLineHeightPx;\n\n input.style.height = 'auto';\n const nextHeight =\n input.value.trim().length === 0\n ? lineHeight\n : Math.min(Math.max(input.scrollHeight, lineHeight), maxHeightPx);\n input.style.height = `${nextHeight}px`;\n wrapper.classList.toggle(\n multilineClass,\n nextHeight > lineHeight * multilineThresholdMultiplier,\n );\n },\n };\n}\n","/**\n * Collapse controller for a bottom-fixed prompt widget.\n *\n * Tracks the scroll position relative to the bottom of the document and\n * collapses the widget when within `thresholdPx` of the page bottom — but\n * only if the widget itself doesn't currently hold focus and hasn't already\n * been manually expanded inside the bottom zone.\n *\n * Caller is responsible for binding scroll/resize listeners; this controller\n * just exposes pure state transitions.\n */\n\nexport interface CollapseControllerOptions {\n /** Distance in px from the bottom of the document at which to collapse. */\n thresholdPx?: number;\n}\n\nexport interface CollapseControllerState {\n isCollapsed(): boolean;\n isCoordinatorVisible(): boolean;\n hasFocusInside(): boolean;\n}\n\nexport interface CollapseController {\n /** Should the widget collapse right now, given current state? */\n evaluate(state: CollapseControllerState): boolean;\n /** Mark that the user has expanded the widget while in the bottom zone. */\n markManuallyExpanded(): void;\n /** Reset manual-expand memory (e.g. when leaving the bottom zone). */\n resetManualExpand(): void;\n}\n\nconst DEFAULT_THRESHOLD_PX = 400;\n\nexport function createCollapseController(\n options: CollapseControllerOptions = {},\n): CollapseController {\n const threshold = options.thresholdPx ?? DEFAULT_THRESHOLD_PX;\n let manuallyExpanded = false;\n\n function isBottomZoneVisible(): boolean {\n const doc = document.documentElement;\n const scrollTop = window.scrollY || doc.scrollTop || 0;\n const viewportBottom = scrollTop + window.innerHeight;\n const distanceToBottom = doc.scrollHeight - viewportBottom;\n const hasEnoughContent =\n doc.scrollHeight > window.innerHeight + threshold;\n return (\n hasEnoughContent && scrollTop > 0 && distanceToBottom <= threshold\n );\n }\n\n return {\n evaluate(state) {\n if (!state.isCoordinatorVisible()) {\n manuallyExpanded = false;\n return false;\n }\n\n if (!isBottomZoneVisible()) {\n manuallyExpanded = false;\n return false;\n }\n\n if (state.hasFocusInside()) {\n // Preserve current state; caller should not re-collapse while focused.\n return state.isCollapsed();\n }\n\n if (manuallyExpanded) {\n return state.isCollapsed();\n }\n\n return true;\n },\n markManuallyExpanded() {\n manuallyExpanded = true;\n },\n resetManualExpand() {\n manuallyExpanded = false;\n },\n };\n}\n","/**\n * Submit-flow helper: navigates the host page to `<baseUrl>?initialQuestion=<value>`.\n *\n * Pure side-effect function; pulled out so per-client widgets share the same\n * URL-build + window.open contract.\n */\n\nimport { buildUrl } from '../shared';\n\nexport interface SubmitFlowOptions {\n baseUrl: string;\n /** Window target for the navigation. Defaults to `_self`. */\n windowTarget?: string;\n}\n\nexport interface SubmitFlow {\n open(value: string): boolean;\n}\n\nexport function createSubmitFlow(options: SubmitFlowOptions): SubmitFlow {\n if (!options.baseUrl) {\n throw new Error(\n '[Web5Embed] createSubmitFlow requires a non-empty baseUrl.',\n );\n }\n const prefix = buildUrl(options.baseUrl);\n const target = options.windowTarget ?? '_self';\n\n return {\n open(value) {\n window.open(prefix + encodeURIComponent(value), target);\n return true;\n },\n };\n}\n"],"names":["GLOBAL_KEY","EDITABLE_SELECTOR","MODAL_SELECTOR","isModalVisible","modal","cs","pathContainsEditable","event","path","node","getFocusShortcutCoordinator","w","targets","listenerAttached","handleKeyDown","e","isSlash","isCmdK","modals","target","t","previousActive","ensureListener","dropListenerIfEmpty","coordinator","index","__resetFocusShortcutCoordinatorForTests","DEFAULT_STATIC_VERSIONS_URL","CACHE_KEY","fetchStaticVersions","url","res","findVersion","versions","artifactId","entry","v","getCachedVersions","cacheVersions","DEFAULTS","resolveOptions","options","getPromptInputTokensUrl","version","tokensCdnBase","tokensFileName","resolvePromptInputTokensVersion","opts","cached","err","resolvePromptInputTokensUrl","attachPromptInputTokensStylesheet","shadowRoot","link","sendIconSvg","buildUrl","base","escapeHtml","value","DEFAULT_CHIPS_ENDPOINT","DEFAULT_CHIPS_TIMEOUT_MS","DEFAULT_TURN_ENDPOINT","DEFAULT_CONVERSATION_ENDPOINT","PDP_CHIPS_PLACEMENT","fetchChipSuggestions","pageUrl","fetchContextualChips","endpoint","timeoutMs","fetchImpl","resolveFetch","controller","timeoutId","response","chip","conversationApi","handle","query","body","text","markdown","accumulateConversationMarkdown","accumulateTurnMarkdown","parseChipsFromJson","override","streamText","candidate","parsed","completed","item","unfenced","resolveTarget","el","script","container","shouldInit","requireParam","shouldInitForPath","excludePaths","defaultExcludePaths","readBaseConfigFromScript","config","placeholder","baseUrl","suggestionsRaw","readBottomConfigFromScript","visibilityMode","showAfterTarget","excludePathsRaw","p","registerWeb5Widget","loader","init","ctx","createVariantMount","id","handler","createFeatureToggleReader","featureToggles","get","result","createInputHeightSync","input","wrapper","multilineClass","maxHeightPx","multilineThresholdMultiplier","fallbackLineHeightPx","computed","lineHeight","nextHeight","DEFAULT_THRESHOLD_PX","createCollapseController","threshold","manuallyExpanded","isBottomZoneVisible","doc","scrollTop","viewportBottom","distanceToBottom","state","createSubmitFlow","prefix"],"mappings":"AAQA,MAAMA,IAAa,8BACbC,IACJ,mEACIC,IAAiB;AAoBvB,SAASC,EAAeC,GAAyB;AAC/C,MAAIA,EAAM,aAAa,OAAO,EAAG,QAAO;AACxC,MAAI,OAAQA,EAAsB,mBAAoB;AACpD,WAAQA,EAAsB,gBAAgB;AAAA,MAC5C,cAAc;AAAA,MACd,oBAAoB;AAAA,IAAA,CACrB;AAEH,QAAMC,IAAK,iBAAiBD,CAAoB;AAChD,SACE,EAAAC,EAAG,eAAe,YAClBA,EAAG,YAAY,UACf,WAAWA,EAAG,OAAO,MAAM;AAK/B;AAEA,SAASC,EAAqBC,GAA+B;AAC3D,QAAMC,IAAOD,EAAM,aAAA;AACnB,aAAWE,KAAQD;AACjB,QACEC,aAAgB,WAChB,OAAOA,EAAK,WAAY,cACxBA,EAAK,QAAQR,CAAiB;AAE9B,aAAO;AAGX,SAAO;AACT;AAEO,SAASS,IAAwD;AACtE,QAAMC,IAAI;AACV,MAAIA,EAAEX,CAAU,EAAG,QAAOW,EAAEX,CAAU;AAEtC,QAAMY,IAAiC,CAAA;AACvC,MAAIC,IAAmB;AAEvB,QAAMC,IAAgB,CAACC,MAAqB;AAC1C,QAAIA,EAAE,oBAAoBA,EAAE,YAAa;AAEzC,UAAMC,IACJD,EAAE,QAAQ,OACV,CAACA,EAAE,WACH,CAACA,EAAE,WACH,CAACA,EAAE,UACH,CAACA,EAAE,UACCE,KACHF,EAAE,WAAWA,EAAE,YAChB,CAACA,EAAE,UACHA,EAAE,IAAI,YAAA,MAAkB;AAI1B,QAFI,CAACC,KAAW,CAACC,KAEbD,KAAWV,EAAqBS,CAAC,EAAG;AAExC,UAAMG,IAAS,SAAS,iBAAiBhB,CAAc;AACvD,eAAWE,KAASc;AAClB,UAAIf,EAAeC,CAAK,EAAG;AAG7B,UAAMe,IAASP,EAAQ,KAAK,CAACQ,MAAMA,EAAE,WAAW;AAChD,QAAI,CAACD,EAAQ;AAEb,IAAAJ,EAAE,eAAA;AACF,UAAMM,IACJ,SAAS,yBAAyB,cAC9B,SAAS,gBACT;AACN,IAAAF,EAAO,MAAME,CAAc;AAAA,EAC7B,GAEMC,IAAiB,MAAM;AAC3B,IAAIT,MACJ,OAAO,iBAAiB,WAAWC,CAAa,GAChDD,IAAmB;AAAA,EACrB,GAEMU,IAAsB,MAAM;AAChC,IAAI,CAACV,KAAoBD,EAAQ,SAAS,MAC1C,OAAO,oBAAoB,WAAWE,CAAa,GACnDD,IAAmB;AAAA,EACrB,GAEMW,IAAmC;AAAA,IACvC,SAASL,GAAQ;AACf,MAAKP,EAAQ,SAASO,CAAM,KAAGP,EAAQ,KAAKO,CAAM,GAClDG,EAAA;AAAA,IACF;AAAA,IACA,WAAWH,GAAQ;AACjB,YAAMM,IAAQb,EAAQ,QAAQO,CAAM;AACpC,MAAIM,KAAS,KAAGb,EAAQ,OAAOa,GAAO,CAAC,GACvCF,EAAA;AAAA,IACF;AAAA,IACA,aAAa;AACX,MAAAX,EAAQ,SAAS,GACbC,MACF,OAAO,oBAAoB,WAAWC,CAAa,GACnDD,IAAmB;AAAA,IAEvB;AAAA,EAAA;AAGF,SAAAF,EAAEX,CAAU,IAAIwB,GACTA;AACT;AAEO,SAASE,IAAgD;AAC9D,QAAMf,IAAI;AACV,EAAAA,EAAEX,CAAU,GAAG,WAAA,GACf,OAAOW,EAAEX,CAAU;AACrB;ACvIO,MAAM2B,IACX,wDAEIC,IAAY;AAMlB,eAAsBC,EACpBC,IAAcH,GACc;AAC5B,QAAMI,IAAM,MAAM,MAAMD,GAAK;AAAA,IAC3B,QAAQ;AAAA,IACR,SAAS,EAAE,QAAQ,mBAAA;AAAA,IACnB,MAAM;AAAA,IACN,aAAa;AAAA,EAAA,CACd;AACD,MAAI,CAACC,EAAI;AACP,UAAM,IAAI,MAAM,wCAAwCA,EAAI,MAAM,EAAE;AAEtE,SAAOA,EAAI,KAAA;AACb;AAEO,SAASC,EACdC,GACAC,GACQ;AACR,QAAMC,IAAQF,EAAS,KAAK,CAACG,MAAMA,EAAE,eAAeF,CAAU;AAC9D,MAAI,CAACC;AACH,UAAM,IAAI;AAAA,MACR,wBAAwBD,CAAU;AAAA,IAAA;AAGtC,SAAOC,EAAM;AACf;AAEO,SAASE,IAAmD;AACjE,SAAQ,OAAkCT,CAAS;AACrD;AAEO,SAASU,EAAcL,GAAmC;AAC9D,SAAkCL,CAAS,IAAIK;AAClD;ACvBA,MAAMM,IAA+C;AAAA,EACnD,mBAAmB;AAAA,EACnB,eAAe;AAAA,EACf,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,iBAAiB;AACnB;AAEA,SAASC,EACPC,GACoC;AACpC,SAAO,EAAE,GAAGF,GAAU,GAAIE,KAAW,CAAA,EAAC;AACxC;AAEO,SAASC,EACdC,GACAF,GACQ;AACR,QAAM,EAAE,eAAAG,GAAe,gBAAAC,MAAmBL,EAAeC,CAAO;AAChE,SAAO,GAAGG,CAAa,IAAID,CAAO,IAAIE,CAAc;AACtD;AAEA,eAAsBC,EACpBL,GACiB;AACjB,QAAMM,IAAOP,EAAeC,CAAO;AACnC,MAAI;AACF,UAAMO,IAASX,EAAA;AACf,QAAIW,EAAQ,QAAOhB,EAAYgB,GAAQD,EAAK,gBAAgB;AAE5D,UAAMd,IAAW,MAAMJ,EAAoBkB,EAAK,iBAAiB;AACjE,WAAAT,EAAcL,CAAQ,GACfD,EAAYC,GAAUc,EAAK,gBAAgB;AAAA,EACpD,SAASE,GAAK;AACZ,mBAAQ;AAAA,MACN;AAAA,MACAA;AAAA,IAAA,GAEKF,EAAK;AAAA,EACd;AACF;AAEA,eAAsBG,EACpBT,GACiB;AACjB,QAAME,IAAU,MAAMG,EAAgCL,CAAO;AAC7D,SAAOC,EAAwBC,GAASF,CAAO;AACjD;AAEA,eAAsBU,EACpBC,GACAX,GACe;AAIf,MAHiBW,EAAW;AAAA,IAC1B;AAAA,EAAA,EAEY;AAEd,QAAMC,IAAO,SAAS,cAAc,MAAM;AAC1C,EAAAA,EAAK,MAAM,cACXA,EAAK,QAAQ,wBAAwB,QACrCA,EAAK,OAAO,MAAMH,EAA4BT,CAAO,GACrDW,EAAW,QAAQC,CAAI;AACzB;AChFO,MAAMC,IAAc;AAAA;AAAA;AAAA;AAAA;AAMpB,SAASC,EAASC,GAAsB;AAC7C,SAAO,GAAGA,CAAI;AAChB;AAEO,SAASC,EAAWC,GAAuB;AAChD,SAAOA,EACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,OAAO;AAC1B;AA4DA,MAAMC,IACJ,iDACIC,IAA2B,KAS3BC,IACJ,sDAOIC,IACJ,8DAGIC,IAAsB;AAE5B,eAAsBC,EACpBC,GACAxB,IAAuC,IACpB;AAEnB,MAAIA,EAAQ,cAAc;AACxB,WAAOyB,EAAqB;AAAA,MAC1B,GAAGzB,EAAQ;AAAA,MACX,WAAWA,EAAQ;AAAA,MACnB,WAAWA,EAAQ;AAAA,IAAA,CACpB;AAGH,QAAM0B,IAAW1B,EAAQ,YAAYkB,GAC/BS,IAAY3B,EAAQ,aAAamB,GACjCS,IAAYC,EAAa7B,EAAQ,SAAS,GAC1C8B,IAAa,IAAI,gBAAA,GACjBC,IAAY,WAAW,MAAMD,EAAW,MAAA,GAASH,CAAS;AAEhE,MAAI;AACF,UAAMK,IAAW,MAAMJ;AAAA,MACrB,GAAGF,CAAQ,QAAQ,mBAAmBF,CAAO,CAAC;AAAA,MAC9C,EAAE,QAAQM,EAAW,OAAA;AAAA,IAAO;AAG9B,QAAI,CAACE,EAAS;AACZ,YAAM,IAAI,MAAM,QAAQA,EAAS,MAAM,EAAE;AAI3C,YAD+B,MAAMA,EAAS,KAAA,GAClC,OAAO,IAAI,CAACC,MAASA,EAAK,KAAK,EAAE,OAAO,OAAO,KAAK,CAAA;AAAA,EAClE,UAAA;AACE,iBAAaF,CAAS;AAAA,EACxB;AACF;AAYA,eAAsBN,EACpBzB,GACmB;AAEnB,QAAMkC,KADMlC,EAAQ,OAAO,YACK,gBAC1B0B,IACJ1B,EAAQ,aACPkC,IAAkBb,IAAgCD,IAC/CO,IAAY3B,EAAQ,aAAamB,GACjCS,IAAYC,EAAa7B,EAAQ,SAAS,GAC1C8B,IAAa,IAAI,gBAAA,GACjBC,IAAY,WAAW,MAAMD,EAAW,MAAA,GAASH,CAAS;AAEhE,MAAI;AACF,UAAMQ,IAASnC,EAAQ,UAAU,aAM3BoC,IAAQ,qBALKpC,EAAQ,cAAc,SAKI,iBAAiBmC,CAAM,mCAE9DE,IAAOH,IACT;AAAA;AAAA,MAEE,GAAIlC,EAAQ,SAAS,EAAE,QAAQA,EAAQ,OAAA,IAAW,CAAA;AAAA,MAClD,OAAAoC;AAAA,MACA,gBAAgB;AAAA,QACd,GAAIpC,EAAQ,OAAO,EAAE,MAAMA,EAAQ,KAAA,IAAS,CAAA;AAAA;AAAA,QAE5C,GAAIA,EAAQ,YACR,EAAE,WAAW,GAAGA,EAAQ,SAAS,IAAIsB,CAAmB,GAAA,IACxD,CAAA;AAAA,QACJ,WAAWA;AAAA,QACX,GAAItB,EAAQ,SACR,EAAE,sBAAsBA,EAAQ,OAAA,IAChC,CAAA;AAAA,MAAC;AAAA,IACP,IAEF;AAAA;AAAA,MAEE,MAAMoC;AAAA,MACN,GAAIpC,EAAQ,YAAY,EAAE,WAAWA,EAAQ,UAAA,IAAc,CAAA;AAAA,MAC3D,GAAIA,EAAQ,SAAS,EAAE,iBAAiBA,EAAQ,OAAA,IAAW,CAAA;AAAA,MAC3D,WAAWsB;AAAA,IAAA,GAGXU,IAAW,MAAMJ,EAAUF,GAAU;AAAA,MACzC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,QAAQ;AAAA,MAAA;AAAA,MAEV,MAAM,KAAK,UAAUW,CAAI;AAAA,MACzB,QAAQP,EAAW;AAAA,IAAA,CACpB;AAED,QAAI,CAACE,EAAS;AACZ,YAAM,IAAI,MAAM,QAAQA,EAAS,MAAM,EAAE;AAM3C,UAAMM,IAAO,MAAMN,EAAS,KAAA,GACtBO,IAAWL,IACbM,EAA+BF,CAAI,IACnCG,EAAuBH,CAAI;AAC/B,WAAOI,EAAmBH,CAAQ;AAAA,EACpC,UAAA;AACE,iBAAaR,CAAS;AAAA,EACxB;AACF;AAEA,SAASF,EAAac,GAAuC;AAC3D,SAAIA,MACG,OAAO,SAAW,MAAc,MAAM,KAAK,MAAM,IAAI;AAC9D;AASA,SAASF,EAAuBG,GAA4B;AAC1D,MAAIC,IAAYD,EAAW,KAAA;AAC3B,MAAI,CAACC,EAAW,QAAO;AACvB,EAAIA,EAAU,SAAS,GAAG,UAAeA,EAAU,MAAM,GAAG,EAAE,IACzDA,EAAU,WAAW,GAAG,MAAGA,IAAY,IAAIA,CAAS,KACpDA,EAAU,SAAS,GAAG,MAAGA,KAAa;AAC3C,MAAI;AACF,UAAMC,IAAS,KAAK,MAAMD,CAAS;AAInC,QAAI,CAAC,MAAM,QAAQC,CAAM,EAAG,QAAO;AACnC,UAAMC,IAAYD,EAAO;AAAA,MACvB,CAACE,MAAS,OAAOA,GAAM,WAAW,YAAa;AAAA,IAAA,GAC9C,WAAW;AACd,WAAID,KACGD,EACJ,IAAI,CAACE,MAAU,OAAOA,GAAM,YAAa,WAAWA,EAAK,WAAW,EAAG,EACvE,KAAK,EAAE;AAAA,EACZ,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,SAASR,EAA+BI,GAA4B;AAClE,MAAIC,IAAYD,EAAW,KAAA;AAC3B,MAAI,CAACC,EAAW,QAAO;AACvB,EAAIA,EAAU,SAAS,GAAG,UAAeA,EAAU,MAAM,GAAG,EAAE,IACzDA,EAAU,WAAW,GAAG,MAAGA,IAAY,IAAIA,CAAS,KACpDA,EAAU,SAAS,GAAG,MAAGA,KAAa;AAC3C,MAAI;AACF,UAAMC,IAAS,KAAK,MAAMD,CAAS;AACnC,WAAK,MAAM,QAAQC,CAAM,IAClBA,EACJ,IAAI,CAACE,MAAU,OAAOA,GAAM,YAAa,WAAWA,EAAK,WAAW,EAAG,EACvE,KAAK,EAAE,IAHyB;AAAA,EAIrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,SAASN,EAAmBH,GAA4B;AACtD,QAAMU,IAAWV,EACd,KAAA,EACA,QAAQ,qBAAqB,EAAE,EAC/B,QAAQ,WAAW,EAAE,EACrB,KAAA;AACH,MAAI,CAACU,EAAU,QAAO,CAAA;AACtB,MAAI;AAEF,WAD+B,KAAK,MAAMA,CAAQ,EACtC,OAAO,IAAI,CAAChB,MAASA,EAAK,KAAK,EAAE,OAAO,OAAO,KAAK,CAAA;AAAA,EAClE,QAAQ;AACN,WAAO,CAAA;AAAA,EACT;AACF;AC/SO,SAASiB,EACdxE,GACa;AACb,MAAIA,aAAkB,YAAa,QAAOA;AAC1C,MAAI,OAAOA,KAAW,UAAU;AAC9B,UAAMyE,IAAK,SAAS,cAA2BzE,CAAM;AACrD,QAAI,CAACyE;AACH,YAAM,IAAI,MAAM,yCAAyCzE,CAAM,EAAE;AAEnE,WAAOyE;AAAA,EACT;AAGA,QAAMC,IAAS,SAAS,eAClBC,IAAY,SAAS,cAAc,KAAK;AAC9C,SAAID,GAAQ,gBACVA,EAAO,cAAc,aAAaC,GAAWD,CAAM,IAEnD,SAAS,KAAK,YAAYC,CAAS,GAE9BA;AACT;AAEO,SAASC,EAAWC,GAAgC;AACzD,SAAKA,IACU,IAAI,gBAAgB,OAAO,SAAS,MAAM,EAC3C,IAAIA,CAAY,IAFJ;AAG5B;AAEO,SAASC,EACdC,GACAC,IAAgC,IACvB;AAMT,SAAO,EALO,MAAM,QAAQD,CAAY,IACpCA,IACAA,KAAgB,OACd,CAACA,CAAY,IACbC,GACQ,SAAS,OAAO,SAAS,QAAQ;AACjD;AAQO,SAASC,EACdP,GACqB;AACrB,MAAI,CAACA,EAAQ,QAAO,CAAA;AAEpB,QAAMQ,IAA8B,CAAA,GAE9BlF,IAAS0E,EAAO,aAAa,aAAa;AAChD,EAAI1E,QAAe,SAASA;AAE5B,QAAMmF,IAAcT,EAAO,aAAa,kBAAkB;AAC1D,EAAIS,QAAoB,cAAcA;AAEtC,QAAMC,IAAUV,EAAO,aAAa,eAAe;AACnD,EAAIU,QAAgB,UAAUA;AAE9B,QAAMP,IAAeH,EAAO,aAAa,oBAAoB;AAC7D,EAAIG,QAAqB,eAAeA;AAExC,QAAMQ,IAAiBX,EAAO,aAAa,kBAAkB;AAC7D,MAAIW;AACF,QAAI;AACF,YAAMjB,IAAS,KAAK,MAAMiB,CAAc;AACxC,MAAI,MAAM,QAAQjB,CAAM,KAAKA,EAAO,WAClCc,EAAO,cAAcd;AAAA,IAEzB,QAAQ;AACN,cAAQ,KAAK,sDAAsD;AAAA,IACrE;AAGF,SAAOc;AACT;AAMO,SAASI,EACdZ,GACiB;AACjB,QAAMQ,IAA0BD,EAAyBP,CAAM;AAC/D,MAAI,CAACA,EAAQ,QAAOQ;AAEpB,QAAMK,IAAiBb,EAAO,aAAa,sBAAsB;AACjE,GAAIa,MAAmB,YAAYA,MAAmB,oBACpDL,EAAO,iBAAiBK;AAG1B,QAAMC,IAAkBd,EAAO,aAAa,wBAAwB;AACpE,EAAIc,QAAwB,kBAAkBA;AAE9C,QAAMC,IAAkBf,EAAO,aAAa,oBAAoB;AAChE,SAAIe,MACFP,EAAO,eAAeO,EAAgB,SAAS,GAAG,IAC9CA,EAAgB,MAAM,GAAG,EAAE,IAAI,CAACC,MAAMA,EAAE,KAAA,CAAM,IAC9CD,IAGCP;AACT;AC7EO,SAASS,GACd/D,GACM;AACN,MAAI,OAAO,SAAW,IAAa;AACnC,QAAMgE,IAAS,OAAO;AACtB,MAAI,CAACA,GAAQ;AACX,YAAQ;AAAA,MACN,0DAA0DhE,EAAK,QAAQ;AAAA,IAAA;AAEzE;AAAA,EACF;AACA,QAAMiE,IAAmB,OAAOC,MAC9BlE,EAAK,MAAMkE,GAAKlE,EAAK,aAAakE,EAAI,OAAO,CAAC;AAChD,EAAAF,EAAO,SAAShE,EAAK,UAAUiE,CAAI;AACrC;AAoBO,SAASE,GACdnE,GACuB;AACvB,SAAO,CAACkE,GAAKZ,MAAW;AACtB,UAAMc,IAAKd,EAAO;AAClB,QAAI,CAACc,KAAMA,MAAO;AAChB,aAAOpE,EAAK,QAAQkE,GAAKZ,CAAM;AAEjC,UAAMe,IAAUrE,EAAK,WAAWoE,CAAE;AAClC,WAAIC,IACKA,EAAQH,GAAKZ,CAAM,KAE5B,QAAQ;AAAA,MACN,gCAAgCc,CAAE;AAAA,IAAA,GAE7BpE,EAAK,QAAQkE,GAAKZ,CAAM;AAAA,EACjC;AACF;ACvEO,SAASgB,GACdC,GACqB;AACrB,QAAMC,IAAM,CAACJ,MACXG,IAAiBH,CAAE;AAErB,SAAO;AAAA,IACL,WAAW,CAACA,MAAO;AACjB,YAAMK,IAASD,EAAIJ,CAAE;AACrB,aAAOK,GAAQ,WAAW,gBAAgBA,EAAO,YAAY;AAAA,IAC/D;AAAA,IACA,KAAAD;AAAA,EAAA;AAEJ;ACJO,SAASE,GACdhF,GACiB;AACjB,QAAM;AAAA,IACJ,OAAAiF;AAAA,IACA,SAAAC;AAAA,IACA,gBAAAC;AAAA,IACA,aAAAC;AAAA,IACA,8BAAAC,IAA+B;AAAA,IAC/B,sBAAAC,IAAuB;AAAA,EAAA,IACrBtF;AAEJ,SAAO;AAAA,IACL,OAAO;AACL,UAAI,CAACkF,EAAS;AAEd,YAAMK,IAAW,OAAO,iBAAiBN,CAAK,GACxCO,IACJ,OAAO,WAAWD,EAAS,UAAU,KAAKD;AAE5C,MAAAL,EAAM,MAAM,SAAS;AACrB,YAAMQ,IACJR,EAAM,MAAM,KAAA,EAAO,WAAW,IAC1BO,IACA,KAAK,IAAI,KAAK,IAAIP,EAAM,cAAcO,CAAU,GAAGJ,CAAW;AACpE,MAAAH,EAAM,MAAM,SAAS,GAAGQ,CAAU,MAClCP,EAAQ,UAAU;AAAA,QAChBC;AAAA,QACAM,IAAaD,IAAaH;AAAA,MAAA;AAAA,IAE9B;AAAA,EAAA;AAEJ;AC1BA,MAAMK,IAAuB;AAEtB,SAASC,GACd3F,IAAqC,IACjB;AACpB,QAAM4F,IAAY5F,EAAQ,eAAe0F;AACzC,MAAIG,IAAmB;AAEvB,WAASC,IAA+B;AACtC,UAAMC,IAAM,SAAS,iBACfC,IAAY,OAAO,WAAWD,EAAI,aAAa,GAC/CE,IAAiBD,IAAY,OAAO,aACpCE,IAAmBH,EAAI,eAAeE;AAG5C,WADEF,EAAI,eAAe,OAAO,cAAcH,KAEpBI,IAAY,KAAKE,KAAoBN;AAAA,EAE7D;AAEA,SAAO;AAAA,IACL,SAASO,GAAO;AAMd,aALI,CAACA,EAAM,0BAKP,CAACL,OACHD,IAAmB,IACZ,MAGLM,EAAM,oBAKNN,IACKM,EAAM,YAAA,IAGR;AAAA,IACT;AAAA,IACA,uBAAuB;AACrB,MAAAN,IAAmB;AAAA,IACrB;AAAA,IACA,oBAAoB;AAClB,MAAAA,IAAmB;AAAA,IACrB;AAAA,EAAA;AAEJ;AC/DO,SAASO,GAAiBpG,GAAwC;AACvE,MAAI,CAACA,EAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAGJ,QAAMqG,IAASvF,EAASd,EAAQ,OAAO,GACjCtB,IAASsB,EAAQ,gBAAgB;AAEvC,SAAO;AAAA,IACL,KAAKiB,GAAO;AACV,oBAAO,KAAKoF,IAAS,mBAAmBpF,CAAK,GAAGvC,CAAM,GAC/C;AAAA,IACT;AAAA,EAAA;AAEJ;"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `registerWeb5Widget` — thin helper that per-client `main.ts` shims use to
|
|
3
|
+
* register a widget against `window.__web5Loader__` (DL #089). Centralizes the
|
|
4
|
+
* "register-or-warn" boilerplate and the `ctx.options → typed config` shape
|
|
5
|
+
* coercion so each per-client entry file is a 5-line shim.
|
|
6
|
+
*
|
|
7
|
+
* `createVariantMount` — small dispatch helper for per-client bundles that
|
|
8
|
+
* expose more than one rendering variant of the same widget id (e.g. circana's
|
|
9
|
+
* default bottom-floating input + a homepage hero variant). The SDK does not
|
|
10
|
+
* enumerate variant ids; each client owns its own.
|
|
11
|
+
*/
|
|
12
|
+
import type { LoaderContext, WidgetInstance } from './loader-types';
|
|
13
|
+
export interface RegisterWidgetOptions<TConfig> {
|
|
14
|
+
/**
|
|
15
|
+
* The widget id under which to register. Must match a key in
|
|
16
|
+
* `Configuration.widgetBundles` on the BE / mock config.
|
|
17
|
+
*/
|
|
18
|
+
widgetId: string;
|
|
19
|
+
/**
|
|
20
|
+
* Cast / parse the loader's raw `ctx.options` into the client's typed
|
|
21
|
+
* config. The loader passes `spec.options` through verbatim, so this is
|
|
22
|
+
* usually a near-no-op cast.
|
|
23
|
+
*/
|
|
24
|
+
parseOptions: (options: Record<string, unknown>) => TConfig;
|
|
25
|
+
/**
|
|
26
|
+
* Mount the widget. Return value supplies the lifecycle hooks the loader
|
|
27
|
+
* uses on SPA route changes. May return synchronously or as a promise —
|
|
28
|
+
* the `WidgetInit` wrapper awaits either form.
|
|
29
|
+
*/
|
|
30
|
+
mount: (ctx: LoaderContext, config: TConfig) => WidgetInstance | Promise<WidgetInstance>;
|
|
31
|
+
}
|
|
32
|
+
export declare function registerWeb5Widget<TConfig>(opts: RegisterWidgetOptions<TConfig>): void;
|
|
33
|
+
export type VariantMount<TConfig extends {
|
|
34
|
+
variant?: string;
|
|
35
|
+
}> = (ctx: LoaderContext, config: TConfig) => WidgetInstance | Promise<WidgetInstance>;
|
|
36
|
+
export interface VariantMountOptions<TConfig extends {
|
|
37
|
+
variant?: string;
|
|
38
|
+
}> {
|
|
39
|
+
/** Mount used when `config.variant` is undefined, `'default'`, or unknown. */
|
|
40
|
+
default: VariantMount<TConfig>;
|
|
41
|
+
/** Optional named variants, keyed by the string value of `config.variant`. */
|
|
42
|
+
variants?: Record<string, VariantMount<TConfig>>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build a mount function that dispatches on `config.variant`. Falls back to
|
|
46
|
+
* `default` (with a console warning) when an unknown variant id is supplied —
|
|
47
|
+
* the loader's route rule is the source of truth, and unknown variants
|
|
48
|
+
* typically indicate a config/bundle version mismatch.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createVariantMount<TConfig extends {
|
|
51
|
+
variant?: string;
|
|
52
|
+
}>(opts: VariantMountOptions<TConfig>): VariantMount<TConfig>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type contract for the unified embed-loader (DL #089).
|
|
3
|
+
*
|
|
4
|
+
* Mirrored here (instead of imported from `@wix/embed-loader`) because
|
|
5
|
+
* `@wix/web5-embed` is destined for public npm and must keep zero internal
|
|
6
|
+
* Wix workspace deps. The mirror is intentionally a strict subset of the
|
|
7
|
+
* loader's public surface and tracks `packages/embed-loader/src/types.ts`.
|
|
8
|
+
*/
|
|
9
|
+
import type { EmbedFeatureToggleResult } from './featureToggles';
|
|
10
|
+
export interface EmbedIdentity {
|
|
11
|
+
visitorSeed: string;
|
|
12
|
+
tenantSeed?: string;
|
|
13
|
+
source: 'generated' | 'host-provided';
|
|
14
|
+
storageKey: string;
|
|
15
|
+
}
|
|
16
|
+
export interface LoaderContext {
|
|
17
|
+
clientId: string;
|
|
18
|
+
origin: string;
|
|
19
|
+
/**
|
|
20
|
+
* The full Configuration the loader is operating against. Widgets typically
|
|
21
|
+
* read their slice via `ctx.options`; `config` is provided for advanced
|
|
22
|
+
* cases (e.g. reading `bundleUrl` for an SDK fetch).
|
|
23
|
+
*/
|
|
24
|
+
config: {
|
|
25
|
+
id: string;
|
|
26
|
+
allowedDomains: string[];
|
|
27
|
+
bundleUrl: string;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
};
|
|
30
|
+
/** Per-widget options as declared in the matching page rule. */
|
|
31
|
+
options: Record<string, unknown>;
|
|
32
|
+
/** Inject a stylesheet into <head>. Idempotent per href. */
|
|
33
|
+
cssLoader: (href: string) => Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Element the loader matched via `hasSelector` for this mount. Widgets
|
|
36
|
+
* that target a slot should prefer this over their own `querySelector`
|
|
37
|
+
* — the loader's persistent watcher re-mounts when the element is
|
|
38
|
+
* replaced, and `target` reflects the current live node each time.
|
|
39
|
+
*/
|
|
40
|
+
target?: HTMLElement;
|
|
41
|
+
embedIdentity?: EmbedIdentity & {
|
|
42
|
+
tenantSeed: string;
|
|
43
|
+
};
|
|
44
|
+
featureToggles?: Record<string, EmbedFeatureToggleResult>;
|
|
45
|
+
}
|
|
46
|
+
export interface WidgetInstance {
|
|
47
|
+
destroy?: () => void;
|
|
48
|
+
onRouteChange?: (prevUrl: string, nextUrl: string) => void;
|
|
49
|
+
}
|
|
50
|
+
export type WidgetInit = (ctx: LoaderContext) => Promise<WidgetInstance>;
|
|
51
|
+
export interface LoaderRegistry {
|
|
52
|
+
register(widgetId: string, init: WidgetInit): void;
|
|
53
|
+
refresh: () => Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
declare global {
|
|
56
|
+
interface Window {
|
|
57
|
+
__web5Loader__?: LoaderRegistry;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves and attaches the `prompt-input-tokens.css` design-token sheet to a
|
|
3
|
+
* widget's shadow root. The sheet is owned by `web50-server-ui` and provides
|
|
4
|
+
* the `--pi-*` CSS variables consumed by widget styles.
|
|
5
|
+
*
|
|
6
|
+
* Defaults match Wix infra; consumers can fully override for non-Wix
|
|
7
|
+
* environments (e.g. self-hosted CSS in CI / partner deployments).
|
|
8
|
+
*/
|
|
9
|
+
export interface PromptInputTokensOptions {
|
|
10
|
+
/** Static-versions endpoint to resolve the version from. */
|
|
11
|
+
staticVersionsUrl?: string;
|
|
12
|
+
/** CDN base where the tokens artifact is published. */
|
|
13
|
+
tokensCdnBase?: string;
|
|
14
|
+
/** Tokens artifact ID looked up in `static-versions`. */
|
|
15
|
+
tokensArtifactId?: string;
|
|
16
|
+
/** File name of the tokens stylesheet. */
|
|
17
|
+
tokensFileName?: string;
|
|
18
|
+
/** Version used when resolution fails. */
|
|
19
|
+
fallbackVersion?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function getPromptInputTokensUrl(version: string, options?: PromptInputTokensOptions): string;
|
|
22
|
+
export declare function resolvePromptInputTokensVersion(options?: PromptInputTokensOptions): Promise<string>;
|
|
23
|
+
export declare function resolvePromptInputTokensUrl(options?: PromptInputTokensOptions): Promise<string>;
|
|
24
|
+
export declare function attachPromptInputTokensStylesheet(shadowRoot: ShadowRoot, options?: PromptInputTokensOptions): Promise<void>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers shared by every per-client embed `main.ts` for parsing host-page
|
|
3
|
+
* script-tag attributes and gating widget initialization.
|
|
4
|
+
*/
|
|
5
|
+
import type { Web5EmbedBaseConfig, Web5EmbedConfig } from './types';
|
|
6
|
+
export declare function resolveTarget(target: string | HTMLElement | undefined): HTMLElement;
|
|
7
|
+
export declare function shouldInit(requireParam?: string): boolean;
|
|
8
|
+
export declare function shouldInitForPath(excludePaths: string | string[] | undefined, defaultExcludePaths?: string[]): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Reads the standard `Web5EmbedBaseConfig` data-attributes from a script tag.
|
|
11
|
+
* Per-client `main.ts` files extend this with their own widget-specific
|
|
12
|
+
* attributes (visibility mode, copy props, etc.) by reading additional
|
|
13
|
+
* attributes from the same script.
|
|
14
|
+
*/
|
|
15
|
+
export declare function readBaseConfigFromScript(script: HTMLOrSVGScriptElement | null): Web5EmbedBaseConfig;
|
|
16
|
+
/**
|
|
17
|
+
* Reads the bottom-floating widget's extended config (visibility mode,
|
|
18
|
+
* showAfterTarget, excludePaths) on top of the base config.
|
|
19
|
+
*/
|
|
20
|
+
export declare function readBottomConfigFromScript(script: HTMLOrSVGScriptElement | null): Web5EmbedConfig;
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for widget rendering and chip-suggestion fetching.
|
|
3
|
+
*
|
|
4
|
+
* The default Wix `DEFAULT_BASE_URL` from legacy embed-input is removed —
|
|
5
|
+
* each consumer must supply its own `baseUrl`.
|
|
6
|
+
*/
|
|
7
|
+
export interface ChipsApiResponse {
|
|
8
|
+
chips?: {
|
|
9
|
+
label: string;
|
|
10
|
+
}[];
|
|
11
|
+
}
|
|
12
|
+
/** Default arrow-up SVG used as the submit/chip icon. */
|
|
13
|
+
export declare const sendIconSvg = "\n <svg class=\"web5-submit-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <path d=\"M13.0306 7.53114C12.9609 7.60106 12.8781 7.65654 12.787 7.69439C12.6958 7.73224 12.5981 7.75173 12.4993 7.75173C12.4006 7.75173 12.3029 7.73224 12.2117 7.69439C12.1206 7.65654 12.0378 7.60106 11.9681 7.53114L8.74997 4.31301V13.5005C8.74997 13.6994 8.67095 13.8902 8.5303 14.0308C8.38965 14.1715 8.19888 14.2505 7.99997 14.2505C7.80106 14.2505 7.61029 14.1715 7.46964 14.0308C7.32899 13.8902 7.24997 13.6994 7.24997 13.5005V4.31301L4.0306 7.53114C3.8897 7.67203 3.69861 7.75119 3.49935 7.75119C3.30009 7.75119 3.10899 7.67203 2.9681 7.53114C2.8272 7.39024 2.74805 7.19915 2.74805 6.99989C2.74805 6.80063 2.8272 6.60953 2.9681 6.46864L7.4681 1.96864C7.53778 1.89872 7.62057 1.84324 7.71173 1.80539C7.8029 1.76753 7.90064 1.74805 7.99935 1.74805C8.09806 1.74805 8.1958 1.76753 8.28696 1.80539C8.37813 1.84324 8.46092 1.89872 8.5306 1.96864L13.0306 6.46864C13.1005 6.53832 13.156 6.62111 13.1938 6.71227C13.2317 6.80344 13.2512 6.90118 13.2512 6.99989C13.2512 7.0986 13.2317 7.19634 13.1938 7.2875C13.156 7.37867 13.1005 7.46146 13.0306 7.53114Z\" fill=\"currentColor\" />\n </svg>\n";
|
|
14
|
+
export declare function buildUrl(base: string): string;
|
|
15
|
+
export declare function escapeHtml(value: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Conversation identity + context for the warm (conversation-aware) chips
|
|
18
|
+
* path. `web5-embed` is a dependency-light SDK with no `@wix/web5-core`
|
|
19
|
+
* dependency, so the caller (the per-client widget, which already imports
|
|
20
|
+
* web5-core) injects the identity (`chatId` / `sessionId` / `mode`) it
|
|
21
|
+
* generates with `getChatId` / `getOrCreateSessionId` / `resolveConversationMode`.
|
|
22
|
+
*/
|
|
23
|
+
export interface ContextualChipsContext {
|
|
24
|
+
/** Conversation trigger id from the page URL (`web5_id`). Maps to `Turn`'s
|
|
25
|
+
* `parentMessageId` / `Conversation`'s `predecessorTriggerId`. */
|
|
26
|
+
web5Id: string;
|
|
27
|
+
/**
|
|
28
|
+
* Product handle (entity id) for the current page. Composed into the query
|
|
29
|
+
* the same way `pdpFraming` does (`composeQuery`) so the BE resolves the
|
|
30
|
+
* product from the handle, not the URL. Falls back to `<unknown>` if absent.
|
|
31
|
+
*/
|
|
32
|
+
handle?: string;
|
|
33
|
+
/** Entity type used in the query phrasing (default `product`). */
|
|
34
|
+
entityType?: string;
|
|
35
|
+
/** Session id (Turn `session_id` / Conversation `additionalData.sessionId`). */
|
|
36
|
+
sessionId?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Which BE API serves the warm chips (B2B-4121, DL #110). `'turn'` uses the
|
|
39
|
+
* lock-free `Turn` RPC; `'conversation'` uses `/v1/conversation`, which runs
|
|
40
|
+
* the same prompt as the streamed page. The widget picks the endpoint.
|
|
41
|
+
*/
|
|
42
|
+
api?: 'turn' | 'conversation';
|
|
43
|
+
/** Conversation wire mode (e.g. `markdown_history_v3`). Conversation API only. */
|
|
44
|
+
mode?: string;
|
|
45
|
+
/** Chat id for the Conversation request body. Conversation API only. */
|
|
46
|
+
chatId?: string;
|
|
47
|
+
/** Override the endpoint (tests). */
|
|
48
|
+
endpoint?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface FetchChipSuggestionsOptions {
|
|
51
|
+
/** Endpoint that accepts `?url=...` and returns ChipsApiResponse. */
|
|
52
|
+
endpoint?: string;
|
|
53
|
+
/** Abort timeout in ms. */
|
|
54
|
+
timeoutMs?: number;
|
|
55
|
+
/** Override `fetch` (tests). */
|
|
56
|
+
fetchImpl?: typeof fetch;
|
|
57
|
+
/**
|
|
58
|
+
* When set, the page is "warm" (the visitor arrived from a Web5 turn,
|
|
59
|
+
* `?web5_id=` present) and chips are generated conversation-aware via the
|
|
60
|
+
* `/conversation` endpoint instead of the URL-only page-enrichment endpoint
|
|
61
|
+
* (B2B-4121).
|
|
62
|
+
*/
|
|
63
|
+
conversation?: ContextualChipsContext;
|
|
64
|
+
}
|
|
65
|
+
export interface FetchContextualChipsOptions extends ContextualChipsContext {
|
|
66
|
+
/** Abort timeout in ms. */
|
|
67
|
+
timeoutMs?: number;
|
|
68
|
+
/** Override `fetch` (tests). */
|
|
69
|
+
fetchImpl?: typeof fetch;
|
|
70
|
+
}
|
|
71
|
+
export declare function fetchChipSuggestions(pageUrl: string, options?: FetchChipSuggestionsOptions): Promise<string[]>;
|
|
72
|
+
/**
|
|
73
|
+
* Generate conversation-aware chip suggestions for the warm path (B2B-4121).
|
|
74
|
+
*
|
|
75
|
+
* POSTs the same `/conversation` shape the placements use (see
|
|
76
|
+
* `embed-placement/src/placementFetch.ts`), tagged `additionalData.placement =
|
|
77
|
+
* 'pdpChips'` so the BE resolves the chips prompt by key and links the turn to
|
|
78
|
+
* the prior conversation via `predecessorTriggerId`. The prompt is authored to
|
|
79
|
+
* return JSON in the `ChipsApiResponse` shape, so the labels parse the same way
|
|
80
|
+
* as the URL-only endpoint.
|
|
81
|
+
*/
|
|
82
|
+
export declare function fetchContextualChips(options: FetchContextualChipsOptions): Promise<string[]>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves artifact versions from a remote `static-versions` JSON endpoint.
|
|
3
|
+
*
|
|
4
|
+
* The default URL points at Wix infra (`wixapis.com`), matching legacy
|
|
5
|
+
* embed-input behaviour. Non-Wix consumers pass their own URL.
|
|
6
|
+
*/
|
|
7
|
+
import type { ArtifactVersion } from './types';
|
|
8
|
+
export declare const DEFAULT_STATIC_VERSIONS_URL = "https://www.wixapis.com/web50-server/static-versions";
|
|
9
|
+
export declare function fetchStaticVersions(url?: string): Promise<ArtifactVersion[]>;
|
|
10
|
+
export declare function findVersion(versions: ArtifactVersion[], artifactId: string): string;
|
|
11
|
+
export declare function getCachedVersions(): ArtifactVersion[] | undefined;
|
|
12
|
+
export declare function cacheVersions(versions: ArtifactVersion[]): void;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public type contracts for `@wix/web5-embed`.
|
|
3
|
+
*
|
|
4
|
+
* Per-client widget packages (e.g. `@wix/w5-client-circana`) extend or use
|
|
5
|
+
* these directly when they parse host-page configuration. Variant-specific
|
|
6
|
+
* shapes (e.g. circana's homepage-entry copy fields) live in the client
|
|
7
|
+
* package, not here — the SDK does not enumerate variant ids.
|
|
8
|
+
*/
|
|
9
|
+
export interface Web5EmbedBaseConfig {
|
|
10
|
+
/** CSS selector or HTMLElement to render the widget into */
|
|
11
|
+
target?: string | HTMLElement;
|
|
12
|
+
/** Placeholder text for the input field */
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
/** Chip suggestions to display. Overrides fetched suggestions when provided. */
|
|
15
|
+
suggestions?: string[];
|
|
16
|
+
/** Base URL for the Web5 server */
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
/** URL query parameter name that must be present for the widget to initialize */
|
|
19
|
+
requireParam?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface Web5EmbedConfig extends Web5EmbedBaseConfig {
|
|
22
|
+
/**
|
|
23
|
+
* Variant id selected by the loader's route options. Each client owns its
|
|
24
|
+
* own set of variant ids; `'default'` (or `undefined`) maps to the client's
|
|
25
|
+
* primary widget. Use `createVariantMount` to dispatch.
|
|
26
|
+
*/
|
|
27
|
+
variant?: string;
|
|
28
|
+
/** Pathnames where the widget should NOT render. Accepts a single path or a list. */
|
|
29
|
+
excludePaths?: string | string[];
|
|
30
|
+
/** Controls whether the bottom widget is always visible or waits until a target has been scrolled past. */
|
|
31
|
+
visibilityMode?: 'always' | 'after-target';
|
|
32
|
+
/** CSS selector of the element that must pass above the viewport before the bottom widget is shown. */
|
|
33
|
+
showAfterTarget?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Submit routing (DL #097 D3).
|
|
36
|
+
* - `'redirect'` (default): navigate to `baseUrl` on submit (legacy `/ai` flow).
|
|
37
|
+
* - `'event'`: broadcast a `web5:user-query` DOM event for an on-page
|
|
38
|
+
* placement to consume — but only on a "warm" page (a `web5_id` is present);
|
|
39
|
+
* on a cold page it falls back to the redirect so a query is never dropped.
|
|
40
|
+
*/
|
|
41
|
+
submitTarget?: 'redirect' | 'event';
|
|
42
|
+
}
|
|
43
|
+
export interface ArtifactVersion {
|
|
44
|
+
artifactId: string;
|
|
45
|
+
version: string;
|
|
46
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collapse controller for a bottom-fixed prompt widget.
|
|
3
|
+
*
|
|
4
|
+
* Tracks the scroll position relative to the bottom of the document and
|
|
5
|
+
* collapses the widget when within `thresholdPx` of the page bottom — but
|
|
6
|
+
* only if the widget itself doesn't currently hold focus and hasn't already
|
|
7
|
+
* been manually expanded inside the bottom zone.
|
|
8
|
+
*
|
|
9
|
+
* Caller is responsible for binding scroll/resize listeners; this controller
|
|
10
|
+
* just exposes pure state transitions.
|
|
11
|
+
*/
|
|
12
|
+
export interface CollapseControllerOptions {
|
|
13
|
+
/** Distance in px from the bottom of the document at which to collapse. */
|
|
14
|
+
thresholdPx?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface CollapseControllerState {
|
|
17
|
+
isCollapsed(): boolean;
|
|
18
|
+
isCoordinatorVisible(): boolean;
|
|
19
|
+
hasFocusInside(): boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface CollapseController {
|
|
22
|
+
/** Should the widget collapse right now, given current state? */
|
|
23
|
+
evaluate(state: CollapseControllerState): boolean;
|
|
24
|
+
/** Mark that the user has expanded the widget while in the bottom zone. */
|
|
25
|
+
markManuallyExpanded(): void;
|
|
26
|
+
/** Reset manual-expand memory (e.g. when leaving the bottom zone). */
|
|
27
|
+
resetManualExpand(): void;
|
|
28
|
+
}
|
|
29
|
+
export declare function createCollapseController(options?: CollapseControllerOptions): CollapseController;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-resizing textarea height with a `multiline` class toggle on a wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: textarea grows from `lineHeight` up to `maxHeightPx`. When growth
|
|
5
|
+
* happens the wrapper element receives `multilineClass` so callers can
|
|
6
|
+
* adjust padding/border-radius for the multi-line state.
|
|
7
|
+
*/
|
|
8
|
+
export interface InputHeightSyncOptions {
|
|
9
|
+
input: HTMLTextAreaElement;
|
|
10
|
+
/** Element that should toggle the `multiline` class. */
|
|
11
|
+
wrapper: Element | null;
|
|
12
|
+
/** Class added/removed on `wrapper` when input grows past one line. */
|
|
13
|
+
multilineClass: string;
|
|
14
|
+
/** Max height in px the input is allowed to grow to. */
|
|
15
|
+
maxHeightPx: number;
|
|
16
|
+
/** Multiplier applied to lineHeight to decide when to flip to multiline. Default 1. */
|
|
17
|
+
multilineThresholdMultiplier?: number;
|
|
18
|
+
/** Fallback lineHeight if computed style is unparsable. */
|
|
19
|
+
fallbackLineHeightPx?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface InputHeightSync {
|
|
22
|
+
sync(): void;
|
|
23
|
+
}
|
|
24
|
+
export declare function createInputHeightSync(options: InputHeightSyncOptions): InputHeightSync;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Submit-flow helper: navigates the host page to `<baseUrl>?initialQuestion=<value>`.
|
|
3
|
+
*
|
|
4
|
+
* Pure side-effect function; pulled out so per-client widgets share the same
|
|
5
|
+
* URL-build + window.open contract.
|
|
6
|
+
*/
|
|
7
|
+
export interface SubmitFlowOptions {
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
/** Window target for the navigation. Defaults to `_self`. */
|
|
10
|
+
windowTarget?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SubmitFlow {
|
|
13
|
+
open(value: string): boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function createSubmitFlow(options: SubmitFlowOptions): SubmitFlow;
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wix/web5-embed",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "SDK primitives for embedding Wix Web5 conversational UI into host pages — bootstrap loader, shadow-DOM mount helpers, focus-shortcut coordinator, and design-token loader.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/wix-private/web5.0-poc/tree/master/packages/web5-embed#readme",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"wix",
|
|
9
|
+
"web5",
|
|
10
|
+
"embed",
|
|
11
|
+
"widget",
|
|
12
|
+
"shadow-dom",
|
|
13
|
+
"sdk"
|
|
14
|
+
],
|
|
15
|
+
"main": "dist/index.cjs",
|
|
16
|
+
"module": "dist/index.js",
|
|
17
|
+
"types": "dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"require": "./dist/index.cjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"LICENSE"
|
|
29
|
+
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"registry": "https://registry.npmjs.org/",
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"start": "vite dev",
|
|
36
|
+
"build": "vite build && tsc -p tsconfig.types.json",
|
|
37
|
+
"preview": "vite preview",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:watch": "vitest"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"happy-dom": "^20.9.0",
|
|
43
|
+
"typescript": "~5.7.0",
|
|
44
|
+
"vite": "^7.3.2",
|
|
45
|
+
"vitest": "^3.2.4"
|
|
46
|
+
},
|
|
47
|
+
"wix": {
|
|
48
|
+
"artifact": {
|
|
49
|
+
"groupId": "com.wixpress",
|
|
50
|
+
"artifactId": "web5-embed"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"falconPackageHash": "9f3e893abbd6eea93e51047bfe3cf276f099ff0c68fe23418c7bad76"
|
|
54
|
+
}
|