feat: redesign run history views
This commit is contained in:
+7
-7
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## 1. 目的与适用范围
|
## 1. 目的与适用范围
|
||||||
|
|
||||||
本文定义 GoodBuddy 桌面端的统一界面规则,适用于聊天与最近对话、知识库、智能心跳、任务与活动记录,以及后续新增的一级页面。
|
本文定义 GoodBuddy 桌面端的统一界面规则,适用于聊天与最近对话、知识库、智能心跳、运行记录,以及后续新增的一级页面。
|
||||||
|
|
||||||
设计系统解决两类问题:
|
设计系统解决两类问题:
|
||||||
|
|
||||||
@@ -501,13 +501,13 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
|
|||||||
- 状态卡片使用统一状态令牌,不只依赖颜色。
|
- 状态卡片使用统一状态令牌,不只依赖颜色。
|
||||||
- 运行历史与配置使用明确区块,不以多套相似页签混合导航、开关和筛选。
|
- 运行历史与配置使用明确区块,不以多套相似页签混合导航、开关和筛选。
|
||||||
|
|
||||||
### 13.5 任务与活动
|
### 13.5 运行记录
|
||||||
|
|
||||||
- 任务使用 `standard` 壳层,活动记录使用 `dashboard` 壳层。
|
- 使用 `dashboard` 壳层,并通过 `PageTabs` 提供“任务与会话 / 活动时间线 / 用量统计”三个同级视图。
|
||||||
- “任务 / 活动”作为同级页面时使用 `PageTabs`。
|
- 默认视图按“项目 → 任务或会话 → 活动详情”组织,项目范围持续可见,任务或会话详情可以折叠。
|
||||||
- 任务状态筛选使用 `SegmentedControl` 或筛选工具栏,不再模拟页签。
|
- 活动时间线按项目分组、按任务或会话建立横向轨道,所有轨道共享同一执行顺序并按事件时间排列,以带身份名称的节点表达用户、主 Agent、子专家、工具、审批、状态和并行关系;选择节点后显示所属会话、不可变范围快照和完整详情。
|
||||||
- 活动记录保留审计字段和范围,支持独立容器横向滚动。
|
- 用量统计与活动记录分离,支持按项目、会话和模型切换统计维度,宽表格在独立容器内横向滚动。
|
||||||
- 批量停止、删除和清空历史遵循破坏性操作政策。
|
- 活动状态筛选使用 `SegmentedControl`,不与页面页签混合。清空历史遵循破坏性操作政策。
|
||||||
|
|
||||||
### 13.6 魔法笔记
|
### 13.6 魔法笔记
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ describe('ActivityPanel', () => {
|
|||||||
await i18n.changeLanguage('zh-CN')
|
await i18n.changeLanguage('zh-CN')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders English interface copy while preserving activity content', async () => {
|
it('renders localized tabs and keeps usage separate from activity', async () => {
|
||||||
await i18n.changeLanguage('en-US')
|
await i18n.changeLanguage('en-US')
|
||||||
const record = makeRecord(1, 'running')
|
const record = makeRecord(1, 'running')
|
||||||
render(
|
render(
|
||||||
@@ -102,19 +102,67 @@ describe('ActivityPanel', () => {
|
|||||||
}).format(new Date(record.createdAt))
|
}).format(new Date(record.createdAt))
|
||||||
expect(screen.getAllByText(englishDate).length).toBeGreaterThan(0)
|
expect(screen.getAllByText(englishDate).length).toBeGreaterThan(0)
|
||||||
expect(
|
expect(
|
||||||
screen.getByRole('heading', {
|
screen.getByRole('heading', { level: 1, name: 'Run history' })
|
||||||
level: 1,
|
|
||||||
name: 'Tasks and activity'
|
|
||||||
})
|
|
||||||
).toBeInTheDocument()
|
).toBeInTheDocument()
|
||||||
expect(
|
expect(
|
||||||
screen.getByRole('button', { name: 'In progress' })
|
screen.getByRole('tab', { name: 'Tasks and conversations' })
|
||||||
).toBeInTheDocument()
|
).toHaveAttribute('aria-selected', 'true')
|
||||||
expect(screen.getByText(record.title)).toBeInTheDocument()
|
expect(screen.getByText(record.title)).toBeInTheDocument()
|
||||||
|
expect(screen.queryByText('Token usage')).not.toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.click(
|
||||||
|
screen.getByRole('tab', { name: 'Usage analytics' })
|
||||||
|
)
|
||||||
expect(screen.getByText('Token usage')).toBeInTheDocument()
|
expect(screen.getByText('Token usage')).toBeInTheDocument()
|
||||||
|
expect(screen.queryByText(record.title)).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('filters active and unsuccessful activity and opens its conversation', () => {
|
it('organizes records by project, conversation, and detail', () => {
|
||||||
|
const request: ActivityRecord = {
|
||||||
|
...makeRecord(1, 'running'),
|
||||||
|
kind: 'request',
|
||||||
|
title: '分析登录问题',
|
||||||
|
scope: {
|
||||||
|
kind: 'project',
|
||||||
|
projectId: 'project-1',
|
||||||
|
projectName: '项目甲'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const tool: ActivityRecord = {
|
||||||
|
...makeRecord(2),
|
||||||
|
conversationId: request.conversationId,
|
||||||
|
requestId: request.requestId,
|
||||||
|
title: '搜索 IPC 代码',
|
||||||
|
scope: request.scope
|
||||||
|
}
|
||||||
|
const { container } = render(
|
||||||
|
<ActivityPanel
|
||||||
|
onClear={vi.fn()}
|
||||||
|
onOpenConversation={vi.fn()}
|
||||||
|
records={[request, tool, makeRecord(3)]}
|
||||||
|
tokenUsage={makeTokenUsage()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(container.querySelectorAll('.activity-project')).toHaveLength(2)
|
||||||
|
expect(screen.getByText('项目:项目甲')).toBeInTheDocument()
|
||||||
|
expect(
|
||||||
|
screen.getByText('1 个任务或会话 · 2 条活动')
|
||||||
|
).toBeInTheDocument()
|
||||||
|
|
||||||
|
const projectConversation = screen
|
||||||
|
.getByText('对话:分析登录问题')
|
||||||
|
.closest('details')
|
||||||
|
expect(projectConversation).not.toBeNull()
|
||||||
|
expect(projectConversation).not.toHaveAttribute('open')
|
||||||
|
fireEvent.click(screen.getByText('对话:分析登录问题'))
|
||||||
|
expect(projectConversation).toHaveAttribute('open')
|
||||||
|
expect(
|
||||||
|
projectConversation?.querySelectorAll('article')
|
||||||
|
).toHaveLength(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('filters active and exceptional activity and opens its conversation', () => {
|
||||||
const onOpenConversation = vi.fn()
|
const onOpenConversation = vi.fn()
|
||||||
render(
|
render(
|
||||||
<ActivityPanel
|
<ActivityPanel
|
||||||
@@ -131,16 +179,15 @@ describe('ActivityPanel', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: '进行中' }))
|
fireEvent.click(screen.getByRole('button', { name: '进行中' }))
|
||||||
fireEvent.click(screen.getByText('对话:活动 1'))
|
expect(screen.getByText('对话:活动 1')).toBeInTheDocument()
|
||||||
expect(screen.getByText('活动 1')).toBeInTheDocument()
|
|
||||||
expect(screen.queryByText('活动 2')).not.toBeInTheDocument()
|
expect(screen.queryByText('活动 2')).not.toBeInTheDocument()
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: '失败' }))
|
fireEvent.click(screen.getByRole('button', { name: '异常' }))
|
||||||
fireEvent.click(screen.getByText('对话:活动 2'))
|
expect(screen.getByText('对话:活动 2')).toBeInTheDocument()
|
||||||
expect(screen.getByText('活动 2')).toBeInTheDocument()
|
expect(screen.getByText('对话:活动 3')).toBeInTheDocument()
|
||||||
expect(screen.getByText('活动 3')).toBeInTheDocument()
|
|
||||||
expect(screen.queryByText('活动 1')).not.toBeInTheDocument()
|
expect(screen.queryByText('活动 1')).not.toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('对话:活动 2'))
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
screen.getAllByRole('button', { name: '打开所属对话' })[0]!
|
screen.getAllByRole('button', { name: '打开所属对话' })[0]!
|
||||||
)
|
)
|
||||||
@@ -236,35 +283,7 @@ describe('ActivityPanel', () => {
|
|||||||
expect(within(item).getByText('进行中')).toBeInTheDocument()
|
expect(within(item).getByText('进行中')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('groups activity by conversation in collapsible sections', () => {
|
it('shows immutable project and unavailable scope snapshots', () => {
|
||||||
const first = makeRecord(1, 'running')
|
|
||||||
const second = {
|
|
||||||
...makeRecord(2, 'failed'),
|
|
||||||
conversationId: first.conversationId
|
|
||||||
}
|
|
||||||
const { container } = render(
|
|
||||||
<ActivityPanel
|
|
||||||
onClear={vi.fn()}
|
|
||||||
onOpenConversation={vi.fn()}
|
|
||||||
records={[first, second]}
|
|
||||||
tokenUsage={makeTokenUsage()}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
const groups =
|
|
||||||
container.querySelectorAll<HTMLDetailsElement>(
|
|
||||||
'details.activity-group'
|
|
||||||
)
|
|
||||||
expect(groups).toHaveLength(1)
|
|
||||||
expect(groups[0]).not.toHaveAttribute('open')
|
|
||||||
expect(within(groups[0]!).getByText('2 条活动')).toBeInTheDocument()
|
|
||||||
|
|
||||||
fireEvent.click(within(groups[0]!).getByText('对话:活动 1'))
|
|
||||||
expect(groups[0]).toHaveAttribute('open')
|
|
||||||
expect(groups[0]!.querySelectorAll('article')).toHaveLength(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('shows immutable scope snapshots on groups and records', () => {
|
|
||||||
const projectRecord: ActivityRecord = {
|
const projectRecord: ActivityRecord = {
|
||||||
...makeRecord(1),
|
...makeRecord(1),
|
||||||
scope: {
|
scope: {
|
||||||
@@ -286,11 +305,11 @@ describe('ActivityPanel', () => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(screen.getAllByText('项目:项目甲')).toHaveLength(2)
|
expect(screen.getByText('项目:项目甲')).toBeInTheDocument()
|
||||||
expect(screen.getAllByText('范围不可用')).toHaveLength(2)
|
expect(screen.getByText('范围不可用')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('uses the shared page hierarchy and explicit global scope', () => {
|
it('uses shared page tabs and explicit global scope', () => {
|
||||||
render(
|
render(
|
||||||
<ActivityPanel
|
<ActivityPanel
|
||||||
onClear={vi.fn()}
|
onClear={vi.fn()}
|
||||||
@@ -301,15 +320,102 @@ describe('ActivityPanel', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
screen.getByRole('heading', { level: 1, name: '任务与活动' })
|
screen.getByRole('heading', { level: 1, name: '运行记录' })
|
||||||
).toBeInTheDocument()
|
).toBeInTheDocument()
|
||||||
expect(screen.getByText('全部项目')).toHaveClass('scope-badge')
|
expect(screen.getByText('全部项目')).toHaveClass('scope-badge')
|
||||||
expect(screen.getByLabelText('Token 用量分组')).toHaveClass(
|
expect(screen.getByLabelText('运行记录视图')).toHaveClass(
|
||||||
'segmented-control'
|
'page-tabs'
|
||||||
)
|
)
|
||||||
|
expect(
|
||||||
|
screen.getByRole('tabpanel', { name: '任务与会话' })
|
||||||
|
).toBeInTheDocument()
|
||||||
expect(screen.getByLabelText('筛选活动')).toHaveClass(
|
expect(screen.getByLabelText('筛选活动')).toHaveClass(
|
||||||
'segmented-control'
|
'segmented-control'
|
||||||
)
|
)
|
||||||
|
expect(
|
||||||
|
screen.queryByLabelText('Token 用量分组')
|
||||||
|
).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders shared-time project and conversation activity tracks', () => {
|
||||||
|
const first: ActivityRecord = {
|
||||||
|
...makeRecord(1, 'running'),
|
||||||
|
kind: 'request',
|
||||||
|
title: '分析登录问题',
|
||||||
|
scope: {
|
||||||
|
kind: 'project',
|
||||||
|
projectId: 'project-1',
|
||||||
|
projectName: '项目甲'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const second: ActivityRecord = {
|
||||||
|
...makeRecord(2, 'failed'),
|
||||||
|
conversationId: first.conversationId,
|
||||||
|
requestId: first.requestId,
|
||||||
|
scope: first.scope
|
||||||
|
}
|
||||||
|
const third: ActivityRecord = {
|
||||||
|
...makeRecord(3),
|
||||||
|
conversationId: 'conversation-parallel',
|
||||||
|
requestId: 'request-parallel',
|
||||||
|
kind: 'subagent',
|
||||||
|
title: '研究专家',
|
||||||
|
scope: first.scope
|
||||||
|
}
|
||||||
|
const fourth: ActivityRecord = {
|
||||||
|
...makeRecord(4),
|
||||||
|
conversationId: first.conversationId,
|
||||||
|
requestId: first.requestId,
|
||||||
|
kind: 'result',
|
||||||
|
title: '任务执行完成',
|
||||||
|
scope: first.scope
|
||||||
|
}
|
||||||
|
render(
|
||||||
|
<ActivityPanel
|
||||||
|
onClear={vi.fn()}
|
||||||
|
onOpenConversation={vi.fn()}
|
||||||
|
records={[first, second, third, fourth]}
|
||||||
|
tokenUsage={makeTokenUsage()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('tab', { name: '活动时间线' }))
|
||||||
|
const timeline = screen.getByLabelText(
|
||||||
|
'按项目和会话分组的并行活动轨道'
|
||||||
|
)
|
||||||
|
expect(timeline).toHaveClass('activity-tracks')
|
||||||
|
expect(
|
||||||
|
timeline.querySelectorAll('.activity-track')
|
||||||
|
).toHaveLength(2)
|
||||||
|
expect(
|
||||||
|
timeline.querySelectorAll('.activity-track__node')
|
||||||
|
).toHaveLength(4)
|
||||||
|
expect(within(timeline).getByText('分析登录问题')).toBeInTheDocument()
|
||||||
|
expect(
|
||||||
|
within(timeline).getAllByText('研究专家').length
|
||||||
|
).toBeGreaterThan(0)
|
||||||
|
expect(within(timeline).getAllByText('用户').length).toBeGreaterThan(0)
|
||||||
|
expect(
|
||||||
|
within(timeline).getAllByText('GoodBuddy').length
|
||||||
|
).toBeGreaterThan(0)
|
||||||
|
expect(within(timeline).getByText('项目:项目甲')).toBeInTheDocument()
|
||||||
|
expect(
|
||||||
|
within(timeline).getByText(
|
||||||
|
'所有轨道共享同一执行顺序,节点按发生时间依次展开,点击节点查看身份和活动详情。'
|
||||||
|
)
|
||||||
|
).toBeInTheDocument()
|
||||||
|
|
||||||
|
fireEvent.click(
|
||||||
|
screen.getByRole('button', {
|
||||||
|
name: /工具,失败,活动 2/u
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const detail = screen.getByLabelText('选中的活动节点详情')
|
||||||
|
expect(within(detail).getByText('活动 2')).toBeInTheDocument()
|
||||||
|
expect(
|
||||||
|
within(detail).getByText('对话:分析登录问题')
|
||||||
|
).toBeInTheDocument()
|
||||||
|
expect(within(detail).getByText('项目:项目甲')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('never renders more than 500 records', () => {
|
it('never renders more than 500 records', () => {
|
||||||
@@ -339,11 +445,10 @@ describe('ActivityPanel', () => {
|
|||||||
tokenUsage={makeTokenUsage()}
|
tokenUsage={makeTokenUsage()}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
fireEvent.click(screen.getByRole('tab', { name: '用量统计' }))
|
||||||
|
|
||||||
const stats = screen.getByLabelText('Token 用量统计')
|
const stats = screen.getByLabelText('Token 用量统计')
|
||||||
expect(
|
expect(within(stats).getByText('150')).toBeInTheDocument()
|
||||||
within(stats).getByText('150')
|
|
||||||
).toBeInTheDocument()
|
|
||||||
|
|
||||||
const projectRow = screen.getByRole('row', {
|
const projectRow = screen.getByRole('row', {
|
||||||
name: '项目甲gpt-5 · openai 100 20 10 40 120'
|
name: '项目甲gpt-5 · openai 100 20 10 40 120'
|
||||||
@@ -354,7 +459,7 @@ describe('ActivityPanel', () => {
|
|||||||
).not.toBeInTheDocument()
|
).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('groups token usage and displays fallback labels', () => {
|
it('groups token usage by project, conversation, and model', () => {
|
||||||
render(
|
render(
|
||||||
<ActivityPanel
|
<ActivityPanel
|
||||||
onClear={vi.fn()}
|
onClear={vi.fn()}
|
||||||
@@ -363,6 +468,7 @@ describe('ActivityPanel', () => {
|
|||||||
tokenUsage={makeTokenUsage()}
|
tokenUsage={makeTokenUsage()}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
fireEvent.click(screen.getByRole('tab', { name: '用量统计' }))
|
||||||
|
|
||||||
expect(screen.getByText('项目甲')).toBeInTheDocument()
|
expect(screen.getByText('项目甲')).toBeInTheDocument()
|
||||||
expect(screen.getByText('未归属项目')).toBeInTheDocument()
|
expect(screen.getByText('未归属项目')).toBeInTheDocument()
|
||||||
|
|||||||
+672
-242
File diff suppressed because it is too large
Load Diff
@@ -3067,10 +3067,13 @@ describe('App', () => {
|
|||||||
throw new Error('Missing request')
|
throw new Error('Missing request')
|
||||||
}
|
}
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('任务与活动'))
|
fireEvent.click(screen.getByText('运行记录'))
|
||||||
|
fireEvent.click(
|
||||||
|
await screen.findByRole('tab', { name: '用量统计' })
|
||||||
|
)
|
||||||
const stats = await screen.findByLabelText('Token 用量统计')
|
const stats = await screen.findByLabelText('Token 用量统计')
|
||||||
expect(
|
expect(
|
||||||
screen.getByRole('heading', { level: 1, name: '任务与活动' })
|
screen.getByRole('heading', { level: 1, name: '运行记录' })
|
||||||
).toBeInTheDocument()
|
).toBeInTheDocument()
|
||||||
expect(
|
expect(
|
||||||
screen.queryByRole('button', { name: /^专家角色:/u })
|
screen.queryByRole('button', { name: /^专家角色:/u })
|
||||||
@@ -3841,7 +3844,7 @@ describe('App', () => {
|
|||||||
)
|
)
|
||||||
expect(cancelledDot).toHaveClass('message__status-dot')
|
expect(cancelledDot).toHaveClass('message__status-dot')
|
||||||
expect(cancelledDot).not.toHaveClass('message__status-dot--active')
|
expect(cancelledDot).not.toHaveClass('message__status-dot--active')
|
||||||
fireEvent.click(screen.getByText('任务与活动'))
|
fireEvent.click(screen.getByText('运行记录'))
|
||||||
expect((await screen.findAllByText('已取消')).length).toBeGreaterThan(0)
|
expect((await screen.findAllByText('已取消')).length).toBeGreaterThan(0)
|
||||||
fireEvent.click(screen.getByRole('button', { name: '进行中' }))
|
fireEvent.click(screen.getByRole('button', { name: '进行中' }))
|
||||||
expect(
|
expect(
|
||||||
@@ -5170,7 +5173,7 @@ describe('App', () => {
|
|||||||
expect(within(statusRegion).getByText('已取消')).toBeInTheDocument()
|
expect(within(statusRegion).getByText('已取消')).toBeInTheDocument()
|
||||||
expect(within(statusRegion).getByText('父任务已停止')).toBeInTheDocument()
|
expect(within(statusRegion).getByText('父任务已停止')).toBeInTheDocument()
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('任务与活动'))
|
fireEvent.click(screen.getByText('运行记录'))
|
||||||
expect(await screen.findAllByText('子专家')).toHaveLength(4)
|
expect(await screen.findAllByText('子专家')).toHaveLength(4)
|
||||||
expect(screen.getAllByText(/智能路由/u).length).toBeGreaterThan(0)
|
expect(screen.getAllByText(/智能路由/u).length).toBeGreaterThan(0)
|
||||||
expect(screen.getAllByText(/手动指定/u).length).toBeGreaterThan(0)
|
expect(screen.getAllByText(/手动指定/u).length).toBeGreaterThan(0)
|
||||||
|
|||||||
@@ -3,10 +3,16 @@ import type { activity as chineseActivity } from '../zh-CN/activity'
|
|||||||
|
|
||||||
export const activity = {
|
export const activity = {
|
||||||
header: {
|
header: {
|
||||||
eyebrow: 'ACTIVITY AUDIT',
|
eyebrow: 'RUN HISTORY',
|
||||||
title: 'Tasks and activity',
|
title: 'Run history',
|
||||||
description:
|
description:
|
||||||
'View task requests, child experts, tool calls, approval results, and token usage across all projects.'
|
'Review execution details by project, task, and conversation, or browse the activity timeline and model usage.'
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
ariaLabel: 'Run history views',
|
||||||
|
tasks: 'Tasks and conversations',
|
||||||
|
timeline: 'Activity timeline',
|
||||||
|
usage: 'Usage analytics'
|
||||||
},
|
},
|
||||||
clear: {
|
clear: {
|
||||||
confirmAriaLabel: 'Confirm clearing activity history',
|
confirmAriaLabel: 'Confirm clearing activity history',
|
||||||
@@ -34,7 +40,7 @@ export const activity = {
|
|||||||
filters: {
|
filters: {
|
||||||
all: 'All',
|
all: 'All',
|
||||||
active: 'In progress',
|
active: 'In progress',
|
||||||
failed: 'Failed',
|
failed: 'Exceptions',
|
||||||
ariaLabel: 'Filter activity',
|
ariaLabel: 'Filter activity',
|
||||||
clear: 'Clear filter'
|
clear: 'Clear filter'
|
||||||
},
|
},
|
||||||
@@ -69,7 +75,32 @@ export const activity = {
|
|||||||
ariaLabel: 'Activity totals',
|
ariaLabel: 'Activity totals',
|
||||||
all: 'All',
|
all: 'All',
|
||||||
active: 'In progress',
|
active: 'In progress',
|
||||||
failed: 'Failed'
|
failed: 'Exceptions'
|
||||||
|
},
|
||||||
|
timeline: {
|
||||||
|
ariaLabel: 'Parallel activity tracks grouped by project and conversation',
|
||||||
|
description:
|
||||||
|
'Every track shares the same execution order. Nodes are spread by event time. Select a node to inspect its identity and details.',
|
||||||
|
lanes: 'Project / conversation',
|
||||||
|
laneAriaLabel: 'Activity track for conversation {{title}}',
|
||||||
|
nodeAriaLabel: '{{actor}}, {{kind}}, {{status}}, {{title}}, {{time}}',
|
||||||
|
legendAriaLabel: 'Activity node identity legend',
|
||||||
|
detailAriaLabel: 'Selected activity node details',
|
||||||
|
closeDetail: 'Close details',
|
||||||
|
actors: {
|
||||||
|
user: 'User',
|
||||||
|
assistant: 'GoodBuddy',
|
||||||
|
subagent: 'Child expert',
|
||||||
|
tool: 'Tool',
|
||||||
|
approval: 'Approval'
|
||||||
|
},
|
||||||
|
nodes: {
|
||||||
|
user: 'U',
|
||||||
|
assistant: 'G',
|
||||||
|
tool: 'T',
|
||||||
|
approval: 'A',
|
||||||
|
subagent: 'S'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
empty: {
|
empty: {
|
||||||
active: 'There is no pending or running activity.',
|
active: 'There is no pending or running activity.',
|
||||||
@@ -87,6 +118,8 @@ export const activity = {
|
|||||||
'The scope could not be determined when this activity was recorded.',
|
'The scope could not be determined when this activity was recorded.',
|
||||||
conversation: 'Conversation: {{title}}',
|
conversation: 'Conversation: {{title}}',
|
||||||
activityCount: 'Activity items: {{formattedCount}}',
|
activityCount: 'Activity items: {{formattedCount}}',
|
||||||
|
projectSummary:
|
||||||
|
'{{conversationCount}} tasks or conversations · {{activityCount}} activity items',
|
||||||
openConversation: 'Open conversation'
|
openConversation: 'Open conversation'
|
||||||
}
|
}
|
||||||
} satisfies TranslationShape<typeof chineseActivity>
|
} satisfies TranslationShape<typeof chineseActivity>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export const app = {
|
|||||||
magicNotes: 'Magic Notes',
|
magicNotes: 'Magic Notes',
|
||||||
knowledge: 'Knowledge',
|
knowledge: 'Knowledge',
|
||||||
heartbeat: 'Smart Heartbeat',
|
heartbeat: 'Smart Heartbeat',
|
||||||
activity: 'Tasks & Activity',
|
activity: 'Run history',
|
||||||
pendingSuggestions: '{{count}} pending suggestions'
|
pendingSuggestions: '{{count}} pending suggestions'
|
||||||
},
|
},
|
||||||
route: {
|
route: {
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
export const activity = {
|
export const activity = {
|
||||||
header: {
|
header: {
|
||||||
eyebrow: 'ACTIVITY AUDIT',
|
eyebrow: 'RUN HISTORY',
|
||||||
title: '任务与活动',
|
title: '运行记录',
|
||||||
description:
|
description:
|
||||||
'查看全部项目中的任务请求、子专家、工具调用、审批结果和 Token 用量。'
|
'按项目、任务与会话查看执行详情,或浏览活动时间线和模型用量。'
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
ariaLabel: '运行记录视图',
|
||||||
|
tasks: '任务与会话',
|
||||||
|
timeline: '活动时间线',
|
||||||
|
usage: '用量统计'
|
||||||
},
|
},
|
||||||
clear: {
|
clear: {
|
||||||
confirmAriaLabel: '确认清空 {{formattedCount}} 条活动记录',
|
confirmAriaLabel: '确认清空 {{formattedCount}} 条活动记录',
|
||||||
@@ -31,7 +37,7 @@ export const activity = {
|
|||||||
filters: {
|
filters: {
|
||||||
all: '全部',
|
all: '全部',
|
||||||
active: '进行中',
|
active: '进行中',
|
||||||
failed: '失败',
|
failed: '异常',
|
||||||
ariaLabel: '筛选活动',
|
ariaLabel: '筛选活动',
|
||||||
clear: '清除筛选'
|
clear: '清除筛选'
|
||||||
},
|
},
|
||||||
@@ -66,7 +72,32 @@ export const activity = {
|
|||||||
ariaLabel: '活动统计',
|
ariaLabel: '活动统计',
|
||||||
all: '全部',
|
all: '全部',
|
||||||
active: '进行中',
|
active: '进行中',
|
||||||
failed: '失败'
|
failed: '异常'
|
||||||
|
},
|
||||||
|
timeline: {
|
||||||
|
ariaLabel: '按项目和会话分组的并行活动轨道',
|
||||||
|
description:
|
||||||
|
'所有轨道共享同一执行顺序,节点按发生时间依次展开,点击节点查看身份和活动详情。',
|
||||||
|
lanes: '项目 / 会话',
|
||||||
|
laneAriaLabel: '会话 {{title}} 的活动轨道',
|
||||||
|
nodeAriaLabel: '{{actor}},{{kind}},{{status}},{{title}},{{time}}',
|
||||||
|
legendAriaLabel: '活动节点身份图例',
|
||||||
|
detailAriaLabel: '选中的活动节点详情',
|
||||||
|
closeDetail: '关闭详情',
|
||||||
|
actors: {
|
||||||
|
user: '用户',
|
||||||
|
assistant: 'GoodBuddy',
|
||||||
|
subagent: '子专家',
|
||||||
|
tool: '工具',
|
||||||
|
approval: '审批'
|
||||||
|
},
|
||||||
|
nodes: {
|
||||||
|
user: '用',
|
||||||
|
assistant: 'G',
|
||||||
|
tool: '工',
|
||||||
|
approval: '审',
|
||||||
|
subagent: '专'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
empty: {
|
empty: {
|
||||||
active: '当前没有等待中或正在运行的活动。',
|
active: '当前没有等待中或正在运行的活动。',
|
||||||
@@ -83,6 +114,8 @@ export const activity = {
|
|||||||
'创建此活动记录时未能确定其归属范围。',
|
'创建此活动记录时未能确定其归属范围。',
|
||||||
conversation: '对话:{{title}}',
|
conversation: '对话:{{title}}',
|
||||||
activityCount: '{{formattedCount}} 条活动',
|
activityCount: '{{formattedCount}} 条活动',
|
||||||
|
projectSummary:
|
||||||
|
'{{conversationCount}} 个任务或会话 · {{activityCount}} 条活动',
|
||||||
openConversation: '打开所属对话'
|
openConversation: '打开所属对话'
|
||||||
}
|
}
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const app = {
|
|||||||
magicNotes: '魔法笔记',
|
magicNotes: '魔法笔记',
|
||||||
knowledge: '知识库',
|
knowledge: '知识库',
|
||||||
heartbeat: '智能心跳',
|
heartbeat: '智能心跳',
|
||||||
activity: '任务与活动',
|
activity: '运行记录',
|
||||||
pendingSuggestions: '{{count}} 条待处理建议'
|
pendingSuggestions: '{{count}} 条待处理建议'
|
||||||
},
|
},
|
||||||
route: {
|
route: {
|
||||||
|
|||||||
+404
-11
@@ -8445,6 +8445,16 @@ details.settings-section > :not(summary) + :not(summary) {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
container: activity-panel / inline-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-panel > .page-tabs {
|
||||||
|
padding: var(--space-4) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-panel__tab-panel {
|
||||||
|
min-width: 0;
|
||||||
|
padding-bottom: var(--space-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-list {
|
.activity-list {
|
||||||
@@ -8458,7 +8468,6 @@ details.settings-section > :not(summary) + :not(summary) {
|
|||||||
padding: 14px;
|
padding: 14px;
|
||||||
border: 1px solid var(--border-default);
|
border: 1px solid var(--border-default);
|
||||||
border-radius: var(--radius-card);
|
border-radius: var(--radius-card);
|
||||||
margin-top: var(--space-4);
|
|
||||||
background: var(--surface-raised);
|
background: var(--surface-raised);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8468,11 +8477,11 @@ details.settings-section > :not(summary) + :not(summary) {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.token-usage__header h3 {
|
.token-usage__header h2 {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: 13px;
|
font-size: var(--font-section-title);
|
||||||
}
|
}
|
||||||
|
|
||||||
.token-usage__stats {
|
.token-usage__stats {
|
||||||
@@ -8561,7 +8570,7 @@ details.settings-section > :not(summary) + :not(summary) {
|
|||||||
|
|
||||||
.activity-panel__stats {
|
.activity-panel__stats {
|
||||||
display: grid;
|
display: grid;
|
||||||
margin: 18px 0 14px;
|
margin: 0 0 var(--space-3);
|
||||||
gap: 9px;
|
gap: 9px;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
}
|
}
|
||||||
@@ -8585,22 +8594,54 @@ details.settings-section > :not(summary) + :not(summary) {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-panel__filters {
|
.activity-panel__toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-bottom: 12px;
|
align-items: flex-start;
|
||||||
gap: 6px;
|
justify-content: space-between;
|
||||||
|
margin-bottom: var(--space-3);
|
||||||
|
gap: var(--space-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-groups {
|
.activity-projects {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
gap: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-project {
|
||||||
|
overflow: clip;
|
||||||
|
border: 1px solid var(--border-default);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
background: var(--surface-raised);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-project__header {
|
||||||
|
display: flex;
|
||||||
|
min-height: 44px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--space-2) var(--space-4);
|
||||||
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
|
background: var(--surface-subtle);
|
||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.activity-project__header > span:last-child {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: var(--font-caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-conversations {
|
||||||
|
display: grid;
|
||||||
|
padding: var(--space-3);
|
||||||
|
background: var(--surface-canvas);
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
.activity-group {
|
.activity-group {
|
||||||
overflow: clip;
|
overflow: clip;
|
||||||
border: 1px solid var(--border-default);
|
border: 1px solid var(--border-default);
|
||||||
border-radius: var(--radius-card);
|
border-radius: var(--radius-control);
|
||||||
background: var(--surface-raised);
|
background: var(--surface-raised);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8648,11 +8689,15 @@ details.settings-section > :not(summary) + :not(summary) {
|
|||||||
.activity-group > .activity-list {
|
.activity-group > .activity-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
padding: var(--space-3);
|
padding: var(--space-3);
|
||||||
background: var(--surface-canvas);
|
border-top: 1px solid var(--border-subtle);
|
||||||
gap: var(--space-2);
|
gap: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-group .activity-item {
|
.activity-list__item {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-list__item .activity-item {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8725,6 +8770,23 @@ details.settings-section > :not(summary) + :not(summary) {
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.activity-item__context {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: var(--space-2);
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: var(--font-caption);
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-item__context > span:first-child {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.activity-item__conversation {
|
.activity-item__conversation {
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
margin-top: 7px;
|
margin-top: 7px;
|
||||||
@@ -8735,6 +8797,305 @@ details.settings-section > :not(summary) + :not(summary) {
|
|||||||
font-weight: 650;
|
font-weight: 650;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.activity-tracks {
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
gap: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__description {
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
border-radius: var(--radius-control);
|
||||||
|
margin: 0;
|
||||||
|
background: var(--surface-subtle);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: var(--font-caption);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__legend {
|
||||||
|
display: flex;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: var(--font-caption);
|
||||||
|
gap: var(--space-3);
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__legend li {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__legend-swatch {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--accent-solid);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__legend-swatch--assistant {
|
||||||
|
background: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__legend-swatch--subagent {
|
||||||
|
background: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__legend-swatch--tool {
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--accent) 55%,
|
||||||
|
var(--success)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__legend-swatch--approval {
|
||||||
|
border-radius: 3px;
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--warning) 65%,
|
||||||
|
var(--danger)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__scroll {
|
||||||
|
max-width: 100%;
|
||||||
|
border: 1px solid var(--border-default);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
overflow-x: auto;
|
||||||
|
background: var(--surface-raised);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__canvas {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 760px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__axis {
|
||||||
|
display: grid;
|
||||||
|
min-height: 38px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 var(--space-3);
|
||||||
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: var(--font-caption);
|
||||||
|
grid-template-columns: 210px minmax(0, 1fr) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__axis strong {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 3;
|
||||||
|
left: var(--space-3);
|
||||||
|
padding-right: var(--space-3);
|
||||||
|
background: var(--surface-raised);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__axis span:nth-child(2) {
|
||||||
|
padding-left: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__project > header {
|
||||||
|
display: flex;
|
||||||
|
min-height: 38px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
|
background: var(--surface-subtle);
|
||||||
|
gap: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__project > header > span:last-child {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: var(--font-caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track {
|
||||||
|
display: grid;
|
||||||
|
min-height: 84px;
|
||||||
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
|
grid-template-columns: 210px minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__project:last-child .activity-track:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__label {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 2;
|
||||||
|
left: 0;
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
align-content: center;
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
border-right: 1px solid var(--border-subtle);
|
||||||
|
background: var(--surface-raised);
|
||||||
|
gap: var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__label > strong {
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: var(--font-body);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__label > span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: var(--font-caption);
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__rail {
|
||||||
|
position: relative;
|
||||||
|
min-width: 0;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to right,
|
||||||
|
var(--border-subtle) 1px,
|
||||||
|
transparent 1px
|
||||||
|
);
|
||||||
|
background-size: 20% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__rail::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: var(--space-3);
|
||||||
|
left: var(--space-3);
|
||||||
|
height: 2px;
|
||||||
|
background: var(--border-default);
|
||||||
|
content: '';
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__node {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: 50%;
|
||||||
|
display: flex;
|
||||||
|
width: 68px;
|
||||||
|
height: 58px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 10px;
|
||||||
|
gap: 2px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__node:hover {
|
||||||
|
z-index: 3;
|
||||||
|
color: var(--text-primary);
|
||||||
|
transform: translate(-50%, -50%) scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__node:focus-visible {
|
||||||
|
z-index: 4;
|
||||||
|
outline: 2px solid var(--accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__node > span {
|
||||||
|
display: grid;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: 2px solid var(--surface-raised);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--accent-solid);
|
||||||
|
color: var(--text-on-accent);
|
||||||
|
font-weight: 750;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__node > small {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 9px;
|
||||||
|
line-height: 1.2;
|
||||||
|
text-align: center;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__node[aria-pressed='true'] > span {
|
||||||
|
box-shadow: 0 0 0 3px var(--accent-selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__node--actor-assistant > span {
|
||||||
|
background: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__node--actor-subagent > span {
|
||||||
|
background: var(--warning);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__node--actor-tool > span {
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--accent) 55%,
|
||||||
|
var(--success)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__node--actor-approval > span {
|
||||||
|
border-radius: 6px;
|
||||||
|
background: color-mix(
|
||||||
|
in srgb,
|
||||||
|
var(--warning) 65%,
|
||||||
|
var(--danger)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__node--running > span {
|
||||||
|
box-shadow: 0 0 0 3px var(--accent-selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__node--failed > span,
|
||||||
|
.activity-track__node--denied > span,
|
||||||
|
.activity-track__node--cancelled > span,
|
||||||
|
.activity-track__node--interrupted > span {
|
||||||
|
border-color: var(--danger);
|
||||||
|
box-shadow: 0 0 0 2px var(--danger-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-track__node--pending > span {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: var(--surface-raised);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__detail {
|
||||||
|
display: grid;
|
||||||
|
padding: var(--space-3);
|
||||||
|
border: 1px solid var(--border-default);
|
||||||
|
border-radius: var(--radius-card);
|
||||||
|
background: var(--surface-subtle);
|
||||||
|
gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__detail > .secondary-button {
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-tracks__detail .activity-item {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.heartbeat-center {
|
.heartbeat-center {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -9820,6 +10181,9 @@ details.settings-section > :not(summary) + :not(summary) {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@container activity-panel (max-width: 720px) {
|
||||||
.activity-group > summary {
|
.activity-group > summary {
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
}
|
}
|
||||||
@@ -9827,6 +10191,35 @@ details.settings-section > :not(summary) + :not(summary) {
|
|||||||
.activity-group > summary time {
|
.activity-group > summary time {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.activity-panel__toolbar {
|
||||||
|
align-items: stretch;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-panel__toolbar > .segmented-control {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container activity-panel (max-width: 480px) {
|
||||||
|
.activity-panel__stats {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-project__header {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-conversations {
|
||||||
|
padding: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-item__context > span:first-child {
|
||||||
|
width: 100%;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@container heartbeat-center (max-width: 620px) {
|
@container heartbeat-center (max-width: 620px) {
|
||||||
|
|||||||
Reference in New Issue
Block a user