@extends('layouts.app') @section('title', 'Product AI Import') @section('content') @php $shopifyListings = $shopifyListings ?? []; $availableChannels = $availableChannels ?? []; $deferRepairBar = (bool) ($deferRepairBar ?? false); @endphp
AVI THE JEWELER · Catalog
Build Shopify drafts from photos and notes, or Improve existing live products (media only). Manual walks the wizard; Auto runs the pipeline and creates a Shopify draft only when everything checks out.
No imports yet. Start with New import to upload images and an optional video.
@else @php $pendingImports = $imports->filter(function ($imp) { if (! filled($imp->shopify_product_gid) || $imp->isRepairAttentionSkipped()) { return false; } return $imp->hasPendingShopifySync() || $imp->shopifyPayloadNeedsPrepare(); }); $pendingCount = $pendingImports->count(); $pendingListRows = $pendingImports->map(function ($imp) { $needsPrepare = $imp->shopifyPayloadNeedsPrepare(); $needsSync = $imp->hasPendingShopifySync(); $reason = $imp->improvePendingWorkReason(); if ($reason === null) { if ($needsPrepare && $needsSync) { $reason = 'Prepare media, then push to Shopify.'; } elseif ($needsPrepare) { $reason = 'Prepare pending media (crop / compress).'; } else { $reason = 'Push pending gallery changes to Shopify.'; } } $preview = $imp->listPreviewImage(); return [ 'import_id' => (int) $imp->id, 'title' => (string) ($imp->title ?: ('Import #'.$imp->id)), 'open_url' => route('product-ai-import.show', $imp), 'dismiss_url' => route('product-ai-import.dismiss-repair', $imp), 'image_url' => $preview?->displayUrl() ?: $preview?->originalUrl(), 'sku' => (string) ($imp->sku ?: ''), 'workflow' => $imp->isImproveExisting() ? 'improve' : 'import', 'mode' => $imp->is_auto_mode ? 'auto' : 'manual', 'needs_prepare' => $needsPrepare, 'needs_sync' => $needsSync, 'summary' => $reason, ]; })->values()->all(); $runningAutos = $imports->filter(function ($imp) { return $imp->isImproveExisting() && $imp->is_auto_mode && in_array((string) $imp->auto_status, [ \App\Models\ProductAiImport::AUTO_STATUS_QUEUED, \App\Models\ProductAiImport::AUTO_STATUS_RUNNING, ], true); }); $runningAutoCount = $runningAutos->count(); $repairStats = $repairStats ?? null; $repairTotal = 0; $repairRedo = 0; $repairSync = 0; $repairVideo = 0; $repairVideoMissing = 0; $repairBusy = 0; $repairQueued = 0; $repairCapped = false; $repairMode = 'mixed'; $syncOnly = false; $videoOnly = false; $omniPaused = false; $omniResumeAt = null; $repairVideoDeferred = 0; $repairVideoRunnable = 0; $repairListRedo = []; $repairListSync = []; $repairListVideo = []; if (is_array($repairStats)) { $repairTotal = (int) ($repairStats['total'] ?? 0); $repairRedo = (int) ($repairStats['redo'] ?? 0); $repairSync = (int) ($repairStats['sync'] ?? 0); $repairVideo = (int) ($repairStats['video'] ?? 0); $repairVideoMissing = (int) ($repairStats['video_missing'] ?? $repairVideo); $repairBusy = (int) ($repairStats['busy'] ?? 0); $repairQueued = (int) ($repairStats['queued'] ?? $repairTotal); $repairCapped = (bool) ($repairStats['capped'] ?? false); $repairMode = (string) ($repairStats['mode'] ?? 'mixed'); $syncOnly = $repairMode === 'sync_only'; $videoOnly = $repairMode === 'video_only'; $omniPaused = (bool) ($repairStats['omni_paused'] ?? false); $omniResumeAt = $repairStats['omni_resume_at'] ?? null; $repairVideoDeferred = (int) ($repairStats['video_deferred'] ?? 0); $repairVideoRunnable = max(0, $repairVideo - $repairVideoDeferred); $repairLists = is_array($repairStats['lists'] ?? null) ? $repairStats['lists'] : []; $repairListRedo = is_array($repairLists['redo'] ?? null) ? $repairLists['redo'] : []; $repairListSync = is_array($repairLists['sync'] ?? null) ? $repairLists['sync'] : []; $repairListVideo = is_array($repairLists['video'] ?? null) ? $repairLists['video'] : []; } $batchLite = $activeBatchLite ?? null; $batchLiteActive = (bool) ($batchLite['is_active'] ?? false); // Orange “now working” row only while the batch is live — never after it finishes. $batchCurrentId = $batchLiteActive ? (int) ($batchLite['current_import_id'] ?? 0) : 0; $batchCurrentOnPage = $batchCurrentId > 0 && $imports->contains(fn ($imp) => (int) $imp->id === $batchCurrentId); $isSearching = $indexSearchQ !== ''; @endphp @if(! $isSearching && $runningAutoCount > 0 && ! $batchLiteActive) @endif @if(! $isSearching && $batchLiteActive) @endif @if(! $isSearching && $deferRepairBar && ! $batchLiteActive)| Product | Mode | Workflow | Import | Shopify | Pending | Sales channels | Updated | |
|---|---|---|---|---|---|---|---|---|
|
@if($previewUrl)
@else
No photo
@endif
@if($websiteUrl)
{{ $import->title ?: 'Untitled' }} ↗
@else
{{ $import->title ?: 'Untitled' }}
@endif
#{{ $import->id }} · SKU {{ $import->sku ?: '—' }}
{{ $imgCount }} photo{{ $imgCount === 1 ? '' : 's' }}@if($vidCount > 0) · {{ $vidCount }} video{{ $vidCount === 1 ? '' : 's' }}@endif
@if($websiteUrl)
@endif
|
@if($import->is_auto_mode) Auto @else Manual @endif | @if($import->isImproveExisting()) Improve @else Import @endif |
@php
// “Needs sync” must never wear the green synced badge.
$paiPendingSync = $workBusy ? null : $import->improvePendingWorkReason();
$paiBadge = $workBusy ? ' pai-badge--running' : '';
if ($import->is_auto_mode && ! $workBusy) {
if ($paiPendingSync !== null) {
$paiBadge = ' pai-badge--blocked';
} elseif ($import->auto_status === \App\Models\ProductAiImport::AUTO_STATUS_BLOCKED) {
$paiBadge = ' pai-badge--blocked';
} elseif ($import->auto_status === \App\Models\ProductAiImport::AUTO_STATUS_FAILED) {
$paiBadge = ' pai-badge--failed';
} elseif ($import->auto_status === \App\Models\ProductAiImport::AUTO_STATUS_SYNCED) {
$paiBadge = ' pai-badge--synced';
}
}
@endphp
@if($isBatchCurrent && ! $autoRunning && ! $bgBusy)
In repair queue
@else
{{ $import->statusLabel() }}
@endif
@if($paiPendingSync !== null)
{{ $paiPendingSync }}
@endif
@php
$idxActivity = $import->indexActivityPresentation(
$isBatchCurrent,
is_array($batchLite ?? null) ? ($batchLite['current_step_message'] ?? null) : null
);
$idxBlockers = is_array($import->form_data['auto_blockers'] ?? null) ? $import->form_data['auto_blockers'] : [];
$idxShowBlockers = $import->is_auto_mode
&& $import->auto_status === \App\Models\ProductAiImport::AUTO_STATUS_BLOCKED
&& count($idxBlockers) > 0;
$idxShowActivity = $idxActivity['show_line'] || $idxShowBlockers;
@endphp
@if($workBusy)
{{ $idxActivity['running_message'] }}
@else
@endif
@if($idxShowBlockers)
@foreach(array_slice($idxBlockers, 0, 3) as $b)
{{ $b['title'] ?? 'Issue' }}
@endforeach
@if(count($idxBlockers) > 3)
+{{ count($idxBlockers) - 3 }}
@endif
@endif
@if(! empty($idxActivity['chips']))
@foreach($idxActivity['chips'] as $chip)
{{ $chip['label'] }}
@endforeach
@endif
|
@if(! $linked)
Not linked
@elseif($shopStatus === 'ACTIVE')
Active
@elseif($shopStatus === 'DRAFT')
Draft
@elseif($shopStatus === 'ARCHIVED')
Archived
@elseif($shopStatus !== '')
{{ ucfirst(strtolower($shopStatus)) }}
@else
Linked
@endif
@if($websiteUrl)
Website ↗
@endif
@if($adminUrl)
Shopify admin ↗
@endif
|
@if($needsPrepare)
Prepare pending
@elseif($pendingSync)
Sync pending
@elseif($noOmniVideo)
No Omni video
@else
—
@endif
|
@if($channelsLive && $channels === [])
Loading channels…
@elseif($channels === [])
Channels unavailable
@else
@foreach($channels as $ch)
@php $on = ! empty($ch['published']); @endphp
{{ $ch['name'] ?? 'Channel' }}
@endforeach
@unless($linked)
Default: all marked on create
@endunless
@endif
|
{{ $import->updated_at?->timezone(config('app.deals_display_timezone', 'America/New_York'))->format('M j, g:i A') }} |
Open
@if($import->isImproveExisting() && $linked && ! $import->wasRevertedToOriginals())
@elseif($import->isImproveExisting() && $linked && $import->wasRevertedToOriginals())
Reverted
@endif
@if($import->canDeleteFromApp())
@endif
|
| @if($indexSearchQ !== '') No products match “{{ $indexSearchQ }}”. Clear search @else No Product AI imports yet. @endif | ||||||||
Download Enhanced, Omni video, Lifestyle AI, and how-it-looks. Studio originals & measurement are excluded.
This permanently removes the import from the app — photos, videos, AI files, and database records. Shopify is not touched (this import is not linked).
Auto is still running on this import. Deleting will stop further progress and erase everything already generated.
Are you sure you want to delete this import?
Second confirmation — type DELETE to confirm.