diff --git a/src/langbot/pkg/api/http/service/bot.py b/src/langbot/pkg/api/http/service/bot.py index b497f88a9..9f9e62829 100644 --- a/src/langbot/pkg/api/http/service/bot.py +++ b/src/langbot/pkg/api/http/service/bot.py @@ -190,17 +190,6 @@ class BotService: # TODO: 检查配置信息格式 bot_data['uuid'] = str(uuid.uuid4()) - # bind the most recently updated pipeline if any exist - result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_pipeline.LegacyPipeline) - .order_by(persistence_pipeline.LegacyPipeline.updated_at.desc()) - .limit(1) - ) - pipeline = result.first() - if pipeline is not None: - bot_data['use_pipeline_uuid'] = pipeline.uuid - bot_data['use_pipeline_name'] = pipeline.name - await self.ap.persistence_mgr.execute_async(sqlalchemy.insert(persistence_bot.Bot).values(bot_data)) bot = await self.get_bot(bot_data['uuid']) @@ -219,18 +208,10 @@ class BotService: if 'event_bindings' in update_data: update_data['event_bindings'] = await self._normalize_event_bindings(update_data.get('event_bindings')) - # set use_pipeline_name - if 'use_pipeline_uuid' in update_data: - result = await self.ap.persistence_mgr.execute_async( - sqlalchemy.select(persistence_pipeline.LegacyPipeline).where( - persistence_pipeline.LegacyPipeline.uuid == update_data['use_pipeline_uuid'] - ) - ) - pipeline = result.first() - if pipeline is not None: - update_data['use_pipeline_name'] = pipeline.name - else: - raise Exception('Pipeline not found') + # clear legacy routing fields — routing is now fully managed via event_bindings + update_data.pop('use_pipeline_uuid', None) + update_data.pop('use_pipeline_name', None) + update_data.pop('pipeline_routing_rules', None) await self.ap.persistence_mgr.execute_async( sqlalchemy.update(persistence_bot.Bot).values(update_data).where(persistence_bot.Bot.uuid == bot_uuid) diff --git a/src/langbot/pkg/persistence/alembic/versions/0009_migrate_routing_to_event_bindings.py b/src/langbot/pkg/persistence/alembic/versions/0009_migrate_routing_to_event_bindings.py new file mode 100644 index 000000000..fef63a2ff --- /dev/null +++ b/src/langbot/pkg/persistence/alembic/versions/0009_migrate_routing_to_event_bindings.py @@ -0,0 +1,111 @@ +"""migrate use_pipeline_uuid and pipeline_routing_rules into event_bindings + +Revision ID: 0009_migrate_routing_to_event_bindings +Revises: 0008_agent_product_surface +Create Date: 2026-06-26 +""" + +import json +import uuid + +import sqlalchemy as sa +from alembic import op + +revision = '0009_migrate_routing_to_event_bindings' +down_revision = '0008_agent_product_surface' +depends_on = None + + +def _rule_to_filters(rule: dict) -> list[dict] | None: + """Convert a pipeline_routing_rule to event_binding filters (best effort). + + Rules that don't map cleanly (message_content, message_has_element) are + skipped — callers should handle None as "cannot migrate". + """ + rule_type = rule.get('type') + operator = rule.get('operator', 'eq') + value = rule.get('value', '') + + if rule_type == 'launcher_type': + if value == 'group': + return [{'field': 'group', 'operator': 'neq', 'value': None}] + if value == 'person': + return [{'field': 'group', 'operator': 'eq', 'value': None}] + elif rule_type == 'launcher_id': + return [{'field': 'chat_id', 'operator': operator, 'value': value}] + + return None # message_content / message_has_element: no clean mapping + + +def upgrade() -> None: + bind = op.get_bind() + rows = bind.execute( + sa.text('SELECT uuid, use_pipeline_uuid, pipeline_routing_rules, event_bindings FROM bots') + ).fetchall() + + for bot_uuid, use_pipeline_uuid, routing_rules_raw, event_bindings_raw in rows: + try: + existing = ( + json.loads(event_bindings_raw) if isinstance(event_bindings_raw, str) else (event_bindings_raw or []) + ) + except Exception: + existing = [] + + if existing: + continue # already has event_bindings — skip + + try: + routing_rules = ( + json.loads(routing_rules_raw) if isinstance(routing_rules_raw, str) else (routing_rules_raw or []) + ) + except Exception: + routing_rules = [] + + new_bindings: list[dict] = [] + base_priority = len(routing_rules) + + for i, rule in enumerate(routing_rules): + target_uuid = rule.get('pipeline_uuid', '') + if not target_uuid: + continue + filters = _rule_to_filters(rule) + if filters is None: + continue + new_bindings.append( + { + 'id': str(uuid.uuid4()), + 'event_pattern': 'message.*', + 'target_type': 'pipeline', + 'target_uuid': target_uuid, + 'filters': filters, + 'priority': base_priority - i, + 'enabled': True, + 'description': f'Migrated from routing rule ({rule.get("type")})', + 'order': i, + } + ) + + if use_pipeline_uuid: + new_bindings.append( + { + 'id': str(uuid.uuid4()), + 'event_pattern': 'message.*', + 'target_type': 'pipeline', + 'target_uuid': use_pipeline_uuid, + 'filters': [], + 'priority': 0, + 'enabled': True, + 'description': 'Migrated from default pipeline binding', + 'order': len(new_bindings), + } + ) + + if new_bindings: + bind.execute( + sa.text('UPDATE bots SET event_bindings = :b WHERE uuid = :u'), + {'b': json.dumps(new_bindings, ensure_ascii=False), 'u': bot_uuid}, + ) + + +def downgrade() -> None: + pass # not reversible diff --git a/src/langbot/pkg/platform/botmgr.py b/src/langbot/pkg/platform/botmgr.py index e5f88de2e..244f57c04 100644 --- a/src/langbot/pkg/platform/botmgr.py +++ b/src/langbot/pkg/platform/botmgr.py @@ -192,6 +192,25 @@ class RuntimeBot: return False return False + @classmethod + def _augment_event_data( + cls, + event_data: dict[str, typing.Any], + ) -> dict[str, typing.Any]: + """Inject virtual computed fields to simplify common filter patterns.""" + message_chain = event_data.get('message_chain') + if isinstance(message_chain, list): + text_parts = [ + comp.get('text', '') for comp in message_chain if isinstance(comp, dict) and comp.get('type') == 'Plain' + ] + event_data['message_text'] = ''.join(text_parts) + event_data['message_element_types'] = [ + comp.get('type', '') for comp in message_chain if isinstance(comp, dict) + ] + if 'group' in event_data: + event_data['chat_type'] = 'group' if event_data.get('group') is not None else 'person' + return event_data + @classmethod def _match_event_filters( cls, @@ -203,7 +222,7 @@ class RuntimeBot: if not isinstance(filters, list): return False - event_data = cls._safe_model_dump(event) + event_data = cls._augment_event_data(cls._safe_model_dump(event)) return all( cls._match_event_filter(event_data, event_filter) for event_filter in filters @@ -854,11 +873,8 @@ class RuntimeBot: launcher_id = custom_launcher_id if pipeline_uuid_override is None: - message_text = str(event.message_chain) - element_types = [comp.type for comp in event.message_chain] - pipeline_uuid, routed_by_rule = self.resolve_pipeline_uuid( - launcher_kind, launcher_id, message_text, element_types - ) + pipeline_uuid = None + routed_by_rule = False else: pipeline_uuid = pipeline_uuid_override routed_by_rule = routed_by_event_binding diff --git a/tests/unit_tests/api/http/service/test_bot_service.py b/tests/unit_tests/api/http/service/test_bot_service.py index 6fdc2342f..6ca8075df 100644 --- a/tests/unit_tests/api/http/service/test_bot_service.py +++ b/tests/unit_tests/api/http/service/test_bot_service.py @@ -28,7 +28,7 @@ class _PersistenceManager: return _FakeResult(SimpleNamespace(name='Updated Pipeline')) -async def test_update_bot_copies_input_before_filtering_and_setting_pipeline_name(): +async def test_update_bot_copies_input_before_filtering_legacy_routing_fields(): persistence_mgr = _PersistenceManager() runtime_bot = SimpleNamespace(enable=False) platform_mgr = SimpleNamespace( @@ -46,17 +46,17 @@ async def test_update_bot_copies_input_before_filtering_and_setting_pipeline_nam 'uuid': 'caller-owned-uuid', 'name': 'Test Bot', 'use_pipeline_uuid': 'pipeline-1', + 'pipeline_routing_rules': [{'type': 'launcher_type'}], } await service.update_bot('bot-1', payload) + # caller's dict must not be mutated assert payload == { 'uuid': 'caller-owned-uuid', 'name': 'Test Bot', 'use_pipeline_uuid': 'pipeline-1', + 'pipeline_routing_rules': [{'type': 'launcher_type'}], } - assert persistence_mgr.update_values == { - 'name': 'Test Bot', - 'use_pipeline_uuid': 'pipeline-1', - 'use_pipeline_name': 'Updated Pipeline', - } + # legacy routing fields are stripped; only name is persisted + assert persistence_mgr.update_values == {'name': 'Test Bot'} diff --git a/tests/unit_tests/api/service/test_bot_service.py b/tests/unit_tests/api/service/test_bot_service.py index 3fe5fb336..3632d4eb7 100644 --- a/tests/unit_tests/api/service/test_bot_service.py +++ b/tests/unit_tests/api/service/test_bot_service.py @@ -395,59 +395,6 @@ class TestBotServiceCreateBot: assert bot_uuid is not None assert len(bot_uuid) == 36 # UUID format - async def test_create_bot_sets_default_pipeline(self): - """Sets default pipeline when one exists.""" - # Setup - ap = SimpleNamespace() - ap.persistence_mgr = SimpleNamespace() - ap.instance_config = SimpleNamespace() - ap.instance_config.data = {'system': {'limitation': {'max_bots': -1}}} - ap.platform_mgr = SimpleNamespace() - ap.platform_mgr.load_bot = AsyncMock() - - # Mock default pipeline - mock_pipeline = SimpleNamespace() - mock_pipeline.uuid = 'default-pipeline-uuid' - mock_pipeline.name = 'Default Pipeline' - pipeline_result = Mock() - pipeline_result.first = Mock(return_value=mock_pipeline) - - # Mock bot after insert - bot_result = Mock() - bot_result.first = Mock(return_value=_create_mock_bot()) - - call_count = 0 - - async def mock_execute(query): - nonlocal call_count - call_count += 1 - if call_count == 1: - return pipeline_result # Check default pipeline - elif call_count == 2: - return Mock() # Insert - return bot_result # Get bot - - ap.persistence_mgr.execute_async = AsyncMock(side_effect=mock_execute) - ap.persistence_mgr.serialize_model = Mock( - return_value={ - 'uuid': 'new-uuid', - 'name': 'New Bot', - 'use_pipeline_uuid': 'default-pipeline-uuid', - 'use_pipeline_name': 'Default Pipeline', - } - ) - - service = BotService(ap) - - # Execute - bot_data = {'name': 'New Bot', 'adapter': 'telegram', 'adapter_config': {}} - bot_uuid = await service.create_bot(bot_data) - - # Verify - pipeline uuid and name were set - assert 'use_pipeline_uuid' in bot_data - assert 'use_pipeline_name' in bot_data - assert bot_uuid is not None # Verify UUID was returned - class TestBotServiceUpdateBot: """Tests for update_bot method.""" @@ -481,64 +428,6 @@ class TestBotServiceUpdateBot: assert update_params['name'] == 'Updated Name' assert 'should-be-removed' not in update_params.values() - async def test_update_bot_pipeline_not_found_raises(self): - """Raises Exception when updating with nonexistent pipeline UUID.""" - # Setup - ap = SimpleNamespace() - ap.persistence_mgr = SimpleNamespace() - - # Mock pipeline query returns None - pipeline_result = Mock() - pipeline_result.first = Mock(return_value=None) - ap.persistence_mgr.execute_async = AsyncMock(return_value=pipeline_result) - - service = BotService(ap) - - # Execute & Verify - with pytest.raises(Exception, match='Pipeline not found'): - await service.update_bot('test-uuid', {'use_pipeline_uuid': 'nonexistent-pipeline'}) - - async def test_update_bot_sets_pipeline_name(self): - """Sets use_pipeline_name when updating use_pipeline_uuid.""" - # Setup - ap = SimpleNamespace() - ap.persistence_mgr = SimpleNamespace() - ap.platform_mgr = SimpleNamespace() - ap.platform_mgr.remove_bot = AsyncMock() - - # Mock pipeline query - mock_pipeline = SimpleNamespace() - mock_pipeline.name = 'Updated Pipeline' - pipeline_result = Mock() - pipeline_result.first = Mock(return_value=mock_pipeline) - - call_count = 0 - - async def mock_execute(query): - nonlocal call_count - call_count += 1 - if call_count == 1: - return pipeline_result - return Mock() - - ap.persistence_mgr.execute_async = AsyncMock(side_effect=mock_execute) - ap.sess_mgr = SimpleNamespace() - ap.sess_mgr.session_list = [] - - service = BotService(ap) - service.get_bot = AsyncMock(return_value={'uuid': 'test-uuid'}) - - runtime_bot = SimpleNamespace() - runtime_bot.enable = False - ap.platform_mgr.load_bot = AsyncMock(return_value=runtime_bot) - - # Execute - await service.update_bot('test-uuid', {'use_pipeline_uuid': 'pipeline-uuid'}) - - update_params = ap.persistence_mgr.execute_async.await_args_list[1].args[0].compile().params - assert update_params['use_pipeline_uuid'] == 'pipeline-uuid' - assert update_params['use_pipeline_name'] == 'Updated Pipeline' - class TestBotServiceEventBindings: """Tests for EBA event binding validation and persistence.""" diff --git a/web/package.json b/web/package.json index 3c3903d4b..7743c1523 100644 --- a/web/package.json +++ b/web/package.json @@ -58,6 +58,7 @@ "axios": "^1.16.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "highlight.js": "^11.11.1", "i18next": "^25.1.2", "i18next-browser-languagedetector": "^8.1.0", @@ -66,6 +67,7 @@ "lucide-react": "^0.507.0", "postcss": "^8.5.10", "qrcode": "^1.5.4", + "radix-ui": "^1.6.0", "react": "19.2.1", "react-dom": "19.2.1", "react-hook-form": "^7.56.3", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index ab0954fe5..165f3ce70 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -105,6 +105,9 @@ dependencies: clsx: specifier: ^2.1.1 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) highlight.js: specifier: ^11.11.1 version: 11.11.1 @@ -129,6 +132,9 @@ dependencies: qrcode: specifier: ^1.5.4 version: 1.5.4 + radix-ui: + specifier: ^1.6.0 + version: 1.6.0(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1) react: specifier: 19.2.1 version: 19.2.1 @@ -546,10 +552,66 @@ packages: resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} dev: false + /@radix-ui/number@1.1.2: + resolution: {integrity: sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==} + dev: false + /@radix-ui/primitive@1.1.3: resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} dev: false + /@radix-ui/primitive@1.1.4: + resolution: {integrity: sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==} + dev: false + + /@radix-ui/react-accessible-icon@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-TraSwZUqTcVbiDV2/RXzAXC7aeVVXchq0daPFZE7zAxYFaMzjOUggLOfQH9KFLgRizuwVKZO/crveV1eeO3/ZQ==} + 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-visually-hidden': 1.2.6(@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-accordion@1.2.14(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-iE8YB9nmTBH8zd73ofBISZ8JCzgMoMkATJr7qDwa6u5F1+7mTM81V6fa71jgZ65rpjVpecDf1vSnwIFP9Ly1zw==} + 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.4 + '@radix-ui/react-collapsible': 1.1.14(@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.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.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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.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.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: @@ -575,6 +637,50 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-alert-dialog@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-563ygGeyWPrxyVCNp7OV4rE2aIXhFPknpFyo4wbDlcyMMPZ6ySh+zC5WTvY0ZFLgPTg/QB6tA8PyDQyJ2b4cPg==} + 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.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-dialog': 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-primitive': 2.1.6(@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-arrow@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-j2VTDz1vgCsmuG0k5lBfOcM8n5JPFqZBcMryasFjHYMhwxYL5SRUV5lMSUpRdNtw3D/Sv8pzJtrlAgkssYSsQQ==} + 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.6(@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-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==} peerDependencies: @@ -595,6 +701,26 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-aspect-ratio@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-kbI7NrqhDeuytYrq7JjAsoXczvL8wgj2tc1MyaYWm+50bMKHCHQtVWCryslx4cCpmCTTkBcwQckE4CmmGV2haQ==} + 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.6(@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: @@ -619,6 +745,30 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-avatar@1.2.0(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-am/CwltXtmtdtP+5FbYblYDnMa/zuKcMJP1i3/SJMDXXfj2mG+BTqLH2wucqeyyiQMursUtg/5cK+Nh2pCaSOA==} + 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.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.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-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: @@ -646,6 +796,33 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-checkbox@1.3.5(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-pREzrmNnVwGvYaBoM64huTRK7B3lrTRuwj8A9nwhPiEtMb+yudiWh6zWAqEtP0Dzd5+iBa1Ki7V1pCxV8ExMdA==} + 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.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-presence': 1.1.6(@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.6(@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.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.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-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: @@ -673,6 +850,56 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-collapsible@1.1.14(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-9bT+FvifX1FK2Mj6UEsTdyu0cN3JaA3KdfhaBao+ONrYFy/pyOy3TU1TNw7iOk1o+0hOEq67RojlUUmoFGwxyA==} + 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.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-presence': 1.1.6(@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.6(@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.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.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-collection@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-IVVz4EvBcKjrzKgof714qDnz/SzQAkLA2Emh5edlHbgcE6fNd3Un6CJLlaYcnm8N4JmAtzQgse4dOKxcD2yc9g==} + 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.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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.0(@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-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: @@ -709,6 +936,19 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==} + 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-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: @@ -734,6 +974,30 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-context-menu@2.3.1(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-XbrxS68W5dyiE4fAb96yvJwSVU5x66B20A99sD5Mk3xSWK/LqeOnx6TZnim1KieMjXS/CTFq8reOAjWxas2G8Q==} + 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.4 + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-menu': 2.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-primitive': 2.1.6(@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.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-context@1.1.2(@types/react@19.2.10)(react@19.2.1): resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} peerDependencies: @@ -760,6 +1024,19 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-context@1.1.4(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==} + 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-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==} peerDependencies: @@ -793,6 +1070,39 @@ packages: react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.1) dev: false + /@radix-ui/react-dialog@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-TDTYmpdq8dI2+Xgvgj9AJ8Ghqq+Eph/TRVEdaFQPDItIY+6QSkU7MJMeevw1568Yw/2Ijz8BTphPSP2XejKphw==} + 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.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@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.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-focus-scope': 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-id': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-portal': 1.1.12(@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.6(@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.6(@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.0(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 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) + 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: @@ -806,6 +1116,19 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-direction@1.1.2(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==} + 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: @@ -830,6 +1153,30 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-dismissable-layer@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-2v+zNAWWe0ySxgC0D0yeXMPQ23xZVgXZTerTz+JKlmdRj6gfTqmCcR29jb6d290DezXPGgruHWDX/vYUebtErg==} + 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.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-escape-keydown': 1.1.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.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: @@ -856,6 +1203,32 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-dropdown-menu@2.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-PZGV82gFk0WltDRI//SsG28ZIjlo9ANTmoNYg0jLNzXXiDsAy5PkOOYQaVD1pPxY6t7gxffb1QMD6qaUvsBZdw==} + 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.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-menu': 2.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-primitive': 2.1.6(@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.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-focus-guards@1.1.3(@types/react@19.2.10)(react@19.2.1): resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} peerDependencies: @@ -869,6 +1242,41 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-focus-guards@1.1.4(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==} + 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-scope@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-Fas/lXQqhVvqwAb64s5RFeHiHYElZ6SUQbZaNd6EkfhP/Al7wTIQ9WIR4QVX475tlu5yFCEdDcJH6/UwsZjMWw==} + 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.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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.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-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: @@ -891,6 +1299,31 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-form@0.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-1NfuvctVtX4sU3Mmq/IdrR8UunxiCMiVg3A5UENKhFzxUBeOyaQQ+lmaQaV7Tc8cqvBKsJL3/KGBsixK0D8WFg==} + 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.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-label': 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-primitive': 2.1.6(@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-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: @@ -919,6 +1352,34 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-hover-card@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-GjZQIEANVkuuWeztlKz6QEHe31ZX2iDfHzcTMCQVZXC0JyQrgfKWSC+LOOEw6aVV64zyjzobIzSA4AU4eKWrHA==} + 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.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@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.1(@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.12(@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.6(@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.6(@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.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-id@1.1.1(@types/react@19.2.10)(react@19.2.1): resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} peerDependencies: @@ -933,6 +1394,40 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-id@1.1.2(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==} + 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.2(@types/react@19.2.10)(react@19.2.1) + '@types/react': 19.2.10 + react: 19.2.1 + dev: false + + /@radix-ui/react-label@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-ib0zvq2ZsAqKm5tRnqGJn3vOxSgIts5ToxsXT0q1S/GfLD1Zj7UOEnkw8u2w6sRmn47djpQWuSU1DCL1R29/yw==} + 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.6(@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-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==} peerDependencies: @@ -990,6 +1485,163 @@ packages: react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.1) dev: false + /@radix-ui/react-menu@2.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-lj8Rxjtn6zJq1oSbE/uDtAwCbB9BnxgHD+8MwJMuTh6u1dPamYhW9iuELr/Z8d0D/UysFblYYHeBPwi7T4k0YQ==} + 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.4 + '@radix-ui/react-collection': 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-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@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.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-focus-scope': 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-id': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-popper': 1.3.1(@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.12(@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.6(@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.6(@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.13(@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.0(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.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-menubar@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-hX7EGx/oFq6DPY27GQuP/2wP48GHf5LG6r06VgNJlG+znmDS8OfopZcRcGly3L4lsB9FqpmLx6JQSE9P3BUpyw==} + 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.4 + '@radix-ui/react-collection': 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-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-menu': 2.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-primitive': 2.1.6(@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.13(@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.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-navigation-menu@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-nJ0SkrSQgudyYhMiYeHA1ayLVuduEJCFLan1RZZN7c9kqzzCFLaU9kuy81uNtqzweM9YaQPgWzxi9MwQ9jZ04g==} + 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.4 + '@radix-ui/react-collection': 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-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@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.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-presence': 1.1.6(@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.6(@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.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-visually-hidden': 1.2.6(@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.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-GHkcJ+WVj91At+OvUVTD4R3W0/wxw9t/sG5xFUBYXaCbtWiooZX5Md376QjJqgH4VsVyXrbVNHO2O4NYcmjfVg==} + 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.2 + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-collection': 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-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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.13(@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.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.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-password-toggle-field@0.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-fVuA82u0b/fClpbEJv8yp1nU9eSvoSEOERsU/hhf3FXGPIvkmE7oEaHEu8poowoXO39/Va7zq2E0TUcYr1dBRg==} + 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.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-is-hydrated': 0.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-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: @@ -1024,6 +1676,40 @@ packages: react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.1) dev: false + /@radix-ui/react-popover@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-/YSAOdJ7YJvdn7bn5sdSx2egW+SKY+u7O5RyAVs94Ymrg2fg5QTSFPMRkzvhGyFuE4/qsmPBdrwYoZMZh/4f+g==} + 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.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@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.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-focus-scope': 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-id': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-popper': 1.3.1(@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.12(@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.6(@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.6(@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.0(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 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) + 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-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==} peerDependencies: @@ -1053,6 +1739,56 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-popper@1.3.1(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-bhnq/0DEPTi2lsOD3J5rTL65qUKHbKbhqHsmN9TMiclSXpipi651ooUKPPp6G5lF/WiHBdn1s0Wuqsn+myVAvw==} + 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.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.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-rect': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/rect': 1.1.2 + '@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-portal@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-m309havGzsjLHHaIX50G5PlvRs3xkgPCsGk/5PTvYm8D5q33yG0J7w/712PTOhid7NTaFETtnSXjngHQavvhVw==} + 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.6(@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.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-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==} peerDependencies: @@ -1095,6 +1831,26 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-presence@1.1.6(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==} + 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-use-layout-effect': 1.1.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-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: @@ -1135,6 +1891,47 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-primitive@2.1.6(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-wetd0QI77DbvrPpTAvH1SqOxsYF2wZe5TNxqwOd5Ty4XDpV3dpV0s8K/1MGMJBeY5o7lg8ub5VIt1Ub+yVen6g==} + 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.0(@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.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-JYzEg60lk79PwKM27WZyKd7PW8O4OM5jOaFfRPfOyeXmMw7tLJh5kSj+CEjVTehszuwml/AdCzPGMXBTGf4BBw==} + 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.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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: @@ -1156,6 +1953,35 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-radio-group@1.4.1(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-/SSxZdKEo2Eo29FFRKd06EfFDYp8HryKg0WYg7QLXaydPzl52YfSvCH2a3QDBRdtcuwACroJT8UVjQVgOJ7P9A==} + 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.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-presence': 1.1.6(@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.6(@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.13(@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.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.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.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: @@ -1184,6 +2010,34 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-roving-focus@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-9gkwneI0guf8JDmrFxPjJF6Ozzgioyw+/lonYNCwefS9ZHA05er0BVHiXr+LbWGHxUfczvMY6G1oiZZi1VzjRw==} + 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.4 + '@radix-ui/react-collection': 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-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 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-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: @@ -1212,6 +2066,34 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-scroll-area@1.2.12(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-xuafVzQiTCLsyEjakowTdG3OgTXsmO7IdCiO77otIa+z44xoLNs9Do5eg7POFumIOCjtG6djfm6RKUKpUa/csA==} + 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.2 + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-presence': 1.1.6(@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.6(@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.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.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-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==} peerDependencies: @@ -1252,6 +2134,67 @@ packages: react-remove-scroll: 2.7.2(@types/react@19.2.10)(react@19.2.1) dev: false + /@radix-ui/react-select@2.3.1(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-w6eDvY78LE9ZUiNnXCA1QVK8RYN7k9galFv09kjVydJqBAgHd7Y9A6h0UJ/6DCZNGZMZrB2ohcSW1Bo9d8+wWA==} + 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.2 + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-collection': 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-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@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.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-focus-scope': 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-id': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-popper': 1.3.1(@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.12(@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.6(@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.6(@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.0(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-visually-hidden': 1.2.6(@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.10(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-Y6K6jLQCVfCnTL2MEtGxDLffkhNfEfHsEg3Wa8JU+IWdn3EWbLXd3OuOfQRN7p/W/cUce1WyTk3QeuAoDBzN9g==} + 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.6(@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-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: @@ -1272,6 +2215,36 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-slider@1.4.1(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-r91WSpQucNGFKAIxT8FT0H0zyjd5tJlqObLp7LOMV4z49KoDCwjy01w3vDOU4e1wxhF9IgjYco7SB6byOW7Buw==} + 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.2 + '@radix-ui/primitive': 1.1.4 + '@radix-ui/react-collection': 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-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.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-slot@1.2.3(@types/react@19.2.10)(react@19.2.1): resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} peerDependencies: @@ -1300,6 +2273,20 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-slot@1.3.0(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==} + 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.3(@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: @@ -1326,6 +2313,32 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-switch@1.3.1(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-55bQtCnOB0BohomSHi6qvQXpJEEqUGDm6hRrM0Bph5OXwhSegqkd8IqgBAQkM1IlgUlWZIxpxRcpOEfRIgimyw==} + 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.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.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.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: @@ -1353,6 +2366,64 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-tabs@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-kxc9gI6/HfcU4nfMMVS3AmQK414kbU1IE6UCJmMmxjhO3cRPXOyYnmvyKD+ODt7q56nRq9l7Wovi6uaGwKgMlg==} + 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.4 + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-presence': 1.1.6(@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.6(@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.13(@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.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-toast@1.2.17(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-uL4kyyWy000pPL43fGGCV5qT6ZchCWEQZOSlkYiPwPt8Hy1iW38RjeptIvz1/SZesrW6Vn58Ct3sV7tfEfiAbw==} + 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.4 + '@radix-ui/react-collection': 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-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@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.12(@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.6(@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.6(@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.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-visually-hidden': 1.2.6(@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-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: @@ -1379,6 +2450,32 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-toggle-group@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-Xb9PLtlvU66F36LiKba6dFswu6V2mDkgidO4fNSbQHQwmZ9ObxMIO17MN/LJ4aWJecVuSVLAHPZjyeMzJrgeiA==} + 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.4 + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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.13(@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.12(@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.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-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: @@ -1401,6 +2498,85 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-toggle@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-AsAVsYNZIlRBsci7BhE+QyQeKd1h6TffJYt+lF0QQkd5OpQ3klfIByPsCb4G0h/Fq6PJwh1FYNluzBFYzhk4+w==} + 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.4 + '@radix-ui/react-primitive': 2.1.6(@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.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-toolbar@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-Za1l4f6fzTkGgz/iynAMN8iaqiKff2wm2/QwiLmHPtDQreWEBrvSimgQFIekxMUdRPhILM7xdIXxuS/o/DGZag==} + 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.4 + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.6(@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.13(@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.10(@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.13(@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-tooltip@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-NlNe8D0dWEpVfXFli90IO6X07Josx/b1iu98tDnx9Xv0HT4wLIL+m2VOheMHhK7qbp2HoTBqALEFzGyZs/levw==} + 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.4 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@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.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-popper': 1.3.1(@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.12(@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.6(@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.6(@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.0(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-visually-hidden': 1.2.6(@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-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: @@ -1445,6 +2621,19 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-use-callback-ref@1.1.2(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==} + 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-controllable-state@1.2.2(@types/react@19.2.10)(react@19.2.1): resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} peerDependencies: @@ -1460,6 +2649,21 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-use-controllable-state@1.2.3(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==} + 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.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 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-use-effect-event@0.0.2(@types/react@19.2.10)(react@19.2.1): resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} peerDependencies: @@ -1474,6 +2678,20 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-use-effect-event@0.0.3(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==} + 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.2(@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.1(@types/react@19.2.10)(react@19.2.1): resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} peerDependencies: @@ -1488,6 +2706,20 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-use-escape-keydown@1.1.2(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw==} + 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.2(@types/react@19.2.10)(react@19.2.1) + '@types/react': 19.2.10 + 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: @@ -1502,6 +2734,19 @@ packages: use-sync-external-store: 1.6.0(react@19.2.1) dev: false + /@radix-ui/react-use-is-hydrated@0.1.1(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==} + 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.1(@types/react@19.2.10)(react@19.2.1): resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} peerDependencies: @@ -1515,6 +2760,19 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==} + 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.1(@types/react@19.2.10)(react@19.2.1): resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} peerDependencies: @@ -1528,6 +2786,19 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-use-previous@1.1.2(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==} + 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-rect@1.1.1(@types/react@19.2.10)(react@19.2.1): resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} peerDependencies: @@ -1542,6 +2813,20 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-use-rect@1.1.2(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==} + 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.2 + '@types/react': 19.2.10 + 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==} peerDependencies: @@ -1556,6 +2841,20 @@ packages: react: 19.2.1 dev: false + /@radix-ui/react-use-size@1.1.2(@types/react@19.2.10)(react@19.2.1): + resolution: {integrity: sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==} + 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.2(@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.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: @@ -1576,10 +2875,34 @@ packages: react-dom: 19.2.1(react@19.2.1) dev: false + /@radix-ui/react-visually-hidden@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-jCE0WljWifTI4niIMCll06kGpsJTAPiZVU9H4WR1N6qW7At9ystHbN7dDB+we2xH535roFHj7qKS+RGj0FMDWQ==} + 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.6(@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.2: + resolution: {integrity: sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==} + dev: false + /@rolldown/binding-android-arm64@1.0.3: resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2503,6 +3826,23 @@ 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): + 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.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-id': 1.1.1(@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) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + dev: false + /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -5057,6 +6397,80 @@ packages: yargs: 15.4.1 dev: false + /radix-ui@1.6.0(@types/react-dom@19.2.3)(@types/react@19.2.10)(react-dom@19.2.1)(react@19.2.1): + resolution: {integrity: sha512-EUEC70O03EgxWMP5aoqfBZ6iLC5bczFagGy7zhSYRt8o5DP7IWNiP3ywetse3L9b8843ExB0OGWZvgbYVJuNeg==} + 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.4 + '@radix-ui/react-accessible-icon': 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-accordion': 1.2.14(@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.17(@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.10(@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.10(@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.0(@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.5(@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.14(@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.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.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context': 1.1.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-context-menu': 2.3.1(@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.17(@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.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.13(@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.18(@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.4(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-focus-scope': 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-form': 0.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-hover-card': 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-label': 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-menu': 2.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-menubar': 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-navigation-menu': 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-one-time-password-field': 0.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-password-toggle-field': 0.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-popover': 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-popper': 1.3.1(@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.12(@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.6(@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.6(@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.10(@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.1(@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.13(@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.12(@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.1(@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.10(@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.1(@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.0(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-switch': 1.3.1(@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.15(@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.17(@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.12(@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.13(@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.13(@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.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.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-escape-keydown': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.10)(react@19.2.1) + '@radix-ui/react-visually-hidden': 1.2.6(@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 + /react-dom@19.2.1(react@19.2.1): resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} peerDependencies: diff --git a/web/src/app/home/bots/components/bot-form/BotForm.tsx b/web/src/app/home/bots/components/bot-form/BotForm.tsx index 8c4a79d09..eb7bea89b 100644 --- a/web/src/app/home/bots/components/bot-form/BotForm.tsx +++ b/web/src/app/home/bots/components/bot-form/BotForm.tsx @@ -1,9 +1,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import i18n from 'i18next'; -import { - IChooseAdapterEntity, - IPipelineEntity, -} from '@/app/home/bots/components/bot-form/ChooseEntity'; +import { IChooseAdapterEntity } from '@/app/home/bots/components/bot-form/ChooseEntity'; import { DynamicFormItemConfig, getDefaultValues, @@ -17,7 +14,6 @@ import { systemInfo } from '@/app/infra/http'; import { Agent, Bot } from '@/app/infra/entities/api'; import { getAdapterDocUrl } from '@/app/infra/entities/adapter-docs'; import { ExternalLink } from 'lucide-react'; -import RoutingRulesEditor from './RoutingRulesEditor'; import EventBindingsEditor from './EventBindingsEditor'; import { zodResolver } from '@hookform/resolvers/zod'; @@ -66,29 +62,6 @@ const getFormSchema = (t: (key: string) => string) => adapter: z.string().min(1, { message: t('bots.adapterRequired') }), adapter_config: z.record(z.string(), z.any()), enable: z.boolean().optional(), - use_pipeline_uuid: z.string().optional(), - pipeline_routing_rules: z - .array( - z.object({ - type: z.enum([ - 'launcher_type', - 'launcher_id', - 'message_content', - 'message_has_element', - ]), - operator: z.enum([ - 'eq', - 'neq', - 'contains', - 'not_contains', - 'starts_with', - 'regex', - ]), - value: z.string(), - pipeline_uuid: z.string(), - }), - ) - .optional(), event_bindings: z .array( z.object({ @@ -128,8 +101,6 @@ export default function BotForm({ adapter: '', adapter_config: {}, enable: true, - use_pipeline_uuid: '', - pipeline_routing_rules: [], event_bindings: [], }, }); @@ -154,9 +125,6 @@ export default function BotForm({ Record >({}); - const [pipelineNameList, setPipelineNameList] = useState( - [], - ); const [agentNameList, setAgentNameList] = useState([]); const [dynamicFormConfigList, setDynamicFormConfigList] = useState< @@ -200,8 +168,6 @@ export default function BotForm({ adapter: val.adapter, adapter_config: val.adapter_config, enable: val.enable, - use_pipeline_uuid: val.use_pipeline_uuid || '', - pipeline_routing_rules: val.pipeline_routing_rules || [], event_bindings: val.event_bindings || [], }); handleAdapterSelect(val.adapter); @@ -231,17 +197,6 @@ export default function BotForm({ } async function initBotFormComponent() { - const pipelinesRes = await httpClient.getPipelines(); - setPipelineNameList( - pipelinesRes.pipelines.map((item) => { - return { - label: item.name, - value: item.uuid ?? '', - emoji: item.emoji, - }; - }), - ); - const agentsRes = await httpClient.getAgents(); setAgentNameList(agentsRes.agents); @@ -331,8 +286,6 @@ export default function BotForm({ name: bot.name, adapter_config: bot.adapter_config, enable: bot.enable ?? true, - use_pipeline_uuid: bot.use_pipeline_uuid ?? '', - pipeline_routing_rules: bot.pipeline_routing_rules ?? [], event_bindings: bot.event_bindings ?? [], webhook_full_url: runtimeValues?.webhook_full_url as | string @@ -377,8 +330,6 @@ export default function BotForm({ adapter: form.getValues().adapter, adapter_config: form.getValues().adapter_config, enable: form.getValues().enable, - use_pipeline_uuid: form.getValues().use_pipeline_uuid, - pipeline_routing_rules: form.getValues().pipeline_routing_rules ?? [], event_bindings: form.getValues().event_bindings ?? [], }; httpClient @@ -468,79 +419,7 @@ export default function BotForm({ - {/* Card 2: Pipeline Binding (edit mode only) */} - {initBotId && ( - - - {t('bots.routingConnection')} - - {t('bots.routingConnectionDescription')} - - - - ( - - {t('bots.bindPipeline')} - - - - - )} - /> - - {/* Pipeline Routing Rules */} - - - - )} - - {/* Card 3: Event Orchestration (edit mode only) */} + {/* Card 2: Event Orchestration (edit mode only) */} {initBotId && ( diff --git a/web/src/app/home/bots/components/bot-form/EventBindingsEditor.tsx b/web/src/app/home/bots/components/bot-form/EventBindingsEditor.tsx index d3996df46..15f43c255 100644 --- a/web/src/app/home/bots/components/bot-form/EventBindingsEditor.tsx +++ b/web/src/app/home/bots/components/bot-form/EventBindingsEditor.tsx @@ -1,29 +1,120 @@ 'use client'; -import { useMemo } from 'react'; +import { useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { UseFormReturn } from 'react-hook-form'; -import { Ban, Bot, Plus, Trash2, Workflow } from 'lucide-react'; +import { + ArrowRight, + Ban, + Bot, + Check, + ChevronDown, + ChevronRight, + ChevronsUpDown, + GripVertical, + Plus, + Trash2, + Workflow, +} from 'lucide-react'; import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Switch } from '@/components/ui/switch'; -import { FormLabel } from '@/components/ui/form'; +import { Badge } from '@/components/ui/badge'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from '@/components/ui/command'; import { Select, SelectContent, SelectItem, - SelectSeparator, SelectTrigger, SelectValue, } from '@/components/ui/select'; +import { + DndContext, + DragOverlay, + closestCenter, + PointerSensor, + KeyboardSensor, + useSensor, + useSensors, + DragEndEvent, + DragStartEvent, +} from '@dnd-kit/core'; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + useSortable, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { Input } from '@/components/ui/input'; import { EventBinding, Agent, AgentKind } from '@/app/infra/entities/api'; +export const PIPELINE_DISCARD = '__discard__'; + +// ── types ───────────────────────────────────────────────────────────────────── + interface EventBindingsEditorProps { form: UseFormReturn; supportedEvents: string[]; agentOptions: Agent[]; } +type FilterField = + | 'chat_type' + | 'chat_id' + | 'message_text' + | 'message_element_types'; +type FilterOperator = + | 'eq' + | 'neq' + | 'contains' + | 'not_contains' + | 'starts_with' + | 'regex'; + +interface FilterRow { + field: FilterField; + operator: FilterOperator; + value: string; +} + +const OPERATORS_BY_FIELD: Record = { + chat_type: ['eq', 'neq'], + chat_id: ['eq', 'neq', 'contains', 'not_contains', 'regex'], + message_text: [ + 'contains', + 'not_contains', + 'eq', + 'neq', + 'starts_with', + 'regex', + ], + message_element_types: ['contains', 'not_contains'], +}; + +const ELEMENTS = [ + 'Image', + 'Voice', + 'File', + 'Forward', + 'Face', + 'At', + 'AtAll', + 'Quote', +]; + const DEFAULT_EVENTS = [ 'message.received', 'feedback.received', @@ -34,51 +125,540 @@ const DEFAULT_EVENTS = [ 'platform.specific', ]; -function isMessageEventPattern(pattern: string): boolean { - return pattern === 'message.*' || pattern.startsWith('message.'); +// ── helpers ─────────────────────────────────────────────────────────────────── + +function isMessageEventPattern(p: string) { + return p === 'message.*' || p.startsWith('message.'); } -function eventPatternCovers( - supportedPattern: string, - bindingPattern: string, -): boolean { - if (supportedPattern === '*') return true; - if (supportedPattern === bindingPattern) return true; - if (bindingPattern === '*') return false; - if (supportedPattern.endsWith('.*')) { - const namespace = supportedPattern.replace('.*', ''); - return ( - bindingPattern === `${namespace}.*` || - bindingPattern.startsWith(`${namespace}.`) - ); +function eventPatternCovers(sup: string, bind: string) { + if (sup === '*') return true; + if (sup === bind) return true; + if (bind === '*') return false; + if (sup.endsWith('.*')) { + const ns = sup.replace('.*', ''); + return bind === `${ns}.*` || bind.startsWith(`${ns}.`); } return false; } -function agentSupportsEventPattern( - agent: Agent, - bindingPattern: string, -): boolean { +function agentSupportsEventPattern(agent: Agent, pattern: string) { const patterns = agent.supported_event_patterns ?? agent.capability?.supported_event_patterns ?? ['*']; - return patterns.some((pattern) => - eventPatternCovers(pattern, bindingPattern), + return patterns.some((p) => eventPatternCovers(p, pattern)); +} + +function eventNamespaces(events: string[]) { + const ns = new Set(); + events.forEach((e) => { + const n = e.split('.')[0]; + if (n) ns.add(`${n}.*`); + }); + return Array.from(ns).sort(); +} + +function targetLabel(agent: Agent) { + return `${agent.emoji ? `${agent.emoji} ` : ''}${agent.name}`; +} + +// ── target combobox (type + target merged, with search + groups) ─────────────── + +// Encoded value: "discard", "agent:", "pipeline:" +function encodeTarget(type: EventBinding['target_type'], uuid: string): string { + return type === 'discard' ? 'discard' : `${type}:${uuid}`; +} +function decodeTarget(val: string): { + type: EventBinding['target_type']; + uuid: string; +} { + if (val === 'discard') return { type: 'discard', uuid: '' }; + const sep = val.indexOf(':'); + return { + type: val.slice(0, sep) as EventBinding['target_type'], + uuid: val.slice(sep + 1), + }; +} + +function TargetCombobox({ + binding, + agentOptions, + onUpdate, +}: { + binding: EventBinding; + agentOptions: Agent[]; + onUpdate: (patch: Partial) => void; +}) { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + const pipelineAllowed = isMessageEventPattern(binding.event_pattern); + const targetType = binding.target_type || 'agent'; + + const current = + targetType === 'discard' + ? encodeTarget('discard', '') + : binding.target_uuid + ? encodeTarget(targetType, binding.target_uuid) + : ''; + + const agents = agentOptions.filter( + (a) => + a.kind === 'agent' && agentSupportsEventPattern(a, binding.event_pattern), + ); + const pipelines = pipelineAllowed + ? agentOptions.filter((a) => a.kind === 'pipeline') + : []; + + function currentLabel() { + if (targetType === 'discard') + return ( + + + {t('bots.targetDiscard')} + + ); + const agent = agentOptions.find((a) => a.uuid === binding.target_uuid); + if (agent) + return ( + + {agent.kind === 'pipeline' ? ( + + ) : ( + + )} + {targetLabel(agent)} + + ); + return ( + {t('bots.selectTarget')} + ); + } + + function select(val: string) { + const { type, uuid } = decodeTarget(val); + onUpdate({ target_type: type, target_uuid: uuid }); + setOpen(false); + } + + return ( + + + + + + + + + {t('bots.noTargetFound')} + {agents.length > 0 && ( + + {agents.map((a) => ( + select(encodeTarget('agent', a.uuid || ''))} + > + + {targetLabel(a)} + {current === encodeTarget('agent', a.uuid || '') && ( + + )} + + ))} + + )} + {pipelines.length > 0 && ( + + {pipelines.map((a) => ( + + select(encodeTarget('pipeline', a.uuid || '')) + } + > + + {targetLabel(a)} + {current === encodeTarget('pipeline', a.uuid || '') && ( + + )} + + ))} + + )} + + + select('discard')} + > + + {t('bots.targetDiscard')} + {current === 'discard' && ( + + )} + + + + + + ); } -function eventNamespaces(events: string[]): string[] { - const namespaces = new Set(); - for (const event of events) { - const namespace = event.split('.')[0]; - if (namespace) namespaces.add(`${namespace}.*`); +// ── filter conditions panel ─────────────────────────────────────────────────── + +function FilterConditionsPanel({ + filters, + onChange, +}: { + filters: FilterRow[]; + onChange: (f: FilterRow[]) => void; +}) { + const { t } = useTranslation(); + + function add() { + onChange([ + ...filters, + { field: 'chat_type', operator: 'eq', value: 'person' }, + ]); } - return Array.from(namespaces).sort(); + + function update(i: number, patch: Partial) { + const next = [...filters]; + next[i] = { ...next[i], ...patch }; + if ('field' in patch) { + next[i].operator = OPERATORS_BY_FIELD[patch.field as FilterField][0]; + next[i].value = ''; + } + onChange(next); + } + + function remove(i: number) { + const next = [...filters]; + next.splice(i, 1); + onChange(next); + } + + return ( +
+ {filters.length === 0 && ( +

+ {t('bots.conditionsEmpty')} +

+ )} + {filters.map((f, i) => ( +
+ + + + + {f.field === 'chat_type' ? ( + + ) : f.field === 'message_element_types' ? ( + + ) : ( + update(i, { value: e.target.value })} + /> + )} + + +
+ ))} + +
+ ); } -function targetLabel(agent: Agent): string { - return `${agent.emoji ? `${agent.emoji} ` : ''}${agent.name}`; +// ── single binding card ─────────────────────────────────────────────────────── + +interface BindingCardProps { + binding: EventBinding; + globalIndex: number; + eventOptions: string[]; + agentOptions: Agent[]; + expandedIds: Set; + onToggleExpand: (id: string) => void; + onUpdate: (globalIndex: number, patch: Partial) => void; + onRemove: (globalIndex: number) => void; + dragHandleProps?: Record; + isOverlay?: boolean; } +function BindingCardContent({ + binding, + globalIndex, + eventOptions, + agentOptions, + expandedIds, + onToggleExpand, + onUpdate, + onRemove, + dragHandleProps, +}: BindingCardProps) { + const { t } = useTranslation(); + const isEnabled = binding.enabled ?? true; + const id = String(binding.id ?? globalIndex); + const isExpanded = expandedIds.has(id); + const filterCount = (binding.filters as FilterRow[] | undefined)?.length ?? 0; + const pipelineAllowed = isMessageEventPattern(binding.event_pattern); + const targetType = binding.target_type || 'agent'; + + function getTargetOptions(kind: AgentKind): Agent[] { + return agentOptions.filter((agent) => { + if (agent.kind !== kind) return false; + if (kind === 'pipeline') + return isMessageEventPattern(binding.event_pattern); + if (kind === 'agent') + return agentSupportsEventPattern(agent, binding.event_pattern); + return true; + }); + } + + return ( +
+ {/* main row */} +
+ {isEnabled && ( + + )} + + + IF + + + + + + + onUpdate(globalIndex, patch)} + /> + + {!pipelineAllowed && binding.target_type === 'pipeline' && ( + + {t('bots.unsupportedPipelineEvent')} + + )} + + {/* conditions toggle */} + + + {/* disable/enable toggle */} + + + +
+ + {/* conditions panel */} + {isExpanded && ( +
+ + onUpdate(globalIndex, { + filters: filters as unknown as Record[], + }) + } + /> +
+ )} +
+ ); +} + +// ── sortable wrapper ────────────────────────────────────────────────────────── + +function SortableBindingCard(props: BindingCardProps) { + const { attributes, listeners, setNodeRef, transform, isDragging } = + useSortable({ id: props.binding.id ?? props.globalIndex }); + return ( +
+ +
+ ); +} + +// ── main editor ─────────────────────────────────────────────────────────────── + export default function EventBindingsEditor({ form, supportedEvents, @@ -86,17 +666,31 @@ export default function EventBindingsEditor({ }: EventBindingsEditorProps) { const { t } = useTranslation(); const bindings: EventBinding[] = form.watch('event_bindings') || []; + const [expandedIds, setExpandedIds] = useState>(new Set()); + const [disabledSectionOpen, setDisabledSectionOpen] = useState(false); + const [activeId, setActiveId] = useState(null); + + // stable ids for dnd + const nextId = useRef(0); + const idsRef = useRef([]); + const enabledBindings = bindings.filter((b) => b.enabled ?? true); + useMemo(() => { + while (idsRef.current.length < enabledBindings.length) + idsRef.current.push(`b-${nextId.current++}`); + if (idsRef.current.length > enabledBindings.length) + idsRef.current = idsRef.current.slice(0, enabledBindings.length); + }, [enabledBindings.length]); const eventOptions = useMemo(() => { - const concreteEvents = + const concrete = supportedEvents.length > 0 ? supportedEvents : DEFAULT_EVENTS; - return ['*', ...eventNamespaces(concreteEvents), ...concreteEvents].filter( - (event, index, list) => list.indexOf(event) === index, + return ['*', ...eventNamespaces(concrete), ...concrete].filter( + (e, i, a) => a.indexOf(e) === i, ); }, [supportedEvents]); - function updateBindings(nextBindings: EventBinding[]) { - form.setValue('event_bindings', nextBindings, { shouldDirty: true }); + function updateBindings(next: EventBinding[]) { + form.setValue('event_bindings', next, { shouldDirty: true }); } function addBinding() { @@ -115,200 +709,167 @@ export default function EventBindingsEditor({ } function updateBinding(index: number, patch: Partial) { - const updated = [...bindings]; - updated[index] = { ...updated[index], ...patch }; - updateBindings(updated); + const next = [...bindings]; + next[index] = { ...next[index], ...patch }; + updateBindings(next); } function removeBinding(index: number) { - const updated = [...bindings]; - updated.splice(index, 1); - updateBindings(updated); + const next = [...bindings]; + next.splice(index, 1); + updateBindings(next); } - function getTargetOptions(binding: EventBinding, kind: AgentKind): Agent[] { - return agentOptions.filter((agent) => { - if (agent.kind !== kind) return false; - if (kind === 'pipeline') { - return isMessageEventPattern(binding.event_pattern); - } - if (kind === 'agent') { - return agentSupportsEventPattern(agent, binding.event_pattern); - } - return true; + function toggleExpand(id: string) { + setExpandedIds((prev) => { + const s = new Set(prev); + s.has(id) ? s.delete(id) : s.add(id); + return s; }); } - return ( -
-
-
- {t('bots.eventBindings')} -

- {t('bots.eventOrchestrationDescription')} -

-
- -
+ const sensors = useSensors( + useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }), + ); - {bindings.length === 0 && ( -
- {t('bots.noEventBindings')} + function handleDragStart(e: DragStartEvent) { + setActiveId(String(e.active.id)); + } + + function handleDragEnd(e: DragEndEvent) { + setActiveId(null); + const { active, over } = e; + if (!over || active.id === over.id) return; + // reorder only within enabled bindings; disabled ones stay at end + const enabledIdxs = bindings.reduce((acc, b, i) => { + if (b.enabled ?? true) acc.push(i); + return acc; + }, []); + const activeEnabledIdx = idsRef.current.indexOf(String(active.id)); + const overEnabledIdx = idsRef.current.indexOf(String(over.id)); + if (activeEnabledIdx === -1 || overEnabledIdx === -1) return; + const newOrder = arrayMove(enabledIdxs, activeEnabledIdx, overEnabledIdx); + idsRef.current = arrayMove( + idsRef.current, + activeEnabledIdx, + overEnabledIdx, + ); + const disabledBindings = bindings.filter((b) => !(b.enabled ?? true)); + updateBindings([...newOrder.map((i) => bindings[i]), ...disabledBindings]); + } + + const disabledBindings = bindings.reduce<{ b: EventBinding; i: number }[]>( + (acc, b, i) => { + if (!(b.enabled ?? true)) acc.push({ b, i }); + return acc; + }, + [], + ); + + const activeEnabledIdx = activeId ? idsRef.current.indexOf(activeId) : -1; + const activeBinding = + activeEnabledIdx >= 0 ? enabledBindings[activeEnabledIdx] : null; + const activeGlobalIdx = activeBinding ? bindings.indexOf(activeBinding) : -1; + + return ( +
+ {/* enabled section */} + + +
+ {enabledBindings.length === 0 && ( +
+ {t('bots.noEventBindings')} +
+ )} + {enabledBindings.map((binding, sortIdx) => { + const globalIdx = bindings.indexOf(binding); + return ( + + ); + })} +
+
+ + {activeBinding && activeGlobalIdx >= 0 ? ( + + ) : null} + +
+ + + + {/* disabled section */} + {disabledBindings.length > 0 && ( +
+ + {disabledSectionOpen && ( +
+ {disabledBindings.map(({ b, i }) => ( + + ))} +
+ )}
)} - -
- {bindings.map((binding, index) => { - const pipelineAllowed = isMessageEventPattern(binding.event_pattern); - const targetType = binding.target_type || 'agent'; - const targetOptions = - targetType === 'discard' - ? [] - : getTargetOptions(binding, targetType as AgentKind); - - return ( -
- - - - - {targetType === 'discard' ? ( -
- - {t('bots.targetDiscard')} -
- ) : ( - - )} - - - updateBinding(index, { - priority: Number(event.target.value || 0), - }) - } - aria-label={t('bots.priority')} - /> - -
- - updateBinding(index, { enabled }) - } - aria-label={t('bots.enabled')} - /> -
- - - - {!pipelineAllowed && binding.target_type === 'pipeline' && ( -
- {t('bots.unsupportedPipelineEvent')} -
- )} -
- ); - })} -
); } diff --git a/web/src/app/home/bots/components/bot-form/RoutingRulesEditor.tsx b/web/src/app/home/bots/components/bot-form/RoutingRulesEditor.tsx deleted file mode 100644 index f8c7efbdf..000000000 --- a/web/src/app/home/bots/components/bot-form/RoutingRulesEditor.tsx +++ /dev/null @@ -1,479 +0,0 @@ -'use client'; - -import { useTranslation } from 'react-i18next'; -import { UseFormReturn } from 'react-hook-form'; -import { - PipelineRoutingRule, - RoutingRuleOperator, -} from '@/app/infra/entities/api'; -import { Ban, GripVertical, Plus, Trash2 } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { FormLabel } from '@/components/ui/form'; -import { - Select, - SelectContent, - SelectItem, - SelectSeparator, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { - DndContext, - DragOverlay, - closestCenter, - PointerSensor, - KeyboardSensor, - useSensor, - useSensors, - DragEndEvent, - DragStartEvent, -} from '@dnd-kit/core'; -import { - arrayMove, - SortableContext, - sortableKeyboardCoordinates, - useSortable, - verticalListSortingStrategy, -} from '@dnd-kit/sortable'; -import { CSS } from '@dnd-kit/utilities'; -import { useRef, useMemo, useState } from 'react'; - -export const PIPELINE_DISCARD = '__discard__'; - -interface PipelineOption { - value: string; - label: string; - emoji?: string; -} - -interface RoutingRulesEditorProps { - form: UseFormReturn; - pipelineNameList: PipelineOption[]; -} - -const OPERATORS_BY_TYPE: Record< - PipelineRoutingRule['type'], - { value: RoutingRuleOperator; labelKey: string }[] -> = { - launcher_type: [ - { value: 'eq', labelKey: 'bots.operatorEq' }, - { value: 'neq', labelKey: 'bots.operatorNeq' }, - ], - launcher_id: [ - { value: 'eq', labelKey: 'bots.operatorEq' }, - { value: 'neq', labelKey: 'bots.operatorNeq' }, - { value: 'contains', labelKey: 'bots.operatorContains' }, - { value: 'not_contains', labelKey: 'bots.operatorNotContains' }, - { value: 'regex', labelKey: 'bots.operatorRegex' }, - ], - message_content: [ - { value: 'eq', labelKey: 'bots.operatorEq' }, - { value: 'neq', labelKey: 'bots.operatorNeq' }, - { value: 'contains', labelKey: 'bots.operatorContains' }, - { value: 'not_contains', labelKey: 'bots.operatorNotContains' }, - { value: 'starts_with', labelKey: 'bots.operatorStartsWith' }, - { value: 'regex', labelKey: 'bots.operatorRegex' }, - ], - message_has_element: [ - { value: 'eq', labelKey: 'bots.operatorHas' }, - { value: 'neq', labelKey: 'bots.operatorNotHas' }, - ], -}; - -function getValuePlaceholder( - t: (key: string) => string, - rule: PipelineRoutingRule, -): string { - if (rule.type === 'launcher_id') - return t('bots.ruleValueLauncherIdPlaceholder'); - if (rule.type === 'message_has_element') - return t('bots.ruleValueElementPlaceholder'); - if (rule.operator === 'regex') return t('bots.ruleValueRegexpPlaceholder'); - return t('bots.ruleValueMessagePlaceholder'); -} - -/* ── Static rule row (used in DragOverlay) ─────────────────────────── */ - -interface RuleRowContentProps { - rule: PipelineRoutingRule; - index: number; - pipelineNameList: PipelineOption[]; - updateRule: (index: number, patch: Partial) => void; - removeRule: (index: number) => void; - dragHandleProps?: Record; - isOverlay?: boolean; -} - -function RuleRowContent({ - rule, - index, - pipelineNameList, - updateRule, - removeRule, - dragHandleProps, - isOverlay, -}: RuleRowContentProps) { - const { t } = useTranslation(); - const operatorsForType = - OPERATORS_BY_TYPE[rule.type] || OPERATORS_BY_TYPE.message_content; - const isDiscard = rule.pipeline_uuid === PIPELINE_DISCARD; - - return ( -
- {/* Drag handle */} - - - {/* Field selector */} - - - {/* Operator selector */} - - - {/* Value input */} - {rule.type === 'launcher_type' ? ( - - ) : rule.type === 'message_has_element' ? ( - - ) : ( - updateRule(index, { value: e.target.value })} - /> - )} - - - - {/* Pipeline selector */} - - - -
- ); -} - -/* ── Sortable rule row ─────────────────────────────────────────────── */ - -interface SortableRuleRowProps { - id: string; - rule: PipelineRoutingRule; - index: number; - pipelineNameList: PipelineOption[]; - updateRule: (index: number, patch: Partial) => void; - removeRule: (index: number) => void; -} - -function SortableRuleRow({ - id, - rule, - index, - pipelineNameList, - updateRule, - removeRule, -}: SortableRuleRowProps) { - const { attributes, listeners, setNodeRef, transform, isDragging } = - useSortable({ id }); - - const style = { - transform: CSS.Transform.toString(transform), - // No transition — items reorder visually during drag via transform; - // on drop the data updates and transform resets, so animating would - // cause a redundant "swap" flicker. - opacity: isDragging ? 0.3 : undefined, - }; - - return ( -
- -
- ); -} - -/* ── Main editor ───────────────────────────────────────────────────── */ - -export default function RoutingRulesEditor({ - form, - pipelineNameList, -}: RoutingRulesEditorProps) { - const { t } = useTranslation(); - const [activeId, setActiveId] = useState(null); - - const rules: PipelineRoutingRule[] = - form.watch('pipeline_routing_rules') || []; - - // Stable unique ids for sortable items. - // We keep a running counter so newly added rules always get fresh ids. - const nextId = useRef(0); - const idsRef = useRef([]); - - const sortableIds = useMemo(() => { - // Grow the id list to match rules length (newly added items get new ids). - while (idsRef.current.length < rules.length) { - idsRef.current.push(`rule-${nextId.current++}`); - } - // Shrink if rules were removed from the end. - if (idsRef.current.length > rules.length) { - idsRef.current = idsRef.current.slice(0, rules.length); - } - return idsRef.current; - }, [rules.length]); - - const updateRules = (newRules: PipelineRoutingRule[]) => { - form.setValue('pipeline_routing_rules', newRules, { shouldDirty: true }); - }; - - const addRule = () => { - updateRules([ - ...rules, - { - type: 'launcher_type', - operator: 'eq', - value: '', - pipeline_uuid: '', - }, - ]); - }; - - const updateRule = (index: number, patch: Partial) => { - const updated = [...rules]; - updated[index] = { ...updated[index], ...patch }; - updateRules(updated); - }; - - const removeRule = (index: number) => { - const updated = [...rules]; - updated.splice(index, 1); - // Also remove the corresponding sortable id so indices stay in sync. - idsRef.current.splice(index, 1); - updateRules(updated); - }; - - const sensors = useSensors( - useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), - useSensor(KeyboardSensor, { - coordinateGetter: sortableKeyboardCoordinates, - }), - ); - - const handleDragStart = (event: DragStartEvent) => { - setActiveId(event.active.id as string); - }; - - const handleDragEnd = (event: DragEndEvent) => { - setActiveId(null); - const { active, over } = event; - if (!over || active.id === over.id) return; - - const oldIndex = sortableIds.indexOf(active.id as string); - const newIndex = sortableIds.indexOf(over.id as string); - if (oldIndex === -1 || newIndex === -1) return; - - idsRef.current = arrayMove(idsRef.current, oldIndex, newIndex); - updateRules(arrayMove(rules, oldIndex, newIndex)); - }; - - const activeIndex = activeId ? sortableIds.indexOf(activeId) : -1; - const activeRule = activeIndex >= 0 ? rules[activeIndex] : null; - - return ( -
-
-
- {t('bots.routingRules')} -

- {t('bots.routingRulesDescription')} -

-
- -
- - - - {rules.map((rule, index) => ( - - ))} - - - {activeRule ? ( - - ) : null} - - -
- ); -} diff --git a/web/src/app/home/bots/components/bot-session/BotSessionMonitor.tsx b/web/src/app/home/bots/components/bot-session/BotSessionMonitor.tsx index e38da3893..09ae23d50 100644 --- a/web/src/app/home/bots/components/bot-session/BotSessionMonitor.tsx +++ b/web/src/app/home/bots/components/bot-session/BotSessionMonitor.tsx @@ -28,7 +28,7 @@ import { Quote, Voice, } from '@/app/infra/entities/message'; -import { PIPELINE_DISCARD } from '@/app/home/bots/components/bot-form/RoutingRulesEditor'; +import { PIPELINE_DISCARD } from '@/app/home/bots/components/bot-form/EventBindingsEditor'; interface SessionInfo { session_id: string; diff --git a/web/src/components/ui/command.tsx b/web/src/components/ui/command.tsx new file mode 100644 index 000000000..ca4b8dcad --- /dev/null +++ b/web/src/components/ui/command.tsx @@ -0,0 +1,113 @@ +import * as React from 'react'; +import { Command as CommandPrimitive } from 'cmdk'; +import { Search } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Command.displayName = CommandPrimitive.displayName; + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)); +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)); +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +export { + Command, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandSeparator, +}; diff --git a/web/src/i18n/locales/en-US.ts b/web/src/i18n/locales/en-US.ts index 4199b227e..99dbccf31 100644 --- a/web/src/i18n/locales/en-US.ts +++ b/web/src/i18n/locales/en-US.ts @@ -372,14 +372,34 @@ const enUS = { targetPipeline: 'Pipeline', targetDiscard: 'Discard', selectTarget: 'Select handling logic', + searchTarget: 'Search…', + noTargetFound: 'No results found', priority: 'Priority', enabled: 'Enabled', eventBindingDescriptionPlaceholder: 'Rule description', noEventBindings: 'No event bindings', unsupportedPipelineEvent: 'Pipelines can only be used for message.* events', + disable: 'Disable', + enable: 'Enable', + disabledBindings: 'Disabled', eventCustom: 'Custom event', eventWildcard: 'All events', eventNamespaceWildcard: '{{namespace}}.*', + conditions: 'Conditions', + conditionsDescription: + 'All conditions must match to trigger this binding. Leave empty to always trigger.', + conditionsEmpty: 'No conditions — always triggers.', + addFilter: 'Add condition', + filterChatType: 'Session type', + filterChatId: 'Session ID', + filterMessageText: 'Message text', + filterMessageElement: 'Message element', + operator_eq: 'equals', + operator_neq: 'not equals', + operator_contains: 'contains', + operator_not_contains: 'not contains', + operator_starts_with: 'starts with', + operator_regex: 'regex', routingRules: 'Conditional Routing Rules', routingRulesDescription: 'Rules are evaluated in order; first match routes to its pipeline. Fallback to the default pipeline above if none match.', diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index 4383728b3..cbd3e8575 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -356,14 +356,33 @@ const zhHans = { targetPipeline: 'Pipeline', targetDiscard: '丢弃', selectTarget: '选择处理逻辑', + searchTarget: '搜索处理逻辑…', + noTargetFound: '未找到匹配项', priority: '优先级', enabled: '启用', eventBindingDescriptionPlaceholder: '规则说明', noEventBindings: '暂无事件绑定', unsupportedPipelineEvent: 'Pipeline 仅可用于 message.* 事件', + disable: '禁用', + enable: '启用', + disabledBindings: '已禁用', eventCustom: '自定义事件', eventWildcard: '全部事件', eventNamespaceWildcard: '{{namespace}}.*', + conditions: '触发条件', + conditionsDescription: '满足所有条件时才触发此绑定,不添加则无条件触发。', + conditionsEmpty: '无条件,始终触发。', + addFilter: '添加条件', + filterChatType: '会话类型', + filterChatId: '会话 ID', + filterMessageText: '消息文本', + filterMessageElement: '消息元素', + operator_eq: '等于', + operator_neq: '不等于', + operator_contains: '包含', + operator_not_contains: '不包含', + operator_starts_with: '前缀匹配', + operator_regex: '正则匹配', routingRules: '条件路由规则', routingRulesDescription: '按顺序匹配,命中第一条规则后路由到对应流水线;都不匹配时使用上方默认流水线',