Skip to content

Commit 73aa2bc

Browse files
authored
feat(workspace): show analyzer effectiveness with source-bound evidence (#6700)
1 parent 72b4093 commit 73aa2bc

31 files changed

Lines changed: 1996 additions & 36 deletions
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
.research-analysis { margin-block: 16px; padding: 24px; border: 1px solid var(--pw-line); border-radius: 12px; min-width: 0; color: var(--pw-text); background: var(--pw-bg); font-size: var(--text-body); line-height: var(--leading-prose); overflow-wrap: anywhere; }
2+
.research-analysis > header, .analysis-source > header { display: flex; align-items: start; justify-content: space-between; gap: 16px; }
3+
.analysis-header-actions { display: flex; gap: 8px; flex-shrink: 0; }
4+
.research-analysis h3 { font-size: var(--text-heading); font-weight: 600; margin: 0; }
5+
.research-analysis h4 { font-size: var(--text-lead); font-weight: 600; line-height: 1.5; margin: 0 0 8px; }
6+
.research-analysis p { margin: 8px 0; }
7+
.research-analysis header p, .analysis-scope, .analysis-boundary, .research-analysis small, .analysis-method, .analysis-finding > span { color: var(--pw-muted); font-size: var(--text-caption); }
8+
.research-analysis button, .research-analysis select { font: inherit; color: inherit; border: 1px solid var(--pw-line); background: var(--pw-bg); border-radius: 6px; min-height: 44px; padding: 8px 12px; display: inline-flex; align-items: center; justify-content: center; gap: 6px; cursor: pointer; }
9+
.research-analysis button:disabled { opacity: .5; cursor: not-allowed; }
10+
.research-analysis button.analysis-primary { color: var(--pw-bg); background: var(--pw-text); border-color: var(--pw-text); }
11+
.analysis-pack-choice { display: flex; align-items: center; gap: 12px; margin-block: 16px; }
12+
.analysis-pack-choice select { min-width: 0; max-width: 100%; }
13+
.analysis-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; margin-block: 16px; }
14+
.analysis-actions p { display: flex; align-items: center; gap: 8px; }
15+
.analysis-actions small { flex-basis: 100%; }
16+
.analysis-warning { border-inline-start: 2px solid var(--pw-caution); padding-inline-start: 12px; }
17+
.analysis-error { color: var(--pw-red); }
18+
.analysis-finding { border-block-start: 1px solid var(--pw-line); padding-block: 20px 8px; margin-top: 20px; max-width: 76ch; }
19+
.analysis-finding h4 { margin-top: 8px; }
20+
.analysis-episode { padding-block: 16px; max-width: 90ch; }
21+
.analysis-episode-context { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; margin-block: 20px; }
22+
.analysis-episode-context strong, .analysis-episode-context small { display: block; }
23+
.analysis-episode-context strong { font-weight: 500; }
24+
.analysis-evidence-details > summary { cursor: pointer; min-height: 44px; }
25+
.analysis-judgments { border-top: 1px solid var(--pw-line); }
26+
.analysis-judgments details { border-bottom: 1px solid var(--pw-line); }
27+
.analysis-judgments summary { min-height: 48px; padding-block: 10px; cursor: pointer; display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
28+
.analysis-judgments summary strong { font-weight: 500; }
29+
.analysis-judgments summary span { color: var(--pw-muted); font-size: var(--text-caption); }
30+
.analysis-judgments details > p, .analysis-citations { margin: 4px 0 12px; }
31+
.analysis-question { color: var(--pw-muted); font-size: var(--text-caption); }
32+
.analysis-citations { display: flex; flex-wrap: wrap; gap: 8px; }
33+
.analysis-citations button { font-size: var(--text-caption); }
34+
.analysis-source { border: 1px solid var(--pw-line); border-radius: 8px; padding: 16px; margin-block: 16px; }
35+
.analysis-source p { font-size: var(--text-caption); color: var(--pw-muted); }
36+
.analysis-source pre { font-family: var(--font-mono); font-size: var(--text-caption); line-height: 1.7; white-space: pre-wrap; overflow-wrap: anywhere; max-height: 360px; overflow: auto; }
37+
.analysis-method { border-block-start: 1px solid var(--pw-line); margin-top: 16px; padding-top: 12px; }
38+
.analysis-method summary { min-height: 44px; display: flex; align-items: center; cursor: pointer; color: var(--pw-text); }
39+
.analysis-method dl { display: grid; grid-template-columns: 100px minmax(0, 1fr); gap: 8px 16px; }
40+
.analysis-method dd { margin: 0; color: var(--pw-text); }
41+
.analysis-spin { animation: analysis-request-spin 1s linear infinite; }
42+
@keyframes analysis-request-spin { to { transform: rotate(360deg); } }
43+
@media (prefers-reduced-motion: reduce) { .analysis-spin { animation: none; } }
44+
@media (max-width: 640px) { .research-analysis { padding: 16px; } .analysis-episode-context { grid-template-columns: 1fr; gap: 12px; } .analysis-method dl { grid-template-columns: 1fr; gap: 2px; } .analysis-method dd { margin-bottom: 8px; } .analysis-pack-choice { flex-direction: column; align-items: stretch; } }

‎apps/presentation/dashboard/src/features/personal-workspace/goal-research-analysis.tsx‎

Lines changed: 234 additions & 0 deletions
Large diffs are not rendered by default.

‎apps/presentation/dashboard/src/features/personal-workspace/goal-research-history.tsx‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {useEffect, useId, useMemo, useRef, useState} from "react";
22
import {defaultParseSearch} from "@tanstack/react-router";
3-
import {ArrowLeft, Circle, FlaskConical, GitBranch, RefreshCw, Sparkles} from "lucide-react";
3+
import {ArrowLeft, Circle, FlaskConical, GitBranch, RefreshCw} from "lucide-react";
4+
import {ResearchAnalysisButton, ResearchAnalysisPanel, ResearchAnalysisScope, ResearchProgressSummary, useResearchAnalysis} from "./goal-research-analysis";
45
import {buildExploreTimeline, parseExploreFindingSections} from "../../../../../../loopx/control_plane/presentation/explore_timeline.js";
56
import {GoalResearchTimeline} from "./goal-research-timeline";
67
import {requestJson} from "../../data/chat";
@@ -25,6 +26,7 @@ const selectedFromUrl = (goal: string): Selection => {
2526
/** An effect-free history consumer. Its source is Explore, not raw sessions or
2627
* a judge. All recorded updates survive; unsupported analysis stays unavailable. */
2728
export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0}: {goalId: string; active: boolean; zh: boolean; refreshRevision?: number}) {
29+
const analysis = useResearchAnalysis(goalId, active, zh);
2830
const [read, setRead] = useState<Read>({events: [], total: null, complete: false});
2931
const [busy, setBusy] = useState(true);
3032
const [error, setError] = useState("");
@@ -43,7 +45,6 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0}: {
4345
title: "研究历程", loading: "正在读取完整记录", reading: "读取中", refresh: "刷新记录",
4446
all: "回到全图", scope: "Explore 已记录历史", coverage: "其他来源覆盖、尝试发生时序尚未核验",
4547
partial: "历史尚未完整载入", failure: "无法继续读取历史。已读记录保留;请重试。",
46-
analysis: "深度分析", unavailable: "尚未接入轨迹评测;浏览历史不会调用 Agent。",
4748
time: "记录时间(本地)", unknown: "时间未记录", records: "条记录", evidence: "已记录依据",
4849
interpretation: "独立检查与实际采用尚未核验", versions: "来源与版本引用", updates: "历史记录",
4950
changes: "次记录", findings: "条结论", links: "显式关联", missing: "部分关联的来源节点尚未记录",
@@ -52,7 +53,6 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0}: {
5253
title: "Research history", loading: "Reading the full recorded history", reading: "Reading", refresh: "Refresh history",
5354
all: "Whole graph", scope: "Recorded Explore history", coverage: "Other-source coverage and attempt-time coverage are unverified",
5455
partial: "History is not fully loaded", failure: "Cannot continue reading history. Loaded records remain; retry.",
55-
analysis: "Deep analysis", unavailable: "Trajectory assessment is not connected. Reading history never invokes an Agent.",
5656
time: "Recorded time (local)", unknown: "Time not recorded", records: "records", evidence: "Recorded evidence",
5757
interpretation: "Independent checking and actual adoption are unverified", versions: "Source and version references", updates: "History",
5858
changes: "records", findings: "findings", links: "Explicit relationships", missing: "Some linked source nodes are not recorded",
@@ -165,20 +165,23 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0}: {
165165
});
166166
};
167167
const choose = (id: string, focus = true) => select({nodes: id ? [id] : [], day: null}, focus);
168-
if (!busy && !error && !read.events.length) return null;
168+
if (!busy && !error && !read.events.length && !analysis.pack && !analysis.snapshot?.catalog.packs.length) return null;
169169
return <section className="research-history" aria-label={copy.title} aria-busy={busy}>
170170
<header className="research-history-heading"><div><h2>{copy.title}</h2>
171171
<p>{copy.scope}{value?.startAt ? <> · {copy.time} <time>{timeLabel(value.startAt)}</time> — <time>{timeLabel(value.endAt)}</time></> : null}</p></div>
172172
<div className="research-history-controls">
173173
{selection.nodes.length || selection.day !== null ? <button type="button" onClick={() => {choose("", false); graph.current?.focus();}}><ArrowLeft size={14} aria-hidden/>{copy.all}</button> : null}
174174
<div className="research-lens" role="group" aria-label={zh ? "视图" : "View"}>{(["timeline", "graph"] as const).map(item => <button key={item} type="button" aria-pressed={lens === item} onClick={() => chooseLens(item)}>{item === "timeline" ? (zh ? "时间线" : "Timeline") : (zh ? "关系图" : "Graph")}</button>)}</div>
175-
<button type="button" disabled title={copy.unavailable} aria-describedby={`${presentationId}-analysis`}><Sparkles size={14} aria-hidden/>{copy.analysis}</button>
175+
<ResearchAnalysisButton analysis={analysis}/>
176176
<button type="button" aria-label={copy.refresh} disabled={busy} onClick={() => setRevision(n => n + 1)}><RefreshCw size={15} aria-hidden/></button>
177177
</div></header>
178178
<p className="research-history-coverage" role="status">{busy ? `${copy.reading} ${loaded}${read.total ? ` / ${read.total}` : ""}`
179179
: read.total === null ? (zh ? "来源尚未读到" : "Source has not been read")
180180
: `${read.events.length} / ${read.total} ${copy.records}${read.complete ? "" : ` · ${copy.partial}`}`} · {copy.coverage}</p>
181181
{error || history.error ? <p role="alert" className="research-history-error">{copy.failure} <small>{error || history.error}</small></p> : null}
182+
<ResearchAnalysisScope analysis={analysis}/>
183+
<ResearchProgressSummary analysis={analysis}/>
184+
<ResearchAnalysisPanel analysis={analysis}/>
182185
{busy && !read.events.length ? <div className="research-history-loading" role="status">{copy.loading}…</div> : null}
183186
{value?.nodes.length ? <div className={`research-history-layout${selected || selectedMembers?.length ? " has-selection" : ""}`}>
184187
{lens === "timeline" && timeline ? <div className="research-history-canvas is-timeline" ref={graph} tabIndex={-1}>
@@ -249,6 +252,5 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0}: {
249252
{recordsOpen ? <><ol className="research-history-records">{value.events.slice(0, recordLimit).map(event => <li key={event.event_id}><time>{timeLabel(event.recorded_at)}</time><span>{event.title} · {stateLabel(event.status)}{event.summary ? ` — ${event.summary}` : ""}</span>{event.parent_id ? <code>{copy.group}: {event.parent_id}</code> : null}{event.evidence_refs.map(ref => <code key={ref}>{ref}</code>)}{event.run_id ? <code>{event.run_id}</code> : null}</li>)}</ol>
250253
{recordLimit < value.events.length ? <button type="button" onClick={() => setRecordLimit(n => n + 50)}>{zh ? "继续展开记录" : "Show more records"} · {Math.min(recordLimit, value.events.length)} / {value.events.length}</button> : null}</> : null}
251254
</details> : null}
252-
<p className="research-history-note" id={`${presentationId}-analysis`}>{copy.unavailable}</p>
253255
</section>;
254256
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
.research-progress { margin-block: 16px; padding-block: 16px; border-block: 1px solid var(--pw-line); color: var(--pw-text); min-width: 0; overflow-wrap: anywhere; }
2+
.research-progress > header { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
3+
.research-progress h3 { margin: 0; font-size: var(--text-body); font-weight: 600; }
4+
.research-progress label { display: flex; align-items: center; gap: 8px; min-width: 0; font-size: var(--text-caption); }
5+
.research-progress select { min-width: 0; max-width: 400px; min-height: 44px; border: 1px solid var(--pw-line); border-radius: 6px; padding: 8px; background: var(--pw-bg); color: var(--pw-text); font: inherit; }
6+
.research-progress p, .research-progress small { color: var(--pw-muted); font-size: var(--text-caption); line-height: var(--leading-prose); }
7+
.research-progress summary { min-height: 44px; padding-block: 12px; cursor: pointer; font-size: var(--text-caption); }
8+
.research-effect-metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px 24px; }
9+
.research-effect-metric { border-block-end: 1px solid var(--pw-line); min-width: 0; }
10+
.research-effect-metric[open] { grid-column: 1 / -1; }
11+
.research-effect-metric > summary { display: grid; gap: 4px; list-style: none; }
12+
.research-effect-metric > summary::-webkit-details-marker { display: none; }
13+
.research-effect-title { display: flex; align-items: center; justify-content: space-between; gap: 8px; color: var(--pw-muted); }
14+
.research-effect-metric[open] .research-effect-title svg { transform: rotate(180deg); }
15+
.research-effect-number { font-size: var(--text-heading); font-weight: 600; font-variant-numeric: tabular-nums; line-height: 1.2; }
16+
.research-effect-verdict { font-size: var(--text-caption); }
17+
.research-effect-metric small { font-size: var(--text-caption); }
18+
.research-effect-evidence { padding-block-end: 12px; }
19+
.research-effect-evidence h4 { font-size: var(--text-caption); line-height: var(--leading-prose); margin: 8px 0; }
20+
.research-effect-evidence article { border-block-start: 1px solid var(--pw-line); padding-block: 8px; }
21+
.research-effect-evidence article > span { font-size: var(--text-caption); }
22+
.research-effect-citations { display: flex; flex-wrap: wrap; gap: 4px; }
23+
.research-effect-citations button { display: inline-flex; align-items: center; gap: 4px; min-height: 44px; font: inherit; font-size: var(--text-caption); background: transparent; border: 1px solid var(--pw-line); border-radius: 6px; padding: 4px 8px; color: var(--pw-text); }
24+
.research-progress-basis > dl, .research-usage-basis { display: grid; grid-template-columns: 120px minmax(0, 1fr); gap: 8px 16px; font-size: var(--text-caption); }
25+
.research-progress-basis dt, .research-usage-basis dt { color: var(--pw-muted); }
26+
.research-progress-basis dd, .research-usage-basis dd { margin: 0; }
27+
.research-progress code { font: inherit; font-family: var(--font-mono); }
28+
.research-progress-values { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; margin-block: 16px; }
29+
.research-progress-values > div { min-width: 0; }
30+
.research-progress-values dt { font-size: var(--text-caption); color: var(--pw-muted); }
31+
.research-progress-values dd { margin: 4px 0; font-size: var(--text-body); font-weight: 600; font-variant-numeric: tabular-nums; }
32+
.research-progress-warning { border-inline-start: 2px solid var(--pw-caution); padding-inline-start: 12px; }
33+
@media (max-width: 640px) {
34+
.research-progress > header { flex-direction: column; gap: 8px; }
35+
.research-progress label { flex-direction: column; align-items: stretch; width: 100%; }
36+
.research-progress select { max-width: 100%; }
37+
.research-effect-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 16px; }
38+
.research-progress-values { grid-template-columns: 1fr; }
39+
.research-progress-basis > dl, .research-usage-basis { grid-template-columns: 1fr; gap: 4px; }
40+
.research-progress-basis dd, .research-usage-basis dd { margin-bottom: 8px; }
41+
}

0 commit comments

Comments
 (0)