mirror of
https://github.com/moeru-ai/airi.git
synced 2026-10-30 19:34:10 +08:00
fix(stage-ui): keep stores off disposed Pinias (#3201)
This commit is contained in:
@@ -276,3 +276,39 @@ describe('issue #3124 restores the active Character after a restart', () => {
|
||||
expect(localStorage.getItem(activeCardIdKey)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
// ROOT CAUSE:
|
||||
//
|
||||
// `initialize()` looked up the module stores after an `await`, through the active Pinia.
|
||||
// A stray action of a disposed Pinia could make that one active, so CI failed with `Pinia destroyed`.
|
||||
//
|
||||
// We fixed this by passing the card store's own Pinia to these lookups.
|
||||
describe('airi card store keeps to its own Pinia', () => {
|
||||
afterEach(() => {
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
it('initializes while another Pinia becomes active', async () => {
|
||||
const stale = createPinia()
|
||||
createApp({}).use(stale)
|
||||
disposePinia(stale)
|
||||
|
||||
const pinia = createPinia()
|
||||
// Stands in for that stray action: every action of this Pinia hands the active Pinia back to the stale one.
|
||||
pinia.use(({ store }) => {
|
||||
store.$onAction(({ after, onError }) => {
|
||||
after(() => setActivePinia(stale))
|
||||
onError(() => setActivePinia(stale))
|
||||
})
|
||||
})
|
||||
createApp({}).use(pinia).use(PiniaColada)
|
||||
setActivePinia(pinia)
|
||||
const cardStore = useAiriCardStore()
|
||||
const initialized = cardStore.initialize()
|
||||
setActivePinia(stale)
|
||||
|
||||
await expect(initialized).resolves.toBeUndefined()
|
||||
expect(cardStore.cards.has('default')).toBe(true)
|
||||
disposePinia(pinia)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -226,10 +226,10 @@ export const useAiriCardStore = defineStore('airi-card', () => {
|
||||
|
||||
function useRuntimeModuleStores() {
|
||||
return {
|
||||
artistry: useArtistryStore(),
|
||||
consciousness: useConsciousnessStore(),
|
||||
speech: useSpeechStore(),
|
||||
vision: useVisionStore(),
|
||||
artistry: useArtistryStore(pinia),
|
||||
consciousness: useConsciousnessStore(pinia),
|
||||
speech: useSpeechStore(pinia),
|
||||
vision: useVisionStore(pinia),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +353,7 @@ export const useAiriCardStore = defineStore('airi-card', () => {
|
||||
/** Loads the effective auth-owned voice catalog after card setup finishes. */
|
||||
async function loadAuthenticatedSpeechVoices(): Promise<void> {
|
||||
const { speech } = useRuntimeModuleStores()
|
||||
const provider = useProviderConfigStore().providers[speech.activeSpeechProvider]
|
||||
const provider = useProviderConfigStore(pinia).providers[speech.activeSpeechProvider]
|
||||
if (provider?.configuredBy !== 'authentication')
|
||||
return
|
||||
|
||||
@@ -647,14 +647,14 @@ export const useAiriCardStore = defineStore('airi-card', () => {
|
||||
*/
|
||||
async function selectActiveCardVisionProvider(provider: string) {
|
||||
await pendingAuthenticationSetup
|
||||
const vision = useVisionStore()
|
||||
const vision = useVisionStore(pinia)
|
||||
vision.enabled = true
|
||||
if (vision.activeProvider === provider)
|
||||
return true
|
||||
vision.activeProvider = provider
|
||||
vision.resetModelSelection()
|
||||
await vision.loadModelsForProvider(provider)
|
||||
const model = useProviderStore().getDefaultModelForProvider(provider) ?? ''
|
||||
const model = useProviderStore(pinia).getDefaultModelForProvider(provider) ?? ''
|
||||
return await updateActiveCardVision({ provider, model })
|
||||
}
|
||||
|
||||
@@ -1383,7 +1383,7 @@ export const useAiriCardStore = defineStore('airi-card', () => {
|
||||
),
|
||||
},
|
||||
}
|
||||
const providers = useProviderConfigStore().providers
|
||||
const providers = useProviderConfigStore(pinia).providers
|
||||
for (const module of ['consciousness', 'vision', 'speech'] as const) {
|
||||
const provider = providers[resolved[module].provider]
|
||||
// Logout disables authenticated providers without deleting card choices.
|
||||
@@ -1422,7 +1422,7 @@ export const useAiriCardStore = defineStore('airi-card', () => {
|
||||
rememberInheritedSettings()
|
||||
// The runtime holds a mix of two cards until the write completes. See followInheritedSettings.
|
||||
appliedModules = undefined
|
||||
const artistry = useArtistryStore()
|
||||
const artistry = useArtistryStore(pinia)
|
||||
|
||||
artistry.resetToGlobal()
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ export const useSettingsStageModel = defineStore('settings-stage-model', () => {
|
||||
let disposed = false
|
||||
onScopeDispose(() => {
|
||||
disposed = true
|
||||
// Cancel a pending update. Its next store action would make this disposed Pinia active again.
|
||||
stageModelUpdateSequence++
|
||||
})
|
||||
const stageModelSelected = computed<string>({
|
||||
get: () => stageModelSelectedState.value,
|
||||
|
||||
Reference in New Issue
Block a user