feat: add project-aware agent workspace
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { AnswerUiView, parseAnswerUiBlock, type AnswerUiBlock, type AnswerUiSelection } from "@/features/agent";
|
||||
import type { OperationalTask, OperationalTaskReport } from "../api/operational-task-client";
|
||||
import type { InvestigationDiscussion, InvestigationMapTarget } from "../api/investigation-report";
|
||||
|
||||
export function InvestigationReportBlocks({ task, snapshot, onContinue, onLocate }: {
|
||||
task: Pick<OperationalTask, "evidence">;
|
||||
snapshot: OperationalTaskReport;
|
||||
onContinue: (discussion?: InvestigationDiscussion) => Promise<boolean | void> | boolean | void;
|
||||
onLocate?: (targets: InvestigationMapTarget[]) => Promise<unknown> | void;
|
||||
}) {
|
||||
const blocks = snapshot.report.ui_blocks ?? [];
|
||||
if (!blocks.length) return null;
|
||||
const ask = async (selection: AnswerUiSelection) => {
|
||||
const result = await onContinue({
|
||||
reference: {
|
||||
title: `${snapshot.report.title} · 报告第 ${snapshot.revision} 版 · ${selection.title}`,
|
||||
text: `${selection.text}\n报告第 ${snapshot.revision} 版${snapshot.execution_generation ? ` · 第 ${snapshot.execution_generation} 轮` : ""}`
|
||||
},
|
||||
context: { report_revision: snapshot.revision },
|
||||
reportGeneration: snapshot.execution_generation
|
||||
});
|
||||
if (result === false) throw new Error("引用暂未带入对话,请重试。");
|
||||
};
|
||||
return <section className="investigation-report-section space-y-4" aria-label="交互分析">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold">数据与分析</h3>
|
||||
<p className="mt-1 text-xs text-muted-foreground">探索报告中的数据,选择内容后可引用到对话。</p>
|
||||
</div>
|
||||
{blocks.map((item) => {
|
||||
const parsed = parseAnswerUiBlock(item.block);
|
||||
const evidence = task.evidence.filter((entry) => item.evidence_refs.includes(entry.evidence_id));
|
||||
const quote = (selection: AnswerUiSelection) => ask({
|
||||
...selection,
|
||||
text: `${selection.text}\n引用依据:${item.evidence_refs.map((id) => {
|
||||
const record = evidence.find((entry) => entry.evidence_id === id);
|
||||
return record ? `${record.source_label}(${id})` : `${id}(记录当前不可用)`;
|
||||
}).join("、")}`
|
||||
});
|
||||
// Evidence facts are supplied by the saved task record, not generated presentation props.
|
||||
const block: AnswerUiBlock | null = parsed ? { ...parsed, spec: {
|
||||
...parsed.spec,
|
||||
elements: Object.fromEntries(Object.entries(parsed.spec.elements).map(([id, node]) => {
|
||||
if (node.type !== "Evidence") return [id, node];
|
||||
const record = evidence.find((entry) => entry.evidence_id === node.props.evidence_id);
|
||||
return [id, record ? { ...node, props: {
|
||||
...node.props, summary: record.summary, source: record.source_label,
|
||||
observed_at: record.observed_at,
|
||||
kind: record.kind === "synthetic" ? "demo" : record.kind === "human_input" ? "human_input" : "observed"
|
||||
} } : { type: "Text", children: [], props: { text: "这条引用证据当前不可用。", tone: "warning" } }];
|
||||
}))
|
||||
} } : null;
|
||||
return <div key={`${snapshot.revision}:${item.block.block_id}`} className="space-y-2">
|
||||
{block ? <AnswerUiView block={block} onAsk={quote} submitMode="draft"
|
||||
onSubmit={(text) => quote({ title: "补充资料", text })}
|
||||
onLocate={onLocate ? (target) => onLocate(target.feature_ids.map((id) => ({ source_id: target.layer, feature_id: id }))) : undefined} />
|
||||
: <p className="whitespace-pre-wrap rounded-lg border p-3 text-sm leading-6">{item.block.fallback_text}</p>}
|
||||
<p className="text-[11px] leading-5 text-muted-foreground">
|
||||
引用依据:{evidence.map((entry) => entry.source_label).join("、") || "关联记录当前不可用"}
|
||||
</p>
|
||||
</div>;
|
||||
})}
|
||||
</section>;
|
||||
}
|
||||
Reference in New Issue
Block a user