Implement creator studio and upload updates
This commit is contained in:
@@ -6,13 +6,17 @@ namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Artwork;
|
||||
use App\Services\Vision\VectorService;
|
||||
use Carbon\CarbonImmutable;
|
||||
use InvalidArgumentException;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
final class IndexArtworkVectorsCommand extends Command
|
||||
{
|
||||
protected $signature = 'artworks:vectors-index
|
||||
{--order=updated-desc : Ordering mode: updated-desc or id-asc}
|
||||
{--start-id=0 : Start from this artwork id (inclusive)}
|
||||
{--after-id=0 : Resume after this artwork id}
|
||||
{--after-updated-at= : Resume updated-desc mode after this ISO-8601 timestamp}
|
||||
{--batch=100 : Batch size per iteration}
|
||||
{--limit=0 : Maximum artworks to process in this run}
|
||||
{--embedded-only : Re-upsert only artworks that already have local embeddings}
|
||||
@@ -29,8 +33,16 @@ final class IndexArtworkVectorsCommand extends Command
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$order = $this->normalizeOrder((string) $this->option('order'));
|
||||
$startId = max(0, (int) $this->option('start-id'));
|
||||
$afterId = max(0, (int) $this->option('after-id'));
|
||||
try {
|
||||
$afterUpdatedAt = $this->resolveAfterUpdatedAt($order, (string) $this->option('after-updated-at'));
|
||||
} catch (InvalidArgumentException $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
|
||||
return self::INVALID;
|
||||
}
|
||||
$batch = max(1, min((int) $this->option('batch'), 1000));
|
||||
$limit = max(0, (int) $this->option('limit'));
|
||||
$publicOnly = (bool) $this->option('public-only');
|
||||
@@ -42,6 +54,7 @@ final class IndexArtworkVectorsCommand extends Command
|
||||
$skipped = 0;
|
||||
$failed = 0;
|
||||
$lastId = $afterId;
|
||||
$nextUpdatedAt = $afterUpdatedAt;
|
||||
|
||||
if ($startId > 0 && $afterId > 0) {
|
||||
$this->warn(sprintf(
|
||||
@@ -51,10 +64,16 @@ final class IndexArtworkVectorsCommand extends Command
|
||||
));
|
||||
}
|
||||
|
||||
if ($order === 'updated-desc' && ($startId > 0 || $afterId > 0) && $afterUpdatedAt === null) {
|
||||
$this->warn('The --start-id/--after-id options are legacy id-asc cursors. They are ignored unless --order=id-asc is used, or unless --after-updated-at is also provided for updated-desc mode.');
|
||||
}
|
||||
|
||||
$this->info(sprintf(
|
||||
'Starting vector index: start_id=%d after_id=%d next_id=%d batch=%d limit=%s embedded_only=%s public_only=%s dry_run=%s',
|
||||
'Starting vector index: order=%s start_id=%d after_id=%d after_updated_at=%s next_id=%d batch=%d limit=%s embedded_only=%s public_only=%s dry_run=%s',
|
||||
$order,
|
||||
$startId,
|
||||
$afterId,
|
||||
$afterUpdatedAt?->toIso8601String() ?? 'none',
|
||||
$nextId,
|
||||
$batch,
|
||||
$limit > 0 ? (string) $limit : 'all',
|
||||
@@ -73,10 +92,27 @@ final class IndexArtworkVectorsCommand extends Command
|
||||
|
||||
$query = Artwork::query()
|
||||
->with(['categories' => fn ($categories) => $categories->with('contentType')->orderBy('sort_order')->orderBy('name')])
|
||||
->where('id', '>=', $nextId)
|
||||
->whereNotNull('hash')
|
||||
->orderBy('id')
|
||||
->limit($take);
|
||||
->whereNotNull('hash');
|
||||
|
||||
if ($order === 'updated-desc') {
|
||||
$query->orderByDesc('updated_at')
|
||||
->orderByDesc('id')
|
||||
->limit($take);
|
||||
|
||||
if ($nextUpdatedAt !== null) {
|
||||
$query->where(function ($cursorQuery) use ($nextUpdatedAt, $afterId): void {
|
||||
$cursorQuery->where('updated_at', '<', $nextUpdatedAt)
|
||||
->orWhere(function ($sameTimestampQuery) use ($nextUpdatedAt, $afterId): void {
|
||||
$sameTimestampQuery->where('updated_at', '=', $nextUpdatedAt)
|
||||
->where('id', '<', $afterId);
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$query->where('id', '>=', $nextId)
|
||||
->orderBy('id')
|
||||
->limit($take);
|
||||
}
|
||||
|
||||
if ($embeddedOnly) {
|
||||
$query->whereHas('embeddings');
|
||||
@@ -93,16 +129,25 @@ final class IndexArtworkVectorsCommand extends Command
|
||||
}
|
||||
|
||||
$this->line(sprintf(
|
||||
'Fetched batch: count=%d first_id=%d last_id=%d',
|
||||
'Fetched batch: count=%d first_id=%d last_id=%d first_updated_at=%s last_updated_at=%s',
|
||||
$artworks->count(),
|
||||
(int) $artworks->first()->id,
|
||||
(int) $artworks->last()->id
|
||||
(int) $artworks->last()->id,
|
||||
optional($artworks->first()->updated_at)->toIso8601String() ?? 'null',
|
||||
optional($artworks->last()->updated_at)->toIso8601String() ?? 'null'
|
||||
));
|
||||
|
||||
foreach ($artworks as $artwork) {
|
||||
$processed++;
|
||||
$lastId = (int) $artwork->id;
|
||||
$nextId = $lastId + 1;
|
||||
if ($order === 'updated-desc') {
|
||||
$nextUpdatedAt = $artwork->updated_at !== null
|
||||
? CarbonImmutable::instance($artwork->updated_at)
|
||||
: null;
|
||||
$afterId = $lastId;
|
||||
} else {
|
||||
$nextId = $lastId + 1;
|
||||
}
|
||||
|
||||
try {
|
||||
$payload = $vectors->payloadForArtwork($artwork);
|
||||
@@ -150,11 +195,49 @@ final class IndexArtworkVectorsCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
$this->info("Vector index finished. processed={$processed} indexed={$indexed} skipped={$skipped} failed={$failed} last_id={$lastId} next_id={$nextId}");
|
||||
$this->info(sprintf(
|
||||
'Vector index finished. processed=%d indexed=%d skipped=%d failed=%d last_id=%d next_id=%d next_updated_at=%s',
|
||||
$processed,
|
||||
$indexed,
|
||||
$skipped,
|
||||
$failed,
|
||||
$lastId,
|
||||
$nextId,
|
||||
$nextUpdatedAt?->toIso8601String() ?? 'none'
|
||||
));
|
||||
|
||||
return $failed > 0 ? self::FAILURE : self::SUCCESS;
|
||||
}
|
||||
|
||||
private function normalizeOrder(string $order): string
|
||||
{
|
||||
$normalized = strtolower(trim($order));
|
||||
|
||||
return match ($normalized) {
|
||||
'updated-desc', 'updated', 'latest', 'latest-updated' => 'updated-desc',
|
||||
'id-asc', 'id', 'legacy' => 'id-asc',
|
||||
default => 'updated-desc',
|
||||
};
|
||||
}
|
||||
|
||||
private function resolveAfterUpdatedAt(string $order, string $afterUpdatedAt): ?CarbonImmutable
|
||||
{
|
||||
if ($order !== 'updated-desc') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = trim($afterUpdatedAt);
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return CarbonImmutable::parse($value);
|
||||
} catch (\Throwable) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid --after-updated-at value [%s]. Use an ISO-8601 timestamp.', $afterUpdatedAt));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $payload
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user