ai改了半天还是不对

This commit is contained in:
2026-08-28 02:52:44 +08:00
parent 144602ef45
commit 5c2be8eef0
18 changed files with 1091 additions and 390 deletions
+146 -5
View File
@@ -17,7 +17,7 @@ import "react-calendar-timeline/style.css";
type Plot = {id: string; title: string; category: string; description: string; dimension: "2D" | "3D"; websocket: string; media: string; schema: string; diagnostics: string; taskflow: string};
type Option = {value: string; label: string};
type Editor = "boolean" | "integer" | "number" | "text" | "select" | "color" | "point2" | "size" | "rect" | "range" | "partition-grid" | "pen" | "brush" | "font" | "color-map" | "vector3" | "matrix4" | "marker-list" | "surface-marker-list" | "json";
type Editor = "boolean" | "integer" | "number" | "text" | "select" | "color" | "point2" | "size" | "rect" | "range" | "partition-grid" | "pen" | "brush" | "font" | "color-map" | "vector3" | "matrix4" | "marker-list" | "surface-marker-list" | "axis3d" | "json";
type Color_Channel_Scale = "normalized" | "byte";
type Field = {key: string; label: string; description: string; technical_description?: string; editor: Editor; editable: boolean; value: unknown; options?: Option[]; color_channel_scale?: Color_Channel_Scale; minimum?: number; maximum?: number; step?: number};
type Component = {id: string; label: string; kind: string; fields: Field[]};
@@ -95,10 +95,23 @@ type Frame_Policy_State = {
latest_completion_ms: number; average_completion_ms: number; maximum_completion_ms: number};
last_frame: {sequence: number; request_source: "unspecified" | "periodic" | "immediate" | "maximum_rate"};
};
type Datoviz_Frame_Observation = {
render_sequence: number;
path: "recorded" | "reused";
gpu_timing_requested: boolean;
readback_requested: boolean;
timings_ms: {render_domain_queue_wait: number; apply: number; emit: number; execute: number;
submit: number; gpu_fence_wait: number; readback: number};
traffic: {uploaded_bytes: number; readback_bytes: number};
gpu_ms?: {render: number; transition: number; copy: number; total: number};
frame_plan: {resource_version: number; frame_index: number; status: number; artifact_json?: string};
validation: {performed: boolean; ok: boolean; code: number; command_index: number};
};
type Taskflow_Frame_Trace = {sequence: number; correlation_id: number; created_time_unix_ns: number; worker_count: number;
request_source?: "unspecified" | "periodic" | "immediate" | "maximum_rate";
frame_policy?: Frame_Policy_State;
markers?: Record<string, number>; measurements?: Record<string, number>;
datoviz?: Datoviz_Frame_Observation;
graphs: Taskflow_Graph_Trace[]; executions: Taskflow_Execution_Trace[]};
type Taskflow_Frame_Response = {protocol: "aethera.taskflow.frames"; version: 1; requested: number; remaining: number;
captured: number; complete: boolean; frames: Taskflow_Frame_Trace[]; media_requested?: number;
@@ -510,6 +523,8 @@ function use_gallery_videos(plots: Plot[], transport_mode: Gallery_Transport_Mod
? await event.data.arrayBuffer() : null;
if (!buffer || stopped || !pixel_surface) return;
const sequence = pixel_surface.render(buffer);
if (socket.readyState === WebSocket.OPEN)
socket.send(JSON.stringify({kind: "pixel_frame_consumed", sequence}));
if (!pixel_live) {
pixel_live = true;
update(current => ({...current, status: "LIVE",
@@ -929,6 +944,87 @@ function Structured_Control({field, on_change}: {field: Field; on_change: (value
</div></fieldset>;
}
type Axis_3D_Value = {
range: {origin: number; target: number};
scale: "linear" | "logarithmic" | "time";
label: string;
unit: string;
target_tick_count: number;
precision: number;
visible: boolean;
grid_visible: boolean;
labels_visible: boolean;
};
function axis_3d_value(value: unknown): Axis_3D_Value {
const source = value && typeof value === "object" ? value as Partial<Axis_3D_Value> : {};
const range = source.range && typeof source.range === "object" ? source.range : {origin: 0, target: 1};
return {
range: {origin: Number(range.origin ?? 0), target: Number(range.target ?? 1)},
scale: source.scale === "logarithmic" || source.scale === "time" ? source.scale : "linear",
label: String(source.label ?? ""), unit: String(source.unit ?? ""),
target_tick_count: Number(source.target_tick_count ?? 6), precision: Number(source.precision ?? 2),
visible: source.visible ?? true, grid_visible: source.grid_visible ?? true,
labels_visible: source.labels_visible ?? true
};
}
function axis_3d_error(value: unknown) {
const axis = axis_3d_value(value);
if (!Number.isFinite(axis.range.origin) || !Number.isFinite(axis.range.target)) return "轴范围必须是有限数字";
if (axis.range.origin === axis.range.target) return "轴范围起点和终点不能相同";
if (axis.scale === "logarithmic" && (axis.range.origin <= 0 || axis.range.target <= 0))
return "对数频率轴的起点和终点必须大于 0";
if (!Number.isInteger(axis.target_tick_count) || axis.target_tick_count < 2 || axis.target_tick_count > 64)
return "目标刻度数必须是 2 到 64 的整数";
if (!Number.isInteger(axis.precision) || axis.precision < 0 || axis.precision > 12)
return "小数精度必须是 0 到 12 的整数";
return "";
}
function Axis_3D_Control({field, on_change}: {field: Field; on_change: (value: unknown) => void}) {
const axis = axis_3d_value(field.value);
const is_frequency = axis.unit.trim().toLowerCase() === "hz" || axis.label.trim().toLowerCase() === "frequency";
const semantic = axis.scale === "time" ? "time" : is_frequency ? "frequency" : "numeric";
const semantic_label = semantic === "time" ? "时间轴" : semantic === "frequency" ? "频率轴" : "数值轴";
const update = (next: Partial<Axis_3D_Value>) => on_change({...axis, ...next});
const update_range = (next: Partial<Axis_3D_Value["range"]>) => update({range: {...axis.range, ...next}});
const select_semantic = (next: string) => {
if (next === "time") update({scale: "time", label: "Time", unit: "s"});
else if (next === "frequency") update({scale: "logarithmic", label: "Frequency", unit: "Hz",
range: axis.range.origin > 0 && axis.range.target > 0 ? axis.range : {origin: 10, target: 20_000}});
else update({scale: axis.scale === "time" ? "linear" : axis.scale});
};
const error = axis_3d_error(axis);
return <fieldset className="control structuredControl axis3dControl" title={field_tooltip(field)}>
<legend>{field_label(field)} · {semantic_label} <code>{field.key}</code></legend>
<div className="axis3dGrid">
<label><span></span><select value={semantic} onChange={event => select_semantic(event.target.value)}>
<option value="numeric"></option><option value="time"></option><option value="frequency"></option>
</select></label>
<label><span></span><select value={axis.scale} onChange={event => update({scale: event.target.value as Axis_3D_Value["scale"]})}>
<option value="linear">线</option><option value="logarithmic"></option><option value="time"></option>
</select></label>
<label><span>{semantic === "time" ? "起始时间" : semantic === "frequency" ? "最低频率" : "范围起点"}</span>
<input type="number" step="any" value={axis.range.origin} onChange={event => update_range({origin: Number(event.target.value)})}/></label>
<label><span>{semantic === "time" ? "结束时间" : semantic === "frequency" ? "最高频率" : "范围终点"}</span>
<input type="number" step="any" value={axis.range.target} onChange={event => update_range({target: Number(event.target.value)})}/></label>
<label><span></span><input value={axis.label} onChange={event => update({label: event.target.value})}/></label>
<label><span></span><input value={axis.unit} onChange={event => update({unit: event.target.value})}/></label>
<label><span></span><input type="number" min="2" max="64" step="1" value={axis.target_tick_count}
onChange={event => update({target_tick_count: Number(event.target.value)})}/></label>
<label><span></span><input type="number" min="0" max="12" step="1" value={axis.precision}
onChange={event => update({precision: Number(event.target.value)})}/></label>
</div>
<div className="axis3dToggles">
<label><input type="checkbox" checked={axis.visible} onChange={event => update({visible: event.target.checked})}/></label>
<label><input type="checkbox" checked={axis.grid_visible} onChange={event => update({grid_visible: event.target.checked})}/></label>
<label><input type="checkbox" checked={axis.labels_visible} onChange={event => update({labels_visible: event.target.checked})}/></label>
</div>
{error ? <small className="error">{error}</small> : <small> Axes_3D 使 Datoviz 线</small>}
</fieldset>;
}
type Marker_Value = {position: {x: number; y: number; z: number}; color: {red: number; green: number; blue: number; alpha: number}; diameter_px: number; angle: number; shape: string; coordinate_label_visible: boolean};
const marker_shapes = ["disc", "square", "triangle", "diamond", "cross"];
@@ -954,6 +1050,7 @@ function Field_Control({field, on_change}: {field: Field; on_change: (value: unk
if (["color", "point2", "size", "rect", "range", "partition-grid", "pen", "brush", "font", "vector3"].includes(field.editor))
return <Structured_Control field={field} on_change={on_change}/>;
if (field.editor === "marker-list" || field.editor === "surface-marker-list") return <Marker_List_Control field={field} on_change={on_change}/>;
if (field.editor === "axis3d") return <Axis_3D_Control field={field} on_change={on_change}/>;
if (["json", "color-map", "matrix4"].includes(field.editor)) return <Json_Control field={field} on_change={on_change}/>;
if (field.editor === "boolean") return <label className="control controlBoolean" title={field_tooltip(field)}><span>{field_label(field)} <code>{field.key}</code></span>
<input type="checkbox" checked={Boolean(field.value)} onChange={event => on_change(event.target.checked)}/></label>;
@@ -968,6 +1065,7 @@ function Property_Control({field, on_commit}: {field: Field; on_commit: (value:
const [dirty, set_dirty] = useState(false);
const [saving, set_saving] = useState(false);
const encoded_value = JSON.stringify(field.value);
const validation_error = field.editor === "axis3d" ? axis_3d_error(draft) : "";
useEffect(() => { set_draft(field.value); set_dirty(false); }, [encoded_value]);
const change = (value: unknown) => {
set_draft(value);
@@ -984,7 +1082,7 @@ function Property_Control({field, on_commit}: {field: Field; on_commit: (value:
return <div className={`propertyEditor${dirty ? " dirty" : ""}`}>
<Field_Control field={{...field, value: draft}} on_change={change}/>
{field.editor !== "boolean" ? <div className="propertyActions">
<button disabled={!dirty || saving} onClick={() => void commit()}>{saving ? "提交中…" : "确定"}</button>
<button disabled={!dirty || saving || Boolean(validation_error)} onClick={() => void commit()}>{saving ? "提交中…" : "确定"}</button>
<button disabled={!dirty || saving} onClick={() => { set_draft(field.value); set_dirty(false); }}></button>
</div> : saving ? <small className="savingHint"></small> : null}
</div>;
@@ -1237,6 +1335,9 @@ function taskflow_node_name(name: string, node?: Taskflow_Node_Trace) {
if (name === "scene.paint") return "Scene · 绘制子图";
if (name === "scene.paint.complete") return "Scene · 像素绘制完成";
if (name === "scene.completion") return "Scene · 帧完成处理";
if (name === "scene.backend.submit") return "3D Scene · 准备并提交 Datoviz 帧";
if (name === "render_3d.backend.collect") return "Datoviz · GPU 完成后读回";
if (name === "render_3d.frame.retire") return "3D Scene · 完成帧退役";
if (name === "plot.frame.publish") return "Plot · 发布完成帧";
if (name === "gallery.sample.capture") return "Gallery · 30 FPS 最近帧采样";
if (name === "FFmpeg.H264.encode" || name === "gallery.h264.encode") return "FFmpeg H.264 · 编码";
@@ -1678,7 +1779,7 @@ function taskflow_render_domain_state(node: Taskflow_Node_Trace, frame?: Taskflo
markers[first] === undefined || markers[last] === undefined
? null : Math.max(0, markers[last] - markers[first]);
const prepare = interval("backend_prepare_started", "backend_prepare_finished");
const submit_queue = interval("backend_submit_queued", "backend_queue_left");
const submit_queue = interval("backend_queue_entered", "backend_queue_left");
const gpu = interval("gpu_submitted", "gpu_completed");
return {
invoke_result: markers.backend_prepare_finished === undefined
@@ -1687,7 +1788,8 @@ function taskflow_render_domain_state(node: Taskflow_Node_Trace, frame?: Taskflo
submit_queue_ms: submit_queue,
gpu_completion_ms: gpu,
frame_sequence: frame.sequence,
correlation_id: frame.correlation_id
correlation_id: frame.correlation_id,
datoviz: frame.datoviz ?? null
};
}
@@ -1886,6 +1988,7 @@ function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_
frame_policy: frame?.frame_policy,
markers: frame?.markers ?? {},
measurements: frame?.measurements ?? {},
datoviz: frame?.datoviz,
graph,
expanded_graph: expanded_taskflow_graph(graph),
/* 保留当前选中拓扑,同时把本帧其余真实 Task_Graph 一并复制。
@@ -2062,7 +2165,11 @@ function Taskflow_Timeline({graph, executions, frame, components, gallery_state,
`${label} ${milliseconds(end - start)}`, label, `${start_key}${end_key}`);
});
add_lifecycle_group("lifecycle-backend", "后端与 GPU", "准备 → 队列 → GPU → 回读");
const datoviz = frame.datoviz;
const datoviz_detail = datoviz
? `${datoviz.path} · 上传 ${datoviz.traffic.uploaded_bytes.toLocaleString("zh-CN")} B · 读回 ${datoviz.traffic.readback_bytes.toLocaleString("zh-CN")} B`
: "准备 → 队列 → GPU → 回读";
add_lifecycle_group("lifecycle-backend", "后端与 GPU", datoviz_detail);
const backend_intervals: Array<[string, string, string, string]> = [
["backend_prepare_started", "backend_prepare_finished", "后端准备", "backend-prepare"],
["backend_queue_entered", "backend_queue_left", "提交队列", "backend-queue"],
@@ -2161,6 +2268,7 @@ function Taskflow_Timeline({graph, executions, frame, components, gallery_state,
frame_policy: frame?.frame_policy,
markers: frame?.markers ?? {},
measurements: frame?.measurements ?? {},
datoviz: frame?.datoviz,
graph,
timeline: {origin: "render_frame_created", submitted_ms: graph.submitted_ms, finished_ms: graph.finished_ms,
wall_time_ms: Math.max(0, graph.finished_ms - graph.submitted_ms), last_execution_completed_ms: model.last_completed,
@@ -2255,6 +2363,38 @@ function Taskflow_Timeline({graph, executions, frame, components, gallery_state,
</section>;
}
function frame_bytes(value: number) {
if (!Number.isFinite(value)) return "--";
if (value >= 1024 * 1024) return `${(value / (1024 * 1024)).toFixed(2)} MiB`;
if (value >= 1024) return `${(value / 1024).toFixed(1)} KiB`;
return `${value.toLocaleString("zh-CN")} B`;
}
function Datoviz_Frame_Summary({observation}: {observation: Datoviz_Frame_Observation}) {
const timing = observation.timings_ms;
const gpu = observation.gpu_ms;
const validation = observation.validation;
const structural = observation.path === "recorded";
return <section className="datovizFrameObservation" aria-label="本帧 Datoviz 观测">
<header><div><strong> Datoviz </strong><span> Frame_3D #{observation.render_sequence}</span></div>
<i className={structural ? "recorded" : "reused"}>{structural ? "重新录制命令" : "复用录制命令"}</i></header>
<dl className="taskflowGraphSummary">
<div title="帧进入 GPU Render Domain 单写者队列到开始执行的墙钟等待。"><dt>Render Domain </dt><dd>{milliseconds(timing.render_domain_queue_wait)}</dd></div>
<div title="属性应用;复用帧只包含当前目标槽的映射缓冲更新。"><dt>Datoviz Apply</dt><dd>{milliseconds(timing.apply)}</dd></div>
<div title="生成 Frame Plan;复用命令路径应为 0。"><dt>Frame Plan emit</dt><dd>{milliseconds(timing.emit)}</dd></div>
<div title="执行 DRP2 Frame Plan;复用命令路径应为 0。"><dt>DRP2 execute</dt><dd>{milliseconds(timing.execute)}</dd></div>
<div><dt>vkQueueSubmit</dt><dd>{milliseconds(timing.submit)}</dd></div>
<div title={gpu ? `渲染 ${milliseconds(gpu.render)} · 转换 ${milliseconds(gpu.transition)} · 拷贝 ${milliseconds(gpu.copy)}` : "该帧没有请求 GPU timestamp"}><dt>GPU </dt><dd>{gpu ? milliseconds(gpu.total) : "未采集"}</dd></div>
<div title="Gpu_Completion_Service 异步等待 fence 的实际墙钟。"><dt>GPU fence </dt><dd>{milliseconds(timing.gpu_fence_wait)}</dd></div>
<div title="fence 完成后在 Taskflow worker 执行的目标槽读回。"><dt></dt><dd>{milliseconds(timing.readback)}</dd></div>
<div><dt></dt><dd>{frame_bytes(observation.traffic.uploaded_bytes)}</dd></div>
<div><dt></dt><dd>{frame_bytes(observation.traffic.readback_bytes)}</dd></div>
<div><dt>Frame Plan</dt><dd>v{observation.frame_plan.resource_version} · {observation.frame_plan.frame_index}</dd></div>
<div><dt></dt><dd>{validation.performed ? validation.ok ? "通过" : `失败 ${validation.code}` : "未执行"}</dd></div>
</dl>
</section>;
}
function Taskflow_Frame_Pane({plot, components}: {plot: Plot; components: Component[]}) {
const [frame_count, set_frame_count] = useState(8);
const [scene_response, set_scene_response] = useState<Taskflow_Frame_Response | null>(null);
@@ -2365,6 +2505,7 @@ function Taskflow_Frame_Pane({plot, components}: {plot: Plot; components: Compon
<button className={view_mode === "timeline" ? "active" : ""} onClick={() => set_view_mode("timeline")}></button>
</div>
<span>Executor {frame.worker_count} workers · {frame.executions.length} </span></section>
{frame.datoviz ? <Datoviz_Frame_Summary observation={frame.datoviz}/> : null}
{displayed_view_mode === "aggregate" && aggregate ? <><dl className="taskflowGraphSummary taskflowAggregateSummary">
<div><dt></dt><dd>{aggregate.graph.stage}</dd></div>
<div><dt> / </dt><dd>{aggregate.frames} / {aggregate.samples}</dd></div>
+15
View File
@@ -220,6 +220,14 @@ canvas { display: block; width: 100%; height: 100%; background: #070d18; }
.taskflowGraphSummary > div, .taskflowRuntimeSummary > div { min-width: 0; padding: 10px; border: 1px solid #213653; border-radius: 9px; background: #0a1422; }
.taskflowGraphSummary dt, .taskflowRuntimeSummary dt { color: #71839e; font-size: 10px; }
.taskflowGraphSummary dd, .taskflowRuntimeSummary dd { margin: 5px 0 0; color: #5ce4c2; font: 700 13px/1.35 ui-monospace, monospace; white-space: normal; overflow-wrap: anywhere; }
.datovizFrameObservation { overflow: hidden; border: 1px solid #28516a; border-radius: 10px; background: #081522; }
.datovizFrameObservation > header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 12px; border-bottom: 1px solid #203c53; background: #0d1d2c; }
.datovizFrameObservation > header > div { display: grid; gap: 3px; }
.datovizFrameObservation > header strong { color: #dce8f8; font-size: 11px; }
.datovizFrameObservation > header span { color: #7189a8; font-size: 9px; }
.datovizFrameObservation > header i { flex: none; padding: 5px 8px; border: 1px solid #337360; border-radius: 99px; color: #72e3c8; background: #0b2822; font: 700 9px/1 ui-monospace, monospace; font-style: normal; }
.datovizFrameObservation > header i.recorded { color: #efc073; border-color: #76572d; background: #291d0d; }
.datovizFrameObservation .taskflowGraphSummary { padding: 10px; }
.taskflowDagSection { min-width: 640px; overflow: visible; border: 1px solid #213653; border-radius: 10px; background: #07101c; }
.taskflowDagToolbar { display: flex; align-items: center; justify-content: flex-start; flex-wrap: wrap; gap: 12px; padding: 9px 11px; border-bottom: 1px solid #213653; background: #0c1727; }
.taskflowDagToolbar button { flex: none; padding: 6px 9px; color: #b9cce3; border: 1px solid #36516f; border-radius: 6px; background: #132238; cursor: pointer; }
@@ -337,6 +345,13 @@ canvas { display: block; width: 100%; height: 100%; background: #070d18; }
.componentCard > .propGrid { padding: 13px; }
.structuredControl { min-width: 0; margin: 0; padding: 10px; border: 1px solid #263b59; border-radius: 9px; }
.structuredControl legend { padding: 0 5px; color: #c8d5e8; }
.axis3dControl { gap: 10px; background: #0a1524; }
.axis3dGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.axis3dGrid label { display: grid; min-width: 0; gap: 5px; color: #8298b4; font-size: 10px; }
.axis3dToggles { display: flex; flex-wrap: wrap; gap: 8px 13px; color: #9eb1c9; font-size: 10px; }
.axis3dToggles label { display: inline-flex; align-items: center; gap: 5px; }
.axis3dToggles input[type="checkbox"] { width: 17px; height: 17px; }
@media (max-width: 620px) { .axis3dGrid { grid-template-columns: 1fr; } }
.inlineFields { display: grid; grid-template-columns: repeat(auto-fit, minmax(82px, 1fr)); gap: 8px; }
.inlineFields > label { display: grid; gap: 4px; min-width: 0; color: #8195b1; font-size: 11px; }
.inlineFields input[type="color"] { min-height: 40px; padding: 3px; }