openfree commited on
Commit
f065a36
ยท
verified ยท
1 Parent(s): 981fa48

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +46 -61
app.py CHANGED
@@ -1,13 +1,20 @@
1
  """
2
- Square Theory Generator (Koreanโ€‘friendly, LLMโ€‘powered)
3
  -----------------------------------------------------
4
- ๋ณ€๊ฒฝ ์‚ฌํ•ญ
5
- 0) Matplotlib ํ•œ๊ธ€ ํฐํŠธ ์ž๋™ ์„ค์ •์œผ๋กœ ๊นจ์ง ํ•ด๊ฒฐ
6
- 1) ์ž…๋ ฅ ํ”„๋กฌํ”„ํŠธ๋ฅผ ํ•˜๋‚˜๋งŒ ๋‘๊ณ , ๋‚˜๋จธ์ง€ 3๋‹จ์–ด๋Š” LLM์ด ์ƒ์„ฑ
7
- 2) OpenAI ๊ณต์‹ Python SDK (`openai`) ์‚ฌ์šฉ โ€“ ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ `client.responses.create` ์˜ˆ์‹œ ๋ฐ˜์˜
8
- 3) LLM์ด ์‚ฌ๊ฐํ˜• ๋‹จ์–ด + ๋‘ ์ค„ ์นดํ”ผยท๋ธŒ๋žœ๋“œ ๋„ค์ž„๊นŒ์ง€ JSON์œผ๋กœ ๋ฐ˜ํ™˜ โ†’ ์ž๋™ ์ถœ๋ ฅ
 
 
 
 
 
 
 
 
9
 
10
- โš ๏ธ **OPENAI_API_KEY** ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋ฏธ๋ฆฌ ์„ค์ •ํ•ด ๋‘์„ธ์š”.
11
  """
12
 
13
  import os
@@ -18,30 +25,24 @@ from matplotlib import patches, font_manager, rcParams
18
  from openai import OpenAI
19
 
20
  # -------------------------------------------------
21
- # 0. ํ•œ๊ธ€ ํฐํŠธ ์ž๋™ ํƒ์ƒ‰ & ์„ค์ • (Malgun, Nanum ๋“ฑ)
22
  # -------------------------------------------------
23
 
24
  def _set_korean_font():
25
- candidates = [
26
- "Malgun Gothic", # Windows
27
- "NanumGothic", # Linux (apt install fonts-nanum)
28
- "AppleGothic", # macOS
29
- "DejaVu Sans", # fallback (๊ธฐ๋ณธ ํฐํŠธ์ง€๋งŒ ํ•œ๊ธ€ ์ผ๋ถ€ ์ง€์›)
30
- ]
31
  available = {f.name for f in font_manager.fontManager.ttflist}
32
  for cand in candidates:
33
  if cand in available:
34
  rcParams["font.family"] = cand
35
  break
36
- rcParams["axes.unicode_minus"] = False # ์Œ์ˆ˜ ๋ถ€ํ˜ธ ๊นจ์ง ๋ฐฉ์ง€
37
 
38
  _set_korean_font()
39
 
40
  # -------------------------------------------------
41
  # 1. OpenAI ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
42
  # -------------------------------------------------
43
- OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
44
- if not OPENAI_API_KEY:
45
  raise EnvironmentError("OPENAI_API_KEY ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
46
 
47
  client = OpenAI()
@@ -51,17 +52,12 @@ client = OpenAI()
51
  # -------------------------------------------------
52
 
53
  def draw_square(words):
54
- """words = dict with keys tl, tr, br, bl"""
55
  fig, ax = plt.subplots(figsize=(4, 4))
56
- # ์‚ฌ๊ฐํ˜• ํ”„๋ ˆ์ž„
57
- square = patches.Rectangle((0, 0), 1, 1, fill=False, linewidth=2)
58
- ax.add_patch(square)
59
-
60
  ax.text(-0.05, 1.05, words["tl"], ha="right", va="bottom", fontsize=14, fontweight="bold")
61
  ax.text(1.05, 1.05, words["tr"], ha="left", va="bottom", fontsize=14, fontweight="bold")
62
  ax.text(1.05, -0.05, words["br"], ha="left", va="top", fontsize=14, fontweight="bold")
63
  ax.text(-0.05, -0.05, words["bl"], ha="right", va="top", fontsize=14, fontweight="bold")
64
-
65
  ax.set_xticks([])
66
  ax.set_yticks([])
67
  ax.set_xlim(-0.2, 1.2)
@@ -72,27 +68,32 @@ def draw_square(words):
72
  # -------------------------------------------------
73
  # 3. LLM ํ”„๋กฌํ”„ํŠธ & ์‘๋‹ต ํŒŒ์‹ฑ
74
  # -------------------------------------------------
75
-
76
- SYSTEM_PROMPT = """๋„ˆ๋Š” ํ•œ๊ตญ์–ด ์นดํ”ผยท๋ธŒ๋žœ๋“œ ๋„ค์ด๋ฐ ์ „๋ฌธ๊ฐ€์ด์ž Square Theory(์‚ฌ๊ฐํ˜• ์ด๋ก ) ๋„์šฐ๋ฏธ๋‹ค.\n์‚ฌ์šฉ์ž๊ฐ€ ์ œ์‹œํ•œ ํ•˜๋‚˜์˜ ๋‹จ์–ด(TL)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ, ๋‹ค์Œ JSON ํ˜•์‹์œผ๋กœ ์‚ฌ๊ฐํ˜•์„ ์™„์„ฑํ•˜๋ผ.\n- "tl": ์ž…๋ ฅ ๋‹จ์–ด ๊ทธ๋Œ€๋กœ\n- "tr": TL๊ณผ ์˜๋ฏธยท์Œ์ด ๋Œ€์‘๋˜๋Š” ๋‹ค๋ฅธ ๋‹จ์–ด\n- "br": TR๊ณผ ์˜๋ฏธยท์Œ์ด ๋Œ€์‘๋˜๋Š” ๋‹ค๋ฅธ ๋‹จ์–ด\n- "bl": BR๊ณผ ์˜๋ฏธยท์Œ์ด ๋Œ€์‘๋˜๋Š” ๋‹ค๋ฅธ ๋‹จ์–ด๋กœ TL๊ณผ๋„ ์—ฐ๊ฒฐ์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค\n๋˜ํ•œ, "top_phrase"(tl+tr), "bottom_phrase"(bl+br) ๋‘ ํ‘œํ˜„์„ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ•œ๊ตญ์–ด ๊ด€์šฉ๊ตฌ๋กœ ์ œ์‹œํ•˜๊ณ ,\n"slogan"(๋‘ ์ค„ ์นดํ”ผ)์™€ "brand"(๋ธŒ๋žœ๋“œ ๋„ค์ž„ ํ›„๋ณด) ํ•„๋“œ๋ฅผ ํ•จ๊ป˜ ํฌํ•จํ•˜๋ผ.\n๊ฒฐ๊ณผ๋Š” ๋ฐ˜๋“œ์‹œ **ํ•œ๊ธ€ UTFโ€‘8**๋กœ ์ธ์ฝ”๋”ฉ๋œ JSON๋งŒ ๋ฐ˜ํ™˜ํ•œ๋‹ค."""
77
-
78
-
79
- def call_llm(seed):
80
- """Returns parsed dict from LLM JSON output."""
81
- response = client.responses.create(
82
- model="gpt-4.1-mini",
83
- input=[
84
- {"role": "system", "content": [{"type": "input_text", "text": SYSTEM_PROMPT}]},
85
- {"role": "user", "content": [{"type": "input_text", "text": seed}]},
 
 
 
 
86
  ],
87
- text={"format": {"type": "text"}},
88
  temperature=0.9,
89
- max_output_tokens=512,
90
  )
91
- raw = response.choices[0].message.content[0]["text"]
 
 
92
  try:
93
  data = json.loads(raw)
94
- except json.JSONDecodeError as e:
95
- raise ValueError(f"LLM ์‘๋‹ต์ด JSON ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค: {e}\n์›๋ณธ: {raw}")
96
  return data
97
 
98
  # -------------------------------------------------
@@ -101,39 +102,23 @@ def call_llm(seed):
101
 
102
  def generate(seed_word):
103
  data = call_llm(seed_word)
104
-
105
- words = {
106
- "tl": data["tl"],
107
- "tr": data["tr"],
108
- "br": data["br"],
109
- "bl": data["bl"],
110
- }
111
- fig = draw_square(words)
112
- return (
113
- fig,
114
- data.get("top_phrase", ""),
115
- data.get("bottom_phrase", ""),
116
- data.get("slogan", ""),
117
- data.get("brand", ""),
118
- )
119
 
120
  # -------------------------------------------------
121
  # 5. Gradio UI
122
  # -------------------------------------------------
123
  with gr.Blocks(title="Square Theory Generator โ€“ LLM Powered ๐Ÿ‡ฐ๐Ÿ‡ท") as demo:
124
- gr.Markdown("""# ๐ŸŸง Square Theory Generator\n๋‹จ์–ด ํ•˜๋‚˜๋งŒ ์ž…๋ ฅํ•˜๋ฉด, LLM์ด ์‚ฌ๊ฐํ˜• ๊ตฌ์กฐยท์นดํ”ผยท๋ธŒ๋žœ๋“œ ๋„ค์ž„์„ ์ž๋™์œผ๋กœ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค.""")
125
-
126
  seed = gr.Textbox(label="์‹œ๋“œ ๋‹จ์–ด(TL)", placeholder="์˜ˆ: ๊ณจ๋“ ")
127
- run_btn = gr.Button("์‚ฌ๊ฐํ˜• ์ƒ์„ฑ")
128
-
129
  fig_out = gr.Plot(label="์‚ฌ๊ฐํ˜• ๋‹ค์ด์–ด๊ทธ๋žจ")
130
  top_out = gr.Textbox(label="์œ—๋ณ€ ํ‘œํ˜„")
131
  bottom_out = gr.Textbox(label="์•„๋žซ๋ณ€ ํ‘œํ˜„")
132
  slogan_out = gr.Textbox(label="๋‘ ์ค„ ์Šฌ๋กœ๊ฑด")
133
- brand_out = gr.Textbox(label="๋ธŒ๋žœ๋“œ ๋„ค์ž„ ์ œ์•ˆ")
134
 
135
- run_btn.click(fn=generate, inputs=seed, outputs=[fig_out, top_out, bottom_out, slogan_out, brand_out])
136
 
137
  if __name__ == "__main__":
138
- # launch(): share=True ํ•˜๋ฉด ์™ธ๋ถ€์—์„œ ์ ‘์† ๊ฐ€๋Šฅ
139
  demo.launch()
 
1
  """
2
+ Square Theory Generator (Korean-friendly, LLM-powered)
3
  -----------------------------------------------------
4
+ 2025โ€‘05โ€‘28 Hotโ€‘patch โ˜… ์˜ค๋ฅ˜ ์ˆ˜์ •
5
+ ================================
6
+ * **๋ฒ„๊ทธ ์›์ธ**: `openai` Python SDK v1.x์˜ `client.responses.create()` ๋ฐ˜ํ™˜ ๊ฐ์ฒด์—๋Š” `.choices` ์†์„ฑ์ด ์—†์Œ โ†’ `AttributeError: 'Response' object has no attribute 'choices'` ๋ฐœ์ƒ.
7
+ * **ํ•ด๊ฒฐ**: `client.chat.completions.create()` ์—”๋“œํฌ์ธํŠธ๋กœ ๊ต์ฒด โ†’ ์ต์ˆ™ํ•œ `.choices[0].message.content` ๊ตฌ์กฐ ์‚ฌ์šฉ.
8
+ * ์ถ”๊ฐ€: ์˜ˆ์™ธ ์ฒ˜๋ฆฌยท๋””๋ฒ„๊ทธ ํ”„๋ฆฐํŠธ ์˜ต์…˜.
9
+
10
+ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•
11
+ =========
12
+ ```bash
13
+ pip install --upgrade gradio matplotlib openai
14
+ export OPENAI_API_KEY="sk-..."
15
+ python square_theory_gradio.py
16
+ ```
17
 
 
18
  """
19
 
20
  import os
 
25
  from openai import OpenAI
26
 
27
  # -------------------------------------------------
28
+ # 0. ํ•œ๊ธ€ ํฐํŠธ ์ž๋™ ํƒ์ƒ‰ & ์„ค์ •
29
  # -------------------------------------------------
30
 
31
  def _set_korean_font():
32
+ candidates = ["Malgun Gothic", "NanumGothic", "AppleGothic", "DejaVu Sans"]
 
 
 
 
 
33
  available = {f.name for f in font_manager.fontManager.ttflist}
34
  for cand in candidates:
35
  if cand in available:
36
  rcParams["font.family"] = cand
37
  break
38
+ rcParams["axes.unicode_minus"] = False
39
 
40
  _set_korean_font()
41
 
42
  # -------------------------------------------------
43
  # 1. OpenAI ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™”
44
  # -------------------------------------------------
45
+ if not os.getenv("OPENAI_API_KEY"):
 
46
  raise EnvironmentError("OPENAI_API_KEY ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
47
 
48
  client = OpenAI()
 
52
  # -------------------------------------------------
53
 
54
  def draw_square(words):
 
55
  fig, ax = plt.subplots(figsize=(4, 4))
56
+ ax.add_patch(patches.Rectangle((0, 0), 1, 1, fill=False, linewidth=2))
 
 
 
57
  ax.text(-0.05, 1.05, words["tl"], ha="right", va="bottom", fontsize=14, fontweight="bold")
58
  ax.text(1.05, 1.05, words["tr"], ha="left", va="bottom", fontsize=14, fontweight="bold")
59
  ax.text(1.05, -0.05, words["br"], ha="left", va="top", fontsize=14, fontweight="bold")
60
  ax.text(-0.05, -0.05, words["bl"], ha="right", va="top", fontsize=14, fontweight="bold")
 
61
  ax.set_xticks([])
62
  ax.set_yticks([])
63
  ax.set_xlim(-0.2, 1.2)
 
68
  # -------------------------------------------------
69
  # 3. LLM ํ”„๋กฌํ”„ํŠธ & ์‘๋‹ต ํŒŒ์‹ฑ
70
  # -------------------------------------------------
71
+ SYSTEM_PROMPT = (
72
+ "๋„ˆ๋Š” ํ•œ๊ตญ์–ด ์นดํ”ผยท๋ธŒ๋žœ๋“œ ๋„ค์ด๋ฐ ์ „๋ฌธ๊ฐ€์ด์ž Square Theory(์‚ฌ๊ฐํ˜• ์ด๋ก ) ๋„์šฐ๋ฏธ๋‹ค.\n"
73
+ "์‚ฌ์šฉ์ž๊ฐ€ ์ œ์‹œํ•œ ํ•˜๋‚˜์˜ ๋‹จ์–ด(TL)๋กœ ๋‹ค์Œ JSON์„ ์™„์„ฑํ•ด๋ผ: \n"
74
+ "{\n \"tl\": str, \"tr\": str, \"br\": str, \"bl\": str,\n \"top_phrase\": str, \"bottom_phrase\": str, \n \"slogan\": str, \"brand\": str\n}\n"
75
+ "๊ฐ corner ๋Š” ์™„์ „ํ•œ ์‚ฌ๊ฐํ˜•์„ ์ด๋ฃจ๋„๋ก ์˜๋ฏธยท์Œ์ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์—ฐ๊ฒฐ๋ผ์•ผ ํ•œ๋‹ค. ๊ฒฐ๊ณผ๋Š” **JSON ๋ฌธ์ž์—ด**๋งŒ ์ถœ๋ ฅํ•œ๋‹ค."
76
+ )
77
+
78
+
79
+ def call_llm(seed: str, debug: bool = False):
80
+ """LLM ํ˜ธ์ถœ โ†’ JSON dict ๋ฐ˜ํ™˜"""
81
+ resp = client.chat.completions.create(
82
+ model="gpt-4o-mini", # ๋˜๋Š” gpt-4-turbo (์กฐ์ • ๊ฐ€๋Šฅ)
83
+ messages=[
84
+ {"role": "system", "content": SYSTEM_PROMPT},
85
+ {"role": "user", "content": seed},
86
  ],
 
87
  temperature=0.9,
88
+ max_tokens=512,
89
  )
90
+ raw = resp.choices[0].message.content.strip()
91
+ if debug:
92
+ print("LLM raw:", raw)
93
  try:
94
  data = json.loads(raw)
95
+ except json.JSONDecodeError as exc:
96
+ raise ValueError(f"LLM ์‘๋‹ต JSON ํŒŒ์‹ฑ ์‹คํŒจ: {exc}\n์›๋ฌธ: {raw}")
97
  return data
98
 
99
  # -------------------------------------------------
 
102
 
103
  def generate(seed_word):
104
  data = call_llm(seed_word)
105
+ fig = draw_square({k: data[k] for k in ("tl", "tr", "br", "bl")})
106
+ return fig, data["top_phrase"], data["bottom_phrase"], data["slogan"], data["brand"]
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
  # -------------------------------------------------
109
  # 5. Gradio UI
110
  # -------------------------------------------------
111
  with gr.Blocks(title="Square Theory Generator โ€“ LLM Powered ๐Ÿ‡ฐ๐Ÿ‡ท") as demo:
112
+ gr.Markdown("""# ๐ŸŸง Square Theory Generator\n๋‹จ์–ด ํ•˜๋‚˜๋งŒ ์ž…๋ ฅํ•˜๋ฉด LLM์ด ์‚ฌ๊ฐํ˜• ๊ตฌ์กฐยท์นดํ”ผยท๋ธŒ๋žœ๋“œ ๋„ค์ž„์„ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.""")
 
113
  seed = gr.Textbox(label="์‹œ๋“œ ๋‹จ์–ด(TL)", placeholder="์˜ˆ: ๊ณจ๋“ ")
114
+ run = gr.Button("์‚ฌ๊ฐํ˜• ์ƒ์„ฑ")
 
115
  fig_out = gr.Plot(label="์‚ฌ๊ฐํ˜• ๋‹ค์ด์–ด๊ทธ๋žจ")
116
  top_out = gr.Textbox(label="์œ—๋ณ€ ํ‘œํ˜„")
117
  bottom_out = gr.Textbox(label="์•„๋žซ๋ณ€ ํ‘œํ˜„")
118
  slogan_out = gr.Textbox(label="๋‘ ์ค„ ์Šฌ๋กœ๊ฑด")
119
+ brand_out = gr.Textbox(label="๋ธŒ๋žœ๋“œ ๋„ค์ž„")
120
 
121
+ run.click(generate, inputs=seed, outputs=[fig_out, top_out, bottom_out, slogan_out, brand_out])
122
 
123
  if __name__ == "__main__":
 
124
  demo.launch()