presentation.ts 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. import type { KanbanEvent } from '@/api/kanban'
  2. export const KANBAN_STATUS_META: Record<string, { label: string; color: string; description: string }> = {
  3. triage: { label: '待分诊', color: '#8b8b86', description: '需要补充或拆解的任务' },
  4. todo: { label: '待办', color: '#68727e', description: '已明确但尚未进入执行队列' },
  5. scheduled: { label: '已计划', color: '#8b5cf6', description: '由系统在指定时间唤醒' },
  6. ready: { label: '就绪', color: '#1677ff', description: '等待调度器分配机器人' },
  7. running: { label: '进行中', color: '#1a9a68', description: '机器人正在处理' },
  8. blocked: { label: '已阻塞', color: '#d44848', description: '等待外部输入或恢复' },
  9. review: { label: '评审中', color: '#c58a15', description: '等待人工或机器人评审' },
  10. done: { label: '已完成', color: '#66756d', description: '已经完成的任务' },
  11. archived: { label: '已归档', color: '#999994', description: '已从活动看板移除' },
  12. }
  13. export const LOCKED_KANBAN_TARGETS = new Set(['scheduled', 'running', 'review', 'archived'])
  14. export function statusMeta(status: string) {
  15. return KANBAN_STATUS_META[status] ?? { label: status, color: '#7c7c78', description: status }
  16. }
  17. export function movableStatuses(columns: string[], current: string): string[] {
  18. const values = columns.includes(current) ? columns : [current, ...columns]
  19. return values.filter((status, index) => values.indexOf(status) === index
  20. && (status === current || !LOCKED_KANBAN_TARGETS.has(status)))
  21. }
  22. export function formatKanbanTime(seconds?: number | null): string {
  23. if (!seconds) return ''
  24. const milliseconds = seconds < 10_000_000_000 ? seconds * 1_000 : seconds
  25. const difference = Date.now() - milliseconds
  26. if (difference < 60_000) return '刚刚'
  27. if (difference < 3_600_000) return `${Math.max(1, Math.floor(difference / 60_000))} 分钟前`
  28. if (difference < 86_400_000) return `${Math.max(1, Math.floor(difference / 3_600_000))} 小时前`
  29. return new Intl.DateTimeFormat('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' })
  30. .format(new Date(milliseconds))
  31. }
  32. function eventPayload(event: KanbanEvent): Record<string, unknown> {
  33. if (event.payload && typeof event.payload === 'object' && !Array.isArray(event.payload)) {
  34. return event.payload as Record<string, unknown>
  35. }
  36. if (typeof event.payload !== 'string') return {}
  37. try {
  38. const parsed = JSON.parse(event.payload) as unknown
  39. return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, unknown> : {}
  40. } catch { return {} }
  41. }
  42. export function eventDescription(event: KanbanEvent): string {
  43. const payload = eventPayload(event)
  44. const text = (key: string) => typeof payload[key] === 'string' ? payload[key] as string : ''
  45. if (event.kind === 'created') return `创建任务${text('assignee') ? `,分配给 ${text('assignee')}` : ''}`
  46. if (event.kind === 'status') return `移动到 ${statusMeta(text('status')).label}`
  47. if (event.kind === 'assigned') return text('assignee') ? `分配给 ${text('assignee')}` : '取消分配'
  48. if (event.kind === 'commented') return `添加评论${text('author') ? ` · ${text('author')}` : ''}`
  49. if (event.kind === 'claimed' || event.kind === 'spawned') return '机器人开始处理'
  50. if (event.kind === 'completed') return '任务完成'
  51. if (event.kind === 'blocked') return `任务阻塞${text('reason') ? ` · ${text('reason')}` : ''}`
  52. if (event.kind === 'unblocked') return '任务已恢复'
  53. if (event.kind === 'reclaimed') return '运行已回收'
  54. return event.kind.replaceAll('_', ' ')
  55. }