fix(web): 整理 Code Review 候選分類
This commit is contained in:
@@ -13,97 +13,83 @@ import {
|
||||
FileCheck2,
|
||||
GitBranch,
|
||||
Gauge,
|
||||
ListChecks,
|
||||
LockKeyhole,
|
||||
SearchCheck,
|
||||
ShieldCheck,
|
||||
Workflow,
|
||||
} from 'lucide-react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
const agents = [
|
||||
{ name: 'Hermes', role: '變更摘要與規則脈絡', state: 'wired' },
|
||||
{ name: 'OpenClaw', role: 'AI 程式碼審查編排', state: 'wired' },
|
||||
{ name: 'Elephant Alpha', role: '風險分級與修復決策', state: 'wired' },
|
||||
{ name: 'NemoTron', role: '高風險推理席位', state: 'standby' },
|
||||
{ name: 'Hermes', roleKey: 'hermes', state: 'wired' },
|
||||
{ name: 'OpenClaw', roleKey: 'openclaw', state: 'wired' },
|
||||
{ name: 'Elephant Alpha', roleKey: 'elephantAlpha', state: 'wired' },
|
||||
{ name: 'NemoTron', roleKey: 'nemotron', state: 'standby' },
|
||||
]
|
||||
|
||||
const stages = [
|
||||
{ label: '推送', state: 'Gitea main' },
|
||||
{ label: '啟動', state: 'Telegram 卡片' },
|
||||
{ label: '掃描', state: '機密 / 毀滅性操作 / 差異檢查' },
|
||||
{ label: '分級', state: '危急 / 高 / 中 / 低' },
|
||||
{ label: '回報', state: 'Telegram 報告' },
|
||||
{ key: 'push' },
|
||||
{ key: 'start' },
|
||||
{ key: 'scan' },
|
||||
{ key: 'classify' },
|
||||
{ key: 'report' },
|
||||
]
|
||||
|
||||
const agentStateLabel: Record<string, string> = {
|
||||
wired: '已接線',
|
||||
standby: '待命',
|
||||
}
|
||||
const handoffFlags = [
|
||||
'codex_handoff_mode=read_only_candidate',
|
||||
'codex_auto_coding_enabled=false',
|
||||
'runtime_execution_authorized=false',
|
||||
'production_deploy_authorized=false',
|
||||
]
|
||||
|
||||
const codexHandoffLanes = [
|
||||
{
|
||||
key: 'draftable',
|
||||
icon: Code2,
|
||||
title: '可交給 Codex 起草',
|
||||
metric: '4 類',
|
||||
detail: '前端體驗、測試補洞、文件同步、低風險重構。',
|
||||
guard: '只建立草稿修正與驗證清單。',
|
||||
className: 'border-emerald-500/30 bg-emerald-500/10 text-emerald-100',
|
||||
iconClassName: 'text-emerald-300',
|
||||
},
|
||||
{
|
||||
key: 'approvalRequired',
|
||||
icon: FileCheck2,
|
||||
title: '需人工批准後接手',
|
||||
metric: '3 類',
|
||||
detail: '高風險程式路徑、部署流程、跨專案設定。',
|
||||
guard: '先有人控範圍、回滾條件與證據。',
|
||||
className: 'border-amber-500/30 bg-amber-500/10 text-amber-100',
|
||||
iconClassName: 'text-amber-300',
|
||||
},
|
||||
{
|
||||
key: 'blocked',
|
||||
icon: LockKeyhole,
|
||||
title: '禁止自動轉工作',
|
||||
metric: '5 條',
|
||||
detail: 'Kali 主機變更、掃描、正式推版、主要來源切換、執行期閘門。',
|
||||
guard: '維持 Gate 0 與獨立人工批准。',
|
||||
className: 'border-rose-500/30 bg-rose-500/10 text-rose-100',
|
||||
iconClassName: 'text-rose-300',
|
||||
},
|
||||
]
|
||||
|
||||
const codexCandidateWork = [
|
||||
{
|
||||
id: 'CR-CX-01',
|
||||
track: '前端體驗',
|
||||
finding: '版面密度、手機寬度、文字層級',
|
||||
handoff: 'Codex 起草 UI 修正與截圖證據',
|
||||
gate: '人工確認 scope',
|
||||
state: '候選',
|
||||
},
|
||||
{
|
||||
id: 'CR-CX-02',
|
||||
track: '守門測試',
|
||||
finding: 'IwoooS 邊界、禁用動作、進度口徑',
|
||||
handoff: '補 guard / smoke test',
|
||||
gate: '不得碰 runtime',
|
||||
state: '候選',
|
||||
},
|
||||
{
|
||||
id: 'CR-CX-03',
|
||||
track: '繁中紀錄',
|
||||
finding: '部署證據、LOGBOOK、平行工作同步',
|
||||
handoff: '整理已驗證事實',
|
||||
gate: '不寫未驗證結論',
|
||||
state: '候選',
|
||||
},
|
||||
{
|
||||
id: 'CR-CX-04',
|
||||
track: '禁止轉派',
|
||||
finding: 'Kali 更新、掃描、GitHub primary、正式部署',
|
||||
handoff: '維持人工閘門',
|
||||
gate: '獨立批准',
|
||||
state: '封鎖',
|
||||
},
|
||||
{ id: 'CR-CX-01', key: 'frontendExperience', state: 'candidate' },
|
||||
{ id: 'CR-CX-02', key: 'testCoverage', state: 'candidate' },
|
||||
{ id: 'CR-CX-03', key: 'documentationSync', state: 'candidate' },
|
||||
{ id: 'CR-CX-04', key: 'lowRiskRefactor', state: 'candidate' },
|
||||
{ id: 'CR-BLOCK-01', key: 'runtimeBlocked', state: 'blocked' },
|
||||
]
|
||||
|
||||
const approvalFlowSteps = [
|
||||
'candidate',
|
||||
'manualApproval',
|
||||
'codexTask',
|
||||
'patch',
|
||||
'guards',
|
||||
'deploy',
|
||||
]
|
||||
|
||||
const summaryCards = [
|
||||
{ key: 'source', icon: GitBranch, iconClassName: 'text-emerald-300' },
|
||||
{ key: 'trigger', icon: Activity, iconClassName: 'text-sky-300' },
|
||||
{ key: 'gate', icon: ShieldCheck, iconClassName: 'text-amber-300' },
|
||||
{ key: 'report', icon: Gauge, iconClassName: 'text-lime-300' },
|
||||
]
|
||||
|
||||
export default function CodeReviewPage({ params }: { params: { locale: string } }) {
|
||||
const t = useTranslations('codeReview')
|
||||
const evidenceHref = `/${params.locale}/awooop/runs`
|
||||
|
||||
return (
|
||||
@@ -114,19 +100,17 @@ export default function CodeReviewPage({ params }: { params: { locale: string }
|
||||
<div>
|
||||
<div className="flex items-center gap-2 text-xs font-mono uppercase text-emerald-300">
|
||||
<SearchCheck className="h-4 w-4" />
|
||||
AWOOOI 程式碼審查
|
||||
{t('header.eyebrow')}
|
||||
</div>
|
||||
<h1 className="mt-2 text-2xl font-semibold text-white">AI 程式碼審查控制面</h1>
|
||||
<p className="mt-1 text-sm text-gray-400">
|
||||
Hermes → OpenClaw → Elephant Alpha → NemoTron,只做審查與分級,不自動推版。
|
||||
</p>
|
||||
<h1 className="mt-2 text-2xl font-semibold text-white">{t('header.title')}</h1>
|
||||
<p className="mt-1 text-sm text-gray-400">{t('header.subtitle')}</p>
|
||||
</div>
|
||||
<a
|
||||
href={evidenceHref}
|
||||
className="inline-flex h-10 items-center justify-center gap-2 rounded border border-emerald-500/40 px-3 text-sm font-medium text-emerald-200 transition-colors hover:border-emerald-300 hover:bg-emerald-500/10"
|
||||
className="inline-flex min-h-10 items-center justify-center gap-2 rounded border border-emerald-500/40 px-3 py-2 text-sm font-medium text-emerald-200 transition-colors hover:border-emerald-300 hover:bg-emerald-500/10"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
查看 AwoooP 執行紀錄
|
||||
{t('header.evidenceLink')}
|
||||
</a>
|
||||
</header>
|
||||
|
||||
@@ -141,26 +125,17 @@ export default function CodeReviewPage({ params }: { params: { locale: string }
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2 text-xs font-mono uppercase text-sky-300">
|
||||
<ClipboardList className="h-4 w-4" />
|
||||
Code Review → Codex
|
||||
{t('handoff.eyebrow')}
|
||||
</div>
|
||||
<h2 className="mt-2 text-xl font-semibold text-white">審查後 Coding 工作橋接</h2>
|
||||
<p className="mt-2 max-w-xl text-sm leading-6 text-gray-400">
|
||||
審查結論先變成可追溯的 Codex 工作草稿;真正修改、推版、主機操作與來源切換仍留在人工閘門後面。
|
||||
</p>
|
||||
<h2 className="mt-2 text-xl font-semibold text-white">{t('handoff.title')}</h2>
|
||||
<p className="mt-2 max-w-xl text-sm leading-6 text-gray-400">{t('handoff.subtitle')}</p>
|
||||
</div>
|
||||
<div className="grid gap-2 text-xs font-mono text-gray-400 sm:grid-cols-2">
|
||||
<div className="min-w-0 break-all rounded border border-gray-800 bg-black/25 px-3 py-2">
|
||||
codex_handoff_mode=read_only_candidate
|
||||
</div>
|
||||
<div className="min-w-0 break-all rounded border border-gray-800 bg-black/25 px-3 py-2">
|
||||
codex_auto_coding_enabled=false
|
||||
</div>
|
||||
<div className="min-w-0 break-all rounded border border-gray-800 bg-black/25 px-3 py-2">
|
||||
runtime_execution_authorized=false
|
||||
</div>
|
||||
<div className="min-w-0 break-all rounded border border-gray-800 bg-black/25 px-3 py-2">
|
||||
production_deploy_authorized=false
|
||||
</div>
|
||||
{handoffFlags.map((flag) => (
|
||||
<div key={flag} className="min-w-0 break-all rounded border border-gray-800 bg-black/25 px-3 py-2">
|
||||
{flag}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -169,16 +144,16 @@ export default function CodeReviewPage({ params }: { params: { locale: string }
|
||||
const Icon = lane.icon
|
||||
return (
|
||||
<div
|
||||
key={lane.title}
|
||||
key={lane.key}
|
||||
className={`flex min-h-[12rem] min-w-0 flex-col rounded border p-3 ${lane.className}`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<Icon className={`h-5 w-5 ${lane.iconClassName}`} />
|
||||
<span className="font-mono text-xl font-semibold">{lane.metric}</span>
|
||||
<span className="font-mono text-xl font-semibold">{t(`handoff.lanes.${lane.key}.metric` as never)}</span>
|
||||
</div>
|
||||
<div className="mt-4 text-sm font-semibold text-white">{lane.title}</div>
|
||||
<p className="mt-2 text-sm leading-5 text-gray-300">{lane.detail}</p>
|
||||
<div className="mt-auto pt-4 text-xs leading-5 text-gray-400">{lane.guard}</div>
|
||||
<div className="mt-4 text-sm font-semibold text-white">{t(`handoff.lanes.${lane.key}.title` as never)}</div>
|
||||
<p className="mt-2 text-sm leading-5 text-gray-300">{t(`handoff.lanes.${lane.key}.detail` as never)}</p>
|
||||
<div className="mt-auto pt-4 text-xs leading-5 text-gray-400">{t(`handoff.lanes.${lane.key}.guard` as never)}</div>
|
||||
{index < codexHandoffLanes.length - 1 ? (
|
||||
<ArrowRight className="mt-3 hidden h-4 w-4 self-end text-gray-600 md:block" />
|
||||
) : null}
|
||||
@@ -194,34 +169,51 @@ export default function CodeReviewPage({ params }: { params: { locale: string }
|
||||
>
|
||||
<div className="grid min-w-0 gap-2 border-b border-gray-800 px-4 py-3 md:grid-cols-[minmax(0,1fr)_auto] md:items-center">
|
||||
<div className="min-w-0">
|
||||
<div className="break-words text-sm font-semibold text-white">Codex 工作候選佇列</div>
|
||||
<div className="mt-1 break-words text-xs text-gray-500">
|
||||
草稿、待批、封鎖分流;不是自動執行佇列。
|
||||
<div className="flex items-center gap-2 break-words text-sm font-semibold text-white">
|
||||
<ListChecks className="h-4 w-4 text-emerald-300" />
|
||||
{t('candidateQueue.title')}
|
||||
</div>
|
||||
<div className="mt-1 break-words text-xs text-gray-500">{t('candidateQueue.subtitle')}</div>
|
||||
</div>
|
||||
<div className="min-w-0 max-w-full break-all rounded border border-emerald-500/30 px-3 py-1 text-xs font-mono text-emerald-200 md:justify-self-end">
|
||||
action_buttons_allowed=false
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden grid-cols-[7rem_8rem_1.1fr_1.15fr_8rem_6rem] gap-3 border-b border-gray-800 px-4 py-2 text-xs font-mono uppercase text-gray-500 md:grid">
|
||||
<span>{t('candidateQueue.columns.id')}</span>
|
||||
<span>{t('candidateQueue.columns.track')}</span>
|
||||
<span>{t('candidateQueue.columns.finding')}</span>
|
||||
<span>{t('candidateQueue.columns.handoff')}</span>
|
||||
<span>{t('candidateQueue.columns.gate')}</span>
|
||||
<span>{t('candidateQueue.columns.state')}</span>
|
||||
</div>
|
||||
<div className="divide-y divide-gray-800">
|
||||
{codexCandidateWork.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="grid gap-3 px-4 py-3 text-sm md:grid-cols-[7rem_8rem_1.2fr_1.1fr_8rem_5rem] md:items-center"
|
||||
className="grid gap-3 px-4 py-3 text-sm md:grid-cols-[7rem_8rem_1.1fr_1.15fr_8rem_6rem] md:items-center"
|
||||
>
|
||||
<span className="min-w-0 break-words font-mono text-gray-500">{item.id}</span>
|
||||
<span className="min-w-0 break-words font-semibold text-white">{item.track}</span>
|
||||
<span className="min-w-0 break-words text-gray-400">{item.finding}</span>
|
||||
<span className="min-w-0 break-words text-gray-300">{item.handoff}</span>
|
||||
<span className="min-w-0 break-words text-gray-500">{item.gate}</span>
|
||||
<span className="min-w-0 break-words font-semibold text-white">
|
||||
{t(`candidateQueue.items.${item.key}.track` as never)}
|
||||
</span>
|
||||
<span className="min-w-0 break-words text-gray-400">
|
||||
{t(`candidateQueue.items.${item.key}.finding` as never)}
|
||||
</span>
|
||||
<span className="min-w-0 break-words text-gray-300">
|
||||
{t(`candidateQueue.items.${item.key}.handoff` as never)}
|
||||
</span>
|
||||
<span className="min-w-0 break-words text-gray-500">
|
||||
{t(`candidateQueue.items.${item.key}.gate` as never)}
|
||||
</span>
|
||||
<span
|
||||
className={
|
||||
item.state === '封鎖'
|
||||
item.state === 'blocked'
|
||||
? 'justify-self-start rounded bg-rose-500/10 px-2 py-1 text-xs font-mono text-rose-200'
|
||||
: 'justify-self-start rounded bg-emerald-500/10 px-2 py-1 text-xs font-mono text-emerald-200'
|
||||
}
|
||||
>
|
||||
{item.state}
|
||||
{t(`states.${item.state}` as never)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
@@ -229,37 +221,34 @@ export default function CodeReviewPage({ params }: { params: { locale: string }
|
||||
</section>
|
||||
|
||||
<section className="grid gap-3 md:grid-cols-4">
|
||||
<div className="rounded border border-gray-800 bg-gray-950 p-4">
|
||||
<div className="flex items-center gap-2 text-xs text-gray-400">
|
||||
<GitBranch className="h-4 w-4 text-emerald-300" />
|
||||
來源
|
||||
</div>
|
||||
<div className="mt-3 text-lg font-semibold text-white">gitea main</div>
|
||||
<div className="mt-1 text-xs text-gray-500">以只讀證據回看,不在前端暴露內網入口</div>
|
||||
{summaryCards.map((card) => {
|
||||
const Icon = card.icon
|
||||
return (
|
||||
<div key={card.key} className="rounded border border-gray-800 bg-gray-950 p-4">
|
||||
<div className="flex items-center gap-2 text-xs text-gray-400">
|
||||
<Icon className={`h-4 w-4 ${card.iconClassName}`} />
|
||||
{t(`summaryCards.${card.key}.label` as never)}
|
||||
</div>
|
||||
<div className="mt-3 text-lg font-semibold text-white">{t(`summaryCards.${card.key}.value` as never)}</div>
|
||||
<div className="mt-1 text-xs text-gray-500">{t(`summaryCards.${card.key}.detail` as never)}</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</section>
|
||||
|
||||
<section className="rounded border border-gray-800 bg-gray-950 p-4">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-white">
|
||||
<Workflow className="h-4 w-4 text-sky-300" />
|
||||
{t('approvalFlow.title')}
|
||||
</div>
|
||||
<div className="rounded border border-gray-800 bg-gray-950 p-4">
|
||||
<div className="flex items-center gap-2 text-xs text-gray-400">
|
||||
<Activity className="h-4 w-4 text-sky-300" />
|
||||
觸發
|
||||
</div>
|
||||
<div className="mt-3 text-lg font-semibold text-white">推送 / 手動</div>
|
||||
<div className="mt-1 text-xs text-gray-500">程式碼審查工作流</div>
|
||||
</div>
|
||||
<div className="rounded border border-gray-800 bg-gray-950 p-4">
|
||||
<div className="flex items-center gap-2 text-xs text-gray-400">
|
||||
<ShieldCheck className="h-4 w-4 text-amber-300" />
|
||||
閘門
|
||||
</div>
|
||||
<div className="mt-3 text-lg font-semibold text-white">非阻擋 v1</div>
|
||||
<div className="mt-1 text-xs text-gray-500">危急 / 高風險優先顯示</div>
|
||||
</div>
|
||||
<div className="rounded border border-gray-800 bg-gray-950 p-4">
|
||||
<div className="flex items-center gap-2 text-xs text-gray-400">
|
||||
<Gauge className="h-4 w-4 text-lime-300" />
|
||||
報告
|
||||
</div>
|
||||
<div className="mt-3 text-lg font-semibold text-white">Telegram + Actions</div>
|
||||
<div className="mt-1 text-xs text-gray-500">啟動卡片與完成卡片</div>
|
||||
<div className="mt-3 grid gap-2 md:grid-cols-6">
|
||||
{approvalFlowSteps.map((step, index) => (
|
||||
<div key={step} className="min-w-0 rounded border border-gray-800 bg-black/20 px-3 py-3">
|
||||
<div className="font-mono text-xs text-gray-500">{String(index + 1).padStart(2, '0')}</div>
|
||||
<div className="mt-2 text-sm font-semibold text-white">{t(`approvalFlow.steps.${step}.title` as never)}</div>
|
||||
<div className="mt-1 text-xs leading-5 text-gray-400">{t(`approvalFlow.steps.${step}.detail` as never)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -267,17 +256,17 @@ export default function CodeReviewPage({ params }: { params: { locale: string }
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-white">
|
||||
<CheckCircle2 className="h-4 w-4 text-emerald-300" />
|
||||
審查流程
|
||||
{t('reviewFlow.title')}
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
{stages.map((stage, index) => (
|
||||
<div
|
||||
key={stage.label}
|
||||
key={stage.key}
|
||||
className="grid grid-cols-[2rem_8rem_1fr] items-center rounded border border-gray-800 bg-gray-950 px-3 py-2 text-sm"
|
||||
>
|
||||
<span className="font-mono text-gray-500">{String(index + 1).padStart(2, '0')}</span>
|
||||
<span className="font-mono text-emerald-200">{stage.label}</span>
|
||||
<span className="truncate text-gray-300">{stage.state}</span>
|
||||
<span className="font-mono text-emerald-200">{t(`reviewFlow.stages.${stage.key}.label` as never)}</span>
|
||||
<span className="truncate text-gray-300">{t(`reviewFlow.stages.${stage.key}.state` as never)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -286,7 +275,7 @@ export default function CodeReviewPage({ params }: { params: { locale: string }
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold text-white">
|
||||
<Bot className="h-4 w-4 text-sky-300" />
|
||||
Agent 分工
|
||||
{t('agents.title')}
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
{agents.map((agent) => (
|
||||
@@ -295,9 +284,9 @@ export default function CodeReviewPage({ params }: { params: { locale: string }
|
||||
className="grid grid-cols-[8rem_1fr_4.5rem] items-center rounded border border-gray-800 bg-gray-950 px-3 py-2 text-sm"
|
||||
>
|
||||
<span className="font-semibold text-white">{agent.name}</span>
|
||||
<span className="truncate text-gray-400">{agent.role}</span>
|
||||
<span className="truncate text-gray-400">{t(`agents.roles.${agent.roleKey}` as never)}</span>
|
||||
<span className="justify-self-end rounded bg-emerald-500/10 px-2 py-1 text-xs font-mono text-emerald-200">
|
||||
{agentStateLabel[agent.state] ?? agent.state}
|
||||
{t(`agentStates.${agent.state}` as never)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user