一、项目背景

随着大模型能力的不断增强,传统的网页客服已经不再局限于“文字问答”。如果将大语言模型与 3D 数字人结合起来,就可以实现一个能够“听懂需求、生成回答、语音播报、表情动作同步”的智能导购系统。

本项目尝试实现一个面向手机门店场景的 AI 数字导购员。用户可以输入自己的购机需求,例如预算、品牌偏好、拍照、游戏、续航、轻薄等要求,系统会调用大模型生成口语化导购回复,并驱动 3D 数字人进行语音播报和动作表达。

最终目标是实现一个类似线下门店导购的交互体验:

用户输入需求,AI 负责分析和推荐,数字人负责自然表达。


二、项目最终效果说明

项目页面整体分为两个区域:

左侧是 3D 数字人展示区域,用于加载魔珐星云数字人。

右侧是聊天交互区域,包括对话记录、快捷需求按钮和输入框。

用户可以直接输入:

预算3000元,想要拍照好一点的手机

也可以点击快捷标签,例如:

3000元拍照手机
4000元游戏手机
长辈用机
轻薄安卓手机

AI 会先收集用户的购机信息,例如预算、品牌偏好、日常用途和核心需求。如果信息不完整,AI 会继续追问;如果信息足够,则推荐 2 到 3 款适合的手机,并给出推荐理由和避坑提醒。


三、实际效果图


四、技术选型

本项目主要使用以下技术:

技术 作用
HTML / CSS / JavaScript 前端页面与交互逻辑
Node.js 搭建本地后端服务
Express 提供后端 API 接口
dotenv 读取本地环境变量
DeepSeek API 生成导购回复
魔珐星云具身驱动 JS SDK 驱动 3D 数字人说话、表情和动作
PyCharm 项目开发工具

整体思路是:

前端负责展示页面、接收用户输入、播放数字人。

后端负责安全调用 DeepSeek,不把大模型密钥暴露在浏览器里。

魔珐 SDK 负责加载数字人,并根据 AI 回复文本进行语音合成、口型同步和动作驱动。


五、项目结构设计

项目整体结构如下:

ai-phone-shopping-guide/
├── index.html        # 前端页面
├── server.js         # Node.js 后端服务
├── package.json      # 项目依赖配置
├── .env              # 环境变量配置,不提交到公开仓库
└── .gitignore        # Git 忽略配置

六、整体实现流程

整个项目可以拆成四个核心步骤:

第一步,搭建前端页面,包括数字人区域和聊天区域。

第二步,使用 Express 搭建后端接口,用于调用 DeepSeek。

第三步,前端调用后端接口获取 AI 回复。

第四步,将 AI 回复传给魔珐 SDK,驱动数字人说话。

整体流程如下:

用户输入购机需求
        ↓
前端发送请求到本地后端 /api/chat
        ↓
后端携带系统提示词请求 DeepSeek
        ↓
DeepSeek 返回导购回复
        ↓
前端显示回复
        ↓
魔珐 SDK 驱动数字人口播回复

七、前端页面设计

前端页面主要分为两个区域:

左侧是数字人容器:

<div class="avatar-box">
  <div id="sdk"></div>
</div>

右侧是聊天区域:

<div class="chat-box">
  <div class="header">
    <h1>AI 手机数字导购员</h1>
    <p>我会根据预算、品牌偏好、用途和核心需求,为顾客推荐合适机型。</p>
  </div>

  <div class="chat-log" id="chatLog"></div>

  <div class="quick-tags">
    <button class="tag">3000元拍照手机</button>
    <button class="tag">4000元游戏手机</button>
    <button class="tag">长辈用机</button>
    <button class="tag">轻薄安卓手机</button>
  </div>

  <div class="input-area">
    <input id="userInput" placeholder="输入你的购机需求" />
    <button id="sendBtn">发送咨询</button>
  </div>
</div>

页面布局采用左右分栏:

左侧固定宽度,用于显示数字人。

右侧自适应宽度,用于聊天交互。

同时加入了移动端适配,当屏幕较窄时,页面会变成上下布局。


八、魔珐星云 JS SDK 接入

本项目使用的是魔珐星云具身驱动 JS SDK。

首先需要在页面中引入 SDK:

<script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script>

然后创建数字人实例。

这里需要特别注意,SDK 文档中使用的是 containerId,而不是普通的 DOM 对象。

核心结构如下:

const avatarSdk = new XmovAvatar({
  containerId: "#sdk",
  appId: "你的魔珐AppID",
  appSecret: "你的魔珐AppSecret",
  gatewayServer: "https://nebula-agent.xingyun3d.com/user/v1/ttsa/session",
  hardwareAcceleration: "prefer-hardware",

  onMessage(message) {
    console.log("SDK message:", message);
  },

  onStateChange(state) {
    console.log("SDK state:", state);
  },

  onStatusChange(status) {
    console.log("SDK status:", status);
  },

  onVoiceStateChange(status) {
    console.log("SDK voice status:", status);
  }
});

初始化数字人时,需要调用 init 方法:

await avatarSdk.init({
  initModel: "normal",
  onDownloadProgress(progress) {
    console.log("数字人资源加载进度:", progress);
  }
});

驱动数字人说话时,使用 speak 方法:

avatarSdk.speak("欢迎使用 AI 手机数字导购员", true, true);

这里的三个参数分别表示:

第一个参数:要播报的文本或 SSML
第二个参数:是否是本次说话开始
第三个参数:是否是本次说话结束

在本项目中,我们主要使用非流式方式,因此通常写成:

avatarSdk.speak(text, true, true);

页面关闭时,建议释放 SDK 连接:

window.addEventListener("beforeunload", function () {
  if (avatarSdk && avatarSdk.destroy) {
    avatarSdk.destroy();
  }
});

这样可以避免数字人连接没有释放,导致并发房间占用。


九、DeepSeek 后端接口设计

为了避免 DeepSeek API Key 暴露在前端,本项目没有在 index.html 中直接请求 DeepSeek,而是通过 Node.js 后端中转。

前端只请求自己的后端接口:

fetch("/api/chat", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    userText,
    history
  })
});

后端再读取 .env 中的 DeepSeek Key,去请求 DeepSeek 接口。

后端核心框架如下:

import express from "express";
import dotenv from "dotenv";

dotenv.config();

const app = express();
app.use(express.json());

const DEEPSEEK_API_KEY = process.env.DEEPSEEK_API_KEY;
const DEEPSEEK_URL = process.env.DEEPSEEK_URL;

app.post("/api/chat", async (req, res) => {
  const { userText, history = [] } = req.body;

  const messages = [
    {
      role: "system",
      content: buildSystemPrompt()
    },
    ...history,
    {
      role: "user",
      content: userText
    }
  ];

  const response = await fetch(DEEPSEEK_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${DEEPSEEK_API_KEY}`
    },
    body: JSON.stringify({
      model: "deepseek-chat",
      messages
    })
  });

  const data = await response.json();

  res.json({
    reply: data.choices[0].message.content
  });
});

app.use(express.static("."));

app.listen(3000, () => {
  console.log("服务已启动:http://localhost:3000");
});

这里没有放完整代码,只展示了核心框架。

实际开发时还需要加入错误处理,例如:

if (!DEEPSEEK_API_KEY) {
  return res.status(500).json({
    error: "服务端未配置 DEEPSEEK_API_KEY"
  });
}

还需要处理 DeepSeek 返回为空、接口请求失败等情况。


十、环境变量 .env 配置

.env 是环境变量配置文件,用来保存不适合写在前端代码中的敏感信息。

本项目中的 .env 大致如下:

DEEPSEEK_API_KEY=你的DeepSeek密钥
DEEPSEEK_URL=https://api.deepseek.com/v1/chat/completions
PORT=3000

同时建议创建 .gitignore

node_modules
.env

这样可以避免依赖目录和密钥文件被误提交。


十一、系统提示词设计

本项目的关键点之一是系统提示词。

因为我们要做的是“手机导购员”,不是普通聊天机器人,所以需要给大模型设置清晰的角色和规则。

系统提示词大致包含以下约束:

你是线下门店专业手机导购数字人,沟通温和、自然、不强行推销。

你需要优先补齐用户缺失信息,每次只问一个问题。

信息收集顺序为:
预算
品牌偏好
日常用途
核心需求

核心需求包括:
游戏
拍照
续航
轻薄
护眼
系统流畅
老人使用
学生使用

当信息足够时,推荐 2 到 3 款机型。

每款机型需要说明:
大致价格段
核心卖点
适配理由

最后给出购机避坑提醒。

输出需要口语化,适合数字人口播。
不要使用 Markdown。
不要输出表格或复杂符号。

这样设计的目的是让 AI 回复更像真实导购,而不是机械地罗列参数。

例如用户只输入:

你好

AI 不应该直接推荐手机,而应该回复类似:

你好呀,欢迎来看手机。请问你这次购机大概的预算是多少呢?

如果用户输入:

预算3000,主要拍照,想要安卓

AI 可以继续追问品牌偏好,或者在信息足够时直接给出推荐。


十二、前端聊天逻辑

前端聊天逻辑主要包含四个步骤:

第一步,获取用户输入。

第二步,将用户消息显示到页面。

第三步,请求后端接口获取 AI 回复。

第四步,将 AI 回复显示到页面,并交给数字人口播。

核心结构如下:

async function sendChat() {
  const text = userInput.value.trim();

  if (!text || isSending) {
    return;
  }

  addMsg(text, true);
  userInput.value = "";

  setSendingState(true);

  const reply = await getAiReply(text);

  addMsg(reply, false);

  await speakByAvatar(reply);

  setSendingState(false);
}

为了避免用户连续点击按钮导致重复请求,可以加入状态控制:

function setSendingState(sending) {
  isSending = sending;
  sendBtn.disabled = sending;
  sendBtn.innerText = sending ? "思考中..." : "发送咨询";
  userInput.disabled = sending;
}

这样用户体验会更稳定。


十三、数字人播报逻辑

数字人播报封装成一个独立方法:

async function speakByAvatar(text) {
  if (!isAvatarReady || !avatarSdk || !text) {
    return;
  }

  try {
    avatarSdk.speak(text, true, true);
  } catch (error) {
    console.error("数字人播报失败:", error);
  }
}

如果数字人没有初始化成功,系统仍然允许文字聊天继续进行。

这是一个很重要的容错设计。

因为数字人 SDK 可能受到网络、授权、浏览器兼容性、资源加载等因素影响,如果数字人失败就让整个系统不可用,用户体验会比较差。

所以本项目采用:

数字人成功初始化:文字 + 数字人口播
数字人初始化失败:保留文字聊天功能

十四、开发过程中遇到的问题与解决方法

1. npm 无法识别

在 PyCharm 终端中执行:

npm run dev

如果出现:

无法将 “npm” 项识别为 cmdlet、函数、脚本文件或可运行程序的名称

说明当前环境找不到 npm。

通常原因是没有安装 Node.js,或者 Node.js 没有加入系统环境变量。

解决方法:

安装 Node.js LTS 版本。

安装完成后重新打开 PyCharm。

然后执行:

node -v
npm -v

如果能够显示版本号,说明安装成功。


2. 页面能打开,但数字人黑屏

如果页面右侧聊天正常,但左侧数字人是黑屏,通常说明魔珐 SDK 初始化失败。

可能原因包括:

AppID 或 AppSecret 没有填写真实值
SDK 文件没有加载成功
数字人应用配置不正确
当前访问地址不符合 SDK 要求
网络无法访问数字人资源

本项目中需要特别注意,魔珐 SDK 文档中写明:

SDK 支持 localhost 或 https 访问

因此本地调试时建议使用:

http://localhost:3000

不要使用:

http://127.0.0.1:3000
http://局域网IP:3000
直接双击 index.html

3. SDK 参数写错导致初始化失败

一开始容易把数字人容器写成:

container: document.getElementById("sdk")

但根据当前 SDK 文档,应该使用:

containerId: "#sdk"

同样,数字人说话方法也需要根据文档使用:

avatarSdk.speak(text, true, true);

而不是其它旧版本写法。


4. DeepSeek 密钥不能放前端

前端代码是可以被浏览器查看的。

如果把大模型密钥写在 index.html 中,例如:

const API_KEY = "真实密钥";

那么别人打开浏览器开发者工具就可能看到。

因此本项目采用 Node.js 后端代理方式:

前端 → 本地后端 → DeepSeek

前端不直接接触 DeepSeek API Key。


5. 聊天历史丢失问题

如果每次请求只发送用户当前输入,大模型就不知道前面聊过什么。

因此前端维护了一个简单的 chatHistory

const chatHistory = [];

每次用户提问和 AI 回复后,都保存到历史中:

chatHistory.push({
  role: "user",
  content: userText
});

chatHistory.push({
  role: "assistant",
  content: reply
});

请求后端时,将历史一起传过去:

body: JSON.stringify({
  userText,
  history: chatHistory
})

后端再将历史拼接进 messages 中。

为了避免历史过长,可以只保留最近若干轮对话。


十五、运行步骤

1. 安装依赖

在项目目录下执行:

npm install

2. 配置环境变量

创建 .env 文件,填写 DeepSeek 配置:

DEEPSEEK_API_KEY=你的DeepSeek密钥
DEEPSEEK_URL=https://api.deepseek.com/v1/chat/completions
PORT=3000

3. 配置魔珐应用信息

在前端代码中填写魔珐星云后台创建的驱动应用信息:

const MOFA_APP_ID = "你的魔珐AppID";
const MOFA_APP_SECRET = "你的魔珐AppSecret";

4. 启动项目

执行:

npm run dev

启动后浏览器访问:

http://localhost:3000

十六、项目测试示例

可以输入以下内容测试项目效果。

测试:

你好

预期效果:

AI 友好回应,并询问购机预算。

测试拍照需求:

预算3000元,想买拍照好一点的安卓手机

预期效果:

AI 继续确认品牌偏好或核心需求,也可能直接根据已有信息推荐机型。

测试游戏需求:

预算4000左右,主要打游戏,续航也要好

预期效果:

AI 推荐偏性能、散热和续航的机型。

测试长辈用机:

给长辈买,要求屏幕大、续航好、系统简单

预期效果:

AI 推荐适合长辈使用的大屏、长续航、操作简单的手机。

十七、项目优化方向

当前项目已经实现了基础的 AI 数字导购能力,但如果要继续完善,还可以从以下几个方向优化。

1. 接入真实商品库

目前手机推荐主要依赖大模型知识。

如果要用于真实门店,最好接入商品库,例如:

门店库存
商品价格
当前促销活动
商品参数
商品上下架状态

这样 AI 推荐时就不会推荐已经停售或门店无货的机型。

可以将商品库设计为 JSON、数据库或 ERP 接口。

2. 增加流式回复

当前项目采用的是一次性返回完整回复。

后续可以使用流式接口,让 AI 边生成边返回,再结合魔珐 SDK 的流式 speak 能力,实现更自然的实时口播。

流式调用时需要控制:

第一次 speak:is_start = true
中间 speak:is_start = false,is_end = false
最后 speak:is_end = true

3. 增加语音输入

当前用户通过文字输入购机需求。

后续可以加入浏览器语音识别能力,让用户直接说:

我想买一台三千左右拍照好看的手机

系统识别成文字后再发送给大模型。

这样更接近真实门店导购体验。

4. 增加商品卡片展示

目前 AI 回复主要是文字。

后续可以在右侧聊天区展示商品卡片,包括:

手机图片
价格
核心参数
推荐理由
购买按钮

也可以结合魔珐 SDK 的 Widget 能力,在数字人播报过程中展示图片、字幕或视频内容。

5. 优化数字人状态

可以根据交互流程切换数字人状态:

用户输入时:listen
AI 思考时:think
AI 回复时:speak
空闲时:interactive_idle

这样数字人的表现会更自然。


十八、总结

本项目实现了一个基于 DeepSeek 和魔珐星云具身驱动 SDK 的 AI 手机数字导购员。

项目核心价值在于:

大模型负责理解和生成导购话术
数字人负责语音、口型、动作和表情表达
前端负责交互展示
后端负责安全调用大模型接口

通过这个项目,可以初步体验到“大模型 + 3D 数字人”在智能导购场景中的落地方式。

相比传统文字客服,数字人导购更直观、更自然,也更适合门店大屏、展厅导览、产品介绍等场景。

当然,当前版本仍然是一个 Demo 原型。如果要进一步应用到真实业务中,还需要接入真实商品库、完善权限安全、优化语音输入、增加商品卡片和部署到 HTTPS 环境。

整体来看,这个项目非常适合作为学习大模型应用开发、数字人 SDK 接入、前后端联调和智能导购场景落地的实践案例。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐