- 上下文清单、任务列表与详情、恢复控制、运行观察、创作对话与消息流、正式变更决策面板、正文写作与候选审阅、资料研究等页面同步本轮后端能力。 - 事件标签表补齐后端事件类型;查询键与生成客户端随 HTTP 契约重新生成。 - 依赖调整后 lockfile 同步;交互用例与浏览器旅程 spec 一并更新。 - 证据:make 前端检查(lint + typecheck + Pi 工具桥 tsc)exit 0;make 前端测试 37 文件 / 120 用例通过。
137 lines
4.6 KiB
TypeScript
137 lines
4.6 KiB
TypeScript
import { useState } from "react";
|
||
import { Ui任务上下文 } from "../上下文";
|
||
import { Ui消息流 } from "../创作对话/消息流";
|
||
import { Ui作者动作 } from "../创作对话/作者动作";
|
||
import { useMuse作者会话 } from "../../应用/作者会话";
|
||
import { Ui恢复控制 } from "./恢复控制";
|
||
import { Ui任务预算 } from "./任务预算";
|
||
import { Ui运行观察 } from "../作者经验/运行观察";
|
||
import { useMuse任务订阅 } from "./任务订阅";
|
||
import { Ui调用账目 } from "./调用账目";
|
||
import { Ui反馈状态 } from "../../界面/反馈状态";
|
||
import { 状态, 事件名, type 任务 } from "./数据";
|
||
|
||
export function Ui任务详情({
|
||
task,
|
||
scope,
|
||
}: {
|
||
task: 任务;
|
||
scope: string | null;
|
||
}) {
|
||
const [openedCandidate, setOpenedCandidate] = useState<string>();
|
||
const { events, notice } = useMuse任务订阅(task, scope);
|
||
const 生成中 = task.task_type === "生成正文";
|
||
const 候选事件 = [...events]
|
||
.reverse()
|
||
.find((条) => 条.event_type === "candidate.saved");
|
||
const eventCandidate = 候选事件?.payload.candidate_id;
|
||
const 候选ID = 生成中
|
||
? (task.result_refs?.find((ref) => ref.kind === "writing_candidate")
|
||
?.candidate_id ??
|
||
(typeof eventCandidate === "string" ? eventCandidate : null))
|
||
: null;
|
||
return (
|
||
<div className="task-columns">
|
||
<section className="paper-panel">
|
||
<div className="panel-heading">
|
||
<h2>任务进度</h2>
|
||
<span className="state-pill">{状态[task.state]}</span>
|
||
</div>
|
||
<p className="mono muted">{task.task_id}</p>
|
||
<Ui恢复控制 task={task} scope={scope} />
|
||
<Ui调用账目 task={task} scope={scope} />
|
||
{task.supports_step_advance && <Ui任务预算 taskId={task.task_id} />}
|
||
<ol className="step-list">
|
||
{task.steps.map((step, index) => (
|
||
<li key={step.step_id}>
|
||
<span className="step-number">
|
||
{String(index + 1).padStart(2, "0")}
|
||
</span>
|
||
<div>
|
||
<strong>{step.step_id}</strong>
|
||
<span>{状态[step.state] ?? step.state}</span>
|
||
</div>
|
||
</li>
|
||
))}
|
||
</ol>
|
||
</section>
|
||
<section className="event-panel">
|
||
<div className="panel-heading">
|
||
<h2>过程记录</h2>
|
||
<span className="muted">
|
||
{task.execution_final_sequence != null ? "执行已收尾" : "实时更新"}
|
||
</span>
|
||
</div>
|
||
<p className="muted">
|
||
显示最近40条过程记录;正文增量仅作有限预览,完整结果请打开候选。
|
||
</p>
|
||
{notice && <Ui反馈状态 message={notice} />}
|
||
<ol className="event-list" aria-live="polite">
|
||
{events.map((event) => (
|
||
<li key={event.event_id}>
|
||
<time>
|
||
{new Date(event.occurred_at).toLocaleTimeString("zh-CN", {
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
})}
|
||
</time>
|
||
<span>{事件名[event.event_type] ?? "进度更新"}</span>
|
||
</li>
|
||
))}
|
||
</ol>
|
||
</section>
|
||
<Ui任务上下文 taskId={task.task_id} scope={scope} />
|
||
<Ui运行观察
|
||
key={task.task_id}
|
||
taskId={task.task_id}
|
||
scope={scope}
|
||
steps={task.steps}
|
||
/>
|
||
{生成中 && (
|
||
<section
|
||
className="paper-panel"
|
||
style={{ gridColumn: "1 / -1" }}
|
||
aria-label="创作对话"
|
||
>
|
||
<div className="panel-heading">
|
||
<h2>创作对话</h2>
|
||
<span className="muted">生成过程与候选检查</span>
|
||
</div>
|
||
<Ui消息流 事件={events} />
|
||
{候选ID && openedCandidate !== 候选ID && (
|
||
<button onClick={() => setOpenedCandidate(候选ID)}>
|
||
打开生成候选
|
||
</button>
|
||
)}
|
||
{openedCandidate && (
|
||
<Ui生成候选动作
|
||
key={openedCandidate}
|
||
scope={scope}
|
||
candidateId={openedCandidate}
|
||
/>
|
||
)}
|
||
</section>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/** 生成候选的检查与决定面板;作者会话由此处自取,任务列表无需传递。 */
|
||
function Ui生成候选动作({
|
||
scope,
|
||
candidateId,
|
||
}: {
|
||
scope: string | null;
|
||
candidateId: string;
|
||
}) {
|
||
const 会话 = useMuse作者会话();
|
||
if (!会话.data) return <Ui反馈状态 message="正在读取作者会话…" />;
|
||
return (
|
||
<Ui作者动作
|
||
scope={scope}
|
||
author={会话.data.author_id}
|
||
candidateId={candidateId}
|
||
/>
|
||
);
|
||
}
|