feat(web): make session header metadata configurable (#1267)

* feat(web): make session header metadata configurable

* fix(web): align mobile header metadata priority
This commit is contained in:
Ananovo
2026-08-01 23:26:17 +08:00
committed by GitHub
parent 079556b43a
commit 0537ddf84a
15 changed files with 366 additions and 25 deletions
+5
View File
@@ -18,6 +18,11 @@ describe('codexStatusLabels', () => {
expect(formatCodexReasoningLabel('Ultra')).toBe('reasoning ultra')
})
it('can omit the reasoning field label', () => {
expect(formatCodexReasoningLabel('xhigh', false)).toBe('xhigh')
expect(formatCodexReasoningLabel(null, false)).toBe('default')
})
it('formats compact effort-only labels', () => {
expect(formatCompactCodexReasoningLabel(null)).toBe('default')
expect(formatCompactCodexReasoningLabel('default')).toBe('default')
+3 -2
View File
@@ -6,8 +6,9 @@ export function formatCompactCodexReasoningLabel(effort?: string | null): string
return normalized
}
export function formatCodexReasoningLabel(effort?: string | null): string {
return `reasoning ${formatCompactCodexReasoningLabel(effort)}`
export function formatCodexReasoningLabel(effort?: string | null, showLabel = true): string {
const value = formatCompactCodexReasoningLabel(effort)
return showLabel ? `reasoning ${value}` : value
}
export function shouldShowCodexReasoningLabel(agentFlavor: string | null | undefined): boolean {
+14
View File
@@ -689,6 +689,20 @@ export default {
'settings.display.sessionListStatus.standard': 'Standard',
'settings.display.sessionListStatus.detailed': 'Detailed',
'settings.display.sessionListStatus.detailedDescription': 'Shows why a session stopped: permission, input, background work, new activity, or a scheduled message (clock icon).',
'settings.display.sessionHeader': 'Session header',
'settings.display.sessionHeader.description': 'Choose which details appear below the session title. Mobile shows the agent plus the highest-priority available detail.',
'settings.display.sessionHeader.showLabels': 'Show field labels',
'settings.display.sessionHeader.agent': 'Agent',
'settings.display.sessionHeader.model': 'Model',
'settings.display.sessionHeader.reasoning': 'Reasoning effort',
'settings.display.sessionHeader.fastMode': 'Fast mode',
'settings.display.sessionHeader.machine': 'Machine',
'settings.display.sessionHeader.lastActive': 'Active time',
'settings.display.sessionHeader.createdAt': 'Created time',
'settings.display.sessionHeader.updatedAt': 'Updated time',
'settings.display.sessionHeader.worktree': 'Worktree branch',
'session.header.createdAt': 'Created',
'session.header.updatedAt': 'Updated',
'settings.chat.title': 'Chat',
'settings.chat.description': 'Message input, tool cards, and conversation colors.',
'settings.chat.input': 'Input',
+14
View File
@@ -693,6 +693,20 @@ export default {
'settings.display.sessionListStatus.standard': '标准',
'settings.display.sessionListStatus.detailed': '详细',
'settings.display.sessionListStatus.detailedDescription': '显示会话停止的原因:权限、输入、后台任务、新活动或定时消息(时钟图标)。',
'settings.display.sessionHeader': '会话顶部信息',
'settings.display.sessionHeader.description': '选择在会话标题下方显示的信息。移动端显示 Agent 类型及优先级最高的一个可用信息。',
'settings.display.sessionHeader.showLabels': '显示字段标题',
'settings.display.sessionHeader.agent': 'Agent 类型',
'settings.display.sessionHeader.model': '模型',
'settings.display.sessionHeader.reasoning': '推理强度',
'settings.display.sessionHeader.fastMode': '快速模式',
'settings.display.sessionHeader.machine': '机器',
'settings.display.sessionHeader.lastActive': '活跃时间',
'settings.display.sessionHeader.createdAt': '创建时间',
'settings.display.sessionHeader.updatedAt': '更新时间',
'settings.display.sessionHeader.worktree': '工作树分支',
'session.header.createdAt': '创建',
'session.header.updatedAt': '更新',
'settings.chat.title': '聊天',
'settings.chat.description': '消息输入、工具卡片和对话颜色。',
'settings.chat.input': '输入',
@@ -0,0 +1,21 @@
import { describe, expect, it } from 'vitest'
import { selectMobileSessionHeaderSecondary } from './sessionHeaderMobileMetadata'
describe('selectMobileSessionHeaderSecondary', () => {
it('follows the upstream-first display order', () => {
expect(selectMobileSessionHeaderSecondary({ machine: true, model: true, updatedAt: true })).toBe('machine')
expect(selectMobileSessionHeaderSecondary({ model: true, updatedAt: true, worktree: true })).toBe('model')
})
it('uses the next enabled and available detail when the model is absent', () => {
expect(selectMobileSessionHeaderSecondary({ machine: true, lastActive: true, updatedAt: true })).toBe('machine')
expect(selectMobileSessionHeaderSecondary({ lastActive: true, updatedAt: true })).toBe('lastActive')
expect(selectMobileSessionHeaderSecondary({ fastMode: true, updatedAt: true, createdAt: true, worktree: true })).toBe('fastMode')
expect(selectMobileSessionHeaderSecondary({ updatedAt: true, createdAt: true, worktree: true })).toBe('createdAt')
expect(selectMobileSessionHeaderSecondary({ worktree: true, fastMode: true })).toBe('fastMode')
})
it('returns no secondary detail when none are available', () => {
expect(selectMobileSessionHeaderSecondary({})).toBeNull()
})
})
@@ -0,0 +1,26 @@
export type SessionHeaderSecondaryMetadataKey =
| 'model'
| 'reasoning'
| 'machine'
| 'lastActive'
| 'updatedAt'
| 'createdAt'
| 'worktree'
| 'fastMode'
const MOBILE_SECONDARY_PRIORITY: ReadonlyArray<SessionHeaderSecondaryMetadataKey> = [
'machine',
'lastActive',
'model',
'reasoning',
'fastMode',
'createdAt',
'updatedAt',
'worktree',
]
export function selectMobileSessionHeaderSecondary(
available: Partial<Record<SessionHeaderSecondaryMetadataKey, boolean>>
): SessionHeaderSecondaryMetadataKey | null {
return MOBILE_SECONDARY_PRIORITY.find((key) => available[key] === true) ?? null
}
@@ -0,0 +1,15 @@
import { describe, expect, it } from 'vitest'
import { formatSessionHeaderTimestamp } from './sessionHeaderTimestamp'
describe('formatSessionHeaderTimestamp', () => {
it('formats a session timestamp in the selected locale', () => {
const value = new Date(2026, 6, 26, 8, 53).getTime()
expect(formatSessionHeaderTimestamp(value, 'en-US')).toMatch(/Jul 26, 2026.*08:53 AM/)
expect(formatSessionHeaderTimestamp(value, 'zh-CN')).toContain('2026年7月26日')
})
it('rejects missing and invalid timestamps', () => {
expect(formatSessionHeaderTimestamp(0)).toBeNull()
expect(formatSessionHeaderTimestamp(Number.NaN)).toBeNull()
})
})
+13
View File
@@ -0,0 +1,13 @@
export function formatSessionHeaderTimestamp(value: number, locale?: string): string | null {
if (!Number.isFinite(value) || value <= 0) return null
const date = new Date(value)
if (Number.isNaN(date.getTime())) return null
return new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
}).format(date)
}