优化生成流程
This commit is contained in:
@@ -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());
|
||||
}
|
||||
@@ -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"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user