优化生成流程

This commit is contained in:
2026-08-25 17:41:24 +08:00
parent 51575161dc
commit beaa5dd9fe
50 changed files with 4204 additions and 4261 deletions
@@ -0,0 +1,11 @@
import { NextRequest, NextResponse } from "next/server";
import { backendFetch, readBackendError } from "@/lib/backend";
export const runtime = "nodejs";
export async function GET(_: NextRequest, { params }: { params: Promise<{ taskId: string }> }) {
const { taskId } = await params;
const response = await backendFetch(`/v1/tasks/${encodeURIComponent(taskId)}/quality`);
if (!response.ok) return NextResponse.json({ error: await readBackendError(response) }, { status: response.status });
return NextResponse.json(await response.json());
}
+12
View File
@@ -442,6 +442,18 @@ button:disabled {
.cad-result { margin-top: 12px; }
.cad-result-title { margin-left: 21px; color: var(--ui-accent-text); font-size: 12px; font-weight: 600; line-height: 1.45; overflow-wrap: anywhere; }
.cad-result-meta { display: flex; flex-wrap: wrap; gap: 3px 10px; margin-left: 21px; color: var(--ui-text-muted); font-size: 10px; line-height: 1.4; overflow-wrap: anywhere; }
.cad-quality-status { font-weight: 600; }
.quality-accepted { color: #2f8f5b; }
.quality-built_with_warnings, .quality-historical { color: #a66a1f; }
.quality-needs_repair, .quality-blocked, .quality-failed { color: #b44545; }
.cad-result-notes { display: grid; grid-template-columns: 42px minmax(0, 1fr); gap: 6px; margin: 8px 0 0 21px; color: var(--ui-text-muted); font-size: 11px; line-height: 1.45; }
.cad-result-notes strong { color: var(--ui-text); }
.cad-quality-results { display: grid; grid-template-columns: 42px minmax(0, 1fr); gap: 6px; margin: 8px 0 0 21px; color: var(--ui-text-muted); font-size: 11px; line-height: 1.45; }
.cad-quality-results > strong { color: var(--ui-text); }
.cad-quality-results ul { display: grid; gap: 3px; margin: 0; padding: 0; list-style: none; }
.cad-quality-results li { display: flex; flex-wrap: wrap; gap: 6px; overflow-wrap: anywhere; }
.quality-rule-passed { color: #2f8f5b; }
.quality-rule-failed, .quality-rule-unavailable { color: #b44545; }
.download-row { display: flex; flex-wrap: wrap; gap: 9px; margin: 2px 0 0 21px; }
.download-link { display: inline-flex; align-items: center; gap: 4px; color: var(--ui-link); font-size: 11px; text-decoration: none; text-underline-offset: 2px; }
.download-link:hover { color: var(--ui-link-hover); text-decoration: underline; }
@@ -1,6 +1,7 @@
"use client";
import { AlertTriangle, Box, Check, Download, Loader2, Ruler } from "lucide-react";
import { useEffect, useState } from "react";
import { encodeArtifactUrl } from "@/lib/cad-artifacts";
import type { CadError, CadImageAnalysis, CadProgress, CadResult } from "@/lib/cad-types";
@@ -34,6 +35,16 @@ export function CadResultPart({ data }: { data: CadResult }) {
["GLB", data.glbPath],
["报告", data.reportPath],
];
if (data.qualityPath) downloads.push(["质量报告", data.qualityPath]);
if (data.snapshotPaths?.length) downloads.push(["快照清单", data.snapshotPaths[0]]);
const qualityLabels: Record<string, string> = {
accepted: "验收通过",
built_with_warnings: "构建完成,有警告",
needs_repair: "需要修复",
blocked: "已阻塞",
failed: "失败",
};
const quality = String(data.qualityStatus || "");
return (
<section className="cad-message cad-result" aria-label="CAD 结果">
<div className="cad-message-heading">
@@ -45,7 +56,12 @@ export function CadResultPart({ data }: { data: CadResult }) {
<span>{data.engine}</span>
<span>{data.revisionId}</span>
<span>{data.referenceIds.length} </span>
{quality ? <span className={`cad-quality-status quality-${quality}`}>{qualityLabels[quality] || quality}</span> : null}
</div>
{data.assumptions?.length ? <div className="cad-result-notes"><strong></strong><span>{data.assumptions.join("")}</span></div> : null}
{data.referenceIds.length ? <div className="cad-result-notes"><strong></strong><span>{data.referenceIds.join("")}</span></div> : null}
<QualitySummary data={data} />
{data.snapshotStatus && data.snapshotStatus !== "unavailable" ? <div className="cad-result-notes"><strong></strong><span>{data.snapshotStatus}</span></div> : null}
<div className="download-row">
{downloads.map(([label, path]) => (
<a key={label} className="download-link" href={encodeArtifactUrl(data.taskId, path)} download aria-label={`下载 ${label}`}>
@@ -58,6 +74,44 @@ export function CadResultPart({ data }: { data: CadResult }) {
);
}
type QualityResult = { id?: string; status?: string; severity?: string; expected?: unknown; actual?: unknown };
type QualityPayload = { quality?: { results?: QualityResult[] }; quality_status?: string };
function QualitySummary({ data }: { data: CadResult }) {
const [quality, setQuality] = useState<QualityPayload | null>(null);
useEffect(() => {
if (!data.qualityPath) {
setQuality(null);
return;
}
const controller = new AbortController();
void fetch(`/api/tasks/${encodeURIComponent(data.taskId)}/quality`, { signal: controller.signal })
.then((response) => response.ok ? response.json() as Promise<QualityPayload> : null)
.then((payload) => { if (!controller.signal.aborted) setQuality(payload); })
.catch(() => { if (!controller.signal.aborted) setQuality(null); });
return () => controller.abort();
}, [data.qualityPath, data.revisionId, data.taskId]);
const results = quality?.quality?.results || [];
if (!results.length) return null;
const label: Record<string, string> = { passed: "通过", failed: "未通过", unavailable: "不可用" };
return (
<div className="cad-quality-results" aria-label="通用验证结果">
<strong></strong>
<ul>
{results.map((result, index) => (
<li key={`${result.id || "rule"}-${index}`} className={`quality-rule-${result.status || "unavailable"}`}>
<span>{result.id || "rule"}</span>
<span>{label[result.status || ""] || result.status || "不可用"}</span>
{result.severity && result.severity !== "blocking" ? <span>{result.severity}</span> : null}
</li>
))}
</ul>
</div>
);
}
export function CadImageAnalysisPart({ data }: { data: CadImageAnalysis }) {
const dimensionCandidates = data.dimensionCandidates ?? data.requiredDimensions ?? [];
return (
@@ -54,6 +54,13 @@ function resultFromBackend(payload: Record<string, unknown>): CadResult {
summary: String(payload.summary || "Updated CDSL model"),
referenceIds: Array.isArray(payload.reference_ids) ? payload.reference_ids.map(String) : [],
engine: String(payload.engine || "cdsl_only"),
qualityStatus: String(payload.quality_status || ""),
qualityPath: typeof payload.quality_path === "string" ? payload.quality_path : undefined,
assumptions: Array.isArray(payload.generation_assumptions) ? payload.generation_assumptions.map(String) : [],
warnings: Array.isArray(payload.warnings) ? payload.warnings.map(String) : [],
repairAttempts: Number(payload.repair_attempts || 0),
snapshotPaths: Array.isArray(payload.snapshot_paths) ? payload.snapshot_paths.map(String) : [],
snapshotStatus: String(payload.snapshot_status || "unavailable"),
};
}
+7
View File
@@ -34,5 +34,12 @@ export function latestSuccessfulResult(task: TaskRecord | null): CadResult | nul
summary: current.summary || "CDSL CAD model",
referenceIds: current.reference_ids || [],
engine: current.engine || "cdsl_only",
qualityStatus: current.quality_status || "",
qualityPath: current.quality_path,
assumptions: current.generation_assumptions || current.assumptions || [],
warnings: current.warnings || [],
repairAttempts: current.repair_attempts || 0,
snapshotPaths: current.snapshot_manifest_path ? [current.snapshot_manifest_path] : [],
snapshotStatus: current.snapshot_status || "unavailable",
};
}
+17
View File
@@ -20,6 +20,13 @@ export type CadResult = {
summary: string;
referenceIds: string[];
engine: string;
qualityStatus?: "accepted" | "built_with_warnings" | "needs_repair" | "blocked" | "failed" | string;
qualityPath?: string;
assumptions?: string[];
warnings?: string[];
repairAttempts?: number;
snapshotPaths?: string[];
snapshotStatus?: string;
};
export type CadError = {
@@ -41,6 +48,7 @@ export type CadImageAnalysis = {
dimensionCandidates?: CadImageDimension[];
// Legacy conversations stored this field before dimensions became optional.
requiredDimensions?: CadImageDimension[];
artifactPath?: string;
};
export type CadDataParts = {
@@ -85,6 +93,14 @@ export type TaskRevision = {
reference_ids?: string[];
engine?: string;
error?: string;
quality_status?: string;
quality_path?: string;
assumptions?: string[];
generation_assumptions?: string[];
warnings?: string[];
repair_attempts?: number;
snapshot_manifest_path?: string;
snapshot_status?: string;
};
export type TaskRecord = {
@@ -104,4 +120,5 @@ export type BackendConfig = {
model: string;
configured: boolean;
library_samples: number;
max_repair_attempts?: number;
};