set('vision.vector_gateway.enabled', true); config()->set('vision.vector_gateway.base_url', 'https://vision.klevze.net'); config()->set('vision.vector_gateway.api_key', 'test-key'); config()->set('vision.vector_gateway.upsert_endpoint', '/vectors/upsert'); config()->set('vision.vector_gateway.upsert_file_endpoint', '/vectors/upsert/file'); config()->set('vision.vector_gateway.search_endpoint', '/vectors/search'); config()->set('vision.vector_gateway.search_point_endpoint', '/vectors/search/point'); config()->set('vision.image_variant', 'md'); config()->set('cdn.files_url', 'https://files.skinbase.org'); }); it('indexes artworks into the vector gateway with artwork metadata', function (): void { $contentType = ContentType::query()->create([ 'name' => 'Photography', 'slug' => 'photography', 'description' => '', ]); $category = Category::query()->create([ 'content_type_id' => $contentType->id, 'parent_id' => null, 'name' => 'Abstract', 'slug' => 'abstract', 'description' => '', 'is_active' => true, 'sort_order' => 0, ]); $artwork = Artwork::factory()->create([ 'hash' => 'aabbcc112233', 'thumb_ext' => 'webp', ]); $artwork->categories()->attach($category->id); $tag = Tag::query()->create(['name' => 'Skyline', 'slug' => 'skyline']); $artwork->tags()->attach($tag->id, ['source' => 'ai', 'confidence' => 0.88]); Http::fake([ 'https://files.skinbase.org/*' => Http::response( 'fake-image-bytes', 200, ['Content-Type' => 'image/webp'] ), 'https://vision.klevze.net/vectors/upsert/file' => Http::response([ 'id' => $artwork->id, 'collection' => 'images', 'dim' => 512, ], 200), ]); artisan('artworks:vectors-index', ['--limit' => 1]) ->assertSuccessful(); $artwork->refresh(); expect($artwork->last_vector_indexed_at) ->not->toBeNull(); Http::assertSentCount(2); Http::assertSent(function ($request): bool { return $request->method() === 'GET' && $request->url() === 'https://files.skinbase.org/artworks/md/aa/bb/aabbcc112233.webp'; }); Http::assertSent(function ($request) use ($artwork): bool { if ( $request->url() !== 'https://vision.klevze.net/vectors/upsert/file' ) { return false; } $parts = collect( $request->data() )->keyBy('name'); $idPart = $parts->get('id', []); $metadataPart = $parts->get( 'metadata_json', [] ); $filePart = $parts->get('file', []); $metadata = json_decode( (string) ( $metadataPart['contents'] ?? '' ), true ); return $request->hasHeader( 'X-API-Key', 'test-key' ) && (string) ( $idPart['contents'] ?? '' ) === (string) $artwork->id && (string) ( $filePart['contents'] ?? '' ) === 'fake-image-bytes' && (string) ( $filePart['filename'] ?? '' ) === sprintf( 'artwork-%d.webp', $artwork->id ) && is_array($metadata) && ($metadata['content_type'] ?? null) === 'Photography' && ($metadata['category'] ?? null) === 'Abstract' && ($metadata['tags'] ?? null) === ['skyline'] && array_key_exists( 'is_public', $metadata ) && array_key_exists( 'is_deleted', $metadata ) && array_key_exists( 'is_nsfw', $metadata ) && array_key_exists( 'category_id', $metadata ) && array_key_exists( 'content_type_id', $metadata ) && array_key_exists( 'status', $metadata ); }); }); it('searches similar artworks through the vector gateway', function (): void { $contentType = ContentType::query()->create([ 'name' => 'Wallpapers', 'slug' => 'wallpapers', 'description' => '', ]); $category = Category::query()->create([ 'content_type_id' => $contentType->id, 'parent_id' => null, 'name' => 'Nature', 'slug' => 'nature', 'description' => '', 'is_active' => true, 'sort_order' => 0, ]); $source = Artwork::factory()->create([ 'title' => 'Source artwork', 'hash' => 'aabbcc112233', 'thumb_ext' => 'webp', ]); $source->categories()->attach($category->id); $similar = Artwork::factory()->create([ 'title' => 'Nearby artwork', 'hash' => 'ddeeff445566', 'thumb_ext' => 'webp', ]); $similar->categories()->attach($category->id); Http::fake([ 'https://vision.klevze.net/vectors/search/point' => Http::response([ 'results' => [ [ 'id' => $source->id, 'score' => 1.0, ], [ 'id' => $similar->id, 'score' => 0.9876, ], ], ], 200), ]); artisan('artworks:vectors-search', [ 'artwork_id' => $source->id, '--limit' => 5, ]) ->expectsTable(['ID', 'Score', 'Title', 'Content Type', 'Category'], [[ 'id' => $similar->id, 'score' => '0.9876', 'title' => 'Nearby artwork', 'content_type' => 'Wallpapers', 'category' => 'Nature', ]]) ->assertSuccessful(); Http::assertSentCount(1); Http::assertSent( function ($request) use ($source): bool { return $request->url() === 'https://vision.klevze.net/vectors/search/point' && ($request->data()['id'] ?? null) === $source->id; } ); Http::assertNotSent( fn ($request): bool => str_contains( $request->url(), 'files.skinbase.org' ) ); }); it('can re-upsert only artworks that already have local embeddings', function (): void { $contentType = ContentType::query()->create([ 'name' => 'Photography', 'slug' => 'photography', 'description' => '', ]); $category = Category::query()->create([ 'content_type_id' => $contentType->id, 'parent_id' => null, 'name' => 'Portraits', 'slug' => 'portraits', 'description' => '', 'is_active' => true, 'sort_order' => 0, ]); $embeddedArtwork = Artwork::factory()->create([ 'hash' => '112233445566', 'thumb_ext' => 'webp', 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), ]); $embeddedArtwork->categories()->attach($category->id); ArtworkEmbedding::query()->create([ 'artwork_id' => $embeddedArtwork->id, 'model' => 'clip', 'model_version' => 'v1', 'algo_version' => 'clip-cosine-v1', 'dim' => 2, 'embedding_json' => json_encode([0.6, 0.8], JSON_THROW_ON_ERROR), 'source_hash' => '112233445566', 'is_normalized' => true, 'generated_at' => now(), 'meta' => ['source' => 'clip'], ]); $nonEmbeddedArtwork = Artwork::factory()->create([ 'hash' => 'aabbccddeeff', 'thumb_ext' => 'webp', 'is_public' => true, 'is_approved' => true, 'published_at' => now()->subDay(), ]); $nonEmbeddedArtwork->categories()->attach($category->id); Http::fake([ 'https://files.skinbase.org/*' => Http::response( 'fake-image-bytes', 200, ['Content-Type' => 'image/webp'] ), 'https://vision.klevze.net/vectors/upsert/file' => Http::response([ 'id' => $embeddedArtwork->id, 'collection' => 'images', 'dim' => 512, ], 200), ]); artisan('artworks:vectors-index', [ '--embedded-only' => true, '--limit' => 10, ])->assertSuccessful(); $embeddedArtwork->refresh(); $nonEmbeddedArtwork->refresh(); expect($embeddedArtwork->last_vector_indexed_at) ->not->toBeNull() ->and( $nonEmbeddedArtwork->last_vector_indexed_at ) ->toBeNull(); /* * One indexed artwork: * 1x CDN image download * 1x /vectors/upsert/file */ Http::assertSentCount(2); Http::assertSent( function ($request): bool { return $request->method() === 'GET' && $request->url() === 'https://files.skinbase.org/artworks/md/11/22/112233445566.webp'; } ); Http::assertSent( function ($request) use ( $embeddedArtwork ): bool { if ( $request->url() !== 'https://vision.klevze.net/vectors/upsert/file' ) { return false; } $parts = collect( $request->data() )->keyBy('name'); $idPart = $parts->get('id', []); $filePart = $parts->get('file', []); return (string) ( $idPart['contents'] ?? '' ) === (string) $embeddedArtwork->id && (string) ( $filePart['contents'] ?? '' ) === 'fake-image-bytes' && (string) ( $filePart['filename'] ?? '' ) === sprintf( 'artwork-%d.webp', $embeddedArtwork->id ); } ); });