From 6549ab92c33ca45e0419eee597dc069546fd2a21 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Oct 2026 01:32:00 +0800 Subject: [PATCH 01/25] =?UTF-8?q?fix(media):=20=E8=AF=A6=E6=83=85=E9=A1=B5?= =?UTF-8?q?=E6=A0=87=E8=AF=AD=E5=8F=AA=E6=98=BE=E7=A4=BA=E7=AE=80=E4=BD=93?= =?UTF-8?q?=E4=B8=AD=E6=96=87,=E7=AE=80=E4=B8=AD=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E6=97=B6=E4=B8=8D=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - adapters 新增 simplifiedChineseTagline:取值语言为繁中 / 非中文、或文字是繁体特有字时不显示;原语言为中文(zh / cn)的作品按简中处理 - TMDB 富详情带回 taglineLanguage;发现页详情的 tagline 按上述口径给出 - 媒体库作品详情新增 displayTagline(存储的标语语言为 null,只能按文字判断);metadata.tagline 存储不变 - 补 adapters 单测与作品详情接口测试 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- .../acquisition-organize/routes/library.ts | 10 ++++ apps/server/src/modules/search/routes.ts | 3 +- .../library-versions.test.ts | 28 +++++++++++ apps/web/app/routes/console/library.$id.tsx | 3 +- packages/adapters/src/tmdb/details.ts | 3 ++ .../adapters/src/tmdb/language-policy.test.ts | 50 +++++++++++++++++++ packages/adapters/src/tmdb/language-policy.ts | 26 ++++++++++ packages/contracts/src/organization.ts | 2 + packages/contracts/src/search.ts | 1 + 9 files changed, 124 insertions(+), 2 deletions(-) diff --git a/apps/server/src/modules/acquisition-organize/routes/library.ts b/apps/server/src/modules/acquisition-organize/routes/library.ts index 3431682e..3720b5ad 100644 --- a/apps/server/src/modules/acquisition-organize/routes/library.ts +++ b/apps/server/src/modules/acquisition-organize/routes/library.ts @@ -1,4 +1,5 @@ import { join } from "node:path"; +import { simplifiedChineseTagline } from "@umt/adapters"; import { type AcquisitionPlan, type AcquisitionSlot, @@ -405,6 +406,7 @@ export function registerLibraryRoutes(app: ModuleApp, d: AoDeps) { finishedAt: r.finished_at ? new Date(r.finished_at).toISOString() : null, })), scrape: await latestEntityScrape(d, id), + displayTagline: heroTagline(e!.metadata), }; return detail; }, @@ -642,3 +644,11 @@ export function registerLibraryRoutes(app: ModuleApp, d: AoDeps) { { tag: TAG }, ); } + +/** 详情页头部的标语:只显示简体中文(见 simplifiedChineseTagline);存储的标语与语言不变 */ +function heroTagline(metadata: unknown): string | null { + const m = (metadata ?? {}) as Record; + const text = typeof m.tagline?.value === "string" ? m.tagline.value : null; + const orig = typeof m.originalLanguage?.value === "string" ? m.originalLanguage.value : null; + return simplifiedChineseTagline(text, m.tagline?.language ?? null, orig); +} diff --git a/apps/server/src/modules/search/routes.ts b/apps/server/src/modules/search/routes.ts index 5c6325ca..1118d446 100644 --- a/apps/server/src/modules/search/routes.ts +++ b/apps/server/src/modules/search/routes.ts @@ -3,6 +3,7 @@ import { fetchRichDetail, fetchRichSeason, type MetadataCandidate, + simplifiedChineseTagline, TmdbError, type TmdbRichDetail, } from "@umt/adapters"; @@ -453,7 +454,7 @@ export function registerSearchRoutes(app: ModuleApp, ctx: ServerContext, d: Sear out.title = rich.title || out.title; out.overview = rich.overview ?? out.overview; out.titleZh = rich.titleZh; - out.tagline = rich.tagline; + out.tagline = simplifiedChineseTagline(rich.tagline, rich.taglineLanguage, rich.originalLanguage); out.genres = rich.genres; out.voteAverage = rich.voteAverage; out.voteCount = rich.voteCount; diff --git a/apps/server/test/acquisition-organize/library-versions.test.ts b/apps/server/test/acquisition-organize/library-versions.test.ts index 2dee71d2..f4d157bf 100644 --- a/apps/server/test/acquisition-organize/library-versions.test.ts +++ b/apps/server/test/acquisition-organize/library-versions.test.ts @@ -336,4 +336,32 @@ describe.skipIf(!hasPg)("LIBV-1 入库版本", () => { expect(response.body.data.downloads).toHaveLength(105); expect(response.body.data.droppedPlans).toHaveLength(51); }); + + it("作品详情头部标语只给简体中文(displayTagline);存储的 metadata.tagline 不变", async () => { + const field = (value: unknown, language: string | null = null) => ({ + value, + source: "tmdb", + language, + at: "2026-10-01T00:00:00.000Z", + locked: false, + }); + const cases: [string, string, string | null][] = [ + ["我们注定要离开地球", "en", "我们注定要离开地球"], + ["我們註定要離開地球", "en", null], + ["Mankind was born on Earth.", "en", null], + ["我們註定要離開地球", "zh", "我們註定要離開地球"], + ]; + for (const [tagline, orig, want] of cases) { + const entityId = await seedEntity(h.db, { kind: "movie", title: `标语 ${tagline}`, year: 2026 }); + await h.db + .updateTable("media_entity") + .set({ metadata: JSON.stringify({ tagline: field(tagline), originalLanguage: field(orig) }) }) + .where("id", "=", entityId) + .execute(); + const r = await h.api("GET", `/library/${entityId}`); + expect(r.status).toBe(200); + expect(r.body.data.displayTagline).toBe(want); + expect(r.body.data.entity.metadata.tagline.value).toBe(tagline); + } + }); }); diff --git a/apps/web/app/routes/console/library.$id.tsx b/apps/web/app/routes/console/library.$id.tsx index e6ebe975..215f60af 100644 --- a/apps/web/app/routes/console/library.$id.tsx +++ b/apps/web/app/routes/console/library.$id.tsx @@ -1091,7 +1091,8 @@ function factsOf(d: LibraryDetail, entity: MediaEntityDetail | undefined): Media platform: strOf(m("platform")), originCountry: Array.isArray(country) ? country.filter((x): x is string => typeof x === "string") : [], overview: strOf(m("overview")), - tagline: strOf(m("tagline")), + // 头部标语只显示简体中文,由服务端按取值语言 / 文字判断(存储的 metadata.tagline 不变) + tagline: d.displayTagline ?? null, posterPath: posterPathOf(e.metadata), backdropPath: strOf(m("backdropPath")), externalLinks: externalLinks({ diff --git a/packages/adapters/src/tmdb/details.ts b/packages/adapters/src/tmdb/details.ts index 13f8303e..1aa46cc0 100644 --- a/packages/adapters/src/tmdb/details.ts +++ b/packages/adapters/src/tmdb/details.ts @@ -66,6 +66,8 @@ export interface TmdbRichDetail { overview: string | null; overviewLanguage: string | null; tagline: string | null; + /** 标语实际取到的语言(链内语言标签;落空为 null) */ + taglineLanguage: string | null; year: number | null; releaseDate: string | null; lastAirDate: string | null; @@ -275,6 +277,7 @@ export async function fetchRichDetail( overview: overview?.value ?? null, overviewLanguage: overview?.language ?? null, tagline: tagline?.value ?? null, + taglineLanguage: tagline?.language ?? null, year: yearOf(date), releaseDate: date, lastAirDate: s(j.last_air_date), diff --git a/packages/adapters/src/tmdb/language-policy.test.ts b/packages/adapters/src/tmdb/language-policy.test.ts index f877119d..7fa51cf9 100644 --- a/packages/adapters/src/tmdb/language-policy.test.ts +++ b/packages/adapters/src/tmdb/language-policy.test.ts @@ -7,6 +7,7 @@ import { chineseScriptOf, createTmdbClient, fetchRichDetail, + simplifiedChineseTagline, textMatchesLanguage, untaggedTextAllowed, } from "./index.ts"; @@ -144,3 +145,52 @@ describe("多语言保留规则:TMDB 详情", () => { expect(rich.titleLanguage).toBe("zh-CN"); }); }); + +describe("详情页标语只显示简体中文", () => { + it("取值语言是简中(zh-CN / zh-SG / zh)显示;繁中(zh-TW / zh-HK)和非中文不显示,不退到繁中", () => { + expect(simplifiedChineseTagline("人类的勇气就是人类的赞歌", "zh-CN", "en")).toBe("人类的勇气就是人类的赞歌"); + expect(simplifiedChineseTagline("一场冒险", "zh-SG", "en")).toBe("一场冒险"); + expect(simplifiedChineseTagline("一场冒险", "zh", "en")).toBe("一场冒险"); + expect(simplifiedChineseTagline("一場冒險", "zh-TW", "en")).toBeNull(); + expect(simplifiedChineseTagline("一场冒险", "zh-HK", "en")).toBeNull(); + expect(simplifiedChineseTagline("Mankind was born on Earth.", "en-US", "en")).toBeNull(); + }); + + it("语言未知(媒体库存的标语)按文字判断:繁体特有字、假名、谚文、没有汉字的都不显示", () => { + expect(simplifiedChineseTagline("我们注定要离开地球", null, "en")).toBe("我们注定要离开地球"); + expect(simplifiedChineseTagline("我們註定要離開地球", null, "en")).toBeNull(); + expect(simplifiedChineseTagline("沙丘", null, "en")).toBe("沙丘"); + expect(simplifiedChineseTagline("君の名は。", null, "ja")).toBeNull(); + expect(simplifiedChineseTagline("Mankind was born on Earth.", null, "en")).toBeNull(); + expect(simplifiedChineseTagline(" ", null, "en")).toBeNull(); + expect(simplifiedChineseTagline(null, "zh-CN", "en")).toBeNull(); + }); + + it("原语言为中文(zh / 粤语 cn)的作品按简中处理", () => { + expect(simplifiedChineseTagline("我們註定要離開地球", null, "zh")).toBe("我們註定要離開地球"); + expect(simplifiedChineseTagline("一場冒險", "zh-HK", "cn")).toBe("一場冒險"); + }); + + it("富详情带回标语的取值语言:简中没有、回退到繁中时取值不变(存储照旧),展示层丢掉", async () => { + const client = { + getJson: async () => + ({ + id: 1, + name: "冒险", + original_name: "Adventure", + original_language: "en", + origin_country: ["US"], + tagline: "", + translations: { + translations: [ + { iso_639_1: "zh", iso_3166_1: "CN", data: { name: "冒险", overview: "简介", tagline: "" } }, + { iso_639_1: "zh", iso_3166_1: "TW", data: { name: "冒險", overview: "簡介", tagline: "一場冒險" } }, + ], + }, + }) as T, + }; + const rich = await fetchRichDetail(client, "tv", 1, ["zh-CN", "zh-TW", "en-US"]); + expect(rich).toMatchObject({ tagline: "一場冒險", taglineLanguage: "zh-TW" }); + expect(simplifiedChineseTagline(rich.tagline, rich.taglineLanguage, rich.originalLanguage)).toBeNull(); + }); +}); diff --git a/packages/adapters/src/tmdb/language-policy.ts b/packages/adapters/src/tmdb/language-policy.ts index 01e3a99e..d3b8ea80 100644 --- a/packages/adapters/src/tmdb/language-policy.ts +++ b/packages/adapters/src/tmdb/language-policy.ts @@ -147,6 +147,32 @@ export function chineseScriptOf(text: string): "zh-Hans" | "zh-Hant" | null { return null; } +/** 原语言是否中文(TMDB 用 zh,粤语作品是 cn) */ +const isChineseOriginal = (lang: string | null | undefined) => { + const p = primaryOf(lang); + return p === "zh" || p === "cn"; +}; + +/** + * 详情页头部展示的标语:只要简体中文,没有就不显示(不退到繁中或其它语言)。只决定展示,不改存储的取值。 + * - language 是取值语言(TMDB 富详情的 taglineLanguage;媒体库存的标语语言多为 null,只能看文字); + * - 原语言为中文的作品按简中处理(TMDB 主记录就是中文原文); + * - 要求含汉字且不含假名 / 谚文;语言标为繁中、非中文,或文字明显是繁体特有字的,不显示。 + */ +export function simplifiedChineseTagline( + text: string | null | undefined, + language: string | null | undefined, + originalLanguage: string | null | undefined, +): string | null { + const t = text?.trim(); + if (!t || !HAN.test(t) || KANA.test(t) || HANGUL.test(t)) return null; + if (isChineseOriginal(originalLanguage)) return t; + if (language) { + if (primaryOf(language) !== "zh" || chineseVariant(language) === "zh-Hant") return null; + } + return chineseScriptOf(t) === "zh-Hant" ? null : t; +} + /** * 标题文字与语言标签是否相符:zh 要求汉字且不含假名 / 谚文(带地区或 Hans / Hant 时再看简繁特有字,明显是另一种的不符); * ja 要求假名或汉字;ko 要求谚文;泰 / 俄 / 阿 / 希伯来 / 印地等按各自文字;`-Latn` 与其余语言按拉丁字母且不含非拉丁强文字。 diff --git a/packages/contracts/src/organization.ts b/packages/contracts/src/organization.ts index 42f98ade..1326cb7b 100644 --- a/packages/contracts/src/organization.ts +++ b/packages/contracts/src/organization.ts @@ -553,6 +553,8 @@ export const LibraryDetail = z.object({ .optional(), /** SCR-1:最近一次刮削(写 NFO 与图片);没刮削过为 null */ scrape: LibraryScrapeState.nullable().optional(), + /** 头部展示的标语:只取简体中文(原语言为中文的按简中),没有为 null;存储的 metadata.tagline 不变 */ + displayTagline: z.string().nullable().optional(), }); export type LibraryDetail = z.infer; diff --git a/packages/contracts/src/search.ts b/packages/contracts/src/search.ts index 53028d0f..d5ccdffd 100644 --- a/packages/contracts/src/search.ts +++ b/packages/contracts/src/search.ts @@ -426,6 +426,7 @@ export const TmdbMediaDetail = z.object({ // MD-1:按语言回退链取的详情字段(媒体详情页与媒体库详情共用展示) /** 中文名:回退链中第一个中文值(zh-CN 优先);没有为 null */ titleZh: z.string().nullable().optional(), + /** 标语(展示用):只取简体中文(原语言为中文的按简中),没有为 null,不退到繁中 */ tagline: z.string().nullable().optional(), genres: z.array(z.string()).optional(), voteAverage: z.number().nullable().optional(), From caf5a1f2f41c40baf7b8458c030b054d2d8cb354 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Oct 2026 01:44:32 +0800 Subject: [PATCH 02/25] =?UTF-8?q?feat(web):=20=E4=BD=9C=E5=93=81=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E5=A4=B4=E5=9B=BE=E6=94=B9=E4=B8=BA=E5=89=A7=E7=85=A7?= =?UTF-8?q?=E9=93=BA=E9=A1=B6=E6=B8=90=E9=9A=90=E5=88=B0=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=BA=95=E8=89=B2(=E6=96=B9=E6=A1=88=20A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MediaHero 去掉深色圆角卡片:剧照铺在内容区顶部,上下与右缘渐隐成透明;左侧文字区压近乎不透明的页面底色、右侧露出剧照,窄屏改为自上而下压底色;文字用常规前景色,日夜同一结构;没有背景图时不渲染剧照层 - 新增材质参数 --mat-hero-*(浅 / 深两套)与 .media-hero-backdrop - 「正常」/ Emby / 库存等徽标并进「连载中」所在的事实行 - 操作按钮(订阅 / 搜索资源 / 查看订阅 / 合并作品 / 插槽入口如补字幕 / 命名覆盖)移进头图区外链行下方,库作品页与 TMDB 详情页一致 - 订阅改为主色实心(Button variant="solid",.pill-solid / --mat-pill-solid-*),作为头图区唯一主操作;设计文档记为例外 - 简介折叠:去掉省略号 + 单独一行「展开」的重复,改为最后一行行尾渐隐、「展开」压在渐隐处;按实际排版测量是否需要折叠 - 补 MediaHero / ClampText 渲染测试 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- apps/web/app/app.css | 80 ++++++++++- .../app/components/media/media-detail.test.ts | 120 ++++++++++++++++ .../web/app/components/media/media-detail.tsx | 136 ++++++++++-------- apps/web/app/components/ui/button.tsx | 2 + apps/web/app/routes/console/library.$id.tsx | 80 ++++++----- .../routes/console/media.tmdb.$type.$id.tsx | 58 ++++---- docs/design/interface-materials.md | 11 +- docs/design/ui-controls.md | 1 + 8 files changed, 370 insertions(+), 118 deletions(-) create mode 100644 apps/web/app/components/media/media-detail.test.ts diff --git a/apps/web/app/app.css b/apps/web/app/app.css index e863b6d3..64d80459 100644 --- a/apps/web/app/app.css +++ b/apps/web/app/app.css @@ -352,6 +352,16 @@ --mat-pill-secondary-bg: rgba(20, 22, 28, 0.03); --mat-pill-secondary-bg-hover: rgba(20, 22, 28, 0.07); --mat-pill-secondary-border: rgba(20, 22, 28, 0.12); + /* pill-solid:详情页头图区的唯一主操作(订阅),主色实心;只用在 MediaHero 的操作行 */ + --mat-pill-solid-bg: var(--primary); + --mat-pill-solid-bg-hover: color-mix(in oklab, var(--primary) 84%, var(--background)); + --mat-pill-solid-fg: var(--primary-foreground); + --mat-pill-solid-shadow: 0 1px 2px rgba(20, 22, 28, 0.14), 0 6px 16px -8px rgba(20, 22, 28, 0.4); + /* media-hero:详情页头图。剧照铺在内容区顶部、渐隐到页面底色;左侧文字区压近乎不透明的底色,右侧露出剧照。 + 窄屏海报与文字上下排,改为自上而下压底色 */ + --mat-hero-image-opacity: 1; + --mat-hero-scrim-side: linear-gradient(90deg, var(--background) 0%, color-mix(in oklab, var(--background) 96%, transparent) 38%, color-mix(in oklab, var(--background) 82%, transparent) 55%, color-mix(in oklab, var(--background) 40%, transparent) 72%, transparent 92%); + --mat-hero-scrim-stack: linear-gradient(180deg, color-mix(in oklab, var(--background) 30%, transparent) 0%, color-mix(in oklab, var(--background) 72%, transparent) 45%, color-mix(in oklab, var(--background) 92%, transparent) 75%, var(--background) 100%); /* 分段控件的滑块(侧栏当前项、SegmentedTabs):日间是托起的白片,夜间是薄白玻璃 */ --mat-seg-thumb-bg: rgba(255, 255, 255, 0.8); --mat-seg-thumb-border: rgba(20, 22, 28, 0.06); @@ -493,6 +503,13 @@ --mat-pill-secondary-bg: rgba(255, 255, 255, 0.04); --mat-pill-secondary-bg-hover: rgba(255, 255, 255, 0.1); --mat-pill-secondary-border: rgba(255, 255, 255, 0.15); + --mat-pill-solid-bg: var(--primary); + --mat-pill-solid-bg-hover: color-mix(in oklab, var(--primary) 84%, var(--background)); + --mat-pill-solid-fg: var(--primary-foreground); + --mat-pill-solid-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 6px 16px -8px rgba(0, 0, 0, 0.6); + --mat-hero-image-opacity: 0.85; + --mat-hero-scrim-side: linear-gradient(90deg, var(--background) 0%, color-mix(in oklab, var(--background) 94%, transparent) 38%, color-mix(in oklab, var(--background) 78%, transparent) 55%, color-mix(in oklab, var(--background) 36%, transparent) 72%, transparent 92%); + --mat-hero-scrim-stack: linear-gradient(180deg, color-mix(in oklab, var(--background) 30%, transparent) 0%, color-mix(in oklab, var(--background) 72%, transparent) 45%, color-mix(in oklab, var(--background) 92%, transparent) 75%, var(--background) 100%); --mat-seg-thumb-bg: rgba(255, 255, 255, 0.08); --mat-seg-thumb-border: rgba(255, 255, 255, 0.15); --mat-seg-thumb-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 4px 18px -6px rgba(0, 0, 0, 0.4); @@ -1313,9 +1330,70 @@ color: var(--content-strong); } + /* 实心主操作:只给详情页头图区的唯一主操作(订阅)用(2026-10-07 用户定的例外), + 其余页面主操作仍是 pill-primary;hover 只微调明度,不换色 */ + .pill-solid { + background: var(--mat-pill-solid-bg); + border: 1px solid transparent; + color: var(--mat-pill-solid-fg); + box-shadow: var(--mat-pill-solid-shadow); + transition: + background-color var(--duration-base) var(--ease-standard), + transform 220ms var(--ease-spring); + } + + .pill-solid:hover { + background: var(--mat-pill-solid-bg-hover); + } + + /* 详情页头图(MediaHero):剧照层上下、右缘渐隐成透明,露出页面底色与光斑,不留硬边; + ::after 是压在剧照上的底色渐变(左侧文字区近乎不透明),参数见 --mat-hero-* */ + .media-hero-backdrop { + -webkit-mask-image: + linear-gradient(to bottom, transparent 0%, #000 14%, #000 55%, transparent 100%), + linear-gradient(to right, #000 0%, #000 90%, transparent 100%); + -webkit-mask-composite: source-in; + mask-image: + linear-gradient(to bottom, transparent 0%, #000 14%, #000 55%, transparent 100%), + linear-gradient(to right, #000 0%, #000 90%, transparent 100%); + mask-composite: intersect; + } + + .media-hero-backdrop > img { + opacity: var(--mat-hero-image-opacity); + } + + .media-hero-backdrop::after { + content: ""; + position: absolute; + inset: 0; + background: var(--mat-hero-scrim-stack); + } + + @media (min-width: 640px) { + .media-hero-backdrop::after { + background: var(--mat-hero-scrim-side); + } + } + + /* 折叠的长文本(ClampText):最后一行行尾渐隐,「展开」压在渐隐处;行数由 --clamp-lines 给 */ + .clamp-fade { + max-height: calc(var(--clamp-lines, 3) * 1lh); + overflow: hidden; + -webkit-mask-image: linear-gradient(#000, #000), linear-gradient(to right, #000 62%, transparent 88%); + -webkit-mask-size: 100% calc(100% - 1lh), 100% 1lh; + -webkit-mask-position: top, bottom; + -webkit-mask-repeat: no-repeat; + mask-image: linear-gradient(#000, #000), linear-gradient(to right, #000 62%, transparent 88%); + mask-size: 100% calc(100% - 1lh), 100% 1lh; + mask-position: top, bottom; + mask-repeat: no-repeat; + } + /* 按下回弹,与 .btn-glass 同一手感 */ .pill-primary:active, - .pill-secondary:active { + .pill-secondary:active, + .pill-solid:active { transform: scale(0.97); } diff --git a/apps/web/app/components/media/media-detail.test.ts b/apps/web/app/components/media/media-detail.test.ts new file mode 100644 index 00000000..47c5b05a --- /dev/null +++ b/apps/web/app/components/media/media-detail.test.ts @@ -0,0 +1,120 @@ +import { createElement, Fragment } from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; +import { StatusBadge } from "~/components/console/status-badge.tsx"; +import { Button } from "~/components/ui/button.tsx"; +import { ClampText, type MediaFacts, MediaHero } from "./media-detail.tsx"; + +// 详情页头图(方案 A):背景图渐隐到页面底色、无背景图退化为普通头部;状态徽标并进事实行; +// 操作按钮并进头图区且只有一个实心主操作;简介折叠只留一个「展开」入口。 + +const facts = (over: Partial = {}): MediaFacts => ({ + title: "人生切割术", + originalTitle: "Severance", + year: 2022, + kind: "series", + kindLabel: "剧集", + genres: ["剧情"], + voteAverage: 8.4, + voteCount: 1200, + airStatus: "Returning Series", + runtime: 55, + seasonCount: 2, + episodeCount: 19, + releaseDate: "2022-02-17", + lastAirDate: "2025-03-20", + platform: "Apple TV", + originCountry: ["US"], + overview: "故事发生在一家名为卢蒙的超级大公司内。", + tagline: null, + posterPath: "/sev-poster.jpg", + backdropPath: "/severance-bg.jpg", + externalLinks: [{ label: "TMDB", href: "https://www.themoviedb.org/tv/95396" }], + ...over, +}); + +const render = (props: Parameters[0]) => renderToStaticMarkup(createElement(MediaHero, props)); + +/** 取 data-slot 标记的元素起始处之后的一段 HTML(足够断言其中的内容) */ +const slotHtml = (html: string, slot: string) => { + const i = html.indexOf(`data-slot="${slot}"`); + return i < 0 ? null : html.slice(i, html.indexOf("", i) + 6); +}; + +describe("MediaHero 头图", () => { + it("有背景图:剧照层经图片代理、带渐隐材质,不再是深色圆角卡片;文字用常规前景色", () => { + const html = render({ facts: facts() }); + expect(html).toContain('data-backdrop="image"'); + expect(html).toContain("media-hero-backdrop"); + expect(html).toContain('src="/api/v1/img/tmdb/w1280/severance-bg.jpg"'); + expect(html).not.toMatch(/bg-scrim|text-on-media|rounded-2xl/); + expect(html).toContain("text-foreground"); + }); + + it("没有背景图:退化为普通头部,不渲染剧照层和空的深色块", () => { + const html = render({ facts: facts({ backdropPath: null }) }); + expect(html).toContain('data-backdrop="none"'); + expect(html).not.toContain("media-hero-backdrop"); + expect(html).not.toContain("w1280"); + expect(html).not.toMatch(/bg-scrim/); + expect(html).toContain("人生切割术"); + }); + + it("状态徽标(正常 / Emby 等)与播出状态在同一行", () => { + const html = render({ + facts: facts(), + badges: createElement(StatusBadge, { tone: "success" }, "正常"), + }); + const row = slotHtml(html, "media-hero-badges"); + expect(row).toContain("连载中"); + expect(row).toContain("正常"); + }); + + it("操作按钮并进头图区、在外链之后;只有订阅是实心主操作,其余是描边次操作,没有棱镜描边", () => { + const html = render({ + facts: facts(), + actions: createElement( + Fragment, + null, + createElement(Button, { variant: "solid", size: "sm" }, "订阅"), + createElement(Button, { variant: "outline", size: "sm" }, "搜索资源"), + createElement(Button, { variant: "outline", size: "sm" }, "查看订阅"), + ), + }); + const actions = slotHtml(html, "media-hero-actions"); + expect(actions).not.toBeNull(); + expect(html.indexOf('data-slot="media-hero-actions"')).toBeGreaterThan(html.indexOf("TMDB")); + expect(actions!.match(/pill-solid/g)).toHaveLength(1); + expect(actions!.match(/pill-secondary/g)).toHaveLength(2); + expect(actions).not.toContain("pill-primary"); + expect(actions!.indexOf("pill-solid")).toBeLessThan(actions!.indexOf("订阅")); + }); + + it("没有操作时不渲染操作行;标语有值才显示", () => { + const plain = render({ facts: facts() }); + expect(plain).not.toContain("media-hero-actions"); + expect(plain).not.toContain("media-hero-tagline"); + const tagged = render({ facts: facts({ tagline: "工作与生活,一刀两断" }) }); + expect(slotHtml(tagged, "media-hero-tagline")).toContain("工作与生活,一刀两断"); + }); +}); + +describe("ClampText 折叠", () => { + const long = "很长的简介。".repeat(60); + + it("超出行数:折叠成行尾渐隐 + 一个「展开」入口,不再另加省略号", () => { + const html = renderToStaticMarkup(createElement(ClampText, { text: long, lines: 4 })); + expect(html).toContain("clamp-fade"); + expect(html).toContain("--clamp-lines:4"); + expect(html).not.toContain("line-clamp"); + expect(html).not.toMatch(/…|\.\.\./); + expect(html.match(/展开/g)).toHaveLength(1); + expect(html).toContain('aria-expanded="false"'); + }); + + it("放得下时不折叠、不给「展开」", () => { + const html = renderToStaticMarkup(createElement(ClampText, { text: "短简介", lines: 4 })); + expect(html).not.toContain("clamp-fade"); + expect(html).not.toContain("展开"); + }); +}); diff --git a/apps/web/app/components/media/media-detail.tsx b/apps/web/app/components/media/media-detail.tsx index b567badf..5ce41898 100644 --- a/apps/web/app/components/media/media-detail.tsx +++ b/apps/web/app/components/media/media-detail.tsx @@ -1,6 +1,6 @@ import { type MediaCredit, tmdbImageUrl } from "@umt/contracts"; import { ChevronDown, ExternalLink, ImageOff, Lock, PencilLine, Star, UserRound } from "lucide-react"; -import { type ReactNode, useState } from "react"; +import { type CSSProperties, type ReactNode, useLayoutEffect, useRef, useState } from "react"; import { StatusBadge, type StatusTone } from "~/components/console/status-badge.tsx"; import { CoverPlaceholder } from "~/components/library/parts.tsx"; import { Button } from "~/components/ui/button.tsx"; @@ -8,7 +8,7 @@ import { cn } from "~/lib/utils.ts"; // 媒体详情(MD-1,对照 MP 媒体详情页):背景图 + 海报 + 标题 / 原名 / 年份 / 类型 / 评分 / 状态 / 集数 + 简介、 // 主创、季折叠与每集行。媒体库详情(本地作品)与仅 TMDB 的作品详情共用;数据由页面整理成下面的结构传入。 -// 图片一律经图片代理(tmdbImageUrl);背景图上压 scrim 遮罩,文字用 on-media 色(不随日夜翻转)。 +// 图片一律经图片代理(tmdbImageUrl);背景图渐隐到页面底色(--mat-hero-*),文字用常规前景色,随日夜翻转。 /** TMDB status 原文 → 界面用词 */ export const AIR_STATUS: Record = { @@ -59,33 +59,44 @@ function runtimeText(min: number | null, kind: string): string | null { return kind === "movie" ? s : `单集 ${s}`; } -/** 可折叠的长文本:默认最多 lines 行,超出给「展开」 */ -export function ClampText({ - text, - lines = 3, - className, - onMedia = false, -}: { - text: string; - lines?: 2 | 3 | 4; - className?: string; - onMedia?: boolean; -}) { +/** 可折叠的长文本:默认最多 lines 行;放不下时最后一行行尾渐隐,「展开」压在渐隐处(只有这一个入口,不另加省略号) */ +export function ClampText({ text, lines = 3, className }: { text: string; lines?: 2 | 3 | 4; className?: string }) { const [open, setOpen] = useState(false); - const long = text.length > lines * 38; - const clamp = lines === 2 ? "line-clamp-2" : lines === 3 ? "line-clamp-3" : "line-clamp-4"; + const ref = useRef(null); + // 首屏(含服务端渲染)按字数估计,挂载后按实际排版量:内容高度超过 lines 行才折叠 + const [long, setLong] = useState(() => text.length > lines * 38); + useLayoutEffect(() => { + const el = ref.current; + if (!el || typeof ResizeObserver === "undefined") return; + const measure = () => { + const cs = getComputedStyle(el); + const lh = Number.parseFloat(cs.lineHeight) || Number.parseFloat(cs.fontSize) * 1.5; + setLong(el.scrollHeight > lines * lh + 1); + }; + measure(); + const ro = new ResizeObserver(measure); + ro.observe(el); + return () => ro.disconnect(); + }, [lines]); + const clamped = long && !open; return ( -
-

{text}

+
+

+ {text} +

{long ? (
) : null} -
-
-
+
-

+

{facts.title} - {facts.year ? ({facts.year}) : null} + {facts.year ? ({facts.year}) : null}

- {sub.length ?

{sub.join(" · ")}

: null} -
+ {sub.length ?

{sub.join(" · ")}

: null} +
{facts.voteAverage ? ( - + {facts.voteAverage.toFixed(1)} {facts.voteCount ? ( - ({facts.voteCount} 人评分) + ({facts.voteCount} 人评分) ) : null} ) : null} - {status ? {status.label} : null} + {status || badges ? ( + + {status ? {status.label} : null} + {badges} + + ) : null} {line.length ? {line.join(" · ")} : null}
{facts.genres.length ? (
    {facts.genres.map((g) => ( -
  • +
  • {g}
  • ))}
) : null} - {badges ?
{badges}
: null} - {facts.tagline ?

{facts.tagline}

: null} + {facts.tagline ? ( +

+ {facts.tagline} +

+ ) : null} {facts.overview ? ( ) : ( -

暂无简介

+

暂无简介

)} {dates.length || facts.externalLinks.length ? ( -
+
{dates.map((d) => ( {d} ))} @@ -196,7 +215,7 @@ export function MediaHero({ facts, badges }: { facts: MediaFacts; badges?: React href={l.href} target="_blank" rel="noreferrer noopener" - className="inline-flex items-center gap-1 underline-offset-4 hover:text-on-media-strong hover:underline" + className="inline-flex items-center gap-1 underline-offset-4 hover:text-foreground hover:underline" > {l.label} @@ -204,6 +223,11 @@ export function MediaHero({ facts, badges }: { facts: MediaFacts; badges?: React ))}
) : null} + {actions ? ( +
+ {actions} +
+ ) : null}
diff --git a/apps/web/app/components/ui/button.tsx b/apps/web/app/components/ui/button.tsx index 0065999c..4d391290 100644 --- a/apps/web/app/components/ui/button.tsx +++ b/apps/web/app/components/ui/button.tsx @@ -14,6 +14,8 @@ const buttonVariants = cva( variant: { // 主操作:半透明胶囊 + 沿边框流转的棱镜光谱。样式见 app.css 的 .pill-primary(--mat-pill-primary-*、--prism-*) default: "pill-primary", + // 实心主操作:只给详情页头图区的唯一主操作(订阅)用,见 app.css 的 .pill-solid(--mat-pill-solid-*) + solid: "pill-solid", // 次操作:描边胶囊。outline 与 secondary 是同一个样子,保留两个名字只为兼容旧调用 outline: "pill-secondary", secondary: "pill-secondary", diff --git a/apps/web/app/routes/console/library.$id.tsx b/apps/web/app/routes/console/library.$id.tsx index 215f60af..2692b07b 100644 --- a/apps/web/app/routes/console/library.$id.tsx +++ b/apps/web/app/routes/console/library.$id.tsx @@ -1117,7 +1117,7 @@ export default function LibraryDetailPage() { const back = ( 媒体库 @@ -1175,42 +1175,54 @@ export default function LibraryDetailPage() { {(ms.data?.items.length ?? 0) > 0 ? : null} } - /> -
- {e.status !== "merged" ? ( + actions={ <> - - + + + ) : null} + + {e.status !== "merged" ? ( + + ) : null} + {/* 作品页操作区插槽(ADR-006):补字幕等功能模块的入口 */} + {entityActions.items({ entity: slotEntity }).map((a) => ( + + ))} + - ) : null} - - {e.status !== "merged" ? ( - - ) : null} - {/* 作品页操作区插槽(ADR-006):补字幕等功能模块的入口 */} - {entityActions.items({ entity: slotEntity }).map((a) => ( - - ))} - -
- + } + /> +
{credits.cast.length || credits.crew.length ? ( diff --git a/apps/web/app/routes/console/media.tmdb.$type.$id.tsx b/apps/web/app/routes/console/media.tmdb.$type.$id.tsx index 374a7711..4f826b25 100644 --- a/apps/web/app/routes/console/media.tmdb.$type.$id.tsx +++ b/apps/web/app/routes/console/media.tmdb.$type.$id.tsx @@ -41,7 +41,7 @@ export default function TmdbMediaPage() { - - {d.localEntityId ? ( - - ) : ( - - )} -
+ } + actions={ + <> + {/* 头图区唯一的主操作:实心;其余保持描边 */} + + + {d.localEntityId ? ( + + ) : ( + + )} + + } + /> -
+
{d.cast?.length || d.crew?.length ? ( diff --git a/docs/design/interface-materials.md b/docs/design/interface-materials.md index 42f9d1a8..fc981c41 100644 --- a/docs/design/interface-materials.md +++ b/docs/design/interface-materials.md @@ -22,7 +22,7 @@ - 选中态是静态的 `pill-selected`; - 分段控件的滑块是 `seg-thumb`。 - 全站没有实心按钮,也没有实心的选中块。 + 全站没有实心按钮,也没有实心的选中块。唯一例外是作品详情头图区(`MediaHero`)的「订阅」:压在剧照上时棱镜环看不清,改用主色实心 `pill-solid`(2026-10-07 定),其余页面不用。 5. **日夜两套,画面跟随页面**。大图、横向滚动行两侧的渐隐和压暗层都取页面底色,不硬切成黑色。播放页永远是夜间,落地页暂时固定夜间。 6. **同一种信息用同一种结构**: - 套餐卡的零件在公共定价页和订阅服务页共用; @@ -96,6 +96,15 @@ 列表页(电影、剧集、收藏、合集、演职人员)不做色带:标题、筛选条和内容坐在同一张页面底上。吸顶筛选条 `StickyFilterBar` 平时透明,滚到顶栏下面吸住之后才出现磨砂底,和顶栏连成一条。 +## 控制台作品详情头图 + +媒体库作品页与发现页 TMDB 详情共用 `MediaHero`(2026-10-07 方案 A): + +- 剧照铺在内容区顶部(左右伸到内容区的内边距,向上伸到返回链接后面),`.media-hero-backdrop` 让剧照上下和右缘渐隐成透明,露出页面底色与光斑,不做圆角卡片; +- 剧照上压一层页面底色的渐变(`--mat-hero-scrim-side`):左侧文字区近乎不透明、右侧露出剧照;窄屏海报与文字上下排,改为自上而下压底色(`--mat-hero-scrim-stack`); +- 文字用常规前景色 token,日夜同一结构,只是 `--background` 不同;没有背景图时不渲染剧照层,就是普通头部; +- 播出状态与作品状态 / Emby 徽标在同一行;操作按钮在外链行下方,「订阅」是唯一的实心主操作,其余描边。 + ## 其他 - 侧栏的选中项由滑块(`seg-thumb`)承担主要形态,导航文字与 hover 保持清晰;分组展开、滚动和路由变化都会影响滑块位置。 diff --git a/docs/design/ui-controls.md b/docs/design/ui-controls.md index 535bd1fd..d7aef53e 100644 --- a/docs/design/ui-controls.md +++ b/docs/design/ui-controls.md @@ -15,6 +15,7 @@ | 图标钮 | ` {entry ? : null} diff --git a/apps/web/app/components/schedules/agent-check-card.tsx b/apps/web/app/components/schedules/agent-check-card.tsx index bd07be18..2fb6cd6f 100644 --- a/apps/web/app/components/schedules/agent-check-card.tsx +++ b/apps/web/app/components/schedules/agent-check-card.tsx @@ -199,7 +199,7 @@ export function AgentCheckCard({
From 822d4e7c635975da839eb835b9d77449b629c003 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Oct 2026 01:48:14 +0800 Subject: [PATCH 07/25] =?UTF-8?q?=E6=96=87=E6=A1=88(=E6=90=9C=E7=B4=A2):?= =?UTF-8?q?=E6=8C=89=20human-copy=20=E9=87=8D=E5=86=99=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E4=B8=8E=E5=91=BD=E4=BB=A4=E9=9D=A2=E6=9D=BF=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- .../app/components/search/command-palette.tsx | 4 ++-- .../app/components/search/release-results.tsx | 20 +++++++++---------- .../app/components/search/search-sections.tsx | 2 +- .../components/search/site-picker-dialog.tsx | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/web/app/components/search/command-palette.tsx b/apps/web/app/components/search/command-palette.tsx index 96ace446..a99a1154 100644 --- a/apps/web/app/components/search/command-palette.tsx +++ b/apps/web/app/components/search/command-palette.tsx @@ -194,7 +194,7 @@ function PaletteBody({ initial }: { initial: string }) { }} />

- 输入作品名、别名或页面名称。选中作品打开详情页。 + 输入作品名、别名或页面名

) : ( @@ -320,7 +320,7 @@ function actionItem(a: SearchAction, run: () => void): Item { icon: actionIcon(a.kind), title: a.title, meta: a.group, - hint: "前往", + hint: "打开", run, }; } diff --git a/apps/web/app/components/search/release-results.tsx b/apps/web/app/components/search/release-results.tsx index d18f9ce2..bc385026 100644 --- a/apps/web/app/components/search/release-results.tsx +++ b/apps/web/app/components/search/release-results.tsx @@ -181,7 +181,7 @@ export function ReleaseResults({ {first ? (

- {first.sites.length} 个站点 · {first.cached ? "缓存结果" : "站点实时结果"} · 抓取于{" "} + {first.sites.length} 个站点 · {first.cached ? "缓存结果" : "实时结果"} · 上次抓取{" "} {relTime(first.fetchedAt)}

) : null} @@ -199,7 +199,7 @@ export function ReleaseResults({ onClick={() => refresh.mutate(api, { onError: (e) => toast.error(errorMessage(e, "刷新失败")) })} > - {refresh.isPending ? "查询中…" : "刷新"} + {refresh.isPending ? "刷新中…" : "刷新"}
@@ -240,7 +240,7 @@ export function ReleaseResults({
{res.isPending ? (
-

正在查询各站点,最慢约 15 秒…

+

查询各站点中,最长约 15 秒…

) : res.isError && !rows.length ? ( @@ -361,7 +361,7 @@ function CoverageNotice({ page }: { page: ReleaseSearchPage }) {
    {page.failedSites.map((s) => (
  • - {s.siteName}:{SITE_STATUS[s.status]} + {s.siteName}:{SITE_STATUS[s.status]} {s.error ? `(${s.error})` : ""}
  • ))} @@ -384,7 +384,7 @@ function CoverageNotice({ page }: { page: ReleaseSearchPage }) {
      {skipped.map((s) => (
    • - {s.siteName}:{s.error ?? "站点不支持搜索"} + {s.siteName}:{s.error ?? "不支持搜索"}
    • ))}
    @@ -650,11 +650,11 @@ function ReleaseRow({ r, onOpen, onDownload }: { r: SearchRelease; onOpen: () => {r.sizeBytes === null ? "—" : formatBytes(r.sizeBytes, 1)} - + {r.seeders ?? "—"} - + {r.leechers ?? "—"} {r.sourceReleaseId ? ( ) : null}
diff --git a/apps/web/app/components/search/site-picker-dialog.tsx b/apps/web/app/components/search/site-picker-dialog.tsx index eceb2327..4ae5675d 100644 --- a/apps/web/app/components/search/site-picker-dialog.tsx +++ b/apps/web/app/components/search/site-picker-dialog.tsx @@ -134,7 +134,7 @@ export function SitePickerDialog({ ))} {sitesQ.isPending ? ( -

加载站点…

+

加载中…

) : sites.length === 0 ? (

还没有启用的站点。 @@ -156,7 +156,7 @@ export function SitePickerDialog({ .filter((s) => ids.includes(s.id)) .map((s) => s.name) .join("、") - : "没有站点勾选「默认参与搜索」。在站点页打开,或改选全部站点。"} + : "没有站点勾选「默认参与搜索」。到站点页勾选,或改选「全部站点」。"}

)} From 5aa19a9a9b8353454dd4af5ff9e0a9a2bc0c5e9f Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Oct 2026 01:48:14 +0800 Subject: [PATCH 08/25] =?UTF-8?q?=E6=96=87=E6=A1=88(=E5=8E=9F=E7=AB=99?= =?UTF-8?q?=E8=A7=86=E5=9B=BE):=E9=87=8D=E5=86=99=E6=B5=8F=E8=A7=88?= =?UTF-8?q?=E5=99=A8=E4=BC=9A=E8=AF=9D=E3=80=81=E7=99=BB=E5=BD=95=E6=80=81?= =?UTF-8?q?=E3=80=81=E9=87=8D=E8=BF=9E=E6=8F=90=E7=A4=BA=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- apps/web/app/components/browser/coords.ts | 8 ++++---- apps/web/app/components/browser/desktop-view.tsx | 2 +- apps/web/app/components/browser/reconnect.tsx | 2 +- apps/web/app/components/browser/resource-download.tsx | 4 ++-- apps/web/app/components/browser/site-origin-view.tsx | 10 +++++----- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/web/app/components/browser/coords.ts b/apps/web/app/components/browser/coords.ts index 9f125377..aabfb828 100644 --- a/apps/web/app/components/browser/coords.ts +++ b/apps/web/app/components/browser/coords.ts @@ -48,9 +48,9 @@ const WAITING_TEXT: Record = { captcha: "登录页需要验证码", challenge: "页面需要人工验证", two_factor: "需要二次验证或本人设备确认", - challenge_unsolved: "自动处理页面挑战未成功", + challenge_unsolved: "页面验证没有自动通过", login_required: "登录已失效:接管后在画面里登录一次,交还后全站生效", - lease_expired: "接管超时未交还", + lease_expired: "接管超时,没有交还", agent_request: "智能体请求人工处理", human_released_waiting: "交还时选择继续等待", }; @@ -80,9 +80,9 @@ export function credentialBadge( case "injected": return { tone: "success", text: `登录态:${src ?? "已注入"}`, title }; case "expired": - return { tone: "warning", text: "登录态已过期:在画面里登录一次即可全站生效", title }; + return { tone: "warning", text: "登录态已过期:在画面里登录一次,全站生效", title }; case "none": - return { tone: "neutral", text: "账号没有登录态:在画面里登录一次即可全站生效", title }; + return { tone: "neutral", text: "账号没有登录态:在画面里登录一次,全站生效", title }; case "unsupported": return { tone: "warning", text: "浏览器服务不支持注入登录态", title }; default: diff --git a/apps/web/app/components/browser/desktop-view.tsx b/apps/web/app/components/browser/desktop-view.tsx index 9fc5d6b4..b6bacb3f 100644 --- a/apps/web/app/components/browser/desktop-view.tsx +++ b/apps/web/app/components/browser/desktop-view.tsx @@ -81,7 +81,7 @@ export function SiteOriginPanel({ site }: { site: SiteListItem }) { if (!s || !canDesktop || mode === "screenshot") { let note: string | null = null; if (s && desktop && !desktop.available && desktop.reason) note = `桌面模式不可用:${desktop.reason}。`; - else if (s && desktop?.available && !secure) note = "桌面模式需要通过 HTTPS 访问控制台,当前使用截图模式。"; + else if (s && desktop?.available && !secure) note = "桌面模式需要用 HTTPS 打开控制台,现在是截图模式。"; return (
{note || (canDesktop && mode === "screenshot") ? ( diff --git a/apps/web/app/components/browser/reconnect.tsx b/apps/web/app/components/browser/reconnect.tsx index a8313c2f..4f249f0f 100644 --- a/apps/web/app/components/browser/reconnect.tsx +++ b/apps/web/app/components/browser/reconnect.tsx @@ -109,7 +109,7 @@ export function ReloadNotice({ session }: { session: BrowserSessionView }) { if (!at || dismissed === at) return null; return (
- 浏览器重启过,页面已重新加载,请核对 + 浏览器重启过,页面已重新加载,表单输入和滚动位置已丢失 diff --git a/apps/web/app/components/browser/resource-download.tsx b/apps/web/app/components/browser/resource-download.tsx index 9f9fccd6..08fbc6a0 100644 --- a/apps/web/app/components/browser/resource-download.tsx +++ b/apps/web/app/components/browser/resource-download.tsx @@ -40,7 +40,7 @@ export function ResourceDownload({ session }: { session: BrowserSessionView }) { } else if (result.submitted) { setPlanId(result.plan?.id ?? null); toast.success("已提交下载"); - } else toast.warning(result.rejected.map((v) => v.message).join(";") || "没有提交下载"); + } else toast.warning(result.rejected.map((v) => v.message).join(";") || "没有提交"); } } catch (e) { toast.error(errorMessage(e)); @@ -53,7 +53,7 @@ export function ResourceDownload({ session }: { session: BrowserSessionView }) {
) : null diff --git a/apps/web/app/components/downloads/business-timeline.tsx b/apps/web/app/components/downloads/business-timeline.tsx index a721d8c2..4ccc33ce 100644 --- a/apps/web/app/components/downloads/business-timeline.tsx +++ b/apps/web/app/components/downloads/business-timeline.tsx @@ -36,7 +36,7 @@ export function BusinessTimeline({ events, loading }: { events: TimelineEvent[]; })}
{ongoing.length ? ( -

当前:{ongoing.map((e) => e.title).join(";")}

+

当前:{ongoing.map((e) => e.title).join(";")}

) : null} {loading && !events.length ? ( @@ -60,7 +60,7 @@ export function BusinessTimeline({ events, loading }: { events: TimelineEvent[]; /> )} {loading && events.length ? ( -

正在加载关联的整理记录与引擎运行…

+

加载整理记录和引擎运行…

) : null}
); diff --git a/apps/web/app/components/downloads/cache-volumes.tsx b/apps/web/app/components/downloads/cache-volumes.tsx index 2b1f5ab8..5b38dc05 100644 --- a/apps/web/app/components/downloads/cache-volumes.tsx +++ b/apps/web/app/components/downloads/cache-volumes.tsx @@ -81,9 +81,9 @@ function VolumeBar({ v }: { v: Volume }) { />
{pct === null ? 读不到磁盘状态,已暂停新提交 : null} - {tripped ? 超过暂停线,停止新下载 : null} + {tripped ? 超过暂停线,已停止新下载 : null} {v.pausedByThreshold ? ( - 因下载盘暂停 {v.pausedByThreshold} 个任务 + 超过暂停线,暂停了 {v.pausedByThreshold} 个任务 ) : null}
{v.notes.map((n) => ( @@ -119,7 +119,7 @@ export function CacheVolumes() { } title="按规则清理下载缓存?" - description="只清理已整理、且满足保留期与 H&R 的本工具任务,其余不动。" + description="只清理本工具管理、已整理且满足保留期和 H&R 要求的任务,其余不动。" confirmLabel="清理" onConfirm={() => cleanup.mutateAsync(undefined).then( @@ -138,7 +138,7 @@ export function CacheVolumes() { ) : vols.error ? ( void vols.refetch()} /> ) : !vols.data.length ? ( -

还没有缓存卷数据。登记下载器并完成巡检后显示。

+

还没有下载盘。添加下载器后显示。

) : (
{vols.data.map((v) => ( diff --git a/apps/web/app/components/downloads/download-board.tsx b/apps/web/app/components/downloads/download-board.tsx index f9ed371b..b03c90fa 100644 --- a/apps/web/app/components/downloads/download-board.tsx +++ b/apps/web/app/components/downloads/download-board.tsx @@ -333,11 +333,11 @@ export const BOARD_EMPTY: Record {DOWNLOAD_STATUS_LABEL[t.status]} {t.pauseReason === "threshold" ? ( - 下载盘满,已暂停 + 超过暂停线,已暂停 ) : t.pauseReason ? ( {PAUSE_REASON_LABEL[t.pauseReason]} ) : null} @@ -151,7 +151,7 @@ export function DownloadDetail({ task, live }: { task: DownloadTask; live?: Down ) : null} {task.noProgressSince ? (

- 自 {formatDateTime(task.noProgressSince)} 起无进展,不会自动换源;开了自动调查时智能体会在授权内尝试恢复。 + {formatDateTime(task.noProgressSince)} 起没有进展,不会自动换源。开启自动调查时,智能体会在持续授权范围内尝试恢复。

) : null} diff --git a/apps/web/app/components/downloads/manual-download-dialog.test.ts b/apps/web/app/components/downloads/manual-download-dialog.test.ts index eb7d866e..0091968b 100644 --- a/apps/web/app/components/downloads/manual-download-dialog.test.ts +++ b/apps/web/app/components/downloads/manual-download-dialog.test.ts @@ -85,7 +85,7 @@ describe("保存目录", () => { expect(custom).not.toHaveProperty("downloaderInstanceId"); }); - it("界面:并排两个下拉,下方写明将保存到哪;自定义时展开输入框", () => { + it("界面:并排两个下拉,下方写明保存到哪;自定义时展开输入框", () => { const props = { formId: "f", instances: paths.instances, @@ -101,10 +101,10 @@ describe("保存目录", () => { const html = render(createElement(SaveTargetFields, props)); expect(html).toContain("下载器"); expect(html).toContain("自动:qb-main"); - expect(html).toContain("将保存到:/data/downloads(本机 /mnt/dl)"); + expect(html).toContain("保存到:/data/downloads(本机 /mnt/dl)"); expect(html).not.toContain("自定义路径"); const custom = render(createElement(SaveTargetFields, { ...props, dir: CUSTOM_DIR, custom: "/x" })); - expect(custom).toContain("将保存到:/x"); + expect(custom).toContain("保存到:/x"); expect(custom).toContain('id="f-custom"'); const none = render( createElement(SaveTargetFields, { ...props, instances: [], autoInstanceId: null, target: null }), @@ -136,7 +136,7 @@ describe("作品身份", () => { expect(html).toContain("未识别"); expect(html).toContain("TMDB 编号"); expect(html).toContain('id="f-tmdb"'); - expect(html).toContain("暂未识别到作品"); + expect(html).toContain("没识别出作品"); }); it("已识别:折叠只显示当前作品", () => { diff --git a/apps/web/app/components/downloads/manual-download-dialog.tsx b/apps/web/app/components/downloads/manual-download-dialog.tsx index de8a3567..4ded6b26 100644 --- a/apps/web/app/components/downloads/manual-download-dialog.tsx +++ b/apps/web/app/components/downloads/manual-download-dialog.tsx @@ -282,7 +282,7 @@ export function ManualDownloadDialog({ }} > - 委托智能体 + 交给智能体 ) : null}
); @@ -372,7 +372,7 @@ export function SaveTargetFields({ : !target && dir !== CUSTOM_DIR ? "没有可用的下载器,先在设置里添加下载器和保存目录" : resolved - ? `将保存到:${savePathText(resolved)}` + ? `保存到:${savePathText(resolved)}` : "填写下载器能访问的路径"; return (
@@ -504,7 +504,7 @@ export function IdentitySection({
{unrecognized && !changed ? (

- 暂未识别到作品。指定后绑定到这个资源;不指定也能下载,整理时再补全识别和元数据。 + 没识别出作品。指定后绑定到这个资源;不指定也能下载,整理时再识别。

) : null} {picked ? ( @@ -533,7 +533,7 @@ export function IdentitySection({ void; }) { - if (loading) return

正在读取已有版本…

; - if (error) return

已有版本读取失败,重试后再选洗版

; + if (loading) return

读取已有版本…

; + if (error) return

读取已有版本失败,重新打开后再选洗版

; if (!versions.length) return null; return (
@@ -640,7 +640,7 @@ export function ExistingVersionsSection({ ))}

- 本次新文件整理成功后替换所选旧文件。剧集只替换成功对应的季集,其余保留。 + 新文件整理成功后替换所选旧文件。剧集只替换整理成功的季集,其余保留。

) : null} From 0bf26b78c69653f85155cdc768d98b35c48a1627 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Oct 2026 01:48:14 +0800 Subject: [PATCH 10/25] =?UTF-8?q?=E6=96=87=E6=A1=88(=E8=A7=84=E5=88=99=20/?= =?UTF-8?q?=20=E5=91=BD=E5=90=8D):=E9=87=8D=E5=86=99=E8=A7=84=E5=88=99?= =?UTF-8?q?=E7=BB=84=E3=80=81=E8=AF=95=E7=AE=97=E4=B8=8E=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- apps/web/app/components/naming/template-editor.tsx | 10 +++++----- apps/web/app/components/rules/group-editor.tsx | 12 ++++++------ apps/web/app/components/rules/labels.ts | 4 ++-- apps/web/app/components/rules/ladder-editor.tsx | 6 +++--- apps/web/app/components/rules/preview-panel.tsx | 4 ++-- apps/web/app/components/rules/tabs.tsx | 4 ++-- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/web/app/components/naming/template-editor.tsx b/apps/web/app/components/naming/template-editor.tsx index 60eae686..27f0c5bf 100644 --- a/apps/web/app/components/naming/template-editor.tsx +++ b/apps/web/app/components/naming/template-editor.tsx @@ -25,13 +25,13 @@ const GROUP_ORDER = ["title", "episode", "tech", "id", "ours"] as const; const ROLE: Record = { category: { label: "分类", className: "text-muted-foreground" }, - root: { label: "作品根", className: "text-primary font-medium" }, + root: { label: "作品目录", className: "text-primary font-medium" }, season: { label: "季目录", className: "text-foreground" }, sub: { label: "子目录", className: "text-muted-foreground" }, file: { label: "文件名", className: "text-foreground font-medium" }, }; -export const NAMING_NOTE = "已建立目录的作品不受模板影响,模板只决定新目录和新文件名。"; +export const NAMING_NOTE = "已建立目录的作品沿用原目录,模板只决定新目录和文件名。"; function useDebounced(v: T, ms = 300): T { const [d, setD] = useState(v); @@ -167,7 +167,7 @@ export function NamingTemplateEditor({ {override && !value.trim() ? `留空:沿用${inherited?.[active]?.source ?? "上级"}模板` - : "/ 分目录,最后一段是文件名,须以 {{fileExt}} 结尾"} + : "用 / 分目录,最后一段是文件名,以 {{fileExt}} 结尾"} {value.length} / 1024
@@ -317,10 +317,10 @@ function PreviewBody({ ))}
)} - {r.usesBinding ? 这部作品已有目录,目录沿用已建立的路径,模板只决定文件名。 : null} + {r.usesBinding ? 这部作品已有目录,沿用原目录,模板只决定文件名。 : null} {r.missing.length ? (

- 无值的变量(输出为空):{r.missing.join("、")} + 没有值的变量:{r.missing.join("、")}

) : null} {r.warnings.map((w) => ( diff --git a/apps/web/app/components/rules/group-editor.tsx b/apps/web/app/components/rules/group-editor.tsx index 69980d60..1bb13c2a 100644 --- a/apps/web/app/components/rules/group-editor.tsx +++ b/apps/web/app/components/rules/group-editor.tsx @@ -108,7 +108,7 @@ export function RuleGroupEditor({ const save = async () => { if (!form.name.trim()) { - setNameError("名称必填"); + setNameError("填写名称"); return; } const scope: RuleGroupScope = { @@ -263,7 +263,7 @@ export function RuleGroupEditor({ label="作用范围" hint={ group && (group.scope.entityIds?.length || group.scope.siteIds?.length || group.scope.subscriptionId) - ? `另有:${scopeText({ ...group.scope, mediaTypes: undefined, mediaKinds: undefined } as RuleGroupScope)}(此处不改)` + ? `另有:${scopeText({ ...group.scope, mediaTypes: undefined, mediaKinds: undefined } as RuleGroupScope)}(这里不改)` : "不选 = 全部类型" } className="sm:col-span-2" @@ -277,15 +277,15 @@ export function RuleGroupEditor({
-

一层都不中时拒绝

+

一层都没命中时拒绝

- {form.ladderRequired ? "资源至少要命中一层" : "只影响排序,不中的资源 pri 为 0"} + {form.ladderRequired ? "资源至少要命中一层" : "只影响排序,没命中的资源 pri 为 0"}

set("ladderRequired", v)} - aria-label="一层都不中时拒绝" + aria-label="一层都没命中时拒绝" />
@@ -379,7 +379,7 @@ const FIELD_LABEL: Record = { scope: "作用范围", layers: "阶梯", hardExcludes: "硬排除", - ladderRequired: "一层不中拒绝", + ladderRequired: "一层都没命中时拒绝", enabled: "启用", label: "名称", rule: "规则", diff --git a/apps/web/app/components/rules/labels.ts b/apps/web/app/components/rules/labels.ts index be56a7dc..29e9fafc 100644 --- a/apps/web/app/components/rules/labels.ts +++ b/apps/web/app/components/rules/labels.ts @@ -29,7 +29,7 @@ export const SUPPRESS_REASON: Record = { disabled: "停用", "status:disabled": "停用", "status:deleted": "删除", - edited: "修改语义", + edited: "改了条件", }; export const suppressReason = (r: string | null) => (r ? (SUPPRESS_REASON[r] ?? r) : "未注明"); @@ -59,5 +59,5 @@ export function ruleSummary(g: RuleGroup): string { parts.push(`${g.ladderRequired ? "要求" : "偏好"} ${g.layers.map((l) => exprToString(l.expr)).join(" > ")}`); for (const h of g.hardExcludes) parts.push(`排除 ${exprToString(h)}`); if (parts.length) return parts.join(";"); - return g.legacyKind ? "旧偏好标签,没有可评估的条件" : "没有条件"; + return g.legacyKind ? "旧版偏好,没有可评估的条件" : "没有条件"; } diff --git a/apps/web/app/components/rules/ladder-editor.tsx b/apps/web/app/components/rules/ladder-editor.tsx index 2fe7fa8f..8ca0cb88 100644 --- a/apps/web/app/components/rules/ladder-editor.tsx +++ b/apps/web/app/components/rules/ladder-editor.tsx @@ -47,7 +47,7 @@ export function ladderFrom(layers: RuleLayer[]): LadderState { /** 保存用:有错误 / 解析中 / 有空层时不可保存 */ export function ladderProblem(s: LadderState): string | null { - if (s.parsing) return "规则串还在解析"; + if (s.parsing) return "规则串解析中"; if (s.failure) return s.failure; if (s.errors.length) return "规则串有错误"; if (s.cards?.some((c) => c.items.length === 0)) return "有空层"; @@ -253,7 +253,7 @@ export function RuleStringField({
  • - 第 {e.position + 1} 个字符{single ? "" : `(第 ${e.layer + 1} 层)`}:{e.message} + 第 {e.position + 1} 个字符{single ? "" : `(第 ${e.layer + 1} 层)`}:{e.message} {e.suggestions?.length ? ( @@ -338,7 +338,7 @@ function AtomChip({ item.neg ? "border-destructive/35 bg-destructive/10 text-destructive" : "border-border bg-foreground/[0.03]", unknown && "border-warning/50", )} - title={keyword ? `${keyword.name}:${keyword.description}` : "未知条件"} + title={keyword ? `${keyword.name}:${keyword.description}` : "未知条件"} > } - title={`删除条件 ${a.key}?`} + title={`删除条件 ${a.key}?`} description="被规则组引用时不能删除。" confirmLabel="删除" destructive From 03c5f668ac87de3858b2948c9ae763b240d1e211 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Oct 2026 01:48:14 +0800 Subject: [PATCH 11/25] =?UTF-8?q?=E6=96=87=E6=A1=88(=E7=AB=99=E7=82=B9=20/?= =?UTF-8?q?=20=E5=AD=97=E5=B9=95):=E9=87=8D=E5=86=99=E7=AB=99=E7=82=B9?= =?UTF-8?q?=E5=8D=A1=E3=80=81=E5=87=AD=E8=AF=81=E3=80=81RSS=E3=80=81Cloudf?= =?UTF-8?q?lare=20=E4=B8=8E=E8=A1=A5=E5=AD=97=E5=B9=95=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- .../components/sites/site-accounts-tab.tsx | 4 ++-- .../app/components/sites/site-add-dialog.tsx | 2 +- apps/web/app/components/sites/site-card.tsx | 6 ++--- .../web/app/components/sites/site-dialogs.tsx | 8 +++---- .../components/sites/site-failure-samples.tsx | 2 +- .../app/components/sites/site-feed-dialog.tsx | 6 ++--- .../app/components/sites/site-fields.test.ts | 6 ++--- apps/web/app/components/sites/site-fields.ts | 24 +++++++++---------- .../components/subtitles/subtitle-dialog.tsx | 2 +- 9 files changed, 30 insertions(+), 30 deletions(-) diff --git a/apps/web/app/components/sites/site-accounts-tab.tsx b/apps/web/app/components/sites/site-accounts-tab.tsx index 91394541..f1af41f0 100644 --- a/apps/web/app/components/sites/site-accounts-tab.tsx +++ b/apps/web/app/components/sites/site-accounts-tab.tsx @@ -118,7 +118,7 @@ function AccountBlock({ onConfirm={async () => { try { await del.mutateAsync(a.id); - toast.success(`已删除账号 ${a.label}`); + toast.success(`已删除账号「${a.label}」`); } catch (err) { toast.error(errorMessage(err, "删除失败")); throw err; @@ -260,7 +260,7 @@ function PasskeyForm({ site, label, has }: { site: SiteListItem; label: string; } }} > -

    站点 Passkey(RSS 与下载链接用)

    +

    站点 Passkey

    用于 RSS 和下载链接,不参与登录。{has ? "填写新值保存会替换。" : "还没有保存。"}

    diff --git a/apps/web/app/components/sites/site-add-dialog.tsx b/apps/web/app/components/sites/site-add-dialog.tsx index 0ce87978..6048fe94 100644 --- a/apps/web/app/components/sites/site-add-dialog.tsx +++ b/apps/web/app/components/sites/site-add-dialog.tsx @@ -375,7 +375,7 @@ export function SiteAddDialog({ onClose, initialKind }: { onClose: () => void; i )}
  • - "未实测"的定义没有在真实站点上验证过,列表和登录可能失败。 + 标「未实测」的站点定义没有在真实站点上验证过,列表和登录可能失败。

    ) : step === 2 ? ( diff --git a/apps/web/app/components/sites/site-card.tsx b/apps/web/app/components/sites/site-card.tsx index 1d00e5f3..5a421bf7 100644 --- a/apps/web/app/components/sites/site-card.tsx +++ b/apps/web/app/components/sites/site-card.tsx @@ -183,7 +183,7 @@ function CheckButtons({ site, checks, compact }: Omit & { com size={compact ? "icon-sm" : "sm"} disabled={h || !site.enabled} aria-label="检测健康" - title={site.enabled ? "检测健康:首页连通 + 登录态,走站点限流" : "站点已停用"} + title={site.enabled ? "检测健康:首页能否打开、登录态是否有效,计入站点限流" : "站点已停用"} onClick={() => void checks.checkHealth(site)} > {h ? : } @@ -194,7 +194,7 @@ function CheckButtons({ site, checks, compact }: Omit & { com size={compact ? "icon-sm" : "sm"} disabled={l || !hasAccount} aria-label="刷新登录" - title={hasAccount ? "刷新登录:用凭证刷新登录状态并拉取用户数据" : "先填写凭证"} + title={hasAccount ? "刷新登录:用凭证重新登录并拉取用户数据" : "先填写凭证"} onClick={() => void checks.refreshLogin(site)} > {l ? : } @@ -289,7 +289,7 @@ function FeedSiteCard({ site, onDialog }: Omit) {
    diff --git a/apps/web/app/components/sites/site-dialogs.tsx b/apps/web/app/components/sites/site-dialogs.tsx index b3d4b527..b02c3982 100644 --- a/apps/web/app/components/sites/site-dialogs.tsx +++ b/apps/web/app/components/sites/site-dialogs.tsx @@ -507,7 +507,7 @@ export function SiteEditSheet({ site, onClose }: { site: SiteListItem; onClose:
    -

    User-Agent 随凭证保存,在"更新凭证"里填写。

    +

    User-Agent 随凭证保存,在「更新凭证」里填写。

    ); @@ -544,9 +544,9 @@ export function DeleteSiteDialog({ }} >
      -
    • 站点、{site.accounts?.length ?? 0} 个账号和全部凭证删除
    • -
    • 已抓到的资源保留,标为"站点已关闭",不再产生新候选
    • -
    • 该站 RSS 停用;已有下载任务按原策略继续
    • +
    • 站点、{site.accounts?.length ?? 0} 个账号和全部凭证都会删除
    • +
    • 已抓到的资源保留,标为「站点已关闭」,不再进入收录评估
    • +
    • 这个站点的 RSS 停用;已有下载任务继续
    • 同一站点以后可以重新添加
    diff --git a/apps/web/app/components/sites/site-failure-samples.tsx b/apps/web/app/components/sites/site-failure-samples.tsx index eec8592f..fb86a0ce 100644 --- a/apps/web/app/components/sites/site-failure-samples.tsx +++ b/apps/web/app/components/sites/site-failure-samples.tsx @@ -70,7 +70,7 @@ export function SiteFailureSamples({ siteId, items }: { siteId: string; items: S if (!items.length) return null; return (
    -

    最近的失败页面(已脱敏,留最近 5 份)

    +

    最近的失败页面(已脱敏,最多保留 5 份)

      {items.map((s) => ( diff --git a/apps/web/app/components/sites/site-feed-dialog.tsx b/apps/web/app/components/sites/site-feed-dialog.tsx index 79f2b7be..12d0dc6c 100644 --- a/apps/web/app/components/sites/site-feed-dialog.tsx +++ b/apps/web/app/components/sites/site-feed-dialog.tsx @@ -23,7 +23,7 @@ import { cn } from "~/lib/utils.ts"; const PURPOSES: { value: FeedPurpose; label: string; hint: string }[] = [ { value: "generic", label: "发现", hint: "全站新种,进入识别和收录评估" }, { value: "search", label: "追踪", hint: "按关键词、分类或收藏筛过的 RSS" }, - { value: "promotion", label: "促销", hint: "只含免费 / 促销种子的 RSS" }, + { value: "promotion", label: "促销", hint: "只含免费 / 促销资源的 RSS" }, ]; /** 首抓最多等 90 秒(站点限流排队);超时后提示去列表看 */ @@ -106,7 +106,7 @@ export function SiteFeedDialog({ - 添加 {site.name} RSS + 新建 RSS · {site.name} 地址加密保存,页面上只显示去掉 passkey 的地址。 @@ -219,7 +219,7 @@ export function SiteFeedDialog({

      - {account ? `账号 ${account.label}` : "该站没有账号,按地址里的 passkey 抓取"} · 解析器按站点定义 · 每 30 + {account ? `账号 ${account.label}` : "这个站点没有账号,按地址里的 passkey 抓取"} · 解析器按站点定义 · 每 30 分钟抓一次,可在 RSS 详情里改

      diff --git a/apps/web/app/components/sites/site-fields.test.ts b/apps/web/app/components/sites/site-fields.test.ts index 1cd408b1..6db87ba9 100644 --- a/apps/web/app/components/sites/site-fields.test.ts +++ b/apps/web/app/components/sites/site-fields.test.ts @@ -43,7 +43,7 @@ describe("cloudflareLine / cloudflareTechItems(CF-2)", () => { const until = new Date("2026-09-30T17:30:00+08:00"); const line = cloudflareLine(cfg({ lastChallengeAt: at, browserUntil: until.toISOString() }), now); const hm = `${String(until.getHours()).padStart(2, "0")}:${String(until.getMinutes()).padStart(2, "0")}`; - expect(line?.text).toBe(`Cloudflare:浏览器代理中,至 ${hm}`); + expect(line?.text).toBe(`Cloudflare:浏览器代抓中,到 ${hm}`); expect(cloudflareLine(cfg({ lastChallengeAt: at, lastSolvedBy: "vision" }), now)?.text).toBe( "Cloudflare:自动通过", ); @@ -63,8 +63,8 @@ describe("cloudflareLine / cloudflareTechItems(CF-2)", () => { }), ); expect(items.map((i) => i.label)).toEqual([ - "最近遇到挑战", - "挑战规则", + "最近遇到验证", + "验证规则", "处理结果", "最近通过方式", "cf_clearance 复用", diff --git a/apps/web/app/components/sites/site-fields.ts b/apps/web/app/components/sites/site-fields.ts index 394ee90d..1bc10232 100644 --- a/apps/web/app/components/sites/site-fields.ts +++ b/apps/web/app/components/sites/site-fields.ts @@ -158,7 +158,7 @@ export function healthMessage(h: SiteLastHealth | null): string | null { const HEALTH_REASON_TEXT: Record = { logged_in: "首页有登录标志", - user_data: "首页没找到登录标志,按用户数据解析成功判为已登录", + user_data: "首页没有登录标志,但用户数据解析成功,按已登录处理", login_page: "返回登录页", permission_page: "返回权限提示页", challenge: "疑似人机验证", @@ -198,7 +198,7 @@ export function cloudflareLine( if (st.browserUntil && Date.parse(st.browserUntil) > now.getTime()) { const t = new Date(st.browserUntil); const hm = `${String(t.getHours()).padStart(2, "0")}:${String(t.getMinutes()).padStart(2, "0")}`; - return { text: `Cloudflare:浏览器代理中,至 ${hm}`, tone: "neutral", takeover: false }; + return { text: `Cloudflare:浏览器代抓中,到 ${hm}`, tone: "neutral", takeover: false }; } if (st.lastSolvedBy) return { text: "Cloudflare:自动通过", tone: "success", takeover: false }; if (st.lastError) return { text: "Cloudflare:没能通过", tone: "warning", takeover: false }; @@ -206,8 +206,8 @@ export function cloudflareLine( } const CF_SOLVED_BY: Record = { - auto: "等它自动放行", - vision: "视觉子流程点了复选框", + auto: "自动放行", + vision: "自动点了验证复选框", human: "你在原站视图里通过", }; const CF_RESULT: Record = { @@ -227,8 +227,8 @@ export function cloudflareTechItems(config: unknown): { label: string; value: st if (!st?.lastChallengeAt) return []; const t = (v: string | null) => (v ? new Date(v).toLocaleString("zh-CN", { hour12: false }) : ""); return [ - { label: "最近遇到挑战", value: t(st.lastChallengeAt) }, - { label: "挑战规则", value: st.lastRule ?? "", mono: true }, + { label: "最近遇到验证", value: t(st.lastChallengeAt) }, + { label: "验证规则", value: st.lastRule ?? "", mono: true }, { label: "处理结果", value: st.lastResult ? `${CF_RESULT[st.lastResult] ?? st.lastResult}(${st.lastResult})` : "" }, { label: "最近通过方式", value: st.lastSolvedBy ? (CF_SOLVED_BY[st.lastSolvedBy] ?? st.lastSolvedBy) : "" }, { label: "最近通过时间", value: t(st.lastSolvedAt) }, @@ -236,7 +236,7 @@ export function cloudflareTechItems(config: unknown): { label: string; value: st label: "cf_clearance 复用", value: st.cfReuse === "ok" ? "有效(ok)" : st.cfReuse === "fails" ? "无效(fails)" : "", }, - { label: "浏览器代理至", value: t(st.browserUntil) }, + { label: "浏览器代抓到", value: t(st.browserUntil) }, { label: "最近尝试", value: t(st.lastAttemptAt) }, { label: "最近错误", value: st.lastError ?? "" }, ].filter((it) => it.value); @@ -317,7 +317,7 @@ export function loginFailureText( return { text: "登录失败:站点要求二次验证。填写 TOTP 密钥后重试,或改用 Cookie", suggestCookie: true }; case "challenge": return { - text: "站点要求人机验证(Cloudflare)。会用浏览器自动过,过不去时在铃铛里提醒你", + text: "站点要求人机验证(Cloudflare)。会先用浏览器自动通过,失败时在铃铛里提醒", suggestCookie: false, }; case "bad_credentials": @@ -333,11 +333,11 @@ export function loginFailureText( suggestCookie: false, }; case "network": - return { text: `连不上站点${message ? `:${message}` : ""}`, suggestCookie: false }; + return { text: `连不上站点${message ? `:${message}` : ""}`, suggestCookie: false }; case "unsupported": - return { text: message ?? "这个站点不支持该凭证类型的自动登录", suggestCookie: false }; + return { text: message ?? "这个站点不支持用这种凭证自动登录", suggestCookie: false }; default: - return { text: message ?? "登录失败,原因未知", suggestCookie: false }; + return { text: message ?? "登录失败", suggestCookie: false }; } } @@ -365,7 +365,7 @@ export const REASON_SHORT: Record = { two_factor: "需二次验证", bad_credentials: "密码错误", account_blocked: "账号封禁", - challenge: "Cloudflare 挑战", + challenge: "Cloudflare 验证", auth_expired: "凭证失效", network: "网络失败", unsupported: "不支持", diff --git a/apps/web/app/components/subtitles/subtitle-dialog.tsx b/apps/web/app/components/subtitles/subtitle-dialog.tsx index 344ae4dd..709fb254 100644 --- a/apps/web/app/components/subtitles/subtitle-dialog.tsx +++ b/apps/web/app/components/subtitles/subtitle-dialog.tsx @@ -470,7 +470,7 @@ function OnlinePanel({ ) : null} {data && !data.noSites && !data.results.length ? (

      - 没搜到(用了:{data.keywords.join("、") || "无"})。可以换个关键词,或去掉季集限制再试。 + 没有找到字幕(搜索词:{data.keywords.join("、") || "无"})。换个关键词,或去掉季集限制再试。

      ) : null} {data?.results.length ? ( From 9c7ce109eaecfe38654a3b64ff27f1a74b4e3109 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Oct 2026 01:48:33 +0800 Subject: [PATCH 12/25] =?UTF-8?q?=E6=96=87=E6=A1=88(=E5=AA=92=E4=BD=93?= =?UTF-8?q?=E5=BA=93=E7=BB=84=E4=BB=B6):=E7=8A=B6=E6=80=81=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E6=8C=89=E6=9C=AF=E8=AF=AD=E8=A1=A8=E7=BB=9F=E4=B8=80?= =?UTF-8?q?,=E5=8E=BB=E6=8E=89=E5=80=99=E9=80=89/=E9=80=80=E5=BD=B9/?= =?UTF-8?q?=E5=B7=B2=E9=A2=84=E7=95=99=E7=AD=89=E5=86=85=E9=83=A8=E8=AF=B4?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- .../app/components/library/entity-dialogs.tsx | 6 ++-- .../components/library/identity-enrich.tsx | 2 +- .../library/identity-pool-sheet.tsx | 8 ++--- .../app/components/library/identity-views.tsx | 2 +- apps/web/app/components/library/labels.ts | 34 +++++++++---------- .../components/library/preferences-form.tsx | 4 +-- .../app/components/library/scrape-panel.tsx | 4 +-- apps/web/app/components/library/slot-card.tsx | 2 +- .../library/subscriptions-policy.tsx | 20 +++++------ .../library/subscriptions-slots.tsx | 14 ++++---- 10 files changed, 48 insertions(+), 48 deletions(-) diff --git a/apps/web/app/components/library/entity-dialogs.tsx b/apps/web/app/components/library/entity-dialogs.tsx index afd65d39..45a51fe0 100644 --- a/apps/web/app/components/library/entity-dialogs.tsx +++ b/apps/web/app/components/library/entity-dialogs.tsx @@ -102,13 +102,13 @@ export function IdentityDialog({ 修改外部身份 - 同类型的旧 TMDB 身份会退役,保留修订记录。加人工锁后,自动匹配和刷新不再改动。 + 旧的 TMDB 身份停用,修订记录保留。加人工锁后,自动匹配和刷新不会再改。
      {usable.length ? (
      -

      候选

      +

      已记录的身份

        {usable.map((c) => (
      • @@ -227,7 +227,7 @@ export function FieldLockDialog({ if (field === "year") { const n = Number(r.data.value); if (!Number.isInteger(n) || n < 1870 || n > 2200) { - setErrors({ value: "年份须是 1870–2200 的整数" }); + setErrors({ value: "年份填 1870–2200 之间的整数" }); return; } v = n; diff --git a/apps/web/app/components/library/identity-enrich.tsx b/apps/web/app/components/library/identity-enrich.tsx index 61e449ff..0978dd3f 100644 --- a/apps/web/app/components/library/identity-enrich.tsx +++ b/apps/web/app/components/library/identity-enrich.tsx @@ -150,7 +150,7 @@ export function EnrichSection({ item }: { item: IdentityPoolItem }) { {!out ? (

        TMDB - 查不到时,按标题搜索网页:读平台和百科网站,其它网站只用搜索摘要,抽取字段并附出处和置信度。先看建议,确认后才建作品。 + 查不到时,按标题搜索网页,从平台和百科页面抽取字段,附出处和置信度。确认后才建作品。

        ) : (
        diff --git a/apps/web/app/components/library/identity-pool-sheet.tsx b/apps/web/app/components/library/identity-pool-sheet.tsx index 7d5ad452..3551393e 100644 --- a/apps/web/app/components/library/identity-pool-sheet.tsx +++ b/apps/web/app/components/library/identity-pool-sheet.tsx @@ -160,12 +160,12 @@ function ManualAssign({ item }: { item: IdentityPoolItem }) { await dismiss.mutateAsync({ id: item.id }); toast.success("已忽略"); } catch (err) { - toast.error(errorMessage(err, "操作失败")); + toast.error(errorMessage(err, "忽略失败")); } }} > - 忽略此资源 + 忽略这个资源 ); @@ -229,7 +229,7 @@ export function PoolSheet({ item, onClose }: { item: IdentityPoolItem | null; on ) : null} - + {item.candidates.length ? (
          {item.candidates.map((c, i) => ( @@ -245,7 +245,7 @@ export function PoolSheet({ item, onClose }: { item: IdentityPoolItem | null; on

          {item.reason === "unmatched" ? "没有命中订阅或本地作品,未查询 TMDB。点「重新识别」会查一次 TMDB;新增相近别名时自动重试。" - : "没有候选。有新证据时自动重试。"} + : "没有候选作品。有新证据时自动重试。"}

          )} diff --git a/apps/web/app/components/library/identity-views.tsx b/apps/web/app/components/library/identity-views.tsx index 7f052abb..8fd7d37b 100644 --- a/apps/web/app/components/library/identity-views.tsx +++ b/apps/web/app/components/library/identity-views.tsx @@ -73,7 +73,7 @@ export function PoolTab() { }, { key: "cands", - header: "候选", + header: "候选作品", hideBelow: "sm", align: "right", cell: (r) => {r.candidates.length}, diff --git a/apps/web/app/components/library/labels.ts b/apps/web/app/components/library/labels.ts index 958ce731..8dd72f3c 100644 --- a/apps/web/app/components/library/labels.ts +++ b/apps/web/app/components/library/labels.ts @@ -43,12 +43,12 @@ export const ENTITY_STATUS: Record = { /** Slot 状态(REQ §6.1):每个状态有明确文本,不只靠颜色 */ export const SLOT_STATUS: Record = { empty: { label: "空缺", tone: "neutral" }, - reserved: { label: "已预留", tone: "neutral" }, + reserved: { label: "准备下载", tone: "neutral" }, downloading: { label: "下载中", tone: "neutral" }, verifying: { label: "核验中", tone: "neutral" }, satisfied: { label: "已满足", tone: "success" }, upgrade_pending: { label: "等待升级", tone: "warning" }, - attention: { label: "需处理", tone: "destructive" }, + attention: { label: "需要处理", tone: "destructive" }, }; export const SLOT_STATUS_ORDER: SlotStatus[] = [ "attention", @@ -64,7 +64,7 @@ export const IDENTITY_STATUS: Record = { accepted: { label: "已确认", tone: "success" }, proposed: { label: "待确认", tone: "warning" }, conflicted: { label: "冲突", tone: "destructive" }, - retired: { label: "已退役", tone: "neutral" }, + retired: { label: "已停用", tone: "neutral" }, }; export const EVIDENCE_LEVEL: Record = { @@ -100,7 +100,7 @@ export const LOCATION_KIND: Record = { }; export const DOWNLOAD_STATUS: Record = { - queued: { label: "排队", tone: "neutral" }, + queued: { label: "排队中", tone: "neutral" }, submitting: { label: "提交中", tone: "neutral" }, downloading: { label: "下载中", tone: "neutral" }, paused: { label: "已暂停", tone: "warning" }, @@ -110,15 +110,15 @@ export const DOWNLOAD_STATUS: Record = { imported: { label: "已整理", tone: "success" }, failed: { label: "失败", tone: "destructive" }, removed: { label: "已移除", tone: "neutral" }, - missing: { label: "下载器中缺失", tone: "warning" }, + missing: { label: "下载器里没有", tone: "warning" }, }; export const ORG_STATUS: Record = { - queued: { label: "排队", tone: "neutral" }, + queued: { label: "排队中", tone: "neutral" }, running: { label: "整理中", tone: "neutral" }, - succeeded: { label: "核验通过", tone: "success" }, - partial: { label: "部分成功", tone: "warning" }, - failed: { label: "失败", tone: "destructive" }, + succeeded: { label: "已整理", tone: "success" }, + partial: { label: "部分整理", tone: "warning" }, + failed: { label: "整理失败", tone: "destructive" }, cancelled: { label: "已取消", tone: "neutral" }, }; @@ -127,8 +127,8 @@ export const CANDIDATE_DECISION: Record = { upgrade: { label: "可升级", tone: "success" }, reject: { label: "拒绝", tone: "destructive" }, unknown_field: { label: "字段未知", tone: "warning" }, - skipped: { label: "跳过", tone: "neutral" }, - deferred: { label: "延后:已有计划在途", tone: "warning" }, + skipped: { label: "已跳过", tone: "neutral" }, + deferred: { label: "延后:已有下载在进行", tone: "warning" }, }; export const DECISION_KIND: Record = { @@ -141,7 +141,7 @@ export const DECISION_KIND: Record = { space_insufficient: "空间不足", approval_required: "需要审批", metadata_conflict: "元数据冲突", - source_unreachable: "来源不可达", + source_unreachable: "来源连不上", downloader_offline: "下载器连不上", cache_volume_unknown: "下载目录读不到容量", other: "其他", @@ -163,7 +163,7 @@ export const SEVERITY: Record = { }; export const POOL_REASON: Record = { - ambiguous: { label: "多个候选", tone: "warning" }, + ambiguous: { label: "匹配到多部作品", tone: "warning" }, no_match: { label: "没有匹配", tone: "neutral" }, proposed: { label: "待确认", tone: "warning" }, lookup_failed: { label: "查询失败", tone: "destructive" }, @@ -207,7 +207,7 @@ export const POLICY_STATUS: Record = { export const SUB_MODE: Record = { once: "单次收录", continuous: "持续追踪" }; export const SEASON_SCOPE: Record = { current_season: "仅本季", - following_seasons: "接续后续季", + following_seasons: "包括后续季", }; export const PRIORITY: Record = { immediate: "立即", @@ -336,9 +336,9 @@ export const opts = (rec: Record`)→ 中文;不认识的原样显示 */ const SLOT_REASON: Record = { "blocked:no_main_video": "没有可整理的主视频", - "deferred:slot_busy": "已有计划在途,等它结束再评估", - verified: "已核验整理", - verified_partial: "部分核验整理", + "deferred:slot_busy": "已有下载在进行,结束后再评估", + verified: "已整理", + verified_partial: "部分整理", hard_conditions_met: "硬条件全部满足", }; export function slotReasonText(raw: string): string { diff --git a/apps/web/app/components/library/preferences-form.tsx b/apps/web/app/components/library/preferences-form.tsx index 4e64c881..0f714cfa 100644 --- a/apps/web/app/components/library/preferences-form.tsx +++ b/apps/web/app/components/library/preferences-form.tsx @@ -108,8 +108,8 @@ export function PreferenceFormDialog({ {tag ? "编辑偏好" : "添加偏好"} {tag - ? "修改作用范围或规则会留下旧语义的抑制记录,智能体不会按旧证据加回。" - : "手动添加等同对话确认。之前取消过的偏好,重新添加会解除抑制。"} + ? "改了作用范围或规则后,智能体不会再按旧证据把它加回来。" + : "手动添加和在对话里确认效果相同。之前取消过的偏好,重新添加后恢复生效。"} diff --git a/apps/web/app/components/library/scrape-panel.tsx b/apps/web/app/components/library/scrape-panel.tsx index 2e18a13a..f49ecbcb 100644 --- a/apps/web/app/components/library/scrape-panel.tsx +++ b/apps/web/app/components/library/scrape-panel.tsx @@ -26,7 +26,7 @@ export const SCRAPE_STATUS: Record = { written: { label: "已写入", tone: "success" }, - skipped: { label: "跳过", tone: "neutral" }, + skipped: { label: "已跳过", tone: "neutral" }, failed: { label: "失败", tone: "destructive" }, }; @@ -144,7 +144,7 @@ export function ScrapePanel({ detail }: { detail: LibraryDetail }) { scrape.mutateAsync(mode ? { mode } : {}).then( (r) => toast.success(r.deduplicated ? "已在排队" : "已提交刮削"), (err) => { - toast.error(errorMessage(err, "刮削没有提交")); + toast.error(errorMessage(err, "刮削提交失败")); throw err; }, ); diff --git a/apps/web/app/components/library/slot-card.tsx b/apps/web/app/components/library/slot-card.tsx index 68e2495a..f995bba3 100644 --- a/apps/web/app/components/library/slot-card.tsx +++ b/apps/web/app/components/library/slot-card.tsx @@ -78,7 +78,7 @@ export function SlotCard({ {latest.length ? (
          -

          最近候选及选用理由(每个资源最近一次评估)

          +

          最近评估的资源和理由(每个资源只列最近一次)

            {latest.map((c) => (
          • diff --git a/apps/web/app/components/library/subscriptions-policy.tsx b/apps/web/app/components/library/subscriptions-policy.tsx index e108c9d2..9d3983d4 100644 --- a/apps/web/app/components/library/subscriptions-policy.tsx +++ b/apps/web/app/components/library/subscriptions-policy.tsx @@ -53,7 +53,7 @@ const KIND_OPTS: { value: string; label: string }[] = SubscriptionMediaKind.opti })); export const PUBLISH_NOTICE = - "发布后,作用范围内的下载、追更和整理按此策略自动执行,不再逐次审批。升级替换、媒体清理和下载缓存清理仍需单独审批。旧的已发布版本被替代,授权随之失效。"; + "发布后,作用范围内的下载、追更和整理按这个策略自动执行,不再逐次审批;升级替换、媒体清理和下载缓存清理仍要单独审批。上一个已发布版本的授权随之失效。"; type TemplateDraft = { key: string; name: string; rules: SlotRulesDraft; open: boolean }; type PolicyDraft = { @@ -164,7 +164,7 @@ function buildPolicy(d: PolicyDraft): const strict = AcquisitionPolicyRules.strict().safeParse(rules); if (!strict.success && !Object.keys(errors).length) { const i = strict.error.issues[0]; - errors.rules = `规则结构不合法:${i?.path.join(".")} ${i?.message}`; + errors.rules = `规则格式有误:${i?.path.join(".")} ${i?.message}`; } if (Object.keys(errors).length || !meta.success) return { ok: false, errors }; return { @@ -308,7 +308,7 @@ function PolicyGroup({ ) : ( ) } @@ -449,7 +449,7 @@ function PolicyDetailSheet({ > {!pv?.success ? (

            - 这个草案还没有预览。先预览可以看到影响范围。 + 还没有预览。预览后能看到影响范围。

            ) : null} @@ -458,14 +458,14 @@ function PolicyDetailSheet({ <> {rev.status === "superseded" || rev.status === "rolled_back" ? ( - 回滚到此版本 + 回滚到这个版本 } title={`回滚到 r${rev.revision}?`} @@ -538,7 +538,7 @@ function PreviewBlock({ value }: { value: JsonObject | null }) { if (!value) return (

            - 还没有预览。预览用最近已识别的资源评估模板,不会执行任何操作。 + 还没有预览。预览用最近识别的资源试算模板,不会下载或整理。

            ); const p = PolicyPreview.safeParse(value); @@ -565,7 +565,7 @@ function PreviewBlock({ value }: { value: JsonObject | null }) { ))}

          - 计算于 {formatDateTime(v.evaluatedAt)};接受/排除按"资源 × 模板"计数。 + 计算于 {formatDateTime(v.evaluatedAt)};接受 / 排除按「资源 × 模板」计数。

          {v.samples.length ? (
            @@ -586,7 +586,7 @@ function PreviewBlock({ value }: { value: JsonObject | null }) { ))}
          ) : ( -

          没有可用于回放的已识别资源。

          +

          没有可回放的已识别资源。

          )}
        ); @@ -659,7 +659,7 @@ function PolicyEditorSheet({ open onOpenChange={(o) => !o && !pending && onClose()} width="lg" - title={editId ? `编辑草案 r${base?.revision}` : base ? `基于 r${base.revision} 新建草案` : "新建收录策略草案"} + title={editId ? `编辑草案 r${base?.revision}` : base ? `从 r${base.revision} 新建草案` : "新建收录策略草案"} description="保存为草案,发布后才生效。" footer={ <> diff --git a/apps/web/app/components/library/subscriptions-slots.tsx b/apps/web/app/components/library/subscriptions-slots.tsx index 8fc362c1..63d1d712 100644 --- a/apps/web/app/components/library/subscriptions-slots.tsx +++ b/apps/web/app/components/library/subscriptions-slots.tsx @@ -66,7 +66,7 @@ export function SlotsPanel({ plain icon={Target} title="还没有收录版本" - description="没有时按已发布收录策略的默认模板创建,也可以手动新建。" + description="订阅时按已发布收录策略的默认模板自动创建,也可以手动新建。" className="py-8" /> ) : ( @@ -80,7 +80,7 @@ export function SlotsPanel({ {[s.currentAsset.resolution, s.currentAsset.sourceType, s.currentAsset.releaseGroup] .filter(Boolean) - .join(" · ") || "交付特征未知"} + .join(" · ") || "规格未知"} {s.currentAsset.sizeBytes ? ` · ${formatBytes(s.currentAsset.sizeBytes, 1)}` : ""} {s.currentAsset.status !== "active" ? ` · ${s.currentAsset.status}` : ""} @@ -140,7 +140,7 @@ function SlotEditorSheet({ const errs: Record = {}; if (!meta.success) for (const i of meta.error.issues) errs[String(i.path[0])] ??= i.message; if (!rules.ok) Object.assign(errs, rules.errors); - if (requireDistinct && !distinctGroup.trim()) errs.distinctGroup = "要求独立文件时须填写分组"; + if (requireDistinct && !distinctGroup.trim()) errs.distinctGroup = "要求独立文件时要填组合分组"; setErrors(errs); if (Object.keys(errs).length || !meta.success || !rules.ok) return; const common = { @@ -180,7 +180,7 @@ function SlotEditorSheet({ width="lg" title={slot ? `编辑收录版本 ${slot.name}` : "新建收录版本"} description={ - slot ? `当前 r${slot.revision}。保存时若已被修改,会提示刷新。` : "不满足硬条件的资源不收;软偏好决定先收哪个。" + slot ? `当前 r${slot.revision}。保存时如果已被别处修改,会提示刷新。` : "不满足硬条件的资源不收;软偏好决定先收哪个。" } footer={ <> @@ -195,7 +195,7 @@ function SlotEditorSheet({ >

        要求独立文件

        -

        同一个文件不能同时满足两个目标

        +

        同一个文件不能同时满足同组的两个收录版本

        From cfc9793dcdc121fe48641a32f39a6cc2e5ceb219 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Oct 2026 01:48:33 +0800 Subject: [PATCH 13/25] =?UTF-8?q?=E6=96=87=E6=A1=88(=E8=AE=A2=E9=98=85/?= =?UTF-8?q?=E5=8F=91=E7=8E=B0/=E9=9B=86=E6=95=B0=E6=98=A0=E5=B0=84):?= =?UTF-8?q?=E5=9C=A8=E9=80=94=E6=94=B9=E4=B8=8B=E8=BD=BD=E4=B8=AD=E3=80=81?= =?UTF-8?q?=E8=AE=A1=E5=88=92=E6=94=B9=E4=B8=8B=E8=BD=BD,=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E5=86=99=E6=B8=85=E5=8A=A8=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- apps/web/app/components/discover/explore-view.tsx | 2 +- .../web/app/components/discover/recommend-view.tsx | 2 +- .../app/components/episode-mapping/decision.tsx | 14 +++++++------- .../episode-mapping/subscription-summary.tsx | 2 +- apps/web/app/components/subscriptions/actions.tsx | 6 +++--- .../components/subscriptions/coverage-matrix.tsx | 6 +++--- .../web/app/components/subscriptions/model.test.ts | 4 ++-- apps/web/app/components/subscriptions/model.ts | 14 +++++++------- .../app/components/subscriptions/overview-item.tsx | 6 +++--- .../app/components/subscriptions/slot-panel.tsx | 2 +- .../components/subscriptions/subscription-form.tsx | 10 +++++----- 11 files changed, 34 insertions(+), 34 deletions(-) diff --git a/apps/web/app/components/discover/explore-view.tsx b/apps/web/app/components/discover/explore-view.tsx index d83f8f2c..6e0be6fb 100644 --- a/apps/web/app/components/discover/explore-view.tsx +++ b/apps/web/app/components/discover/explore-view.tsx @@ -271,7 +271,7 @@ function ExploreResults({ s }: { s: ExploreState }) { if (!items.length) return ( ); diff --git a/apps/web/app/components/discover/recommend-view.tsx b/apps/web/app/components/discover/recommend-view.tsx index b8c21738..9ac0c20d 100644 --- a/apps/web/app/components/discover/recommend-view.tsx +++ b/apps/web/app/components/discover/recommend-view.tsx @@ -83,7 +83,7 @@ function Section({ section, actions }: { section: RecommendSection; actions: Ret
      ) : q.isSuccess && items.length === 0 ? (

      - 这里暂时没有条目。 + 这一栏暂时没有作品。

      ) : (
        diff --git a/apps/web/app/components/episode-mapping/decision.tsx b/apps/web/app/components/episode-mapping/decision.tsx index f9ab24df..658aa50f 100644 --- a/apps/web/app/components/episode-mapping/decision.tsx +++ b/apps/web/app/components/episode-mapping/decision.tsx @@ -213,7 +213,7 @@ function IgnoreButton({ d, view }: { d: Decision; view: EpisodeDecisionView }) { .then( () => toast.success(withRule ? "已忽略,并建了忽略规则" : "已忽略"), (err) => { - toast.error(errorMessage(err, "操作失败")); + toast.error(errorMessage(err, "忽略失败")); throw err; }, ) @@ -269,12 +269,12 @@ export function EpisodeDecision({ d, open }: { d: Decision; open: boolean }) { trigger={ } - title="按智能体建议的规则处理?" + title="按智能体建议建规则?" description={`${view.confirmAgent.description ?? ""}。规则在作品页的集数映射里可以再改。`} - confirmLabel="确认" + confirmLabel="建规则并重算" onConfirm={() => resolve.mutateAsync({ id: d.id, optionKey: view.confirmAgent!.key }).then( () => toast.success("已建规则并重算"), @@ -291,12 +291,12 @@ export function EpisodeDecision({ d, open }: { d: Decision; open: boolean }) { trigger={ } - title="按建议规则处理?" + title="按建议建规则?" description={`${view.confirm.description ?? ""}。规则在作品页的集数映射里可以再改。`} - confirmLabel="确认" + confirmLabel="建规则并重算" onConfirm={() => resolve.mutateAsync({ id: d.id, optionKey: view.confirm!.key }).then( () => toast.success("已建规则并重算"), diff --git a/apps/web/app/components/episode-mapping/subscription-summary.tsx b/apps/web/app/components/episode-mapping/subscription-summary.tsx index 96ecc58e..b3e72faa 100644 --- a/apps/web/app/components/episode-mapping/subscription-summary.tsx +++ b/apps/web/app/components/episode-mapping/subscription-summary.tsx @@ -32,7 +32,7 @@ export function SubscriptionMappingSummary({ return ( reset.mutateAsync(id).then((r) => { const kept = r.keptPlans.length ? `,${r.keptPlans.length} 个仍在下载的保留` : ""; - toast.success(`已重置:释放 ${r.releasedPlans.length} 个计划${kept}`); + toast.success(`已重置:取消 ${r.releasedPlans.length} 个下载${kept}`); }), remove: (ids: string[]) => batch.mutateAsync({ ids, action: "delete" }).then((r) => { @@ -63,7 +63,7 @@ export function useSubscriptionOps() { const ok = r.results.filter((x) => x.ok).length; const label = { search: "开始搜索", pause: "暂停", resume: "恢复" }[a]; if (ok === ids.length) toast.success(`${ok} 个订阅已${label}`); - else toast.warning(`${ok}/${ids.length} 个已${label};${r.results.find((x) => !x.ok)?.error ?? ""}`); + else toast.warning(`${ok}/${ids.length} 个已${label};${r.results.find((x) => !x.ok)?.error ?? ""}`); }, onError: (e) => toast.error(errorMessage(e)), }, @@ -186,7 +186,7 @@ export function SubscriptionMenu({ open={confirmReset} onOpenChange={setConfirmReset} title="重置这个订阅的进度?" - description="取消还没提交、或下载任务已不在的计划,清掉延后记录,然后重新评估。仍在下载的任务、已整理文件和总集数保留。" + description="取消还没提交或下载任务已不在的下载,清掉延后记录,再重新评估。仍在下载的任务、已整理文件和总集数保留。" confirmLabel="重置" onConfirm={() => ops.reset(sub.id).catch((e) => { diff --git a/apps/web/app/components/subscriptions/coverage-matrix.tsx b/apps/web/app/components/subscriptions/coverage-matrix.tsx index 3e5e2409..46c29023 100644 --- a/apps/web/app/components/subscriptions/coverage-matrix.tsx +++ b/apps/web/app/components/subscriptions/coverage-matrix.tsx @@ -302,17 +302,17 @@ export function CellSheet({ { label: "下载", value: c.planId - ? `${PLAN_STAGE[c.planStage ?? ""] ?? c.planStage ?? "在途"} · ${c.planId.slice(0, 8)}` + ? `${PLAN_STAGE[c.planStage ?? ""] ?? c.planStage ?? "下载中"} · ${c.planId.slice(0, 8)}` : null, }, { label: "原因", value: c.reason ? (SKIP_REASON[c.reason] ?? c.reason) : null }, - { label: "延后", value: c.deferred ? "有资源在等待重评(预算或自动补全关闭)" : null }, + { label: "延后", value: c.deferred ? "有资源等待重新评估(预算用完或自动补全已关)" : null }, ]} /> {fillable ? (

        - 手动补全不受自动补全开关和补全预算限制,仍受全局每日预算与缓存卷阈值约束。 + 手动补全不受自动补全开关和补全预算限制,但仍受每日总预算和下载盘阈值限制。

        ) : null} {sel.row.slotId === null && c.state === "missing" ? ( diff --git a/apps/web/app/components/subscriptions/model.test.ts b/apps/web/app/components/subscriptions/model.test.ts index 29c76d8b..65ebb9c7 100644 --- a/apps/web/app/components/subscriptions/model.test.ts +++ b/apps/web/app/components/subscriptions/model.test.ts @@ -59,9 +59,9 @@ describe("订阅总览文案", () => { expect(seasonLabel({ mediaKind: "series", seasonNumber: 1, seasonScope: "current_season" })).toBe("S1"); expect(seasonLabel({ mediaKind: "movie", seasonNumber: null, seasonScope: "current_season" })).toBeNull(); }); - it("版本徽标按 洗版 → 在途 → 缺 → 已齐 判定", () => { + it("版本徽标按 洗版 → 下载中 → 缺 → 已齐 判定", () => { expect(slotBadge(slot({ upgrading: true, minPri: 97, targetPri: 100, missing: 2 })).label).toBe("洗版中 97→100"); - expect(slotBadge(slot({ inflight: 2, missing: 3 }))).toEqual({ label: "在途 2", tone: "neutral" }); + expect(slotBadge(slot({ inflight: 2, missing: 3 }))).toEqual({ label: "下载中 2", tone: "neutral" }); expect(slotBadge(slot({ missing: 3 })).tone).toBe("destructive"); expect(slotBadge(slot({ have: 10, total: 10 }))).toEqual({ label: "已齐", tone: "success" }); expect(slotBadge(slot({})).label).toBe("未开始"); diff --git a/apps/web/app/components/subscriptions/model.ts b/apps/web/app/components/subscriptions/model.ts index 44bfdc9e..b76b7f20 100644 --- a/apps/web/app/components/subscriptions/model.ts +++ b/apps/web/app/components/subscriptions/model.ts @@ -27,7 +27,7 @@ export const GROUP_LABEL: Record = { following: "追更中", waiting: "待搜索", completed: "已完成", - paused: "暂停", + paused: "已暂停", pending: "待定", }; @@ -62,7 +62,7 @@ export function slotBadge(s: SubscriptionSlotSummary): ToneLabel { label: s.minPri !== null && s.targetPri !== null ? `洗版中 ${s.minPri}→${s.targetPri}` : "洗版中", tone: "warning", }; - if (s.inflight > 0) return { label: `在途 ${s.inflight}`, tone: "neutral" }; + if (s.inflight > 0) return { label: `下载中 ${s.inflight}`, tone: "neutral" }; if (s.missing > 0) return { label: `缺 ${s.missing}`, tone: "destructive" }; if (s.total !== null && s.total > 0 && s.have >= s.total) return { label: "已齐", tone: "success" }; return { label: s.have > 0 ? `已整理 ${s.have}` : "未开始", tone: "neutral" }; @@ -79,11 +79,11 @@ export const AUTOFILL_LABEL: Record = { inherit: "跟随 export const AUTOFILL_SOURCE: Record<"subscription" | "rule_group" | "default", string> = { subscription: "订阅设置", rule_group: "规则组", - default: "系统缺省", + default: "系统默认", }; export function autoFillText(a: SubscriptionOverviewItem["autoFill"]): string { - return `自动补全${a.enabled ? "开" : "关"}${a.mode === "inherit" ? `(${AUTOFILL_SOURCE[a.source]})` : ""}`; + return `自动补全${a.enabled ? "开" : "关"}${a.mode === "inherit" ? `(${AUTOFILL_SOURCE[a.source]})` : ""}`; } // ── 覆盖矩阵 ── @@ -123,7 +123,7 @@ export function cellKind(c: CoverageCell): CellKind { export const CELL_LABEL: Record = { have: "已整理", upgradable: "可升级", - inflight: "在途", + inflight: "下载中", deferred: "延后", missing: "缺失", suspected: "疑似缺失", @@ -131,12 +131,12 @@ export const CELL_LABEL: Record = { skipped: "不需要", }; -export const PLAN_STAGE: Record = { reserved: "已预留", downloading: "下载中", verifying: "核验中" }; +export const PLAN_STAGE: Record = { reserved: "准备下载", downloading: "下载中", verifying: "核验中" }; export const SKIP_REASON: Record = { out_of_range: "集范围外", not_aired: "未播出", - not_required: "该收录版本不要求这个内容版本", + not_required: "收录版本不要求这个内容版本", suspected: "总集数未知,按已见最高集推断", deferred: "有延后的资源", }; diff --git a/apps/web/app/components/subscriptions/overview-item.tsx b/apps/web/app/components/subscriptions/overview-item.tsx index 0c682c30..61633b9f 100644 --- a/apps/web/app/components/subscriptions/overview-item.tsx +++ b/apps/web/app/components/subscriptions/overview-item.tsx @@ -25,7 +25,7 @@ export const itemTitle = (x: Item) => x.mediaEntityTitle ?? x.titleQuery ?? "未 function Flags({ x }: { x: Item }) { return ( <> - {x.group === "paused" ? 暂停 : null} + {x.group === "paused" ? 已暂停 : null} {x.group === "pending" ? ( {x.mediaEntityId ? "待定" : "不在媒体库"} ) : null} @@ -45,7 +45,7 @@ export function ProgressBlock({ x, className }: { x: Pick {progressText(x.progress)} {x.progress.inflight > 0 ? ( - · 在途 {x.progress.inflight} + · 下载中 {x.progress.inflight} ) : null} {unknown ? 持续追踪 : null} @@ -57,7 +57,7 @@ export function ProgressBlock({ x, className }: { x: Pick {unknown ? null : ( <> diff --git a/apps/web/app/components/subscriptions/slot-panel.tsx b/apps/web/app/components/subscriptions/slot-panel.tsx index 59d77a94..7ae8b474 100644 --- a/apps/web/app/components/subscriptions/slot-panel.tsx +++ b/apps/web/app/components/subscriptions/slot-panel.tsx @@ -112,7 +112,7 @@ export function SlotPanel({ 已整理 {r.summary.have} 缺 {r.summary.missing} - 在途 {r.summary.inflight} + 下载中 {r.summary.inflight} {i === 0 ? null : {r.autoFill ? "自动补全" : "只登记缺失"}}
    @@ -871,7 +871,7 @@ export function SubscriptionFormDialog({ id={f("tpl")} value={d.policyRevisionId} onChange={(v) => patch({ policyRevisionId: v })} - placeholder={defaultTpl ? `按已发布收录策略(${defaultTpl.title})` : "按已发布收录策略"} + placeholder={defaultTpl ? `按已发布收录策略(${defaultTpl.title})` : "按已发布收录策略"} options={templates.map((p) => ({ value: p.id, label: `${p.title} r${p.revision}:${tplNames(p.id).join(" → ") || "无模板"}`, @@ -972,7 +972,7 @@ export function AutoFillFields({

    - 预算只算自动补全的计划,超出的延到次日;同时受全局每日预算限制。 + 预算只算自动补全发起的下载,超出的延到第二天;也受每日总预算限制。

    ); From 7abc4001d601b092c2e00729e2b28c6fc60abf1d Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Oct 2026 01:48:33 +0800 Subject: [PATCH 14/25] =?UTF-8?q?=E6=96=87=E6=A1=88(=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=8F=B0=E8=B7=AF=E7=94=B1):=E5=8E=BB=E6=8E=89=E5=A4=8D?= =?UTF-8?q?=E8=BF=B0=E7=95=8C=E9=9D=A2=E7=9A=84=E8=AF=B4=E6=98=8E,?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E9=A1=B5=E4=B8=8D=E5=86=8D=E6=8F=90=E5=B7=B2?= =?UTF-8?q?=E5=8F=96=E6=B6=88=E7=9A=84=E5=BE=85=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- apps/web/app/routes/console/discover.tsx | 2 +- apps/web/app/routes/console/files.trash.tsx | 4 ++-- apps/web/app/routes/console/files.tsx | 4 ++-- apps/web/app/routes/console/layout.tsx | 2 +- apps/web/app/routes/console/library.tsx | 4 ++-- apps/web/app/routes/console/organize.$id.tsx | 6 +++--- apps/web/app/routes/console/settings.tsx | 4 ++-- apps/web/app/routes/console/sites.$siteId.tsx | 3 +-- apps/web/app/routes/console/sites.tsx | 8 ++++---- apps/web/app/routes/console/status.tsx | 4 ++-- apps/web/app/routes/console/subscriptions.$id.tsx | 4 ++-- apps/web/app/routes/console/tasks.$id.tsx | 4 ++-- apps/web/app/routes/login.tsx | 4 ++-- 13 files changed, 26 insertions(+), 27 deletions(-) diff --git a/apps/web/app/routes/console/discover.tsx b/apps/web/app/routes/console/discover.tsx index 6eba0786..d79237b0 100644 --- a/apps/web/app/routes/console/discover.tsx +++ b/apps/web/app/routes/console/discover.tsx @@ -28,7 +28,7 @@ export default function DiscoverPage() { return ( <> - + diff --git a/apps/web/app/routes/console/files.trash.tsx b/apps/web/app/routes/console/files.trash.tsx index ab4a3249..95f80e30 100644 --- a/apps/web/app/routes/console/files.trash.tsx +++ b/apps/web/app/routes/console/files.trash.tsx @@ -219,7 +219,7 @@ export default function FilesTrashPage() { if (!snap?.title) return —; const label = `${snap.title}${snap.contentUnit ? ` ${snap.contentUnit}` : ""}`; return ( - + {snap.mediaEntityId ? ( {label} @@ -227,7 +227,7 @@ export default function FilesTrashPage() { ) : ( label )} - 已退役 + 已移出媒体库 ); }, diff --git a/apps/web/app/routes/console/files.tsx b/apps/web/app/routes/console/files.tsx index 802b685e..9c7d78c8 100644 --- a/apps/web/app/routes/console/files.tsx +++ b/apps/web/app/routes/console/files.tsx @@ -721,7 +721,7 @@ export default function FilesPage() { key: "scrape", label: "刷新元数据", icon: RefreshCw, - disabled: selected.every((e) => e.asset) ? null : "只对已整理的文件", + disabled: selected.every((e) => e.asset) ? null : "只能刷新已整理的文件", onSelect: () => act("scrape", selected), }); items.push({ @@ -854,7 +854,7 @@ export default function FilesPage() { From c57411f9a5b1c33b69d9d6086ac3480ebd52a041 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Oct 2026 01:48:45 +0800 Subject: [PATCH 15/25] =?UTF-8?q?docs(copy):=20=E8=AE=BE=E7=BD=AE=E9=A1=B5?= =?UTF-8?q?=E8=B4=A6=E6=88=B7=E4=B8=8E=E6=99=BA=E8=83=BD=E4=BD=93=E5=88=86?= =?UTF-8?q?=E5=8C=BA=E6=96=87=E6=A1=88=E6=8C=89=20human-copy=20=E9=87=8D?= =?UTF-8?q?=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 改密码后的会话说法统一为「其他会话已退出」 - 模型服务向导里的 provider 统一写「提供方」 - 能力类别「提案」改「方案」;自动调查说明去掉「有界调查」 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- apps/web/app/components/settings/account.tsx | 10 +++++----- apps/web/app/components/settings/agent-tools.tsx | 4 ++-- apps/web/app/components/settings/agent.tsx | 2 +- apps/web/app/components/settings/pi-oauth.tsx | 2 +- apps/web/app/components/settings/pi-shared.tsx | 2 +- apps/web/app/components/settings/pi-wizard.tsx | 10 +++++----- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/web/app/components/settings/account.tsx b/apps/web/app/components/settings/account.tsx index 2fbaf3bc..0d8b5430 100644 --- a/apps/web/app/components/settings/account.tsx +++ b/apps/web/app/components/settings/account.tsx @@ -41,7 +41,7 @@ function PasswordBlock({ disabled }: { disabled: boolean }) { setErrors({}); try { const r = await change.mutateAsync({ currentPassword: form.currentPassword, newPassword: form.newPassword }); - toast.success(r.revokedSessions ? `密码已修改,注销了其它 ${r.revokedSessions} 个会话` : "密码已修改"); + toast.success(r.revokedSessions ? `密码已修改,其他 ${r.revokedSessions} 个会话已退出` : "密码已修改"); setForm(EMPTY_PWD); } catch (err) { const msg = errorMessage(err); @@ -58,7 +58,7 @@ function PasswordBlock({ disabled }: { disabled: boolean }) { section="account" anchor="password" title="修改密码" - description="修改后其它设备上的会话全部注销,当前会话保留。" + description="其他设备上的会话会退出登录,当前会话保留。" dirty={dirty} > @@ -136,13 +136,13 @@ function SessionBlock() {
    当前会话
    {s?.expiresAt ? `到 ${formatDateTime(s.expiresAt)} 失效` : "未使用登录会话"}
    -
    其它会话
    -
    修改密码时全部注销
    +
    其他会话
    +
    修改密码时全部退出登录

    本机 Token

    - 只存在这个浏览器,有值时请求会带上它。{hasToken ? "已设置。" : "未设置。"} + 只保存在这个浏览器,有值时每个请求都会带上。{hasToken ? "已设置。" : "未设置。"}

    = { read: "只读", preview: "预览", - propose: "提案", + propose: "方案", approve: "审批", execute: "执行", }; @@ -49,7 +49,7 @@ const RISK: Record = { }; const AUTH: Record = { - read_only: { label: "无需授权", tone: "neutral", hint: "只读或预览,不落变更,Key 有对应 scope 即可调用" }, + read_only: { label: "无需授权", tone: "neutral", hint: "只读或预览,不改数据;Key 有对应 scope 就能调用" }, granted: { label: "有持续授权", tone: "success", hint: "智能体、MCP、API Key 调用按持续授权放行" }, needs_approval: { label: "需审批", diff --git a/apps/web/app/components/settings/agent.tsx b/apps/web/app/components/settings/agent.tsx index fbae8051..29adea51 100644 --- a/apps/web/app/components/settings/agent.tsx +++ b/apps/web/app/components/settings/agent.tsx @@ -105,7 +105,7 @@ export const BUDGET_CARD: CardSpec = { export const INVESTIGATE_CARD: CardSpec = { anchor: "investigate", title: "自动处理", - description: "整理时识别不到作品就叫智能体识别;下载失败、集数对不上等问题出现时,自动发起一次有界调查。", + description: "整理时识别不到作品就叫智能体识别;下载失败、集数对不上时自动发起一次调查,受下面的上限约束。", fields: [ { name: "agent.autoIdentifyOrganize", diff --git a/apps/web/app/components/settings/pi-oauth.tsx b/apps/web/app/components/settings/pi-oauth.tsx index d2b10293..594cbfef 100644 --- a/apps/web/app/components/settings/pi-oauth.tsx +++ b/apps/web/app/components/settings/pi-oauth.tsx @@ -102,7 +102,7 @@ export function PiOAuthLogin({ abortRef.current = ac; while (!ac.signal.aborted) { if (Date.now() > new Date(s.expiresAt).getTime()) { - finish("expired", "授权已超时。"); + finish("expired", "授权已超时"); return; } try { diff --git a/apps/web/app/components/settings/pi-shared.tsx b/apps/web/app/components/settings/pi-shared.tsx index 01cd7e4f..faaeb49b 100644 --- a/apps/web/app/components/settings/pi-shared.tsx +++ b/apps/web/app/components/settings/pi-shared.tsx @@ -258,7 +258,7 @@ export function ModelFetchBar({ : remote ? `来源:远程 · ${formatRelative(data?.fetchedAt)}更新` : unsupported - ? "来源:静态(这个 provider 没有模型列表接口)" + ? "来源:静态(这个提供方没有模型列表接口)" : data ? "来源:静态" : null} diff --git a/apps/web/app/components/settings/pi-wizard.tsx b/apps/web/app/components/settings/pi-wizard.tsx index 33888752..12d4358b 100644 --- a/apps/web/app/components/settings/pi-wizard.tsx +++ b/apps/web/app/components/settings/pi-wizard.tsx @@ -70,7 +70,7 @@ export function PiUpstreamWizard({ open, onClose }: { open: boolean; onClose: () : (created?.name ?? ""); const description = step === "provider" - ? "第 1 步,共 3 步:选 provider" + ? "第 1 步,共 3 步:选提供方" : step === "config" ? "第 2 步,共 3 步:凭证与模型" : created?.authKind === "oauth" @@ -116,7 +116,7 @@ function ProviderPicker({ onPick }: { onPick: (p: PiProviderInfo) => void }) {
    setFilter(e.target.value)} @@ -148,7 +148,7 @@ function ProviderPicker({ onPick }: { onPick: (p: PiProviderInfo) => void }) { ); })} - {list.length === 0 ?

    没有匹配的 provider。

    : null} + {list.length === 0 ?

    没有匹配的提供方。

    : null}
    ); } @@ -252,7 +252,7 @@ function ConfigStep({
    {provider.note && !custom ?

    {provider.note}

    : null}
    @@ -356,7 +356,7 @@ function ConfigStep({ htmlFor="pw-model" error={errors.defaultModel} required={custom} - hint={custom ? "端点上的模型 id" : "留空用 provider 的默认模型"} + hint={custom ? "端点上的模型 id" : "留空用提供方的默认模型"} className="sm:col-span-2" > Date: Wed, 7 Oct 2026 01:48:45 +0800 Subject: [PATCH 16/25] =?UTF-8?q?docs(copy):=20=E8=AE=BE=E7=BD=AE=E9=A1=B5?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=E3=80=81=E5=AA=92=E4=BD=93=E5=BA=93=E3=80=81?= =?UTF-8?q?=E6=95=B4=E7=90=86=E3=80=81=E7=B3=BB=E7=BB=9F=E5=88=86=E5=8C=BA?= =?UTF-8?q?=E6=96=87=E6=A1=88=E6=8C=89=20human-copy=20=E9=87=8D=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 下载器说明去掉「接管」,选下载器顺序写成一句;缓存盘统一为下载盘 - 「凭据」统一为「凭证」;去掉说明里的「同 MP」「待处理」等旧称 - 整理来源的基线、存量说法改成用户能懂的话,确认按钮写动作 - 数据保留里「站点快照」「待识别条目」按术语表改名 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- apps/web/app/components/settings/acquire.tsx | 8 +++---- .../components/settings/browser-providers.tsx | 8 +++---- apps/web/app/components/settings/cache.tsx | 2 +- .../web/app/components/settings/instances.tsx | 6 ++--- apps/web/app/components/settings/library.tsx | 2 +- apps/web/app/components/settings/metadata.tsx | 2 +- apps/web/app/components/settings/naming.tsx | 2 +- .../components/settings/organize-sources.tsx | 22 +++++++++---------- apps/web/app/components/settings/organize.tsx | 2 +- apps/web/app/components/settings/sites.tsx | 2 +- apps/web/app/components/settings/storages.tsx | 4 ++-- apps/web/app/components/settings/system.tsx | 10 ++++----- 12 files changed, 35 insertions(+), 35 deletions(-) diff --git a/apps/web/app/components/settings/acquire.tsx b/apps/web/app/components/settings/acquire.tsx index 4359f53c..4056382f 100644 --- a/apps/web/app/components/settings/acquire.tsx +++ b/apps/web/app/components/settings/acquire.tsx @@ -88,20 +88,20 @@ function SearchSitesBlock() { export const IDENTITY_CARD: CardSpec = { anchor: "identity", title: "资源识别", - description: "默认只在资源命中订阅、作品别名或新作品规则,或你手动识别和搜索时查 TMDB;其余放进未识别,不进待处理。", + description: "默认只在资源命中订阅、作品别名或新作品规则,或你手动识别和搜索时查 TMDB;其余资源留在未识别。", fields: [ { name: "identity.eagerTmdbLookup", label: "每条新资源都查 TMDB", kind: "switch", - hint: "每条新资源都查一次 TMDB。RSS 量大时每天可能上千次请求,站点独有的短剧和综艺多半查不到", + hint: "RSS 量大时每天可能上千次请求;站点独有的短剧和综艺多半查不到", wide: true, }, { name: "identity.detailIdsPerSiteHour", label: "识别时每站每小时最多抓详情页", kind: "number", - hint: "标题认不准时,抓站点详情页取 IMDb、TMDB 或豆瓣 ID 再识别;只在搜索、订阅相关资源、补全和手动重新识别时抓,RSS 收进来的资源不抓。0 = 不抓", + hint: "标题认不准时,抓站点详情页取 IMDb、TMDB 或豆瓣 ID 再识别;只在搜索、命中订阅、补全和手动重新识别时抓,RSS 收进来的资源不抓。0 = 不抓", }, { name: "identity.autoEnrich", @@ -125,7 +125,7 @@ export const IDENTITY_CARD: CardSpec = { }, { name: "identity.enrichSnippetAllSites", - label: "也用其它网站的搜索摘要", + label: "也用其他网站的搜索摘要", kind: "switch", hint: "短剧多半只有聚合站收录。打开后,白名单以外网站的搜索摘要也用来抽年份、集数、主演、简介(只看搜索引擎给的摘要,不打开这些网页)", wide: true, diff --git a/apps/web/app/components/settings/browser-providers.tsx b/apps/web/app/components/settings/browser-providers.tsx index a906222b..764c8a72 100644 --- a/apps/web/app/components/settings/browser-providers.tsx +++ b/apps/web/app/components/settings/browser-providers.tsx @@ -50,7 +50,7 @@ function Verified({ p }: { p: BrowserProviderView }) { function CapabilityTable({ p }: { p: BrowserProviderView }) { const caps = p.capabilities; - if (!caps) return

    还没有探测过。点"测试连通"读取能力。

    ; + if (!caps) return

    还没有探测过。点「测试连通」读取能力。

    ; return (

    @@ -81,7 +81,7 @@ function CapabilityTable({ p }: { p: BrowserProviderView }) { })}

    - "已核验"只来自对真实实例的逐项核验记录(见运行手册),探测只能判断接口在不在。未核验的能力在原站视图里标出。 + 「已核验」只来自对真实实例的逐项核验记录(见运行手册),探测只能判断接口在不在。未核验的能力在原站视图里标出。

    ); @@ -124,7 +124,7 @@ export function BrowserProvidersBlock() { }, { key: "key", - header: "访问凭据", + header: "访问凭证", hideBelow: "md", cell: (r) => (r.hasAccessKey ? "已设置" : "未设置"), }, @@ -148,7 +148,7 @@ export function BrowserProvidersBlock() { const auth = (v.health?.detail as { error?: string } | undefined)?.error === "auth"; return { ok: v.health?.status === "ok", - message: auth ? "访问凭据不对" : v.health?.status === "degraded" ? "浏览器服务正在恢复" : "连不上", + message: auth ? "访问凭证不对" : v.health?.status === "degraded" ? "浏览器服务正在恢复" : "连不上", version: v.capabilities?.upstreamVersion ?? null, }; }} diff --git a/apps/web/app/components/settings/cache.tsx b/apps/web/app/components/settings/cache.tsx index abe188a5..fc2ad39e 100644 --- a/apps/web/app/components/settings/cache.tsx +++ b/apps/web/app/components/settings/cache.tsx @@ -279,7 +279,7 @@ export const TTL_CARD: CardSpec = { fields: [ ttlField("tmdbSearchDays", "TMDB 搜索与识别结果"), ttlField("tmdbDetailDays", "TMDB 详情"), - ttlField("tmdbMissDays", "TMDB 空结果", "搜索无结果、未识别时的负缓存"), + ttlField("tmdbMissDays", "TMDB 空结果", "搜索无结果或没识别出的结果也缓存,期内不重复查"), ttlField("imageDays", "TMDB 人物与图片"), ttlField("metaSearchDays", "豆瓣 / Bangumi 搜索"), ttlField("metaDetailDays", "豆瓣 / Bangumi 条目"), diff --git a/apps/web/app/components/settings/instances.tsx b/apps/web/app/components/settings/instances.tsx index 202b7086..e1f0c52f 100644 --- a/apps/web/app/components/settings/instances.tsx +++ b/apps/web/app/components/settings/instances.tsx @@ -118,7 +118,7 @@ export function DownloadersTable() { config={{ noun: "下载器实例", description: - "只接管带管理标签的任务。可添加多个下载器:订阅或手动下载指定了下载器就用指定的,否则用站点指定的,再否则用默认下载器。多个实例共用一块缓存盘时填相同的缓存卷标识,水位合并计算。点行编辑实例和路径映射。", + "只管理带管理标签的任务。选下载器的顺序:订阅或手动下载指定的、站点指定的、默认下载器。多个实例共用一块下载盘时填相同的缓存卷标识,水位合并计算。点行编辑实例和路径映射。", emptyHint: "添加 qBittorrent 后才能提交下载。", drawerExtra: (r) => , columns: [ @@ -180,7 +180,7 @@ export function DownloadersTable() { placeholder: "qbt_…", visible: (v) => v.authKind === "api_key", }, - { name: "managedTag", label: "管理标签", kind: "text", placeholder: "umt", hint: "只接管带此标签的任务" }, + { name: "managedTag", label: "管理标签", kind: "text", placeholder: "umt", hint: "只管理带这个标签的任务" }, { name: "cacheVolumeKey", label: "缓存卷标识", kind: "text", placeholder: "cache-1" }, { name: "hardPct", @@ -357,7 +357,7 @@ const MEDIA_KIND = [ { value: "series", label: "剧集" }, { value: "variety", label: "综艺" }, { value: "mixed", label: "混合" }, - { value: "other", label: "其它" }, + { value: "other", label: "其他" }, ]; /** 库根路径 ↔ 存储 + 路径:Alist 上的库根存成 /@alist/<存储 id>/ */ diff --git a/apps/web/app/components/settings/library.tsx b/apps/web/app/components/settings/library.tsx index df6f32ac..c43c7f5c 100644 --- a/apps/web/app/components/settings/library.tsx +++ b/apps/web/app/components/settings/library.tsx @@ -23,7 +23,7 @@ export function LibrarySection() { section="library" anchor="roots" title="媒体库目录" - description="整理把作品放进这些目录;媒体服务器扫的也是它们。" + description="整理后的作品放在这些目录里,媒体服务器也扫描这些目录。" plain > diff --git a/apps/web/app/components/settings/metadata.tsx b/apps/web/app/components/settings/metadata.tsx index 7da0f92f..659c9db7 100644 --- a/apps/web/app/components/settings/metadata.tsx +++ b/apps/web/app/components/settings/metadata.tsx @@ -69,7 +69,7 @@ export const LANGUAGE_CARD: CardSpec = { label: "语言回退链", kind: "text", placeholder: DEFAULT_LANGUAGES.join(", "), - hint: "标题和每集信息按顺序取第一个有值的语言,简介默认只取链里的中文;中文名取链中第一个中文值。只认简繁中文、英文和作品原语言(如日语作品的 ja-JP),其它语言跳过。逗号分隔,如 zh-CN, zh-SG", + hint: "逗号分隔,如 zh-CN, zh-SG。标题和每集信息取链上第一个有值的语言,简介默认只取中文。只认简繁中文、英文和作品原语言(如日语作品的 ja-JP),其他语言跳过", wide: true, }, { diff --git a/apps/web/app/components/settings/naming.tsx b/apps/web/app/components/settings/naming.tsx index 5a558a46..eab6e8eb 100644 --- a/apps/web/app/components/settings/naming.tsx +++ b/apps/web/app/components/settings/naming.tsx @@ -190,7 +190,7 @@ function RootNamingEditor({ root }: { root: LibraryRoot }) {
    {KINDS.map((k) => ( - {NAMING_KIND_LABEL[k]}:{saved[k] ? "媒体库目录覆盖" : "沿用全局"} + {NAMING_KIND_LABEL[k]}:{saved[k] ? "媒体库目录覆盖" : "沿用全局"} ))}
    diff --git a/apps/web/app/components/settings/organize-sources.tsx b/apps/web/app/components/settings/organize-sources.tsx index 4410f7e8..18d13d2c 100644 --- a/apps/web/app/components/settings/organize-sources.tsx +++ b/apps/web/app/components/settings/organize-sources.tsx @@ -59,7 +59,7 @@ export function ClassificationSection() { }) } > - 让智能体协助 + 问智能体 } > @@ -159,7 +159,7 @@ function SourceEditor({ initial, onClose }: { initial: OrganizeSourceView | null {initial ? "编辑整理来源" : "新增整理来源"} - 首次保存或更换来源目录先建立当前文件基线,只自动处理后续新增文件。历史文件请使用「整理存量」。 + 首次保存或更换来源目录时记下现有文件作为基线,之后只自动处理新增文件。已有文件用「整理存量」整理。
    @@ -316,7 +316,7 @@ function SourceEditor({ initial, onClose }: { initial: OrganizeSourceView | null }, { onSuccess: () => { - toast.success("来源已保存,当前存量不会自动整理"); + toast.success("来源已保存,已有文件不会自动整理"); onClose(); }, onError: (e) => toast.error(filesErrorText(e, "保存来源失败")), @@ -324,7 +324,7 @@ function SourceEditor({ initial, onClose }: { initial: OrganizeSourceView | null ); }} > - {save.isPending ? "保存并建立基线…" : "保存来源"} + {save.isPending ? "保存中…" : "保存来源"}
    @@ -360,7 +360,7 @@ export function OrganizeSourcesSection() { section="library" anchor="sources" title="整理来源" - description="每条配置绑定一个来源与一个媒体库目标。新增来源建立基线,历史存量由你预览后执行。" + description="每条配置把一个来源目录对应到一个媒体库目录。新增的来源只自动处理之后出现的文件,已有文件用「整理存量」预览后整理。" actions={ diff --git a/apps/web/app/components/settings/organize.tsx b/apps/web/app/components/settings/organize.tsx index abae5672..e816d5fe 100644 --- a/apps/web/app/components/settings/organize.tsx +++ b/apps/web/app/components/settings/organize.tsx @@ -61,7 +61,7 @@ export const SCRAPE_CARD: CardSpec = { name: "organize.scrapeAfterImport", label: "整理后自动刮削", kind: "switch", - hint: "只对认出作品(有 TMDB ID 或本地元数据)的整理排刮削;没刮削的在整理记录上标「未刮削」", + hint: "只刮削认出作品的整理(有 TMDB ID 或本地元数据);没刮削的整理记录标「未刮削」", }, { name: "organize.scrapeMode", diff --git a/apps/web/app/components/settings/sites.tsx b/apps/web/app/components/settings/sites.tsx index 4c13be28..5e12e1fd 100644 --- a/apps/web/app/components/settings/sites.tsx +++ b/apps/web/app/components/settings/sites.tsx @@ -94,7 +94,7 @@ export const NETWORK_CARD: CardSpec = { label: "User-Agent", kind: "text", placeholder: "跟随浏览器", - hint: "留空跟随浏览器(推荐),站点请求和浏览器用同一个 UA。填了则账号没单独填 UA 时都用它,和浏览器不一致", + hint: "留空时站点请求和浏览器用同一个 UA(推荐)。填写后,没单独设 UA 的账号都用这个值,与浏览器的 UA 不一致", wide: true, }, { diff --git a/apps/web/app/components/settings/storages.tsx b/apps/web/app/components/settings/storages.tsx index cdfabfda..08102db0 100644 --- a/apps/web/app/components/settings/storages.tsx +++ b/apps/web/app/components/settings/storages.tsx @@ -123,7 +123,7 @@ export const STORAGE_FIELDS: FieldSpec[] = [ name: "uploadAsTask", label: "上传交给 Alist 后台任务", kind: "switch", - hint: "开启后上传由 Alist 排队完成,本工具等任务结束再继续;缺省关闭(同 MP)", + hint: "开启后上传由 Alist 排队完成,本工具等任务结束再继续;缺省关闭", }, { name: "snapshotCheckFolderModtime", @@ -197,7 +197,7 @@ export function StoragesTable() { description: "登记 Alist / OpenList 后,媒体库目录、整理来源和文件管理都能用它。网盘方向的上传、下载、复制和移动以接口成功、目标存在为准,不比对大小和哈希。", emptyHint: "还没有登记 Alist / OpenList。", - deleteHint: "只删除登记和保存的凭据;被媒体库目录使用时不能删除。", + deleteHint: "只删除登记和保存的凭证;被媒体库目录使用时不能删除。", columns: [ { key: "name", diff --git a/apps/web/app/components/settings/system.tsx b/apps/web/app/components/settings/system.tsx index 99c503b5..625243fb 100644 --- a/apps/web/app/components/settings/system.tsx +++ b/apps/web/app/components/settings/system.tsx @@ -59,9 +59,9 @@ const BUDGET_KEYS = ["pgDataGb", "pgDataWarnGb", "imagesGb", "backupsGb", "sessi export const STORAGE_BUDGET_CARD: CardSpec = { anchor: "storage", title: "存储预算", - description: "超过预算时健康页告警;图片缓存超出容量按最久未访问删除。单位 GB。", + description: "超过预算时运行状态页告警;图片缓存超出容量按最久未访问删除。单位 GB。", fields: [ - gbField("pgDataGb", "数据库预算", "超过为严重"), + gbField("pgDataGb", "数据库预算", "超过时标为需要处理"), gbField("pgDataWarnGb", "数据库告警线", "不高于数据库预算"), gbField("imagesGb", "图片缓存容量"), gbField("backupsGb", "备份目录预算"), @@ -104,11 +104,11 @@ export const STORAGE_RETENTION_CARD: CardSpec = { "未识别资源归档", "未命中的条目这么久没有新证据就归档,仍可手动指定", ), - dayField("retentionDays", "engineRun", "后台运行记录", "仍被任务记录引用的跟随任务保留"), + dayField("retentionDays", "engineRun", "后台运行记录", "被任务记录引用的随任务一起保留"), dayField("retentionDays", "outboxEvent", "已投递的内部事件"), dayField("retentionDays", "resolutionAttempt", "识别尝试记录", "每个资源至少留最近 3 次"), - dayField("retentionDays", "identityResolved", "已识别的待识别条目", "忽略和归档的不删"), - dayField("retentionDays", "siteSnapshot", "站点快照", "每个站点最近一次成功的保留"), + dayField("retentionDays", "identityResolved", "未识别中已识别的条目", "忽略和归档的不删"), + dayField("retentionDays", "siteSnapshot", "站点抓取结果", "每个站点保留最近一次成功的抓取"), ], fromSettings: (s) => Object.fromEntries(RETENTION_KEYS.map((k) => [`storage.retentionDays.${k}`, n(s.storage.retentionDays[k])])), From 1fdcefda6a4e1e57c4471f08d6fc853f92feeb13 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Oct 2026 01:49:10 +0800 Subject: [PATCH 17/25] =?UTF-8?q?docs(copy):=20=E8=A1=A5=E4=B8=80=E8=BD=AE?= =?UTF-8?q?:=E5=8E=BB=E6=8E=89=E3=80=8C=E5=B0=9A=E6=9C=AA=E3=80=8D?= =?UTF-8?q?=E3=80=8C=E6=97=A0=E9=9C=80=E3=80=8D,=E5=88=A0=E9=99=A4=20Passk?= =?UTF-8?q?ey=20=E7=9A=84=E7=A1=AE=E8=AE=A4=E6=8C=89=E9=92=AE=E5=86=99?= =?UTF-8?q?=E6=98=8E=E5=AF=B9=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- apps/web/app/components/console/workspace-placeholder.tsx | 2 +- apps/web/app/components/settings/account-passkeys.tsx | 2 +- apps/web/app/components/settings/agent-tools.tsx | 2 +- apps/web/app/components/settings/organize-sources.tsx | 4 ++-- apps/web/app/components/status/storage-section.tsx | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/web/app/components/console/workspace-placeholder.tsx b/apps/web/app/components/console/workspace-placeholder.tsx index 189e3b69..0caf0dc3 100644 --- a/apps/web/app/components/console/workspace-placeholder.tsx +++ b/apps/web/app/components/console/workspace-placeholder.tsx @@ -11,7 +11,7 @@ export function WorkspacePlaceholder({ milestone = "W3" }: { milestone?: string return ( <> - + ); } diff --git a/apps/web/app/components/settings/account-passkeys.tsx b/apps/web/app/components/settings/account-passkeys.tsx index 6a6f5c8f..71b73f1c 100644 --- a/apps/web/app/components/settings/account-passkeys.tsx +++ b/apps/web/app/components/settings/account-passkeys.tsx @@ -101,7 +101,7 @@ function PasskeyRow({ item }: { item: PasskeyInfo }) { } title={`删除 Passkey「${item.name}」?`} description="删除后这把钥匙不能再登录控制台;设备上的钥匙要在系统设置里另外删。" - confirmLabel="删除" + confirmLabel="删除 Passkey" destructive onConfirm={async () => { try { diff --git a/apps/web/app/components/settings/agent-tools.tsx b/apps/web/app/components/settings/agent-tools.tsx index 948975d6..fb52cd81 100644 --- a/apps/web/app/components/settings/agent-tools.tsx +++ b/apps/web/app/components/settings/agent-tools.tsx @@ -49,7 +49,7 @@ const RISK: Record = { }; const AUTH: Record = { - read_only: { label: "无需授权", tone: "neutral", hint: "只读或预览,不改数据;Key 有对应 scope 就能调用" }, + read_only: { label: "不需要授权", tone: "neutral", hint: "只读或预览,不改数据;Key 有对应 scope 就能调用" }, granted: { label: "有持续授权", tone: "success", hint: "智能体、MCP、API Key 调用按持续授权放行" }, needs_approval: { label: "需审批", diff --git a/apps/web/app/components/settings/organize-sources.tsx b/apps/web/app/components/settings/organize-sources.tsx index 18d13d2c..ea47f48c 100644 --- a/apps/web/app/components/settings/organize-sources.tsx +++ b/apps/web/app/components/settings/organize-sources.tsx @@ -435,8 +435,8 @@ export function OrganizeSourcesSection() { : "手动"}

    - 基线:{p.baselineAt ? formatDateTime(p.baselineAt) : "尚未建立"} · 最近扫描: - {p.lastScanAt ? formatDateTime(p.lastScanAt) : "尚未扫描"} · 快照 {p.fileCount} 个文件 + 基线:{p.baselineAt ? formatDateTime(p.baselineAt) : "未建立"} · 最近扫描: + {p.lastScanAt ? formatDateTime(p.lastScanAt) : "未扫描"} · 快照 {p.fileCount} 个文件

    {p.lastError ?

    {p.lastError}

    : null}
    diff --git a/apps/web/app/components/status/storage-section.tsx b/apps/web/app/components/status/storage-section.tsx index 2b8660fd..6a407652 100644 --- a/apps/web/app/components/status/storage-section.tsx +++ b/apps/web/app/components/status/storage-section.tsx @@ -387,7 +387,7 @@ export function StorageSection({ health }: { health: SystemHealth }) {
    - + rows={s.kvCache} rowKey={(r) => r.region} From 4b67d304f0d23ce707e96a4c1999f232dbc0256a Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Oct 2026 01:49:27 +0800 Subject: [PATCH 18/25] =?UTF-8?q?=E6=96=87=E6=A1=88(=E5=AA=92=E4=BD=93?= =?UTF-8?q?=E5=BA=93/=E8=AE=A2=E9=98=85):=E7=BC=A9=E7=9F=AD=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E5=AD=A3=E9=9B=86=E7=BB=93=E6=9E=84=E4=B8=8E=E6=9C=AA?= =?UTF-8?q?=E8=AF=86=E5=88=AB=E7=A9=BA=E6=80=81=E8=AF=B4=E6=98=8E,?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=A1=A5=E5=85=A8=E5=BC=80=E5=85=B3=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E5=86=99=E5=AF=B9=E8=B1=A1=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- apps/web/app/components/library/episode-structure-model.ts | 2 +- apps/web/app/components/library/identity-views.tsx | 2 +- apps/web/app/components/subscriptions/subscription-form.tsx | 2 +- apps/web/app/routes/console/files.trash.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/app/components/library/episode-structure-model.ts b/apps/web/app/components/library/episode-structure-model.ts index 0fa17d17..0612d98f 100644 --- a/apps/web/app/components/library/episode-structure-model.ts +++ b/apps/web/app/components/library/episode-structure-model.ts @@ -63,5 +63,5 @@ export function originText( /** 切换确认的说明:改的是作品设置,影响季表、集数映射、订阅与刮削 */ export function switchDescription(target: string): string { - return `作品改按「${target}」分季:季与集按 TMDB 原季集迁到新位置,已整理文件跟着走;集数映射、订阅和刮削都按新季集,已绑定的资源会重新换算。之后可以再切回来。`; + return `改按「${target}」分季后,已整理文件随季集一起调整;集数映射、订阅和刮削都按新季集,已绑定的资源会重新换算。之后可以再切回来。`; } diff --git a/apps/web/app/components/library/identity-views.tsx b/apps/web/app/components/library/identity-views.tsx index 8fd7d37b..bd436946 100644 --- a/apps/web/app/components/library/identity-views.tsx +++ b/apps/web/app/components/library/identity-views.tsx @@ -139,7 +139,7 @@ export function PoolTab() { empty={{ icon: SearchX, title: filtered ? "没有符合条件的未识别资源" : "没有未识别资源", - description: "未命中订阅、证据不足或冲突的资源会出现在这里;新增相近别名时自动重试,也可手动指定。", + description: "未命中订阅、证据不足或有冲突的资源。新增相近别名时会自动重试,也可以手动指定。", }} hasMore={pool.hasNextPage} onLoadMore={() => pool.fetchNextPage()} diff --git a/apps/web/app/components/subscriptions/subscription-form.tsx b/apps/web/app/components/subscriptions/subscription-form.tsx index b8ffa978..7ca33038 100644 --- a/apps/web/app/components/subscriptions/subscription-form.tsx +++ b/apps/web/app/components/subscriptions/subscription-form.tsx @@ -930,7 +930,7 @@ export function AutoFillFields({

    )}
    - +
    {(["inherit", "on", "off"] as AutoFillMode[]).map((m) => (
    ) : null} @@ -336,7 +336,7 @@ export function InlineApproval({ view, stepTitle }: { view: ApprovalView; stepTi ...(s === "rejected" && reason.trim() ? { reason: reason.trim() } : {}), }, { - onSuccess: () => toast.success(s === "approved" ? "已批准,智能体继续执行" : "已拒绝,智能体按理由调整"), + onSuccess: () => toast.success(s === "approved" ? "已批准,智能体继续执行" : "已拒绝,智能体会调整做法"), onError: (e) => toast.error(errorMessage(e)), }, ); @@ -364,7 +364,7 @@ export function InlineApproval({ view, stepTitle }: { view: ApprovalView; stepTi value={reason} maxLength={2000} aria-label="拒绝理由" - placeholder="理由或建议(可选),智能体按它调整" + placeholder="理由或建议,可不填" onChange={(e) => setReason(e.target.value)} /> ) : null} diff --git a/apps/web/app/components/agent/agent-run-panel.tsx b/apps/web/app/components/agent/agent-run-panel.tsx index a980c690..5cc0937b 100644 --- a/apps/web/app/components/agent/agent-run-panel.tsx +++ b/apps/web/app/components/agent/agent-run-panel.tsx @@ -119,7 +119,7 @@ export function GoalComposer({ maxRows={10} maxLength={4000} aria-label="目标" - placeholder="例如:把沙丘:第二部的 4K 收进来" + placeholder="例如:下载沙丘 2 的 4K 版本" submitLabel={pending ? "提交中" : "开始"} commands={NEW_RUN_COMMANDS} context={context} @@ -491,7 +491,7 @@ export function AgentRunPanel({ maxRows={6} maxLength={8000} aria-label="发消息" - placeholder={ended ? "接着说,输入 / 看指令" : "补充或纠正"} + placeholder={ended ? "继续对话,输入 / 查看指令" : "补充或纠正"} commands={RUN_COMMANDS} />
    @@ -499,13 +499,13 @@ export function AgentRunPanel({ abort.mutateAsync(r.id).then( - () => toast.success("已请求停止"), + () => toast.success("正在停止"), (e) => { toast.error(errorMessage(e)); throw e; diff --git a/apps/web/app/components/agent/agent-session-list.tsx b/apps/web/app/components/agent/agent-session-list.tsx index ea4e37ca..b0436f33 100644 --- a/apps/web/app/components/agent/agent-session-list.tsx +++ b/apps/web/app/components/agent/agent-session-list.tsx @@ -380,7 +380,7 @@ export function AgentSessionList({ ? "没有等你处理的会话" : "没有符合条件的会话" : f.group === "auto" - ? "还没有巡检会话,巡检到点后会出现在这里" + ? "还没有巡检会话" : "还没有会话,写一个目标开始" } /> diff --git a/apps/web/app/components/agent/agent-session-ui.test.ts b/apps/web/app/components/agent/agent-session-ui.test.ts index 18bac1ba..1572e344 100644 --- a/apps/web/app/components/agent/agent-session-ui.test.ts +++ b/apps/web/app/components/agent/agent-session-ui.test.ts @@ -245,7 +245,7 @@ describe("拒绝:缩成一行", () => { it("授权拒绝 → 需要你批准 + [批准并重试];白名单拦截没有按钮", () => { expect(deniedReason(denied)).toEqual({ text: "需要你批准", approvable: true }); expect(deniedReason(call({ status: "denied", reason: "不在白名单" }))).toEqual({ - text: "本次没有开放", + text: "这次会话没开放这个能力", approvable: false, }); const t = text( @@ -529,7 +529,7 @@ describe("实际结果行(FIX-9):只列系统核实的事实", () => { const html = render( createElement(ResultLine, { run: RUN, report: done, live: withText("提交了 2 个下载,整理了 1 个。") }), ); - expect(text(html).replace(/\s*、\s*/g, "、")).toContain("提交了 2 个下载、完成 1 次整理、1 个还在进行 · 41 秒"); + expect(text(html).replace(/\s*、\s*/g, "、")).toContain("提交了 2 个下载、完成 1 次整理、1 个进行中 · 41 秒"); expect(html).toContain(`href="/console/organize/${ID(3)}"`); expect(html).toContain('href="/console/downloads"'); expect(html).not.toContain("与汇报不一致"); diff --git a/apps/web/app/components/agent/agent-store.ts b/apps/web/app/components/agent/agent-store.ts index 3a21c58d..129d332f 100644 --- a/apps/web/app/components/agent/agent-store.ts +++ b/apps/web/app/components/agent/agent-store.ts @@ -180,7 +180,7 @@ export function contextFromPath(pathname: string): AgentContext | null { case "organize": return { label: `整理记录 ${short}`, recordId: id, fromPath: true }; case "engine": - return { label: `引擎任务 ${short}`, taskId: id, fromPath: true }; + return { label: `后台任务 ${short}`, taskId: id, fromPath: true }; default: return null; } diff --git a/apps/web/app/components/agent/agent-subagent-tree.tsx b/apps/web/app/components/agent/agent-subagent-tree.tsx index 746c5d96..0b8ffab9 100644 --- a/apps/web/app/components/agent/agent-subagent-tree.tsx +++ b/apps/web/app/components/agent/agent-subagent-tree.tsx @@ -46,9 +46,9 @@ function budgetText(b: Record) { const parts: string[] = []; if (b.maxToolCalls !== undefined) parts.push(`${b.maxToolCalls} 次调用`); if (b.maxTokens !== undefined) parts.push(`${tokens(b.maxTokens)} token`); - if (b.maxDurationSec !== undefined) parts.push(`${b.maxDurationSec}s`); + if (b.maxDurationSec !== undefined) parts.push(`${b.maxDurationSec} 秒`); if (b.maxCostUsd !== undefined) parts.push(`$${b.maxCostUsd}`); - return parts.length ? parts.join(" · ") : "沿用设置"; + return parts.length ? parts.join(" · ") : "沿用默认"; } function Node({ node, byParent, depth }: { node: AgentRunNode; byParent: Map; depth: number }) { @@ -119,7 +119,7 @@ export function SubagentTree({ snapshot, className }: { snapshot: RunSnapshot; c

    {shortGoalTitle(parent.goal)}

    - 第 {run.depth} 层子会话 · 继承父会话的发起人与授权上限 · 能力 {caps} 项(⊆ 父会话)· 划拨预算{" "} + 第 {run.depth} 层子会话 · 权限不超过父会话 · 可用能力 {caps} 项 · 预算{" "} {budgetText(run.budget as Record)} {run.spawnReason ? ` · 理由:${run.spawnReason}` : ""}

    @@ -129,7 +129,7 @@ export function SubagentTree({ snapshot, className }: { snapshot: RunSnapshot; c

    - 子会话 {children.length} 个{familyUsage ? : null} + {children.length} 个子会话{familyUsage ? : null}

      {direct.map((c) => ( @@ -143,5 +143,5 @@ export function SubagentTree({ snapshot, className }: { snapshot: RunSnapshot; c } function FamilyUsage({ usage }: { usage: AgentFamilyUsage }) { - return 会话树合计 {usageLine(usage)}; + return 合计 {usageLine(usage)}; } diff --git a/apps/web/app/components/agent/conversation-model.ts b/apps/web/app/components/agent/conversation-model.ts index 73e47080..9e30c96b 100644 --- a/apps/web/app/components/agent/conversation-model.ts +++ b/apps/web/app/components/agent/conversation-model.ts @@ -29,7 +29,7 @@ export function authDenial(call: Pick) /** 被服务端拒绝(白名单拦截或授权校验失败) */ export const isDenied = (call: ToolCallView) => call.status === "denied" || authDenial(call) !== null; -/** 拒绝的一行说明:需要批准 / 本次没有开放 / 次数用完 */ +/** 拒绝的一行说明:需要批准 / 这次会话没开放 / 次数用完 */ export function deniedReason(call: ToolCallView): { text: string; approvable: boolean } { const auth = authDenial(call); if (auth) { @@ -37,8 +37,8 @@ export function deniedReason(call: ToolCallView): { text: string; approvable: bo if (/自动化总开关|只读对账/.test(auth)) return { text: "自动执行已关闭", approvable: false }; return { text: "需要你批准", approvable: true }; } - if (call.reason && /预算|次数/.test(call.reason)) return { text: "调用次数用完了", approvable: false }; - return { text: "本次没有开放", approvable: false }; + if (call.reason && /预算|次数/.test(call.reason)) return { text: "工具调用次数已用完", approvable: false }; + return { text: "这次会话没开放这个能力", approvable: false }; } // ── 进度行的人话计数 ── diff --git a/apps/web/app/components/agent/quick-goals.ts b/apps/web/app/components/agent/quick-goals.ts index 381b5577..611f12aa 100644 --- a/apps/web/app/components/agent/quick-goals.ts +++ b/apps/web/app/components/agent/quick-goals.ts @@ -30,7 +30,7 @@ const RULES: Rule[] = [ }, { match: /^\/console\/organize/, - goals: ["识别需要处理的整理记录里的作品", "这次整理为什么有文件失败", "把失败的文件重新整理一遍"], + goals: ["识别整理记录里没识别出的作品", "这次整理为什么有文件失败", "把失败的文件重新整理一遍"], }, { match: /^\/console\/files/, diff --git a/apps/web/app/components/agent/tool-call-card-io.tsx b/apps/web/app/components/agent/tool-call-card-io.tsx index ac98244c..8c04c96e 100644 --- a/apps/web/app/components/agent/tool-call-card-io.tsx +++ b/apps/web/app/components/agent/tool-call-card-io.tsx @@ -66,7 +66,7 @@ function ToolCallIoSheet({ call, onClose }: { call: ToolCallView; onClose: () => {input.truncated || output.truncated ? ( -

      事件流只保存 500 字符以内的摘要,超出部分已截断。

      +

      只保存前 500 个字符,其余已截断。

      ) : null} {tools.isPending ? ( @@ -76,7 +76,7 @@ function ToolCallIoSheet({ call, onClose }: { call: ToolCallView; onClose: () => ) : tool ? ( ) : ( -

      这个能力没有注册,没有 schema。

      +

      这个能力未注册,没有 schema。

      )}
      {tool ? ( diff --git a/apps/web/app/components/agentui/approval-card.tsx b/apps/web/app/components/agentui/approval-card.tsx index 3adcecfa..259c83b7 100644 --- a/apps/web/app/components/agentui/approval-card.tsx +++ b/apps/web/app/components/agentui/approval-card.tsx @@ -20,7 +20,7 @@ export type ApprovalCardStatus = | "consumed"; const LABEL: Record = { - pending: "待批准", + pending: "等你批准", submitting: "提交中", approved: "已批准", rejected: "已拒绝", diff --git a/apps/web/app/components/agentui/code-block.tsx b/apps/web/app/components/agentui/code-block.tsx index 3e1c388d..c6be4bb3 100644 --- a/apps/web/app/components/agentui/code-block.tsx +++ b/apps/web/app/components/agentui/code-block.tsx @@ -101,7 +101,7 @@ export function CodeBlock({ ) : ( )} - {streaming ? "输出中" : "完整"} + {streaming ? "输出中" : "已完成"} {copyable ? (