Update
This commit is contained in:
60
app/Helpers/FirstParty/AI/RunwareAI.php
Normal file
60
app/Helpers/FirstParty/AI/RunwareAI.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers\FirstParty\AI;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
class RunwareAI
|
||||||
|
{
|
||||||
|
public static function generateSchnellImage($uuid, $prompt, $width = 1024, $height = 1024)
|
||||||
|
{
|
||||||
|
$api_key = config('services.runware.api_key');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = Http::timeout(60)
|
||||||
|
->withHeaders([
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'Authorization' => 'Bearer ' . $api_key,
|
||||||
|
])
|
||||||
|
->post('https://api.runware.ai/v1', [
|
||||||
|
[
|
||||||
|
"taskUUID" => $uuid,
|
||||||
|
"taskType" => "imageInference",
|
||||||
|
"width" => $width,
|
||||||
|
"height" => $height,
|
||||||
|
"numberResults" => 1,
|
||||||
|
"outputFormat" => "WEBP",
|
||||||
|
"outputType" => ["URL"],
|
||||||
|
"includeCost" => true,
|
||||||
|
"inputImages" => [],
|
||||||
|
"positivePrompt" => $prompt,
|
||||||
|
"model" => "runware:100@1"
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Check if the request was successful
|
||||||
|
if ($response->successful()) {
|
||||||
|
$data = $response->json();
|
||||||
|
|
||||||
|
// Extract the image URL from the response
|
||||||
|
if (isset($data['data']) && count($data['data']) > 0) {
|
||||||
|
$imageData = collect($data['data'])
|
||||||
|
->where('taskType', 'imageInference')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($imageData && isset($imageData['imageURL'])) {
|
||||||
|
return $imageData['imageURL'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \Exception('Image URL not found in response');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \Exception('API request failed: ' . $response->status() . ' - ' . $response->body());
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
// Log the error or handle as needed
|
||||||
|
\Log::error('RunwareAI API Error: ' . $e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ public static function populateDurations()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function getDurationUsingFfmpeg($meme_media)
|
public static function getDurationUsingFfmpeg($meme_media)
|
||||||
{
|
{
|
||||||
$duration_milliseconds = FFMpeg::openUrl($meme_media->webm_url)->getDurationInMiliseconds();
|
$duration_milliseconds = FFMpeg::openUrl($meme_media->webm_url)->getDurationInMiliseconds();
|
||||||
$duration_seconds = ($duration_milliseconds / 1000);
|
$duration_seconds = ($duration_milliseconds / 1000);
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Helpers\FirstParty\AI\OpenAI;
|
use App\Helpers\FirstParty\AI\OpenAI;
|
||||||
|
use App\Helpers\FirstParty\AI\RunwareAI;
|
||||||
|
use Str;
|
||||||
|
|
||||||
class TestController extends Controller
|
class TestController extends Controller
|
||||||
{
|
{
|
||||||
@@ -24,4 +26,12 @@ public function writeMeme()
|
|||||||
|
|
||||||
dd($meme_output);
|
dd($meme_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function generateSchnellImage()
|
||||||
|
{
|
||||||
|
$uuid = Str::uuid();
|
||||||
|
$image_url = RunwareAI::generateSchnellImage($uuid, 'office desk cluttered with papers and a computer', 1024, 1024);
|
||||||
|
|
||||||
|
dd($image_url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
app/Models/Category.php
Normal file
83
app/Models/Category.php
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Reliese Model.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Kalnoy\Nestedset\NodeTrait;
|
||||||
|
use Pgvector\Laravel\HasNeighbors;
|
||||||
|
use Pgvector\Laravel\Vector;
|
||||||
|
use Spatie\Tags\HasTags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Category
|
||||||
|
*
|
||||||
|
* @property int $id
|
||||||
|
* @property string $name
|
||||||
|
* @property string $slug
|
||||||
|
* @property string $description
|
||||||
|
* @property int $_lft
|
||||||
|
* @property int $_rgt
|
||||||
|
* @property int|null $parent_id
|
||||||
|
* @property string|null $subcategories
|
||||||
|
* @property string|null $meme_angles
|
||||||
|
* @property string|null $sample_captions
|
||||||
|
* @property string|null $keywords
|
||||||
|
* @property string $payload
|
||||||
|
* @property Carbon|null $created_at
|
||||||
|
* @property Carbon|null $updated_at
|
||||||
|
*
|
||||||
|
* @package App\Models
|
||||||
|
*/
|
||||||
|
class Category extends Model
|
||||||
|
{
|
||||||
|
use NodeTrait, HasTags, HasNeighbors;
|
||||||
|
|
||||||
|
protected $table = 'categories';
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'embedding' => Vector::class,
|
||||||
|
'_lft' => 'int',
|
||||||
|
'_rgt' => 'int',
|
||||||
|
'parent_id' => 'int',
|
||||||
|
'subcategories' => 'object',
|
||||||
|
'meme_angles' => 'array',
|
||||||
|
'sample_captions' => 'array',
|
||||||
|
'keywords' => 'array',
|
||||||
|
'payload' => 'object',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'name',
|
||||||
|
'slug',
|
||||||
|
'description',
|
||||||
|
'_lft',
|
||||||
|
'_rgt',
|
||||||
|
'parent_id',
|
||||||
|
'subcategories',
|
||||||
|
'meme_angles',
|
||||||
|
'sample_captions',
|
||||||
|
'keywords',
|
||||||
|
'payload'
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scope to get only main categories
|
||||||
|
*/
|
||||||
|
public function scopeMainCategories($query)
|
||||||
|
{
|
||||||
|
return $query->whereNull('parent_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scope to get only subcategories
|
||||||
|
*/
|
||||||
|
public function scopeSubcategories($query)
|
||||||
|
{
|
||||||
|
return $query->whereNotNull('parent_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Pgvector\Laravel\HasNeighbors;
|
use Pgvector\Laravel\HasNeighbors;
|
||||||
use Pgvector\Laravel\Vector;
|
use Pgvector\Laravel\Vector;
|
||||||
|
use Spatie\Tags\HasTags;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class MemeMedia
|
* Class MemeMedia
|
||||||
@@ -33,13 +34,15 @@
|
|||||||
*/
|
*/
|
||||||
class MemeMedia extends Model
|
class MemeMedia extends Model
|
||||||
{
|
{
|
||||||
use HasNeighbors, SoftDeletes;
|
use HasNeighbors, SoftDeletes, HasTags;
|
||||||
|
|
||||||
protected $table = 'meme_medias';
|
protected $table = 'meme_medias';
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'embedding' => Vector::class,
|
'embedding' => Vector::class,
|
||||||
'duration' => 'double',
|
'duration' => 'double',
|
||||||
|
'keywords' => 'array',
|
||||||
|
'is_enabled' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
@@ -85,7 +88,7 @@ class MemeMedia extends Model
|
|||||||
protected function ids(): Attribute
|
protected function ids(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: fn ($value, $attributes) => hashids_encode($attributes['id']),
|
get: fn($value, $attributes) => hashids_encode($attributes['id']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
"artesaos/seotools": "^1.3",
|
"artesaos/seotools": "^1.3",
|
||||||
"inertiajs/inertia-laravel": "^2.0",
|
"inertiajs/inertia-laravel": "^2.0",
|
||||||
|
"kalnoy/nestedset": "^6.0",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/horizon": "^5.31",
|
"laravel/horizon": "^5.31",
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.0",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"pbmedia/laravel-ffmpeg": "^8.7",
|
"pbmedia/laravel-ffmpeg": "^8.7",
|
||||||
"pgvector/pgvector": "^0.2.2",
|
"pgvector/pgvector": "^0.2.2",
|
||||||
"spatie/laravel-responsecache": "^7.7",
|
"spatie/laravel-responsecache": "^7.7",
|
||||||
|
"spatie/laravel-tags": "^4.10",
|
||||||
"tightenco/ziggy": "^2.4",
|
"tightenco/ziggy": "^2.4",
|
||||||
"vinkla/hashids": "^13.0"
|
"vinkla/hashids": "^13.0"
|
||||||
},
|
},
|
||||||
|
|||||||
292
composer.lock
generated
292
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "fe17bc6263907633c97c7d1670d002ba",
|
"content-hash": "79e2a426d2f3fc1237920417f93984c5",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "artesaos/seotools",
|
"name": "artesaos/seotools",
|
||||||
@@ -1530,6 +1530,69 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-04-10T15:08:36+00:00"
|
"time": "2025-04-10T15:08:36+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "kalnoy/nestedset",
|
||||||
|
"version": "v6.0.6",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/lazychaser/laravel-nestedset.git",
|
||||||
|
"reference": "3cfc56a9759fb592bc903056166bfc0867f9e679"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/lazychaser/laravel-nestedset/zipball/3cfc56a9759fb592bc903056166bfc0867f9e679",
|
||||||
|
"reference": "3cfc56a9759fb592bc903056166bfc0867f9e679",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/database": "^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
|
||||||
|
"illuminate/events": "^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
|
||||||
|
"illuminate/support": "^7.0|^8.0|^9.0|^10.0|^11.0|^12.0",
|
||||||
|
"php": "^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "7.*|8.*|9.*|^10.5"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Kalnoy\\Nestedset\\NestedSetServiceProvider"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "v5.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Kalnoy\\Nestedset\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Alexander Kalnoy",
|
||||||
|
"email": "lazychaser@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Nested Set Model for Laravel 5.7 and up",
|
||||||
|
"keywords": [
|
||||||
|
"database",
|
||||||
|
"hierarchy",
|
||||||
|
"laravel",
|
||||||
|
"nested sets",
|
||||||
|
"nsm"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/lazychaser/laravel-nestedset/issues",
|
||||||
|
"source": "https://github.com/lazychaser/laravel-nestedset/tree/v6.0.6"
|
||||||
|
},
|
||||||
|
"time": "2025-04-22T19:38:02+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/framework",
|
"name": "laravel/framework",
|
||||||
"version": "v12.9.2",
|
"version": "v12.9.2",
|
||||||
@@ -4301,6 +4364,80 @@
|
|||||||
],
|
],
|
||||||
"time": "2024-04-27T21:32:50+00:00"
|
"time": "2024-04-27T21:32:50+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spatie/eloquent-sortable",
|
||||||
|
"version": "4.5.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/spatie/eloquent-sortable.git",
|
||||||
|
"reference": "76c8fbc79e1d5eec85e7145e46c7f0a65e1f4cda"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/spatie/eloquent-sortable/zipball/76c8fbc79e1d5eec85e7145e46c7f0a65e1f4cda",
|
||||||
|
"reference": "76c8fbc79e1d5eec85e7145e46c7f0a65e1f4cda",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/database": "^9.31|^10.0|^11.0|^12.0",
|
||||||
|
"illuminate/support": "^9.31|^10.0|^11.0|^12.0",
|
||||||
|
"nesbot/carbon": "^2.63|^3.0",
|
||||||
|
"php": "^8.1",
|
||||||
|
"spatie/laravel-package-tools": "^1.9"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"orchestra/testbench": "^7.0|^8.0|^9.0|^10.0",
|
||||||
|
"phpunit/phpunit": "^9.5|^10.0|^11.5.3"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Spatie\\EloquentSortable\\EloquentSortableServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Spatie\\EloquentSortable\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Freek Van der Herten",
|
||||||
|
"email": "freek@spatie.be"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Sortable behaviour for eloquent models",
|
||||||
|
"homepage": "https://github.com/spatie/eloquent-sortable",
|
||||||
|
"keywords": [
|
||||||
|
"behaviour",
|
||||||
|
"eloquent",
|
||||||
|
"laravel",
|
||||||
|
"model",
|
||||||
|
"sort",
|
||||||
|
"sortable"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/spatie/eloquent-sortable/issues",
|
||||||
|
"source": "https://github.com/spatie/eloquent-sortable/tree/4.5.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://spatie.be/open-source/support-us",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/spatie",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-06-03T12:41:10+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spatie/laravel-package-tools",
|
"name": "spatie/laravel-package-tools",
|
||||||
"version": "1.92.4",
|
"version": "1.92.4",
|
||||||
@@ -4445,6 +4582,159 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-05-20T08:39:19+00:00"
|
"time": "2025-05-20T08:39:19+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spatie/laravel-tags",
|
||||||
|
"version": "4.10.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/spatie/laravel-tags.git",
|
||||||
|
"reference": "9fc59a9328e892bbb5b01c948b0d703e22d543ec"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/spatie/laravel-tags/zipball/9fc59a9328e892bbb5b01c948b0d703e22d543ec",
|
||||||
|
"reference": "9fc59a9328e892bbb5b01c948b0d703e22d543ec",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"laravel/framework": "^10.0|^11.0|^12.0",
|
||||||
|
"nesbot/carbon": "^2.63|^3.0",
|
||||||
|
"php": "^8.1",
|
||||||
|
"spatie/eloquent-sortable": "^4.0",
|
||||||
|
"spatie/laravel-package-tools": "^1.4",
|
||||||
|
"spatie/laravel-translatable": "^6.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"orchestra/testbench": "^8.0|^9.0|^10.0",
|
||||||
|
"pestphp/pest": "^1.22|^2.0",
|
||||||
|
"phpunit/phpunit": "^9.5.2"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Spatie\\Tags\\TagsServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Spatie\\Tags\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Freek Van der Herten",
|
||||||
|
"email": "freek@spatie.be",
|
||||||
|
"homepage": "https://spatie.be",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Add tags and taggable behaviour to your Laravel app",
|
||||||
|
"homepage": "https://github.com/spatie/laravel-tags",
|
||||||
|
"keywords": [
|
||||||
|
"laravel-tags",
|
||||||
|
"spatie"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/spatie/laravel-tags/issues",
|
||||||
|
"source": "https://github.com/spatie/laravel-tags/tree/4.10.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/spatie",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-03-08T07:49:06+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "spatie/laravel-translatable",
|
||||||
|
"version": "6.11.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/spatie/laravel-translatable.git",
|
||||||
|
"reference": "032d85b28de315310dab2048b857016f1194f68b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/spatie/laravel-translatable/zipball/032d85b28de315310dab2048b857016f1194f68b",
|
||||||
|
"reference": "032d85b28de315310dab2048b857016f1194f68b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/database": "^10.0|^11.0|^12.0",
|
||||||
|
"illuminate/support": "^10.0|^11.0|^12.0",
|
||||||
|
"php": "^8.0",
|
||||||
|
"spatie/laravel-package-tools": "^1.11"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.64",
|
||||||
|
"mockery/mockery": "^1.4",
|
||||||
|
"orchestra/testbench": "^7.0|^8.0|^9.0|^10.0",
|
||||||
|
"pestphp/pest": "^1.20|^2.0|^3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"aliases": {
|
||||||
|
"Translatable": "Spatie\\Translatable\\Facades\\Translatable"
|
||||||
|
},
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Spatie\\Translatable\\TranslatableServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Spatie\\Translatable\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Freek Van der Herten",
|
||||||
|
"email": "freek@spatie.be",
|
||||||
|
"homepage": "https://spatie.be",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sebastian De Deyne",
|
||||||
|
"email": "sebastian@spatie.be",
|
||||||
|
"homepage": "https://spatie.be",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A trait to make an Eloquent model hold translations",
|
||||||
|
"homepage": "https://github.com/spatie/laravel-translatable",
|
||||||
|
"keywords": [
|
||||||
|
"eloquent",
|
||||||
|
"i8n",
|
||||||
|
"laravel-translatable",
|
||||||
|
"model",
|
||||||
|
"multilingual",
|
||||||
|
"spatie",
|
||||||
|
"translate"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/spatie/laravel-translatable/issues",
|
||||||
|
"source": "https://github.com/spatie/laravel-translatable/tree/6.11.4"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/spatie",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-02-20T15:51:22+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spatie/temporary-directory",
|
"name": "spatie/temporary-directory",
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
|
|||||||
@@ -39,4 +39,8 @@
|
|||||||
'api_token' => env('REPLICATE_API_TOKEN'),
|
'api_token' => env('REPLICATE_API_TOKEN'),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'runware' => [
|
||||||
|
'api_key' => env('RUNWARE_API_KEY'),
|
||||||
|
]
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
28
config/tags.php
Normal file
28
config/tags.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The given function generates a URL friendly "slug" from the tag name property before saving it.
|
||||||
|
* Defaults to Str::slug (https://laravel.com/docs/master/helpers#method-str-slug)
|
||||||
|
*/
|
||||||
|
'slugger' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The fully qualified class name of the tag model.
|
||||||
|
*/
|
||||||
|
'tag_model' => Spatie\Tags\Tag::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The name of the table associated with the taggable morph relation.
|
||||||
|
*/
|
||||||
|
'taggable' => [
|
||||||
|
'table_name' => 'taggables',
|
||||||
|
'morph_name' => 'taggable',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The fully qualified class name of the pivot model.
|
||||||
|
*/
|
||||||
|
'class_name' => Illuminate\Database\Eloquent\Relations\MorphPivot::class,
|
||||||
|
]
|
||||||
|
];
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::table('meme_medias', function (Blueprint $table) {
|
|
||||||
$table->double('duration')->nullable();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('meme_medias', function (Blueprint $table) {
|
|
||||||
$table->dropColumn('duration');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::table('meme_medias', function (Blueprint $table) {
|
|
||||||
$table->integer('media_width')->default(720);
|
|
||||||
$table->integer('media_height')->default(1280);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('meme_medias', function (Blueprint $table) {
|
|
||||||
$table->dropColumn('media_width');
|
|
||||||
$table->dropColumn('media_height');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::table('background_medias', function (Blueprint $table) {
|
|
||||||
$table->integer('media_width')->default(720);
|
|
||||||
$table->integer('media_height')->default(720);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('background_medias', function (Blueprint $table) {
|
|
||||||
$table->dropColumn('media_width');
|
|
||||||
$table->dropColumn('media_height');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
36
database/migrations/2025_06_19_002713_create_tag_tables.php
Normal file
36
database/migrations/2025_06_19_002713_create_tag_tables.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('tags', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->json('name');
|
||||||
|
$table->json('slug');
|
||||||
|
$table->string('type')->nullable();
|
||||||
|
$table->integer('order_column')->nullable();
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('taggables', function (Blueprint $table) {
|
||||||
|
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
|
||||||
|
|
||||||
|
$table->morphs('taggable');
|
||||||
|
|
||||||
|
$table->unique(['tag_id', 'taggable_id', 'taggable_type']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('taggables');
|
||||||
|
Schema::dropIfExists('tags');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('categories', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->boolean('is_enabled')->default(false);
|
||||||
|
$table->string('name');
|
||||||
|
$table->text('description');
|
||||||
|
$table->vector('embedding', 384);
|
||||||
|
|
||||||
|
// Nested set model columns (Kalnoy NestedSet)
|
||||||
|
$table->nestedSet();
|
||||||
|
|
||||||
|
// Nullable columns for category/subcategory-specific fields
|
||||||
|
$table->json('subcategories')->nullable();
|
||||||
|
$table->json('meme_angles')->nullable();
|
||||||
|
$table->json('sample_captions')->nullable();
|
||||||
|
$table->json('keywords')->nullable();
|
||||||
|
|
||||||
|
// Store original JSON payload
|
||||||
|
$table->json('payload');
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('categories');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -13,12 +13,13 @@ public function up(): void
|
|||||||
{
|
{
|
||||||
Schema::create('meme_medias', function (Blueprint $table) {
|
Schema::create('meme_medias', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
|
$table->boolean('is_enabled')->default(false);
|
||||||
$table->string('original_id');
|
$table->string('original_id');
|
||||||
$table->enum('type', ['video', 'image']);
|
$table->enum('type', ['video', 'image']);
|
||||||
$table->enum('sub_type', ['background', 'overlay']);
|
$table->enum('sub_type', ['background', 'overlay']);
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->text('description');
|
$table->text('description');
|
||||||
$table->string('keywords');
|
$table->json('keywords');
|
||||||
$table->uuid('mov_uuid');
|
$table->uuid('mov_uuid');
|
||||||
$table->uuid('webm_uuid');
|
$table->uuid('webm_uuid');
|
||||||
$table->uuid('gif_uuid');
|
$table->uuid('gif_uuid');
|
||||||
@@ -28,6 +29,9 @@ public function up(): void
|
|||||||
$table->string('gif_url');
|
$table->string('gif_url');
|
||||||
$table->string('webp_url');
|
$table->string('webp_url');
|
||||||
$table->vector('embedding', 384)->nullable();
|
$table->vector('embedding', 384)->nullable();
|
||||||
|
$table->double('duration')->nullable();
|
||||||
|
$table->integer('media_width')->default(720);
|
||||||
|
$table->integer('media_height')->default(1280);
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
$table->softDeletes();
|
$table->softDeletes();
|
||||||
});
|
});
|
||||||
@@ -13,19 +13,20 @@ public function up(): void
|
|||||||
{
|
{
|
||||||
Schema::create('background_medias', function (Blueprint $table) {
|
Schema::create('background_medias', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('list_type');
|
|
||||||
$table->enum('area', ['interior', 'exterior']);
|
|
||||||
$table->string('location_name');
|
|
||||||
$table->text('prompt')->nullable();
|
$table->text('prompt')->nullable();
|
||||||
$table->enum('status', ['pending_media', 'completed'])->default('pending_media');
|
$table->enum('status', ['pending_media', 'completed'])->default('pending_media');
|
||||||
$table->uuid('media_uuid')->nullable();
|
$table->uuid('media_uuid')->nullable();
|
||||||
$table->string('media_url')->nullable();
|
$table->string('media_url')->nullable();
|
||||||
$table->vector('embedding', 384)->nullable();
|
$table->vector('embedding', 384)->nullable();
|
||||||
|
$table->integer('media_width');
|
||||||
|
$table->integer('media_height');
|
||||||
|
$table->enum('aspect_ratio', ['9:16', '16:9', '1:1']);
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
$table->softDeletes();
|
$table->softDeletes();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverse the migrations.
|
* Reverse the migrations.
|
||||||
*/
|
*/
|
||||||
230
database/seeders/CategorySeeder.php
Normal file
230
database/seeders/CategorySeeder.php
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Helpers\FirstParty\AI\CloudflareAI;
|
||||||
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use App\Models\Category;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class CategorySeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
// Path to the JSON files
|
||||||
|
$jsonPath = database_path('seeders/data/json/category');
|
||||||
|
|
||||||
|
// Check if directory exists
|
||||||
|
if (!File::exists($jsonPath)) {
|
||||||
|
$this->command->error("JSON directory not found: {$jsonPath}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all JSON files except the schema file
|
||||||
|
$jsonFiles = File::glob($jsonPath . '/*.json');
|
||||||
|
$jsonFiles = array_filter($jsonFiles, function ($file) {
|
||||||
|
return !str_contains(basename($file), 'schema');
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->command->info('Starting to seed categories from JSON files...');
|
||||||
|
$this->command->info('Found ' . count($jsonFiles) . ' JSON files to process.');
|
||||||
|
|
||||||
|
foreach ($jsonFiles as $jsonFile) {
|
||||||
|
$this->processJsonFile($jsonFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->command->info('Category seeding completed successfully!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a single JSON file
|
||||||
|
*/
|
||||||
|
private function processJsonFile(string $filePath): void
|
||||||
|
{
|
||||||
|
$fileName = basename($filePath);
|
||||||
|
$this->command->info("Processing: {$fileName}");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Read and decode JSON content
|
||||||
|
$jsonContent = File::get($filePath);
|
||||||
|
$data = json_decode($jsonContent, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
$this->command->error("Invalid JSON in file: {$fileName} - " . json_last_error_msg());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate JSON structure
|
||||||
|
if (!isset($data['category'])) {
|
||||||
|
$this->command->error("Missing 'category' key in file: {$fileName}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$categoryData = $data['category'];
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!isset($categoryData['name']) || !isset($categoryData['description'])) {
|
||||||
|
$this->command->error("Missing required fields (name/description) in file: {$fileName}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create main category
|
||||||
|
$mainCategory = $this->createMainCategory($categoryData, $data);
|
||||||
|
|
||||||
|
if (!$mainCategory) {
|
||||||
|
$this->command->error("Failed to create main category for file: {$fileName}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create subcategories
|
||||||
|
if (isset($categoryData['subcategories']) && is_array($categoryData['subcategories'])) {
|
||||||
|
foreach ($categoryData['subcategories'] as $index => $subcategoryData) {
|
||||||
|
if (!$this->createSubcategory($subcategoryData, $mainCategory, $data, $index)) {
|
||||||
|
$this->command->warn("Failed to create subcategory at index {$index} for file: {$fileName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->command->info("✓ Successfully processed: {$fileName}");
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->command->error("Error processing {$fileName}: " . $e->getMessage());
|
||||||
|
Log::error("CategorySeeder error for {$fileName}", [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a main category
|
||||||
|
*/
|
||||||
|
private function createMainCategory(array $categoryData, array $originalData): ?Category
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Check if category already exists
|
||||||
|
$existingCategory = Category::where('name', $categoryData['name'])
|
||||||
|
->whereNull('parent_id')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existingCategory) {
|
||||||
|
$this->command->warn("Main category '{$categoryData['name']}' already exists. Skipping...");
|
||||||
|
return $existingCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the main category
|
||||||
|
$category = Category::create([
|
||||||
|
'is_enabled' => true,
|
||||||
|
'name' => $categoryData['name'],
|
||||||
|
'description' => $categoryData['description'],
|
||||||
|
'subcategories' => $categoryData['subcategories'] ?? null,
|
||||||
|
'keywords' => $categoryData['keywords'] ?? null,
|
||||||
|
'meme_angles' => null, // Main categories don't have meme_angles
|
||||||
|
'sample_captions' => null, // Main categories don't have sample_captions
|
||||||
|
'payload' => $originalData,
|
||||||
|
'embedding' => CloudflareAI::getVectorEmbeddingBgeSmall($categoryData['name'] . " " . $categoryData['description']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Add keywords as tags
|
||||||
|
if (isset($categoryData['keywords']) && is_array($categoryData['keywords'])) {
|
||||||
|
$this->attachKeywordsAsTags($category, $categoryData['keywords']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->command->line(" ✓ Created main category: {$category->name}");
|
||||||
|
|
||||||
|
return $category;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->command->error("Error creating main category: " . $e->getMessage());
|
||||||
|
Log::error("Error creating main category", [
|
||||||
|
'category_data' => $categoryData,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a subcategory
|
||||||
|
*/
|
||||||
|
private function createSubcategory(array $subcategoryData, Category $parentCategory, array $originalData, int $index): ?Category
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Validate required subcategory fields
|
||||||
|
if (!isset($subcategoryData['name']) || !isset($subcategoryData['description'])) {
|
||||||
|
$this->command->warn("Subcategory at index {$index} missing required fields (name/description). Skipping...");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if subcategory already exists
|
||||||
|
$existingSubcategory = Category::where('name', $subcategoryData['name'])
|
||||||
|
->where('parent_id', $parentCategory->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existingSubcategory) {
|
||||||
|
$this->command->warn(" Subcategory '{$subcategoryData['name']}' already exists. Skipping...");
|
||||||
|
return $existingSubcategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create subcategory payload
|
||||||
|
$subcategoryPayload = [
|
||||||
|
'subcategory' => $subcategoryData,
|
||||||
|
'parent_category' => [
|
||||||
|
'name' => $parentCategory->name,
|
||||||
|
'description' => $parentCategory->description
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
// Create the subcategory using the correct nested set method
|
||||||
|
$subcategory = Category::create([
|
||||||
|
'is_enabled' => false,
|
||||||
|
'name' => $subcategoryData['name'],
|
||||||
|
'description' => $subcategoryData['description'],
|
||||||
|
'meme_angles' => $subcategoryData['meme_angles'] ?? null,
|
||||||
|
'sample_captions' => $subcategoryData['sample_captions'] ?? null,
|
||||||
|
'keywords' => $subcategoryData['keywords'] ?? null,
|
||||||
|
'subcategories' => null, // Subcategories don't have subcategories
|
||||||
|
'payload' => $subcategoryPayload,
|
||||||
|
'parent_id' => $parentCategory->id, // Set parent_id directly
|
||||||
|
'embedding' => CloudflareAI::getVectorEmbeddingBgeSmall($subcategoryData['name'] . " " . $subcategoryData['description']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Add keywords as tags
|
||||||
|
if (isset($subcategoryData['keywords']) && is_array($subcategoryData['keywords'])) {
|
||||||
|
$this->attachKeywordsAsTags($subcategory, $subcategoryData['keywords']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->command->line(" ✓ Created subcategory: {$subcategory->name}");
|
||||||
|
|
||||||
|
return $subcategory;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->command->error("Error creating subcategory at index {$index}: " . $e->getMessage());
|
||||||
|
Log::error("Error creating subcategory", [
|
||||||
|
'subcategory_data' => $subcategoryData,
|
||||||
|
'parent_id' => $parentCategory->id,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach keywords as tags to a category
|
||||||
|
*/
|
||||||
|
private function attachKeywordsAsTags(Category $category, array $keywords): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$category->attachTags($keywords, 'category');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->command->warn("Failed to attach tags to category '{$category->name}': " . $e->getMessage());
|
||||||
|
Log::warning("Failed to attach tags", [
|
||||||
|
'category_id' => $category->id,
|
||||||
|
'keywords' => $keywords,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,10 +20,12 @@
|
|||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Helpers\FirstParty\AI\CloudflareAI;
|
use App\Helpers\FirstParty\AI\CloudflareAI;
|
||||||
|
use App\Helpers\FirstParty\Maintenance\MemeMediaMaintenance;
|
||||||
use App\Helpers\FirstParty\MediaEngine\MediaEngine;
|
use App\Helpers\FirstParty\MediaEngine\MediaEngine;
|
||||||
use App\Models\MediaCollection;
|
use App\Models\MediaCollection;
|
||||||
use App\Models\MemeMedia;
|
use App\Models\MemeMedia;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
use Log;
|
||||||
|
|
||||||
class MemeMediaSeeder extends Seeder
|
class MemeMediaSeeder extends Seeder
|
||||||
{
|
{
|
||||||
@@ -50,7 +52,7 @@ public function run(): void
|
|||||||
$csv_path = database_path('seeders/data/webm_metadata.csv');
|
$csv_path = database_path('seeders/data/webm_metadata.csv');
|
||||||
$meme_data = $this->parseCsvFile($csv_path);
|
$meme_data = $this->parseCsvFile($csv_path);
|
||||||
|
|
||||||
$this->command->info('📊 Found '.count($meme_data).' memes to import');
|
$this->command->info('📊 Found ' . count($meme_data) . ' memes to import');
|
||||||
|
|
||||||
// Process records individually for PostgreSQL compatibility
|
// Process records individually for PostgreSQL compatibility
|
||||||
$total_processed = 0;
|
$total_processed = 0;
|
||||||
@@ -58,7 +60,11 @@ public function run(): void
|
|||||||
$total_failed = 0;
|
$total_failed = 0;
|
||||||
|
|
||||||
foreach ($meme_data as $index => $meme_record) {
|
foreach ($meme_data as $index => $meme_record) {
|
||||||
$this->command->info('Processing '.($index + 1).'/'.count($meme_data).': '.$meme_record['filename']);
|
$this->command->info('Processing ' . ($index + 1) . '/' . count($meme_data) . ': ' . $meme_record['filename']);
|
||||||
|
|
||||||
|
|
||||||
|
$meme_record['keywords'] = $this->stringToCleanArray($meme_record['keywords']);
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check for duplicates OUTSIDE of transaction
|
// Check for duplicates OUTSIDE of transaction
|
||||||
@@ -146,6 +152,18 @@ private function parseCsvFile(string $csv_path): array
|
|||||||
return $meme_data;
|
return $meme_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function stringToCleanArray($string)
|
||||||
|
{
|
||||||
|
// Split by comma, clean each element, and filter empty ones
|
||||||
|
return array_filter(array_map(function ($item) {
|
||||||
|
$item = trim($item); // Remove whitespace
|
||||||
|
$item = preg_replace('/[^\w\s]/', '', $item); // Remove punctuation
|
||||||
|
return trim(preg_replace('/\s+/', ' ', $item)); // Clean extra spaces
|
||||||
|
}, explode(',', $string)), function ($value) {
|
||||||
|
return $value !== '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import a single meme with all its formats
|
* Import a single meme with all its formats
|
||||||
*/
|
*/
|
||||||
@@ -174,13 +192,13 @@ private function importSingleMeme(array $meme_record): bool
|
|||||||
'save_url', // Mode: just save URL reference
|
'save_url', // Mode: just save URL reference
|
||||||
null, // Auto-generate filename
|
null, // Auto-generate filename
|
||||||
'r2', // Disk (not used for URL mode)
|
'r2', // Disk (not used for URL mode)
|
||||||
trim($meme_record['name'])." ({$format})", // Name with format
|
trim($meme_record['name']) . " ({$format})", // Name with format
|
||||||
null, // No specific user
|
null, // No specific user
|
||||||
$config['mime'] // MIME type
|
$config['mime'] // MIME type
|
||||||
);
|
);
|
||||||
|
|
||||||
$media_uuids[$format.'_uuid'] = $media->uuid;
|
$media_uuids[$format . '_uuid'] = $media->uuid;
|
||||||
$media_urls[$format.'_url'] = $url;
|
$media_urls[$format . '_url'] = $url;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->command->error("Failed to create {$format} media for {$meme_record['filename']}: {$e->getMessage()}");
|
$this->command->error("Failed to create {$format} media for {$meme_record['filename']}: {$e->getMessage()}");
|
||||||
throw $e;
|
throw $e;
|
||||||
@@ -190,7 +208,7 @@ private function importSingleMeme(array $meme_record): bool
|
|||||||
// Generate embedding
|
// Generate embedding
|
||||||
try {
|
try {
|
||||||
$embedding = CloudflareAI::getVectorEmbeddingBgeSmall(
|
$embedding = CloudflareAI::getVectorEmbeddingBgeSmall(
|
||||||
$meme_record['name'].' '.$meme_record['description'].' '.$meme_record['keywords']
|
$meme_record['name'] . ' ' . $meme_record['description'] . ' ' . implode(' ', $meme_record['keywords'])
|
||||||
);
|
);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->command->warn("Failed to generate embedding for {$meme_record['filename']}: {$e->getMessage()}");
|
$this->command->warn("Failed to generate embedding for {$meme_record['filename']}: {$e->getMessage()}");
|
||||||
@@ -209,7 +227,8 @@ private function importSingleMeme(array $meme_record): bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create MemeMedia record
|
// Create MemeMedia record
|
||||||
MemeMedia::create([
|
$meme_media = MemeMedia::create([
|
||||||
|
'is_enabled' => true,
|
||||||
'original_id' => $meme_record['filename'],
|
'original_id' => $meme_record['filename'],
|
||||||
'type' => $meme_record['type'],
|
'type' => $meme_record['type'],
|
||||||
'sub_type' => $meme_record['sub_type'],
|
'sub_type' => $meme_record['sub_type'],
|
||||||
@@ -228,12 +247,16 @@ private function importSingleMeme(array $meme_record): bool
|
|||||||
'webm_url' => $media_urls['webm_url'],
|
'webm_url' => $media_urls['webm_url'],
|
||||||
'gif_url' => $media_urls['gif_url'],
|
'gif_url' => $media_urls['gif_url'],
|
||||||
'webp_url' => $media_urls['webp_url'],
|
'webp_url' => $media_urls['webp_url'],
|
||||||
|
|
||||||
// Embedding (may be null)
|
|
||||||
'embedding' => $embedding,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->command->info('✅ Imported: '.trim($meme_record['name']));
|
$meme_media->duration = MemeMediaMaintenance::getDurationUsingFfmpeg($meme_media);
|
||||||
|
$meme_media->embedding = $embedding;
|
||||||
|
$meme_media->save();
|
||||||
|
|
||||||
|
// Add keywords as tags
|
||||||
|
$this->attachKeywordsAsTags($meme_media, $meme_record['keywords']);
|
||||||
|
|
||||||
|
$this->command->info('✅ Imported: ' . trim($meme_record['name']));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
@@ -242,12 +265,26 @@ private function importSingleMeme(array $meme_record): bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function attachKeywordsAsTags(MemeMedia $meme_media, array $keywords): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$meme_media->attachTags($keywords, 'meme');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->command->warn("Failed to attach tags to meme media '{$meme_media->name}': " . $e->getMessage());
|
||||||
|
Log::warning("Failed to attach tags", [
|
||||||
|
'category_id' => $meme_media->id,
|
||||||
|
'keywords' => $keywords,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate CDN URL for specific format
|
* Generate CDN URL for specific format
|
||||||
*/
|
*/
|
||||||
private function generateCdnUrl(string $base_filename, string $extension): string
|
private function generateCdnUrl(string $base_filename, string $extension): string
|
||||||
{
|
{
|
||||||
return self::CDN_BASE_URL."/{$extension}/{$base_filename}.{$extension}";
|
return self::CDN_BASE_URL . "/{$extension}/{$base_filename}.{$extension}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,3 +7,5 @@
|
|||||||
Route::get('/populateDuration', [TestController::class, 'populateDuration']);
|
Route::get('/populateDuration', [TestController::class, 'populateDuration']);
|
||||||
|
|
||||||
Route::get('/writeMeme', [TestController::class, 'writeMeme']);
|
Route::get('/writeMeme', [TestController::class, 'writeMeme']);
|
||||||
|
|
||||||
|
Route::get('/generateSchnellImage', [TestController::class, 'generateSchnellImage']);
|
||||||
|
|||||||
Reference in New Issue
Block a user