@eka-care/medassist-widget-embed 0.2.6 → 0.2.8
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/dist/iframe.js +51 -18
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +79 -11
- package/dist/src/medassist-widget.css +6 -0
- package/dist/src/medassist-widget.js +9 -6
- package/dist/src/medassist-widget.js.map +1 -1
- package/iframe.ts +58 -21
- package/index.ts +81 -12
- package/package.json +1 -1
- package/src/medassist-widget.css +6 -0
- package/src/medassist-widget.js +9 -6
- package/src/medassist-widget.js.map +1 -1
- package/test.html +0 -1
package/iframe.ts
CHANGED
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
/** Theme from agent-config API (has mode; textColor derived: dark → black, light → white) */
|
|
33
33
|
type AgentConfig = {
|
|
34
34
|
name?: string;
|
|
35
|
-
allowed?: ("text" | "file" | "audio")[];
|
|
36
35
|
theme?: {
|
|
37
36
|
background?: string;
|
|
38
37
|
background_img?: string;
|
|
@@ -92,7 +91,7 @@
|
|
|
92
91
|
});
|
|
93
92
|
if (!res.ok) return undefined;
|
|
94
93
|
const json = await res.json();
|
|
95
|
-
if (json?.success && json?.data) return json.data as AgentConfig;
|
|
94
|
+
if (json?.success && json?.data?.theme) return json.data as AgentConfig;
|
|
96
95
|
return undefined;
|
|
97
96
|
} catch {
|
|
98
97
|
return undefined;
|
|
@@ -143,9 +142,54 @@
|
|
|
143
142
|
let widgetScriptPromise: Promise<void> | null = null;
|
|
144
143
|
let widgetCssTextPromise: Promise<string> | null = null;
|
|
145
144
|
|
|
145
|
+
// --- Start loading as early as possible (don't wait for DOMContentLoaded) ---
|
|
146
|
+
const urlParams = new URLSearchParams(
|
|
147
|
+
typeof window !== "undefined" ? window.location.search : ""
|
|
148
|
+
);
|
|
149
|
+
const earlyAgentId = urlParams.get("agentId");
|
|
150
|
+
const earlyBaseUrl =
|
|
151
|
+
urlParams.get("baseUrl") || "https://matrix.eka.care/reloaded";
|
|
152
|
+
|
|
153
|
+
if (typeof document !== "undefined" && document.head) {
|
|
154
|
+
try {
|
|
155
|
+
const assetOrigin = new URL(widgetAssetBaseUrl).origin;
|
|
156
|
+
const apiOrigin = new URL(earlyBaseUrl).origin;
|
|
157
|
+
const preconnectAsset = document.createElement("link");
|
|
158
|
+
preconnectAsset.rel = "preconnect";
|
|
159
|
+
preconnectAsset.href = assetOrigin;
|
|
160
|
+
preconnectAsset.crossOrigin = "anonymous";
|
|
161
|
+
document.head.appendChild(preconnectAsset);
|
|
162
|
+
if (assetOrigin !== apiOrigin) {
|
|
163
|
+
const preconnectApi = document.createElement("link");
|
|
164
|
+
preconnectApi.rel = "preconnect";
|
|
165
|
+
preconnectApi.href = apiOrigin;
|
|
166
|
+
preconnectApi.crossOrigin = "anonymous";
|
|
167
|
+
document.head.appendChild(preconnectApi);
|
|
168
|
+
}
|
|
169
|
+
const preloadScript = document.createElement("link");
|
|
170
|
+
preloadScript.rel = "preload";
|
|
171
|
+
preloadScript.as = "script";
|
|
172
|
+
preloadScript.href = WIDGET_JS_URL;
|
|
173
|
+
document.head.appendChild(preloadScript);
|
|
174
|
+
const preloadCss = document.createElement("link");
|
|
175
|
+
preloadCss.rel = "preload";
|
|
176
|
+
preloadCss.as = "style";
|
|
177
|
+
preloadCss.href = WIDGET_CSS_URL;
|
|
178
|
+
document.head.appendChild(preloadCss);
|
|
179
|
+
} catch {
|
|
180
|
+
// ignore URL/preload errors
|
|
181
|
+
}
|
|
182
|
+
loadWidgetCss();
|
|
183
|
+
loadWidgetScript();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const agentConfigPromise: Promise<AgentConfig | undefined> =
|
|
187
|
+
earlyAgentId && earlyBaseUrl
|
|
188
|
+
? fetchAgentConfig(earlyBaseUrl, earlyAgentId)
|
|
189
|
+
: Promise.resolve(undefined);
|
|
190
|
+
|
|
146
191
|
// Auto-initialize from URL parameters (no shadow DOM needed in iframe)
|
|
147
192
|
const initializeFromUrlParams = async (): Promise<void> => {
|
|
148
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
149
193
|
const agentId = urlParams.get("agentId");
|
|
150
194
|
|
|
151
195
|
if (!agentId) {
|
|
@@ -170,7 +214,11 @@
|
|
|
170
214
|
: undefined;
|
|
171
215
|
const container = document.getElementById("root") || document.body;
|
|
172
216
|
try {
|
|
173
|
-
await Promise.all([
|
|
217
|
+
const [, , agentConfig] = await Promise.all([
|
|
218
|
+
loadWidgetCss(),
|
|
219
|
+
loadWidgetScript(),
|
|
220
|
+
agentConfigPromise,
|
|
221
|
+
]);
|
|
174
222
|
if (
|
|
175
223
|
!window?.renderMedAssist ||
|
|
176
224
|
typeof window?.renderMedAssist !== "function"
|
|
@@ -178,29 +226,18 @@
|
|
|
178
226
|
throw new Error("renderMedAssist is not available on window");
|
|
179
227
|
}
|
|
180
228
|
|
|
181
|
-
// Fetch agent-config when baseUrl is available; 10s timeout aborts pending request
|
|
182
229
|
let apiTheme: WidgetTheme | undefined;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
title = agentConfig?.name ?? title;
|
|
190
|
-
iconUrl = agentConfig?.theme?.icon_img ?? iconUrl;
|
|
191
|
-
apiTheme = mapAgentConfigThemeToWidgetTheme(
|
|
192
|
-
agentConfig?.theme ?? undefined
|
|
193
|
-
);
|
|
194
|
-
allowed = agentConfig?.allowed;
|
|
195
|
-
} catch {
|
|
196
|
-
// ignore; use URL params only
|
|
197
|
-
}
|
|
230
|
+
if (agentConfig) {
|
|
231
|
+
title = agentConfig?.name ?? title;
|
|
232
|
+
iconUrl = agentConfig?.theme?.icon_img ?? iconUrl;
|
|
233
|
+
apiTheme = mapAgentConfigThemeToWidgetTheme(
|
|
234
|
+
agentConfig?.theme ?? undefined
|
|
235
|
+
);
|
|
198
236
|
}
|
|
199
237
|
|
|
200
238
|
const config = {
|
|
201
239
|
title,
|
|
202
240
|
iconUrl,
|
|
203
|
-
allowed,
|
|
204
241
|
environment,
|
|
205
242
|
onClose: () => {
|
|
206
243
|
if (window.parent !== window) {
|
package/index.ts
CHANGED
|
@@ -82,6 +82,22 @@ function mapAgentConfigThemeToWidgetTheme(apiTheme: AgentConfig["theme"] | undef
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/** Preload an image by URL so it is cached and loads faster when the widget uses it. */
|
|
86
|
+
function preloadImage(url: string): void {
|
|
87
|
+
if (!url || typeof document === "undefined") return;
|
|
88
|
+
try {
|
|
89
|
+
const link = document.createElement("link");
|
|
90
|
+
link.rel = "preload";
|
|
91
|
+
link.as = "image";
|
|
92
|
+
link.href = url;
|
|
93
|
+
document.head.appendChild(link);
|
|
94
|
+
} catch {
|
|
95
|
+
// fallback: use Image to prime cache
|
|
96
|
+
const img = new Image();
|
|
97
|
+
img.src = url;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
85
101
|
/** Fetch agent-config from API and return theme from response data */
|
|
86
102
|
async function fetchAgentConfig(baseUrl: string, agentId: string): Promise<AgentConfig | undefined> {
|
|
87
103
|
const url = `${baseUrl.replace(/\/$/, "")}/med-assist/agent-config/${agentId}`;
|
|
@@ -136,6 +152,10 @@ if (typeof window !== "undefined") {
|
|
|
136
152
|
}
|
|
137
153
|
}
|
|
138
154
|
|
|
155
|
+
if (config.theme?.backgroundImage) {
|
|
156
|
+
preloadImage(config.theme.backgroundImage);
|
|
157
|
+
}
|
|
158
|
+
|
|
139
159
|
(el as any).openFromBridge?.();
|
|
140
160
|
},
|
|
141
161
|
};
|
|
@@ -179,6 +199,21 @@ const WIDGET_CSS_URL = `${widgetAssetBaseUrl}medassist-widget.css`;
|
|
|
179
199
|
let widgetScriptPromise: Promise<void> | null = null;
|
|
180
200
|
let widgetCssTextPromise: Promise<string> | null = null;
|
|
181
201
|
|
|
202
|
+
/** Start fetching widget CSS (shared promise). Used for preload and by loadWidgetCss. */
|
|
203
|
+
function getWidgetCssTextPromise(): Promise<string> {
|
|
204
|
+
if (!widgetCssTextPromise) {
|
|
205
|
+
widgetCssTextPromise = fetch(WIDGET_CSS_URL).then((response) => {
|
|
206
|
+
if (!response.ok) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`Unable to fetch widget styles from ${WIDGET_CSS_URL}`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return response.text();
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return widgetCssTextPromise;
|
|
215
|
+
}
|
|
216
|
+
|
|
182
217
|
class MedAssistWidgetLoader extends HTMLElement {
|
|
183
218
|
private defaultIconUrl: string;
|
|
184
219
|
private widgetLoaded: boolean;
|
|
@@ -204,6 +239,7 @@ class MedAssistWidgetLoader extends HTMLElement {
|
|
|
204
239
|
this.initializeFullMode();
|
|
205
240
|
} else {
|
|
206
241
|
this.renderButton();
|
|
242
|
+
this.preloadWidgetAssets();
|
|
207
243
|
}
|
|
208
244
|
}
|
|
209
245
|
|
|
@@ -226,6 +262,50 @@ class MedAssistWidgetLoader extends HTMLElement {
|
|
|
226
262
|
}
|
|
227
263
|
}
|
|
228
264
|
|
|
265
|
+
/** When in widget mode, start loading JS and CSS on page load so first open is faster. */
|
|
266
|
+
private preloadWidgetAssets(): void {
|
|
267
|
+
getWidgetCssTextPromise(); // start CSS fetch (no inject until open)
|
|
268
|
+
void this.loadWidgetScript(); // start JS load
|
|
269
|
+
void this.preloadBackgroundImage();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Preload theme background image from init config, theme attribute, or agent-config API. */
|
|
273
|
+
private preloadBackgroundImage(): void {
|
|
274
|
+
const initConfig =
|
|
275
|
+
typeof window !== "undefined"
|
|
276
|
+
? (window as EkaMedAssistWindow).__ekaMedAssistConfig__
|
|
277
|
+
: undefined;
|
|
278
|
+
const attributeTheme = this.getAttribute("theme")
|
|
279
|
+
? (() => {
|
|
280
|
+
try {
|
|
281
|
+
return JSON.parse(this.getAttribute("theme") || "{}") as { backgroundImage?: string };
|
|
282
|
+
} catch {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
})()
|
|
286
|
+
: undefined;
|
|
287
|
+
|
|
288
|
+
const fromInit = initConfig?.theme?.backgroundImage;
|
|
289
|
+
const fromAttr = attributeTheme?.backgroundImage;
|
|
290
|
+
if (fromInit) preloadImage(fromInit);
|
|
291
|
+
if (fromAttr && fromAttr !== fromInit) preloadImage(fromAttr);
|
|
292
|
+
|
|
293
|
+
const baseUrl =
|
|
294
|
+
(initConfig?.baseUrl as string) || this.getAttribute("base-url") || "";
|
|
295
|
+
const agentId =
|
|
296
|
+
(initConfig?.agentId as string) || this.getAttribute("agent-id") || "";
|
|
297
|
+
if (baseUrl && agentId) {
|
|
298
|
+
fetchAgentConfig(baseUrl, agentId)
|
|
299
|
+
.then((agentConfig) => {
|
|
300
|
+
const url = agentConfig?.theme?.background_img;
|
|
301
|
+
if (url) preloadImage(url);
|
|
302
|
+
})
|
|
303
|
+
.catch(() => {
|
|
304
|
+
// ignore; widget will load config when it opens
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
229
309
|
private setupAuthExpirationListener(): void {
|
|
230
310
|
if (typeof window === "undefined") return;
|
|
231
311
|
|
|
@@ -470,18 +550,7 @@ class MedAssistWidgetLoader extends HTMLElement {
|
|
|
470
550
|
return;
|
|
471
551
|
}
|
|
472
552
|
|
|
473
|
-
|
|
474
|
-
widgetCssTextPromise = fetch(WIDGET_CSS_URL).then((response) => {
|
|
475
|
-
if (!response.ok) {
|
|
476
|
-
throw new Error(
|
|
477
|
-
`Unable to fetch widget styles from ${WIDGET_CSS_URL}`
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
return response.text();
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const cssText = await widgetCssTextPromise;
|
|
553
|
+
const cssText = await getWidgetCssTextPromise();
|
|
485
554
|
const styleTag = document.createElement("style");
|
|
486
555
|
styleTag.setAttribute("data-medassist-style", "true");
|
|
487
556
|
styleTag.textContent = cssText;
|
package/package.json
CHANGED
package/src/medassist-widget.css
CHANGED
|
@@ -2727,6 +2727,12 @@ body {
|
|
|
2727
2727
|
opacity: 0.6;
|
|
2728
2728
|
}
|
|
2729
2729
|
|
|
2730
|
+
.disabled\:hover\:scale-100:hover:disabled {
|
|
2731
|
+
--tw-scale-x: 1;
|
|
2732
|
+
--tw-scale-y: 1;
|
|
2733
|
+
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2730
2736
|
.group:hover .group-hover\:bg-purple-200\/70 {
|
|
2731
2737
|
background-color: rgb(233 213 255 / 0.7);
|
|
2732
2738
|
}
|
package/src/medassist-widget.js
CHANGED
|
@@ -32017,8 +32017,10 @@ var MedAssistWidget = (function(exports) {
|
|
|
32017
32017
|
}) {
|
|
32018
32018
|
const fileInputRef = reactExports.useRef(null);
|
|
32019
32019
|
const textareaRef = reactExports.useRef(null);
|
|
32020
|
-
const
|
|
32020
|
+
const hasContent = value.trim().length > 0 || hasAttachment;
|
|
32021
32021
|
const showMic = allowed === void 0 || allowed.includes("audio");
|
|
32022
|
+
const showSendButton = hasContent || !showMic;
|
|
32023
|
+
const canSend = hasContent;
|
|
32022
32024
|
const showFileAttachment = allowed === void 0 || allowed.includes("file");
|
|
32023
32025
|
const isMicDisabled = (isDisabled || recordingStatus !== AudioRecordingStatus.IDLE) && !isRecording;
|
|
32024
32026
|
const micButtonClasses = cn(
|
|
@@ -32038,7 +32040,7 @@ var MedAssistWidget = (function(exports) {
|
|
|
32038
32040
|
fileInputRef.current?.click();
|
|
32039
32041
|
};
|
|
32040
32042
|
const handleKeyPress = (e) => {
|
|
32041
|
-
if (e.key === "Enter" && !e.shiftKey &&
|
|
32043
|
+
if (e.key === "Enter" && !e.shiftKey && canSend && !isDisabled) {
|
|
32042
32044
|
e.preventDefault();
|
|
32043
32045
|
onSend();
|
|
32044
32046
|
}
|
|
@@ -32178,15 +32180,16 @@ var MedAssistWidget = (function(exports) {
|
|
|
32178
32180
|
children: /* @__PURE__ */ jsxRuntimeExports.jsx(Mic, { className: "w-5 h-5" })
|
|
32179
32181
|
}
|
|
32180
32182
|
),
|
|
32181
|
-
showSendButton &&
|
|
32183
|
+
showSendButton && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
32182
32184
|
Button,
|
|
32183
32185
|
{
|
|
32184
32186
|
type: "button",
|
|
32185
32187
|
size: "icon",
|
|
32186
32188
|
onClick: onSend,
|
|
32187
|
-
|
|
32188
|
-
|
|
32189
|
-
"
|
|
32189
|
+
disabled: isDisabled || !canSend,
|
|
32190
|
+
className: `absolute right-0.5 top-1/2 -translate-y-1/2 h-10 w-10 ${!showMic ? "" : "bg-primary"} hover:bg-primary/90 rounded-full text-primary-foreground transition-all duration-200 shadow-lg hover:shadow-xl hover:scale-105 active:scale-95 shrink-0 disabled:opacity-50 disabled:pointer-events-none disabled:hover:scale-100`,
|
|
32191
|
+
title: canSend ? "Send message" : "Type a message to send",
|
|
32192
|
+
"aria-label": canSend ? "Send message" : "Type a message to send",
|
|
32190
32193
|
children: /* @__PURE__ */ jsxRuntimeExports.jsx(SendIcon, { className: "w-4 h-4 text-primary-foreground" })
|
|
32191
32194
|
}
|
|
32192
32195
|
)
|