Recent Posts
Recent Comments
반응형
«   2025/11   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
Archives
Today
Total
관리 메뉴

오늘도 공부

Capstone Project - 나만의 AI 프로젝트 구축 본문

카테고리 없음

Capstone Project - 나만의 AI 프로젝트 구축

행복한 수지아빠 2025. 10. 31. 16:18
반응형

🎯 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 접속

📖 사용 방법

  1. 주제 입력: 생성하고 싶은 콘텐츠의 주제 입력
  2. 타겟 설정: 독자층과 콘텐츠 타입 선택
  3. 생성 시작: "콘텐츠 생성 시작" 버튼 클릭
  4. 결과 확인: 생성된 블로그, 소셜 포스트, 이미지 확인
  5. 다운로드: 원하는 형식으로 다운로드

🎯 활용 사례

  • 📝 블로그 운영자: 매주 새로운 포스트 자동 생성
  • 📱 소셜 미디어 매니저: 플랫폼별 맞춤 콘텐츠
  • 🏢 기업 마케팅: 제품/서비스 소개 콘텐츠
  • 🎓 교육자: 강의 자료 및 학습 콘텐츠

🔧 커스터마이징

프롬프트 수정

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 서비스 론칭

---

**어떤 방향으로 더 발전시키고 싶으신가요?** 🌟
반응형