File size: 5,562 Bytes
32d0583
6db868f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32d0583
6db868f
32d0583
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6db868f
 
08ff933
6db868f
 
 
 
 
518de1e
6db868f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// src/contexts/AppContext.tsx (نسخه نهایی با قابلیت شناسایی نام در همه شخصیت‌ها)
import React, { createContext, FC, ReactNode, useContext, useEffect, useState, useCallback } from "react";
import { useLiveAPI, type UseLiveAPIResults } from "../hooks/use-live-api";
import { LiveConfig } from "../multimodal-live-types";

const LS_SELECTED_PERSONALITY = 'app_selected_personality';
const LS_CUSTOM_NAME = 'app_custom_name';
const LS_CUSTOM_INSTRUCTIONS = 'app_custom_instructions';

export type PersonalityType = "default" | "teacher" | "poetic" | "funny" | "custom";
export type PersonalityInstructions = Partial<Record<PersonalityType, string>>;

interface AppContextType extends UseLiveAPIResults {
  selectedPersonality: PersonalityType;
  changePersonality: (personality: PersonalityType, customDetails?: { name: string, instructions: string }) => void;
  personalityInstructions: PersonalityInstructions;
  customUserName: string;
  customInstructions: string;
  isRestarting: boolean;
}

const AppContext = createContext<AppContextType | undefined>(undefined);

const BASE_INSTRUCTION = `تو یک دستیار صوتی و تصویری پیشرفته هستی. همیشه به زبان فارسی روان، دقیق و طبیعی صحبت کن. هرگز خودت را به عنوان محصول شرکت دیگری معرفی نکن. در پاسخ‌های خود از ایموجی یا شکلک استفاده نکن.`;

export const AppProvider: FC<{ children: ReactNode; initialConfig?: LiveConfig; personalityInstructions: PersonalityInstructions; url?: string; }> = ({
  children, initialConfig, personalityInstructions, url
}) => {
  const liveAPI = useLiveAPI({ url });

  const [selectedPersonality, setSelectedPersonality] = useState<PersonalityType>(() => (localStorage.getItem(LS_SELECTED_PERSONALITY) as PersonalityType) || 'default');
  const [customUserName, setCustomUserName] = useState<string>(() => localStorage.getItem(LS_CUSTOM_NAME) || '');
  const [customInstructions, setCustomInstructions] = useState<string>(() => localStorage.getItem(LS_CUSTOM_INSTRUCTIONS) || '');
  const [isRestarting, setIsRestarting] = useState(false);

  useEffect(() => {
    // ✅ START: منطق جدید و بهبود یافته برای ساخت دستورالعمل نهایی
    
    // بخش اول: دستورالعمل پایه و جهانی
    let instructionParts = [BASE_INSTRUCTION];

    // بخش دوم: اضافه کردن نام کاربر (اگر وجود داشته باشد) برای همه شخصیت‌ها
    if (customUserName) {
        instructionParts.push(`نام کاربر ${customUserName} است. او را با نامش صدا بزن.`);
    }

    // بخش سوم: اضافه کردن دستورالعمل مخصوص شخصیت انتخاب شده
    if (selectedPersonality === 'custom') {
        // اگر شخصیت اختصاصی بود، از دستورات کاربر استفاده کن
        if (customInstructions) {
            instructionParts.push(customInstructions);
        }
    } else {
        // برای سایر شخصیت‌ها، از دستورالعمل‌های تعریف شده استفاده کن
        const personalityPrompt = personalityInstructions[selectedPersonality];
        if (personalityPrompt) {
            instructionParts.push(personalityPrompt);
        }
    }

    // تمام بخش‌ها را با دو خط جدید از هم جدا می‌کنیم تا برای مدل خوانا باشد
    const finalInstruction = instructionParts.join('\n\n');

    // ✅ END: پایان منطق جدید

    const newConfig: LiveConfig = {
      model: initialConfig?.model || "gemini-2.5-flash-exp-native-audio-thinking-dialog",
      tools: [{ googleSearch: {} }],
      generationConfig: { responseModalities: "audio" },
      systemInstruction: { parts: [{ text: finalInstruction.trim() }] },
    };
    liveAPI.setConfig(newConfig);

  }, [selectedPersonality, customUserName, customInstructions, personalityInstructions, liveAPI.setConfig, initialConfig]);

  useEffect(() => {
    if (isRestarting && !liveAPI.connected) {
      const timer = setTimeout(() => liveAPI.connect().then(() => setIsRestarting(false)), 200);
      return () => clearTimeout(timer);
    }
  }, [isRestarting, liveAPI.connected, liveAPI.connect]);

  const changePersonality = useCallback((newPersonality: PersonalityType, customDetails?: { name: string; instructions: string }) => {
    if (newPersonality === 'custom' && customDetails) {
      localStorage.setItem(LS_CUSTOM_NAME, customDetails.name);
      localStorage.setItem(LS_CUSTOM_INSTRUCTIONS, customDetails.instructions);
      setCustomUserName(customDetails.name);
      setCustomInstructions(customDetails.instructions);
    }
    
    localStorage.setItem(LS_SELECTED_PERSONALITY, newPersonality);
    setSelectedPersonality(newPersonality);

    if (liveAPI.connected) {
      setIsRestarting(true);
      liveAPI.disconnect();
    }
  }, [liveAPI.connected, liveAPI.disconnect]);

  const contextValue: AppContextType = {
    ...liveAPI,
    selectedPersonality,
    changePersonality,
    personalityInstructions,
    customUserName,
    customInstructions,
    isRestarting,
  };

  return <AppContext.Provider value={contextValue}>{children}</AppContext.Provider>;
};

export const useAppContext = (): AppContextType => {
  const context = useContext(AppContext);
  if (!context) throw new Error("useAppContext must be used within an AppProvider");
  return context;
};