Ezmary commited on
Commit
96791ed
·
verified ·
1 Parent(s): fa51019

Update src/App.tsx

Browse files
Files changed (1) hide show
  1. src/App.tsx +171 -135
src/App.tsx CHANGED
@@ -1,4 +1,4 @@
1
- // ... (لایسنس، ایمپورت‌ها، myCustomInstruction, initialAppConfig، SvgHumanIcon, SvgReferenceMicrophoneIcon، AppInternalLogic بدون تغییر از پاسخ قبلی) ...
2
  import React, { useEffect, useRef, useState } from "react";
3
  import './App.scss';
4
  import { LiveAPIProvider, useLiveAPIContext } from "./contexts/LiveAPIContext";
@@ -107,145 +107,174 @@ const SvgReferenceMicrophoneIcon = () => (
107
  </svg>
108
  );
109
 
110
- const AppInternalLogic: React.FC<{ /* ...props... */ }> = ({ /* ...props... */ }) => {
111
- // ... (بدون تغییر)
112
- const { isMicActive, isCamActive, setIsMicActive, setIsCamActive, createLogoFunction, videoRef, notificationPopoverRef, notificationButtonRef, isNotificationOpen, setIsNotificationOpen } = arguments[0];
113
- const { connected, disconnect } = useLiveAPIContext();
114
-
115
- useEffect(() => {
116
- if (!isMicActive && !isCamActive && connected) {
117
- disconnect();
118
- }
119
- }, [isMicActive, isCamActive, connected, disconnect]);
120
-
121
- return (
122
- <div className="w-full flex flex-col items-center justify-center min-h-screen text-foreground antialiased">
123
- <div className="main-wrapper max-w-3xl w-full flex flex-col items-center justify-center h-full relative">
124
- <div className="header-controls">
125
- <div id="notification-trigger-container">
126
- <button
127
- ref={notificationButtonRef}
128
- id="notification-button"
129
- aria-label="Notifications"
130
- className="header-button"
131
- onClick={(e) => {
132
- e.stopPropagation();
133
- setIsNotificationOpen(!isNotificationOpen);
134
- }}
135
- >
136
- <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>
137
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  </div>
139
- <div className="back-button-container">
140
- <button
141
- id="back-button"
142
- aria-label="Go back"
143
- className="header-button"
144
- onClick={() => alert('Back clicked (implement navigation)')}
 
 
145
  >
146
- <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>
147
- </button>
148
- </div>
149
- </div>
150
-
151
- <div ref={notificationPopoverRef} id="notification-popover-wrapper" className="notification-popover-wrapper">
152
- <div
153
- id="notification-popover"
154
- className={cn("popover-content", {
155
- "open animate-popover-open-top-center": isNotificationOpen,
156
- "animate-popover-close-top-center": !isNotificationOpen && document.getElementById('notification-popover')?.classList.contains('open'),
157
- })}
158
- >
159
- <div className="notification-popover-text-content">
160
- مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.
161
  </div>
162
  </div>
163
- </div>
164
-
165
- <div className="media-area w-full flex flex-col items-center justify-center flex-grow relative">
166
- <video
167
- id="video-feed"
168
- ref={videoRef}
169
- autoPlay
170
- playsInline
171
- className={cn(
172
- "absolute top-0 left-0 w-full h-full object-cover scale-x-[-1]",
173
- { "hidden": !isCamActive }
 
 
 
 
 
 
 
 
174
  )}
 
 
 
 
 
 
 
 
 
 
 
 
175
  />
176
- {isMicActive && !isCamActive && (
177
- <div
178
- id="large-logo-container"
179
- className="absolute top-0 left-0 w-full h-full flex items-center justify-center pointer-events-none"
180
- >
181
- {createLogoFunction(false, true, 'human', false)}
182
- </div>
183
- )}
184
  </div>
185
-
186
- <ControlTray
187
- videoRef={videoRef}
188
- supportsVideo={true}
189
- onVideoStreamChange={(stream) => { /* ... */ }}
190
- isAppMicActive={isMicActive}
191
- onAppMicToggle={setIsMicActive}
192
- isAppCamActive={isCamActive}
193
- onAppCamToggle={setIsCamActive}
194
- createLogoFunction={createLogoFunction}
195
- ReferenceMicrophoneIcon={SvgReferenceMicrophoneIcon}
196
- />
197
  </div>
198
- </div>
199
- );
200
  };
201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
- const logoColorConfig = { /* ... (بدون تغییر) ... */ };
204
- // ... (App و createLogoFunction مثل قبل، با تمرکز روی صحت رندر حلقه‌ها) ...
205
- function App() {
206
- const videoRef = useRef<HTMLVideoElement>(null);
207
- const [showIOSModal, setShowIOSModal] = useState(false);
208
- const [isAllowedOrigin, setIsAllowedOrigin] = useState<boolean | null>(null);
209
-
210
- const [isMicActive, setIsMicActive] = useState(false);
211
- const [isCamActive, setIsCamActive] = useState(false);
212
- const [isNotificationOpen, setIsNotificationOpen] = useState(false);
213
-
214
- const notificationPopoverRef = useRef<HTMLDivElement>(null);
215
- const notificationButtonRef = useRef<HTMLButtonElement>(null);
216
-
217
- useEffect(() => {
218
- if (isIOS()) {
219
- setShowIOSModal(true);
220
- }
221
- const timer = setTimeout(() => {
222
- setIsAllowedOrigin(true);
223
- }, 100);
224
- return () => clearTimeout(timer);
225
- }, []);
226
-
227
- useEffect(() => {
228
- const handleClickOutside = (event: MouseEvent) => {
229
- if (
230
- isNotificationOpen &&
231
- notificationPopoverRef.current &&
232
- !notificationPopoverRef.current.contains(event.target as Node) &&
233
- notificationButtonRef.current &&
234
- !notificationButtonRef.current.contains(event.target as Node)
235
- ) {
236
- setIsNotificationOpen(false);
237
  }
238
- };
239
- document.addEventListener("mousedown", handleClickOutside);
240
- return () => {
241
- document.removeEventListener("mousedown", handleClickOutside);
242
- };
243
- }, [isNotificationOpen]);
244
-
245
- if (isAllowedOrigin === null) {
246
- return <div style={{ padding: '20px', textAlign: 'center' }}>در حال بررسی دسترسی...</div>;
247
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
 
 
249
  const createLogoFunction = (isMini: boolean, isActive: boolean, type: 'human' | 'ai' = 'human', forFooter: boolean = false) => {
250
  if (!isActive) return null;
251
 
@@ -256,17 +285,23 @@ function App() {
256
  const iconDisplaySize = isMini ? 35 : 70;
257
  const iconInset = (size - iconDisplaySize) / 2;
258
 
259
- // مقادیر inset برای حلقه‌ها از HTML مرجع شما
260
- // این مقادیر باید ثابت باشند تا ظاهر انیمیشن حفظ شود
261
- const insetValues = isMini
262
- ? { ping: 10, outer: 0, mid: 5, inner: 12 } // مقادیر برای لوگوی کوچک
263
- : { ping: 40, outer: 0, mid: 20, inner: 50 }; // مقادیر برای لوگوی بزرگ
 
 
 
 
 
 
264
 
265
  const IconComponent = type === 'human' ? SvgHumanIcon : null;
266
 
267
  return (
268
  <div className={cn("logo-animation-wrapper", {"for-footer": forFooter})} style={{ width: `${size}px`, height: `${size}px` }}>
269
- {/* حلقه Ping */}
270
  <div className={`absolute rounded-full opacity-50 animate-ping ${currentColors.ping}`} style={{ inset: `${insetValues.ping}px` }}></div>
271
  {/* حلقه بیرونی */}
272
  <div className={`absolute rounded-full opacity-50 ${currentColors.outer}`} style={{ inset: `${insetValues.outer}px` }}></div>
@@ -275,14 +310,14 @@ function App() {
275
  {/* حلقه داخلی */}
276
  <div className={`absolute rounded-full opacity-50 ${currentColors.inner}`} style={{ inset: `${insetValues.inner}px` }}></div>
277
  {/* کانتینر آیکون */}
278
- <div className="z-10 absolute flex items-center justify-center" style={{ inset: `${iconInset}px`, width: `${iconDisplaySize}px`, height: `${iconDisplaySize}px` }}>
279
  {IconComponent && <IconComponent />}
280
  </div>
281
  </div>
282
  );
283
  };
284
 
285
- return (
286
  <LiveAPIProvider initialConfig={initialAppConfig}>
287
  <AppInternalLogic
288
  isMicActive={isMicActive}
@@ -300,4 +335,5 @@ function App() {
300
  </LiveAPIProvider>
301
  );
302
  }
 
303
  export default App;
 
1
+ // ... (ایمپورت‌ها، myCustomInstruction, initialAppConfig, SVG Icons, AppInternalLogic، و بخش اول تابع App بدون تغییر) ...
2
  import React, { useEffect, useRef, useState } from "react";
3
  import './App.scss';
4
  import { LiveAPIProvider, useLiveAPIContext } from "./contexts/LiveAPIContext";
 
107
  </svg>
108
  );
109
 
110
+ const AppInternalLogic: React.FC<{ /* ... props ... */ }> = ({ /* ... props ... */ }) => {
111
+ // ... (بدون تغییر)
112
+ isMicActive,
113
+ isCamActive,
114
+ setIsMicActive,
115
+ setIsCamActive,
116
+ createLogoFunction,
117
+ videoRef,
118
+ notificationPopoverRef,
119
+ notificationButtonRef,
120
+ isNotificationOpen,
121
+ setIsNotificationOpen
122
+ }) => {
123
+ const { connected, disconnect } = useLiveAPIContext();
124
+
125
+ useEffect(() => {
126
+ if (!isMicActive && !isCamActive && connected) {
127
+ disconnect();
128
+ }
129
+ }, [isMicActive, isCamActive, connected, disconnect]);
130
+
131
+ return (
132
+ <div className="w-full flex flex-col items-center justify-center min-h-screen text-foreground antialiased">
133
+ <div className="main-wrapper max-w-3xl w-full flex flex-col items-center justify-center h-full relative">
134
+ <div className="header-controls">
135
+ <div id="notification-trigger-container">
136
+ <button
137
+ ref={notificationButtonRef}
138
+ id="notification-button"
139
+ aria-label="Notifications"
140
+ className="header-button"
141
+ onClick={(e) => {
142
+ e.stopPropagation();
143
+ setIsNotificationOpen(!isNotificationOpen);
144
+ }}
145
+ >
146
+ <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>
147
+ </button>
148
+ </div>
149
+ <div className="back-button-container">
150
+ <button
151
+ id="back-button"
152
+ aria-label="Go back"
153
+ className="header-button"
154
+ onClick={() => alert('Back clicked (implement navigation)')}
155
+ >
156
+ <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>
157
+ </button>
158
+ </div>
159
  </div>
160
+
161
+ <div ref={notificationPopoverRef} id="notification-popover-wrapper" className="notification-popover-wrapper">
162
+ <div
163
+ id="notification-popover"
164
+ className={cn("popover-content", {
165
+ "open animate-popover-open-top-center": isNotificationOpen,
166
+ "animate-popover-close-top-center": !isNotificationOpen && document.getElementById('notification-popover')?.classList.contains('open'),
167
+ })}
168
  >
169
+ <div className="notification-popover-text-content">
170
+ مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.
171
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
172
  </div>
173
  </div>
174
+
175
+ <div className="media-area w-full flex flex-col items-center justify-center flex-grow relative">
176
+ <video
177
+ id="video-feed"
178
+ ref={videoRef}
179
+ autoPlay
180
+ playsInline
181
+ className={cn(
182
+ "absolute top-0 left-0 w-full h-full object-cover scale-x-[-1]",
183
+ { "hidden": !isCamActive }
184
+ )}
185
+ />
186
+ {isMicActive && !isCamActive && (
187
+ <div
188
+ id="large-logo-container"
189
+ className="absolute top-0 left-0 w-full h-full flex items-center justify-center pointer-events-none"
190
+ >
191
+ {createLogoFunction(false, true, 'human', false)}
192
+ </div>
193
  )}
194
+ </div>
195
+
196
+ <ControlTray
197
+ videoRef={videoRef}
198
+ supportsVideo={true}
199
+ onVideoStreamChange={(stream) => { /* ... */ }}
200
+ isAppMicActive={isMicActive}
201
+ onAppMicToggle={setIsMicActive}
202
+ isAppCamActive={isCamActive}
203
+ onAppCamToggle={setIsCamActive}
204
+ createLogoFunction={createLogoFunction}
205
+ ReferenceMicrophoneIcon={SvgReferenceMicrophoneIcon}
206
  />
 
 
 
 
 
 
 
 
207
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
208
  </div>
209
+ );
 
210
  };
211
 
212
+ const logoColorConfig = { /* ... (بدون تغییر) ... */
213
+ blue: {
214
+ ping: "bg-blue-200 dark:bg-blue-700",
215
+ outer: "bg-blue-200 dark:bg-blue-700",
216
+ mid: "bg-blue-300 dark:bg-blue-600",
217
+ inner: "bg-blue-400 dark:bg-blue-500",
218
+ },
219
+ green: {
220
+ ping: "bg-green-200 dark:bg-green-700",
221
+ outer: "bg-green-200 dark:bg-green-700",
222
+ mid: "bg-green-300 dark:bg-green-600",
223
+ inner: "bg-green-400 dark:bg-green-500",
224
+ },
225
+ gray: {
226
+ ping: "bg-gray-200 dark:bg-gray-700",
227
+ outer: "bg-gray-200 dark:bg-gray-700",
228
+ mid: "bg-gray-300 dark:bg-gray-600",
229
+ inner: "bg-gray-400 dark:bg-gray-500",
230
+ }
231
+ };
232
 
233
+ function App() { /* ... (useEffect ها و state ها بدون تغییر) ... */
234
+ const videoRef = useRef<HTMLVideoElement>(null);
235
+ const [showIOSModal, setShowIOSModal] = useState(false);
236
+ const [isAllowedOrigin, setIsAllowedOrigin] = useState<boolean | null>(null);
237
+
238
+ const [isMicActive, setIsMicActive] = useState(false);
239
+ const [isCamActive, setIsCamActive] = useState(false);
240
+ const [isNotificationOpen, setIsNotificationOpen] = useState(false);
241
+
242
+ const notificationPopoverRef = useRef<HTMLDivElement>(null);
243
+ const notificationButtonRef = useRef<HTMLButtonElement>(null);
244
+
245
+ useEffect(() => {
246
+ if (isIOS()) {
247
+ setShowIOSModal(true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  }
249
+ const timer = setTimeout(() => {
250
+ setIsAllowedOrigin(true);
251
+ }, 100);
252
+ return () => clearTimeout(timer);
253
+ }, []);
254
+
255
+ useEffect(() => {
256
+ const handleClickOutside = (event: MouseEvent) => {
257
+ if (
258
+ isNotificationOpen &&
259
+ notificationPopoverRef.current &&
260
+ !notificationPopoverRef.current.contains(event.target as Node) &&
261
+ notificationButtonRef.current &&
262
+ !notificationButtonRef.current.contains(event.target as Node)
263
+ ) {
264
+ setIsNotificationOpen(false);
265
+ }
266
+ };
267
+ document.addEventListener("mousedown", handleClickOutside);
268
+ return () => {
269
+ document.removeEventListener("mousedown", handleClickOutside);
270
+ };
271
+ }, [isNotificationOpen]);
272
+
273
+ if (isAllowedOrigin === null) {
274
+ return <div style={{ padding: '20px', textAlign: 'center' }}>در حال بررسی دسترسی...</div>;
275
+ }
276
 
277
+ // *** MODIFIED: createLogoFunction با دقت بیشتر در inset ها و کلاس‌ها ***
278
  const createLogoFunction = (isMini: boolean, isActive: boolean, type: 'human' | 'ai' = 'human', forFooter: boolean = false) => {
279
  if (!isActive) return null;
280
 
 
285
  const iconDisplaySize = isMini ? 35 : 70;
286
  const iconInset = (size - iconDisplaySize) / 2;
287
 
288
+ // مقادیر inset برای حلقه‌ها از HTML مرجع شما:
289
+ // بزرگ: ping: 40, outer: 0, mid: 20, inner: 50, icon: 65
290
+ // کوچک: ping: 10, outer: 0, mid: 5, inner: 12, icon: 22
291
+ // ما iconInset را خودمان محاسبه کردیم، بقیه را از HTML می‌گیریم.
292
+ const insetValues = {
293
+ ping: isMini ? 10 : 40,
294
+ outer: 0, // در HTML شما برای outer از inset:0 استفاده شده
295
+ mid: isMini ? 5 : 20,
296
+ inner: isMini ? 12 : 50,
297
+ icon: iconInset
298
+ };
299
 
300
  const IconComponent = type === 'human' ? SvgHumanIcon : null;
301
 
302
  return (
303
  <div className={cn("logo-animation-wrapper", {"for-footer": forFooter})} style={{ width: `${size}px`, height: `${size}px` }}>
304
+ {/* حلقه پینگ */}
305
  <div className={`absolute rounded-full opacity-50 animate-ping ${currentColors.ping}`} style={{ inset: `${insetValues.ping}px` }}></div>
306
  {/* حلقه بیرونی */}
307
  <div className={`absolute rounded-full opacity-50 ${currentColors.outer}`} style={{ inset: `${insetValues.outer}px` }}></div>
 
310
  {/* حلقه داخلی */}
311
  <div className={`absolute rounded-full opacity-50 ${currentColors.inner}`} style={{ inset: `${insetValues.inner}px` }}></div>
312
  {/* کانتینر آیکون */}
313
+ <div className="z-10 absolute flex items-center justify-center" style={{ inset: `${insetValues.icon}px`, width: `${iconDisplaySize}px`, height: `${iconDisplaySize}px` }}>
314
  {IconComponent && <IconComponent />}
315
  </div>
316
  </div>
317
  );
318
  };
319
 
320
+ return ( /* ... (LiveAPIProvider و AppInternalLogic مثل قبل) ... */
321
  <LiveAPIProvider initialConfig={initialAppConfig}>
322
  <AppInternalLogic
323
  isMicActive={isMicActive}
 
335
  </LiveAPIProvider>
336
  );
337
  }
338
+
339
  export default App;