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 */ $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); }