File size: 1,712 Bytes
33e9c6e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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'
  }
}