feat: show active conversations in sidebar
This commit is contained in:
@@ -1592,6 +1592,20 @@ describe('App', () => {
|
||||
if (!request) {
|
||||
throw new Error('Missing request')
|
||||
}
|
||||
const conversationList =
|
||||
container.querySelector<HTMLElement>('.conversation-list')
|
||||
if (!conversationList) {
|
||||
throw new Error('Missing conversation list')
|
||||
}
|
||||
const activeIndicator = within(conversationList).getByRole('status', {
|
||||
name: '会话正在活动'
|
||||
})
|
||||
expect(activeIndicator).toHaveClass(
|
||||
'conversation-activity-indicator'
|
||||
)
|
||||
expect(activeIndicator.closest('.conversation-row')).toHaveTextContent(
|
||||
'离开页面后继续生成'
|
||||
)
|
||||
await act(
|
||||
() =>
|
||||
new Promise<void>((resolve) =>
|
||||
@@ -1613,6 +1627,11 @@ describe('App', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: '知识库' }))
|
||||
expect(chat).toBeInTheDocument()
|
||||
expect(chat.closest('[data-route="chat"]')).toHaveAttribute('hidden')
|
||||
expect(
|
||||
within(conversationList).getByRole('status', {
|
||||
name: '会话正在活动'
|
||||
})
|
||||
).toBeInTheDocument()
|
||||
act(() => {
|
||||
agentListener?.({
|
||||
requestId: request.requestId,
|
||||
@@ -1620,6 +1639,19 @@ describe('App', () => {
|
||||
delta: '后台新增的回复内容'
|
||||
})
|
||||
})
|
||||
act(() => {
|
||||
agentListener?.({
|
||||
requestId: request.requestId,
|
||||
type: 'done'
|
||||
})
|
||||
})
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
within(conversationList).queryByRole('status', {
|
||||
name: '会话正在活动'
|
||||
})
|
||||
).not.toBeInTheDocument()
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '对话' }))
|
||||
expect(await screen.findByText('后台新增的回复内容')).toBeInTheDocument()
|
||||
|
||||
@@ -2046,6 +2046,26 @@ function App(): React.JSX.Element {
|
||||
const activityRecordsRef = useRef(activityRecords)
|
||||
const activeRuns = useRef(new Map<string, ActiveRun>())
|
||||
const preparingConversations = useRef(new Set<string>())
|
||||
const [activeConversationIds, setActiveConversationIds] = useState<
|
||||
ReadonlySet<string>
|
||||
>(() => new Set())
|
||||
const setConversationActivity = useCallback(
|
||||
(conversationId: string, active: boolean): void => {
|
||||
setActiveConversationIds((current) => {
|
||||
if (current.has(conversationId) === active) {
|
||||
return current
|
||||
}
|
||||
const next = new Set(current)
|
||||
if (active) {
|
||||
next.add(conversationId)
|
||||
} else {
|
||||
next.delete(conversationId)
|
||||
}
|
||||
return next
|
||||
})
|
||||
},
|
||||
[]
|
||||
)
|
||||
const hydratingArtifactIds = useRef(new Set<string>())
|
||||
const knowledgeScopeInitialized = useRef(false)
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null)
|
||||
@@ -3464,6 +3484,7 @@ function App(): React.JSX.Element {
|
||||
}
|
||||
})
|
||||
activeRuns.current.delete(event.requestId)
|
||||
setConversationActivity(run.conversationId, false)
|
||||
flushConversationPersistenceAfterRenderRef.current = true
|
||||
}
|
||||
},
|
||||
@@ -3471,6 +3492,7 @@ function App(): React.JSX.Element {
|
||||
recordActivity,
|
||||
loadWorkspaceChanges,
|
||||
refreshTokenUsage,
|
||||
setConversationActivity,
|
||||
updateMessage,
|
||||
updateRequestActivity
|
||||
]
|
||||
@@ -4651,6 +4673,7 @@ function App(): React.JSX.Element {
|
||||
delete next[conversationId]
|
||||
return next
|
||||
})
|
||||
setConversationActivity(conversationId, false)
|
||||
const remaining = conversations.filter(
|
||||
(conversation) => conversation.id !== conversationId
|
||||
)
|
||||
@@ -4954,6 +4977,7 @@ function App(): React.JSX.Element {
|
||||
runtime.capability === 'image-generation' ? '' : selectedExpertId
|
||||
const workModeSnapshot = effectiveWorkMode
|
||||
preparingConversations.current.add(conversationId)
|
||||
setConversationActivity(conversationId, true)
|
||||
setInput('')
|
||||
updateAttachments([])
|
||||
const userMessage: Message = {
|
||||
@@ -5546,6 +5570,7 @@ function App(): React.JSX.Element {
|
||||
await window.goodbuddy.agent.cancel(requestId)
|
||||
}
|
||||
activeRuns.current.clear()
|
||||
setActiveConversationIds(new Set())
|
||||
for (const attachments of attachmentsRef.current.values()) {
|
||||
for (const attachment of attachments) {
|
||||
await window.goodbuddy.context.remove(attachment.id)
|
||||
@@ -5889,6 +5914,14 @@ function App(): React.JSX.Element {
|
||||
</span>
|
||||
<small>{formatTime(conversation.updatedAt, locale)}</small>
|
||||
</button>
|
||||
{activeConversationIds.has(conversation.id) && (
|
||||
<span
|
||||
aria-label={t('conversation.active')}
|
||||
className="conversation-activity-indicator"
|
||||
role="status"
|
||||
title={t('conversation.active')}
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
aria-controls={`conversation-actions-${conversation.id}`}
|
||||
aria-expanded={
|
||||
|
||||
@@ -76,6 +76,7 @@ export const app = {
|
||||
greeting:
|
||||
'Hi, I’m GoodBuddy. Ask me a question, add local files, or use your knowledge base. With an Agent Runtime enabled, I can also use tools with your authorization.',
|
||||
interrupted: 'The previous run stopped unexpectedly. You can resend your question.',
|
||||
active: 'Conversation is active',
|
||||
unread: 'Unread',
|
||||
unreadRemote: 'Unread remote message',
|
||||
noRemote: 'No remote conversations yet',
|
||||
|
||||
@@ -72,6 +72,7 @@ export const app = {
|
||||
greeting:
|
||||
'你好,我是 GoodBuddy。你可以直接向我提问、添加本地文件或使用知识库。启用 Agent Runtime 后,我也可以在你的授权下调用工具。',
|
||||
interrupted: '上次运行意外中断,可以重新发送问题',
|
||||
active: '会话正在活动',
|
||||
unread: '未读',
|
||||
unreadRemote: '未读远程消息',
|
||||
noRemote: '尚无远程会话',
|
||||
|
||||
@@ -6962,6 +6962,19 @@ details.settings-section > :not(summary) + :not(summary) {
|
||||
padding-right: 58px;
|
||||
}
|
||||
|
||||
.conversation-activity-indicator {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
animation: pulse 1.1s ease-in-out infinite;
|
||||
background: var(--success);
|
||||
box-shadow: 0 0 0 4px
|
||||
color-mix(in srgb, var(--success) 14%, transparent);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.conversation-more,
|
||||
.conversation-delete {
|
||||
position: absolute;
|
||||
|
||||
Reference in New Issue
Block a user