Build or update the BlueBubbles external channel plugin for Moltbot (extension package, REST...
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)
- Safety: NEVER hardcode API keys. Use
import.meta.envor similar. - UX: Always show Loading / Streaming states.
- Reliability: Handle errors gracefully (network, blocking, rate limits).
- Attribution: AI content must be labeled.
Usage
- Check for Client Library: Ensure
@google/generative-aiis installed. If not, install it. - Create the Hook: Create
hooks/useGemini.ts(or similar). - 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
useThinkingis true, theisLoadingstate might be longer. Considering adding a "Reasoning..." label. - Cancel Support: Consider passing an
AbortSignaltogeneratefor long-running thinking tasks. - Images: If using
gemini-3-pro-image-preview, use standardgenerateContent(not stream) and handle base64 output. - Disable Submit:
disabled={isLoading || isStreaming} - Loading Spinner: Show when
isLoadingis true. - Cursor/Typewriter: Show when
isStreamingis true. - Error Banner: Display
error.messageiferrorexists.
# 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.