Ezmary commited on
Commit
f5d2206
·
verified ·
1 Parent(s): 3a18fb8

Update src/App.tsx

Browse files
Files changed (1) hide show
  1. src/App.tsx +58 -68
src/App.tsx CHANGED
@@ -12,20 +12,17 @@ limitations under the License.
12
  */
13
 
14
  import React, { useEffect, useRef, useState, useCallback } from "react";
15
- import "./App.scss"; // Updated SCSS file
16
  import { LiveAPIProvider, useLiveAPIContext } from "./contexts/LiveAPIContext";
17
- // SidePanel and Altair are part of the original Gemini UI.
18
- // You need to decide if/how they fit into the new UI.
19
- // For now, we'll include them but they might need styling or to be conditionally rendered.
20
  // import SidePanel from "./components/side-panel/SidePanel"; // اگر استفاده نمیکنید کامنت کنید
21
  import { Altair } from "./components/altair/Altair";
22
- import ControlTray from "./components/control-tray/ControlTray"; // This will be our new footer
23
  import { IOSModal } from "./components/ios-modal/IOSModal";
24
  import { isIOS } from "./lib/platform";
25
  import { LiveConfig } from "./multimodal-live-types";
26
 
27
- // --- 👇 دامنه مجاز خودتان را اینجا قرار دهید (با https یا http) 👇 ---
28
- const ALLOWED_ORIGIN = "https://www.aisada.ir"; // یا "http://www.aisada.ir"; // یا "https://aisada.ir"; // یا "http://aisada.ir" اگر سایتتان http است
29
  // --- 👆 ---
30
 
31
  // --- 👇 دستورالعمل شخصی‌سازی شما (بدون تغییر) 👇 ---
@@ -101,16 +98,13 @@ const myCustomInstruction = `
101
  `.trim();
102
  // --- 👆 ---
103
 
104
- // --- 👇 تنظیمات اولیه (بدون تغییر) 👇 ---
105
  const initialAppConfig: LiveConfig = {
106
- model: "models/gemini-2.0-flash-exp", // یا هر مدلی که استفاده می‌کنید
107
  systemInstruction: {
108
  parts: [{ text: myCustomInstruction }]
109
  }
110
  };
111
- // --- 👆 ---
112
 
113
- // SVG Icons (as React components or functions returning JSX)
114
  const NotificationIcon = () => (
115
  <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>
116
  );
@@ -124,19 +118,16 @@ const HumanIconSVG = ({ width = 70, height = 70 }: { width?: number, height?: nu
124
  );
125
 
126
  const LogoDisplay = ({ isMini, isHumanActive }: { isMini: boolean, isHumanActive: boolean }) => {
127
- if (!isHumanActive) return null; // Simplified: only show human logo
128
-
129
  const size = isMini ? 80 : 200;
130
  const iconSize = isMini ? 35 : 70;
131
  const insetBase = isMini
132
- ? { ping: 10, outer: 0, mid: 5, inner: 12, icon: 22.5 } // Centered icon
133
- : { ping: 40, outer: 0, mid: 20, inner: 50, icon: 65 }; // Centered icon
134
- const bgColorBase = 'blue'; // Simplified to blue for human
135
 
136
  return (
137
  <div className="relative" style={{ width: `${size}px`, height: `${size}px` }}>
138
- {/* These divs are for Tailwind JIT, not strictly needed if classes are used directly */}
139
- {/* <div className="hidden bg-blue-200 bg-blue-300 bg-blue-400"></div> */}
140
  <div className={`absolute rounded-full opacity-50 animate-ping bg-${bgColorBase}-200 dark:bg-${bgColorBase}-700`} style={{ inset: `${insetBase.ping}px` }}></div>
141
  <div className={`absolute rounded-full opacity-50 bg-${bgColorBase}-200 dark:bg-${bgColorBase}-700`} style={{ inset: `${insetBase.outer}px` }}></div>
142
  <div className={`absolute rounded-full opacity-50 bg-${bgColorBase}-300 dark:bg-${bgColorBase}-600`} style={{ inset: `${insetBase.mid}px` }}></div>
@@ -148,54 +139,57 @@ const LogoDisplay = ({ isMini, isHumanActive }: { isMini: boolean, isHumanActive
148
  );
149
  };
150
 
151
-
152
  function AppContent() {
153
  const videoRef = useRef<HTMLVideoElement>(null);
154
- const [videoStream, setVideoStream] = useState<MediaStream | null>(null);
155
  const [showIOSModal, setShowIOSModal] = useState(false);
156
- const [isAllowedOrigin, setIsAllowedOrigin] = useState<boolean | null>(null);
157
- const [isNotificationOpen, setIsNotificationOpen] = useState(false); // <--- اصلاح در این خط
158
  const notificationPopoverRef = useRef<HTMLDivElement>(null);
159
  const notificationButtonRef = useRef<HTMLButtonElement>(null);
160
 
161
- // States for new UI, to be controlled by ControlTray or LiveAPIContext
162
- const [isMicActive, setIsMicActive] = useState(false); // Is microphone capturing
163
- const [isCamActive, setIsCamActive] = useState(false); // Is camera capturing
164
 
165
- const { connected, volume } = useLiveAPIContext(); // Get connection status and volume
166
 
167
  useEffect(() => {
168
  if (isIOS()) {
169
  setShowIOSModal(true);
170
  }
171
 
 
172
  try {
173
- if (window.self !== window.top) {
174
  if (window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0) {
175
  const parentOrigin = window.location.ancestorOrigins[0];
176
- if (parentOrigin === ALLOWED_ORIGIN) {
177
- setIsAllowedOrigin(true);
 
 
178
  } else {
179
- console.warn(`Blocked load from origin: ${parentOrigin}`);
180
- setIsAllowedOrigin(false);
181
  }
182
  } else {
183
- console.warn("Cannot verify parent origin (ancestorOrigins not available/empty). Blocking.");
184
- setIsAllowedOrigin(false);
 
 
185
  }
186
- } else {
187
- console.warn("App loaded directly, not in an iframe. Blocking.");
188
- setIsAllowedOrigin(false); // For security, must be in iframe from allowed origin
189
  }
190
  } catch (e) {
191
- console.error("Cross-origin access error, cannot verify parent. Blocking.", e);
192
- setIsAllowedOrigin(false);
 
 
 
 
193
  }
 
194
 
195
- // Dark mode toggle for testing (can be removed or integrated with a theme switcher)
196
- // document.documentElement.classList.add('dark');
197
-
198
- // Click outside listener for notification popover
199
  const handleClickOutside = (event: MouseEvent) => {
200
  if (isNotificationOpen &&
201
  notificationPopoverRef.current &&
@@ -213,10 +207,8 @@ function AppContent() {
213
 
214
  }, [isNotificationOpen]);
215
 
216
-
217
  const handleVideoStreamChange = useCallback((stream: MediaStream | null) => {
218
- setVideoStream(stream);
219
- setIsCamActive(!!stream); // Update cam active state based on stream
220
  if (videoRef.current) {
221
  videoRef.current.srcObject = stream;
222
  if (stream) {
@@ -224,32 +216,35 @@ function AppContent() {
224
  }
225
  }
226
  }, []);
227
-
228
  useEffect(() => {
229
- // This useEffect was intended for CSS --volume for mic pulse,
230
- // but ControlTray.tsx now handles its own inVolume for that.
231
- // This 'volume' from useLiveAPIContext is usually output volume.
232
- // If you need to visualize output volume elsewhere, you can use it.
233
  }, [volume]);
234
 
235
-
236
  if (isAllowedOrigin === null) {
237
  return <div style={{ padding: '20px', textAlign: 'center' }}>در حال بررسی دسترسی...</div>;
238
  }
239
 
 
240
  if (isAllowedOrigin === false) {
241
- return <div style={{ padding: '20px', textAlign: 'center', color: 'red' }}>دسترسی غیرمجاز! اگر چت صوتی و تصویری برای شما باز نمیشود این لینک رو با مرورگر کروم باز کنید و همچنین مرورگر کروم رو به عنوان مرورگر پیشفرض گوشی خود قرار دهید تا هر بار زدن روی دکمه شروع داخل برنامه لینک با مرورگر کروم باز بشه، برای پیشفرض قرار دادن مرورگر کروم وارد تنظیمات گوشی خود شوید قسمت برنامه ها ، مدیریت برنامه ها رو کلیک کنید بالای صفحه روی سه نقطه بزنید و تنظیمات بیشتر رو انتخاب کنید بعدا وارد قسمت برنامه های پیش فرض شوید و مرورگر کروم رو به عنوان مرورگر پیشفرض خود قرار دهید، اگر مشکلی بود حتماً به پشتیبانی برنامه پیام بفرستید</div>;
 
 
 
 
 
 
 
242
  }
 
243
 
 
244
  return (
245
- <div className="App"> {/* Main container from original Gemini App */}
246
- {/* <SidePanel /> // اگر استفاده نمیکنید کامنت کنید یا حذف کنید */}
247
- <main> {/* Original main, Altair will go here */}
248
- <div className="main-app-area"> {/* For Altair and video */}
249
- {/* Altair is the chat bubbles. Position it as needed. */}
250
  <Altair />
251
-
252
- {/* Header */}
253
  <div className="header-controls">
254
  <button
255
  id="notification-button"
@@ -268,7 +263,6 @@ function AppContent() {
268
  </button>
269
  </div>
270
 
271
- {/* Notification Popover */}
272
  <div id="notification-popover-wrapper" className="notification-popover-wrapper">
273
  <div
274
  id="notification-popover"
@@ -281,7 +275,6 @@ function AppContent() {
281
  </div>
282
  </div>
283
 
284
- {/* Media Area (Video feed and Logos) */}
285
  <div className="media-toggle-area">
286
  <video
287
  id="video-feed"
@@ -290,23 +283,20 @@ function AppContent() {
290
  playsInline
291
  className={`video-feed ${!isCamActive ? 'hidden' : ''}`}
292
  />
293
- {!isCamActive && isMicActive && ( // Show large logo if mic is on and cam is off
294
  <div id="large-logo-container" className="large-logo-container">
295
  <LogoDisplay isMini={false} isHumanActive={true} />
296
  </div>
297
  )}
298
- {/* The small logo is part of the footer logic in ControlTray */}
299
  </div>
300
  </div>
301
 
302
- {/* Footer Controls */}
303
  <ControlTray
304
- videoRef={videoRef} // Pass videoRef for video operations
305
  onVideoStreamChange={handleVideoStreamChange}
306
- // Pass setters for App to know mic/cam state for UI logic (e.g., logos)
307
  setIsMicActive={setIsMicActive}
308
- setIsCamActiveExternal={setIsCamActive} // Renamed to avoid conflict if ControlTray has its own isCamActive
309
- isCamActiveApp={isCamActive} // Pass down App's view of cam state
310
  />
311
  </main>
312
 
 
12
  */
13
 
14
  import React, { useEffect, useRef, useState, useCallback } from "react";
15
+ import "./App.scss";
16
  import { LiveAPIProvider, useLiveAPIContext } from "./contexts/LiveAPIContext";
 
 
 
17
  // import SidePanel from "./components/side-panel/SidePanel"; // اگر استفاده نمیکنید کامنت کنید
18
  import { Altair } from "./components/altair/Altair";
19
+ import ControlTray from "./components/control-tray/ControlTray";
20
  import { IOSModal } from "./components/ios-modal/IOSModal";
21
  import { isIOS } from "./lib/platform";
22
  import { LiveConfig } from "./multimodal-live-types";
23
 
24
+ // --- 👇 دامنه **ممنوع** شما 👇 ---
25
+ const FORBIDDEN_ORIGIN = "https://www.aisada.ir"; // یا "http://www.aisada.ir";
26
  // --- 👆 ---
27
 
28
  // --- 👇 دستورالعمل شخصی‌سازی شما (بدون تغییر) 👇 ---
 
98
  `.trim();
99
  // --- 👆 ---
100
 
 
101
  const initialAppConfig: LiveConfig = {
102
+ model: "models/gemini-2.0-flash-exp",
103
  systemInstruction: {
104
  parts: [{ text: myCustomInstruction }]
105
  }
106
  };
 
107
 
 
108
  const NotificationIcon = () => (
109
  <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>
110
  );
 
118
  );
119
 
120
  const LogoDisplay = ({ isMini, isHumanActive }: { isMini: boolean, isHumanActive: boolean }) => {
121
+ if (!isHumanActive) return null;
 
122
  const size = isMini ? 80 : 200;
123
  const iconSize = isMini ? 35 : 70;
124
  const insetBase = isMini
125
+ ? { ping: 10, outer: 0, mid: 5, inner: 12, icon: 22.5 }
126
+ : { ping: 40, outer: 0, mid: 20, inner: 50, icon: 65 };
127
+ const bgColorBase = 'blue';
128
 
129
  return (
130
  <div className="relative" style={{ width: `${size}px`, height: `${size}px` }}>
 
 
131
  <div className={`absolute rounded-full opacity-50 animate-ping bg-${bgColorBase}-200 dark:bg-${bgColorBase}-700`} style={{ inset: `${insetBase.ping}px` }}></div>
132
  <div className={`absolute rounded-full opacity-50 bg-${bgColorBase}-200 dark:bg-${bgColorBase}-700`} style={{ inset: `${insetBase.outer}px` }}></div>
133
  <div className={`absolute rounded-full opacity-50 bg-${bgColorBase}-300 dark:bg-${bgColorBase}-600`} style={{ inset: `${insetBase.mid}px` }}></div>
 
139
  );
140
  };
141
 
 
142
  function AppContent() {
143
  const videoRef = useRef<HTMLVideoElement>(null);
 
144
  const [showIOSModal, setShowIOSModal] = useState(false);
145
+ const [isAllowedOrigin, setIsAllowedOrigin] = useState<boolean | null>(null); // true: allowed, false: blocked
146
+ const [isNotificationOpen, setIsNotificationOpen] = useState(false);
147
  const notificationPopoverRef = useRef<HTMLDivElement>(null);
148
  const notificationButtonRef = useRef<HTMLButtonElement>(null);
149
 
150
+ const [isMicActive, setIsMicActive] = useState(false);
151
+ const [isCamActive, setIsCamActive] = useState(false);
 
152
 
153
+ const { connected, volume } = useLiveAPIContext();
154
 
155
  useEffect(() => {
156
  if (isIOS()) {
157
  setShowIOSModal(true);
158
  }
159
 
160
+ // --- 👇 منطق بررسی دامنه مجاز (معکوس شده) 👇 ---
161
  try {
162
+ if (window.self !== window.top) { // App is in an iframe
163
  if (window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0) {
164
  const parentOrigin = window.location.ancestorOrigins[0];
165
+ console.log("Parent Origin:", parentOrigin);
166
+ if (parentOrigin === FORBIDDEN_ORIGIN) {
167
+ console.warn(`Blocked load from forbidden origin: ${parentOrigin}`);
168
+ setIsAllowedOrigin(false); // Block if from forbidden origin
169
  } else {
170
+ console.log(`Allowed load from origin: ${parentOrigin}`);
171
+ setIsAllowedOrigin(true); // Allow if from any other origin
172
  }
173
  } else {
174
+ // Cannot verify parent origin, but it's in an iframe.
175
+ // For "allow everywhere except specific site", we can assume allow here.
176
+ console.warn("Cannot verify parent origin (ancestorOrigins not available/empty), but in iframe. Allowing.");
177
+ setIsAllowedOrigin(true);
178
  }
179
+ } else { // App loaded directly, not in an iframe
180
+ console.log("App loaded directly. Allowing.");
181
+ setIsAllowedOrigin(true); // Allow if loaded directly
182
  }
183
  } catch (e) {
184
+ // Cross-origin access error. This usually happens if the iframe has sandbox attributes
185
+ // that prevent accessing ancestorOrigins. In this "allow everywhere except" scenario,
186
+ // it's safer to allow if we can't determine, or block if you prefer higher security.
187
+ // Let's allow for wider compatibility, assuming it's not the forbidden one.
188
+ console.error("Cross-origin access error, cannot verify parent. Allowing by default.", e);
189
+ setIsAllowedOrigin(true);
190
  }
191
+ // --- 👆 ---
192
 
 
 
 
 
193
  const handleClickOutside = (event: MouseEvent) => {
194
  if (isNotificationOpen &&
195
  notificationPopoverRef.current &&
 
207
 
208
  }, [isNotificationOpen]);
209
 
 
210
  const handleVideoStreamChange = useCallback((stream: MediaStream | null) => {
211
+ setIsCamActive(!!stream);
 
212
  if (videoRef.current) {
213
  videoRef.current.srcObject = stream;
214
  if (stream) {
 
216
  }
217
  }
218
  }, []);
219
+
220
  useEffect(() => {
221
+ // This effect is for potential output volume visualization, not currently used for mic pulse
 
 
 
222
  }, [volume]);
223
 
 
224
  if (isAllowedOrigin === null) {
225
  return <div style={{ padding: '20px', textAlign: 'center' }}>در حال بررسی دسترسی...</div>;
226
  }
227
 
228
+ // --- 👇 رندر شرطی با پیام خطای جدید 👇 ---
229
  if (isAllowedOrigin === false) {
230
+ // این پیام زمانی نمایش داده می‌شود که برنامه در iframe سایت ممنوعه (aisada.ir) بارگذاری شود.
231
+ return (
232
+ <div style={{ padding: '20px', textAlign: 'center', color: 'red', direction: 'rtl' }}>
233
+ دسترسی به این محتوا از دامنه شما ({FORBIDDEN_ORIGIN}) مجاز نمی‌باشد.
234
+ <br />
235
+ لطفا این اسپیس را مستقیما یا از طریق دامنه‌های مجاز دیگر باز کنید.
236
+ </div>
237
+ );
238
  }
239
+ // --- 👆 ---
240
 
241
+ // اگر isAllowedOrigin === true بود، برنامه اصلی رندر می‌شود
242
  return (
243
+ <div className="App">
244
+ {/* <SidePanel /> */}
245
+ <main>
246
+ <div className="main-app-area">
 
247
  <Altair />
 
 
248
  <div className="header-controls">
249
  <button
250
  id="notification-button"
 
263
  </button>
264
  </div>
265
 
 
266
  <div id="notification-popover-wrapper" className="notification-popover-wrapper">
267
  <div
268
  id="notification-popover"
 
275
  </div>
276
  </div>
277
 
 
278
  <div className="media-toggle-area">
279
  <video
280
  id="video-feed"
 
283
  playsInline
284
  className={`video-feed ${!isCamActive ? 'hidden' : ''}`}
285
  />
286
+ {!isCamActive && isMicActive && (
287
  <div id="large-logo-container" className="large-logo-container">
288
  <LogoDisplay isMini={false} isHumanActive={true} />
289
  </div>
290
  )}
 
291
  </div>
292
  </div>
293
 
 
294
  <ControlTray
295
+ videoRef={videoRef}
296
  onVideoStreamChange={handleVideoStreamChange}
 
297
  setIsMicActive={setIsMicActive}
298
+ setIsCamActiveExternal={setIsCamActive}
299
+ isCamActiveApp={isCamActive}
300
  />
301
  </main>
302