diff --git a/Cargo.toml b/Cargo.toml index 11c03e77f..de45f5a0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,12 @@ [workspace] -members = [ "crates/tauri-plugin-ipc-audio-transcription-ort", "crates/tauri-plugin-ipc-audio-vad-ort", "crates/tauri-plugin-mcp", "crates/tauri-plugin-rdev", "crates/tauri-plugin-window-pass-through-on-hover", "crates/tauri-plugin-window-router-link" ] +members = [ + "crates/tauri-plugin-ipc-audio-transcription-ort", + "crates/tauri-plugin-ipc-audio-vad-ort", + "crates/tauri-plugin-mcp", + "crates/tauri-plugin-rdev", + "crates/tauri-plugin-window-pass-through-on-hover", + "crates/tauri-plugin-window-router-link" +] resolver = "2" [workspace.package] diff --git a/packages/server-shared/src/types/websocket/events.ts b/packages/server-shared/src/types/websocket/events.ts index 1ceca2870..16e15b0f4 100644 --- a/packages/server-shared/src/types/websocket/events.ts +++ b/packages/server-shared/src/types/websocket/events.ts @@ -15,14 +15,14 @@ interface InputSource { discord: Discord } -export type ContextSource = - | 'text' - | 'stt' - | 'vision' - | 'llm' - | 'server-channel' - | 'plugin' - | 'system' +export type ContextSource + = | 'text' + | 'stt' + | 'vision' + | 'llm' + | 'server-channel' + | 'plugin' + | 'system' export interface ContextMessage> { /** diff --git a/packages/stage-ui/src/components/scenarios/index.ts b/packages/stage-ui/src/components/scenarios/index.ts index 878a0d7f9..42ef8ff9f 100644 --- a/packages/stage-ui/src/components/scenarios/index.ts +++ b/packages/stage-ui/src/components/scenarios/index.ts @@ -1,5 +1,5 @@ -export * from './dialogs' export * from './chat' +export * from './dialogs' export * from './providers' export * from './settings' export * from './toasters' diff --git a/packages/stage-ui/src/composables/live2d/motion-manager.ts b/packages/stage-ui/src/composables/live2d/motion-manager.ts index 7120c3fff..d01f1813c 100644 --- a/packages/stage-ui/src/composables/live2d/motion-manager.ts +++ b/packages/stage-ui/src/composables/live2d/motion-manager.ts @@ -94,7 +94,7 @@ export function useLive2DMotionManagerUpdate(options: UseLive2DMotionManagerUpda if (!ctx.handled && ctx.hookedUpdate) { const result = ctx.hookedUpdate.call(motionManager, model, now) - if (Boolean(result)) + if (result) ctx.handled = true } diff --git a/packages/stage-ui/src/composables/use-data-maintenance.ts b/packages/stage-ui/src/composables/use-data-maintenance.ts index 0a1fa4479..c9842bc7f 100644 --- a/packages/stage-ui/src/composables/use-data-maintenance.ts +++ b/packages/stage-ui/src/composables/use-data-maintenance.ts @@ -1,6 +1,11 @@ +import type { ChatEntry } from '../stores/chat' + import { isStageTamagotchi } from '@proj-airi/stage-shared' -import { DEFAULT_THEME_COLORS_HUE, useSettings, useSettingsAudioDevice } from '../stores/settings' -import { defaultModelParameters, useLive2d } from '../stores/live2d' + +import { useChatStore } from '../stores/chat' +import { useDisplayModelsStore } from '../stores/display-models' +import { useLive2d } from '../stores/live2d' +import { useMcpStore } from '../stores/mcp' import { useAiriCardStore } from '../stores/modules/airi-card' import { useConsciousnessStore } from '../stores/modules/consciousness' import { useDiscordStore } from '../stores/modules/discord' @@ -9,12 +14,9 @@ import { useMinecraftStore } from '../stores/modules/gaming-minecraft' import { useHearingStore } from '../stores/modules/hearing' import { useSpeechStore } from '../stores/modules/speech' import { useTwitterStore } from '../stores/modules/twitter' -import type { ChatEntry } from '../stores/chat' -import { useChatStore } from '../stores/chat' -import { useDisplayModelsStore } from '../stores/display-models' -import { useMcpStore } from '../stores/mcp' import { useOnboardingStore } from '../stores/onboarding' import { useProvidersStore } from '../stores/providers' +import { useSettings, useSettingsAudioDevice } from '../stores/settings' export function useDataMaintenance() { const chatStore = useChatStore() @@ -45,45 +47,13 @@ export function useDataMaintenance() { } function resetModulesSettings() { - hearingStore.activeTranscriptionProvider = '' - hearingStore.activeTranscriptionModel = '' - hearingStore.activeCustomModelName = '' - - speechStore.activeSpeechProvider = '' - speechStore.activeSpeechModel = 'eleven_multilingual_v2' - speechStore.activeSpeechVoiceId = '' - speechStore.activeSpeechVoice = undefined - speechStore.pitch = 0 - speechStore.rate = 1 - speechStore.ssmlEnabled = false - speechStore.selectedLanguage = 'en-US' - - consciousnessStore.activeProvider = '' - consciousnessStore.activeModel = '' - consciousnessStore.activeCustomModelName = '' - - twitterStore.enabled = false - twitterStore.apiKey = '' - twitterStore.apiSecret = '' - twitterStore.accessToken = '' - twitterStore.accessTokenSecret = '' - twitterStore.saveSettings() - - discordStore.enabled = false - discordStore.token = '' - discordStore.saveSettings() - - factorioStore.enabled = false - factorioStore.serverAddress = '' - factorioStore.serverPort = 34197 - factorioStore.username = '' - factorioStore.saveSettings() - - minecraftStore.enabled = false - minecraftStore.serverAddress = '' - minecraftStore.serverPort = 25565 - minecraftStore.username = '' - minecraftStore.saveSettings() + hearingStore.resetState() + speechStore.resetState() + consciousnessStore.resetState() + twitterStore.resetState() + discordStore.resetState() + factorioStore.resetState() + minecraftStore.resetState() } function deleteAllChatSessions() { @@ -107,42 +77,13 @@ export function useDataMaintenance() { chatStore.replaceSessions(sessions) } - function resetSettingsState() { - settingsStore.language = '' - settingsStore.stageModelSelected = 'preset-live2d-1' - settingsStore.stageViewControlsEnabled = false - - settingsStore.live2dDisableFocus = false - settingsStore.live2dIdleAnimationEnabled = true - settingsStore.live2dAutoBlinkEnabled = true - settingsStore.live2dShadowEnabled = true - - settingsStore.disableTransitions = true - settingsStore.usePageSpecificTransitions = true - - settingsStore.themeColorsHue = DEFAULT_THEME_COLORS_HUE - settingsStore.themeColorsHueDynamic = false - - settingsStore.allowVisibleOnAllWorkspaces = true - - audioSettingsStore.enabled = false - audioSettingsStore.selectedAudioInput = '' - audioSettingsStore.stopStream() - - live2dStore.position = { x: 0, y: 0 } - live2dStore.scale = 1 - live2dStore.motionMap = {} - live2dStore.modelParameters = { ...defaultModelParameters } - - mcpStore.serverCmd = '' - mcpStore.serverArgs = '' - mcpStore.connected = false - + async function resetSettingsState() { + await settingsStore.resetState() + audioSettingsStore.resetState() + live2dStore.resetState() + mcpStore.resetState() onboardingStore.resetSetupState() - airiCardStore.activeCardId = 'default' - airiCardStore.cards = new Map() - - void settingsStore.updateStageModel() + airiCardStore.resetState() } async function deleteAllData() { @@ -150,14 +91,14 @@ export function useDataMaintenance() { await resetProvidersSettings() resetModulesSettings() deleteAllChatSessions() - resetSettingsState() + await resetSettingsState() } - function resetDesktopApplicationState() { + async function resetDesktopApplicationState() { if (!isStageTamagotchi()) return - resetSettingsState() + await resetSettingsState() resetModulesSettings() } diff --git a/packages/stage-ui/src/stores/live2d.ts b/packages/stage-ui/src/stores/live2d.ts index 9a4f8780e..9bfb1d14d 100644 --- a/packages/stage-ui/src/stores/live2d.ts +++ b/packages/stage-ui/src/stores/live2d.ts @@ -1,7 +1,9 @@ -import { useBroadcastChannel, useLocalStorage } from '@vueuse/core' +import { useBroadcastChannel } from '@vueuse/core' import { defineStore } from 'pinia' import { computed, ref, watch } from 'vue' +import { createResettableLocalStorage, createResettableRef } from '../utils/resettable' + type BroadcastChannelEvents = | BroadcastChannelEventShouldUpdateView @@ -53,18 +55,28 @@ export const useLive2d = defineStore('live2d', () => { } }) - const position = useLocalStorage('settings/live2d/position', { x: 0, y: 0 }) // position is relative to the center of the screen, units are % + const [position, resetPosition] = createResettableLocalStorage('settings/live2d/position', { x: 0, y: 0 }) // position is relative to the center of the screen, units are % const positionInPercentageString = computed(() => ({ x: `${position.value.x}%`, y: `${position.value.y}%`, })) - const currentMotion = ref<{ group: string, index?: number }>({ group: 'Idle', index: 0 }) - const availableMotions = ref<{ motionName: string, motionIndex: number, fileName: string }[]>([]) - const motionMap = useLocalStorage>('settings/live2d/motion-map', {}) - const scale = useLocalStorage('settings/live2d/scale', 1) + const [currentMotion, resetCurrentMotion] = createResettableRef<{ group: string, index?: number }>({ group: 'Idle', index: 0 }) + const [availableMotions, resetAvailableMotions] = createResettableRef<{ motionName: string, motionIndex: number, fileName: string }[]>([]) + const [motionMap, resetMotionMap] = createResettableLocalStorage>('settings/live2d/motion-map', {}) + const [scale, resetScale] = createResettableLocalStorage('settings/live2d/scale', 1) // Live2D model parameters - const modelParameters = useLocalStorage('settings/live2d/parameters', defaultModelParameters) + const [modelParameters, resetModelParameters] = createResettableLocalStorage('settings/live2d/parameters', defaultModelParameters) + + function resetState() { + resetPosition() + resetCurrentMotion() + resetAvailableMotions() + resetMotionMap() + resetScale() + resetModelParameters() + shouldUpdateView() + } return { position, @@ -77,5 +89,6 @@ export const useLive2d = defineStore('live2d', () => { onShouldUpdateView, shouldUpdateView, + resetState, } }) diff --git a/packages/stage-ui/src/stores/mcp.ts b/packages/stage-ui/src/stores/mcp.ts index 48ede9244..0608fac38 100644 --- a/packages/stage-ui/src/stores/mcp.ts +++ b/packages/stage-ui/src/stores/mcp.ts @@ -1,14 +1,22 @@ -import { useLocalStorage } from '@vueuse/core' import { defineStore } from 'pinia' +import { createResettableLocalStorage } from '../utils/resettable' + export const useMcpStore = defineStore('mcp', () => { - const serverCmd = useLocalStorage('settings/mcp/server-cmd', '') - const serverArgs = useLocalStorage('settings/mcp/server-args', '') - const connected = useLocalStorage('mcp/connected', false) // use local storage to sync between windows + const [serverCmd, resetServerCmd] = createResettableLocalStorage('settings/mcp/server-cmd', '') + const [serverArgs, resetServerArgs] = createResettableLocalStorage('settings/mcp/server-args', '') + const [connected, resetConnected] = createResettableLocalStorage('mcp/connected', false) // use local storage to sync between windows + + function resetState() { + resetServerCmd() + resetServerArgs() + resetConnected() + } return { serverCmd, serverArgs, connected, + resetState, } }) diff --git a/packages/stage-ui/src/stores/modules/airi-card.ts b/packages/stage-ui/src/stores/modules/airi-card.ts index 56d4674a3..005b2b538 100644 --- a/packages/stage-ui/src/stores/modules/airi-card.ts +++ b/packages/stage-ui/src/stores/modules/airi-card.ts @@ -1,6 +1,5 @@ import type { Card, ccv3 } from '@proj-airi/ccc' -import { useLocalStorage } from '@vueuse/core' import { nanoid } from 'nanoid' import { defineStore, storeToRefs } from 'pinia' import { computed, onMounted, watch } from 'vue' @@ -8,6 +7,7 @@ import { useI18n } from 'vue-i18n' import SystemPromptV2 from '../../constants/prompts/system-v2' +import { createResettableLocalStorage } from '../../utils/resettable' import { useConsciousnessStore } from './consciousness' import { useSpeechStore } from './speech' @@ -54,8 +54,8 @@ export interface AiriCard extends Card { } export const useAiriCardStore = defineStore('airi-card', () => { - const cards = useLocalStorage>('airi-cards', new Map()) - const activeCardId = useLocalStorage('airi-card-active-id', 'default') + const [cards, resetCards] = createResettableLocalStorage>('airi-cards', new Map()) + const [activeCardId, resetActiveCardId] = createResettableLocalStorage('airi-card-active-id', 'default') const activeCard = computed(() => cards.value.get(activeCardId.value)) @@ -208,6 +208,11 @@ export const useAiriCardStore = defineStore('airi-card', () => { activeSpeechVoiceId.value = extension?.modules?.speech?.voice_id }) + function resetState() { + resetActiveCardId() + resetCards() + } + return { cards, activeCard, @@ -215,6 +220,7 @@ export const useAiriCardStore = defineStore('airi-card', () => { addCard, removeCard, getCard, + resetState, currentModels: computed(() => { return { diff --git a/packages/stage-ui/src/stores/modules/consciousness.ts b/packages/stage-ui/src/stores/modules/consciousness.ts index 7bb6d2bca..a32a9fbcd 100644 --- a/packages/stage-ui/src/stores/modules/consciousness.ts +++ b/packages/stage-ui/src/stores/modules/consciousness.ts @@ -1,18 +1,18 @@ -import { useLocalStorage } from '@vueuse/core' import { defineStore } from 'pinia' -import { computed, ref } from 'vue' +import { computed } from 'vue' +import { createResettableLocalStorage, createResettableRef } from '../../utils/resettable' import { useProvidersStore } from '../providers' export const useConsciousnessStore = defineStore('consciousness', () => { const providersStore = useProvidersStore() // State - const activeProvider = useLocalStorage('settings/consciousness/active-provider', '') - const activeModel = useLocalStorage('settings/consciousness/active-model', '') - const activeCustomModelName = useLocalStorage('settings/consciousness/active-custom-model', '') - const expandedDescriptions = ref>({}) - const modelSearchQuery = ref('') + const [activeProvider, resetActiveProvider] = createResettableLocalStorage('settings/consciousness/active-provider', '') + const [activeModel, resetActiveModel] = createResettableLocalStorage('settings/consciousness/active-model', '') + const [activeCustomModelName, resetActiveCustomModelName] = createResettableLocalStorage('settings/consciousness/active-custom-model', '') + const [expandedDescriptions, resetExpandedDescriptions] = createResettableRef>({}) + const [modelSearchQuery, resetModelSearchQuery] = createResettableRef('') // Computed properties const supportsModelListing = computed(() => { @@ -45,10 +45,10 @@ export const useConsciousnessStore = defineStore('consciousness', () => { }) function resetModelSelection() { - activeModel.value = '' - activeCustomModelName.value = '' - expandedDescriptions.value = {} - modelSearchQuery.value = '' + resetActiveModel() + resetActiveCustomModelName() + resetExpandedDescriptions() + resetModelSearchQuery() } async function loadModelsForProvider(provider: string) { @@ -69,6 +69,11 @@ export const useConsciousnessStore = defineStore('consciousness', () => { return !!activeProvider.value && !!activeModel.value }) + function resetState() { + resetActiveProvider() + resetModelSelection() + } + return { // State configured, @@ -89,5 +94,6 @@ export const useConsciousnessStore = defineStore('consciousness', () => { resetModelSelection, loadModelsForProvider, getModelsForProvider, + resetState, } }) diff --git a/packages/stage-ui/src/stores/modules/discord.ts b/packages/stage-ui/src/stores/modules/discord.ts index dfd2b80fd..864377ea0 100644 --- a/packages/stage-ui/src/stores/modules/discord.ts +++ b/packages/stage-ui/src/stores/modules/discord.ts @@ -1,13 +1,13 @@ -import { useLocalStorage } from '@vueuse/core' import { defineStore } from 'pinia' import { computed } from 'vue' +import { createResettableLocalStorage } from '../../utils/resettable' import { useConfiguratorByModsChannelServer } from '../configurator' export const useDiscordStore = defineStore('discord', () => { const configurator = useConfiguratorByModsChannelServer() - const enabled = useLocalStorage('settings/discord/enabled', false) - const token = useLocalStorage('settings/discord/token', '') + const [enabled, resetEnabled] = createResettableLocalStorage('settings/discord/enabled', false) + const [token, resetToken] = createResettableLocalStorage('settings/discord/token', '') function saveSettings() { // Data is automatically saved to localStorage via useLocalStorage @@ -22,10 +22,17 @@ export const useDiscordStore = defineStore('discord', () => { return !!token.value.trim() }) + function resetState() { + resetEnabled() + resetToken() + saveSettings() + } + return { enabled, token, configured, saveSettings, + resetState, } }) diff --git a/packages/stage-ui/src/stores/modules/gaming-module-factory.ts b/packages/stage-ui/src/stores/modules/gaming-module-factory.ts index 236c00cf5..9a8d15fc7 100644 --- a/packages/stage-ui/src/stores/modules/gaming-module-factory.ts +++ b/packages/stage-ui/src/stores/modules/gaming-module-factory.ts @@ -1,17 +1,17 @@ -import { useLocalStorage } from '@vueuse/core' import { defineStore } from 'pinia' import { computed } from 'vue' +import { createResettableLocalStorage } from '../../utils/resettable' import { useConfiguratorByModsChannelServer } from '../configurator' export function createGamingModuleStore(moduleName: string, defaultPort: number) { return defineStore(moduleName, () => { const configurator = useConfiguratorByModsChannelServer() - const enabled = useLocalStorage(`settings/${moduleName}/enabled`, false) - const serverAddress = useLocalStorage(`settings/${moduleName}/server-address`, '') - const serverPort = useLocalStorage(`settings/${moduleName}/server-port`, defaultPort) - const username = useLocalStorage(`settings/${moduleName}/username`, '') + const [enabled, resetEnabled] = createResettableLocalStorage(`settings/${moduleName}/enabled`, false) + const [serverAddress, resetServerAddress] = createResettableLocalStorage(`settings/${moduleName}/server-address`, '') + const [serverPort, resetServerPort] = createResettableLocalStorage(`settings/${moduleName}/server-port`, defaultPort) + const [username, resetUsername] = createResettableLocalStorage(`settings/${moduleName}/username`, '') function saveSettings() { configurator.updateFor(moduleName, { @@ -22,10 +22,18 @@ export function createGamingModuleStore(moduleName: string, defaultPort: number) }) } + function resetState() { + resetEnabled() + resetServerAddress() + resetServerPort() + resetUsername() + saveSettings() + } + const configured = computed(() => { return !!(serverAddress.value.trim() && username.value.trim() && serverPort.value !== null) }) - return { enabled, serverAddress, serverPort, username, configured, saveSettings } + return { enabled, serverAddress, serverPort, username, configured, saveSettings, resetState } }) } diff --git a/packages/stage-ui/src/stores/modules/hearing.ts b/packages/stage-ui/src/stores/modules/hearing.ts index b0ac402e4..1e7d5d215 100644 --- a/packages/stage-ui/src/stores/modules/hearing.ts +++ b/packages/stage-ui/src/stores/modules/hearing.ts @@ -2,12 +2,12 @@ import type { TranscriptionProviderWithExtraOptions } from '@xsai-ext/shared-pro import type { WithUnknown } from '@xsai/shared' import type { StreamTranscriptionResult, StreamTranscriptionOptions as XSAIStreamTranscriptionOptions } from '@xsai/stream-transcription' -import { useLocalStorage } from '@vueuse/core' import { generateTranscription } from '@xsai/generate-transcription' import { streamTranscription } from '@xsai/stream-transcription' import { defineStore, storeToRefs } from 'pinia' import { computed, ref } from 'vue' +import { createResettableLocalStorage, createResettableRef } from '../../utils/resettable' import { useProvidersStore } from '../providers' export interface StreamTranscriptionFileInputOptions extends Omit { @@ -44,10 +44,10 @@ export const useHearingStore = defineStore('hearing-store', () => { const { allAudioTranscriptionProvidersMetadata } = storeToRefs(providersStore) // State - const activeTranscriptionProvider = useLocalStorage('settings/hearing/active-provider', '') - const activeTranscriptionModel = useLocalStorage('settings/hearing/active-model', '') - const activeCustomModelName = useLocalStorage('settings/hearing/active-custom-model', '') - const transcriptionModelSearchQuery = ref('') + const [activeTranscriptionProvider, resetActiveTranscriptionProvider] = createResettableLocalStorage('settings/hearing/active-provider', '') + const [activeTranscriptionModel, resetActiveTranscriptionModel] = createResettableLocalStorage('settings/hearing/active-model', '') + const [activeCustomModelName, resetActiveCustomModelName] = createResettableLocalStorage('settings/hearing/active-custom-model', '') + const [transcriptionModelSearchQuery, resetTranscriptionModelSearchQuery] = createResettableRef('') // Computed properties const availableProvidersMetadata = computed(() => allAudioTranscriptionProvidersMetadata.value) @@ -87,6 +87,13 @@ export const useHearingStore = defineStore('hearing-store', () => { return !!activeTranscriptionProvider.value && !!activeTranscriptionModel.value }) + function resetState() { + resetActiveTranscriptionProvider() + resetActiveTranscriptionModel() + resetActiveCustomModelName() + resetTranscriptionModelSearchQuery() + } + async function transcription( providerId: string, provider: TranscriptionProviderWithExtraOptions, @@ -178,6 +185,7 @@ export const useHearingStore = defineStore('hearing-store', () => { transcription, loadModelsForProvider, getModelsForProvider, + resetState, } }) diff --git a/packages/stage-ui/src/stores/modules/speech.ts b/packages/stage-ui/src/stores/modules/speech.ts index c5976dbb8..1233b9e87 100644 --- a/packages/stage-ui/src/stores/modules/speech.ts +++ b/packages/stage-ui/src/stores/modules/speech.ts @@ -2,13 +2,13 @@ import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers' import type { VoiceInfo } from '../providers' -import { useLocalStorage } from '@vueuse/core' import { generateSpeech } from '@xsai/generate-speech' import { defineStore, storeToRefs } from 'pinia' -import { computed, onMounted, ref, watch } from 'vue' +import { computed, onMounted, watch } from 'vue' import { toXml } from 'xast-util-to-xml' import { x } from 'xastscript' +import { createResettableLocalStorage, createResettableRef } from '../../utils/resettable' import { useProvidersStore } from '../providers' export const useSpeechStore = defineStore('speech', () => { @@ -16,19 +16,19 @@ export const useSpeechStore = defineStore('speech', () => { const { allAudioSpeechProvidersMetadata } = storeToRefs(providersStore) // State - const activeSpeechProvider = useLocalStorage('settings/speech/active-provider', '') - const activeSpeechModel = useLocalStorage('settings/speech/active-model', 'eleven_multilingual_v2') - const activeSpeechVoiceId = useLocalStorage('settings/speech/voice', '') - const activeSpeechVoice = ref() + const [activeSpeechProvider, resetActiveSpeechProvider] = createResettableLocalStorage('settings/speech/active-provider', '') + const [activeSpeechModel, resetActiveSpeechModel] = createResettableLocalStorage('settings/speech/active-model', 'eleven_multilingual_v2') + const [activeSpeechVoiceId, resetActiveSpeechVoiceId] = createResettableLocalStorage('settings/speech/voice', '') + const [activeSpeechVoice, resetActiveSpeechVoice] = createResettableRef(undefined) - const pitch = useLocalStorage('settings/speech/pitch', 0) - const rate = useLocalStorage('settings/speech/rate', 1) - const ssmlEnabled = useLocalStorage('settings/speech/ssml-enabled', false) - const isLoadingSpeechProviderVoices = ref(false) - const speechProviderError = ref(null) - const availableVoices = ref>({}) - const selectedLanguage = useLocalStorage('settings/speech/language', 'en-US') - const modelSearchQuery = ref('') + const [pitch, resetPitch] = createResettableLocalStorage('settings/speech/pitch', 0) + const [rate, resetRate] = createResettableLocalStorage('settings/speech/rate', 1) + const [ssmlEnabled, resetSsmlEnabled] = createResettableLocalStorage('settings/speech/ssml-enabled', false) + const [isLoadingSpeechProviderVoices, resetIsLoadingSpeechProviderVoices] = createResettableRef(false) + const [speechProviderError, resetSpeechProviderError] = createResettableRef(null) + const [availableVoices, resetAvailableVoices] = createResettableRef>({}) + const [selectedLanguage, resetSelectedLanguage] = createResettableLocalStorage('settings/speech/language', 'en-US') + const [modelSearchQuery, resetModelSearchQuery] = createResettableRef('') // Computed properties const availableSpeechProvidersMetadata = computed(() => allAudioSpeechProvidersMetadata.value) @@ -204,6 +204,21 @@ export const useSpeechStore = defineStore('speech', () => { return !!activeSpeechProvider.value && !!activeSpeechModel.value && !!activeSpeechVoiceId.value }) + function resetState() { + resetActiveSpeechProvider() + resetActiveSpeechModel() + resetActiveSpeechVoiceId() + resetActiveSpeechVoice() + resetPitch() + resetRate() + resetSsmlEnabled() + resetSelectedLanguage() + resetModelSearchQuery() + resetAvailableVoices() + resetSpeechProviderError() + resetIsLoadingSpeechProviderVoices() + } + return { // State configured, @@ -234,5 +249,6 @@ export const useSpeechStore = defineStore('speech', () => { loadVoicesForProvider, getVoicesForProvider, generateSSML, + resetState, } }) diff --git a/packages/stage-ui/src/stores/modules/twitter.ts b/packages/stage-ui/src/stores/modules/twitter.ts index a6c890c8a..c08228e8e 100644 --- a/packages/stage-ui/src/stores/modules/twitter.ts +++ b/packages/stage-ui/src/stores/modules/twitter.ts @@ -1,17 +1,17 @@ -import { useLocalStorage } from '@vueuse/core' import { defineStore } from 'pinia' import { computed } from 'vue' +import { createResettableLocalStorage } from '../../utils/resettable' import { useConfiguratorByModsChannelServer } from '../configurator' export const useTwitterStore = defineStore('twitter', () => { const configurator = useConfiguratorByModsChannelServer() - const enabled = useLocalStorage('settings/twitter/enabled', false) - const apiKey = useLocalStorage('settings/twitter/api-key', '') - const apiSecret = useLocalStorage('settings/twitter/api-secret', '') - const accessToken = useLocalStorage('settings/twitter/access-token', '') - const accessTokenSecret = useLocalStorage('settings/twitter/access-token-secret', '') + const [enabled, resetEnabled] = createResettableLocalStorage('settings/twitter/enabled', false) + const [apiKey, resetApiKey] = createResettableLocalStorage('settings/twitter/api-key', '') + const [apiSecret, resetApiSecret] = createResettableLocalStorage('settings/twitter/api-secret', '') + const [accessToken, resetAccessToken] = createResettableLocalStorage('settings/twitter/access-token', '') + const [accessTokenSecret, resetAccessTokenSecret] = createResettableLocalStorage('settings/twitter/access-token-secret', '') function saveSettings() { // Data is automatically saved to localStorage via useLocalStorage @@ -29,6 +29,15 @@ export const useTwitterStore = defineStore('twitter', () => { return !!(apiKey.value.trim() && apiSecret.value.trim() && accessToken.value.trim() && accessTokenSecret.value.trim()) }) + function resetState() { + resetEnabled() + resetApiKey() + resetApiSecret() + resetAccessToken() + resetAccessTokenSecret() + saveSettings() + } + return { enabled, apiKey, @@ -37,5 +46,6 @@ export const useTwitterStore = defineStore('twitter', () => { accessTokenSecret, configured, saveSettings, + resetState, } }) diff --git a/packages/stage-ui/src/stores/plugins/chat-context-bridge.ts b/packages/stage-ui/src/stores/plugins/chat-context-bridge.ts index 725324c53..6851b9ebf 100644 --- a/packages/stage-ui/src/stores/plugins/chat-context-bridge.ts +++ b/packages/stage-ui/src/stores/plugins/chat-context-bridge.ts @@ -1,9 +1,10 @@ import type { ContextMessage } from '@proj-airi/server-sdk' +import type { ContextPayload } from '../chat' + import { useBroadcastChannel } from '@vueuse/core' import { watch } from 'vue' -import type { ContextPayload } from '../chat' import { CONTEXT_CHANNEL_NAME, useChatStore } from '../chat' import { useModsChannelServerStore } from '../mods/api/channel-server' diff --git a/packages/stage-ui/src/stores/settings.ts b/packages/stage-ui/src/stores/settings.ts index c4329b04a..2640d8447 100644 --- a/packages/stage-ui/src/stores/settings.ts +++ b/packages/stage-ui/src/stores/settings.ts @@ -2,11 +2,12 @@ import type { DisplayModel } from './display-models' import messages from '@proj-airi/i18n/locales' -import { useEventListener, useLocalStorage } from '@vueuse/core' +import { useEventListener } from '@vueuse/core' import { converter } from 'culori' import { defineStore } from 'pinia' -import { onMounted, ref, watch } from 'vue' +import { onMounted, watch } from 'vue' +import { createResettableLocalStorage, createResettableRef } from '../utils/resettable' import { useAudioDevice } from './audio' import { DisplayModelFormat, useDisplayModelsStore } from './display-models' @@ -37,12 +38,12 @@ const getHueFrom = (color?: string) => color ? convert(color)?.h : DEFAULT_THEME export const useSettings = defineStore('settings', () => { const displayModelsStore = useDisplayModelsStore() - const language = useLocalStorage('settings/language', '') + const [language, resetLanguage] = createResettableLocalStorage('settings/language', '') - const stageModelSelected = useLocalStorage('settings/stage/model', 'preset-live2d-1') - const stageModelSelectedDisplayModel = ref() - const stageModelSelectedUrl = ref() - const stageModelRenderer = ref<'live2d' | 'vrm' | 'disabled'>() + const [stageModelSelected, resetStageModelSelected] = createResettableLocalStorage('settings/stage/model', 'preset-live2d-1') + const [stageModelSelectedDisplayModel, resetStageModelSelectedDisplayModel] = createResettableRef(undefined) + const [stageModelSelectedUrl, resetStageModelSelectedUrl] = createResettableRef(undefined) + const [stageModelRenderer, resetStageModelRenderer] = createResettableRef<'live2d' | 'vrm' | 'disabled' | undefined>(undefined) async function updateStageModel() { if (!stageModelSelected.value) { @@ -96,20 +97,20 @@ export const useSettings = defineStore('settings', () => { } }) - const stageViewControlsEnabled = ref(false) + const [stageViewControlsEnabled, resetStageViewControlsEnabled] = createResettableRef(false) - const live2dDisableFocus = useLocalStorage('settings/live2d/disable-focus', false) - const live2dIdleAnimationEnabled = useLocalStorage('settings/live2d/idle-animation-enabled', true) - const live2dAutoBlinkEnabled = useLocalStorage('settings/live2d/auto-blink-enabled', true) - const live2dShadowEnabled = useLocalStorage('settings/live2d/shadow-enabled', true) + const [live2dDisableFocus, resetLive2dDisableFocus] = createResettableLocalStorage('settings/live2d/disable-focus', false) + const [live2dIdleAnimationEnabled, resetLive2dIdleAnimationEnabled] = createResettableLocalStorage('settings/live2d/idle-animation-enabled', true) + const [live2dAutoBlinkEnabled, resetLive2dAutoBlinkEnabled] = createResettableLocalStorage('settings/live2d/auto-blink-enabled', true) + const [live2dShadowEnabled, resetLive2dShadowEnabled] = createResettableLocalStorage('settings/live2d/shadow-enabled', true) - const disableTransitions = useLocalStorage('settings/disable-transitions', true) - const usePageSpecificTransitions = useLocalStorage('settings/use-page-specific-transitions', true) + const [disableTransitions, resetDisableTransitions] = createResettableLocalStorage('settings/disable-transitions', true) + const [usePageSpecificTransitions, resetUsePageSpecificTransitions] = createResettableLocalStorage('settings/use-page-specific-transitions', true) - const themeColorsHue = useLocalStorage('settings/theme/colors/hue', DEFAULT_THEME_COLORS_HUE) - const themeColorsHueDynamic = useLocalStorage('settings/theme/colors/hue-dynamic', false) + const [themeColorsHue, resetThemeColorsHue] = createResettableLocalStorage('settings/theme/colors/hue', DEFAULT_THEME_COLORS_HUE) + const [themeColorsHueDynamic, resetThemeColorsHueDynamic] = createResettableLocalStorage('settings/theme/colors/hue-dynamic', false) - const allowVisibleOnAllWorkspaces = useLocalStorage('settings/allow-visible-on-all-workspaces', true) + const [allowVisibleOnAllWorkspaces, resetAllowVisibleOnAllWorkspaces] = createResettableLocalStorage('settings/allow-visible-on-all-workspaces', true) function getLanguage() { let language = localStorage.getItem('settings/language') @@ -158,6 +159,33 @@ export const useSettings = defineStore('settings', () => { return hueDifference < 0.01 || hueDifference > 359.99 } + async function resetState() { + if (stageModelSelectedUrl.value) + URL.revokeObjectURL(stageModelSelectedUrl.value) + + resetLanguage() + resetStageModelSelected() + resetStageModelSelectedDisplayModel() + resetStageModelSelectedUrl() + resetStageModelRenderer() + resetStageViewControlsEnabled() + + resetLive2dDisableFocus() + resetLive2dIdleAnimationEnabled() + resetLive2dAutoBlinkEnabled() + resetLive2dShadowEnabled() + + resetDisableTransitions() + resetUsePageSpecificTransitions() + + resetThemeColorsHue() + resetThemeColorsHueDynamic() + + resetAllowVisibleOnAllWorkspaces() + + await updateStageModel() + } + onMounted(() => language.value = getLanguage()) return { @@ -185,14 +213,15 @@ export const useSettings = defineStore('settings', () => { isColorSelectedForPrimary, initializeStageModel, updateStageModel, + resetState, } }) export const useSettingsAudioDevice = defineStore('settings-audio-devices', () => { const { audioInputs, deviceConstraints, selectedAudioInput: selectedAudioInputNonPersist, startStream, stopStream, stream, askPermission } = useAudioDevice() - const selectedAudioInputPersist = useLocalStorage('settings/audio/input', selectedAudioInputNonPersist.value) - const selectedAudioInputEnabledPersist = useLocalStorage('settings/audio/input/enabled', false) + const [selectedAudioInputPersist, resetSelectedAudioInputPersist] = createResettableLocalStorage('settings/audio/input', selectedAudioInputNonPersist.value) + const [selectedAudioInputEnabledPersist, resetSelectedAudioInputEnabledPersist] = createResettableLocalStorage('settings/audio/input/enabled', false) watch(selectedAudioInputPersist, (newValue) => { selectedAudioInputNonPersist.value = newValue @@ -216,6 +245,13 @@ export const useSettingsAudioDevice = defineStore('settings-audio-devices', () = } }) + function resetState() { + resetSelectedAudioInputPersist() + selectedAudioInputNonPersist.value = '' + resetSelectedAudioInputEnabledPersist() + stopStream() + } + return { audioInputs, deviceConstraints, @@ -227,5 +263,6 @@ export const useSettingsAudioDevice = defineStore('settings-audio-devices', () = askPermission, startStream, stopStream, + resetState, } }) diff --git a/packages/stage-ui/src/utils/resettable.ts b/packages/stage-ui/src/utils/resettable.ts new file mode 100644 index 000000000..e2ee99fdb --- /dev/null +++ b/packages/stage-ui/src/utils/resettable.ts @@ -0,0 +1,36 @@ +import { useLocalStorage } from '@vueuse/core' +import { ref } from 'vue' + +const structuredCloneFn: ((value: T) => T) | undefined = (globalThis as any).structuredClone + +function cloneDefault(value: T): T { + if (typeof structuredCloneFn === 'function') + return structuredCloneFn(value) + + if (value instanceof Map) + return new Map(value) as unknown as T + + if (Array.isArray(value)) + return value.map(item => cloneDefault(item)) as unknown as T + + if (value && typeof value === 'object') + return JSON.parse(JSON.stringify(value)) as T + + return value +} + +export function createResettableRef(initialValue: T) { + const state = ref(cloneDefault(initialValue)) + const reset = () => { + state.value = cloneDefault(initialValue) + } + return [state, reset] as const +} + +export function createResettableLocalStorage(key: string, initialValue: T) { + const state = useLocalStorage(key, cloneDefault(initialValue)) + const reset = () => { + state.value = cloneDefault(initialValue) + } + return [state, reset] as const +} diff --git a/plugins/airi-plugin-vscode/package.json b/plugins/airi-plugin-vscode/package.json index 122ed4ad2..b0fd0b5b5 100644 --- a/plugins/airi-plugin-vscode/package.json +++ b/plugins/airi-plugin-vscode/package.json @@ -75,4 +75,4 @@ "tinyexec": "^1.0.2", "vscode-ext-gen": "^1.4.0" } -} \ No newline at end of file +}