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()