From ed1967405ca0e3c35fb7fb6bde72633db173ab24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sun, 15 Feb 2026 13:08:57 +0900 Subject: [PATCH] =?UTF-8?q?feat:=ED=8A=B8=EB=A0=8C=EB=93=9C=20=ED=82=A4?= =?UTF-8?q?=EC=9B=8C=EB=93=9C=EB=A5=BC=20=EA=B1=B4=EA=B0=95=20=EC=B1=84?= =?UTF-8?q?=EB=84=90=EC=9A=A9=EC=9C=BC=EB=A1=9C=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 건강 채널 전용 트렌딩 시스템: - Gemini로 실시간 트렌드에서 건강 관련 키워드만 필터링 - 간접적 키워드도 건강 앵글로 리프레이밍 (예: 김치 → 장건강) - 필터 결과 30분 캐싱 (Gemini 호출 최소화) - 필터 실패 시 원본 키워드 폴백 제목 생성 건강 앵글 반영: - generateTrendingHookTitles 프롬프트에 건강 채널 명시 - trending_context에 health_angle, suggested_topic 추가 - 모든 제목이 건강/웰빙 관점으로 생성되도록 가이드 UI 건강 테마 적용: - 버튼/칩 색상: orange/indigo → green 테마 - 칩에 건강 앵글 태그 배지 표시 - 칩 클릭 시 건강 주제(suggested_topic)가 인풋에 채워짐 Co-Authored-By: Claude Opus 4.6 --- app/Http/Controllers/Video/Veo3Controller.php | 12 ++- app/Services/Video/GeminiScriptService.php | 77 ++++++++++++++++++- resources/views/video/veo3/index.blade.php | 31 +++++--- 3 files changed, 105 insertions(+), 15 deletions(-) diff --git a/app/Http/Controllers/Video/Veo3Controller.php b/app/Http/Controllers/Video/Veo3Controller.php index a458dbab..eb132ca1 100644 --- a/app/Http/Controllers/Video/Veo3Controller.php +++ b/app/Http/Controllers/Video/Veo3Controller.php @@ -12,6 +12,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Support\Facades\Cache; use Illuminate\View\View; use Symfony\Component\HttpFoundation\BinaryFileResponse; @@ -36,11 +37,18 @@ public function index(Request $request): View|Response } /** - * 실시간 급상승 키워드 목록 + * 실시간 급상승 키워드 목록 (건강 채널용 필터링) */ public function fetchTrending(): JsonResponse { - $keywords = $this->trendingService->fetchTrendingKeywords(10); + $keywords = Cache::remember('health_trending', 1800, function () { + $rawKeywords = $this->trendingService->fetchTrendingKeywords(20); + + $healthKeywords = $this->geminiService->filterHealthTrending($rawKeywords); + + // Gemini 필터링 실패 시 원본 반환 + return ! empty($healthKeywords) ? $healthKeywords : $rawKeywords; + }); return response()->json([ 'success' => true, diff --git a/app/Services/Video/GeminiScriptService.php b/app/Services/Video/GeminiScriptService.php index 3a919657..9e67bf20 100644 --- a/app/Services/Video/GeminiScriptService.php +++ b/app/Services/Video/GeminiScriptService.php @@ -19,6 +19,73 @@ public function __construct(GoogleCloudService $googleCloud) $this->config = AiConfig::getActiveGemini(); } + /** + * 실시간 트렌딩 키워드 → 건강 채널용 필터링/리프레이밍 + */ + public function filterHealthTrending(array $trendingKeywords): array + { + if (empty($trendingKeywords)) { + return []; + } + + $keywordList = collect($trendingKeywords)->map(function ($item, $i) { + $news = ! empty($item['news_title']) ? " (뉴스: {$item['news_title']})" : ''; + + return ($i + 1) . ". {$item['keyword']}{$news}"; + })->implode("\n"); + + $prompt = <<callGemini($prompt); + + if (! $result) { + return []; + } + + $parsed = $this->parseJsonResponse($result); + + if (! $parsed || empty($parsed['keywords'])) { + return []; + } + + // 원본 트렌딩 데이터와 매칭하여 traffic 등 보존 + $trendingMap = collect($trendingKeywords)->keyBy('keyword'); + + return collect($parsed['keywords'])->map(function ($item) use ($trendingMap) { + $original = $trendingMap->get($item['keyword']); + + return [ + 'keyword' => $item['keyword'], + 'health_angle' => $item['health_angle'] ?? '', + 'suggested_topic' => $item['suggested_topic'] ?? $item['keyword'], + 'traffic' => $original['traffic'] ?? '', + 'news_title' => $original['news_title'] ?? '', + 'pub_date' => $original['pub_date'] ?? null, + ]; + })->values()->toArray(); + } + /** * 키워드 → 트렌딩 제목 5개 생성 (기본) */ @@ -68,21 +135,27 @@ public function generateTrendingHookTitles(string $keyword, array $context = []) if (! empty($context)) { $newsTitle = $context['news_title'] ?? ''; $traffic = $context['traffic'] ?? ''; + $healthAngle = $context['health_angle'] ?? ''; + $suggestedTopic = $context['suggested_topic'] ?? ''; $contextBlock = << { - setKeyword(item.keyword); + setKeyword(item.suggested_topic || item.keyword); setTrendingContext({ keyword: item.keyword, news_title: item.news_title, traffic: item.traffic, pub_date: item.pub_date, + health_angle: item.health_angle || '', + suggested_topic: item.suggested_topic || '', }); inputRef.current?.focus(); }; @@ -154,7 +156,7 @@

키워드를 입력하세요

-

AI가 트렌딩 YouTube Shorts 제목을 생성합니다

+

AI가 건강 채널용 YouTube Shorts 제목을 생성합니다

{/* Trending Button */} @@ -163,7 +165,7 @@ type="button" onClick={fetchTrending} disabled={trendingLoading} - className="inline-flex items-center gap-2 px-4 py-2 border border-orange-300 bg-orange-50 text-orange-700 rounded-lg text-sm font-medium hover:bg-orange-100 hover:border-orange-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" + className="inline-flex items-center gap-2 px-4 py-2 border border-green-300 bg-green-50 text-green-700 rounded-lg text-sm font-medium hover:bg-green-100 hover:border-green-400 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" > {trendingLoading ? ( <> @@ -172,8 +174,8 @@ className="inline-flex items-center gap-2 px-4 py-2 border border-orange-300 bg- ) : ( <> - 🔥 - 트렌드 주제 자동추출 + 💚 + 건강 트렌드 자동추출 )} @@ -187,7 +189,7 @@ className="inline-flex items-center gap-2 px-4 py-2 border border-orange-300 bg- {/* Trending Keyword Chips */} {trendingKeywords.length > 0 && (
-
Google 실시간 급상승 키워드 (클릭하여 선택)
+
💚 건강 트렌드 키워드 (클릭하여 선택)
{trendingKeywords.map((item, i) => { const isSelected = trendingContext?.keyword === item.keyword; @@ -198,13 +200,20 @@ className="inline-flex items-center gap-2 px-4 py-2 border border-orange-300 bg- onClick={() => selectTrending(item)} className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm transition-all cursor-pointer border ${ isSelected - ? 'bg-indigo-600 text-white border-indigo-600 shadow-sm' - : 'bg-white text-gray-700 border-gray-300 hover:border-indigo-400 hover:bg-indigo-50' + ? 'bg-green-600 text-white border-green-600 shadow-sm' + : 'bg-white text-gray-700 border-gray-300 hover:border-green-400 hover:bg-green-50' }`} > {item.keyword} - {item.traffic && ( - + {item.health_angle && ( + + {item.health_angle} + + )} + {!item.health_angle && item.traffic && ( + {item.traffic} )} @@ -222,7 +231,7 @@ className={`inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm tr value={keyword} onChange={(e) => { setKeyword(e.target.value); - if (trendingContext && e.target.value !== trendingContext.keyword) { + if (trendingContext && e.target.value !== (trendingContext.suggested_topic || trendingContext.keyword)) { setTrendingContext(null); } }}