Spaces:
Running
Running
Update src/contexts/AppContext.tsx
Browse files- src/contexts/AppContext.tsx +61 -34
src/contexts/AppContext.tsx
CHANGED
@@ -1,13 +1,16 @@
|
|
1 |
-
// src/contexts/AppContext.tsx (نسخه نهایی با
|
2 |
-
import React, { createContext, FC, ReactNode, useContext, useEffect, useState, useCallback } from "react";
|
3 |
import { useLiveAPI, type UseLiveAPIResults } from "../hooks/use-live-api";
|
4 |
import { LiveConfig } from "../multimodal-live-types";
|
5 |
-
import { speakers } from '../data/speakers';
|
6 |
|
7 |
const LS_SELECTED_PERSONALITY = 'app_selected_personality';
|
8 |
-
const LS_SELECTED_VOICE = 'app_selected_voice';
|
9 |
const LS_CUSTOM_NAME = 'app_custom_name';
|
10 |
const LS_CUSTOM_INSTRUCTIONS = 'app_custom_instructions';
|
|
|
|
|
|
|
11 |
|
12 |
export type PersonalityType = "default" | "teacher" | "poetic" | "funny" | "custom";
|
13 |
export type PersonalityInstructions = Partial<Record<PersonalityType, string>>;
|
@@ -21,28 +24,67 @@ interface AppContextType extends UseLiveAPIResults {
|
|
21 |
customUserName: string;
|
22 |
customInstructions: string;
|
23 |
isRestarting: boolean;
|
|
|
|
|
|
|
|
|
|
|
24 |
}
|
25 |
|
26 |
const AppContext = createContext<AppContextType | undefined>(undefined);
|
27 |
-
|
28 |
-
const BASE_INSTRUCTION = `تو یک دستیار صوتی و تصویری پیشرفته هستی. همیشه به زبان فارسی روان، دقیق و طبیعی صحبت کن. هرگز خودت را به عنوان محصول شرکت دیگری معرفی نکن تو توسط هوش مصنوعی آلفا ساخته شدی به هیچ عنوان نباید خودت را به عنوان محصول گوگل یا مایکروسافت یا شرکت دیگری معرفی کنی. در پاسخهای خود از ایموجی یا شکلک استفاده نکن.`;
|
29 |
|
30 |
export const AppProvider: FC<{ children: ReactNode; initialConfig?: LiveConfig; personalityInstructions: PersonalityInstructions; url?: string; }> = ({
|
31 |
children, initialConfig, personalityInstructions, url
|
32 |
}) => {
|
33 |
const liveAPI = useLiveAPI({ url });
|
34 |
-
|
35 |
const [selectedPersonality, setSelectedPersonality] = useState<PersonalityType>(() => (localStorage.getItem(LS_SELECTED_PERSONALITY) as PersonalityType) || 'default');
|
36 |
-
const [selectedVoice, setSelectedVoice] = useState<string>(() => localStorage.getItem(LS_SELECTED_VOICE) || speakers[0].id);
|
37 |
const [customUserName, setCustomUserName] = useState<string>(() => localStorage.getItem(LS_CUSTOM_NAME) || '');
|
38 |
const [customInstructions, setCustomInstructions] = useState<string>(() => localStorage.getItem(LS_CUSTOM_INSTRUCTIONS) || '');
|
39 |
const [isRestarting, setIsRestarting] = useState(false);
|
|
|
|
|
|
|
40 |
|
41 |
useEffect(() => {
|
42 |
-
|
43 |
-
|
44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
if (selectedPersonality === 'custom') {
|
47 |
if (customInstructions) instructionParts.push(customInstructions);
|
48 |
} else {
|
@@ -50,26 +92,13 @@ export const AppProvider: FC<{ children: ReactNode; initialConfig?: LiveConfig;
|
|
50 |
if (personalityPrompt) instructionParts.push(personalityPrompt);
|
51 |
}
|
52 |
const finalInstruction = instructionParts.join('\n\n');
|
53 |
-
|
54 |
const newConfig: LiveConfig = {
|
55 |
-
// ✅ تغییر ۱: استفاده از مدل جدید
|
56 |
model: "models/gemini-live-2.5-flash-preview",
|
57 |
tools: [{ googleSearch: {} }],
|
58 |
-
|
59 |
-
generationConfig: {
|
60 |
-
responseModalities: "audio",
|
61 |
-
speechConfig: {
|
62 |
-
voiceConfig: {
|
63 |
-
prebuiltVoiceConfig: {
|
64 |
-
voiceName: selectedVoice,
|
65 |
-
},
|
66 |
-
},
|
67 |
-
},
|
68 |
-
},
|
69 |
systemInstruction: { parts: [{ text: finalInstruction.trim() }] },
|
70 |
};
|
71 |
liveAPI.setConfig(newConfig);
|
72 |
-
|
73 |
}, [selectedPersonality, selectedVoice, customUserName, customInstructions, personalityInstructions, liveAPI.setConfig, initialConfig]);
|
74 |
|
75 |
useEffect(() => {
|
@@ -88,20 +117,13 @@ export const AppProvider: FC<{ children: ReactNode; initialConfig?: LiveConfig;
|
|
88 |
}
|
89 |
localStorage.setItem(LS_SELECTED_PERSONALITY, newPersonality);
|
90 |
setSelectedPersonality(newPersonality);
|
91 |
-
if (liveAPI.connected) {
|
92 |
-
setIsRestarting(true);
|
93 |
-
liveAPI.disconnect();
|
94 |
-
}
|
95 |
}, [liveAPI.connected, liveAPI.disconnect]);
|
96 |
|
97 |
-
// ✅ تغییر ۳: تابع جدید برای تغییر گوینده
|
98 |
const changeVoice = useCallback((voiceId: string) => {
|
99 |
localStorage.setItem(LS_SELECTED_VOICE, voiceId);
|
100 |
setSelectedVoice(voiceId);
|
101 |
-
if (liveAPI.connected) {
|
102 |
-
setIsRestarting(true);
|
103 |
-
liveAPI.disconnect();
|
104 |
-
}
|
105 |
}, [liveAPI.connected, liveAPI.disconnect]);
|
106 |
|
107 |
const contextValue: AppContextType = {
|
@@ -114,6 +136,11 @@ export const AppProvider: FC<{ children: ReactNode; initialConfig?: LiveConfig;
|
|
114 |
customUserName,
|
115 |
customInstructions,
|
116 |
isRestarting,
|
|
|
|
|
|
|
|
|
|
|
117 |
};
|
118 |
|
119 |
return <AppContext.Provider value={contextValue}>{children}</AppContext.Provider>;
|
|
|
1 |
+
// src/contexts/AppContext.tsx (نسخه نهایی با اصلاح منطق اتصال)
|
2 |
+
import React, { createContext, FC, ReactNode, useContext, useEffect, useState, useCallback, useRef } from "react";
|
3 |
import { useLiveAPI, type UseLiveAPIResults } from "../hooks/use-live-api";
|
4 |
import { LiveConfig } from "../multimodal-live-types";
|
5 |
+
import { speakers } from '../data/speakers';
|
6 |
|
7 |
const LS_SELECTED_PERSONALITY = 'app_selected_personality';
|
8 |
+
const LS_SELECTED_VOICE = 'app_selected_voice';
|
9 |
const LS_CUSTOM_NAME = 'app_custom_name';
|
10 |
const LS_CUSTOM_INSTRUCTIONS = 'app_custom_instructions';
|
11 |
+
const LS_TIMER_SECONDS = 'app_timer_seconds';
|
12 |
+
const LS_TIMER_DATE = 'app_timer_date';
|
13 |
+
const DAILY_LIMIT_SECONDS = 900;
|
14 |
|
15 |
export type PersonalityType = "default" | "teacher" | "poetic" | "funny" | "custom";
|
16 |
export type PersonalityInstructions = Partial<Record<PersonalityType, string>>;
|
|
|
24 |
customUserName: string;
|
25 |
customInstructions: string;
|
26 |
isRestarting: boolean;
|
27 |
+
remainingTime: number;
|
28 |
+
isTimeUp: boolean;
|
29 |
+
isTimerActive: boolean;
|
30 |
+
startTimer: () => void;
|
31 |
+
stopTimer: () => void;
|
32 |
}
|
33 |
|
34 |
const AppContext = createContext<AppContextType | undefined>(undefined);
|
35 |
+
const BASE_INSTRUCTION = `تو یک دستیار صوتی و تصویری پیشرفته از برنامه هوش مصنوعی آلفا هستی. همیشه به زبان فارسی روان، دقیق و طبیعی صحبت کن. هرگز خودت را به عنوان محصول شرکت دیگری معرفی نکن تو توسط هوش مصنوعی آلفا ساخته و توسعه داده شدی. در پاسخهای خود از ایموجی یا شکلک استفاده نکن.`;
|
|
|
36 |
|
37 |
export const AppProvider: FC<{ children: ReactNode; initialConfig?: LiveConfig; personalityInstructions: PersonalityInstructions; url?: string; }> = ({
|
38 |
children, initialConfig, personalityInstructions, url
|
39 |
}) => {
|
40 |
const liveAPI = useLiveAPI({ url });
|
41 |
+
const timerIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
42 |
const [selectedPersonality, setSelectedPersonality] = useState<PersonalityType>(() => (localStorage.getItem(LS_SELECTED_PERSONALITY) as PersonalityType) || 'default');
|
43 |
+
const [selectedVoice, setSelectedVoice] = useState<string>(() => localStorage.getItem(LS_SELECTED_VOICE) || speakers[0].id);
|
44 |
const [customUserName, setCustomUserName] = useState<string>(() => localStorage.getItem(LS_CUSTOM_NAME) || '');
|
45 |
const [customInstructions, setCustomInstructions] = useState<string>(() => localStorage.getItem(LS_CUSTOM_INSTRUCTIONS) || '');
|
46 |
const [isRestarting, setIsRestarting] = useState(false);
|
47 |
+
const [remainingTime, setRemainingTime] = useState<number>(DAILY_LIMIT_SECONDS);
|
48 |
+
const [isTimerActive, setIsTimerActive] = useState(false);
|
49 |
+
const isTimeUp = remainingTime <= 0;
|
50 |
|
51 |
useEffect(() => {
|
52 |
+
const today = new Date().toLocaleDateString('fa-IR');
|
53 |
+
const storedDate = localStorage.getItem(LS_TIMER_DATE);
|
54 |
+
if (storedDate === today) {
|
55 |
+
const storedTime = parseInt(localStorage.getItem(LS_TIMER_SECONDS) || `${DAILY_LIMIT_SECONDS}`, 10);
|
56 |
+
setRemainingTime(storedTime);
|
57 |
+
} else {
|
58 |
+
localStorage.setItem(LS_TIMER_DATE, today);
|
59 |
+
localStorage.setItem(LS_TIMER_SECONDS, `${DAILY_LIMIT_SECONDS}`);
|
60 |
+
setRemainingTime(DAILY_LIMIT_SECONDS);
|
61 |
+
}
|
62 |
+
}, []);
|
63 |
+
|
64 |
+
const stopTimer = useCallback(() => {
|
65 |
+
if (timerIntervalRef.current) {
|
66 |
+
clearInterval(timerIntervalRef.current);
|
67 |
+
timerIntervalRef.current = null;
|
68 |
}
|
69 |
+
setIsTimerActive(false);
|
70 |
+
}, []);
|
71 |
+
|
72 |
+
const startTimer = useCallback(() => {
|
73 |
+
if (isTimeUp || timerIntervalRef.current) return;
|
74 |
+
setIsTimerActive(true);
|
75 |
+
timerIntervalRef.current = setInterval(() => {
|
76 |
+
setRemainingTime(prevTime => {
|
77 |
+
const newTime = Math.max(0, prevTime - 1);
|
78 |
+
localStorage.setItem(LS_TIMER_SECONDS, `${newTime}`);
|
79 |
+
if (newTime <= 0) stopTimer();
|
80 |
+
return newTime;
|
81 |
+
});
|
82 |
+
}, 1000);
|
83 |
+
}, [isTimeUp, stopTimer]);
|
84 |
+
|
85 |
+
useEffect(() => {
|
86 |
+
let instructionParts = [BASE_INSTRUCTION];
|
87 |
+
if (customUserName) instructionParts.push(`نام کاربر ${customUserName} است. او را با نامش صدا بزن.`);
|
88 |
if (selectedPersonality === 'custom') {
|
89 |
if (customInstructions) instructionParts.push(customInstructions);
|
90 |
} else {
|
|
|
92 |
if (personalityPrompt) instructionParts.push(personalityPrompt);
|
93 |
}
|
94 |
const finalInstruction = instructionParts.join('\n\n');
|
|
|
95 |
const newConfig: LiveConfig = {
|
|
|
96 |
model: "models/gemini-live-2.5-flash-preview",
|
97 |
tools: [{ googleSearch: {} }],
|
98 |
+
generationConfig: { responseModalities: "audio", speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: selectedVoice } } } },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
systemInstruction: { parts: [{ text: finalInstruction.trim() }] },
|
100 |
};
|
101 |
liveAPI.setConfig(newConfig);
|
|
|
102 |
}, [selectedPersonality, selectedVoice, customUserName, customInstructions, personalityInstructions, liveAPI.setConfig, initialConfig]);
|
103 |
|
104 |
useEffect(() => {
|
|
|
117 |
}
|
118 |
localStorage.setItem(LS_SELECTED_PERSONALITY, newPersonality);
|
119 |
setSelectedPersonality(newPersonality);
|
120 |
+
if (liveAPI.connected) { setIsRestarting(true); liveAPI.disconnect(); }
|
|
|
|
|
|
|
121 |
}, [liveAPI.connected, liveAPI.disconnect]);
|
122 |
|
|
|
123 |
const changeVoice = useCallback((voiceId: string) => {
|
124 |
localStorage.setItem(LS_SELECTED_VOICE, voiceId);
|
125 |
setSelectedVoice(voiceId);
|
126 |
+
if (liveAPI.connected) { setIsRestarting(true); liveAPI.disconnect(); }
|
|
|
|
|
|
|
127 |
}, [liveAPI.connected, liveAPI.disconnect]);
|
128 |
|
129 |
const contextValue: AppContextType = {
|
|
|
136 |
customUserName,
|
137 |
customInstructions,
|
138 |
isRestarting,
|
139 |
+
remainingTime,
|
140 |
+
isTimeUp,
|
141 |
+
isTimerActive,
|
142 |
+
startTimer,
|
143 |
+
stopTimer,
|
144 |
};
|
145 |
|
146 |
return <AppContext.Provider value={contextValue}>{children}</AppContext.Provider>;
|