diff --git a/apps/web/app/routes/console/inbox.tsx b/apps/web/app/routes/console/inbox.tsx
index c330baef..a38f86d7 100644
--- a/apps/web/app/routes/console/inbox.tsx
+++ b/apps/web/app/routes/console/inbox.tsx
@@ -25,6 +25,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "~
import { Textarea } from "~/components/ui/textarea.tsx";
import { errorMessage } from "~/lib/api.ts";
import { actorText } from "~/lib/labels.ts";
+import { humanizeReason } from "~/lib/reasons.ts";
import { useSse, useSseStatus } from "~/lib/sse.ts";
import { formatDateTime } from "~/lib/utils.ts";
@@ -377,7 +378,9 @@ function DecisionCard({ d }: { d: Decision }) {
{d.title}
- {d.summary ? {d.summary}
: null}
+ {d.summary ? (
+ {humanizeReason(d.summary)}
+ ) : null}
diff --git a/apps/web/app/routes/console/organize.$id.tsx b/apps/web/app/routes/console/organize.$id.tsx
index 07b9a6e1..59aafd47 100644
--- a/apps/web/app/routes/console/organize.$id.tsx
+++ b/apps/web/app/routes/console/organize.$id.tsx
@@ -179,7 +179,7 @@ export default function OrganizeRecordPage() {
{RECORD_STATUS_LABEL[r.status]}
{[
- `${TRIGGER_LABEL[r.trigger]}${ACTION_LABEL[r.action]}`,
+ `${ACTION_LABEL[r.action]} · ${TRIGGER_LABEL[r.trigger]}`,
r.counts.total ? `${r.counts.succeeded}/${r.counts.total} 个文件成功` : null,
r.counts.failed ? `${r.counts.failed} 个失败` : null,
r.counts.skipped ? `${r.counts.skipped} 个跳过` : null,
diff --git a/apps/web/scripts/.deai2-shots.mjs b/apps/web/scripts/.deai2-shots.mjs
new file mode 100644
index 00000000..e2da93af
--- /dev/null
+++ b/apps/web/scripts/.deai2-shots.mjs
@@ -0,0 +1,37 @@
+import { existsSync } from "node:fs";
+import { chromium } from "playwright-core";
+const OUT = "/opt/uhd_media_tool-deai2/apps/web/test/screenshots/deai2";
+const PAGES = [
+ ["01-home", "/console"],
+ ["02-inbox", "/console/inbox"],
+ ["03-schedules", "/console/schedules"],
+ ["04-agent-run", "/console/agent/a01dccf0-383f-46a9-acec-c2ca3cf571f8"],
+ ["05-organize-detail", "/console/organize/33000000-0000-4000-8000-000000000903"],
+ ["06-download-detail", "/console/downloads/33000000-0000-4000-8000-000000000802"],
+ ["07-tasks", "/console/tasks"],
+ ["08-settings-interface", "/console/settings#interface"],
+];
+const exe = ["/usr/bin/chromium", "/usr/bin/google-chrome"].find((p) => existsSync(p));
+const browser = await chromium.launch({ executablePath: exe, headless: true, args: ["--no-sandbox"] });
+for (const [tag, port] of (process.env.ONLY === "after" ? [["after", 5192]] : [["before", 5191], ["after", 5192]])) {
+ const base = `http://127.0.0.1:${port}`;
+ for (const [mode, vp, theme] of [["desk-light", { width: 1440, height: 900 }, "light"], ["desk-dark", { width: 1440, height: 900 }, "dark"], ["mobile", { width: 390, height: 844 }, "light"]]) {
+ const ctx = await browser.newContext({ viewport: vp, colorScheme: theme, deviceScaleFactor: mode === "mobile" ? 2 : 1 });
+ await ctx.addInitScript((t) => localStorage.setItem("umt-theme", t), theme);
+ const page = await ctx.newPage();
+ await page.goto(`${base}/login`, { waitUntil: "networkidle" });
+ await page.fill("#username", "w31-admin");
+ await page.fill("#password", "deai2-shots-pass");
+ await page.click('button[type="submit"]');
+ await page.waitForURL((u) => u.pathname.startsWith("/console"), { timeout: 20_000 });
+ for (const [name, path] of PAGES) {
+ if (tag === "before" && name === "08-settings-interface") continue;
+ await page.goto(base + path, { waitUntil: "networkidle" }).catch(() => {});
+ await page.waitForTimeout(1500);
+ await page.screenshot({ path: `${OUT}/${name}-${mode}-${tag}.png`, fullPage: true });
+ }
+ await ctx.close();
+ }
+}
+await browser.close();
+console.log("done");
diff --git a/docs/design/de-ai-review.md b/docs/design/de-ai-review.md
index 68d75b89..df733f38 100644
--- a/docs/design/de-ai-review.md
+++ b/docs/design/de-ai-review.md
@@ -320,3 +320,53 @@
5. **首页是否删掉「运行状态」「计划」两块**,只在异常时出现。
6. **「收录」「建档」两个词是否全站退场**,换成「加入媒体库 / 在媒体库 / 已下载」。
7. **Agent 会话里「模型说的(未经核查)」这层防护是否保留**。建议保留一个很轻的标记(回复旁一行「以下是模型的说法」),删掉另外两处。
+
+## 6. DEAI-2 处理情况(2026-09-29)
+
+用户拍板(见 `docs/plan/00-first-version-plan.md`):「版本目标」→「收录版本」;「Agent 上游」→「模型服务」;界面上的 Agent → 「智能体」;技术信息默认折叠(设置 → 界面可默认展开);首页只留三块;计划页维护类收成「数据清理」、智能体巡检置顶;「未经核查」只在最终汇报留一处。术语落地见 [glossary.md](glossary.md)。
+
+### P0
+
+| # | 状态 | 说明 |
+| --- | --- | --- |
+| 1 | 完成 | 原因码表 `packages/contracts/src/reasons.ts`,服务端整理记录 message、冲突事项标题/摘要与前端(整理记录、下载时间线、待处理摘要、任务详情)共用;原码进「技术信息」 |
+| 2 | 完成 | 「指定作品」主按钮:搜媒体库 + TMDB,走已有 decision resolve(local_entity / manual_tmdb);整理类事项打开重新整理,整理成功后自动关闭事项 |
+| 3 | 完成 | 首页状态行按数据说话,0 订阅写「还没有订阅。去搜索添加」 |
+| 4 | 部分 | 下载详情在「下载完成但最近一次整理失败」时单独提示入库失败并链到待处理;下载列表行状态未改——需要服务端在下载任务上带最近整理结果,QB-1 正在改整理服务,留给合并后另做 |
+| 5 | 完成 | 只读对账移出首页,放进设置 → 自动化 →「高级」 |
+| 6 | 完成 | 统一为「没识别出作品」(事项);未确认身份的资源叫「未识别」 |
+
+### P1
+
+| # | 状态 | 说明 |
+| --- | --- | --- |
+| 1 | 完成 | 见提交说明;对话组件结构不动(另有代理出易用性方案) |
+| 2 | 完成 | 删口号;悬浮按钮只在无侧栏的窄屏出现,抽屉打开时不再显示胶囊 |
+| 3 | 完成 | 默认隐藏系统任务(可一键显示),标题不用 ID |
+| 4 | 完成 | 维护类合成「数据清理」组;自动任务按分类折叠并记忆;说明去表名 |
+| 5 | 完成 | |
+| 6 | 完成 | 约 14 处页描述删除或改一句 |
+| 7 | 完成 | 首页、站点卡、下载、待处理、文件管理、整理记录 |
+| 8 | 完成 | RSS、媒体库、建档 / 收录、资产、账本、纳管、哨兵、快照;「外部身份」一词在作品详情改「匹配的 TMDB 条目」,其余处保留 |
+| 9 | 完成 | UUID、字段来源、别名全集、路径核验 |
+
+### P2
+
+| # | 状态 | 说明 |
+| --- | --- | --- |
+| 1 | 完成 | `formatUsd`、`formatDuration` |
+| 2 | 完成 | |
+| 3 | 部分 | 自动化分区的只读对账 / 存储预算 / 流水保留 / 缓存有效期收进「高级」;会话目录与 fake 脚本只在开发构建显示;没有在 contracts 加 `advanced` 标记(纯前端即可) |
+| 4 | 完成 | 进程、趋势、最大表折叠;图例同名序列加序号 |
+| 5 | 完成 | |
+| 6 | 完成 | |
+| 7 | 完成 | |
+| 8 | 不做 | 用户栈数据,不是代码 |
+
+### 页面级条目中不做的
+
+- 05b 站点概况的配置回显、05c 资源行「已识别,无收录版本」、「实时补查」按钮位置:DETAIL-1 / SEARCH-3 正在改站点详情与资源列表,只改了名词(抓取、去站点搜),结构留给它们。
+- 10 整理记录里连续的 0 文件失败行合并:需要服务端按下载任务聚合,放到 QB-1 合并之后。
+- 12 媒体库页签只留「作品 / 未识别」、「未命中订阅」不进未识别:DH-1 正在改身份服务,避免冲突。
+- 13-settings-agent MCP 区块的能力计数与接入说明移到文档:保留,只改了一句说明。
+- 14 「已下线实例 N 个」文案:已随进程区折叠,文案不改。
diff --git a/packages/contracts/src/reasons.ts b/packages/contracts/src/reasons.ts
index 79fe46c7..a6cd04f5 100644
--- a/packages/contracts/src/reasons.ts
+++ b/packages/contracts/src/reasons.ts
@@ -38,6 +38,8 @@ const REASONS: Record = {
already_in_library: { text: "已在媒体库里" },
main_not_transferred: { text: "主文件没有整理,附属文件一起跳过" },
timeout: { text: "操作超时", action: "稍后重试" },
+ downloader_unavailable: { text: "下载器连不上", action: "在设置 → 下载器里测试连接" },
+ downloader_error: { text: "下载器报错", action: "查看下载器日志" },
};
/**