import sys import os import gradio as gr #from dotenv import load_dotenv # 현재 디렉토리를 Python 경로에 추가 current_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.append(current_dir) # TinyTroupe 패키지 경로 추가 #tinytroupe_path = os.path.join(current_dir, 'TinyTroupe') #sys.path.append(tinytroupe_path) from TinyTroupe.wellfood_marketing.wellfood_persona_analysis import WellFoodPersona, ResultsExtractor, generate_marketing_report from TinyTroupe.tinytroupe.agent import TinyPerson import time # Load environment variables #load_dotenv() # OpenAI API 키는 Hugging Face Space의 Secrets에서 자동으로 로드됨 def analyze_single_persona(age_group, gender, product_description, progress=gr.Progress()): """단일 페르소나에 대한 제품 분석 수행""" # 페르소나 정보 가져오기 persona_info = WellFoodPersona.PERSONAS[age_group][gender] # 페르소나 기본 정보 포맷팅 base_info = f"""## {persona_info['display_name']}의 제품 평가 ### 페르소나 정보 - 직업: {persona_info['occupation']} - 관심사: {', '.join(persona_info['interests'])} - 라이프스타일: {persona_info['lifestyle']} - 주요 고민점: {', '.join(persona_info['pain_points'])} ### 제품 분석 과정 """ current_content = base_info # 분석 시작을 표시 progress(0.1, desc=f"{persona_info['display_name']} 페르소나 초기화 중...") yield current_content, None # 기존 페르소나가 있는지 확인 existing_persona = None if hasattr(TinyPerson, '_instances'): for instance in TinyPerson._instances: if instance.name == persona_info['name']: existing_persona = instance break # 기존 페르소나가 없을 때만 새로 생성 if existing_persona is None: persona = WellFoodPersona(age_group, gender) else: persona = WellFoodPersona.__new__(WellFoodPersona) persona.persona = existing_persona persona.age_group = age_group persona.gender = gender progress(0.2, desc=f"{persona_info['display_name']} 응답 생성 시작...") current_content += f"\n🤔 {persona_info['display_name']}이(가) 생각하고 있습니다...\n" yield current_content, None # 페르소나의 응답 처리 response = persona.analyze_product(product_description) progress(0.8, desc=f"{persona_info['display_name']} 결과 추출 중...") current_content += f"\n💬 {persona_info['display_name']}이(가) 의견을 제시하고 있습니다...\n" yield current_content, None # ResultsExtractor를 사용하여 구조화된 결과 추출 extractor = ResultsExtractor() res = extractor.extract_results_from_agent( persona.persona, extraction_objective="제품 평가 결과를 추출합니다. 각 필드에 해당하는 내용을 추출하세요.", situation=product_description, fields=["first_impression", "purchase_intention", "usage_scenario", "price_evaluation", "improvement_points"], fields_hints={ "first_impression": "제품에 대한 첫인상을 한글로 작성", "purchase_intention": "구매 의향 점수(1-10)와 그 이유를 포함하는 객체", "usage_scenario": "주된 구매 동기나 사용 상황을 한글로 작성", "price_evaluation": "가격 적정성에 대한 평가를 한글로 작성", "improvement_points": "개선되었으면 하는 점을 한글로 작성" }, verbose=True ) progress(1.0, desc=f"{persona_info['display_name']} 분석 완료") # 최종 결과 포맷팅 final_result = f"""## {persona_info['display_name']}의 제품 평가 ### 페르소나 정보 - 직업: {persona_info['occupation']} - 관심사: {', '.join(persona_info['interests'])} - 라이프스타일: {persona_info['lifestyle']} - 주요 고민점: {', '.join(persona_info['pain_points'])} ### 최종 평가 결과 1. 첫인상: {res['first_impression']} 2. 구매의향: {res['purchase_intention']['score']}/10 이유: {res['purchase_intention']['reason']} 3. 사용상황: {res['usage_scenario']} 4. 가격평가: {res['price_evaluation']} 5. 개선점: {res['improvement_points']} """ yield final_result, res def process_input(product_description, selected_personas, generate_report, product_name, progress=gr.Progress()): """입력 처리 및 결과 생성""" all_results = [] selected_persona_info = [] # 선택된 페르소나 정보 저장 persona_responses = "" marketing_report = "" current_analysis = "" # 현재 진행 중인 분석 결과를 저장 # 분석 시작 상태 표시 progress(0, desc="페르소나 분석을 시작합니다...") yield persona_responses, marketing_report, gr.update(value="분석 중...", interactive=False) # 선택된 페르소나들에 대한 분석 수행 for i, persona_str in enumerate(selected_personas, 1): age, gender = persona_str.split(':')[0].strip().split('_') persona_name = persona_str.split(':')[1].strip() # 개별 페르소나 분석 for result, res in analyze_single_persona(age, gender, product_description, progress): if res: # 최종 결과인 경우 persona_responses = result + "\n\n" + "-"*50 + "\n\n" + persona_responses all_results.append(res) selected_persona_info.append({ 'age': age, 'gender': gender, 'info': WellFoodPersona.PERSONAS[age][gender] }) else: # 중간 결과인 경우 current_analysis = result yield current_analysis + "\n\n" + persona_responses, marketing_report, gr.update(value="분석 중...", interactive=False) time.sleep(0.1) # 화면 업데이트를 위한 짧은 대기 # 마케팅 리포트 생성 if generate_report and all_results: progress(0.9, desc="마케팅 리포트 생성 중...") marketing_report = generate_marketing_report(all_results, selected_persona_info, product_name) # 완료 표시 progress(1.0, desc="분석이 완료되었습니다!") yield persona_responses, marketing_report, gr.update(value="분석 시작", interactive=True) def create_demo(): """Gradio 데모 인터페이스 생성""" # 제품 설명 예시 example_name = "제로 플레인 요거트" example_features = """무가당 순수 플레인 요거트: 첨가물이나 인공 감미료 없이, 요거트 본연의 깔끔하고 산뜻한 맛을 유지 고농축 유산균 함유: 소화와 장 건강에 도움을 주는 다양한 유산균 균주를 함유하여, 건강 증진 효과 제공 고단백 저지방: 균형 잡힌 단백질 공급과 낮은 지방 함량으로, 다이어트 및 건강 관리에 최적화 신선한 현지 원유 사용: 지역 유기농 우유를 엄선하여 사용, 신선도와 안전성을 극대화 휴대성과 간편함: 소포장 형태로 언제 어디서나 간편하게 섭취 가능 가격: 개당 2,900원 유통기한: 제조일로부터 15일 (냉장 보관)""" example_composition = """제로 플레인 요거트 (150g): – 고농축 플레인 요거트, 무가당 및 무첨가 상태로 본연의 맛을 즐길 수 있도록 제작 옵션 추가 팩: 신선 과일 토핑 팩 (20g): 블루베리, 딸기 등 계절 과일 선택 가능 (별도 구매 옵션) 건과일/견과류 믹스 (15g): 요거트와 함께 곁들여 영양 및 식감 보강""" # 페르소나 선택 옵션 생성 def get_persona_choices(): """현재 페르소나 정보를 바탕으로 선택 옵션 생성""" persona_choices = [] persona_display = {} for age in ['20', '30', '40']: for gender in ['M', 'F']: persona_info = WellFoodPersona.PERSONAS[age][gender] choice_key = f"{age}_{gender}" persona_choices.append(choice_key) persona_display[choice_key] = persona_info['display_name'] return [f"{k}: {v}" for k, v in persona_display.items()] def update_persona_info(age, gender, name, occupation, interests, lifestyle, pain_points): """페르소나 정보 업데이트""" if age and gender: WellFoodPersona.PERSONAS[age][gender].update({ 'name': name, 'occupation': occupation, 'interests': [i.strip() for i in interests.split(',')], 'lifestyle': lifestyle, 'pain_points': [p.strip() for p in pain_points.split(',')], 'display_name': f"{name} ({age}대 {'남성' if gender == 'M' else '여성'}, {occupation})" }) # 페르소나 선택 옵션 업데이트 updated_choices = get_persona_choices() return ( f"페르소나 정보가 업데이트되었습니다: {name}", gr.update(choices=updated_choices, value=[]), # choices와 value 모두 업데이트 gr.update(choices=updated_choices, value=[]) # choices와 value 모두 업데이트 ) return "연령대와 성별을 선택해주세요.", None, None def load_persona_info(age, gender): """선택된 페르소나 정보 로드""" if age and gender: info = WellFoodPersona.PERSONAS[age][gender] return ( info['name'], info['occupation'], ', '.join(info['interests']), info['lifestyle'], ', '.join(info['pain_points']) ) return "", "", "", "", "" # 페르소나 선택 옵션을 상태(state)로 관리 persona_choices = gr.State(value=get_persona_choices()) with gr.Blocks(title="웰푸드 페르소나 분석 데모") as demo: with gr.Tab("제품 분석"): gr.Markdown("# 웰푸드 페르소나 분석 데모") gr.Markdown("분석 방법: 제품 정보 입력 -> 분석 페르소나 선택 -> 분석 시작") with gr.Row(): with gr.Column(scale=1): # 제품 정보를 구조화된 형태로 입력받기 product_name = gr.Textbox( label="제품명", placeholder="예: 데일리 밸런스 도시락", value=example_name ) product_features = gr.Textbox( label="제품 특징", placeholder="가격, 유통기한 등 제품의 주요 특징을 입력하세요", lines=6, value=example_features ) product_composition = gr.Textbox( label="제품 구성", placeholder="제품의 구성과 용량을 입력하세요", lines=4, value=example_composition ) personas = gr.CheckboxGroup( choices=get_persona_choices(), label="분석할 페르소나 선택", value=get_persona_choices(), info="분석하고 싶은 페르소나를 모두 선택하세요. \n페르소나를 수정하고 싶으시다면 상단 페르소나 설정 탭에서 수정할 수 있습니다." ) generate_report = gr.Checkbox( label="마케팅 리포트 생성", value=True, info="선택된 페르소나들의 모든 분석이 끝나면, 분석 결과를 종합한 마케팅 리포트를 생성합니다." ) submit_btn = gr.Button("분석 시작", variant="primary") with gr.Column(scale=2): with gr.Tab("페르소나 분석"): persona_output = gr.Markdown() with gr.Tab("마케팅 리포트"): marketing_output = gr.Markdown() with gr.Tab("페르소나 설정"): gr.Markdown("# 페르소나 정보 설정") with gr.Row(): age_select = gr.Dropdown( choices=['20', '30', '40'], label="연령대", value='20' ) gender_select = gr.Dropdown( choices=[('남성', 'M'), ('여성', 'F')], label="성별", value='M' ) # 초기값 설정을 위해 기본 페르소나 정보 가져오기 initial_info = WellFoodPersona.PERSONAS['20']['M'] name_input = gr.Textbox( label="이름", value=initial_info['name'] ) occupation_input = gr.Textbox( label="직업", value=initial_info['occupation'] ) interests_input = gr.Textbox( label="관심사", placeholder="쉼표(,)로 구분하여 입력하세요", value=', '.join(initial_info['interests']) ) lifestyle_input = gr.Textbox( label="라이프스타일", value=initial_info['lifestyle'] ) pain_points_input = gr.Textbox( label="주요 고민점", placeholder="쉼표(,)로 구분하여 입력하세요", value=', '.join(initial_info['pain_points']) ) update_btn = gr.Button("페르소나 정보 업데이트") update_result = gr.Markdown() # 이벤트 핸들러 연결 age_select.change( load_persona_info, inputs=[age_select, gender_select], outputs=[name_input, occupation_input, interests_input, lifestyle_input, pain_points_input] ) gender_select.change( load_persona_info, inputs=[age_select, gender_select], outputs=[name_input, occupation_input, interests_input, lifestyle_input, pain_points_input] ) # 업데이트 버튼 클릭 시 페르소나 정보 업데이트 및 선택 옵션 갱신 update_btn.click( update_persona_info, inputs=[age_select, gender_select, name_input, occupation_input, interests_input, lifestyle_input, pain_points_input], outputs=[update_result, personas, personas] ) # 제품 정보를 조합하여 전체 설명 생성 def combine_product_info(name, features, composition): return f"""웰푸드의 신제품 "{name}" 특징: {features} 구성: {composition}""" # process_input을 감싸는 새로운 함수 추가 def process_input_wrapper(name, features, comp, personas, gen_report, progress=gr.Progress()): """process_input 함수를 감싸서 제너레이터를 처리하는 함수""" combined_description = combine_product_info(name, features, comp) # 제너레이터의 모든 중간 결과를 yield for result in process_input(combined_description, personas, gen_report, name, progress): yield result # submit 버튼의 이벤트 핸들러 수정 submit_btn.click( fn=process_input_wrapper, inputs=[product_name, product_features, product_composition, personas, generate_report], outputs=[persona_output, marketing_output, submit_btn], api_name="analyze" ) return demo if __name__ == "__main__": demo = create_demo() demo.queue().launch(share=True)