About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- Simple, fast routing engine.
- Powerful dependency injection container.
- Multiple back-ends for session and cache storage.
- Expressive, intuitive database ORM.
- Database agnostic schema migrations.
- Robust background job processing.
- Real-time event broadcasting.
Laravel is accessible, powerful, and provides tools required for large, robust applications.
Learning Laravel
Laravel has the most extensive and thorough documentation and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out Laravel Learn, where you will be guided through building a modern Laravel application.
If you don't feel like reading, Laracasts can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
Laravel Sponsors
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the Laravel Partners program.
Premium Partners
Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the Laravel documentation.
Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the Code of Conduct.
Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via taylor@laravel.com. All security vulnerabilities will be promptly addressed.
Vision & AI Auto-Tagging Integration
Upload UI Feature Flag (uploads.v2)
The new React upload wizard is behind a feature flag and is disabled by default.
- Flag env var:
SKINBASE_UPLOADS_V2 - Config key:
features.uploads_v2 - Client flags source:
window.SKINBASE_FLAGS
Default behavior
SKINBASE_UPLOADS_V2=false→ legacy upload UI is rendered.SKINBASE_UPLOADS_V2=true→UploadWizardis rendered.
Setup
In .env (or .env.example for project defaults):
SKINBASE_UPLOADS_V2=false
Enable explicitly when ready:
SKINBASE_UPLOADS_V2=true
After changing env values, clear/reload config as usual:
php artisan config:clear
The system intentionally keeps legacy upload as the default until the flag is explicitly turned on.
Upload Moderation UI Flow
Admin moderation for draft uploads is available through a dedicated queue page.
- Page route:
/admin/uploads/moderation - Access: authenticated users with
role=adminorrole=moderator - Data source:
GET /api/admin/uploads/pending
Queue behavior
- The page loads pending draft uploads (
moderation_status=pending). - Moderators can enter an optional note per upload.
- Approve action calls:
POST /api/admin/uploads/{id}/approve- Sets moderation to approved and records moderator + timestamp.
- Reject action calls:
POST /api/admin/uploads/{id}/reject- Sets upload status/processing state to rejected and stores note.
Publish gate
- Normal users can publish only when
moderation_status=approved. - Admin users can publish with override behavior.
Similar Artworks Analytics (A/B Evaluation)
The artwork page similar-items block emits two event types:
impression(block rendered)click(item clicked)
Events are stored in similar_artwork_events and aggregated daily into similar_artwork_daily_metrics by algo_version.
- Ingest endpoint:
POST /api/analytics/similar-artworks - Aggregation command:
php artisan analytics:aggregate-similar-artworks --date=YYYY-MM-DD - Scheduler: runs daily at
03:10
Personalized Discovery Foundation (Phase 8)
This foundation adds versioned, async-only ingestion and profile normalization for personalized discovery.
- Tables:
user_interest_profilesuser_discovery_eventsuser_recommendation_cache
- Ingest endpoint:
POST /api/discovery/events(auth required) - Supported event types:
view,click,favorite,download - Processing model: non-blocking queue job (
IngestUserDiscoveryEventJob) - Normalization: recency-decay + score normalization in
UserInterestProfileService
No feed ranking/UI behavior is introduced in this foundation step.
Feed Endpoint Skeleton
The backend now exposes a personalized feed API skeleton:
- Endpoint:
GET /api/v1/feed(auth required) - Query params:
limit(1-50, default 24)cursor(opaque cursor token for pagination)algo_version(optional override)
- Response includes
dataitems andmeta.next_cursorfor cursor pagination.
Behavior:
- Reads
user_recommendation_cachebyuser_id + algo_version. - On cache miss/stale, returns immediate fallback results and dispatches async regeneration job.
- Regeneration runs in queue (
RegenerateUserRecommendationCacheJob) and writes refreshed cache. - Includes cold-start fallback (
popular + similar) and a diversity guard to avoid near-duplicates.
Feed Analytics Instrumentation
Feed analytics now track:
feed_impressionfeed_click
Payload dimensions:
user_id(derived from auth session)artwork_idpositionalgo_versionsource(personalized,cold_start,fallback)
Optional:
dwell_seconds(for click dwell bucket metrics)
Endpoints:
- Ingest:
POST /api/analytics/feed(auth required) - Daily aggregation:
php artisan analytics:aggregate-feed --date=YYYY-MM-DD - Admin report:
GET /api/admin/reports/feed-performance
Daily metrics include CTR, save-rate, and dwell buckets.
For non-blocking client transport, use navigator.sendBeacon with fetch(..., { keepalive: true }) fallback.
Reference helper: resources/js/lib/feedAnalytics.js.
Phase 8B: Ranking Weight Tuning (Manual + Data-Driven)
Discovery ranking now supports versioned blend weights per algo_version in config/discovery.php.
- Blend terms:
w1interest,w2recency,w3popularity,w4novelty - Per-algo sets:
discovery.ranking.algo_weight_sets - Safe rollout: deterministic traffic split by
algo_versionwith config gates (g10,g50,g100) - Emergency rollback:
DISCOVERY_FORCE_ALGO_VERSION=clip-cosine-v1
Offline evaluator and A/B helper:
- Evaluate objective across one/all algos:
php artisan analytics:evaluate-feed-weights --from=YYYY-MM-DD --to=YYYY-MM-DD- Optional:
--algo=clip-cosine-v1
- Baseline vs candidate comparison:
php artisan analytics:compare-feed-ab clip-cosine-v1 clip-cosine-v2 --from=YYYY-MM-DD --to=YYYY-MM-DD
Objective score uses feed_daily_metrics and configurable objective weights in discovery.evaluation.objective_weights.
Temporary production policy: set DISCOVERY_EVAL_SAVE_RATE_INFORMATIONAL=true to keep save_rate visible but excluded from objective score until save-event ingestion is verified.
Operational runbook: docs/feed-rollout-runbook.md.
Operations / Runbooks
- Upload UI v2 rollout, post-deploy monitoring, and rollback:
docs/ui/upload-v2-rollout-runbook.md - Feed rollout and rollback:
docs/feed-rollout-runbook.md
No automatic tuning is enabled in this phase.
Skinbase uses asynchronous AI tagging via AutoTagArtworkJob.
The job calls external vision services (CLIP and optional YOLO), normalizes tags, and attaches them through TagService as AI tags with confidence values.
Critical Safety Rule
⚠️ Publish must never depend on vision services.
- Upload/publish flow dispatches AI tagging to queue after publish work.
- Vision failures, timeouts, or service outages must not block artwork publish.
- If AI tagging fails, artwork remains published and can be tagged later (retry/manual/batch).
Environment Variables (Vision)
Set these in .env (all are optional; defaults are in config/vision.php):
Global
VISION_ENABLED(default:true)- Master switch for all AI auto-tagging.
VISION_QUEUE(default:default)- Queue name used by
AutoTagArtworkJob.
- Queue name used by
VISION_IMAGE_VARIANT(default:md)- Derivative variant sent to vision services (e.g.
md,lg).
- Derivative variant sent to vision services (e.g.
CLIP
CLIP_BASE_URL(default: empty)- Base URL for CLIP service (example:
https://clip.internal). - If empty, CLIP call is skipped.
- Base URL for CLIP service (example:
CLIP_ANALYZE_ENDPOINT(default:/analyze)- Path appended to
CLIP_BASE_URL.
- Path appended to
CLIP_TIMEOUT_SECONDS(default:8)- Request timeout for CLIP calls.
CLIP_CONNECT_TIMEOUT_SECONDS(default:2)- Connection timeout for CLIP calls.
CLIP_HTTP_RETRIES(default:1)- HTTP retry attempts for CLIP requests.
CLIP_HTTP_RETRY_DELAY_MS(default:200)- Delay between CLIP retries.
YOLO (optional)
YOLO_ENABLED(default:true)- Enables YOLO integration.
YOLO_BASE_URL(default: empty)- Base URL for YOLO service. If empty, YOLO call is skipped.
YOLO_ANALYZE_ENDPOINT(default:/analyze)- Path appended to
YOLO_BASE_URL.
- Path appended to
YOLO_TIMEOUT_SECONDS(default:8)- Request timeout for YOLO calls.
YOLO_CONNECT_TIMEOUT_SECONDS(default:2)- Connection timeout for YOLO calls.
YOLO_HTTP_RETRIES(default:1)- HTTP retry attempts for YOLO requests.
YOLO_HTTP_RETRY_DELAY_MS(default:200)- Delay between YOLO retries.
YOLO_PHOTOGRAPHY_ONLY(default:true)- When
true, YOLO is called only for artworks in photography content type.
- When
Expected CLIP Response Format
CLIP /analyze should return tags as either a direct list or under tags / data:
[
{ "tag": "cyberpunk", "confidence": 0.42 },
{ "tag": "city", "confidence": 0.31 }
]
Also accepted:
{
"tags": [
{ "tag": "cyberpunk", "confidence": 0.42 }
]
}
or
{
"data": [
{ "tag": "cyberpunk", "confidence": 0.42 }
]
}
Expected YOLO Response Format
YOLO may return the same tag list format as CLIP, or object detections:
{
"objects": [
{ "label": "person", "confidence": 0.91 },
{ "label": "camera", "confidence": 0.67 }
]
}
label values are converted to tags, confidence is preserved when present.
AutoTagArtworkJob Behavior
- Calls CLIP
/analyzewhenVISION_ENABLED=trueandCLIP_BASE_URLis set. - Optionally calls YOLO based on
YOLO_ENABLEDandYOLO_PHOTOGRAPHY_ONLY. - Merges CLIP + YOLO tags and keeps highest confidence for duplicates.
- Normalizes tags before attach (lowercase, cleanup, slug-safe format).
- Uses
TagService::attachAiTags()to store pivot data:source = aiconfidence = <float|null>
- Runs with queue retry + timeout safety (
tries,backoff,timeout). - Logs failures with reference/context for troubleshooting.
- On non-retriable response scenarios (e.g. 4xx), job exits safely without blocking publish.
Queue / Worker Requirements (VISION_QUEUE)
- Ensure a worker is running for the configured queue.
- Example worker command:
php artisan queue:work --queue=default
- If
VISION_QUEUE=vision, run worker for that queue:
php artisan queue:work --queue=vision
- In production, use Supervisor/systemd/Horizon to keep workers alive.
- Without an active worker, auto-tagging jobs remain queued and will not execute.
Local vs Production Notes
Local development
- For fully offline local work, set
VISION_ENABLED=false. - Or set only
CLIP_BASE_URL/YOLO_BASE_URLyou can reach locally. - Prefer short timeouts to avoid slow dev feedback loops.
Production
- Use internal/private service endpoints for CLIP/YOLO when possible.
- Keep conservative timeouts and low retry counts to prevent queue congestion.
- Monitor failed jobs and logs for vision service reliability.
- Scale queue workers based on upload volume and service latency.
Verify Setup (Health + Test Call)
After configuring env vars and restarting workers, verify in this order:
Quick helper (PowerShell):
pwsh -File ./scripts/vision-smoke.ps1
Optional flags:
pwsh -File ./scripts/vision-smoke.ps1 -EnvFile ".env" -SampleImageUrl "https://files.skinbase.org/img/aa/bb/cc/md.webp"
pwsh -File ./scripts/vision-smoke.ps1 -SkipAnalyze
- Confirm queue worker is consuming
VISION_QUEUE.
php artisan queue:work --queue=default
- Check CLIP/YOLO health endpoints (replace host/port as needed):
curl -fsS "$CLIP_BASE_URL/health"
curl -fsS "$YOLO_BASE_URL/health"
- Make a direct analyze test call (CLIP example):
curl -X POST "$CLIP_BASE_URL$CLIP_ANALYZE_ENDPOINT" \
-H "Content-Type: application/json" \
-d '{"image_url":"https://files.skinbase.org/img/aa/bb/cc/md.webp"}'
- Trigger an upload/publish and confirm:
- Publish response succeeds even if CLIP/YOLO is down.
AutoTagArtworkJobis queued/executed asynchronously.- AI tags appear on the artwork when services are healthy.
- Failures are logged, but publish is unaffected.
License
The Laravel framework is open-sourced software licensed under the MIT license.