refactor(stage-ui,stage-web,stage-tamagotchi): rename as useContextBridgeStore

This commit is contained in:
Neko Ayaka
2025-12-27 17:12:22 +08:00
parent 0dc4e96663
commit fb2da39384
5 changed files with 156 additions and 153 deletions
+4 -4
View File
@@ -1,9 +1,9 @@
<script setup lang="ts">
import { defineInvoke, defineInvokeHandler } from '@moeru/eventa'
import { useContextBridge } from '@proj-airi/stage-ui/composables'
import { useSharedAnalyticsStore } from '@proj-airi/stage-ui/stores/analytics'
import { useDisplayModelsStore } from '@proj-airi/stage-ui/stores/display-models'
import { useModsServerChannelStore } from '@proj-airi/stage-ui/stores/mods/api/channel-server'
import { useContextBridgeStore } from '@proj-airi/stage-ui/stores/mods/api/context-bridge'
import { useAiriCardStore } from '@proj-airi/stage-ui/stores/modules/airi-card'
import { useOnboardingStore } from '@proj-airi/stage-ui/stores/onboarding'
import { useSettings } from '@proj-airi/stage-ui/stores/settings'
@@ -19,7 +19,7 @@ import { themeColorFromValue, useThemeColor } from './composables/theme-color'
const { isDark: dark } = useTheme()
const i18n = useI18n()
const contextBridge = useContextBridge()
const contextBridgeStore = useContextBridgeStore()
const displayModelsStore = useDisplayModelsStore()
const settingsStore = useSettings()
const { language, themeColorsHue, themeColorsHueDynamic } = storeToRefs(settingsStore)
@@ -49,7 +49,7 @@ onMounted(async () => {
await settingsStore.initializeStageModel()
await serverChannelStore.initialize({ possibleEvents: ['ui:configure'] }).catch((err) => { console.error('Failed to initialize Mods Server Channel in App.vue:', err) })
await contextBridge.initialize()
await contextBridgeStore.initialize()
const context = useElectronEventaContext()
const startTrackingCursorPoint = defineInvoke(context.value, electronStartTrackMousePosition)
@@ -67,7 +67,7 @@ watch(themeColorsHueDynamic, () => {
document.documentElement.classList.toggle('dynamic-hue', themeColorsHueDynamic.value)
}, { immediate: true })
onUnmounted(() => contextBridge.dispose())
onUnmounted(() => contextBridgeStore.dispose())
</script>
<template>
+4 -4
View File
@@ -1,9 +1,9 @@
<script setup lang="ts">
import { OnboardingDialog, ToasterRoot } from '@proj-airi/stage-ui/components'
import { useContextBridge } from '@proj-airi/stage-ui/composables'
import { useSharedAnalyticsStore } from '@proj-airi/stage-ui/stores/analytics'
import { useDisplayModelsStore } from '@proj-airi/stage-ui/stores/display-models'
import { useModsServerChannelStore } from '@proj-airi/stage-ui/stores/mods/api/channel-server'
import { useContextBridgeStore } from '@proj-airi/stage-ui/stores/mods/api/context-bridge'
import { useAiriCardStore } from '@proj-airi/stage-ui/stores/modules/airi-card'
import { useOnboardingStore } from '@proj-airi/stage-ui/stores/onboarding'
import { useSettings } from '@proj-airi/stage-ui/stores/settings'
@@ -21,7 +21,7 @@ import 'vue-sonner/style.css'
usePWAStore()
const contextBridge = useContextBridge()
const contextBridgeStore = useContextBridgeStore()
const i18n = useI18n()
const displayModelsStore = useDisplayModelsStore()
const settingsStore = useSettings()
@@ -75,14 +75,14 @@ onMounted(async () => {
onboardingStore.initializeSetupCheck()
await serverChannelStore.initialize({ possibleEvents: ['ui:configure'] }).catch((err) => { console.error('Failed to initialize Mods Server Channel in App.vue:', err) })
await contextBridge.initialize()
await contextBridgeStore.initialize()
await displayModelsStore.loadDisplayModelsFromIndexedDB()
await settingsStore.initializeStageModel()
})
onUnmounted(() => {
contextBridge.dispose()
contextBridgeStore.dispose()
})
// Handle first-time setup events
@@ -6,5 +6,4 @@ export * from './micvad'
export * from './queues'
export * from './use-analytics'
export * from './use-build-info'
export * from './use-context-bridge'
export * from './whisper'
@@ -1,144 +0,0 @@
import type { ChatStreamEvent, ContextMessage } from '../types/chat'
import { useBroadcastChannel } from '@vueuse/core'
import { Mutex } from 'es-toolkit'
import { watch } from 'vue'
import { CHAT_STREAM_CHANNEL_NAME, CONTEXT_CHANNEL_NAME, useChatStore } from '../stores/chat'
import { useModsServerChannelStore } from '../stores/mods/api/channel-server'
const mutex = new Mutex()
export function useContextBridge() {
const chatStore = useChatStore()
const serverChannelStore = useModsServerChannelStore()
const { post: broadcastContext, data: incomingContext } = useBroadcastChannel<ContextMessage, ContextMessage>({ name: CONTEXT_CHANNEL_NAME })
const { post: broadcastStreamEvent, data: incomingStreamEvent } = useBroadcastChannel<ChatStreamEvent, ChatStreamEvent>({ name: CHAT_STREAM_CHANNEL_NAME })
let disposeHookFns = [] as Array<() => void>
return {
initialize: async () => {
await mutex.acquire()
try {
let isProcessingRemoteStream = false
const { stop } = watch(incomingContext, (event) => {
if (event)
chatStore.ingestContextMessage(event)
})
disposeHookFns.push(stop)
disposeHookFns.push(serverChannelStore.onContextUpdate((event) => {
chatStore.ingestContextMessage({ source: event.source, createdAt: Date.now(), ...event.data })
broadcastContext(event.data as ContextMessage)
}))
disposeHookFns.push(
chatStore.onBeforeMessageComposed(async (message) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'before-compose', message, sessionId: chatStore.activeSessionId })
}),
chatStore.onAfterMessageComposed(async (message) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'after-compose', message, sessionId: chatStore.activeSessionId })
}),
chatStore.onBeforeSend(async (message) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'before-send', message, sessionId: chatStore.activeSessionId })
}),
chatStore.onAfterSend(async (message) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'after-send', message, sessionId: chatStore.activeSessionId })
}),
chatStore.onTokenLiteral(async (literal) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'token-literal', literal, sessionId: chatStore.activeSessionId })
}),
chatStore.onTokenSpecial(async (special) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'token-special', special, sessionId: chatStore.activeSessionId })
}),
chatStore.onStreamEnd(async () => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'stream-end', sessionId: chatStore.activeSessionId })
}),
chatStore.onAssistantResponseEnd(async (message) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'assistant-end', message, sessionId: chatStore.activeSessionId })
}),
)
const { stop: stopIncomingStreamWatch } = watch(incomingStreamEvent, async (event) => {
if (!event)
return
isProcessingRemoteStream = true
try {
if (event.sessionId && chatStore.activeSessionId !== event.sessionId)
chatStore.setActiveSession(event.sessionId)
switch (event.type) {
case 'before-compose':
await chatStore.emitBeforeMessageComposedHooks(event.message)
break
case 'after-compose':
await chatStore.emitAfterMessageComposedHooks(event.message)
break
case 'before-send':
await chatStore.emitBeforeSendHooks(event.message)
break
case 'after-send':
await chatStore.emitAfterSendHooks(event.message)
break
case 'token-literal':
await chatStore.emitTokenLiteralHooks(event.literal)
break
case 'token-special':
await chatStore.emitTokenSpecialHooks(event.special)
break
case 'stream-end':
await chatStore.emitStreamEndHooks()
break
case 'assistant-end':
await chatStore.emitAssistantResponseEndHooks(event.message)
break
}
}
finally {
isProcessingRemoteStream = false
}
})
disposeHookFns.push(stopIncomingStreamWatch)
}
finally {
mutex.release()
}
},
dispose: async () => {
await mutex.acquire()
try {
for (const fn of disposeHookFns) {
fn()
}
}
finally {
mutex.release()
}
disposeHookFns = []
},
}
}
@@ -0,0 +1,148 @@
import type { ChatStreamEvent, ContextMessage } from '../../../types/chat'
import { useBroadcastChannel } from '@vueuse/core'
import { Mutex } from 'es-toolkit'
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import { CHAT_STREAM_CHANNEL_NAME, CONTEXT_CHANNEL_NAME, useChatStore } from '../../chat'
import { useModsServerChannelStore } from './channel-server'
export const useContextBridgeStore = defineStore('mods:api:context-bridge', () => {
const mutex = new Mutex()
const chatStore = useChatStore()
const serverChannelStore = useModsServerChannelStore()
const { post: broadcastContext, data: incomingContext } = useBroadcastChannel<ContextMessage, ContextMessage>({ name: CONTEXT_CHANNEL_NAME })
const { post: broadcastStreamEvent, data: incomingStreamEvent } = useBroadcastChannel<ChatStreamEvent, ChatStreamEvent>({ name: CHAT_STREAM_CHANNEL_NAME })
const disposeHookFns = ref<Array<() => void>>([])
async function initialize() {
await mutex.acquire()
try {
let isProcessingRemoteStream = false
const { stop } = watch(incomingContext, (event) => {
if (event)
chatStore.ingestContextMessage(event)
})
disposeHookFns.value.push(stop)
disposeHookFns.value.push(serverChannelStore.onContextUpdate((event) => {
chatStore.ingestContextMessage({ source: event.source, createdAt: Date.now(), ...event.data })
broadcastContext(event.data as ContextMessage)
}))
disposeHookFns.value.push(
chatStore.onBeforeMessageComposed(async (message) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'before-compose', message, sessionId: chatStore.activeSessionId })
}),
chatStore.onAfterMessageComposed(async (message) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'after-compose', message, sessionId: chatStore.activeSessionId })
}),
chatStore.onBeforeSend(async (message) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'before-send', message, sessionId: chatStore.activeSessionId })
}),
chatStore.onAfterSend(async (message) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'after-send', message, sessionId: chatStore.activeSessionId })
}),
chatStore.onTokenLiteral(async (literal) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'token-literal', literal, sessionId: chatStore.activeSessionId })
}),
chatStore.onTokenSpecial(async (special) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'token-special', special, sessionId: chatStore.activeSessionId })
}),
chatStore.onStreamEnd(async () => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'stream-end', sessionId: chatStore.activeSessionId })
}),
chatStore.onAssistantResponseEnd(async (message) => {
if (isProcessingRemoteStream)
return
broadcastStreamEvent({ type: 'assistant-end', message, sessionId: chatStore.activeSessionId })
}),
)
const { stop: stopIncomingStreamWatch } = watch(incomingStreamEvent, async (event) => {
if (!event)
return
isProcessingRemoteStream = true
try {
if (event.sessionId && chatStore.activeSessionId !== event.sessionId)
chatStore.setActiveSession(event.sessionId)
switch (event.type) {
case 'before-compose':
await chatStore.emitBeforeMessageComposedHooks(event.message)
break
case 'after-compose':
await chatStore.emitAfterMessageComposedHooks(event.message)
break
case 'before-send':
await chatStore.emitBeforeSendHooks(event.message)
break
case 'after-send':
await chatStore.emitAfterSendHooks(event.message)
break
case 'token-literal':
await chatStore.emitTokenLiteralHooks(event.literal)
break
case 'token-special':
await chatStore.emitTokenSpecialHooks(event.special)
break
case 'stream-end':
await chatStore.emitStreamEndHooks()
break
case 'assistant-end':
await chatStore.emitAssistantResponseEndHooks(event.message)
break
}
}
finally {
isProcessingRemoteStream = false
}
})
disposeHookFns.value.push(stopIncomingStreamWatch)
}
finally {
mutex.release()
}
}
async function dispose() {
await mutex.acquire()
try {
for (const fn of disposeHookFns.value) {
fn()
}
}
finally {
mutex.release()
}
disposeHookFns.value = []
}
return {
initialize,
dispose,
}
})