aegisai / frontend / src / services / geminiService.ts
geminiService.ts
Raw
/**
 * Gemini Service - AI Analysis API Client
 */

import { GoogleGenerativeAI } from "@google/generative-ai";
import { SYSTEM_INSTRUCTION, GEMINI_CONFIG } from "../constants";
import type { AnalysisResponse } from "../types";

let genAI: GoogleGenerativeAI | null = null;

/**
 * Initialize Gemini API client
 */
export const initGemini = (): void => {
  const apiKey =
    (import.meta as any).env?.VITE_GEMINI_API_KEY ||
    (window as any).env?.VITE_GEMINI_API_KEY;

  if (!apiKey) {
    console.error("❌ VITE_GEMINI_API_KEY is missing");
    console.error("Add it to frontend/.env.local");
    return;
  }

  try {
    genAI = new GoogleGenerativeAI(apiKey);
    console.log("✅ Gemini AI initialized successfully");
  } catch (error) {
    console.error("❌ Failed to initialize Gemini:", error);
  }
};

/**
 * Analyze a video frame for security threats
 */
export const analyzeFrame = async (
  base64Image: string
): Promise<AnalysisResponse | null> => {

  if (!genAI) {
    initGemini();
    if (!genAI) return null;
  }

  try {
    const model = genAI.getGenerativeModel({
      model: GEMINI_CONFIG.MODEL
    });

    // Clean base64
    const cleanBase64 = base64Image.includes(",")
      ? base64Image.split(",")[1]
      : base64Image;

    const result = await model.generateContent([
      SYSTEM_INSTRUCTION,
      {
        inlineData: {
          mimeType: "image/jpeg",
          data: cleanBase64
        }
      }
    ]);

    const response = await result.response;
    const text = response.text();

    if (!text) {
      console.error("Empty response from Gemini");
      return null;
    }

    // Parse JSON safely
    try {
      let cleanText = text.trim();

      if (cleanText.startsWith("```")) {
        cleanText = cleanText
          .replace(/```json/g, "")
          .replace(/```/g, "")
          .trim();
      }

      const parsed = JSON.parse(cleanText) as AnalysisResponse;

      // Normalize severity
      if (parsed.severity) {
        parsed.severity = parsed.severity.toLowerCase();
      }

      // Ensure fields
      if (typeof parsed.incident === "undefined") {
        parsed.incident = false;
      }

      return parsed;
    } catch (parseError) {
      console.error("❌ JSON parse failed");
      console.error("RAW:", text);
      return null;
    }

  } catch (error) {
    console.error("❌ Gemini API Error:", error);
    return null;
  }
};

/**
 * Test Gemini connection
 */
export const testConnection = async (): Promise<boolean> => {
  if (!genAI) {
    initGemini();
    if (!genAI) return false;
  }

  try {
    const model = genAI.getGenerativeModel({
      model: GEMINI_CONFIG.MODEL
    });

    const result = await model.generateContent(
      'Respond with "OK"'
    );

    const text = result.response.text();
    console.log("✅ Gemini test success:", text);

    return true;
  } catch (error) {
    console.error("❌ Gemini test failed:", error);
    return false;
  }
};