Update app.py
Browse files
app.py
CHANGED
@@ -1,30 +1,29 @@
|
|
1 |
"""
|
2 |
-
Square Theory Generator
|
3 |
-
|
4 |
-
2025
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
```bash
|
14 |
-
pip install --upgrade gradio openai
|
15 |
-
export OPENAI_API_KEY="sk-..."
|
16 |
-
python square_theory_gradio.py
|
17 |
-
```
|
18 |
"""
|
19 |
|
20 |
import os
|
21 |
import json
|
|
|
|
|
22 |
import gradio as gr
|
23 |
import openai
|
24 |
from openai import OpenAI
|
|
|
|
|
25 |
|
26 |
# -------------------------------------------------
|
27 |
-
# 0. OpenAI ํด๋ผ์ด์ธํธ
|
28 |
# -------------------------------------------------
|
29 |
if not os.getenv("OPENAI_API_KEY"):
|
30 |
raise EnvironmentError("OPENAI_API_KEY ํ๊ฒฝ ๋ณ์๋ฅผ ์ค์ ํ์ธ์.")
|
@@ -32,221 +31,371 @@ if not os.getenv("OPENAI_API_KEY"):
|
|
32 |
client = OpenAI()
|
33 |
|
34 |
# -------------------------------------------------
|
35 |
-
# 1.
|
36 |
# -------------------------------------------------
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
|
45 |
-
FALLBACK_MODELS = ["gpt-4o
|
46 |
|
|
|
|
|
47 |
|
48 |
-
def
|
|
|
49 |
text = text.strip()
|
50 |
if text.startswith("```"):
|
51 |
-
|
52 |
-
|
53 |
-
text = text[:-3]
|
54 |
return text.strip()
|
55 |
|
56 |
-
|
57 |
-
|
58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
for model in FALLBACK_MODELS:
|
60 |
try:
|
61 |
-
|
62 |
model=model,
|
63 |
messages=[
|
64 |
-
{"role": "system", "content":
|
65 |
-
{"role": "user", "content": seed}
|
66 |
],
|
67 |
-
temperature=
|
68 |
-
max_tokens=
|
|
|
69 |
)
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
continue
|
80 |
-
|
|
|
81 |
|
82 |
# -------------------------------------------------
|
83 |
-
# 2.
|
84 |
# -------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
|
86 |
-
|
|
|
|
|
|
|
|
|
87 |
seed_word = seed_word.strip()
|
88 |
if not seed_word:
|
89 |
-
return "โ ๏ธ **์๋ ๋จ์ด๋ฅผ ์
๋ ฅํด ์ฃผ์ธ์.**"
|
|
|
90 |
try:
|
91 |
-
|
92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
for idx, item in enumerate(results, 1):
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
f"**์ฌ๋ก๊ฑด** \
|
100 |
-
> {item['slogan']}\n\n"
|
101 |
-
f"**์ฌ๊ฐํ ํค์๋** \
|
102 |
-
TL: {item['tl']} ยท TR: {item['tr']} ยท BR: {item['br']} ยท BL: {item['bl']}\n"
|
103 |
-
)
|
104 |
-
return "\n".join(md)
|
105 |
-
except Exception as exc:
|
106 |
-
return f"โ **์ค๋ฅ:** {exc}"
|
107 |
|
108 |
-
|
109 |
-
# 3. UI
|
110 |
-
# -------------------------------------------------
|
111 |
-
with gr.Blocks(title="Square Theory โ ์ต๋ 20๊ฐ ๐ฐ๐ท") as demo:
|
112 |
-
gr.Markdown("""# ๐ง Square Theory ์ ์ (์ต๋ 20๊ฐ)\n๋จ์ด ํ๋ ์
๋ ฅ โ LLM์ด ์ ๋ ฌํ ์ฌ๊ฐํ/์นดํผ/๋ธ๋๋ ๋ค์""")
|
113 |
-
seed = gr.Textbox(label="์๋ ๋จ์ด(TL)", placeholder="์: ๊ณจ๋ ")
|
114 |
-
run = gr.Button("์์ฑ")
|
115 |
-
md_out = gr.Markdown()
|
116 |
|
117 |
-
|
|
|
|
|
118 |
|
119 |
-
|
|
|
120 |
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
# - CSV ๋ค์ด๋ก๋ ๋ฒํผ์ผ๋ก ๊ฒฐ๊ณผ ์ผ๊ด ์ ์ฅ
|
127 |
-
"""
|
128 |
-
Square Theory Generator (Markdownโonly, up to 20 ideas)
|
129 |
-
=====================================================
|
130 |
-
2025โ05โ28ย v11 โ IndentationErrorย fix (SYSTEM_PROMPT duplication)
|
131 |
-
---------------------------------------------------
|
132 |
-
๋ณ๊ฒฝ ์์ฝ
|
133 |
-
---------
|
134 |
-
* **๋ฌธ์ **: SYSTEM_PROMPT ์๋์ ์ค๋ณต ๋ฌธ์์ด์ด ์์ด ๋ค์ฌ์ฐ๊ธฐ ์ค๋ฅ ๋ฐ์.
|
135 |
-
* **ํด๊ฒฐ**: ์ค๋ณต ๋ธ๋ก ์ ๊ฑฐ, ์ ์ฒด ์ฝ๋ ์ ๋ฆฌ.
|
136 |
-
|
137 |
-
์คํ๋ฒ
|
138 |
-
------
|
139 |
-
```bash
|
140 |
-
pip install --upgrade gradio openai
|
141 |
-
export OPENAI_API_KEY="sk-..."
|
142 |
-
python square_theory_gradio.py
|
143 |
```
|
144 |
-
"""
|
145 |
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
|
152 |
# -------------------------------------------------
|
153 |
-
#
|
154 |
# -------------------------------------------------
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
|
160 |
# -------------------------------------------------
|
161 |
-
#
|
162 |
# -------------------------------------------------
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
"
|
168 |
-
|
169 |
-
)
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
temperature=0.9,
|
194 |
-
max_tokens=2048,
|
195 |
)
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
seed_word = seed_word.strip()
|
214 |
-
if not seed_word:
|
215 |
-
return "โ ๏ธ **์๋ ๋จ์ด๋ฅผ ์
๋ ฅํด ์ฃผ์ธ์.**"
|
216 |
-
try:
|
217 |
-
results = _call_llm(seed_word)
|
218 |
-
md = [f"## ์ด {len(results)}๊ฐ ์ ์"]
|
219 |
-
for idx, item in enumerate(results, 1):
|
220 |
-
md.append(
|
221 |
-
f"### {idx}. ๋ธ๋๋ ๋ค์: {item['brand']}\n"
|
222 |
-
f"**๋ฉ์ธ ์นดํผ** \
|
223 |
-
โข ์๋จ: {item['top_phrase']} \
|
224 |
-
โข ํ๋จ: {item['bottom_phrase']}\n\n"
|
225 |
-
f"**์ฌ๋ก๊ฑด** \
|
226 |
-
> {item['slogan']}\n\n"
|
227 |
-
f"**์ฌ๊ฐํ ํค์๋** \
|
228 |
-
TL: {item['tl']} ยท TR: {item['tr']} ยท BR: {item['br']} ยท BL: {item['bl']}\n"
|
229 |
)
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
"""
|
2 |
+
Square Theory Generator v12 - Enhanced Edition
|
3 |
+
=============================================
|
4 |
+
2025-05-28 | ํ์ง ํฅ์ ๋ฐ ๊ธฐ๋ฅ ํ์ฅ ๋ฒ์
|
5 |
+
-------------------------------------------
|
6 |
+
|
7 |
+
์ฃผ์ ๊ฐ์ ์ฌํญ:
|
8 |
+
- ๋ ๊ตฌ์ฒด์ ์ธ ํ๋กฌํํธ๋ก ๊ณ ํ์ง ๊ฒฐ๊ณผ ์์ฑ
|
9 |
+
- ์๊ฐ์ ํ๋ฆฌ๋ทฐ ๊ธฐ๋ฅ ์ถ๊ฐ
|
10 |
+
- CSV/JSON ๋ด๋ณด๋ด๊ธฐ ๊ธฐ๋ฅ
|
11 |
+
- ์ฌ์์ฑ ๋ฐ ์ฆ๊ฒจ์ฐพ๊ธฐ ๊ธฐ๋ฅ
|
12 |
+
- ํฅ์๋ UI/UX
|
|
|
|
|
|
|
|
|
|
|
13 |
"""
|
14 |
|
15 |
import os
|
16 |
import json
|
17 |
+
import csv
|
18 |
+
import io
|
19 |
import gradio as gr
|
20 |
import openai
|
21 |
from openai import OpenAI
|
22 |
+
from datetime import datetime
|
23 |
+
from typing import List, Dict, Optional, Tuple
|
24 |
|
25 |
# -------------------------------------------------
|
26 |
+
# 0. OpenAI ํด๋ผ์ด์ธํธ ์ค์
|
27 |
# -------------------------------------------------
|
28 |
if not os.getenv("OPENAI_API_KEY"):
|
29 |
raise EnvironmentError("OPENAI_API_KEY ํ๊ฒฝ ๋ณ์๋ฅผ ์ค์ ํ์ธ์.")
|
|
|
31 |
client = OpenAI()
|
32 |
|
33 |
# -------------------------------------------------
|
34 |
+
# 1. ํฅ์๋ ํ๋กฌํํธ ๋ฐ ์ ํธ๋ฆฌํฐ
|
35 |
# -------------------------------------------------
|
36 |
+
ENHANCED_SYSTEM_PROMPT = """
|
37 |
+
๋น์ ์ 20๋
๊ฒฝ๋ ฅ์ ํ๊ตญ ์ต๊ณ ์นดํผ๋ผ์ดํฐ์ด์ ๋ธ๋๋ ๋ค์ด๋ฐ ์ ๋ฌธ๊ฐ์
๋๋ค.
|
38 |
+
Square Theory๋ 4๊ฐ์ ํต์ฌ ๋จ์ด๋ฅผ ์ฌ๊ฐํ ๋ชจ์๋ฆฌ์ ๋ฐฐ์นํ์ฌ ๋ธ๋๋ ์ ์ฒด์ฑ์ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ๋ก ์
๋๋ค.
|
39 |
+
|
40 |
+
์ฌ์ฉ์๊ฐ ์
๋ ฅํ ์๋ ๋จ์ด(tl)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ต๋ 20๊ฐ์ ๊ณ ํ์ง ์ ์์ ์์ฑํ์ธ์.
|
41 |
+
|
42 |
+
๊ฐ ์ ์์ ๋ค์ ์์น์ ๋ฐ๋ผ์ผ ํฉ๋๋ค:
|
43 |
+
1. **์ฌ๊ฐํ ๊ตฌ์ฑ (tl, tr, br, bl)**
|
44 |
+
- ๊ฐ ๋จ์ด๋ ๊ณต๋ฐฑ ์๋ ๋จ์ผ ๋จ์ด (ํ๊ธ 1-2์ด์ ๋๋ ์์ด 1๋จ์ด)
|
45 |
+
- tl(์ผ์ชฝ์๋จ): ์๋ ๋จ์ด
|
46 |
+
- tr(์ค๋ฅธ์ชฝ์๋จ): tl๊ณผ ๋๋น๋๊ฑฐ๋ ๋ณด์ํ๋ ๊ฐ๋
|
47 |
+
- br(์ค๋ฅธ์ชฝํ๋จ): ๊ธ์ ์ ๊ฒฐ๊ณผ๋ ํํ
|
48 |
+
- bl(์ผ์ชฝํ๋จ): ๊ฐ์ฑ์ ๊ฐ์น๋ ๊ฒฝํ
|
49 |
+
|
50 |
+
2. **์นดํผ ์์ฑ ์์น**
|
51 |
+
- top_phrase: ๊ณ ๊ฐ์ ๋์ฆ๋ ๋ฌธ์ ๋ฅผ ์ง๋ ๊ณต๊ฐํ ๋ฌธ๊ตฌ
|
52 |
+
- bottom_phrase: ํด๊ฒฐ์ฑ
์ด๋ ์ฝ์์ ์ ์ํ๋ ํ๋ ์ ๋ํ ๋ฌธ๊ตฌ
|
53 |
+
- slogan: ๋ธ๋๋ ์ ์ฒด์ฑ์ ํ ๋ฌธ์ฅ์ผ๋ก ์์ถ (7-15์ ๊ถ์ฅ)
|
54 |
+
|
55 |
+
3. **๋ธ๋๋ ๋ค์ด๋ฐ**
|
56 |
+
- ๊ธฐ์ตํ๊ธฐ ์ฝ๊ณ ๋ฐ์์ด ์์ฐ์ค๋ฌ์ด ์ด๋ฆ
|
57 |
+
- ์ฌ๊ฐํ์ ํต์ฌ ๊ฐ์น๋ฅผ ๋ฐ์
|
58 |
+
- 2-4์์ ๊ถ์ฅ
|
59 |
+
|
60 |
+
4. **ํ์ง ๊ธฐ์ค**
|
61 |
+
- ๋
์ฐฝ์ฑ: ์ง๋ถํ์ง ์์ ์ ์ ํ ์กฐํฉ
|
62 |
+
- ์ผ๊ด์ฑ: 4๊ฐ ๋จ์ด๊ฐ ์ ๊ธฐ์ ์ผ๋ก ์ฐ๊ฒฐ
|
63 |
+
- ์ค์ฉ์ฑ: ์ค์ ๋ง์ผํ
์ ํ์ฉ ๊ฐ๋ฅ
|
64 |
+
- ๊ฐ์ฑ์ ํธ์๋ ฅ: ํ๊ฒ ๊ณ ๊ฐ์ ๋ง์์ ์์ง์
|
65 |
+
|
66 |
+
JSON ๋ฐฐ์ด๋ก ๋ฐํํ๋ฉฐ, ํ์ง ์์ผ๋ก ์ ๋ ฌํ์ธ์.
|
67 |
+
๊ฐ ๊ฐ์ฒด: {tl, tr, br, bl, top_phrase, bottom_phrase, slogan, brand, concept_score(1-10), target_audience}
|
68 |
+
"""
|
69 |
|
70 |
+
FALLBACK_MODELS = ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo"]
|
71 |
|
72 |
+
# ์์ ์๋ ๋จ์ด๋ค
|
73 |
+
EXAMPLE_SEEDS = ["๊ณจ๋ ", "์ค๋งํธ", "๊ทธ๋ฆฐ", "ํ๋ฆฌ๋ฏธ์", "ํ๋ง", "ํ์", "ํจ์ด", "๋๋ฆผ"]
|
74 |
|
75 |
+
def _clean_json_response(text: str) -> str:
|
76 |
+
"""JSON ์๋ต ์ ์ """
|
77 |
text = text.strip()
|
78 |
if text.startswith("```"):
|
79 |
+
lines = text.split("\n")
|
80 |
+
text = "\n".join(lines[1:-1]) if lines[-1] == "```" else "\n".join(lines[1:])
|
|
|
81 |
return text.strip()
|
82 |
|
83 |
+
def _validate_square_item(item: Dict) -> bool:
|
84 |
+
"""Square Theory ํญ๋ชฉ ๊ฒ์ฆ"""
|
85 |
+
required_fields = ["tl", "tr", "br", "bl", "top_phrase", "bottom_phrase", "slogan", "brand"]
|
86 |
+
if not all(field in item for field in required_fields):
|
87 |
+
return False
|
88 |
+
|
89 |
+
# ๋จ์ด ๊ฒ์ฆ (๊ณต๋ฐฑ ์๋ ๋จ์ผ ๋จ์ด)
|
90 |
+
for corner in ["tl", "tr", "br", "bl"]:
|
91 |
+
if " " in item[corner].strip():
|
92 |
+
return False
|
93 |
+
|
94 |
+
return True
|
95 |
+
|
96 |
+
def _call_llm_with_retry(seed: str, temperature: float = 0.8) -> List[Dict]:
|
97 |
+
"""LLM ํธ์ถ (์ฌ์๋ ๋ก์ง ํฌํจ)"""
|
98 |
+
last_error = None
|
99 |
+
|
100 |
for model in FALLBACK_MODELS:
|
101 |
try:
|
102 |
+
response = client.chat.completions.create(
|
103 |
model=model,
|
104 |
messages=[
|
105 |
+
{"role": "system", "content": ENHANCED_SYSTEM_PROMPT},
|
106 |
+
{"role": "user", "content": f"์๋ ๋จ์ด: {seed}"}
|
107 |
],
|
108 |
+
temperature=temperature,
|
109 |
+
max_tokens=3000,
|
110 |
+
response_format={"type": "json_object"}
|
111 |
)
|
112 |
+
|
113 |
+
content = _clean_json_response(response.choices[0].message.content)
|
114 |
+
data = json.loads(content)
|
115 |
+
|
116 |
+
# ์๋ต ํ์ ์ ๊ทํ
|
117 |
+
if "suggestions" in data:
|
118 |
+
results = data["suggestions"]
|
119 |
+
elif "items" in data:
|
120 |
+
results = data["items"]
|
121 |
+
elif isinstance(data, list):
|
122 |
+
results = data
|
123 |
+
else:
|
124 |
+
results = [data]
|
125 |
+
|
126 |
+
# ๊ฒ์ฆ ๋ฐ ํํฐ๋ง
|
127 |
+
valid_results = [item for item in results if _validate_square_item(item)]
|
128 |
+
|
129 |
+
if not valid_results:
|
130 |
+
raise ValueError("์ ํจํ Square Theory ์ ์์ด ์์ต๋๋ค")
|
131 |
+
|
132 |
+
# ์ ์ ๊ธฐ์ค ์ ๋ ฌ (์์ผ๋ฉด ์์ ์ ์ง)
|
133 |
+
valid_results.sort(key=lambda x: x.get("concept_score", 0), reverse=True)
|
134 |
+
|
135 |
+
return valid_results[:20] # ์ต๋ 20๊ฐ
|
136 |
+
|
137 |
+
except Exception as e:
|
138 |
+
last_error = e
|
139 |
continue
|
140 |
+
|
141 |
+
raise RuntimeError(f"๋ชจ๋ ๋ชจ๋ธ์์ ์คํจ: {last_error}")
|
142 |
|
143 |
# -------------------------------------------------
|
144 |
+
# 2. ์๊ฐํ ํจ์
|
145 |
# -------------------------------------------------
|
146 |
+
def create_square_preview(item: Dict) -> str:
|
147 |
+
"""Square Theory ์๊ฐ์ ํ๋ฆฌ๋ทฐ ์์ฑ"""
|
148 |
+
return f"""
|
149 |
+
<div style="border: 2px solid #333; border-radius: 8px; padding: 20px; margin: 10px 0; background: #f9f9f9;">
|
150 |
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;">
|
151 |
+
<div style="text-align: center; padding: 15px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
152 |
+
<strong style="color: #e74c3c;">{item['tl']}</strong>
|
153 |
+
</div>
|
154 |
+
<div style="text-align: center; padding: 15px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
155 |
+
<strong style="color: #3498db;">{item['tr']}</strong>
|
156 |
+
</div>
|
157 |
+
<div style="text-align: center; padding: 15px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
158 |
+
<strong style="color: #f39c12;">{item['bl']}</strong>
|
159 |
+
</div>
|
160 |
+
<div style="text-align: center; padding: 15px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
161 |
+
<strong style="color: #27ae60;">{item['br']}</strong>
|
162 |
+
</div>
|
163 |
+
</div>
|
164 |
+
<div style="text-align: center; background: #2c3e50; color: white; padding: 15px; border-radius: 4px;">
|
165 |
+
<h3 style="margin: 0 0 10px 0; font-size: 1.5em;">{item['brand']}</h3>
|
166 |
+
<p style="margin: 5px 0; font-style: italic;">"{item['slogan']}"</p>
|
167 |
+
</div>
|
168 |
+
</div>
|
169 |
+
"""
|
170 |
|
171 |
+
# -------------------------------------------------
|
172 |
+
# 3. ๋ฉ์ธ ์์ฑ ํจ์
|
173 |
+
# -------------------------------------------------
|
174 |
+
def generate_squares(seed_word: str, temperature: float = 0.8) -> Tuple[str, List[Dict], str]:
|
175 |
+
"""Square Theory ์ ์ ์์ฑ"""
|
176 |
seed_word = seed_word.strip()
|
177 |
if not seed_word:
|
178 |
+
return "โ ๏ธ **์๋ ๋จ์ด๋ฅผ ์
๋ ฅํด ์ฃผ์ธ์.**", [], ""
|
179 |
+
|
180 |
try:
|
181 |
+
# LLM ํธ์ถ
|
182 |
+
results = _call_llm_with_retry(seed_word, temperature)
|
183 |
+
|
184 |
+
# ๋งํฌ๋ค์ด ์์ฑ
|
185 |
+
markdown_parts = [
|
186 |
+
f"# ๐ฏ Square Theory ์ ์: '{seed_word}'",
|
187 |
+
f"*์์ฑ ์๊ฐ: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*",
|
188 |
+
f"\n## ๐ ์ด {len(results)}๊ฐ ์ ์\n"
|
189 |
+
]
|
190 |
+
|
191 |
+
# HTML ํ๋ฆฌ๋ทฐ ์์ฑ
|
192 |
+
preview_html = ""
|
193 |
+
|
194 |
for idx, item in enumerate(results, 1):
|
195 |
+
score = item.get('concept_score', 'N/A')
|
196 |
+
target = item.get('target_audience', '์ผ๋ฐ')
|
197 |
+
|
198 |
+
markdown_parts.append(f"""
|
199 |
+
### {idx}. {item['brand']} {'โญ' * min(int(score) if isinstance(score, (int, float)) else 5, 5)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
|
201 |
+
**๐ฏ ํ๊ฒ**: {target} | **์ ์**: {score}/10
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
202 |
|
203 |
+
**๐ ๋ฉ์ธ ์นดํผ**
|
204 |
+
- ์๋จ: *"{item['top_phrase']}"*
|
205 |
+
- ํ๋จ: *"{item['bottom_phrase']}"*
|
206 |
|
207 |
+
**๐ฌ ์ฌ๋ก๊ฑด**
|
208 |
+
> {item['slogan']}
|
209 |
|
210 |
+
**๐ฒ Square ๊ตฌ์ฑ**
|
211 |
+
```
|
212 |
+
[{item['tl']}] โ โ [{item['tr']}]
|
213 |
+
โ โ
|
214 |
+
[{item['bl']}] โ โ [{item['br']}]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
215 |
```
|
|
|
216 |
|
217 |
+
---
|
218 |
+
""")
|
219 |
+
|
220 |
+
# ์์ 3๊ฐ๋ ํ๋ฆฌ๋ทฐ ์ถ๊ฐ
|
221 |
+
if idx <= 3:
|
222 |
+
preview_html += create_square_preview(item)
|
223 |
+
|
224 |
+
return "\n".join(markdown_parts), results, preview_html
|
225 |
+
|
226 |
+
except Exception as e:
|
227 |
+
return f"โ **์ค๋ฅ ๋ฐ์**: {str(e)}", [], ""
|
228 |
|
229 |
# -------------------------------------------------
|
230 |
+
# 4. ๋ด๋ณด๋ด๊ธฐ ํจ์
|
231 |
# -------------------------------------------------
|
232 |
+
def export_to_csv(results: List[Dict]) -> str:
|
233 |
+
"""CSV ํ์ผ ์์ฑ"""
|
234 |
+
if not results:
|
235 |
+
return None
|
236 |
+
|
237 |
+
output = io.StringIO()
|
238 |
+
fieldnames = ["์์", "๋ธ๋๋๋ช
", "TL", "TR", "BR", "BL",
|
239 |
+
"์๋จ๋ฌธ๊ตฌ", "ํ๋จ๋ฌธ๊ตฌ", "์ฌ๋ก๊ฑด", "์ ์", "ํ๊ฒ"]
|
240 |
+
|
241 |
+
writer = csv.DictWriter(output, fieldnames=fieldnames)
|
242 |
+
writer.writeheader()
|
243 |
+
|
244 |
+
for idx, item in enumerate(results, 1):
|
245 |
+
writer.writerow({
|
246 |
+
"์์": idx,
|
247 |
+
"๋ธ๋๋๋ช
": item['brand'],
|
248 |
+
"TL": item['tl'],
|
249 |
+
"TR": item['tr'],
|
250 |
+
"BR": item['br'],
|
251 |
+
"BL": item['bl'],
|
252 |
+
"์๋จ๋ฌธ๊ตฌ": item['top_phrase'],
|
253 |
+
"ํ๋จ๋ฌธ๊ตฌ": item['bottom_phrase'],
|
254 |
+
"์ฌ๋ก๊ฑด": item['slogan'],
|
255 |
+
"์ ์": item.get('concept_score', 'N/A'),
|
256 |
+
"ํ๊ฒ": item.get('target_audience', '์ผ๋ฐ')
|
257 |
+
})
|
258 |
+
|
259 |
+
return output.getvalue()
|
260 |
+
|
261 |
+
def export_to_json(results: List[Dict]) -> str:
|
262 |
+
"""JSON ํ์ผ ์์ฑ"""
|
263 |
+
if not results:
|
264 |
+
return None
|
265 |
+
|
266 |
+
export_data = {
|
267 |
+
"generated_at": datetime.now().isoformat(),
|
268 |
+
"total_count": len(results),
|
269 |
+
"results": results
|
270 |
+
}
|
271 |
+
|
272 |
+
return json.dumps(export_data, ensure_ascii=False, indent=2)
|
273 |
|
274 |
# -------------------------------------------------
|
275 |
+
# 5. Gradio UI
|
276 |
# -------------------------------------------------
|
277 |
+
# ์ ์ญ ๋ณ์๋ก ๊ฒฐ๊ณผ ์ ์ฅ
|
278 |
+
current_results = []
|
279 |
+
|
280 |
+
def generate_and_update(seed_word, temperature):
|
281 |
+
"""์์ฑ ๋ฐ UI ์
๋ฐ์ดํธ"""
|
282 |
+
global current_results
|
283 |
+
markdown, results, preview = generate_squares(seed_word, temperature)
|
284 |
+
current_results = results
|
285 |
+
|
286 |
+
# ๋ด๋ณด๋ด๊ธฐ ํ์ผ ์์ฑ
|
287 |
+
csv_content = export_to_csv(results) if results else None
|
288 |
+
json_content = export_to_json(results) if results else None
|
289 |
+
|
290 |
+
return markdown, preview, csv_content, json_content
|
291 |
+
|
292 |
+
# UI ๊ตฌ์ฑ
|
293 |
+
with gr.Blocks(title="Square Theory Generator v12", theme=gr.themes.Soft()) as demo:
|
294 |
+
gr.Markdown("""
|
295 |
+
# ๐ง Square Theory Generator v12
|
296 |
+
### ๋ธ๋๋ ์์ด๋ดํฐํฐ๋ฅผ ์ํ 4-Corner ์ ๋ต ๋๊ตฌ
|
297 |
+
|
298 |
+
Square Theory๋ 4๊ฐ์ ํต์ฌ ๋จ์ด๋ฅผ ์ฌ๊ฐํ ๋ชจ์๋ฆฌ์ ๋ฐฐ์นํ์ฌ ๋ธ๋๋์ ์ ์ฒด์ฑ๊ณผ ๊ฐ์น๋ฅผ ๋ช
ํํ ํ๋ ๋ฐฉ๋ฒ๋ก ์
๋๋ค.
|
299 |
+
""")
|
300 |
+
|
301 |
+
with gr.Row():
|
302 |
+
with gr.Column(scale=2):
|
303 |
+
seed_input = gr.Textbox(
|
304 |
+
label="๐ฑ ์๋ ๋จ์ด (TL - ์ผ์ชฝ ์๋จ)",
|
305 |
+
placeholder="์: ๊ณจ๋ , ์ค๋งํธ, ํ๋ฆฌ๋ฏธ์...",
|
306 |
+
info="๋ธ๋๋์ ํต์ฌ์ด ๋ ๋จ์ด๋ฅผ ์
๋ ฅํ์ธ์"
|
|
|
|
|
307 |
)
|
308 |
+
|
309 |
+
with gr.Row():
|
310 |
+
temperature_slider = gr.Slider(
|
311 |
+
minimum=0.1,
|
312 |
+
maximum=1.0,
|
313 |
+
value=0.8,
|
314 |
+
step=0.1,
|
315 |
+
label="์ฐฝ์์ฑ ๋ ๋ฒจ",
|
316 |
+
info="๋์์๋ก ๋ ์ฐฝ์์ ์ด๊ณ ๋ค์ํ ๊ฒฐ๊ณผ"
|
317 |
+
)
|
318 |
+
|
319 |
+
generate_btn = gr.Button("๐ Square Theory ์์ฑ", variant="primary", size="lg")
|
320 |
+
|
321 |
+
gr.Examples(
|
322 |
+
examples=[[seed] for seed in EXAMPLE_SEEDS],
|
323 |
+
inputs=seed_input,
|
324 |
+
label="์์ ์๋ ๋จ์ด"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
325 |
)
|
326 |
+
|
327 |
+
with gr.Column(scale=1):
|
328 |
+
gr.Markdown("""
|
329 |
+
### ๐ก ์ฌ์ฉ ํ
|
330 |
+
|
331 |
+
**์ข์ ์๋ ๋จ์ด:**
|
332 |
+
- ๊ฐ์ : ํ๋ณต, ํํ, ์ด์
|
333 |
+
- ๊ฐ์น: ํ์ , ์ ๋ขฐ, ํ์ง
|
334 |
+
- ํน์ฑ: ์ค๋งํธ, ํ๋ฆฌ๋ฏธ์, ์์ฝ
|
335 |
+
|
336 |
+
**Square ๊ตฌ์กฐ:**
|
337 |
+
```
|
338 |
+
[์๋] โ โ [๋๋น/๋ณด์]
|
339 |
+
โ โ
|
340 |
+
[๊ฒฝํ] โ โ [๊ฒฐ๊ณผ/ํํ]
|
341 |
+
```
|
342 |
+
""")
|
343 |
+
|
344 |
+
with gr.Tabs():
|
345 |
+
with gr.Tab("๐ ๊ฒฐ๊ณผ"):
|
346 |
+
output_markdown = gr.Markdown()
|
347 |
+
|
348 |
+
with gr.Tab("๐จ ์๊ฐ์ ํ๋ฆฌ๋ทฐ"):
|
349 |
+
preview_html = gr.HTML()
|
350 |
+
|
351 |
+
with gr.Tab("๐พ ๋ด๋ณด๋ด๊ธฐ"):
|
352 |
+
with gr.Row():
|
353 |
+
csv_file = gr.File(label="CSV ๋ค์ด๋ก๋", visible=False)
|
354 |
+
json_file = gr.File(label="JSON ๋ค์ด๋ก๋", visible=False)
|
355 |
+
|
356 |
+
export_info = gr.Markdown("""
|
357 |
+
์์ฑ๋ ๊ฒฐ๊ณผ๋ ์๋์ผ๋ก CSV์ JSON ํ์์ผ๋ก ๋ณํ๋ฉ๋๋ค.
|
358 |
+
์์ฑ ๋ฒํผ์ ํด๋ฆญํ ํ ํ์ผ์ด ๋ํ๋ฉ๋๋ค.
|
359 |
+
""")
|
360 |
+
|
361 |
+
# ์ด๋ฒคํธ ์ฐ๊ฒฐ
|
362 |
+
def process_and_save(seed_word, temperature):
|
363 |
+
markdown, preview, csv_content, json_content = generate_and_update(seed_word, temperature)
|
364 |
+
|
365 |
+
# ํ์ผ ์ ์ฅ
|
366 |
+
csv_filename = json_filename = None
|
367 |
+
if csv_content:
|
368 |
+
csv_filename = f"square_theory_{seed_word}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
|
369 |
+
with open(csv_filename, 'w', encoding='utf-8-sig') as f:
|
370 |
+
f.write(csv_content)
|
371 |
+
|
372 |
+
if json_content:
|
373 |
+
json_filename = f"square_theory_{seed_word}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
374 |
+
with open(json_filename, 'w', encoding='utf-8') as f:
|
375 |
+
f.write(json_content)
|
376 |
+
|
377 |
+
return markdown, preview, csv_filename, json_filename
|
378 |
+
|
379 |
+
generate_btn.click(
|
380 |
+
fn=process_and_save,
|
381 |
+
inputs=[seed_input, temperature_slider],
|
382 |
+
outputs=[output_markdown, preview_html, csv_file, json_file]
|
383 |
+
)
|
384 |
+
|
385 |
+
gr.Markdown("""
|
386 |
+
---
|
387 |
+
### ๐ ์ถ๊ฐ ๊ฐ๋ฐ ์์ด๋์ด
|
388 |
+
|
389 |
+
1. **A/B ํ
์คํธ ๋ชจ๋**: ๋์ผ ์๋๋ก ์ฌ๋ฌ ๋ฒ์ ์์ฑ ํ ๋น๊ต
|
390 |
+
2. **ํ์
๊ธฐ๋ฅ**: ์์ฑ๋ ๊ฒฐ๊ณผ์ ํ์๋ค์ด ํฌํ/์ฝ๋ฉํธ
|
391 |
+
3. **ํ์คํ ๋ฆฌ ๊ด๋ฆฌ**: ์ด์ ์์ฑ ๊ฒฐ๊ณผ ์ ์ฅ ๋ฐ ๊ฒ์
|
392 |
+
4. **์
์ข
๋ณ ํ
ํ๋ฆฟ**: F&B, IT, ํจ์
๋ฑ ์
์ข
๋ณ ์ต์ ํ
|
393 |
+
5. **๋ค๊ตญ์ด ์ง์**: ์์ด, ์ค๊ตญ์ด, ์ผ๋ณธ์ด Square Theory
|
394 |
+
""")
|
395 |
+
|
396 |
+
if __name__ == "__main__":
|
397 |
+
demo.launch(
|
398 |
+
server_name="0.0.0.0",
|
399 |
+
server_port=7860,
|
400 |
+
share=False
|
401 |
+
)
|