fix: replace raw codes and internal IDs in the console with Chinese labels

Found in v0.2 visual QA (docs/plan/09-visual-qa.md):
- actor strings (admin_session:<uuid>, user:<uuid>, system) -> 管理员/系统/API Key
  in policy revisions, subscriptions, inbox, trash, business tasks, organize detail
- cache volume without key shows the downloader name instead of instance:<uuid>
- download timeline/plan: task and organize statuses, plan purpose, priority and
  status reason localized; audit gets labels for capability/files/site/feed actions
- organize record reason uses conflict detail (没有可入库的主视频) instead of
  blocked:no_main_video; slot/candidate reasons drop inflight:<uuid>
- identity decision summary no longer shows no_viable_candidate
- upstream test: 'Connection error.' classified as network, with a Chinese hint
- new policy draft prefills key default (placeholder looked like a value)
- dialog/sheet close sr-only text in Chinese

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 15:22:15 +08:00
1 parent d7eb6a45d0
commit 46d37d87fe
23 files changed
+176 -43

No files matched your search

+16 -8
View File
@@ -8,18 +8,12 @@ import { FilterBar, FilterSelect } from "~/components/console/filter-bar.tsx";
import { JsonView } from "~/components/console/json-view.tsx";
import { KvList } from "~/components/console/kv-list.tsx";
import { PageHeader } from "~/components/console/page-header.tsx";
import { formatRelative } from "~/lib/labels.ts";
import { ACTOR_TYPES, actorTypeLabel, formatRelative } from "~/lib/labels.ts";
import { formatDateTime } from "~/lib/utils.ts";
export const meta = () => [{ title: "审计日志 · UHD 媒体自动化控制台" }];
const ACTOR_TYPES = [
{ value: "user", label: "管理员" },
{ value: "api_key", label: "API Key" },
{ value: "agent", label: "Agent" },
{ value: "system", label: "系统" },
];
const actorLabel = (t: string) => ACTOR_TYPES.find((a) => a.value === t)?.label ?? t;
const actorLabel = actorTypeLabel;
const ACTION_LABEL: Record<string, string> = {
"admin.setup": "初始化管理员",
@@ -43,6 +37,20 @@ const ACTION_LABEL: Record<string, string> = {
"schedule.run_now": "立即执行定时任务",
"schedule.skip": "跳过定时任务",
"acquisition_plan.manual_submit": "手动提交下载计划",
"engine.run_retry": "重跑引擎运行",
"capability.invoke": "Agent 调用能力",
"site.created": "登记站点",
"site.updated": "修改站点",
"feed_source.created": "新建 Feed",
"identity.release_assigned": "指定资源身份",
"schedule.update": "修改定时任务",
"files.mkdir": "新建目录",
"files.rename": "重命名",
"files.move": "移动文件",
"files.copy": "复制文件",
"files.organize": "整理文件",
"files.delete": "删除文件",
"files.trash_restore": "从回收站还原",
};
const VERB: Record<string, string> = { create: "新建", update: "修改", delete: "删除", test: "测试", verify: "校验" };
const NOUN: Record<string, string> = {
@@ -12,8 +12,10 @@ import { consolePath } from "~/components/console/nav.ts";
import { PageHeader } from "~/components/console/page-header.tsx";
import { StatusBadge } from "~/components/console/status-badge.tsx";
import { useQueryState } from "~/components/console/use-query-state.ts";
import { PRIORITY } from "~/components/library/labels.ts";
import { Button } from "~/components/ui/button.tsx";
import { relTime } from "~/components/workbench/release-fields.ts";
import { planReasonText } from "~/lib/labels.ts";
import { formatBytes, formatDateTime } from "~/lib/utils.ts";
export const meta = () => [{ title: "下载计划 · UHD 媒体自动化控制台" }];
@@ -41,7 +43,7 @@ export default function DownloadPlansPage() {
cell: (p) => (
<div className="min-w-0">
<p className="text-foreground">
{PURPOSE[p.purpose]} · 优先级 {p.priority}
{PURPOSE[p.purpose]} · 优先级 {PRIORITY[p.priority as keyof typeof PRIORITY] ?? p.priority}
</p>
<p className="truncate font-mono text-meta text-muted-foreground">{p.id.slice(0, 8)}</p>
</div>
@@ -63,7 +65,7 @@ export default function DownloadPlansPage() {
key: "reason",
header: "说明",
hideBelow: "lg",
cell: (p) => <span className="line-clamp-1 text-muted-foreground">{p.statusReason ?? "—"}</span>,
cell: (p) => <span className="line-clamp-1 text-muted-foreground">{planReasonText(p.statusReason) ?? "—"}</span>,
},
{
key: "at",
@@ -107,7 +109,7 @@ export default function DownloadPlansPage() {
open={!!openPlan}
onOpenChange={(o) => !o && setOpenPlan(null)}
title={openPlan ? `${PURPOSE[openPlan.purpose]}计划` : ""}
description={openPlan?.statusReason ?? undefined}
description={planReasonText(openPlan?.statusReason) ?? undefined}
footer={
linkedDownload ? (
<Button asChild size="sm">
+2 -3
View File
@@ -31,6 +31,7 @@ import {
} from "~/components/ui/dialog.tsx";
import { Input } from "~/components/ui/input.tsx";
import { relTime } from "~/components/workbench/release-fields.ts";
import { actorText } from "~/lib/labels.ts";
import { formatBytes, formatDateTime } from "~/lib/utils.ts";
const DAY = 86_400_000;
@@ -160,9 +161,7 @@ export default function FilesTrashPage() {
title={`${formatDateTime(r.deletedAt)}${r.actor ? ` · ${r.actor}` : ""}`}
>
<span className="block whitespace-nowrap">{relTime(r.deletedAt)}</span>
{r.actor ? (
<span className="block truncate text-meta">{r.actor.replace(/^admin_session:/, "管理员 ")}</span>
) : null}
{r.actor ? <span className="block truncate text-meta">{actorText(r.actor)}</span> : null}
</span>
),
},
+4 -2
View File
@@ -26,6 +26,7 @@ import { consolePath } from "~/components/console/nav.ts";
import { PageHeader } from "~/components/console/page-header.tsx";
import { StatTile } from "~/components/console/stat-tile.tsx";
import { StatusBadge } from "~/components/console/status-badge.tsx";
import { useVolumeLabel } from "~/components/downloads/cache-volumes.tsx";
import { Button } from "~/components/ui/button.tsx";
import { Switch } from "~/components/ui/switch.tsx";
import { errorMessage } from "~/lib/api.ts";
@@ -302,6 +303,7 @@ function NeedsAttention({ n }: { n: Dashboard["needsAttention"] }) {
}
function CacheSection({ c }: { c: Dashboard["cache"] }) {
const volumeLabel = useVolumeLabel();
return (
<Section title="缓存水位" icon={HardDrive} to={consolePath("downloads")}>
{c.volumes.length === 0 ? (
@@ -321,7 +323,7 @@ function CacheSection({ c }: { c: Dashboard["cache"] }) {
return (
<li key={v.volumeKey} className="min-w-0">
<div className="flex items-center justify-between gap-2 text-sm">
<span className="truncate font-medium">{v.volumeKey}</span>
<span className="truncate font-medium">{volumeLabel(v)}</span>
<StatusBadge tone={pct === null ? "neutral" : over ? "destructive" : near ? "warning" : "success"}>
{pct === null ? "用量未知" : `${pct.toFixed(0)}%${over ? " 已达阈值" : ""}`}
</StatusBadge>
@@ -441,7 +443,7 @@ function RecentOutput({ items }: { items: Dashboard["recentOutput"] }) {
<li key={r.recordId} className="min-w-0 py-2">
<div className="flex items-center justify-between gap-3 text-sm">
<Link to={consolePath("organize", r.recordId)} className="min-w-0 truncate hover:underline">
{r.title ?? "未命名"}
{r.title ?? "未关联作品"}
</Link>
<StatusBadge tone={s.tone}>{s.label}</StatusBadge>
</div>
+2 -1
View File
@@ -20,6 +20,7 @@ import { Button } from "~/components/ui/button.tsx";
import { Input } from "~/components/ui/input.tsx";
import { Textarea } from "~/components/ui/textarea.tsx";
import { errorMessage } from "~/lib/api.ts";
import { actorText } from "~/lib/labels.ts";
import { useSse, useSseStatus } from "~/lib/sse.ts";
import { formatDateTime } from "~/lib/utils.ts";
@@ -249,7 +250,7 @@ function DecisionCard({ d }: { d: Decision }) {
</div>
) : (
<p className="mt-3 text-meta text-muted-foreground">
{d.resolvedBy ? `${d.resolvedBy} ` : ""}于 {formatDateTime(d.resolvedAt)}
{d.resolvedBy ? `${actorText(d.resolvedBy)} ` : ""}于 {formatDateTime(d.resolvedAt)}
{resolution?.optionKey
? ` 选择「${d.options.find((o) => o.key === resolution.optionKey)?.label ?? resolution.optionKey}」`
: " 关闭"}
+5 -1
View File
@@ -9,6 +9,7 @@ import { KvList } from "~/components/console/kv-list.tsx";
import { PageHeader } from "~/components/console/page-header.tsx";
import { StatusBadge, type StatusTone } from "~/components/console/status-badge.tsx";
import { Button } from "~/components/ui/button.tsx";
import { actorTypeLabel } from "~/lib/labels.ts";
import { cn, formatBytes, formatDateTime } from "~/lib/utils.ts";
const ACTION_LABEL = {
@@ -194,7 +195,10 @@ export default function OrganizeRecordPage() {
{ label: "开始", value: formatDateTime(r.startedAt) },
{ label: "完成", value: formatDateTime(r.finishedAt) },
{ label: "用时", value: duration(r.startedAt, r.finishedAt) },
{ label: "触发者", value: `${r.actor.type}${r.actor.id ? ` · ${r.actor.id}` : ""}` },
{
label: "触发者",
value: `${actorTypeLabel(r.actor.type)}${r.actor.id && r.actor.type !== "system" ? ` · ${r.actor.id}` : ""}`,
},
{
label: "外部身份",
value: r.media?.externalIds.map((e) => `${e.provider}:${e.entityType}:${e.externalId}`).join(", "),
@@ -55,6 +55,7 @@ import {
import { Input } from "~/components/ui/input.tsx";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~/components/ui/select.tsx";
import { errorMessage } from "~/lib/api.ts";
import { actorText } from "~/lib/labels.ts";
import { cn, formatDateTime } from "~/lib/utils.ts";
import { legacyRedirectLoader } from "./redirects.ts";
@@ -317,7 +318,7 @@ function SubscriptionSheet({ sub, onClose }: { sub: Subscription | null; onClose
? `上次 ${formatDateTime(sub.lastSearchedAt)}${sub.nextSearchAt ? ` · 下次 ${formatDateTime(sub.nextSearchAt)}` : " · 周期搜索已关"}`
: "尚未搜索",
},
{ label: "创建", value: `${formatDateTime(sub.createdAt)} · ${sub.createdBy ?? "—"}` },
{ label: "创建", value: `${formatDateTime(sub.createdAt)} · ${actorText(sub.createdBy)}` },
{ label: "策略版本", value: sub.policyRevisionId ?? "按当前已发布策略", mono: !!sub.policyRevisionId },
]}
/>