添加cadfs_to_cdsl代码,优化cad生成

This commit is contained in:
2026-09-03 10:40:51 +08:00
parent f3eb5dff54
commit c25b73c71e
34 changed files with 3586 additions and 147 deletions
+3 -1
View File
@@ -395,7 +395,9 @@ button:disabled {
.studio-main { display: flex; min-height: 0; flex: 1; }
.task-documents { border-top: 1px solid var(--ui-border); max-height: 34vh; overflow: auto; background: var(--ui-panel-bg); }
.task-checklist { padding: 8px 12px; border-bottom: 1px solid var(--ui-border); display: grid; gap: 5px; }
.task-checklist-item { display: grid; grid-template-columns: 48px minmax(0, 1fr); gap: 8px; font-size: 12px; line-height: 1.35; }
.task-checklist-item { display: grid; grid-template-columns: 48px minmax(0, 1fr) auto; gap: 8px; font-size: 12px; line-height: 1.35; align-items: start; }
.task-checklist-item small { display: block; color: var(--ui-muted); margin-top: 2px; overflow-wrap: anywhere; }
.task-checklist-item button { min-height: 24px; padding: 2px 7px; font-size: 12px; }
.task-checklist-item > span { color: var(--ui-muted); }
.task-checklist-item.is-pass > span { color: #16784d; }
.task-checklist-item.is-fail > span { color: #b73c32; }
+29 -4
View File
@@ -561,7 +561,23 @@ function StudioShell({
<div className="studio-main">
<aside className="agent-pane">
<AgentThread attachments={attachments} uploading={uploading} uploadError={uploadError} taskRunning={taskRunning} onUpload={onUpload} onCancel={onCancel} />
<TaskDocuments task={taskRecord} />
<TaskDocuments task={taskRecord} onSelectRevision={(revisionId) => {
const revision = taskRecord?.revisions.find((item) => item.revision_id === revisionId);
if (!revision?.cdsl_path || !revision.step_path || !revision.glb_path || !revision.report_path || !taskRecord) return;
onCadResult({
taskId: taskRecord.task_id,
revisionId,
cdslPath: revision.cdsl_path,
stepPath: revision.step_path,
glbPath: revision.glb_path,
reportPath: revision.report_path,
summary: revision.summary || "CDSL CAD model",
referenceIds: revision.reference_ids || [],
engine: revision.engine || "cdsl_only",
checkpoint: revision.visibility === "checkpoint",
lifecycle: taskRecord.lifecycle || "running",
});
}} />
</aside>
<section className="preview-pane">
<CadViewerPreview result={cadResult} isGenerating={running} lastError={lastError} theme={theme} onError={handleViewerError} onSelectionChange={onSelectionChange} />
@@ -571,17 +587,26 @@ function StudioShell({
);
}
function TaskDocuments({ task }: { task: TaskRecord | null }) {
function TaskDocuments({ task, onSelectRevision }: { task: TaskRecord | null; onSelectRevision: (revisionId: string) => void }) {
const documents = [
["需求文档", task?.requirements_markdown],
["完成目标", task?.completion_target_markdown],
["建模计划", task?.modeling_plan_markdown],
] as const;
if (!documents.some(([, markdown]) => markdown)) return null;
if (!documents.some(([, markdown]) => markdown) && !task?.feature_nodes?.length) return null;
return <section className="task-documents" aria-label="任务文档">
{task?.checklist_progress?.length ? <div className="task-checklist" aria-label="验收进度">
{task.checklist_progress.map((item) => <div key={item.requirement_id || item.statement} className={`task-checklist-item is-${item.status}`}><span>{item.status === "pass" ? "完成" : item.status === "fail" ? "未通过" : "待验证"}</span>{item.statement}</div>)}
</div> : null}
{task?.feature_nodes?.length ? <details open className="task-document" aria-label="特征 DAG">
<summary>特征 DAG</summary>
<div className="task-checklist">
{task.feature_nodes.slice().sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0)).map((node) => <div key={node.node_id} className={`task-checklist-item is-${node.status || "pending"}`}>
<span>{node.status === "done" ? "完成" : node.status === "running" ? "执行中" : node.status === "failed" ? "失败" : node.status === "blocked" ? "阻塞" : "待执行"}</span>
<div><strong>{node.intent || node.node_id}</strong><small>{node.atomic_id} · 优先级 {node.priority}{node.depends_on?.length ? ` · 依赖 ${node.depends_on.join(", ")}` : ""}{node.attempt ? ` · 尝试 ${node.attempt}` : ""}</small>{node.error ? <small>{node.error}</small> : null}</div>
{node.status === "done" && node.revision_id ? <button type="button" title="查看此特征检查点" onClick={() => onSelectRevision(node.revision_id!)}>查看</button> : null}
</div>)}
</div>
</details> : null}
{documents.map(([title, markdown]) => markdown ? <details key={title} className="task-document"><summary>{title}</summary><MarkdownDocument>{markdown}</MarkdownDocument></details> : null)}
</section>;
}
+4 -3
View File
@@ -44,9 +44,10 @@ export function activeCheckpointPreview(task: TaskRecord | null): CadResult | nu
export function latestSuccessfulResult(task: TaskRecord | null): CadResult | null {
if (!task) return null;
// Checkpoints are intentionally private after a failed run. Returning one
// here makes the restored viewer request an artifact the backend denies.
if (task.lifecycle === "failed" && !task.published_revision) return null;
// v3.2 deliberately exposes its last verified checkpoint on a failed DAG:
// failure means requirements were not completed, not that earlier geometry
// should disappear. Legacy task projections retain the former policy.
if (task.lifecycle === "failed" && !task.published_revision && task.schema_version !== "3.2") return null;
const current =
task.revisions.find((revision) => revision.revision_id === (task.published_revision || task.current_revision)) ??
[...task.revisions].reverse().find((revision) => revision.status === "success" && revision.visibility !== "checkpoint");
+15
View File
@@ -180,6 +180,21 @@ test("does not restore a private checkpoint after a failed run", () => {
assert.equal(result, null);
});
test("restores the last verified v3.2 DAG checkpoint after a failed run", () => {
const result = latestSuccessfulResult({
schema_version: "3.2",
task_id: "cad_abc",
current_revision: "rev_002",
active_revision: "rev_002",
lifecycle: "failed",
revisions: [
{ revision_id: "rev_002", status: "success", visibility: "checkpoint", cdsl_path: "aa", step_path: "bb", glb_path: "cc", report_path: "dd" },
],
});
assert.equal(result?.revisionId, "rev_002");
assert.equal(result?.checkpoint, true);
});
test("restores an active checkpoint only while the task is running", () => {
const result = activeCheckpointPreview({
task_id: "cad_abc", current_revision: "rev_002", active_revision: "rev_002", published_revision: "rev_001", lifecycle: "running",
+26 -2
View File
@@ -124,8 +124,32 @@ export type TaskRecord = {
requirements_document_path?: string;
completion_target_markdown?: string | null;
completion_target_path?: string;
modeling_plan_markdown?: string | null;
modeling_plan_path?: string;
feature_plan?: {
schema_version?: string;
parent_plan_hash?: string;
replaces_node_ids?: string[];
nodes?: Array<Record<string, unknown>>;
final_claim_ids?: string[];
} | null;
feature_plan_path?: string;
feature_plan_hash?: string;
current_feature_node_id?: string;
pending_feature?: { action_id: string; node_id: string; plan_hash: string; atomic_id: string; claim_ids: string[]; depends_on_node_ids: string[] } | null;
feature_nodes?: Array<{
node_id: string;
intent?: string;
atomic_id?: string;
priority?: number;
depends_on?: string[];
claim_ids?: string[];
status?: "pending" | "ready" | "running" | "done" | "failed" | "blocked" | "invalidated" | string;
attempt?: number;
failure_class?: string;
error?: string;
revision_id?: string;
feature_id?: string;
evidence?: Array<Record<string, unknown>>;
}>;
completion_result_markdown?: string | null;
completion_result_path?: string;
claim_summary?: Array<{