refactor(web): Overview is now a dashboard, drop the idea-capture hero

Per direction: 概述 should be a 看板, not a "turn an idea into shippable
software" marketing surface.
- Removed the idea-hero (home-hero.tsx: IdeaInput + mcp-fed ContinueTasks/
  TodayFocus/HelperEntries, which were often empty/unreachable).
- Overview now renders the live metrics dashboard (CockpitView, previously
  dead code): Running / Completed / Failed / Pending counts by display_status
  + recent runs, fed by the working listAgentDeployments.
- Dropped the marketing "Quick actions / Control plane online" tier.
- Subtitle: "From an idea to shippable software." -> "Live overview of your
  Agent runs." Added zh translations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 23:22:44 +08:00
co-authored by Claude Opus 4.8
parent 6f76162126
commit 8329a323b0
4 changed files with 25 additions and 622 deletions
@@ -3,7 +3,6 @@ import { useQuery } from '@tanstack/react-query'
import { Link } from '@tanstack/react-router'
import { useTranslation } from 'react-i18next'
import {
Activity,
AlertTriangle,
ArrowUpRight,
Building2,
@@ -12,7 +11,6 @@ import {
PlayCircle,
Rocket,
ScrollText,
StopCircle,
} from 'lucide-react'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
@@ -155,7 +153,7 @@ export function CockpitView() {
pending: 0,
}
list.forEach((d) => {
counters[classifyStatus(d.status || d.phase || '')]++
counters[classifyStatus(d.display_status || d.status || '')]++
})
const total = list.length || 1
const successRate = Math.round(
@@ -185,30 +183,30 @@ export function CockpitView() {
{/* Tier 1: Status */}
<div className='grid grid-cols-2 gap-3 lg:grid-cols-4'>
<StatCard
label={t('Running deployments')}
label={t('Running agents')}
value={String(stats.counters.running)}
hint={t('Active code delivery runs across tenants')}
hint={t('Agent runs in progress')}
icon={PlayCircle}
tone='primary'
/>
<StatCard
label={t('Failed (visible window)')}
value={String(stats.counters.failed)}
hint={t('Triggers an audit entry')}
icon={AlertTriangle}
tone='danger'
/>
<StatCard
label={t('Healthy ratio')}
value={`${stats.successRate}%`}
hint={t('Running + success / total')}
label={t('Completed')}
value={String(stats.counters.success)}
hint={t('Finished with a deliverable')}
icon={CheckCircle2}
tone='success'
/>
<StatCard
label={t('Pending approvals')}
label={t('Failed')}
value={String(stats.counters.failed)}
hint={t('Failed or no deliverable')}
icon={AlertTriangle}
tone='danger'
/>
<StatCard
label={t('Pending')}
value={String(stats.counters.pending)}
hint={t('Awaiting platform arbitration')}
hint={t('Queued / waiting / needs codegen')}
icon={CircleDashed}
tone='warn'
/>
@@ -293,74 +291,7 @@ export function CockpitView() {
</ul>
)}
</section>
</div>
{/* Tier 3: Action */}
<section className='rounded-2xl border border-[color-mix(in_oklch,var(--primary)_22%,var(--border))] bg-[color-mix(in_oklch,var(--card)_70%,transparent)] p-5 backdrop-blur'>
<header className='mb-4 flex items-center justify-between'>
<div>
<p className='text-[11px] font-semibold tracking-[0.16em] text-muted-foreground uppercase'>
{t('Quick actions')}
</p>
<h3 className='mt-1 text-lg font-semibold'>
{t('Common code-delivery actions')}
</h3>
</div>
<span className='inline-flex items-center gap-1.5 rounded-full bg-emerald-500/15 px-2.5 py-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-emerald-400 ring-1 ring-inset ring-emerald-500/30'>
<Activity className='h-3 w-3' />
{t('Control plane online')}
</span>
</header>
<div className='grid gap-3 sm:grid-cols-2 lg:grid-cols-4'>
<ActionTile
icon={Rocket}
title={t('New deployment')}
desc={t('Create a new code delivery run with checks.')}
to='/deployments'
/>
<ActionTile
icon={StopCircle}
title={t('Halt failing run')}
desc={t('Stop a deployment and capture an audit record.')}
to='/deployments'
/>
</div>
</section>
</div>
)
}
function ActionTile({
icon: Icon,
title,
desc,
to,
}: {
icon: React.ComponentType<{ className?: string }>
title: string
desc: string
to: '/deployments'
}) {
return (
<Link
to={to}
className='group flex flex-col gap-3 rounded-xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] bg-[color-mix(in_oklch,var(--card)_55%,transparent)] p-4 transition hover:border-primary/45 hover:bg-[color-mix(in_oklch,var(--primary)_10%,var(--card))]'
>
<div className='inline-flex h-9 w-9 items-center justify-center rounded-lg bg-[color-mix(in_oklch,var(--primary)_18%,transparent)] text-primary'>
<Icon className='h-4 w-4' />
</div>
<div>
<p className='text-sm font-semibold'>{title}</p>
<p className='mt-1 text-xs leading-relaxed text-muted-foreground'>
{desc}
</p>
</div>
<span className='mt-auto inline-flex items-center gap-1 text-[11px] font-semibold uppercase tracking-[0.12em] text-primary opacity-70 group-hover:opacity-100'>
{title}
<ArrowUpRight className='h-3 w-3' />
</span>
</Link>
)
}
@@ -1,534 +0,0 @@
/**
* Heicode 首页主视图 — 实现 docs/product-package/10-frontend-detail-spec.md
* 「初始首页」+ 11-product-prototype-wireframes.md §4 Manager 辅助控制台原型。
*
* 四块对应 §10:
* 主输入 — "你想把什么想法变成可以上线的软件?"
* 继续任务 — 最近任务、运行状态、待审批提示
* 今日焦点 — 当前最重要任务、失败任务、待确认事项
* 辅助入口 — 客户端下载、账户安全、准备清单、最近审计
*
* 设计原则(§10 §"设计原则" 1-8):
* 1. 第一屏只强调"你想做什么"
* 2. 主流程围绕当前任务,不围绕后台模块
* 8. 状态必须覆盖空态、加载、错误、成功
*
* Manager 不承担主开发对话(§13 §5.1)— 主输入只把想法暂存到 localStorage
* 草稿,提示用户打开客户端继续推进。后端 /api/task 之类的 idea-task 接口
* 还没接,等接通后这个 stash 流程换成 POST /api/task。
*/
import { useCallback, useMemo, useRef, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Link, useNavigate } from '@tanstack/react-router'
import {
ArrowRight,
ArrowUpRight,
CheckCircle2,
CircleDashed,
Cpu,
Download,
GitBranch,
PlayCircle,
Rocket,
Sparkles,
UserCog,
XCircle,
} from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { toast as sonnerToast } from 'sonner'
import {
createTaskFromIntent,
listHeicodeTasks,
type HeicodeTask,
} from '@/lib/heicode-mcp'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton'
const DRAFT_STORAGE_KEY = 'heicode_idea_draft'
type StatusKey = 'running' | 'success' | 'failed' | 'pending'
const STATUS_MAP: Record<string, StatusKey> = {
running: 'running',
active: 'running',
in_progress: 'running',
succeeded: 'success',
success: 'success',
completed: 'success',
failed: 'failed',
error: 'failed',
rejected: 'failed',
pending: 'pending',
queued: 'pending',
awaiting: 'pending',
}
function classifyStatus(s: string): StatusKey {
return STATUS_MAP[(s || '').toLowerCase()] ?? 'pending'
}
function StatusBadge({ phase }: { phase: string }) {
const k = classifyStatus(phase)
const palette: Record<StatusKey, { cls: string; Icon: typeof PlayCircle }> = {
running: {
cls: 'bg-[color-mix(in_oklch,var(--primary)_22%,transparent)] text-primary ring-primary/40',
Icon: PlayCircle,
},
success: {
cls: 'bg-emerald-500/15 text-emerald-400 ring-emerald-500/30',
Icon: CheckCircle2,
},
failed: {
cls: 'bg-rose-500/15 text-rose-400 ring-rose-500/30',
Icon: XCircle,
},
pending: {
cls: 'bg-amber-500/15 text-amber-400 ring-amber-500/30',
Icon: CircleDashed,
},
}
const p = palette[k]
return (
<span
className={cn(
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold tracking-[0.12em] uppercase ring-1 ring-inset',
p.cls
)}
>
<p.Icon className='h-3 w-3' />
{phase || 'pending'}
</span>
)
}
function formatRelativeTimeMs(ms?: number): string {
if (!ms) return '—'
const diff = Date.now() - ms
if (diff < 0) return '—'
const sec = Math.round(diff / 1000)
if (sec < 60) return `${sec}s`
const min = Math.round(sec / 60)
if (min < 60) return `${min}m`
const hr = Math.round(min / 60)
if (hr < 24) return `${hr}h`
return `${Math.round(hr / 24)}d`
}
function IdeaInput({ t }: { t: ReturnType<typeof useTranslation>['t'] }) {
const [value, setValue] = useState<string>(() => {
if (typeof window === 'undefined') return ''
return window.localStorage.getItem(DRAFT_STORAGE_KEY) ?? ''
})
const [submitting, setSubmitting] = useState(false)
const taRef = useRef<HTMLTextAreaElement | null>(null)
const navigate = useNavigate()
// POST /api/user/tasks/intent on the mcp-server. Backend returns a new
// HeicodeTask with status=configuring and a thread containing the first
// round of follow-up questions. We navigate to /tasks/$id so the user
// can answer them — that's where the recommendation summary materialises
// (status flips to running once all followups answered, see §6.4).
const handleSubmit = useCallback(async () => {
const trimmed = value.trim()
if (!trimmed) {
taRef.current?.focus()
return
}
try {
window.localStorage.setItem(DRAFT_STORAGE_KEY, trimmed)
} catch {
/* localStorage unavailable — ignore */
}
setSubmitting(true)
try {
const task = await createTaskFromIntent(trimmed)
sonnerToast.success(
t('Task drafted. Answer the follow-ups to build the recommendation.')
)
void navigate({ to: '/tasks/$id', params: { id: task.id } })
} catch (err) {
sonnerToast.error(
err instanceof Error ? err.message : t('Failed to create task')
)
} finally {
setSubmitting(false)
}
}, [value, t, navigate])
return (
<section
className='relative overflow-hidden rounded-3xl border p-6 sm:p-8'
style={{
borderColor: 'rgba(123,107,227,0.28)',
backgroundColor: 'rgba(123,107,227,0.04)',
backgroundImage:
'radial-gradient(circle at 0% 0%, rgba(184,136,229,0.16), transparent 60%), radial-gradient(circle at 100% 100%, rgba(107,124,224,0.14), transparent 55%)',
}}
>
<div className='relative z-10 flex flex-col gap-5'>
<div className='flex items-start gap-3'>
<span
className='inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl text-white shadow-[0_10px_30px_-12px_rgba(123,107,227,0.6)]'
style={{ backgroundImage: 'var(--gradient-brand)' }}
>
<Sparkles className='h-5 w-5' />
</span>
<div className='min-w-0 flex-1'>
<h2
className='text-2xl font-semibold tracking-tight sm:text-3xl'
style={{
backgroundImage: 'var(--gradient-brand)',
WebkitBackgroundClip: 'text',
backgroundClip: 'text',
color: 'transparent',
}}
>
{t('What idea would you like to turn into shippable software?')}
</h2>
<p className='text-muted-foreground mt-2 text-sm'>
{t(
'Describe the product, feature, or fix. Heicode will draft the task, recommend resources, and dispatch the Agent team.'
)}
</p>
</div>
</div>
<textarea
ref={taRef}
value={value}
onChange={(e) => setValue(e.target.value)}
rows={5}
spellCheck={false}
placeholder={t(
'e.g. A task-management SaaS for small teams: login, projects, tasks, comments, notifications, deploy to Azure.'
)}
className='w-full resize-none rounded-2xl border px-4 py-3 text-sm leading-relaxed shadow-[inset_0_1px_0_rgba(123,107,227,0.10)] transition outline-none focus:ring-2 focus:ring-[color-mix(in_oklch,var(--primary)_40%,transparent)]'
style={{
borderColor: 'rgba(123,107,227,0.22)',
backgroundColor: 'rgba(255,255,255,0.04)',
}}
onKeyDown={(e) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
e.preventDefault()
handleSubmit()
}
}}
/>
<div className='flex flex-wrap items-center justify-between gap-3'>
<p className='text-muted-foreground text-xs'>
{t(
'Heicode Manager only captures the idea. The main task conversation happens in the desktop client.'
)}
</p>
<Button
type='button'
onClick={handleSubmit}
disabled={submitting}
className='h-10 gap-2 rounded-xl px-5 text-sm font-semibold text-white shadow-[0_18px_48px_-18px_rgba(123,107,227,0.65)] transition-transform hover:translate-y-[-1px] active:translate-y-0 active:scale-[0.99]'
style={{
backgroundImage: 'var(--gradient-brand-btn)',
border: '1px solid rgba(255,255,255,0.16)',
}}
>
<Sparkles className='h-4 w-4' />
{submitting ? t('Saving') : t('Save idea')}
<span className='ms-1 hidden text-[10px] font-medium opacity-80 sm:inline'>
{t('⌘/Ctrl + Enter')}
</span>
</Button>
</div>
</div>
</section>
)
}
function ContinueTasks({
tasks,
isLoading,
t,
}: {
tasks: HeicodeTask[]
isLoading: boolean
t: ReturnType<typeof useTranslation>['t']
}) {
const recent = useMemo(() => {
const sorted = [...tasks].sort(
(a, b) => (b.updated_at || 0) - (a.updated_at || 0)
)
return sorted.slice(0, 4)
}, [tasks])
return (
<section className='rounded-2xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] bg-[color-mix(in_oklch,var(--card)_60%,transparent)] p-5'>
<header className='mb-4 flex items-center justify-between'>
<div>
<p className='text-muted-foreground text-[11px] font-semibold tracking-[0.16em] uppercase'>
{t('Continue working')}
</p>
<h3 className='mt-0.5 text-base font-semibold'>
{t('Recent tasks')}
</h3>
</div>
<Button
asChild
variant='ghost'
size='sm'
className='text-primary gap-1'
>
<Link to='/deployments'>
{t('All tasks')}
<ArrowUpRight className='h-3.5 w-3.5' />
</Link>
</Button>
</header>
{isLoading ? (
<div className='space-y-2'>
{Array.from({ length: 3 }).map((_, i) => (
<Skeleton key={i} className='h-16 rounded-xl' />
))}
</div>
) : recent.length === 0 ? (
<p className='bg-background/40 text-muted-foreground rounded-xl border border-dashed border-[color-mix(in_oklch,var(--primary)_22%,var(--border))] p-4 text-center text-xs'>
{t(
'No tasks yet. Capture an idea above, then confirm the recommendation in the desktop client to launch Agent.'
)}
</p>
) : (
<ul className='space-y-2'>
{recent.map((task) => (
<li key={task.id}>
<Link
to='/tasks/$id'
params={{ id: task.id }}
className='bg-background/40 hover:border-primary/40 flex items-start justify-between gap-3 rounded-xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] p-3 transition hover:-translate-y-px hover:shadow-[0_18px_48px_-32px_rgba(123,107,227,0.4)]'
>
<div className='min-w-0 flex-1'>
<p className='line-clamp-1 text-sm font-medium'>
{task.name || task.intent || t('Untitled task')}
</p>
<p className='text-muted-foreground mt-1 text-[11px]'>
{task.status_caption || task.status}
{' · '}
{t('Updated')} {formatRelativeTimeMs(task.updated_at)}{' '}
{t('ago')}
</p>
</div>
<StatusBadge phase={task.status} />
</Link>
</li>
))}
</ul>
)}
</section>
)
}
function TodayFocus({
tasks,
isLoading,
t,
}: {
tasks: HeicodeTask[]
isLoading: boolean
t: ReturnType<typeof useTranslation>['t']
}) {
const buckets = useMemo(() => {
const failed = tasks.filter(
(task) => classifyStatus(task.status) === 'failed'
)
// "Pending confirmation" maps to HeicodeTask.status = 'configuring'
// (followups not all answered yet) AND tasks awaiting_approval.
const pending = tasks.filter(
(task) =>
task.status === 'configuring' || task.status === 'awaiting_approval'
)
const running = tasks.filter(
(task) => classifyStatus(task.status) === 'running'
)
return { failed, pending, running }
}, [tasks])
const focusItems: Array<{
Icon: typeof XCircle
tone: 'failed' | 'pending' | 'running' | 'idle'
title: string
count: number
hint: string
}> = [
{
Icon: XCircle,
tone: 'failed',
title: t('Failed tasks'),
count: buckets.failed.length,
hint: t('Review and decide next action'),
},
{
Icon: CircleDashed,
tone: 'pending',
title: t('Pending confirmation'),
count: buckets.pending.length,
hint: t('Awaiting recommendation summary review'),
},
{
Icon: PlayCircle,
tone: 'running',
title: t('Running'),
count: buckets.running.length,
hint: t('Active Agent sub-loops'),
},
]
const toneCls: Record<'failed' | 'pending' | 'running' | 'idle', string> = {
failed: 'text-rose-400 bg-rose-500/10 ring-rose-500/25',
pending: 'text-amber-400 bg-amber-500/10 ring-amber-500/25',
running:
'text-primary bg-[color-mix(in_oklch,var(--primary)_18%,transparent)] ring-primary/30',
idle: 'text-muted-foreground bg-muted/40 ring-border',
}
return (
<section className='rounded-2xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] bg-[color-mix(in_oklch,var(--card)_60%,transparent)] p-5'>
<header className='mb-4'>
<p className='text-muted-foreground text-[11px] font-semibold tracking-[0.16em] uppercase'>
{t("Today's focus")}
</p>
<h3 className='mt-0.5 text-base font-semibold'>
{t('What needs your attention')}
</h3>
</header>
{isLoading ? (
<div className='grid gap-2 sm:grid-cols-3'>
{Array.from({ length: 3 }).map((_, i) => (
<Skeleton key={i} className='h-20 rounded-xl' />
))}
</div>
) : (
<div className='grid gap-3 sm:grid-cols-3'>
{focusItems.map((it) => (
<div
key={it.title}
className='bg-background/40 rounded-xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] p-3'
>
<div className='flex items-center gap-2'>
<span
className={cn(
'inline-flex h-7 w-7 items-center justify-center rounded-lg ring-1 ring-inset',
toneCls[it.tone]
)}
>
<it.Icon className='h-3.5 w-3.5' />
</span>
<p className='text-muted-foreground text-xs font-medium'>
{it.title}
</p>
</div>
<p className='mt-2 text-2xl font-semibold'>{it.count}</p>
<p className='text-muted-foreground mt-0.5 text-[11px]'>
{it.hint}
</p>
</div>
))}
</div>
)}
</section>
)
}
function HelperEntries({ t }: { t: ReturnType<typeof useTranslation>['t'] }) {
const entries: Array<{
Icon: typeof GitBranch
title: string
desc: string
to: string
}> = [
{
Icon: Download,
title: t('Heicode desktop client'),
desc: t('Download macOS / Windows builds'),
to: '/desktop-client',
},
{
Icon: Cpu,
title: t('Models and balance'),
desc: t('Available models, quota and recent usage'),
to: '/wallet',
},
{
Icon: UserCog,
title: t('Account security'),
desc: t('Tokens, password and active sessions'),
to: '/profile',
},
{
Icon: Rocket,
title: t('Task overview'),
desc: t('Status of every Agent task you launched'),
to: '/deployments',
},
]
return (
<section>
<header className='mb-3'>
<p className='text-muted-foreground text-[11px] font-semibold tracking-[0.16em] uppercase'>
{t('Helpers')}
</p>
<h3 className='mt-0.5 text-base font-semibold'>
{t('Other things you can do')}
</h3>
</header>
<div className='grid gap-3 sm:grid-cols-2 lg:grid-cols-3'>
{entries.map((e) => (
<Link
key={e.to}
to={e.to}
className='group hover:border-primary/40 flex items-start gap-3 rounded-2xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] bg-[color-mix(in_oklch,var(--card)_55%,transparent)] p-4 transition hover:-translate-y-0.5 hover:shadow-[0_18px_48px_-32px_rgba(123,107,227,0.55)]'
>
<span
className='text-primary inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-xl'
style={{
backgroundColor: 'rgba(123,107,227,0.12)',
border: '1px solid rgba(123,107,227,0.22)',
}}
>
<e.Icon className='h-4 w-4' />
</span>
<div className='min-w-0 flex-1'>
<p className='flex items-center gap-1 text-sm font-medium'>
{e.title}
<ArrowRight className='h-3.5 w-3.5 -translate-x-0.5 opacity-0 transition group-hover:translate-x-0 group-hover:opacity-70' />
</p>
<p className='text-muted-foreground mt-0.5 text-xs'>{e.desc}</p>
</div>
</Link>
))}
</div>
</section>
)
}
export function HomeHeroView() {
const { t } = useTranslation()
const { data, isLoading } = useQuery({
queryKey: ['heicode', 'tasks', 'recent'],
queryFn: () => listHeicodeTasks({ limit: 20 }),
refetchInterval: 60_000,
// mcp-server may be unreachable while the JWT bridge isn't established.
// Treat that as empty list instead of a hard error in the hero.
retry: false,
})
const tasks = data?.items ?? []
return (
<div className='space-y-5'>
<IdeaInput t={t} />
<div className='grid gap-5 lg:grid-cols-[minmax(0,1.05fr)_minmax(0,1fr)]'>
<ContinueTasks tasks={tasks} isLoading={isLoading} t={t} />
<TodayFocus tasks={tasks} isLoading={isLoading} t={t} />
</div>
<HelperEntries t={t} />
</div>
)
}
+4 -4
View File
@@ -7,7 +7,7 @@ import { Skeleton } from '@/components/ui/skeleton'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { SectionPageLayout } from '@/components/layout'
import { FadeIn } from '@/components/page-transition'
import { HomeHeroView } from './components/home-hero'
import { CockpitView } from './components/cockpit'
import { ModelsFilter } from './components/models/models-filter-dialog'
import { DEFAULT_TIME_GRANULARITY } from './constants'
import {
@@ -78,9 +78,9 @@ const SECTION_META: Record<
{ titleKey: string; descriptionKey: string }
> = {
overview: {
// Hero per docs/product-package/10 §"初始首页": main input + continue + focus + helpers.
// Dashboard / 看板: live counts of Agent runs + recent runs.
titleKey: 'Heicode',
descriptionKey: 'From an idea to shippable software.',
descriptionKey: 'Live overview of your Agent runs.',
},
models: {
titleKey: 'Model usage analytics',
@@ -168,7 +168,7 @@ export function Dashboard() {
</TabsList>
</Tabs>
)}
{activeSection === 'overview' && <HomeHeroView />}
{activeSection === 'overview' && <CockpitView />}
{activeSection === 'models' && (
<>
<FadeIn>
+6
View File
@@ -212,6 +212,12 @@
"All Status": "所有状态",
"All statuses": "全部状态",
"Agent runs": "运行总览",
"Running agents": "正在运行",
"Agent runs in progress": "进行中的 Agent 运行",
"Finished with a deliverable": "已完成且有交付物",
"Failed or no deliverable": "失败或无交付物",
"Queued / waiting / needs codegen": "排队 / 等待 / 需补代码",
"Live overview of your Agent runs.": "你的 Agent 运行实时概览。",
"Every Agent run on the server, by mode and Manager-judged status. The desktop client shows the runs you started there.": "服务端上的全部 Agent 运行,按模式和 Manager 裁决状态展示。桌面客户端只显示你在客户端发起的运行。",
"Completed without deliverable": "完成但无交付物",
"Needs codegen": "需补充代码",