Update
This commit is contained in:
@@ -9,9 +9,18 @@ class OpenAI
|
|||||||
public static function getSingleMemeGenerator($user_prompt)
|
public static function getSingleMemeGenerator($user_prompt)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
$caption_descriptions = [
|
||||||
|
'A humorous, funny one-liner that starts with "When you", describes a specific visual or situational moment, avoids vagueness, and ends with no punctuation',
|
||||||
|
'A POV meme that starts with "POV: ", clearly describes a specific scenario or feeling with high context, and ends with no punctuation',
|
||||||
|
'A humorous, funny one-liner that starts with "I", grounded in a relatable, situational experience with visual context, and ends with no punctuation',
|
||||||
|
'A humorous, funny one-liner that starts with "You", focused on a clear, specific reaction or moment, and ends with no punctuation',
|
||||||
|
'A humorous, funny one-liner with no punctuation that describes a vivid, realistic scenario or reaction in a clearly defined context',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
$response = Http::withHeaders([
|
$response = Http::withHeaders([
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'Authorization' => 'Bearer '.env('OPENAI_API_KEY'),
|
'Authorization' => 'Bearer ' . env('OPENAI_API_KEY'),
|
||||||
])
|
])
|
||||||
->post('https://api.openai.com/v1/responses', [
|
->post('https://api.openai.com/v1/responses', [
|
||||||
'model' => 'gpt-4.1-nano',
|
'model' => 'gpt-4.1-nano',
|
||||||
@@ -45,15 +54,19 @@ public static function getSingleMemeGenerator($user_prompt)
|
|||||||
'properties' => [
|
'properties' => [
|
||||||
'caption' => [
|
'caption' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'description' => 'A humorous, one-liner or POV meme (under 15 words, no punctuation)',
|
'description' => $caption_descriptions[rand(0, count($caption_descriptions) - 1)],
|
||||||
],
|
],
|
||||||
'meme_overlay' => [
|
'meme_keywords' => [
|
||||||
'type' => 'string',
|
'type' => 'array',
|
||||||
'description' => 'A short visual reaction that pairs with the caption (e.g., "guy blinking in disbelief")',
|
'description' => 'Array of 3–5 tags that describe only internal emotional states (e.g., "anxiety", "burnout", "imposter syndrome", "joy", "excitement", "confusion", "awkwardness"). Avoid external concepts, situations, or behaviors like "familylife" or "comparison". Only include pure feelings.',
|
||||||
|
'items' => [
|
||||||
|
'type' => 'string',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'background' => [
|
'background' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'description' => 'The setting or location of the meme (e.g., "Zoom call grid", "office cubicle")',
|
'description' => "Use this exact structure: 'Location: [setting only, e.g., empty office with cluttered desk and monitor]'. Do not mention people, animals, actions, or any living beings. No verbs. No brand names. Only interior spaces or physical locations. Examples: 'Location: quiet office cubicle with headset and papers'.",
|
||||||
|
|
||||||
],
|
],
|
||||||
'keywords' => [
|
'keywords' => [
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
@@ -65,7 +78,7 @@ public static function getSingleMemeGenerator($user_prompt)
|
|||||||
],
|
],
|
||||||
'required' => [
|
'required' => [
|
||||||
'caption',
|
'caption',
|
||||||
'meme_overlay',
|
'meme_keywords',
|
||||||
'background',
|
'background',
|
||||||
'keywords',
|
'keywords',
|
||||||
],
|
],
|
||||||
@@ -90,12 +103,24 @@ public static function getSingleMemeGenerator($user_prompt)
|
|||||||
return $data;
|
return $data;
|
||||||
} else {
|
} else {
|
||||||
// Handle error
|
// Handle error
|
||||||
throw new \Exception('API request failed: '.$response->body());
|
throw new \Exception('API request failed: ' . $response->body());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getOpenAIOutput($data)
|
public static function getOpenAIOutput($data)
|
||||||
{
|
{
|
||||||
return data_get($data, 'output.0.content.0.text', null);
|
//dump($data);
|
||||||
|
|
||||||
|
$output = null;
|
||||||
|
|
||||||
|
$response_type = data_get($data, 'output.0.content.0.type', null);
|
||||||
|
|
||||||
|
if ($response_type === 'output_text') {
|
||||||
|
$output = data_get($data, 'output.0.content.0.text', null);
|
||||||
|
} else if ($response_type === 'refusal') {
|
||||||
|
$output = data_get($data, 'output.0.content.0.refusal', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
93
app/Helpers/FirstParty/AspectRatio.php
Normal file
93
app/Helpers/FirstParty/AspectRatio.php
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers\FirstParty;
|
||||||
|
|
||||||
|
class AspectRatio
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the aspect ratio for given width and height
|
||||||
|
* Returns common aspect ratios first, then computed ratios
|
||||||
|
*
|
||||||
|
* @param int|float $width
|
||||||
|
* @param int|float $height
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function get($width, $height)
|
||||||
|
{
|
||||||
|
// Handle edge cases
|
||||||
|
if ($width <= 0 || $height <= 0) {
|
||||||
|
return "Invalid dimensions";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the actual ratio
|
||||||
|
$actualRatio = $width / $height;
|
||||||
|
|
||||||
|
// Define common aspect ratios with their decimal values and string representations
|
||||||
|
$commonRatios = [
|
||||||
|
'1:1' => 1.0, // 1/1 = 1.0
|
||||||
|
'4:3' => 4 / 3, // 1.333...
|
||||||
|
'3:4' => 3 / 4, // 0.75
|
||||||
|
'16:9' => 16 / 9, // 1.777...
|
||||||
|
'9:16' => 9 / 16, // 0.5625
|
||||||
|
'20:9' => 20 / 9, // 2.222...
|
||||||
|
'9:20' => 9 / 20, // 0.45
|
||||||
|
'5:4' => 5 / 4, // 1.25
|
||||||
|
'4:5' => 4 / 5, // 0.8
|
||||||
|
];
|
||||||
|
|
||||||
|
// Tolerance for floating point comparison (about 1% difference)
|
||||||
|
$tolerance = 0.01;
|
||||||
|
|
||||||
|
// Check against common ratios first
|
||||||
|
foreach ($commonRatios as $ratioString => $ratioValue) {
|
||||||
|
if (abs($actualRatio - $ratioValue) < $tolerance) {
|
||||||
|
return $ratioString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no common ratio matches, compute the simplified ratio
|
||||||
|
return self::computeSimplifiedRatio($width, $height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute simplified aspect ratio using GCD
|
||||||
|
*
|
||||||
|
* @param int|float $width
|
||||||
|
* @param int|float $height
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function computeSimplifiedRatio($width, $height)
|
||||||
|
{
|
||||||
|
// Convert to integers for GCD calculation
|
||||||
|
$intWidth = (int) $width;
|
||||||
|
$intHeight = (int) $height;
|
||||||
|
|
||||||
|
// Find the greatest common divisor
|
||||||
|
$gcd = self::gcd($intWidth, $intHeight);
|
||||||
|
|
||||||
|
// Simplify the ratio
|
||||||
|
$simplifiedWidth = $intWidth / $gcd;
|
||||||
|
$simplifiedHeight = $intHeight / $gcd;
|
||||||
|
|
||||||
|
return $simplifiedWidth . ':' . $simplifiedHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate Greatest Common Divisor using Euclidean algorithm
|
||||||
|
*
|
||||||
|
* @param int $a
|
||||||
|
* @param int $b
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
private static function gcd($a, $b)
|
||||||
|
{
|
||||||
|
// Euclidean algorithm for finding GCD
|
||||||
|
while ($b != 0) {
|
||||||
|
$temp = $b;
|
||||||
|
$b = $a % $b;
|
||||||
|
$a = $temp;
|
||||||
|
}
|
||||||
|
return $a;
|
||||||
|
}
|
||||||
|
}
|
||||||
183
app/Helpers/FirstParty/Meme/MemeGenerator.php
Normal file
183
app/Helpers/FirstParty/Meme/MemeGenerator.php
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Helpers\FirstParty\Meme;
|
||||||
|
|
||||||
|
use App\Helpers\FirstParty\AI\CloudflareAI;
|
||||||
|
use App\Helpers\FirstParty\AI\OpenAI;
|
||||||
|
use App\Helpers\FirstParty\AI\RunwareAI;
|
||||||
|
use App\Helpers\FirstParty\AspectRatio;
|
||||||
|
use App\Helpers\FirstParty\MediaEngine\MediaEngine;
|
||||||
|
use App\Models\BackgroundMedia;
|
||||||
|
use App\Models\Category;
|
||||||
|
use App\Models\Meme;
|
||||||
|
use App\Models\MemeMedia;
|
||||||
|
use Pgvector\Laravel\Distance;
|
||||||
|
use Str;
|
||||||
|
|
||||||
|
class MemeGenerator
|
||||||
|
{
|
||||||
|
|
||||||
|
const TYPE_SINGLE_CAPTION_MEME_BACKGROUND = 'single_caption_meme_background';
|
||||||
|
|
||||||
|
const STATUS_PENDING = 'pending';
|
||||||
|
const STATUS_COMPLETED = 'completed';
|
||||||
|
|
||||||
|
public static function generateMemeByCategory(Category $category)
|
||||||
|
{
|
||||||
|
$meme_output = self::getMemeOutputByCategory($category);
|
||||||
|
|
||||||
|
$meme = null;
|
||||||
|
|
||||||
|
if ($meme_output->success) {
|
||||||
|
$meme = Meme::create([
|
||||||
|
'type' => self::TYPE_SINGLE_CAPTION_MEME_BACKGROUND,
|
||||||
|
'prompt' => $meme_output->prompt,
|
||||||
|
'category_id' => $category->id,
|
||||||
|
'caption' => $meme_output->caption,
|
||||||
|
'meme_keywords' => $meme_output->keywords,
|
||||||
|
'background' => $meme_output->background,
|
||||||
|
'keywords' => $meme_output->keywords,
|
||||||
|
'is_system' => true,
|
||||||
|
'status' => self::STATUS_PENDING
|
||||||
|
]);
|
||||||
|
|
||||||
|
$meme->attachTags($meme_output->keywords, 'meme');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_null($meme) && $meme->status == self::STATUS_PENDING) {
|
||||||
|
// populate meme_id
|
||||||
|
$meme->meme_id = self::getMemeMediaByKeywords($meme_output->keywords)->id;
|
||||||
|
$meme->background_id = self::generateBackgroundMediaWithRunware($meme_output->background)->id;
|
||||||
|
|
||||||
|
if (!is_null($meme->meme_id) && !is_null($meme->background_id)) {
|
||||||
|
$meme->status = self::STATUS_COMPLETED;
|
||||||
|
}
|
||||||
|
|
||||||
|
$meme->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $meme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getMemeOutputByCategory(Category $category)
|
||||||
|
{
|
||||||
|
$retries = 3;
|
||||||
|
$attempt = 0;
|
||||||
|
|
||||||
|
$random_keyword = Str::lower($category->name);
|
||||||
|
|
||||||
|
if (!is_null($category->parent_id)) {
|
||||||
|
$random_keyword = $category->parent->name . " - " . $random_keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_null($category->meme_angles)) {
|
||||||
|
$random_keyword .= " - " . collect($category->meme_angles)->random();
|
||||||
|
} else if (!is_null($category->keywords)) {
|
||||||
|
$random_keyword .= " - " . collect($category->keywords)->random();
|
||||||
|
}
|
||||||
|
|
||||||
|
$prompt = "Write me 1 meme about {$random_keyword}";
|
||||||
|
|
||||||
|
// RETRY MECHANISM START
|
||||||
|
do {
|
||||||
|
$attempt++;
|
||||||
|
|
||||||
|
$meme_response = OpenAI::getSingleMemeGenerator($prompt);
|
||||||
|
$meme_output = json_decode(OpenAI::getOpenAIOutput($meme_response));
|
||||||
|
|
||||||
|
$output_is_valid = false;
|
||||||
|
|
||||||
|
if (!is_null($meme_output)) {
|
||||||
|
if (
|
||||||
|
isset($meme_output->caption) &&
|
||||||
|
isset($meme_output->meme_keywords) &&
|
||||||
|
isset($meme_output->background) &&
|
||||||
|
isset($meme_output->keywords)
|
||||||
|
) {
|
||||||
|
$output_is_valid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If output is valid or we've exhausted all retries, break the loop
|
||||||
|
if ($output_is_valid || $attempt >= $retries) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Add a small delay between retries to avoid rate limiting
|
||||||
|
// sleep(1);
|
||||||
|
|
||||||
|
} while ($attempt < $retries);
|
||||||
|
// RETRY MECHANISM END
|
||||||
|
|
||||||
|
if ($output_is_valid) {
|
||||||
|
$meme_output->success = true;
|
||||||
|
$meme_output->prompt = $prompt;
|
||||||
|
$meme_output->category = $category;
|
||||||
|
$meme_output->attempts = $attempt; // Optional: track how many attempts it took
|
||||||
|
} else {
|
||||||
|
$meme_output = (object) [
|
||||||
|
'success' => false,
|
||||||
|
'attempts' => $attempt, // Optional: track how many attempts were made
|
||||||
|
'error' => 'Failed to generate valid meme after ' . $retries . ' attempts'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $meme_output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateBackgroundMediaWithRunware($prompt)
|
||||||
|
{
|
||||||
|
$media_width = 1024;
|
||||||
|
$media_height = 1024;
|
||||||
|
$aspect_ratio = AspectRatio::get($media_width, $media_height);
|
||||||
|
|
||||||
|
|
||||||
|
$runware_output_url = RunwareAI::generateSchnellImage(Str::uuid(), $prompt, $media_width, $media_height);
|
||||||
|
|
||||||
|
$media = MediaEngine::addMedia(
|
||||||
|
'system-i',
|
||||||
|
'image',
|
||||||
|
'system_uploaded',
|
||||||
|
'replicate',
|
||||||
|
null,
|
||||||
|
$runware_output_url,
|
||||||
|
'download'
|
||||||
|
);
|
||||||
|
|
||||||
|
$background_media = BackgroundMedia::create([
|
||||||
|
'media_uuid' => $media->uuid,
|
||||||
|
'media_url' => MediaEngine::getMediaCloudUrl($media),
|
||||||
|
'prompt' => $prompt,
|
||||||
|
'status' => 'completed',
|
||||||
|
'aspect_ratio' => $aspect_ratio,
|
||||||
|
'media_width' => $media_width,
|
||||||
|
'media_height' => $media_height,
|
||||||
|
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $background_media;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getMemeMediaByKeywords(array $keywords)
|
||||||
|
{
|
||||||
|
$meme_media = null;
|
||||||
|
|
||||||
|
$meme_medias = MemeMedia::withAnyTags($keywords)->take(10)->get();
|
||||||
|
|
||||||
|
if ($meme_medias->count() > 0) {
|
||||||
|
$meme_media = $meme_medias->random();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_null($meme_media)) {
|
||||||
|
$meme_embedding = CloudflareAI::getVectorEmbeddingBgeSmall(implode(' ', $keywords));
|
||||||
|
|
||||||
|
$meme_medias = MemeMedia::query()->nearestNeighbors('embedding', $meme_embedding, Distance::L2)->take(10)->get();
|
||||||
|
|
||||||
|
if ($meme_medias->count() > 0) {
|
||||||
|
$meme_media = $meme_medias->random();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $meme_media;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Helpers\FirstParty\MediaEngine\MediaEngine;
|
||||||
|
use App\Helpers\FirstParty\Meme\MemeGenerator;
|
||||||
use App\Models\BackgroundMedia;
|
use App\Models\BackgroundMedia;
|
||||||
|
use App\Models\Meme;
|
||||||
use App\Models\MemeMedia;
|
use App\Models\MemeMedia;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
@@ -10,16 +13,15 @@ class FrontMediaController extends Controller
|
|||||||
{
|
{
|
||||||
public function init(Request $request)
|
public function init(Request $request)
|
||||||
{
|
{
|
||||||
|
$meme = Meme::with('meme_media', 'background_media')->where('status', MemeGenerator::STATUS_COMPLETED)->take(1)->latest()->first();
|
||||||
$meme = MemeMedia::where('type', 'video')->where('sub_type', 'overlay')->take(1)->inRandomOrder()->first();
|
|
||||||
$background = BackgroundMedia::where('status', 'completed')->take(1)->inRandomOrder()->first();
|
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => [
|
'success' => [
|
||||||
'data' => [
|
'data' => [
|
||||||
'init' => [
|
'init' => [
|
||||||
'meme' => $meme,
|
'caption' => $meme->caption,
|
||||||
'background' => $background,
|
'meme' => $meme->meme_media,
|
||||||
|
'background' => $meme->background_media,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -2,8 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Helpers\FirstParty\AI\CloudflareAI;
|
||||||
use App\Helpers\FirstParty\AI\OpenAI;
|
use App\Helpers\FirstParty\AI\OpenAI;
|
||||||
use App\Helpers\FirstParty\AI\RunwareAI;
|
use App\Helpers\FirstParty\AI\RunwareAI;
|
||||||
|
use App\Helpers\FirstParty\AspectRatio;
|
||||||
|
use App\Helpers\FirstParty\Meme\MemeGenerator;
|
||||||
|
use App\Models\Category;
|
||||||
|
use App\Models\Meme;
|
||||||
|
use App\Models\MemeMedia;
|
||||||
|
use Pgvector\Laravel\Distance;
|
||||||
use Str;
|
use Str;
|
||||||
|
|
||||||
class TestController extends Controller
|
class TestController extends Controller
|
||||||
@@ -13,6 +20,22 @@ public function index()
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function aspectRatio()
|
||||||
|
{
|
||||||
|
$aspect_ratio = AspectRatio::get(1024, 1024);
|
||||||
|
|
||||||
|
dd($aspect_ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateMeme()
|
||||||
|
{
|
||||||
|
$category = Category::inRandomOrder()->first();
|
||||||
|
|
||||||
|
$meme = MemeGenerator::generateMemeByCategory($category);
|
||||||
|
|
||||||
|
dd($meme);
|
||||||
|
}
|
||||||
|
|
||||||
public function populateDuration()
|
public function populateDuration()
|
||||||
{
|
{
|
||||||
\App\Helpers\FirstParty\Maintenance\MemeMediaMaintenance::populateDurations();
|
\App\Helpers\FirstParty\Maintenance\MemeMediaMaintenance::populateDurations();
|
||||||
|
|||||||
@@ -35,17 +35,19 @@ class BackgroundMedia extends Model
|
|||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'embedding' => Vector::class,
|
'embedding' => Vector::class,
|
||||||
|
'media_width' => 'integer',
|
||||||
|
'media_height' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'list_type',
|
|
||||||
'area',
|
|
||||||
'location_name',
|
|
||||||
'status',
|
'status',
|
||||||
'media_uuid',
|
'media_uuid',
|
||||||
'media_url',
|
'media_url',
|
||||||
'embedding',
|
'embedding',
|
||||||
'prompt',
|
'prompt',
|
||||||
|
'media_width',
|
||||||
|
'media_height',
|
||||||
|
'aspect_ratio',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
@@ -53,9 +55,6 @@ class BackgroundMedia extends Model
|
|||||||
'created_at',
|
'created_at',
|
||||||
'updated_at',
|
'updated_at',
|
||||||
'deleted_at',
|
'deleted_at',
|
||||||
'list_type',
|
|
||||||
'area',
|
|
||||||
'location_name',
|
|
||||||
'status',
|
'status',
|
||||||
'media_uuid',
|
'media_uuid',
|
||||||
'embedding',
|
'embedding',
|
||||||
@@ -68,7 +67,7 @@ class BackgroundMedia 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']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
78
app/Models/Meme.php
Normal file
78
app/Models/Meme.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Reliese Model.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Spatie\Tags\HasTags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Meme
|
||||||
|
*
|
||||||
|
* @property int $id
|
||||||
|
* @property string $type
|
||||||
|
* @property int|null $category_id
|
||||||
|
* @property int|null $user_id
|
||||||
|
* @property int|null $meme_id
|
||||||
|
* @property int|null $background_id
|
||||||
|
* @property string $status
|
||||||
|
* @property string $prompt
|
||||||
|
* @property string|null $caption
|
||||||
|
* @property string|null $meme_keywords
|
||||||
|
* @property string|null $background
|
||||||
|
* @property string $keywords
|
||||||
|
* @property Carbon|null $created_at
|
||||||
|
* @property Carbon|null $updated_at
|
||||||
|
* @property string|null $deleted_at
|
||||||
|
*
|
||||||
|
* @package App\Models
|
||||||
|
*/
|
||||||
|
class Meme extends Model
|
||||||
|
{
|
||||||
|
use SoftDeletes, HasTags;
|
||||||
|
|
||||||
|
protected $table = 'memes';
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
|
||||||
|
'is_system' => 'boolean',
|
||||||
|
|
||||||
|
'category_id' => 'int',
|
||||||
|
'user_id' => 'int',
|
||||||
|
'meme_id' => 'int',
|
||||||
|
'background_id' => 'int',
|
||||||
|
|
||||||
|
'meme_keywords' => 'array',
|
||||||
|
'keywords' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'type',
|
||||||
|
'is_system',
|
||||||
|
'category_id',
|
||||||
|
'user_id',
|
||||||
|
'meme_id',
|
||||||
|
'background_id',
|
||||||
|
'status',
|
||||||
|
'prompt',
|
||||||
|
'caption',
|
||||||
|
'meme_keywords',
|
||||||
|
'background',
|
||||||
|
'keywords'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function meme_media()
|
||||||
|
{
|
||||||
|
return $this->hasOne(MemeMedia::class, 'id', 'meme_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function background_media()
|
||||||
|
{
|
||||||
|
return $this->hasOne(BackgroundMedia::class, 'id', 'background_id');
|
||||||
|
}
|
||||||
|
}
|
||||||
59
database/migrations/2025_06_19_051802_create_memes_table.php
Normal file
59
database/migrations/2025_06_19_051802_create_memes_table.php
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?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
|
||||||
|
{
|
||||||
|
|
||||||
|
// {#488 ▼ // app/Http/Controllers/TestController.php:24
|
||||||
|
// +"caption": "thesis writing procrastination blues"
|
||||||
|
// +"meme_keywords": array:3 [▼
|
||||||
|
// 0 => "anxiety"
|
||||||
|
// 1 => "overwhelm"
|
||||||
|
// 2 => "imposter syndrome"
|
||||||
|
// ]
|
||||||
|
// +"background": "cluttered desk with piles of papers and a laptop"
|
||||||
|
// +"keywords": array:3 [▼
|
||||||
|
// 0 => "thesis"
|
||||||
|
// 1 => "anxiety"
|
||||||
|
// 2 => "breaks"
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
Schema::create('memes', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->enum('type', ['single_caption_meme_background'])->default('single_caption_meme_background');
|
||||||
|
$table->boolean('is_system');
|
||||||
|
|
||||||
|
$table->foreignId('category_id')->nullable();
|
||||||
|
$table->foreignId('user_id')->nullable();
|
||||||
|
$table->foreignId('meme_id')->nullable();
|
||||||
|
$table->foreignId('background_id')->nullable();
|
||||||
|
|
||||||
|
$table->enum('status', ['pending', 'completed'])->default('pending');
|
||||||
|
$table->text('prompt');
|
||||||
|
$table->string('caption')->nullable();
|
||||||
|
$table->json('meme_keywords')->nullable();
|
||||||
|
$table->string('background')->nullable();
|
||||||
|
$table->json('keywords');
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('memes');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -268,7 +268,7 @@ private function importSingleMeme(array $meme_record): bool
|
|||||||
private function attachKeywordsAsTags(MemeMedia $meme_media, array $keywords): void
|
private function attachKeywordsAsTags(MemeMedia $meme_media, array $keywords): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$meme_media->attachTags($keywords, 'meme');
|
$meme_media->attachTags($keywords, 'meme_media');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->command->warn("Failed to attach tags to meme media '{$meme_media->name}': " . $e->getMessage());
|
$this->command->warn("Failed to attach tags to meme media '{$meme_media->name}': " . $e->getMessage());
|
||||||
Log::warning("Failed to attach tags", [
|
Log::warning("Failed to attach tags", [
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ const useMediaStore = create(
|
|||||||
|
|
||||||
if (response?.data?.success?.data?.init) {
|
if (response?.data?.success?.data?.init) {
|
||||||
set({
|
set({
|
||||||
|
currentCaption: response.data.success.data.init.caption,
|
||||||
selectedMeme: response.data.success.data.init.meme,
|
selectedMeme: response.data.success.data.init.meme,
|
||||||
selectedBackground: response.data.success.data.init.background,
|
selectedBackground: response.data.success.data.init.background,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,3 +9,7 @@
|
|||||||
Route::get('/writeMeme', [TestController::class, 'writeMeme']);
|
Route::get('/writeMeme', [TestController::class, 'writeMeme']);
|
||||||
|
|
||||||
Route::get('/generateSchnellImage', [TestController::class, 'generateSchnellImage']);
|
Route::get('/generateSchnellImage', [TestController::class, 'generateSchnellImage']);
|
||||||
|
|
||||||
|
Route::get('/generateMeme', [TestController::class, 'generateMeme']);
|
||||||
|
|
||||||
|
Route::get('/aspectRatio', [TestController::class, 'aspectRatio']);
|
||||||
|
|||||||
Reference in New Issue
Block a user