Skip to content

Commit

Permalink
Merge pull request #18 from memori-ai/feat_add_new_layout
Browse files Browse the repository at this point in the history
Feat add new layouts
  • Loading branch information
andrepat0 authored Oct 1, 2024
2 parents 034eb38 + e8ed1c9 commit abd20a3
Show file tree
Hide file tree
Showing 15 changed files with 540 additions and 42 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const App = () => (
| `defaultSpeakerActive` | | `boolean` | `true` | Default value for the speaker activation |
| `disableTextEnteredEvents` | | `boolean` | `false` | Disable MemoriTextEntered events listeners for `typeMessage` functions, useful to avoid issues with multiple widgets in page. |
| `AZURE_COGNITIVE_SERVICES_TTS_KEY` | | `string` | | Azure Cognitive Services TTS key, used to generate the audio of the Memori and for STT recognition |
| `layout` | | `string` | | Layout of the Memori, can be "FULLPAGE" (default), "CHAT", "WEBSITE_ASSISTANT" or "TOTEM", see [below](#layouts) |
| `layout` | | `string` | | Layout of the Memori, can be "FULLPAGE" (default), "CHAT", "WEBSITE_ASSISTANT", "TOTEM" or "HIDDEN LAYOUT", see [below](#layouts) |
| `customLayout` | | `React.FC<LayoutProps>` | | Custom layout component, see [below](#custom-layout) |
| `customMediaRenderer` | | `(mimeType: string) => JSX.Element \| null` | | Custom media renderer, see [below](#custom-media-renderer) |
| `additionalSettings` | | `JSX.Element` | | Custom JSX or component to render within the settings drawer |
Expand All @@ -99,7 +99,7 @@ const App = () => (

### Layouts

The Memori can be displayed in three different layouts: `FULLPAGE`, `CHAT`, `WEBSITE_ASSISTANT` and `TOTEM`.
The Memori can be displayed in five different layouts: `FULLPAGE`, `CHAT`, `WEBSITE_ASSISTANT`,`TOTEM`, `HIDDEN_CHAT` and `ZOOMED_AVATAR`.
If you don't specify a layout, the default one is `FULLPAGE`.

#### FULLPAGE
Expand All @@ -118,6 +118,15 @@ If you don't specify a layout, the default one is `FULLPAGE`.

<img alt="Website assistant layout" src="./docs/website-assistant.png" width="300" />

#### HIDDEN_CHAT

<img alt="Hidden Chat" src="./docs/hidden-chat.png" width="300">

#### ZOOMED_AVATAR

<img alt="Zoomed Avatar" src="./docs/zoomed-avatar.png" width="300">


#### Custom layout

You can override the default layout by passing a custom layout component to the `customLayout` prop.
Expand Down
Binary file added docs/hidden-chat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/zoomed-avatar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 7 additions & 4 deletions src/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface Props {
baseUrl?: string;
apiUrl?: string;
animation?: string;
isZoomed?: boolean;
}

const Avatar: React.FC<Props> = ({
Expand All @@ -47,6 +48,7 @@ const Avatar: React.FC<Props> = ({
baseUrl,
apiUrl,
animation,
isZoomed = false,
}) => {
const { t } = useTranslation();
const [isClient, setIsClient] = useState(false);
Expand All @@ -73,12 +75,12 @@ const Avatar: React.FC<Props> = ({
return undefined;
};

// Render the avatar, if the avatar is a user avatar, the avatar is rendered, if the avatar is a default avatar, the avatar is rendered
const renderAvatar = () => {
if (
(integrationConfig?.avatar === 'readyplayerme' ||
integrationConfig?.avatar === 'readyplayerme-full' ||
integrationConfig?.avatar === 'customglb') &&
integrationConfig?.avatar === 'customglb' ||
integrationConfig?.avatar === 'customrpm') &&
integrationConfig?.avatarURL
) {
return (
Expand Down Expand Up @@ -133,6 +135,7 @@ const Avatar: React.FC<Props> = ({
speaking={isPlayingAudio}
loading={loading}
style={getAvatarStyle()}
isZoomed={isZoomed}
/>
</ErrorBoundary>
);
Expand Down Expand Up @@ -177,8 +180,8 @@ const Avatar: React.FC<Props> = ({
};
}
return {
width: '600px',
height: '600px',
width: '100%',
height: '100%',
backgroundColor: 'none',
};
};
Expand Down
15 changes: 15 additions & 0 deletions src/components/Avatar/AvatarView/AvatarView.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ Default.args = {
'https://assets.memori.ai/api/v2/asset/d8035229-08cf-42a7-a532-ab051df2603d.png',
};


export const EyeBlink = Template.bind({});
EyeBlink.args = {
eyeBlink: true,
Expand Down Expand Up @@ -157,6 +158,20 @@ Fullbody.args = {
halfBody: false,
};

export const FullbodyZoomed = Template.bind({});
FullbodyZoomed.args = {
sex: 'MALE',
eyeBlink: true,
headMovement: true,
rotateAvatar: true,
speaking: false,
isZoomed: true,
url: 'https://models.readyplayer.me/63b55751f17e295642bf07a2.glb',
fallbackImg:
'https://assets.memori.ai/api/v2/asset/3049582f-db5f-452c-913d-e4340d4afd0a.png',
halfBody: false,
};

export const FullbodyAnimatedIdle = Template.bind({});
FullbodyAnimatedIdle.args = {
sex: 'MALE',
Expand Down
25 changes: 8 additions & 17 deletions src/components/Avatar/AvatarView/components/fullbodyAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ export interface FullbodyAvatarProps {
timeScale: number;
loading?: boolean;
speaking?: boolean;
isZoomed?: boolean;
}

const AVATAR_POSITION = new Vector3(0, -1, 0);
const AVATAR_ROTATION = new Euler(0.175, 0, 0);
const AVATAR_POSITION_ZOOMED = new Vector3(0, -1.53, 0);
const ANIMATION_URLS = {
MALE: 'https://assets.memori.ai/api/v2/asset/5de7456f-0cd8-4e29-95a7-0cd0045a5325.glb',
FEMALE:
Expand All @@ -46,7 +48,8 @@ export default function FullbodyAvatar({
onLoaded,
currentBaseAction,
additiveActions,
timeScale
timeScale,
isZoomed,
}: FullbodyAvatarProps) {
const { scene } = useGLTF(url);
const { animations } = useGLTF(ANIMATION_URLS[sex]);
Expand Down Expand Up @@ -87,21 +90,6 @@ export default function FullbodyAvatar({
};
}, [currentBaseAction, timeScale]);




// useEffect(() => {
// if (speaking && actions['Talk 1'] && actions['Talk 2']) {
// const talk1 = actions['Talk 1'].getClip();
// const talk2 = actions['Talk 2'].getClip();
// const talk = new AnimationClip(
// 'Talk',
// talk1.duration + talk2.duration,
// );
// mixer.clipAction(talk, scene).play();
// }
// }, [speaking]);

// Additive actions
useEyeBlink(additiveActions.blink.weight > 0, nodes);
useMouthSpeaking(additiveActions.speak.weight > 0, nodes);
Expand All @@ -113,7 +101,10 @@ export default function FullbodyAvatar({
});

return (
<group position={AVATAR_POSITION} rotation={AVATAR_ROTATION}>
<group
position={isZoomed ? AVATAR_POSITION_ZOOMED : AVATAR_POSITION}
rotation={AVATAR_ROTATION}
>
<primitive object={scene} />
</group>
);
Expand Down
21 changes: 17 additions & 4 deletions src/components/Avatar/AvatarView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface Props {
loading?: boolean;
animation?: string;
showControls?: boolean;
isZoomed?: boolean;
}

const baseActions: Record<string, BaseAction> = {
Expand All @@ -49,8 +50,9 @@ const baseActions: Record<string, BaseAction> = {

const defaultStyles = {
halfBody: {
width: '250px',
height: '250px',
width: '100%',
height: '100%',
minHeight: '500px', // Ensure minimum height
backgroundColor: 'white',
borderRadius: '100%',
},
Expand All @@ -62,12 +64,18 @@ const defaultStyles = {
};

/* Animation Control Panel */
const getCameraSettings = (halfBody: boolean) =>
const getCameraSettings = (halfBody: boolean, isZoomed?: boolean) =>
halfBody
? {
fov: 40,
position: [0, 0, 0.6],
}
: !halfBody && isZoomed
? {
// Zoomed in
fov: 44,
position: [0,0,1.25],
}
: { fov: 40, position: [0, 0.0000175, 3] };

const getLightingComponent = () =>
Expand Down Expand Up @@ -99,6 +107,7 @@ const AvatarComponent = ({
timeScale: number;
loading?: boolean;
animation?: string;
isZoomed?: boolean;
}) =>
halfBody ? <HalfBodyAvatar {...props} /> : <FullbodyAvatar {...props} />;

Expand All @@ -112,6 +121,7 @@ const AvatarView = ({
headMovement,
speaking,
halfBody,
isZoomed,
}: Props & { halfBody: boolean }) => {
const [currentBaseAction, setCurrentBaseAction] = useState({
action: animation || 'Idle',
Expand Down Expand Up @@ -209,6 +219,7 @@ const AvatarView = ({
currentBaseAction={currentBaseAction}
timeScale={timeScale}
animation={animation}
isZoomed={isZoomed}
/>
</>
);
Expand All @@ -228,13 +239,14 @@ export default function ContainerAvatarView({
loading,
animation,
showControls = false,
isZoomed,
}: Props) {
return (
<Canvas
style={
style || (halfBody ? defaultStyles.halfBody : defaultStyles.fullBody)
}
camera={getCameraSettings(halfBody) as any}
camera={getCameraSettings(halfBody, isZoomed) as any}
>
<Suspense fallback={fallback || <Loader fallbackImg={fallbackImg} />}>
{getLightingComponent()}
Expand All @@ -249,6 +261,7 @@ export default function ContainerAvatarView({
loading={loading}
animation={animation}
showControls={showControls}
isZoomed={isZoomed}
/>
</Suspense>
</Canvas>
Expand Down
8 changes: 7 additions & 1 deletion src/components/MemoriWidget/MemoriWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ import {
import { anonTag, uiLanguages } from '../../helpers/constants';
import { getErrori18nKey } from '../../helpers/error';
import { getCredits } from '../../helpers/credits';
import HiddenChatLayout from '../layouts/HiddenChat';
import ZoomedFullBodyLayout from '../layouts/ZoomedFullBody';

// Widget utilities and helpers
const getMemoriState = (integrationId?: string): object | null => {
Expand Down Expand Up @@ -333,7 +335,7 @@ export interface Props {
memoriLang?: string;
multilingual?: boolean;
integration?: Integration;
layout?: 'DEFAULT' | 'FULLPAGE' | 'TOTEM' | 'CHAT' | 'WEBSITE_ASSISTANT';
layout?: 'DEFAULT' | 'FULLPAGE' | 'TOTEM' | 'CHAT' | 'WEBSITE_ASSISTANT' | 'HIDDEN_CHAT' | 'ZOOMED_FULL_BODY';
customLayout?: React.FC<LayoutProps>;
showShare?: boolean;
showCopyButton?: boolean;
Expand Down Expand Up @@ -3134,6 +3136,10 @@ const MemoriWidget = ({
? FullPageLayout
: selectedLayout === 'WEBSITE_ASSISTANT'
? WebsiteAssistantLayout
: selectedLayout === 'HIDDEN_CHAT'
? HiddenChatLayout
: selectedLayout === 'ZOOMED_FULL_BODY'
? ZoomedFullBodyLayout
: FullPageLayout;

return (
Expand Down
2 changes: 1 addition & 1 deletion src/components/StartPanel/StartPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ const StartPanel: React.FC<Props> = ({
</Button>

<CompletionProviderStatus
provider={memori.completionConfigForQuestionAnswering?.provider}
provider={memori?.completionConfigForQuestionAnswering?.provider}
forceStatus={_TEST_forceProviderStatus}
/>

Expand Down
Loading

0 comments on commit abd20a3

Please sign in to comment.