Gemini Chat
Hi! I'm Gemini. How can I help you today?
Write a browser-side app that uses google's gemini pro api to chat in a standalone HTML. Requirements are listed as follows: (1) Save my Gemini key in a local storage. (2) Support the conversation history and save it a local storage. Add a button to clear the conversation history so that gemini is aware of the context of previous messages. Allow the user to set the token usage threshold, compare it with tokens used in the metadata of gemini pro api response and alert the user if beyond the threshold. (3) Use marked.js to display Gemini messages. (4) Use the textarea element for user to paste multi-line text. (5) Use highlight.js to check the code language automatically and highlight its code block if any. (6) Add one feature that uses free Google Translate API (https://translate.googleapis.com/translate_a/single) to translate the Gemini message to any language and append the translated message to the "normal" Gemini message. Provide an HTML element for the user to enable/disable the translation feature. Use select and option elements for the user to select output language which defaults to Simplified Chinese. Languages also support Traditional Chinese, Japanese, English and Spanish. Note that the translated text may not have the same format as the input text and marked.js may not handle it well. (7) Maximize the display window. Maximize the message window and minimize control/config buttons except the send message button.

Okay, here is a standalone HTML file that incorporates all the requested features for a Gemini Pro chat application.

Key Features Implemented:

  1. API Key Storage: Saves/loads your Gemini API key in localStorage.
  2. Conversation History:
    • Maintains chat context.
    • Saves history to localStorage.
    • "Clear History" button resets context and storage.
    • Token threshold setting with alert on exceeding limit.
  3. Markdown Rendering: Uses marked.js for Gemini's responses.
  4. Multi-line Input: Uses <textarea>.
  5. Code Highlighting: Uses highlight.js with automatic language detection.
  6. Translation:
    • Uses the free Google Translate API.
    • Enable/disable checkbox.
    • Language selection (Defaults to Simplified Chinese, includes Traditional Chinese, Japanese, English, Spanish).
    • Appends translation below the original message.
  7. UI: Maximized layout, prominent message window, minimal controls panel (except Send button).

Important Notes:

  • API Key Security: Storing the API key in localStorage is convenient for personal use but is not secure if others can access your browser or if the code were hosted publicly. Be careful with your key.
  • Free Google Translate API: This is an unofficial endpoint. It might change or become unavailable without notice. It also has usage limits. Formatting might be lost during translation.
  • Standalone: This file includes library code directly (via CDN links). For true offline use, you would need to download the marked.min.js and highlight.min.js files and potentially the CSS theme, then change the <script> and <link> tags to point to local files. For simplicity and direct usability, this version uses CDNs.
  • Error Handling: Basic error handling is included, but complex network or API issues might require more robust handling.
  • Token Counting: Gemini's totalTokenCount includes both input and output tokens for the current turn, plus system instructions if any. The history contributes to the input tokens.

gemini_chat.html (Save this code as an HTML file)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Gemini Pro Chat</title>
    <!-- highlight.js CSS (choose a theme) -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
    <style>
        :root {
            --user-bg: #e1f5fe;
            --gemini-bg: #f1f8e9;
            --border-color: #ccc;
            --control-bg: #f5f5f5;
            --text-color: #333;
            --error-color: #d32f2f;
            --button-bg: #4CAF50;
            --button-text: white;
        }

        html, body {
            height: 100%;
            margin: 0;
            padding: 0;
            font-family: sans-serif;
            background-color: #e0e0e0; /* Give body a slight background */
            color: var(--text-color);
            display: flex;
            flex-direction: column;
        }

        #app-container {
            display: flex;
            flex-direction: column;
            height: 100vh; /* Full viewport height */
            width: 100%;
            max-width: 1200px; /* Optional: Max width for very large screens */
            margin: 0 auto; /* Center the container */
            background-color: white;
            box-shadow: 0 0 10px rgba(0,0,0,0.1);
        }

        #chatbox {
            flex-grow: 1; /* Takes up available space */
            overflow-y: auto;
            padding: 15px;
            border-bottom: 1px solid var(--border-color);
            background-color: #ffffff;
            scroll-behavior: smooth;
        }

        .message {
            margin-bottom: 15px;
            padding: 10px 15px;
            border-radius: 8px;
            max-width: 90%;
            word-wrap: break-word; /* Ensure long words break */
            line-height: 1.5;
        }

        .user-message {
            background-color: var(--user-bg);
            margin-left: auto; /* Align to right */
            text-align: left; /* Keep text left-aligned within the bubble */
            border-bottom-right-radius: 0;
         }

        .gemini-message {
            background-color: var(--gemini-bg);
            margin-right: auto; /* Align to left */
            border-bottom-left-radius: 0;
         }

        /* Styling for Markdown content */
        .gemini-message h1, .gemini-message h2, .gemini-message h3 { margin-top: 0.8em; margin-bottom: 0.4em; }
        .gemini-message p { margin-top: 0; margin-bottom: 0.8em; }
        .gemini-message ul, .gemini-message ol { margin-bottom: 0.8em; padding-left: 25px;}
        .gemini-message li { margin-bottom: 0.3em; }
        .gemini-message code:not(pre code) { /* Inline code */
            background-color: #eee;
            padding: 0.2em 0.4em;
            border-radius: 3px;
            font-size: 85%;
        }
        .gemini-message pre { /* Code blocks */
            background-color: #2d2d2d; /* Dark background for code */
            color: #f8f8f2; /* Light text for code */
            padding: 15px;
            border-radius: 5px;
            overflow-x: auto; /* Allow horizontal scrolling for long code lines */
            margin-bottom: 1em;
        }
        .gemini-message pre code {
            background-color: transparent; /* Override inline style */
            padding: 0;
            font-size: 90%;
            line-height: 1.4;
        }
        .gemini-message blockquote {
          margin: 1em 0;
          padding-left: 1em;
          border-left: 4px solid #ccc;
          color: #666;
        }
        .gemini-message table {
          border-collapse: collapse;
          margin-bottom: 1em;
          width: auto; /* Don't force full width */
        }
        .gemini-message th, .gemini-message td {
          border: 1px solid #ccc;
          padding: 8px 12px;
          text-align: left;
        }
        .gemini-message th {
          background-color: #f2f2f2;
          font-weight: bold;
        }
        .gemini-message hr {
            border: none;
            border-top: 1px solid #eee;
            margin: 1em 0;
        }
        .gemini-message a {
          color: #007bff;
          text-decoration: none;
        }
        .gemini-message a:hover {
          text-decoration: underline;
        }


        #input-area {
            display: flex;
            padding: 10px;
            border-top: 1px solid var(--border-color);
            background-color: white; /* Ensure consistent background */
        }

        #userInput {
            flex-grow: 1;
            min-height: 40px; /* Start height */
            max-height: 150px; /* Max height before scrolling */
            padding: 10px;
            border: 1px solid var(--border-color);
            border-radius: 5px;
            margin-right: 10px;
            resize: vertical; /* Allow vertical resize */
            font-size: 1em;
            line-height: 1.4;
        }

        #sendButton {
            padding: 0 25px; /* Make button wider */
            height: auto; /* Adjust height based on content/padding */
            min-height: 40px; /* Match textarea initial height */
            align-self: flex-end; /* Align to bottom if textarea grows */
            background-color: var(--button-bg);
            color: var(--button-text);
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 1em;
            transition: background-color 0.2s;
        }
        #sendButton:hover {
            background-color: #45a049;
        }
        #sendButton:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }

        #controls {
            padding: 8px 15px;
            background-color: var(--control-bg);
            border-top: 1px solid var(--border-color);
            display: flex;
            flex-wrap: wrap; /* Allow wrapping on small screens */
            gap: 15px; /* Spacing between control items */
            align-items: center;
            font-size: 0.9em;
        }
        .control-group {
            display: flex;
            align-items: center;
            gap: 5px;
        }
        #controls label {
           white-space: nowrap; /* Prevent label text wrapping */
        }
        #controls input[type="text"],
        #controls input[type="password"],
        #controls input[type="number"],
        #controls select {
            padding: 5px 8px;
            border: 1px solid #ccc;
            border-radius: 3px;
            font-size: 0.95em;
        }
         #controls input[type="checkbox"] {
            margin-right: 3px;
            cursor: pointer;
         }
         #controls button {
            padding: 5px 10px;
            background-color: #6c757d;
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            font-size: 0.9em;
            transition: background-color 0.2s;
         }
         #controls button:hover {
            background-color: #5a6268;
         }
         #clearHistoryButton {
             background-color: #dc3545;
         }
         #clearHistoryButton:hover {
             background-color: #c82333;
         }

        .loading-indicator {
            text-align: center;
            padding: 10px;
            font-style: italic;
            color: #666;
        }
        .error-message {
            color: var(--error-color);
            background-color: #ffcdd2;
            border: 1px solid var(--error-color);
        }
        .translation-block {
            margin-top: 15px;
            padding-top: 10px;
            border-top: 1px dashed #aaa;
            font-size: 0.95em;
            color: #555;
        }
        .translation-block strong {
            color: #333;
        }

    </style>
</head>
<body>
    <div id="app-container">
        <div id="controls">
            <div class="control-group">
                <label for="apiKey">API Key:</label>
                <input type="password" id="apiKey" placeholder="Enter Gemini API Key">
                <button id="saveApiKeyButton">Save</button>
                <span id="apiKeyStatus"></span>
            </div>
            <div class="control-group">
                <label for="tokenThreshold">Token Alert:</label>
                <input type="number" id="tokenThreshold" min="1" value="3000">
                <button id="saveTokenThresholdButton">Set</button>
            </div>
             <div class="control-group">
                <label for="enableTranslation">Translate:</label>
                <input type="checkbox" id="enableTranslation">
                <select id="translateLang">
                    <option value="zh-CN">Simplified Chinese</option>
                    <option value="zh-TW">Traditional Chinese</option>
                    <option value="ja">Japanese</option>
                    <option value="en">English</option>
                    <option value="es">Spanish</option>
                </select>
            </div>
            <div class="control-group">
                 <button id="clearHistoryButton">Clear History</button>
            </div>
        </div>

        <div id="chatbox">
            <!-- Messages will be appended here -->
        </div>

        <div id="input-area">
            <textarea id="userInput" placeholder="Enter your message (Shift+Enter for new line)" rows="1"></textarea>
            <button id="sendButton" disabled>Send</button>
        </div>
    </div>

    <!-- Marked.js for Markdown parsing -->
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <!-- highlight.js for code syntax highlighting -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>

    <script>
        const GEMINI_API_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=";
        const TRANSLATE_API_URL = "https://translate.googleapis.com/translate_a/single?client=gtx&dt=t"; // sl=auto&tl={targetLang}&q={text}

        // DOM Elements
        const chatbox = document.getElementById('chatbox');
        const userInput = document.getElementById('userInput');
        const sendButton = document.getElementById('sendButton');
        const apiKeyInput = document.getElementById('apiKey');
        const saveApiKeyButton = document.getElementById('saveApiKeyButton');
        const apiKeyStatus = document.getElementById('apiKeyStatus');
        const clearHistoryButton = document.getElementById('clearHistoryButton');
        const tokenThresholdInput = document.getElementById('tokenThreshold');
        const saveTokenThresholdButton = document.getElementById('saveTokenThresholdButton');
        const enableTranslationCheckbox = document.getElementById('enableTranslation');
        const translateLangSelect = document.getElementById('translateLang');

        // State Variables
        let apiKey = '';
        let conversationHistory = []; // Stores { role: 'user'/'model', parts: [{ text: '...' }] }
        let tokenThreshold = 3000;
        let translationEnabled = false;
        let translateLang = 'zh-CN';
        let isLoading = false;

        // --- Initialization ---

        function initializeApp() {
            loadApiKey();
            loadConversationHistory();
            loadTokenThreshold();
            loadTranslationSettings();
            renderHistory();
            updateSendButtonState();

            // Configure marked.js to use highlight.js
             marked.setOptions({
                highlight: function(code, lang) {
                    const language = hljs.getLanguage(lang) ? lang : 'plaintext';
                    return hljs.highlight(code, { language }).value;
                },
                breaks: true, // Convert single line breaks to <br>
                gfm: true // Enable GitHub Flavored Markdown
            });

            // Event Listeners
            saveApiKeyButton.addEventListener('click', saveApiKey);
            apiKeyInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') saveApiKey(); });
            sendButton.addEventListener('click', sendMessage);
            userInput.addEventListener('keydown', handleUserInputKeydown);
            clearHistoryButton.addEventListener('click', clearConversation);
            saveTokenThresholdButton.addEventListener('click', saveTokenThreshold);
            tokenThresholdInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') saveTokenThreshold(); });
            enableTranslationCheckbox.addEventListener('change', handleTranslationToggle);
            translateLangSelect.addEventListener('change', handleLanguageChange);
        }

        // --- Local Storage Functions ---

        function saveApiKey() {
            const key = apiKeyInput.value.trim();
            if (key) {
                localStorage.setItem('geminiApiKey', key);
                apiKey = key;
                apiKeyStatus.textContent = ' (Saved)';
                apiKeyStatus.style.color = 'green';
                apiKeyInput.value = ''; // Clear input after saving
                updateSendButtonState();
                console.log('API Key saved.');
            } else {
                apiKeyStatus.textContent = ' (Invalid)';
                 apiKeyStatus.style.color = 'red';
            }
        }

        function loadApiKey() {
            const savedKey = localStorage.getItem('geminiApiKey');
            if (savedKey) {
                apiKey = savedKey;
                apiKeyStatus.textContent = ' (Loaded)';
                apiKeyStatus.style.color = 'green';
            } else {
                apiKeyStatus.textContent = ' (Not Set)';
                apiKeyStatus.style.color = 'orange';
            }
        }

        function saveConversationHistory() {
            localStorage.setItem('geminiConversationHistory', JSON.stringify(conversationHistory));
             console.log('History saved:', conversationHistory.length, 'items');
        }

        function loadConversationHistory() {
            const savedHistory = localStorage.getItem('geminiConversationHistory');
            if (savedHistory) {
                try {
                    conversationHistory = JSON.parse(savedHistory);
                    console.log('History loaded:', conversationHistory.length, 'items');
                } catch (e) {
                    console.error('Error parsing saved history:', e);
                    conversationHistory = [];
                    localStorage.removeItem('geminiConversationHistory'); // Clear corrupted data
                }
            } else {
                 conversationHistory = [];
            }
        }

        function saveTokenThreshold() {
            const threshold = parseInt(tokenThresholdInput.value, 10);
            if (!isNaN(threshold) && threshold > 0) {
                tokenThreshold = threshold;
                localStorage.setItem('geminiTokenThreshold', threshold.toString());
                alert(`Token alert threshold set to ${threshold}`);
            } else {
                alert('Please enter a valid positive number for the token threshold.');
                tokenThresholdInput.value = tokenThreshold; // Reset to previous valid value
            }
        }

        function loadTokenThreshold() {
            const savedThreshold = localStorage.getItem('geminiTokenThreshold');
            if (savedThreshold) {
                const parsed = parseInt(savedThreshold, 10);
                if (!isNaN(parsed) && parsed > 0) {
                    tokenThreshold = parsed;
                }
            }
            tokenThresholdInput.value = tokenThreshold;
        }

         function saveTranslationSettings() {
            localStorage.setItem('geminiTranslationEnabled', translationEnabled.toString());
            localStorage.setItem('geminiTranslateLang', translateLang);
        }

        function loadTranslationSettings() {
            const savedEnabled = localStorage.getItem('geminiTranslationEnabled');
            const savedLang = localStorage.getItem('geminiTranslateLang');

            translationEnabled = savedEnabled === 'true';
            enableTranslationCheckbox.checked = translationEnabled;

            if (savedLang) {
                translateLang = savedLang;
                translateLangSelect.value = translateLang;
            } else {
                 translateLangSelect.value = 'zh-CN'; // Default
            }
        }

        // --- Chat & UI Functions ---

        function addMessageToChatbox(text, role, translation = null) {
            const messageDiv = document.createElement('div');
            messageDiv.classList.add('message', role === 'user' ? 'user-message' : 'gemini-message');

            let htmlContent = '';
            if (role === 'model') {
                // Render Gemini message with Marked.js
                try {
                    htmlContent = marked.parse(text);
                } catch (e) {
                    console.error("Marked parsing error:", e);
                    htmlContent = escapeHtml(text); // Fallback to escaped text
                }
            } else {
                // User message - just escape HTML to prevent injection
                htmlContent = escapeHtml(text);
            }

            messageDiv.innerHTML = htmlContent;

             // Append translation if available
            if (translation && translation.text && role === 'model') {
                const translationDiv = document.createElement('div');
                translationDiv.classList.add('translation-block');
                const langName = translateLangSelect.options[translateLangSelect.selectedIndex].text;
                translationDiv.innerHTML = `<strong>Translation (${langName}):</strong><br>${escapeHtml(translation.text)}`; // Escape translated text for safety
                messageDiv.appendChild(translationDiv);
            }


            chatbox.appendChild(messageDiv);

            // Highlight code blocks within the newly added message
            if (role === 'model') {
                messageDiv.querySelectorAll('pre code').forEach((block) => {
                    try {
                       hljs.highlightElement(block);
                    } catch (e) {
                        console.error("Highlight.js error:", e, block);
                    }
                });
            }

            scrollToBottom();
        }

        function renderHistory() {
            chatbox.innerHTML = ''; // Clear existing messages
            conversationHistory.forEach(message => {
                // Assuming history only stores the text part for simplicity here
                // If history stored translations, you'd need to handle that
                 addMessageToChatbox(message.parts[0].text, message.role);
            });
            scrollToBottom();
        }

        function clearConversation() {
            if (confirm('Are you sure you want to clear the conversation history? This cannot be undone.')) {
                conversationHistory = [];
                localStorage.removeItem('geminiConversationHistory');
                chatbox.innerHTML = ''; // Clear display
                addSystemMessage("Conversation history cleared.");
                console.log('History cleared.');
            }
        }

        function handleUserInputKeydown(e) {
            // Send on Enter, add newline on Shift+Enter
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault(); // Prevent default newline behavior
                sendMessage();
            }
             // Auto-resize textarea
            autoResizeTextarea(e.target);
        }

        function autoResizeTextarea(textarea) {
            textarea.style.height = 'auto'; // Temporarily shrink
            textarea.style.height = textarea.scrollHeight + 'px'; // Set to scroll height
        }


        function updateSendButtonState() {
            sendButton.disabled = isLoading || !apiKey || userInput.value.trim() === '';
        }

        function showLoading(show) {
            isLoading = show;
            updateSendButtonState();
            // Optional: Add a visual loading indicator in the chatbox
            const existingIndicator = chatbox.querySelector('.loading-indicator');
            if (show && !existingIndicator) {
                const loadingDiv = document.createElement('div');
                loadingDiv.classList.add('loading-indicator');
                loadingDiv.textContent = 'Gemini is thinking...';
                chatbox.appendChild(loadingDiv);
                scrollToBottom();
            } else if (!show && existingIndicator) {
                existingIndicator.remove();
            }
        }

        function addSystemMessage(text, isError = false) {
             const messageDiv = document.createElement('div');
             messageDiv.classList.add('message', 'system-message');
             if (isError) {
                 messageDiv.classList.add('error-message');
             }
             messageDiv.textContent = text;
             chatbox.appendChild(messageDiv);
             scrollToBottom();
        }


        function scrollToBottom() {
            chatbox.scrollTop = chatbox.scrollHeight;
        }

        // Basic HTML escaping
        function escapeHtml(unsafe) {
            if (!unsafe) return '';
            return unsafe
                 .replace(/&/g, "&amp;")
                 .replace(/</g, "&lt;")
                 .replace(/>/g, "&gt;")
                 .replace(/"/g, "&quot;")
                 .replace(/'/g, "&#039;");
        }

        // --- API Interaction ---

        async function sendMessage() {
            const userText = userInput.value.trim();
            if (!userText || !apiKey || isLoading) {
                return;
            }

            showLoading(true);
            userInput.value = ''; // Clear input immediately
            autoResizeTextarea(userInput); // Resize after clearing
            updateSendButtonState(); // Disable send button while processing

            // Add user message to history and display
            const userMessage = { role: 'user', parts: [{ text: userText }] };
            conversationHistory.push(userMessage);
            addMessageToChatbox(userText, 'user');
            saveConversationHistory(); // Save after adding user message

            try {
                const requestBody = {
                    contents: conversationHistory,
                    // Optional: Add safetySettings, generationConfig if needed
                    // generationConfig: {
                    //   temperature: 0.7,
                    //   topK: 40,
                    //   topP: 0.95,
                    //   maxOutputTokens: 1024,
                    // },
                    // safetySettings: [ ... ]
                };

                const response = await fetch(GEMINI_API_BASE_URL + apiKey, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify(requestBody),
                });

                if (!response.ok) {
                    let errorText = `API Error: ${response.status} ${response.statusText}`;
                    try {
                        const errorData = await response.json();
                        errorText += ` - ${errorData.error?.message || 'No additional details.'}`;
                    } catch (e) { /* Ignore JSON parsing error */ }
                    throw new Error(errorText);
                }

                const data = await response.json();

                // --- Process Gemini Response ---
                if (data.candidates && data.candidates.length > 0 && data.candidates[0].content && data.candidates[0].content.parts) {
                    const geminiText = data.candidates[0].content.parts[0].text;

                    // Token Usage Check
                    if (data.usageMetadata && data.usageMetadata.totalTokenCount) {
                        const tokensUsed = data.usageMetadata.totalTokenCount;
                        console.log(`Tokens used (this turn): ${tokensUsed}`);
                        if (tokensUsed > tokenThreshold) {
                            alert(`Warning: Token usage (${tokensUsed}) exceeded your threshold of ${tokenThreshold}.`);
                        }
                    } else {
                        console.log("Token usage metadata not found in response.");
                    }

                    // Add Gemini response to history
                    const modelMessage = { role: 'model', parts: [{ text: geminiText }] };
                    conversationHistory.push(modelMessage);
                    saveConversationHistory(); // Save after adding model response

                    // Translation (if enabled)
                    let translationResult = null;
                    if (translationEnabled) {
                        try {
                            translationResult = await translateText(geminiText, translateLang);
                        } catch (translateError) {
                             console.error('Translation failed:', translateError);
                             addSystemMessage(`Translation failed: ${translateError.message}`, true);
                        }
                    }

                     // Display Gemini message (with or without translation)
                    addMessageToChatbox(geminiText, 'model', translationResult);

                } else if (data.promptFeedback) {
                    // Handle cases where the prompt was blocked
                    const blockReason = data.promptFeedback.blockReason;
                    const safetyRatings = data.promptFeedback.safetyRatings.map(r => `${r.category}: ${r.probability}`).join(', ');
                    const errorMessage = `Request blocked due to: ${blockReason}. Safety Ratings: [${safetyRatings}]`;
                     addSystemMessage(errorMessage, true);
                     // Remove the last user message from history as it caused the block
                     conversationHistory.pop();
                     saveConversationHistory();
                     console.error("Gemini request blocked:", data.promptFeedback);
                }
                else {
                    console.error("Invalid response structure from Gemini:", data);
                    throw new Error('Received an unexpected response structure from the API.');
                }

            } catch (error) {
                console.error('Error sending message:', error);
                addSystemMessage(`Error: ${error.message}`, true);
                 // Attempt to remove the last user message if the API call failed fundamentally
                // Check if the last message was the user message we just added
                 if (conversationHistory.length > 0 && conversationHistory[conversationHistory.length - 1].role === 'user') {
                     // Heuristic: if the error wasn't a content block, the API call might have failed before Gemini processed it.
                     // Let's keep the user message for retry, unless it's a clear API key error etc.
                     // Or, optionally remove it:
                     // conversationHistory.pop();
                     // saveConversationHistory();
                 }
            } finally {
                showLoading(false);
                userInput.focus(); // Put focus back on input
            }
        }

        // --- Translation ---
        async function translateText(text, targetLang) {
             if (!text) return null;
             const url = `${TRANSLATE_API_URL}&sl=auto&tl=${targetLang}&q=${encodeURIComponent(text)}`;

             try {
                 const response = await fetch(url);
                 if (!response.ok) {
                     throw new Error(`Translation API Error: ${response.status}`);
                 }
                 const data = await response.json();

                 // Extract translated text (structure is complex: nested arrays)
                 let translatedText = '';
                 if (data && data[0] && data[0].length > 0) {
                     data[0].forEach(segment => {
                        if (segment[0]) {
                           translatedText += segment[0];
                        }
                    });
                 }

                if (!translatedText) {
                    console.warn("Translation API returned empty result:", data);
                    return null;
                }

                return { text: translatedText };

             } catch (error) {
                 console.error("Translation fetch error:", error);
                 throw error; // Re-throw to be caught in sendMessage
             }
         }

        function handleTranslationToggle() {
            translationEnabled = enableTranslationCheckbox.checked;
            saveTranslationSettings();
             console.log(`Translation ${translationEnabled ? 'enabled' : 'disabled'}`);
         }

        function handleLanguageChange() {
            translateLang = translateLangSelect.value;
            saveTranslationSettings();
             console.log(`Translation language set to: ${translateLang}`);
         }


        // --- Run ---
        document.addEventListener('DOMContentLoaded', initializeApp);

    </script>
</body>
</html>

How to Use:

  1. Save: Save the code above as an HTML file (e.g., gemini_chat.html).
  2. Get API Key: Obtain a Gemini API key from Google AI Studio (https://aistudio.google.com/app/apikey).
  3. Open: Open the saved HTML file in your web browser (like Chrome, Firefox, Edge).
  4. Enter Key: Paste your Gemini API key into the "API Key" input field at the top and click "Save". The status should change to "(Saved)". The key is stored in your browser's local storage for future visits.
  5. Configure (Optional):
    • Set the "Token Alert" threshold (defaults to 3000) and click "Set".
    • Check the "Translate" box to enable translation and select your desired target language.
  6. Chat: Type your message in the text area at the bottom and click "Send" or press Enter (use Shift+Enter for new lines within the message).
  7. Clear History: Click the "Clear History" button to start a fresh conversation context with Gemini.

This setup provides a functional chat interface directly in your browser, leveraging Gemini Pro and incorporating your specific requirements. Remember the security implications of storing the API key in local storage.

Tokens used: 7952