From 7fe410b96937574a1ecbdf131728998293a8ca70 Mon Sep 17 00:00:00 2001 From: Neko Ayaka Date: Fri, 20 Mar 2026 01:49:51 +0800 Subject: [PATCH] chore(stage-tamagotchi,stage-pages,stage-layouts): vision devtools improved --- .../composables/use-vision-screen-capture.ts | 48 +- .../src/renderer/pages/devtools/vision.vue | 11 +- .../pages/settings/system/developer.vue | 33 +- .../src/pages/settings/system/developer.vue | 6 - packages/i18n/src/locales/en/settings.yaml | 3 - .../src/pages/devtools/context-observer.vue | 652 ------------------ .../stage-ui/src/components/gadgets/index.ts | 1 - .../gadgets/timeline-range.story.vue | 45 -- .../src/components/gadgets/timeline-range.vue | 179 ----- .../src/stores/mods/api/channel-server.ts | 12 +- .../src/stores/modules/vision/index.ts | 1 - .../modules/vision/orchestrator.test.ts | 96 +++ .../src/stores/modules/vision/orchestrator.ts | 7 + .../modules/vision/utils/model-catalog.ts | 55 -- 14 files changed, 161 insertions(+), 988 deletions(-) delete mode 100644 packages/stage-pages/src/pages/devtools/context-observer.vue delete mode 100644 packages/stage-ui/src/components/gadgets/timeline-range.story.vue delete mode 100644 packages/stage-ui/src/components/gadgets/timeline-range.vue create mode 100644 packages/stage-ui/src/stores/modules/vision/orchestrator.test.ts delete mode 100644 packages/stage-ui/src/stores/modules/vision/utils/model-catalog.ts diff --git a/apps/stage-tamagotchi/src/renderer/composables/use-vision-screen-capture.ts b/apps/stage-tamagotchi/src/renderer/composables/use-vision-screen-capture.ts index 5f7c6ec39..3df35bdec 100644 --- a/apps/stage-tamagotchi/src/renderer/composables/use-vision-screen-capture.ts +++ b/apps/stage-tamagotchi/src/renderer/composables/use-vision-screen-capture.ts @@ -36,6 +36,33 @@ export function useVisionScreenCapture(sourcesOptions: MaybeRefOrGetter sources.value.find(source => source.id === activeSourceId.value) || null) + function isActiveStream(stream: MediaStream | null | undefined) { + if (!stream) + return false + + return stream.getVideoTracks().some(track => track.readyState === 'live') + } + + function clearActiveStream() { + const stream = activeStream.value + if (!stream) { + activeStream.value = null + return + } + + stream.getTracks().forEach(track => track.stop()) + activeStream.value = null + } + + function attachStreamLifecycle(stream: MediaStream) { + stream.getTracks().forEach((track) => { + track.addEventListener('ended', () => { + if (activeStream.value === stream) + activeStream.value = null + }, { once: true }) + }) + } + async function refetchSources() { try { isRefetching.value = true @@ -61,9 +88,9 @@ export function useVisionScreenCapture(sourcesOptions: MaybeRefOrGetter 0 ? toObjectUrl(source.thumbnail, 'image/jpeg') : undefined, })) - if (!activeSourceId.value && sources.value.length > 0) { + const hasActiveSource = sources.value.some(source => source.id === activeSourceId.value) + if (!hasActiveSource) activeSourceId.value = sources.value[0]?.id || '' - } } finally { isRefetching.value = false @@ -75,9 +102,10 @@ export function useVisionScreenCapture(sourcesOptions: MaybeRefOrGetter track.stop()) - } + if (isActiveStream(activeStream.value)) + return activeStream.value! + + clearActiveStream() const handle = await setSource({ options: toRaw(toValue(sourcesOptions)), @@ -87,18 +115,20 @@ export function useVisionScreenCapture(sourcesOptions: MaybeRefOrGetter track.stop()) - } - activeStream.value = null + clearActiveStream() } function cleanup() { diff --git a/apps/stage-tamagotchi/src/renderer/pages/devtools/vision.vue b/apps/stage-tamagotchi/src/renderer/pages/devtools/vision.vue index 1f5e1bb60..6e6bf8915 100644 --- a/apps/stage-tamagotchi/src/renderer/pages/devtools/vision.vue +++ b/apps/stage-tamagotchi/src/renderer/pages/devtools/vision.vue @@ -130,6 +130,13 @@ const expectedRateMax = computed(() => { return Math.max(60, Math.ceil(60000 / interval)) }) +function hasLiveVideoStream(stream: MediaStream | null) { + if (!stream) + return false + + return stream.getVideoTracks().some(track => track.readyState === 'live') +} + async function ensureVideoStream() { if (!activeSourceId.value) return @@ -156,8 +163,10 @@ async function handleVisionTick() { return try { - if (!activeStream.value) + if (!hasLiveVideoStream(activeStream.value)) { + activeStream.value = null await ensureVideoStream() + } const video = videoRef.value if (!video) diff --git a/apps/stage-tamagotchi/src/renderer/pages/settings/system/developer.vue b/apps/stage-tamagotchi/src/renderer/pages/settings/system/developer.vue index 42231bacf..ce2ff2a93 100644 --- a/apps/stage-tamagotchi/src/renderer/pages/settings/system/developer.vue +++ b/apps/stage-tamagotchi/src/renderer/pages/settings/system/developer.vue @@ -43,12 +43,6 @@ const menu = computed(() => [ icon: 'i-solar:chat-square-call-bold-duotone', to: '/devtools/context-flow', }, - { - title: 'Context Observer', - description: 'Track how active context entries merge over time', - icon: 'i-solar:chat-square-bold-duotone', - to: '/devtools/context-observer', - }, { title: 'Relative Mouse', description: 'Get mouse position relative to the window', @@ -100,42 +94,27 @@ const openDevtoolsWindow = useElectronEventaInvoke(electronOpenDevtoolsWindow)