@flamingo-stack/openframe-frontend-core 0.0.685 → 0.0.686-2463.7461.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/{chunk-T6KWNJDW.js → chunk-22RSJ57Z.js} +470 -465
- package/dist/chunk-22RSJ57Z.js.map +1 -0
- package/dist/{chunk-65KTXRBY.js → chunk-2G2CB7ZZ.js} +6 -6
- package/dist/{chunk-K2HZG4RM.cjs → chunk-2OD2PPNO.cjs} +32 -32
- package/dist/{chunk-K2HZG4RM.cjs.map → chunk-2OD2PPNO.cjs.map} +1 -1
- package/dist/{chunk-HMXUOLZR.cjs → chunk-3DO76UAB.cjs} +20 -20
- package/dist/{chunk-HMXUOLZR.cjs.map → chunk-3DO76UAB.cjs.map} +1 -1
- package/dist/{chunk-XW7MUIW4.js → chunk-3NIZ2VLQ.js} +3 -3
- package/dist/{chunk-5LJZPJHX.cjs → chunk-4LBCUBVL.cjs} +59 -59
- package/dist/{chunk-5LJZPJHX.cjs.map → chunk-4LBCUBVL.cjs.map} +1 -1
- package/dist/{chunk-ZF4Z36G4.js → chunk-4RJQMOQW.js} +7 -7
- package/dist/{chunk-TJI5V5BR.cjs → chunk-AF4XOURN.cjs} +7 -7
- package/dist/{chunk-TJI5V5BR.cjs.map → chunk-AF4XOURN.cjs.map} +1 -1
- package/dist/{chunk-7XF63THA.js → chunk-ASBBGP36.js} +6 -6
- package/dist/{chunk-O7LOZW4R.js → chunk-C5TFPMOM.js} +5 -5
- package/dist/{chunk-CMQXPD5C.cjs → chunk-GETQTNRV.cjs} +28 -28
- package/dist/{chunk-CMQXPD5C.cjs.map → chunk-GETQTNRV.cjs.map} +1 -1
- package/dist/{chunk-TUG7IT6H.js → chunk-HLEXYTOE.js} +2 -2
- package/dist/{chunk-M4HYIXP6.cjs → chunk-IBUFFKAK.cjs} +399 -383
- package/dist/chunk-IBUFFKAK.cjs.map +1 -0
- package/dist/{chunk-GXV6UEVY.js → chunk-IELOPDTA.js} +3 -3
- package/dist/{chunk-T4DUTP5C.cjs → chunk-II5GVLTN.cjs} +5 -5
- package/dist/{chunk-T4DUTP5C.cjs.map → chunk-II5GVLTN.cjs.map} +1 -1
- package/dist/{chunk-B32YLOYQ.js → chunk-JBJZL5PI.js} +4 -4
- package/dist/{chunk-UT333EOT.js → chunk-LCOMNQLX.js} +4 -4
- package/dist/{chunk-VDPCK437.js → chunk-MHTB67ZH.js} +21 -5
- package/dist/{chunk-VDPCK437.js.map → chunk-MHTB67ZH.js.map} +1 -1
- package/dist/{chunk-EFBMNVSH.cjs → chunk-MZAZUSPQ.cjs} +15 -15
- package/dist/{chunk-EFBMNVSH.cjs.map → chunk-MZAZUSPQ.cjs.map} +1 -1
- package/dist/{chunk-C3B36OYZ.cjs → chunk-N3I53B5W.cjs} +3 -3
- package/dist/{chunk-C3B36OYZ.cjs.map → chunk-N3I53B5W.cjs.map} +1 -1
- package/dist/{chunk-WJK2EETW.js → chunk-NINO3PI7.js} +2 -2
- package/dist/{chunk-YCTVLN47.cjs → chunk-P4SBHO7U.cjs} +10 -10
- package/dist/{chunk-YCTVLN47.cjs.map → chunk-P4SBHO7U.cjs.map} +1 -1
- package/dist/{chunk-PSQKD6ZN.js → chunk-PG65AE3I.js} +3 -3
- package/dist/{chunk-DQOQ3KTP.cjs → chunk-QNWQQ4RT.cjs} +78 -73
- package/dist/chunk-QNWQQ4RT.cjs.map +1 -0
- package/dist/{chunk-BZ2XOEHW.js → chunk-QOH45QSE.js} +3 -3
- package/dist/{chunk-DWXWEGGO.cjs → chunk-QTXDXHMD.cjs} +22 -22
- package/dist/{chunk-DWXWEGGO.cjs.map → chunk-QTXDXHMD.cjs.map} +1 -1
- package/dist/{chunk-B25CF3PO.cjs → chunk-RBCMBRI7.cjs} +30 -30
- package/dist/{chunk-B25CF3PO.cjs.map → chunk-RBCMBRI7.cjs.map} +1 -1
- package/dist/{chunk-QHLJSB2J.cjs → chunk-RKH7DEDF.cjs} +12 -12
- package/dist/{chunk-QHLJSB2J.cjs.map → chunk-RKH7DEDF.cjs.map} +1 -1
- package/dist/{chunk-Y7ZYOQNB.js → chunk-RSMAVO2Z.js} +3 -3
- package/dist/{chunk-A3CSQB4A.cjs → chunk-SI77HF4U.cjs} +1247 -805
- package/dist/chunk-SI77HF4U.cjs.map +1 -0
- package/dist/{chunk-GHLGXFNU.js → chunk-U6JC3RS2.js} +3 -3
- package/dist/{chunk-B5N3AHPG.cjs → chunk-UBXEETWQ.cjs} +81 -81
- package/dist/{chunk-B5N3AHPG.cjs.map → chunk-UBXEETWQ.cjs.map} +1 -1
- package/dist/{chunk-N4Z6X5BV.js → chunk-WFH52OVQ.js} +5 -5
- package/dist/{chunk-U3IXG7TR.cjs → chunk-WJX3ZSOE.cjs} +33 -33
- package/dist/{chunk-U3IXG7TR.cjs.map → chunk-WJX3ZSOE.cjs.map} +1 -1
- package/dist/{chunk-XGX6JSEU.js → chunk-YPXXXX6R.js} +2086 -1644
- package/dist/chunk-YPXXXX6R.js.map +1 -0
- package/dist/{chunk-P2ASEPA2.cjs → chunk-ZS3YCJYY.cjs} +6 -6
- package/dist/{chunk-P2ASEPA2.cjs.map → chunk-ZS3YCJYY.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +16 -16
- package/dist/components/case-studies/index.js +4 -4
- package/dist/components/chat/ai-assistant-info.d.ts.map +1 -1
- package/dist/components/chat/approval-request-message.d.ts.map +1 -1
- package/dist/components/chat/chat-appearance-context.d.ts +9 -0
- package/dist/components/chat/chat-appearance-context.d.ts.map +1 -0
- package/dist/components/chat/chat-input.d.ts.map +1 -1
- 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/chat-nav-sidebar.d.ts +50 -0
- package/dist/components/chat/chat-nav-sidebar.d.ts.map +1 -0
- package/dist/components/chat/chat-top-navigation.d.ts +29 -0
- package/dist/components/chat/chat-top-navigation.d.ts.map +1 -0
- package/dist/components/chat/error-message-display.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +21 -3
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +6 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +20 -2
- package/dist/components/chat/msp-organization-card.d.ts +7 -1
- package/dist/components/chat/msp-organization-card.d.ts.map +1 -1
- package/dist/components/chat/onboarding-carousel.d.ts +29 -0
- package/dist/components/chat/onboarding-carousel.d.ts.map +1 -0
- package/dist/components/chat/system-event-message.d.ts +14 -0
- package/dist/components/chat/system-event-message.d.ts.map +1 -0
- package/dist/components/chat/types/chat.types.d.ts +13 -0
- package/dist/components/chat/types/chat.types.d.ts.map +1 -1
- package/dist/components/chat/types/component.types.d.ts +20 -1
- package/dist/components/chat/types/component.types.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +5 -5
- package/dist/components/contact/index.js +4 -4
- package/dist/components/docs/index.cjs +8 -8
- package/dist/components/docs/index.js +7 -7
- package/dist/components/embeds/index.cjs +5 -5
- package/dist/components/embeds/index.js +4 -4
- package/dist/components/faq/index.cjs +5 -5
- package/dist/components/faq/index.js +4 -4
- package/dist/components/features/index.cjs +4 -4
- package/dist/components/features/index.js +3 -3
- package/dist/components/help-center-pages/index.cjs +91 -91
- package/dist/components/help-center-pages/index.js +15 -15
- package/dist/components/index.cjs +73 -55
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +33 -15
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +41 -41
- package/dist/components/meeting-scheduler/index.js +3 -3
- package/dist/components/navigation/index.cjs +7 -7
- package/dist/components/navigation/index.js +6 -6
- package/dist/components/onboarding-guides/index.cjs +7 -7
- package/dist/components/onboarding-guides/index.js +6 -6
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +8 -8
- package/dist/components/tickets/index.js +7 -7
- package/dist/components/ui/avatar-stack.d.ts +5 -1
- package/dist/components/ui/avatar-stack.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +22 -4
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +21 -3
- package/dist/components/ui/reopen-ticket-request-modal.d.ts +4 -1
- package/dist/components/ui/reopen-ticket-request-modal.d.ts.map +1 -1
- package/dist/components/ui/tab-navigation.d.ts +7 -0
- package/dist/components/ui/tab-navigation.d.ts.map +1 -1
- package/dist/index.cjs +27 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +25 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/__tests__/chat-appearance.test.tsx +79 -0
- package/src/components/chat/__tests__/onboarding-carousel.test.tsx +56 -0
- package/src/components/chat/ai-assistant-info.tsx +7 -1
- package/src/components/chat/approval-request-message.tsx +6 -1
- package/src/components/chat/chat-appearance-context.tsx +15 -0
- package/src/components/chat/chat-input.tsx +26 -1
- package/src/components/chat/chat-message-enhanced.tsx +180 -142
- package/src/components/chat/chat-message-list.tsx +169 -164
- package/src/components/chat/chat-nav-sidebar.tsx +163 -0
- package/src/components/chat/chat-top-navigation.tsx +109 -0
- package/src/components/chat/error-message-display.tsx +21 -3
- package/src/components/chat/index.ts +6 -0
- package/src/components/chat/msp-organization-card.tsx +35 -0
- package/src/components/chat/onboarding-carousel.tsx +162 -0
- package/src/components/chat/system-event-message.tsx +54 -0
- package/src/components/chat/types/chat.types.ts +15 -0
- package/src/components/chat/types/component.types.ts +20 -0
- package/src/components/ui/__tests__/avatar-stack.test.tsx +25 -0
- package/src/components/ui/__tests__/tab-navigation.test.tsx +56 -2
- package/src/components/ui/avatar-stack.tsx +15 -5
- package/src/components/ui/reopen-ticket-request-modal.tsx +6 -2
- package/src/components/ui/tab-navigation.tsx +23 -0
- package/src/stories/FaeLayoutV2.stories.tsx +150 -0
- package/src/stories/FaeOnboardingV2.stories.tsx +68 -0
- package/src/stories/FaeThreadV2.stories.tsx +192 -0
- package/src/stories/fae-brand-decorator.tsx +28 -0
- package/src/styles/ods-colors.css +10 -0
- package/src/styles/ods-effects.css +13 -0
- package/dist/chunk-A3CSQB4A.cjs.map +0 -1
- package/dist/chunk-DQOQ3KTP.cjs.map +0 -1
- package/dist/chunk-M4HYIXP6.cjs.map +0 -1
- package/dist/chunk-T6KWNJDW.js.map +0 -1
- package/dist/chunk-XGX6JSEU.js.map +0 -1
- /package/dist/{chunk-65KTXRBY.js.map → chunk-2G2CB7ZZ.js.map} +0 -0
- /package/dist/{chunk-XW7MUIW4.js.map → chunk-3NIZ2VLQ.js.map} +0 -0
- /package/dist/{chunk-ZF4Z36G4.js.map → chunk-4RJQMOQW.js.map} +0 -0
- /package/dist/{chunk-7XF63THA.js.map → chunk-ASBBGP36.js.map} +0 -0
- /package/dist/{chunk-O7LOZW4R.js.map → chunk-C5TFPMOM.js.map} +0 -0
- /package/dist/{chunk-TUG7IT6H.js.map → chunk-HLEXYTOE.js.map} +0 -0
- /package/dist/{chunk-GXV6UEVY.js.map → chunk-IELOPDTA.js.map} +0 -0
- /package/dist/{chunk-B32YLOYQ.js.map → chunk-JBJZL5PI.js.map} +0 -0
- /package/dist/{chunk-UT333EOT.js.map → chunk-LCOMNQLX.js.map} +0 -0
- /package/dist/{chunk-WJK2EETW.js.map → chunk-NINO3PI7.js.map} +0 -0
- /package/dist/{chunk-PSQKD6ZN.js.map → chunk-PG65AE3I.js.map} +0 -0
- /package/dist/{chunk-BZ2XOEHW.js.map → chunk-QOH45QSE.js.map} +0 -0
- /package/dist/{chunk-Y7ZYOQNB.js.map → chunk-RSMAVO2Z.js.map} +0 -0
- /package/dist/{chunk-GHLGXFNU.js.map → chunk-U6JC3RS2.js.map} +0 -0
- /package/dist/{chunk-N4Z6X5BV.js.map → chunk-WFH52OVQ.js.map} +0 -0
|
@@ -14,10 +14,12 @@ import { useStickToBottom } from 'use-stick-to-bottom';
|
|
|
14
14
|
import { cn } from '../../utils/cn';
|
|
15
15
|
import { DotsLoaderIcon, Arrow02DownIcon } from '../icons-v2-generated';
|
|
16
16
|
import { OverlayOpenRegistryProvider } from '../ui/overlay-open-registry';
|
|
17
|
+
import { ChatAppearanceContext } from './chat-appearance-context';
|
|
17
18
|
import { ChatMessageEnhanced } from './chat-message-enhanced';
|
|
18
19
|
import { ChatMessageListSkeleton } from './chat-message-skeleton';
|
|
19
20
|
import { CyclingPhrase } from './cycling-phrase';
|
|
20
21
|
import type { ChatMessageListProps } from './types';
|
|
22
|
+
import { CHAT_APPEARANCE } from './types/chat.types';
|
|
21
23
|
import { SCROLL_ANCHOR } from './types/message.types';
|
|
22
24
|
import type { MessageContent } from './types/message.types';
|
|
23
25
|
|
|
@@ -128,6 +130,7 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
|
|
|
128
130
|
contentClassName,
|
|
129
131
|
assistantType,
|
|
130
132
|
approvalVariant,
|
|
133
|
+
appearance = CHAT_APPEARANCE.CLASSIC,
|
|
131
134
|
assistantIcon,
|
|
132
135
|
pendingApprovals,
|
|
133
136
|
hasNextPage,
|
|
@@ -915,118 +918,119 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
|
|
|
915
918
|
const showStreamingLoader = isTyping && !isPausedOnApproval && !(pendingApprovals && pendingApprovals.length > 0);
|
|
916
919
|
|
|
917
920
|
return (
|
|
918
|
-
<
|
|
919
|
-
<
|
|
920
|
-
|
|
921
|
+
<ChatAppearanceContext.Provider value={appearance}>
|
|
922
|
+
<OverlayOpenRegistryProvider onOpenChange={handleOverlayOpenChange}>
|
|
923
|
+
<div className="flex min-h-0 flex-1 flex-col">
|
|
924
|
+
{/* Positioning box for the jump-to-bottom button: it wraps ONLY the
|
|
921
925
|
scroller, so `absolute bottom-…` really is the scroller's lower
|
|
922
926
|
edge. The outer flex column also holds the streaming loader and the
|
|
923
927
|
approvals bar, and anchoring against THAT box put the button over
|
|
924
928
|
the loader for the whole of every stream — precisely when it is
|
|
925
929
|
most likely to be visible. */}
|
|
926
|
-
|
|
927
|
-
<div
|
|
928
|
-
ref={setScrollRef}
|
|
929
|
-
className={cn(
|
|
930
|
-
'flex h-full w-full flex-1 flex-col overflow-y-auto overflow-x-hidden',
|
|
931
|
-
// `overscroll-contain` (default ON, opt-out via `overscrollContain={false}`):
|
|
932
|
-
// reaching the top/bottom of the thread must NOT chain the wheel/
|
|
933
|
-
// touch scroll to the page behind the chat — e.g. the company-hub
|
|
934
|
-
// deck (native body scroll + sticky slide panels) would otherwise
|
|
935
|
-
// advance slides while you scroll the chat (fixed in #1501; kept as
|
|
936
|
-
// the prop default here). Passive in-page DEMO chats disable it so
|
|
937
|
-
// the surrounding page keeps scrolling normally when the pointer is
|
|
938
|
-
// over the (non-interactive) thread.
|
|
939
|
-
overscrollContain && 'overscroll-contain',
|
|
940
|
-
'scrollbar-thin scrollbar-track-transparent scrollbar-thumb-ods-border/30 hover:scrollbar-thumb-ods-text-secondary/30',
|
|
941
|
-
className,
|
|
942
|
-
)}
|
|
943
|
-
{...props}
|
|
944
|
-
>
|
|
930
|
+
<div className="relative flex min-h-0 flex-1 flex-col">
|
|
945
931
|
<div
|
|
946
|
-
ref={
|
|
932
|
+
ref={setScrollRef}
|
|
947
933
|
className={cn(
|
|
948
|
-
|
|
949
|
-
//
|
|
950
|
-
//
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
934
|
+
'flex h-full w-full flex-1 flex-col overflow-y-auto overflow-x-hidden',
|
|
935
|
+
// `overscroll-contain` (default ON, opt-out via `overscrollContain={false}`):
|
|
936
|
+
// reaching the top/bottom of the thread must NOT chain the wheel/
|
|
937
|
+
// touch scroll to the page behind the chat — e.g. the company-hub
|
|
938
|
+
// deck (native body scroll + sticky slide panels) would otherwise
|
|
939
|
+
// advance slides while you scroll the chat (fixed in #1501; kept as
|
|
940
|
+
// the prop default here). Passive in-page DEMO chats disable it so
|
|
941
|
+
// the surrounding page keeps scrolling normally when the pointer is
|
|
942
|
+
// over the (non-interactive) thread.
|
|
943
|
+
overscrollContain && 'overscroll-contain',
|
|
944
|
+
'scrollbar-thin scrollbar-track-transparent scrollbar-thumb-ods-border/30 hover:scrollbar-thumb-ods-text-secondary/30',
|
|
945
|
+
className,
|
|
955
946
|
)}
|
|
956
|
-
|
|
947
|
+
{...props}
|
|
957
948
|
>
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
949
|
+
<div
|
|
950
|
+
ref={setContentRef}
|
|
951
|
+
className={cn(
|
|
952
|
+
// `fullWidth=true` drops the centered-narrow column for
|
|
953
|
+
// side-panel hosts (e.g. multi-platform-hub Mingo). Same
|
|
954
|
+
// semantics as ChatHeader / ChatInput / ChatFooter.
|
|
955
|
+
fullWidth
|
|
956
|
+
? 'flex w-full min-w-0 flex-col pb-[var(--spacing-system-xs)]'
|
|
957
|
+
: 'mx-auto flex w-full min-w-0 max-w-ods-content-narrow flex-col pb-[var(--spacing-system-xs)]',
|
|
958
|
+
contentClassName ?? 'px-[var(--spacing-system-m)]',
|
|
959
|
+
)}
|
|
960
|
+
style={{ minHeight: '100%' }}
|
|
961
|
+
>
|
|
962
|
+
{hasNextPage && <div ref={sentinelRef} className="h-px" />}
|
|
963
|
+
<div className="flex-1" />
|
|
964
|
+
{messages.map((message, index) => {
|
|
965
|
+
// Hidden messages (synthetic continuation prompts the host
|
|
966
|
+
// injects after an approval card) are part of the API
|
|
967
|
+
// conversation history but never render. Skipping here
|
|
968
|
+
// keeps the visible thread coherent — see
|
|
969
|
+
// `Message.hidden` doc-comment in message.types.ts.
|
|
970
|
+
if (message.hidden) return null;
|
|
971
|
+
// ONE OWNER for the pending turn. Both send paths mint an empty
|
|
972
|
+
// assistant placeholder (the accumulation target — it MUST stay
|
|
973
|
+
// in state) and flip the phase to 'thinking'. Rendering it here
|
|
974
|
+
// too would put an author label with no body in the transcript
|
|
975
|
+
// WHILE the footer loader below already represents the same
|
|
976
|
+
// pending turn — two owners, and an `aria-live` log that
|
|
977
|
+
// announces a bare "Mingo". The footer loader wins (it is
|
|
978
|
+
// already `role="status" aria-live="polite"`, sits outside the
|
|
979
|
+
// scroller so it can't jitter the thread, and cycles the
|
|
980
|
+
// progress phrase), so the placeholder is not rendered AT ALL
|
|
981
|
+
// until it carries something visible — removed from the live
|
|
982
|
+
// region, not merely hidden. Mirrors `endSseTurn`, which prunes
|
|
983
|
+
// an empty trailing assistant AFTER a turn; this closes the same
|
|
984
|
+
// window BEFORE the first token. Any non-text segment (tool
|
|
985
|
+
// execution, approval card, thinking) counts as visible.
|
|
986
|
+
const isEmptyPendingTurn =
|
|
987
|
+
index === messages.length - 1 &&
|
|
988
|
+
isTyping &&
|
|
989
|
+
message.role === 'assistant' &&
|
|
990
|
+
!hasNonEmptyContent(message.content) &&
|
|
991
|
+
(!Array.isArray(message.content) || message.content.every(s => s.type === 'text'));
|
|
992
|
+
if (isEmptyPendingTurn) return null;
|
|
993
|
+
const ownedContent = renderAfterMessage?.(message, index);
|
|
994
|
+
const row = (
|
|
995
|
+
<ChatMessageEnhanced
|
|
996
|
+
key={message.id}
|
|
997
|
+
ref={getRegisterMessageEl(message.id)}
|
|
998
|
+
role={message.role}
|
|
999
|
+
name={message.name}
|
|
1000
|
+
content={message.content}
|
|
1001
|
+
timestamp={message.timestamp}
|
|
1002
|
+
isTyping={index === messages.length - 1 && isTyping && message.role === 'assistant'}
|
|
1003
|
+
avatar={showAvatars ? message.avatar : null}
|
|
1004
|
+
showAvatar={showAvatars}
|
|
1005
|
+
assistantType={message.assistantType || assistantType}
|
|
1006
|
+
approvalVariant={approvalVariant}
|
|
1007
|
+
authorType={message.authorType}
|
|
1008
|
+
assistantIcon={message.role !== 'user' ? assistantIcon : undefined}
|
|
1009
|
+
contextItems={message.contextItems}
|
|
1010
|
+
resolveContextIcon={resolveContextIcon}
|
|
1011
|
+
renderContextItem={renderContextItem}
|
|
1012
|
+
renderMention={renderMention}
|
|
1013
|
+
renderEntityCard={renderEntityCard}
|
|
1014
|
+
refs={message.refs}
|
|
1015
|
+
onAskSelect={index > lastUserMessageIndex ? onAskSelect : undefined}
|
|
1016
|
+
NavLinkAnchor={NavLinkAnchor}
|
|
1017
|
+
/>
|
|
1018
|
+
);
|
|
1019
|
+
// Only wrap when there IS owned content: the bare row keeps
|
|
1020
|
+
// its own key and its place as a direct flex child, so a
|
|
1021
|
+
// thread without any stays byte-identical to before.
|
|
1022
|
+
if (!ownedContent) return row;
|
|
1023
|
+
return (
|
|
1024
|
+
<div key={message.id} className="grid w-full min-w-0 grid-cols-1">
|
|
1025
|
+
{row}
|
|
1026
|
+
{ownedContent}
|
|
1027
|
+
</div>
|
|
1028
|
+
);
|
|
1029
|
+
})}
|
|
1030
|
+
</div>
|
|
1026
1031
|
</div>
|
|
1027
|
-
</div>
|
|
1028
1032
|
|
|
1029
|
-
|
|
1033
|
+
{/* Jump to bottom. The manual escape hatch from auto-scroll that
|
|
1030
1034
|
every 2026 chat surface ships (AI Elements calls it
|
|
1031
1035
|
`ConversationScrollButton`) and the WCAG 2.2.2 counterpart to
|
|
1032
1036
|
moving content: the reader can always get back to the live
|
|
@@ -1037,79 +1041,80 @@ const ChatMessageList = forwardRef<HTMLDivElement, ChatMessageListProps>(
|
|
|
1037
1041
|
height either way — a sibling would shrink the scroller and
|
|
1038
1042
|
move the very bottom it points at. Hidden while at the bottom
|
|
1039
1043
|
and in passive demo hosts. */}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1044
|
+
{autoScroll && !atBottom && messages.length > 0 && (
|
|
1045
|
+
<button
|
|
1046
|
+
type="button"
|
|
1047
|
+
onClick={() => {
|
|
1048
|
+
// Clicking IS the intent to return to the live end, so it
|
|
1049
|
+
// re-arms the follow lock a scroll-up had released.
|
|
1050
|
+
followBottomRef.current = true;
|
|
1051
|
+
void scrollToBottom({ animation: 'smooth' });
|
|
1052
|
+
}}
|
|
1053
|
+
aria-label="Scroll to latest message"
|
|
1054
|
+
className={cn(
|
|
1055
|
+
'absolute bottom-[var(--spacing-system-s)] left-1/2 z-10 -translate-x-1/2',
|
|
1056
|
+
'flex size-9 items-center justify-center rounded-full',
|
|
1057
|
+
'border border-ods-border bg-ods-card text-ods-text-primary shadow-lg',
|
|
1058
|
+
'transition-colors hover:bg-ods-bg-hover',
|
|
1059
|
+
)}
|
|
1060
|
+
>
|
|
1061
|
+
<Arrow02DownIcon size={16} />
|
|
1062
|
+
</button>
|
|
1063
|
+
)}
|
|
1064
|
+
</div>
|
|
1061
1065
|
|
|
1062
|
-
|
|
1066
|
+
{/* Footer-pinned streaming loader — outside the scroller so it
|
|
1063
1067
|
doesn't jitter as the streaming message grows. Color is set
|
|
1064
1068
|
via inline style (CSS var) rather than a Tailwind class so it
|
|
1065
1069
|
is JIT-independent when this lib is consumed via yalc. */}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1070
|
+
{showStreamingLoader && (
|
|
1071
|
+
<div
|
|
1072
|
+
className={cn(
|
|
1073
|
+
fullWidth
|
|
1074
|
+
? 'flex w-full items-center gap-[var(--spacing-system-xxs)] py-[var(--spacing-system-xs)]'
|
|
1075
|
+
: 'mx-auto flex w-full max-w-ods-content-narrow items-center gap-[var(--spacing-system-xxs)] py-[var(--spacing-system-xs)]',
|
|
1076
|
+
contentClassName ?? 'px-[var(--spacing-system-m)]',
|
|
1077
|
+
)}
|
|
1078
|
+
style={{ color: 'var(--color-text-muted)' }}
|
|
1079
|
+
role="status"
|
|
1080
|
+
aria-live="polite"
|
|
1081
|
+
>
|
|
1082
|
+
<DotsLoaderIcon className="h-6 w-6" />
|
|
1083
|
+
<CyclingPhrase words={STREAMING_WORDS} className="text-h6" />
|
|
1084
|
+
</div>
|
|
1085
|
+
)}
|
|
1082
1086
|
|
|
1083
|
-
|
|
1087
|
+
{/* Sticky Pending Approvals — outside the scroller; same
|
|
1084
1088
|
structure as the v1 baseline. The library's RO watches
|
|
1085
1089
|
`contentRef` (INSIDE the scroller), so height changes to
|
|
1086
1090
|
this sibling don't trigger the library directly. If a
|
|
1087
1091
|
future UX requires snapping to bottom when approvals
|
|
1088
1092
|
appear, that hookup goes here. */}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1093
|
+
{pendingApprovals && pendingApprovals.length > 0 && (
|
|
1094
|
+
<div
|
|
1095
|
+
className={cn(
|
|
1096
|
+
'border-t border-ods-border bg-ods-bg/95 backdrop-blur-sm',
|
|
1097
|
+
fullWidth ? 'w-full' : 'mx-auto w-full max-w-ods-content-narrow',
|
|
1098
|
+
contentClassName ?? 'px-[var(--spacing-system-m)]',
|
|
1099
|
+
)}
|
|
1100
|
+
>
|
|
1101
|
+
<ChatMessageEnhanced
|
|
1102
|
+
role="assistant"
|
|
1103
|
+
name={assistantType === 'mingo' ? 'Mingo' : 'Fae'}
|
|
1104
|
+
content={pendingApprovals}
|
|
1105
|
+
timestamp={new Date()}
|
|
1106
|
+
showAvatar={showAvatars}
|
|
1107
|
+
assistantType={assistantType}
|
|
1108
|
+
approvalVariant={approvalVariant}
|
|
1109
|
+
assistantIcon={assistantIcon}
|
|
1110
|
+
NavLinkAnchor={NavLinkAnchor}
|
|
1111
|
+
className="py-3"
|
|
1112
|
+
/>
|
|
1113
|
+
</div>
|
|
1114
|
+
)}
|
|
1115
|
+
</div>
|
|
1116
|
+
</OverlayOpenRegistryProvider>
|
|
1117
|
+
</ChatAppearanceContext.Provider>
|
|
1113
1118
|
);
|
|
1114
1119
|
},
|
|
1115
1120
|
);
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef, type ButtonHTMLAttributes, type HTMLAttributes, type ReactNode } from 'react';
|
|
4
|
+
import { cn } from '../../utils/cn';
|
|
5
|
+
import { ScrollFadeOverlay, useScrollFade } from '../ui/scroll-fade';
|
|
6
|
+
|
|
7
|
+
export interface ChatNavSidebarItem {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
/** 24px glyph before the label. */
|
|
11
|
+
icon: ReactNode;
|
|
12
|
+
/** The view this item opens is the one on screen. */
|
|
13
|
+
active?: boolean;
|
|
14
|
+
onClick?: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ChatNavSidebarProps extends HTMLAttributes<HTMLElement> {
|
|
18
|
+
/** Brand row at the top. */
|
|
19
|
+
logo?: ReactNode;
|
|
20
|
+
/** Primary actions under the logo (new chat, archive, help, ...). */
|
|
21
|
+
items: ChatNavSidebarItem[];
|
|
22
|
+
/** Caption over the list (e.g. "Your Current Chats"). */
|
|
23
|
+
sectionLabel?: string;
|
|
24
|
+
/** The list itself, typically `ChatListItem` rows or an empty state. Scrolls
|
|
25
|
+
* on its own; everything above it stays put. */
|
|
26
|
+
children?: ReactNode;
|
|
27
|
+
/** Pinned under the list, full width, above a divider (e.g. the MSP card). */
|
|
28
|
+
footer?: ReactNode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Left rail of the fae chat v2 layout (Figma `chat-sidebar`): brand, a stack of
|
|
33
|
+
* navigation actions, a captioned scrolling list and a pinned footer. It only
|
|
34
|
+
* lays things out; which rows exist, what they show and what selecting them
|
|
35
|
+
* does belong to the host.
|
|
36
|
+
*/
|
|
37
|
+
const ChatNavSidebar = forwardRef<HTMLElement, ChatNavSidebarProps>(
|
|
38
|
+
({ className, logo, items, sectionLabel, children, footer, ...props }, ref) => {
|
|
39
|
+
const { scrollRef, fadeTop, fadeBottom, update: updateFade } = useScrollFade<HTMLDivElement>();
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<aside
|
|
43
|
+
ref={ref}
|
|
44
|
+
className={cn('flex h-full w-[296px] shrink-0 flex-col border-r border-ods-border bg-ods-bg', className)}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
<div className="flex min-h-0 flex-1 flex-col gap-[var(--spacing-system-m)] px-[var(--spacing-system-xs)] pt-[var(--spacing-system-mf)]">
|
|
48
|
+
{logo && <div className="flex shrink-0 items-center px-[var(--spacing-system-xs)]">{logo}</div>}
|
|
49
|
+
|
|
50
|
+
<nav className="flex shrink-0 flex-col gap-[var(--spacing-system-xxs)]">
|
|
51
|
+
{items.map(item => (
|
|
52
|
+
<ChatNavSidebarButton key={item.id} icon={item.icon} active={item.active} onClick={item.onClick}>
|
|
53
|
+
{item.label}
|
|
54
|
+
</ChatNavSidebarButton>
|
|
55
|
+
))}
|
|
56
|
+
</nav>
|
|
57
|
+
|
|
58
|
+
<div className="flex min-h-0 flex-1 flex-col gap-[var(--spacing-system-xxs)]">
|
|
59
|
+
{sectionLabel && (
|
|
60
|
+
<p className="shrink-0 px-[var(--spacing-system-xs)] pt-[var(--spacing-system-xs)] text-ods-text-secondary text-h5">
|
|
61
|
+
{sectionLabel}
|
|
62
|
+
</p>
|
|
63
|
+
)}
|
|
64
|
+
<div className="relative flex min-h-0 flex-1 flex-col">
|
|
65
|
+
<div
|
|
66
|
+
ref={scrollRef}
|
|
67
|
+
onScroll={updateFade}
|
|
68
|
+
className="flex min-h-0 flex-1 flex-col gap-[var(--spacing-system-xxs)] overflow-y-auto overscroll-contain pb-[var(--spacing-system-mf)]"
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
</div>
|
|
72
|
+
<ScrollFadeOverlay edge="top" visible={fadeTop} />
|
|
73
|
+
<ScrollFadeOverlay edge="bottom" visible={fadeBottom} />
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
{footer && <div className="shrink-0 border-t border-ods-border">{footer}</div>}
|
|
79
|
+
</aside>
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
ChatNavSidebar.displayName = 'ChatNavSidebar';
|
|
85
|
+
|
|
86
|
+
interface ChatNavSidebarButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
87
|
+
icon: ReactNode;
|
|
88
|
+
active?: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function ChatNavSidebarButton({ icon, active, className, children, ...props }: ChatNavSidebarButtonProps) {
|
|
92
|
+
return (
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
aria-current={active ? 'page' : undefined}
|
|
96
|
+
className={cn(
|
|
97
|
+
'flex w-full items-center gap-[var(--spacing-system-xs)] rounded-md p-[var(--spacing-system-xs)] text-left text-h4',
|
|
98
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-focus',
|
|
99
|
+
'[&_svg]:size-6 [&_svg]:shrink-0',
|
|
100
|
+
active ? 'text-ods-accent' : 'text-ods-text-primary [&_svg]:text-ods-text-secondary',
|
|
101
|
+
className,
|
|
102
|
+
)}
|
|
103
|
+
{...props}
|
|
104
|
+
>
|
|
105
|
+
{icon}
|
|
106
|
+
<span className="min-w-0 flex-1 truncate">{children}</span>
|
|
107
|
+
</button>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface ChatListItemProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'title'> {
|
|
112
|
+
title: ReactNode;
|
|
113
|
+
/** Second line (e.g. the ticket number). */
|
|
114
|
+
subtitle?: ReactNode;
|
|
115
|
+
/** 16px mark before the title (e.g. a resolved check). */
|
|
116
|
+
leading?: ReactNode;
|
|
117
|
+
/** 24px status glyph at the row's end. */
|
|
118
|
+
trailing?: ReactNode;
|
|
119
|
+
/** The chat open on screen. */
|
|
120
|
+
active?: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* One row of the `ChatNavSidebar` list (Figma `chat-list-item`): a one-line
|
|
125
|
+
* title over a caption, an optional mark before the title and a status glyph
|
|
126
|
+
* at the end. The selected row takes the accent's secondary fill, so a tenant
|
|
127
|
+
* accent recolours it with the rest of the brand.
|
|
128
|
+
*/
|
|
129
|
+
const ChatListItem = forwardRef<HTMLButtonElement, ChatListItemProps>(
|
|
130
|
+
({ className, title, subtitle, leading, trailing, active, ...props }, ref) => (
|
|
131
|
+
<button
|
|
132
|
+
ref={ref}
|
|
133
|
+
type="button"
|
|
134
|
+
aria-current={active ? 'true' : undefined}
|
|
135
|
+
className={cn(
|
|
136
|
+
'flex w-full shrink-0 items-center gap-[var(--spacing-system-xs)] rounded-md p-[var(--spacing-system-xs)] text-left',
|
|
137
|
+
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ods-focus',
|
|
138
|
+
active && 'bg-ods-accent-secondary',
|
|
139
|
+
className,
|
|
140
|
+
)}
|
|
141
|
+
{...props}
|
|
142
|
+
>
|
|
143
|
+
<span className="flex min-w-0 flex-1 flex-col">
|
|
144
|
+
<span className="flex min-w-0 items-center gap-[var(--spacing-system-xxs)]">
|
|
145
|
+
{leading && <span className="flex shrink-0 items-center [&_svg]:size-4">{leading}</span>}
|
|
146
|
+
<span className={cn('min-w-0 flex-1 truncate text-h4', active ? 'text-ods-accent' : 'text-ods-text-primary')}>
|
|
147
|
+
{title}
|
|
148
|
+
</span>
|
|
149
|
+
</span>
|
|
150
|
+
{subtitle != null && (
|
|
151
|
+
<span className={cn('truncate text-h6', active ? 'text-ods-text-primary' : 'text-ods-text-secondary')}>
|
|
152
|
+
{subtitle}
|
|
153
|
+
</span>
|
|
154
|
+
)}
|
|
155
|
+
</span>
|
|
156
|
+
{trailing && <span className="flex shrink-0 items-center [&_svg]:size-6">{trailing}</span>}
|
|
157
|
+
</button>
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
ChatListItem.displayName = 'ChatListItem';
|
|
162
|
+
|
|
163
|
+
export { ChatNavSidebar, ChatListItem };
|