☰
我用华为云码道,把甲骨文交给了大模型——「汉字演变实验室」开发手记
2026/10/20 11:27:52 网站建设 项目流程

我用华为云码道,把甲骨文交给了大模型——「汉字演变实验室」开发手记

开发方式:华为云码道(CodeArts 代码智能体)人机结对开发
技术栈:单文件 HTML/CSS/JS + Node 本地代理 + DeepSeek(deepseek-chat)+ 微软 Edge 神经语音 TTS
开源仓库:https://atomgit.com/chenge1/lab
一句话:输入任意汉字,AI 画出它从图画期到楷书的三千年演变,还能讲给孩子听。

先看一个字。

「日」。三千年前它是个圆圈,中间点一点;刻上龟甲后圆圈变硬了;小篆把它拉成长方形;到了楷书,它彻底住进方块里,中间那个点,变成了一横。

几乎每个科普视频都这么讲。但讲完之后呢?孩子记住的是一段文案,不是一场演变。

所以我想做的不是"汉字科普 App",而是一个实验室:字要在孩子眼前逐笔重演它自己的三千年——先民画它、刻刀刻它、毛笔写它。而这件事最疯狂的版本是:除了 16 个手工打磨的内置字,其他任何字都交给 DeepSeek 现场画出来。

这篇文章不写产品功能清单,只复盘这条路上真正难啃的五个技术问题,以及我和华为云码道(CodeArts 代码智能体)结对闯关的过程。


一、笔画即数据:为什么是 SVG path,而不是图片

让 AI 展示字形演变,最省事的做法是喂图:文生图模型出一套"甲骨文风格"的图片,排成时间轴就完事。但沿着这个方向论证下去,会发现它撑不起这个产品。我把两条路线的推演整理成一张表:

维度文生图方案SVG path 方案(本项目采用)
字形可控性风格随机,"甲骨文"多半是艺术创作,教错字是原则性事故坐标级精确,笔画与演变脉络可人工校订
演示形态整张图片切换,像 PPT可逐笔描画、可点睛,像现场书写
体积成本16 字 × 4 阶段 = 64 张图16 个内置字约 240 行结构化数据,随 HTML 一次加载
AI 扩展性需文生图模型,输出不可校验大模型直接生成文本,正则即可验证
离线可用图片可离线,但新字不可造内置字零依赖;新字仅需一次 AI 调用后永久本地化

结论很清晰:每个字形就是一组 SVG path 字符串。内置字的「日」是这样存的:

ri:{zi:"日",py:"rì",mean:"太阳",stages:[{d:["M60,60 m-38,0 a38,38 0 1,0 76,0 a38,38 0 1,0 -76,0"],dots:[[60,60,6]],note:"先民抬头看天:太阳就是一个圆,中间有个小黑点。"},{d:["M26,60 a34,28 0 1,0 68,0 a34,28 0 1,0 -68,0"],dots:[[60,60,5]],note:"刻在龟甲上,圆滚滚的太阳线条变硬了。"},{d:["M34,26 h52 v68 h-52 z","M46,60 h28"],note:"笔画拉得匀匀的,圆圈站直了身子。"},{kai:true,note:"横平竖直,太阳住进了方块字。"}]},

看到这份结构,一个推论自然浮出水面:既然字形是结构化文本,那大模型完全可以直接生成它。这就是后面所有故事的起点——AI 不需要"画"出图片,它只需要"写"出坐标。


二、教大模型画画:一份会画字的 Prompt

难点在于,DeepSeek 没受过书法训练。你不能对它说"画个甲骨文的马"——它可能会给你一段美术课式的描述,也可能给你一串根本渲染不出来的坐标。

我的解法是把 prompt 写成一份甲方需求书,把画布约束死:

constusr=`为汉字「${zi}」设计演变学习内容,返回JSON: {"zi":"${zi}","py":"拼音带声调","mean":"2-4字本义",..., "stages":[ {"d":["SVG path"],"dots":[[60,60,5]],"note":"25字内讲解"}, {"d":["SVG path"],"note":"25字内讲解"}, {"d":["SVG path"],"note":"25字内讲解"}, {"kai":true,"note":"25字内讲解"}], ...} SVG 规则:viewBox 0 0 120 120 居中构图,stroke-width 5 圆头, 只用 M/L/Q/A/C 命令。stages 前3项依次为:图画期(像儿童简笔画)、 甲骨文(线条硬直简练)、小篆(线条圆转弯曲),要能看出同一个字逐渐演变。 dots 是朱红圆点数组,用于点睛,可省略。...`;

几个被反复验证有效的细节:

  • 限定命令白名单(M/L/Q/A/C):把复杂命令留给内置字,AI 只用最朴素的五个,出错率大幅下降;
  • 给每个阶段定义"画风":图画期=儿童简笔画、甲骨文=硬直简练、小篆=圆转弯曲。这比"参考真实文物"有效得多——模型理解的是风格词,不是考古学;
  • 要求"能看出同一个字逐渐演变":这句是灵魂。没有它,AI 经常给出四个毫不相干的图形;有了它,四个阶段之间会出现明显的继承关系——比如「山」的三个尖峰,从图画期的曲线到甲骨文的三根竖线再到楷书,主线不丢。

实测「龙」「鱼」「牛」这类复杂字,DeepSeek 一次成图的质量超出预期:笔画少而传神,恰好是儿童简笔画的气质。

三、AI 是个不可靠的画师:三道防线

指望大模型每次都返回完美 JSON?天真了。真实世界里的返回包括:markdown 代码块包着 JSON、开头带一句"好的,以下是…"、path 字符串里混进中文逗号、甚至直接缺一个阶段。

所以拿到响应之后,还有一整套"验画师"流程:

constm=txt.match(/\{[\s\S]*\}/);// ① 从杂质里抠出 JSONconstj=JSON.parse(m[0]);[0,1,2].forEach(i=>{if(!validStage(j.stages[i]))// ② 逐阶段验图形thrownewError('第'+(i+1)+'阶段图形无效,请重试')});functionvalidStage(s){returns&&Array.isArray(s.d)&&s.d.length>0&&s.d.every(p=>typeofp==='string'&&/^[-MZmLlHhVvCcSsQqTtAa0-9,.\s]+$/.test(p)&&// 只许合法字符/[Mm]/.test(p[0]));// 必须以 M 开头(先落笔)}

这个正则验证器看似简单,实际拦下了绝大多数畸形输出。最妙的是那条/[Mm]/检查——SVG path 必须从 M(moveTo)开始,一个从没落笔的 path 是画不出任何东西的。

第三道防线在故事层:内置 16 个字每个都带全套手写故事(fb字段),AI 生故事失败时静默降级:

catch(e){toast('AI 生成失败,已用内置故事:'+(e.message||''));fillStory({...g.fb,src:'内置故事(AI 降级)'});}

对用户来说,故事永远不缺席——这和功能多不多无关,这是可用性的底线。

顺带一提一个隐蔽的坑:项目早期接的是华为云 MaaS,后来切到 DeepSeek 官方 API。已存了旧配置的用户怎么办?我在启动时做了一次静默迁移:

if(/modelarts|deepseek-v4/.test(cfgLLM.base+' '+cfgLLM.model)){cfgLLM.base='https://api.deepseek.com';cfgLLM.model='deepseek-chat';store.set('hl_llm',cfgLLM)}

API Key 原样保留,用户无感切换。凡是动过配置结构的迭代,都欠用户一次自动迁移——这是这次学到的。


四、让字自己写自己:描画动画的秘密

字形有了,下一个问题是怎么呈现。直接切换?那就是 PPT。这个项目的核心体验——“每个字都是一幅画了几千年的画”——要求字要被画出来。

浏览器其实自带这个能力:stroke-dashoffset。原理一句话:把虚线偏移量设为路径总长,描边就"消失"了;再把偏移量动画回 0,描边就会从起点一路"生长"到终点。

functionanimateGlyph(el){el.querySelectorAll('svg path').forEach((p,i)=>{constL=p.getTotalLength();p.style.strokeDasharray=L+' '+L;p.style.strokeDashoffset=L;p.animate([{strokeDashoffset:L},{strokeDashoffset:0}],{duration:Math.min(1100,300+L*2.2),// 路径越长画得越久,封顶 1.1sdelay:120+i*140,// 笔画依次落笔easing:'cubic-bezier(.45,.05,.55,.95)',fill:'both'});});el.querySelectorAll('svg circle').forEach((c,i)=>{// 朱红点睛,弹出来c.animate([{opacity:0,transform:'scale(0)'},{opacity:1,transform:'scale(1)'}],{duration:340,delay:480+i*120,easing:'cubic-bezier(.34,1.56,.64,1)',fill:'both'});});}

三个调了很久的参数:

  • 300+L*2.2:短笔画快、长笔画慢,模拟真实的书写节奏;封顶 1100ms 防止一根超长曲线拖垮整体节奏;
  • delay:120+i*140:笔画之间留 140ms 空隙——这是"运笔"的时间,没有它笔画会同时涌出,像打印机而不是毛笔;
  • 圆点用回弹缓动:cubic-bezier(.34,1.56,.64,1)的 overshoot 让"点睛"有落章的顿感。

内置字的 stage 数据里藏着一个小伏笔:dots数组专门用来表示"朱砂点睛"——太阳的黑点、马的眼睛、羊的眼珠。描画动画里它们最后弹出,恰好复现了"画龙点睛"这个成语的动作本身。


五、声音追着笔画跑:朗读与舞台的时序同步

光有画面还不够。这个 App 的正确打开方式是孩子点「听演变故事」,然后朗读讲到哪个朝代,舞台就演到哪个朝代。

实现上就是一个 token 校验的顺序循环:

asyncfunctionspeakAll(){stopSpeak();stopAuto();constmyToken=speakToken;for(leti=0;i<paras.length;i++){if(speakToken!==myToken)break;// 用户随时打断,后续全部作废paras.forEach(p=>p.classList.toggle('reading',p===paras[i]));showStage(i);// 舞台切到第 i 朝代awaitspeak(txt);// 等这段读完}}

speakToken是整个音频系统的"改朝换代"计数器:任何打断(切字、暂停、点段落单读)都会speakToken++,循环里的旧 token 立刻失效。用整数 token 做异步竞态的守门员,比任何标志位都可靠。

朗读本身是三层容错:Edge 神经语音(8 音色)→ 系统speechSynthesis→ 静默。有个容易被忽略的细节:speechSynthesis.cancel()在部分浏览器上会触发上一个 utterance 的onerror,如果那里挂了 resolve 之外的逻辑就会出现幽灵回调。把onend/onerror都只指向res(),是最省心的防诈尸写法。


六、实测数据:这个"实验室"到底跑多快

光说不练假把式。以下为本机(Windows 11,node tts-proxy.js,浏览器访问http://localhost:8788/)的真实实测。

① 页面加载(单文件 App,含全部 16 个内置字数据与样式,连续 3 次):

次数耗时传输体积
第 1 次0.218s63.4 KB
第 2 次0.209s63.4 KB
第 3 次0.210s63.4 KB

平均0.21s首屏可用——单文件零构建架构的收益直接体现在这:无需打包器、无需请求合并,一个 HTTP 往返带走整个应用。

② 神经语音合成延迟(文本"太阳就是一个圆,中间有个小黑点",16 字,音色"晓晓",语速 85%,连续 3 次):

次数耗时产出音频
第 1 次2.23s21,024 字节 MP3
第 2 次2.13s21,024 字节 MP3
第 3 次2.68s21,024 字节 MP3

平均2.35s拿到完整神经语音。实际体验中按段落合成、读完一段再取下一段,等待被朗读节奏消化,孩子无感知。合成失败时自动回退系统语音,故事流程不中断。

③ AI 造字链路:输入一个新汉字,DeepSeek 一次性返回拼音、本义、三阶段 SVG、故事与测验的完整 JSON(max_tokens 1800),前端校验通过即永久写入 localStorage——同一个字第二次打开是零网络依赖的,这也是"AI 生成一次、本地受用永远"架构的核心收益。


七、十六个字到无限:把造字权交出去

内置 16 个象形字是手工打磨的,质量稳定,但字库永远是死的。真正的分水岭是第二节那套"AI 画字" prompt 成熟之后——「创造新字」功能几乎是白送的:

开启 AI 后,识字页顶部出现一张卡片,输入任意汉字,DeepSeek 一次性吐出全套数据。前端校验通过后,这个字就永久住进 localStorage 的自定义字库,和内置字平起平坐——有描画动画、有朗读、答对测验照样盖"记住了"的朱砂大印。

constG=k=>GLYPHS[k]||CUSTOM[k];// 内置与 AI 字同权constallGlyphs=()=>({...GLYPHS,...CUSTOM});

代码层面只需要一个统一的取字函数,UI 层完全不用区分字的出身。先设计好数据结构,功能就只是往里灌数据——这条朴素规律在这类项目里反复应验。

唯一加的专属逻辑是删除:AI 造的字在实验室右上角有个 ✕,删掉时同步清理学习记录。孩子造了一堆奇怪的字?没问题,造字的快乐本来就包含拆掉重来。


八、和华为云码道结对:模糊需求怎么变成可调参数

最后必须说说这个项目的开发方式:它不是我一个人闷头写的,也不是 AI 一键生成的,而是**"我提需求 + 码道写码 + 我验收"的结对模式**。两个印象最深的片段:

  1. “写字要有运笔感”——这种模糊反馈最难传给机器。码道把它翻译成了第四节那组公式:路径时长300+L*2.2、笔间空隙i*140、点睛回弹曲线。我试听两轮、各微调一次就收敛了。人负责"舒服不舒服"的判断,机器负责把"舒服"参数化,各干各的擅长事。
  2. 一次改动解决三个问题——中途要从 MaaS 切到 DeepSeek,我以为要大改:结果码道给出的方案是前端默认值 + 启动时正则匹配旧配置静默迁移 + 代理端兼容两套环境变量,一次提交同时搞定了切换、老用户兼容、密钥安全。

这轮结对验证了我对 AI 辅助编程的一个判断:正确的用法不是让 AI 写完所有代码,而是人守住产品判断和验收,把实现密度交给它。SVG prompt 的多轮调优尤其如此——我负责说"这批画得不像小孩画的",它负责改需求书里的措辞。


九、美学不是装饰

再记两处非功能的细节,这是整个 App 气质的来源。方向是"宣纸水墨":暖米色纸面、衬线宋体、朱砂红印章。

纸的质感用一段 SVG 噪声内联进 CSS,零图片开销:

--paperbg:var(--paper)url("data:image/svg+xml,%3Csvg ... %3E %3CfeTurbulence type='fractalNoise'baseFrequency='.72'.../%3E %3CfeColorMatrix values='0 0 0 0 .28 ... 0 0 0 .05 0'/%3E ...");

feColorMatrix把噪声的 alpha 压到 0.05——质感的第一要义是克制,噪点能被感觉到、但不能被看见。

印章的仪式感:测验答对后,一枚双线圆章"记住了"带旋转砸下来(rotate(-12deg) scale(2.2)→ 回弹到 1),配合触觉般的动画曲线,这是整个 App 里孩子最期待的 0.55 秒。传统文化 App 的奖励,不应该是星星和糖果,应该是盖章。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询