@memori.ai/memori-react 7.5.1 → 7.6.0

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.
Files changed (156) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +10 -2
  3. package/dist/components/Avatar/Avatar.d.ts +2 -0
  4. package/dist/components/Avatar/Avatar.js +11 -6
  5. package/dist/components/Avatar/Avatar.js.map +1 -1
  6. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +20 -0
  7. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +107 -0
  8. package/dist/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -0
  9. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controls.d.ts +26 -0
  10. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controls.js +59 -0
  11. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controls.js.map +1 -0
  12. package/dist/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.d.ts +30 -0
  13. package/dist/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.js +148 -0
  14. package/dist/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.js.map +1 -0
  15. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +15 -0
  16. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +77 -0
  17. package/dist/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -0
  18. package/dist/components/Avatar/AvatarView/AvatarComponent/components/loader.d.ts +5 -0
  19. package/dist/components/Avatar/AvatarView/AvatarComponent/components/loader.js +12 -0
  20. package/dist/components/Avatar/AvatarView/AvatarComponent/components/loader.js.map +1 -0
  21. package/dist/components/Avatar/AvatarView/components/fullbodyAvatar.d.ts +2 -1
  22. package/dist/components/Avatar/AvatarView/components/fullbodyAvatar.js +3 -2
  23. package/dist/components/Avatar/AvatarView/components/fullbodyAvatar.js.map +1 -1
  24. package/dist/components/Avatar/AvatarView/index.d.ts +6 -1
  25. package/dist/components/Avatar/AvatarView/index.js +14 -84
  26. package/dist/components/Avatar/AvatarView/index.js.map +1 -1
  27. package/dist/components/Avatar/AvatarView/utils/useEyeBlink.d.ts +16 -2
  28. package/dist/components/Avatar/AvatarView/utils/useEyeBlink.js +62 -38
  29. package/dist/components/Avatar/AvatarView/utils/useEyeBlink.js.map +1 -1
  30. package/dist/components/Avatar/AvatarView/utils/useMouthAnimation.d.ts +16 -0
  31. package/dist/components/Avatar/AvatarView/utils/useMouthAnimation.js +59 -0
  32. package/dist/components/Avatar/AvatarView/utils/useMouthAnimation.js.map +1 -0
  33. package/dist/components/Avatar/AvatarView/utils/useSmile.js +1 -1
  34. package/dist/components/Avatar/AvatarView/utils/useSmile.js.map +1 -1
  35. package/dist/components/ChatBubble/ChatBubble.js +2 -3
  36. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  37. package/dist/components/CompletionProviderStatus/CompletionProviderStatus.d.ts +1 -1
  38. package/dist/components/CompletionProviderStatus/CompletionProviderStatus.js +24 -3
  39. package/dist/components/CompletionProviderStatus/CompletionProviderStatus.js.map +1 -1
  40. package/dist/components/MemoriWidget/MemoriWidget.d.ts +1 -1
  41. package/dist/components/MemoriWidget/MemoriWidget.js +25 -3
  42. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  43. package/dist/components/StartPanel/StartPanel.js +1 -1
  44. package/dist/components/StartPanel/StartPanel.js.map +1 -1
  45. package/dist/components/layouts/HiddenChat.d.ts +4 -0
  46. package/dist/components/layouts/HiddenChat.js +51 -0
  47. package/dist/components/layouts/HiddenChat.js.map +1 -0
  48. package/dist/components/layouts/ZoomedFullBody.d.ts +4 -0
  49. package/dist/components/layouts/ZoomedFullBody.js +8 -0
  50. package/dist/components/layouts/ZoomedFullBody.js.map +1 -0
  51. package/dist/components/layouts/hidden-chat.css +184 -0
  52. package/dist/context/visemeContext.d.ts +27 -0
  53. package/dist/context/visemeContext.js +221 -0
  54. package/dist/context/visemeContext.js.map +1 -0
  55. package/dist/helpers/utils.d.ts +7 -0
  56. package/dist/helpers/utils.js +51 -1
  57. package/dist/helpers/utils.js.map +1 -1
  58. package/dist/index.js +20 -16
  59. package/dist/index.js.map +1 -1
  60. package/dist/styles.css +1 -0
  61. package/esm/components/Avatar/Avatar.d.ts +2 -0
  62. package/esm/components/Avatar/Avatar.js +11 -6
  63. package/esm/components/Avatar/Avatar.js.map +1 -1
  64. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.d.ts +20 -0
  65. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js +102 -0
  66. package/esm/components/Avatar/AvatarView/AvatarComponent/avatarComponent.js.map +1 -0
  67. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controls.d.ts +26 -0
  68. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controls.js +56 -0
  69. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controls.js.map +1 -0
  70. package/esm/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.d.ts +30 -0
  71. package/esm/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.js +145 -0
  72. package/esm/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.js.map +1 -0
  73. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.d.ts +15 -0
  74. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js +73 -0
  75. package/esm/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.js.map +1 -0
  76. package/esm/components/Avatar/AvatarView/AvatarComponent/components/loader.d.ts +5 -0
  77. package/esm/components/Avatar/AvatarView/AvatarComponent/components/loader.js +9 -0
  78. package/esm/components/Avatar/AvatarView/AvatarComponent/components/loader.js.map +1 -0
  79. package/esm/components/Avatar/AvatarView/components/fullbodyAvatar.d.ts +2 -1
  80. package/esm/components/Avatar/AvatarView/components/fullbodyAvatar.js +3 -2
  81. package/esm/components/Avatar/AvatarView/components/fullbodyAvatar.js.map +1 -1
  82. package/esm/components/Avatar/AvatarView/index.d.ts +6 -1
  83. package/esm/components/Avatar/AvatarView/index.js +15 -85
  84. package/esm/components/Avatar/AvatarView/index.js.map +1 -1
  85. package/esm/components/Avatar/AvatarView/utils/useEyeBlink.d.ts +16 -2
  86. package/esm/components/Avatar/AvatarView/utils/useEyeBlink.js +61 -38
  87. package/esm/components/Avatar/AvatarView/utils/useEyeBlink.js.map +1 -1
  88. package/esm/components/Avatar/AvatarView/utils/useMouthAnimation.d.ts +16 -0
  89. package/esm/components/Avatar/AvatarView/utils/useMouthAnimation.js +55 -0
  90. package/esm/components/Avatar/AvatarView/utils/useMouthAnimation.js.map +1 -0
  91. package/esm/components/Avatar/AvatarView/utils/useSmile.js +1 -1
  92. package/esm/components/Avatar/AvatarView/utils/useSmile.js.map +1 -1
  93. package/esm/components/ChatBubble/ChatBubble.js +2 -3
  94. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  95. package/esm/components/CompletionProviderStatus/CompletionProviderStatus.d.ts +1 -1
  96. package/esm/components/CompletionProviderStatus/CompletionProviderStatus.js +24 -3
  97. package/esm/components/CompletionProviderStatus/CompletionProviderStatus.js.map +1 -1
  98. package/esm/components/MemoriWidget/MemoriWidget.d.ts +1 -1
  99. package/esm/components/MemoriWidget/MemoriWidget.js +26 -4
  100. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  101. package/esm/components/StartPanel/StartPanel.js +1 -1
  102. package/esm/components/StartPanel/StartPanel.js.map +1 -1
  103. package/esm/components/layouts/HiddenChat.d.ts +4 -0
  104. package/esm/components/layouts/HiddenChat.js +48 -0
  105. package/esm/components/layouts/HiddenChat.js.map +1 -0
  106. package/esm/components/layouts/ZoomedFullBody.d.ts +4 -0
  107. package/esm/components/layouts/ZoomedFullBody.js +5 -0
  108. package/esm/components/layouts/ZoomedFullBody.js.map +1 -0
  109. package/esm/components/layouts/hidden-chat.css +184 -0
  110. package/esm/context/visemeContext.d.ts +27 -0
  111. package/esm/context/visemeContext.js +216 -0
  112. package/esm/context/visemeContext.js.map +1 -0
  113. package/esm/helpers/utils.d.ts +7 -0
  114. package/esm/helpers/utils.js +45 -0
  115. package/esm/helpers/utils.js.map +1 -1
  116. package/esm/index.js +20 -16
  117. package/esm/index.js.map +1 -1
  118. package/esm/styles.css +1 -0
  119. package/package.json +2 -2
  120. package/src/components/Avatar/Avatar.test.tsx +28 -20
  121. package/src/components/Avatar/Avatar.tsx +19 -5
  122. package/src/components/Avatar/AvatarView/AvatarComponent/avatarComponent.tsx +222 -0
  123. package/src/components/Avatar/AvatarView/{components → AvatarComponent/components}/controls.tsx +16 -10
  124. package/src/components/Avatar/AvatarView/AvatarComponent/components/fullbodyAvatar.tsx +234 -0
  125. package/src/components/Avatar/AvatarView/AvatarComponent/components/halfbodyAvatar.tsx +123 -0
  126. package/src/components/Avatar/AvatarView/{components → AvatarComponent/components}/loader.tsx +1 -1
  127. package/src/components/Avatar/AvatarView/AvatarView.stories.tsx +47 -8
  128. package/src/components/Avatar/AvatarView/index.tsx +35 -167
  129. package/src/components/Avatar/AvatarView/utils/useEyeBlink.ts +89 -48
  130. package/src/components/Avatar/AvatarView/utils/useMouthAnimation.ts +93 -0
  131. package/src/components/Avatar/AvatarView/utils/useSmile.ts +1 -1
  132. package/src/components/ChatBubble/ChatBubble.tsx +3 -4
  133. package/src/components/CompletionProviderStatus/CompletionProviderStatus.tsx +33 -3
  134. package/src/components/CompletionProviderStatus/__snapshots__/CompletionProviderStatus.test.tsx.snap +18 -0
  135. package/src/components/MemoriWidget/MemoriWidget.tsx +60 -5
  136. package/src/components/StartPanel/StartPanel.tsx +1 -1
  137. package/src/components/layouts/Chat.test.tsx +7 -5
  138. package/src/components/layouts/FullPage.test.tsx +11 -8
  139. package/src/components/layouts/HiddenChat.test.tsx +37 -0
  140. package/src/components/layouts/HiddenChat.tsx +108 -0
  141. package/src/components/layouts/Totem.test.tsx +6 -4
  142. package/src/components/layouts/WebsiteAssistant.test.tsx +7 -5
  143. package/src/components/layouts/ZoomedFullBody.test.tsx +37 -0
  144. package/src/components/layouts/ZoomedFullBody.tsx +55 -0
  145. package/src/components/layouts/__snapshots__/HiddenChat.test.tsx.snap +210 -0
  146. package/src/components/layouts/__snapshots__/ZoomedFullBody.test.tsx.snap +444 -0
  147. package/src/components/layouts/hidden-chat.css +184 -0
  148. package/src/components/layouts/layouts.stories.tsx +135 -19
  149. package/src/context/visemeContext.tsx +328 -0
  150. package/src/helpers/utils.ts +73 -0
  151. package/src/index.stories.tsx +40 -17
  152. package/src/index.tsx +82 -78
  153. package/src/styles.css +1 -0
  154. package/src/components/Avatar/AvatarView/components/fullbodyAvatar.tsx +0 -120
  155. package/src/components/Avatar/AvatarView/components/halfbodyAvatar.tsx +0 -69
  156. package/src/components/Avatar/AvatarView/utils/useMouthSpeaking.ts +0 -87
@@ -1,23 +1,10 @@
1
1
  import { CSSProperties } from 'react';
2
2
  import React, { Suspense } from 'react';
3
- import FullbodyAvatar from './components/fullbodyAvatar';
4
3
  import { Canvas } from '@react-three/fiber';
5
4
  import { OrbitControls, SpotLight, Environment } from '@react-three/drei';
6
5
  import { isAndroid, isiOS } from '../../../helpers/utils';
7
- import { useState, useEffect } from 'react';
8
- import AnimationControlPanel from './components/controls';
9
- import HalfBodyAvatar from './components/halfbodyAvatar';
10
- import Loader from './components/loader';
11
-
12
- interface BaseAction {
13
- weight: number;
14
- action?: string;
15
- }
16
-
17
- interface AdditiveAction {
18
- weight: number;
19
- action?: string;
20
- }
6
+ import {AvatarView} from './AvatarComponent/avatarComponent';
7
+ import Loader from './AvatarComponent/components/loader';
21
8
 
22
9
  export interface Props {
23
10
  url: string;
@@ -33,24 +20,18 @@ export interface Props {
33
20
  loading?: boolean;
34
21
  animation?: string;
35
22
  showControls?: boolean;
23
+ isZoomed?: boolean;
24
+ chatEmission?: any;
25
+ setMeshRef?: any;
26
+ clearVisemes: () => void;
27
+ setEmotion: (emotion: string) => void;
36
28
  }
37
29
 
38
- const baseActions: Record<string, BaseAction> = {
39
- Idle: { weight: 1 },
40
- 'Idle 1': { weight: 0 },
41
- 'Idle 2': { weight: 0 },
42
- 'Idle 3': { weight: 0 },
43
- Loading: { weight: 0 },
44
- Sad: { weight: 0 },
45
- 'Talk 1': { weight: 0 },
46
- 'Talk 2': { weight: 0 },
47
- 'Talk 3': { weight: 0 },
48
- };
49
-
50
30
  const defaultStyles = {
51
31
  halfBody: {
52
- width: '250px',
53
- height: '250px',
32
+ width: '100%',
33
+ height: '100%',
34
+ minHeight: '500px', // Ensure minimum height
54
35
  backgroundColor: 'white',
55
36
  borderRadius: '100%',
56
37
  },
@@ -62,12 +43,18 @@ const defaultStyles = {
62
43
  };
63
44
 
64
45
  /* Animation Control Panel */
65
- const getCameraSettings = (halfBody: boolean) =>
46
+ const getCameraSettings = (halfBody: boolean, isZoomed?: boolean) =>
66
47
  halfBody
67
48
  ? {
68
49
  fov: 40,
69
50
  position: [0, 0, 0.6],
70
51
  }
52
+ : !halfBody && isZoomed
53
+ ? {
54
+ // Zoomed in
55
+ fov: 44,
56
+ position: [0, 0, 1.25],
57
+ }
71
58
  : { fov: 40, position: [0, 0.0000175, 3] };
72
59
 
73
60
  const getLightingComponent = () =>
@@ -82,137 +69,8 @@ const getLightingComponent = () =>
82
69
  ) : (
83
70
  <Environment files="https://raw.githack.com/pmndrs/drei-assets/456060a26bbeb8fdf79326f224b6d99b8bcce736/hdri/venice_sunset_1k.hdr" />
84
71
  );
72
+
85
73
 
86
- const AvatarComponent = ({
87
- animation,
88
- loading,
89
- halfBody,
90
- ...props
91
- }: Props & {
92
- halfBody: boolean;
93
- currentBaseAction: {
94
- action: string;
95
- weight: number;
96
- };
97
- baseActions: Record<string, BaseAction>;
98
- additiveActions: Record<string, AdditiveAction>;
99
- timeScale: number;
100
- loading?: boolean;
101
- animation?: string;
102
- }) =>
103
- halfBody ? <HalfBodyAvatar {...props} /> : <FullbodyAvatar {...props} />;
104
-
105
- const AvatarView = ({
106
- showControls,
107
- animation,
108
- loading,
109
- url,
110
- sex,
111
- eyeBlink,
112
- headMovement,
113
- speaking,
114
- halfBody,
115
- }: Props & { halfBody: boolean }) => {
116
- const [currentBaseAction, setCurrentBaseAction] = useState({
117
- action: animation || 'Idle',
118
- weight: 1,
119
- });
120
-
121
- const [additiveActions, setAdditiveActions] = useState({
122
- smile: { weight: 0 },
123
- blink: { weight: eyeBlink ? 1 : 0 },
124
- speak: { weight: speaking ? 1 : 0 },
125
- headMovement: { weight: headMovement ? 1 : 0 },
126
- });
127
-
128
- const [timeScale, setTimeScale] = useState(0.8);
129
-
130
- function onBaseActionChange(action: string) {
131
- setCurrentBaseAction({
132
- action,
133
- weight: 1,
134
- });
135
- }
136
-
137
- function onAdditiveActionChange(action: string, weight: number) {
138
- setAdditiveActions({
139
- ...additiveActions,
140
- [action]: { weight },
141
- });
142
- }
143
-
144
- function modifyTimeScale(value: number) {
145
- setTimeScale(value);
146
- }
147
-
148
- useEffect(() => {
149
- // If loading and not speaking, set to loading animation
150
- if (loading && currentBaseAction.action !== 'Loading' && !speaking) {
151
- setCurrentBaseAction({
152
- action: 'Loading',
153
- weight: 1,
154
- });
155
- return;
156
- }
157
-
158
- // Otherwise, if speaking, set to random talking animation
159
- if (speaking) {
160
- const talkingAnimations = ['Talk 1', 'Talk 2', 'Talk 3'];
161
- const randomIndex = Math.floor(Math.random() * talkingAnimations.length);
162
- const randomTalkingAnimation = talkingAnimations[randomIndex];
163
-
164
- setAdditiveActions({
165
- ...additiveActions,
166
- speak: { weight: 1 },
167
- });
168
-
169
- setCurrentBaseAction({
170
- action: randomTalkingAnimation,
171
- weight: 1,
172
- });
173
- } else if (!speaking && additiveActions.speak.weight !== 0) {
174
- // Otherwise, if not speaking, set to idle
175
- setAdditiveActions({
176
- ...additiveActions,
177
- speak: { weight: 0 },
178
- });
179
- setCurrentBaseAction({
180
- action: 'Idle',
181
- weight: 1,
182
- });
183
- }
184
- }, [speaking, loading]);
185
-
186
- return (
187
- <>
188
- {showControls && (
189
- <AnimationControlPanel
190
- timeScale={timeScale}
191
- onBaseActionChange={onBaseActionChange}
192
- onAdditiveActionChange={onAdditiveActionChange}
193
- baseActions={baseActions}
194
- additiveActions={additiveActions}
195
- currentBaseAction={currentBaseAction}
196
- modifyTimeScale={modifyTimeScale}
197
- />
198
- )}
199
- <AvatarComponent
200
- halfBody={halfBody}
201
- url={url}
202
- sex={sex}
203
- eyeBlink={eyeBlink}
204
- headMovement={headMovement}
205
- speaking={speaking}
206
- additiveActions={additiveActions}
207
- baseActions={baseActions}
208
- loading={loading || false}
209
- currentBaseAction={currentBaseAction}
210
- timeScale={timeScale}
211
- animation={animation}
212
- />
213
- </>
214
- );
215
- };
216
74
 
217
75
  export default function ContainerAvatarView({
218
76
  url,
@@ -228,27 +86,37 @@ export default function ContainerAvatarView({
228
86
  loading,
229
87
  animation,
230
88
  showControls = false,
89
+ isZoomed,
90
+ chatEmission,
91
+ setMeshRef,
92
+ clearVisemes,
93
+ setEmotion,
231
94
  }: Props) {
232
95
  return (
233
96
  <Canvas
234
97
  style={
235
98
  style || (halfBody ? defaultStyles.halfBody : defaultStyles.fullBody)
236
99
  }
237
- camera={getCameraSettings(halfBody) as any}
100
+ camera={getCameraSettings(halfBody, isZoomed) as any}
238
101
  >
239
102
  <Suspense fallback={fallback || <Loader fallbackImg={fallbackImg} />}>
240
103
  {getLightingComponent()}
241
104
  {rotateAvatar && <OrbitControls enablePan={false} enableZoom={false} />}
242
105
  <AvatarView
243
- halfBody={halfBody}
244
106
  url={url}
245
107
  sex={sex}
246
- eyeBlink={eyeBlink}
247
- headMovement={headMovement}
248
- speaking={speaking}
249
- loading={loading}
250
- animation={animation}
251
108
  showControls={showControls}
109
+ loading={loading || false}
110
+ animation={animation}
111
+ isZoomed={isZoomed || false}
112
+ eyeBlink={eyeBlink || false}
113
+ headMovement={headMovement || false}
114
+ speaking={speaking || false}
115
+ halfBody={halfBody || false}
116
+ chatEmission={chatEmission}
117
+ setMeshRef={setMeshRef}
118
+ clearVisemes={clearVisemes}
119
+ setEmotion={setEmotion}
252
120
  />
253
121
  </Suspense>
254
122
  </Canvas>
@@ -1,59 +1,100 @@
1
- import { SkinnedMesh } from 'three';
2
- import { Nodes } from './utils';
3
- import { useEffect, useRef, useCallback } from 'react';
4
- import { useFrame } from '@react-three/fiber';
1
+ import { useCallback, useEffect, useRef } from 'react';
5
2
 
6
- interface BlinkState {
7
- blinkTime: number;
8
- headMesh: SkinnedMesh | null;
9
- morphIndex: number;
3
+ interface BlinkConfig {
4
+ minInterval: number;
5
+ maxInterval: number;
6
+ blinkDuration: number;
10
7
  }
11
8
 
12
- const BLINK_DURATION = 2;
13
- const BLINK_INTERVAL_MIN = 2000;
14
- const BLINK_INTERVAL_MAX = 7000;
9
+ const DEFAULT_BLINK_CONFIG: BlinkConfig = {
10
+ minInterval: 1000, // Minimum time between blinks in milliseconds
11
+ maxInterval: 5000, // Maximum time between blinks in milliseconds
12
+ blinkDuration: 150, // Duration of a single blink in milliseconds
13
+ };
15
14
 
16
- export default function useEyeBlink(enabled: boolean | undefined, nodes: Nodes) {
17
- const blinkStateRef = useRef<BlinkState>({
18
- blinkTime: 999,
19
- headMesh: null,
20
- morphIndex: 0,
21
- });
15
+ interface UseAvatarBlinkProps {
16
+ enabled: boolean;
17
+ setMorphTargetInfluences: (morphTargetInfluences: any) => void;
18
+ config?: Partial<BlinkConfig>;
19
+ }
22
20
 
23
- const setNextBlink = useCallback(() => {
24
- blinkStateRef.current.blinkTime = 0;
25
- const nextBlinkDelay = Math.random() * (BLINK_INTERVAL_MAX - BLINK_INTERVAL_MIN) + BLINK_INTERVAL_MIN;
26
- setTimeout(setNextBlink, nextBlinkDelay);
27
- }, []);
21
+ export function useAvatarBlink({
22
+ enabled,
23
+ setMorphTargetInfluences,
24
+ config = {}
25
+ }: UseAvatarBlinkProps) {
26
+ const blinkTimeoutRef = useRef<NodeJS.Timeout>();
27
+ const isBlinkingRef = useRef(false);
28
+ const lastBlinkTime = useRef(0);
29
+
30
+ const blinkConfig = {
31
+ ...DEFAULT_BLINK_CONFIG,
32
+ ...config
33
+ };
34
+
35
+ const blink = useCallback(() => {
36
+ if (!enabled || isBlinkingRef.current) return;
37
+
38
+ isBlinkingRef.current = true;
39
+ // Close eyes
40
+ setMorphTargetInfluences((prev: any) => ({
41
+ ...prev,
42
+ eyesClosed: 1
43
+ }));
44
+
45
+ // Open eyes after blinkDuration
46
+ setTimeout(() => {
47
+ setMorphTargetInfluences((prev: any) => ({
48
+ ...prev,
49
+ eyesClosed: 0
50
+ }));
51
+ isBlinkingRef.current = false;
52
+ lastBlinkTime.current = Date.now();
53
+
54
+ // Schedule next blink
55
+ scheduleNextBlink();
56
+ }, blinkConfig.blinkDuration);
57
+ }, [enabled, blinkConfig.blinkDuration, setMorphTargetInfluences]);
58
+
59
+ const scheduleNextBlink = useCallback(() => {
60
+ if (blinkTimeoutRef.current) {
61
+ clearTimeout(blinkTimeoutRef.current);
62
+ }
63
+
64
+ // Randomize the next blink delay between min and max interval
65
+ const nextBlinkDelay = Math.random() *
66
+ (blinkConfig.maxInterval - blinkConfig.minInterval) +
67
+ blinkConfig.minInterval;
68
+
69
+ blinkTimeoutRef.current = setTimeout(blink, nextBlinkDelay);
70
+ }, [blink, blinkConfig.maxInterval, blinkConfig.minInterval]);
28
71
 
72
+ // Handle enabled state changes
29
73
  useEffect(() => {
30
- if (!enabled) return;
31
-
32
- const headMesh = (nodes.Wolf3D_Head || nodes.Wolf3D_Avatar) as SkinnedMesh;
33
- blinkStateRef.current.headMesh = headMesh;
34
-
35
- if (headMesh?.morphTargetDictionary && headMesh?.morphTargetInfluences) {
36
- blinkStateRef.current.morphIndex = headMesh.morphTargetDictionary.eyesClosed;
74
+ if (enabled) {
75
+ scheduleNextBlink();
76
+ } else {
77
+ if (blinkTimeoutRef.current) {
78
+ clearTimeout(blinkTimeoutRef.current);
79
+ }
80
+ // Reset eyes to open
81
+ setMorphTargetInfluences((prevInfluences: any) => ({
82
+ ...prevInfluences,
83
+ eyesClosed: 0
84
+ }));
37
85
  }
38
-
39
- const initialBlinkDelay = setTimeout(setNextBlink, 3000);
40
-
86
+
87
+ // Cleanup
41
88
  return () => {
42
- clearTimeout(initialBlinkDelay);
89
+ if (blinkTimeoutRef.current) {
90
+ clearTimeout(blinkTimeoutRef.current);
91
+ }
43
92
  };
44
- }, [nodes, enabled, setNextBlink]);
45
-
46
- useFrame((_, delta) => {
47
- if (!enabled) return;
48
-
49
- const { blinkTime, headMesh, morphIndex } = blinkStateRef.current;
93
+ }, [enabled, scheduleNextBlink, setMorphTargetInfluences]);
50
94
 
51
- if (blinkTime < BLINK_DURATION && headMesh?.morphTargetInfluences) {
52
- const value = Math.abs(Math.sin((blinkTime * Math.PI) / 2));
53
- blinkStateRef.current.blinkTime += delta * 10;
54
- headMesh.morphTargetInfluences[morphIndex] = value;
55
- } else if (headMesh?.morphTargetInfluences) {
56
- headMesh.morphTargetInfluences[morphIndex] = 0;
57
- }
58
- });
59
- }
95
+ return {
96
+ isBlinking: isBlinkingRef.current,
97
+ lastBlinkTime: lastBlinkTime.current,
98
+ triggerBlink: blink
99
+ };
100
+ }
@@ -0,0 +1,93 @@
1
+ import React, { useRef } from 'react';
2
+ import { SkinnedMesh } from 'three';
3
+
4
+ const VISEME_SMOOTHING = 0.2;
5
+ const VISEME_SPEED_FACTOR = 0.44;
6
+
7
+ interface Viseme {
8
+ name: string;
9
+ duration: number;
10
+ weight: number;
11
+ startTime: number;
12
+ }
13
+
14
+ interface UseMouthAnimationProps {
15
+ currentVisemes: Viseme[];
16
+ avatarMeshRef: React.RefObject<SkinnedMesh>;
17
+ }
18
+
19
+ const lerp = (start: number, end: number, alpha: number): number => {
20
+ return start * (1 - alpha) + end * alpha;
21
+ };
22
+
23
+ export function useMouthAnimation({ currentVisemes, avatarMeshRef }: UseMouthAnimationProps) {
24
+ const visemeStartTimeRef = useRef(0);
25
+ const currentVisemeWeightRef = useRef<{ [key: string]: number }>({});
26
+
27
+ // Helper function to get current viseme information
28
+ const getCurrentVisemeInfo = (elapsedTime: number) => {
29
+ let currentVisemeIndex = 0;
30
+ let accumulatedDuration = 0;
31
+
32
+ while (
33
+ currentVisemeIndex < currentVisemes.length &&
34
+ accumulatedDuration <= elapsedTime
35
+ ) {
36
+ accumulatedDuration += currentVisemes[currentVisemeIndex].duration;
37
+ currentVisemeIndex++;
38
+ }
39
+
40
+ return { currentVisemeIndex, accumulatedDuration };
41
+ };
42
+
43
+ // Helper function to apply the current viseme
44
+ const applyCurrentViseme = (index: number, elapsedTime: number, accumulatedDuration: number) => {
45
+ const currentViseme = currentVisemes[index - 1];
46
+ const visemeProgress = (elapsedTime - (accumulatedDuration - currentViseme.duration)) / currentViseme.duration;
47
+ const targetWeight = Math.sin(visemeProgress * Math.PI) * currentViseme.weight;
48
+
49
+ // Smooth the transition between visemes
50
+ if (!currentVisemeWeightRef.current[currentViseme.name]) {
51
+ currentVisemeWeightRef.current[currentViseme.name] = 0;
52
+ }
53
+ currentVisemeWeightRef.current[currentViseme.name] = lerp(
54
+ currentVisemeWeightRef.current[currentViseme.name],
55
+ targetWeight,
56
+ VISEME_SMOOTHING
57
+ );
58
+
59
+ const visemeIndex = avatarMeshRef.current?.morphTargetDictionary?.[currentViseme.name];
60
+ if (typeof visemeIndex === 'number' && avatarMeshRef.current?.morphTargetInfluences) {
61
+ avatarMeshRef.current.morphTargetInfluences[visemeIndex] = currentVisemeWeightRef.current[currentViseme.name];
62
+ }
63
+ };
64
+
65
+ // Helper function to reset viseme animation
66
+ const resetVisemeAnimation = (currentTime: number) => {
67
+ visemeStartTimeRef.current = currentTime;
68
+ currentVisemeWeightRef.current = {};
69
+ };
70
+
71
+ // Main function to handle viseme-based mouth movement
72
+ const handleMouthMovement = (elapsedTime: number) => {
73
+ if (currentVisemes.length === 0) return;
74
+
75
+ const currentTime = elapsedTime * VISEME_SPEED_FACTOR;
76
+ const visemeElapsedTime = currentTime - visemeStartTimeRef.current;
77
+
78
+ const { currentVisemeIndex, accumulatedDuration } = getCurrentVisemeInfo(visemeElapsedTime);
79
+
80
+ if (currentVisemeIndex > 0) {
81
+ applyCurrentViseme(currentVisemeIndex, visemeElapsedTime, accumulatedDuration);
82
+ }
83
+
84
+ // Reset viseme animation if we've reached the end
85
+ if (visemeElapsedTime > accumulatedDuration) {
86
+ resetVisemeAnimation(currentTime);
87
+ }
88
+ };
89
+
90
+ return {
91
+ handleMouthMovement,
92
+ };
93
+ }
@@ -17,7 +17,7 @@ export default function useSmile(smiling: boolean | undefined, nodes: Nodes) {
17
17
  });
18
18
 
19
19
  useEffect(() => {
20
- const headMesh = (nodes.Wolf3D_Head || nodes.Wolf3D_Avatar || nodes.Wolf3D_Avatar001) as SkinnedMesh;
20
+ const headMesh = (nodes.Wolf3D_Head || nodes.Wolf3D_Avatar020 || nodes.Wolf3D_Avatar001) as SkinnedMesh;
21
21
  smileStateRef.current.headMesh = headMesh;
22
22
 
23
23
  if (headMesh?.morphTargetDictionary && headMesh?.morphTargetInfluences) {
@@ -1,4 +1,4 @@
1
- import React, { useLayoutEffect, useRef, useState } from 'react';
1
+ import React, { useLayoutEffect, useState } from 'react';
2
2
  import cx from 'classnames';
3
3
  import {
4
4
  ExpertReference,
@@ -18,13 +18,12 @@ import FeedbackButtons from '../FeedbackButtons/FeedbackButtons';
18
18
  import { useTranslation } from 'react-i18next';
19
19
  import { marked } from 'marked';
20
20
  import DOMPurify from 'dompurify';
21
- import { cleanUrl } from '../../helpers/utils';
22
21
  import Button from '../ui/Button';
23
22
  import QuestionHelp from '../icons/QuestionHelp';
24
23
  import Copy from '../icons/Copy';
25
24
  import Code from '../icons/Code';
26
25
  import WhyThisAnswer from '../WhyThisAnswer/WhyThisAnswer';
27
- import { stripEmojis, escapeHTML, stripMarkdown } from '../../helpers/utils';
26
+ import { cleanUrl, stripHTML, stripOutputTags } from '../../helpers/utils';
28
27
 
29
28
  import markedLinkifyIt from 'marked-linkify-it';
30
29
  import markedKatex from 'marked-katex-extension';
@@ -149,7 +148,7 @@ const ChatBubble: React.FC<Props> = ({
149
148
 
150
149
  const plainText = message.fromUser
151
150
  ? text
152
- : escapeHTML(stripMarkdown(stripEmojis(text)));
151
+ : stripHTML(stripOutputTags(renderedText));
153
152
 
154
153
  useLayoutEffect(() => {
155
154
  if (typeof window !== 'undefined' && !message.fromUser) {
@@ -22,7 +22,6 @@ const initProviderStatus = (
22
22
  statusPage: string;
23
23
  } => {
24
24
  switch (provider) {
25
- case 'DEFAULT':
26
25
  case 'OpenAI':
27
26
  return {
28
27
  getStatus: async () => {
@@ -37,6 +36,34 @@ const initProviderStatus = (
37
36
  },
38
37
  statusPage: 'https://status.openai.com/',
39
38
  };
39
+ case 'Mistral':
40
+ return {
41
+ getStatus: async () => {
42
+ const res = await fetch(
43
+ 'https://status.mistral-data.com/api/v2/summary.json'
44
+ );
45
+ const data = await res.json();
46
+ const status = data.components.find(
47
+ (component: { name: string }) => component.name === 'API'
48
+ )?.status as Status;
49
+ return status ?? 'operational';
50
+ },
51
+ statusPage: 'https://status.mistral-data.com/',
52
+ };
53
+ case 'Anthropic':
54
+ return {
55
+ getStatus: async () => {
56
+ const res = await fetch(
57
+ 'https://status.anthropic.com/api/v2/summary.json'
58
+ );
59
+ const data = await res.json();
60
+ const status = data.components.find(
61
+ (component: { name: string }) => component.name === 'API'
62
+ )?.status as Status;
63
+ return status ?? 'operational';
64
+ },
65
+ statusPage: 'https://status.anthropic.com/',
66
+ };
40
67
  default:
41
68
  return {
42
69
  getStatus: async () => 'operational',
@@ -45,7 +72,10 @@ const initProviderStatus = (
45
72
  }
46
73
  };
47
74
 
48
- const CompletionProviderStatus = ({ forceStatus, provider }: Props) => {
75
+ const CompletionProviderStatus = ({
76
+ forceStatus,
77
+ provider = 'OpenAI',
78
+ }: Props) => {
49
79
  const { t } = useTranslation();
50
80
  const [status, setStatus] = useState<Status>(forceStatus ?? 'operational');
51
81
 
@@ -58,7 +88,7 @@ const CompletionProviderStatus = ({ forceStatus, provider }: Props) => {
58
88
  .getStatus()
59
89
  .then(status => setStatus(status))
60
90
  .catch(console.log);
61
- }, [forceStatus, provider]);
91
+ }, [forceStatus, providerStatus]);
62
92
 
63
93
  return status !== 'operational' ? (
64
94
  <Tooltip
@@ -12,6 +12,15 @@ exports[`renders CompletionProviderStatus errored unchanged 1`] = `
12
12
  <p>
13
13
  completionProviderDown
14
14
  </p>
15
+ <p>
16
+ <a
17
+ href="https://status.openai.com/"
18
+ rel="noopener noreferrer"
19
+ target="_blank"
20
+ >
21
+ completionProviderCheckStatusPage
22
+ </a>
23
+ </p>
15
24
  </div>
16
25
  </div>
17
26
  <div
@@ -89,6 +98,15 @@ exports[`renders CompletionProviderStatus unchanged 1`] = `
89
98
  <p>
90
99
  completionProviderDown
91
100
  </p>
101
+ <p>
102
+ <a
103
+ href="https://status.openai.com/"
104
+ rel="noopener noreferrer"
105
+ target="_blank"
106
+ >
107
+ completionProviderCheckStatusPage
108
+ </a>
109
+ </p>
92
110
  </div>
93
111
  </div>
94
112
  <div