diff --git a/smarketing-ai/Dockerfile b/smarketing-ai/Dockerfile deleted file mode 100644 index 68c4544..0000000 --- a/smarketing-ai/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -# 1. Dockerfile에 한글 폰트 추가 -FROM python:3.11-slim - -WORKDIR /app - -# 시스템 패키지 및 한글 폰트 설치 -RUN apt-get update && apt-get install -y \ - fonts-dejavu-core \ - fonts-noto-cjk \ - fonts-nanum \ - wget \ - && rm -rf /var/lib/apt/lists/* - -# 추가 한글 폰트 다운로드 (선택사항) -RUN mkdir -p /app/fonts && \ - wget -O /app/fonts/NotoSansKR-Bold.ttf \ - "https://fonts.gstatic.com/s/notosanskr/v13/PbykFmXiEBPT4ITbgNA5Cgm20xz64px_1hVWr0wuPNGmlQNMEfD4.ttf" - -# Python 의존성 설치 -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -# 애플리케이션 코드 복사 -COPY . . - -# 업로드 및 포스터 디렉토리 생성 -RUN mkdir -p uploads/temp uploads/posters templates/poster_templates - -# 포트 노출 -EXPOSE 5000 - -# 애플리케이션 실행 -CMD ["python", "app.py"] \ No newline at end of file diff --git a/smarketing-ai/app.py b/smarketing-ai/app.py index f133993..d6ef693 100644 --- a/smarketing-ai/app.py +++ b/smarketing-ai/app.py @@ -12,10 +12,7 @@ from config.config import Config from services.poster_service import PosterService from services.sns_content_service import SnsContentService from models.request_models import ContentRequest, PosterRequest, SnsContentGetRequest, PosterContentGetRequest -from services.poster_service_v2 import PosterServiceV2 -from api.marketing_tip_api import marketing_tip_bp - - +from services.poster_service_v3 import PosterServiceV3 def create_app(): @@ -33,7 +30,7 @@ def create_app(): # 서비스 인스턴스 생성 poster_service = PosterService() - poster_service_v2 = PosterServiceV2() + poster_service_v3 = PosterServiceV3() sns_content_service = SnsContentService() # Blueprint 등록 @@ -103,8 +100,8 @@ def create_app(): @app.route('/api/ai/poster', methods=['GET']) def generate_poster_content(): """ - 홍보 포스터 생성 API (개선된 버전) - 원본 이미지 보존 + 한글 텍스트 오버레이 + 홍보 포스터 생성 API + 실제 제품 이미지를 포함한 분위기 배경 포스터 생성 """ try: # JSON 요청 데이터 검증 @@ -121,6 +118,23 @@ def create_app(): if field not in data: return jsonify({'error': f'필수 필드가 누락되었습니다: {field}'}), 400 + # 날짜 변환 처리 + start_date = None + end_date = None + if data.get('startDate'): + try: + from datetime import datetime + start_date = datetime.strptime(data['startDate'], '%Y-%m-%d').date() + except ValueError: + return jsonify({'error': 'startDate 형식이 올바르지 않습니다. YYYY-MM-DD 형식을 사용하세요.'}), 400 + + if data.get('endDate'): + try: + from datetime import datetime + end_date = datetime.strptime(data['endDate'], '%Y-%m-%d').date() + except ValueError: + return jsonify({'error': 'endDate 형식이 올바르지 않습니다. YYYY-MM-DD 형식을 사용하세요.'}), 400 + # 요청 모델 생성 poster_request = PosterContentGetRequest( title=data.get('title'), @@ -133,16 +147,18 @@ def create_app(): emotionIntensity=data.get('emotionIntensity'), menuName=data.get('menuName'), eventName=data.get('eventName'), - startDate=data.get('startDate'), - endDate=data.get('endDate') + startDate=start_date, + endDate=end_date ) - # 포스터 생성 - # result = poster_service.generate_poster(poster_request) - result = poster_service_v2.generate_poster(poster_request) + # 포스터 생성 (V3 사용) + result = poster_service_v3.generate_poster(poster_request) if result['success']: - return jsonify({'content': result['content']}) + return jsonify({ + 'content': result['content'], + 'analysis': result.get('analysis', {}) + }) else: return jsonify({'error': result['error']}), 500 @@ -285,4 +301,7 @@ def create_app(): if __name__ == '__main__': app = create_app() - app.run(host='0.0.0.0', port=5001, debug=True) + host = os.getenv('SERVER_HOST', '0.0.0.0') + port = int(os.getenv('SERVER_PORT', '5001')) + + app.run(host=host, port=port, debug=True) diff --git a/smarketing-ai/config/config.py b/smarketing-ai/config/config.py index de6f276..6b63540 100644 --- a/smarketing-ai/config/config.py +++ b/smarketing-ai/config/config.py @@ -4,7 +4,10 @@ Flask 애플리케이션 설정 """ import os from dotenv import load_dotenv + load_dotenv() + + class Config: """애플리케이션 설정 클래스""" # Flask 기본 설정 @@ -19,8 +22,9 @@ class Config: ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'} # 템플릿 설정 POSTER_TEMPLATE_PATH = 'templates/poster_templates' + @staticmethod def allowed_file(filename): """업로드 파일 확장자 검증""" return '.' in filename and \ - filename.rsplit('.', 1)[1].lower() in Config.ALLOWED_EXTENSIONS \ No newline at end of file + filename.rsplit('.', 1)[1].lower() in Config.ALLOWED_EXTENSIONS diff --git a/smarketing-ai/deployment/Dockerfile b/smarketing-ai/deployment/Dockerfile new file mode 100644 index 0000000..223ed21 --- /dev/null +++ b/smarketing-ai/deployment/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# 애플리케이션 코드 복사 +COPY . . + +# 포트 노출 +EXPOSE 5001 + +# 애플리케이션 실행 +CMD ["python", "app.py"] \ No newline at end of file diff --git a/smarketing-ai/deployment/manifest/configmap.yaml b/smarketing-ai/deployment/manifest/configmap.yaml new file mode 100644 index 0000000..798804c --- /dev/null +++ b/smarketing-ai/deployment/manifest/configmap.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: smarketing-config + namespace: smarketing +data: + SERVER_HOST: "0.0.0.0" + SERVER_PORT: "5001" + UPLOAD_FOLDER: "/app/uploads" + MAX_CONTENT_LENGTH: "16777216" # 16MB + ALLOWED_EXTENSIONS: "png,jpg,jpeg,gif,webp" \ No newline at end of file diff --git a/smarketing-ai/deployment/manifest/deployment.yaml b/smarketing-ai/deployment/manifest/deployment.yaml new file mode 100644 index 0000000..1a5df1d --- /dev/null +++ b/smarketing-ai/deployment/manifest/deployment.yaml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: smarketing + namespace: smarketing + labels: + app: smarketing +spec: + replicas: 1 + selector: + matchLabels: + app: smarketing + template: + metadata: + labels: + app: smarketing + spec: + imagePullSecrets: + - name: acr-secret + containers: + - name: smarketing + image: dg0408cr.azurecr.io/smarketing-ai:latest + imagePullPolicy: Always + ports: + - containerPort: 5001 + resources: + requests: + cpu: 256m + memory: 512Mi + limits: + cpu: 1024m + memory: 2048Mi + envFrom: + - configMapRef: + name: smarketing-config + - secretRef: + name: smarketing-secret + volumeMounts: + - name: upload-storage + mountPath: /app/uploads + - name: temp-storage + mountPath: /app/uploads/temp + volumes: + - name: upload-storage + emptyDir: {} + - name: temp-storage + emptyDir: {} \ No newline at end of file diff --git a/smarketing-ai/deployment/manifest/ingress.yaml b/smarketing-ai/deployment/manifest/ingress.yaml new file mode 100644 index 0000000..5b5c4f4 --- /dev/null +++ b/smarketing-ai/deployment/manifest/ingress.yaml @@ -0,0 +1,26 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: smarketing-ingress + namespace: smarketing + annotations: + kubernetes.io/ingress.class: nginx + nginx.ingress.kubernetes.io/proxy-body-size: "16m" + nginx.ingress.kubernetes.io/proxy-read-timeout: "300" + nginx.ingress.kubernetes.io/proxy-send-timeout: "300" + nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS" + nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization" + nginx.ingress.kubernetes.io/cors-allow-origin: "*" + nginx.ingress.kubernetes.io/enable-cors: "true" +spec: + rules: + - host: smarketing.20.249.184.228.nip.io + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: smarketing-service + port: + number: 80 \ No newline at end of file diff --git a/smarketing-ai/deployment/manifest/secret.yaml b/smarketing-ai/deployment/manifest/secret.yaml new file mode 100644 index 0000000..d013ead --- /dev/null +++ b/smarketing-ai/deployment/manifest/secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: smarketing-secret + namespace: smarketing +type: Opaque +stringData: + SECRET_KEY: "your-secret-key-change-in-production" + CLAUDE_API_KEY: "your-claude-api-key" + OPENAI_API_KEY: "your-openai-api-key" \ No newline at end of file diff --git a/smarketing-ai/deployment/manifest/service.yaml b/smarketing-ai/deployment/manifest/service.yaml new file mode 100644 index 0000000..2bbc84d --- /dev/null +++ b/smarketing-ai/deployment/manifest/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: smarketing-service + namespace: smarketing + labels: + app: smarketing +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 5001 + protocol: TCP + name: http + selector: + app: smarketing \ No newline at end of file diff --git a/smarketing-ai/models/request_models.py b/smarketing-ai/models/request_models.py index b47b257..2aba14f 100644 --- a/smarketing-ai/models/request_models.py +++ b/smarketing-ai/models/request_models.py @@ -19,8 +19,8 @@ class SnsContentGetRequest: emotionIntensity: Optional[str] = None menuName: Optional[str] = None eventName: Optional[str] = None - startDate: Optional[str] = None - endDate: Optional[str] = None + startDate: Optional[date] = None # LocalDate -> date + endDate: Optional[date] = None # LocalDate -> date @dataclass @@ -36,8 +36,8 @@ class PosterContentGetRequest: emotionIntensity: Optional[str] = None menuName: Optional[str] = None eventName: Optional[str] = None - startDate: Optional[str] = None - endDate: Optional[str] = None + startDate: Optional[date] = None # LocalDate -> date + endDate: Optional[date] = None # LocalDate -> date # 기존 모델들은 유지 diff --git a/smarketing-ai/services/poster_service_v3.py b/smarketing-ai/services/poster_service_v3.py new file mode 100644 index 0000000..c8c2385 --- /dev/null +++ b/smarketing-ai/services/poster_service_v3.py @@ -0,0 +1,220 @@ +""" +포스터 생성 서비스 V3 +OpenAI DALL-E를 사용한 이미지 생성 (메인 메뉴 이미지 1개 + 프롬프트 내 예시 링크 10개) +""" +import os +from typing import Dict, Any, List +from datetime import datetime +from utils.ai_client import AIClient +from utils.image_processor import ImageProcessor +from models.request_models import PosterContentGetRequest + + +class PosterServiceV3: + """포스터 생성 서비스 V3 클래스""" + + def __init__(self): + """서비스 초기화""" + self.ai_client = AIClient() + self.image_processor = ImageProcessor() + + # Azure Blob Storage 예시 이미지 링크 10개 (카페 음료 관련) + self.example_images = [ + "https://stdigitalgarage02.blob.core.windows.net/ai-content/example1.png", + "https://stdigitalgarage02.blob.core.windows.net/ai-content/example2.png", + "https://stdigitalgarage02.blob.core.windows.net/ai-content/example3.png", + "https://stdigitalgarage02.blob.core.windows.net/ai-content/example4.png", + "https://stdigitalgarage02.blob.core.windows.net/ai-content/example5.png", + "https://stdigitalgarage02.blob.core.windows.net/ai-content/example6.png", + "https://stdigitalgarage02.blob.core.windows.net/ai-content/example7.png" + ] + + # 포토 스타일별 프롬프트 + self.photo_styles = { + '미니멀': '미니멀하고 깔끔한 디자인, 단순함, 여백 활용', + '모던': '현대적이고 세련된 디자인, 깔끔한 레이아웃', + '빈티지': '빈티지 느낌, 레트로 스타일, 클래식한 색감', + '컬러풀': '다채로운 색상, 밝고 생동감 있는 컬러', + '우아한': '우아하고 고급스러운 느낌, 세련된 분위기', + '캐주얼': '친근하고 편안한 느낌, 접근하기 쉬운 디자인' + } + + # 카테고리별 이미지 스타일 + self.category_styles = { + '음식': '음식 사진, 먹음직스러운, 맛있어 보이는', + '매장': '레스토랑 인테리어, 아늑한 분위기', + '이벤트': '홍보용 디자인, 눈길을 끄는', + '메뉴': '메뉴 디자인, 정리된 레이아웃', + '할인': '세일 포스터, 할인 디자인', + '음료': '시원하고 상쾌한, 맛있어 보이는 음료' + } + + # 톤앤매너별 디자인 스타일 + self.tone_styles = { + '친근한': '따뜻하고 친근한 색감, 부드러운 느낌', + '정중한': '격식 있고 신뢰감 있는 디자인', + '재미있는': '밝고 유쾌한 분위기, 활기찬 색상', + '전문적인': '전문적이고 신뢰할 수 있는 디자인' + } + + # 감정 강도별 디자인 + self.emotion_designs = { + '약함': '은은하고 차분한 색감, 절제된 표현', + '보통': '적당히 활기찬 색상, 균형잡힌 디자인', + '강함': '강렬하고 임팩트 있는 색상, 역동적인 디자인' + } + + def generate_poster(self, request: PosterContentGetRequest) -> Dict[str, Any]: + """ + 포스터 생성 (메인 이미지 1개 분석 + 예시 링크 10개 프롬프트 제공) + """ + try: + # 메인 이미지 확인 + if not request.images: + return {'success': False, 'error': '메인 메뉴 이미지가 제공되지 않았습니다.'} + + main_image_url = request.images[0] # 첫 번째 이미지가 메인 메뉴 + + # 메인 이미지 분석 + main_image_analysis = self._analyze_main_image(main_image_url) + + # 포스터 생성 프롬프트 생성 (예시 링크 10개 포함) + prompt = self._create_poster_prompt_v3(request, main_image_analysis) + + # OpenAI로 이미지 생성 + image_url = self.ai_client.generate_image_with_openai(prompt, "1024x1536") + + return { + 'success': True, + 'content': image_url, + 'analysis': { + 'main_image': main_image_analysis, + 'example_images_used': len(self.example_images) + } + } + + except Exception as e: + return { + 'success': False, + 'error': str(e) + } + + def _analyze_main_image(self, image_url: str) -> Dict[str, Any]: + """ + 메인 메뉴 이미지 분석 + """ + temp_files = [] + try: + # 이미지 다운로드 + temp_path = self.ai_client.download_image_from_url(image_url) + if temp_path: + temp_files.append(temp_path) + + # 이미지 분석 + image_info = self.image_processor.get_image_info(temp_path) + image_description = self.ai_client.analyze_image(temp_path) + colors = self.image_processor.analyze_colors(temp_path, 5) + + return { + 'url': image_url, + 'info': image_info, + 'description': image_description, + 'dominant_colors': colors, + 'is_food': self.image_processor.is_food_image(temp_path) + } + else: + return { + 'url': image_url, + 'error': '이미지 다운로드 실패' + } + + except Exception as e: + return { + 'url': image_url, + 'error': str(e) + } + finally: + # 임시 파일 정리 + for temp_file in temp_files: + try: + os.remove(temp_file) + except: + pass + + def _create_poster_prompt_v3(self, request: PosterContentGetRequest, + main_analysis: Dict[str, Any]) -> str: + """ + V3 포스터 생성을 위한 AI 프롬프트 생성 (한글, 글자 완전 제외, 메인 이미지 기반 + 예시 링크 10개 포함) + """ + + # 메인 이미지 정보 활용 + main_description = main_analysis.get('description', '맛있는 음식') + main_colors = main_analysis.get('dominant_colors', []) + main_image_url = main_analysis.get('url', '') + image_info = main_analysis.get('info', {}) + is_food = main_analysis.get('is_food', False) + + # 이미지 크기 및 비율 정보 + aspect_ratio = image_info.get('aspect_ratio', 1.0) if image_info else 1.0 + image_orientation = "가로형" if aspect_ratio > 1.2 else "세로형" if aspect_ratio < 0.8 else "정사각형" + + # 색상 정보를 텍스트로 변환 + color_description = "" + if main_colors: + color_rgb = main_colors[:3] # 상위 3개 색상 + color_description = f"주요 색상 RGB 값: {color_rgb}를 기반으로 한 조화로운 색감" + + # 예시 이미지 링크들을 문자열로 변환 + example_links = "\n".join([f"- {link}" for link in self.example_images]) + + prompt = f""" + ## 카페 홍보 포스터 디자인 요청 + + ### 📋 기본 정보 + 카테고리: {request.category} + 콘텐츠 타입: {request.contentType} + 메뉴명: {request.menuName or '없음'} + 메뉴 정보: {main_description} + + ### 📅 이벤트 기간 + 시작일: {request.startDate or '지금'} + 종료일: {request.endDate or '한정 기간'} + 이벤트 시작일과 종료일은 필수로 포스터에 명시해주세요. + + ### 🎨 디자인 요구사항 + 메인 이미지 처리 + - 기존 메인 이미지는 변경하지 않고 그대로 유지 + - 포스터 전체 크기의 1/3 이하로 배치 + - 이미지와 조화로운 작은 장식 이미지 추가 + - 크기: {image_orientation} + + 텍스트 요소 + - 메뉴명 (필수) + - 간단한 추가 홍보 문구 (새로 생성, 한글) 혹은 "{request.requirement or '눈길을 끄는 전문적인 디자인'}"라는 요구사항에 맞는 문구 + - 메뉴명 외 추가되는 문구는 1줄만 작성 + + + 텍스트 배치 규칙 + - 글자가 이미지 경계를 벗어나지 않도록 주의 + - 모서리에 너무 가깝게 배치하지 말 것 + - 적당한 크기로 가독성 확보 + - 아기자기한 한글 폰트 사용 + + ### 🎨 디자인 스타일 + 참조 이미지 + {example_links}의 URL을 참고하여 비슷한 스타일로 제작 + + 색상 가이드 + {color_description} + 전체적인 디자인 방향 + + 타겟: 한국 카페 고객층 + 스타일: 화려하고 매력적인 디자인 + 목적: 소셜미디어 공유용 (적합한 크기) + 톤앤매너: 맛있어 보이는 색상, 방문 유도하는 비주얼 + + ### 🎯 최종 목표 + 고객들이 "이 카페에 가보고 싶다!"라고 생각하게 만드는 시각적으로 매력적인 홍보 포스터 제작 + """ + + return prompt diff --git a/smarketing-ai/uploads/temp/poster_20250616_132932.png b/smarketing-ai/uploads/temp/poster_20250616_132932.png new file mode 100644 index 0000000..8da34f8 Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_132932.png differ diff --git a/smarketing-ai/uploads/temp/poster_20250616_133204.png b/smarketing-ai/uploads/temp/poster_20250616_133204.png new file mode 100644 index 0000000..ffdff9e Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_133204.png differ diff --git a/smarketing-ai/uploads/temp/poster_20250616_133514.png b/smarketing-ai/uploads/temp/poster_20250616_133514.png new file mode 100644 index 0000000..42e62db Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_133514.png differ diff --git a/smarketing-ai/uploads/temp/poster_20250616_134301.png b/smarketing-ai/uploads/temp/poster_20250616_134301.png new file mode 100644 index 0000000..d8550cd Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_134301.png differ diff --git a/smarketing-ai/uploads/temp/poster_20250616_134523.png b/smarketing-ai/uploads/temp/poster_20250616_134523.png new file mode 100644 index 0000000..7d8aacc Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_134523.png differ diff --git a/smarketing-ai/uploads/temp/poster_20250616_134806.png b/smarketing-ai/uploads/temp/poster_20250616_134806.png new file mode 100644 index 0000000..e274a92 Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_134806.png differ diff --git a/smarketing-ai/uploads/temp/poster_20250616_135236.png b/smarketing-ai/uploads/temp/poster_20250616_135236.png new file mode 100644 index 0000000..fc1350c Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_135236.png differ diff --git a/smarketing-ai/uploads/temp/poster_20250616_135525.png b/smarketing-ai/uploads/temp/poster_20250616_135525.png new file mode 100644 index 0000000..a59a3b5 Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_135525.png differ diff --git a/smarketing-ai/uploads/temp/poster_20250616_135751.png b/smarketing-ai/uploads/temp/poster_20250616_135751.png new file mode 100644 index 0000000..2756b40 Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_135751.png differ diff --git a/smarketing-ai/uploads/temp/poster_20250616_140227.png b/smarketing-ai/uploads/temp/poster_20250616_140227.png new file mode 100644 index 0000000..b0f9f1e Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_140227.png differ diff --git a/smarketing-ai/uploads/temp/poster_20250616_141018.png b/smarketing-ai/uploads/temp/poster_20250616_141018.png new file mode 100644 index 0000000..c77adee Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_141018.png differ diff --git a/smarketing-ai/uploads/temp/poster_20250616_141225.png b/smarketing-ai/uploads/temp/poster_20250616_141225.png new file mode 100644 index 0000000..10fc5fe Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_141225.png differ diff --git a/smarketing-ai/uploads/temp/poster_20250616_141428.png b/smarketing-ai/uploads/temp/poster_20250616_141428.png new file mode 100644 index 0000000..e03c115 Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_141428.png differ diff --git a/smarketing-ai/uploads/temp/poster_20250616_141528.png b/smarketing-ai/uploads/temp/poster_20250616_141528.png new file mode 100644 index 0000000..27c34a2 Binary files /dev/null and b/smarketing-ai/uploads/temp/poster_20250616_141528.png differ diff --git a/smarketing-ai/uploads/temp/temp_1e7658c3-43ba-4d61-9bfc-4c1d9c2a5098.jpg b/smarketing-ai/uploads/temp/temp_1e7658c3-43ba-4d61-9bfc-4c1d9c2a5098.jpg deleted file mode 100644 index 3d8f70d..0000000 Binary files a/smarketing-ai/uploads/temp/temp_1e7658c3-43ba-4d61-9bfc-4c1d9c2a5098.jpg and /dev/null differ diff --git a/smarketing-ai/uploads/temp/temp_44b7841c-56e8-4c94-b769-4580f00f7723.jpg b/smarketing-ai/uploads/temp/temp_44b7841c-56e8-4c94-b769-4580f00f7723.jpg deleted file mode 100644 index 4216da1..0000000 Binary files a/smarketing-ai/uploads/temp/temp_44b7841c-56e8-4c94-b769-4580f00f7723.jpg and /dev/null differ diff --git a/smarketing-ai/utils/ai_client.py b/smarketing-ai/utils/ai_client.py index 7b1fe52..51a36ab 100644 --- a/smarketing-ai/utils/ai_client.py +++ b/smarketing-ai/utils/ai_client.py @@ -78,19 +78,36 @@ class AIClient: raise Exception("OpenAI API 키가 설정되지 않았습니다.") response = self.openai_client.images.generate( - model="dall-e-3", + model="gpt-image-1", prompt=prompt, - size="1024x1024", - quality="hd", # 고품질 설정 - style="vivid", # 또는 "natural" + size=size, n=1, ) - return response.data[0].url + # base64를 파일로 저장 + import base64 + from datetime import datetime + + b64_data = response.data[0].b64_json + image_data = base64.b64decode(b64_data) + + # 로컬 파일 저장 + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + filename = f"poster_{timestamp}.png" + filepath = os.path.join('uploads', 'temp', filename) + + os.makedirs(os.path.dirname(filepath), exist_ok=True) + + with open(filepath, 'wb') as f: + f.write(image_data) + + print(f"✅ 이미지 저장 완료: {filepath}") + + # 그냥 파일 경로만 반환 + return filepath except Exception as e: - print(f"OpenAI 이미지 생성 실패: {e}") - raise Exception(f"이미지 생성 중 오류가 발생했습니다: {str(e)}") + raise Exception(f"이미지 생성 실패: {str(e)}") def generate_text(self, prompt: str, max_tokens: int = 1000) -> str: """