diff --git a/service/index.html b/service/index.html index 0b0fb88..372698d 100644 --- a/service/index.html +++ b/service/index.html @@ -2213,6 +2213,10 @@ A+B design_target_target: "目标浓度", design_tube_defect: "Tube 缺陷", design_complex_defect: "Complex 缺陷", + design_analyze_direct: "直接分析设计结果", + design_load_analysis: "载入为 Analysis 输入", + design_analysis_loaded: "已载入设计序列,可修改链浓度后分析", + design_analysis_unavailable: "没有可分析的设计结果", details_json: "展开原始 JSON", toggle_structure_data: "结构数据", toggle_pairs_panel: "配对热图", @@ -2517,6 +2521,10 @@ A+B design_target_target: "Target Concentration", design_tube_defect: "Tube Defect", design_complex_defect: "Complex Defect", + design_analyze_direct: "Analyze Designed Result", + design_load_analysis: "Load as Analysis Input", + design_analysis_loaded: "Designed sequences loaded; adjust strand concentrations before analysis", + design_analysis_unavailable: "No designed result is available for analysis", details_json: "Show raw JSON", toggle_structure_data: "Structure Data", toggle_pairs_panel: "Pair Heatmap", @@ -3747,6 +3755,81 @@ A+B }; } + function buildAnalysisOptionsFromControls() { + return { + num_sample: Number(document.getElementById("numSample").value), + energy_gap: Number(document.getElementById("energyGap").value), + sparsity_fraction: Number(document.getElementById("sparsityFraction").value), + sparsity_threshold: Number(document.getElementById("sparsityThreshold").value), + single_mfe: false, + result_limit: Number(document.getElementById("resultLimit").value), + pairs_preview_size: Number(document.getElementById("pairsPreviewSize").value), + }; + } + + function buildDesignedAnalysisPayload(result) { + if (!result || workflowKind(result) !== "design" || !Array.isArray(result.strands) || !result.strands.length) { + throw new Error(t("design_analysis_unavailable")); + } + const inputStrandsByName = new Map((lastPayload?.strands || []).map((strand) => [strand.name, strand])); + const strands = result.strands.map((strand) => { + const input = inputStrandsByName.get(strand.name) || {}; + return { + name: strand.name, + sequence: strand.sequence, + concentration: Number(input.concentration ?? 1.0), + unit: input.unit || "uM", + }; + }); + const targetLines = (result.design?.targets || result.complexes || []) + .map((target) => Array.isArray(target.strand_names) ? target.strand_names.join("+") : "") + .filter(Boolean); + return { + workflow: "analysis", + mode: result.mode || "tube", + model: { ...result.model }, + compute: ["pfunc", "mfe", "pairs"], + options: buildAnalysisOptionsFromControls(), + strands, + tube: { + name: result.tube?.name || result.design?.target_tubes?.[0]?.name || "designed_tube", + max_size: Number(result.tube?.max_size ?? result.design?.target_tubes?.[0]?.max_size ?? 2), + }, + complexes_text: targetLines.join("\n"), + design_domains: [], + design_complexes: [], + design_tubes: [], + design: {}, + hard_constraints: [], + soft_constraints: [], + defect_weights: [], + }; + } + + function loadDesignedAnalysisInput(result) { + const payload = buildDesignedAnalysisPayload(result); + workflowSelect.value = "analysis"; + modeSelect.value = payload.mode; + document.getElementById("material").value = payload.model.material; + document.getElementById("ensemble").value = payload.model.ensemble; + document.getElementById("celsius").value = payload.model.celsius; + document.getElementById("sodium").value = payload.model.sodium; + document.getElementById("magnesium").value = payload.model.magnesium; + document.getElementById("tubeName").value = payload.tube.name; + document.getElementById("maxSize").value = payload.tube.max_size; + document.querySelectorAll(".check input").forEach((input) => { + input.checked = payload.compute.includes(input.value); + }); + strandList.innerHTML = ""; + payload.strands.forEach((strand) => addStrandRow(strand)); + syncComplexRowsFromText(payload.complexes_text); + syncWorkflow(); + syncMode(); + document.querySelector(".main-grid")?.scrollIntoView({ behavior: "smooth", block: "start" }); + setStatus(formatStatus(t("design_analysis_loaded"))); + return payload; + } + function renderModelCard(result) { return `
${String(error)}