sagedesk 2.2.0 → 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,6 +347,11 @@ 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;
@@ -352,9 +362,11 @@ function useSageDesk(config) {
352
362
  embedderRef.current = new EmbedderRuntime();
353
363
  await embedderRef.current.load(config.agent.model);
354
364
  dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "ready" } });
365
+ notifyEngineReady();
355
366
  } catch (err) {
356
367
  console.warn("[sagedesk] WASM embedder failed to load - LLM mode will use fallback messages.", err);
357
368
  dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "error-model", error: String(err) } });
369
+ notifyEngineReady();
358
370
  }
359
371
  return;
360
372
  }
@@ -367,6 +379,7 @@ function useSageDesk(config) {
367
379
  type: "SET_ENGINE_STATUS",
368
380
  payload: { status: "error-index", error: String(err) }
369
381
  });
382
+ notifyEngineReady();
370
383
  addMessage({
371
384
  role: "bot",
372
385
  text: "I'm having trouble loading right now. Please try again in a moment."
@@ -379,12 +392,14 @@ function useSageDesk(config) {
379
392
  embedderRef.current = new EmbedderRuntime();
380
393
  await embedderRef.current.load(config.agent.model);
381
394
  dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "ready" } });
395
+ notifyEngineReady();
382
396
  } catch (err) {
383
397
  console.warn("[sagedesk] WASM model failed to load, falling back to keyword search -", err);
384
398
  embedderRef.current = new EmbedderRuntime();
385
399
  dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "degraded" } });
400
+ notifyEngineReady();
386
401
  }
387
- }, [config.mode, config.indexUrl, config.agent.model, config.agent.suggestedChips, addMessage]);
402
+ }, [config.mode, config.indexUrl, config.agent.model, config.agent.suggestedChips, addMessage, notifyEngineReady]);
388
403
  const greetingShownRef = (0, import_react.useRef)(false);
389
404
  const open = (0, import_react.useCallback)(() => {
390
405
  dispatch({ type: "OPEN" });
@@ -401,16 +416,12 @@ function useSageDesk(config) {
401
416
  dispatch({ type: "CLOSE" });
402
417
  }, []);
403
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
+ }
404
423
  return new Promise((resolve) => {
405
- const check = () => {
406
- const s = engineStatusRef.current;
407
- if (s === "ready" || s === "degraded" || s === "error-index" || s === "error-model") {
408
- resolve();
409
- } else {
410
- setTimeout(check, 100);
411
- }
412
- };
413
- check();
424
+ engineReadyCallbacksRef.current.push(resolve);
414
425
  });
415
426
  }, []);
416
427
  const submit = (0, import_react.useCallback)(
@@ -491,8 +502,8 @@ function useSageDesk(config) {
491
502
  }
492
503
  if (config.mode !== "llm") {
493
504
  const elapsed = Date.now() - typingStart;
494
- const delayBase = retrievalMode === "keyword" || isFallback ? 800 : 3e3;
495
- const minTypingMs = delayBase + Math.random() * 2e3;
505
+ const delayBase = retrievalMode === "keyword" || isFallback ? 400 : 800;
506
+ const minTypingMs = delayBase + Math.random() * 400;
496
507
  const remaining = minTypingMs - elapsed;
497
508
  if (remaining > 0) await new Promise((r) => setTimeout(r, remaining));
498
509
  }
@@ -510,11 +521,9 @@ function useSageDesk(config) {
510
521
  return () => document.removeEventListener("keydown", handler);
511
522
  }, [state.isOpen, close]);
512
523
  const activeChips = (0, import_react.useMemo)(() => {
513
- const askedTexts = new Set(
514
- state.messages.filter((m) => m.role === "user").map((m) => m.text.toLowerCase().trim())
515
- );
524
+ const askedTexts = new Set(state.userMessages.map((m) => m.text.toLowerCase().trim()));
516
525
  return chips.filter((chip) => !askedTexts.has(chip.toLowerCase().trim()));
517
- }, [chips, state.messages]);
526
+ }, [chips, state.userMessages]);
518
527
  return { state, chips: activeChips, open, close, submit };
519
528
  }
520
529
 
@@ -710,9 +719,9 @@ var PoweredBy = ({ dark = false }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx
710
719
  }
711
720
  )
712
721
  ] });
713
- function ClassicMessageBubble({ msg, accent }) {
722
+ var ClassicMessageBubble = import_react2.default.memo(function ClassicMessageBubble2({ msg, accent }) {
714
723
  const isBot = msg.role === "bot";
715
- const renderedHtml = isBot ? parseMarkdown(msg.text) : msg.text;
724
+ const renderedHtml = (0, import_react2.useMemo)(() => isBot ? parseMarkdown(msg.text) : msg.text, [isBot, msg.text]);
716
725
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", alignItems: isBot ? "flex-start" : "flex-end", gap: "4px" }, children: [
717
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" }),
718
727
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
@@ -737,7 +746,7 @@ function ClassicMessageBubble({ msg, accent }) {
737
746
  fontFamily: "inherit"
738
747
  }, children: "just now" })
739
748
  ] });
740
- }
749
+ });
741
750
  function ClassicTypingIndicator() {
742
751
  const dot = { width: 6, height: 6, borderRadius: "50%", background: "#c8c8ce", display: "inline-block" };
743
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: {
@@ -755,9 +764,9 @@ function ClassicTypingIndicator() {
755
764
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: dot, className: "sd-r-dot-3" })
756
765
  ] }) });
757
766
  }
758
- function LightMessageBubble({ msg, accent, agentName }) {
767
+ var LightMessageBubble = import_react2.default.memo(function LightMessageBubble2({ msg, accent, agentName }) {
759
768
  const isBot = msg.role === "bot";
760
- const renderedHtml = isBot ? parseMarkdown(msg.text) : msg.text;
769
+ const renderedHtml = (0, import_react2.useMemo)(() => isBot ? parseMarkdown(msg.text) : msg.text, [isBot, msg.text]);
761
770
  if (isBot) {
762
771
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: "10px" }, children: [
763
772
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
@@ -792,7 +801,7 @@ function LightMessageBubble({ msg, accent, agentName }) {
792
801
  wordBreak: "break-word",
793
802
  fontFamily: "inherit"
794
803
  }, children: msg.text }) });
795
- }
804
+ });
796
805
  function LightTypingIndicator({ accent }) {
797
806
  const dot = { width: 6, height: 6, borderRadius: "50%", background: "#c4c4be", display: "inline-block" };
798
807
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: "10px" }, children: [
@@ -820,9 +829,9 @@ function LightTypingIndicator({ accent }) {
820
829
  ] })
821
830
  ] });
822
831
  }
823
- function DarkMessageBubble({ msg, accent }) {
832
+ var DarkMessageBubble = import_react2.default.memo(function DarkMessageBubble2({ msg, accent }) {
824
833
  const isBot = msg.role === "bot";
825
- const renderedHtml = isBot ? parseMarkdown(msg.text) : msg.text;
834
+ const renderedHtml = (0, import_react2.useMemo)(() => isBot ? parseMarkdown(msg.text) : msg.text, [isBot, msg.text]);
826
835
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
827
836
  maxWidth: "85%",
828
837
  padding: "12px 14px",
@@ -840,7 +849,7 @@ function DarkMessageBubble({ msg, accent }) {
840
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" }),
841
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 })
842
851
  ] });
843
- }
852
+ });
844
853
  function DarkTypingIndicator() {
845
854
  const dot = { width: 6, height: 6, borderRadius: "50%", background: "rgba(255,255,255,0.4)", display: "inline-block" };
846
855
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
@@ -859,7 +868,7 @@ function DarkTypingIndicator() {
859
868
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: dot, className: "sd-r-dot-3" })
860
869
  ] });
861
870
  }
862
- function renderClassic(p) {
871
+ function ClassicTheme(p) {
863
872
  const {
864
873
  agent,
865
874
  state,
@@ -1048,7 +1057,7 @@ function renderClassic(p) {
1048
1057
  ] })
1049
1058
  ] });
1050
1059
  }
1051
- function renderLight(p) {
1060
+ function LightTheme(p) {
1052
1061
  const {
1053
1062
  agent,
1054
1063
  state,
@@ -1236,7 +1245,7 @@ function renderLight(p) {
1236
1245
  ] })
1237
1246
  ] });
1238
1247
  }
1239
- function renderDark(p) {
1248
+ function DarkTheme(p) {
1240
1249
  const {
1241
1250
  agent,
1242
1251
  state,
@@ -1473,7 +1482,10 @@ function SageDeskWidget({ mode, indexUrl, endpoint, agent, search: search2 }) {
1473
1482
  '[sagedesk] Required prop "endpoint" is missing for llm mode. Provide your backend route, e.g. endpoint="/api/sagedesk".'
1474
1483
  );
1475
1484
  }
1476
- 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
+ );
1477
1489
  const { state, chips, open, close, submit } = useSageDesk(config);
1478
1490
  const theme = agent.theme ?? "classic";
1479
1491
  const accent = agent.accentColor ?? "#534AB7";
@@ -1552,7 +1564,7 @@ function SageDeskWidget({ mode, indexUrl, endpoint, agent, search: search2 }) {
1552
1564
  open,
1553
1565
  submit
1554
1566
  };
1555
- 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 });
1556
1568
  return (0, import_react_dom.createPortal)(content, document.body);
1557
1569
  }
1558
1570
  // Annotate the CommonJS export names for ESM import in node: