@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
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { OnboardingCarousel } from '../onboarding-carousel';
|
|
4
|
+
|
|
5
|
+
const STEPS = [
|
|
6
|
+
{ id: 'a', icon: null, title: 'First' },
|
|
7
|
+
{ id: 'b', icon: null, title: 'Second' },
|
|
8
|
+
{ id: 'c', icon: null, title: 'Third' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const currentSlide = () =>
|
|
12
|
+
screen.getAllByRole('group', { hidden: true }).find(el => el.getAttribute('aria-hidden') === 'false');
|
|
13
|
+
|
|
14
|
+
describe('OnboardingCarousel', () => {
|
|
15
|
+
it('starts on the first step with Previous disabled', () => {
|
|
16
|
+
render(<OnboardingCarousel steps={STEPS} onComplete={vi.fn()} />);
|
|
17
|
+
expect(currentSlide()).toHaveTextContent('First');
|
|
18
|
+
expect(screen.getByRole('button', { name: 'Previous Step' })).toHaveAttribute('aria-disabled', 'true');
|
|
19
|
+
expect(screen.getByRole('button', { name: 'Next Step' })).toBeEnabled();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('walks forward and back', () => {
|
|
23
|
+
render(<OnboardingCarousel steps={STEPS} onComplete={vi.fn()} />);
|
|
24
|
+
fireEvent.click(screen.getByRole('button', { name: 'Next Step' }));
|
|
25
|
+
expect(currentSlide()).toHaveTextContent('Second');
|
|
26
|
+
fireEvent.click(screen.getByRole('button', { name: 'Previous Step' }));
|
|
27
|
+
expect(currentSlide()).toHaveTextContent('First');
|
|
28
|
+
expect(screen.getByText('Step 1 of 3: First')).toHaveAttribute('aria-live', 'polite');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('swaps Next for the complete action on the last step', () => {
|
|
32
|
+
const onComplete = vi.fn();
|
|
33
|
+
render(<OnboardingCarousel steps={STEPS} onComplete={onComplete} />);
|
|
34
|
+
fireEvent.click(screen.getByRole('button', { name: 'Go to step 3' }));
|
|
35
|
+
expect(screen.queryByRole('button', { name: 'Next Step' })).not.toBeInTheDocument();
|
|
36
|
+
fireEvent.click(screen.getByRole('button', { name: 'Start Chat' }));
|
|
37
|
+
expect(onComplete).toHaveBeenCalledTimes(1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('follows the arrow keys and stops at the ends', () => {
|
|
41
|
+
render(<OnboardingCarousel steps={STEPS} onComplete={vi.fn()} />);
|
|
42
|
+
const region = screen.getByRole('region', { name: 'Introduction' });
|
|
43
|
+
fireEvent.keyDown(region, { key: 'ArrowLeft' });
|
|
44
|
+
expect(currentSlide()).toHaveTextContent('First');
|
|
45
|
+
fireEvent.keyDown(region, { key: 'ArrowRight' });
|
|
46
|
+
fireEvent.keyDown(region, { key: 'ArrowRight' });
|
|
47
|
+
fireEvent.keyDown(region, { key: 'ArrowRight' });
|
|
48
|
+
expect(currentSlide()).toHaveTextContent('Third');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('marks the current dot', () => {
|
|
52
|
+
render(<OnboardingCarousel steps={STEPS} onComplete={vi.fn()} />);
|
|
53
|
+
expect(screen.getByRole('button', { name: 'Go to step 1' })).toHaveAttribute('aria-current', 'step');
|
|
54
|
+
expect(screen.getByRole('button', { name: 'Go to step 2' })).not.toHaveAttribute('aria-current');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';
|
|
4
4
|
import { cn } from '../../utils/cn';
|
|
5
5
|
import { formatTime } from '../../utils/format-date';
|
|
6
|
+
import { useChatAppearance } from './chat-appearance-context';
|
|
7
|
+
import { CHAT_APPEARANCE } from './types/chat.types';
|
|
6
8
|
|
|
7
9
|
export interface AiAssistantInfoProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
10
|
/** Rendered inside the standard 48px framed box (typically a 24px icon). */
|
|
@@ -34,11 +36,15 @@ export interface AiAssistantInfoProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
34
36
|
*/
|
|
35
37
|
const AiAssistantInfo = forwardRef<HTMLDivElement, AiAssistantInfoProps>(
|
|
36
38
|
({ className, icon, leading, title, body, timestamp, ...props }, ref) => {
|
|
39
|
+
// v2: on the page surface, content centered against the 48px icon, and
|
|
40
|
+
// the thread (not the card) owns the space below it.
|
|
41
|
+
const isV2 = useChatAppearance() === CHAT_APPEARANCE.V2;
|
|
37
42
|
return (
|
|
38
43
|
<div
|
|
39
44
|
ref={ref}
|
|
40
45
|
className={cn(
|
|
41
|
-
'
|
|
46
|
+
'flex gap-[var(--spacing-system-s)] rounded-md border border-ods-border p-[var(--spacing-system-s)]',
|
|
47
|
+
isV2 ? 'items-center bg-ods-bg' : 'mb-[var(--spacing-system-xsf)] items-start bg-ods-card',
|
|
42
48
|
className,
|
|
43
49
|
)}
|
|
44
50
|
{...props}
|
|
@@ -6,7 +6,9 @@ import { cn } from '../../utils/cn';
|
|
|
6
6
|
import { Button } from '../ui/button';
|
|
7
7
|
import { Tag } from '../ui/tag';
|
|
8
8
|
import { ApprovalStatusTag } from './approval-batch-message';
|
|
9
|
+
import { useChatAppearance } from './chat-appearance-context';
|
|
9
10
|
import type { ApprovalRequestMessageProps } from './types';
|
|
11
|
+
import { CHAT_APPEARANCE } from './types/chat.types';
|
|
10
12
|
import type { ApprovalRequestField } from './types/message.types';
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -77,6 +79,7 @@ const ApprovalRequestMessage = forwardRef<HTMLDivElement, ApprovalRequestMessage
|
|
|
77
79
|
ref,
|
|
78
80
|
) => {
|
|
79
81
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
82
|
+
const isV2 = useChatAppearance() === CHAT_APPEARANCE.V2;
|
|
80
83
|
|
|
81
84
|
const handleApprove = async () => {
|
|
82
85
|
setIsProcessing(true);
|
|
@@ -104,7 +107,9 @@ const ApprovalRequestMessage = forwardRef<HTMLDivElement, ApprovalRequestMessage
|
|
|
104
107
|
<div
|
|
105
108
|
ref={ref}
|
|
106
109
|
className={cn(
|
|
107
|
-
'
|
|
110
|
+
'flex flex-col gap-[var(--spacing-system-mf)] rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-mf)]',
|
|
111
|
+
// v2: the thread owns the space below the card.
|
|
112
|
+
!isV2 && 'mb-[var(--spacing-system-xsf)]',
|
|
108
113
|
className,
|
|
109
114
|
)}
|
|
110
115
|
{...props}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
|
+
import { CHAT_APPEARANCE, type ChatAppearance } from './types/chat.types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Thread-wide `ChatAppearance`. `ChatMessageList` provides it, so the blocks a
|
|
8
|
+
* message renders (error, lifecycle receipts) follow the thread without every
|
|
9
|
+
* segment component growing an `appearance` prop.
|
|
10
|
+
*/
|
|
11
|
+
export const ChatAppearanceContext = createContext<ChatAppearance>(CHAT_APPEARANCE.CLASSIC);
|
|
12
|
+
|
|
13
|
+
export function useChatAppearance(): ChatAppearance {
|
|
14
|
+
return useContext(ChatAppearanceContext);
|
|
15
|
+
}
|
|
@@ -16,10 +16,12 @@ import {
|
|
|
16
16
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
17
17
|
import { cn } from '../../utils/cn';
|
|
18
18
|
import { Send01Icon, StopCircleIcon } from '../icons-v2-generated';
|
|
19
|
+
import { AvatarStack } from '../ui/avatar-stack';
|
|
19
20
|
import { Tag } from '../ui/tag';
|
|
20
21
|
import { ChatTypingIndicator } from './chat-typing-indicator';
|
|
21
22
|
import { SlashCommandSuggestions } from './slash-command-suggestions';
|
|
22
23
|
import type { ChatInputProps, ChatInputRef, MentionMeta, SlashCommandSummary } from './types';
|
|
24
|
+
import { CHAT_APPEARANCE } from './types/chat.types';
|
|
23
25
|
|
|
24
26
|
/** SHARED with `lib/config/slash-commands-config.ts` AND the chat-route slash
|
|
25
27
|
* dispatch parser. Keep all three in sync. */
|
|
@@ -164,6 +166,9 @@ const ChatInput = forwardRef<ChatInputRef, ChatInputProps>((allProps, ref) => {
|
|
|
164
166
|
onStop,
|
|
165
167
|
sending = false,
|
|
166
168
|
awaitingResponse = false,
|
|
169
|
+
awaitingTeam,
|
|
170
|
+
awaitingTeamCount,
|
|
171
|
+
appearance = CHAT_APPEARANCE.CLASSIC,
|
|
167
172
|
placeholder = 'Enter your Request...',
|
|
168
173
|
reserveAvatarOffset: _reserveAvatarOffset,
|
|
169
174
|
disabled = false,
|
|
@@ -560,11 +565,31 @@ const ChatInput = forwardRef<ChatInputRef, ChatInputProps>((allProps, ref) => {
|
|
|
560
565
|
className,
|
|
561
566
|
)}
|
|
562
567
|
>
|
|
563
|
-
{awaitingResponse ? (
|
|
568
|
+
{awaitingResponse && appearance === CHAT_APPEARANCE.CLASSIC ? (
|
|
564
569
|
<div className="relative flex items-center justify-center gap-[var(--spacing-system-xs)] rounded-md border border-ods-border bg-ods-card px-[var(--spacing-system-s)] py-[var(--spacing-system-s)] transition-colors">
|
|
565
570
|
<ChatTypingIndicator size="sm" dotClassName="bg-ods-text-primary" />
|
|
566
571
|
<p className="text-ods-text-secondary text-h4">Waiting for Technician Response</p>
|
|
567
572
|
</div>
|
|
573
|
+
) : awaitingResponse ? (
|
|
574
|
+
// Hand-off bar (fae chat v2): who has the conversation now, and that a
|
|
575
|
+
// reply is on its way. Same 48px height as the editor row it replaces,
|
|
576
|
+
// so the thread above does not jump when the chat is handed off.
|
|
577
|
+
<div
|
|
578
|
+
role="status"
|
|
579
|
+
className="flex min-h-11 w-full items-center justify-center gap-[var(--spacing-system-xs)] rounded-md border border-ods-border bg-ods-bg p-[var(--spacing-system-sf)] md:min-h-12"
|
|
580
|
+
>
|
|
581
|
+
{awaitingTeam && awaitingTeam.length > 0 && (
|
|
582
|
+
<AvatarStack
|
|
583
|
+
people={awaitingTeam}
|
|
584
|
+
total={awaitingTeamCount}
|
|
585
|
+
size="xs"
|
|
586
|
+
ringClassName="ring-ods-bg"
|
|
587
|
+
label="Technicians"
|
|
588
|
+
/>
|
|
589
|
+
)}
|
|
590
|
+
<p className="min-w-0 truncate text-ods-text-primary text-h4">Handed off to your technical support team</p>
|
|
591
|
+
<ChatTypingIndicator size="sm" dotClassName="bg-ods-text-secondary" />
|
|
592
|
+
</div>
|
|
568
593
|
) : (
|
|
569
594
|
<div className="relative">
|
|
570
595
|
<SlashCommandSuggestions
|
|
@@ -11,6 +11,7 @@ import { SquareAvatar } from '../ui/square-avatar';
|
|
|
11
11
|
import { ApprovalBatchMessage } from './approval-batch-message';
|
|
12
12
|
import { ApprovalRequestMessage } from './approval-request-message';
|
|
13
13
|
import { AskDisplay } from './ask-display';
|
|
14
|
+
import { ChatAppearanceContext, useChatAppearance } from './chat-appearance-context';
|
|
14
15
|
import { ChatContextChipStrip } from './chat-context-picker';
|
|
15
16
|
import type { ChatRef } from './chat-ref.types';
|
|
16
17
|
import { ContextCompactionDisplay } from './context-compaction-display';
|
|
@@ -20,11 +21,13 @@ import { EscalationOfferMessage } from './escalation-offer-message';
|
|
|
20
21
|
import { remarkCardLinks } from './remark-card-links';
|
|
21
22
|
import { remarkMentionChips } from './remark-mention-chips';
|
|
22
23
|
import { remarkStripCitations } from './remark-strip-citations';
|
|
24
|
+
import { SystemEventMessage } from './system-event-message';
|
|
23
25
|
import { ThinkingDisplay } from './thinking-display';
|
|
24
26
|
import { TicketEscalatedMessage } from './ticket-escalated-message';
|
|
25
27
|
import { TicketEventMessage } from './ticket-event-message';
|
|
26
28
|
import { ToolExecutionDisplay } from './tool-execution-display';
|
|
27
29
|
import type { AskSegment, MessageSegment, MessageContent, ChatMessageEnhancedProps } from './types';
|
|
30
|
+
import { CHAT_APPEARANCE } from './types/chat.types';
|
|
28
31
|
|
|
29
32
|
/** Inline `@marker:id` mention token in the message body (sibling of the
|
|
30
33
|
* `[card://]` grammar) — used to filter out items rendered inline from the
|
|
@@ -94,6 +97,7 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
94
97
|
showAvatar = true,
|
|
95
98
|
assistantType,
|
|
96
99
|
approvalVariant,
|
|
100
|
+
appearance: appearanceProp,
|
|
97
101
|
authorType: authorTypeProp,
|
|
98
102
|
assistantIcon,
|
|
99
103
|
contextItems,
|
|
@@ -110,6 +114,9 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
110
114
|
) => {
|
|
111
115
|
const isUser = role === 'user';
|
|
112
116
|
const isError = role === 'error';
|
|
117
|
+
const inheritedAppearance = useChatAppearance();
|
|
118
|
+
const appearance = appearanceProp ?? inheritedAppearance;
|
|
119
|
+
const isV2 = appearance === CHAT_APPEARANCE.V2;
|
|
113
120
|
const authorType = authorTypeProp ?? (isUser ? 'user' : assistantType === 'mingo' ? 'mingo' : 'fae');
|
|
114
121
|
|
|
115
122
|
// Inline-card rendering uses a HOST-PROVIDED `renderEntityCard` function
|
|
@@ -518,7 +525,8 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
518
525
|
// (passing pre-joined initials like "PS" would collapse to one letter,
|
|
519
526
|
// since getFirstLastInitials treats it as a single word).
|
|
520
527
|
fallback: displayName,
|
|
521
|
-
|
|
528
|
+
// v2 (fae chat v2): a 24px face that sits on the name's line height.
|
|
529
|
+
size: isV2 ? ('xs' as const) : ('sm' as const),
|
|
522
530
|
variant: 'round' as const,
|
|
523
531
|
// User avatar: compact 20×20 with 2px padding and a subtle gray fill
|
|
524
532
|
// (`bg-ods-card`) so the `border-ods-border` ring stays visible — the
|
|
@@ -536,9 +544,32 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
536
544
|
|
|
537
545
|
const isSystem = authorType === 'system';
|
|
538
546
|
|
|
547
|
+
// v2 draws a system line (e.g. a technician joining) as an in-thread
|
|
548
|
+
// receipt card rather than as an author row with no body.
|
|
549
|
+
if (isV2 && isSystem && name) {
|
|
550
|
+
return (
|
|
551
|
+
<div ref={ref} className={cn('relative py-[calc(var(--spacing-system-m)/2)]', className)} {...props}>
|
|
552
|
+
<ChatAppearanceContext.Provider value={appearance}>
|
|
553
|
+
<SystemEventMessage text={name} timestamp={timestamp} />
|
|
554
|
+
</ChatAppearanceContext.Provider>
|
|
555
|
+
</div>
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
539
559
|
return (
|
|
540
|
-
<
|
|
541
|
-
|
|
560
|
+
<ChatAppearanceContext.Provider value={appearance}>
|
|
561
|
+
<div
|
|
562
|
+
ref={ref}
|
|
563
|
+
className={cn(
|
|
564
|
+
'relative',
|
|
565
|
+
// v2 spaces messages 16px apart (spacing-system-m), split across the
|
|
566
|
+
// two neighbours' padding so blocks outside a row keep the rhythm.
|
|
567
|
+
isV2 ? 'py-[calc(var(--spacing-system-m)/2)]' : 'py-[var(--spacing-system-s)]',
|
|
568
|
+
className,
|
|
569
|
+
)}
|
|
570
|
+
{...props}
|
|
571
|
+
>
|
|
572
|
+
{/* Message Content — full panel width.
|
|
542
573
|
Avatar is INLINE in the name row below (2025-2026 chat
|
|
543
574
|
pattern — Claude.ai, ChatGPT, Gemini, Perplexity).
|
|
544
575
|
Legacy hanging-avatar layout (`absolute -left-16`) wasted
|
|
@@ -552,8 +583,8 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
552
583
|
layout per drawer-resize frame in Safari / the desktop WKWebView
|
|
553
584
|
vs ~9ms as grid (Chrome: ~2ms either way). Same gap and stretch,
|
|
554
585
|
no margin collapsing — visually identical. */}
|
|
555
|
-
|
|
556
|
-
|
|
586
|
+
<div className="grid min-w-0 grid-cols-1 gap-[var(--spacing-system-xxs)]">
|
|
587
|
+
{/* Avatar + Name + Timestamp Row.
|
|
557
588
|
Sizing rationale (per design-token measurements):
|
|
558
589
|
- Name uses `text-h3` = 14px mobile / 18px desktop.
|
|
559
590
|
- Avatar uses `SquareAvatar size="sm"` = 32px — the
|
|
@@ -569,155 +600,161 @@ const ChatMessageEnhanced = forwardRef<HTMLDivElement, ChatMessageEnhancedProps>
|
|
|
569
600
|
inside via `flex items-center justify-center` — they
|
|
570
601
|
should be sized at ~50-60% of the wrapper (h-4 w-4 =
|
|
571
602
|
16px works well for a 32px circle). */}
|
|
572
|
-
|
|
573
|
-
|
|
603
|
+
<div
|
|
604
|
+
className={cn(
|
|
605
|
+
'flex items-center',
|
|
606
|
+
isV2 ? 'gap-[var(--spacing-system-xxs)]' : 'gap-[var(--spacing-system-xs)]',
|
|
607
|
+
)}
|
|
608
|
+
>
|
|
609
|
+
{/* Avatar rules:
|
|
574
610
|
- Assistant/Fae always show an avatar — host brand icon when no
|
|
575
611
|
image is supplied, else the filled SquareAvatar.
|
|
576
612
|
- User shows the SquareAvatar ONLY when an avatar image actually
|
|
577
613
|
arrived. With no user avatar we hide the block entirely (just
|
|
578
614
|
the name), instead of an initials placeholder. TEMPORARY —
|
|
579
615
|
restore the user placeholder when user avatars ship. */}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
? 'text-ods-open-yellow'
|
|
595
|
-
: authorType === 'admin'
|
|
616
|
+
{showAvatar &&
|
|
617
|
+
!isSystem &&
|
|
618
|
+
!(isUser && !avatar) &&
|
|
619
|
+
(!isUser && assistantIcon && !avatar ? (
|
|
620
|
+
// Host-supplied brand icon (e.g. Mingo): render it directly,
|
|
621
|
+
// no filled pill — the icon carries its own brand accent.
|
|
622
|
+
<div className="flex flex-shrink-0 items-center justify-center">{assistantIcon}</div>
|
|
623
|
+
) : (
|
|
624
|
+
<SquareAvatar {...avatarProps} />
|
|
625
|
+
))}
|
|
626
|
+
<span
|
|
627
|
+
className={cn(
|
|
628
|
+
'flex-1 !font-mono !font-medium text-h3',
|
|
629
|
+
authorType === 'system'
|
|
596
630
|
? 'text-ods-open-yellow'
|
|
597
|
-
: authorType === '
|
|
598
|
-
? 'text-ods-
|
|
599
|
-
: authorType === '
|
|
600
|
-
? 'text-ods-flamingo-
|
|
601
|
-
: '
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
<span className="shrink-0 whitespace-nowrap text-ods-text-secondary text-h6">
|
|
609
|
-
{formatMessageTimestamp(timestamp)}
|
|
631
|
+
: authorType === 'admin'
|
|
632
|
+
? 'text-ods-open-yellow'
|
|
633
|
+
: authorType === 'mingo'
|
|
634
|
+
? 'text-ods-flamingo-cyan'
|
|
635
|
+
: authorType === 'fae'
|
|
636
|
+
? 'text-ods-flamingo-pink'
|
|
637
|
+
: 'text-ods-text-secondary',
|
|
638
|
+
)}
|
|
639
|
+
>
|
|
640
|
+
{name || (isUser ? 'User' : assistantType === 'mingo' ? 'Mingo' : 'Fae')}
|
|
641
|
+
{!isSystem && !isV2 && ':'}
|
|
610
642
|
</span>
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
<div className="grid grid-cols-1 gap-2">
|
|
617
|
-
{segments.map((segment, index) => {
|
|
618
|
-
// The engine's streaming path (atomic-block memoization +
|
|
619
|
-
// fence tail-completion + aria-live) applies ONLY to the
|
|
620
|
-
// actively streaming segment: last segment of a message that
|
|
621
|
-
// is still typing. On completion `isTyping` flips false and
|
|
622
|
-
// the engine does one authoritative whole-document parse.
|
|
623
|
-
const segmentIsStreaming = index === segments.length - 1 && !!isTyping;
|
|
624
|
-
if (segment.type === 'text') {
|
|
625
|
-
return (
|
|
626
|
-
<div
|
|
627
|
-
key={index}
|
|
628
|
-
className={cn(
|
|
629
|
-
'w-full min-w-0 break-words text-h4',
|
|
630
|
-
isError ? 'text-ods-error' : 'text-ods-text-primary',
|
|
631
|
-
)}
|
|
632
|
-
>
|
|
633
|
-
{renderSegmentBody(index, segment.text, segmentIsStreaming)}
|
|
634
|
-
</div>
|
|
635
|
-
);
|
|
636
|
-
} else if (segment.type === 'ask') {
|
|
637
|
-
// Only the run's head draws — the tail segments are pages of
|
|
638
|
-
// the card already rendered above them.
|
|
639
|
-
const run = askRuns.get(index);
|
|
640
|
-
if (!run) return null;
|
|
641
|
-
return <AskDisplay key={index} cards={run} onSelect={onAskSelect} />;
|
|
642
|
-
} else if (segment.type === 'tool_execution') {
|
|
643
|
-
return (
|
|
644
|
-
<ToolExecutionDisplay
|
|
645
|
-
key={index}
|
|
646
|
-
message={segment.data}
|
|
647
|
-
assistantType={assistantType}
|
|
648
|
-
variant={approvalVariant}
|
|
649
|
-
/>
|
|
650
|
-
);
|
|
651
|
-
} else if (segment.type === 'approval_request') {
|
|
652
|
-
return (
|
|
653
|
-
<ApprovalRequestMessage
|
|
654
|
-
key={index}
|
|
655
|
-
data={segment.data}
|
|
656
|
-
status={segment.status}
|
|
657
|
-
resolvedByName={segment.resolvedByName}
|
|
658
|
-
onApprove={segment.onApprove}
|
|
659
|
-
onReject={segment.onReject}
|
|
660
|
-
assistantType={assistantType}
|
|
661
|
-
variant={approvalVariant}
|
|
662
|
-
/>
|
|
663
|
-
);
|
|
664
|
-
} else if (segment.type === 'approval_batch') {
|
|
665
|
-
return (
|
|
666
|
-
<ApprovalBatchMessage
|
|
667
|
-
key={index}
|
|
668
|
-
data={segment.data}
|
|
669
|
-
status={segment.status}
|
|
670
|
-
resolvedByName={segment.resolvedByName}
|
|
671
|
-
onApprove={segment.onApprove}
|
|
672
|
-
onReject={segment.onReject}
|
|
673
|
-
assistantType={assistantType}
|
|
674
|
-
variant={approvalVariant}
|
|
675
|
-
/>
|
|
676
|
-
);
|
|
677
|
-
} else if (segment.type === 'escalation_offer') {
|
|
678
|
-
return (
|
|
679
|
-
<EscalationOfferMessage
|
|
680
|
-
key={index}
|
|
681
|
-
data={segment.data}
|
|
682
|
-
status={segment.status}
|
|
683
|
-
resolvedByName={segment.resolvedByName}
|
|
684
|
-
onApprove={segment.onApprove}
|
|
685
|
-
onReject={segment.onReject}
|
|
686
|
-
/>
|
|
687
|
-
);
|
|
688
|
-
} else if (segment.type === 'ticket_escalated') {
|
|
689
|
-
return <TicketEscalatedMessage key={index} data={segment.data} timestamp={timestamp} />;
|
|
690
|
-
} else if (segment.type === 'ticket_event') {
|
|
691
|
-
// The card's own event time; the bubble timestamp is the
|
|
692
|
-
// turn's FIRST row and lags every later lifecycle event.
|
|
693
|
-
return (
|
|
694
|
-
<TicketEventMessage key={index} data={segment.data} timestamp={segment.occurredAt ?? timestamp} />
|
|
695
|
-
);
|
|
696
|
-
} else if (segment.type === 'error') {
|
|
697
|
-
return <ErrorMessageDisplay key={index} title={segment.title} details={segment.details} />;
|
|
698
|
-
} else if (segment.type === 'context_compaction') {
|
|
699
|
-
return <ContextCompactionDisplay key={index} status={segment.status} />;
|
|
700
|
-
} else if (segment.type === 'thinking') {
|
|
701
|
-
const isStreaming = index === segments.length - 1 && isTyping;
|
|
702
|
-
return <ThinkingDisplay key={index} text={segment.text} isStreaming={isStreaming} />;
|
|
703
|
-
}
|
|
704
|
-
return null;
|
|
705
|
-
})}
|
|
643
|
+
{timestamp && (
|
|
644
|
+
<span className="shrink-0 whitespace-nowrap text-ods-text-secondary text-h6">
|
|
645
|
+
{formatMessageTimestamp(timestamp)}
|
|
646
|
+
</span>
|
|
647
|
+
)}
|
|
706
648
|
</div>
|
|
707
|
-
)}
|
|
708
649
|
|
|
709
|
-
|
|
650
|
+
{/* Message segments — hidden for system messages without content */}
|
|
651
|
+
{(!isSystem || segments.length > 0) && (
|
|
652
|
+
<div className="grid grid-cols-1 gap-2">
|
|
653
|
+
{segments.map((segment, index) => {
|
|
654
|
+
// The engine's streaming path (atomic-block memoization +
|
|
655
|
+
// fence tail-completion + aria-live) applies ONLY to the
|
|
656
|
+
// actively streaming segment: last segment of a message that
|
|
657
|
+
// is still typing. On completion `isTyping` flips false and
|
|
658
|
+
// the engine does one authoritative whole-document parse.
|
|
659
|
+
const segmentIsStreaming = index === segments.length - 1 && !!isTyping;
|
|
660
|
+
if (segment.type === 'text') {
|
|
661
|
+
return (
|
|
662
|
+
<div
|
|
663
|
+
key={index}
|
|
664
|
+
className={cn(
|
|
665
|
+
'w-full min-w-0 break-words text-h4',
|
|
666
|
+
isError ? 'text-ods-error' : 'text-ods-text-primary',
|
|
667
|
+
)}
|
|
668
|
+
>
|
|
669
|
+
{renderSegmentBody(index, segment.text, segmentIsStreaming)}
|
|
670
|
+
</div>
|
|
671
|
+
);
|
|
672
|
+
} else if (segment.type === 'ask') {
|
|
673
|
+
// Only the run's head draws — the tail segments are pages of
|
|
674
|
+
// the card already rendered above them.
|
|
675
|
+
const run = askRuns.get(index);
|
|
676
|
+
if (!run) return null;
|
|
677
|
+
return <AskDisplay key={index} cards={run} onSelect={onAskSelect} />;
|
|
678
|
+
} else if (segment.type === 'tool_execution') {
|
|
679
|
+
return (
|
|
680
|
+
<ToolExecutionDisplay
|
|
681
|
+
key={index}
|
|
682
|
+
message={segment.data}
|
|
683
|
+
assistantType={assistantType}
|
|
684
|
+
variant={approvalVariant}
|
|
685
|
+
/>
|
|
686
|
+
);
|
|
687
|
+
} else if (segment.type === 'approval_request') {
|
|
688
|
+
return (
|
|
689
|
+
<ApprovalRequestMessage
|
|
690
|
+
key={index}
|
|
691
|
+
data={segment.data}
|
|
692
|
+
status={segment.status}
|
|
693
|
+
resolvedByName={segment.resolvedByName}
|
|
694
|
+
onApprove={segment.onApprove}
|
|
695
|
+
onReject={segment.onReject}
|
|
696
|
+
assistantType={assistantType}
|
|
697
|
+
variant={approvalVariant}
|
|
698
|
+
/>
|
|
699
|
+
);
|
|
700
|
+
} else if (segment.type === 'approval_batch') {
|
|
701
|
+
return (
|
|
702
|
+
<ApprovalBatchMessage
|
|
703
|
+
key={index}
|
|
704
|
+
data={segment.data}
|
|
705
|
+
status={segment.status}
|
|
706
|
+
resolvedByName={segment.resolvedByName}
|
|
707
|
+
onApprove={segment.onApprove}
|
|
708
|
+
onReject={segment.onReject}
|
|
709
|
+
assistantType={assistantType}
|
|
710
|
+
variant={approvalVariant}
|
|
711
|
+
/>
|
|
712
|
+
);
|
|
713
|
+
} else if (segment.type === 'escalation_offer') {
|
|
714
|
+
return (
|
|
715
|
+
<EscalationOfferMessage
|
|
716
|
+
key={index}
|
|
717
|
+
data={segment.data}
|
|
718
|
+
status={segment.status}
|
|
719
|
+
resolvedByName={segment.resolvedByName}
|
|
720
|
+
onApprove={segment.onApprove}
|
|
721
|
+
onReject={segment.onReject}
|
|
722
|
+
/>
|
|
723
|
+
);
|
|
724
|
+
} else if (segment.type === 'ticket_escalated') {
|
|
725
|
+
return <TicketEscalatedMessage key={index} data={segment.data} timestamp={timestamp} />;
|
|
726
|
+
} else if (segment.type === 'ticket_event') {
|
|
727
|
+
// The card's own event time; the bubble timestamp is the
|
|
728
|
+
// turn's FIRST row and lags every later lifecycle event.
|
|
729
|
+
return (
|
|
730
|
+
<TicketEventMessage key={index} data={segment.data} timestamp={segment.occurredAt ?? timestamp} />
|
|
731
|
+
);
|
|
732
|
+
} else if (segment.type === 'error') {
|
|
733
|
+
return <ErrorMessageDisplay key={index} title={segment.title} details={segment.details} />;
|
|
734
|
+
} else if (segment.type === 'context_compaction') {
|
|
735
|
+
return <ContextCompactionDisplay key={index} status={segment.status} />;
|
|
736
|
+
} else if (segment.type === 'thinking') {
|
|
737
|
+
const isStreaming = index === segments.length - 1 && isTyping;
|
|
738
|
+
return <ThinkingDisplay key={index} text={segment.text} isStreaming={isStreaming} />;
|
|
739
|
+
}
|
|
740
|
+
return null;
|
|
741
|
+
})}
|
|
742
|
+
</div>
|
|
743
|
+
)}
|
|
744
|
+
|
|
745
|
+
{/* Attached entity-context chips (user bubbles). Read-only — no
|
|
710
746
|
remove affordance once the message is sent (Figma 31:28709). */}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
747
|
+
{stripContextItems && stripContextItems.length > 0 && (
|
|
748
|
+
<ChatContextChipStrip
|
|
749
|
+
items={stripContextItems}
|
|
750
|
+
resolveIcon={resolveContextIcon}
|
|
751
|
+
renderItem={renderContextItem}
|
|
752
|
+
className="mt-2"
|
|
753
|
+
/>
|
|
754
|
+
)}
|
|
755
|
+
</div>
|
|
719
756
|
</div>
|
|
720
|
-
</
|
|
757
|
+
</ChatAppearanceContext.Provider>
|
|
721
758
|
);
|
|
722
759
|
},
|
|
723
760
|
);
|
|
@@ -735,6 +772,7 @@ const MemoizedChatMessageEnhanced = memo(ChatMessageEnhanced, (prevProps, nextPr
|
|
|
735
772
|
prevProps.showAvatar === nextProps.showAvatar &&
|
|
736
773
|
prevProps.assistantType === nextProps.assistantType &&
|
|
737
774
|
prevProps.approvalVariant === nextProps.approvalVariant &&
|
|
775
|
+
prevProps.appearance === nextProps.appearance &&
|
|
738
776
|
prevProps.authorType === nextProps.authorType &&
|
|
739
777
|
prevProps.assistantIcon === nextProps.assistantIcon &&
|
|
740
778
|
prevProps.className === nextProps.className &&
|