Skip to content
This repository has been archived by the owner on Feb 5, 2025. It is now read-only.

fix: header actions (COR-3811) #278

Merged
merged 2 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 104 additions & 104 deletions apps/documentation/public/bundle/bundle.mjs

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions apps/documentation/src/pages/molecules/header.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ import { StoryEmbed } from '../../components/StoryEmbed';

## Base

<StoryEmbed for="Header" name="Base" clientOnly={true} />
<StoryEmbed for="Header" name="Actionable" clientOnly={true} />

## With actions
## Muted

<StoryEmbed for="Header" name="Actionable" clientOnly={true} />
<StoryEmbed for="Header" name="Muted" clientOnly={true} />

## Without an image

<StoryEmbed for="Header" name="NoImage" clientOnly={true} />

## Mobile

<StoryEmbed for="Header" name="Mobile" clientOnly={true} />

</div>
1 change: 1 addition & 0 deletions packages/chat/src/assets/svg/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export { default as documentUrl } from './document-url.svg?react';
export { default as largeArrowLeft } from './large-arrow-left.svg?react';
export { default as microphone } from './microphone.svg?react';
export { default as minus } from './minus.svg?react';
export { default as mute } from './mute.svg?react';
export { default as reset } from './reset.svg?react';
export { default as smallArrowUp } from './small-arrow-up.svg?react';
export { default as sound } from './sound.svg?react';
Expand Down
5 changes: 5 additions & 0 deletions packages/chat/src/assets/svg/mute.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 18 additions & 5 deletions packages/chat/src/components/Header/Header.story.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react';

import { VF_ICON } from '@/fixtures';
import EMPTY_IMAGE from '@/__fixtures__/empty-image.png';
import { WithDefaultPalette } from '@/storybook/decorators';

import { Header } from '.';
Expand All @@ -10,7 +10,7 @@ const meta: Meta<typeof Header> = {
component: Header,
args: {
title: 'Agent name',
image: VF_ICON,
image: EMPTY_IMAGE,
actions: [],
},
render: (args) => <Header {...args} />,
Expand All @@ -25,21 +25,34 @@ export const Base: Story = {};

export const Actionable: Story = {
args: {
actions: [{ svg: 'volume' }, { svg: 'reset' }, { svg: 'close' }],
actions: [{ svg: 'volume' }, { svg: 'reset' }],
},
};

export const Muted: Story = {
args: {
actions: [{ svg: 'mute' }, { svg: 'reset' }],
},
};

export const Themed: Story = {
args: {
actions: [{ svg: 'volume' }, { svg: 'reset' }, { svg: 'close' }],
actions: [{ svg: 'volume' }, { svg: 'reset' }],
},
decorators: [WithDefaultPalette],
};

export const NoImage: Story = {
args: {
actions: [{ svg: 'volume' }, { svg: 'reset' }, { svg: 'close' }],
actions: [{ svg: 'volume' }, { svg: 'reset' }],
image: undefined,
},
decorators: [WithDefaultPalette],
};

export const Mobile: Story = {
args: {
actions: [{ svg: 'volume' }, { svg: 'reset' }, { svg: 'close' }],
},
decorators: [WithDefaultPalette],
};
21 changes: 12 additions & 9 deletions packages/chat/src/components/NewChat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ import { useContext, useMemo, useRef, useState } from 'react';

import { ClassName } from '@/constants';
import { AutoScrollProvider, RuntimeStateAPIContext, RuntimeStateContext } from '@/contexts';
import { RenderMode } from '@/dtos/RenderOptions.dto';
import type { Nullish } from '@/types';
import { chain } from '@/utils/functional';

import mockAvatar from '../../assets/blank-image.png';
import { Header, type HeaderActionProps, type HeaderProps } from '../Header';
import { type INewFooter, NewFooter } from '../NewFooter';
import { Prompt } from '../Prompt';
Expand All @@ -26,6 +24,11 @@ export interface INewChat extends HeaderProps, IWelcomeMessage, INewFooter, Reac
*/
audioInterface?: boolean;

/**
* If true, the user is using a mobile device.
*/
isMobile?: boolean;

/**
* A unix timestamp indicating the start of the conversation.
*/
Expand Down Expand Up @@ -58,6 +61,7 @@ export const NewChat: React.FC<INewChat> = ({
extraLinkUrl,
children,
audioInterface,
isMobile,
}) => {
const [hasAlert, setAlert] = useState(false);

Expand All @@ -75,15 +79,14 @@ export const NewChat: React.FC<INewChat> = ({
const handleResume = (): void => setAlert(false);

const headerActions = useMemo<HeaderActionProps[]>(() => {
const items: HeaderActionProps[] = [{ svg: 'close', onClick: handleClose }];

if (config.render?.mode === RenderMode.OVERLAY) {
items.unshift({ svg: 'minus', onClick: onMinimize });
const items: HeaderActionProps[] = [{ svg: 'reset', onClick: handleClose }];
if (isMobile) {
items.push({ svg: 'close', onClick: onMinimize });
}

if (audioInterface) {
items.unshift({
svg: state.audioOutput ? 'sound' : 'soundOff',
svg: state.audioOutput ? 'volume' : 'mute',
onClick: toggleAudioOutput,
});
}
Expand All @@ -96,7 +99,7 @@ export const NewChat: React.FC<INewChat> = ({

return (
<div className={clsx(ClassName.CHAT, chatContainer)}>
<Header title={title} image={mockAvatar} actions={headerActions} />
<Header title={title} image={avatar} actions={headerActions} />
<div ref={scrollableAreaRef} className={dialogContainer}>
<AutoScrollProvider target={scrollableAreaRef}>
<WelcomeMessage title={title} description={description} avatar={avatar} />
Expand All @@ -117,7 +120,7 @@ export const NewChat: React.FC<INewChat> = ({
/>
<Prompt
visible={hasAlert}
accept={{ label: 'End Chat', /* type: 'warn', */ onClick: chain(onEnd, handleResume) }}
accept={{ label: 'Start new chat', onClick: chain(onEnd, handleResume) }}
cancel={{ label: 'Cancel', onClick: handleResume }}
/>
</div>
Expand Down
10 changes: 10 additions & 0 deletions packages/chat/src/dtos/RenderOptions.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@ import { z } from 'zod';
export const EMBEDDED_TARGET = 'voiceflow-chat-frame';

export enum RenderMode {
/**
* Embed the chat window into a specific container in the screen.
* This won't show the Launcher button because the chat will be
* opened by default.
*/
EMBEDDED = 'embedded',

/**
* Shows the launcher button in the bottom corder of the screen,
* and the user needs to push it to open/minimize the chat window.
*/
OVERLAY = 'overlay',
}

Expand Down
4 changes: 3 additions & 1 deletion packages/chat/src/views/ChatWindow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import { SessionStatus, TurnType } from '@/types';

export interface ChatWindowProps {
className?: string;
isMobile?: boolean;
}

export const ChatWindow: React.FC<ChatWindowProps> = ({ className }) => {
export const ChatWindow: React.FC<ChatWindowProps> = ({ isMobile, className }) => {
const runtime = useContext(RuntimeStateAPIContext);
const state = useContext(RuntimeStateContext);
const { assistant, config } = runtime;
Expand Down Expand Up @@ -59,6 +60,7 @@ export const ChatWindow: React.FC<ChatWindowProps> = ({ className }) => {
messageInputProps={{
onSubmit: runtime.reply,
}}
isMobile={isMobile}
>
{state.session.turns.map((turn, turnIndex) =>
match(turn)
Expand Down