@paymanai/payman-ask-sdk 4.0.8 → 4.0.10

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,23 @@ 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
+ }
2096
+ function getFeedbackState(message) {
2097
+ const feedback = message.feedback;
2098
+ if (feedback === "up" || feedback === "down") return feedback;
2099
+ if (feedback && typeof feedback === "object") {
2100
+ const value = feedback.feedback;
2101
+ if (!value) return null;
2102
+ return value === "POSITIVE" ? "up" : "down";
2103
+ }
2104
+ return null;
2105
+ }
2052
2106
  function AssistantMessageV2({
2053
2107
  message,
2054
2108
  onImageClick,
@@ -2058,21 +2112,11 @@ function AssistantMessageV2({
2058
2112
  typingSpeed = 4
2059
2113
  }) {
2060
2114
  const [copied, setCopied] = react.useState(false);
2061
- const [activeFeedback, setActiveFeedback] = react.useState(null);
2115
+ const [activeFeedback, setActiveFeedback] = react.useState(
2116
+ () => getFeedbackState(message)
2117
+ );
2062
2118
  const [reasonModalOpen, setReasonModalOpen] = react.useState(false);
2063
2119
  const canSubmitFeedback = !!onSubmitFeedback && !!message.executionId;
2064
- const handlePositiveFeedback = () => {
2065
- if (!canSubmitFeedback || activeFeedback === "up") return;
2066
- const previous = activeFeedback;
2067
- setActiveFeedback("up");
2068
- Promise.resolve(
2069
- onSubmitFeedback?.({
2070
- messageId: message.id,
2071
- executionId: message.executionId,
2072
- feedback: "POSITIVE"
2073
- })
2074
- ).catch(() => setActiveFeedback(previous));
2075
- };
2076
2120
  const [toast, setToast] = react.useState(null);
2077
2121
  const copyResetTimerRef = react.useRef(null);
2078
2122
  const toastTimerRef = react.useRef(null);
@@ -2080,8 +2124,12 @@ function AssistantMessageV2({
2080
2124
  const showTraceAction = (actions?.trace ?? true) && !!onExecutionTraceClick;
2081
2125
  const showThumbsUp = actions?.thumbsUp ?? true;
2082
2126
  const showThumbsDown = actions?.thumbsDown ?? true;
2127
+ const hydratedFeedback = message.feedback;
2083
2128
  const hasEverStreamed = react.useRef(!!message.isStreaming);
2084
2129
  if (message.isStreaming) hasEverStreamed.current = true;
2130
+ react.useEffect(() => {
2131
+ setActiveFeedback(getFeedbackState(message));
2132
+ }, [hydratedFeedback, message.id]);
2085
2133
  react.useEffect(() => {
2086
2134
  return () => {
2087
2135
  if (copyResetTimerRef.current) clearTimeout(copyResetTimerRef.current);
@@ -2091,7 +2139,8 @@ function AssistantMessageV2({
2091
2139
  const rawResponseContent = (() => {
2092
2140
  const raw = message.isStreaming ? message.streamingContent || message.content : message.content;
2093
2141
  if (!raw) return "";
2094
- return raw.replace(/\\n/g, "\n");
2142
+ const normalized = raw.replace(/\\n/g, "\n");
2143
+ return message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
2095
2144
  })();
2096
2145
  const isThinkingStreaming = !!message.isStreaming && !rawResponseContent && !message.isError;
2097
2146
  const responseTypingEnabled = hasEverStreamed.current && Boolean(rawResponseContent) && !message.isError;
@@ -2147,6 +2196,21 @@ function AssistantMessageV2({
2147
2196
  setToast({ label, tone });
2148
2197
  toastTimerRef.current = setTimeout(() => setToast(null), 1800);
2149
2198
  };
2199
+ const handlePositiveFeedback = () => {
2200
+ if (!canSubmitFeedback || activeFeedback === "up") return;
2201
+ const previous = activeFeedback;
2202
+ setActiveFeedback("up");
2203
+ Promise.resolve(
2204
+ onSubmitFeedback?.({
2205
+ messageId: message.id,
2206
+ executionId: message.executionId,
2207
+ feedback: "POSITIVE"
2208
+ })
2209
+ ).then(() => showToast("Thank you for your feedback", "success")).catch(() => {
2210
+ setActiveFeedback(previous);
2211
+ showToast("Could not send feedback", "error");
2212
+ });
2213
+ };
2150
2214
  const handleCopy = async () => {
2151
2215
  try {
2152
2216
  if (!navigator.clipboard?.writeText) {
@@ -2293,7 +2357,13 @@ function AssistantMessageV2({
2293
2357
  activeFeedback === "up" && "payman-v2-assistant-msg-action-btn-active"
2294
2358
  ),
2295
2359
  "aria-label": "Good response",
2296
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ThumbsUp, { style: { width: 15, height: 15 } })
2360
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2361
+ lucideReact.ThumbsUp,
2362
+ {
2363
+ fill: activeFeedback === "up" ? "currentColor" : "none",
2364
+ style: { width: 15, height: 15 }
2365
+ }
2366
+ )
2297
2367
  }
2298
2368
  ) }),
2299
2369
  showThumbsDown && canSubmitFeedback && /* @__PURE__ */ jsxRuntime.jsx(ActionTooltipV2, { label: "Bad response", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -2305,7 +2375,13 @@ function AssistantMessageV2({
2305
2375
  activeFeedback === "down" && "payman-v2-assistant-msg-action-btn-active"
2306
2376
  ),
2307
2377
  "aria-label": "Bad response",
2308
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ThumbsDown, { style: { width: 15, height: 15 } })
2378
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2379
+ lucideReact.ThumbsDown,
2380
+ {
2381
+ fill: activeFeedback === "down" ? "currentColor" : "none",
2382
+ style: { width: 15, height: 15 }
2383
+ }
2384
+ )
2309
2385
  }
2310
2386
  ) }),
2311
2387
  showTraceAction && /* @__PURE__ */ jsxRuntime.jsx(ActionTooltipV2, { label: "Trace", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -2332,6 +2408,7 @@ function AssistantMessageV2({
2332
2408
  details
2333
2409
  });
2334
2410
  setActiveFeedback("down");
2411
+ showToast("Thank you for your feedback", "success");
2335
2412
  }
2336
2413
  }
2337
2414
  )
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,23 @@ 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
+ }
2070
+ function getFeedbackState(message) {
2071
+ const feedback = message.feedback;
2072
+ if (feedback === "up" || feedback === "down") return feedback;
2073
+ if (feedback && typeof feedback === "object") {
2074
+ const value = feedback.feedback;
2075
+ if (!value) return null;
2076
+ return value === "POSITIVE" ? "up" : "down";
2077
+ }
2078
+ return null;
2079
+ }
2026
2080
  function AssistantMessageV2({
2027
2081
  message,
2028
2082
  onImageClick,
@@ -2032,21 +2086,11 @@ function AssistantMessageV2({
2032
2086
  typingSpeed = 4
2033
2087
  }) {
2034
2088
  const [copied, setCopied] = useState(false);
2035
- const [activeFeedback, setActiveFeedback] = useState(null);
2089
+ const [activeFeedback, setActiveFeedback] = useState(
2090
+ () => getFeedbackState(message)
2091
+ );
2036
2092
  const [reasonModalOpen, setReasonModalOpen] = useState(false);
2037
2093
  const canSubmitFeedback = !!onSubmitFeedback && !!message.executionId;
2038
- const handlePositiveFeedback = () => {
2039
- if (!canSubmitFeedback || activeFeedback === "up") return;
2040
- const previous = activeFeedback;
2041
- setActiveFeedback("up");
2042
- Promise.resolve(
2043
- onSubmitFeedback?.({
2044
- messageId: message.id,
2045
- executionId: message.executionId,
2046
- feedback: "POSITIVE"
2047
- })
2048
- ).catch(() => setActiveFeedback(previous));
2049
- };
2050
2094
  const [toast, setToast] = useState(null);
2051
2095
  const copyResetTimerRef = useRef(null);
2052
2096
  const toastTimerRef = useRef(null);
@@ -2054,8 +2098,12 @@ function AssistantMessageV2({
2054
2098
  const showTraceAction = (actions?.trace ?? true) && !!onExecutionTraceClick;
2055
2099
  const showThumbsUp = actions?.thumbsUp ?? true;
2056
2100
  const showThumbsDown = actions?.thumbsDown ?? true;
2101
+ const hydratedFeedback = message.feedback;
2057
2102
  const hasEverStreamed = useRef(!!message.isStreaming);
2058
2103
  if (message.isStreaming) hasEverStreamed.current = true;
2104
+ useEffect(() => {
2105
+ setActiveFeedback(getFeedbackState(message));
2106
+ }, [hydratedFeedback, message.id]);
2059
2107
  useEffect(() => {
2060
2108
  return () => {
2061
2109
  if (copyResetTimerRef.current) clearTimeout(copyResetTimerRef.current);
@@ -2065,7 +2113,8 @@ function AssistantMessageV2({
2065
2113
  const rawResponseContent = (() => {
2066
2114
  const raw = message.isStreaming ? message.streamingContent || message.content : message.content;
2067
2115
  if (!raw) return "";
2068
- return raw.replace(/\\n/g, "\n");
2116
+ const normalized = raw.replace(/\\n/g, "\n");
2117
+ return message.isStreaming ? stripIncompleteImageToken(normalized) : normalized;
2069
2118
  })();
2070
2119
  const isThinkingStreaming = !!message.isStreaming && !rawResponseContent && !message.isError;
2071
2120
  const responseTypingEnabled = hasEverStreamed.current && Boolean(rawResponseContent) && !message.isError;
@@ -2121,6 +2170,21 @@ function AssistantMessageV2({
2121
2170
  setToast({ label, tone });
2122
2171
  toastTimerRef.current = setTimeout(() => setToast(null), 1800);
2123
2172
  };
2173
+ const handlePositiveFeedback = () => {
2174
+ if (!canSubmitFeedback || activeFeedback === "up") return;
2175
+ const previous = activeFeedback;
2176
+ setActiveFeedback("up");
2177
+ Promise.resolve(
2178
+ onSubmitFeedback?.({
2179
+ messageId: message.id,
2180
+ executionId: message.executionId,
2181
+ feedback: "POSITIVE"
2182
+ })
2183
+ ).then(() => showToast("Thank you for your feedback", "success")).catch(() => {
2184
+ setActiveFeedback(previous);
2185
+ showToast("Could not send feedback", "error");
2186
+ });
2187
+ };
2124
2188
  const handleCopy = async () => {
2125
2189
  try {
2126
2190
  if (!navigator.clipboard?.writeText) {
@@ -2267,7 +2331,13 @@ function AssistantMessageV2({
2267
2331
  activeFeedback === "up" && "payman-v2-assistant-msg-action-btn-active"
2268
2332
  ),
2269
2333
  "aria-label": "Good response",
2270
- children: /* @__PURE__ */ jsx(ThumbsUp, { style: { width: 15, height: 15 } })
2334
+ children: /* @__PURE__ */ jsx(
2335
+ ThumbsUp,
2336
+ {
2337
+ fill: activeFeedback === "up" ? "currentColor" : "none",
2338
+ style: { width: 15, height: 15 }
2339
+ }
2340
+ )
2271
2341
  }
2272
2342
  ) }),
2273
2343
  showThumbsDown && canSubmitFeedback && /* @__PURE__ */ jsx(ActionTooltipV2, { label: "Bad response", children: /* @__PURE__ */ jsx(
@@ -2279,7 +2349,13 @@ function AssistantMessageV2({
2279
2349
  activeFeedback === "down" && "payman-v2-assistant-msg-action-btn-active"
2280
2350
  ),
2281
2351
  "aria-label": "Bad response",
2282
- children: /* @__PURE__ */ jsx(ThumbsDown, { style: { width: 15, height: 15 } })
2352
+ children: /* @__PURE__ */ jsx(
2353
+ ThumbsDown,
2354
+ {
2355
+ fill: activeFeedback === "down" ? "currentColor" : "none",
2356
+ style: { width: 15, height: 15 }
2357
+ }
2358
+ )
2283
2359
  }
2284
2360
  ) }),
2285
2361
  showTraceAction && /* @__PURE__ */ jsx(ActionTooltipV2, { label: "Trace", children: /* @__PURE__ */ jsx(
@@ -2306,6 +2382,7 @@ function AssistantMessageV2({
2306
2382
  details
2307
2383
  });
2308
2384
  setActiveFeedback("down");
2385
+ showToast("Thank you for your feedback", "success");
2309
2386
  }
2310
2387
  }
2311
2388
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paymanai/payman-ask-sdk",
3
- "version": "4.0.8",
3
+ "version": "4.0.10",
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",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@lottiefiles/dotlottie-react": "^0.19.0",
45
- "@paymanai/payman-typescript-ask-sdk": "4.0.8",
45
+ "@paymanai/payman-typescript-ask-sdk": "/Users/ibraheem/Desktop/Work/payman/sdks/payman-sdk-typescript",
46
46
  "@sentry/react": "^10.46.0",
47
47
  "clsx": "^2.1.1",
48
48
  "remark-breaks": "^4.0.0",