|
import gradio as gr |
|
import os |
|
import tempfile |
|
import datetime |
|
import pytz |
|
from gradio_client import Client, handle_file |
|
import dotenv |
|
|
|
|
|
dotenv.load_dotenv() |
|
|
|
|
|
API_ENDPOINT = os.getenv("API_ENDPOINT") |
|
if not API_ENDPOINT: |
|
raise ValueError("API_ENDPOINT ํ๊ฒฝ๋ณ์๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค.") |
|
|
|
|
|
client = Client(API_ENDPOINT) |
|
|
|
|
|
fontawesome_link = """ |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" /> |
|
""" |
|
|
|
|
|
custom_css = """ |
|
:root { |
|
--primary-color: #FB7F0D; |
|
--secondary-color: #ff9a8b; |
|
--accent-color: #FF6B6B; |
|
--background-color: #FFF3E9; |
|
--card-bg: #ffffff; |
|
--text-color: #334155; |
|
--border-radius: 18px; |
|
--shadow: 0 8px 30px rgba(251, 127, 13, 0.08); |
|
} |
|
/* โโ ํญ ๋ด๋ถ ํจ๋ ๋ฐฐ๊ฒฝ ์ ๊ฑฐ โโ */ |
|
.gr-tabs-panel { |
|
background-color: var(--background-color) !important; |
|
box-shadow: none !important; |
|
} |
|
.gr-tabs-panel::before, |
|
.gr-tabs-panel::after { |
|
display: none !important; |
|
content: none !important; |
|
} |
|
/* โโ ๊ทธ๋ฃน ๋ํผ ๋ฐฐ๊ฒฝ ์์ ์ ๊ฑฐ โโ */ |
|
.custom-section-group, |
|
.gr-block.gr-group { |
|
background-color: var(--background-color) !important; |
|
box-shadow: none !important; |
|
} |
|
.custom-section-group::before, |
|
.custom-section-group::after, |
|
.gr-block.gr-group::before, |
|
.gr-block.gr-group::after { |
|
display: none !important; |
|
content: none !important; |
|
} |
|
/* ๊ทธ๋ฃน ์ปจํ
์ด๋ ๋ฐฐ๊ฒฝ์ ์์ด๋ณด๋ฆฌ๋ก, ๊ทธ๋ฆผ์ ์ ๊ฑฐ */ |
|
.custom-section-group { |
|
background-color: var(--background-color) !important; |
|
box-shadow: none !important; |
|
} |
|
/* ์๋จยทํ๋จ์ ๊ทธ๋ ค์ง๋ ํ์ ์บก(๋ฅ๊ทผ ๋ชจ์๋ฆฌ) ์ ๊ฑฐ */ |
|
.custom-section-group::before, |
|
.custom-section-group::after { |
|
display: none !important; |
|
content: none !important; |
|
} |
|
body { |
|
font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif; |
|
background-color: var(--background-color); |
|
color: var(--text-color); |
|
line-height: 1.6; |
|
margin: 0; |
|
padding: 0; |
|
} |
|
.gradio-container { |
|
width: 100%; /* ์ ์ฒด ๋๋น 100% ๊ณ ์ */ |
|
margin: 0 auto; |
|
padding: 20px; |
|
background-color: var(--background-color); |
|
} |
|
/* ํค๋ ์คํ์ผ - ์ฃผํฉ์ ๋ฐ์ค ํํ๋ก ๋ณ๊ฒฝ */ |
|
.custom-header { |
|
background: #FF7F00; /* ๋จ์ ์ฃผํฉ์ */ |
|
padding: 2rem; |
|
border-radius: 15px; /* ๋ผ์ด๋ ์ฒ๋ฆฌ๋ฅผ ์ฝํ๊ฒ ์กฐ์ */ |
|
margin-bottom: 20px; |
|
box-shadow: var(--shadow); |
|
text-align: center; |
|
} |
|
.custom-header h1 { |
|
margin: 0; |
|
font-size: 2.5rem; |
|
font-weight: 700; |
|
color: black; /* ๊ธ์์์ ๊ฒ์์์ผ๋ก ๋ณ๊ฒฝ */ |
|
} |
|
.custom-header p { |
|
margin: 10px 0 0; |
|
font-size: 1.2rem; |
|
color: black; /* ์์ ๋ชฉ๋ ๊ฒ์์์ผ๋ก ๋ณ๊ฒฝ */ |
|
} |
|
/* ์ฝํ
์ธ ๋ฐ์ค (ํ๋ ์) ์คํ์ผ */ |
|
.custom-frame { |
|
background-color: var(--card-bg); |
|
border: 1px solid rgba(0, 0, 0, 0.04); |
|
border-radius: var(--border-radius); |
|
padding: 20px; |
|
margin: 10px 0; |
|
box-shadow: var(--shadow); |
|
} |
|
/* ์น์
๊ทธ๋ฃน ์คํ์ผ - ํ์ ๋ฐฐ๊ฒฝ ์์ ์ ๊ฑฐ */ |
|
.custom-section-group { |
|
margin-top: 20px; |
|
padding: 0; |
|
border: none; |
|
border-radius: 0; |
|
background-color: var(--background-color); /* ํ์ โ ์์ด๋ณด๋ฆฌ(์ ์ฒด ๋ฐฐ๊ฒฝ์) */ |
|
box-shadow: none !important; /* ํน์ ๋จ์์๋ ๊ทธ๋ฆผ์๋ ๊ฐ์ด ์ ๊ฑฐ */ |
|
} |
|
/* ๋ฒํผ ์คํ์ผ - ๊ธ์ ํฌ๊ธฐ 18px */ |
|
.custom-button { |
|
border-radius: 30px !important; |
|
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important; |
|
color: white !important; |
|
font-size: 18px !important; |
|
padding: 10px 20px !important; |
|
border: none; |
|
box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25); |
|
transition: transform 0.3s ease; |
|
} |
|
.custom-button:hover { |
|
transform: translateY(-2px); |
|
box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3); |
|
} |
|
/* ์ ๋ชฉ ์คํ์ผ (๋ชจ๋ ํญ๋ชฉ๋ช
์ด ๋์ผํ๊ฒ custom-title ํด๋์ค๋ก) */ |
|
.custom-title { |
|
font-size: 28px; |
|
font-weight: bold; |
|
margin-bottom: 10px; |
|
color: var(--text-color); |
|
border-bottom: 2px solid var(--primary-color); |
|
padding-bottom: 5px; |
|
} |
|
/* ์ฌ์ฉ ๊ฐ์ด๋ ์คํ์ผ ์ถ๊ฐ */ |
|
.guide-container { |
|
background-color: var(--card-bg); |
|
border-radius: var(--border-radius); |
|
box-shadow: var(--shadow); |
|
padding: 1.5rem; |
|
margin-bottom: 1.5rem; |
|
border: 1px solid rgba(0, 0, 0, 0.04); |
|
} |
|
.guide-title { |
|
font-size: 1.5rem; |
|
font-weight: 700; |
|
color: var(--primary-color); |
|
margin-bottom: 1.5rem; |
|
padding-bottom: 0.5rem; |
|
border-bottom: 2px solid var(--primary-color); |
|
display: flex; |
|
align-items: center; |
|
} |
|
.guide-title i { |
|
margin-right: 0.8rem; |
|
font-size: 1.5rem; |
|
} |
|
.guide-item { |
|
display: flex; |
|
margin-bottom: 1rem; |
|
align-items: flex-start; |
|
} |
|
.guide-number { |
|
background-color: var(--primary-color); |
|
color: white; |
|
width: 25px; |
|
height: 25px; |
|
border-radius: 50%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
font-weight: bold; |
|
margin-right: 10px; |
|
flex-shrink: 0; |
|
} |
|
.guide-text { |
|
flex: 1; |
|
line-height: 1.6; |
|
} |
|
.guide-text a { |
|
color: var(--primary-color); |
|
text-decoration: underline; |
|
font-weight: 600; |
|
} |
|
""" |
|
|
|
|
|
def call_analyze_options(uploaded_file, selected_year): |
|
"""์ต์
๋ถ์ API ํธ์ถ""" |
|
try: |
|
if uploaded_file is None: |
|
return None, gr.update(visible=False), gr.update(choices=["์ ์ฒด์ต์
๋ถ์"], value="์ ์ฒด์ต์
๋ถ์") |
|
|
|
|
|
result = client.predict( |
|
uploaded_file=handle_file(uploaded_file), |
|
selected_year=selected_year, |
|
api_name="/on_click_analyze_options" |
|
) |
|
|
|
|
|
choices = result if isinstance(result, list) else ["์ ์ฒด์ต์
๋ถ์"] |
|
|
|
return "success", gr.update(visible=True), gr.update(choices=choices, value=choices[0] if choices else "์ ์ฒด์ต์
๋ถ์") |
|
except Exception as e: |
|
print(f"์ต์
๋ถ์ API ํธ์ถ ์ค ์ค๋ฅ: {e}") |
|
return None, gr.update(visible=False), gr.update(choices=["์ ์ฒด์ต์
๋ถ์"], value="์ ์ฒด์ต์
๋ถ์") |
|
|
|
def call_analyze_reviews(selected_option, analysis_state): |
|
"""๋ฆฌ๋ทฐ ๋ถ์ API ํธ์ถ""" |
|
try: |
|
if analysis_state is None: |
|
return None, "", "", "", "", "", "", "", "" |
|
|
|
|
|
result = client.predict( |
|
selected_option=selected_option, |
|
api_name="/on_click_analyze_reviews" |
|
) |
|
|
|
|
|
if isinstance(result, tuple) and len(result) >= 9: |
|
return result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7], result[8] |
|
else: |
|
return None, "", "", "", "", "", "", "", "" |
|
except Exception as e: |
|
print(f"๋ฆฌ๋ทฐ ๋ถ์ API ํธ์ถ ์ค ์ค๋ฅ: {e}") |
|
return None, "", "", "", "", "", "", "", "" |
|
|
|
def call_direct_analyze(positive_input, negative_input): |
|
"""์ง์ ์
๋ ฅ ๋ถ์ API ํธ์ถ""" |
|
try: |
|
|
|
result = client.predict( |
|
positive_input=positive_input, |
|
negative_input=negative_input, |
|
api_name="/on_click_direct_analyze" |
|
) |
|
|
|
|
|
if isinstance(result, tuple) and len(result) >= 7: |
|
return result[0], result[1], result[2], result[3], result[4], result[5], result[6] |
|
else: |
|
return None, "", "", "", "", "", "" |
|
except Exception as e: |
|
print(f"์ง์ ์
๋ ฅ ๋ถ์ API ํธ์ถ ์ค ์ค๋ฅ: {e}") |
|
return None, "", "", "", "", "", "" |
|
|
|
def call_apply_excel_example(): |
|
"""์์
์์ ์ ์ฉ API ํธ์ถ""" |
|
try: |
|
result = client.predict(api_name="/apply_excel_example") |
|
if isinstance(result, tuple) and len(result) >= 2: |
|
return result[0], result[1] |
|
else: |
|
return None, gr.update() |
|
except Exception as e: |
|
print(f"์์
์์ ์ ์ฉ API ํธ์ถ ์ค ์ค๋ฅ: {e}") |
|
return None, gr.update() |
|
|
|
def call_apply_direct_example(): |
|
"""์ง์ ์
๋ ฅ ์์ ์ ์ฉ API ํธ์ถ""" |
|
try: |
|
result = client.predict(api_name="/apply_direct_example") |
|
if isinstance(result, tuple) and len(result) >= 2: |
|
return result[0], result[1] |
|
else: |
|
return "", "" |
|
except Exception as e: |
|
print(f"์ง์ ์
๋ ฅ ์์ ์ ์ฉ API ํธ์ถ ์ค ์ค๋ฅ: {e}") |
|
return "", "" |
|
|
|
|
|
demo = gr.Blocks(css=custom_css, theme=gr.themes.Default( |
|
primary_hue="orange", |
|
secondary_hue="orange", |
|
font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"] |
|
)) |
|
|
|
with demo: |
|
gr.HTML(fontawesome_link) |
|
|
|
|
|
with gr.Tabs() as tabs: |
|
|
|
|
|
|
|
with gr.TabItem("๐พ ์ค๋งํธ์คํ ์ด ์์
๋ฆฌ๋ทฐ๋ฐ์ดํฐ ํ์ฉ"): |
|
|
|
with gr.Row(): |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ ๋ฐ์ดํฐ ์
๋ ฅ</div>") |
|
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("<div class='custom-title'>๐ ๋ถ์๋ณด๊ณ ์ ๋ค์ด๋ก๋</div>") |
|
download_final_output = gr.File(label="๋ณด๊ณ ์ ๋ค์ด๋ก๋") |
|
|
|
|
|
with gr.Column(elem_classes="custom-frame", visible=False) as review_analysis_frame: |
|
gr.HTML("<div class='custom-title'>๐ ๋ฆฌ๋ทฐ๋ถ์</div>") |
|
top20_dropdown = gr.Dropdown( |
|
label="์์ดํ
์ต์
๋ถ์", |
|
choices=["์ ์ฒด์ต์
๋ถ์"], |
|
value="์ ์ฒด์ต์
๋ถ์" |
|
) |
|
review_button = gr.Button("๋ฆฌ๋ทฐ ๋ถ์ํ๊ธฐ", elem_classes="custom-button") |
|
|
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>โจ ์ฃผ์๊ธ์ ๋ฆฌ๋ทฐ</div>") |
|
positive_output = gr.Textbox(label="๊ธ์ ๋ฆฌ๋ทฐ๋ฆฌ์คํธ (20๊ฐ)", lines=10) |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>โจ ์ฃผ์๋ถ์ ๋ฆฌ๋ทฐ</div>") |
|
negative_output = gr.Textbox(label="๋ถ์ ๋ฆฌ๋ทฐ๋ฆฌ์คํธ (30๊ฐ)", lines=10) |
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ข ๊ธ์ ๋ฆฌ๋ทฐ ๋ถ์</div>") |
|
positive_analysis_output = gr.Textbox(label="๊ธ์ ๋ฆฌ๋ทฐ ๋ถ์", lines=8) |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ข ๋ถ์ ๋ฆฌ๋ทฐ ๋ถ์</div>") |
|
negative_analysis_output = gr.Textbox(label="๋ถ์ ๋ฆฌ๋ทฐ ๋ถ์", lines=8) |
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ ๋์ฆ์์ธ ๋ถ์</div>") |
|
insight_analysis_output = gr.Textbox(label="๋์ฆ์์ธ ๋ถ์", lines=8) |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ง ์ํํ๋งค๋ฐฉํฅ์ฑ</div>") |
|
strategy_analysis_output = gr.Textbox(label="์ํํ๋งค๋ฐฉํฅ์ฑ", lines=8) |
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ ์์ฑ์ ๋ต</div>") |
|
sourcing_analysis_output = gr.Textbox(label="์์ฑ์ ๋ต", lines=8) |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ผ๏ธ ๋ง์ผํ
์ ๋ต</div>") |
|
detail_page_analysis_output = gr.Textbox(label="๋ง์ผํ
์ ๋ต", lines=8) |
|
|
|
|
|
analysis_state = gr.State() |
|
|
|
|
|
analyze_button.click( |
|
fn=call_analyze_options, |
|
inputs=[file_input, year_radio], |
|
outputs=[analysis_state, review_analysis_frame, top20_dropdown] |
|
) |
|
|
|
review_button.click( |
|
fn=call_analyze_reviews, |
|
inputs=[top20_dropdown, analysis_state], |
|
outputs=[download_final_output, positive_output, negative_output, |
|
positive_analysis_output, negative_analysis_output, |
|
insight_analysis_output, strategy_analysis_output, |
|
sourcing_analysis_output, detail_page_analysis_output] |
|
) |
|
|
|
|
|
|
|
|
|
with gr.TabItem("๐ ์ง์ ์
๋ ฅํ ์๋ฃํ์ฉ"): |
|
with gr.Row(): |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ ๋ฆฌ๋ทฐ ์ง์ ์
๋ ฅ</div>") |
|
direct_positive_input = gr.Textbox( |
|
label="๊ธ์ ๋ฆฌ๋ทฐ ์
๋ ฅ", |
|
placeholder="๊ธ์ ๋ฆฌ๋ทฐ๋ฅผ ์ฌ๊ธฐ์ ์
๋ ฅํ์ธ์.(์ต๋ 8000์)", |
|
lines=10, max_length=8000 |
|
) |
|
direct_negative_input = gr.Textbox( |
|
label="๋ถ์ ๋ฆฌ๋ทฐ ์
๋ ฅ", |
|
placeholder="๋ถ์ ๋ฆฌ๋ทฐ๋ฅผ ์ฌ๊ธฐ์ ์
๋ ฅํ์ธ์.(์ต๋ 8000์)", |
|
lines=10, max_length=8000 |
|
) |
|
direct_review_button = gr.Button("๋ฆฌ๋ทฐ ๋ถ์ํ๊ธฐ", elem_classes="custom-button") |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ ๋ถ์๋ณด๊ณ ์ ๋ค์ด๋ก๋</div>") |
|
direct_download_output = gr.File(label="๋ถ์ ๋ณด๊ณ ์ ๋ค์ด๋ก๋") |
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ข ๊ธ์ ๋ฆฌ๋ทฐ๋ถ์</div>") |
|
direct_positive_analysis_output = gr.Textbox( |
|
label="๊ธ์ ๋ฆฌ๋ทฐ๋ถ์", lines=8 |
|
) |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ข ๋ถ์ ๋ฆฌ๋ทฐ๋ถ์</div>") |
|
direct_negative_analysis_output = gr.Textbox( |
|
label="๋ถ์ ๋ฆฌ๋ทฐ๋ถ์", lines=8 |
|
) |
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ ๋์ฆ์์ธ ๋ถ์</div>") |
|
direct_insight_analysis_output = gr.Textbox( |
|
label="๋์ฆ์์ธ ๋ถ์", lines=8 |
|
) |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ง ์ํํ๋งค๋ฐฉํฅ์ฑ</div>") |
|
direct_strategy_analysis_output = gr.Textbox( |
|
label="์ํํ๋งค๋ฐฉํฅ์ฑ", lines=8 |
|
) |
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ ์์ฑ์ ๋ต</div>") |
|
direct_sourcing_analysis_output = gr.Textbox( |
|
label="์์ฑ์ ๋ต", lines=8 |
|
) |
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ผ๏ธ ๋ง์ผํ
์ ๋ต</div>") |
|
direct_detail_page_analysis_output = gr.Textbox( |
|
label="๋ง์ผํ
์ ๋ต", lines=8 |
|
) |
|
|
|
|
|
direct_review_button.click( |
|
fn=call_direct_analyze, |
|
inputs=[direct_positive_input, direct_negative_input], |
|
outputs=[direct_download_output, direct_positive_analysis_output, direct_negative_analysis_output, |
|
direct_insight_analysis_output, direct_strategy_analysis_output, |
|
direct_sourcing_analysis_output, direct_detail_page_analysis_output] |
|
) |
|
|
|
|
|
with gr.Column(elem_classes="custom-frame"): |
|
gr.HTML("<div class='custom-title'>๐ ์์ ์ ์ฉํ๊ธฐ</div>") |
|
with gr.Row(): |
|
example_excel_button = gr.Button("๐ ์์
๋ถ์ ์์ ์ ์ฉํ๊ธฐ", elem_classes="custom-button") |
|
example_direct_button = gr.Button("๐ ์ง์ ์
๋ ฅ ์์ ์ ์ฉํ๊ธฐ", elem_classes="custom-button") |
|
|
|
|
|
example_excel_button.click( |
|
fn=call_apply_excel_example, |
|
outputs=[file_input, year_radio] |
|
) |
|
|
|
example_direct_button.click( |
|
fn=call_apply_direct_example, |
|
outputs=[direct_positive_input, direct_negative_input] |
|
) |
|
|
|
if __name__ == "__main__": |
|
demo.launch() |