Hamed744 commited on
Commit
03d327c
·
verified ·
1 Parent(s): d429c26

Update src/App.tsx

Browse files
Files changed (1) hide show
  1. src/App.tsx +50 -39
src/App.tsx CHANGED
@@ -1,4 +1,4 @@
1
- // src/App.tsx (نسخه نهایی با رفع کامل مشکل ترجمه مودال)
2
  import React, { useEffect, useRef, useState, FC } from "react";
3
  import './App.scss';
4
  import { AppProvider, useAppContext, PersonalityType, PersonalityInstructions } from "./contexts/AppContext";
@@ -10,7 +10,7 @@ import Logo from "./components/logo/Logo";
10
  import { LiveConfig } from "./multimodal-live-types";
11
  import { speakers } from "./data/speakers";
12
 
13
- // کامپوننت مودال
14
  interface CustomModalProps { isOpen: boolean; onClose: () => void; onSave: (name: string, instructions: string) => void; initialName: string; initialInstructions: string; }
15
  const CustomModal: FC<CustomModalProps> = ({ isOpen, onClose, onSave, initialName, initialInstructions }) => {
16
  const [name, setName] = useState(initialName);
@@ -18,17 +18,26 @@ const CustomModal: FC<CustomModalProps> = ({ isOpen, onClose, onSave, initialNam
18
  useEffect(() => { if (isOpen) { setName(initialName); setInstructions(initialInstructions); } }, [isOpen, initialName, initialInstructions]);
19
  if (!isOpen) return null;
20
  const handleSave = () => { if (name.trim() && instructions.trim()) { onSave(name.trim(), instructions.trim()); onClose(); } else { alert("لطفا نام و دستورالعمل‌ها را وارد کنید."); } };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  return (
22
- // ✅ تغییر اصلی اینجاست: اضافه کردن translate="no" به کل کانتینر مودال
23
- <div className="modal-overlay" onClick={onClose} translate="no">
24
- <div className="modal-content" onClick={(e) => e.stopPropagation()}>
25
- <div className="modal-header"><h3>ساخت شخصیت اختصاصی</h3><button className="close-button" onClick={onClose}>×</button></div>
26
- <div className="modal-body">
27
- <div className="form-group"><label htmlFor="name">نام شما</label><input id="name" type="text" placeholder="مثلا: حامد" value={name} onChange={(e) => setName(e.target.value)} /></div>
28
- <div className="form-group"><label htmlFor="instructions">دستورالعمل‌ها</label><textarea id="instructions" placeholder="مثلا: خیلی خودمونی صحبت کن..." value={instructions} onChange={(e) => setInstructions(e.target.value)} rows={6} /><small>اینجا فقط سبک صحبت کردن را مشخص کنید.</small></div>
29
- </div>
30
- <div className="modal-footer"><button className="save-button" onClick={handleSave}>ذخیره</button></div>
31
- </div>
32
  </div>
33
  );
34
  };
@@ -49,39 +58,23 @@ const PersonalityMenu: React.FC<{ isOpen: boolean; onClose: () => void; onSelect
49
  return (
50
  <div ref={menuRef} className="personality-popover-wrapper open" translate="no">
51
  <div className="popover-content">
52
- <div className={cn("selected-voice-display", { speaking: volume > 0.01 })}>
53
- <div className="voice-image-wrapper"><img src={selectedSpeaker.imgUrl} alt={selectedSpeaker.name} /></div>
54
- <h3>{selectedSpeaker.name}</h3><p>{selectedSpeaker.desc}</p>
55
- </div>
56
  <hr className="menu-divider" />
57
  <h4 className="menu-subtitle">انتخاب شخصیت</h4>
58
- <ul>
59
- {(Object.keys(personalityIcons) as PersonalityType[]).map(key => (
60
- <li key={key} className={cn({ active: selectedPersonality === key })} onClick={() => onSelectPersonality(key)}>
61
- <div><span className="material-symbols-outlined">{personalityIcons[key]}</span>{personalityLabels[key]}</div>
62
- {selectedPersonality === key && <span className="material-symbols-outlined tick">done</span>}
63
- </li>
64
- ))}
65
- </ul>
66
  <hr className="menu-divider" />
67
  <h4 className="menu-subtitle">انتخاب گوینده</h4>
68
- <div className="voice-grid">
69
- {speakers.map(speaker => (
70
- <div key={speaker.id} className={cn("voice-card", { active: selectedVoice === speaker.id })} onClick={() => changeVoice(speaker.id)} title={speaker.name}>
71
- <img src={speaker.imgUrl} alt={speaker.name} loading="lazy" />
72
- <div className="voice-name">{speaker.name.split(' ')[0]}</div>
73
- </div>
74
- ))}
75
- </div>
76
  </div>
77
  </div>
78
  );
79
  };
80
 
81
- // کامپوننت داخلی اصلی برنامه (بدون تغییر)
82
  const AppInternal: React.FC = () => {
83
  const videoRef = useRef<HTMLVideoElement>(null);
84
- const { volume, changePersonality, customUserName, customInstructions } = useAppContext();
85
  const [isUserSpeaking, setIsUserSpeaking] = useState(false);
86
  const [isMicActive, setIsMicActive] = useState(false);
87
  const [isCamActive, setIsCamActive] = useState(false);
@@ -98,6 +91,10 @@ const AppInternal: React.FC = () => {
98
  const handleSaveCustom = (name: string, instructions: string) => {
99
  changePersonality('custom', { name, instructions });
100
  };
 
 
 
 
101
  useEffect(() => {
102
  const handleClickOutside = (event: MouseEvent) => {
103
  if (isNotificationOpen && notificationPopoverRef.current && !notificationPopoverRef.current.contains(event.target as Node) && notificationButtonRef.current && !notificationButtonRef.current.contains(event.target as Node)) setIsNotificationOpen(false);
@@ -105,6 +102,7 @@ const AppInternal: React.FC = () => {
105
  document.addEventListener("mousedown", handleClickOutside);
106
  return () => document.removeEventListener("mousedown", handleClickOutside);
107
  }, [isNotificationOpen]);
 
108
  return (
109
  <>
110
  <div className="main-wrapper">
@@ -118,11 +116,24 @@ const AppInternal: React.FC = () => {
118
  </div>
119
  </div>
120
  <PersonalityMenu isOpen={isPersonalityMenuOpen} onClose={() => setIsPersonalityMenuOpen(false)} onSelectPersonality={handleSelectPersonality} volume={volume} />
121
- <div className="media-area">
122
- <video id="video-feed" ref={videoRef} autoPlay playsInline className={cn({ hidden: !isCamActive }, { "scale-x-[-1]": currentFacingMode === 'user' })} />
123
- {isMicActive && !isCamActive && <div id="large-logo-container"><Logo isMini={false} isActive={true} isAi={false} speakingVolume={volume} isUserSpeaking={isUserSpeaking} /></div>}
124
- </div>
125
- <ControlTray videoRef={videoRef} onUserSpeakingChange={setIsUserSpeaking} isAppMicActive={isMicActive} onAppMicToggle={setIsMicActive} isAppCamActive={isCamActive} onAppCamToggle={setIsCamActive} currentFacingMode={currentFacingMode} onFacingModeChange={setCurrentFacingMode} />
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  </div>
127
  <CustomModal isOpen={isCustomModalOpen} onClose={() => setIsCustomModalOpen(false)} onSave={handleSaveCustom} initialName={customUserName} initialInstructions={customInstructions} />
128
  </>
 
1
+ // src/App.tsx (نسخه نهایی با رفع کامل مشکل ترجمه همه بخش‌ها)
2
  import React, { useEffect, useRef, useState, FC } from "react";
3
  import './App.scss';
4
  import { AppProvider, useAppContext, PersonalityType, PersonalityInstructions } from "./contexts/AppContext";
 
10
  import { LiveConfig } from "./multimodal-live-types";
11
  import { speakers } from "./data/speakers";
12
 
13
+ // کامپوننت مودال (بدون تغییر)
14
  interface CustomModalProps { isOpen: boolean; onClose: () => void; onSave: (name: string, instructions: string) => void; initialName: string; initialInstructions: string; }
15
  const CustomModal: FC<CustomModalProps> = ({ isOpen, onClose, onSave, initialName, initialInstructions }) => {
16
  const [name, setName] = useState(initialName);
 
18
  useEffect(() => { if (isOpen) { setName(initialName); setInstructions(initialInstructions); } }, [isOpen, initialName, initialInstructions]);
19
  if (!isOpen) return null;
20
  const handleSave = () => { if (name.trim() && instructions.trim()) { onSave(name.trim(), instructions.trim()); onClose(); } else { alert("لطفا نام و دستورالعمل‌ها را وارد کنید."); } };
21
+ return ( <div className="modal-overlay" onClick={onClose} translate="no"><div className="modal-content" onClick={(e) => e.stopPropagation()}><div className="modal-header"><h3>ساخت شخصیت اختصاصی</h3><button className="close-button" onClick={onClose}>×</button></div><div className="modal-body"><div className="form-group"><label htmlFor="name">نام شما</label><input id="name" type="text" placeholder="مثلا: حامد" value={name} onChange={(e) => setName(e.target.value)} /></div><div className="form-group"><label htmlFor="instructions">دستورالعمل‌ها</label><textarea id="instructions" placeholder="مثلا: خیلی خودمونی صحبت کن..." value={instructions} onChange={(e) => setInstructions(e.target.value)} rows={6} /><small>اینجا فقط سبک صحبت کردن را مشخص کنید.</small></div></div><div className="modal-footer"><button className="save-button" onClick={handleSave}>ذخیره</button></div></div></div> );
22
+ };
23
+
24
+ // کامپوننت تایمر
25
+ const TimerDisplay: React.FC = () => {
26
+ const { remainingTime, isTimeUp, isTimerActive } = useAppContext();
27
+ const minutes = Math.floor(remainingTime / 60);
28
+ const seconds = remainingTime % 60;
29
+ const progressPercent = (remainingTime / 900) * 100;
30
+ if (isTimeUp) {
31
+ return (
32
+ <div className="timer-section time-up">
33
+ <p>برای استفاده نامحدود، از نسخه پیشرفته استفاده کنید.</p>
34
+ </div>
35
+ );
36
+ }
37
  return (
38
+ <div className="timer-section">
39
+ <div className="timer-text"><span>زمان باقیمانده روزانه</span><span>{minutes}:{seconds < 10 ? `0${seconds}` : seconds}</span></div>
40
+ <div className="timer-bar"><div className={cn("timer-bar-fill", { active: isTimerActive })} style={{ width: `${progressPercent}%` }}/></div>
 
 
 
 
 
 
 
41
  </div>
42
  );
43
  };
 
58
  return (
59
  <div ref={menuRef} className="personality-popover-wrapper open" translate="no">
60
  <div className="popover-content">
61
+ <div className={cn("selected-voice-display", { speaking: volume > 0.01 })}><div className="voice-image-wrapper"><img src={selectedSpeaker.imgUrl} alt={selectedSpeaker.name} /></div><h3>{selectedSpeaker.name}</h3><p>{selectedSpeaker.desc}</p></div>
 
 
 
62
  <hr className="menu-divider" />
63
  <h4 className="menu-subtitle">انتخاب شخصیت</h4>
64
+ <ul>{(Object.keys(personalityIcons) as PersonalityType[]).map(key => (<li key={key} className={cn({ active: selectedPersonality === key })} onClick={() => onSelectPersonality(key)}><div><span className="material-symbols-outlined">{personalityIcons[key]}</span>{personalityLabels[key]}</div>{selectedPersonality === key && <span className="material-symbols-outlined tick">done</span>}</li>))}</ul>
65
+ <TimerDisplay />
 
 
 
 
 
 
66
  <hr className="menu-divider" />
67
  <h4 className="menu-subtitle">انتخاب گوینده</h4>
68
+ <div className="voice-grid">{speakers.map(speaker => (<div key={speaker.id} className={cn("voice-card", { active: selectedVoice === speaker.id })} onClick={() => changeVoice(speaker.id)} title={speaker.name}><img src={speaker.imgUrl} alt={speaker.name} loading="lazy" /><div className="voice-name">{speaker.name.split(' ')[0]}</div></div>))}</div>
 
 
 
 
 
 
 
69
  </div>
70
  </div>
71
  );
72
  };
73
 
74
+ // کامپوننت داخلی اصلی برنامه
75
  const AppInternal: React.FC = () => {
76
  const videoRef = useRef<HTMLVideoElement>(null);
77
+ const { volume, changePersonality, customUserName, customInstructions, startTimer, stopTimer, connected, isTimeUp } = useAppContext();
78
  const [isUserSpeaking, setIsUserSpeaking] = useState(false);
79
  const [isMicActive, setIsMicActive] = useState(false);
80
  const [isCamActive, setIsCamActive] = useState(false);
 
91
  const handleSaveCustom = (name: string, instructions: string) => {
92
  changePersonality('custom', { name, instructions });
93
  };
94
+ useEffect(() => {
95
+ if (connected && isMicActive && !isTimeUp) { startTimer(); } else { stopTimer(); }
96
+ return () => stopTimer();
97
+ }, [connected, isMicActive, isTimeUp, startTimer, stopTimer]);
98
  useEffect(() => {
99
  const handleClickOutside = (event: MouseEvent) => {
100
  if (isNotificationOpen && notificationPopoverRef.current && !notificationPopoverRef.current.contains(event.target as Node) && notificationButtonRef.current && !notificationButtonRef.current.contains(event.target as Node)) setIsNotificationOpen(false);
 
102
  document.addEventListener("mousedown", handleClickOutside);
103
  return () => document.removeEventListener("mousedown", handleClickOutside);
104
  }, [isNotificationOpen]);
105
+
106
  return (
107
  <>
108
  <div className="main-wrapper">
 
116
  </div>
117
  </div>
118
  <PersonalityMenu isOpen={isPersonalityMenuOpen} onClose={() => setIsPersonalityMenuOpen(false)} onSelectPersonality={handleSelectPersonality} volume={volume} />
119
+
120
+ {isTimeUp ? (
121
+ // تغییر اصلی اینجاست: اضافه کردن translate="no" به کل صفحه اتمام زمان
122
+ <div className="time-up-overlay" translate="no">
123
+ <div className="time-up-card">
124
+ <span className="material-symbols-outlined medal-icon" translate="no">workspace_premium</span>
125
+ <h2>زمان شما به پایان رسید</h2>
126
+ <p>زمان استفاده رایگان 15 دقیقه روزانه به پایان رسیده است. برای استفاده نامحدود، حساب خود را ارتقا دهید و از نسخه پیشرفته بصورت نامحدود استفاده کنید.</p>
127
+ </div>
128
+ </div>
129
+ ) : (
130
+ <div className="media-area">
131
+ <video id="video-feed" ref={videoRef} autoPlay playsInline className={cn({ hidden: !isCamActive }, { "scale-x-[-1]": currentFacingMode === 'user' })} />
132
+ {isMicActive && !isCamActive && <div id="large-logo-container"><Logo isMini={false} isActive={true} isAi={false} speakingVolume={volume} isUserSpeaking={isUserSpeaking} /></div>}
133
+ </div>
134
+ )}
135
+
136
+ <ControlTray videoRef={videoRef} onUserSpeakingChange={setIsUserSpeaking} isAppMicActive={isMicActive} onAppMicToggle={setIsMicActive} isAppCamActive={isCamActive} onAppCamToggle={setIsCamActive} currentFacingMode={currentFacingMode} onFacingModeChange={setCurrentFacingMode} isTimeUp={isTimeUp} />
137
  </div>
138
  <CustomModal isOpen={isCustomModalOpen} onClose={() => setIsCustomModalOpen(false)} onSave={handleSaveCustom} initialName={customUserName} initialInstructions={customInstructions} />
139
  </>