Building an AI-Powered Browser-Based Spreadsheet

A deep dive into creating a modern spreadsheet with AI-powered formula assistance

April 9, 2025 10 min read

In this article, we'll take a deep dive into how we built our browser-based spreadsheet application, including our recent integration of AI-powered formula assistance. We'll cover the design decisions, implementation details, and challenges we faced while creating a modern, feature-rich spreadsheet that runs entirely in the browser.

Core Architecture

The spreadsheet is built using vanilla JavaScript, HTML, and CSS, with minimal external dependencies. This choice was made to ensure fast loading times and maximum compatibility. The core components include:

  • A grid system for cell layout and navigation
  • Local storage for data persistence
  • Formula parsing and evaluation engine
  • AI-powered formula assistance via Gemini/OpenAI APIs
  • JSON import/export functionality
  • Theme support for light/dark modes
flowchart TB %% Main components with clear hierarchy UI[User Interface] %% Core components in a row subgraph Core["Core Components"] direction LR Grid[Grid System] Formula[Formula Engine] AI[AI Assistant] end %% Support components in a row subgraph Support["Support Components"] direction LR Import[Import/Export] Theme[Theme Manager] end %% External components Storage[Local Storage] AIAPI[AI API Services] %% Main UI connections - no crossing UI --> Core UI --> Support %% Core component relationships - minimal crossing Grid <--> Formula Formula <--> AI AI --> AIAPI %% Storage relationships - clean connections Grid --> Storage Import --> Storage %% Style definitions for better appearance classDef interface fill:#f0f8ff,stroke:#4682b4,stroke-width:2px classDef core fill:#f9f9ff,stroke:#9370db,stroke-width:2px classDef support fill:#fff0f5,stroke:#db7093,stroke-width:2px classDef storage fill:#fffaf0,stroke:#daa520,stroke-width:2px classDef external fill:#f0fff0,stroke:#3cb371,stroke-width:2px %% Apply styles class UI interface class Grid,Formula,AI core class Import,Theme support class Storage storage class AIAPI external %% Style subgraphs style Core fill:#f9f9ff,stroke:#9370db,stroke-width:1px style Support fill:#fff0f5,stroke:#db7093,stroke-width:1px

Figure 1: High-level architecture of the spreadsheet application

Grid System Implementation

The spreadsheet grid is implemented using a table structure with dynamic row and column headers. Key features include:

  • Dynamic cell creation and management
  • Keyboard navigation support
  • Cell selection and editing
  • Row and column operations (insert/delete)
// Example of cell creation
function createCell(row, col) {
    const cell = document.createElement('td');
    cell.contentEditable = true;
    cell.dataset.row = row;
    cell.dataset.col = col;
    cell.dataset.address = `${COLUMN_NAMES[col]}${row + 1}`;
    return cell;
}

Data Management

Data persistence is handled through the browser's local storage API. The spreadsheet data is stored as a JSON object where keys are cell addresses and values are either raw data or formulas:

// Example of data structure
{
    "A1": "Hello",
    "B2": "=SUM(A1:A5)",
    "C3": "42"
}

Auto-save functionality ensures that changes are persisted immediately, preventing data loss.

Formula Engine

The formula engine supports mathematical operations, range references, and a variety of functions. Key features include:

  • Basic arithmetic operations (+, -, *, /)
  • Range references (A1:A5)
  • Built-in functions (SUM, AVERAGE, MIN, MAX, COUNT)
  • Error handling and validation
  • Automatic formula updates when referenced cells change
  • Dependency tracking for both individual cells and ranges
  • Circular reference detection and handling
// Example of formula evaluation with dependency tracking
function calculateFormula(formula, cellAddress) {
    if (isCalculating) {
        return '#CIRCULAR!';
    }
    
    isCalculating = true;
    
    try {
        // Remove the = sign
        formula = formula.substring(1);
        
        // Handle function calls
        const functionMatch = formula.match(/^([A-Z]+)\((.*)\)$/);
        if (functionMatch) {
            const [_, funcName, argsStr] = functionMatch;
            const func = FUNCTIONS[funcName];
            
            if (!func) {
                return '#NAME?';
            }
            
            // Parse arguments and track dependencies
            const args = [];
            let currentArg = '';
            let inQuotes = false;
            let bracketCount = 0;
            
            for (let i = 0; i < argsStr.length; i++) {
                const char = argsStr[i];
                
                if (char === '"' && argsStr[i-1] !== '\\') {
                    inQuotes = !inQuotes;
                } else if (char === '(' && !inQuotes) {
                    bracketCount++;
                } else if (char === ')' && !inQuotes) {
                    bracketCount--;
                } else if (char === ',' && !inQuotes && bracketCount === 0) {
                    args.push(currentArg.trim());
                    currentArg = '';
                    continue;
                }
                
                currentArg += char;
            }
            
            if (currentArg) {
                args.push(currentArg.trim());
            }
            
            // Process arguments and track dependencies
            const processedArgs = args.map(arg => {
                // Handle cell references
                if (/^[A-Z]+\d+$/.test(arg)) {
                    // Track dependency
                    if (!cellDependencies[arg]) {
                        cellDependencies[arg] = new Set();
                    }
                    cellDependencies[arg].add(cellAddress);
                    return getCellValue(arg);
                }
                
                // Handle ranges
                if (arg.includes(':')) {
                    const [start, end] = arg.split(':');
                    const rangeValues = getRangeValues(start, end);
                    // Track dependencies for each cell in the range
                    const rangeCells = getRangeCells(start, end);
                    rangeCells.forEach(rangeCell => {
                        if (!cellDependencies[rangeCell]) {
                            cellDependencies[rangeCell] = new Set();
                        }
                        cellDependencies[rangeCell].add(cellAddress);
                    });
                    return rangeValues;
                }
                
                return arg;
            });
            
            // Call the function
            const result = func(processedArgs);
            isCalculating = false;
            return result;
        }
        
        // Handle arithmetic expressions
        const result = evaluateArithmetic(formula);
        isCalculating = false;
        return result;
    } catch (error) {
        isCalculating = false;
        return '#ERROR!';
    }
}

AI-Powered Formula Assistance

One of the most innovative features of our spreadsheet is AI-powered formula assistance. This feature allows users to describe what they want to calculate in natural language, and the system suggests appropriate formulas using the built-in functions.

RAG Architecture

We implemented a Retrieval-Augmented Generation (RAG) approach where the system:

  1. Retrieves the complete list of available spreadsheet functions and their descriptions
  2. Sends this context along with the user's natural language query to an LLM
  3. Receives formula suggestions that are guaranteed to work with the existing function set
sequenceDiagram participant User participant UI participant Context as Context Builder participant API as AI API (Gemini/OpenAI) User->>UI: Enter "=describe sum of cells in B column" UI->>Context: Request function context Context->>Context: Compile available functions Context->>Context: Format function descriptions and syntax Context->>API: Send prompt with: Note right of API: 1. User query
2. Available functions list
3. Function descriptions
4. Response format instructions API->>API: Generate formula suggestions API->>UI: Return JSON formatted suggestions UI->>UI: Parse and display suggestions User->>UI: Select preferred formula UI->>UI: Apply formula to cell

Figure 2: Sequence diagram of AI formula assistance

Implementation Details

The RAG implementation includes several components:

flowchart TB %% Main components with improved top-down layout NL[Natural Language Query] %% Context preparation section subgraph CP["Context Preparation"] FC[Function Catalog] CD[Categories Dictionary] FS[Function Syntax] P[Prompt Engineering] end %% API integration section subgraph API["API Integration"] REQ[Request Formatting] RES[Response Parsing] end %% UI elements section subgraph UI["UI Elements"] SD[Suggestion Display] IR[Interactive Results] end %% Flow connections with cleaner layout NL --> P FC --> P CD --> P FS --> P P --> REQ REQ --> RES RES --> SD SD --> IR %% Style definitions for better appearance classDef input fill:#f0f8ff,stroke:#4682b4,stroke-width:2px classDef context fill:#fff0f5,stroke:#db7093,stroke-width:2px classDef api fill:#f5fffa,stroke:#3cb371,stroke-width:2px classDef ui fill:#fffaf0,stroke:#ffa500,stroke-width:2px %% Apply styles class NL input class FC,CD,FS,P context class REQ,RES api class SD,IR ui %% Specify subgraph styles style CP fill:#fff0f5,stroke:#db7093,stroke-width:1px style API fill:#f5fffa,stroke:#3cb371,stroke-width:1px style UI fill:#fffaf0,stroke:#ffa500,stroke-width:1px

Figure 3: Components of the AI formula assistance feature

The implementation supports both Google's Gemini API and OpenAI-compatible endpoints. Users can configure their preferred AI service through the settings dialog:

// Get AI formula suggestions from the API
async function getAIFormulaSuggestions(query) {
    // Get appropriate endpoint based on API URL
    const isOpenAIEndpoint = aiSettings.apiUrl.includes('openai');
    const endpoint = isOpenAIEndpoint
        ? aiSettings.apiUrl
        : `${aiSettings.apiUrl}${aiSettings.model}:generateContent?key=${aiSettings.apiKey}`;
    
    // Build detailed function context
    const availableFunctions = Object.keys(FUNCTIONS);
    const functionCategories = Object.entries(FUNCTION_CATEGORIES).map(([category, funcs]) => {
        return `${category}: ${funcs.join(', ')}`;
    }).join('\n');
    
    // Build detailed function descriptions
    const functionList = Object.entries(FUNCTIONS)
        .map(([name, func]) => {
            const description = typeof func === 'function' 
                ? `Function to ${name.toLowerCase()} values` 
                : (func.description || '');
            const syntax = func.syntax || `${name}(values)`;
            return `${name}: ${description}\nSyntax: ${syntax}\nCategory: ${getCategoryForFunction(name)}`;
        })
        .join('\n\n');
    
    // Clear instruction with strict function list
    const promptText = `You are a spreadsheet formula assistant for a specific spreadsheet application. 
Generate formula suggestions based on the user's natural language description.

IMPORTANT: You must ONLY suggest formulas using the functions listed below. Do not suggest any functions not in this list.

Available Function Categories:
${functionCategories}

Detailed Function Reference:
${functionList}

Complete List of Available Functions (ONLY use these): ${availableFunctions.join(', ')}

User Description: "${query}"

Return exactly 3 appropriate formula suggestions with explanations. Format your response as JSON...`;

    // Make API request and process response...
}

User Experience

From the user's perspective, the flow is simple:

  1. The user types =describe followed by their natural language query
  2. The system displays a loading indicator while processing
  3. A popup shows formula suggestions with explanations
  4. The user clicks on their preferred formula to apply it to the cell

This feature dramatically improves usability for users who may not be familiar with spreadsheet functions or syntax.

User Interface

The UI is designed to be clean and intuitive, with features like:

  • Toolbar for common operations
  • Cell formatting and styling
  • Theme support with light/dark modes
  • AI settings configuration
  • Responsive design for different screen sizes

Challenges and Solutions

Some of the key challenges we faced and how we solved them:

  • Formula Dependencies: Implemented a dependency graph to track and update dependent cells
  • Performance: Optimized cell updates and formula evaluation for large spreadsheets
  • Data Integrity: Added validation and error handling for formulas and data input
  • AI Integration: Developed a precise prompt engineering approach to ensure suggested formulas use only available functions
  • API Compatibility: Created an abstraction layer that supports both Gemini and OpenAI-compatible endpoints
  • Security: Storing API keys securely in local storage with appropriate authorization headers
flowchart LR subgraph Challenges C1[Formula Dependencies] C2[Performance] C3[Data Integrity] C4[AI Integration] C5[API Compatibility] C6[Security] end subgraph Solutions S1[Dependency Graph] S2[Optimized Updates] S3[Validation & Error Handling] S4[RAG Architecture] S5[API Abstraction Layer] S6[Secure Storage] end C1 --> S1 C2 --> S2 C3 --> S3 C4 --> S4 C5 --> S5 C6 --> S6

Figure 4: Challenges and corresponding solutions

Conclusion

Building a browser-based spreadsheet with AI-powered formula assistance was an interesting challenge that required careful consideration of performance, user experience, and integration of external AI services. The result is a lightweight, feature-rich application that demonstrates the power of modern web technologies combined with AI capabilities.

Future improvements could include:

  • More advanced AI features like data analysis and chart suggestions
  • Better formula error correction and debugging
  • Expanded function library
  • Collaborative features
  • Export to other formats (CSV, Excel)