ManimCat / frontend /src /studio /components /StudioTaskTimeline.tsx
Bin29's picture
Sync from main: c078b6e feat: add plot studio timing observability
47bdbad
import type { StudioTask } from '../protocol/studio-agent-types'
import { translateTaskStatus, translateTaskType } from '../labels'
import { formatStudioTime } from '../theme'
import { useI18n } from '../../i18n'
interface StudioTaskTimelineProps {
tasks: StudioTask[]
}
export function StudioTaskTimeline({ tasks }: StudioTaskTimelineProps) {
const { t } = useI18n()
if (!tasks.length) {
return <div className="text-sm text-text-secondary/55">{t('studio.timeline.empty')}</div>
}
return (
<div className="relative ml-2">
<div className="absolute bottom-0 left-[5px] top-0 w-px bg-border/10" />
{tasks.map((task, index) => (
<div key={task.id} className={`relative flex gap-3 pl-5 ${index > 0 ? 'mt-4' : ''}`}>
<div className={`absolute left-0 top-1.5 h-2.5 w-2.5 rounded-full ${taskDotColor(task.status)}`} />
<div className="min-w-0 flex-1">
<div className="text-sm text-text-primary/84">{task.title}</div>
<div className="mt-0.5 text-[11px] text-text-secondary/50">
{translateTaskType(task.type, t)} 路 {translateTaskStatus(task.status, t)} 路 {formatStudioTime(task.updatedAt)}
</div>
{task.detail && <div className="mt-1 text-xs leading-5 text-text-secondary/55">{task.detail}</div>}
</div>
</div>
))}
</div>
)
}
function taskDotColor(status: string) {
switch (status) {
case 'running':
return 'bg-emerald-500'
case 'completed':
return 'bg-sky-500'
case 'failed':
return 'bg-rose-500'
case 'queued':
case 'pending_confirmation':
return 'bg-amber-500'
default:
return 'bg-text-secondary/30'
}
}