Pārlūkot izejas kodu

Move demand summary into drawer

Sam Lee 3 nedēļas atpakaļ
vecāks
revīzija
ee29cda6c0
2 mainītis faili ar 91 papildinājumiem un 68 dzēšanām
  1. 8 1
      web2/app/globals.css
  2. 83 67
      web2/features/LedgerPage.tsx

+ 8 - 1
web2/app/globals.css

@@ -127,6 +127,13 @@ a:focus-visible {
   margin: 0;
   margin: 0;
 }
 }
 
 
+.demand-detail-button {
+  min-height: 26px;
+  color: #1e3a8a;
+  border-color: #bfdbfe;
+  background: #eff6ff;
+}
+
 .toolbar-button,
 .toolbar-button,
 .icon-button,
 .icon-button,
 .mini-button {
 .mini-button {
@@ -461,7 +468,7 @@ a:focus-visible {
 
 
 .sticky-source {
 .sticky-source {
   position: sticky !important;
   position: sticky !important;
-  left: 300px;
+  left: 0;
   z-index: 3;
   z-index: 3;
 }
 }
 
 

+ 83 - 67
web2/features/LedgerPage.tsx

@@ -2,7 +2,7 @@
 
 
 import { useState } from "react";
 import { useState } from "react";
 import Link from "next/link";
 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 { AppShell } from "@/components/AppShell";
 import { BusinessDrawer, type BusinessDrawerState } from "@/components/BusinessDrawer";
 import { BusinessDrawer, type BusinessDrawerState } from "@/components/BusinessDrawer";
 import { EmptyState, ErrorState, LoadingState } from "@/components/StateBlocks";
 import { EmptyState, ErrorState, LoadingState } from "@/components/StateBlocks";
@@ -28,7 +28,14 @@ export function LedgerPage({ runId }: { runId: string }) {
   return (
   return (
     <AppShell
     <AppShell
       title="本次流程摘要"
       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}
       onRefresh={reload}
     >
     >
       {loading ? <LoadingState /> : null}
       {loading ? <LoadingState /> : null}
@@ -40,7 +47,6 @@ export function LedgerPage({ runId }: { runId: string }) {
             <table className="ledger-table">
             <table className="ledger-table">
               <thead>
               <thead>
                 <tr>
                 <tr>
-                  <th className="group-demand sticky-demand">原始需求</th>
                   <th className="group-source sticky-source">需求 -&gt; query</th>
                   <th className="group-source sticky-source">需求 -&gt; query</th>
                   <th className="group-videos">query -&gt; 渠道</th>
                   <th className="group-videos">query -&gt; 渠道</th>
                   <th className="group-rules">渠道 -&gt; 判断</th>
                   <th className="group-rules">渠道 -&gt; 判断</th>
@@ -51,7 +57,6 @@ export function LedgerPage({ runId }: { runId: string }) {
               <tbody>
               <tbody>
                 {rows.map((row) => (
                 {rows.map((row) => (
                   <tr className="ledger-row" key={row.id}>
                   <tr className="ledger-row" key={row.id}>
-                    <DemandCell demand={ledger.demandSummary} isFirst={row.id === rows[0]?.id} />
                     <SourceCell row={row} />
                     <SourceCell row={row} />
                     <VideoCell runId={runId} row={row} />
                     <VideoCell runId={runId} row={row} />
                     <RuleSummaryCell row={row} onOpenScore={setDrawer} />
                     <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) {
   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 (
   return (
     <div className="header-summary-chips">
     <div className="header-summary-chips">
       <span className="chip blue">搜索词 {rows.length}</span>
       <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 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">入池 {rows.reduce((sum, row) => sum + row.finalAssets.assetCount, 0)}</span>
       <span className="chip blue">{dataOriginLabel}</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>
     </div>
   );
   );
 }
 }
@@ -244,10 +250,20 @@ function scoreRuleDrawer(row: FlowLedgerRow): BusinessDrawerState {
         label: "两个评分项",
         label: "两个评分项",
         items: [
         items: [
           "是否契合需求(50%):看视频内容是否贴合本次需求和当前 query。",
           "是否契合需求(50%):看视频内容是否贴合本次需求和当前 query。",
-          "平台表现(50%):看点赞、评论、转发等平台反馈综合表现。"
+          "平台表现(50%):抖音当前按点赞、评论、转发、收藏合成平台表现。"
         ],
         ],
         tone: "neutral"
         tone: "neutral"
       },
       },
+      {
+        label: "平台表现的具体占比",
+        items: [
+          "点赞:占平台表现 40%,折算到总分是 20%。",
+          "评论:占平台表现 20%,折算到总分是 10%。",
+          "转发:占平台表现 20%,折算到总分是 10%。",
+          "收藏:占平台表现 20%,折算到总分是 10%。"
+        ],
+        tone: "info"
+      },
       {
       {
         label: "怎么判定",
         label: "怎么判定",
         items: [
         items: [