refine active session live state

This commit is contained in:
mysoul12138
2026-04-19 21:51:25 +08:00
parent 2ea20fc128
commit 7c4b025e6a
3 changed files with 39 additions and 12 deletions
@@ -82,9 +82,9 @@ function sourceSortKey(source: string): number {
function sortSessionsWithActiveFirst(items: Session[]): Session[] { function sortSessionsWithActiveFirst(items: Session[]): Session[] {
return [...items].sort((a, b) => { return [...items].sort((a, b) => {
const aActive = a.id === chatStore.activeSessionId const aLive = chatStore.isSessionLive(a.id)
const bActive = b.id === chatStore.activeSessionId const bLive = chatStore.isSessionLive(b.id)
if (aActive !== bActive) return aActive ? -1 : 1 if (aLive !== bLive) return aLive ? -1 : 1
if (b.createdAt !== a.createdAt) return b.createdAt - a.createdAt if (b.createdAt !== a.createdAt) return b.createdAt - a.createdAt
return b.updatedAt - a.updatedAt return b.updatedAt - a.updatedAt
}) })
@@ -106,9 +106,9 @@ const groupedSessions = computed<SessionGroup[]>(() => {
} }
const keys = [...map.keys()].sort((a, b) => { const keys = [...map.keys()].sort((a, b) => {
const aHasActive = map.get(a)?.some(s => s.id === chatStore.activeSessionId) || false const aHasLive = map.get(a)?.some(s => chatStore.isSessionLive(s.id)) || false
const bHasActive = map.get(b)?.some(s => s.id === chatStore.activeSessionId) || false const bHasLive = map.get(b)?.some(s => chatStore.isSessionLive(s.id)) || false
if (aHasActive !== bHasActive) return aHasActive ? -1 : 1 if (aHasLive !== bHasLive) return aHasLive ? -1 : 1
const ka = sourceSortKey(a) const ka = sourceSortKey(a)
const kb = sourceSortKey(b) const kb = sourceSortKey(b)
if (ka !== kb) return ka - kb if (ka !== kb) return ka - kb
@@ -327,14 +327,14 @@ async function handleRenameConfirm() {
v-for="s in group.sessions" v-for="s in group.sessions"
:key="s.id" :key="s.id"
class="session-item" class="session-item"
:class="{ active: s.id === chatStore.activeSessionId }" :class="{ active: chatStore.isSessionLive(s.id) }"
@click="handleSessionClick(s.id)" @click="handleSessionClick(s.id)"
@contextmenu="handleContextMenu($event, s.id)" @contextmenu="handleContextMenu($event, s.id)"
> >
<div class="session-item-content"> <div class="session-item-content">
<span class="session-item-title-row"> <span class="session-item-title-row">
<span <span
v-if="s.id === chatStore.activeSessionId" v-if="chatStore.isSessionLive(s.id)"
class="session-item-active-indicator" class="session-item-active-indicator"
aria-hidden="true" aria-hidden="true"
> >
@@ -249,6 +249,10 @@ export const useChatStore = defineStore('chat', () => {
const activeSession = ref<Session | null>(null) const activeSession = ref<Session | null>(null)
const messages = computed<Message[]>(() => activeSession.value?.messages || []) const messages = computed<Message[]>(() => activeSession.value?.messages || [])
function isSessionLive(sessionId: string): boolean {
return streamStates.value.has(sessionId) || resumingRuns.value.has(sessionId)
}
function persistSessionsList() { function persistSessionsList() {
// Cache lightweight summaries only (messages are cached per-session). // Cache lightweight summaries only (messages are cached per-session).
saveJson( saveJson(
@@ -912,6 +916,7 @@ export const useChatStore = defineStore('chat', () => {
messages, messages,
isStreaming, isStreaming,
isRunActive, isRunActive,
isSessionLive,
isLoadingSessions, isLoadingSessions,
isLoadingMessages, isLoadingMessages,
newChat, newChat,
+26 -4
View File
@@ -7,6 +7,7 @@ const mockChatStore = vi.hoisted(() => ({
activeSessionId: null as string | null, activeSessionId: null as string | null,
activeSession: null as Record<string, any> | null, activeSession: null as Record<string, any> | null,
isLoadingSessions: false, isLoadingSessions: false,
isSessionLive: vi.fn((sessionId: string) => sessionId === 'discord-active'),
newChat: vi.fn(), newChat: vi.fn(),
switchSession: vi.fn(), switchSession: vi.fn(),
deleteSession: vi.fn(), deleteSession: vi.fn(),
@@ -81,6 +82,12 @@ describe('ChatPanel session list', () => {
createdAt: 200, createdAt: 200,
updatedAt: 400, updatedAt: 400,
}) })
const slackSession = makeSession('slack-1', {
title: 'Slack Selected',
source: 'slack',
createdAt: 50,
updatedAt: 50,
})
const apiSession = makeSession('api-1', { const apiSession = makeSession('api-1', {
title: 'API Session', title: 'API Session',
source: 'api_server', source: 'api_server',
@@ -88,13 +95,18 @@ describe('ChatPanel session list', () => {
updatedAt: 300, updatedAt: 300,
}) })
mockChatStore.sessions = [apiSession, olderDiscord, activeDiscord] mockChatStore.sessions = [apiSession, slackSession, olderDiscord, activeDiscord]
mockChatStore.activeSessionId = activeDiscord.id mockChatStore.activeSessionId = apiSession.id
mockChatStore.activeSession = activeDiscord mockChatStore.activeSession = apiSession
mockChatStore.isLoadingSessions = false mockChatStore.isLoadingSessions = false
mockChatStore.isSessionLive.mockImplementation((sessionId: string) => sessionId === activeDiscord.id)
mockChatStore.switchSession.mockImplementation((sessionId: string) => {
mockChatStore.activeSessionId = sessionId
mockChatStore.activeSession = mockChatStore.sessions.find(s => s.id === sessionId) ?? null
})
}) })
it('pins the active session group to the top and renders an active indicator', () => { it('pins the live session group to the top and keeps the indicator on the runtime live session', async () => {
const wrapper = mount(ChatPanel, { const wrapper = mount(ChatPanel, {
global: { global: {
stubs: { stubs: {
@@ -118,5 +130,15 @@ describe('ChatPanel session list', () => {
const activeIndicator = wrapper.find('.session-item.active .session-item-active-indicator') const activeIndicator = wrapper.find('.session-item.active .session-item-active-indicator')
expect(activeIndicator.exists()).toBe(true) expect(activeIndicator.exists()).toBe(true)
await wrapper.findAll('.session-item').find(node => node.text().includes('Slack Selected'))!.trigger('click')
expect(mockChatStore.switchSession).toHaveBeenCalledWith('slack-1')
const groupLabelsAfterClick = wrapper.findAll('.session-group-label').map(node => node.text())
expect(groupLabelsAfterClick[0]).toBe('Discord')
const activeTitlesAfterClick = wrapper.findAll('.session-item.active .session-item-title').map(node => node.text())
expect(activeTitlesAfterClick).toEqual(['Discord Active'])
}) })
}) })