refactor: optimize platform enum and enhance AI content generator prompts with localization updates
This commit is contained in:
parent
028fe1fefd
commit
e6efa8410e
9 changed files with 102 additions and 4 deletions
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
namespace App\Ai\Agents;
|
||||
|
||||
use App\Enums\PostPlatform\ContentType;
|
||||
use App\Models\Workspace;
|
||||
use App\Services\Ai\TemplateContextResolver;
|
||||
use Illuminate\Contracts\JsonSchema\JsonSchema;
|
||||
|
|
@ -42,6 +43,20 @@ public function instructions(): string
|
|||
->all();
|
||||
}
|
||||
|
||||
// Two budgets: the platform's HARD cap (must never exceed — would break
|
||||
// publishing) and the recommended SWEET SPOT length (what well-performing
|
||||
// posts actually look like, way below the hard cap on most platforms).
|
||||
$hardMaxChars = null;
|
||||
$targetChars = null;
|
||||
$platformLabel = null;
|
||||
$contentType = $this->platformContext ? ContentType::tryFrom($this->platformContext) : null;
|
||||
if ($contentType) {
|
||||
$platform = $contentType->platform();
|
||||
$hardMaxChars = $platform->maxContentLength();
|
||||
$targetChars = $platform->recommendedAiContentLength();
|
||||
$platformLabel = $platform->label();
|
||||
}
|
||||
|
||||
return view('prompts.post_content.generator', [
|
||||
'brand_name' => $this->workspace->name ?? '',
|
||||
'brand_description' => $this->workspace->brand_description ?? '',
|
||||
|
|
@ -52,6 +67,9 @@ public function instructions(): string
|
|||
'format' => $this->format,
|
||||
'slide_count' => $this->slideCount,
|
||||
'examples' => $examples,
|
||||
'hard_max_chars' => $hardMaxChars,
|
||||
'target_chars' => $targetChars,
|
||||
'platform_label' => $platformLabel,
|
||||
])->render();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,13 +88,31 @@ public function maxImages(): int
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hard cap (in characters) the platform's API will accept. Going over this
|
||||
* means the post can't be published. Values are the documented API maxes:
|
||||
*
|
||||
* - LinkedIn UGC: 3000 (`commentary` field)
|
||||
* - X standard tweet: 280 (X Premium accepts 25K — ignored, conservative)
|
||||
* - TikTok caption: 2200
|
||||
* - YouTube Shorts: title=100, description=5000. We feed `content` to both
|
||||
* (publisher derives title from the first line via `buildTitle`), and
|
||||
* Shorts UX only shows ~100 chars before "more" — capping at 100 keeps
|
||||
* posts appropriate for the format.
|
||||
* - Facebook text status: 63206
|
||||
* - Instagram feed caption: 2200
|
||||
* - Threads: 500
|
||||
* - Pinterest pin description: 800 (title is 100, not modeled here)
|
||||
* - Bluesky: 300 graphemes
|
||||
* - Mastodon: 500 default; instances may be higher (we stay conservative)
|
||||
*/
|
||||
public function maxContentLength(): int
|
||||
{
|
||||
return match ($this) {
|
||||
self::LinkedIn, self::LinkedInPage => 3000,
|
||||
self::X => 280,
|
||||
self::TikTok => 2200,
|
||||
self::YouTube => 5000,
|
||||
self::YouTube => 100,
|
||||
self::Facebook => 63206,
|
||||
self::Instagram, self::InstagramFacebook => 2200,
|
||||
self::Threads => 500,
|
||||
|
|
@ -104,6 +122,36 @@ public function maxContentLength(): int
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Recommended target length (in characters) for AI-generated posts. This
|
||||
* is the engagement sweet spot — much shorter than the platform's hard
|
||||
* `maxContentLength()`. Use this to instruct the LLM at generation time;
|
||||
* use `maxContentLength()` for publish-time validation.
|
||||
*/
|
||||
public function recommendedAiContentLength(): int
|
||||
{
|
||||
return match ($this) {
|
||||
// Microblogging — 70-200 char tweets perform best, leave hashtag room
|
||||
self::X, self::Bluesky => 220,
|
||||
// Threads/Mastodon — similar feel, slightly more relaxed
|
||||
self::Threads, self::Mastodon => 300,
|
||||
// LinkedIn — readable long-form sweet spot is ~1200-1500
|
||||
self::LinkedIn, self::LinkedInPage => 1200,
|
||||
// Instagram captions — most viewers expand only when interested,
|
||||
// 100-150 words performs best
|
||||
self::Instagram, self::InstagramFacebook => 600,
|
||||
// Facebook — short posts dominate the algorithm
|
||||
self::Facebook => 280,
|
||||
// Pinterest pin description — image does the work, keep it tight
|
||||
self::Pinterest => 200,
|
||||
// TikTok caption — the video carries the story
|
||||
self::TikTok => 150,
|
||||
// YouTube Shorts — fits within the 100-char title (with " #Shorts"
|
||||
// suffix taking 8 chars) so the same string works as title + desc
|
||||
self::YouTube => 80,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string>
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -459,6 +459,13 @@
|
|||
'account_inactive' => 'Social account is deactivated',
|
||||
],
|
||||
|
||||
'delete' => [
|
||||
'title' => 'Delete post?',
|
||||
'description' => 'This action can\'t be undone. The post and all its media will be permanently removed.',
|
||||
'confirm' => 'Yes, delete',
|
||||
'cancel' => 'Cancel',
|
||||
],
|
||||
|
||||
'create' => [
|
||||
'title' => 'Create a new post',
|
||||
'description' => 'Choose how you want to start.',
|
||||
|
|
|
|||
|
|
@ -472,6 +472,13 @@
|
|||
'account_inactive' => 'Cuenta social desactivada',
|
||||
],
|
||||
|
||||
'delete' => [
|
||||
'title' => '¿Eliminar post?',
|
||||
'description' => 'Esta acción no se puede deshacer. El post y todos sus archivos multimedia se eliminarán de forma permanente.',
|
||||
'confirm' => 'Sí, eliminar',
|
||||
'cancel' => 'Cancelar',
|
||||
],
|
||||
|
||||
'create' => [
|
||||
'title' => 'Crear nuevo post',
|
||||
'description' => 'Elige cómo quieres empezar.',
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -472,6 +472,13 @@
|
|||
'account_inactive' => 'Conta social está desativada',
|
||||
],
|
||||
|
||||
'delete' => [
|
||||
'title' => 'Excluir post?',
|
||||
'description' => 'Esta ação não pode ser desfeita. O post e todas as suas mídias serão removidos permanentemente.',
|
||||
'confirm' => 'Sim, excluir',
|
||||
'cancel' => 'Cancelar',
|
||||
],
|
||||
|
||||
'create' => [
|
||||
'title' => 'Criar novo post',
|
||||
'description' => 'Escolha como quer começar.',
|
||||
|
|
|
|||
|
|
@ -24,6 +24,17 @@
|
|||
- Avoid AI-clichés (testament, pivotal moment, emojis on every line, "Let's dive in").
|
||||
- Keep paragraphs short. Vary sentence rhythm.
|
||||
- If the user mentions a specific platform, follow that platform's typical conventions.
|
||||
@if(!empty($target_chars))
|
||||
|
||||
CRITICAL — length for {{ $platform_label ?? 'the target platform' }}:
|
||||
- Aim for around {{ $target_chars }} characters in the `content` field. This is the engagement sweet spot for this platform.
|
||||
- Hard cap (must NEVER exceed): {{ $hard_max_chars }} characters total — including spaces, line breaks, hashtags and emojis.
|
||||
- Going LONGER than ~{{ $target_chars }} chars hurts performance even if it fits the hard cap. High-performing posts on this platform are concise.
|
||||
- Count before responding. Pad nothing. Stop when you've said it.
|
||||
@if($target_chars <= 300)
|
||||
- This is a short-form platform: 1-2 punchy sentences, no fluff. Use line breaks sparingly.
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if(!empty($examples))
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue