fix(web): localize toast messages and keep full session counts (#573)

Normalize hub toast text in the web client for i18n coverage (including Ready for input notifications) and stop deduplicating session rows by agentSessionId so outline/group counts reflect user-visible sessions.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
junes
2026-05-06 09:13:02 +08:00
committed by GitHub
co-authored by Cursor
parent 23c0fa4872
commit fad5dbbc30
5 changed files with 64 additions and 7 deletions
+48 -3
View File
@@ -239,14 +239,59 @@ function AppInner() {
clearMessageWindow(event.sessionId)
void fetchLatestMessages(api, event.sessionId)
}, [api, selectedSessionId])
const translateIncomingToast = useCallback((title: string, body: string): { title: string; body: string } => {
const normalizedTitle = title.trim()
const normalizedBody = body.trim()
if (normalizedTitle === 'Ready for input') {
const waitingMatch = normalizedBody.match(/^(.+)\s+is waiting in\s+(.+)$/i)
if (waitingMatch) {
const agent = waitingMatch[1]?.trim() ?? ''
const sessionName = waitingMatch[2]?.trim() ?? ''
return {
title: t('toast.ready.title'),
body: t('toast.ready.body', { agent, session: sessionName })
}
}
return {
title: t('toast.ready.title'),
body: normalizedBody
}
}
if (normalizedTitle === 'Permission Request') {
return {
title: t('toast.permission.title'),
body: normalizedBody
}
}
if (normalizedTitle === 'Task completed') {
return {
title: t('toast.task.completed'),
body: normalizedBody
}
}
if (normalizedTitle === 'Task failed') {
return {
title: t('toast.task.failed'),
body: normalizedBody
}
}
return { title, body }
}, [t])
const handleToast = useCallback((event: ToastEvent) => {
const localized = translateIncomingToast(event.data.title, event.data.body)
addToast({
title: event.data.title,
body: event.data.body,
title: localized.title,
body: localized.body,
sessionId: event.data.sessionId,
url: event.data.url
})
}, [addToast])
}, [addToast, translateIncomingToast])
const eventSubscription = useMemo(() => {
if (selectedSessionId) {
+2 -2
View File
@@ -711,8 +711,8 @@ export function SessionList(props: {
}
const allSessions = useMemo(
() => deduplicateSessionsByAgentId(props.sessions, selectedSessionId),
[props.sessions, selectedSessionId]
() => props.sessions,
[props.sessions]
)
const visibleSessions = useMemo(
() => isSearching
+6
View File
@@ -274,6 +274,12 @@ export default {
// Send blocked
'send.blocked.title': 'Cannot send message',
'send.blocked.noConnection': 'Not connected to server',
'resume.failed.title': 'Resume failed',
'toast.ready.title': 'Ready for input',
'toast.ready.body': '{agent} is waiting in {session}',
'toast.permission.title': 'Permission Request',
'toast.task.completed': 'Task completed',
'toast.task.failed': 'Task failed',
// Install prompt
'install.title': 'Install HAPI',
+6
View File
@@ -276,6 +276,12 @@ export default {
// Send blocked
'send.blocked.title': '无法发送消息',
'send.blocked.noConnection': '未连接到服务器',
'resume.failed.title': '恢复会话失败',
'toast.ready.title': '等待输入',
'toast.ready.body': '{agent} 正在 {session} 等待你的输入',
'toast.permission.title': '权限请求',
'toast.task.completed': '任务完成',
'toast.task.failed': '任务失败',
// Install prompt
'install.title': '安装 HAPI',
+2 -2
View File
@@ -276,9 +276,9 @@ function SessionPage() {
try {
return await api.resumeSession(currentSessionId, { permissionMode: session.permissionMode ?? undefined })
} catch (error) {
const message = error instanceof Error ? error.message : 'Resume failed'
const message = error instanceof Error ? error.message : t('dialog.error.default')
addToast({
title: 'Resume failed',
title: t('resume.failed.title'),
body: message,
sessionId: currentSessionId,
url: ''