fix(sites): HHClub parsing, resource list layout and request restraint
HHClub (憨憨) was checked read-only against a real account (one detail
request; list, user data and health came from the product's own fetch).
Parsing fixes:
- list titles carried the site's new-torrent marker "[新]" (58 of 100
rows; the detail page title has none); stripped with a replace filter
- refreshing a detail overwrote seeders / leechers / snatched / category
name / H&R with null because the HHClub div detail page lacks them;
detail values now fall back to the list snapshot (mergeDetailOverList),
and an unknown detail promotion keeps the list promotion
- level "贵宾" is VIP (umt.vipLevels)
- sanitized minimal fixture HHANCLUB_LIST_PAGE_2026 + tests; README marks
HHClub as verified 2026-09-29
Size, counts, category ids/names, +08:00 time and subtitle were correct.
Request restraint:
- site definitions may carry umt.defaults; merged with settings.sites
(longer intervals, fewer pages): new settings defaultSnapshotMaxPages 1,
defaultHealthCheckIntervalSec 6 h, defaultDetailCacheSec 24 h; default
burst 2. HHClub: snapshot 60 min page 1 only, health 6 h, search
concurrency 1, detail cache 24 h, 1 token / 3 s burst 2
- periodic / manual snapshots are capped to that page count; scheduled
health checks skip sites checked within the interval; detail refresh is
served from cache inside the window; live searches on one site queue
- every rate-limiter token is counted per site per hour
(site_request_count, migration 0046); GET /sites/{id}/request-stats;
site overview and resource list show requests in the last 24 hours
Resource list: two-layer rows. Main line: title + spec tags (episodes,
resolution, source, codec, HDR, group, 国语/中字, site tags) + promotion
with time left + H&R. Second line: subtitle (one line, ellipsis) and
fixed-width category / size / seed-leech-snatched / upload time
(relative, absolute on hover) columns, stale badge, inventory overlay;
folds into a card on phones.
Also aligns identity_candidate.normalized_title order in db types with
the migrations (schema drift test).
Takes effect after the stack is rebuilt (migration 0046).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
20b63db254
commit
901b01d1a1
43 files changed
+1222
-1315
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import type { AgentFamilyUsage, AgentRunNode } from "@umt/contracts";
|
||||
import { ChevronRight, CornerLeftUp, GitBranch } from "lucide-react";
|
||||
import { createContext, type ReactNode, useContext, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router";
|
||||
import { agentMockEnabled, type RunSnapshot } from "~/api/agent.ts";
|
||||
import { cn } from "~/lib/utils.ts";
|
||||
@@ -20,27 +20,6 @@ export function usageLine(u: { toolCalls: number; totalTokens?: number; costUsd?
|
||||
|
||||
const href = (id: string) => `/console/agent/${id}${agentMockEnabled() ? "?mock=1" : ""}`;
|
||||
|
||||
/** 抽屉里提供:点父/子会话在抽屉内打开,不跳页 */
|
||||
export const AgentRunOpenContext = createContext<((runId: string) => void) | null>(null);
|
||||
|
||||
const LINK_CLASS =
|
||||
"line-clamp-2 cursor-pointer rounded-tight text-left text-caption break-words text-foreground outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring";
|
||||
|
||||
function RunLink({ id, children }: { id: string; children: ReactNode }) {
|
||||
const open = useContext(AgentRunOpenContext);
|
||||
if (open)
|
||||
return (
|
||||
<button type="button" onClick={() => open(id)} className={LINK_CLASS}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<Link to={href(id)} className={LINK_CLASS}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function budgetText(b: Record<string, number | undefined>) {
|
||||
const parts: string[] = [];
|
||||
if (b.maxToolCalls !== undefined) parts.push(`${b.maxToolCalls} 次调用`);
|
||||
@@ -70,7 +49,12 @@ function Node({ node, byParent, depth }: { node: AgentRunNode; byParent: Map<str
|
||||
<span className="mt-0.5 size-4 shrink-0" aria-hidden />
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<RunLink id={node.id}>{splitContextGoal(node.goal).text}</RunLink>
|
||||
<Link
|
||||
to={href(node.id)}
|
||||
className="line-clamp-2 rounded-tight text-caption break-words text-foreground outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
{splitContextGoal(node.goal).text}
|
||||
</Link>
|
||||
<div className="mt-0.5 flex flex-wrap items-center gap-1.5">
|
||||
<RunStatusBadge status={node.status} />
|
||||
<span className="text-meta text-muted-foreground tabular-nums">
|
||||
@@ -98,7 +82,7 @@ function Node({ node, byParent, depth }: { node: AgentRunNode; byParent: Map<str
|
||||
}
|
||||
|
||||
/** 子会话树 + 父会话链接;两者都没有时返回 null */
|
||||
export function SubagentTree({ snapshot, className }: { snapshot: RunSnapshot; className?: string }) {
|
||||
export function SubagentTree({ snapshot }: { snapshot: RunSnapshot }) {
|
||||
const { run, parent, children = [], familyUsage } = snapshot;
|
||||
if (!parent && children.length === 0) return null;
|
||||
const byParent = new Map<string, AgentRunNode[]>();
|
||||
@@ -109,14 +93,19 @@ export function SubagentTree({ snapshot, className }: { snapshot: RunSnapshot; c
|
||||
const direct = byParent.get(run.id) ?? [];
|
||||
const caps = ((run.scope as { capabilities?: string[] }).capabilities ?? []).length;
|
||||
return (
|
||||
<section aria-label="子会话" className={cn("space-y-2 rounded-xl border border-border/70 p-3", className)}>
|
||||
<section aria-label="子会话" className="space-y-2 rounded-xl border border-border/70 p-3">
|
||||
{parent ? (
|
||||
<div className="space-y-1">
|
||||
<p className="flex items-center gap-1.5 text-meta text-muted-foreground">
|
||||
<CornerLeftUp aria-hidden className="size-3.5" />
|
||||
父会话
|
||||
</p>
|
||||
<RunLink id={parent.id}>{splitContextGoal(parent.goal).text}</RunLink>
|
||||
<Link
|
||||
to={href(parent.id)}
|
||||
className="line-clamp-2 rounded-tight text-caption break-words text-foreground outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
{splitContextGoal(parent.goal).text}
|
||||
</Link>
|
||||
<p className="text-meta text-muted-foreground">
|
||||
第 {run.depth} 层子会话 · 继承父会话的发起人与授权上限 · 能力 {caps} 项(⊆ 父会话)· 划拨预算{" "}
|
||||
{budgetText(run.budget as Record<string, number | undefined>)}
|
||||
@@ -126,7 +115,7 @@ export function SubagentTree({ snapshot, className }: { snapshot: RunSnapshot; c
|
||||
) : null}
|
||||
{direct.length ? (
|
||||
<div className="space-y-1">
|
||||
<p className="flex flex-wrap items-center gap-1.5 text-meta text-muted-foreground">
|
||||
<p className="flex items-center gap-1.5 text-meta text-muted-foreground">
|
||||
<GitBranch aria-hidden className="size-3.5" />
|
||||
子会话 {children.length} 个{familyUsage ? <FamilyUsage usage={familyUsage} /> : null}
|
||||
</p>
|
||||
|
||||
Reference in new issue
Block a user