Building your own AI assistant has never been easier thanks to Google's Gemini API. With just a few lines of code, you can create a powerful, intelligent assistant that can answer questions, generate content, analyze images, and much more - all without needing a complex backend infrastructure.
In this comprehensive guide, we'll build a complete AI assistant from scratch using the Gemini API. You'll learn everything from API setup to creating a beautiful, functional interface that rivals commercial AI assistants.
Table of Contents
- What is Gemini API? - Understanding Google's advanced AI model
- Getting Your API Key - Step-by-step setup guide
- Project Setup - Organizing your files and folders
- Building the HTML Structure - Creating the chat interface
- Creating the CSS Styling - Designing a modern UI
- JavaScript Core Logic - The brain of your assistant
- API Integration - Connecting to Gemini
- Adding Advanced Features - Voice, export, and more
- Deployment Options - Going live with your assistant
- Troubleshooting Guide - Common issues and solutions
What is Gemini API?
Gemini is Google's most advanced AI model family, designed to be multimodal - capable of understanding text, images, audio, video, and code. The Gemini API allows developers to integrate these capabilities directly into their applications, making it one of the most powerful AI tools available today.
Unlike traditional language models that can only process text, Gemini can understand and generate content across multiple formats. This makes it perfect for building versatile AI assistants that can handle a wide range of tasks, from answering questions and writing code to analyzing images and generating creative content.
Key Features of Gemini API:
- Multimodal Understanding: Process and generate text, images, audio, and video in a single model
- Massive Context Window: Handle up to 1 million tokens of context - enough to process entire books
- Multiple Model Sizes: Choose from Gemini Pro, Flash, and Ultra models based on your needs
- Function Calling: Connect to external tools and APIs for extended functionality
- Built-in Safety: Advanced content moderation with configurable safety settings
- Streaming Responses: Real-time text generation for interactive experiences
- Temperature Control: Adjust the creativity vs. determinism balance
- System Instructions: Customize assistant behavior and personality
- Cost-Effective: Free tier available with generous usage limits
Getting Your API Key
To use the Gemini API, you'll need an API key from Google AI Studio. This key acts as your authentication token, allowing your application to access Gemini's powerful AI capabilities. The process is straightforward and takes just a few minutes.
Before starting, make sure you have a Google account. The free tier includes 60 requests per minute, which is more than enough for development and personal projects.
Step-by-Step Process:
- Visit Google AI Studio: Go to makersuite.google.com/app/apikey
- Sign In: Use your Google account credentials
- Get API Key: Click on "Get API Key" or "Create API Key" button
- Create New Key: Click "Create API key in new project" or select an existing project
- Copy Your Key: Copy the generated API key immediately (it won't be shown again)
- Store Securely: Save your API key in a secure location - never share it publicly
- Set Up Billing: If you need higher limits, you'll need to enable billing (free tier is available)
Project Setup
Creating a well-organized project structure is essential for building any application. A clean file structure makes your code easier to maintain, debug, and scale. Here's the recommended organization for your Gemini AI assistant project.
Create a new project folder on your computer with the following structure. Each file serves a specific purpose: HTML for structure, CSS for styling, JavaScript for functionality, and a config file for settings.
gemini-assistant/ │ ├── index.html # Main HTML file - contains the chat interface ├── style.css # CSS styles - all visual design and layout ├── script.js # Main JavaScript - core assistant logic ├── config.js # Configuration - API key and settings ├── README.md # Documentation - project description │ └── assets/ # Images and static files ├── logo.svg # App logo/icon ├── user-avatar.png # User avatar image └── gemini-avatar.png # Gemini bot avatar image Building the HTML Structure
The HTML structure forms the foundation of your AI assistant interface. We'll create a complete chat application with all necessary elements: a header with controls, a scrollable chat area, an input field, and settings modal.
This structure follows modern web best practices with semantic HTML5 elements, accessibility attributes, and a clean separation of concerns. The design is fully responsive and works on all devices from desktop to mobile.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Gemini AI Assistant</title> <link rel="stylesheet" href="style.css" /> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" /> </head> <body> <div class="app-container"> <!-- ===== HEADER ===== --> <header class="app-header"> <div class="header-content"> <div class="logo-area"> <img src="assets/logo.svg" alt="Gemini Logo" class="logo" /> <h1>Gemini Assistant</h1> </div> <div class="header-controls"> <button id="theme-toggle" title="Toggle Theme"> <i class="fas fa-moon"></i> </button> <button id="clear-chat" title="Clear Chat"> <i class="fas fa-trash"></i> </button> <button id="settings-btn" title="Settings"> <i class="fas fa-sliders-h"></i> </button> </div> </div> <div class="model-indicator"> <span id="model-name">gemini-pro</span> <span class="status-dot"></span> <span id="status-text">Ready</span> </div> </header> <!-- ===== MAIN CHAT AREA ===== --> <main class="chat-container"> <div id="chat-box" class="chat-box"> <!-- Welcome Message --> <div class="message ai-message welcome-message"> <div class="avatar"> <img src="assets/gemini-avatar.png" alt="Gemini" /> </div> <div class="message-content"> <div class="message-text"> <h3>👋 Hello! I'm your Gemini Assistant</h3> <p> I'm powered by Google's Gemini AI. I can help you with: </p> <ul> <li>Answering questions</li> <li>Writing and editing text</li> <li>Brainstorming ideas</li> <li>Analyzing documents</li> <li>And much more!</li> </ul> <p>How can I assist you today? 🚀</p> </div> </div> </div> </div> <!-- Typing Indicator --> <div id="typing-indicator" class="typing-indicator hidden"> <div class="avatar"> <img src="assets/gemini-avatar.png" alt="Gemini" /> </div> <div class="typing-dots"> <span></span> <span></span> <span></span> </div> </div> </main> <!-- ===== INPUT AREA ===== --> <footer class="input-area"> <div class="input-container"> <button id="attach-btn" class="icon-btn" title="Attach file"> <i class="fas fa-paperclip"></i> </button> <input type="text" id="user-input" placeholder="Ask me anything..." autocomplete="off" /> <button id="send-btn" class="send-btn" title="Send message"> <i class="fas fa-paper-plane"></i> </button> </div> <div class="input-footer"> <span>Press Enter to send. Gemini may make mistakes.</span> </div> </footer> </div> <!-- ===== SETTINGS MODAL ===== --> <div id="settings-modal" class="modal hidden"> <div class="modal-content"> <h2>Settings</h2> <div class="settings-group"> <label>Model Version:</label> <select id="model-select"> <option value="gemini-pro">Gemini Pro</option> <option value="gemini-1.5-pro">Gemini 1.5 Pro</option> <option value="gemini-1.5-flash">Gemini 1.5 Flash</option> </select> </div> <div class="settings-group"> <label>Temperature: <span id="temp-value">0.7</span></label> <input type="range" id="temperature" min="0" max="2" step="0.1" value="0.7" /> </div> <div class="settings-group"> <label>Max Tokens: <span id="tokens-value">1000</span></label> <input type="range" id="max-tokens" min="100" max="2000" step="100" value="1000" /> </div> <button id="close-settings" class="close-btn">Close</button> </div> </div> <script src="config.js"></script> <script src="script.js"></script> </body> </html> Creating the CSS Styling
Now let's create a beautiful, modern, responsive design for our AI assistant. The CSS plays a crucial role in creating an engaging user experience. We'll implement a clean, professional design with support for both light and dark themes.
The styling includes carefully crafted animations, transitions, and responsive layouts that work seamlessly on all devices. The color scheme follows modern design principles with proper contrast ratios for accessibility.
/* ======================================== CSS VARIABLES & RESET ======================================== */ :root { /* Light Theme Colors */ --bg-primary: #ffffff; --bg-secondary: #f8f9fa; --bg-chat: #f0f2f5; --text-primary: #1a1a1a; --text-secondary: #4a4a4a; --text-muted: #888888; --border-color: #e4e6eb; --shadow-color: rgba(0, 0, 0, 0.1); --message-user: #0084ff; --message-user-text: #ffffff; --message-ai: #f0f2f5; --message-ai-text: #1a1a1a; --input-bg: #ffffff; --header-bg: #ffffff; --accent-color: #4285f4; --hover-color: #f0f2f5; --scrollbar-track: #f1f1f1; --scrollbar-thumb: #c1c1c1; --modal-overlay: rgba(0, 0, 0, 0.5); } [data-theme="dark"] { --bg-primary: #1a1a2e; --bg-secondary: #16213e; --bg-chat: #0f0f23; --text-primary: #ffffff; --text-secondary: #b0b0b0; --text-muted: #6b6b6b; --border-color: #2a2a4a; --shadow-color: rgba(0, 0, 0, 0.3); --message-user: #4a6fa5; --message-user-text: #ffffff; --message-ai: #2a2a4a; --message-ai-text: #e0e0e0; --input-bg: #2a2a4a; --header-bg: #1a1a2e; --accent-color: #6b8cff; --hover-color: #2a2a4a; --scrollbar-track: #2a2a4a; --scrollbar-thumb: #4a4a6a; --modal-overlay: rgba(0, 0, 0, 0.8); } /* Reset */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg-secondary); color: var(--text-primary); height: 100vh; overflow: hidden; transition: background 0.3s ease, color 0.3s ease; } /* ======================================== APP CONTAINER ======================================== */ .app-container { display: flex; flex-direction: column; height: 100vh; max-width: 900px; margin: 0 auto; background: var(--bg-primary); box-shadow: 0 0 40px var(--shadow-color); position: relative; } /* ======================================== HEADER ======================================== */ .app-header { background: var(--header-bg); padding: 16px 24px; border-bottom: 1px solid var(--border-color); flex-shrink: 0; transition: background 0.3s ease, border-color 0.3s ease; } .header-content { display: flex; justify-content: space-between; align-items: center; } .logo-area { display: flex; align-items: center; gap: 12px; } .logo { width: 36px; height: 36px; border-radius: 50%; } .logo-area h1 { font-size: 20px; font-weight: 600; background: linear-gradient(135deg, #4285f4, #34a853, #fbbc04, #ea4335); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .header-controls { display: flex; gap: 8px; } .header-controls button { background: none; border: none; color: var(--text-secondary); padding: 8px 10px; border-radius: 8px; cursor: pointer; font-size: 18px; transition: all 0.2s ease; } .header-controls button:hover { background: var(--hover-color); color: var(--text-primary); } .model-indicator { display: flex; align-items: center; gap: 10px; margin-top: 8px; font-size: 13px; color: var(--text-secondary); } .status-dot { width: 8px; height: 8px; border-radius: 50%; background: #34a853; display: inline-block; animation: pulse-dot 2s infinite; } @keyframes pulse-dot { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } } #status-text { font-weight: 400; } /* ======================================== CHAT BOX ======================================== */ .chat-container { flex: 1; overflow: hidden; display: flex; flex-direction: column; background: var(--bg-chat); transition: background 0.3s ease; } .chat-box { flex: 1; overflow-y: auto; padding: 24px; display: flex; flex-direction: column; gap: 16px; } /* Scrollbar Styling */ .chat-box::-webkit-scrollbar { width: 6px; } .chat-box::-webkit-scrollbar-track { background: var(--scrollbar-track); } .chat-box::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 3px; } /* ======================================== MESSAGES ======================================== */ .message { display: flex; gap: 12px; animation: slide-in 0.3s ease; max-width: 85%; } @keyframes slide-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .user-message { align-self: flex-end; flex-direction: row-reverse; } .ai-message { align-self: flex-start; } .avatar { width: 36px; height: 36px; border-radius: 50%; overflow: hidden; flex-shrink: 0; background: var(--accent-color); display: flex; align-items: center; justify-content: center; } .avatar img { width: 100%; height: 100%; object-fit: cover; } .user-message .avatar { background: #0084ff; } .message-content { display: flex; flex-direction: column; gap: 4px; } .message-text { padding: 12px 16px; border-radius: 16px; font-size: 15px; line-height: 1.6; word-wrap: break-word; } .user-message .message-text { background: var(--message-user); color: var(--message-user-text); border-bottom-right-radius: 4px; } .ai-message .message-text { background: var(--message-ai); color: var(--message-ai-text); border-bottom-left-radius: 4px; } .message-text h3 { font-size: 16px; margin-bottom: 8px; font-weight: 600; } .message-text ul { margin: 8px 0; padding-left: 20px; } .message-text ul li { margin: 4px 0; } .message-time { font-size: 11px; color: var(--text-muted); padding: 0 4px; margin-top: 4px; } .user-message .message-time { text-align: right; } /* Code blocks in messages */ .message-text pre { background: rgba(0, 0, 0, 0.05); border-radius: 8px; padding: 12px; overflow-x: auto; margin: 8px 0; font-size: 13px; } [data-theme="dark"] .message-text pre { background: rgba(255, 255, 255, 0.05); } .message-text code { font-family: 'Courier New', monospace; font-size: 13px; background: rgba(0, 0, 0, 0.05); padding: 2px 6px; border-radius: 4px; } [data-theme="dark"] .message-text code { background: rgba(255, 255, 255, 0.05); } /* Welcome Message */ .welcome-message .message-text { background: var(--message-ai); border: 1px solid var(--border-color); } /* ======================================== TYPING INDICATOR ======================================== */ .typing-indicator { display: flex; gap: 12px; padding: 4px 0; animation: slide-in 0.3s ease; } .typing-indicator.hidden { display: none !important; } .typing-dots { display: flex; gap: 6px; padding: 12px 16px; background: var(--message-ai); border-radius: 16px; border-bottom-left-radius: 4px; } .typing-dots span { width: 8px; height: 8px; background: var(--text-secondary); border-radius: 50%; animation: typing-bounce 1.4s infinite; } .typing-dots span:nth-child(2) { animation-delay: 0.2s; } .typing-dots span:nth-child(3) { animation-delay: 0.4s; } @keyframes typing-bounce { 0%, 60%, 100% { transform: translateY(0); } 30% { transform: translateY(-6px); } } /* ======================================== INPUT AREA ======================================== */ .input-area { background: var(--bg-primary); border-top: 1px solid var(--border-color); padding: 12px 24px; flex-shrink: 0; transition: background 0.3s ease, border-color 0.3s ease; } .input-container { display: flex; align-items: center; gap: 12px; background: var(--input-bg); border: 2px solid var(--border-color); border-radius: 24px; padding: 4px 4px 4px 16px; transition: border-color 0.3s ease, box-shadow 0.3s ease; } .input-container:focus-within { border-color: var(--accent-color); box-shadow: 0 0 0 4px rgba(66, 133, 244, 0.1); } #user-input { flex: 1; background: none; border: none; outline: none; padding: 10px 0; font-size: 15px; font-family: inherit; color: var(--text-primary); } #user-input::placeholder { color: var(--text-muted); } .icon-btn { background: none; border: none; color: var(--text-secondary); padding: 8px 10px; border-radius: 50%; cursor: pointer; font-size: 18px; transition: all 0.2s ease; } .icon-btn:hover { background: var(--hover-color); color: var(--text-primary); } .send-btn { background: var(--accent-color); border: none; color: white; padding: 10px 18px; border-radius: 50px; cursor: pointer; font-size: 18px; transition: all 0.2s ease; display: flex; align-items: center; gap: 6px; } .send-btn:hover:not(:disabled) { transform: scale(1.05); box-shadow: 0 4px 12px rgba(66, 133, 244, 0.3); } .send-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } .input-footer { text-align: center; font-size: 11px; color: var(--text-muted); margin-top: 8px; } /* ======================================== MODAL ======================================== */ .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: var(--modal-overlay); display: flex; align-items: center; justify-content: center; z-index: 1000; backdrop-filter: blur(4px); } .modal.hidden { display: none; } .modal-content { background: var(--bg-primary); padding: 32px; border-radius: 16px; max-width: 480px; width: 90%; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); } .modal-content h2 { margin-bottom: 24px; font-size: 24px; } .settings-group { margin-bottom: 20px; } .settings-group label { display: block; margin-bottom: 6px; font-weight: 500; color: var(--text-secondary); } .settings-group select { width: 100%; padding: 10px 12px; border: 1px solid var(--border-color); border-radius: 8px; background: var(--bg-secondary); color: var(--text-primary); font-size: 14px; font-family: inherit; } .settings-group input[type="range"] { width: 100%; margin: 4px 0; -webkit-appearance: none; background: var(--border-color); height: 4px; border-radius: 2px; outline: none; } .settings-group input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; border-radius: 50%; background: var(--accent-color); cursor: pointer; } .close-btn { width: 100%; padding: 12px; background: var(--accent-color); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; transition: background 0.2s ease; } .close-btn:hover { background: #3b78e7; } /* ======================================== RESPONSIVE DESIGN ======================================== */ /* Tablet */ @media (max-width: 768px) { .app-container { max-width: 100%; } .chat-box { padding: 16px; } .message { max-width: 90%; } .header-content { flex-wrap: wrap; gap: 8px; } .logo-area h1 { font-size: 18px; } .input-area { padding: 8px 12px; } .modal-content { padding: 24px; margin: 16px; } } /* Mobile */ @media (max-width: 480px) { .header-content { flex-direction: column; align-items: flex-start; gap: 8px; } .header-controls { align-self: flex-end; margin-top: -36px; } .logo-area h1 { font-size: 16px; } .chat-box { padding: 12px; } .message { max-width: 95%; gap: 8px; } .message-text { font-size: 14px; padding: 10px 14px; } .avatar { width: 30px; height: 30px; } .input-container { padding: 2px 2px 2px 12px; } #user-input { font-size: 14px; padding: 8px 0; } .send-btn { padding: 8px 14px; font-size: 16px; } .model-indicator { font-size: 11px; } .welcome-message .message-text h3 { font-size: 15px; } .modal-content { padding: 20px; margin: 12px; } } /* ======================================== PRINT STYLES ======================================== */ @media print { .app-header, .input-area, .header-controls, .model-indicator { display: none; } .app-container { height: auto; max-width: 100%; box-shadow: none; } .chat-box { overflow: visible; } .message { break-inside: avoid; } } Config File
The configuration file centralizes all your settings and API credentials. This approach makes it easy to update your API key, change models, and adjust parameters without digging through multiple files.
Remember to replace YOUR_GEMINI_API_KEY_HERE with your actual API key from Google AI Studio. Never commit this file to public repositories or share it with others.
// ======================================== // CONFIGURATION FILE // ======================================== // Replace with your actual Gemini API key // Get it from: https://makersuite.google.com/app/apikey const CONFIG = { API_KEY: 'YOUR_GEMINI_API_KEY_HERE', MODEL: 'gemini-pro', TEMPERATURE: 0.7, MAX_TOKENS: 1000, SYSTEM_PROMPT: 'You are a helpful AI assistant powered by Google Gemini. Provide clear, accurate, and helpful responses.', }; // API Endpoints const API_ENDPOINTS = { GEMINI_PRO: 'https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent', GEMINI_15_PRO: 'https://generativelanguage.googleapis.com/v1/models/gemini-1.5-pro:generateContent', GEMINI_15_FLASH: 'https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent', }; // Export for use in other files if (typeof module !== 'undefined' && module.exports) { module.exports = { CONFIG, API_ENDPOINTS }; } JavaScript Core Logic
Now let's build the complete JavaScript functionality for our AI assistant. This is the brain of your application - it handles user interactions, communicates with the Gemini API, manages conversation history, and updates the UI in real-time.
The JavaScript code follows modern best practices with async/await for API calls, clean event handling, and efficient DOM manipulation. It includes comprehensive error handling, status indicators, and smooth animations.
// ======================================== // GEMINI AI ASSISTANT - MAIN SCRIPT // ======================================== // ===== DOM Elements ===== const chatBox = document.getElementById('chat-box'); const userInput = document.getElementById('user-input'); const sendBtn = document.getElementById('send-btn'); const clearBtn = document.getElementById('clear-chat'); const themeToggle = document.getElementById('theme-toggle'); const settingsBtn = document.getElementById('settings-btn'); const typingIndicator = document.getElementById('typing-indicator'); const modelName = document.getElementById('model-name'); const statusText = document.getElementById('status-text'); // ===== Settings Elements ===== const settingsModal = document.getElementById('settings-modal'); const closeSettings = document.getElementById('close-settings'); const modelSelect = document.getElementById('model-select'); const temperatureInput = document.getElementById('temperature'); const tempValue = document.getElementById('temp-value'); const maxTokensInput = document.getElementById('max-tokens'); const tokensValue = document.getElementById('tokens-value'); // ===== State ===== let currentModel = CONFIG.MODEL || 'gemini-pro'; let temperature = CONFIG.TEMPERATURE || 0.7; let maxTokens = CONFIG.MAX_TOKENS || 1000; let conversationHistory = []; let isProcessing = false; // ======================================== // INITIALIZATION // ======================================== document.addEventListener('DOMContentLoaded', () => { loadSettings(); updateModelName(); userInput.focus(); }); // ======================================== // CORE FUNCTIONS // ======================================== /** * Send a message to the Gemini API */ async function sendMessageToGemini(message) { try { // Update status setStatus('thinking', 'Thinking...'); // Get API key from config const apiKey = CONFIG.API_KEY; if (!apiKey || apiKey === 'YOUR_GEMINI_API_KEY_HERE') { throw new Error('Please set your Gemini API key in config.js'); } // Determine endpoint based on model let endpoint; switch (currentModel) { case 'gemini-pro': endpoint = API_ENDPOINTS.GEMINI_PRO; break; case 'gemini-1.5-pro': endpoint = API_ENDPOINTS.GEMINI_15_PRO; break; case 'gemini-1.5-flash': endpoint = API_ENDPOINTS.GEMINI_15_FLASH; break; default: endpoint = API_ENDPOINTS.GEMINI_PRO; } // Build conversation history const history = conversationHistory.map(msg => ({ role: msg.role === 'user' ? 'user' : 'model', parts: [{ text: msg.content }] })); // Add current message history.push({ role: 'user', parts: [{ text: message }] }); // Build request body const requestBody = { contents: history, generationConfig: { temperature: temperature, maxOutputTokens: maxTokens, topP: 0.95, topK: 40, }, safetySettings: [ { category: 'HARM_CATEGORY_HARASSMENT', threshold: 'BLOCK_MEDIUM_AND_ABOVE' }, { category: 'HARM_CATEGORY_HATE_SPEECH', threshold: 'BLOCK_MEDIUM_AND_ABOVE' }, { category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT', threshold: 'BLOCK_MEDIUM_AND_ABOVE' }, { category: 'HARM_CATEGORY_DANGEROUS_CONTENT', threshold: 'BLOCK_MEDIUM_AND_ABOVE' } ] }; // Make API request const response = await fetch(`${endpoint}?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(requestBody) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error?.message || `HTTP error! status: ${response.status}`); } const data = await response.json(); // Extract response text if (data.candidates && data.candidates.length > 0) { const candidate = data.candidates[0]; if (candidate.content && candidate.content.parts && candidate.content.parts.length > 0) { const reply = candidate.content.parts[0].text; return reply; } } throw new Error('No response from Gemini'); } catch (error) { console.error('Gemini API Error:', error); throw error; } } /** * Add a message to the chat */ function addMessage(content, role, isStreaming = false) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${role === 'user' ? 'user-message' : 'ai-message'}`; const avatar = document.createElement('div'); avatar.className = 'avatar'; const avatarImg = document.createElement('img'); avatarImg.src = role === 'user' ? 'assets/user-avatar.png' : 'assets/gemini-avatar.png'; avatarImg.alt = role === 'user' ? 'User' : 'Gemini'; avatar.appendChild(avatarImg); const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; const textDiv = document.createElement('div'); textDiv.className = 'message-text'; textDiv.innerHTML = formatMessage(content); const timeDiv = document.createElement('div'); timeDiv.className = 'message-time'; timeDiv.textContent = new Date().toLocaleTimeString(); contentDiv.appendChild(textDiv); contentDiv.appendChild(timeDiv); messageDiv.appendChild(avatar); messageDiv.appendChild(contentDiv); chatBox.appendChild(messageDiv); scrollToBottom(); return textDiv; } /** * Format message with markdown-like support */ function formatMessage(text) { if (!text) return ''; // Escape HTML let formatted = text.replace(/&/g, '&').replace(//g, '>'); // Code blocks formatted = formatted.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => { return `${code.trim()}
`;
});
// Inline code
formatted = formatted.replace(/([^]+)`/g, '$1');
// Bold
formatted = formatted.replace(/**([^*]+)**/g, '$1');
// Italic
formatted = formatted.replace(/*([^*]+)*/g, '$1');
// Links
formatted = formatted.replace(/]+)]
(
[
)
]
+
)
([
)
]+)/g, '$1');
// Line breaks
formatted = formatted.replace(/\n/g, '
');
return formatted;
}
/**
Show typing indicator
*/
function showTyping() {
typingIndicator.classList.remove('hidden');
scrollToBottom();
}
/**
Hide typing indicator
*/
function hideTyping() {
typingIndicator.classList.add('hidden');
}
/**
Scroll chat to bottom
*/
function scrollToBottom() {
chatBox.scrollTop = chatBox.scrollHeight;
}
/**
Set status indicator
*/
function setStatus(type, text) {
const statusText = document.getElementById('status-text');
const statusDot = document.querySelector('.status-dot');
statusText.textContent = text;
if (type === 'thinking') {
statusDot.style.background = '#fbbc04';
statusDot.style.animation = 'pulse-dot 0.8s infinite';
} else if (type === 'error') {
statusDot.style.background = '#ea4335';
statusDot.style.animation = 'none';
} else {
statusDot.style.background = '#34a853';
statusDot.style.animation = 'pulse-dot 2s infinite';
}
}
/**
Update model name display
*/
function updateModelName() {
const modelNameMap = {
'gemini-pro': 'Gemini Pro',
'gemini-1.5-pro': 'Gemini 1.5 Pro',
'gemini-1.5-flash': 'Gemini 1.5 Flash'
};
modelName.textContent = modelNameMap[currentModel] || currentModel;
}
// ========================================
// MESSAGE HANDLING
// ========================================
/**
Process and send a message
*/
async function processMessage(message) {
if (isProcessing || !message.trim()) return;
isProcessing = true;
sendBtn.disabled = true;
userInput.disabled = true;
// Add user message
addMessage(message, 'user');
// Add to history
conversationHistory.push({
role: 'user',
content: message
});
// Show typing indicator
showTyping();
try {
// Get response from Gemini
const reply = await sendMessageToGemini(message);
// Hide typing indicator
hideTyping();
// Add AI response
const messageElement = addMessage(reply, 'ai');
// Add to history
conversationHistory.push({
role: 'assistant',
content: reply
});
setStatus('ready', 'Ready');
} catch (error) {
hideTyping();
setStatus('error', 'Error');
// Show error message
const errorMsg = error.message || 'Sorry, I encountered an error. Please try again.';
addMessage(❌ ${errorMsg}, 'ai');
console.error('Error:', error);
}
isProcessing = false;
sendBtn.disabled = false;
userInput.disabled = false;
userInput.focus();
}
/**
Clear chat history
*/
function clearChat() {
if (confirm('Are you sure you want to clear all messages?')) {
// Remove all messages except welcome
const messages = chatBox.querySelectorAll('.message:not(.welcome-message)');
messages.forEach(msg => msg.remove());
// Reset conversation history
conversationHistory = [];
// Reset status
setStatus('ready', 'Ready');
// Show welcome back message
const welcomeDiv = document.createElement('div');
welcomeDiv.className = 'message ai-message';
welcomeDiv.innerHTML = <div class="avatar"> <img src="assets/gemini-avatar.png" alt="Gemini"> </div> <div class="message-content"> <div class="message-text" style="background: var(--message-ai); padding: 12px 16px; border-radius: 16px; border-bottom-left-radius: 4px;"> 🗑️ Chat cleared. How can I help you? </div> <div class="message-time">${new Date().toLocaleTimeString()}</div> </div>;
chatBox.appendChild(welcomeDiv);
scrollToBottom();
}
}
// ========================================
// THEME TOGGLE
// ========================================
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
// Update icon
const icon = themeToggle.querySelector('i');
icon.className = newTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
}
function loadTheme() {
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
const icon = themeToggle.querySelector('i');
icon.className = savedTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
}
// ========================================
// SETTINGS
// ========================================
function loadSettings() {
// Load from localStorage
const savedSettings = localStorage.getItem('gemini_settings');
if (savedSettings) {
try {
const settings = JSON.parse(savedSettings);
currentModel = settings.model || CONFIG.MODEL || 'gemini-pro';
temperature = settings.temperature || CONFIG.TEMPERATURE || 0.7;
maxTokens = settings.maxTokens || CONFIG.MAX_TOKENS || 1000;
// Update UI
modelSelect.value = currentModel;
temperatureInput.value = temperature;
tempValue.textContent = temperature;
maxTokensInput.value = maxTokens;
tokensValue.textContent = maxTokens;
updateModelName();
} catch (e) {
console.warn('Failed to load settings:', e);
}
}
}
function saveSettings() {
const settings = {
model: currentModel,
temperature: temperature,
maxTokens: maxTokens
};
localStorage.setItem('gemini_settings', JSON.stringify(settings));
}
function openSettings() {
settingsModal.classList.remove('hidden');
}
function closeSettingsModal() {
settingsModal.classList.add('hidden');
saveSettings();
}
// ========================================
// EVENT LISTENERS
// ========================================
// Send button
sendBtn.addEventListener('click', () => {
const message = userInput.value.trim();
if (message) {
processMessage(message);
userInput.value = '';
}
});
// Enter key
userInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendBtn.click();
}
});
// Clear chat
clearBtn.addEventListener('click', clearChat);
// Theme toggle
themeToggle.addEventListener('click', toggleTheme);
// Settings
settingsBtn.addEventListener('click', openSettings);
closeSettings.addEventListener('click', closeSettingsModal);
// Settings modal - close on outside click
settingsModal.addEventListener('click', (e) => {
if (e.target === settingsModal) {
closeSettingsModal();
}
});
// Model change
modelSelect.addEventListener('change', (e) => {
currentModel = e.target.value;
updateModelName();
});
// Temperature
temperatureInput.addEventListener('input', (e) => {
temperature = parseFloat(e.target.value);
tempValue.textContent = temperature.toFixed(1);
});
// Max Tokens
maxTokensInput.addEventListener('input', (e) => {
maxTokens = parseInt(e.target.value);
tokensValue.textContent = maxTokens;
});
// Keyboard shortcut: Ctrl+K to focus input
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
userInput.focus();
}
});
// ========================================
// INIT
// ========================================
loadTheme();
loadSettings();
setTimeout(() => userInput.focus(), 100);
console.log('🤖 Gemini AI Assistant initialized successfully!');
console.log(📦 Model: ${currentModel});
console.log(🌡️ Temperature: ${temperature});
console.log(📝 Max Tokens: ${maxTokens});
Adding Advanced Features
Let's enhance your AI assistant with powerful advanced features. These additions transform your basic chatbot into a fully-featured AI assistant with voice input, chat export, quick suggestions, and more.
Each feature is self-contained and can be added individually. The code includes voice recognition using the Web Speech API, chat export functionality, keyboard shortcuts, and a notification system for better user feedback.
// ======================================== // ADVANCED FEATURES // ======================================== /** * Feature 1: Export Chat History * Exports the entire conversation as a text file */ function exportChat() { const messages = chatBox.querySelectorAll('.message:not(.welcome-message)'); let exportText = '=== Gemini Assistant Chat Export ===\n'; exportText += `Exported: ${new Date().toLocaleString()}\n\n`; messages.forEach(msg => { const role = msg.classList.contains('user-message') ? 'User' : 'Gemini'; const textEl = msg.querySelector('.message-text'); const timeEl = msg.querySelector('.message-time'); if (textEl) { const text = textEl.textContent.trim(); const time = timeEl ? timeEl.textContent : ''; exportText += `[${time}] ${role}:\n${text}\n\n`; } }); // Create download const blob = new Blob([exportText], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `chat-export-${Date.now()}.txt`; a.click(); URL.revokeObjectURL(url); } /** * Feature 2: Quick Suggestions * Adds clickable suggestion chips for common questions */ function addQuickSuggestions() { const suggestions = [ 'Explain quantum computing', 'Write a poem about AI', 'What is the meaning of life?', 'Tell me a joke', 'Help me with Python code' ]; const container = document.createElement('div'); container.className = 'suggestions-container'; container.style.cssText = ` display: flex; flex-wrap: wrap; gap: 8px; padding: 8px 0 4px 0; `; suggestions.forEach(text => { const chip = document.createElement('button'); chip.className = 'suggestion-chip'; chip.textContent = text; chip.style.cssText = ` background: var(--message-ai); border: 1px solid var(--border-color); border-radius: 20px; padding: 6px 14px; font-size: 13px; cursor: pointer; color: var(--text-primary); transition: all 0.2s ease; font-family: inherit; `; chip.addEventListener('mouseenter', () => { chip.style.transform = 'translateY(-2px)'; chip.style.boxShadow = '0 4px 8px var(--shadow-color)'; }); chip.addEventListener('mouseleave', () => { chip.style.transform = 'translateY(0)'; chip.style.boxShadow = 'none'; }); chip.addEventListener('click', () => { userInput.value = text; sendBtn.click(); }); container.appendChild(chip); }); // Insert after welcome message const welcome = chatBox.querySelector('.welcome-message'); if (welcome) { welcome.after(container); } } /** * Feature 3: Voice Input (Web Speech API) * Allows users to speak their questions */ function startVoiceInput() { if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) { alert('Voice input is not supported in your browser.'); return; } const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; const recognition = new SpeechRecognition(); recognition.lang = 'en-US'; recognition.continuous = false; recognition.interimResults = false; recognition.onstart = () => { setStatus('thinking', 'Listening...'); userInput.placeholder = '🎤 Listening...'; }; recognition.onresult = (event) => { const transcript = event.results[0][0].transcript; userInput.value = transcript; userInput.placeholder = 'Ask me anything...'; setStatus('ready', 'Ready'); setTimeout(() => sendBtn.click(), 500); }; recognition.onerror = (event) => { userInput.placeholder = 'Ask me anything...'; setStatus('ready', 'Ready'); console.warn('Speech recognition error:', event.error); }; recognition.start(); } /** * Feature 4: Keyboard Shortcuts * Adds productivity shortcuts for common actions */ function showShortcuts() { const shortcuts = [ { keys: 'Ctrl+K', description: 'Focus input' }, { keys: 'Ctrl+Enter', description: 'Send message' }, { keys: 'Ctrl+Shift+C', description: 'Clear chat' }, { keys: 'Ctrl+D', description: 'Toggle dark mode' }, ]; let message = '=== Keyboard Shortcuts ===\n\n'; shortcuts.forEach(s => { message += `${s.keys}: ${s.description}\n`; }); alert(message); } /** * Feature 5: Notification System * Shows non-intrusive notifications */ function showNotification(title, message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.style.cssText = ` position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%); padding: 12px 24px; border-radius: 12px; background: var(--bg-primary); border: 1px solid var(--border-color); box-shadow: 0 8px 24px var(--shadow-color); z-index: 9999; max-width: 400px; text-align: center; animation: slide-up 0.3s ease; `; notification.innerHTML = ` <strong>${title}</strong> <p style="margin: 4px 0 0 0; color: var(--text-secondary);">${message}</p> `; document.body.appendChild(notification); setTimeout(() => { notification.style.animation = 'slide-down 0.3s ease'; setTimeout(() => notification.remove(), 300); }, 3000); } // Add notification styles dynamically const styleSheet = document.createElement('style'); styleSheet.textContent = ` @keyframes slide-up { from { opacity: 0; transform: translateX(-50%) translateY(20px); } to { opacity: 1; transform: translateX(-50%) translateY(0); } } @keyframes slide-down { from { opacity: 1; transform: translateX(-50%) translateY(0); } to { opacity: 0; transform: translateX(-50%) translateY(20px); } } `; document.head.appendChild(styleSheet); // ===== Add UI Controls ===== const voiceBtn = document.createElement('button'); voiceBtn.className = 'icon-btn'; voiceBtn.title = 'Voice input'; voiceBtn.innerHTML = '<i class="fas fa-microphone"></i>'; voiceBtn.addEventListener('click', startVoiceInput); const exportBtn = document.createElement('button'); exportBtn.className = 'icon-btn'; exportBtn.title = 'Export chat'; exportBtn.innerHTML = '<i class="fas fa-download"></i>'; exportBtn.addEventListener('click', exportChat); // Add to header controls const headerControls = document.querySelector('.header-controls'); headerControls.appendChild(voiceBtn); headerControls.appendChild(exportBtn); // Add quick suggestions addQuickSuggestions(); console.log('🎯 Advanced features loaded successfully!'); Deployment Options
Now that your AI assistant is complete, it's time to deploy it to the world. Here are several deployment options ranging from simple free services to professional hosting solutions.
Choose the option that best fits your needs. For beginners, GitHub Pages or Netlify are excellent free choices. For production applications, consider using a backend server to securely handle API requests.
# DEPLOYMENT OPTIONS ## 1. GitHub Pages (Free) 1. Create a GitHub repository 2. Push your code to the repository 3. Go to Settings > Pages 4. Select the main branch as source 5. Your site will be available at: username.github.io/repository-name ## 2. Netlify (Free) 1. Create a Netlify account 2. Drag and drop your project folder 3. Or connect your GitHub repository 4. Your site will be automatically deployed ## 3. Vercel (Free) 1. Install Vercel CLI: npm i -g vercel 2. Run: vercel in your project directory 3. Follow the prompts 4. Your site will be deployed ## 4. Cloudflare Pages (Free) 1. Create a Cloudflare account 2. Connect your GitHub repository 3. Configure build settings 4. Deploy automatically ## 5. Firebase Hosting (Free tier) 1. Install Firebase CLI: npm i -g firebase-tools 2. Login: firebase login 3. Initialize: firebase init hosting 4. Deploy: firebase deploy ## 6. AWS S3 + CloudFront (Pay-as-you-go) 1. Create S3 bucket 2. Enable static website hosting 3. Upload files 4. Configure CloudFront for CDN ## 7. Self-Hosting (Any Web Server) 1. Upload files to your server 2. Configure web server (Apache/Nginx) 3. Your site will be accessible via your domain Security Best Practices
Security should be your top priority when building AI applications. Never expose your API keys in client-side code for production applications. Here are several security patterns to protect your application and users.
// ======================================== // SECURITY BEST PRACTICES // ======================================== // NEVER commit your API key to version control // Use environment variables or a backend proxy // OPTION 1: Environment Variables (Node.js backend) // const API_KEY = process.env.GEMINI_API_KEY; // OPTION 2: Backend Proxy (Recommended for production) // Your frontend calls your backend, which then calls Gemini API // This keeps your API key completely hidden from clients // OPTION 3: Key Rotation // Regularly rotate your API keys for better security // ===== Example Backend Proxy (Node.js + Express) ===== /* const express = require('express'); const cors = require('cors'); const axios = require('axios'); const app = express(); app.use(cors()); app.use(express.json()); app.post('/api/gemini', async (req, res) => { try { const response = await axios.post( `https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent?key=${process.env.GEMINI_API_KEY}`, req.body ); res.json(response.data); } catch (error) { res.status(500).json({ error: error.message }); } }); app.listen(3000, () => console.log('Proxy running on port 3000')); */ // Client-side request to proxy: // fetch('/api/gemini', { method: 'POST', body: JSON.stringify(data) }) // ===== Additional Security Measures ===== // 1. Use HTTPS always // 2. Implement rate limiting // 3. Validate user input // 4. Use CORS properly // 5. Keep dependencies updated // 6. Use security headers // 7. Monitor for abuse Troubleshooting Guide
Having issues with your AI assistant? Here's a comprehensive troubleshooting guide covering the most common problems and their solutions.
# COMMON ISSUES AND SOLUTIONS ## 1. API Key Errors - **Error**: "API key not valid" or "Invalid API key" - **Solution**: - Double-check your API key in config.js - Ensure the key is active in Google AI Studio - Check if billing is enabled (if needed) ## 2. CORS Issues - **Error**: "Blocked by CORS policy" - **Solution**: - Use a backend proxy - Or use a CORS proxy service (for development only) ## 3. Rate Limiting - **Error**: "429 Too Many Requests" - **Solution**: - Implement exponential backoff - Cache responses - Upgrade to paid tier if needed ## 4. Model Not Found - **Error**: "Model not found" - **Solution**: - Check model name spelling - Ensure you have access to the model - Use a different model version ## 5. Security Vulnerabilities - **Issue**: API key exposed in browser - **Solution**: - Use backend proxy - Implement API key rotation - Use restricted API keys ## 6. Performance Issues - **Issue**: Slow responses - **Solution**: - Implement loading states - Use streaming responses - Reduce token count - Use Flash model for faster responses ## 7. Mobile Responsiveness - **Issue**: UI breaks on mobile - **Solution**: - Test with Chrome DevTools mobile view - Implement responsive breakpoints - Use relative units (%, rem, vh) Conclusion
Building your own AI assistant using Google's Gemini API is a rewarding project that opens up endless possibilities. Throughout this comprehensive guide, we've covered everything from obtaining your API key to creating a polished, feature-rich chat interface with advanced capabilities.
What You've Learned:
- API Integration: How to connect your application to Gemini's powerful AI models
- UI/UX Design: Creating a modern, responsive chat interface that works on all devices
- Core Features: Message handling, conversation history, typing indicators, and status management
- Advanced Features: Voice input, chat export, quick suggestions, and keyboard shortcuts
- Deployment: Multiple options to host your assistant for free or with paid services
- Security: Best practices for protecting your API keys and user data
- Troubleshooting: Solutions for common issues and error scenarios
Next Steps:
- Customize: Add your own branding, colors, and personality to the assistant
- Extend: Implement additional features like document analysis, image generation, or code execution
- Scale: Move to a backend architecture with user authentication, chat history storage, and rate limiting
- Share: Deploy your assistant and share it with friends, colleagues, or the world
- Contribute: Share your improvements with the developer community
Resources:
- Gemini API Documentation
- Google AI Studio (Get API Key)
- Gemini JavaScript SDK
- Model Comparison
- Google AI Codelabs
The possibilities with Gemini AI are truly endless. Whether you're building a simple chat assistant for personal use or a complex AI-powered application for your business, the foundation we've built here will serve you well.
Remember, the key to building great AI applications is to start simple, iterate quickly, and always keep the user experience at the forefront. Your AI assistant will grow and improve as you add more features and refine the interactions based on user feedback.
Happy building! ✨