sagedesk 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,7 +36,7 @@ __export(react_exports, {
36
36
  module.exports = __toCommonJS(react_exports);
37
37
 
38
38
  // src/react/SageDeskWidget.tsx
39
- var import_react2 = require("react");
39
+ var import_react2 = __toESM(require("react"), 1);
40
40
  var import_react_dom = require("react-dom");
41
41
 
42
42
  // src/react/useSageDesk.ts
@@ -295,6 +295,7 @@ function logFallbackWarning(reason) {
295
295
  }
296
296
  var initialState = {
297
297
  messages: [],
298
+ userMessages: [],
298
299
  isOpen: false,
299
300
  isTyping: false,
300
301
  engineStatus: "idle",
@@ -307,8 +308,11 @@ function reducer(state, action) {
307
308
  return { ...state, isOpen: true };
308
309
  case "CLOSE":
309
310
  return { ...state, isOpen: false };
310
- case "ADD_MESSAGE":
311
- return { ...state, messages: [...state.messages, action.payload] };
311
+ case "ADD_MESSAGE": {
312
+ const newMessages = [...state.messages, action.payload];
313
+ const newUserMessages = action.payload.role === "user" ? [...state.userMessages, action.payload] : state.userMessages;
314
+ return { ...state, messages: newMessages, userMessages: newUserMessages };
315
+ }
312
316
  case "SET_TYPING":
313
317
  return { ...state, isTyping: action.payload };
314
318
  case "SET_ENGINE_STATUS":
@@ -331,6 +335,7 @@ function useSageDesk(config) {
331
335
  const embedderRef = (0, import_react.useRef)(null);
332
336
  const engineStartedRef = (0, import_react.useRef)(false);
333
337
  const msgCounterRef = (0, import_react.useRef)(0);
338
+ const engineReadyCallbacksRef = (0, import_react.useRef)([]);
334
339
  const [chips, setChips] = (0, import_react.useState)([]);
335
340
  const makeId = () => `msg-${++msgCounterRef.current}`;
336
341
  const addMessage = (0, import_react.useCallback)(
@@ -342,12 +347,27 @@ function useSageDesk(config) {
342
347
  },
343
348
  []
344
349
  );
350
+ const notifyEngineReady = (0, import_react.useCallback)(() => {
351
+ const callbacks = engineReadyCallbacksRef.current;
352
+ engineReadyCallbacksRef.current = [];
353
+ callbacks.forEach((cb) => cb());
354
+ }, []);
345
355
  const startEngine = (0, import_react.useCallback)(async () => {
346
356
  if (engineStartedRef.current) return;
347
357
  engineStartedRef.current = true;
348
358
  if (config.mode === "llm") {
349
359
  setChips(config.agent.suggestedChips ?? []);
350
- dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "ready" } });
360
+ dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "loading-model" } });
361
+ try {
362
+ embedderRef.current = new EmbedderRuntime();
363
+ await embedderRef.current.load(config.agent.model);
364
+ dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "ready" } });
365
+ notifyEngineReady();
366
+ } catch (err) {
367
+ console.warn("[sagedesk] WASM embedder failed to load - LLM mode will use fallback messages.", err);
368
+ dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "error-model", error: String(err) } });
369
+ notifyEngineReady();
370
+ }
351
371
  return;
352
372
  }
353
373
  dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "loading-index" } });
@@ -359,6 +379,7 @@ function useSageDesk(config) {
359
379
  type: "SET_ENGINE_STATUS",
360
380
  payload: { status: "error-index", error: String(err) }
361
381
  });
382
+ notifyEngineReady();
362
383
  addMessage({
363
384
  role: "bot",
364
385
  text: "I'm having trouble loading right now. Please try again in a moment."
@@ -371,12 +392,14 @@ function useSageDesk(config) {
371
392
  embedderRef.current = new EmbedderRuntime();
372
393
  await embedderRef.current.load(config.agent.model);
373
394
  dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "ready" } });
395
+ notifyEngineReady();
374
396
  } catch (err) {
375
397
  console.warn("[sagedesk] WASM model failed to load, falling back to keyword search -", err);
376
398
  embedderRef.current = new EmbedderRuntime();
377
399
  dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "degraded" } });
400
+ notifyEngineReady();
378
401
  }
379
- }, [config.mode, config.indexUrl, config.agent.suggestedChips, addMessage]);
402
+ }, [config.mode, config.indexUrl, config.agent.model, config.agent.suggestedChips, addMessage, notifyEngineReady]);
380
403
  const greetingShownRef = (0, import_react.useRef)(false);
381
404
  const open = (0, import_react.useCallback)(() => {
382
405
  dispatch({ type: "OPEN" });
@@ -393,16 +416,12 @@ function useSageDesk(config) {
393
416
  dispatch({ type: "CLOSE" });
394
417
  }, []);
395
418
  const waitForEngine = (0, import_react.useCallback)(() => {
419
+ const s = engineStatusRef.current;
420
+ if (s === "ready" || s === "degraded" || s === "error-index" || s === "error-model") {
421
+ return Promise.resolve();
422
+ }
396
423
  return new Promise((resolve) => {
397
- const check = () => {
398
- const s = engineStatusRef.current;
399
- if (s === "ready" || s === "degraded" || s === "error-index" || s === "error-model") {
400
- resolve();
401
- } else {
402
- setTimeout(check, 100);
403
- }
404
- };
405
- check();
424
+ engineReadyCallbacksRef.current.push(resolve);
406
425
  });
407
426
  }, []);
408
427
  const submit = (0, import_react.useCallback)(
@@ -426,12 +445,20 @@ function useSageDesk(config) {
426
445
  console.warn('[sagedesk] LLM mode requires an "endpoint" prop.');
427
446
  botText = getFallback(config.agent);
428
447
  isFallback = true;
448
+ } else if (!embedderRef.current?.isReady) {
449
+ console.warn("[sagedesk] Embedder not ready - showing fallback.");
450
+ botText = getFallback(config.agent);
451
+ isFallback = true;
429
452
  } else {
430
453
  try {
454
+ const vector = await embedderRef.current.embed(trimmed);
431
455
  const res = await fetch(config.endpoint, {
432
456
  method: "POST",
433
457
  headers: { "Content-Type": "application/json" },
434
- body: JSON.stringify({ query: trimmed })
458
+ body: JSON.stringify({
459
+ query: trimmed,
460
+ queryVector: Array.from(vector)
461
+ })
435
462
  });
436
463
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
437
464
  const data = await res.json();
@@ -475,8 +502,8 @@ function useSageDesk(config) {
475
502
  }
476
503
  if (config.mode !== "llm") {
477
504
  const elapsed = Date.now() - typingStart;
478
- const delayBase = retrievalMode === "keyword" || isFallback ? 800 : 3e3;
479
- const minTypingMs = delayBase + Math.random() * 2e3;
505
+ const delayBase = retrievalMode === "keyword" || isFallback ? 400 : 800;
506
+ const minTypingMs = delayBase + Math.random() * 400;
480
507
  const remaining = minTypingMs - elapsed;
481
508
  if (remaining > 0) await new Promise((r) => setTimeout(r, remaining));
482
509
  }
@@ -494,11 +521,9 @@ function useSageDesk(config) {
494
521
  return () => document.removeEventListener("keydown", handler);
495
522
  }, [state.isOpen, close]);
496
523
  const activeChips = (0, import_react.useMemo)(() => {
497
- const askedTexts = new Set(
498
- state.messages.filter((m) => m.role === "user").map((m) => m.text.toLowerCase().trim())
499
- );
524
+ const askedTexts = new Set(state.userMessages.map((m) => m.text.toLowerCase().trim()));
500
525
  return chips.filter((chip) => !askedTexts.has(chip.toLowerCase().trim()));
501
- }, [chips, state.messages]);
526
+ }, [chips, state.userMessages]);
502
527
  return { state, chips: activeChips, open, close, submit };
503
528
  }
504
529
 
@@ -694,9 +719,9 @@ var PoweredBy = ({ dark = false }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx
694
719
  }
695
720
  )
696
721
  ] });
697
- function ClassicMessageBubble({ msg, accent }) {
722
+ var ClassicMessageBubble = import_react2.default.memo(function ClassicMessageBubble2({ msg, accent }) {
698
723
  const isBot = msg.role === "bot";
699
- const renderedHtml = isBot ? parseMarkdown(msg.text) : msg.text;
724
+ const renderedHtml = (0, import_react2.useMemo)(() => isBot ? parseMarkdown(msg.text) : msg.text, [isBot, msg.text]);
700
725
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", alignItems: isBot ? "flex-start" : "flex-end", gap: "4px" }, children: [
701
726
  msg.isFallback && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: "11px", fontWeight: 500, color: "#9b9aa3", margin: 0, padding: "0 4px", fontFamily: "inherit" }, children: "Not sure about that one" }),
702
727
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
@@ -721,7 +746,7 @@ function ClassicMessageBubble({ msg, accent }) {
721
746
  fontFamily: "inherit"
722
747
  }, children: "just now" })
723
748
  ] });
724
- }
749
+ });
725
750
  function ClassicTypingIndicator() {
726
751
  const dot = { width: 6, height: 6, borderRadius: "50%", background: "#c8c8ce", display: "inline-block" };
727
752
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-start", gap: "4px" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
@@ -739,9 +764,9 @@ function ClassicTypingIndicator() {
739
764
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: dot, className: "sd-r-dot-3" })
740
765
  ] }) });
741
766
  }
742
- function LightMessageBubble({ msg, accent, agentName }) {
767
+ var LightMessageBubble = import_react2.default.memo(function LightMessageBubble2({ msg, accent, agentName }) {
743
768
  const isBot = msg.role === "bot";
744
- const renderedHtml = isBot ? parseMarkdown(msg.text) : msg.text;
769
+ const renderedHtml = (0, import_react2.useMemo)(() => isBot ? parseMarkdown(msg.text) : msg.text, [isBot, msg.text]);
745
770
  if (isBot) {
746
771
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: "10px" }, children: [
747
772
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
@@ -776,7 +801,7 @@ function LightMessageBubble({ msg, accent, agentName }) {
776
801
  wordBreak: "break-word",
777
802
  fontFamily: "inherit"
778
803
  }, children: msg.text }) });
779
- }
804
+ });
780
805
  function LightTypingIndicator({ accent }) {
781
806
  const dot = { width: 6, height: 6, borderRadius: "50%", background: "#c4c4be", display: "inline-block" };
782
807
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: "10px" }, children: [
@@ -804,9 +829,9 @@ function LightTypingIndicator({ accent }) {
804
829
  ] })
805
830
  ] });
806
831
  }
807
- function DarkMessageBubble({ msg, accent }) {
832
+ var DarkMessageBubble = import_react2.default.memo(function DarkMessageBubble2({ msg, accent }) {
808
833
  const isBot = msg.role === "bot";
809
- const renderedHtml = isBot ? parseMarkdown(msg.text) : msg.text;
834
+ const renderedHtml = (0, import_react2.useMemo)(() => isBot ? parseMarkdown(msg.text) : msg.text, [isBot, msg.text]);
810
835
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
811
836
  maxWidth: "85%",
812
837
  padding: "12px 14px",
@@ -824,7 +849,7 @@ function DarkMessageBubble({ msg, accent }) {
824
849
  msg.isFallback && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "11px", color: "rgba(255,255,255,0.5)", display: "block", marginBottom: "4px" }, children: "Not sure about that one" }),
825
850
  isBot ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: renderedHtml } }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { whiteSpace: "pre-wrap" }, children: msg.text })
826
851
  ] });
827
- }
852
+ });
828
853
  function DarkTypingIndicator() {
829
854
  const dot = { width: 6, height: 6, borderRadius: "50%", background: "rgba(255,255,255,0.4)", display: "inline-block" };
830
855
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
@@ -843,7 +868,7 @@ function DarkTypingIndicator() {
843
868
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: dot, className: "sd-r-dot-3" })
844
869
  ] });
845
870
  }
846
- function renderClassic(p) {
871
+ function ClassicTheme(p) {
847
872
  const {
848
873
  agent,
849
874
  state,
@@ -1032,7 +1057,7 @@ function renderClassic(p) {
1032
1057
  ] })
1033
1058
  ] });
1034
1059
  }
1035
- function renderLight(p) {
1060
+ function LightTheme(p) {
1036
1061
  const {
1037
1062
  agent,
1038
1063
  state,
@@ -1220,7 +1245,7 @@ function renderLight(p) {
1220
1245
  ] })
1221
1246
  ] });
1222
1247
  }
1223
- function renderDark(p) {
1248
+ function DarkTheme(p) {
1224
1249
  const {
1225
1250
  agent,
1226
1251
  state,
@@ -1457,7 +1482,10 @@ function SageDeskWidget({ mode, indexUrl, endpoint, agent, search: search2 }) {
1457
1482
  '[sagedesk] Required prop "endpoint" is missing for llm mode. Provide your backend route, e.g. endpoint="/api/sagedesk".'
1458
1483
  );
1459
1484
  }
1460
- const config = { mode: resolvedMode, indexUrl, endpoint, agent, search: search2 };
1485
+ const config = (0, import_react2.useMemo)(
1486
+ () => ({ mode: resolvedMode, indexUrl, endpoint, agent, search: search2 }),
1487
+ [resolvedMode, indexUrl, endpoint, agent, search2]
1488
+ );
1461
1489
  const { state, chips, open, close, submit } = useSageDesk(config);
1462
1490
  const theme = agent.theme ?? "classic";
1463
1491
  const accent = agent.accentColor ?? "#534AB7";
@@ -1536,7 +1564,7 @@ function SageDeskWidget({ mode, indexUrl, endpoint, agent, search: search2 }) {
1536
1564
  open,
1537
1565
  submit
1538
1566
  };
1539
- const content = theme === "dark" ? renderDark(props) : theme === "light" ? renderLight(props) : renderClassic(props);
1567
+ const content = theme === "dark" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DarkTheme, { ...props }) : theme === "light" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LightTheme, { ...props }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ClassicTheme, { ...props });
1540
1568
  return (0, import_react_dom.createPortal)(content, document.body);
1541
1569
  }
1542
1570
  // Annotate the CommonJS export names for ESM import in node: