Ezmary commited on
Commit
192e3ba
·
verified ·
1 Parent(s): c98c9e8

Update src/App.tsx

Browse files
Files changed (1) hide show
  1. src/App.tsx +56 -11
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";
@@ -8,7 +8,7 @@ import { isIOS } from "./lib/platform";
8
  import cn from "classnames";
9
  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; }
@@ -22,12 +22,14 @@ const CustomModal: FC<CustomModalProps> = ({ isOpen, onClose, onSave, initialNam
22
  };
23
 
24
  // کامپوننت منوی شخصیت‌ها
25
- const PersonalityMenu: React.FC<{ isOpen: boolean; onClose: () => void; onSelectPersonality: (p: PersonalityType) => void; }> = ({ isOpen, onClose, onSelectPersonality }) => {
26
  const menuRef = useRef<HTMLDivElement>(null);
27
  const { selectedPersonality, selectedVoice, changeVoice } = useAppContext();
28
  const personalityIcons: Record<PersonalityType, string> = { default: "person", teacher: "school", poetic: "auto_awesome", funny: "sentiment_satisfied", custom: "tune" };
29
  const personalityLabels: Record<PersonalityType, string> = { default: 'دستیار پیش‌فرض', teacher: 'استاد زبان', poetic: 'حس خوب', funny: 'شوخ‌طبع', custom: 'شخصیت اختصاصی' };
30
 
 
 
31
  useEffect(() => {
32
  const handleClickOutside = (e: MouseEvent) => menuRef.current && !menuRef.current.contains(e.target as Node) && onClose();
33
  if (isOpen) document.addEventListener('mousedown', handleClickOutside);
@@ -39,6 +41,17 @@ const PersonalityMenu: React.FC<{ isOpen: boolean; onClose: () => void; onSelect
39
  return (
40
  <div ref={menuRef} className="personality-popover-wrapper open">
41
  <div className="popover-content">
 
 
 
 
 
 
 
 
 
 
 
42
  <ul>
43
  {(Object.keys(personalityIcons) as PersonalityType[]).map(key => (
44
  <li key={key} className={cn({ active: selectedPersonality === key })} onClick={() => onSelectPersonality(key)}>
@@ -47,7 +60,6 @@ const PersonalityMenu: React.FC<{ isOpen: boolean; onClose: () => void; onSelect
47
  </li>
48
  ))}
49
  </ul>
50
- {/* ✅ تغییر اصلی: اضافه شدن گالری گویندگان */}
51
  <hr className="menu-divider" />
52
  <h4 className="menu-subtitle">انتخاب گوینده</h4>
53
  <div className="voice-grid">
@@ -66,7 +78,7 @@ const PersonalityMenu: React.FC<{ isOpen: boolean; onClose: () => void; onSelect
66
  // کامپوننت داخلی اصلی برنامه
67
  const AppInternal: React.FC = () => {
68
  const videoRef = useRef<HTMLVideoElement>(null);
69
- const { volume, changePersonality, customUserName, customInstructions } = useAppContext();
70
  const [isUserSpeaking, setIsUserSpeaking] = useState(false);
71
  const [isMicActive, setIsMicActive] = useState(false);
72
  const [isCamActive, setIsCamActive] = useState(false);
@@ -76,13 +88,40 @@ const AppInternal: React.FC = () => {
76
  const [isCustomModalOpen, setIsCustomModalOpen] = useState(false);
77
  const notificationButtonRef = useRef<HTMLButtonElement>(null);
78
  const notificationPopoverRef = useRef<HTMLDivElement>(null);
 
 
 
 
 
 
 
79
 
80
  const handleSelectPersonality = (p: PersonalityType) => {
81
  setIsPersonalityMenuOpen(false);
82
- if (p === 'custom') setIsCustomModalOpen(true);
83
- else changePersonality(p);
 
 
 
 
 
84
  };
85
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  useEffect(() => {
87
  const handleClickOutside = (event: MouseEvent) => {
88
  if (isNotificationOpen && notificationPopoverRef.current && !notificationPopoverRef.current.contains(event.target as Node) && notificationButtonRef.current && !notificationButtonRef.current.contains(event.target as Node)) setIsNotificationOpen(false);
@@ -107,19 +146,25 @@ const AppInternal: React.FC = () => {
107
  <div className="notification-popover-text-content">مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از بیان اطلاعات حساس بپرهیزید.</div>
108
  </div>
109
  </div>
110
- <PersonalityMenu isOpen={isPersonalityMenuOpen} onClose={() => setIsPersonalityMenuOpen(false)} onSelectPersonality={handleSelectPersonality} />
111
  <div className="media-area">
112
  <video id="video-feed" ref={videoRef} autoPlay playsInline className={cn({ hidden: !isCamActive }, { "scale-x-[-1]": currentFacingMode === 'user' })} />
113
  {isMicActive && !isCamActive && <div id="large-logo-container"><Logo isMini={false} isActive={true} isAi={false} speakingVolume={volume} isUserSpeaking={isUserSpeaking} /></div>}
114
  </div>
115
  <ControlTray videoRef={videoRef} onUserSpeakingChange={setIsUserSpeaking} isAppMicActive={isMicActive} onAppMicToggle={setIsMicActive} isAppCamActive={isCamActive} onAppCamToggle={setIsCamActive} currentFacingMode={currentFacingMode} onFacingModeChange={setCurrentFacingMode} />
116
  </div>
117
- <CustomModal isOpen={isCustomModalOpen} onClose={() => setIsCustomModalOpen(false)} onSave={(name, instructions) => changePersonality('custom', { name, instructions })} initialName={customUserName} initialInstructions={customInstructions} />
 
 
 
 
 
 
118
  </>
119
  );
120
  };
121
 
122
- // کامپوننت ریشه App
123
  function App() {
124
  const [showIOSModal, setShowIOSModal] = useState(false);
125
  const [personalityInstructions, setPersonalityInstructions] = useState<PersonalityInstructions | null>(null);
 
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";
 
8
  import cn from "classnames";
9
  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; }
 
22
  };
23
 
24
  // کامپوننت منوی شخصیت‌ها
25
+ const PersonalityMenu: React.FC<{ isOpen: boolean; onClose: () => void; onSelectPersonality: (p: PersonalityType) => void; volume: number; }> = ({ isOpen, onClose, onSelectPersonality, volume }) => {
26
  const menuRef = useRef<HTMLDivElement>(null);
27
  const { selectedPersonality, selectedVoice, changeVoice } = useAppContext();
28
  const personalityIcons: Record<PersonalityType, string> = { default: "person", teacher: "school", poetic: "auto_awesome", funny: "sentiment_satisfied", custom: "tune" };
29
  const personalityLabels: Record<PersonalityType, string> = { default: 'دستیار پیش‌فرض', teacher: 'استاد زبان', poetic: 'حس خوب', funny: 'شوخ‌طبع', custom: 'شخصیت اختصاصی' };
30
 
31
+ const selectedSpeaker = speakers.find(s => s.id === selectedVoice) || speakers[0];
32
+
33
  useEffect(() => {
34
  const handleClickOutside = (e: MouseEvent) => menuRef.current && !menuRef.current.contains(e.target as Node) && onClose();
35
  if (isOpen) document.addEventListener('mousedown', handleClickOutside);
 
41
  return (
42
  <div ref={menuRef} className="personality-popover-wrapper open">
43
  <div className="popover-content">
44
+ {/* ✅ بخش جدید: پیش‌نمایش گوینده منتخب */}
45
+ <div className={cn("selected-voice-display", { speaking: volume > 0.01 })}>
46
+ <div className="voice-image-wrapper">
47
+ <img src={selectedSpeaker.imgUrl} alt={selectedSpeaker.name} />
48
+ </div>
49
+ <h3>{selectedSpeaker.name}</h3>
50
+ <p>{selectedSpeaker.desc}</p>
51
+ </div>
52
+ <hr className="menu-divider" />
53
+
54
+ <h4 className="menu-subtitle">انتخاب شخصیت</h4>
55
  <ul>
56
  {(Object.keys(personalityIcons) as PersonalityType[]).map(key => (
57
  <li key={key} className={cn({ active: selectedPersonality === key })} onClick={() => onSelectPersonality(key)}>
 
60
  </li>
61
  ))}
62
  </ul>
 
63
  <hr className="menu-divider" />
64
  <h4 className="menu-subtitle">انتخاب گوینده</h4>
65
  <div className="voice-grid">
 
78
  // کامپوننت داخلی اصلی برنامه
79
  const AppInternal: React.FC = () => {
80
  const videoRef = useRef<HTMLVideoElement>(null);
81
+ const { volume, changePersonality, customUserName, customInstructions, selectedVoice } = useAppContext();
82
  const [isUserSpeaking, setIsUserSpeaking] = useState(false);
83
  const [isMicActive, setIsMicActive] = useState(false);
84
  const [isCamActive, setIsCamActive] = useState(false);
 
88
  const [isCustomModalOpen, setIsCustomModalOpen] = useState(false);
89
  const notificationButtonRef = useRef<HTMLButtonElement>(null);
90
  const notificationPopoverRef = useRef<HTMLDivElement>(null);
91
+ const [confirmationMessage, setConfirmationMessage] = useState<string | null>(null);
92
+
93
+ // ✅ منطق جدید برای پیغام تایید
94
+ const showConfirmation = (message: string) => {
95
+ setConfirmationMessage(message);
96
+ setTimeout(() => setConfirmationMessage(null), 3000); // پیغام بعد از ۳ ثانیه محو می‌شود
97
+ };
98
 
99
  const handleSelectPersonality = (p: PersonalityType) => {
100
  setIsPersonalityMenuOpen(false);
101
+ if (p === 'custom') {
102
+ setIsCustomModalOpen(true);
103
+ } else {
104
+ changePersonality(p);
105
+ const personalityLabels: Record<PersonalityType, string> = { default: 'دستیار پیش‌فرض', teacher: 'استاد زبان', poetic: 'حس خوب', funny: 'شوخ‌طبع', custom: 'شخصیت اختصاصی' };
106
+ showConfirmation(`شخصیت به ${personalityLabels[p]} تغییر کرد`);
107
+ }
108
  };
109
+
110
+ const handleSaveCustom = (name: string, instructions: string) => {
111
+ changePersonality('custom', { name, instructions });
112
+ showConfirmation("شخصیت اختصاصی با موفقیت ذخیره شد");
113
+ };
114
+
115
+ // ✅ هوک برای نمایش پیغام تایید هنگام تغییر گوینده
116
+ useEffect(() => {
117
+ if(isPersonalityMenuOpen) return; // جلوگیری از نمایش هنگام باز بودن منو
118
+ const speaker = speakers.find(s => s.id === selectedVoice);
119
+ if (speaker) {
120
+ showConfirmation(`گوینده به ${speaker.name} تغییر کرد`);
121
+ }
122
+ }, [selectedVoice]);
123
+
124
+
125
  useEffect(() => {
126
  const handleClickOutside = (event: MouseEvent) => {
127
  if (isNotificationOpen && notificationPopoverRef.current && !notificationPopoverRef.current.contains(event.target as Node) && notificationButtonRef.current && !notificationButtonRef.current.contains(event.target as Node)) setIsNotificationOpen(false);
 
146
  <div className="notification-popover-text-content">مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از بیان اطلاعات حساس بپرهیزید.</div>
147
  </div>
148
  </div>
149
+ <PersonalityMenu isOpen={isPersonalityMenuOpen} onClose={() => setIsPersonalityMenuOpen(false)} onSelectPersonality={handleSelectPersonality} volume={volume} />
150
  <div className="media-area">
151
  <video id="video-feed" ref={videoRef} autoPlay playsInline className={cn({ hidden: !isCamActive }, { "scale-x-[-1]": currentFacingMode === 'user' })} />
152
  {isMicActive && !isCamActive && <div id="large-logo-container"><Logo isMini={false} isActive={true} isAi={false} speakingVolume={volume} isUserSpeaking={isUserSpeaking} /></div>}
153
  </div>
154
  <ControlTray videoRef={videoRef} onUserSpeakingChange={setIsUserSpeaking} isAppMicActive={isMicActive} onAppMicToggle={setIsMicActive} isAppCamActive={isCamActive} onAppCamToggle={setIsCamActive} currentFacingMode={currentFacingMode} onFacingModeChange={setCurrentFacingMode} />
155
  </div>
156
+ <CustomModal isOpen={isCustomModalOpen} onClose={() => setIsCustomModalOpen(false)} onSave={handleSaveCustom} initialName={customUserName} initialInstructions={customInstructions} />
157
+ {/* ✅ JSX برای نمایش پیغام تایید */}
158
+ {confirmationMessage && (
159
+ <div className="confirmation-toast">
160
+ {confirmationMessage}
161
+ </div>
162
+ )}
163
  </>
164
  );
165
  };
166
 
167
+ // کامپوننت ریشه App (بدون تغییر)
168
  function App() {
169
  const [showIOSModal, setShowIOSModal] = useState(false);
170
  const [personalityInstructions, setPersonalityInstructions] = useState<PersonalityInstructions | null>(null);