Notice
Recent Posts
Recent Comments
반응형
오늘도 공부
Capstone Project - 나만의 AI 프로젝트 구축 본문
반응형
🎯 Capstone 개요
지금까지 배운 모든 기술을 통합하여 실제 사용 가능한 프로젝트를 만듭니다!
배운 기술 요약:
- ✅ Project 1: LLM Playground (프롬프트 엔지니어링, 기본 LLM 사용)
- ✅ Project 2: Customer Support Chatbot (RAG, Vector DB, Fine-tuning)
- ✅ Project 3: Ask-the-Web Agent (ReAct, 웹 검색, 에이전트)
- ✅ Project 4: Deep Research (CoT, ToT, Self-Consistency, 복잡한 추론)
- ✅ Project 5: Image Generation (Stable Diffusion, ControlNet, LoRA)
💡 Capstone 프로젝트 아이디어
1. AI Content Creation Platform (추천 ⭐⭐⭐)
개요: 블로그, 소셜미디어용 콘텐츠를 자동 생성하는 올인원 플랫폼
기능:
- 📝 주제 입력 → AI가 블로그 포스트 작성
- 🎨 자동 썸네일 이미지 생성 (Stable Diffusion)
- 🔍 관련 정보 웹 검색 및 팩트체크
- 📊 SEO 최적화 제안
- 🌐 다국어 번역
기술 스택:
- LLM: GPT-4o / Claude (글 작성)
- Image Generation: Stable Diffusion (썸네일)
- RAG: 참고 자료 검색
- Web Search: 최신 정보 수집
2. AI Research Assistant (학술/비즈니스)
개요: 논문 분석, 시장 조사, 경쟁사 분석을 돕는 AI 어시스턴트
기능:
- 📄 PDF 논문 업로드 → 요약 및 핵심 인사이트 추출
- 🔬 관련 논문 자동 검색 및 비교
- 📈 데이터 시각화 (그래프, 차트)
- 💡 질문 응답 (Deep Research 기법 활용)
- 📋 보고서 자동 생성
기술 스택:
- Document Processing: PyPDF2, docx
- Deep Research: ToT, Self-Consistency
- Vector DB: ChromaDB (논문 임베딩)
- Visualization: Matplotlib, Plotly
3. AI Personal Brand Manager
개요: 개인 브랜딩을 위한 AI 매니저 (SNS, 블로그 관리)
기능:
- 📅 콘텐츠 캘린더 자동 생성
- 🎨 포스트 이미지 생성 (일관된 스타일)
- ✍️ 글 작성 (톤앤매너 학습)
- 📊 트렌드 분석 및 추천
- 💬 댓글 자동 응답
기술 스택:
- LLM: 콘텐츠 생성
- Image Generation: LoRA로 개인 스타일 학습
- Web Search: 트렌드 분석
- Scheduling: Cron jobs
4. AI Code Review & Documentation System
개요: 코드 리뷰, 문서화, 버그 탐지를 자동화하는 시스템
기능:
- 🔍 코드 분석 및 개선 제안
- 📝 자동 문서화 (README, API docs)
- 🐛 버그 탐지 및 수정 제안
- 🧪 테스트 케이스 생성
- 📊 코드 품질 리포트
기술 스택:
- LLM: Claude (코드 분석)
- AST: Python ast 모듈
- Git Integration: GitPython
5. AI Learning Tutor (교육)
개요: 개인 맞춤형 AI 튜터
기능:
- 📚 학습 자료 생성 (문제, 해설)
- 🎓 진도에 맞춰 커리큘럼 조정
- 💬 질문 응답 (단계별 설명)
- 🎨 시각 자료 생성 (다이어그램, 일러스트)
- 📊 학습 진척도 분석
기술 스택:
- LLM: 설명 및 문제 생성
- Image Generation: 교육용 시각 자료
- Knowledge Base: RAG
🚀 실전 Capstone: AI Content Studio 구축
가장 실용적이고 통합적인 AI Content Studio를 만들어보겠습니다!
프로젝트 개요
AI Content Studio
├── 주제/키워드 입력
├── AI가 콘텐츠 기획
│ ├── 웹 검색으로 트렌드 파악
│ ├── Deep Research로 깊이 있는 분석
│ └── 아웃라인 생성
├── 콘텐츠 작성
│ ├── 블로그 포스트
│ ├── 소셜미디어 포스트
│ └── SEO 최적화
├── 비주얼 생성
│ ├── 썸네일 이미지
│ ├── 인포그래픽
│ └── 소셜미디어 카드
└── 출력
├── Markdown
├── HTML
└── 이미지 파일
📁 프로젝트 구조
mkdir ai-content-studio
cd ai-content-studio
# 가상환경
python -m venv venv
source venv/bin/activate
# 디렉토리 구조
mkdir -p src/{content,research,visual,export}
mkdir -p utils templates outputs
touch .env app.py requirements.txt
ai-content-studio/
├── src/
│ ├── content/
│ │ ├── planner.py # 콘텐츠 기획
│ │ ├── writer.py # 글 작성
│ │ └── optimizer.py # SEO 최적화
│ ├── research/
│ │ ├── trend_analyzer.py # 트렌드 분석
│ │ ├── web_researcher.py # 웹 리서치
│ │ └── fact_checker.py # 팩트체크
│ ├── visual/
│ │ ├── thumbnail_generator.py # 썸네일
│ │ ├── social_card_maker.py # 소셜 카드
│ │ └── style_manager.py # 스타일 관리
│ └── export/
│ ├── markdown_exporter.py
│ ├── html_exporter.py
│ └── pdf_exporter.py
├── utils/
│ ├── llm_client.py # LLM 통합
│ ├── prompt_library.py # 프롬프트 모음
│ └── config.py # 설정
├── templates/ # HTML 템플릿
├── outputs/ # 생성된 콘텐츠
├── app.py # Streamlit UI
└── requirements.txt
🛠️ Step 1: 핵심 유틸리티 구축 (20분)
requirements.txt
# LLM
openai==1.59.5
anthropic==0.42.0
# Image Generation
diffusers==0.32.1
transformers==4.47.1
torch==2.5.1
accelerate==1.2.1
# Web & Search
tavily-python==0.5.0
duckduckgo-search==7.1.3
beautifulsoup4==4.12.3
requests==2.32.3
# Document Processing
markdown==3.7
pypdf2==3.0.1
python-docx==1.1.2
jinja2==3.1.4
# Data & ML
chromadb==0.6.3
sentence-transformers==3.3.1
numpy==2.2.1
pandas==2.2.3
# UI
streamlit==1.41.1
pillow==11.0.0
# Utils
python-dotenv==1.0.1
utils/llm_client.py
import os
from openai import OpenAI
from anthropic import Anthropic
from dotenv import load_dotenv
from typing import Optional, Dict
load_dotenv()
class UnifiedLLMClient:
"""통합 LLM 클라이언트"""
def __init__(self, provider: str = "openai", model: Optional[str] = None):
"""
Args:
provider: 'openai' 또는 'anthropic'
model: 모델 이름 (None이면 기본값)
"""
self.provider = provider
if provider == "openai":
self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
self.model = model or "gpt-4o-mini"
elif provider == "anthropic":
self.client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
self.model = model or "claude-3-5-sonnet-20241022"
else:
raise ValueError(f"지원하지 않는 provider: {provider}")
def generate(
self,
prompt: str,
system_prompt: Optional[str] = None,
temperature: float = 0.7,
max_tokens: int = 2000
) -> str:
"""텍스트 생성"""
if self.provider == "openai":
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": prompt})
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens
)
return response.choices[0].message.content
else: # anthropic
if system_prompt:
prompt = f"{system_prompt}\n\n{prompt}"
response = self.client.messages.create(
model=self.model,
max_tokens=max_tokens,
temperature=temperature,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
def generate_structured(
self,
prompt: str,
system_prompt: Optional[str] = None,
response_format: str = "json"
) -> Dict:
"""구조화된 출력 생성 (JSON 등)"""
full_prompt = f"{prompt}\n\n응답 형식: {response_format}"
response = self.generate(
prompt=full_prompt,
system_prompt=system_prompt,
temperature=0.3
)
# JSON 파싱 시도
import json
try:
return json.loads(response)
except:
# 파싱 실패시 원본 반환
return {"raw": response}
# 테스트
if __name__ == "__main__":
client = UnifiedLLMClient(provider="openai")
response = client.generate(
prompt="AI에 대해 한 문장으로 설명해주세요",
system_prompt="당신은 친절한 AI 어시스턴트입니다"
)
print(response)
utils/prompt_library.py
class PromptLibrary:
"""재사용 가능한 프롬프트 라이브러리"""
# 콘텐츠 기획
CONTENT_PLANNER = """당신은 전문 콘텐츠 기획자입니다.
주제: {topic}
타겟 오디언스: {audience}
콘텐츠 타입: {content_type}
다음 형식으로 콘텐츠를 기획하세요:
1. 제목 (매력적이고 클릭하고 싶은)
2. 핵심 메시지 (한 문장)
3. 아웃라인 (3-5개 섹션)
- 섹션 1: [제목]
- 주요 포인트
- 섹션 2: [제목]
- 주요 포인트
...
4. 키워드 (SEO용, 5-7개)
5. 예상 독자 반응
JSON 형식으로 답변하세요."""
# 블로그 작성
BLOG_WRITER = """당신은 전문 블로그 작가입니다.
아웃라인:
{outline}
참고 자료:
{references}
다음 기준으로 블로그 포스트를 작성하세요:
- 톤: {tone} (전문적/캐주얼/친근함)
- 길이: {length} 단어
- 구조: 서론 → 본론 → 결론
- 스타일: 읽기 쉽고, 명확하고, 흥미로움
- 각 섹션에 소제목 사용
- 구체적인 예시 포함
Markdown 형식으로 작성하세요."""
# SEO 최적화
SEO_OPTIMIZER = """당신은 SEO 전문가입니다.
콘텐츠:
{content}
다음을 분석하고 개선하세요:
1. 메타 제목 (60자 이내)
2. 메타 설명 (160자 이내)
3. 주요 키워드 (5-10개)
4. 헤딩 구조 개선 제안
5. 내부 링크 제안
6. 가독성 점수 및 개선 방안
JSON 형식으로 답변하세요."""
# 트렌드 분석
TREND_ANALYZER = """당신은 트렌드 분석가입니다.
검색 결과:
{search_results}
다음을 분석하세요:
1. 현재 트렌드 (Top 3)
2. 주요 토픽 및 키워드
3. 대중의 관심사
4. 콘텐츠 기회 (어떤 각도로 접근할지)
5. 경쟁 콘텐츠 분석
JSON 형식으로 답변하세요."""
# 소셜 미디어
SOCIAL_POST = """당신은 소셜 미디어 마케터입니다.
원본 콘텐츠:
{content}
플랫폼: {platform} (Instagram/Twitter/LinkedIn/Facebook)
플랫폼에 맞는 포스트를 작성하세요:
- Instagram: 감성적, 해시태그 10-15개
- Twitter: 간결, 280자 이내, 해시태그 1-3개
- LinkedIn: 전문적, 인사이트 중심
- Facebook: 친근, 스토리텔링
형식:
POST:
[포스트 내용]
HASHTAGS:
[해시태그 목록]
IMAGE_PROMPT:
[썸네일용 이미지 프롬프트]"""
# 이미지 프롬프트
IMAGE_PROMPT_ENHANCER = """당신은 Stable Diffusion 프롬프트 전문가입니다.
콘텐츠 주제: {topic}
이미지 타입: {image_type} (thumbnail/hero/social)
스타일: {style} (realistic/illustration/abstract/minimal)
고품질 이미지 생성을 위한 프롬프트를 작성하세요:
POSITIVE_PROMPT:
[상세한 긍정 프롬프트]
- 주제 명확히
- 스타일 키워드
- 조명/색감
- 품질 키워드 (highly detailed, 4k, masterpiece)
NEGATIVE_PROMPT:
[부정 프롬프트]
- 원하지 않는 요소
PARAMETERS:
- aspect_ratio: [16:9 / 4:3 / 1:1]
- style_strength: [0.5-1.5]"""
@classmethod
def get(cls, template_name: str, **kwargs) -> str:
"""템플릿 가져오기 및 포맷팅"""
template = getattr(cls, template_name, None)
if not template:
raise ValueError(f"템플릿을 찾을 수 없습니다: {template_name}")
return template.format(**kwargs)
# 테스트
if __name__ == "__main__":
# 콘텐츠 기획 프롬프트 생성
prompt = PromptLibrary.get(
"CONTENT_PLANNER",
topic="인공지능의 미래",
audience="일반 대중",
content_type="블로그 포스트"
)
print(prompt)
📝 Step 2: 콘텐츠 리서치 모듈 (25분)
src/research/web_researcher.py
import os
from typing import List, Dict
from tavily import TavilyClient
from duckduckgo_search import DDGS
import sys
sys.path.append('../..')
from utils.llm_client import UnifiedLLMClient
from utils.prompt_library import PromptLibrary
class WebResearcher:
"""웹 리서치 및 정보 수집"""
def __init__(self):
self.tavily_key = os.getenv("TAVILY_API_KEY")
self.tavily = TavilyClient(api_key=self.tavily_key) if self.tavily_key else None
self.llm = UnifiedLLMClient(provider="openai")
def search(self, query: str, max_results: int = 5) -> List[Dict]:
"""웹 검색"""
print(f"🔍 검색 중: {query}")
if self.tavily:
# Tavily 사용
response = self.tavily.search(query, max_results=max_results)
results = response.get('results', [])
else:
# DuckDuckGo 사용
ddgs = DDGS()
results = []
for r in ddgs.text(query, max_results=max_results):
results.append({
'title': r.get('title'),
'url': r.get('href'),
'content': r.get('body'),
'score': 0.5
})
print(f"✅ {len(results)}개 결과 발견")
return results
def analyze_trends(self, topic: str) -> Dict:
"""트렌드 분석"""
print(f"📊 트렌드 분석: {topic}")
# 검색
search_results = self.search(f"{topic} 트렌드 2024", max_results=5)
# 검색 결과 요약
results_text = "\n\n".join([
f"제목: {r['title']}\n내용: {r['content'][:200]}..."
for r in search_results
])
# LLM으로 분석
prompt = PromptLibrary.get(
"TREND_ANALYZER",
search_results=results_text
)
analysis = self.llm.generate_structured(prompt)
analysis['sources'] = search_results
return analysis
def gather_references(self, topic: str, num_sources: int = 3) -> List[Dict]:
"""참고 자료 수집"""
print(f"📚 참고 자료 수집: {topic}")
results = self.search(topic, max_results=num_sources)
references = []
for r in results:
references.append({
'title': r['title'],
'url': r['url'],
'summary': r['content'][:300],
'relevance': r.get('score', 0.5)
})
return references
# 테스트
if __name__ == "__main__":
researcher = WebResearcher()
# 트렌드 분석
topic = "생성형 AI"
trends = researcher.analyze_trends(topic)
print("\n" + "="*60)
print("트렌드 분석 결과:")
print("="*60)
print(trends)
# 참고 자료
print("\n" + "="*60)
print("참고 자료:")
print("="*60)
references = researcher.gather_references(topic)
for i, ref in enumerate(references, 1):
print(f"\n{i}. {ref['title']}")
print(f" {ref['url']}")
print(f" {ref['summary'][:100]}...")
✍️ Step 3: 콘텐츠 생성 모듈 (30분)
src/content/planner.py
import sys
sys.path.append('../..')
from utils.llm_client import UnifiedLLMClient
from utils.prompt_library import PromptLibrary
from typing import Dict
class ContentPlanner:
"""콘텐츠 기획"""
def __init__(self):
self.llm = UnifiedLLMClient(provider="openai", model="gpt-4o-mini")
def create_plan(
self,
topic: str,
audience: str = "일반 대중",
content_type: str = "블로그 포스트",
trend_data: Dict = None
) -> Dict:
"""콘텐츠 플랜 생성"""
print(f"📋 콘텐츠 기획 중: {topic}")
# 트렌드 정보 추가
extra_context = ""
if trend_data:
extra_context = f"\n\n현재 트렌드:\n{trend_data}"
prompt = PromptLibrary.get(
"CONTENT_PLANNER",
topic=topic,
audience=audience,
content_type=content_type
) + extra_context
plan = self.llm.generate_structured(prompt)
print("✅ 기획 완료")
return plan
# 테스트
if __name__ == "__main__":
planner = ContentPlanner()
plan = planner.create_plan(
topic="ChatGPT가 바꾼 업무 방식",
audience="직장인",
content_type="블로그 포스트"
)
print("\n" + "="*60)
print("콘텐츠 플랜:")
print("="*60)
import json
print(json.dumps(plan, ensure_ascii=False, indent=2))
src/content/writer.py
import sys
sys.path.append('../..')
from utils.llm_client import UnifiedLLMClient
from utils.prompt_library import PromptLibrary
from typing import Dict, List
class ContentWriter:
"""콘텐츠 작성"""
def __init__(self):
self.llm = UnifiedLLMClient(provider="openai", model="gpt-4o")
def write_blog(
self,
plan: Dict,
references: List[Dict] = None,
tone: str = "친근함",
length: int = 1000
) -> str:
"""블로그 포스트 작성"""
print(f"✍️ 블로그 작성 중...")
# 아웃라인 포맷팅
outline = plan.get('raw', str(plan))
# 참고 자료 포맷팅
refs_text = ""
if references:
refs_text = "\n\n".join([
f"- {ref['title']}: {ref['summary']}"
for ref in references[:3]
])
prompt = PromptLibrary.get(
"BLOG_WRITER",
outline=outline,
references=refs_text or "없음",
tone=tone,
length=length
)
blog_content = self.llm.generate(
prompt=prompt,
temperature=0.7,
max_tokens=3000
)
print("✅ 작성 완료")
return blog_content
def create_social_posts(
self,
content: str,
platforms: List[str] = ["instagram", "twitter", "linkedin"]
) -> Dict[str, str]:
"""소셜 미디어 포스트 생성"""
print(f"📱 소셜 포스트 생성 중...")
posts = {}
for platform in platforms:
prompt = PromptLibrary.get(
"SOCIAL_POST",
content=content[:500], # 요약본만
platform=platform
)
post = self.llm.generate(prompt, temperature=0.8)
posts[platform] = post
print(f" ✅ {platform} 완료")
return posts
# 테스트
if __name__ == "__main__":
from planner import ContentPlanner
# 기획
planner = ContentPlanner()
plan = planner.create_plan(
topic="AI 시대의 업무 효율성",
audience="직장인"
)
# 작성
writer = ContentWriter()
blog = writer.write_blog(
plan=plan,
tone="전문적",
length=800
)
print("\n" + "="*60)
print("블로그 콘텐츠:")
print("="*60)
print(blog)
# 소셜 포스트
social = writer.create_social_posts(blog, platforms=["twitter"])
print("\n" + "="*60)
print("Twitter 포스트:")
print("="*60)
print(social['twitter'])
src/content/optimizer.py
import sys
sys.path.append('../..')
from utils.llm_client import UnifiedLLMClient
from utils.prompt_library import PromptLibrary
from typing import Dict
class SEOOptimizer:
"""SEO 최적화"""
def __init__(self):
self.llm = UnifiedLLMClient(provider="openai")
def optimize(self, content: str) -> Dict:
"""SEO 최적화 제안"""
print("🔍 SEO 최적화 중...")
prompt = PromptLibrary.get(
"SEO_OPTIMIZER",
content=content[:2000] # 처음 2000자만
)
seo_data = self.llm.generate_structured(prompt)
print("✅ 최적화 완료")
return seo_data
# 테스트
if __name__ == "__main__":
optimizer = SEOOptimizer()
test_content = """
# AI가 바꾸는 업무 방식
인공지능 기술이 발전하면서 우리의 업무 방식도 크게 변화하고 있습니다.
특히 ChatGPT와 같은 생성형 AI는...
"""
seo = optimizer.optimize(test_content)
print("\n" + "="*60)
print("SEO 최적화 제안:")
print("="*60)
import json
print(json.dumps(seo, ensure_ascii=False, indent=2))
🎨 Step 4: 비주얼 생성 모듈 (30분)
src/visual/thumbnail_generator.py
import torch
from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
from PIL import Image, ImageDraw, ImageFont
import os
from typing import Optional, Dict
import sys
sys.path.append('../..')
from utils.llm_client import UnifiedLLMClient
from utils.prompt_library import PromptLibrary
class ThumbnailGenerator:
"""썸네일 이미지 생성"""
def __init__(self, use_gpu: bool = True):
"""
Args:
use_gpu: GPU 사용 여부
"""
self.device = "cuda" if use_gpu and torch.cuda.is_available() else "cpu"
self.llm = UnifiedLLMClient(provider="openai")
self.pipe = None
print(f"🖥️ 디바이스: {self.device}")
def _load_model(self):
"""모델 로드 (지연 로딩)"""
if self.pipe is not None:
return
print("📥 Stable Diffusion 모델 로딩 중...")
self.pipe = StableDiffusionPipeline.from_pretrained(
"runwayml/stable-diffusion-v1-5",
torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
safety_checker=None
)
self.pipe.scheduler = DPMSolverMultistepScheduler.from_config(
self.pipe.scheduler.config
)
self.pipe = self.pipe.to(self.device)
if self.device == "cuda":
self.pipe.enable_attention_slicing()
print("✅ 모델 로드 완료")
def enhance_image_prompt(
self,
topic: str,
style: str = "professional",
image_type: str = "thumbnail"
) -> Dict[str, str]:
"""이미지 프롬프트 개선"""
print(f"✨ 이미지 프롬프트 생성 중: {topic}")
prompt = PromptLibrary.get(
"IMAGE_PROMPT_ENHANCER",
topic=topic,
image_type=image_type,
style=style
)
result = self.llm.generate(prompt, temperature=0.7)
# 파싱
positive = ""
negative = ""
if "POSITIVE_PROMPT:" in result:
positive = result.split("POSITIVE_PROMPT:")[1].split("NEGATIVE_PROMPT:")[0].strip()
if "NEGATIVE_PROMPT:" in result:
negative = result.split("NEGATIVE_PROMPT:")[1].split("PARAMETERS:")[0].strip()
return {
'positive': positive or "high quality digital art",
'negative': negative or "blurry, low quality"
}
def generate(
self,
topic: str,
style: str = "professional",
width: int = 1024,
height: int = 576, # 16:9 비율
num_inference_steps: int = 30,
guidance_scale: float = 7.5,
seed: Optional[int] = None
) -> Image.Image:
"""썸네일 생성"""
# 모델 로드
self._load_model()
# 프롬프트 생성
prompts = self.enhance_image_prompt(topic, style, "thumbnail")
print(f"🎨 이미지 생성 중...")
print(f" 프롬프트: {prompts['positive'][:80]}...")
# 시드 설정
generator = None
if seed is not None:
generator = torch.Generator(device=self.device).manual_seed(seed)
# 생성
with torch.autocast(self.device):
result = self.pipe(
prompt=prompts['positive'],
negative_prompt=prompts['negative'],
width=width,
height=height,
num_inference_steps=num_inference_steps,
guidance_scale=guidance_scale,
generator=generator
)
image = result.images[0]
print("✅ 이미지 생성 완료")
return image
def add_text_overlay(
self,
image: Image.Image,
title: str,
subtitle: Optional[str] = None
) -> Image.Image:
"""텍스트 오버레이 추가"""
print("📝 텍스트 오버레이 추가 중...")
# 이미지 복사
img = image.copy()
draw = ImageDraw.Draw(img)
# 폰트 설정 (시스템 기본 폰트)
try:
# 제목 폰트 (큰 글씨)
title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 60)
subtitle_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 40)
except:
# 폰트 로드 실패시 기본 폰트
title_font = ImageFont.load_default()
subtitle_font = ImageFont.load_default()
# 반투명 배경 추가 (하단)
overlay = Image.new('RGBA', img.size, (0, 0, 0, 0))
overlay_draw = ImageDraw.Draw(overlay)
# 하단 그라데이션 효과 (간단한 사각형)
overlay_height = img.height // 3
overlay_draw.rectangle(
[(0, img.height - overlay_height), (img.width, img.height)],
fill=(0, 0, 0, 180)
)
# 오버레이 합성
img = Image.alpha_composite(img.convert('RGBA'), overlay).convert('RGB')
draw = ImageDraw.Draw(img)
# 텍스트 위치 계산
title_y = img.height - overlay_height + 20
# 제목
draw.text(
(40, title_y),
title,
font=title_font,
fill=(255, 255, 255)
)
# 부제목
if subtitle:
subtitle_y = title_y + 80
draw.text(
(40, subtitle_y),
subtitle,
font=subtitle_font,
fill=(200, 200, 200)
)
print("✅ 텍스트 추가 완료")
return img
# 테스트
if __name__ == "__main__":
generator = ThumbnailGenerator(use_gpu=True)
# 썸네일 생성
topic = "AI가 바꾸는 미래의 일자리"
image = generator.generate(
topic=topic,
style="modern",
width=1024,
height=576,
seed=42
)
# 텍스트 추가
final_image = generator.add_text_overlay(
image=image,
title="AI와 미래",
subtitle="일자리의 변화"
)
# 저장
os.makedirs("../../outputs", exist_ok=True)
final_image.save("../../outputs/thumbnail_test.png")
print("💾 저장: outputs/thumbnail_test.png")
src/visual/social_card_maker.py
from PIL import Image, ImageDraw, ImageFont
import textwrap
from typing import Optional
import os
class SocialCardMaker:
"""소셜 미디어 카드 생성"""
SIZES = {
'instagram': (1080, 1080),
'twitter': (1200, 675),
'facebook': (1200, 630),
'linkedin': (1200, 627)
}
COLORS = {
'gradient_blue': [(30, 87, 153), (125, 185, 232)],
'gradient_purple': [(106, 17, 203), (237, 117, 255)],
'gradient_orange': [(251, 146, 60), (252, 211, 77)],
'gradient_green': [(16, 185, 129), (101, 163, 13)],
'solid_white': [(255, 255, 255), (255, 255, 255)],
'solid_black': [(0, 0, 0), (0, 0, 0)]
}
def __init__(self):
pass
def create_gradient(self, width: int, height: int, colors: list) -> Image.Image:
"""그라데이션 배경 생성"""
base = Image.new('RGB', (width, height), colors[0])
top = Image.new('RGB', (width, height), colors[1])
mask = Image.new('L', (width, height))
mask_data = []
for y in range(height):
mask_data.extend([int(255 * (y / height))] * width)
mask.putdata(mask_data)
base.paste(top, (0, 0), mask)
return base
def create_card(
self,
platform: str,
title: str,
subtitle: Optional[str] = None,
color_scheme: str = 'gradient_blue',
logo_text: Optional[str] = None
) -> Image.Image:
"""소셜 카드 생성"""
print(f"🎨 {platform} 카드 생성 중...")
# 크기 설정
size = self.SIZES.get(platform, (1200, 630))
width, height = size
# 배경 생성
colors = self.COLORS.get(color_scheme, self.COLORS['gradient_blue'])
img = self.create_gradient(width, height, colors)
draw = ImageDraw.Draw(img)
# 폰트 로드
try:
title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 80)
subtitle_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 50)
logo_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 40)
except:
title_font = ImageFont.load_default()
subtitle_font = ImageFont.load_default()
logo_font = ImageFont.load_default()
# 제목 (중앙 정렬, 여러 줄)
title_wrapped = textwrap.fill(title, width=20)
# 제목 위치 (중앙)
title_bbox = draw.textbbox((0, 0), title_wrapped, font=title_font)
title_width = title_bbox[2] - title_bbox[0]
title_height = title_bbox[3] - title_bbox[1]
title_x = (width - title_width) // 2
title_y = (height - title_height) // 2 - 50
# 그림자 효과
shadow_offset = 4
draw.text(
(title_x + shadow_offset, title_y + shadow_offset),
title_wrapped,
font=title_font,
fill=(0, 0, 0, 128),
align='center'
)
# 제목
draw.text(
(title_x, title_y),
title_wrapped,
font=title_font,
fill=(255, 255, 255),
align='center'
)
# 부제목
if subtitle:
subtitle_wrapped = textwrap.fill(subtitle, width=30)
subtitle_bbox = draw.textbbox((0, 0), subtitle_wrapped, font=subtitle_font)
subtitle_width = subtitle_bbox[2] - subtitle_bbox[0]
subtitle_x = (width - subtitle_width) // 2
subtitle_y = title_y + title_height + 30
draw.text(
(subtitle_x, subtitle_y),
subtitle_wrapped,
font=subtitle_font,
fill=(230, 230, 230),
align='center'
)
# 로고/브랜드 (하단)
if logo_text:
logo_bbox = draw.textbbox((0, 0), logo_text, font=logo_font)
logo_width = logo_bbox[2] - logo_bbox[0]
logo_x = (width - logo_width) // 2
logo_y = height - 80
draw.text(
(logo_x, logo_y),
logo_text,
font=logo_font,
fill=(255, 255, 255, 200)
)
print(f"✅ {platform} 카드 완성")
return img
# 테스트
if __name__ == "__main__":
maker = SocialCardMaker()
platforms = ['instagram', 'twitter', 'linkedin']
for platform in platforms:
card = maker.create_card(
platform=platform,
title="AI Content Studio",
subtitle="자동 콘텐츠 생성의 미래",
color_scheme='gradient_purple',
logo_text="YourBrand.com"
)
# 저장
os.makedirs("../../outputs", exist_ok=True)
filename = f"../../outputs/social_card_{platform}.png"
card.save(filename)
print(f"💾 저장: {filename}\n")
📤 Step 5: 출력 모듈 (20분)
src/export/markdown_exporter.py
from typing import Dict, List, Optional
import os
from datetime import datetime
class MarkdownExporter:
"""Markdown 형식으로 출력"""
@staticmethod
def export_blog(
content: str,
metadata: Dict,
seo_data: Optional[Dict] = None,
output_path: str = "outputs/blog.md"
) -> str:
"""블로그 포스트를 Markdown으로 출력"""
print(f"📝 Markdown 출력 중: {output_path}")
# Front Matter (Jekyll, Hugo 등에서 사용)
front_matter = f"""---
title: "{metadata.get('title', 'Untitled')}"
date: {datetime.now().strftime('%Y-%m-%d')}
author: {metadata.get('author', 'AI Content Studio')}
"""
if seo_data:
front_matter += f"""description: "{seo_data.get('meta_description', '')}"
keywords: {seo_data.get('keywords', [])}
"""
front_matter += "---\n\n"
# 전체 콘텐츠
full_content = front_matter + content
# 저장
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, 'w', encoding='utf-8') as f:
f.write(full_content)
print(f"✅ 저장 완료: {output_path}")
return output_path
@staticmethod
def export_social_posts(
posts: Dict[str, str],
output_dir: str = "outputs/social"
) -> List[str]:
"""소셜 미디어 포스트들을 개별 파일로 출력"""
print(f"📱 소셜 포스트 출력 중: {output_dir}")
os.makedirs(output_dir, exist_ok=True)
saved_files = []
for platform, content in posts.items():
filename = os.path.join(output_dir, f"{platform}_post.md")
with open(filename, 'w', encoding='utf-8') as f:
f.write(f"# {platform.title()} Post\n\n")
f.write(content)
saved_files.append(filename)
print(f" ✅ {platform}: {filename}")
return saved_files
# 테스트
if __name__ == "__main__":
exporter = MarkdownExporter()
# 테스트 콘텐츠
test_content = """
# AI가 바꾸는 미래
인공지능 기술의 발전은...
## 주요 변화
1. 업무 자동화
2. 의사결정 지원
3. 창의적 작업
## 결론
AI는 우리의 파트너입니다.
"""
metadata = {
'title': 'AI가 바꾸는 미래',
'author': 'AI Writer'
}
seo_data = {
'meta_description': 'AI 기술이 미래에 미칠 영향',
'keywords': ['AI', '인공지능', '미래', '기술']
}
# 출력
path = exporter.export_blog(test_content, metadata, seo_data)
print(f"\n저장된 파일: {path}")
src/export/html_exporter.py
from jinja2 import Template
from typing import Dict, Optional
import os
class HTMLExporter:
"""HTML 형식으로 출력"""
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<meta name="description" content="{{ description }}">
<meta name="keywords" content="{{ keywords }}">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.8;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 40px 20px;
}
header {
margin-bottom: 60px;
border-bottom: 3px solid #667eea;
padding-bottom: 20px;
}
h1 {
font-size: 2.5rem;
color: #1a202c;
margin-bottom: 10px;
}
.meta {
color: #718096;
font-size: 0.9rem;
}
.content {
font-size: 1.1rem;
}
.content h2 {
font-size: 1.8rem;
color: #2d3748;
margin-top: 40px;
margin-bottom: 20px;
}
.content h3 {
font-size: 1.4rem;
color: #4a5568;
margin-top: 30px;
margin-bottom: 15px;
}
.content p {
margin-bottom: 20px;
}
.content ul, .content ol {
margin-left: 30px;
margin-bottom: 20px;
}
.content li {
margin-bottom: 10px;
}
.thumbnail {
width: 100%;
height: auto;
border-radius: 10px;
margin: 30px 0;
}
footer {
margin-top: 60px;
padding-top: 20px;
border-top: 1px solid #e2e8f0;
text-align: center;
color: #718096;
font-size: 0.9rem;
}
</style>
</head>
<body>
<header>
<h1>{{ title }}</h1>
<div class="meta">
<span>{{ author }}</span> •
<span>{{ date }}</span>
</div>
</header>
{% if thumbnail %}
<img src="{{ thumbnail }}" alt="{{ title }}" class="thumbnail">
{% endif %}
<article class="content">
{{ content | safe }}
</article>
<footer>
<p>Generated by AI Content Studio</p>
</footer>
</body>
</html>
"""
@staticmethod
def export(
content: str,
metadata: Dict,
thumbnail_path: Optional[str] = None,
output_path: str = "outputs/blog.html"
) -> str:
"""HTML로 출력"""
print(f"🌐 HTML 출력 중: {output_path}")
# Markdown을 HTML로 변환 (간단한 변환)
import markdown
content_html = markdown.markdown(content)
# 템플릿 렌더링
template = Template(HTMLExporter.HTML_TEMPLATE)
html = template.render(
title=metadata.get('title', 'Untitled'),
author=metadata.get('author', 'AI Writer'),
date=metadata.get('date', '2024-01-01'),
description=metadata.get('description', ''),
keywords=', '.join(metadata.get('keywords', [])),
content=content_html,
thumbnail=thumbnail_path
)
# 저장
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, 'w', encoding='utf-8') as f:
f.write(html)
print(f"✅ 저장 완료: {output_path}")
return output_path
# 테스트
if __name__ == "__main__":
exporter = HTMLExporter()
test_content = """
# AI가 바꾸는 미래
인공지능 기술의 발전은 우리 사회를 근본적으로 변화시키고 있습니다.
## 주요 변화
1. **업무 자동화**: 반복적인 작업의 자동화
2. **의사결정 지원**: 데이터 기반 인사이트
3. **창의적 작업**: AI와의 협업
## 결론
AI는 위협이 아닌 **파트너**입니다.
"""
metadata = {
'title': 'AI가 바꾸는 미래',
'author': 'AI Content Studio',
'date': '2024-11-01',
'description': 'AI 기술이 미래에 미칠 영향',
'keywords': ['AI', '인공지능', '미래']
}
path = exporter.export(test_content, metadata)
print(f"\n저장된 파일: {path}")
🎨 Step 6: 통합 Streamlit UI (35분)
app.py
import streamlit as st
import sys
sys.path.append('src')
from research.web_researcher import WebResearcher
from content.planner import ContentPlanner
from content.writer import ContentWriter
from content.optimizer import SEOOptimizer
from visual.thumbnail_generator import ThumbnailGenerator
from visual.social_card_maker import SocialCardMaker
from export.markdown_exporter import MarkdownExporter
from export.html_exporter import HTMLExporter
import time
from datetime import datetime
import io
# 페이지 설정
st.set_page_config(
page_title="AI Content Studio",
page_icon="🎨",
layout="wide"
)
# CSS
st.markdown("""
<style>
.main-title {
font-size: 3.5rem;
font-weight: bold;
text-align: center;
background: linear-gradient(120deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 0.5rem;
}
.subtitle {
text-align: center;
color: #666;
font-size: 1.3rem;
margin-bottom: 2rem;
}
.step-container {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 10px;
margin: 20px 0;
}
.success-box {
background-color: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 5px;
padding: 15px;
margin: 10px 0;
}
</style>
""", unsafe_allow_html=True)
# 세션 상태 초기화
if 'content_data' not in st.session_state:
st.session_state.content_data = {}
if 'generated_images' not in st.session_state:
st.session_state.generated_images = {}
# 헤더
st.markdown('<h1 class="main-title">🎨 AI Content Studio</h1>', unsafe_allow_html=True)
st.markdown('<p class="subtitle">AI 기반 올인원 콘텐츠 생성 플랫폼</p>', unsafe_allow_html=True)
# 사이드바
with st.sidebar:
st.header("⚙️ 프로젝트 설정")
project_name = st.text_input(
"프로젝트 이름",
value="My Content Project"
)
author_name = st.text_input(
"작성자",
value="AI Writer"
)
st.markdown("---")
st.subheader("🎨 스타일 설정")
writing_tone = st.selectbox(
"글 톤",
["친근함", "전문적", "캐주얼", "정중함"]
)
visual_style = st.selectbox(
"비주얼 스타일",
["modern", "professional", "creative", "minimal"]
)
color_scheme = st.selectbox(
"색상 테마",
["gradient_blue", "gradient_purple", "gradient_orange", "gradient_green"]
)
st.markdown("---")
use_gpu = st.checkbox(
"GPU 사용 (이미지 생성)",
value=True,
help="GPU가 있으면 활성화하세요"
)
st.markdown("---")
st.subheader("💾 이전 프로젝트")
st.info("프로젝트 저장 기능은 곧 추가됩니다!")
# 메인 워크플로우
st.markdown('<div class="step-container">', unsafe_allow_html=True)
st.markdown("### 📋 Step 1: 주제 및 타겟 설정")
st.markdown('</div>', unsafe_allow_html=True)
col1, col2 = st.columns([2, 1])
with col1:
topic = st.text_area(
"콘텐츠 주제",
height=100,
placeholder="예: ChatGPT가 바꾸는 업무 방식",
help="생성하고 싶은 콘텐츠의 주제를 입력하세요"
)
with col2:
target_audience = st.selectbox(
"타겟 독자",
["일반 대중", "직장인", "학생", "전문가", "기업"]
)
content_type = st.selectbox(
"콘텐츠 타입",
["블로그 포스트", "뉴스레터", "소셜 미디어", "백서"]
)
content_length = st.slider(
"글 길이 (단어)",
500, 3000, 1000, 100
)
# 워크플로우 시작
if st.button("🚀 콘텐츠 생성 시작", type="primary", use_container_width=True):
if not topic:
st.error("❌ 주제를 입력해주세요!")
else:
# 프로그레스 바
progress_bar = st.progress(0)
status_container = st.empty()
try:
# Step 1: 리서치 (0-20%)
status_container.markdown("### 🔍 Step 2: 트렌드 분석 및 리서치")
progress_bar.progress(5)
researcher = WebResearcher()
with st.spinner("웹 검색 중..."):
trends = researcher.analyze_trends(topic)
references = researcher.gather_references(topic, num_sources=3)
progress_bar.progress(20)
with st.expander("📊 트렌드 분석 결과", expanded=False):
st.json(trends)
with st.expander("📚 참고 자료", expanded=False):
for i, ref in enumerate(references, 1):
st.markdown(f"**{i}. [{ref['title']}]({ref['url']})**")
st.caption(ref['summary'])
# Step 2: 기획 (20-30%)
status_container.markdown("### 📋 Step 3: 콘텐츠 기획")
progress_bar.progress(25)
planner = ContentPlanner()
with st.spinner("콘텐츠 기획 중..."):
plan = planner.create_plan(
topic=topic,
audience=target_audience,
content_type=content_type,
trend_data=trends
)
progress_bar.progress(30)
with st.expander("📝 콘텐츠 플랜", expanded=True):
st.json(plan)
# Step 3: 작성 (30-50%)
status_container.markdown("### ✍️ Step 4: 콘텐츠 작성")
progress_bar.progress(35)
writer = ContentWriter()
with st.spinner("블로그 작성 중... (1-2분 소요)"):
blog_content = writer.write_blog(
plan=plan,
references=references,
tone=writing_tone,
length=content_length
)
progress_bar.progress(50)
st.markdown("### 📄 생성된 블로그")
st.markdown(blog_content)
# 세션에 저장
st.session_state.content_data['blog'] = blog_content
st.session_state.content_data['plan'] = plan
# Step 4: SEO 최적화 (50-60%)
status_container.markdown("### 🔍 Step 5: SEO 최적화")
progress_bar.progress(55)
optimizer = SEOOptimizer()
with st.spinner("SEO 분석 중..."):
seo_data = optimizer.optimize(blog_content)
progress_bar.progress(60)
with st.expander("📊 SEO 분석", expanded=False):
st.json(seo_data)
st.session_state.content_data['seo'] = seo_data
# Step 5: 소셜 포스트 생성 (60-70%)
status_container.markdown("### 📱 Step 6: 소셜 미디어 포스트")
progress_bar.progress(65)
with st.spinner("소셜 포스트 생성 중..."):
social_posts = writer.create_social_posts(
blog_content,
platforms=["instagram", "twitter", "linkedin"]
)
progress_bar.progress(70)
st.session_state.content_data['social'] = social_posts
tabs = st.tabs(["Instagram", "Twitter", "LinkedIn"])
for tab, platform in zip(tabs, ["instagram", "twitter", "linkedin"]):
with tab:
st.markdown(social_posts[platform])
# Step 6: 썸네일 생성 (70-90%)
status_container.markdown("### 🎨 Step 7: 비주얼 생성")
progress_bar.progress(75)
# 제목 추출
title_line = blog_content.split('\n')[0].replace('#', '').strip()
with st.spinner("썸네일 이미지 생성 중... (GPU 필요, 30초-1분)"):
thumbnail_gen = ThumbnailGenerator(use_gpu=use_gpu)
thumbnail = thumbnail_gen.generate(
topic=topic,
style=visual_style,
width=1024,
height=576,
seed=42
)
# 텍스트 추가
thumbnail_final = thumbnail_gen.add_text_overlay(
thumbnail,
title=title_line[:40],
subtitle=None
)
progress_bar.progress(85)
st.markdown("### 🖼️ 생성된 썸네일")
st.image(thumbnail_final, use_column_width=True)
st.session_state.generated_images['thumbnail'] = thumbnail_final
# 소셜 카드 생성
with st.spinner("소셜 미디어 카드 생성 중..."):
card_maker = SocialCardMaker()
social_cards = {}
for platform in ["instagram", "twitter", "linkedin"]:
card = card_maker.create_card(
platform=platform,
title=title_line[:30],
subtitle=target_audience,
color_scheme=color_scheme,
logo_text=author_name
)
social_cards[platform] = card
progress_bar.progress(90)
st.markdown("### 📱 소셜 미디어 카드")
card_cols = st.columns(3)
for col, (platform, card) in zip(card_cols, social_cards.items()):
with col:
st.markdown(f"**{platform.title()}**")
st.image(card, use_column_width=True)
st.session_state.generated_images['social_cards'] = social_cards
# Step 7: 내보내기 (90-100%)
status_container.markdown("### 💾 Step 8: 파일 저장")
progress_bar.progress(95)
metadata = {
'title': title_line,
'author': author_name,
'date': datetime.now().strftime('%Y-%m-%d'),
'description': seo_data.get('raw', {}).get('meta_description', '')[:160],
'keywords': []
}
# Markdown 저장
md_exporter = MarkdownExporter()
md_path = md_exporter.export_blog(
blog_content,
metadata,
seo_data,
output_path=f"outputs/{project_name.replace(' ', '_')}.md"
)
# HTML 저장
html_exporter = HTMLExporter()
html_path = html_exporter.export(
blog_content,
metadata,
output_path=f"outputs/{project_name.replace(' ', '_')}.html"
)
# 이미지 저장
import os
os.makedirs("outputs/images", exist_ok=True)
thumbnail_final.save(f"outputs/images/thumbnail.png")
for platform, card in social_cards.items():
card.save(f"outputs/images/social_{platform}.png")
progress_bar.progress(100)
# 완료 메시지
st.success("✅ 모든 콘텐츠 생성 완료!")
status_container.markdown("### 🎉 생성 완료!")
# 다운로드 섹션
st.markdown("---")
st.markdown("### 📥 다운로드")
download_cols = st.columns(4)
with download_cols[0]:
# Markdown 다운로드
with open(md_path, 'r', encoding='utf-8') as f:
st.download_button(
"📝 Markdown",
f.read(),
file_name=f"{project_name}.md",
mime="text/markdown"
)
with download_cols[1]:
# HTML 다운로드
with open(html_path, 'r', encoding='utf-8') as f:
st.download_button(
"🌐 HTML",
f.read(),
file_name=f"{project_name}.html",
mime="text/html"
)
with download_cols[2]:
# 썸네일 다운로드
buf = io.BytesIO()
thumbnail_final.save(buf, format='PNG')
st.download_button(
"🖼️ 썸네일",
buf.getvalue(),
file_name="thumbnail.png",
mime="image/png"
)
with download_cols[3]:
st.info("💡 모든 파일은 outputs/ 폴더에도 저장됩니다")
except Exception as e:
st.error(f"❌ 오류 발생: {str(e)}")
st.exception(e)
# Footer
st.markdown("---")
st.markdown("""
<div style='text-align: center; color: #666; padding: 20px;'>
<p><strong>🎨 AI Content Studio</strong></p>
<p>Research • Planning • Writing • SEO • Visual • Export</p>
<p style='font-size: 0.9rem; margin-top: 10px;'>
Powered by GPT-4, Claude, Stable Diffusion
</p>
</div>
""", unsafe_allow_html=True)
실행
streamlit run app.py
🚀 Step 7: 최종 정리 및 배포 준비 (15분)
README.md
# 🎨 AI Content Studio
AI 기반 올인원 콘텐츠 생성 플랫폼
## ✨ 주요 기능
- 📊 **자동 트렌드 분석**: 실시간 웹 검색으로 최신 트렌드 파악
- 📝 **AI 콘텐츠 작성**: GPT-4/Claude로 고품질 블로그 포스트 생성
- 🔍 **SEO 최적화**: 메타 태그, 키워드, 가독성 자동 분석
- 📱 **소셜 미디어 포스트**: Instagram, Twitter, LinkedIn 맞춤 콘텐츠
- 🎨 **자동 썸네일 생성**: Stable Diffusion으로 고품질 이미지
- 📤 **다양한 출력 형식**: Markdown, HTML, 이미지 파일
## 🛠️ 기술 스택
- **LLM**: OpenAI GPT-4, Anthropic Claude
- **Image Generation**: Stable Diffusion
- **Web Search**: Tavily API, DuckDuckGo
- **Framework**: Streamlit
- **Export**: Markdown, HTML, Jinja2
## 📦 설치
```bash
# 저장소 클론
git clone https://github.com/yourusername/ai-content-studio.git
cd ai-content-studio
# 가상환경 생성
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# 패키지 설치
pip install -r requirements.txt
⚙️ 환경 설정
.env 파일 생성:
OPENAI_API_KEY=your_openai_key
ANTHROPIC_API_KEY=your_anthropic_key
TAVILY_API_KEY=your_tavily_key # 선택
HUGGINGFACE_TOKEN=your_hf_token
🚀 실행
streamlit run app.py
브라우저에서 http://localhost:8501 접속
📖 사용 방법
- 주제 입력: 생성하고 싶은 콘텐츠의 주제 입력
- 타겟 설정: 독자층과 콘텐츠 타입 선택
- 생성 시작: "콘텐츠 생성 시작" 버튼 클릭
- 결과 확인: 생성된 블로그, 소셜 포스트, 이미지 확인
- 다운로드: 원하는 형식으로 다운로드
🎯 활용 사례
- 📝 블로그 운영자: 매주 새로운 포스트 자동 생성
- 📱 소셜 미디어 매니저: 플랫폼별 맞춤 콘텐츠
- 🏢 기업 마케팅: 제품/서비스 소개 콘텐츠
- 🎓 교육자: 강의 자료 및 학습 콘텐츠
🔧 커스터마이징
프롬프트 수정
utils/prompt_library.py에서 프롬프트 템플릿 수정
스타일 변경
src/visual/social_card_maker.py에서 색상/레이아웃 수정
모델 변경
utils/llm_client.py에서 다른 LLM 모델 사용
📊 프로젝트 구조
ai-content-studio/
├── src/
│ ├── content/ # 콘텐츠 생성
│ ├── research/ # 리서치
│ ├── visual/ # 비주얼 생성
│ └── export/ # 출력
├── utils/ # 유틸리티
├── outputs/ # 생성 결과
├── app.py # 메인 앱
└── requirements.txt
🤝 기여
Pull Request 환영합니다!
📄 라이선스
MIT License
📞 문의
Issues 탭에서 버그 리포트나 기능 제안을 남겨주세요.
---
## 🎓 Capstone 완성 체크리스트
### 필수 기능
- [x] 웹 리서치 및 트렌드 분석
- [x] AI 콘텐츠 기획
- [x] 블로그 포스트 작성
- [x] SEO 최적화
- [x] 소셜 미디어 포스트 생성
- [x] 썸네일 이미지 생성
- [x] 소셜 미디어 카드 생성
- [x] Markdown/HTML 출력
- [x] Streamlit UI
### 심화 기능 (선택)
- [ ] 사용자 인증 시스템
- [ ] 프로젝트 저장/불러오기
- [ ] 일정 자동 발행 (스케줄링)
- [ ] A/B 테스트
- [ ] 분석 대시보드
- [ ] API 서버화 (FastAPI)
- [ ] Docker 컨테이너화
- [ ] 클라우드 배포 (AWS/GCP)
---
## 🎉 축하합니다!
**6개 프로젝트를 모두 완성했습니다!**
배운 내용:
1. ✅ LLM 기본 사용 및 프롬프트 엔지니어링
2. ✅ RAG 시스템 구축
3. ✅ AI 에이전트 개발
4. ✅ 고급 추론 기법 (CoT, ToT, Self-Consistency)
5. ✅ 이미지 생성 (Stable Diffusion)
6. ✅ 실전 프로젝트 통합
---
## 🚀 다음 단계
### 1. 포트폴리오 만들기
- GitHub에 프로젝트 업로드
- README 작성
- 데모 영상 녹화
### 2. 배포하기
- Streamlit Cloud (무료)
- Hugging Face Spaces
- Railway / Render
### 3. 심화 학습
- LangChain 고급 기능
- Fine-tuning 실습
- 프로덕션 최적화
- 비용 관리
### 4. 취업/창업
- 포트폴리오로 취업 지원
- 프리랜서 프로젝트
- SaaS 서비스 론칭
---
**어떤 방향으로 더 발전시키고 싶으신가요?** 🌟
반응형
