From b6c4e2c395bae2cdd4bdcc25c94b34b97b64baf9 Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Sun, 11 Oct 2026 04:09:52 +0000 Subject: [PATCH 01/16] feat(cloud): enforce signed workspace subscription resource limits --- .../pkg/api/http/controller/groups/plugins.py | 4 +- .../api/http/controller/groups/workspaces.py | 6 +++ src/langbot/pkg/api/http/service/bot.py | 4 +- src/langbot/pkg/api/http/service/knowledge.py | 6 +-- src/langbot/pkg/api/http/service/mcp.py | 4 +- src/langbot/pkg/api/http/service/pipeline.py | 8 +++- .../pkg/api/http/service/workspace_limits.py | 20 +++++++++ tests/integration/api/test_workspaces.py | 2 + .../test_workspace_subscription_limits.py | 41 +++++++++++++++++++ .../useWorkspaceQuotaStatus.ts | 14 ++++--- web/src/app/infra/entities/workspace.ts | 1 + 11 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 src/langbot/pkg/api/http/service/workspace_limits.py create mode 100644 tests/unit_tests/test_workspace_subscription_limits.py diff --git a/src/langbot/pkg/api/http/controller/groups/plugins.py b/src/langbot/pkg/api/http/controller/groups/plugins.py index 950a8954d..eb8b09592 100644 --- a/src/langbot/pkg/api/http/controller/groups/plugins.py +++ b/src/langbot/pkg/api/http/controller/groups/plugins.py @@ -296,7 +296,9 @@ class PluginsRouterGroup(group.RouterGroup): async def _check_extensions_limit(self, request_context: RequestContext) -> str | None: """Check if extensions limit is reached. Returns error response if limit exceeded, None otherwise.""" await self.ap.plugin_connector.require_workspace_context(request_context) - limitation = self.ap.instance_config.data.get('system', {}).get('limitation', {}) + from ...service.workspace_limits import workspace_limits + + limitation = await workspace_limits(self.ap, request_context.workspace_uuid) max_extensions = limitation.get('max_extensions', -1) if max_extensions >= 0: plugins = await self.ap.plugin_connector.list_plugins() diff --git a/src/langbot/pkg/api/http/controller/groups/workspaces.py b/src/langbot/pkg/api/http/controller/groups/workspaces.py index 7873e9f17..39dc9c577 100644 --- a/src/langbot/pkg/api/http/controller/groups/workspaces.py +++ b/src/langbot/pkg/api/http/controller/groups/workspaces.py @@ -129,6 +129,7 @@ class WorkspacesRouterGroup(group.RouterGroup): membership = quart.g.workspace_membership workspace = await self.ap.workspace_service.get_workspace(request_context.workspace_uuid) plan_name: str | None = None + limitation = None resolver = getattr(self.ap, 'entitlement_resolver', None) if workspace.source == WorkspaceSource.CLOUD_PROJECTION.value and resolver is not None: entitlement = await resolver.resolve( @@ -136,6 +137,9 @@ class WorkspacesRouterGroup(group.RouterGroup): minimum_revision=request_context.entitlement_revision, ) plan_name = entitlement.plan_name + from ...service.workspace_limits import LIMIT_KEYS + + limitation = {key: entitlement.limit(value) for key, value in LIMIT_KEYS.items()} if request_context.principal.principal_type == PrincipalType.SUPPORT_ADMIN: return self.success( data={ @@ -154,6 +158,7 @@ class WorkspacesRouterGroup(group.RouterGroup): 'permissions': sorted(request_context.workspace.permissions), 'placement_generation': request_context.placement_generation, 'plan_name': plan_name, + 'limitation': limitation, } ) account = await self.ap.user_service.get_user_by_uuid(request_context.account_uuid) @@ -170,6 +175,7 @@ class WorkspacesRouterGroup(group.RouterGroup): 'permissions': sorted(request_context.workspace.permissions), 'placement_generation': request_context.placement_generation, 'plan_name': plan_name, + 'limitation': limitation, } ) diff --git a/src/langbot/pkg/api/http/service/bot.py b/src/langbot/pkg/api/http/service/bot.py index 59ab6fe58..981c7cf50 100644 --- a/src/langbot/pkg/api/http/service/bot.py +++ b/src/langbot/pkg/api/http/service/bot.py @@ -685,7 +685,9 @@ class BotService: """Create bot""" workspace_uuid = require_workspace_uuid(context) # Check limitation - limitation = self.ap.instance_config.data.get('system', {}).get('limitation', {}) + from .workspace_limits import workspace_limits + + limitation = await workspace_limits(self.ap, workspace_uuid) max_bots = limitation.get('max_bots', -1) if max_bots >= 0: existing_bots = await self.get_bots(context) diff --git a/src/langbot/pkg/api/http/service/knowledge.py b/src/langbot/pkg/api/http/service/knowledge.py index 654bf2f17..5897aa578 100644 --- a/src/langbot/pkg/api/http/service/knowledge.py +++ b/src/langbot/pkg/api/http/service/knowledge.py @@ -56,9 +56,9 @@ class KnowledgeService: require_workspace_uuid(context) # In new architecture, we delegate entirely to RAGManager which uses plugins. # Legacy internal KB creation is removed. - limitation = ( - getattr(getattr(self.ap, 'instance_config', None), 'data', {}).get('system', {}).get('limitation', {}) - ) + from .workspace_limits import workspace_limits + + limitation = await workspace_limits(self.ap, require_workspace_uuid(context)) max_knowledge_bases = limitation.get('max_knowledge_bases', -1) if max_knowledge_bases >= 0: knowledge_bases = await self.ap.rag_mgr.get_all_knowledge_base_details(context) diff --git a/src/langbot/pkg/api/http/service/mcp.py b/src/langbot/pkg/api/http/service/mcp.py index 67e8e14ae..89a78f79a 100644 --- a/src/langbot/pkg/api/http/service/mcp.py +++ b/src/langbot/pkg/api/http/service/mcp.py @@ -212,7 +212,9 @@ class MCPService: # stdio MCP even though Box Runtime itself remains available. require_stdio_mcp_enabled(self.ap, server_data) - limitation = self.ap.instance_config.data.get('system', {}).get('limitation', {}) + from .workspace_limits import workspace_limits + + limitation = await workspace_limits(self.ap, workspace_uuid) max_extensions = limitation.get('max_extensions', -1) if max_extensions >= 0: mcp_count_result = await self.ap.persistence_mgr.execute_async( diff --git a/src/langbot/pkg/api/http/service/pipeline.py b/src/langbot/pkg/api/http/service/pipeline.py index bdd24f3d7..22a1c86c0 100644 --- a/src/langbot/pkg/api/http/service/pipeline.py +++ b/src/langbot/pkg/api/http/service/pipeline.py @@ -173,7 +173,9 @@ class PipelineService: if 'config' in pipeline_data: RunnerConfigResolver.validate_pipeline_config(pipeline_data['config']) # Check limitation - limitation = self.ap.instance_config.data.get('system', {}).get('limitation', {}) + from .workspace_limits import workspace_limits + + limitation = await workspace_limits(self.ap, workspace_uuid) max_pipelines = limitation.get('max_pipelines', -1) if max_pipelines >= 0: existing_pipelines = await self.get_pipelines(context) @@ -290,7 +292,9 @@ class PipelineService: """Copy a pipeline with all its configurations""" workspace_uuid = require_workspace_uuid(context) # Check limitation - limitation = self.ap.instance_config.data.get('system', {}).get('limitation', {}) + from .workspace_limits import workspace_limits + + limitation = await workspace_limits(self.ap, workspace_uuid) max_pipelines = limitation.get('max_pipelines', -1) if max_pipelines >= 0: existing_pipelines = await self.get_pipelines(context) diff --git a/src/langbot/pkg/api/http/service/workspace_limits.py b/src/langbot/pkg/api/http/service/workspace_limits.py new file mode 100644 index 000000000..dc41a2a2b --- /dev/null +++ b/src/langbot/pkg/api/http/service/workspace_limits.py @@ -0,0 +1,20 @@ +"""Resolve resource limits at the Workspace service boundary.""" + +from ....cloud.entitlements import EntitlementUnavailableError + +LIMIT_KEYS = { + 'max_bots': 'bots.max', + 'max_pipelines': 'pipelines.max', + 'max_extensions': 'plugins.max', + 'max_knowledge_bases': 'knowledge_bases.max', +} + + +async def workspace_limits(ap, workspace_uuid: str, *, minimum_revision: int = 0) -> dict[str, int]: + if getattr(getattr(ap, 'deployment', None), 'mode', None) != 'cloud': + return dict(ap.instance_config.data.get('system', {}).get('limitation', {})) + resolver = getattr(ap, 'entitlement_resolver', None) + if resolver is None: + raise EntitlementUnavailableError('Workspace entitlement resolver is unavailable') + snapshot = await resolver.resolve(workspace_uuid, minimum_revision=minimum_revision) + return {config_key: snapshot.limit(limit_key) for config_key, limit_key in LIMIT_KEYS.items()} diff --git a/tests/integration/api/test_workspaces.py b/tests/integration/api/test_workspaces.py index a561cd44c..7a6254d01 100644 --- a/tests/integration/api/test_workspaces.py +++ b/tests/integration/api/test_workspaces.py @@ -824,6 +824,7 @@ async def test_cloud_projection_is_selected_explicitly_and_collaboration_runs_in not_before=1, expires_at=4102444800, plan_name='free', + limits={'bots.max': 2, 'pipelines.max': 3, 'plugins.max': 4, 'knowledge_bases.max': 5}, ) application.entitlement_resolver = PlanResolver() @@ -843,6 +844,7 @@ async def test_cloud_projection_is_selected_explicitly_and_collaboration_runs_in assert current['workspace']['source'] == 'cloud_projection' assert current['placement_generation'] == 12 assert current['plan_name'] == 'free' + assert current['limitation']['max_bots'] == 2 create_workspace = await client.post( '/api/v1/workspaces', diff --git a/tests/unit_tests/test_workspace_subscription_limits.py b/tests/unit_tests/test_workspace_subscription_limits.py new file mode 100644 index 000000000..c16604ed9 --- /dev/null +++ b/tests/unit_tests/test_workspace_subscription_limits.py @@ -0,0 +1,41 @@ +import asyncio +from types import SimpleNamespace + +import pytest + +from langbot.pkg.cloud.entitlements import EntitlementSnapshot, EntitlementUnavailableError +from langbot.pkg.api.http.service.workspace_limits import workspace_limits + + +def test_oss_retains_instance_limits(): + ap = SimpleNamespace(instance_config=SimpleNamespace(data={'system': {'limitation': {'max_bots': 7}}})) + assert asyncio.run(workspace_limits(ap, 'one')) == {'max_bots': 7} + + +def test_cloud_uses_signed_workspace_limits(): + class Resolver: + async def resolve(self, workspace_uuid, **kwargs): + assert workspace_uuid == 'one' + return EntitlementSnapshot( + instance_uuid='i', + workspace_uuid='one', + entitlement_revision=1, + status='active', + not_before=0, + expires_at=9999999999, + limits={'bots.max': 2, 'pipelines.max': 3, 'plugins.max': 4, 'knowledge_bases.max': 5}, + ) + + ap = SimpleNamespace(deployment=SimpleNamespace(mode='cloud'), entitlement_resolver=Resolver()) + assert asyncio.run(workspace_limits(ap, 'one')) == { + 'max_bots': 2, + 'max_pipelines': 3, + 'max_extensions': 4, + 'max_knowledge_bases': 5, + } + + +def test_cloud_missing_resolver_fails_closed(): + ap = SimpleNamespace(deployment=SimpleNamespace(mode='cloud')) + with pytest.raises(EntitlementUnavailableError): + asyncio.run(workspace_limits(ap, 'one')) diff --git a/web/src/app/home/components/workspace-quota/useWorkspaceQuotaStatus.ts b/web/src/app/home/components/workspace-quota/useWorkspaceQuotaStatus.ts index 4788b7477..8786caf39 100644 --- a/web/src/app/home/components/workspace-quota/useWorkspaceQuotaStatus.ts +++ b/web/src/app/home/components/workspace-quota/useWorkspaceQuotaStatus.ts @@ -1,3 +1,4 @@ +import { useCurrentWorkspace } from '@/app/infra/http'; import { systemInfo } from '@/app/infra/http/HttpClient'; import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext'; @@ -46,27 +47,30 @@ export function useWorkspaceQuotaStatus(): WorkspaceQuotaStatus { skills, quotaDataLoaded, } = useSidebarData(); - const limitation = systemInfo.limitation; + const currentWorkspace = useCurrentWorkspace(); + const cloud = currentWorkspace?.workspace.source === 'cloud_projection'; + const limitation = cloud ? currentWorkspace.limitation : systemInfo.limitation; + const loaded = quotaDataLoaded && (!cloud || limitation != null); const botQuota = quotaItem( bots.length, limitation?.max_bots, - quotaDataLoaded, + loaded, ); const pipelineQuota = quotaItem( pipelines.length, limitation?.max_pipelines, - quotaDataLoaded, + loaded, ); const knowledgeBaseQuota = quotaItem( knowledgeBases.length, limitation?.max_knowledge_bases, - quotaDataLoaded, + loaded, ); const extensionQuota = quotaItem( pluginCount + mcpServers.length + skills.length, limitation?.max_extensions, - quotaDataLoaded, + loaded, ); return { diff --git a/web/src/app/infra/entities/workspace.ts b/web/src/app/infra/entities/workspace.ts index c369961a2..8ce39485e 100644 --- a/web/src/app/infra/entities/workspace.ts +++ b/web/src/app/infra/entities/workspace.ts @@ -34,6 +34,7 @@ export interface CurrentWorkspace { placement_generation: number; /** Signed Cloud display metadata; never used for client-side authorization. */ plan_name?: string | null; + limitation?: Record | null; } export interface WorkspaceSpaceBilling { From b1d94848767f1cff54299ef3457d935bdf822f93 Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Sun, 11 Oct 2026 04:12:39 +0000 Subject: [PATCH 02/16] style: format workspace quota hook --- .../workspace-quota/useWorkspaceQuotaStatus.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/web/src/app/home/components/workspace-quota/useWorkspaceQuotaStatus.ts b/web/src/app/home/components/workspace-quota/useWorkspaceQuotaStatus.ts index 8786caf39..94d021b4d 100644 --- a/web/src/app/home/components/workspace-quota/useWorkspaceQuotaStatus.ts +++ b/web/src/app/home/components/workspace-quota/useWorkspaceQuotaStatus.ts @@ -49,14 +49,12 @@ export function useWorkspaceQuotaStatus(): WorkspaceQuotaStatus { } = useSidebarData(); const currentWorkspace = useCurrentWorkspace(); const cloud = currentWorkspace?.workspace.source === 'cloud_projection'; - const limitation = cloud ? currentWorkspace.limitation : systemInfo.limitation; + const limitation = cloud + ? currentWorkspace.limitation + : systemInfo.limitation; const loaded = quotaDataLoaded && (!cloud || limitation != null); - const botQuota = quotaItem( - bots.length, - limitation?.max_bots, - loaded, - ); + const botQuota = quotaItem(bots.length, limitation?.max_bots, loaded); const pipelineQuota = quotaItem( pipelines.length, limitation?.max_pipelines, From e29fd27f02212e073f581ac9f626fd11809e27d0 Mon Sep 17 00:00:00 2001 From: Hyu Date: Mon, 12 Oct 2026 12:22:03 +0800 Subject: [PATCH 03/16] feat(i18n): localize backend messages by workspace language --- AGENTS.md | 13 +- docs/architecture/backend-i18n.md | 68 ++++++ pyproject.toml | 2 +- skills/skills/langbot-mcp-ops/SKILL.md | 12 ++ src/langbot/pkg/api/http/controller/group.py | 36 +++- .../controller/groups/agent_debug_stream.py | 12 +- .../http/controller/groups/pipelines/embed.py | 21 +- .../groups/pipelines/websocket_chat.py | 14 +- .../controller/groups/platform/adapters.py | 10 +- .../pkg/api/http/controller/groups/plugins.py | 8 +- .../api/http/controller/groups/workspaces.py | 30 +++ src/langbot/pkg/api/http/service/agent.py | 6 +- src/langbot/pkg/api/http/service/knowledge.py | 10 +- src/langbot/pkg/api/http/service/mcp.py | 10 +- src/langbot/pkg/api/http/service/model.py | 10 +- src/langbot/pkg/api/http/service/user.py | 6 +- src/langbot/pkg/api/mcp/server.py | 24 ++- src/langbot/pkg/core/taskmgr.py | 6 +- src/langbot/pkg/i18n/__init__.py | 197 ++++++++++++++++++ src/langbot/pkg/i18n/adapters.py | 31 +++ src/langbot/pkg/i18n/locales/en_US.json | 114 ++++++++++ src/langbot/pkg/i18n/locales/es_ES.json | 114 ++++++++++ src/langbot/pkg/i18n/locales/ja_JP.json | 114 ++++++++++ src/langbot/pkg/i18n/locales/ru_RU.json | 114 ++++++++++ src/langbot/pkg/i18n/locales/th_TH.json | 114 ++++++++++ src/langbot/pkg/i18n/locales/vi_VN.json | 114 ++++++++++ src/langbot/pkg/i18n/locales/zh_Hans.json | 114 ++++++++++ src/langbot/pkg/i18n/locales/zh_Hant.json | 114 ++++++++++ src/langbot/pkg/i18n/workspace.py | 71 +++++++ .../cntfilter/filters/baiduexamine.py | 8 +- .../pipeline/cntfilter/filters/banwords.py | 12 +- src/langbot/pkg/pipeline/pipelinemgr.py | 7 + .../pkg/pipeline/process/handlers/chat.py | 18 +- .../pkg/pipeline/ratelimit/ratelimit.py | 4 +- src/langbot/pkg/pipeline/wrapper/wrapper.py | 6 +- .../pkg/platform/adapters/lark/adapter.py | 21 +- .../pkg/platform/adapters/lark/interaction.py | 7 +- .../pkg/platform/adapters/lark/stream_card.py | 10 +- src/langbot/pkg/platform/botmgr.py | 32 +-- src/langbot/pkg/platform/sources/discord.py | 32 ++- src/langbot/pkg/platform/sources/lark.py | 64 ++++-- src/langbot/pkg/platform/sources/matrix.py | 25 ++- .../pkg/provider/modelmgr/codex_auth.py | 41 ++-- .../pkg/provider/modelmgr/codex_errors.py | 4 +- src/langbot/pkg/provider/modelmgr/errors.py | 5 +- .../modelmgr/requesters/litellmchat.py | 24 ++- src/langbot/pkg/provider/tools/loaders/mcp.py | 4 +- .../templates/default-pipeline-config.json | 2 +- .../templates/metadata/pipeline/output.yaml | 76 ++++++- tests/integration/api/test_workspaces.py | 61 ++++++ .../api/http/test_route_tenant_scope.py | 5 +- .../api/service/test_provider_cascade.py | 3 +- .../api/service/test_user_service.py | 5 +- .../api/test_extensions_runtime_fence.py | 4 + tests/unit_tests/api/test_mcp_server.py | 12 ++ tests/unit_tests/pipeline/conftest.py | 6 + tests/unit_tests/pipeline/test_banwords.py | 4 +- .../platform/test_lark_eba_adapter.py | 6 +- tests/unit_tests/provider/test_litellmchat.py | 14 +- tests/unit_tests/test_backend_i18n.py | 107 ++++++++++ .../WorkspaceSettingsPanel.tsx | 69 +++++- web/src/app/infra/http/BackendClient.ts | 11 + web/src/app/infra/http/BaseHttpClient.ts | 1 + web/src/i18n/locales/en-US.ts | 7 + web/src/i18n/locales/es-ES.ts | 7 + web/src/i18n/locales/ja-JP.ts | 7 + web/src/i18n/locales/ru-RU.ts | 7 + web/src/i18n/locales/th-TH.ts | 7 + web/src/i18n/locales/vi-VN.ts | 7 + web/src/i18n/locales/zh-Hans.ts | 6 + web/src/i18n/locales/zh-Hant.ts | 6 + web/tests/e2e/fixtures/langbot-api.ts | 16 ++ .../fixtures/runner-migration-contract.json | 104 ++++++++- web/tests/e2e/workspace-language.spec.ts | 80 +++++++ 74 files changed, 2296 insertions(+), 177 deletions(-) create mode 100644 docs/architecture/backend-i18n.md create mode 100644 src/langbot/pkg/i18n/__init__.py create mode 100644 src/langbot/pkg/i18n/adapters.py create mode 100644 src/langbot/pkg/i18n/locales/en_US.json create mode 100644 src/langbot/pkg/i18n/locales/es_ES.json create mode 100644 src/langbot/pkg/i18n/locales/ja_JP.json create mode 100644 src/langbot/pkg/i18n/locales/ru_RU.json create mode 100644 src/langbot/pkg/i18n/locales/th_TH.json create mode 100644 src/langbot/pkg/i18n/locales/vi_VN.json create mode 100644 src/langbot/pkg/i18n/locales/zh_Hans.json create mode 100644 src/langbot/pkg/i18n/locales/zh_Hant.json create mode 100644 src/langbot/pkg/i18n/workspace.py create mode 100644 tests/unit_tests/test_backend_i18n.py create mode 100644 web/tests/e2e/workspace-language.spec.ts diff --git a/AGENTS.md b/AGENTS.md index 4fb546042..bc016a26e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -87,11 +87,22 @@ Config keys to verify in `data/config.yaml` / `src/langbot/templates/config.yaml - When changing Service API routes or capabilities used by `lbctl`, check compatibility with the separate `langbot-cli` repository. - New schema changes use Alembic under `src/langbot/pkg/persistence/alembic/versions/`. LangBot 4.x does not support upgrading 3.x databases. - New platform behavior belongs in platform adapters only for platform translation; pipeline/business logic belongs in `pkg/pipeline/` or services. -- User-facing strings must support i18n (`en_US`, `zh_Hans`; include `ja_JP` where the repo already does). +- User-facing strings must support i18n; follow the backend language rules below for server-generated content. - Code comments and docstrings must be English. - Keep compatibility and security in mind; LangBot is used in both self-hosted/community and toB deployments. - Commit message format: `(): `. +## Backend Message Language + +- LangBot-owned business errors, runtime notices, and default messages sent to users must use the owning Workspace's message language. Keep this independent of each member's WebUI language; an unset Workspace language defaults to `en_US`. +- Reuse `src/langbot/pkg/i18n/` and the existing `message_language` entry in `WorkspaceMetadata`. Use `t()` with stable English templates and named parameters; add matching keys and placeholders to all eight catalogs: `en_US`, `zh_Hans`, `zh_Hant`, `ja_JP`, `es_ES`, `ru_RU`, `th_TH`, and `vi_VN`. Avoid new translation frameworks or duplicate language settings. +- Resolve language from a trusted Workspace or bot execution context, and isolate it across concurrent requests, tasks, and adapter callbacks. Browser language hints may supply account-only or unauthenticated response language, but must not override a resolved Workspace or authorize tenant selection. Reading/changing the setting requires `workspace.view` / `workspace.update`. +- Pipeline notices should be configurable in Pipeline output settings through `pipeline_notice()`. Blank overrides inherit the Workspace translation. Preserve user-authored text literally except for documented placeholders, and retain existing visibility switches. Do not rewrite historical messages or custom text when the language changes. +- China-only adapters and configuration/field validation diagnostics do not require localization. An adapter also serving international users is not exempt merely because it supports a Chinese platform. +- Preserve raw provider/plugin diagnostics, tracebacks, protocol values, and machine-readable error/status codes. Translate LangBot's own category or explanation around them; do not translate arbitrary errors with substring replacement. Plugin-authored messages remain the plugin's localization responsibility. +- Keep changes small and maintainable. Reuse existing persistence and response boundaries; delete obsolete fields only after verifying they have no remaining readers, including compatibility paths. +- When changing language behavior, verify catalog/placeholder parity, Workspace isolation and permissions, and Pipeline inheritance/custom overrides. Update affected configuration snapshots and API/MCP documentation. Implementation details and focused test locations: [Backend message language](docs/architecture/backend-i18n.md). + ## Runtime Pitfalls - Local stdio Plugin Runtime disconnects do not auto-reconnect; restart LangBot if that path breaks. diff --git a/docs/architecture/backend-i18n.md b/docs/architecture/backend-i18n.md new file mode 100644 index 000000000..bf5a4ba35 --- /dev/null +++ b/docs/architecture/backend-i18n.md @@ -0,0 +1,68 @@ +# Backend message language + +Workspace settings expose one **Workspace language**, independent of each member's +WebUI language. It selects LangBot-owned API business errors, model error prefixes, +Pipeline default notices and international platform replies. Supported locales are +`en_US`, `zh_Hans`, `zh_Hant`, `ja_JP`, `es_ES`, `ru_RU`, `th_TH` and `vi_VN`. +Workspaces without a setting default to English. Existing custom messages and +historical records are not rewritten. + +## Storage and boundaries + +`pkg/i18n/workspace.py` stores `message_language` in existing `WorkspaceMetadata`; +no database migration or instance-wide configuration is needed. Reads and writes +use tenant-scoped units of work. GET/PUT `/api/v1/workspaces/current/language` +require `workspace.view` / `workspace.update`; matching MCP tools are +`get_workspace_message_language` and `set_workspace_message_language`. + +An authenticated Workspace takes precedence over the browser's +`X-LangBot-Language` header. The header supplies presentation language only for +account-only or unauthenticated responses and never selects a tenant. HTTP, MCP, +Pipeline and adapter boundaries set a ContextVar so concurrent Workspaces do not +share a language. Adapter SDK thread callbacks use their owning bot's cached +Workspace language, never the calling request's language. + +The per-application cache holds at most 256 Workspaces for 30 seconds. Local +writes update the cache immediately; other processes refresh at the next async +message/request boundary after expiry. A running operation keeps its selected +language. Synchronous SDK callbacks use the most recently loaded value. + +## Pipeline overrides + +The output `misc` section accepts these optional string settings: + +| Setting | Default purpose | Placeholder | +| --- | --- | --- | +| `failure-hint` | Request/runner execution failure | none | +| `rate-limit-hint` | Rate limit reached | none | +| `content-filter-hint` | Message rejected by content filter | none | +| `content-filter-error-hint` | Content check failed | none | +| `tool-call-hint` | Tool call notification | `{tools}` | +| `runner-not-found-hint` | Configured runner missing | `{runner}` | +| `runner-not-authorized-hint` | Runner authorization failure | none | +| `runner-unavailable-hint` | Runner temporarily unavailable | none | + +Blank values inherit the Workspace translation. Custom text is emitted literally +with only the documented placeholder replaced. The previous shipped +`failure-hint` value `Request failed.` also inherits language; this avoids a data +migration. Existing output switches (including tool-call visibility and exception +handling) retain their behavior. + +## Adding messages + +Use `pkg.i18n.t()` with a stable English template and named parameters. Add the +same key and placeholders to all eight JSON catalogs. The catalog parity test +checks completeness and formatting. `pipeline_notice()` handles configurable +notices. Exact aliases at the API response boundary cover legacy business wording; +do not add substring replacement of arbitrary error text. + +China-only adapters, field validation diagnostics, raw provider/plugin errors, +tracebacks, protocol values and machine-readable status codes are outside this +translation layer. Their values remain useful for diagnostics and interoperability. +Model errors translate LangBot's category/prefix while retaining upstream detail. +Plugin-authored content must use the plugin's own localization. System prompts and +user-authored content are not translated. + +Locale JSON files are included in Python package data. Focused coverage lives in +`tests/unit_tests/test_backend_i18n.py`, the Workspace HTTP integration tests and +`web/tests/e2e/workspace-language.spec.ts`. diff --git a/pyproject.toml b/pyproject.toml index f4e887645..3da5b3e6c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -129,7 +129,7 @@ requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" [tool.setuptools] -package-data = { "langbot" = ["templates/**", "pkg/provider/modelmgr/requesters/*", "pkg/platform/sources/*", "pkg/platform/adapters/**", "web/dist/**", "pkg/persistence/alembic/**"] } +package-data = { "langbot" = ["templates/**", "pkg/i18n/locales/*.json", "pkg/provider/modelmgr/requesters/*", "pkg/platform/sources/*", "pkg/platform/adapters/**", "web/dist/**", "pkg/persistence/alembic/**"] } [dependency-groups] dev = [ diff --git a/skills/skills/langbot-mcp-ops/SKILL.md b/skills/skills/langbot-mcp-ops/SKILL.md index edea7b762..c0c8cedbe 100644 --- a/skills/skills/langbot-mcp-ops/SKILL.md +++ b/skills/skills/langbot-mcp-ops/SKILL.md @@ -212,6 +212,18 @@ Monitoring record filters accept `mode` (`all`, `real`, `debug`) and execution, not the individual model/tool call outcome. Calls without a recorded execution link are excluded when an execution filter is active. +### Workspace message language + +`get_workspace_message_language` reads the current Workspace's backend language +(`workspace.view`). `set_workspace_message_language(language)` requires +`workspace.update`; supported values are `en_US`, `zh_Hans`, `zh_Hant`, `ja_JP`, +`es_ES`, `ru_RU`, `th_TH`, `vi_VN`. HTTP equivalents are GET/PUT +`/api/v1/workspaces/current/language` (PUT body: `{"language":"ja_JP"}`). +The API key's Workspace remains authoritative. The setting controls new backend +messages and default bot notices, independently of personal UI language. +Pipeline `output.misc` hint fields override defaults; empty values inherit this +setting. Existing custom text and historical logs are not rewritten. + ### Workspace default LLM model `get_starred_model` reads the Workspace-wide favorite. `set_starred_model` diff --git a/src/langbot/pkg/api/http/controller/group.py b/src/langbot/pkg/api/http/controller/group.py index bb9fcc048..a290c25c1 100644 --- a/src/langbot/pkg/api/http/controller/group.py +++ b/src/langbot/pkg/api/http/controller/group.py @@ -17,6 +17,8 @@ from ....workspace.errors import WorkspaceNotFoundError from ....cloud.entitlements import EntitlementUnavailableError from ....core.errors import TaskCapacityError from ....provider.modelmgr.codex_errors import CodexProviderError +from ....i18n import DEFAULT_LANGUAGE, language, language_scope, t, error_text +from ....i18n.workspace import get_workspace_language from ..authz import ( AuthenticationDeniedError, AuthorizationError, @@ -137,7 +139,7 @@ class RouterGroup(abc.ABC): # receive RequestContext or enforce Workspace permissions. self._inject_handler_context(f, kwargs, user_email, None, account) except Exception as e: - return self._auth_error_response(e) + return await self._localized_auth_error_response(e) elif auth_type == AuthType.USER_TOKEN: # get token from Authorization header @@ -160,7 +162,7 @@ class RouterGroup(abc.ABC): require_permission(request_context, permission) self._inject_handler_context(f, kwargs, user_email, request_context) except Exception as e: - return self._auth_error_response(e) + return await self._localized_auth_error_response(e) elif auth_type == AuthType.API_KEY: # get API key from Authorization header or X-API-Key header @@ -179,7 +181,7 @@ class RouterGroup(abc.ABC): require_permission(request_context, permission) self._inject_handler_context(f, kwargs, None, request_context) except Exception as e: - return self._auth_error_response(e) + return await self._localized_auth_error_response(e) elif auth_type == AuthType.USER_TOKEN_OR_API_KEY: token = quart.request.headers.get('Authorization', '').replace('Bearer ', '') @@ -193,7 +195,7 @@ class RouterGroup(abc.ABC): require_permission(request_context, permission) self._inject_handler_context(f, kwargs, None, request_context) except Exception as e: - return self._auth_error_response(e) + return await self._localized_auth_error_response(e) # Try API key first (check X-API-Key header) elif api_key := quart.request.headers.get('X-API-Key', ''): # API key authentication @@ -203,7 +205,7 @@ class RouterGroup(abc.ABC): require_permission(request_context, permission) self._inject_handler_context(f, kwargs, None, request_context) except Exception as e: - return self._auth_error_response(e) + return await self._localized_auth_error_response(e) else: # Try user token authentication (Authorization header) if not token: @@ -223,7 +225,7 @@ class RouterGroup(abc.ABC): # Authentication succeeded and authorization was # evaluated. Do not reinterpret a denied user token # as an API key, which would mask the stable 403/404. - return self._auth_error_response(e) + return await self._localized_auth_error_response(e) except Exception: # If user token fails, maybe it's an API key in Authorization header try: @@ -232,10 +234,12 @@ class RouterGroup(abc.ABC): require_permission(request_context, permission) self._inject_handler_context(f, kwargs, None, request_context) except Exception as e: - return self._auth_error_response(e) + return await self._localized_auth_error_response(e) observed_at = time.monotonic() try: + if request_context is not None: + language.set(await get_workspace_language(self.ap, request_context.workspace_uuid)) if request_context is not None: with bounded_executor.blocking_work_scope(request_context.workspace_uuid): persistence_mgr = getattr( @@ -319,7 +323,11 @@ class RouterGroup(abc.ABC): ) return self.internal_error_response(request_id) - new_f = handler_error + async def new_f(*args, **kwargs): + # Reset even on errors; child tasks inherit this request's locale. + with language_scope(quart.request.headers.get('X-LangBot-Language', DEFAULT_LANGUAGE)): + return await handler_error(*args, **kwargs) + # Quart/Flask requires a unique endpoint name even when the same URL # intentionally has separate handlers for different HTTP methods. # Include the method set so CRUD routes can declare distinct @@ -687,6 +695,14 @@ class RouterGroup(abc.ABC): 'Invalid authentication credentials', ) + async def _localized_auth_error_response(self, error: Exception): + # A resolved context is trusted even when its permissions deny this + # operation. Never look up a language using the unverified selector. + context = getattr(quart.g, 'request_context', None) + if context is not None: + language.set(await get_workspace_language(self.ap, context.workspace_uuid)) + return self._auth_error_response(error) + def request_id(self) -> str: """Return one stable request ID for authentication, logs, and errors.""" @@ -707,7 +723,7 @@ class RouterGroup(abc.ABC): response = quart.jsonify( { 'code': 'internal_error', - 'msg': 'Internal server error', + 'msg': t('Internal server error'), 'request_id': resolved_request_id, } ) @@ -729,7 +745,7 @@ class RouterGroup(abc.ABC): return quart.jsonify( { 'code': code, - 'msg': msg, + 'msg': error_text(code, msg), } ) diff --git a/src/langbot/pkg/api/http/controller/groups/agent_debug_stream.py b/src/langbot/pkg/api/http/controller/groups/agent_debug_stream.py index bf3fc03bd..700eab509 100644 --- a/src/langbot/pkg/api/http/controller/groups/agent_debug_stream.py +++ b/src/langbot/pkg/api/http/controller/groups/agent_debug_stream.py @@ -2,6 +2,8 @@ from __future__ import annotations +from .....i18n import t + import asyncio import contextlib import json @@ -45,20 +47,20 @@ def debug_stream_response(service, context, agent_uuid: str, payload: dict) -> q if isinstance(exc, RunnerExecutionError): code, message = exc.error_code or 'runner_execution_failed', exc.message elif isinstance(exc, RunnerNotFoundError): - code, message = 'runner_not_found', 'The configured Agent runner is unavailable' + code, message = 'runner_not_found', t('Agent runner temporarily unavailable. Please try again.') elif isinstance(exc, RunnerNotAuthorizedError): - code, message = 'runner_not_authorized', 'The configured Agent runner is not authorized' + code, message = 'runner_not_authorized', t('Agent runner not authorized for this pipeline.') elif isinstance(exc, RunnerProtocolError): - code, message = 'runner_protocol_error', 'The Agent runner returned an invalid response' + code, message = 'runner_protocol_error', t('The Agent runner returned an invalid response.') elif isinstance(exc, ValueError): code, message = 'invalid_request', str(exc) elif isinstance(exc, RunnerError): - code, message = 'runner_error', 'The Agent runner could not complete this test' + code, message = 'runner_error', t('Agent runner execution failed.') else: logger = getattr(application, 'logger', None) if logger is not None: logger.exception('Agent debug stream execution failed') - code, message = 'runner_error', 'The Agent debug execution failed' + code, message = 'runner_error', t('Agent runner execution failed.') await queue.put({'kind': 'error', 'code': code, 'msg': message}) task = asyncio.create_task(execute()) diff --git a/src/langbot/pkg/api/http/controller/groups/pipelines/embed.py b/src/langbot/pkg/api/http/controller/groups/pipelines/embed.py index 88deea8e6..9f32a63d3 100644 --- a/src/langbot/pkg/api/http/controller/groups/pipelines/embed.py +++ b/src/langbot/pkg/api/http/controller/groups/pipelines/embed.py @@ -21,6 +21,8 @@ import httpx import quart from ... import group +from ......i18n import language, t +from ......i18n.workspace import get_workspace_language from ......utils import httpclient, paths from ......platform.sources.websocket_manager import WebSocketScope, is_valid_session_id, ws_connection_manager from .websocket_chat import create_scoped_duplex_tasks, wait_for_duplex_tasks @@ -73,6 +75,7 @@ class EmbedRouterGroup(group.RouterGroup): bot = await self.ap.platform_mgr.resolve_public_bot(bot_uuid) pipeline_uuid = bot.get_pipeline_target_for_event_type('message.received') if bot is not None else None if bot is not None and bot.bot_entity.adapter == 'web_page_bot' and bot.bot_entity.enable and pipeline_uuid: + language.set(await get_workspace_language(self.ap, bot.execution_context.workspace_uuid)) return bot, pipeline_uuid return None, None @@ -284,7 +287,7 @@ class EmbedRouterGroup(group.RouterGroup): return self.http_status(404, -1, 'WebSocket adapter not found') websocket_adapter.reset_session(pipeline_uuid, session_type, session_id) - return self.success(data={'message': 'Session reset successfully'}) + return self.success(data={'message': t('Session reset successfully')}) except Exception as e: logger.error(f'Failed to reset embed session: {e}', exc_info=True) @@ -338,7 +341,9 @@ class EmbedRouterGroup(group.RouterGroup): runtime_bot, pipeline_uuid = await self._resolve_bot(bot_uuid) if runtime_bot is None: - await quart.websocket.send(json.dumps({'type': 'error', 'message': 'Bot not found or not available'})) + await quart.websocket.send( + json.dumps({'type': 'error', 'message': t('Bot not found or not available')}) + ) return session_type = quart.websocket.args.get('session_type', 'person') @@ -357,13 +362,15 @@ class EmbedRouterGroup(group.RouterGroup): await self._authenticate_websocket(runtime_bot) await self._assert_execution_active(runtime_bot) except Exception: - await quart.websocket.send(json.dumps({'type': 'error', 'message': 'Unauthorized'})) + await quart.websocket.send(json.dumps({'type': 'error', 'message': t('Unauthorized')})) return try: proxy_bot = await self.ap.platform_mgr.get_websocket_proxy_bot(runtime_bot.execution_context) websocket_adapter = proxy_bot.adapter if not websocket_adapter: - await quart.websocket.send(json.dumps({'type': 'error', 'message': 'WebSocket adapter not found'})) + await quart.websocket.send( + json.dumps({'type': 'error', 'message': t('WebSocket adapter not found')}) + ) return connection = await ws_connection_manager.add_connection( @@ -428,7 +435,7 @@ class EmbedRouterGroup(group.RouterGroup): except Exception as e: logger.error(f'Embed WebSocket connection error: {e}', exc_info=True) try: - await quart.websocket.send(json.dumps({'type': 'error', 'message': 'Internal server error'})) + await quart.websocket.send(json.dumps({'type': 'error', 'message': t('Internal server error')})) except Exception: pass @@ -451,7 +458,7 @@ class EmbedRouterGroup(group.RouterGroup): try: current_bot = await self._resolve_connected_bot(owner_bot, pipeline_uuid) except Exception: - await connection.send_queue.put({'type': 'error', 'message': 'Bot is unavailable'}) + await connection.send_queue.put({'type': 'error', 'message': t('Bot is unavailable')}) break await websocket_adapter.handle_websocket_message(connection, data, owner_bot=current_bot) elif message_type == 'disconnect': @@ -460,7 +467,7 @@ class EmbedRouterGroup(group.RouterGroup): pass except json.JSONDecodeError: - await connection.send_queue.put({'type': 'error', 'message': 'Invalid JSON format'}) + await connection.send_queue.put({'type': 'error', 'message': t('Invalid JSON format')}) except Exception as e: logger.error(f'Embed receive error: {e}', exc_info=True) diff --git a/src/langbot/pkg/api/http/controller/groups/pipelines/websocket_chat.py b/src/langbot/pkg/api/http/controller/groups/pipelines/websocket_chat.py index 16406de90..649293e10 100644 --- a/src/langbot/pkg/api/http/controller/groups/pipelines/websocket_chat.py +++ b/src/langbot/pkg/api/http/controller/groups/pipelines/websocket_chat.py @@ -2,6 +2,9 @@ from __future__ import annotations +from ......i18n import language, t +from ......i18n.workspace import get_workspace_language + import asyncio import datetime import json @@ -217,8 +220,9 @@ class WebSocketChatRouterGroup(group.RouterGroup): try: request_context, token = await self._authenticate_websocket() except Exception: - await quart.websocket.send(json.dumps({'type': 'error', 'message': 'Unauthorized'})) + await quart.websocket.send(json.dumps({'type': 'error', 'message': t('Unauthorized')})) return + language.set(await get_workspace_language(self.ap, request_context.workspace_uuid)) session_type = quart.websocket.args.get('session_type', 'person') if session_type not in ['person', 'group']: await quart.websocket.send( @@ -229,7 +233,7 @@ class WebSocketChatRouterGroup(group.RouterGroup): try: websocket_adapter = await self._get_scoped_adapter(request_context, pipeline_uuid) if websocket_adapter is None: - await quart.websocket.send(json.dumps({'type': 'error', 'message': 'Pipeline not found'})) + await quart.websocket.send(json.dumps({'type': 'error', 'message': t('Pipeline not found')})) return connection = await ws_connection_manager.add_connection( @@ -294,7 +298,7 @@ class WebSocketChatRouterGroup(group.RouterGroup): except Exception: logger.error('Dashboard WebSocket connection error', exc_info=True) try: - await quart.websocket.send(json.dumps({'type': 'error', 'message': 'Internal server error'})) + await quart.websocket.send(json.dumps({'type': 'error', 'message': t('Internal server error')})) except Exception: pass @@ -419,7 +423,7 @@ class WebSocketChatRouterGroup(group.RouterGroup): try: request_context = await self._revalidate_websocket_authorization(request_context, token) except Exception: - await connection.send_queue.put({'type': 'error', 'message': 'Unauthorized'}) + await connection.send_queue.put({'type': 'error', 'message': t('Unauthorized')}) break await websocket_adapter.handle_websocket_message(connection, data) elif message_type == 'disconnect': @@ -427,7 +431,7 @@ class WebSocketChatRouterGroup(group.RouterGroup): else: logger.warning(f'Unknown WebSocket message type: {message_type}') except json.JSONDecodeError: - await connection.send_queue.put({'type': 'error', 'message': 'Invalid JSON format'}) + await connection.send_queue.put({'type': 'error', 'message': t('Invalid JSON format')}) except Exception: logger.error('Dashboard WebSocket receive error', exc_info=True) diff --git a/src/langbot/pkg/api/http/controller/groups/platform/adapters.py b/src/langbot/pkg/api/http/controller/groups/platform/adapters.py index 4715ceb95..759e5aa8a 100644 --- a/src/langbot/pkg/api/http/controller/groups/platform/adapters.py +++ b/src/langbot/pkg/api/http/controller/groups/platform/adapters.py @@ -1,3 +1,5 @@ +from langbot.pkg.i18n import t, DEFAULT_LANGUAGE +from langbot.pkg.i18n.workspace import get_workspace_language import asyncio import dataclasses import mimetypes @@ -213,16 +215,20 @@ class AdaptersRouterGroup(group.RouterGroup): @self.route('/slack/setup/callback', methods=['GET'], auth_type=group.AuthType.NONE) async def slack_callback(): + session = slack_setup.sessions.get(quart.request.args.get('state', '')) + locale = DEFAULT_LANGUAGE + if session is not None: + locale = await get_workspace_language(self.ap, session['context'].workspace_uuid) try: await slack_setup.finish( quart.request.args.get('state', ''), quart.request.args.get('code', ''), quart.request.args.get('error'), ) - message = 'Slack authorization completed. Return to LangBot to finish setup. / Slack 授权完成,请返回 LangBot 完成配置。' + message = t('Slack authorization completed. Return to LangBot to finish setup.', locale=locale) status = 200 except SlackSetupError: - message = 'Slack authorization failed or expired. Return to LangBot for details. / Slack 授权失败或已过期,请返回 LangBot 查看详情。' + message = t('Slack authorization failed or expired. Return to LangBot for details.', locale=locale) status = 400 return quart.Response( message, diff --git a/src/langbot/pkg/api/http/controller/groups/plugins.py b/src/langbot/pkg/api/http/controller/groups/plugins.py index eb8b09592..28024ca5d 100644 --- a/src/langbot/pkg/api/http/controller/groups/plugins.py +++ b/src/langbot/pkg/api/http/controller/groups/plugins.py @@ -1,5 +1,7 @@ from __future__ import annotations +from .....i18n import t + import asyncio import base64 import collections.abc @@ -305,7 +307,11 @@ class PluginsRouterGroup(group.RouterGroup): mcp_servers = await self.ap.mcp_service.get_mcp_servers(request_context) total_extensions = len(plugins) + len(mcp_servers) if total_extensions >= max_extensions: - return self.http_status(400, -1, f'Maximum number of extensions ({max_extensions}) reached') + return self.http_status( + 400, + -1, + t('Maximum number of {resource} ({limit}) reached', resource=t('extensions'), limit=max_extensions), + ) return None @staticmethod diff --git a/src/langbot/pkg/api/http/controller/groups/workspaces.py b/src/langbot/pkg/api/http/controller/groups/workspaces.py index 39dc9c577..020d2f7fd 100644 --- a/src/langbot/pkg/api/http/controller/groups/workspaces.py +++ b/src/langbot/pkg/api/http/controller/groups/workspaces.py @@ -14,6 +14,8 @@ from .....workspace.errors import WorkspaceNotFoundError from .....workspace.invitation_delivery import InvitationDeliveryService from .....operation_trace import service as settings_service from .. import group +from .....i18n import SUPPORTED_LANGUAGES +from .....i18n.workspace import get_workspace_language, set_workspace_language def _workspace_payload(workspace: Workspace) -> dict[str, typing.Any]: @@ -74,6 +76,34 @@ class WorkspacesRouterGroup(group.RouterGroup): return await operation() async def initialize(self) -> None: + @self.route( + '/current/language', + methods=['GET'], + auth_type=group.AuthType.USER_TOKEN_OR_API_KEY, + permission=Permission.WORKSPACE_VIEW, + ) + async def get_language(request_context: RequestContext): + return self.success( + data={ + 'language': await get_workspace_language(self.ap, request_context.workspace_uuid, refresh=True), + 'supported_languages': list(SUPPORTED_LANGUAGES), + } + ) + + @self.route( + '/current/language', + methods=['PUT'], + auth_type=group.AuthType.USER_TOKEN_OR_API_KEY, + permission=Permission.WORKSPACE_UPDATE, + ) + async def update_language(request_context: RequestContext): + body = await quart.request.get_json() + locale = body.get('language') if isinstance(body, dict) else None + if locale not in SUPPORTED_LANGUAGES: + return self.http_status(400, 'invalid_language', 'Unsupported Workspace language') + locale = await set_workspace_language(self.ap, request_context.workspace_uuid, locale) + return self.success(data={'language': locale}) + @self.route('/bootstrap', methods=['GET'], auth_type=group.AuthType.ACCOUNT_TOKEN) async def _(account) -> typing.Any: """List the active Workspaces available to an authenticated Account. diff --git a/src/langbot/pkg/api/http/service/agent.py b/src/langbot/pkg/api/http/service/agent.py index 394c3b3dd..c0f0b34bf 100644 --- a/src/langbot/pkg/api/http/service/agent.py +++ b/src/langbot/pkg/api/http/service/agent.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ....i18n import t + from contextlib import aclosing import datetime @@ -432,7 +434,7 @@ class AgentService: pipeline_uuid = await self.ap.pipeline_service.create_pipeline( context, { - 'name': agent_data.get('name') or 'New Pipeline', + 'name': agent_data.get('name') or t('New Pipeline'), 'description': agent_data.get('description') or '', 'emoji': agent_data.get('emoji') or '⚙️', 'config': {}, @@ -461,7 +463,7 @@ class AgentService: values = { 'workspace_uuid': workspace_uuid, 'uuid': new_uuid, - 'name': agent_data.get('name') or 'New Agent', + 'name': agent_data.get('name') or t('New Agent'), 'description': agent_data.get('description') or '', 'emoji': agent_data.get('emoji') or ('🧩' if kind == AGENT_KIND_EVENT_PROCESSOR else '🤖'), 'kind': kind, diff --git a/src/langbot/pkg/api/http/service/knowledge.py b/src/langbot/pkg/api/http/service/knowledge.py index 5897aa578..e85d16fd8 100644 --- a/src/langbot/pkg/api/http/service/knowledge.py +++ b/src/langbot/pkg/api/http/service/knowledge.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ....i18n import t + import sqlalchemy from ....api.http.authz import WorkspaceRequiredError @@ -63,7 +65,13 @@ class KnowledgeService: if max_knowledge_bases >= 0: knowledge_bases = await self.ap.rag_mgr.get_all_knowledge_base_details(context) if len(knowledge_bases) >= max_knowledge_bases: - raise ValueError(f'Maximum number of knowledge bases ({max_knowledge_bases}) reached') + raise ValueError( + t( + 'Maximum number of {resource} ({limit}) reached', + resource=t('knowledge bases'), + limit=max_knowledge_bases, + ) + ) knowledge_engine_plugin_id = kb_data.get('knowledge_engine_plugin_id') if not knowledge_engine_plugin_id: diff --git a/src/langbot/pkg/api/http/service/mcp.py b/src/langbot/pkg/api/http/service/mcp.py index 89a78f79a..3e5eb7339 100644 --- a/src/langbot/pkg/api/http/service/mcp.py +++ b/src/langbot/pkg/api/http/service/mcp.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ....i18n import t + import copy import re import uuid @@ -228,7 +230,9 @@ class MCPService: .where(persistence_plugin.PluginSetting.workspace_uuid == workspace_uuid) ) if (mcp_count_result.scalar() or 0) + (plugin_count_result.scalar() or 0) >= max_extensions: - raise ValueError(f'Maximum number of extensions ({max_extensions}) reached') + raise ValueError( + t('Maximum number of {resource} ({limit}) reached', resource=t('extensions'), limit=max_extensions) + ) payload = dict(server_data) payload.pop('workspace_uuid', None) @@ -246,7 +250,7 @@ class MCPService: ) ) if existing_result.first() is not None: - raise ValueError(f'MCP server already exists: {server_name}') + raise ValueError(t('MCP server already exists: {name}', name=server_name)) await self.ap.persistence_mgr.execute_async(sqlalchemy.insert(persistence_mcp.MCPServer).values(payload)) created = await self._get_mcp_server_by_uuid_raw(execution_context, payload['uuid']) @@ -332,7 +336,7 @@ class MCPService: raise ValueError('MCP server name is required') duplicate = await self._get_mcp_server_by_name_raw(execution_context, payload['name']) if duplicate is not None and duplicate['uuid'] != server_uuid: - raise ValueError(f'MCP server already exists: {payload["name"]}') + raise ValueError(t('MCP server already exists: {name}', name=payload['name'])) effective_server = {**old_server, **payload} # Existing disabled rows remain readable/deletable. Switching away diff --git a/src/langbot/pkg/api/http/service/model.py b/src/langbot/pkg/api/http/service/model.py index 601d79d30..bfd20bfaa 100644 --- a/src/langbot/pkg/api/http/service/model.py +++ b/src/langbot/pkg/api/http/service/model.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ....i18n import t + import uuid import sqlalchemy @@ -146,7 +148,13 @@ async def _validate_provider_supports( if not support_type: return if model_type not in support_type: - raise ValueError(f'Provider requester "{requester_name}" does not support {model_type} models') + raise ValueError( + t( + 'Provider {provider} does not support {model_type} models', + provider=requester_name, + model_type=model_type, + ) + ) async def _require_workspace_provider( diff --git a/src/langbot/pkg/api/http/service/user.py b/src/langbot/pkg/api/http/service/user.py index 5cd5766d0..60926178f 100644 --- a/src/langbot/pkg/api/http/service/user.py +++ b/src/langbot/pkg/api/http/service/user.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ....i18n import t + import sqlalchemy import argon2 import jwt @@ -423,12 +425,12 @@ class UserService: user_obj = await self.get_user_by_email(user_email) if user_obj is None: - raise ValueError('用户不存在') + raise ValueError(t('Resource not found')) self._require_active_account(user_obj) # Check if this user has a local password set if not user_obj.password: - raise ValueError('请使用 LangBot 账号登录') + raise ValueError(t('Please sign in with your LangBot Account')) await self._verify_password(user_obj.password, password) diff --git a/src/langbot/pkg/api/mcp/server.py b/src/langbot/pkg/api/mcp/server.py index 77fa0ade2..7bef96fc5 100644 --- a/src/langbot/pkg/api/mcp/server.py +++ b/src/langbot/pkg/api/mcp/server.py @@ -19,11 +19,14 @@ from __future__ import annotations import json import typing +import functools from mcp.server.fastmcp import FastMCP from ..http.authz import Permission, require_permission from .context import get_request_context +from ...i18n import language_scope +from ...i18n.workspace import get_workspace_language, set_workspace_language if typing.TYPE_CHECKING: from ...core import app as app_module @@ -78,6 +81,20 @@ class LangBotMCPServer: ap = self.ap mcp = self + @mcp.tool(description='Get the Workspace language used for backend messages and default bot replies.') + async def get_workspace_message_language() -> str: + context = _authorized(Permission.WORKSPACE_VIEW) + return _dump({'language': await get_workspace_language(ap, context.workspace_uuid, refresh=True)}) + + @mcp.tool( + description='Set the Workspace message language. Requires workspace.update; does not change personal UI language.' + ) + async def set_workspace_message_language( + language: typing.Literal['en_US', 'zh_Hans', 'zh_Hant', 'ja_JP', 'es_ES', 'ru_RU', 'th_TH', 'vi_VN'], + ) -> str: + context = _authorized(Permission.WORKSPACE_UPDATE) + return _dump({'language': await set_workspace_language(ap, context.workspace_uuid, language)}) + # ----- System (read-only) -------------------------------------- # @mcp.tool(description='Get basic LangBot system/runtime information (version, edition).') async def get_system_info() -> str: @@ -571,7 +588,12 @@ class LangBotMCPServer: """Register a tool boundary before its authorization and service call.""" def register(fn): - observed = fn + @functools.wraps(fn) + async def observed(*args, **kwargs): + context = get_request_context() + with language_scope(await get_workspace_language(self.ap, context.workspace_uuid)): + return await fn(*args, **kwargs) + return self.mcp.tool(**options)(observed) return register diff --git a/src/langbot/pkg/core/taskmgr.py b/src/langbot/pkg/core/taskmgr.py index 6c5df7b81..b3ae3d1c7 100644 --- a/src/langbot/pkg/core/taskmgr.py +++ b/src/langbot/pkg/core/taskmgr.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ..i18n import t + import asyncio import json import typing @@ -202,7 +204,7 @@ class TaskWrapper: """Return the stable task projection exposed to API-key callers.""" if self.task.cancelled(): status = 'cancelled' - error = {'type': 'task_cancelled', 'message': 'Task was cancelled'} + error = {'type': 'task_cancelled', 'message': t('Task was cancelled')} result = None elif not self.task.done(): status = 'running' @@ -212,7 +214,7 @@ class TaskWrapper: exception = self.assume_exception() if exception is not None: status = 'failed' - error = {'type': 'task_failed', 'message': 'Task execution failed'} + error = {'type': 'task_failed', 'message': t('Task execution failed')} result = None else: status = 'succeeded' diff --git a/src/langbot/pkg/i18n/__init__.py b/src/langbot/pkg/i18n/__init__.py new file mode 100644 index 000000000..113b4f909 --- /dev/null +++ b/src/langbot/pkg/i18n/__init__.py @@ -0,0 +1,197 @@ +"""Small, dependency-free catalog for LangBot-owned runtime messages. + +English message templates are the stable catalog IDs. Diagnostic text from +providers, plugins and validators is deliberately not translated. +""" + +from __future__ import annotations + +import contextlib +import contextvars +import json +from pathlib import Path + +DEFAULT_LANGUAGE = 'en_US' +SUPPORTED_LANGUAGES = ('en_US', 'zh_Hans', 'zh_Hant', 'ja_JP', 'es_ES', 'ru_RU', 'th_TH', 'vi_VN') +language = contextvars.ContextVar('langbot_message_language', default=DEFAULT_LANGUAGE) +_catalogs = { + locale: json.loads((Path(__file__).parent / 'locales' / f'{locale}.json').read_text(encoding='utf-8')) + for locale in SUPPORTED_LANGUAGES +} + +# Normalize existing API wording at the response boundary. Only exact owned +# messages are matched; validators and third-party diagnostics pass through. +_ALIASES = { + **{ + name: 'Resource not found' + for name in ( + 'bot not found', + 'model not found', + 'provider not found', + 'plugin not found', + 'Server not found', + 'Skill not found', + 'Pipeline not found', + 'pipeline not found', + 'knowledge base not found', + 'Knowledge base not found', + 'User not found', + 'Account not found', + 'agent not found', + 'API key not found', + 'Message not found', + 'Task not found', + 'Passkey not found', + 'Webhook not found', + 'Bot not found or not available', + 'Bot is unavailable', + 'WebSocket adapter not found', + 'adapter not found', + 'requester not found', + 'icon not found', + 'Session not found', + ) + }, + 'Unauthorized': 'Invalid authentication credentials', + 'No valid user token provided': 'Invalid authentication credentials', + 'No valid API key provided': 'Invalid authentication credentials', + 'Invalid username or password': 'Invalid authentication credentials', + 'Invalid TOTP code': 'Invalid TOTP or recovery code', + 'Too many attempts, try again later': 'Too many requests. Please try again later.', + 'The configured Agent runner is unavailable': 'Agent runner temporarily unavailable. Please try again.', + 'The configured Agent runner is not authorized': 'Agent runner not authorized for this pipeline.', + 'The Agent runner could not complete this test': 'Agent runner execution failed.', + 'Forbidden': 'Permission denied', + 'Member removal permission is required': 'Permission denied', + 'The Agent runner returned an invalid response': 'The Agent runner returned an invalid response.', + 'Invalid or expired state': 'Authorization failed. Please try signing in again.', + 'Space OAuth Workspace binding mismatch': 'Authorization failed. Please try signing in again.', + 'Space OAuth response is missing the Cloud Workspace binding': 'Authorization failed. Please try signing in again.', + 'Space launch requires Cloud mode': 'This feature is unavailable in this Workspace.', + 'An Account is required': 'Please sign in with your LangBot Account', + 'ChatGPT authorization requires an authorized workspace user': 'Permission denied', + 'ChatGPT authorization is busy. Please retry shortly.': 'Server is busy. Please try again later.', + 'ChatGPT authorization network error. Please retry.': 'Connection failed', + 'ChatGPT credential storage failed. Please retry.': 'Failed to save ChatGPT credentials. Please retry.', + 'ChatGPT Codex response was not completed': 'ChatGPT Codex response failed or was incomplete. Please retry.', + 'ChatGPT Codex stream ended before completion. Please retry.': 'ChatGPT Codex response failed or was incomplete. Please retry.', + 'Not permitted': 'Permission denied', + 'Unauthorized or session expired': 'Invalid authentication credentials', + 'TOTP is not enabled for that Account': 'TOTP is not enabled for this Account', + 'No pending TOTP enrolment for this Account': 'TOTP is not enabled for this Account', + 'Too many active credential exchanges': 'Server is busy. Please try again later.', + 'Timeout waiting for QR code': 'Request timed out', + 'Model availability is unavailable': 'This feature is unavailable in this Workspace.', + 'Survey not available': 'This feature is unavailable in this Workspace.', + **{ + message: 'Submission failed. Please try again.' + for message in ( + 'Failed to submit response', + 'Failed to submit feedback', + 'Failed to dismiss', + ) + }, + **{ + message: 'File size exceeds the limit.' + for message in ( + 'Image size exceeds 10MB limit.', + 'file size exceeds 10MB limit', + 'attachment too large', + ) + }, + **{ + message: 'Authorization failed. Please try signing in again.' + for message in ( + 'Space launch failed', + 'Space OAuth failed', + 'LangBot Account binding failed', + 'Failed to get access token from Space', + 'ChatGPT returned an incomplete authorization. Please sign in again.', + 'ChatGPT authorization was cancelled or replaced. Please retry.', + 'ChatGPT returned an invalid authorization response', + 'ChatGPT returned an invalid device code', + 'ChatGPT returned an invalid device code expiry', + 'ChatGPT device authorization failed. Please start again.', + 'ChatGPT returned an incomplete device authorization', + 'ChatGPT token exchange failed. Please start sign-in again.', + ) + }, +} + + +def normalize_language(value: str | None) -> str: + value = (value or '').replace('-', '_') + aliases = { + 'zh_CN': 'zh_Hans', + 'zh_TW': 'zh_Hant', + 'en': 'en_US', + 'ja': 'ja_JP', + 'es': 'es_ES', + 'ru': 'ru_RU', + 'th': 'th_TH', + 'vi': 'vi_VN', + } + value = aliases.get(value, value) + return value if value in SUPPORTED_LANGUAGES else DEFAULT_LANGUAGE + + +@contextlib.contextmanager +def language_scope(locale: str): + token = language.set(normalize_language(locale)) + try: + yield + finally: + language.reset(token) + + +def t(message: str, *, locale: str | None = None, **params) -> str: + selected = normalize_language(locale or language.get()) + # Preserve established English diagnostic wording and resource names. + # Aliases only share translations; they do not rewrite the source language. + key = _ALIASES.get(message, message) + text = message if selected == DEFAULT_LANGUAGE else _catalogs[selected].get(key, message) + return text.format(**params) if params else text + + +def error_text(code: int | str, message: str) -> str: + """Stable business codes take precedence over variable diagnostic wording.""" + if language.get() == DEFAULT_LANGUAGE: + return message + messages = { + 'permission_denied': 'Permission denied', + 'resource_not_found': 'Resource not found', + 'membership_not_found': 'Resource not found', + 'invalid_authentication': 'Invalid authentication credentials', + 'task_capacity_exceeded': 'Server is busy. Please try again later.', + 'blocking_work_capacity_exceeded': 'Server is busy. Please try again later.', + 'edition_limit': 'This feature is unavailable in this Workspace.', + 'entitlement_unavailable': 'This feature is unavailable in this Workspace.', + 'last_owner_required': 'The Workspace must retain an owner.', + 'already_a_member': 'This user is already a member.', + 'invitation_email_mismatch': 'The invitation email does not match your Account', + 'totp_invalid_code': 'Invalid TOTP or recovery code', + 'totp_challenge_invalid': 'Invalid TOTP or recovery code', + 'totp_already_enrolled': 'TOTP is already enabled for this Account', + 'totp_not_enrolled': 'TOTP is not enabled for this Account', + } + if code in ('invitation_invalid', 'invitation_expired', 'invitation_revoked', 'invitation_used'): + return t('Invalid or expired invitation') + return t(messages.get(code, message)) + + +def pipeline_notice(query, name: str, default: str, **params) -> str: + """Empty overrides inherit the Workspace language; user text stays literal. + + Only documented placeholders are substituted, so arbitrary braces in custom + messages cannot cause a formatting exception. + """ + config = query.pipeline_config.get('output', {}).get('misc', {}) + custom = config.get(name) + # This was the shipped default before language inheritance existed. + if name == 'failure-hint' and custom == 'Request failed.': + custom = None + if not isinstance(custom, str) or not custom.strip(): + return t(default, **params) + for key, value in params.items(): + custom = custom.replace('{' + key + '}', str(value)) + return custom diff --git a/src/langbot/pkg/i18n/adapters.py b/src/langbot/pkg/i18n/adapters.py new file mode 100644 index 000000000..899b57a19 --- /dev/null +++ b/src/langbot/pkg/i18n/adapters.py @@ -0,0 +1,31 @@ +"""Language boundaries for adapter-owned replies outside the Pipeline.""" + +import functools + +from . import DEFAULT_LANGUAGE, language_scope, t +from .workspace import _cache, get_workspace_language + + +def _owner(adapter): + logger = getattr(adapter, 'logger', None) + context = getattr(logger, 'execution_context', None) + workspace_uuid = getattr(context, 'workspace_uuid', None) + return getattr(logger, 'ap', None), workspace_uuid if isinstance(workspace_uuid, str) else None + + +def adapter_t(adapter, message: str, **params) -> str: + """Synchronous SDK callbacks use the bot's own cached language, never a peer's.""" + ap, workspace_uuid = _owner(adapter) + cached = _cache(ap).get(workspace_uuid) if ap is not None and workspace_uuid else None + return t(message, locale=cached[0] if cached else DEFAULT_LANGUAGE, **params) + + +def localized_adapter(method): + @functools.wraps(method) + async def wrapped(self, *args, **kwargs): + ap, workspace_uuid = _owner(self) + locale = await get_workspace_language(ap, workspace_uuid) if ap is not None else DEFAULT_LANGUAGE + with language_scope(locale): + return await method(self, *args, **kwargs) + + return wrapped diff --git a/src/langbot/pkg/i18n/locales/en_US.json b/src/langbot/pkg/i18n/locales/en_US.json new file mode 100644 index 000000000..dd07ff255 --- /dev/null +++ b/src/langbot/pkg/i18n/locales/en_US.json @@ -0,0 +1,114 @@ +{ + "Request failed.": "Request failed.", + "Too many requests. Please try again later.": "Too many requests. Please try again later.", + "Your message contains inappropriate content. Please revise it.": "Your message contains inappropriate content. Please revise it.", + "Content checking failed. Please contact the administrator.": "Content checking failed. Please contact the administrator.", + "Calling {tools}...": "Calling {tools}...", + "Agent runner not found: {runner}": "Agent runner not found: {runner}", + "Agent runner not authorized for this pipeline.": "Agent runner not authorized for this pipeline.", + "Agent runner temporarily unavailable. Please try again.": "Agent runner temporarily unavailable. Please try again.", + "Agent runner execution failed.": "Agent runner execution failed.", + "The Agent runner returned an invalid response.": "The Agent runner returned an invalid response.", + "Model request failed": "Model request failed", + "Context length exceeded": "Context length exceeded", + "Invalid request parameters": "Invalid request parameters", + "Invalid API key": "Invalid API key", + "Invalid model or endpoint": "Invalid model or endpoint", + "Rate limit exceeded or insufficient balance": "Rate limit exceeded or insufficient balance", + "Request timed out": "Request timed out", + "Connection failed": "Connection failed", + "API error": "API error", + "Unknown error": "Unknown error", + "Internal server error": "Internal server error", + "Resource not found": "Resource not found", + "Permission denied": "Permission denied", + "Invalid authentication credentials": "Invalid authentication credentials", + "MCP runtime failed": "MCP runtime failed", + "Task execution failed": "Task execution failed", + "Task was cancelled": "Task was cancelled", + "Maximum number of {resource} ({limit}) reached": "Maximum number of {resource} ({limit}) reached", + "extensions": "extensions", + "knowledge bases": "knowledge bases", + "MCP server already exists: {name}": "MCP server already exists: {name}", + "LangBot Models is managed by Cloud and cannot be modified": "LangBot Models is managed by Cloud and cannot be modified", + "Please sign in with your LangBot Account": "Please sign in with your LangBot Account", + "Current password is incorrect": "Current password is incorrect", + "Invalid TOTP or recovery code": "Invalid TOTP or recovery code", + "No local password set, please set a password first": "No local password set, please set a password first", + "This LangBot Account is already bound to another user": "This LangBot Account is already bound to another user", + "Invalid recovery key": "Invalid recovery key", + "Done": "Done", + "Thank you for your feedback": "Thank you for your feedback", + "Failed to process feedback": "Failed to process feedback", + "Helpful": "Helpful", + "Not helpful": "Not helpful", + "Select an option": "Select an option", + "Enter a value": "Enter a value", + "Submit": "Submit", + "Submitted": "Submitted", + "Thinking": "Thinking", + "AI-generated": "AI-generated", + "AI-generated content. Refer to the cited sources for details.": "AI-generated content. Refer to the cited sources for details.", + "No bridges configured.": "No bridges configured.", + "Signing in to all bridges again...": "Signing in to all bridges again...", + "Bridge status:": "Bridge status:", + "Signed in ✓": "Signed in ✓", + "Not signed in ✗": "Not signed in ✗", + "None": "None", + "Bridge {bridge}: skipped (login command or DM room missing).": "Bridge {bridge}: skipped (login command or DM room missing).", + "Bridge {bridge}: sending {command}...": "Bridge {bridge}: sending {command}...", + "Bridge {bridge}: sign-out failed.": "Bridge {bridge}: sign-out failed.", + "Bridge {bridge}: sign-in started.": "Bridge {bridge}: sign-in started.", + "Slack authorization completed. Return to LangBot to finish setup.": "Slack authorization completed. Return to LangBot to finish setup.", + "Slack authorization failed or expired. Return to LangBot for details.": "Slack authorization failed or expired. Return to LangBot for details.", + "TOTP is already enabled for this Account": "TOTP is already enabled for this Account", + "TOTP is not enabled for this Account": "TOTP is not enabled for this Account", + "Invalid or expired passkey challenge": "Invalid or expired passkey challenge", + "Passkey credential not recognized": "Passkey credential not recognized", + "Account is disabled": "Account is disabled", + "Modifying login info is disabled": "Modifying login info is disabled", + "Invalid GitHub repository URL": "Invalid GitHub repository URL", + "Server is busy. Please try again later.": "Server is busy. Please try again later.", + "This feature is unavailable in this Workspace.": "This feature is unavailable in this Workspace.", + "The Workspace must retain an owner.": "The Workspace must retain an owner.", + "Invalid or expired invitation": "Invalid or expired invitation", + "This user is already a member.": "This user is already a member.", + "Voice connection failed": "Voice connection failed", + "User is not in the voice channel": "User is not in the voice channel", + "Voice service is unavailable": "Voice service is unavailable", + "Submission failed. Please try again.": "Submission failed. Please try again.", + "File size exceeds the limit.": "File size exceeds the limit.", + "File size exceeds 10MB limit. Please split large files into smaller parts.": "File size exceeds 10MB limit. Please split large files into smaller parts.", + "Authorization failed. Please try signing in again.": "Authorization failed. Please try signing in again.", + "Bind the LangBot Account with the same email as this local Account": "Bind the LangBot Account with the same email as this local Account", + "Only local accounts can bind to Space": "Only local accounts can bind to Space", + "System already initialized": "System already initialized", + "System not initialized": "System not initialized", + "Password login is disabled on LangBot Cloud": "Password login is disabled on LangBot Cloud", + "This edition supports one Workspace per instance": "This edition supports one Workspace per instance", + "Cloud Workspaces are created by the SaaS control plane": "Cloud Workspaces are created by the SaaS control plane", + "Login with your LangBot Account to accept this invitation": "Login with your LangBot Account to accept this invitation", + "RAG migration is not needed": "RAG migration is not needed", + "New Agent": "New Agent", + "New Pipeline": "New Pipeline", + "Provider {provider} does not support {model_type} models": "Provider {provider} does not support {model_type} models", + "Invalid JSON format": "Invalid JSON format", + "ChatGPT sign-in required. Open this provider and sign in again.": "ChatGPT sign-in required. Open this provider and sign in again.", + "ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.", + "ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT denied this request. Check subscription and workspace permissions.", + "ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT rejected the model or request. Check the selected model and request settings.", + "ChatGPT Codex upstream request failed. Please retry later.": "ChatGPT Codex upstream request failed. Please retry later.", + "ChatGPT subscription usage limit reached. Please retry later or check your plan.": "ChatGPT subscription usage limit reached. Please retry later or check your plan.", + "ChatGPT Codex response failed or was incomplete. Please retry.": "ChatGPT Codex response failed or was incomplete. Please retry.", + "ChatGPT token refresh temporarily failed. Please retry.": "ChatGPT token refresh temporarily failed. Please retry.", + "A second factor is required": "A second factor is required", + "Sign in or provide registration details to accept this invitation": "Sign in or provide registration details to accept this invitation", + "Sign out before creating the invited local Account": "Sign out before creating the invited local Account", + "Turnstile verification failed": "Turnstile verification failed", + "The invitation email does not match your Account": "The invitation email does not match your Account", + "Failed to save ChatGPT credentials. Please retry.": "Failed to save ChatGPT credentials. Please retry.", + "Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.", + "Option {number}": "Option {number}", + "Confirmation needed": "Confirmation needed", + "Session reset successfully": "Session reset successfully" +} diff --git a/src/langbot/pkg/i18n/locales/es_ES.json b/src/langbot/pkg/i18n/locales/es_ES.json new file mode 100644 index 000000000..116aa09a8 --- /dev/null +++ b/src/langbot/pkg/i18n/locales/es_ES.json @@ -0,0 +1,114 @@ +{ + "Request failed.": "La solicitud ha fallado.", + "Too many requests. Please try again later.": "Demasiadas solicitudes. Inténtalo más tarde.", + "Your message contains inappropriate content. Please revise it.": "Tu mensaje contiene contenido inapropiado. Modifícalo.", + "Content checking failed. Please contact the administrator.": "No se pudo verificar el contenido. Contacta con el administrador.", + "Calling {tools}...": "Llamando a {tools}…", + "Agent runner not found: {runner}": "No se encontró el ejecutor del agente: {runner}", + "Agent runner not authorized for this pipeline.": "Este flujo no tiene autorización para usar el ejecutor del agente.", + "Agent runner temporarily unavailable. Please try again.": "El ejecutor del agente no está disponible temporalmente. Reinténtalo.", + "Agent runner execution failed.": "La ejecución del agente ha fallado.", + "The Agent runner returned an invalid response.": "El ejecutor del agente devolvió una respuesta no válida.", + "Model request failed": "Error en la solicitud al modelo", + "Context length exceeded": "Se superó el límite de contexto", + "Invalid request parameters": "Parámetros de solicitud no válidos", + "Invalid API key": "Clave API no válida", + "Invalid model or endpoint": "Modelo o endpoint no válido", + "Rate limit exceeded or insufficient balance": "Límite de solicitudes excedido o saldo insuficiente", + "Request timed out": "La solicitud agotó el tiempo de espera", + "Connection failed": "Error de conexión", + "API error": "Error de API", + "Unknown error": "Error desconocido", + "Internal server error": "Error interno del servidor", + "Resource not found": "Recurso no encontrado", + "Permission denied": "No tienes permiso para realizar esta acción", + "Invalid authentication credentials": "Error de autenticación. Vuelve a iniciar sesión", + "MCP runtime failed": "Error de ejecución de MCP", + "Task execution failed": "La tarea ha fallado", + "Task was cancelled": "La tarea se ha cancelado", + "Maximum number of {resource} ({limit}) reached": "Se alcanzó el máximo de {resource} ({limit})", + "extensions": "extensiones", + "knowledge bases": "bases de conocimiento", + "MCP server already exists: {name}": "El servidor MCP ya existe: {name}", + "LangBot Models is managed by Cloud and cannot be modified": "LangBot Models está gestionado por Cloud y no se puede modificar", + "Please sign in with your LangBot Account": "Inicia sesión con tu cuenta de LangBot", + "Current password is incorrect": "La contraseña actual es incorrecta", + "Invalid TOTP or recovery code": "Código TOTP o de recuperación no válido", + "No local password set, please set a password first": "No hay contraseña local. Configura una primero", + "This LangBot Account is already bound to another user": "Esta cuenta de LangBot ya está vinculada a otro usuario", + "Invalid recovery key": "Clave de recuperación no válida", + "Done": "Hecho", + "Thank you for your feedback": "Gracias por tus comentarios", + "Failed to process feedback": "No se pudieron procesar los comentarios", + "Helpful": "Útil", + "Not helpful": "No es útil", + "Select an option": "Selecciona una opción", + "Enter a value": "Introduce un valor", + "Submit": "Enviar", + "Submitted": "Enviado", + "Thinking": "Razonamiento", + "AI-generated": "Generado por IA", + "AI-generated content. Refer to the cited sources for details.": "Contenido generado por IA. Consulta las fuentes citadas para más detalles.", + "No bridges configured.": "No hay puentes configurados.", + "Signing in to all bridges again...": "Iniciando sesión de nuevo en todos los puentes…", + "Bridge status:": "Estado de los puentes:", + "Signed in ✓": "Sesión iniciada ✓", + "Not signed in ✗": "Sin sesión ✗", + "None": "Ninguno", + "Bridge {bridge}: skipped (login command or DM room missing).": "Puente {bridge}: omitido (falta comando de inicio de sesión o sala DM).", + "Bridge {bridge}: sending {command}...": "Puente {bridge}: enviando {command}…", + "Bridge {bridge}: sign-out failed.": "Puente {bridge}: no se pudo cerrar sesión.", + "Bridge {bridge}: sign-in started.": "Puente {bridge}: inicio de sesión en curso.", + "Slack authorization completed. Return to LangBot to finish setup.": "Autorización de Slack completada. Vuelve a LangBot para terminar la configuración.", + "Slack authorization failed or expired. Return to LangBot for details.": "La autorización de Slack falló o caducó. Consulta los detalles en LangBot.", + "TOTP is already enabled for this Account": "Esta cuenta ya tiene TOTP activado", + "TOTP is not enabled for this Account": "Esta cuenta no tiene TOTP activado", + "Invalid or expired passkey challenge": "El desafío de clave de acceso no es válido o ha caducado", + "Passkey credential not recognized": "No se reconoce la clave de acceso", + "Account is disabled": "La cuenta está desactivada", + "Modifying login info is disabled": "No se permite modificar los datos de inicio de sesión", + "Invalid GitHub repository URL": "URL del repositorio GitHub no válida", + "Server is busy. Please try again later.": "El servidor está ocupado. Inténtalo más tarde.", + "This feature is unavailable in this Workspace.": "Esta función no está disponible en este espacio de trabajo.", + "The Workspace must retain an owner.": "El espacio de trabajo debe conservar un propietario.", + "Invalid or expired invitation": "Invitación no válida o caducada", + "This user is already a member.": "Este usuario ya es miembro.", + "Voice connection failed": "Error de conexión de voz", + "User is not in the voice channel": "El usuario no está en el canal de voz", + "Voice service is unavailable": "El servicio de voz no está disponible", + "Submission failed. Please try again.": "El envío ha fallado. Reinténtalo.", + "File size exceeds the limit.": "El archivo supera el tamaño permitido.", + "File size exceeds 10MB limit. Please split large files into smaller parts.": "El archivo supera los 10 MB. Divídelo en partes más pequeñas.", + "Authorization failed. Please try signing in again.": "La autorización ha fallado. Vuelve a iniciar sesión.", + "Bind the LangBot Account with the same email as this local Account": "Vincula una cuenta de LangBot con el mismo correo que esta cuenta local", + "Only local accounts can bind to Space": "Solo las cuentas locales pueden vincularse a Space", + "System already initialized": "El sistema ya está inicializado", + "System not initialized": "El sistema no está inicializado", + "Password login is disabled on LangBot Cloud": "El acceso con contraseña está desactivado en LangBot Cloud", + "This edition supports one Workspace per instance": "Esta edición admite un espacio de trabajo por instancia", + "Cloud Workspaces are created by the SaaS control plane": "Los espacios de trabajo Cloud se crean desde el panel SaaS", + "Login with your LangBot Account to accept this invitation": "Inicia sesión con tu cuenta de LangBot para aceptar la invitación", + "RAG migration is not needed": "No es necesaria la migración de RAG", + "New Agent": "Nuevo agente", + "New Pipeline": "Nuevo flujo", + "Provider {provider} does not support {model_type} models": "El proveedor {provider} no admite modelos {model_type}", + "Invalid JSON format": "Formato JSON no válido", + "ChatGPT sign-in required. Open this provider and sign in again.": "Debes iniciar sesión en ChatGPT. Abre este proveedor y vuelve a iniciar sesión.", + "ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "ChatGPT limitó la solicitud por frecuencia o uso. Inténtalo más tarde o revisa tu plan.", + "ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT rechazó la solicitud. Revisa la suscripción y los permisos del espacio de trabajo.", + "ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT rechazó el modelo o la solicitud. Revisa el modelo seleccionado y la configuración.", + "ChatGPT Codex upstream request failed. Please retry later.": "Falló la solicitud a ChatGPT Codex. Inténtalo más tarde.", + "ChatGPT subscription usage limit reached. Please retry later or check your plan.": "Se alcanzó el límite de uso de tu suscripción de ChatGPT. Inténtalo más tarde o revisa tu plan.", + "ChatGPT Codex response failed or was incomplete. Please retry.": "La respuesta de ChatGPT Codex falló o está incompleta. Inténtalo de nuevo.", + "ChatGPT token refresh temporarily failed. Please retry.": "No se pudo renovar el token de ChatGPT temporalmente. Inténtalo de nuevo.", + "A second factor is required": "Se requiere un segundo factor de autenticación", + "Sign in or provide registration details to accept this invitation": "Inicia sesión o proporciona los datos de registro para aceptar esta invitación", + "Sign out before creating the invited local Account": "Cierra sesión antes de crear la cuenta local invitada", + "Turnstile verification failed": "La verificación de Turnstile falló. Inténtalo de nuevo", + "The invitation email does not match your Account": "El correo de la invitación no coincide con tu cuenta", + "Failed to save ChatGPT credentials. Please retry.": "No se pudieron guardar las credenciales de ChatGPT. Inténtalo de nuevo.", + "Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "No se pudo iniciar el acceso por dispositivo. Activa el inicio de sesión con código de dispositivo en la configuración de ChatGPT.", + "Option {number}": "Opción {number}", + "Confirmation needed": "Se requiere confirmación", + "Session reset successfully": "Sesión restablecida" +} diff --git a/src/langbot/pkg/i18n/locales/ja_JP.json b/src/langbot/pkg/i18n/locales/ja_JP.json new file mode 100644 index 000000000..292bb1b56 --- /dev/null +++ b/src/langbot/pkg/i18n/locales/ja_JP.json @@ -0,0 +1,114 @@ +{ + "Request failed.": "リクエストに失敗しました。", + "Too many requests. Please try again later.": "リクエストが多すぎます。しばらくしてから再試行してください。", + "Your message contains inappropriate content. Please revise it.": "不適切な内容が含まれています。メッセージを修正してください。", + "Content checking failed. Please contact the administrator.": "コンテンツの確認に失敗しました。管理者にお問い合わせください。", + "Calling {tools}...": "{tools} を呼び出しています…", + "Agent runner not found: {runner}": "Agent ランナーが見つかりません:{runner}", + "Agent runner not authorized for this pipeline.": "このパイプラインには Agent ランナーの使用権限がありません。", + "Agent runner temporarily unavailable. Please try again.": "Agent ランナーは一時的に利用できません。再試行してください。", + "Agent runner execution failed.": "Agent の実行に失敗しました。", + "The Agent runner returned an invalid response.": "Agent ランナーが無効な応答を返しました。", + "Model request failed": "モデルへのリクエストに失敗しました", + "Context length exceeded": "コンテキスト長の上限を超えました", + "Invalid request parameters": "リクエストのパラメータが無効です", + "Invalid API key": "API キーが無効です", + "Invalid model or endpoint": "モデルまたはエンドポイントが無効です", + "Rate limit exceeded or insufficient balance": "リクエスト制限を超えたか、残高が不足しています", + "Request timed out": "リクエストがタイムアウトしました", + "Connection failed": "接続に失敗しました", + "API error": "API エラー", + "Unknown error": "不明なエラー", + "Internal server error": "サーバー内部エラー", + "Resource not found": "リソースが見つかりません", + "Permission denied": "この操作の権限がありません", + "Invalid authentication credentials": "認証に失敗しました。再度ログインしてください", + "MCP runtime failed": "MCP の実行に失敗しました", + "Task execution failed": "タスクの実行に失敗しました", + "Task was cancelled": "タスクがキャンセルされました", + "Maximum number of {resource} ({limit}) reached": "{resource}の上限数({limit})に達しました", + "extensions": "拡張機能", + "knowledge bases": "ナレッジベース", + "MCP server already exists: {name}": "MCP サーバーは既に存在します:{name}", + "LangBot Models is managed by Cloud and cannot be modified": "LangBot Models はクラウドで管理されているため変更できません", + "Please sign in with your LangBot Account": "LangBot アカウントでログインしてください", + "Current password is incorrect": "現在のパスワードが正しくありません", + "Invalid TOTP or recovery code": "認証コードまたはリカバリーコードが無効です", + "No local password set, please set a password first": "ローカルパスワードが未設定です。先に設定してください", + "This LangBot Account is already bound to another user": "この LangBot アカウントは別のユーザーに連携されています", + "Invalid recovery key": "リカバリーキーが無効です", + "Done": "完了しました", + "Thank you for your feedback": "フィードバックありがとうございます", + "Failed to process feedback": "フィードバックの処理に失敗しました", + "Helpful": "役に立った", + "Not helpful": "役に立たなかった", + "Select an option": "選択してください", + "Enter a value": "入力してください", + "Submit": "送信", + "Submitted": "送信済み", + "Thinking": "思考過程", + "AI-generated": "AI による生成コンテンツ", + "AI-generated content. Refer to the cited sources for details.": "AI が生成した内容です。詳細は引用元を参照してください。", + "No bridges configured.": "ブリッジが設定されていません。", + "Signing in to all bridges again...": "すべてのブリッジに再ログインしています…", + "Bridge status:": "ブリッジの状態:", + "Signed in ✓": "ログイン済み ✓", + "Not signed in ✗": "未ログイン ✗", + "None": "なし", + "Bridge {bridge}: skipped (login command or DM room missing).": "ブリッジ {bridge}:スキップしました(ログインコマンドまたは DM ルームがありません)。", + "Bridge {bridge}: sending {command}...": "ブリッジ {bridge}:{command} を送信中…", + "Bridge {bridge}: sign-out failed.": "ブリッジ {bridge}:ログアウトに失敗しました。", + "Bridge {bridge}: sign-in started.": "ブリッジ {bridge}:ログインを開始しました。", + "Slack authorization completed. Return to LangBot to finish setup.": "Slack の認証が完了しました。LangBot に戻って設定を完了してください。", + "Slack authorization failed or expired. Return to LangBot for details.": "Slack の認証に失敗したか期限切れです。詳細は LangBot で確認してください。", + "TOTP is already enabled for this Account": "このアカウントでは二段階認証が有効です", + "TOTP is not enabled for this Account": "このアカウントでは二段階認証が無効です", + "Invalid or expired passkey challenge": "パスキーの認証要求が無効または期限切れです", + "Passkey credential not recognized": "このパスキーは認識できません", + "Account is disabled": "アカウントは無効です", + "Modifying login info is disabled": "ログイン情報の変更は無効です", + "Invalid GitHub repository URL": "GitHub リポジトリの URL が無効です", + "Server is busy. Please try again later.": "サーバーが混雑しています。後で再試行してください。", + "This feature is unavailable in this Workspace.": "このワークスペースではこの機能を利用できません。", + "The Workspace must retain an owner.": "ワークスペースにはオーナーが必要です。", + "Invalid or expired invitation": "招待が無効または期限切れです", + "This user is already a member.": "このユーザーは既にメンバーです。", + "Voice connection failed": "音声接続に失敗しました", + "User is not in the voice channel": "ユーザーは音声チャンネルに参加していません", + "Voice service is unavailable": "音声サービスを利用できません", + "Submission failed. Please try again.": "送信に失敗しました。再試行してください。", + "File size exceeds the limit.": "ファイルサイズが上限を超えています。", + "File size exceeds 10MB limit. Please split large files into smaller parts.": "ファイルが 10MB を超えています。分割してアップロードしてください。", + "Authorization failed. Please try signing in again.": "認証に失敗しました。再度ログインしてください。", + "Bind the LangBot Account with the same email as this local Account": "このローカルアカウントと同じメールアドレスの LangBot アカウントを連携してください", + "Only local accounts can bind to Space": "Space と連携できるのはローカルアカウントのみです", + "System already initialized": "システムは初期化済みです", + "System not initialized": "システムが初期化されていません", + "Password login is disabled on LangBot Cloud": "LangBot Cloud ではパスワードログインは無効です", + "This edition supports one Workspace per instance": "このエディションではインスタンスごとにワークスペースは1つです", + "Cloud Workspaces are created by the SaaS control plane": "クラウドのワークスペースは SaaS 管理画面で作成します", + "Login with your LangBot Account to accept this invitation": "招待を承諾するには LangBot アカウントでログインしてください", + "RAG migration is not needed": "RAG の移行は不要です", + "New Agent": "新しい Agent", + "New Pipeline": "新しいパイプライン", + "Provider {provider} does not support {model_type} models": "プロバイダー {provider} は {model_type} モデルに対応していません", + "Invalid JSON format": "JSON 形式が無効です", + "ChatGPT sign-in required. Open this provider and sign in again.": "ChatGPT へのログインが必要です。このプロバイダーを開いて再ログインしてください。", + "ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "ChatGPT のリクエストが制限されました(頻度または使用量)。後で再試行するか、プランを確認してください。", + "ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT がリクエストを拒否しました。サブスクリプションとワークスペースの権限を確認してください。", + "ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT がモデルまたはリクエストを拒否しました。選択したモデルとリクエスト設定を確認してください。", + "ChatGPT Codex upstream request failed. Please retry later.": "ChatGPT Codex の上流リクエストに失敗しました。後で再試行してください。", + "ChatGPT subscription usage limit reached. Please retry later or check your plan.": "ChatGPT のサブスクリプション使用量が上限に達しました。後で再試行するか、プランを確認してください。", + "ChatGPT Codex response failed or was incomplete. Please retry.": "ChatGPT Codex の応答に失敗したか、応答が不完全です。再試行してください。", + "ChatGPT token refresh temporarily failed. Please retry.": "ChatGPT トークンの更新に一時的に失敗しました。再試行してください。", + "A second factor is required": "二要素認証が必要です", + "Sign in or provide registration details to accept this invitation": "招待を承諾するにはログインするか、登録情報を入力してください", + "Sign out before creating the invited local Account": "招待されたローカルアカウントを作成する前にログアウトしてください", + "Turnstile verification failed": "人間確認に失敗しました。再試行してください", + "The invitation email does not match your Account": "招待のメールアドレスがアカウントと一致しません", + "Failed to save ChatGPT credentials. Please retry.": "ChatGPT 認証情報の保存に失敗しました。再試行してください。", + "Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "ChatGPT のデバイスログインを開始できません。ChatGPT の設定でデバイスコードログインを有効にしてください。", + "Option {number}": "選択肢 {number}", + "Confirmation needed": "確認が必要です", + "Session reset successfully": "セッションをリセットしました" +} diff --git a/src/langbot/pkg/i18n/locales/ru_RU.json b/src/langbot/pkg/i18n/locales/ru_RU.json new file mode 100644 index 000000000..ed9529018 --- /dev/null +++ b/src/langbot/pkg/i18n/locales/ru_RU.json @@ -0,0 +1,114 @@ +{ + "Request failed.": "Запрос не выполнен.", + "Too many requests. Please try again later.": "Слишком много запросов. Повторите позже.", + "Your message contains inappropriate content. Please revise it.": "Сообщение содержит недопустимый контент. Измените его.", + "Content checking failed. Please contact the administrator.": "Ошибка проверки содержимого. Обратитесь к администратору.", + "Calling {tools}...": "Вызов {tools}…", + "Agent runner not found: {runner}": "Исполнитель агента не найден: {runner}", + "Agent runner not authorized for this pipeline.": "У конвейера нет разрешения на использование исполнителя агента.", + "Agent runner temporarily unavailable. Please try again.": "Исполнитель агента временно недоступен. Повторите попытку.", + "Agent runner execution failed.": "Ошибка выполнения агента.", + "The Agent runner returned an invalid response.": "Исполнитель агента вернул некорректный ответ.", + "Model request failed": "Ошибка запроса к модели", + "Context length exceeded": "Превышена длина контекста", + "Invalid request parameters": "Некорректные параметры запроса", + "Invalid API key": "Недействительный API-ключ", + "Invalid model or endpoint": "Некорректная модель или адрес API", + "Rate limit exceeded or insufficient balance": "Превышен лимит запросов или недостаточно средств", + "Request timed out": "Время ожидания запроса истекло", + "Connection failed": "Ошибка подключения", + "API error": "Ошибка API", + "Unknown error": "Неизвестная ошибка", + "Internal server error": "Внутренняя ошибка сервера", + "Resource not found": "Ресурс не найден", + "Permission denied": "Нет разрешения на это действие", + "Invalid authentication credentials": "Ошибка аутентификации. Войдите снова", + "MCP runtime failed": "Ошибка выполнения MCP", + "Task execution failed": "Ошибка выполнения задачи", + "Task was cancelled": "Задача отменена", + "Maximum number of {resource} ({limit}) reached": "Достигнуто максимальное число {resource} ({limit})", + "extensions": "расширений", + "knowledge bases": "баз знаний", + "MCP server already exists: {name}": "Сервер MCP уже существует: {name}", + "LangBot Models is managed by Cloud and cannot be modified": "LangBot Models управляется облаком и не может быть изменён", + "Please sign in with your LangBot Account": "Войдите с учётной записью LangBot", + "Current password is incorrect": "Текущий пароль неверен", + "Invalid TOTP or recovery code": "Неверный код TOTP или восстановления", + "No local password set, please set a password first": "Локальный пароль не задан. Сначала задайте пароль", + "This LangBot Account is already bound to another user": "Эта учётная запись LangBot уже связана с другим пользователем", + "Invalid recovery key": "Недействительный ключ восстановления", + "Done": "Готово", + "Thank you for your feedback": "Спасибо за отзыв", + "Failed to process feedback": "Не удалось обработать отзыв", + "Helpful": "Полезно", + "Not helpful": "Не полезно", + "Select an option": "Выберите вариант", + "Enter a value": "Введите значение", + "Submit": "Отправить", + "Submitted": "Отправлено", + "Thinking": "Ход рассуждений", + "AI-generated": "Создано ИИ", + "AI-generated content. Refer to the cited sources for details.": "Контент создан ИИ. Подробности смотрите в указанных источниках.", + "No bridges configured.": "Мосты не настроены.", + "Signing in to all bridges again...": "Повторный вход во все мосты…", + "Bridge status:": "Состояние мостов:", + "Signed in ✓": "Вход выполнен ✓", + "Not signed in ✗": "Вход не выполнен ✗", + "None": "Нет", + "Bridge {bridge}: skipped (login command or DM room missing).": "Мост {bridge}: пропущен (нет команды входа или комнаты DM).", + "Bridge {bridge}: sending {command}...": "Мост {bridge}: отправка {command}…", + "Bridge {bridge}: sign-out failed.": "Мост {bridge}: ошибка выхода.", + "Bridge {bridge}: sign-in started.": "Мост {bridge}: вход начат.", + "Slack authorization completed. Return to LangBot to finish setup.": "Авторизация Slack завершена. Вернитесь в LangBot для завершения настройки.", + "Slack authorization failed or expired. Return to LangBot for details.": "Авторизация Slack не удалась или истекла. Подробности в LangBot.", + "TOTP is already enabled for this Account": "Для этой учётной записи TOTP уже включён", + "TOTP is not enabled for this Account": "Для этой учётной записи TOTP не включён", + "Invalid or expired passkey challenge": "Запрос проверки ключа доступа недействителен или истёк", + "Passkey credential not recognized": "Ключ доступа не распознан", + "Account is disabled": "Учётная запись отключена", + "Modifying login info is disabled": "Изменение данных входа отключено", + "Invalid GitHub repository URL": "Некорректный URL репозитория GitHub", + "Server is busy. Please try again later.": "Сервер занят. Повторите позже.", + "This feature is unavailable in this Workspace.": "Эта функция недоступна в данном рабочем пространстве.", + "The Workspace must retain an owner.": "У рабочего пространства должен остаться владелец.", + "Invalid or expired invitation": "Приглашение недействительно или истекло", + "This user is already a member.": "Этот пользователь уже является участником.", + "Voice connection failed": "Ошибка голосового соединения", + "User is not in the voice channel": "Пользователь не в голосовом канале", + "Voice service is unavailable": "Голосовая служба недоступна", + "Submission failed. Please try again.": "Не удалось отправить. Повторите попытку.", + "File size exceeds the limit.": "Размер файла превышает ограничение.", + "File size exceeds 10MB limit. Please split large files into smaller parts.": "Размер файла превышает 10 МБ. Разделите его на части.", + "Authorization failed. Please try signing in again.": "Авторизация не удалась. Войдите снова.", + "Bind the LangBot Account with the same email as this local Account": "Свяжите учётную запись LangBot с тем же адресом почты, что и локальная", + "Only local accounts can bind to Space": "К Space можно привязать только локальные учётные записи", + "System already initialized": "Система уже инициализирована", + "System not initialized": "Система не инициализирована", + "Password login is disabled on LangBot Cloud": "В LangBot Cloud вход по паролю отключён", + "This edition supports one Workspace per instance": "Эта редакция поддерживает одно рабочее пространство на экземпляр", + "Cloud Workspaces are created by the SaaS control plane": "Облачные рабочие пространства создаются в панели SaaS", + "Login with your LangBot Account to accept this invitation": "Войдите в LangBot, чтобы принять приглашение", + "RAG migration is not needed": "Миграция RAG не требуется", + "New Agent": "Новый агент", + "New Pipeline": "Новый конвейер", + "Provider {provider} does not support {model_type} models": "Провайдер {provider} не поддерживает модели {model_type}", + "Invalid JSON format": "Некорректный формат JSON", + "ChatGPT sign-in required. Open this provider and sign in again.": "Требуется вход в ChatGPT. Откройте этого провайдера и войдите снова.", + "ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "Запрос ChatGPT ограничен по частоте или использованию. Повторите позже или проверьте тариф.", + "ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT отклонил запрос. Проверьте подписку и права рабочего пространства.", + "ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT отклонил модель или запрос. Проверьте выбранную модель и настройки запроса.", + "ChatGPT Codex upstream request failed. Please retry later.": "Ошибка запроса к ChatGPT Codex. Повторите позже.", + "ChatGPT subscription usage limit reached. Please retry later or check your plan.": "Достигнут лимит использования подписки ChatGPT. Повторите позже или проверьте тариф.", + "ChatGPT Codex response failed or was incomplete. Please retry.": "Ошибка или неполный ответ ChatGPT Codex. Повторите запрос.", + "ChatGPT token refresh temporarily failed. Please retry.": "Временная ошибка обновления токена ChatGPT. Повторите попытку.", + "A second factor is required": "Требуется второй фактор аутентификации", + "Sign in or provide registration details to accept this invitation": "Войдите или укажите данные регистрации, чтобы принять приглашение", + "Sign out before creating the invited local Account": "Выйдите перед созданием приглашённой локальной учётной записи", + "Turnstile verification failed": "Проверка Turnstile не пройдена. Повторите попытку", + "The invitation email does not match your Account": "Адрес приглашения не совпадает с вашей учётной записью", + "Failed to save ChatGPT credentials. Please retry.": "Не удалось сохранить учётные данные ChatGPT. Повторите попытку.", + "Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "Не удалось начать вход с устройства. Включите вход по коду устройства в настройках ChatGPT.", + "Option {number}": "Вариант {number}", + "Confirmation needed": "Требуется подтверждение", + "Session reset successfully": "Сессия сброшена" +} diff --git a/src/langbot/pkg/i18n/locales/th_TH.json b/src/langbot/pkg/i18n/locales/th_TH.json new file mode 100644 index 000000000..ff40282cd --- /dev/null +++ b/src/langbot/pkg/i18n/locales/th_TH.json @@ -0,0 +1,114 @@ +{ + "Request failed.": "คำขอล้มเหลว", + "Too many requests. Please try again later.": "มีคำขอมากเกินไป โปรดลองอีกครั้งภายหลัง", + "Your message contains inappropriate content. Please revise it.": "ข้อความมีเนื้อหาไม่เหมาะสม โปรดแก้ไข", + "Content checking failed. Please contact the administrator.": "ตรวจสอบเนื้อหาไม่สำเร็จ โปรดติดต่อผู้ดูแลระบบ", + "Calling {tools}...": "กำลังเรียกใช้ {tools}…", + "Agent runner not found: {runner}": "ไม่พบตัวรัน Agent: {runner}", + "Agent runner not authorized for this pipeline.": "ไปป์ไลน์นี้ไม่ได้รับอนุญาตให้ใช้ตัวรัน Agent", + "Agent runner temporarily unavailable. Please try again.": "ตัวรัน Agent ไม่พร้อมใช้งานชั่วคราว โปรดลองอีกครั้ง", + "Agent runner execution failed.": "การทำงานของ Agent ล้มเหลว", + "The Agent runner returned an invalid response.": "ตัวรัน Agent ส่งการตอบกลับที่ไม่ถูกต้อง", + "Model request failed": "คำขอไปยังโมเดลล้มเหลว", + "Context length exceeded": "ความยาวบริบทเกินขีดจำกัด", + "Invalid request parameters": "พารามิเตอร์คำขอไม่ถูกต้อง", + "Invalid API key": "คีย์ API ไม่ถูกต้อง", + "Invalid model or endpoint": "โมเดลหรือจุดเชื่อมต่อไม่ถูกต้อง", + "Rate limit exceeded or insufficient balance": "เกินขีดจำกัดคำขอหรือยอดเงินไม่เพียงพอ", + "Request timed out": "คำขอหมดเวลา", + "Connection failed": "การเชื่อมต่อล้มเหลว", + "API error": "ข้อผิดพลาด API", + "Unknown error": "ข้อผิดพลาดที่ไม่ทราบสาเหตุ", + "Internal server error": "ข้อผิดพลาดภายในเซิร์ฟเวอร์", + "Resource not found": "ไม่พบทรัพยากร", + "Permission denied": "ไม่มีสิทธิ์ดำเนินการนี้", + "Invalid authentication credentials": "ยืนยันตัวตนไม่สำเร็จ โปรดเข้าสู่ระบบอีกครั้ง", + "MCP runtime failed": "การทำงานของ MCP ล้มเหลว", + "Task execution failed": "การทำงานของงานล้มเหลว", + "Task was cancelled": "งานถูกยกเลิกแล้ว", + "Maximum number of {resource} ({limit}) reached": "ถึงจำนวนสูงสุดของ{resource} ({limit}) แล้ว", + "extensions": "ส่วนขยาย", + "knowledge bases": "ฐานความรู้", + "MCP server already exists: {name}": "มีเซิร์ฟเวอร์ MCP อยู่แล้ว: {name}", + "LangBot Models is managed by Cloud and cannot be modified": "LangBot Models จัดการโดย Cloud และไม่สามารถแก้ไขได้", + "Please sign in with your LangBot Account": "โปรดเข้าสู่ระบบด้วยบัญชี LangBot", + "Current password is incorrect": "รหัสผ่านปัจจุบันไม่ถูกต้อง", + "Invalid TOTP or recovery code": "รหัส TOTP หรือรหัสกู้คืนไม่ถูกต้อง", + "No local password set, please set a password first": "ยังไม่ได้ตั้งรหัสผ่านในระบบ โปรดตั้งรหัสผ่านก่อน", + "This LangBot Account is already bound to another user": "บัญชี LangBot นี้เชื่อมโยงกับผู้ใช้อื่นแล้ว", + "Invalid recovery key": "คีย์กู้คืนไม่ถูกต้อง", + "Done": "เสร็จแล้ว", + "Thank you for your feedback": "ขอบคุณสำหรับความคิดเห็น", + "Failed to process feedback": "ประมวลผลความคิดเห็นไม่สำเร็จ", + "Helpful": "มีประโยชน์", + "Not helpful": "ไม่มีประโยชน์", + "Select an option": "โปรดเลือก", + "Enter a value": "โปรดป้อนค่า", + "Submit": "ส่ง", + "Submitted": "ส่งแล้ว", + "Thinking": "กระบวนการคิด", + "AI-generated": "สร้างโดย AI", + "AI-generated content. Refer to the cited sources for details.": "เนื้อหาสร้างโดย AI ดูรายละเอียดจากแหล่งอ้างอิง", + "No bridges configured.": "ยังไม่ได้ตั้งค่าบริดจ์", + "Signing in to all bridges again...": "กำลังเข้าสู่ระบบบริดจ์ทั้งหมดอีกครั้ง…", + "Bridge status:": "สถานะบริดจ์:", + "Signed in ✓": "เข้าสู่ระบบแล้ว ✓", + "Not signed in ✗": "ยังไม่ได้เข้าสู่ระบบ ✗", + "None": "ไม่มี", + "Bridge {bridge}: skipped (login command or DM room missing).": "บริดจ์ {bridge}: ข้ามแล้ว (ไม่มีคำสั่งเข้าสู่ระบบหรือห้อง DM)", + "Bridge {bridge}: sending {command}...": "บริดจ์ {bridge}: กำลังส่ง {command}…", + "Bridge {bridge}: sign-out failed.": "บริดจ์ {bridge}: ออกจากระบบไม่สำเร็จ", + "Bridge {bridge}: sign-in started.": "บริดจ์ {bridge}: เริ่มเข้าสู่ระบบแล้ว", + "Slack authorization completed. Return to LangBot to finish setup.": "อนุญาต Slack สำเร็จแล้ว กลับไปที่ LangBot เพื่อดำเนินการตั้งค่าให้เสร็จ", + "Slack authorization failed or expired. Return to LangBot for details.": "อนุญาต Slack ไม่สำเร็จหรือหมดอายุ ดูรายละเอียดใน LangBot", + "TOTP is already enabled for this Account": "บัญชีนี้เปิดใช้ TOTP แล้ว", + "TOTP is not enabled for this Account": "บัญชีนี้ยังไม่ได้เปิดใช้ TOTP", + "Invalid or expired passkey challenge": "คำขอยืนยัน Passkey ไม่ถูกต้องหรือหมดอายุ", + "Passkey credential not recognized": "ไม่รู้จัก Passkey นี้", + "Account is disabled": "บัญชีถูกปิดใช้งาน", + "Modifying login info is disabled": "ไม่อนุญาตให้แก้ไขข้อมูลเข้าสู่ระบบ", + "Invalid GitHub repository URL": "URL ที่เก็บ GitHub ไม่ถูกต้อง", + "Server is busy. Please try again later.": "เซิร์ฟเวอร์ไม่ว่าง โปรดลองอีกครั้งภายหลัง", + "This feature is unavailable in this Workspace.": "พื้นที่ทำงานนี้ไม่สามารถใช้ฟีเจอร์นี้ได้", + "The Workspace must retain an owner.": "พื้นที่ทำงานต้องมีเจ้าของอย่างน้อยหนึ่งคน", + "Invalid or expired invitation": "คำเชิญไม่ถูกต้องหรือหมดอายุ", + "This user is already a member.": "ผู้ใช้นี้เป็นสมาชิกอยู่แล้ว", + "Voice connection failed": "การเชื่อมต่อเสียงล้มเหลว", + "User is not in the voice channel": "ผู้ใช้ไม่ได้อยู่ในช่องเสียง", + "Voice service is unavailable": "บริการเสียงไม่พร้อมใช้งาน", + "Submission failed. Please try again.": "ส่งไม่สำเร็จ โปรดลองอีกครั้ง", + "File size exceeds the limit.": "ขนาดไฟล์เกินขีดจำกัด", + "File size exceeds 10MB limit. Please split large files into smaller parts.": "ไฟล์ใหญ่กว่า 10MB โปรดแบ่งเป็นไฟล์ย่อย", + "Authorization failed. Please try signing in again.": "การอนุญาตล้มเหลว โปรดเข้าสู่ระบบอีกครั้ง", + "Bind the LangBot Account with the same email as this local Account": "เชื่อมโยงบัญชี LangBot ที่ใช้อีเมลเดียวกับบัญชีในระบบนี้", + "Only local accounts can bind to Space": "เฉพาะบัญชีในระบบเท่านั้นที่เชื่อมโยงกับ Space ได้", + "System already initialized": "ระบบเริ่มต้นแล้ว", + "System not initialized": "ระบบยังไม่ได้เริ่มต้น", + "Password login is disabled on LangBot Cloud": "LangBot Cloud ปิดการเข้าสู่ระบบด้วยรหัสผ่าน", + "This edition supports one Workspace per instance": "รุ่นนี้รองรับพื้นที่ทำงานหนึ่งแห่งต่ออินสแตนซ์", + "Cloud Workspaces are created by the SaaS control plane": "พื้นที่ทำงาน Cloud สร้างจากแผงควบคุม SaaS", + "Login with your LangBot Account to accept this invitation": "เข้าสู่ระบบด้วยบัญชี LangBot เพื่อรับคำเชิญ", + "RAG migration is not needed": "ไม่จำเป็นต้องย้าย RAG", + "New Agent": "Agent ใหม่", + "New Pipeline": "ไปป์ไลน์ใหม่", + "Provider {provider} does not support {model_type} models": "ผู้ให้บริการ {provider} ไม่รองรับโมเดล {model_type}", + "Invalid JSON format": "รูปแบบ JSON ไม่ถูกต้อง", + "ChatGPT sign-in required. Open this provider and sign in again.": "ต้องเข้าสู่ระบบ ChatGPT โปรดเปิดผู้ให้บริการนี้แล้วเข้าสู่ระบบอีกครั้ง", + "ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "คำขอ ChatGPT ถูกจำกัดตามความถี่หรือปริมาณการใช้งาน โปรดลองภายหลังหรือตรวจสอบแผนของคุณ", + "ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT ปฏิเสธคำขอ โปรดตรวจสอบการสมัครสมาชิกและสิทธิ์ของพื้นที่ทำงาน", + "ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT ปฏิเสธโมเดลหรือคำขอ โปรดตรวจสอบโมเดลและการตั้งค่าคำขอ", + "ChatGPT Codex upstream request failed. Please retry later.": "คำขอไปยัง ChatGPT Codex ล้มเหลว โปรดลองอีกครั้งภายหลัง", + "ChatGPT subscription usage limit reached. Please retry later or check your plan.": "การใช้งานสมาชิก ChatGPT ถึงขีดจำกัดแล้ว โปรดลองภายหลังหรือตรวจสอบแผนของคุณ", + "ChatGPT Codex response failed or was incomplete. Please retry.": "การตอบกลับของ ChatGPT Codex ล้มเหลวหรือไม่ครบถ้วน โปรดลองอีกครั้ง", + "ChatGPT token refresh temporarily failed. Please retry.": "ไม่สามารถรีเฟรชโทเค็น ChatGPT ได้ชั่วคราว โปรดลองอีกครั้ง", + "A second factor is required": "ต้องยืนยันตัวตนสองขั้นตอน", + "Sign in or provide registration details to accept this invitation": "เข้าสู่ระบบหรือกรอกข้อมูลลงทะเบียนเพื่อยอมรับคำเชิญนี้", + "Sign out before creating the invited local Account": "ออกจากระบบก่อนสร้างบัญชีภายในที่ได้รับเชิญ", + "Turnstile verification failed": "การยืนยัน Turnstile ล้มเหลว โปรดลองอีกครั้ง", + "The invitation email does not match your Account": "อีเมลคำเชิญไม่ตรงกับบัญชีของคุณ", + "Failed to save ChatGPT credentials. Please retry.": "บันทึกข้อมูลรับรอง ChatGPT ไม่สำเร็จ โปรดลองอีกครั้ง", + "Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "ไม่สามารถเริ่มเข้าสู่ระบบด้วยอุปกรณ์ได้ โปรดเปิดการเข้าสู่ระบบด้วยรหัสอุปกรณ์ในการตั้งค่า ChatGPT", + "Option {number}": "ตัวเลือก {number}", + "Confirmation needed": "ต้องยืนยัน", + "Session reset successfully": "รีเซ็ตเซสชันแล้ว" +} diff --git a/src/langbot/pkg/i18n/locales/vi_VN.json b/src/langbot/pkg/i18n/locales/vi_VN.json new file mode 100644 index 000000000..39b3110a8 --- /dev/null +++ b/src/langbot/pkg/i18n/locales/vi_VN.json @@ -0,0 +1,114 @@ +{ + "Request failed.": "Yêu cầu thất bại.", + "Too many requests. Please try again later.": "Quá nhiều yêu cầu. Vui lòng thử lại sau.", + "Your message contains inappropriate content. Please revise it.": "Tin nhắn chứa nội dung không phù hợp. Vui lòng chỉnh sửa.", + "Content checking failed. Please contact the administrator.": "Kiểm tra nội dung thất bại. Vui lòng liên hệ quản trị viên.", + "Calling {tools}...": "Đang gọi {tools}…", + "Agent runner not found: {runner}": "Không tìm thấy trình chạy Agent: {runner}", + "Agent runner not authorized for this pipeline.": "Pipeline này không được phép sử dụng trình chạy Agent.", + "Agent runner temporarily unavailable. Please try again.": "Trình chạy Agent tạm thời không khả dụng. Vui lòng thử lại.", + "Agent runner execution failed.": "Thực thi Agent thất bại.", + "The Agent runner returned an invalid response.": "Trình chạy Agent trả về phản hồi không hợp lệ.", + "Model request failed": "Yêu cầu đến mô hình thất bại", + "Context length exceeded": "Đã vượt quá độ dài ngữ cảnh", + "Invalid request parameters": "Tham số yêu cầu không hợp lệ", + "Invalid API key": "Khóa API không hợp lệ", + "Invalid model or endpoint": "Mô hình hoặc địa chỉ API không hợp lệ", + "Rate limit exceeded or insufficient balance": "Vượt giới hạn yêu cầu hoặc số dư không đủ", + "Request timed out": "Yêu cầu đã hết thời gian chờ", + "Connection failed": "Kết nối thất bại", + "API error": "Lỗi API", + "Unknown error": "Lỗi không xác định", + "Internal server error": "Lỗi máy chủ nội bộ", + "Resource not found": "Không tìm thấy tài nguyên", + "Permission denied": "Bạn không có quyền thực hiện thao tác này", + "Invalid authentication credentials": "Xác thực thất bại. Vui lòng đăng nhập lại", + "MCP runtime failed": "Thực thi MCP thất bại", + "Task execution failed": "Thực thi tác vụ thất bại", + "Task was cancelled": "Tác vụ đã bị hủy", + "Maximum number of {resource} ({limit}) reached": "Đã đạt số lượng tối đa của {resource} ({limit})", + "extensions": "tiện ích mở rộng", + "knowledge bases": "cơ sở tri thức", + "MCP server already exists: {name}": "Máy chủ MCP đã tồn tại: {name}", + "LangBot Models is managed by Cloud and cannot be modified": "LangBot Models được quản lý bởi Cloud và không thể chỉnh sửa", + "Please sign in with your LangBot Account": "Vui lòng đăng nhập bằng tài khoản LangBot", + "Current password is incorrect": "Mật khẩu hiện tại không đúng", + "Invalid TOTP or recovery code": "Mã TOTP hoặc mã khôi phục không hợp lệ", + "No local password set, please set a password first": "Chưa có mật khẩu cục bộ. Vui lòng đặt mật khẩu trước", + "This LangBot Account is already bound to another user": "Tài khoản LangBot này đã được liên kết với người dùng khác", + "Invalid recovery key": "Khóa khôi phục không hợp lệ", + "Done": "Hoàn tất", + "Thank you for your feedback": "Cảm ơn bạn đã phản hồi", + "Failed to process feedback": "Không thể xử lý phản hồi", + "Helpful": "Hữu ích", + "Not helpful": "Không hữu ích", + "Select an option": "Chọn một mục", + "Enter a value": "Nhập giá trị", + "Submit": "Gửi", + "Submitted": "Đã gửi", + "Thinking": "Quá trình suy nghĩ", + "AI-generated": "Được tạo bởi AI", + "AI-generated content. Refer to the cited sources for details.": "Nội dung do AI tạo. Xem nguồn trích dẫn để biết chi tiết.", + "No bridges configured.": "Chưa cấu hình cầu nối nào.", + "Signing in to all bridges again...": "Đang đăng nhập lại vào tất cả cầu nối…", + "Bridge status:": "Trạng thái cầu nối:", + "Signed in ✓": "Đã đăng nhập ✓", + "Not signed in ✗": "Chưa đăng nhập ✗", + "None": "Không có", + "Bridge {bridge}: skipped (login command or DM room missing).": "Cầu nối {bridge}: đã bỏ qua (thiếu lệnh đăng nhập hoặc phòng DM).", + "Bridge {bridge}: sending {command}...": "Cầu nối {bridge}: đang gửi {command}…", + "Bridge {bridge}: sign-out failed.": "Cầu nối {bridge}: đăng xuất thất bại.", + "Bridge {bridge}: sign-in started.": "Cầu nối {bridge}: đã bắt đầu đăng nhập.", + "Slack authorization completed. Return to LangBot to finish setup.": "Đã cấp quyền Slack. Quay lại LangBot để hoàn tất thiết lập.", + "Slack authorization failed or expired. Return to LangBot for details.": "Cấp quyền Slack thất bại hoặc đã hết hạn. Xem chi tiết trong LangBot.", + "TOTP is already enabled for this Account": "Tài khoản này đã bật TOTP", + "TOTP is not enabled for this Account": "Tài khoản này chưa bật TOTP", + "Invalid or expired passkey challenge": "Yêu cầu xác minh passkey không hợp lệ hoặc đã hết hạn", + "Passkey credential not recognized": "Không nhận diện được passkey", + "Account is disabled": "Tài khoản đã bị vô hiệu hóa", + "Modifying login info is disabled": "Không được phép sửa thông tin đăng nhập", + "Invalid GitHub repository URL": "URL kho GitHub không hợp lệ", + "Server is busy. Please try again later.": "Máy chủ đang bận. Vui lòng thử lại sau.", + "This feature is unavailable in this Workspace.": "Tính năng này không khả dụng trong không gian làm việc này.", + "The Workspace must retain an owner.": "Không gian làm việc phải có ít nhất một chủ sở hữu.", + "Invalid or expired invitation": "Lời mời không hợp lệ hoặc đã hết hạn", + "This user is already a member.": "Người dùng này đã là thành viên.", + "Voice connection failed": "Kết nối thoại thất bại", + "User is not in the voice channel": "Người dùng không ở trong kênh thoại", + "Voice service is unavailable": "Dịch vụ thoại không khả dụng", + "Submission failed. Please try again.": "Gửi thất bại. Vui lòng thử lại.", + "File size exceeds the limit.": "Kích thước tệp vượt quá giới hạn.", + "File size exceeds 10MB limit. Please split large files into smaller parts.": "Tệp vượt quá 10MB. Vui lòng chia thành các phần nhỏ hơn.", + "Authorization failed. Please try signing in again.": "Cấp quyền thất bại. Vui lòng đăng nhập lại.", + "Bind the LangBot Account with the same email as this local Account": "Liên kết tài khoản LangBot có cùng email với tài khoản cục bộ này", + "Only local accounts can bind to Space": "Chỉ tài khoản cục bộ mới có thể liên kết với Space", + "System already initialized": "Hệ thống đã được khởi tạo", + "System not initialized": "Hệ thống chưa được khởi tạo", + "Password login is disabled on LangBot Cloud": "LangBot Cloud không cho phép đăng nhập bằng mật khẩu", + "This edition supports one Workspace per instance": "Phiên bản này hỗ trợ một không gian làm việc cho mỗi phiên bản triển khai", + "Cloud Workspaces are created by the SaaS control plane": "Không gian làm việc Cloud được tạo từ bảng điều khiển SaaS", + "Login with your LangBot Account to accept this invitation": "Đăng nhập bằng tài khoản LangBot để chấp nhận lời mời", + "RAG migration is not needed": "Không cần di chuyển RAG", + "New Agent": "Agent mới", + "New Pipeline": "Pipeline mới", + "Provider {provider} does not support {model_type} models": "Nhà cung cấp {provider} không hỗ trợ mô hình {model_type}", + "Invalid JSON format": "Định dạng JSON không hợp lệ", + "ChatGPT sign-in required. Open this provider and sign in again.": "Cần đăng nhập ChatGPT. Mở nhà cung cấp này và đăng nhập lại.", + "ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "Yêu cầu ChatGPT bị giới hạn tần suất hoặc mức sử dụng. Hãy thử lại sau hoặc kiểm tra gói của bạn.", + "ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT đã từ chối yêu cầu. Kiểm tra gói đăng ký và quyền của không gian làm việc.", + "ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT đã từ chối mô hình hoặc yêu cầu. Kiểm tra mô hình đã chọn và cài đặt yêu cầu.", + "ChatGPT Codex upstream request failed. Please retry later.": "Yêu cầu tới ChatGPT Codex thất bại. Vui lòng thử lại sau.", + "ChatGPT subscription usage limit reached. Please retry later or check your plan.": "Đã đạt giới hạn sử dụng gói ChatGPT. Hãy thử lại sau hoặc kiểm tra gói của bạn.", + "ChatGPT Codex response failed or was incomplete. Please retry.": "Phản hồi ChatGPT Codex thất bại hoặc chưa hoàn tất. Vui lòng thử lại.", + "ChatGPT token refresh temporarily failed. Please retry.": "Tạm thời không thể làm mới token ChatGPT. Vui lòng thử lại.", + "A second factor is required": "Cần xác thực hai yếu tố", + "Sign in or provide registration details to accept this invitation": "Đăng nhập hoặc cung cấp thông tin đăng ký để chấp nhận lời mời", + "Sign out before creating the invited local Account": "Đăng xuất trước khi tạo tài khoản cục bộ được mời", + "Turnstile verification failed": "Xác minh Turnstile thất bại. Vui lòng thử lại.", + "The invitation email does not match your Account": "Email trong lời mời không khớp với tài khoản của bạn", + "Failed to save ChatGPT credentials. Please retry.": "Không thể lưu thông tin xác thực ChatGPT. Vui lòng thử lại.", + "Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "Không thể bắt đầu đăng nhập thiết bị ChatGPT. Hãy bật đăng nhập bằng mã thiết bị trong cài đặt ChatGPT.", + "Option {number}": "Lựa chọn {number}", + "Confirmation needed": "Cần xác nhận", + "Session reset successfully": "Đã đặt lại phiên" +} diff --git a/src/langbot/pkg/i18n/locales/zh_Hans.json b/src/langbot/pkg/i18n/locales/zh_Hans.json new file mode 100644 index 000000000..29a12e840 --- /dev/null +++ b/src/langbot/pkg/i18n/locales/zh_Hans.json @@ -0,0 +1,114 @@ +{ + "Request failed.": "请求失败。", + "Too many requests. Please try again later.": "请求过于频繁,请稍后重试。", + "Your message contains inappropriate content. Please revise it.": "消息中存在不合适的内容,请修改。", + "Content checking failed. Please contact the administrator.": "内容检查失败,请联系管理员。", + "Calling {tools}...": "正在调用 {tools}…", + "Agent runner not found: {runner}": "找不到 Agent 运行器:{runner}", + "Agent runner not authorized for this pipeline.": "此流水线未获授权使用该 Agent 运行器。", + "Agent runner temporarily unavailable. Please try again.": "Agent 运行器暂时不可用,请重试。", + "Agent runner execution failed.": "Agent 执行失败。", + "The Agent runner returned an invalid response.": "Agent 运行器返回了无效响应。", + "Model request failed": "模型请求失败", + "Context length exceeded": "上下文长度超限", + "Invalid request parameters": "请求参数错误", + "Invalid API key": "API 密钥无效", + "Invalid model or endpoint": "模型或接口地址无效", + "Rate limit exceeded or insufficient balance": "请求过于频繁或余额不足", + "Request timed out": "请求超时", + "Connection failed": "连接失败", + "API error": "API 错误", + "Unknown error": "未知错误", + "Internal server error": "服务器内部错误", + "Resource not found": "资源不存在", + "Permission denied": "没有执行此操作的权限", + "Invalid authentication credentials": "身份验证失败,请重新登录", + "MCP runtime failed": "MCP 运行失败", + "Task execution failed": "任务执行失败", + "Task was cancelled": "任务已取消", + "Maximum number of {resource} ({limit}) reached": "{resource}数量已达到上限({limit})", + "extensions": "扩展", + "knowledge bases": "知识库", + "MCP server already exists: {name}": "MCP 服务器已存在:{name}", + "LangBot Models is managed by Cloud and cannot be modified": "LangBot Models 由云端管理,无法修改", + "Please sign in with your LangBot Account": "请使用 LangBot 账号登录", + "Current password is incorrect": "当前密码不正确", + "Invalid TOTP or recovery code": "验证码或恢复码无效", + "No local password set, please set a password first": "尚未设置本地密码,请先设置密码", + "This LangBot Account is already bound to another user": "此 LangBot 账号已绑定其他用户", + "Invalid recovery key": "恢复密钥无效", + "Done": "操作成功", + "Thank you for your feedback": "感谢您的反馈", + "Failed to process feedback": "反馈处理失败", + "Helpful": "有帮助", + "Not helpful": "无帮助", + "Select an option": "请选择", + "Enter a value": "请输入", + "Submit": "提交", + "Submitted": "已提交", + "Thinking": "思考过程", + "AI-generated": "内容由 AI 生成", + "AI-generated content. Refer to the cited sources for details.": "内容由 AI 生成,详情请参考引用来源。", + "No bridges configured.": "没有配置任何桥。", + "Signing in to all bridges again...": "正在重新登录所有桥…", + "Bridge status:": "桥状态:", + "Signed in ✓": "已登录 ✓", + "Not signed in ✗": "未登录 ✗", + "None": "无", + "Bridge {bridge}: skipped (login command or DM room missing).": "桥 {bridge}:已跳过(缺少登录命令或私聊房间)。", + "Bridge {bridge}: sending {command}...": "桥 {bridge}:正在发送 {command}…", + "Bridge {bridge}: sign-out failed.": "桥 {bridge}:退出登录失败。", + "Bridge {bridge}: sign-in started.": "桥 {bridge}:已开始登录。", + "Slack authorization completed. Return to LangBot to finish setup.": "Slack 授权完成,请返回 LangBot 完成配置。", + "Slack authorization failed or expired. Return to LangBot for details.": "Slack 授权失败或已过期,请返回 LangBot 查看详情。", + "TOTP is already enabled for this Account": "此账号已启用两步验证", + "TOTP is not enabled for this Account": "此账号尚未启用两步验证", + "Invalid or expired passkey challenge": "通行密钥验证请求无效或已过期", + "Passkey credential not recognized": "无法识别此通行密钥", + "Account is disabled": "账号已停用", + "Modifying login info is disabled": "不允许修改登录信息", + "Invalid GitHub repository URL": "GitHub 仓库地址无效", + "Server is busy. Please try again later.": "服务器繁忙,请稍后重试。", + "This feature is unavailable in this Workspace.": "此工作区无法使用该功能。", + "The Workspace must retain an owner.": "工作区必须保留一位所有者。", + "Invalid or expired invitation": "邀请无效或已过期", + "This user is already a member.": "此用户已是工作区成员。", + "Voice connection failed": "语音连接失败", + "User is not in the voice channel": "用户不在语音频道中", + "Voice service is unavailable": "语音服务不可用", + "Submission failed. Please try again.": "提交失败,请重试。", + "File size exceeds the limit.": "文件大小超过限制。", + "File size exceeds 10MB limit. Please split large files into smaller parts.": "文件大小超过 10MB,请拆分后上传。", + "Authorization failed. Please try signing in again.": "授权失败,请重新登录。", + "Bind the LangBot Account with the same email as this local Account": "请绑定与此本地账号邮箱相同的 LangBot 账号", + "Only local accounts can bind to Space": "只有本地账号可以绑定 Space", + "System already initialized": "系统已初始化", + "System not initialized": "系统尚未初始化", + "Password login is disabled on LangBot Cloud": "LangBot Cloud 不支持密码登录", + "This edition supports one Workspace per instance": "此版本每个实例仅支持一个工作区", + "Cloud Workspaces are created by the SaaS control plane": "云工作区由 SaaS 控制台创建", + "Login with your LangBot Account to accept this invitation": "请登录 LangBot 账号以接受邀请", + "RAG migration is not needed": "无需迁移知识库", + "New Agent": "新 Agent", + "New Pipeline": "新流水线", + "Provider {provider} does not support {model_type} models": "提供商 {provider} 不支持 {model_type} 模型", + "Invalid JSON format": "JSON 格式无效", + "ChatGPT sign-in required. Open this provider and sign in again.": "需要登录 ChatGPT。请打开此提供商并重新登录。", + "ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "ChatGPT 请求受限(请求频率或用量限制)。请稍后重试或检查套餐。", + "ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT 拒绝了此请求。请检查订阅和工作区权限。", + "ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT 拒绝了模型或请求。请检查所选模型和请求设置。", + "ChatGPT Codex upstream request failed. Please retry later.": "ChatGPT Codex 上游请求失败,请稍后重试。", + "ChatGPT subscription usage limit reached. Please retry later or check your plan.": "ChatGPT 订阅用量已达上限。请稍后重试或检查套餐。", + "ChatGPT Codex response failed or was incomplete. Please retry.": "ChatGPT Codex 响应失败或不完整,请重试。", + "ChatGPT token refresh temporarily failed. Please retry.": "ChatGPT 令牌暂时刷新失败,请重试。", + "A second factor is required": "需要二次验证", + "Sign in or provide registration details to accept this invitation": "请登录或填写注册信息以接受此邀请", + "Sign out before creating the invited local Account": "请先退出登录,再创建受邀本地账号", + "Turnstile verification failed": "人机验证失败,请重试", + "The invitation email does not match your Account": "邀请邮箱与您的账号不匹配", + "Failed to save ChatGPT credentials. Please retry.": "保存 ChatGPT 凭据失败,请重试。", + "Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "无法启动 ChatGPT 设备登录。请在 ChatGPT 设置中启用设备代码登录。", + "Option {number}": "选项 {number}", + "Confirmation needed": "需要确认", + "Session reset successfully": "会话已重置" +} diff --git a/src/langbot/pkg/i18n/locales/zh_Hant.json b/src/langbot/pkg/i18n/locales/zh_Hant.json new file mode 100644 index 000000000..15f5e90c3 --- /dev/null +++ b/src/langbot/pkg/i18n/locales/zh_Hant.json @@ -0,0 +1,114 @@ +{ + "Request failed.": "請求失敗。", + "Too many requests. Please try again later.": "請求過於頻繁,請稍後重試。", + "Your message contains inappropriate content. Please revise it.": "訊息中存在不適當的內容,請修改。", + "Content checking failed. Please contact the administrator.": "內容檢查失敗,請聯絡管理員。", + "Calling {tools}...": "正在呼叫 {tools}…", + "Agent runner not found: {runner}": "找不到 Agent 執行器:{runner}", + "Agent runner not authorized for this pipeline.": "此流水線未獲授權使用該 Agent 執行器。", + "Agent runner temporarily unavailable. Please try again.": "Agent 執行器暫時無法使用,請重試。", + "Agent runner execution failed.": "Agent 執行失敗。", + "The Agent runner returned an invalid response.": "Agent 執行器傳回了無效回應。", + "Model request failed": "模型請求失敗", + "Context length exceeded": "上下文長度超出限制", + "Invalid request parameters": "請求參數錯誤", + "Invalid API key": "API 金鑰無效", + "Invalid model or endpoint": "模型或介面位址無效", + "Rate limit exceeded or insufficient balance": "請求過於頻繁或餘額不足", + "Request timed out": "請求逾時", + "Connection failed": "連線失敗", + "API error": "API 錯誤", + "Unknown error": "未知錯誤", + "Internal server error": "伺服器內部錯誤", + "Resource not found": "資源不存在", + "Permission denied": "沒有執行此操作的權限", + "Invalid authentication credentials": "身分驗證失敗,請重新登入", + "MCP runtime failed": "MCP 執行失敗", + "Task execution failed": "任務執行失敗", + "Task was cancelled": "任務已取消", + "Maximum number of {resource} ({limit}) reached": "{resource}數量已達上限({limit})", + "extensions": "擴充功能", + "knowledge bases": "知識庫", + "MCP server already exists: {name}": "MCP 伺服器已存在:{name}", + "LangBot Models is managed by Cloud and cannot be modified": "LangBot Models 由雲端管理,無法修改", + "Please sign in with your LangBot Account": "請使用 LangBot 帳號登入", + "Current password is incorrect": "目前密碼不正確", + "Invalid TOTP or recovery code": "驗證碼或復原碼無效", + "No local password set, please set a password first": "尚未設定本機密碼,請先設定密碼", + "This LangBot Account is already bound to another user": "此 LangBot 帳號已綁定其他使用者", + "Invalid recovery key": "復原金鑰無效", + "Done": "操作成功", + "Thank you for your feedback": "感謝您的回饋", + "Failed to process feedback": "回饋處理失敗", + "Helpful": "有幫助", + "Not helpful": "沒有幫助", + "Select an option": "請選擇", + "Enter a value": "請輸入", + "Submit": "提交", + "Submitted": "已提交", + "Thinking": "思考過程", + "AI-generated": "內容由 AI 產生", + "AI-generated content. Refer to the cited sources for details.": "內容由 AI 產生,詳情請參考引用來源。", + "No bridges configured.": "尚未設定任何橋接。", + "Signing in to all bridges again...": "正在重新登入所有橋接…", + "Bridge status:": "橋接狀態:", + "Signed in ✓": "已登入 ✓", + "Not signed in ✗": "未登入 ✗", + "None": "無", + "Bridge {bridge}: skipped (login command or DM room missing).": "橋接 {bridge}:已略過(缺少登入命令或私訊房間)。", + "Bridge {bridge}: sending {command}...": "橋接 {bridge}:正在傳送 {command}…", + "Bridge {bridge}: sign-out failed.": "橋接 {bridge}:登出失敗。", + "Bridge {bridge}: sign-in started.": "橋接 {bridge}:已開始登入。", + "Slack authorization completed. Return to LangBot to finish setup.": "Slack 授權完成,請返回 LangBot 完成設定。", + "Slack authorization failed or expired. Return to LangBot for details.": "Slack 授權失敗或已過期,請返回 LangBot 查看詳情。", + "TOTP is already enabled for this Account": "此帳號已啟用兩步驗證", + "TOTP is not enabled for this Account": "此帳號尚未啟用兩步驗證", + "Invalid or expired passkey challenge": "通行金鑰驗證請求無效或已過期", + "Passkey credential not recognized": "無法識別此通行金鑰", + "Account is disabled": "帳號已停用", + "Modifying login info is disabled": "不允許修改登入資訊", + "Invalid GitHub repository URL": "GitHub 儲存庫網址無效", + "Server is busy. Please try again later.": "伺服器忙碌中,請稍後重試。", + "This feature is unavailable in this Workspace.": "此工作區無法使用該功能。", + "The Workspace must retain an owner.": "工作區必須保留一位擁有者。", + "Invalid or expired invitation": "邀請無效或已過期", + "This user is already a member.": "此使用者已是工作區成員。", + "Voice connection failed": "語音連線失敗", + "User is not in the voice channel": "使用者不在語音頻道中", + "Voice service is unavailable": "語音服務無法使用", + "Submission failed. Please try again.": "提交失敗,請重試。", + "File size exceeds the limit.": "檔案大小超過限制。", + "File size exceeds 10MB limit. Please split large files into smaller parts.": "檔案大小超過 10MB,請分割後上傳。", + "Authorization failed. Please try signing in again.": "授權失敗,請重新登入。", + "Bind the LangBot Account with the same email as this local Account": "請綁定與此本機帳號電子郵件相同的 LangBot 帳號", + "Only local accounts can bind to Space": "只有本機帳號可以綁定 Space", + "System already initialized": "系統已初始化", + "System not initialized": "系統尚未初始化", + "Password login is disabled on LangBot Cloud": "LangBot Cloud 不支援密碼登入", + "This edition supports one Workspace per instance": "此版本每個執行個體僅支援一個工作區", + "Cloud Workspaces are created by the SaaS control plane": "雲端工作區由 SaaS 控制台建立", + "Login with your LangBot Account to accept this invitation": "請登入 LangBot 帳號以接受邀請", + "RAG migration is not needed": "無需遷移知識庫", + "New Agent": "新 Agent", + "New Pipeline": "新流水線", + "Provider {provider} does not support {model_type} models": "提供商 {provider} 不支援 {model_type} 模型", + "Invalid JSON format": "JSON 格式無效", + "ChatGPT sign-in required. Open this provider and sign in again.": "需要登入 ChatGPT。請開啟此供應商並重新登入。", + "ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "ChatGPT 請求受限(請求頻率或用量限制)。請稍後重試或檢查方案。", + "ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT 拒絕了此請求。請檢查訂閱和工作區權限。", + "ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT 拒絕了模型或請求。請檢查所選模型和請求設定。", + "ChatGPT Codex upstream request failed. Please retry later.": "ChatGPT Codex 上游請求失敗,請稍後重試。", + "ChatGPT subscription usage limit reached. Please retry later or check your plan.": "ChatGPT 訂閱用量已達上限。請稍後重試或檢查方案。", + "ChatGPT Codex response failed or was incomplete. Please retry.": "ChatGPT Codex 回應失敗或不完整,請重試。", + "ChatGPT token refresh temporarily failed. Please retry.": "ChatGPT 權杖暫時更新失敗,請重試。", + "A second factor is required": "需要雙重驗證", + "Sign in or provide registration details to accept this invitation": "請登入或填寫註冊資料以接受此邀請", + "Sign out before creating the invited local Account": "請先登出,再建立受邀本機帳號", + "Turnstile verification failed": "人機驗證失敗,請重試", + "The invitation email does not match your Account": "邀請信箱與您的帳號不符", + "Failed to save ChatGPT credentials. Please retry.": "儲存 ChatGPT 憑證失敗,請重試。", + "Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "無法啟動 ChatGPT 裝置登入。請在 ChatGPT 設定中啟用裝置代碼登入。", + "Option {number}": "選項 {number}", + "Confirmation needed": "需要確認", + "Session reset successfully": "對話已重設" +} diff --git a/src/langbot/pkg/i18n/workspace.py b/src/langbot/pkg/i18n/workspace.py new file mode 100644 index 000000000..0527b818b --- /dev/null +++ b/src/langbot/pkg/i18n/workspace.py @@ -0,0 +1,71 @@ +"""Workspace-owned message language, stored in the existing tenant metadata.""" + +from __future__ import annotations + +import time +from collections import OrderedDict + +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import insert as pg_insert +from sqlalchemy.dialects.sqlite import insert as sqlite_insert + +from ..entity.persistence.metadata import WorkspaceMetadata +from . import DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES, normalize_language + +LANGUAGE_KEY = 'message_language' +_CACHE_TTL = 30 +_CACHE_SIZE = 256 + + +def _cache(ap) -> OrderedDict: + cache = getattr(ap, '_message_language_cache', None) + if cache is None: + cache = OrderedDict() + ap._message_language_cache = cache + return cache + + +async def get_workspace_language(ap, workspace_uuid: str, *, refresh: bool = False) -> str: + if not workspace_uuid: + return DEFAULT_LANGUAGE + cache = _cache(ap) + cached = cache.get(workspace_uuid) + if not refresh and cached and cached[1] > time.monotonic(): + return cached[0] + manager = getattr(ap, 'persistence_mgr', None) + if manager is None or not callable(getattr(manager, 'tenant_uow', None)): + return DEFAULT_LANGUAGE + async with manager.tenant_uow(workspace_uuid) as uow: + result = await uow.execute( + sa.select(WorkspaceMetadata.value).where( + WorkspaceMetadata.workspace_uuid == workspace_uuid, + WorkspaceMetadata.key == LANGUAGE_KEY, + ) + ) + locale = normalize_language(result.scalar_one_or_none()) + cache[workspace_uuid] = (locale, time.monotonic() + _CACHE_TTL) + cache.move_to_end(workspace_uuid) + while len(cache) > _CACHE_SIZE: + cache.popitem(last=False) + return locale + + +async def set_workspace_language(ap, workspace_uuid: str, locale: str) -> str: + if locale not in SUPPORTED_LANGUAGES: + raise ValueError('Unsupported Workspace language') + async with ap.persistence_mgr.tenant_uow(workspace_uuid) as uow: + dialect = ap.persistence_mgr.get_db_engine().dialect.name + insert = pg_insert if dialect == 'postgresql' else sqlite_insert + statement = insert(WorkspaceMetadata).values(workspace_uuid=workspace_uuid, key=LANGUAGE_KEY, value=locale) + await uow.execute( + statement.on_conflict_do_update( + index_elements=['workspace_uuid', 'key'], + set_={'value': locale}, + ) + ) + # Synchronous platform callbacks may run in SDK threads. Publish the new + # value only after commit, so they see changes without restarting a bot. + _cache(ap)[workspace_uuid] = (locale, time.monotonic() + _CACHE_TTL) + while len(_cache(ap)) > _CACHE_SIZE: + _cache(ap).popitem(last=False) + return locale diff --git a/src/langbot/pkg/pipeline/cntfilter/filters/baiduexamine.py b/src/langbot/pkg/pipeline/cntfilter/filters/baiduexamine.py index 8c74150b0..444fd4798 100644 --- a/src/langbot/pkg/pipeline/cntfilter/filters/baiduexamine.py +++ b/src/langbot/pkg/pipeline/cntfilter/filters/baiduexamine.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ....i18n import pipeline_notice + from .. import entities from .. import filter as filter_model import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query @@ -58,6 +60,10 @@ class BaiduCloudExamine(filter_model.ContentFilter): return entities.FilterResult( level=entities.ResultLevel.BLOCK, replacement=message, - user_notice='消息中存在不合适的内容, 请修改', + user_notice=pipeline_notice( + query, + 'content-filter-hint', + 'Your message contains inappropriate content. Please revise it.', + ), console_notice=f'百度云判定结果:{conclusion}', ) diff --git a/src/langbot/pkg/pipeline/cntfilter/filters/banwords.py b/src/langbot/pkg/pipeline/cntfilter/filters/banwords.py index bfd8ca772..32a3a5f30 100644 --- a/src/langbot/pkg/pipeline/cntfilter/filters/banwords.py +++ b/src/langbot/pkg/pipeline/cntfilter/filters/banwords.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ....i18n import pipeline_notice + from .. import filter as filter_model from .. import entities import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query @@ -35,13 +37,19 @@ class BanWordFilter(filter_model.ContentFilter): return entities.FilterResult( level=entities.ResultLevel.BLOCK, replacement='', - user_notice='内容检查规则执行失败,请联系管理员', + user_notice=pipeline_notice( + query, 'content-filter-error-hint', 'Content checking failed. Please contact the administrator.' + ), console_notice=f'Sensitive-word regex rejected: {exc}', ) return entities.FilterResult( level=entities.ResultLevel.MASKED if found else entities.ResultLevel.PASS, replacement=current, - user_notice='消息中存在不合适的内容, 请修改' if found else '', + user_notice=pipeline_notice( + query, 'content-filter-hint', 'Your message contains inappropriate content. Please revise it.' + ) + if found + else '', console_notice='', ) diff --git a/src/langbot/pkg/pipeline/pipelinemgr.py b/src/langbot/pkg/pipeline/pipelinemgr.py index e15be688f..fc7aaaf31 100644 --- a/src/langbot/pkg/pipeline/pipelinemgr.py +++ b/src/langbot/pkg/pipeline/pipelinemgr.py @@ -20,6 +20,8 @@ from ..api.http.context import ExecutionContext, PrincipalContext, PrincipalType from ..workspace.errors import WorkspaceError, WorkspaceInvariantError from .config_coercion import coerce_pipeline_config from .pool import get_query_execution_context +from ..i18n import language_scope +from ..i18n.workspace import get_workspace_language from ..agent.runner.config_resolver import RunnerConfigResolver from .extension_preferences import normalize_extension_preferences @@ -414,6 +416,11 @@ class RuntimePipeline: return event_type[:160] or 'message.received' async def process_query(self, query: pipeline_query.Query): + workspace_uuid = getattr(query, 'workspace_uuid', None) or getattr(self, 'workspace_uuid', '') + with language_scope(await get_workspace_language(self.ap, workspace_uuid)): + return await self._process_query_with_language(query) + + async def _process_query_with_language(self, query: pipeline_query.Query): from ..telemetry import trace as trace_mod from ..telemetry.execution import ingress from ..telemetry.execution import record as record_execution diff --git a/src/langbot/pkg/pipeline/process/handlers/chat.py b/src/langbot/pkg/pipeline/process/handlers/chat.py index 1bc6143b9..5226d56b0 100644 --- a/src/langbot/pkg/pipeline/process/handlers/chat.py +++ b/src/langbot/pkg/pipeline/process/handlers/chat.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ....i18n import pipeline_notice + import uuid import typing import traceback @@ -238,14 +240,20 @@ class ChatMessageHandler(handler.MessageHandler): # Handle specific runner errors with appropriate messages if isinstance(e, RunnerNotFoundError): - user_notice = f'Agent runner not found: {e.runner_id}' + user_notice = pipeline_notice( + query, 'runner-not-found-hint', 'Agent runner not found: {runner}', runner=e.runner_id + ) elif isinstance(e, RunnerNotAuthorizedError): - user_notice = 'Agent runner not authorized for this pipeline' + user_notice = pipeline_notice( + query, 'runner-not-authorized-hint', 'Agent runner not authorized for this pipeline.' + ) elif isinstance(e, RunnerExecutionError): if e.retryable: - user_notice = 'Agent runner temporarily unavailable. Please try again.' + user_notice = pipeline_notice( + query, 'runner-unavailable-hint', 'Agent runner temporarily unavailable. Please try again.' + ) else: - user_notice = 'Agent runner execution failed.' + user_notice = pipeline_notice(query, 'failure-hint', 'Agent runner execution failed.') else: # Use existing exception handling exception_handling = query.pipeline_config['output']['misc'].get('exception-handling', 'show-hint') @@ -253,7 +261,7 @@ class ChatMessageHandler(handler.MessageHandler): if exception_handling == 'show-error': user_notice = f'{e}' elif exception_handling == 'show-hint': - user_notice = query.pipeline_config['output']['misc'].get('failure-hint', 'Request failed.') + user_notice = pipeline_notice(query, 'failure-hint', 'Request failed.') else: # hide user_notice = None diff --git a/src/langbot/pkg/pipeline/ratelimit/ratelimit.py b/src/langbot/pkg/pipeline/ratelimit/ratelimit.py index cab62b8d5..8ee1bb639 100644 --- a/src/langbot/pkg/pipeline/ratelimit/ratelimit.py +++ b/src/langbot/pkg/pipeline/ratelimit/ratelimit.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ...i18n import pipeline_notice + import typing from .. import entities, stage @@ -62,7 +64,7 @@ class RateLimit(stage.PipelineStage): result_type=entities.ResultType.INTERRUPT, new_query=query, console_notice=f'根据限速规则忽略 {query.launcher_type.value}:{query.launcher_id} 消息', - user_notice='请求数超过限速器设定值,已丢弃本消息。', + user_notice=pipeline_notice(query, 'rate-limit-hint', 'Too many requests. Please try again later.'), ) elif stage_inst_name == 'ReleaseRateLimitOccupancy': await self.algo.release_access( diff --git a/src/langbot/pkg/pipeline/wrapper/wrapper.py b/src/langbot/pkg/pipeline/wrapper/wrapper.py index 9ea275e1d..2dcda3a05 100644 --- a/src/langbot/pkg/pipeline/wrapper/wrapper.py +++ b/src/langbot/pkg/pipeline/wrapper/wrapper.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ...i18n import pipeline_notice + import typing from .. import entities @@ -175,7 +177,9 @@ class ResponseWrapper(stage.PipelineStage): if result.tool_calls is not None and len(result.tool_calls) > 0: # 有函数调用 function_names = [tc.function.name for tc in result.tool_calls] - reply_text = f'Call {".".join(function_names)}...' + reply_text = pipeline_notice( + query, 'tool-call-hint', 'Calling {tools}...', tools=', '.join(function_names) + ) # Only surface the tool-call notice when the pipeline option # is enabled. Emitting it unconditionally would become the diff --git a/src/langbot/pkg/platform/adapters/lark/adapter.py b/src/langbot/pkg/platform/adapters/lark/adapter.py index df820ae74..aa04ae8a7 100644 --- a/src/langbot/pkg/platform/adapters/lark/adapter.py +++ b/src/langbot/pkg/platform/adapters/lark/adapter.py @@ -1,5 +1,7 @@ from __future__ import annotations +from langbot.pkg.i18n.adapters import adapter_t, localized_adapter + from langbot.pkg.platform.sources.lark import ( LarkAdapter as LegacyLarkAdapter, NonBlockingLarkWSClient, @@ -347,6 +349,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte self.request_tenant_access_token(tenant_key) return self.tenant_access_tokens.get(tenant_key, {}).get('token') + @localized_adapter async def send_message( self, target_type: str, @@ -380,6 +383,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte message_id=message_ids[-1] if message_ids else '', raw={'message_ids': message_ids} ) + @localized_adapter async def reply_message( self, message_source: platform_events.MessageEvent, @@ -538,6 +542,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte raise RuntimeError(f'Lark card update failed: {response.code} {response.msg}') self.closed_streaming_cards.add(card_id) + @localized_adapter async def reply_message_chunk( self, message_source: platform_events.MessageEvent, @@ -592,6 +597,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte self.card_last_update_dict.pop(card_id, None) self.closed_streaming_cards.discard(card_id) + @localized_adapter async def call_platform_api(self, action: str, params: dict = {}) -> dict: if action == 'interaction.request': return await send_interaction(self, params) @@ -632,6 +638,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte return f'{event.group.id}_{thread_id}' return None + @localized_adapter async def handle_unified_webhook(self, bot_uuid: str, path: str, request): try: data = await request.json @@ -668,7 +675,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte feedback_event = self._feedback_event_from_webhook(data) if feedback_event and platform_events.FeedbackEvent in self.listeners: await self.listeners[platform_events.FeedbackEvent](feedback_event, self) - return {'toast': {'type': 'success', 'content': '感谢您的反馈'}} + return {'toast': {'type': 'success', 'content': adapter_t(self, 'Thank you for your feedback')}} await self._dispatch_eba_event(LarkEventConverter.platform_specific(data, event_type, data)) return {'code': 200, 'message': 'ok'} except Exception: @@ -689,6 +696,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte def _webhook_header(self, data: dict): return type('LarkWebhookHeader', (), data.get('header', {}))() + @localized_adapter async def run_async(self): self.event_loop = asyncio.get_running_loop() if self.config.get('app_type', 'self') != 'isv': @@ -892,12 +900,13 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte self._submit_coro(self.listeners[platform_events.FeedbackEvent](feedback_event, self)) from lark_oapi.event.callback.model.p2_card_action_trigger import P2CardActionTriggerResponse - return P2CardActionTriggerResponse({'toast': {'type': 'success', 'content': '感谢您的反馈'}}) + return P2CardActionTriggerResponse( + {'toast': {'type': 'success', 'content': adapter_t(self, 'Thank you for your feedback')}} + ) - @staticmethod - def _interaction_action_response(event: platform_events.PlatformSpecificEvent) -> dict[str, typing.Any]: + def _interaction_action_response(self, event: platform_events.PlatformSpecificEvent) -> dict[str, typing.Any]: response: dict[str, typing.Any] = { - 'toast': {'type': 'success', 'content': 'Submitted / 已提交'}, + 'toast': {'type': 'success', 'content': adapter_t(self, 'Submitted')}, } if not event.data.get('cardkit'): response['card'] = { @@ -907,7 +916,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte 'elements': [ { 'tag': 'div', - 'text': {'tag': 'lark_md', 'content': '**Submitted / 已提交**'}, + 'text': {'tag': 'lark_md', 'content': '**' + adapter_t(self, 'Submitted') + '**'}, } ], }, diff --git a/src/langbot/pkg/platform/adapters/lark/interaction.py b/src/langbot/pkg/platform/adapters/lark/interaction.py index 9c9873125..8896f6316 100644 --- a/src/langbot/pkg/platform/adapters/lark/interaction.py +++ b/src/langbot/pkg/platform/adapters/lark/interaction.py @@ -2,6 +2,8 @@ from __future__ import annotations +from langbot.pkg.i18n import t + import json import time import typing @@ -61,8 +63,7 @@ def _field_form_elements( component_name = 'lbi_field_0' label = str(field.get('label') or field_id) placeholder = str( - field.get('placeholder') - or ('Select an option / 请选择' if field_type == 'select' else 'Enter a value / 请输入') + field.get('placeholder') or (t('Select an option') if field_type == 'select' else t('Enter a value')) ) required = bool(field.get('required')) if field_type == 'select': @@ -122,7 +123,7 @@ def _field_form_elements( submit_button = { 'tag': 'button', 'name': 'lbi_submit', - 'text': {'tag': 'plain_text', 'content': 'Submit / 提交'}, + 'text': {'tag': 'plain_text', 'content': t('Submit')}, 'type': 'primary', 'width': 'fill', 'form_action_type': 'submit', diff --git a/src/langbot/pkg/platform/adapters/lark/stream_card.py b/src/langbot/pkg/platform/adapters/lark/stream_card.py index 332400a82..cd5adbd56 100644 --- a/src/langbot/pkg/platform/adapters/lark/stream_card.py +++ b/src/langbot/pkg/platform/adapters/lark/stream_card.py @@ -2,6 +2,8 @@ import re +from langbot.pkg.i18n import t + def split_content(content: str) -> tuple[str, str]: reasoning, answer = [], [] @@ -28,7 +30,7 @@ def streaming_text(content: str) -> str: if len(reasoning) > 360: preview = '…' + preview quote = '> ' + preview.replace('\n', '\n> ') - return '**思考过程 / Thinking**\n\n' + quote + ('\n\n---\n\n' + answer if answer else '') + return '**' + t('Thinking') + '**\n\n' + quote + ('\n\n---\n\n' + answer if answer else '') def build_card(content: str = '', *, streaming: bool = False, finished: bool = True) -> dict: @@ -42,7 +44,7 @@ def build_card(content: str = '', *, streaming: bool = False, finished: bool = T { 'tag': 'collapsible_panel', 'expanded': not finished, - 'header': {'title': {'tag': 'plain_text', 'content': '思考过程 / Thinking'}}, + 'header': {'title': {'tag': 'plain_text', 'content': t('Thinking')}}, 'elements': [{'tag': 'markdown', 'content': reasoning}], } ) @@ -58,9 +60,7 @@ def build_card(content: str = '', *, streaming: bool = False, finished: bool = T 'width': 'weighted', 'weight': 1, 'vertical_align': 'center', - 'elements': [ - {'tag': 'markdown', 'content': '内容由 AI 生成 / AI-generated', 'text_size': 'notation'} - ], + 'elements': [{'tag': 'markdown', 'content': t('AI-generated'), 'text_size': 'notation'}], }, { 'tag': 'column', diff --git a/src/langbot/pkg/platform/botmgr.py b/src/langbot/pkg/platform/botmgr.py index dfb99d292..93b7a8d2f 100644 --- a/src/langbot/pkg/platform/botmgr.py +++ b/src/langbot/pkg/platform/botmgr.py @@ -1,5 +1,8 @@ from __future__ import annotations +from ..i18n import language_scope +from ..i18n.workspace import get_workspace_language + import asyncio import contextlib import dataclasses @@ -1713,21 +1716,22 @@ class RuntimeBot: def tenant_scoped_listener(listener): @functools.wraps(listener) async def wrapped(*args, **kwargs): - tenant_scope = getattr(self.ap.persistence_mgr, 'tenant_scope', None) - cloud_runtime = ( - getattr( - getattr(self.ap.persistence_mgr, 'mode', None), - 'value', - None, + with language_scope(await get_workspace_language(self.ap, self.workspace_uuid)): + tenant_scope = getattr(self.ap.persistence_mgr, 'tenant_scope', None) + cloud_runtime = ( + getattr( + getattr(self.ap.persistence_mgr, 'mode', None), + 'value', + None, + ) + == 'cloud_runtime' ) - == 'cloud_runtime' - ) - if cloud_runtime: - if not callable(tenant_scope): - raise RuntimeError('Cloud platform callbacks require a tenant scope') - async with tenant_scope(self.workspace_uuid): - return await listener(*args, **kwargs) - return await listener(*args, **kwargs) + if cloud_runtime: + if not callable(tenant_scope): + raise RuntimeError('Cloud platform callbacks require a tenant scope') + async with tenant_scope(self.workspace_uuid): + return await listener(*args, **kwargs) + return await listener(*args, **kwargs) return wrapped diff --git a/src/langbot/pkg/platform/sources/discord.py b/src/langbot/pkg/platform/sources/discord.py index e7119e391..1d9a29234 100644 --- a/src/langbot/pkg/platform/sources/discord.py +++ b/src/langbot/pkg/platform/sources/discord.py @@ -1,5 +1,8 @@ from __future__ import annotations +from langbot.pkg.i18n import t +from langbot.pkg.i18n.adapters import adapter_t, localized_adapter + import discord from discord import ui as discord_ui @@ -219,11 +222,13 @@ class VoiceConnectionManager: # 获取服务器和频道对象 guild = self.bot.get_guild(guild_id) if not guild: - raise VoiceConnectionError(f'无法找到服务器 {guild_id}', 'GUILD_NOT_FOUND', guild_id) + raise VoiceConnectionError(t('Resource not found') + f': {guild_id}', 'GUILD_NOT_FOUND', guild_id) channel = guild.get_channel(channel_id) if not channel or not isinstance(channel, discord.VoiceChannel): - raise VoiceConnectionError(f'无法找到语音频道 {channel_id}', 'CHANNEL_NOT_FOUND', guild_id) + raise VoiceConnectionError( + t('Resource not found') + f': {channel_id}', 'CHANNEL_NOT_FOUND', guild_id + ) # 验证用户是否在语音频道中(如果提供了用户ID) if user_id: @@ -258,12 +263,14 @@ class VoiceConnectionManager: return voice_client except discord.ClientException as e: - raise VoiceNetworkError(f'Discord 客户端错误: {str(e)}') + raise VoiceNetworkError(t('Voice connection failed') + f': {e}') except discord.opus.OpusNotLoaded as e: - raise VoiceConnectionError(f'Opus 编码器未加载: {str(e)}', 'OPUS_NOT_LOADED', guild_id) + raise VoiceConnectionError( + t('Voice service is unavailable') + f': Opus: {e}', 'OPUS_NOT_LOADED', guild_id + ) except Exception as e: await self.logger.error(f'连接语音频道时发生未知错误: {str(e)}') - raise VoiceConnectionError(f'连接失败: {str(e)}', 'UNKNOWN_ERROR', guild_id) + raise VoiceConnectionError(t('Voice connection failed') + f': {e}', 'UNKNOWN_ERROR', guild_id) async def leave_voice_channel(self, guild_id: int) -> bool: """ @@ -494,11 +501,13 @@ class VoiceConnectionManager: """ member = guild.get_member(user_id) if not member: - raise VoicePermissionError(f'无法找到用户 {user_id}', ['member_not_found'], user_id, channel.id) + raise VoicePermissionError( + t('Resource not found') + f': {user_id}', ['member_not_found'], user_id, channel.id + ) if not member.voice or member.voice.channel != channel: raise VoicePermissionError( - f'用户 {member.display_name} 不在语音频道 {channel.name} 中', + t('User is not in the voice channel') + f': {member.display_name}, {channel.name}', ['user_not_in_channel'], user_id, channel.id, @@ -528,7 +537,7 @@ class VoiceConnectionManager: if missing_permissions: raise VoicePermissionError( - f'机器人在频道 {channel.name} 中缺少权限: {", ".join(missing_permissions)}', + t('Permission denied') + f': {channel.name}: {", ".join(missing_permissions)}', missing_permissions, channel_id=channel.id, ) @@ -843,7 +852,7 @@ class DiscordFormView(discord_ui.View): # silently — most Dify forms have ≤10 actions in practice. for idx, action in enumerate(actions[:25]): action_id = str(action.get('id') or '') - label = str(action.get('title') or action_id or f'Option {idx + 1}') + label = str(action.get('title') or action_id or adapter_t(adapter, 'Option {number}', number=idx + 1)) style = self._STYLE_MAP.get( str(action.get('button_style') or '').lower(), discord.ButtonStyle.secondary, @@ -966,6 +975,7 @@ class DiscordAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): self._pending_forms.pop(next(iter(self._pending_forms)), None) # Voice functionality methods + @localized_adapter async def join_voice_channel(self, guild_id: int, channel_id: int, user_id: int = None) -> discord.VoiceClient: """ 加入语音频道 @@ -990,7 +1000,7 @@ class DiscordAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): VoiceConnectionError: 其他连接错误 """ if not self.voice_manager: - raise VoiceConnectionError('语音管理器未初始化', 'MANAGER_NOT_READY') + raise VoiceConnectionError(t('Voice service is unavailable'), 'MANAGER_NOT_READY') return await self.voice_manager.join_voice_channel(guild_id, channel_id, user_id) @@ -1358,7 +1368,7 @@ class DiscordAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): ) return - node_title = str(form_data.get('node_title') or 'Confirmation needed') + node_title = str(form_data.get('node_title') or adapter_t(self, 'Confirmation needed')) form_content = str(form_data.get('form_content') or '').strip() # Two paths: diff --git a/src/langbot/pkg/platform/sources/lark.py b/src/langbot/pkg/platform/sources/lark.py index b4c4dc827..c6c2c9455 100644 --- a/src/langbot/pkg/platform/sources/lark.py +++ b/src/langbot/pkg/platform/sources/lark.py @@ -1,5 +1,7 @@ from __future__ import annotations +from langbot.pkg.i18n.adapters import adapter_t, localized_adapter + import lark_oapi from lark_oapi.api.im.v1 import CreateImageRequest, CreateImageRequestBody, CreateFileRequest, CreateFileRequestBody import traceback @@ -1240,7 +1242,9 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): from lark_oapi.event.callback.model.p2_card_action_trigger import P2CardActionTriggerResponse - return P2CardActionTriggerResponse({'toast': {'type': 'success', 'content': '操作成功'}}) + return P2CardActionTriggerResponse( + {'toast': {'type': 'success', 'content': adapter_t(self, 'Done')}} + ) if action_value == '有帮助': feedback_type = 1 @@ -1249,7 +1253,9 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): else: from lark_oapi.event.callback.model.p2_card_action_trigger import P2CardActionTriggerResponse - return P2CardActionTriggerResponse({'toast': {'type': 'success', 'content': '操作成功'}}) + return P2CardActionTriggerResponse( + {'toast': {'type': 'success', 'content': adapter_t(self, 'Done')}} + ) operator = getattr(event.event, 'operator', None) context = getattr(event.event, 'context', None) @@ -1286,13 +1292,17 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): from lark_oapi.event.callback.model.p2_card_action_trigger import P2CardActionTriggerResponse - return P2CardActionTriggerResponse({'toast': {'type': 'success', 'content': '感谢您的反馈'}}) + return P2CardActionTriggerResponse( + {'toast': {'type': 'success', 'content': adapter_t(self, 'Thank you for your feedback')}} + ) except Exception: traceback.print_exc() schedule_on_app_loop(self.logger.error(f'Error in lark card action callback: {traceback.format_exc()}')) from lark_oapi.event.callback.model.p2_card_action_trigger import P2CardActionTriggerResponse - return P2CardActionTriggerResponse({'toast': {'type': 'error', 'content': '反馈处理失败'}}) + return P2CardActionTriggerResponse( + {'toast': {'type': 'error', 'content': adapter_t(self, 'Failed to process feedback')}} + ) event_handler = ( lark_oapi.EventDispatcherHandler.builder('', '') @@ -1545,6 +1555,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): return True return False + @localized_adapter async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain): text_elements, media_items = await self.message_converter.yiri2target(message, self.api_client) @@ -1809,7 +1820,11 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): 'elements': [ { 'tag': 'markdown', - 'content': '以上内容由 AI 生成,仅供参考。更多详细、准确信息可点击引用链接查看', + 'content': '' + + adapter_t( + self, 'AI-generated content. Refer to the cited sources for details.' + ) + + '', 'text_align': 'left', 'text_size': 'notation', 'margin': '4px 0px 0px 0px', @@ -1840,7 +1855,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): 'width': 'fill', 'size': 'medium', 'icon': {'tag': 'standard_icon', 'token': 'thumbsup_outlined'}, - 'hover_tips': {'tag': 'plain_text', 'content': '有帮助'}, + 'hover_tips': {'tag': 'plain_text', 'content': adapter_t(self, 'Helpful')}, 'behaviors': [{'type': 'callback', 'value': {'feedback': '有帮助'}}], 'margin': '0px 0px 0px 0px', } @@ -1864,7 +1879,10 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): 'width': 'default', 'size': 'medium', 'icon': {'tag': 'standard_icon', 'token': 'thumbdown_outlined'}, - 'hover_tips': {'tag': 'plain_text', 'content': '无帮助'}, + 'hover_tips': { + 'tag': 'plain_text', + 'content': adapter_t(self, 'Not helpful'), + }, 'behaviors': [{'type': 'callback', 'value': {'feedback': '无帮助'}}], 'margin': '0px 0px 0px 0px', } @@ -2064,6 +2082,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): ) return new_card_id + @localized_adapter async def reply_message( self, message_source: platform_events.MessageEvent, @@ -2174,6 +2193,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): f'client.im.v1.message.reply ({media["msg_type"]}) failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}, resp: \n{json.dumps(json.loads(response.raw.content), indent=4, ensure_ascii=False)}' ) + @localized_adapter async def reply_message_chunk( self, message_source: platform_events.MessageEvent, @@ -2491,7 +2511,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): 'tag': 'select_static', 'name': component_name, 'label': {'tag': 'plain_text', 'content': field_name}, - 'placeholder': {'tag': 'plain_text', 'content': '请选择'}, + 'placeholder': {'tag': 'plain_text', 'content': adapter_t(self, 'Select an option')}, 'options': [ { 'text': {'tag': 'plain_text', 'content': option}, @@ -2525,7 +2545,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): 'tag': 'input', 'name': component_name, 'label': {'tag': 'plain_text', 'content': field_name}, - 'placeholder': {'tag': 'plain_text', 'content': '请输入'}, + 'placeholder': {'tag': 'plain_text', 'content': adapter_t(self, 'Enter a value')}, 'default_value': _dify_default_value(field), 'width': 'fill', 'required': False, @@ -2820,7 +2840,11 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): 'elements': [ { 'tag': 'markdown', - 'content': '以上内容由 AI 生成,仅供参考。更多详细、准确信息可点击引用链接查看', + 'content': '' + + adapter_t( + self, 'AI-generated content. Refer to the cited sources for details.' + ) + + '', 'text_align': 'left', 'text_size': 'notation', 'margin': '4px 0px 0px 0px', @@ -2855,7 +2879,10 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): 'width': 'fill', 'size': 'medium', 'icon': {'tag': 'standard_icon', 'token': 'thumbsup_outlined'}, - 'hover_tips': {'tag': 'plain_text', 'content': '有帮助'}, + 'hover_tips': { + 'tag': 'plain_text', + 'content': adapter_t(self, 'Helpful'), + }, 'behaviors': [{'type': 'callback', 'value': {'feedback': '有帮助'}}], 'margin': '0px 0px 0px 0px', } @@ -2879,7 +2906,10 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): 'width': 'default', 'size': 'medium', 'icon': {'tag': 'standard_icon', 'token': 'thumbdown_outlined'}, - 'hover_tips': {'tag': 'plain_text', 'content': '无帮助'}, + 'hover_tips': { + 'tag': 'plain_text', + 'content': adapter_t(self, 'Not helpful'), + }, 'behaviors': [{'type': 'callback', 'value': {'feedback': '无帮助'}}], 'margin': '0px 0px 0px 0px', } @@ -2973,6 +3003,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): else: return data['type'] + @localized_adapter async def handle_unified_webhook(self, bot_uuid: str, path: str, request): """处理统一 webhook 请求。 Args: @@ -3132,14 +3163,14 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): }, ) - return {'toast': {'type': 'success', 'content': '操作成功'}} + return {'toast': {'type': 'success', 'content': adapter_t(self, 'Done')}} if action_value == '有帮助': feedback_type = 1 elif action_value == '无帮助': feedback_type = 2 else: - return {'toast': {'type': 'success', 'content': '操作成功'}} + return {'toast': {'type': 'success', 'content': adapter_t(self, 'Done')}} user_id = operator.get('open_id') or operator.get('user_id') open_chat_id = context_data.get('open_chat_id') @@ -3171,10 +3202,10 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): if platform_events.FeedbackEvent in self.listeners: await self.listeners[platform_events.FeedbackEvent](feedback_event, self) - return {'toast': {'type': 'success', 'content': '感谢您的反馈'}} + return {'toast': {'type': 'success', 'content': adapter_t(self, 'Thank you for your feedback')}} except Exception: await self.logger.error(f'Error in lark card action callback: {traceback.format_exc()}') - return {'toast': {'type': 'error', 'content': '反馈处理失败'}} + return {'toast': {'type': 'error', 'content': adapter_t(self, 'Failed to process feedback')}} elif 'im.chat.member.bot.added_v1' == type: try: @@ -3227,6 +3258,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): await self.logger.error(f'Error in lark callback: {traceback.format_exc()}') return {'code': 500, 'message': 'error'} + @localized_adapter async def run_async(self): enable_webhook = self.config['enable-webhook'] diff --git a/src/langbot/pkg/platform/sources/matrix.py b/src/langbot/pkg/platform/sources/matrix.py index f0a9822e3..3fc311392 100644 --- a/src/langbot/pkg/platform/sources/matrix.py +++ b/src/langbot/pkg/platform/sources/matrix.py @@ -1,5 +1,8 @@ from __future__ import annotations +from langbot.pkg.i18n import t +from langbot.pkg.i18n.adapters import localized_adapter + import typing import asyncio import traceback @@ -666,25 +669,26 @@ class MatrixAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): except Exception: await self.logger.error(f'[{bridge.user_id}] Bridge status check fatal error: {traceback.format_exc()}') + @localized_adapter async def _handle_relogin_command(self, room_id: str): """Handle !relogin command: logout then re-login all bridges.""" if not self._bridges: await self.client.room_send( room_id=room_id, message_type='m.room.message', - content={'msgtype': 'm.text', 'body': '没有配置任何桥。'}, + content={'msgtype': 'm.text', 'body': t('No bridges configured.')}, ) return - lines = ['开始重新登录所有桥...'] + lines = [t('Signing in to all bridges again...')] for bridge in self._bridges: if not bridge.login_command or not bridge.dm_room_id: - lines.append(f'[{bridge.user_id}] 跳过(未配置登录命令或无DM房间)') + lines.append(t('Bridge {bridge}: skipped (login command or DM room missing).', bridge=bridge.user_id)) continue # Use configured logout command, fallback to deriving from login command logout_cmd = bridge.logout_command or bridge.login_command.replace('login', 'logout') - lines.append(f'[{bridge.user_id}] 发送 "{logout_cmd}"...') + lines.append(t('Bridge {bridge}: sending {command}...', bridge=bridge.user_id, command=logout_cmd)) # Cancel existing tasks if bridge.login_task and not bridge.login_task.done(): @@ -700,14 +704,14 @@ class MatrixAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): content={'msgtype': 'm.text', 'body': logout_cmd}, ) except Exception as e: - lines.append(f'[{bridge.user_id}] logout 发送失败: {e}') + lines.append(t('Bridge {bridge}: sign-out failed.', bridge=bridge.user_id) + f' {e}') await asyncio.sleep(2) # Reset state and restart login bridge.logged_in = False self._restart_bridge_login(bridge) - lines.append(f'[{bridge.user_id}] 已触发重新登录') + lines.append(t('Bridge {bridge}: sign-in started.', bridge=bridge.user_id)) await self.client.room_send( room_id=room_id, @@ -715,20 +719,21 @@ class MatrixAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter): content={'msgtype': 'm.text', 'body': '\n'.join(lines)}, ) + @localized_adapter async def _handle_status_command(self, room_id: str): """Handle !status command: show bridge states.""" if not self._bridges: await self.client.room_send( room_id=room_id, message_type='m.room.message', - content={'msgtype': 'm.text', 'body': '没有配置任何桥。'}, + content={'msgtype': 'm.text', 'body': t('No bridges configured.')}, ) return - lines = ['桥状态:'] + lines = [t('Bridge status:')] for bridge in self._bridges: - status = '已登录 ✓' if bridge.logged_in else '未登录 ✗' - dm = bridge.dm_room_id or '无' + status = t('Signed in ✓') if bridge.logged_in else t('Not signed in ✗') + dm = bridge.dm_room_id or t('None') lines.append(f'• {bridge.user_id}: {status} (DM: {dm})') await self.client.room_send( room_id=room_id, diff --git a/src/langbot/pkg/provider/modelmgr/codex_auth.py b/src/langbot/pkg/provider/modelmgr/codex_auth.py index 571a67b40..1f6b618ec 100644 --- a/src/langbot/pkg/provider/modelmgr/codex_auth.py +++ b/src/langbot/pkg/provider/modelmgr/codex_auth.py @@ -23,6 +23,7 @@ from ...api.http.context import PrincipalType, RequestContext from ...api.http.authz import Permission, has_permission from ...api.http.service.tenant import require_workspace_uuid from ...workspace.errors import WorkspaceNotFoundError +from ...i18n import t REQUESTER = 'openai-codex' BASE_URL = 'https://chatgpt.com/backend-api/codex' @@ -77,7 +78,7 @@ def _tokens(data: dict, previous: dict | None = None) -> dict: or not math.isfinite(expires_at) or expires_at <= time.time() ): - raise ValueError('ChatGPT returned an incomplete authorization. Please sign in again.') + raise ValueError(t('ChatGPT returned an incomplete authorization. Please sign in again.')) return { 'access_token': access, 'refresh_token': refresh, @@ -99,7 +100,7 @@ class CodexAuth: try: return await self.ap.persistence_mgr.execute_async(statement) except Exception: - raise ValueError('ChatGPT credential storage failed. Please retry.') from None + raise ValueError(t('ChatGPT credential storage failed. Please retry.')) from None async def _read(self, workspace: str, provider: str) -> dict | None: result = await self._execute(sa.select(CodexCredential).where(*self._where(workspace, provider))) @@ -107,7 +108,7 @@ class CodexAuth: row = result.first() return dict(row._mapping) if row is not None else None except Exception: - raise ValueError('ChatGPT credential storage failed. Please retry.') from None + raise ValueError(t('ChatGPT credential storage failed. Please retry.')) from None async def _provider(self, context, provider: str, *, user: bool = False) -> str: workspace = require_workspace_uuid(context) @@ -117,7 +118,7 @@ class CodexAuth: or not context.account_uuid or not has_permission(context, Permission.PROVIDER_SECRET_MANAGE) ): - raise ValueError('ChatGPT authorization requires an authorized workspace user') + raise ValueError(t('ChatGPT authorization requires an authorized workspace user')) result = await self._execute( sa.select(ModelProvider.requester).where( ModelProvider.workspace_uuid == workspace, ModelProvider.uuid == provider @@ -147,9 +148,9 @@ class CodexAuth: if result.rowcount == 1: break if await self._read(workspace, provider) is None: - raise ValueError(LOGIN_REQUIRED) + raise ValueError(t(LOGIN_REQUIRED)) if time.monotonic() >= deadline: - raise ValueError('ChatGPT authorization is busy. Please retry shortly.') + raise ValueError(t('ChatGPT authorization is busy. Please retry shortly.')) await asyncio.sleep(0.1) try: yield owner @@ -171,7 +172,7 @@ class CodexAuth: .values(payload=payload, version=CodexCredential.version + 1) ) if result.rowcount != 1: - raise ValueError('ChatGPT authorization was cancelled or replaced. Please retry.') + raise ValueError(t('ChatGPT authorization was cancelled or replaced. Please retry.')) async def _post(self, path: str, *, data=None, json_body=None) -> httpx.Response: try: @@ -186,7 +187,7 @@ class CodexAuth: 25, ) except (httpx.HTTPError, TimeoutError): - raise ValueError('ChatGPT authorization network error. Please retry.') from None + raise ValueError(t('ChatGPT authorization network error. Please retry.')) from None @staticmethod def _json(response: httpx.Response) -> dict: @@ -196,7 +197,7 @@ class CodexAuth: raise ValueError return value except ValueError: - raise ValueError('ChatGPT returned an invalid authorization response') from None + raise ValueError(t('ChatGPT returned an invalid authorization response')) from None async def status(self, context, provider: str) -> dict: workspace = await self._provider(context, provider, user=True) @@ -215,7 +216,9 @@ class CodexAuth: async with self._lease(workspace, provider) as owner: response = await self._post('/api/accounts/deviceauth/usercode', json_body={'client_id': CLIENT_ID}) if response.status_code != 200: - raise ValueError('Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.') + raise ValueError( + t('Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.') + ) data = self._json(response) try: code = data.get('user_code') or data['usercode'] @@ -224,7 +227,7 @@ class CodexAuth: if not isinstance(code, str) or not isinstance(device, str) or not code or not device: raise ValueError except (KeyError, ValueError, TypeError): - raise ValueError('ChatGPT returned an invalid device code') from None + raise ValueError(t('ChatGPT returned an invalid device code')) from None now = time.time() try: expiry = data.get('expires_at') @@ -241,7 +244,7 @@ class CodexAuth: raise ValueError expires_at = min(now + 900, expires_at) except (ValueError, TypeError): - raise ValueError('ChatGPT returned an invalid device code expiry') from None + raise ValueError(t('ChatGPT returned an invalid device code expiry')) from None pending = { 'authorization_id': secrets.token_urlsafe(32), 'user_code': code, @@ -306,12 +309,12 @@ class CodexAuth: if response.status_code != 200: payload.pop('pending', None) await self._save(workspace, provider, owner, payload) - raise ValueError('ChatGPT device authorization failed. Please start again.') + raise ValueError(t('ChatGPT device authorization failed. Please start again.')) data = self._json(response) if not data.get('authorization_code') or not data.get('code_verifier'): payload.pop('pending', None) await self._save(workspace, provider, owner, payload) - raise ValueError('ChatGPT returned an incomplete device authorization') + raise ValueError(t('ChatGPT returned an incomplete device authorization')) # Keep an attempt tombstone so cancel can preempt exchange, but never replay a code. pending['consumed'] = True await self._save(workspace, provider, owner, payload) @@ -326,7 +329,7 @@ class CodexAuth: }, ) if response.status_code != 200: - raise ValueError('ChatGPT token exchange failed. Please start sign-in again.') + raise ValueError(t('ChatGPT token exchange failed. Please start sign-in again.')) tokens = _tokens(self._json(response)) await self._save( workspace, @@ -384,7 +387,7 @@ class CodexAuth: payload = row['payload'] if row else {} tokens = payload.get('tokens') if not tokens or payload.get('invalid'): - raise ValueError(LOGIN_REQUIRED) + raise ValueError(t(LOGIN_REQUIRED)) if tokens['expires_at'] > time.time() + 120 and tokens['access_token'] != rejected_token: return tokens async with self._lease(workspace, provider, refresh=True) as owner: @@ -392,7 +395,7 @@ class CodexAuth: payload = dict(row['payload']) tokens = payload.get('tokens') if not tokens or payload.get('invalid'): - raise ValueError(LOGIN_REQUIRED) + raise ValueError(t(LOGIN_REQUIRED)) if tokens['expires_at'] > time.time() + 120 and tokens['access_token'] != rejected_token: return tokens response = await self._post( @@ -411,9 +414,9 @@ class CodexAuth: payload.pop('tokens', None) payload.pop('completed', None) await self._save(workspace, provider, owner, payload) - raise ValueError(LOGIN_REQUIRED) + raise ValueError(t(LOGIN_REQUIRED)) if response.status_code != 200: - raise ValueError('ChatGPT token refresh temporarily failed. Please retry.') + raise ValueError(t('ChatGPT token refresh temporarily failed. Please retry.')) refreshed = _tokens(self._json(response), tokens) payload['tokens'] = refreshed await self._save(workspace, provider, owner, payload) diff --git a/src/langbot/pkg/provider/modelmgr/codex_errors.py b/src/langbot/pkg/provider/modelmgr/codex_errors.py index a7ceb0301..5efa21a7f 100644 --- a/src/langbot/pkg/provider/modelmgr/codex_errors.py +++ b/src/langbot/pkg/provider/modelmgr/codex_errors.py @@ -1,10 +1,12 @@ """Explicitly safe Codex failures; never construct messages from upstream bodies.""" +from ...i18n import t + class CodexProviderError(ValueError): """A known provider failure safe to expose at the HTTP boundary.""" def __init__(self, message: str, status_code: int = 502, error_code: str = 'codex_upstream_failure'): - super().__init__(message) + super().__init__(t(message)) self.status_code = status_code self.error_code = error_code diff --git a/src/langbot/pkg/provider/modelmgr/errors.py b/src/langbot/pkg/provider/modelmgr/errors.py index dc3b35b6f..2d2a5d5b6 100644 --- a/src/langbot/pkg/provider/modelmgr/errors.py +++ b/src/langbot/pkg/provider/modelmgr/errors.py @@ -1,5 +1,8 @@ +from ...i18n import t + + class RequesterError(Exception): """Base class for all Requester errors.""" def __init__(self, message: str): - super().__init__('模型请求失败: ' + message) + super().__init__(t('Model request failed') + ': ' + message) diff --git a/src/langbot/pkg/provider/modelmgr/requesters/litellmchat.py b/src/langbot/pkg/provider/modelmgr/requesters/litellmchat.py index e927a3baf..b97d87930 100644 --- a/src/langbot/pkg/provider/modelmgr/requesters/litellmchat.py +++ b/src/langbot/pkg/provider/modelmgr/requesters/litellmchat.py @@ -2,6 +2,8 @@ from __future__ import annotations +from ....i18n import t + import typing import asyncio @@ -1037,22 +1039,22 @@ class LiteLLMRequester(requester.ProviderAPIRequester): """Convert LiteLLM exceptions to RequesterError. Never returns, always raises.""" # Check more specific exceptions first (they inherit from base exceptions) if isinstance(e, litellm.ContextWindowExceededError): - raise errors.RequesterError(f'上下文长度超限: {str(e)}') + raise errors.RequesterError(t('Context length exceeded') + f': {e}') if isinstance(e, litellm.BadRequestError): - raise errors.RequesterError(f'请求参数错误: {str(e)}') + raise errors.RequesterError(t('Invalid request parameters') + f': {e}') if isinstance(e, litellm.AuthenticationError): - raise errors.RequesterError(f'API key 无效: {str(e)}') + raise errors.RequesterError(t('Invalid API key') + f': {e}') if isinstance(e, litellm.NotFoundError): - raise errors.RequesterError(f'模型或路径无效: {str(e)}') + raise errors.RequesterError(t('Invalid model or endpoint') + f': {e}') if isinstance(e, litellm.RateLimitError): - raise errors.RequesterError(f'请求过于频繁或余额不足: {str(e)}') + raise errors.RequesterError(t('Rate limit exceeded or insufficient balance') + f': {e}') if isinstance(e, litellm.Timeout): - raise errors.RequesterError(f'请求超时: {str(e)}') + raise errors.RequesterError(t('Request timed out') + f': {e}') if isinstance(e, litellm.APIConnectionError): - raise errors.RequesterError(f'连接错误: {str(e)}') + raise errors.RequesterError(t('Connection failed') + f': {e}') if isinstance(e, litellm.APIError): - raise errors.RequesterError(f'API 错误: {str(e)}') - raise errors.RequesterError(f'未知错误: {str(e)}') + raise errors.RequesterError(t('API error') + f': {e}') + raise errors.RequesterError(t('Unknown error') + f': {e}') async def _build_completion_args( self, @@ -1523,9 +1525,9 @@ class LiteLLMRequester(requester.ProviderAPIRequester): body = await httpclient.response_text(e.response) except Exception: pass - raise errors.RequesterError(f'rerank 请求失败 (HTTP {e.response.status_code}): {body or str(e)}') + raise errors.RequesterError(t('API error') + f' (HTTP {e.response.status_code}): {body or str(e)}') except httpx.HTTPError as e: - raise errors.RequesterError(f'rerank 连接错误: {str(e)}') + raise errors.RequesterError(t('Connection failed') + f': {e}') raw_results = data.get('results', []) if isinstance(data, dict) else [] results = [] diff --git a/src/langbot/pkg/provider/tools/loaders/mcp.py b/src/langbot/pkg/provider/tools/loaders/mcp.py index 7b5a83afd..3cb8b8cf5 100644 --- a/src/langbot/pkg/provider/tools/loaders/mcp.py +++ b/src/langbot/pkg/provider/tools/loaders/mcp.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ....i18n import t + import dataclasses import enum import json @@ -1490,7 +1492,7 @@ class RuntimeMCPSession: # Raw transport exceptions may echo command arguments, headers, or # environment values. Detailed diagnostics belong in AUDIT_VIEW # logs; resource-list responses expose only a stable status. - 'error_message': 'MCP runtime failed' if self.error_message else None, + 'error_message': t('MCP runtime failed') if self.error_message else None, 'error_code': self._public_error_code if self.error_message else None, 'error_phase': self.error_phase.value if self.error_phase else None, 'retry_count': self.retry_count, diff --git a/src/langbot/templates/default-pipeline-config.json b/src/langbot/templates/default-pipeline-config.json index 23666ab03..b692c3e0e 100644 --- a/src/langbot/templates/default-pipeline-config.json +++ b/src/langbot/templates/default-pipeline-config.json @@ -55,7 +55,7 @@ }, "misc": { "exception-handling": "show-hint", - "failure-hint": "Request failed.", + "failure-hint": "", "at-sender": true, "quote-origin": true, "track-function-calls": false, diff --git a/src/langbot/templates/metadata/pipeline/output.yaml b/src/langbot/templates/metadata/pipeline/output.yaml index d5e0fae07..d3acfc693 100644 --- a/src/langbot/templates/metadata/pipeline/output.yaml +++ b/src/langbot/templates/metadata/pipeline/output.yaml @@ -106,11 +106,81 @@ stages: en_US: Failure Hint Text zh_Hans: 失败提示文本 description: - en_US: The text to display when a request fails. Only effective when Exception Handling Strategy is set to "Show Failure Hint" - zh_Hans: 请求失败时显示的提示文本,仅在异常处理策略设置为"仅文字提示"时生效 + en_US: Leave empty to use the Workspace language. Used for request and runner execution failures; custom text is not translated. + zh_Hans: 留空时使用工作区语言的默认提示。用于请求和运行器执行失败;自定义文本不会自动翻译。 type: string required: false - default: 'Request failed.' + default: '' + - name: rate-limit-hint + label: + en_US: Rate Limit Hint + zh_Hans: 限速提示 + description: + en_US: "Leave empty to use the Workspace language." + zh_Hans: "留空时使用工作区语言的默认提示。" + type: string + required: false + default: '' + - name: content-filter-hint + label: + en_US: Content Filter Hint + zh_Hans: 内容过滤提示 + description: + en_US: "Leave empty to use the Workspace language." + zh_Hans: "留空时使用工作区语言的默认提示。" + type: string + required: false + default: '' + - name: content-filter-error-hint + label: + en_US: Content Check Failure Hint + zh_Hans: 内容检查失败提示 + description: + en_US: "Leave empty to use the Workspace language." + zh_Hans: "留空时使用工作区语言的默认提示。" + type: string + required: false + default: '' + - name: tool-call-hint + label: + en_US: Tool Call Hint + zh_Hans: 工具调用提示 + description: + en_US: "Leave empty to use the Workspace language. Use {tools} for tool names. Requires Track Function Calls." + zh_Hans: "留空时使用工作区语言的默认提示。 可用 {tools} 插入工具名称,需开启跟踪函数调用。" + type: string + required: false + default: '' + - name: runner-not-found-hint + label: + en_US: Runner Missing Hint + zh_Hans: 运行器不存在提示 + description: + en_US: "Leave empty to use the Workspace language. Use {runner} for the runner ID." + zh_Hans: "留空时使用工作区语言的默认提示。 可用 {runner} 插入运行器 ID。" + type: string + required: false + default: '' + - name: runner-not-authorized-hint + label: + en_US: Runner Authorization Hint + zh_Hans: 运行器未授权提示 + description: + en_US: "Leave empty to use the Workspace language." + zh_Hans: "留空时使用工作区语言的默认提示。" + type: string + required: false + default: '' + - name: runner-unavailable-hint + label: + en_US: Runner Unavailable Hint + zh_Hans: 运行器不可用提示 + description: + en_US: "Leave empty to use the Workspace language." + zh_Hans: "留空时使用工作区语言的默认提示。" + type: string + required: false + default: '' - name: at-sender label: en_US: At Sender diff --git a/tests/integration/api/test_workspaces.py b/tests/integration/api/test_workspaces.py index 7a6254d01..aba1765cc 100644 --- a/tests/integration/api/test_workspaces.py +++ b/tests/integration/api/test_workspaces.py @@ -928,3 +928,64 @@ async def test_account_bootstrap_does_not_disclose_non_member_workspaces(workspa ) assert current.status_code == 404 assert (await current.get_json())['code'] == 'resource_not_found' + + +async def test_workspace_language_persists_and_controls_api_errors(workspace_api): + from langbot.pkg.i18n.workspace import get_workspace_language, set_workspace_language + + application, client, engine, owner_token = workspace_api + current = await client.get('/api/v1/workspaces/current', headers=_auth(owner_token)) + current = (await current.get_json())['data'] + workspace_uuid = current['workspace']['uuid'] + owner_uuid = current['membership']['account_uuid'] + headers = {**_auth(owner_token, workspace_uuid), 'X-LangBot-Language': 'ja-JP'} + initial = await client.get('/api/v1/workspaces/current/language', headers=headers) + assert (await initial.get_json())['data']['language'] == 'en_US' + + updated = await client.put('/api/v1/workspaces/current/language', headers=headers, json={'language': 'zh_Hans'}) + assert updated.status_code == 200 + assert await get_workspace_language(application, workspace_uuid) == 'zh_Hans' + restarted = SimpleNamespace(persistence_mgr=application.persistence_mgr) + assert await get_workspace_language(restarted, workspace_uuid) == 'zh_Hans' + + invalid = await client.put('/api/v1/workspaces/current/language', headers=headers, json={'language': '../bad'}) + assert invalid.status_code == 400 + assert await get_workspace_language(application, workspace_uuid, refresh=True) == 'zh_Hans' + + # A second Workspace has independent persisted metadata and cache entries. + async with engine.begin() as connection: + await connection.execute( + sqlalchemy.insert(Workspace).values( + uuid='language-other', + instance_uuid='instance-workspace-api', + name='Other', + slug='language-other', + source='cloud_projection', + type='team', + ) + ) + await set_workspace_language(application, 'language-other', 'es_ES') + assert await get_workspace_language(application, 'language-other') == 'es_ES' + assert await get_workspace_language(application, workspace_uuid) == 'zh_Hans' + + # Read-only members can inspect the setting but cannot modify it. The + # verified Workspace setting wins over the browser language hint. + async with engine.begin() as connection: + await connection.execute( + sqlalchemy.update(WorkspaceMembership) + .where( + WorkspaceMembership.workspace_uuid == workspace_uuid, + WorkspaceMembership.account_uuid == owner_uuid, + ) + .values(role='viewer') + ) + denied = await client.put('/api/v1/workspaces/current/language', headers=headers, json={'language': 'ja_JP'}) + assert denied.status_code == 403 + assert (await denied.get_json())['msg'] == '没有执行此操作的权限' + visible = await client.get('/api/v1/workspaces/current/language', headers=headers) + assert visible.status_code == 200 + assert (await visible.get_json())['data']['language'] == 'zh_Hans' + + # An untrusted selector never causes us to read another tenant's language. + unauthorized = await client.get('/api/v1/workspaces/current/language', headers=_auth(owner_token, 'language-other')) + assert unauthorized.status_code in (403, 404) diff --git a/tests/unit_tests/api/http/test_route_tenant_scope.py b/tests/unit_tests/api/http/test_route_tenant_scope.py index 4b4cb96e6..26e5ae271 100644 --- a/tests/unit_tests/api/http/test_route_tenant_scope.py +++ b/tests/unit_tests/api/http/test_route_tenant_scope.py @@ -10,6 +10,7 @@ from sqlalchemy.ext.asyncio import create_async_engine from langbot.pkg.api.http.controller import group from langbot.pkg.persistence.mgr import PersistenceManager, PersistenceMode +from langbot.pkg.entity.persistence.metadata import WorkspaceMetadata pytestmark = pytest.mark.asyncio @@ -20,6 +21,8 @@ async def test_authenticated_route_does_not_hold_database_session_during_externa release = asyncio.Event() observations: list[bool] = [] engine = create_async_engine('sqlite+aiosqlite:///:memory:') + async with engine.begin() as connection: + await connection.run_sync(WorkspaceMetadata.__table__.create) persistence = PersistenceManager(object(), mode=PersistenceMode.CLOUD_RUNTIME) persistence.db = SimpleNamespace(get_engine=lambda: engine) @@ -60,7 +63,7 @@ async def test_authenticated_route_does_not_hold_database_session_during_externa ) ) try: - await entered.wait() + await asyncio.wait_for(entered.wait(), timeout=5) assert observations == [True] release.set() response = await request diff --git a/tests/unit_tests/api/service/test_provider_cascade.py b/tests/unit_tests/api/service/test_provider_cascade.py index 1fd58e98d..971dafc3b 100644 --- a/tests/unit_tests/api/service/test_provider_cascade.py +++ b/tests/unit_tests/api/service/test_provider_cascade.py @@ -15,6 +15,7 @@ from langbot.pkg.api.http.controller.groups.provider.providers import ModelProvi from langbot.pkg.api.http.service.provider import ModelProviderService from langbot.pkg.entity.persistence.model import CodexCredential, EmbeddingModel, LLMModel, ModelProvider, RerankModel from langbot.pkg.entity.persistence.user import User +from langbot.pkg.entity.persistence.metadata import WorkspaceMetadata from langbot.pkg.entity.persistence.workspace import Workspace from langbot.pkg.persistence.mgr import PersistenceManager, PersistenceMode from langbot.pkg.provider.modelmgr.modelmgr import ModelManager @@ -51,7 +52,7 @@ async def deletion(tmp_path): } async with engine.begin() as conn: - for table in (User, Workspace, ModelProvider, CodexCredential, *MODEL_TYPES): + for table in (User, Workspace, WorkspaceMetadata, ModelProvider, CodexCredential, *MODEL_TYPES): await conn.run_sync(table.__table__.create) for workspace in contexts: await conn.execute( diff --git a/tests/unit_tests/api/service/test_user_service.py b/tests/unit_tests/api/service/test_user_service.py index 12b440b32..add2ea7b3 100644 --- a/tests/unit_tests/api/service/test_user_service.py +++ b/tests/unit_tests/api/service/test_user_service.py @@ -357,7 +357,7 @@ class TestUserServiceAuthenticate: service = UserService(ap) # Execute & Verify - with pytest.raises(ValueError, match='用户不存在'): + with pytest.raises(ValueError, match='Resource not found'): await service.authenticate('nonexistent@example.com', 'password') async def test_authenticate_space_user_without_password_raises_error(self): @@ -377,7 +377,7 @@ class TestUserServiceAuthenticate: service = UserService(ap) # Execute & Verify - with pytest.raises(ValueError, match='请使用 LangBot 账号登录'): + with pytest.raises(ValueError, match='Please sign in with your LangBot Account'): await service.authenticate('space@example.com', 'password') @@ -418,7 +418,6 @@ class TestUserServiceGenerateJwtToken: assert token is not None - class TestUserServiceVerifyJwtToken: """Tests for verify_jwt_token method.""" diff --git a/tests/unit_tests/api/test_extensions_runtime_fence.py b/tests/unit_tests/api/test_extensions_runtime_fence.py index 12f36917e..02bff861f 100644 --- a/tests/unit_tests/api/test_extensions_runtime_fence.py +++ b/tests/unit_tests/api/test_extensions_runtime_fence.py @@ -99,7 +99,11 @@ async def test_extensions_route_redacts_plugin_secrets_without_mutating_runtime_ @pytest.mark.asyncio async def test_extensions_parallel_reads_open_explicit_child_task_scopes(): + from langbot.pkg.entity.persistence.metadata import WorkspaceMetadata + engine = create_async_engine('sqlite+aiosqlite:///:memory:') + async with engine.begin() as connection: + await connection.run_sync(WorkspaceMetadata.__table__.create) account = SimpleNamespace(uuid='account-a', user='owner@example.com') ap = SimpleNamespace() ap.persistence_mgr = PersistenceManager(ap) diff --git a/tests/unit_tests/api/test_mcp_server.py b/tests/unit_tests/api/test_mcp_server.py index 228b23ffa..3615f8286 100644 --- a/tests/unit_tests/api/test_mcp_server.py +++ b/tests/unit_tests/api/test_mcp_server.py @@ -65,3 +65,15 @@ async def test_mcp_server_exposes_bot_event_route_tools(): assert 'test_bot_event_route' not in tool_names assert 'list_processors' in tool_names assert 'list_agents' not in tool_names + assert 'get_workspace_message_language' in tool_names + language_tool = next(tool for tool in tools if tool.name == 'set_workspace_message_language') + assert set(language_tool.inputSchema['properties']['language']['enum']) == { + 'en_US', + 'zh_Hans', + 'zh_Hant', + 'ja_JP', + 'es_ES', + 'ru_RU', + 'th_TH', + 'vi_VN', + } diff --git a/tests/unit_tests/pipeline/conftest.py b/tests/unit_tests/pipeline/conftest.py index 22d81e2b4..07567c1f7 100644 --- a/tests/unit_tests/pipeline/conftest.py +++ b/tests/unit_tests/pipeline/conftest.py @@ -88,6 +88,12 @@ class MockApplication: def _create_mock_persistence_manager(self): persistence_mgr = AsyncMock() persistence_mgr.execute_async = AsyncMock() + result = Mock(scalar_one_or_none=Mock(return_value=None)) + uow = AsyncMock() + uow.execute = AsyncMock(return_value=result) + boundary = AsyncMock() + boundary.__aenter__.return_value = uow + persistence_mgr.tenant_uow = Mock(return_value=boundary) return persistence_mgr def _create_mock_query_pool(self): diff --git a/tests/unit_tests/pipeline/test_banwords.py b/tests/unit_tests/pipeline/test_banwords.py index 2dcaf2cf4..a33a0a2e1 100644 --- a/tests/unit_tests/pipeline/test_banwords.py +++ b/tests/unit_tests/pipeline/test_banwords.py @@ -82,7 +82,7 @@ async def test_invalid_sensitive_word_regex_still_blocks(): result = await filt.process(Mock(), 'any message') assert result.level == entities.ResultLevel.BLOCK - assert result.user_notice == '内容检查规则执行失败,请联系管理员' + assert result.user_notice == 'Content checking failed. Please contact the administrator.' assert 'rejected' in result.console_notice.lower() or 'invalid' in result.console_notice.lower() @@ -97,7 +97,7 @@ async def test_oversized_word_list_is_blocked(): assert result.level == entities.ResultLevel.BLOCK assert result.replacement == '' - assert result.user_notice == '内容检查规则执行失败,请联系管理员' + assert result.user_notice == 'Content checking failed. Please contact the administrator.' assert 'at most 256 regex patterns are allowed' in result.console_notice.lower() diff --git a/tests/unit_tests/platform/test_lark_eba_adapter.py b/tests/unit_tests/platform/test_lark_eba_adapter.py index 209e9a2a6..9db1d3c28 100644 --- a/tests/unit_tests/platform/test_lark_eba_adapter.py +++ b/tests/unit_tests/platform/test_lark_eba_adapter.py @@ -881,9 +881,9 @@ async def test_lark_webhook_dispatches_native_form_submission(): response = await adapter.handle_unified_webhook('bot-1', '', request) - assert response['toast'] == {'type': 'success', 'content': 'Submitted / 已提交'} + assert response['toast'] == {'type': 'success', 'content': 'Submitted'} assert response['card']['type'] == 'raw' - assert response['card']['data']['elements'][0]['text']['content'] == '**Submitted / 已提交**' + assert response['card']['data']['elements'][0]['text']['content'] == '**Submitted**' assert len(calls) == 1 assert calls[0].action == 'interaction.submitted' assert calls[0].data['callback_token'] == 'callback-token' @@ -912,7 +912,7 @@ async def test_lark_webhook_cardkit_submission_uses_async_card_update_only(): response = await adapter.handle_unified_webhook('bot-1', '', request) - assert response == {'toast': {'type': 'success', 'content': 'Submitted / 已提交'}} + assert response == {'toast': {'type': 'success', 'content': 'Submitted'}} def test_lark_stream_card_separates_multiple_reasoning_rounds(): diff --git a/tests/unit_tests/provider/test_litellmchat.py b/tests/unit_tests/provider/test_litellmchat.py index f73fac2e4..311367d5f 100644 --- a/tests/unit_tests/provider/test_litellmchat.py +++ b/tests/unit_tests/provider/test_litellmchat.py @@ -767,7 +767,7 @@ class TestHandleLiteLLMError: with pytest.raises(errors.RequesterError) as exc_info: requester._handle_litellm_error(error) - assert '请求参数错误' in str(exc_info.value) + assert 'Invalid request parameters' in str(exc_info.value) def test_authentication_error(self): """Test AuthenticationError translation""" @@ -778,7 +778,7 @@ class TestHandleLiteLLMError: with pytest.raises(errors.RequesterError) as exc_info: requester._handle_litellm_error(error) - assert 'API key 无效' in str(exc_info.value) + assert 'Invalid API key' in str(exc_info.value) def test_rate_limit_error(self): """Test RateLimitError translation""" @@ -789,7 +789,7 @@ class TestHandleLiteLLMError: with pytest.raises(errors.RequesterError) as exc_info: requester._handle_litellm_error(error) - assert '请求过于频繁' in str(exc_info.value) + assert 'Rate limit exceeded' in str(exc_info.value) def test_timeout_error(self): """Test Timeout translation""" @@ -800,7 +800,7 @@ class TestHandleLiteLLMError: with pytest.raises(errors.RequesterError) as exc_info: requester._handle_litellm_error(error) - assert '请求超时' in str(exc_info.value) + assert 'Request timed out' in str(exc_info.value) def test_context_window_error(self): """Test ContextWindowExceededError translation""" @@ -811,7 +811,7 @@ class TestHandleLiteLLMError: with pytest.raises(errors.RequesterError) as exc_info: requester._handle_litellm_error(error) - assert '上下文长度超限' in str(exc_info.value) + assert 'Context length exceeded' in str(exc_info.value) def test_unknown_error(self): """Test unknown error translation""" @@ -820,7 +820,7 @@ class TestHandleLiteLLMError: with pytest.raises(errors.RequesterError) as exc_info: requester._handle_litellm_error(Exception('unknown')) - assert '未知错误' in str(exc_info.value) + assert 'Unknown error' in str(exc_info.value) class TestInvokeLLM: @@ -976,7 +976,7 @@ class TestInvokeLLM: messages=messages, ) - assert 'API key 无效' in str(exc_info.value) + assert 'Invalid API key' in str(exc_info.value) class TestInvokeEmbedding: diff --git a/tests/unit_tests/test_backend_i18n.py b/tests/unit_tests/test_backend_i18n.py new file mode 100644 index 000000000..a43f4113d --- /dev/null +++ b/tests/unit_tests/test_backend_i18n.py @@ -0,0 +1,107 @@ +"""Runtime language isolation, catalog completeness, and Pipeline overrides.""" + +import asyncio +import json +import string +from pathlib import Path +from types import SimpleNamespace + +import pytest + +from langbot.pkg import i18n +from langbot.pkg.i18n.adapters import adapter_t, localized_adapter +from langbot.pkg.provider.modelmgr.errors import RequesterError + + +def test_catalogs_have_matching_keys_and_placeholders(): + folder = Path(i18n.__file__).parent / 'locales' + catalogs = { + locale: json.loads((folder / f'{locale}.json').read_text(encoding='utf-8')) + for locale in i18n.SUPPORTED_LANGUAGES + } + english = catalogs['en_US'] + formatter = string.Formatter() + + def fields(text): + return {name for _, name, _, _ in formatter.parse(text) if name is not None} + + for locale, catalog in catalogs.items(): + assert catalog.keys() == english.keys(), locale + for key, text in catalog.items(): + assert text.strip(), (locale, key) + assert fields(text) == fields(key), (locale, key) + + +@pytest.mark.asyncio +async def test_concurrent_requests_and_children_keep_their_language(): + async def render(locale): + with i18n.language_scope(locale): + + async def child(): + await asyncio.sleep(0) + return str(RequesterError(i18n.t('Request timed out'))) + + return await asyncio.create_task(child()) + + assert await asyncio.gather(render('zh_Hans'), render('ja_JP'), render('en_US')) == [ + '模型请求失败: 请求超时', + 'モデルへのリクエストに失敗しました: リクエストがタイムアウトしました', + 'Model request failed: Request timed out', + ] + assert i18n.language.get() == 'en_US' + with pytest.raises(RuntimeError), i18n.language_scope('zh_Hans'): + raise RuntimeError('interrupted') + assert i18n.language.get() == 'en_US' + + +def test_pipeline_overrides_inherit_language_and_preserve_literal_braces(): + query = SimpleNamespace(pipeline_config={'output': {'misc': {}}}) + with i18n.language_scope('zh_Hans'): + assert i18n.pipeline_notice(query, 'failure-hint', 'Request failed.') == '请求失败。' + query.pipeline_config['output']['misc']['failure-hint'] = 'Request failed.' + assert i18n.pipeline_notice(query, 'failure-hint', 'Request failed.') == '请求失败。' + query.pipeline_config['output']['misc']['failure-hint'] = 'My custom notice {literal}' + assert i18n.pipeline_notice(query, 'failure-hint', 'Request failed.') == 'My custom notice {literal}' + query.pipeline_config['output']['misc']['tool-call-hint'] = 'Using {tools}; keep {other}' + assert i18n.pipeline_notice(query, 'tool-call-hint', 'Calling {tools}...', tools='search') == ( + 'Using search; keep {other}' + ) + + +def test_diagnostics_are_never_parsed_or_rewritten(): + diagnostic = 'upstream: {"error": "some provider detail"}' + with i18n.language_scope('zh_Hans'): + assert i18n.t(diagnostic) == diagnostic + assert i18n.error_text('permission_denied', 'Missing Workspace permission: workspace.update') == ( + '没有执行此操作的权限' + ) + assert i18n.t('Please do not replace model not found in user content') == ( + 'Please do not replace model not found in user content' + ) + + +@pytest.mark.asyncio +async def test_adapter_callbacks_use_their_owner_not_the_callers_language(): + app = SimpleNamespace(_message_language_cache={'bot-workspace': ('ja_JP', float('inf'))}) + + class Adapter: + logger = SimpleNamespace(ap=app, execution_context=SimpleNamespace(workspace_uuid='bot-workspace')) + + @localized_adapter + async def reply(self): + return i18n.t('Submitted') + + with i18n.language_scope('zh_Hans'): + assert await Adapter().reply() == '送信済み' + assert adapter_t(Adapter(), 'Submitted') == '送信済み' + assert i18n.language.get() == 'zh_Hans' + + +def test_lark_card_uses_selected_language(): + from langbot.pkg.platform.adapters.lark.stream_card import build_card + + with i18n.language_scope('ja_JP'): + card = json.dumps(build_card('reasonanswer'), ensure_ascii=False) + assert '思考過程' in card + assert 'AI による生成コンテンツ' in card + assert 'Thinking' not in card diff --git a/web/src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx b/web/src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx index ecd3e08cb..14dda966f 100644 --- a/web/src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx +++ b/web/src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx @@ -51,6 +51,17 @@ const ASSIGNABLE_ROLES: Exclude[] = [ 'viewer', ]; +const MESSAGE_LANGUAGES: Record = { + en_US: 'English', + zh_Hans: '简体中文', + zh_Hant: '繁體中文', + ja_JP: '日本語', + es_ES: 'Español', + ru_RU: 'Русский', + th_TH: 'ไทย', + vi_VN: 'Tiếng Việt', +}; + export default function WorkspaceSettingsPanel({ active, }: WorkspaceSettingsPanelProps) { @@ -61,6 +72,9 @@ export default function WorkspaceSettingsPanel({ const [members, setMembers] = useState([]); const [invitations, setInvitations] = useState([]); const [loading, setLoading] = useState(false); + const [messageLanguage, setMessageLanguage] = useState('en_US'); + const [supportedLanguages, setSupportedLanguages] = useState([]); + const [savingLanguage, setSavingLanguage] = useState(false); const [inviteEmail, setInviteEmail] = useState(''); const [inviteRole, setInviteRole] = useState>('viewer'); @@ -86,8 +100,13 @@ export default function WorkspaceSettingsPanel({ const loadWorkspace = useCallback(async () => { setLoading(true); try { - const current = await backendClient.getCurrentWorkspace(); + const [current, languageSettings] = await Promise.all([ + backendClient.getCurrentWorkspace(), + backendClient.getWorkspaceLanguage(), + ]); setWorkspaceInfo(current); + setMessageLanguage(languageSettings.language); + setSupportedLanguages(languageSettings.supported_languages); const [memberResponse, invitationResponse] = await Promise.all([ current.permissions.includes('member.view') @@ -106,6 +125,19 @@ export default function WorkspaceSettingsPanel({ } }, [t]); + async function saveLanguage(locale: string) { + setSavingLanguage(true); + try { + const result = await backendClient.setWorkspaceLanguage(locale); + setMessageLanguage(result.language); + toast.success(t('workspaceLanguage.saved')); + } catch { + toast.error(t('workspaceLanguage.saveFailed')); + } finally { + setSavingLanguage(false); + } + } + useEffect(() => { if (active) void loadWorkspace(); }, [active, loadWorkspace]); @@ -222,6 +254,41 @@ export default function WorkspaceSettingsPanel({ +
+
+

+ {t('workspaceLanguage.label')} +

+

+ {t('workspaceLanguage.description')} +

+
+ +
{canInvite && (
diff --git a/web/src/app/infra/http/BackendClient.ts b/web/src/app/infra/http/BackendClient.ts index c9a42a07e..1c56ac215 100644 --- a/web/src/app/infra/http/BackendClient.ts +++ b/web/src/app/infra/http/BackendClient.ts @@ -111,6 +111,17 @@ import type { * 负责与后端 API 的所有交互 */ export class BackendClient extends BaseHttpClient { + public getWorkspaceLanguage(): Promise<{ + language: string; + supported_languages: string[]; + }> { + return this.get('/api/v1/workspaces/current/language'); + } + + public setWorkspaceLanguage(language: string): Promise<{ language: string }> { + return this.put('/api/v1/workspaces/current/language', { language }); + } + constructor(baseURL: string) { super(baseURL, false); } diff --git a/web/src/app/infra/http/BaseHttpClient.ts b/web/src/app/infra/http/BaseHttpClient.ts index d1c86022a..a4d3e8026 100644 --- a/web/src/app/infra/http/BaseHttpClient.ts +++ b/web/src/app/infra/http/BaseHttpClient.ts @@ -11,6 +11,7 @@ import { } from './workspaceContext'; import { setCurrentWorkspaceSnapshot } from './currentWorkspaceStore'; import { clearWorkspaceBootstrapSnapshot } from './workspaceBootstrapStore'; +import i18n from 'i18next'; type JSONValue = string | number | boolean | JSONObject | JSONArray | null; interface JSONObject { diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index 500e484e4..a0a421313 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -1,6 +1,13 @@ import slackSetup from './slack-setup/en-US'; import pipelineMigration from './pipeline-migration/en-US'; const enUS = { + workspaceLanguage: { + label: 'Workspace language', + description: + 'Language for backend messages and default bot replies. Your interface language stays independent.', + saved: 'Language saved', + saveFailed: 'Failed to save language', + }, slackSetup, inflight: { autoCollapse: 'Auto-collapse', diff --git a/web/src/i18n/locales/es-ES.ts b/web/src/i18n/locales/es-ES.ts index f0639285f..a0478b8ee 100644 --- a/web/src/i18n/locales/es-ES.ts +++ b/web/src/i18n/locales/es-ES.ts @@ -1,5 +1,12 @@ import pipelineMigration from './pipeline-migration/es-ES'; const esES = { + workspaceLanguage: { + label: 'Idioma del espacio de trabajo', + description: + 'Idioma de los mensajes del servidor y respuestas predeterminadas del bot. No cambia tu interfaz.', + saved: 'Idioma guardado', + saveFailed: 'No se pudo guardar el idioma', + }, inflight: { autoCollapse: 'Plegar auto.', empty: 'No hay eventos en proceso', diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index ceb64102c..6932be4db 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -1,6 +1,13 @@ import slackSetup from './slack-setup/ja-JP'; import pipelineMigration from './pipeline-migration/ja-JP'; const jaJP = { + workspaceLanguage: { + label: 'ワークスペースの言語', + description: + 'バックエンドの通知とボットの既定の返信に使用します。個人の表示言語には影響しません。', + saved: '言語設定を保存しました', + saveFailed: '言語設定の保存に失敗しました', + }, slackSetup, inflight: { autoCollapse: '自動折りたたみ', diff --git a/web/src/i18n/locales/ru-RU.ts b/web/src/i18n/locales/ru-RU.ts index da83671ab..1c3170b2a 100644 --- a/web/src/i18n/locales/ru-RU.ts +++ b/web/src/i18n/locales/ru-RU.ts @@ -1,5 +1,12 @@ import pipelineMigration from './pipeline-migration/ru-RU'; const ruRU = { + workspaceLanguage: { + label: 'Язык рабочего пространства', + description: + 'Язык сообщений сервера и стандартных ответов бота. Язык интерфейса не меняется.', + saved: 'Язык сохранён', + saveFailed: 'Не удалось сохранить язык', + }, inflight: { autoCollapse: 'Автосворачивание', empty: 'Нет событий в обработке', diff --git a/web/src/i18n/locales/th-TH.ts b/web/src/i18n/locales/th-TH.ts index 3ffc83053..7dc14a6c2 100644 --- a/web/src/i18n/locales/th-TH.ts +++ b/web/src/i18n/locales/th-TH.ts @@ -1,5 +1,12 @@ import pipelineMigration from './pipeline-migration/th-TH'; const thTH = { + workspaceLanguage: { + label: 'ภาษาของพื้นที่ทำงาน', + description: + 'ใช้สำหรับข้อความจากระบบและคำตอบเริ่มต้นของบอต โดยไม่เปลี่ยนภาษาอินเทอร์เฟซของคุณ', + saved: 'บันทึกภาษาแล้ว', + saveFailed: 'บันทึกภาษาไม่สำเร็จ', + }, inflight: { autoCollapse: 'ยุบอัตโนมัติ', empty: 'ไม่มีเหตุการณ์ที่กำลังประมวลผล', diff --git a/web/src/i18n/locales/vi-VN.ts b/web/src/i18n/locales/vi-VN.ts index b5ec94671..932905864 100644 --- a/web/src/i18n/locales/vi-VN.ts +++ b/web/src/i18n/locales/vi-VN.ts @@ -1,5 +1,12 @@ import pipelineMigration from './pipeline-migration/vi-VN'; const viVN = { + workspaceLanguage: { + label: 'Ngôn ngữ không gian làm việc', + description: + 'Dùng cho thông báo máy chủ và phản hồi mặc định của bot. Không thay đổi ngôn ngữ giao diện cá nhân.', + saved: 'Đã lưu ngôn ngữ', + saveFailed: 'Không thể lưu ngôn ngữ', + }, inflight: { autoCollapse: 'Tự thu gọn', empty: 'Không có sự kiện đang xử lý', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index 1c4ea076b..124b555c3 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -1,6 +1,12 @@ import slackSetup from './slack-setup/zh-Hans'; import pipelineMigration from './pipeline-migration/zh-Hans'; const zhHans = { + workspaceLanguage: { + label: '工作区语言', + description: '用于后端提示和机器人的默认回复,不影响个人界面语言。', + saved: '语言设置已保存', + saveFailed: '语言设置保存失败', + }, slackSetup, inflight: { autoCollapse: '自动折叠', diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index ca2a7239f..dd2c08701 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -1,6 +1,12 @@ import slackSetup from './slack-setup/zh-Hant'; import pipelineMigration from './pipeline-migration/zh-Hant'; const zhHant = { + workspaceLanguage: { + label: '工作區語言', + description: '用於後端提示和機器人的預設回覆,不影響個人介面語言。', + saved: '語言設定已儲存', + saveFailed: '語言設定儲存失敗', + }, slackSetup, inflight: { autoCollapse: '自動摺疊', diff --git a/web/tests/e2e/fixtures/langbot-api.ts b/web/tests/e2e/fixtures/langbot-api.ts index f78fc8e22..32e0fb207 100644 --- a/web/tests/e2e/fixtures/langbot-api.ts +++ b/web/tests/e2e/fixtures/langbot-api.ts @@ -750,6 +750,22 @@ async function handleBackendApi(route: Route, state: LangBotApiMockState) { return fulfillJson(route, { workspaces: state.workspaces }); } + if (path === '/api/v1/workspaces/current/language') { + return fulfillJson(route, { + language: 'en_US', + supported_languages: [ + 'en_US', + 'zh_Hans', + 'zh_Hant', + 'ja_JP', + 'es_ES', + 'ru_RU', + 'th_TH', + 'vi_VN', + ], + }); + } + if (path === '/api/v1/workspaces/current') { const selectedWorkspaceUuid = request.headers()['x-workspace-id']; const entry = state.workspaces.find( diff --git a/web/tests/e2e/fixtures/runner-migration-contract.json b/web/tests/e2e/fixtures/runner-migration-contract.json index 892e827f4..1c066a061 100644 --- a/web/tests/e2e/fixtures/runner-migration-contract.json +++ b/web/tests/e2e/fixtures/runner-migration-contract.json @@ -5765,12 +5765,110 @@ "zh_Hans": "失败提示文本" }, "description": { - "en_US": "The text to display when a request fails. Only effective when Exception Handling Strategy is set to \"Show Failure Hint\"", - "zh_Hans": "请求失败时显示的提示文本,仅在异常处理策略设置为\"仅文字提示\"时生效" + "en_US": "Leave empty to use the Workspace language. Used for request and runner execution failures; custom text is not translated.", + "zh_Hans": "留空时使用工作区语言的默认提示。用于请求和运行器执行失败;自定义文本不会自动翻译。" }, "type": "string", "required": false, - "default": "Request failed." + "default": "" + }, + { + "name": "rate-limit-hint", + "label": { + "en_US": "Rate Limit Hint", + "zh_Hans": "限速提示" + }, + "description": { + "en_US": "Leave empty to use the Workspace language.", + "zh_Hans": "留空时使用工作区语言的默认提示。" + }, + "type": "string", + "required": false, + "default": "" + }, + { + "name": "content-filter-hint", + "label": { + "en_US": "Content Filter Hint", + "zh_Hans": "内容过滤提示" + }, + "description": { + "en_US": "Leave empty to use the Workspace language.", + "zh_Hans": "留空时使用工作区语言的默认提示。" + }, + "type": "string", + "required": false, + "default": "" + }, + { + "name": "content-filter-error-hint", + "label": { + "en_US": "Content Check Failure Hint", + "zh_Hans": "内容检查失败提示" + }, + "description": { + "en_US": "Leave empty to use the Workspace language.", + "zh_Hans": "留空时使用工作区语言的默认提示。" + }, + "type": "string", + "required": false, + "default": "" + }, + { + "name": "tool-call-hint", + "label": { + "en_US": "Tool Call Hint", + "zh_Hans": "工具调用提示" + }, + "description": { + "en_US": "Leave empty to use the Workspace language. Use {tools} for tool names. Requires Track Function Calls.", + "zh_Hans": "留空时使用工作区语言的默认提示。 可用 {tools} 插入工具名称,需开启跟踪函数调用。" + }, + "type": "string", + "required": false, + "default": "" + }, + { + "name": "runner-not-found-hint", + "label": { + "en_US": "Runner Missing Hint", + "zh_Hans": "运行器不存在提示" + }, + "description": { + "en_US": "Leave empty to use the Workspace language. Use {runner} for the runner ID.", + "zh_Hans": "留空时使用工作区语言的默认提示。 可用 {runner} 插入运行器 ID。" + }, + "type": "string", + "required": false, + "default": "" + }, + { + "name": "runner-not-authorized-hint", + "label": { + "en_US": "Runner Authorization Hint", + "zh_Hans": "运行器未授权提示" + }, + "description": { + "en_US": "Leave empty to use the Workspace language.", + "zh_Hans": "留空时使用工作区语言的默认提示。" + }, + "type": "string", + "required": false, + "default": "" + }, + { + "name": "runner-unavailable-hint", + "label": { + "en_US": "Runner Unavailable Hint", + "zh_Hans": "运行器不可用提示" + }, + "description": { + "en_US": "Leave empty to use the Workspace language.", + "zh_Hans": "留空时使用工作区语言的默认提示。" + }, + "type": "string", + "required": false, + "default": "" }, { "name": "at-sender", diff --git a/web/tests/e2e/workspace-language.spec.ts b/web/tests/e2e/workspace-language.spec.ts new file mode 100644 index 000000000..7ff0a5fc1 --- /dev/null +++ b/web/tests/e2e/workspace-language.spec.ts @@ -0,0 +1,80 @@ +import { expect, test } from '@playwright/test'; +import { + installLangBotApiMocks, + makeWorkspaceEntry, +} from './fixtures/langbot-api'; + +test('workspace language persists independently of personal UI language', async ({ + page, +}, testInfo) => { + const workspace = makeWorkspaceEntry( + 'workspace-language', + 'Language Workspace', + 'local', + ); + workspace.permissions = ['workspace.view', 'workspace.update']; + await installLangBotApiMocks(page, { + authenticated: true, + workspaces: [workspace], + }); + let language = 'en_US'; + await page.route('**/api/v1/workspaces/current/language', async (route) => { + if (route.request().method() === 'PUT') { + expect(route.request().headers()['x-workspace-id']).toBe( + 'workspace-language', + ); + language = route.request().postDataJSON().language; + } + await route.fulfill({ + json: { + code: 0, + msg: 'ok', + data: { + language, + supported_languages: ['en_US', 'zh_Hans', 'ja_JP'], + }, + }, + }); + }); + await page.goto('/home?action=showWorkspaceSettings'); + const select = page.getByRole('combobox', { + name: 'Workspace language', + exact: true, + }); + await expect(select).toHaveText('English'); + await select.click(); + await page.getByRole('option', { name: '日本語', exact: true }).click(); + await expect(select).toHaveText('日本語'); + await expect(page.getByText('Language saved', { exact: true })).toBeVisible(); + await page.reload(); + await page.goto('/home?action=showWorkspaceSettings'); + await expect(select).toHaveText('日本語'); + await expect( + page.getByText('Workspace language', { exact: true }), + ).toBeVisible(); + await page.screenshot({ + path: testInfo.outputPath('workspace-language.png'), + fullPage: true, + }); +}); + +test('viewer can read but cannot edit workspace language', async ({ page }) => { + const workspace = makeWorkspaceEntry( + 'workspace-language', + 'Language Workspace', + 'local', + ); + workspace.membership.role = 'viewer'; + workspace.permissions = ['workspace.view']; + await installLangBotApiMocks(page, { + authenticated: true, + workspaces: [workspace], + }); + await page.goto('/home?action=showWorkspaceSettings'); + const select = page.getByRole('combobox', { + name: 'Workspace language', + exact: true, + }); + await expect(select).toHaveText('English'); + await expect(select).toBeDisabled(); +}); From 14b5bdb8c2c19874128dd6f50921e9ecc86f62a3 Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Mon, 12 Oct 2026 17:43:30 +0800 Subject: [PATCH 04/16] feat(cloud): enforce plan policies and refine workspace interface --- skills/skills/langbot-mcp-ops/SKILL.md | 4 + src/langbot/pkg/api/http/controller/group.py | 2 + .../controller/groups/platform/adapters.py | 12 +- src/langbot/pkg/api/http/service/bot.py | 5 + .../pkg/api/http/service/monitoring.py | 3 + src/langbot/pkg/api/mcp/server.py | 2 +- src/langbot/pkg/cloud/plan_policy.py | 42 + src/langbot/pkg/core/app.py | 2 +- src/langbot/pkg/platform/botmgr.py | 6 + .../api/http/test_entitlement_context.py | 18 + .../api/service/test_monitoring_tenancy.py | 18 + tests/unit_tests/cloud/test_plan_policy.py | 44 + web/pnpm-lock.yaml | 4031 +++++++---------- web/src/app/global.css | 25 + .../WorkspaceSettingsPanel.tsx | 51 +- .../workspace-settings/WorkspaceSwitcher.tsx | 8 +- web/src/i18n/locales/en-US.ts | 2 +- web/src/i18n/locales/es-ES.ts | 2 +- web/src/i18n/locales/ja-JP.ts | 2 +- web/src/i18n/locales/ru-RU.ts | 2 +- web/src/i18n/locales/th-TH.ts | 2 +- web/src/i18n/locales/vi-VN.ts | 2 +- web/src/i18n/locales/zh-Hans.ts | 2 +- web/src/i18n/locales/zh-Hant.ts | 2 +- 24 files changed, 1745 insertions(+), 2544 deletions(-) create mode 100644 src/langbot/pkg/cloud/plan_policy.py create mode 100644 tests/unit_tests/cloud/test_plan_policy.py diff --git a/skills/skills/langbot-mcp-ops/SKILL.md b/skills/skills/langbot-mcp-ops/SKILL.md index c0c8cedbe..a464b93b3 100644 --- a/skills/skills/langbot-mcp-ops/SKILL.md +++ b/skills/skills/langbot-mcp-ops/SKILL.md @@ -238,3 +238,7 @@ are not rewritten. Embedding and rerank models are not eligible. ### Reset a bot session context Use `reset_session_context(bot_id, session_id)` only when a user asks to start a session afresh. It requires `resource.manage`, preserves monitoring records, and refuses sessions with active tasks. It clears conversation runner state and excludes earlier transcript entries from subsequent model context. Files and long-term memory are not removed. + +## Cloud plan platform restrictions + +Bot creation and enabling use the same Workspace entitlement policy as HTTP. A disabled platform is rejected even through MCP or plugin calls; do not retry using an Omni or legacy alias to bypass the restriction. The Workspace-scoped HTTP adapter catalog omits disabled platforms. diff --git a/src/langbot/pkg/api/http/controller/group.py b/src/langbot/pkg/api/http/controller/group.py index a290c25c1..6cafbdc8c 100644 --- a/src/langbot/pkg/api/http/controller/group.py +++ b/src/langbot/pkg/api/http/controller/group.py @@ -296,6 +296,8 @@ class RouterGroup(abc.ABC): ) if isinstance(e, CodexProviderError): return self.http_status(e.status_code, e.error_code, str(e)) + if isinstance(e, EntitlementUnavailableError): + return self.http_status(403, 'entitlement_unavailable', str(e)) if isinstance(e, AuthorizationError): return self.http_status(e.status_code, e.error_code, str(e)) if isinstance(e, WorkspaceNotFoundError): diff --git a/src/langbot/pkg/api/http/controller/groups/platform/adapters.py b/src/langbot/pkg/api/http/controller/groups/platform/adapters.py index 759e5aa8a..76f185b21 100644 --- a/src/langbot/pkg/api/http/controller/groups/platform/adapters.py +++ b/src/langbot/pkg/api/http/controller/groups/platform/adapters.py @@ -8,6 +8,7 @@ import quart from langbot.pkg.api.http.authz import Permission from langbot.pkg.api.http.context import RequestContext +from langbot.pkg.cloud.plan_policy import workspace_entitlement, platform_feature_names, require_workspace_platform from langbot.pkg.core.errors import TaskCapacityError from langbot.pkg.utils import httpclient, importutil @@ -238,11 +239,16 @@ class AdaptersRouterGroup(group.RouterGroup): ) @self.route('', methods=['GET']) - async def _() -> str: - return self.success(data={'adapters': self.ap.platform_mgr.get_available_adapters_info()}) + async def _(request_context: RequestContext) -> str: + snapshot = await workspace_entitlement(self.ap, request_context.workspace_uuid) + adapters = self.ap.platform_mgr.get_available_adapters_info() + if snapshot is not None: + adapters = [a for a in adapters if not any(snapshot.features.get(f) is False for f in platform_feature_names(a['name']))] + return self.success(data={'adapters': adapters}) @self.route('/', methods=['GET']) - async def _(adapter_name: str) -> str: + async def _(adapter_name: str, request_context: RequestContext) -> str: + await require_workspace_platform(self.ap, request_context.workspace_uuid, adapter_name) adapter_info = self.ap.platform_mgr.get_available_adapter_info_by_name(adapter_name) if adapter_info is None: diff --git a/src/langbot/pkg/api/http/service/bot.py b/src/langbot/pkg/api/http/service/bot.py index 981c7cf50..766adbc87 100644 --- a/src/langbot/pkg/api/http/service/bot.py +++ b/src/langbot/pkg/api/http/service/bot.py @@ -16,6 +16,7 @@ from .tenant import TenantContext, require_workspace_uuid, scope_statement from ....utils import httpclient from ....platform.sources import http_bot_signing from ....platform.adapter_names import canonical_adapter_name +from ....cloud.plan_policy import require_workspace_platform from ....agent.runner.errors import RunnerNotFoundError @@ -696,6 +697,7 @@ class BotService: # TODO: 检查配置信息格式 bot_data = await self._prepare_bot_data(context, bot_data, include_uuid=True) + await require_workspace_platform(self.ap, workspace_uuid, bot_data['adapter']) bot_data['uuid'] = str(uuid.uuid4()) bot_data['workspace_uuid'] = workspace_uuid bot_data.setdefault('event_bindings', []) @@ -718,6 +720,9 @@ class BotService: workspace_uuid = require_workspace_uuid(context) update_data = await self._prepare_bot_data(context, bot_data, include_uuid=False) update_data.pop('workspace_uuid', None) + if 'adapter' in update_data or update_data.get('enable'): + current = await self.get_bot(context, bot_uuid, include_secret=True) + await require_workspace_platform(self.ap, workspace_uuid, update_data.get('adapter', current['adapter'])) result = await self.ap.persistence_mgr.execute_async( scope_statement( diff --git a/src/langbot/pkg/api/http/service/monitoring.py b/src/langbot/pkg/api/http/service/monitoring.py index dbc4eefb0..a84aa0543 100644 --- a/src/langbot/pkg/api/http/service/monitoring.py +++ b/src/langbot/pkg/api/http/service/monitoring.py @@ -260,6 +260,9 @@ class MonitoringService(ExecutionDetailsMixin): A dict mapping table name to the number of deleted rows. """ workspace_uuid = self._require_write_context(context) + from ....cloud.plan_policy import workspace_history_retention_days + + retention_days = await workspace_history_retention_days(self.ap, workspace_uuid, retention_days) if retention_days < 1: raise ValueError('retention_days must be >= 1') if batch_size < 1: diff --git a/src/langbot/pkg/api/mcp/server.py b/src/langbot/pkg/api/mcp/server.py index 7bef96fc5..44658ccc9 100644 --- a/src/langbot/pkg/api/mcp/server.py +++ b/src/langbot/pkg/api/mcp/server.py @@ -347,7 +347,7 @@ class LangBotMCPServer: @mcp.tool( description=( - 'Create a bot. `bot_data` is a JSON object matching the LangBot ' + 'Create a bot. Cloud Workspace platform restrictions also apply to this tool. `bot_data` is a JSON object matching the LangBot ' 'POST /api/v1/platform/bots body (e.g. name, adapter, config). ' 'Use event_bindings for exclusive Agent/Pipeline routes; plugin_processors is an array ' 'of {processor_uuid, enabled} subscriptions that automatically match Runner-declared events. ' diff --git a/src/langbot/pkg/cloud/plan_policy.py b/src/langbot/pkg/cloud/plan_policy.py new file mode 100644 index 000000000..743cf6baa --- /dev/null +++ b/src/langbot/pkg/cloud/plan_policy.py @@ -0,0 +1,42 @@ +"""Workspace plan policies evaluated from trusted entitlement projections.""" + +from .entitlements import EntitlementFeatureUnavailableError, EntitlementUnavailableError +from ..platform.adapter_names import canonical_adapter_name + + +def platform_feature_names(adapter: str) -> set[str]: + canonical = canonical_adapter_name(adapter) + names = {adapter, canonical} + if adapter.lower() == "line": + names.update({"line", "LINE"}) + if canonical.endswith('-omni'): + names.add(canonical[:-5]) + names.add(canonical[:-5] + '-eba') + return {'platform.' + name for name in names} + + +async def workspace_entitlement(ap, workspace_uuid: str): + if getattr(getattr(ap, 'deployment', None), 'mode', None) != 'cloud': + return None + resolver = getattr(ap, 'entitlement_resolver', None) + if resolver is None: + raise EntitlementUnavailableError('Workspace entitlement resolver is unavailable') + return await resolver.resolve(workspace_uuid) + + +async def require_workspace_platform(ap, workspace_uuid: str, adapter: str) -> None: + snapshot = await workspace_entitlement(ap, workspace_uuid) + if snapshot is None: + return + for feature in platform_feature_names(adapter): + if snapshot.features.get(feature) is False: + raise EntitlementFeatureUnavailableError(feature, entitlement_revision=snapshot.entitlement_revision) + + +async def workspace_history_retention_days(ap, workspace_uuid: str, fallback: int = 30) -> int: + snapshot = await workspace_entitlement(ap, workspace_uuid) + if snapshot is None: + return fallback + # Old signed snapshots inherit the existing 30-day policy. Never interpret + # missing entitlement data as an unlimited retention policy. + return max(1, snapshot.limits.get('chat_history.retention_days', 30)) diff --git a/src/langbot/pkg/core/app.py b/src/langbot/pkg/core/app.py index 41aefe6f5..f0d68ccef 100644 --- a/src/langbot/pkg/core/app.py +++ b/src/langbot/pkg/core/app.py @@ -427,7 +427,7 @@ class Application: monitoring_cfg = self.instance_config.data.get('monitoring', {}) auto_cleanup_cfg = monitoring_cfg.get('auto_cleanup', {}) - monitoring_enabled = auto_cleanup_cfg.get('enabled', True) + monitoring_enabled = auto_cleanup_cfg.get('enabled', True) or getattr(getattr(self, 'deployment', None), 'mode', None) == 'cloud' retention_days = self._get_positive_int_config( auto_cleanup_cfg.get('retention_days', 30), default=30, diff --git a/src/langbot/pkg/platform/botmgr.py b/src/langbot/pkg/platform/botmgr.py index 93b7a8d2f..bb3beb0e8 100644 --- a/src/langbot/pkg/platform/botmgr.py +++ b/src/langbot/pkg/platform/botmgr.py @@ -1,5 +1,7 @@ from __future__ import annotations +from ..cloud.plan_policy import require_workspace_platform + from ..i18n import language_scope from ..i18n.workspace import get_workspace_language @@ -117,6 +119,7 @@ class RuntimeBot: async def assert_execution_active(self) -> None: """Fail closed when this long-lived adapter belongs to a stale placement.""" + await require_workspace_platform(self.ap, self.workspace_uuid, self.bot_entity.adapter) await self.ap.workspace_service.get_execution_binding( self.workspace_uuid, expected_generation=self.placement_generation, @@ -919,6 +922,7 @@ class RuntimeBot: from ..telemetry import trace as trace_mod from ..telemetry.execution import ingress + await self.assert_execution_active() event.bot_uuid = self.bot_entity.uuid execution_id = self._platform_event_execution_id(event) if getattr(event, 'adapter_name', None) == 'wecomcs-omni': @@ -2316,6 +2320,7 @@ class PlatformManager: bot_entity = persistence_bot.Bot(**bot_entity) execution_context = self._normalize_execution_context(context, bot_uuid=bot_entity.uuid) + await require_workspace_platform(self.ap, execution_context.workspace_uuid, bot_entity.adapter) if bot_entity.workspace_uuid != execution_context.workspace_uuid: raise WorkspaceRequiredError('Bot entity Workspace does not match its runtime context') if not _binding_validated: @@ -2405,6 +2410,7 @@ class PlatformManager: ) if bot is None or bot.placement_generation != execution_context.placement_generation: return None + await require_workspace_platform(self.ap, execution_context.workspace_uuid, bot.bot_entity.adapter) return bot async def resolve_public_bot(self, route_key: str) -> RuntimeBot | None: diff --git a/tests/unit_tests/api/http/test_entitlement_context.py b/tests/unit_tests/api/http/test_entitlement_context.py index 600eb1082..56232c999 100644 --- a/tests/unit_tests/api/http/test_entitlement_context.py +++ b/tests/unit_tests/api/http/test_entitlement_context.py @@ -76,3 +76,21 @@ def test_execution_context_preserves_entitlement_revision(): ) assert ExecutionContext.from_request(request).entitlement_revision == 11 + + +@pytest.mark.asyncio +async def test_handler_entitlement_denial_is_403_not_internal_error(): + from langbot.pkg.api.http.controller.group import AuthType + from langbot.pkg.cloud.entitlements import EntitlementFeatureUnavailableError + + router = _router(SimpleNamespace(mode='cloud')) + router.path = '/policy' + router.name = 'policy' + + @router.route('', methods=['GET'], auth_type=AuthType.NONE) + async def denied(): + raise EntitlementFeatureUnavailableError('platform.telegram', entitlement_revision=2) + + response = await router.quart_app.test_client().get('/policy') + assert response.status_code == 403 + assert (await response.get_json())['code'] == 'entitlement_unavailable' diff --git a/tests/unit_tests/api/service/test_monitoring_tenancy.py b/tests/unit_tests/api/service/test_monitoring_tenancy.py index 2e2b4216f..e745757c1 100644 --- a/tests/unit_tests/api/service/test_monitoring_tenancy.py +++ b/tests/unit_tests/api/service/test_monitoring_tenancy.py @@ -423,3 +423,21 @@ async def test_cleanup_commits_sqlite_delete_before_vacuum(tmp_path): assert remaining == 3 finally: await engine.dispose() + + +async def test_cloud_cleanup_uses_plan_retention_and_isolates_workspaces(service): + from unittest.mock import AsyncMock + + service.ap.deployment = SimpleNamespace(mode='cloud') + service.ap.entitlement_resolver = SimpleNamespace(resolve=AsyncMock(return_value=SimpleNamespace(limits={'chat_history.retention_days': 7}))) + old = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) - datetime.timedelta(days=10) + for workspace in (WORKSPACE_A, WORKSPACE_B): + await service.ap.persistence_mgr.execute_async(sqlalchemy.insert(MonitoringMessage).values( + id=f'policy-{workspace}', workspace_uuid=workspace, timestamp=old, + bot_id='bot', bot_name='Bot', pipeline_id='pipeline', pipeline_name='Pipeline', + message_content='history', session_id='session', status='success', level='info', + )) + deleted = await service.cleanup_expired_records(_context(WORKSPACE_A), retention_days=90) + assert deleted['monitoring_messages'] == 1 + rows = await service.ap.persistence_mgr.execute_async(sqlalchemy.select(MonitoringMessage.workspace_uuid)) + assert rows.scalars().all() == [WORKSPACE_B] diff --git a/tests/unit_tests/cloud/test_plan_policy.py b/tests/unit_tests/cloud/test_plan_policy.py new file mode 100644 index 000000000..20b5b36e8 --- /dev/null +++ b/tests/unit_tests/cloud/test_plan_policy.py @@ -0,0 +1,44 @@ +from types import SimpleNamespace +from unittest.mock import AsyncMock + +import pytest + +from langbot.pkg.cloud.entitlements import EntitlementFeatureUnavailableError, EntitlementUnavailableError +from langbot.pkg.cloud.plan_policy import require_workspace_platform, workspace_history_retention_days + + +def cloud_app(): + snapshots = { + 'restricted': SimpleNamespace(features={'platform.telegram': False}, limits={'chat_history.retention_days': 7}, entitlement_revision=2), + 'open': SimpleNamespace(features={}, limits={'chat_history.retention_days': 90}, entitlement_revision=3), + } + return SimpleNamespace(deployment=SimpleNamespace(mode='cloud'), entitlement_resolver=SimpleNamespace(resolve=AsyncMock(side_effect=lambda ws: snapshots[ws]))) + + +@pytest.mark.asyncio +async def test_platform_policy_is_scoped_and_covers_aliases(): + ap = cloud_app() + for adapter in ['telegram', 'telegram-omni', 'telegram-eba']: + with pytest.raises(EntitlementFeatureUnavailableError): + await require_workspace_platform(ap, 'restricted', adapter) + await require_workspace_platform(ap, 'open', adapter) + await require_workspace_platform(ap, 'restricted', 'discord') + + +@pytest.mark.asyncio +async def test_retention_uses_workspace_entitlement(): + ap = cloud_app() + assert await workspace_history_retention_days(ap, 'restricted', 100) == 7 + assert await workspace_history_retention_days(ap, 'open', 100) == 90 + + +@pytest.mark.asyncio +async def test_oss_and_unavailable_projection(): + ap = SimpleNamespace(deployment=SimpleNamespace(mode='local')) + await require_workspace_platform(ap, 'any', 'telegram') + assert await workspace_history_retention_days(ap, 'any', 60) == 60 + ap.deployment.mode = 'cloud' + with pytest.raises(EntitlementUnavailableError): + await require_workspace_platform(ap, 'any', 'telegram') + with pytest.raises(EntitlementUnavailableError): + await workspace_history_retention_days(ap, 'any') diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index a861a5328..4838d2a6f 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -29,70 +29,70 @@ dependencies: version: 4.6.2 '@hookform/resolvers': specifier: ^5.0.1 - version: 5.2.2(react-hook-form@7.71.1) + version: 5.9.1(react-hook-form@7.90.0)(zod@3.25.76) '@radix-ui/react-alert-dialog': specifier: ^1.1.15 - version: 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.1.24(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-avatar': specifier: ^1.1.11 - version: 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.2.7(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-checkbox': specifier: ^1.3.1 - version: 1.3.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.3.12(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-collapsible': specifier: ^1.1.12 - version: 1.1.12(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.1.21(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-context-menu': specifier: ^2.2.15 - version: 2.2.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 2.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-dialog': specifier: ^1.1.14 - version: 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-dropdown-menu': specifier: ^2.1.16 - version: 2.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 2.1.25(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-hover-card': specifier: ^1.1.13 - version: 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.1.24(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-label': specifier: ^2.1.6 - version: 2.1.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 2.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-popover': specifier: ^1.1.14 - version: 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-progress': specifier: ^1.1.8 - version: 1.1.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.1.17(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-scroll-area': specifier: ^1.2.9 - version: 1.2.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.3.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-select': specifier: ^2.2.4 - version: 2.2.6(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 2.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-separator': specifier: ^1.1.8 - version: 1.1.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-slider': specifier: ^1.4.7 - version: 1.4.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.5.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-slot': specifier: ^1.2.3 - version: 1.2.4(@types/react@19.2.10)(react@19.2.1) + version: 1.4.0(@types/react@19.2.18)(react@19.2.1) '@radix-ui/react-switch': specifier: ^1.2.4 - version: 1.2.6(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-tabs': specifier: ^1.1.11 - version: 1.1.13(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.1.22(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-toggle': specifier: ^1.1.8 - version: 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.1.19(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-toggle-group': specifier: ^1.1.9 - version: 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@radix-ui/react-tooltip': specifier: ^1.2.7 - version: 1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.3.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) '@remixicon/react': specifier: ^4.9.0 version: 4.9.0(react@19.2.1) @@ -101,16 +101,16 @@ dependencies: version: 14.0.0 '@tailwindcss/postcss': specifier: ^4.1.5 - version: 4.1.18 + version: 4.3.3 '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@19.2.1)(react@19.2.1) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(vite@8.0.16) + version: 6.1.2(vite@8.3.4) axios: specifier: ^1.16.0 - version: 1.17.0 + version: 1.20.0 class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -119,19 +119,19 @@ dependencies: version: 2.1.1 cmdk: specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.1.1(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) highlight.js: specifier: ^11.11.1 - version: 11.11.1 + version: 11.12.0 i18next: specifier: ^25.1.2 - version: 25.8.0(typescript@5.9.3) + version: 25.10.10(typescript@5.9.3) i18next-browser-languagedetector: specifier: ^8.1.0 - version: 8.2.0 + version: 8.2.1 input-otp: specifier: ^1.4.2 - version: 1.4.2(react-dom@19.2.1)(react@19.2.1) + version: 1.5.0(react-dom@19.2.1)(react@19.2.1) lodash: specifier: ^4.18.0 version: 4.18.1 @@ -140,13 +140,13 @@ dependencies: version: 0.507.0(react@19.2.1) postcss: specifier: ^8.5.10 - version: 8.5.15 + version: 8.5.29 qrcode: specifier: ^1.5.4 version: 1.5.4 radix-ui: specifier: ^1.6.0 - version: 1.6.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + version: 1.7.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) react: specifier: 19.2.1 version: 19.2.1 @@ -155,22 +155,22 @@ dependencies: version: 19.2.1(react@19.2.1) react-hook-form: specifier: ^7.56.3 - version: 7.71.1(react@19.2.1) + version: 7.90.0(@types/react@19.2.18)(react@19.2.1) react-i18next: specifier: ^15.5.1 - version: 15.7.4(i18next@25.8.0)(react-dom@19.2.1)(react@19.2.1)(typescript@5.9.3) + version: 15.7.4(i18next@25.10.10)(react-dom@19.2.1)(react@19.2.1)(typescript@5.9.3) react-markdown: specifier: ^10.1.0 - version: 10.1.0(@types/react@19.2.10)(react@19.2.1) + version: 10.1.0(@types/react@19.2.18)(react@19.2.1) react-photo-view: specifier: ^1.2.7 version: 1.2.7(react-dom@19.2.1)(react@19.2.1) react-router-dom: specifier: ^7.15.0 - version: 7.17.0(react-dom@19.2.1)(react@19.2.1) + version: 7.18.4(react-dom@19.2.1)(react@19.2.1) react-syntax-highlighter: specifier: ^16.1.0 - version: 16.1.0(react@19.2.1) + version: 16.1.2(react@19.2.1) recharts: specifier: 2.15.4 version: 2.15.4(react-dom@19.2.1)(react@19.2.1) @@ -194,19 +194,19 @@ dependencies: version: 4.0.1 sonner: specifier: ^2.0.3 - version: 2.0.7(react-dom@19.2.1)(react@19.2.1) + version: 2.0.8(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) tailwind-merge: specifier: ^3.2.0 - version: 3.4.0 + version: 3.7.0 tailwindcss: specifier: ^4.1.5 - version: 4.1.18 + version: 4.3.3 uuidjs: specifier: ^5.1.0 version: 5.1.0 vite: specifier: ^8.0.16 - version: 8.0.16(@types/node@20.19.30) + version: 8.3.4(@types/node@20.19.43) zod: specifier: ^3.24.4 version: 3.25.76 @@ -214,22 +214,22 @@ dependencies: devDependencies: '@playwright/test': specifier: ^1.61.0 - version: 1.61.0 + version: 1.64.0 '@types/debug': specifier: ^4.1.12 - version: 4.1.12 + version: 4.1.13 '@types/estree': specifier: ^1.0.8 - version: 1.0.8 + version: 1.0.9 '@types/estree-jsx': specifier: ^1.0.5 version: 1.0.5 '@types/hast': specifier: ^3.0.4 - version: 3.0.4 + version: 3.0.5 '@types/lodash': specifier: ^4.17.16 - version: 4.17.23 + version: 4.17.26 '@types/mdast': specifier: ^4.0.4 version: 4.0.4 @@ -238,16 +238,16 @@ devDependencies: version: 2.1.0 '@types/node': specifier: ^20 - version: 20.19.30 + version: 20.19.43 '@types/qrcode': specifier: ^1.5.6 version: 1.5.6 '@types/react': specifier: ~19.2.7 - version: 19.2.10 + version: 19.2.18 '@types/react-dom': specifier: ~19.2.3 - version: 19.2.3(@types/react@19.2.10) + version: 19.2.7(@types/react@19.2.18) '@types/react-syntax-highlighter': specifier: ^15.5.13 version: 15.5.13 @@ -256,25 +256,25 @@ devDependencies: version: 3.0.3 eslint: specifier: ^9 - version: 9.39.2 + version: 9.39.5 eslint-config-prettier: specifier: ^10.1.2 - version: 10.1.8(eslint@9.39.2) + version: 10.1.8(eslint@9.39.5) eslint-plugin-prettier: specifier: ^5.2.6 - version: 5.5.5(eslint-config-prettier@10.1.8)(eslint@9.39.2)(prettier@3.8.1) + version: 5.5.6(eslint-config-prettier@10.1.8)(eslint@9.39.5)(prettier@3.9.10) eslint-plugin-react: specifier: ^7.37.5 - version: 7.37.5(eslint@9.39.2) + version: 7.37.5(eslint@9.39.5) eslint-plugin-react-hooks: specifier: ^5.2.0 - version: 5.2.0(eslint@9.39.2) + version: 5.2.0(eslint@9.39.5) lint-staged: specifier: ^15.5.1 version: 15.5.2 prettier: specifier: ^3.5.3 - version: 3.8.1 + version: 3.9.10 tw-animate-css: specifier: ^1.2.9 version: 1.4.0 @@ -283,17 +283,17 @@ devDependencies: version: 5.9.3 typescript-eslint: specifier: ^8.31.1 - version: 8.54.0(eslint@9.39.2)(typescript@5.9.3) + version: 8.71.1(eslint@9.39.5)(typescript@5.9.3) packages: - /@alloc/quick-lru@5.2.0: - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + /@alloc/quick-lru@5.3.0: + resolution: {integrity: sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==} engines: {node: '>=10'} dev: false - /@babel/runtime@7.28.6: - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + /@babel/runtime@7.29.10: + resolution: {integrity: sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==} engines: {node: '>=6.9.0'} dev: false @@ -340,38 +340,13 @@ packages: tslib: 2.8.1 dev: false - /@emnapi/core@1.10.0: - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - requiresBuild: true - dependencies: - '@emnapi/wasi-threads': 1.2.1 - tslib: 2.8.1 - dev: false - optional: true - - /@emnapi/runtime@1.10.0: - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - requiresBuild: true - dependencies: - tslib: 2.8.1 - dev: false - optional: true - - /@emnapi/wasi-threads@1.2.1: - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - requiresBuild: true - dependencies: - tslib: 2.8.1 - dev: false - optional: true - - /@eslint-community/eslint-utils@4.9.1(eslint@9.39.2): - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + /@eslint-community/eslint-utils@4.10.1(eslint@9.39.5): + resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 9.39.2 + eslint: 9.39.5 eslint-visitor-keys: 3.4.3 dev: true @@ -380,13 +355,13 @@ packages: engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/config-array@0.21.1: - resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + /@eslint/config-array@0.21.2: + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@eslint/object-schema': 2.1.7 debug: 4.4.3 - minimatch: 3.1.3 + minimatch: 3.1.5 transitivePeerDependencies: - supports-color dev: true @@ -405,25 +380,25 @@ packages: '@types/json-schema': 7.0.15 dev: true - /@eslint/eslintrc@3.3.3: - resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + /@eslint/eslintrc@3.3.7: + resolution: {integrity: sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - ajv: 6.12.6 + ajv: 6.15.0 debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.2.0 - minimatch: 3.1.3 + js-yaml: 4.3.2 + minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color dev: true - /@eslint/js@9.39.2: - resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + /@eslint/js@9.39.5: + resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true @@ -446,56 +421,138 @@ packages: tslib: 2.8.1 dev: false - /@floating-ui/core@1.7.4: - resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==} + /@floating-ui/core@1.8.0: + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} dependencies: - '@floating-ui/utils': 0.2.10 + '@floating-ui/utils': 0.2.12 dev: false - /@floating-ui/dom@1.7.5: - resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==} + /@floating-ui/dom@1.8.0: + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} dependencies: - '@floating-ui/core': 1.7.4 - '@floating-ui/utils': 0.2.10 + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 dev: false - /@floating-ui/react-dom@2.1.7(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==} + /@floating-ui/react-dom@2.1.9(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' dependencies: - '@floating-ui/dom': 1.7.5 + '@floating-ui/dom': 1.8.0 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@floating-ui/utils@0.2.10: - resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + /@floating-ui/utils@0.2.12: + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} dev: false - /@hookform/resolvers@5.2.2(react-hook-form@7.71.1): - resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} + /@hookform/resolvers@5.9.1(react-hook-form@7.90.0)(zod@3.25.76): + resolution: {integrity: sha512-7b7vsbraJxKgjVSA1Nur9tLwj539WGJUBLA7QNvXnFoT2pM5Z7G+6rlukk4B2/QrTZy6huRtH6wKeESPKuIr6w==} peerDependencies: + '@sinclair/typebox': '>=0.25.24' + '@standard-schema/spec': ^1.0.0 + '@typeschema/main': '>=0.13.7' + '@vinejs/vine': ^2.0.0 || ^3.0.0 || ^4.0.0 + ajv: ^8.12.0 + ajv-errors: ^3.0.0 + ajv-formats: ^2.1.1 + arktype: ^2.0.0 + ata-validator: ^1.2.0 + class-transformer: '>=0.4.0' + class-validator: '>=0.12.0' + computed-types: ^1.0.0 + effect: ^3.10.3 + fluentvalidation-ts: ^3.0.0 + fp-ts: ^2.7.0 + io-ts: ^2.0.0 + joi: ^17.0.0 || ^18.0.0 + nope-validator: '>=0.12.0' react-hook-form: ^7.55.0 + superstruct: '>=0.12.0' + typanion: ^3.3.2 + valibot: '>=0.31.0 || ^1.0.0-beta.4 || ^1.0.0-rc' + vest: '>=6.0.0' + yup: ^1.0.0 + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + '@sinclair/typebox': + optional: true + '@standard-schema/spec': + optional: true + '@typeschema/main': + optional: true + '@vinejs/vine': + optional: true + ajv: + optional: true + ajv-errors: + optional: true + ajv-formats: + optional: true + arktype: + optional: true + ata-validator: + optional: true + class-transformer: + optional: true + class-validator: + optional: true + computed-types: + optional: true + effect: + optional: true + fluentvalidation-ts: + optional: true + fp-ts: + optional: true + io-ts: + optional: true + joi: + optional: true + nope-validator: + optional: true + superstruct: + optional: true + typanion: + optional: true + valibot: + optional: true + vest: + optional: true + yup: + optional: true + zod: + optional: true dependencies: '@standard-schema/utils': 0.3.0 - react-hook-form: 7.71.1(react@19.2.1) + react-hook-form: 7.90.0(@types/react@19.2.18)(react@19.2.1) + zod: 3.25.76 dev: false - /@humanfs/core@0.19.1: - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} - engines: {node: '>=18.18.0'} - dev: true - - /@humanfs/node@0.16.7: - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + /@humanfs/core@0.19.2: + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} dependencies: - '@humanfs/core': 0.19.1 + '@humanfs/types': 0.15.0 + dev: true + + /@humanfs/node@0.16.8: + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 '@humanwhocodes/retry': 0.4.3 dev: true + /@humanfs/types@0.15.0: + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + dev: true + /@humanwhocodes/module-importer@1.0.1: resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} @@ -509,7 +566,7 @@ packages: /@jridgewell/gen-mapping@0.3.13: resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 '@jridgewell/trace-mapping': 0.3.31 dev: false @@ -525,65 +582,44 @@ packages: engines: {node: '>=6.0.0'} dev: false - /@jridgewell/sourcemap-codec@1.5.5: - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + /@jridgewell/sourcemap-codec@1.6.0: + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} dev: false /@jridgewell/trace-mapping@0.3.31: resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 dev: false - /@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): - resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} - requiresBuild: true - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.1 - dev: false - optional: true - - /@oxc-project/types@0.133.0: - resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + /@oxc-project/types@0.153.0: + resolution: {integrity: sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==} dev: false - /@pkgr/core@0.2.9: - resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + /@pkgr/core@0.3.6: + resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} + engines: {node: ^14.18.0 || >=16.0.0} dev: true - /@playwright/test@1.61.0: - resolution: {integrity: sha512-cKA5B6lpFEMyMGjxF54QihfYpB4FkEGH+qZhtArDEG+wezQAJY8Pq6C7T1SjWz+FFzt3TbyoXBQYk/0292TdJA==} - engines: {node: '>=18'} + /@playwright/test@1.64.0: + resolution: {integrity: sha512-oTN9FNYuN6bqQMwiho6jJKLCG2/QOYCixGXuCjRmzOq23eODeukuY2XiAVoVpAECbuezMt1MHQjAiQxK8F+Y+g==} + engines: {node: '>=20'} hasBin: true dependencies: - playwright: 1.61.0 + playwright: 1.64.0 dev: true - /@radix-ui/number@1.1.1: - resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} - dev: false - /@radix-ui/number@1.1.3: resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==} dev: false - /@radix-ui/primitive@1.1.3: - resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} - dev: false - /@radix-ui/primitive@1.1.7: resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==} dev: false - /@radix-ui/react-accessible-icon@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A==} + /@radix-ui/react-accessible-icon@1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-I+T/Hc6SY3WbFVZ9Ne1o0DJbpjQpTpLIBZcVzYiHCQ9z1yGUd/G88u5SKyM6eJ223txAOS7bp7bb9k0Ocfkjwg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -595,15 +631,15 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-visually-hidden': 1.2.12(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-accordion@1.2.20(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==} + /@radix-ui/react-accordion@1.2.21(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-2fmc6COuKLq2k3P7JZrOt9Zf8HT9nTQhHN5V+A3ih7ftqoyDcoEUrPoLq8ngr7QwTd7hpQ2xoUKWtvy1Xw9RAQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -616,47 +652,22 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collapsible': 1.1.20(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-collapsible': 1.1.21(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-collection': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-alert-dialog@1.1.23(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==} + /@radix-ui/react-alert-dialog@1.1.24(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-dRvB59HhcjIWcCPKzK5Px1yQ8pIULQQrxqgyepCqin7tNXi/xvmC86WkyMOKhuCbjz3+v9Q6oE7/tUN5NQ9dCg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -669,18 +680,18 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-dialog': 1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-arrow@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==} + /@radix-ui/react-arrow@1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-nXPI1tzJsnQw8oXvB2r49rO9TIbbJ8VFeaWDiBsqY8+iwO5xb7WjUsrXDagjAgPKTdhW8WODQnAltUhBx8I+sg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -692,15 +703,15 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + /@radix-ui/react-aspect-ratio@1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-w2u6oiTzb7WkWwV3ZClX/llYYlTbilUOkHIKkwb+PWWytQ/IdEVRNLIChjbcXy/mkqcKKM3wEhrOilJW+uPHDg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -712,59 +723,15 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-aspect-ratio@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-avatar@1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-context': 1.1.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-avatar@1.2.6(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==} + /@radix-ui/react-avatar@1.2.7(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-6AizCpbU3Zngojp9vQak8Lt4AXwdKJ2lkRYAfpoETraT+qKVNeRqytiADzneHetFEB0Wu4+qeSfmZEq0PiVDmQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -777,19 +744,19 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-checkbox@1.3.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==} + /@radix-ui/react-checkbox@1.3.12(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-gNOS2lLrpQxNLWHhCX0sayfVRiyB5gQp7nQIzpl+PM1Hkab52utsKtD27alAr78uqhpvX51kwqDp71hM5hNSwg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -802,74 +769,20 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-collapsible@1.1.20(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==} + /@radix-ui/react-collapsible@1.1.21(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-IzkusZHCZBnED5skW+b+syl8d+e3+zelvxZ2Ed9lfawqEBfa5osPj53sTJ+q/uNmGAj9e7wMYgyzwPu/luWJ4Q==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -882,21 +795,21 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-collection@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==} + /@radix-ui/react-collection@1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-yEQrjfI5eBbeYEJXOASpm+gs9eNHY8+UjkZ81GzcuwdLrSMOlEM8jnGoGdeGo92oiBMkuwZUhHTsnsrDiJLcyA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -908,53 +821,17 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-slot': 1.4.0(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.10)(react@19.2.1): + /@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==} peerDependencies: '@types/react': '*' @@ -963,37 +840,12 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-context-menu@2.3.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==} + /@radix-ui/react-context-menu@2.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-s8ABBkEuAOyhDub0Y99f8lVRlVsd28mUnOHtuqGg8LuHnefkmPN5rYzA2g8MAPLjAZMisnDHhN4dBHUM7ZF7Rg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1006,43 +858,17 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-menu': 2.1.25(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-context@1.1.2(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-context@1.1.3(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-context@1.2.2(@types/react@19.2.10)(react@19.2.1): + /@radix-ui/react-context@1.2.2(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==} peerDependencies: '@types/react': '*' @@ -1051,12 +877,12 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + /@radix-ui/react-dialog@1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-l5CX8HdNL+9cc4s7HkNyplumSA3FIx68sg9dttBHKr286fRBwq1MBniDVTeI/tkof92/APntMQvC1YG1Khb0Cg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1068,28 +894,42 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-focus-scope': 1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-portal': 1.1.18(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-slot': 1.4.0(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) aria-hidden: 1.2.6 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.1) dev: false - /@radix-ui/react-dialog@1.1.23(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==} + /@radix-ui/react-direction@1.1.5(@types/react@19.2.18)(react@19.2.1): + resolution: {integrity: sha512-/NlO/rOqQabmg+GGr86vCmbJVhqTv85NfM9g6Gknf/JVygXeZ6e6HM/RW1omerW9HsFtecwNqgEAKV6UqDsvpA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 19.2.18 + react: 19.2.1 + dev: false + + /@radix-ui/react-dismissable-layer@1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-35WHRrycfnj2m4LvPiKMKHGLM3Y2WvUA744CkRdxjs1BGDX1c8PVMfmiSGTUwnHRbqdtIoxveYTXvcJSt7VfgQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1102,78 +942,18 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - aria-hidden: 1.2.6 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.1) - dev: false - - /@radix-ui/react-direction@1.1.1(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-direction@1.1.4(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==} + /@radix-ui/react-dropdown-menu@2.1.25(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-45QO76jn/a+cRl6OPgZKCKnIMdfYZaOa6SuAJeKHFiM3orX6bnscuFQJDx3qS4Agp/RE+eKv+9gmUL8t3Ma7bw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1186,82 +966,19 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-menu': 2.1.25(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-dropdown-menu@2.1.24(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.10)(react@19.2.1): + /@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==} peerDependencies: '@types/react': '*' @@ -1270,12 +987,12 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==} + /@radix-ui/react-focus-scope@1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-I5S7vsAOozx2lWSBAyuXjWIJ/A8qx4fjbv6115WoaRYQSb1yZDaeNHonUAQzXyE8oD+0whJHbQRl4HIE2gQ0Xw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1287,39 +1004,17 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-form@0.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA==} + /@radix-ui/react-form@0.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-5ELeC+bQUHXEjU51svw11DynjGFlaUjo5QOUCr5hptvLLtBiqWtva2O3IWdjrFWQwitw6k6KpHHEyI61/kCVeg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1332,47 +1027,19 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-label': 2.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-label': 2.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-hover-card@1.1.23(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==} + /@radix-ui/react-hover-card@1.1.24(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-0w/tZHRo1fv4WsfcyaOdi5eDftFA8wQkr8WCOOuxw7fx9S2ERfshQioM269aFZsmyAHd0X1LWoZ2BQTlA/JYUw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1385,35 +1052,22 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-focus-scope': 1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-popper': 1.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-portal': 1.1.18(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-id@1.1.1(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-id@1.1.4(@types/react@19.2.10)(react@19.2.1): + /@radix-ui/react-id@1.1.4(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==} peerDependencies: '@types/react': '*' @@ -1422,13 +1076,13 @@ packages: '@types/react': optional: true dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-label@2.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==} + /@radix-ui/react-label@2.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-2vTQj72YFmeoTxWKJEMbO/9uQt6zzLgbzi0+nWBffq49oo6KhIDP/D+fA2im0wsxBCAX7QfaF3xgsYBVdDbNFQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1440,15 +1094,15 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} + /@radix-ui/react-menu@2.1.25(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-e8JaQc0ijBbTx37YVI09Nfq8ThUWJ4L1rvZ3Hvaufehco9/ug9+U3PHz9ue6w3gKd91UcKf9fgDWejeSO+VSKQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1460,52 +1114,32 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-focus-scope': 1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-popper': 1.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-portal': 1.1.18(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-roving-focus': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-slot': 1.4.0(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) aria-hidden: 1.2.6 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.1) dev: false - /@radix-ui/react-menu@2.1.24(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==} + /@radix-ui/react-menubar@1.1.25(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-JIQiKcXsgl6BtwcMe7BMLvCGH2p9Czgcmxl6Ie9Zoi+MW1mmitkcm5We01m/ndLYVI8PpRJnM/RcLV0eOCxaJQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1518,31 +1152,23 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - aria-hidden: 1.2.6 + '@radix-ui/react-collection': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-menu': 2.1.25(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-roving-focus': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.1) dev: false - /@radix-ui/react-menubar@1.1.24(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==} + /@radix-ui/react-navigation-menu@1.3.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-hZqTqDwc8Z5aI4rGIyPqCSJ2JoSNorqX/F1/gqNHkZzWJO6JuYzL7hn5sWOZJjpkfT+cbxWWRbKVU/IeiOM9ag==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1555,56 +1181,28 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-collection': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-slot': 1.4.0(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-visually-hidden': 1.2.12(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-navigation-menu@1.2.22(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-one-time-password-field@0.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw==} + /@radix-ui/react-one-time-password-field@0.1.17(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-+6jmOqjSNBr7F1uSc7HSDqD3XL54A2qA99I9rIH4JoOD/jDScw8tUmDz6HCAkjytbrE/cdL66i+QkeDyZ0WNyw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1618,24 +1216,24 @@ packages: dependencies: '@radix-ui/number': 1.1.3 '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-collection': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-roving-focus': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-password-toggle-field@0.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw==} + /@radix-ui/react-password-toggle-field@0.1.12(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-+SWSgf3HRpE4zUira+p6t5KUR473PSqewtQgj9qzl3znZPRWPCGycwXNTfHwkiRTD3tEXwNIXJg0i1NTteiULw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1648,55 +1246,21 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - aria-hidden: 1.2.6 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.1) - dev: false - - /@radix-ui/react-popover@1.1.23(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==} + /@radix-ui/react-popover@1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-e70Nh+0iQ+hRAIM2nAGib5BhaDL2Rjlzxw9kIzsU2nz+t3/t82Z5qtPhY5nTnfj8FbmrU6RRIKH8RF3ynCe+vw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1709,28 +1273,29 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-focus-scope': 1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-popper': 1.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-portal': 1.1.18(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-slot': 1.4.0(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) aria-hidden: 1.2.6 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.1) dev: false - /@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + /@radix-ui/react-popper@1.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-y92/9iIdG1FyNvow0Y0qP5WJZ7oRTAKDVKCy9kJnhZfItwIKYtt0KKQeOhBKfXz0ETizduKwDRbrQHrTQScs5A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1742,53 +1307,24 @@ packages: '@types/react-dom': optional: true dependencies: - '@floating-ui/react-dom': 2.1.7(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/rect': 1.1.1 - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-popper@1.3.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@floating-ui/react-dom': 2.1.7(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-rect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@floating-ui/react-dom': 2.1.9(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-arrow': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-rect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.2.18)(react@19.2.1) '@radix-ui/rect': 1.1.3 - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-portal@1.1.17(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==} + /@radix-ui/react-portal@1.1.18(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-ygFIJ+QXNKf72AAq8x3hd22+ws7pYpmc1S0E4kW0Je+qD4qEb3FvxeKVQRGrVDMOkQaYfsB+Qs+KDh81a1gAOw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1800,16 +1336,16 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + /@radix-ui/react-presence@1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-Z7/atlkab36Z8hm+V8zYc79r0W+tiOB1o0tun1By94JyadkekLQmtEAnBSP9XKXBNQsStoStS4+gX10JpS5akA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1821,16 +1357,15 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==} + /@radix-ui/react-primitive@2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-RzcaSw5aKl0OPhBJ1wbolKSF6nVvN+0o/QLeIVq+09npiPdnCkPggy2k6gI6yetxl4ZyrlBPYFgCB8BQJZwXcQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1842,15 +1377,15 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-slot': 1.4.0(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + /@radix-ui/react-progress@1.1.17(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-A+RfMARd9u+UaVbLQDmysGlAGuaFQrF+uxF3v+7+xU8tkKoYxtCSs+6HHnIKJVgam2sGZ7c0hLRPWSnmnFyGJQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1862,118 +1397,16 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-slot': 1.2.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-progress@1.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-progress@1.1.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-context': 1.1.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-radio-group@1.4.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==} + /@radix-ui/react-radio-group@1.4.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-MaOOZ1TlaYoOeSDnHeVIONss0rp5ngOTY5HJY+Xkq16PAopnj21iU9r9m0O/77cBYu8Ksaq3BcjlaOdpbfbzzg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1986,50 +1419,22 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-roving-focus': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-roving-focus@1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==} + /@radix-ui/react-roving-focus@1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-+ZjbkU21fTSJdn4nq/IFKUdKS+JSZHORBZ8ma1f6YpOMNzRt0xf3sVAWVK/+ktHCajPy4HYDsyHXZIZmOMxzIA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2042,52 +1447,24 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-collection': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-scroll-area@1.2.18(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==} + /@radix-ui/react-scroll-area@1.3.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-1WSuAXaahfeLmFX/dfYo/ftg1filMfaWjVye2d40VlgtWMMq/NOVocfukZEXkG+dwrAoNI+qpo9bjhNSJ/XC8g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2101,21 +1478,22 @@ packages: dependencies: '@radix-ui/number': 1.1.3 '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-slot': 1.4.0(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + /@radix-ui/react-select@2.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-Tz9xOpV9Rbca5WTk7pzmfAECV3zb++uScT9mP3Px7ziAhH7SlK53b0uwMw8VubPhRK4x7uDKCy5fwevT8I0fAg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2127,35 +1505,56 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-focus-scope': 1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-popper': 1.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-portal': 1.1.18(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-slot': 1.4.0(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-visually-hidden': 1.2.12(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) aria-hidden: 1.2.6 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.1) dev: false - /@radix-ui/react-select@2.3.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==} + /@radix-ui/react-separator@1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-vugPjGQBXbJVVkftKqv/BVeZ108SyRGT3FR1oa14az6qyLgX8jPhnZYHgdSB7Gw8UJusqwb9eaK6mluOnsn/Vg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + dev: false + + /@radix-ui/react-slider@1.5.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-ZOBLK7KZvGL02V+4VwVbj8NYM4vPrAtyZdrgHSv+AMS9VL+5yAdnQF3DZT4Y8cbCp1Yn4xdp92orvez8fJa77w==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2169,104 +1568,23 @@ packages: dependencies: '@radix-ui/number': 1.1.3 '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - aria-hidden: 1.2.6 - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.1) - dev: false - - /@radix-ui/react-separator@1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-collection': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-slider@1.4.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/number': 1.1.3 - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-slot@1.2.3(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + /@radix-ui/react-slot@1.4.0(@types/react@19.2.18)(react@19.2.1): + resolution: {integrity: sha512-IuMVHZ+Ah5fkctu3X3qeFrV/ZrBcuWuDTFUUPfS/L5hvoTjzhp/HdWGE14CsQombPdEidsa33HVlYZXi49PZ1Q==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -2274,67 +1592,13 @@ packages: '@types/react': optional: true dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-slot@1.2.4(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-slot@1.3.3(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-switch@1.3.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==} + /@radix-ui/react-switch@1.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-rr+zuoAeHWK3m6H4HgjOUiu3VsrQ/9JkjwY/uMnTgHJGPe9WNzDP/MOuIV7S+65p42DIxjGL0hAHktqBq82+2A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2347,46 +1611,19 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-tabs@1.1.21(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==} + /@radix-ui/react-tabs@1.1.22(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-GmVH+P3wx1ZPMuH7WajQzygZIbqgnsb5h8z4Ow3sQMuDqgcqISSBNom6MLVNJvfrkuJTtqfXc3ZolHjX+w0CHg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2399,21 +1636,21 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-roving-focus': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-toast@1.2.23(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg==} + /@radix-ui/react-toast@1.2.24(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-XR9pDEz9CH0ez98zPXQZOpF46anvbarkoOpjXpPSX7gm21vEl5Tzoe1PSQd3EY1OTOHac4lNq+TPx/2Z7jObdQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2426,51 +1663,25 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-collection': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-portal': 1.1.18(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-visually-hidden': 1.2.12(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-toggle-group@1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==} + /@radix-ui/react-toggle-group@1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-O+Sjiv64DfnByHYtnKEXmn63uUskU/X61vjf69X1v8JFn/ZvuNNLdJTBfYurVTSDv4/RJnJGtGgb/uvuS/rRJw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2483,42 +1694,20 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-toggle': 1.1.18(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-roving-focus': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-toggle': 1.1.19(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-toggle@1.1.18(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==} + /@radix-ui/react-toggle@1.1.19(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-l7FQHtuZjUJ0vKLdhKu5LzZ8Jw6K60QEC1tC22QNLuXN2PkydSoqEhKTCqwxBFZJ52NTCjzudDvl9sUsZeUbPw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2531,16 +1720,16 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-toolbar@1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==} + /@radix-ui/react-toolbar@1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-5gyzL6B5/zjBwU3ATBWAVZ4xXQC5F8E9CB4BmYC1UvZ08pN2OJQoooeIwJtZZ/2rz4sTiQmbGfdDvmU73wRJCQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2553,20 +1742,20 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-separator': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-toggle-group': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-roving-focus': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-separator': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-toggle-group': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-tooltip@1.2.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==} + /@radix-ui/react-tooltip@1.3.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-4HM4b1Cft1CBXCGDRDG3S/fh8GedBuVOMNh145wMIfTbVqcuv+tyj03nZqYgFevWJMYo58H0s+8P6s6NaUHWtA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2579,69 +1768,25 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-popper': 1.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-portal': 1.1.18(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-slot': 1.4.0(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-visually-hidden': 1.2.12(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.2.10)(react@19.2.1): + /@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==} peerDependencies: '@types/react': '*' @@ -2650,26 +1795,11 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.2.10)(react@19.2.1): + /@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==} peerDependencies: '@types/react': '*' @@ -2679,27 +1809,13 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-use-effect-event@0.0.5(@types/react@19.2.10)(react@19.2.1): + /@radix-ui/react-use-effect-event@0.0.5(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==} peerDependencies: '@types/react': '*' @@ -2708,26 +1824,12 @@ packages: '@types/react': optional: true dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-use-escape-keydown@1.1.5(@types/react@19.2.10)(react@19.2.1): + /@radix-ui/react-use-escape-keydown@1.1.5(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA==} peerDependencies: '@types/react': '*' @@ -2736,26 +1838,12 @@ packages: '@types/react': optional: true dependencies: - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 19.2.10 - react: 19.2.1 - use-sync-external-store: 1.6.0(react@19.2.1) - dev: false - - /@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.2.10)(react@19.2.1): + /@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==} peerDependencies: '@types/react': '*' @@ -2764,24 +1852,11 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.2.10)(react@19.2.1): + /@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==} peerDependencies: '@types/react': '*' @@ -2790,24 +1865,11 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-use-previous@1.1.1(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-use-previous@1.1.4(@types/react@19.2.10)(react@19.2.1): + /@radix-ui/react-use-previous@1.1.4(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==} peerDependencies: '@types/react': '*' @@ -2816,25 +1878,11 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-use-rect@1.1.1(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/rect': 1.1.1 - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-use-rect@1.1.4(@types/react@19.2.10)(react@19.2.1): + /@radix-ui/react-use-rect@1.1.4(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==} peerDependencies: '@types/react': '*' @@ -2844,12 +1892,12 @@ packages: optional: true dependencies: '@radix-ui/rect': 1.1.3 - '@types/react': 19.2.10 + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-use-size@1.1.1(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + /@radix-ui/react-use-size@1.1.5(@types/react@19.2.18)(react@19.2.1): + resolution: {integrity: sha512-V1McDQkMGzgivwqdu7u96APs7oi0Jvj9LTzgq3WWrDZqxWdAqU9yZnZQqk5OizGFR8badUQwZsrlao7MOlWYOA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -2857,27 +1905,13 @@ packages: '@types/react': optional: true dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@types/react': 19.2.18 react: 19.2.1 dev: false - /@radix-ui/react-use-size@1.1.4(@types/react@19.2.10)(react@19.2.1): - resolution: {integrity: sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@types/react': 19.2.10 - react: 19.2.1 - dev: false - - /@radix-ui/react-visually-hidden@1.2.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==} + /@radix-ui/react-visually-hidden@1.2.12(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-/iPLVHRBzKWvekN+5xaDmXw80f+nleYYceomVORrGjuJe0LF2SCbSH4f/LG4hu32q92QSuPZx4hmSACYBVBfzQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2889,37 +1923,13 @@ packages: '@types/react-dom': optional: true dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - dev: false - - /@radix-ui/rect@1.1.1: - resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - dev: false - /@radix-ui/rect@1.1.3: resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==} dev: false @@ -2932,8 +1942,17 @@ packages: react: 19.2.1 dev: false - /@rolldown/binding-android-arm64@1.0.3: - resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + /@rolldown/binding-android-arm-eabi@1.2.13: + resolution: {integrity: sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + requiresBuild: true + dev: false + optional: true + + /@rolldown/binding-android-arm64@1.2.13: + resolution: {integrity: sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] @@ -2941,8 +1960,8 @@ packages: dev: false optional: true - /@rolldown/binding-darwin-arm64@1.0.3: - resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + /@rolldown/binding-darwin-arm64@1.2.13: + resolution: {integrity: sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] @@ -2950,8 +1969,8 @@ packages: dev: false optional: true - /@rolldown/binding-darwin-x64@1.0.3: - resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + /@rolldown/binding-darwin-x64@1.2.13: + resolution: {integrity: sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] @@ -2959,8 +1978,8 @@ packages: dev: false optional: true - /@rolldown/binding-freebsd-x64@1.0.3: - resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + /@rolldown/binding-freebsd-x64@1.2.13: + resolution: {integrity: sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] @@ -2968,8 +1987,8 @@ packages: dev: false optional: true - /@rolldown/binding-linux-arm-gnueabihf@1.0.3: - resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + /@rolldown/binding-linux-arm-gnueabihf@1.2.13: + resolution: {integrity: sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] @@ -2977,68 +1996,62 @@ packages: dev: false optional: true - /@rolldown/binding-linux-arm64-gnu@1.0.3: - resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + /@rolldown/binding-linux-arm64-gnu@1.2.13: + resolution: {integrity: sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] requiresBuild: true dev: false optional: true - /@rolldown/binding-linux-arm64-musl@1.0.3: - resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + /@rolldown/binding-linux-arm64-musl@1.2.13: + resolution: {integrity: sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] requiresBuild: true dev: false optional: true - /@rolldown/binding-linux-ppc64-gnu@1.0.3: - resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + /@rolldown/binding-linux-ppc64-gnu@1.2.13: + resolution: {integrity: sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] requiresBuild: true dev: false optional: true - /@rolldown/binding-linux-s390x-gnu@1.0.3: - resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + /@rolldown/binding-linux-s390x-gnu@1.2.13: + resolution: {integrity: sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] requiresBuild: true dev: false optional: true - /@rolldown/binding-linux-x64-gnu@1.0.3: - resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + /@rolldown/binding-linux-x64-gnu@1.2.13: + resolution: {integrity: sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] requiresBuild: true dev: false optional: true - /@rolldown/binding-linux-x64-musl@1.0.3: - resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + /@rolldown/binding-linux-x64-musl@1.2.13: + resolution: {integrity: sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] requiresBuild: true dev: false optional: true - /@rolldown/binding-openharmony-arm64@1.0.3: - resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + /@rolldown/binding-openharmony-arm64@1.2.13: + resolution: {integrity: sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] @@ -3046,20 +2059,8 @@ packages: dev: false optional: true - /@rolldown/binding-wasm32-wasi@1.0.3: - resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [wasm32] - requiresBuild: true - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) - dev: false - optional: true - - /@rolldown/binding-win32-arm64-msvc@1.0.3: - resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + /@rolldown/binding-win32-arm64-msvc@1.2.13: + resolution: {integrity: sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] @@ -3067,8 +2068,8 @@ packages: dev: false optional: true - /@rolldown/binding-win32-x64-msvc@1.0.3: - resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + /@rolldown/binding-win32-x64-msvc@1.2.13: + resolution: {integrity: sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -3076,10 +2077,6 @@ packages: dev: false optional: true - /@rolldown/pluginutils@1.0.0-rc.7: - resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} - dev: false - /@rolldown/pluginutils@1.0.1: resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} dev: false @@ -3092,105 +2089,101 @@ packages: resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} dev: false - /@tailwindcss/node@4.1.18: - resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + /@tailwindcss/node@4.3.3: + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.18.4 - jiti: 2.6.1 - lightningcss: 1.30.2 + enhanced-resolve: 5.26.0 + jiti: 2.7.0 + lightningcss: 1.32.0 magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.1.18 + source-map-js: 1.2.2 + tailwindcss: 4.3.3 dev: false - /@tailwindcss/oxide-android-arm64@4.1.18: - resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} - engines: {node: '>= 10'} + /@tailwindcss/oxide-android-arm64@4.3.3: + resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} + engines: {node: '>= 20'} cpu: [arm64] os: [android] requiresBuild: true dev: false optional: true - /@tailwindcss/oxide-darwin-arm64@4.1.18: - resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} - engines: {node: '>= 10'} + /@tailwindcss/oxide-darwin-arm64@4.3.3: + resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} + engines: {node: '>= 20'} cpu: [arm64] os: [darwin] requiresBuild: true dev: false optional: true - /@tailwindcss/oxide-darwin-x64@4.1.18: - resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} - engines: {node: '>= 10'} + /@tailwindcss/oxide-darwin-x64@4.3.3: + resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} + engines: {node: '>= 20'} cpu: [x64] os: [darwin] requiresBuild: true dev: false optional: true - /@tailwindcss/oxide-freebsd-x64@4.1.18: - resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} - engines: {node: '>= 10'} + /@tailwindcss/oxide-freebsd-x64@4.3.3: + resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} + engines: {node: '>= 20'} cpu: [x64] os: [freebsd] requiresBuild: true dev: false optional: true - /@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18: - resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} - engines: {node: '>= 10'} + /@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3: + resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} + engines: {node: '>= 20'} cpu: [arm] os: [linux] requiresBuild: true dev: false optional: true - /@tailwindcss/oxide-linux-arm64-gnu@4.1.18: - resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} - engines: {node: '>= 10'} + /@tailwindcss/oxide-linux-arm64-gnu@4.3.3: + resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [glibc] requiresBuild: true dev: false optional: true - /@tailwindcss/oxide-linux-arm64-musl@4.1.18: - resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} - engines: {node: '>= 10'} + /@tailwindcss/oxide-linux-arm64-musl@4.3.3: + resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [musl] requiresBuild: true dev: false optional: true - /@tailwindcss/oxide-linux-x64-gnu@4.1.18: - resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} - engines: {node: '>= 10'} + /@tailwindcss/oxide-linux-x64-gnu@4.3.3: + resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [glibc] requiresBuild: true dev: false optional: true - /@tailwindcss/oxide-linux-x64-musl@4.1.18: - resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} - engines: {node: '>= 10'} + /@tailwindcss/oxide-linux-x64-musl@4.3.3: + resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [musl] requiresBuild: true dev: false optional: true - /@tailwindcss/oxide-wasm32-wasi@4.1.18: - resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + /@tailwindcss/oxide-wasm32-wasi@4.3.3: + resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} engines: {node: '>=14.0.0'} cpu: [wasm32] requiresBuild: true @@ -3204,50 +2197,50 @@ packages: - '@emnapi/wasi-threads' - tslib - /@tailwindcss/oxide-win32-arm64-msvc@4.1.18: - resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} - engines: {node: '>= 10'} + /@tailwindcss/oxide-win32-arm64-msvc@4.3.3: + resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [win32] requiresBuild: true dev: false optional: true - /@tailwindcss/oxide-win32-x64-msvc@4.1.18: - resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} - engines: {node: '>= 10'} + /@tailwindcss/oxide-win32-x64-msvc@4.3.3: + resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} + engines: {node: '>= 20'} cpu: [x64] os: [win32] requiresBuild: true dev: false optional: true - /@tailwindcss/oxide@4.1.18: - resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} - engines: {node: '>= 10'} + /@tailwindcss/oxide@4.3.3: + resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} + engines: {node: '>= 20'} optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.18 - '@tailwindcss/oxide-darwin-arm64': 4.1.18 - '@tailwindcss/oxide-darwin-x64': 4.1.18 - '@tailwindcss/oxide-freebsd-x64': 4.1.18 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 - '@tailwindcss/oxide-linux-x64-musl': 4.1.18 - '@tailwindcss/oxide-wasm32-wasi': 4.1.18 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + '@tailwindcss/oxide-android-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-x64': 4.3.3 + '@tailwindcss/oxide-freebsd-x64': 4.3.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-x64-musl': 4.3.3 + '@tailwindcss/oxide-wasm32-wasi': 4.3.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 dev: false - /@tailwindcss/postcss@4.1.18: - resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==} + /@tailwindcss/postcss@4.3.3: + resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==} dependencies: - '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.1.18 - '@tailwindcss/oxide': 4.1.18 - postcss: 8.5.15 - tailwindcss: 4.1.18 + '@alloc/quick-lru': 5.3.0 + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 + postcss: 8.5.29 + tailwindcss: 4.3.3 dev: false /@tanstack/react-table@8.21.3(react-dom@19.2.1)(react@19.2.1): @@ -3267,14 +2260,6 @@ packages: engines: {node: '>=12'} dev: false - /@tybys/wasm-util@0.10.1: - resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - requiresBuild: true - dependencies: - tslib: 2.8.1 - dev: false - optional: true - /@types/d3-array@3.2.2: resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} dev: false @@ -3303,8 +2288,8 @@ packages: '@types/d3-time': 3.0.4 dev: false - /@types/d3-shape@3.1.8: - resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + /@types/d3-shape@3.2.0: + resolution: {integrity: sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==} dependencies: '@types/d3-path': 3.1.1 dev: false @@ -3317,21 +2302,21 @@ packages: resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} dev: false - /@types/debug@4.1.12: - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + /@types/debug@4.1.13: + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} dependencies: '@types/ms': 2.1.0 /@types/estree-jsx@1.0.5: resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 - /@types/estree@1.0.8: - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + /@types/estree@1.0.9: + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - /@types/hast@3.0.4: - resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + /@types/hast@3.0.5: + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} dependencies: '@types/unist': 3.0.3 @@ -3339,8 +2324,8 @@ packages: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true - /@types/lodash@4.17.23: - resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==} + /@types/lodash@4.17.26: + resolution: {integrity: sha512-48NWfiY6oNcKOh47Z4h+sIvwcQR91h6KPhaYKgeMIrkT2jV82Gy90SErj60vW8jQbeMSOSbhIv3bZ7JoqPPVhA==} dev: true /@types/mdast@4.0.4: @@ -3351,36 +2336,36 @@ packages: /@types/ms@2.1.0: resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - /@types/node@20.19.30: - resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==} + /@types/node@20.19.43: + resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==} dependencies: undici-types: 6.21.0 - /@types/prismjs@1.26.5: - resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} + /@types/prismjs@1.26.6: + resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==} dev: false /@types/qrcode@1.5.6: resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} dependencies: - '@types/node': 20.19.30 + '@types/node': 20.19.43 dev: true - /@types/react-dom@19.2.3(@types/react@19.2.10): - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + /@types/react-dom@19.2.7(@types/react@19.2.18): + resolution: {integrity: sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==} peerDependencies: '@types/react': ^19.2.0 dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 /@types/react-syntax-highlighter@15.5.13: resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 dev: true - /@types/react@19.2.10: - resolution: {integrity: sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==} + /@types/react@19.2.18: + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} dependencies: csstype: 3.2.3 @@ -3391,177 +2376,180 @@ packages: /@types/unist@3.0.3: resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - /@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0)(eslint@9.39.2)(typescript@5.9.3): - resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} + /@typescript-eslint/eslint-plugin@8.71.1(@typescript-eslint/parser@8.71.1)(eslint@9.39.5)(typescript@5.9.3): + resolution: {integrity: sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.54.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/parser': ^8.71.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.54.0 - eslint: 9.39.2 - ignore: 7.0.5 + '@typescript-eslint/parser': 8.71.1(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.71.1 + '@typescript-eslint/type-utils': 8.71.1(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/utils': 8.71.1(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.71.1 + eslint: 9.39.5 + ignore: 7.0.12 natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@5.9.3) + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@8.54.0(eslint@9.39.2)(typescript@5.9.3): - resolution: {integrity: sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==} + /@typescript-eslint/parser@8.71.1(eslint@9.39.5)(typescript@5.9.3): + resolution: {integrity: sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' dependencies: - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.54.0 + '@typescript-eslint/scope-manager': 8.71.1 + '@typescript-eslint/types': 8.71.1 + '@typescript-eslint/typescript-estree': 8.71.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.71.1 debug: 4.4.3 - eslint: 9.39.2 + eslint: 9.39.5 typescript: 5.9.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/project-service@8.54.0(typescript@5.9.3): - resolution: {integrity: sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==} + /@typescript-eslint/project-service@8.71.1(typescript@5.9.3): + resolution: {integrity: sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' dependencies: - '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) - '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/tsconfig-utils': 8.71.1(typescript@5.9.3) + '@typescript-eslint/types': 8.71.1 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@8.54.0: - resolution: {integrity: sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==} + /@typescript-eslint/scope-manager@8.71.1: + resolution: {integrity: sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/visitor-keys': 8.54.0 + '@typescript-eslint/types': 8.71.1 + '@typescript-eslint/visitor-keys': 8.71.1 dev: true - /@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.9.3): - resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==} + /@typescript-eslint/tsconfig-utils@8.71.1(typescript@5.9.3): + resolution: {integrity: sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' dependencies: typescript: 5.9.3 dev: true - /@typescript-eslint/type-utils@8.54.0(eslint@9.39.2)(typescript@5.9.3): - resolution: {integrity: sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==} + /@typescript-eslint/type-utils@8.71.1(eslint@9.39.5)(typescript@5.9.3): + resolution: {integrity: sha512-kH3t3jZCYv2PZSBEWPKTariLrCaoF1CLoPitYFBJOhAGEwWnSRAtGEifrfrpBa4K9JlwWNZotgA5w3/wLSk/FQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' dependencies: - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/types': 8.71.1 + '@typescript-eslint/typescript-estree': 8.71.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.71.1(eslint@9.39.5)(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.2 - ts-api-utils: 2.4.0(typescript@5.9.3) + eslint: 9.39.5 + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@8.54.0: - resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} + /@typescript-eslint/types@8.71.1: + resolution: {integrity: sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3): - resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==} + /@typescript-eslint/typescript-estree@8.71.1(typescript@5.9.3): + resolution: {integrity: sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' dependencies: - '@typescript-eslint/project-service': 8.54.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/visitor-keys': 8.54.0 + '@typescript-eslint/project-service': 8.71.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.71.1(typescript@5.9.3) + '@typescript-eslint/types': 8.71.1 + '@typescript-eslint/visitor-keys': 8.71.1 debug: 4.4.3 - minimatch: 9.0.7 - semver: 7.7.3 - tinyglobby: 0.2.15 - ts-api-utils: 2.4.0(typescript@5.9.3) + minimatch: 10.2.6 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@8.54.0(eslint@9.39.2)(typescript@5.9.3): - resolution: {integrity: sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==} + /@typescript-eslint/utils@8.71.1(eslint@9.39.5)(typescript@5.9.3): + resolution: {integrity: sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) - '@typescript-eslint/scope-manager': 8.54.0 - '@typescript-eslint/types': 8.54.0 - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - eslint: 9.39.2 + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5) + '@typescript-eslint/scope-manager': 8.71.1 + '@typescript-eslint/types': 8.71.1 + '@typescript-eslint/typescript-estree': 8.71.1(typescript@5.9.3) + eslint: 9.39.5 typescript: 5.9.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/visitor-keys@8.54.0: - resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==} + /@typescript-eslint/visitor-keys@8.71.1: + resolution: {integrity: sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - '@typescript-eslint/types': 8.54.0 - eslint-visitor-keys: 4.2.1 + '@typescript-eslint/types': 8.71.1 + eslint-visitor-keys: 5.0.1 dev: true - /@ungap/structured-clone@1.3.0: - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + /@ungap/structured-clone@1.4.0: + resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} dev: false - /@vitejs/plugin-react@6.0.1(vite@8.0.16): - resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} + /@vitejs/plugin-react@6.1.2(vite@8.3.4): + resolution: {integrity: sha512-fhJNGUdscQa4HkI0xFxwonWyhnVReCPDbXI7FH7jmzpnVCrFQI7PRrhWecO263610vOrs9tr2Y4xcf8uAdFxbw==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 babel-plugin-react-compiler: ^1.0.0 + oxc-transform-react: ^0.152.0 vite: ^8.0.0 peerDependenciesMeta: '@rolldown/plugin-babel': optional: true babel-plugin-react-compiler: optional: true + oxc-transform-react: + optional: true dependencies: - '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.16(@types/node@20.19.30) + '@rolldown/pluginutils': 1.0.1 + vite: 8.3.4(@types/node@20.19.43) dev: false - /acorn-jsx@5.3.2(acorn@8.15.0): + /acorn-jsx@5.3.2(acorn@8.19.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 8.15.0 + acorn: 8.19.0 dev: true - /acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + /acorn@8.19.0: + resolution: {integrity: sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -3575,8 +2563,8 @@ packages: - supports-color dev: false - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + /ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -3584,8 +2572,8 @@ packages: uri-js: 4.4.1 dev: true - /ansi-escapes@7.2.0: - resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} + /ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} engines: {node: '>=18'} dependencies: environment: 1.1.0 @@ -3596,8 +2584,8 @@ packages: engines: {node: '>=8'} dev: false - /ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + /ansi-regex@6.4.0: + resolution: {integrity: sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==} engines: {node: '>=12'} dev: true @@ -3631,16 +2619,16 @@ packages: is-array-buffer: 3.0.5 dev: true - /array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + /array-includes@3.2.0: + resolution: {integrity: sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 is-string: 1.1.1 math-intrinsics: 1.1.0 dev: true @@ -3649,11 +2637,11 @@ packages: resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 es-shim-unscopables: 1.1.0 dev: true @@ -3661,9 +2649,9 @@ packages: resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 dev: true @@ -3671,9 +2659,9 @@ packages: resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 dev: true @@ -3681,9 +2669,9 @@ packages: resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-shim-unscopables: 1.1.0 dev: true @@ -3693,9 +2681,9 @@ packages: engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 @@ -3717,10 +2705,10 @@ packages: possible-typed-array-names: 1.1.0 dev: true - /axios@1.17.0: - resolution: {integrity: sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==} + /axios@1.20.0: + resolution: {integrity: sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==} dependencies: - follow-redirects: 1.16.0 + follow-redirects: 1.16.1 form-data: 4.0.6 https-proxy-agent: 5.0.1 proxy-from-env: 2.1.0 @@ -3742,16 +2730,16 @@ packages: engines: {node: 18 || 20 || >=22} dev: true - /brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + /brace-expansion@1.1.21: + resolution: {integrity: sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 dev: true - /brace-expansion@5.0.6: - resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} - engines: {node: 18 || 20 || >=22} + /brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + engines: {node: 20 || >=22} dependencies: balanced-match: 4.0.4 dev: true @@ -3770,8 +2758,8 @@ packages: es-errors: 1.3.0 function-bind: 1.1.2 - /call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + /call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} dependencies: call-bind-apply-helpers: 1.0.2 @@ -3865,16 +2853,16 @@ packages: engines: {node: '>=6'} dev: false - /cmdk@1.1.1(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + /cmdk@1.1.1(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} peerDependencies: react: ^18 || ^19 || ^19.0.0-rc react-dom: ^18 || ^19 || ^19.0.0-rc dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-dialog': 1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) transitivePeerDependencies: @@ -4117,7 +3105,7 @@ packages: /dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.10 csstype: 3.2.3 dev: false @@ -4137,12 +3125,12 @@ packages: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: false - /enhanced-resolve@5.18.4: - resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + /enhanced-resolve@5.26.0: + resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==} engines: {node: '>=10.13.0'} dependencies: graceful-fs: 4.2.11 - tapable: 2.3.0 + tapable: 2.3.3 dev: false /entities@6.0.1: @@ -4155,24 +3143,34 @@ packages: engines: {node: '>=18'} dev: true - /es-abstract@1.24.1: - resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + /es-abstract-get@1.0.0: + resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + is-callable: 1.2.7 + object-inspect: 1.13.4 + dev: true + + /es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} engines: {node: '>= 0.4'} dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 data-view-byte-offset: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 + es-to-primitive: 1.3.4 + function.prototype.name: 1.2.0 get-intrinsic: 1.3.0 get-proto: 1.0.1 get-symbol-description: 1.1.0 @@ -4181,7 +3179,7 @@ packages: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.4 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -4197,22 +3195,22 @@ packages: object-inspect: 1.13.4 object-keys: 1.1.1 object.assign: 4.1.7 - own-keys: 1.0.1 + own-keys: 1.0.2 regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 + safe-array-concat: 1.1.4 safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 + string.prototype.trim: 1.2.11 + string.prototype.trimend: 1.0.10 string.prototype.trimstart: 1.0.8 typed-array-buffer: 1.0.3 typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 + typed-array-byte-offset: 1.0.5 + typed-array-length: 1.0.8 unbox-primitive: 1.1.0 - which-typed-array: 1.1.20 + which-typed-array: 1.1.24 dev: true /es-define-property@1.0.1: @@ -4223,14 +3221,14 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - /es-iterator-helpers@1.3.1: - resolution: {integrity: sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==} + /es-iterator-helpers@1.4.0: + resolution: {integrity: sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-set-tostringtag: 2.1.0 function-bind: 1.1.2 @@ -4243,11 +3241,10 @@ packages: internal-slot: 1.1.0 iterator.prototype: 1.1.5 math-intrinsics: 1.1.0 - safe-array-concat: 1.1.3 dev: true - /es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + /es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 @@ -4259,19 +3256,22 @@ packages: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.4 /es-shim-unscopables@1.1.0: resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} engines: {node: '>= 0.4'} dependencies: - hasown: 2.0.2 + hasown: 2.0.4 dev: true - /es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + /es-to-primitive@1.3.4: + resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} engines: {node: '>= 0.4'} dependencies: + es-abstract-get: 1.0.0 + es-define-property: 1.0.1 + es-errors: 1.3.0 is-callable: 1.2.7 is-date-object: 1.1.0 is-symbol: 1.1.1 @@ -4287,17 +3287,17 @@ packages: engines: {node: '>=12'} dev: false - /eslint-config-prettier@10.1.8(eslint@9.39.2): + /eslint-config-prettier@10.1.8(eslint@9.39.5): resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 9.39.2 + eslint: 9.39.5 dev: true - /eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8)(eslint@9.39.2)(prettier@3.8.1): - resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} + /eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8)(eslint@9.39.5)(prettier@3.9.10): + resolution: {integrity: sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: '@types/eslint': '>=8.0.0' @@ -4310,46 +3310,46 @@ packages: eslint-config-prettier: optional: true dependencies: - eslint: 9.39.2 - eslint-config-prettier: 10.1.8(eslint@9.39.2) - prettier: 3.8.1 + eslint: 9.39.5 + eslint-config-prettier: 10.1.8(eslint@9.39.5) + prettier: 3.9.10 prettier-linter-helpers: 1.0.1 - synckit: 0.11.12 + synckit: 0.11.13 dev: true - /eslint-plugin-react-hooks@5.2.0(eslint@9.39.2): + /eslint-plugin-react-hooks@5.2.0(eslint@9.39.5): resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} engines: {node: '>=10'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 dependencies: - eslint: 9.39.2 + eslint: 9.39.5 dev: true - /eslint-plugin-react@7.37.5(eslint@9.39.2): + /eslint-plugin-react@7.37.5(eslint@9.39.5): resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 dependencies: - array-includes: 3.1.9 + array-includes: 3.2.0 array.prototype.findlast: 1.2.5 array.prototype.flatmap: 1.3.3 array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 - es-iterator-helpers: 1.3.1 - eslint: 9.39.2 + es-iterator-helpers: 1.4.0 + eslint: 9.39.5 estraverse: 5.3.0 - hasown: 2.0.2 + hasown: 2.0.4 jsx-ast-utils: 3.3.5 minimatch: 3.1.3 object.entries: 1.1.9 object.fromentries: 2.0.8 object.values: 1.2.1 prop-types: 15.8.1 - resolve: 2.0.0-next.6 + resolve: 2.0.0-next.7 semver: 6.3.1 - string.prototype.matchall: 4.0.12 + string.prototype.matchall: 4.1.0 string.prototype.repeat: 1.0.0 dev: true @@ -4371,9 +3371,15 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /eslint@9.39.2: - resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + /eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + dev: true + + /eslint@9.39.5: + resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -4381,19 +3387,19 @@ packages: jiti: optional: true dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.1 + '@eslint/config-array': 0.21.2 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.3 - '@eslint/js': 9.39.2 + '@eslint/eslintrc': 3.3.7 + '@eslint/js': 9.39.5 '@eslint/plugin-kit': 0.4.1 - '@humanfs/node': 0.16.7 + '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.12.6 + '@types/estree': 1.0.9 + ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.3 @@ -4412,7 +3418,7 @@ packages: is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 - minimatch: 3.1.3 + minimatch: 3.1.5 natural-compare: 1.4.0 optionator: 0.9.4 transitivePeerDependencies: @@ -4423,8 +3429,8 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) + acorn: 8.19.0 + acorn-jsx: 5.3.2(acorn@8.19.0) eslint-visitor-keys: 4.2.1 dev: true @@ -4491,8 +3497,8 @@ packages: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} dev: true - /fast-equals@5.4.0: - resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} + /fast-equals@5.4.4: + resolution: {integrity: sha512-d9/I4rfMfUlO9HOkQqLJgfyRM4siqk1SF3Gjxm4OAMZzV+kCKHe1t/bHxn3xtCPHs3HCQFcvqzkTuanzqECQxw==} engines: {node: '>=6.0.0'} dev: false @@ -4510,7 +3516,7 @@ packages: format: 0.2.2 dev: false - /fdir@6.5.0(picomatch@4.0.4): + /fdir@6.5.0(picomatch@4.0.7): resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} peerDependencies: @@ -4519,7 +3525,7 @@ packages: picomatch: optional: true dependencies: - picomatch: 4.0.4 + picomatch: 4.0.7 /file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} @@ -4555,16 +3561,16 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} dependencies: - flatted: 3.4.2 + flatted: 3.4.4 keyv: 4.5.4 dev: true - /flatted@3.4.2: - resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + /flatted@3.4.4: + resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==} dev: true - /follow-redirects@1.16.0: - resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + /follow-redirects@1.16.1: + resolution: {integrity: sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -4596,14 +3602,6 @@ packages: engines: {node: '>=0.4.x'} dev: false - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true - optional: true - /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -4615,16 +3613,19 @@ packages: /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - /function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + /function.prototype.name@1.2.0: + resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 - define-properties: 1.2.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 functions-have-names: 1.2.3 - hasown: 2.0.2 + has-property-descriptors: 1.0.2 + hasown: 2.0.4 is-callable: 1.2.7 + is-document.all: 1.0.0 dev: true /functions-have-names@1.2.3: @@ -4641,8 +3642,8 @@ packages: engines: {node: 6.* || 8.* || >= 10.*} dev: false - /get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + /get-east-asian-width@1.7.0: + resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==} engines: {node: '>=18'} dev: true @@ -4653,12 +3654,12 @@ packages: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.4 math-intrinsics: 1.1.0 /get-nonce@1.0.1: @@ -4671,7 +3672,7 @@ packages: engines: {node: '>= 0.4'} dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 /get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} @@ -4752,27 +3753,20 @@ packages: dependencies: has-symbols: 1.1.0 - /hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - dependencies: - function-bind: 1.1.2 - /hasown@2.0.4: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 - dev: false /hast-util-from-parse5@8.0.3: resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/unist': 3.0.3 devlop: 1.1.0 hastscript: 9.0.1 - property-information: 7.1.0 + property-information: 7.2.0 vfile: 6.0.3 vfile-location: 5.0.3 web-namespaces: 2.0.1 @@ -4781,27 +3775,27 @@ packages: /hast-util-heading-rank@3.0.0: resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 dev: false /hast-util-is-element@3.0.0: resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 dev: false /hast-util-parse-selector@4.0.0: resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 dev: false /hast-util-raw@9.1.0: resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/unist': 3.0.3 - '@ungap/structured-clone': 1.3.0 + '@ungap/structured-clone': 1.4.0 hast-util-from-parse5: 8.0.3 hast-util-to-parse5: 8.0.1 html-void-elements: 3.0.0 @@ -4817,16 +3811,16 @@ packages: /hast-util-sanitize@5.0.2: resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==} dependencies: - '@types/hast': 3.0.4 - '@ungap/structured-clone': 1.3.0 + '@types/hast': 3.0.5 + '@ungap/structured-clone': 1.4.0 unist-util-position: 5.0.0 dev: false /hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} dependencies: - '@types/estree': 1.0.8 - '@types/hast': 3.0.4 + '@types/estree': 1.0.9 + '@types/hast': 3.0.5 '@types/unist': 3.0.3 comma-separated-tokens: 2.0.3 devlop: 1.1.0 @@ -4835,7 +3829,7 @@ packages: mdast-util-mdx-expression: 2.0.1 mdast-util-mdx-jsx: 3.2.0 mdast-util-mdxjs-esm: 2.0.1 - property-information: 7.1.0 + property-information: 7.2.0 space-separated-tokens: 2.0.2 style-to-js: 1.1.21 unist-util-position: 5.0.0 @@ -4847,10 +3841,10 @@ packages: /hast-util-to-parse5@8.0.1: resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 comma-separated-tokens: 2.0.3 devlop: 1.1.0 - property-information: 7.1.0 + property-information: 7.2.0 space-separated-tokens: 2.0.2 web-namespaces: 2.0.1 zwitch: 2.0.4 @@ -4859,13 +3853,13 @@ packages: /hast-util-to-string@3.0.1: resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 dev: false /hast-util-to-text@4.0.2: resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/unist': 3.0.3 hast-util-is-element: 3.0.0 unist-util-find-after: 5.0.0 @@ -4874,16 +3868,16 @@ packages: /hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 dev: false /hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 comma-separated-tokens: 2.0.3 hast-util-parse-selector: 4.0.0 - property-information: 7.1.0 + property-information: 7.2.0 space-separated-tokens: 2.0.2 dev: false @@ -4891,8 +3885,13 @@ packages: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} dev: false - /highlight.js@11.11.1: - resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + /highlight.js@11.11.2: + resolution: {integrity: sha512-oaXMACAU0kzOMXBjWpNcX+vlwSBCIAiZ9BHa7gA15NOTtT2L/l8OSZDuqS2XppOhZBPJ7hm4o8ep2kyuip2uEQ==} + engines: {node: '>=12.0.0'} + dev: false + + /highlight.js@11.12.0: + resolution: {integrity: sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg==} engines: {node: '>=12.0.0'} dev: false @@ -4900,8 +3899,8 @@ packages: resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} dev: false - /html-parse-stringify@3.0.1: - resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + /html-parse-stringify@3.1.0: + resolution: {integrity: sha512-E0oAXcELOtsXe+BmpJ2EZyedbldPpriV5vICzEuo6xjC/D1lDukOI7KrpfQGF2Qc4wWEy0nk3bFORS2K5ZAhFQ==} dependencies: void-elements: 3.1.0 dev: false @@ -4929,21 +3928,21 @@ packages: engines: {node: '>=16.17.0'} dev: true - /i18next-browser-languagedetector@8.2.0: - resolution: {integrity: sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==} + /i18next-browser-languagedetector@8.2.1: + resolution: {integrity: sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==} dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.10 dev: false - /i18next@25.8.0(typescript@5.9.3): - resolution: {integrity: sha512-urrg4HMFFMQZ2bbKRK7IZ8/CTE7D8H4JRlAwqA2ZwDRFfdd0K/4cdbNNLgfn9mo+I/h9wJu61qJzH7jCFAhUZQ==} + /i18next@25.10.10(typescript@5.9.3): + resolution: {integrity: sha512-cqUW2Z3EkRx7NqSyywjkgCLK7KLCL6IFVFcONG7nVYIJ3ekZ1/N5jUsihHV6Bq37NfhgtczxJcxduELtjTwkuQ==} peerDependencies: - typescript: ^5 + typescript: ^5 || ^6 peerDependenciesMeta: typescript: optional: true dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.10 typescript: 5.9.3 dev: false @@ -4952,8 +3951,8 @@ packages: engines: {node: '>= 4'} dev: true - /ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + /ignore@7.0.12: + resolution: {integrity: sha512-/8UvqAPU9DGTI9k4mxtf49U37Isfwr8Uts96+SBHIkFxPJnHS0Ew4f00sM4Scd8V8EjM0jUNtVDdv1kPdt35lg==} engines: {node: '>= 4'} dev: true @@ -4974,8 +3973,8 @@ packages: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} dev: false - /input-otp@1.4.2(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==} + /input-otp@1.5.0(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-3AcfdW1sNG0FmSA5hHMBXG7jNW5CdcdKs8ln8JOmn6S2SRkoXoJx+2UwC+ZvjflfnOdHJx0SfQ9/YjlxVYLtGQ==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc @@ -4989,8 +3988,8 @@ packages: engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.1.0 + hasown: 2.0.4 + side-channel: 1.1.1 dev: true /internmap@2.0.3: @@ -5013,7 +4012,7 @@ packages: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 dev: true @@ -5049,11 +4048,11 @@ packages: engines: {node: '>= 0.4'} dev: true - /is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + /is-core-module@2.17.0: + resolution: {integrity: sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==} engines: {node: '>= 0.4'} dependencies: - hasown: 2.0.2 + hasown: 2.0.4 dev: true /is-data-view@1.0.2: @@ -5077,6 +4076,13 @@ packages: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} dev: false + /is-document.all@1.0.0: + resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} + engines: {node: '>= 0.4'} + dependencies: + call-bound: 1.0.4 + dev: true + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -5103,7 +4109,7 @@ packages: resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} engines: {node: '>=18'} dependencies: - get-east-asian-width: 1.4.0 + get-east-asian-width: 1.7.0 dev: true /is-generator-function@1.1.2: @@ -5163,7 +4169,7 @@ packages: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.4 dev: true /is-set@2.0.3: @@ -5204,7 +4210,7 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} dependencies: - which-typed-array: 1.1.20 + which-typed-array: 1.1.24 dev: true /is-weakmap@2.0.2: @@ -5240,23 +4246,23 @@ packages: engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 get-proto: 1.0.1 has-symbols: 1.1.0 set-function-name: 2.0.2 dev: true - /jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + /jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true dev: false /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - /js-yaml@4.2.0: - resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} + /js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true dependencies: argparse: 2.0.1 @@ -5278,7 +4284,7 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} dependencies: - array-includes: 3.1.9 + array-includes: 3.2.0 array.prototype.flat: 1.3.3 object.assign: 4.1.7 object.values: 1.2.1 @@ -5298,15 +4304,6 @@ packages: type-check: 0.4.0 dev: true - /lightningcss-android-arm64@1.30.2: - resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: false - optional: true - /lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -5316,11 +4313,11 @@ packages: dev: false optional: true - /lightningcss-darwin-arm64@1.30.2: - resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + /lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} engines: {node: '>= 12.0.0'} cpu: [arm64] - os: [darwin] + os: [android] requiresBuild: true dev: false optional: true @@ -5334,10 +4331,10 @@ packages: dev: false optional: true - /lightningcss-darwin-x64@1.30.2: - resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + /lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} engines: {node: '>= 12.0.0'} - cpu: [x64] + cpu: [arm64] os: [darwin] requiresBuild: true dev: false @@ -5352,11 +4349,11 @@ packages: dev: false optional: true - /lightningcss-freebsd-x64@1.30.2: - resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + /lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} engines: {node: '>= 12.0.0'} cpu: [x64] - os: [freebsd] + os: [darwin] requiresBuild: true dev: false optional: true @@ -5370,11 +4367,11 @@ packages: dev: false optional: true - /lightningcss-linux-arm-gnueabihf@1.30.2: - resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + /lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] + cpu: [x64] + os: [freebsd] requiresBuild: true dev: false optional: true @@ -5388,12 +4385,11 @@ packages: dev: false optional: true - /lightningcss-linux-arm64-gnu@1.30.2: - resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + /lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} engines: {node: '>= 12.0.0'} - cpu: [arm64] + cpu: [arm] os: [linux] - libc: [glibc] requiresBuild: true dev: false optional: true @@ -5403,17 +4399,15 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] requiresBuild: true dev: false optional: true - /lightningcss-linux-arm64-musl@1.30.2: - resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + /lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] requiresBuild: true dev: false optional: true @@ -5423,17 +4417,15 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] requiresBuild: true dev: false optional: true - /lightningcss-linux-x64-gnu@1.30.2: - resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + /lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} engines: {node: '>= 12.0.0'} - cpu: [x64] + cpu: [arm64] os: [linux] - libc: [glibc] requiresBuild: true dev: false optional: true @@ -5443,17 +4435,15 @@ packages: engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] requiresBuild: true dev: false optional: true - /lightningcss-linux-x64-musl@1.30.2: - resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + /lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] requiresBuild: true dev: false optional: true @@ -5463,16 +4453,15 @@ packages: engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] requiresBuild: true dev: false optional: true - /lightningcss-win32-arm64-msvc@1.30.2: - resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + /lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] + cpu: [x64] + os: [linux] requiresBuild: true dev: false optional: true @@ -5486,10 +4475,10 @@ packages: dev: false optional: true - /lightningcss-win32-x64-msvc@1.30.2: - resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + /lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} engines: {node: '>= 12.0.0'} - cpu: [x64] + cpu: [arm64] os: [win32] requiresBuild: true dev: false @@ -5504,24 +4493,14 @@ packages: dev: false optional: true - /lightningcss@1.30.2: - resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + /lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} engines: {node: '>= 12.0.0'} - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.30.2 - lightningcss-darwin-arm64: 1.30.2 - lightningcss-darwin-x64: 1.30.2 - lightningcss-freebsd-x64: 1.30.2 - lightningcss-linux-arm-gnueabihf: 1.30.2 - lightningcss-linux-arm64-gnu: 1.30.2 - lightningcss-linux-arm64-musl: 1.30.2 - lightningcss-linux-x64-gnu: 1.30.2 - lightningcss-linux-x64-musl: 1.30.2 - lightningcss-win32-arm64-msvc: 1.30.2 - lightningcss-win32-x64-msvc: 1.30.2 + cpu: [x64] + os: [win32] + requiresBuild: true dev: false + optional: true /lightningcss@1.32.0: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} @@ -5542,6 +4521,25 @@ packages: lightningcss-win32-x64-msvc: 1.32.0 dev: false + /lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + dev: false + /lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -5559,9 +4557,9 @@ packages: lilconfig: 3.1.3 listr2: 8.3.3 micromatch: 4.0.8 - pidtree: 0.6.0 + pidtree: 0.6.1 string-argv: 0.3.2 - yaml: 2.8.2 + yaml: 2.9.1 transitivePeerDependencies: - supports-color dev: true @@ -5604,10 +4602,10 @@ packages: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} engines: {node: '>=18'} dependencies: - ansi-escapes: 7.2.0 + ansi-escapes: 7.3.0 cli-cursor: 5.0.0 slice-ansi: 7.1.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrap-ansi: 9.0.2 dev: true @@ -5631,9 +4629,9 @@ packages: /lowlight@3.3.0: resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 devlop: 1.1.0 - highlight.js: 11.11.1 + highlight.js: 11.11.2 dev: false /lucide-react@0.507.0(react@19.2.1): @@ -5647,7 +4645,7 @@ packages: /magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/sourcemap-codec': 1.6.0 dev: false /markdown-table@3.0.4: @@ -5658,8 +4656,8 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - /mdast-util-find-and-replace@3.0.2: - resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + /mdast-util-find-and-replace@3.0.3: + resolution: {integrity: sha512-xPgpDNl0/OXHsI7TlaIs22lWnH3KlpvZdXG1ET/m/YT2Hhdkx8lJV4kLphE6l9simyM4KPDFBwas08BgEob6Jw==} dependencies: '@types/mdast': 4.0.4 escape-string-regexp: 5.0.0 @@ -5667,20 +4665,20 @@ packages: unist-util-visit-parents: 6.0.2 dev: false - /mdast-util-from-markdown@2.0.2: - resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + /mdast-util-from-markdown@2.1.0: + resolution: {integrity: sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==} dependencies: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 decode-named-character-reference: 1.3.0 devlop: 1.1.0 mdast-util-to-string: 4.0.0 - micromark: 4.0.2 + micromark: 4.0.3 micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-decode-string: 2.0.1 micromark-util-normalize-identifier: 2.0.1 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 unist-util-stringify-position: 4.0.0 transitivePeerDependencies: - supports-color @@ -5692,7 +4690,7 @@ packages: '@types/mdast': 4.0.4 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-find-and-replace: 3.0.2 + mdast-util-find-and-replace: 3.0.3 micromark-util-character: 2.1.1 dev: false @@ -5701,19 +4699,19 @@ packages: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 micromark-util-normalize-identifier: 2.0.1 transitivePeerDependencies: - supports-color dev: false - /mdast-util-gfm-strikethrough@2.0.0: - resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + /mdast-util-gfm-strikethrough@2.0.1: + resolution: {integrity: sha512-OuJHqvr455pwu2OaOrir7dbzqs4jlWKOtlu9L6GjcIjjQwNyw02VntQsr/Ek6/A13vgnXfUhBekWRUP1OkNivw==} dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 transitivePeerDependencies: - supports-color dev: false @@ -5724,8 +4722,8 @@ packages: '@types/mdast': 4.0.4 devlop: 1.1.0 markdown-table: 3.0.4 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 transitivePeerDependencies: - supports-color dev: false @@ -5735,8 +4733,8 @@ packages: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 transitivePeerDependencies: - supports-color dev: false @@ -5744,13 +4742,13 @@ packages: /mdast-util-gfm@3.1.0: resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} dependencies: - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.1.0 mdast-util-gfm-autolink-literal: 2.0.1 mdast-util-gfm-footnote: 2.1.0 - mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-strikethrough: 2.0.1 mdast-util-gfm-table: 2.0.0 mdast-util-gfm-task-list-item: 2.0.0 - mdast-util-to-markdown: 2.1.2 + mdast-util-to-markdown: 2.2.0 transitivePeerDependencies: - supports-color dev: false @@ -5759,11 +4757,11 @@ packages: resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} dependencies: '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 transitivePeerDependencies: - supports-color dev: false @@ -5772,13 +4770,13 @@ packages: resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} dependencies: '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 '@types/unist': 3.0.3 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 parse-entities: 4.0.2 stringify-entities: 4.0.4 unist-util-stringify-position: 4.0.0 @@ -5791,11 +4789,11 @@ packages: resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} dependencies: '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 + mdast-util-from-markdown: 2.1.0 + mdast-util-to-markdown: 2.2.0 transitivePeerDependencies: - supports-color dev: false @@ -5810,9 +4808,9 @@ packages: /mdast-util-to-hast@13.2.1: resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.0 + '@ungap/structured-clone': 1.4.0 devlop: 1.1.0 micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 @@ -5821,16 +4819,18 @@ packages: vfile: 6.0.3 dev: false - /mdast-util-to-markdown@2.1.2: - resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + /mdast-util-to-markdown@2.2.0: + resolution: {integrity: sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA==} dependencies: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 longest-streak: 3.1.0 mdast-util-phrasing: 4.1.0 mdast-util-to-string: 4.0.0 + micromark-util-character: 2.1.1 micromark-util-classify-character: 2.0.1 micromark-util-decode-string: 2.0.1 + micromark-util-html-tag-name: 2.0.1 unist-util-visit: 5.1.0 zwitch: 2.0.4 dev: false @@ -5845,25 +4845,26 @@ packages: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true - /micromark-core-commonmark@2.0.3: - resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + /micromark-core-commonmark@2.0.4: + resolution: {integrity: sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==} dependencies: decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-factory-destination: 2.0.1 micromark-factory-label: 2.0.1 - micromark-factory-space: 2.0.1 + micromark-factory-space: 2.1.0 micromark-factory-title: 2.0.1 micromark-factory-whitespace: 2.0.1 micromark-util-character: 2.1.1 micromark-util-chunked: 2.0.1 micromark-util-classify-character: 2.0.1 + micromark-util-edit-map: 1.0.0 micromark-util-html-tag-name: 2.0.1 micromark-util-normalize-identifier: 2.0.1 micromark-util-resolve-all: 2.0.1 micromark-util-subtokenize: 2.1.0 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-extension-gfm-autolink-literal@2.1.0: @@ -5872,20 +4873,20 @@ packages: micromark-util-character: 2.1.1 micromark-util-sanitize-uri: 2.0.1 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-extension-gfm-footnote@2.1.0: resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} dependencies: devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 + micromark-core-commonmark: 2.0.4 + micromark-factory-space: 2.1.0 micromark-util-character: 2.1.1 micromark-util-normalize-identifier: 2.0.1 micromark-util-sanitize-uri: 2.0.1 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-extension-gfm-strikethrough@2.1.0: @@ -5896,33 +4897,33 @@ packages: micromark-util-classify-character: 2.0.1 micromark-util-resolve-all: 2.0.1 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false - /micromark-extension-gfm-table@2.1.1: - resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + /micromark-extension-gfm-table@2.1.2: + resolution: {integrity: sha512-pRzm4kDTu0MjlmBkxmS9yYhw60nncfcEwu9NNdPFSQEFXS95ZKyIIyTSHu/o3ReBUrLKYEq+7YaXCRn/bPB4MA==} dependencies: devlop: 1.1.0 - micromark-factory-space: 2.0.1 + micromark-factory-space: 2.1.0 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-extension-gfm-tagfilter@2.0.0: resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} dependencies: - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-extension-gfm-task-list-item@2.1.0: resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} dependencies: devlop: 1.1.0 - micromark-factory-space: 2.0.1 + micromark-factory-space: 2.1.0 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-extension-gfm@3.0.0: @@ -5931,11 +4932,11 @@ packages: micromark-extension-gfm-autolink-literal: 2.1.0 micromark-extension-gfm-footnote: 2.1.0 micromark-extension-gfm-strikethrough: 2.1.0 - micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-table: 2.1.2 micromark-extension-gfm-tagfilter: 2.0.0 micromark-extension-gfm-task-list-item: 2.1.0 micromark-util-combine-extensions: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-factory-destination@2.0.1: @@ -5943,7 +4944,7 @@ packages: dependencies: micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-factory-label@2.0.1: @@ -5952,39 +4953,39 @@ packages: devlop: 1.1.0 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false - /micromark-factory-space@2.0.1: - resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + /micromark-factory-space@2.1.0: + resolution: {integrity: sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==} dependencies: micromark-util-character: 2.1.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-factory-title@2.0.1: resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} dependencies: - micromark-factory-space: 2.0.1 + micromark-factory-space: 2.1.0 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-factory-whitespace@2.0.1: resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} dependencies: - micromark-factory-space: 2.0.1 + micromark-factory-space: 2.1.0 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-util-character@2.1.1: resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} dependencies: micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-util-chunked@2.0.1: @@ -5998,14 +4999,14 @@ packages: dependencies: micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-util-combine-extensions@2.0.1: resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} dependencies: micromark-util-chunked: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-util-decode-numeric-character-reference@2.0.2: @@ -6023,6 +5024,12 @@ packages: micromark-util-symbol: 2.0.1 dev: false + /micromark-util-edit-map@1.0.0: + resolution: {integrity: sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==} + dependencies: + micromark-util-types: 2.0.3 + dev: false + /micromark-util-encode@2.0.1: resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} dev: false @@ -6040,7 +5047,7 @@ packages: /micromark-util-resolve-all@2.0.1: resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} dependencies: - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-util-sanitize-uri@2.0.1: @@ -6057,37 +5064,38 @@ packages: devlop: 1.1.0 micromark-util-chunked: 2.0.1 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 dev: false /micromark-util-symbol@2.0.1: resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} dev: false - /micromark-util-types@2.0.2: - resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + /micromark-util-types@2.0.3: + resolution: {integrity: sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==} dev: false - /micromark@4.0.2: - resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + /micromark@4.0.3: + resolution: {integrity: sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==} dependencies: - '@types/debug': 4.1.12 + '@types/debug': 4.1.13 debug: 4.4.3 decode-named-character-reference: 1.3.0 devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 + micromark-core-commonmark: 2.0.4 + micromark-factory-space: 2.1.0 micromark-util-character: 2.1.1 micromark-util-chunked: 2.0.1 micromark-util-combine-extensions: 2.0.1 micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-edit-map: 1.0.0 micromark-util-encode: 2.0.1 micromark-util-normalize-identifier: 2.0.1 micromark-util-resolve-all: 2.0.1 micromark-util-sanitize-uri: 2.0.1 micromark-util-subtokenize: 2.1.0 micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 + micromark-util-types: 2.0.3 transitivePeerDependencies: - supports-color dev: false @@ -6122,24 +5130,30 @@ packages: engines: {node: '>=18'} dev: true + /minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + dependencies: + brace-expansion: 5.0.12 + dev: true + /minimatch@3.1.3: resolution: {integrity: sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==} dependencies: - brace-expansion: 1.1.12 + brace-expansion: 1.1.21 dev: true - /minimatch@9.0.7: - resolution: {integrity: sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==} - engines: {node: '>=16 || 14 >=14.17'} + /minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} dependencies: - brace-expansion: 5.0.6 + brace-expansion: 1.1.21 dev: true /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - /nanoid@3.3.12: - resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + /nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true dev: false @@ -6148,8 +5162,8 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true - /node-exports-info@1.6.0: - resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} + /node-exports-info@1.6.2: + resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==} engines: {node: '>= 0.4'} dependencies: array.prototype.flatmap: 1.3.3 @@ -6183,10 +5197,10 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 has-symbols: 1.1.0 object-keys: 1.1.1 dev: true @@ -6195,30 +5209,30 @@ packages: resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 dev: true /object.fromentries@2.0.8: resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 dev: true /object.values@1.2.1: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 dev: true /onetime@6.0.0: @@ -6247,10 +5261,11 @@ packages: word-wrap: 1.2.5 dev: true - /own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + /own-keys@1.0.2: + resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==} engines: {node: '>= 0.4'} dependencies: + call-bound: 1.0.4 get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 @@ -6341,30 +5356,28 @@ packages: engines: {node: '>=8.6'} dev: true - /picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + /picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} engines: {node: '>=12'} - /pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + /pidtree@0.6.1: + resolution: {integrity: sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw==} engines: {node: '>=0.10'} hasBin: true dev: true - /playwright-core@1.61.0: - resolution: {integrity: sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==} - engines: {node: '>=18'} + /playwright-core@1.64.0: + resolution: {integrity: sha512-T9r+MZkTECl2+oUcZ26YgZzTrYidTsIhYYkJix+6iDOymOb1FB2RLLSOxRGnZGv5DStt+aduIVbt+E8WygXYpg==} + engines: {node: '>=20'} hasBin: true dev: true - /playwright@1.61.0: - resolution: {integrity: sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==} - engines: {node: '>=18'} + /playwright@1.64.0: + resolution: {integrity: sha512-kVzTnYFYEyQ9RGzBjNCd2+yEqWmVbxl3AHPynv3qQ6JTjv4a+kwRcnqQwIpuP3NnrnIrE/Q6wWAIAvMfIE2/Yg==} + engines: {node: '>=20'} hasBin: true dependencies: - playwright-core: 1.61.0 - optionalDependencies: - fsevents: 2.3.2 + playwright-core: 1.64.0 dev: true /pngjs@5.0.0: @@ -6377,13 +5390,13 @@ packages: engines: {node: '>= 0.4'} dev: true - /postcss@8.5.15: - resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + /postcss@8.5.29: + resolution: {integrity: sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==} engines: {node: ^10 || ^12 || >=14} dependencies: - nanoid: 3.3.12 + nanoid: 3.3.20 picocolors: 1.1.1 - source-map-js: 1.2.1 + source-map-js: 1.2.2 dev: false /prelude-ls@1.2.1: @@ -6398,8 +5411,8 @@ packages: fast-diff: 1.3.0 dev: true - /prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + /prettier@3.9.10: + resolution: {integrity: sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==} engines: {node: '>=14'} hasBin: true dev: true @@ -6416,8 +5429,8 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 - /property-information@7.1.0: - resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + /property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} dev: false /proxy-from-env@2.1.0: @@ -6440,8 +5453,8 @@ packages: yargs: 15.4.1 dev: false - /radix-ui@1.6.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-QBdhh1arIEUvPC0dQ5+nwWAxt7+N+oP/9jPwjJkGFoSk/sqxg32gJtSXGtFh8frAIcS6oC9cx2Q+7KYCQLOAeA==} + /radix-ui@1.7.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-x+z5TV2v6u0QpAQ4dQfNMcvtIdSQ1Op2oqBEyfDXjNldRZbMTP+hcjr5Iie31bep81Axeioa+70Ww7pZDzTEQQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -6454,62 +5467,62 @@ packages: optional: true dependencies: '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-accessible-icon': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-accordion': 1.2.20(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-alert-dialog': 1.1.23(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-aspect-ratio': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-avatar': 1.2.6(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-checkbox': 1.3.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-collapsible': 1.1.20(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-context-menu': 2.3.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-dropdown-menu': 2.1.24(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-form': 0.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-hover-card': 1.1.23(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-label': 2.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-menubar': 1.1.24(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-navigation-menu': 1.2.22(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-one-time-password-field': 0.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-password-toggle-field': 0.1.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-popover': 1.1.23(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-progress': 1.1.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-radio-group': 1.4.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-scroll-area': 1.2.18(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-select': 2.3.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-separator': 1.1.15(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slider': 1.4.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-switch': 1.3.7(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-tabs': 1.1.21(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-toast': 1.2.23(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-toggle': 1.1.18(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-toggle-group': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-toolbar': 1.1.19(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-tooltip': 1.2.16(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-escape-keydown': 1.1.5(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.10)(react@19.2.1) - '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) - '@types/react': 19.2.10 - '@types/react-dom': 19.2.3(@types/react@19.2.10) + '@radix-ui/react-accessible-icon': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-accordion': 1.2.21(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-alert-dialog': 1.1.24(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-arrow': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-aspect-ratio': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-avatar': 1.2.7(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-checkbox': 1.3.12(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-collapsible': 1.1.21(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-collection': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-context-menu': 2.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-dialog': 1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-direction': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-dropdown-menu': 2.1.25(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-focus-scope': 1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-form': 0.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-hover-card': 1.1.24(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-label': 2.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-menu': 2.1.25(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-menubar': 1.1.25(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-navigation-menu': 1.3.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-one-time-password-field': 0.1.17(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-password-toggle-field': 0.1.12(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-popover': 1.2.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-popper': 1.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-portal': 1.1.18(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-presence': 1.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.11(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-progress': 1.1.17(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-radio-group': 1.4.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-roving-focus': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-scroll-area': 1.3.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-select': 2.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-separator': 1.1.16(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-slider': 1.5.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-slot': 1.4.0(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-switch': 1.3.8(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-tabs': 1.1.22(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-toast': 1.2.24(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-toggle': 1.1.19(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-toggle-group': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-toolbar': 1.1.20(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-tooltip': 1.3.0(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-escape-keydown': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.5(@types/react@19.2.18)(react@19.2.1) + '@radix-ui/react-visually-hidden': 1.2.12(@types/react-dom@19.2.7)(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1) + '@types/react': 19.2.18 + '@types/react-dom': 19.2.7(@types/react@19.2.18) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false @@ -6523,16 +5536,21 @@ packages: scheduler: 0.27.0 dev: false - /react-hook-form@7.71.1(react@19.2.1): - resolution: {integrity: sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==} + /react-hook-form@7.90.0(@types/react@19.2.18)(react@19.2.1): + resolution: {integrity: sha512-+NdG0FLzCPhFOp2NNxaUYLF9fyZhNjx9FTBBSYWF71/l6BBlmIc/j6xlp3DbaHWLJbIG7Zd8oznA19xJClU97w==} engines: {node: '>=18.0.0'} peerDependencies: + '@types/react': '*' react: ^16.8.0 || ^17 || ^18 || ^19 + peerDependenciesMeta: + '@types/react': + optional: true dependencies: + '@types/react': 19.2.18 react: 19.2.1 dev: false - /react-i18next@15.7.4(i18next@25.8.0)(react-dom@19.2.1)(react@19.2.1)(typescript@5.9.3): + /react-i18next@15.7.4(i18next@25.10.10)(react-dom@19.2.1)(react@19.2.1)(typescript@5.9.3): resolution: {integrity: sha512-nyU8iKNrI5uDJch0z9+Y5XEr34b0wkyYj3Rp+tfbahxtlswxSCjcUL9H0nqXo9IR3/t5Y5PKIA3fx3MfUyR9Xw==} peerDependencies: i18next: '>= 23.4.0' @@ -6548,9 +5566,9 @@ packages: typescript: optional: true dependencies: - '@babel/runtime': 7.28.6 - html-parse-stringify: 3.0.1 - i18next: 25.8.0(typescript@5.9.3) + '@babel/runtime': 7.29.10 + html-parse-stringify: 3.1.0 + i18next: 25.10.10(typescript@5.9.3) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) typescript: 5.9.3 @@ -6563,15 +5581,15 @@ packages: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} dev: false - /react-markdown@10.1.0(@types/react@19.2.10)(react@19.2.1): + /react-markdown@10.1.0(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} peerDependencies: '@types/react': '>=18' react: '>=18' dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 - '@types/react': 19.2.10 + '@types/react': 19.2.18 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 @@ -6596,7 +5614,7 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false - /react-remove-scroll-bar@2.3.8(@types/react@19.2.10)(react@19.2.1): + /react-remove-scroll-bar@2.3.8(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} peerDependencies: @@ -6606,13 +5624,13 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 react: 19.2.1 - react-style-singleton: 2.2.3(@types/react@19.2.10)(react@19.2.1) + react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.1) tslib: 2.8.1 dev: false - /react-remove-scroll@2.7.2(@types/react@19.2.10)(react@19.2.1): + /react-remove-scroll@2.7.2(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} engines: {node: '>=10'} peerDependencies: @@ -6622,17 +5640,17 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 react: 19.2.1 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.10)(react@19.2.1) - react-style-singleton: 2.2.3(@types/react@19.2.10)(react@19.2.1) + react-remove-scroll-bar: 2.3.8(@types/react@19.2.18)(react@19.2.1) + react-style-singleton: 2.2.3(@types/react@19.2.18)(react@19.2.1) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.10)(react@19.2.1) - use-sidecar: 1.1.3(@types/react@19.2.10)(react@19.2.1) + use-callback-ref: 1.3.3(@types/react@19.2.18)(react@19.2.1) + use-sidecar: 1.1.3(@types/react@19.2.18)(react@19.2.1) dev: false - /react-router-dom@7.17.0(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-fyU2yjGups/hE6Xz0I5ZYbVL8Gx29eCjgpHaRaTaVU+OOAdfRX05KsvyRm0GO8YQwOkhpU3MurW1jyMUJn+zSw==} + /react-router-dom@7.18.4(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -6640,11 +5658,11 @@ packages: dependencies: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) - react-router: 7.17.0(react-dom@19.2.1)(react@19.2.1) + react-router: 7.18.4(react-dom@19.2.1)(react@19.2.1) dev: false - /react-router@7.17.0(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ==} + /react-router@7.18.4(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -6665,14 +5683,14 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 dependencies: - fast-equals: 5.4.0 + fast-equals: 5.4.4 prop-types: 15.8.1 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) react-transition-group: 4.4.5(react-dom@19.2.1)(react@19.2.1) dev: false - /react-style-singleton@2.2.3(@types/react@19.2.10)(react@19.2.1): + /react-style-singleton@2.2.3(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} peerDependencies: @@ -6682,19 +5700,19 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 get-nonce: 1.0.1 react: 19.2.1 tslib: 2.8.1 dev: false - /react-syntax-highlighter@16.1.0(react@19.2.1): - resolution: {integrity: sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==} + /react-syntax-highlighter@16.1.2(react@19.2.1): + resolution: {integrity: sha512-4dM0+mYtfnhgwYWAvFx56fgyjIYk2yFopfiICFvMDhRfeSGRNd+ngFALdPD9q7llyGAmiE6pzOSOwI8Lf9ca1w==} engines: {node: '>= 16.20.2'} peerDependencies: react: '>= 0.14.0' dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.10 highlight.js: 10.7.3 highlightjs-vue: 1.0.0 lowlight: 1.20.0 @@ -6709,7 +5727,7 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' dependencies: - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.10 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -6731,6 +5749,7 @@ packages: /recharts@2.15.4(react-dom@19.2.1)(react@19.2.1): resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==} engines: {node: '>=14'} + deprecated: 1.x and 2.x branches are no longer active. Bump to Recharts v3 to receive latest features and bugfixes. See https://github.com/recharts/recharts/wiki/3.0-migration-guide peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -6751,11 +5770,11 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 @@ -6764,8 +5783,8 @@ packages: /refractor@5.0.0: resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==} dependencies: - '@types/hast': 3.0.4 - '@types/prismjs': 1.26.5 + '@types/hast': 3.0.5 + '@types/prismjs': 1.26.6 hastscript: 9.0.1 parse-entities: 4.0.2 dev: false @@ -6774,7 +5793,7 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 @@ -6785,8 +5804,8 @@ packages: /rehype-autolink-headings@7.1.0: resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==} dependencies: - '@types/hast': 3.0.4 - '@ungap/structured-clone': 1.3.0 + '@types/hast': 3.0.5 + '@ungap/structured-clone': 1.4.0 hast-util-heading-rank: 3.0.0 hast-util-is-element: 3.0.0 unified: 11.0.5 @@ -6796,7 +5815,7 @@ packages: /rehype-highlight@7.0.2: resolution: {integrity: sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 hast-util-to-text: 4.0.2 lowlight: 3.3.0 unist-util-visit: 5.1.0 @@ -6806,7 +5825,7 @@ packages: /rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 hast-util-raw: 9.1.0 vfile: 6.0.3 dev: false @@ -6814,14 +5833,14 @@ packages: /rehype-sanitize@6.0.0: resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 hast-util-sanitize: 5.0.2 dev: false /rehype-slug@6.0.0: resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 github-slugger: 2.0.0 hast-util-heading-rank: 3.0.0 hast-util-to-string: 3.0.1 @@ -6845,8 +5864,8 @@ packages: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 - micromark-util-types: 2.0.2 + mdast-util-from-markdown: 2.1.0 + micromark-util-types: 2.0.3 unified: 11.0.5 transitivePeerDependencies: - supports-color @@ -6855,7 +5874,7 @@ packages: /remark-rehype@11.1.2: resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 mdast-util-to-hast: 13.2.1 unified: 11.0.5 @@ -6866,7 +5885,7 @@ packages: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} dependencies: '@types/mdast': 4.0.4 - mdast-util-to-markdown: 2.1.2 + mdast-util-to-markdown: 2.2.0 unified: 11.0.5 dev: false @@ -6884,14 +5903,14 @@ packages: engines: {node: '>=4'} dev: true - /resolve@2.0.0-next.6: - resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} + /resolve@2.0.0-next.7: + resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} engines: {node: '>= 0.4'} hasBin: true dependencies: es-errors: 1.3.0 - is-core-module: 2.16.1 - node-exports-info: 1.6.0 + is-core-module: 2.17.0 + node-exports-info: 1.6.2 object-keys: 1.1.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -6909,36 +5928,36 @@ packages: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} dev: true - /rolldown@1.0.3: - resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + /rolldown@1.2.13: + resolution: {integrity: sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true dependencies: - '@oxc-project/types': 0.133.0 + '@oxc-project/types': 0.153.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.3 - '@rolldown/binding-darwin-arm64': 1.0.3 - '@rolldown/binding-darwin-x64': 1.0.3 - '@rolldown/binding-freebsd-x64': 1.0.3 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 - '@rolldown/binding-linux-arm64-gnu': 1.0.3 - '@rolldown/binding-linux-arm64-musl': 1.0.3 - '@rolldown/binding-linux-ppc64-gnu': 1.0.3 - '@rolldown/binding-linux-s390x-gnu': 1.0.3 - '@rolldown/binding-linux-x64-gnu': 1.0.3 - '@rolldown/binding-linux-x64-musl': 1.0.3 - '@rolldown/binding-openharmony-arm64': 1.0.3 - '@rolldown/binding-wasm32-wasi': 1.0.3 - '@rolldown/binding-win32-arm64-msvc': 1.0.3 - '@rolldown/binding-win32-x64-msvc': 1.0.3 + '@rolldown/binding-android-arm-eabi': 1.2.13 + '@rolldown/binding-android-arm64': 1.2.13 + '@rolldown/binding-darwin-arm64': 1.2.13 + '@rolldown/binding-darwin-x64': 1.2.13 + '@rolldown/binding-freebsd-x64': 1.2.13 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.13 + '@rolldown/binding-linux-arm64-gnu': 1.2.13 + '@rolldown/binding-linux-arm64-musl': 1.2.13 + '@rolldown/binding-linux-ppc64-gnu': 1.2.13 + '@rolldown/binding-linux-s390x-gnu': 1.2.13 + '@rolldown/binding-linux-x64-gnu': 1.2.13 + '@rolldown/binding-linux-x64-musl': 1.2.13 + '@rolldown/binding-openharmony-arm64': 1.2.13 + '@rolldown/binding-win32-arm64-msvc': 1.2.13 + '@rolldown/binding-win32-x64-msvc': 1.2.13 dev: false - /safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + /safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 @@ -6971,8 +5990,8 @@ packages: hasBin: true dev: true - /semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + /semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} hasBin: true dev: true @@ -7013,7 +6032,7 @@ packages: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 dev: true /shebang-command@2.0.0: @@ -7028,8 +6047,8 @@ packages: engines: {node: '>=8'} dev: true - /side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + /side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 @@ -7057,13 +6076,13 @@ packages: side-channel-map: 1.0.1 dev: true - /side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + /side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - side-channel-list: 1.0.0 + side-channel-list: 1.0.1 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 dev: true @@ -7089,18 +6108,23 @@ packages: is-fullwidth-code-point: 5.1.0 dev: true - /sonner@2.0.7(react-dom@19.2.1)(react@19.2.1): - resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + /sonner@2.0.8(@types/react@19.2.18)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg==} peerDependencies: + '@types/react': ^18.0.0 || ^19.0.0 react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true dependencies: + '@types/react': 19.2.18 react: 19.2.1 react-dom: 19.2.1(react@19.2.1) dev: false - /source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + /source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} engines: {node: '>=0.10.0'} dev: false @@ -7135,66 +6159,67 @@ packages: engines: {node: '>=18'} dependencies: emoji-regex: 10.6.0 - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 dev: true - /string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + /string.prototype.matchall@4.1.0: + resolution: {integrity: sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 gopd: 1.2.0 has-symbols: 1.1.0 internal-slot: 1.1.0 regexp.prototype.flags: 1.5.4 set-function-name: 2.0.2 - side-channel: 1.1.0 + side-channel: 1.1.1 dev: true /string.prototype.repeat@1.0.0: resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} dependencies: define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 dev: true - /string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + /string.prototype.trim@1.2.11: + resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 has-property-descriptors: 1.0.2 + safe-regex-test: 1.1.0 dev: true - /string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + /string.prototype.trimend@1.0.10: + resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 dev: true /string.prototype.trimstart@1.0.8: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 dev: true /stringify-entities@4.0.4: @@ -7211,11 +6236,11 @@ packages: ansi-regex: 5.0.1 dev: false - /strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + /strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} dependencies: - ansi-regex: 6.2.2 + ansi-regex: 6.4.0 dev: true /strip-final-newline@3.0.0: @@ -7252,23 +6277,23 @@ packages: engines: {node: '>= 0.4'} dev: true - /synckit@0.11.12: - resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} + /synckit@0.11.13: + resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==} engines: {node: ^14.18.0 || >=16.0.0} dependencies: - '@pkgr/core': 0.2.9 + '@pkgr/core': 0.3.6 dev: true - /tailwind-merge@3.4.0: - resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + /tailwind-merge@3.7.0: + resolution: {integrity: sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==} dev: false - /tailwindcss@4.1.18: - resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + /tailwindcss@4.3.3: + resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} dev: false - /tapable@2.3.0: - resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + /tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} dev: false @@ -7276,21 +6301,12 @@ packages: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} dev: false - /tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - dev: true - /tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - dev: false + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} @@ -7307,8 +6323,8 @@ packages: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} dev: false - /ts-api-utils@2.4.0(typescript@5.9.3): - resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + /ts-api-utils@2.5.0(typescript@5.9.3): + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -7344,31 +6360,30 @@ packages: resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 dev: true - /typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + /typed-array-byte-offset@1.0.5: + resolution: {integrity: sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==} engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 - has-proto: 1.2.0 is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 dev: true - /typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + /typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 @@ -7376,18 +6391,18 @@ packages: reflect.getprototypeof: 1.0.10 dev: true - /typescript-eslint@8.54.0(eslint@9.39.2)(typescript@5.9.3): - resolution: {integrity: sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==} + /typescript-eslint@8.71.1(eslint@9.39.5)(typescript@5.9.3): + resolution: {integrity: sha512-oiNVPC3/NbV1FpaHU07l8O6ynqG9v4PAiW2WvTrAmZdR0f2HI/gE668ElMLCK7Xbsu5k2fK5C4fXNiq05j7/nQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' dependencies: - '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0)(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/parser': 8.54.0(eslint@9.39.2)(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.54.0(eslint@9.39.2)(typescript@5.9.3) - eslint: 9.39.2 + '@typescript-eslint/eslint-plugin': 8.71.1(@typescript-eslint/parser@8.71.1)(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/parser': 8.71.1(eslint@9.39.5)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.71.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.71.1(eslint@9.39.5)(typescript@5.9.3) + eslint: 9.39.5 typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -7469,7 +6484,7 @@ packages: punycode: 2.3.1 dev: true - /use-callback-ref@1.3.3(@types/react@19.2.10)(react@19.2.1): + /use-callback-ref@1.3.3(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} peerDependencies: @@ -7479,12 +6494,12 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 react: 19.2.1 tslib: 2.8.1 dev: false - /use-sidecar@1.1.3(@types/react@19.2.10)(react@19.2.1): + /use-sidecar@1.1.3(@types/react@19.2.18)(react@19.2.1): resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} engines: {node: '>=10'} peerDependencies: @@ -7494,20 +6509,12 @@ packages: '@types/react': optional: true dependencies: - '@types/react': 19.2.10 + '@types/react': 19.2.18 detect-node-es: 1.1.0 react: 19.2.1 tslib: 2.8.1 dev: false - /use-sync-external-store@1.6.0(react@19.2.1): - resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - dependencies: - react: 19.2.1 - dev: false - /uuidjs@5.1.0: resolution: {integrity: sha512-HAQPtUkr7t5Ud3uCwRcqtBRNagu/2aerrrBQE6PzgSluGijvFF75UaOq22Xw545GGviRjSLhc4c8CaSMI5h4Ng==} hasBin: true @@ -7541,7 +6548,7 @@ packages: '@types/d3-ease': 3.0.2 '@types/d3-interpolate': 3.0.4 '@types/d3-scale': 4.0.9 - '@types/d3-shape': 3.1.8 + '@types/d3-shape': 3.2.0 '@types/d3-time': 3.0.4 '@types/d3-timer': 3.0.2 d3-array: 3.2.4 @@ -7553,13 +6560,13 @@ packages: d3-timer: 3.0.1 dev: false - /vite@8.0.16(@types/node@20.19.30): - resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + /vite@8.3.4(@types/node@20.19.43): + resolution: {integrity: sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.18 + '@vitejs/devtools': ^0.7.1 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -7596,11 +6603,11 @@ packages: yaml: optional: true dependencies: - '@types/node': 20.19.30 - lightningcss: 1.32.0 - picomatch: 4.0.4 - postcss: 8.5.15 - rolldown: 1.0.3 + '@types/node': 20.19.43 + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.29 + rolldown: 1.2.13 tinyglobby: 0.2.17 optionalDependencies: fsevents: 2.3.3 @@ -7631,7 +6638,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bound: 1.0.4 - function.prototype.name: 1.1.8 + function.prototype.name: 1.2.0 has-tostringtag: 1.0.2 is-async-function: 2.1.1 is-date-object: 1.1.0 @@ -7642,7 +6649,7 @@ packages: isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.20 + which-typed-array: 1.1.24 dev: true /which-collection@1.0.2: @@ -7659,12 +6666,12 @@ packages: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} dev: false - /which-typed-array@1.1.20: - resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + /which-typed-array@1.1.24: + resolution: {integrity: sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==} engines: {node: '>= 0.4'} dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 @@ -7700,15 +6707,15 @@ packages: dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 dev: true /y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} dev: false - /yaml@2.8.2: - resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + /yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} engines: {node: '>= 14.6'} hasBin: true dev: true diff --git a/web/src/app/global.css b/web/src/app/global.css index a1be44406..7458cdc84 100644 --- a/web/src/app/global.css +++ b/web/src/app/global.css @@ -195,3 +195,28 @@ body:has([data-tour-modal='true']) [data-tour-modal='false'] { display: none; } + +/* Reserve native scrollbar space before content overflows. Extending the + utilities also covers responsive variants, nested panels and popup menus. + Horizontal-only scrollers and custom overlay scroll areas stay unchanged. */ +@utility overflow-auto { + scrollbar-gutter: stable; +} + +@utility overflow-y-auto { + scrollbar-gutter: stable; +} + +@utility overflow-scroll { + scrollbar-gutter: stable; +} + +@utility overflow-y-scroll { + scrollbar-gutter: stable; +} + +@layer base { + textarea { + scrollbar-gutter: stable; + } +} diff --git a/web/src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx b/web/src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx index 14dda966f..eed014858 100644 --- a/web/src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx +++ b/web/src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx @@ -225,11 +225,21 @@ export default function WorkspaceSettingsPanel({ return ( <> - -
-

- {workspaceInfo?.workspace.name ?? t('workspace.title')} -

+ +
+
+

+ {workspaceInfo?.workspace.name ?? t('workspace.title')} +

+ {workspaceInfo && ( + + {t(`workspace.roles.${workspaceInfo.membership.role}`)} + + )} +

{t( isCloudProjection @@ -238,18 +248,27 @@ export default function WorkspaceSettingsPanel({ )}

- {workspaceInfo && ( - - {t(`workspace.roles.${workspaceInfo.membership.role}`)} - - )} {isCloudProjection && workspaceInfo && ( - +
+
+

+ {t('workspace.currentPlan')} +

+

+ {workspaceInfo.plan_name || t('workspace.planUnavailable')} +

+
+ +
)}
diff --git a/web/src/app/home/components/workspace-settings/WorkspaceSwitcher.tsx b/web/src/app/home/components/workspace-settings/WorkspaceSwitcher.tsx index c8fc53924..9614650fd 100644 --- a/web/src/app/home/components/workspace-settings/WorkspaceSwitcher.tsx +++ b/web/src/app/home/components/workspace-settings/WorkspaceSwitcher.tsx @@ -1,4 +1,4 @@ -import { Building2, Check, Settings } from 'lucide-react'; +import { Building2, Settings } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { @@ -56,7 +56,10 @@ export default function WorkspaceSwitcher({ return ( { if (!selected) void switchWorkspaceAndReload(entry.workspace.uuid); @@ -71,7 +74,6 @@ export default function WorkspaceSwitcher({ {entry.plan_name || t('workspace.planUnavailable')} )} - {selected && } {selected && ( + + + {children} + + + ); +} + +function Benefits({ + plan, + spaceURL, +}: { + plan: PublicCloudPlan; + spaceURL: string; +}) { + const { t, i18n } = useTranslation(); + const text = (values: Parameters[0]) => + localizedPlanText(values, i18n.language); + const config = plan.display_config; + const platforms = supportedPlanPlatforms(plan.disabled_adapters); + const quotaLabel = text(config?.quota_summary) || t('cloudPlans.quotas'); + const legacyFeatures: Record = { + 'Multi-user workspace': 'multiUserWorkspace', + 'Remote E2B sandbox configuration': 'remoteE2BSandbox', + 'Community support': 'communitySupport', + 'Everything in Free': 'everythingInFree', + 'One always-on global sandbox': 'alwaysOnSandbox', + 'Higher workspace limits': 'higherWorkspaceLimits', + }; + const items = + config?.items ?? + (plan.features ?? []).map((feature) => ({ + label: { + en_US: legacyFeatures[feature] + ? t(`cloudPlans.features.${legacyFeatures[feature]}`) + : feature, + }, + icon: 'ri-check-line', + tooltip: {}, + })); + const rows: Record = { + quota_summary: ( + <> + + {quotaLabel} + + {text(config?.tooltips?.quota_summary) && ( +

{text(config?.tooltips?.quota_summary)}

+ )} +
    + {Object.entries(plan.limits).map(([key, limit]) => { + const resource = key.replace(/\.max$/, ''); + const label = + text(config?.quotas?.[key]?.label) || + t(`cloudPlans.limits.${resource}`, { + defaultValue: resource.replace(/_/g, ' '), + }); + return ( +
  • +
    + {label} + + {limit < 0 + ? t('cloudPlans.unlimited') + : limit.toLocaleString()} + +
    + {text(config?.quotas?.[key]?.tooltip) && ( +

    + {text(config?.quotas?.[key]?.tooltip)} +

    + )} +
  • + ); + })} +
+
+ + ), + retention: ( + <> + + + {t('cloudPlans.retention', { + count: plan.chat_history_retention_days, + })} + + + {text(config?.tooltips?.retention) || t('cloudPlans.retentionHelp')} + + + ), + monthly_credits: ( + <> + + + {t('cloudPlans.credits', { count: plan.monthly_bonus_credits })} + + + {text(config?.tooltips?.monthly_credits) || + t('cloudPlans.creditsHelp')} + + + ), + supported_platforms: ( + <> + + {t('cloudPlans.platforms')} + + { + if (event.pointerType === 'touch') event.preventDefault(); + }} + onClick={(event) => { + event.preventDefault(); + event.stopPropagation(); + }} + > + + + + {text(config?.tooltips?.supported_platforms) && ( +

+ {text(config?.tooltips?.supported_platforms)} +

+ )} +
+ {platforms.map((platform) => ( + + + {t( + `cloudPlans.platformNames.${platform.name.replace('LINE', 'line').replace('openclaw-weixin', 'openclaw_weixin')}`, + { defaultValue: platform.label }, + )} + + ))} +
+ {platforms.length === 0 &&

{t('common.none')}

} +
+
+ + ), + }; + items.forEach((item, index) => { + const label = text(item.label); + rows[`item:${index}`] = ( + <> + + {label} + {text(item.tooltip) && {text(item.tooltip)}} + + ); + }); + const order = [ + ...new Set([ + ...(config?.order ?? []), + 'quota_summary', + 'supported_platforms', + 'retention', + 'monthly_credits', + ...items.map((_, index) => `item:${index}`), + ]), + ].filter((key) => key in rows); + return ( +
    + {order.map((key) => ( +
  • + {rows[key]} +
  • + ))} +
+ ); +} + +export default function CloudPlansDialog({ + open, + onOpenChange, + workspace, + spaceURL, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + workspace: CurrentWorkspace; + spaceURL: string; +}) { + const { t, i18n } = useTranslation(); + const [plans, setPlans] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(false); + const [attempt, setAttempt] = useState(0); + const [cycle, setCycle] = useState('annual'); + const currency = cloudPlanCurrencyForLanguage(i18n.language); + const canManage = ['owner', 'admin'].includes(workspace.membership.role); + useEffect(() => { + if (!open) return; + const controller = new AbortController(); + let active = true; + const timeout = window.setTimeout(() => controller.abort(), 15000); + setLoading(true); + setError(false); + fetchPublicCloudPlans(spaceURL, controller.signal) + .then((value) => { + if (active) setPlans(value); + }) + .catch(() => { + if (active) setError(true); + }) + .finally(() => { + window.clearTimeout(timeout); + if (active) setLoading(false); + }); + return () => { + active = false; + controller.abort(); + window.clearTimeout(timeout); + }; + }, [open, spaceURL, attempt]); + const formatPrice = (price: number) => + new Intl.NumberFormat(i18n.language.replace('_', '-'), { + style: 'currency', + currency, + maximumFractionDigits: currency === 'JPY' ? 0 : 2, + }).format(price); + return ( + + + + {t('cloudPlans.title')} + + {t('cloudPlans.description', { + workspace: workspace.workspace.name, + })} + + +
+
+ {(['monthly', 'annual'] as const).map((value) => ( + + ))} +
+
+
+ {loading ? ( +
+ + {t('cloudPlans.loading')} +
+ ) : error ? ( +
+

{t('cloudPlans.error')}

+ +
+ ) : !plans.length ? ( +

+ {t('cloudPlans.empty')} +

+ ) : ( + +
2 && 'lg:grid-cols-3', + )} + > + {plans.map((plan) => { + const current = workspace.plan_name === plan.name; + const downgradeBlocked = + Boolean( + workspace.plan_name && workspace.plan_name !== 'free', + ) && plan.name === 'free'; + const price = cloudPlanPrice(plan, cycle, currency); + const name = + localizedPlanText(plan.display_name, i18n.language) || + plan.name.toUpperCase(); + return ( +
+
+

+ + {name} +

+ {current ? ( + + {t('cloudPlans.current')} + + ) : plan.highlight ? ( + {t('cloudPlans.recommended')} + ) : null} +
+

+ {localizedPlanText(plan.description, i18n.language)} +

+

+ {price === 0 + ? t('cloudPlans.free') + : formatPrice(price)} + {price > 0 && ( + + / {t(`cloudPlans.${cycle}`)} + + )} +

+

+ {cycle === 'annual' && price > 0 + ? t('cloudPlans.annualNote', { + price: formatPrice(price / 12), + }) + : ''} +

+
+ +
+ {downgradeBlocked ? null : !canManage ? ( + + ) : ( + + )} +
+ ); + })} +
+
+ )} +
+

+ {t('cloudPlans.handoff')} +

+
+
+ ); +} diff --git a/web/src/app/home/components/workspace-settings/WorkspacePlanBadge.tsx b/web/src/app/home/components/workspace-settings/WorkspacePlanBadge.tsx new file mode 100644 index 000000000..b5eb7471b --- /dev/null +++ b/web/src/app/home/components/workspace-settings/WorkspacePlanBadge.tsx @@ -0,0 +1,28 @@ +import { useTranslation } from 'react-i18next'; + +import { Badge } from '@/components/ui/badge'; +import { cn } from '@/lib/utils'; + +export default function WorkspacePlanBadge({ + planName, +}: { + planName?: string | null; +}) { + const { t } = useTranslation(); + const isPaidPlan = + Boolean(planName?.trim()) && planName?.trim().toLowerCase() !== 'free'; + + return ( + + {planName || t('workspace.planUnavailable')} + + ); +} diff --git a/web/src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx b/web/src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx index eed014858..9e614c5d0 100644 --- a/web/src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx +++ b/web/src/app/home/components/workspace-settings/WorkspaceSettingsPanel.tsx @@ -1,7 +1,14 @@ -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { + lazy, + Suspense, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; import { Copy, - ExternalLink, + ArrowRight, Loader2, Trash2, UserPlus, @@ -39,6 +46,9 @@ import { PanelBody, PanelToolbar, } from '@/app/home/components/settings-dialog/panel-layout'; +import WorkspacePlanBadge from './WorkspacePlanBadge'; + +const CloudPlansDialog = lazy(() => import('./CloudPlansDialog')); interface WorkspaceSettingsPanelProps { active: boolean; @@ -66,6 +76,7 @@ export default function WorkspaceSettingsPanel({ active, }: WorkspaceSettingsPanelProps) { const { t } = useTranslation(); + const [plansOpen, setPlansOpen] = useState(false); const [workspaceInfo, setWorkspaceInfo] = useState( null, ); @@ -93,9 +104,6 @@ export default function WorkspaceSettingsPanel({ const canInvite = permissions.has('member.invite'); const canUpdateMembers = permissions.has('member.update_role'); const canRemoveMembers = permissions.has('member.remove'); - const cloudPortalURL = workspaceInfo - ? `${systemInfo.cloud_service_url.replace(/\/$/, '')}/cloud?workspace=${encodeURIComponent(workspaceInfo.workspace.uuid)}&step=plan` - : ''; const loadWorkspace = useCallback(async () => { setLoading(true); @@ -254,19 +262,15 @@ export default function WorkspaceSettingsPanel({

{t('workspace.currentPlan')}

-

- {workspaceInfo.plan_name || t('workspace.planUnavailable')} -

+
-
)} @@ -490,6 +494,17 @@ export default function WorkspaceSettingsPanel({
)}
+ {isCloudProjection && workspaceInfo && plansOpen && ( + + + + )} ); } diff --git a/web/src/app/home/components/workspace-settings/WorkspaceSwitcher.tsx b/web/src/app/home/components/workspace-settings/WorkspaceSwitcher.tsx index 9614650fd..5c5ca2b81 100644 --- a/web/src/app/home/components/workspace-settings/WorkspaceSwitcher.tsx +++ b/web/src/app/home/components/workspace-settings/WorkspaceSwitcher.tsx @@ -15,6 +15,7 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { cn } from '@/lib/utils'; +import WorkspacePlanBadge from './WorkspacePlanBadge'; export const OPEN_WORKSPACE_SETTINGS_EVENT = 'langbot:open-workspace-settings'; @@ -70,9 +71,7 @@ export default function WorkspaceSwitcher({ {entry.workspace.name} {entry.workspace.source === 'cloud_projection' && ( - - {entry.plan_name || t('workspace.planUnavailable')} - + )} {selected && ( - - - - ); -} diff --git a/web/src/components/ui/sidebar.tsx b/web/src/components/ui/sidebar.tsx index 3ef69ff30..d142aaf03 100644 --- a/web/src/components/ui/sidebar.tsx +++ b/web/src/components/ui/sidebar.tsx @@ -203,9 +203,7 @@ function SidebarProvider({ { '--sidebar-width': `${sidebarWidth}px`, '--sidebar-width-icon': SIDEBAR_WIDTH_ICON, - // Global notices (e.g. the beta banner) publish their height so - // the shell starts below them instead of underneath. - height: 'calc(100svh - var(--beta-banner-height, 0px))', + height: '100svh', ...style, } as React.CSSProperties } @@ -302,8 +300,8 @@ function Sidebar({
+ {!loading && ( + <> + {!workspace.current_plan && ( +

+ {t('planRevision.unknown')} +

+ )} + + )} {loading ? (
- ) : !plans.length ? ( + ) : !entries.length ? (

{t('cloudPlans.empty')}

@@ -371,120 +481,197 @@ export default function CloudPlansDialog({
2 && 'lg:grid-cols-3', + 'flex gap-4 overflow-x-auto p-1 [&>*]:w-72 [&>*]:shrink-0 [&>*]:grow', )} > - {plans.map((plan) => { - const current = workspace.plan_name === plan.name; - const downgradeBlocked = - Boolean( - workspace.plan_name && workspace.plan_name !== 'free', - ) && plan.name === 'free'; - const price = cloudPlanPrice(plan, cycle, currency); - const name = - localizedPlanText(plan.display_name, i18n.language) || - plan.name.toUpperCase(); - return ( -
-
-

- - {name} -

- {current ? ( - - {t('cloudPlans.current')} - - ) : plan.highlight ? ( - {t('cloudPlans.recommended')} - ) : null} -
-

- {localizedPlanText(plan.description, i18n.language)} -

-

- {price === 0 - ? t('cloudPlans.free') - : formatPrice(price)} - {price > 0 && ( - - / {t(`cloudPlans.${cycle}`)} - + {entries.map((plan) => { + const renderCard = ( + plan: PublicCloudPlan, + ownedCard = false, + ) => { + const samePlan = workspace.plan_name === plan.name; + const current = + ownedCard || (samePlan && !distinct && !!owned); + // Both stacked revisions share one card style; only the + // badge text tells them apart. + const stacked = distinct && plan.name === owned?.name; + const downgradeBlocked = + Boolean( + workspace.plan_name && workspace.plan_name !== 'free', + ) && plan.name === 'free'; + const price = cloudPlanPrice(plan, cycle, currency); + const name = + localizedPlanText(plan.display_name, i18n.language) || + plan.name.toUpperCase(); + return ( +

-

- {cycle === 'annual' && price > 0 - ? t('cloudPlans.annualNote', { - price: formatPrice(price / 12), - }) - : ''} -

-
- -
- {plan.subscription_disabled && ( -

- {t('cloudPlans.subscriptionUnavailableHint')} -

- )} - {plan.subscription_disabled && !current ? ( - - ) : downgradeBlocked ? null : !canManage ? ( - - ) : ( - - )} -
+ {ownedCard && subscribedOn && ( + + {subscribedOn} + + )} +

+ {ownedCard && !ownedCatalog ? ( +
+ ) : ( + <> +

+ {price === 0 + ? t('cloudPlans.free') + : formatPrice(price)} + {price > 0 && ( + + / {t(`cloudPlans.${cycle}`)} + + )} +

+

+ {cycle === 'annual' && price > 0 + ? t('cloudPlans.annualNote', { + price: formatPrice(price / 12), + }) + : ''} +

+ + )} +
+ +
+ {plan.subscription_disabled && ( +

+ {t('cloudPlans.subscriptionUnavailableHint')} +

+ )} + {plan.subscription_disabled && !current ? ( + + ) : downgradeBlocked ? null : !canManage ? ( + + ) : distinct && + !ownedCard && + plan.name === owned?.name ? ( + + + + + + {t('planRevision.cancelToSubscribe')} + + + ) : ( + + )} + {ownedCard && !latest && ( +

+ {t('planRevision.unavailable')} +

+ )} +
+ ); + }; + if (plan === 'owned') + return ownedPlan && !latest + ? renderCard(ownedPlan, true) + : null; + return ownedPlan && latest === plan ? ( + + ) : ( + renderCard(plan) ); })}
diff --git a/web/src/app/home/components/workspace-settings/PlanRevisionStack.tsx b/web/src/app/home/components/workspace-settings/PlanRevisionStack.tsx new file mode 100644 index 000000000..22e5130a2 --- /dev/null +++ b/web/src/app/home/components/workspace-settings/PlanRevisionStack.tsx @@ -0,0 +1,84 @@ +import { useState, type ReactNode } from 'react'; +import { ChevronLeft } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { cn } from '@/lib/utils'; + +type Layer = 'current' | 'latest'; + +/** + * Stacks the purchased revision in front of the plan's latest revision. The + * toggle only changes which card is shown; it never changes the subscription. + * Layers stay opaque; the back card is dimmed with an overlay instead of + * opacity so the two cards never show through each other while swapping. + */ +export default function PlanRevisionStack({ + current, + latest, +}: { + current: ReactNode; + latest: ReactNode; +}) { + const { t } = useTranslation(); + const [front, setFront] = useState('current'); + const back: Layer = front === 'current' ? 'latest' : 'current'; + const label = t( + back === 'latest' ? 'planRevision.showLatest' : 'planRevision.showCurrent', + ); + const layer = (key: Layer, node: ReactNode) => { + const isFront = front === key; + return ( +
article]:flex-1', + isFront + ? 'z-10 after:opacity-0' + : 'pointer-events-none z-0 translate-x-8 scale-[0.95] after:opacity-50 [&>article]:shadow-none', + )} + > + {node} +
+ ); + }; + return ( +
+ {layer('current', current)} + {layer('latest', latest)} + +
+ ); +} diff --git a/web/src/app/home/components/workspace-settings/cloud-plans.ts b/web/src/app/home/components/workspace-settings/cloud-plans.ts index f70d5dd2b..1ad7a392a 100644 --- a/web/src/app/home/components/workspace-settings/cloud-plans.ts +++ b/web/src/app/home/components/workspace-settings/cloud-plans.ts @@ -2,6 +2,7 @@ export type CloudPlanBillingCycle = 'monthly' | 'annual'; export type CloudPlanCurrency = 'CNY' | 'USD' | 'JPY'; export type PlanLocalizedText = Partial>; export interface PublicCloudPlan { + entitlement_features?: Record; uuid: string; name: string; revision: number; @@ -158,3 +159,25 @@ export async function fetchPublicCloudPlans( } return plans.sort((a, b) => a.sort_order - b.sort_order); } + +// Catalog metadata (copy, prices, display items) of one historical revision. +export async function fetchCloudPlanRevision( + base: string, + name: string, + revision: number, + signal: AbortSignal, +): Promise { + const response = await fetch( + spaceURL( + base, + `/api/v1/cloud/v2/plans/${encodeURIComponent(name)}/revisions/${revision}`, + ), + { credentials: 'omit', referrerPolicy: 'no-referrer', signal }, + ); + if (!response.ok) throw new Error('Could not load plan revision'); + const result = await response.json(); + const plan = result?.data as PublicCloudPlan | undefined; + if (result?.code !== 0 || !plan?.name || plan.name !== name) + throw new Error('Invalid plan revision'); + return plan; +} diff --git a/web/src/app/home/components/workspace-settings/plan-revisions.ts b/web/src/app/home/components/workspace-settings/plan-revisions.ts new file mode 100644 index 000000000..0c5cf31ec --- /dev/null +++ b/web/src/app/home/components/workspace-settings/plan-revisions.ts @@ -0,0 +1,48 @@ +/** Only trusted subscription data may populate the current side of a comparison. */ +export interface PlanRevisionBenefits { + name: string; + revision: number; + limits: Record; + entitlement_features?: Record; + disabled_adapters?: string[]; + chat_history_retention_days?: number; + monthly_bonus_credits?: number; +} + +export function benefitValues(plan: PlanRevisionBenefits) { + const values: Record = {}; + for (const [key, value] of Object.entries(plan.limits)) + values[`limit:${key}`] = value; + values['limit:chat_history.retention_days'] = + plan.limits['chat_history.retention_days'] ?? + plan.chat_history_retention_days ?? + 30; + values.monthly_credits = plan.monthly_bonus_credits ?? 0; + for (const [key, value] of Object.entries(plan.entitlement_features ?? {})) + values[`feature:${key}`] = value; + for (const adapter of plan.disabled_adapters ?? []) + values[`feature:platform.${adapter}`] = false; + return values; +} + +export function revisionRows( + current: PlanRevisionBenefits, + latest?: PlanRevisionBenefits, +) { + const left = benefitValues(current); + const right = latest ? benefitValues(latest) : {}; + return [...new Set([...Object.keys(left), ...Object.keys(right)])] + .sort() + .map((key) => { + // Platform adapters are allowed unless explicitly disabled. Other feature + // switches must be explicitly granted, matching Core's admission semantics. + const fallback = key.startsWith('feature:platform.') + ? true + : key.startsWith('feature:') + ? false + : undefined; + const before = left[key] ?? fallback; + const after = right[key] ?? fallback; + return { key, before, after, changed: !!latest && before !== after }; + }); +} diff --git a/web/src/app/infra/entities/workspace.ts b/web/src/app/infra/entities/workspace.ts index f08d7ab75..f5b720558 100644 --- a/web/src/app/infra/entities/workspace.ts +++ b/web/src/app/infra/entities/workspace.ts @@ -24,6 +24,14 @@ export interface WorkspaceMembership { } export interface CurrentWorkspace { + current_plan?: { + subscription_started_at?: number | null; + name: string; + revision: number; + limits: Record; + entitlement_features: Record; + monthly_bonus_credits: number; + } | null; workspace: Workspace; membership: WorkspaceMembership; permissions: string[]; diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index b5d6733d5..092582e22 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -1,6 +1,51 @@ import slackSetup from './slack-setup/en-US'; import pipelineMigration from './pipeline-migration/en-US'; const enUS = { + planRevision: { + subscribedOn: 'Subscribed on {{date}}', + latestQuota: 'Latest quotas for this plan', + showLatest: 'Show latest quotas for this plan', + showCurrent: 'Show your current subscription', + latestTab: 'Latest', + currentTab: 'Current', + cancelToSubscribe: + 'Cancel your subscription to subscribe to the latest quota next period', + + current: 'Current subscription: {{plan}} · revision {{revision}}', + owned: 'Purchased revision {{revision}}', + latest: 'Latest revision {{revision}}', + changed: + 'Your purchased benefits and the latest version differ. Highlighted rows show the changes.', + same: 'Benefits match latest revision {{revision}}. Your purchased revision remains unchanged.', + unavailable: + 'The latest plan is unavailable. Your purchased benefits remain in effect.', + benefit: 'Benefit', + change: 'Changed', + enabled: 'Enabled', + disabled: 'Disabled', + monthlyCredits: 'Monthly bonus credits', + retention: 'Chat history retention (days)', + unknown: + 'Current subscription details are unavailable. Refresh or manage your subscription in Space; latest catalog benefits are not your purchased benefits.', + handoff: + 'Manage your subscription in Space. Cancel automatic renewal before purchasing a new version. Prepaid subscriptions end automatically unless renewed before expiry.', + cancelFirst: + 'Cancel automatic renewal in subscription management first. Cancellation preserves paid benefits until the end of the period, without a refund. After confirmation, you can purchase the latest version, effective when payment succeeds.', + canceled: + 'Renewal is canceled. Paid benefits remain until the end of the period, without a refund. A new purchase applies its benefits when payment succeeds.', + prepaid: + 'This prepaid subscription ends automatically. You can renew your current plan before expiry; after expiry, subscriptions use the latest quotas for each plan.', + renew: 'Renew current subscription', + buying: 'Purchasing revision {{revision}}.', + fields: { + bots_max: 'Bots', + members_max: 'Members', + pipelines_max: 'Processors', + plugins_max: 'Extensions', + knowledge_bases_max: 'Knowledge bases', + managed_sandbox_sessions_max: 'Sandbox sessions', + }, + }, cloudPlans: { subscriptionUnavailable: 'Not available to subscribe', subscriptionUnavailableHint: diff --git a/web/src/i18n/locales/es-ES.ts b/web/src/i18n/locales/es-ES.ts index c7475dada..f791c2ed3 100644 --- a/web/src/i18n/locales/es-ES.ts +++ b/web/src/i18n/locales/es-ES.ts @@ -1,5 +1,50 @@ import pipelineMigration from './pipeline-migration/es-ES'; const esES = { + planRevision: { + subscribedOn: 'Fecha de suscripción: {{date}}', + latestQuota: 'Cuotas más recientes de este plan', + showLatest: 'Ver las cuotas más recientes de este plan', + showCurrent: 'Ver tu suscripción actual', + latestTab: 'Más reciente', + currentTab: 'Actual', + cancelToSubscribe: + 'Cancela tu suscripción para acceder a las últimas cuotas el próximo período', + + current: 'Current subscription: {{plan}} · revision {{revision}}', + owned: 'Purchased revision {{revision}}', + latest: 'Latest revision {{revision}}', + changed: + 'Your purchased benefits and the latest version differ. Highlighted rows show the changes.', + same: 'Benefits match latest revision {{revision}}. Your purchased revision remains unchanged.', + unavailable: + 'The latest plan is unavailable. Your purchased benefits remain in effect.', + benefit: 'Benefit', + change: 'Changed', + enabled: 'Enabled', + disabled: 'Disabled', + monthlyCredits: 'Monthly bonus credits', + retention: 'Chat history retention (days)', + unknown: + 'Current subscription details are unavailable. Refresh or manage your subscription in Space; latest catalog benefits are not your purchased benefits.', + handoff: + 'Manage your subscription in Space. Cancel automatic renewal before purchasing a new version. Prepaid subscriptions end automatically unless renewed before expiry.', + cancelFirst: + 'Cancel automatic renewal in subscription management first. Cancellation preserves paid benefits until the end of the period, without a refund. After confirmation, you can purchase the latest version, effective when payment succeeds.', + canceled: + 'Renewal is canceled. Paid benefits remain until the end of the period, without a refund. A new purchase applies its benefits when payment succeeds.', + prepaid: + 'This prepaid subscription ends automatically. You can renew your current plan before expiry; after expiry, subscriptions use the latest quotas for each plan.', + renew: 'Renovar la suscripción actual', + buying: 'Purchasing revision {{revision}}.', + fields: { + bots_max: 'Bots', + members_max: 'Members', + pipelines_max: 'Processors', + plugins_max: 'Extensions', + knowledge_bases_max: 'Knowledge bases', + managed_sandbox_sessions_max: 'Sandbox sessions', + }, + }, cloudPlans: { subscriptionUnavailable: 'Suscripción no disponible', subscriptionUnavailableHint: diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index 0099d4c80..6e9b40eed 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -1,6 +1,51 @@ import slackSetup from './slack-setup/ja-JP'; import pipelineMigration from './pipeline-migration/ja-JP'; const jaJP = { + planRevision: { + subscribedOn: '契約日:{{date}}', + latestQuota: 'このプランの最新の利用枠', + showLatest: 'このプランの最新クォータを表示', + showCurrent: '現在のサブスクリプションを表示', + latestTab: '最新', + currentTab: '現在', + cancelToSubscribe: + '現在のサブスクリプションをキャンセルすると、次期から最新のクォータに登録できます', + + current: '現在の契約:{{plan}} · revision {{revision}}', + owned: '購入済み revision {{revision}}', + latest: '最新 revision {{revision}}', + changed: + '購入済みの特典と最新バージョンに違いがあります。変更箇所を強調表示しています。', + same: '特典は最新 revision {{revision}} と同じです。購入済みバージョンは変更されません。', + unavailable: + '最新プランは現在利用できません。購入済みの特典は引き続き有効です。', + benefit: '特典', + change: '変更あり', + enabled: '有効', + disabled: '無効', + monthlyCredits: '毎月のボーナスクレジット', + retention: 'チャット履歴の保存日数', + unknown: + '現在の契約情報を取得できません。再読み込みするか Space で契約を管理してください。最新カタログの特典は購入済み特典ではありません。', + handoff: + 'Space で契約を管理してください。新バージョンの購入前に自動更新を解除してください。前払い契約は期限前に更新しなければ自動終了します。', + cancelFirst: + 'まず契約管理で自動更新を解除してください。支払済みの特典は期間終了まで保持され、返金はありません。解除の確認後、最新バージョンを購入でき、決済成功時に新しい特典が有効になります。', + canceled: + '更新は解除済みです。支払済みの特典は期間終了まで保持され、返金はありません。新しい購入の特典は決済成功時に有効になります。', + prepaid: + 'この前払い契約は期間終了時に自動終了します。期限前は現在の内容で更新でき、期限後は各プランの最新の利用枠でのみ契約できます。', + renew: '現在の契約を更新', + buying: 'revision {{revision}} を購入します。', + fields: { + bots_max: 'ボット', + members_max: 'メンバー', + pipelines_max: 'プロセッサー', + plugins_max: '拡張機能', + knowledge_bases_max: 'ナレッジベース', + managed_sandbox_sessions_max: 'サンドボックスセッション', + }, + }, cloudPlans: { subscriptionUnavailable: '現在お申し込みできません', subscriptionUnavailableHint: diff --git a/web/src/i18n/locales/ru-RU.ts b/web/src/i18n/locales/ru-RU.ts index 9c26c0f4f..132fda288 100644 --- a/web/src/i18n/locales/ru-RU.ts +++ b/web/src/i18n/locales/ru-RU.ts @@ -1,5 +1,50 @@ import pipelineMigration from './pipeline-migration/ru-RU'; const ruRU = { + planRevision: { + subscribedOn: 'Дата подписки: {{date}}', + latestQuota: 'Актуальные квоты этого плана', + showLatest: 'Показать актуальные квоты этого тарифа', + showCurrent: 'Показать текущую подписку', + latestTab: 'Новые', + currentTab: 'Текущая', + cancelToSubscribe: + 'Отмените подписку, чтобы получить доступ к новым квотам со следующего периода', + + current: 'Current subscription: {{plan}} · revision {{revision}}', + owned: 'Purchased revision {{revision}}', + latest: 'Latest revision {{revision}}', + changed: + 'Your purchased benefits and the latest version differ. Highlighted rows show the changes.', + same: 'Benefits match latest revision {{revision}}. Your purchased revision remains unchanged.', + unavailable: + 'The latest plan is unavailable. Your purchased benefits remain in effect.', + benefit: 'Benefit', + change: 'Changed', + enabled: 'Enabled', + disabled: 'Disabled', + monthlyCredits: 'Monthly bonus credits', + retention: 'Chat history retention (days)', + unknown: + 'Current subscription details are unavailable. Refresh or manage your subscription in Space; latest catalog benefits are not your purchased benefits.', + handoff: + 'Manage your subscription in Space. Cancel automatic renewal before purchasing a new version. Prepaid subscriptions end automatically unless renewed before expiry.', + cancelFirst: + 'Cancel automatic renewal in subscription management first. Cancellation preserves paid benefits until the end of the period, without a refund. After confirmation, you can purchase the latest version, effective when payment succeeds.', + canceled: + 'Renewal is canceled. Paid benefits remain until the end of the period, without a refund. A new purchase applies its benefits when payment succeeds.', + prepaid: + 'This prepaid subscription ends automatically. You can renew your current plan before expiry; after expiry, subscriptions use the latest quotas for each plan.', + renew: 'Продлить текущую подписку', + buying: 'Purchasing revision {{revision}}.', + fields: { + bots_max: 'Bots', + members_max: 'Members', + pipelines_max: 'Processors', + plugins_max: 'Extensions', + knowledge_bases_max: 'Knowledge bases', + managed_sandbox_sessions_max: 'Sandbox sessions', + }, + }, cloudPlans: { subscriptionUnavailable: 'Подписка недоступна', subscriptionUnavailableHint: diff --git a/web/src/i18n/locales/th-TH.ts b/web/src/i18n/locales/th-TH.ts index 1658976a3..92e591ebb 100644 --- a/web/src/i18n/locales/th-TH.ts +++ b/web/src/i18n/locales/th-TH.ts @@ -1,5 +1,49 @@ import pipelineMigration from './pipeline-migration/th-TH'; const thTH = { + planRevision: { + subscribedOn: 'วันที่สมัคร: {{date}}', + latestQuota: 'โควตาล่าสุดของแผนนี้', + showLatest: 'ดูโควตาล่าสุดของแผนนี้', + showCurrent: 'ดูการสมัครสมาชิกปัจจุบัน', + latestTab: 'ล่าสุด', + currentTab: 'ปัจจุบัน', + cancelToSubscribe: 'ยกเลิกการสมัครสมาชิกเพื่อรับโควต้าล่าสุดในรอบถัดไป', + + current: 'Current subscription: {{plan}} · revision {{revision}}', + owned: 'Purchased revision {{revision}}', + latest: 'Latest revision {{revision}}', + changed: + 'Your purchased benefits and the latest version differ. Highlighted rows show the changes.', + same: 'Benefits match latest revision {{revision}}. Your purchased revision remains unchanged.', + unavailable: + 'The latest plan is unavailable. Your purchased benefits remain in effect.', + benefit: 'Benefit', + change: 'Changed', + enabled: 'Enabled', + disabled: 'Disabled', + monthlyCredits: 'Monthly bonus credits', + retention: 'Chat history retention (days)', + unknown: + 'Current subscription details are unavailable. Refresh or manage your subscription in Space; latest catalog benefits are not your purchased benefits.', + handoff: + 'Manage your subscription in Space. Cancel automatic renewal before purchasing a new version. Prepaid subscriptions end automatically unless renewed before expiry.', + cancelFirst: + 'Cancel automatic renewal in subscription management first. Cancellation preserves paid benefits until the end of the period, without a refund. After confirmation, you can purchase the latest version, effective when payment succeeds.', + canceled: + 'Renewal is canceled. Paid benefits remain until the end of the period, without a refund. A new purchase applies its benefits when payment succeeds.', + prepaid: + 'This prepaid subscription ends automatically. You can renew your current plan before expiry; after expiry, subscriptions use the latest quotas for each plan.', + renew: 'ต่ออายุการสมัครปัจจุบัน', + buying: 'Purchasing revision {{revision}}.', + fields: { + bots_max: 'Bots', + members_max: 'Members', + pipelines_max: 'Processors', + plugins_max: 'Extensions', + knowledge_bases_max: 'Knowledge bases', + managed_sandbox_sessions_max: 'Sandbox sessions', + }, + }, cloudPlans: { subscriptionUnavailable: 'ยังไม่เปิดรับการสมัคร', subscriptionUnavailableHint: diff --git a/web/src/i18n/locales/vi-VN.ts b/web/src/i18n/locales/vi-VN.ts index 5db80b39e..ebd9faf67 100644 --- a/web/src/i18n/locales/vi-VN.ts +++ b/web/src/i18n/locales/vi-VN.ts @@ -1,5 +1,50 @@ import pipelineMigration from './pipeline-migration/vi-VN'; const viVN = { + planRevision: { + subscribedOn: 'Ngày đăng ký: {{date}}', + latestQuota: 'Hạn mức mới nhất của gói này', + showLatest: 'Xem hạn mức mới nhất của gói này', + showCurrent: 'Xem gói đăng ký hiện tại', + latestTab: 'Mới nhất', + currentTab: 'Hiện tại', + cancelToSubscribe: + 'Hủy gói hiện tại để đăng ký hạn mức mới nhất vào kỳ tiếp theo', + + current: 'Current subscription: {{plan}} · revision {{revision}}', + owned: 'Purchased revision {{revision}}', + latest: 'Latest revision {{revision}}', + changed: + 'Your purchased benefits and the latest version differ. Highlighted rows show the changes.', + same: 'Benefits match latest revision {{revision}}. Your purchased revision remains unchanged.', + unavailable: + 'The latest plan is unavailable. Your purchased benefits remain in effect.', + benefit: 'Benefit', + change: 'Changed', + enabled: 'Enabled', + disabled: 'Disabled', + monthlyCredits: 'Monthly bonus credits', + retention: 'Chat history retention (days)', + unknown: + 'Current subscription details are unavailable. Refresh or manage your subscription in Space; latest catalog benefits are not your purchased benefits.', + handoff: + 'Manage your subscription in Space. Cancel automatic renewal before purchasing a new version. Prepaid subscriptions end automatically unless renewed before expiry.', + cancelFirst: + 'Cancel automatic renewal in subscription management first. Cancellation preserves paid benefits until the end of the period, without a refund. After confirmation, you can purchase the latest version, effective when payment succeeds.', + canceled: + 'Renewal is canceled. Paid benefits remain until the end of the period, without a refund. A new purchase applies its benefits when payment succeeds.', + prepaid: + 'This prepaid subscription ends automatically. You can renew your current plan before expiry; after expiry, subscriptions use the latest quotas for each plan.', + renew: 'Gia hạn gói đăng ký hiện tại', + buying: 'Purchasing revision {{revision}}.', + fields: { + bots_max: 'Bots', + members_max: 'Members', + pipelines_max: 'Processors', + plugins_max: 'Extensions', + knowledge_bases_max: 'Knowledge bases', + managed_sandbox_sessions_max: 'Sandbox sessions', + }, + }, cloudPlans: { subscriptionUnavailable: 'Chưa thể đăng ký', subscriptionUnavailableHint: diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index fed8f4b7f..4d4c3d0fc 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -1,6 +1,48 @@ import slackSetup from './slack-setup/zh-Hans'; import pipelineMigration from './pipeline-migration/zh-Hans'; const zhHans = { + planRevision: { + subscribedOn: '订阅日期:{{date}}', + latestQuota: '该计划最新配额', + showLatest: '查看该计划最新配额', + showCurrent: '查看当前订阅', + latestTab: '最新配额', + currentTab: '当前订阅', + cancelToSubscribe: '取消本期订阅,下一期即可订阅最新配额', + + current: '当前订阅:{{plan}} · revision {{revision}}', + owned: '已购买 revision {{revision}}', + latest: '最新 revision {{revision}}', + changed: '已购买权益与最新版本存在差异,高亮行标示变动。', + same: '权益与最新 revision {{revision}} 相同,已购买版本保持不变。', + unavailable: '最新计划暂不可用,已购买权益继续有效。', + benefit: '权益', + change: '有变化', + enabled: '启用', + disabled: '禁用', + monthlyCredits: '每月赠送额度', + retention: '聊天记录保留天数', + unknown: + '当前订阅详情不可用。请刷新或前往 Space 管理订阅;最新目录权益不代表已购买权益。', + handoff: + '请在 Space 管理订阅。自动续费需先取消后才能购买新版本;预付订阅到期自动结束,除非到期前手动续订。', + cancelFirst: + '请先在订阅管理中取消自动续费。取消后保留本期已付权益,不退款。服务端确认取消后可重新购买最新版本,付款成功后新权益生效。', + canceled: + '续费已取消。本期已付权益保留至到期,不退款;重新购买的权益在付款成功后生效。', + prepaid: + '本预付订阅到期自动结束。到期前可续订当前方案;到期后只能按各计划的最新配额订阅。', + renew: '续订当前订阅', + buying: '正在购买 revision {{revision}}。', + fields: { + bots_max: '机器人', + members_max: '成员', + pipelines_max: '处理器', + plugins_max: '扩展', + knowledge_bases_max: '知识库', + managed_sandbox_sessions_max: '沙箱会话', + }, + }, cloudPlans: { subscriptionUnavailable: '暂不可订阅', subscriptionUnavailableHint: diff --git a/web/src/i18n/locales/zh-Hant.ts b/web/src/i18n/locales/zh-Hant.ts index da7b8f831..49b2ce982 100644 --- a/web/src/i18n/locales/zh-Hant.ts +++ b/web/src/i18n/locales/zh-Hant.ts @@ -1,6 +1,48 @@ import slackSetup from './slack-setup/zh-Hant'; import pipelineMigration from './pipeline-migration/zh-Hant'; const zhHant = { + planRevision: { + subscribedOn: '訂閱日期:{{date}}', + latestQuota: '該方案最新配額', + showLatest: '查看該計畫最新配額', + showCurrent: '查看目前訂閱', + latestTab: '最新配額', + currentTab: '目前訂閱', + cancelToSubscribe: '取消本期訂閱,下一期即可訂閱最新配額', + + current: '当前订阅:{{plan}} · revision {{revision}}', + owned: '已购买 revision {{revision}}', + latest: '最新 revision {{revision}}', + changed: '已购买权益与最新版本存在差异,高亮行标示变动。', + same: '权益与最新 revision {{revision}} 相同,已购买版本保持不变。', + unavailable: '最新计划暂不可用,已购买权益继续有效。', + benefit: '权益', + change: '有变化', + enabled: '启用', + disabled: '禁用', + monthlyCredits: '每月赠送额度', + retention: '聊天记录保留天数', + unknown: + '当前订阅详情不可用。请刷新或前往 Space 管理订阅;最新目录权益不代表已购买权益。', + handoff: + '请在 Space 管理订阅。自动续费需先取消后才能购买新版本;预付订阅到期自动结束,除非到期前手动续订。', + cancelFirst: + '请先在订阅管理中取消自动续费。取消后保留本期已付权益,不退款。服务端确认取消后可重新购买最新版本,付款成功后新权益生效。', + canceled: + '续费已取消。本期已付权益保留至到期,不退款;重新购买的权益在付款成功后生效。', + prepaid: + '本預付訂閱到期自動結束。到期前可續訂目前方案;到期後只能按各方案的最新配額訂閱。', + renew: '續訂目前訂閱', + buying: '正在购买 revision {{revision}}。', + fields: { + bots_max: '机器人', + members_max: '成员', + pipelines_max: '处理器', + plugins_max: '扩展', + knowledge_bases_max: '知识库', + managed_sandbox_sessions_max: '沙箱会话', + }, + }, cloudPlans: { subscriptionUnavailable: '暫不可訂閱', subscriptionUnavailableHint: diff --git a/web/tests/revisions/index.html b/web/tests/revisions/index.html new file mode 100644 index 000000000..ea3013314 --- /dev/null +++ b/web/tests/revisions/index.html @@ -0,0 +1 @@ +
diff --git a/web/tests/revisions/preview.tsx b/web/tests/revisions/preview.tsx new file mode 100644 index 000000000..c40db14b6 --- /dev/null +++ b/web/tests/revisions/preview.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { createInstance } from 'i18next'; +import { I18nextProvider } from 'react-i18next'; +import enUS from '../../src/i18n/locales/en-US'; +import CloudPlansDialog from '../../src/app/home/components/workspace-settings/CloudPlansDialog'; +import '../../src/app/global.css'; +const mode = new URLSearchParams(location.search).get('mode') || 'changed'; +const current = { + name: 'pro', + subscription_started_at: 1790812800, + revision: 1, + limits: { + 'bots.max': 2, + 'members.max': 3, + 'chat_history.retention_days': 30, + }, + entitlement_features: { mcp_stdio: false, 'platform.telegram': false }, + monthly_bonus_credits: 10, +}; +const i18n = createInstance(); +await i18n.init({ + lng: 'en-US', + resources: { 'en-US': { translation: enUS } }, +}); +createRoot(document.getElementById('root')!).render( + + {}} + workspace={ + { + workspace: { uuid: 'fixture', name: 'Review workspace' }, + membership: { role: mode === 'member' ? 'viewer' : 'owner' }, + plan_name: 'pro', + current_plan: mode === 'unknown' ? null : current, + } as any + } + spaceURL="https://space.example.test" + /> + , +); diff --git a/web/tests/revisions/verify.mjs b/web/tests/revisions/verify.mjs new file mode 100644 index 000000000..367ac2963 --- /dev/null +++ b/web/tests/revisions/verify.mjs @@ -0,0 +1,72 @@ +import {createServer} from 'vite'; +import {chromium} from '@playwright/test'; +import {fileURLToPath} from 'node:url'; +import path from 'node:path'; +import {mkdir,writeFile} from 'node:fs/promises'; +const workdir=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'../../../..'); +const evidence=path.join(workdir,'evidence'); +await mkdir(evidence,{recursive:true}); +const servers=[];let browser; +const results=[]; +const latest=(mode)=>({uuid:'latest',name:'pro',revision:mode==='same'?1:2,display_name:{en_US:'Pro'},description:{en_US:'Workspace plan'},price_cny:'70',price_usd:'10',price_jpy:'1500',features:[],limits: mode==='equal'||mode==='same'?{'bots.max':2,'members.max':3}:{'bots.max':8,'members.max':5},entitlement_features:{mcp_stdio:!['same','equal'].includes(mode)},disabled_adapters:['same','equal'].includes(mode)?['telegram']:[],chat_history_retention_days:['same','equal'].includes(mode)?30:90,monthly_bonus_credits:['same','equal'].includes(mode)?10:20,is_active:true,subscription_disabled:mode==='paused',sort_order:1,highlight:false}); +try { + for(const [repo,port] of [['space',4185],['core',4186]]){ + const server=await createServer({configFile:path.join(workdir,repo,'web/tests/revisions/vite.config.mjs')});await server.listen();servers.push(server); + } + browser=await chromium.launch({headless:true}); + for(const repo of ['space','core']){ + const modes=repo==='space'?['changed','same','equal','paused','removed','member','stripe','cancel-failure']:['changed','same','equal','paused','removed','member','unknown','error']; + for(const mode of modes){ + const page=await browser.newPage({viewport:{width:1360,height:1100}}); + page.setDefaultTimeout(120000); + const errors=[];page.on('pageerror',error=>{errors.push(error.message);console.error(error.message);}); + await page.route('**/*',async route=>{ + const url=new URL(route.request().url()); + if(url.hostname==='127.0.0.1')return route.continue(); + if(url.pathname==='/api/v1/cloud/v2/plans')return route.fulfill({status:mode==='error'?500:200,contentType:'application/json',body:JSON.stringify({code:0,data:mode==='removed'?[]:[latest(mode)]})}); + return route.abort(); + }); + const port=repo==='space'?4185:4186; + await page.goto(`http://127.0.0.1:${port}/tests/revisions/index.html?mode=${mode}${mode==='stripe'?'&intent=1':''}`,{waitUntil:'domcontentloaded',timeout:120000}); + if(repo==='core'&&mode==='error')await page.getByRole('alert').waitFor(); + else if(mode==='unknown')await page.getByText(/Current subscription details are unavailable/).waitFor(); + else await page.locator(repo==='space'?'[data-plan-comparison]':'[data-cloud-plans] article').first().waitFor(); + if(errors.length)throw new Error(`${repo}/${mode}: ${errors.join(';')}`); + if(await page.locator('[data-revision-comparison]').count())throw new Error('Standalone comparison remains'); + if(['same','equal'].includes(mode) && await page.locator('[data-owned-plan]').count())throw new Error('Identical benefits duplicated'); + const area=page.locator(repo==='space'?'[data-plan-comparison]':'[data-cloud-plans]'); + if(!['unknown','error'].includes(mode)) { + const copy=await area.innerText(); + if(/&#|revision|mcp stdio|workspace multi user|platform telegram/.test(copy))throw new Error('Internal metadata leaked into plan presentation'); + if(repo==='core'&&await area.locator('[data-changed], .bg-amber-500\\/10').count())throw new Error('Diff highlighting remains'); + if(['same','equal'].includes(mode)&&/Subscribed on|Latest quotas for this plan/.test(copy))throw new Error('Unnecessary comparison labels'); + } + if(mode==='changed') { + await area.getByText(/Subscribed on/).waitFor(); + await area.getByText('Latest quotas for this plan',{exact:true}).waitFor(); + const owned=page.locator('[data-owned-plan]'); + if(await owned.count()!==1)throw new Error('Missing integrated owned version'); + const horizontal=await owned.evaluate(el=>{const next=el.nextElementSibling;return !!next && Math.abs(next.getBoundingClientRect().y-el.getBoundingClientRect().y)<5 && next.getBoundingClientRect().x>el.getBoundingClientRect().x;}); + if(!horizontal)throw new Error('Owned and latest are not adjacent horizontally'); + } + if(repo==='space'&&mode==='stripe'){ + if(await page.getByRole('dialog').count())throw new Error('Deep link bypassed renewal gate'); + await page.getByRole('button',{name:'Manage subscription'}).first().click(); + await page.getByText('Server refresh confirmed cancellation').waitFor(); + if (!(await page.getByRole('dialog').count())) await page.getByRole('button',{name:/Upgrade to|Subscribe to|Choose/}).last().click(); + await page.getByRole('dialog').waitFor(); + } + if(repo==='space'&&mode==='cancel-failure'){ + await page.getByRole('button',{name:'Manage subscription'}).first().click(); + await page.getByText('Cancellation failed').waitFor(); + if(await page.getByRole('dialog').count())throw new Error('Failed cancellation opened checkout'); + } + if(mode==='member'&&repo==='core'&&await page.getByRole('link').count())throw new Error('Member purchase link'); + if(mode==='changed'||mode==='equal'||mode==='member'||mode==='paused')await page.screenshot({path:path.join(evidence,`${repo}-${mode}.png`),fullPage:true}); + results.push(`${repo}/${mode}: PASS`);console.log(results.at(-1));await page.close(); + } + } +} finally { + await writeFile(path.join(evidence,'browser-results.txt'),results.join('\n')+'\n'); + if(browser)await browser.close();for(const server of servers)await server.close(); +} diff --git a/web/tests/revisions/vite.config.mjs b/web/tests/revisions/vite.config.mjs new file mode 100644 index 000000000..390cf2604 --- /dev/null +++ b/web/tests/revisions/vite.config.mjs @@ -0,0 +1,5 @@ +import path from 'node:path'; +import tailwind from '@tailwindcss/postcss'; +import {fileURLToPath} from 'node:url'; +const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'../..'); +export default {root,resolve:{alias:{'@':path.join(root,'src')}},esbuild:{jsx:'automatic'},css:{postcss:{plugins:[tailwind({base:root})]}},server:{host:'127.0.0.1',port:4186,strictPort:true}}; diff --git a/web/tests/unit/plan-revisions.test.mjs b/web/tests/unit/plan-revisions.test.mjs new file mode 100644 index 000000000..ebf985005 --- /dev/null +++ b/web/tests/unit/plan-revisions.test.mjs @@ -0,0 +1,27 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { readFileSync } from 'node:fs'; +import ts from 'typescript'; +const m = { exports: {} }; +new Function('module', 'exports', ts.transpileModule(readFileSync(new URL('../../src/app/home/components/workspace-settings/plan-revisions.ts', import.meta.url), 'utf8'), {compilerOptions: {module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022}}).outputText)(m, m.exports); +const { revisionRows } = m.exports; +const current = { name: 'pro', revision: 1, limits: {'bots.max': 2, 'chat_history.retention_days': 30}, monthly_bonus_credits: 10, entitlement_features: {'platform.telegram': false, mcp_stdio: false} }; +test('same version and revision-only changes do not duplicate comparisons', () => { + assert.ok(revisionRows(current, current).every(row => !row.changed)); + assert.ok(revisionRows(current, {...current, revision: 2}).every(row => !row.changed)); +}); +test('compares effective quotas, credits, retention, platforms and feature switches', () => { + const latest = {name:'pro', revision:2, limits:{'bots.max':5}, chat_history_retention_days:60, monthly_bonus_credits:20, entitlement_features:{mcp_stdio:true}, disabled_adapters:[]}; + const changed = revisionRows(current, latest).filter(row => row.changed); + assert.deepEqual(changed.map(row => row.key).sort(), ['feature:mcp_stdio','feature:platform.telegram','limit:bots.max','limit:chat_history.retention_days','monthly_credits'].sort()); + assert.equal(changed.find(row => row.key === 'feature:platform.telegram').after, true); +}); +test('catalog representations normalize to snapshot values', () => { + const latest = {name:'pro',revision:2,limits:{'bots.max':2},chat_history_retention_days:30,monthly_bonus_credits:10,disabled_adapters:['telegram']}; + assert.ok(revisionRows(current, latest).every(row => !row.changed)); +}); +test('missing latest keeps current values without invented changes', () => { + const rows=revisionRows(current); + assert.equal(rows.find(row=>row.key==='limit:bots.max').before,2); + assert.ok(rows.every(row=>!row.changed)); +}); From 624d8efd9749b1e26c61e71560639fc0b4803072 Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Sat, 17 Oct 2026 01:54:05 +0800 Subject: [PATCH 15/16] test(web): align frontend checks with the plan comparison dialog - Use LangBot Account wording in the Japanese checkout handoff copy. - Assert the Workspace Settings plan entry opens CloudPlansDialog. - Format revision tests with Prettier. Co-Authored-By: Claude Opus 5.5 --- web/src/i18n/locales/ja-JP.ts | 3 +- web/tests/revisions/verify.mjs | 287 ++++++++++++++---- web/tests/unit/plan-revisions.test.mjs | 81 ++++- .../unit/workspace-switcher-layout.test.mjs | 5 +- 4 files changed, 292 insertions(+), 84 deletions(-) diff --git a/web/src/i18n/locales/ja-JP.ts b/web/src/i18n/locales/ja-JP.ts index 6e9b40eed..402e10b75 100644 --- a/web/src/i18n/locales/ja-JP.ts +++ b/web/src/i18n/locales/ja-JP.ts @@ -94,7 +94,8 @@ const jaJP = { annualNote: '月額 {{price}} 相当', ownerOnly: '所有者または管理者のみ', choose: '支払いに進む', - handoff: 'Space でログインしてお支払いください。選択内容は保持されます。', + handoff: + 'LangBot アカウントでログインしてお支払いください。選択内容は保持されます。', quotas: 'ワークスペースの上限', unlimited: '無制限', retention: 'メッセージ履歴 · {{count}} 日間', diff --git a/web/tests/revisions/verify.mjs b/web/tests/revisions/verify.mjs index 367ac2963..6d4ba1efb 100644 --- a/web/tests/revisions/verify.mjs +++ b/web/tests/revisions/verify.mjs @@ -1,72 +1,225 @@ -import {createServer} from 'vite'; -import {chromium} from '@playwright/test'; -import {fileURLToPath} from 'node:url'; +import { createServer } from 'vite'; +import { chromium } from '@playwright/test'; +import { fileURLToPath } from 'node:url'; import path from 'node:path'; -import {mkdir,writeFile} from 'node:fs/promises'; -const workdir=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'../../../..'); -const evidence=path.join(workdir,'evidence'); -await mkdir(evidence,{recursive:true}); -const servers=[];let browser; -const results=[]; -const latest=(mode)=>({uuid:'latest',name:'pro',revision:mode==='same'?1:2,display_name:{en_US:'Pro'},description:{en_US:'Workspace plan'},price_cny:'70',price_usd:'10',price_jpy:'1500',features:[],limits: mode==='equal'||mode==='same'?{'bots.max':2,'members.max':3}:{'bots.max':8,'members.max':5},entitlement_features:{mcp_stdio:!['same','equal'].includes(mode)},disabled_adapters:['same','equal'].includes(mode)?['telegram']:[],chat_history_retention_days:['same','equal'].includes(mode)?30:90,monthly_bonus_credits:['same','equal'].includes(mode)?10:20,is_active:true,subscription_disabled:mode==='paused',sort_order:1,highlight:false}); +import { mkdir, writeFile } from 'node:fs/promises'; +const workdir = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '../../../..', +); +const evidence = path.join(workdir, 'evidence'); +await mkdir(evidence, { recursive: true }); +const servers = []; +let browser; +const results = []; +const latest = (mode) => ({ + uuid: 'latest', + name: 'pro', + revision: mode === 'same' ? 1 : 2, + display_name: { en_US: 'Pro' }, + description: { en_US: 'Workspace plan' }, + price_cny: '70', + price_usd: '10', + price_jpy: '1500', + features: [], + limits: + mode === 'equal' || mode === 'same' + ? { 'bots.max': 2, 'members.max': 3 } + : { 'bots.max': 8, 'members.max': 5 }, + entitlement_features: { mcp_stdio: !['same', 'equal'].includes(mode) }, + disabled_adapters: ['same', 'equal'].includes(mode) ? ['telegram'] : [], + chat_history_retention_days: ['same', 'equal'].includes(mode) ? 30 : 90, + monthly_bonus_credits: ['same', 'equal'].includes(mode) ? 10 : 20, + is_active: true, + subscription_disabled: mode === 'paused', + sort_order: 1, + highlight: false, +}); try { - for(const [repo,port] of [['space',4185],['core',4186]]){ - const server=await createServer({configFile:path.join(workdir,repo,'web/tests/revisions/vite.config.mjs')});await server.listen();servers.push(server); - } - browser=await chromium.launch({headless:true}); - for(const repo of ['space','core']){ - const modes=repo==='space'?['changed','same','equal','paused','removed','member','stripe','cancel-failure']:['changed','same','equal','paused','removed','member','unknown','error']; - for(const mode of modes){ - const page=await browser.newPage({viewport:{width:1360,height:1100}}); - page.setDefaultTimeout(120000); - const errors=[];page.on('pageerror',error=>{errors.push(error.message);console.error(error.message);}); - await page.route('**/*',async route=>{ - const url=new URL(route.request().url()); - if(url.hostname==='127.0.0.1')return route.continue(); - if(url.pathname==='/api/v1/cloud/v2/plans')return route.fulfill({status:mode==='error'?500:200,contentType:'application/json',body:JSON.stringify({code:0,data:mode==='removed'?[]:[latest(mode)]})}); - return route.abort(); - }); - const port=repo==='space'?4185:4186; - await page.goto(`http://127.0.0.1:${port}/tests/revisions/index.html?mode=${mode}${mode==='stripe'?'&intent=1':''}`,{waitUntil:'domcontentloaded',timeout:120000}); - if(repo==='core'&&mode==='error')await page.getByRole('alert').waitFor(); - else if(mode==='unknown')await page.getByText(/Current subscription details are unavailable/).waitFor(); - else await page.locator(repo==='space'?'[data-plan-comparison]':'[data-cloud-plans] article').first().waitFor(); - if(errors.length)throw new Error(`${repo}/${mode}: ${errors.join(';')}`); - if(await page.locator('[data-revision-comparison]').count())throw new Error('Standalone comparison remains'); - if(['same','equal'].includes(mode) && await page.locator('[data-owned-plan]').count())throw new Error('Identical benefits duplicated'); - const area=page.locator(repo==='space'?'[data-plan-comparison]':'[data-cloud-plans]'); - if(!['unknown','error'].includes(mode)) { - const copy=await area.innerText(); - if(/&#|revision|mcp stdio|workspace multi user|platform telegram/.test(copy))throw new Error('Internal metadata leaked into plan presentation'); - if(repo==='core'&&await area.locator('[data-changed], .bg-amber-500\\/10').count())throw new Error('Diff highlighting remains'); - if(['same','equal'].includes(mode)&&/Subscribed on|Latest quotas for this plan/.test(copy))throw new Error('Unnecessary comparison labels'); - } - if(mode==='changed') { - await area.getByText(/Subscribed on/).waitFor(); - await area.getByText('Latest quotas for this plan',{exact:true}).waitFor(); - const owned=page.locator('[data-owned-plan]'); - if(await owned.count()!==1)throw new Error('Missing integrated owned version'); - const horizontal=await owned.evaluate(el=>{const next=el.nextElementSibling;return !!next && Math.abs(next.getBoundingClientRect().y-el.getBoundingClientRect().y)<5 && next.getBoundingClientRect().x>el.getBoundingClientRect().x;}); - if(!horizontal)throw new Error('Owned and latest are not adjacent horizontally'); - } - if(repo==='space'&&mode==='stripe'){ - if(await page.getByRole('dialog').count())throw new Error('Deep link bypassed renewal gate'); - await page.getByRole('button',{name:'Manage subscription'}).first().click(); - await page.getByText('Server refresh confirmed cancellation').waitFor(); - if (!(await page.getByRole('dialog').count())) await page.getByRole('button',{name:/Upgrade to|Subscribe to|Choose/}).last().click(); - await page.getByRole('dialog').waitFor(); - } - if(repo==='space'&&mode==='cancel-failure'){ - await page.getByRole('button',{name:'Manage subscription'}).first().click(); - await page.getByText('Cancellation failed').waitFor(); - if(await page.getByRole('dialog').count())throw new Error('Failed cancellation opened checkout'); - } - if(mode==='member'&&repo==='core'&&await page.getByRole('link').count())throw new Error('Member purchase link'); - if(mode==='changed'||mode==='equal'||mode==='member'||mode==='paused')await page.screenshot({path:path.join(evidence,`${repo}-${mode}.png`),fullPage:true}); - results.push(`${repo}/${mode}: PASS`);console.log(results.at(-1));await page.close(); + for (const [repo] of [ + ['space', 4185], + ['core', 4186], + ]) { + const server = await createServer({ + configFile: path.join( + workdir, + repo, + 'web/tests/revisions/vite.config.mjs', + ), + }); + await server.listen(); + servers.push(server); + } + browser = await chromium.launch({ headless: true }); + for (const repo of ['space', 'core']) { + const modes = + repo === 'space' + ? [ + 'changed', + 'same', + 'equal', + 'paused', + 'removed', + 'member', + 'stripe', + 'cancel-failure', + ] + : [ + 'changed', + 'same', + 'equal', + 'paused', + 'removed', + 'member', + 'unknown', + 'error', + ]; + for (const mode of modes) { + const page = await browser.newPage({ + viewport: { width: 1360, height: 1100 }, + }); + page.setDefaultTimeout(120000); + const errors = []; + page.on('pageerror', (error) => { + errors.push(error.message); + console.error(error.message); + }); + await page.route('**/*', async (route) => { + const url = new URL(route.request().url()); + if (url.hostname === '127.0.0.1') return route.continue(); + if (url.pathname === '/api/v1/cloud/v2/plans') + return route.fulfill({ + status: mode === 'error' ? 500 : 200, + contentType: 'application/json', + body: JSON.stringify({ + code: 0, + data: mode === 'removed' ? [] : [latest(mode)], + }), + }); + return route.abort(); + }); + const port = repo === 'space' ? 4185 : 4186; + await page.goto( + `http://127.0.0.1:${port}/tests/revisions/index.html?mode=${mode}${mode === 'stripe' ? '&intent=1' : ''}`, + { waitUntil: 'domcontentloaded', timeout: 120000 }, + ); + if (repo === 'core' && mode === 'error') + await page.getByRole('alert').waitFor(); + else if (mode === 'unknown') + await page + .getByText(/Current subscription details are unavailable/) + .waitFor(); + else + await page + .locator( + repo === 'space' + ? '[data-plan-comparison]' + : '[data-cloud-plans] article', + ) + .first() + .waitFor(); + if (errors.length) + throw new Error(`${repo}/${mode}: ${errors.join(';')}`); + if (await page.locator('[data-revision-comparison]').count()) + throw new Error('Standalone comparison remains'); + if ( + ['same', 'equal'].includes(mode) && + (await page.locator('[data-owned-plan]').count()) + ) + throw new Error('Identical benefits duplicated'); + const area = page.locator( + repo === 'space' ? '[data-plan-comparison]' : '[data-cloud-plans]', + ); + if (!['unknown', 'error'].includes(mode)) { + const copy = await area.innerText(); + if ( + /&#|revision|mcp stdio|workspace multi user|platform telegram/.test( + copy, + ) + ) + throw new Error('Internal metadata leaked into plan presentation'); + if ( + repo === 'core' && + (await area.locator('[data-changed], .bg-amber-500\\/10').count()) + ) + throw new Error('Diff highlighting remains'); + if ( + ['same', 'equal'].includes(mode) && + /Subscribed on|Latest quotas for this plan/.test(copy) + ) + throw new Error('Unnecessary comparison labels'); + } + if (mode === 'changed') { + await area.getByText(/Subscribed on/).waitFor(); + await area + .getByText('Latest quotas for this plan', { exact: true }) + .waitFor(); + const owned = page.locator('[data-owned-plan]'); + if ((await owned.count()) !== 1) + throw new Error('Missing integrated owned version'); + const horizontal = await owned.evaluate((el) => { + const next = el.nextElementSibling; + return ( + !!next && + Math.abs( + next.getBoundingClientRect().y - el.getBoundingClientRect().y, + ) < 5 && + next.getBoundingClientRect().x > el.getBoundingClientRect().x + ); + }); + if (!horizontal) + throw new Error('Owned and latest are not adjacent horizontally'); + } + if (repo === 'space' && mode === 'stripe') { + if (await page.getByRole('dialog').count()) + throw new Error('Deep link bypassed renewal gate'); + await page + .getByRole('button', { name: 'Manage subscription' }) + .first() + .click(); + await page.getByText('Server refresh confirmed cancellation').waitFor(); + if (!(await page.getByRole('dialog').count())) + await page + .getByRole('button', { name: /Upgrade to|Subscribe to|Choose/ }) + .last() + .click(); + await page.getByRole('dialog').waitFor(); + } + if (repo === 'space' && mode === 'cancel-failure') { + await page + .getByRole('button', { name: 'Manage subscription' }) + .first() + .click(); + await page.getByText('Cancellation failed').waitFor(); + if (await page.getByRole('dialog').count()) + throw new Error('Failed cancellation opened checkout'); + } + if ( + mode === 'member' && + repo === 'core' && + (await page.getByRole('link').count()) + ) + throw new Error('Member purchase link'); + if ( + mode === 'changed' || + mode === 'equal' || + mode === 'member' || + mode === 'paused' + ) + await page.screenshot({ + path: path.join(evidence, `${repo}-${mode}.png`), + fullPage: true, + }); + results.push(`${repo}/${mode}: PASS`); + console.log(results.at(-1)); + await page.close(); + } } - } } finally { - await writeFile(path.join(evidence,'browser-results.txt'),results.join('\n')+'\n'); - if(browser)await browser.close();for(const server of servers)await server.close(); + await writeFile( + path.join(evidence, 'browser-results.txt'), + results.join('\n') + '\n', + ); + if (browser) await browser.close(); + for (const server of servers) await server.close(); } diff --git a/web/tests/unit/plan-revisions.test.mjs b/web/tests/unit/plan-revisions.test.mjs index ebf985005..d5c57f805 100644 --- a/web/tests/unit/plan-revisions.test.mjs +++ b/web/tests/unit/plan-revisions.test.mjs @@ -3,25 +3,80 @@ import test from 'node:test'; import { readFileSync } from 'node:fs'; import ts from 'typescript'; const m = { exports: {} }; -new Function('module', 'exports', ts.transpileModule(readFileSync(new URL('../../src/app/home/components/workspace-settings/plan-revisions.ts', import.meta.url), 'utf8'), {compilerOptions: {module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022}}).outputText)(m, m.exports); +new Function( + 'module', + 'exports', + ts.transpileModule( + readFileSync( + new URL( + '../../src/app/home/components/workspace-settings/plan-revisions.ts', + import.meta.url, + ), + 'utf8', + ), + { + compilerOptions: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES2022, + }, + }, + ).outputText, +)(m, m.exports); const { revisionRows } = m.exports; -const current = { name: 'pro', revision: 1, limits: {'bots.max': 2, 'chat_history.retention_days': 30}, monthly_bonus_credits: 10, entitlement_features: {'platform.telegram': false, mcp_stdio: false} }; +const current = { + name: 'pro', + revision: 1, + limits: { 'bots.max': 2, 'chat_history.retention_days': 30 }, + monthly_bonus_credits: 10, + entitlement_features: { 'platform.telegram': false, mcp_stdio: false }, +}; test('same version and revision-only changes do not duplicate comparisons', () => { - assert.ok(revisionRows(current, current).every(row => !row.changed)); - assert.ok(revisionRows(current, {...current, revision: 2}).every(row => !row.changed)); + assert.ok(revisionRows(current, current).every((row) => !row.changed)); + assert.ok( + revisionRows(current, { ...current, revision: 2 }).every( + (row) => !row.changed, + ), + ); }); test('compares effective quotas, credits, retention, platforms and feature switches', () => { - const latest = {name:'pro', revision:2, limits:{'bots.max':5}, chat_history_retention_days:60, monthly_bonus_credits:20, entitlement_features:{mcp_stdio:true}, disabled_adapters:[]}; - const changed = revisionRows(current, latest).filter(row => row.changed); - assert.deepEqual(changed.map(row => row.key).sort(), ['feature:mcp_stdio','feature:platform.telegram','limit:bots.max','limit:chat_history.retention_days','monthly_credits'].sort()); - assert.equal(changed.find(row => row.key === 'feature:platform.telegram').after, true); + const latest = { + name: 'pro', + revision: 2, + limits: { 'bots.max': 5 }, + chat_history_retention_days: 60, + monthly_bonus_credits: 20, + entitlement_features: { mcp_stdio: true }, + disabled_adapters: [], + }; + const changed = revisionRows(current, latest).filter((row) => row.changed); + assert.deepEqual( + changed.map((row) => row.key).sort(), + [ + 'feature:mcp_stdio', + 'feature:platform.telegram', + 'limit:bots.max', + 'limit:chat_history.retention_days', + 'monthly_credits', + ].sort(), + ); + assert.equal( + changed.find((row) => row.key === 'feature:platform.telegram').after, + true, + ); }); test('catalog representations normalize to snapshot values', () => { - const latest = {name:'pro',revision:2,limits:{'bots.max':2},chat_history_retention_days:30,monthly_bonus_credits:10,disabled_adapters:['telegram']}; - assert.ok(revisionRows(current, latest).every(row => !row.changed)); + const latest = { + name: 'pro', + revision: 2, + limits: { 'bots.max': 2 }, + chat_history_retention_days: 30, + monthly_bonus_credits: 10, + disabled_adapters: ['telegram'], + }; + assert.ok(revisionRows(current, latest).every((row) => !row.changed)); }); test('missing latest keeps current values without invented changes', () => { - const rows=revisionRows(current); - assert.equal(rows.find(row=>row.key==='limit:bots.max').before,2); - assert.ok(rows.every(row=>!row.changed)); + const rows = revisionRows(current); + assert.equal(rows.find((row) => row.key === 'limit:bots.max').before, 2); + assert.ok(rows.every((row) => !row.changed)); }); diff --git a/web/tests/unit/workspace-switcher-layout.test.mjs b/web/tests/unit/workspace-switcher-layout.test.mjs index 7b7e241f5..09e63aefa 100644 --- a/web/tests/unit/workspace-switcher-layout.test.mjs +++ b/web/tests/unit/workspace-switcher-layout.test.mjs @@ -90,9 +90,8 @@ test('keeps workspace plan and settings controls on the workspace row', () => { }); test('moves Cloud plan upgrades into Workspace Settings', () => { - assert.match(workspaceSettingsPanelSource, /workspace\.upgradePlan/); - assert.match(workspaceSettingsPanelSource, /cloudPortalURL/); - assert.match(workspaceSettingsPanelSource, /step=plan/); + assert.match(workspaceSettingsPanelSource, /cloudPlans\.title/); + assert.match(workspaceSettingsPanelSource, / Date: Sat, 17 Oct 2026 01:54:18 +0800 Subject: [PATCH 16/16] style(web): format revision preview fixtures Co-Authored-By: Claude Opus 5.5 --- web/tests/revisions/index.html | 10 +++++++++- web/tests/revisions/vite.config.mjs | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/web/tests/revisions/index.html b/web/tests/revisions/index.html index ea3013314..90e9c1c0b 100644 --- a/web/tests/revisions/index.html +++ b/web/tests/revisions/index.html @@ -1 +1,9 @@ -
+ + + + + +
+ + + diff --git a/web/tests/revisions/vite.config.mjs b/web/tests/revisions/vite.config.mjs index 390cf2604..5021b7626 100644 --- a/web/tests/revisions/vite.config.mjs +++ b/web/tests/revisions/vite.config.mjs @@ -1,5 +1,14 @@ import path from 'node:path'; import tailwind from '@tailwindcss/postcss'; -import {fileURLToPath} from 'node:url'; -const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'../..'); -export default {root,resolve:{alias:{'@':path.join(root,'src')}},esbuild:{jsx:'automatic'},css:{postcss:{plugins:[tailwind({base:root})]}},server:{host:'127.0.0.1',port:4186,strictPort:true}}; +import { fileURLToPath } from 'node:url'; +const root = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '../..', +); +export default { + root, + resolve: { alias: { '@': path.join(root, 'src') } }, + esbuild: { jsx: 'automatic' }, + css: { postcss: { plugins: [tailwind({ base: root })] } }, + server: { host: '127.0.0.1', port: 4186, strictPort: true }, +};