From 9df3fcbcd39f381597f0aa562e3a1c8524094397 Mon Sep 17 00:00:00 2001 From: jaehwang Date: Wed, 25 Feb 2026 04:07:23 +0000 Subject: [PATCH] =?UTF-8?q?autoseo=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/SocialPostingModal.tsx | 462 ++++++++++++++------------ src/locales/en.json | 5 +- src/locales/ko.json | 5 +- src/types/api.ts | 12 + src/utils/api.ts | 16 + 5 files changed, 283 insertions(+), 217 deletions(-) diff --git a/src/components/SocialPostingModal.tsx b/src/components/SocialPostingModal.tsx index 373f55b..0d2c499 100644 --- a/src/components/SocialPostingModal.tsx +++ b/src/components/SocialPostingModal.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { getSocialAccounts, uploadToSocial, waitForUploadComplete, TokenExpiredError, handleSocialReconnect } from '../utils/api'; +import { getSocialAccounts, uploadToSocial, waitForUploadComplete, TokenExpiredError, handleSocialReconnect, getAutoSeoYoutube } from '../utils/api'; import { SocialAccount, VideoListItem, SocialUploadStatusResponse } from '../types/api'; import UploadProgressModal, { UploadStatus } from './UploadProgressModal'; @@ -43,6 +43,7 @@ const SocialPostingModal: React.FC = ({ const [isPosting, setIsPosting] = useState(false); const [isChannelDropdownOpen, setIsChannelDropdownOpen] = useState(false); const [isPrivacyDropdownOpen, setIsPrivacyDropdownOpen] = useState(false); + const [isLoadingAutoDescription, setIsLoadingAutoDescription] = useState(false); const channelDropdownRef = useRef(null); const privacyDropdownRef = useRef(null); @@ -75,12 +76,13 @@ const SocialPostingModal: React.FC = ({ useEffect(() => { if (isOpen) { loadSocialAccounts(); - // 비디오 정보로 기본 제목 설정 - if (video) { - const date = new Date(video.created_at); - const formattedDate = `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`; - setTitle(`${video.store_name} ${formattedDate}`); - } + loadAutocomplete(); + // // 비디오 정보로 기본 제목 설정 + // if (video) { + // const date = new Date(video.created_at); + // const formattedDate = `${date.getFullYear()}.${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`; + // setTitle(`${video.store_name} ${formattedDate}`); + // } } }, [isOpen, video]); @@ -105,6 +107,30 @@ const SocialPostingModal: React.FC = ({ } }; + const loadAutocomplete = async () => { + if (!video?.task_id) return; + + setIsLoadingAutoDescription(true); + try { + const requestPayload = { + task_id : video.task_id, + }; + // Call autoSEO API + console.log('[Upload] Request payload:', requestPayload); + const autoSeoResponse = await getAutoSeoYoutube(requestPayload); + + // 각 필드가 있을 때만 덮어씌움 (기존 값 보호) + if (autoSeoResponse.title) setTitle(autoSeoResponse.title); + if (autoSeoResponse.description) setDescription(autoSeoResponse.description); + if (autoSeoResponse.keywords) setTags(autoSeoResponse.keywords.join(',')); + } catch (error) { + console.error('Failed to load autocomplete:', error); + // 실패해도 사용자에게 별도 알림 없이 조용히 처리 + } finally { + setIsLoadingAutoDescription(false); + } + }; + const handlePost = async () => { if (!selectedChannel || !title.trim() || !video) { alert(t('social.channelAndTitleRequired')); @@ -251,248 +277,254 @@ const SocialPostingModal: React.FC = ({ return ( <> -
-
e.stopPropagation()}> - {/* Header */} -
-

{t('social.title')}

- -
- - {/* Content */} -
- {/* Left: Video Preview */} -
-
-
+
+
e.stopPropagation()}> + {/* Header */} +
+

{t('social.title')}

+
- {/* Right: Form */} -
- {/* Video Info */} -
- {t('social.postNumber')} - + + {/* Content */} +
+ {/* Left: Video Preview */} +
+
+
-
-

- {video.store_name} {new Date(video.created_at).toLocaleString('ko-KR')} -

-

{t('social.videoSpecs')}

-
+ {/* Right: Form */} +
+ {/* Video Info */} +
+ {t('social.postNumber')} + + +
- {/* Channel Selector - Custom Dropdown */} -
- - {isLoadingAccounts ? ( -
{t('social.loadingAccounts')}
- ) : socialAccounts.length === 0 ? ( -
-

{t('social.noAccounts')}

-

{t('social.noAccountsHint')}

-
- ) : ( -
+
+

+ {video.store_name} {new Date(video.created_at).toLocaleString('ko-KR')} +

+

{t('social.videoSpecs')}

+
+ + {/* Channel Selector - Custom Dropdown */} +
+ + {isLoadingAccounts ? ( +
{t('social.loadingAccounts')}
+ ) : socialAccounts.length === 0 ? ( +
+

{t('social.noAccounts')}

+

{t('social.noAccountsHint')}

+
+ ) : ( +
+ + {isChannelDropdownOpen && ( +
+ {socialAccounts.map(account => ( + + ))} +
+ )} +
+ )} +
+ + {/* Title */} +
+ + setTitle(e.target.value)} + placeholder={isLoadingAutoDescription ? t('social.autoSeoTitle') : t('social.postTitlePlaceholder')} + // placeholder={t('social.postTitlePlaceholder')} + className="social-posting-input" + maxLength={100} + disabled={isLoadingAutoDescription} + /> + {title.length}/100 +
+ + {/* Description */} +
+ +