diff --git a/backend/.env.example b/backend/.env.example index 967c071..5dfbe6f 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -2,7 +2,7 @@ # Application Settings APP_NAME=InPactAI - +GEMINI_API_KEY=your-gemini-api-key-here # Supabase Configuration SUPABASE_URL=https://yoursupabaseurl.supabase.co SUPABASE_KEY=your-supabase-anon-key-here diff --git a/backend/app/api/routes/gemini_generate.py b/backend/app/api/routes/gemini_generate.py new file mode 100644 index 0000000..c8b5093 --- /dev/null +++ b/backend/app/api/routes/gemini_generate.py @@ -0,0 +1,39 @@ +import os +import httpx +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel +from app.core.config import settings + + + + + + + +router = APIRouter() +GEMINI_API_KEY = settings.gemini_api_key +GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent" + +class GenerateRequest(BaseModel): + prompt: str + +@router.post("/generate") +async def generate_content(request: GenerateRequest): + if not GEMINI_API_KEY: + raise HTTPException(status_code=500, detail="Gemini API is not configured. Please set GEMINI_API_KEY in environment.") + payload = { + "contents": [{"role": "user", "parts": [{"text": request.prompt}]}] + } + headers = { + "Content-Type": "application/json", + } + params = {"key": GEMINI_API_KEY} + try: + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.post(GEMINI_API_URL, json=payload, headers=headers, params=params) + response.raise_for_status() + return response.json() + except httpx.RequestError as e: + raise HTTPException(status_code=502, detail=f"Gemini API error: {str(e)}") + except httpx.HTTPStatusError as e: + raise HTTPException(status_code=502, detail=f"Gemini API error: {str(e)}") diff --git a/backend/app/core/config.py b/backend/app/core/config.py index f62c9fe..69d5bfc 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -14,6 +14,7 @@ class Settings(BaseSettings): # AI Configuration ai_api_key: Optional[str] = None groq_api_key: Optional[str] = None + gemini_api_key: Optional[str] = None # CORS Configuration allowed_origins: str = "http://localhost:3000" diff --git a/backend/app/main.py b/backend/app/main.py index 9445f6a..47a14f2 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -4,7 +4,7 @@ from app.api.routes import health from app.services.supabase_client import supabase from app.api.routes import auth - +from app.api.routes import gemini_generate app = FastAPI(title="Inpact Backend", version="0.1.0") # Verify Supabase client initialization on startup @@ -27,7 +27,7 @@ allow_methods=["*"], allow_headers=["*"], ) - +app.include_router(gemini_generate.router) app.include_router(health.router) app.include_router(auth.router) diff --git a/backend/env_example b/backend/env_example index c726190..6335d97 100644 --- a/backend/env_example +++ b/backend/env_example @@ -19,6 +19,10 @@ DATABASE_URL=postgresql://postgres.your-project-ref:[YOUR-PASSWORD]@aws-0-region GROQ_API_KEY=your-groq-api-key AI_API_KEY=your-openai-api-key-optional +# Gemini API Key (Optional) + +GEMINI_API_KEY=your-gemini-api-key-here + # CORS Origins (comma-separated) ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001 diff --git a/backend/requirements.txt b/backend/requirements.txt index 6b66b15..b119657 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -14,7 +14,7 @@ pydantic==2.12.3 pydantic-settings>=2.0.3 pydantic_core==2.41.4 python-dotenv==1.2.1 -pytokens==0.2.0 +httpx ruff==0.14.3 sniffio==1.3.1 starlette==0.49.1 diff --git a/frontend/lib/geminiApi.ts b/frontend/lib/geminiApi.ts new file mode 100644 index 0000000..cb5f91e --- /dev/null +++ b/frontend/lib/geminiApi.ts @@ -0,0 +1,23 @@ +// Gemini text generation API integration +// Calls backend /generate endpoint securely +export async function generateGeminiText(prompt: string): Promise { + const apiUrl = process.env.NEXT_PUBLIC_API_URL; + if (!apiUrl) { + throw new Error("NEXT_PUBLIC_API_URL is not set in environment."); + } + try { + const res = await fetch(`${apiUrl}/generate`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ prompt }), + }); + if (!res.ok) { + throw new Error(`Backend error: ${res.status} ${res.statusText}`); + } + return await res.json(); + } catch (err: any) { + throw new Error(`Gemini API call failed: ${err.message}`); + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6bc0efe..c73e66d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.1.0", "dependencies": { + "@google/genai": "^1.29.0", "@hookform/resolvers": "^5.2.2", "@supabase/supabase-js": "^2.78.0", "@tanstack/react-query": "^5.90.5", @@ -488,6 +489,27 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@google/genai": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.29.0.tgz", + "integrity": "sha512-cQP7Ssa06W+MSAyVtL/812FBtZDoDehnFObIpK1xo5Uv4XvqBcVZ8OhXgihOIXWn7xvPQGvLclR8+yt3Ysnd9g==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.20.1" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, "node_modules/@hookform/resolvers": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", @@ -980,6 +1002,23 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1235,6 +1274,16 @@ "node": ">=12.4.0" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1684,7 +1733,7 @@ "version": "19.2.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -2302,6 +2351,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2319,11 +2377,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -2586,7 +2655,26 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT" }, "node_modules/baseline-browser-mapping": { @@ -2599,6 +2687,15 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -2657,6 +2754,12 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -2763,7 +2866,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2776,7 +2878,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -2809,7 +2910,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2824,7 +2924,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -2834,6 +2934,15 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -2892,7 +3001,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2995,6 +3103,21 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.244", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", @@ -3006,7 +3129,6 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/enhanced-resolve": { @@ -3643,6 +3765,12 @@ "node": ">=0.10.0" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3704,6 +3832,29 @@ "reusify": "^1.0.4" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3804,6 +3955,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", @@ -3820,6 +3987,18 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/framer-motion": { "version": "12.23.24", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz", @@ -3887,6 +4066,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -3975,6 +4183,26 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3988,6 +4216,30 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -4018,6 +4270,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.2.tgz", + "integrity": "sha512-YsFPGVgDFf4IzSwbwIR0iaFJQFmR5Jp7V1WuYSjuRgAm9yWqsMhKE9YPlL+wvFLnc/wMiFV4SQUD9Y/JMpxIxQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -4044,6 +4323,19 @@ "dev": true, "license": "MIT" }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -4152,6 +4444,19 @@ "hermes-estree": "0.25.1" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4388,6 +4693,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -4630,7 +4944,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/iterator.prototype": { @@ -4651,6 +4964,21 @@ "node": ">= 0.4" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -4694,6 +5022,15 @@ "node": ">=6" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -4744,6 +5081,27 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -5191,6 +5549,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/motion-dom": { "version": "12.23.23", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", @@ -5210,7 +5577,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -5334,6 +5700,44 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -5532,6 +5936,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5559,7 +5969,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5572,6 +5981,28 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5829,6 +6260,21 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5873,6 +6319,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -6033,7 +6499,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -6046,7 +6511,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6128,6 +6592,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6158,6 +6634,65 @@ "node": ">= 0.4" } }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -6271,6 +6806,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -6706,6 +7278,15 @@ "punycode": "^2.1.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -6726,7 +7307,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -6837,6 +7417,94 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", diff --git a/guides/summaries/GEMINI_API_IMPLEMENTATION_GUIDE.md b/guides/summaries/GEMINI_API_IMPLEMENTATION_GUIDE.md new file mode 100644 index 0000000..6b4e317 --- /dev/null +++ b/guides/summaries/GEMINI_API_IMPLEMENTATION_GUIDE.md @@ -0,0 +1,95 @@ +# Gemini API Integration Guide for InPactAI + +## Overview + +This guide explains how Gemini API integration is implemented in the InPactAI project, including backend and frontend setup, security best practices, and step-by-step instructions for future use. + +--- + +## Implementation Summary + +### 1. Backend (Python/FastAPI) + +- **API Route:** `/generate` (POST) +- **File:** `backend/app/api/routes/gemini_generate.py` +- **Functionality:** Accepts a JSON body with a `prompt` string, calls the Gemini REST API using the API key from environment, and returns the response as JSON. +- **Security:** The Gemini API key is stored in `backend/.env` and loaded via Pydantic `Settings` (`gemini_api_key`). It is **never** exposed to the frontend. +- **Router Inclusion:** The Gemini router is included in `backend/app/main.py` using `app.include_router(gemini_generate.router)`. + +### 2. Frontend (Next.js/TypeScript) + +- **API Call Function:** `frontend/lib/geminiApi.ts` +- **Functionality:** Calls the backend `/generate` endpoint using the API URL from `NEXT_PUBLIC_API_URL` in `.env`. Accepts a prompt and returns the backend response. +- **Security:** The frontend never accesses the Gemini API key directly. + +--- + +## How to Use Gemini API in This Project + +### Backend Usage + +1. **Add your Gemini API key** to `backend/.env`: + ``` + GEMINI_API_KEY="your-gemini-api-key" + ``` +2. **Access the key in code** via Pydantic Settings: + ```python + from app.core.config import settings + GEMINI_API_KEY = settings.gemini_api_key + ``` +3. **Call the Gemini REST API** using Python's `requests`: + ```python + response = requests.post( + "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent", + json={"contents": [{"role": "user", "parts": [{"text": prompt}]}]}, + headers={"Content-Type": "application/json"}, + params={"key": GEMINI_API_KEY}, + timeout=30 + ) + result = response.json() + ``` +4. **Never expose the API key to the frontend.** + +### Frontend Usage + +1. **Set the backend API URL** in `frontend/.env`: + ``` + NEXT_PUBLIC_API_URL="http://localhost:8000" + ``` +2. **Use the provided function** in `frontend/lib/geminiApi.ts`: + ```typescript + import { generateGeminiText } from "./lib/geminiApi"; + const result = await generateGeminiText("Your prompt here"); + ``` +3. **Do not use the Gemini API key in frontend code.** + +--- + +## Security Best Practices + +- Store secrets only in backend `.env` files. +- Use Pydantic Settings for environment variable management. +- Only call Gemini API from backend code. +- Frontend communicates with backend via secure endpoints. + +--- + +## Troubleshooting + +- If requests hang, check your API key, network, and backend logs. +- Ensure all required Python packages are installed: `fastapi`, `requests`, `pydantic-settings`. +- Confirm routers are included in `main.py`. + +--- + +## References + +- [Gemini REST API Docs](https://ai.google.dev/gemini-api/docs) +- [Pydantic Settings](https://docs.pydantic.dev/latest/concepts/settings/) +- [FastAPI Docs](https://fastapi.tiangolo.com/) + +--- + +## Contact + +For questions or improvements, contact the InPactAI maintainers. diff --git a/guides/summaries/Gemini_codegen_guide b/guides/summaries/Gemini_codegen_guide new file mode 100644 index 0000000..18edc2e --- /dev/null +++ b/guides/summaries/Gemini_codegen_guide @@ -0,0 +1,679 @@ +# Gemini API Coding Guidelines (JavaScript/TypeScript) + +You are a Gemini API coding expert. Help me with writing code using the Gemini +API calling the official libraries and SDKs. + +Please follow the following guidelines when generating code. + +You can find the official SDK documentation and code samples here: +https://googleapis.github.io/js-genai/ + +## Golden Rule: Use the Correct and Current SDK + +Always use the Google Gen AI SDK to call the Gemini models, which is the +standard library for all Gemini API interactions. Do not use legacy libraries +and SDKs. + +- **Library Name:** Google Gen AI SDK +- **NPM Package:** `@google/genai` +- **Legacy Libraries**: (`@google/generative-ai`) are deprecated + +**Installation:** + +- **Incorrect:** `npm install @google/generative-ai` +- **Incorrect:** `npm install @google-ai/generativelanguage` +- **Correct:** `npm install @google/genai` + +**APIs and Usage:** + +- **Incorrect:** `const { GenerativeModel } = + require('@google/generative-ai')` -> **Correct:** `import { GoogleGenAI } + from '@google/genai'` +- **Incorrect:** `const model = genai.getGenerativeModel(...)` -> **Correct:** + `const ai = new GoogleGenAI({apiKey: "..."})` +- **Incorrect:** `await model.generateContent(...)` -> **Correct:** `await + ai.models.generateContent(...)` +- **Incorrect:** `await model.generateContentStream(...)` -> **Correct:** + `await ai.models.generateContentStream(...)` +- **Incorrect:** `const generationConfig = { ... }` -> **Correct:** Pass + configuration directly: `config: { safetySettings: [...] }` +- **Incorrect** `GoogleGenerativeAI` +- **Incorrect** `google.generativeai` +- **Incorrect** `models.create` +- **Incorrect** `ai.models.create` +- **Incorrect** `models.getGenerativeModel` +- **Incorrect** `ai.models.getModel` +- **Incorrect** `ai.models['model_name']` +- **Incorrect** `generationConfig` +- **Incorrect** `GoogleGenAIError` -> **Correct** `ApiError` +- **Incorrect** `GenerateContentResult` -> **Correct** + `GenerateContentResponse`. +- **Incorrect** `GenerateContentRequest` -> **Correct** + `GenerateContentParameters` + +## Initialization and API key + +The `@google/genai` library requires creating a `GoogleGenAI` instance for all +API calls. + +- Always use `const ai = new GoogleGenAI({})` to create an instance. +- Set the `GEMINI_API_KEY` environment variable, which will be picked up + automatically in Node.js environments. + +```javascript +import { GoogleGenAI } from '@google/genai'; + +// Uses the GEMINI_API_KEY environment variable if apiKey not specified +const ai = new GoogleGenAI({}); + +// Or pass the API key directly +// const ai = new GoogleGenAI({apiKey: process.env.GEMINI_API_KEY}); +``` + +## Models + +- By default, use the following models when using `google-genai`: + - **General Text & Multimodal Tasks:** `gemini-2.5-flash` + - **Coding and Complex Reasoning Tasks:** `gemini-2.5-pro` + - **Image Generation Tasks:** `imagen-4.0-fast-generate-001`, + `imagen-4.0-generate-001` or `imagen-4.0-ultra-generate-001` + - **Image Editing Tasks:** `gemini-2.5-flash-image-preview` + - **Video Generation Tasks:** `veo-3.0-fast-generate-preview` or + `veo-3.0-generate-preview`. + +- It is also acceptable to use the following model if explicitly requested by + the user: + - **Gemini 2.0 Series**: `gemini-2.0-flash`, `gemini-2.0-pro` + +- Do not use the following deprecated models (or their variants like + `gemini-1.5-flash-latest`): + - **Prohibited:** `gemini-1.5-flash` + - **Prohibited:** `gemini-1.5-pro` + - **Prohibited:** `gemini-pro` + +## Basic Inference (Text Generation) + +Here's how to generate a response from a text prompt. + +```javascript +import { GoogleGenAI } from '@google/genai'; + +const ai = new GoogleGenAI({}); // Assumes GEMINI_API_KEY is set + +async function run() { + const response = await ai.models.generateContent({ + model: 'gemini-2.5-flash', + contents: 'why is the sky blue?', + }); + + console.log(response.text); // output is often markdown +} + +run(); +``` + +Multimodal inputs are supported by passing file data in the `contents` array. + +```javascript +import { GoogleGenAI, Part } from '@google/genai'; +import * as fs from 'fs'; + +const ai = new GoogleGenAI({}); + +// Converts local file information to a Part object. +function fileToGenerativePart(path, mimeType): Part { + return { + inlineData: { + data: Buffer.from(fs.readFileSync(path)).toString("base64"), + mimeType + }, + }; +} + +async function run() { + const imagePart = fileToGenerativePart("path/to/image.jpg", "image/jpeg"); + + const response = await ai.models.generateContent({ + model: 'gemini-2.5-flash', + contents: [imagePart, "explain that image"], + }); + + console.log(response.text); // The output often is markdown +} + +run(); +``` + +You can use this approach to pass a variety of data types (images, audio, video, +pdf). For PDF, use `application/pdf` as `mimeType`. + +For larger files, use `ai.files.upload`: + +```javascript +import { GoogleGenAI, createPartFromUri, createUserContent } from '@google/genai'; +const ai = new GoogleGenAI({}); + +async function run() { + const f = await ai.files.upload({ + file: 'path/to/sample.mp3', + config:{mimeType: 'audio/mp3'}, + }); + + const response = await ai.models.generateContent({ + model: 'gemini-2.5-flash', + contents: createUserContent([ + createPartFromUri(f.uri, f.mimeType), + "Describe this audio clip" + ]) + }); + + console.log(response.text); +} + +run(); +``` + +You can delete files after use like this: + +```javascript +const myFile = await ai.files.upload({file: 'path/to/sample.mp3', mimeType: 'audio/mp3'}); +await ai.files.delete({name: myFile.name}); +``` + +## Additional Capabilities and Configurations + +Below are examples of advanced configurations. + +### Thinking + +Gemini 2.5 series models support thinking, which is on by default for +`gemini-2.5-flash`. It can be adjusted by using `thinking_budget` setting. +Setting it to zero turns thinking off, and will reduce latency. + +```javascript +import { GoogleGenAI } from "@google/genai"; + +const ai = new GoogleGenAI({}); + +async function main() { + const response = await ai.models.generateContent({ + model: "gemini-2.5-pro", + contents: "Provide a list of 3 famous physicists and their key contributions", + config: { + thinkingConfig: { + thinkingBudget: 1024, + // Turn off thinking: + // thinkingBudget: 0 + // Turn on dynamic thinking: + // thinkingBudget: -1 + }, + }, + }); + + console.log(response.text); +} + +main(); +``` + +IMPORTANT NOTES: + +- Minimum thinking budget for `gemini-2.5-pro` is `128` and thinking can not + be turned off for that model. +- No models (apart from Gemini 2.5 series) support thinking or thinking + budgets APIs. Do not try to adjust thinking budgets other models (such as + `gemini-2.0-flash` or `gemini-2.0-pro`) otherwise it will cause syntax + errors. + +### System instructions + +Use system instructions to guide the model's behavior. + +```javascript +import { GoogleGenAI } from '@google/genai'; + +const ai = new GoogleGenAI({}); + +async function run() { + const response = await ai.models.generateContent({ + model: 'gemini-2.5-flash', + contents: "Hello.", + config: { + systemInstruction: "You are a pirate", + } + }); + console.log(response.text); +} +run(); +``` + +### Hyperparameters + +You can also set `temperature` or `maxOutputTokens` within the `config` object. +**Avoid** setting `maxOutputTokens`, `topP`, `topK` unless explicitly requested +by the user. + +### Safety configurations + +Avoid setting safety configurations unless explicitly requested by the user. If +explicitly asked for by the user, here is a sample API: + +```javascript +import { GoogleGenAI, HarmCategory, HarmBlockThreshold, Part } from '@google/genai'; +import * as fs from 'fs'; + +const ai = new GoogleGenAI({}); + +function fileToGenerativePart(path, mimeType): Part { + return { + inlineData: { + data: Buffer.from(fs.readFileSync(path)).toString("base64"), + mimeType + }, + }; +} + +async function run() { + const img = fileToGenerativePart("/path/to/img.jpg", "image/jpeg"); + const response = await ai.models.generateContent({ + model: "gemini-2.5-flash", + contents: ['Do these look store-bought or homemade?', img], + config: { + safetySettings: [ + { + category: HarmCategory.HARM_CATEGORY_HATE_SPEECH, + threshold: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE, + }, + ] + } + }); + console.log(response.text); +} +run(); +``` + +### Streaming + +It is possible to stream responses to reduce user perceived latency: + +```javascript +import { GoogleGenAI } from '@google/genai'; +const ai = new GoogleGenAI({}); + +async function run() { + const responseStream = await ai.models.generateContentStream({ + model: "gemini-2.5-flash", + contents: ["Explain how AI works"], + }); + + for await (const chunk of responseStream) { + process.stdout.write(chunk.text); + } + console.log(); // for a final newline +} +run(); +``` + +### Chat + +For multi-turn conversations, use the `chats` service to maintain conversation +history. + +```javascript +import { GoogleGenAI } from '@google/genai'; + +const ai = new GoogleGenAI({}); + +async function run() { + const chat = ai.chats.create({model: "gemini-2.5-flash"}); + + let response = await chat.sendMessage({message:"I have 2 dogs in my house."}); + console.log(response.text); + + response = await chat.sendMessage({message: "How many paws are in my house?"}); + console.log(response.text); + + const history = await chat.getHistory(); + for (const message of history) { + console.log(`role - ${message.role}: ${message.parts[0].text}`); + } +} +run(); +``` It is also possible to use streaming with Chat: + +```javascript + const chat = ai.chats.create({model: "gemini-2.5-flash"}); + const stream = await chat.sendMessageStream({message:"I have 2 dogs in my house."}); + for await (const chunk of stream) { + console.log(chunk.text); + console.log("_".repeat(80)); + } +``` + +Note: ai.chats.create({model}) returns `Chat` under `@google/genai` which tracks +the session. + +### Structured outputs + +Ask the model to return a response in JSON format. + +The recommended way is to configure a `responseSchema` for the expected output. + +See the available types below that can be used in the `responseSchema`. + +```javascript +export enum Type { + /** + * Not specified, should not be used. + */ + TYPE_UNSPECIFIED = 'TYPE_UNSPECIFIED', + /** + * OpenAPI string type + */ + STRING = 'STRING', + /** + * OpenAPI number type + */ + NUMBER = 'NUMBER', + /** + * OpenAPI integer type + */ + INTEGER = 'INTEGER', + /** + * OpenAPI boolean type + */ + BOOLEAN = 'BOOLEAN', + /** + * OpenAPI array type + */ + ARRAY = 'ARRAY', + /** + * OpenAPI object type + */ + OBJECT = 'OBJECT', + /** + * Null type + */ + NULL = 'NULL', +} +``` + +`Type.OBJECT` cannot be empty; it must contain other properties. + +```javascript +import { GoogleGenAI, Type } from "@google/genai"; + +const ai = new GoogleGenAI({}); +const response = await ai.models.generateContent({ + model: "gemini-2.5-flash", + contents: "List a few popular cookie recipes, and include the amounts of ingredients.", + config: { + responseMimeType: "application/json", + responseSchema: { + type: Type.ARRAY, + items: { + type: Type.OBJECT, + properties: { + recipeName: { + type: Type.STRING, + description: 'The name of the recipe.', + }, + ingredients: { + type: Type.ARRAY, + items: { + type: Type.STRING, + }, + description: 'The ingredients for the recipe.', + }, + }, + propertyOrdering: ["recipeName", "ingredients"], + }, + }, + }, +}); + +let jsonStr = response.text.trim(); +``` + +The `jsonStr` might look like this: + +```javascript +[ + { + "recipeName": "Chocolate Chip Cookies", + "ingredients": [ + "1 cup (2 sticks) unsalted butter, softened", + "3/4 cup granulated sugar", + "3/4 cup packed brown sugar", + "1 teaspoon vanilla extract", + "2 large eggs", + "2 1/4 cups all-purpose flour", + "1 teaspoon baking soda", + "1 teaspoon salt", + "2 cups chocolate chips" + ] + }, + ... +] +``` + +#### Function Calling (Tools) + +You can provide the model with tools (functions) it can use to bring in external +information to answer a question or act on a request outside the model. + +```javascript +import {GoogleGenAI, FunctionDeclaration, Type} from '@google/genai'; +const ai = new GoogleGenAI({}); + +async function run() { + const controlLightDeclaration = { + name: 'controlLight', + parameters: { + type: Type.OBJECT, + description: 'Set brightness and color temperature of a light.', + properties: { + brightness: { type: Type.NUMBER, description: 'Light level from 0 to 100.' }, + colorTemperature: { type: Type.STRING, description: '`daylight`, `cool`, or `warm`.'}, + }, + required: ['brightness', 'colorTemperature'], + }, + }; + + const response = await ai.models.generateContent({ + model: 'gemini-2.5-flash', + contents: 'Dim the lights so the room feels cozy and warm.', + config: { + tools: [{functionDeclarations: [controlLightDeclaration]}] + } + }); + + if (response.functionCalls) { + console.log(response.functionCalls); + // In a real app, you would execute the function and send the result back. + } +} +run(); +``` + +### Generate Images + +Here's how to generate images using the Imagen models. + +```javascript +import { GoogleGenAI } from "@google/genai"; + +const ai = new GoogleGenAI({}); + +async function run() { + const response = await ai.models.generateImages({ + model: 'imagen-4.0-fast-generate-001', + prompt: 'A friendly robot holding a red skateboard, minimalist vector art', + config: { + numberOfImages: 1, // 1 to 4 (always 1 for the ultra model) + outputMimeType: 'image/jpeg', + aspectRatio: '1:1', // "1:1", "3:4", "4:3", "9:16", or "16:9" + }, + }); + + const base64ImageBytes = response.generatedImages[0].image.imageBytes; + // This can be used directly in an src attribute + const imageUrl = `data:image/jpeg;base64,${base64ImageBytes}`; + console.log(imageUrl); +} +run(); +``` + +Note: Do not include negativePrompts in config, it's not supported. + +### Edit Images + +Editing images is better done using the Gemini native image generation model. +Configs are not supported in this model (except modality). + +```javascript +import { GoogleGenAI } from '@google/genai'; + +const ai = new GoogleGenAI({}); + +const response = await ai.models.generateContent({ + model: 'gemini-2.5-flash-image-preview', + contents: [imagePart, 'koala eating a nano banana'] +}); +for (const part of response.candidates[0].content.parts) { + if (part.inlineData) { + const base64ImageBytes: string = part.inlineData.data; + const imageUrl = `data:image/png;base64,${base64ImageBytes}`; + } +} +``` + +### Generate Videos + +Here's how to generate videos using the Veo models. Usage of Veo can be costly, +so after generating code for it, give user a heads up to check pricing for Veo. + +```javascript +import { GoogleGenAI } from "@google/genai"; +import { createWriteStream } from "fs"; +import { Readable } from "stream"; + +const ai = new GoogleGenAI({}); + +async function main() { + let operation = await ai.models.generateVideos({ + model: "veo-3.0-fast-generate-preview", + prompt: "Panning wide shot of a calico kitten sleeping in the sunshine", + config: { + personGeneration: "dont_allow", + aspectRatio: "16:9", + }, + }); + + while (!operation.done) { + await new Promise((resolve) => setTimeout(resolve, 10000)); + operation = await ai.operations.getVideosOperation({ + operation: operation, + }); + } + + operation.response?.generatedVideos?.forEach(async (generatedVideo, n) => { + const resp = await fetch(`${generatedVideo.video?.uri}&key=GEMINI_API_KEY`); // append your API key + const writer = createWriteStream(`video${n}.mp4`); + Readable.fromWeb(resp.body).pipe(writer); + }); +} + +main(); +``` + +### Search Grounding + +Google Search can be used as a tool for grounding queries that with up to date +information from the web. + +```javascript +import { GoogleGenAI } from "@google/genai"; + +const ai = new GoogleGenAI({}); + +async function run() { + const response = await ai.models.generateContent({ + model: "gemini-2.5-flash", + contents: "Who won the latest F1 race?", + config: { + tools: [{googleSearch: {}}], + }, + }); + + console.log("Response:", response.text); + + // Extract and display grounding URLs + const searchChunks = response.candidates?.[0]?.groundingMetadata?.groundingChunks; + if (searchChunks) { + const urls = searchChunks.map(chunk => chunk.web.uri); + console.log("Sources:", urls); + } +} +run(); +``` + +### Content and Part Hierarchy + +While the simpler API call is often sufficient, you may run into scenarios where +you need to work directly with the underlying `Content` and `Part` objects for +more explicit control. These are the fundamental building blocks of the +`generateContent` API. + +For instance, the following simple API call: + +```javascript +import { GoogleGenAI } from '@google/genai'; +const ai = new GoogleGenAI({}); + +async function run() { + const response = await ai.models.generateContent({ + model: "gemini-2.5-flash", + contents: "How does AI work?", + }); + console.log(response.text); +} +run(); +``` + +is effectively a shorthand for this more explicit structure: + +```javascript +import { GoogleGenAI } from '@google/genai'; +const ai = new GoogleGenAI({}); + +async function run() { + const response = await ai.models.generateContent({ + model: "gemini-2.5-flash", + contents: [ + { role: "user", parts: [{ text: "How does AI work?" }] }, + ], + }); + console.log(response.text); +} +run(); +``` + +## API Errors + +`ApiError` from `@google/genai` extends from EcmaScript `Error` and has +`message`, `name` fields in addition to `status` (HTTP Code). + +## Other APIs + +The list of APIs and capabilities above are not comprehensive. If users ask you +to generate code for a capability not provided above, refer them to +https://googleapis.github.io/js-genai/. + +## Useful Links + +- Documentation: ai.google.dev/gemini-api/docs +- API Keys and Authentication: ai.google.dev/gemini-api/docs/api-key +- Models: ai.google.dev/models +- API Pricing: ai.google.dev/pricing +- Rate Limits: ai.google.dev/rate-limits