@@ -9,6 +9,7 @@ import {sourceReadApiUrl} from "../../data/local-status-query";
99import { ContextHelp } from "./context-help" ;
1010import { groupUnlinkedHistoryQuestions , layoutExploreHistory , parseExploreHistoryPage , projectExploreHistory , type ExploreHistoryEvent } from "../../../../../../loopx/control_plane/presentation/explore_history.js" ;
1111import "./goal-research-history.css" ;
12+ import { ExploreMeasurements , formatExploreMeasurementValue } from "./explore-measurements" ;
1213
1314type Selection = { nodes : string [ ] ; day : string | null } ;
1415type Lens = "timeline" | "graph" ;
@@ -78,7 +79,7 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0, re
7879 const decision = sections ?. decision ;
7980 const execution = finding . execution_observation ;
8081 const measured = ( value : unknown ) => value === null ? ( zh ? "未测量" : "Not measured" ) : String ( value ) ;
81- return < > { sections ? < dl className = "research-finding-sections" >
82+ return < > { finding . measurements ? < ExploreMeasurements measurements = { finding . measurements } zh = { zh } /> : null } { sections ? < dl className = "research-finding-sections" >
8283 < dt > { sectionCopy . observation } </ dt > < dd > { sections . observation } </ dd >
8384 { decision ? < >
8485 < dt > { zh ? "决策" : "Decision" } </ dt > < dd > < b className = { `research-outcome is-${ decision . outcome } ` } > { outcomeCopy [ decision . outcome ] ?? decision . outcome } </ b > </ dd >
@@ -99,7 +100,7 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0, re
99100 </ details > : null } </ > ;
100101 } ;
101102 const findingElement = ( finding : ExploreHistoryEvent ) => < div className = "research-recorded-finding" key = { finding . event_id } > < strong > { finding . title } </ strong > < small > { timeLabel ( finding . recorded_at ) } · { zh ? "来源标记" : "Source label" } · { stateLabel ( finding . status ) } { finding . confidence !== null ? ` · ${ zh ? "来源自报置信度" : "Source-reported confidence" } ${ finding . confidence } ` : "" } </ small > { findingBody ( finding ) } { finding . evidence_refs . map ( ref => < code key = { ref } > { ref } </ code > ) } </ div > ;
102- const updateRecords = ( events : readonly ExploreHistoryEvent [ ] ) => < ol className = "research-history-records" > { events . map ( event => < li key = { event . event_id } > < time > { timeLabel ( event . recorded_at ) } </ time > < span > { stateLabel ( event . status ) } · { event . title } { 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 > ;
103+ const updateRecords = ( events : readonly ExploreHistoryEvent [ ] ) => < ol className = "research-history-records" > { events . map ( event => < li key = { event . event_id } > < time > { timeLabel ( event . recorded_at ) } </ time > < span > { stateLabel ( event . status ) } · { event . title } { event . summary ? ` — ${ event . summary } ` : "" } </ span > { event . measurements ? < ExploreMeasurements measurements = { event . measurements } zh = { zh } /> : null } { 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 > ;
103104
104105 useEffect ( ( ) => {
105106 const pop = ( ) => setSelection ( selectedFromUrl ( goalId ) ) ;
@@ -147,6 +148,12 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0, re
147148 catch ( failure ) { return { value : null , error : String ( failure ) } ; }
148149 } , [ read . events , goalId ] ) ;
149150 const value = history . value ;
151+ const measurementFeedback = value ?. measurementFeedback ?? [ ] ;
152+ const measurementSummary = ( nodeId : string ) => {
153+ const feedback = measurementFeedback . filter ( item => item . node_id === nodeId ) ;
154+ return feedback . length ? < ExploreMeasurements title = { zh ? "最近记录与历史最佳" : "Latest recorded and historical best" }
155+ measurements = { feedback . map ( item => item . latest . measurement ) } feedback = { feedback } zh = { zh } /> : null ;
156+ } ;
150157 const grouped = useMemo ( ( ) => groupUnlinkedHistoryQuestions ( value ?. nodes ?? [ ] , value ?. edges ?? [ ] ) , [ value ] ) ;
151158 const layout = useMemo ( ( ) => layoutExploreHistory ( grouped . nodes , value ?. edges ?? [ ] ) , [ grouped , value ] ) ;
152159 const timeline = useMemo ( ( ) => value ? buildExploreTimeline ( value . nodes , value . edges , value . events ) : null , [ value ] ) ;
@@ -223,11 +230,15 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0, re
223230 return < path key = { `parent:${ item . node . id } ` } className = "is-grouping" d = { `M${ parent . x + 206 } ,${ parent . y + 47 } C${ parent . x + 226 } ,${ parent . y + 47 } ${ item . x - 20 } ,${ item . y + 47 } ${ item . x } ,${ item . y + 47 } ` } /> ;
224231 } ) }
225232 </ svg >
226- { layout . map ( ( { node, x, y} ) => < button key = { node . id } type = "button" className = { `research-history-node is-${ node . latest . status } ${ selection . nodes . includes ( node . id ) ? " is-selected" : "" } ` }
233+ { layout . map ( ( { node, x, y} ) => {
234+ const measured = measurementFeedback . find ( item => item . node_id === node . id ) ;
235+ return < button key = { node . id } type = "button" className = { `research-history-node is-${ node . latest . status } ${ selection . nodes . includes ( node . id ) ? " is-selected" : "" } ` }
227236 style = { { left : x , top : y } } aria-pressed = { selection . nodes . includes ( node . id ) } aria-label = { `${ node . latest . title } · ${ stateLabel ( node . latest . status ) } ` } onClick = { ( ) => choose ( node . id ) } >
228237 < span className = "research-node-meta" > { node . latest . node_kind === "experiment" ? < FlaskConical size = { 13 } aria-hidden /> : < Circle size = { 12 } aria-hidden /> } { kindLabel ( node . latest . node_kind ) } < small > { stateLabel ( node . latest . status ) } </ small > </ span >
229- < strong > { node . latest . title } </ strong > < span className = "research-node-count" > < time > { timeLabel ( node . updates [ 0 ] ?. recorded_at ) } </ time > { node . updates . length > 1 ? ` · ${ node . updates . length } ${ copy . changes } ` : "" } { node . findings . length ? ` · ${ node . findings . length } ${ copy . findings } ` : "" } </ span >
230- </ button > ) }
238+ < strong > { node . latest . title } </ strong > { measured ? < span className = "research-node-count" title = { `${ measured . metric } (${ measured . unit } ) · ${ measured . comparison_scope } · ${ measured . evaluation_ref } ` } >
239+ { measured . metric } { formatExploreMeasurementValue ( measured . latest . measurement . value , zh ) } · best { formatExploreMeasurementValue ( measured . best ?. value ?? null , zh ) } { measured . higher_is_better ? "↑" : "↓" }
240+ </ span > : < span className = "research-node-count" > < time > { timeLabel ( node . updates [ 0 ] ?. recorded_at ) } </ time > { node . updates . length > 1 ? ` · ${ node . updates . length } ${ copy . changes } ` : "" } { node . findings . length ? ` · ${ node . findings . length } ${ copy . findings } ` : "" } </ span > }
241+ </ button > } ) }
231242 { grouped . groups . map ( ( group , index ) => < button type = "button" key = { `day:${ group . day } ` } className = { `research-history-node research-history-group${ selectedGroup === group ? " is-selected" : "" } ` } style = { { left : ( index % 4 ) * 230 + 24 , top : clusterY + Math . floor ( index / 4 ) * 124 } }
232243 aria-pressed = { selectedGroup === group } onClick = { ( ) => select ( { nodes : [ ] , day : group . day } ) } >
233244 < span className = "research-node-meta" > < Circle size = { 12 } aria-hidden /> { zh ? "未关联问题" : "Unlinked questions" } </ span >
@@ -239,6 +250,7 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0, re
239250 < p className = "research-detail-kind" > < GitBranch size = { 14 } aria-hidden /> { kindLabel ( selected . latest . node_kind ) } · { stateLabel ( selected . latest . status ) } </ p >
240251 < h3 > { selected . latest . title } </ h3 > < p > { selected . latest . summary || ( zh ? "此节点尚无详细说明。" : "No detail was recorded for this node." ) } </ p >
241252 { selected . latest . blocked_reason ? < p className = "research-history-error" > { selected . latest . blocked_reason } </ p > : null }
253+ { measurementSummary ( selected . id ) }
242254 < h4 > { copy . evidence } </ h4 >
243255 { selected . findings . length ? selected . findings . map ( findingElement ) : < p > { zh ? "此节点没有附属结论记录。" : "No finding is attached to this node." } </ p > }
244256 < h4 > { copy . versions } </ h4 > { selected . latest . evidence_refs . length ? selected . latest . evidence_refs . map ( ref => < code key = { ref } > { ref } </ code > ) : < p > { zh ? "产物版本与来源引用未记录。" : "Artifact version and source references were not recorded." } </ p > }
@@ -249,6 +261,7 @@ export function GoalResearchHistory({goalId, active, zh, refreshRevision = 0, re
249261 { selectedMembers ?. length ? < div className = "research-history-detail" ref = { detail } role = "region" aria-label = { selectedGroup ? ( zh ? "按日期收纳的未关联问题" : "Unlinked questions by date" ) : ( zh ? "相邻研究项的全部记录" : "All records for nearby research items" ) } tabIndex = { - 1 } onKeyDown = { event => { if ( event . key === "Escape" ) { event . stopPropagation ( ) ; choose ( "" , false ) ; graph . current ?. focus ( ) ; } } } >
250262 < h3 > { selectedGroup ? `${ selectedGroup . day || copy . unknown } · ${ zh ? "未关联问题" : "Unlinked questions" } ` : ( zh ? `相邻研究项 · ${ selectedMembers . length } ` : `Nearby research items · ${ selectedMembers . length } ` ) } </ h3 >
251263 { selectedMembers . map ( node => < article className = "research-history-group-member" key = { node . id } data-research-node = { node . id } > < h4 > { node . latest . title } </ h4 > < p > { stateLabel ( node . latest . status ) } · { node . latest . summary } </ p > { node . latest . evidence_refs . map ( ref => < code key = { ref } > { ref } </ code > ) }
264+ { measurementSummary ( node . id ) }
252265 { node . findings . map ( findingElement ) }
253266 { updateRecords ( node . revisions ) }
254267 </ article > ) }
0 commit comments