From ad81f150a516dbdf855caf8e8c6717c7b0fd7f6b Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 10 Jul 2026 13:51:41 +0900 Subject: [PATCH] =?UTF-8?q?refactor(insta):=20Trends=203=ED=8C=A8=EB=84=90?= =?UTF-8?q?+PromptTemplatesEditor=20=EC=B6=94=EC=B6=9C=20=E2=86=92=20Insta?= =?UTF-8?q?Cards=20shell=20=ED=99=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/insta/InstaCards.jsx | 287 +----------------- .../insta/components/AccountFocusPanel.jsx | 73 +++++ .../insta/components/ExternalTrendsPanel.jsx | 104 +++++++ .../components/PreferenceImpactPanel.jsx | 37 +++ .../components/PromptTemplatesEditor.jsx | 80 +++++ 5 files changed, 298 insertions(+), 283 deletions(-) create mode 100644 src/pages/insta/components/AccountFocusPanel.jsx create mode 100644 src/pages/insta/components/ExternalTrendsPanel.jsx create mode 100644 src/pages/insta/components/PreferenceImpactPanel.jsx create mode 100644 src/pages/insta/components/PromptTemplatesEditor.jsx diff --git a/src/pages/insta/InstaCards.jsx b/src/pages/insta/InstaCards.jsx index 1739471..3cae41e 100644 --- a/src/pages/insta/InstaCards.jsx +++ b/src/pages/insta/InstaCards.jsx @@ -4,219 +4,15 @@ import { getInstaStatus, createInstaSlate, getInstaTask, - getInstaPrompt, - putInstaPrompt, - getInstaTrends, - instaCollectTrends, - getInstaPreferences, - putInstaPreferences, } from '../../api'; import './InstaCards.css'; -import { fmtDate } from './instaUtils'; -import TaskStatusBox from './components/TaskStatusBox'; import TriggerPanel from './components/TriggerPanel'; import KeywordsPanel from './components/KeywordsPanel'; import SlatesPanel from './components/SlatesPanel'; - -/* ══════════════════════ Trends 탭 패널 1: AccountFocusPanel ══════════════ */ -function AccountFocusPanel() { - const [prefs, setPrefs] = useState([]); - const [draft, setDraft] = useState({}); - const [saving, setSaving] = useState(false); - const [newCat, setNewCat] = useState(''); - - const load = useCallback(async () => { - const data = await getInstaPreferences(); - setPrefs(data.categories || []); - const m = {}; - (data.categories || []).forEach(p => { m[p.category] = Math.round(p.weight * 100); }); - setDraft(m); - }, []); - - useEffect(() => { load(); }, [load]); - - const save = async () => { - setSaving(true); - try { - const payload = {}; - Object.entries(draft).forEach(([k, v]) => { payload[k] = (Number(v) || 0) / 100; }); - await putInstaPreferences(payload); - await load(); - } finally { setSaving(false); } - }; - - const addCat = () => { - const name = newCat.trim().toLowerCase(); - if (!name || draft[name] !== undefined) return; - setDraft({ ...draft, [name]: 0 }); - setNewCat(''); - }; - - return ( -
-

🎯 이 계정의 주제 (카테고리 가중치)

-

슬라이더는 각 카테고리에 자동 추출 키워드 비율을 결정합니다. 합계는 자동 정규화됩니다.

-
- {Object.entries(draft).map(([cat, val]) => ( -
- - setDraft({ ...draft, [cat]: Number(e.target.value) })} - className="ic-focus__slider" - /> - {val}% -
- ))} -
-
- setNewCat(e.target.value)} - /> - -
- -
- 💡 신규 카테고리를 추가했다면 Cards 탭의 Prompt Templates Editor에서 - category_seeds에 시드 키워드도 함께 정의해야 자동 추출에 반영됩니다. -
-
- ); -} - -/* ══════════════════════ Trends 탭 패널 2: ExternalTrendsPanel ══════════ */ -const CATEGORY_COLORS = { - economy: '#0F62FE', psychology: '#A66CFF', - celebrity: '#FF5C8A', uncategorized: '#6B7280', -}; - -function ExternalTrendsPanel({ onCreateSlate }) { - const [naver, setNaver] = useState([]); - const [google, setGoogle] = useState([]); - const [lastFetched, setLastFetched] = useState(null); - const [collecting, setCollecting] = useState(false); - const [task, setTask] = useState(null); - - const load = useCallback(async () => { - const [n, g] = await Promise.all([ - getInstaTrends({ source: 'naver_popular', days: 2 }), - getInstaTrends({ source: 'youtube_trending', days: 2 }), - ]); - setNaver(n.items || []); - setGoogle(g.items || []); - const all = [...(n.items || []), ...(g.items || [])]; - if (all.length) { - const latest = all.map(t => t.suggested_at).sort().reverse()[0]; - setLastFetched(latest); - } - }, []); - - useEffect(() => { load(); }, [load]); - - const trigger = async () => { - setCollecting(true); - try { - const { task_id } = await instaCollectTrends(); - let st = null; - for (let i = 0; i < 60; i++) { - st = await getInstaTask(task_id); - setTask(st); - if (st.status === 'succeeded' || st.status === 'failed') break; - await new Promise(r => setTimeout(r, 3000)); - } - await load(); - } finally { setCollecting(false); } - }; - - const groupByCat = (items) => { - const g = {}; - items.forEach(it => { (g[it.category] = g[it.category] || []).push(it); }); - return g; - }; - - const renderRow = (t) => ( -
- - {t.keyword} - {(t.score || 0).toFixed(2)} - -
- ); - - const naverGrouped = groupByCat(naver); - return ( -
-
-

📈 외부 트렌드

-
- - {lastFetched ? `마지막 수집: ${fmtDate(lastFetched)}` : '아직 수집 없음'} - - -
-
- {task && } -
-
-

🔥 NAVER 인기

- {Object.keys(naverGrouped).length === 0 &&

없음

} - {Object.entries(naverGrouped).map(([cat, items]) => ( -
-
{cat}
- {items.map(renderRow)} -
- ))} -
-
-

📺 YouTube 인기

- {google.length === 0 &&

없음

} - {google.map(renderRow)} -
-
-
- ); -} - -/* ══════════════════════ Trends 탭 패널 3: PreferenceImpactPanel ══════ */ -function PreferenceImpactPanel() { - const [prefs, setPrefs] = useState([]); - const TOTAL = 15; - - useEffect(() => { - (async () => { - const data = await getInstaPreferences(); - setPrefs(data.categories || []); - })(); - }, []); - - const totalWeight = prefs.reduce((s, p) => s + (p.weight || 0), 0) || 1; - const breakdown = prefs.map(p => ({ - category: p.category, - count: Math.round(TOTAL * (p.weight || 0) / totalWeight), - })); - - return ( -
-

📊 다음 자동 추출 미리보기

-
- {breakdown.map(b => ( -
- {b.category} - {b.count}개 -
- ))} -
-
- ); -} +import PromptTemplatesEditor from './components/PromptTemplatesEditor'; +import AccountFocusPanel from './components/AccountFocusPanel'; +import ExternalTrendsPanel from './components/ExternalTrendsPanel'; +import PreferenceImpactPanel from './components/PreferenceImpactPanel'; /* ══════════════════════════════════════════════════════════════════════════ */ export default function InstaCards() { @@ -387,78 +183,3 @@ export default function InstaCards() { ); } - -/* ══════════════════════ 프롬프트 템플릿 에디터 ══════════════════════════ */ -const PROMPT_NAMES = ['slate_writer', 'category_seeds']; - -function PromptTemplatesEditor() { - const [prompts, setPrompts] = useState({}); - const [drafts, setDrafts] = useState({}); - const [saving, setSaving] = useState({}); - - useEffect(() => { - PROMPT_NAMES.forEach((name) => { - getInstaPrompt(name) - .then((p) => { - setPrompts((prev) => ({ ...prev, [name]: p })); - setDrafts((prev) => ({ ...prev, [name]: p.template })); - }) - .catch(() => { - setPrompts((prev) => ({ ...prev, [name]: null })); - setDrafts((prev) => ({ ...prev, [name]: '' })); - }); - }); - }, []); - - async function handleSave(name) { - setSaving((prev) => ({ ...prev, [name]: true })); - try { - const updated = await putInstaPrompt(name, drafts[name] || '', prompts[name]?.description || ''); - setPrompts((prev) => ({ ...prev, [name]: updated })); - alert(`${name} 저장 완료`); - } catch (e) { - alert('저장 실패: ' + e.message); - } finally { - setSaving((prev) => ({ ...prev, [name]: false })); - } - } - - return ( -
-

프롬프트 템플릿

- {PROMPT_NAMES.map((name) => ( -
-
- {name} - {prompts[name]?.updated_at && ( - - 최종 수정: {fmtDate(prompts[name].updated_at)} - - )} -
- {prompts[name]?.description && ( -
- {prompts[name].description} -
- )} -