Upload beautify
This commit is contained in:
7
config/cdn.php
Normal file
7
config/cdn.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'files_url' => env('FILES_CDN_URL', 'https://files.skinbase.org'),
|
||||
];
|
||||
108
config/discovery.php
Normal file
108
config/discovery.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'queue' => env('DISCOVERY_QUEUE', env('RECOMMENDATIONS_QUEUE', env('VISION_QUEUE', 'default'))),
|
||||
|
||||
// Versioned from day one for safe future migrations/experiments.
|
||||
'profile_version' => env('DISCOVERY_PROFILE_VERSION', 'profile-v1'),
|
||||
'event_version' => env('DISCOVERY_EVENT_VERSION', 'event-v1'),
|
||||
'algo_version' => env('DISCOVERY_ALGO_VERSION', env('RECOMMENDATIONS_ALGO_VERSION', 'clip-cosine-v1')),
|
||||
'cache_version' => env('DISCOVERY_CACHE_VERSION', 'cache-v1'),
|
||||
|
||||
'decay' => [
|
||||
// Exponential half-life: score * 0.5 every N hours.
|
||||
'half_life_hours' => (float) env('DISCOVERY_DECAY_HALF_LIFE_HOURS', 72),
|
||||
],
|
||||
|
||||
// Baseline event contribution weights.
|
||||
'weights' => [
|
||||
'view' => (float) env('DISCOVERY_WEIGHT_VIEW', 1.0),
|
||||
'click' => (float) env('DISCOVERY_WEIGHT_CLICK', 2.0),
|
||||
'favorite' => (float) env('DISCOVERY_WEIGHT_FAVORITE', 4.0),
|
||||
'download' => (float) env('DISCOVERY_WEIGHT_DOWNLOAD', 3.0),
|
||||
],
|
||||
|
||||
// Recommendation cache TTL in minutes (schema foundation only for now).
|
||||
'cache_ttl_minutes' => (int) env('DISCOVERY_CACHE_TTL_MINUTES', 60),
|
||||
|
||||
// Phase 8B: versioned ranking blend weights.
|
||||
// Blend components: w1=interest, w2=recency, w3=popularity, w4=novelty.
|
||||
'ranking' => [
|
||||
'default_weights' => [
|
||||
'version' => env('DISCOVERY_RANKING_WEIGHTS_VERSION', 'rank-w-v1'),
|
||||
'w1' => (float) env('DISCOVERY_RANKING_W1', 0.65),
|
||||
'w2' => (float) env('DISCOVERY_RANKING_W2', 0.20),
|
||||
'w3' => (float) env('DISCOVERY_RANKING_W3', 0.10),
|
||||
'w4' => (float) env('DISCOVERY_RANKING_W4', 0.05),
|
||||
],
|
||||
|
||||
// Per-algo overrides for safe rollout by algo_version.
|
||||
'algo_weight_sets' => [
|
||||
'clip-cosine-v1' => [
|
||||
'version' => env('DISCOVERY_RANKING_WEIGHTS_VERSION_CLIP_COSINE_V1', 'rank-w-v1'),
|
||||
'w1' => (float) env('DISCOVERY_RANKING_W1_CLIP_COSINE_V1', 0.65),
|
||||
'w2' => (float) env('DISCOVERY_RANKING_W2_CLIP_COSINE_V1', 0.20),
|
||||
'w3' => (float) env('DISCOVERY_RANKING_W3_CLIP_COSINE_V1', 0.10),
|
||||
'w4' => (float) env('DISCOVERY_RANKING_W4_CLIP_COSINE_V1', 0.05),
|
||||
],
|
||||
'clip-cosine-v2' => [
|
||||
'version' => env('DISCOVERY_RANKING_WEIGHTS_VERSION_CLIP_COSINE_V2', 'rank-w-v2-prod-1'),
|
||||
'w1' => (float) env('DISCOVERY_RANKING_W1_CLIP_COSINE_V2', 0.52),
|
||||
'w2' => (float) env('DISCOVERY_RANKING_W2_CLIP_COSINE_V2', 0.23),
|
||||
'w3' => (float) env('DISCOVERY_RANKING_W3_CLIP_COSINE_V2', 0.15),
|
||||
'w4' => (float) env('DISCOVERY_RANKING_W4_CLIP_COSINE_V2', 0.10),
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
// Phase 8 production rollout gates (deterministic user bucketing).
|
||||
'rollout' => [
|
||||
'enabled' => (bool) env('DISCOVERY_ROLLOUT_ENABLED', false),
|
||||
'baseline_algo_version' => env('DISCOVERY_ROLLOUT_BASELINE_ALGO_VERSION', 'clip-cosine-v1'),
|
||||
'candidate_algo_version' => env('DISCOVERY_ROLLOUT_CANDIDATE_ALGO_VERSION', 'clip-cosine-v2'),
|
||||
|
||||
// One of: g10, g50, g100.
|
||||
'active_gate' => env('DISCOVERY_ROLLOUT_ACTIVE_GATE', 'g10'),
|
||||
'gates' => [
|
||||
'g10' => [
|
||||
'name' => '10%',
|
||||
'percentage' => (int) env('DISCOVERY_ROLLOUT_GATE_10_PERCENT', 10),
|
||||
],
|
||||
'g50' => [
|
||||
'name' => '50%',
|
||||
'percentage' => (int) env('DISCOVERY_ROLLOUT_GATE_50_PERCENT', 50),
|
||||
],
|
||||
'g100' => [
|
||||
'name' => '100%',
|
||||
'percentage' => (int) env('DISCOVERY_ROLLOUT_GATE_100_PERCENT', 100),
|
||||
],
|
||||
],
|
||||
|
||||
// Emergency rollback toggle: force all traffic to one algo_version.
|
||||
'force_algo_version' => env('DISCOVERY_FORCE_ALGO_VERSION', ''),
|
||||
|
||||
// Guardrails (used operationally in runbook and dashboards).
|
||||
'monitoring_thresholds' => [
|
||||
'ctr_warn_drop_pct' => (float) env('DISCOVERY_ROLLOUT_WARN_CTR_DROP_PCT', 3.0),
|
||||
'ctr_rollback_drop_pct' => (float) env('DISCOVERY_ROLLOUT_ROLLBACK_CTR_DROP_PCT', 5.0),
|
||||
'long_dwell_warn_drop_pct' => (float) env('DISCOVERY_ROLLOUT_WARN_LONG_DWELL_DROP_PCT', 4.0),
|
||||
'long_dwell_rollback_drop_pct' => (float) env('DISCOVERY_ROLLOUT_ROLLBACK_LONG_DWELL_DROP_PCT', 8.0),
|
||||
'diversity_warn_concentration_rise_pct' => (float) env('DISCOVERY_ROLLOUT_WARN_DIVERSITY_CONCENTRATION_RISE_PCT', 10.0),
|
||||
'diversity_rollback_concentration_rise_pct' => (float) env('DISCOVERY_ROLLOUT_ROLLBACK_DIVERSITY_CONCENTRATION_RISE_PCT', 15.0),
|
||||
],
|
||||
],
|
||||
|
||||
// Offline evaluation objective weights (manual/data-driven tuning).
|
||||
'evaluation' => [
|
||||
'objective_weights' => [
|
||||
'ctr' => (float) env('DISCOVERY_EVAL_WEIGHT_CTR', 0.45),
|
||||
'save_rate' => (float) env('DISCOVERY_EVAL_WEIGHT_SAVE_RATE', 0.35),
|
||||
'long_dwell_share' => (float) env('DISCOVERY_EVAL_WEIGHT_LONG_DWELL', 0.25),
|
||||
'bounce_rate_penalty' => (float) env('DISCOVERY_EVAL_WEIGHT_BOUNCE_PENALTY', 0.15),
|
||||
],
|
||||
// Temporary switch: keep save_rate visible but exclude it from objective score.
|
||||
'save_rate_informational' => (bool) env('DISCOVERY_EVAL_SAVE_RATE_INFORMATIONAL', true),
|
||||
],
|
||||
];
|
||||
5
config/features.php
Normal file
5
config/features.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'uploads_v2' => (bool) env('SKINBASE_UPLOADS_V2', true),
|
||||
];
|
||||
37
config/recommendations.php
Normal file
37
config/recommendations.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
// Uses same queue family as vision jobs by default; keeps embedding work async and non-blocking.
|
||||
'queue' => env('RECOMMENDATIONS_QUEUE', env('VISION_QUEUE', 'default')),
|
||||
|
||||
'embedding' => [
|
||||
'enabled' => env('RECOMMENDATIONS_EMBEDDING_ENABLED', true),
|
||||
'model' => env('RECOMMENDATIONS_EMBEDDING_MODEL', 'clip'),
|
||||
'model_version' => env('RECOMMENDATIONS_EMBEDDING_MODEL_VERSION', 'v1'),
|
||||
'algo_version' => env('RECOMMENDATIONS_ALGO_VERSION', 'clip-cosine-v1'),
|
||||
|
||||
// Preferred CLIP endpoint for embeddings. The service also accepts an embedding payload from the analyze endpoint response.
|
||||
'endpoint' => env('CLIP_EMBED_ENDPOINT', '/embed'),
|
||||
'timeout_seconds' => (int) env('CLIP_EMBED_TIMEOUT_SECONDS', 8),
|
||||
'connect_timeout_seconds' => (int) env('CLIP_EMBED_CONNECT_TIMEOUT_SECONDS', 2),
|
||||
'retries' => (int) env('CLIP_EMBED_HTTP_RETRIES', 1),
|
||||
'retry_delay_ms' => (int) env('CLIP_EMBED_HTTP_RETRY_DELAY_MS', 200),
|
||||
|
||||
// Guardrails for malformed service responses.
|
||||
'min_dim' => (int) env('RECOMMENDATIONS_MIN_DIM', 64),
|
||||
'max_dim' => (int) env('RECOMMENDATIONS_MAX_DIM', 4096),
|
||||
],
|
||||
|
||||
// Backfill chunk size for resumable queue fan-out.
|
||||
'backfill_batch_size' => (int) env('RECOMMENDATIONS_BACKFILL_BATCH', 200),
|
||||
|
||||
// A/B support for recommendation ranking variants.
|
||||
'ab' => [
|
||||
'algo_versions' => array_values(array_filter(array_map(
|
||||
static fn (string $value): string => trim($value),
|
||||
explode(',', (string) env('RECOMMENDATIONS_AB_ALGO_VERSIONS', env('RECOMMENDATIONS_ALGO_VERSION', 'clip-cosine-v1')))
|
||||
))),
|
||||
],
|
||||
];
|
||||
@@ -35,4 +35,8 @@ return [
|
||||
],
|
||||
],
|
||||
|
||||
'image' => [
|
||||
'driver' => env('IMAGE_DRIVER', 'gd'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
28
config/tags.php
Normal file
28
config/tags.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Tag system configuration
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Keep moderation/banning logic in config (not hardcoded in code).
|
||||
| Populate these lists in production as needed.
|
||||
|
|
||||
*/
|
||||
|
||||
'max_length' => 32,
|
||||
'max_user_tags' => 15,
|
||||
|
||||
// Exact-match banned tags after normalization.
|
||||
'banned' => [
|
||||
// e.g. 'nsfw', 'hate', 'spam'
|
||||
],
|
||||
|
||||
// Optional regex patterns (PCRE) to block tags.
|
||||
'banned_regex' => [
|
||||
// e.g. '/\\b(?:badword1|badword2)\\b/i'
|
||||
],
|
||||
];
|
||||
81
config/uploads.php
Normal file
81
config/uploads.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'storage_root' => env('SKINBASE_STORAGE_ROOT', storage_path('app/artworks')),
|
||||
|
||||
'paths' => [
|
||||
'tmp' => 'tmp',
|
||||
'quarantine' => 'quarantine',
|
||||
'originals' => 'originals',
|
||||
'public' => 'public',
|
||||
],
|
||||
|
||||
'public_img_prefix' => 'img',
|
||||
|
||||
'max_size_mb' => 50,
|
||||
'max_pixels' => 12000,
|
||||
|
||||
'allowed_mimes' => [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/webp',
|
||||
],
|
||||
|
||||
'allow_gif' => env('UPLOAD_ALLOW_GIF', false),
|
||||
|
||||
'derivatives' => [
|
||||
'thumb' => ['max' => 320],
|
||||
'sq' => ['size' => 512],
|
||||
'md' => ['max' => 1024],
|
||||
'lg' => ['max' => 1920],
|
||||
'xl' => ['max' => 2560],
|
||||
],
|
||||
|
||||
'quality' => 85,
|
||||
|
||||
'queue_derivatives' => env('UPLOAD_QUEUE_DERIVATIVES', false),
|
||||
|
||||
'rate_limits' => [
|
||||
'decay_minutes' => env('UPLOAD_RATE_DECAY_MINUTES', 1),
|
||||
'init' => [
|
||||
'per_user' => env('UPLOAD_RATE_INIT_USER', 10),
|
||||
'per_ip' => env('UPLOAD_RATE_INIT_IP', 30),
|
||||
],
|
||||
'finish' => [
|
||||
'per_user' => env('UPLOAD_RATE_FINISH_USER', 6),
|
||||
'per_ip' => env('UPLOAD_RATE_FINISH_IP', 12),
|
||||
],
|
||||
'status' => [
|
||||
'per_user' => env('UPLOAD_RATE_STATUS_USER', 60),
|
||||
'per_ip' => env('UPLOAD_RATE_STATUS_IP', 120),
|
||||
],
|
||||
],
|
||||
|
||||
'quotas' => [
|
||||
'max_active_sessions' => env('UPLOAD_MAX_ACTIVE_SESSIONS', 100),
|
||||
'max_daily_sessions' => env('UPLOAD_MAX_DAILY_SESSIONS', 250),
|
||||
],
|
||||
|
||||
'draft_quota' => [
|
||||
'max_drafts_per_user' => env('SKINBASE_MAX_DRAFTS', 10),
|
||||
'max_draft_storage_mb_per_user' => env('SKINBASE_MAX_DRAFT_STORAGE_MB', 1024),
|
||||
'duplicate_hash_policy' => env('SKINBASE_DUPLICATE_HASH_POLICY', 'block'), // block|warn
|
||||
],
|
||||
|
||||
'tokens' => [
|
||||
'ttl_minutes' => env('UPLOAD_TOKEN_TTL_MINUTES', 60),
|
||||
],
|
||||
|
||||
'chunk' => [
|
||||
'max_bytes' => env('UPLOAD_CHUNK_MAX_BYTES', 5242880),
|
||||
'lock_seconds' => env('UPLOAD_CHUNK_LOCK_SECONDS', 10),
|
||||
'lock_wait_seconds' => env('UPLOAD_CHUNK_LOCK_WAIT_SECONDS', 5),
|
||||
],
|
||||
|
||||
'scan' => [
|
||||
'enabled' => env('UPLOAD_SCAN_ENABLED', false),
|
||||
'command' => env('UPLOAD_SCAN_COMMAND', []),
|
||||
],
|
||||
];
|
||||
34
config/vision.php
Normal file
34
config/vision.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'enabled' => env('VISION_ENABLED', true),
|
||||
|
||||
'queue' => env('VISION_QUEUE', 'default'),
|
||||
|
||||
'clip' => [
|
||||
'base_url' => env('CLIP_BASE_URL', ''),
|
||||
'endpoint' => env('CLIP_ANALYZE_ENDPOINT', '/analyze'),
|
||||
'timeout_seconds' => (int) env('CLIP_TIMEOUT_SECONDS', 8),
|
||||
'connect_timeout_seconds' => (int) env('CLIP_CONNECT_TIMEOUT_SECONDS', 2),
|
||||
'retries' => (int) env('CLIP_HTTP_RETRIES', 1),
|
||||
'retry_delay_ms' => (int) env('CLIP_HTTP_RETRY_DELAY_MS', 200),
|
||||
],
|
||||
|
||||
'yolo' => [
|
||||
'enabled' => env('YOLO_ENABLED', true),
|
||||
'base_url' => env('YOLO_BASE_URL', ''),
|
||||
'endpoint' => env('YOLO_ANALYZE_ENDPOINT', '/analyze'),
|
||||
'timeout_seconds' => (int) env('YOLO_TIMEOUT_SECONDS', 8),
|
||||
'connect_timeout_seconds' => (int) env('YOLO_CONNECT_TIMEOUT_SECONDS', 2),
|
||||
'retries' => (int) env('YOLO_HTTP_RETRIES', 1),
|
||||
'retry_delay_ms' => (int) env('YOLO_HTTP_RETRY_DELAY_MS', 200),
|
||||
|
||||
// Only run YOLO for photography content type.
|
||||
'photography_only' => env('YOLO_PHOTOGRAPHY_ONLY', true),
|
||||
],
|
||||
|
||||
// Which derivative variant to send to vision services.
|
||||
'image_variant' => env('VISION_IMAGE_VARIANT', 'md'),
|
||||
];
|
||||
Reference in New Issue
Block a user