Files
chat/backend/tests/comfy_image_edit_integration.php
2026-07-22 10:18:59 +08:00

139 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
require dirname(__DIR__) . '/vendor/autoload.php';
use app\controller\api\Chat;
use app\model\UploadFile;
use app\service\ComfyJobDeferredException;
use app\service\ComfyUIService;
use app\service\OpenAIService;
$app = new think\App(dirname(__DIR__) . DIRECTORY_SEPARATOR);
$app->initialize();
$model = OpenAIService::getImageModel();
$source = null;
foreach (UploadFile::where('file_type', 'image')->order('id', 'desc')->limit(50)->select() as $candidate) {
$path = Chat::resolveStoredPath('/api/uploads/' . rawurlencode((string) $candidate->stored_name));
if ($path !== null && @getimagesize($path)) {
$source = ['record' => $candidate, 'path' => $path];
break;
}
}
if ($source === null) {
fwrite(STDERR, "No stored source image is available for integration testing.\n");
exit(2);
}
$createdAttachments = [];
$maskPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'comfy_inpaint_mask_' . bin2hex(random_bytes(6)) . '.png';
$cleanup = static function () use (&$createdAttachments, &$maskPath): void {
foreach ($createdAttachments as $attachment) {
$filename = urldecode(basename(parse_url((string) ($attachment['url'] ?? ''), PHP_URL_PATH) ?: ''));
if ($filename === '') {
continue;
}
$record = UploadFile::where('stored_name', $filename)->find();
if (!$record) {
continue;
}
$path = Chat::resolveStoredPath('/api/uploads/' . rawurlencode($filename));
if ($path !== null && is_file($path)) {
@unlink($path);
}
$record->delete();
}
if (is_file($maskPath)) {
@unlink($maskPath);
}
};
register_shutdown_function($cleanup);
/**
* Wait for a ComfyUI task until it finishes, even if the first request is
* intentionally deferred into the background by the HTTP budget guard.
*
* @return array<int, array{type:string,url:string,name:string,mime:string,size:int}>
*/
$waitForFinalOutputs = static function (
$model,
string $promptId,
int $userId,
int $httpBudgetSeconds = 120,
int $pollTimeoutSeconds = 900
): array {
try {
return ComfyUIService::waitAndCollect($model, $promptId, $userId, null, $httpBudgetSeconds);
} catch (ComfyJobDeferredException $exception) {
$deadline = time() + $pollTimeoutSeconds;
while (time() < $deadline) {
$status = ComfyUIService::inspect($model, $promptId);
if (($status['state'] ?? '') === 'done' && !empty($status['images'])) {
return ComfyUIService::storeInspectImages($model, $status['images'], $userId);
}
if (($status['state'] ?? '') === 'error') {
throw new RuntimeException((string) ($status['message'] ?? 'ComfyUI task failed'));
}
sleep(2);
}
throw new RuntimeException('ComfyUI task did not finish before the integration timeout');
}
};
try {
$userId = (int) $source['record']->user_id;
$imgPromptId = ComfyUIService::submitEdit(
$model,
'Preserve the exact source composition and subjects, improve natural lighting and fine detail, photorealistic.',
$source['path'],
null,
'img2img'
);
$imgOutputs = $waitForFinalOutputs($model, $imgPromptId, $userId);
$createdAttachments = array_merge($createdAttachments, $imgOutputs);
if ($imgOutputs === []) {
throw new RuntimeException('img2img returned no output');
}
echo 'IMG2IMG PASS prompt_id=' . $imgPromptId . PHP_EOL;
[$width, $height] = getimagesize($source['path']);
$mask = imagecreatetruecolor($width, $height);
$black = imagecolorallocate($mask, 0, 0, 0);
$white = imagecolorallocate($mask, 255, 255, 255);
imagefill($mask, 0, 0, $black);
$boxWidth = max(16, (int) round($width * 0.12));
$boxHeight = max(16, (int) round($height * 0.08));
imagefilledrectangle(
$mask,
max(0, $width - $boxWidth - 8),
max(0, $height - $boxHeight - 8),
max(0, $width - 8),
max(0, $height - 8),
$white
);
imagepng($mask, $maskPath);
imagedestroy($mask);
$inpaintPromptId = ComfyUIService::submitEdit(
$model,
'Reconstruct the white masked region using the surrounding background texture, seamless and natural.',
$source['path'],
$maskPath,
'inpaint'
);
$inpaintOutputs = $waitForFinalOutputs($model, $inpaintPromptId, $userId);
$createdAttachments = array_merge($createdAttachments, $inpaintOutputs);
if ($inpaintOutputs === []) {
throw new RuntimeException('inpaint returned no output');
}
echo 'INPAINT PASS prompt_id=' . $inpaintPromptId . PHP_EOL;
echo 'RESULT 2/2 integration tasks passed' . PHP_EOL;
} catch (Throwable $exception) {
fwrite(STDERR, get_class($exception) . ': ' . $exception->getMessage() . PHP_EOL);
exit(1);
}