ScriptProgress.vue 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
  1. <script setup lang="ts">
  2. // 脚本调用工具的进度(docs/design/17-run-script.md):已调用次数、脚本报告的进度、用时
  3. import type { ScriptProgressPayload } from '@agent-server/shared'
  4. defineProps<{ scripts: ScriptProgressPayload[] }>()
  5. const STATUS: Record<ScriptProgressPayload['status'], string> = { running: '脚本运行中', completed: '脚本已完成', failed: '脚本未正常结束', cancelled: '脚本已取消' }
  6. function elapsed(ms: number) {
  7. const s = Math.floor(ms / 1000)
  8. return s < 60 ? `${s} 秒` : `${Math.floor(s / 60)} 分 ${s % 60} 秒`
  9. }
  10. const pct = (p: ScriptProgressPayload) => (p.total ? Math.min(100, Math.round(((p.done ?? 0) / p.total) * 100)) : null)
  11. </script>
  12. <template>
  13. <div class="script-progress">
  14. <div v-for="p in scripts" :key="p.scriptId" class="row" :class="p.status">
  15. <div class="line">
  16. <span class="codicon" :class="p.status === 'running' ? 'codicon-loading codicon-modifier-spin' : p.status === 'completed' ? 'codicon-pass' : 'codicon-error'" />
  17. <span class="status">{{ STATUS[p.status] }}</span>
  18. <span class="title">{{ p.title }}</span>
  19. <span class="meta">
  20. 已调用工具 {{ p.calls }} 次<template v-if="p.failures">(失败 {{ p.failures }})</template>
  21. <template v-if="p.total"> · {{ p.done ?? 0 }} / {{ p.total }}</template>
  22. <template v-if="p.message"> · {{ p.message }}</template>
  23. · {{ elapsed(p.elapsedMs) }}
  24. </span>
  25. </div>
  26. <div v-if="pct(p) !== null" class="bar"><div class="fill" :style="{ width: `${pct(p)}%` }" /></div>
  27. </div>
  28. </div>
  29. </template>
  30. <style scoped>
  31. .script-progress { display: flex; flex-direction: column; gap: 6px; padding: 6px 0 0 30px; }
  32. .row { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--app-secondary-foreground); }
  33. .line { display: flex; align-items: baseline; flex-wrap: wrap; gap: 0 6px; min-width: 0; }
  34. .line .codicon { font-size: 12px; align-self: center; }
  35. .row.completed .codicon { color: var(--app-success); }
  36. .row.failed .codicon, .row.cancelled .codicon { color: var(--app-danger); }
  37. .status { color: var(--app-primary-foreground); font-weight: 500; }
  38. .title { overflow-wrap: anywhere; }
  39. .meta { overflow-wrap: anywhere; }
  40. .bar { height: 3px; max-width: 320px; border-radius: 2px; background: var(--app-input-border); overflow: hidden; }
  41. .fill { height: 100%; background: var(--app-claude-orange); transition: width 0.3s; }
  42. </style>