|
|
@@ -44,8 +44,6 @@ type DashboardData = {
|
|
|
contentItems: ContentItemsResponse;
|
|
|
timeline: TimelineResponse;
|
|
|
runtimeFiles: RuntimeFilesResponse;
|
|
|
- sourceContext: RuntimeFileResponse | null;
|
|
|
- patternSeed: RuntimeFileResponse | null;
|
|
|
};
|
|
|
|
|
|
type DrawerContent =
|
|
|
@@ -54,16 +52,6 @@ type DrawerContent =
|
|
|
| { kind: "walk"; title: string; payload: unknown }
|
|
|
| null;
|
|
|
|
|
|
-async function optionalRuntimeFile(runId: string, filename: string): Promise<RuntimeFileResponse | null> {
|
|
|
- try {
|
|
|
- return await getRuntimeFile(runId, filename, 80);
|
|
|
- } catch (err) {
|
|
|
- // 可选文件加载失败不再静默:控制台留痕,页面对应区块显示"缺失"。
|
|
|
- console.warn(`runtime file load failed: ${filename}`, err);
|
|
|
- return null;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
export function RunDashboardPage({ runId }: { runId: string }) {
|
|
|
const [activeStage, setActiveStage] = useState("walk");
|
|
|
const [data, setData] = useState<DashboardData | null>(null);
|
|
|
@@ -76,16 +64,14 @@ export function RunDashboardPage({ runId }: { runId: string }) {
|
|
|
setLoading(true);
|
|
|
setError(null);
|
|
|
try {
|
|
|
- const [dashboard, queries, contentItems, timeline, runtimeFiles, sourceContext, patternSeed] = await Promise.all([
|
|
|
+ const [dashboard, queries, contentItems, timeline, runtimeFiles] = await Promise.all([
|
|
|
getDashboard(runId),
|
|
|
getQueries(runId),
|
|
|
getContentItems(runId),
|
|
|
getTimeline(runId),
|
|
|
- getRuntimeFiles(runId),
|
|
|
- optionalRuntimeFile(runId, "source_context.json"),
|
|
|
- optionalRuntimeFile(runId, "pattern_seed_pack.json")
|
|
|
+ getRuntimeFiles(runId)
|
|
|
]);
|
|
|
- setData({ dashboard, queries, contentItems, timeline, runtimeFiles, sourceContext, patternSeed });
|
|
|
+ setData({ dashboard, queries, contentItems, timeline, runtimeFiles });
|
|
|
} catch (err) {
|
|
|
setError(err instanceof Error ? err.message : String(err));
|
|
|
} finally {
|
|
|
@@ -207,9 +193,9 @@ function RunMetaInline({ runId, timeline }: { runId: string; timeline: TimelineR
|
|
|
);
|
|
|
}
|
|
|
|
|
|
-// 激进合一:被折叠的 query/platform/judge 漏斗段点击后跳「发现旅程」。
|
|
|
+// 激进合一:被折叠的漏斗段(数据源/query/platform/judge)点击后跳「发现旅程」。
|
|
|
function funnelTarget(stageId: string): string {
|
|
|
- if (stageId === "query" || stageId === "platform" || stageId === "judge") return "walk";
|
|
|
+ if (stageId === "source" || stageId === "query" || stageId === "platform" || stageId === "judge") return "walk";
|
|
|
return stageId;
|
|
|
}
|
|
|
|
|
|
@@ -246,7 +232,6 @@ function FunnelStrip({
|
|
|
|
|
|
// 第二层:5 面板导航(前端常量驱动)。发现旅程卡副行挂 Query/内容/判定 三计数。
|
|
|
const PANELS: Array<{ id: string; label: string }> = [
|
|
|
- { id: "source", label: "需求" },
|
|
|
{ id: "walk", label: "发现旅程" },
|
|
|
{ id: "asset", label: "内容资产" },
|
|
|
{ id: "learning", label: "学习复盘" },
|
|
|
@@ -293,81 +278,6 @@ function stageCount(dashboard: DashboardResponse, stageId: string): number | nul
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
-function runtimeData(file: RuntimeFileResponse | null): Record<string, unknown> {
|
|
|
- if (!file?.data || typeof file.data !== "object") {
|
|
|
- return {};
|
|
|
- }
|
|
|
- return file.data as Record<string, unknown>;
|
|
|
-}
|
|
|
-
|
|
|
-function evidencePackFrom(sourceContext: Record<string, unknown>): Record<string, unknown> {
|
|
|
- const extData = sourceContext.ext_data;
|
|
|
- if (extData && typeof extData === "object" && "evidence_pack" in extData) {
|
|
|
- const evidencePack = (extData as Record<string, unknown>).evidence_pack;
|
|
|
- return evidencePack && typeof evidencePack === "object" ? evidencePack as Record<string, unknown> : {};
|
|
|
- }
|
|
|
- return {};
|
|
|
-}
|
|
|
-
|
|
|
-function firstCategoryPath(patternSeed: Record<string, unknown>): string {
|
|
|
- const bindings = Array.isArray(patternSeed.category_bindings) ? patternSeed.category_bindings : [];
|
|
|
- const first = bindings[0];
|
|
|
- if (first && typeof first === "object") {
|
|
|
- return compactValue((first as Record<string, unknown>).category_path || (first as Record<string, unknown>).category_full_path);
|
|
|
- }
|
|
|
- const itemsets = Array.isArray(patternSeed.itemsets) ? patternSeed.itemsets : [];
|
|
|
- const firstItemset = itemsets[0];
|
|
|
- if (firstItemset && typeof firstItemset === "object") {
|
|
|
- return compactValue((firstItemset as Record<string, unknown>).category_path);
|
|
|
- }
|
|
|
- return "缺失";
|
|
|
-}
|
|
|
-
|
|
|
-function SourceEvidenceSummary({
|
|
|
- sourceContext,
|
|
|
- patternSeed
|
|
|
-}: {
|
|
|
- sourceContext: RuntimeFileResponse | null;
|
|
|
- patternSeed: RuntimeFileResponse | null;
|
|
|
-}) {
|
|
|
- const source = runtimeData(sourceContext);
|
|
|
- const seed = runtimeData(patternSeed);
|
|
|
- const evidencePack = evidencePackFrom(source);
|
|
|
- const extData = source.ext_data && typeof source.ext_data === "object"
|
|
|
- ? source.ext_data as Record<string, unknown>
|
|
|
- : {};
|
|
|
- const seedTerms = Array.isArray(seed.seed_terms) ? seed.seed_terms : evidencePack.seed_terms;
|
|
|
- const matchedPostIds = Array.isArray(seed.matched_post_ids) ? seed.matched_post_ids : evidencePack.matched_post_ids;
|
|
|
- return (
|
|
|
- <div className="source-summary-grid">
|
|
|
- <div>
|
|
|
- <span>需求名称</span>
|
|
|
- <strong>{compactValue(source.name || extData.type)}</strong>
|
|
|
- <small>{compactValue(extData.desc || extData.reason)}</small>
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <span>Pattern 来源</span>
|
|
|
- <strong>{compactValue(seed.pattern_source_system || evidencePack.pattern_source_system)}</strong>
|
|
|
- <small>Pattern 执行 ID:{compactValue(seed.pattern_execution_id || evidencePack.pattern_execution_id)}</small>
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <span>种子词</span>
|
|
|
- <strong>{compactValue(seedTerms)}</strong>
|
|
|
- <small>Itemset:{compactValue(seed.itemset_ids || evidencePack.itemset_ids)}</small>
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <span>Pattern 分类路径</span>
|
|
|
- <strong>{firstCategoryPath(seed)}</strong>
|
|
|
- <small>来源样本:{compactValue(seed.source_post_id || evidencePack.source_post_id)}</small>
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <span>证据规模</span>
|
|
|
- <strong>{Array.isArray(matchedPostIds) ? `${matchedPostIds.length} 条匹配样本` : "缺失"}</strong>
|
|
|
- <small>验证状态:{compactValue(seed.validation_status || evidencePack.validation_status || source.validation_status)}</small>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- );
|
|
|
-}
|
|
|
|
|
|
function StagePanel({
|
|
|
activeStage,
|
|
|
@@ -382,24 +292,6 @@ function StagePanel({
|
|
|
onOpenRuntimeFile: (filename: string) => void;
|
|
|
onOpenDrawer: (drawer: DrawerContent) => void;
|
|
|
}) {
|
|
|
- if (activeStage === "source") {
|
|
|
- return (
|
|
|
- <BusinessSection title="数据源结论" icon={<Target size={17} />}>
|
|
|
- <ConclusionBody stage={data.dashboard.stage_conclusions.find((stage) => stage.stage_id === "source")} />
|
|
|
- <SourceEvidenceSummary sourceContext={data.sourceContext} patternSeed={data.patternSeed} />
|
|
|
- <div className="business-action-row">
|
|
|
- <button className="text-button" onClick={() => onOpenRuntimeFile("source_context.json")} type="button">
|
|
|
- <FileJson size={15} />
|
|
|
- 查看需求证据
|
|
|
- </button>
|
|
|
- <button className="text-button" onClick={() => onOpenRuntimeFile("pattern_seed_pack.json")} type="button">
|
|
|
- <FileJson size={15} />
|
|
|
- 查看 Pattern 种子
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </BusinessSection>
|
|
|
- );
|
|
|
- }
|
|
|
if (activeStage === "walk") {
|
|
|
return (
|
|
|
<BusinessSection title="内容发现旅程" icon={<GitBranch size={17} />}>
|