@flamingo-stack/openframe-frontend-core 0.0.588 → 0.0.589-1973.5571.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.
- package/dist/chat-protocol/events.d.ts +8 -2
- package/dist/chat-protocol/events.d.ts.map +1 -1
- package/dist/chat-protocol/index.cjs +188 -0
- package/dist/chat-protocol/index.cjs.map +1 -1
- package/dist/chat-protocol/index.js +188 -0
- package/dist/chat-protocol/index.js.map +1 -1
- package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
- package/dist/chat-protocol/source-metadata.d.ts +5 -0
- package/dist/chat-protocol/source-metadata.d.ts.map +1 -0
- package/dist/{chunk-GKPY7VLK.js → chunk-47NIV4FC.js} +4 -4
- package/dist/{chunk-D7YISHXC.js → chunk-4MVGFYRE.js} +534 -190
- package/dist/chunk-4MVGFYRE.js.map +1 -0
- package/dist/{chunk-I5CJJFEJ.js → chunk-4VDGIODG.js} +3 -3
- package/dist/{chunk-PAUJQV3C.cjs → chunk-627JVTID.cjs} +15 -15
- package/dist/{chunk-PAUJQV3C.cjs.map → chunk-627JVTID.cjs.map} +1 -1
- package/dist/{chunk-3OCYNMZ5.js → chunk-CQNILR7U.js} +3 -3
- package/dist/{chunk-BONXSRHZ.cjs → chunk-DPQGO4PH.cjs} +21 -21
- package/dist/{chunk-BONXSRHZ.cjs.map → chunk-DPQGO4PH.cjs.map} +1 -1
- package/dist/{chunk-A6X7EXP6.cjs → chunk-DVAAXDZV.cjs} +10 -10
- package/dist/{chunk-A6X7EXP6.cjs.map → chunk-DVAAXDZV.cjs.map} +1 -1
- package/dist/{chunk-CN36E4PK.js → chunk-EJEHIAOV.js} +2 -2
- package/dist/{chunk-G45RGWHF.cjs → chunk-EKKR5ZFB.cjs} +887 -543
- package/dist/chunk-EKKR5ZFB.cjs.map +1 -0
- package/dist/{chunk-IYDHV2CS.cjs → chunk-FIEOWSXH.cjs} +43 -43
- package/dist/{chunk-IYDHV2CS.cjs.map → chunk-FIEOWSXH.cjs.map} +1 -1
- package/dist/{chunk-CJOWLRUU.js → chunk-G4ND6ZZK.js} +2 -2
- package/dist/{chunk-KWTV3KGY.cjs → chunk-GDHQTVBP.cjs} +9 -9
- package/dist/{chunk-KWTV3KGY.cjs.map → chunk-GDHQTVBP.cjs.map} +1 -1
- package/dist/{chunk-ADANP3ZA.cjs → chunk-GOHHFX4O.cjs} +33 -33
- package/dist/{chunk-ADANP3ZA.cjs.map → chunk-GOHHFX4O.cjs.map} +1 -1
- package/dist/{chunk-KVNGYFFP.js → chunk-GU5TWGAJ.js} +3 -3
- package/dist/{chunk-EGYX55HT.cjs → chunk-HQKWJFFX.cjs} +159 -159
- package/dist/{chunk-EGYX55HT.cjs.map → chunk-HQKWJFFX.cjs.map} +1 -1
- package/dist/{chunk-NXZC6UTN.js → chunk-HZ6VUTLA.js} +2 -2
- package/dist/{chunk-WUCYI3BC.cjs → chunk-KH5KDKCE.cjs} +24 -24
- package/dist/{chunk-WUCYI3BC.cjs.map → chunk-KH5KDKCE.cjs.map} +1 -1
- package/dist/{chunk-JDINUWND.cjs → chunk-MQFA5DXU.cjs} +69 -69
- package/dist/{chunk-JDINUWND.cjs.map → chunk-MQFA5DXU.cjs.map} +1 -1
- package/dist/{chunk-5CHW7AMP.cjs → chunk-NHW2AM2X.cjs} +10 -10
- package/dist/{chunk-5CHW7AMP.cjs.map → chunk-NHW2AM2X.cjs.map} +1 -1
- package/dist/{chunk-KK3XU6YJ.cjs → chunk-NIHPN72N.cjs} +4 -4
- package/dist/{chunk-KK3XU6YJ.cjs.map → chunk-NIHPN72N.cjs.map} +1 -1
- package/dist/{chunk-EGUU3QCU.cjs → chunk-NNRRRWCD.cjs} +3 -3
- package/dist/{chunk-EGUU3QCU.cjs.map → chunk-NNRRRWCD.cjs.map} +1 -1
- package/dist/{chunk-4ZKAMPBX.js → chunk-OAY342OE.js} +2 -2
- package/dist/{chunk-TOYHZHTK.js → chunk-PDFH7CXB.js} +2 -2
- package/dist/{chunk-NIDPX52C.js → chunk-QIHBS5PM.js} +5 -5
- package/dist/{chunk-CNKVEVUN.js → chunk-SPOIDC23.js} +5 -5
- package/dist/{chunk-MENQLD57.cjs → chunk-T5FJOWHA.cjs} +5 -5
- package/dist/{chunk-MENQLD57.cjs.map → chunk-T5FJOWHA.cjs.map} +1 -1
- package/dist/{chunk-5FNWOF46.js → chunk-WZZSFOWG.js} +6 -6
- package/dist/{chunk-FVRRYJ6P.cjs → chunk-XIKSZSHC.cjs} +22 -22
- package/dist/{chunk-FVRRYJ6P.cjs.map → chunk-XIKSZSHC.cjs.map} +1 -1
- package/dist/{chunk-C6RJRSIM.cjs → chunk-XTMNO7MC.cjs} +3 -3
- package/dist/{chunk-C6RJRSIM.cjs.map → chunk-XTMNO7MC.cjs.map} +1 -1
- package/dist/{chunk-FQBR6SCA.js → chunk-XW4GOMK6.js} +3 -3
- package/dist/{chunk-77Y7R243.js → chunk-YEEMXD25.js} +5 -5
- package/dist/{chunk-LGBNHWDN.js → chunk-YTVTZ7JO.js} +3 -3
- package/dist/components/case-studies/index.cjs +9 -9
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-list.d.ts.map +1 -1
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +4 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-slash-commands.d.ts +3 -3
- package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +2 -28
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/remark-strip-citations.d.ts.map +1 -1
- package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
- package/dist/components/chat/tool-execution-display.d.ts.map +1 -1
- package/dist/components/chat/types/component.types.d.ts +4 -0
- package/dist/components/chat/types/component.types.d.ts.map +1 -1
- package/dist/components/chat/types/message.types.d.ts +35 -1
- package/dist/components/chat/types/message.types.d.ts.map +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +3 -3
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
- package/dist/components/chat/utils/chat-source-metadata.d.ts +5 -0
- package/dist/components/chat/utils/chat-source-metadata.d.ts.map +1 -0
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +36 -36
- package/dist/components/index.js +13 -13
- package/dist/components/meeting-scheduler/index.cjs +21 -21
- package/dist/components/meeting-scheduler/index.js +2 -2
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/hover-dropdown.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/index.cjs +7 -7
- package/dist/index.js +6 -6
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +2 -1
- package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +130 -0
- package/src/chat-protocol/__tests__/source-metadata.test.ts +74 -0
- package/src/chat-protocol/events.ts +9 -1
- package/src/chat-protocol/nats-decoder.ts +7 -0
- package/src/chat-protocol/source-metadata.ts +202 -0
- package/src/components/chat/__tests__/chat-message-list.test.tsx +70 -0
- package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +86 -0
- package/src/components/chat/__tests__/embeddable-chat-sources.test.tsx +411 -0
- package/src/components/chat/__tests__/remark-strip-citations.test.ts +52 -0
- package/src/components/chat/__tests__/tool-execution-display.test.tsx +93 -0
- package/src/components/chat/chat-message-enhanced.tsx +28 -13
- package/src/components/chat/chat-message-list.tsx +27 -22
- package/src/components/chat/embeddable-chat.tsx +55 -36
- package/src/components/chat/entity-cards/__tests__/dispatch-video-refs.test.tsx +74 -0
- package/src/components/chat/entity-cards/dispatch.tsx +86 -17
- package/src/components/chat/hooks/__tests__/use-slash-commands.test.ts +64 -0
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +7 -0
- package/src/components/chat/hooks/use-slash-commands.ts +9 -5
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +2 -29
- package/src/components/chat/remark-strip-citations.ts +5 -3
- package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +56 -0
- package/src/components/chat/stream/chat-stream-reducer.ts +31 -0
- package/src/components/chat/tool-execution-display.tsx +5 -2
- package/src/components/chat/types/component.types.ts +4 -0
- package/src/components/chat/types/message.types.ts +39 -0
- package/src/components/chat/types/unified-chat-state.types.ts +4 -3
- package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +57 -0
- package/src/components/chat/utils/chat-source-metadata.ts +19 -0
- package/src/components/chat/utils/process-historical-messages.ts +19 -1
- package/src/components/ui/hover-dropdown.tsx +8 -3
- package/dist/chunk-D7YISHXC.js.map +0 -1
- package/dist/chunk-G45RGWHF.cjs.map +0 -1
- /package/dist/{chunk-GKPY7VLK.js.map → chunk-47NIV4FC.js.map} +0 -0
- /package/dist/{chunk-I5CJJFEJ.js.map → chunk-4VDGIODG.js.map} +0 -0
- /package/dist/{chunk-3OCYNMZ5.js.map → chunk-CQNILR7U.js.map} +0 -0
- /package/dist/{chunk-CN36E4PK.js.map → chunk-EJEHIAOV.js.map} +0 -0
- /package/dist/{chunk-CJOWLRUU.js.map → chunk-G4ND6ZZK.js.map} +0 -0
- /package/dist/{chunk-KVNGYFFP.js.map → chunk-GU5TWGAJ.js.map} +0 -0
- /package/dist/{chunk-NXZC6UTN.js.map → chunk-HZ6VUTLA.js.map} +0 -0
- /package/dist/{chunk-4ZKAMPBX.js.map → chunk-OAY342OE.js.map} +0 -0
- /package/dist/{chunk-TOYHZHTK.js.map → chunk-PDFH7CXB.js.map} +0 -0
- /package/dist/{chunk-NIDPX52C.js.map → chunk-QIHBS5PM.js.map} +0 -0
- /package/dist/{chunk-CNKVEVUN.js.map → chunk-SPOIDC23.js.map} +0 -0
- /package/dist/{chunk-5FNWOF46.js.map → chunk-WZZSFOWG.js.map} +0 -0
- /package/dist/{chunk-FQBR6SCA.js.map → chunk-XW4GOMK6.js.map} +0 -0
- /package/dist/{chunk-77Y7R243.js.map → chunk-YEEMXD25.js.map} +0 -0
- /package/dist/{chunk-LGBNHWDN.js.map → chunk-YTVTZ7JO.js.map} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
Fragment,
|
|
4
5
|
useRef,
|
|
5
6
|
useState,
|
|
6
7
|
useCallback,
|
|
@@ -138,6 +139,7 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
|
|
|
138
139
|
renderContextItem,
|
|
139
140
|
renderMention,
|
|
140
141
|
onAskSelect,
|
|
142
|
+
renderAfterMessage,
|
|
141
143
|
NavLinkAnchor,
|
|
142
144
|
...props
|
|
143
145
|
},
|
|
@@ -986,28 +988,31 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
|
|
|
986
988
|
(!Array.isArray(message.content) || message.content.every(s => s.type === 'text'));
|
|
987
989
|
if (isEmptyPendingTurn) return null;
|
|
988
990
|
return (
|
|
989
|
-
<
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
991
|
+
<Fragment key={message.id}>
|
|
992
|
+
<ChatMessageEnhanced
|
|
993
|
+
ref={getRegisterMessageEl(message.id)}
|
|
994
|
+
role={message.role}
|
|
995
|
+
name={message.name}
|
|
996
|
+
content={message.content}
|
|
997
|
+
timestamp={message.timestamp}
|
|
998
|
+
isTyping={index === messages.length - 1 && isTyping && message.role === 'assistant'}
|
|
999
|
+
avatar={showAvatars ? message.avatar : null}
|
|
1000
|
+
showAvatar={showAvatars}
|
|
1001
|
+
assistantType={message.assistantType || assistantType}
|
|
1002
|
+
approvalVariant={approvalVariant}
|
|
1003
|
+
authorType={message.authorType}
|
|
1004
|
+
assistantIcon={message.role !== 'user' ? assistantIcon : undefined}
|
|
1005
|
+
contextItems={message.contextItems}
|
|
1006
|
+
resolveContextIcon={resolveContextIcon}
|
|
1007
|
+
renderContextItem={renderContextItem}
|
|
1008
|
+
renderMention={renderMention}
|
|
1009
|
+
renderEntityCard={renderEntityCard}
|
|
1010
|
+
refs={message.refs}
|
|
1011
|
+
onAskSelect={index > lastUserMessageIndex ? onAskSelect : undefined}
|
|
1012
|
+
NavLinkAnchor={NavLinkAnchor}
|
|
1013
|
+
/>
|
|
1014
|
+
{renderAfterMessage?.(message, index)}
|
|
1015
|
+
</Fragment>
|
|
1011
1016
|
);
|
|
1012
1017
|
})}
|
|
1013
1018
|
</div>
|
|
@@ -808,13 +808,14 @@ function SourceChips({
|
|
|
808
808
|
const [expanded, setExpanded] = useState(false);
|
|
809
809
|
|
|
810
810
|
if (cited.length === 0) {
|
|
811
|
-
const
|
|
812
|
-
|
|
811
|
+
const visible = expanded ? uncited : uncited.slice(0, FALLBACK_TOP_RETRIEVED);
|
|
812
|
+
const hiddenCount = Math.max(0, uncited.length - FALLBACK_TOP_RETRIEVED);
|
|
813
|
+
if (visible.length === 0) return null;
|
|
813
814
|
return (
|
|
814
815
|
<div className="mt-2 flex flex-col gap-1.5 border-t border-ods-border pt-2">
|
|
815
816
|
<span className="uppercase text-ods-text-muted text-h6">Top retrieved sources</span>
|
|
816
|
-
<div className=
|
|
817
|
-
{
|
|
817
|
+
<div className={`flex flex-wrap gap-1.5 ${expanded ? 'max-h-[200px] overflow-y-auto overscroll-contain' : ''}`}>
|
|
818
|
+
{visible.map(src => (
|
|
818
819
|
<SourceChip
|
|
819
820
|
key={src.index}
|
|
820
821
|
src={src}
|
|
@@ -824,6 +825,16 @@ function SourceChips({
|
|
|
824
825
|
onDiscuss={onDiscuss}
|
|
825
826
|
/>
|
|
826
827
|
))}
|
|
828
|
+
{hiddenCount > 0 && (
|
|
829
|
+
<button
|
|
830
|
+
onClick={() => setExpanded(!expanded)}
|
|
831
|
+
className="inline-flex cursor-pointer items-center gap-1 rounded border border-ods-accent bg-ods-card px-2 py-0.5 text-ods-accent transition-colors text-h6 hover:bg-ods-accent/10"
|
|
832
|
+
aria-expanded={expanded}
|
|
833
|
+
aria-label={expanded ? 'Show fewer sources' : `Show ${hiddenCount} additional retrieved sources`}
|
|
834
|
+
>
|
|
835
|
+
{expanded ? 'Show less' : `+${hiddenCount} more retrieved ${hiddenCount === 1 ? 'source' : 'sources'}`}
|
|
836
|
+
</button>
|
|
837
|
+
)}
|
|
827
838
|
</div>
|
|
828
839
|
</div>
|
|
829
840
|
);
|
|
@@ -1579,6 +1590,10 @@ function EmbeddableChatInner({
|
|
|
1579
1590
|
// explicitly — see `Message.hidden` and `chat-message-list`'s skip.
|
|
1580
1591
|
...(m.hidden ? { hidden: true } : {}),
|
|
1581
1592
|
...(m.scrollAnchor ? { scrollAnchor: m.scrollAnchor } : {}),
|
|
1593
|
+
// Preserve same-turn rich metadata. Source chips and entity-card
|
|
1594
|
+
// renderers consume it after this raw-to-Message projection.
|
|
1595
|
+
...(m.sources && m.sources.length > 0 ? { sources: m.sources } : {}),
|
|
1596
|
+
...(m.refs && m.refs.length > 0 ? { refs: m.refs } : {}),
|
|
1582
1597
|
// Forward attached context items so the user bubble renders its chips.
|
|
1583
1598
|
...(m.contextItems && m.contextItems.length > 0 ? { contextItems: m.contextItems } : {}),
|
|
1584
1599
|
};
|
|
@@ -1829,24 +1844,41 @@ function EmbeddableChatInner({
|
|
|
1829
1844
|
return out;
|
|
1830
1845
|
}, [commandsById, enabledSet]);
|
|
1831
1846
|
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
.
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1847
|
+
const renderSourcesAfterMessage = useCallback(
|
|
1848
|
+
(message: Message, index: number) => {
|
|
1849
|
+
if (message.role !== 'assistant' || !message.sources?.length) return null;
|
|
1850
|
+
if (chatLoading && index === messages.length - 1) return null;
|
|
1851
|
+
const content =
|
|
1852
|
+
typeof message.content === 'string'
|
|
1853
|
+
? message.content
|
|
1854
|
+
: message.content
|
|
1855
|
+
.filter(segment => segment.type === 'text')
|
|
1856
|
+
.map(segment => segment.text)
|
|
1857
|
+
.join('\n');
|
|
1858
|
+
const citationOrder = [...content.matchAll(/\[(\d+)\]/g)].map(m => parseInt(m[1], 10));
|
|
1859
|
+
const seenOrder = new Map<number, number>();
|
|
1860
|
+
citationOrder.forEach(idx => {
|
|
1861
|
+
if (!seenOrder.has(idx)) seenOrder.set(idx, seenOrder.size);
|
|
1862
|
+
});
|
|
1863
|
+
const cited = message.sources
|
|
1864
|
+
.filter(s => seenOrder.has(s.index))
|
|
1865
|
+
.sort((a, b) => (seenOrder.get(a.index) ?? 0) - (seenOrder.get(b.index) ?? 0));
|
|
1866
|
+
const uncited = message.sources.filter(s => !seenOrder.has(s.index));
|
|
1867
|
+
return (
|
|
1868
|
+
<div className="flex-shrink-0 pb-2">
|
|
1869
|
+
<SourceChips
|
|
1870
|
+
cited={cited}
|
|
1871
|
+
uncited={uncited}
|
|
1872
|
+
baseRoute={resolvedBaseRoute}
|
|
1873
|
+
chipBasePlatform={chipBasePlatform}
|
|
1874
|
+
onClose={handleNavigationClose}
|
|
1875
|
+
onDiscuss={discussRef}
|
|
1876
|
+
/>
|
|
1877
|
+
</div>
|
|
1878
|
+
);
|
|
1879
|
+
},
|
|
1880
|
+
[chatLoading, messages.length, resolvedBaseRoute, chipBasePlatform, handleNavigationClose, discussRef],
|
|
1881
|
+
);
|
|
1850
1882
|
|
|
1851
1883
|
// Host node for in-panel Radix portals (see the body wrapper below).
|
|
1852
1884
|
const [portalHost, setPortalHost] = useState<HTMLDivElement | null>(null);
|
|
@@ -2390,6 +2422,7 @@ function EmbeddableChatInner({
|
|
|
2390
2422
|
resolveContextIcon={resolveContextIcon}
|
|
2391
2423
|
renderContextItem={renderContextItem}
|
|
2392
2424
|
renderMention={renderMention}
|
|
2425
|
+
renderAfterMessage={renderSourcesAfterMessage}
|
|
2393
2426
|
// Gated on `chatLoading` for the same reason the composer
|
|
2394
2427
|
// is: no second send while a turn is in flight. Passive
|
|
2395
2428
|
// demo hosts (previewMode) stay read-only.
|
|
@@ -2427,20 +2460,6 @@ function EmbeddableChatInner({
|
|
|
2427
2460
|
onLoadMore={loadMoreMessages}
|
|
2428
2461
|
/>
|
|
2429
2462
|
)}
|
|
2430
|
-
{lastSources &&
|
|
2431
|
-
(lastSources.cited.length > 0 || lastSources.uncited.length > 0) &&
|
|
2432
|
-
!chatLoading && (
|
|
2433
|
-
<div className="flex-shrink-0 pb-2">
|
|
2434
|
-
<SourceChips
|
|
2435
|
-
cited={lastSources.cited}
|
|
2436
|
-
uncited={lastSources.uncited}
|
|
2437
|
-
baseRoute={resolvedBaseRoute}
|
|
2438
|
-
chipBasePlatform={chipBasePlatform}
|
|
2439
|
-
onClose={handleNavigationClose}
|
|
2440
|
-
onDiscuss={discussRef}
|
|
2441
|
-
/>
|
|
2442
|
-
</div>
|
|
2443
|
-
)}
|
|
2444
2463
|
</div>
|
|
2445
2464
|
) : activeMode === 'mingo' ? (
|
|
2446
2465
|
/* Figma node 7532:222444 — default (Mingo-mode) empty state:
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import type { ChatRef } from '../../chat-ref.types';
|
|
4
|
+
import { BlockCard } from '../block-card';
|
|
5
|
+
import { ChatVideoEntityCard } from '../chat-video-entity-card';
|
|
6
|
+
import { renderChatInlineEntityCard } from '../dispatch';
|
|
7
|
+
|
|
8
|
+
describe('renderChatInlineEntityCard video refs', () => {
|
|
9
|
+
it.each([
|
|
10
|
+
{
|
|
11
|
+
id: 'MdFJNoJeqZQ',
|
|
12
|
+
url: 'https://www.youtube.com/watch?v=MdFJNoJeqZQ',
|
|
13
|
+
metadata: { youtubeUrl: 'MdFJNoJeqZQ' },
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'mux-9b6586b494',
|
|
17
|
+
url: 'https://stream.mux.com/playback-id.m3u8',
|
|
18
|
+
metadata: { videoUrl: 'https://stream.mux.com/playback-id.m3u8' },
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'mp4-1b2047dc1b',
|
|
22
|
+
url: 'https://cdn.example.test/product-overview.mp4',
|
|
23
|
+
metadata: { videoUrl: 'https://cdn.example.test/product-overview.mp4' },
|
|
24
|
+
},
|
|
25
|
+
])('passes playable metadata for $id to the video card', ({ id, url, metadata }) => {
|
|
26
|
+
const ref: ChatRef = { type: 'video', id, title: 'Video', url, metadata };
|
|
27
|
+
|
|
28
|
+
const result = renderChatInlineEntityCard(ref);
|
|
29
|
+
|
|
30
|
+
expect(React.isValidElement(result)).toBe(true);
|
|
31
|
+
const block = result as React.ReactElement<React.ComponentProps<typeof BlockCard>>;
|
|
32
|
+
expect(block.type).toBe(BlockCard);
|
|
33
|
+
const video = block.props.children as React.ReactElement<React.ComponentProps<typeof ChatVideoEntityCard>>;
|
|
34
|
+
expect(video.type).toBe(ChatVideoEntityCard);
|
|
35
|
+
expect(video.props.chatRef.metadata).toMatchObject(metadata);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('lets a validated top-level URL override conflicting route metadata', () => {
|
|
39
|
+
const ref: ChatRef = {
|
|
40
|
+
type: 'video',
|
|
41
|
+
id: 'mux-9b6586b494',
|
|
42
|
+
title: 'Video',
|
|
43
|
+
url: 'https://stream.mux.com/trusted-playback.m3u8',
|
|
44
|
+
metadata: {
|
|
45
|
+
videoUrl: 'https://cdn.example.test/untrusted.mp4',
|
|
46
|
+
youtubeUrl: 'untrustedId',
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const result = renderChatInlineEntityCard(ref);
|
|
51
|
+
const block = result as React.ReactElement<React.ComponentProps<typeof BlockCard>>;
|
|
52
|
+
const video = block.props.children as React.ReactElement<React.ComponentProps<typeof ChatVideoEntityCard>>;
|
|
53
|
+
|
|
54
|
+
expect(video.props.chatRef.metadata).toEqual({
|
|
55
|
+
videoUrl: 'https://stream.mux.com/trusted-playback.m3u8',
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('keeps a direct-video URL direct when its query mentions a YouTube URL', () => {
|
|
60
|
+
const directUrl = 'https://cdn.example.test/product-overview.mp4?source=youtube.com/watch?v=MdFJNoJeqZQ';
|
|
61
|
+
const ref: ChatRef = {
|
|
62
|
+
type: 'video',
|
|
63
|
+
id: 'mp4-direct',
|
|
64
|
+
title: 'Product overview',
|
|
65
|
+
url: directUrl,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const result = renderChatInlineEntityCard(ref);
|
|
69
|
+
const block = result as React.ReactElement<React.ComponentProps<typeof BlockCard>>;
|
|
70
|
+
const video = block.props.children as React.ReactElement<React.ComponentProps<typeof ChatVideoEntityCard>>;
|
|
71
|
+
|
|
72
|
+
expect(video.props.chatRef.metadata).toEqual({ videoUrl: directUrl });
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import React, { type ReactNode } from 'react';
|
|
33
|
+
import { isSourceMetadataCardRef } from '../../../chat-protocol/source-metadata';
|
|
33
34
|
import { useRequiredChatRuntime } from '../../../contexts/chat-runtime-context';
|
|
34
35
|
import Image from '../../../embed-shims/next-image';
|
|
35
36
|
import { useRouter } from '../../../embed-shims/next-navigation';
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
formatTimeWithTimezone,
|
|
42
43
|
formatDurationFromRange,
|
|
43
44
|
} from '../../../utils/format';
|
|
45
|
+
import { extractYouTubeId } from '../../features/video';
|
|
44
46
|
import { MingoIcon } from '../../icons';
|
|
45
47
|
import { ArrowRightUpIcon } from '../../icons-v2-generated/arrows/arrow-right-up-icon';
|
|
46
48
|
import { ClickupLogoIcon } from '../../icons-v2-generated/brand-logos/clickup-logo-icon';
|
|
@@ -422,6 +424,15 @@ function hubspotStatusToVariant(status: string | undefined): MingoInfoCardStatus
|
|
|
422
424
|
* which renders the marker as plain text (same anti-hallucination
|
|
423
425
|
* posture as every other card type).
|
|
424
426
|
*/
|
|
427
|
+
function validatedHttpsUrl(value: unknown): string | null {
|
|
428
|
+
if (typeof value !== 'string') return null;
|
|
429
|
+
try {
|
|
430
|
+
return new URL(value).protocol === 'https:' ? value : null;
|
|
431
|
+
} catch {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
425
436
|
function decodeVideoMarkerId(id: string): { videoUrl?: string; youtubeUrl?: string } | null {
|
|
426
437
|
// Prefix checks FIRST: an id like `mux-1234567` (11 chars) would also
|
|
427
438
|
// satisfy the bare-YouTube-id pattern below.
|
|
@@ -439,8 +450,8 @@ function decodeVideoMarkerId(id: string): { videoUrl?: string; youtubeUrl?: stri
|
|
|
439
450
|
const url = atob(b64);
|
|
440
451
|
// https-only — the id is model-emitted text; never let a marker
|
|
441
452
|
// smuggle a non-https src into a player.
|
|
442
|
-
|
|
443
|
-
return { videoUrl:
|
|
453
|
+
const safeUrl = validatedHttpsUrl(url);
|
|
454
|
+
return safeUrl ? { videoUrl: safeUrl } : null;
|
|
444
455
|
} catch {
|
|
445
456
|
return null;
|
|
446
457
|
}
|
|
@@ -449,6 +460,29 @@ function decodeVideoMarkerId(id: string): { videoUrl?: string; youtubeUrl?: stri
|
|
|
449
460
|
return null;
|
|
450
461
|
}
|
|
451
462
|
|
|
463
|
+
function validatedVideoMetadata(chatRef: ChatRef): Record<string, unknown> | null {
|
|
464
|
+
const input = chatRef.metadata ?? {};
|
|
465
|
+
const metadata: Record<string, unknown> = {};
|
|
466
|
+
for (const key of ['videoUrl', 'highlightVideoUrl', 'videoPoster', 'highlightVideoPoster'] as const) {
|
|
467
|
+
const url = validatedHttpsUrl(input[key]);
|
|
468
|
+
if (url) metadata[key] = url;
|
|
469
|
+
}
|
|
470
|
+
const youtubeUrl = input.youtubeUrl;
|
|
471
|
+
if (typeof youtubeUrl === 'string' && /^[A-Za-z0-9_-]{11}$/.test(youtubeUrl)) {
|
|
472
|
+
metadata.youtubeUrl = youtubeUrl;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const safeUrl = validatedHttpsUrl(chatRef.url);
|
|
476
|
+
if (safeUrl) {
|
|
477
|
+
const youtubeId = extractYouTubeId(safeUrl);
|
|
478
|
+
delete metadata.videoUrl;
|
|
479
|
+
delete metadata.youtubeUrl;
|
|
480
|
+
metadata[youtubeId ? 'youtubeUrl' : 'videoUrl'] = youtubeId ?? safeUrl;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return Object.keys(metadata).length > 0 ? metadata : null;
|
|
484
|
+
}
|
|
485
|
+
|
|
452
486
|
/** Read hero-video metadata off a FETCHED item — the API-driven
|
|
453
487
|
* replacement for the retired refs-frame `metadata.videoUrl` check. Two item
|
|
454
488
|
* shapes: ChatRef-shaped items (per-object hydration routes) carry the
|
|
@@ -1228,6 +1262,10 @@ interface ChatCardRegistryEntry {
|
|
|
1228
1262
|
* point — a host that re-homes a type must be able to say so even when
|
|
1229
1263
|
* the row already carries the content host's own url. */
|
|
1230
1264
|
noComposedHref?: boolean;
|
|
1265
|
+
/** Render a document card from same-turn SOURCES metadata when its optional
|
|
1266
|
+
* hydration endpoint has no row. Only doc refs opt in: their source record
|
|
1267
|
+
* already carries the complete title/path/navigation fallback. */
|
|
1268
|
+
renderFromSourceMetadata?: boolean;
|
|
1231
1269
|
}
|
|
1232
1270
|
|
|
1233
1271
|
/**
|
|
@@ -1250,6 +1288,7 @@ function refHydratedEntry(
|
|
|
1250
1288
|
docType: string,
|
|
1251
1289
|
label: string,
|
|
1252
1290
|
renderRef: (displayRef: ChatRef, opts: ChatCardRenderOptions) => React.ReactNode,
|
|
1291
|
+
renderFromSourceMetadata = false,
|
|
1253
1292
|
): ChatCardRegistryEntry {
|
|
1254
1293
|
return {
|
|
1255
1294
|
label,
|
|
@@ -1259,6 +1298,19 @@ function refHydratedEntry(
|
|
|
1259
1298
|
fallbackHref: (item: { url?: string | null }) => item?.url ?? null,
|
|
1260
1299
|
skeleton: () => <MingoInfoCardSkeleton />,
|
|
1261
1300
|
render: (item, chatRef, opts) => renderRef(fetchedItemDisplayRef(item, chatRef), opts),
|
|
1301
|
+
renderFromSourceMetadata,
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
function sourceMetadataCardItem(chatRef: ChatRef): ChatCardItem {
|
|
1306
|
+
return {
|
|
1307
|
+
type: chatRef.type,
|
|
1308
|
+
id: chatRef.id,
|
|
1309
|
+
title: chatRef.title,
|
|
1310
|
+
url: chatRef.url,
|
|
1311
|
+
targetPlatform: chatRef.targetPlatform,
|
|
1312
|
+
sourceRepo: chatRef.sourceRepo,
|
|
1313
|
+
metadata: chatRef.metadata,
|
|
1262
1314
|
};
|
|
1263
1315
|
}
|
|
1264
1316
|
|
|
@@ -1454,12 +1506,18 @@ const CHAT_CARD_REGISTRY: Record<string, ChatCardRegistryEntry> = {
|
|
|
1454
1506
|
hubspot_ticket_self: refHydratedEntry('hubspot_ticket_self', 'HubSpot ticket (self)', (displayRef, opts) => (
|
|
1455
1507
|
<HubspotTicketChatCard chatRef={displayRef} isNewTab={opts.isNewTab} discuss={opts.discuss} />
|
|
1456
1508
|
)),
|
|
1457
|
-
data_room_doc: refHydratedEntry(
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
)
|
|
1509
|
+
data_room_doc: refHydratedEntry(
|
|
1510
|
+
'data_room_doc',
|
|
1511
|
+
'Data-room doc',
|
|
1512
|
+
(displayRef, opts) => <DataRoomDocChatCard chatRef={displayRef} isNewTab={opts.isNewTab} discuss={opts.discuss} />,
|
|
1513
|
+
true,
|
|
1514
|
+
),
|
|
1515
|
+
markdown: refHydratedEntry(
|
|
1516
|
+
'markdown',
|
|
1517
|
+
'Doc page (markdown)',
|
|
1518
|
+
(displayRef, opts) => <DataRoomDocChatCard chatRef={displayRef} isNewTab={opts.isNewTab} discuss={opts.discuss} />,
|
|
1519
|
+
true,
|
|
1520
|
+
),
|
|
1463
1521
|
// Body-synthesized video ids (`shortVideoId` content hashes / YouTube
|
|
1464
1522
|
// ids) have no backing table row to fetch — ref-only by nature.
|
|
1465
1523
|
video: {
|
|
@@ -1902,6 +1960,13 @@ export function ChatCardLoader({
|
|
|
1902
1960
|
}
|
|
1903
1961
|
if (isLoading) return <>{fetchEntry.skeleton?.() ?? null}</>;
|
|
1904
1962
|
if (!item) {
|
|
1963
|
+
const hasSourceMetadata =
|
|
1964
|
+
fetchEntry.renderFromSourceMetadata &&
|
|
1965
|
+
isSourceMetadataCardRef(chatRef) &&
|
|
1966
|
+
typeof finalChatRef.metadata?.path === 'string';
|
|
1967
|
+
if (hasSourceMetadata) {
|
|
1968
|
+
return finish(fetchEntry.render(sourceMetadataCardItem(finalChatRef), finalChatRef, renderOpts));
|
|
1969
|
+
}
|
|
1905
1970
|
// FAIL LOUD (explicit product decision 2026-08-13): a card that
|
|
1906
1971
|
// cannot hydrate renders a VISIBLE error card — silent removal made
|
|
1907
1972
|
// broken answers look fine. Three distinguishable states:
|
|
@@ -1983,24 +2048,28 @@ export function renderChatInlineEntityCard(
|
|
|
1983
2048
|
/>
|
|
1984
2049
|
);
|
|
1985
2050
|
|
|
1986
|
-
//
|
|
1987
|
-
//
|
|
1988
|
-
//
|
|
1989
|
-
//
|
|
2051
|
+
// SOURCES metadata may enrich an opaque video marker with its playable URL.
|
|
2052
|
+
// Self-describing marker ids remain transport-independent through
|
|
2053
|
+
// `decodeVideoMarkerId`; undecodable ids without matching metadata fall
|
|
2054
|
+
// through to the loader and degrade to plain text.
|
|
1990
2055
|
// Hero videos on FETCHED entity types (webinars, releases, …) are
|
|
1991
2056
|
// handled post-fetch inside ChatCardLoader (`itemVideoMetadata`) — the
|
|
1992
|
-
// marker
|
|
2057
|
+
// marker metadata is not required for that post-fetch path.
|
|
1993
2058
|
if (chatRef.type === 'video') {
|
|
2059
|
+
const enrichedMetadata = validatedVideoMetadata(chatRef);
|
|
1994
2060
|
const decoded = decodeVideoMarkerId(chatRef.id);
|
|
1995
|
-
|
|
2061
|
+
const playableMetadata = enrichedMetadata ?? decoded;
|
|
2062
|
+
if (playableMetadata) {
|
|
1996
2063
|
const displayRef: ChatRef = {
|
|
1997
2064
|
...chatRef,
|
|
1998
2065
|
title: chatRef.title && chatRef.title !== chatRef.id ? chatRef.title : 'Video',
|
|
1999
2066
|
url:
|
|
2000
2067
|
chatRef.url ??
|
|
2001
|
-
|
|
2002
|
-
(
|
|
2003
|
-
|
|
2068
|
+
(typeof playableMetadata.videoUrl === 'string' ? playableMetadata.videoUrl : null) ??
|
|
2069
|
+
(typeof playableMetadata.youtubeUrl === 'string'
|
|
2070
|
+
? `https://www.youtube.com/watch?v=${playableMetadata.youtubeUrl}`
|
|
2071
|
+
: null),
|
|
2072
|
+
metadata: playableMetadata,
|
|
2004
2073
|
};
|
|
2005
2074
|
return (
|
|
2006
2075
|
<BlockCard inline={<ChatInlineVideoPill chatRef={displayRef} />}>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { SlashCommandSummary } from '../../types/component.types';
|
|
3
|
+
|
|
4
|
+
const { embedAuthedFetchMock } = vi.hoisted(() => ({
|
|
5
|
+
embedAuthedFetchMock: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
vi.mock('../../../../utils/embed-authed-fetch', () => ({
|
|
9
|
+
embedAuthedFetch: embedAuthedFetchMock,
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
import { fetchSlashCommands } from '../use-slash-commands';
|
|
13
|
+
|
|
14
|
+
const command = (id: string): SlashCommandSummary => ({
|
|
15
|
+
id,
|
|
16
|
+
description: `${id} description`,
|
|
17
|
+
actions: [{ id: 'browse', label: 'Recent' }],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const commandsUrl = 'https://hub.example/api/docs/commands';
|
|
21
|
+
|
|
22
|
+
describe('fetchSlashCommands', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
embedAuthedFetchMock.mockReset();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('fetches the full registry and filters normalized prefixes locally', async () => {
|
|
28
|
+
embedAuthedFetchMock.mockResolvedValue({
|
|
29
|
+
ok: true,
|
|
30
|
+
json: () => Promise.resolve({ commands: [command('roadmap'), command('webinars')] }),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const commands = await fetchSlashCommands(' W ', undefined, commandsUrl);
|
|
34
|
+
|
|
35
|
+
expect(commands.map(({ id }) => id)).toEqual(['webinars']);
|
|
36
|
+
expect(embedAuthedFetchMock).toHaveBeenCalledTimes(1);
|
|
37
|
+
const [requestedUrl] = embedAuthedFetchMock.mock.calls[0] as [string];
|
|
38
|
+
expect(new URL(requestedUrl).search).toBe('');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('returns every command for an empty prefix', async () => {
|
|
42
|
+
const registry = [command('roadmap'), command('webinars')];
|
|
43
|
+
embedAuthedFetchMock.mockResolvedValue({
|
|
44
|
+
ok: true,
|
|
45
|
+
json: () => Promise.resolve({ commands: registry }),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await expect(fetchSlashCommands('', undefined, commandsUrl)).resolves.toEqual(registry);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('caps non-empty prefix matches at ten commands', async () => {
|
|
52
|
+
embedAuthedFetchMock.mockResolvedValue({
|
|
53
|
+
ok: true,
|
|
54
|
+
json: () =>
|
|
55
|
+
Promise.resolve({
|
|
56
|
+
commands: Array.from({ length: 12 }, (_, index) => command(`webinar-${index}`)),
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const commands = await fetchSlashCommands('webinar', undefined, commandsUrl);
|
|
61
|
+
|
|
62
|
+
expect(commands).toHaveLength(10);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -61,6 +61,7 @@ import type {
|
|
|
61
61
|
MessageSegment,
|
|
62
62
|
NatsMessageType,
|
|
63
63
|
ChatApprovalStatus,
|
|
64
|
+
ChatSource,
|
|
64
65
|
} from '../types';
|
|
65
66
|
import type { DialogItem } from '../types/component.types';
|
|
66
67
|
import type {
|
|
@@ -352,6 +353,8 @@ export function mapProcessedToUnified(
|
|
|
352
353
|
role: 'user' | 'assistant' | 'error';
|
|
353
354
|
content: string | MessageSegment[];
|
|
354
355
|
name?: string;
|
|
356
|
+
sources?: ChatSource[];
|
|
357
|
+
refs?: ChatRef[];
|
|
355
358
|
}>,
|
|
356
359
|
): UnifiedChatMessage[] {
|
|
357
360
|
const out: UnifiedChatMessage[] = [];
|
|
@@ -363,12 +366,16 @@ export function mapProcessedToUnified(
|
|
|
363
366
|
role: m.role,
|
|
364
367
|
content: '',
|
|
365
368
|
segments: m.content,
|
|
369
|
+
...(m.sources ? { sources: m.sources } : {}),
|
|
370
|
+
...(m.refs ? { refs: m.refs } : {}),
|
|
366
371
|
});
|
|
367
372
|
} else {
|
|
368
373
|
out.push({
|
|
369
374
|
id: m.id,
|
|
370
375
|
role: m.role,
|
|
371
376
|
content: m.content,
|
|
377
|
+
...(m.sources ? { sources: m.sources } : {}),
|
|
378
|
+
...(m.refs ? { refs: m.refs } : {}),
|
|
372
379
|
});
|
|
373
380
|
}
|
|
374
381
|
}
|
|
@@ -24,10 +24,12 @@ import type { SlashCommandActionId, SlashCommandSummaryAction, SlashCommandSumma
|
|
|
24
24
|
// canonical declaration.
|
|
25
25
|
export type { SlashCommandActionId, SlashCommandSummaryAction, SlashCommandSummary };
|
|
26
26
|
|
|
27
|
+
const COMMANDS_MAX_RESULTS = 10;
|
|
28
|
+
|
|
27
29
|
/**
|
|
28
|
-
* Fetch slash-command
|
|
29
|
-
*
|
|
30
|
-
* client sends NO `source` param on the URL.
|
|
30
|
+
* Fetch the full slash-command registry for the CURRENT deployment platform,
|
|
31
|
+
* then match id-prefix `prefix` locally. The chat source is server-resolved —
|
|
32
|
+
* the client sends NO `source` param on the URL.
|
|
31
33
|
*
|
|
32
34
|
* `commandsUrl` is required — supplied by `useChatRuntime().endpoints.commandsUrl`
|
|
33
35
|
* at the call site. NO default value.
|
|
@@ -56,13 +58,15 @@ export async function fetchSlashCommands(
|
|
|
56
58
|
): Promise<SlashCommandSummary[]> {
|
|
57
59
|
try {
|
|
58
60
|
const url = new URL(commandsUrl, window.location.origin);
|
|
59
|
-
if (prefix) url.searchParams.set('q', prefix);
|
|
60
61
|
// `headers: {}` opts out of the default `Content-Type: application/json`
|
|
61
62
|
// — this is a bare GET with no body, so no content-type is needed.
|
|
62
63
|
const res = await embedAuthedFetch(url.toString(), { signal, headers: {} });
|
|
63
64
|
if (!res.ok) return [];
|
|
64
65
|
const data = (await res.json()) as { commands?: SlashCommandSummary[] };
|
|
65
|
-
|
|
66
|
+
const commands = data.commands ?? [];
|
|
67
|
+
const normalizedPrefix = prefix.trim().toLowerCase();
|
|
68
|
+
if (!normalizedPrefix) return commands;
|
|
69
|
+
return commands.filter(({ id }) => id.startsWith(normalizedPrefix)).slice(0, COMMANDS_MAX_RESULTS);
|
|
66
70
|
} catch (err) {
|
|
67
71
|
// Cancellation (unmount / dep change) MUST propagate so react-query treats
|
|
68
72
|
// it as cancelled, not as a successful empty result. Every OTHER failure
|
|
@@ -63,7 +63,7 @@ import type { ChatDialogStore } from '../stream/chat-dialog-store';
|
|
|
63
63
|
import type { ChatStreamReducerOptions } from '../stream/chat-stream-reducer';
|
|
64
64
|
import { useChatStreamReducer } from '../stream/use-chat-stream-reducer';
|
|
65
65
|
import type { DialogItem } from '../types/component.types';
|
|
66
|
-
import type { Message, MessageSegment } from '../types/message.types';
|
|
66
|
+
import type { ChatSource, Message, MessageSegment } from '../types/message.types';
|
|
67
67
|
import type {
|
|
68
68
|
UnifiedChatState,
|
|
69
69
|
UnifiedChatMessage,
|
|
@@ -81,6 +81,7 @@ import { useSlashCommandRegistry, type SlashCommandSummary } from './use-slash-c
|
|
|
81
81
|
// Canonical home of the per-turn meta row moved to the stream module in
|
|
82
82
|
// Phase 3; re-exported here to keep the legacy import path stable.
|
|
83
83
|
export type { ChatTurnMeta } from '../stream/chat-stream-reducer';
|
|
84
|
+
export type { ChatSource } from '../types/message.types';
|
|
84
85
|
|
|
85
86
|
// ─── Stable no-op references for the Guide-mode dialog-management stubs ──
|
|
86
87
|
// Plain module-scope constants so the adapter's return identity stays
|
|
@@ -128,34 +129,6 @@ const noopBumpMetaTick = (): void => {};
|
|
|
128
129
|
* hub-side platform-utils, not in lib). */
|
|
129
130
|
export type DocSource = string;
|
|
130
131
|
|
|
131
|
-
export interface ChatSource {
|
|
132
|
-
index: number;
|
|
133
|
-
name: string;
|
|
134
|
-
path: string;
|
|
135
|
-
documentType: string;
|
|
136
|
-
externalUrl?: string;
|
|
137
|
-
/** Platform that owns the destination at `externalUrl`. */
|
|
138
|
-
targetPlatform?: string | null;
|
|
139
|
-
/** Primary-key value for single-row chips. */
|
|
140
|
-
id?: string;
|
|
141
|
-
/** Per-row items for grouped chips. */
|
|
142
|
-
items?: Array<{
|
|
143
|
-
id: string;
|
|
144
|
-
documentType: string;
|
|
145
|
-
name: string;
|
|
146
|
-
externalUrl?: string;
|
|
147
|
-
targetPlatform?: string | null;
|
|
148
|
-
/** In-app doc-tree path for markdown / data-room-doc rows so the
|
|
149
|
-
* grouped chip's anchor can trigger an in-page doc-tree swap via
|
|
150
|
-
* `handleChatNavClick` (parity with single-row chips + cards). */
|
|
151
|
-
path?: string | null;
|
|
152
|
-
}>;
|
|
153
|
-
/** RagTableConfig.id for this source. */
|
|
154
|
-
sourceRepo?: string;
|
|
155
|
-
/** Optional display label override returned by the chat API. */
|
|
156
|
-
label?: string;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
132
|
export interface DocChatMessage {
|
|
160
133
|
id: string;
|
|
161
134
|
role: 'user' | 'assistant';
|