数字人主题化实践(十):婚庆策划主题的拱门对称布局与多模态感知、端到端编排

数字人主题化实践(十):婚庆策划主题的拱门对称布局与多模态感知、端到端编排

用一场婚礼看懂端到端

要理解端到端数字人,最好的类比恰恰是这个主题本身------婚礼现场。

一场婚礼从来不是一个人的独白:宾客在入场、司仪在看流程、签到处在登记、音乐在等 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%。

小结:可迁移到其他主题的经验

  1. 感知通道不能因为"正在输出"而关闭。 端到端 SDK 默认 enableClientInterrupt 为 true、startASR/stopASR 由业务控制,让数字人在说话时仍能听见插话------"持续知道现场"是编排的前提。

  2. 智脑与模型供应商解耦。 换 BASE_URL/DEFAULT_MODEL 即可在豆包、DeepSeek、ChatGPT 之间切换,也可接魔珐星云自研智脑;人格由 SYSTEM_PROMPT 与知识库定义,不依赖某一次调用。

  3. 端到端把编排收进一次调用。 agent.ask(text) 一次串起 LLM → 语音 → 口型 → 表情 → 动作,前端不再自己切句、拼 SSML、判断首尾句,onConversationChange 的 state === 'completed' 提供了原生的轮次边界。

  4. 对称布局在数据层实现。 productList.slice(0, 2) / slice(2) 把同一份数据切进左右两翼,模板不为对称写任何特判。

  5. 把业务状态机交给用户看。 步骤条绑定 currentStep,表单提交成功即推进步骤------"需求沟通 → 方案设计 → 团队对接 → 婚礼执行"被可视化为可点击的状态机,这既是产品表达,也是端到端编排在界面上的投影。

相关推荐
snow@li1 小时前
全景深度解析:大模型的思考力与内置知识库,是否天然绑定?
ai
mjhcsp1 小时前
DeepSeek V4.1 Flash 快速上手与实战指南
ai·语言模型·ai编程
恩喜玛生物2 小时前
AIDD系列02:ProteinMPNN序列设计,从已知骨架到可检验的肽候选
python·ai
2601_949695592 小时前
GPT-6.1 Terra正式发布:电脑操作更强,API降价50%
人工智能·ai
奇牙coding2 小时前
qwen3.7-max 报 400 context_length_exceeded 怎么办?删了聊天记录为什么还超限?
ai
警醒与鞭策14 小时前
Codex桌面版与Claude桌面版使用设置
android·unity·ai·性能优化
择势15 小时前
从零散提效到体系化协作:iOS 团队 AI 研发工作流完整实践
ai编程
奇牙coding16 小时前
GPT-5.5 报 401 invalid_api_key 怎么办?换密钥前按 4 层排查请求头、项目权限和端点
数据库·gpt·ios·ai
leeguoo16 小时前
让 Claude Code / Codex 用上你已登录的 Chrome
前端·chrome·ai编程