数字人主题化实践(十):婚庆策划主题的拱门对称布局与多模态感知、端到端编排
用一场婚礼看懂端到端
要理解端到端数字人,最好的类比恰恰是这个主题本身------婚礼现场。

一场婚礼从来不是一个人的独白:宾客在入场、司仪在看流程、签到处在登记、音乐在等 cue 点、新娘在换装。现场每时每刻都在产生信息,而一个合格的统筹(司仪或策划督导)做的事只有两件:持续感知现场 ,然后把感知编排成一段连贯的仪式。感知是多通道的(看、听、问),编排是一条链(从早晨化妆到晚上送客)。
端到端数字人干的是同一件事。老一代方案里,"听"(ASR)、"想"(LLM)、"说"(TTS + 口型 + 动作)是三个独立模块,靠前端当主持人来回传话,谁先说谁后说要自己排;端到端方案把这三段收成一条链,交给一个 Agent 统一调度。婚庆主题之所以适合讲这件事,是因为它的业务流程本身就是一条"策划 → 编排 → 呈现"的链路,页面上的金圈步骤条(需求沟通 → 方案设计 → 团队对接 → 婚礼执行)画的就是这条链。
多模态感知:数字人始终知道"现场在发生什么"
多模态感知解决的是"此刻正在发生什么"。它不是"用户按了一次发送键"这种单点触发,而是持续在线:听通道(麦克风与 ASR)、看通道(摄像头/画面理解)、业务通道(页面上的选择、表单、步骤状态)同时在采集,数字人因此能一直知道现场的状态,而不是等一句话来了才醒。
落到端到端 SDK,最直接的一件事是数字人说话时仍然能听见用户插话 。旧方案里"机器开口"和"用户开口"往往是互斥的------要么数字人说完,要么用户打断后整段重来。端到端 SDK 在这一层做了默认处理:enableClientInterrupt 默认为 true,需要禁用时才显式传 false。业务侧对应的是 ASR 的开始与停止:
ts
// 感知通道的开合由业务侧控制
agent.startASR() // 开始收音
agent.stopASR() // 停止收音
把这条线放到婚庆场景里就很直观:用户一边听梦梦讲"森系套餐包含哪些项",一边想起"我们大概 120 人",可以直接插话补充规模,而不用等整段念完再重来。感知通道不因为有输出就关闭,这是"持续知道现场"的前提,也是端到端相比"三段拼接"最体感上的差别。
组件侧的感知结果最终汇到一个响应式状态上------字幕就是感知回流的可视证据:
ts
watch(() => appState.ui.subTitleText, (val) => {
subtitleText.value = val || "";
});
SDK 感知到"正在说什么",通过 onWidgetEvent 的 subtitle_on 把文本推上来,最终渲染成三行截断的字幕卡:
css
.wedding-subtitle-content {
font-size: 13px;
color: #6e5f44;
line-height: 1.6;
margin: 5px 0 0;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
婚庆的话术普遍比其他主题长(套餐讲解动辄百字),所以视觉上只保留前三行------语音承担完整信息,界面只承担情绪与锚点。这也是一种"感知后的取舍":知道得全,呈现得省。
专属智脑:不绑死任何一家大模型
感知之后要认知,"我是谁、知道什么、应该做什么"------这层归专属智脑。项目在实现上刻意没有把数字人焊死在某一家模型上:
-
LLM_CONFIG_LIST每个主题一套配置,BASE_URL指向https://api.deepseek.com/v1、DEFAULT_MODEL: "deepseek-chat";换成豆包(SUPPORTED_LLM_MODELS里就列了doubao-1-5-pro-32k-250115)或 ChatGPT,只需要替换这三个字段。 -
反过来,也可以直接接魔珐星云自研智脑:主打低延迟、强逻辑、场景化理解,且数据在自家链路上,安全可控。对婚庆这种涉及宾客信息、档期与报价的场景,"数据不出门"本身就是一项产品能力。
主题 10 的智脑人格由 SYSTEM_PROMPT 定义:
ts
// LLM_CONFIG_LIST 中主题 10 的 SYSTEM_PROMPT(节选)
`你是婚庆策划智能服务平台数字人,专注婚礼主题设计、流程编排指导、
场地推荐对比、预算分配建议、婚庆团队对接、婚品选购咨询。
你的回答需要符合以下要求:
1. 语言浪漫贴心,表述周到细致,贴合婚礼策划师形象;
2. 依据婚庆行业流程与流行趋势作答,信息真实准确,不虚构内容;
......
4. 行文简洁凝练,核心内容控制在300字以内;`
换模型不换人格,这是把"智脑"和"模型供应商"解耦的直接收益:策划师梦梦的专业度来自提示词与知识库,不来自某一次 API 调用。
端到端编排:ask() 一次串起感知与表达
把感知到的、认知出的结果,变成一段语言、口型、表情、眼神、手势同步的"表演",就是多模态表达 ;而把"感知 → 认知 → 表达"接成一条链,是端到端。
接入层已经整体迁移到端到端 SDK XingyunAvatarAgent(全局 window.XingyunAvatarAgent)。src/services/avatar.ts 的构造参数如下:
ts
const containerEl = document.getElementById(this.containerId)
if (!containerEl) {
throw new Error(`数字人容器 #${this.containerId} 未找到`)
}
const constructorOptions = {
container: containerEl, // 传 DOM 元素,不再是 containerId 字符串
appId: APP_ID,
appSecret: APP_SECRET,
enableDebugger: false,
gatewayServer: url.toString(),
onWidgetEvent: (event: any) => {
if (event.type === 'subtitle_on') onSubtitleOn(event.text)
else if (event.type === 'subtitle_off') onSubtitleOff()
},
onStateChange,
onMessage: async (error: any) => { /* ... */ },
agentCallbacks: {
onError(error: any) { /* ... */ },
onConversationChange(event: any) {
if (event?.state === 'completed') {
console.log('[Conversation] 一轮对话结束')
}
},
onAgentStateChange(state: string) { /* ... */ },
},
}
const agent = new window.XingyunAvatarAgent(constructorOptions)
await agent.init({ onDownloadProgress, onClose })
编排的核心是这一个方法:
ts
agent.ask(text) // 一次调用:LLM 推理 → 语音 → 口型 → 表情 → 动作
投递一句文本,整条链在后端跑完,回来的是一个已经在说话、带着相应神态的数字人。老方案里前端要做的事------等上一段说完、把长句切成短句、拼 SSML、判断"是不是第一句"------在端到端模式下都不必由业务层操心。可以参考现有服务层的流式写法,体会两代方案的差别:AppStore.sendMessage() 里要自己 splitSentence(buffer) 切句、generateSSML() 造 SSML,再按"第一句 speak(ssml, true, false)、中间句 speak(ssml, false, false)、最后 speak(finalSsml, false, true)"来区分起止:
ts
const ssml = generateSSML(arr[0] || '')
if (isFirstChunk) {
avatar.instance.speak(ssml, true, false) // 第一句
isFirstChunk = false
} else {
avatar.instance.speak(ssml, false, false) // 中间句
}
// ...
avatar.instance.speak(generateSSML(''), false, true) // 收尾
agent.ask(text) 相当于把这一段编排逻辑收进了 SDK 内部。断连依旧是 agent.destroy('user_disconnect')。
编排落到代码:婚庆主题的调用链
拱门画布与字幕流转
组件不持有 SDK 生命周期,只注入全局状态并把容器 id 绑到模板:
ts
const appState = inject<AppState>("appState")!;
const appStore = inject<AppStore>("appStore")!;
const containerId = ref(avatarService.getContainerId());
与书房"纸面画布"不同,这里把数字人容器整体嵌进一个拱门龛(wedding-avatar-arch),下方挂"铭牌"显示身份与状态。initAvatar 与外层 store 的 connectAvatar() 配合:store 内部把 onSubtitleOn / onSubtitleOff / onStateChange 三个回调接到 appState,组件只等 await 结果来点灯、收遮罩。
三条话术触发线
婚庆主题比书房多了一条交互线------顶部金圈步骤条。goToStep 切换阶段并播报指引:
ts
const goToStep = (idx: number) => {
currentStep.value = idx;
const step = stepList.value[idx];
ElMessage.success(`已切换到步骤:${step.title}`);
const tipText = `当前进入${step.title}阶段,${step.desc}。请根据指引完成相关操作,我将全程为您提供专业的婚礼策划指导。`;
speakText(tipText);
};
stepList 的四段就是那条"从策划到呈现"的业务链:
ts
const stepList = ref([
{ title: "需求沟通", desc: "了解婚礼设想与预算" },
{ title: "方案设计", desc: "定制主题与流程方案" },
{ title: "团队对接", desc: "锁定四大金刚与场地" },
{ title: "婚礼执行", desc: "全程督导现场执行" },
]);
六枚圆形"印章"快捷服务走 selectQuickService,逻辑与书房的便签相同:记录选中态、拼固定话术、交 speakText 播报。所有入口继续收敛到同一台"编排器",与端到端 SDK"一次调用管完一轮"的思路一致。
表单驱动的步骤状态机
底部预约表单提交后会推进顶部步骤条,把业务状态机直接交给用户看:
ts
const submitService = () => {
if (!serviceForm.name || !serviceForm.phone || !serviceForm.type) {
ElMessage.warning("请填写新人姓名、联系电话和婚礼形式");
return;
}
ElMessage.success("婚礼预约提交成功!策划师梦梦将尽快与您对接。");
const submitText = `你的婚礼预约已提交成功,${serviceForm.name},我将根据你的婚礼形式和预算规划,为你量身定制专属婚礼方案。策划师将在24小时内与你联系,请保持电话畅通。`;
speakText(submitText);
if (currentStep.value < stepList.value.length - 1) {
currentStep.value = currentStep.value + 1;
}
// ......清空表单字段
};
这是端到端编排思想在业务层的复用:用户的每一次动作都推动链路往前走一格 ------提交表单 → 播报确认(表达)→ 步骤前进(状态)→ 步骤条上的金圈由数字变 ✓(completed 类)。感知、认知、表达在同一个 handler 里闭环。
布局与 CSS:把多方协同画成一座拱门
拱门花窗与垂落纱幔
整页最核心的视觉符号是一座"教堂拱门":
css
.wedding-arch-frame {
position: relative;
width: min(660px, 94%);
border: 3px double #c9a96e;
border-radius: 999px 999px 14px 14px;
background: linear-gradient(180deg, #fffdf8 0%, #fdf6ec 70%, #f9efe0 100%);
box-shadow:
0 10px 30px rgba(184, 166, 126, 0.25),
inset 0 0 26px rgba(212, 197, 160, 0.28);
padding: 44px 40px 16px;
text-align: center;
}
border-radius: 999px 999px 14px 14px 用四值语法,上半两个超大半径构成拱顶、下半小圆角落地;用 999px 是为了让圆弧半径始终取宽高较小边的一半,配合 min(660px, 94%) 自适应任意容器宽。3px double 正好利用 border-width ≥ 3px 的下限值画出细金双线;inset 内阴影铺一层浅金光晕,模拟缎面内衬反光。两侧垂落的纱幔只用一个伪元素完成:
css
.wedding-arch-frame::before,
.wedding-arch-frame::after {
content: '';
position: absolute;
top: -2px;
width: 64px;
height: 140px;
background: linear-gradient(180deg, rgba(240, 224, 232, 0.95) 0%, rgba(240, 224, 232, 0.4) 55%, transparent 100%);
border-radius: 0 0 50% 50% / 0 0 36px 36px;
z-index: -1;
pointer-events: none;
}
.wedding-arch-frame::before { left: -34px; }
.wedding-arch-frame::after { right: -34px; }
斜杠语法 0 0 50% 50% / 0 0 36px 36px 分别指定水平与垂直半径,底边被拉成下垂的布料弧线;z-index: -1 把纱幔垫到主体后面,做出"挂在框后"的层次。数字人拱门龛复用同一套配方,再加 ::after 的 inset: 5px 虚线圈做内侧花环描边。
中央对称三列与丝带角标
主区是"左翼 --- 中央仪式台 --- 右翼"的对称结构:
css
.wedding-ceremony { flex: 1; display: flex; gap: 18px; overflow: hidden; min-height: 0; }
.wedding-wing { flex: 1; min-width: 330px; display: flex; flex-direction: column; overflow-y: auto; }
.wedding-altar { width: 440px; flex-shrink: 0; display: flex; flex-direction: column; overflow-y: auto; }
两翼 flex: 1 均分剩余宽度,中央仪式台固定 440px 且 flex-shrink: 0------数字人容器永远不被压缩,这是围绕"仪式台居中"这个视觉中心做的取舍。对称感在数据层完成:同一份 productList 用 slice(0, 2) 渲染进左翼、slice(2) 渲染进右翼,四个套餐天然两两对列。min-height: 0 是 flex 纵向滚动能生效的关键。
套餐卡左上角的"森系/中式/海岛/晚宴"角标是一条用多边形切出的燕尾丝带:
css
.wedding-package-ribbon {
position: absolute;
top: -6px;
left: 14px;
height: 22px;
background: linear-gradient(135deg, #c9a96e 0%, #b8965a 100%);
clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 68%, 0 100%);
color: #fffdf8;
letter-spacing: 3px;
}
一条丝带的全部几何信息只需要一个 clip-path: polygon(...),五个顶点在底边切出 V 形燕尾,不需要背景图。
印章、花瓣与请柬
六枚圆形"印章"按钮用 inset 阴影叠出双圈描边:
css
.wedding-seal {
width: 76px;
height: 76px;
border-radius: 50%;
border: 2px solid #c9a96e;
background: radial-gradient(circle at 35% 30%, #fffdf8 0%, #f7efe2 100%);
box-shadow:
inset 0 0 0 3px #fffdf8,
inset 0 0 0 4px #d8cbab,
0 4px 12px rgba(184, 166, 126, 0.22);
}
inset 0 0 0 3px 白圈 + inset 0 0 0 4px 金细线在边框内侧再勾两道圈,加上左上高光,做出火漆印的立体感。背景漂浮花瓣是六个实例的错峰调度,只动 transform 与 opacity,容器设 pointer-events: none 与 overflow: hidden 与交互、布局完全隔离:
css
.wedding-petal { border-radius: 80% 0 80% 0; transform: rotate(45deg); animation: wedding-fall linear infinite; }
.wedding-petal.p1 { left: 8%; animation-duration: 11s; animation-delay: 0s; }
.wedding-petal.p6 { left: 92%; animation-duration: 15s; animation-delay: 8s; width: 9px; height: 9px; }
FAQ 区被做成一张"请柬"(wedding-invitation):居中标题 ❀ 常见问题 ❀、--- ❦ --- 分隔线,每条问答展开时箭头 ❯ 旋转 90 度,toggleFaq 仍是单例手风琴。
数据缎带
"服务新人 3200+、签约场地 86 处"这组数据用斜体衬线加金色渐变文字:
css
.wedding-stat-value {
font-family: Georgia, 'Times New Roman', serif;
font-style: italic;
font-weight: 700;
font-size: 28px;
background: linear-gradient(135deg, #c9a96e 0%, #e8d5a8 55%, #c9a96e 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
Georgia italic 自带手写笔势,是系统字体里成本最低的"手写感"方案;金色渐变文字需要背景渐变 + -webkit-background-clip: text + -webkit-text-fill-color: transparent 三件套,再补标准 background-clip: text 兜底。数据带上下两条 2px 金线(border-top/bottom: 2px solid #c9a96e),像缎带压在页面上------这也是类名 wedding-stats-ribbon 的由来。全篇配色收敛为樱白系(#fdf9f6 / #fffdf8)与香槟金系(#c9a96e / #b8965a / #e8d5a8),玫瑰粉(#f0e0e8)只出现在纱幔尾部、印章选中态和花瓣上做点缀。
数字人的知识底座:策划师该懂什么
数字人的回答质量取决于行业参数的颗粒度。这套界面的知识底座有四块,其中两块直接写进了关键词话术。
婚礼当天流程时间轴。 经典单日婚礼:08:00 化妆造型(新娘妆造通常需 2--3 小时,要给摄影预留晨袍拍摄窗口)→ 10:00 接亲游戏(堵门环节控制在 30--60 分钟)→ 12:00 酒店迎宾 → 14:18 仪式开始(宣誓、交换戒指、敬茶,整场 30--40 分钟)→ 17:00 晚宴开席 → 19:00 敬酒与送客。编排原则是"锚点定死、环节之间留 10--15 分钟弹性缓冲";"14:18"这类吉时锚点是中式习俗,策划要做的是把吉时反推成各环节的截止时间。
"四大金刚"分工。 行业把四类核心人员合称四大金刚:司仪负责现场节奏与情绪调动,需提前对流程台词、主持彩排;摄影负责平面记录,抓拍不可重来的瞬间;摄像负责动态影像,通常双机位起;化妆负责早晚两套造型与全程跟妆。锁定顺序有讲究:主持与摄影摄像的档期最刚性(好档期常提前一年订满),建议婚期确定后第一时间锁定并支持看片试音;化妆师相对弹性但必须试妆。
场地区域规划。 一个标准宴会场地分四个功能区:迎宾区(签到台、婚纱照展示、合影背景)、仪式区(舞台、T 台或通道、观礼席)、宴会区(主桌正对舞台、居通道末端)、互动区(甜品台、游戏区)。规划原则是动线分离:新娘换装/补妆路线与宾客入场路线不能交叉。户外场地必须配雨备方案------提前确认室内备用厅或搭建透明帐篷,婚礼前 3 天根据天气预报决定是否切换,机位方案同步调整。
预算分配逻辑。 行业通行的锚点是:场地餐饮约 45%、布置花艺 20%、四大金刚 18%、婚纱礼服 8%、杂项机动 9%。用法不是死套比例,而是"锚定比例、按总预算缩放、再标注弹性项":可压缩项有当季花材、布置物料复用;不建议压缩的是摄影摄像------布置花的是当天的心思,影像留的是十年的记忆。支付节奏上对齐筹备节点:签约付 30% 定金锁档期与团队,方案确认付 40%,婚礼结束验收后付尾款 30%。
小结:可迁移到其他主题的经验
-
感知通道不能因为"正在输出"而关闭。 端到端 SDK 默认
enableClientInterrupt为 true、startASR/stopASR由业务控制,让数字人在说话时仍能听见插话------"持续知道现场"是编排的前提。 -
智脑与模型供应商解耦。 换
BASE_URL/DEFAULT_MODEL即可在豆包、DeepSeek、ChatGPT 之间切换,也可接魔珐星云自研智脑;人格由 SYSTEM_PROMPT 与知识库定义,不依赖某一次调用。 -
端到端把编排收进一次调用。
agent.ask(text)一次串起 LLM → 语音 → 口型 → 表情 → 动作,前端不再自己切句、拼 SSML、判断首尾句,onConversationChange的state === 'completed'提供了原生的轮次边界。 -
对称布局在数据层实现。
productList.slice(0, 2)/slice(2)把同一份数据切进左右两翼,模板不为对称写任何特判。 -
把业务状态机交给用户看。 步骤条绑定
currentStep,表单提交成功即推进步骤------"需求沟通 → 方案设计 → 团队对接 → 婚礼执行"被可视化为可点击的状态机,这既是产品表达,也是端到端编排在界面上的投影。