Update
This commit is contained in:
148
_ide_helper.php
148
_ide_helper.php
@@ -24337,6 +24337,103 @@ public static function inertiaPage()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Illuminate\Database\Schema {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Blueprint {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @see \Kalnoy\Nestedset\NestedSetServiceProvider::register()
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
public static function nestedSet()
|
||||||
|
{
|
||||||
|
return \Illuminate\Database\Schema\Blueprint::nestedSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @see \Kalnoy\Nestedset\NestedSetServiceProvider::register()
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
public static function dropNestedSet()
|
||||||
|
{
|
||||||
|
return \Illuminate\Database\Schema\Blueprint::dropNestedSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @see \Pgvector\Laravel\Schema::register()
|
||||||
|
* @param string $column
|
||||||
|
* @param mixed|null $dimensions
|
||||||
|
* @return \Illuminate\Database\Schema\ColumnDefinition
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
public static function halfvec($column, $dimensions = null)
|
||||||
|
{
|
||||||
|
return \Illuminate\Database\Schema\Blueprint::halfvec($column, $dimensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @see \Pgvector\Laravel\Schema::register()
|
||||||
|
* @param string $column
|
||||||
|
* @param mixed|null $length
|
||||||
|
* @return \Illuminate\Database\Schema\ColumnDefinition
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
public static function bit($column, $length = null)
|
||||||
|
{
|
||||||
|
return \Illuminate\Database\Schema\Blueprint::bit($column, $length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @see \Pgvector\Laravel\Schema::register()
|
||||||
|
* @param string $column
|
||||||
|
* @param mixed|null $dimensions
|
||||||
|
* @return \Illuminate\Database\Schema\ColumnDefinition
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
public static function sparsevec($column, $dimensions = null)
|
||||||
|
{
|
||||||
|
return \Illuminate\Database\Schema\Blueprint::sparsevec($column, $dimensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Illuminate\Database\Eloquent\Factories {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @template TModel of \Illuminate\Database\Eloquent\Model
|
||||||
|
* @method $this trashed()
|
||||||
|
*/
|
||||||
|
class Factory {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @see \Spatie\Translatable\TranslatableServiceProvider::packageRegistered()
|
||||||
|
* @param array|string $locales
|
||||||
|
* @param mixed|null $value
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
public static function translations($locales, $value)
|
||||||
|
{
|
||||||
|
return \Illuminate\Database\Eloquent\Factories\Factory::translations($locales, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace Illuminate\Database\Schema\Grammars {
|
namespace Illuminate\Database\Schema\Grammars {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -24517,57 +24614,6 @@ public static function typeSparsevec($column)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Illuminate\Database\Schema {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class Blueprint {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @see \Pgvector\Laravel\Schema::register()
|
|
||||||
* @param string $column
|
|
||||||
* @param mixed|null $dimensions
|
|
||||||
* @return \Illuminate\Database\Schema\ColumnDefinition
|
|
||||||
* @static
|
|
||||||
*/
|
|
||||||
public static function halfvec($column, $dimensions = null)
|
|
||||||
{
|
|
||||||
return \Illuminate\Database\Schema\Blueprint::halfvec($column, $dimensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @see \Pgvector\Laravel\Schema::register()
|
|
||||||
* @param string $column
|
|
||||||
* @param mixed|null $length
|
|
||||||
* @return \Illuminate\Database\Schema\ColumnDefinition
|
|
||||||
* @static
|
|
||||||
*/
|
|
||||||
public static function bit($column, $length = null)
|
|
||||||
{
|
|
||||||
return \Illuminate\Database\Schema\Blueprint::bit($column, $length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @see \Pgvector\Laravel\Schema::register()
|
|
||||||
* @param string $column
|
|
||||||
* @param mixed|null $dimensions
|
|
||||||
* @return \Illuminate\Database\Schema\ColumnDefinition
|
|
||||||
* @static
|
|
||||||
*/
|
|
||||||
public static function sparsevec($column, $dimensions = null)
|
|
||||||
{
|
|
||||||
return \Illuminate\Database\Schema\Blueprint::sparsevec($column, $dimensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Illuminate\Database\Query\Grammars {
|
namespace Illuminate\Database\Query\Grammars {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -6,6 +6,51 @@
|
|||||||
|
|
||||||
class OpenAI
|
class OpenAI
|
||||||
{
|
{
|
||||||
|
public static function getMemeKeywords(string $name, string $description)
|
||||||
|
{
|
||||||
|
$apiKey = config('services.openai.api_key'); // Make sure to set this in config/services.php
|
||||||
|
|
||||||
|
$response = Http::withHeaders([
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'Authorization' => 'Bearer ' . $apiKey,
|
||||||
|
])->post('https://api.openai.com/v1/responses', [
|
||||||
|
'model' => 'gpt-4.1-nano',
|
||||||
|
'input' => [
|
||||||
|
[
|
||||||
|
'role' => 'system',
|
||||||
|
'content' => [
|
||||||
|
[
|
||||||
|
'type' => 'input_text',
|
||||||
|
'text' => "You are a meme annotation assistant. Given a meme name and description, you must populate three keyword categories: action_keywords: List specific actions being performed in the meme (e.g., wearing, singing, dancing) emotion_keywords: List feelings and emotions conveyed by the meme (e.g., joy, excitement, sadness) misc_keywords: List core identifying elements - keep this concise and focused on essential references only (e.g., main subjects, key objects, proper nouns) Format your response with clear category labels. Be precise and avoid over-elaborating, especially in misc_keywords. Example: Input: \"7th Element OIIA Cat, OIIA cat wearing 7th element head ban and sings 7th element song chorous with OIIA sounds\" Output: action_keywords: wearing, singing, performing emotion_keywords: excitement, joy, playfulness misc_keywords: cat, 7th element, headband, OIIA. the description may also have spelling and grammar issues, please fix it\n\nreturn in json:\n{\ndescription: \"\",\naction_keywords:[],\nemotion_keywords:[],\nmisc_keywords:[],\n}",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'role' => 'user',
|
||||||
|
'content' => [
|
||||||
|
[
|
||||||
|
'type' => 'input_text',
|
||||||
|
'text' => "Name: $name\nDescription: $description",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'text' => [
|
||||||
|
'format' => [
|
||||||
|
'type' => 'json_object',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'reasoning' => new \stdClass,
|
||||||
|
'tools' => [],
|
||||||
|
'temperature' => 1,
|
||||||
|
'max_output_tokens' => 2048,
|
||||||
|
'top_p' => 1,
|
||||||
|
'store' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $response->json();
|
||||||
|
}
|
||||||
|
|
||||||
public static function getSingleMemeGenerator($user_prompt)
|
public static function getSingleMemeGenerator($user_prompt)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -17,7 +62,6 @@ public static function getSingleMemeGenerator($user_prompt)
|
|||||||
'A humorous, funny one-liner with no punctuation that describes a vivid, realistic scenario or reaction in a clearly defined context',
|
'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'),
|
||||||
@@ -56,9 +100,27 @@ public static function getSingleMemeGenerator($user_prompt)
|
|||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'description' => $caption_descriptions[rand(0, count($caption_descriptions) - 1)],
|
'description' => $caption_descriptions[rand(0, count($caption_descriptions) - 1)],
|
||||||
],
|
],
|
||||||
'meme_keywords' => [
|
'primary_keyword_type' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => "Primary keyword type, choose only between: (action|emotion|misc)",
|
||||||
|
],
|
||||||
|
'action_keywords' => [
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'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.',
|
'description' => 'List specific actions being performed in the meme (e.g., wearing, singing, dancing)',
|
||||||
|
'items' => [
|
||||||
|
'type' => 'string',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'emotion_keywords' => [
|
||||||
|
'type' => 'array',
|
||||||
|
'description' => 'List feelings and emotions conveyed by the meme (e.g., joy, excitement, sadness)',
|
||||||
|
'items' => [
|
||||||
|
'type' => 'string',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'misc_keywords' => [
|
||||||
|
'type' => 'array',
|
||||||
|
'description' => 'List core identifying elements - keep this concise and focused on essential references only (e.g., main subjects, key objects, proper nouns)',
|
||||||
'items' => [
|
'items' => [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
],
|
],
|
||||||
@@ -78,7 +140,10 @@ public static function getSingleMemeGenerator($user_prompt)
|
|||||||
],
|
],
|
||||||
'required' => [
|
'required' => [
|
||||||
'caption',
|
'caption',
|
||||||
'meme_keywords',
|
'primary_keyword_type',
|
||||||
|
'action_keywords',
|
||||||
|
'emotion_keywords',
|
||||||
|
'misc_keywords',
|
||||||
'background',
|
'background',
|
||||||
'keywords',
|
'keywords',
|
||||||
],
|
],
|
||||||
@@ -109,7 +174,7 @@ public static function getSingleMemeGenerator($user_prompt)
|
|||||||
|
|
||||||
public static function getOpenAIOutput($data)
|
public static function getOpenAIOutput($data)
|
||||||
{
|
{
|
||||||
//dump($data);
|
// dump($data);
|
||||||
|
|
||||||
$output = null;
|
$output = null;
|
||||||
|
|
||||||
@@ -117,7 +182,7 @@ public static function getOpenAIOutput($data)
|
|||||||
|
|
||||||
if ($response_type === 'output_text') {
|
if ($response_type === 'output_text') {
|
||||||
$output = data_get($data, 'output.0.content.0.text', null);
|
$output = data_get($data, 'output.0.content.0.text', null);
|
||||||
} else if ($response_type === 'refusal') {
|
} elseif ($response_type === 'refusal') {
|
||||||
$output = data_get($data, 'output.0.content.0.refusal', null);
|
$output = data_get($data, 'output.0.content.0.refusal', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,22 +14,22 @@ public static function generateSchnellImage($uuid, $prompt, $width = 1024, $heig
|
|||||||
$response = Http::timeout(60)
|
$response = Http::timeout(60)
|
||||||
->withHeaders([
|
->withHeaders([
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'Authorization' => 'Bearer ' . $api_key,
|
'Authorization' => 'Bearer '.$api_key,
|
||||||
])
|
])
|
||||||
->post('https://api.runware.ai/v1', [
|
->post('https://api.runware.ai/v1', [
|
||||||
[
|
[
|
||||||
"taskUUID" => $uuid,
|
'taskUUID' => $uuid,
|
||||||
"taskType" => "imageInference",
|
'taskType' => 'imageInference',
|
||||||
"width" => $width,
|
'width' => $width,
|
||||||
"height" => $height,
|
'height' => $height,
|
||||||
"numberResults" => 1,
|
'numberResults' => 1,
|
||||||
"outputFormat" => "WEBP",
|
'outputFormat' => 'WEBP',
|
||||||
"outputType" => ["URL"],
|
'outputType' => ['URL'],
|
||||||
"includeCost" => true,
|
'includeCost' => true,
|
||||||
"inputImages" => [],
|
'inputImages' => [],
|
||||||
"positivePrompt" => $prompt,
|
'positivePrompt' => $prompt,
|
||||||
"model" => "runware:100@1"
|
'model' => 'runware:100@1',
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Check if the request was successful
|
// Check if the request was successful
|
||||||
@@ -50,10 +50,10 @@ public static function generateSchnellImage($uuid, $prompt, $width = 1024, $heig
|
|||||||
throw new \Exception('Image URL not found in response');
|
throw new \Exception('Image URL not found in response');
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new \Exception('API request failed: ' . $response->status() . ' - ' . $response->body());
|
throw new \Exception('API request failed: '.$response->status().' - '.$response->body());
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
// Log the error or handle as needed
|
// Log the error or handle as needed
|
||||||
\Log::error('RunwareAI API Error: ' . $e->getMessage());
|
\Log::error('RunwareAI API Error: '.$e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,19 @@
|
|||||||
|
|
||||||
class AspectRatio
|
class AspectRatio
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the aspect ratio for given width and height
|
* Get the aspect ratio for given width and height
|
||||||
* Returns common aspect ratios first, then computed ratios
|
* Returns common aspect ratios first, then computed ratios
|
||||||
*
|
*
|
||||||
* @param int|float $width
|
* @param int|float $width
|
||||||
* @param int|float $height
|
* @param int|float $height
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function get($width, $height)
|
public static function get($width, $height)
|
||||||
{
|
{
|
||||||
// Handle edge cases
|
// Handle edge cases
|
||||||
if ($width <= 0 || $height <= 0) {
|
if ($width <= 0 || $height <= 0) {
|
||||||
return "Invalid dimensions";
|
return 'Invalid dimensions';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the actual ratio
|
// Calculate the actual ratio
|
||||||
@@ -53,8 +52,8 @@ public static function get($width, $height)
|
|||||||
/**
|
/**
|
||||||
* Compute simplified aspect ratio using GCD
|
* Compute simplified aspect ratio using GCD
|
||||||
*
|
*
|
||||||
* @param int|float $width
|
* @param int|float $width
|
||||||
* @param int|float $height
|
* @param int|float $height
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
private static function computeSimplifiedRatio($width, $height)
|
private static function computeSimplifiedRatio($width, $height)
|
||||||
@@ -70,14 +69,14 @@ private static function computeSimplifiedRatio($width, $height)
|
|||||||
$simplifiedWidth = $intWidth / $gcd;
|
$simplifiedWidth = $intWidth / $gcd;
|
||||||
$simplifiedHeight = $intHeight / $gcd;
|
$simplifiedHeight = $intHeight / $gcd;
|
||||||
|
|
||||||
return $simplifiedWidth . ':' . $simplifiedHeight;
|
return $simplifiedWidth.':'.$simplifiedHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate Greatest Common Divisor using Euclidean algorithm
|
* Calculate Greatest Common Divisor using Euclidean algorithm
|
||||||
*
|
*
|
||||||
* @param int $a
|
* @param int $a
|
||||||
* @param int $b
|
* @param int $b
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
private static function gcd($a, $b)
|
private static function gcd($a, $b)
|
||||||
@@ -88,6 +87,7 @@ private static function gcd($a, $b)
|
|||||||
$b = $a % $b;
|
$b = $a % $b;
|
||||||
$a = $temp;
|
$a = $temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $a;
|
return $a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,44 @@
|
|||||||
|
|
||||||
namespace App\Helpers\FirstParty\Maintenance;
|
namespace App\Helpers\FirstParty\Maintenance;
|
||||||
|
|
||||||
|
use App\Helpers\FirstParty\AI\OpenAI;
|
||||||
use App\Models\MemeMedia;
|
use App\Models\MemeMedia;
|
||||||
use ProtoneMedia\LaravelFFMpeg\Support\FFMpeg;
|
use ProtoneMedia\LaravelFFMpeg\Support\FFMpeg;
|
||||||
|
|
||||||
class MemeMediaMaintenance
|
class MemeMediaMaintenance
|
||||||
{
|
{
|
||||||
|
public static function patchMemeKeywords()
|
||||||
|
{
|
||||||
|
$meme_medias = MemeMedia::whereNull('action_keywords')->get();
|
||||||
|
|
||||||
|
foreach ($meme_medias as $key => $meme_media) {
|
||||||
|
|
||||||
|
dump('Processing '.$key + 1 .'/'.$meme_medias->count().': '.$meme_media->name);
|
||||||
|
|
||||||
|
$meme_keywords_response = OpenAI::getMemeKeywords($meme_media->name, $meme_media->description);
|
||||||
|
|
||||||
|
$meme_keywords_output = json_decode(OpenAI::getOpenAIOutput($meme_keywords_response));
|
||||||
|
|
||||||
|
$meme_media->description = $meme_keywords_output->description;
|
||||||
|
$meme_media->action_keywords = $meme_keywords_output->action_keywords;
|
||||||
|
$meme_media->emotion_keywords = $meme_keywords_output->emotion_keywords;
|
||||||
|
$meme_media->misc_keywords = $meme_keywords_output->misc_keywords;
|
||||||
|
|
||||||
|
$meme_media->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function addMemeKeywordsToTags()
|
||||||
|
{
|
||||||
|
$meme_medias = MemeMedia::all();
|
||||||
|
|
||||||
|
foreach ($meme_medias as $key => $meme_media) {
|
||||||
|
$meme_media->attachTags($meme_media->action_keywords, 'meme_media_action');
|
||||||
|
$meme_media->attachTags($meme_media->emotion_keywords, 'meme_media_emotion');
|
||||||
|
$meme_media->attachTags($meme_media->misc_keywords, 'meme_media_misc');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static function populateDurations()
|
public static function populateDurations()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -16,15 +16,21 @@
|
|||||||
|
|
||||||
class MemeGenerator
|
class MemeGenerator
|
||||||
{
|
{
|
||||||
|
|
||||||
const TYPE_SINGLE_CAPTION_MEME_BACKGROUND = 'single_caption_meme_background';
|
const TYPE_SINGLE_CAPTION_MEME_BACKGROUND = 'single_caption_meme_background';
|
||||||
|
|
||||||
const STATUS_PENDING = 'pending';
|
const STATUS_PENDING = 'pending';
|
||||||
|
|
||||||
const STATUS_COMPLETED = 'completed';
|
const STATUS_COMPLETED = 'completed';
|
||||||
|
|
||||||
|
public static function getSuitableMeme(Meme $meme)
|
||||||
|
{
|
||||||
|
//dd($meme->toArray());
|
||||||
|
return MemeMedia::first();
|
||||||
|
}
|
||||||
|
|
||||||
public static function generateMemeByCategory(Category $category)
|
public static function generateMemeByCategory(Category $category)
|
||||||
{
|
{
|
||||||
$meme_output = self::getMemeOutputByCategory($category);
|
$meme_output = self::generateMemeOutputByCategory($category);
|
||||||
|
|
||||||
$meme = null;
|
$meme = null;
|
||||||
|
|
||||||
@@ -38,18 +44,25 @@ public static function generateMemeByCategory(Category $category)
|
|||||||
'background' => $meme_output->background,
|
'background' => $meme_output->background,
|
||||||
'keywords' => $meme_output->keywords,
|
'keywords' => $meme_output->keywords,
|
||||||
'is_system' => true,
|
'is_system' => true,
|
||||||
'status' => self::STATUS_PENDING
|
'status' => self::STATUS_PENDING,
|
||||||
|
'primary_keyword_type' => $meme_output->primary_keyword_type,
|
||||||
|
'action_keywords' => $meme_output->action_keywords,
|
||||||
|
'emotion_keywords' => $meme_output->emotion_keywords,
|
||||||
|
'misc_keywords' => $meme_output->misc_keywords,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$meme->attachTags($meme_output->keywords, 'meme');
|
$meme->attachTags($meme_output->keywords, 'meme');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_null($meme) && $meme->status == self::STATUS_PENDING) {
|
if (! is_null($meme) && $meme->status == self::STATUS_PENDING) {
|
||||||
// populate meme_id
|
// populate meme_id
|
||||||
$meme->meme_id = self::getMemeMediaByKeywords($meme_output->keywords)->id;
|
$meme->meme_id = null; // self::getMemeMediaByKeywords($meme_output->keywords)->id;
|
||||||
$meme->background_id = self::generateBackgroundMediaWithRunware($meme_output->background)->id;
|
$meme->background_id = self::generateBackgroundMediaWithRunware($meme_output->background)->id;
|
||||||
|
|
||||||
if (!is_null($meme->meme_id) && !is_null($meme->background_id)) {
|
if (
|
||||||
|
//!is_null($meme->meme_id) &&
|
||||||
|
! is_null($meme->background_id)
|
||||||
|
) {
|
||||||
$meme->status = self::STATUS_COMPLETED;
|
$meme->status = self::STATUS_COMPLETED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,21 +72,21 @@ public static function generateMemeByCategory(Category $category)
|
|||||||
return $meme;
|
return $meme;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getMemeOutputByCategory(Category $category)
|
public static function generateMemeOutputByCategory(Category $category)
|
||||||
{
|
{
|
||||||
$retries = 3;
|
$retries = 3;
|
||||||
$attempt = 0;
|
$attempt = 0;
|
||||||
|
|
||||||
$random_keyword = Str::lower($category->name);
|
$random_keyword = Str::lower($category->name);
|
||||||
|
|
||||||
if (!is_null($category->parent_id)) {
|
if (! is_null($category->parent_id)) {
|
||||||
$random_keyword = $category->parent->name . " - " . $random_keyword;
|
$random_keyword = $category->parent->name . ' - ' . $random_keyword;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_null($category->meme_angles)) {
|
if (! is_null($category->meme_angles)) {
|
||||||
$random_keyword .= " - " . collect($category->meme_angles)->random();
|
$random_keyword .= ' - ' . collect($category->meme_angles)->random();
|
||||||
} else if (!is_null($category->keywords)) {
|
} elseif (! is_null($category->keywords)) {
|
||||||
$random_keyword .= " - " . collect($category->keywords)->random();
|
$random_keyword .= ' - ' . collect($category->keywords)->random();
|
||||||
}
|
}
|
||||||
|
|
||||||
$prompt = "Write me 1 meme about {$random_keyword}";
|
$prompt = "Write me 1 meme about {$random_keyword}";
|
||||||
@@ -87,12 +100,14 @@ public static function getMemeOutputByCategory(Category $category)
|
|||||||
|
|
||||||
$output_is_valid = false;
|
$output_is_valid = false;
|
||||||
|
|
||||||
if (!is_null($meme_output)) {
|
if (! is_null($meme_output)) {
|
||||||
if (
|
if (
|
||||||
isset($meme_output->caption) &&
|
isset($meme_output->caption) &&
|
||||||
isset($meme_output->meme_keywords) &&
|
|
||||||
isset($meme_output->background) &&
|
isset($meme_output->background) &&
|
||||||
isset($meme_output->keywords)
|
isset($meme_output->keywords) &&
|
||||||
|
isset($meme_output->primary_keyword_type) &&
|
||||||
|
isset($meme_output->action_keywords) &&
|
||||||
|
isset($meme_output->emotion_keywords)
|
||||||
) {
|
) {
|
||||||
$output_is_valid = true;
|
$output_is_valid = true;
|
||||||
}
|
}
|
||||||
@@ -118,7 +133,7 @@ public static function getMemeOutputByCategory(Category $category)
|
|||||||
$meme_output = (object) [
|
$meme_output = (object) [
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'attempts' => $attempt, // Optional: track how many attempts were made
|
'attempts' => $attempt, // Optional: track how many attempts were made
|
||||||
'error' => 'Failed to generate valid meme after ' . $retries . ' attempts'
|
'error' => 'Failed to generate valid meme after ' . $retries . ' attempts',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +146,6 @@ public static function generateBackgroundMediaWithRunware($prompt)
|
|||||||
$media_height = 1024;
|
$media_height = 1024;
|
||||||
$aspect_ratio = AspectRatio::get($media_width, $media_height);
|
$aspect_ratio = AspectRatio::get($media_width, $media_height);
|
||||||
|
|
||||||
|
|
||||||
$runware_output_url = RunwareAI::generateSchnellImage(Str::uuid(), $prompt, $media_width, $media_height);
|
$runware_output_url = RunwareAI::generateSchnellImage(Str::uuid(), $prompt, $media_width, $media_height);
|
||||||
|
|
||||||
$media = MediaEngine::addMedia(
|
$media = MediaEngine::addMedia(
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Helpers\FirstParty\MediaEngine\MediaEngine;
|
|
||||||
use App\Helpers\FirstParty\Meme\MemeGenerator;
|
use App\Helpers\FirstParty\Meme\MemeGenerator;
|
||||||
use App\Models\BackgroundMedia;
|
use App\Models\BackgroundMedia;
|
||||||
use App\Models\Meme;
|
use App\Models\Meme;
|
||||||
@@ -15,12 +14,14 @@ public function init(Request $request)
|
|||||||
{
|
{
|
||||||
$meme = Meme::with('meme_media', 'background_media')->where('status', MemeGenerator::STATUS_COMPLETED)->take(1)->latest()->first();
|
$meme = Meme::with('meme_media', 'background_media')->where('status', MemeGenerator::STATUS_COMPLETED)->take(1)->latest()->first();
|
||||||
|
|
||||||
|
$meme_media = MemeGenerator::getSuitableMeme($meme);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => [
|
'success' => [
|
||||||
'data' => [
|
'data' => [
|
||||||
'init' => [
|
'init' => [
|
||||||
'caption' => $meme->caption,
|
'caption' => $meme->caption,
|
||||||
'meme' => $meme->meme_media,
|
'meme' => $meme_media,
|
||||||
'background' => $meme->background_media,
|
'background' => $meme->background_media,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -2,15 +2,12 @@
|
|||||||
|
|
||||||
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\AspectRatio;
|
||||||
use App\Helpers\FirstParty\Meme\MemeGenerator;
|
use App\Helpers\FirstParty\Meme\MemeGenerator;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
use App\Models\Meme;
|
|
||||||
use App\Models\MemeMedia;
|
use App\Models\MemeMedia;
|
||||||
use Pgvector\Laravel\Distance;
|
|
||||||
use Str;
|
use Str;
|
||||||
|
|
||||||
class TestController extends Controller
|
class TestController extends Controller
|
||||||
@@ -20,6 +17,23 @@ public function index()
|
|||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMemeKeywords()
|
||||||
|
{
|
||||||
|
|
||||||
|
$meme_media = MemeMedia::whereNull('action_keywords')->first();
|
||||||
|
|
||||||
|
$meme_keywords_response = OpenAI::getMemeKeywords($meme_media->name, $meme_media->description);
|
||||||
|
|
||||||
|
$meme_keywords_output = json_decode(OpenAI::getOpenAIOutput($meme_keywords_response));
|
||||||
|
|
||||||
|
$meme_media->description = $meme_keywords_output->description;
|
||||||
|
$meme_media->action_keywords = $meme_keywords_output->action_keywords;
|
||||||
|
$meme_media->emotion_keywords = $meme_keywords_output->emotion_keywords;
|
||||||
|
$meme_media->misc_keywords = $meme_keywords_output->misc_keywords;
|
||||||
|
|
||||||
|
$meme_media->save();
|
||||||
|
}
|
||||||
|
|
||||||
public function aspectRatio()
|
public function aspectRatio()
|
||||||
{
|
{
|
||||||
$aspect_ratio = AspectRatio::get(1024, 1024);
|
$aspect_ratio = AspectRatio::get(1024, 1024);
|
||||||
|
|||||||
@@ -67,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']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,12 +30,10 @@
|
|||||||
* @property string $payload
|
* @property string $payload
|
||||||
* @property Carbon|null $created_at
|
* @property Carbon|null $created_at
|
||||||
* @property Carbon|null $updated_at
|
* @property Carbon|null $updated_at
|
||||||
*
|
|
||||||
* @package App\Models
|
|
||||||
*/
|
*/
|
||||||
class Category extends Model
|
class Category extends Model
|
||||||
{
|
{
|
||||||
use NodeTrait, HasTags, HasNeighbors;
|
use HasNeighbors, HasTags, NodeTrait;
|
||||||
|
|
||||||
protected $table = 'categories';
|
protected $table = 'categories';
|
||||||
|
|
||||||
@@ -62,7 +60,7 @@ class Category extends Model
|
|||||||
'meme_angles',
|
'meme_angles',
|
||||||
'sample_captions',
|
'sample_captions',
|
||||||
'keywords',
|
'keywords',
|
||||||
'payload'
|
'payload',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,12 +29,10 @@
|
|||||||
* @property Carbon|null $created_at
|
* @property Carbon|null $created_at
|
||||||
* @property Carbon|null $updated_at
|
* @property Carbon|null $updated_at
|
||||||
* @property string|null $deleted_at
|
* @property string|null $deleted_at
|
||||||
*
|
|
||||||
* @package App\Models
|
|
||||||
*/
|
*/
|
||||||
class Meme extends Model
|
class Meme extends Model
|
||||||
{
|
{
|
||||||
use SoftDeletes, HasTags;
|
use HasTags, SoftDeletes;
|
||||||
|
|
||||||
protected $table = 'memes';
|
protected $table = 'memes';
|
||||||
|
|
||||||
@@ -49,6 +47,10 @@ class Meme extends Model
|
|||||||
|
|
||||||
'meme_keywords' => 'array',
|
'meme_keywords' => 'array',
|
||||||
'keywords' => 'array',
|
'keywords' => 'array',
|
||||||
|
|
||||||
|
'action_keywords' => 'array',
|
||||||
|
'emotion_keywords' => 'array',
|
||||||
|
'misc_keywords' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
@@ -63,7 +65,10 @@ class Meme extends Model
|
|||||||
'caption',
|
'caption',
|
||||||
'meme_keywords',
|
'meme_keywords',
|
||||||
'background',
|
'background',
|
||||||
'keywords'
|
'keywords',
|
||||||
|
'action_keywords',
|
||||||
|
'emotion_keywords',
|
||||||
|
'misc_keywords',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function meme_media()
|
public function meme_media()
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
*/
|
*/
|
||||||
class MemeMedia extends Model
|
class MemeMedia extends Model
|
||||||
{
|
{
|
||||||
use HasNeighbors, SoftDeletes, HasTags;
|
use HasNeighbors, HasTags, SoftDeletes;
|
||||||
|
|
||||||
protected $table = 'meme_medias';
|
protected $table = 'meme_medias';
|
||||||
|
|
||||||
@@ -43,6 +43,9 @@ class MemeMedia extends Model
|
|||||||
'duration' => 'double',
|
'duration' => 'double',
|
||||||
'keywords' => 'array',
|
'keywords' => 'array',
|
||||||
'is_enabled' => 'boolean',
|
'is_enabled' => 'boolean',
|
||||||
|
'action_keywords' => 'array',
|
||||||
|
'emotion_keywords' => 'array',
|
||||||
|
'misc_keywords' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
@@ -61,6 +64,9 @@ class MemeMedia extends Model
|
|||||||
'gif_url',
|
'gif_url',
|
||||||
'webp_url',
|
'webp_url',
|
||||||
'embedding',
|
'embedding',
|
||||||
|
'action_keywords',
|
||||||
|
'emotion_keywords',
|
||||||
|
'misc_keywords',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
@@ -88,7 +94,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']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,10 @@
|
|||||||
|
|
||||||
'runware' => [
|
'runware' => [
|
||||||
'api_key' => env('RUNWARE_API_KEY'),
|
'api_key' => env('RUNWARE_API_KEY'),
|
||||||
]
|
],
|
||||||
|
|
||||||
|
'openai' => [
|
||||||
|
'api_key' => env('OPENAI_API_KEY'),
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -24,5 +24,5 @@
|
|||||||
* The fully qualified class name of the pivot model.
|
* The fully qualified class name of the pivot model.
|
||||||
*/
|
*/
|
||||||
'class_name' => Illuminate\Database\Eloquent\Relations\MorphPivot::class,
|
'class_name' => Illuminate\Database\Eloquent\Relations\MorphPivot::class,
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ public function up(): void
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverse the migrations.
|
* Reverse the migrations.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?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->json('action_keywords')->nullable();
|
||||||
|
$table->json('emotion_keywords')->nullable();
|
||||||
|
$table->json('misc_keywords')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('meme_medias', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('action_keywords');
|
||||||
|
$table->dropColumn('emotion_keywords');
|
||||||
|
$table->dropColumn('misc_keywords');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?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('memes', function (Blueprint $table) {
|
||||||
|
$table->json('action_keywords')->nullable();
|
||||||
|
$table->json('emotion_keywords')->nullable();
|
||||||
|
$table->json('misc_keywords')->nullable();
|
||||||
|
$table->string('primary_keyword_type')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('memes', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('action_keywords');
|
||||||
|
$table->dropColumn('emotion_keywords');
|
||||||
|
$table->dropColumn('misc_keywords');
|
||||||
|
$table->dropColumn('primary_keyword_type');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -3,9 +3,8 @@
|
|||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Helpers\FirstParty\AI\CloudflareAI;
|
use App\Helpers\FirstParty\AI\CloudflareAI;
|
||||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
@@ -20,19 +19,20 @@ public function run(): void
|
|||||||
$jsonPath = database_path('seeders/data/json/category');
|
$jsonPath = database_path('seeders/data/json/category');
|
||||||
|
|
||||||
// Check if directory exists
|
// Check if directory exists
|
||||||
if (!File::exists($jsonPath)) {
|
if (! File::exists($jsonPath)) {
|
||||||
$this->command->error("JSON directory not found: {$jsonPath}");
|
$this->command->error("JSON directory not found: {$jsonPath}");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all JSON files except the schema file
|
// Get all JSON files except the schema file
|
||||||
$jsonFiles = File::glob($jsonPath . '/*.json');
|
$jsonFiles = File::glob($jsonPath.'/*.json');
|
||||||
$jsonFiles = array_filter($jsonFiles, function ($file) {
|
$jsonFiles = array_filter($jsonFiles, function ($file) {
|
||||||
return !str_contains(basename($file), 'schema');
|
return ! str_contains(basename($file), 'schema');
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->command->info('Starting to seed categories from JSON files...');
|
$this->command->info('Starting to seed categories from JSON files...');
|
||||||
$this->command->info('Found ' . count($jsonFiles) . ' JSON files to process.');
|
$this->command->info('Found '.count($jsonFiles).' JSON files to process.');
|
||||||
|
|
||||||
foreach ($jsonFiles as $jsonFile) {
|
foreach ($jsonFiles as $jsonFile) {
|
||||||
$this->processJsonFile($jsonFile);
|
$this->processJsonFile($jsonFile);
|
||||||
@@ -55,36 +55,40 @@ private function processJsonFile(string $filePath): void
|
|||||||
$data = json_decode($jsonContent, true);
|
$data = json_decode($jsonContent, true);
|
||||||
|
|
||||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
$this->command->error("Invalid JSON in file: {$fileName} - " . json_last_error_msg());
|
$this->command->error("Invalid JSON in file: {$fileName} - ".json_last_error_msg());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate JSON structure
|
// Validate JSON structure
|
||||||
if (!isset($data['category'])) {
|
if (! isset($data['category'])) {
|
||||||
$this->command->error("Missing 'category' key in file: {$fileName}");
|
$this->command->error("Missing 'category' key in file: {$fileName}");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$categoryData = $data['category'];
|
$categoryData = $data['category'];
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
if (!isset($categoryData['name']) || !isset($categoryData['description'])) {
|
if (! isset($categoryData['name']) || ! isset($categoryData['description'])) {
|
||||||
$this->command->error("Missing required fields (name/description) in file: {$fileName}");
|
$this->command->error("Missing required fields (name/description) in file: {$fileName}");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create main category
|
// Create main category
|
||||||
$mainCategory = $this->createMainCategory($categoryData, $data);
|
$mainCategory = $this->createMainCategory($categoryData, $data);
|
||||||
|
|
||||||
if (!$mainCategory) {
|
if (! $mainCategory) {
|
||||||
$this->command->error("Failed to create main category for file: {$fileName}");
|
$this->command->error("Failed to create main category for file: {$fileName}");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create subcategories
|
// Create subcategories
|
||||||
if (isset($categoryData['subcategories']) && is_array($categoryData['subcategories'])) {
|
if (isset($categoryData['subcategories']) && is_array($categoryData['subcategories'])) {
|
||||||
foreach ($categoryData['subcategories'] as $index => $subcategoryData) {
|
foreach ($categoryData['subcategories'] as $index => $subcategoryData) {
|
||||||
if (!$this->createSubcategory($subcategoryData, $mainCategory, $data, $index)) {
|
if (! $this->createSubcategory($subcategoryData, $mainCategory, $data, $index)) {
|
||||||
$this->command->warn("Failed to create subcategory at index {$index} for file: {$fileName}");
|
$this->command->warn("Failed to create subcategory at index {$index} for file: {$fileName}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,10 +96,10 @@ private function processJsonFile(string $filePath): void
|
|||||||
|
|
||||||
$this->command->info("✓ Successfully processed: {$fileName}");
|
$this->command->info("✓ Successfully processed: {$fileName}");
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->command->error("Error processing {$fileName}: " . $e->getMessage());
|
$this->command->error("Error processing {$fileName}: ".$e->getMessage());
|
||||||
Log::error("CategorySeeder error for {$fileName}", [
|
Log::error("CategorySeeder error for {$fileName}", [
|
||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
'trace' => $e->getTraceAsString()
|
'trace' => $e->getTraceAsString(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,6 +117,7 @@ private function createMainCategory(array $categoryData, array $originalData): ?
|
|||||||
|
|
||||||
if ($existingCategory) {
|
if ($existingCategory) {
|
||||||
$this->command->warn("Main category '{$categoryData['name']}' already exists. Skipping...");
|
$this->command->warn("Main category '{$categoryData['name']}' already exists. Skipping...");
|
||||||
|
|
||||||
return $existingCategory;
|
return $existingCategory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +131,7 @@ private function createMainCategory(array $categoryData, array $originalData): ?
|
|||||||
'meme_angles' => null, // Main categories don't have meme_angles
|
'meme_angles' => null, // Main categories don't have meme_angles
|
||||||
'sample_captions' => null, // Main categories don't have sample_captions
|
'sample_captions' => null, // Main categories don't have sample_captions
|
||||||
'payload' => $originalData,
|
'payload' => $originalData,
|
||||||
'embedding' => CloudflareAI::getVectorEmbeddingBgeSmall($categoryData['name'] . " " . $categoryData['description']),
|
'embedding' => CloudflareAI::getVectorEmbeddingBgeSmall($categoryData['name'].' '.$categoryData['description']),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Add keywords as tags
|
// Add keywords as tags
|
||||||
@@ -138,11 +143,12 @@ private function createMainCategory(array $categoryData, array $originalData): ?
|
|||||||
|
|
||||||
return $category;
|
return $category;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->command->error("Error creating main category: " . $e->getMessage());
|
$this->command->error('Error creating main category: '.$e->getMessage());
|
||||||
Log::error("Error creating main category", [
|
Log::error('Error creating main category', [
|
||||||
'category_data' => $categoryData,
|
'category_data' => $categoryData,
|
||||||
'error' => $e->getMessage()
|
'error' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,8 +160,9 @@ private function createSubcategory(array $subcategoryData, Category $parentCateg
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Validate required subcategory fields
|
// Validate required subcategory fields
|
||||||
if (!isset($subcategoryData['name']) || !isset($subcategoryData['description'])) {
|
if (! isset($subcategoryData['name']) || ! isset($subcategoryData['description'])) {
|
||||||
$this->command->warn("Subcategory at index {$index} missing required fields (name/description). Skipping...");
|
$this->command->warn("Subcategory at index {$index} missing required fields (name/description). Skipping...");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +173,7 @@ private function createSubcategory(array $subcategoryData, Category $parentCateg
|
|||||||
|
|
||||||
if ($existingSubcategory) {
|
if ($existingSubcategory) {
|
||||||
$this->command->warn(" Subcategory '{$subcategoryData['name']}' already exists. Skipping...");
|
$this->command->warn(" Subcategory '{$subcategoryData['name']}' already exists. Skipping...");
|
||||||
|
|
||||||
return $existingSubcategory;
|
return $existingSubcategory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,8 +182,8 @@ private function createSubcategory(array $subcategoryData, Category $parentCateg
|
|||||||
'subcategory' => $subcategoryData,
|
'subcategory' => $subcategoryData,
|
||||||
'parent_category' => [
|
'parent_category' => [
|
||||||
'name' => $parentCategory->name,
|
'name' => $parentCategory->name,
|
||||||
'description' => $parentCategory->description
|
'description' => $parentCategory->description,
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
// Create the subcategory using the correct nested set method
|
// Create the subcategory using the correct nested set method
|
||||||
@@ -189,7 +197,7 @@ private function createSubcategory(array $subcategoryData, Category $parentCateg
|
|||||||
'subcategories' => null, // Subcategories don't have subcategories
|
'subcategories' => null, // Subcategories don't have subcategories
|
||||||
'payload' => $subcategoryPayload,
|
'payload' => $subcategoryPayload,
|
||||||
'parent_id' => $parentCategory->id, // Set parent_id directly
|
'parent_id' => $parentCategory->id, // Set parent_id directly
|
||||||
'embedding' => CloudflareAI::getVectorEmbeddingBgeSmall($subcategoryData['name'] . " " . $subcategoryData['description']),
|
'embedding' => CloudflareAI::getVectorEmbeddingBgeSmall($subcategoryData['name'].' '.$subcategoryData['description']),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Add keywords as tags
|
// Add keywords as tags
|
||||||
@@ -201,12 +209,13 @@ private function createSubcategory(array $subcategoryData, Category $parentCateg
|
|||||||
|
|
||||||
return $subcategory;
|
return $subcategory;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->command->error("Error creating subcategory at index {$index}: " . $e->getMessage());
|
$this->command->error("Error creating subcategory at index {$index}: ".$e->getMessage());
|
||||||
Log::error("Error creating subcategory", [
|
Log::error('Error creating subcategory', [
|
||||||
'subcategory_data' => $subcategoryData,
|
'subcategory_data' => $subcategoryData,
|
||||||
'parent_id' => $parentCategory->id,
|
'parent_id' => $parentCategory->id,
|
||||||
'error' => $e->getMessage()
|
'error' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,11 +228,11 @@ private function attachKeywordsAsTags(Category $category, array $keywords): void
|
|||||||
try {
|
try {
|
||||||
$category->attachTags($keywords, 'category');
|
$category->attachTags($keywords, 'category');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->command->warn("Failed to attach tags to category '{$category->name}': " . $e->getMessage());
|
$this->command->warn("Failed to attach tags to category '{$category->name}': ".$e->getMessage());
|
||||||
Log::warning("Failed to attach tags", [
|
Log::warning('Failed to attach tags', [
|
||||||
'category_id' => $category->id,
|
'category_id' => $category->id,
|
||||||
'keywords' => $keywords,
|
'keywords' => $keywords,
|
||||||
'error' => $e->getMessage()
|
'error' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,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;
|
||||||
@@ -60,12 +60,10 @@ 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']);
|
$meme_record['keywords'] = $this->stringToCleanArray($meme_record['keywords']);
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check for duplicates OUTSIDE of transaction
|
// Check for duplicates OUTSIDE of transaction
|
||||||
$base_filename = pathinfo($meme_record['filename'], PATHINFO_FILENAME);
|
$base_filename = pathinfo($meme_record['filename'], PATHINFO_FILENAME);
|
||||||
@@ -158,6 +156,7 @@ private function stringToCleanArray($string)
|
|||||||
return array_filter(array_map(function ($item) {
|
return array_filter(array_map(function ($item) {
|
||||||
$item = trim($item); // Remove whitespace
|
$item = trim($item); // Remove whitespace
|
||||||
$item = preg_replace('/[^\w\s]/', '', $item); // Remove punctuation
|
$item = preg_replace('/[^\w\s]/', '', $item); // Remove punctuation
|
||||||
|
|
||||||
return trim(preg_replace('/\s+/', ' ', $item)); // Clean extra spaces
|
return trim(preg_replace('/\s+/', ' ', $item)); // Clean extra spaces
|
||||||
}, explode(',', $string)), function ($value) {
|
}, explode(',', $string)), function ($value) {
|
||||||
return $value !== '';
|
return $value !== '';
|
||||||
@@ -192,13 +191,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;
|
||||||
@@ -208,7 +207,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'] . ' ' . implode(' ', $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()}");
|
||||||
@@ -256,7 +255,7 @@ private function importSingleMeme(array $meme_record): bool
|
|||||||
// Add keywords as tags
|
// Add keywords as tags
|
||||||
$this->attachKeywordsAsTags($meme_media, $meme_record['keywords']);
|
$this->attachKeywordsAsTags($meme_media, $meme_record['keywords']);
|
||||||
|
|
||||||
$this->command->info('✅ Imported: ' . trim($meme_record['name']));
|
$this->command->info('✅ Imported: '.trim($meme_record['name']));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
@@ -270,11 +269,11 @@ private function attachKeywordsAsTags(MemeMedia $meme_media, array $keywords): v
|
|||||||
try {
|
try {
|
||||||
$meme_media->attachTags($keywords, 'meme_media');
|
$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', [
|
||||||
'category_id' => $meme_media->id,
|
'category_id' => $meme_media->id,
|
||||||
'keywords' => $keywords,
|
'keywords' => $keywords,
|
||||||
'error' => $e->getMessage()
|
'error' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,7 +283,7 @@ private function attachKeywordsAsTags(MemeMedia $meme_media, array $keywords): v
|
|||||||
*/
|
*/
|
||||||
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}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useMitt } from '@/plugins/MittContext';
|
import { useMitt } from '@/plugins/MittContext';
|
||||||
import useVideoEditorStore from '@/stores/VideoEditorStore';
|
import useVideoEditorStore from '@/stores/VideoEditorStore';
|
||||||
import { Download, Edit3, Play, Square, Type } from 'lucide-react';
|
import { Download, Edit3, Play, Square } from 'lucide-react';
|
||||||
|
|
||||||
const EditorControls = ({ className = '', onEditClick = () => {}, isEditActive = false }) => {
|
const EditorControls = ({ className = '', onEditClick = () => {}, isEditActive = false }) => {
|
||||||
const { videoIsPlaying } = useVideoEditorStore();
|
const { videoIsPlaying } = useVideoEditorStore();
|
||||||
@@ -40,9 +40,9 @@ const EditorControls = ({ className = '', onEditClick = () => {}, isEditActive =
|
|||||||
<span className="text-sm font-medium ">9:16</span>
|
<span className="text-sm font-medium ">9:16</span>
|
||||||
</Button> */}
|
</Button> */}
|
||||||
|
|
||||||
<Button variant="outline" size="icon" className="h-12 w-12 rounded-full border shadow-sm">
|
{/* <Button variant="outline" size="icon" className="h-12 w-12 rounded-full border shadow-sm">
|
||||||
<Type className="h-8 w-8" />
|
<Type className="h-8 w-8" />
|
||||||
</Button>
|
</Button> */}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
id="edit"
|
id="edit"
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ export default function TextSidebar({ isOpen, onClose }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Sheet open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
<Sheet open={isOpen} onOpenChange={(open) => !open && onClose()}>
|
||||||
<SheetContent side="right" className="max-[140px] w-full overflow-y-auto dark:bg-neutral-900">
|
<SheetContent side="right" className="max-w-[300px] overflow-y-auto dark:bg-neutral-900">
|
||||||
<SheetHeader>
|
<SheetHeader>
|
||||||
<SheetTitle className="flex items-center gap-3">
|
<SheetTitle className="flex items-center gap-3">
|
||||||
<Type className="h-6 w-6" />
|
<Type className="h-6 w-6" />
|
||||||
@@ -201,11 +201,12 @@ export default function TextSidebar({ isOpen, onClose }) {
|
|||||||
value={textValue}
|
value={textValue}
|
||||||
onChange={handleTextChange}
|
onChange={handleTextChange}
|
||||||
placeholder="Enter your text..."
|
placeholder="Enter your text..."
|
||||||
className="mt-2 text-center text-nowrap dark:bg-neutral-800"
|
className="mx-auto mt-2 text-center text-wrap dark:bg-neutral-800"
|
||||||
rows={4}
|
rows={4}
|
||||||
style={{
|
style={{
|
||||||
|
maxWidth: 300,
|
||||||
fontFamily: fontFamily,
|
fontFamily: fontFamily,
|
||||||
fontSize: `${fontSize * 0.45}px`, // Cap preview size for readability
|
fontSize: `${fontSize * 0.5}px`, // Cap preview size for readability
|
||||||
fontWeight: isBold ? 'bold' : 'normal',
|
fontWeight: isBold ? 'bold' : 'normal',
|
||||||
fontStyle: isItalic ? 'italic' : 'normal',
|
fontStyle: isItalic ? 'italic' : 'normal',
|
||||||
color: fillColor,
|
color: fillColor,
|
||||||
|
|||||||
@@ -13,3 +13,5 @@
|
|||||||
Route::get('/generateMeme', [TestController::class, 'generateMeme']);
|
Route::get('/generateMeme', [TestController::class, 'generateMeme']);
|
||||||
|
|
||||||
Route::get('/aspectRatio', [TestController::class, 'aspectRatio']);
|
Route::get('/aspectRatio', [TestController::class, 'aspectRatio']);
|
||||||
|
|
||||||
|
Route::get('/getMemeKeywords', [TestController::class, 'getMemeKeywords']);
|
||||||
|
|||||||
Reference in New Issue
Block a user