Ezmary commited on
Commit
25cd3bd
·
verified ·
1 Parent(s): 518de1e

Update src/App.tsx

Browse files
Files changed (1) hide show
  1. src/App.tsx +12 -51
src/App.tsx CHANGED
@@ -1,8 +1,6 @@
1
- // src/App.tsx (نسخه یکپارچه و نهایی)
2
-
3
  import React, { useEffect, useRef, useState, FC } from "react";
4
  import './App.scss';
5
- // AppContext را از مسیر درست ایمپورت می‌کنیم
6
  import { AppProvider, useAppContext, PersonalityType, PersonalityInstructions } from "./contexts/AppContext";
7
  import ControlTray from "./components/control-tray/ControlTray";
8
  import { IOSModal } from "./components/ios-modal/IOSModal";
@@ -11,9 +9,7 @@ import cn from "classnames";
11
  import Logo from "./components/logo/Logo";
12
  import { LiveConfig } from "./multimodal-live-types";
13
 
14
- // ===================================================================
15
- // START: کامپوننت مودال مستقیماً اینجا تعریف شده است
16
- // ===================================================================
17
  interface CustomModalProps {
18
  isOpen: boolean;
19
  onClose: () => void;
@@ -21,74 +17,48 @@ interface CustomModalProps {
21
  initialName: string;
22
  initialInstructions: string;
23
  }
24
-
25
  const CustomModal: FC<CustomModalProps> = ({ isOpen, onClose, onSave, initialName, initialInstructions }) => {
26
  const [name, setName] = useState(initialName);
27
  const [instructions, setInstructions] = useState(initialInstructions);
28
-
29
  useEffect(() => {
30
  if (isOpen) {
31
  setName(initialName);
32
  setInstructions(initialInstructions);
33
  }
34
  }, [isOpen, initialName, initialInstructions]);
35
-
36
  if (!isOpen) return null;
37
-
38
  const handleSave = () => {
39
  if (name.trim() && instructions.trim()) {
40
  onSave(name.trim(), instructions.trim());
41
  onClose();
42
- } else {
43
- alert("لطفا نام و دستورالعمل‌ها را وارد کنید.");
44
- }
45
  };
46
-
47
  return (
48
  <div className="modal-overlay" onClick={onClose}>
49
  <div className="modal-content" onClick={(e) => e.stopPropagation()}>
50
- <div className="modal-header">
51
- <h3>ساخت شخصیت اختصاصی</h3>
52
- <button className="close-button" onClick={onClose}>×</button>
53
- </div>
54
  <div className="modal-body">
55
- <div className="form-group">
56
- <label htmlFor="name">نام شما (برای صدا زدن)</label>
57
- <input id="name" type="text" placeholder="مثلا: علی" value={name} onChange={(e) => setName(e.target.value)} />
58
- </div>
59
- <div className="form-group">
60
- <label htmlFor="instructions">دستورالعمل‌های شخصیت</label>
61
- <textarea id="instructions" placeholder="مثلا: خیلی خودمونی و بامزه صحبت کن..." value={instructions} onChange={(e) => setInstructions(e.target.value)} rows={6} />
62
- <small>اینجا فقط رفتار و سبک صحبت کردن دستیار را مشخص کنید.</small>
63
- </div>
64
- </div>
65
- <div className="modal-footer">
66
- <button className="save-button" onClick={handleSave}>ذخیره و فعال‌سازی</button>
67
  </div>
 
68
  </div>
69
  </div>
70
  );
71
  };
72
- // ===================================================================
73
- // END: تعریف کامپوننت مودال
74
- // ===================================================================
75
 
76
-
77
- // کامپوننت منوی شخصیت‌ها (به صورت یک کامپوننت داخلی ساده)
78
  const PersonalityMenu: React.FC<{ isOpen: boolean; onClose: () => void; onSelect: (p: PersonalityType) => void; }> = ({ isOpen, onClose, onSelect }) => {
79
  const menuRef = useRef<HTMLDivElement>(null);
80
  const { selectedPersonality } = useAppContext();
81
  const personalityIcons: Record<PersonalityType, string> = { default: "person", teacher: "school", poetic: "auto_awesome", funny: "sentiment_satisfied", custom: "tune" };
82
  const personalityLabels: Record<PersonalityType, string> = { default: 'دستیار پیش‌فرض', teacher: 'استاد زبان', poetic: 'حس خوب', funny: 'شوخ‌طبع', custom: 'شخصیت اختصاصی' };
83
-
84
  useEffect(() => {
85
  const handleClickOutside = (e: MouseEvent) => menuRef.current && !menuRef.current.contains(e.target as Node) && onClose();
86
  if (isOpen) document.addEventListener('mousedown', handleClickOutside);
87
  return () => document.removeEventListener('mousedown', handleClickOutside);
88
  }, [isOpen, onClose]);
89
-
90
  if (!isOpen) return null;
91
-
92
  return (
93
  <div ref={menuRef} className="personality-popover-wrapper open">
94
  <div className="popover-content">
@@ -116,30 +86,26 @@ const AppInternal: React.FC = () => {
116
  const [isNotificationOpen, setIsNotificationOpen] = useState(false);
117
  const [isPersonalityMenuOpen, setIsPersonalityMenuOpen] = useState(false);
118
  const [isCustomModalOpen, setIsCustomModalOpen] = useState(false);
119
-
120
  const handleSelectPersonality = (p: PersonalityType) => {
121
  setIsPersonalityMenuOpen(false);
122
  if (p === 'custom') setIsCustomModalOpen(true);
123
  else changePersonality(p);
124
  };
125
-
126
  return (
127
  <>
128
- <div className="main-wrapper">
129
  <div className="header-controls">
130
- <button aria-label="انتخاب شخصیت" className="header-icon-button" onClick={() => setIsPersonalityMenuOpen(true)}>
131
  <span className="material-symbols-outlined">psychology</span>
132
  </button>
133
  <button aria-label="اطلاعات" className="header-icon-button" onClick={() => setIsNotificationOpen(v => !v)}>
134
  <span className="material-symbols-outlined">info</span>
135
  </button>
136
  </div>
137
-
138
  {isNotificationOpen && <div className="notification-popover-wrapper open"><div className="popover-content">مدل‌های هوش مصنوعی می‌توانند اشتباه کنند.</div></div>}
139
-
140
  <PersonalityMenu isOpen={isPersonalityMenuOpen} onClose={() => setIsPersonalityMenuOpen(false)} onSelect={handleSelectPersonality} />
141
-
142
- <div className="media-area">
143
  <video id="video-feed" ref={videoRef} autoPlay playsInline className={cn({ hidden: !isCamActive }, { "scale-x-[-1]": currentFacingMode === 'user' })} />
144
  {isMicActive && !isCamActive && <div id="large-logo-container"><Logo isMini={false} isActive={true} isAi={false} speakingVolume={volume} isUserSpeaking={isUserSpeaking} /></div>}
145
  </div>
@@ -156,7 +122,6 @@ function App() {
156
  const [showIOSModal, setShowIOSModal] = useState(false);
157
  const [personalityInstructions, setPersonalityInstructions] = useState<PersonalityInstructions | null>(null);
158
  const [loadingError, setLoadingError] = useState<string | null>(null);
159
-
160
  useEffect(() => {
161
  if (isIOS()) setShowIOSModal(true);
162
  const fetchInstructions = async () => {
@@ -170,12 +135,9 @@ function App() {
170
  };
171
  fetchInstructions();
172
  }, []);
173
-
174
  if (loadingError) return <div className="loading-screen">{loadingError}</div>;
175
  if (!personalityInstructions) return <div className="loading-screen">در حال بارگذاری...</div>;
176
-
177
  const initialAppConfig: LiveConfig = { model: "models/gemini-2.0-flash-exp" };
178
-
179
  return (
180
  <AppProvider initialConfig={initialAppConfig} personalityInstructions={personalityInstructions}>
181
  <AppInternal />
@@ -183,5 +145,4 @@ function App() {
183
  </AppProvider>
184
  );
185
  }
186
-
187
  export default App;
 
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";
5
  import ControlTray from "./components/control-tray/ControlTray";
6
  import { IOSModal } from "./components/ios-modal/IOSModal";
 
9
  import Logo from "./components/logo/Logo";
10
  import { LiveConfig } from "./multimodal-live-types";
11
 
12
+ // کامپوننت مودال (که قبلاً در فایل جدا بود، برای سادگی اینجا قرار گرفته)
 
 
13
  interface CustomModalProps {
14
  isOpen: boolean;
15
  onClose: () => void;
 
17
  initialName: string;
18
  initialInstructions: string;
19
  }
 
20
  const CustomModal: FC<CustomModalProps> = ({ isOpen, onClose, onSave, initialName, initialInstructions }) => {
21
  const [name, setName] = useState(initialName);
22
  const [instructions, setInstructions] = useState(initialInstructions);
 
23
  useEffect(() => {
24
  if (isOpen) {
25
  setName(initialName);
26
  setInstructions(initialInstructions);
27
  }
28
  }, [isOpen, initialName, initialInstructions]);
 
29
  if (!isOpen) return null;
 
30
  const handleSave = () => {
31
  if (name.trim() && instructions.trim()) {
32
  onSave(name.trim(), instructions.trim());
33
  onClose();
34
+ } else { alert("لطفا نام و دستورالعمل‌ها را وارد کنید."); }
 
 
35
  };
 
36
  return (
37
  <div className="modal-overlay" onClick={onClose}>
38
  <div className="modal-content" onClick={(e) => e.stopPropagation()}>
39
+ <div className="modal-header"><h3>ساخت شخصیت اختصاصی</h3><button className="close-button" onClick={onClose}>×</button></div>
 
 
 
40
  <div className="modal-body">
41
+ <div className="form-group"><label htmlFor="name">نام شما (برای صدا زدن)</label><input id="name" type="text" placeholder="مثلا: علی" value={name} onChange={(e) => setName(e.target.value)} /></div>
42
+ <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>
 
 
 
 
 
 
 
 
 
 
43
  </div>
44
+ <div className="modal-footer"><button className="save-button" onClick={handleSave}>ذخیره و فعال‌سازی</button></div>
45
  </div>
46
  </div>
47
  );
48
  };
 
 
 
49
 
50
+ // کامپوننت منوی شخصیت‌ها
 
51
  const PersonalityMenu: React.FC<{ isOpen: boolean; onClose: () => void; onSelect: (p: PersonalityType) => void; }> = ({ isOpen, onClose, onSelect }) => {
52
  const menuRef = useRef<HTMLDivElement>(null);
53
  const { selectedPersonality } = useAppContext();
54
  const personalityIcons: Record<PersonalityType, string> = { default: "person", teacher: "school", poetic: "auto_awesome", funny: "sentiment_satisfied", custom: "tune" };
55
  const personalityLabels: Record<PersonalityType, string> = { default: 'دستیار پیش‌فرض', teacher: 'استاد زبان', poetic: 'حس خوب', funny: 'شوخ‌طبع', custom: 'شخصیت اختصاصی' };
 
56
  useEffect(() => {
57
  const handleClickOutside = (e: MouseEvent) => menuRef.current && !menuRef.current.contains(e.target as Node) && onClose();
58
  if (isOpen) document.addEventListener('mousedown', handleClickOutside);
59
  return () => document.removeEventListener('mousedown', handleClickOutside);
60
  }, [isOpen, onClose]);
 
61
  if (!isOpen) return null;
 
62
  return (
63
  <div ref={menuRef} className="personality-popover-wrapper open">
64
  <div className="popover-content">
 
86
  const [isNotificationOpen, setIsNotificationOpen] = useState(false);
87
  const [isPersonalityMenuOpen, setIsPersonalityMenuOpen] = useState(false);
88
  const [isCustomModalOpen, setIsCustomModalOpen] = useState(false);
 
89
  const handleSelectPersonality = (p: PersonalityType) => {
90
  setIsPersonalityMenuOpen(false);
91
  if (p === 'custom') setIsCustomModalOpen(true);
92
  else changePersonality(p);
93
  };
 
94
  return (
95
  <>
96
+ <div className="main-wrapper"> {/* ✅ این ساختار اصلی حفظ شده است */}
97
  <div className="header-controls">
98
+ <button aria-label="انتخاب شخصیت" className="header-icon-button" onClick={() => setIsPersonalityMenuOpen(v => !v)}>
99
  <span className="material-symbols-outlined">psychology</span>
100
  </button>
101
  <button aria-label="اطلاعات" className="header-icon-button" onClick={() => setIsNotificationOpen(v => !v)}>
102
  <span className="material-symbols-outlined">info</span>
103
  </button>
104
  </div>
 
105
  {isNotificationOpen && <div className="notification-popover-wrapper open"><div className="popover-content">مدل‌های هوش مصنوعی می‌توانند اشتباه کنند.</div></div>}
 
106
  <PersonalityMenu isOpen={isPersonalityMenuOpen} onClose={() => setIsPersonalityMenuOpen(false)} onSelect={handleSelectPersonality} />
107
+
108
+ <div className="media-area"> {/* ✅ این بخش برای نمایش ویدیو و لوگو ضروری است */}
109
  <video id="video-feed" ref={videoRef} autoPlay playsInline className={cn({ hidden: !isCamActive }, { "scale-x-[-1]": currentFacingMode === 'user' })} />
110
  {isMicActive && !isCamActive && <div id="large-logo-container"><Logo isMini={false} isActive={true} isAi={false} speakingVolume={volume} isUserSpeaking={isUserSpeaking} /></div>}
111
  </div>
 
122
  const [showIOSModal, setShowIOSModal] = useState(false);
123
  const [personalityInstructions, setPersonalityInstructions] = useState<PersonalityInstructions | null>(null);
124
  const [loadingError, setLoadingError] = useState<string | null>(null);
 
125
  useEffect(() => {
126
  if (isIOS()) setShowIOSModal(true);
127
  const fetchInstructions = async () => {
 
135
  };
136
  fetchInstructions();
137
  }, []);
 
138
  if (loadingError) return <div className="loading-screen">{loadingError}</div>;
139
  if (!personalityInstructions) return <div className="loading-screen">در حال بارگذاری...</div>;
 
140
  const initialAppConfig: LiveConfig = { model: "models/gemini-2.0-flash-exp" };
 
141
  return (
142
  <AppProvider initialConfig={initialAppConfig} personalityInstructions={personalityInstructions}>
143
  <AppInternal />
 
145
  </AppProvider>
146
  );
147
  }
 
148
  export default App;