Spaces:
Running
Running
Update src/App.tsx
Browse files- src/App.tsx +30 -200
src/App.tsx
CHANGED
@@ -12,22 +12,15 @@ limitations under the License.
|
|
12 |
*/
|
13 |
|
14 |
import React, { useEffect, useRef, useState } from "react";
|
15 |
-
|
16 |
import { LiveAPIProvider } from "./contexts/LiveAPIContext";
|
17 |
-
// SidePanel و Altair دیگر مستقیما اینجا استفاده نمیشوند، مگر اینکه بخواهید آنها را در جای دیگری از UI جدید قرار دهید.
|
18 |
-
// import SidePanel from "./components/side-panel/SidePanel";
|
19 |
-
// import { Altair } from "./components/altair/Altair";
|
20 |
import ControlTray from "./components/control-tray/ControlTray";
|
21 |
import { IOSModal } from "./components/ios-modal/IOSModal";
|
22 |
import { isIOS } from "./lib/platform";
|
23 |
import cn from "classnames";
|
24 |
import { LiveConfig } from "./multimodal-live-types";
|
25 |
|
26 |
-
// ---
|
27 |
-
const ALLOWED_ORIGIN = "https://www.aisada.ir"; // یا "http://www.aisada.ir"; // یا "https://aisada.ir"; // یا "http://aisada.ir" اگر سایتتان http است
|
28 |
-
// --- 👆 ---
|
29 |
-
|
30 |
-
// --- 👇 دستورالعمل شخصیسازی شما (بدون تغییر) 👇 ---
|
31 |
const myCustomInstruction = `
|
32 |
ت1. هویت دستیار:
|
33 |
|
@@ -98,10 +91,10 @@ const myCustomInstruction = `
|
|
98 |
|
99 |
سطح سختی واژگان و جملات را بر اساس سطح کاربر (مبتدی، متوسط، پیشرفته) تنظیم کن.".
|
100 |
`.trim();
|
101 |
-
// ---
|
102 |
|
103 |
const initialAppConfig: LiveConfig = {
|
104 |
-
model: "models/gemini-2.0-flash-exp",
|
105 |
systemInstruction: {
|
106 |
parts: [{ text: myCustomInstruction }],
|
107 |
},
|
@@ -115,9 +108,9 @@ const SvgHumanIcon = () => (
|
|
115 |
|
116 |
function App() {
|
117 |
const videoRef = useRef<HTMLVideoElement>(null);
|
118 |
-
const [videoStream, setVideoStream] = useState<MediaStream | null>(null);
|
119 |
const [showIOSModal, setShowIOSModal] = useState(false);
|
120 |
-
const [isAllowedOrigin, setIsAllowedOrigin] = useState<boolean | null>(null);
|
121 |
|
122 |
const [isMicActive, setIsMicActive] = useState(false);
|
123 |
const [isCamActive, setIsCamActive] = useState(false);
|
@@ -131,36 +124,15 @@ function App() {
|
|
131 |
if (isIOS()) {
|
132 |
setShowIOSModal(true);
|
133 |
}
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
setIsAllowedOrigin(false);
|
144 |
-
}
|
145 |
-
} else {
|
146 |
-
console.warn("Cannot verify parent origin (ancestorOrigins not available/empty). Blocking.");
|
147 |
-
setIsAllowedOrigin(false);
|
148 |
-
}
|
149 |
-
} else {
|
150 |
-
// اگر مستقیماً بارگذاری شده و نه در یک iframe از دامنه مجاز، آن را مسدود کنید
|
151 |
-
// مگر اینکه بخواهید برای تست محلی به آن اجازه دهید
|
152 |
-
if (process.env.NODE_ENV === 'development' && window.location.hostname === 'localhost') {
|
153 |
-
console.warn("App loaded directly in development. Allowed for local testing.");
|
154 |
-
setIsAllowedOrigin(true);
|
155 |
-
} else {
|
156 |
-
console.warn("App loaded directly, not in an iframe from allowed origin. Blocking.");
|
157 |
-
setIsAllowedOrigin(false);
|
158 |
-
}
|
159 |
-
}
|
160 |
-
} catch (e) {
|
161 |
-
console.error("Cross-origin access error, cannot verify parent. Blocking.", e);
|
162 |
-
setIsAllowedOrigin(false);
|
163 |
-
}
|
164 |
}, []);
|
165 |
|
166 |
useEffect(() => {
|
@@ -183,12 +155,14 @@ function App() {
|
|
183 |
|
184 |
|
185 |
if (isAllowedOrigin === null) {
|
|
|
186 |
return <div style={{ padding: '20px', textAlign: 'center' }}>در حال بررسی دسترسی...</div>;
|
187 |
}
|
188 |
|
189 |
-
|
190 |
-
|
191 |
-
}
|
|
|
192 |
|
193 |
// تابع برای ساخت لوگو (ساده شده)
|
194 |
const createLogoHTML = (isMini: boolean, isActive: boolean, type: 'human' | 'ai' = 'human') => {
|
@@ -198,10 +172,11 @@ function App() {
|
|
198 |
const insetBase = isMini
|
199 |
? { ping: 10, outer: 0, mid: 5, inner: 12, icon: 22 }
|
200 |
: { ping: 40, outer: 0, mid: 20, inner: 50, icon: 65 };
|
201 |
-
const bgColorBase = type === 'human' ? 'blue' : 'green';
|
202 |
|
203 |
return (
|
204 |
<div className="relative" style={{ width: `${size}px`, height: `${size}px` }}>
|
|
|
205 |
<div className={`absolute rounded-full opacity-50 animate-ping bg-${bgColorBase}-200`} style={{ inset: `${insetBase.ping}px` }}></div>
|
206 |
<div className={`absolute inset-0 rounded-full opacity-50 bg-${bgColorBase}-200`} style={{ inset: `${insetBase.outer}px` }}></div>
|
207 |
<div className={`absolute rounded-full opacity-50 bg-${bgColorBase}-300`} style={{ inset: `${insetBase.mid}px` }}></div>
|
@@ -216,146 +191,7 @@ function App() {
|
|
216 |
|
217 |
|
218 |
return (
|
219 |
-
|
220 |
-
{/* Global Styles from your HTML example */}
|
221 |
-
<style jsx global>{`
|
222 |
-
:root {
|
223 |
-
--radius: 0.625rem; /* 10px */
|
224 |
-
--radius-md: 0.5rem; /* 8px */
|
225 |
-
--background: oklch(1 0 0);
|
226 |
-
--foreground: oklch(0.145 0 0);
|
227 |
-
--popover: oklch(1 0 0);
|
228 |
-
--popover-foreground: oklch(0.145 0 0);
|
229 |
-
--border: oklch(0.922 0 0);
|
230 |
-
}
|
231 |
-
.dark {
|
232 |
-
--background: oklch(0.145 0 0);
|
233 |
-
--foreground: oklch(0.985 0 0);
|
234 |
-
--popover: oklch(0.205 0 0);
|
235 |
-
--popover-foreground: oklch(0.985 0 0);
|
236 |
-
--border: oklch(1 0 0 / 10%);
|
237 |
-
}
|
238 |
-
/* Tailwind base layer directives are handled by Tailwind itself if setup via CDN/build */
|
239 |
-
/* Notification Popover */
|
240 |
-
.notification-popover-wrapper {
|
241 |
-
position: fixed; top: 1rem; left: 50%;
|
242 |
-
transform: translateX(-50%); z-index: 100;
|
243 |
-
width: calc(100% - 2rem); max-width: 28rem;
|
244 |
-
display: flex; justify-content: center; pointer-events: none;
|
245 |
-
}
|
246 |
-
.popover-content {
|
247 |
-
width: 100%; border-radius: var(--radius-md, 0.5rem);
|
248 |
-
border-width: 1px; border-color: var(--border);
|
249 |
-
background-color: var(--popover); color: var(--popover-foreground);
|
250 |
-
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
251 |
-
outline: none; transition: opacity 0.3s ease-out, transform 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55);
|
252 |
-
opacity: 0; transform: translateY(-100%) scale(0.9); pointer-events: none;
|
253 |
-
}
|
254 |
-
.popover-content.open { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
|
255 |
-
.notification-popover-text-content {
|
256 |
-
background-color: #eff6ff; /* bg-blue-50 */
|
257 |
-
font-size: 0.875rem; line-height: 1.5rem; direction: rtl;
|
258 |
-
padding: 1rem; border-radius: var(--radius-md, 0.5rem);
|
259 |
-
color: oklch(0.145 0 0);
|
260 |
-
}
|
261 |
-
.dark .notification-popover-text-content {
|
262 |
-
background-color: oklch(0.25 0.05 230); color: oklch(0.95 0.01 230);
|
263 |
-
}
|
264 |
-
/* Header controls */
|
265 |
-
.header-controls {
|
266 |
-
display: flex; padding: 1rem; justify-content: space-between;
|
267 |
-
align-items: center; width: 100%; position: absolute; top: 0; left: 0; z-index: 10;
|
268 |
-
}
|
269 |
-
.header-button {
|
270 |
-
display: flex; align-items: center; justify-content: center;
|
271 |
-
padding: 0.5rem; border-radius: var(--radius-lg, 0.625rem);
|
272 |
-
background-color: #e5e7eb; /* bg-gray-200 */
|
273 |
-
cursor: pointer; transition: background-color 0.2s;
|
274 |
-
}
|
275 |
-
.header-button:hover { background-color: #d1d5db; /* bg-gray-300 */ }
|
276 |
-
.header-button svg { opacity: 0.7; stroke: #374151 /* gray-700 */; }
|
277 |
-
.dark .header-button { background-color: oklch(0.28 0 0); }
|
278 |
-
.dark .header-button:hover { background-color: oklch(0.35 0 0); }
|
279 |
-
.dark .header-button svg { opacity: 0.8; stroke: oklch(0.85 0 0); }
|
280 |
-
|
281 |
-
/* Footer and Control Buttons */
|
282 |
-
.footer-controls {
|
283 |
-
width: 100%; display: flex; gap: 1rem; position: absolute;
|
284 |
-
bottom: 0; padding: 2rem 3rem; align-items: center;
|
285 |
-
}
|
286 |
-
.footer-controls.layout-default { justify-content: space-between; }
|
287 |
-
.footer-controls.layout-with-small-logo { justify-content: space-around; }
|
288 |
-
|
289 |
-
.control-button {
|
290 |
-
height: 80px; width: 80px; border-radius: 9999px; /* rounded-full */
|
291 |
-
padding: 0; display: flex; align-items: center; justify-content: center;
|
292 |
-
border-width: 1px; border-color: var(--border);
|
293 |
-
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -1px rgba(0,0,0,0.06);
|
294 |
-
cursor: pointer; transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
|
295 |
-
}
|
296 |
-
.control-button:hover {
|
297 |
-
transform: scale(1.05);
|
298 |
-
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05);
|
299 |
-
}
|
300 |
-
.cam-button-color { background-color: #E0ECFF; }
|
301 |
-
.mic-button-color { background-color: #fecdd3; }
|
302 |
-
.dark .cam-button-color { background-color: #223355; }
|
303 |
-
.dark .mic-button-color { background-color: #5C2129; }
|
304 |
-
|
305 |
-
/* Switch Camera Button */
|
306 |
-
.switch-camera-button-container {
|
307 |
-
position: absolute; bottom: calc(100% + 0.65rem); left: 50%;
|
308 |
-
z-index: 5; opacity: 0;
|
309 |
-
transform: translateY(15px) scale(0.7) translateX(-50%);
|
310 |
-
pointer-events: none;
|
311 |
-
transition: opacity 0.35s cubic-bezier(0.68, -0.55, 0.27, 1.55), transform 0.35s cubic-bezier(0.68, -0.55, 0.27, 1.55);
|
312 |
-
transform-origin: center bottom;
|
313 |
-
}
|
314 |
-
.switch-camera-button-container.visible {
|
315 |
-
opacity: 1; transform: translateY(0) scale(1) translateX(-50%); pointer-events: auto;
|
316 |
-
}
|
317 |
-
.switch-camera-button-content {
|
318 |
-
width: 48px; height: 48px; background-color: var(--background);
|
319 |
-
border: 1px solid var(--border); border-radius: 9999px;
|
320 |
-
display: flex; align-items: center; justify-content: center;
|
321 |
-
box-shadow: 0 5px 10px rgba(0,0,0,0.12), 0 2px 4px rgba(0,0,0,0.08);
|
322 |
-
cursor: pointer; transform-origin: center;
|
323 |
-
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
|
324 |
-
}
|
325 |
-
.switch-camera-button-content:hover {
|
326 |
-
transform: scale(1.12) rotate(-6deg);
|
327 |
-
box-shadow: 0 7px 15px rgba(0,0,0,0.18), 0 3px 6px rgba(0,0,0,0.12);
|
328 |
-
}
|
329 |
-
.switch-camera-button-content:active { transform: scale(1.03) rotate(0deg); }
|
330 |
-
.switch-camera-button-content svg {
|
331 |
-
width: 22px; height: 22px; stroke: var(--foreground);
|
332 |
-
transition: transform 0.3s ease-in-out;
|
333 |
-
}
|
334 |
-
.switch-camera-button-content:hover svg { transform: rotate(360deg); }
|
335 |
-
|
336 |
-
/* Keyframes for popover (Tailwind keyframes should be in tailwind.config.js or added via <style>) */
|
337 |
-
@keyframes popover-drop-in {
|
338 |
-
'0%': { opacity: '0', transform: 'translateY(-100%) scale(0.9)' },
|
339 |
-
'70%': { opacity: '1', transform: 'translateY(5px) scale(1.02)' },
|
340 |
-
'100%': { opacity: '1', transform: 'translateY(0) scale(1)' },
|
341 |
-
}
|
342 |
-
@keyframes popover-lift-out {
|
343 |
-
'0%': { opacity: '1', transform: 'translateY(0) scale(1)' },
|
344 |
-
'100%': { opacity: '0', transform: 'translateY(-100%) scale(0.9)' },
|
345 |
-
}
|
346 |
-
.animate-popover-open-top-center { animation: popover-drop-in 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55) forwards; }
|
347 |
-
.animate-popover-close-top-center { animation: popover-lift-out 0.3s ease-in forwards; }
|
348 |
-
|
349 |
-
/* Forcing Tailwind specific classes for logo bg as an example if not working via template literals in JS */
|
350 |
-
.bg-blue-200 { background-color: #bfdbfe !important; }
|
351 |
-
.bg-blue-300 { background-color: #93c5fd !important; }
|
352 |
-
.bg-blue-400 { background-color: #60a5fa !important; }
|
353 |
-
.bg-green-200 { background-color: #bbf7d0 !important; }
|
354 |
-
.bg-green-300 { background-color: #86efac !important; }
|
355 |
-
.bg-green-400 { background-color: #4ade80 !important; }
|
356 |
-
|
357 |
-
`}</style>
|
358 |
-
|
359 |
<LiveAPIProvider initialConfig={initialAppConfig}>
|
360 |
<div className="w-full flex flex-col items-center justify-center min-h-[90dvh] md:min-h-screen bg-background text-foreground antialiased">
|
361 |
<div className="max-w-3xl w-full flex flex-col items-center justify-center h-full relative">
|
@@ -379,9 +215,9 @@ function App() {
|
|
379 |
<div ref={notificationPopoverRef} id="notification-popover-wrapper" className="notification-popover-wrapper">
|
380 |
<div
|
381 |
id="notification-popover"
|
382 |
-
className={cn("popover-content", {
|
383 |
"open animate-popover-open-top-center": isNotificationOpen,
|
384 |
-
"animate-popover-close-top-center": !isNotificationOpen && document.getElementById('notification-popover')?.classList.contains('open'),
|
385 |
})}
|
386 |
>
|
387 |
<div className="notification-popover-text-content">
|
@@ -392,19 +228,17 @@ function App() {
|
|
392 |
|
393 |
{/* Main Media Area */}
|
394 |
<div className="w-full flex flex-col items-center justify-center h-[90dvh] bg-background top-0 left-0 relative">
|
395 |
-
{/* Video Feed (controlled by ControlTray's activeVideoStream) */}
|
396 |
<video
|
397 |
id="video-feed"
|
398 |
ref={videoRef}
|
399 |
autoPlay
|
400 |
playsInline
|
401 |
className={cn("absolute top-0 left-0 w-full h-full object-cover scale-x-[-1]", {
|
402 |
-
"hidden": !isCamActive,
|
403 |
})}
|
404 |
/>
|
405 |
-
{/* Large Logo Container */}
|
406 |
<div id="large-logo-container" className={cn("items-center justify-center w-full h-full absolute top-0 left-0", {
|
407 |
-
"flex": !isCamActive && isMicActive,
|
408 |
"hidden": isCamActive || !isMicActive
|
409 |
})}>
|
410 |
{createLogoHTML(false, !isCamActive && isMicActive)}
|
@@ -414,25 +248,21 @@ function App() {
|
|
414 |
{/* <div className="absolute bottom-24 left-4 z-20 w-1/3 max-w-md"> <Altair /> </div> */}
|
415 |
{/* <SidePanel /> */}
|
416 |
|
417 |
-
|
418 |
-
{/* Footer Controls - Passed to ControlTray */}
|
419 |
<ControlTray
|
420 |
videoRef={videoRef}
|
421 |
-
supportsVideo={true}
|
422 |
-
onVideoStreamChange={setVideoStream}
|
423 |
-
// Props جدید برای کنترل ظاهر از App.tsx
|
424 |
isAppMicActive={isMicActive}
|
425 |
onAppMicToggle={setIsMicActive}
|
426 |
isAppCamActive={isCamActive}
|
427 |
onAppCamToggle={setIsCamActive}
|
428 |
-
createLogoFunction={createLogoHTML}
|
429 |
/>
|
430 |
</div>
|
431 |
</div>
|
432 |
</div>
|
433 |
<IOSModal isOpen={showIOSModal} onClose={() => setShowIOSModal(false)} />
|
434 |
</LiveAPIProvider>
|
435 |
-
</>
|
436 |
);
|
437 |
}
|
438 |
|
|
|
12 |
*/
|
13 |
|
14 |
import React, { useEffect, useRef, useState } from "react";
|
15 |
+
import './App.scss'; // اطمینان از ایمپورت شدن فایل SCSS
|
16 |
import { LiveAPIProvider } from "./contexts/LiveAPIContext";
|
|
|
|
|
|
|
17 |
import ControlTray from "./components/control-tray/ControlTray";
|
18 |
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 |
+
// --- دستورالعمل شخصیسازی شما ---
|
|
|
|
|
|
|
|
|
24 |
const myCustomInstruction = `
|
25 |
ت1. هویت دستیار:
|
26 |
|
|
|
91 |
|
92 |
سطح سختی واژگان و جملات را بر اساس سطح کاربر (مبتدی، متوسط، پیشرفته) تنظیم کن.".
|
93 |
`.trim();
|
94 |
+
// --- ---
|
95 |
|
96 |
const initialAppConfig: LiveConfig = {
|
97 |
+
model: "models/gemini-2.0-flash-exp", // یا هر مدلی که استفاده میکنید
|
98 |
systemInstruction: {
|
99 |
parts: [{ text: myCustomInstruction }],
|
100 |
},
|
|
|
108 |
|
109 |
function App() {
|
110 |
const videoRef = useRef<HTMLVideoElement>(null);
|
111 |
+
const [videoStream, setVideoStream] = useState<MediaStream | null>(null);
|
112 |
const [showIOSModal, setShowIOSModal] = useState(false);
|
113 |
+
const [isAllowedOrigin, setIsAllowedOrigin] = useState<boolean | null>(null); // برای نمایش اولیه "در حال بررسی"
|
114 |
|
115 |
const [isMicActive, setIsMicActive] = useState(false);
|
116 |
const [isCamActive, setIsCamActive] = useState(false);
|
|
|
124 |
if (isIOS()) {
|
125 |
setShowIOSModal(true);
|
126 |
}
|
127 |
+
// برای اجازه دادن به برنامه در همه جا، isAllowedOrigin را مستقیماً true قرار میدهیم.
|
128 |
+
// نمایش اولیه "در حال بررسی دسترسی..." همچنان مفید است.
|
129 |
+
// بنابراین با یک تاخیر کوچک true میکنیم تا کاربر آن را ببیند.
|
130 |
+
const timer = setTimeout(() => {
|
131 |
+
setIsAllowedOrigin(true);
|
132 |
+
console.log("Access allowed globally.");
|
133 |
+
}, 100); // تاخیر کوتاه برای نمایش پیام "در حال بررسی"
|
134 |
+
|
135 |
+
return () => clearTimeout(timer); // پاکسازی تایمر
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
}, []);
|
137 |
|
138 |
useEffect(() => {
|
|
|
155 |
|
156 |
|
157 |
if (isAllowedOrigin === null) {
|
158 |
+
// این پیام برای مدت کوتاهی نمایش داده میشود و سپس به true تغییر میکند.
|
159 |
return <div style={{ padding: '20px', textAlign: 'center' }}>در حال بررسی دسترسی...</div>;
|
160 |
}
|
161 |
|
162 |
+
// این بخش دیگر لازم نیست چون isAllowedOrigin همیشه true خواهد شد:
|
163 |
+
// if (isAllowedOrigin === false) {
|
164 |
+
// return <div style={{ padding: '20px', textAlign: 'center', color: 'red' }}>دسترسی غیرمجاز! ... </div>;
|
165 |
+
// }
|
166 |
|
167 |
// تابع برای ساخت لوگو (ساده شده)
|
168 |
const createLogoHTML = (isMini: boolean, isActive: boolean, type: 'human' | 'ai' = 'human') => {
|
|
|
172 |
const insetBase = isMini
|
173 |
? { ping: 10, outer: 0, mid: 5, inner: 12, icon: 22 }
|
174 |
: { ping: 40, outer: 0, mid: 20, inner: 50, icon: 65 };
|
175 |
+
const bgColorBase = type === 'human' ? 'blue' : 'green'; // رنگها باید با Tailwind هماهنگ باشند
|
176 |
|
177 |
return (
|
178 |
<div className="relative" style={{ width: `${size}px`, height: `${size}px` }}>
|
179 |
+
{/* کلاسهای رنگی مانند bg-blue-200 باید توسط Tailwind شناسایی شوند */}
|
180 |
<div className={`absolute rounded-full opacity-50 animate-ping bg-${bgColorBase}-200`} style={{ inset: `${insetBase.ping}px` }}></div>
|
181 |
<div className={`absolute inset-0 rounded-full opacity-50 bg-${bgColorBase}-200`} style={{ inset: `${insetBase.outer}px` }}></div>
|
182 |
<div className={`absolute rounded-full opacity-50 bg-${bgColorBase}-300`} style={{ inset: `${insetBase.mid}px` }}></div>
|
|
|
191 |
|
192 |
|
193 |
return (
|
194 |
+
// تگ <style jsx global> از اینجا حذف شده و استایلها از App.scss میآیند
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
195 |
<LiveAPIProvider initialConfig={initialAppConfig}>
|
196 |
<div className="w-full flex flex-col items-center justify-center min-h-[90dvh] md:min-h-screen bg-background text-foreground antialiased">
|
197 |
<div className="max-w-3xl w-full flex flex-col items-center justify-center h-full relative">
|
|
|
215 |
<div ref={notificationPopoverRef} id="notification-popover-wrapper" className="notification-popover-wrapper">
|
216 |
<div
|
217 |
id="notification-popover"
|
218 |
+
className={cn("popover-content", { // کلاسهای انیمیشن از App.scss میآیند
|
219 |
"open animate-popover-open-top-center": isNotificationOpen,
|
220 |
+
"animate-popover-close-top-center": !isNotificationOpen && document.getElementById('notification-popover')?.classList.contains('open'),
|
221 |
})}
|
222 |
>
|
223 |
<div className="notification-popover-text-content">
|
|
|
228 |
|
229 |
{/* Main Media Area */}
|
230 |
<div className="w-full flex flex-col items-center justify-center h-[90dvh] bg-background top-0 left-0 relative">
|
|
|
231 |
<video
|
232 |
id="video-feed"
|
233 |
ref={videoRef}
|
234 |
autoPlay
|
235 |
playsInline
|
236 |
className={cn("absolute top-0 left-0 w-full h-full object-cover scale-x-[-1]", {
|
237 |
+
"hidden": !isCamActive,
|
238 |
})}
|
239 |
/>
|
|
|
240 |
<div id="large-logo-container" className={cn("items-center justify-center w-full h-full absolute top-0 left-0", {
|
241 |
+
"flex": !isCamActive && isMicActive,
|
242 |
"hidden": isCamActive || !isMicActive
|
243 |
})}>
|
244 |
{createLogoHTML(false, !isCamActive && isMicActive)}
|
|
|
248 |
{/* <div className="absolute bottom-24 left-4 z-20 w-1/3 max-w-md"> <Altair /> </div> */}
|
249 |
{/* <SidePanel /> */}
|
250 |
|
|
|
|
|
251 |
<ControlTray
|
252 |
videoRef={videoRef}
|
253 |
+
supportsVideo={true}
|
254 |
+
onVideoStreamChange={setVideoStream}
|
|
|
255 |
isAppMicActive={isMicActive}
|
256 |
onAppMicToggle={setIsMicActive}
|
257 |
isAppCamActive={isCamActive}
|
258 |
onAppCamToggle={setIsCamActive}
|
259 |
+
createLogoFunction={createLogoHTML}
|
260 |
/>
|
261 |
</div>
|
262 |
</div>
|
263 |
</div>
|
264 |
<IOSModal isOpen={showIOSModal} onClose={() => setShowIOSModal(false)} />
|
265 |
</LiveAPIProvider>
|
|
|
266 |
);
|
267 |
}
|
268 |
|