refine active session live state
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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'])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user