@shepai/cli 1.53.2 → 1.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apis/json-schema/NotificationEvent.yaml +4 -0
- package/dist/packages/core/src/domain/generated/output.d.ts +4 -0
- package/dist/packages/core/src/domain/generated/output.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts +4 -0
- package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/notifications/notification-bus.js +19 -6
- package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts +5 -3
- package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/notifications/notification-watcher.service.js +21 -13
- package/dist/src/presentation/cli/commands/ui.command.d.ts.map +1 -1
- package/dist/src/presentation/cli/commands/ui.command.js +2 -1
- package/dist/src/presentation/web/app/api/agent-events/health/route.d.ts +13 -0
- package/dist/src/presentation/web/app/api/agent-events/health/route.d.ts.map +1 -0
- package/dist/src/presentation/web/app/api/agent-events/health/route.js +60 -0
- package/dist/src/presentation/web/app/api/agent-events/route.d.ts +8 -4
- package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -1
- package/dist/src/presentation/web/app/api/agent-events/route.js +227 -55
- package/dist/src/presentation/web/app/layout.d.ts.map +1 -1
- package/dist/src/presentation/web/app/layout.js +14 -0
- package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts +5 -1
- package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/derive-feature-state.js +29 -1
- package/dist/src/presentation/web/components/common/repository-node/repository-node.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/repository-node/repository-node.js +1 -1
- package/dist/src/presentation/web/components/common/sound-toggle/index.d.ts +2 -0
- package/dist/src/presentation/web/components/common/sound-toggle/index.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/sound-toggle/index.js +1 -0
- package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.d.ts +2 -0
- package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.js +9 -0
- package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.d.ts +8 -0
- package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.js +17 -0
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +14 -2
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts +1 -0
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +151 -19
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.d.ts.map +1 -1
- package/dist/src/presentation/web/components/layouts/app-shell/app-shell.js +2 -1
- package/dist/src/presentation/web/dev-server.js +14 -0
- package/dist/src/presentation/web/hooks/use-agent-events.d.ts +7 -0
- package/dist/src/presentation/web/hooks/use-agent-events.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/use-agent-events.js +135 -47
- package/dist/src/presentation/web/hooks/use-notifications.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/use-notifications.js +56 -16
- package/dist/src/presentation/web/hooks/use-notifications.stories.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/use-notifications.stories.js +28 -9
- package/dist/src/presentation/web/hooks/use-sound-enabled.d.ts +6 -0
- package/dist/src/presentation/web/hooks/use-sound-enabled.d.ts.map +1 -0
- package/dist/src/presentation/web/hooks/use-sound-enabled.js +27 -0
- package/dist/src/presentation/web/hooks/use-sound.d.ts +15 -0
- package/dist/src/presentation/web/hooks/use-sound.d.ts.map +1 -0
- package/dist/src/presentation/web/hooks/use-sound.js +83 -0
- package/dist/src/presentation/web/hooks/use-sound.stories.d.ts +8 -0
- package/dist/src/presentation/web/hooks/use-sound.stories.d.ts.map +1 -0
- package/dist/src/presentation/web/hooks/use-sound.stories.js +67 -0
- package/dist/src/presentation/web/public/agent-events-sw.d.ts +35 -0
- package/dist/src/presentation/web/public/agent-events-sw.d.ts.map +1 -0
- package/dist/src/presentation/web/public/agent-events-sw.js +140 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/app-path-routes-manifest.json +1 -0
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/cache/.previewinfo +1 -1
- package/web/.next/cache/.rscinfo +1 -1
- package/web/.next/cache/.tsbuildinfo +1 -1
- package/web/.next/cache/config.json +3 -3
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +1 -1
- package/web/.next/required-server-files.json +1 -1
- package/web/.next/routes-manifest.json +6 -0
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/api/agent-events/health/route/app-paths-manifest.json +3 -0
- package/web/.next/server/app/api/agent-events/health/route/build-manifest.json +11 -0
- package/web/.next/server/app/api/agent-events/health/route/server-reference-manifest.json +4 -0
- package/web/.next/server/app/api/agent-events/health/route.js +6 -0
- package/web/.next/server/app/api/agent-events/health/route.js.map +5 -0
- package/web/.next/server/app/api/agent-events/health/route.js.nft.json +1 -0
- package/web/.next/server/app/api/agent-events/health/route_client-reference-manifest.js +2 -0
- package/web/.next/server/app/api/agent-events/route.js +2 -1
- package/web/.next/server/app/api/agent-events/route.js.nft.json +1 -1
- package/web/.next/server/app/page/server-reference-manifest.json +14 -14
- package/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page.js +1 -1
- package/web/.next/server/app/version/page.js.nft.json +1 -1
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app-paths-manifest.json +1 -0
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +6 -0
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -0
- package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_health_route_actions_4d471160.js +3 -0
- package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_health_route_actions_4d471160.js.map +1 -0
- package/web/.next/server/chunks/[root-of-the-server]__3b22a1eb._.js +3 -0
- package/web/.next/server/chunks/[root-of-the-server]__3b22a1eb._.js.map +1 -0
- package/web/.next/server/chunks/[root-of-the-server]__d9d410a8._.js +3 -0
- package/web/.next/server/chunks/[root-of-the-server]__d9d410a8._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
- package/web/.next/{standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__249c74f6._.js → server/chunks/ssr/[root-of-the-server]__6e8b5181._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_1fafa9db._.js → _1632a21f._.js} +2 -2
- package/web/.next/server/chunks/ssr/_1632a21f._.js.map +1 -0
- package/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_ed0934e5._.js.map +1 -1
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +15 -15
- package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/src/presentation/web/.next/app-path-routes-manifest.json +1 -0
- package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/routes-manifest.json +6 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route/app-paths-manifest.json +3 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route/build-manifest.json +11 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route/server-reference-manifest.json +4 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route.js +6 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route.js.map +5 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route.js.nft.json +1 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/health/route_client-reference-manifest.js +2 -0
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/route.js +2 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/api/agent-events/route.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +14 -14
- package/web/.next/standalone/src/presentation/web/.next/server/app/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page.js.nft.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app-paths-manifest.json +1 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +6 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/744ca_web__next-internal_server_app_api_agent-events_health_route_actions_4d471160.js +3 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/[root-of-the-server]__3b22a1eb._.js +3 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/[root-of-the-server]__d9d410a8._.js +3 -0
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__2d1a8e37._.js +2 -2
- package/web/.next/{server/chunks/ssr/[root-of-the-server]__249c74f6._.js → standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__6e8b5181._.js} +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/{_1fafa9db._.js → _1632a21f._.js} +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_67178b88._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +15 -15
- package/web/.next/standalone/src/presentation/web/app/api/agent-events/health/route.ts +69 -0
- package/web/.next/standalone/src/presentation/web/app/api/agent-events/route.ts +258 -54
- package/web/.next/standalone/src/presentation/web/app/layout.tsx +14 -0
- package/web/.next/standalone/src/presentation/web/components/common/feature-node/derive-feature-state.ts +39 -2
- package/web/.next/standalone/src/presentation/web/components/common/repository-node/repository-node.tsx +1 -0
- package/web/.next/standalone/src/presentation/web/components/common/sound-toggle/index.ts +1 -0
- package/web/.next/standalone/src/presentation/web/components/common/sound-toggle/sound-toggle.stories.tsx +27 -0
- package/web/.next/standalone/src/presentation/web/components/common/sound-toggle/sound-toggle.tsx +21 -0
- package/web/.next/standalone/src/presentation/web/components/features/control-center/control-center-inner.tsx +15 -1
- package/web/.next/standalone/src/presentation/web/components/features/control-center/use-control-center-state.ts +194 -31
- package/web/.next/standalone/src/presentation/web/components/layouts/app-shell/app-shell.tsx +3 -1
- package/web/.next/standalone/src/presentation/web/dev-server.ts +21 -0
- package/web/.next/standalone/src/presentation/web/hooks/use-agent-events.ts +161 -52
- package/web/.next/standalone/src/presentation/web/hooks/use-notifications.stories.tsx +39 -10
- package/web/.next/standalone/src/presentation/web/hooks/use-notifications.ts +68 -15
- package/web/.next/standalone/src/presentation/web/hooks/use-sound-enabled.ts +38 -0
- package/web/.next/standalone/src/presentation/web/hooks/use-sound.stories.tsx +109 -0
- package/web/.next/standalone/src/presentation/web/hooks/use-sound.ts +101 -0
- package/web/.next/standalone/src/presentation/web/public/agent-events-sw.js +164 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/button.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/caution.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/celebration.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/disabled.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/notification.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/progress_loop.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/ringtone_loop.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/select.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/swipe.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/swipe_01.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/swipe_02.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/swipe_03.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/swipe_04.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/swipe_05.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/tap_01.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/tap_02.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/tap_03.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/tap_04.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/tap_05.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/toggle_off.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/toggle_on.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/transition_down.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/transition_up.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/type_01.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/type_02.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/type_03.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/type_04.wav +0 -0
- package/web/.next/standalone/src/presentation/web/public/sounds/type_05.wav +0 -0
- package/web/.next/standalone/src/presentation/web/server.js +1 -1
- package/web/.next/static/chunks/1f13bc20e938335c.css +2 -0
- package/web/.next/static/chunks/702384d364482e4b.js +10 -0
- package/web/.next/static/chunks/{68fb91b6eca14536.js → 8f4461aee9a05741.js} +2 -2
- package/web/.next/static/chunks/9a82d8c76f175235.js +1 -0
- package/web/.next/trace +1 -1
- package/web/.next/trace-build +1 -1
- package/web/.next/types/link.d.ts +1 -0
- package/web/.next/types/routes.d.ts +2 -1
- package/web/.next/types/validator.ts +9 -0
- package/web/public/agent-events-sw.js +164 -0
- package/web/public/sounds/button.wav +0 -0
- package/web/public/sounds/caution.wav +0 -0
- package/web/public/sounds/celebration.wav +0 -0
- package/web/public/sounds/disabled.wav +0 -0
- package/web/public/sounds/notification.wav +0 -0
- package/web/public/sounds/progress_loop.wav +0 -0
- package/web/public/sounds/ringtone_loop.wav +0 -0
- package/web/public/sounds/select.wav +0 -0
- package/web/public/sounds/swipe.wav +0 -0
- package/web/public/sounds/swipe_01.wav +0 -0
- package/web/public/sounds/swipe_02.wav +0 -0
- package/web/public/sounds/swipe_03.wav +0 -0
- package/web/public/sounds/swipe_04.wav +0 -0
- package/web/public/sounds/swipe_05.wav +0 -0
- package/web/public/sounds/tap_01.wav +0 -0
- package/web/public/sounds/tap_02.wav +0 -0
- package/web/public/sounds/tap_03.wav +0 -0
- package/web/public/sounds/tap_04.wav +0 -0
- package/web/public/sounds/tap_05.wav +0 -0
- package/web/public/sounds/toggle_off.wav +0 -0
- package/web/public/sounds/toggle_on.wav +0 -0
- package/web/public/sounds/transition_down.wav +0 -0
- package/web/public/sounds/transition_up.wav +0 -0
- package/web/public/sounds/type_01.wav +0 -0
- package/web/public/sounds/type_02.wav +0 -0
- package/web/public/sounds/type_03.wav +0 -0
- package/web/public/sounds/type_04.wav +0 -0
- package/web/public/sounds/type_05.wav +0 -0
- package/web/.next/server/chunks/[root-of-the-server]__7d5c8b74._.js +0 -6
- package/web/.next/server/chunks/[root-of-the-server]__7d5c8b74._.js.map +0 -1
- package/web/.next/server/chunks/ssr/_1fafa9db._.js.map +0 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/[root-of-the-server]__7d5c8b74._.js +0 -6
- package/web/.next/static/chunks/13d5334b17480a16.js +0 -10
- package/web/.next/static/chunks/16dec8021e251230.css +0 -2
- package/web/.next/static/chunks/22f30ce91a3c79c1.js +0 -1
- /package/web/.next/server/chunks/ssr/{[root-of-the-server]__249c74f6._.js.map → [root-of-the-server]__6e8b5181._.js.map} +0 -0
- /package/web/.next/static/{nFvWyY2BF3AIS3FpZBKEb → RwzxYU9D2fPc_4j5RYu0v}/_buildManifest.js +0 -0
- /package/web/.next/static/{nFvWyY2BF3AIS3FpZBKEb → RwzxYU9D2fPc_4j5RYu0v}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{nFvWyY2BF3AIS3FpZBKEb → RwzxYU9D2fPc_4j5RYu0v}/_ssgManifest.js +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { toast } from 'sonner';
|
|
4
|
-
import { NotificationSeverity } from '../../../../packages/core/src/domain/generated/output.js';
|
|
4
|
+
import { NotificationEventType, NotificationSeverity } from '../../../../packages/core/src/domain/generated/output.js';
|
|
5
5
|
import { useAgentEventsContext } from './agent-events-provider.js';
|
|
6
|
+
import { useSound } from './use-sound.js';
|
|
6
7
|
const SEVERITY_TO_TOAST = {
|
|
7
8
|
[NotificationSeverity.Success]: 'success',
|
|
8
9
|
[NotificationSeverity.Error]: 'error',
|
|
@@ -11,34 +12,73 @@ const SEVERITY_TO_TOAST = {
|
|
|
11
12
|
};
|
|
12
13
|
function dispatchToast(event) {
|
|
13
14
|
const method = SEVERITY_TO_TOAST[event.severity] ?? 'info';
|
|
14
|
-
|
|
15
|
+
const isActionable = event.eventType === NotificationEventType.WaitingApproval;
|
|
16
|
+
toast[method](event.featureName, {
|
|
17
|
+
description: event.message,
|
|
18
|
+
...(isActionable && {
|
|
19
|
+
action: {
|
|
20
|
+
label: 'Review',
|
|
21
|
+
onClick: () => {
|
|
22
|
+
window.dispatchEvent(new CustomEvent('shep:select-feature', { detail: { featureId: event.featureId } }));
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
15
27
|
}
|
|
16
28
|
function dispatchBrowserNotification(event) {
|
|
17
29
|
if (globalThis.Notification?.permission !== 'granted') {
|
|
18
30
|
return;
|
|
19
31
|
}
|
|
20
|
-
new Notification(event.featureName
|
|
32
|
+
new Notification(`Shep: ${event.featureName}`, {
|
|
33
|
+
body: event.message,
|
|
34
|
+
icon: '/favicon-light.svg',
|
|
35
|
+
});
|
|
21
36
|
}
|
|
37
|
+
const SEVERITY_TO_SOUND = {
|
|
38
|
+
[NotificationSeverity.Success]: 'celebration',
|
|
39
|
+
[NotificationSeverity.Error]: 'caution',
|
|
40
|
+
[NotificationSeverity.Warning]: 'notification',
|
|
41
|
+
[NotificationSeverity.Info]: 'button',
|
|
42
|
+
};
|
|
22
43
|
export function useNotifications() {
|
|
23
|
-
const {
|
|
44
|
+
const { events } = useAgentEventsContext();
|
|
45
|
+
const successSound = useSound('celebration', { volume: 0.5 });
|
|
46
|
+
const errorSound = useSound('caution', { volume: 0.5 });
|
|
47
|
+
const warningSound = useSound('notification', { volume: 0.5 });
|
|
48
|
+
const infoSound = useSound('button', { volume: 0.5 });
|
|
49
|
+
const soundsByName = useMemo(() => ({
|
|
50
|
+
celebration: successSound,
|
|
51
|
+
caution: errorSound,
|
|
52
|
+
notification: warningSound,
|
|
53
|
+
button: infoSound,
|
|
54
|
+
}), [successSound, errorSound, warningSound, infoSound]);
|
|
24
55
|
const [browserPermissionState, setBrowserPermissionState] = useState(() => {
|
|
25
56
|
if (typeof globalThis.Notification === 'undefined')
|
|
26
57
|
return 'default';
|
|
27
58
|
return globalThis.Notification.permission;
|
|
28
59
|
});
|
|
29
|
-
// Track
|
|
30
|
-
|
|
60
|
+
// Track how many events from the array we've already processed.
|
|
61
|
+
// Using the array index (instead of lastEvent) prevents React batching
|
|
62
|
+
// from silently dropping events when multiple SSE messages arrive together.
|
|
63
|
+
const processedCountRef = useRef(0);
|
|
31
64
|
useEffect(() => {
|
|
32
|
-
if (
|
|
33
|
-
return;
|
|
34
|
-
// Create a key for deduplication
|
|
35
|
-
const key = `${lastEvent.agentRunId}-${lastEvent.eventType}-${lastEvent.timestamp}`;
|
|
36
|
-
if (processedRef.current.has(key))
|
|
65
|
+
if (events.length <= processedCountRef.current)
|
|
37
66
|
return;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
67
|
+
const newEvents = events.slice(processedCountRef.current);
|
|
68
|
+
processedCountRef.current = events.length;
|
|
69
|
+
for (const event of newEvents) {
|
|
70
|
+
// Only notify for actionable events and completion celebrations
|
|
71
|
+
if (event.severity !== NotificationSeverity.Error &&
|
|
72
|
+
event.severity !== NotificationSeverity.Warning &&
|
|
73
|
+
event.severity !== NotificationSeverity.Success) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
dispatchToast(event);
|
|
77
|
+
dispatchBrowserNotification(event);
|
|
78
|
+
const soundName = SEVERITY_TO_SOUND[event.severity];
|
|
79
|
+
soundsByName[soundName]?.play();
|
|
80
|
+
}
|
|
81
|
+
}, [events, soundsByName]);
|
|
42
82
|
const requestBrowserPermission = useCallback(async () => {
|
|
43
83
|
if (typeof globalThis.Notification === 'undefined')
|
|
44
84
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-notifications.stories.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-notifications.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"use-notifications.stories.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-notifications.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAqKvD,QAAA,MAAM,IAAI,EAAE,IAcX,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC;AAEtB,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC"}
|
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
2
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
3
3
|
import { toast } from 'sonner';
|
|
4
4
|
import { Toaster } from '../components/ui/sonner.js';
|
|
5
5
|
import { Button } from '../components/ui/button.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
6
|
+
import { useSound } from './use-sound.js';
|
|
7
|
+
const SEVERITY_SOUNDS = {
|
|
8
|
+
success: 'celebration',
|
|
9
|
+
error: 'caution',
|
|
10
|
+
warning: 'notification',
|
|
11
|
+
info: 'button',
|
|
12
|
+
};
|
|
13
13
|
function NotificationDemo() {
|
|
14
|
+
const successSound = useSound('celebration', { volume: 0.5 });
|
|
15
|
+
const errorSound = useSound('caution', { volume: 0.5 });
|
|
16
|
+
const warningSound = useSound('notification', { volume: 0.5 });
|
|
17
|
+
const infoSound = useSound('button', { volume: 0.5 });
|
|
18
|
+
const soundsByName = useMemo(() => ({
|
|
19
|
+
celebration: successSound,
|
|
20
|
+
caution: errorSound,
|
|
21
|
+
notification: warningSound,
|
|
22
|
+
button: infoSound,
|
|
23
|
+
}), [successSound, errorSound, warningSound, infoSound]);
|
|
24
|
+
const simulateNotification = useCallback((event) => {
|
|
25
|
+
const method = event.severity ?? 'info';
|
|
26
|
+
toast[method](event.featureName, { description: event.message });
|
|
27
|
+
if (globalThis.Notification?.permission === 'granted') {
|
|
28
|
+
new Notification(event.featureName, { body: event.message });
|
|
29
|
+
}
|
|
30
|
+
const soundName = SEVERITY_SOUNDS[event.severity];
|
|
31
|
+
soundsByName[soundName]?.play();
|
|
32
|
+
}, [soundsByName]);
|
|
14
33
|
const [browserPermission, setBrowserPermission] = useState(typeof globalThis.Notification !== 'undefined' ? globalThis.Notification.permission : 'default');
|
|
15
34
|
const requestPermission = async () => {
|
|
16
35
|
if (typeof globalThis.Notification === 'undefined')
|
|
@@ -18,7 +37,7 @@ function NotificationDemo() {
|
|
|
18
37
|
const result = await globalThis.Notification.requestPermission();
|
|
19
38
|
setBrowserPermission(result);
|
|
20
39
|
};
|
|
21
|
-
return (_jsxs("div", { className: "flex w-[420px] flex-col gap-6", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("h3", { className: "text-sm font-semibold", children: "Browser Notification Permission" }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "text-muted-foreground text-sm", children: ["Status: ", _jsx("code", { className: "bg-muted rounded px-1.5 py-0.5", children: browserPermission })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: requestPermission, children: "Request Permission" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("h3", { className: "text-sm font-semibold", children: "Simulate Notification Events" }), _jsx("p", { className: "text-muted-foreground text-xs", children: "Click a button to fire a toast
|
|
40
|
+
return (_jsxs("div", { className: "flex w-[420px] flex-col gap-6", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("h3", { className: "text-sm font-semibold", children: "Browser Notification Permission" }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("span", { className: "text-muted-foreground text-sm", children: ["Status: ", _jsx("code", { className: "bg-muted rounded px-1.5 py-0.5", children: browserPermission })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: requestPermission, children: "Request Permission" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("h3", { className: "text-sm font-semibold", children: "Simulate Notification Events" }), _jsx("p", { className: "text-muted-foreground text-xs", children: "Click a button to fire a toast, browser notification, and sound." }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { size: "sm", variant: "outline", onClick: () => simulateNotification({
|
|
22
41
|
eventType: 'agent_started',
|
|
23
42
|
severity: 'info',
|
|
24
43
|
featureName: 'Auth Module',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-sound-enabled.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-sound-enabled.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,wBAAgB,eAAe,IAAI,qBAAqB,CAyBvD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
const STORAGE_KEY = 'shep-sound-enabled';
|
|
4
|
+
const SYNC_EVENT = 'shep:sound-toggle';
|
|
5
|
+
export function useSoundEnabled() {
|
|
6
|
+
const [enabled, setEnabled] = useState(true);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
9
|
+
if (stored === 'false')
|
|
10
|
+
setEnabled(false);
|
|
11
|
+
// Sync across all useSoundEnabled instances in the same tab
|
|
12
|
+
const onSync = (e) => {
|
|
13
|
+
setEnabled(e.detail);
|
|
14
|
+
};
|
|
15
|
+
window.addEventListener(SYNC_EVENT, onSync);
|
|
16
|
+
return () => window.removeEventListener(SYNC_EVENT, onSync);
|
|
17
|
+
}, []);
|
|
18
|
+
const toggle = useCallback(() => {
|
|
19
|
+
setEnabled((prev) => {
|
|
20
|
+
const next = !prev;
|
|
21
|
+
localStorage.setItem(STORAGE_KEY, String(next));
|
|
22
|
+
window.dispatchEvent(new CustomEvent(SYNC_EVENT, { detail: next }));
|
|
23
|
+
return next;
|
|
24
|
+
});
|
|
25
|
+
}, []);
|
|
26
|
+
return { enabled, toggle };
|
|
27
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare const SOUND_NAMES: readonly ["button", "caution", "celebration", "disabled", "notification", "progress_loop", "ringtone_loop", "select", "swipe", "swipe_01", "swipe_02", "swipe_03", "swipe_04", "swipe_05", "tap_01", "tap_02", "tap_03", "tap_04", "tap_05", "toggle_off", "toggle_on", "transition_down", "transition_up", "type_01", "type_02", "type_03", "type_04", "type_05"];
|
|
2
|
+
export type SoundName = (typeof SOUND_NAMES)[number];
|
|
3
|
+
export interface UseSoundOptions {
|
|
4
|
+
volume?: number;
|
|
5
|
+
loop?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface UseSoundResult {
|
|
8
|
+
play: () => void;
|
|
9
|
+
stop: () => void;
|
|
10
|
+
isPlaying: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function useSound(name: SoundName, options?: UseSoundOptions): UseSoundResult;
|
|
13
|
+
/** All available sound names for enumeration (e.g. in Storybook). */
|
|
14
|
+
export { SOUND_NAMES };
|
|
15
|
+
//# sourceMappingURL=use-sound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-sound.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-sound.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,WAAW,oWA6BP,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,eAAoB,GAAG,cAAc,CAgDvF;AAED,qEAAqE;AACrE,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
3
|
+
import { useSoundEnabled } from './use-sound-enabled.js';
|
|
4
|
+
const SOUND_NAMES = [
|
|
5
|
+
'button',
|
|
6
|
+
'caution',
|
|
7
|
+
'celebration',
|
|
8
|
+
'disabled',
|
|
9
|
+
'notification',
|
|
10
|
+
'progress_loop',
|
|
11
|
+
'ringtone_loop',
|
|
12
|
+
'select',
|
|
13
|
+
'swipe',
|
|
14
|
+
'swipe_01',
|
|
15
|
+
'swipe_02',
|
|
16
|
+
'swipe_03',
|
|
17
|
+
'swipe_04',
|
|
18
|
+
'swipe_05',
|
|
19
|
+
'tap_01',
|
|
20
|
+
'tap_02',
|
|
21
|
+
'tap_03',
|
|
22
|
+
'tap_04',
|
|
23
|
+
'tap_05',
|
|
24
|
+
'toggle_off',
|
|
25
|
+
'toggle_on',
|
|
26
|
+
'transition_down',
|
|
27
|
+
'transition_up',
|
|
28
|
+
'type_01',
|
|
29
|
+
'type_02',
|
|
30
|
+
'type_03',
|
|
31
|
+
'type_04',
|
|
32
|
+
'type_05',
|
|
33
|
+
];
|
|
34
|
+
export function useSound(name, options = {}) {
|
|
35
|
+
const { volume = 1, loop = false } = options;
|
|
36
|
+
const { enabled } = useSoundEnabled();
|
|
37
|
+
const audioRef = useRef(null);
|
|
38
|
+
const isPlayingRef = useRef(false);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (typeof window === 'undefined')
|
|
41
|
+
return;
|
|
42
|
+
const audio = new Audio(`/sounds/${name}.wav`);
|
|
43
|
+
audio.preload = 'auto';
|
|
44
|
+
audio.loop = loop;
|
|
45
|
+
audio.volume = Math.max(0, Math.min(1, volume));
|
|
46
|
+
audio.addEventListener('ended', () => {
|
|
47
|
+
isPlayingRef.current = false;
|
|
48
|
+
});
|
|
49
|
+
audioRef.current = audio;
|
|
50
|
+
return () => {
|
|
51
|
+
audio.pause();
|
|
52
|
+
audio.removeAttribute('src');
|
|
53
|
+
audioRef.current = null;
|
|
54
|
+
isPlayingRef.current = false;
|
|
55
|
+
};
|
|
56
|
+
}, [name, loop, volume]);
|
|
57
|
+
const play = useCallback(() => {
|
|
58
|
+
if (!enabled)
|
|
59
|
+
return;
|
|
60
|
+
const audio = audioRef.current;
|
|
61
|
+
if (!audio)
|
|
62
|
+
return;
|
|
63
|
+
audio.currentTime = 0;
|
|
64
|
+
audio.play()?.catch((err) => {
|
|
65
|
+
if (process.env.NODE_ENV === 'development') {
|
|
66
|
+
// eslint-disable-next-line no-console
|
|
67
|
+
console.warn(`[useSound] play("${name}") blocked:`, err.message);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
isPlayingRef.current = true;
|
|
71
|
+
}, [enabled, name]);
|
|
72
|
+
const stop = useCallback(() => {
|
|
73
|
+
const audio = audioRef.current;
|
|
74
|
+
if (!audio)
|
|
75
|
+
return;
|
|
76
|
+
audio.pause();
|
|
77
|
+
audio.currentTime = 0;
|
|
78
|
+
isPlayingRef.current = false;
|
|
79
|
+
}, []);
|
|
80
|
+
return { play, stop, isPlaying: isPlayingRef.current };
|
|
81
|
+
}
|
|
82
|
+
/** All available sound names for enumeration (e.g. in Storybook). */
|
|
83
|
+
export { SOUND_NAMES };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-sound.stories.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/hooks/use-sound.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAsFvD,QAAA,MAAM,IAAI,EAAE,IAcX,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE1C,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Button } from '../components/ui/button.js';
|
|
4
|
+
import { useSound, SOUND_NAMES } from './use-sound.js';
|
|
5
|
+
/* ------------------------------------------------------------------ */
|
|
6
|
+
/* Helpers */
|
|
7
|
+
/* ------------------------------------------------------------------ */
|
|
8
|
+
const CATEGORIES = {
|
|
9
|
+
'UI Actions': ['button', 'select', 'disabled'],
|
|
10
|
+
Toggles: ['toggle_on', 'toggle_off'],
|
|
11
|
+
Transitions: ['transition_up', 'transition_down'],
|
|
12
|
+
Alerts: ['notification', 'caution', 'celebration'],
|
|
13
|
+
Swipes: ['swipe', 'swipe_01', 'swipe_02', 'swipe_03', 'swipe_04', 'swipe_05'],
|
|
14
|
+
Taps: ['tap_01', 'tap_02', 'tap_03', 'tap_04', 'tap_05'],
|
|
15
|
+
Typing: ['type_01', 'type_02', 'type_03', 'type_04', 'type_05'],
|
|
16
|
+
Loops: ['progress_loop', 'ringtone_loop'],
|
|
17
|
+
};
|
|
18
|
+
/* ------------------------------------------------------------------ */
|
|
19
|
+
/* Single sound player (used per button) */
|
|
20
|
+
/* ------------------------------------------------------------------ */
|
|
21
|
+
function SoundButton({ name, volume }) {
|
|
22
|
+
const isLoop = name.includes('loop');
|
|
23
|
+
const { play, stop } = useSound(name, { volume, loop: isLoop });
|
|
24
|
+
const [playing, setPlaying] = useState(false);
|
|
25
|
+
const handleClick = () => {
|
|
26
|
+
if (isLoop && playing) {
|
|
27
|
+
stop();
|
|
28
|
+
setPlaying(false);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
play();
|
|
32
|
+
setPlaying(true);
|
|
33
|
+
if (!isLoop) {
|
|
34
|
+
setTimeout(() => setPlaying(false), 2000);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return (_jsx(Button, { size: "sm", variant: playing ? 'default' : 'outline', onClick: handleClick, className: "min-w-[120px] font-mono text-xs", children: isLoop && playing ? `Stop ${name}` : name }));
|
|
39
|
+
}
|
|
40
|
+
/* ------------------------------------------------------------------ */
|
|
41
|
+
/* Full catalog demo */
|
|
42
|
+
/* ------------------------------------------------------------------ */
|
|
43
|
+
function SoundCatalog({ volume }) {
|
|
44
|
+
return (_jsxs("div", { className: "flex w-[560px] flex-col gap-6", children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("h2", { className: "text-base font-semibold", children: "Sound Library" }), _jsxs("p", { className: "text-muted-foreground text-sm", children: [SOUND_NAMES.length, " sounds available at", ' ', _jsx("code", { className: "bg-muted rounded px-1 py-0.5 text-xs", children: "/sounds/*.wav" })] })] }), Object.entries(CATEGORIES).map(([category, sounds]) => (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("h3", { className: "text-sm font-semibold", children: category }), _jsx("div", { className: "flex flex-wrap gap-2", children: sounds.map((name) => (_jsx(SoundButton, { name: name, volume: volume }, name))) })] }, category)))] }));
|
|
45
|
+
}
|
|
46
|
+
/* ------------------------------------------------------------------ */
|
|
47
|
+
/* Storybook meta */
|
|
48
|
+
/* ------------------------------------------------------------------ */
|
|
49
|
+
const meta = {
|
|
50
|
+
title: 'Hooks/useSound',
|
|
51
|
+
parameters: { layout: 'centered' },
|
|
52
|
+
tags: ['autodocs'],
|
|
53
|
+
argTypes: {
|
|
54
|
+
volume: {
|
|
55
|
+
control: { type: 'range', min: 0, max: 1, step: 0.1 },
|
|
56
|
+
description: 'Playback volume (0 to 1)',
|
|
57
|
+
defaultValue: 0.5,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
args: {
|
|
61
|
+
volume: 0.5,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
export default meta;
|
|
65
|
+
export const Catalog = {
|
|
66
|
+
render: ({ volume }) => _jsx(SoundCatalog, { volume: volume }),
|
|
67
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
declare function connect(): void;
|
|
2
|
+
declare function disconnect(): void;
|
|
3
|
+
declare function cleanup(): void;
|
|
4
|
+
declare function setStatus(status: any): void;
|
|
5
|
+
declare function broadcast(message: any): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Service Worker: agent-events-sw.js
|
|
8
|
+
*
|
|
9
|
+
* Maintains a single SSE connection to /api/agent-events and broadcasts
|
|
10
|
+
* NotificationEvent deltas to all connected tabs via postMessage.
|
|
11
|
+
*
|
|
12
|
+
* Messages FROM clients:
|
|
13
|
+
* { type: 'subscribe', runId?: string } — register this tab
|
|
14
|
+
* { type: 'unsubscribe' } — unregister this tab
|
|
15
|
+
*
|
|
16
|
+
* Messages TO clients:
|
|
17
|
+
* { type: 'notification', data: NotificationEvent }
|
|
18
|
+
* { type: 'status', status: 'connected' | 'connecting' | 'disconnected' }
|
|
19
|
+
*/
|
|
20
|
+
declare const BASE_BACKOFF_MS: 1000;
|
|
21
|
+
declare const MAX_BACKOFF_MS: 30000;
|
|
22
|
+
declare const STABLE_CONNECTION_MS: 5000;
|
|
23
|
+
/** @type {EventSource | null} */
|
|
24
|
+
declare let eventSource: EventSource | null;
|
|
25
|
+
/** @type {string | undefined} */
|
|
26
|
+
declare let currentRunId: string | undefined;
|
|
27
|
+
declare let backoff: number;
|
|
28
|
+
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
29
|
+
declare let reconnectTimer: ReturnType<typeof setTimeout> | null;
|
|
30
|
+
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
31
|
+
declare let stableTimer: ReturnType<typeof setTimeout> | null;
|
|
32
|
+
/** @type {'connected' | 'connecting' | 'disconnected'} */
|
|
33
|
+
declare let connectionStatus: "connected" | "connecting" | "disconnected";
|
|
34
|
+
declare let subscriberCount: number;
|
|
35
|
+
//# sourceMappingURL=agent-events-sw.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-events-sw.d.ts","sourceRoot":"","sources":["../../../../../src/presentation/web/public/agent-events-sw.js"],"names":[],"mappings":"AAmFA,iCA6CC;AAED,oCAQC;AAED,iCASC;AAID,8CAGC;AAED,wDAKC;AAnKD;;;;;;;;;;;;;GAaG;AAIH,+BAAwB,IAAI,CAAC;AAC7B,8BAAuB,KAAM,CAAC;AAC9B,oCAA6B,IAAK,CAAC;AAEnC,iCAAiC;AACjC,yBADW,WAAW,GAAG,IAAI,CACN;AACvB,iCAAiC;AACjC,0BADW,MAAM,GAAG,SAAS,CACZ;AACjB,4BAA8B;AAC9B,mDAAmD;AACnD,4BADW,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CACrB;AAC1B,mDAAmD;AACnD,yBADW,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CACxB;AACvB,0DAA0D;AAC1D,8BADW,WAAW,GAAG,YAAY,GAAG,cAAc,CAChB;AACtC,oCAAwB"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Service Worker: agent-events-sw.js
|
|
4
|
+
*
|
|
5
|
+
* Maintains a single SSE connection to /api/agent-events and broadcasts
|
|
6
|
+
* NotificationEvent deltas to all connected tabs via postMessage.
|
|
7
|
+
*
|
|
8
|
+
* Messages FROM clients:
|
|
9
|
+
* { type: 'subscribe', runId?: string } — register this tab
|
|
10
|
+
* { type: 'unsubscribe' } — unregister this tab
|
|
11
|
+
*
|
|
12
|
+
* Messages TO clients:
|
|
13
|
+
* { type: 'notification', data: NotificationEvent }
|
|
14
|
+
* { type: 'status', status: 'connected' | 'connecting' | 'disconnected' }
|
|
15
|
+
*/
|
|
16
|
+
/* global self, EventSource, setTimeout, clearTimeout */
|
|
17
|
+
const BASE_BACKOFF_MS = 1000;
|
|
18
|
+
const MAX_BACKOFF_MS = 30_000;
|
|
19
|
+
const STABLE_CONNECTION_MS = 5_000;
|
|
20
|
+
/** @type {EventSource | null} */
|
|
21
|
+
let eventSource = null;
|
|
22
|
+
/** @type {string | undefined} */
|
|
23
|
+
let currentRunId;
|
|
24
|
+
let backoff = BASE_BACKOFF_MS;
|
|
25
|
+
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
26
|
+
let reconnectTimer = null;
|
|
27
|
+
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
28
|
+
let stableTimer = null;
|
|
29
|
+
/** @type {'connected' | 'connecting' | 'disconnected'} */
|
|
30
|
+
let connectionStatus = 'disconnected';
|
|
31
|
+
let subscriberCount = 0;
|
|
32
|
+
// --- Lifecycle ---
|
|
33
|
+
self.addEventListener('install', () => {
|
|
34
|
+
self.skipWaiting();
|
|
35
|
+
});
|
|
36
|
+
self.addEventListener('activate', (event) => {
|
|
37
|
+
event.waitUntil(self.clients.claim().then(async () => {
|
|
38
|
+
// After claiming clients, check if there are open tabs that need a connection.
|
|
39
|
+
// This handles the case where the SW updated via skipWaiting() and the old
|
|
40
|
+
// SW's EventSource was closed — we auto-start for existing clients.
|
|
41
|
+
const clients = await self.clients.matchAll({ type: 'window' });
|
|
42
|
+
if (clients.length > 0 && subscriberCount === 0) {
|
|
43
|
+
subscriberCount = clients.length;
|
|
44
|
+
connect();
|
|
45
|
+
}
|
|
46
|
+
}));
|
|
47
|
+
});
|
|
48
|
+
// --- Client messaging ---
|
|
49
|
+
self.addEventListener('message', (event) => {
|
|
50
|
+
const { type, runId } = event.data ?? {};
|
|
51
|
+
if (type === 'subscribe') {
|
|
52
|
+
subscriberCount++;
|
|
53
|
+
currentRunId = runId;
|
|
54
|
+
// Send current status to the new subscriber immediately
|
|
55
|
+
event.source?.postMessage({ type: 'status', status: connectionStatus });
|
|
56
|
+
// Start connection if this is the first subscriber
|
|
57
|
+
if (subscriberCount === 1) {
|
|
58
|
+
connect();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else if (type === 'unsubscribe') {
|
|
62
|
+
subscriberCount = Math.max(0, subscriberCount - 1);
|
|
63
|
+
// Close connection when no subscribers remain
|
|
64
|
+
if (subscriberCount === 0) {
|
|
65
|
+
disconnect();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
// --- SSE connection management ---
|
|
70
|
+
function connect() {
|
|
71
|
+
if (eventSource)
|
|
72
|
+
return;
|
|
73
|
+
const url = currentRunId
|
|
74
|
+
? `/api/agent-events?runId=${encodeURIComponent(currentRunId)}`
|
|
75
|
+
: '/api/agent-events';
|
|
76
|
+
eventSource = new EventSource(url);
|
|
77
|
+
setStatus('connecting');
|
|
78
|
+
eventSource.onopen = () => {
|
|
79
|
+
setStatus('connected');
|
|
80
|
+
// Only reset backoff after connection is stable
|
|
81
|
+
stableTimer = setTimeout(() => {
|
|
82
|
+
stableTimer = null;
|
|
83
|
+
backoff = BASE_BACKOFF_MS;
|
|
84
|
+
}, STABLE_CONNECTION_MS);
|
|
85
|
+
};
|
|
86
|
+
eventSource.onerror = () => {
|
|
87
|
+
cleanup();
|
|
88
|
+
setStatus('disconnected');
|
|
89
|
+
// Don't reconnect if no subscribers
|
|
90
|
+
if (subscriberCount <= 0)
|
|
91
|
+
return;
|
|
92
|
+
const delay = backoff;
|
|
93
|
+
backoff = Math.min(delay * 2, MAX_BACKOFF_MS);
|
|
94
|
+
reconnectTimer = setTimeout(() => {
|
|
95
|
+
reconnectTimer = null;
|
|
96
|
+
connect();
|
|
97
|
+
}, delay);
|
|
98
|
+
};
|
|
99
|
+
eventSource.addEventListener('notification', (event) => {
|
|
100
|
+
/** @type {unknown} */
|
|
101
|
+
let data;
|
|
102
|
+
try {
|
|
103
|
+
data = JSON.parse(event.data);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
broadcast({ type: 'notification', data });
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function disconnect() {
|
|
112
|
+
if (reconnectTimer !== null) {
|
|
113
|
+
clearTimeout(reconnectTimer);
|
|
114
|
+
reconnectTimer = null;
|
|
115
|
+
}
|
|
116
|
+
cleanup();
|
|
117
|
+
backoff = BASE_BACKOFF_MS;
|
|
118
|
+
setStatus('disconnected');
|
|
119
|
+
}
|
|
120
|
+
function cleanup() {
|
|
121
|
+
if (stableTimer !== null) {
|
|
122
|
+
clearTimeout(stableTimer);
|
|
123
|
+
stableTimer = null;
|
|
124
|
+
}
|
|
125
|
+
if (eventSource) {
|
|
126
|
+
eventSource.close();
|
|
127
|
+
eventSource = null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// --- Helpers ---
|
|
131
|
+
function setStatus(status) {
|
|
132
|
+
connectionStatus = status;
|
|
133
|
+
broadcast({ type: 'status', status });
|
|
134
|
+
}
|
|
135
|
+
async function broadcast(message) {
|
|
136
|
+
const clients = await self.clients.matchAll({ type: 'window' });
|
|
137
|
+
for (const client of clients) {
|
|
138
|
+
client.postMessage(message);
|
|
139
|
+
}
|
|
140
|
+
}
|