Naming / app.py
openfree's picture
Update app.py
673fa0a verified
raw
history blame
6.19 kB
"""
Square Theory Generator (10 best variations)
===========================================
2025โ€‘05โ€‘28ย v5 โ— ์ฝ”๋“œ๋ธ”๋ก JSON ํŒŒ์‹ฑ ์˜ค๋ฅ˜ ํ•ด๊ฒฐ + ํ•œ๊ธ€ ํฐํŠธย OK
-------------------------------------------------
๋ณ€๊ฒฝ ์š”์•ฝ
---------
* **๋ฌธ์ œ**: ์ผ๋ถ€ LLM ์‘๋‹ต์ด ```json โ€ฆ ``` ์ฝ”๋“œํŽœ์Šค๋กœ ๊ฐ์‹ธ์ ธ `json.loads()` ์‹คํŒจ โ†’ ์˜ˆ์™ธ.
* **ํ•ด๊ฒฐ**: ์ฝ”๋“œํŽœ์Šค ์ œ๊ฑฐ `clean_json_block()` ํ•จ์ˆ˜ ์ถ”๊ฐ€.
* ๊ธฐํƒ€: ํฐํŠธ ๋กœ์ง ๊ทธ๋Œ€๋กœ ์œ ์ง€.
์‹คํ–‰๋ฒ•
------
```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)๊ณผ ๋‘ ํ‘œํ˜„ยท์Šฌ๋กœ๊ฑดยท๋ธŒ๋žœ๋“œ ๋„ค์ž„์ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์—ฐ๊ฒฐ๋ผ์•ผ ํ•œ๋‹ค. "
"๋ฐฐ์—ด์€ ์ตœ๊ณ ์˜ ์ œ์•ˆ์ด index 0, ๊ทธ๋‹ค์Œ์ด index 1 โ€ฆ 9 ์ˆœ์„œ์—ฌ์•ผ ํ•œ๋‹ค. "
"๊ฒฐ๊ณผ๋Š” JSON ์™ธ ๋‹ค๋ฅธ ํ…์ŠคํŠธ๋ฅผ ํฌํ•จํ•˜๋ฉด ์•ˆ ๋œ๋‹ค."
)
def clean_json_block(text: str) -> str:
"""Remove ```json ... ``` or ``` ... ``` fences."""
text = text.strip()
if text.startswith("```"):
# remove first ``` line
text = text.split("\n", 1)[1] if "\n" in text else text[3:]
# strip trailing fence
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 not isinstance(data, list) or len(data) != 10:
raise ValueError("JSON ๋ฐฐ์—ด ๊ธธ์ด๊ฐ€ 10์ด ์•„๋‹˜")
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 = []
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 โ€“ Topย 10 ๐Ÿ‡ฐ๐Ÿ‡ท") as demo:
gr.Markdown("""# ๐Ÿ”Ÿ Square Theory ์ œ์•ˆ Topย 10\n๋‹จ์–ด 1๊ฐœ โ†’ LLM์ด ํ‰๊ฐ€ยท์ •๋ ฌํ•œ 10๊ฐœ ์‚ฌ๊ฐํ˜•/์นดํ”ผ/๋ธŒ๋žœ๋“œ ๋„ค์ž„""")
seed = gr.Textbox(label="์‹œ๋“œ ๋‹จ์–ด(TL)", placeholder="์˜ˆ: ๊ณจ๋“ ")
run = gr.Button("์ƒ์„ฑ")
fig_out = gr.Plot(label="1์œ„ ์‚ฌ๊ฐํ˜•")
md_out = gr.Markdown(label="Topย 10 ์ œ์•ˆ")
run.click(generate, inputs=seed, outputs=[fig_out, md_out])
if __name__ == "__main__":
demo.launch()