mirror of
https://github.com/moeru-ai/airi.git
synced 2026-08-14 00:48:06 +00:00
fix(stage-tamagotchi): restore chat list actions
Route persistent session mutations through the authority while keeping per-window selection local. Force the standalone chat window to use the dialog interaction boundary. Fixes #2085
This commit is contained in:
@@ -5,7 +5,10 @@ import { shallowRef } from 'vue'
|
||||
import InteractiveArea from '../components/InteractiveArea.vue'
|
||||
import WindowTitleBar from '../components/Window/TitleBar.vue'
|
||||
|
||||
import { useChatSyncStore } from '../stores/chat-sync'
|
||||
|
||||
const sessionsDrawerOpen = shallowRef(false)
|
||||
const chatSync = useChatSyncStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -20,7 +23,12 @@ const sessionsDrawerOpen = shallowRef(false)
|
||||
class="interaction-area block"
|
||||
h-full w-full p-4 transition="opacity duration-250"
|
||||
/>
|
||||
<ChatSessionsDrawer v-model="sessionsDrawerOpen" />
|
||||
<ChatSessionsDrawer
|
||||
v-model="sessionsDrawerOpen"
|
||||
presentation="dialog"
|
||||
:create-session="chatSync.requestCreateSession"
|
||||
:delete-session="chatSync.requestDeleteSession"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import type { ChatSessionsExport } from '@proj-airi/stage-ui/types/chat-session'
|
||||
import type { ChatSessionMeta, ChatSessionsExport } from '@proj-airi/stage-ui/types/chat-session'
|
||||
import type { Tool } from '@xsai/shared-chat'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
@@ -105,9 +105,12 @@ function assistantMessage(content: string): MockChatMessage {
|
||||
interface MockState {
|
||||
activeSessionId: Ref<string>
|
||||
sessionMessages: Ref<Record<string, MockChatMessage[]>>
|
||||
sessionMetas: Ref<Record<string, unknown>>
|
||||
sessionMetas: Ref<Record<string, ChatSessionMeta>>
|
||||
applyRemoteSnapshot: ReturnType<typeof vi.fn>
|
||||
createSession: ReturnType<typeof vi.fn>
|
||||
deleteSession: ReturnType<typeof vi.fn>
|
||||
setSessionMessages: ReturnType<typeof vi.fn>
|
||||
setActiveSession: ReturnType<typeof vi.fn>
|
||||
getSessionMessages: ReturnType<typeof vi.fn>
|
||||
importSessions: MockImportSessions
|
||||
ingest: ReturnType<typeof vi.fn>
|
||||
@@ -121,6 +124,8 @@ vi.mock('@proj-airi/stage-ui/stores/chat/session-store', () => ({
|
||||
sessionMessages: mockState.sessionMessages,
|
||||
sessionMetas: mockState.sessionMetas,
|
||||
applyRemoteSnapshot: mockState.applyRemoteSnapshot,
|
||||
createSession: mockState.createSession,
|
||||
deleteSession: mockState.deleteSession,
|
||||
getSnapshot: vi.fn(() => ({
|
||||
activeSessionId: mockState.activeSessionId.value,
|
||||
sessionMessages: mockState.sessionMessages.value,
|
||||
@@ -128,6 +133,7 @@ vi.mock('@proj-airi/stage-ui/stores/chat/session-store', () => ({
|
||||
})),
|
||||
getSessionMessages: mockState.getSessionMessages,
|
||||
importSessions: mockState.importSessions,
|
||||
setActiveSession: mockState.setActiveSession,
|
||||
setSessionMessages: mockState.setSessionMessages,
|
||||
}),
|
||||
}))
|
||||
@@ -208,11 +214,19 @@ describe('useChatSyncStore', async () => {
|
||||
const sessionMessages = ref<Record<string, MockChatMessage[]>>({
|
||||
'session-1': [{ role: 'system', content: 'init' }],
|
||||
})
|
||||
const sessionMetas = ref<Record<string, unknown>>({})
|
||||
const sessionMetas = ref<Record<string, ChatSessionMeta>>({
|
||||
'session-1': {
|
||||
sessionId: 'session-1',
|
||||
userId: 'local',
|
||||
characterId: 'default',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
},
|
||||
})
|
||||
const applyRemoteSnapshot = vi.fn((snapshot: {
|
||||
activeSessionId: string
|
||||
sessionMessages: Record<string, MockChatMessage[]>
|
||||
sessionMetas: Record<string, unknown>
|
||||
sessionMetas: Record<string, ChatSessionMeta>
|
||||
}) => {
|
||||
activeSessionId.value = snapshot.activeSessionId
|
||||
sessionMessages.value = snapshot.sessionMessages
|
||||
@@ -222,6 +236,29 @@ describe('useChatSyncStore', async () => {
|
||||
const setSessionMessages = vi.fn((sessionId: string, next: MockChatMessage[]) => {
|
||||
sessionMessages.value[sessionId] = next
|
||||
})
|
||||
const setActiveSession = vi.fn((sessionId: string) => {
|
||||
activeSessionId.value = sessionId
|
||||
})
|
||||
const createSession = vi.fn(async (characterId: string, options?: { setActive?: boolean }) => {
|
||||
const sessionId = 'session-2'
|
||||
sessionMetas.value[sessionId] = {
|
||||
sessionId,
|
||||
userId: 'local',
|
||||
characterId,
|
||||
createdAt: 2,
|
||||
updatedAt: 2,
|
||||
}
|
||||
sessionMessages.value[sessionId] = [{ role: 'system', content: 'new session' }]
|
||||
if (options?.setActive !== false)
|
||||
activeSessionId.value = sessionId
|
||||
return sessionId
|
||||
})
|
||||
const deleteSession = vi.fn(async (sessionId: string) => {
|
||||
delete sessionMetas.value[sessionId]
|
||||
delete sessionMessages.value[sessionId]
|
||||
if (activeSessionId.value === sessionId)
|
||||
activeSessionId.value = 'session-1'
|
||||
})
|
||||
|
||||
const getSessionMessages = vi.fn((sessionId: string) => sessionMessages.value[sessionId] ?? [])
|
||||
const importSessions = vi.fn<(payload: ChatSessionsExport) => Promise<void>>().mockResolvedValue(undefined)
|
||||
@@ -244,7 +281,10 @@ describe('useChatSyncStore', async () => {
|
||||
sessionMessages,
|
||||
sessionMetas,
|
||||
applyRemoteSnapshot,
|
||||
createSession,
|
||||
deleteSession,
|
||||
setSessionMessages,
|
||||
setActiveSession,
|
||||
getSessionMessages,
|
||||
importSessions,
|
||||
ingest,
|
||||
@@ -471,11 +511,25 @@ describe('useChatSyncStore', async () => {
|
||||
store.dispose()
|
||||
})
|
||||
|
||||
it('keeps the follower chat window on its local session while applying remote snapshots', async () => {
|
||||
// https://github.com/moeru-ai/airi/issues/2085
|
||||
it('issue #2085: keeps a follower-selected session when the authority has not loaded its messages', async () => {
|
||||
// ROOT CAUSE:
|
||||
//
|
||||
// The authority snapshot can know a session through sessionMetas without
|
||||
// having loaded that session's messages. The follower previously checked
|
||||
// only snapshot.sessionMessages, so the next heartbeat replaced its local
|
||||
// selection even though the selected session still existed.
|
||||
mockState.activeSessionId.value = 'session-2'
|
||||
mockState.sessionMessages.value = {
|
||||
'session-2': [{ role: 'system', content: 'chat-window' }],
|
||||
}
|
||||
mockState.sessionMetas.value['session-2'] = {
|
||||
sessionId: 'session-2',
|
||||
userId: 'local',
|
||||
characterId: 'default',
|
||||
createdAt: 2,
|
||||
updatedAt: 2,
|
||||
}
|
||||
|
||||
const store = useChatSyncStore()
|
||||
store.initialize('follower')
|
||||
@@ -488,9 +542,23 @@ describe('useChatSyncStore', async () => {
|
||||
activeSessionId: 'session-1',
|
||||
sessionMessages: {
|
||||
'session-1': [{ role: 'system', content: 'main-window' }],
|
||||
'session-2': [{ role: 'system', content: 'chat-window' }, { role: 'user', content: 'retry me' }],
|
||||
},
|
||||
sessionMetas: {},
|
||||
sessionMetas: {
|
||||
'session-1': {
|
||||
sessionId: 'session-1',
|
||||
userId: 'local',
|
||||
characterId: 'default',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
},
|
||||
'session-2': {
|
||||
sessionId: 'session-2',
|
||||
userId: 'local',
|
||||
characterId: 'default',
|
||||
createdAt: 2,
|
||||
updatedAt: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -501,13 +569,47 @@ describe('useChatSyncStore', async () => {
|
||||
expect(mockState.activeSessionId.value).toBe('session-2')
|
||||
expect(mockState.sessionMessages.value['session-2']).toEqual([
|
||||
{ role: 'system', content: 'chat-window' },
|
||||
{ role: 'user', content: 'retry me' },
|
||||
])
|
||||
|
||||
authority.close()
|
||||
store.dispose()
|
||||
})
|
||||
|
||||
// https://github.com/moeru-ai/airi/issues/2085
|
||||
it('issue #2085: creates a session through the authority and activates it only in the follower', async () => {
|
||||
const { authorityStore, followerStore } = initializeAuthorityAndFollower()
|
||||
|
||||
await expect(followerStore.requestCreateSession('default')).resolves.toBe('session-2')
|
||||
|
||||
expect(mockState.createSession).toHaveBeenCalledWith('default', { setActive: false })
|
||||
expect(mockState.setActiveSession).toHaveBeenCalledWith('session-2')
|
||||
|
||||
authorityStore.dispose()
|
||||
followerStore.dispose()
|
||||
})
|
||||
|
||||
// https://github.com/moeru-ai/airi/issues/2085
|
||||
it('issue #2085: deletes a session through the authority', async () => {
|
||||
mockState.sessionMetas.value['session-2'] = {
|
||||
sessionId: 'session-2',
|
||||
userId: 'local',
|
||||
characterId: 'default',
|
||||
createdAt: 2,
|
||||
updatedAt: 2,
|
||||
}
|
||||
mockState.sessionMessages.value['session-2'] = [{ role: 'system', content: 'remove me' }]
|
||||
|
||||
const { authorityStore, followerStore } = initializeAuthorityAndFollower()
|
||||
|
||||
await expect(followerStore.requestDeleteSession('session-2')).resolves.toBeUndefined()
|
||||
|
||||
expect(mockState.deleteSession).toHaveBeenCalledWith('session-2')
|
||||
expect(mockState.sessionMetas.value['session-2']).toBeUndefined()
|
||||
|
||||
authorityStore.dispose()
|
||||
followerStore.dispose()
|
||||
})
|
||||
|
||||
it('sends spotlight commands through shared request and response messages', async () => {
|
||||
mockState.ingest.mockImplementationOnce(async () => {
|
||||
mockState.sessionMessages.value['session-1'] = [
|
||||
|
||||
@@ -59,6 +59,10 @@ interface SpotlightIngestResult {
|
||||
visibleText: string
|
||||
}
|
||||
|
||||
interface CreateSessionResult {
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
interface ChatCommandMessage<C extends string = string, P = unknown> {
|
||||
type: 'command'
|
||||
authorityId?: string
|
||||
@@ -74,7 +78,7 @@ interface RetryCommandPayload {
|
||||
}
|
||||
|
||||
type ChatResponsePayload
|
||||
= | { ok: true, result?: SpotlightIngestResult }
|
||||
= | { ok: true, result?: SpotlightIngestResult | CreateSessionResult }
|
||||
| { ok: false, error?: string }
|
||||
|
||||
type ChatSyncMessage
|
||||
@@ -88,6 +92,8 @@ type ChatSyncMessage
|
||||
| ChatCommandMessage<'tool-call-rerun', ToolCallRerunPayload<ToolsetId>>
|
||||
| ChatCommandMessage<'cleanup', { sessionId?: string }>
|
||||
| ChatCommandMessage<'delete-message', { sessionId?: string, messageId?: string, index?: number }>
|
||||
| ChatCommandMessage<'create-session', { characterId: string }>
|
||||
| ChatCommandMessage<'delete-session', { sessionId: string }>
|
||||
| ChatCommandMessage<'import-sessions', ChatSessionsExport>
|
||||
| ({ type: 'response', requestId: string, authorityId: string } & ChatResponsePayload)
|
||||
|
||||
@@ -277,15 +283,26 @@ export const useChatSyncStore = defineStore('stage-tamagotchi:chat-sync', () =>
|
||||
|
||||
function applySessionSnapshot(snapshot: SessionSnapshotPayload) {
|
||||
const localActiveSessionId = activeSessionId.value
|
||||
const localActiveSessionMessages = sessionMessages.value[localActiveSessionId]
|
||||
const shouldPreserveLocalActiveSession = mode.value === 'follower'
|
||||
&& !!localActiveSessionId
|
||||
&& !!snapshot.sessionMessages[localActiveSessionId]
|
||||
&& !!snapshot.sessionMetas[localActiveSessionId]
|
||||
|
||||
const nextSessionMessages = shouldPreserveLocalActiveSession
|
||||
&& localActiveSessionMessages
|
||||
&& !snapshot.sessionMessages[localActiveSessionId]
|
||||
? {
|
||||
...snapshot.sessionMessages,
|
||||
[localActiveSessionId]: localActiveSessionMessages,
|
||||
}
|
||||
: snapshot.sessionMessages
|
||||
|
||||
chatSession.applyRemoteSnapshot({
|
||||
...snapshot,
|
||||
activeSessionId: shouldPreserveLocalActiveSession
|
||||
? localActiveSessionId
|
||||
: snapshot.activeSessionId,
|
||||
sessionMessages: nextSessionMessages,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -468,6 +485,17 @@ export const useChatSyncStore = defineStore('stage-tamagotchi:chat-sync', () =>
|
||||
case 'delete-message':
|
||||
executeDeleteMessage(message.payload)
|
||||
break
|
||||
case 'create-session':
|
||||
respond({
|
||||
ok: true,
|
||||
result: {
|
||||
sessionId: await chatSession.createSession(message.payload.characterId, { setActive: false }),
|
||||
},
|
||||
})
|
||||
return
|
||||
case 'delete-session':
|
||||
await chatSession.deleteSession(message.payload.sessionId)
|
||||
break
|
||||
case 'import-sessions':
|
||||
await chatSession.importSessions(message.payload)
|
||||
break
|
||||
@@ -707,6 +735,42 @@ export const useChatSyncStore = defineStore('stage-tamagotchi:chat-sync', () =>
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a persisted session on the authority and activates it in the
|
||||
* requesting window. A follower must not mutate its local session store
|
||||
* first because the next authority snapshot would replace that mutation.
|
||||
*/
|
||||
async function requestCreateSession(characterId: string) {
|
||||
if (mode.value === 'authority')
|
||||
return await chatSession.createSession(characterId, { setActive: true })
|
||||
|
||||
const result = await dispatch<CreateSessionResult>({
|
||||
type: 'command',
|
||||
requestId: createRequestId(),
|
||||
senderId: instanceId,
|
||||
command: 'create-session',
|
||||
payload: { characterId },
|
||||
})
|
||||
chatSession.setActiveSession(result.sessionId)
|
||||
return result.sessionId
|
||||
}
|
||||
|
||||
/** Deletes a persisted session through the authority that owns session state. */
|
||||
async function requestDeleteSession(sessionId: string) {
|
||||
if (mode.value === 'authority') {
|
||||
await chatSession.deleteSession(sessionId)
|
||||
return
|
||||
}
|
||||
|
||||
await dispatch<void>({
|
||||
type: 'command',
|
||||
requestId: createRequestId(),
|
||||
senderId: instanceId,
|
||||
command: 'delete-session',
|
||||
payload: { sessionId },
|
||||
})
|
||||
}
|
||||
|
||||
/** Imports persisted chat sessions through the authority so every chat window receives the resulting snapshot. */
|
||||
async function requestImportSessions(payload: ChatSessionsExport) {
|
||||
if (mode.value === 'authority') {
|
||||
@@ -743,6 +807,8 @@ export const useChatSyncStore = defineStore('stage-tamagotchi:chat-sync', () =>
|
||||
requestToolCallRerun,
|
||||
requestCleanup,
|
||||
requestDeleteMessage,
|
||||
requestCreateSession,
|
||||
requestDeleteSession,
|
||||
requestImportSessions,
|
||||
}
|
||||
})
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
import { createPinia, defineStore } from 'pinia'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { render } from 'vitest-browser-vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import ChatSessionsDrawer from './sessions-drawer.vue'
|
||||
|
||||
vi.mock('../../../../composables/use-analytics', () => ({
|
||||
useAnalytics: () => ({
|
||||
trackChatSessionSelected: vi.fn(),
|
||||
trackChatSessionStarted: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../../../composables/use-breakpoints', () => ({
|
||||
useBreakpoints: () => ({
|
||||
isDesktop: computed(() => false),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../../../stores/auth', () => ({
|
||||
useAuthStore: defineStore('test-auth', () => ({
|
||||
userId: ref('local'),
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('../../../../stores/chat/session-store', () => ({
|
||||
useChatSessionStore: defineStore('test-chat-session', () => {
|
||||
const activeSessionId = ref('session-1')
|
||||
const sessionMessages = ref({
|
||||
'session-1': [{ role: 'user', content: 'Existing conversation' }],
|
||||
})
|
||||
const sessionMetas = ref({
|
||||
'session-1': {
|
||||
sessionId: 'session-1',
|
||||
userId: 'local',
|
||||
characterId: 'default',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
activeSessionId,
|
||||
sessionMessages,
|
||||
sessionMetas,
|
||||
createSession: vi.fn(async () => 'session-2'),
|
||||
deleteSession: vi.fn(async () => undefined),
|
||||
loadSession: vi.fn(async () => undefined),
|
||||
setActiveSession: vi.fn((sessionId: string) => {
|
||||
activeSessionId.value = sessionId
|
||||
}),
|
||||
}
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../../../stores/modules/airi-card', () => ({
|
||||
useAiriCardStore: defineStore('test-airi-card', () => ({
|
||||
activeCardId: ref('default'),
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('../../../../stores/modules/consciousness', () => ({
|
||||
useConsciousnessStore: defineStore('test-consciousness', () => ({
|
||||
activeModel: ref('test-model'),
|
||||
})),
|
||||
}))
|
||||
|
||||
function createTestI18n() {
|
||||
return createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
messages: {
|
||||
en: {
|
||||
stage: {
|
||||
chat: {
|
||||
sessions: {
|
||||
'title': 'Conversations',
|
||||
'new': 'New conversation',
|
||||
'empty': 'No conversations',
|
||||
'delete': 'Delete conversation',
|
||||
'cloud-badge': 'Cloud synced',
|
||||
'new-chat-fallback': 'New conversation',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function renderDrawer(options: {
|
||||
createSession?: (characterId: string) => Promise<string>
|
||||
deleteSession?: (sessionId: string) => Promise<void>
|
||||
}) {
|
||||
const pinia = createPinia()
|
||||
|
||||
return await render(ChatSessionsDrawer, {
|
||||
props: {
|
||||
modelValue: true,
|
||||
presentation: 'dialog',
|
||||
createSession: options.createSession,
|
||||
deleteSession: options.deleteSession,
|
||||
},
|
||||
global: {
|
||||
plugins: [pinia, createTestI18n()],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('chat sessions drawer desktop window actions', () => {
|
||||
// https://github.com/moeru-ai/airi/issues/2085
|
||||
it('issue #2085: forces the dialog boundary in a narrow desktop window', async () => {
|
||||
await renderDrawer({})
|
||||
|
||||
expect(document.querySelector('[role="dialog"]')).not.toBeNull()
|
||||
expect(document.querySelector('[data-vaul-drawer]')).toBeNull()
|
||||
})
|
||||
|
||||
// https://github.com/moeru-ai/airi/issues/2085
|
||||
it('issue #2085: delegates create and delete to the window action adapter', async () => {
|
||||
const createSession = vi.fn(async () => 'session-2')
|
||||
const deleteSession = vi.fn(async () => undefined)
|
||||
const screen = await renderDrawer({ createSession, deleteSession })
|
||||
|
||||
await screen.getByRole('button', { name: 'Delete conversation' }).click()
|
||||
await expect.poll(() => deleteSession.mock.calls.length).toBe(1)
|
||||
expect(deleteSession).toHaveBeenCalledWith('session-1')
|
||||
|
||||
await screen.getByRole('button', { name: 'New conversation' }).click()
|
||||
await expect.poll(() => createSession.mock.calls.length).toBe(1)
|
||||
expect(createSession).toHaveBeenCalledWith('default')
|
||||
})
|
||||
})
|
||||
@@ -16,6 +16,24 @@ import { useChatSessionStore } from '../../../../stores/chat/session-store'
|
||||
import { useAiriCardStore } from '../../../../stores/modules/airi-card'
|
||||
import { useConsciousnessStore } from '../../../../stores/modules/consciousness'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
/**
|
||||
* Forces the dialog interaction boundary when a narrow desktop window
|
||||
* would otherwise be mistaken for a touch layout.
|
||||
*
|
||||
* @default 'responsive'
|
||||
*/
|
||||
presentation?: 'responsive' | 'dialog'
|
||||
/** Routes session creation through the owning runtime when provided. */
|
||||
createSession?: (characterId: string) => Promise<string>
|
||||
/** Routes session deletion through the owning runtime when provided. */
|
||||
deleteSession?: (sessionId: string) => Promise<void>
|
||||
}>(), {
|
||||
presentation: 'responsive',
|
||||
})
|
||||
|
||||
const showDialog = defineModel({ type: Boolean, default: false, required: false })
|
||||
|
||||
/**
|
||||
* Bottom-sheet (mobile) / centered-modal (desktop) UI surface that lists every
|
||||
* chat session belonging to the current user, lets the user switch between
|
||||
@@ -36,9 +54,8 @@ import { useConsciousnessStore } from '../../../../stores/modules/consciousness'
|
||||
* updatedAt timestamp.
|
||||
*/
|
||||
|
||||
const showDialog = defineModel({ type: Boolean, default: false, required: false })
|
||||
|
||||
const { isDesktop } = useBreakpoints()
|
||||
const useDialogPresentation = computed(() => props.presentation === 'dialog' || isDesktop.value)
|
||||
const screenSafeArea = useScreenSafeArea()
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -172,7 +189,10 @@ async function startNewSession() {
|
||||
isCreatingSession.value = true
|
||||
try {
|
||||
const characterId = activeCardId.value || 'default'
|
||||
await chatSession.createSession(characterId, { setActive: true })
|
||||
if (props.createSession)
|
||||
await props.createSession(characterId)
|
||||
else
|
||||
await chatSession.createSession(characterId, { setActive: true })
|
||||
// PostHog retention denominator. We pick this call site (UI new-session
|
||||
// button) rather than `createSession` in the store because the store also
|
||||
// creates sessions for cloud-reconcile / fork / restore flows that aren't
|
||||
@@ -190,7 +210,10 @@ async function deleteRow(event: Event, sessionId: string) {
|
||||
// Stop the parent button's click — otherwise we'd switch into the session
|
||||
// we are about to remove and immediately need a fallback.
|
||||
event.stopPropagation()
|
||||
await chatSession.deleteSession(sessionId)
|
||||
if (props.deleteSession)
|
||||
await props.deleteSession(sessionId)
|
||||
else
|
||||
await chatSession.deleteSession(sessionId)
|
||||
}
|
||||
|
||||
// Per-open generation counter. The batch loadSession loop checks this before
|
||||
@@ -225,7 +248,7 @@ watch(showDialog, async (open) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogRoot v-if="isDesktop" :open="showDialog" @update:open="value => showDialog = value">
|
||||
<DialogRoot v-if="useDialogPresentation" :open="showDialog" @update:open="value => showDialog = value">
|
||||
<slot name="trigger" />
|
||||
<DialogPortal>
|
||||
<DialogOverlay
|
||||
|
||||
Reference in New Issue
Block a user