diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 21ef47c6..86b1899e 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -11,6 +11,7 @@ Use this file only for rules that apply across the whole repository. ## Current Site-Specific Instructions - `83.149.164.4` deployment and promotion rules live in `.github/instructions/regalamiunsorriso-83-149-164-4.instructions.md`. +- `pve02docker.maddo.science` FaceAI Docker debugging and container-management rules live in `.github/instructions/regalamiunsorriso-faceai-pve02docker.instructions.md`. ## Deployment Documentation diff --git a/.github/instructions/regalamiunsorriso-faceai-pve02docker.instructions.md b/.github/instructions/regalamiunsorriso-faceai-pve02docker.instructions.md new file mode 100644 index 00000000..d5cd77dc --- /dev/null +++ b/.github/instructions/regalamiunsorriso-faceai-pve02docker.instructions.md @@ -0,0 +1,162 @@ +--- +description: 'Use when: debugging, inspecting, or updating the FaceAI Docker deployment on root@pve02docker.maddo.science, especially for faceai/** and stacks/faceai.yml changes.' +applyTo: 'faceai/**, stacks/faceai.yml' +--- + +# Regalami Un Sorriso FaceAI Docker Host + +Instructions in this file are specific to the FaceAI Docker deployment reachable as `root@pve02docker.maddo.science` through the preconfigured SSH tunnel and stored credentials. + +## Host Access + +- SSH target: `root@pve02docker.maddo.science` +- Plain `ssh root@pve02docker.maddo.science` works in the current environment. +- The remote shell is `/bin/bash`, not `tcsh`. +- Do not add manual tunnel, key, or credential flags unless the current workflow is revalidated. + +## Preferred SSH Workflow + +For routine inspection, use plain SSH: + +```powershell +ssh root@pve02docker.maddo.science +``` + +From PowerShell on Windows, prefer invoking the SSH binary directly instead of wrapping it in `cmd /c`: + +```powershell +& 'C:\Windows\System32\OpenSSH\ssh.exe' 'root@pve02docker.maddo.science' +``` + +When you must run a single remote command non-interactively, pass the whole remote command as one SSH argument. + +## Shell Behavior From PowerShell + +- Prefer one remote command per SSH invocation when doing reconnaissance. Complex commands with pipes, grouped expressions, or escaped parentheses are more likely to break under PowerShell-to-SSH quoting. +- On Windows PowerShell, avoid `cmd /c "ssh ..."` wrappers for anything nontrivial. Nested quoting can collapse before SSH runs and spill later tokens into the local PowerShell session. +- Prefer the PowerShell call operator form `& 'C:\Windows\System32\OpenSSH\ssh.exe' ...` and pass the remote command as a single argument when you must stay non-interactive. +- If PowerShell shows the continuation prompt `? >`, the command was malformed locally before SSH executed it. Cancel it and rerun a simpler command instead of trying to answer the prompt. +- When running remote commands from PowerShell, quoting can break if the command contains both nested quotes and file paths with spaces. +- For read-only verification commands from PowerShell, prefer `ssh ... --% ` so the remote command is passed verbatim. +- If repeated SSH commands start cancelling or interleaving poorly in the same terminal, rerun them sequentially instead of in parallel. +- The remote shell is normal `bash`, so standard POSIX shell constructs usually work once they reach the host intact. + +## Docker Runtime Facts + +- The FaceAI deployment is currently managed by Docker Compose, not Docker Swarm. +- Compose project: `faceai` +- Compose working directory: `/data/compose/4` +- Compose file: `/data/compose/4/docker-compose.yml` +- Main containers: + - `regalami-faceai` + - `regalami-faceai-processor` + - `regalami-faceai-redis` + +## Container Roles + +- `regalami-faceai`: public HTTP application +- `regalami-faceai-processor`: background queue worker that runs the matcher jobs +- `regalami-faceai-redis`: Redis queue and state store + +## Useful Runtime Paths + +- Runtime data: `/mnt/storage/data/faceai/runtime` +- Persistent logs: `/mnt/storage/data/faceai/logs` +- Read-only PKL dataset: `/mnt/nas12/nas2/RUS` +- In-container runtime root: `/data/runtime` +- In-container log root: `/data/logs` +- In-container PKL root: `/data/pkl` + +## Logs And Health Checks + +Prefer Docker logs first for quick inspection: + +```bash +docker logs --tail 200 regalami-faceai +docker logs --tail 200 regalami-faceai-processor +docker logs --tail 200 regalami-faceai-redis +``` + +Persistent host logs are also available directly: + +- `/mnt/storage/data/faceai/logs/backend.log` +- `/mnt/storage/data/faceai/logs/processor.log` +- `/mnt/storage/data/faceai/logs/searches//worker.log` +- `/mnt/storage/data/faceai/logs/searches//matcher.log` + +Operational notes: + +- `regalami-faceai` has shown `ECONNREFUSED` errors when Redis was not yet reachable, then recovered once Redis became healthy. +- `docker ps` health output is meaningful here because the public app has an HTTP healthcheck and Redis has a readiness check. +- If the public app is running but marked `unhealthy`, inspect both `docker logs regalami-faceai` and `/mnt/storage/data/faceai/logs/backend.log` before changing anything. + +## Read-Only Debugging Commands + +Use these patterns before considering any state-changing action: + +```bash +docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}' +docker compose -f /data/compose/4/docker-compose.yml ps +docker inspect regalami-faceai +docker inspect regalami-faceai-processor +docker inspect regalami-faceai-redis +``` + +When you need recent logs without attaching to a live stream: + +```bash +docker logs --tail 200 regalami-faceai +docker logs --tail 200 regalami-faceai-processor +docker logs --tail 200 regalami-faceai-redis +``` + +When you need file-backed diagnostics: + +```bash +tail -n 200 /mnt/storage/data/faceai/logs/backend.log +tail -n 200 /mnt/storage/data/faceai/logs/processor.log +``` + +## Updates And Container Management + +If the user explicitly approves a rollout or image refresh, the Compose-managed update path is from `/data/compose/4`: + +```bash +cd /data/compose/4 +docker compose pull +docker compose up -d +``` + +After any approved update, verify with: + +- `docker compose -f /data/compose/4/docker-compose.yml ps` +- `docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.RunningFor}}'` +- `docker logs --tail 200 regalami-faceai` +- `docker logs --tail 200 regalami-faceai-processor` + +## Consent Rules + +Read-only inspection may proceed without additional confirmation. + +Any destructive or service-affecting action requires the user's express consent, and that consent must be collected through the `vscode_askQuestions` tool before running the command. + +Treat all of the following as consent-gated actions: + +- `docker compose pull` +- `docker compose up -d` +- `docker compose down` +- `docker restart`, `docker stop`, `docker kill`, `docker rm` +- `docker exec` commands that modify files, data, or runtime state +- deleting logs, runtime files, volumes, images, networks, or containers +- `docker system prune`, `docker volume prune`, and similar cleanup commands +- editing files under `/data/compose/4` or the mounted FaceAI host paths + +Before any consent-gated action, ask a concise confirmation question that names the exact action and the affected services. + +## Safety Boundaries + +- Default to inspection first. Do not jump directly to restarts or updates when logs or status already explain the issue. +- Do not run cleanup commands opportunistically. +- Do not edit Compose files on the host unless the user explicitly asks for that change. +- Do not assume that `regalami-faceai` startup errors are frontend issues. Check Redis reachability, processor availability, and mounted log files first. +- If a command may produce a long interactive stream, prefer a bounded `docker logs --tail ...` read before following with a live stream. \ No newline at end of file diff --git a/faceai/tests/live-site/live-race.spec.js b/faceai/tests/live-site/live-race.spec.js index 67b3590c..278b380c 100644 --- a/faceai/tests/live-site/live-race.spec.js +++ b/faceai/tests/live-site/live-race.spec.js @@ -1,3 +1,4 @@ +const path = require('path'); const { test, expect } = require('@playwright/test'); const { LIVE_EXPECTED_RACE_STORAGE, @@ -112,6 +113,16 @@ async function readVisibleLegacyPhotoIds(page) { }); } +async function readFaceAiMatchState(page) { + return page.evaluate(() => { + if (typeof getFaceAiMatchState === 'function') { + return getFaceAiMatchState(); + } + + return window.faceAiMatchState || null; + }); +} + async function waitForVisibleLegacyPhotoIds(page, expectedCount) { await expect.poll(async () => { const visiblePhotoIds = await readVisibleLegacyPhotoIds(page); @@ -345,7 +356,7 @@ test('accepts the supplied portrait image for the live upload flow', async ({ pa await expect(fileInput).toBeEnabled(); await fileInput.setInputFiles(LIVE_SITE_PORTRAIT_PATH); - await expect(page.locator('.faceai-file-name')).toContainText('test_portrait_1.png'); + await expect(page.locator('.faceai-file-name')).toContainText(path.basename(LIVE_SITE_PORTRAIT_PATH)); const searchResponsePromise = page.waitForResponse((response) => { return response.request().method() === 'POST' && response.url().includes('/api/searches'); @@ -376,15 +387,26 @@ test('accepts the supplied portrait image for the live upload flow', async ({ pa await expect.poll(async () => page.url(), { timeout: 30 * 1000, message: 'Expected the browser to land on the legacy race page with FaceAI filter parameters after FaceAI completed.' - }).toMatch(new RegExp(`^${escapeRegExp(LIVE_SITE_BASE_URL)}/.*faceaiPhotoIds=`)); + }).toMatch(new RegExp(`^${escapeRegExp(LIVE_SITE_BASE_URL)}/.*(?:faceaiPhotoIds=|faceaiMatchStorageKey=)`)); await expect(page.locator('form[onsubmit="return searching()"]')).toBeVisible(); await expect(page.locator('#faceAiFilterBanner')).toContainText(/Face ID filter active|Filtro Face ID attivo/i); await expect(page.locator('.gallery-card')).toHaveCount(0); const finalUrl = new URL(page.url()); - const expectedPhotoIds = (finalUrl.searchParams.get('faceaiPhotoIds') || '').split(',').map((value) => value.trim()).filter(Boolean); + await expect.poll(async () => { + const matchState = await readFaceAiMatchState(page); + return Array.isArray(matchState && matchState.photoIds) ? matchState.photoIds.length : 0; + }, { + timeout: 30 * 1000, + message: 'Expected the legacy race page to resolve FaceAI match state after redirect.' + }).toBeGreaterThan(0); + + const matchState = await readFaceAiMatchState(page); + + const expectedPhotoIds = Array.isArray(matchState.photoIds) ? matchState.photoIds.map((value) => String(value || '').trim()).filter(Boolean) : []; expect(expectedPhotoIds.length, 'Expected the final race page URL to include at least one FaceAI photo identifier.').toBeGreaterThan(0); + expect(Number(finalUrl.searchParams.get('faceaiMatchCount') || 0)).toBe(expectedPhotoIds.length); for (const photoKey of expectedPhotoIds) { const lookup = await lookupLivePhoto(page, photoKey); diff --git a/sync/www-deploy-manifest.md b/sync/www-deploy-manifest.md index 5dc6cde7..626ced47 100644 --- a/sync/www-deploy-manifest.md +++ b/sync/www-deploy-manifest.md @@ -14,8 +14,15 @@ All files in this rollout are deployed from the current working tree. ## Updated Files -- `www/fotoCR.jsp` -- `www/fotoCR-en.jsp` +- `www/_inc_header.jsp` +- `www/_inc_headerNoCr.jsp` +- `www/_inc_headerNoCr-en.jsp` +- `www/_inc_headerNoCrNews.jsp` +- `www/_inc_headerNoCrNews-en.jsp` +- `www/_js/rus-ecom-240621.js` +- `www/faceai_simulator_view.php` +- `www/faceai_config.php` +- `www/faceai_return.php` ## Excluded Files @@ -27,7 +34,7 @@ All files in this rollout are deployed from the current working tree. - Remote host: `marco@83.149.164.4:410` - Remote staging path: `/home/marco/regalamiunsorriso/incoming/www` - Remote live path: `/home/sites/regalamiunsorriso/www` -- Total files in this manifest: `2` +- Total files in this manifest: `9` ## Transfer Method diff --git a/test_pkl/test_images/photo_2026-04-20_12-10-59.jpg b/test_pkl/test_images/photo_2026-04-20_12-10-59.jpg new file mode 100644 index 00000000..290a4421 Binary files /dev/null and b/test_pkl/test_images/photo_2026-04-20_12-10-59.jpg differ diff --git a/www/_inc_header.jsp b/www/_inc_header.jsp index d61f4367..da07c2e2 100644 --- a/www/_inc_header.jsp +++ b/www/_inc_header.jsp @@ -43,7 +43,7 @@ - + diff --git a/www/_inc_headerNoCr-en.jsp b/www/_inc_headerNoCr-en.jsp index 5444a6cc..5725686b 100644 --- a/www/_inc_headerNoCr-en.jsp +++ b/www/_inc_headerNoCr-en.jsp @@ -21,7 +21,7 @@ - + diff --git a/www/_inc_headerNoCr.jsp b/www/_inc_headerNoCr.jsp index 5444a6cc..5725686b 100644 --- a/www/_inc_headerNoCr.jsp +++ b/www/_inc_headerNoCr.jsp @@ -21,7 +21,7 @@ - + diff --git a/www/_inc_headerNoCrNews-en.jsp b/www/_inc_headerNoCrNews-en.jsp index 10aea7f4..68ea3268 100644 --- a/www/_inc_headerNoCrNews-en.jsp +++ b/www/_inc_headerNoCrNews-en.jsp @@ -27,7 +27,7 @@ - + diff --git a/www/_inc_headerNoCrNews.jsp b/www/_inc_headerNoCrNews.jsp index 78bb7428..ca7030f7 100644 --- a/www/_inc_headerNoCrNews.jsp +++ b/www/_inc_headerNoCrNews.jsp @@ -29,7 +29,7 @@ - + diff --git a/www/_js/rus-ecom-240621.js b/www/_js/rus-ecom-240621.js index a7889efc..6d886712 100644 --- a/www/_js/rus-ecom-240621.js +++ b/www/_js/rus-ecom-240621.js @@ -228,6 +228,197 @@ function clearFaceAiErrorState() { window.history.replaceState({}, document.title, cleanUrl.pathname + cleanUrl.search + cleanUrl.hash); } +function getFaceAiMatchStorageEntryKey(storageKey) { + return "faceai-match-state:" + String(storageKey || "").trim(); +} + +function getFaceAiPendingMatchEntryKey() { + return "faceai-pending-match-state"; +} + +function getFaceAiPathForComparison(value) { + if (!value) { + return ""; + } + + try { + return decodeURIComponent(String(value)); + } catch (error) { + return String(value); + } +} + +function getFaceAiCurrentRaceId() { + return String($("#id_gara").val() || "").trim(); +} + +function isFaceAiStoredPayloadFresh(storedAt, maxAgeMs) { + var timestamp = Date.parse(String(storedAt || "")); + + if (!timestamp) { + return false; + } + + return (Date.now() - timestamp) <= maxAgeMs; +} + +function canUseFaceAiWebStorage(storage) { + var testKey = "__faceai_storage_test__"; + + if (!storage) { + return false; + } + + try { + storage.setItem(testKey, "1"); + storage.removeItem(testKey); + return true; + } catch (error) { + return false; + } +} + +function readFaceAiStoredMatchPayload(storageKey) { + var entryKey = getFaceAiMatchStorageEntryKey(storageKey); + var rawPayload = ""; + var windowNamePayload; + + if (!storageKey) { + return null; + } + + if (canUseFaceAiWebStorage(window.sessionStorage)) { + rawPayload = window.sessionStorage.getItem(entryKey) || ""; + } + + if (!rawPayload && canUseFaceAiWebStorage(window.localStorage)) { + rawPayload = window.localStorage.getItem(entryKey) || ""; + } + + if (!rawPayload && window.name) { + try { + windowNamePayload = JSON.parse(window.name); + if (windowNamePayload && windowNamePayload.faceAiStorageKey === storageKey && windowNamePayload.faceAiMatchState) { + rawPayload = JSON.stringify(windowNamePayload.faceAiMatchState); + } + } catch (error) { + rawPayload = ""; + } + } + + if (!rawPayload) { + return null; + } + + try { + return JSON.parse(rawPayload); + } catch (error) { + return null; + } +} + +function readFaceAiPendingMatchPayload() { + var entryKey = getFaceAiPendingMatchEntryKey(); + var rawPayload = ""; + var parsedPayload = null; + var windowNamePayload; + var currentPath = getFaceAiPathForComparison(window.location.pathname || ""); + var currentRaceId = getFaceAiCurrentRaceId(); + + if (canUseFaceAiWebStorage(window.sessionStorage)) { + rawPayload = window.sessionStorage.getItem(entryKey) || ""; + } + + if (!rawPayload && canUseFaceAiWebStorage(window.localStorage)) { + rawPayload = window.localStorage.getItem(entryKey) || ""; + } + + if (!rawPayload && window.name) { + try { + windowNamePayload = JSON.parse(window.name); + if (windowNamePayload && windowNamePayload.faceAiPendingMatchState) { + rawPayload = JSON.stringify(windowNamePayload.faceAiPendingMatchState); + } + } catch (error) { + rawPayload = ""; + } + } + + if (!rawPayload) { + return null; + } + + try { + parsedPayload = JSON.parse(rawPayload); + } catch (error) { + return null; + } + + if (!parsedPayload || !parsedPayload.payload || !parsedPayload.payload.photoIds || !parsedPayload.payload.photoIds.length) { + return null; + } + + if (!isFaceAiStoredPayloadFresh(parsedPayload.payload.storedAt, 15 * 60 * 1000)) { + return null; + } + + if (String(parsedPayload.raceId || "").trim() && currentRaceId && String(parsedPayload.raceId || "").trim() !== currentRaceId) { + return null; + } + + if (!String(parsedPayload.raceId || "").trim() && getFaceAiPathForComparison(parsedPayload.targetPath || "") !== currentPath) { + return null; + } + + return parsedPayload; +} + +function clearFaceAiStoredMatchPayload(storageKey) { + var entryKey = getFaceAiMatchStorageEntryKey(storageKey); + var pendingEntryKey = getFaceAiPendingMatchEntryKey(); + var windowNamePayload; + var pendingPayload = readFaceAiPendingMatchPayload(); + + if (!storageKey) { + return; + } + + if (canUseFaceAiWebStorage(window.sessionStorage)) { + window.sessionStorage.removeItem(entryKey); + } + + if (canUseFaceAiWebStorage(window.localStorage)) { + window.localStorage.removeItem(entryKey); + if (pendingPayload && pendingPayload.storageKey === storageKey) { + window.localStorage.removeItem(pendingEntryKey); + } + } + + if (canUseFaceAiWebStorage(window.sessionStorage) && pendingPayload && pendingPayload.storageKey === storageKey) { + window.sessionStorage.removeItem(pendingEntryKey); + } + + if (!window.name) { + return; + } + + try { + windowNamePayload = JSON.parse(window.name); + if (windowNamePayload && windowNamePayload.faceAiStorageKey === storageKey) { + delete windowNamePayload.faceAiStorageKey; + delete windowNamePayload.faceAiMatchState; + } + if (windowNamePayload && windowNamePayload.faceAiPendingMatchState && windowNamePayload.faceAiPendingMatchState.storageKey === storageKey) { + delete windowNamePayload.faceAiPendingMatchState; + } + if (!windowNamePayload || !Object.keys(windowNamePayload).length) { + window.name = ""; + } else { + window.name = JSON.stringify(windowNamePayload); + } + } catch (error) {} +} + function showFaceAiErrorModal(title, message) { var modal = $("#faceAiErrorModal"); @@ -266,6 +457,7 @@ function stripFaceAiStateFromUrl(url) { cleanUrl.searchParams.delete("faceaiMatchSource"); cleanUrl.searchParams.delete("faceaiMatchCount"); cleanUrl.searchParams.delete("faceaiPhotoIds"); + cleanUrl.searchParams.delete("faceaiMatchStorageKey"); return cleanUrl.toString(); } catch (error) { return url || fallbackUrl; @@ -279,6 +471,26 @@ function getFaceAiMatchState() { var params = new URLSearchParams(window.location.search || ""); var rawIds = params.get("faceaiPhotoIds") || ""; + var storageKey = String(params.get("faceaiMatchStorageKey") || "").trim(); + var storedPayload = null; + var pendingPayload = null; + + if (!rawIds && storageKey) { + storedPayload = readFaceAiStoredMatchPayload(storageKey); + if (storedPayload && storedPayload.photoIds && storedPayload.photoIds.length) { + rawIds = storedPayload.photoIds.join(","); + } + } + + if (!rawIds) { + pendingPayload = readFaceAiPendingMatchPayload(); + if (pendingPayload && pendingPayload.payload && pendingPayload.payload.photoIds && pendingPayload.payload.photoIds.length) { + rawIds = pendingPayload.payload.photoIds.join(","); + storageKey = storageKey || String(pendingPayload.storageKey || "").trim(); + storedPayload = storedPayload || pendingPayload.payload; + } + } + if (!rawIds) { return null; } @@ -303,7 +515,8 @@ function getFaceAiMatchState() { return { photoIds: photoIds, photoIdSet: photoIdSet, - matchCount: isNaN(parsedCount) ? photoIds.length : parsedCount, + matchCount: isNaN(parsedCount) ? ((storedPayload && storedPayload.matchCount) ? storedPayload.matchCount : photoIds.length) : parsedCount, + storageKey: storageKey, clearUrl: stripFaceAiStateFromUrl(window.location.href) }; } @@ -499,12 +712,17 @@ function renderFaceAiLegacyBanner(state) { } banner = $("#faceAiFilterBanner"); - $(document).off("click.faceAiClearFilter", "#faceAiClearFilterButton"); - $(document).on("click.faceAiClearFilter", "#faceAiClearFilterButton", function() { - window.location.href = state.clearUrl; - }); } + $(document).off("click.faceAiClearFilter", "#faceAiClearFilterButton"); + $(document).on("click.faceAiClearFilter", "#faceAiClearFilterButton", function() { + var activeState = window.faceAiMatchState || state; + if (activeState && activeState.storageKey) { + clearFaceAiStoredMatchPayload(activeState.storageKey); + } + window.location.href = activeState && activeState.clearUrl ? activeState.clearUrl : state.clearUrl; + }); + $("#faceAiFilterBannerTitle").text(copy.bannerTitle); $("#faceAiFilterBannerText").html(message); $("#faceAiClearFilterButton").text(copy.clearText); diff --git a/www/faceai_config.php b/www/faceai_config.php index 516ba828..b9327427 100644 --- a/www/faceai_config.php +++ b/www/faceai_config.php @@ -233,6 +233,63 @@ function faceai_render_message_page($title, $message, array $details = array(), exit; } +function faceai_render_storage_redirect_page($targetUrl, $storageKey, array $photoIds, $matchCount, $raceId = '') +{ + http_response_code(200); + header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); + header('Pragma: no-cache'); + header('Content-Type: text/html; charset=UTF-8'); + + $payload = array( + 'photoIds' => array_values($photoIds), + 'matchCount' => (int) $matchCount, + 'storedAt' => gmdate('c') + ); + $pendingPayload = array( + 'storageKey' => (string) $storageKey, + 'raceId' => trim((string) $raceId), + 'targetPath' => (string) (parse_url((string) $targetUrl, PHP_URL_PATH) ?: ''), + 'payload' => $payload + ); + + echo ''; + echo 'FaceAI redirect'; + echo ''; + echo '

Reindirizzamento FaceAI in corso

Sto preparando i risultati e torno alla galleria della gara.

'; + echo '
'; + exit; +} + function faceai_fetch_json($url) { $context = stream_context_create(array( diff --git a/www/faceai_return.php b/www/faceai_return.php index 6998bf03..334812f6 100644 --- a/www/faceai_return.php +++ b/www/faceai_return.php @@ -54,12 +54,23 @@ try { $photoIds[$photoId] = true; } - header('Location: ' . faceai_build_url($returnUrl, array( + $matchCount = count($photoIds); + if ($matchCount === 0) { + header('Location: ' . faceai_build_url($returnUrl, array( + 'faceaiMatchSource' => 'faceai', + 'faceaiMatchCount' => 0 + )), true, 302); + exit; + } + + $storageKey = 'faceai-result-' . preg_replace('/[^a-zA-Z0-9_-]/', '', $resultId); + $redirectUrl = faceai_build_url($returnUrl, array( 'faceaiMatchSource' => 'faceai', - 'faceaiMatchCount' => count($photoIds), - 'faceaiPhotoIds' => implode(',', array_keys($photoIds)) - )), true, 302); - exit; + 'faceaiMatchCount' => $matchCount, + 'faceaiMatchStorageKey' => $storageKey + )); + + faceai_render_storage_redirect_page($redirectUrl, $storageKey, array_keys($photoIds), $matchCount, (string) ($result['raceId'] ?? ($payload['raceId'] ?? ''))); } catch (Throwable $error) { faceai_render_message_page('Errore return FaceAI', $error->getMessage(), array(), 500); } diff --git a/www/faceai_simulator_view.php b/www/faceai_simulator_view.php index 847e15f6..002377c9 100644 --- a/www/faceai_simulator_view.php +++ b/www/faceai_simulator_view.php @@ -235,7 +235,7 @@ window.faceAiSimulator = { - +