@paymanai/payman-ask-sdk 4.0.9 → 4.0.11

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/index.js CHANGED
@@ -1521,6 +1521,7 @@ var RAG_IMAGE_PATH_REGEX2 = /^(?:https?:\/\/[^/\s]+)?\/api\/rag\/chunks\/[^"'\s]
1521
1521
  function isUnresolvedRagImageSource2(src) {
1522
1522
  return RAG_IMAGE_PATH_REGEX2.test(src);
1523
1523
  }
1524
+ var loadedImageCache = /* @__PURE__ */ new Set();
1524
1525
  var frameStyle = {
1525
1526
  width: "min(100%, 32rem)",
1526
1527
  maxWidth: "100%"
@@ -1539,24 +1540,36 @@ function MarkdownImageV2({
1539
1540
  isResolving = false,
1540
1541
  onImageClick
1541
1542
  }) {
1542
- const [isVisible, setIsVisible] = react.useState(false);
1543
- const [isLoaded, setIsLoaded] = react.useState(false);
1543
+ const cachedAlready = src ? loadedImageCache.has(src) : false;
1544
+ const [isVisible, setIsVisible] = react.useState(cachedAlready);
1545
+ const [isLoaded, setIsLoaded] = react.useState(cachedAlready);
1544
1546
  const [hasError, setHasError] = react.useState(false);
1545
1547
  const [retryCount, setRetryCount] = react.useState(0);
1546
1548
  const sentinelRef = react.useRef(null);
1549
+ const prevLoadedRef = react.useRef(cachedAlready);
1547
1550
  const isUnresolvedRag = react.useMemo(
1548
1551
  () => src ? isUnresolvedRagImageSource2(src) : false,
1549
1552
  [src]
1550
1553
  );
1551
1554
  const isResolvingRag = Boolean(isResolving && isUnresolvedRag);
1552
1555
  react.useEffect(() => {
1553
- setIsLoaded(false);
1556
+ if (src && loadedImageCache.has(src)) {
1557
+ setIsLoaded(true);
1558
+ setIsVisible(true);
1559
+ prevLoadedRef.current = true;
1560
+ }
1554
1561
  setHasError(false);
1555
1562
  setRetryCount(0);
1556
1563
  }, [src]);
1557
1564
  react.useEffect(() => {
1558
1565
  const el = sentinelRef.current;
1559
1566
  if (!el || !src) return;
1567
+ const rect = el.getBoundingClientRect();
1568
+ const alreadyNear = rect.bottom >= -200 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) + 200;
1569
+ if (alreadyNear) {
1570
+ setIsVisible(true);
1571
+ return;
1572
+ }
1560
1573
  const observer = new IntersectionObserver(
1561
1574
  ([entry]) => {
1562
1575
  if (entry.isIntersecting) {
@@ -1570,7 +1583,7 @@ function MarkdownImageV2({
1570
1583
  return () => observer.disconnect();
1571
1584
  }, [src]);
1572
1585
  if (!src) return null;
1573
- if (isResolvingRag) {
1586
+ if (isResolvingRag && !isLoaded) {
1574
1587
  return /* @__PURE__ */ jsxRuntime.jsx(
1575
1588
  "span",
1576
1589
  {
@@ -1645,10 +1658,13 @@ function MarkdownImageV2({
1645
1658
  opacity: isLoaded ? 1 : 0
1646
1659
  },
1647
1660
  onLoad: () => {
1661
+ if (src) loadedImageCache.add(src);
1662
+ prevLoadedRef.current = true;
1648
1663
  setHasError(false);
1649
1664
  setIsLoaded(true);
1650
1665
  },
1651
1666
  onError: () => {
1667
+ prevLoadedRef.current = false;
1652
1668
  setIsLoaded(false);
1653
1669
  setHasError(true);
1654
1670
  }
@@ -1666,7 +1682,7 @@ function MarkdownImageV2({
1666
1682
  }
1667
1683
  ) });
1668
1684
  }
1669
- function buildComponents(onImageClick, isResolvingImages) {
1685
+ function buildComponents(onImageClick, isResolvingRef) {
1670
1686
  return {
1671
1687
  p: ({ children }) => /* @__PURE__ */ jsxRuntime.jsx("p", { children }),
1672
1688
  code: ({ children }) => /* @__PURE__ */ jsxRuntime.jsx("code", { children }),
@@ -1687,7 +1703,7 @@ function buildComponents(onImageClick, isResolvingImages) {
1687
1703
  {
1688
1704
  src: typeof src === "string" ? src : void 0,
1689
1705
  alt: typeof alt === "string" ? alt : void 0,
1690
- isResolving: isResolvingImages,
1706
+ isResolving: isResolvingRef?.current,
1691
1707
  onImageClick
1692
1708
  }
1693
1709
  ),
@@ -1704,9 +1720,11 @@ function MarkdownRendererV2({
1704
1720
  isResolvingImages,
1705
1721
  onImageClick
1706
1722
  }) {
1723
+ const isResolvingRef = react.useRef(isResolvingImages);
1724
+ isResolvingRef.current = isResolvingImages;
1707
1725
  const components = react.useMemo(
1708
- () => buildComponents(onImageClick, isResolvingImages),
1709
- [onImageClick, isResolvingImages]
1726
+ () => buildComponents(onImageClick, isResolvingRef),
1727
+ [onImageClick]
1710
1728
  );
1711
1729
  return /* @__PURE__ */ jsxRuntime.jsx(
1712
1730
  "div",
@@ -1978,6 +1996,7 @@ var RESPONSE_SPEED = {
1978
1996
  newline: [4, 6],
1979
1997
  idle: 15
1980
1998
  };
1999
+ var MARKDOWN_IMAGE_REGEX = /^!\[[^\]]*\]\([^)]*\)/;
1981
2000
  function charDelay(char, speed, multiplier) {
1982
2001
  const raw = (() => {
1983
2002
  if (char === "*") return speed.fast;
@@ -2017,6 +2036,10 @@ function useTypingEffect(targetText, enabled, speed = RESPONSE_SPEED, initialDis
2017
2036
  displayedRef.current = initialDisplayedRef.current;
2018
2037
  setDisplayedText(initialDisplayedRef.current);
2019
2038
  }
2039
+ if (displayedRef.current && !targetRef.current.startsWith(displayedRef.current)) {
2040
+ displayedRef.current = targetRef.current;
2041
+ setDisplayedText(targetRef.current);
2042
+ }
2020
2043
  if (runningRef.current) return;
2021
2044
  runningRef.current = true;
2022
2045
  const tick = () => {
@@ -2024,8 +2047,22 @@ function useTypingEffect(targetText, enabled, speed = RESPONSE_SPEED, initialDis
2024
2047
  runningRef.current = false;
2025
2048
  return;
2026
2049
  }
2050
+ if (displayedRef.current && !targetRef.current.startsWith(displayedRef.current)) {
2051
+ displayedRef.current = targetRef.current;
2052
+ setDisplayedText(targetRef.current);
2053
+ timerRef.current = setTimeout(tick, speed.idle / multiplier);
2054
+ return;
2055
+ }
2027
2056
  if (displayedRef.current.length < targetRef.current.length) {
2028
- const nextChar = targetRef.current[displayedRef.current.length];
2057
+ const remaining = targetRef.current.slice(displayedRef.current.length);
2058
+ const imgMatch = MARKDOWN_IMAGE_REGEX.exec(remaining);
2059
+ if (imgMatch) {
2060
+ displayedRef.current += imgMatch[0];
2061
+ setDisplayedText(displayedRef.current);
2062
+ timerRef.current = setTimeout(tick, 0);
2063
+ return;
2064
+ }
2065
+ const nextChar = remaining[0];
2029
2066
  displayedRef.current += nextChar;
2030
2067
  setDisplayedText(displayedRef.current);
2031
2068
  const delay = charDelay(nextChar, speed, multiplier);
@@ -2049,6 +2086,13 @@ function useTypingEffect(targetText, enabled, speed = RESPONSE_SPEED, initialDis
2049
2086
  isTyping
2050
2087
  };
2051
2088
  }
2089
+ function stripIncompleteImageToken(text) {
2090
+ const lastBang = text.lastIndexOf("![");
2091
+ if (lastBang === -1) return text;
2092
+ const after = text.slice(lastBang);
2093
+ if (/^!\[[^\]]*\]\([^)]*\)/.test(after)) return text;
2094
+ return text.slice(0, lastBang);
2095
+ }
2052
2096
  function getFeedbackState(message) {
2053
2097
  const feedback = message.feedback;
2054
2098
  if (feedback === "up" || feedback === "down") return feedback;
@@ -2095,7 +2139,8 @@ function AssistantMessageV2({
2095
2139
  const rawResponseContent = (() => {
2096
2140
  const raw = message.isStreaming ? message.streamingContent || message.content : message.content;
2097
2141
  if (!raw) return "";
2098
- return raw.replace(/\\n/g, "\n");
2142
+ const normalized = raw.replace(/\\n/g, "\n");
2143
+ return message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
2099
2144
  })();
2100
2145
  const isThinkingStreaming = !!message.isStreaming && !rawResponseContent && !message.isError;
2101
2146
  const responseTypingEnabled = hasEverStreamed.current && Boolean(rawResponseContent) && !message.isError;
package/dist/index.mjs CHANGED
@@ -1495,6 +1495,7 @@ var RAG_IMAGE_PATH_REGEX2 = /^(?:https?:\/\/[^/\s]+)?\/api\/rag\/chunks\/[^"'\s]
1495
1495
  function isUnresolvedRagImageSource2(src) {
1496
1496
  return RAG_IMAGE_PATH_REGEX2.test(src);
1497
1497
  }
1498
+ var loadedImageCache = /* @__PURE__ */ new Set();
1498
1499
  var frameStyle = {
1499
1500
  width: "min(100%, 32rem)",
1500
1501
  maxWidth: "100%"
@@ -1513,24 +1514,36 @@ function MarkdownImageV2({
1513
1514
  isResolving = false,
1514
1515
  onImageClick
1515
1516
  }) {
1516
- const [isVisible, setIsVisible] = useState(false);
1517
- const [isLoaded, setIsLoaded] = useState(false);
1517
+ const cachedAlready = src ? loadedImageCache.has(src) : false;
1518
+ const [isVisible, setIsVisible] = useState(cachedAlready);
1519
+ const [isLoaded, setIsLoaded] = useState(cachedAlready);
1518
1520
  const [hasError, setHasError] = useState(false);
1519
1521
  const [retryCount, setRetryCount] = useState(0);
1520
1522
  const sentinelRef = useRef(null);
1523
+ const prevLoadedRef = useRef(cachedAlready);
1521
1524
  const isUnresolvedRag = useMemo(
1522
1525
  () => src ? isUnresolvedRagImageSource2(src) : false,
1523
1526
  [src]
1524
1527
  );
1525
1528
  const isResolvingRag = Boolean(isResolving && isUnresolvedRag);
1526
1529
  useEffect(() => {
1527
- setIsLoaded(false);
1530
+ if (src && loadedImageCache.has(src)) {
1531
+ setIsLoaded(true);
1532
+ setIsVisible(true);
1533
+ prevLoadedRef.current = true;
1534
+ }
1528
1535
  setHasError(false);
1529
1536
  setRetryCount(0);
1530
1537
  }, [src]);
1531
1538
  useEffect(() => {
1532
1539
  const el = sentinelRef.current;
1533
1540
  if (!el || !src) return;
1541
+ const rect = el.getBoundingClientRect();
1542
+ const alreadyNear = rect.bottom >= -200 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) + 200;
1543
+ if (alreadyNear) {
1544
+ setIsVisible(true);
1545
+ return;
1546
+ }
1534
1547
  const observer = new IntersectionObserver(
1535
1548
  ([entry]) => {
1536
1549
  if (entry.isIntersecting) {
@@ -1544,7 +1557,7 @@ function MarkdownImageV2({
1544
1557
  return () => observer.disconnect();
1545
1558
  }, [src]);
1546
1559
  if (!src) return null;
1547
- if (isResolvingRag) {
1560
+ if (isResolvingRag && !isLoaded) {
1548
1561
  return /* @__PURE__ */ jsx(
1549
1562
  "span",
1550
1563
  {
@@ -1619,10 +1632,13 @@ function MarkdownImageV2({
1619
1632
  opacity: isLoaded ? 1 : 0
1620
1633
  },
1621
1634
  onLoad: () => {
1635
+ if (src) loadedImageCache.add(src);
1636
+ prevLoadedRef.current = true;
1622
1637
  setHasError(false);
1623
1638
  setIsLoaded(true);
1624
1639
  },
1625
1640
  onError: () => {
1641
+ prevLoadedRef.current = false;
1626
1642
  setIsLoaded(false);
1627
1643
  setHasError(true);
1628
1644
  }
@@ -1640,7 +1656,7 @@ function MarkdownImageV2({
1640
1656
  }
1641
1657
  ) });
1642
1658
  }
1643
- function buildComponents(onImageClick, isResolvingImages) {
1659
+ function buildComponents(onImageClick, isResolvingRef) {
1644
1660
  return {
1645
1661
  p: ({ children }) => /* @__PURE__ */ jsx("p", { children }),
1646
1662
  code: ({ children }) => /* @__PURE__ */ jsx("code", { children }),
@@ -1661,7 +1677,7 @@ function buildComponents(onImageClick, isResolvingImages) {
1661
1677
  {
1662
1678
  src: typeof src === "string" ? src : void 0,
1663
1679
  alt: typeof alt === "string" ? alt : void 0,
1664
- isResolving: isResolvingImages,
1680
+ isResolving: isResolvingRef?.current,
1665
1681
  onImageClick
1666
1682
  }
1667
1683
  ),
@@ -1678,9 +1694,11 @@ function MarkdownRendererV2({
1678
1694
  isResolvingImages,
1679
1695
  onImageClick
1680
1696
  }) {
1697
+ const isResolvingRef = useRef(isResolvingImages);
1698
+ isResolvingRef.current = isResolvingImages;
1681
1699
  const components = useMemo(
1682
- () => buildComponents(onImageClick, isResolvingImages),
1683
- [onImageClick, isResolvingImages]
1700
+ () => buildComponents(onImageClick, isResolvingRef),
1701
+ [onImageClick]
1684
1702
  );
1685
1703
  return /* @__PURE__ */ jsx(
1686
1704
  "div",
@@ -1952,6 +1970,7 @@ var RESPONSE_SPEED = {
1952
1970
  newline: [4, 6],
1953
1971
  idle: 15
1954
1972
  };
1973
+ var MARKDOWN_IMAGE_REGEX = /^!\[[^\]]*\]\([^)]*\)/;
1955
1974
  function charDelay(char, speed, multiplier) {
1956
1975
  const raw = (() => {
1957
1976
  if (char === "*") return speed.fast;
@@ -1991,6 +2010,10 @@ function useTypingEffect(targetText, enabled, speed = RESPONSE_SPEED, initialDis
1991
2010
  displayedRef.current = initialDisplayedRef.current;
1992
2011
  setDisplayedText(initialDisplayedRef.current);
1993
2012
  }
2013
+ if (displayedRef.current && !targetRef.current.startsWith(displayedRef.current)) {
2014
+ displayedRef.current = targetRef.current;
2015
+ setDisplayedText(targetRef.current);
2016
+ }
1994
2017
  if (runningRef.current) return;
1995
2018
  runningRef.current = true;
1996
2019
  const tick = () => {
@@ -1998,8 +2021,22 @@ function useTypingEffect(targetText, enabled, speed = RESPONSE_SPEED, initialDis
1998
2021
  runningRef.current = false;
1999
2022
  return;
2000
2023
  }
2024
+ if (displayedRef.current && !targetRef.current.startsWith(displayedRef.current)) {
2025
+ displayedRef.current = targetRef.current;
2026
+ setDisplayedText(targetRef.current);
2027
+ timerRef.current = setTimeout(tick, speed.idle / multiplier);
2028
+ return;
2029
+ }
2001
2030
  if (displayedRef.current.length < targetRef.current.length) {
2002
- const nextChar = targetRef.current[displayedRef.current.length];
2031
+ const remaining = targetRef.current.slice(displayedRef.current.length);
2032
+ const imgMatch = MARKDOWN_IMAGE_REGEX.exec(remaining);
2033
+ if (imgMatch) {
2034
+ displayedRef.current += imgMatch[0];
2035
+ setDisplayedText(displayedRef.current);
2036
+ timerRef.current = setTimeout(tick, 0);
2037
+ return;
2038
+ }
2039
+ const nextChar = remaining[0];
2003
2040
  displayedRef.current += nextChar;
2004
2041
  setDisplayedText(displayedRef.current);
2005
2042
  const delay = charDelay(nextChar, speed, multiplier);
@@ -2023,6 +2060,13 @@ function useTypingEffect(targetText, enabled, speed = RESPONSE_SPEED, initialDis
2023
2060
  isTyping
2024
2061
  };
2025
2062
  }
2063
+ function stripIncompleteImageToken(text) {
2064
+ const lastBang = text.lastIndexOf("![");
2065
+ if (lastBang === -1) return text;
2066
+ const after = text.slice(lastBang);
2067
+ if (/^!\[[^\]]*\]\([^)]*\)/.test(after)) return text;
2068
+ return text.slice(0, lastBang);
2069
+ }
2026
2070
  function getFeedbackState(message) {
2027
2071
  const feedback = message.feedback;
2028
2072
  if (feedback === "up" || feedback === "down") return feedback;
@@ -2069,7 +2113,8 @@ function AssistantMessageV2({
2069
2113
  const rawResponseContent = (() => {
2070
2114
  const raw = message.isStreaming ? message.streamingContent || message.content : message.content;
2071
2115
  if (!raw) return "";
2072
- return raw.replace(/\\n/g, "\n");
2116
+ const normalized = raw.replace(/\\n/g, "\n");
2117
+ return message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
2073
2118
  })();
2074
2119
  const isThinkingStreaming = !!message.isStreaming && !rawResponseContent && !message.isError;
2075
2120
  const responseTypingEnabled = hasEverStreamed.current && Boolean(rawResponseContent) && !message.isError;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paymanai/payman-ask-sdk",
3
- "version": "4.0.9",
3
+ "version": "4.0.11",
4
4
  "description": "Reusable web chat SDK for Payman K2 Agents with streaming support",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",