talking-head-studio 0.2.4 → 0.2.6

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.
@@ -1,6 +1,33 @@
1
1
  import React from 'react';
2
2
  import { type StyleProp, type ViewStyle } from 'react-native';
3
3
  export type TalkingHeadMood = 'neutral' | 'happy' | 'sad' | 'angry' | 'excited' | 'thinking' | 'concerned' | 'surprised';
4
+ /**
5
+ * Standard viseme keys supported by the avatar.
6
+ * Use with sendViseme() from your TTS viseme callbacks.
7
+ */
8
+ export type TalkingHeadViseme = 'sil' | 'PP' | 'FF' | 'TH' | 'DD' | 'kk' | 'CH' | 'SS' | 'nn' | 'RR' | 'aa' | 'ee' | 'ih' | 'oh' | 'ou';
9
+ /** Rhubarb mouth shape cue (Preston Blair set: A-H, X) */
10
+ export interface TalkingHeadVisemeCue {
11
+ startMs: number;
12
+ endMs: number;
13
+ viseme: 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'X';
14
+ }
15
+ /**
16
+ * A full viseme schedule from the Rhubarb sidecar endpoint.
17
+ * Pass to scheduleVisemes() when agent_visemes arrives on the data channel.
18
+ */
19
+ export interface TalkingHeadVisemeSchedule {
20
+ /** Matches X-TTS-Request-Id / agent_visemes.requestId */
21
+ requestId?: string;
22
+ /**
23
+ * Wall-clock ms at which audio playback started.
24
+ * Anchor this to the moment you observe agent_state: speaking.
25
+ * Used to skip cues that are already in the past on late delivery.
26
+ */
27
+ startedAtMs?: number;
28
+ durationMs?: number;
29
+ cues: TalkingHeadVisemeCue[];
30
+ }
4
31
  export interface TalkingHeadAccessory {
5
32
  id: string;
6
33
  url: string;
@@ -25,6 +52,33 @@ export interface TalkingHeadProps {
25
52
  }
26
53
  export interface TalkingHeadRef {
27
54
  sendAmplitude: (amplitude: number) => void;
55
+ /**
56
+ * Drive a viseme morph target from your TTS pipeline.
57
+ * Works on iOS/Android WebViews where AudioWorklet is unavailable.
58
+ *
59
+ * @example
60
+ * // ElevenLabs websocket:
61
+ * ws.on('viseme', ({ visemeId }) => avatarRef.current?.sendViseme(ELEVENLABS_MAP[visemeId], 1.0));
62
+ * // Azure TTS:
63
+ * synthesizer.visemeReceived = (s, e) => avatarRef.current?.sendViseme(AZURE_MAP[e.visemeId], 1.0);
64
+ */
65
+ sendViseme: (viseme: TalkingHeadViseme, weight?: number) => void;
66
+ /**
67
+ * Schedule a full Rhubarb viseme payload for playback.
68
+ * Call this when agent_visemes arrives on the LiveKit data channel.
69
+ * The scheduler gates amplitude fallback while visemes are active.
70
+ *
71
+ * @example
72
+ * room.on(RoomEvent.DataReceived, (payload) => {
73
+ * const msg = JSON.parse(new TextDecoder().decode(payload));
74
+ * if (msg.type === 'agent_visemes') {
75
+ * avatarRef.current?.scheduleVisemes({ ...msg, startedAtMs: speakingStartedAt });
76
+ * }
77
+ * });
78
+ */
79
+ scheduleVisemes: (schedule: TalkingHeadVisemeSchedule) => void;
80
+ /** Cancel any running viseme schedule and return to amplitude fallback. */
81
+ clearVisemes: () => void;
28
82
  setMood: (mood: TalkingHeadMood) => void;
29
83
  setHairColor: (color: string) => void;
30
84
  setSkinColor: (color: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"TalkingHead.d.ts","sourceRoot":"","sources":["../src/TalkingHead.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,KAAK,SAAS,EAAoB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAIhF,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,OAAO,GACP,KAAK,GACL,OAAO,GACP,SAAS,GACT,UAAU,GACV,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,OAAO,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAC;IACzC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,cAAc,EAAE,CAAC,WAAW,EAAE,oBAAoB,EAAE,KAAK,IAAI,CAAC;CAC/D;AAED,eAAO,MAAM,WAAW,yFAyIvB,CAAC"}
1
+ {"version":3,"file":"TalkingHead.d.ts","sourceRoot":"","sources":["../src/TalkingHead.tsx"],"names":[],"mappings":"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"}
@@ -1,33 +1,45 @@
1
- import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, } from 'react';
1
+ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
2
2
  import { StyleSheet, View } from 'react-native';
3
3
  import { WebView } from 'react-native-webview';
4
4
  import { buildAvatarHtml } from './html';
5
5
  export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral', cameraView = 'upper', cameraDistance = -0.5, hairColor, skinColor, eyeColor, accessories, onReady, onError, style, }, ref) => {
6
6
  const webViewRef = useRef(null);
7
+ const readyRef = useRef(false);
8
+ const accessoriesRef = useRef(accessories);
9
+ // The WebView HTML is built once from stable initial values.
10
+ // avatarUrl + authToken changing causes a controlled key-based remount.
11
+ // All other prop changes (mood, colors, accessories) go via postMessage.
12
+ const [webViewKey, setWebViewKey] = useState(() => `${avatarUrl}__${authToken ?? ''}`);
13
+ const prevAvatarRef = useRef({ avatarUrl, authToken });
14
+ useEffect(() => {
15
+ const prev = prevAvatarRef.current;
16
+ if (prev.avatarUrl !== avatarUrl || prev.authToken !== authToken) {
17
+ prevAvatarRef.current = { avatarUrl, authToken };
18
+ readyRef.current = false;
19
+ setWebViewKey(`${avatarUrl}__${authToken ?? ''}`);
20
+ }
21
+ }, [avatarUrl, authToken]);
7
22
  const post = useCallback((msg) => {
8
23
  webViewRef.current?.postMessage(JSON.stringify(msg));
9
24
  }, []);
10
25
  useImperativeHandle(ref, () => ({
11
26
  sendAmplitude: (amplitude) => post({ type: 'amplitude', value: amplitude }),
27
+ sendViseme: (viseme, weight = 1.0) => post({ type: 'viseme', viseme, weight }),
28
+ scheduleVisemes: (schedule) => post({ type: 'schedule_visemes', schedule }),
29
+ clearVisemes: () => post({ type: 'clear_visemes' }),
12
30
  setMood: (nextMood) => post({ type: 'mood', value: nextMood }),
13
31
  setHairColor: (color) => post({ type: 'hair_color', value: color }),
14
32
  setSkinColor: (color) => post({ type: 'skin_color', value: color }),
15
33
  setEyeColor: (color) => post({ type: 'eye_color', value: color }),
16
34
  setAccessories: (newAccessories) => post({ type: 'set_accessories', accessories: newAccessories }),
17
35
  }), [post]);
36
+ // Sync mood via postMessage only — never causes a WebView reload
18
37
  useEffect(() => {
19
38
  if (readyRef.current)
20
39
  post({ type: 'mood', value: mood });
21
40
  }, [mood, post]);
22
- // Track whether the WebView JS is ready to receive messages
23
- const readyRef = useRef(false);
24
- // Always hold the latest accessories so the ready handler can send them
25
- const accessoriesRef = useRef(accessories);
26
41
  useEffect(() => {
27
42
  accessoriesRef.current = accessories;
28
- }, [accessories]);
29
- useEffect(() => {
30
- // Only post if the WebView is already ready; otherwise the ready handler sends them
31
43
  if (accessories && readyRef.current) {
32
44
  post({ type: 'set_accessories', accessories });
33
45
  }
@@ -44,53 +56,48 @@ export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral',
44
56
  if (eyeColor && readyRef.current)
45
57
  post({ type: 'eye_color', value: eyeColor });
46
58
  }, [eyeColor, post]);
47
- // Color props are intentionally excluded from deps — live updates go via postMessage.
48
- // Only avatarUrl, authToken, cameraView, cameraDistance cause a full WebView reload.
49
- const [initialMood] = React.useState(mood);
50
- const [initialHairColor] = React.useState(hairColor);
51
- const [initialSkinColor] = React.useState(skinColor);
52
- const [initialEyeColor] = React.useState(eyeColor);
59
+ // Capture stable initial values at first mount only
60
+ const [initialMood] = useState(mood);
61
+ const [initialHairColor] = useState(hairColor);
62
+ const [initialSkinColor] = useState(skinColor);
63
+ const [initialEyeColor] = useState(eyeColor);
64
+ // html is stable — only rebuilds when webViewKey changes (avatarUrl/authToken)
53
65
  const html = useMemo(() => buildAvatarHtml({
54
66
  avatarUrl,
55
67
  authToken,
56
68
  mood: initialMood,
57
69
  cameraView,
58
70
  cameraDistance,
59
- initialHairColor: initialHairColor,
60
- initialSkinColor: initialSkinColor,
61
- initialEyeColor: initialEyeColor,
62
- }), [
63
- avatarUrl,
64
- authToken,
65
- cameraView,
66
- cameraDistance,
67
- initialMood,
68
71
  initialHairColor,
69
72
  initialSkinColor,
70
73
  initialEyeColor,
71
- ]);
74
+ }),
75
+ // eslint-disable-next-line react-hooks/exhaustive-deps
76
+ [webViewKey]);
72
77
  const onMessage = useCallback((event) => {
73
78
  try {
74
79
  const msg = JSON.parse(event.nativeEvent.data);
75
80
  if (msg.type === 'ready') {
76
81
  readyRef.current = true;
77
- // Flush any accessories that arrived before the WebView was ready
82
+ // Flush pending props that may have arrived before WebView was ready
78
83
  if (accessoriesRef.current?.length) {
79
84
  post({ type: 'set_accessories', accessories: accessoriesRef.current });
80
85
  }
81
86
  onReady?.();
82
87
  }
83
- else if (msg.type === 'error')
88
+ else if (msg.type === 'error') {
84
89
  onError?.(msg.message);
85
- else if (msg.type === 'log')
90
+ }
91
+ else if (msg.type === 'log') {
86
92
  console.log('[TalkingHead]', msg.message);
93
+ }
87
94
  }
88
95
  catch (err) {
89
- console.warn('[TalkingHead] Invalid message received from WebView:', err);
96
+ console.warn('[TalkingHead] Invalid message from WebView:', err);
90
97
  }
91
98
  }, [onReady, onError, post]);
92
99
  return (<View style={[styles.container, style]}>
93
- <WebView ref={webViewRef} source={{ html }} style={styles.webview} javaScriptEnabled domStorageEnabled allowsInlineMediaPlayback mediaPlaybackRequiresUserAction={false} onMessage={onMessage} originWhitelist={['*']} mixedContentMode="always"/>
100
+ <WebView key={webViewKey} ref={webViewRef} source={{ html }} style={styles.webview} javaScriptEnabled domStorageEnabled allowsInlineMediaPlayback mediaPlaybackRequiresUserAction={false} onMessage={onMessage} originWhitelist={['*']} mixedContentMode="always"/>
94
101
  </View>);
95
102
  });
96
103
  TalkingHead.displayName = 'TalkingHead';
@@ -1,5 +1,7 @@
1
1
  import React from 'react';
2
2
  import { type StyleProp, type ViewStyle } from 'react-native';
3
+ import type { TalkingHeadVisemeCue, TalkingHeadVisemeSchedule } from './TalkingHead';
4
+ export type { TalkingHeadVisemeCue, TalkingHeadVisemeSchedule };
3
5
  export type TalkingHeadMood = 'neutral' | 'happy' | 'sad' | 'angry' | 'excited' | 'thinking' | 'concerned' | 'surprised';
4
6
  export interface TalkingHeadAccessory {
5
7
  id: string;
@@ -25,6 +27,8 @@ export interface TalkingHeadProps {
25
27
  }
26
28
  export interface TalkingHeadRef {
27
29
  sendAmplitude: (amplitude: number) => void;
30
+ scheduleVisemes: (schedule: TalkingHeadVisemeSchedule) => void;
31
+ clearVisemes: () => void;
28
32
  setMood: (mood: TalkingHeadMood) => void;
29
33
  setHairColor: (color: string) => void;
30
34
  setSkinColor: (color: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"TalkingHead.web.d.ts","sourceRoot":"","sources":["../src/TalkingHead.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,KAAK,SAAS,EAAoB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAGhF,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,OAAO,GACP,KAAK,GACL,OAAO,GACP,SAAS,GACT,UAAU,GACV,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,OAAO,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAC;IACzC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,cAAc,EAAE,CAAC,WAAW,EAAE,oBAAoB,EAAE,KAAK,IAAI,CAAC;CAC/D;AAED,eAAO,MAAM,WAAW,yFAiJvB,CAAC"}
1
+ {"version":3,"file":"TalkingHead.web.d.ts","sourceRoot":"","sources":["../src/TalkingHead.web.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,KAAK,SAAS,EAAoB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEhF,OAAO,KAAK,EACV,oBAAoB,EACpB,yBAAyB,EAC1B,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,CAAC;AAEhE,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,OAAO,GACP,KAAK,GACL,OAAO,GACP,SAAS,GACT,UAAU,GACV,WAAW,GACX,WAAW,CAAC;AAEhB,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,eAAe,EAAE,CAAC,QAAQ,EAAE,yBAAyB,KAAK,IAAI,CAAC;IAC/D,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,OAAO,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAC;IACzC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,cAAc,EAAE,CAAC,WAAW,EAAE,oBAAoB,EAAE,KAAK,IAAI,CAAC;CAC/D;AAED,eAAO,MAAM,WAAW,yFAmJvB,CAAC"}
@@ -13,6 +13,8 @@ export const TalkingHead = forwardRef(({ avatarUrl, authToken, mood = 'neutral',
13
13
  }, []);
14
14
  useImperativeHandle(ref, () => ({
15
15
  sendAmplitude: (amplitude) => post({ type: 'amplitude', value: amplitude }),
16
+ scheduleVisemes: (schedule) => post({ type: 'schedule_visemes', schedule }),
17
+ clearVisemes: () => post({ type: 'clear_visemes' }),
16
18
  setMood: (nextMood) => post({ type: 'mood', value: nextMood }),
17
19
  setHairColor: (color) => post({ type: 'hair_color', value: color }),
18
20
  setSkinColor: (color) => post({ type: 'skin_color', value: color }),
@@ -1 +1 @@
1
- {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC;IACjG,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAgkB5D"}
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC;IACjG,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAIF,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAupB5D"}