openfree commited on
Commit
56acaac
ยท
verified ยท
1 Parent(s): 5edac77

Create app-backup.py

Browse files
Files changed (1) hide show
  1. app-backup.py +436 -0
app-backup.py ADDED
@@ -0,0 +1,436 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Square Theory Brand Generator
3
+ =============================
4
+ 2025-05-28 | Square Theory๋ฅผ ํ™œ์šฉํ•œ ๋ธŒ๋žœ๋“œ ๋„ค์ด๋ฐ & ์Šฌ๋กœ๊ฑด ์ƒ์„ฑ๊ธฐ
5
+ ----------------------------------------------------------
6
+
7
+ Square Theory๋ฅผ ๋ธŒ๋žœ๋”ฉ์— ์ ์šฉ: ๋ธŒ๋žœ๋“œ๋ช…์ด Square๋ฅผ ์™„์„ฑํ•˜๋Š” ๊ตฌ์กฐ
8
+ ์˜ˆ: GRUBHUB = GRUB(์Œ์‹) + HUB(์ค‘์‹ฌ) โ†’ ์Œ์‹ ๋ฐฐ๋‹ฌ์˜ ์ค‘์‹ฌ
9
+ """
10
+
11
+ import os
12
+ import json
13
+ import gradio as gr
14
+ import openai
15
+ from openai import OpenAI
16
+ from datetime import datetime
17
+ from typing import List, Dict, Tuple, Optional
18
+
19
+ # OpenAI ํด๋ผ์ด์–ธํŠธ
20
+ if not os.getenv("OPENAI_API_KEY"):
21
+ raise EnvironmentError("OPENAI_API_KEY ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
22
+
23
+ client = OpenAI()
24
+
25
+ # Square Theory ๋ธŒ๋žœ๋”ฉ ์ „์šฉ ํ”„๋กฌํ”„ํŠธ
26
+ BRANDING_SQUARE_PROMPT = """
27
+ ๋‹น์‹ ์€ Square Theory๋ฅผ ํ™œ์šฉํ•œ ๋ธŒ๋žœ๋“œ ๋„ค์ด๋ฐ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค.
28
+
29
+ Square Theory๋Š” 4๊ฐœ์˜ ๋‹จ์–ด๊ฐ€ ์˜๋ฏธ์  ๊ด€๊ณ„๋กœ ์—ฐ๊ฒฐ๋˜์–ด ์‚ฌ๊ฐํ˜•์„ ์ด๋ฃจ๋Š” ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.
30
+ ์ด๋ฅผ ๋ธŒ๋žœ๋”ฉ์— ์ ์šฉํ•˜๋ฉด, ๋ธŒ๋žœ๋“œ๋ช…์ด Square๋ฅผ ์™„์„ฑํ•˜๋ฉฐ "์•„ํ•˜!" ๋ชจ๋จผํŠธ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
31
+
32
+ ์„ฑ๊ณต์ ์ธ ๋ธŒ๋žœ๋“œ Square ์˜ˆ์‹œ:
33
+ 1. GRUBHUB: GRUB(์Œ์‹) + HUB(์ค‘์‹ฌ) = ์Œ์‹ ๋ฐฐ๋‹ฌ์˜ ์ค‘์‹ฌ์ง€
34
+ 2. Brand New (๋ฆฌ๋ธŒ๋žœ๋”ฉ ๋ธ”๋กœ๊ทธ): BRAND + NEW = ์ƒˆ๋กœ์šด ๋ธŒ๋žœ๋“œ = ์—…๋ฐ์ดํŠธ๋œ ๋ธŒ๋žœ๋“œ
35
+ 3. Crosscord: CROSSWORD + DISCORD = ํฌ๋กœ์Šค์›Œ๋“œ ์ปค๋ฎค๋‹ˆํ‹ฐ ์„œ๋ฒ„
36
+
37
+ ๋ธŒ๋žœ๋“œ Square ์ƒ์„ฑ ์›์น™:
38
+ 1. ๋ธŒ๋žœ๋“œ๋ช…์€ ๋‘ ๋‹จ์–ด์˜ ์กฐํ•ฉ (ํ•ฉ์„ฑ์–ด, ํฌํŠธ๋งจํ† , ๋˜๋Š” ๊ตฌ๋ฌธ)
39
+ 2. ๊ฐ ๋‹จ์–ด๋Š” ๋น„์ฆˆ๋‹ˆ์Šค์˜ ํ•ต์‹ฌ ์†์„ฑ๊ณผ ์—ฐ๊ฒฐ
40
+ 3. ์ „์ฒด Square๊ฐ€ ๋ธŒ๋žœ๋“œ์˜ ์ •์ฒด์„ฑ์„ ๊ฐ•ํ™”
41
+ 4. ์Šฌ๋กœ๊ฑด์€ Square์˜ ์˜๋ฏธ๋ฅผ ํ™•์žฅ
42
+
43
+ ์‚ฌ์šฉ์ž ์ž…๋ ฅ(์—…์ข…/ํ‚ค์›Œ๋“œ)์„ ๋ฐ›์•„ ๋‹ค์Œ ํ˜•์‹์˜ JSON ๋ฐฐ์—ด์„ ์ƒ์„ฑํ•˜์„ธ์š”:
44
+ {
45
+ "brand_name": "๋ธŒ๋žœ๋“œ๋ช…",
46
+ "brand_type": "compound/portmanteau/phrase",
47
+ "tl": "์™ผ์ชฝ์ƒ๋‹จ ๋‹จ์–ด",
48
+ "tr": "์˜ค๋ฅธ์ชฝ์ƒ๋‹จ ๋‹จ์–ด",
49
+ "bl": "์™ผ์ชฝํ•˜๋‹จ ๋‹จ์–ด",
50
+ "br": "์˜ค๋ฅธ์ชฝํ•˜๋‹จ ๋‹จ์–ด",
51
+ "top_edge": "์ƒ๋‹จ ๊ด€๊ณ„",
52
+ "bottom_edge": "ํ•˜๋‹จ ๊ด€๊ณ„",
53
+ "left_edge": "์™ผ์ชฝ ๊ด€๊ณ„",
54
+ "right_edge": "์˜ค๋ฅธ์ชฝ ๊ด€๊ณ„",
55
+ "slogan": "๋ธŒ๋žœ๋“œ ์Šฌ๋กœ๊ฑด",
56
+ "tagline": "์งง์€ ํƒœ๊ทธ๋ผ์ธ",
57
+ "business_description": "๋น„์ฆˆ๋‹ˆ์Šค ์„ค๋ช…",
58
+ "why_it_works": "์™œ ์ด Square๊ฐ€ ํšจ๊ณผ์ ์ธ์ง€",
59
+ "target_audience": "ํƒ€๊ฒŸ ๊ณ ๊ฐ",
60
+ "brand_personality": "๋ธŒ๋žœ๋“œ ๊ฐœ์„ฑ",
61
+ "impact_score": 1-10
62
+ }
63
+
64
+ ์ฐฝ์˜์ ์ด๋ฉด์„œ๋„ ๊ธฐ์–ตํ•˜๊ธฐ ์‰ฝ๊ณ , ๋น„์ฆˆ๋‹ˆ์Šค ๋ณธ์งˆ์„ ๋‹ด์€ ๋ธŒ๋žœ๋“œ๋ฅผ ๋งŒ๋“œ์„ธ์š”.
65
+ """
66
+
67
+ # ์—…์ข…๋ณ„ ์˜ˆ์‹œ
68
+ INDUSTRY_EXAMPLES = [
69
+ "์นดํŽ˜/์ปคํ”ผ์ˆ",
70
+ "ํ”ผํŠธ๋‹ˆ์Šค/ํ—ฌ์Šค์žฅ",
71
+ "๊ต์œก/์—๋“€ํ…Œํฌ",
72
+ "๋ทฐํ‹ฐ/ํ™”์žฅํ’ˆ",
73
+ "์Œ์‹ ๋ฐฐ๋‹ฌ",
74
+ "์—ฌํ–‰/๊ด€๊ด‘",
75
+ "๊ธˆ์œต/ํ•€ํ…Œํฌ",
76
+ "ํŒจ์…˜/์˜๋ฅ˜",
77
+ "๋ฐ˜๋ ค๋™๋ฌผ",
78
+ "์นœํ™˜๊ฒฝ/์ง€์†๊ฐ€๋Šฅ"
79
+ ]
80
+
81
+ def generate_brand_squares(industry: str, keywords: str, count: int = 5) -> List[Dict]:
82
+ """Square Theory ๊ธฐ๋ฐ˜ ๋ธŒ๋žœ๋“œ ์ƒ์„ฑ"""
83
+
84
+ user_prompt = f"""
85
+ ์—…์ข…: {industry}
86
+ ํ‚ค์›Œ๋“œ: {keywords}
87
+
88
+ ์œ„ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ Square Theory๋ฅผ ํ™œ์šฉํ•œ ๋ธŒ๋žœ๋“œ {count}๊ฐœ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”.
89
+ ๊ฐ ๋ธŒ๋žœ๋“œ๋Š” ์™„์ „ํ•œ Square๋ฅผ ํ˜•์„ฑํ•ด์•ผ ํ•˜๋ฉฐ, ๋ธŒ๋žœ๋“œ๋ช…์ด Square์˜ ํ•ต์‹ฌ์ด ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
90
+ """
91
+
92
+ try:
93
+ response = client.chat.completions.create(
94
+ model="gpt-4o",
95
+ messages=[
96
+ {"role": "system", "content": BRANDING_SQUARE_PROMPT},
97
+ {"role": "user", "content": user_prompt}
98
+ ],
99
+ temperature=0.85,
100
+ max_tokens=4000,
101
+ response_format={"type": "json_object"}
102
+ )
103
+
104
+ content = response.choices[0].message.content
105
+ data = json.loads(content)
106
+
107
+ # ์‘๋‹ต ์ •๊ทœํ™”
108
+ if isinstance(data, dict):
109
+ if "brands" in data:
110
+ results = data["brands"]
111
+ elif "results" in data:
112
+ results = data["results"]
113
+ else:
114
+ results = [data]
115
+ else:
116
+ results = data
117
+
118
+ # ์ ์ˆ˜์ˆœ ์ •๋ ฌ
119
+ results.sort(key=lambda x: x.get("impact_score", 0), reverse=True)
120
+
121
+ return results[:count]
122
+
123
+ except Exception as e:
124
+ raise RuntimeError(f"๋ธŒ๋žœ๋“œ ์ƒ์„ฑ ์‹คํŒจ: {e}")
125
+
126
+ def visualize_brand_square(brand: Dict) -> str:
127
+ """๋ธŒ๋žœ๋“œ Square ์‹œ๊ฐํ™”"""
128
+
129
+ brand_name = brand.get('brand_name', 'BRAND')
130
+ tl, tr = brand.get('tl', '?'), brand.get('tr', '?')
131
+ bl, br = brand.get('bl', '?'), brand.get('br', '?')
132
+
133
+ # ๋ธŒ๋žœ๋“œ๋ช… ๋ถ„ํ•ด (compound/portmanteau์ธ ๊ฒฝ์šฐ)
134
+ if brand.get('brand_type') == 'compound':
135
+ parts = brand_name.split()
136
+ brand_part1 = parts[0] if len(parts) > 0 else brand_name[:len(brand_name)//2]
137
+ brand_part2 = parts[1] if len(parts) > 1 else brand_name[len(brand_name)//2:]
138
+ else:
139
+ # ํฌํŠธ๋งจํ† ์˜ ๊ฒฝ์šฐ ๋Œ€๋žต์ ์œผ๋กœ ๋ถ„ํ• 
140
+ mid = len(brand_name) // 2
141
+ brand_part1 = brand_name[:mid+1]
142
+ brand_part2 = brand_name[mid-1:]
143
+
144
+ return f"""
145
+ <div style="max-width: 700px; margin: 20px auto; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
146
+
147
+ <!-- ๋ธŒ๋žœ๋“œ๋ช… ํ—ค๋” -->
148
+ <div style="text-align: center; margin-bottom: 30px;">
149
+ <h2 style="font-size: 2.5em; margin: 0; color: #2c3e50; letter-spacing: -1px;">{brand_name}</h2>
150
+ <p style="font-size: 1.2em; color: #7f8c8d; margin: 10px 0; font-style: italic;">"{brand.get('slogan', '')}"</p>
151
+ <p style="font-size: 0.9em; color: #95a5a6; margin: 5px 0;">{brand.get('tagline', '')}</p>
152
+ </div>
153
+
154
+ <!-- Square ๋‹ค์ด์–ด๊ทธ๋žจ -->
155
+ <div style="position: relative; width: 100%; height: 350px; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); border-radius: 12px; padding: 30px; box-shadow: 0 10px 30px rgba(0,0,0,0.1);">
156
+
157
+ <!-- ์ค‘์•™ ๋ธŒ๋žœ๋“œ๋ช… -->
158
+ <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px 40px; border-radius: 12px; box-shadow: 0 5px 20px rgba(0,0,0,0.15); z-index: 10;">
159
+ <div style="font-size: 1.8em; font-weight: bold; color: #2c3e50; text-align: center;">{brand_name}</div>
160
+ <div style="font-size: 0.9em; color: #7f8c8d; text-align: center; margin-top: 5px;">{brand.get('brand_type', 'compound')}</div>
161
+ </div>
162
+
163
+ <!-- ๊ผญ์ง“์  -->
164
+ <div style="position: absolute; top: 30px; left: 30px; background: #3498db; color: white; padding: 12px 20px; border-radius: 8px; font-weight: 600; box-shadow: 0 3px 10px rgba(52, 152, 219, 0.3);">
165
+ {tl}
166
+ </div>
167
+ <div style="position: absolute; top: 30px; right: 30px; background: #e74c3c; color: white; padding: 12px 20px; border-radius: 8px; font-weight: 600; box-shadow: 0 3px 10px rgba(231, 76, 60, 0.3);">
168
+ {tr}
169
+ </div>
170
+ <div style="position: absolute; bottom: 30px; left: 30px; background: #f39c12; color: white; padding: 12px 20px; border-radius: 8px; font-weight: 600; box-shadow: 0 3px 10px rgba(243, 156, 18, 0.3);">
171
+ {bl}
172
+ </div>
173
+ <div style="position: absolute; bottom: 30px; right: 30px; background: #27ae60; color: white; padding: 12px 20px; border-radius: 8px; font-weight: 600; box-shadow: 0 3px 10px rgba(39, 174, 96, 0.3);">
174
+ {br}
175
+ </div>
176
+
177
+ <!-- ๊ด€๊ณ„ ๋ ˆ์ด๋ธ” -->
178
+ <div style="position: absolute; top: 45px; left: 50%; transform: translateX(-50%); background: rgba(44, 62, 80, 0.9); color: white; padding: 4px 12px; border-radius: 4px; font-size: 0.8em;">
179
+ {brand.get('top_edge', '๊ด€๊ณ„')}
180
+ </div>
181
+ <div style="position: absolute; bottom: 45px; left: 50%; transform: translateX(-50%); background: rgba(44, 62, 80, 0.9); color: white; padding: 4px 12px; border-radius: 4px; font-size: 0.8em;">
182
+ {brand.get('bottom_edge', '๊ด€๊ณ„')}
183
+ </div>
184
+ <div style="position: absolute; top: 50%; left: 45px; transform: translateY(-50%) rotate(-90deg); background: rgba(44, 62, 80, 0.9); color: white; padding: 4px 12px; border-radius: 4px; font-size: 0.8em;">
185
+ {brand.get('left_edge', '๊ด€๊ณ„')}
186
+ </div>
187
+ <div style="position: absolute; top: 50%; right: 45px; transform: translateY(-50%) rotate(90deg); background: rgba(44, 62, 80, 0.9); color: white; padding: 4px 12px; border-radius: 4px; font-size: 0.8em;">
188
+ {brand.get('right_edge', '๊ด€๊ณ„')}
189
+ </div>
190
+
191
+ <!-- ์—ฐ๊ฒฐ์„  -->
192
+ <svg style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;">
193
+ <defs>
194
+ <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
195
+ <stop offset="0%" style="stop-color:#3498db;stop-opacity:0.5" />
196
+ <stop offset="100%" style="stop-color:#e74c3c;stop-opacity:0.5" />
197
+ </linearGradient>
198
+ <linearGradient id="grad2" x1="0%" y1="0%" x2="100%" y2="0%">
199
+ <stop offset="0%" style="stop-color:#f39c12;stop-opacity:0.5" />
200
+ <stop offset="100%" style="stop-color:#27ae60;stop-opacity:0.5" />
201
+ </linearGradient>
202
+ </defs>
203
+ <!-- ์ƒ๋‹จ -->
204
+ <line x1="100" y1="45" x2="600" y2="45" stroke="url(#grad1)" stroke-width="3"/>
205
+ <!-- ํ•˜๋‹จ -->
206
+ <line x1="100" y1="305" x2="600" y2="305" stroke="url(#grad2)" stroke-width="3"/>
207
+ <!-- ์™ผ์ชฝ -->
208
+ <line x1="50" y1="80" x2="50" y2="270" stroke="#7f8c8d" stroke-width="3" opacity="0.5"/>
209
+ <!-- ์˜ค๋ฅธ์ชฝ -->
210
+ <line x1="650" y1="80" x2="650" y2="270" stroke="#7f8c8d" stroke-width="3" opacity="0.5"/>
211
+ </svg>
212
+ </div>
213
+
214
+ <!-- ๋ธŒ๋žœ๋“œ ์ •๋ณด -->
215
+ <div style="margin-top: 30px; display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
216
+ <div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.05);">
217
+ <h4 style="margin: 0 0 10px 0; color: #2c3e50;">๐ŸŽฏ ๏ฟฝ๏ฟฝ๏ฟฝ๊ฒŸ ๊ณ ๊ฐ</h4>
218
+ <p style="margin: 0; color: #7f8c8d;">{brand.get('target_audience', 'N/A')}</p>
219
+ </div>
220
+ <div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.05);">
221
+ <h4 style="margin: 0 0 10px 0; color: #2c3e50;">โœจ ๋ธŒ๋žœ๋“œ ๊ฐœ์„ฑ</h4>
222
+ <p style="margin: 0; color: #7f8c8d;">{brand.get('brand_personality', 'N/A')}</p>
223
+ </div>
224
+ </div>
225
+
226
+ <div style="margin-top: 20px; background: #ecf0f1; padding: 20px; border-radius: 8px;">
227
+ <h4 style="margin: 0 0 10px 0; color: #2c3e50;">๐Ÿ’ก ์™œ ํšจ๊ณผ์ ์ธ๊ฐ€?</h4>
228
+ <p style="margin: 0; color: #34495e;">{brand.get('why_it_works', '')}</p>
229
+ </div>
230
+ </div>
231
+ """
232
+
233
+ def generate_brands(industry: str, keywords: str, count: int) -> Tuple[str, str, List[Dict]]:
234
+ """๋ธŒ๋žœ๋“œ ์ƒ์„ฑ ๋ฐ ํ‘œ์‹œ"""
235
+
236
+ if not industry or not keywords:
237
+ return "โš ๏ธ ์—…์ข…๊ณผ ํ‚ค์›Œ๋“œ๋ฅผ ๋ชจ๋‘ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", "", []
238
+
239
+ try:
240
+ brands = generate_brand_squares(industry, keywords, count)
241
+
242
+ # ๋งˆํฌ๋‹ค์šด ๊ฒฐ๊ณผ
243
+ markdown_parts = [
244
+ f"# ๐Ÿข Square Theory ๋ธŒ๋žœ๋“œ ์ œ์•ˆ",
245
+ f"**์—…์ข…**: {industry} | **ํ‚ค์›Œ๋“œ**: {keywords}",
246
+ f"*์ƒ์„ฑ ์‹œ๊ฐ: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n"
247
+ ]
248
+
249
+ # HTML ์‹œ๊ฐํ™”
250
+ html_parts = []
251
+
252
+ for idx, brand in enumerate(brands, 1):
253
+ score = brand.get('impact_score', 0)
254
+
255
+ markdown_parts.append(f"""
256
+ ## {idx}. {brand.get('brand_name', 'N/A')} {'โญ' * min(score, 5)}
257
+
258
+ **์Šฌ๋กœ๊ฑด**: *"{brand.get('slogan', 'N/A')}"*
259
+ **ํƒœ๊ทธ๋ผ์ธ**: {brand.get('tagline', 'N/A')}
260
+
261
+ ### ๐Ÿ“ Square ๊ตฌ์กฐ
262
+ ```
263
+ [{brand.get('tl', '?')}] โ”€({brand.get('top_edge', '?')})โ”€ [{brand.get('tr', '?')}]
264
+ โ”‚ โ”‚
265
+ ({brand.get('left_edge', '?')}) ({brand.get('right_edge', '?')})
266
+ โ”‚ โ”‚
267
+ [{brand.get('bl', '?')}] โ”€({brand.get('bottom_edge', '?')})โ”€ [{brand.get('br', '?')}]
268
+ ```
269
+
270
+ **๋น„์ฆˆ๋‹ˆ์Šค**: {brand.get('business_description', 'N/A')}
271
+ **ํƒ€๊ฒŸ**: {brand.get('target_audience', 'N/A')}
272
+ **๊ฐœ์„ฑ**: {brand.get('brand_personality', 'N/A')}
273
+
274
+ ๐Ÿ’ก **ํšจ๊ณผ**: {brand.get('why_it_works', 'N/A')}
275
+
276
+ ---
277
+ """)
278
+
279
+ # ๋ชจ๋“  ๋ธŒ๋žœ๋“œ ์‹œ๊ฐํ™”
280
+ html_parts.append(f"<h3 style='text-align: center; color: #7f8c8d; margin: 40px 0 20px 0;'>#{idx}</h3>")
281
+ html_parts.append(visualize_brand_square(brand))
282
+
283
+ return "\n".join(markdown_parts), "\n".join(html_parts), brands
284
+
285
+ except Exception as e:
286
+ return f"โŒ ์˜ค๋ฅ˜: {str(e)}", "", []
287
+
288
+ def export_brands(brands: List[Dict]) -> str:
289
+ """๋ธŒ๋žœ๋“œ ์ •๋ณด JSON ๋‚ด๋ณด๋‚ด๊ธฐ"""
290
+ if not brands:
291
+ return None
292
+
293
+ export_data = {
294
+ "generated_at": datetime.now().isoformat(),
295
+ "total_brands": len(brands),
296
+ "brands": brands
297
+ }
298
+
299
+ return json.dumps(export_data, ensure_ascii=False, indent=2)
300
+
301
+ # Gradio UI
302
+ with gr.Blocks(title="Square Theory Brand Generator", theme=gr.themes.Soft()) as demo:
303
+ gr.Markdown("""
304
+ # ๐Ÿข Square Theory Brand Generator
305
+ ### ์˜๋ฏธ์  Square๋ฅผ ์™„์„ฑํ•˜๋Š” ๋ธŒ๋žœ๋“œ ๋„ค์ด๋ฐ & ์Šฌ๋กœ๊ฑด ์ƒ์„ฑ๊ธฐ
306
+
307
+ Square Theory๋ฅผ ํ™œ์šฉํ•ด ๋ธŒ๋žœ๋“œ๋ช…์ด ์˜๋ฏธ์  ์‚ฌ๊ฐํ˜•์„ ์™„์„ฑํ•˜๋Š” ๊ฐ•๋ ฅํ•œ ๋ธŒ๋žœ๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”.
308
+ ์ข‹์€ ๋ธŒ๋žœ๋“œ๋ช…์€ ๋‹จ์ˆœํ•œ ์ด๋ฆ„์ด ์•„๋‹Œ, ๋น„์ฆˆ๋‹ˆ์Šค์˜ ๋ณธ์งˆ์„ ๋‹ด์€ Square๋ฅผ ํ˜•์„ฑํ•ฉ๋‹ˆ๋‹ค.
309
+
310
+ **์„ฑ๊ณต ์‚ฌ๋ก€**: GRUBHUB (GRUB+HUB), Brand New, Crosscord
311
+ """)
312
+
313
+ with gr.Row():
314
+ with gr.Column(scale=2):
315
+ industry_input = gr.Dropdown(
316
+ choices=INDUSTRY_EXAMPLES,
317
+ label="๐Ÿญ ์—…์ข…",
318
+ allow_custom_value=True,
319
+ value="์นดํŽ˜/์ปคํ”ผ์ˆ"
320
+ )
321
+
322
+ keywords_input = gr.Textbox(
323
+ label="๐Ÿ”‘ ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ",
324
+ placeholder="ํ”„๋ฆฌ๋ฏธ์—„, ํŽธ์•ˆํ•œ, ๋„์‹œ์ ์ธ, ์นœํ™˜๊ฒฝ...",
325
+ info="๋ธŒ๋žœ๋“œ๊ฐ€ ๋‹ด์•„์•ผ ํ•  ํ•ต์‹ฌ ๊ฐ€์น˜๋‚˜ ํŠน์ง•๋“ค"
326
+ )
327
+
328
+ count_slider = gr.Slider(
329
+ minimum=3,
330
+ maximum=10,
331
+ value=5,
332
+ step=1,
333
+ label="์ƒ์„ฑ ๊ฐœ์ˆ˜"
334
+ )
335
+
336
+ generate_btn = gr.Button("๐Ÿš€ ๋ธŒ๋žœ๋“œ Square ์ƒ์„ฑ", variant="primary", size="lg")
337
+
338
+ with gr.Column(scale=1):
339
+ gr.Markdown("""
340
+ ### ๐Ÿ’ก Square Theory ๋ธŒ๋žœ๋”ฉ
341
+
342
+ **์ข‹์€ ๋ธŒ๋žœ๋“œ Square์˜ ์กฐ๊ฑด:**
343
+ 1. ๋ธŒ๋žœ๋“œ๋ช…์˜ ๊ฐ ๋ถ€๋ถ„์ด ์˜๋ฏธ๋ฅผ ๊ฐ€์ง
344
+ 2. ๋น„์ฆˆ๋‹ˆ์Šค ๋ณธ์งˆ๊ณผ ์—ฐ๊ฒฐ
345
+ 3. ๊ธฐ์–ตํ•˜๊ธฐ ์‰ฝ๊ณ  ๋ฐœ์Œ ๊ฐ€๋Šฅ
346
+ 4. Square๊ฐ€ "์•„ํ•˜!" ๋ชจ๋จผํŠธ ์ƒ์„ฑ
347
+
348
+ **Square ๊ตฌ๏ฟฝ๏ฟฝ ์˜ˆ์‹œ:**
349
+ ```
350
+ GRUB โ”€(์Œ์‹)โ”€ FOOD
351
+ โ”‚ โ”‚
352
+ (์ค‘์‹ฌ) (๋ฐฐ๋‹ฌ)
353
+ โ”‚ โ”‚
354
+ HUB โ”€(์„œ๋น„์Šค)โ”€ DELIVERY
355
+ ```
356
+ """)
357
+
358
+ # ์ „์—ญ ๋ณ€์ˆ˜
359
+ current_brands = gr.State([])
360
+
361
+ with gr.Tabs():
362
+ with gr.Tab("๐Ÿ“Š ๊ฒฐ๊ณผ ๋ฆฌ์ŠคํŠธ"):
363
+ output_markdown = gr.Markdown()
364
+
365
+ with gr.Tab("๐ŸŽจ ๋ธŒ๋žœ๋“œ ์‹œ๊ฐํ™”"):
366
+ output_visual = gr.HTML()
367
+
368
+ with gr.Tab("๐Ÿ’พ ๋‚ด๋ณด๋‚ด๊ธฐ"):
369
+ export_btn = gr.Button("JSON ํŒŒ์ผ ์ƒ์„ฑ")
370
+ download_file = gr.File(label="๋‹ค์šด๋กœ๋“œ", visible=False)
371
+
372
+ # ์˜ˆ์‹œ
373
+ gr.Examples(
374
+ examples=[
375
+ ["์นดํŽ˜/์ปคํ”ผ์ˆ", "ํ”„๋ฆฌ๋ฏธ์—„, ์•„๋Š‘ํ•œ, ๋„์‹œ"],
376
+ ["ํ”ผํŠธ๋‹ˆ์Šค/ํ—ฌ์Šค์žฅ", "๊ฐ•๋ ฅํ•œ, ์ปค๋ฎค๋‹ˆํ‹ฐ, ๋ณ€ํ™”"],
377
+ ["๊ต์œก/์—๋“€ํ…Œํฌ", "์Šค๋งˆํŠธ, ์žฌ๋ฏธ์žˆ๋Š”, ์„ฑ์žฅ"],
378
+ ["์Œ์‹ ๋ฐฐ๋‹ฌ", "๋น ๋ฅธ, ์‹ ์„ ํ•œ, ๋‹ค์–‘ํ•œ"],
379
+ ["์นœํ™˜๊ฒฝ/์ง€์†๊ฐ€๋Šฅ", "์ž์—ฐ, ๋ฏธ๋ž˜, ์ˆœํ™˜"]
380
+ ],
381
+ inputs=[industry_input, keywords_input]
382
+ )
383
+
384
+ # ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
385
+ def generate_and_store(industry, keywords, count):
386
+ markdown, html, brands = generate_brands(industry, keywords, count)
387
+ return markdown, html, brands
388
+
389
+ generate_btn.click(
390
+ fn=generate_and_store,
391
+ inputs=[industry_input, keywords_input, count_slider],
392
+ outputs=[output_markdown, output_visual, current_brands]
393
+ )
394
+
395
+ def create_export_file(brands):
396
+ if not brands:
397
+ return None
398
+
399
+ content = export_brands(brands)
400
+ filename = f"square_theory_brands_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
401
+
402
+ with open(filename, 'w', encoding='utf-8') as f:
403
+ f.write(content)
404
+
405
+ return gr.File(value=filename, visible=True)
406
+
407
+ export_btn.click(
408
+ fn=create_export_file,
409
+ inputs=[current_brands],
410
+ outputs=[download_file]
411
+ )
412
+
413
+ gr.Markdown("""
414
+ ---
415
+ ### ๐ŸŽฏ ํ™œ์šฉ ๋ฐฉ๋ฒ•
416
+
417
+ 1. **๋ธŒ๋žœ๋“œ ๊ฐœ๋ฐœ**: ์ƒ์„ฑ๋œ Square ์ค‘ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ๊ฒƒ ์„ ํƒ
418
+ 2. **๋งˆ์ผ€ํŒ… ์ „๋žต**: Square์˜ ๊ฐ ์š”์†Œ๋ฅผ ์บ ํŽ˜์ธ์— ํ™œ์šฉ
419
+ 3. **์Šคํ† ๋ฆฌํ…”๋ง**: Square๊ฐ€ ๋งŒ๋“œ๋Š” ๋‚ด๋Ÿฌํ‹ฐ๋ธŒ ํ™œ์šฉ
420
+ 4. **ํ™•์žฅ ๊ฐ€๋Šฅ์„ฑ**: Square์˜ ๊ฐ ๋ชจ์„œ๋ฆฌ์—์„œ ์„œ๋ธŒ๋ธŒ๋žœ๋“œ ํŒŒ์ƒ
421
+
422
+ ### ๐Ÿ“š Square Theory ๋ธŒ๋žœ๋”ฉ์˜ ํž˜
423
+
424
+ Square๋ฅผ ์™„์„ฑํ•˜๋Š” ๋ธŒ๋žœ๋“œ๋Š”:
425
+ - **๊ธฐ์–ตํ•˜๊ธฐ ์‰ฌ์›€**: ์˜๋ฏธ์  ์—ฐ๊ฒฐ์ด ๊ธฐ์–ต์„ ๊ฐ•ํ™”
426
+ - **์Šคํ† ๋ฆฌ๊ฐ€ ์žˆ์Œ**: Square ์ž์ฒด๊ฐ€ ๋ธŒ๋žœ๋“œ ์Šคํ† ๋ฆฌ
427
+ - **ํ™•์žฅ ๊ฐ€๋Šฅ**: ๊ฐ ์š”์†Œ์—์„œ ์ƒˆ๋กœ์šด ์˜๋ฏธ ํŒŒ์ƒ
428
+ - **์ฐจ๋ณ„ํ™”๋จ**: ๋…ํŠนํ•œ ์˜๋ฏธ ๊ตฌ์กฐ๋กœ ๊ฒฝ์Ÿ์‚ฌ์™€ ๊ตฌ๋ณ„
429
+ """)
430
+
431
+ if __name__ == "__main__":
432
+ demo.launch(
433
+ server_name="0.0.0.0",
434
+ server_port=7860,
435
+ share=False
436
+ )