Naming / app.py
openfree's picture
Update app.py
dd53e8d verified
raw
history blame
6.28 kB
"""
Square Theory Generator (len-flexible)
=====================================
2025โ€‘05โ€‘28 v6 โ— LLM ๊ฒฐ๊ณผ๊ฐ€ 10๊ฐœ ๋ฏธ๋งŒ์ผ ๋•Œ ์œ ์—ฐ ์ฒ˜๋ฆฌ
---------------------------------------------------
๋ณ€๊ฒฝ ์š”์•ฝ
---------
* **๋ฌธ์ œ**: LLM์ด 10๊ฐœ ๋ฏธ๋งŒ์˜ ์ œ์•ˆ์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๊ธธ์ด ๊ฒ€์ฆ ์˜ˆ์™ธ ๋ฐœ์ƒ.
* **ํ•ด๊ฒฐ**:
1. `call_llm()`์—์„œ JSON์ด dict ๋‹จ์ผ ๊ฐ์ฒด or 1~9๊ฐœ ๋ฆฌ์ŠคํŠธ์—ฌ๋„ ํ—ˆ์šฉ.
2. ๋ฆฌ์ŠคํŠธ๊ฐ€ 1๊ฐœ ๋ฏธ๋งŒ์ด๋ฉด ์˜ค๋ฅ˜, 2~9๊ฐœ๋Š” ๊ฒฝ๊ณ ๋งŒ ํ‘œ๊ธฐ.
3. Markdown์— ์‹ค์ œ ๊ฐœ์ˆ˜๋กœ ํ—ค๋” ์ถœ๋ ฅ.
์‹คํ–‰๋ฒ•
------
```bash
pip install --upgrade gradio matplotlib openai requests
export OPENAI_API_KEY="sk-..."
python square_theory_gradio.py
```
"""
import os
import json
import tempfile
import urllib.request
import gradio as gr
import matplotlib.pyplot as plt
from matplotlib import patches, font_manager, rcParams
from openai import OpenAI
# -------------------------------------------------
# 0. ํ•œ๊ธ€ ํฐํŠธ ์„ค์ • (๋‹ค์šด๋กœ๋“œ fallback ํฌํ•จ)
# -------------------------------------------------
PREFERRED_FONTS = ["Malgun Gothic", "NanumGothic", "AppleGothic", "DejaVu Sans"]
NANUM_URL = (
"https://github.com/google/fonts/raw/main/ofl/nanumgothic/"
"NanumGothic-Regular.ttf"
)
def _set_korean_font():
available = {f.name for f in font_manager.fontManager.ttflist}
for cand in PREFERRED_FONTS:
if cand in available:
rcParams["font.family"] = cand
break
else:
try:
tmp_dir = tempfile.gettempdir()
font_path = os.path.join(tmp_dir, "NanumGothic-Regular.ttf")
if not os.path.exists(font_path):
urllib.request.urlretrieve(NANUM_URL, font_path)
font_manager.fontManager.addfont(font_path)
rcParams["font.family"] = font_manager.FontProperties(fname=font_path).get_name()
except Exception as e:
print("[WARN] Font download failed, Korean text may break:", e)
rcParams["axes.unicode_minus"] = False
_set_korean_font()
# -------------------------------------------------
# 1. OpenAI ํด๋ผ์ด์–ธํŠธ
# -------------------------------------------------
if not os.getenv("OPENAI_API_KEY"):
raise EnvironmentError("OPENAI_API_KEY ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.")
client = OpenAI()
# -------------------------------------------------
# 2. Square Diagram
# -------------------------------------------------
def draw_square(words):
fig, ax = plt.subplots(figsize=(4, 4))
ax.add_patch(patches.Rectangle((0, 0), 1, 1, fill=False, linewidth=2))
ax.text(-0.05, 1.05, str(words.get("tl", "")), ha="right", va="bottom", fontsize=14, fontweight="bold")
ax.text(1.05, 1.05, str(words.get("tr", "")), ha="left", va="bottom", fontsize=14, fontweight="bold")
ax.text(1.05, -0.05, str(words.get("br", "")), ha="left", va="top", fontsize=14, fontweight="bold")
ax.text(-0.05, -0.05, str(words.get("bl", "")), ha="right", va="top", fontsize=14, fontweight="bold")
ax.set_xticks([])
ax.set_yticks([])
ax.set_xlim(-0.2, 1.2)
ax.set_ylim(-0.2, 1.2)
ax.set_aspect("equal")
return fig
# -------------------------------------------------
# 3. LLM Prompt & Call
# -------------------------------------------------
SYSTEM_PROMPT = (
"๋„ˆ๋Š” ํ•œ๊ตญ์–ด ์นดํ”ผยท๋ธŒ๋žœ๋“œ ๋„ค์ด๋ฐ ์ „๋ฌธ๊ฐ€์ด์ž Square Theory ๋„์šฐ๋ฏธ๋‹ค. "
"์‚ฌ์šฉ์ž๊ฐ€ ์ค€ ํ•˜๋‚˜์˜ ๋‹จ์–ด(tl)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ’ˆ์งˆ์ด ๋›ฐ์–ด๋‚œ ์ˆœ์„œ๋Œ€๋กœ ์ตœ๋Œ€ 10๊ฐœ์˜ ์ œ์•ˆ์„ JSON ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜ํ•ด๋ผ. "
"๊ฐ ๋ฐฐ์—ด ์›์†Œ๋Š” tl, tr, br, bl, top_phrase, bottom_phrase, slogan, brand ํ•„๋“œ๋ฅผ ๊ฐ€์ง„๋‹ค. "
"์‚ฌ๊ฐํ˜• ๋„ค ๊ผญ์ง“์ (tlโ†’trโ†’brโ†’bl)์ด ์ž์—ฐ์Šค๋ ˆ ์—ฐ๊ฒฐ๋ผ์•ผ ํ•˜๊ณ , ๋ฐฐ์—ด ์ฒซ ์›์†Œ๊ฐ€ ์ตœ์šฐ์ˆ˜ ์ œ์•ˆ์ด๋‹ค. "
"๊ฒฐ๊ณผ๋Š” JSON ์™ธ ํ…์ŠคํŠธ๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค."
)
def clean_json_block(text: str) -> str:
text = text.strip()
if text.startswith("```"):
text = text.split("\n", 1)[1] if "\n" in text else text[3:]
if text.endswith("```"):
text = text[:-3]
return text.strip()
def call_llm(seed: str):
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": seed},
],
temperature=0.9,
max_tokens=1024,
)
raw = resp.choices[0].message.content
cleaned = clean_json_block(raw)
try:
data = json.loads(cleaned)
# ๋‹จ์ผ ๊ฐ์ฒด๋ฉด ๋ฆฌ์ŠคํŠธ๋กœ ๋ณ€ํ™˜
if isinstance(data, dict):
data = [data]
if not isinstance(data, list):
raise TypeError("LLM ์‘๋‹ต์ด ๋ฆฌ์ŠคํŠธ๊ฐ€ ์•„๋‹˜")
if len(data) == 0:
raise ValueError("LLM์ด ๋นˆ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜")
except Exception as exc:
raise ValueError(f"LLM JSON ํŒŒ์‹ฑ ์‹คํŒจ: {exc}\n์›๋ฌธ ์ผ๋ถ€: {cleaned[:300]} โ€ฆ")
return data
# -------------------------------------------------
# 4. Gradio callback
# -------------------------------------------------
def generate(seed_word: str):
results = call_llm(seed_word)
fig = draw_square({k: results[0][k] for k in ("tl", "tr", "br", "bl")})
md_lines = [f"## ์ด {len(results)}๊ฐœ ์ œ์•ˆ\n"]
for idx, item in enumerate(results, 1):
md_lines.append(
f"### {idx}. {item['top_phrase']} / {item['bottom_phrase']}\n"
f"- **์Šฌ๋กœ๊ฑด**: {item['slogan']}\n"
f"- **๋ธŒ๋žœ๋“œ ๋„ค์ž„**: {item['brand']}\n"
f"- (tl={item['tl']}, tr={item['tr']}, br={item['br']}, bl={item['bl']})\n"
)
return fig, "\n".join(md_lines)
# -------------------------------------------------
# 5. UI
# -------------------------------------------------
with gr.Blocks(title="Square Theory โ€“ ์ตœ๊ณ  ์ œ์•ˆ ๐Ÿ‡ฐ๐Ÿ‡ท") as demo:
gr.Markdown("""# ๐ŸŸง Square Theory ์ œ์•ˆ (์ตœ๋Œ€ 10๊ฐœ)\n๋‹จ์–ด 1๊ฐœ ์ž…๋ ฅ โ†’ LLM์ด ํ‰๊ฐ€ยท์ •๋ ฌํ•œ ์‚ฌ๊ฐํ˜•/์นดํ”ผ/๋ธŒ๋žœ๋“œ ๋„ค์ž„""")
seed = gr.Textbox(label="์‹œ๋“œ ๋‹จ์–ด(TL)", placeholder="์˜ˆ: ๊ณจ๋“ ")
run = gr.Button("์ƒ์„ฑ")
fig_out = gr.Plot(label="1์œ„ ์‚ฌ๊ฐํ˜•")
md_out = gr.Markdown(label="์ œ์•ˆ ๋ชฉ๋ก")
run.click(generate, inputs=seed, outputs=[fig_out, md_out])
if __name__ == "__main__":
demo.launch()