From 1a1c44d5536b6735ab03fd3f949a976db43f0b90 Mon Sep 17 00:00:00 2001 From: Vince Mammoliti Date: Mon, 15 Jun 2026 10:24:22 -0400 Subject: [PATCH 01/18] Updated Changes to allow local LLM --- guard-demo-client.vm | 1 + 1 file changed, 1 insertion(+) create mode 160000 guard-demo-client.vm diff --git a/guard-demo-client.vm b/guard-demo-client.vm new file mode 160000 index 0000000..94718ea --- /dev/null +++ b/guard-demo-client.vm @@ -0,0 +1 @@ +Subproject commit 94718ea848ccd8507627f1d92a90bd8d641cc242 From 632fe6f73c04f9cc53959df8f136d9a082bdb70d Mon Sep 17 00:00:00 2001 From: Vince Mammoliti Date: Mon, 15 Jun 2026 10:28:36 -0400 Subject: [PATCH 02/18] Added changes for local LLM, fixed model fetch --- backend/llm_client.py | 134 ++- backend/main.py | 35 +- backend/models.py | 1 + backend/schemas.py | 1 + guard-demo-client.vm | 1 - package-lock.json | 377 +++---- package.json | 10 +- src/components/DemoPromptManager.tsx | 2 +- src/pages/AdminConsole.tsx | 222 +++- src/pages/AdminConsole.tsx.box | 1417 ++++++++++++++++++++++++++ src/services/api.ts | 4 + src/types/index.ts | 2 + 12 files changed, 1984 insertions(+), 222 deletions(-) delete mode 160000 guard-demo-client.vm create mode 100644 src/pages/AdminConsole.tsx.box diff --git a/backend/llm_client.py b/backend/llm_client.py index 3d76904..51594c4 100644 --- a/backend/llm_client.py +++ b/backend/llm_client.py @@ -15,9 +15,6 @@ LITELLM_MODELS_TIMEOUT = 10.0 STATIC_MODELS = [ - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", "gpt-4o", "gpt-4o-mini", "gpt-4", @@ -31,10 +28,10 @@ def _supports_custom_temperature(model: str) -> bool: """ - GPT-5 family currently rejects non-default temperature values on chat completions. + Some models may not support custom temperature values. """ name = (model or "").strip().lower() - return not name.startswith("gpt-5") + return True # Most models support custom temperature class LiteLLMGuardrailError(Exception): @@ -184,23 +181,23 @@ def _call_litellm_chat( return response.model_dump() -def _get_embeddings_openai(texts: List[str], api_key: str) -> List[List[float]]: +def _get_embeddings_openai(texts: List[str], api_key: str, model: str = "text-embedding-3-small") -> List[List[float]]: """Get embeddings from OpenAI directly""" client = openai.OpenAI(api_key=api_key) response = client.embeddings.create( - model="text-embedding-ada-002", + model=model, input=texts, ) return [embedding.embedding for embedding in response.data] def _get_embeddings_litellm( - texts: List[str], api_key: str, base_url: str + texts: List[str], api_key: str, base_url: str, model: str = "text-embedding-3-small" ) -> List[List[float]]: """Get embeddings from LiteLLM proxy""" client = openai.OpenAI(api_key=api_key, base_url=f"{base_url.rstrip('/')}/v1") response = client.embeddings.create( - model="text-embedding-ada-002", + model=model, input=texts, ) return [embedding.embedding for embedding in response.data] @@ -284,6 +281,15 @@ def get_embeddings(texts: List[str], config: Optional[AppConfig] = None) -> List raise Exception("Configure LLM API key in Admin → Security") api_key = effective_llm_api_key(cfg) or "" + embeddings_model = getattr(cfg, "embeddings_model", None) + valid_models = get_embedding_models(cfg) + if valid_models: + if not embeddings_model or embeddings_model not in valid_models: + embeddings_model = valid_models[0] + elif not embeddings_model: + raise Exception( + "Unable to determine available embedding models. Check API credentials or LiteLLM connection." + ) litellm_base_url = getattr(cfg, "litellm_base_url", None) or "http://localhost:4000" @@ -293,9 +299,10 @@ def get_embeddings(texts: List[str], config: Optional[AppConfig] = None) -> List texts=texts, api_key=api_key, base_url=litellm_base_url, + model=embeddings_model, ) else: - return _get_embeddings_openai(texts=texts, api_key=api_key) + return _get_embeddings_openai(texts=texts, api_key=api_key, model=embeddings_model) except openai.APIConnectionError as e: if use_litellm: raise Exception( @@ -330,6 +337,15 @@ def _get_models_litellm(api_key: str, base_url: str) -> Optional[List[str]]: return result if result else None +def _is_standard_model(model_id: Optional[str]) -> bool: + if not isinstance(model_id, str) or not model_id.strip(): + return False + normalized = model_id.strip().lower() + if "embed" in normalized or "embedding" in normalized: + return False + return True + + def get_models(config: Optional[AppConfig] = None) -> List[str]: """ Get available models. When using LiteLLM, returns key-specific models from proxy. @@ -338,12 +354,104 @@ def get_models(config: Optional[AppConfig] = None) -> List[str]: cfg = config or _get_config() if not cfg: return STATIC_MODELS + use_litellm = getattr(cfg, "use_litellm", False) api_key = effective_llm_api_key(cfg) - if not use_litellm or not api_key: + + if not api_key and not use_litellm: + return STATIC_MODELS + + # --- OpenAI direct mode --- + if not use_litellm: + models = _get_models_openai(api_key) + if models: + filtered_models = [model for model in models if _is_standard_model(model)] + return filtered_models or STATIC_MODELS return STATIC_MODELS + + # --- LiteLLM mode --- litellm_base_url = getattr(cfg, "litellm_base_url", None) or "http://localhost:4000" - models = _get_models_litellm(api_key=api_key, base_url=litellm_base_url) + models = _get_models_litellm(api_key=api_key or "", base_url=litellm_base_url) if models: - return models + filtered_models = [model for model in models if _is_standard_model(model)] + return filtered_models or STATIC_MODELS + return STATIC_MODELS + + + +def _get_models_openai(api_key: str) -> Optional[List[str]]: + """ + Fetch available models directly from OpenAI. + Returns list of model ids on success, None on failure. + Added by Vince 6/9/26 + """ + try: + client = openai.OpenAI(api_key=api_key) + resp = client.models.list() + return [m.id for m in resp.data] if resp.data else None + except Exception: + return None + + +def get_embedding_models(config: Optional[AppConfig] = None) -> List[str]: + """ + Get available embeddings models. + For OpenAI direct: filter chat models to find embedding models. + For LiteLLM: call /v1/models and filter for embeddings. + Falls back to common defaults. + """ + EMBEDDING_DEFAULTS = [ + "text-embedding-3-large", + "text-embedding-3-small", + "text-embedding-ada-002", + "nomic-embed-text", + ] + + cfg = config or _get_config() + if not cfg: + return EMBEDDING_DEFAULTS + + use_litellm = getattr(cfg, "use_litellm", False) + api_key = effective_llm_api_key(cfg) + + # Helper to detect embedding model ids + def _filter_embedding_model(model_id: Optional[str]) -> bool: + if not isinstance(model_id, str) or not model_id.strip(): + return False + normalized = model_id.strip().lower() + return "embed" in normalized or "embedding" in normalized + + # --- OpenAI direct mode --- + if not use_litellm: + if not api_key: + return EMBEDDING_DEFAULTS + try: + client = openai.OpenAI(api_key=api_key) + models = client.models.list() + embedding_models = [m.id for m in models.data if _filter_embedding_model(m.id)] + result = embedding_models if embedding_models else EMBEDDING_DEFAULTS.copy() +# if "nomic-embed-text" not in result: +# result.append("nomic-embed-text") + return result + except Exception: + return EMBEDDING_DEFAULTS + + # --- LiteLLM mode --- + litellm_base_url = getattr(cfg, "litellm_base_url", None) or "http://localhost:4000" + url = f"{litellm_base_url.rstrip('/')}/v1/models" + headers = {"Authorization": f"Bearer {api_key}"} if api_key else {} + try: + with httpx.Client(timeout=LITELLM_MODELS_TIMEOUT) as client: + resp = client.get(url, headers=headers) + resp.raise_for_status() + data = resp.json() + models = data.get("data", []) + embedding_models = [m.get("id") for m in models if isinstance(m, dict) and _filter_embedding_model(m.get("id"))] + result = embedding_models if embedding_models else EMBEDDING_DEFAULTS.copy() +# if "nomic-embed-text" not in result: +# result.append("nomic-embed-text") + return result + except Exception: + return EMBEDDING_DEFAULTS + diff --git a/backend/main.py b/backend/main.py index c36b2b0..505475f 100644 --- a/backend/main.py +++ b/backend/main.py @@ -73,6 +73,16 @@ def _migrate_demo_prompts_preferred_llm(): conn.commit() +def _migrate_app_config_embeddings_model(): + """Add embeddings_model to app_config if missing (existing DBs).""" + with engine.connect() as conn: + r = conn.execute(text("PRAGMA table_info(app_config)")) + columns = [row[1] for row in r.fetchall()] + if "embeddings_model" not in columns: + conn.execute(text("ALTER TABLE app_config ADD COLUMN embeddings_model VARCHAR")) + conn.commit() + + # Migration: add theme to app_config if missing (for UI theming) def _migrate_app_config_theme(): with engine.connect() as conn: @@ -86,6 +96,7 @@ def _migrate_app_config_theme(): _migrate_app_config_litellm() +_migrate_app_config_embeddings_model() def _migrate_app_config_litellm_virtual_key(): @@ -184,14 +195,16 @@ async def update_config(config_update: AppConfigUpdate, db: Session = Depends(ge for field, value in config_update.dict(exclude_unset=True).items(): setattr(config, field, value) - # Auto-pick model when saving LiteLLM virtual key: if current model invalid for key, set to first allowed - use_litellm = getattr(config, "use_litellm", False) - if use_litellm and getattr(config, "litellm_virtual_key", None): + # Auto-pick models only when missing/null (don't override user's explicit selection) + if not config.openai_model: allowed = llm_client.get_models(config) - if allowed and (not config.openai_model or config.openai_model not in allowed): + if allowed: config.openai_model = allowed[0] - elif not use_litellm and config.openai_model not in llm_client.STATIC_MODELS: - config.openai_model = llm_client.STATIC_MODELS[0] + + if not config.embeddings_model: + allowed_embeddings = llm_client.get_embedding_models(config) + if allowed_embeddings: + config.embeddings_model = allowed_embeddings[0] db.commit() db.refresh(config) @@ -1195,3 +1208,13 @@ async def get_available_models(): return {"models": models} except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to get models: {str(e)}") from e + + +@app.get("/api/embeddings-models") +async def get_available_embeddings_models(): + """Get available embeddings models""" + try: + models = llm_client.get_embedding_models() + return {"models": models} + except Exception as e: + raise HTTPException(status_code=500, detail=f"Failed to get embeddings models: {str(e)}") from e diff --git a/backend/models.py b/backend/models.py index 0e48ea1..5be166d 100644 --- a/backend/models.py +++ b/backend/models.py @@ -20,6 +20,7 @@ class AppConfig(Base): logo_url = Column(String, nullable=True) system_prompt = Column(Text, nullable=True) openai_model = Column(String, default="gpt-4o-mini") + embeddings_model = Column(String, nullable=True) temperature = Column(String, default="7") lakera_enabled = Column(Boolean, default=True) lakera_blocking_mode = Column(Boolean, default=False) diff --git a/backend/schemas.py b/backend/schemas.py index 504788b..0560a58 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -20,6 +20,7 @@ class AppConfigBase(BaseModel): rag_content_scanning: bool = False rag_lakera_project_id: Optional[str] = None openai_model: str = "gpt-4o-mini" + embeddings_model: Optional[str] = None temperature: int = 7 system_prompt: Optional[str] = None # UI theme: e.g. "blue", "emerald", "purple", "amber" diff --git a/guard-demo-client.vm b/guard-demo-client.vm deleted file mode 160000 index 94718ea..0000000 --- a/guard-demo-client.vm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 94718ea848ccd8507627f1d92a90bd8d641cc242 diff --git a/package-lock.json b/package-lock.json index 4050f21..d5d7b83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -869,11 +869,10 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -919,11 +918,10 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1089,10 +1087,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", - "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", - "license": "MIT", + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", "engines": { "node": ">=14.0.0" } @@ -1105,280 +1102,325 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.49.0.tgz", - "integrity": "sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", + "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.49.0.tgz", - "integrity": "sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", + "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz", - "integrity": "sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", + "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.49.0.tgz", - "integrity": "sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", + "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.49.0.tgz", - "integrity": "sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", + "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.49.0.tgz", - "integrity": "sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", + "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.49.0.tgz", - "integrity": "sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", + "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.49.0.tgz", - "integrity": "sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", + "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.49.0.tgz", - "integrity": "sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", + "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.49.0.tgz", - "integrity": "sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", + "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz", - "integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", + "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", + "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", "cpu": [ "loong64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.49.0.tgz", - "integrity": "sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", + "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", + "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.49.0.tgz", - "integrity": "sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", + "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.49.0.tgz", - "integrity": "sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", + "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.49.0.tgz", - "integrity": "sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", + "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", "cpu": [ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.49.0.tgz", - "integrity": "sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", + "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.49.0.tgz", - "integrity": "sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", + "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", + "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", + "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.49.0.tgz", - "integrity": "sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", + "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.49.0.tgz", - "integrity": "sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", + "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", "cpu": [ "ia32" ], "dev": true, - "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", + "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", + "cpu": [ + "x64" + ], + "dev": true, "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz", - "integrity": "sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", + "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -1761,11 +1803,10 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2196,9 +2237,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001737", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001737.tgz", - "integrity": "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==", + "version": "1.0.30001797", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz", + "integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==", "dev": true, "funding": [ { @@ -2213,8 +2254,7 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/chalk": { "version": "4.1.2", @@ -2917,11 +2957,10 @@ } }, "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2999,11 +3038,10 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3207,11 +3245,10 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true }, "node_modules/for-each": { "version": "0.3.5", @@ -3437,11 +3474,10 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4156,11 +4192,10 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, - "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -4364,13 +4399,12 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, - "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -5048,12 +5082,11 @@ } }, "node_modules/react-router": { - "version": "6.30.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", - "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", - "license": "MIT", + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", "dependencies": { - "@remix-run/router": "1.23.0" + "@remix-run/router": "1.23.2" }, "engines": { "node": ">=14.0.0" @@ -5063,13 +5096,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.30.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", - "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", - "license": "MIT", + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", "dependencies": { - "@remix-run/router": "1.23.0", - "react-router": "6.30.1" + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" }, "engines": { "node": ">=14.0.0" @@ -5206,11 +5238,10 @@ } }, "node_modules/rollup": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.49.0.tgz", - "integrity": "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==", + "version": "4.60.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", + "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/estree": "1.0.8" }, @@ -5222,26 +5253,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.49.0", - "@rollup/rollup-android-arm64": "4.49.0", - "@rollup/rollup-darwin-arm64": "4.49.0", - "@rollup/rollup-darwin-x64": "4.49.0", - "@rollup/rollup-freebsd-arm64": "4.49.0", - "@rollup/rollup-freebsd-x64": "4.49.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", - "@rollup/rollup-linux-arm-musleabihf": "4.49.0", - "@rollup/rollup-linux-arm64-gnu": "4.49.0", - "@rollup/rollup-linux-arm64-musl": "4.49.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", - "@rollup/rollup-linux-ppc64-gnu": "4.49.0", - "@rollup/rollup-linux-riscv64-gnu": "4.49.0", - "@rollup/rollup-linux-riscv64-musl": "4.49.0", - "@rollup/rollup-linux-s390x-gnu": "4.49.0", - "@rollup/rollup-linux-x64-gnu": "4.49.0", - "@rollup/rollup-linux-x64-musl": "4.49.0", - "@rollup/rollup-win32-arm64-msvc": "4.49.0", - "@rollup/rollup-win32-ia32-msvc": "4.49.0", - "@rollup/rollup-win32-x64-msvc": "4.49.0", + "@rollup/rollup-android-arm-eabi": "4.60.0", + "@rollup/rollup-android-arm64": "4.60.0", + "@rollup/rollup-darwin-arm64": "4.60.0", + "@rollup/rollup-darwin-x64": "4.60.0", + "@rollup/rollup-freebsd-arm64": "4.60.0", + "@rollup/rollup-freebsd-x64": "4.60.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", + "@rollup/rollup-linux-arm-musleabihf": "4.60.0", + "@rollup/rollup-linux-arm64-gnu": "4.60.0", + "@rollup/rollup-linux-arm64-musl": "4.60.0", + "@rollup/rollup-linux-loong64-gnu": "4.60.0", + "@rollup/rollup-linux-loong64-musl": "4.60.0", + "@rollup/rollup-linux-ppc64-gnu": "4.60.0", + "@rollup/rollup-linux-ppc64-musl": "4.60.0", + "@rollup/rollup-linux-riscv64-gnu": "4.60.0", + "@rollup/rollup-linux-riscv64-musl": "4.60.0", + "@rollup/rollup-linux-s390x-gnu": "4.60.0", + "@rollup/rollup-linux-x64-gnu": "4.60.0", + "@rollup/rollup-linux-x64-musl": "4.60.0", + "@rollup/rollup-openbsd-x64": "4.60.0", + "@rollup/rollup-openharmony-arm64": "4.60.0", + "@rollup/rollup-win32-arm64-msvc": "4.60.0", + "@rollup/rollup-win32-ia32-msvc": "4.60.0", + "@rollup/rollup-win32-x64-gnu": "4.60.0", + "@rollup/rollup-win32-x64-msvc": "4.60.0", "fsevents": "~2.3.2" } }, @@ -5763,11 +5799,11 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -6160,11 +6196,10 @@ "license": "MIT" }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, - "license": "MIT", "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/package.json b/package.json index 7e42ada..2666ac2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "agentic-demo-frontend", "private": true, - "version": "0.0.0", + "version": "1.0.1", "type": "module", "scripts": { "dev": "vite", @@ -10,13 +10,13 @@ "preview": "vite preview" }, "dependencies": { + "clsx": "^2.0.0", + "lucide-react": "^0.294.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", "react-router-dom": "^6.20.1", - "lucide-react": "^0.294.0", - "clsx": "^2.0.0", - "tailwind-merge": "^2.0.0", - "react-dropzone": "^14.2.3" + "tailwind-merge": "^2.0.0" }, "devDependencies": { "@types/react": "^18.2.37", diff --git a/src/components/DemoPromptManager.tsx b/src/components/DemoPromptManager.tsx index 2d37d74..cec171d 100644 --- a/src/components/DemoPromptManager.tsx +++ b/src/components/DemoPromptManager.tsx @@ -335,7 +335,7 @@ const PromptForm: React.FC = ({ prompt, onSave, onCancel }) => {availableModels.map((model) => ( ))} diff --git a/src/pages/AdminConsole.tsx b/src/pages/AdminConsole.tsx index e479aa7..b594689 100644 --- a/src/pages/AdminConsole.tsx +++ b/src/pages/AdminConsole.tsx @@ -8,6 +8,7 @@ import ToolManager from '../components/ToolManager'; import GenerateContentModal from '../components/GenerateContentModal'; import RagManagement, { RagManagementRef } from '../components/RagManagement'; import DemoPromptManager from '../components/DemoPromptManager'; +import packageJson from '../../package.json'; type TabType = 'setup' | 'branding' | 'llm' | 'rag' | 'rag-scanning' | 'tools' | 'security' | 'prompts' | 'export'; @@ -24,6 +25,8 @@ const AdminConsole: React.FC = () => { const [showLakeraKey, setShowLakeraKey] = useState(false); const [showMCPInstructions, setShowMCPInstructions] = useState(false); const [availableModels, setAvailableModels] = useState([]); + const [availableEmbeddingModels, setAvailableEmbeddingModels] = useState([]); + const [embeddingModelsLoaded, setEmbeddingModelsLoaded] = useState(false); const [ragScanningNotificationCount, setRagScanningNotificationCount] = useState(0); const [ragScanningProgress, setRagScanningProgress] = useState<{isScanning: boolean; current: number; total: number; filename?: string} | null>(null); const progressPollingRef = useRef(null); @@ -46,6 +49,7 @@ const AdminConsole: React.FC = () => { useEffect(() => { loadConfig(); loadModels(); + loadEmbeddingModels(); loadRagScanningResult(); }, []); @@ -55,6 +59,17 @@ const AdminConsole: React.FC = () => { } }, [config?.theme]); + useEffect(() => { + if ( + config && + embeddingModelsLoaded && + availableEmbeddingModels.length > 0 && + !availableEmbeddingModels.includes(config.embeddings_model || '') + ) { + setConfig({ ...config, embeddings_model: availableEmbeddingModels[0] }); + } + }, [config, embeddingModelsLoaded, availableEmbeddingModels]); + const applyTheme = (theme?: string) => { const themes = ['blue', 'emerald', 'purple', 'amber']; const body = document.body; @@ -73,9 +88,13 @@ const AdminConsole: React.FC = () => { // Reload models when switching to LLM tab (e.g. after saving LiteLLM key in Security) useEffect(() => { if (activeTab === 'llm') { + loadConfig(); // Reload config to get latest saved model loadModels(); + if (!embeddingModelsLoaded) { + loadEmbeddingModels(); + } } - }, [activeTab]); + }, [activeTab, embeddingModelsLoaded]); // Poll for RAG scanning progress useEffect(() => { @@ -222,20 +241,49 @@ const AdminConsole: React.FC = () => { const loadModels = async () => { try { const modelsData = await apiService.getModels(); - setAvailableModels(modelsData.models); + const models = modelsData.models; + setAvailableModels(models); + + // If current model is not available, suggest switching + if (config && config.openai_model && !models.includes(config.openai_model)) { + console.warn(`Current model '${config.openai_model}' not available. Available models:`, models); + } } catch (error) { console.error('Failed to load models:', error); - // Fallback to hardcoded models if API fails + // Fallback to common models that are usually available (remove gpt-5 which often isn't) setAvailableModels([ - "gpt-5", - "gpt-5-mini", - "gpt-5-nano", "gpt-4o", "gpt-4o-mini", "gpt-4", "gpt-4-turbo", - "gpt-3.5-turbo" + "gpt-3.5-turbo", + "llama3.2", + "llama2" + ]); + } + }; + + const loadEmbeddingModels = async (force = false) => { + if (!force && embeddingModelsLoaded) { + return; + } + setEmbeddingModelsLoaded(false); + try { + const modelsData = await apiService.getEmbeddingModels(); + const models = modelsData.models; + setAvailableEmbeddingModels(models); + setEmbeddingModelsLoaded(true); + console.log('Loaded embedding models:', models); + } catch (error) { + console.error('Failed to load embedding models:', error); + // Fallback to common embedding models including Nomic + setAvailableEmbeddingModels([ + "text-embedding-3-large", + "text-embedding-3-small", + "text-embedding-ada-002", + "nomic-embed-text", ]); + setEmbeddingModelsLoaded(true); } }; @@ -268,6 +316,8 @@ const AdminConsole: React.FC = () => { if (configData.rag_content_scanning === undefined) { configData.rag_content_scanning = false; } + console.log('Loaded config:', configData); + console.log('Loaded model:', configData.openai_model); setConfig(configData); } catch (error) { console.error('Failed to load config:', error); @@ -297,6 +347,7 @@ const AdminConsole: React.FC = () => { rag_content_scanning: ragContentScanning, rag_lakera_project_id: updates.rag_lakera_project_id ?? config.rag_lakera_project_id, openai_model: updates.openai_model ?? config.openai_model, + embeddings_model: updates.embeddings_model ?? config.embeddings_model, temperature: updates.temperature ?? config.temperature, system_prompt: updates.system_prompt ?? config.system_prompt, openai_api_key: updates.openai_api_key ?? config.openai_api_key, @@ -312,14 +363,106 @@ const AdminConsole: React.FC = () => { await apiService.updateConfig(updatedConfig); await loadConfig(); - await loadModels(); setMessage({ type: 'success', text: 'Configuration updated successfully' }); } catch (error) { console.error('Failed to update config:', error); setMessage({ type: 'error', text: 'Failed to update configuration' }); + await loadConfig(); // Reload to revert to last saved state on error } }; + const handleModelChange = (newModel: string) => { + console.log('Model changed to:', newModel); + + // Optimistic update - immediately update UI without reloading + const updatedConfig: AppConfigUpdate = { + business_name: config.business_name, + tagline: config.tagline, + hero_text: config.hero_text, + hero_image_url: config.hero_image_url, + logo_url: config.logo_url, + theme: config.theme, + lakera_enabled: config.lakera_enabled, + lakera_blocking_mode: config.lakera_blocking_mode, + rag_content_scanning: config.rag_content_scanning, + rag_lakera_project_id: config.rag_lakera_project_id, + openai_model: newModel, + embeddings_model: config.embeddings_model, + temperature: config.temperature, + system_prompt: config.system_prompt, + openai_api_key: config.openai_api_key, + litellm_virtual_key: config.litellm_virtual_key, + litellm_guardrail_name: config.litellm_guardrail_name, + litellm_guardrail_monitor_name: config.litellm_guardrail_monitor_name, + lakera_api_key: config.lakera_api_key, + lakera_project_id: config.lakera_project_id, + use_litellm: config.use_litellm, + litellm_base_url: config.litellm_base_url, + }; + + console.log('Sending to backend:', updatedConfig); + + // Update UI immediately + setConfig({ ...config, openai_model: newModel }); + + // Save to backend without reloading + apiService.updateConfig(updatedConfig) + .then(() => { + console.log('Model saved successfully'); + setMessage({ type: 'success', text: 'Model updated' }); + }) + .catch((error) => { + console.error('Failed to update model:', error); + setMessage({ type: 'error', text: 'Failed to update model' }); + loadConfig(); // Revert on error + }); + }; + + const handleEmbeddingModelChange = (newModel: string) => { + console.log('Embedding model changed to:', newModel); + + // Optimistic update + const updatedConfig: AppConfigUpdate = { + business_name: config.business_name, + tagline: config.tagline, + hero_text: config.hero_text, + hero_image_url: config.hero_image_url, + logo_url: config.logo_url, + theme: config.theme, + lakera_enabled: config.lakera_enabled, + lakera_blocking_mode: config.lakera_blocking_mode, + rag_content_scanning: config.rag_content_scanning, + rag_lakera_project_id: config.rag_lakera_project_id, + openai_model: config.openai_model, + embeddings_model: newModel, + temperature: config.temperature, + system_prompt: config.system_prompt, + openai_api_key: config.openai_api_key, + litellm_virtual_key: config.litellm_virtual_key, + litellm_guardrail_name: config.litellm_guardrail_name, + litellm_guardrail_monitor_name: config.litellm_guardrail_monitor_name, + lakera_api_key: config.lakera_api_key, + lakera_project_id: config.lakera_project_id, + use_litellm: config.use_litellm, + litellm_base_url: config.litellm_base_url, + }; + + // Update UI immediately + setConfig({ ...config, embeddings_model: newModel }); + + // Save to backend + apiService.updateConfig(updatedConfig) + .then(() => { + console.log('Embedding model saved successfully'); + setMessage({ type: 'success', text: 'Embedding model updated' }); + }) + .catch((error) => { + console.error('Failed to update embedding model:', error); + setMessage({ type: 'error', text: 'Failed to update embedding model' }); + loadConfig(); // Revert on error + }); + }; + const handleExport = async () => { setIsExporting(true); setMessage(null); @@ -417,7 +560,10 @@ const AdminConsole: React.FC = () => { Back to Demo -

Admin Console

+
+

Admin Console

+

v{packageJson.version}

+
@@ -693,11 +839,6 @@ const AdminConsole: React.FC = () => { {activeTab === 'llm' && (

LLM Configuration

- {config.openai_model && availableModels.length > 0 && !availableModels.includes(config.openai_model) && ( -
- Current model may not be available for this key. Select a model below and save. -
- )}
+ {availableModels.length === 0 && ( +

Models are loading. Make sure you've entered a valid API key in Security settings.

+ )} +
+
+ + + {!embeddingModelsLoaded ? ( +

Embeddings models are loading...

+ ) : availableEmbeddingModels.length === 0 ? ( +

No available embedding models. Check your API key or LiteLLM connection.

+ ) : null}