sagedesk 1.0.0 → 2.1.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.
@@ -4,7 +4,7 @@
4
4
  import { useState, useEffect, Suspense, lazy } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  var LazyWidget = lazy(
7
- () => import("./SageDeskWidget-P3H2VJR5.js").then((mod) => ({ default: mod.SageDeskWidget })).catch((err) => {
7
+ () => import("./SageDeskWidget-SJVE6QK3.js").then((mod) => ({ default: mod.SageDeskWidget })).catch((err) => {
8
8
  console.warn("[sagedesk] Failed to load widget bundle:", err);
9
9
  const Empty = () => null;
10
10
  return { default: Empty };
@@ -13,17 +13,23 @@ var LazyWidget = lazy(
13
13
  function SageDeskNext(props) {
14
14
  const [mounted, setMounted] = useState(false);
15
15
  useEffect(() => {
16
- if (!props.indexUrl) {
16
+ const mode = props.mode ?? "local";
17
+ if (mode === "local" && !props.indexUrl) {
17
18
  console.warn(
18
19
  '[sagedesk] Missing required prop: indexUrl. The widget will not load.\nMake sure you ran `npx sagedesk build` and are passing indexUrl="/support-index.json" (or wherever you placed the output file in public/).'
19
20
  );
20
- } else if (!props.indexUrl.startsWith("/") && !props.indexUrl.startsWith("http")) {
21
+ } else if (mode === "llm" && !props.endpoint) {
22
+ console.warn(
23
+ '[sagedesk] Missing required prop: endpoint for LLM mode. The widget will not load.\nProvide your backend route, e.g. endpoint="/api/sagedesk".'
24
+ );
25
+ }
26
+ if (props.indexUrl && !props.indexUrl.startsWith("/") && !props.indexUrl.startsWith("http")) {
21
27
  console.warn(
22
28
  `[sagedesk] indexUrl "${props.indexUrl}" looks like a relative path. It should start with "/" (e.g. "/support-index.json") so it resolves correctly from any page.`
23
29
  );
24
30
  }
25
31
  setMounted(true);
26
- }, []);
32
+ }, [props.mode, props.indexUrl, props.endpoint]);
27
33
  if (!mounted) return null;
28
34
  return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(LazyWidget, { ...props }) });
29
35
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/next/SageDeskNext.tsx"],"sourcesContent":["'use client';\n\nimport { useState, useEffect, Suspense, lazy } from 'react';\nimport type { SageDeskWidgetProps } from '../react/SageDeskWidget.js';\n\nconst LazyWidget = lazy(() =>\n import('../react/SageDeskWidget.js')\n .then((mod) => ({ default: mod.SageDeskWidget }))\n .catch((err) => {\n console.warn('[sagedesk] Failed to load widget bundle:', err);\n const Empty = () => null;\n return { default: Empty };\n })\n);\n\nexport function SageDeskNext(props: SageDeskWidgetProps) {\n // useState + useEffect ensures server render and initial client hydration\n // both return null (no mismatch). The widget appears only after hydration.\n // Using typeof window directly in render body causes a hydration mismatch\n // in Next.js App Router because 'use client' components still SSR.\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n if (!props.indexUrl) {\n console.warn(\n '[sagedesk] Missing required prop: indexUrl. The widget will not load.\\n' +\n 'Make sure you ran `npx sagedesk build` and are passing indexUrl=\"/support-index.json\" (or wherever you placed the output file in public/).'\n );\n } else if (!props.indexUrl.startsWith('/') && !props.indexUrl.startsWith('http')) {\n console.warn(\n `[sagedesk] indexUrl \"${props.indexUrl}\" looks like a relative path. ` +\n 'It should start with \"/\" (e.g. \"/support-index.json\") so it resolves correctly from any page.'\n );\n }\n setMounted(true);\n }, []);\n\n if (!mounted) return null;\n\n return (\n <Suspense fallback={null}>\n <LazyWidget {...props} />\n </Suspense>\n );\n}\n"],"mappings":";;;AAEA,SAAS,UAAU,WAAW,UAAU,YAAY;AAuC9C;AApCN,IAAM,aAAa;AAAA,EAAK,MACtB,OAAO,8BAA4B,EAChC,KAAK,CAAC,SAAS,EAAE,SAAS,IAAI,eAAe,EAAE,EAC/C,MAAM,CAAC,QAAQ;AACd,YAAQ,KAAK,4CAA4C,GAAG;AAC5D,UAAM,QAAQ,MAAM;AACpB,WAAO,EAAE,SAAS,MAAM;AAAA,EAC1B,CAAC;AACL;AAEO,SAAS,aAAa,OAA4B;AAKvD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,YAAU,MAAM;AACd,QAAI,CAAC,MAAM,UAAU;AACnB,cAAQ;AAAA,QACN;AAAA,MAEF;AAAA,IACF,WAAW,CAAC,MAAM,SAAS,WAAW,GAAG,KAAK,CAAC,MAAM,SAAS,WAAW,MAAM,GAAG;AAChF,cAAQ;AAAA,QACN,wBAAwB,MAAM,QAAQ;AAAA,MAExC;AAAA,IACF;AACA,eAAW,IAAI;AAAA,EACjB,GAAG,CAAC,CAAC;AAEL,MAAI,CAAC,QAAS,QAAO;AAErB,SACE,oBAAC,YAAS,UAAU,MAClB,8BAAC,cAAY,GAAG,OAAO,GACzB;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/next/SageDeskNext.tsx"],"sourcesContent":["'use client';\n\nimport { useState, useEffect, Suspense, lazy } from 'react';\nimport type { SageDeskWidgetProps } from '../react/SageDeskWidget.js';\n\nconst LazyWidget = lazy(() =>\n import('../react/SageDeskWidget.js')\n .then((mod) => ({ default: mod.SageDeskWidget }))\n .catch((err) => {\n console.warn('[sagedesk] Failed to load widget bundle:', err);\n const Empty = () => null;\n return { default: Empty };\n })\n);\n\nexport function SageDeskNext(props: SageDeskWidgetProps) {\n // useState + useEffect ensures server render and initial client hydration\n // both return null (no mismatch). The widget appears only after hydration.\n // Using typeof window directly in render body causes a hydration mismatch\n // in Next.js App Router because 'use client' components still SSR.\n const [mounted, setMounted] = useState(false);\n\n useEffect(() => {\n const mode = props.mode ?? 'local';\n\n if (mode === 'local' && !props.indexUrl) {\n console.warn(\n '[sagedesk] Missing required prop: indexUrl. The widget will not load.\\n' +\n 'Make sure you ran `npx sagedesk build` and are passing indexUrl=\"/support-index.json\" (or wherever you placed the output file in public/).'\n );\n } else if (mode === 'llm' && !props.endpoint) {\n console.warn(\n '[sagedesk] Missing required prop: endpoint for LLM mode. The widget will not load.\\n' +\n 'Provide your backend route, e.g. endpoint=\"/api/sagedesk\".'\n );\n }\n\n if (props.indexUrl && !props.indexUrl.startsWith('/') && !props.indexUrl.startsWith('http')) {\n console.warn(\n `[sagedesk] indexUrl \"${props.indexUrl}\" looks like a relative path. ` +\n 'It should start with \"/\" (e.g. \"/support-index.json\") so it resolves correctly from any page.'\n );\n }\n setMounted(true);\n }, [props.mode, props.indexUrl, props.endpoint]);\n\n if (!mounted) return null;\n\n return (\n <Suspense fallback={null}>\n <LazyWidget {...props} />\n </Suspense>\n );\n}\n"],"mappings":";;;AAEA,SAAS,UAAU,WAAW,UAAU,YAAY;AAgD9C;AA7CN,IAAM,aAAa;AAAA,EAAK,MACtB,OAAO,8BAA4B,EAChC,KAAK,CAAC,SAAS,EAAE,SAAS,IAAI,eAAe,EAAE,EAC/C,MAAM,CAAC,QAAQ;AACd,YAAQ,KAAK,4CAA4C,GAAG;AAC5D,UAAM,QAAQ,MAAM;AACpB,WAAO,EAAE,SAAS,MAAM;AAAA,EAC1B,CAAC;AACL;AAEO,SAAS,aAAa,OAA4B;AAKvD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAE5C,YAAU,MAAM;AACd,UAAM,OAAO,MAAM,QAAQ;AAE3B,QAAI,SAAS,WAAW,CAAC,MAAM,UAAU;AACvC,cAAQ;AAAA,QACN;AAAA,MAEF;AAAA,IACF,WAAW,SAAS,SAAS,CAAC,MAAM,UAAU;AAC5C,cAAQ;AAAA,QACN;AAAA,MAEF;AAAA,IACF;AAEA,QAAI,MAAM,YAAY,CAAC,MAAM,SAAS,WAAW,GAAG,KAAK,CAAC,MAAM,SAAS,WAAW,MAAM,GAAG;AAC3F,cAAQ;AAAA,QACN,wBAAwB,MAAM,QAAQ;AAAA,MAExC;AAAA,IACF;AACA,eAAW,IAAI;AAAA,EACjB,GAAG,CAAC,MAAM,MAAM,MAAM,UAAU,MAAM,QAAQ,CAAC;AAE/C,MAAI,CAAC,QAAS,QAAO;AAErB,SACE,oBAAC,YAAS,UAAU,MAClB,8BAAC,cAAY,GAAG,OAAO,GACzB;AAEJ;","names":[]}
@@ -126,17 +126,33 @@ function dotProduct(a, b) {
126
126
  for (let i = 0; i < a.length; i++) dot += a[i] * b[i];
127
127
  return dot;
128
128
  }
129
+ function insertSorted(arr, item, maxLen) {
130
+ arr.push(item);
131
+ let i = arr.length - 1;
132
+ while (i > 0 && arr[i - 1].score < arr[i].score) {
133
+ const tmp = arr[i - 1];
134
+ arr[i - 1] = arr[i];
135
+ arr[i] = tmp;
136
+ i--;
137
+ }
138
+ if (arr.length > maxLen) arr.pop();
139
+ }
129
140
  function search(queryVector, index, topK = 3, minScore = 0.42) {
130
141
  const results = [];
131
142
  for (const chunk of index) {
132
143
  const score = dotProduct(queryVector, chunk.vector384);
133
144
  if (score < minScore) continue;
134
145
  if (results.length < topK) {
135
- results.push({ chunk, score });
136
- results.sort((a, b) => b.score - a.score);
146
+ insertSorted(results, { chunk, score }, topK);
137
147
  } else if (score > results[topK - 1].score) {
138
148
  results[topK - 1] = { chunk, score };
139
- results.sort((a, b) => b.score - a.score);
149
+ let i = topK - 1;
150
+ while (i > 0 && results[i - 1].score < results[i].score) {
151
+ const tmp = results[i - 1];
152
+ results[i - 1] = results[i];
153
+ results[i] = tmp;
154
+ i--;
155
+ }
140
156
  }
141
157
  }
142
158
  return results;
@@ -147,15 +163,23 @@ function keywordSearch(query, index, topK = 3) {
147
163
  const results = [];
148
164
  for (const chunk of index) {
149
165
  const chunkLower = chunk.textLower || chunk.text.toLowerCase();
150
- const matchCount = terms.filter((t) => chunkLower.includes(t)).length;
166
+ let matchCount = 0;
167
+ for (const t of terms) {
168
+ if (chunkLower.includes(t)) matchCount++;
169
+ }
151
170
  const score = matchCount / terms.length;
152
171
  if (score <= 0) continue;
153
172
  if (results.length < topK) {
154
- results.push({ chunk, score });
155
- results.sort((a, b) => b.score - a.score);
173
+ insertSorted(results, { chunk, score }, topK);
156
174
  } else if (score > results[topK - 1].score) {
157
175
  results[topK - 1] = { chunk, score };
158
- results.sort((a, b) => b.score - a.score);
176
+ let i = topK - 1;
177
+ while (i > 0 && results[i - 1].score < results[i].score) {
178
+ const tmp = results[i - 1];
179
+ results[i - 1] = results[i];
180
+ results[i] = tmp;
181
+ i--;
182
+ }
159
183
  }
160
184
  }
161
185
  return results;
@@ -258,6 +282,17 @@ function getFallback(config) {
258
282
  }
259
283
 
260
284
  // src/react/useSageDesk.ts
285
+ function logFallbackWarning(reason) {
286
+ if (!reason) return;
287
+ const messages = {
288
+ "auth-error": "[sagedesk] Support service authentication failed. Showing relevant knowledge instead.",
289
+ "quota-exceeded": "[sagedesk] Support service quota exhausted. Showing relevant knowledge instead.",
290
+ "timeout": "[sagedesk] Support service took too long to respond. Showing relevant knowledge instead.",
291
+ "api-error": "[sagedesk] Support service error. Showing relevant knowledge instead.",
292
+ "malformed-response": "[sagedesk] Support service returned invalid response. Showing relevant knowledge instead."
293
+ };
294
+ console.warn(messages[reason] || "[sagedesk] Support service unavailable. Showing relevant knowledge instead.");
295
+ }
261
296
  var initialState = {
262
297
  messages: [],
263
298
  isOpen: false,
@@ -310,6 +345,11 @@ function useSageDesk(config) {
310
345
  const startEngine = (0, import_react.useCallback)(async () => {
311
346
  if (engineStartedRef.current) return;
312
347
  engineStartedRef.current = true;
348
+ if (config.mode === "llm") {
349
+ setChips(config.agent.suggestedChips ?? []);
350
+ dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "ready" } });
351
+ return;
352
+ }
313
353
  dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "loading-index" } });
314
354
  try {
315
355
  indexRef.current = await fetchIndex(config.indexUrl);
@@ -336,7 +376,7 @@ function useSageDesk(config) {
336
376
  embedderRef.current = new EmbedderRuntime();
337
377
  dispatch({ type: "SET_ENGINE_STATUS", payload: { status: "degraded" } });
338
378
  }
339
- }, [config.indexUrl, config.agent.suggestedChips, addMessage]);
379
+ }, [config.mode, config.indexUrl, config.agent.suggestedChips, addMessage]);
340
380
  const greetingShownRef = (0, import_react.useRef)(false);
341
381
  const open = (0, import_react.useCallback)(() => {
342
382
  dispatch({ type: "OPEN" });
@@ -379,8 +419,37 @@ function useSageDesk(config) {
379
419
  }
380
420
  let botText;
381
421
  let isFallback = false;
382
- let mode = "keyword";
383
- if (!indexRef.current) {
422
+ let fallbackReason;
423
+ let retrievalMode = "keyword";
424
+ if (config.mode === "llm") {
425
+ if (!config.endpoint) {
426
+ console.warn('[sagedesk] LLM mode requires an "endpoint" prop.');
427
+ botText = getFallback(config.agent);
428
+ isFallback = true;
429
+ } else {
430
+ try {
431
+ const res = await fetch(config.endpoint, {
432
+ method: "POST",
433
+ headers: { "Content-Type": "application/json" },
434
+ body: JSON.stringify({ query: trimmed })
435
+ });
436
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
437
+ const data = await res.json();
438
+ if (data.isFallback || !data.answer) {
439
+ fallbackReason = data.fallbackReason;
440
+ logFallbackWarning(fallbackReason);
441
+ botText = getFallback(config.agent);
442
+ isFallback = true;
443
+ } else {
444
+ botText = data.answer;
445
+ }
446
+ } catch (err) {
447
+ console.warn("[sagedesk] Support service unavailable. Using cached knowledge instead.");
448
+ botText = getFallback(config.agent);
449
+ isFallback = true;
450
+ }
451
+ }
452
+ } else if (!indexRef.current) {
384
453
  botText = getFallback(config.agent);
385
454
  isFallback = true;
386
455
  } else {
@@ -391,7 +460,7 @@ function useSageDesk(config) {
391
460
  embedderRef.current,
392
461
  config.search
393
462
  );
394
- mode = res.mode;
463
+ retrievalMode = res.mode;
395
464
  if (res.results.length > 0) {
396
465
  botText = buildAnswer(res.results);
397
466
  } else {
@@ -404,11 +473,13 @@ function useSageDesk(config) {
404
473
  isFallback = true;
405
474
  }
406
475
  }
407
- const elapsed = Date.now() - typingStart;
408
- const delayBase = mode === "keyword" || isFallback ? 800 : 3e3;
409
- const minTypingMs = delayBase + Math.random() * 2e3;
410
- const remaining = minTypingMs - elapsed;
411
- if (remaining > 0) await new Promise((r) => setTimeout(r, remaining));
476
+ if (config.mode !== "llm") {
477
+ const elapsed = Date.now() - typingStart;
478
+ const delayBase = retrievalMode === "keyword" || isFallback ? 800 : 3e3;
479
+ const minTypingMs = delayBase + Math.random() * 2e3;
480
+ const remaining = minTypingMs - elapsed;
481
+ if (remaining > 0) await new Promise((r) => setTimeout(r, remaining));
482
+ }
412
483
  dispatch({ type: "SET_TYPING", payload: false });
413
484
  addMessage({ role: "bot", text: botText, isFallback });
414
485
  },
@@ -431,6 +502,51 @@ function useSageDesk(config) {
431
502
  return { state, chips: activeChips, open, close, submit };
432
503
  }
433
504
 
505
+ // src/react/markdownUtils.ts
506
+ var import_marked = require("marked");
507
+ var import_dompurify = __toESM(require("dompurify"), 1);
508
+ import_marked.marked.setOptions({
509
+ breaks: true,
510
+ gfm: true,
511
+ pedantic: false
512
+ });
513
+ var PURIFY_CONFIG = {
514
+ ALLOWED_TAGS: [
515
+ "p",
516
+ "br",
517
+ "strong",
518
+ "em",
519
+ "u",
520
+ "h1",
521
+ "h2",
522
+ "h3",
523
+ "h4",
524
+ "h5",
525
+ "h6",
526
+ "ul",
527
+ "ol",
528
+ "li",
529
+ "blockquote",
530
+ "code",
531
+ "pre",
532
+ "a",
533
+ "hr"
534
+ ],
535
+ ALLOWED_ATTR: ["href", "title", "target", "rel"],
536
+ ALLOW_DATA_ATTR: false
537
+ };
538
+ import_dompurify.default.addHook("afterSanitizeAttributes", (node) => {
539
+ if (node.tagName.toLowerCase() === "a") {
540
+ node.setAttribute("target", "_blank");
541
+ node.setAttribute("rel", "noopener noreferrer");
542
+ }
543
+ });
544
+ function parseMarkdown(markdown) {
545
+ const html = import_marked.marked.parse(markdown);
546
+ const sanitized = import_dompurify.default.sanitize(html, PURIFY_CONFIG);
547
+ return sanitized;
548
+ }
549
+
434
550
  // src/react/SageDeskWidget.tsx
435
551
  var import_jsx_runtime = require("react/jsx-runtime");
436
552
  var STYLE_ID = "sagedesk-widget-styles";
@@ -460,6 +576,28 @@ var SHARED = `
460
576
  }
461
577
  .sd-r-scrollable::-webkit-scrollbar { display: none !important; }
462
578
  .sd-r-scrollable > * { flex-shrink: 0 !important; }
579
+ .sd-r-markdown h1, .sd-r-markdown h2, .sd-r-markdown h3, .sd-r-markdown h4, .sd-r-markdown h5, .sd-r-markdown h6 {
580
+ margin: 12px 0 8px 0 !important; font-weight: 600 !important; line-height: 1.3 !important;
581
+ }
582
+ .sd-r-markdown h1 { font-size: 1.3em !important; }
583
+ .sd-r-markdown h2 { font-size: 1.2em !important; }
584
+ .sd-r-markdown h3 { font-size: 1.1em !important; }
585
+ .sd-r-markdown h4, .sd-r-markdown h5, .sd-r-markdown h6 { font-size: 1em !important; }
586
+ .sd-r-markdown strong { font-weight: 600 !important; }
587
+ .sd-r-markdown em { font-style: italic !important; }
588
+ .sd-r-markdown u { text-decoration: underline !important; }
589
+ .sd-r-markdown ul, .sd-r-markdown ol { margin: 8px 0 !important; padding-left: 20px !important; }
590
+ .sd-r-markdown li { margin: 4px 0 !important; }
591
+ .sd-r-markdown blockquote { margin: 8px 0 !important; padding-left: 12px !important; border-left: 3px solid currentColor !important; opacity: 0.8 !important; }
592
+ .sd-r-markdown code { font-family: 'Monaco', 'Courier New', monospace !important; font-size: 0.9em !important; padding: 2px 4px !important; background: rgba(0,0,0,0.05) !important; border-radius: 3px !important; }
593
+ .sd-r-markdown pre { background: rgba(0,0,0,0.05) !important; padding: 8px 10px !important; border-radius: 6px !important; overflow-x: auto !important; margin: 8px 0 !important; }
594
+ .sd-r-markdown pre code { background: none !important; padding: 0 !important; }
595
+ .sd-r-markdown hr { border: none !important; border-top: 1px solid currentColor !important; opacity: 0.3 !important; margin: 10px 0 !important; }
596
+ .sd-r-markdown a { text-decoration: underline !important; opacity: 0.9 !important; }
597
+ .sd-r-markdown a:hover { opacity: 1 !important; }
598
+ .sd-r-markdown p { margin: 6px 0 !important; }
599
+ .sd-r-markdown > *:first-child { margin-top: 0 !important; }
600
+ .sd-r-markdown > *:last-child { margin-bottom: 0 !important; }
463
601
  @media (max-width: 420px) {
464
602
  .sd-r-panel {
465
603
  bottom: 0 !important; right: 0 !important; left: 0 !important;
@@ -546,7 +684,7 @@ var PoweredBy = ({ dark = false }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx
546
684
  {
547
685
  href: "https://github.com/mzeeshanwahid/sagedesk",
548
686
  target: "_blank",
549
- rel: "noopener noreferrer",
687
+ rel: "noopener",
550
688
  style: {
551
689
  color: dark ? "rgba(255,255,255,0.7)" : "#5a5a64",
552
690
  fontWeight: 500,
@@ -558,6 +696,7 @@ var PoweredBy = ({ dark = false }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx
558
696
  ] });
559
697
  function ClassicMessageBubble({ msg, accent }) {
560
698
  const isBot = msg.role === "bot";
699
+ const renderedHtml = isBot ? parseMarkdown(msg.text) : msg.text;
561
700
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", flexDirection: "column", alignItems: isBot ? "flex-start" : "flex-end", gap: "4px" }, children: [
562
701
  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" }),
563
702
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
@@ -570,10 +709,9 @@ function ClassicMessageBubble({ msg, accent }) {
570
709
  color: isBot ? "#1a1a2e" : "#fff",
571
710
  border: isBot ? "1px solid rgba(20,20,40,0.06)" : "none",
572
711
  boxShadow: isBot ? "0 1px 2px rgba(20,20,40,0.04)" : `0 6px 16px -6px color-mix(in oklab, ${accent} 60%, transparent)`,
573
- whiteSpace: "pre-wrap",
574
712
  wordBreak: "break-word",
575
713
  fontFamily: "inherit"
576
- }, children: msg.text }),
714
+ }, className: "sd-r-markdown", children: 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 }) }),
577
715
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: {
578
716
  fontSize: "11px",
579
717
  color: "#a8a8b0",
@@ -603,6 +741,7 @@ function ClassicTypingIndicator() {
603
741
  }
604
742
  function LightMessageBubble({ msg, accent, agentName }) {
605
743
  const isBot = msg.role === "bot";
744
+ const renderedHtml = isBot ? parseMarkdown(msg.text) : msg.text;
606
745
  if (isBot) {
607
746
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: "10px" }, children: [
608
747
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
@@ -619,7 +758,7 @@ function LightMessageBubble({ msg, accent, agentName }) {
619
758
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "11px", color: "#a8a89e", fontVariantNumeric: "tabular-nums", fontFamily: "inherit" }, children: "just now" })
620
759
  ] }),
621
760
  msg.isFallback && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { fontSize: "11px", color: "#9b9aa3", margin: "0 0 4px", fontFamily: "inherit" }, children: "Not sure about that one" }),
622
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: "14px", lineHeight: 1.55, color: "#2a2a36", fontFamily: "inherit", whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: msg.text })
761
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: "14px", lineHeight: 1.55, color: "#2a2a36", fontFamily: "inherit", wordBreak: "break-word" }, className: "sd-r-markdown", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: renderedHtml } }) })
623
762
  ] })
624
763
  ] });
625
764
  }
@@ -667,6 +806,7 @@ function LightTypingIndicator({ accent }) {
667
806
  }
668
807
  function DarkMessageBubble({ msg, accent }) {
669
808
  const isBot = msg.role === "bot";
809
+ const renderedHtml = isBot ? parseMarkdown(msg.text) : msg.text;
670
810
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: {
671
811
  maxWidth: "85%",
672
812
  padding: "12px 14px",
@@ -678,12 +818,11 @@ function DarkMessageBubble({ msg, accent }) {
678
818
  color: isBot ? "rgba(255,255,255,0.92)" : "#fff",
679
819
  alignSelf: isBot ? "flex-start" : "flex-end",
680
820
  boxShadow: isBot ? "none" : `0 8px 20px -8px color-mix(in oklab, ${accent} 70%, transparent)`,
681
- whiteSpace: "pre-wrap",
682
821
  wordBreak: "break-word",
683
822
  fontFamily: "inherit"
684
- }, children: [
823
+ }, className: isBot ? "sd-r-markdown" : "", children: [
685
824
  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" }),
686
- msg.text
825
+ 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 })
687
826
  ] });
688
827
  }
689
828
  function DarkTypingIndicator() {
@@ -1303,16 +1442,22 @@ function renderDark(p) {
1303
1442
  ] })
1304
1443
  ] });
1305
1444
  }
1306
- function SageDeskWidget({ indexUrl, agent, search: search2 }) {
1307
- if (!indexUrl) {
1445
+ function SageDeskWidget({ mode, indexUrl, endpoint, agent, search: search2 }) {
1446
+ const resolvedMode = mode ?? "local";
1447
+ if (!agent?.name) {
1448
+ throw new Error('[sagedesk] Required prop "agent.name" is missing.');
1449
+ }
1450
+ if (resolvedMode === "local" && !indexUrl) {
1308
1451
  throw new Error(
1309
- '[sagedesk] Required prop "indexUrl" is missing. Run `npx sagedesk build` and pass the output path, e.g. indexUrl="/support-index.json".'
1452
+ '[sagedesk] Required prop "indexUrl" is missing for local mode. Run `npx sagedesk build` and pass the output path, e.g. indexUrl="/support-index.json".'
1310
1453
  );
1311
1454
  }
1312
- if (!agent?.name) {
1313
- throw new Error('[sagedesk] Required prop "agent.name" is missing.');
1455
+ if (resolvedMode === "llm" && !endpoint) {
1456
+ throw new Error(
1457
+ '[sagedesk] Required prop "endpoint" is missing for llm mode. Provide your backend route, e.g. endpoint="/api/sagedesk".'
1458
+ );
1314
1459
  }
1315
- const config = { indexUrl, agent, search: search2 };
1460
+ const config = { mode: resolvedMode, indexUrl, endpoint, agent, search: search2 };
1316
1461
  const { state, chips, open, close, submit } = useSageDesk(config);
1317
1462
  const theme = agent.theme ?? "classic";
1318
1463
  const accent = agent.accentColor ?? "#534AB7";
@@ -1325,7 +1470,7 @@ function SageDeskWidget({ indexUrl, agent, search: search2 }) {
1325
1470
  const triggerRef = (0, import_react2.useRef)(null);
1326
1471
  const [mounted, setMounted] = (0, import_react2.useState)(false);
1327
1472
  (0, import_react2.useEffect)(() => {
1328
- if (!indexUrl.startsWith("/") && !indexUrl.startsWith("http")) {
1473
+ if (resolvedMode === "local" && indexUrl && !indexUrl.startsWith("/") && !indexUrl.startsWith("http")) {
1329
1474
  console.warn(
1330
1475
  `[sagedesk] indexUrl "${indexUrl}" looks like a relative path. It should start with "/" so it resolves correctly from any page.`
1331
1476
  );
@@ -1367,7 +1512,7 @@ function SageDeskWidget({ indexUrl, agent, search: search2 }) {
1367
1512
  [handleSubmit]
1368
1513
  );
1369
1514
  if (!mounted || typeof document === "undefined") return null;
1370
- const showPoweredBy = agent.poweredBy !== false;
1515
+ const showPoweredBy = true;
1371
1516
  const showChips = chips.length > 0;
1372
1517
  const panelClass = isClosing ? "sd-r-closing" : state.isOpen ? "sd-r-opening" : "";
1373
1518
  const props = {