get('per_page', 24)); $query = Artwork::public()->published(); if ($category) { $query->whereHas('categories', function ($q) use ($category) { $q->where('categories.id', $category->id); }); } if ($request->filled('q')) { $q = $request->get('q'); $query->where(function ($sub) use ($q) { $sub->where('title', 'like', '%' . $q . '%') ->orWhere('description', 'like', '%' . $q . '%'); }); } $sort = $request->get('sort', 'latest'); if ($sort === 'oldest') { $query->orderBy('published_at', 'asc'); } else { $query->orderByDesc('published_at'); } // Important: do NOT eager-load artwork_stats in listings $artworks = $query->cursorPaginate($perPage); return view('artworks.index', [ 'artworks' => $artworks, 'category' => $category, ]); } /** * Show a single artwork by slug. Resolve the slug manually to avoid implicit * route-model binding exceptions when the slug does not correspond to an artwork. */ public function show(Request $request, string $contentTypeSlug, string $categoryPath, $artwork = null) { // Manually resolve artwork by slug when provided. The route may bind // the 'artwork' parameter to an Artwork model or pass the slug string. $foundArtwork = null; $artworkSlug = null; if ($artwork instanceof Artwork) { $foundArtwork = $artwork; $artworkSlug = $artwork->slug; } elseif ($artwork) { $artworkSlug = (string) $artwork; $foundArtwork = Artwork::where('slug', $artworkSlug)->first(); } // If no artwork was found, treat the request as a category path. // The route places the artwork slug in the last segment, so include it // when forwarding to CategoryPageController to support arbitrary-depth paths if (! $foundArtwork) { $combinedPath = $categoryPath; if ($artworkSlug) { $combinedPath = trim($categoryPath . '/' . $artworkSlug, '/'); } return app(CategoryPageController::class)->show(request(), $contentTypeSlug, $combinedPath); } if (! $foundArtwork->is_public || ! $foundArtwork->is_approved || $foundArtwork->trashed()) { abort(404); } $foundArtwork->loadMissing(['categories.contentType', 'user']); $defaultAlgoVersion = (string) config('recommendations.embedding.algo_version', 'clip-cosine-v1'); $selectedAlgoVersion = $this->selectAlgoVersionForRequest($request, $defaultAlgoVersion); $similarService = app(SimilarArtworksService::class); $similarArtworks = $similarService->forArtwork((int) $foundArtwork->id, 12, $selectedAlgoVersion); if ($similarArtworks->isEmpty() && $selectedAlgoVersion !== $defaultAlgoVersion) { $similarArtworks = $similarService->forArtwork((int) $foundArtwork->id, 12, $defaultAlgoVersion); $selectedAlgoVersion = $defaultAlgoVersion; } $similarArtworks->each(static function (Artwork $item): void { $item->loadMissing(['categories.contentType', 'user']); }); $similarItems = $similarArtworks ->map(function (Artwork $item): ?array { $category = $item->categories->first(); $contentType = $category?->contentType; if (! $category || ! $contentType || empty($item->slug)) { return null; } return [ 'id' => (int) $item->id, 'title' => (string) $item->title, 'author' => (string) optional($item->user)->name, 'thumb' => (string) ($item->thumb_url ?? $item->thumb ?? '/gfx/sb_join.jpg'), 'thumb_srcset' => (string) ($item->thumb_srcset ?? ''), 'url' => route('artworks.show', [ 'contentTypeSlug' => (string) $contentType->slug, 'categoryPath' => (string) $category->slug, 'artwork' => (string) $item->slug, ]), ]; }) ->filter() ->values(); return view('artworks.show', [ 'artwork' => $foundArtwork, 'similarItems' => $similarItems, 'similarAlgoVersion' => $selectedAlgoVersion, ]); } private function selectAlgoVersionForRequest(Request $request, string $default): string { $configured = (array) config('recommendations.ab.algo_versions', []); $versions = array_values(array_filter(array_map(static fn ($value): string => trim((string) $value), $configured))); if ($versions === []) { return $default; } if (! in_array($default, $versions, true)) { array_unshift($versions, $default); $versions = array_values(array_unique($versions)); } $forced = trim((string) $request->query('algo_version', '')); if ($forced !== '' && in_array($forced, $versions, true)) { return $forced; } if (count($versions) === 1) { return $versions[0]; } $visitorKey = $request->user()?->id ? 'u:' . (string) $request->user()->id : 's:' . (string) $request->session()->getId(); $bucket = abs(crc32($visitorKey)) % count($versions); return $versions[$bucket] ?? $default; } }