(null);
+
+ const handleCountTokensClick = useCallback(async () => {
+ // Prevent counting if main action is loading or already counting
+ if (isLoading || isCountingTokens) return;
+
+ setIsCountingTokens(true);
+ setTokenCount(null);
+ setTokenCountError(null);
+
+ const currentPromptText = prompt.trim();
+ const parts: Part[] = [];
+
+ // Add text part if present
+ if (currentPromptText) {
+ parts.push({ text: currentPromptText });
+ }
+
+ // Add file part if present
+ if (selectedFile) {
+ try {
+ const filePart = await fileToGenerativePart(selectedFile);
+ parts.push(filePart);
+ } catch (err: unknown) {
+ console.error("Error processing file for token count:", err);
+ setTokenCountError(`Error processing file`);
+ setIsCountingTokens(false);
+ return;
+ }
+ }
+
+ // Don't count if there's nothing to count
+ if (parts.length === 0) {
+ setTokenCountError("Nothing to count tokens for (no text or file).");
+ setIsCountingTokens(false);
+ return;
+ }
+
+ try {
+ if (!currentParams) {
+ throw Error(
+ "[PromptInput] currentParams is undefined when counting tokens",
+ );
+ }
+ console.log("[PromptInput] Counting tokens with params:", currentParams);
+ const model = getGenerativeModel(aiInstance, currentParams);
+
+ const request = {
+ contents: [{ role: "user" as const, parts }],
+ systemInstruction: currentParams.systemInstruction,
+ tools: currentParams.tools,
+ };
+ console.log("[PromptInput] Calling countTokens with request:", request);
+
+ const result = await model.countTokens(request);
+ setTokenCount(result);
+ console.log("[PromptInput] Token count result:", result);
+ } catch (err: unknown) {
+ console.error("Error counting tokens:", err);
+ setTokenCountError(`Token count failed`);
+ setTokenCount(null); // Clear previous count on error
+ } finally {
+ setIsCountingTokens(false);
+ }
+ }, [
+ prompt,
+ selectedFile,
+ aiInstance,
+ currentParams,
+ isLoading,
+ isCountingTokens,
+ ]);
+
+ return (
+
+ {/* Suggestions */}
+ {suggestions && suggestions.length > 0 && onSuggestionClick && (
+
+ {suggestions.map((suggestion, index) => (
+ onSuggestionClick(suggestion)}
+ disabled={isLoading} // Disable buttons while loading
+ >
+ {suggestion}
+
+ ))}
+
+ )}
+
+ {/* Main Input Area */}
+
+
+
+ {/* Token Count Section - Only available for GenerativeModel */}
+ {activeMode === "chat" && (
+
+
+ {isCountingTokens ? "Counting..." : "Count Tokens"}
+
+
+ {tokenCountError && (
+ {tokenCountError}
+ )}
+ {!tokenCountError && tokenCount && (
+ Total Tokens: {tokenCount.totalTokens}
+ )}
+ {!tokenCountError && !tokenCount && !isCountingTokens && (
+
+ )}
+
+
+ )}
+
+ );
+};
+
+export default PromptInput;
diff --git a/ai/ai-react-app/src/components/Common/ResponseDisplay.module.css b/ai/ai-react-app/src/components/Common/ResponseDisplay.module.css
new file mode 100644
index 000000000..4e455a77c
--- /dev/null
+++ b/ai/ai-react-app/src/components/Common/ResponseDisplay.module.css
@@ -0,0 +1,97 @@
+.responseContainer {
+ margin: 20px 24px;
+ padding: 0;
+ color: var(--fb-gray-90);
+}
+
+.responseTitle {
+ color: var(--fb-gray-70);
+ font-weight: 500;
+ margin: 0 0 12px 0;
+ padding-bottom: 0;
+ border-bottom: none;
+ font-size: 0.8125rem;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.responseOutput {
+ background-color: var(--fb-gray-10);
+ padding: 16px;
+ border-radius: 4px;
+ border: 1px solid var(--fb-gray-30);
+ min-height: 100px;
+ margin-bottom: 16px;
+ position: relative;
+ overflow-wrap: break-word;
+ white-space: pre-wrap;
+ font-family: "Roboto Mono", monospace;
+ font-size: 0.875rem;
+ line-height: 1.6;
+ color: var(--fb-gray-90);
+}
+
+.responseText {
+ margin: 0;
+}
+
+.placeholder {
+ color: var(--fb-gray-60);
+ font-style: italic;
+}
+
+.loadingIndicator {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(48, 48, 48, 0.7);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: var(--fb-gray-80);
+ font-style: italic;
+ border-radius: 4px;
+ z-index: 1;
+}
+
+.jsonDetails {
+ margin-top: 16px;
+ border: 1px solid var(--fb-gray-40);
+ border-radius: 4px;
+ background-color: var(--fb-gray-10);
+}
+
+.jsonSummary {
+ padding: 8px 12px;
+ cursor: pointer;
+ background-color: var(--fb-gray-10);
+ border-radius: 4px 4px 0 0;
+ border-bottom: 1px solid var(--fb-gray-40);
+ outline: none;
+ font-size: 0.8125rem;
+ color: var(--fb-gray-80);
+ font-weight: 500;
+ list-style: none;
+}
+.jsonSummary::-webkit-details-marker {
+ display: none;
+}
+.jsonSummary:hover {
+ background-color: var(--fb-gray-30);
+}
+
+.jsonPre {
+ padding: 12px 16px;
+ margin: 0;
+ font-family: "Roboto Mono", monospace;
+ font-size: 0.8125rem;
+ color: var(--fb-gray-90);
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ background-color: var(--fb-gray-10);
+ border-radius: 0 0 4px 4px;
+ max-height: 400px;
+ overflow: auto;
+}
diff --git a/ai/ai-react-app/src/components/Common/ResponseDisplay.tsx b/ai/ai-react-app/src/components/Common/ResponseDisplay.tsx
new file mode 100644
index 000000000..295ff0f2b
--- /dev/null
+++ b/ai/ai-react-app/src/components/Common/ResponseDisplay.tsx
@@ -0,0 +1,82 @@
+import React from "react";
+import styles from "./ResponseDisplay.module.css";
+
+// Simple component to format and display JSON data within a tag.
+const JsonViewer: React.FC<{ data: object }> = ({ data }) => {
+ try {
+ // Pretty-print JSON with 2-space indentation
+ const jsonString = JSON.stringify(data, null, 2);
+ return {jsonString} ;
+ } catch (error) {
+ console.error("Error formatting JSON for display:", error);
+ return (
+ Error: Could not display JSON data.
+ );
+ }
+};
+
+interface ResponseDisplayProps {
+ /** The primary text output from the AI model (can be plain text or stringified JSON). */
+ responseText: string;
+ /** Optional parsed JSON object to display in a collapsible section. */
+ responseJson?: object | null;
+ /** Flag to indicate if the response is currently being generated. */
+ isLoading: boolean;
+ /** Optional title for the response area. */
+ title?: string;
+}
+
+/**
+ * Displays the AI model's response, handling loading states, text output,
+ * and an optional collapsible raw JSON view.
+ */
+const ResponseDisplay: React.FC = ({
+ responseText,
+ responseJson,
+ isLoading,
+ title = "Result",
+}) => {
+ // Determine whether to show the placeholder text.
+ // Show only if not loading AND there's no text or JSON data yet.
+ const showPlaceholder = !isLoading && !responseText && !responseJson;
+
+ // Determine whether to show the loading overlay.
+ // Show only when actively loading AND no response text has arrived yet (allows for streaming).
+ const showLoadingOverlay = isLoading && !responseText;
+
+ return (
+
+
{title}
+
+ {/* Loading Overlay */}
+ {showLoadingOverlay && (
+
Generating...
+ )}
+
+ {/* Main Response Text */}
+ {/* Render the responseText if it exists, even during loading to support streaming output */}
+ {responseText && (
+
{responseText}
+ )}
+
+ {/* Placeholder Text */}
+ {showPlaceholder && (
+
Output will appear here.
+ )}
+
+
+ {/* Optional JSON Viewer */}
+ {/* Show only if JSON data exists AND we are not currently loading */}
+ {responseJson && !isLoading && (
+
+
+ View Raw Response JSON
+
+
+
+ )}
+
+ );
+};
+
+export default ResponseDisplay;
diff --git a/ai/ai-react-app/src/components/Layout/LeftSidebar.module.css b/ai/ai-react-app/src/components/Layout/LeftSidebar.module.css
new file mode 100644
index 000000000..360af63e2
--- /dev/null
+++ b/ai/ai-react-app/src/components/Layout/LeftSidebar.module.css
@@ -0,0 +1,73 @@
+.sidebar ul {
+ list-style: none;
+ padding: 0 12px;
+ margin: 0;
+}
+
+.sidebar li {
+ margin-bottom: 4px;
+}
+
+.navButton {
+ background-color: transparent;
+ border: none;
+ color: var(--fb-gray-70);
+ padding: 8px 12px;
+ text-align: left;
+ width: 100%;
+ cursor: pointer;
+ border-radius: 4px;
+ font-size: 0.875rem;
+ transition:
+ background-color 0.15s ease,
+ color 0.15s ease;
+ font-weight: 500;
+}
+
+.navButton:hover {
+ background-color: var(--fb-gray-30);
+ color: var(--fb-gray-95);
+}
+
+.navButton.active {
+ background-color: var(--fb-gray-30);
+ color: var(--fb-yellow);
+ font-weight: 500;
+}
+
+.navButton.active:hover {
+ background-color: var(--fb-gray-40);
+}
+
+.backendSelector {
+ margin-top: 24px;
+ padding: 0 12px;
+ border-top: 1px solid var(--fb-gray-30);
+ padding-top: 16px;
+}
+
+.selectorTitle {
+ color: var(--fb-gray-70);
+ font-size: 0.75rem;
+ font-weight: 500;
+ margin: 0 0 8px 0;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.radioGroup {
+ margin-bottom: 6px;
+}
+
+.radioGroup label {
+ display: flex;
+ align-items: center;
+ font-size: 0.875rem;
+ color: var(--fb-gray-80);
+ cursor: pointer;
+}
+
+.radioGroup input[type="radio"] {
+ margin-right: 8px;
+ accent-color: var(--google-blue);
+}
diff --git a/ai/ai-react-app/src/components/Layout/LeftSidebar.tsx b/ai/ai-react-app/src/components/Layout/LeftSidebar.tsx
new file mode 100644
index 000000000..75e23d500
--- /dev/null
+++ b/ai/ai-react-app/src/components/Layout/LeftSidebar.tsx
@@ -0,0 +1,83 @@
+import React from "react";
+import { AppMode } from "../../App";
+import styles from "./LeftSidebar.module.css";
+import { BackendType } from "firebase/ai";
+
+interface LeftSidebarProps {
+ /** The currently active application mode (e.g., 'chat', 'imagenGen'). */
+ activeMode: AppMode;
+ /** Function to call when a mode button is clicked, updating the active mode in the parent. */
+ setActiveMode: (mode: AppMode) => void;
+ activeBackend: BackendType;
+ setActiveBackend: (backend: BackendType) => void;
+}
+
+/**
+ * Renders the left navigation sidebar allowing users to switch between application modes.
+ */
+const LeftSidebar: React.FC = ({
+ activeMode,
+ setActiveMode,
+ activeBackend,
+ setActiveBackend,
+}) => {
+ // Define the available modes and their display names
+ const modes: { id: AppMode; label: string }[] = [
+ { id: "chat", label: "Chat" },
+ { id: "imagenGen", label: "Imagen Generation" },
+ ];
+
+ const handleBackendChange = (event: React.ChangeEvent) => {
+ setActiveBackend(event.target.value as BackendType);
+ };
+
+ return (
+
+
+ {modes.map((modeInfo) => (
+
+ setActiveMode(modeInfo.id)}
+ aria-current={activeMode === modeInfo.id ? "page" : undefined}
+ >
+ {modeInfo.label}
+
+
+ ))}
+
+
+ {/* Backend Selection */}
+
+
Backend API
+
+
+
+ Gemini Developer API
+
+
+
+
+
+ Vertex AI Gemini API
+
+
+
+
+ );
+};
+
+export default LeftSidebar;
diff --git a/ai/ai-react-app/src/components/Layout/MainLayout.module.css b/ai/ai-react-app/src/components/Layout/MainLayout.module.css
new file mode 100644
index 000000000..6a9ce4b3e
--- /dev/null
+++ b/ai/ai-react-app/src/components/Layout/MainLayout.module.css
@@ -0,0 +1,39 @@
+.appContainer {
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ background-color: var(--fb-gray-10);
+ color: var(--fb-gray-90);
+ overflow: hidden;
+}
+
+.mainContentArea {
+ display: flex;
+ flex-grow: 1;
+ overflow: hidden;
+}
+
+.leftSidebar {
+ width: 240px;
+ flex-shrink: 0;
+ background-color: var(--fb-gray-10);
+ padding-top: 16px;
+ overflow-y: auto;
+ border-right: 1px solid var(--fb-gray-30);
+}
+
+.centerContent {
+ flex-grow: 1;
+ background-color: var(--fb-gray-10);
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
+}
+
+.rightSidebar {
+ width: 300px;
+ flex-shrink: 0;
+ background-color: var(--fb-gray-10);
+ overflow-y: auto;
+ border-left: 1px solid var(--fb-gray-30);
+}
diff --git a/ai/ai-react-app/src/components/Layout/MainLayout.tsx b/ai/ai-react-app/src/components/Layout/MainLayout.tsx
new file mode 100644
index 000000000..6e97a3a23
--- /dev/null
+++ b/ai/ai-react-app/src/components/Layout/MainLayout.tsx
@@ -0,0 +1,157 @@
+import React, { useState, useEffect } from "react";
+import TopBar from "./TopBar";
+import LeftSidebar from "./LeftSidebar";
+import RightSidebar from "./RightSidebar";
+import ChatView from "../../views/ChatView";
+import ImagenView from "../../views/ImagenView";
+import { AppMode } from "../../App";
+import {
+ UsageMetadata,
+ ModelParams,
+ ImagenModelParams,
+ BackendType,
+ AI,
+ VertexAIBackend,
+ GoogleAIBackend,
+ getAI,
+} from "firebase/ai";
+import {
+ AVAILABLE_GENERATIVE_MODELS,
+ AVAILABLE_IMAGEN_MODELS,
+ defaultGenerativeParams,
+ defaultImagenParams,
+} from "../../services/firebaseAIService";
+import styles from "./MainLayout.module.css";
+import { getApp } from "firebase/app";
+
+interface MainLayoutProps {
+ activeMode: AppMode;
+ setActiveMode: (mode: AppMode) => void;
+}
+
+/**
+ * Main layout component.
+ */
+const MainLayout: React.FC = ({
+ activeMode,
+ setActiveMode,
+}) => {
+ const [activeBackendType, setActiveBackendType] = useState(
+ BackendType.GOOGLE_AI,
+ ); // Default to Gemini Developer API
+ const [activeAI, setActiveAI] = useState(null);
+
+ const [generativeParams, setGenerativeParams] = useState({
+ model: AVAILABLE_GENERATIVE_MODELS[0],
+ ...defaultGenerativeParams,
+ });
+ const [imagenParams, setImagenParams] = useState({
+ model: AVAILABLE_IMAGEN_MODELS[0],
+ ...defaultImagenParams,
+ });
+
+ const [usageMetadata, setUsageMetadata] = useState(
+ null,
+ );
+
+ useEffect(() => {
+ console.log(`Initializing AI instance for backend: ${activeBackendType}`);
+ try {
+ const backendInstance =
+ activeBackendType === BackendType.VERTEX_AI
+ ? new VertexAIBackend()
+ : new GoogleAIBackend();
+ const aiInstance = getAI(getApp(), { backend: backendInstance });
+ setActiveAI(aiInstance);
+
+ console.log(
+ `AI instance for ${activeBackendType} initialized successfully.`,
+ );
+ } catch (error) {
+ console.error(
+ `Failed to initialize AI for backend ${activeBackendType}:`,
+ error,
+ );
+ setActiveAI(null);
+ }
+ }, [activeBackendType]);
+
+ useEffect(() => {
+ setUsageMetadata(null);
+ }, [activeMode]);
+
+ useEffect(() => {
+ const validModes: AppMode[] = ["chat", "imagenGen"];
+ if (!validModes.includes(activeMode)) {
+ console.warn(`Invalid activeMode "${activeMode}". Resetting to "chat".`);
+ setActiveMode("chat");
+ }
+ }, [activeMode, setActiveMode]);
+
+ const renderActiveView = () => {
+ // Show loading/error message if AI instance isn't ready
+ if (!activeAI) {
+ return (
+
+ Initializing AI for {activeBackendType}...
+
+ );
+ }
+
+ switch (activeMode) {
+ case "chat":
+ return (
+
+ );
+ case "imagenGen":
+ return (
+
+ );
+ default:
+ console.error(`Unexpected activeMode: ${activeMode}`);
+ return (
+
+ );
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
{renderActiveView()}
+
+ {/* Pass backend type for potential conditional rendering/logic */}
+
+
+
+
+ );
+};
+
+export default MainLayout;
diff --git a/ai/ai-react-app/src/components/Layout/RightSidebar.module.css b/ai/ai-react-app/src/components/Layout/RightSidebar.module.css
new file mode 100644
index 000000000..044e1b2ee
--- /dev/null
+++ b/ai/ai-react-app/src/components/Layout/RightSidebar.module.css
@@ -0,0 +1,255 @@
+.rightSidebarContainer {
+ padding: 24px;
+ color: var(--fb-gray-90);
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ font-size: 0.875rem;
+ background-color: var(--fb-gray-10);
+ overflow-y: auto;
+}
+
+.sectionTitle {
+ color: var(--fb-gray-80);
+ font-weight: 500;
+ margin: 0 0 16px 0;
+ padding-bottom: 12px;
+ border-bottom: 1px solid var(--fb-gray-30);
+ font-size: 0.9rem;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.subSectionTitle {
+ color: var(--fb-gray-70);
+ font-weight: 500;
+ margin: 16px 0 12px 0;
+ font-size: 0.8125rem;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.controlGroup {
+ margin-bottom: 16px;
+}
+
+.controlGroup label {
+ display: block;
+ margin-bottom: 8px;
+ color: var(--fb-gray-70);
+ font-size: 0.8125rem;
+ font-weight: 500;
+}
+
+.controlGroup select,
+.controlGroup input[type="number"],
+.controlGroup input[type="text"],
+.controlGroup textarea {
+ width: 100%;
+ padding: 8px 12px;
+ background-color: var(--fb-gray-10);
+ border: 1px solid var(--fb-gray-40);
+ color: var(--fb-gray-90);
+ border-radius: 4px;
+ box-sizing: border-box;
+ font-size: 0.875rem;
+ transition:
+ border-color 0.15s ease,
+ background-color 0.15s ease;
+}
+.controlGroup select:hover,
+.controlGroup input:not([type="range"]):hover,
+.controlGroup textarea:hover {
+ border-color: var(--fb-gray-50);
+}
+.controlGroup select:focus,
+.controlGroup input:not([type="range"]):focus,
+.controlGroup textarea:focus {
+ border-color: var(--google-blue);
+ background-color: var(--fb-gray-10);
+ outline: none;
+}
+.controlGroup input:-webkit-autofill {
+ -webkit-box-shadow: 0 0 0 30px var(--fb-gray-10) inset !important;
+ -webkit-text-fill-color: var(--fb-gray-90) !important;
+}
+
+.controlGroup input[type="range"] {
+ width: 100%;
+ height: 4px;
+ cursor: pointer;
+ appearance: none;
+ background: var(--fb-gray-40);
+ outline: none;
+ border-radius: 2px;
+ padding: 0;
+ margin-top: 8px;
+}
+.controlGroup input[type="range"]::-webkit-slider-thumb {
+ appearance: none;
+ width: 14px;
+ height: 14px;
+ background: var(--fb-gray-80);
+ border-radius: 50%;
+ cursor: pointer;
+}
+.controlGroup input[type="range"]::-moz-range-thumb {
+ width: 14px;
+ height: 14px;
+ background: var(--fb-gray-80);
+ border-radius: 50%;
+ cursor: pointer;
+ border: none;
+}
+.controlGroup input[type="range"]:active::-webkit-slider-thumb {
+ background: var(--fb-gray-90);
+}
+.controlGroup input[type="range"]:active::-moz-range-thumb {
+ background: var(--fb-gray-90);
+}
+
+.controlGroup textarea {
+ resize: vertical;
+ min-height: 60px;
+}
+
+.temperatureDisplay {
+ text-align: right;
+ font-size: 0.8125rem;
+ color: var(--fb-gray-60);
+ margin-top: 6px;
+}
+
+.tokenDisplay {
+ background-color: var(--fb-gray-10);
+ border: 1px solid var(--fb-gray-40);
+ padding: 8px 12px;
+ border-radius: 4px;
+ font-size: 0.8125rem;
+ color: var(--fb-gray-70);
+ text-align: right;
+ font-family: "Roboto Mono", monospace;
+}
+
+.toggleGroup {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 12px;
+ min-height: 30px;
+}
+.toggleGroup label:first-child {
+ color: var(--fb-gray-70);
+ font-size: 0.875rem;
+ padding-right: 10px;
+}
+
+.switch {
+ position: relative;
+ display: inline-block;
+ width: 36px;
+ height: 14px;
+ flex-shrink: 0;
+}
+
+.switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: var(--fb-gray-40);
+ transition: 0.2s;
+ border-radius: 7px;
+}
+
+.slider:before {
+ position: absolute;
+ content: "";
+ height: 20px;
+ width: 20px;
+ left: -2px;
+ bottom: -3px;
+ background-color: var(--fb-gray-80);
+ transition: 0.2s;
+ border-radius: 50%;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
+}
+
+input:checked + .slider {
+ background-color: var(--google-blue);
+}
+
+input:checked + .slider:before {
+ transform: translateX(20px);
+ background-color: var(--fb-gray-95);
+}
+
+.toggleGroup input:disabled + .slider {
+ background-color: var(--fb-gray-30) !important;
+ cursor: not-allowed;
+}
+.toggleGroup input:disabled + .slider:before {
+ background-color: var(--fb-gray-50) !important;
+ cursor: not-allowed;
+ box-shadow: none;
+}
+.toggleGroup.disabledText label:first-child,
+.toggleGroup label input:disabled ~ span {
+ color: var(--fb-gray-50);
+ cursor: not-allowed;
+}
+.toggleGroup input:disabled ~ span {
+ cursor: not-allowed;
+}
+
+.advancedSettings details {
+ border: none;
+ margin-top: 20px;
+ background-color: transparent;
+}
+.advancedSettings summary {
+ padding: 8px 0;
+ cursor: pointer;
+ outline: none;
+ font-weight: 500;
+ color: var(--fb-gray-70);
+ font-size: 0.875rem;
+ list-style: none;
+ position: relative;
+}
+.advancedSettings summary:hover {
+ color: var(--fb-gray-90);
+}
+.advancedSettings summary::-webkit-details-marker {
+ display: none;
+}
+.advancedSettings summary::before {
+ content: "▶";
+ display: inline-block;
+ margin-right: 8px;
+ font-size: 0.7em;
+ transition: transform 0.2s ease-out;
+ color: var(--fb-gray-60);
+}
+.advancedSettings details[open] summary::before {
+ transform: rotate(90deg);
+}
+
+.advancedSettings .advancedContent {
+ padding: 16px 0 0 0;
+ border-top: 1px solid var(--fb-gray-30);
+ margin-top: 8px;
+}
+.advancedSettings .controlGroup label {
+ font-weight: 400;
+ color: var(--fb-gray-70);
+}
diff --git a/ai/ai-react-app/src/components/Layout/RightSidebar.tsx b/ai/ai-react-app/src/components/Layout/RightSidebar.tsx
new file mode 100644
index 000000000..9c296c9b6
--- /dev/null
+++ b/ai/ai-react-app/src/components/Layout/RightSidebar.tsx
@@ -0,0 +1,472 @@
+import React from "react";
+import { AppMode } from "../../App";
+import styles from "./RightSidebar.module.css";
+import {
+ AVAILABLE_GENERATIVE_MODELS,
+ AVAILABLE_IMAGEN_MODELS,
+ defaultFunctionCallingTool,
+} from "../../services/firebaseAIService";
+import {
+ ModelParams,
+ ImagenModelParams,
+ HarmCategory,
+ HarmBlockThreshold,
+ FunctionCallingMode,
+ ImagenSafetyFilterLevel,
+ ImagenPersonFilterLevel,
+ UsageMetadata,
+} from "firebase/ai";
+
+interface RightSidebarProps {
+ usageMetadata: UsageMetadata | null;
+ activeMode: AppMode;
+ generativeParams: ModelParams;
+ setGenerativeParams: React.Dispatch>;
+ imagenParams: ImagenModelParams;
+ setImagenParams: React.Dispatch>;
+}
+
+const RightSidebar: React.FC = ({
+ usageMetadata,
+ activeMode,
+ generativeParams,
+ setGenerativeParams,
+ imagenParams,
+ setImagenParams,
+}) => {
+ const handleModelParamsUpdate = (
+ updateFn: (prevState: ModelParams) => ModelParams,
+ ) => {
+ setGenerativeParams((prevState) => updateFn(prevState));
+ };
+
+ const handleImagenModelParamsUpdate = (
+ updateFn: (prevState: ImagenModelParams) => ImagenModelParams,
+ ) => {
+ setImagenParams((prevState) => updateFn(prevState));
+ };
+
+ const getThresholdForCategory = (
+ category: HarmCategory,
+ ): HarmBlockThreshold => {
+ const setting = (generativeParams.safetySettings || []).find(
+ (s) => s.category === category,
+ );
+ return setting?.threshold || HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE;
+ };
+
+ const handleGenerativeModelChange = (
+ event: React.ChangeEvent,
+ ) => {
+ const newModel = event.target.value;
+ handleModelParamsUpdate((prev: ModelParams) => ({
+ ...prev,
+ model: newModel,
+ }));
+ };
+
+ const handleTemperatureChange = (
+ event: React.ChangeEvent,
+ ) => {
+ const newTemp = parseFloat(event.target.value);
+ handleModelParamsUpdate((prev: ModelParams) => ({
+ ...prev,
+ generationConfig: { ...prev.generationConfig, temperature: newTemp },
+ }));
+ };
+
+ const handleTopPChange = (event: React.ChangeEvent) => {
+ const newTopP = parseFloat(event.target.value);
+ handleModelParamsUpdate((prev: ModelParams) => ({
+ ...prev,
+ generationConfig: { ...prev.generationConfig, topP: newTopP },
+ }));
+ };
+
+ const handleTopKChange = (event: React.ChangeEvent) => {
+ const newTopK = parseInt(event.target.value, 10);
+ handleModelParamsUpdate((prev: ModelParams) => ({
+ ...prev,
+ generationConfig: { ...prev.generationConfig, topK: newTopK },
+ }));
+ };
+
+ const handleSafetySettingChange = (
+ category: HarmCategory,
+ threshold: HarmBlockThreshold,
+ ) => {
+ handleModelParamsUpdate((prev: ModelParams) => {
+ const currentSettings = prev.safetySettings || [];
+ let settingExists = false;
+ const newSettings = currentSettings.map((s) => {
+ if (s.category === category) {
+ settingExists = true;
+ return { ...s, threshold };
+ }
+ return s;
+ });
+ if (!settingExists) {
+ newSettings.push({ category, threshold });
+ }
+ return { ...prev, safetySettings: newSettings };
+ });
+ };
+
+ const handleToggleChange = (event: React.ChangeEvent) => {
+ const { name, checked } = event.target;
+ console.log(`[RightSidebar] Toggle change: ${name}, Checked: ${checked}`);
+
+ handleModelParamsUpdate((prev: ModelParams): ModelParams => {
+ // Clone the previous state to avoid direct mutation
+ const nextState = JSON.parse(JSON.stringify(prev));
+
+ // Ensure nested objects exist before modifying
+ nextState.generationConfig = nextState.generationConfig ?? {};
+ nextState.toolConfig = nextState.toolConfig ?? {};
+
+ if (name === "structured-output-toggle") {
+ if (checked) {
+ // Turn ON JSON
+ nextState.generationConfig.responseMimeType = "application/json";
+ nextState.generationConfig.responseSchema = undefined; // Use a default schema
+
+ // Turn OFF Function Calling by clearing its related fields
+ nextState.tools = undefined;
+ nextState.toolConfig = undefined;
+ } else {
+ // Turn OFF JSON
+ nextState.generationConfig.responseMimeType = undefined;
+ nextState.generationConfig.responseSchema = undefined;
+ }
+ } else if (name === "function-call-toggle") {
+ if (checked) {
+ // Turn ON Function Calling
+ // Use default tools if none were previously defined, otherwise keep existing
+ nextState.tools =
+ prev.tools && prev.tools.length > 0
+ ? prev.tools
+ : [defaultFunctionCallingTool];
+ nextState.toolConfig = {
+ functionCallingConfig: { mode: FunctionCallingMode.AUTO },
+ };
+
+ // Turn OFF JSON mode by clearing its related fields
+ nextState.generationConfig.responseMimeType = undefined;
+ nextState.generationConfig.responseSchema = undefined;
+ } else {
+ // Turn OFF Function Calling
+ nextState.tools = undefined;
+ nextState.toolConfig = undefined; // Clear config when turning off
+ }
+ }
+ console.log("[RightSidebar] Updated generative params state:", nextState);
+ return nextState;
+ });
+ };
+
+ const handleImagenModelChange = (
+ event: React.ChangeEvent,
+ ) => {
+ const newModel = event.target.value;
+ handleImagenModelParamsUpdate((prev: ImagenModelParams) => ({
+ ...prev,
+ model: newModel,
+ }));
+ };
+ const handleImagenSamplesChange = (
+ event: React.ChangeEvent,
+ ) => {
+ const newCount = parseInt(event.target.value, 10);
+ handleImagenModelParamsUpdate((prev: ImagenModelParams) => ({
+ ...prev,
+ generationConfig: { ...prev.generationConfig, numberOfImages: newCount },
+ }));
+ };
+ const handleImagenNegativePromptChange = (
+ event: React.ChangeEvent,
+ ) => {
+ const newPrompt = event.target.value || undefined;
+ handleImagenModelParamsUpdate((prev: ImagenModelParams) => ({
+ ...prev,
+ generationConfig: { ...prev.generationConfig, negativePrompt: newPrompt },
+ }));
+ };
+ const handleImagenSafetyChange = (
+ event: React.ChangeEvent,
+ ) => {
+ const newLevel = event.target.value as ImagenSafetyFilterLevel;
+ handleImagenModelParamsUpdate((prev: ImagenModelParams) => ({
+ ...prev,
+ safetySettings: { ...prev.safetySettings, safetyFilterLevel: newLevel },
+ }));
+ };
+ const handleImagenPersonChange = (
+ event: React.ChangeEvent,
+ ) => {
+ const newLevel = event.target.value as ImagenPersonFilterLevel;
+ handleImagenModelParamsUpdate((prev: ImagenModelParams) => ({
+ ...prev,
+ safetySettings: { ...prev.safetySettings, personFilterLevel: newLevel },
+ }));
+ };
+
+ // Derive UI state from config
+ const isStructuredOutputActive =
+ generativeParams.generationConfig?.responseMimeType === "application/json";
+ const isFunctionCallingActive =
+ (generativeParams.toolConfig?.functionCallingConfig?.mode ===
+ FunctionCallingMode.AUTO ||
+ generativeParams.toolConfig?.functionCallingConfig?.mode ===
+ FunctionCallingMode.ANY) &&
+ !!generativeParams.tools?.length;
+
+ return (
+
+ {/* Generative Model Settings */}
+ {activeMode === "chat" && (
+ <>
+
+
+ Generative Model Settings
+
+
+ Model
+
+ {AVAILABLE_GENERATIVE_MODELS.map((modelName) => (
+
+ {modelName}
+
+ ))}
+
+
+
+
+ Temperature:{" "}
+ {generativeParams.generationConfig?.temperature?.toFixed(1) ??
+ "N/A"}
+
+
+
+
+
Last Response Tokens
+
+ {usageMetadata
+ ? `Prompt: ${usageMetadata.promptTokenCount} / Candidate: ${usageMetadata.candidatesTokenCount} / Total: ${usageMetadata.totalTokenCount}`
+ : `N/A`}
+
+
+
+
+
+
+ Advanced Generation
+
+
+
+ Top P:{" "}
+ {generativeParams.generationConfig?.topP?.toFixed(2) ??
+ "N/A (Default)"}
+
+
+
+
+
+ Top K:{" "}
+ {generativeParams.generationConfig?.topK ?? "N/A (Default)"}
+
+
+
+
+
+
+
+ Safety Settings
+
+ {Object.values(HarmCategory).map((category) => (
+
+
+ {category
+ .replace("HARM_CATEGORY_", "")
+ .replace(/_/g, " ")
+ .toLowerCase()
+ .replace(/\b\w/g, (l) => l.toUpperCase())}
+
+
+ handleSafetySettingChange(
+ category,
+ e.target.value as HarmBlockThreshold,
+ )
+ }
+ >
+ {Object.values(HarmBlockThreshold).map((threshold) => (
+
+ {threshold.replace("BLOCK_", "").replace(/_/g, " ")}
+
+ ))}
+
+
+ ))}
+
+
+
+
Tools
+
+
+ Structured output (JSON)
+
+
+
+
+
+
+
+ Function calling
+
+
+
+
+
+
+ >
+ )}
+
+ {/* Imagen Settings */}
+ {activeMode === "imagenGen" && (
+
+
Imagen Settings
+
+ Model
+
+ {AVAILABLE_IMAGEN_MODELS.map((modelName) => (
+
+ {modelName}
+
+ ))}
+
+
+
+ Number of Images
+
+
+
+ Negative Prompt
+
+
+
+ Safety Filter Level
+
+
+ Select Level
+
+ {Object.values(ImagenSafetyFilterLevel).map((level) => (
+
+ {level.replace(/_/g, " ")}
+
+ ))}
+
+
+
+ Person Filter Level
+
+
+ Select Level
+
+ {Object.values(ImagenPersonFilterLevel).map((level) => (
+
+ {level.replace(/_/g, " ")}
+
+ ))}
+
+
+
+ )}
+
+ );
+};
+
+export default RightSidebar;
diff --git a/ai/ai-react-app/src/components/Layout/TopBar.module.css b/ai/ai-react-app/src/components/Layout/TopBar.module.css
new file mode 100644
index 000000000..ad4804e69
--- /dev/null
+++ b/ai/ai-react-app/src/components/Layout/TopBar.module.css
@@ -0,0 +1,29 @@
+.topBarContainer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ height: 50px;
+ padding: 0 24px;
+ background-color: var(--fb-gray-10);
+ border-bottom: 1px solid var(--fb-gray-30);
+ color: var(--fb-gray-90);
+ flex-shrink: 0;
+}
+
+.leftSection {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.logoImage {
+ height: 24px;
+ width: 24px;
+ display: block;
+}
+
+.title {
+ font-size: 1rem;
+ font-weight: 500;
+ color: var(--fb-gray-80);
+}
diff --git a/ai/ai-react-app/src/components/Layout/TopBar.tsx b/ai/ai-react-app/src/components/Layout/TopBar.tsx
new file mode 100644
index 000000000..0042488fe
--- /dev/null
+++ b/ai/ai-react-app/src/components/Layout/TopBar.tsx
@@ -0,0 +1,22 @@
+import React from "react";
+import styles from "./TopBar.module.css";
+import firebaseIcon from "../../assets/firebase-icon.png";
+
+const TopBar: React.FC = () => {
+ return (
+
+
+
+
Firebase AI SDK Sample
+
+
+ );
+};
+
+export default TopBar;
diff --git a/ai/ai-react-app/src/components/Specific/ChatMessage.module.css b/ai/ai-react-app/src/components/Specific/ChatMessage.module.css
new file mode 100644
index 000000000..6adb2ff8c
--- /dev/null
+++ b/ai/ai-react-app/src/components/Specific/ChatMessage.module.css
@@ -0,0 +1,49 @@
+.messageContainer {
+ display: flex;
+ margin-bottom: 8px;
+ max-width: 80%;
+}
+
+/* Align user messages to the right */
+.messageContainer.user {
+ justify-content: flex-end;
+ margin-left: auto;
+}
+
+/* Align model messages to the left */
+.messageContainer.model {
+ justify-content: flex-start;
+ margin-right: auto;
+}
+
+.messageBubble {
+ padding: 8px 14px;
+ border-radius: 16px;
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ line-height: 1.5;
+ font-size: 0.875rem;
+ border: 1px solid transparent;
+}
+
+.messageContainer.user .messageBubble {
+ background-color: var(--fb-gray-40);
+ color: var(--fb-gray-95);
+ border-color: var(--fb-gray-50);
+ border-bottom-right-radius: 4px;
+}
+
+.messageContainer.model .messageBubble {
+ background-color: var(--fb-gray-10);
+ color: var(--fb-gray-90);
+ border-color: var(--fb-gray-30);
+ border-bottom-left-radius: 4px;
+}
+
+.messageText {
+ margin: 0;
+ font-family: inherit;
+ font-size: inherit;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
diff --git a/ai/ai-react-app/src/components/Specific/ChatMessage.tsx b/ai/ai-react-app/src/components/Specific/ChatMessage.tsx
new file mode 100644
index 000000000..6d0f94a49
--- /dev/null
+++ b/ai/ai-react-app/src/components/Specific/ChatMessage.tsx
@@ -0,0 +1,63 @@
+import React from "react";
+import { Content } from "firebase/ai";
+import styles from "./ChatMessage.module.css";
+
+interface ChatMessageProps {
+ message: Content;
+}
+
+/**
+ * Extracts and concatenates text from all 'text' parts within a Content message.
+ * Ignores other part types like images or function calls for text extraction.
+ * @param message The Content object.
+ * @returns The combined text string, or an empty string if no text parts are found.
+ */
+const getMessageText = (message: Content): string => {
+ if (!message.parts || message.parts.length === 0) {
+ return "";
+ }
+ // Filter for parts that are TextPart (have a 'text' property) and join them.
+ return message.parts
+ .filter((part): part is { text: string } => typeof part.text === "string")
+ .map((part) => part.text)
+ .join("");
+};
+
+/**
+ * Renders a single chat message bubble, styled based on the message role ('user' or 'model').
+ * It only renders messages that should be visible in the log (user messages, or model messages
+ * containing text). Function role messages or model messages consisting only of function calls
+ * are typically not rendered directly as chat bubbles.
+ */
+const ChatMessage: React.FC = ({ message }) => {
+ const text = getMessageText(message);
+ const isUser = message.role === "user";
+ const isModel = message.role === "model";
+
+ // We render:
+ // 1. User messages (even if they only contain images/files, the 'user' role indicates an entry).
+ // 2. Model messages *only if* they contain actual text content.
+ // We *don't* render:
+ // 1. 'function' role messages (these represent execution results, not direct chat).
+ // 2. 'model' role messages that *only* contain function calls (these are instructions, not display text).
+ // 3. 'system' role messages (handled separately, not usually in chat history display).
+ const shouldRender = isUser || (isModel && text.trim() !== "");
+
+ if (!shouldRender) {
+ return null;
+ }
+
+ return (
+
+
+ {/* Use
to preserve whitespace and newlines within the text content.
+ Handles potential multi-line responses correctly. */}
+ {text}
+
+
+ );
+};
+
+export default ChatMessage;
diff --git a/ai/ai-react-app/src/config/firebase-config.ts b/ai/ai-react-app/src/config/firebase-config.ts
new file mode 100644
index 000000000..8cd3a394d
--- /dev/null
+++ b/ai/ai-react-app/src/config/firebase-config.ts
@@ -0,0 +1,9 @@
+// TODO (developer): Replace with your Firebase project configuration
+export const firebaseConfig = {
+ apiKey: "YOUR_API_KEY",
+ authDomain: "YOUR_AUTH_DOMAIN",
+ projectId: "YOUR_PROJECT_ID",
+ storageBucket: "YOUR_STORAGE_BUCKET",
+ messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
+ appId: "YOUR_APP_ID",
+};
\ No newline at end of file
diff --git a/ai/ai-react-app/src/index.css b/ai/ai-react-app/src/index.css
new file mode 100644
index 000000000..051b3a985
--- /dev/null
+++ b/ai/ai-react-app/src/index.css
@@ -0,0 +1,87 @@
+:root {
+ --fb-gray-0: #000000;
+ --fb-gray-10: #121212;
+ --fb-gray-20: #303030;
+ --fb-gray-30: #474747;
+ --fb-gray-40: #5e5e5e;
+ --fb-gray-50: #757575;
+ --fb-gray-60: #8f8f8f;
+ --fb-gray-70: #ababab;
+ --fb-gray-80: #c7c7c7;
+ --fb-gray-90: #e3e3e3;
+ --fb-gray-95: #f2f2f2;
+ --fb-gray-99: #fdfcfb;
+ --fb-gray-100: #ffffff;
+
+ --fb-yellow: #ffc400;
+ --fb-orange: #ff9100;
+ --fb-red: #dd2c00;
+
+ --google-blue: #4285f4;
+ --google-blue-darker: #1b66c9;
+ --google-blue-focus: #8ab4f8;
+
+ --fb-error-bg: rgba(221, 44, 0, 0.1);
+ --fb-error-text: #f79489;
+ --fb-error-border: rgba(221, 44, 0, 0.4);
+
+ --fb-warning-bg: rgba(255, 145, 0, 0.1);
+ --fb-warning-text: #ffcc80;
+ --fb-warning-border: rgba(255, 145, 0, 0.4);
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+}
+
+body {
+ background-color: var(--fb-gray-10);
+ color: var(--fb-gray-90);
+ font-family:
+ "Roboto",
+ -apple-system,
+ BlinkMacSystemFont,
+ "Segoe UI",
+ Oxygen,
+ "Ubuntu",
+ Cantarell,
+ "Fira Sans",
+ "Droid Sans",
+ "Helvetica Neue",
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ overflow: hidden;
+ font-weight: 500;
+ font-size: 14px;
+}
+
+#root {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+*:focus-visible {
+ outline: 2px solid var(--google-blue-focus);
+ outline-offset: 1px;
+}
+
+button,
+input,
+select,
+textarea {
+ font-family: inherit;
+ font-size: inherit;
+ font-weight: inherit;
+ color: inherit;
+}
diff --git a/ai/ai-react-app/src/main.tsx b/ai/ai-react-app/src/main.tsx
new file mode 100644
index 000000000..ee5628fd9
--- /dev/null
+++ b/ai/ai-react-app/src/main.tsx
@@ -0,0 +1,15 @@
+import { createRoot } from "react-dom/client";
+import "./index.css";
+import App from "./App";
+
+const rootElement = document.getElementById("root");
+
+if (!rootElement) {
+ throw new Error(
+ "Failed to find the root element. Make sure your HTML file has an element with id='root'.",
+ );
+}
+
+const root = createRoot(rootElement);
+
+root.render( );
diff --git a/ai/ai-react-app/src/services/firebaseAIService.ts b/ai/ai-react-app/src/services/firebaseAIService.ts
new file mode 100644
index 000000000..12ebb9e8b
--- /dev/null
+++ b/ai/ai-react-app/src/services/firebaseAIService.ts
@@ -0,0 +1,160 @@
+import { initializeApp, FirebaseApp } from "firebase/app";
+import {
+ HarmCategory,
+ HarmBlockThreshold,
+ Schema,
+ FunctionDeclaration,
+ Part,
+ Content,
+ CountTokensResponse,
+ GenerativeModel,
+ ModelParams,
+ ImagenModelParams,
+ FunctionCall,
+} from "firebase/vertexai";
+
+import { firebaseConfig } from "../config/firebase-config";
+
+export const AVAILABLE_GENERATIVE_MODELS = [
+ "gemini-2.0-flash",
+ "gemini-2.0-flash-lite",
+ "gemini-2.0-flash-exp",
+];
+export const AVAILABLE_IMAGEN_MODELS = ["imagen-3.0-generate-002"];
+
+let app: FirebaseApp;
+try {
+ if (firebaseConfig.apiKey === "YOUR_API_KEY" || !firebaseConfig.projectId) {
+ console.error(
+ "Firebase config uses placeholder values. Update src/config/firebase-config.ts.",
+ );
+ }
+ app = initializeApp(firebaseConfig);
+ console.log("Firebase App Initialized.");
+} catch (error) {
+ console.error("Error initializing Firebase App:", error);
+ throw new Error("Firebase initialization failed.");
+}
+
+export const defaultFunctionCallingTool = {
+ functionDeclarations: [
+ {
+ name: "getCurrentWeather",
+ description: "Get the current weather in a given location",
+ parameters: Schema.object({
+ properties: {
+ location: Schema.string({
+ description: "The city and state, e.g. San Francisco, CA",
+ }),
+ unit: Schema.enumString({
+ enum: ["celsius", "fahrenheit"],
+ description: "Temperature unit",
+ }),
+ },
+ required: ["location"],
+ }),
+ } as FunctionDeclaration,
+ ],
+};
+
+export const defaultGenerativeParams: Omit = {
+ // Model name itself is selected in the UI
+ generationConfig: {
+ temperature: 0.9,
+ },
+ safetySettings: [
+ {
+ category: HarmCategory.HARM_CATEGORY_HARASSMENT,
+ threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
+ },
+ {
+ category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
+ threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
+ },
+ {
+ category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
+ threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
+ },
+ {
+ category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
+ threshold: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
+ },
+ ],
+ // tools, toolConfig, systemInstruction default to undefined
+};
+
+export const defaultImagenParams: Omit = {
+ // Model name selected in UI
+ generationConfig: {
+ numberOfImages: 1,
+ },
+ // Default all safety settings to undefined
+};
+
+/**
+ * Mock function call
+ */
+export const handleFunctionExecution = async (
+ functionCall: FunctionCall,
+): Promise => {
+ console.log(
+ `[Service] Executing function: ${functionCall.name}, Args:`,
+ functionCall.args,
+ );
+ if (functionCall.name === "getCurrentWeather") {
+ await new Promise((resolve) => setTimeout(resolve, 800)); // Simulate delay
+ const location: string =
+ "location" in functionCall.args &&
+ typeof functionCall.args.location === "string"
+ ? functionCall.args.location
+ : "Default City, ST";
+ const unit: string =
+ "unit" in functionCall.args && typeof functionCall.args.unit === "string"
+ ? functionCall.args.unit
+ : "celsius";
+ const temp =
+ unit === "celsius"
+ ? Math.floor(Math.random() * 30 + 5)
+ : Math.floor(Math.random() * 50 + 40);
+ const conditions = ["Sunny", "Partly Cloudy", "Cloudy", "Rainy"];
+ const condition = conditions[Math.floor(Math.random() * conditions.length)];
+ return {
+ weather: { location, temperature: temp, unit, condition },
+ };
+ }
+ console.warn(`[Service] Unknown function called: ${name}`);
+ return { error: `Function ${name} not implemented.` };
+};
+
+export const countTokensInPrompt = async (
+ modelInstance: GenerativeModel,
+ parts: Part[],
+ history: Content[] = [],
+ params?: {
+ systemInstruction?: ModelParams["systemInstruction"];
+ tools?: ModelParams["tools"];
+ },
+): Promise => {
+ const contents: Content[] = [...history];
+ if (parts.length > 0) {
+ contents.push({ role: "user", parts });
+ }
+
+ const request = {
+ contents,
+ systemInstruction: params?.systemInstruction,
+ tools: params?.tools,
+ };
+
+ console.log("[Service] Counting tokens for request:", request);
+ try {
+ const result = await modelInstance.countTokens(request);
+ console.log("[Service] Token count result:", result);
+ return result;
+ } catch (error) {
+ console.error("[Service] Error counting tokens:", error);
+ throw error;
+ }
+};
+
+export { app };
diff --git a/ai/ai-react-app/src/utils/fileUtils.ts b/ai/ai-react-app/src/utils/fileUtils.ts
new file mode 100644
index 000000000..3493eaa58
--- /dev/null
+++ b/ai/ai-react-app/src/utils/fileUtils.ts
@@ -0,0 +1,49 @@
+import { Part } from "firebase/ai";
+
+/**
+ * Converts a File object into a Firebase AI SDK Part object suitable for multimodal requests.
+ * Reads the file as a base64-encoded string and includes its MIME type.
+ *
+ * @param file The File object (e.g., from an element).
+ * @returns A Promise resolving to a Part object containing inline base64 data and MIME type.
+ * @throws An error if the file cannot be read or processed.
+ */
+export async function fileToGenerativePart(file: File): Promise {
+ const base64EncodedDataPromise = new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ // The FileReader result includes the Data URL prefix (e.g., "data:image/jpeg;base64,").
+ // We only need the actual base64 data part after the comma.
+ const base64String = reader.result as string;
+ if (base64String && base64String.includes(",")) {
+ resolve(base64String.split(",")[1]);
+ } else {
+ reject(new Error("Invalid file data format received from FileReader."));
+ }
+ };
+ reader.onerror = (errorEvent) => {
+ reject(
+ new Error(
+ `FileReader error: ${errorEvent?.target?.error?.message || "Unknown error"}`,
+ ),
+ );
+ };
+ // Start reading the file
+ reader.readAsDataURL(file);
+ });
+
+ try {
+ const base64EncodedData = await base64EncodedDataPromise;
+ return {
+ inlineData: {
+ data: base64EncodedData,
+ mimeType: file.type,
+ },
+ };
+ } catch (error) {
+ console.error("Error converting file to Generative Part:", error);
+ throw new Error(
+ `Failed to process the file "${file.name}". Please ensure it's a valid file.`,
+ );
+ }
+}
diff --git a/ai/ai-react-app/src/views/ChatView.module.css b/ai/ai-react-app/src/views/ChatView.module.css
new file mode 100644
index 000000000..b14f84165
--- /dev/null
+++ b/ai/ai-react-app/src/views/ChatView.module.css
@@ -0,0 +1,132 @@
+.chatViewContainer {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ background-color: var(--fb-gray-10);
+}
+
+.chatHistory {
+ flex-grow: 1;
+ overflow-y: auto;
+ padding: 16px 24px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.emptyChat {
+ text-align: center;
+ color: var(--fb-gray-60);
+ margin-top: 40px;
+ font-style: italic;
+}
+
+.loadingBubble {
+ display: flex;
+ justify-content: flex-start;
+ margin-right: auto;
+ margin-bottom: 8px;
+ padding: 8px 14px;
+ border-radius: 16px;
+ border-bottom-left-radius: 4px;
+ background-color: var(--fb-gray-10);
+ border: 1px solid var(--fb-gray-30);
+ align-items: center;
+ min-height: 1em;
+}
+.loadingBubble span {
+ display: inline-block;
+ width: 6px;
+ height: 6px;
+ background-color: var(--fb-gray-60);
+ border-radius: 50%;
+ margin: 0 2px;
+ animation: bounce 1.4s infinite ease-in-out both;
+}
+.loadingBubble span:nth-child(1) {
+ animation-delay: -0.32s;
+}
+.loadingBubble span:nth-child(2) {
+ animation-delay: -0.16s;
+}
+.loadingBubble span:nth-child(3) {
+ animation-delay: 0s;
+}
+@keyframes bounce {
+ 0%,
+ 80%,
+ 100% {
+ transform: scale(0);
+ opacity: 0.5;
+ }
+ 40% {
+ transform: scale(0.8);
+ opacity: 1;
+ }
+}
+
+.errorMessage {
+ background-color: var(--fb-error-bg);
+ color: var(--fb-error-text);
+ border: 1px solid var(--fb-error-border);
+ padding: 10px 16px;
+ border-radius: 4px;
+ margin: 10px 0;
+ font-size: 0.875rem;
+ white-space: pre-wrap;
+}
+
+.jsonDetails {
+ margin-top: 4px;
+ margin-bottom: 8px;
+ border: 1px solid var(--fb-gray-40);
+ border-radius: 4px;
+ background-color: var(--fb-gray-10);
+ max-width: 80%;
+ margin-left: 0;
+ margin-right: auto;
+}
+.jsonSummary {
+ padding: 6px 12px;
+ cursor: pointer;
+ background-color: var(--fb-gray-10);
+ border-bottom: 1px solid var(--fb-gray-40);
+ outline: none;
+ font-size: 0.8125rem;
+ color: var(--fb-gray-80);
+ font-weight: 500;
+ list-style: none;
+}
+.jsonSummary::-webkit-details-marker {
+ display: none;
+}
+.jsonSummary:hover {
+ background-color: var(--fb-gray-30);
+}
+.jsonDetails pre {
+ padding: 12px 16px;
+ margin: 0;
+ font-family: "Roboto Mono", monospace;
+ font-size: 0.8125rem;
+ color: var(--fb-gray-90);
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ background-color: var(--fb-gray-10);
+ border-radius: 0 0 4px 4px;
+ max-height: 300px;
+ overflow: auto;
+}
+
+.inputAreaContainer {
+ flex-shrink: 0;
+ background-color: var(--fb-gray-10);
+ border-top: 1px solid var(--fb-gray-30);
+ padding: 16px 24px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.fileUploaderContainer {
+ max-width: 250px;
+}
diff --git a/ai/ai-react-app/src/views/ChatView.tsx b/ai/ai-react-app/src/views/ChatView.tsx
new file mode 100644
index 000000000..01db1cbb0
--- /dev/null
+++ b/ai/ai-react-app/src/views/ChatView.tsx
@@ -0,0 +1,531 @@
+import React, { useState, useEffect, useRef, useCallback } from "react";
+import { handleFunctionExecution } from "../services/firebaseAIService";
+import {
+ getGenerativeModel,
+ Content,
+ Part,
+ UsageMetadata,
+ FunctionResponsePart,
+ ChatSession,
+ GenerateContentCandidate,
+ ModelParams,
+ FunctionCall,
+ AIError,
+ AI,
+} from "firebase/ai";
+import PromptInput from "../components/Common/PromptInput";
+import ChatMessage from "../components/Specific/ChatMessage";
+import FileUploader from "../components/Common/FileUploader";
+import { fileToGenerativePart } from "../utils/fileUtils";
+import styles from "./ChatView.module.css";
+import { AppMode } from "../App";
+
+interface ChatViewProps {
+ aiInstance: AI;
+ onUsageMetadataChange: (metadata: UsageMetadata | null) => void;
+ currentParams: ModelParams;
+ activeMode: AppMode;
+}
+
+const modelParamsChanged = (
+ newParams: ModelParams,
+ oldParams: ModelParams | null,
+): boolean => {
+ if (!oldParams) return true; // No old params means change
+
+ // Compare critical fields that define a session's core behavior
+ if (newParams.model !== oldParams.model) return true;
+ if (
+ JSON.stringify(newParams.systemInstruction) !==
+ JSON.stringify(oldParams.systemInstruction)
+ )
+ return true;
+ if (JSON.stringify(newParams.tools) !== JSON.stringify(oldParams.tools))
+ return true;
+ if (
+ JSON.stringify(newParams.toolConfig) !==
+ JSON.stringify(oldParams.toolConfig)
+ )
+ return true;
+ if (
+ JSON.stringify(newParams.generationConfig) !==
+ JSON.stringify(oldParams.generationConfig)
+ )
+ return true;
+ if (
+ JSON.stringify(newParams.safetySettings) !==
+ JSON.stringify(oldParams.safetySettings)
+ )
+ return true;
+
+ return false; // If none of the above changed, params are not significantly different
+};
+
+const ChatView: React.FC = ({
+ onUsageMetadataChange,
+ currentParams,
+ aiInstance,
+ activeMode,
+}) => {
+ const [chatHistory, setChatHistory] = useState([]);
+ const [currentInput, setCurrentInput] = useState("");
+ const [selectedFile, setSelectedFile] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [lastResponseParsedJson, setLastResponseParsedJson] = useState<
+ object | null
+ >(null);
+
+ const chatContainerRef = useRef(null);
+ const chatSessionRef = useRef(null);
+ const sessionParamsRef = useRef(null);
+
+ const initializeChatSession = useCallback(
+ (params: ModelParams) => {
+ try {
+ console.log(
+ "[ChatView] Initializing new chat session with params:",
+ params,
+ );
+ const model = getGenerativeModel(aiInstance, params);
+ const newSession = model.startChat({
+ history: [],
+ safetySettings: params.safetySettings,
+ generationConfig: params.generationConfig,
+ tools: params.tools,
+ toolConfig: params.toolConfig,
+ systemInstruction: params.systemInstruction,
+ });
+ chatSessionRef.current = newSession;
+ // Store a deep copy of the params used for this session
+ sessionParamsRef.current = JSON.parse(JSON.stringify(params));
+ setChatHistory([]);
+ setError(null);
+ onUsageMetadataChange(null);
+ console.log("[ChatView] New chat session initialized successfully.");
+ } catch (initError: unknown) {
+ console.error("[ChatView] Error initializing chat session:", initError);
+ setError(`Failed to initialize chat session`);
+ chatSessionRef.current = null;
+ sessionParamsRef.current = null;
+ }
+ },
+ [onUsageMetadataChange, aiInstance],
+ );
+
+ useEffect(() => {
+ if (aiInstance) {
+ initializeChatSession(currentParams);
+ }
+ }, [aiInstance, initializeChatSession, currentParams]);
+
+ // Effect to scroll chat history
+ useEffect(() => {
+ if (chatContainerRef.current) {
+ chatContainerRef.current.scrollTop =
+ chatContainerRef.current.scrollHeight;
+ }
+ }, [chatHistory]);
+
+ const suggestions = [
+ "Explain the difference between `let`, `const`, and `var` in JavaScript.",
+ "Write a short story about a friendly robot.",
+ "Describe this image.",
+ "What's the weather in London in Celsius?",
+ ];
+ const handleSuggestion = (suggestion: string) => {
+ setCurrentInput(suggestion);
+ };
+
+ const processChatTurn = useCallback(
+ async (
+ partsToSend: Part[],
+ session: ChatSession,
+ expectStructuredOutput: boolean,
+ ) => {
+ console.log(
+ "[ChatView] Processing chat turn. Expect JSON:",
+ expectStructuredOutput,
+ "Parts:",
+ partsToSend,
+ );
+ setChatHistory((prev) => [...prev, { role: "model", parts: [] }]); // Add model placeholder
+ let accumulatedParts: Part[] = [];
+ let finalModelCandidate: GenerateContentCandidate | undefined;
+
+ try {
+ const streamResult = await session.sendMessageStream(partsToSend);
+ for await (const chunk of streamResult.stream) {
+ if (chunk.candidates?.[0]?.content?.parts) {
+ accumulatedParts = [
+ ...accumulatedParts,
+ ...chunk.candidates[0].content.parts,
+ ];
+ setChatHistory((prev) => {
+ const newHistory = [...prev];
+ if (
+ newHistory.length > 0 &&
+ newHistory[newHistory.length - 1].role === "model"
+ ) {
+ newHistory[newHistory.length - 1].parts = accumulatedParts;
+ }
+ return newHistory;
+ });
+ if (chunk.usageMetadata) onUsageMetadataChange(chunk.usageMetadata);
+ }
+ }
+
+ const finalResponse = await streamResult.response;
+ const finalResponseText = finalResponse.text
+ ? finalResponse.text()
+ : "";
+ finalModelCandidate = finalResponse.candidates?.[0];
+
+ if (!finalModelCandidate) {
+ console.warn("[ChatView] No candidate in final response.");
+ setError("Model did not return a valid response.");
+ setChatHistory((prev) => prev.slice(0, -1)); // Remove model placeholder
+ setIsLoading(false);
+ return;
+ }
+
+ // Update history definitively
+ setChatHistory((prev) => {
+ const newHistory = [...prev];
+ if (
+ newHistory.length > 0 &&
+ newHistory[newHistory.length - 1].role === "model"
+ ) {
+ newHistory[newHistory.length - 1].parts =
+ finalModelCandidate?.content?.parts ?? [];
+ }
+ return newHistory;
+ });
+
+ const functionCalls = finalModelCandidate.content?.parts
+ .filter((part) => !!part.functionCall)
+ .map((part) => part.functionCall as FunctionCall);
+
+ if (functionCalls && functionCalls.length > 0) {
+ console.log("[ChatView] Function call(s) requested:", functionCalls);
+ setLastResponseParsedJson(null); // Clear JSON display
+
+ const functionResponses: FunctionResponsePart[] = [];
+ for (const call of functionCalls) {
+ if (!call) continue;
+ try {
+ const apiResult = await handleFunctionExecution(call);
+ functionResponses.push({
+ functionResponse: { name: call.name, response: apiResult },
+ });
+ } catch (execError: unknown) {
+ console.error(
+ `[ChatView] Error executing function ${call.name}:`,
+ execError,
+ );
+ functionResponses.push({
+ functionResponse: {
+ name: call.name,
+ response: { error: `Execution failed` },
+ },
+ });
+ setError(`Error executing function ${call.name}.`);
+ }
+ }
+
+ setChatHistory((prev) => [
+ ...prev,
+ { role: "function", parts: functionResponses },
+ ]);
+ console.log(
+ "[ChatView] Sending function responses back:",
+ functionResponses,
+ );
+
+ // Add model placeholder for the NEXT response
+ setChatHistory((prev) => [...prev, { role: "model", parts: [] }]);
+ accumulatedParts = [];
+
+ // Send function responses and process the stream
+ const funcResponseResult =
+ await session.sendMessageStream(functionResponses);
+ for await (const chunk of funcResponseResult.stream) {
+ if (chunk.candidates?.[0]?.content?.parts) {
+ accumulatedParts = [
+ ...accumulatedParts,
+ ...chunk.candidates[0].content.parts,
+ ];
+ setChatHistory((prev) => {
+ const newHistory = [...prev];
+ if (
+ newHistory.length > 0 &&
+ newHistory[newHistory.length - 1].role === "model"
+ ) {
+ newHistory[newHistory.length - 1].parts = accumulatedParts;
+ }
+ return newHistory;
+ });
+ if (chunk.usageMetadata)
+ onUsageMetadataChange(chunk.usageMetadata);
+ }
+ }
+
+ const finalFuncResponse = await funcResponseResult.response;
+ const finalFuncResponseText = finalFuncResponse.text
+ ? finalFuncResponse.text()
+ : "";
+ const finalFuncCandidate = finalFuncResponse.candidates?.[0];
+
+ setChatHistory((prev) => {
+ const newHistory = [...prev];
+ if (
+ newHistory.length > 0 &&
+ newHistory[newHistory.length - 1].role === "model"
+ ) {
+ newHistory[newHistory.length - 1].parts =
+ finalFuncCandidate?.content?.parts ?? [];
+ }
+ return newHistory;
+ });
+
+ // Check for structured output in the final response after function calls
+ if (expectStructuredOutput && finalFuncResponseText) {
+ try {
+ const parsedJson = JSON.parse(finalFuncResponseText);
+ setLastResponseParsedJson(parsedJson);
+ } catch (parseError) {
+ console.error(
+ "[ChatView] Failed to parse JSON after function call:",
+ parseError,
+ );
+ setError(
+ "Received non-JSON response when JSON was expected after function call.",
+ );
+ setLastResponseParsedJson({
+ error: "Failed to parse final response as JSON",
+ rawText: finalFuncResponseText,
+ });
+ }
+ } else {
+ setLastResponseParsedJson(null);
+ }
+ } else {
+ if (expectStructuredOutput && finalResponseText) {
+ try {
+ const parsedJson = JSON.parse(finalResponseText);
+ setLastResponseParsedJson(parsedJson);
+ } catch (parseError) {
+ console.error(
+ "[ChatView] Failed to parse JSON response:",
+ parseError,
+ );
+ setError("Received non-JSON response when JSON was expected.");
+ setLastResponseParsedJson({
+ error: "Failed to parse response as JSON",
+ rawText: finalResponseText,
+ });
+ }
+ } else {
+ setLastResponseParsedJson(null);
+ }
+ }
+ } catch (err: unknown) {
+ console.error("[ChatView] Error during chat turn processing:", err);
+ // Check if it's an AIError and potentially handle specific codes
+ if (err instanceof AIError) {
+ setError(`AI Error (${err.code}): ${err.message}`);
+ } else {
+ setError(`Error`);
+ }
+ // Attempt to remove model placeholder if it was the last thing added on error
+ setChatHistory((prev) => {
+ if (
+ prev.length > 0 &&
+ prev[prev.length - 1].role === "model" &&
+ prev[prev.length - 1].parts.length === 0
+ ) {
+ return prev.slice(0, -1);
+ }
+ return prev;
+ });
+ onUsageMetadataChange(null);
+ } finally {
+ setIsLoading(false);
+ }
+ },
+ [onUsageMetadataChange],
+ );
+
+ const handleChatSubmit = useCallback(async () => {
+ if (isLoading || (!currentInput.trim() && !selectedFile)) {
+ return;
+ }
+
+ setIsLoading(true);
+ setError(null);
+ setLastResponseParsedJson(null);
+ onUsageMetadataChange(null);
+
+ const userMessageParts: Part[] = [];
+ if (currentInput.trim())
+ userMessageParts.push({ text: currentInput.trim() });
+ if (selectedFile) {
+ try {
+ userMessageParts.push(await fileToGenerativePart(selectedFile));
+ } catch (imgErr: unknown) {
+ if (imgErr instanceof Error) {
+ setError(`Failed to process file: ${imgErr.message}`);
+ } else {
+ setError(`Failed to process file`);
+ }
+ setIsLoading(false);
+ setSelectedFile(null);
+ return;
+ }
+ }
+ if (userMessageParts.length === 0) {
+ setIsLoading(false);
+ return;
+ }
+
+ const needsNewSession = modelParamsChanged(
+ currentParams,
+ sessionParamsRef.current,
+ );
+ let sessionToUse: ChatSession;
+
+ const currentHistoryForSession = [...chatHistory];
+
+ if (needsNewSession) {
+ try {
+ console.log(
+ "[ChatView] Significant parameters changed or no session exists. Initializing new session *with existing history*.",
+ );
+ const model = getGenerativeModel(aiInstance, currentParams);
+ sessionToUse = model.startChat({
+ history: currentHistoryForSession,
+ safetySettings: currentParams.safetySettings,
+ generationConfig: currentParams.generationConfig,
+ tools: currentParams.tools,
+ toolConfig: currentParams.toolConfig,
+ systemInstruction: currentParams.systemInstruction,
+ });
+ chatSessionRef.current = sessionToUse;
+ sessionParamsRef.current = JSON.parse(JSON.stringify(currentParams));
+ console.log("[ChatView] New session started with preserved history.");
+ } catch (sessionError: unknown) {
+ console.error(
+ "[ChatView] Error creating new chat session:",
+ sessionError,
+ );
+ setError(`Failed to start new chat session`);
+ setIsLoading(false);
+ return;
+ }
+ } else {
+ console.log("[ChatView] Reusing existing chat session.");
+ if (!chatSessionRef.current) {
+ console.error(
+ "[ChatView] Session ref is null unexpectedly. Attempting recovery.",
+ );
+ try {
+ const model = getGenerativeModel(aiInstance, currentParams);
+ sessionToUse = model.startChat({ history: currentHistoryForSession });
+ chatSessionRef.current = sessionToUse;
+ sessionParamsRef.current = JSON.parse(JSON.stringify(currentParams));
+ } catch (err: unknown) {
+ console.error("[ChatView] Error recovering chat session:", err);
+ setError(`Failed to recover chat session`);
+ setIsLoading(false);
+ return;
+ }
+ } else {
+ sessionToUse = chatSessionRef.current;
+ }
+ }
+
+ const userMessage: Content = { role: "user", parts: userMessageParts };
+ setChatHistory((prev) => [...prev, userMessage]);
+ setCurrentInput("");
+ setSelectedFile(null);
+
+ const expectStructuredOutput =
+ currentParams.generationConfig?.responseMimeType === "application/json";
+ await processChatTurn(
+ userMessageParts,
+ sessionToUse,
+ expectStructuredOutput,
+ );
+ }, [
+ isLoading,
+ currentInput,
+ selectedFile,
+ currentParams,
+ chatHistory,
+ aiInstance,
+ processChatTurn,
+ onUsageMetadataChange,
+ ]);
+
+ return (
+
+ {/* Chat History Area */}
+
+ {chatHistory.length === 0 && !isLoading && (
+
+ {`Start chatting with ${currentParams.model}!`}
+
+ )}
+ {chatHistory.map((message, index) => (
+
+ ))}
+ {isLoading && (
+
+ .
+ .
+ .
+
+ )}
+ {error &&
{error}
}
+ {lastResponseParsedJson && !isLoading && (
+
+
+ View Last Response JSON
+
+ {JSON.stringify(lastResponseParsedJson, null, 2)}
+
+ )}
+
+
+ {/* Input Area Wrapper */}
+
+
+ );
+};
+
+export default ChatView;
diff --git a/ai/ai-react-app/src/views/ImagenView.module.css b/ai/ai-react-app/src/views/ImagenView.module.css
new file mode 100644
index 000000000..d6e878620
--- /dev/null
+++ b/ai/ai-react-app/src/views/ImagenView.module.css
@@ -0,0 +1,105 @@
+.imagenViewContainer {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ background-color: var(--fb-gray-10);
+}
+
+.errorMessage {
+ color: var(--fb-error-text);
+ margin: 10px 24px;
+ padding: 10px 16px;
+ background-color: var(--fb-error-bg);
+ border: 1px solid var(--fb-error-border);
+ border-radius: 4px;
+ white-space: pre-wrap;
+ font-size: 0.875rem;
+ flex-shrink: 0;
+}
+
+.displayArea {
+ flex-grow: 1;
+ overflow-y: auto;
+ padding: 24px;
+ display: flex;
+ flex-direction: column;
+}
+
+.inputAreaContainer {
+ flex-shrink: 0;
+
+ & > div {
+ margin: 0;
+ border-radius: 0;
+ border-left: none;
+ border-right: none;
+ border-bottom: none;
+ box-shadow: none;
+ border-top: 1px solid var(--fb-gray-30);
+ background-color: var(--fb-gray-10);
+ }
+}
+
+.imageDisplayContainer {
+ padding: 0;
+ border-radius: 4px;
+ background-color: transparent;
+ min-height: 150px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ color: var(--fb-gray-90);
+ width: 100%;
+ flex-grow: 1;
+}
+
+.loading,
+.placeholder {
+ font-style: italic;
+ color: var(--fb-gray-60);
+ margin: 20px 0;
+}
+
+.filteredReason {
+ color: var(--fb-warning-text);
+ background-color: var(--fb-warning-bg);
+ border: 1px solid var(--fb-warning-border);
+ padding: 8px 16px;
+ border-radius: 4px;
+ margin-bottom: 16px;
+ font-size: 0.875rem;
+ text-align: center;
+ width: auto;
+ max-width: 90%;
+ flex-shrink: 0;
+}
+
+.imageList {
+ display: grid;
+
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 16px;
+ justify-content: center;
+ margin-top: 16px;
+ width: 100%;
+}
+
+.imageWrapper {
+ border: 1px solid var(--fb-gray-30);
+ padding: 4px;
+ background-color: var(--fb-gray-10);
+ border-radius: 4px;
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.generatedImage {
+ max-width: 100%;
+ max-height: 300px;
+ height: auto;
+ display: block;
+ border-radius: 2px;
+}
diff --git a/ai/ai-react-app/src/views/ImagenView.tsx b/ai/ai-react-app/src/views/ImagenView.tsx
new file mode 100644
index 000000000..46845ab32
--- /dev/null
+++ b/ai/ai-react-app/src/views/ImagenView.tsx
@@ -0,0 +1,147 @@
+import React, { useState, useCallback } from "react";
+import {
+ getImagenModel,
+ ImagenModelParams,
+ ImagenInlineImage,
+ ImagenGenerationResponse,
+ AI,
+ AIError,
+} from "firebase/ai";
+import PromptInput from "../components/Common/PromptInput";
+import styles from "./ImagenView.module.css";
+
+// Simple component to display generated images or placeholders/loading states.
+const ImageDisplay: React.FC<{
+ images: ImagenInlineImage[];
+ filteredReason?: string;
+ isLoading: boolean;
+}> = ({ images, filteredReason, isLoading }) => {
+ return (
+
+ {isLoading &&
Generating images...
}
+ {!isLoading && images.length === 0 && !filteredReason && (
+
+ Generated images will appear here.
+
+ )}
+ {filteredReason && (
+
Filtered: {filteredReason}
+ )}
+
+ {images.map((img, index) => (
+
+
+
+ ))}
+
+
+ );
+};
+
+interface ImagenViewProps {
+ currentParams: ImagenModelParams;
+ aiInstance: AI;
+}
+
+/**
+ * View component for interacting with the Imagen model to generate images.
+ */
+const ImagenView: React.FC = ({
+ currentParams,
+ aiInstance,
+}) => {
+ const [currentPrompt, setCurrentPrompt] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
+ const [resultImages, setResultImages] = useState([]);
+ const [filteredReason, setFilteredReason] = useState(
+ undefined,
+ );
+ const [error, setError] = useState(null);
+
+ const handleImageGenerationSubmit = useCallback(async () => {
+ if (!currentPrompt.trim() || isLoading) {
+ return;
+ }
+
+ const promptText = currentPrompt.trim();
+ console.log(
+ `[ImagenView] Starting generation for prompt: "${promptText}" with params:`,
+ currentParams,
+ );
+ setIsLoading(true);
+ setResultImages([]); // Clear previous results
+ setFilteredReason(undefined);
+ setError(null);
+
+ try {
+ const imagenModel = getImagenModel(aiInstance, currentParams);
+ console.log(`[ImagenView] Using Imagen model: ${imagenModel.model}`);
+
+ const result: ImagenGenerationResponse =
+ await imagenModel.generateImages(promptText);
+
+ console.log("[ImagenView] Image generation successful.", result);
+ setResultImages(result.images);
+ setFilteredReason(result.filteredReason);
+ } catch (err: unknown) {
+ console.error("[ImagenView] Error during image generation:", err);
+ if (err instanceof AIError) {
+ const message =
+ err.message || "Failed to generate images due to an unknown error.";
+ const details = err.customErrorData?.errorDetails
+ ? ` Details: ${JSON.stringify(err.customErrorData.errorDetails)}`
+ : "";
+ setError(`Error: ${message}${details}`);
+ } else {
+ setError("Failed to generate images due to an unknown error.");
+ }
+ setResultImages([]);
+ } finally {
+ setIsLoading(false);
+ setCurrentPrompt("");
+ }
+ }, [currentPrompt, isLoading, currentParams, aiInstance]);
+
+ const suggestions = [
+ "A photorealistic portrait of a tabby cat wearing sunglasses.",
+ "Impressionist painting of a sunflower field at sunset.",
+ "Logo for a coffee shop called 'The Daily Grind', minimalist style.",
+ "Pixel art sprite of a friendly robot navigating a maze.",
+ ];
+ const handleSuggestion = (suggestion: string) => {
+ setCurrentPrompt(suggestion);
+ };
+
+ return (
+
+ {error &&
{error}
}
+
+
+
+
+
+ );
+};
+
+export default ImagenView;
diff --git a/ai/ai-react-app/src/vite-env.d.ts b/ai/ai-react-app/src/vite-env.d.ts
new file mode 100644
index 000000000..11f02fe2a
--- /dev/null
+++ b/ai/ai-react-app/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/ai/ai-react-app/tsconfig.app.json b/ai/ai-react-app/tsconfig.app.json
new file mode 100644
index 000000000..358ca9ba9
--- /dev/null
+++ b/ai/ai-react-app/tsconfig.app.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/ai/ai-react-app/tsconfig.json b/ai/ai-react-app/tsconfig.json
new file mode 100644
index 000000000..1ffef600d
--- /dev/null
+++ b/ai/ai-react-app/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/ai/ai-react-app/tsconfig.node.json b/ai/ai-react-app/tsconfig.node.json
new file mode 100644
index 000000000..db0becc8b
--- /dev/null
+++ b/ai/ai-react-app/tsconfig.node.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/ai/ai-react-app/vite.config.ts b/ai/ai-react-app/vite.config.ts
new file mode 100644
index 000000000..081c8d9f6
--- /dev/null
+++ b/ai/ai-react-app/vite.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+});
diff --git a/ai/ai-react-app/yarn.lock b/ai/ai-react-app/yarn.lock
new file mode 100644
index 000000000..9cbb62e4c
--- /dev/null
+++ b/ai/ai-react-app/yarn.lock
@@ -0,0 +1,2206 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@ampproject/remapping@^2.2.0":
+ version "2.3.0"
+ resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz"
+ integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@babel/code-frame@^7.26.2":
+ version "7.26.2"
+ resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz"
+ integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.25.9"
+ js-tokens "^4.0.0"
+ picocolors "^1.0.0"
+
+"@babel/compat-data@^7.26.8":
+ version "7.26.8"
+ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz"
+ integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==
+
+"@babel/core@^7.26.10":
+ version "7.26.10"
+ resolved "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz"
+ integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.26.2"
+ "@babel/generator" "^7.26.10"
+ "@babel/helper-compilation-targets" "^7.26.5"
+ "@babel/helper-module-transforms" "^7.26.0"
+ "@babel/helpers" "^7.26.10"
+ "@babel/parser" "^7.26.10"
+ "@babel/template" "^7.26.9"
+ "@babel/traverse" "^7.26.10"
+ "@babel/types" "^7.26.10"
+ convert-source-map "^2.0.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.3"
+ semver "^6.3.1"
+
+"@babel/generator@^7.26.10", "@babel/generator@^7.27.0":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz"
+ integrity sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==
+ dependencies:
+ "@babel/parser" "^7.27.0"
+ "@babel/types" "^7.27.0"
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.25"
+ jsesc "^3.0.2"
+
+"@babel/helper-compilation-targets@^7.26.5":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz"
+ integrity sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==
+ dependencies:
+ "@babel/compat-data" "^7.26.8"
+ "@babel/helper-validator-option" "^7.25.9"
+ browserslist "^4.24.0"
+ lru-cache "^5.1.1"
+ semver "^6.3.1"
+
+"@babel/helper-module-imports@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz"
+ integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==
+ dependencies:
+ "@babel/traverse" "^7.25.9"
+ "@babel/types" "^7.25.9"
+
+"@babel/helper-module-transforms@^7.26.0":
+ version "7.26.0"
+ resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz"
+ integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==
+ dependencies:
+ "@babel/helper-module-imports" "^7.25.9"
+ "@babel/helper-validator-identifier" "^7.25.9"
+ "@babel/traverse" "^7.25.9"
+
+"@babel/helper-plugin-utils@^7.25.9":
+ version "7.26.5"
+ resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz"
+ integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==
+
+"@babel/helper-string-parser@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz"
+ integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==
+
+"@babel/helper-validator-identifier@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz"
+ integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==
+
+"@babel/helper-validator-option@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz"
+ integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
+
+"@babel/helpers@^7.26.10":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz"
+ integrity sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==
+ dependencies:
+ "@babel/template" "^7.27.0"
+ "@babel/types" "^7.27.0"
+
+"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.26.10", "@babel/parser@^7.27.0":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz"
+ integrity sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==
+ dependencies:
+ "@babel/types" "^7.27.0"
+
+"@babel/plugin-transform-react-jsx-self@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz"
+ integrity sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/plugin-transform-react-jsx-source@^7.25.9":
+ version "7.25.9"
+ resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz"
+ integrity sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.25.9"
+
+"@babel/template@^7.26.9", "@babel/template@^7.27.0":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz"
+ integrity sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==
+ dependencies:
+ "@babel/code-frame" "^7.26.2"
+ "@babel/parser" "^7.27.0"
+ "@babel/types" "^7.27.0"
+
+"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.10":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz"
+ integrity sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==
+ dependencies:
+ "@babel/code-frame" "^7.26.2"
+ "@babel/generator" "^7.27.0"
+ "@babel/parser" "^7.27.0"
+ "@babel/template" "^7.27.0"
+ "@babel/types" "^7.27.0"
+ debug "^4.3.1"
+ globals "^11.1.0"
+
+"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.25.9", "@babel/types@^7.26.10", "@babel/types@^7.27.0":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz"
+ integrity sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.25.9"
+ "@babel/helper-validator-identifier" "^7.25.9"
+
+"@esbuild/aix-ppc64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz#014180d9a149cffd95aaeead37179433f5ea5437"
+ integrity sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==
+
+"@esbuild/android-arm64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz#649e47e04ddb24a27dc05c395724bc5f4c55cbfe"
+ integrity sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==
+
+"@esbuild/android-arm@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.3.tgz#8a0f719c8dc28a4a6567ef7328c36ea85f568ff4"
+ integrity sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==
+
+"@esbuild/android-x64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.3.tgz#e2ab182d1fd06da9bef0784a13c28a7602d78009"
+ integrity sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==
+
+"@esbuild/darwin-arm64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz"
+ integrity sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==
+
+"@esbuild/darwin-x64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz#d8c5342ec1a4bf4b1915643dfe031ba4b173a87a"
+ integrity sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==
+
+"@esbuild/freebsd-arm64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz#9f7d789e2eb7747d4868817417cc968ffa84f35b"
+ integrity sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==
+
+"@esbuild/freebsd-x64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz#8ad35c51d084184a8e9e76bb4356e95350a64709"
+ integrity sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==
+
+"@esbuild/linux-arm64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz#3af0da3d9186092a9edd4e28fa342f57d9e3cd30"
+ integrity sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==
+
+"@esbuild/linux-arm@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz#e91cafa95e4474b3ae3d54da12e006b782e57225"
+ integrity sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==
+
+"@esbuild/linux-ia32@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz#81025732d85b68ee510161b94acdf7e3007ea177"
+ integrity sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==
+
+"@esbuild/linux-loong64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz#3c744e4c8d5e1148cbe60a71a11b58ed8ee5deb8"
+ integrity sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==
+
+"@esbuild/linux-mips64el@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz#1dfe2a5d63702db9034cc6b10b3087cc0424ec26"
+ integrity sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==
+
+"@esbuild/linux-ppc64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz#2e85d9764c04a1ebb346dc0813ea05952c9a5c56"
+ integrity sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==
+
+"@esbuild/linux-riscv64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz#a9ea3334556b09f85ccbfead58c803d305092415"
+ integrity sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==
+
+"@esbuild/linux-s390x@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz#f6a7cb67969222b200974de58f105dfe8e99448d"
+ integrity sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==
+
+"@esbuild/linux-x64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz#a237d3578ecdd184a3066b1f425e314ade0f8033"
+ integrity sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==
+
+"@esbuild/netbsd-arm64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz#4c15c68d8149614ddb6a56f9c85ae62ccca08259"
+ integrity sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==
+
+"@esbuild/netbsd-x64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz#12f6856f8c54c2d7d0a8a64a9711c01a743878d5"
+ integrity sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==
+
+"@esbuild/openbsd-arm64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz#ca078dad4a34df192c60233b058db2ca3d94bc5c"
+ integrity sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==
+
+"@esbuild/openbsd-x64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz#c9178adb60e140e03a881d0791248489c79f95b2"
+ integrity sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==
+
+"@esbuild/sunos-x64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz#03765eb6d4214ff27e5230af779e80790d1ee09f"
+ integrity sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==
+
+"@esbuild/win32-arm64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz#f1c867bd1730a9b8dfc461785ec6462e349411ea"
+ integrity sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==
+
+"@esbuild/win32-ia32@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz#77491f59ef6c9ddf41df70670d5678beb3acc322"
+ integrity sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==
+
+"@esbuild/win32-x64@0.25.3":
+ version "0.25.3"
+ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz#b17a2171f9074df9e91bfb07ef99a892ac06412a"
+ integrity sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==
+
+"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
+ version "4.6.1"
+ resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz"
+ integrity sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==
+ dependencies:
+ eslint-visitor-keys "^3.4.3"
+
+"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.12.1":
+ version "4.12.1"
+ resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz"
+ integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
+
+"@eslint/config-array@^0.20.0":
+ version "0.20.0"
+ resolved "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz"
+ integrity sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==
+ dependencies:
+ "@eslint/object-schema" "^2.1.6"
+ debug "^4.3.1"
+ minimatch "^3.1.2"
+
+"@eslint/config-helpers@^0.2.1":
+ version "0.2.1"
+ resolved "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz"
+ integrity sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==
+
+"@eslint/core@^0.13.0":
+ version "0.13.0"
+ resolved "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz"
+ integrity sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==
+ dependencies:
+ "@types/json-schema" "^7.0.15"
+
+"@eslint/eslintrc@^3.3.1":
+ version "3.3.1"
+ resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz"
+ integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==
+ dependencies:
+ ajv "^6.12.4"
+ debug "^4.3.2"
+ espree "^10.0.1"
+ globals "^14.0.0"
+ ignore "^5.2.0"
+ import-fresh "^3.2.1"
+ js-yaml "^4.1.0"
+ minimatch "^3.1.2"
+ strip-json-comments "^3.1.1"
+
+"@eslint/js@9.25.1", "@eslint/js@^9.22.0":
+ version "9.25.1"
+ resolved "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz"
+ integrity sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==
+
+"@eslint/object-schema@^2.1.6":
+ version "2.1.6"
+ resolved "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz"
+ integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==
+
+"@eslint/plugin-kit@^0.2.8":
+ version "0.2.8"
+ resolved "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz"
+ integrity sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==
+ dependencies:
+ "@eslint/core" "^0.13.0"
+ levn "^0.4.1"
+
+"@firebase/analytics-compat@0.2.18":
+ version "0.2.18"
+ resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.18.tgz#5ea9bdea188d4c91938f539af4c4414813ddc5c9"
+ integrity sha512-Hw9mzsSMZaQu6wrTbi3kYYwGw9nBqOHr47pVLxfr5v8CalsdrG5gfs9XUlPOZjHRVISp3oQrh1j7d3E+ulHPjQ==
+ dependencies:
+ "@firebase/analytics" "0.10.12"
+ "@firebase/analytics-types" "0.8.3"
+ "@firebase/component" "0.6.13"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/analytics-types@0.8.3":
+ version "0.8.3"
+ resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.8.3.tgz#d08cd39a6209693ca2039ba7a81570dfa6c1518f"
+ integrity sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==
+
+"@firebase/analytics@0.10.12":
+ version "0.10.12"
+ resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.12.tgz#b49b9351b8cb19da007320a52932b953fff90d71"
+ integrity sha512-iDCGnw6qdFqwI5ywkgece99WADJNoymu+nLIQI4fZM/vCZ3bEo4wlpEetW71s1HqGpI0hQStiPhqVjFxDb2yyw==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/installations" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/app-check-compat@0.3.20":
+ version "0.3.20"
+ resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.3.20.tgz#0dfce42699402f3b621be98857a8109b9397a37b"
+ integrity sha512-/twgmlnNAaZ/wbz3kcQrL/26b+X+zUX+lBmu5LwwEcWcpnb+mrVEAKhD7/ttm52dxYiSWtLDeuXy3FXBhqBC5A==
+ dependencies:
+ "@firebase/app-check" "0.8.13"
+ "@firebase/app-check-types" "0.5.3"
+ "@firebase/component" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/app-check-interop-types@0.3.3":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz#ed9c4a4f48d1395ef378f007476db3940aa5351a"
+ integrity sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==
+
+"@firebase/app-check-types@0.5.3":
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.5.3.tgz#38ba954acf4bffe451581a32fffa20337f11d8e5"
+ integrity sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==
+
+"@firebase/app-check@0.8.13":
+ version "0.8.13"
+ resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.8.13.tgz#20b212d0ea5b79c9492f434abc276d4f28b19371"
+ integrity sha512-ONsgml8/dplUOAP42JQO6hhiWDEwR9+RUTLenxAN9S8N6gel/sDQ9Ci721Py1oASMGdDU8v9R7xAZxzvOX5lPg==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/app-compat@0.2.53":
+ version "0.2.53"
+ resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.2.53.tgz#ad5c520a2ea6df4cff0a8b4d5cc14c03c0d7bc57"
+ integrity sha512-vDeZSit0q4NyaDIVcaiJF3zhLgguP6yc0JwQAfpTyllgt8XMtkMFyY/MxJtFrK2ocpQX/yCbV2DXwvpY2NVuJw==
+ dependencies:
+ "@firebase/app" "0.11.4"
+ "@firebase/component" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/app-types@0.9.3":
+ version "0.9.3"
+ resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.9.3.tgz#8408219eae9b1fb74f86c24e7150a148460414ad"
+ integrity sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==
+
+"@firebase/app@0.11.4":
+ version "0.11.4"
+ resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.11.4.tgz#93f2637ed5b8dbc1ddf879c727d66a00c656c959"
+ integrity sha512-GPREsZjfSaHzwyC6cI/Cqvzf6zxqMzya+25tSpUstdqC2w0IdfxEfOMjfdW7bDfVEf4Rb4Nb6gfoOAgVSp4c4g==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.11.0"
+ idb "7.1.1"
+ tslib "^2.1.0"
+
+"@firebase/auth-compat@0.5.20":
+ version "0.5.20"
+ resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.5.20.tgz#b17755e874f1f0fe5e2b638c462bbdfb519526f4"
+ integrity sha512-8FwODTSBnaqGQbKfML7LcpzGGPyouB7YHg3dZq+CZMziVc7oBY1jJeNvpnM1hAQoVuTjWPXoRrCltdGeOlkKfQ==
+ dependencies:
+ "@firebase/auth" "1.10.0"
+ "@firebase/auth-types" "0.13.0"
+ "@firebase/component" "0.6.13"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/auth-interop-types@0.2.4":
+ version "0.2.4"
+ resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz#176a08686b0685596ff03d7879b7e4115af53de0"
+ integrity sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==
+
+"@firebase/auth-types@0.13.0":
+ version "0.13.0"
+ resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.13.0.tgz#ae6e0015e3bd4bfe18edd0942b48a0a118a098d9"
+ integrity sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==
+
+"@firebase/auth@1.10.0":
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.10.0.tgz#eabfe747700a835a89c0069177ac58c40bfa6153"
+ integrity sha512-S7SqBsN7sIQsftNE3bitLlK+4bWrTHY+Rx2JFlNitgVYu2nK8W8ZQrkG8GCEwiFPq0B2vZ9pO5kVTFfq2sP96A==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/component@0.6.13":
+ version "0.6.13"
+ resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.6.13.tgz#6513379f1c09264133969d87282ce0d5bbbb2cd9"
+ integrity sha512-I/Eg1NpAtZ8AAfq8mpdfXnuUpcLxIDdCDtTzWSh+FXnp/9eCKJ3SNbOCKrUCyhLzNa2SiPJYruei0sxVjaOTeg==
+ dependencies:
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/data-connect@0.3.3":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@firebase/data-connect/-/data-connect-0.3.3.tgz#644e67c248ceccbed749b1eb10418079783c5542"
+ integrity sha512-JsgppNX1wcQYP5bg4Sg6WTS7S0XazklSjr1fG3ox9DHtt4LOQwJ3X1/c81mKMIZxocV22ujiwLYQWG6Y9D1FiQ==
+ dependencies:
+ "@firebase/auth-interop-types" "0.2.4"
+ "@firebase/component" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/database-compat@2.0.5":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-2.0.5.tgz#110f612901995f9800f2435f58686e0c6f3d2544"
+ integrity sha512-CNf1UbvWh6qIaSf4sn6sx2DTDz/em/D7QxULH1LTxxDQHr9+CeYGvlAqrKnk4ZH0P0eIHyQFQU7RwkUJI0B9gQ==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/database" "1.0.14"
+ "@firebase/database-types" "1.0.10"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/database-types@1.0.10":
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.10.tgz#14cfed45bb06394cf1641e19265cbf90e4f6fb51"
+ integrity sha512-mH6RC1E9/Pv8jf1/p+M8YFTX+iu+iHDN89hecvyO7wHrI4R1V0TXjxOHvX3nLJN1sfh0CWG6CHZ0VlrSmK/cwg==
+ dependencies:
+ "@firebase/app-types" "0.9.3"
+ "@firebase/util" "1.11.0"
+
+"@firebase/database@1.0.14":
+ version "1.0.14"
+ resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.0.14.tgz#1d579b345c0f926eaddb7703051999489300c3bd"
+ integrity sha512-9nxYtkHAG02/Nh2Ssms1T4BbWPPjiwohCvkHDUl4hNxnki1kPgsLo5xe9kXNzbacOStmVys+RUXvwzynQSKmUQ==
+ dependencies:
+ "@firebase/app-check-interop-types" "0.3.3"
+ "@firebase/auth-interop-types" "0.2.4"
+ "@firebase/component" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.11.0"
+ faye-websocket "0.11.4"
+ tslib "^2.1.0"
+
+"@firebase/firestore-compat@0.3.45":
+ version "0.3.45"
+ resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.3.45.tgz#93061f7d3644cd511749c9268d146e8c8c49a5de"
+ integrity sha512-uRvi7AYPmsDl7UZwPyV7jgDGYusEZ2+U2g7MndbQHKIA8fNHpYC6QrzMs58+/IjX+kF/lkUn67Vrr0AkVjlY+Q==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/firestore" "4.7.10"
+ "@firebase/firestore-types" "3.0.3"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/firestore-types@3.0.3":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-3.0.3.tgz#7d0c3dd8850c0193d8f5ee0cc8f11961407742c1"
+ integrity sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==
+
+"@firebase/firestore@4.7.10":
+ version "4.7.10"
+ resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.7.10.tgz#6fe0cd31fcd7f4a8e13f9585f53e9300cf3114c0"
+ integrity sha512-6nKsyo2U+jYSCcSE5sjMdDNA23DMUvYPUvsYGg09CNvcTO8GGKsPs7SpOhspsB91mbacq+u627CDAx3FUhPSSQ==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.11.0"
+ "@firebase/webchannel-wrapper" "1.0.3"
+ "@grpc/grpc-js" "~1.9.0"
+ "@grpc/proto-loader" "^0.7.8"
+ tslib "^2.1.0"
+
+"@firebase/functions-compat@0.3.20":
+ version "0.3.20"
+ resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.3.20.tgz#addf89242be8b4d63feacb15ef27785e16c5e220"
+ integrity sha512-iIudmYDAML6n3c7uXO2YTlzra2/J6lnMzmJTXNthvrKVMgNMaseNoQP1wKfchK84hMuSF8EkM4AvufwbJ+Juew==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/functions" "0.12.3"
+ "@firebase/functions-types" "0.6.3"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/functions-types@0.6.3":
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.6.3.tgz#f5faf770248b13f45d256f614230da6a11bfb654"
+ integrity sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==
+
+"@firebase/functions@0.12.3":
+ version "0.12.3"
+ resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.12.3.tgz#b3e395aed1641d0d7169a22698c684a2745e02dd"
+ integrity sha512-Wv7JZMUkKLb1goOWRtsu3t7m97uK6XQvjQLPvn8rncY91+VgdU72crqnaYCDI/ophNuBEmuK8mn0/pAnjUeA6A==
+ dependencies:
+ "@firebase/app-check-interop-types" "0.3.3"
+ "@firebase/auth-interop-types" "0.2.4"
+ "@firebase/component" "0.6.13"
+ "@firebase/messaging-interop-types" "0.2.3"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/installations-compat@0.2.13":
+ version "0.2.13"
+ resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.13.tgz#0db0867ed58b782f7e2d142d9289a9eef1da24d5"
+ integrity sha512-f/o6MqCI7LD/ulY9gvgkv6w5k6diaReD8BFHd/y/fEdpsXmFWYS/g28GXCB72bRVBOgPpkOUNl+VsMvDwlRKmw==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/installations" "0.6.13"
+ "@firebase/installations-types" "0.5.3"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/installations-types@0.5.3":
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.5.3.tgz#cac8a14dd49f09174da9df8ae453f9b359c3ef2f"
+ integrity sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==
+
+"@firebase/installations@0.6.13":
+ version "0.6.13"
+ resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.13.tgz#a2a00aebb5dfb74fae08600ea98cd2681211dd3c"
+ integrity sha512-6ZpkUiaygPFwgVneYxuuOuHnSPnTA4KefLEaw/sKk/rNYgC7X6twaGfYb0sYLpbi9xV4i5jXsqZ3WO+yaguNgg==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/util" "1.11.0"
+ idb "7.1.1"
+ tslib "^2.1.0"
+
+"@firebase/logger@0.4.4":
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.4.4.tgz#29e8379d20fd1149349a195ee6deee4573a86f48"
+ integrity sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==
+ dependencies:
+ tslib "^2.1.0"
+
+"@firebase/messaging-compat@0.2.17":
+ version "0.2.17"
+ resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.2.17.tgz#fc223495319fe3784347dea77094b6e03548647d"
+ integrity sha512-5Q+9IG7FuedusdWHVQRjpA3OVD9KUWp/IPegcv0s5qSqRLBjib7FlAeWxN+VL0Ew43tuPJBY2HKhEecuizmO1Q==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/messaging" "0.12.17"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/messaging-interop-types@0.2.3":
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz#e647c9cd1beecfe6a6e82018a6eec37555e4da3e"
+ integrity sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==
+
+"@firebase/messaging@0.12.17":
+ version "0.12.17"
+ resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.12.17.tgz#41eaeee70a89136715a4f7cb2b1a602423fc44ec"
+ integrity sha512-W3CnGhTm6Nx8XGb6E5/+jZTuxX/EK8Vur4QXvO1DwZta/t0xqWMRgO9vNsZFMYBqFV4o3j4F9qK/iddGYwWS6g==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/installations" "0.6.13"
+ "@firebase/messaging-interop-types" "0.2.3"
+ "@firebase/util" "1.11.0"
+ idb "7.1.1"
+ tslib "^2.1.0"
+
+"@firebase/performance-compat@0.2.15":
+ version "0.2.15"
+ resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.15.tgz#4e5034add63917cb6357938126e9e6562d0e7208"
+ integrity sha512-wUxsw7hGBEMN6XfvYQqwPIQp5LcJXawWM5tmYp6L7ClCoTQuEiCKHWWVurJgN8Q1YHzoHVgjNfPQAOVu29iMVg==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/performance" "0.7.2"
+ "@firebase/performance-types" "0.2.3"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/performance-types@0.2.3":
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.2.3.tgz#5ce64e90fa20ab5561f8b62a305010cf9fab86fb"
+ integrity sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==
+
+"@firebase/performance@0.7.2":
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.7.2.tgz#e30ee3e3c120c53f48bde7bdd915687f1e3b27e1"
+ integrity sha512-DXLLp0R0jdxH/yTmv+WTkOzsLl8YYecXh4lGZE0dzqC0IV8k+AxpLSSWvOTCkAETze8yEU/iF+PtgYVlGjfMMQ==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/installations" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+ web-vitals "^4.2.4"
+
+"@firebase/remote-config-compat@0.2.13":
+ version "0.2.13"
+ resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.13.tgz#4a35c2c6bb582d96aecc45da18f5094359cb5361"
+ integrity sha512-UmHoO7TxAEJPIZf8e1Hy6CeFGMeyjqSCpgoBkQZYXFI2JHhzxIyDpr8jVKJJN1dmAePKZ5EX7dC13CmcdTOl7Q==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/remote-config" "0.6.0"
+ "@firebase/remote-config-types" "0.4.0"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/remote-config-types@0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz#91b9a836d5ca30ced68c1516163b281fbb544537"
+ integrity sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg==
+
+"@firebase/remote-config@0.6.0":
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.6.0.tgz#24a6966a4d092260983ba8597cc039f5795cd35f"
+ integrity sha512-Yrk4l5+6FJLPHC6irNHMzgTtJ3NfHXlAXVChCBdNFtgmzyGmufNs/sr8oA0auEfIJ5VpXCaThRh3P4OdQxiAlQ==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/installations" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/storage-compat@0.3.17":
+ version "0.3.17"
+ resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.3.17.tgz#67f6bbc498971e6e404e0ea660fa5df81c5ba5ea"
+ integrity sha512-CBlODWEZ5b6MJWVh21VZioxwxNwVfPA9CAdsk+ZgVocJQQbE2oDW1XJoRcgthRY1HOitgbn4cVrM+NlQtuUYhw==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/storage" "0.13.7"
+ "@firebase/storage-types" "0.8.3"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/storage-types@0.8.3":
+ version "0.8.3"
+ resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.8.3.tgz#2531ef593a3452fc12c59117195d6485c6632d3d"
+ integrity sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==
+
+"@firebase/storage@0.13.7":
+ version "0.13.7"
+ resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.13.7.tgz#809fc23685ad9ba8fdfb3bc758e3353867c9e796"
+ integrity sha512-FkRyc24rK+Y6EaQ1tYFm3TevBnnfSNA0VyTfew2hrYyL/aYfatBg7HOgktUdB4kWMHNA9VoTotzZTGoLuK92wg==
+ dependencies:
+ "@firebase/component" "0.6.13"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/util@1.11.0":
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.11.0.tgz#e74ee2dc260ec4f9e75fe5d52bc4b0254d9872a9"
+ integrity sha512-PzSrhIr++KI6y4P6C/IdgBNMkEx0Ex6554/cYd0Hm+ovyFSJtJXqb/3OSIdnBoa2cpwZT1/GW56EmRc5qEc5fQ==
+ dependencies:
+ tslib "^2.1.0"
+
+"@firebase/vertexai@1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@firebase/vertexai/-/vertexai-1.2.1.tgz#6962b8b389f10b58033b8c700b27d0cfdc9ebd22"
+ integrity sha512-cukZ5ne2RsOWB4PB1EO6nTXgOLxPMKDJfEn+XnSV5ZKWM0ID5o0DvbyS59XihFaBzmy2SwJldP5ap7/xUnW4jA==
+ dependencies:
+ "@firebase/app-check-interop-types" "0.3.3"
+ "@firebase/component" "0.6.13"
+ "@firebase/logger" "0.4.4"
+ "@firebase/util" "1.11.0"
+ tslib "^2.1.0"
+
+"@firebase/webchannel-wrapper@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz#a73bab8eb491d7b8b7be2f0e6c310647835afe83"
+ integrity sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==
+
+"@grpc/grpc-js@~1.9.0":
+ version "1.9.15"
+ resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz"
+ integrity sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==
+ dependencies:
+ "@grpc/proto-loader" "^0.7.8"
+ "@types/node" ">=12.12.47"
+
+"@grpc/proto-loader@^0.7.8":
+ version "0.7.15"
+ resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz"
+ integrity sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==
+ dependencies:
+ lodash.camelcase "^4.3.0"
+ long "^5.0.0"
+ protobufjs "^7.2.5"
+ yargs "^17.7.2"
+
+"@humanfs/core@^0.19.1":
+ version "0.19.1"
+ resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz"
+ integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==
+
+"@humanfs/node@^0.16.6":
+ version "0.16.6"
+ resolved "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz"
+ integrity sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==
+ dependencies:
+ "@humanfs/core" "^0.19.1"
+ "@humanwhocodes/retry" "^0.3.0"
+
+"@humanwhocodes/module-importer@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz"
+ integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
+
+"@humanwhocodes/retry@^0.3.0":
+ version "0.3.1"
+ resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz"
+ integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==
+
+"@humanwhocodes/retry@^0.4.2":
+ version "0.4.2"
+ resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz"
+ integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==
+
+"@jridgewell/gen-mapping@^0.3.5":
+ version "0.3.8"
+ resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz"
+ integrity sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==
+ dependencies:
+ "@jridgewell/set-array" "^1.2.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz"
+ integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
+
+"@jridgewell/set-array@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz"
+ integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
+
+"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
+ version "1.5.0"
+ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz"
+ integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
+
+"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
+ version "0.3.25"
+ resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz"
+ integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
+"@nodelib/fs.scandir@2.1.5":
+ version "2.1.5"
+ resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
+ integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
+ dependencies:
+ "@nodelib/fs.stat" "2.0.5"
+ run-parallel "^1.1.9"
+
+"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
+ version "2.0.5"
+ resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
+ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
+
+"@nodelib/fs.walk@^1.2.3":
+ version "1.2.8"
+ resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz"
+ integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
+ dependencies:
+ "@nodelib/fs.scandir" "2.1.5"
+ fastq "^1.6.0"
+
+"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz"
+ integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==
+
+"@protobufjs/base64@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz"
+ integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
+
+"@protobufjs/codegen@^2.0.4":
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz"
+ integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
+
+"@protobufjs/eventemitter@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz"
+ integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==
+
+"@protobufjs/fetch@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz"
+ integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==
+ dependencies:
+ "@protobufjs/aspromise" "^1.1.1"
+ "@protobufjs/inquire" "^1.1.0"
+
+"@protobufjs/float@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz"
+ integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==
+
+"@protobufjs/inquire@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz"
+ integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==
+
+"@protobufjs/path@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz"
+ integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==
+
+"@protobufjs/pool@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz"
+ integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==
+
+"@protobufjs/utf8@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz"
+ integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
+
+"@rollup/rollup-android-arm-eabi@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz#d964ee8ce4d18acf9358f96adc408689b6e27fe3"
+ integrity sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==
+
+"@rollup/rollup-android-arm64@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz#9b5e130ecc32a5fc1e96c09ff371743ee71a62d3"
+ integrity sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==
+
+"@rollup/rollup-darwin-arm64@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz"
+ integrity sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==
+
+"@rollup/rollup-darwin-x64@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz#d7380c1531ab0420ca3be16f17018ef72dd3d504"
+ integrity sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==
+
+"@rollup/rollup-freebsd-arm64@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz#cbcbd7248823c6b430ce543c59906dd3c6df0936"
+ integrity sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==
+
+"@rollup/rollup-freebsd-x64@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz#96bf6ff875bab5219c3472c95fa6eb992586a93b"
+ integrity sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==
+
+"@rollup/rollup-linux-arm-gnueabihf@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz#d80cd62ce6d40f8e611008d8dbf03b5e6bbf009c"
+ integrity sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==
+
+"@rollup/rollup-linux-arm-musleabihf@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz#75440cfc1e8d0f87a239b4c31dfeaf4719b656b7"
+ integrity sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==
+
+"@rollup/rollup-linux-arm64-gnu@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz#ac527485ecbb619247fb08253ec8c551a0712e7c"
+ integrity sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==
+
+"@rollup/rollup-linux-arm64-musl@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz#74d2b5cb11cf714cd7d1682e7c8b39140e908552"
+ integrity sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==
+
+"@rollup/rollup-linux-loongarch64-gnu@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz#a0a310e51da0b5fea0e944b0abd4be899819aef6"
+ integrity sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==
+
+"@rollup/rollup-linux-powerpc64le-gnu@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz#4077e2862b0ac9f61916d6b474d988171bd43b83"
+ integrity sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==
+
+"@rollup/rollup-linux-riscv64-gnu@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz#5812a1a7a2f9581cbe12597307cc7ba3321cf2f3"
+ integrity sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==
+
+"@rollup/rollup-linux-riscv64-musl@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz#973aaaf4adef4531375c36616de4e01647f90039"
+ integrity sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==
+
+"@rollup/rollup-linux-s390x-gnu@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz#9bad59e907ba5bfcf3e9dbd0247dfe583112f70b"
+ integrity sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==
+
+"@rollup/rollup-linux-x64-gnu@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz#68b045a720bd9b4d905f462b997590c2190a6de0"
+ integrity sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==
+
+"@rollup/rollup-linux-x64-musl@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz#8e703e2c2ad19ba7b2cb3d8c3a4ad11d4ee3a282"
+ integrity sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==
+
+"@rollup/rollup-win32-arm64-msvc@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz#c5bee19fa670ff5da5f066be6a58b4568e9c650b"
+ integrity sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==
+
+"@rollup/rollup-win32-ia32-msvc@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz#846e02c17044bd922f6f483a3b4d36aac6e2b921"
+ integrity sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==
+
+"@rollup/rollup-win32-x64-msvc@4.40.0":
+ version "4.40.0"
+ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz#fd92d31a2931483c25677b9c6698106490cbbc76"
+ integrity sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==
+
+"@types/babel__core@^7.20.5":
+ version "7.20.5"
+ resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz"
+ integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==
+ dependencies:
+ "@babel/parser" "^7.20.7"
+ "@babel/types" "^7.20.7"
+ "@types/babel__generator" "*"
+ "@types/babel__template" "*"
+ "@types/babel__traverse" "*"
+
+"@types/babel__generator@*":
+ version "7.27.0"
+ resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz"
+ integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==
+ dependencies:
+ "@babel/types" "^7.0.0"
+
+"@types/babel__template@*":
+ version "7.4.4"
+ resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz"
+ integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==
+ dependencies:
+ "@babel/parser" "^7.1.0"
+ "@babel/types" "^7.0.0"
+
+"@types/babel__traverse@*":
+ version "7.20.7"
+ resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz"
+ integrity sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==
+ dependencies:
+ "@babel/types" "^7.20.7"
+
+"@types/estree@1.0.7", "@types/estree@^1.0.6":
+ version "1.0.7"
+ resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz"
+ integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==
+
+"@types/json-schema@^7.0.15":
+ version "7.0.15"
+ resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz"
+ integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
+
+"@types/node@>=12.12.47", "@types/node@>=13.7.0":
+ version "22.14.1"
+ resolved "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz"
+ integrity sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==
+ dependencies:
+ undici-types "~6.21.0"
+
+"@types/react-dom@^19.0.4":
+ version "19.1.2"
+ resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz"
+ integrity sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==
+
+"@types/react@^19.0.10":
+ version "19.1.2"
+ resolved "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz"
+ integrity sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==
+ dependencies:
+ csstype "^3.0.2"
+
+"@typescript-eslint/eslint-plugin@8.31.0":
+ version "8.31.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz"
+ integrity sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==
+ dependencies:
+ "@eslint-community/regexpp" "^4.10.0"
+ "@typescript-eslint/scope-manager" "8.31.0"
+ "@typescript-eslint/type-utils" "8.31.0"
+ "@typescript-eslint/utils" "8.31.0"
+ "@typescript-eslint/visitor-keys" "8.31.0"
+ graphemer "^1.4.0"
+ ignore "^5.3.1"
+ natural-compare "^1.4.0"
+ ts-api-utils "^2.0.1"
+
+"@typescript-eslint/parser@8.31.0":
+ version "8.31.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz"
+ integrity sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==
+ dependencies:
+ "@typescript-eslint/scope-manager" "8.31.0"
+ "@typescript-eslint/types" "8.31.0"
+ "@typescript-eslint/typescript-estree" "8.31.0"
+ "@typescript-eslint/visitor-keys" "8.31.0"
+ debug "^4.3.4"
+
+"@typescript-eslint/scope-manager@8.31.0":
+ version "8.31.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz"
+ integrity sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==
+ dependencies:
+ "@typescript-eslint/types" "8.31.0"
+ "@typescript-eslint/visitor-keys" "8.31.0"
+
+"@typescript-eslint/type-utils@8.31.0":
+ version "8.31.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz"
+ integrity sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==
+ dependencies:
+ "@typescript-eslint/typescript-estree" "8.31.0"
+ "@typescript-eslint/utils" "8.31.0"
+ debug "^4.3.4"
+ ts-api-utils "^2.0.1"
+
+"@typescript-eslint/types@8.31.0":
+ version "8.31.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz"
+ integrity sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==
+
+"@typescript-eslint/typescript-estree@8.31.0":
+ version "8.31.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz"
+ integrity sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==
+ dependencies:
+ "@typescript-eslint/types" "8.31.0"
+ "@typescript-eslint/visitor-keys" "8.31.0"
+ debug "^4.3.4"
+ fast-glob "^3.3.2"
+ is-glob "^4.0.3"
+ minimatch "^9.0.4"
+ semver "^7.6.0"
+ ts-api-utils "^2.0.1"
+
+"@typescript-eslint/utils@8.31.0":
+ version "8.31.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz"
+ integrity sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.4.0"
+ "@typescript-eslint/scope-manager" "8.31.0"
+ "@typescript-eslint/types" "8.31.0"
+ "@typescript-eslint/typescript-estree" "8.31.0"
+
+"@typescript-eslint/visitor-keys@8.31.0":
+ version "8.31.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz"
+ integrity sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==
+ dependencies:
+ "@typescript-eslint/types" "8.31.0"
+ eslint-visitor-keys "^4.2.0"
+
+"@vitejs/plugin-react@^4.3.4":
+ version "4.4.1"
+ resolved "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz"
+ integrity sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==
+ dependencies:
+ "@babel/core" "^7.26.10"
+ "@babel/plugin-transform-react-jsx-self" "^7.25.9"
+ "@babel/plugin-transform-react-jsx-source" "^7.25.9"
+ "@types/babel__core" "^7.20.5"
+ react-refresh "^0.17.0"
+
+acorn-jsx@^5.3.2:
+ version "5.3.2"
+ resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
+ integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+
+acorn@^8.14.0:
+ version "8.14.1"
+ resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz"
+ integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==
+
+ajv@^6.12.4:
+ version "6.12.6"
+ resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+brace-expansion@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz"
+ integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+ dependencies:
+ balanced-match "^1.0.0"
+
+braces@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz"
+ integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
+ dependencies:
+ fill-range "^7.1.1"
+
+browserslist@^4.24.0:
+ version "4.24.4"
+ resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz"
+ integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==
+ dependencies:
+ caniuse-lite "^1.0.30001688"
+ electron-to-chromium "^1.5.73"
+ node-releases "^2.0.19"
+ update-browserslist-db "^1.1.1"
+
+callsites@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
+ integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
+caniuse-lite@^1.0.30001688:
+ version "1.0.30001715"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz"
+ integrity sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==
+
+chalk@^4.0.0:
+ version "4.1.2"
+ resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+cliui@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz"
+ integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.1"
+ wrap-ansi "^7.0.0"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+convert-source-map@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz"
+ integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
+
+cross-spawn@^7.0.6:
+ version "7.0.6"
+ resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz"
+ integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+csstype@^3.0.2:
+ version "3.1.3"
+ resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz"
+ integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
+
+debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
+ version "4.4.0"
+ resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz"
+ integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
+ dependencies:
+ ms "^2.1.3"
+
+deep-is@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
+ integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
+
+electron-to-chromium@^1.5.73:
+ version "1.5.140"
+ resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.140.tgz"
+ integrity sha512-o82Rj+ONp4Ip7Cl1r7lrqx/pXhbp/lh9DpKcMNscFJdh8ebyRofnc7Sh01B4jx403RI0oqTBvlZ7OBIZLMr2+Q==
+
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+esbuild@^0.25.0:
+ version "0.25.3"
+ resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz"
+ integrity sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==
+ optionalDependencies:
+ "@esbuild/aix-ppc64" "0.25.3"
+ "@esbuild/android-arm" "0.25.3"
+ "@esbuild/android-arm64" "0.25.3"
+ "@esbuild/android-x64" "0.25.3"
+ "@esbuild/darwin-arm64" "0.25.3"
+ "@esbuild/darwin-x64" "0.25.3"
+ "@esbuild/freebsd-arm64" "0.25.3"
+ "@esbuild/freebsd-x64" "0.25.3"
+ "@esbuild/linux-arm" "0.25.3"
+ "@esbuild/linux-arm64" "0.25.3"
+ "@esbuild/linux-ia32" "0.25.3"
+ "@esbuild/linux-loong64" "0.25.3"
+ "@esbuild/linux-mips64el" "0.25.3"
+ "@esbuild/linux-ppc64" "0.25.3"
+ "@esbuild/linux-riscv64" "0.25.3"
+ "@esbuild/linux-s390x" "0.25.3"
+ "@esbuild/linux-x64" "0.25.3"
+ "@esbuild/netbsd-arm64" "0.25.3"
+ "@esbuild/netbsd-x64" "0.25.3"
+ "@esbuild/openbsd-arm64" "0.25.3"
+ "@esbuild/openbsd-x64" "0.25.3"
+ "@esbuild/sunos-x64" "0.25.3"
+ "@esbuild/win32-arm64" "0.25.3"
+ "@esbuild/win32-ia32" "0.25.3"
+ "@esbuild/win32-x64" "0.25.3"
+
+escalade@^3.1.1, escalade@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"
+ integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
+
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+eslint-config-prettier@^10.1.2:
+ version "10.1.2"
+ resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz"
+ integrity sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==
+
+eslint-plugin-react-hooks@^5.2.0:
+ version "5.2.0"
+ resolved "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz"
+ integrity sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==
+
+eslint-plugin-react-refresh@^0.4.19:
+ version "0.4.20"
+ resolved "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz"
+ integrity sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==
+
+eslint-scope@^8.3.0:
+ version "8.3.0"
+ resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz"
+ integrity sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^5.2.0"
+
+eslint-visitor-keys@^3.4.3:
+ version "3.4.3"
+ resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
+ integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
+
+eslint-visitor-keys@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz"
+ integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==
+
+eslint@^9.22.0:
+ version "9.25.1"
+ resolved "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz"
+ integrity sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.2.0"
+ "@eslint-community/regexpp" "^4.12.1"
+ "@eslint/config-array" "^0.20.0"
+ "@eslint/config-helpers" "^0.2.1"
+ "@eslint/core" "^0.13.0"
+ "@eslint/eslintrc" "^3.3.1"
+ "@eslint/js" "9.25.1"
+ "@eslint/plugin-kit" "^0.2.8"
+ "@humanfs/node" "^0.16.6"
+ "@humanwhocodes/module-importer" "^1.0.1"
+ "@humanwhocodes/retry" "^0.4.2"
+ "@types/estree" "^1.0.6"
+ "@types/json-schema" "^7.0.15"
+ ajv "^6.12.4"
+ chalk "^4.0.0"
+ cross-spawn "^7.0.6"
+ debug "^4.3.2"
+ escape-string-regexp "^4.0.0"
+ eslint-scope "^8.3.0"
+ eslint-visitor-keys "^4.2.0"
+ espree "^10.3.0"
+ esquery "^1.5.0"
+ esutils "^2.0.2"
+ fast-deep-equal "^3.1.3"
+ file-entry-cache "^8.0.0"
+ find-up "^5.0.0"
+ glob-parent "^6.0.2"
+ ignore "^5.2.0"
+ imurmurhash "^0.1.4"
+ is-glob "^4.0.0"
+ json-stable-stringify-without-jsonify "^1.0.1"
+ lodash.merge "^4.6.2"
+ minimatch "^3.1.2"
+ natural-compare "^1.4.0"
+ optionator "^0.9.3"
+
+espree@^10.0.1, espree@^10.3.0:
+ version "10.3.0"
+ resolved "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz"
+ integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==
+ dependencies:
+ acorn "^8.14.0"
+ acorn-jsx "^5.3.2"
+ eslint-visitor-keys "^4.2.0"
+
+esquery@^1.5.0:
+ version "1.6.0"
+ resolved "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz"
+ integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
+ dependencies:
+ estraverse "^5.1.0"
+
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
+estraverse@^5.1.0, estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+esutils@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-glob@^3.3.2:
+ version "3.3.3"
+ resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz"
+ integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.8"
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
+ integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fast-levenshtein@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
+ integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
+
+fastq@^1.6.0:
+ version "1.19.1"
+ resolved "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz"
+ integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==
+ dependencies:
+ reusify "^1.0.4"
+
+faye-websocket@0.11.4:
+ version "0.11.4"
+ resolved "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz"
+ integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==
+ dependencies:
+ websocket-driver ">=0.5.1"
+
+fdir@^6.4.3, fdir@^6.4.4:
+ version "6.4.4"
+ resolved "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz"
+ integrity sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==
+
+file-entry-cache@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz"
+ integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==
+ dependencies:
+ flat-cache "^4.0.0"
+
+fill-range@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz"
+ integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+firebase@11.6.0:
+ version "11.6.0"
+ resolved "https://registry.yarnpkg.com/firebase/-/firebase-11.6.0.tgz#0a0d76144decc988812cba0bea5e62588ba69fc3"
+ integrity sha512-Xqm6j6zszIEmI5nW1MPR8yTafoRTSrW3mWG9Lk9elCJtQDQSiTEkKZiNtUm9y6XfOPl8xoF1TNpxZe8HjgA0Og==
+ dependencies:
+ "@firebase/analytics" "0.10.12"
+ "@firebase/analytics-compat" "0.2.18"
+ "@firebase/app" "0.11.4"
+ "@firebase/app-check" "0.8.13"
+ "@firebase/app-check-compat" "0.3.20"
+ "@firebase/app-compat" "0.2.53"
+ "@firebase/app-types" "0.9.3"
+ "@firebase/auth" "1.10.0"
+ "@firebase/auth-compat" "0.5.20"
+ "@firebase/data-connect" "0.3.3"
+ "@firebase/database" "1.0.14"
+ "@firebase/database-compat" "2.0.5"
+ "@firebase/firestore" "4.7.10"
+ "@firebase/firestore-compat" "0.3.45"
+ "@firebase/functions" "0.12.3"
+ "@firebase/functions-compat" "0.3.20"
+ "@firebase/installations" "0.6.13"
+ "@firebase/installations-compat" "0.2.13"
+ "@firebase/messaging" "0.12.17"
+ "@firebase/messaging-compat" "0.2.17"
+ "@firebase/performance" "0.7.2"
+ "@firebase/performance-compat" "0.2.15"
+ "@firebase/remote-config" "0.6.0"
+ "@firebase/remote-config-compat" "0.2.13"
+ "@firebase/storage" "0.13.7"
+ "@firebase/storage-compat" "0.3.17"
+ "@firebase/util" "1.11.0"
+ "@firebase/vertexai" "1.2.1"
+
+flat-cache@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz"
+ integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==
+ dependencies:
+ flatted "^3.2.9"
+ keyv "^4.5.4"
+
+flatted@^3.2.9:
+ version "3.3.3"
+ resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz"
+ integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
+
+fsevents@~2.3.2, fsevents@~2.3.3:
+ version "2.3.3"
+ resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
+
+gensync@^1.0.0-beta.2:
+ version "1.0.0-beta.2"
+ resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz"
+ integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+
+get-caller-file@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
+glob-parent@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+glob-parent@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz"
+ integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+ dependencies:
+ is-glob "^4.0.3"
+
+globals@^11.1.0:
+ version "11.12.0"
+ resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz"
+ integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
+
+globals@^14.0.0:
+ version "14.0.0"
+ resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz"
+ integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
+
+globals@^16.0.0:
+ version "16.0.0"
+ resolved "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz"
+ integrity sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==
+
+graphemer@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz"
+ integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+http-parser-js@>=0.5.1:
+ version "0.5.10"
+ resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz"
+ integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==
+
+idb@7.1.1:
+ version "7.1.1"
+ resolved "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz"
+ integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==
+
+ignore@^5.2.0, ignore@^5.3.1:
+ version "5.3.2"
+ resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz"
+ integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
+
+immer@^10.1.1:
+ version "10.1.1"
+ resolved "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz"
+ integrity sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==
+
+import-fresh@^3.2.1:
+ version "3.3.1"
+ resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz"
+ integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
+ integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-fullwidth-code-point@^3.0.0:
+ 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==
+
+is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+js-tokens@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
+ integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+js-yaml@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ dependencies:
+ argparse "^2.0.1"
+
+jsesc@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz"
+ integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
+
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-stable-stringify-without-jsonify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
+ integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
+
+json5@^2.2.3:
+ version "2.2.3"
+ resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
+ integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
+
+keyv@^4.5.4:
+ version "4.5.4"
+ resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz"
+ integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+ dependencies:
+ json-buffer "3.0.1"
+
+levn@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz"
+ integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+ dependencies:
+ prelude-ls "^1.2.1"
+ type-check "~0.4.0"
+
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+lodash.camelcase@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz"
+ integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
+
+lodash.merge@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
+ integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
+long@^5.0.0:
+ version "5.3.2"
+ resolved "https://registry.npmjs.org/long/-/long-5.3.2.tgz"
+ integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==
+
+lru-cache@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz"
+ integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
+ dependencies:
+ yallist "^3.0.2"
+
+merge2@^1.3.0:
+ version "1.4.1"
+ resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
+ integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
+micromatch@^4.0.8:
+ version "4.0.8"
+ resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz"
+ integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
+ dependencies:
+ braces "^3.0.3"
+ picomatch "^2.3.1"
+
+minimatch@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimatch@^9.0.4:
+ version "9.0.5"
+ resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz"
+ integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+ms@^2.1.3:
+ version "2.1.3"
+ resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+nanoid@^3.3.8:
+ version "3.3.11"
+ resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz"
+ integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
+
+natural-compare@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
+ integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
+
+node-releases@^2.0.19:
+ version "2.0.19"
+ resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz"
+ integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
+
+optionator@^0.9.3:
+ version "0.9.4"
+ resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz"
+ integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
+ dependencies:
+ deep-is "^0.1.3"
+ fast-levenshtein "^2.0.6"
+ levn "^0.4.1"
+ prelude-ls "^1.2.1"
+ type-check "^0.4.0"
+ word-wrap "^1.2.5"
+
+p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
+parent-module@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
+ integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+ dependencies:
+ callsites "^3.0.0"
+
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+picocolors@^1.0.0, picocolors@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz"
+ integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
+
+picomatch@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+picomatch@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz"
+ integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
+
+postcss@^8.5.3:
+ version "8.5.3"
+ resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz"
+ integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==
+ dependencies:
+ nanoid "^3.3.8"
+ picocolors "^1.1.1"
+ source-map-js "^1.2.1"
+
+prelude-ls@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
+ integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
+prettier@^3.5.3:
+ version "3.5.3"
+ resolved "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz"
+ integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==
+
+protobufjs@^7.2.5:
+ version "7.5.0"
+ resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.0.tgz"
+ integrity sha512-Z2E/kOY1QjoMlCytmexzYfDm/w5fKAiRwpSzGtdnXW1zC88Z2yXazHHrOtwCzn+7wSxyE8PYM4rvVcMphF9sOA==
+ dependencies:
+ "@protobufjs/aspromise" "^1.1.2"
+ "@protobufjs/base64" "^1.1.2"
+ "@protobufjs/codegen" "^2.0.4"
+ "@protobufjs/eventemitter" "^1.1.0"
+ "@protobufjs/fetch" "^1.1.0"
+ "@protobufjs/float" "^1.0.2"
+ "@protobufjs/inquire" "^1.1.0"
+ "@protobufjs/path" "^1.1.2"
+ "@protobufjs/pool" "^1.1.0"
+ "@protobufjs/utf8" "^1.1.0"
+ "@types/node" ">=13.7.0"
+ long "^5.0.0"
+
+punycode@^2.1.0:
+ version "2.3.1"
+ resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
+ integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+
+queue-microtask@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
+ integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+
+react-dom@^19.0.0:
+ version "19.1.0"
+ resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz"
+ integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==
+ dependencies:
+ scheduler "^0.26.0"
+
+react-refresh@^0.17.0:
+ version "0.17.0"
+ resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz"
+ integrity sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==
+
+react@^19.0.0:
+ version "19.1.0"
+ resolved "https://registry.npmjs.org/react/-/react-19.1.0.tgz"
+ integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
+ integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
+
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+reusify@^1.0.4:
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz"
+ integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==
+
+rollup@^4.34.9:
+ version "4.40.0"
+ resolved "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz"
+ integrity sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==
+ dependencies:
+ "@types/estree" "1.0.7"
+ optionalDependencies:
+ "@rollup/rollup-android-arm-eabi" "4.40.0"
+ "@rollup/rollup-android-arm64" "4.40.0"
+ "@rollup/rollup-darwin-arm64" "4.40.0"
+ "@rollup/rollup-darwin-x64" "4.40.0"
+ "@rollup/rollup-freebsd-arm64" "4.40.0"
+ "@rollup/rollup-freebsd-x64" "4.40.0"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.40.0"
+ "@rollup/rollup-linux-arm-musleabihf" "4.40.0"
+ "@rollup/rollup-linux-arm64-gnu" "4.40.0"
+ "@rollup/rollup-linux-arm64-musl" "4.40.0"
+ "@rollup/rollup-linux-loongarch64-gnu" "4.40.0"
+ "@rollup/rollup-linux-powerpc64le-gnu" "4.40.0"
+ "@rollup/rollup-linux-riscv64-gnu" "4.40.0"
+ "@rollup/rollup-linux-riscv64-musl" "4.40.0"
+ "@rollup/rollup-linux-s390x-gnu" "4.40.0"
+ "@rollup/rollup-linux-x64-gnu" "4.40.0"
+ "@rollup/rollup-linux-x64-musl" "4.40.0"
+ "@rollup/rollup-win32-arm64-msvc" "4.40.0"
+ "@rollup/rollup-win32-ia32-msvc" "4.40.0"
+ "@rollup/rollup-win32-x64-msvc" "4.40.0"
+ fsevents "~2.3.2"
+
+run-parallel@^1.1.9:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz"
+ integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
+ dependencies:
+ queue-microtask "^1.2.2"
+
+safe-buffer@>=5.1.0:
+ version "5.2.1"
+ resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+scheduler@^0.26.0:
+ version "0.26.0"
+ resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz"
+ integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==
+
+semver@^6.3.1:
+ version "6.3.1"
+ resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
+ integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
+
+semver@^7.6.0:
+ version "7.7.1"
+ resolved "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz"
+ integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+source-map-js@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz"
+ integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
+
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-json-comments@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+tinyglobby@^0.2.12:
+ version "0.2.13"
+ resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz"
+ integrity sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==
+ dependencies:
+ fdir "^6.4.4"
+ picomatch "^4.0.2"
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+ts-api-utils@^2.0.1:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz"
+ integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==
+
+tslib@^2.1.0:
+ version "2.8.1"
+ resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
+ integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+
+type-check@^0.4.0, type-check@~0.4.0:
+ version "0.4.0"
+ resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz"
+ integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+ dependencies:
+ prelude-ls "^1.2.1"
+
+typescript-eslint@^8.26.1:
+ version "8.31.0"
+ resolved "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.31.0.tgz"
+ integrity sha512-u+93F0sB0An8WEAPtwxVhFby573E8ckdjwUUQUj9QA4v8JAvgtoDdIyYR3XFwFHq2W1KJ1AurwJCO+w+Y1ixyQ==
+ dependencies:
+ "@typescript-eslint/eslint-plugin" "8.31.0"
+ "@typescript-eslint/parser" "8.31.0"
+ "@typescript-eslint/utils" "8.31.0"
+
+typescript@~5.7.2:
+ version "5.7.3"
+ resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz"
+ integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
+
+undici-types@~6.21.0:
+ version "6.21.0"
+ resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz"
+ integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
+
+update-browserslist-db@^1.1.1:
+ version "1.1.3"
+ resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz"
+ integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==
+ dependencies:
+ escalade "^3.2.0"
+ picocolors "^1.1.1"
+
+uri-js@^4.2.2:
+ version "4.4.1"
+ resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
+vite@^6.3.1:
+ version "6.3.2"
+ resolved "https://registry.npmjs.org/vite/-/vite-6.3.2.tgz"
+ integrity sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==
+ dependencies:
+ esbuild "^0.25.0"
+ fdir "^6.4.3"
+ picomatch "^4.0.2"
+ postcss "^8.5.3"
+ rollup "^4.34.9"
+ tinyglobby "^0.2.12"
+ optionalDependencies:
+ fsevents "~2.3.3"
+
+web-vitals@^4.2.4:
+ version "4.2.4"
+ resolved "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz"
+ integrity sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==
+
+websocket-driver@>=0.5.1:
+ version "0.7.4"
+ resolved "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz"
+ integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==
+ dependencies:
+ http-parser-js ">=0.5.1"
+ safe-buffer ">=5.1.0"
+ websocket-extensions ">=0.1.1"
+
+websocket-extensions@>=0.1.1:
+ version "0.1.4"
+ resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz"
+ integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+word-wrap@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz"
+ integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
+
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
+yallist@^3.0.2:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz"
+ integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+
+yargs-parser@^21.1.1:
+ version "21.1.1"
+ resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
+ integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
+yargs@^17.7.2:
+ version "17.7.2"
+ resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz"
+ integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
+ dependencies:
+ cliui "^8.0.1"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.3"
+ y18n "^5.0.5"
+ yargs-parser "^21.1.1"
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/scripts/test.sh b/scripts/test.sh
index dbc6e798a..a908fbc2e 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -1,4 +1,4 @@
set -e
# Run linter
-find . -type f -name "*.js" -not -path "*node_modules*" -not -path "*dataconnect-sdk/*" \
- | xargs eslint
+find . -type f -name "*.js" -not -path "*node_modules*" -not -path "*dataconnect-sdk/*" -not -path "*ai/sample-app/*" \
+ | xargs eslint
\ No newline at end of file