ssboost commited on
Commit
2ef5632
ยท
verified ยท
1 Parent(s): 26d2dba

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +471 -0
app.py ADDED
@@ -0,0 +1,471 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import tempfile
4
+ import datetime
5
+ import pytz
6
+ from gradio_client import Client, handle_file
7
+ import dotenv
8
+
9
+ # ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋กœ๋“œ
10
+ dotenv.load_dotenv()
11
+
12
+ # API ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๊ฐ€์ ธ์˜ด (๋กœ๊ทธ์— ์ถœ๋ ฅํ•˜์ง€ ์•Š์Œ)
13
+ API_ENDPOINT = os.getenv("API_ENDPOINT")
14
+ if not API_ENDPOINT:
15
+ raise ValueError("API_ENDPOINT ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
16
+
17
+ # ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” (๋กœ๊ทธ์— ์ถœ๋ ฅํ•˜์ง€ ์•Š์Œ)
18
+ client = Client(API_ENDPOINT)
19
+
20
+ # ===================== ์‚ฌ์šฉ ๊ฐ€์ด๋“œ HTML ์ •์˜ =====================
21
+ fontawesome_link = """
22
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
23
+ """
24
+
25
+ # ===================== CSS ์Šคํƒ€์ผ ์ •์˜ =====================
26
+ custom_css = """
27
+ :root {
28
+ --primary-color: #FB7F0D;
29
+ --secondary-color: #ff9a8b;
30
+ --accent-color: #FF6B6B;
31
+ --background-color: #FFF3E9;
32
+ --card-bg: #ffffff;
33
+ --text-color: #334155;
34
+ --border-radius: 18px;
35
+ --shadow: 0 8px 30px rgba(251, 127, 13, 0.08);
36
+ }
37
+ /* โ”€โ”€ ํƒญ ๋‚ด๋ถ€ ํŒจ๋„ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ โ”€โ”€ */
38
+ .gr-tabs-panel {
39
+ background-color: var(--background-color) !important;
40
+ box-shadow: none !important;
41
+ }
42
+ .gr-tabs-panel::before,
43
+ .gr-tabs-panel::after {
44
+ display: none !important;
45
+ content: none !important;
46
+ }
47
+ /* โ”€โ”€ ๊ทธ๋ฃน ๋ž˜ํผ ๋ฐฐ๊ฒฝ ์™„์ „ ์ œ๊ฑฐ โ”€โ”€ */
48
+ .custom-section-group,
49
+ .gr-block.gr-group {
50
+ background-color: var(--background-color) !important;
51
+ box-shadow: none !important;
52
+ }
53
+ .custom-section-group::before,
54
+ .custom-section-group::after,
55
+ .gr-block.gr-group::before,
56
+ .gr-block.gr-group::after {
57
+ display: none !important;
58
+ content: none !important;
59
+ }
60
+ /* ๊ทธ๋ฃน ์ปจํ…Œ์ด๋„ˆ ๋ฐฐ๊ฒฝ์„ ์•„์ด๋ณด๋ฆฌ๋กœ, ๊ทธ๋ฆผ์ž ์ œ๊ฑฐ */
61
+ .custom-section-group {
62
+ background-color: var(--background-color) !important;
63
+ box-shadow: none !important;
64
+ }
65
+ /* ์ƒ๋‹จยทํ•˜๋‹จ์— ๊ทธ๋ ค์ง€๋Š” ํšŒ์ƒ‰ ์บก(๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ) ์ œ๊ฑฐ */
66
+ .custom-section-group::before,
67
+ .custom-section-group::after {
68
+ display: none !important;
69
+ content: none !important;
70
+ }
71
+ body {
72
+ font-family: 'Pretendard', 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
73
+ background-color: var(--background-color);
74
+ color: var(--text-color);
75
+ line-height: 1.6;
76
+ margin: 0;
77
+ padding: 0;
78
+ }
79
+ .gradio-container {
80
+ width: 100%; /* ์ „์ฒด ๋„ˆ๋น„ 100% ๊ณ ์ • */
81
+ margin: 0 auto;
82
+ padding: 20px;
83
+ background-color: var(--background-color);
84
+ }
85
+ /* ํ—ค๋” ์Šคํƒ€์ผ - ์ฃผํ™ฉ์ƒ‰ ๋ฐ•์Šค ํ˜•ํƒœ๋กœ ๋ณ€๊ฒฝ */
86
+ .custom-header {
87
+ background: #FF7F00; /* ๋‹จ์ƒ‰ ์ฃผํ™ฉ์ƒ‰ */
88
+ padding: 2rem;
89
+ border-radius: 15px; /* ๋ผ์šด๋“œ ์ฒ˜๋ฆฌ๋ฅผ ์•ฝํ•˜๊ฒŒ ์กฐ์ • */
90
+ margin-bottom: 20px;
91
+ box-shadow: var(--shadow);
92
+ text-align: center;
93
+ }
94
+ .custom-header h1 {
95
+ margin: 0;
96
+ font-size: 2.5rem;
97
+ font-weight: 700;
98
+ color: black; /* ๊ธ€์ž์ƒ‰์„ ๊ฒ€์€์ƒ‰์œผ๋กœ ๋ณ€๊ฒฝ */
99
+ }
100
+ .custom-header p {
101
+ margin: 10px 0 0;
102
+ font-size: 1.2rem;
103
+ color: black; /* ์†Œ์ œ๋ชฉ๋„ ๊ฒ€์€์ƒ‰์œผ๋กœ ๋ณ€๊ฒฝ */
104
+ }
105
+ /* ์ฝ˜ํ…์ธ  ๋ฐ•์Šค (ํ”„๋ ˆ์ž„) ์Šคํƒ€์ผ */
106
+ .custom-frame {
107
+ background-color: var(--card-bg);
108
+ border: 1px solid rgba(0, 0, 0, 0.04);
109
+ border-radius: var(--border-radius);
110
+ padding: 20px;
111
+ margin: 10px 0;
112
+ box-shadow: var(--shadow);
113
+ }
114
+ /* ์„น์…˜ ๊ทธ๋ฃน ์Šคํƒ€์ผ - ํšŒ์ƒ‰ ๋ฐฐ๊ฒฝ ์™„์ „ ์ œ๊ฑฐ */
115
+ .custom-section-group {
116
+ margin-top: 20px;
117
+ padding: 0;
118
+ border: none;
119
+ border-radius: 0;
120
+ background-color: var(--background-color); /* ํšŒ์ƒ‰ โ†’ ์•„์ด๋ณด๋ฆฌ(์ „์ฒด ๋ฐฐ๊ฒฝ์ƒ‰) */
121
+ box-shadow: none !important; /* ํ˜น์‹œ ๋‚จ์•„์žˆ๋Š” ๊ทธ๋ฆผ์ž๋„ ๊ฐ™์ด ์ œ๊ฑฐ */
122
+ }
123
+ /* ๋ฒ„ํŠผ ์Šคํƒ€์ผ - ๊ธ€์ž ํฌ๊ธฐ 18px */
124
+ .custom-button {
125
+ border-radius: 30px !important;
126
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
127
+ color: white !important;
128
+ font-size: 18px !important;
129
+ padding: 10px 20px !important;
130
+ border: none;
131
+ box-shadow: 0 4px 8px rgba(251, 127, 13, 0.25);
132
+ transition: transform 0.3s ease;
133
+ }
134
+ .custom-button:hover {
135
+ transform: translateY(-2px);
136
+ box-shadow: 0 6px 12px rgba(251, 127, 13, 0.3);
137
+ }
138
+ /* ์ œ๋ชฉ ์Šคํƒ€์ผ (๋ชจ๋“  ํ•ญ๋ชฉ๋ช…์ด ๋™์ผํ•˜๊ฒŒ custom-title ํด๋ž˜์Šค๋กœ) */
139
+ .custom-title {
140
+ font-size: 28px;
141
+ font-weight: bold;
142
+ margin-bottom: 10px;
143
+ color: var(--text-color);
144
+ border-bottom: 2px solid var(--primary-color);
145
+ padding-bottom: 5px;
146
+ }
147
+ /* ์‚ฌ์šฉ ๊ฐ€์ด๋“œ ์Šคํƒ€์ผ ์ถ”๊ฐ€ */
148
+ .guide-container {
149
+ background-color: var(--card-bg);
150
+ border-radius: var(--border-radius);
151
+ box-shadow: var(--shadow);
152
+ padding: 1.5rem;
153
+ margin-bottom: 1.5rem;
154
+ border: 1px solid rgba(0, 0, 0, 0.04);
155
+ }
156
+ .guide-title {
157
+ font-size: 1.5rem;
158
+ font-weight: 700;
159
+ color: var(--primary-color);
160
+ margin-bottom: 1.5rem;
161
+ padding-bottom: 0.5rem;
162
+ border-bottom: 2px solid var(--primary-color);
163
+ display: flex;
164
+ align-items: center;
165
+ }
166
+ .guide-title i {
167
+ margin-right: 0.8rem;
168
+ font-size: 1.5rem;
169
+ }
170
+ .guide-item {
171
+ display: flex;
172
+ margin-bottom: 1rem;
173
+ align-items: flex-start;
174
+ }
175
+ .guide-number {
176
+ background-color: var(--primary-color);
177
+ color: white;
178
+ width: 25px;
179
+ height: 25px;
180
+ border-radius: 50%;
181
+ display: flex;
182
+ align-items: center;
183
+ justify-content: center;
184
+ font-weight: bold;
185
+ margin-right: 10px;
186
+ flex-shrink: 0;
187
+ }
188
+ .guide-text {
189
+ flex: 1;
190
+ line-height: 1.6;
191
+ }
192
+ .guide-text a {
193
+ color: var(--primary-color);
194
+ text-decoration: underline;
195
+ font-weight: 600;
196
+ }
197
+ """
198
+
199
+ # ===================== API ํ˜ธ์ถœ ํ•จ์ˆ˜๋“ค =====================
200
+ def call_analyze_options(uploaded_file, selected_year):
201
+ """์˜ต์…˜ ๋ถ„์„ API ํ˜ธ์ถœ"""
202
+ try:
203
+ if uploaded_file is None:
204
+ return None, gr.update(visible=False), gr.update(choices=["์ „์ฒด์˜ต์…˜๋ถ„์„"], value="์ „์ฒด์˜ต์…˜๋ถ„์„")
205
+
206
+ # API ํ˜ธ์ถœ
207
+ result = client.predict(
208
+ uploaded_file=handle_file(uploaded_file),
209
+ selected_year=selected_year,
210
+ api_name="/on_click_analyze_options"
211
+ )
212
+
213
+ # ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ
214
+ choices = result if isinstance(result, list) else ["์ „์ฒด์˜ต์…˜๋ถ„์„"]
215
+
216
+ return "success", gr.update(visible=True), gr.update(choices=choices, value=choices[0] if choices else "์ „์ฒด์˜ต์…˜๋ถ„์„")
217
+ except Exception as e:
218
+ print(f"์˜ต์…˜ ๋ถ„์„ API ํ˜ธ์ถœ ์ค‘ ์˜ค๋ฅ˜: {e}")
219
+ return None, gr.update(visible=False), gr.update(choices=["์ „์ฒด์˜ต์…˜๋ถ„์„"], value="์ „์ฒด์˜ต์…˜๋ถ„์„")
220
+
221
+ def call_analyze_reviews(selected_option, analysis_state):
222
+ """๋ฆฌ๋ทฐ ๋ถ„์„ API ํ˜ธ์ถœ"""
223
+ try:
224
+ if analysis_state is None:
225
+ return None, "", "", "", "", "", "", "", ""
226
+
227
+ # API ํ˜ธ์ถœ
228
+ result = client.predict(
229
+ selected_option=selected_option,
230
+ api_name="/on_click_analyze_reviews"
231
+ )
232
+
233
+ # ๊ฒฐ๊ณผ ์–ธํŒจํ‚น
234
+ if isinstance(result, tuple) and len(result) >= 9:
235
+ return result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7], result[8]
236
+ else:
237
+ return None, "", "", "", "", "", "", "", ""
238
+ except Exception as e:
239
+ print(f"๋ฆฌ๋ทฐ ๋ถ„์„ API ํ˜ธ์ถœ ์ค‘ ์˜ค๋ฅ˜: {e}")
240
+ return None, "", "", "", "", "", "", "", ""
241
+
242
+ def call_direct_analyze(positive_input, negative_input):
243
+ """์ง์ ‘ ์ž…๋ ฅ ๋ถ„์„ API ํ˜ธ์ถœ"""
244
+ try:
245
+ # API ํ˜ธ์ถœ
246
+ result = client.predict(
247
+ positive_input=positive_input,
248
+ negative_input=negative_input,
249
+ api_name="/on_click_direct_analyze"
250
+ )
251
+
252
+ # ๊ฒฐ๊ณผ ์–ธํŒจํ‚น
253
+ if isinstance(result, tuple) and len(result) >= 7:
254
+ return result[0], result[1], result[2], result[3], result[4], result[5], result[6]
255
+ else:
256
+ return None, "", "", "", "", "", ""
257
+ except Exception as e:
258
+ print(f"์ง์ ‘ ์ž…๋ ฅ ๋ถ„์„ API ํ˜ธ์ถœ ์ค‘ ์˜ค๋ฅ˜: {e}")
259
+ return None, "", "", "", "", "", ""
260
+
261
+ def call_apply_excel_example():
262
+ """์—‘์…€ ์˜ˆ์‹œ ์ ์šฉ API ํ˜ธ์ถœ"""
263
+ try:
264
+ result = client.predict(api_name="/apply_excel_example")
265
+ if isinstance(result, tuple) and len(result) >= 2:
266
+ return result[0], result[1]
267
+ else:
268
+ return None, gr.update()
269
+ except Exception as e:
270
+ print(f"์—‘์…€ ์˜ˆ์‹œ ์ ์šฉ API ํ˜ธ์ถœ ์ค‘ ์˜ค๋ฅ˜: {e}")
271
+ return None, gr.update()
272
+
273
+ def call_apply_direct_example():
274
+ """์ง์ ‘ ์ž…๋ ฅ ์˜ˆ์‹œ ์ ์šฉ API ํ˜ธ์ถœ"""
275
+ try:
276
+ result = client.predict(api_name="/apply_direct_example")
277
+ if isinstance(result, tuple) and len(result) >= 2:
278
+ return result[0], result[1]
279
+ else:
280
+ return "", ""
281
+ except Exception as e:
282
+ print(f"์ง์ ‘ ์ž…๋ ฅ ์˜ˆ์‹œ ์ ์šฉ API ํ˜ธ์ถœ ์ค‘ ์˜ค๋ฅ˜: {e}")
283
+ return "", ""
284
+
285
+ # ===================== Gradio UI ๊ตฌ์„ฑ =====================
286
+ demo = gr.Blocks(css=custom_css, theme=gr.themes.Default(
287
+ primary_hue="orange",
288
+ secondary_hue="orange",
289
+ font=[gr.themes.GoogleFont("Noto Sans KR"), "ui-sans-serif", "system-ui"]
290
+ ))
291
+
292
+ with demo:
293
+ gr.HTML(fontawesome_link)
294
+
295
+ # ํƒญ ๊ตฌ์„ฑ: ์—‘์…€ ๋ถ„์„ ๋ชจ๋“œ์™€ ์ง์ ‘ ์ž…๋ ฅ ๋ถ„์„ ๋ชจ๋“œ
296
+ with gr.Tabs() as tabs:
297
+ #############################
298
+ # ์—‘์…€ ๋ถ„์„ ๋ชจ๋“œ
299
+ #############################
300
+ with gr.TabItem("๐Ÿ’พ ์Šค๋งˆํŠธ์Šคํ† ์–ด ์—‘์…€๋ฆฌ๋ทฐ๋ฐ์ดํ„ฐ ํ™œ์šฉ"):
301
+ # ์ขŒ์ธก: ๋ฐ์ดํ„ฐ ์ž…๋ ฅ ์„น์…˜
302
+ with gr.Row():
303
+ with gr.Column(elem_classes="custom-frame"):
304
+ gr.HTML("<div class='custom-title'>๐Ÿ“‘ ๋ฐ์ดํ„ฐ ์ž…๋ ฅ</div>")
305
+ file_input = gr.File(label="์›๋ณธ ์—‘์…€ ํŒŒ์ผ ์—…๋กœ๋“œ", file_types=[".xlsx"])
306
+ year_radio = gr.Radio(
307
+ choices=[f"{str(y)[-2:]}๋…„" for y in range(datetime.datetime.now().year, datetime.datetime.now().year-5, -1)],
308
+ label="๋ถ„์„๋…„๋„ ์„ ํƒ",
309
+ value=f"{str(datetime.datetime.now().year)[-2:]}๋…„"
310
+ )
311
+ analyze_button = gr.Button("์˜ต์…˜ ๋ถ„์„ํ•˜๊ธฐ", elem_classes="custom-button")
312
+ with gr.Column(elem_classes="custom-frame"):
313
+ gr.HTML("<div class='custom-title'>๐Ÿ“‘ ๋ถ„์„๋ณด๊ณ ์„œ ๋‹ค์šด๋กœ๋“œ</div>")
314
+ download_final_output = gr.File(label="๋ณด๊ณ ์„œ ๋‹ค์šด๋กœ๋“œ")
315
+
316
+ # ๋ฆฌ๋ทฐ๋ถ„์„ ์„น์…˜
317
+ with gr.Column(elem_classes="custom-frame", visible=False) as review_analysis_frame:
318
+ gr.HTML("<div class='custom-title'>๐Ÿ“‘ ๋ฆฌ๋ทฐ๋ถ„์„</div>")
319
+ top20_dropdown = gr.Dropdown(
320
+ label="์•„์ดํ…œ์˜ต์…˜ ๋ถ„์„",
321
+ choices=["์ „์ฒด์˜ต์…˜๋ถ„์„"],
322
+ value="์ „์ฒด์˜ต์…˜๋ถ„์„"
323
+ )
324
+ review_button = gr.Button("๋ฆฌ๋ทฐ ๋ถ„์„ํ•˜๊ธฐ", elem_classes="custom-button")
325
+
326
+ # โ”€โ”€โ”€ ๋ถ„์„ ๊ฒฐ๊ณผ: 4ํ–‰ ร— 2์—ด โ”€โ”€โ”€
327
+ # 1ํ–‰: โœจ ์ฃผ์š”๊ธ์ •๋ฆฌ๋ทฐ / โœจ ์ฃผ์š”๋ถ€์ •๋ฆฌ๋ทฐ
328
+ with gr.Row():
329
+ with gr.Column(elem_classes="custom-frame"):
330
+ gr.HTML("<div class='custom-title'>โœจ ์ฃผ์š”๊ธ์ •๋ฆฌ๋ทฐ</div>")
331
+ positive_output = gr.Textbox(label="๊ธ์ •๋ฆฌ๋ทฐ๋ฆฌ์ŠคํŠธ (20๊ฐœ)", lines=10)
332
+ with gr.Column(elem_classes="custom-frame"):
333
+ gr.HTML("<div class='custom-title'>โœจ ์ฃผ์š”๋ถ€์ •๋ฆฌ๋ทฐ</div>")
334
+ negative_output = gr.Textbox(label="๋ถ€์ •๋ฆฌ๋ทฐ๋ฆฌ์ŠคํŠธ (30๊ฐœ)", lines=10)
335
+
336
+ # 2ํ–‰: ๐Ÿ“ข ๊ธ์ •๋ฆฌ๋ทฐ ๋ถ„์„ / ๐Ÿ“ข ๋ถ€์ •๋ฆฌ๋ทฐ ๋ถ„์„
337
+ with gr.Row():
338
+ with gr.Column(elem_classes="custom-frame"):
339
+ gr.HTML("<div class='custom-title'>๐Ÿ“ข ๊ธ์ •๋ฆฌ๋ทฐ ๋ถ„์„</div>")
340
+ positive_analysis_output = gr.Textbox(label="๊ธ์ •๋ฆฌ๋ทฐ ๋ถ„์„", lines=8)
341
+ with gr.Column(elem_classes="custom-frame"):
342
+ gr.HTML("<div class='custom-title'>๐Ÿ“ข ๋ถ€์ •๋ฆฌ๋ทฐ ๋ถ„์„</div>")
343
+ negative_analysis_output = gr.Textbox(label="๋ถ€์ •๋ฆฌ๋ทฐ ๋ถ„์„", lines=8)
344
+
345
+ # 3ํ–‰: ๐Ÿ“Š ๋‹ˆ์ฆˆ ์›์ธ  ๋ถ„์„ / ๐Ÿ”ง ํŒ๋งค์ „๋žต ์ˆ˜๋ฆฝ
346
+ with gr.Row():
347
+ with gr.Column(elem_classes="custom-frame"):
348
+ gr.HTML("<div class='custom-title'>๐Ÿ“Š ๋‹ˆ์ฆˆ์›์ธ ๋ถ„์„</div>")
349
+ insight_analysis_output = gr.Textbox(label="๋‹ˆ์ฆˆ์›์ธ ๋ถ„์„", lines=8)
350
+ with gr.Column(elem_classes="custom-frame"):
351
+ gr.HTML("<div class='custom-title'>๐Ÿ”ง ์ƒํ’ˆํŒ๋งค๋ฐฉํ–ฅ์„ฑ</div>")
352
+ strategy_analysis_output = gr.Textbox(label="์ƒํ’ˆํŒ๋งค๋ฐฉํ–ฅ์„ฑ", lines=8)
353
+
354
+ # 4ํ–‰: ๐Ÿ” ์†Œ์‹ฑ์ „๋žต / ๐Ÿ–ผ๏ธ ์ƒ์„ธํŽ˜์ด์ง€ ์ „๋žต
355
+ with gr.Row():
356
+ with gr.Column(elem_classes="custom-frame"):
357
+ gr.HTML("<div class='custom-title'>๐Ÿ” ์†Œ์‹ฑ์ „๋žต</div>")
358
+ sourcing_analysis_output = gr.Textbox(label="์†Œ์‹ฑ์ „๋žต", lines=8)
359
+ with gr.Column(elem_classes="custom-frame"):
360
+ gr.HTML("<div class='custom-title'>๐Ÿ–ผ๏ธ ๋งˆ์ผ€ํŒ…์ „๋žต</div>")
361
+ detail_page_analysis_output = gr.Textbox(label="๋งˆ์ผ€ํŒ…์ „๋žต", lines=8)
362
+
363
+ # ์ƒํƒœ ๋ณ€์ˆ˜
364
+ analysis_state = gr.State()
365
+
366
+ # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
367
+ analyze_button.click(
368
+ fn=call_analyze_options,
369
+ inputs=[file_input, year_radio],
370
+ outputs=[analysis_state, review_analysis_frame, top20_dropdown]
371
+ )
372
+
373
+ review_button.click(
374
+ fn=call_analyze_reviews,
375
+ inputs=[top20_dropdown, analysis_state],
376
+ outputs=[download_final_output, positive_output, negative_output,
377
+ positive_analysis_output, negative_analysis_output,
378
+ insight_analysis_output, strategy_analysis_output,
379
+ sourcing_analysis_output, detail_page_analysis_output]
380
+ )
381
+
382
+ #############################
383
+ # ์ง์ ‘ ์ž…๋ ฅ ๋ถ„์„ ๋ชจ๋“œ
384
+ #############################
385
+ with gr.TabItem("๐Ÿ“– ์ง์ ‘ ์ž…๋ ฅํ•œ ์ž๋ฃŒํ™œ์šฉ"):
386
+ with gr.Row():
387
+ with gr.Column(elem_classes="custom-frame"):
388
+ gr.HTML("<div class='custom-title'>๐Ÿ“ ๋ฆฌ๋ทฐ ์ง์ ‘ ์ž…๋ ฅ</div>")
389
+ direct_positive_input = gr.Textbox(
390
+ label="๊ธ์ • ๋ฆฌ๋ทฐ ์ž…๋ ฅ",
391
+ placeholder="๊ธ์ • ๋ฆฌ๋ทฐ๋ฅผ ์—ฌ๊ธฐ์— ์ž…๋ ฅํ•˜์„ธ์š”.(์ตœ๋Œ€ 8000์ž)",
392
+ lines=10, max_length=8000
393
+ )
394
+ direct_negative_input = gr.Textbox(
395
+ label="๋ถ€์ • ๋ฆฌ๋ทฐ ์ž…๋ ฅ",
396
+ placeholder="๋ถ€์ • ๋ฆฌ๋ทฐ๋ฅผ ์—ฌ๊ธฐ์— ์ž…๋ ฅํ•˜์„ธ์š”.(์ตœ๋Œ€ 8000์ž)",
397
+ lines=10, max_length=8000
398
+ )
399
+ direct_review_button = gr.Button("๋ฆฌ๋ทฐ ๋ถ„์„ํ•˜๊ธฐ", elem_classes="custom-button")
400
+ with gr.Column(elem_classes="custom-frame"):
401
+ gr.HTML("<div class='custom-title'>๐Ÿ“‘ ๋ถ„์„๋ณด๊ณ ์„œ ๋‹ค์šด๋กœ๋“œ</div>")
402
+ direct_download_output = gr.File(label="๋ถ„์„ ๋ณด๊ณ ์„œ ๋‹ค์šด๋กœ๋“œ")
403
+
404
+ # 2ํ–‰: ๐Ÿ“ข ๊ธ์ •๋ฆฌ๋ทฐ ๋ถ„์„ / ๐Ÿ“ข ๋ถ€์ •๋ฆฌ๋ทฐ ๋ถ„์„
405
+ with gr.Row():
406
+ with gr.Column(elem_classes="custom-frame"):
407
+ gr.HTML("<div class='custom-title'>๐Ÿ“ข ๊ธ์ •๋ฆฌ๋ทฐ๋ถ„์„</div>")
408
+ direct_positive_analysis_output = gr.Textbox(
409
+ label="๊ธ์ •๋ฆฌ๋ทฐ๋ถ„์„", lines=8
410
+ )
411
+ with gr.Column(elem_classes="custom-frame"):
412
+ gr.HTML("<div class='custom-title'>๐Ÿ“ข ๋ถ€์ •๋ฆฌ๋ทฐ๋ถ„์„</div>")
413
+ direct_negative_analysis_output = gr.Textbox(
414
+ label="๋ถ€์ •๋ฆฌ๋ทฐ๋ถ„์„", lines=8
415
+ )
416
+
417
+ # 3ํ–‰: ๐Ÿ“Š ๋‹ˆ์ฆˆ ์›์ธ  ๋ถ„์„ / ๐Ÿ”ง ํŒ๋งค์ „๋žต ์ˆ˜๋ฆฝ
418
+ with gr.Row():
419
+ with gr.Column(elem_classes="custom-frame"):
420
+ gr.HTML("<div class='custom-title'>๐Ÿ“Š ๋‹ˆ์ฆˆ์›์ธ ๋ถ„์„</div>")
421
+ direct_insight_analysis_output = gr.Textbox(
422
+ label="๋‹ˆ์ฆˆ์›์ธ ๋ถ„์„", lines=8
423
+ )
424
+ with gr.Column(elem_classes="custom-frame"):
425
+ gr.HTML("<div class='custom-title'>๐Ÿ”ง ์ƒํ’ˆํŒ๋งค๋ฐฉํ–ฅ์„ฑ</div>")
426
+ direct_strategy_analysis_output = gr.Textbox(
427
+ label="์ƒํ’ˆํŒ๋งค๋ฐฉํ–ฅ์„ฑ", lines=8
428
+ )
429
+
430
+ # 4ํ–‰: ๐Ÿ” ์†Œ์‹ฑ์ „๋žต / ๐Ÿ–ผ๏ธ ์ƒ์„ธํŽ˜์ด์ง€ ์ „๋žต
431
+ with gr.Row():
432
+ with gr.Column(elem_classes="custom-frame"):
433
+ gr.HTML("<div class='custom-title'>๐Ÿ” ์†Œ์‹ฑ์ „๋žต</div>")
434
+ direct_sourcing_analysis_output = gr.Textbox(
435
+ label="์†Œ์‹ฑ์ „๋žต", lines=8
436
+ )
437
+ with gr.Column(elem_classes="custom-frame"):
438
+ gr.HTML("<div class='custom-title'>๐Ÿ–ผ๏ธ ๋งˆ์ผ€ํŒ…์ „๋žต</div>")
439
+ direct_detail_page_analysis_output = gr.Textbox(
440
+ label="๋งˆ์ผ€ํŒ…์ „๋žต", lines=8
441
+ )
442
+
443
+ # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
444
+ direct_review_button.click(
445
+ fn=call_direct_analyze,
446
+ inputs=[direct_positive_input, direct_negative_input],
447
+ outputs=[direct_download_output, direct_positive_analysis_output, direct_negative_analysis_output,
448
+ direct_insight_analysis_output, direct_strategy_analysis_output,
449
+ direct_sourcing_analysis_output, direct_detail_page_analysis_output]
450
+ )
451
+
452
+ # ์˜ˆ์‹œ ์ ์šฉ ์„น์…˜
453
+ with gr.Column(elem_classes="custom-frame"):
454
+ gr.HTML("<div class='custom-title'>๐Ÿ“š ์˜ˆ์‹œ ์ ์šฉํ•˜๊ธฐ</div>")
455
+ with gr.Row():
456
+ example_excel_button = gr.Button("๐Ÿ“Š ์—‘์…€ ๋ถ„์„ ์˜ˆ์‹œ ์ ์šฉํ•˜๊ธฐ", elem_classes="custom-button")
457
+ example_direct_button = gr.Button("๐Ÿ“ ์ง์ ‘ ์ž…๋ ฅ ์˜ˆ์‹œ ์ ์šฉํ•˜๊ธฐ", elem_classes="custom-button")
458
+
459
+ # ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ
460
+ example_excel_button.click(
461
+ fn=call_apply_excel_example,
462
+ outputs=[file_input, year_radio]
463
+ )
464
+
465
+ example_direct_button.click(
466
+ fn=call_apply_direct_example,
467
+ outputs=[direct_positive_input, direct_negative_input]
468
+ )
469
+
470
+ if __name__ == "__main__":
471
+ demo.launch()