feat(stage): group chat controls and track speech mute (#2139)

This commit is contained in:
RainbowBird
2026-07-28 19:34:14 +08:00
committed by GitHub
parent 899131b0a1
commit ae35b580d8
16 changed files with 299 additions and 107 deletions
@@ -44,7 +44,6 @@ const { activeCard, activeCardId } = storeToRefs(airiCardStore)
const { t } = useI18n()
const { openImagePreview } = journalPreviewStore
const isComposing = ref(false)
const sessionsDrawerOpen = defineModel<boolean>('sessionsDrawerOpen', { default: false })
const DOUBLE_ENTER_INTERVAL_MS = 300
const TRAILING_NEWLINES_REGEX = /[\r\n]+$/
const SEND_MODES = ['enter', 'ctrl-enter', 'double-enter'] as const
@@ -329,17 +328,6 @@ async function handleCleanupMessages() {
</div>
</div>
<div :class="['flex items-center justify-end gap-2 py-1']">
<button
:class="[
'max-h-[10lh] min-h-[1lh] flex items-center justify-center rounded-md p-2 outline-none',
'bg-neutral-100 text-lg text-neutral-500 transition-colors transition-transform active:scale-95',
'dark:bg-neutral-800 dark:text-neutral-400 hover:text-primary-500 dark:hover:text-primary-400',
]"
title="Conversations"
@click="sessionsDrawerOpen = true"
>
<div class="i-solar:chat-line-bold-duotone" />
</button>
<DropdownMenuRoot>
<DropdownMenuTrigger as-child>
<button
@@ -179,15 +179,6 @@ function resetMainWindowPosition() {
</template>
</ControlButtonTooltip>
<ControlButtonTooltip disable-hoverable-content>
<ControlButton :button-style="adjustStyleClasses.button" @click="openChat">
<div i-solar:chat-line-line-duotone :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</ControlButton>
<template #tooltip>
{{ t('tamagotchi.stage.controls-island.open-chat') }}
</template>
</ControlButtonTooltip>
<ControlButtonTooltip disable-hoverable-content>
<ControlButton :button-style="adjustStyleClasses.button" @click="refreshWindow">
<div i-solar:refresh-linear :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
@@ -257,6 +248,15 @@ function resetMainWindowPosition() {
</template>
</ControlButtonTooltip>
<ControlButtonTooltip side="left">
<ControlButton :button-style="adjustStyleClasses.button" @click="openChat">
<div i-solar:chat-line-line-duotone :class="adjustStyleClasses.icon" text="neutral-800 dark:neutral-300" />
</ControlButton>
<template #tooltip>
{{ t('tamagotchi.stage.controls-island.open-chat') }}
</template>
</ControlButtonTooltip>
<ControlButtonTooltip side="left">
<ControlsIslandHearingConfig :show="blockingOverlays.has('hearing')" @update:show="setOverlay('hearing', $event)">
<div class="relative">
@@ -1,6 +1,8 @@
<script setup lang="ts">
import { defineInvoke } from '@moeru/eventa'
import { useStopSpeakingButton } from '@proj-airi/stage-layouts/composables/useStopSpeakingButton'
import { ChatSessionsDrawer } from '@proj-airi/stage-ui/components'
import { getSpeechBusContext, speechOutputGetPlaybackState } from '@proj-airi/stage-ui/services/speech/bus'
import { shallowRef } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -8,7 +10,17 @@ import InteractiveArea from '../components/InteractiveArea.vue'
import WindowTitleBar from '../components/Window/TitleBar.vue'
const sessionsDrawerOpen = shallowRef(false)
const { speechMuted, toggleSpeechMuted } = useStopSpeakingButton()
const getOutputPlaybackState = defineInvoke(getSpeechBusContext(), speechOutputGetPlaybackState)
const { speechMuted, toggleSpeechMuted } = useStopSpeakingButton({
resolveSpeakingState: async () => {
// A BroadcastChannel round trip is normally immediate. Bound the
// analytics-only lookup so a reloading output renderer cannot stall mute.
const state = await getOutputPlaybackState(undefined, {
signal: AbortSignal.timeout(1000),
})
return state.speaking
},
})
const { t } = useI18n()
</script>
@@ -20,6 +32,19 @@ const { t } = useI18n()
@title-click="sessionsDrawerOpen = true"
>
<template #actions>
<button
data-testid="conversation-selector-button"
:class="[
'h-7 w-7 flex items-center justify-center rounded-md outline-none',
'text-base text-neutral-400 transition-colors transition-transform active:scale-95',
'hover:bg-neutral-200 hover:text-primary-500 dark:text-neutral-500 dark:hover:bg-neutral-800 dark:hover:text-primary-400',
]"
:title="t('stage.chat.sessions.title')"
:aria-label="t('stage.chat.sessions.title')"
@click="sessionsDrawerOpen = true"
>
<div class="i-solar:chat-line-bold-duotone" />
</button>
<button
data-testid="speech-mute-button"
:class="[
@@ -40,7 +65,6 @@ const { t } = useI18n()
</template>
</WindowTitleBar>
<InteractiveArea
v-model:sessions-drawer-open="sessionsDrawerOpen"
class="interaction-area block"
h-full w-full p-4 transition="opacity duration-250"
/>