Skip to content
Draft

squash #4280

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
e7f7fd1
squash
ildyria Apr 6, 2026
e8d5a72
fix
ildyria Apr 6, 2026
859bf81
fix ty
ildyria Apr 6, 2026
c454fd7
progress but still not ready
ildyria Apr 6, 2026
073de50
WIP still broken
ildyria Apr 8, 2026
20dc32f
more work
ildyria Apr 9, 2026
21872eb
Merge branch 'master' into assisted-vision
ildyria Apr 10, 2026
fe5983c
Merge branch 'master' into assisted-vision
ildyria Apr 10, 2026
f8d0353
disable AI vision for now
ildyria Apr 10, 2026
8f440eb
fixes
ildyria Apr 11, 2026
7229e5c
Merge branch 'pr/ildyria/4280' into assisted-vision
ildyria Apr 11, 2026
9b11422
WIP
ildyria Apr 11, 2026
f2595e6
more work
ildyria Apr 11, 2026
a80de73
more things
ildyria Apr 11, 2026
f077258
Merge branch 'master' into assisted-vision
ildyria Apr 11, 2026
e5f9037
feat(feature-30): implement policy gates, request authorizers, and ri…
Copilot Apr 11, 2026
231f575
docs: tick T-030-93 through T-030-103 as completed in tasks.md
Copilot Apr 11, 2026
86b29ab
Merge branch 'master' into assisted-vision
ildyria Apr 11, 2026
0e34152
Merge branch 'master' into assisted-vision
ildyria Apr 12, 2026
cdffae9
fixes
ildyria Apr 12, 2026
5f9e727
Fix language
ildyria Apr 12, 2026
092f616
Fix ty
ildyria Apr 12, 2026
4ccc43b
more fixes
ildyria Apr 12, 2026
74ff1de
more fixes
ildyria Apr 12, 2026
00cc766
Merge branch 'master' into assisted-vision
ildyria Apr 24, 2026
d0d056e
fix open questions
ildyria Apr 24, 2026
2bf7023
feat: migrate face recognition from InsightFace to DeepFace (MIT lice…
Copilot Apr 25, 2026
34b3daa
fixes
ildyria Apr 25, 2026
800fc6e
fixes
ildyria Apr 25, 2026
90acda6
force python 3.13
ildyria Apr 25, 2026
5538bd2
fix
ildyria Apr 25, 2026
1efba6b
fixes
ildyria Apr 26, 2026
4ef17a5
Add configuration data
ildyria May 1, 2026
dac77c8
feat(ai-vision): add min_face_size_pixels filter before laplacian che…
Copilot May 4, 2026
1ec0559
Merge branch 'master' into assisted-vision
ildyria May 5, 2026
8003352
fix formatting
ildyria May 5, 2026
43d61ba
Merge branch 'master' into assisted-vision
ildyria May 19, 2026
44e5280
Merge branch 'master' into assisted-vision
ildyria May 22, 2026
343e5d4
fixes
ildyria May 22, 2026
2354f9d
bump uv lock
ildyria May 22, 2026
8f20e82
Merge branch 'master' into assisted-vision
ildyria May 29, 2026
fc26268
Merge branch 'master' into assisted-vision
ildyria Jun 11, 2026
8e1009c
Merge branch 'master' into assisted-vision
ildyria Jun 12, 2026
f242f5d
Improvements
ildyria Jun 12, 2026
90e0ac8
progress
ildyria Jun 13, 2026
b2e6c67
Move it elsewhere
ildyria Jun 14, 2026
4fada48
also remove pipeline
ildyria Jun 14, 2026
9d2fb06
Merge branch 'master' into assisted-vision
ildyria Jun 15, 2026
260b19d
more fixes
ildyria Jun 16, 2026
39cd690
Merge branch 'master' into assisted-vision
ildyria Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ vite/*
secrets/*
# Local testing @ildyria
public/uploads-bck/*
public/uploads/*
*.sql

# Node
node_modules/
Expand All @@ -56,8 +58,12 @@ npm-debug.log
# Mapping for database and config used by docker compose
lychee/*

# Python
ai-vision-service/*

# Laravel
/storage/logs/*
/storage/tmp/*
/storage/framework/cache/*
/storage/framework/sessions/*
/storage/framework/views/*
Expand Down
3 changes: 0 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ updates:
dependency-type: "production"
development-dependencies:
dependency-type: "development"
ignore:
- dependency-name: "typescript"
versions: [ ">=6.0.0" ]

- package-ecosystem: composer
directory: /
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/CICD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ on:
- '**/*.md'
- 'public/dist/*.js'
- 'public/dist/**/*.js'
- 'ai-vision-service/**'
pull_request:
paths-ignore:
- '**/*.md'
- 'public/dist/*.js'
- 'public/dist/**/*.js'
- 'ai-vision-service/**'
# Allow manually triggering the workflow.
workflow_dispatch:

Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,8 @@ jobs:
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: 'Dependency Review'
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
# No fix available yet
# Note that the model is directly baked into the inage
# So the risk is limited.
allow-ghsas: GHSA-hqmj-h5c6-369m
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ clover.xml
.NO_AUTO_COMPOSER_MIGRATE
storage/bootstrap/cache/*
storage/image-jobs/*
**/__pycache__/**
.coverage

# used by Vite
public/hot
Expand Down
2 changes: 1 addition & 1 deletion app/Actions/Albums/Flow.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ private function getQuery(Album|null $base, bool $with_relations): AlbumBuilder
'min_privilege_cover', 'min_privilege_cover.size_variants',
'statistics',
'photos',
'photos.statistics', 'photos.size_variants', 'photos.palette', 'photos.tags', 'photos.rating']);
'photos.statistics', 'photos.size_variants', 'photos.palette', 'photos.tags', 'photos.rating', 'photos.faces', 'photos.faces.person', ]);
}

// Only join what we need for ordering.
Expand Down
4 changes: 4 additions & 0 deletions app/Actions/Diagnostics/Errors.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
namespace App\Actions\Diagnostics;

use App\Actions\Diagnostics\Pipes\Checks\AdminUserExistsCheck;
use App\Actions\Diagnostics\Pipes\Checks\AiVisionServiceCheck;
use App\Actions\Diagnostics\Pipes\Checks\AiVisionServiceConfigCheck;
use App\Actions\Diagnostics\Pipes\Checks\AppUrlMatchCheck;
use App\Actions\Diagnostics\Pipes\Checks\AuthDisabledCheck;
use App\Actions\Diagnostics\Pipes\Checks\BasicPermissionCheck;
Expand Down Expand Up @@ -76,6 +78,8 @@ class Errors
SupporterCheck::class,
ImagickPdfCheck::class,
WatermarkerEnabledCheck::class,
AiVisionServiceCheck::class,
AiVisionServiceConfigCheck::class,
StatisticsIntegrityCheck::class,
WebshopCheck::class,
SecurityAdvisoriesCheck::class,
Expand Down
115 changes: 115 additions & 0 deletions app/Actions/Diagnostics/Pipes/Checks/AiVisionServiceCheck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

/**
* SPDX-License-Identifier: MIT
* Copyright (c) 2017-2018 Tobias Reich
* Copyright (c) 2018-2026 LycheeOrg.
*/

namespace App\Actions\Diagnostics\Pipes\Checks;

use App\Contracts\DiagnosticPipe;
use App\DTO\DiagnosticData;
use App\Repositories\ConfigManager;
use App\Services\Image\FacialRecognitionService;
use Illuminate\Support\Facades\Schema;

/**
* Check if the AI Vision service is properly configured and reachable.
*/
class AiVisionServiceCheck implements DiagnosticPipe
{
public function __construct(
protected readonly ConfigManager $config_manager,
protected readonly FacialRecognitionService $facial_recognition_service,
) {
}

/**
* {@inheritDoc}
*/
public function handle(array &$data, \Closure $next): array
{
if (!Schema::hasTable('configs')) {
return $next($data);
}

// Skip check if AI Vision is disabled
if (!$this->config_manager->getValueAsBool('ai_vision_enabled')) {
return $next($data);
}

if (!$this->facial_recognition_service->isConfigured()) {
$data[] = DiagnosticData::error(
'AI Vision: Service URL is not configured. Set AI_VISION_FACE_URL in your .env file.',
self::class,
[]
);

return $next($data);
}

$this->checkServiceHealth($data);

return $next($data);
}

/**
* Check if the AI Vision service health endpoint is reachable and returns proper data.
*
* @param DiagnosticData[] &$data
*
* @return void
*/
private function checkServiceHealth(array &$data): void
{
$service_url = config('features.ai-vision-service.face-url', '');

try {
$response = $this->facial_recognition_service->checkHealthRaw(5);

if (!$response->successful()) {
$data[] = DiagnosticData::error(
'AI Vision: Service health check failed with status ' . $response->status() . '. The service may be offline or unreachable.',
self::class,
['Check that the AI Vision service is running at: ' . $service_url]
);

return;
}

$health_data = $response->json();
if (!is_array($health_data) || !isset($health_data['status'])) {
$data[] = DiagnosticData::error(
'AI Vision: Service health endpoint returned invalid response format. Expected JSON with "status" field.',
self::class,
['Response: ' . $response->body()]
);

return;
}

if ($health_data['status'] !== 'ok' && $health_data['status'] !== 'healthy') {
$data[] = DiagnosticData::warn(
'AI Vision: Service reported unhealthy status: ' . $health_data['status'],
self::class,
[]
);
}
} catch (\Illuminate\Http\Client\ConnectionException $e) {
$data[] = DiagnosticData::error(
'AI Vision: Could not connect to service at ' . rtrim($service_url, '/') . '/health',
self::class,
['Check that the AI Vision service is running and the URL is correct.', $e->getMessage()]
);
} catch (\Exception $e) {
// @codeCoverageIgnoreStart
$data[] = DiagnosticData::error(
'AI Vision: Service health check failed with error: ' . $e->getMessage(),
self::class,
[]
);
// @codeCoverageIgnoreEnd
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

/**
* SPDX-License-Identifier: MIT
* Copyright (c) 2017-2018 Tobias Reich
* Copyright (c) 2018-2026 LycheeOrg.
*/

namespace App\Actions\Diagnostics\Pipes\Checks;

use App\Contracts\DiagnosticPipe;
use App\DTO\DiagnosticData;
use App\Repositories\ConfigManager;
use App\Services\Image\FacialRecognitionService;
use Illuminate\Support\Facades\Schema;

/**
* In debug mode, expose the AI Vision runtime configuration in diagnostics.
*/
class AiVisionServiceConfigCheck implements DiagnosticPipe
{
public function __construct(
protected readonly ConfigManager $config_manager,
protected readonly FacialRecognitionService $facial_recognition_service,
) {
}

/**
* {@inheritDoc}
*/
public function handle(array &$data, \Closure $next): array
{
if (config('app.debug') !== true) {
return $next($data);
}

if (!Schema::hasTable('configs')) {
return $next($data);
}

if ($this->config_manager->getValueAsBool('ai_vision_enabled') !== true) {
return $next($data);
}

if (!$this->facial_recognition_service->isConfigured()) {
return $next($data);
}

$configuration = $this->facial_recognition_service->getConfiguration();
if ($configuration === null) {
$data[] = DiagnosticData::warn(
'AI Vision: Could not fetch runtime configuration from the service while APP_DEBUG is enabled.',
self::class,
[]
);

return $next($data);
}

$details = [];
foreach ($configuration as $key => $value) {
$details[] = $key . ': ' . $value;
}

$data[] = DiagnosticData::info(
'AI Vision: Runtime configuration from service (debug mode).',
self::class,
$details
);

return $next($data);
}
}
1 change: 1 addition & 0 deletions app/Actions/Photo/Create.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ private function handleStandalone(InitDTO $init_dto): Photo
Shared\GeodecodeLocation::class,
Shared\ExtractColourPalette::class,
Shared\NotifyAlbums::class,
Standalone\AutoScanFacesOnUpload::class,
];

return $this->executePipeOnDTO($pipes, $dto)->getPhoto();
Expand Down
54 changes: 54 additions & 0 deletions app/Actions/Photo/Pipes/Standalone/AutoScanFacesOnUpload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/**
* SPDX-License-Identifier: MIT
* Copyright (c) 2017-2018 Tobias Reich
* Copyright (c) 2018-2026 LycheeOrg.
*/

namespace App\Actions\Photo\Pipes\Standalone;

use App\Contracts\PhotoCreate\StandalonePipe;
use App\DTO\PhotoCreate\StandaloneDTO;
use App\Jobs\DispatchFaceScanJob;
use App\Repositories\ConfigManager;
use Illuminate\Support\Facades\Log;

/**
* Automatically trigger face scanning when a photo is uploaded,
* if the AI Vision face scanning feature is enabled.
*/
class AutoScanFacesOnUpload implements StandalonePipe
{
public function __construct(
protected readonly ConfigManager $config_manager,
) {
}

public function handle(StandaloneDTO $state, \Closure $next): StandaloneDTO
{
// Process through the rest of the pipeline first
$state = $next($state);

if ($state->photo->isPhoto() === false) {
// If this is not a photo (e.g., a video), we skip face scanning
return $state;
}

// Check if AI vision is enabled
if ($this->config_manager->getValueAsString('ai_vision_enabled') !== '1') {
return $state;
}

// Check if face scanning is enabled
if ($this->config_manager->getValueAsString('ai_vision_face_enabled') !== '1') {
return $state;
}

// Dispatch face scanning job for the uploaded photo
Log::info("AutoScanFacesOnUpload: dispatching face scan for photo {$state->photo->id}.");
DispatchFaceScanJob::dispatch($state->photo->id);

return $state;
}
}
Loading
Loading