Ezmary commited on
Commit
894377a
·
verified ·
1 Parent(s): 219be00

Update src/App.tsx

Browse files
Files changed (1) hide show
  1. src/App.tsx +115 -66
src/App.tsx CHANGED
@@ -19,6 +19,7 @@ import { IOSModal } from "./components/ios-modal/IOSModal";
19
  import { isIOS } from "./lib/platform";
20
  import cn from "classnames";
21
  import { LiveConfig } from "./multimodal-live-types";
 
22
 
23
  const myCustomInstruction = `
24
  ت1. هویت دستیار:
@@ -104,13 +105,8 @@ const initialAppConfig: LiveConfig = {
104
  },
105
  };
106
 
107
- const SvgHumanIcon = () => (
108
- <svg width="100%" height="100%" viewBox="0 0 88 89" fill="none" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet">
109
- <path d="M75.1481 81.6361H12.9259C9.66667 81.6361 7 78.9721 7 75.7161V58.5112C7 57.5862 7 57.1052 7.44444 56.2172C8.85185 52.9612 13 50.2232 19.4815 47.8922C24.1111 56.6982 33.3704 62.6921 44 62.6921C54.6296 62.6921 63.9259 56.6982 68.5185 47.8922C75 50.1862 79.1852 52.9982 80.5556 56.2172C81 56.6612 81 57.6232 81 58.5112V75.7161C81 78.9721 78.3333 81.6361 75.0741 81.6361H75.1481Z" stroke="currentColor" strokeWidth="6.42146" strokeLinecap="round" strokeLinejoin="round"/>
110
- <path d="M44.0371 50.1862C33.8519 50.1862 25.5186 41.8612 25.5186 31.6863V26.1363C25.5186 15.9613 33.8519 7.63635 44.0371 7.63635C54.2223 7.63635 62.5556 15.9613 62.5556 26.1363V31.6863C62.5556 41.8612 54.2223 50.1862 44.0371 50.1862Z" stroke="currentColor" strokeWidth="6.42146" strokeLinecap="round" strokeLinejoin="round"/>
111
- </svg>
112
- );
113
-
114
  const SvgReferenceMicrophoneIcon = () => (
115
  <svg className="reference-mic-svg" viewBox="0 0 69 68" fill="none" xmlns="http://www.w3.org/2000/svg">
116
  <path opacity="0.4" d="M49.9479 27.1824C49.0803 27.1824 48.3907 27.872 48.3907 28.7396V32.2544C48.3907 40.1293 41.984 46.5361 34.109 46.5361C26.234 46.5361 19.8273 40.1293 19.8273 32.2544V28.7173C19.8273 27.8497 19.1377 27.1601 18.2701 27.1601C17.4025 27.1601 16.7129 27.8497 16.7129 28.7173V32.2321C16.7129 41.2861 23.6758 48.7384 32.5518 49.5393V54.2776C32.5518 55.1452 33.2414 55.8348 34.109 55.8348C34.9766 55.8348 35.6662 55.1452 35.6662 54.2776V49.5393C44.52 48.7607 51.5051 41.2861 51.5051 32.2321V28.7173C51.4829 27.872 50.7933 27.1824 49.9479 27.1824Z" fill="#BE123C"/>
@@ -118,119 +114,172 @@ const SvgReferenceMicrophoneIcon = () => (
118
  </svg>
119
  );
120
 
121
- // *** MODIFIED: تعریف دقیق پراپ‌ها برای AppInternalLogic ***
122
- interface AppInternalLogicProps {
123
  isMicActive: boolean;
124
  isCamActive: boolean;
125
  setIsMicActive: React.Dispatch<React.SetStateAction<boolean>>;
126
  setIsCamActive: React.Dispatch<React.SetStateAction<boolean>>;
127
- createLogoFunction: (isMini: boolean, isActive: boolean, type?: 'human' | 'ai', forFooter?: boolean) => React.ReactNode;
128
  videoRef: React.RefObject<HTMLVideoElement>;
129
  notificationPopoverRef: React.RefObject<HTMLDivElement>;
130
  notificationButtonRef: React.RefObject<HTMLButtonElement>;
131
  isNotificationOpen: boolean;
132
  setIsNotificationOpen: React.Dispatch<React.SetStateAction<boolean>>;
133
- }
134
-
135
- const AppInternalLogic: React.FC<AppInternalLogicProps> = ({
136
- isMicActive, isCamActive, setIsMicActive, setIsCamActive, createLogoFunction,
137
- videoRef, notificationPopoverRef, notificationButtonRef, isNotificationOpen, setIsNotificationOpen
 
 
 
 
 
138
  }) => {
139
  const { connected, disconnect } = useLiveAPIContext();
140
- useEffect(() => { if (!isMicActive && !isCamActive && connected) { disconnect(); } }, [isMicActive, isCamActive, connected, disconnect]);
 
 
 
 
 
141
 
142
  return (
143
  <div className="w-full flex flex-col items-center justify-center min-h-screen text-foreground antialiased">
144
  <div className="main-wrapper max-w-3xl w-full flex flex-col items-center justify-center h-full relative">
145
  <div className="header-controls">
146
  <div id="notification-trigger-container">
147
- <button ref={notificationButtonRef} id="notification-button" aria-label="Notifications" className="header-button" onClick={(e) => { e.stopPropagation(); setIsNotificationOpen(!isNotificationOpen); }}>
 
 
 
 
 
 
 
 
 
148
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
149
  </button>
150
  </div>
151
  <div className="back-button-container">
152
- <button id="back-button" aria-label="Go back" className="header-button" onClick={() => alert('Back clicked (implement navigation)')}>
 
 
 
 
 
153
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m15 18-6-6 6-6"></path></svg>
154
  </button>
155
  </div>
156
  </div>
 
157
  <div ref={notificationPopoverRef} id="notification-popover-wrapper" className="notification-popover-wrapper">
158
- <div id="notification-popover" className={cn("popover-content", { "open animate-popover-open-top-center": isNotificationOpen, "animate-popover-close-top-center": !isNotificationOpen && document.getElementById('notification-popover')?.classList.contains('open'), })}>
159
- <div className="notification-popover-text-content"> مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید. </div>
 
 
 
 
 
 
 
 
160
  </div>
161
  </div>
 
162
  <div className="media-area w-full flex flex-col items-center justify-center flex-grow relative">
163
- <video id="video-feed" ref={videoRef} autoPlay playsInline className={cn("absolute top-0 left-0 w-full h-full object-cover scale-x-[-1]", { "hidden": !isCamActive })} />
 
 
 
 
 
 
 
 
 
164
  {isMicActive && !isCamActive && (
165
- <div id="large-logo-container" className="large-logo-container-style">
166
- {createLogoFunction(false, true, 'human', false)}
 
 
 
167
  </div>
168
  )}
169
  </div>
 
170
  <ControlTray
171
- videoRef={videoRef} supportsVideo={true} onVideoStreamChange={() => {}}
172
- isAppMicActive={isMicActive} onAppMicToggle={setIsMicActive}
173
- isAppCamActive={isCamActive} onAppCamToggle={setIsCamActive}
174
- createLogoFunction={createLogoFunction}
 
 
 
175
  ReferenceMicrophoneIcon={SvgReferenceMicrophoneIcon}
 
176
  />
177
  </div>
178
  </div>
179
  );
180
- };
181
-
182
- const logoColorConfig = {
183
- blue: { ping: "bg-blue-200 dark:bg-blue-700", outer: "bg-blue-200 dark:bg-blue-700", mid: "bg-blue-300 dark:bg-blue-600", inner: "bg-blue-400 dark:bg-blue-500", },
184
- green: { ping: "bg-green-200 dark:bg-green-700", outer: "bg-green-200 dark:bg-green-700", mid: "bg-green-300 dark:bg-green-600", inner: "bg-green-400 dark:bg-green-500", },
185
- gray: { ping: "bg-gray-200 dark:bg-gray-700", outer: "bg-gray-200 dark:bg-gray-700", mid: "bg-gray-300 dark:bg-gray-600", inner: "bg-gray-400 dark:bg-gray-500", }
186
- };
187
 
188
  function App() {
189
  const videoRef = useRef<HTMLVideoElement>(null);
190
  const [showIOSModal, setShowIOSModal] = useState(false);
191
  const [isAllowedOrigin, setIsAllowedOrigin] = useState<boolean | null>(null);
 
192
  const [isMicActive, setIsMicActive] = useState(false);
193
  const [isCamActive, setIsCamActive] = useState(false);
194
  const [isNotificationOpen, setIsNotificationOpen] = useState(false);
 
195
  const notificationPopoverRef = useRef<HTMLDivElement>(null);
196
  const notificationButtonRef = useRef<HTMLButtonElement>(null);
197
- useEffect(() => { if (isIOS()) { setShowIOSModal(true); } const timer = setTimeout(() => { setIsAllowedOrigin(true); }, 100); return () => clearTimeout(timer); }, []);
198
- useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (isNotificationOpen && notificationPopoverRef.current && !notificationPopoverRef.current.contains(event.target as Node) && notificationButtonRef.current && !notificationButtonRef.current.contains(event.target as Node)) { setIsNotificationOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, [isNotificationOpen]);
199
- if (isAllowedOrigin === null) { return <div style={{ padding: '20px', textAlign: 'center' }}>در حال بررسی دسترسی...</div>; }
200
-
201
- const createLogoFunction = (isMini: boolean, isActive: boolean, type: 'human' | 'ai' = 'human', forFooter: boolean = false) => {
202
- if (!isActive) return null;
203
- const colorKey = type === 'human' ? 'blue' : (type === 'ai' ? 'green' : 'gray');
204
- const currentColors = logoColorConfig[colorKey as keyof typeof logoColorConfig] || logoColorConfig.gray;
205
- const size = isMini ? 80 : 200;
206
- const iconDisplaySize = isMini ? 35 : 70;
207
- const iconInset = (size - iconDisplaySize) / 2;
208
- const insetsForRings = isMini
209
- ? { ping: 10, outer: 0, mid: 5, inner: 12 }
210
- : { ping: 40, outer: 0, mid: 20, inner: 50 };
211
- const IconComponent = type === 'human' ? SvgHumanIcon : null;
212
- return (
213
- <div className={cn("logo-animation-wrapper", {"for-footer": forFooter})} style={{ width: `${size}px`, height: `${size}px` }}>
214
- <div className={`absolute rounded-full opacity-50 animate-ping ${currentColors.ping}`} style={{ inset: `${insetsForRings.ping}px` }}></div>
215
- <div className={`absolute rounded-full opacity-50 ${currentColors.outer}`} style={{ inset: `${insetsForRings.outer}px` }}></div>
216
- <div className={`absolute rounded-full opacity-50 ${currentColors.mid}`} style={{ inset: `${insetsForRings.mid}px` }}></div>
217
- <div className={`absolute rounded-full opacity-50 ${currentColors.inner}`} style={{ inset: `${insetsForRings.inner}px` }}></div>
218
- <div className="z-10 absolute flex items-center justify-center" style={{ inset: `${iconInset}px`, width: `${iconDisplaySize}px`, height: `${iconDisplaySize}px` }}>
219
- {IconComponent && <IconComponent />}
220
- </div>
221
- </div>
222
- );
223
- };
 
 
 
 
 
224
 
225
  return (
226
  <LiveAPIProvider initialConfig={initialAppConfig}>
227
  <AppInternalLogic
228
- isMicActive={isMicActive} setIsMicActive={setIsMicActive}
229
- isCamActive={isCamActive} setIsCamActive={setIsCamActive}
230
- createLogoFunction={createLogoFunction}
 
231
  videoRef={videoRef}
232
- notificationPopoverRef={notificationPopoverRef} notificationButtonRef={notificationButtonRef}
233
- isNotificationOpen={isNotificationOpen} setIsNotificationOpen={setIsNotificationOpen}
 
 
234
  />
235
  <IOSModal isOpen={showIOSModal} onClose={() => setShowIOSModal(false)} />
236
  </LiveAPIProvider>
 
19
  import { isIOS } from "./lib/platform";
20
  import cn from "classnames";
21
  import { LiveConfig } from "./multimodal-live-types";
22
+ import LogoAnimation from "./components/logo-animation/LogoAnimation"; // ایمپورت کامپوننت لوگو
23
 
24
  const myCustomInstruction = `
25
  ت1. هویت دستیار:
 
105
  },
106
  };
107
 
108
+ // SVG آیکون میکروفون از HTML مرجع شما (با سه خط افقی)
109
+ // این آیکون به ControlTray پاس داده می‌شود
 
 
 
 
 
110
  const SvgReferenceMicrophoneIcon = () => (
111
  <svg className="reference-mic-svg" viewBox="0 0 69 68" fill="none" xmlns="http://www.w3.org/2000/svg">
112
  <path opacity="0.4" d="M49.9479 27.1824C49.0803 27.1824 48.3907 27.872 48.3907 28.7396V32.2544C48.3907 40.1293 41.984 46.5361 34.109 46.5361C26.234 46.5361 19.8273 40.1293 19.8273 32.2544V28.7173C19.8273 27.8497 19.1377 27.1601 18.2701 27.1601C17.4025 27.1601 16.7129 27.8497 16.7129 28.7173V32.2321C16.7129 41.2861 23.6758 48.7384 32.5518 49.5393V54.2776C32.5518 55.1452 33.2414 55.8348 34.109 55.8348C34.9766 55.8348 35.6662 55.1452 35.6662 54.2776V49.5393C44.52 48.7607 51.5051 41.2861 51.5051 32.2321V28.7173C51.4829 27.872 50.7933 27.1824 49.9479 27.1824Z" fill="#BE123C"/>
 
114
  </svg>
115
  );
116
 
117
+ const AppInternalLogic: React.FC<{
 
118
  isMicActive: boolean;
119
  isCamActive: boolean;
120
  setIsMicActive: React.Dispatch<React.SetStateAction<boolean>>;
121
  setIsCamActive: React.Dispatch<React.SetStateAction<boolean>>;
 
122
  videoRef: React.RefObject<HTMLVideoElement>;
123
  notificationPopoverRef: React.RefObject<HTMLDivElement>;
124
  notificationButtonRef: React.RefObject<HTMLButtonElement>;
125
  isNotificationOpen: boolean;
126
  setIsNotificationOpen: React.Dispatch<React.SetStateAction<boolean>>;
127
+ }> = ({
128
+ isMicActive,
129
+ isCamActive,
130
+ setIsMicActive,
131
+ setIsCamActive,
132
+ videoRef,
133
+ notificationPopoverRef,
134
+ notificationButtonRef,
135
+ isNotificationOpen,
136
+ setIsNotificationOpen
137
  }) => {
138
  const { connected, disconnect } = useLiveAPIContext();
139
+
140
+ useEffect(() => {
141
+ if (!isMicActive && !isCamActive && connected) {
142
+ disconnect();
143
+ }
144
+ }, [isMicActive, isCamActive, connected, disconnect]);
145
 
146
  return (
147
  <div className="w-full flex flex-col items-center justify-center min-h-screen text-foreground antialiased">
148
  <div className="main-wrapper max-w-3xl w-full flex flex-col items-center justify-center h-full relative">
149
  <div className="header-controls">
150
  <div id="notification-trigger-container">
151
+ <button
152
+ ref={notificationButtonRef}
153
+ id="notification-button"
154
+ aria-label="Notifications"
155
+ className="header-button"
156
+ onClick={(e) => {
157
+ e.stopPropagation();
158
+ setIsNotificationOpen(!isNotificationOpen);
159
+ }}
160
+ >
161
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
162
  </button>
163
  </div>
164
  <div className="back-button-container">
165
+ <button
166
+ id="back-button"
167
+ aria-label="Go back"
168
+ className="header-button"
169
+ onClick={() => alert('Back clicked (implement navigation)')}
170
+ >
171
  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m15 18-6-6 6-6"></path></svg>
172
  </button>
173
  </div>
174
  </div>
175
+
176
  <div ref={notificationPopoverRef} id="notification-popover-wrapper" className="notification-popover-wrapper">
177
+ <div
178
+ id="notification-popover"
179
+ className={cn("popover-content", {
180
+ "open animate-popover-open-top-center": isNotificationOpen,
181
+ "animate-popover-close-top-center": !isNotificationOpen && document.getElementById('notification-popover')?.classList.contains('open'),
182
+ })}
183
+ >
184
+ <div className="notification-popover-text-content">
185
+ مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.
186
+ </div>
187
  </div>
188
  </div>
189
+
190
  <div className="media-area w-full flex flex-col items-center justify-center flex-grow relative">
191
+ <video
192
+ id="video-feed"
193
+ ref={videoRef}
194
+ autoPlay
195
+ playsInline
196
+ className={cn(
197
+ "absolute top-0 left-0 w-full h-full object-cover scale-x-[-1]",
198
+ { "hidden": !isCamActive }
199
+ )}
200
+ />
201
  {isMicActive && !isCamActive && (
202
+ <div
203
+ id="large-logo-container"
204
+ className="absolute top-0 left-0 w-full h-full flex items-center justify-center pointer-events-none"
205
+ >
206
+ <LogoAnimation isMini={false} isActive={true} type="human" />
207
  </div>
208
  )}
209
  </div>
210
+
211
  <ControlTray
212
+ videoRef={videoRef}
213
+ supportsVideo={true}
214
+ onVideoStreamChange={(stream) => { /* ... */ }}
215
+ isAppMicActive={isMicActive}
216
+ onAppMicToggle={setIsMicActive}
217
+ isAppCamActive={isCamActive}
218
+ onAppCamToggle={setIsCamActive}
219
  ReferenceMicrophoneIcon={SvgReferenceMicrophoneIcon}
220
+ // LogoAnimation کامپوننت مستقیما در ControlTray ایمپورت و استفاده می شود
221
  />
222
  </div>
223
  </div>
224
  );
225
+ }
 
 
 
 
 
 
226
 
227
  function App() {
228
  const videoRef = useRef<HTMLVideoElement>(null);
229
  const [showIOSModal, setShowIOSModal] = useState(false);
230
  const [isAllowedOrigin, setIsAllowedOrigin] = useState<boolean | null>(null);
231
+
232
  const [isMicActive, setIsMicActive] = useState(false);
233
  const [isCamActive, setIsCamActive] = useState(false);
234
  const [isNotificationOpen, setIsNotificationOpen] = useState(false);
235
+
236
  const notificationPopoverRef = useRef<HTMLDivElement>(null);
237
  const notificationButtonRef = useRef<HTMLButtonElement>(null);
238
+
239
+ useEffect(() => {
240
+ if (isIOS()) {
241
+ setShowIOSModal(true);
242
+ }
243
+ const timer = setTimeout(() => {
244
+ setIsAllowedOrigin(true);
245
+ }, 100);
246
+ return () => clearTimeout(timer);
247
+ }, []);
248
+
249
+ useEffect(() => {
250
+ const handleClickOutside = (event: MouseEvent) => {
251
+ if (
252
+ isNotificationOpen &&
253
+ notificationPopoverRef.current &&
254
+ !notificationPopoverRef.current.contains(event.target as Node) &&
255
+ notificationButtonRef.current &&
256
+ !notificationButtonRef.current.contains(event.target as Node)
257
+ ) {
258
+ setIsNotificationOpen(false);
259
+ }
260
+ };
261
+ document.addEventListener("mousedown", handleClickOutside);
262
+ return () => {
263
+ document.removeEventListener("mousedown", handleClickOutside);
264
+ };
265
+ }, [isNotificationOpen]);
266
+
267
+ if (isAllowedOrigin === null) {
268
+ return <div style={{ padding: '20px', textAlign: 'center' }}>در حال بررسی دسترسی...</div>;
269
+ }
270
 
271
  return (
272
  <LiveAPIProvider initialConfig={initialAppConfig}>
273
  <AppInternalLogic
274
+ isMicActive={isMicActive}
275
+ setIsMicActive={setIsMicActive}
276
+ isCamActive={isCamActive}
277
+ setIsCamActive={setIsCamActive}
278
  videoRef={videoRef}
279
+ notificationPopoverRef={notificationPopoverRef}
280
+ notificationButtonRef={notificationButtonRef}
281
+ isNotificationOpen={isNotificationOpen}
282
+ setIsNotificationOpen={setIsNotificationOpen}
283
  />
284
  <IOSModal isOpen={showIOSModal} onClose={() => setShowIOSModal(false)} />
285
  </LiveAPIProvider>