|
|
@@ -2,7 +2,7 @@
|
|
|
|
|
|
import { useState } from "react";
|
|
|
import Link from "next/link";
|
|
|
-import { GitBranch, Info, ListVideo } from "lucide-react";
|
|
|
+import { FileText, GitBranch, Info, ListVideo } from "lucide-react";
|
|
|
import { AppShell } from "@/components/AppShell";
|
|
|
import { BusinessDrawer, type BusinessDrawerState } from "@/components/BusinessDrawer";
|
|
|
import { EmptyState, ErrorState, LoadingState } from "@/components/StateBlocks";
|
|
|
@@ -28,7 +28,14 @@ export function LedgerPage({ runId }: { runId: string }) {
|
|
|
return (
|
|
|
<AppShell
|
|
|
title="本次流程摘要"
|
|
|
- titleExtra={ledger ? <HeaderSummaryChips rows={rows} dataOriginLabel={ledger.dataOriginLabel} /> : null}
|
|
|
+ titleExtra={ledger ? (
|
|
|
+ <HeaderSummaryChips
|
|
|
+ rows={rows}
|
|
|
+ dataOriginLabel={ledger.dataOriginLabel}
|
|
|
+ demand={ledger.demandSummary}
|
|
|
+ onOpenDemand={setDrawer}
|
|
|
+ />
|
|
|
+ ) : null}
|
|
|
onRefresh={reload}
|
|
|
>
|
|
|
{loading ? <LoadingState /> : null}
|
|
|
@@ -40,7 +47,6 @@ export function LedgerPage({ runId }: { runId: string }) {
|
|
|
<table className="ledger-table">
|
|
|
<thead>
|
|
|
<tr>
|
|
|
- <th className="group-demand sticky-demand">原始需求</th>
|
|
|
<th className="group-source sticky-source">需求 -> query</th>
|
|
|
<th className="group-videos">query -> 渠道</th>
|
|
|
<th className="group-rules">渠道 -> 判断</th>
|
|
|
@@ -51,7 +57,6 @@ export function LedgerPage({ runId }: { runId: string }) {
|
|
|
<tbody>
|
|
|
{rows.map((row) => (
|
|
|
<tr className="ledger-row" key={row.id}>
|
|
|
- <DemandCell demand={ledger.demandSummary} isFirst={row.id === rows[0]?.id} />
|
|
|
<SourceCell row={row} />
|
|
|
<VideoCell runId={runId} row={row} />
|
|
|
<RuleSummaryCell row={row} onOpenScore={setDrawer} />
|
|
|
@@ -69,72 +74,69 @@ export function LedgerPage({ runId }: { runId: string }) {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
-function DemandCell({ demand, isFirst }: { demand?: DemandSummary; isFirst: boolean }) {
|
|
|
- if (!isFirst) {
|
|
|
- return (
|
|
|
- <td className="demand-cell sticky-demand repeated-demand">
|
|
|
- <span>同需求单 {demand?.id || "待确认"}</span>
|
|
|
- </td>
|
|
|
- );
|
|
|
- }
|
|
|
+function demandDrawer(demand?: DemandSummary): BusinessDrawerState {
|
|
|
if (!demand) {
|
|
|
- return (
|
|
|
- <td className="demand-cell sticky-demand">
|
|
|
- <div className="cell-stack">
|
|
|
- <strong>需求待确认</strong>
|
|
|
- <span className="muted">当前运行没有写入原始需求单摘要</span>
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- );
|
|
|
+ return {
|
|
|
+ title: "原始需求",
|
|
|
+ sections: [
|
|
|
+ {
|
|
|
+ label: "需求待确认",
|
|
|
+ value: "当前运行没有写入原始需求单摘要。",
|
|
|
+ tone: "warn"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
}
|
|
|
- return (
|
|
|
- <td className="demand-cell sticky-demand">
|
|
|
- <div className="demand-card">
|
|
|
- <div className="demand-card-head">
|
|
|
- <span className="chip dark">需求单 {demand.id || "待确认"}</span>
|
|
|
- {demand.source ? <span className="chip blue">{demand.source}</span> : null}
|
|
|
- </div>
|
|
|
- <strong className="demand-title">{demand.name || "未命名需求"}</strong>
|
|
|
- <DemandLine label="需求描述" value={demand.description} />
|
|
|
- <DemandLine label="形成原因" value={demand.reason} />
|
|
|
- <div className="demand-meta-row">
|
|
|
- {demand.itemsetIds.length ? <span>itemset {demand.itemsetIds.join(" / ")}</span> : null}
|
|
|
- {demand.support ? <span>支持 {demand.support}</span> : null}
|
|
|
- {demand.sourcePostId ? <span>来源帖子 {demand.sourcePostId}</span> : null}
|
|
|
- </div>
|
|
|
- {demand.seedTerms.length ? <DemandLine label="Pattern 词" value={demand.seedTerms.join("、")} /> : null}
|
|
|
- {demand.categoryPaths.length ? <DemandList label="分类树路径" items={demand.categoryPaths} limit={3} /> : null}
|
|
|
- {demand.extractedPoints.length ? <DemandList label="帖子抽取点" items={demand.extractedPoints} limit={4} /> : null}
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- );
|
|
|
-}
|
|
|
-
|
|
|
-function DemandLine({ label, value }: { label: string; value?: string }) {
|
|
|
- if (!value) return null;
|
|
|
- return (
|
|
|
- <p className="demand-line">
|
|
|
- <b>{label}:</b>
|
|
|
- <span>{value}</span>
|
|
|
- </p>
|
|
|
- );
|
|
|
-}
|
|
|
-
|
|
|
-function DemandList({ label, items, limit }: { label: string; items: string[]; limit: number }) {
|
|
|
- const visible = items.slice(0, limit);
|
|
|
- const more = Math.max(0, items.length - visible.length);
|
|
|
- return (
|
|
|
- <div className="demand-list">
|
|
|
- <b>{label}:</b>
|
|
|
- <div>
|
|
|
- {visible.map((item) => <span key={item}>{item}</span>)}
|
|
|
- {more ? <em>还有 {more} 项</em> : null}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- );
|
|
|
+ const meta = [
|
|
|
+ demand.id ? `需求单:${demand.id}` : "",
|
|
|
+ demand.source ? `来源:${demand.source}` : "",
|
|
|
+ demand.sourcePostId ? `来源帖子:${demand.sourcePostId}` : "",
|
|
|
+ demand.itemsetIds.length ? `itemset:${demand.itemsetIds.join(" / ")}` : "",
|
|
|
+ demand.support ? `支持度:${demand.support}` : ""
|
|
|
+ ].filter(Boolean);
|
|
|
+ return {
|
|
|
+ title: `原始需求:${demand.name || demand.id || "待确认"}`,
|
|
|
+ sections: [
|
|
|
+ {
|
|
|
+ label: "需求说明",
|
|
|
+ value: demand.description || "暂无需求说明",
|
|
|
+ tone: "info"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "形成原因",
|
|
|
+ value: demand.reason || "暂无形成原因",
|
|
|
+ tone: "neutral"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "来源信息",
|
|
|
+ items: meta,
|
|
|
+ tone: "neutral"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "Pattern 词",
|
|
|
+ value: demand.seedTerms.length ? demand.seedTerms.join("、") : "暂无 Pattern 词",
|
|
|
+ tone: "good"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "分类树路径",
|
|
|
+ items: demand.categoryPaths.length ? demand.categoryPaths : ["暂无分类树路径"],
|
|
|
+ tone: "neutral"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
-function HeaderSummaryChips({ rows, dataOriginLabel }: { rows: FlowLedgerRow[]; dataOriginLabel: string }) {
|
|
|
+function HeaderSummaryChips({
|
|
|
+ rows,
|
|
|
+ dataOriginLabel,
|
|
|
+ demand,
|
|
|
+ onOpenDemand
|
|
|
+}: {
|
|
|
+ rows: FlowLedgerRow[];
|
|
|
+ dataOriginLabel: string;
|
|
|
+ demand?: DemandSummary;
|
|
|
+ onOpenDemand: (drawer: BusinessDrawerState) => void;
|
|
|
+}) {
|
|
|
return (
|
|
|
<div className="header-summary-chips">
|
|
|
<span className="chip blue">搜索词 {rows.length}</span>
|
|
|
@@ -142,6 +144,10 @@ function HeaderSummaryChips({ rows, dataOriginLabel }: { rows: FlowLedgerRow[];
|
|
|
<span className="chip yellow">待复看 {rows.reduce((sum, row) => sum + row.ruleSummary.reviewCount, 0)}</span>
|
|
|
<span className="chip">入池 {rows.reduce((sum, row) => sum + row.finalAssets.assetCount, 0)}</span>
|
|
|
<span className="chip blue">{dataOriginLabel}</span>
|
|
|
+ <button className="mini-button demand-detail-button" type="button" onClick={() => onOpenDemand(demandDrawer(demand))}>
|
|
|
+ <FileText size={13} />
|
|
|
+ 原始需求
|
|
|
+ </button>
|
|
|
</div>
|
|
|
);
|
|
|
}
|
|
|
@@ -244,10 +250,20 @@ function scoreRuleDrawer(row: FlowLedgerRow): BusinessDrawerState {
|
|
|
label: "两个评分项",
|
|
|
items: [
|
|
|
"是否契合需求(50%):看视频内容是否贴合本次需求和当前 query。",
|
|
|
- "平台表现(50%):看点赞、评论、转发等平台反馈综合表现。"
|
|
|
+ "平台表现(50%):抖音当前按点赞、评论、转发、收藏合成平台表现。"
|
|
|
],
|
|
|
tone: "neutral"
|
|
|
},
|
|
|
+ {
|
|
|
+ label: "平台表现的具体占比",
|
|
|
+ items: [
|
|
|
+ "点赞:占平台表现 40%,折算到总分是 20%。",
|
|
|
+ "评论:占平台表现 20%,折算到总分是 10%。",
|
|
|
+ "转发:占平台表现 20%,折算到总分是 10%。",
|
|
|
+ "收藏:占平台表现 20%,折算到总分是 10%。"
|
|
|
+ ],
|
|
|
+ tone: "info"
|
|
|
+ },
|
|
|
{
|
|
|
label: "怎么判定",
|
|
|
items: [
|