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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +219 -307
app.py CHANGED
@@ -1,396 +1,308 @@
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 ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
30
 
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__":
 
1
  """
2
+ Square Theory Generator - True Implementation
3
+ ============================================
4
+ 2025-05-28 | Based on the actual Square Theory concept
5
+ ------------------------------------------------------
6
 
7
+ Square Theory: 4๊ฐœ์˜ ๋‹จ์–ด๊ฐ€ ์˜๋ฏธ์  ๊ด€๊ณ„๋กœ ์—ฐ๊ฒฐ๋˜์–ด ์‚ฌ๊ฐํ˜•์„ ์ด๋ฃจ๋Š” ์–ธ์–ด์œ ํฌ
8
+ ์˜ˆ: PUB QUIZ / BAR EXAM (PUB-BAR ๋™์˜์–ด, QUIZ-EXAM ๋™์˜์–ด)
 
 
 
 
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
+ SQUARE_THEORY_PROMPT = """
27
+ ๋‹น์‹ ์€ Square Theory ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. Square Theory๋Š” 4๊ฐœ์˜ ๋‹จ์–ด๊ฐ€ ์‚ฌ๊ฐํ˜•์˜ ๊ผญ์ง“์ ์„ ์ด๋ฃจ๊ณ ,
28
+ ๊ฐ ๋ณ€์ด ์˜๋ฏธ์  ๊ด€๊ณ„(๋™์˜์–ด, ๊ตฌ๋ฌธ, ์šด์œจ ๋“ฑ)๋กœ ์—ฐ๊ฒฐ๋˜๋Š” ์–ธ์–ด ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.
 
 
29
 
30
+ ํ•ต์‹ฌ ์›์น™:
31
+ 1. 4๊ฐœ์˜ ๋‹จ์–ด๊ฐ€ ์‚ฌ๊ฐํ˜•์˜ ๊ผญ์ง“์ ์„ ํ˜•์„ฑ
32
+ 2. ์ธ์ ‘ํ•œ ๋‹จ์–ด๋“ค์€ ํŠน์ • ๊ด€๊ณ„๋กœ ์—ฐ๊ฒฐ (๋ณ€)
33
+ 3. ๋Œ€๋ณ€์˜ ๋‹จ์–ด๋“ค์€ ์ง์ ‘ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์ง€๋งŒ, ๋‹ค๋ฅธ ๋ณ€๋“ค์„ ํ†ตํ•ด ๋†€๋ผ์šด ์ผ์น˜๋ฅผ ์ด๋ฃธ
34
+ 4. ์ „์ฒด ๊ตฌ์กฐ๊ฐ€ "์•„ํ•˜!" ๋ชจ๋จผํŠธ๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•จ
35
 
36
+ ์˜ˆ์‹œ:
37
+ - PUB QUIZ / BAR EXAM: PUB-BAR(๋™์˜์–ด), QUIZ-EXAM(๋™์˜์–ด), PUB QUIZ(๊ตฌ๋ฌธ), BAR EXAM(๊ตฌ๋ฌธ)
38
+ - BOOTY CALL / BUTT DIAL: BOOTY-BUTT(๋™์˜์–ด), CALL-DIAL(๋™์˜์–ด), BOOTY CALL(๊ตฌ๋ฌธ), BUTT DIAL(๊ตฌ๋ฌธ)
39
+ - TOP GUN / TSHIRT CANNON: TOP-TSHIRT(์œ„์น˜), GUN-CANNON(๋ฌด๊ธฐ), TOP GUN(๊ตฌ๋ฌธ), TSHIRT CANNON(๊ตฌ๋ฌธ)
 
 
 
40
 
41
+ ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ๋“œ ๋‹จ์–ด๋‚˜ ์ปจ์…‰์„ ์ œ๊ณตํ•˜๋ฉด, ์ด๋ฅผ ํฌํ•จํ•˜๋Š” Square Theory ๊ตฌ์กฐ๋“ค์„ ์ƒ์„ฑํ•˜์„ธ์š”.
 
 
 
42
 
43
+ ๊ฐ ์ œ์•ˆ์€ ๋‹ค์Œ ํ˜•์‹์˜ JSON์œผ๋กœ:
44
+ {
45
+ "tl": "์™ผ์ชฝ์ƒ๋‹จ ๋‹จ์–ด",
46
+ "tr": "์˜ค๋ฅธ์ชฝ์ƒ๋‹จ ๋‹จ์–ด",
47
+ "bl": "์™ผ์ชฝํ•˜๋‹จ ๋‹จ์–ด",
48
+ "br": "์˜ค๋ฅธ์ชฝํ•˜๋‹จ ๋‹จ์–ด",
49
+ "top_edge": "์ƒ๋‹จ ๊ด€๊ณ„ ์„ค๋ช… (tl-tr)",
50
+ "bottom_edge": "ํ•˜๋‹จ ๊ด€๊ณ„ ์„ค๋ช… (bl-br)",
51
+ "left_edge": "์™ผ์ชฝ ๊ด€๊ณ„ ์„ค๋ช… (tl-bl)",
52
+ "right_edge": "์˜ค๋ฅธ์ชฝ ๊ด€๊ณ„ ์„ค๋ช… (tr-br)",
53
+ "top_phrase": "tl + tr ๊ตฌ๋ฌธ",
54
+ "bottom_phrase": "bl + br ๊ตฌ๋ฌธ",
55
+ "explanation": "์™œ ์ด ์‚ฌ๊ฐํ˜•์ด ํฅ๋ฏธ๋กœ์šด์ง€",
56
+ "cleverness_score": 1-10
57
+ }
58
 
59
+ ์ฐฝ์˜์ ์ด๊ณ  ๋†€๋ผ์šด ์—ฐ๊ฒฐ์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. ๋‹จ์ˆœํ•œ ๋™์˜์–ด ๋‚˜์—ด์ด ์•„๋‹Œ,
60
+ ๋Œ€๋ณ€ ์‚ฌ์ด์˜ ์ˆจ๊ฒจ์ง„ ์—ฐ๊ฒฐ์ด ๋“œ๋Ÿฌ๋‚  ๋•Œ์˜ "์•„ํ•˜!" ๋ชจ๋จผํŠธ๋ฅผ ๋ชฉํ‘œ๋กœ ํ•˜์„ธ์š”.
 
 
 
 
 
 
61
  """
62
 
63
+ def call_square_theory_llm(seed: str, mode: str = "word") -> List[Dict]:
64
+ """Square Theory ์ƒ์„ฑ"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
+ if mode == "word":
67
+ user_prompt = f"์‹œ๋“œ ๋‹จ์–ด '{seed}'๋ฅผ ํฌํ•จํ•˜๋Š” Square Theory ๊ตฌ์กฐ๋ฅผ 5-10๊ฐœ ์ƒ์„ฑํ•˜์„ธ์š”."
68
+ elif mode == "concept":
69
+ user_prompt = f"'{seed}' ์ฃผ์ œ์™€ ๊ด€๋ จ๋œ Square Theory ๊ตฌ์กฐ๋ฅผ 5-10๊ฐœ ์ƒ์„ฑํ•˜์„ธ์š”."
70
+ else: # pair
71
+ user_prompt = f"'{seed}'์™€ ๋น„์Šทํ•œ ํ˜•ํƒœ์˜ Square Theory ๊ตฌ์กฐ๋ฅผ 5-10๊ฐœ ์ƒ์„ฑํ•˜์„ธ์š”."
72
 
73
+ try:
74
+ response = client.chat.completions.create(
75
+ model="gpt-4o",
76
+ messages=[
77
+ {"role": "system", "content": SQUARE_THEORY_PROMPT},
78
+ {"role": "user", "content": user_prompt}
79
+ ],
80
+ temperature=0.8,
81
+ max_tokens=3000,
82
+ response_format={"type": "json_object"}
83
+ )
84
+
85
+ content = response.choices[0].message.content
86
+ data = json.loads(content)
87
+
88
+ # ์‘๋‹ต ์ •๊ทœํ™”
89
+ if isinstance(data, dict):
90
+ if "squares" in data:
91
+ results = data["squares"]
92
+ elif "results" in data:
93
+ results = data["results"]
 
 
 
 
 
 
 
 
94
  else:
95
  results = [data]
96
+ else:
97
+ results = data
98
 
99
+ # ์ ์ˆ˜์ˆœ ์ •๋ ฌ
100
+ results.sort(key=lambda x: x.get("cleverness_score", 0), reverse=True)
101
+
102
+ return results[:10]
103
+
104
+ except Exception as e:
105
+ raise RuntimeError(f"Square Theory ์ƒ์„ฑ ์‹คํŒจ: {e}")
 
 
 
 
 
 
 
 
 
106
 
107
+ def visualize_square(square: Dict) -> str:
108
+ """Square Theory ์‹œ๊ฐํ™”"""
109
+
110
+ tl, tr = square.get('tl', '?'), square.get('tr', '?')
111
+ bl, br = square.get('bl', '?'), square.get('br', '?')
112
+
113
+ top_edge = square.get('top_edge', '๊ด€๊ณ„')
114
+ bottom_edge = square.get('bottom_edge', '๊ด€๊ณ„')
115
+ left_edge = square.get('left_edge', '๊ด€๊ณ„')
116
+ right_edge = square.get('right_edge', '๊ด€๊ณ„')
117
+
118
  return f"""
119
+ <div style="max-width: 600px; margin: 20px auto; font-family: 'Courier New', monospace;">
120
+ <div style="position: relative; width: 100%; height: 300px; background: #f8f9fa; border-radius: 8px; padding: 20px;">
121
+
122
+ <!-- ๊ผญ์ง“์  (๋‹จ์–ด๋“ค) -->
123
+ <div style="position: absolute; top: 20px; left: 20px; background: #3498db; color: white; padding: 15px 25px; border-radius: 8px; font-weight: bold; font-size: 1.2em; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">
124
+ {tl}
125
  </div>
126
+ <div style="position: absolute; top: 20px; right: 20px; background: #e74c3c; color: white; padding: 15px 25px; border-radius: 8px; font-weight: bold; font-size: 1.2em; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">
127
+ {tr}
128
  </div>
129
+ <div style="position: absolute; bottom: 20px; left: 20px; background: #f39c12; color: white; padding: 15px 25px; border-radius: 8px; font-weight: bold; font-size: 1.2em; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">
130
+ {bl}
131
  </div>
132
+ <div style="position: absolute; bottom: 20px; right: 20px; background: #27ae60; color: white; padding: 15px 25px; border-radius: 8px; font-weight: bold; font-size: 1.2em; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">
133
+ {br}
134
  </div>
135
+
136
+ <!-- ๋ณ€ (๊ด€๊ณ„) -->
137
+ <div style="position: absolute; top: 40px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.8); color: white; padding: 5px 15px; border-radius: 4px; font-size: 0.9em;">
138
+ {top_edge}
139
+ </div>
140
+ <div style="position: absolute; bottom: 40px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.8); color: white; padding: 5px 15px; border-radius: 4px; font-size: 0.9em;">
141
+ {bottom_edge}
142
+ </div>
143
+ <div style="position: absolute; top: 50%; left: 40px; transform: translateY(-50%) rotate(-90deg); background: rgba(0,0,0,0.8); color: white; padding: 5px 15px; border-radius: 4px; font-size: 0.9em;">
144
+ {left_edge}
145
+ </div>
146
+ <div style="position: absolute; top: 50%; right: 40px; transform: translateY(-50%) rotate(90deg); background: rgba(0,0,0,0.8); color: white; padding: 5px 15px; border-radius: 4px; font-size: 0.9em;">
147
+ {right_edge}
148
+ </div>
149
+
150
+ <!-- ์—ฐ๊ฒฐ์„  -->
151
+ <svg style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;">
152
+ <!-- ์ƒ๋‹จ -->
153
+ <line x1="80" y1="40" x2="520" y2="40" stroke="#666" stroke-width="2" stroke-dasharray="5,5"/>
154
+ <!-- ํ•˜๋‹จ -->
155
+ <line x1="80" y1="260" x2="520" y2="260" stroke="#666" stroke-width="2" stroke-dasharray="5,5"/>
156
+ <!-- ์™ผ์ชฝ -->
157
+ <line x1="50" y1="70" x2="50" y2="230" stroke="#666" stroke-width="2" stroke-dasharray="5,5"/>
158
+ <!-- ์˜ค๋ฅธ์ชฝ -->
159
+ <line x1="550" y1="70" x2="550" y2="230" stroke="#666" stroke-width="2" stroke-dasharray="5,5"/>
160
+ </svg>
161
  </div>
162
+
163
+ <div style="margin-top: 20px; padding: 20px; background: #ecf0f1; border-radius: 8px;">
164
+ <p style="margin: 5px 0;"><strong>์ƒ๋‹จ ๊ตฌ๋ฌธ:</strong> {square.get('top_phrase', 'N/A')}</p>
165
+ <p style="margin: 5px 0;"><strong>ํ•˜๋‹จ ๊ตฌ๋ฌธ:</strong> {square.get('bottom_phrase', 'N/A')}</p>
166
+ <p style="margin: 15px 0 5px 0; font-style: italic; color: #7f8c8d;">๐Ÿ’ก {square.get('explanation', '')}</p>
167
  </div>
168
  </div>
169
  """
170
 
171
+ def generate_squares(user_input: str, mode: str) -> Tuple[str, str]:
172
+ """Square Theory ์ƒ์„ฑ ๋ฐ ํ‘œ์‹œ"""
 
 
 
 
 
 
173
 
174
  try:
175
+ squares = call_square_theory_llm(user_input, mode)
176
+
177
+ if not squares:
178
+ return "Square๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.", ""
179
 
180
+ # ๋งˆํฌ๋‹ค์šด ๊ฒฐ๊ณผ
181
  markdown_parts = [
182
+ f"# ๐ŸŸฆ Square Theory ๊ฒฐ๊ณผ",
183
+ f"**์ž…๋ ฅ**: {user_input} ({mode} ๋ชจ๋“œ)\n"
 
184
  ]
185
 
186
+ # HTML ์‹œ๊ฐํ™”
187
+ html_parts = []
188
 
189
+ for idx, square in enumerate(squares, 1):
190
+ score = square.get('cleverness_score', 0)
 
191
 
192
  markdown_parts.append(f"""
193
+ ### {idx}. {square.get('top_phrase', '?')} / {square.get('bottom_phrase', '?')} {'โญ' * min(score, 5)}
194
 
195
+ **๊ตฌ์กฐ**:
 
 
 
 
 
 
 
 
 
196
  ```
197
+ [{square.get('tl', '?')}] โ”€({square.get('top_edge', '?')})โ”€ [{square.get('tr', '?')}]
198
+ โ”‚ โ”‚
199
+ ({square.get('left_edge', '?')}) ({square.get('right_edge', '?')})
200
+ โ”‚ โ”‚
201
+ [{square.get('bl', '?')}] โ”€({square.get('bottom_edge', '?')})โ”€ [{square.get('br', '?')}]
202
  ```
203
 
204
+ **์„ค๋ช…**: {square.get('explanation', 'N/A')}
205
+
206
  ---
207
  """)
208
 
209
+ # ์ƒ์œ„ 5๊ฐœ ์‹œ๊ฐํ™”
210
+ if idx <= 5:
211
+ html_parts.append(visualize_square(square))
212
 
213
+ return "\n".join(markdown_parts), "\n".join(html_parts)
214
 
215
  except Exception as e:
216
+ return f"โŒ ์˜ค๋ฅ˜: {str(e)}", ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
 
218
+ # Gradio UI
219
+ with gr.Blocks(title="Square Theory Generator", theme=gr.themes.Soft()) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  gr.Markdown("""
221
+ # ๐ŸŸฆ Square Theory Generator
222
+ ### ์˜๋ฏธ์  ๊ด€๊ณ„๋กœ ์—ฐ๊ฒฐ๋œ 4๋‹จ์–ด ์‚ฌ๊ฐํ˜• ๊ตฌ์กฐ ์ƒ์„ฑ๊ธฐ
223
+
224
+ [Square Theory](https://www.lesswrong.com/posts/square-theory)๋Š” 4๊ฐœ์˜ ๋‹จ์–ด๊ฐ€ ์˜๋ฏธ์  ๊ด€๊ณ„๋กœ
225
+ ์—ฐ๊ฒฐ๋˜์–ด ์‚ฌ๊ฐํ˜•์„ ์ด๋ฃจ๋Š” ์–ธ์–ด ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.
226
 
227
+ **์˜ˆ์‹œ**: PUB QUIZ / BAR EXAM
228
+ - PUB โ†” BAR (๋™์˜์–ด)
229
+ - QUIZ โ†” EXAM (๋™์˜์–ด)
230
+ - PUB QUIZ (๊ตฌ๋ฌธ)
231
+ - BAR EXAM (๊ตฌ๋ฌธ)
232
  """)
233
 
234
  with gr.Row():
235
  with gr.Column(scale=2):
236
+ input_text = gr.Textbox(
237
+ label="์ž…๋ ฅ",
238
+ placeholder="๋‹จ์–ด, ๊ฐœ๋…, ๋˜๋Š” ์˜ˆ์‹œ ์ž…๋ ฅ...",
239
+ lines=1
240
  )
241
 
242
+ mode_radio = gr.Radio(
243
+ choices=[
244
+ ("๋‹จ์–ด ํฌํ•จ", "word"),
245
+ ("์ฃผ์ œ ๊ด€๋ จ", "concept"),
246
+ ("์œ ์‚ฌ ํŒจํ„ด", "pair")
247
+ ],
248
+ label="์ƒ์„ฑ ๋ชจ๋“œ",
249
+ value="word"
250
+ )
251
 
252
+ generate_btn = gr.Button("๐Ÿ”ฒ Square ์ƒ์„ฑ", variant="primary")
253
 
 
 
 
 
 
 
254
  with gr.Column(scale=1):
255
  gr.Markdown("""
256
+ ### ๐Ÿ’ก ์‚ฌ์šฉ๋ฒ•
257
 
258
+ **๋‹จ์–ด ํฌํ•จ**: ํŠน์ • ๋‹จ์–ด๋ฅผ ํฌํ•จํ•˜๋Š” Square
259
+ - ์˜ˆ: "BLACK" โ†’ JET BLACK / JETBLUE
 
 
260
 
261
+ **์ฃผ์ œ ๊ด€๋ จ**: ํŠน์ • ์ฃผ์ œ์˜ Square
262
+ - ์˜ˆ: "์Œ์‹" โ†’ ๊ด€๋ จ Square๋“ค
263
+
264
+ **์œ ์‚ฌ ํŒจํ„ด**: ์˜ˆ์‹œ์™€ ๋น„์Šทํ•œ Square
265
+ - ์˜ˆ: "PUB QUIZ / BAR EXAM"
 
266
  """)
267
 
268
  with gr.Tabs():
269
+ with gr.Tab("๐Ÿ“ ๊ฒฐ๊ณผ"):
270
  output_markdown = gr.Markdown()
271
 
272
+ with gr.Tab("๐ŸŽจ ์‹œ๊ฐํ™”"):
273
+ output_visual = gr.HTML()
 
 
 
 
 
 
 
 
 
 
274
 
275
+ # ์˜ˆ์‹œ
276
+ gr.Examples(
277
+ examples=[
278
+ ["BLACK", "word"],
279
+ ["์Œ์‹", "concept"],
280
+ ["PUB QUIZ / BAR EXAM", "pair"],
281
+ ["CALL", "word"],
282
+ ["๋ธŒ๋žœ๋“œ", "concept"]
283
+ ],
284
+ inputs=[input_text, mode_radio]
285
+ )
 
 
 
 
 
 
286
 
287
+ # ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
288
  generate_btn.click(
289
+ fn=generate_squares,
290
+ inputs=[input_text, mode_radio],
291
+ outputs=[output_markdown, output_visual]
292
  )
293
 
294
  gr.Markdown("""
295
  ---
296
+ ### ๐Ÿ“š Square Theory๋ž€?
297
+
298
+ Square Theory๋Š” ์–ธ์–ด์˜ ์˜๋ฏธ์  ๊ด€๊ณ„๋ฅผ ์‚ฌ๊ฐํ˜• ๊ตฌ์กฐ๋กœ ํ‘œํ˜„ํ•˜๋Š” ๊ฐœ๋…์ž…๋‹ˆ๋‹ค.
299
+ ์ข‹์€ Square๋Š” ๋‹ค์Œ ํŠน์ง•์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค:
300
+
301
+ 1. **์™„์„ฑ๋„**: 4๊ฐœ ๋ณ€์ด ๋ชจ๋‘ ์˜๋ฏธ์žˆ๋Š” ๊ด€๊ณ„
302
+ 2. **๋†€๋ผ์›€**: ๋Œ€๋ณ€ ์—ฐ๊ฒฐ์ด ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ํ†ต์ฐฐ ์ œ๊ณต
303
+ 3. **๊ท ํ˜•**: ๋ชจ๋“  ๊ด€๊ณ„๊ฐ€ ๋น„์Šทํ•œ ๊ฐ•๋„
304
 
305
+ ์ด๋Š” ๋ง์žฅ๋‚œ, ๋ธŒ๋žœ๋“œ ๋„ค์ด๋ฐ, ํฌ๋กœ์Šค์›Œ๋“œ ํผ์ฆ ๋“ฑ์—์„œ ์ž์ฃผ ๋ฐœ๊ฒฌ๋ฉ๋‹ˆ๋‹ค.
 
 
 
 
306
  """)
307
 
308
  if __name__ == "__main__":