Development Guide

Embedding AI in Static Pages

Techniques for adding AI features to static websites using embedded code and client-side implementations

Overview

Static websites can leverage AI capabilities through client-side JavaScript, embedded widgets, and serverless functions. This approach enables dynamic AI features while maintaining the simplicity and performance benefits of static hosting.

Client-Side Integration

Run AI features directly in the browser using JavaScript

Embedded Widgets

Pre-built AI components that can be easily added to any site

Serverless Backend

Use cloud functions for AI processing without managing servers

Approaches to AI Integration

Client-Side Only

  1. Browser-based Models: Use TensorFlow.js or ONNX runtime
  2. WebAssembly: Run optimized AI models in the browser
  3. Pre-trained Models: Load models from CDN for immediate use
  4. Limited Scope: Best for classification, sentiment analysis

Hybrid Approach

  • Client Pre-processing: Handle simple tasks in browser
  • API Calls: Send complex requests to AI services
  • Caching: Store results locally to reduce API calls
  • Progressive Enhancement: Basic functionality without JavaScript

Client-Side AI Implementation

TensorFlow.js

  • Use Cases: Image recognition, NLP
  • Models: Pre-trained or custom models
  • Performance: WebGL acceleration
  • Limitations: Model size constraints

Hugging Face Transformers.js

  • Use Cases: Text generation, classification
  • Models: Transformer-based models
  • Performance: WebAssembly optimized
  • Limitations: Limited model selection

Custom WebAssembly

  • Use Cases: High-performance inference
  • Models: Any ONNX compatible model
  • Performance: Near-native speed
  • Limitations: Complex setup

Embedded Chat Widget

HTML Implementation

<!-- Simple AI Chat Widget -->
<div id="ai-chat-widget">
    <div class="chat-header">
        <h3>AI Assistant</h3>
        <button class="close-btn">×</button>
    </div>
    <div class="chat-messages" id="chat-messages">
        <div class="message bot-message">
            Hello! How can I help you today?
        </div>
    </div>
    <div class="chat-input">
        <input type="text" id="user-input" placeholder="Type your message...">
        <button id="send-btn">Send</button>
    </div>
</div>

<style>
#ai-chat-widget {
    position: fixed;
    bottom: 20px;
    right: 20px;
    width: 350px;
    height: 500px;
    background: white;
    border: 1px solid #ddd;
    border-radius: 10px;
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
    display: flex;
    flex-direction: column;
    z-index: 1000;
}

.chat-header {
    background: #007bff;
    color: white;
    padding: 15px;
    border-radius: 10px 10px 0 0;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.chat-messages {
    flex: 1;
    padding: 15px;
    overflow-y: auto;
    background: #f8f9fa;
}

.message {
    margin: 10px 0;
    padding: 10px 15px;
    border-radius: 15px;
    max-width: 80%;
}

.user-message {
    background: #007bff;
    color: white;
    margin-left: auto;
}

.bot-message {
    background: white;
    border: 1px solid #ddd;
}

.chat-input {
    padding: 15px;
    border-top: 1px solid #ddd;
    display: flex;
    gap: 10px;
}

.chat-input input {
    flex: 1;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
}

.chat-input button {
    background: #007bff;
    color: white;
    border: none;
    padding: 10px 15px;
    border-radius: 5px;
    cursor: pointer;
}
</style>

JavaScript Integration

// AI Chat Widget JavaScript
class AIChatWidget {
    constructor(apiKey, model = 'gpt-3.5-turbo') {
        this.apiKey = apiKey;
        this.model = model;
        this.conversationHistory = [];
        this.init();
    }

    init() {
        this.bindEvents();
        this.loadFromStorage();
    }

    bindEvents() {
        document.getElementById('send-btn').addEventListener('click', () => this.sendMessage());
        document.getElementById('user-input').addEventListener('keypress', (e) => {
            if (e.key === 'Enter') this.sendMessage();
        });
        
        document.querySelector('.close-btn').addEventListener('click', () => {
            this.hideWidget();
        });
    }

    async sendMessage() {
        const input = document.getElementById('user-input');
        const message = input.value.trim();
        
        if (!message) return;

        // Add user message to UI
        this.addMessage(message, 'user');
        input.value = '';

        // Show loading indicator
        this.showLoading();

        try {
            const response = await this.callAIAPI(message);
            this.addMessage(response, 'bot');
            this.saveToStorage();
        } catch (error) {
            this.addMessage('Sorry, I encountered an error. Please try again.', 'bot');
            console.error('AI API Error:', error);
        } finally {
            this.hideLoading();
        }
    }

    async callAIAPI(userMessage) {
        // Add user message to conversation history
        this.conversationHistory.push({ role: 'user', content: userMessage });

        // Keep only last 10 messages to manage context
        if (this.conversationHistory.length > 10) {
            this.conversationHistory = this.conversationHistory.slice(-10);
        }

        const response = await fetch('https://api.openai.com/v1/chat/completions', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${this.apiKey}`
            },
            body: JSON.stringify({
                model: this.model,
                messages: [
                    { role: 'system', content: 'You are a helpful assistant.' },
                    ...this.conversationHistory
                ],
                max_tokens: 500,
                temperature: 0.7
            })
        });

        if (!response.ok) {
            throw new Error(`API Error: ${response.status}`);
        }

        const data = await response.json();
        const aiResponse = data.choices[0].message.content;

        // Add AI response to conversation history
        this.conversationHistory.push({ role: 'assistant', content: aiResponse });

        return aiResponse;
    }

    addMessage(content, sender) {
        const messagesContainer = document.getElementById('chat-messages');
        const messageDiv = document.createElement('div');
        messageDiv.className = `message ${sender}-message`;
        messageDiv.textContent = content;
        
        messagesContainer.appendChild(messageDiv);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    }

    showLoading() {
        const messagesContainer = document.getElementById('chat-messages');
        const loadingDiv = document.createElement('div');
        loadingDiv.id = 'loading-message';
        loadingDiv.className = 'message bot-message';
        loadingDiv.innerHTML = '<div class="loading-dots"><span></span><span></span><span></span></div>';
        
        messagesContainer.appendChild(loadingDiv);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    }

    hideLoading() {
        const loadingMessage = document.getElementById('loading-message');
        if (loadingMessage) {
            loadingMessage.remove();
        }
    }

    saveToStorage() {
        localStorage.setItem('aiChatHistory', JSON.stringify(this.conversationHistory));
    }

    loadFromStorage() {
        const saved = localStorage.getItem('aiChatHistory');
        if (saved) {
            this.conversationHistory = JSON.parse(saved);
            // Display last message if exists
            if (this.conversationHistory.length > 0) {
                const lastMessage = this.conversationHistory[this.conversationHistory.length - 1];
                if (lastMessage.role === 'assistant') {
                    this.addMessage(lastMessage.content, 'bot');
                }
            }
        }
    }

    hideWidget() {
        document.getElementById('ai-chat-widget').style.display = 'none';
    }

    showWidget() {
        document.getElementById('ai-chat-widget').style.display = 'flex';
    }
}

// Initialize the chat widget
// Note: In production, use environment variables for API key
const chatWidget = new AIChatWidget('your-openai-api-key-here');

Serverless AI Integration

Netlify Function Example

// netlify/functions/ai-chat.js
const OpenAI = require('openai');

exports.handler = async function(event, context) {
    // Handle CORS
    const headers = {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Headers': 'Content-Type',
        'Access-Control-Allow-Methods': 'POST, OPTIONS'
    };

    if (event.httpMethod === 'OPTIONS') {
        return {
            statusCode: 200,
            headers,
            body: ''
        };
    }

    if (event.httpMethod !== 'POST') {
        return {
            statusCode: 405,
            headers,
            body: JSON.stringify({ error: 'Method not allowed' })
        };
    }

    try {
        const { message, conversationHistory = [] } = JSON.parse(event.body);
        
        if (!message) {
            return {
                statusCode: 400,
                headers,
                body: JSON.stringify({ error: 'Message is required' })
            };
        }

        const openai = new OpenAI({
            apiKey: process.env.OPENAI_API_KEY
        });

        const messages = [
            {
                role: 'system',
                content: 'You are a helpful assistant. Provide concise and helpful responses.'
            },
            ...conversationHistory.slice(-8), // Keep last 8 messages for context
            { role: 'user', content: message }
        ];

        const completion = await openai.chat.completions.create({
            model: 'gpt-3.5-turbo',
            messages: messages,
            max_tokens: 500,
            temperature: 0.7
        });

        const response = completion.choices[0].message.content;

        return {
            statusCode: 200,
            headers,
            body: JSON.stringify({
                response: response,
                usage: completion.usage
            })
        };

    } catch (error) {
        console.error('Error:', error);
        
        return {
            statusCode: 500,
            headers,
            body: JSON.stringify({ 
                error: 'Internal server error',
                message: error.message 
            })
        };
    }
};

Client-Side Integration with Serverless

// Enhanced chat widget with serverless backend
class ServerlessAIChatWidget extends AIChatWidget {
    constructor() {
        super(); // No API key needed for client
        this.endpoint = '/.netlify/functions/ai-chat';
    }

    async callAIAPI(userMessage) {
        const response = await fetch(this.endpoint, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                message: userMessage,
                conversationHistory: this.conversationHistory
            })
        });

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(errorData.error || 'API request failed');
        }

        const data = await response.json();
        const aiResponse = data.response;

        // Update conversation history
        this.conversationHistory.push(
            { role: 'user', content: userMessage },
            { role: 'assistant', content: aiResponse }
        );

        return aiResponse;
    }
}

// Usage - no API key exposed in client code
const chatWidget = new ServerlessAIChatWidget();

Pre-built AI Widgets

Embeddable Chat Interface

<!-- One-line embed code -->
<script>
(function() {
    var script = document.createElement('script');
    script.src = 'https://cdn.yourdomain.com/ai-widget.js';
    script.setAttribute('data-api-key', 'YOUR_PUBLIC_KEY');
    script.setAttribute('data-theme', 'light');
    script.setAttribute('data-position', 'bottom-right');
    document.head.appendChild(script);
})();
</script>

// ai-widget.js - Self-contained widget
(function() {
    const config = {
        apiKey: document.currentScript.getAttribute('data-api-key'),
        theme: document.currentScript.getAttribute('data-theme') || 'light',
        position: document.currentScript.getAttribute('data-position') || 'bottom-right'
    };

    // Create widget HTML
    const widgetHTML = `
        <div id="ai-widget-container" class="ai-widget ${config.theme} ${config.position}">
            <button id="ai-widget-toggle" class="ai-widget-toggle">
                🤖
            </button>
            <div id="ai-widget-content" class="ai-widget-content hidden">
                <div class="ai-widget-header">
                    <h3>AI Assistant</h3>
                    <button class="ai-widget-close">×</button>
                </div>
                <div class="ai-widget-messages"></div>
                <div class="ai-widget-input">
                    <input type="text" placeholder="Ask me anything...">
                    <button>Send</button>
                </div>
            </div>
        </div>
    `;

    // Inject styles
    const styles = `
        .ai-widget {
            position: fixed;
            z-index: 10000;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        }
        
        .ai-widget.bottom-right {
            bottom: 20px;
            right: 20px;
        }
        
        .ai-widget-toggle {
            width: 60px;
            height: 60px;
            border-radius: 50%;
            background: #007bff;
            color: white;
            border: none;
            font-size: 24px;
            cursor: pointer;
            box-shadow: 0 4px 12px rgba(0,0,0,0.2);
        }
        
        .ai-widget-content {
            position: absolute;
            bottom: 70px;
            right: 0;
            width: 350px;
            height: 500px;
            background: white;
            border-radius: 10px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.15);
            display: flex;
            flex-direction: column;
        }
        
        .ai-widget.light .ai-widget-content {
            background: white;
            color: #333;
        }
        
        .ai-widget.dark .ai-widget-content {
            background: #2d3748;
            color: white;
        }
        
        .hidden {
            display: none !important;
        }
        
        /* Additional styles for chat interface */
    `;

    const styleSheet = document.createElement('style');
    styleSheet.textContent = styles;
    document.head.appendChild(styleSheet);

    // Inject widget HTML
    document.body.insertAdjacentHTML('beforeend', widgetHTML);

    // Initialize widget functionality
    const toggleBtn = document.getElementById('ai-widget-toggle');
    const widgetContent = document.getElementById('ai-widget-content');
    const closeBtn = document.querySelector('.ai-widget-close');

    toggleBtn.addEventListener('click', () => {
        widgetContent.classList.toggle('hidden');
    });

    closeBtn.addEventListener('click', () => {
        widgetContent.classList.add('hidden');
    });

    // Add chat functionality
    // ... similar to previous chat implementation
})();
</script>

AI-Powered Search

// Semantic search for static sites
class AISiteSearch {
    constructor(apiKey, searchIndex) {
        this.apiKey = apiKey;
        this.searchIndex = searchIndex; // Pre-computed embeddings
        this.embeddings = {};
        this.init();
    }

    async init() {
        await this.loadEmbeddings();
        this.setupSearchUI();
    }

    async loadEmbeddings() {
        // Load pre-computed embeddings for site content
        const response = await fetch('/search-index.json');
        this.embeddings = await response.json();
    }

    setupSearchUI() {
        const searchHTML = `
            <div class="ai-search-container">
                <input type="text" id="ai-search-input" placeholder="Search with AI...">
                <div id="ai-search-results" class="ai-search-results"></div>
            </div>
        `;

        // Inject search UI into page
        const searchContainer = document.querySelector('.search-area') || document.body;
        searchContainer.insertAdjacentHTML('beforeend', searchHTML);

        // Add event listeners
        const searchInput = document.getElementById('ai-search-input');
        searchInput.addEventListener('input', this.debounce(() => {
            this.performSearch(searchInput.value);
        }, 300));
    }

    async performSearch(query) {
        if (!query.trim()) {
            this.clearResults();
            return;
        }

        // Get query embedding
        const queryEmbedding = await this.getEmbedding(query);
        
        // Find similar content using cosine similarity
        const results = this.findSimilarContent(queryEmbedding);
        
        this.displayResults(results, query);
    }

    async getEmbedding(text) {
        // Use OpenAI embeddings API
        const response = await fetch('https://api.openai.com/v1/embeddings', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${this.apiKey}`
            },
            body: JSON.stringify({
                model: 'text-embedding-ada-002',
                input: text
            })
        });

        const data = await response.json();
        return data.data[0].embedding;
    }

    findSimilarContent(queryEmbedding, topK = 5) {
        const similarities = [];

        for (const [url, pageData] of Object.entries(this.embeddings)) {
            const similarity = this.cosineSimilarity(queryEmbedding, pageData.embedding);
            similarities.push({
                url: url,
                title: pageData.title,
                content: pageData.content,
                similarity: similarity
            });
        }

        // Sort by similarity and return top results
        return similarities
            .sort((a, b) => b.similarity - a.similarity)
            .slice(0, topK);
    }

    cosineSimilarity(a, b) {
        const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
        const magnitudeA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
        const magnitudeB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
        return dotProduct / (magnitudeA * magnitudeB);
    }

    displayResults(results, query) {
        const resultsContainer = document.getElementById('ai-search-results');
        
        if (results.length === 0) {
            resultsContainer.innerHTML = '<div class="no-results">No results found</div>';
            return;
        }

        const resultsHTML = results.map(result => `
            <div class="search-result">
                <a href="${result.url}" class="result-title">${result.title}</a>
                <div class="result-snippet">${this.generateSnippet(result.content, query)}</div>
                <div class="result-confidence">Relevance: ${(result.similarity * 100).toFixed(1)}%</div>
            </div>
        `).join('');

        resultsContainer.innerHTML = resultsHTML;
    }

    generateSnippet(content, query) {
        // Simple snippet generation - highlight query terms
        const words = query.toLowerCase().split(' ');
        let snippet = content.substring(0, 200);
        
        words.forEach(word => {
            const regex = new RegExp(word, 'gi');
            snippet = snippet.replace(regex, '<mark>$&</mark>');
        });
        
        return snippet + '...';
    }

    clearResults() {
        document.getElementById('ai-search-results').innerHTML = '';
    }

    debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }
}

// Initialize AI search
// const aiSearch = new AISiteSearch('your-api-key', 'search-index.json');

Performance Optimization

Lazy Loading AI Features

// Lazy load AI widgets when needed
class LazyAILoader {
    constructor() {
        this.observers = new Map();
        this.init();
    }

    init() {
        // Observe elements with data-ai-widget attribute
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    this.loadWidget(entry.target);
                    observer.unobserve(entry.target);
                }
            });
        }, {
            rootMargin: '50px', // Load 50px before element is visible
            threshold: 0.1
        });

        // Find all lazy AI widgets
        document.querySelectorAll('[data-ai-widget]').forEach(element => {
            observer.observe(element);
        });
    }

    async loadWidget(element) {
        const widgetType = element.getAttribute('data-ai-widget');
        
        switch(widgetType) {
            case 'chat':
                await this.loadChatWidget(element);
                break;
            case 'search':
                await this.loadSearchWidget(element);
                break;
            case 'summary':
                await this.loadSummaryWidget(element);
                break;
        }
    }

    async loadChatWidget(container) {
        // Dynamically import chat widget
        const { AIChatWidget } = await import('./ai-chat-widget.js');
        new AIChatWidget(container);
    }

    async loadSearchWidget(container) {
        const { AISiteSearch } = await import('./ai-search.js');
        new AISiteSearch(container);
    }

    async loadSummaryWidget(container) {
        // Generate summary for long content
        const content = container.getAttribute('data-content') || container.textContent;
        const summary = await this.generateSummary(content);
        container.innerHTML = summary;
    }

    async generateSummary(text) {
        // Use AI to generate concise summary
        // Implementation depends on your AI provider
        return text.substring(0, 200) + '...'; // Fallback
    }
}

// Initialize lazy loader when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
    new LazyAILoader();
});

Caching Strategies

// Cache AI responses for better performance
class AICache {
    constructor() {
        this.cache = new Map();
        this.maxSize = 100; // Maximum cache entries
    }

    getKey(messages, model) {
        // Create a unique key for the request
        return JSON.stringify({ messages, model });
    }

    get(messages, model) {
        const key = this.getKey(messages, model);
        const entry = this.cache.get(key);
        
        if (entry && Date.now() < entry.expiry) {
            return entry.response;
        }
        
        // Remove expired entry
        this.cache.delete(key);
        return null;
    }

    set(messages, model, response, ttl = 5 * 60 * 1000) { // 5 minutes default
        const key = this.getKey(messages, model);
        
        // Remove oldest entry if cache is full
        if (this.cache.size >= this.maxSize) {
            const firstKey = this.cache.keys().next().value;
            this.cache.delete(firstKey);
        }
        
        this.cache.set(key, {
            response: response,
            expiry: Date.now() + ttl
        });
    }

    clear() {
        this.cache.clear();
    }
}

// Enhanced AI client with caching
class CachedAIClient extends AIChatWidget {
    constructor(apiKey, model = 'gpt-3.5-turbo') {
        super(apiKey, model);
        this.cache = new AICache();
    }

    async callAIAPI(userMessage) {
        const messages = [
            { role: 'system', content: 'You are a helpful assistant.' },
            ...this.conversationHistory,
            { role: 'user', content: userMessage }
        ];

        // Check cache first
        const cachedResponse = this.cache.get(messages, this.model);
        if (cachedResponse) {
            console.log('Using cached response');
            return cachedResponse;
        }

        // Not in cache, make API call
        const response = await super.callAIAPI(userMessage);
        
        // Cache the response
        this.cache.set(messages, this.model, response);
        
        return response;
    }
}

Security Considerations

Risk Mitigation Implementation
API Key Exposure Use serverless functions Netlify/Vercel functions
Rate Limiting Client-side throttling Request queuing and delays
Content Security Input sanitization HTML escaping, content filtering
Data Privacy Minimal data collection Local storage only, no PII

Secure Implementation Checklist

  • Never expose API keys in client-side code
  • Implement rate limiting and request throttling
  • Sanitize all user inputs before processing
  • Use HTTPS for all API communications
  • Implement proper CORS policies
  • Regularly update dependencies and libraries
  • Monitor for unusual usage patterns
  • Provide clear privacy policies to users