Spaces:
Running
Running
Update src/App.tsx
Browse files- src/App.tsx +35 -24
src/App.tsx
CHANGED
@@ -1,4 +1,16 @@
|
|
1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
import React, { useEffect, useRef, useState } from "react";
|
3 |
import './App.scss';
|
4 |
import { LiveAPIProvider, useLiveAPIContext } from "./contexts/LiveAPIContext";
|
@@ -106,7 +118,21 @@ const SvgReferenceMicrophoneIcon = () => (
|
|
106 |
</svg>
|
107 |
);
|
108 |
|
109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
110 |
isMicActive, isCamActive, setIsMicActive, setIsCamActive, createLogoFunction,
|
111 |
videoRef, notificationPopoverRef, notificationButtonRef, isNotificationOpen, setIsNotificationOpen
|
112 |
}) => {
|
@@ -116,7 +142,6 @@ const AppInternalLogic: React.FC<{ /* ...props... */ }> = ({ /* ...props... */
|
|
116 |
return (
|
117 |
<div className="w-full flex flex-col items-center justify-center min-h-screen text-foreground antialiased">
|
118 |
<div className="main-wrapper max-w-3xl w-full flex flex-col items-center justify-center h-full relative">
|
119 |
-
{/* Header and Popover ... */}
|
120 |
<div className="header-controls">
|
121 |
<div id="notification-trigger-container">
|
122 |
<button ref={notificationButtonRef} id="notification-button" aria-label="Notifications" className="header-button" onClick={(e) => { e.stopPropagation(); setIsNotificationOpen(!isNotificationOpen); }}>
|
@@ -134,17 +159,15 @@ const AppInternalLogic: React.FC<{ /* ...props... */ }> = ({ /* ...props... */
|
|
134 |
<div className="notification-popover-text-content"> مدلهای هوش مصنوعی میتوانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید. </div>
|
135 |
</div>
|
136 |
</div>
|
137 |
-
|
138 |
<div className="media-area w-full flex flex-col items-center justify-center flex-grow relative">
|
139 |
<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 })} />
|
140 |
-
{/* *** MODIFIED: Ensure large logo is displayed correctly *** */}
|
141 |
{isMicActive && !isCamActive && (
|
142 |
<div id="large-logo-container" className="large-logo-container-style">
|
143 |
{createLogoFunction(false, true, 'human', false)}
|
144 |
</div>
|
145 |
)}
|
146 |
</div>
|
147 |
-
<ControlTray
|
148 |
videoRef={videoRef} supportsVideo={true} onVideoStreamChange={() => {}}
|
149 |
isAppMicActive={isMicActive} onAppMicToggle={setIsMicActive}
|
150 |
isAppCamActive={isCamActive} onAppCamToggle={setIsCamActive}
|
@@ -156,13 +179,13 @@ const AppInternalLogic: React.FC<{ /* ...props... */ }> = ({ /* ...props... */
|
|
156 |
);
|
157 |
};
|
158 |
|
159 |
-
const logoColorConfig = {
|
160 |
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", },
|
161 |
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", },
|
162 |
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", }
|
163 |
};
|
164 |
|
165 |
-
function App() {
|
166 |
const videoRef = useRef<HTMLVideoElement>(null);
|
167 |
const [showIOSModal, setShowIOSModal] = useState(false);
|
168 |
const [isAllowedOrigin, setIsAllowedOrigin] = useState<boolean | null>(null);
|
@@ -175,35 +198,23 @@ function App() { /* ... (state ها و useEffect ها بدون تغییر) ... *
|
|
175 |
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]);
|
176 |
if (isAllowedOrigin === null) { return <div style={{ padding: '20px', textAlign: 'center' }}>در حال بررسی دسترسی...</div>; }
|
177 |
|
178 |
-
// *** MODIFIED createLogoFunction for complete animation ***
|
179 |
const createLogoFunction = (isMini: boolean, isActive: boolean, type: 'human' | 'ai' = 'human', forFooter: boolean = false) => {
|
180 |
if (!isActive) return null;
|
181 |
-
|
182 |
const colorKey = type === 'human' ? 'blue' : (type === 'ai' ? 'green' : 'gray');
|
183 |
const currentColors = logoColorConfig[colorKey as keyof typeof logoColorConfig] || logoColorConfig.gray;
|
184 |
-
|
185 |
const size = isMini ? 80 : 200;
|
186 |
const iconDisplaySize = isMini ? 35 : 70;
|
187 |
const iconInset = (size - iconDisplaySize) / 2;
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
const insetsForRings = isMini
|
192 |
-
? { ping: 10, outer: 0, mid: 5, inner: 12 } // مقادیر HTML برای لوگوی کوچک
|
193 |
-
: { ping: 40, outer: 0, mid: 20, inner: 50 }; // مقادیر HTML برای لوگوی بزرگ
|
194 |
-
|
195 |
const IconComponent = type === 'human' ? SvgHumanIcon : null;
|
196 |
-
|
197 |
return (
|
198 |
-
// کلاس logo-animation-wrapper در App.scss استایلهای position:relative و flex را دارد
|
199 |
<div className={cn("logo-animation-wrapper", {"for-footer": forFooter})} style={{ width: `${size}px`, height: `${size}px` }}>
|
200 |
-
{/* حلقههای انیمیشن با inset های صحیح */}
|
201 |
<div className={`absolute rounded-full opacity-50 animate-ping ${currentColors.ping}`} style={{ inset: `${insetsForRings.ping}px` }}></div>
|
202 |
<div className={`absolute rounded-full opacity-50 ${currentColors.outer}`} style={{ inset: `${insetsForRings.outer}px` }}></div>
|
203 |
<div className={`absolute rounded-full opacity-50 ${currentColors.mid}`} style={{ inset: `${insetsForRings.mid}px` }}></div>
|
204 |
<div className={`absolute rounded-full opacity-50 ${currentColors.inner}`} style={{ inset: `${insetsForRings.inner}px` }}></div>
|
205 |
-
|
206 |
-
{/* کانتینر آیکون */}
|
207 |
<div className="z-10 absolute flex items-center justify-center" style={{ inset: `${iconInset}px`, width: `${iconDisplaySize}px`, height: `${iconDisplaySize}px` }}>
|
208 |
{IconComponent && <IconComponent />}
|
209 |
</div>
|
@@ -213,7 +224,7 @@ function App() { /* ... (state ها و useEffect ها بدون تغییر) ... *
|
|
213 |
|
214 |
return (
|
215 |
<LiveAPIProvider initialConfig={initialAppConfig}>
|
216 |
-
<AppInternalLogic
|
217 |
isMicActive={isMicActive} setIsMicActive={setIsMicActive}
|
218 |
isCamActive={isCamActive} setIsCamActive={setIsCamActive}
|
219 |
createLogoFunction={createLogoFunction}
|
|
|
1 |
+
/**
|
2 |
+
Copyright 2024 Google LLC
|
3 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4 |
+
you may not use this file except in compliance with the License.
|
5 |
+
You may obtain a copy of the License at
|
6 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
7 |
+
Unless required by applicable law or agreed to in writing, software
|
8 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
9 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
10 |
+
See the License for the specific language governing permissions and
|
11 |
+
limitations under the License.
|
12 |
+
*/
|
13 |
+
|
14 |
import React, { useEffect, useRef, useState } from "react";
|
15 |
import './App.scss';
|
16 |
import { LiveAPIProvider, useLiveAPIContext } from "./contexts/LiveAPIContext";
|
|
|
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 |
}) => {
|
|
|
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); }}>
|
|
|
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}
|
|
|
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);
|
|
|
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>
|
|
|
224 |
|
225 |
return (
|
226 |
<LiveAPIProvider initialConfig={initialAppConfig}>
|
227 |
+
<AppInternalLogic
|
228 |
isMicActive={isMicActive} setIsMicActive={setIsMicActive}
|
229 |
isCamActive={isCamActive} setIsCamActive={setIsCamActive}
|
230 |
createLogoFunction={createLogoFunction}
|