import { toast } from "sonner"; import { useClearImageCache, useClearTmdbCache, useImageCache, useTmdbCache } from "~/api/cache.ts"; import { useUpdateSettings } from "~/api/settings.ts"; import { StatusBadge } from "~/components/console/status-badge.tsx"; import { Button } from "~/components/ui/button.tsx"; import { errorMessage } from "~/lib/api.ts"; import { str } from "./resource-section.tsx"; import { type CardSpec, n, SettingsCard, SettingsGate } from "./settings-card.tsx"; // 元数据:TMDB API Key(只写,存 secret)与 Base URL const TMDB_CARD: CardSpec = { anchor: "tmdb", title: "TMDB", description: "留空时用环境变量 TMDB_API_KEY / TMDB_ACCESS_TOKEN / TMDB_BASE_URL。", fields: [ { name: "metadata.tmdbApiKey", label: "API Key", kind: "password", hint: "v3 API Key 或 v4 Read Access Token;留空不修改", wide: true, }, { name: "metadata.tmdbBaseUrl", label: "Base URL", kind: "url", placeholder: "https://api.themoviedb.org", hint: "自建反代时填写;留空用官方地址", wide: true, }, ], fromSettings: (s) => ({ "metadata.tmdbApiKey": "", "metadata.tmdbBaseUrl": s.metadata.tmdbBaseUrl ?? "" }), toPatch: (v) => ({ metadata: { tmdbBaseUrl: str(v["metadata.tmdbBaseUrl"]) || null, ...(str(v["metadata.tmdbApiKey"]) ? { tmdbApiKey: str(v["metadata.tmdbApiKey"]) } : {}), }, }), extra: (s) => , }; function TmdbKeyStatus({ saved }: { saved: boolean }) { const update = useUpdateSettings(); return (
{saved ? "API Key 已保存" : "未保存 API Key"} {saved ? ( ) : null}
); } // TMDB 缓存(对照 MP:META_CACHE_EXPIRE / CONF.tmdb / EMPTY_RESULT_CACHE_TTL) const TMDB_CACHE_CARD: CardSpec = { anchor: "tmdb-cache", title: "TMDB 缓存", description: "请求响应与识别结果存在数据库里,三个进程共用。超过条数上限时删最久未用的条目。", fields: [ { name: "cache.tmdbTtlHours", label: "有效期(小时)", kind: "number" }, { name: "cache.tmdbMaxSize", label: "条数上限", kind: "number", hint: "请求缓存和识别缓存各自计数" }, { name: "cache.emptyResultTtlSec", label: "空结果有效期(秒)", kind: "number", hint: "搜索无结果、未识别时的短缓存", }, ], fromSettings: (s) => ({ "cache.tmdbTtlHours": n(s.cache.tmdbTtlHours), "cache.tmdbMaxSize": n(s.cache.tmdbMaxSize), "cache.emptyResultTtlSec": n(s.cache.emptyResultTtlSec), }), toPatch: (v) => ({ cache: { tmdbTtlHours: Number(v["cache.tmdbTtlHours"]), tmdbMaxSize: Number(v["cache.tmdbMaxSize"]), emptyResultTtlSec: Number(v["cache.emptyResultTtlSec"]), }, }), extra: () => , }; function TmdbCacheStatus() { const q = useTmdbCache(); const clear = useClearTmdbCache(); const c = q.data?.counts; return (
{c ? `识别 ${c.recognize} 条 · 请求 ${c.request} 条 · 上限各 ${q.data?.maxSize} 条` : "读取中…"}
); } // 图片代理与缓存(对照 MP GLOBAL_IMAGE_CACHE / GLOBAL_IMAGE_CACHE_DAYS) const IMAGES_CARD: CardSpec = { anchor: "images", title: "图片代理与缓存", description: "海报经服务端从 TMDB 取回。开启缓存后保存到数据目录,下次直接读取。", fields: [ { name: "cache.globalImageCache", label: "缓存图片", kind: "switch" }, { name: "cache.globalImageCacheDays", label: "保留天数", kind: "number", hint: "超过天数的图片每天清理一次" }, ], fromSettings: (s) => ({ "cache.globalImageCache": s.cache.globalImageCache, "cache.globalImageCacheDays": n(s.cache.globalImageCacheDays), }), toPatch: (v) => ({ cache: { globalImageCache: v["cache.globalImageCache"] === true, globalImageCacheDays: Number(v["cache.globalImageCacheDays"]), }, }), extra: () => , }; function ImageCacheStatus() { const q = useImageCache(); const clear = useClearImageCache(); const d = q.data; return (
{d ? `${d.files} 张 · ${(d.usedBytes / 1024 / 1024).toFixed(1)} MiB` : "读取中…"}
); } // 身份识别(DEC-1 惰性识别,对照 MP:RSS / 列表资源只在匹配订阅或用户搜索时识别) const IDENTITY_CARD: CardSpec = { anchor: "identity", title: "资源识别", description: "缺省只在资源命中订阅或本地作品别名、新作品收录规则前置条件通过、你手动识别或搜索时查 TMDB;其余进待识别池,不进待处理。", fields: [ { name: "identity.eagerTmdbLookup", label: "每条新资源都查 TMDB", kind: "switch", hint: "旧行为。每条资源约 1–2 次搜索 + 命中时 1 次详情(识别缓存命中为 0);每小时 100 条的 RSS 约每天 2400–7200 次请求,站点独有的短剧 / 综艺多半查不到", wide: true, }, { name: "identity.poolArchiveDays", label: "待识别池归档(天)", kind: "number", hint: "未命中订阅的条目超过这么多天没有新证据就归档,仍可筛出并手动指定", }, ], fromSettings: (s) => ({ "identity.eagerTmdbLookup": s.identity.eagerTmdbLookup, "identity.poolArchiveDays": n(s.identity.poolArchiveDays), }), toPatch: (v) => ({ identity: { eagerTmdbLookup: v["identity.eagerTmdbLookup"] === true, poolArchiveDays: Number(v["identity.poolArchiveDays"]), }, }), }; export function MetadataSection() { return ( {(s) => ( <> )} ); }