sofiamak commited on
Commit
d62cdd4
·
verified ·
1 Parent(s): d41fbd4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +173 -29
app.py CHANGED
@@ -2,11 +2,12 @@ import os, html, random
2
  import gradio as gr
3
  from openai import OpenAI
4
 
 
5
  API_KEY = os.getenv("OPENAI_API_KEY")
6
  MODEL_NAME = "gpt-4o-mini"
7
  client = OpenAI(api_key=API_KEY)
8
 
9
- # ===== 可爱名字 + 真实名言:系统提示 =====
10
  SYSTEM_PROMPT = """You are a playful, high-creativity pet name generator.
11
  Goal: Produce EXACTLY ONE cute, kid-pronounceable pet name + one authentic quote line.
12
 
@@ -24,7 +25,7 @@ NAME rules (must be easy for kids):
24
  • Korean: 2 simple syllables (e.g., “Bori”, “Toto”).
25
  • French: 1–2 syllables with soft endings (-ou, -i, -ette).
26
  - You MAY coin a new word if it sounds cute and natural.
27
- - Output in requested language (Chinese/English/Japanese/Korean/French).
28
  - Provide ONE short meaning/explanation (≤12 words), theme-first, traits-second.
29
  - No numbers; no long phrases.
30
 
@@ -54,7 +55,7 @@ Task:
54
  Remember: inspiration first (~70%), traits second (~30%); keep both lines in {language}.
55
  """
56
 
57
- # ===== 真实名言兜底(多语言) =====
58
  FALLBACK_QUOTES = {
59
  "English": [
60
  ('"Until one has loved an animal, a part of one\'s soul remains unawakened."', "Anatole France"),
@@ -77,9 +78,9 @@ FALLBACK_QUOTES = {
77
  ('“동물은 참으로 상냥한 친구다—묻지도 비난하지도 않는다.”', "George Eliot"),
78
  ],
79
  "Français": [
80
- ('« Jusqu\'à ce qu\'on ait aimé un animal, une partie de notre âme reste endormie. »', "Anatole France"),
81
- ('« Les yeux d’un animal ont le pouvoir de parler un grand langage. »', "Martin Buber"),
82
- ('« Les animaux sont de si agréables amis — ils ne posent aucune question et ne font aucune critique. »', "George Eliot"),
83
  ],
84
  }
85
 
@@ -88,16 +89,14 @@ def get_fallback_quote(language: str):
88
  text, author = random.choice(FALLBACK_QUOTES[lang])
89
  return f'Quote: {text} — {author}'
90
 
91
- def ensure_two_lines_and_quote(content: str, language: str):
92
  """
93
- 确保严格两行输出;若缺失或格式不当,为第二行注入真实名言兜底。
94
  """
95
  if not content:
96
- return None
97
  lines = [ln.strip() for ln in content.splitlines() if ln.strip()]
98
- # 尽量抓取首行作为名字
99
  name_line = lines[0] if lines else ""
100
- # 找到包含 Quote: 的行
101
  quote_line = None
102
  for ln in lines[1:]:
103
  if ln.lower().startswith("quote:"):
@@ -105,8 +104,7 @@ def ensure_two_lines_and_quote(content: str, language: str):
105
  break
106
  if not quote_line:
107
  quote_line = get_fallback_quote(language)
108
- # 只返回两行
109
- return f"{name_line}\n{quote_line}"
110
 
111
  def generate_name(species, look, personality, gender, language, inspiration):
112
  try:
@@ -118,7 +116,7 @@ def generate_name(species, look, personality, gender, language, inspiration):
118
  )
119
  resp = client.chat.completions.create(
120
  model=MODEL_NAME,
121
- temperature=0.95, # 强创意
122
  top_p=0.95,
123
  max_tokens=280,
124
  messages=[
@@ -127,30 +125,176 @@ def generate_name(species, look, personality, gender, language, inspiration):
127
  ]
128
  )
129
  raw = (resp.choices[0].message.content or "").strip()
130
- final_out = ensure_two_lines_and_quote(raw, language)
131
- return final_out or "(没有生成内容,请稍后再试)"
 
 
132
  except Exception as e:
133
- return f"⚠️ 出错:{html.escape(str(e))}"
134
 
135
- # ===== 界面(去掉那句介绍,只留标题) =====
136
- with gr.Blocks(title="Pawfect Match") as demo:
137
- gr.Markdown("## 🐾 Pawfect Match|宠物起名机")
 
 
 
 
 
 
 
 
 
 
 
138
 
139
- species = gr.Textbox(label="宠物种类", placeholder="例:金毛 / 英短 / 仓鼠 / 鹦鹉(可留空)")
140
- look = gr.Textbox(label="外表特征", placeholder="例:橘色短毛,大眼睛,白爪(可留空)")
141
- personality = gr.Textbox(label="性格特点", placeholder="例:粘人、活泼、聪明(可留空)")
142
- gender = gr.Radio(["male","female"], value="male", label="宠物性别")
143
- language = gr.Radio(["中文","English","日本語","한국어","Français"], value="English", label="输出语言")
144
- inspiration = gr.Radio(["山海经","迪士尼","哈利波特","生活"], value="生活", label="灵感来源")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
- btn = gr.Button("✨ 起名!")
147
- out = gr.Markdown()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  btn.click(
150
  generate_name,
151
  inputs=[species, look, personality, gender, language, inspiration],
152
- outputs=out
153
  )
154
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  if __name__ == "__main__":
156
  demo.launch()
 
2
  import gradio as gr
3
  from openai import OpenAI
4
 
5
+ # ==== 基础配置 ====
6
  API_KEY = os.getenv("OPENAI_API_KEY")
7
  MODEL_NAME = "gpt-4o-mini"
8
  client = OpenAI(api_key=API_KEY)
9
 
10
+ # ==== 系统提示:可爱名字 + 真实名言,灵感优先 ====
11
  SYSTEM_PROMPT = """You are a playful, high-creativity pet name generator.
12
  Goal: Produce EXACTLY ONE cute, kid-pronounceable pet name + one authentic quote line.
13
 
 
25
  • Korean: 2 simple syllables (e.g., “Bori”, “Toto”).
26
  • French: 1–2 syllables with soft endings (-ou, -i, -ette).
27
  - You MAY coin a new word if it sounds cute and natural.
28
+ - Output in the requested language (Chinese/English/Japanese/Korean/French).
29
  - Provide ONE short meaning/explanation (≤12 words), theme-first, traits-second.
30
  - No numbers; no long phrases.
31
 
 
55
  Remember: inspiration first (~70%), traits second (~30%); keep both lines in {language}.
56
  """
57
 
58
+ # ---- 名言兜底(真实名言的多语言版本) ----
59
  FALLBACK_QUOTES = {
60
  "English": [
61
  ('"Until one has loved an animal, a part of one\'s soul remains unawakened."', "Anatole France"),
 
78
  ('“동물은 참으로 상냥한 친구다—묻지도 비난하지도 않는다.”', "George Eliot"),
79
  ],
80
  "Français": [
81
+ ("« Jusqu'à ce qu'on ait aimé un animal, une partie de notre âme reste endormie. »", "Anatole France"),
82
+ ("« Les yeux d’un animal ont le pouvoir de parler un grand langage. »", "Martin Buber"),
83
+ ("« Les animaux sont de si agréables amis — ils ne posent aucune question et ne font aucune critique. »", "George Eliot"),
84
  ],
85
  }
86
 
 
89
  text, author = random.choice(FALLBACK_QUOTES[lang])
90
  return f'Quote: {text} — {author}'
91
 
92
+ def ensure_two_lines_and_split(content: str, language: str):
93
  """
94
+ 确保严格两行;若缺失名言则注入兜底。返回 (name_line, quote_line)
95
  """
96
  if not content:
97
+ return None, None
98
  lines = [ln.strip() for ln in content.splitlines() if ln.strip()]
 
99
  name_line = lines[0] if lines else ""
 
100
  quote_line = None
101
  for ln in lines[1:]:
102
  if ln.lower().startswith("quote:"):
 
104
  break
105
  if not quote_line:
106
  quote_line = get_fallback_quote(language)
107
+ return name_line, quote_line
 
108
 
109
  def generate_name(species, look, personality, gender, language, inspiration):
110
  try:
 
116
  )
117
  resp = client.chat.completions.create(
118
  model=MODEL_NAME,
119
+ temperature=0.95,
120
  top_p=0.95,
121
  max_tokens=280,
122
  messages=[
 
125
  ]
126
  )
127
  raw = (resp.choices[0].message.content or "").strip()
128
+ name_line, quote_line = ensure_two_lines_and_split(raw, language)
129
+ if not (name_line and quote_line):
130
+ return render_result_html("(没有生成内容,请稍后再试)", "")
131
+ return render_result_html(name_line, quote_line)
132
  except Exception as e:
133
+ return render_result_html(f"⚠️ 出错:{html.escape(str(e))}", "")
134
 
135
+ # ======= 渲染结果为“软件风格”的结果卡片(HTML) =======
136
+ def render_result_html(name_line: str, quote_line: str) -> str:
137
+ name_safe = html.escape(name_line or "")
138
+ quote_safe = html.escape(quote_line or "")
139
+ # 支持复制时保留纯文本(按钮用 JS 读隐藏文本)
140
+ plain_text = (name_line + "\n" + quote_line).strip()
141
+ plain_text = html.escape(plain_text)
142
+ return f"""
143
+ <div class="pm-result-card">
144
+ <div class="pm-name">{name_safe}</div>
145
+ <div class="pm-quote">{quote_safe}</div>
146
+ <textarea id="pm-plain" style="position:absolute; left:-9999px; top:-9999px;">{plain_text}</textarea>
147
+ </div>
148
+ """
149
 
150
+ # ======= 主题与样式(让它更像真正的软件) =======
151
+ theme = gr.themes.Soft(
152
+ primary_hue="pink",
153
+ secondary_hue="cyan",
154
+ neutral_hue="slate"
155
+ ).set(
156
+ radius_size="10px",
157
+ block_title_text_size="20px",
158
+ spacing_size="14px"
159
+ )
160
+
161
+ CUSTOM_CSS = """
162
+ :root{
163
+ --pm-bg:#faf7fb;
164
+ --pm-surface:#ffffff;
165
+ --pm-border:#eddcf3;
166
+ --pm-accent:#ff4fa3;
167
+ --pm-accent-2:#42c6d3;
168
+ --pm-text:#111;
169
+ --pm-muted:#6b7280;
170
+ }
171
+ body, .gradio-container{ background:var(--pm-bg) !important; }
172
 
173
+ /* 顶栏 */
174
+ .pm-topbar{
175
+ display:flex; align-items:center; justify-content:space-between;
176
+ padding:12px 12px; background:linear-gradient(90deg,#ffe8f3,#e8fbff);
177
+ border:1px solid var(--pm-border); border-radius:12px;
178
+ }
179
+ .pm-brand{ display:flex; align-items:center; gap:10px; }
180
+ .pm-logo{ font-size:24px; }
181
+ .pm-title{ font-weight:800; font-size:20px; color:var(--pm-text); }
182
+ .pm-sub{ color:var(--pm-muted); font-size:12px; }
183
+
184
+ /* 卡片 */
185
+ .pm-card{
186
+ background:var(--pm-surface); border:1px solid var(--pm-border); border-radius:12px;
187
+ box-shadow:0 4px 18px rgba(17,17,17,0.04); padding:16px;
188
+ }
189
 
190
+ /* 结果卡片 */
191
+ .pm-result-card{
192
+ background:var(--pm-surface);
193
+ border:1px solid var(--pm-border);
194
+ border-radius:14px; padding:18px;
195
+ box-shadow:0 10px 26px rgba(255,79,163,0.08);
196
+ }
197
+ .pm-name{
198
+ font-weight:800; font-size:24px; color:var(--pm-text); margin-bottom:10px;
199
+ }
200
+ .pm-quote{
201
+ font-weight:600; font-size:14px; color:#374151; opacity:.95;
202
+ }
203
+
204
+ /* 按钮组 */
205
+ .pm-actions{ display:flex; gap:10px; align-items:center; }
206
+ .pm-actions .gr-button{ font-weight:700; }
207
+
208
+ .pm-footer{
209
+ text-align:center; color:var(--pm-muted); font-size:12px; margin-top:10px;
210
+ }
211
+ """
212
+
213
+ # ======= 界面布局 =======
214
+ with gr.Blocks(title="Pawfect Match", theme=theme, css=CUSTOM_CSS) as demo:
215
+ # 顶栏
216
+ gr.HTML("""
217
+ <div class="pm-topbar">
218
+ <div class="pm-brand">
219
+ <div class="pm-logo">🐾</div>
220
+ <div>
221
+ <div class="pm-title">Pawfect Match</div>
222
+ <div class="pm-sub">Cute pet naming • Inspired by myth & magic</div>
223
+ </div>
224
+ </div>
225
+ <div class="pm-sub">v1.3</div>
226
+ </div>
227
+ """)
228
+
229
+ with gr.Row():
230
+ # 左侧:输入卡
231
+ with gr.Column(scale=6):
232
+ with gr.Group(elem_classes=["pm-card"]):
233
+ gr.Markdown("#### 基本信息")
234
+ species = gr.Textbox(label="宠物种类", placeholder="例:金毛 / 英短 / 仓鼠 / 鹦鹉(可留空)", autofocus=True)
235
+ look = gr.Textbox(label="外表特征", placeholder="例:橘色短毛,大眼睛,白爪(可留空)")
236
+ personality = gr.Textbox(label="性格特点", placeholder="例:粘人、活泼、聪明(可留空)")
237
+ with gr.Row():
238
+ gender = gr.Radio(["male","female"], value="male", label="宠物性别")
239
+ language = gr.Radio(["中文","English","日本語","한국어","Français"], value="English", label="输出语言")
240
+
241
+ with gr.Group(elem_classes=["pm-card"]):
242
+ gr.Markdown("#### 灵感来源")
243
+ inspiration = gr.Radio(["山海经","迪士尼","哈利波特","生活"], value="生活", label="", info="决定整体风格(主题优先)")
244
+
245
+ with gr.Group(elem_classes=["pm-card"]):
246
+ gr.Markdown("#### 操作")
247
+ with gr.Row(elem_classes=["pm-actions"]):
248
+ btn = gr.Button("✨ 起名", variant="primary")
249
+ clear_btn = gr.Button("清空", variant="secondary")
250
+ copy_btn = gr.Button("复制结果", variant="secondary")
251
+
252
+ with gr.Accordion("快速示例(点击自动填充)", open=False):
253
+ gr.Examples(
254
+ examples=[
255
+ ["英短猫","银渐层,圆脸,绿眼睛","高冷但会撒娇","female","中文","迪士尼"],
256
+ ["柯基犬","短腿、红白毛、笑脸","亲人、活泼、贪吃","male","English","生活"],
257
+ ["狸花猫","虎斑、黄眼睛","机灵好奇","male","日本語","山海经"],
258
+ ["小型犬","白色卷毛","温柔、爱撒娇","female","Français","哈利波特"],
259
+ ],
260
+ inputs=[],
261
+ outputs=[]
262
+ )
263
+
264
+ # 右侧:结果卡
265
+ with gr.Column(scale=6):
266
+ with gr.Group(elem_classes=["pm-card"]):
267
+ gr.Markdown("#### 结果")
268
+ out_html = gr.HTML(value="""
269
+ <div class="pm-result-card">
270
+ <div class="pm-name">(点击「✨ 起名」后显示)</div>
271
+ <div class="pm-quote"></div>
272
+ </div>
273
+ """)
274
+
275
+ gr.HTML('<div class="pm-footer">© Pawfect Match</div>')
276
+
277
+ # 交互
278
  btn.click(
279
  generate_name,
280
  inputs=[species, look, personality, gender, language, inspiration],
281
+ outputs=out_html
282
  )
283
 
284
+ clear_btn.click(
285
+ lambda: ("","","","male","English","生活"),
286
+ outputs=[species, look, personality, gender, language, inspiration]
287
+ )
288
+
289
+ # 复制纯文本(从隐藏 textarea 读取)
290
+ copy_btn.click(None, None, None, _js="""
291
+ () => {
292
+ const ta = document.getElementById('pm-plain');
293
+ if (!ta || !ta.value.trim()) { alert('还没有结果哦~'); return; }
294
+ navigator.clipboard.writeText(ta.value.trim());
295
+ alert('已复制到剪贴板');
296
+ }
297
+ """)
298
+
299
  if __name__ == "__main__":
300
  demo.launch()