@flamingo-stack/openframe-frontend-core 0.0.583 → 0.0.584-1973.5420.1

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.
Files changed (149) hide show
  1. package/dist/chat-protocol/events.d.ts +8 -2
  2. package/dist/chat-protocol/events.d.ts.map +1 -1
  3. package/dist/chat-protocol/index.cjs +177 -0
  4. package/dist/chat-protocol/index.cjs.map +1 -1
  5. package/dist/chat-protocol/index.js +177 -0
  6. package/dist/chat-protocol/index.js.map +1 -1
  7. package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
  8. package/dist/chat-protocol/source-metadata.d.ts +3 -0
  9. package/dist/chat-protocol/source-metadata.d.ts.map +1 -0
  10. package/dist/{chunk-EATPU5EF.cjs → chunk-3MAG3EG4.cjs} +9 -9
  11. package/dist/{chunk-EATPU5EF.cjs.map → chunk-3MAG3EG4.cjs.map} +1 -1
  12. package/dist/{chunk-IKUSKKFZ.cjs → chunk-4TZOFXPH.cjs} +712 -432
  13. package/dist/chunk-4TZOFXPH.cjs.map +1 -0
  14. package/dist/{chunk-WMRXB6RS.js → chunk-54WRMPCY.js} +3 -3
  15. package/dist/{chunk-WTC6QQNX.cjs → chunk-7IVT56YY.cjs} +3 -3
  16. package/dist/{chunk-WTC6QQNX.cjs.map → chunk-7IVT56YY.cjs.map} +1 -1
  17. package/dist/{chunk-PFKWJ6PJ.js → chunk-AQW2PDRS.js} +2 -2
  18. package/dist/{chunk-DROXHJVZ.js → chunk-AVVKXNGI.js} +2 -2
  19. package/dist/{chunk-YNLIXABT.cjs → chunk-CE6CA6EM.cjs} +43 -43
  20. package/dist/{chunk-YNLIXABT.cjs.map → chunk-CE6CA6EM.cjs.map} +1 -1
  21. package/dist/{chunk-TRD2YICZ.cjs → chunk-CGLQLBMQ.cjs} +21 -21
  22. package/dist/{chunk-TRD2YICZ.cjs.map → chunk-CGLQLBMQ.cjs.map} +1 -1
  23. package/dist/{chunk-XWBND4G5.js → chunk-CO4WRST6.js} +301 -21
  24. package/dist/chunk-CO4WRST6.js.map +1 -0
  25. package/dist/{chunk-IJVWKLJK.cjs → chunk-DHLD3JXN.cjs} +69 -69
  26. package/dist/{chunk-IJVWKLJK.cjs.map → chunk-DHLD3JXN.cjs.map} +1 -1
  27. package/dist/{chunk-M6ZUEBDK.cjs → chunk-F2EN6MXN.cjs} +159 -159
  28. package/dist/{chunk-M6ZUEBDK.cjs.map → chunk-F2EN6MXN.cjs.map} +1 -1
  29. package/dist/{chunk-J3EEL6YC.js → chunk-FH2FH337.js} +5 -5
  30. package/dist/{chunk-L26MD32B.js → chunk-GDHI3JN5.js} +3 -3
  31. package/dist/{chunk-VWHEXQ64.js → chunk-GFIH73Q3.js} +3 -3
  32. package/dist/{chunk-R5A3MGMY.js → chunk-H6GYY5CB.js} +3 -3
  33. package/dist/{chunk-FCLM2RXI.js → chunk-HGJAUYHW.js} +2 -2
  34. package/dist/{chunk-RBDVHLT5.cjs → chunk-HKKQCVVY.cjs} +22 -22
  35. package/dist/{chunk-RBDVHLT5.cjs.map → chunk-HKKQCVVY.cjs.map} +1 -1
  36. package/dist/{chunk-KDWNMFX4.cjs → chunk-JEMGFSC2.cjs} +24 -24
  37. package/dist/{chunk-KDWNMFX4.cjs.map → chunk-JEMGFSC2.cjs.map} +1 -1
  38. package/dist/{chunk-BTBW4L2W.cjs → chunk-JX6RSSZP.cjs} +33 -33
  39. package/dist/{chunk-BTBW4L2W.cjs.map → chunk-JX6RSSZP.cjs.map} +1 -1
  40. package/dist/{chunk-WHNC7C76.js → chunk-KN2ZCZQ7.js} +5 -5
  41. package/dist/{chunk-WBTKXNKZ.cjs → chunk-MYVHYVQM.cjs} +10 -10
  42. package/dist/{chunk-WBTKXNKZ.cjs.map → chunk-MYVHYVQM.cjs.map} +1 -1
  43. package/dist/{chunk-3TNM6SDP.cjs → chunk-PI3BNDCW.cjs} +15 -15
  44. package/dist/{chunk-3TNM6SDP.cjs.map → chunk-PI3BNDCW.cjs.map} +1 -1
  45. package/dist/{chunk-XZ3MKTJZ.js → chunk-PWI66GZF.js} +2 -2
  46. package/dist/{chunk-IFPGXSMZ.cjs → chunk-RTI3TYMX.cjs} +3 -3
  47. package/dist/{chunk-IFPGXSMZ.cjs.map → chunk-RTI3TYMX.cjs.map} +1 -1
  48. package/dist/{chunk-6BI3AYBY.cjs → chunk-SS2TCIKA.cjs} +4 -4
  49. package/dist/{chunk-6BI3AYBY.cjs.map → chunk-SS2TCIKA.cjs.map} +1 -1
  50. package/dist/{chunk-HIHPWVKP.js → chunk-T35JA5XV.js} +5 -5
  51. package/dist/{chunk-EZ5VU5WS.js → chunk-TLZ7HTXP.js} +2 -2
  52. package/dist/{chunk-OCXFFGFA.cjs → chunk-UZUG7QZ3.cjs} +10 -10
  53. package/dist/{chunk-OCXFFGFA.cjs.map → chunk-UZUG7QZ3.cjs.map} +1 -1
  54. package/dist/{chunk-3RR67XC2.cjs → chunk-W46U5J3H.cjs} +5 -5
  55. package/dist/{chunk-3RR67XC2.cjs.map → chunk-W46U5J3H.cjs.map} +1 -1
  56. package/dist/{chunk-IOP7BCRQ.js → chunk-WFF5JMQ2.js} +6 -6
  57. package/dist/{chunk-AJLF6MBD.js → chunk-X4HLHN36.js} +3 -3
  58. package/dist/{chunk-PMA7DVQR.js → chunk-ZWB64HOD.js} +4 -4
  59. package/dist/components/case-studies/index.cjs +9 -9
  60. package/dist/components/case-studies/index.js +3 -3
  61. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  62. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  63. package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
  64. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +4 -1
  65. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  66. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +2 -28
  67. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  68. package/dist/components/chat/index.cjs +2 -2
  69. package/dist/components/chat/index.js +1 -1
  70. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
  71. package/dist/components/chat/tool-execution-display.d.ts.map +1 -1
  72. package/dist/components/chat/types/component.types.d.ts +2 -0
  73. package/dist/components/chat/types/component.types.d.ts.map +1 -1
  74. package/dist/components/chat/types/message.types.d.ts +35 -1
  75. package/dist/components/chat/types/message.types.d.ts.map +1 -1
  76. package/dist/components/chat/types/unified-chat-state.types.d.ts +3 -3
  77. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
  78. package/dist/components/chat/utils/chat-source-metadata.d.ts +5 -0
  79. package/dist/components/chat/utils/chat-source-metadata.d.ts.map +1 -0
  80. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  81. package/dist/components/contact/index.cjs +4 -4
  82. package/dist/components/contact/index.js +3 -3
  83. package/dist/components/docs/index.cjs +7 -7
  84. package/dist/components/docs/index.js +6 -6
  85. package/dist/components/embeds/index.cjs +4 -4
  86. package/dist/components/embeds/index.js +3 -3
  87. package/dist/components/faq/index.cjs +4 -4
  88. package/dist/components/faq/index.js +3 -3
  89. package/dist/components/features/index.cjs +3 -3
  90. package/dist/components/features/index.js +2 -2
  91. package/dist/components/help-center-pages/index.cjs +23 -23
  92. package/dist/components/help-center-pages/index.js +14 -14
  93. package/dist/components/index.cjs +36 -36
  94. package/dist/components/index.js +13 -13
  95. package/dist/components/meeting-scheduler/index.cjs +21 -21
  96. package/dist/components/meeting-scheduler/index.js +2 -2
  97. package/dist/components/navigation/index.cjs +6 -6
  98. package/dist/components/navigation/index.js +5 -5
  99. package/dist/components/onboarding-guides/index.cjs +6 -6
  100. package/dist/components/onboarding-guides/index.js +5 -5
  101. package/dist/components/tickets/index.cjs +7 -7
  102. package/dist/components/tickets/index.js +6 -6
  103. package/dist/components/ui/index.cjs +3 -3
  104. package/dist/components/ui/index.js +2 -2
  105. package/dist/index.cjs +7 -7
  106. package/dist/index.js +6 -6
  107. package/dist/utils/index.cjs.map +1 -1
  108. package/dist/utils/index.js.map +1 -1
  109. package/package.json +1 -1
  110. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +130 -0
  111. package/src/chat-protocol/events.ts +9 -1
  112. package/src/chat-protocol/nats-decoder.ts +7 -0
  113. package/src/chat-protocol/source-metadata.ts +187 -0
  114. package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +86 -0
  115. package/src/components/chat/__tests__/embeddable-chat-sources.test.tsx +171 -0
  116. package/src/components/chat/__tests__/tool-execution-display.test.tsx +93 -0
  117. package/src/components/chat/chat-message-enhanced.tsx +28 -13
  118. package/src/components/chat/chat-message-list.tsx +1 -0
  119. package/src/components/chat/embeddable-chat.tsx +10 -1
  120. package/src/components/chat/entity-cards/__tests__/dispatch-video-refs.test.tsx +74 -0
  121. package/src/components/chat/entity-cards/dispatch.tsx +48 -11
  122. package/src/components/chat/hooks/use-nats-chat-adapter.ts +7 -0
  123. package/src/components/chat/hooks/use-sse-chat-adapter.ts +2 -29
  124. package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +56 -0
  125. package/src/components/chat/stream/chat-stream-reducer.ts +31 -0
  126. package/src/components/chat/tool-execution-display.tsx +5 -2
  127. package/src/components/chat/types/component.types.ts +2 -0
  128. package/src/components/chat/types/message.types.ts +39 -0
  129. package/src/components/chat/types/unified-chat-state.types.ts +4 -3
  130. package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +57 -0
  131. package/src/components/chat/utils/chat-source-metadata.ts +19 -0
  132. package/src/components/chat/utils/process-historical-messages.ts +19 -1
  133. package/dist/chunk-IKUSKKFZ.cjs.map +0 -1
  134. package/dist/chunk-XWBND4G5.js.map +0 -1
  135. /package/dist/{chunk-WMRXB6RS.js.map → chunk-54WRMPCY.js.map} +0 -0
  136. /package/dist/{chunk-PFKWJ6PJ.js.map → chunk-AQW2PDRS.js.map} +0 -0
  137. /package/dist/{chunk-DROXHJVZ.js.map → chunk-AVVKXNGI.js.map} +0 -0
  138. /package/dist/{chunk-J3EEL6YC.js.map → chunk-FH2FH337.js.map} +0 -0
  139. /package/dist/{chunk-L26MD32B.js.map → chunk-GDHI3JN5.js.map} +0 -0
  140. /package/dist/{chunk-VWHEXQ64.js.map → chunk-GFIH73Q3.js.map} +0 -0
  141. /package/dist/{chunk-R5A3MGMY.js.map → chunk-H6GYY5CB.js.map} +0 -0
  142. /package/dist/{chunk-FCLM2RXI.js.map → chunk-HGJAUYHW.js.map} +0 -0
  143. /package/dist/{chunk-WHNC7C76.js.map → chunk-KN2ZCZQ7.js.map} +0 -0
  144. /package/dist/{chunk-XZ3MKTJZ.js.map → chunk-PWI66GZF.js.map} +0 -0
  145. /package/dist/{chunk-HIHPWVKP.js.map → chunk-T35JA5XV.js.map} +0 -0
  146. /package/dist/{chunk-EZ5VU5WS.js.map → chunk-TLZ7HTXP.js.map} +0 -0
  147. /package/dist/{chunk-IOP7BCRQ.js.map → chunk-WFF5JMQ2.js.map} +0 -0
  148. /package/dist/{chunk-AJLF6MBD.js.map → chunk-X4HLHN36.js.map} +0 -0
  149. /package/dist/{chunk-PMA7DVQR.js.map → chunk-ZWB64HOD.js.map} +0 -0
@@ -1156,6 +1156,8 @@ import { forwardRef as forwardRef6 } from "react";
1156
1156
  var MESSAGE_TYPE = {
1157
1157
  TEXT: "TEXT",
1158
1158
  THINKING: "THINKING",
1159
+ GUIDE: "GUIDE",
1160
+ SOURCES: "SOURCES",
1159
1161
  ASK: "ASK",
1160
1162
  EXECUTING_TOOL: "EXECUTING_TOOL",
1161
1163
  EXECUTED_TOOL: "EXECUTED_TOOL",
@@ -6854,6 +6856,8 @@ var ToolExecutionDisplay = forwardRef15(
6854
6856
  }, [message.parameters, variant]);
6855
6857
  const hasResult = isExecuted && typeof message.result === "string" && message.result.length > 0;
6856
6858
  const hasBody = argEntries.length > 0 || hasResult || isExecuting;
6859
+ const isConciseRemoteRead = !message.integratedToolType && !!message.toolTitle?.trim() && argEntries.length === 0 && !hasResult;
6860
+ const isStaticRow = isClientView || isConciseRemoteRead;
6857
6861
  const renderStatusIcon = () => {
6858
6862
  if (isExecuting) return /* @__PURE__ */ jsx34(DotsLoaderIcon, { size: 16, className: "text-ods-text-secondary" });
6859
6863
  if (isExecuted && message.success === true) return /* @__PURE__ */ jsx34(CheckCircleIcon, { className: "h-4 w-4 text-ods-success" });
@@ -6873,7 +6877,7 @@ var ToolExecutionDisplay = forwardRef15(
6873
6877
  }
6874
6878
  ),
6875
6879
  /* @__PURE__ */ jsx34("div", { className: "flex h-5 w-5 shrink-0 items-center justify-center", children: renderStatusIcon() }),
6876
- !isClientView && /* @__PURE__ */ jsx34("div", { className: "flex h-5 w-5 shrink-0 items-center justify-center", children: /* @__PURE__ */ jsx34(ExpandChevron, { expanded }) })
6880
+ !isStaticRow && /* @__PURE__ */ jsx34("div", { className: "flex h-5 w-5 shrink-0 items-center justify-center", children: /* @__PURE__ */ jsx34(ExpandChevron, { expanded }) })
6877
6881
  ] });
6878
6882
  return /* @__PURE__ */ jsx34(
6879
6883
  "div",
@@ -6886,7 +6890,7 @@ var ToolExecutionDisplay = forwardRef15(
6886
6890
  className
6887
6891
  ),
6888
6892
  ...props,
6889
- children: isClientView ? (
6893
+ children: isStaticRow ? (
6890
6894
  // Client (Fae end-user): static, non-expandable row — no chevron, no
6891
6895
  // body. Just the explanation + status; command/args/result are hidden.
6892
6896
  /* @__PURE__ */ jsx34("div", { className: "flex w-full items-start gap-[var(--spacing-system-xs)] p-[var(--spacing-system-s)] text-left", children: headerContent })
@@ -6980,6 +6984,7 @@ var ChatMessageEnhanced = forwardRef16(
6980
6984
  renderContextItem,
6981
6985
  renderMention,
6982
6986
  renderEntityCard,
6987
+ refs,
6983
6988
  onAskSelect,
6984
6989
  NavLinkAnchor,
6985
6990
  ...props
@@ -6988,6 +6993,10 @@ var ChatMessageEnhanced = forwardRef16(
6988
6993
  const isError = role === "error";
6989
6994
  const authorType = authorTypeProp ?? (isUser ? "user" : assistantType === "mingo" ? "mingo" : "fae");
6990
6995
  const hasMarkerSupport = !!renderEntityCard;
6996
+ const refsByKey = useMemo8(
6997
+ () => new Map((refs ?? []).map((reference) => [`${reference.type}:${reference.id}`, reference])),
6998
+ [refs]
6999
+ );
6991
7000
  const segments = useMemo8(() => normalizeContent(content), [content]);
6992
7001
  const askRuns = useMemo8(() => groupAskRuns(segments), [segments]);
6993
7002
  const hasMentionSupport = !!renderMention;
@@ -7036,14 +7045,15 @@ var ChatMessageEnhanced = forwardRef16(
7036
7045
  const key = `${cardType}:${cardId}`;
7037
7046
  usedKeys.add(key);
7038
7047
  let entry = cache.get(key);
7039
- if (!entry || entry.render !== render) {
7040
- const refForRender = {
7041
- type: cardType,
7042
- id: cardId,
7043
- title: cardId,
7044
- url: null
7045
- };
7046
- entry = { render, node: render(refForRender) };
7048
+ const enrichedRef = refsByKey.get(key);
7049
+ const refForRender = enrichedRef ?? {
7050
+ type: cardType,
7051
+ id: cardId,
7052
+ title: cardId,
7053
+ url: null
7054
+ };
7055
+ if (!entry || entry.render !== render || entry.reference !== refForRender) {
7056
+ entry = { render, reference: refForRender, node: render(refForRender) };
7047
7057
  cache.set(key, entry);
7048
7058
  }
7049
7059
  const rendered = entry.node;
@@ -7077,7 +7087,7 @@ var ChatMessageEnhanced = forwardRef16(
7077
7087
  }
7078
7088
  });
7079
7089
  return { inlineByKey, partsBySegment, usedKeys };
7080
- }, [hasMarkerSupport, renderEntityCard, segments]);
7090
+ }, [hasMarkerSupport, refsByKey, renderEntityCard, segments]);
7081
7091
  useEffect6(() => {
7082
7092
  const cache = renderedCardNodeCache.current;
7083
7093
  const usedKeys = renderingPlan?.usedKeys;
@@ -7304,7 +7314,7 @@ var MemoizedChatMessageEnhanced = memo2(ChatMessageEnhanced, (prevProps, nextPro
7304
7314
  // message (it's set once on the optimistic send and never mutated).
7305
7315
  prevProps.contextItems === nextProps.contextItems && prevProps.resolveContextIcon === nextProps.resolveContextIcon && prevProps.renderContextItem === nextProps.renderContextItem && // Host keeps this stable (module const / useCallback), so reference
7306
7316
  // equality holds across streaming chunks.
7307
- prevProps.renderMention === nextProps.renderMention && prevProps.renderEntityCard === nextProps.renderEntityCard && // Same stability contract as the renderers above: hosts pass a `useCallback`
7317
+ prevProps.renderMention === nextProps.renderMention && prevProps.renderEntityCard === nextProps.renderEntityCard && prevProps.refs === nextProps.refs && // Same stability contract as the renderers above: hosts pass a `useCallback`
7308
7318
  // (EmbeddableChat passes its memoized `handleSend`), so this holds across
7309
7319
  // streaming chunks instead of re-rendering every ask card per chunk.
7310
7320
  prevProps.onAskSelect === nextProps.onAskSelect && prevProps.NavLinkAnchor === nextProps.NavLinkAnchor;
@@ -7892,6 +7902,7 @@ var ChatMessageList = forwardRef17(
7892
7902
  renderContextItem,
7893
7903
  renderMention,
7894
7904
  renderEntityCard,
7905
+ refs: message.refs,
7895
7906
  onAskSelect: index > lastUserMessageIndex ? onAskSelect : void 0,
7896
7907
  NavLinkAnchor
7897
7908
  },
@@ -14799,6 +14810,14 @@ function hubspotStatusToVariant(status) {
14799
14810
  if (s === "OPEN") return "warning";
14800
14811
  return "grey";
14801
14812
  }
14813
+ function validatedHttpsUrl(value) {
14814
+ if (typeof value !== "string") return null;
14815
+ try {
14816
+ return new URL(value).protocol === "https:" ? value : null;
14817
+ } catch {
14818
+ return null;
14819
+ }
14820
+ }
14802
14821
  function decodeVideoMarkerId(id) {
14803
14822
  const mux = id.match(/^mux-([A-Za-z0-9_-]+)$/);
14804
14823
  if (mux) {
@@ -14811,8 +14830,8 @@ function decodeVideoMarkerId(id) {
14811
14830
  try {
14812
14831
  const b64 = mp4[1].replace(/-/g, "+").replace(/_/g, "/");
14813
14832
  const url = atob(b64);
14814
- if (!/^https:\/\//.test(url)) return null;
14815
- return { videoUrl: url };
14833
+ const safeUrl = validatedHttpsUrl(url);
14834
+ return safeUrl ? { videoUrl: safeUrl } : null;
14816
14835
  } catch {
14817
14836
  return null;
14818
14837
  }
@@ -14820,6 +14839,26 @@ function decodeVideoMarkerId(id) {
14820
14839
  if (/^[A-Za-z0-9_-]{11}$/.test(id)) return { youtubeUrl: id };
14821
14840
  return null;
14822
14841
  }
14842
+ function validatedVideoMetadata(chatRef) {
14843
+ const input = chatRef.metadata ?? {};
14844
+ const metadata = {};
14845
+ for (const key of ["videoUrl", "highlightVideoUrl", "videoPoster", "highlightVideoPoster"]) {
14846
+ const url = validatedHttpsUrl(input[key]);
14847
+ if (url) metadata[key] = url;
14848
+ }
14849
+ const youtubeUrl = input.youtubeUrl;
14850
+ if (typeof youtubeUrl === "string" && /^[A-Za-z0-9_-]{11}$/.test(youtubeUrl)) {
14851
+ metadata.youtubeUrl = youtubeUrl;
14852
+ }
14853
+ const safeUrl = validatedHttpsUrl(chatRef.url);
14854
+ if (safeUrl) {
14855
+ const youtubeId2 = extractYouTubeId(safeUrl);
14856
+ delete metadata.videoUrl;
14857
+ delete metadata.youtubeUrl;
14858
+ metadata[youtubeId2 ? "youtubeUrl" : "videoUrl"] = youtubeId2 ?? safeUrl;
14859
+ }
14860
+ return Object.keys(metadata).length > 0 ? metadata : null;
14861
+ }
14823
14862
  function itemVideoMetadata(item) {
14824
14863
  const pick = (...vals) => {
14825
14864
  for (const v of vals) if (typeof v === "string" && v.length > 0) return v;
@@ -15757,13 +15796,15 @@ function renderChatInlineEntityCard(chatRef, options = {}) {
15757
15796
  }
15758
15797
  );
15759
15798
  if (chatRef.type === "video") {
15799
+ const enrichedMetadata = validatedVideoMetadata(chatRef);
15760
15800
  const decoded = decodeVideoMarkerId(chatRef.id);
15761
- if (decoded) {
15801
+ const playableMetadata = enrichedMetadata ?? decoded;
15802
+ if (playableMetadata) {
15762
15803
  const displayRef = {
15763
15804
  ...chatRef,
15764
15805
  title: chatRef.title && chatRef.title !== chatRef.id ? chatRef.title : "Video",
15765
- url: chatRef.url ?? decoded.videoUrl ?? (decoded.youtubeUrl ? `https://www.youtube.com/watch?v=${decoded.youtubeUrl}` : null),
15766
- metadata: { ...chatRef.metadata ?? {}, ...decoded }
15806
+ url: chatRef.url ?? (typeof playableMetadata.videoUrl === "string" ? playableMetadata.videoUrl : null) ?? (typeof playableMetadata.youtubeUrl === "string" ? `https://www.youtube.com/watch?v=${playableMetadata.youtubeUrl}` : null),
15807
+ metadata: playableMetadata
15767
15808
  };
15768
15809
  return /* @__PURE__ */ jsx93(BlockCard, { inline: /* @__PURE__ */ jsx93(ChatInlineVideoPill, { chatRef: displayRef }), children: /* @__PURE__ */ jsx93(ChatVideoEntityCard, { chatRef: displayRef }) });
15769
15810
  }
@@ -17862,6 +17903,23 @@ function approvalDisplaysInline(approvalType, displayApprovalTypes) {
17862
17903
  return !displayApprovalTypes || displayApprovalTypes.includes(approvalType);
17863
17904
  }
17864
17905
 
17906
+ // src/components/chat/utils/chat-source-metadata.ts
17907
+ function mergeChatSources(current, incoming) {
17908
+ const sourcesByIndex = new Map((current ?? []).map((source) => [source.index, source]));
17909
+ incoming.forEach((source) => {
17910
+ if (!sourcesByIndex.has(source.index)) sourcesByIndex.set(source.index, source);
17911
+ });
17912
+ return Array.from(sourcesByIndex.values());
17913
+ }
17914
+ function mergeChatRefs(current, incoming) {
17915
+ const refsByIdentity = new Map((current ?? []).map((ref) => [`${ref.type}:${ref.id}`, ref]));
17916
+ incoming.forEach((ref) => {
17917
+ const identity = `${ref.type}:${ref.id}`;
17918
+ if (!refsByIdentity.has(identity)) refsByIdentity.set(identity, ref);
17919
+ });
17920
+ return Array.from(refsByIdentity.values());
17921
+ }
17922
+
17865
17923
  // src/components/chat/stream/message-mutations.ts
17866
17924
  function nextId(role) {
17867
17925
  return `${role}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
@@ -18721,6 +18779,8 @@ function createChatStreamReducer(options = {}) {
18721
18779
  const sourcesMap = /* @__PURE__ */ new Map();
18722
18780
  let sendCount = 0;
18723
18781
  let sseCurrentText = "";
18782
+ let pendingSources;
18783
+ let pendingRefs;
18724
18784
  let stateCache = null;
18725
18785
  function invalidate() {
18726
18786
  stateCache = null;
@@ -18782,6 +18842,17 @@ function createChatStreamReducer(options = {}) {
18782
18842
  }
18783
18843
  setMessagesInternal(stampTrailingAssistantSeq(updateTrailingAssistant(messages, segments), seq));
18784
18844
  }
18845
+ function attachPendingSourceMetadata() {
18846
+ if (!pendingSources && !pendingRefs) return;
18847
+ const last = messages[messages.length - 1];
18848
+ if (!last || last.role !== "assistant") return;
18849
+ const next = {
18850
+ ...last,
18851
+ ...pendingSources ? { sources: pendingSources } : {},
18852
+ ...pendingRefs ? { refs: pendingRefs } : {}
18853
+ };
18854
+ setMessagesInternal([...messages.slice(0, -1), next]);
18855
+ }
18785
18856
  function applyStreamStartToState() {
18786
18857
  setPhaseInternal("streaming");
18787
18858
  const adoptTail = adoptTrailingAssistant;
@@ -18900,6 +18971,8 @@ function createChatStreamReducer(options = {}) {
18900
18971
  }
18901
18972
  switch (event.type) {
18902
18973
  case "turn-start": {
18974
+ pendingSources = void 0;
18975
+ pendingRefs = void 0;
18903
18976
  isInStream = true;
18904
18977
  hasEverStreamed = true;
18905
18978
  turnStartedAt = Date.now();
@@ -18914,6 +18987,14 @@ function createChatStreamReducer(options = {}) {
18914
18987
  emit("onStreamEnd");
18915
18988
  setPhaseInternal("idle");
18916
18989
  accumulator.resetSegments();
18990
+ pendingSources = void 0;
18991
+ pendingRefs = void 0;
18992
+ break;
18993
+ }
18994
+ case "sources": {
18995
+ pendingSources = mergeChatSources(pendingSources, event.sources);
18996
+ if (event.refs) pendingRefs = mergeChatRefs(pendingRefs, event.refs);
18997
+ attachPendingSourceMetadata();
18917
18998
  break;
18918
18999
  }
18919
19000
  case "metadata": {
@@ -18942,10 +19023,12 @@ function createChatStreamReducer(options = {}) {
18942
19023
  if (isInStream || !hasEverStreamed) {
18943
19024
  emitSegments(segments);
18944
19025
  applySegmentsToState(segments, withSeqMeta(void 0));
19026
+ attachPendingSourceMetadata();
18945
19027
  } else {
18946
19028
  const delta = [{ type: kind, text: event.text }];
18947
19029
  emitSegments(delta, { append: true });
18948
19030
  applySegmentsToState(delta, withSeqMeta({ append: true }));
19031
+ attachPendingSourceMetadata();
18949
19032
  }
18950
19033
  break;
18951
19034
  }
@@ -20565,6 +20648,176 @@ function useSseChatAdapter(options, runtimeOptions = {}) {
20565
20648
  // src/components/chat/hooks/use-nats-chat-adapter.ts
20566
20649
  import { useCallback as useCallback21, useEffect as useEffect34, useMemo as useMemo26, useRef as useRef29, useState as useState40 } from "react";
20567
20650
 
20651
+ // src/chat-protocol/source-metadata.ts
20652
+ var CARD_REFERENCE = /^\[card:\/\/([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)\]$/;
20653
+ function requiredString(value) {
20654
+ if (typeof value !== "string") return null;
20655
+ const normalized = value.trim();
20656
+ return normalized ? normalized : null;
20657
+ }
20658
+ function optionalString(value) {
20659
+ return requiredString(value) ?? void 0;
20660
+ }
20661
+ function httpsUrl(value) {
20662
+ const candidate = optionalString(value);
20663
+ if (!candidate) return void 0;
20664
+ try {
20665
+ return new URL(candidate).protocol === "https:" ? candidate : void 0;
20666
+ } catch {
20667
+ return void 0;
20668
+ }
20669
+ }
20670
+ function youtubeId(value) {
20671
+ const candidate = optionalString(value);
20672
+ if (!candidate) return void 0;
20673
+ if (/^[A-Za-z0-9_-]{11}$/.test(candidate)) return candidate;
20674
+ const url = httpsUrl(candidate);
20675
+ if (!url) return void 0;
20676
+ const parsed = new URL(url);
20677
+ const hostname = parsed.hostname.toLowerCase();
20678
+ const youtubeHost = hostname === "youtu.be" || hostname === "youtube.com" || hostname.endsWith(".youtube.com");
20679
+ if (!youtubeHost) return void 0;
20680
+ const pathId = hostname === "youtu.be" ? parsed.pathname.split("/").filter(Boolean)[0] : parsed.pathname.match(/^\/(?:embed|v|shorts)\/([A-Za-z0-9_-]{11})(?:\/|$)/)?.[1];
20681
+ const id = pathId ?? parsed.searchParams.get("v") ?? void 0;
20682
+ return id && /^[A-Za-z0-9_-]{11}$/.test(id) ? id : void 0;
20683
+ }
20684
+ function nullableString(value) {
20685
+ return value === null ? null : optionalString(value);
20686
+ }
20687
+ function normalizeSourceItems(value) {
20688
+ if (!Array.isArray(value)) return void 0;
20689
+ const items = value.flatMap((candidate) => {
20690
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return [];
20691
+ const item = candidate;
20692
+ const id = requiredString(item.id);
20693
+ const documentType = requiredString(item.documentType);
20694
+ const name = requiredString(item.name);
20695
+ if (!id || !documentType || !name) return [];
20696
+ const externalUrl = httpsUrl(item.externalUrl);
20697
+ const targetPlatform = nullableString(item.targetPlatform);
20698
+ const path = nullableString(item.path);
20699
+ return [
20700
+ {
20701
+ id,
20702
+ documentType,
20703
+ name,
20704
+ ...externalUrl ? { externalUrl } : {},
20705
+ ...targetPlatform !== void 0 ? { targetPlatform } : {},
20706
+ ...path !== void 0 ? { path } : {}
20707
+ }
20708
+ ];
20709
+ });
20710
+ return items.length > 0 ? items : void 0;
20711
+ }
20712
+ function normalizeSources(value) {
20713
+ if (!Array.isArray(value)) return [];
20714
+ const seen = /* @__PURE__ */ new Set();
20715
+ const sources = [];
20716
+ for (const candidate of value) {
20717
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue;
20718
+ const source = candidate;
20719
+ const index = source.index;
20720
+ const name = requiredString(source.name);
20721
+ const path = requiredString(source.path);
20722
+ const documentType = requiredString(source.documentType);
20723
+ const validIndex = typeof index === "number" && Number.isFinite(index) && Number.isInteger(index) && index > 0 && !seen.has(index);
20724
+ if (!validIndex || !name || !path || !documentType) continue;
20725
+ seen.add(index);
20726
+ const externalUrl = httpsUrl(source.externalUrl);
20727
+ const targetPlatform = nullableString(source.targetPlatform);
20728
+ const id = optionalString(source.id);
20729
+ const sourceRepo = optionalString(source.sourceRepo);
20730
+ const label = optionalString(source.label);
20731
+ const items = normalizeSourceItems(source.items);
20732
+ sources.push({
20733
+ index,
20734
+ name,
20735
+ path,
20736
+ documentType,
20737
+ ...externalUrl ? { externalUrl } : {},
20738
+ ...targetPlatform !== void 0 ? { targetPlatform } : {},
20739
+ ...id ? { id } : {},
20740
+ ...sourceRepo ? { sourceRepo } : {},
20741
+ ...label ? { label } : {},
20742
+ ...items ? { items } : {}
20743
+ });
20744
+ }
20745
+ return sources;
20746
+ }
20747
+ function normalizeVideoMetadata(value) {
20748
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
20749
+ const input = value;
20750
+ const metadata = {};
20751
+ for (const key of ["videoUrl", "highlightVideoUrl", "videoPoster", "highlightVideoPoster"]) {
20752
+ const url = httpsUrl(input[key]);
20753
+ if (url) metadata[key] = url;
20754
+ }
20755
+ const id = youtubeId(input.youtubeUrl);
20756
+ if (id) metadata.youtubeUrl = id;
20757
+ return metadata;
20758
+ }
20759
+ function normalizeVideos(value) {
20760
+ if (!Array.isArray(value)) return [];
20761
+ return value.flatMap((candidate) => {
20762
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return [];
20763
+ const video = candidate;
20764
+ const match = typeof video.ref === "string" ? CARD_REFERENCE.exec(video.ref) : null;
20765
+ if (!match || match[1] !== "video") return [];
20766
+ const id = match[2];
20767
+ const title = optionalString(video.title) ?? id;
20768
+ const url = httpsUrl(video.url) ?? null;
20769
+ const sourceRepo = optionalString(video.sourceRepo);
20770
+ const metadata = normalizeVideoMetadata(video.metadata);
20771
+ if (url) {
20772
+ const idFromUrl = youtubeId(url);
20773
+ if (idFromUrl) {
20774
+ metadata.youtubeUrl = idFromUrl;
20775
+ delete metadata.videoUrl;
20776
+ } else {
20777
+ metadata.videoUrl = url;
20778
+ delete metadata.youtubeUrl;
20779
+ }
20780
+ }
20781
+ return [
20782
+ {
20783
+ type: "video",
20784
+ id,
20785
+ title,
20786
+ url,
20787
+ ...sourceRepo ? { sourceRepo } : {},
20788
+ ...Object.keys(metadata).length > 0 ? { metadata } : {}
20789
+ }
20790
+ ];
20791
+ });
20792
+ }
20793
+ function normalizeCards(value) {
20794
+ if (!Array.isArray(value)) return [];
20795
+ return value.flatMap((candidate) => {
20796
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return [];
20797
+ const card = candidate;
20798
+ const match = typeof card.ref === "string" ? CARD_REFERENCE.exec(card.ref) : null;
20799
+ if (!match || match[1] === "video") return [];
20800
+ const type = requiredString(card.entityType);
20801
+ const id = requiredString(card.entityId);
20802
+ if (!type || !id || type !== match[1] || id !== match[2]) return [];
20803
+ return [{ type, id, title: id, url: null }];
20804
+ });
20805
+ }
20806
+ function sourceMetadataEvent(payload) {
20807
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
20808
+ const metadata = payload;
20809
+ const sources = normalizeSources(metadata.sources);
20810
+ const refs = [...normalizeVideos(metadata.videos), ...normalizeCards(metadata.cards)];
20811
+ const refsByIdentity = /* @__PURE__ */ new Map();
20812
+ refs.forEach((ref) => {
20813
+ const identity = `${ref.type}:${ref.id}`;
20814
+ if (!refsByIdentity.has(identity)) refsByIdentity.set(identity, ref);
20815
+ });
20816
+ const uniqueRefs = Array.from(refsByIdentity.values());
20817
+ if (sources.length === 0 && uniqueRefs.length === 0) return null;
20818
+ return { type: "sources", sources, ...uniqueRefs.length > 0 ? { refs: uniqueRefs } : {} };
20819
+ }
20820
+
20568
20821
  // src/chat-protocol/nats-decoder.ts
20569
20822
  function isRecord2(value) {
20570
20823
  return typeof value === "object" && value !== null;
@@ -20633,6 +20886,11 @@ function decodeNatsChunk(chunk) {
20633
20886
  return { type: "thinking-delta", text: data.text, ...seq };
20634
20887
  }
20635
20888
  return null;
20889
+ case MESSAGE_TYPE.GUIDE:
20890
+ case MESSAGE_TYPE.SOURCES: {
20891
+ const event = sourceMetadataEvent(data.payload);
20892
+ return event ? { ...event, ...seq } : null;
20893
+ }
20636
20894
  // An ask card is only an ask card with something to pick: a question and at
20637
20895
  // least one option. Anything less is dropped rather than rendered as an
20638
20896
  // empty card the user can't answer — the backend already falls back to a
@@ -20984,6 +21242,9 @@ function decodeHistoricalMessageData(data) {
20984
21242
  return { type: "thinking-delta", text: data.text };
20985
21243
  }
20986
21244
  return null;
21245
+ case MESSAGE_TYPE.GUIDE:
21246
+ case MESSAGE_TYPE.SOURCES:
21247
+ return decodeNatsChunk({ type: data.type, payload: data.payload });
20987
21248
  // Same completeness gate as the live decoder (`decodeNatsChunk`): a
20988
21249
  // persisted row without a question or without options is not a card the
20989
21250
  // user can answer, so it replays as nothing rather than as empty chrome.
@@ -21339,6 +21600,8 @@ function processHistoricalMessages(messages, options = {}) {
21339
21600
  let currentAssistantTimestamp = null;
21340
21601
  let lastAssistantId = null;
21341
21602
  let currentAssistantStreamSeq;
21603
+ let currentAssistantSources;
21604
+ let currentAssistantRefs;
21342
21605
  const flushAssistantMessage = () => {
21343
21606
  const idToUse = lastAssistantId || currentAssistantId;
21344
21607
  if (idToUse && accumulator.hasContent()) {
@@ -21351,6 +21614,8 @@ function processHistoricalMessages(messages, options = {}) {
21351
21614
  authorType: assistantType,
21352
21615
  timestamp: currentAssistantTimestamp || /* @__PURE__ */ new Date(),
21353
21616
  avatar: assistantAvatar,
21617
+ ...currentAssistantSources ? { sources: currentAssistantSources } : {},
21618
+ ...currentAssistantRefs ? { refs: currentAssistantRefs } : {},
21354
21619
  ...currentAssistantStreamSeq !== void 0 ? { streamSeq: currentAssistantStreamSeq } : {}
21355
21620
  });
21356
21621
  accumulator.resetSegments();
@@ -21359,6 +21624,8 @@ function processHistoricalMessages(messages, options = {}) {
21359
21624
  currentAssistantTimestamp = null;
21360
21625
  lastAssistantId = null;
21361
21626
  currentAssistantStreamSeq = void 0;
21627
+ currentAssistantSources = void 0;
21628
+ currentAssistantRefs = void 0;
21362
21629
  };
21363
21630
  messages.forEach((msg, index) => {
21364
21631
  if (chatTypeFilter && msg.chatType !== chatTypeFilter) return;
@@ -21402,6 +21669,11 @@ function processHistoricalMessages(messages, options = {}) {
21402
21669
  messageDataArray.forEach((data) => {
21403
21670
  const event = decodeHistoricalMessageData(data);
21404
21671
  if (!event) return;
21672
+ if (event.type === "sources") {
21673
+ currentAssistantSources = mergeChatSources(currentAssistantSources, event.sources);
21674
+ if (event.refs) currentAssistantRefs = mergeChatRefs(currentAssistantRefs, event.refs);
21675
+ return;
21676
+ }
21405
21677
  applyHistoryEvent(
21406
21678
  event,
21407
21679
  accumulator,
@@ -21494,13 +21766,17 @@ function mapProcessedToUnified(processed) {
21494
21766
  id: m.id,
21495
21767
  role: m.role,
21496
21768
  content: "",
21497
- segments: m.content
21769
+ segments: m.content,
21770
+ ...m.sources ? { sources: m.sources } : {},
21771
+ ...m.refs ? { refs: m.refs } : {}
21498
21772
  });
21499
21773
  } else {
21500
21774
  out.push({
21501
21775
  id: m.id,
21502
21776
  role: m.role,
21503
- content: m.content
21777
+ content: m.content,
21778
+ ...m.sources ? { sources: m.sources } : {},
21779
+ ...m.refs ? { refs: m.refs } : {}
21504
21780
  });
21505
21781
  }
21506
21782
  }
@@ -23755,6 +24031,10 @@ function EmbeddableChatInner({
23755
24031
  // explicitly — see `Message.hidden` and `chat-message-list`'s skip.
23756
24032
  ...m.hidden ? { hidden: true } : {},
23757
24033
  ...m.scrollAnchor ? { scrollAnchor: m.scrollAnchor } : {},
24034
+ // Preserve same-turn rich metadata. Source chips and entity-card
24035
+ // renderers consume it after this raw-to-Message projection.
24036
+ ...m.sources && m.sources.length > 0 ? { sources: m.sources } : {},
24037
+ ...m.refs && m.refs.length > 0 ? { refs: m.refs } : {},
23758
24038
  // Forward attached context items so the user bubble renders its chips.
23759
24039
  ...m.contextItems && m.contextItems.length > 0 ? { contextItems: m.contextItems } : {}
23760
24040
  };
@@ -23911,7 +24191,7 @@ function EmbeddableChatInner({
23911
24191
  if (chatLoading) return void 0;
23912
24192
  const sources = lastAssistantMsg?.sources;
23913
24193
  if (!sources || sources.length === 0) return void 0;
23914
- const content = lastAssistantMsg?.content || "";
24194
+ const content = lastAssistantMsg.content || (lastAssistantMsg.segments ?? []).filter((segment) => segment.type === "text").map((segment) => segment.text).join("\n");
23915
24195
  const citationOrder = [...content.matchAll(/\[(\d+)\]/g)].map((m) => parseInt(m[1], 10));
23916
24196
  const seenOrder = /* @__PURE__ */ new Map();
23917
24197
  citationOrder.forEach((idx) => {
@@ -24848,4 +25128,4 @@ export {
24848
25128
  buildStreamFrames,
24849
25129
  EmbeddableChat
24850
25130
  };
24851
- //# sourceMappingURL=chunk-XWBND4G5.js.map
25131
+ //# sourceMappingURL=chunk-CO4WRST6.js.map