"""계층 분석 요청 탭. 5단계 설정 폼 → ProcessorConfig 매핑 → API 호출. """ import streamlit as st from core.api_client import ChainShiftClient # 17 Journey Types across 3 depth1 groups _JOURNEY_GROUPS: dict[str, list[tuple[str, str]]] = { "인지/비교 (Awareness & Comparison)": [ ("verification", "사실확인"), ("market_trends", "최신 트렌드"), ("preparation", "준비/필요"), ("timing", "시기/타이밍"), ("review_experience", "리뷰/경험"), ("information_discovery", "정보탐색/개념"), ("result_effectiveness", "효과/결과"), ("recommendation", "구매추천"), ("comparison", "구매추천(비교)"), ("problem_solving", "문제해결"), ("difference_pros_cons", "차이점/장단점"), ], "구매 (Purchase)": [ ("pricing", "비용/가격"), ("promotion_benefits", "프로모션/할인/혜택"), ("where_to_buy", "구매처"), ], "구매 후 (Post-Purchase)": [ ("howto", "제품/서비스 how-to"), ("refund_customer_service", "환불 A/S"), ("side_effect", "부작용"), ], } _PRODUCT_TYPES = ["product", "service", "brand"] _MODELS = ["gemini-3.1-pro-preview", "gemini-3-flash-preview", "gemini-2.5-pro", "gemini-2.5-flash"] _AGE_OPTIONS = ["10대", "20대", "30대", "40대", "50대", "60대 이상"] _GENDER_OPTIONS = ["여성", "남성"] _TRAIT_OPTIONS = ["가성비중시", "프리미엄선호", "트렌드민감", "실용주의"] def render(base_ctx: dict): """분석 요청 폼 렌더링.""" st.markdown("##### 📝 계층 분석 요청") st.caption("키워드와 분석 조건을 설정하고 분석을 시작합니다") if not base_ctx.get("api_key") and not base_ctx.get("access_token"): st.warning("인증 정보가 설정되지 않았습니다.") return client = ChainShiftClient( api_key=base_ctx.get("api_key"), access_token=base_ctx.get("access_token"), ) # ── Step 1: 입력 분석 ── st.markdown("###### 1. 입력 분석") col1, col2 = st.columns([3, 1]) with col1: raw_text = st.text_input( "분석 키워드", key="hier:raw_text", placeholder="예: 강아지 사료, 여행 가방, 전기차 보험", ) with col2: product_type = st.selectbox( "제품 유형", _PRODUCT_TYPES, key="hier:product_type", ) title = st.text_input( "분석 제목 (선택)", key="hier:title", placeholder="분석 결과 구분용 제목", ) # ── Step 2: 여정 유형 ── st.markdown("---") st.markdown("###### 2. 소비자 여정 유형") st.caption("분석에 포함할 여정 유형을 선택하세요 (기본: 구매추천)") selected_journeys: list[str] = [] for group_label, types in _JOURNEY_GROUPS.items(): with st.expander(group_label, expanded=(group_label.startswith("인지"))): for code, label in types: default = code == "recommendation" if st.checkbox( label, value=default, key=f"hier:cej:{code}", ): selected_journeys.append(code) # ── Step 3: 페르소나 (선택) ── st.markdown("---") st.markdown("###### 3. 페르소나 설정 (선택)") use_persona = st.checkbox("페르소나 적용", key="hier:use_persona") persona_ages: list[str] = [] persona_gender: str | None = None persona_trait: str | None = None if use_persona: col1, col2, col3 = st.columns(3) with col1: persona_ages = st.multiselect("연령대", _AGE_OPTIONS, key="hier:ages") with col2: gender_sel = st.selectbox( "성별", ["선택 안함"] + _GENDER_OPTIONS, key="hier:gender", ) persona_gender = gender_sel if gender_sel != "선택 안함" else None with col3: trait_sel = st.selectbox( "소비 성향", ["선택 안함"] + _TRAIT_OPTIONS, key="hier:trait", ) persona_trait = trait_sel if trait_sel != "선택 안함" else None # ── Step 4: 브랜드 컨텍스트 (선택) ── st.markdown("---") st.markdown("###### 4. 브랜드 컨텍스트 (선택)") brand_mention = st.checkbox( "질문에 브랜드 포함", key="hier:brand_mention", help="활성화하면 생성된 질문에 브랜드명이 포함됩니다", ) own_brands_input = "" if brand_mention: own_brands_input = st.text_input( "자사 브랜드 (쉼표 구분)", key="hier:own_brands", placeholder="브랜드A, 브랜드B", ) # ── Step 5: 분석 설정 ── st.markdown("---") st.markdown("###### 5. 분석 설정") col1, col2, col3 = st.columns(3) with col1: model = st.selectbox("AI 모델", _MODELS, key="hier:model") with col2: questions_per_kw = st.number_input( "키워드당 질문 수", min_value=5, max_value=100, value=25, step=5, key="hier:qpk", ) with col3: max_nodes = st.number_input( "최대 노드 수", min_value=10, max_value=500, value=100, step=10, key="hier:max_nodes", ) # ── Submit ── st.markdown("---") can_submit = bool(raw_text and raw_text.strip() and selected_journeys) if not raw_text or not raw_text.strip(): st.info("분석 키워드를 입력하세요.") elif not selected_journeys: st.info("최소 1개의 여정 유형을 선택하세요.") if st.button( "▶️ 분석 시작", type="primary", key="hier:submit", disabled=not can_submit, ): keyword = raw_text.strip() own_brands = [b.strip() for b in own_brands_input.split(",") if b.strip()] if own_brands_input else [] processor_config = { "version": "1.0", "inputAnalysis": { "rawText": keyword, "primaryKeyword": keyword, "productType": product_type, "locationCode": 2410, "languageCode": "ko", }, "brandContext": { "brandMention": brand_mention, "ownBrands": own_brands, }, "selectedJourneyTypes": selected_journeys, "persona": { "attributes": { "ages": persona_ages, "gender": persona_gender, "trait": persona_trait, }, }, "modifiers": [], } settings = { "model": model, "questionsPerKeyword": questions_per_kw, "maxNodes": max_nodes, "outputLanguage": "ko", } try: client.create_hierarchy_job( prompt=keyword, title=title.strip() if title else None, processor_config=processor_config, settings=settings, ) st.success("분석 Job이 생성되었습니다! '진행 현황' 탭에서 확인하세요.") st.rerun() except Exception as e: st.error(f"분석 시작 실패: {e}")