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.
@@ -1,9 +1,10 @@
1
1
  // src/react/SageDeskWidget.tsx
2
- import {
2
+ import React, {
3
3
  useState as useState2,
4
4
  useRef as useRef2,
5
5
  useEffect as useEffect2,
6
- useCallback as useCallback2
6
+ useCallback as useCallback2,
7
+ useMemo as useMemo2
7
8
  } from "react";
8
9
  import { createPortal } from "react-dom";
9
10
 
@@ -263,6 +264,7 @@ function logFallbackWarning(reason) {
263
264
  }
264
265
  var initialState = {
265
266
  messages: [],
267
+ userMessages: [],
266
268
  isOpen: false,
267
269
  isTyping: false,
268
270
  engineStatus: "idle",
@@ -275,8 +277,11 @@ function reducer(state, action) {
275
277
  return { ...state, isOpen: true };
276
278
  case "CLOSE":
277
279
  return { ...state, isOpen: false };
278
- case "ADD_MESSAGE":
279
- return { ...state, messages: [...state.messages, action.payload] };
280
+ case "ADD_MESSAGE": {
281
+ const newMessages = [...state.messages, action.payload];
282
+ const newUserMessages = action.payload.role === "user" ? [...state.userMessages, action.payload] : state.userMessages;
283
+ return { ...state, messages: newMessages, userMessages: newUserMessages };
284
+ }
280
285
  case "SET_TYPING":
281
286
  return { ...state, isTyping: action.payload };
282
287
  case "SET_ENGINE_STATUS":
@@ -299,6 +304,7 @@ function useSageDesk(config) {
299
304
  const embedderRef = useRef(null);
300
305
  const engineStartedRef = useRef(false);
301
306
  const msgCounterRef = useRef(0);
307
+ const engineReadyCallbacksRef = useRef([]);
302
308
  const [chips, setChips] = useState([]);
303
309
  const makeId = () => `msg-${++msgCounterRef.current}`;
304
310
  const addMessage = useCallback(
@@ -310,6 +316,11 @@ function useSageDesk(config) {
310
316
  },
311
317
  []
312
318
  );
319
+ const notifyEngineReady = useCallback(() => {
320
+ const callbacks = engineReadyCallbacksRef.current;
321
+ engineReadyCallbacksRef.current = [];
322
+ callbacks.forEach((cb) => cb());
323
+ }, []);
313
324
  const startEngine = useCallback(async () => {
314
325
  if (engineStartedRef.current) return;
315
326
  engineStartedRef.current = true;
@@ -320,9 +331,11 @@ function useSageDesk(config) {
320
331
  embedderRef.current = new EmbedderRuntime();
321
332
  await embedderRef.current.load(config.agent.model);
322
333
  dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "ready" } });
334
+ notifyEngineReady();
323
335
  } catch (err) {
324
336
  console.warn("[sagedesk] WASM embedder failed to load - LLM mode will use fallback messages.", err);
325
337
  dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "error-model", error: String(err) } });
338
+ notifyEngineReady();
326
339
  }
327
340
  return;
328
341
  }
@@ -335,6 +348,7 @@ function useSageDesk(config) {
335
348
  type: "SET_ENGINE_STATUS",
336
349
  payload: { status: "error-index", error: String(err) }
337
350
  });
351
+ notifyEngineReady();
338
352
  addMessage({
339
353
  role: "bot",
340
354
  text: "I'm having trouble loading right now. Please try again in a moment."
@@ -347,12 +361,14 @@ function useSageDesk(config) {
347
361
  embedderRef.current = new EmbedderRuntime();
348
362
  await embedderRef.current.load(config.agent.model);
349
363
  dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "ready" } });
364
+ notifyEngineReady();
350
365
  } catch (err) {
351
366
  console.warn("[sagedesk] WASM model failed to load, falling back to keyword search -", err);
352
367
  embedderRef.current = new EmbedderRuntime();
353
368
  dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "degraded" } });
369
+ notifyEngineReady();
354
370
  }
355
- }, [config.mode, config.indexUrl, config.agent.model, config.agent.suggestedChips, addMessage]);
371
+ }, [config.mode, config.indexUrl, config.agent.model, config.agent.suggestedChips, addMessage, notifyEngineReady]);
356
372
  const greetingShownRef = useRef(false);
357
373
  const open = useCallback(() => {
358
374
  dispatch({ type: "OPEN" });
@@ -369,16 +385,12 @@ function useSageDesk(config) {
369
385
  dispatch({ type: "CLOSE" });
370
386
  }, []);
371
387
  const waitForEngine = useCallback(() => {
388
+ const s = engineStatusRef.current;
389
+ if (s === "ready" || s === "degraded" || s === "error-index" || s === "error-model") {
390
+ return Promise.resolve();
391
+ }
372
392
  return new Promise((resolve) => {
373
- const check = () => {
374
- const s = engineStatusRef.current;
375
- if (s === "ready" || s === "degraded" || s === "error-index" || s === "error-model") {
376
- resolve();
377
- } else {
378
- setTimeout(check, 100);
379
- }
380
- };
381
- check();
393
+ engineReadyCallbacksRef.current.push(resolve);
382
394
  });
383
395
  }, []);
384
396
  const submit = useCallback(
@@ -459,8 +471,8 @@ function useSageDesk(config) {
459
471
  }
460
472
  if (config.mode !== "llm") {
461
473
  const elapsed = Date.now() - typingStart;
462
- const delayBase = retrievalMode === "keyword" || isFallback ? 800 : 3e3;
463
- const minTypingMs = delayBase + Math.random() * 2e3;
474
+ const delayBase = retrievalMode === "keyword" || isFallback ? 400 : 800;
475
+ const minTypingMs = delayBase + Math.random() * 400;
464
476
  const remaining = minTypingMs - elapsed;
465
477
  if (remaining > 0) await new Promise((r) => setTimeout(r, remaining));
466
478
  }
@@ -478,11 +490,9 @@ function useSageDesk(config) {
478
490
  return () => document.removeEventListener("keydown", handler);
479
491
  }, [state.isOpen, close]);
480
492
  const activeChips = useMemo(() => {
481
- const askedTexts = new Set(
482
- state.messages.filter((m) => m.role === "user").map((m) => m.text.toLowerCase().trim())
483
- );
493
+ const askedTexts = new Set(state.userMessages.map((m) => m.text.toLowerCase().trim()));
484
494
  return chips.filter((chip) => !askedTexts.has(chip.toLowerCase().trim()));
485
- }, [chips, state.messages]);
495
+ }, [chips, state.userMessages]);
486
496
  return { state, chips: activeChips, open, close, submit };
487
497
  }
488
498
 
@@ -678,9 +688,9 @@ var PoweredBy = ({ dark = false }) => /* @__PURE__ */ jsxs("div", { style: {
678
688
  }
679
689
  )
680
690
  ] });
681
- function ClassicMessageBubble({ msg, accent }) {
691
+ var ClassicMessageBubble = React.memo(function ClassicMessageBubble2({ msg, accent }) {
682
692
  const isBot = msg.role === "bot";
683
- const renderedHtml = isBot ? parseMarkdown(msg.text) : msg.text;
693
+ const renderedHtml = useMemo2(() => isBot ? parseMarkdown(msg.text) : msg.text, [isBot, msg.text]);
684
694
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: isBot ? "flex-start" : "flex-end", gap: "4px" }, children: [
685
695
  msg.isFallback && /* @__PURE__ */ jsx("p", { style: { fontSize: "11px", fontWeight: 500, color: "#9b9aa3", margin: 0, padding: "0 4px", fontFamily: "inherit" }, children: "Not sure about that one" }),
686
696
  /* @__PURE__ */ jsx("div", { style: {
@@ -705,7 +715,7 @@ function ClassicMessageBubble({ msg, accent }) {
705
715
  fontFamily: "inherit"
706
716
  }, children: "just now" })
707
717
  ] });
708
- }
718
+ });
709
719
  function ClassicTypingIndicator() {
710
720
  const dot = { width: 6, height: 6, borderRadius: "50%", background: "#c8c8ce", display: "inline-block" };
711
721
  return /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-start", gap: "4px" }, children: /* @__PURE__ */ jsxs("div", { style: {
@@ -723,9 +733,9 @@ function ClassicTypingIndicator() {
723
733
  /* @__PURE__ */ jsx("span", { style: dot, className: "sd-r-dot-3" })
724
734
  ] }) });
725
735
  }
726
- function LightMessageBubble({ msg, accent, agentName }) {
736
+ var LightMessageBubble = React.memo(function LightMessageBubble2({ msg, accent, agentName }) {
727
737
  const isBot = msg.role === "bot";
728
- const renderedHtml = isBot ? parseMarkdown(msg.text) : msg.text;
738
+ const renderedHtml = useMemo2(() => isBot ? parseMarkdown(msg.text) : msg.text, [isBot, msg.text]);
729
739
  if (isBot) {
730
740
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "10px" }, children: [
731
741
  /* @__PURE__ */ jsx("div", { style: {
@@ -760,7 +770,7 @@ function LightMessageBubble({ msg, accent, agentName }) {
760
770
  wordBreak: "break-word",
761
771
  fontFamily: "inherit"
762
772
  }, children: msg.text }) });
763
- }
773
+ });
764
774
  function LightTypingIndicator({ accent }) {
765
775
  const dot = { width: 6, height: 6, borderRadius: "50%", background: "#c4c4be", display: "inline-block" };
766
776
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "10px" }, children: [
@@ -788,9 +798,9 @@ function LightTypingIndicator({ accent }) {
788
798
  ] })
789
799
  ] });
790
800
  }
791
- function DarkMessageBubble({ msg, accent }) {
801
+ var DarkMessageBubble = React.memo(function DarkMessageBubble2({ msg, accent }) {
792
802
  const isBot = msg.role === "bot";
793
- const renderedHtml = isBot ? parseMarkdown(msg.text) : msg.text;
803
+ const renderedHtml = useMemo2(() => isBot ? parseMarkdown(msg.text) : msg.text, [isBot, msg.text]);
794
804
  return /* @__PURE__ */ jsxs("div", { style: {
795
805
  maxWidth: "85%",
796
806
  padding: "12px 14px",
@@ -808,7 +818,7 @@ function DarkMessageBubble({ msg, accent }) {
808
818
  msg.isFallback && /* @__PURE__ */ jsx("span", { style: { fontSize: "11px", color: "rgba(255,255,255,0.5)", display: "block", marginBottom: "4px" }, children: "Not sure about that one" }),
809
819
  isBot ? /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: renderedHtml } }) : /* @__PURE__ */ jsx("div", { style: { whiteSpace: "pre-wrap" }, children: msg.text })
810
820
  ] });
811
- }
821
+ });
812
822
  function DarkTypingIndicator() {
813
823
  const dot = { width: 6, height: 6, borderRadius: "50%", background: "rgba(255,255,255,0.4)", display: "inline-block" };
814
824
  return /* @__PURE__ */ jsxs("div", { style: {
@@ -827,7 +837,7 @@ function DarkTypingIndicator() {
827
837
  /* @__PURE__ */ jsx("span", { style: dot, className: "sd-r-dot-3" })
828
838
  ] });
829
839
  }
830
- function renderClassic(p) {
840
+ function ClassicTheme(p) {
831
841
  const {
832
842
  agent,
833
843
  state,
@@ -1016,7 +1026,7 @@ function renderClassic(p) {
1016
1026
  ] })
1017
1027
  ] });
1018
1028
  }
1019
- function renderLight(p) {
1029
+ function LightTheme(p) {
1020
1030
  const {
1021
1031
  agent,
1022
1032
  state,
@@ -1204,7 +1214,7 @@ function renderLight(p) {
1204
1214
  ] })
1205
1215
  ] });
1206
1216
  }
1207
- function renderDark(p) {
1217
+ function DarkTheme(p) {
1208
1218
  const {
1209
1219
  agent,
1210
1220
  state,
@@ -1441,7 +1451,10 @@ function SageDeskWidget({ mode, indexUrl, endpoint, agent, search: search2 }) {
1441
1451
  '[sagedesk] Required prop "endpoint" is missing for llm mode. Provide your backend route, e.g. endpoint="/api/sagedesk".'
1442
1452
  );
1443
1453
  }
1444
- const config = { mode: resolvedMode, indexUrl, endpoint, agent, search: search2 };
1454
+ const config = useMemo2(
1455
+ () => ({ mode: resolvedMode, indexUrl, endpoint, agent, search: search2 }),
1456
+ [resolvedMode, indexUrl, endpoint, agent, search2]
1457
+ );
1445
1458
  const { state, chips, open, close, submit } = useSageDesk(config);
1446
1459
  const theme = agent.theme ?? "classic";
1447
1460
  const accent = agent.accentColor ?? "#534AB7";
@@ -1520,7 +1533,7 @@ function SageDeskWidget({ mode, indexUrl, endpoint, agent, search: search2 }) {
1520
1533
  open,
1521
1534
  submit
1522
1535
  };
1523
- const content = theme === "dark" ? renderDark(props) : theme === "light" ? renderLight(props) : renderClassic(props);
1536
+ const content = theme === "dark" ? /* @__PURE__ */ jsx(DarkTheme, { ...props }) : theme === "light" ? /* @__PURE__ */ jsx(LightTheme, { ...props }) : /* @__PURE__ */ jsx(ClassicTheme, { ...props });
1524
1537
  return createPortal(content, document.body);
1525
1538
  }
1526
1539
  export {