Mark Duppenthaler commited on
Commit
34021fc
1 Parent(s): 7505741

Port over VR performance fixes and cleanup

Browse files
streaming-react-app/src/StreamingInterface.tsx CHANGED
@@ -64,19 +64,19 @@ const AUDIO_STREAM_DEFAULTS: {
64
  [key in SupportedInputSource]: BrowserAudioStreamConfig;
65
  } = {
66
  userMedia: {
67
- noiseSuppression: true,
68
  echoCancellation: false,
 
69
  },
70
  displayMedia: {
71
- noiseSuppression: false,
72
  echoCancellation: false,
 
73
  },
74
  };
75
 
76
  async function requestUserMediaAudioStream(
77
  config: BrowserAudioStreamConfig = {
78
- noiseSuppression: true,
79
  echoCancellation: false,
 
80
  },
81
  ) {
82
  const stream = await navigator.mediaDevices.getUserMedia({
@@ -91,8 +91,8 @@ async function requestUserMediaAudioStream(
91
 
92
  async function requestDisplayMediaAudioStream(
93
  config: BrowserAudioStreamConfig = {
94
- noiseSuppression: false,
95
  echoCancellation: false,
 
96
  },
97
  ) {
98
  const stream = await navigator.mediaDevices.getDisplayMedia({
@@ -130,7 +130,9 @@ export const TYPING_ANIMATION_DELAY_MS = 6;
130
  export default function StreamingInterface() {
131
  const urlParams = getURLParams();
132
  const debugParam = urlParams.debug;
133
- const animateTextDisplay = urlParams.animateTextDisplay;
 
 
134
 
135
  const socketObject = useSocket();
136
  const {socket, clientID} = socketObject;
@@ -165,6 +167,9 @@ export default function StreamingInterface() {
165
  const [enableNoiseSuppression, setEnableNoiseSuppression] = useState<
166
  boolean | null
167
  >(null);
 
 
 
168
 
169
  // Dynamic Params:
170
  const [targetLang, setTargetLang] = useState<string | null>(null);
@@ -358,14 +363,18 @@ export default function StreamingInterface() {
358
  noiseSuppression:
359
  enableNoiseSuppression ??
360
  AUDIO_STREAM_DEFAULTS['userMedia'].noiseSuppression,
361
- echoCancellation: false,
 
 
362
  });
363
  } else if (inputSource === 'displayMedia') {
364
  stream = await requestDisplayMediaAudioStream({
365
  noiseSuppression:
366
  enableNoiseSuppression ??
367
  AUDIO_STREAM_DEFAULTS['displayMedia'].noiseSuppression,
368
- echoCancellation: false,
 
 
369
  });
370
  } else {
371
  throw new Error(`Unsupported input source requested: ${inputSource}`);
@@ -733,6 +742,10 @@ export default function StreamingInterface() {
733
  startStreaming={startStreaming}
734
  stopStreaming={stopStreaming}
735
  debugParam={debugParam}
 
 
 
 
736
  />
737
  );
738
 
@@ -980,6 +993,23 @@ export default function StreamingInterface() {
980
  }
981
  label="Noise Suppression (Browser)"
982
  />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
983
  <FormControlLabel
984
  control={
985
  <Checkbox
 
64
  [key in SupportedInputSource]: BrowserAudioStreamConfig;
65
  } = {
66
  userMedia: {
 
67
  echoCancellation: false,
68
+ noiseSuppression: true,
69
  },
70
  displayMedia: {
 
71
  echoCancellation: false,
72
+ noiseSuppression: false,
73
  },
74
  };
75
 
76
  async function requestUserMediaAudioStream(
77
  config: BrowserAudioStreamConfig = {
 
78
  echoCancellation: false,
79
+ noiseSuppression: true,
80
  },
81
  ) {
82
  const stream = await navigator.mediaDevices.getUserMedia({
 
91
 
92
  async function requestDisplayMediaAudioStream(
93
  config: BrowserAudioStreamConfig = {
 
94
  echoCancellation: false,
95
+ noiseSuppression: false,
96
  },
97
  ) {
98
  const stream = await navigator.mediaDevices.getDisplayMedia({
 
130
  export default function StreamingInterface() {
131
  const urlParams = getURLParams();
132
  const debugParam = urlParams.debug;
133
+ const [animateTextDisplay, setAnimateTextDisplay] = useState<boolean>(
134
+ urlParams.animateTextDisplay,
135
+ );
136
 
137
  const socketObject = useSocket();
138
  const {socket, clientID} = socketObject;
 
167
  const [enableNoiseSuppression, setEnableNoiseSuppression] = useState<
168
  boolean | null
169
  >(null);
170
+ const [enableEchoCancellation, setEnableEchoCancellation] = useState<
171
+ boolean | null
172
+ >(null);
173
 
174
  // Dynamic Params:
175
  const [targetLang, setTargetLang] = useState<string | null>(null);
 
363
  noiseSuppression:
364
  enableNoiseSuppression ??
365
  AUDIO_STREAM_DEFAULTS['userMedia'].noiseSuppression,
366
+ echoCancellation:
367
+ enableEchoCancellation ??
368
+ AUDIO_STREAM_DEFAULTS['userMedia'].echoCancellation,
369
  });
370
  } else if (inputSource === 'displayMedia') {
371
  stream = await requestDisplayMediaAudioStream({
372
  noiseSuppression:
373
  enableNoiseSuppression ??
374
  AUDIO_STREAM_DEFAULTS['displayMedia'].noiseSuppression,
375
+ echoCancellation:
376
+ enableEchoCancellation ??
377
+ AUDIO_STREAM_DEFAULTS['displayMedia'].echoCancellation,
378
  });
379
  } else {
380
  throw new Error(`Unsupported input source requested: ${inputSource}`);
 
742
  startStreaming={startStreaming}
743
  stopStreaming={stopStreaming}
744
  debugParam={debugParam}
745
+ onARHidden={() => {
746
+ setAnimateTextDisplay(urlParams.animateTextDisplay);
747
+ }}
748
+ onARVisible={() => setAnimateTextDisplay(false)}
749
  />
750
  );
751
 
 
993
  }
994
  label="Noise Suppression (Browser)"
995
  />
996
+ <FormControlLabel
997
+ control={
998
+ <Checkbox
999
+ checked={
1000
+ enableEchoCancellation ??
1001
+ AUDIO_STREAM_DEFAULTS[inputSource]
1002
+ .echoCancellation
1003
+ }
1004
+ onChange={(
1005
+ event: React.ChangeEvent<HTMLInputElement>,
1006
+ ) =>
1007
+ setEnableEchoCancellation(event.target.checked)
1008
+ }
1009
+ />
1010
+ }
1011
+ label="Echo Cancellation (Browser)"
1012
+ />
1013
  <FormControlLabel
1014
  control={
1015
  <Checkbox
streaming-react-app/src/URLParams.ts CHANGED
@@ -1,5 +1,5 @@
1
- import {getBooleanParamFlag, getStringParamFlag} from './getParamFlag';
2
- import {URLParamsObject} from './types/URLParamsTypes';
3
 
4
  /**
5
  * These are the URL parameters you can provide to the app to change its behavior.
 
1
+ import { getBooleanParamFlag, getStringParamFlag } from './getParamFlag';
2
+ import { URLParamsObject } from './types/URLParamsTypes';
3
 
4
  /**
5
  * These are the URL parameters you can provide to the app to change its behavior.
streaming-react-app/src/react-xr/ARButton.tsx ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as THREE from 'three';
2
+ import {Button} from '@mui/material';
3
+ import {useCallback, useEffect, useState} from 'react';
4
+ import {BufferedSpeechPlayer} from '../createBufferedSpeechPlayer';
5
+
6
+ type Props = {
7
+ bufferedSpeechPlayer: BufferedSpeechPlayer;
8
+ renderer: THREE.WebGLRenderer | null;
9
+ onARVisible?: () => void;
10
+ onARHidden?: () => void;
11
+ };
12
+
13
+ export default function ARButton({
14
+ bufferedSpeechPlayer,
15
+ renderer,
16
+ onARVisible,
17
+ onARHidden,
18
+ }: Props) {
19
+ const [session, setSession] = useState<XRSession | null>(null);
20
+ const [supported, setSupported] = useState<boolean>(true);
21
+
22
+ useEffect(() => {
23
+ if (!navigator.xr) {
24
+ setSupported(false);
25
+ return;
26
+ }
27
+ navigator.xr.isSessionSupported('immersive-ar').then((supported) => {
28
+ setSupported(supported);
29
+ });
30
+ }, []);
31
+
32
+ const resetBuffers = useCallback(
33
+ (event: XRSessionEvent) => {
34
+ const session = event.target;
35
+ if (!(session instanceof XRSession)) {
36
+ return;
37
+ }
38
+ switch (session.visibilityState) {
39
+ case 'visible':
40
+ console.log('Restarting speech player, device is visible');
41
+ bufferedSpeechPlayer.stop();
42
+ bufferedSpeechPlayer.start();
43
+ onARVisible?.();
44
+ break;
45
+ case 'hidden':
46
+ console.log('Stopping speech player, device is hidden');
47
+ bufferedSpeechPlayer.stop();
48
+ bufferedSpeechPlayer.start();
49
+ onARHidden?.();
50
+ break;
51
+ }
52
+ },
53
+ [bufferedSpeechPlayer],
54
+ );
55
+
56
+ async function onSessionStarted(session: XRSession) {
57
+ setSession(session);
58
+
59
+ session.onvisibilitychange = resetBuffers;
60
+ session.onend = onSessionEnded;
61
+
62
+ await renderer.xr.setSession(session);
63
+ }
64
+
65
+ function onSessionEnded() {
66
+ setSession(null);
67
+ }
68
+
69
+ const onClick = () => {
70
+ if (session === null) {
71
+ navigator.xr!.requestSession('immersive-ar').then(onSessionStarted);
72
+ } else {
73
+ session.end();
74
+ }
75
+ };
76
+ return (
77
+ <Button
78
+ variant="contained"
79
+ onClick={onClick}
80
+ disabled={!supported || renderer == null}
81
+ sx={{mt: 1}}>
82
+ {supported
83
+ ? renderer != null
84
+ ? 'Enter AR'
85
+ : 'Initializing AR...'
86
+ : 'AR Not Supported'}
87
+ </Button>
88
+ );
89
+ }
streaming-react-app/src/react-xr/XRDialog.css CHANGED
@@ -11,3 +11,9 @@
11
  .xr-dialog-text-center {
12
  text-align: center;
13
  }
 
 
 
 
 
 
 
11
  .xr-dialog-text-center {
12
  text-align: center;
13
  }
14
+
15
+ .xr-dialog-canvas-container {
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: center;
19
+ }
streaming-react-app/src/react-xr/XRDialog.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  import {
2
  Button,
3
  Dialog,
@@ -7,43 +8,99 @@ import {
7
  Typography,
8
  } from '@mui/material';
9
  import CloseIcon from '@mui/icons-material/Close';
10
- import XRConfig, {XRConfigProps} from './XRConfig';
11
- import {useState} from 'react';
12
  import './XRDialog.css';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  export default function XRDialog(props: XRConfigProps) {
15
  const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
 
16
  return (
17
  <>
18
  <Button variant="contained" onClick={() => setIsDialogOpen(true)}>
19
  Enter AR Experience
20
  </Button>
21
- <Dialog onClose={() => setIsDialogOpen(false)} open={isDialogOpen}>
22
- <DialogTitle sx={{m: 0, p: 2}} className="xr-dialog-text-center">
23
- FAIR Seamless Streaming Demo
24
- </DialogTitle>
25
- <IconButton
26
- aria-label="close"
27
- onClick={() => setIsDialogOpen(false)}
28
- sx={{
29
- position: 'absolute',
30
- right: 8,
31
- top: 8,
32
- color: (theme) => theme.palette.grey[500],
33
- }}>
34
- <CloseIcon />
35
- </IconButton>
36
- <DialogContent
37
- dividers
38
- className="xr-dialog-container xr-dialog-text-center">
39
- <Typography gutterBottom>
40
- Welcome to the Seamless team streaming demo experience! In this demo
41
- you will experience AI powered text and audio translation in real
42
- time.
43
- </Typography>
44
- <XRConfig {...props} />
45
- </DialogContent>
46
- </Dialog>
47
  </>
48
  );
49
  }
 
1
+ import * as THREE from 'three';
2
  import {
3
  Button,
4
  Dialog,
 
8
  Typography,
9
  } from '@mui/material';
10
  import CloseIcon from '@mui/icons-material/Close';
11
+ import {useEffect, useRef, useState} from 'react';
 
12
  import './XRDialog.css';
13
+ import {getRenderer, init, updatetranslationText} from './XRRendering';
14
+ import ARButton from './ARButton';
15
+ import {getURLParams} from '../URLParams';
16
+ import { BufferedSpeechPlayer } from '../createBufferedSpeechPlayer';
17
+ import { TranslationSentences } from '../types/StreamingTypes';
18
+ import { RoomState } from '../types/RoomState';
19
+
20
+ type XRConfigProps = {
21
+ animateTextDisplay: boolean;
22
+ bufferedSpeechPlayer: BufferedSpeechPlayer;
23
+ translationSentences: TranslationSentences;
24
+ roomState: RoomState | null;
25
+ roomID: string | null;
26
+ startStreaming: () => Promise<void>;
27
+ stopStreaming: () => Promise<void>;
28
+ debugParam: boolean | null;
29
+ onARVisible?: () => void;
30
+ onARHidden?: () => void;
31
+ };
32
+
33
+ function XRContent(props: XRConfigProps) {
34
+ const debugParam = getURLParams().debug;
35
+ const {translationSentences} = props;
36
+ useEffect(() => {
37
+ updatetranslationText(translationSentences);
38
+ }, [translationSentences]);
39
+
40
+ const [renderer, setRenderer] = useState<THREE.WebGLRenderer | null>(null);
41
+ const canvasRef = useRef<HTMLDivElement | null>(null);
42
+ useEffect(() => {
43
+ if (canvasRef.current != null || debugParam === false) {
44
+ const existingRenderer = getRenderer();
45
+ if (existingRenderer) {
46
+ setRenderer(existingRenderer);
47
+ } else {
48
+ const newRenderer = init(
49
+ 400,
50
+ 300,
51
+ debugParam ? canvasRef.current : null,
52
+ );
53
+ setRenderer(newRenderer);
54
+ }
55
+ }
56
+ }, [canvasRef.current]);
57
+
58
+ return (
59
+ <DialogContent
60
+ dividers
61
+ className="xr-dialog-container xr-dialog-text-center">
62
+ <Typography gutterBottom>
63
+ Welcome to the Seamless team streaming demo experience! In this demo you
64
+ will experience AI powered text and audio translation in real time.
65
+ </Typography>
66
+ <div ref={canvasRef} className="xr-dialog-canvas-container" />
67
+ <ARButton
68
+ bufferedSpeechPlayer={props.bufferedSpeechPlayer}
69
+ renderer={renderer}
70
+ onARHidden={props.onARHidden}
71
+ onARVisible={props.onARVisible}
72
+ />
73
+ </DialogContent>
74
+ );
75
+ }
76
 
77
  export default function XRDialog(props: XRConfigProps) {
78
  const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
79
+
80
  return (
81
  <>
82
  <Button variant="contained" onClick={() => setIsDialogOpen(true)}>
83
  Enter AR Experience
84
  </Button>
85
+ {isDialogOpen && (
86
+ <Dialog onClose={() => setIsDialogOpen(false)} open={true}>
87
+ <DialogTitle sx={{m: 0, p: 2}} className="xr-dialog-text-center">
88
+ FAIR Seamless Streaming Demo
89
+ </DialogTitle>
90
+ <IconButton
91
+ aria-label="close"
92
+ onClick={() => setIsDialogOpen(false)}
93
+ sx={{
94
+ position: 'absolute',
95
+ right: 8,
96
+ top: 8,
97
+ color: (theme) => theme.palette.grey[500],
98
+ }}>
99
+ <CloseIcon />
100
+ </IconButton>
101
+ <XRContent {...props} />
102
+ </Dialog>
103
+ )}
 
 
 
 
 
 
 
104
  </>
105
  );
106
  }
streaming-react-app/src/react-xr/XRRendering.ts ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as THREE from 'three';
2
+ import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js';
3
+
4
+ import ThreeMeshUI, {Block, Text} from 'three-mesh-ui';
5
+
6
+ import FontJSON from '../assets/RobotoMono-Regular-msdf.json?url';
7
+ import FontImage from '../assets/RobotoMono-Regular.png';
8
+ import {TranslationSentences} from '../types/StreamingTypes';
9
+ import supportedCharSet from './supportedCharSet';
10
+
11
+ // Augment three-mesh-ui types which aren't implemented
12
+ declare module 'three-mesh-ui' {
13
+ interface Block {
14
+ add(any: any);
15
+ set(props: BlockOptions);
16
+ position: {
17
+ x: number;
18
+ y: number;
19
+ z: number;
20
+ set: (x: number, y: number, z: number) => void;
21
+ };
22
+ }
23
+ interface Text {
24
+ set(props: {content: string});
25
+ }
26
+ }
27
+
28
+ // Various configuration parameters
29
+ const INITIAL_PROMPT = 'Listening...\n';
30
+ const NUM_LINES = 3;
31
+ const CHARS_PER_LINE = 37;
32
+ const CHARS_PER_SECOND = 15;
33
+
34
+ const MAX_WIDTH = 0.89;
35
+ const CHAR_WIDTH = 0.0233;
36
+ const Y_COORD_START = -0.38;
37
+ const Z_COORD = -1.3;
38
+ const LINE_HEIGHT = 0.062;
39
+ const BLOCK_SPACING = 0.02;
40
+ const FONT_SIZE = 0.038;
41
+
42
+ // Speed of scrolling of text lines
43
+ const SCROLL_Y_DELTA = 0.01;
44
+
45
+ // Overlay an extra block for padding due to inflexibilities of native padding
46
+ const OFFSET = 0.01;
47
+ const OFFSET_WIDTH = OFFSET * 3;
48
+
49
+ // The tick interval
50
+ const CURSOR_BLINK_INTERVAL_MS = 500;
51
+
52
+ type TranscriptState = {
53
+ translationText: string;
54
+ textBlocksProps: TextBlockProps[];
55
+ lastTranslationStringIndex: number;
56
+ lastTranslationLineStartIndex: number;
57
+ transcriptLines: string[];
58
+ lastUpdateTime: number;
59
+ };
60
+
61
+ type TextBlockProps = {
62
+ content: string;
63
+ // The end position when animating
64
+ targetY: number;
65
+ // Current scroll position that caps at targetY
66
+ currentY: number;
67
+ textOpacity: number;
68
+ backgroundOpacity: number;
69
+ index: number;
70
+ isBottomLine: boolean;
71
+ };
72
+
73
+ function initialTextBlockProps(count: number): TextBlockProps[] {
74
+ return Array.from({length: count}).map(() => {
75
+ // Push in non display blocks because mesh UI crashes if elements are add / removed from screen.
76
+
77
+ return {
78
+ // key: textBlocksProps.length,
79
+ targetY: Y_COORD_START,
80
+ currentY: Y_COORD_START,
81
+ index: 0,
82
+ textOpacity: 0,
83
+ backgroundOpacity: 0,
84
+ width: MAX_WIDTH,
85
+ height: LINE_HEIGHT,
86
+ content: '',
87
+ isBottomLine: true,
88
+ };
89
+ });
90
+ }
91
+
92
+ function initialState(): TranscriptState {
93
+ return {
94
+ translationText: '',
95
+ textBlocksProps: initialTextBlockProps(NUM_LINES),
96
+ lastTranslationStringIndex: 0,
97
+ lastTranslationLineStartIndex: 0,
98
+ transcriptLines: [],
99
+ lastUpdateTime: new Date().getTime(),
100
+ };
101
+ }
102
+
103
+ let transcriptState: TranscriptState = initialState();
104
+
105
+ let scene: THREE.Scene | null;
106
+ let camera: THREE.PerspectiveCamera | null;
107
+ let renderer: THREE.WebGLRenderer | null;
108
+ let controls: THREE.OrbitControls | null;
109
+
110
+ let cursorBlinkOn: boolean = false;
111
+
112
+ setInterval(() => {
113
+ cursorBlinkOn = !cursorBlinkOn;
114
+ }, CURSOR_BLINK_INTERVAL_MS);
115
+
116
+ type TextBlock = {
117
+ textBlockOuterContainer: Block;
118
+ textBlockInnerContainer: Block;
119
+ text: Text;
120
+ };
121
+ const textBlocks: TextBlock[] = [];
122
+
123
+ export function getRenderer(): THREE.WebGLRenderer | null {
124
+ return renderer;
125
+ }
126
+
127
+ export function init(
128
+ width: number,
129
+ height: number,
130
+ parentElement: HTMLDivElement | null,
131
+ ): THREE.WebGLRenderer {
132
+ scene = new THREE.Scene();
133
+ scene.background = new THREE.Color(0x505050);
134
+
135
+ camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
136
+ camera.position.z = 1;
137
+
138
+ renderer = new THREE.WebGLRenderer({
139
+ antialias: true,
140
+ });
141
+ renderer.setPixelRatio(window.devicePixelRatio);
142
+ renderer.setSize(width, height);
143
+ renderer.xr.enabled = true;
144
+
145
+ renderer.xr.setReferenceSpaceType('local');
146
+
147
+ parentElement?.appendChild(renderer.domElement);
148
+
149
+ controls = new OrbitControls(camera, renderer.domElement);
150
+ controls.update();
151
+
152
+ scene.add(camera);
153
+
154
+ textBlocks.push(
155
+ ...initialTextBlockProps(NUM_LINES).map((props) => makeTextBlock(props)),
156
+ );
157
+
158
+ renderer.setAnimationLoop(loop);
159
+ return renderer;
160
+ }
161
+
162
+ export function updatetranslationText(
163
+ translationSentences: TranslationSentences,
164
+ ): void {
165
+ const newText = INITIAL_PROMPT + translationSentences.join('\n');
166
+ if (transcriptState.translationText === newText) {
167
+ return;
168
+ }
169
+ transcriptState.translationText = newText;
170
+ }
171
+
172
+ export function resetState(): void {
173
+ transcriptState = initialState();
174
+ }
175
+
176
+ function makeTextBlock({
177
+ content,
178
+ backgroundOpacity,
179
+ }: TextBlockProps): TextBlock {
180
+ const width = MAX_WIDTH;
181
+ const height = LINE_HEIGHT;
182
+
183
+ const fontProps = {
184
+ fontSize: FONT_SIZE,
185
+ textAlign: 'left',
186
+ // TODO: support more language charsets
187
+ // This renders using MSDF format supported in WebGL. Renderable characters are defined in the "charset" json
188
+ // Currently supports most default keyboard inputs but this would exclude many non latin charset based languages.
189
+ // You can use https://msdf-bmfont.donmccurdy.com/ for easily generating these files
190
+ fontFamily: FontJSON,
191
+ fontTexture: FontImage,
192
+ };
193
+
194
+ const textBlockOuterContainer = new Block({
195
+ backgroundOpacity,
196
+ width: width + OFFSET_WIDTH,
197
+ height: height,
198
+ borderRadius: 0,
199
+ ...fontProps,
200
+ });
201
+
202
+ const text = new Text({content});
203
+ const textBlockInnerContainer = new Block({
204
+ padding: 0,
205
+ backgroundOpacity: 0,
206
+ width,
207
+ height,
208
+ });
209
+
210
+ // Adding it to the camera makes the UI follow it.
211
+ camera.add(textBlockOuterContainer);
212
+ textBlockOuterContainer.add(textBlockInnerContainer);
213
+ textBlockInnerContainer.add(text);
214
+
215
+ return {
216
+ textBlockOuterContainer,
217
+ textBlockInnerContainer,
218
+ text,
219
+ };
220
+ }
221
+
222
+ // Updates the position and text of a text block from its props
223
+ function updateTextBlock(
224
+ id: number,
225
+ {content, targetY, currentY, backgroundOpacity, isBottomLine}: TextBlockProps,
226
+ ): void {
227
+ const {textBlockOuterContainer, textBlockInnerContainer, text} =
228
+ textBlocks[id];
229
+
230
+ const {lastTranslationStringIndex, translationText} = transcriptState;
231
+
232
+ // Add blinking cursor if we don't have any new input to render
233
+ const numChars = content.length;
234
+
235
+ if (
236
+ isBottomLine &&
237
+ cursorBlinkOn &&
238
+ lastTranslationStringIndex >= translationText.length
239
+ ) {
240
+ content = content + '|';
241
+ }
242
+
243
+ // Accounting for potential cursor for block width (the +1)
244
+ const width =
245
+ (numChars + (isBottomLine ? 1.1 : 0) + (numChars < 10 ? 1 : 0)) *
246
+ CHAR_WIDTH;
247
+ const height = LINE_HEIGHT;
248
+
249
+ // Width starts from 0 and goes 1/2 in each direction so offset x
250
+ const xPosition = width / 2 - MAX_WIDTH / 2 + OFFSET_WIDTH;
251
+ textBlockOuterContainer?.set({
252
+ backgroundOpacity,
253
+ width: width + 2 * OFFSET_WIDTH,
254
+ height: height + OFFSET / 3,
255
+ borderRadius: 0,
256
+ });
257
+
258
+ // Scroll up line toward target
259
+ const y = isBottomLine
260
+ ? targetY
261
+ : Math.min(currentY + SCROLL_Y_DELTA, targetY);
262
+ transcriptState.textBlocksProps[id].currentY = y;
263
+
264
+ textBlockOuterContainer.position.set(-OFFSET_WIDTH + xPosition, y, Z_COORD);
265
+ textBlockInnerContainer.set({
266
+ padding: 0,
267
+ backgroundOpacity: 0,
268
+ width,
269
+ height,
270
+ });
271
+ text.set({content});
272
+ }
273
+
274
+ // We split the text so it fits line by line into the UI
275
+ function chunkTranslationTextIntoLines(
276
+ translationText: string,
277
+ nextTranslationStringIndex: number,
278
+ ): string[] {
279
+ // Ideally we continue where we left off but this is complicated when we have mid-words. Recalculating for now
280
+ const newSentences = translationText
281
+ .substring(0, nextTranslationStringIndex)
282
+ .split('\n');
283
+ const transcriptLines = [''];
284
+ newSentences.forEach((newSentence, sentenceIdx) => {
285
+ const words = newSentence.split(/\s+/);
286
+ words.forEach((word) => {
287
+ const filteredWord = [...word]
288
+ .filter((c) => {
289
+ if (supportedCharSet().has(c)) {
290
+ return true;
291
+ }
292
+ console.error(
293
+ `Unsupported char ${c} - make sure this is supported in the font family msdf file`,
294
+ );
295
+ return false;
296
+ })
297
+ .join('')
298
+ // Filter out unknown symbol
299
+ .replace('<unk>', '');
300
+
301
+ const lastLineSoFar = transcriptLines[0];
302
+ const charCount = lastLineSoFar.length + filteredWord.length + 1;
303
+
304
+ if (charCount <= CHARS_PER_LINE) {
305
+ transcriptLines[0] = lastLineSoFar + ' ' + filteredWord;
306
+ } else {
307
+ transcriptLines.unshift(filteredWord);
308
+ }
309
+ });
310
+
311
+ if (sentenceIdx < newSentences.length - 1) {
312
+ transcriptLines.unshift('\n');
313
+ transcriptLines.unshift('');
314
+ }
315
+ });
316
+ return transcriptLines;
317
+ }
318
+
319
+ // The main loop,
320
+ function updateTextBlocksProps(): void {
321
+ const {translationText, lastTranslationStringIndex, lastUpdateTime} =
322
+ transcriptState;
323
+
324
+ const currentTime = new Date().getTime();
325
+ const charsToRender = Math.round(
326
+ ((currentTime - lastUpdateTime) * CHARS_PER_SECOND) / 1000,
327
+ );
328
+
329
+ if (charsToRender < 1) {
330
+ // Wait some more until we render more characters
331
+ return;
332
+ }
333
+
334
+ const nextTranslationStringIndex = Math.min(
335
+ lastTranslationStringIndex + charsToRender,
336
+ translationText.length,
337
+ );
338
+ if (nextTranslationStringIndex === lastTranslationStringIndex) {
339
+ // No new characters to render
340
+ transcriptState.lastUpdateTime = currentTime;
341
+ return;
342
+ }
343
+
344
+ // Ideally we continue where we left off but this is complicated when we have mid-words. Recalculating for now
345
+ const transcriptLines = chunkTranslationTextIntoLines(
346
+ translationText,
347
+ nextTranslationStringIndex,
348
+ );
349
+ transcriptState.transcriptLines = transcriptLines;
350
+ transcriptState.lastTranslationStringIndex = nextTranslationStringIndex;
351
+
352
+ // Compute the new props for each text block
353
+ const newTextBlocksProps: TextBlockProps[] = [];
354
+ // We start with the most recent line and increment the y coordinate for older lines.
355
+ // If it is a new sentence we increment the y coordinate a little more to leave a visible space
356
+ let y = Y_COORD_START;
357
+ transcriptLines.forEach((line, i) => {
358
+ if (newTextBlocksProps.length == NUM_LINES) {
359
+ return;
360
+ }
361
+
362
+ if (line === '\n') {
363
+ y += BLOCK_SPACING;
364
+ return;
365
+ }
366
+
367
+ const isBottomLine = newTextBlocksProps.length === 0;
368
+
369
+ const textOpacity = 1 - 0.1 * newTextBlocksProps.length;
370
+
371
+ const previousProps = transcriptState.textBlocksProps.find(
372
+ (props) => props.index === i,
373
+ );
374
+ const props = {
375
+ targetY: y + LINE_HEIGHT / 2,
376
+ currentY: isBottomLine ? y : previousProps?.currentY || y,
377
+ index: i,
378
+ textOpacity,
379
+ backgroundOpacity: 1,
380
+ content: line,
381
+ isBottomLine,
382
+ };
383
+ newTextBlocksProps.push(props);
384
+
385
+ y += LINE_HEIGHT;
386
+ });
387
+
388
+ transcriptState.textBlocksProps = newTextBlocksProps;
389
+ transcriptState.lastUpdateTime = currentTime;
390
+ }
391
+
392
+ // The main render loop, everything gets rendered here.
393
+ function loop() {
394
+ updateTextBlocksProps();
395
+
396
+ transcriptState.textBlocksProps.map((props, i) => updateTextBlock(i, props));
397
+
398
+ ThreeMeshUI.update();
399
+
400
+ controls.update();
401
+ renderer.render(scene, camera);
402
+ }
streaming-react-app/src/react-xr/supportedCharSet.ts ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import robotoFontFamilyJson from '../assets/RobotoMono-Regular-msdf.json?url';
2
+
3
+ async function fetchSupportedCharSet(): Promise<Set<string>> {
4
+ try {
5
+ const response = await fetch(robotoFontFamilyJson);
6
+ const fontFamily = await response.json();
7
+
8
+ return new Set(fontFamily.info.charset);
9
+ } catch (e) {
10
+ console.error('Failed to fetch supported XR charset', e);
11
+ return new Set();
12
+ }
13
+ }
14
+
15
+ let charSet = new Set<string>();
16
+ fetchSupportedCharSet().then((result) => (charSet = result));
17
+
18
+ export default function supportedCharSet(): Set<string> {
19
+ return charSet;
20
+ }
streaming-react-app/src/types/StreamingTypes.ts CHANGED
@@ -49,10 +49,10 @@ export const SUPPORTED_OUTPUT_MODES: Array<{
49
  value: (typeof SUPPORTED_OUTPUT_MODE_VALUES)[number];
50
  label: string;
51
  }> = [
52
- {value: 's2s&t', label: 'Text & Speech'},
53
- {value: 's2t', label: 'Text'},
54
- {value: 's2s', label: 'Speech'},
55
- ];
56
 
57
  export const SUPPORTED_INPUT_SOURCE_VALUES = [
58
  'userMedia',
@@ -66,9 +66,9 @@ export const SUPPORTED_INPUT_SOURCES: Array<{
66
  value: SupportedInputSource;
67
  label: string;
68
  }> = [
69
- {value: 'userMedia', label: 'Microphone'},
70
- {value: 'displayMedia', label: 'Browser Tab'},
71
- ];
72
 
73
  export type StartStreamEventConfig = {
74
  event: 'config';
@@ -100,7 +100,7 @@ export type ServerLockObject = {
100
  export type ServerState = ServerStateItem & {
101
  agentsCapabilities: Array<AgentCapabilities>;
102
  statusByRoom: {
103
- [key: string]: {activeConnections: number; activeTranscoders: number};
104
  };
105
  totalActiveConnections: number;
106
  totalActiveTranscoders: number;
 
49
  value: (typeof SUPPORTED_OUTPUT_MODE_VALUES)[number];
50
  label: string;
51
  }> = [
52
+ { value: 's2s&t', label: 'Text & Speech' },
53
+ { value: 's2t', label: 'Text' },
54
+ { value: 's2s', label: 'Speech' },
55
+ ];
56
 
57
  export const SUPPORTED_INPUT_SOURCE_VALUES = [
58
  'userMedia',
 
66
  value: SupportedInputSource;
67
  label: string;
68
  }> = [
69
+ { value: 'userMedia', label: 'Microphone' },
70
+ { value: 'displayMedia', label: 'Browser Tab' },
71
+ ];
72
 
73
  export type StartStreamEventConfig = {
74
  event: 'config';
 
100
  export type ServerState = ServerStateItem & {
101
  agentsCapabilities: Array<AgentCapabilities>;
102
  statusByRoom: {
103
+ [key: string]: { activeConnections: number; activeTranscoders: number };
104
  };
105
  totalActiveConnections: number;
106
  totalActiveTranscoders: number;
streaming-react-app/src/types/URLParamsTypes.ts CHANGED
@@ -7,10 +7,10 @@ export type URLParamsObject = {
7
  serverURL: string | null;
8
  skipARIntro: boolean;
9
  ARTranscriptionType:
10
- | 'single_block'
11
- | 'lines'
12
- | 'lines_with_background'
13
- | string;
14
  };
15
 
16
  export type URLParamNames = keyof URLParamsObject;
 
7
  serverURL: string | null;
8
  skipARIntro: boolean;
9
  ARTranscriptionType:
10
+ | 'single_block'
11
+ | 'lines'
12
+ | 'lines_with_background'
13
+ | string;
14
  };
15
 
16
  export type URLParamNames = keyof URLParamsObject;
streaming-react-app/yarn.lock CHANGED
@@ -61,7 +61,7 @@
61
  resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz"
62
  integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==
63
 
64
- "@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.22.9":
65
  version "7.22.10"
66
  resolved "https://registry.npmjs.org/@babel/core/-/core-7.22.10.tgz"
67
  integrity sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==
@@ -298,7 +298,7 @@
298
  resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz"
299
  integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==
300
 
301
- "@emotion/react@^11.0.0-rc.0", "@emotion/react@^11.4.1", "@emotion/react@^11.5.0", "@emotion/react@11.11.1":
302
  version "11.11.1"
303
  resolved "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz"
304
  integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==
@@ -328,7 +328,7 @@
328
  resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz"
329
  integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==
330
 
331
- "@emotion/styled@^11.3.0", "@emotion/styled@11.11.0":
332
  version "11.11.0"
333
  resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz"
334
  integrity sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==
@@ -360,11 +360,116 @@
360
  resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz"
361
  integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
362
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  "@esbuild/linux-x64@0.18.20":
364
  version "0.18.20"
365
  resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz"
366
  integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==
367
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
368
  "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
369
  version "4.4.0"
370
  resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz"
@@ -479,7 +584,7 @@
479
  dependencies:
480
  "@babel/runtime" "^7.22.6"
481
 
482
- "@mui/material@^5.0.0", "@mui/material@5.14.5":
483
  version "5.14.5"
484
  resolved "https://registry.npmjs.org/@mui/material/-/material-5.14.5.tgz"
485
  integrity sha512-4qa4GMfuZH0Ai3mttk5ccXP8a3sf7aPlAJwyMrUSz6h9hPri6BPou94zeu3rENhhmKLby9S/W1y+pmficy8JKA==
@@ -554,7 +659,7 @@
554
  "@nodelib/fs.stat" "2.0.5"
555
  run-parallel "^1.1.9"
556
 
557
- "@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
558
  version "2.0.5"
559
  resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
560
  integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -647,7 +752,7 @@
647
  utility-types "^3.10.0"
648
  zustand "^3.5.13"
649
 
650
- "@react-three/fiber@^8.14.1", "@react-three/fiber@>=6.0", "@react-three/fiber@>=8.0", "@react-three/fiber@>=8.0.0":
651
  version "8.14.1"
652
  resolved "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.14.1.tgz"
653
  integrity sha512-Ky/FiCyJiyaI8bd+vckzgkHgKDSQDOcSSUVFOHCuCO9XOLb7Ebs6lof2hPpFa1HkaeE5ZIbTWIprvN0QqdPF0w==
@@ -693,7 +798,7 @@
693
  resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz"
694
  integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
695
 
696
- "@types/node@^20.5.3", "@types/node@>= 14":
697
  version "20.5.3"
698
  resolved "https://registry.npmjs.org/@types/node/-/node-20.5.3.tgz"
699
  integrity sha512-ITI7rbWczR8a/S6qjAW7DMqxqFMjjTo61qZVWJ1ubPvbIQsL5D/TvwjYEalM8Kthpe3hTzOGrF2TGbAu2uyqeA==
@@ -748,7 +853,7 @@
748
  dependencies:
749
  "@types/react" "*"
750
 
751
- "@types/react@*", "@types/react@^17.0.0 || ^18.0.0", "@types/react@^18.2.15", "@types/react@>=16.8":
752
  version "18.2.20"
753
  resolved "https://registry.npmjs.org/@types/react/-/react-18.2.20.tgz"
754
  integrity sha512-WKNtmsLWJM/3D5mG4U84cysVY31ivmyw85dE84fOCk5Hx78wezB/XEjVPWl2JTZ5FkEeaTJf+VgUAUn3PE7Isw==
@@ -767,21 +872,6 @@
767
  resolved "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz"
768
  integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==
769
 
770
- "@types/stats.js@*":
771
- version "0.17.3"
772
- resolved "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz"
773
- integrity sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==
774
-
775
- "@types/three@>=0.144.0":
776
- version "0.158.3"
777
- resolved "https://registry.npmjs.org/@types/three/-/three-0.158.3.tgz"
778
- integrity sha512-6Qs1rUvLSbkJ4hlIe6/rdwIf61j1x2UKvGJg7s8KjswYsz1C1qDTs6voVXXB8kYaI0hgklgZgbZUupfL1l9xdA==
779
- dependencies:
780
- "@types/stats.js" "*"
781
- "@types/webxr" "*"
782
- fflate "~0.6.10"
783
- meshoptimizer "~0.18.1"
784
-
785
  "@types/uuid@^9.0.2":
786
  version "9.0.2"
787
  resolved "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz"
@@ -809,7 +899,7 @@
809
  semver "^7.5.4"
810
  ts-api-utils "^1.0.1"
811
 
812
- "@typescript-eslint/parser@^6.0.0", "@typescript-eslint/parser@^6.0.0 || ^6.0.0-alpha":
813
  version "6.4.0"
814
  resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz"
815
  integrity sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg==
@@ -904,7 +994,7 @@ acorn-jsx@^5.3.2:
904
  resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
905
  integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
906
 
907
- "acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.9.0:
908
  version "8.10.0"
909
  resolved "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz"
910
  integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
@@ -942,14 +1032,7 @@ ansi-styles@^3.2.1:
942
  dependencies:
943
  color-convert "^1.9.0"
944
 
945
- ansi-styles@^4.0.0:
946
- version "4.3.0"
947
- resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
948
- integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
949
- dependencies:
950
- color-convert "^2.0.1"
951
-
952
- ansi-styles@^4.1.0:
953
  version "4.3.0"
954
  resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
955
  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
@@ -1033,7 +1116,7 @@ braces@^3.0.2:
1033
  dependencies:
1034
  fill-range "^7.0.1"
1035
 
1036
- browserslist@^4.21.9, "browserslist@>= 4.21.0":
1037
  version "4.21.10"
1038
  resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz"
1039
  integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==
@@ -1084,15 +1167,7 @@ chalk@^2.4.2:
1084
  escape-string-regexp "^1.0.5"
1085
  supports-color "^5.3.0"
1086
 
1087
- chalk@^4.0.0:
1088
- version "4.1.2"
1089
- resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
1090
- integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
1091
- dependencies:
1092
- ansi-styles "^4.1.0"
1093
- supports-color "^7.1.0"
1094
-
1095
- chalk@^4.1.2:
1096
  version "4.1.2"
1097
  resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
1098
  integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -1128,16 +1203,16 @@ color-convert@^2.0.1:
1128
  dependencies:
1129
  color-name "~1.1.4"
1130
 
1131
- color-name@~1.1.4:
1132
- version "1.1.4"
1133
- resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
1134
- integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
1135
-
1136
  color-name@1.1.3:
1137
  version "1.1.3"
1138
  resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
1139
  integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
1140
 
 
 
 
 
 
1141
  concat-map@0.0.1:
1142
  version "0.0.1"
1143
  resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
@@ -1352,7 +1427,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
1352
  resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
1353
  integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
1354
 
1355
- "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0", eslint@^8.45.0, eslint@>=7:
1356
  version "8.47.0"
1357
  resolved "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz"
1358
  integrity sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==
@@ -1471,7 +1546,7 @@ fastq@^1.6.0:
1471
  dependencies:
1472
  reusify "^1.0.4"
1473
 
1474
- fflate@^0.6.9, fflate@~0.6.10:
1475
  version "0.6.10"
1476
  resolved "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz"
1477
  integrity sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==
@@ -1528,6 +1603,11 @@ fs.realpath@^1.0.0:
1528
  resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
1529
  integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
1530
 
 
 
 
 
 
1531
  function-bind@^1.1.1:
1532
  version "1.1.1"
1533
  resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
@@ -1661,7 +1741,7 @@ hoist-non-react-statics@^3.3.1:
1661
  dependencies:
1662
  react-is "^16.7.0"
1663
 
1664
- ieee754@^1.1.4, ieee754@1.1.13:
1665
  version "1.1.13"
1666
  resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz"
1667
  integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
@@ -1692,7 +1772,7 @@ inflight@^1.0.4:
1692
  once "^1.3.0"
1693
  wrappy "1"
1694
 
1695
- inherits@^2.0.3, inherits@2:
1696
  version "2.0.4"
1697
  resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
1698
  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -1890,7 +1970,7 @@ lodash.pick@^4.4.0:
1890
  resolved "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz"
1891
  integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==
1892
 
1893
- lodash@^4.17.21, lodash@4.17.21:
1894
  version "4.17.21"
1895
  resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
1896
  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -1931,11 +2011,6 @@ meshline@^3.1.6:
1931
  resolved "https://registry.npmjs.org/meshline/-/meshline-3.1.6.tgz"
1932
  integrity sha512-8JZJOdaL5oz3PI/upG8JvP/5FfzYUOhrkJ8np/WKvXzl0/PZ2V9pqTvCIjSKv+w9ccg2xb+yyBhXAwt6ier3ug==
1933
 
1934
- meshoptimizer@~0.18.1:
1935
- version "0.18.1"
1936
- resolved "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz"
1937
- integrity sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==
1938
-
1939
  micromatch@^4.0.4:
1940
  version "4.0.5"
1941
  resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz"
@@ -2109,16 +2184,16 @@ prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1:
2109
  object-assign "^4.1.1"
2110
  react-is "^16.13.1"
2111
 
2112
- punycode@^2.1.0:
2113
- version "2.3.0"
2114
- resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz"
2115
- integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
2116
-
2117
  punycode@1.3.2:
2118
  version "1.3.2"
2119
  resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz"
2120
  integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==
2121
 
 
 
 
 
 
2122
  querystring@0.2.0:
2123
  version "0.2.0"
2124
  resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz"
@@ -2136,7 +2211,7 @@ react-composer@^5.0.3:
2136
  dependencies:
2137
  prop-types "^15.6.0"
2138
 
2139
- "react-dom@^17.0.0 || ^18.0.0", react-dom@^18.2.0, react-dom@>=16.13, react-dom@>=16.3.0, react-dom@>=16.6.0, react-dom@>=18.0:
2140
  version "18.2.0"
2141
  resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
2142
  integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
@@ -2149,12 +2224,7 @@ react-google-charts@^4.0.1:
2149
  resolved "https://registry.npmjs.org/react-google-charts/-/react-google-charts-4.0.1.tgz"
2150
  integrity sha512-V/hcMcNuBgD5w49BYTUDye+bUKaPmsU5vy/9W/Nj2xEeGn+6/AuH9IvBkbDcNBsY00cV9OeexdmgfI5RFHgsXQ==
2151
 
2152
- react-is@^16.13.1:
2153
- version "16.13.1"
2154
- resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
2155
- integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
2156
-
2157
- react-is@^16.7.0:
2158
  version "16.13.1"
2159
  resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
2160
  integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -2199,7 +2269,7 @@ react-use-measure@^2.1.1:
2199
  dependencies:
2200
  debounce "^1.2.1"
2201
 
2202
- "react@^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^17.0.0 || ^18.0.0", react@^18.0.0, react@^18.2.0, "react@>= 16.8.0", react@>=16.13, react@>=16.3.0, react@>=16.6.0, react@>=16.8, react@>=16.8.0, react@>=17.0, react@>=18.0:
2203
  version "18.2.0"
2204
  resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
2205
  integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
@@ -2268,7 +2338,7 @@ rxjs@^7.8.1:
2268
  dependencies:
2269
  tslib "^2.1.0"
2270
 
2271
- sax@>=0.6.0, sax@1.2.1:
2272
  version "1.2.1"
2273
  resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz"
2274
  integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==
@@ -2457,7 +2527,7 @@ three-stdlib@^2.21.1, three-stdlib@^2.25.1:
2457
  potpack "^1.0.1"
2458
  zstddec "^0.0.2"
2459
 
2460
- three@^0.156.1, "three@>= 0.151.0", three@>=0.125.0, three@>=0.126, three@>=0.126.1, three@>=0.128.0, three@>=0.133, three@>=0.137, three@>=0.141, three@>=0.144.0:
2461
  version "0.156.1"
2462
  resolved "https://registry.npmjs.org/three/-/three-0.156.1.tgz"
2463
  integrity sha512-kP7H0FK9d/k6t/XvQ9FO6i+QrePoDcNhwl0I02+wmUJRNSLCUIDMcfObnzQvxb37/0Uc9TDT0T1HgsRRrO6SYQ==
@@ -2519,17 +2589,7 @@ tslib@^1.11.1:
2519
  resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
2520
  integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
2521
 
2522
- tslib@^2.1.0:
2523
- version "2.6.2"
2524
- resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
2525
- integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
2526
-
2527
- tslib@^2.3.1:
2528
- version "2.6.2"
2529
- resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
2530
- integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
2531
-
2532
- tslib@^2.5.0:
2533
  version "2.6.2"
2534
  resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
2535
  integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
@@ -2546,7 +2606,7 @@ type-fest@^0.20.2:
2546
  resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz"
2547
  integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
2548
 
2549
- typescript@>=4.2.0, typescript@5.1.6:
2550
  version "5.1.6"
2551
  resolved "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz"
2552
  integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==
@@ -2600,17 +2660,17 @@ utility-types@^3.10.0:
2600
  resolved "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz"
2601
  integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==
2602
 
2603
- uuid@^9.0.0:
2604
- version "9.0.0"
2605
- resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz"
2606
- integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
2607
-
2608
  uuid@8.0.0:
2609
  version "8.0.0"
2610
  resolved "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz"
2611
  integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==
2612
 
2613
- vite@^4.2.0, vite@^4.4.5:
 
 
 
 
 
2614
  version "4.4.9"
2615
  resolved "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz"
2616
  integrity sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==
@@ -2747,12 +2807,7 @@ zstddec@^0.0.2:
2747
  resolved "https://registry.npmjs.org/zstddec/-/zstddec-0.0.2.tgz"
2748
  integrity sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==
2749
 
2750
- zustand@^3.5.13:
2751
- version "3.7.2"
2752
- resolved "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz"
2753
- integrity sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==
2754
-
2755
- zustand@^3.7.1:
2756
  version "3.7.2"
2757
  resolved "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz"
2758
  integrity sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==
 
61
  resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz"
62
  integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==
63
 
64
+ "@babel/core@^7.22.9":
65
  version "7.22.10"
66
  resolved "https://registry.npmjs.org/@babel/core/-/core-7.22.10.tgz"
67
  integrity sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==
 
298
  resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz"
299
  integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==
300
 
301
+ "@emotion/react@11.11.1":
302
  version "11.11.1"
303
  resolved "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz"
304
  integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==
 
328
  resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz"
329
  integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==
330
 
331
+ "@emotion/styled@11.11.0":
332
  version "11.11.0"
333
  resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz"
334
  integrity sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==
 
360
  resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz"
361
  integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==
362
 
363
+ "@esbuild/android-arm64@0.18.20":
364
+ version "0.18.20"
365
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622"
366
+ integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==
367
+
368
+ "@esbuild/android-arm@0.18.20":
369
+ version "0.18.20"
370
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682"
371
+ integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==
372
+
373
+ "@esbuild/android-x64@0.18.20":
374
+ version "0.18.20"
375
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2"
376
+ integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==
377
+
378
+ "@esbuild/darwin-arm64@0.18.20":
379
+ version "0.18.20"
380
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1"
381
+ integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==
382
+
383
+ "@esbuild/darwin-x64@0.18.20":
384
+ version "0.18.20"
385
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d"
386
+ integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==
387
+
388
+ "@esbuild/freebsd-arm64@0.18.20":
389
+ version "0.18.20"
390
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54"
391
+ integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==
392
+
393
+ "@esbuild/freebsd-x64@0.18.20":
394
+ version "0.18.20"
395
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e"
396
+ integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==
397
+
398
+ "@esbuild/linux-arm64@0.18.20":
399
+ version "0.18.20"
400
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0"
401
+ integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==
402
+
403
+ "@esbuild/linux-arm@0.18.20":
404
+ version "0.18.20"
405
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0"
406
+ integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==
407
+
408
+ "@esbuild/linux-ia32@0.18.20":
409
+ version "0.18.20"
410
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7"
411
+ integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==
412
+
413
+ "@esbuild/linux-loong64@0.18.20":
414
+ version "0.18.20"
415
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d"
416
+ integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==
417
+
418
+ "@esbuild/linux-mips64el@0.18.20":
419
+ version "0.18.20"
420
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231"
421
+ integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==
422
+
423
+ "@esbuild/linux-ppc64@0.18.20":
424
+ version "0.18.20"
425
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb"
426
+ integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==
427
+
428
+ "@esbuild/linux-riscv64@0.18.20":
429
+ version "0.18.20"
430
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6"
431
+ integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==
432
+
433
+ "@esbuild/linux-s390x@0.18.20":
434
+ version "0.18.20"
435
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071"
436
+ integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==
437
+
438
  "@esbuild/linux-x64@0.18.20":
439
  version "0.18.20"
440
  resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz"
441
  integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==
442
 
443
+ "@esbuild/netbsd-x64@0.18.20":
444
+ version "0.18.20"
445
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1"
446
+ integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==
447
+
448
+ "@esbuild/openbsd-x64@0.18.20":
449
+ version "0.18.20"
450
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae"
451
+ integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==
452
+
453
+ "@esbuild/sunos-x64@0.18.20":
454
+ version "0.18.20"
455
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d"
456
+ integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==
457
+
458
+ "@esbuild/win32-arm64@0.18.20":
459
+ version "0.18.20"
460
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9"
461
+ integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==
462
+
463
+ "@esbuild/win32-ia32@0.18.20":
464
+ version "0.18.20"
465
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102"
466
+ integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==
467
+
468
+ "@esbuild/win32-x64@0.18.20":
469
+ version "0.18.20"
470
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d"
471
+ integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==
472
+
473
  "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
474
  version "4.4.0"
475
  resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz"
 
584
  dependencies:
585
  "@babel/runtime" "^7.22.6"
586
 
587
+ "@mui/material@5.14.5":
588
  version "5.14.5"
589
  resolved "https://registry.npmjs.org/@mui/material/-/material-5.14.5.tgz"
590
  integrity sha512-4qa4GMfuZH0Ai3mttk5ccXP8a3sf7aPlAJwyMrUSz6h9hPri6BPou94zeu3rENhhmKLby9S/W1y+pmficy8JKA==
 
659
  "@nodelib/fs.stat" "2.0.5"
660
  run-parallel "^1.1.9"
661
 
662
+ "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
663
  version "2.0.5"
664
  resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
665
  integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
 
752
  utility-types "^3.10.0"
753
  zustand "^3.5.13"
754
 
755
+ "@react-three/fiber@^8.14.1":
756
  version "8.14.1"
757
  resolved "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.14.1.tgz"
758
  integrity sha512-Ky/FiCyJiyaI8bd+vckzgkHgKDSQDOcSSUVFOHCuCO9XOLb7Ebs6lof2hPpFa1HkaeE5ZIbTWIprvN0QqdPF0w==
 
798
  resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz"
799
  integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
800
 
801
+ "@types/node@^20.5.3":
802
  version "20.5.3"
803
  resolved "https://registry.npmjs.org/@types/node/-/node-20.5.3.tgz"
804
  integrity sha512-ITI7rbWczR8a/S6qjAW7DMqxqFMjjTo61qZVWJ1ubPvbIQsL5D/TvwjYEalM8Kthpe3hTzOGrF2TGbAu2uyqeA==
 
853
  dependencies:
854
  "@types/react" "*"
855
 
856
+ "@types/react@*", "@types/react@^18.2.15":
857
  version "18.2.20"
858
  resolved "https://registry.npmjs.org/@types/react/-/react-18.2.20.tgz"
859
  integrity sha512-WKNtmsLWJM/3D5mG4U84cysVY31ivmyw85dE84fOCk5Hx78wezB/XEjVPWl2JTZ5FkEeaTJf+VgUAUn3PE7Isw==
 
872
  resolved "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz"
873
  integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==
874
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
875
  "@types/uuid@^9.0.2":
876
  version "9.0.2"
877
  resolved "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz"
 
899
  semver "^7.5.4"
900
  ts-api-utils "^1.0.1"
901
 
902
+ "@typescript-eslint/parser@^6.0.0":
903
  version "6.4.0"
904
  resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.0.tgz"
905
  integrity sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg==
 
994
  resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
995
  integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
996
 
997
+ acorn@^8.9.0:
998
  version "8.10.0"
999
  resolved "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz"
1000
  integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
 
1032
  dependencies:
1033
  color-convert "^1.9.0"
1034
 
1035
+ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
 
 
 
 
 
 
 
1036
  version "4.3.0"
1037
  resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
1038
  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
 
1116
  dependencies:
1117
  fill-range "^7.0.1"
1118
 
1119
+ browserslist@^4.21.9:
1120
  version "4.21.10"
1121
  resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz"
1122
  integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==
 
1167
  escape-string-regexp "^1.0.5"
1168
  supports-color "^5.3.0"
1169
 
1170
+ chalk@^4.0.0, chalk@^4.1.2:
 
 
 
 
 
 
 
 
1171
  version "4.1.2"
1172
  resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
1173
  integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
 
1203
  dependencies:
1204
  color-name "~1.1.4"
1205
 
 
 
 
 
 
1206
  color-name@1.1.3:
1207
  version "1.1.3"
1208
  resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
1209
  integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
1210
 
1211
+ color-name@~1.1.4:
1212
+ version "1.1.4"
1213
+ resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
1214
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
1215
+
1216
  concat-map@0.0.1:
1217
  version "0.0.1"
1218
  resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
 
1427
  resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
1428
  integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
1429
 
1430
+ eslint@^8.45.0:
1431
  version "8.47.0"
1432
  resolved "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz"
1433
  integrity sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==
 
1546
  dependencies:
1547
  reusify "^1.0.4"
1548
 
1549
+ fflate@^0.6.9:
1550
  version "0.6.10"
1551
  resolved "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz"
1552
  integrity sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==
 
1603
  resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
1604
  integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
1605
 
1606
+ fsevents@~2.3.2:
1607
+ version "2.3.3"
1608
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
1609
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
1610
+
1611
  function-bind@^1.1.1:
1612
  version "1.1.1"
1613
  resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
 
1741
  dependencies:
1742
  react-is "^16.7.0"
1743
 
1744
+ ieee754@1.1.13, ieee754@^1.1.4:
1745
  version "1.1.13"
1746
  resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz"
1747
  integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
 
1772
  once "^1.3.0"
1773
  wrappy "1"
1774
 
1775
+ inherits@2, inherits@^2.0.3:
1776
  version "2.0.4"
1777
  resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
1778
  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
 
1970
  resolved "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz"
1971
  integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==
1972
 
1973
+ lodash@4.17.21, lodash@^4.17.21:
1974
  version "4.17.21"
1975
  resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
1976
  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
2011
  resolved "https://registry.npmjs.org/meshline/-/meshline-3.1.6.tgz"
2012
  integrity sha512-8JZJOdaL5oz3PI/upG8JvP/5FfzYUOhrkJ8np/WKvXzl0/PZ2V9pqTvCIjSKv+w9ccg2xb+yyBhXAwt6ier3ug==
2013
 
 
 
 
 
 
2014
  micromatch@^4.0.4:
2015
  version "4.0.5"
2016
  resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz"
 
2184
  object-assign "^4.1.1"
2185
  react-is "^16.13.1"
2186
 
 
 
 
 
 
2187
  punycode@1.3.2:
2188
  version "1.3.2"
2189
  resolved "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz"
2190
  integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==
2191
 
2192
+ punycode@^2.1.0:
2193
+ version "2.3.0"
2194
+ resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz"
2195
+ integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
2196
+
2197
  querystring@0.2.0:
2198
  version "0.2.0"
2199
  resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz"
 
2211
  dependencies:
2212
  prop-types "^15.6.0"
2213
 
2214
+ react-dom@^18.2.0:
2215
  version "18.2.0"
2216
  resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
2217
  integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
 
2224
  resolved "https://registry.npmjs.org/react-google-charts/-/react-google-charts-4.0.1.tgz"
2225
  integrity sha512-V/hcMcNuBgD5w49BYTUDye+bUKaPmsU5vy/9W/Nj2xEeGn+6/AuH9IvBkbDcNBsY00cV9OeexdmgfI5RFHgsXQ==
2226
 
2227
+ react-is@^16.13.1, react-is@^16.7.0:
 
 
 
 
 
2228
  version "16.13.1"
2229
  resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
2230
  integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
2269
  dependencies:
2270
  debounce "^1.2.1"
2271
 
2272
+ react@^18.2.0:
2273
  version "18.2.0"
2274
  resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
2275
  integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
 
2338
  dependencies:
2339
  tslib "^2.1.0"
2340
 
2341
+ sax@1.2.1, sax@>=0.6.0:
2342
  version "1.2.1"
2343
  resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz"
2344
  integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==
 
2527
  potpack "^1.0.1"
2528
  zstddec "^0.0.2"
2529
 
2530
+ three@^0.156.1:
2531
  version "0.156.1"
2532
  resolved "https://registry.npmjs.org/three/-/three-0.156.1.tgz"
2533
  integrity sha512-kP7H0FK9d/k6t/XvQ9FO6i+QrePoDcNhwl0I02+wmUJRNSLCUIDMcfObnzQvxb37/0Uc9TDT0T1HgsRRrO6SYQ==
 
2589
  resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
2590
  integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
2591
 
2592
+ tslib@^2.1.0, tslib@^2.3.1, tslib@^2.5.0:
 
 
 
 
 
 
 
 
 
 
2593
  version "2.6.2"
2594
  resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
2595
  integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
 
2606
  resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz"
2607
  integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
2608
 
2609
+ typescript@5.1.6:
2610
  version "5.1.6"
2611
  resolved "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz"
2612
  integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==
 
2660
  resolved "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz"
2661
  integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==
2662
 
 
 
 
 
 
2663
  uuid@8.0.0:
2664
  version "8.0.0"
2665
  resolved "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz"
2666
  integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==
2667
 
2668
+ uuid@^9.0.0:
2669
+ version "9.0.0"
2670
+ resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz"
2671
+ integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
2672
+
2673
+ vite@^4.4.5:
2674
  version "4.4.9"
2675
  resolved "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz"
2676
  integrity sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==
 
2807
  resolved "https://registry.npmjs.org/zstddec/-/zstddec-0.0.2.tgz"
2808
  integrity sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==
2809
 
2810
+ zustand@^3.5.13, zustand@^3.7.1:
 
 
 
 
 
2811
  version "3.7.2"
2812
  resolved "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz"
2813
  integrity sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==