Upload beautify

This commit is contained in:
2026-02-14 15:14:12 +01:00
parent e129618910
commit 79192345e3
249 changed files with 24436 additions and 1021 deletions

366
README.md
View File

@@ -54,6 +54,372 @@ In order to ensure that the Laravel community is welcoming to all, please review
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto: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``UploadWizard` is rendered.
### Setup
In `.env` (or `.env.example` for project defaults):
```dotenv
SKINBASE_UPLOADS_V2=false
```
Enable explicitly when ready:
```dotenv
SKINBASE_UPLOADS_V2=true
```
After changing env values, clear/reload config as usual:
```bash
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=admin` or `role=moderator`
- Data source: `GET /api/admin/uploads/pending`
### Queue behavior
1. The page loads pending draft uploads (`moderation_status=pending`).
2. Moderators can enter an optional note per upload.
3. Approve action calls:
- `POST /api/admin/uploads/{id}/approve`
- Sets moderation to approved and records moderator + timestamp.
4. 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_profiles`
- `user_discovery_events`
- `user_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 `data` items and `meta.next_cursor` for cursor pagination.
Behavior:
- Reads `user_recommendation_cache` by `user_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_impression`
- `feed_click`
Payload dimensions:
- `user_id` (derived from auth session)
- `artwork_id`
- `position`
- `algo_version`
- `source` (`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: `w1` interest, `w2` recency, `w3` popularity, `w4` novelty
- Per-algo sets: `discovery.ranking.algo_weight_sets`
- Safe rollout: deterministic traffic split by `algo_version` with 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`.
- `VISION_IMAGE_VARIANT` (default: `md`)
- Derivative variant sent to vision services (e.g. `md`, `lg`).
#### CLIP
- `CLIP_BASE_URL` (default: empty)
- Base URL for CLIP service (example: `https://clip.internal`).
- If empty, CLIP call is skipped.
- `CLIP_ANALYZE_ENDPOINT` (default: `/analyze`)
- Path appended to `CLIP_BASE_URL`.
- `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`.
- `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.
### Expected CLIP Response Format
CLIP `/analyze` should return tags as either a direct list or under `tags` / `data`:
```json
[
{ "tag": "cyberpunk", "confidence": 0.42 },
{ "tag": "city", "confidence": 0.31 }
]
```
Also accepted:
```json
{
"tags": [
{ "tag": "cyberpunk", "confidence": 0.42 }
]
}
```
or
```json
{
"data": [
{ "tag": "cyberpunk", "confidence": 0.42 }
]
}
```
### Expected YOLO Response Format
YOLO may return the same tag list format as CLIP, or object detections:
```json
{
"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 `/analyze` when `VISION_ENABLED=true` and `CLIP_BASE_URL` is set.
- Optionally calls YOLO based on `YOLO_ENABLED` and `YOLO_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 = ai`
- `confidence = <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:
```bash
php artisan queue:work --queue=default
```
- If `VISION_QUEUE=vision`, run worker for that queue:
```bash
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_URL` you 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):
```powershell
pwsh -File ./scripts/vision-smoke.ps1
```
Optional flags:
```powershell
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
```
1. Confirm queue worker is consuming `VISION_QUEUE`.
```bash
php artisan queue:work --queue=default
```
1. Check CLIP/YOLO health endpoints (replace host/port as needed):
```bash
curl -fsS "$CLIP_BASE_URL/health"
curl -fsS "$YOLO_BASE_URL/health"
```
1. Make a direct analyze test call (CLIP example):
```bash
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"}'
```
1. Trigger an upload/publish and confirm:
- Publish response succeeds even if CLIP/YOLO is down.
- `AutoTagArtworkJob` is 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](https://opensource.org/licenses/MIT).