一、大模型已经很强了,但它还困在对话框里

过去一年,国产大模型的发展速度超出了很多人的预期。DeepSeek、Qwen 等模型在推理、编码、多轮对话上的表现,已经可以和海外主流模型正面竞争。我自己在日常开发中,已经习惯把很多问题直接丢给 DeepSeek,得到的回复质量常常让我感到意外。

但用着用着,一个问题越来越清晰:这些模型再强,它们跟用户的交互方式,仍然是单调的文本框**,**缺少面对面沟通的氛围感。

你打字,它回字。你有情绪,它用文字安慰你。你说"我今天好累",它回"辛苦了,记得休息"——逻辑没毛病,内容也到位。但这个体验,说实话,跟我对着搜索引擎打字没什么本质区别。

这在一些场景里是可以接受的。但在另一些场景里——比如情绪陪伴、心理咨询、教育辅导、门店接待——纯文字的交互方式,就像让一个世界顶级的心理咨询师只通过短信跟来访者沟通。话术逻辑完整,但无法传递情绪温度。

在这里插入图片描述

大模型补齐了 AI 认知大脑,但想要落地全终端真实场景,必须配套完整具身交互智能体系**——需要被看见、能说话、会表达、能实时回应。**

这就是我最近做的一个实验想探索的问题。


二、一个实验:给 DeepSeek 装上身体

我的想法很简单:把 DeepSeek 的对话能力,和魔珐星云具身交互智能 SDK 接在一起,看看会发生什么。

具体来说,我搭建了一个全天候 AI 情感陪伴导师的 Demo——一个 3D 数字人形象,由 DeepSeek 驱动对话,能根据用户输入的情绪状态,用合适的语气和节奏做出回应。

在这里插入图片描述

技术选型是这样的:

能力层 方案 说明
Trea AI 开发工具 Skill技能 + 项目描述
实现0代码生成整套逻辑
对话理解与生成 DeepSeek Chat API 负责理解用户情绪、生成专业疏导回复
上下文记忆 前端维护 10 轮对话历史 保障连续对话的连贯性
3D 数字人渲染与语音合成
魔珐星云具身交互智能 SDK
提供数字人形象、TTS、表情动作同步

为什么选这个组合?DeepSeek 的对话质量我是信得过的,关键在于如何把它的文字输出"具身化"——让一段文字不只是显示在屏幕上,而是由一个 3D 数字人用温柔的语气说出来,配合相应的表情和动作。

DeepSeek太强啦,我工作中一般都会用到 DeepSeek-V4-Pro

在这里插入图片描述

魔珐星云在这件事上是比较直接的选择。它的 SDK 封装了从数字人渲染、语音合成到动作驱动的一整套链路,用一个 sdk.speak() 就能驱动数字人开口说话,不需要自己处理底层渲染和音频管线。


三、Demo 搭建过程

3.1 Trae如何0代码开发

提供的地址skill.md:AI Coding 操作手册

在我们的Trae编辑器中 设置 -> 技能与中心

在这里插入图片描述
在这里插入图片描述

导入成功后我们在AI输入框输入我们的指令

在这里插入图片描述

输入:基于我的数字人skill 以及我的项目描述 帮我可直接运行的情感陪伴交互智能体 Demo

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Trea编辑器就能够成功调用我们的skill技能,有没有很简单,到现在为止我没有进行任何代码相关的手动开发操作,全程都是通过AI来实现我们的数字人生成。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

生成完成后,我们进行Html文件的一个预览

\>serve \-p 3000 运行我们的html
运行 http://localhost:3000/ai-emotional/-mentor

我们可以看到这个页面已经成功展示了

现在我们去官网地址:查看官网 官网拿到我们的数字人密钥

在这里插入图片描述

拿到密钥

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

再打开https://www.deepseek.com 的 API 开放平台
在这里插入图片描述
在这里插入图片描述

填充我们的数字人以及大模型APIKey,我们的数字人就可以完美呈现出来~

已将本次使用到项目描述以及 上传至Gitee仓库:https://gitee.com/xiaotietou/xingyun-demo,大家可以根据本篇教程学习。

整个 Demo 是一个单 HTML 文件,打开即用。核心链路是:

用户输入文字 → DeepSeek 生成回复 → 星云 SDK 驱动数字人播报

下面是我实现的几个关键模块。

3.2 数字人初始化

星云 SDK 通过 CDN 引入,初始化只需要容器 ID 和 App 凭证:

sdk = new XmovAvatar({
  containerId: "#sdk",
  appId: appId,
  appSecret: appSecret,
  gatewayServer: "https://nebula-agent.xingyun3d.com/user/v1/ttsa/session",
});

await sdk.init({
  onDownloadProgress: (p) => {
    if (p < 100) updateStatus("loading", `加载 ${p}%`);
  },
});

初始化完成后,数字人会出现在页面上,等待后续指令。

3.3 DeepSeek 接入与情感导师人设

DeepSeek 的 API 使用标准的 OpenAI 兼容格式,接入非常简单。关键是系统提示词的设计——我需要让 DeepSeek 的输出既专业又口语化,适合被 TTS 朗读:

const systemPrompt = `你是一名7×24小时全天候在线的专业AI情感导师,人设温柔包容、理性共情、耐心治愈。

你的核心职责:专注解决用户焦虑内耗、情绪低落、职场学业压力、人际情感矛盾、心态迷茫等问题。

固定对话流程:
1. 先共情接纳用户情绪,安抚当下心情;
2. 温和梳理问题核心逻辑;
3. 给出简单、可落地、轻量化的心态调节方法与建议。

语言风格:全程口语化、温柔治愈、逻辑清晰。回复精炼(100-200字),让TTS播报自然流畅。`;

const resp = await fetch("https://api.deepseek.com/v1/chat/completions", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${dsKey}`,
  },
  body: JSON.stringify({
    model: "deepseek-chat",
    messages: [{ role: "system", content: systemPrompt }, ...history],
    temperature: 0.75,
    max_tokens: 1024,
  }),
});

这里 temperature: 0.75 是一个经过几次调试后选定的值——比默认值稍高,让回复更自然、更有温度,但不至于跑偏。max_tokens: 1024 保证回复不会太长,适合 TTS 播报。

3.4 对话记忆

为了不出现"每轮都在重新自我介绍"的情况,我维护了一个 10 轮对话的上下文数组:

let conversationHistory = [];
const MAX_HISTORY = 10;

// 每次对话后维护记忆
conversationHistory.push({ role: "user", content: userMessage });
conversationHistory.push({ role: "assistant", content: reply });

// 截断旧记录
if (conversationHistory.length > MAX_HISTORY * 2) {
  conversationHistory = conversationHistory.slice(-MAX_HISTORY * 2);
}

这意味着用户连续倾诉 5-10 轮后,数字人仍然能记住一开始的问题背景,给出连贯的建议,而不是每次都像第一次见面。

3.5 打断播报

情绪陪伴场景里有一个刚需:用户可能在数字人播报到一半的时候想插话。如果只能等它说完才能说下一句,体验会很差。星云 SDK 提供了 interactiveidle() 接口来处理这个场景:

function doInterrupt() {
  if (!sdk) return;
  sdk.interactiveidle();
}

调用后用户任意时段中途插话,即时切换交互待机状态。


四、实际体验:几个让我意外的瞬间

Demo 搭好后,我让几个朋友试了一下。有几个细节让我印象很深。

第一个是响应速度。 用户输入一句话后,DeepSeek 通常在 1-2 秒内返回结果,然后数字人几乎同时开始播报。星云 SDK 宣称的端到端约 500ms 响应在实际体验中确实感受明显——数字人不会出现"卡一下再开口"的停顿感,整个交互非常流畅。

第二个是打断的自然感。 朋友在数字人说话到一半时点了打断按钮,数字人立刻停止,安静地等待下一条输入。没有生硬的截断音,也没有重置到奇怪的状态。这个细节在真正使用中非常重要——情绪对话不是问答机器,用户需要随时接管对话节奏。

第三个是对话记忆的连贯性。 朋友连续聊了大约 8 轮,从工作压力聊到家庭关系,数字人每一轮都能关联前面的内容。最让我惊讶的是,第 6 轮时朋友说"你说的上次那个呼吸法,我今天试了",DeepSeek 在上下文里确实找到了前面提到的"呼吸练习",回复说"太好了,坚持练习会有帮助的,今天感觉怎么样?"——这个连续性让对话摆脱了"一问一答"的机械感。


五、完整 Demo 代码

我把整个 Demo 整理成了一个可直接运行的 HTML 文件。你只需要填入三个参数——星云 App ID、App Secret 和 DeepSeek API Key——就能在浏览器里看到效果。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>全天候 AI 情感陪伴导师 | 魔珐星云 × DeepSeek</title>
  <style>
    /* ========== 1. 全局重置与暖色调主题 ========== */
    :root {
      --primary: #e8a87c;
      --primary-hover: #d4956a;
      --primary-light: #fdf0e6;
      --accent: #f4a261;
      --danger: #e76f51;
      --success: #2a9d8f;
      --bg: #fef9f4;
      --card-bg: #ffffff;
      --text-main: #3e3640;
      --text-sub: #8b7e74;
      --text-muted: #b0a59b;
      --border: #f0e4d8;
      --shadow: 0 4px 24px rgba(0,0,0,0.06);
    }

    * { box-sizing: border-box; }

    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
      margin: 0;
      padding: 30px 20px;
      background: linear-gradient(135deg, #fef9f4 0%, #fdf3e7 50%, #fef9f4 100%);
      color: var(--text-main);
      display: flex;
      flex-direction: column;
      align-items: center;
      min-height: 100vh;
    }

    /* ========== 2. 主标题 ========== */
    .main-title {
      text-align: center;
      margin-bottom: 10px;
    }
    .main-title h1 {
      font-size: 28px;
      font-weight: 700;
      color: var(--text-main);
      margin: 0;
      letter-spacing: 1px;
    }
    .main-title .slogan {
      font-size: 14px;
      color: var(--text-sub);
      margin-top: 6px;
      letter-spacing: 0.5px;
    }

    /* ========== 3. 配置面板 ========== */
    .config-panel {
      background: var(--card-bg);
      padding: 20px 24px;
      border-radius: 16px;
      box-shadow: var(--shadow);
      width: 100%;
      max-width: 960px;
      display: flex;
      flex-wrap: wrap;
      align-items: flex-end;
      gap: 14px;
      margin-bottom: 24px;
      border: 1px solid var(--border);
    }
    .input-group {
      display: flex;
      flex-direction: column;
      gap: 5px;
      flex: 1;
      min-width: 180px;
    }
    .input-group label {
      font-size: 11px;
      font-weight: 700;
      color: var(--text-sub);
      text-transform: uppercase;
      letter-spacing: 0.6px;
    }
    .input-group input {
      padding: 0 12px;
      height: 40px;
      border: 1.5px solid var(--border);
      border-radius: 10px;
      font-size: 13px;
      outline: none;
      transition: border-color 0.2s, box-shadow 0.2s;
      background: #fefefe;
    }
    .input-group input:focus {
      border-color: var(--primary);
      box-shadow: 0 0 0 3px rgba(232,168,124,0.12);
    }
    .action-group {
      display: flex;
      align-items: flex-end;
      gap: 8px;
      flex-wrap: wrap;
    }
    button {
      padding: 0 18px;
      height: 40px;
      border: none;
      border-radius: 10px;
      font-weight: 600;
      font-size: 13px;
      cursor: pointer;
      transition: all 0.2s ease;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      white-space: nowrap;
    }
    button:active { transform: scale(0.97); }
    .btn-primary {
      background: var(--primary);
      color: #fff;
    }
    .btn-primary:hover {
      background: var(--primary-hover);
      box-shadow: 0 4px 14px rgba(232,168,124,0.35);
    }
    .btn-danger {
      background: var(--danger);
      color: #fff;
    }
    .btn-danger:hover { background: #d45d3e; }
    .btn-outline {
      background: transparent;
      border: 1.5px solid var(--border);
      color: var(--text-main);
    }
    .btn-outline:hover { background: var(--primary-light); border-color: var(--primary); }
    .btn-accent {
      background: var(--accent);
      color: #fff;
    }
    .btn-accent:hover { box-shadow: 0 4px 14px rgba(244,162,97,0.35); }

    .status-badge {
      margin-left: auto;
      display: flex;
      align-items: center;
      gap: 8px;
      font-size: 13px;
      color: var(--text-sub);
      background: var(--primary-light);
      padding: 0 16px;
      border-radius: 20px;
      height: 40px;
    }
    .status-dot {
      width: 9px;
      height: 9px;
      border-radius: 50%;
      background: #d1d5db;
      transition: background 0.3s, box-shadow 0.3s;
    }
    .status-dot.success { background: var(--success); box-shadow: 0 0 8px var(--success); }
    .status-dot.error   { background: var(--danger); box-shadow: 0 0 8px var(--danger); }
    .status-dot.loading { background: #f4a261; animation: pulse 1s infinite; }
    @keyframes pulse {
      0%,100% { opacity: 1; }
      50%     { opacity: 0.4; }
    }

    /* ========== 4. 数字人舞台 ========== */
    #sdk-wrapper {
      display: flex;
      justify-content: center;
      width: 100%;
      margin-bottom: 20px;
    }
    #sdk {
      width: 800px;
      height: 450px;
      background: radial-gradient(ellipse at center, #2d2838 0%, #1a1720 100%);
      border-radius: 16px;
      box-shadow: 0 20px 48px -12px rgba(0,0,0,0.25);
      transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
      position: relative;
      overflow: hidden;
      max-width: 100%;
    }
    #sdk::before {
      content: "✨ 点击上方 [连接] 按钮激活数字人";
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      color: rgba(255,255,255,0.25);
      font-size: 15px;
      z-index: 0;
      pointer-events: none;
    }

    /* ========== 5. 底部对话栏 ========== */
    .chat-bar {
      background: var(--card-bg);
      padding: 6px;
      border-radius: 50px;
      box-shadow: var(--shadow);
      width: 100%;
      max-width: 700px;
      display: flex;
      gap: 8px;
      border: 1px solid var(--border);
      margin-bottom: 12px;
    }
    .chat-bar input {
      flex: 1;
      border: none;
      background: transparent;
      padding: 0 20px;
      font-size: 15px;
      outline: none;
      color: var(--text-main);
      height: 42px;
    }
    .chat-bar input::placeholder { color: var(--text-muted); }
    .chat-bar button {
      border-radius: 40px;
      padding: 0 28px;
    }

    /* ========== 6. 快捷场景按钮 ========== */
    .quick-scenes {
      display: flex;
      gap: 8px;
      flex-wrap: wrap;
      justify-content: center;
      max-width: 700px;
      width: 100%;
      margin-bottom: 16px;
    }
    .quick-scenes button {
      font-size: 12px;
      height: 32px;
      padding: 0 14px;
      border-radius: 20px;
      background: var(--card-bg);
      border: 1px solid var(--border);
      color: var(--text-sub);
    }
    .quick-scenes button:hover {
      border-color: var(--primary);
      color: var(--primary);
      background: var(--primary-light);
    }

    /* ========== 7. 对话历史面板 ========== */
    .chat-history {
      background: var(--card-bg);
      border-radius: 12px;
      box-shadow: var(--shadow);
      width: 100%;
      max-width: 700px;
      max-height: 200px;
      overflow-y: auto;
      padding: 14px 18px;
      margin-bottom: 10px;
      border: 1px solid var(--border);
      display: none;
      font-size: 13px;
      line-height: 1.6;
    }
    .chat-history.show { display: block; }
    .chat-history .msg { margin-bottom: 10px; }
    .chat-history .msg.user { color: var(--text-sub); text-align: right; }
    .chat-history .msg.mentor { color: var(--text-main); }
    .chat-history .msg .label {
      font-size: 10px;
      font-weight: 700;
      text-transform: uppercase;
      opacity: 0.5;
      margin-bottom: 2px;
    }

    /* ========== 8. 页脚 ========== */
    .footer {
      margin-top: 12px;
      font-size: 11px;
      color: var(--text-muted);
      text-align: center;
    }
    .footer a {
      color: var(--primary);
      text-decoration: none;
    }

    /* ========== 9. 响应式 ========== */
    @media (max-width: 768px) {
      .config-panel { flex-direction: column; align-items: stretch; }
      .status-badge { margin-left: 0; width: fit-content; }
      .action-group { flex-wrap: wrap; }
      #sdk { height: 350px; }
      .main-title h1 { font-size: 22px; }
    }
  </style>
</head>
<body>

  <!-- 标题 -->
  <div class="main-title">
    <h1>全天候 AI 情感陪伴导师</h1>
    <p class="slogan">深夜有陪伴,情绪有出口 — 24 小时在线的专属情感导师</p>
  </div>

  <!-- 配置面板 -->
  <div class="config-panel">
    <div class="input-group">
      <label>Xmov App ID</label>
      <input type="text" id="inp-appid" placeholder="粘贴你的 App ID" />
    </div>
    <div class="input-group">
      <label>Xmov App Secret</label>
      <input type="password" id="inp-secret" placeholder="粘贴你的 App Secret" />
    </div>
    <div class="input-group">
      <label>DeepSeek API Key</label>
      <input type="password" id="inp-ds-key" placeholder="sk-xxxxxxxx" />
    </div>

    <div class="action-group">
      <button class="btn-primary" onclick="doConnect()" id="btn-conn">连接</button>
      <button class="btn-danger" onclick="doDisconnect()" id="btn-disconn" style="display:none">断开</button>
      <button class="btn-outline" onclick="toggleRatio()" id="btn-ratio">切换至竖屏</button>
      <button class="btn-outline" onclick="toggleHistory()" id="btn-history">显示对话</button>
    </div>

    <div class="status-badge">
      <div class="status-dot" id="status-light"></div>
      <span id="status-text">未连接</span>
    </div>
  </div>

  <!-- 数字人舞台 -->
  <div id="sdk-wrapper">
    <div id="sdk"></div>
  </div>

  <!-- 快捷场景 -->
  <div class="quick-scenes">
    <button onclick="quickScene('最近压力很大,经常失眠,感觉很焦虑')">😰 焦虑失眠</button>
    <button onclick="quickScene('和伴侣吵架了,心里很难受')">💔 情感矛盾</button>
    <button onclick="quickScene('工作好累,对未来很迷茫')">😞 职场迷茫</button>
    <button onclick="quickScene('总是自我否定,觉得自己不够好')">🪞 自我否定</button>
    <button onclick="quickScene('帮我做个简单的呼吸放松练习')">🧘 正念放松</button>
  </div>

  <!-- 对话栏 -->
  <div class="chat-bar">
    <input type="text" id="tts-input" placeholder="输入你想说的话..." value="最近总是很焦虑,能陪我聊聊吗?" />
    <button class="btn-primary" onclick="doSpeak()">💬 发送</button>
    <button class="btn-accent" onclick="doInterrupt()" title="打断当前播报">⏹</button>
  </div>

  <!-- 对话历史 -->
  <div class="chat-history" id="chat-history"></div>

  <!-- 页脚 -->
  <div class="footer">
    基于 <a href="https://xingyun3d.com/" target="_blank">魔珐星云</a> SDK + DeepSeek 大模型驱动 |
    具身交互智能 · 让 AI 进入终端
  </div>

  <!-- ==================== 脚本加载 ==================== -->
  <script src="https://media.xingyun3d.com/xingyun3d/general/litesdk/xmovAvatar@latest.js"></script>

  <script>
    // ============ 全局状态 ============
    let sdk = null;
    let isPortrait = false;
    let conversationHistory = [];   // 对话上下文记忆
    const MAX_HISTORY = 10;         // 保留最近 10 轮对话

    // ============ 1. 连接 SDK + 初始化 LLM ============
    async function doConnect() {
      if (sdk) return;

      const appId = document.getElementById("inp-appid").value.trim();
      const appSecret = document.getElementById("inp-secret").value.trim();
      const dsKey = document.getElementById("inp-ds-key").value.trim();

      if (!appId || !appSecret) {
        alert("请先填写 Xmov App ID 和 App Secret");
        return;
      }
      if (!dsKey) {
        alert("请填写 DeepSeek API Key,用于驱动 AI 情感对话能力");
        return;
      }

      updateStatus("loading", "连接中...");

      // 实例化 Xmov SDK
      sdk = new XmovAvatar({
        containerId: "#sdk",
        appId: appId,
        appSecret: appSecret,
        gatewayServer: "https://nebula-agent.xingyun3d.com/user/v1/ttsa/session",
        onMessage: (msg) => console.log("[SDK Event]", msg),
      });

      try {
        await sdk.init({
          onDownloadProgress: (p) => {
            if (p < 100) updateStatus("loading", `加载 ${p}%`);
          },
        });

        updateStatus("success", "已连接");
        toggleButtons(true);
        console.log("✅ Xmov SDK 初始化成功");

        // 连接成功后,发送欢迎语
        const welcome = "你好,我是你全天候在线的 AI 情感陪伴导师。无论何时,只要你需要,我都在这儿安静倾听。";
        addMessage("mentor", welcome);
        sdk.speak(welcome, true, true);

        // 🔥 存储 DeepSeek Key 到 sessionStorage,页面刷新后自动回填
        sessionStorage.setItem("ds_key", dsKey);
      } catch (err) {
        console.error("连接失败:", err);
        updateStatus("error", "连接失败");
        alert("连接失败,请检查凭证是否正确 (F12 查看详情)");
        sdk = null;
      }
    }

    // ============ 2. 断开连接 ============
    function doDisconnect() {
      if (sdk) {
        sdk.destroy();
        sdk = null;
        conversationHistory = [];
        document.getElementById("chat-history").innerHTML = "";
        updateStatus("error", "已断开");
        toggleButtons(false);
      }
    }

    // ============ 3. 切换横竖屏 ============
    function toggleRatio() {
      const container = document.getElementById("sdk");
      const btn = document.getElementById("btn-ratio");
      if (isPortrait) {
        container.style.width = "800px";
        container.style.height = "450px";
        isPortrait = false;
        btn.innerText = "切换至竖屏";
      } else {
        container.style.width = "360px";
        container.style.height = "640px";
        isPortrait = true;
        btn.innerText = "切换至横屏";
      }
    }

    // ============ 4. 智能播报(LLM 驱动) ============
    async function doSpeak() {
      if (!sdk) {
        alert("请先点击顶部的 [连接] 按钮");
        return;
      }

      const input = document.getElementById("tts-input");
      const userText = input.value.trim();
      if (!userText) return;

      // 显示用户消息
      addMessage("user", userText);
      input.value = "";
      updateStatus("loading", "思考中...");

      // 请求 DeepSeek 生成回复
      const reply = await callDeepSeek(userText);
      if (reply) {
        addMessage("mentor", reply);
        sdk.speak(reply, true, true);
        updateStatus("success", "已连接");
      } else {
        updateStatus("success", "已连接");
        sdk.speak("抱歉,我暂时无法回应,请稍后再试。", true, true);
      }
    }

    // ============ 5. 打断当前播报 ============
    function doInterrupt() {
      if (!sdk) return;
      sdk.interactiveidle();
      console.log("⏹ 已发送打断信号");
    }

    // ============ 6. 快捷场景 ============
    function quickScene(text) {
      document.getElementById("tts-input").value = text;
      doSpeak();
    }

    // ============ 7. 切换对话历史显示 ============
    function toggleHistory() {
      const panel = document.getElementById("chat-history");
      const btn = document.getElementById("btn-history");
      if (panel.classList.contains("show")) {
        panel.classList.remove("show");
        btn.innerText = "显示对话";
      } else {
        panel.classList.add("show");
        btn.innerText = "隐藏对话";
      }
    }

    // ============ 8. DeepSeek LLM 调用 ============
    async function callDeepSeek(userMessage) {
      const dsKey = document.getElementById("inp-ds-key").value.trim()
        || sessionStorage.getItem("ds_key") || "";
      if (!dsKey) {
        alert("请填写 DeepSeek API Key");
        updateStatus("success", "已连接");
        return null;
      }

      // 情感导师系统提示词
      const systemPrompt = `你是一名7×24小时全天候在线的专业AI情感导师,人设温柔包容、理性共情、耐心治愈,拒绝鸡汤、拒绝说教、不评判用户。

你的核心职责:专注解决用户焦虑内耗、情绪低落、职场学业压力、人际情感矛盾、心态迷茫等问题。

固定对话流程:
1. 先共情接纳用户情绪,安抚当下心情;
2. 温和梳理问题核心逻辑;
3. 给出简单、可落地、轻量化的心态调节方法与建议。

语言风格:全程口语化、温柔治愈、逻辑清晰,适配全天候私人情感陪伴场景。回复精炼(100-200字),让TTS播报自然流畅。`;

      // 拼接历史上下文
      const messages = [{ role: "system", content: systemPrompt }];
      for (const item of conversationHistory) {
        messages.push({ role: item.role, content: item.content });
      }
      messages.push({ role: "user", content: userMessage });

      // 更新记忆
      conversationHistory.push({ role: "user", content: userMessage });
      if (conversationHistory.length > MAX_HISTORY * 2) {
        conversationHistory = conversationHistory.slice(-MAX_HISTORY * 2);
      }

      try {
        const resp = await fetch("https://api.deepseek.com/v1/chat/completions", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${dsKey}`,
          },
          body: JSON.stringify({
            model: "deepseek-chat",
            messages: messages,
            temperature: 0.75,
            top_p: 0.9,
            max_tokens: 1024,
          }),
        });

        if (!resp.ok) {
          const err = await resp.text();
          console.error("DeepSeek API 错误:", err);
          throw new Error(`HTTP ${resp.status}`);
        }

        const data = await resp.json();
        const reply = data.choices?.[0]?.message?.content || "";
        if (reply) {
          conversationHistory.push({ role: "assistant", content: reply });
        }
        return reply;
      } catch (err) {
        console.error("LLM 请求失败:", err);
        return null;
      }
    }

    // ============ 9. 对话记录 ============
    function addMessage(role, text) {
      const panel = document.getElementById("chat-history");
      const div = document.createElement("div");
      div.className = `msg ${role}`;
      const label = role === "user" ? "🧑 你" : "💫 导师";
      div.innerHTML = `<div class="label">${label}</div><div>${escapeHtml(text)}</div>`;
      panel.appendChild(div);
      panel.scrollTop = panel.scrollHeight;
    }

    function escapeHtml(str) {
      const div = document.createElement("div");
      div.textContent = str;
      return div.innerHTML;
    }

    // ============ 10. UI 状态管理 ============
    function updateStatus(state, text) {
      const light = document.getElementById("status-light");
      const label = document.getElementById("status-text");
      light.className = "status-dot";
      if (state === "success") light.classList.add("success");
      else if (state === "error") light.classList.add("error");
      else if (state === "loading") light.classList.add("loading");
      label.innerText = text;
    }

    function toggleButtons(isConnected) {
      document.getElementById("btn-conn").style.display = isConnected ? "none" : "inline-flex";
      document.getElementById("btn-disconn").style.display = isConnected ? "inline-flex" : "none";
    }

    // ============ 11. 生命周期 ============
    window.addEventListener("beforeunload", () => {
      if (sdk) sdk.destroy();
    });

    // 页面加载时自动回填 DeepSeek Key
    window.addEventListener("DOMContentLoaded", () => {
      const savedKey = sessionStorage.getItem("ds_key");
      if (savedKey) {
        document.getElementById("inp-ds-key").value = savedKey;
      }
      // 默认显示对话历史面板
      toggleHistory();
    });
  </script>
</body>
</html>

整个 Demo 的核心逻辑不到 200 行 JavaScript,但已经覆盖了:SDK 初始化、DeepSeek API 调用、系统提示词注入、对话记忆管理、打断播报、横竖屏切换等完整链路。
我整个UI美化下来也只有600行


六、从 Demo 到落地:几个实际考量

Demo 跑通之后,我也在想,如果要把它变成一个真正可用的产品,还需要考虑什么。

部署门槛。 这个 Demo 是纯前端实现,一个 HTML 文件解决所有问题。但生产环境中,API Key 显然不能放在前端。合理的做法是把 LLM 调用放到后端,前端只负责数字人渲染和交互。魔珐星云的 SDK 设计上并没有绑定特定后端,这个迁移是可行的。

响应延迟。 我实测下来,从用户发送消息到数字人开口,整体链路(DeepSeek API + SDK 渲染)在 2 秒左右。其中 DeepSeek 占了大部分时间(1-2 秒),SDK 本身的启动延迟非常低——这就是参数流架构的优势:不需要等视频渲染完毕再推流,数字人可以"边说边动"。

多终端适配。 目前的 Demo 在浏览器上运行良好。根据星云的文档,同一套 SDK 也适配移动端和大屏设备,这给后续在不同场景落地留了空间。


七、写在最后

这个实验做完,我最大的感受是:大模型和具身交互之间,不是"谁替代谁"的关系,而是"谁补齐谁"的关系。

DeepSeek 赋予 AI 完整思考推理能力,魔珐星云作为标准化具身交互智能基础设施,补齐 AI 线下终端真人化沟通全套能力,让文字层面的共情理解转化为有温度的面对面实时交互。—让它能站在你面前,看着你的眼睛,用合适的语气和节奏说出那些温暖的话。

回到文章最开头的问题:国产大模型已经很能"想"了,但怎么进入终端、进入真实场景?这个实验给我了一个思路——大模型负责理解和生成,具身交互平台负责让这种理解"活"起来。 一个是大脑,一个是身体和表达,两者结合,AI 才能真正走进人们的生活。

对于开发者来说,这个组合的门槛比想象中低。如果你已经熟悉调用大模型 API,加上数字人 SDK 并不会增加太多额外的工作量——本质上就是多了一个"能说话、有表情"的输出通道。而这个通道在某些场景里,可能就是用户体验从"还行"到"很棒"的关键。

魔珐星云官网:https://xingyun3d.com?utm_campaign=daily&utm_source=CSDNwanfen3&utm_medium=&utm_term=&utm_content=


Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐