diff --git a/frontend/e2e/pages/sessions-page.ts b/frontend/e2e/pages/sessions-page.ts index dd9ebce7a..599770a92 100644 --- a/frontend/e2e/pages/sessions-page.ts +++ b/frontend/e2e/pages/sessions-page.ts @@ -14,6 +14,7 @@ export class SessionsPage { readonly sortButton: Locator; readonly projectTypeahead: Locator; readonly sessionListHeader: Locator; + readonly sessionCount: Locator; readonly analyticsPage: Locator; readonly analyticsToolbar: Locator; @@ -27,6 +28,7 @@ export class SessionsPage { this.sortButton = page.getByLabel("Toggle sort order"); this.projectTypeahead = page.locator(".typeahead"); this.sessionListHeader = page.locator(".session-list-header"); + this.sessionCount = this.sessionListHeader.locator(".session-count"); this.analyticsPage = page.locator(".analytics-page"); this.analyticsToolbar = page.locator(".analytics-toolbar"); this.exportBtn = page.locator(".export-btn"); diff --git a/frontend/e2e/termination.spec.ts b/frontend/e2e/termination.spec.ts index 0e1a72e1a..3d4bf8598 100644 --- a/frontend/e2e/termination.spec.ts +++ b/frontend/e2e/termination.spec.ts @@ -27,9 +27,7 @@ test.describe("session termination status", () => { // The fixture has exactly one unclean session. await expect(sp.sessionItems).toHaveCount(1); - await expect(sp.sessionListHeader).toContainText( - "1 sessions", - ); + await expect(sp.sessionCount).toHaveText("1 session"); // Surviving session renders the unclean StatusDot. await expect( diff --git a/frontend/e2e/virtual-list.spec.ts b/frontend/e2e/virtual-list.spec.ts index 84e599c62..4eb0e0453 100644 --- a/frontend/e2e/virtual-list.spec.ts +++ b/frontend/e2e/virtual-list.spec.ts @@ -89,10 +89,9 @@ test.describe("Virtual list behavior", () => { // Wait for filtered results to render before checking // scroll position — on CI the re-render can be slow. - await expect(sp.sessionListHeader).toContainText( - "1 sessions", - { timeout: 5_000 }, - ); + await expect(sp.sessionCount).toHaveText("1 session", { + timeout: 5_000, + }); await expect .poll(() => getScrollTop(sp.sessionListScroll), { diff --git a/frontend/messages/en.json b/frontend/messages/en.json index a1e6c83fa..fa6709095 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -67,9 +67,54 @@ "header_actions_settings": "Settings", "header_actions_keyboard_shortcuts": "Keyboard shortcuts", "header_actions_keyboard_shortcuts_shortcut": "Keyboard shortcuts (?)", - "status_bar_sessions": "{count} sessions", - "status_bar_messages": "{count} messages", - "status_bar_projects": "{count} projects", + "status_bar_sessions": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=one": "{countLabel} session", + "countPlural=other": "{countLabel} sessions" + } + } + ], + "status_bar_messages": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=one": "{countLabel} message", + "countPlural=other": "{countLabel} messages" + } + } + ], + "status_bar_projects": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=one": "{countLabel} project", + "countPlural=other": "{countLabel} projects" + } + } + ], "status_bar_open_performance_debug": "Open performance debug", "status_bar_perf": "Perf", "status_bar_remote_unreachable_title": "Can't reach the remote server. Open settings to check the URL, token, or disconnect.", @@ -248,8 +293,34 @@ "compact_boundary_title": "Context window compacted at this point", "compact_boundary_label": "Context compacted", "compact_boundary_show_full_summary": "Show full summary", - "sidebar_session_count": "{count} sessions", + "sidebar_session_count": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=one": "{countLabel} session", + "countPlural=other": "{countLabel} sessions" + } + } + ], "sidebar_loading": "loading", + "sidebar_multi_select": "Multi-select", + "sidebar_exit_multi_select": "Exit multi-select", + "sidebar_select_all_visible": "Select all visible", + "sidebar_clear_selection": "Clear selection", + "sidebar_batch_clear": "Clear", + "sidebar_batch_all": "All", + "sidebar_selected_count": "{countLabel} selected", + "sidebar_move_selected_to_trash": "Move selected sessions to trash", + "sidebar_deleting": "Deleting...", + "sidebar_delete": "Delete", + "sidebar_cancel": "Cancel", "sidebar_status": "Status", "sidebar_active": "Active", "sidebar_active_title": "Last activity within 10 minutes", @@ -287,6 +358,8 @@ "sidebar_filters_clear_filters": "Clear filters", "sidebar_row_expand": "Expand", "sidebar_row_collapse": "Collapse", + "sidebar_row_select_session": "Select session", + "sidebar_row_deselect_session": "Deselect session", "sidebar_row_star_session": "Star session", "sidebar_row_unstar_session": "Unstar session", "sidebar_row_rename": "Rename", @@ -599,6 +672,9 @@ "usage_top_sessions_by_cost": "Top Sessions by Cost", "settings_title": "Settings", "settings_loading": "Loading settings...", + "settings_resync_title_unavailable": "Full resync unavailable in read-only mode", + "settings_resync_unavailable_hint": "Unavailable while connected to a read-only backend", + "settings_resync_hint": "Re-scan all session files from scratch", "settings_language_title": "Language", "settings_language_description": "Choose the interface language.", "settings_language_label": "Interface language", @@ -649,7 +725,23 @@ "activity_int_auto_short": "{int} int / {auto} auto", "activity_peak_label": "peak {count}", "activity_agent_min_value": "{value} agent-min", - "activity_output_tokens_value": "{count} output tokens", + "activity_no_sessions_selected_slot": "No sessions active in the selected slot.", + "activity_output_tokens_value": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=one": "{countLabel} output token", + "countPlural=other": "{countLabel} output tokens" + } + } + ], "activity_concurrency": "Concurrency", "activity_overlay": "Overlay", "activity_overlay_metric": "Concurrency overlay metric", @@ -791,6 +883,104 @@ "status_dot_stale": "Flagged session, idle for 10–60 minutes", "status_dot_unclean": "Terminated mid tool call (or file truncated)", "call_row_toggle_subagent_calls": "Toggle sub-agent calls", + "call_row_running_duration": "running {duration}+", + "call_group_parallel_label": "parallel", + "call_group_parallel_call_count": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=one": "{countLabel} call", + "countPlural=other": "{countLabel} calls" + } + } + ], + "subagent_calls_label": "↳ sub-agent", + "subagent_calls_summary": [ + { + "declarations": [ + "input count", + "input countLabel", + "input duration", + "input running", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural", + "running" + ], + "match": { + "countPlural=one,running=true": "{countLabel} call · running {duration}+", + "countPlural=other,running=true": "{countLabel} calls · running {duration}+", + "countPlural=one,running=false": "{countLabel} call · {duration}", + "countPlural=other,running=false": "{countLabel} calls · {duration}" + } + } + ], + "activity_lane_activity": "activity", + "activity_lane_message_count": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=one": "{countLabel} message", + "countPlural=other": "{countLabel} messages" + } + } + ], + "session_vitals_title": "Analysis", + "session_vitals_subtitle": "Session vital signs", + "session_vitals_close": "Close session analysis", + "session_vitals_session": "Session", + "session_vitals_running": "running", + "session_vitals_running_duration": "running {duration}+", + "session_vitals_tool_calls": "tool calls", + "session_vitals_tool_time": "tool time", + "session_vitals_slowest_call": "slowest call", + "session_vitals_jump_to_call": "Jump to call", + "session_vitals_turns": "turns", + "session_vitals_subagents": "sub-agents", + "session_vitals_time_spent": "Time spent", + "session_vitals_clear_category_filter": "clear category filter", + "session_vitals_completed_turns_hint": "completed turns · click to highlight", + "session_vitals_timeline": "Timeline", + "session_vitals_click_marks_to_scroll": "click marks to scroll", + "session_vitals_jump_to_turn": "Jump to {category} turn at {time}", + "session_vitals_calls": "Calls", + "session_vitals_calls_summary": [ + { + "declarations": [ + "input count", + "input countLabel", + "input runningCount", + "local countPlural = count: plural", + "local runningPlural = runningCount: plural" + ], + "selectors": [ + "countPlural", + "runningPlural" + ], + "match": { + "countPlural=one,runningPlural=one": "{countLabel} call · {runningCount} running", + "countPlural=other,runningPlural=one": "{countLabel} calls · {runningCount} running", + "countPlural=one,runningPlural=other": "{countLabel} call", + "countPlural=other,runningPlural=other": "{countLabel} calls" + } + } + ], + "session_vitals_now": "now", "code_block_copy": "Copy code block", "code_block_copy_title": "Copy code", "common_copied": "Copied!", @@ -915,7 +1105,22 @@ "insights_page_sessions_computed": "sessions computed", "insights_page_score_distribution": "Score distribution", "insights_page_pct_affected": "{pct}% affected", - "insights_page_driver_sessions": "{count} sessions", + "insights_page_driver_sessions": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=one": "{countLabel} session", + "countPlural=other": "{countLabel} sessions" + } + } + ], "insights_page_session_evidence": "Session evidence", "insights_page_close": "Close", "insights_page_loading_examples": "Loading examples...", @@ -1028,6 +1233,8 @@ "resync_failed": "Failed: {count}", "resync_close_btn": "Close", "resync_retry": "Retry", + "resync_error_read_only": "Full resync is unavailable for read-only backends.", + "resync_error_in_progress": "A sync is already in progress.", "shortcuts_open_command_palette": "Open command palette", "shortcuts_find_in_session": "Find in session", "shortcuts_close_palette": "Close palette / modal / find", @@ -1169,7 +1376,22 @@ "trash_title": "Trash", "trash_emptying": "Emptying...", "trash_empty_trash": "Empty Trash", - "trash_msgs": "{count} msgs", + "trash_msgs": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=one": "{countLabel} msg", + "countPlural=other": "{countLabel} msgs" + } + } + ], "trash_deleted_ago": "deleted {time}", "trash_restore_session": "Restore session", "trash_restore": "Restore", diff --git a/frontend/messages/zh-CN.json b/frontend/messages/zh-CN.json index a8ea2074a..a4fd462ec 100644 --- a/frontend/messages/zh-CN.json +++ b/frontend/messages/zh-CN.json @@ -67,9 +67,51 @@ "header_actions_settings": "设置", "header_actions_keyboard_shortcuts": "键盘快捷键", "header_actions_keyboard_shortcuts_shortcut": "键盘快捷键 (?)", - "status_bar_sessions": "{count} 个会话", - "status_bar_messages": "{count} 条消息", - "status_bar_projects": "{count} 个项目", + "status_bar_sessions": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=other": "{countLabel} 个会话" + } + } + ], + "status_bar_messages": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=other": "{countLabel} 条消息" + } + } + ], + "status_bar_projects": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=other": "{countLabel} 个项目" + } + } + ], "status_bar_open_performance_debug": "打开性能调试", "status_bar_perf": "性能", "status_bar_remote_unreachable_title": "无法连接远程服务器。打开设置检查 URL、token 或断开连接。", @@ -243,8 +285,33 @@ "compact_boundary_title": "上下文窗口在此处被压缩", "compact_boundary_label": "上下文已压缩", "compact_boundary_show_full_summary": "显示完整摘要", - "sidebar_session_count": "{count} 个会话", + "sidebar_session_count": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=other": "{countLabel} 个会话" + } + } + ], "sidebar_loading": "加载中", + "sidebar_multi_select": "多选", + "sidebar_exit_multi_select": "退出多选", + "sidebar_select_all_visible": "选择所有可见会话", + "sidebar_clear_selection": "清除选择", + "sidebar_batch_clear": "清除", + "sidebar_batch_all": "全部", + "sidebar_selected_count": "已选择 {countLabel} 个", + "sidebar_move_selected_to_trash": "将选中的会话移至回收站", + "sidebar_deleting": "正在删除...", + "sidebar_delete": "删除", + "sidebar_cancel": "取消", "sidebar_status": "状态", "sidebar_active": "活跃", "sidebar_active_title": "10 分钟内有活动", @@ -282,6 +349,8 @@ "sidebar_filters_clear_filters": "清除筛选器", "sidebar_row_expand": "展开", "sidebar_row_collapse": "折叠", + "sidebar_row_select_session": "选择会话", + "sidebar_row_deselect_session": "取消选择会话", "sidebar_row_star_session": "固定会话", "sidebar_row_unstar_session": "取消固定会话", "sidebar_row_rename": "重命名", @@ -584,6 +653,9 @@ "usage_top_sessions_by_cost": "按成本排序的热门会话", "settings_title": "设置", "settings_loading": "正在加载设置...", + "settings_resync_title_unavailable": "只读模式下无法完整重新同步", + "settings_resync_unavailable_hint": "连接到只读后端时不可用", + "settings_resync_hint": "从头重新扫描所有会话文件", "settings_language_title": "语言", "settings_language_description": "选择界面语言。", "settings_language_label": "界面语言", @@ -634,7 +706,22 @@ "activity_int_auto_short": "{int} 交互 / {auto} 自动", "activity_peak_label": "峰值 {count}", "activity_agent_min_value": "{value} 代理分钟", - "activity_output_tokens_value": "{count} 输出 Token", + "activity_no_sessions_selected_slot": "所选时段内没有活跃会话。", + "activity_output_tokens_value": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=other": "{countLabel} 输出 Token" + } + } + ], "activity_concurrency": "并发", "activity_overlay": "叠加", "activity_overlay_metric": "并发叠加指标", @@ -776,6 +863,98 @@ "status_dot_stale": "已标记会话,空闲 10–60 分钟", "status_dot_unclean": "在工具调用中途终止(或文件被截断)", "call_row_toggle_subagent_calls": "切换子代理调用", + "call_row_running_duration": "运行中 {duration}+", + "call_group_parallel_label": "并行", + "call_group_parallel_call_count": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=other": "{countLabel} 次调用" + } + } + ], + "subagent_calls_label": "↳ sub-agent", + "subagent_calls_summary": [ + { + "declarations": [ + "input count", + "input countLabel", + "input duration", + "input running", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural", + "running" + ], + "match": { + "countPlural=other,running=true": "{countLabel} 次调用 · 运行中 {duration}+", + "countPlural=other,running=false": "{countLabel} 次调用 · {duration}" + } + } + ], + "activity_lane_activity": "活动", + "activity_lane_message_count": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=other": "{countLabel} 条消息" + } + } + ], + "session_vitals_title": "分析", + "session_vitals_subtitle": "会话指标", + "session_vitals_close": "关闭会话分析", + "session_vitals_session": "会话", + "session_vitals_running": "运行中", + "session_vitals_running_duration": "运行中 {duration}+", + "session_vitals_tool_calls": "工具调用", + "session_vitals_tool_time": "工具耗时", + "session_vitals_slowest_call": "最慢调用", + "session_vitals_jump_to_call": "跳转到调用", + "session_vitals_turns": "回合", + "session_vitals_subagents": "sub-agents", + "session_vitals_time_spent": "耗时分布", + "session_vitals_clear_category_filter": "清除分类筛选", + "session_vitals_completed_turns_hint": "已完成回合 · 点击高亮显示", + "session_vitals_timeline": "时间线", + "session_vitals_click_marks_to_scroll": "点击标记滚动", + "session_vitals_jump_to_turn": "跳转到 {time} 的 {category} 回合", + "session_vitals_calls": "调用", + "session_vitals_calls_summary": [ + { + "declarations": [ + "input count", + "input countLabel", + "input runningCount", + "local countPlural = count: plural", + "local runningPlural = runningCount: plural" + ], + "selectors": [ + "countPlural", + "runningPlural" + ], + "match": { + "countPlural=other,runningPlural=one": "{countLabel} 次调用 · {runningCount} 个运行中", + "countPlural=other,runningPlural=other": "{countLabel} 次调用" + } + } + ], + "session_vitals_now": "现在", "code_block_copy": "复制代码块", "code_block_copy_title": "复制代码", "common_copied": "已复制!", @@ -900,7 +1079,21 @@ "insights_page_sessions_computed": "个已计算会话", "insights_page_score_distribution": "评分分布", "insights_page_pct_affected": "{pct}% 受影响", - "insights_page_driver_sessions": "{count} 个会话", + "insights_page_driver_sessions": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=other": "{countLabel} 个会话" + } + } + ], "insights_page_session_evidence": "会话证据", "insights_page_close": "关闭", "insights_page_loading_examples": "正在加载示例...", @@ -1013,6 +1206,8 @@ "resync_failed": "失败:{count}", "resync_close_btn": "关闭", "resync_retry": "重试", + "resync_error_read_only": "只读后端不支持完整重新同步。", + "resync_error_in_progress": "同步已在进行中。", "shortcuts_open_command_palette": "打开命令面板", "shortcuts_find_in_session": "在会话中查找", "shortcuts_close_palette": "关闭面板 / 弹窗 / 查找", @@ -1154,7 +1349,21 @@ "trash_title": "回收站", "trash_emptying": "正在清空...", "trash_empty_trash": "清空回收站", - "trash_msgs": "{count} 条消息", + "trash_msgs": [ + { + "declarations": [ + "input count", + "input countLabel", + "local countPlural = count: plural" + ], + "selectors": [ + "countPlural" + ], + "match": { + "countPlural=other": "{countLabel} 条消息" + } + } + ], "trash_deleted_ago": "{time} 删除", "trash_restore_session": "恢复会话", "trash_restore": "恢复", diff --git a/frontend/src/lib/components/activity/ActivityPage.svelte b/frontend/src/lib/components/activity/ActivityPage.svelte index 3e3658a86..a13aff0c1 100644 --- a/frontend/src/lib/components/activity/ActivityPage.svelte +++ b/frontend/src/lib/components/activity/ActivityPage.svelte @@ -389,11 +389,11 @@
{activity.error}
{:else} -
No data for this period.
+
{m.shared_no_data_for_period()}
{/if}
- activity + {m.activity_lane_activity()} {#if chart} {#each chart.bars as bar (bar.index)} diff --git a/frontend/src/lib/components/content/CallGroup.svelte b/frontend/src/lib/components/content/CallGroup.svelte index f0497b38d..5072486e6 100644 --- a/frontend/src/lib/components/content/CallGroup.svelte +++ b/frontend/src/lib/components/content/CallGroup.svelte @@ -1,7 +1,9 @@