import os
import random
import re
import requests
import logging
import tempfile
from bs4 import BeautifulSoup
from datetime import datetime
from zoneinfo import ZoneInfo
import html
from PIL import Image
from urllib.request import urlopen
import markdown2
import gradio as gr
# 로깅 설정 (INFO 레벨)
logging.basicConfig(level=logging.INFO)
# 상수 정의
TARGET_CHAR_LENGTH = 4000
MIN_SECTION_LENGTH = 600
MAX_TOKENS = 15000
TEMPERATURE = 0.75
TOP_P = 0.95
# API 관련 설정
gemini_api_key = os.getenv("GEMINI_API_KEY")
# --- Google Gemini SDK 초기화 ---
from google import genai
from google.genai import types
client = genai.Client(api_key=gemini_api_key)
# -------------------------------
# 기본 도우미 함수들
# -------------------------------
def remove_unwanted_phrases(text):
"""불필요한 표현 제거 함수"""
unwanted_phrases = [
'여러분', '최근', '마지막으로', '결론적으로', '결국',
'종합적으로', '따라서', '마무리', '끝으로', '요약',
'한 줄 요약', '정리하자면', '총정리', '글을 마치며',
'이상으로', '추천드립니다', '참고하세요', '도움이 되셨길',
'좋은 하루 되세요', '다음 글에서', '도움이 되었길',
'즐거운 하루 되세요', '감사합니다'
]
# 문단별로 나누어 처리
lines = text.split('\n')
result_lines = []
for line in lines:
if "다음 섹션에서는" in line:
parts = line.split("다음 섹션에서는")
if parts[0].strip():
result_lines.append(parts[0].strip())
else:
# 불필요한 표현 제거 (구두점 포함)
for phrase in unwanted_phrases:
# 불필요한 표현 앞뒤의 구두점과 공백까지 포함하여 제거
pattern = rf'(\b{re.escape(phrase)}\b[\s,.!?]*)|([,.!?]*\b{re.escape(phrase)}\b)'
line = re.sub(pattern, '', line)
# 문장 내 잔여 공백 및 구두점 정리
line = re.sub(r'\s{2,}', ' ', line) # 연속 공백 제거
line = line.strip() # 앞뒤 공백 제거
result_lines.append(line)
return '\n'.join(result_lines)
def convert_to_html(text):
"""마크다운 형식을 HTML로 변환 - 개선된 버전"""
# 제목 형식 변환 (# -> h1, ## -> h2, 등)
for i in range(6, 0, -1):
pattern = r'^' + r'#' * i + r'\s+(.+)$'
text = re.sub(pattern, r'
{line}
') elif not line.strip(): result_lines.append('참고 자료가 없습니다. 최소 하나 이상의 참고 자료를 입력해주세요.
" if not selected_feature.strip(): return "핵심기능이 선택되지 않았습니다. 핵심기능을 입력해주세요.
" # 스타일 프롬프트 가져오기 style_prompt = get_style_prompt(style) # 블로그 글 생성 프롬프트 구성 blog_prompt = f""" [핵심기능 집중형 상품리뷰 작성 요청] 선택한 핵심기능: {selected_feature} [리뷰 작성 형식] 1. 리뷰는 '도입부', '5가지 소재', '마무리' 구조로 작성하세요. 2. 마크다운 형식은 최소한으로 사용하고, 가능한 한 일반 텍스트로 작성하세요. 3. 각 부분은 명확히 구분되어야 하며, 서술형 문장으로 자연스럽게 이어지도록 작성하세요. 4. 단락은 적절히 나누되, 너무 짧은 단락을 많이 만들지 마세요. [리뷰 내용 구조] 1. 도입부 (전체의 10%) - 선택한 핵심기능의 중요성과 특징을 간략히 소개 - 이 기능이 상품에서 어떤 가치를 제공하는지 설명 - 마지막 문장에서 본문에서 다룰 내용을 예고 2. 5가지 소재 (전체의 80%) - 선택한 핵심기능에 가장 적합한 5가지 소재를 자유롭게 선정하세요 - 각 소재는 기능의 서로 다른 측면을 다루어야 합니다 - 예시 소재: 기술적 원리, 작동 방식, 성능 분석, 경쟁 제품 비교, 활용 방법, 사용 경험, 설정 팁, 업데이트 이력, 산업 표준과의 비교, 사용 시나리오, 호환성, 한계점과 개선 방향 등 - 각 소재는 비슷한 분량으로 자연스럽게 연결되어야 합니다 3. 마무리 (전체의 10%) - 이 기능의 종합적 평가와 가치 - 어떤 유형의 사용자에게 특히 유용한지 - 핵심기능과 제품 전체에 대한 최종 견해 [중요 작성 지침] 1. 전체 글은 최소 4000자 이상으로 작성하세요. 2. 각 소재는 최소 600자 이상 작성하고, 서로 유기적으로 연결되게 하세요. 3. 제목은 따로 사용하지 말고, 도입부, 소재, 마무리를 하나의 연결된 글로 작성하세요. 4. 기술적 정확성을 유지하면서 선택한 핵심기능에 대한 심층적 분석과 구체적인 정보를 제공하세요. 5. 마케팅적 과장 표현보다는 사실적이고 분석적인 표현을 사용하세요. 6. 불필요한 반복이나 장황한 설명은 피하고, 핵심 정보와 통찰을 강조하세요. 7. 전체 글의 일관성을 유지하고, 문단 간 자연스러운 흐름을 만드세요. 8. 소재는 핵심기능의 성격에 맞게 가장 적절한 것을 자유롭게 선정하세요. 참고글: {references[0]} {references[1] if len(references) > 1 else ""} {references[2] if len(references) > 2 else ""} {style_prompt} """ # Gemini API 호출 logging.info("핵심기능 집중형 블로그 글 생성 시작") blog_content = call_gemini_api(blog_prompt, temperature=0.7) logging.info(f"생성된 원본 글 길이: {len(blog_content)}") # 후처리 processed_content = post_process_blog(blog_content, style) # 글자 수 체크 char_count = len(processed_content) logging.info(f"처리 후 블로그 글 글자 수: {char_count}") # 글자 수가 목표에 미달하면 확장 시도 if char_count < TARGET_CHAR_LENGTH: logging.info(f"글자 수 부족 ({char_count} < {TARGET_CHAR_LENGTH}), 확장 시도") expansion_prompt = f""" [핵심기능 분석 확장 요청] 현재 글은 목표 글자수인 4000자에 미치지 못합니다. 현재 글자수는 약 {char_count}자입니다. 선택한 핵심기능: {selected_feature} [확장 지침] 1. 원래 글의 구조(도입부, 5가지 소재, 마무리)를 유지하면서 내용을 확장하세요. 2. 각 소재에 더 구체적인 정보, 예시, 분석 내용을 추가하세요. 3. 서술적 흐름을 유지하고, 불필요한 마크다운 사용은 피하세요. 4. 글의 전체 일관성과 응집성을 유지하세요. 원본 글: {processed_content} """ # 확장 시도 expanded_content = call_gemini_api(expansion_prompt, temperature=0.75) processed_content = post_process_blog(expanded_content, style) # 다시 글자 수 체크 char_count = len(processed_content) logging.info(f"확장 후 블로그 글 글자 수: {char_count}") # HTML 변환 final_html = convert_to_html(processed_content) return final_html except Exception as e: logging.error(f"블로그 글 생성 중 오류 발생: {str(e)}") return f"블로그 글 생성 중 오류 발생: {str(e)}
" def get_style_prompt(style="친근한"): """블로그 글의 스타일 프롬프트를 반환""" prompts = { "친근한": """ [친근한 핵심기능 리뷰 스타일 가이드] 1. 톤과 어조 - 대화하듯 편안하고 친근한 말투 사용 (예: "오늘은 ~에 대해 알아볼게요") - 1인칭 시점으로 직접 사용한 경험을 생생하게 표현 - 구어체와 일상적인 표현 사용하여 친근함 유지 2. 문장 및 어투 - '해요체'로 작성 (예: "~했어요", "~인 것 같아요") - 문장은 길지 않게 자연스럽게 연결 - 기술적 내용도 쉽고 이해하기 편한 표현으로 설명 3. 정보 전달 방식 - 개인 경험과 체감을 중심으로 정보 전달 - 전문적인 내용도 일상적인 비유와 예시로 풀어서 설명 - "제가 사용해보니~", "실제로 경험해보면~"과 같은 표현 활용 - 독자에게 직접 말하듯 중간중간 "~하시면 좋아요" 같은 조언 추가 """, "일반": """ [일반적인 핵심기능 리뷰 스타일 가이드] 1. 톤과 어조 - 객관적이고 중립적인 톤 유지 - 직접적인 경험과 객관적 데이터를 균형 있게 활용 - 존댓말 사용하되 딱딱하지 않게 표현 2. 문장 및 어투 - '합니다체' 사용 (예: "~합니다", "~입니다") - 명확하고 간결한 문장 구성 - 내용의 논리적 흐름을 중시 3. 정보 전달 방식 - 사실과 데이터를 중심으로 내용 전개 - 개인 경험과 객관적 분석을 적절히 혼합 - 불필요한 과장이나 주관적 평가 최소화 - 실용적인 관점에서 기능의 장단점 균형있게 서술 """, "전문적인": """ [전문적인 핵심기능 리뷰 스타일 가이드] 1. 톤과 어조 - 전문적이고 분석적인 톤 사용 - 기술적 깊이와 정확성 강조 - 존중과 권위를 느낄 수 있는 표현 사용 2. 문장 및 어투 - '합니다체'로 일관성 있게 작성 - 논리적이고 체계적인 문장 구성 - 전문 용어를 적절히 활용하되 필요시 간략한 설명 제공 3. 정보 전달 방식 - 기술적 원리와 메커니즘에 대한 심층 분석 - 벤치마크 데이터와 구체적 수치를 활용한 객관적 평가 - 경쟁 제품과의 세부적인 기술 비교 제공 - 기능의 기술적 한계와 발전 가능성에 대한 통찰 제시 """ } return prompts.get(style, prompts["친근한"]) def call_gemini_api(prompt, temperature=TEMPERATURE, top_p=TOP_P): """Gemini API 호출 함수""" try: logging.info("Gemini API 호출 시작") response = client.models.generate_content( model="gemini-2.0-flash", contents=[prompt], config=types.GenerateContentConfig( max_output_tokens=MAX_TOKENS, temperature=temperature, top_p=top_p ) ) logging.info("Gemini API 호출 완료") return response.text.strip() except Exception as e: logging.error(f"Gemini API 호출 중 오류 발생: {str(e)}") return f"API 호출 중 오류 발생: {str(e)}" # API 함수들 def generate_outline_4(category, style, ref1, ref2, ref3): features = generate_outline(category, style, ref1, ref2, ref3) # 3개의 문자열 튜플 반환 return (features[0], features[1], features[2]) def generate_blog_post_4(category, style, ref1, ref2, ref3, outline): # outline은 선택된 핵심기능 이름 return generate_blog_post(category, style, ref1, ref2, ref3, outline)