import os import gradio as gr import random import time import logging import google.generativeai as genai # 로깅 설정 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("api_debug.log"), logging.StreamHandler() ] ) logger = logging.getLogger("idea_generator") # Gemini API 키는 환경 변수 GEMINI_API_KEY에서 가져옵니다. GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") genai.configure(api_key=GEMINI_API_KEY) # 슬래시("/")가 포함된 변환 문자열에서 두 옵션 중 하나만 선택하는 헬퍼 함수 def choose_alternative(transformation): if "/" not in transformation: return transformation parts = transformation.split("/") if len(parts) != 2: return random.choice([part.strip() for part in parts]) left = parts[0].strip() right = parts[1].strip() if " " in left: tokens = left.split(" ", 1) prefix = tokens[0] if not right.startswith(prefix): option1 = left option2 = prefix + " " + right else: option1 = left option2 = right return random.choice([option1, option2]) else: return random.choice([left, right]) # 창의적인 모델/컨셉/형상 변화 아이디어를 위한 카테고리 (기존 '물리적 변화' 사전 유지) physical_transformation_categories = { "공간적 변화": [ "전진/후진 이동", "좌/우 이동", "상승/하강", "피치 회전", "요 회전", "롤 회전", "궤도 운동", "나선형 이동", "관성 드리프트", "자이로스코픽 세차 운동", "비대칭 회전", "진자 운동", "탄도학적 이동", "무중력 상태에서의 부유" ], "물리적 형상 변화": [ "부피 증가/감소", "길이 신장/수축", "너비 확장/축소", "높이 증가/감소", "밀도 변화", "무게 증가/감소", "형태 변형", "위상 변화", "비등방성 변형", "비선형 변형", "뒤틀림/비틀림", "수축/팽창 비율 변화", "모서리 라운딩/샤프닝", "파단/균열 진행", "세그먼트화" ], "표면/외관 변화": [ "색상 변화", "텍스처 변화", "투명도/불투명도 변화", "광택/무광 변화", "반사율 변화", "패턴 변화", "이색성 효과", "광변색 효과", "열변색 효과", "홀로그래픽 패턴 변화", "프레넬 반사", "표면 모핑", "나노구조 패턴 변화", "자가 청소 효과" ], "물질 상태 변화": [ "고체화/액화/기화", "결정화/용해", "산화/부식", "경화/연화", "초임계 상태 전환", "비정질화/결정화", "상분리", "콜로이드 형성/분해", "겔화/졸화", "준안정 상태 전이", "자기조립/분해", "상전이 히스테리시스", "융해", "응고" ], "열역학적 변화": [ "온도 상승/냉각", "열팽창/수축", "열 전도/단열", "압력 증가/감소", "단열 자화/탈자화", "엔트로피 변화", "열전기 효과", "자기열량 효과", "상변화 축열/방열", "열 스트레스 발생/완화", "열충격 효과", "복사 냉각/가열" ], "운동학적 변화": [ "가속/감속", "등속 운동", "진동/진동 감쇠", "충돌/반발", "회전 속도 증가/감소", "각속도 변화", "카오스적 운동", "스틱-슬립 현상", "공진/반공진", "유체역학적 양력/항력 변화", "댐핑 계수 변화", "하모닉 모션 합성", "비뉴턴 유체 거동", "회전-병진 운동 커플링" ], "구조적 변화": [ "부품 추가/제거", "조립/분해", "접이/펼침", "변형/복원", "위상최적화 형상 변화", "자가 재구성", "프랙탈 패턴 형성/소멸", "세포자동자 패턴 변화", "모듈식 변형", "발현적 구조 형성", "형상 기억 효과", "4D 프린팅 구조 변화", "부분 제거", "부분 교환", "결합", "분리" ], "전기/자기적 변화": [ "자화/탈자화", "전하 증가/감소", "전기장 생성/소멸", "자기장 생성/소멸", "초전도 상태 전이", "강유전/반강유전 전이", "양자 스핀 상태 변화", "플라즈마 상태 형성/소멸", "스핀파 전파", "광전 효과", "압전 효과", "홀 효과" ], "화학적 변화": [ "표면 코팅 변화", "물질 조성 변화", "화학 반응에 의한 변화", "촉매 활성화/비활성화", "광화학 반응", "전기화학 반응", "자기조립 단분자막 형성", "분자컴퓨팅 변화", "생체모방 표면 변화", "스마트 폴리머 반응", "화학적 진동 반응", "산화", "환원", "중합", "가수분해", "융합", "방사화" ], "시간 관련 변화": [ "노화/풍화", "마모/부식", "퇴색/변색", "손상/복구", "수명 주기 단계 변화", "사용자 인터랙션 기반 적응", "학습기반 형상 최적화", "시간적 물성 변화", "집단 기억 효과", "문화적 의미 변화", "시간 지연 응답", "이력 의존적 변화", "퍼지 시간 거동", "진화적 변화", "주기적 재생/재생성" ] } ############################################################################## # Gemini API 호출 함수 (예: gemini-2.0-flash-thinking-exp-01-21 -> 다른 모델 사용 시 수정) ############################################################################## def query_gemini_api(prompt): try: # 예시: 기존 gemini-2.0... 대신, 다른 모델이 필요하다면 교체하세요. model = genai.GenerativeModel('gemini-2.0-flash-thinking-exp-01-21') response = model.generate_content(prompt) # 응답 구조 방어적으로 처리 try: if hasattr(response, 'text'): return response.text if hasattr(response, 'candidates') and response.candidates: if len(response.candidates) > 0: candidate = response.candidates[0] if hasattr(candidate, 'content'): content = candidate.content if hasattr(content, 'parts') and content.parts: if len(content.parts) > 0: return content.parts[0].text if hasattr(response, 'parts') and response.parts: if len(response.parts) > 0: return response.parts[0].text return "Unable to generate a response. API response structure is different than expected." except Exception as inner_e: logger.error(f"Error processing response: {inner_e}") return f"An error occurred while processing the response: {str(inner_e)}" except Exception as e: logger.error(f"Error calling Gemini API: {e}") if "API key not valid" in str(e): return "API key is not valid. Please check your GEMINI_API_KEY environment variable." return f"An error occurred while calling the API: {str(e)}" ############################################################################## # 설명 확장 함수: "모델/컨셉/형상의 변화에 대한 이해와 혁신 포인트, 기능성 등을 중심"으로 ############################################################################## def enhance_with_llm(base_description, obj_name, category): prompt = f""" 다음은 '{obj_name}'의 '{category}' 관련 간단한 설명입니다: "{base_description}" 위 내용을 보다 구체화하여, 1) 창의적인 모델/컨셉/형상의 변화에 대한 이해, 2) 혁신 포인트와 기능성 등을 중심으로 3~4문장의 아이디어로 확장해 주세요. """ return query_gemini_api(prompt) ############################################################################## # 단일 키워드(오브젝트)에 대한 "창의적 변화 아이디어" 생성 ############################################################################## def generate_single_object_transformations(obj): results = {} for category, transformations in physical_transformation_categories.items(): transformation = choose_alternative(random.choice(transformations)) base_description = f"{obj}이(가) {transformation} 현상을 보인다" results[category] = {"base": base_description, "enhanced": None} return results ############################################################################## # 두 키워드에 대한 "창의적 변화 아이디어" 생성 ############################################################################## def generate_two_objects_interaction(obj1, obj2): results = {} for category, transformations in physical_transformation_categories.items(): transformation = choose_alternative(random.choice(transformations)) template = random.choice([ "{obj1}이(가) {obj2}에 결합하여 {change}가 발생했다", "{obj1}과(와) {obj2}이(가) 충돌하면서 {change}가 일어났다" ]) base_description = template.format(obj1=obj1, obj2=obj2, change=transformation) results[category] = {"base": base_description, "enhanced": None} return results ############################################################################## # 세 키워드에 대한 "창의적 변화 아이디어" 생성 ############################################################################## def generate_three_objects_interaction(obj1, obj2, obj3): results = {} for category, transformations in physical_transformation_categories.items(): transformation = choose_alternative(random.choice(transformations)) template = random.choice([ "{obj1}, {obj2}, {obj3}이(가) 삼각형 구조로 결합하여 {change}가 발생했다", "{obj1}이(가) {obj2}와(과) {obj3} 사이에서 매개체 역할을 하며 {change}를 촉진했다" ]) base_description = template.format(obj1=obj1, obj2=obj2, obj3=obj3, change=transformation) results[category] = {"base": base_description, "enhanced": None} return results ############################################################################## # 생성된 기본 설명을 LLM을 통해 확장 ############################################################################## def enhance_descriptions(results, objects): obj_name = " 및 ".join([obj for obj in objects if obj]) for category, result in results.items(): result["enhanced"] = enhance_with_llm(result["base"], obj_name, category) return results ############################################################################## # 사용자 입력(최대 3개 키워드)에 따라 창의적 변화 아이디어 생성 ############################################################################## def generate_transformations(text1, text2=None, text3=None): if text2 and text3: results = generate_three_objects_interaction(text1, text2, text3) objects = [text1, text2, text3] elif text2: results = generate_two_objects_interaction(text1, text2) objects = [text1, text2] else: results = generate_single_object_transformations(text1) objects = [text1] return enhance_descriptions(results, objects) ############################################################################## # 결과 포맷팅 ############################################################################## def format_results(results): formatted = "" for category, result in results.items(): formatted += f"## {category}\n**기본 아이디어**: {result['base']}\n\n**확장된 아이디어**: {result['enhanced']}\n\n---\n\n" return formatted ############################################################################## # Gradio UI에서 호출할 함수 ############################################################################## def process_inputs(text1, text2, text3): messages = [] messages.append("입력값 확인 중...") time.sleep(0.3) text1 = text1.strip() if text1 else None text2 = text2.strip() if text2 else None text3 = text3.strip() if text3 else None if not text1: messages.append("오류: 최소 하나의 키워드를 입력해주세요.") return "\n\n".join(messages) messages.append("창의적인 모델/컨셉/형상 변화 아이디어 생성 중...") time.sleep(0.3) results = generate_transformations(text1, text2, text3) messages.append("결과 포맷팅 중...") time.sleep(0.3) formatted = format_results(results) messages.append("완료!") messages.append(formatted) return "\n\n".join(messages) ############################################################################## # API 키 경고 메시지 ############################################################################## def get_warning_message(): if not GEMINI_API_KEY: return "⚠️ 환경 변수 GEMINI_API_KEY가 설정되지 않았습니다. Gemini API 키를 설정하세요." return "" ############################################################################## # Gradio UI ############################################################################## with gr.Blocks(title="키워드 기반 창의적 변화 아이디어 생성기", theme=gr.themes.Soft(primary_hue="teal", secondary_hue="slate", neutral_hue="neutral")) as demo: gr.HTML(""" """) gr.Markdown("# 🚀 키워드 기반 창의적 변화 아이디어 생성기") gr.Markdown("입력한 **키워드**(최대 3개)를 바탕으로, **창의적인 모델/컨셉/형상 변화**에 대한 이해와 **혁신 포인트**, **기능성** 등을 중심으로 확장된 아이디어를 제시합니다.") warning = gr.Markdown(get_warning_message()) # 좌측 입력 영역 with gr.Row(): with gr.Column(scale=1): text_input1 = gr.Textbox(label="키워드 1 (필수)", placeholder="예: 스마트폰") text_input2 = gr.Textbox(label="키워드 2 (선택)", placeholder="예: 인공지능") text_input3 = gr.Textbox(label="키워드 3 (선택)", placeholder="예: 헬스케어") submit_button = gr.Button("아이디어 생성하기") # 우측 출력 영역 (탭은 하나만 사용) with gr.Column(scale=2): with gr.TabItem("창의적인 모델/컨셉/형상 변화 아이디어", id="creative_tab"): idea_output = gr.Markdown(label="아이디어 결과") # 예시 입력 gr.Examples( examples=[ ["스마트폰", "", ""], ["커피", "책", ""], ["자동차", "로봇", "인공지능"], ["운동화", "웨어러블", "건강"], ], inputs=[text_input1, text_input2, text_input3], ) # 버튼 이벤트: 첫 번째 탭에만 연결 submit_button.click(fn=process_inputs, inputs=[text_input1, text_input2, text_input3], outputs=idea_output) if __name__ == "__main__": demo.launch(debug=True)