Spaces:
Sleeping
Sleeping
""" | |
메인 그라디오 애플리케이션 | |
이벤트 공지사항 자동 생성기 | |
""" | |
import gradio as gr | |
import json | |
import tempfile | |
import sys | |
import os | |
from datetime import datetime | |
from PIL import Image | |
from typing import Dict, List, Any, Optional | |
# 현재 디렉토리를 Python 경로에 추가 | |
current_dir = os.path.dirname(os.path.abspath(__file__)) | |
sys.path.append(current_dir) | |
# 모듈 import (절대 경로로 수정) | |
try: | |
from modules.ai_analyzer import MonthlyConceptAnalyzer, EventNoticeGenerator | |
from modules.image_analyzer import ImageAnalyzer, format_image_analysis_result | |
from modules.design_guide import DesignGuideGenerator | |
from modules.utils import EventUtils, ValidationUtils, TextUtils | |
except ImportError: | |
# modules 폴더가 없는 경우 직접 정의된 클래스들 사용 | |
import warnings | |
warnings.warn("modules 폴더를 찾을 수 없습니다. 내장 클래스를 사용합니다.") | |
# 필요한 클래스들을 여기에 직접 정의 | |
from datetime import datetime | |
import random | |
import cv2 | |
import numpy as np | |
from sklearn.cluster import KMeans | |
class EventGeneratorApp: | |
"""이벤트 생성기 메인 애플리케이션""" | |
def __init__(self): | |
self.concept_analyzer = MonthlyConceptAnalyzer() | |
self.notice_generator = EventNoticeGenerator() | |
self.image_analyzer = ImageAnalyzer() | |
self.design_generator = DesignGuideGenerator() | |
# 애플리케이션 상태 | |
self.current_concepts = [] | |
self.selected_concept = None | |
self.current_image_analysis = None | |
def create_interface(self): | |
"""그라디오 인터페이스 생성""" | |
with gr.Blocks( | |
theme=gr.themes.Soft(), | |
title="스마트 이벤트 공지사항 생성기", | |
css=""" | |
.main-header { | |
text-align: center; | |
padding: 20px; | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
color: white; | |
border-radius: 10px; | |
margin-bottom: 20px; | |
} | |
.input-section { | |
background: #f8f9fa; | |
padding: 20px; | |
border-radius: 10px; | |
box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
} | |
.output-section { | |
background: white; | |
padding: 20px; | |
border-radius: 10px; | |
box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
} | |
.concept-card { | |
background: #e3f2fd; | |
padding: 15px; | |
border-radius: 8px; | |
margin: 10px 0; | |
border-left: 4px solid #2196f3; | |
} | |
.success-message { | |
background: #d4edda; | |
color: #155724; | |
padding: 10px; | |
border-radius: 5px; | |
border: 1px solid #c3e6cb; | |
} | |
.error-message { | |
background: #f8d7da; | |
color: #721c24; | |
padding: 10px; | |
border-radius: 5px; | |
border: 1px solid #f5c6cb; | |
} | |
""" | |
) as demo: | |
# 헤더 | |
with gr.Column(elem_classes="main-header"): | |
gr.Markdown(""" | |
# 🎉 AI 이벤트 공지사항 생성기 | |
### 실시간 트렌드 분석으로 완벽한 이벤트 기획을 도와드립니다 | |
""") | |
with gr.Row(): | |
# 좌측: 입력 영역 | |
with gr.Column(scale=1, elem_classes="input-section"): | |
gr.Markdown("## ⚙️ 기본 설정") | |
year_input = gr.Number( | |
value=2025, | |
label="📅 연도", | |
minimum=2024, | |
maximum=2030, | |
step=1, | |
info="이벤트를 진행할 연도를 선택해주세요" | |
) | |
month_input = gr.Dropdown( | |
choices=[f"{i}월" for i in range(1, 13)], | |
label="📅 이벤트 월 선택", | |
value=f"{datetime.now().month}월", | |
info="이벤트를 진행할 월을 선택해주세요" | |
) | |
analyze_concepts_btn = gr.Button( | |
"🧠 AI 컨셉 분석 시작", | |
variant="secondary", | |
size="lg" | |
) | |
# 컨셉 선택 영역 (초기에는 숨김) | |
with gr.Group(visible=False) as concept_selection_group: | |
concept_selector = gr.Dropdown( | |
label="🎨 추천 컨셉 선택", | |
interactive=True, | |
info="AI가 분석한 컨셉 중 하나를 선택해주세요" | |
) | |
concept_preview = gr.Textbox( | |
label="📊 선택된 컨셉 미리보기", | |
lines=6, | |
interactive=False | |
) | |
gr.Markdown("## 🎯 이벤트 설정") | |
event_type_selector = gr.Dropdown( | |
choices=[ | |
"댓글 달기 이벤트", | |
"게시글 작성 이벤트", | |
"좋아요/공감 이벤트", | |
"출석체크 이벤트", | |
"추천인 이벤트", | |
"리뷰/후기 이벤트", | |
"사진 업로드 이벤트", | |
"퀴즈/설문 이벤트", | |
"직접 입력" | |
], | |
label="🎯 이벤트 유형 선택", | |
value="댓글 달기 이벤트", | |
info="진행하고 싶은 이벤트 유형을 선택해주세요" | |
) | |
custom_event_input = gr.Textbox( | |
label="✏️ 커스텀 이벤트 상세 설명", | |
placeholder="'직접 입력' 선택시 원하는 이벤트 내용을 자세히 설명해주세요", | |
visible=False, | |
lines=4 | |
) | |
gr.Markdown("## 📸 레퍼런스 분석 (선택사항)") | |
reference_image_input = gr.Image( | |
label="레퍼런스 이미지 업로드", | |
type="pil", | |
height=250, | |
info="기존 디자인이나 참고하고 싶은 이미지를 업로드하면 분석해서 반영합니다" | |
) | |
# 생성 버튼 (초기에는 숨김) | |
generate_notice_btn = gr.Button( | |
"✨ 완성된 공지사항 생성하기", | |
variant="primary", | |
size="lg", | |
visible=False | |
) | |
# 우측: 결과 출력 영역 | |
with gr.Column(scale=2, elem_classes="output-section"): | |
with gr.Tabs() as output_tabs: | |
with gr.TabItem("🧠 AI 컨셉 분석 결과"): | |
concept_analysis_output = gr.Markdown( | |
value="👆 먼저 좌측에서 연도와 월을 선택한 후 'AI 컨셉 분석 시작' 버튼을 클릭해주세요", | |
elem_classes="concept-card" | |
) | |
with gr.TabItem("📝 완성된 공지사항"): | |
final_notice_output = gr.Textbox( | |
label="생성된 이벤트 공지사항", | |
lines=30, | |
placeholder="컨셉 선택 후 '완성된 공지사항 생성하기' 버튼을 클릭하면 결과가 나타납니다", | |
show_copy_button=True, | |
max_lines=50 | |
) | |
with gr.Row(): | |
download_txt_btn = gr.DownloadButton( | |
"💾 텍스트 파일로 다운로드", | |
size="sm", | |
variant="secondary", | |
visible=False | |
) | |
regenerate_btn = gr.Button( | |
"🔄 다시 생성하기", | |
size="sm", | |
visible=False | |
) | |
# 텍스트 분석 정보 | |
text_stats = gr.Textbox( | |
label="📊 공지사항 통계", | |
lines=3, | |
interactive=False, | |
visible=False | |
) | |
with gr.TabItem("🎨 디자인 가이드"): | |
design_guide_output = gr.Markdown( | |
value="컨셉 분석 완료 후 이 탭에서 디자인 가이드를 확인할 수 있습니다" | |
) | |
color_palette_display = gr.HTML( | |
label="추천 컬러 팔레트", | |
visible=False | |
) | |
with gr.TabItem("📊 레퍼런스 이미지 분석"): | |
image_analysis_output = gr.Textbox( | |
label="업로드된 이미지 분석 결과", | |
lines=15, | |
placeholder="레퍼런스 이미지를 업로드하면 자동으로 분석 결과가 나타납니다", | |
interactive=False | |
) | |
extracted_elements_output = gr.JSON( | |
label="추출된 디자인 요소들", | |
visible=False | |
) | |
# 상태 관리용 변수들 | |
concepts_state = gr.State([]) | |
selected_concept_state = gr.State(None) | |
image_analysis_state = gr.State(None) | |
# 이벤트 핸들러 함수들 | |
def handle_concept_analysis(year, month): | |
"""AI 컨셉 분석 처리""" | |
try: | |
# 월 번호 추출 | |
month_num = int(month.replace('월', '')) | |
# AI 컨셉 분석 실행 | |
concepts = self.concept_analyzer.analyze_monthly_concepts(month_num, year) | |
self.current_concepts = concepts | |
# 컨셉 선택 드롭다운 업데이트 | |
concept_choices = [f"{concept['name']} - {concept['theme']}" for concept in concepts] | |
# 분석 결과 마크다운 생성 | |
analysis_md = f"# 🎯 {year}년 {month} AI 분석 결과\n\n" | |
analysis_md += f"**분석 완료 시간:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" | |
for i, concept in enumerate(concepts, 1): | |
colors_preview = " ".join([f"<span style='background:{color}; padding:2px 8px; border-radius:3px; color:white; font-size:10px;'>{color}</span>" for color in concept['colors'][:3]]) | |
analysis_md += f""" | |
## {i}. {concept['name']} | |
**테마:** {concept['theme']} | |
**어필 포인트:** {concept['target_appeal']} | |
**예상 참여도:** ⭐ {concept['participation_score']}/10점 | |
**추천 이벤트:** {concept['event_style']} | |
**색상 팔레트:** {colors_preview} | |
**선정 이유:** {concept['reason']} | |
--- | |
""" | |
return ( | |
gr.update(visible=True), # concept_selection_group | |
gr.update(choices=concept_choices, value=concept_choices[0] if concept_choices else None), # concept_selector | |
gr.update(visible=True), # generate_notice_btn | |
analysis_md, # concept_analysis_output | |
concepts # concepts_state | |
) | |
except Exception as e: | |
error_message = f"❌ 컨셉 분석 중 오류가 발생했습니다: {str(e)}" | |
return ( | |
gr.update(visible=False), | |
gr.update(choices=[], value=None), | |
gr.update(visible=False), | |
error_message, | |
[] | |
) | |
def handle_concept_selection(selected_concept_name, concepts_data): | |
"""컨셉 선택 처리""" | |
try: | |
if selected_concept_name and concepts_data: | |
# 선택된 컨셉 찾기 | |
selected_idx = next(i for i, c in enumerate(concepts_data) | |
if f"{c['name']} - {c['theme']}" == selected_concept_name) | |
selected_concept = concepts_data[selected_idx] | |
self.selected_concept = selected_concept | |
# 미리보기 텍스트 생성 | |
preview_text = f"""🎨 컨셉: {selected_concept['name']} | |
🏷️ 테마: {selected_concept['theme']} | |
💬 캐치프레이즈: {selected_concept['catchphrase']} | |
🎯 타겟 어필: {selected_concept['target_appeal']} | |
⭐ 예상 참여도: {selected_concept['participation_score']}/10점 | |
🎪 추천 이벤트: {selected_concept['event_style']} | |
🎨 색상: {', '.join(selected_concept['colors'][:3])} | |
💡 차별화: {selected_concept['competitive_edge']}""" | |
return preview_text, selected_concept | |
return "", None | |
except Exception as e: | |
return f"❌ 컨셉 선택 오류: {str(e)}", None | |
def handle_event_type_change(event_type): | |
"""이벤트 유형 변경 처리""" | |
return gr.update(visible=(event_type == "직접 입력")) | |
def handle_image_upload(image): | |
"""이미지 업로드 처리""" | |
try: | |
if image: | |
analysis = self.image_analyzer.analyze_reference_image(image) | |
self.current_image_analysis = analysis | |
formatted_result = format_image_analysis_result(analysis) | |
return ( | |
formatted_result, # image_analysis_output | |
gr.update(visible=True, value=analysis), # extracted_elements_output | |
analysis # image_analysis_state | |
) | |
return "", gr.update(visible=False), None | |
except Exception as e: | |
error_msg = f"❌ 이미지 분석 오류: {str(e)}" | |
return error_msg, gr.update(visible=False), None | |
def handle_final_generation(year, month, selected_concept, event_type, custom_event, image_analysis): | |
"""최종 공지사항 생성 처리""" | |
try: | |
if not selected_concept: | |
return ( | |
"❌ 먼저 컨셉을 선택해주세요!", | |
"", | |
gr.update(visible=False), | |
gr.update(visible=False), | |
gr.update(visible=False), | |
"" | |
) | |
# 최종 공지사항 생성 | |
notice = self.notice_generator.generate_event_notice( | |
concept_data=selected_concept, | |
event_type=event_type, | |
custom_event=custom_event, | |
reference_analysis=image_analysis | |
) | |
# 디자인 가이드 생성 | |
design_guide = self.design_generator.generate_design_guidelines( | |
selected_concept, image_analysis | |
) | |
# 색상 팔레트 HTML 생성 | |
color_html = self.design_generator.generate_color_palette_html( | |
selected_concept.get('colors', []) | |
) | |
# 텍스트 통계 생성 | |
stats = TextUtils.count_characters(notice) | |
stats_text = f"총 글자수: {stats['total_chars']:,}자 | 단어수: {stats['words']:,}개 | 줄수: {stats['lines']:,}줄" | |
# 파일 다운로드 준비 | |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
filename = f"event_notice_{timestamp}.txt" | |
return ( | |
notice, # final_notice_output | |
design_guide, # design_guide_output | |
gr.update(visible=True, value=color_html), # color_palette_display | |
gr.update(visible=True), # download_txt_btn | |
gr.update(visible=True), # regenerate_btn | |
stats_text # text_stats | |
) | |
except Exception as e: | |
error_message = f"❌ 공지사항 생성 중 오류가 발생했습니다: {str(e)}" | |
return ( | |
error_message, | |
"", | |
gr.update(visible=False), | |
gr.update(visible=False), | |
gr.update(visible=False), | |
"" | |
) | |
def handle_download_preparation(notice_content): | |
"""다운로드 파일 준비""" | |
try: | |
if notice_content and notice_content.strip(): | |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
filename = f"event_notice_{timestamp}.txt" | |
# 임시 파일 생성 | |
temp_file = tempfile.NamedTemporaryFile( | |
mode='w', | |
suffix='.txt', | |
delete=False, | |
encoding='utf-8' | |
) | |
temp_file.write(notice_content) | |
temp_file.close() | |
return temp_file.name | |
return None | |
except Exception as e: | |
print(f"파일 준비 오류: {e}") | |
return None | |
def handle_regeneration(year, month, selected_concept, event_type, custom_event, image_analysis): | |
"""공지사항 재생성""" | |
return handle_final_generation(year, month, selected_concept, event_type, custom_event, image_analysis) | |
# 이벤트 바인딩 | |
analyze_concepts_btn.click( | |
handle_concept_analysis, | |
inputs=[year_input, month_input], | |
outputs=[ | |
concept_selection_group, | |
concept_selector, | |
generate_notice_btn, | |
concept_analysis_output, | |
concepts_state | |
] | |
) | |
concept_selector.change( | |
handle_concept_selection, | |
inputs=[concept_selector, concepts_state], | |
outputs=[concept_preview, selected_concept_state] | |
) | |
event_type_selector.change( | |
handle_event_type_change, | |
inputs=[event_type_selector], | |
outputs=[custom_event_input] | |
) | |
reference_image_input.change( | |
handle_image_upload, | |
inputs=[reference_image_input], | |
outputs=[image_analysis_output, extracted_elements_output, image_analysis_state] | |
) | |
generate_notice_btn.click( | |
handle_final_generation, | |
inputs=[ | |
year_input, | |
month_input, | |
selected_concept_state, | |
event_type_selector, | |
custom_event_input, | |
image_analysis_state | |
], | |
outputs=[ | |
final_notice_output, | |
design_guide_output, | |
color_palette_display, | |
download_txt_btn, | |
regenerate_btn, | |
text_stats | |
] | |
) | |
regenerate_btn.click( | |
handle_regeneration, | |
inputs=[ | |
year_input, | |
month_input, | |
selected_concept_state, | |
event_type_selector, | |
custom_event_input, | |
image_analysis_state | |
], | |
outputs=[ | |
final_notice_output, | |
design_guide_output, | |
color_palette_display, | |
download_txt_btn, | |
regenerate_btn, | |
text_stats | |
] | |
) | |
# 다운로드 버튼 클릭 이벤트 | |
download_txt_btn.click( | |
handle_download_preparation, | |
inputs=[final_notice_output], | |
outputs=[] | |
) | |
return demo | |
def main(): | |
"""메인 실행 함수""" | |
print("🎉 이벤트 공지사항 생성기를 시작합니다...") | |
try: | |
# 애플리케이션 초기화 | |
app = EventGeneratorApp() | |
# 그라디오 인터페이스 생성 | |
demo = app.create_interface() | |
# 서버 실행 | |
print("🚀 서버를 시작합니다...") | |
demo.launch( | |
server_name="0.0.0.0", | |
server_port=7860, | |
share=True, | |
debug=True, | |
show_tips=True, | |
show_error=True | |
) | |
except Exception as e: | |
print(f"❌ 애플리케이션 시작 중 오류 발생: {e}") | |
import traceback | |
traceback.print_exc() | |
if __name__ == "__main__": | |
main() |