From 614cfefc1591475c1e5a3297738360641d7e9745 Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Thu, 21 Aug 2025 10:58:41 -0400 Subject: [PATCH 1/2] Run Firebase AI app check on master instead of main --- .github/workflows/firebase-ai-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/firebase-ai-check.yml b/.github/workflows/firebase-ai-check.yml index d06b41582..4a61db01f 100644 --- a/.github/workflows/firebase-ai-check.yml +++ b/.github/workflows/firebase-ai-check.yml @@ -6,7 +6,7 @@ permissions: on: pull_request: branches: - - main + - master paths: - 'ai/ai-react-app/**' From 1659278bd3ca803d3391f8bef1f949553aa212fe Mon Sep 17 00:00:00 2001 From: Daniel La Rocque Date: Thu, 21 Aug 2025 09:38:57 -0400 Subject: [PATCH 2/2] Add Live to Firebase AI sample app Add a new entry in the right sidebar that allows users to use the Live Audio Conversation (`startAudioConversation()`) API. --- ai/ai-react-app/package.json | 2 +- ai/ai-react-app/src/App.tsx | 2 +- .../src/components/Layout/LeftSidebar.tsx | 3 +- .../src/components/Layout/MainLayout.tsx | 7 +- .../src/services/firebaseAIService.ts | 7 +- ai/ai-react-app/src/views/LiveView.module.css | 101 +++++++++++++ ai/ai-react-app/src/views/LiveView.tsx | 138 ++++++++++++++++++ ai/ai-react-app/yarn.lock | 106 +++++++------- 8 files changed, 308 insertions(+), 58 deletions(-) create mode 100644 ai/ai-react-app/src/views/LiveView.module.css create mode 100644 ai/ai-react-app/src/views/LiveView.tsx diff --git a/ai/ai-react-app/package.json b/ai/ai-react-app/package.json index d2cbb351a..e3f501942 100644 --- a/ai/ai-react-app/package.json +++ b/ai/ai-react-app/package.json @@ -10,7 +10,7 @@ "preview": "vite preview" }, "dependencies": { - "firebase": "12.0.0", + "firebase": "12.2.1", "immer": "^10.1.1", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/ai/ai-react-app/src/App.tsx b/ai/ai-react-app/src/App.tsx index 5e77a25d3..48052c9a6 100644 --- a/ai/ai-react-app/src/App.tsx +++ b/ai/ai-react-app/src/App.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import MainLayout from "./components/Layout/MainLayout"; // Defines the primary modes or views available in the application. -export type AppMode = "chat" | "imagenGen"; +export type AppMode = "chat" | "imagenGen" | "live"; function App() { // State to manage which main view ('chat' or 'imagenGen') is currently active. diff --git a/ai/ai-react-app/src/components/Layout/LeftSidebar.tsx b/ai/ai-react-app/src/components/Layout/LeftSidebar.tsx index 879a3f13b..07b6d8b30 100644 --- a/ai/ai-react-app/src/components/Layout/LeftSidebar.tsx +++ b/ai/ai-react-app/src/components/Layout/LeftSidebar.tsx @@ -5,7 +5,7 @@ import { BackendType, Content, ModelParams } from "firebase/ai"; import { PREDEFINED_PERSONAS } from "../../config/personas"; interface LeftSidebarProps { - /** The currently active application mode (e.g., 'chat', 'imagenGen'). */ + /** The currently active application mode. */ activeMode: AppMode; /** Function to call when a mode button is clicked, updating the active mode in the parent. */ setActiveMode: (mode: AppMode) => void; @@ -41,6 +41,7 @@ const LeftSidebar: React.FC = ({ const modes: { id: AppMode; label: string }[] = [ { id: "chat", label: "Chat" }, { id: "imagenGen", label: "Imagen Generation" }, + { id: "live", label: "Live Conversation" }, ]; const handleBackendChange = (event: React.ChangeEvent) => { diff --git a/ai/ai-react-app/src/components/Layout/MainLayout.tsx b/ai/ai-react-app/src/components/Layout/MainLayout.tsx index 4900a0bd3..a72ad13ab 100644 --- a/ai/ai-react-app/src/components/Layout/MainLayout.tsx +++ b/ai/ai-react-app/src/components/Layout/MainLayout.tsx @@ -4,6 +4,7 @@ import LeftSidebar from "./LeftSidebar"; import RightSidebar from "./RightSidebar"; import ChatView from "../../views/ChatView"; import ImagenView from "../../views/ImagenView"; +import LiveView from "../../views/LiveView"; import { AppMode } from "../../App"; import { UsageMetadata, @@ -81,7 +82,7 @@ const MainLayout: React.FC = ({ }, [activeMode]); useEffect(() => { - const validModes: AppMode[] = ["chat", "imagenGen"]; + const validModes: AppMode[] = ["chat", "imagenGen", "live"]; if (!validModes.includes(activeMode)) { console.warn(`Invalid activeMode "${activeMode}". Resetting to "chat".`); setActiveMode("chat"); @@ -112,6 +113,10 @@ const MainLayout: React.FC = ({ return ( ); + case "live": + return ( + + ); default: console.error(`Unexpected activeMode: ${activeMode}`); return ( diff --git a/ai/ai-react-app/src/services/firebaseAIService.ts b/ai/ai-react-app/src/services/firebaseAIService.ts index 98ce14d0a..435560a8e 100644 --- a/ai/ai-react-app/src/services/firebaseAIService.ts +++ b/ai/ai-react-app/src/services/firebaseAIService.ts @@ -12,6 +12,7 @@ import { ImagenModelParams, FunctionCall, GoogleSearchTool, + BackendType, } from "firebase/ai"; import { firebaseConfig } from "../config/firebase-config"; @@ -23,6 +24,10 @@ export const AVAILABLE_GENERATIVE_MODELS = [ "gemini-2.5-flash" ]; export const AVAILABLE_IMAGEN_MODELS = ["imagen-3.0-generate-002"]; +export const LIVE_MODELS = new Map([ + [BackendType.GOOGLE_AI, 'gemini-live-2.5-flash-preview'], + [BackendType.VERTEX_AI, 'gemini-2.0-flash-exp'] +]) let app: FirebaseApp; try { @@ -163,4 +168,4 @@ export const countTokensInPrompt = async ( } }; -export { app }; +export { app }; \ No newline at end of file diff --git a/ai/ai-react-app/src/views/LiveView.module.css b/ai/ai-react-app/src/views/LiveView.module.css new file mode 100644 index 000000000..65396255c --- /dev/null +++ b/ai/ai-react-app/src/views/LiveView.module.css @@ -0,0 +1,101 @@ +.liveViewContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + padding: 24px; + text-align: center; + gap: 20px; +} + +.title { + font-size: 1.5rem; + font-weight: 400; + color: var(--color-text-primary); + margin: 0; +} + +.instructions { + max-width: 500px; + color: var(--color-text-secondary); + font-size: 0.875rem; + line-height: 1.5; +} + +.statusContainer { + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + min-height: 24px; +} + +.statusIndicator { + width: 12px; + height: 12px; + border-radius: 50%; + background-color: var(--color-text-placeholder); + transition: background-color 0.3s ease; +} + +.statusIndicator.active { + background-color: var(--brand-google-cloud-green); + animation: pulse 2s infinite; +} + +.statusText { + font-size: 1rem; + font-weight: 500; + color: var(--color-text-secondary); +} + +.controlButton { + background-color: var(--color-surface-interactive); + color: var(--color-text-on-interactive); + border: none; + padding: 12px 24px; + border-radius: 24px; + cursor: pointer; + font-weight: 500; + font-size: 1rem; + transition: background-color 0.15s ease; + min-width: 200px; +} +.controlButton:hover:not(:disabled) { + background-color: var(--color-surface-interactive-hover); +} +.controlButton.stop { + background-color: var(--brand-firebase-red); +} +.controlButton.stop:hover:not(:disabled) { + background-color: #b71c1c; +} +.controlButton:disabled { + background-color: var(--color-surface-tertiary); + color: var(--color-text-disabled); + cursor: not-allowed; +} + +.errorMessage { + background-color: var(--color-error-bg); + color: var(--color-error-text); + border: 1px solid var(--color-error-border); + padding: 10px 16px; + border-radius: 4px; + margin-top: 20px; + max-width: 500px; + white-space: pre-wrap; +} + +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(52, 168, 83, 0.7); + } + 70% { + box-shadow: 0 0 0 10px rgba(52, 168, 83, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(52, 168, 83, 0); + } +} \ No newline at end of file diff --git a/ai/ai-react-app/src/views/LiveView.tsx b/ai/ai-react-app/src/views/LiveView.tsx new file mode 100644 index 000000000..d328d94b3 --- /dev/null +++ b/ai/ai-react-app/src/views/LiveView.tsx @@ -0,0 +1,138 @@ +import React, { useState, useEffect, useCallback } from "react"; +import styles from "./LiveView.module.css"; +import { + AI, + getLiveGenerativeModel, + startAudioConversation, + AudioConversationController, + AIError, + ResponseModality, +} from "firebase/ai"; +import { LIVE_MODELS } from "../services/firebaseAIService"; + +interface LiveViewProps { + aiInstance: AI; +} + +type ConversationState = "idle" | "active" | "error"; + +const LiveView: React.FC = ({ aiInstance }) => { + const [conversationState, setConversationState] = + useState("idle"); + const [error, setError] = useState(null); + const [controller, setController] = + useState(null); + + const handleStartConversation = useCallback(async () => { + setError(null); + setConversationState("active"); + + try { + const modelName = LIVE_MODELS.get(aiInstance.backend.backendType)!; + console.log(`[LiveView] Getting live model: ${modelName}`); + const model = getLiveGenerativeModel(aiInstance, { + model: modelName, + generationConfig: { + responseModalities: [ResponseModality.AUDIO] + } + }); + + console.log("[LiveView] Connecting to live session..."); + const liveSession = await model.connect(); + + console.log( + "[LiveView] Starting audio conversation. This will request microphone permissions.", + ); + + const newController = await startAudioConversation(liveSession); + + setController(newController); + console.log("[LiveView] Audio conversation started successfully."); + } catch (err: unknown) { + console.error("[LiveView] Failed to start conversation:", err); + let errorMessage = "An unknown error occurred."; + if (err instanceof AIError) { + errorMessage = `Error (${err.code}): ${err.message}`; + } else if (err instanceof Error) { + errorMessage = err.message; + } + setError(errorMessage); + setConversationState("error"); + setController(null); // Ensure controller is cleared on error + } + }, [aiInstance]); + + const handleStopConversation = useCallback(async () => { + if (!controller) return; + + console.log("[LiveView] Stopping audio conversation..."); + await controller.stop(); + setController(null); + setConversationState("idle"); + console.log("[LiveView] Audio conversation stopped."); + }, [controller]); + + // Cleanup effect to stop the conversation if the component unmounts + useEffect(() => { + return () => { + if (controller) { + console.log( + "[LiveView] Component unmounting, stopping active conversation.", + ); + controller.stop(); + } + }; + }, [controller]); + + const getStatusText = () => { + switch (conversationState) { + case "idle": + return "Ready"; + case "active": + return "In Conversation"; + case "error": + return "Error"; + default: + return "Unknown"; + } + }; + + return ( +
+

Live Conversation

+

+ Click the button below to start a real-time voice conversation with the + model. Your browser will ask for microphone permissions. +

+ +
+
+ Status: {getStatusText()} +
+ + + + {error &&
{error}
} +
+ ); +}; + +export default LiveView; diff --git a/ai/ai-react-app/yarn.lock b/ai/ai-react-app/yarn.lock index 8a422f4ca..a7d6aee00 100644 --- a/ai/ai-react-app/yarn.lock +++ b/ai/ai-react-app/yarn.lock @@ -354,10 +354,10 @@ "@eslint/core" "^0.13.0" levn "^0.4.1" -"@firebase/ai@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@firebase/ai/-/ai-2.0.0.tgz#c0324b48c2451b28d621c96517e056ab67576dc4" - integrity sha512-N/aSHjqOpU+KkYU3piMkbcuxzvqsOvxflLUXBAkYAPAz8wjE2Ye3BQDgKHEYuhMmEWqj6LFgEBUN8wwc6dfMTw== +"@firebase/ai@2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@firebase/ai/-/ai-2.2.1.tgz#fa3b15f4e50b4c0a9408fd3cbbaac32ee3442388" + integrity sha512-0VWlkGB18oDhwMqsgxpt/usMsyjnH3a7hTvQPcAbk7VhFg0QZMDX60mQKfLTFKrB5VwmlaIdVsSZznsTY2S0wA== dependencies: "@firebase/app-check-interop-types" "0.3.3" "@firebase/component" "0.7.0" @@ -424,12 +424,12 @@ "@firebase/util" "1.13.0" tslib "^2.1.0" -"@firebase/app-compat@0.5.0": - version "0.5.0" - resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.5.0.tgz#7463e9fb8d84706787773d3660accedff4479d05" - integrity sha512-nUnNpOeRj0KZzVzHsyuyrmZKKHfykZ8mn40FtG28DeSTWeM5b/2P242Va4bmQpJsy5y32vfv50+jvdckrpzy7Q== +"@firebase/app-compat@0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.5.2.tgz#142a1529dc8284b04c4e0fe0a4570b81fa06ec7a" + integrity sha512-cn+U27GDaBS/irsbvrfnPZdcCzeZPRGKieSlyb7vV6LSOL6mdECnB86PgYjYGxSNg8+U48L/NeevTV1odU+mOQ== dependencies: - "@firebase/app" "0.14.0" + "@firebase/app" "0.14.2" "@firebase/component" "0.7.0" "@firebase/logger" "0.5.0" "@firebase/util" "1.13.0" @@ -440,10 +440,10 @@ resolved "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz" integrity sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw== -"@firebase/app@0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.14.0.tgz#7132e96df95e85783922fd09750f08ab2ebfd699" - integrity sha512-APIAeKvRNFWKJLjIL8wLDjh7u8g6ZjaeVmItyqSjCdEkJj14UuVlus74D8ofsOMWh45HEwxwkd96GYbi+CImEg== +"@firebase/app@0.14.2": + version "0.14.2" + resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.14.2.tgz#b97ca1a28d6a7bd0490af4c75005c1ba27228280" + integrity sha512-Ecx2ig/JLC9ayIQwZHqm41Tzlf4c1WUuFhFUZB1y+JIJqDRE579x7Uil7tKT8MwDpOPwrK5ZtpxdSsrfy/LF8Q== dependencies: "@firebase/component" "0.7.0" "@firebase/logger" "0.5.0" @@ -534,13 +534,13 @@ faye-websocket "0.11.4" tslib "^2.1.0" -"@firebase/firestore-compat@0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.4.0.tgz#911cf956e489fa8335ed2f2ace14a74909bcd94d" - integrity sha512-4O7v4VFeSEwAZtLjsaj33YrMHMRjplOIYC2CiYsF6o/MboOhrhe01VrTt8iY9Y5EwjRHuRz4pS6jMBT8LfQYJA== +"@firebase/firestore-compat@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.4.1.tgz#abcfc30bb8e71cc7d46b3a91003d35d31dca8217" + integrity sha512-BjalPTDh/K0vmR/M/DE148dpIqbcfvtFVTietbUDWDWYIl9YH0TTVp/EwXRbZwswPxyjx4GdHW61GB2AYVz1SQ== dependencies: "@firebase/component" "0.7.0" - "@firebase/firestore" "4.9.0" + "@firebase/firestore" "4.9.1" "@firebase/firestore-types" "3.0.3" "@firebase/util" "1.13.0" tslib "^2.1.0" @@ -550,10 +550,10 @@ resolved "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz" integrity sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q== -"@firebase/firestore@4.9.0": - version "4.9.0" - resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.9.0.tgz#753d73c002b4c0ae639437b049ef0086791a0cf3" - integrity sha512-5zl0+/h1GvlCSLt06RMwqFsd7uqRtnNZt4sW99k2rKRd6k/ECObIWlEnvthm2cuOSnUmwZknFqtmd1qyYSLUuQ== +"@firebase/firestore@4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.9.1.tgz#44baf1bd697f6ef5adbe8265774c978357340d85" + integrity sha512-PYVUTkhC9y8pydrqC3O1Oc4AMfkGSWdmuH9xgPJjiEbpUIUPQ4J8wJhyuash+o2u+axmyNRFP8ULNUKb+WzBzQ== dependencies: "@firebase/component" "0.7.0" "@firebase/logger" "0.5.0" @@ -563,13 +563,13 @@ "@grpc/proto-loader" "^0.7.8" tslib "^2.1.0" -"@firebase/functions-compat@0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.4.0.tgz#aa63dea248053e9c06904605704662ea550e50ed" - integrity sha512-VPgtvoGFywWbQqtvgJnVWIDFSHV1WE6Hmyi5EGI+P+56EskiGkmnw6lEqc/MEUfGpPGdvmc4I9XMU81uj766/g== +"@firebase/functions-compat@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.4.1.tgz#b253b761845f0c82bbdf76ef59975978ed84eb65" + integrity sha512-AxxUBXKuPrWaVNQ8o1cG1GaCAtXT8a0eaTDfqgS5VsRYLAR0ALcfqDLwo/QyijZj1w8Qf8n3Qrfy/+Im245hOQ== dependencies: "@firebase/component" "0.7.0" - "@firebase/functions" "0.13.0" + "@firebase/functions" "0.13.1" "@firebase/functions-types" "0.6.3" "@firebase/util" "1.13.0" tslib "^2.1.0" @@ -579,10 +579,10 @@ resolved "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz" integrity sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg== -"@firebase/functions@0.13.0": - version "0.13.0" - resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.13.0.tgz#91685a59589b3a00f6c48faf383acd28a35800c2" - integrity sha512-2/LH5xIbD8aaLOWSFHAwwAybgSzHIM0dB5oVOL0zZnxFG1LctX2bc1NIAaPk1T+Zo9aVkLKUlB5fTXTkVUQprQ== +"@firebase/functions@0.13.1": + version "0.13.1" + resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.13.1.tgz#472e8456568689154b87a494ee8c10ee2e610d94" + integrity sha512-sUeWSb0rw5T+6wuV2o9XNmh9yHxjFI9zVGFnjFi+n7drTEWpl7ZTz1nROgGrSu472r+LAaj+2YaSicD4R8wfbw== dependencies: "@firebase/app-check-interop-types" "0.3.3" "@firebase/auth-interop-types" "0.2.4" @@ -651,14 +651,14 @@ idb "7.1.1" tslib "^2.1.0" -"@firebase/performance-compat@0.2.21": - version "0.2.21" - resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.21.tgz#60f04ecb5ff98b5d84a7a932f2e8294aae711c72" - integrity sha512-OQfYRsIQiEf9ez1SOMLb5TRevBHNIyA2x1GI1H10lZ432W96AK5r4LTM+SNApg84dxOuHt6RWSQWY7TPWffKXg== +"@firebase/performance-compat@0.2.22": + version "0.2.22" + resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.22.tgz#1c24ea360b03cfef831bdf379b4fc7080f412741" + integrity sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg== dependencies: "@firebase/component" "0.7.0" "@firebase/logger" "0.5.0" - "@firebase/performance" "0.7.8" + "@firebase/performance" "0.7.9" "@firebase/performance-types" "0.2.3" "@firebase/util" "1.13.0" tslib "^2.1.0" @@ -668,10 +668,10 @@ resolved "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz" integrity sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ== -"@firebase/performance@0.7.8": - version "0.7.8" - resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.7.8.tgz#a3e5ff36070e0f26e59f84fcd8faf7a8ee77c677" - integrity sha512-k6xfNM/CdTl4RaV4gT/lH53NU+wP33JiN0pUeNBzGVNvfXZ3HbCkoISE3M/XaiOwHgded1l6XfLHa4zHgm0Wyg== +"@firebase/performance@0.7.9": + version "0.7.9" + resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.7.9.tgz#7e3a072b1542f0df3f502684a38a0516b0d72cab" + integrity sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ== dependencies: "@firebase/component" "0.7.0" "@firebase/installations" "0.6.19" @@ -1525,34 +1525,34 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -firebase@12.0.0: - version "12.0.0" - resolved "https://registry.yarnpkg.com/firebase/-/firebase-12.0.0.tgz#436ee1b97b64265f3e7d3b6b0ce4419cc9fe5ae9" - integrity sha512-KV+OrMJpi2uXlqL2zaCcXb7YuQbY/gMIWT1hf8hKeTW1bSumWaHT5qfmn0WTpHwKQa3QEVOtZR2ta9EchcmYuw== +firebase@12.2.1: + version "12.2.1" + resolved "https://registry.yarnpkg.com/firebase/-/firebase-12.2.1.tgz#617817c6b64db05f4264822f89472cadc08f8924" + integrity sha512-UkuW2ZYaq/QuOQ24bfaqmkVqoBFhkA/ptATfPuRtc5vdm+zhwc3mfZBwFe6LqH9yrCN/6rAblgxKz2/0tDvA7w== dependencies: - "@firebase/ai" "2.0.0" + "@firebase/ai" "2.2.1" "@firebase/analytics" "0.10.18" "@firebase/analytics-compat" "0.2.24" - "@firebase/app" "0.14.0" + "@firebase/app" "0.14.2" "@firebase/app-check" "0.11.0" "@firebase/app-check-compat" "0.4.0" - "@firebase/app-compat" "0.5.0" + "@firebase/app-compat" "0.5.2" "@firebase/app-types" "0.9.3" "@firebase/auth" "1.11.0" "@firebase/auth-compat" "0.6.0" "@firebase/data-connect" "0.3.11" "@firebase/database" "1.1.0" "@firebase/database-compat" "2.1.0" - "@firebase/firestore" "4.9.0" - "@firebase/firestore-compat" "0.4.0" - "@firebase/functions" "0.13.0" - "@firebase/functions-compat" "0.4.0" + "@firebase/firestore" "4.9.1" + "@firebase/firestore-compat" "0.4.1" + "@firebase/functions" "0.13.1" + "@firebase/functions-compat" "0.4.1" "@firebase/installations" "0.6.19" "@firebase/installations-compat" "0.2.19" "@firebase/messaging" "0.12.23" "@firebase/messaging-compat" "0.2.23" - "@firebase/performance" "0.7.8" - "@firebase/performance-compat" "0.2.21" + "@firebase/performance" "0.7.9" + "@firebase/performance-compat" "0.2.22" "@firebase/remote-config" "0.6.6" "@firebase/remote-config-compat" "0.2.19" "@firebase/storage" "0.14.0"