import gradio as gr import pandas as pd import openpyxl import datetime import tempfile import os from openpyxl.utils.dataframe import dataframe_to_rows import openai # ===================== OpenAI API 클라이언트 설정 ===================== openai.api_key = os.getenv("OPENAI_API_KEY") def call_api(content, system_message, max_tokens, temperature, top_p): """ ChatGPT API 호출 함수. """ response = openai.ChatCompletion.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_message}, {"role": "user", "content": content} ], max_tokens=max_tokens, temperature=temperature, top_p=top_p ) return response.choices[0].message['content'] def respond_gemini_qna(question, system_message, max_tokens, temperature, top_p, model_id): """ Gemini Flash 모델을 이용해 질문(question)에 대한 답변을 반환하는 함수. """ try: import google.generativeai as genai except ModuleNotFoundError: return ("오류가 발생했습니다: 'google-generativeai' 모듈을 찾을 수 없습니다. " "해결 방법: 'pip install --upgrade google-generativeai' 를 실행하여 설치해주세요.") gemini_api_key = os.getenv("GEMINI_API_KEY") if not gemini_api_key: return "Gemini API 토큰이 필요합니다." genai.configure(api_key=gemini_api_key) prompt = f"{system_message}\n\n{question}" try: model = genai.GenerativeModel(model_name=model_id) response = model.generate_content(prompt) return response.text except Exception as e: return f"오류가 발생했습니다: {str(e)}" def respond_o1mini_qna(question, system_message, max_tokens, temperature): """ o1-mini 모델을 이용해 한 번의 질문(question)에 대한 답변을 반환하는 함수. o1-mini에서는 'system' 메시지를 지원하지 않으므로 system_message와 question을 하나의 'user' 메시지로 합쳐 전달합니다. 또한, o1-mini에서는 'max_tokens' 대신 'max_completion_tokens'를 사용하며, temperature는 고정값 1만 지원합니다. """ openai_token = os.getenv("OPENAI_API_KEY") if not openai_token: return "OpenAI API 토큰이 필요합니다." openai.api_key = openai_token combined_message = f"{system_message}\n\n{question}" messages = [{"role": "user", "content": combined_message}] try: response = openai.ChatCompletion.create( model="o1-mini", messages=messages, max_completion_tokens=max_tokens, temperature=1, # 고정된 값 1 사용 ) assistant_message = response.choices[0].message['content'] return assistant_message except Exception as e: return f"오류가 발생했습니다: {str(e)}" # ===================== (1) 리뷰옵션분석: 미션 1~9 처리 ===================== def analyze_options(uploaded_file, selected_year, llm_model_choice): """ 업로드된 파일로부터 미션 1~9(옵션/평점 집계)를 수행하고, 임시 엑셀 파일과 선택년도 Top20 옵션 리스트(전체옵션분석 포함)를 반환한다. """ if uploaded_file is None: return None, ["전체옵션분석"] try: df_upload = pd.read_excel(uploaded_file) except Exception as e: return None, ["전체옵션분석"] template_file = "리뷰분석.ver1.0.xlsx" if not os.path.exists(template_file): return None, ["전체옵션분석"] try: wb = openpyxl.load_workbook(template_file) except Exception as e: return None, ["전체옵션분석"] # (미션1) 원본데이터 시트 생성 if "원본데이터" in wb.sheetnames: ws_source = wb["원본데이터"] wb.remove(ws_source) ws_source = wb.create_sheet("원본데이터") else: ws_source = wb.create_sheet("원본데이터") for r_idx, row in enumerate(dataframe_to_rows(df_upload, index=False, header=True), start=1): for c_idx, value in enumerate(row, start=1): ws_source.cell(row=r_idx, column=c_idx, value=value) # 리뷰 날짜 처리 try: df_upload['리뷰날짜'] = pd.to_datetime(df_upload.iloc[:, 1], errors='coerce') df_upload['리뷰날짜'] = df_upload['리뷰날짜'].apply( lambda d: d.replace(tzinfo=None) if pd.notnull(d) and d.tzinfo is not None else d ) df_valid = df_upload.dropna(subset=['리뷰날짜']).copy() except Exception as e: return None, ["전체옵션분석"] now = datetime.datetime.now() current_year = now.year start_year_val = current_year - 2 end_year_val = current_year # 대시보드데이터 시트 if "대시보드데이터" in wb.sheetnames: ws_dashboard = wb["대시보드데이터"] else: ws_dashboard = wb.create_sheet("대시보드데이터") # (미션2) 최근 3년 '년월' 추이 ws_dashboard["C2"] = str(current_year)[-2:] row_idx = 5 for year in range(start_year_val, end_year_val + 1): for month in range(1, 13): count = df_valid[(df_valid['리뷰날짜'].dt.year == year) & (df_valid['리뷰날짜'].dt.month == month)].shape[0] ws_dashboard.cell(row=row_idx, column=3, value=count) row_idx += 1 # (미션3) 최근 3년 '년도' 추이 ws_dashboard["E2"] = str(current_year)[-2:] row_year = 5 for y in [current_year, current_year - 1, current_year - 2]: count = df_valid[df_valid['리뷰날짜'].dt.year == y].shape[0] ws_dashboard.cell(row=row_year, column=6, value=count) row_year += 1 # (미션4) 선택된 년도 '월별' 추이 try: selected_year_int = int("20" + selected_year[:2]) except Exception as e: return None, ["전체옵션분석"] ws_dashboard["I2"] = selected_year[:2] for month in range(1, 13): count = df_valid[(df_valid['리뷰날짜'].dt.year == selected_year_int) & (df_valid['리뷰날짜'].dt.month == month)].shape[0] ws_dashboard.cell(row=4 + month, column=9, value=count) # (미션5) 선택된 년도 '월일' 추이 start_date = datetime.date(selected_year_int, 1, 1) end_date = datetime.date(selected_year_int, 12, 31) df_selected = df_valid[df_valid['리뷰날짜'].dt.year == selected_year_int].copy() df_selected['리뷰날짜_일'] = df_selected['리뷰날짜'].dt.date daily_counts = df_selected.groupby('리뷰날짜_일').size().to_dict() row_day = 5 current_day = start_date while current_day <= end_date: ws_dashboard.cell(row=row_day, column=12, value=daily_counts.get(current_day, 0)) row_day += 1 current_day += datetime.timedelta(days=1) # (미션6) 최근 3년 전체옵션 데이터 입력 df_recent = df_valid[(df_valid['리뷰날짜'].dt.year >= start_year_val) & (df_valid['리뷰날짜'].dt.year <= end_year_val)].copy() if df_recent.shape[1] >= 3: df_recent['옵션원본'] = df_recent.iloc[:, 2].astype(str).fillna('') def get_first_option(opt_str): return opt_str.split(' / ')[0].strip() df_recent['옵션1'] = df_recent['옵션원본'].apply(get_first_option) option_counts_3y = df_recent['옵션1'].value_counts() top20_3y = option_counts_3y.head(20) sum_others_3y = option_counts_3y.iloc[20:].sum() if len(option_counts_3y) > 20 else 0 row_opt = 5 for opt_name, cnt in top20_3y.items(): ws_dashboard.cell(row=row_opt, column=14, value=opt_name) ws_dashboard.cell(row=row_opt, column=15, value=cnt) row_opt += 1 ws_dashboard.cell(row=25, column=15, value=sum_others_3y) else: ws_dashboard.cell(row=5, column=14, value="구매옵션 열이 없습니다.") ws_dashboard.cell(row=5, column=15, value=0) # (미션7) 선택년도 기준 옵션 데이터 (내림차순) df_selected_year = df_valid[df_valid['리뷰날짜'].dt.year == selected_year_int].copy() if df_selected_year.shape[1] >= 3: df_selected_year['옵션원본'] = df_selected_year.iloc[:, 2].astype(str).fillna('') def get_first_option(opt_str): return opt_str.split(' / ')[0].strip() df_selected_year['옵션1'] = df_selected_year['옵션원본'].apply(get_first_option) option_counts_selected = df_selected_year['옵션1'].value_counts() top20_selected = option_counts_selected.head(20) sum_others_selected = option_counts_selected.iloc[20:].sum() if len(option_counts_selected) > 20 else 0 row_opt = 5 for opt_name, cnt in top20_selected.items(): ws_dashboard.cell(row=row_opt, column=18, value=opt_name) ws_dashboard.cell(row=row_opt, column=19, value=cnt) row_opt += 1 ws_dashboard.cell(row=25, column=19, value=sum_others_selected) else: ws_dashboard.cell(row=5, column=18, value="구매옵션 열이 없습니다.") ws_dashboard.cell(row=5, column=19, value=0) # (미션8) 최근3년 평점현황 - top 20 옵션 df_recent_score = df_valid[(df_valid['리뷰날짜'].dt.year >= start_year_val) & (df_valid['리뷰날짜'].dt.year <= end_year_val)].copy() if df_recent_score.shape[1] >= 5: df_recent_score['옵션원본'] = df_recent_score.iloc[:, 2].astype(str).fillna('') def get_first_option(opt_str): return opt_str.split(' / ')[0].strip() df_recent_score['옵션1'] = df_recent_score['옵션원본'].apply(get_first_option) df_recent_score['평점'] = pd.to_numeric(df_recent_score.iloc[:, 4], errors='coerce') df_recent_score = df_recent_score.dropna(subset=['평점']) group_8 = df_recent_score.groupby(['옵션1', '평점']).size().unstack(fill_value=0) group_8 = group_8.reindex(columns=[5,4,3,2,1], fill_value=0) group_8['total_count'] = group_8.sum(axis=1) group_8 = group_8.sort_values(by='total_count', ascending=False) top20_8 = group_8.head(20).copy() others_8 = group_8.iloc[20:].copy() sum_others_5 = others_8[5].sum() sum_others_4 = others_8[4].sum() sum_others_3 = others_8[3].sum() sum_others_2 = others_8[2].sum() sum_others_1 = others_8[1].sum() row_v = 5 for opt_name, row_data in top20_8.iterrows(): ws_dashboard.cell(row=row_v, column=22, value=opt_name) ws_dashboard.cell(row=row_v, column=24, value=row_data[5]) ws_dashboard.cell(row=row_v, column=25, value=row_data[4]) ws_dashboard.cell(row=row_v, column=26, value=row_data[3]) ws_dashboard.cell(row=row_v, column=27, value=row_data[2]) ws_dashboard.cell(row=row_v, column=28, value=row_data[1]) row_v += 1 ws_dashboard.cell(row=25, column=24, value=sum_others_5) ws_dashboard.cell(row=25, column=25, value=sum_others_4) ws_dashboard.cell(row=25, column=26, value=sum_others_3) ws_dashboard.cell(row=25, column=27, value=sum_others_2) ws_dashboard.cell(row=25, column=28, value=sum_others_1) else: ws_dashboard.cell(row=5, column=22, value="옵션 또는 평점 열이 부족합니다.") # (미션9) 선택년도 평점현황 - top 20 옵션 df_selected_score = df_valid[df_valid['리뷰날짜'].dt.year == selected_year_int].copy() if df_selected_score.shape[1] >= 5: df_selected_score['옵션원본'] = df_selected_score.iloc[:, 2].astype(str).fillna('') def get_first_option(opt_str): return opt_str.split(' / ')[0].strip() df_selected_score['옵션1'] = df_selected_score['옵션원본'].apply(get_first_option) df_selected_score['평점'] = pd.to_numeric(df_selected_score.iloc[:, 4], errors='coerce') df_selected_score = df_selected_score.dropna(subset=['평점']) group_9 = df_selected_score.groupby(['옵션1', '평점']).size().unstack(fill_value=0) group_9 = group_9.reindex(columns=[5,4,3,2,1], fill_value=0) group_9['total_count'] = group_9.sum(axis=1) group_9 = group_9.sort_values(by='total_count', ascending=False) top20_9 = group_9.head(20).copy() others_9 = group_9.iloc[20:].copy() sum_others_5 = others_9[5].sum() sum_others_4 = others_9[4].sum() sum_others_3 = others_9[3].sum() sum_others_2 = others_9[2].sum() sum_others_1 = others_9[1].sum() row_ad = 5 for opt_name, row_data in top20_9.iterrows(): ws_dashboard.cell(row=row_ad, column=30, value=opt_name) ws_dashboard.cell(row=row_ad, column=32, value=row_data[5]) ws_dashboard.cell(row=row_ad, column=33, value=row_data[4]) ws_dashboard.cell(row=row_ad, column=34, value=row_data[3]) ws_dashboard.cell(row=row_ad, column=35, value=row_data[2]) ws_dashboard.cell(row=row_ad, column=36, value=row_data[1]) row_ad += 1 ws_dashboard.cell(row=25, column=32, value=sum_others_5) ws_dashboard.cell(row=25, column=33, value=sum_others_4) ws_dashboard.cell(row=25, column=34, value=sum_others_3) ws_dashboard.cell(row=25, column=35, value=sum_others_2) ws_dashboard.cell(row=25, column=36, value=sum_others_1) else: ws_dashboard.cell(row=5, column=30, value="옵션 또는 평점 열이 부족합니다.") # 임시 엑셀 파일 저장 temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") wb.save(temp_file.name) temp_file.close() # 선택년도 Top20 옵션 리스트 생성 (전체옵션분석 포함) top20_option_list = ["전체옵션분석"] if df_selected_year.shape[1] >= 3: option_counts_selected = df_selected_year['옵션1'].value_counts().head(20) for opt_name, cnt in option_counts_selected.items(): top20_option_list.append(f"{opt_name}({cnt})") return temp_file.name, top20_option_list # ===================== (2) 리뷰분석: 미션 10~12 실행 ===================== def analyze_reviews(partial_file, selected_option, llm_model_choice): """ '리뷰옵션분석' 결과 엑셀과 선택 옵션(또는 전체옵션분석)을 바탕으로 미션 10~12(주요긍정/부정 리뷰 추출 + LLM 분석)를 수행하고, 최종 엑셀 파일과 텍스트 분석 결과를 반환한다. """ if partial_file is None: return None, "", "", "", "" try: wb = openpyxl.load_workbook(partial_file) except Exception as e: return None, "", "", "", "" if "원본데이터" not in wb.sheetnames: return None, "", "", "", "" ws_source = wb["원본데이터"] data = ws_source.values cols = next(data) df_upload = pd.DataFrame(data, columns=cols) # (미션10) 주요 리뷰 내용 시트 생성 if "주요 리뷰 내용" in wb.sheetnames: ws_review = wb["주요 리뷰 내용"] wb.remove(ws_review) ws_review = wb.create_sheet("주요 리뷰 내용") else: ws_review = wb.create_sheet("주요 리뷰 내용") if df_upload.shape[1] < 5: ws_review.cell(row=1, column=1, value="리뷰내용 및 평점 열이 부족합니다.") return None, "", "", "", "" df_upload['옵션원본'] = df_upload.iloc[:, 2].astype(str).fillna('') if selected_option == "전체옵션분석": df_filtered = df_upload.copy() else: selected_opt_name = selected_option.rsplit("(", 1)[0].strip() def get_first_option(opt_str): return opt_str.split(' / ')[0].strip() df_upload['옵션1'] = df_upload['옵션원본'].apply(get_first_option) df_filtered = df_upload[df_upload['옵션1'] == selected_opt_name].copy() # (미션10) 주요 리뷰 내용(평점=3 제외) + 글자수 열 생성 → 내림차순 정렬 df_review = df_filtered.copy() df_review = df_review[df_review.iloc[:,4] != 3] df_review['글자수'] = df_review.iloc[:,3].astype(str).apply(len) df_review = df_review.sort_values(by='글자수', ascending=False) for r_idx, row in enumerate(dataframe_to_rows(df_review, index=False, header=True), start=1): for c_idx, value in enumerate(row, start=1): ws_review.cell(row=r_idx, column=c_idx, value=value) # (미션11) 긍정/부정 리뷰 추출 df_positive = df_review[(df_review.iloc[:,4] == 5) & (df_review['글자수'] < 500)].copy() if df_positive.shape[0] < 20: df_positive_4 = df_review[(df_review.iloc[:,4] == 4) & (df_review['글자수'] < 500)] df_positive = pd.concat([df_positive, df_positive_4]) df_positive = df_positive.head(20) positive_reviews = "" for idx, row in df_positive.iterrows(): positive_reviews += f"아이디: {row.iloc[0]}, 점수: {row.iloc[4]}, 글자수: {row['글자수']}\n리뷰: {row.iloc[3]}\n\n" df_negative = df_review[(df_review.iloc[:,4] == 1) & (df_review['글자수'] < 500)].copy() if df_negative.shape[0] < 30: df_negative_2 = df_review[(df_review.iloc[:,4] == 2) & (df_review['글자수'] < 500)] df_negative = pd.concat([df_negative, df_negative_2]) df_negative = df_negative.head(30) negative_reviews = "" for idx, row in df_negative.iterrows(): negative_reviews += f"아이디: {row.iloc[0]}, 점수: {row.iloc[4]}, 글자수: {row['글자수']}\n리뷰: {row.iloc[3]}\n\n" # (미션12) LLM을 이용한 리뷰 분석 positive_system_msg = ( "📝 긍정리뷰 분석:\n" "너는 리뷰 데이터를 분석하는 빅데이터 분석가이다. 고객의 리뷰 데이터를 바탕으로 긍정적인 의견만을 분석해라. 반드시 제공된 리뷰 데이터에서만 분석하며, 너의 생각은 포함하지 말 것.\n" "[분석 조건]\n" "- 반드시 긍정적인 의견만 분석하고, 부정적인 의견은 제외할 것.\n" "- 기능 및 성능, 감성, 실제 사용, 배송, 타겟별 관점으로 분석할 것.\n" "- 마케팅에 활용할 수 있는 고객의 실제 리뷰 단어를 반드시 포함할 것.\n" "[출력 형태]\n" "- 각각의 제목 앞에는 '📝' 이모지를 사용하며, '#'나 '##'은 사용하지 말 것.\n" "- 가장 마지막에는 \"🏆종합의견\"이라는 제목으로 종합 의견을 작성하라.\n" " - 종합의견에는 항목별 제목을 제외하고 서술식 문장으로 작성할 것.\n" " - 이어서 '🏹 강점'과 '🏹 기회' 제목으로 SWOT 분석 결과를 제공할 것.\n" "- 실제 고객의 리뷰에서 사용된 단어를 반드시 포함할 것." ) positive_user_msg = "다음 리뷰를 분석해 주세요:\n" + positive_reviews negative_system_msg = ( "📝 부정리뷰 분석:\n" "너는 리뷰 데이터를 분석하는 빅데이터 분석가이다. 고객의 리뷰 데이터를 바탕으로 부정적인 의견만을 분석해라. 반드시 제공된 리뷰 데이터에서만 분석하며, 너의 생각은 포함하지 말 것.\n" "[분석 조건]\n" "- 부정적인 의견만 분석하고, 긍정적인 의견은 제외할 것.\n" "- 기능 및 성능, 감성, 실제 사용, 배송, 고객의 분노 관점으로 분석할 것.\n" "- 부정적인 리뷰 분석 결과를 바탕으로 '개선할 점'을 출력할 것.\n" "[출력 형태]\n" "- 각각의 제목 앞에는 '📝' 이모지를 사용하며, '#'나 '##'은 사용하지 말 것.\n" "- 가장 마지막에는 \"📢개선할 점\"이라는 제목으로 개선할 점을 작성하라.\n" " - 개선할 점에는 항목별 제목을 제외하고 서술식 문장으로 작성할 것.\n" " - 이어서 '💉 약점'과 '💉 위협' 제목으로 SWOT 분석 결과를 제공할 것.\n" "- 실제 고객의 리뷰에서 사용된 단어를 반드시 포함할 것." ) negative_user_msg = "다음 리뷰를 분석해 주세요:\n" + negative_reviews if llm_model_choice == "ChatGPT (gpt-4o-mini)": positive_analysis = call_api( positive_user_msg, positive_system_msg, max_tokens=15000, temperature=0.3, top_p=0.95 ) negative_analysis = call_api( negative_user_msg, negative_system_msg, max_tokens=15000, temperature=0.3, top_p=0.95 ) elif llm_model_choice == "Gemini Flash (gemini-2.0-flash)": positive_analysis = respond_gemini_qna( positive_user_msg, positive_system_msg, max_tokens=15000, temperature=0.3, top_p=0.95, model_id="gemini-2.0-flash" ) negative_analysis = respond_gemini_qna( negative_user_msg, negative_system_msg, max_tokens=15000, temperature=0.3, top_p=0.95, model_id="gemini-2.0-flash" ) elif llm_model_choice == "o1-mini": positive_analysis = respond_o1mini_qna( positive_user_msg, positive_system_msg, max_tokens=15000, temperature=1 ) negative_analysis = respond_o1mini_qna( negative_user_msg, negative_system_msg, max_tokens=15000, temperature=1 ) else: positive_analysis = "LLM 모델 선택 오류" negative_analysis = "LLM 모델 선택 오류" if "대시보드데이터" in wb.sheetnames: ws_dashboard = wb["대시보드데이터"] ws_dashboard["AL5"] = positive_analysis ws_dashboard["AM5"] = negative_analysis final_file = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") wb.save(final_file.name) final_file.close() return final_file.name, positive_reviews, negative_reviews, positive_analysis, negative_analysis # ===================== Gradio UI 구성 (HTML/CSS 커스터마이징 적용) ===================== custom_css = """ body { background: #f7f7f7; font-family: 'Arial', sans-serif; } .gradio-container { padding: 20px; } .custom-header { text-align: center; font-size: 36px; font-weight: bold; margin-bottom: 20px; color: #333; } .custom-frame { border: 2px solid #ccc; border-radius: 20px; padding: 20px; margin: 10px 0; background-color: #fff; } .custom-button { border-radius: 20px !important; background: #4caf50 !important; color: white !important; font-size: 20px !important; padding: 10px 20px !important; } .custom-title { font-size: 28px; font-weight: bold; margin-bottom: 10px; color: #555; } .custom-subtitle { font-size: 22px; font-weight: bold; margin-bottom: 8px; color: #666; } .instructions { background-color: #e0f7fa; border: 2px dashed #00796b; border-radius: 15px; padding: 15px; font-size: 18px; color: #004d40; margin-bottom: 20px; text-align: left; } """ # 간단하고 좌측 정렬된 단계별 사용설명서 (먼저 정의) usage_instructions = """

📖 사용설명서

1단계: 좌측 "📑 데이터 입력"에서 파일 업로드와 분석년도 선택 후, 리뷰옵션분석 버튼 클릭
2단계: 하단 "📑 리뷰분석 옵션선택"에서 LLM 모델과 아이템옵션 선택 후, 리뷰분석 옵션선택 버튼 클릭
3단계: 우측 "📑분석보고서 다운로드"에서 보고서를 다운로드하고 결과 확인

""" with gr.Blocks(css=custom_css, title="리뷰 분석 서비스") as demo: gr.HTML("
🌟 고객 리뷰 분석 서비스 🌟
") gr.HTML(usage_instructions) # [데이터 입력 및 분석보고서 다운로드 프레임] (좌/우 배치) with gr.Row(): with gr.Column(elem_classes="custom-frame"): gr.HTML("
📑 데이터 입력
") file_input = gr.File(label="원본 엑셀 파일 업로드", file_types=[".xlsx"]) year_radio = gr.Radio( choices=[f"{str(y)[-2:]}년" for y in range(datetime.datetime.now().year, datetime.datetime.now().year-5, -1)], label="분석년도 선택", value=f"{str(datetime.datetime.now().year)[-2:]}년" ) analyze_button = gr.Button("리뷰옵션분석", elem_classes="custom-button") with gr.Column(elem_classes="custom-frame"): gr.HTML("
📑분석보고서 다운로드
") download_final_output = gr.File(label="보고서 다운로드") # [리뷰분석 프레임] (데이터 입력 프레임과 별도; 초기에는 숨김) with gr.Column(elem_classes="custom-frame", visible=False) as review_analysis_frame: gr.HTML("
리뷰분석 옵션선택
") llm_model_radio = gr.Radio( choices=["ChatGPT (gpt-4o-mini)", "Gemini Flash (gemini-2.0-flash)", "o1-mini"], label="LLM 모델 선택", value="ChatGPT (gpt-4o-mini)" ) top20_dropdown = gr.Dropdown( label="아이템옵션 분석", choices=["전체옵션분석"], value="전체옵션분석" ) review_button = gr.Button("📑 리뷰분석 옵션선택", elem_classes="custom-button") # [분석 결과 프레임] - 상단 제목 추가: "📑 옵션별 리뷰분석" with gr.Column(elem_classes="custom-frame"): gr.HTML("
📑 옵션별 리뷰분석
") with gr.Row(): with gr.Column(elem_classes="custom-frame"): gr.HTML("
✨ 주요긍정리뷰
") positive_output = gr.Textbox(label="긍정리뷰리스트(20개)", lines=10) with gr.Column(elem_classes="custom-frame"): gr.HTML("
✨ 주요부정리뷰
") negative_output = gr.Textbox(label="부정리뷰리스트(30개)", lines=10) with gr.Row(): with gr.Column(elem_classes="custom-frame"): gr.HTML("
📢 긍정리뷰 분석
") positive_analysis_output = gr.Textbox(label="긍정리뷰 분석", lines=8) with gr.Column(elem_classes="custom-frame"): gr.HTML("
📢 부정리뷰 분석
") negative_analysis_output = gr.Textbox(label="부정리뷰 분석", lines=8) # hidden state: 리뷰옵션분석 결과 엑셀 파일 저장 partial_file_state = gr.State() # [데이터 입력] - 리뷰옵션분석 버튼 클릭 시 실행: # 리뷰옵션분석 결과를 저장하고, 리뷰분석 프레임을 보이게 하며 아이템옵션 분석 드롭다운 업데이트 def on_click_analyze_options(uploaded_file, selected_year): partial_file, top20_list = analyze_options(uploaded_file, selected_year, "ChatGPT (gpt-4o-mini)") return partial_file, gr.update(visible=True), gr.update(choices=top20_list, value="전체옵션분석") analyze_button.click( fn=on_click_analyze_options, inputs=[file_input, year_radio], outputs=[partial_file_state, review_analysis_frame, top20_dropdown] ) # [리뷰분석] - 리뷰분석 버튼 클릭 시 실행 def on_click_analyze_reviews(partial_file, selected_option, llm_model): final_file, pos_reviews, neg_reviews, pos_analysis, neg_analysis = analyze_reviews( partial_file, selected_option, llm_model ) return final_file, pos_reviews, neg_reviews, pos_analysis, neg_analysis review_button.click( fn=on_click_analyze_reviews, inputs=[partial_file_state, top20_dropdown, llm_model_radio], outputs=[download_final_output, positive_output, negative_output, positive_analysis_output, negative_analysis_output] ) if __name__ == "__main__": demo.launch()