第三步补全接入

This commit is contained in:
Ukawka
2026-09-20 22:21:50 +08:00
parent f095c28858
commit 99d65377ab
14 changed files with 912 additions and 14 deletions
+27 -5
View File
@@ -6,11 +6,13 @@ import {requestJson} from '../providers/http.mjs';
import {runUnderstanding, runCompletionProbe} from '../stages/model-stage.mjs';
import {readImage} from '../lib/images.mjs';
import {runSegmentation} from '../stages/segmentation-stage.mjs';
import {runCompletion} from '../stages/completion-stage.mjs';
const help = `模型接入与结构理解(新增模式,不调用旧拆件流程)
node pipeline.mjs --mode annotated-parts --reference image.png --out-dir output/run --stop-after understand
node tools/model-check.mjs --stage completion --image local.png --prompt-file task.txt --out-dir output/edit
node tools/model-check.mjs --stage segmentation --input-dir output/understand --out-dir output/segment --sam-dir F:/Study/SAM3
node tools/model-check.mjs --stage completion --input-dir output/segment --out-dir output/complete --part head --prepare-only
node tools/model-check.mjs --list-models
node tools/model-check.mjs --dry-run
@@ -25,7 +27,12 @@ const help = `模型接入与结构理解(新增模式,不调用旧拆件流
--resume 输入/配置/代码和产物哈希相同时复用
--parts-file FILE 离线读取已有 v1/v2 计划,做 Alpha 校验与种子生成,不请求模型
--python FILE ai-spine Conda 环境解释器(默认使用已激活环境)
--input-dir DIR 已完成的结构理解目录(SAM 阶段输入)
--input-dir DIR SAM 阶段读理解目录;补全阶段读 SAM 目录
--part ID 补全部件 ID,可重复;默认处理所有需补全部件
--prepare-only 仅准备补全画布、掩码和提示词,不请求模型
--candidates-dir DIR 离线导入 ID.png 补全候选,不请求模型
--completion-size INT 补全方形画布边长,默认 1024,不缩放原像素
--extension-radius INT 补全区域距可见轮廓的最大像素距离,默认 32
--sam-dir DIR 本地 SAM 根目录,包含 sam3 与 sam3.1checkpoint
--sam-code-dir DIR SAM 源码仓库目录覆盖
--sam-checkpoint FILE 本地权重路径覆盖
@@ -35,10 +42,11 @@ const help = `模型接入与结构理解(新增模式,不调用旧拆件流
--mask FILE 透明处可编辑的 RGBA PNG;需配置 capabilities
--dry-run 检查配置与本地输入,不请求模型、不生成产物
--stop-after 支持 understand / segment。SAM 在临时 GPU 子进程运行,完成后退出;补全合并/导出尚未接入。`;
--stop-after 支持 understand / segment。SAM 在临时 GPU 子进程运行,完成后退出。
部件补全使用 --stage completion --input-dir;可见像素恢复与几何检查已接入,Spine 导出尚未接入。`;
export function parseOptions(argv) {
const options = {stage: 'understanding', imagePaths: []};
const options = {stage: 'understanding', imagePaths: [], partIds: []};
const valueFlags = new Map([
['--config', 'configFile'], ['--env-file', 'envFile'], ['--reference', 'reference'], ['--out-dir', 'outDir'],
['--understanding-model', 'understandingModel'], ['--completion-model', 'completionModel'],
@@ -47,17 +55,20 @@ export function parseOptions(argv) {
['--mode', 'mode'], ['--python', 'python'], ['--stream', 'streamRaw'], ['--parts-file', 'partsFile'],
['--input-dir', 'inputDir'], ['--sam-dir', 'samDir'], ['--sam-code-dir', 'samCodeDir'],
['--sam-checkpoint', 'samCheckpoint'], ['--sam-confidence', 'samConfidence'], ['--sam-precision', 'samPrecision'],
['--candidates-dir', 'candidatesDir'], ['--completion-size', 'completionSize'], ['--extension-radius', 'extensionRadius'],
]);
for (let i = 0; i < argv.length; i += 1) {
const flag = argv[i];
if (['--help', '-h'].includes(flag)) options.help = true;
else if (flag === '--resume') options.resume = true;
else if (flag === '--dry-run') options.dryRun = true;
else if (flag === '--prepare-only') options.prepareOnly = true;
else if (flag === '--list-models') options.listModels = true;
else if (valueFlags.has(flag) || flag === '--image') {
else if (valueFlags.has(flag) || flag === '--image' || flag === '--part') {
const value = argv[++i];
if (!value || value.startsWith('--')) throw new Error(`${flag} 缺少参数值`);
if (flag === '--image') options.imagePaths.push(value);
else if (flag === '--part') options.partIds.push(value);
else options[valueFlags.get(flag)] = value;
} else throw new Error(`未知参数:${flag},请用 --help 查看`);
}
@@ -76,13 +87,24 @@ export async function main(argv = process.argv.slice(2)) {
throw new Error('--stop-after 仅支持 understand / segment,且必须匹配阶段');
}
const loaded = loadConfig(options);
const partCompletion = options.stage === 'completion' && options.inputDir;
if (!partCompletion && (options.prepareOnly || options.candidatesDir || options.partIds.length || options.completionSize || options.extensionRadius)) {
throw new Error('部件补全参数需要 --stage completion --input-dir');
}
if (partCompletion) {
if (options.reference || options.imagePaths.length || options.maskPath || options.promptFile || options.listModels) throw new Error('部件补全从 --input-dir 生成画布与提示,不接受探针输入参数');
const stage = resolveStage(loaded, 'completion', {model: options.completionModel}, {requireKey: !options.dryRun && !options.prepareOnly && !options.candidatesDir});
const result = await runCompletion({...options, stage});
if (!result.dryRun) console.log(`部件补全${result.reused ? '复用完成' : options.prepareOnly ? '准备完成(未请求模型)' : '处理完成,需复核'}:${path.join(result.out, 'report.html')}`);
return result;
}
if (options.stage === 'segmentation' || (options.stopAfter === 'segment' && options.inputDir)) {
if (options.reference || options.partsFile || options.imagePaths.length || options.maskPath || options.promptFile || options.listModels) throw new Error('独立 SAM 阶段使用 --input-dir,不接受模型请求或编辑参数');
const result = await runSegmentation({...options, loaded});
if (!result.dryRun) console.log(`SAM 分割${result.reused ? '复用完成' : '完成'}:${path.join(result.out, 'report.html')}`);
return result;
}
if (options.inputDir) throw new Error('--input-dir 仅用于 SAM 分割阶段');
if (options.inputDir) throw new Error('--input-dir 仅用于 SAM 分割或部件补全阶段');
const overrides = options.stage === 'understanding'
? {model: options.understandingModel, protocol: options.understandingProtocol, outputMode: options.outputMode, stream: options.stream}
: {model: options.completionModel};