openfree commited on
Commit
e420f43
ยท
verified ยท
1 Parent(s): dd53e8d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +125 -74
app.py CHANGED
@@ -1,20 +1,19 @@
1
  """
2
- Square Theory Generator (len-flexible)
3
- =====================================
4
- 2025โ€‘05โ€‘28 v6 โ— LLM ๊ฒฐ๊ณผ๊ฐ€ 10๊ฐœ ๋ฏธ๋งŒ์ผ ๋•Œ ์œ ์—ฐ ์ฒ˜๋ฆฌ
5
  ---------------------------------------------------
6
  ๋ณ€๊ฒฝ ์š”์•ฝ
7
  ---------
8
- * **๋ฌธ์ œ**: LLM์ด 10๊ฐœ ๋ฏธ๋งŒ์˜ ์ œ์•ˆ์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๊ธธ์ด ๊ฒ€์ฆ ์˜ˆ์™ธ ๋ฐœ์ƒ.
9
- * **ํ•ด๊ฒฐ**:
10
- 1. `call_llm()`์—์„œ JSON์ด dict ๋‹จ์ผ ๊ฐ์ฒด or 1~9๊ฐœ ๋ฆฌ์ŠคํŠธ์—ฌ๋„ ํ—ˆ์šฉ.
11
- 2. ๋ฆฌ์ŠคํŠธ๊ฐ€ 1๊ฐœ ๋ฏธ๋งŒ์ด๋ฉด ์˜ค๋ฅ˜, 2~9๊ฐœ๋Š” ๊ฒฝ๊ณ ๋งŒ ํ‘œ๊ธฐ.
12
- 3. Markdown์— ์‹ค์ œ ๊ฐœ์ˆ˜๋กœ ํ—ค๋” ์ถœ๋ ฅ.
13
 
14
  ์‹คํ–‰๋ฒ•
15
  ------
16
  ```bash
17
- pip install --upgrade gradio matplotlib openai requests
18
  export OPENAI_API_KEY="sk-..."
19
  python square_theory_gradio.py
20
  ```
@@ -22,44 +21,11 @@ python square_theory_gradio.py
22
 
23
  import os
24
  import json
25
- import tempfile
26
- import urllib.request
27
  import gradio as gr
28
- import matplotlib.pyplot as plt
29
- from matplotlib import patches, font_manager, rcParams
30
  from openai import OpenAI
31
 
32
  # -------------------------------------------------
33
- # 0. ํ•œ๊ธ€ ํฐํŠธ ์„ค์ • (๋‹ค์šด๋กœ๋“œ fallback ํฌํ•จ)
34
- # -------------------------------------------------
35
- PREFERRED_FONTS = ["Malgun Gothic", "NanumGothic", "AppleGothic", "DejaVu Sans"]
36
- NANUM_URL = (
37
- "https://github.com/google/fonts/raw/main/ofl/nanumgothic/"
38
- "NanumGothic-Regular.ttf"
39
- )
40
-
41
- def _set_korean_font():
42
- available = {f.name for f in font_manager.fontManager.ttflist}
43
- for cand in PREFERRED_FONTS:
44
- if cand in available:
45
- rcParams["font.family"] = cand
46
- break
47
- else:
48
- try:
49
- tmp_dir = tempfile.gettempdir()
50
- font_path = os.path.join(tmp_dir, "NanumGothic-Regular.ttf")
51
- if not os.path.exists(font_path):
52
- urllib.request.urlretrieve(NANUM_URL, font_path)
53
- font_manager.fontManager.addfont(font_path)
54
- rcParams["font.family"] = font_manager.FontProperties(fname=font_path).get_name()
55
- except Exception as e:
56
- print("[WARN] Font download failed, Korean text may break:", e)
57
- rcParams["axes.unicode_minus"] = False
58
-
59
- _set_korean_font()
60
-
61
- # -------------------------------------------------
62
- # 1. OpenAI ํด๋ผ์ด์–ธํŠธ
63
  # -------------------------------------------------
64
  if not os.getenv("OPENAI_API_KEY"):
65
  raise EnvironmentError("OPENAI_API_KEY ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
@@ -67,32 +33,120 @@ if not os.getenv("OPENAI_API_KEY"):
67
  client = OpenAI()
68
 
69
  # -------------------------------------------------
70
- # 2. Square Diagram
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  # -------------------------------------------------
 
 
72
 
73
- def draw_square(words):
74
- fig, ax = plt.subplots(figsize=(4, 4))
75
- ax.add_patch(patches.Rectangle((0, 0), 1, 1, fill=False, linewidth=2))
76
- ax.text(-0.05, 1.05, str(words.get("tl", "")), ha="right", va="bottom", fontsize=14, fontweight="bold")
77
- ax.text(1.05, 1.05, str(words.get("tr", "")), ha="left", va="bottom", fontsize=14, fontweight="bold")
78
- ax.text(1.05, -0.05, str(words.get("br", "")), ha="left", va="top", fontsize=14, fontweight="bold")
79
- ax.text(-0.05, -0.05, str(words.get("bl", "")), ha="right", va="top", fontsize=14, fontweight="bold")
80
- ax.set_xticks([])
81
- ax.set_yticks([])
82
- ax.set_xlim(-0.2, 1.2)
83
- ax.set_ylim(-0.2, 1.2)
84
- ax.set_aspect("equal")
85
- return fig
86
 
87
  # -------------------------------------------------
88
- # 3. LLM Prompt & Call
89
  # -------------------------------------------------
90
  SYSTEM_PROMPT = (
91
  "๋„ˆ๋Š” ํ•œ๊ตญ์–ด ์นดํ”ผยท๋ธŒ๋žœ๋“œ ๋„ค์ด๋ฐ ์ „๋ฌธ๊ฐ€์ด์ž Square Theory ๋„์šฐ๋ฏธ๋‹ค. "
92
- "์‚ฌ์šฉ์ž๊ฐ€ ์ค€ ํ•˜๋‚˜์˜ ๋‹จ์–ด(tl)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ’ˆ์งˆ์ด ๋›ฐ์–ด๋‚œ ์ˆœ์„œ๋Œ€๋กœ ์ตœ๋Œ€ 10๊ฐœ์˜ ์ œ์•ˆ์„ JSON ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•ด๋ผ. "
93
- "๊ฐ ๋ฐฐ์—ด ์›์†Œ๋Š” tl, tr, br, bl, top_phrase, bottom_phrase, slogan, brand ํ•„๋“œ๋ฅผ ๊ฐ€์ง„๋‹ค. "
94
- "์‚ฌ๊ฐํ˜• ๋„ค ๊ผญ์ง“์ (tlโ†’trโ†’brโ†’bl)์ด ์ž์—ฐ์Šค๋ ˆ ์—ฐ๊ฒฐ๋ผ์•ผ ํ•˜๊ณ , ๋ฐฐ์—ด ์ฒซ ์›์†Œ๊ฐ€ ์ตœ์šฐ์ˆ˜ ์ œ์•ˆ์ด๋‹ค. "
95
- "๊ฒฐ๊ณผ๋Š” JSON ์™ธ ํ…์ŠคํŠธ๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค."
96
  )
97
 
98
 
@@ -113,30 +167,28 @@ def call_llm(seed: str):
113
  {"role": "user", "content": seed},
114
  ],
115
  temperature=0.9,
116
- max_tokens=1024,
117
  )
118
  raw = resp.choices[0].message.content
119
  cleaned = clean_json_block(raw)
120
  try:
121
  data = json.loads(cleaned)
122
- # ๋‹จ์ผ ๊ฐ์ฒด๋ฉด ๋ฆฌ์ŠคํŠธ๋กœ ๋ณ€ํ™˜
123
  if isinstance(data, dict):
124
  data = [data]
125
  if not isinstance(data, list):
126
  raise TypeError("LLM ์‘๋‹ต์ด ๋ฆฌ์ŠคํŠธ๊ฐ€ ์•„๋‹˜")
127
- if len(data) == 0:
128
- raise ValueError("LLM์ด ๋นˆ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜")
129
  except Exception as exc:
130
  raise ValueError(f"LLM JSON ํŒŒ์‹ฑ ์‹คํŒจ: {exc}\n์›๋ฌธ ์ผ๋ถ€: {cleaned[:300]} โ€ฆ")
131
  return data
132
 
133
  # -------------------------------------------------
134
- # 4. Gradio callback
135
  # -------------------------------------------------
136
 
137
  def generate(seed_word: str):
138
  results = call_llm(seed_word)
139
- fig = draw_square({k: results[0][k] for k in ("tl", "tr", "br", "bl")})
140
  md_lines = [f"## ์ด {len(results)}๊ฐœ ์ œ์•ˆ\n"]
141
  for idx, item in enumerate(results, 1):
142
  md_lines.append(
@@ -145,19 +197,18 @@ def generate(seed_word: str):
145
  f"- **๋ธŒ๋žœ๋“œ ๋„ค์ž„**: {item['brand']}\n"
146
  f"- (tl={item['tl']}, tr={item['tr']}, br={item['br']}, bl={item['bl']})\n"
147
  )
148
- return fig, "\n".join(md_lines)
149
 
150
  # -------------------------------------------------
151
- # 5. UI
152
  # -------------------------------------------------
153
- with gr.Blocks(title="Square Theory โ€“ ์ตœ๊ณ  ์ œ์•ˆ ๐Ÿ‡ฐ๐Ÿ‡ท") as demo:
154
- gr.Markdown("""# ๐ŸŸง Square Theory ์ œ์•ˆ (์ตœ๋Œ€ 10๊ฐœ)\n๋‹จ์–ด 1๊ฐœ ์ž…๋ ฅ โ†’ LLM์ด ํ‰๊ฐ€ยท์ •๋ ฌํ•œ ์‚ฌ๊ฐํ˜•/์นดํ”ผ/๋ธŒ๋žœ๋“œ ๋„ค์ž„""")
155
  seed = gr.Textbox(label="์‹œ๋“œ ๋‹จ์–ด(TL)", placeholder="์˜ˆ: ๊ณจ๋“ ")
156
  run = gr.Button("์ƒ์„ฑ")
157
- fig_out = gr.Plot(label="1์œ„ ์‚ฌ๊ฐํ˜•")
158
  md_out = gr.Markdown(label="์ œ์•ˆ ๋ชฉ๋ก")
159
 
160
- run.click(generate, inputs=seed, outputs=[fig_out, md_out])
161
 
162
  if __name__ == "__main__":
163
  demo.launch()
 
1
  """
2
+ Square Theory Generator (Markdownโ€‘only, up to 20 ideas)
3
+ =====================================================
4
+ 2025โ€‘05โ€‘28ย v7 โ— ๋ฐ•์Šค ๋‹ค์ด์–ด๊ทธ๋žจ ์ œ๊ฑฐ & ์ œ์•ˆย 20๊ฐœ๋กœ ํ™•์žฅ
5
  ---------------------------------------------------
6
  ๋ณ€๊ฒฝ ์š”์•ฝ
7
  ---------
8
+ 1. **์‚ฌ๊ฐํ˜• ์‹œ๊ฐํ™” ์ œ๊ฑฐ** โ€’ ์ถœ๋ ฅ/์˜์กด์—์„œ `matplotlib` ์ „๋ถ€ ์‚ญ์ œ.
9
+ 2. **์ œ์•ˆ ๊ฐœ์ˆ˜ ์ƒํ–ฅ** โ€’ LLM์ด ์ตœ๋Œ€ **20๊ฐœ** ์•„์ด๋””์–ด ๋ฐ˜ํ™˜.
10
+ 3. **์œ ์—ฐ ๊ธธ์ด ํ—ˆ์šฉ** โ€’ 1โ€ฏโ‰คโ€ฏNโ€ฏโ‰คโ€ฏ20 ๋ฆฌ์ŠคํŠธ๋ฉด OK.
11
+ 4. **UI ๋‹จ์ˆœํ™”** โ€’ ์‹œ๋“œ ์ž…๋ ฅ โ†’ ๋ฒ„ํŠผ โ†’ ๋งˆํฌ๋‹ค์šด ๊ฒฐ๊ณผ.
 
12
 
13
  ์‹คํ–‰๋ฒ•
14
  ------
15
  ```bash
16
+ pip install --upgrade gradio openai
17
  export OPENAI_API_KEY="sk-..."
18
  python square_theory_gradio.py
19
  ```
 
21
 
22
  import os
23
  import json
 
 
24
  import gradio as gr
 
 
25
  from openai import OpenAI
26
 
27
  # -------------------------------------------------
28
+ # 0. OpenAI ํด๋ผ์ด์–ธํŠธ
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  # -------------------------------------------------
30
  if not os.getenv("OPENAI_API_KEY"):
31
  raise EnvironmentError("OPENAI_API_KEY ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
 
33
  client = OpenAI()
34
 
35
  # -------------------------------------------------
36
+ # 1. LLM Prompt & Utilities
37
+ # -------------------------------------------------
38
+ SYSTEM_PROMPT = (
39
+ "๋„ˆ๋Š” ํ•œ๊ตญ์–ด ์นดํ”ผยท๋ธŒ๋žœ๋“œ ๋„ค์ด๋ฐ ์ „๋ฌธ๊ฐ€์ด์ž Square Theory ๋„์šฐ๋ฏธ๋‹ค. "
40
+ "์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ํ•˜๋‚˜์˜ ๋‹จ์–ด(tl)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ’ˆ์งˆ์ด ๋›ฐ์–ด๋‚œ ์ˆœ์„œ๋Œ€๋กœ ์ตœ๋Œ€ 20๊ฐœ์˜ ์ œ์•ˆ์„ JSON ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•ด๋ผ. "
41
+ "๊ฐ ์›์†Œ๋Š” tl, tr, br, bl, top_phrase, bottom_phrase, slogan, brand ํ•„๋“œ๋ฅผ ๊ฐ€์ง„๋‹ค. "
42
+ "๋ฐฐ์—ด ์ฒซ ๋ฒˆ์งธ ์›์†Œ๊ฐ€ ๊ฐ€์žฅ ์šฐ์ˆ˜ํ•ด์•ผ ํ•œ๋‹ค. JSON ์™ธ ๋ฌธ์ž๋Š” ๊ธˆ์ง€ํ•œ๋‹ค."
43
+ )
44
+
45
+
46
+ def clean_json_block(text: str) -> str:
47
+ text = text.strip()
48
+ if text.startswith("```"):
49
+ text = text.split("\n", 1)[1] if "\n" in text else text[3:]
50
+ if text.endswith("```"):
51
+ text = text[:-3]
52
+ return text.strip()
53
+
54
+
55
+ def call_llm(seed: str):
56
+ resp = client.chat.completions.create(
57
+ model="gpt-4o-mini",
58
+ messages=[
59
+ {"role": "system", "content": SYSTEM_PROMPT},
60
+ {"role": "user", "content": seed},
61
+ ],
62
+ temperature=0.9,
63
+ max_tokens=2048,
64
+ )
65
+ raw = resp.choices[0].message.content
66
+ cleaned = clean_json_block(raw)
67
+ try:
68
+ data = json.loads(cleaned)
69
+ if isinstance(data, dict):
70
+ data = [data]
71
+ if not isinstance(data, list):
72
+ raise TypeError("LLM ์‘๋‹ต์ด ๋ฆฌ์ŠคํŠธ๊ฐ€ ์•„๋‹˜")
73
+ if not 1 <= len(data) <= 20:
74
+ raise ValueError("์‘๋‹ต ๋ฆฌ์ŠคํŠธ ๊ธธ์ด๊ฐ€ 1~20 ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚จ")
75
+ except Exception as exc:
76
+ raise ValueError(f"LLM JSON ํŒŒ์‹ฑ ์‹คํŒจ: {exc}\n์›๋ฌธ ์ผ๋ถ€: {cleaned[:300]} โ€ฆ")
77
+ return data
78
+
79
+ # -------------------------------------------------
80
+ # 2. Gradio callback
81
+ # -------------------------------------------------
82
+
83
+ def generate(seed_word: str):
84
+ results = call_llm(seed_word)
85
+ md_lines = [f"## ์ด {len(results)}๊ฐœ ์ œ์•ˆ\n"]
86
+ for idx, item in enumerate(results, 1):
87
+ md_lines.append(
88
+ f"### {idx}. {item['top_phrase']} / {item['bottom_phrase']}\n"
89
+ f"- **์Šฌ๋กœ๊ฑด**: {item['slogan']}\n"
90
+ f"- **๋ธŒ๋žœ๋“œ ๋„ค์ž„**: {item['brand']}\n"
91
+ f"- (tl={item['tl']}, tr={item['tr']}, br={item['br']}, bl={item['bl']})\n"
92
+ )
93
+ return "\n".join(md_lines)
94
+
95
+ # -------------------------------------------------
96
+ # 3. UI
97
+ # -------------------------------------------------
98
+ with gr.Blocks(title="Square Theory โ€“ ์ตœ๋Œ€ 20๊ฐœ ๐Ÿ‡ฐ๐Ÿ‡ท") as demo:
99
+ gr.Markdown("""# ๐ŸŸง Square Theory ์ œ์•ˆ (์ตœ๋Œ€ 20๊ฐœ)\n๋‹จ์–ด ํ•˜๋‚˜ ์ž…๋ ฅ โ†’ LLM์ด ์ ์ˆ˜ํ™”ยท์ •๋ ฌํ•œ ์‚ฌ๊ฐํ˜•/์นดํ”ผ/๋ธŒ๋žœ๋“œ ๋„ค์ž„""")
100
+ seed = gr.Textbox(label="์‹œ๋“œ ๋‹จ์–ด(TL)", placeholder="์˜ˆ: ๊ณจ๋“ ")
101
+ run = gr.Button("์ƒ์„ฑ")
102
+ md_out = gr.Markdown(label="์ œ์•ˆ ๋ชฉ๋ก")
103
+
104
+ run.click(generate, inputs=seed, outputs=md_out)
105
+
106
+ if __name__ == "__main__":
107
+ demo.launch()
108
+ """
109
+ Square Theory Generator (Markdownโ€‘only, up to 20 ideas)
110
+ =====================================================
111
+ 2025โ€‘05โ€‘28ย v7 โ— ๋ฐ•์Šค ๋‹ค์ด์–ด๊ทธ๋žจ ์ œ๊ฑฐ & ์ œ์•ˆย 20๊ฐœ๋กœ ํ™•์žฅ
112
+ ---------------------------------------------------
113
+ ๋ณ€๊ฒฝ ์š”์•ฝ
114
+ ---------
115
+ 1. **์‚ฌ๊ฐํ˜• ์‹œ๊ฐํ™” ์ œ๊ฑฐ** โ€’ ์ถœ๋ ฅ/์˜์กด์—์„œ `matplotlib` ์ „๋ถ€ ์‚ญ์ œ.
116
+ 2. **์ œ์•ˆ ๊ฐœ์ˆ˜ ์ƒํ–ฅ** โ€’ LLM์ด ์ตœ๋Œ€ **20๊ฐœ** ์•„์ด๋””์–ด ๋ฐ˜ํ™˜.
117
+ 3. **์œ ์—ฐ ๊ธธ์ด ํ—ˆ์šฉ** โ€’ 1โ€ฏโ‰คโ€ฏNโ€ฏโ‰คโ€ฏ20 ๋ฆฌ์ŠคํŠธ๋ฉด OK.
118
+ 4. **UI ๋‹จ์ˆœํ™”** โ€’ ์‹œ๋“œ ์ž…๋ ฅ โ†’ ๋ฒ„ํŠผ โ†’ ๋งˆํฌ๋‹ค์šด ๊ฒฐ๊ณผ.
119
+
120
+ ์‹คํ–‰๋ฒ•
121
+ ------
122
+ ```bash
123
+ pip install --upgrade gradio openai
124
+ export OPENAI_API_KEY="sk-..."
125
+ python square_theory_gradio.py
126
+ ```
127
+ """
128
+
129
+ import os
130
+ import json
131
+ import gradio as gr
132
+ from openai import OpenAI
133
+
134
+ # -------------------------------------------------
135
+ # 0. OpenAI ํด๋ผ์ด์–ธํŠธ
136
  # -------------------------------------------------
137
+ if not os.getenv("OPENAI_API_KEY"):
138
+ raise EnvironmentError("OPENAI_API_KEY ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
139
 
140
+ client = OpenAI()
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
  # -------------------------------------------------
143
+ # 1. LLM Prompt & Utilities
144
  # -------------------------------------------------
145
  SYSTEM_PROMPT = (
146
  "๋„ˆ๋Š” ํ•œ๊ตญ์–ด ์นดํ”ผยท๋ธŒ๋žœ๋“œ ๋„ค์ด๋ฐ ์ „๋ฌธ๊ฐ€์ด์ž Square Theory ๋„์šฐ๋ฏธ๋‹ค. "
147
+ "์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ํ•˜๋‚˜์˜ ๋‹จ์–ด(tl)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ’ˆ์งˆ์ด ๋›ฐ์–ด๋‚œ ์ˆœ์„œ๋Œ€๋กœ ์ตœ๋Œ€ 20๊ฐœ์˜ ์ œ์•ˆ์„ JSON ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•ด๋ผ. "
148
+ "๊ฐ ์›์†Œ๋Š” tl, tr, br, bl, top_phrase, bottom_phrase, slogan, brand ํ•„๋“œ๋ฅผ ๊ฐ€์ง„๋‹ค. "
149
+ "๋ฐฐ์—ด ์ฒซ ๋ฒˆ์งธ ์›์†Œ๊ฐ€ ๊ฐ€์žฅ ์šฐ์ˆ˜ํ•ด์•ผ ํ•œ๋‹ค. JSON ์™ธ ๋ฌธ์ž๋Š” ๊ธˆ์ง€ํ•œ๋‹ค."
 
150
  )
151
 
152
 
 
167
  {"role": "user", "content": seed},
168
  ],
169
  temperature=0.9,
170
+ max_tokens=2048,
171
  )
172
  raw = resp.choices[0].message.content
173
  cleaned = clean_json_block(raw)
174
  try:
175
  data = json.loads(cleaned)
 
176
  if isinstance(data, dict):
177
  data = [data]
178
  if not isinstance(data, list):
179
  raise TypeError("LLM ์‘๋‹ต์ด ๋ฆฌ์ŠคํŠธ๊ฐ€ ์•„๋‹˜")
180
+ if not 1 <= len(data) <= 20:
181
+ raise ValueError("์‘๋‹ต ๋ฆฌ์ŠคํŠธ ๊ธธ์ด๊ฐ€ 1~20 ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚จ")
182
  except Exception as exc:
183
  raise ValueError(f"LLM JSON ํŒŒ์‹ฑ ์‹คํŒจ: {exc}\n์›๋ฌธ ์ผ๋ถ€: {cleaned[:300]} โ€ฆ")
184
  return data
185
 
186
  # -------------------------------------------------
187
+ # 2. Gradio callback
188
  # -------------------------------------------------
189
 
190
  def generate(seed_word: str):
191
  results = call_llm(seed_word)
 
192
  md_lines = [f"## ์ด {len(results)}๊ฐœ ์ œ์•ˆ\n"]
193
  for idx, item in enumerate(results, 1):
194
  md_lines.append(
 
197
  f"- **๋ธŒ๋žœ๋“œ ๋„ค์ž„**: {item['brand']}\n"
198
  f"- (tl={item['tl']}, tr={item['tr']}, br={item['br']}, bl={item['bl']})\n"
199
  )
200
+ return "\n".join(md_lines)
201
 
202
  # -------------------------------------------------
203
+ # 3. UI
204
  # -------------------------------------------------
205
+ with gr.Blocks(title="Square Theory โ€“ ์ตœ๋Œ€ 20๊ฐœ ๐Ÿ‡ฐ๐Ÿ‡ท") as demo:
206
+ gr.Markdown("""# ๐ŸŸง Square Theory ์ œ์•ˆ (์ตœ๋Œ€ 20๊ฐœ)\n๋‹จ์–ด ํ•˜๋‚˜ ์ž…๋ ฅ โ†’ LLM์ด ์ ์ˆ˜ํ™”ยท์ •๋ ฌํ•œ ์‚ฌ๊ฐํ˜•/์นดํ”ผ/๋ธŒ๋žœ๋“œ ๋„ค์ž„""")
207
  seed = gr.Textbox(label="์‹œ๋“œ ๋‹จ์–ด(TL)", placeholder="์˜ˆ: ๊ณจ๋“ ")
208
  run = gr.Button("์ƒ์„ฑ")
 
209
  md_out = gr.Markdown(label="์ œ์•ˆ ๋ชฉ๋ก")
210
 
211
+ run.click(generate, inputs=seed, outputs=md_out)
212
 
213
  if __name__ == "__main__":
214
  demo.launch()