ericthayer

integrate-gemini

0
0
# Install this skill:
npx skills add ericthayer/agents-config --skill "integrate-gemini"

Install specific skill from multi-skill repository

# Description

Implement robust Google Gemini API integration with streaming, error handling, and type safety.

# SKILL.md


name: integrate-gemini
description: Implement robust Google Gemini API integration with streaming, error handling, and type safety.


integrate-gemini

Use this skill when the user wants to "connect to Gemini", "add AI features", or "fix API generation".

Requirements (from rules/gemini.md)

  1. Safety: NEVER hardcode API keys. Use import.meta.env or similar.
  2. UX: Always show Loading / Streaming states.
  3. Reliability: Handle errors gracefully (network, blocking, rate limits).
  4. Attribution: AI content must be labeled.

Usage

  1. Check for Client Library: Ensure @google/generative-ai is installed. If not, install it.
  2. Create the Hook: Create hooks/useGemini.ts (or similar).
  3. Implement Logic: Use the pattern below to handle the complexities of streaming.

Code Pattern: useGemini Hook

import { useState, useCallback } from 'react';
import { GoogleGenerativeAI, GenerativeModel } from '@google/generative-ai';

// Initialize outside component if key is static, or inside if dynamic
// const genAI = new GoogleGenerativeAI(import.meta.env.VITE_GEMINI_API_KEY);

interface UseGeminiOptions {
  modelName?: string;
  // Gemini 3 Thinking Config
  useThinking?: boolean; 
  thinkingBudget?: number; // e.g., 16384
  onError?: (error: Error) => void;
}

export const useGemini = ({ 
  modelName = "gemini-3-pro-preview", // Updated default to Gemini 3
  useThinking = false,
  thinkingBudget = 16384,
  onError 
}: UseGeminiOptions = {}) => {
  const [response, setResponse] = useState<string>('');
  const [isLoading, setIsLoading] = useState(false);
  const [isStreaming, setIsStreaming] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const generate = useCallback(async (prompt: string, images?: File[]) => {
    setIsLoading(true);
    setIsStreaming(false);
    setError(null);
    setResponse('');

    try {
      const genAI = new GoogleGenerativeAI(import.meta.env.VITE_GEMINI_API_KEY);

      // Determine configuration based on Thinking
      const generationConfig: any = {};
      if (useThinking) {
        generationConfig.thinkingConfig = { thinkingBudget };
        // Must increase maxOutputTokens to accommodate thinking process
        generationConfig.maxOutputTokens = thinkingBudget + 8192; 
      }

      const model = genAI.getGenerativeModel({ 
        model: modelName,
        generationConfig 
      });

      // Prepare contents (Text + optional Images)
      let contents: any[] = [{ text: prompt }];
      if (images && images.length > 0) {
        // Simple example for adding images (implementation varies by input format)
        // Ideally convert File -> Base64 here
        // const imageParts = await Promise.all(images.map(fileToGenerativePart));
        // contents = [...imageParts, { text: prompt }];
      }

      // Use streaming for better UX
      const result = await model.generateContentStream(contents);

      setIsLoading(false); // Initial wait is over, now we stream
      setIsStreaming(true);

      let fullText = '';
      for await (const chunk of result.stream) {
        const chunkText = chunk.text();
        fullText += chunkText;
        setResponse(prev => prev + chunkText);
      }

      setIsStreaming(false);
      return fullText;

    } catch (err: any) {
      const errorObj = err instanceof Error ? err : new Error(String(err));
      setError(errorObj);
      setIsLoading(false);
      setIsStreaming(false);
      if (onError) onError(errorObj);
      console.error("Gemini API Error:", err);
    }
  }, [modelName, useThinking, thinkingBudget, onError]);

  return {
    generate,
    response,
    isLoading,
    isStreaming,
    error,
    attribution: "Generated by Google Gemini" 
  };
};

UI Implementation Tips

  • Thinking UI: If useThinking is true, the isLoading state might be longer. Considering adding a "Reasoning..." label.
  • Cancel Support: Consider passing an AbortSignal to generate for long-running thinking tasks.
  • Images: If using gemini-3-pro-image-preview, use standard generateContent (not stream) and handle base64 output.
  • Disable Submit: disabled={isLoading || isStreaming}
  • Loading Spinner: Show when isLoading is true.
  • Cursor/Typewriter: Show when isStreaming is true.
  • Error Banner: Display error.message if error exists.

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.