| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- "use client";
- import Link from "next/link";
- import { useEffect, useMemo, useState } from "react";
- import { AppShell } from "@/components/AppShell";
- import { EmptyState, ErrorState, LoadingState } from "@/components/StateBlocks";
- import { listAigcPushRecords } from "@/lib/api/client";
- import type { AigcPushRecord } from "@/lib/api/types";
- import { platformLabel } from "@/lib/flow-ledger/business";
- function formatBeijingTime(value?: string | null): string {
- if (!value) return "未记录时间";
- const iso = /z$|[+-]\d{2}:\d{2}$/i.test(value) ? value : `${value}Z`;
- const date = new Date(iso);
- if (Number.isNaN(date.getTime())) return "时间待确认";
- return new Intl.DateTimeFormat("zh-CN", {
- timeZone: "Asia/Shanghai",
- year: "numeric",
- month: "2-digit",
- day: "2-digit",
- hour: "2-digit",
- minute: "2-digit",
- hour12: false
- }).format(date);
- }
- function byPushedDesc(a: AigcPushRecord, b: AigcPushRecord): number {
- return (b.pushed_at || "") > (a.pushed_at || "") ? 1 : (b.pushed_at || "") < (a.pushed_at || "") ? -1 : 0;
- }
- export function AigcPushRecordsPage() {
- const [records, setRecords] = useState<AigcPushRecord[]>([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState<string | null>(null);
- const [query, setQuery] = useState("");
- async function load() {
- setLoading(true);
- setError(null);
- try {
- const data = await listAigcPushRecords(new URLSearchParams({ limit: "500" }));
- setRecords(data.items || []);
- } catch (err) {
- setError(err instanceof Error ? err.message : String(err));
- } finally {
- setLoading(false);
- }
- }
- useEffect(() => {
- void load();
- }, []);
- const filtered = useMemo(() => {
- const q = query.trim().toLowerCase();
- const sorted = [...records].sort(byPushedDesc);
- if (!q) return sorted;
- return sorted.filter((record) => {
- const haystack = [
- record.push_record_id,
- record.run_id,
- record.platform,
- record.demand_content_id,
- record.demand_name,
- record.hive_merge_leve2,
- record.plans?.map((plan) => `${plan.crawler_plan_id} ${plan.produce_plan_id}`).join(" "),
- record.items?.map((item) => item.platform_content_id).join(" ")
- ].join(" ").toLowerCase();
- return haystack.includes(q);
- });
- }, [records, query]);
- return (
- <AppShell
- title="AIGC 推送记录"
- subtitle="只展示真实推送成功的 crawler plan,不展示 dry-run"
- showBack
- backHref="/runs"
- onRefresh={load}
- >
- <section className="aigc-page">
- <input
- className="rbd-search"
- type="search"
- placeholder="搜索需求 / run / 计划 ID / 视频 ID"
- value={query}
- onChange={(event) => setQuery(event.target.value)}
- />
- {loading ? <LoadingState /> : null}
- {error ? <ErrorState message={error} /> : null}
- {!loading && !error && filtered.length === 0 ? <EmptyState label="暂无真实 AIGC 推送记录" /> : null}
- <div className="aigc-record-list">
- {filtered.map((record) => (
- <article className="aigc-record" key={record.push_record_id}>
- <header className="aigc-record-head">
- <div>
- <strong>{record.demand_name || `需求 ${record.demand_content_id || ""}`}</strong>
- <span className="muted">
- {record.demand_content_id ? `#${record.demand_content_id} · ` : ""}
- {record.hive_merge_leve2 || "未知品类"} · {platformLabel(record.platform)}
- </span>
- </div>
- <div className="aigc-record-meta">
- <span className="chip green">已推送</span>
- <span className="muted">北京时间 {formatBeijingTime(record.pushed_at)}</span>
- </div>
- </header>
- <div className="aigc-record-grid">
- <div>
- <span className="muted">Run</span>
- <Link href={`/runs/${encodeURIComponent(record.run_id)}`}>{record.run_id}</Link>
- </div>
- <div>
- <span className="muted">需求生成</span>
- <b>{record.demand_dt || "未记录"}</b>
- </div>
- <div>
- <span className="muted">Hive gap</span>
- <b>{record.hive_gap_dt || "未记录"}</b>
- </div>
- <div>
- <span className="muted">视频数</span>
- <b>{record.pushed_content_count ?? record.items?.length ?? 0}</b>
- </div>
- </div>
- <div className="aigc-plan-list">
- {(record.plans || []).map((plan) => {
- const items = (record.items || []).filter((item) => item.plan_part === plan.plan_part);
- return (
- <details className="aigc-plan" key={`${record.push_record_id}-${plan.plan_part}`}>
- <summary>
- <span>第 {plan.plan_part} 组</span>
- <span className="muted">produce {plan.produce_plan_id}</span>
- <span className="muted">crawler {plan.crawler_plan_id}</span>
- <span className="chip">{plan.content_count} 条</span>
- </summary>
- <div className="aigc-video-list">
- {items.map((item) => (
- <span className="aigc-video-id" key={item.platform_content_id}>
- {item.platform_content_id}
- </span>
- ))}
- </div>
- </details>
- );
- })}
- </div>
- {record.notes ? <p className="muted">{record.notes}</p> : null}
- </article>
- ))}
- </div>
- </section>
- </AppShell>
- );
- }
|