openfree commited on
Commit
faa3549
ยท
verified ยท
1 Parent(s): a6792e3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +347 -198
app.py CHANGED
@@ -1,30 +1,29 @@
1
  """
2
- Square Theory Generator (Markdownโ€‘only, up to 20 ideas)
3
- =====================================================
4
- 2025โ€‘05โ€‘28ย v11 โ— IndentationErrorย fix (SYSTEM_PROMPT duplication)
5
- ---------------------------------------------------
6
- ๋ณ€๊ฒฝ ์š”์•ฝ
7
- ---------
8
- * **๋ฌธ์ œ**: SYSTEM_PROMPT ์•„๋ž˜์— ์ค‘๋ณต ๋ฌธ์ž์—ด์ด ์žˆ์–ด ๋“ค์—ฌ์“ฐ๊ธฐ ์˜ค๋ฅ˜ ๋ฐœ์ƒ.
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. LLM Prompt & Utilities
36
  # -------------------------------------------------
37
- SYSTEM_PROMPT = (
38
- "๋„ˆ๋Š” ํ•œ๊ตญ์–ด ์นดํ”ผยท๋ธŒ๋žœ๋“œ ๋„ค์ด๋ฐ ์ „๋ฌธ๊ฐ€์ด์ž Square Theory ๋„์šฐ๋ฏธ๋‹ค. "
39
- "์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ํ•˜๋‚˜์˜ ๋‹จ์–ด(tl)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ’ˆ์งˆ์ด ๋›ฐ์–ด๋‚œ ์ˆœ์„œ๋Œ€๋กœ ์ตœ๋Œ€ 20๊ฐœ์˜ ์ œ์•ˆ์„ JSON ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•ด๋ผ. "
40
- "**์ค‘์š”**: tl, tr, br, bl ๊ฐ ํ•„๋“œ๋Š” ๋ฐ˜๋“œ์‹œ ๊ณต๋ฐฑ์ด ์—†๋Š” **๋‹จ์ผ ๋‹จ์–ด**(ํ•œ๊ธ€ 1~2์–ด์ ˆ ๋˜๋Š” ์˜์–ด 1๋‹จ์–ด)์—ฌ์•ผ ํ•œ๋‹ค. "
41
- "๊ฐ ์›์†Œ๋Š” tl, tr, br, bl, top_phrase, bottom_phrase, slogan, brand ํ•„๋“œ๋ฅผ ๊ฐ€์ง„๋‹ค. "
42
- "๋ฐฐ์—ด ์ฒซ ๋ฒˆ์งธ ์›์†Œ๊ฐ€ ๊ฐ€์žฅ ์šฐ์ˆ˜ํ•ด์•ผ ํ•˜๋ฉฐ JSON ์™ธ ๋ฌธ์ž๋Š” ๊ธˆ์ง€ํ•œ๋‹ค."
43
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
- FALLBACK_MODELS = ["gpt-4o-mini", "gpt-4o", "gpt-4o-preview", "gpt-4-turbo"]
46
 
 
 
47
 
48
- def _clean_json_block(text: str) -> str:
 
49
  text = text.strip()
50
  if text.startswith("```"):
51
- text = text.split("\n", 1)[1] if "\n" in text else text[3:]
52
- if text.endswith("```"):
53
- text = text[:-3]
54
  return text.strip()
55
 
56
-
57
- def _call_llm(seed: str):
58
- last_exc = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  for model in FALLBACK_MODELS:
60
  try:
61
- resp = client.chat.completions.create(
62
  model=model,
63
  messages=[
64
- {"role": "system", "content": SYSTEM_PROMPT},
65
- {"role": "user", "content": seed},
66
  ],
67
- temperature=0.9,
68
- max_tokens=2048,
 
69
  )
70
- cleaned = _clean_json_block(resp.choices[0].message.content)
71
- data = json.loads(cleaned) if cleaned else []
72
- if isinstance(data, dict):
73
- data = [data]
74
- if not isinstance(data, list) or not (1 <= len(data) <= 20):
75
- raise ValueError("LLM ์‘๋‹ต์ด ์˜ฌ๋ฐ”๋ฅธ 1โ€‘20๊ฐœ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์•„๋‹˜")
76
- return data
77
- except (openai.OpenAIError, json.JSONDecodeError, ValueError, TypeError) as exc:
78
- last_exc = exc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  continue
80
- raise RuntimeError(f"LLM ํ˜ธ์ถœ ์‹คํŒจ: {last_exc}")
 
81
 
82
  # -------------------------------------------------
83
- # 2. Gradio callback
84
  # -------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
- def generate(seed_word: str):
 
 
 
 
87
  seed_word = seed_word.strip()
88
  if not seed_word:
89
- return "โš ๏ธ **์‹œ๋“œ ๋‹จ์–ด๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.**"
 
90
  try:
91
- results = _call_llm(seed_word)
92
- md = [f"## ์ด {len(results)}๊ฐœ ์ œ์•ˆ"]
 
 
 
 
 
 
 
 
 
 
 
93
  for idx, item in enumerate(results, 1):
94
- md.append(
95
- f"### {idx}. ๋ธŒ๋žœ๋“œ ๋„ค์ž„: {item['brand']}\n"
96
- f"**๋ฉ”์ธ ์นดํ”ผ** \
97
- โ€ข ์ƒ๋‹จ: {item['top_phrase']} \
98
- โ€ข ํ•˜๋‹จ: {item['bottom_phrase']}\n\n"
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
- run.click(generate, inputs=seed, outputs=md_out)
 
 
118
 
119
- demo.queue().launch()
 
120
 
121
- # -------------------------------------------------
122
- # 4. (Optional) ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ์•„์ด๋””์–ด
123
- # -------------------------------------------------
124
- # - JSON์— "why" ํ•„๋“œ ์š”์ฒญ โ€” ๊ฐ ์ œ์•ˆ์˜ ์„ ์ • ์ด์œ ๋ฅผ ํ‘œ์‹œํ•ด ์„ค๋“๋ ฅ ๊ฐ•ํ™”
125
- # - ํƒ€๊นƒ ์ฑ„๋„๋ณ„ ํƒœ๊ทธ๋ผ์ธ ๋ณ€ํ˜• ์˜ต์…˜(OHHยทSNSยทTV)
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
- import os
147
- import json
148
- import gradio as gr
149
- import openai
150
- from openai import OpenAI
 
 
 
 
 
 
151
 
152
  # -------------------------------------------------
153
- # 0. OpenAI ํด๋ผ์ด์–ธํŠธ
154
  # -------------------------------------------------
155
- if not os.getenv("OPENAI_API_KEY"):
156
- raise EnvironmentError("OPENAI_API_KEY ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
157
-
158
- client = OpenAI()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
  # -------------------------------------------------
161
- # 1. LLM Prompt & Utilities
162
  # -------------------------------------------------
163
- SYSTEM_PROMPT = (
164
- "๋„ˆ๋Š” ํ•œ๊ตญ์–ด ์นดํ”ผยท๋ธŒ๋žœ๋“œ ๋„ค์ด๋ฐ ์ „๋ฌธ๊ฐ€์ด์ž Square Theory ๋„์šฐ๋ฏธ๋‹ค. "
165
- "์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ํ•˜๋‚˜์˜ ๋‹จ์–ด(tl)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ’ˆ์งˆ์ด ๋›ฐ์–ด๋‚œ ์ˆœ์„œ๋Œ€๋กœ ์ตœ๋Œ€ 20๊ฐœ์˜ ์ œ์•ˆ์„ JSON ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•ด๋ผ. "
166
- "**์ค‘์š”**: tl, tr, br, bl ๊ฐ ํ•„๋“œ๋Š” ๋ฐ˜๋“œ์‹œ ๊ณต๋ฐฑ์ด ์—†๋Š” **๋‹จ์ผ ๋‹จ์–ด**(ํ•œ๊ธ€ 1~2์–ด์ ˆ ๋˜๋Š” ์˜์–ด 1๋‹จ์–ด)์—ฌ์•ผ ํ•œ๋‹ค. "
167
- "๊ฐ ์›์†Œ๋Š” tl, tr, br, bl, top_phrase, bottom_phrase, slogan, brand ํ•„๋“œ๋ฅผ ๊ฐ€์ง„๋‹ค. "
168
- "๋ฐฐ์—ด ์ฒซ ๋ฒˆ์งธ ์›์†Œ๊ฐ€ ๊ฐ€์žฅ ์šฐ์ˆ˜ํ•ด์•ผ ํ•˜๋ฉฐ JSON ์™ธ ๋ฌธ์ž๋Š” ๊ธˆ์ง€ํ•œ๋‹ค."
169
- )
170
-
171
- FALLBACK_MODELS = ["gpt-4o-mini", "gpt-4o", "gpt-4o-preview", "gpt-4-turbo"]
172
-
173
-
174
- def _clean_json_block(text: str) -> str:
175
- text = text.strip()
176
- if text.startswith("```"):
177
- text = text.split("\n", 1)[1] if "\n" in text else text[3:]
178
- if text.endswith("```"):
179
- text = text[:-3]
180
- return text.strip()
181
-
182
-
183
- def _call_llm(seed: str):
184
- last_exc = None
185
- for model in FALLBACK_MODELS:
186
- try:
187
- resp = client.chat.completions.create(
188
- model=model,
189
- messages=[
190
- {"role": "system", "content": SYSTEM_PROMPT},
191
- {"role": "user", "content": seed},
192
- ],
193
- temperature=0.9,
194
- max_tokens=2048,
195
  )
196
- cleaned = _clean_json_block(resp.choices[0].message.content)
197
- data = json.loads(cleaned) if cleaned else []
198
- if isinstance(data, dict):
199
- data = [data]
200
- if not isinstance(data, list) or not (1 <= len(data) <= 20):
201
- raise ValueError("LLM ์‘๋‹ต์ด ์˜ฌ๋ฐ”๋ฅธ 1โ€‘20๊ฐœ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์•„๋‹˜")
202
- return data
203
- except (openai.OpenAIError, json.JSONDecodeError, ValueError, TypeError) as exc:
204
- last_exc = exc
205
- continue
206
- raise RuntimeError(f"LLM ํ˜ธ์ถœ ์‹คํŒจ: {last_exc}")
207
-
208
- # -------------------------------------------------
209
- # 2. Gradio callback
210
- # -------------------------------------------------
211
-
212
- def generate(seed_word: str):
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
- return "\n".join(md)
231
- except Exception as exc:
232
- return f"โŒ **์˜ค๋ฅ˜:** {exc}"
233
-
234
- # -------------------------------------------------
235
- # 3. UI
236
- # -------------------------------------------------
237
- with gr.Blocks(title="Square Theory โ€“ ์ตœ๋Œ€ 20๊ฐœ ๐Ÿ‡ฐ๐Ÿ‡ท") as demo:
238
- gr.Markdown("""# ๐ŸŸง Square Theory ์ œ์•ˆ (์ตœ๋Œ€ 20๊ฐœ)\n๋‹จ์–ด ํ•˜๋‚˜ ์ž…๋ ฅ โ†’ LLM์ด ์ •๋ ฌํ•œ ์‚ฌ๊ฐํ˜•/์นดํ”ผ/๋ธŒ๋žœ๋“œ ๋„ค์ž„""")
239
- seed = gr.Textbox(label="์‹œ๋“œ ๋‹จ์–ด(TL)", placeholder="์˜ˆ: ๊ณจ๋“ ")
240
- run = gr.Button("์ƒ์„ฑ")
241
- md_out = gr.Markdown()
242
-
243
- run.click(generate, inputs=seed, outputs=md_out)
244
-
245
- demo.queue().launch()
246
-
247
- # -------------------------------------------------
248
- # 4. (Optional) ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ์•„์ด๋””์–ด
249
- # -------------------------------------------------
250
- # - JSON์— "why" ํ•„๋“œ ์š”์ฒญ โ€” ๊ฐ ์ œ์•ˆ์˜ ์„ ์ • ์ด์œ ๋ฅผ ํ‘œ์‹œํ•ด ์„ค๋“๋ ฅ ๊ฐ•ํ™”
251
- # - ํƒ€๊นƒ ์ฑ„๋„๋ณ„ ํƒœ๊ทธ๋ผ์ธ ๋ณ€ํ˜• ์˜ต์…˜(OHHยทSNSยทTV)
252
- # - CSV ๋‹ค์šด๋กœ๋“œ ๋ฒ„ํŠผ์œผ๋กœ ๊ฒฐ๊ณผ ์ผ๊ด„ ์ €์žฅ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ )