From 248a7178989e0d5c33fca3b83f5da77965f2d4cc Mon Sep 17 00:00:00 2001 From: ct Date: Fri, 13 Jun 2025 13:51:16 +0800 Subject: [PATCH] Update --- app/Models/BackgroundMedia.php | 48 ++++ app/Models/MemeMedia.php | 2 +- ..._053140_create_background_medias_table.php | 35 +++ database/seeders/BackgroundMediaSeeder.php | 254 ++++++++++++++++++ 4 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 app/Models/BackgroundMedia.php create mode 100644 database/migrations/2025_06_13_053140_create_background_medias_table.php create mode 100644 database/seeders/BackgroundMediaSeeder.php diff --git a/app/Models/BackgroundMedia.php b/app/Models/BackgroundMedia.php new file mode 100644 index 0000000..08778e5 --- /dev/null +++ b/app/Models/BackgroundMedia.php @@ -0,0 +1,48 @@ + Vector::class, + ]; + + protected $fillable = [ + 'list_type', + 'area', + 'location_name', + 'status', + 'media_uuid', + 'media_url', + 'embedding', + ]; +} diff --git a/app/Models/MemeMedia.php b/app/Models/MemeMedia.php index ec77edb..6e8a723 100644 --- a/app/Models/MemeMedia.php +++ b/app/Models/MemeMedia.php @@ -26,7 +26,7 @@ * @property uuid|null $media_2_uuid * @property string $media_1_mime_type * @property string|null $media_2_mime_type - * @property USER-DEFINED|null $embedding + * @property Vector|null $embedding * @property Carbon|null $created_at * @property Carbon|null $updated_at * @property string|null $deleted_at diff --git a/database/migrations/2025_06_13_053140_create_background_medias_table.php b/database/migrations/2025_06_13_053140_create_background_medias_table.php new file mode 100644 index 0000000..f587644 --- /dev/null +++ b/database/migrations/2025_06_13_053140_create_background_medias_table.php @@ -0,0 +1,35 @@ +id(); + $table->string('list_type'); + $table->enum('area', ['interior', 'exterior']); + $table->string('location_name'); + $table->enum('status', ['pending_media', 'completed'])->default('pending_media'); + $table->uuid('media_uuid')->nullable(); + $table->string('media_url')->nullable(); + $table->vector('embedding', 384)->nullable(); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('background_medias'); + } +}; diff --git a/database/seeders/BackgroundMediaSeeder.php b/database/seeders/BackgroundMediaSeeder.php new file mode 100644 index 0000000..b21b632 --- /dev/null +++ b/database/seeders/BackgroundMediaSeeder.php @@ -0,0 +1,254 @@ +command->info('Starting background media records import...'); + + $total_processed = 0; + $total_skipped = 0; + $total_failed = 0; + + // Process each CSV file + foreach (self::CSV_FILES as $csv_file) { + $this->command->info("\nProcessing {$csv_file}..."); + + $csv_path = database_path("seeders/data/areas/{$csv_file}"); + + if (! file_exists($csv_path)) { + $this->command->warn("CSV file not found: {$csv_path}"); + + continue; + } + + try { + $location_data = $this->readCsv($csv_path); + $list_type = pathinfo($csv_file, PATHINFO_FILENAME); + + $this->command->info('Found '.count($location_data)." locations in {$csv_file}"); + + // Process each location record + foreach ($location_data as $index => $location_record) { + $this->command->info('Processing '.($index + 1).'/'.count($location_data).": {$location_record['location']}"); + + try { + // Check for duplicates + if ($this->recordExists($list_type, $location_record['location'], $location_record['area'])) { + $this->command->warn("Skipping existing: {$location_record['location']} in {$location_record['area']}"); + $total_skipped++; + + continue; + } + + // Create the record + $result = $this->createRecord($list_type, $location_record); + + if ($result) { + $total_processed++; + if ($total_processed % 25 === 0) { + $this->command->info("Processed {$total_processed} background media records..."); + } + } else { + $total_failed++; + } + } catch (\Exception $e) { + $total_failed++; + $this->command->error("Failed to import: {$location_record['location']} - {$e->getMessage()}"); + } + } + } catch (\Exception $e) { + $this->command->error("Failed to process {$csv_file}: {$e->getMessage()}"); + $total_failed++; + } + } + + // Summary + $this->command->info("\nImport Summary:"); + $this->command->info("Successfully imported: {$total_processed} background media records"); + if ($total_skipped > 0) { + $this->command->info("Skipped (existing): {$total_skipped} records"); + } + if ($total_failed > 0) { + $this->command->error("Failed: {$total_failed} records"); + } + $this->command->info("All records created with status 'pending_media' - ready for media generation/upload"); + } + + /** + * Check if record already exists + */ + private function recordExists(string $list_type, string $location_name, string $area): bool + { + try { + return BackgroundMedia::where('list_type', $list_type) + ->where('location_name', $location_name) + ->where('area', $area) + ->exists(); + } catch (\Exception $e) { + $this->command->warn("Could not check duplicate for {$location_name}: {$e->getMessage()}"); + + return false; + } + } + + /** + * Read CSV file and return data + */ + private function readCsv(string $csv_path): array + { + if (! file_exists($csv_path)) { + throw new \RuntimeException("CSV file not found: {$csv_path}"); + } + + $csv_content = file_get_contents($csv_path); + $lines = str_getcsv($csv_content, "\n"); + + // Parse header row + $headers = str_getcsv(array_shift($lines)); + + $location_data = []; + foreach ($lines as $line) { + if (empty(trim($line))) { + continue; + } + + $row = str_getcsv($line); + if (count($row) === count($headers)) { + $location_data[] = array_combine($headers, $row); + } + } + + return $location_data; + } + + /** + * Create a background media record + */ + private function createRecord(string $list_type, array $location_record): bool + { + try { + // Validate area enum value + $area = strtolower($location_record['area']); + if (! in_array($area, ['interior', 'exterior'])) { + $this->command->warn("Invalid area value '{$area}' for {$location_record['location']}, skipping..."); + + return false; + } + + // Generate embedding based on location data + try { + $embedding_text = $this->buildEmbeddingText($list_type, $location_record); + $embedding = CloudflareAI::getVectorEmbeddingBgeSmall($embedding_text); + } catch (\Exception $e) { + $this->command->warn("Failed to generate embedding for {$location_record['location']}: {$e->getMessage()}"); + $embedding = null; // Continue without embedding + } + + // Check if record exists one more time + $existing_media = BackgroundMedia::where('list_type', $list_type) + ->where('location_name', $location_record['location']) + ->where('area', $area) + ->first(); + + if ($existing_media) { + $this->command->warn("Record already exists for {$location_record['location']}, skipping..."); + + return false; + } + + // Create BackgroundMedia record with pending_media status + BackgroundMedia::create([ + 'list_type' => $list_type, + 'area' => $area, + 'location_name' => $location_record['location'], + 'status' => 'pending_media', // Default status - ready for media generation + 'media_uuid' => null, // Will be set when media is generated/uploaded + 'media_url' => null, // Will be set when media is generated/uploaded + 'embedding' => $embedding, + ]); + + $this->command->info("Created: {$location_record['location']} ({$list_type}, {$area})"); + + return true; + } catch (\Exception $e) { + $this->command->error("Error importing {$location_record['location']}: {$e->getMessage()}"); + throw $e; + } + } + + /** + * Build text for embedding + */ + private function buildEmbeddingText(string $list_type, array $location_record): string + { + $text_parts = [ + $list_type, + $location_record['location'], + $location_record['area'], + $location_record['type'] ?? '', + ]; + + // Add geographic information if available + if (isset($location_record['city'])) { + $text_parts[] = $location_record['city']; + } + if (isset($location_record['state'])) { + $text_parts[] = $location_record['state']; + } + if (isset($location_record['country'])) { + $text_parts[] = $location_record['country']; + } + if (isset($location_record['continent'])) { + $text_parts[] = $location_record['continent']; + } + + // Clean and join + $text_parts = array_filter($text_parts, function ($part) { + return ! empty(trim($part)); + }); + + return implode(' ', $text_parts); + } +}