optimizations

This commit is contained in:
2026-03-28 19:15:39 +01:00
parent 0b25d9570a
commit cab4fbd83e
509 changed files with 1016804 additions and 1605 deletions

View File

@@ -0,0 +1,210 @@
<?php
declare(strict_types=1);
namespace App\Services\NovaCards;
use App\Models\NovaCard;
use Illuminate\Support\Arr;
/**
* AI-assist hooks for Nova Cards v3.
*
* This service provides assistive suggestions it does NOT auto-publish
* or override the creator. All suggestions are optional and editable.
*
* Integration strategy: each suggest* method returns a plain array of
* suggestions. If an AI/ML backend is configured (via config/nova_cards.php),
* this service dispatches to it; otherwise it uses deterministic rule-based
* fallbacks. This keeps the system functional without a live AI dependency.
*/
class NovaCardAiAssistService
{
public function suggestTags(NovaCard $card): array
{
$text = implode(' ', array_filter([
$card->quote_text,
$card->quote_author,
$card->title,
$card->description,
]));
// Rule-based: extract meaningful words as tag suggestions.
// In production, replace/extend with an actual NLP/AI call.
$words = preg_split('/[\s\-_,\.]+/u', strtolower($text));
$stopWords = ['the', 'a', 'an', 'in', 'on', 'at', 'to', 'for', 'of', 'and', 'or', 'is', 'it', 'be', 'by', 'that', 'this', 'with', 'you', 'your', 'we', 'our', 'not'];
$filtered = array_values(array_filter(
array_unique($words ?? []),
fn ($w) => is_string($w) && mb_strlen($w) >= 4 && ! in_array($w, $stopWords, true),
));
return array_slice($filtered, 0, 6);
}
public function suggestMood(NovaCard $card): array
{
$text = strtolower((string) $card->quote_text . ' ' . (string) $card->title);
$moods = [];
$moodMap = [
'love|heart|romance|kiss|tender' => 'romantic',
'dark|shadow|night|alone|silence|void|lost' => 'dark-poetry',
'inspire|hope|dream|believe|courage|strength|rise' => 'inspirational',
'morning|sunrise|calm|peace|gentle|soft|breeze' => 'soft-morning',
'minimal|simple|quiet|still|breath|moment' => 'minimal',
'power|bold|fierce|fire|warrior|fight|hustle' => 'motivational',
'cyber|neon|digital|code|matrix|tech|signal' => 'cyber-mood',
];
foreach ($moodMap as $pattern => $mood) {
if (preg_match('/(' . $pattern . ')/i', $text)) {
$moods[] = $mood;
}
}
return array_slice(array_unique($moods), 0, 3);
}
public function suggestLayout(NovaCard $card): array
{
$project = is_array($card->project_json) ? $card->project_json : [];
$quoteLength = mb_strlen((string) $card->quote_text);
$hasAuthor = ! empty($card->quote_author);
// Heuristic layout suggestions based on text content.
$suggestions = [];
if ($quoteLength < 80) {
$suggestions[] = [
'layout' => 'quote_centered',
'reason' => 'Short quotes work well centered with generous padding.',
];
} elseif ($quoteLength < 200) {
$suggestions[] = [
'layout' => 'quote_heavy',
'reason' => 'Medium quotes benefit from a focused heavy layout.',
];
} else {
$suggestions[] = [
'layout' => 'editorial',
'reason' => 'Longer quotes fit editorial multi-column layouts.',
];
}
if ($hasAuthor) {
$suggestions[] = [
'layout' => 'byline_bottom',
'reason' => 'With an author, a bottom byline anchors attribution cleanly.',
];
}
return $suggestions;
}
public function suggestBackground(NovaCard $card): array
{
$moods = $this->suggestMood($card);
$suggestions = [];
$moodGradientMap = [
'romantic' => ['gradient_preset' => 'rose-poetry', 'reason' => 'Warm rose tones suit romantic content.'],
'dark-poetry' => ['gradient_preset' => 'midnight-nova', 'reason' => 'Deep dark gradients amplify dark poetry vibes.'],
'inspirational' => ['gradient_preset' => 'golden-hour', 'reason' => 'Warm golden tones elevate inspirational messages.'],
'soft-morning' => ['gradient_preset' => 'soft-dawn', 'reason' => 'Gentle pastels suit morning or calm content.'],
'minimal' => ['gradient_preset' => 'carbon-minimal', 'reason' => 'Clean dark or light neutrals suit minimal style.'],
'motivational' => ['gradient_preset' => 'bold-fire', 'reason' => 'Bold warm gradients energise motivational content.'],
'cyber-mood' => ['gradient_preset' => 'cyber-pulse', 'reason' => 'Electric neon gradients suit cyber aesthetic.'],
];
foreach ($moods as $mood) {
if (isset($moodGradientMap[$mood])) {
$suggestions[] = $moodGradientMap[$mood];
}
}
// Default if no match.
if (empty($suggestions)) {
$suggestions[] = ['gradient_preset' => 'midnight-nova', 'reason' => 'A versatile dark gradient that works for most content.'];
}
return $suggestions;
}
public function suggestFontPairing(NovaCard $card): array
{
$moods = $this->suggestMood($card);
$moodFontMap = [
'romantic' => ['font_preset' => 'romantic-serif', 'reason' => 'Elegant serif pairs beautifully with romantic content.'],
'dark-poetry' => ['font_preset' => 'dark-poetic', 'reason' => 'Strong contrast serif pairs with dark poetry style.'],
'inspirational' => ['font_preset' => 'modern-sans', 'reason' => 'Clean modern sans feels energising and clear.'],
'soft-morning' => ['font_preset' => 'soft-rounded', 'reason' => 'Rounded type has warmth and approachability.'],
'minimal' => ['font_preset' => 'minimal-mono', 'reason' => 'Monospaced type enforces a minimalist aesthetic.'],
'cyber-mood' => ['font_preset' => 'cyber-display', 'reason' => 'Tech display fonts suit cyber and digital themes.'],
];
foreach ($moods as $mood) {
if (isset($moodFontMap[$mood])) {
return [$moodFontMap[$mood]];
}
}
return [['font_preset' => 'modern-sans', 'reason' => 'A clean, versatile sans-serif for most content.']];
}
public function suggestReadabilityFixes(NovaCard $card): array
{
$issues = [];
$project = is_array($card->project_json) ? $card->project_json : [];
$textColor = Arr::get($project, 'typography.text_color', '#ffffff');
$bgType = Arr::get($project, 'background.type', 'gradient');
$overlayStyle = Arr::get($project, 'background.overlay_style', 'dark-soft');
$quoteSize = (int) Arr::get($project, 'typography.quote_size', 72);
$lineHeight = (float) Arr::get($project, 'typography.line_height', 1.2);
// Detect light text without overlay on upload background.
if ($bgType === 'upload' && in_array($overlayStyle, ['none', 'minimal'], true)) {
$issues[] = [
'field' => 'background.overlay_style',
'message' => 'Adding a dark overlay improves text legibility on photo backgrounds.',
'suggestion' => 'dark-soft',
];
}
// Detect very small quote text.
if ($quoteSize < 40) {
$issues[] = [
'field' => 'typography.quote_size',
'message' => 'Quote text may be too small to read on mobile.',
'suggestion' => 52,
];
}
// Detect very tight line height on long text.
if ($lineHeight < 1.1 && mb_strlen((string) $card->quote_text) > 100) {
$issues[] = [
'field' => 'typography.line_height',
'message' => 'Increasing line height improves readability for longer quotes.',
'suggestion' => 1.3,
];
}
return $issues;
}
/**
* Return all suggestions in one call, for the AI assist panel.
*/
public function allSuggestions(NovaCard $card): array
{
return [
'tags' => $this->suggestTags($card),
'moods' => $this->suggestMood($card),
'layouts' => $this->suggestLayout($card),
'backgrounds' => $this->suggestBackground($card),
'font_pairings' => $this->suggestFontPairing($card),
'readability_fixes' => $this->suggestReadabilityFixes($card),
];
}
}