Initial TrueGrowth source import
This commit is contained in:
313
scripts/verify-aigcpanel-model-flow.mjs
Normal file
313
scripts/verify-aigcpanel-model-flow.mjs
Normal file
@@ -0,0 +1,313 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn, execFileSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import http from 'node:http';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
const ROOT = process.cwd();
|
||||
const PORT = Number(process.env.TRUEGROWTH_LOCAL_API_PORT || 48177);
|
||||
const BASE_URL = `http://127.0.0.1:${PORT}`;
|
||||
const SMOKE_VERSION = 'v1.1.0-smoke';
|
||||
const SMOKE_TITLE = 'LatentSync 自动识别烟测模型';
|
||||
const TIMEOUT_MS = 30_000;
|
||||
const RUNTIME_MODEL_DOWNLOAD_DIR = path.join(ROOT, '.truegrowth-runtime', 'aigcpanel-model-downloads');
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function safeJson(text) {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function requestJson(url, options = {}) {
|
||||
const response = await fetch(url, options);
|
||||
const text = await response.text();
|
||||
const json = safeJson(text);
|
||||
if (!response.ok) {
|
||||
throw new Error(`${options.method || 'GET'} ${url} failed ${response.status}: ${text}`);
|
||||
}
|
||||
return json ?? text;
|
||||
}
|
||||
|
||||
async function waitForApi() {
|
||||
const startedAt = Date.now();
|
||||
while (Date.now() - startedAt < TIMEOUT_MS) {
|
||||
try {
|
||||
await requestJson(`${BASE_URL}/local-api/health`);
|
||||
return;
|
||||
} catch {
|
||||
await delay(300);
|
||||
}
|
||||
}
|
||||
throw new Error('Local API did not become ready in time.');
|
||||
}
|
||||
|
||||
async function apiIsReady() {
|
||||
try {
|
||||
await requestJson(`${BASE_URL}/local-api/health`);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function makeSmokePackage() {
|
||||
const smokeRoot = path.join(os.tmpdir(), `truegrowth-aigcpanel-flow-${Date.now()}`);
|
||||
const modelRoot = path.join(smokeRoot, 'LatentSyncPackage');
|
||||
fs.mkdirSync(modelRoot, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(modelRoot, 'config.json'),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: 'LatentSync',
|
||||
title: SMOKE_TITLE,
|
||||
version: SMOKE_VERSION,
|
||||
entry: '__EasyServer__',
|
||||
functions: {
|
||||
videoGen: { param: [] },
|
||||
videoGenFlow: { param: [] },
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
fs.writeFileSync(path.join(modelRoot, 'server.js'), 'module.exports = {};\n');
|
||||
const archivePath = path.join(smokeRoot, 'LatentSync-official-auto-smoke.zip');
|
||||
execFileSync('zip', ['-qr', archivePath, 'LatentSyncPackage'], {
|
||||
cwd: smokeRoot,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
return { smokeRoot, archivePath };
|
||||
}
|
||||
|
||||
function smokeModelDir() {
|
||||
return path.join(
|
||||
os.homedir(),
|
||||
'Library',
|
||||
'Application Support',
|
||||
'aigcpanel',
|
||||
'data',
|
||||
'model',
|
||||
'latentsync'
|
||||
);
|
||||
}
|
||||
|
||||
function storagePath() {
|
||||
return path.join(
|
||||
os.homedir(),
|
||||
'Library',
|
||||
'Application Support',
|
||||
'aigcpanel',
|
||||
'data',
|
||||
'storage',
|
||||
'server.json'
|
||||
);
|
||||
}
|
||||
|
||||
function cleanupSmoke(smokeRoot) {
|
||||
const modelDir = smokeModelDir();
|
||||
const configPath = path.join(modelDir, 'config.json');
|
||||
if (fs.existsSync(configPath)) {
|
||||
const config = safeJson(fs.readFileSync(configPath, 'utf8')) || {};
|
||||
if (config.version === SMOKE_VERSION || String(config.title || '').includes('烟测')) {
|
||||
fs.rmSync(modelDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
const registryPath = storagePath();
|
||||
if (fs.existsSync(registryPath)) {
|
||||
const registry = safeJson(fs.readFileSync(registryPath, 'utf8')) || {};
|
||||
for (const key of ['records', 'items', 'servers', 'models']) {
|
||||
if (Array.isArray(registry[key])) {
|
||||
registry[key] = registry[key].filter((item) => {
|
||||
return !(
|
||||
item?.version === SMOKE_VERSION ||
|
||||
item?.config?.version === SMOKE_VERSION ||
|
||||
String(item?.title || '').includes('烟测')
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2));
|
||||
}
|
||||
|
||||
const downloadsPath = path.join(ROOT, '.truegrowth-runtime', 'aigcpanel-model-downloads.json');
|
||||
if (fs.existsSync(downloadsPath)) {
|
||||
const downloads = safeJson(fs.readFileSync(downloadsPath, 'utf8')) || {};
|
||||
downloads.items = (Array.isArray(downloads.items) ? downloads.items : []).filter((item) => {
|
||||
return !(
|
||||
String(item?.filePath || '').includes('LatentSync-official-auto-smoke') ||
|
||||
String(item?.finalUrl || '').includes('LatentSync-official-auto-smoke') ||
|
||||
item?.version === SMOKE_VERSION
|
||||
);
|
||||
});
|
||||
fs.writeFileSync(downloadsPath, JSON.stringify(downloads, null, 2));
|
||||
}
|
||||
|
||||
if (fs.existsSync(RUNTIME_MODEL_DOWNLOAD_DIR)) {
|
||||
for (const entry of fs.readdirSync(RUNTIME_MODEL_DOWNLOAD_DIR, { withFileTypes: true })) {
|
||||
if (entry.isFile() && entry.name.includes('LatentSync-official-auto-smoke')) {
|
||||
fs.rmSync(path.join(RUNTIME_MODEL_DOWNLOAD_DIR, entry.name), { force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (smokeRoot) {
|
||||
fs.rmSync(smokeRoot, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const { smokeRoot, archivePath } = makeSmokePackage();
|
||||
const existingModelDir = smokeModelDir();
|
||||
const backupModelDir = fs.existsSync(existingModelDir)
|
||||
? path.join(os.tmpdir(), `truegrowth-latentsync-backup-${Date.now()}`)
|
||||
: '';
|
||||
let child = null;
|
||||
try {
|
||||
if (backupModelDir) {
|
||||
fs.renameSync(existingModelDir, backupModelDir);
|
||||
}
|
||||
cleanupSmoke(null);
|
||||
let stderr = '';
|
||||
if (!(await apiIsReady())) {
|
||||
child = spawn(process.execPath, ['scripts/truegrowth-local-api.mjs'], {
|
||||
cwd: ROOT,
|
||||
env: { ...process.env, TRUEGROWTH_LOCAL_API_PORT: String(PORT) },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
child.stderr.on('data', (chunk) => {
|
||||
stderr += String(chunk);
|
||||
});
|
||||
}
|
||||
await waitForApi();
|
||||
|
||||
fs.mkdirSync(RUNTIME_MODEL_DOWNLOAD_DIR, { recursive: true });
|
||||
const scannedArchivePath = path.join(
|
||||
RUNTIME_MODEL_DOWNLOAD_DIR,
|
||||
`LatentSync-official-auto-smoke-${Date.now()}.zip`
|
||||
);
|
||||
fs.copyFileSync(archivePath, scannedArchivePath);
|
||||
|
||||
const scannedBeforeInstall = await requestJson(`${BASE_URL}/local-api/aigcpanel/model-assets`);
|
||||
const scannedPackage = scannedBeforeInstall.localPackages?.find(
|
||||
(item) => item.path === scannedArchivePath
|
||||
);
|
||||
assert(scannedPackage, 'Downloaded model package was not discovered by local package scan.');
|
||||
assert(scannedPackage.assetId === 'latentsync', 'Scanned package did not infer LatentSync.');
|
||||
|
||||
const install = await requestJson(
|
||||
`${BASE_URL}/local-api/aigcpanel/model-assets/__auto__/install-local`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ localPath: scannedArchivePath }),
|
||||
}
|
||||
);
|
||||
assert(install.ok, 'Auto install response was not ok.');
|
||||
assert(install.resolvedAsset?.id === 'latentsync', 'Auto import did not resolve to LatentSync.');
|
||||
assert(install.installedModel?.key === `LatentSync|${SMOKE_VERSION}`, 'Installed model key was not returned.');
|
||||
assert(install.registration?.registered, 'Installed model was not registered.');
|
||||
|
||||
const assets = await requestJson(`${BASE_URL}/local-api/aigcpanel/model-assets`);
|
||||
const latentsync = assets.items?.find((item) => item.id === 'latentsync');
|
||||
assert(latentsync?.installed, 'Model asset list did not show LatentSync as installed.');
|
||||
assert(
|
||||
latentsync.installedModel?.service?.status === 'registered_pending_reload',
|
||||
'Installed model status did not reflect pending local-service refresh.'
|
||||
);
|
||||
assert(
|
||||
Array.isArray(assets.categories) && assets.categories.includes('视频换口型'),
|
||||
'Official category list did not include 视频换口型.'
|
||||
);
|
||||
|
||||
const modelsResult = await requestJson(`${BASE_URL}/local-api/aigcpanel/models`);
|
||||
const models = Array.isArray(modelsResult)
|
||||
? modelsResult
|
||||
: modelsResult.items || modelsResult.models || [];
|
||||
const avatarModel = models.find((model) => model.key === `LatentSync|${SMOKE_VERSION}`);
|
||||
assert(avatarModel, 'Imported model was not exposed through /models for form selection.');
|
||||
assert(
|
||||
avatarModel.functionNames?.includes('videoGenFlow'),
|
||||
'Imported model was missing videoGenFlow in form model list.'
|
||||
);
|
||||
|
||||
const task = await requestJson(`${BASE_URL}/local-api/aigcpanel/tasks`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
capability: 'avatar.render',
|
||||
title: '数字人自动模型脚本烟测',
|
||||
payload: {
|
||||
biz: 'VideoGenFlow',
|
||||
model: `LatentSync|${SMOKE_VERSION}`,
|
||||
videoUrl: 'local://smoke/avatar.mp4',
|
||||
audioUrl: 'local://smoke/voice.wav',
|
||||
script: '自动导入模型后生成数字人口播。',
|
||||
format: '9:16 竖屏',
|
||||
},
|
||||
}),
|
||||
});
|
||||
assert(task.ok && task.task?.id, 'Digital-human task was not accepted.');
|
||||
|
||||
let finalTask = null;
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const polled = await requestJson(`${BASE_URL}/local-api/tasks/${task.task.id}`);
|
||||
finalTask = polled.item;
|
||||
if (finalTask?.status === 'completed' || finalTask?.status === 'failed') {
|
||||
assert(polled.assets?.some((asset) => asset.type === 'avatar'), 'Completed task did not create avatar asset.');
|
||||
break;
|
||||
}
|
||||
await delay(500);
|
||||
}
|
||||
assert(finalTask?.status === 'completed', `Digital-human task did not complete: ${finalTask?.status || 'unknown'}`);
|
||||
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
ok: true,
|
||||
scannedPackage: scannedPackage.name,
|
||||
resolvedAsset: scannedPackage.assetId,
|
||||
installedModel: install.installedModel.key,
|
||||
modelAssetStatus: latentsync.installedModel.service.status,
|
||||
formModelCount: models.length,
|
||||
taskStatus: finalTask.status,
|
||||
taskAssetIds: finalTask.assetIds,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
if (stderr.trim()) {
|
||||
console.error(stderr.trim());
|
||||
}
|
||||
} finally {
|
||||
if (child && !child.killed) {
|
||||
child.kill('SIGTERM');
|
||||
}
|
||||
cleanupSmoke(smokeRoot);
|
||||
if (backupModelDir && fs.existsSync(backupModelDir)) {
|
||||
fs.rmSync(existingModelDir, { recursive: true, force: true });
|
||||
fs.renameSync(backupModelDir, existingModelDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.stack || error.message : String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user