Ezmary commited on
Commit
bcf74e1
·
verified ·
1 Parent(s): a28e1ed

Update src/App.tsx

Browse files
Files changed (1) hide show
  1. src/App.tsx +67 -61
src/App.tsx CHANGED
@@ -19,8 +19,11 @@ 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
 
@@ -89,7 +92,13 @@ const myCustomInstruction = `
89
 
90
  در صورت درخواست کاربر، سرعت مکالمه را کند یا تند کن.
91
 
92
- سطح سختی واژگان و جملات را بر اساس سطح کاربر (مبتدی، متوسط، پیشرفته) تنظیم کن.".
 
 
 
 
 
 
93
  `.trim();
94
  // --- ---
95
 
@@ -108,9 +117,10 @@ const SvgHumanIcon = () => (
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,15 +134,12 @@ function App() {
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,16 +162,9 @@ function App() {
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') => {
169
  if (!isActive) return null;
170
  const size = isMini ? 80 : 200;
@@ -172,18 +172,16 @@ function App() {
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>
183
- <div className={`absolute rounded-full opacity-50 bg-${bgColorBase}-400`} style={{ inset: `${insetBase.inner}px` }}></div>
184
  <div className="z-10 absolute" style={{ inset: `${insetBase.icon}px`, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
185
  {type === 'human' && <SvgHumanIcon />}
186
- {/* Add AI icon if needed */}
187
  </div>
188
  </div>
189
  );
@@ -191,10 +189,9 @@ function App() {
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">
198
  {/* Header */}
199
  <div className="header-controls">
200
  <button
@@ -202,11 +199,14 @@ function App() {
202
  id="notification-button"
203
  aria-label="Notifications"
204
  className="header-button"
205
- onClick={() => setIsNotificationOpen(!isNotificationOpen)}
 
 
 
206
  >
207
  <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>
208
  </button>
209
- <div className="header-button" onClick={() => alert('Back clicked (implement navigation)')}>
210
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" 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>
211
  </div>
212
  </div>
@@ -215,7 +215,7 @@ function App() {
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
  })}
@@ -226,39 +226,45 @@ function App() {
226
  </div>
227
  </div>
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)}
245
- </div>
246
 
247
- {/* Altair Chat component - شما تصمیم می‌گیرید کجا و چگونه نمایش داده شود */}
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)} />
 
19
  import { isIOS } from "./lib/platform";
20
  import cn from "classnames";
21
  import { LiveConfig } from "./multimodal-live-types";
22
+ import { Altair } from "./components/altair/Altair"; // اگر می‌خواهید نمایش چت را داشته باشید
23
+ import SidePanel from "./components/side-panel/SidePanel"; // اگر می‌خواهید پنل کناری را داشته باشید
24
 
25
+
26
+ // --- دستورالعمل شخصی‌سازی شما (با بخش تعامل تصویری) ---
27
  const myCustomInstruction = `
28
  ت1. هویت دستیار:
29
 
 
92
 
93
  در صورت درخواست کاربر، سرعت مکالمه را کند یا تند کن.
94
 
95
+ سطح سختی واژگان و جملات را بر اساس سطح کاربر (مبتدی، متوسط، پیشرفته) تنظیم کن.
96
+
97
+ **تعامل تصویری:**
98
+ - به تصویر زنده‌ای که از کاربر دریافت می‌کنی توجه کن.
99
+ - اگر در تصویر نکته قابل توجهی وجود دارد (مانند حالت چهره، اشیاء خاص، یا محیط اطراف کاربر)، می‌توانی به آن در مکالمه اشاره کنی، البته فقط اگر مرتبط با موضوع صحبت باشد یا کاربر از تو بخواهد.
100
+ - اگر کاربر سوالی در مورد چیزی که در تصویر می‌بیند پرسید، سعی کن بر اساس تصویر پاسخ دهی.
101
+ - هدف اصلی، کمک به یادگیری زبان است، پس تعامل تصویری باید در خدمت این هدف باشد.
102
  `.trim();
103
  // --- ---
104
 
 
117
 
118
  function App() {
119
  const videoRef = useRef<HTMLVideoElement>(null);
120
+ // videoStream دیگر مستقیماً توسط App مدیریت نمی‌شود، بلکه توسط ControlTray از طریق onVideoStreamChange
121
+ // const [videoStream, setVideoStream] = useState<MediaStream | null>(null);
122
  const [showIOSModal, setShowIOSModal] = useState(false);
123
+ const [isAllowedOrigin, setIsAllowedOrigin] = useState<boolean | null>(null);
124
 
125
  const [isMicActive, setIsMicActive] = useState(false);
126
  const [isCamActive, setIsCamActive] = useState(false);
 
134
  if (isIOS()) {
135
  setShowIOSModal(true);
136
  }
 
 
 
137
  const timer = setTimeout(() => {
138
  setIsAllowedOrigin(true);
139
+ console.log("Access allowed globally by default in App.tsx.");
140
+ }, 100);
141
 
142
+ return () => clearTimeout(timer);
143
  }, []);
144
 
145
  useEffect(() => {
 
162
 
163
 
164
  if (isAllowedOrigin === null) {
 
165
  return <div style={{ padding: '20px', textAlign: 'center' }}>در حال بررسی دسترسی...</div>;
166
  }
 
 
 
 
 
167
 
 
168
  const createLogoHTML = (isMini: boolean, isActive: boolean, type: 'human' | 'ai' = 'human') => {
169
  if (!isActive) return null;
170
  const size = isMini ? 80 : 200;
 
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';
176
 
177
  return (
178
  <div className="relative" style={{ width: `${size}px`, height: `${size}px` }}>
179
+ <div className={`absolute rounded-full opacity-50 animate-ping bg-${bgColorBase}-200 dark:bg-${bgColorBase}-700`} style={{ inset: `${insetBase.ping}px` }}></div>
180
+ <div className={`absolute inset-0 rounded-full opacity-50 bg-${bgColorBase}-200 dark:bg-${bgColorBase}-700`} style={{ inset: `${insetBase.outer}px` }}></div>
181
+ <div className={`absolute rounded-full opacity-50 bg-${bgColorBase}-300 dark:bg-${bgColorBase}-600`} style={{ inset: `${insetBase.mid}px` }}></div>
182
+ <div className={`absolute rounded-full opacity-50 bg-${bgColorBase}-400 dark:bg-${bgColorBase}-500`} style={{ inset: `${insetBase.inner}px` }}></div>
 
183
  <div className="z-10 absolute" style={{ inset: `${insetBase.icon}px`, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
184
  {type === 'human' && <SvgHumanIcon />}
 
185
  </div>
186
  </div>
187
  );
 
189
 
190
 
191
  return (
 
192
  <LiveAPIProvider initialConfig={initialAppConfig}>
193
+ <div className="w-full flex flex-col items-center justify-center min-h-screen bg-background text-foreground antialiased"> {/* min-h-screen for full height */}
194
+ <div className="max-w-3xl w-full flex flex-col items-center justify-center h-full relative"> {/* h-full */}
195
  {/* Header */}
196
  <div className="header-controls">
197
  <button
 
199
  id="notification-button"
200
  aria-label="Notifications"
201
  className="header-button"
202
+ onClick={(e) => {
203
+ e.stopPropagation(); // برای جلوگیری از بسته شدن بلافاصله توسط کلیک بیرون
204
+ setIsNotificationOpen(!isNotificationOpen);
205
+ }}
206
  >
207
  <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>
208
  </button>
209
+ <div className="header-button" onClick={() => alert('Back clicked (navigation logic)')}>
210
  <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" 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>
211
  </div>
212
  </div>
 
215
  <div ref={notificationPopoverRef} id="notification-popover-wrapper" className="notification-popover-wrapper">
216
  <div
217
  id="notification-popover"
218
+ className={cn("popover-content", {
219
  "open animate-popover-open-top-center": isNotificationOpen,
220
  "animate-popover-close-top-center": !isNotificationOpen && document.getElementById('notification-popover')?.classList.contains('open'),
221
  })}
 
226
  </div>
227
  </div>
228
 
229
+ {/* Main Media Area and Chat Area */}
230
+ <div className="flex flex-col md:flex-row w-full h-[calc(100vh-100px)] mt-[60px] mb-[40px]"> {/* Adjust height and margins as needed */}
231
+ {/* SidePanel - اگر می‌خواهید پنل کناری چت را داشته باشید */}
232
+ <div className="w-full md:w-1/3 p-2 overflow-y-auto">
233
+ <SidePanel />
234
+ <Altair />
235
+ </div>
 
 
 
 
 
 
 
 
 
 
236
 
237
+ {/* Video Area */}
238
+ <div className="w-full md:w-2/3 flex flex-col items-center justify-center bg-background relative"> {/* Removed h-[90dvh] to fit within flex layout */}
239
+ <video
240
+ id="video-feed"
241
+ ref={videoRef}
242
+ autoPlay
243
+ playsInline
244
+ className={cn("absolute top-0 left-0 w-full h-full object-cover scale-x-[-1]", {
245
+ "hidden": !isCamActive,
246
+ })}
247
+ />
248
+ <div id="large-logo-container" className={cn("items-center justify-center w-full h-full absolute top-0 left-0", {
249
+ "flex": !isCamActive && isMicActive,
250
+ "hidden": isCamActive || !isMicActive
251
+ })}>
252
+ {createLogoHTML(false, !isCamActive && isMicActive)}
253
+ </div>
254
+ </div>
255
  </div>
256
+
257
+ <ControlTray
258
+ videoRef={videoRef}
259
+ supportsVideo={true}
260
+ onVideoStreamChange={(stream) => { /* App.tsx no longer needs direct videoStream state */ }}
261
+ isAppMicActive={isMicActive}
262
+ onAppMicToggle={setIsMicActive}
263
+ isAppCamActive={isAppCamActive}
264
+ onAppCamToggle={setIsCamActive}
265
+ createLogoFunction={createLogoHTML}
266
+ />
267
+
268
  </div>
269
  </div>
270
  <IOSModal isOpen={showIOSModal} onClose={() => setShowIOSModal(false)} />