englishname / app.py
sofiamak's picture
Update app.py
f93d6f7 verified
Raw History Blame Contribute Delete
18.6 kB
import gradio as gr
import random
import re
import json
# ========== 可选:加载免费开源小模型(CPU本地推理,无需Key) ==========
USE_LLM = True
tokenizer = model = None
MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
try:
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
trust_remote_code=True,
torch_dtype=torch.float32
)
model.eval()
except Exception:
USE_LLM = False
# ===== 名字合法性:拒绝拼音/奇怪符号,仅允许一个单词 =====
ALLOWLIST = {"Chloe","Charlotte","Xavier","Zach","Shawn","Shane","Zoe","Zoey","Chase","Charles","Xena","Phoenix"}
PINYIN_FRAGMENTS = [
"zh","ch","sh","xue","xiao","ming","jing","qiang","qian","qin","qiu","xuan","xun",
"yue","yu","yang","ying","yong","yan","jun","hao","lei","peng","lin","zhao","zhou","zhang",
"wang","li","liu","chen","gao","guo","wu","he","luo","sun","tang","song"
]
NAME_RE = re.compile(r"^[A-Z][a-z]{2,15}$")
def looks_like_pinyin(name:str)->bool:
return (name not in ALLOWLIST) and any(f in name.lower() for f in PINYIN_FRAGMENTS)
def name_sanity(name:str)->bool:
return bool(NAME_RE.match(name or "")) and (not looks_like_pinyin(name))
# ===== 基于中文的“主题/意象”提取 =====
# 主题 -> 候选英文“常规名”和“创意词名”
THEME_POOL = {
"bright": {
"male": ["Leo","Lucian","Clarence","Ray"],
"female": ["Clara","Lucy","Claire","Eleanor"],
"word": ["Bright","Dawn","Halo","Glow","Solar"]
},
"wisdom": {
"male": ["Solomon","Hugo","Sage"],
"female": ["Sophia","Sage","Minerva"],
"word": ["Oracle","Logic","Riddle","Cipher"]
},
"brave": {
"male": ["Andrew","Conrad","Leon","Victor"],
"female": ["Valerie","Andrea","Bridget"],
"word": ["Valor","Brave","Vanguard","Grit","Steel"]
},
"strong": {
"male": ["Ethan","Arnold","Magnus"],
"female": ["Matilda","Valerie"],
"word": ["Atlas","Titan","Forge","Anchor"]
},
"peace": {
"male": ["Irenaeus","Pax","Felix"],
"female": ["Irene","Serena","Pax"],
"word": ["Truce","Calm","Zen","Peace"]
},
"snow": {
"male": ["Nevin","Frost"],
"female": ["Neve","Elsa"],
"word": ["Snow","Frost","Blanc","Crystal"]
},
"pure": {
"male": ["Aaron"],
"female": ["Agnes","Catherine"],
"word": ["Pure","Prism"]
},
"dawn": {
"male": ["Aiden"],
"female": ["Aurora","Zora"],
"word": ["Dawn","Eos","Sunrise","Firstlight"]
},
"sunny": {
"male": ["Sunny","Helios"],
"female": ["Sunny","Solana"],
"word": ["Sunny","Sunbeam","Sol"]
},
"cosmic": {
"male": ["Cosmo","Orion"],
"female": ["Nova","Stella"],
"word": ["Nova","Stellar","Comet","Nebula","Orbit"]
},
"grand": {
"male": ["August","Grant"],
"female": ["Gloria","Augusta"],
"word": ["Grand","Majesty","Prime"]
},
"beauty": {
"male": ["Beau"],
"female": ["Bella"],
"word": ["Beauty","Belle"]
},
"serene": {
"male": ["Irenaeus"],
"female": ["Serena","Irene"],
"word": ["Serene","Still","Quiet"]
},
"talented": {
"male": ["Aiden","Gavin","Kevin"],
"female": ["Ava","Mia"],
"word": ["Talent","Flair","Spark"]
},
"positive": {
"male": ["Felix"],
"female": ["Joy"],
"word": ["Joy","Lucky","Fortune"]
},
# 通用兜底
"general": {
"male": ["Henry","Daniel","Samuel","Arthur","Miles","James","William","Michael"],
"female": ["Emma","Olivia","Emily","Grace","Lucy","Clara","Jane","Rachel"],
"word": ["Echo","River","Willow","Raven","Skye","Blaze","Rider","Fox","Bolt"]
}
}
# 中文字符到主题映射
CHAR2THEMES = {
"明": ["bright","wisdom","dawn"],
"文": ["wisdom"],
"静": ["serene","peace"],
"勇": ["brave"],
"伟": ["grand","strong"],
"强": ["strong"],
"俊": ["talented"],
"晨": ["dawn","bright"],
"雪": ["snow","pure"],
"宁": ["peace","serene"],
"美": ["beauty"],
"慧": ["wisdom"],
"宇": ["cosmic","grand"],
"阳": ["sunny","positive","bright"],
}
# 引用库(扩展版,含一些“词名”的流行文化对照)
REFERENCE_DB = {
"Elizabeth": ["Elizabeth Bennet(《傲慢与偏见》)"],
"Darcy": ["Mr. Darcy(《傲慢与偏见》)"],
"Emma": ["Emma Woodhouse(《爱玛》)"],
"Jane": ["Jane Eyre(《简·爱》)"],
"Alice": ["Alice(《爱丽丝梦游仙境》)"],
"Hermione": ["Hermione Granger(《哈利·波特》)"],
"Harry": ["Harry Potter(《哈利·波特》)"],
"Ron": ["Ron Weasley(《哈利·波特》)"],
"Luna": ["Luna Lovegood(《哈利·波特》)"],
"Clara": ["Clara Oswald(《神秘博士》)"],
"Lucy": ["Lucy Pevensie(《纳尼亚传奇》)"],
"Peter": ["Peter Parker(蜘蛛侠)"],
"Tony": ["Tony Stark(钢铁侠)"],
"Bruce": ["Bruce Wayne(蝙蝠侠)"],
"Arthur": ["Arthur(亚瑟王传说)"],
"Victor": ["Victor Frankenstein(《弗兰肯斯坦》)"],
"Magnus": ["Magnus Bane(《凡人圣器》)"],
"Gatsby": ["Jay Gatsby(《了不起的盖茨比》)"],
"Daisy": ["Daisy Buchanan(《了不起的盖茨比》)"],
"Holden": ["Holden Caulfield(《麦田里的守望者》)"],
"Atticus": ["Atticus Finch(《杀死一只知更鸟》)"],
"Romeo": ["Romeo(《罗密欧与朱丽叶》)"],
"Juliet": ["Juliet(《罗密欧与朱丽叶》)"],
"Arya": ["Arya Stark(《权力的游戏》)"],
"Sansa": ["Sansa Stark(《权力的游戏》)"],
"Jon": ["Jon Snow(《权力的游戏》)"],
"Daenerys": ["Daenerys Targaryen(《权力的游戏》)"],
"Sam": ["Samwise Gamgee(《指环王》)"],
"Frodo": ["Frodo Baggins(《指环王》)"],
"Gandalf": ["Gandalf(《指环王》)"],
"Katniss": ["Katniss Everdeen(《饥饿游戏》)"],
"Tris": ["Tris Prior(《分歧者》)"],
"Leo": ["Leo McGarry(《白宫风云》)"],
"Felix": ["Felix Krull(《骗子费利克斯》)"],
"Aurora": ["Aurora(《睡美人》)"],
"Holly": ["Holly Golightly(《蒂凡尼的早餐》)"],
"Henry": ["Henry Higgins(《窈窕淑女》)"],
"Emily": ["Emily Dickinson(诗人)"],
"Olivia": ["Olivia Pope(《丑闻》)"],
"Rachel": ["Rachel Green(《老友记》)"],
"Monica": ["Monica Geller(《老友记》)"],
"Phoebe": ["Phoebe Buffay(《老友记》)"],
"Chandler": ["Chandler Bing(《老友记》)"],
"Ross": ["Ross Geller(《老友记》)"],
"Joey": ["Joey Tribbiani(《老友记》)"],
"Michael": ["Michael Corleone(《教父》)"],
"James": ["James Bond(007)"],
"Ethan": ["Ethan Hunt(《碟中谍》)"],
"Liam": ["Liam Neeson(演员)"],
"Ava": ["Ava(《机械姬》)"],
"Mia": ["Mia Thermopolis(《公主日记》)"],
"Chloe": ["Chloe Price(《奇异人生》)"],
"Willow": ["Willow Rosenberg(《吸血鬼猎人巴菲》)"],
"River": ["River Tam(《萤火虫》)"],
"Raven": ["Raven(《少年泰坦》)"],
"Echo": ["Echo / Maya Lopez(漫威)"],
"Nova": ["Nova(漫威/多部作品)"],
"Logan": ["Logan / Wolverine(《X战警》)"],
"Edward": ["Edward Cullen(《暮光之城》)"],
"Bella": ["Bella Swan(《暮光之城》)"],
"Skye": ["Skye / Daisy Johnson(《神盾局特工》)"],
"Atlas": ["Atlas(希腊神话)"],
"Orion": ["Orion(猎户座/神话)"],
"Comet": ["Comet(多部作品/角色名常见称呼)"],
"Blaze": ["Blaze the Cat(《索尼克》)"],
"Fox": ["Fox Mulder(《X档案》)"],
}
NORMAL_POOL_MALE = ["Ethan","Liam","Noah","Mason","Leo","Andrew","Victor","Felix","Grant","Conrad","Kevin","Gavin","Miles","Henry","Arthur","Daniel","Michael","James","Samuel","Benjamin","Alexander","Logan","Edward","William","Joseph"]
NORMAL_POOL_FEMALE = ["Emma","Olivia","Emily","Grace","Lucy","Clara","Jane","Rachel","Ava","Mia","Chloe","Stella","Bella","Claire","Phoebe","Serena","Irene","Aurora","Zoe","Sophie","Hannah","Sarah","Natalie","Victoria","Willow"]
MALE_SET = set(NORMAL_POOL_MALE)
FEMALE_SET = set(NORMAL_POOL_FEMALE)
FEMALE_HINT = set(list("芳娜丽敏静婷雪梅玲颖倩婧薇诗妍悦宁珂伊菲茜可璇欣美慧"))
MALE_HINT = set(list("强军伟刚涛磊鹏杰阳龙峰超辉宁宇航晨鑫豪剑霖勇健俊"))
def gender_hint_from_cn(name: str) -> str:
score = 0
for ch in name:
if ch in FEMALE_HINT: score -= 1
if ch in MALE_HINT: score += 1
return "male" if score > 0 else "female"
# —— 从中文名提取主题列表(按频次和优先级) ——
def themes_from_cn(cn_name: str):
themes = []
for ch in cn_name:
themes += CHAR2THEMES.get(ch, [])
if not themes:
themes = ["general"]
# 去重保序
seen = set()
out = []
for t in themes:
if t not in seen:
seen.add(t)
out.append(t)
if "general" not in out:
out.append("general")
return out
# —— 动态含义模板(避免每次一样) ——
MEANING_TEMPLATES = [
"取“{chars}”的意象({themes_cn}),所以选了 {name}。",
"参考“{chars}”带来的感觉({themes_cn}),给你 {name}。",
"从“{chars}”联想到 {themes_cn},因此是 {name}。",
"因为“{chars}”→{themes_cn},所以用了 {name} 这个名字。",
"由“{chars}”启发({themes_cn}),名字定为 {name}。"
]
THEME_ZH = {
"bright":"明亮/光芒","wisdom":"智慧/学识","brave":"勇气/无畏","strong":"强壮/坚韧",
"peace":"平和/宁静","snow":"雪意/纯净","pure":"纯洁","dawn":"黎明/新生","sunny":"阳光/积极",
"cosmic":"宇宙/星空","grand":"宏大/气势","beauty":"美感/优雅","serene":"安静/从容",
"talented":"才华/灵气","positive":"好运/积极","general":"通用/耐看"
}
def render_meaning(cn_name, used_themes, name):
chars = "".join([ch for ch in cn_name if ch in CHAR2THEMES]) or cn_name
zh = "、".join([THEME_ZH.get(t,t) for t in used_themes[:2]]) # 取前两个主题做说明
tpl = random.choice(MEANING_TEMPLATES)
return tpl.format(chars=chars, themes_cn=zh, name=name)
# —— 主题驱动选名:可能是常规英文名,也可能是“词名” ——
def themed_pick(cn_name: str, gender: str, creative_ratio=0.4, seed=None):
rnd = random.Random(seed)
themes = themes_from_cn(cn_name)
t = rnd.choice(themes[:2] or themes) # 更偏向前两个主题
pool = THEME_POOL.get(t, THEME_POOL["general"])
creative = rnd.random() < creative_ratio
if creative:
# 允许词名(不做男女白名单)
cand = [w for w in pool["word"] if name_sanity(w)]
if not cand:
cand = [w for w in THEME_POOL["general"]["word"] if name_sanity(w)]
pick = rnd.choice(cand)
kind = "word"
else:
base = pool["male"] if gender=="male" else pool["female"]
# 性别白名单 + 合法性
cand = [n for n in base if name_sanity(n) and ((n in MALE_SET) if gender=="male" else (n in FEMALE_SET))]
if not cand:
base2 = NORMAL_POOL_MALE if gender=="male" else NORMAL_POOL_FEMALE
cand = [n for n in base2 if name_sanity(n)]
pick = rnd.choice(cand)
kind = "given"
meaning = render_meaning(cn_name, [t], pick)
# 引用:优先直接命中;如果词名没命中,用若干主题近似兜底
refs = REFERENCE_DB.get(pick, [])
if not refs:
approx = {
"River":["River Tam(《萤火虫》)"],
"Willow":["Willow Rosenberg(《吸血鬼猎人巴菲》)"],
"Raven":["Raven(《少年泰坦》)"],
"Echo":["Echo / Maya Lopez(漫威)"],
"Nova":["Nova(漫威)》"],
"Blaze":["Blaze the Cat(《索尼克》)"],
"Fox":["Fox Mulder(《X档案》)"],
"Atlas":["Atlas(希腊神话)"],
"Orion":["Orion(神话/星座)"],
"Comet":["Comet(常见角色/代号)"],
}
refs = approx.get(pick, [])
if not refs:
refs = ["未找到特别典型的文学/影视角色"]
return {"name": pick, "meaning": meaning, "references": refs, "kind": kind}
# —— LLM生成(仍保留,但确保和主题相关;失败则用 themed_pick) ——
def llm_one(cn_name: str, gender: str, seed: int = None):
if not USE_LLM or tokenizer is None or model is None:
return themed_pick(cn_name, gender, seed=seed)
themes = themes_from_cn(cn_name)[:3]
sys_prompt = (
"你是英文起名专家。给用户生成1个名字,它可以是:\n"
"A) 常规英文名(Given name,单词),需符合指定性别;或\n"
"B) 一个英文单词式的“词名”(如 Nova, Raven, River, Echo, Blaze 等,单词)。\n"
"要求:\n"
"1) 名字必须与中文名的意象/主题“稍有关联”,主题如下:" + ", ".join(themes) + ";\n"
"2) 名字必须是一个单词;禁止中文拼音/音译(Li, Ming, Xue 等);\n"
"3) 若选择常规英文名,需符合性别(male/female);\n"
"4) 输出JSON对象:{\"name\":..., \"meaning\":..., \"references\":[...], \"kind\":\"given|word\"};\n"
" meaning 用中文,简述与哪个汉字/主题有关;references 给2-4个文学/影视/名人同名(若无写“未找到特别典型的文学/影视角色”)。"
)
user = {"cn_name": cn_name, "gender": gender}
prompt = f"<|im_start|>system\n{sys_prompt}\n<|im_end|>\n<|im_start|>user\n{json.dumps(user, ensure_ascii=False)}\n<|im_end|>\n<|im_start|>assistant\n"
if seed and hasattr(torch, "manual_seed"):
torch.manual_seed(seed)
def try_once():
inputs = tokenizer(prompt, return_tensors="pt")
with torch.no_grad():
out = model.generate(
**inputs,
max_new_tokens=320,
do_sample=True,
top_p=0.9,
temperature=0.9,
eos_token_id=tokenizer.eos_token_id
)
text = tokenizer.decode(out[0], skip_special_tokens=True)
m = re.findall(r"\{.*\}", text, flags=re.S)
if not m:
return None
try:
obj = json.loads(m[-1])
name = (obj.get("name") or "").strip()
kind = (obj.get("kind") or "").strip().lower()
meaning = (obj.get("meaning") or "").strip()
refs = obj.get("references") or []
if not name_sanity(name):
return None
if kind != "word": # 常规名要性别匹配
if gender=="male" and name not in MALE_SET: return None
if gender=="female" and name not in FEMALE_SET: return None
kind = "given"
# 引用兜底
if not isinstance(refs, list) or not refs:
refs = REFERENCE_DB.get(name, []) or ["未找到特别典型的文学/影视角色"]
if not meaning:
meaning = render_meaning(cn_name, themes[:1] or ["general"], name)
return {"name": name, "meaning": meaning, "references": refs[:4], "kind": kind}
except Exception:
return None
for _ in range(4):
r = try_once()
if r:
return r
if hasattr(torch, "manual_seed"):
torch.manual_seed(random.randint(1, 10_000_000))
return themed_pick(cn_name, gender, seed=seed)
# ======== 页面交互 =========
def do_generate(cn_name, gender, seed):
cn_name = (cn_name or "").strip()
if not cn_name:
return gr.update(visible=False), gr.update(visible=True), "请输入中文名字。", "", "—"
if any(not ('\u4e00' <= ch <= '\u9fff') for ch in cn_name):
return gr.update(visible=False), gr.update(visible=True), "请输入**中文**名字(例如:李明 / 王静)。", "", "—"
if gender not in ("male","female"):
gender = "male"
res = llm_one(cn_name, gender, seed=seed)
name, meaning, refs = res["name"], res["meaning"], res["references"]
refs_md = "、".join(refs)
title = f"🎉 你的英文名字:**{name}**"
return gr.update(visible=False), gr.update(visible=True), title, f"含义/灵感:{meaning}", f"同名角色:{refs_md}"
def go_home():
return gr.update(visible=True), gr.update(visible=False), "", "", ""
with gr.Blocks(title="给自己起一个英文名字", fill_height=True) as demo:
seed_state = gr.State(value=random.randint(1, 10_000_000))
# 首页
gr.Markdown("## 给自己起一个英文名字")
with gr.Group(visible=True) as home:
gr.Markdown("输入中文名与性别,然后点击 **起名** 👇")
with gr.Row():
cn_name = gr.Textbox(label="中文名字", placeholder="例如:李明 / 王静 / 陈宇", lines=1)
with gr.Row():
gender = gr.Radio(choices=["male","female"], value="male", label="性别")
btn_go = gr.Button("起名", variant="primary")
# 结果页
with gr.Group(visible=False) as result:
result_title = gr.Markdown("")
result_meaning = gr.Markdown("")
result_refs = gr.Markdown("")
with gr.Row():
btn_shuffle = gr.Button("换名字")
btn_back = gr.Button("返回首页", variant="secondary")
# 绑定事件
btn_go.click(
do_generate,
inputs=[cn_name, gender, seed_state],
outputs=[home, result, result_title, result_meaning, result_refs]
)
def on_shuffle(cn, g, sd):
new_seed = (sd or 0) + random.randint(7, 9999)
return (*do_generate(cn, g, new_seed),)
btn_shuffle.click(
on_shuffle,
inputs=[cn_name, gender, seed_state],
outputs=[home, result, result_title, result_meaning, result_refs]
)
btn_back.click(
go_home,
inputs=[],
outputs=[home, result, result_title, result_meaning, result_refs]
)
if __name__ == "__main__":
demo.launch()