diff --git a/apps/presentation/dashboard/smoke/research-analysis-read-smoke.mjs b/apps/presentation/dashboard/smoke/research-analysis-read-smoke.mjs index cfb660366f..4e08f6317b 100644 --- a/apps/presentation/dashboard/smoke/research-analysis-read-smoke.mjs +++ b/apps/presentation/dashboard/smoke/research-analysis-read-smoke.mjs @@ -11,7 +11,7 @@ import {resolveTestPython} from "../../../../scripts/test-python.mjs"; import {readAnalysisSnapshot, ResearchAnalysisPanel} from "../src/features/personal-workspace/goal-research-analysis.tsx"; import {ResearchCaseStudy} from "../src/features/personal-workspace/research-case-study.tsx"; import {fetchExploreResults} from "../src/data/chat.ts"; -import {ResearchGoalNavigation} from "../src/features/personal-workspace/goal-research-workbench.tsx"; +import {GoalResearchWorkbench} from "../src/features/personal-workspace/goal-research-workbench.tsx"; // The persisted directory can organize explicitly linked physical sources. // Navigation must not turn organization into shared evidence or execution. @@ -30,8 +30,9 @@ const goals = [ {goalId:'child',parentGoalId:'parent',title:'Local case'}, {goalId:'foreign',parentGoalId:'parent',title:'Foreign case',sourceAccess:{namespace:'other',label:'Other',readOnly:true}}, ]; -const navigate = goalId => renderToStaticMarkup(createElement(ResearchGoalNavigation, { - goalId, goals, zh:false, onSelect: () => {throw Error('read navigated');}, +const navigate = goalId => renderToStaticMarkup(createElement(GoalResearchWorkbench, { + goalId, goals, zh: false, active: true, refreshRevision: 0, + statusNotice: createElement("p", null, "Loading status"), onSelectGoal: () => {throw Error('read navigated');}, })); assert.ok(navigate('parent').includes('Local case')); assert.ok(navigate('parent').includes('Foreign case')); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-analysis.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-analysis.tsx index 211cb001b7..085f9f582b 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-analysis.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-analysis.tsx @@ -572,7 +572,8 @@ export function ResearchAnalysisPanel({analysis: a, onHistory}: {analysis: Contr : benefit?.unknown ? (zh ? "原目标收益的检验情况仍未知" : "Original-objective utility testing remains unknown") : benefit?.yes ? (zh ? "模型记录了收益检验;这不等于收益为正" : "The model identified a utility test; this does not imply positive utility") : (zh ? "此方法未覆盖原目标收益检验" : "This method does not cover original-objective utility testing"); - const scope = p ? <>{p.episode_count} {zh ? "个研究片段" : "research episodes"} · {p.selection.exhaustive ? (zh ? "固定范围" : "Fixed scope") : (zh ? "定向抽样" : "Purposive sample")} : null; + const episodeLabel = result ? (zh ? "个研究片段" : "research episodes") : (zh ? "个输入片段" : "input episodes"); + const scope = p ? <>{p.episode_count} {episodeLabel} · {p.selection.exhaustive ? (zh ? "固定范围" : "Fixed scope") : (zh ? "定向抽样" : "Purposive sample")} : null; const scopeHelp = p ? {result?.synthesis ?

{zh ? "Agent 分析 · 未校准" : "Agent analysis · uncalibrated"}

: null}

{p.selection.exhaustive ? (zh ? "覆盖此固定分析范围;不等于覆盖全部研究。" : "Covers this fixed analysis scope, not necessarily the whole research history.") : (zh ? "定向抽样,仅分析选定研究片段;范围外或未捕获的工作未纳入,不代表全程。" : "Purposive sample of selected episodes; work outside this scope or not captured is excluded. It does not represent the whole run.")}

diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.tsx index b80de116f5..62b963b118 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-progress.tsx @@ -281,19 +281,27 @@ export function ResearchProgress({pack: p, packs, selectedId, zh, onSelect, onCi const incomplete = usage?.incomplete_turn; const coverage = usage?.request_coverage; const number = (n: number) => new Intl.NumberFormat(zh ? "zh-CN" : "en", {maximumFractionDigits: 1}).format(n); + const scope = p ? `${number(p.episode_count)} ${zh ? "个片段" : "episodes"} · ${p.selection.exhaustive ? (zh ? "固定范围" : "Fixed scope") : (zh ? "定向抽样" : "Purposive sample")}` : ""; const duration = (s: number) => `${number(s / 60)} ${zh ? "分钟" : "min"}`; const stamp = (s: string) => new Intl.DateTimeFormat(zh ? "zh-CN" : "en", {timeZone: "UTC", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false}).format(new Date(s)); return

{p?.native_outcomes ? (zh ? "研究结果" : "Research results") : (zh ? "研究效果判读" : "Research effectiveness")}

{historical ?

{zh ? "来源或方法已变化;历史捕获与判读不能当作当前结果。" : "The source or method changed; historical captures and judgments cannot establish current results."}

: null} - {p && !result ?

{zh ? "可先浏览已采集的测量与记录;深度分析需单独点击,不会随浏览自动运行。" : "Captured measurements and records remain readable. Deep analysis requires a separate action and never runs while browsing."}

: null} + {p && !result ? <> +

{zh ? "分析输入" : "Analysis input"} · {scope}

+

{zh ? "可先浏览已采集的测量与记录;深度分析需单独点击,不会随浏览自动运行。" : "Captured measurements and records remain readable. Deep analysis requires a separate action and never runs while browsing."}

+ : null} {p ?

{p.title}

: null} -

{zh ? "分析器判读未校准,仅限所选片段;范围外或未捕获的工作未纳入。数字为“是 / 适用片段”,未知仍在分母中;点选指标可看依据。" : "Uncalibrated judgments of selected episodes only; work outside this scope or not captured is excluded. Counts are yes / eligible episodes, including unknowns. Select a metric for evidence."}

-

{zh ? "不适用来自原始片段的明确取样规则,不算“否”或“未知”;展开指标可看被排除的片段。" : "Ineligible episodes follow the source's explicit selection, not a no or unknown judgment. Expand a metric to identify them."}

-

{zh ? "收益检验的“是”不保证收益为正;无益重复的“是”表示发现问题。六项分别判断,不合成总分、成功率或原生收益。" : "Yes for benefit testing does not imply positive utility; yes for repetition identifies a problem. The six judgments are not a total score, success rate or native utility."}

-

{p?.selection.exhaustive ? (zh ? "仅覆盖声明的固定分析范围,不等于覆盖全部研究。" : "Covers the declared fixed analysis scope, not necessarily the whole study.") : p?.selection.population_episodes == null ? (zh ? "定向抽样,全程覆盖未知。" : "Purposive sample; whole-run coverage is unknown.") : (zh ? "定向抽样,不代表全程。" : "Purposive sample, not the whole run.")}

-

{zh ? "原生测量缺失时保持未采集;分析器判读不能补成实际收益。完整取样规则与版本在“来源与用量”中。" : "Missing native measurements remain uncaptured; judgments cannot supply realized utility. Full selection rules and revisions are under Sources and usage."}

-
{p ?

{number(p.episode_count)} {zh ? "个片段" : "episodes"} · {p.selection.exhaustive ? (zh ? "固定范围" : "Fixed scope") : (zh ? "定向抽样" : "Purposive sample")}{result ? (zh ? " · 未校准" : " · Uncalibrated") : ""}

: null}
{packs.length > 1 ? : null}
+ {result ? <> +

{zh ? "分析器判读未校准,仅限所选片段;范围外或未捕获的工作未纳入。数字为“是 / 适用片段”,未知仍在分母中;点选指标可看依据。" : "Uncalibrated judgments of selected episodes only; work outside this scope or not captured is excluded. Counts are yes / eligible episodes, including unknowns. Select a metric for evidence."}

+

{zh ? "不适用来自原始片段的明确取样规则,不算“否”或“未知”;展开指标可看被排除的片段。" : "Ineligible episodes follow the source's explicit selection, not a no or unknown judgment. Expand a metric to identify them."}

+

{zh ? "收益检验的“是”不保证收益为正;无益重复的“是”表示发现问题。六项分别判断,不合成总分、成功率或原生收益。" : "Yes for benefit testing does not imply positive utility; yes for repetition identifies a problem. The six judgments are not a total score, success rate or native utility."}

+ : null} + {p ? <> +

{p.selection.exhaustive ? (zh ? "仅覆盖声明的固定分析范围,不等于覆盖全部研究。" : "Covers the declared fixed analysis scope, not necessarily the whole study.") : p.selection.population_episodes == null ? (zh ? "定向抽样,全程覆盖未知。" : "Purposive sample; whole-run coverage is unknown.") : (zh ? "定向抽样,不代表全程。" : "Purposive sample, not the whole run.")}

+

{zh ? "原生测量缺失时保持未采集;分析器判读不能补成实际收益。完整取样规则与版本在“来源与用量”中。" : "Missing native measurements remain uncaptured; judgments cannot supply realized utility. Full selection rules and revisions are under Sources and usage."}

+ :

{zh ? "当前没有可读的分析范围。" : "No analysis scope is available."}

} + {p ?

{result ? `${scope}${zh ? " · 未校准" : " · Uncalibrated"}` : (zh ? "尚无分析器判读" : "No analyzer judgments yet")}

: null}{packs.length > 1 ? : null} {p ? <> {p.native_outcomes ? : null} {p.native_outcomes ? : null} diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-workbench.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-workbench.tsx index c9da132892..852461c8d1 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/goal-research-workbench.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-research-workbench.tsx @@ -1,4 +1,4 @@ -import {useState} from "react"; +import {useState, type ReactNode} from "react"; import {GoalResearchHistory} from "./goal-research-history"; import {GoalExploreResults} from "./goal-explore-results"; import {goalHierarchy} from "./goal-hierarchy"; @@ -9,9 +9,9 @@ import './research-case-study.css'; type ResearchGoal = ResearchChildGoal & Pick; /** Parentage organizes explicit source views; it never combines evidence or execution scope. */ -export function ResearchGoalNavigation({goalId, goals, zh, onSelect, directoryUrl, active, refreshRevision}: { +function ResearchGoalNavigation({goalId, goals, zh, onSelect, directoryUrl, active, showTiming, refreshRevision}: { goalId: string; goals: ResearchGoal[]; zh: boolean; onSelect: (goalId: string) => void; - directoryUrl?: string; active: boolean; refreshRevision: number; + directoryUrl?: string; active: boolean; showTiming: boolean; refreshRevision: number; }) { const selected = goals.find(g => g.goalId === goalId); if (!selected) return null; @@ -24,25 +24,30 @@ export function ResearchGoalNavigation({goalId, goals, zh, onSelect, directoryUr return ; } /** One read surface for the selected original Goal; opening it never starts research. */ -export function GoalResearchWorkbench(props: {goalId: string; active: boolean; zh: boolean; refreshRevision: number; readSourceUrl?: string; directoryReadSourceUrl?: string; goals?: ResearchGoal[]; onSelectGoal?: (goalId: string) => void; onOpenTasks?: () => void}) { +export function GoalResearchWorkbench(props: {goalId: string; active: boolean; zh: boolean; refreshRevision: number; readSourceUrl?: string; directoryReadSourceUrl?: string; goals?: ResearchGoal[]; onSelectGoal?: (goalId: string) => void; onOpenTasks?: () => void; statusNotice?: ReactNode}) { const selected = props.goals?.find(goal => goal.goalId === props.goalId); const source = selected?.sourceAccess; const [evidenceOpen, setEvidenceOpen] = useState(false); return
+ {props.statusNotice} + {/* The directory supplies navigation while the selected snapshot loads. + A denied or stale snapshot still blocks evidence reads. */} {props.goals && props.onSelectGoal ? : null} - -
setEvidenceOpen(event.currentTarget.open)}> - {props.zh ? "阅读已保存的结论" : "Read saved findings"} - {evidenceOpen ? : null} -
+ active={props.active} showTiming={!props.statusNotice} refreshRevision={props.refreshRevision}/> : null} + {!props.statusNotice ? <> + +
setEvidenceOpen(event.currentTarget.open)}> + {props.zh ? "阅读已保存的结论" : "Read saved findings"} + {evidenceOpen ? : null} +
+ : null}
; } diff --git a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx index b299561a41..b25cb2b032 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx @@ -2797,6 +2797,21 @@ export function PersonalWorkspacePage({ onOpenConversation={() => {setSelectedGoalTab("chat"); requestAnimationFrame(() => composerRef.current?.focus());}} /> : undefined} />) : null; + const goalStatusNotice = selectedGoal?.loadState ? ( +
+
{t(selectedGoal.loadState === "error" ? "startup.goalError" : "startup.goalLoading")} +

{t(selectedGoal.loadError ? `startup.error.${selectedGoal.loadError}` : "startup.independent")}

+ {selectedGoal.loadState === "error" ? selectedGoal.loadError === "authority" && !readOnly ? + + : : null}
+
+ ) : null; + const researchPanel = selectedGoal && (!readOnly || researchReadSourceUrl) + ? selectGoal(id, "research")} + onOpenTasks={() => setSelectedGoalTab("tasks")} active={!settingsOpen && selectedGoalTab === "research"} + zh={locale === "zh-CN"} refreshRevision={historyRefreshRevision} readSourceUrl={researchReadSourceUrl} + directoryReadSourceUrl={directoryReadSourceUrl} statusNotice={goalStatusNotice} /> + : goalStatusNotice ??

{locale === "zh-CN" ? "此来源尚未提供研究记录的读取入口。" : "This source does not provide a research history endpoint."}

; return ( <> {!rootUnavailable ?