Spaces:
Build error
Build error
Download app.py from sofiamak/englishname: direct link, hf CLI and curl.
- Browser
- Download file 18.6 kB
-
https://huggingface.co/spaces/sofiamak/englishname/resolve/main/app.py
- Command line
-
hf download hf://spaces/sofiamak/englishname/app.py
-
curl -L -o app.py https://huggingface.co/spaces/sofiamak/englishname/resolve/main/app.py
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() |