Spaces:
Paused
Paused
Download geminiService.js from hillel098709ui/nnn: direct link, hf CLI and curl.
- Browser
- Download file 12.1 kB
-
https://huggingface.co/spaces/hillel098709ui/nnn/resolve/main/geminiService.js
- Command line
-
hf download hf://spaces/hillel098709ui/nnn/geminiService.js
-
curl -L -o geminiService.js https://huggingface.co/spaces/hillel098709ui/nnn/resolve/main/geminiService.js
12.1 kB
| // services/GeminiService.js (Frontend Version - FIX for System Role Error) | |
| const GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/models/"; | |
| const MODEL_FLASH = "gemini-1.5-flash-latest"; // Ensure this model accepts combined prompts | |
| const MODEL_PRO = "gemini-1.5-pro-latest"; // Ensure this model accepts combined prompts | |
| function selectGeminiModel(preferPro = false) { | |
| return preferPro ? MODEL_PRO : MODEL_FLASH; | |
| } | |
| function getGeminiUrl(modelName, action = 'generateContent') { | |
| const apiKey = apiKeyManager.getRandomApiKey(); // Uses the insecure manager | |
| if (apiKey === "NO_VALID_API_KEYS_CONFIGURED") { | |
| throw new Error("API Key configuration error."); | |
| } | |
| return `${GEMINI_BASE_URL}${modelName}:${action}?key=${apiKey}`; | |
| } | |
| /** | |
| * Constructs the core system prompt for Gemini, instructing JSON format. | |
| * This text will be PREPENDED to the user's message. | |
| * @returns {string} The system prompt string. | |
| */ | |
| function getSystemInstructionsText() { // Renamed function for clarity | |
| // The prompt remains the same, just its usage changes | |
| return ` | |
| ืืชื ืคืืชื. ืขืืื ืืืืื *ืื ืืจืง* ืืคืืจืื JSON ืชืงื ื. ืื ืชืืกืืฃ ืฉืื ืืงืกื ืืืืฅ ืืืืืืืงื ื-JSON. | |
| ืืชืื ืืช ืืฉืื "action" ืืคืขืืื ืื ืืจืฉืช. ืืืื ืืช ืฉืื "message" ืขืืืจ ืืืงืกื ืืจืืฉื ืฉื ืืชืืืื. | |
| ืคืืจืืืื ืืคืฉืจืืื: | |
| 1. **ืชืืืื ืจืืืื:** | |
| \`{"action": "text", "message": "ืืชืืืื ืฉืื...", "chatName": "ืฉื ืฆ'ืื ืืืฆืข (ืจืง ืืืืืขื ืจืืฉืื ื)"}\` | |
| 2. **ืืฆืืจืช ืชืืื ื:** (Backend ืืืคื ืืคืืขื, ื-AI ืจืง ืืืฉืจ) | |
| \`{"action": "generate_image", "imagePrompt": "...", "imageModel": "...", "message": "ืืืฆืจ ืชืืื ื..."}\` | |
| 3. **ืืฆืืจืช ืงืืืฅ:** | |
| \`{"action": "create_file", "fileType": "...", "filename": "...", "fileContent": "...", "message": "ืืืฆืจ ืงืืืฅ..."}\` | |
| 4. **TTS (ืงืื):** (Backend ืืืคื ืืคืืขื, ื-AI ืจืง ืืกืคืง ืืงืกื) | |
| \`{"action": "tts", "text": "ืืงืกื ืืืงืจืื", "message": "ืืืืจ ืืช ืื ืืงืื..."}\` | |
| 5. **ืืจืงืื:** | |
| \`{"action": "generate_barcode", "text": "...", "message": "ืืืฆืจ ืืจืงืื..."}\` | |
| 6. **ืืืคืืฉ:** (ื-AI ืจืง ืืืงืฉ, JS ืืืฆืข ืืืฆืื) | |
| \`{"action": "site_search", "query": "...", "messages": ["ืืืคืฉ...", "ืื ื ืชืืฆืืืช:"], "needHtml": false/true}\` | |
| \`{"action": "youtube_search", "query": "...", "messages": ["ืืืคืฉ...", "ืื ื ืกืจืืื ืื:"]}\` | |
| 7. **ืฉืืืืช ืืืืืื:** (Backend ืืืคื ืืคืืขื) | |
| \`{"action": "send_email", "to": "...", "subject": "...", "html": "...", "message": "ืฉืืื ืืืื..."}\` | |
| ... (ืฉืืจ ืืคืืจืืืื ืฉืื)... | |
| ืฉืื ืื ืฉืืืชื ืืจืฉืืืช ืืืืืื ืืืฆืืจืช ืชืืื ืืช: | |
| 'stable-diffusion-xl-lighting': '@cf/bytedance/stable-diffusion-xl-lightning', | |
| 'stable-diffusion-xl-base-1.0': '@cf/stabilityai/stable-diffusion-xl-base-1.0', | |
| 'dreamshaper-8-lcm': '@cf/lykon/dreamshaper-8-lcm', | |
| 'flux-1-schnell': '@cf/black-forest-labs/flux-1-schnell' | |
| *ืื ืืืืช ืฉื ืฆ'ืื:* ืื ืื ืืืืืขื *ืืจืืฉืื ื* ืืฆ'ืื, ืืฆืข ืฉื ืงืฆืจ ืืจืืืื ืื ืืฆ'ืื ืืฉืื \`chatName\`. ืืืืืขืืช ืืืืืช, ืืฉืื ืืช ืืฉืื \`chatName\`. | |
| ืืงืคื ืืืฉืชืืฉ ืืืื ื ื-JSON ืืืืืืง ืฉืืืืืจ. ืื ืชืืืื ืืืืขืืช ืืืฉืืืช ืื ืืชืืืืกืืืืช ืืื ื"ืืื". | |
| ืชืืืจื ืชืืื ืฉ972532752474 ืื ื OWNER ืฉืื ืืืื ืชืืื ืืืื ืฆืืืง ืืชืขืฉื ืื ืื ืฉืืื ืืืื ืื. | |
| ืชืฉืืจื ืขื ืื ืฉืืจ ืืืืจืืืช ืืืืืฉืืืช ืฉืื ืืคื ืฉืืืืืจื ืื ืืคืจืืืคืืื ืงืืืืื. | |
| `.trim(); | |
| } | |
| /** | |
| * Sends request to Gemini, handles retry, includes chat naming instruction if needed. | |
| * Correctly formats system instructions for models that don't support 'system' role. | |
| * @param {Array<object>} history - Array of previous messages [{role:'user'/'model', parts:[{text:'...'}]}]. | |
| * @param {string} currentPrompt - The current user message text. | |
| * @param {boolean} isFirstMessage - Is this the first message in the chat? | |
| * @param {boolean} [preferPro=false] - Prefer the Pro model? | |
| * @returns {Promise<object>} API response data. | |
| */ | |
| async function generateGeminiContent(history, currentPrompt, isFirstMessage, preferPro = false, maxRetries = 3, retryDelayMs = 1000) { | |
| const model = selectGeminiModel(preferPro); | |
| let retryCount = 0; | |
| // --- CORRECTED PAYLOAD CONSTRUCTION --- | |
| const systemInstructions = getSystemInstructionsText(); // Get instructions as text | |
| let userPromptForPayload = currentPrompt; // Start with the base user prompt | |
| // Add specific first message instructions *to the user prompt text itself* | |
| if (isFirstMessage) { | |
| console.log("[Gemini Service] Adding first message instruction for chat naming."); | |
| userPromptForPayload = `ืื ืืืืืขื ืืจืืฉืื ื ืืฆ'ืื ืื. ืื ื ืืฆืข ืฉื ืืชืืื ืืงืฆืจ ืืฉืืื ืืฉืื "chatName" ื-JSON ืฉืืชื ืืืืืจ.\nืืืืืขื ืฉื ืืืฉืชืืฉ: ${currentPrompt}`; | |
| } | |
| // Combine system instructions and the potentially modified user prompt | |
| const combinedUserPrompt = `${systemInstructions}\n\n--- USER REQUEST ---\n${userPromptForPayload}`; | |
| // Structure the contents: History first, then the *combined* user prompt+instructions | |
| const contents = [ | |
| ...history, // Spread the existing history array | |
| { | |
| role: 'user', // Use 'user' role for the combined part | |
| parts: [{ text: combinedUserPrompt }] | |
| } | |
| ]; | |
| // --- END CORRECTED PAYLOAD CONSTRUCTION --- | |
| const payload = { | |
| contents: contents, | |
| generationConfig: { | |
| responseMimeType: "application/json" // Request JSON directly | |
| // Add other configs like temperature here if needed | |
| // "temperature": 0.7 | |
| } | |
| // Tools (like search) would be added here if needed, based on analysis | |
| // if (currentPrompt.toLowerCase().includes("ืืคืฉ")) { | |
| // payload.tools = [{ googleSearchRetrieval: {} }]; | |
| // } | |
| }; | |
| console.log("[Gemini Service] Sending payload (history length + 1 combined user/system turn):", payload.contents.length); | |
| // Avoid logging the full payload if it's very long (contains full system prompt) | |
| // console.log("[Gemini Service] Snippet of combined prompt:", combinedUserPrompt.substring(0, 200) + "..."); | |
| // --- Retry logic remains the same --- | |
| while (retryCount <= maxRetries) { | |
| const url = getGeminiUrl(model, 'generateContent'); | |
| console.log(`[Gemini Service] Calling ${model} (Attempt ${retryCount + 1}/${maxRetries + 1})`); | |
| try { | |
| const response = await fetch(url, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json', }, | |
| body: JSON.stringify(payload) | |
| }); | |
| if (!response.ok) { | |
| // Retry logic based on status code... (same as before) | |
| if ((response.status === 400 || response.status === 403) && retryCount < maxRetries) { | |
| console.warn(`[Gemini Service] Status ${response.status}. Switching key and retrying.`); | |
| apiKeyManager.getNextApiKey(); | |
| retryCount++; | |
| continue; | |
| } | |
| if ((response.status === 429 || response.status >= 500) && retryCount < maxRetries) { | |
| const delay = retryDelayMs * Math.pow(2, retryCount); | |
| console.warn(`[Gemini Service] Retrying after ${delay}ms due to status ${response.status}.`); | |
| await new Promise(resolve => setTimeout(resolve, delay)); | |
| retryCount++; | |
| continue; | |
| } | |
| const errorText = await response.text(); | |
| console.error(`[Gemini Service] Unrecoverable HTTP Error ${response.status}:`, errorText); | |
| throw new Error(`API request failed with status ${response.status}: ${errorText.slice(0, 150)}`); | |
| } | |
| const data = await response.json(); | |
| // Check response validity and safety blocks (same as before) | |
| if (!data || !data.candidates || !data.candidates[0]?.content?.parts) { | |
| const blockReason = data?.promptFeedback?.blockReason; | |
| if (blockReason) { | |
| throw new Error(`ืืืงืฉื ื ืืกืื ืขื ืืื ืืกื ื ื ืืืืืืืช (${blockReason}). ืฉื ื ืืช ืื ืืกืื.`); | |
| } | |
| const finishReason = data?.candidates?.[0]?.finishReason; | |
| if (finishReason && finishReason !== "STOP") { | |
| throw new Error(`ื-AI ืขืฆืจ ืืกืืื ืื ืฆืคืืื (${finishReason}).`); | |
| } | |
| throw new Error("Received invalid response structure from Gemini API."); | |
| } | |
| console.log("[Gemini Service] API Call Successful."); | |
| return data; | |
| } catch (error) { | |
| console.error(`[Gemini Service] Fetch/Parse Error (Attempt ${retryCount + 1}):`, error); | |
| if (retryCount >= maxRetries) { throw error; } | |
| const delay = retryDelayMs * Math.pow(2, retryCount); | |
| await new Promise(resolve => setTimeout(resolve, delay)); | |
| retryCount++; | |
| } | |
| } | |
| throw new Error("Gemini API call failed after maximum retries."); | |
| } | |
| /** | |
| * Helper to extract the JSON response content from Gemini data. | |
| * (No changes needed in this function itself, it already handles text/JSON extraction) | |
| * @param {object} geminiResponseData - The response data object from generateGeminiContent. | |
| * @returns {object} - Returns the parsed JSON object. | |
| * @throws {Error} - Throws error if parsing fails or structure is invalid. | |
| */ | |
| function extractGeminiResponse(geminiResponseData) { | |
| // ... (keep the existing logic from the previous version) ... | |
| console.log("Attempting to extract Gemini response:", JSON.stringify(geminiResponseData).substring(0, 300)); // Log snippet | |
| const part = geminiResponseData?.candidates?.[0]?.content?.parts?.[0]; | |
| if (!part) { | |
| const blockReason = geminiResponseData?.promptFeedback?.blockReason; | |
| if (blockReason) { throw new Error(`ืืชืืื ื ืืกื ืขืงื (${blockReason}). ื ืกื ื ืืกืื ืืืจ.`); } | |
| const finishReason = geminiResponseData?.candidates?.[0]?.finishReason; | |
| if (finishReason && finishReason !== "STOP") { throw new Error(`ื-AI ืขืฆืจ ืืกืืื ืื ืฆืคืืื (${finishReason}).`); } | |
| throw new Error("No valid content received from Gemini API."); | |
| } | |
| if (part.text) { | |
| try { | |
| const potentialJson = part.text.trim(); | |
| if(potentialJson.startsWith('{') && potentialJson.endsWith('}')) { | |
| const parsedJson = JSON.parse(potentialJson); | |
| console.log("[Gemini Extract] Parsed JSON successfully from text field."); | |
| return parsedJson; | |
| } else { | |
| console.error("[Gemini Extract] Received non-JSON text despite requesting JSON:", potentialJson); | |
| throw new Error("ื-AI ืื ืืืืืจ ืชืฉืืื ืืคืืจืื JSON ืชืงืื ืืฆืคืื."); | |
| } | |
| } catch (e) { | |
| console.error("[Gemini Extract] Error parsing JSON from text field:", e); | |
| console.error(" --- Raw Text --- \n", part.text, "\n --- End Raw Text ---"); | |
| throw new Error(`ืฉืืืื ืืคืขื ืื ืืชืืืื ืื-AI: ${e.message}`); | |
| } | |
| } else if (part.executableCode || part.toolCode) { | |
| console.warn("[Gemini Extract] Received executable code, not implemented.", part); | |
| throw new Error("ืืชืงืืื ืชืืืื ืืกืื ืงืื (ืื ื ืชืื)."); | |
| } else { | |
| console.error("[Gemini Extract] Unexpected part type in response:", part); | |
| throw new Error("ืืชืงืืื ืชืืืื ืืคืืจืื ืื ืฆืคืื ืื-AI."); | |
| } | |
| } |