Agent 重构大屏价值:从静态数据墙到实时交互体

文章目录

    • 摘要
    • [1. 引言:打破大屏的固有局限](#1. 引言:打破大屏的固有局限)
      • [1.1 传统大屏的惯性短板](#1.1 传统大屏的惯性短板)
      • [1.2 Agent 带来的本质改变](#1.2 Agent 带来的本质改变)
      • [1.3 本文目标](#1.3 本文目标)
    • [2. 技术选型:跳出云端旧模式](#2. 技术选型:跳出云端旧模式)
      • [2.1 传统云端方案的固有缺陷](#2.1 传统云端方案的固有缺陷)
      • [2.2 端侧 Agent:重构技术新范式](#2.2 端侧 Agent:重构技术新范式)
      • [2.3 技术架构本质升级](#2.3 技术架构本质升级)
    • [3. DeepSeek大模型详解](#3. DeepSeek大模型详解)
      • [3.1 为什么选择DeepSeek?](#3.1 为什么选择DeepSeek?)
    • [4. 项目架构设计](#4. 项目架构设计)
      • [4.1 整体架构图](#4.1 整体架构图)
      • [4.2 核心功能模块](#4.2 核心功能模块)
      • [4.3 数据流程图](#4.3 数据流程图)
    • [5. 核心实现:数字人接入](#5. 核心实现:数字人接入)
      • [5.1 SDK控制器实](#5.1 SDK控制器实)
      • [5.2 数字人状态管理](#5.2 数字人状态管理)
      • [5.3 数字人容器组件](#5.3 数字人容器组件)
    • [6. 核心实现:AI数据生成](#6. 核心实现:AI数据生成)
      • [6.1 数据结构设计](#6.1 数据结构设计)
      • [6.2 AI数据生成服务](#6.2 AI数据生成服务)
      • [6.3 数据生成API路由](#6.3 数据生成API路由)
    • [7. 核心实现:智能对话服务](#7. 核心实现:智能对话服务)
      • [7.1 对话服务设计](#7.1 对话服务设计)
      • [7.2 降级响应实现](#7.2 降级响应实现)
    • [8. 前端应用集成](#8. 前端应用集成)
      • [8.1 主应用组件](#8.1 主应用组件)
      • [8.2 大屏布局适配](#8.2 大屏布局适配)
    • [9. 落地场景与效果展示](#9. 落地场景与效果展示)
      • [9.1 企业展厅场景](#9.1 企业展厅场景)
      • [9.2 指挥中心场景](#9.2 指挥中心场景)
      • [9.3 效果展示](#9.3 效果展示)
      • [9.4 技术效果对比](#9.4 技术效果对比)
    • [10. 常见问题与解决方案](#10. 常见问题与解决方案)
    • [11. 总结:打破惯性,重构大屏价值](#11. 总结:打破惯性,重构大屏价值)

摘要

长期以来,企业展厅、指挥中心大屏都被固化为「静态数据展示墙」------ 满屏图表晦涩难懂、无实时解读、无法互动,沦为单纯的视觉摆设。本文依托魔珐星云端侧 Agent 与 DeepSeek 大模型,打破行业固有惯性 ,将传统大屏升级为可实时讲解、随问随答、场景适配的交互智能体,实现从「被动看数据」到「主动聊数据」的本质转变。


1. 引言:打破大屏的固有局限

1.1 传统大屏的惯性短板

走进企业展厅、运营指挥中心,大屏始终逃不开一个固有模式:只是冰冷的数据陈列工具。满屏数字、图表、仪表盘堆砌,非专业人员难以读懂;没有实时解读,数据亮点、异常点无从知晓;单向展示无法互动,参观、调度仅流于形式,毫无实际价值。

图1:传统BI可视化大屏

1.2 Agent 带来的本质改变

行业旧模式里,大屏的价值仅停留在「视觉展示」;而Agent 重构大屏核心价值 ------ 不再是静态摆设,而是能实时解读、随问随答、贴合场景的交互体

访客无需费力看图表,Agent 主动讲解;有疑问直接提问,Agent 即时回应;适配展厅参观、指挥调度等场景,彻底改变大屏的使用逻辑。

1.3 本文目标

本文从模式重构视角出发,拆解从传统静态大屏到 Agent 交互体的完整落地路径:

  1. 跳出云端旧模式,选择端侧 Agent 技术;
  2. 实现大屏 Agent 实时讲解、交互问答;
  3. 适配展厅、指挥中心等实体场景;
  4. 完成规模化部署与效果验证。

2. 技术选型:跳出云端旧模式

2.1 传统云端方案的固有缺陷

行业多数数字人方案沿用云端渲染旧模式,看似能呈现动态形象,实则存在致命短板:

plain 复制代码
用户输入→云端处理→云端渲染→下发展示
  • 延迟高:响应耗时 1-3 秒,互动节奏脱节;
  • 易卡顿:网络波动直接画面冻结;
  • 成本高:云端 GPU 持续计费,规模化部署难;
  • 适配差:实体场地弱网环境完全无法使用。

2.2 端侧 Agent:重构技术新范式

图2:魔珐星云数字人

魔珐星云AI 端渲 + 端侧解算 方案,跳出云端固有路径,重构 Agent 交互底层逻辑:


传输:仅传 KB 级轻量驱动指令,而非完整视频;

渲染:场地终端本地完成,弱网稳定;

延迟:端到端响应≤500ms,贴合实时互动;

成本:一次接入、云端零算力消耗,适配规模化部署。

2.3 技术架构本质升级

魔珐星云实现端到端全栈能力,打通三层核心架构:

打通「感知→理解→表达→渲染」全链路,打破模块拼接旧模式,实现思考与表达同步,Agent 回应自然连贯。


3. DeepSeek大模型详解

3.1 为什么选择DeepSeek?

在本项目中,我们需要一个能理解业务数据、生成洞察、回答问题的智能大脑。经过对比测试,选择魔搭社区提供的DeepSeek-V3.2模型:

模型 参数规模 推理能力 成本 适用场景
GPT-4 1.8T ⭐⭐⭐⭐⭐ 复杂推理
DeepSeek-V3 671B ⭐⭐⭐⭐⭐ 数据分析、对话
Qwen2.5 72B ⭐⭐⭐⭐ 通用对话
GLM-4 130B ⭐⭐⭐⭐ 中文场景

DeepSeek的优势:

  1. 推理能力强:在数学、代码、数据分析等任务上表现优异
  2. 成本低:通过魔搭社区调用,价格远低于GPT-4
  3. 中文友好:对中文业务场景的理解更准确
  4. 流式输出:支持SSE流式响应,适合对话场景

4. 项目架构设计

4.1 整体架构图

本项目采用前后端一体化架构,前端负责数字人展示和数据可视化,后端负责AI数据生成和对话服务:

4.2 核心功能模块

模块 功能 技术实现
场景切换 常规运营/营销活动/销售淡季/特殊事件 场景配置 + AI数据生成
数据可视化 7个核心指标 + 地区数据 + 产品数据 ECharts图表组件
智能对话 AI助手回答业务问题 DeepSeek + SSE流式响应
数字人播报 自动播报数据洞察 魔珐星云TTS + 动作控制

4.3 数据流程图


5. 核心实现:数字人接入

5.1 SDK控制器实

魔珐星云提供XmovAvatar SDK,我们封装了一个控制器来管理连接、播报和状态切换。

以下是项目中的真实实现源码:

plain 复制代码
// src/client/components/Avatar/AvatarController.ts
import keyService from '../../services/keyService';
import { useAvatarStore } from '../../store/avatarStore';

/**
 * 魔珐星云数字人SDK控制器
 * 支持手动连接和断开
 */

declare global {
  interface Window {
    XmovAvatar: any;
  }
}

export interface SpeakOptions {
  text: string;
  isStart?: boolean;
  isEnd?: boolean;
}

class AvatarController {
  private sdkInstance: any = null;
  private isConnected: boolean = false;

  /**
   * 初始化并连接SDK(从localStorage读取密钥)
   */
  async connect(): Promise<void> {
    const keys = keyService.getApiKeys();
    if (!keys) {
      throw new Error('未配置星云密钥');
    }

    const { xmovAppId, xmovAppSecret } = keys;

    // 等待SDK加载完成
    await this.waitForSDK();

    // 如果已有实例,先销毁
    if (this.sdkInstance) {
      this.sdkInstance.destroy();
    }

    useAvatarStore.getState().setStatus('connecting');

    // 创建SDK实例
    this.sdkInstance = new window.XmovAvatar({
      containerId: '#avatar-container',
      appId: xmovAppId,
      appSecret: xmovAppSecret,
      gatewayServer: 'https://nebula-agent.xingyun3d.com/user/v1/ttsa/session',
      enableLogger: true,
      onMessage: (message: any) => {
        console.log('[Avatar] SDK Message:', message);
        if (message.code !== 0) {
          useAvatarStore.getState().setError(message.message || 'SDK错误');
        }
      },
      onStateChange: (state: string) => {
        console.log('[Avatar] State Change:', state);
      },
      onStatusChange: (status: any) => {
        console.log('[Avatar] Status Change:', status);
      },
      onVoiceStateChange: (voiceStatus: string) => {
        console.log('[Avatar] Voice Status:', voiceStatus);
      },
      onNetworkInfo: (networkInfo: any) => {
        console.log('[Avatar] Network Info:', networkInfo);
      }
    });

    try {
      // 初始化SDK
      await this.sdkInstance.init({
        onDownloadProgress: (progress: number) => {
          console.log(`[Avatar] Download Progress: ${progress}%`);
        },
        onError: (error: any) => {
          console.error('[Avatar] Init Error:', error);
          this.isConnected = false;
          useAvatarStore.getState().setError(error.message || '初始化失败');
        },
        onClose: () => {
          console.log('[Avatar] Connection closed');
          this.isConnected = false;
          useAvatarStore.getState().setStatus('disconnected');
        }
      });

      // 连接成功,更新状态
      this.isConnected = true;
      useAvatarStore.getState().setSdkInstance(this.sdkInstance);
      useAvatarStore.getState().setStatus('connected');
      console.log('[Avatar] Connected successfully');
    } catch (error: any) {
      console.error('[Avatar] Connect failed:', error);
      useAvatarStore.getState().setError(error.message || '连接失败');
      throw error;
    }
  }

  /**
   * 文本播报
   */
  speak(options: SpeakOptions): void {
    if (!this.sdkInstance || !this.isConnected) {
      throw new Error('数字人未连接');
    }

    const { text, isStart = true, isEnd = true } = options;
    this.sdkInstance.speak(text, isStart, isEnd);
    console.log('[Avatar] Speaking:', text);
  }

  /**
   * 切换到待机状态
   */
  idle(): void {
    if (this.sdkInstance && this.isConnected) {
      this.sdkInstance.idle();
    }
  }

  /**
   * 切换到倾听状态
   */
  listen(): void {
    if (this.sdkInstance && this.isConnected) {
      this.sdkInstance.listen();
    }
  }

  /**
   * 切换到思考状态
   */
  think(): void {
    if (this.sdkInstance && this.isConnected) {
      this.sdkInstance.think();
    }
  }
}

export default new AvatarController();

上述代码是项目中真实的SDK控制器实现。关键点说明:

  • containerId :数字人渲染的DOM容器ID,值为#avatar-container
  • gatewayServer:魔珐星云的网关服务器地址
  • onMessage/onStateChange/onVoiceStateChange:各种事件回调,用于状态监控
  • speak() :文本播报方法,isStartisEnd参数用于分段播报控制
  • idle()/listen()/think():状态切换方法,让数字人呈现不同姿态

5.2 数字人状态管理

使用Zustand进行轻量级状态管理,

以下是项目中的真实实现源码:

plain 复制代码
// src/client/store/avatarStore.ts
import { create } from 'zustand';

export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';

interface AvatarState {
  // 连接状态
  status: ConnectionStatus;
  errorMessage: string;

  // SDK实例
  sdkInstance: any;

  // 动作
  setStatus: (status: ConnectionStatus) => void;
  setError: (message: string) => void;
  setSdkInstance: (instance: any) => void;

  // 连接控制
  disconnect: () => void;
}

export const useAvatarStore = create<AvatarState>()((set) => ({
  status: 'disconnected',
  errorMessage: '',
  sdkInstance: null,

  setStatus: (status) => set({ status, errorMessage: '' }),
  setError: (errorMessage) => set({ status: 'error', errorMessage }),
  setSdkInstance: (sdkInstance) => set({ sdkInstance }),

  disconnect: () => {
    set({
      status: 'disconnected',
      sdkInstance: null,
      errorMessage: ''
    });
  }
}));

状态管理非常简洁,只维护三个核心状态:

  • status:连接状态(disconnected/connecting/connected/error)
  • errorMessage:错误信息
  • sdkInstance:SDK实例引用

5.3 数字人容器组件

React组件封装数字人展示区域

以下是项目中的真实实现源码:

plain 复制代码
// src/client/components/Avatar/AvatarContainer.tsx
import React, { useRef, useEffect, useState } from 'react';
import { useAvatarStore } from '../../store/avatarStore';
import AvatarController from './AvatarController';

export const AvatarContainer: React.FC = () => {
  const containerRef = useRef<HTMLDivElement>(null);
  const { status, errorMessage } = useAvatarStore();
  const [isConnecting, setIsConnecting] = useState(false);

  const handleConnect = async () => {
    setIsConnecting(true);
    try {
      await AvatarController.connect();
    } catch (error) {
      console.error('[AvatarContainer] Connect failed:', error);
    } finally {
      setIsConnecting(false);
    }
  };

  // 组件卸载时清理
  useEffect(() => {
    return () => {
      AvatarController.dispose();
    };
  }, []);

  return (
    <div className="relative h-full">
      {/* 数字人渲染容器 */}
      <div
        id="avatar-container"
        ref={containerRef}
        className="w-full h-full bg-black/20 rounded-2xl overflow-hidden"
      />

      {/* 未连接时显示连接按钮 */}
      {status === 'disconnected' && (
        <div className="absolute inset-0 flex items-center justify-center">
          <button
            onClick={handleConnect}
            disabled={isConnecting}
            className="flex items-center gap-2 px-8 py-4 rounded-full font-medium transition-all bg-blue-500/80 hover:bg-blue-500 text-white disabled:opacity-50 text-lg"
          >
            <span>{isConnecting ? '⏳' : '🔗'}</span>
            <span>{isConnecting ? '连接中...' : '连接数字人'}</span>
          </button>
        </div>
      )}

      {/* 连接中状态 */}
      {status === 'connecting' && (
        <div className="absolute inset-0 flex items-center justify-center bg-black/30 rounded-2xl">
          <div className="text-center text-white">
            <div className="animate-spin text-5xl mb-3">⚙️</div>
            <p className="text-lg">正在连接数字人...</p>
          </div>
        </div>
      )}

      {/* 错误状态 */}
      {status === 'error' && (
        <div className="absolute inset-0 flex items-center justify-center bg-black/50 rounded-2xl">
          <div className="text-center">
            <div className="text-5xl mb-3">⚠️</div>
            <p className="text-red-400 text-lg mb-4">连接失败</p>
            {errorMessage && (
              <p className="text-white text-sm mb-4">{errorMessage}</p>
            )}
            <button
              onClick={handleConnect}
              className="px-6 py-2 bg-blue-500/80 hover:bg-blue-500 text-white rounded-full text-sm"
            >
              重试
            </button>
          </div>
        </div>
      )}
    </div>
  );
};

组件实现了完整的连接状态UI:

  • disconnected:显示"连接数字人"按钮
  • connecting:显示加载动画
  • connected:数字人正常显示
  • error:显示错误信息和重试按钮

6. 核心实现:AI数据生成

6.1 数据结构设计

项目中定义了完整的业务数据结构:

以下是项目中的真实实现源码:

plain 复制代码
// src/server/services/aiDataGenerator.ts

/**
 * 场景类型定义
 */
export type ScenarioType = 'normal' | 'promotion' | 'off_season' | 'anomaly';

/**
 * 指标数据
 */
export interface MetricData {
  name: string;
  value: number;
  previousValue: number;
  change: number;
  changePercent: number;
  unit: string;
  trend: 'up' | 'down' | 'stable';
}

/**
 * 趋势数据点
 */
export interface TrendPoint {
  timestamp: number;
  value: number;
}

/**
 * 地区数据
 */
export interface RegionalData {
  name: string;
  value: number;
  changePercent: number;
}

/**
 * 产品分类数据
 */
export interface ProductData {
  name: string;
  revenue: number;
  margin: number;
  share: number;
}

/**
 * 预警数据
 */
export interface AlertData {
  level: 'info' | 'warning' | 'critical';
  message: string;
}

/**
 * AI生成数据响应
 */
export interface AIGeneratedData {
  metrics: MetricData[];
  trend: TrendPoint[];
  regionalData: RegionalData[];
  productData: ProductData[];
  insight: string;
  suggestion: string;
  alerts: AlertData[];
}

数据结构涵盖了BI大屏需要的所有数据类型:

  • metrics:7个核心指标(营业收入、订单量、毛利率、活跃用户、转化率、客单价、复购率)
  • trend:12小时趋势数据
  • regionalData:4个地区数据
  • productData:4个产品数据
  • insight:数据洞察
  • suggestion:业务建议
  • alerts:预警信息

6.2 AI数据生成服务

项目中的AI数据生成服务实现如下:

plain 复制代码
// src/server/services/aiDataGenerator.ts(核心部分)

export class AIDataGeneratorService {
  private baseURL: string = 'https://api-inference.modelscope.cn/v1';

  /**
   * 生成AI数据
   */
  async generateData(
    request: DataGenerateRequest,
    apiKey: string
  ): Promise<AIGeneratedData> {
    const { scenario, previousData } = request;

    // 构建场景描述
    const scenarioDesc = this.getScenarioDescription(scenario);

    // 构建Prompt
    const prompt = this.buildPrompt(scenarioDesc, previousData);

    try {
      // 调用魔搭AI
      const response = await axios.post(
        `${this.baseURL}/chat/completions`,
        {
          model: 'deepseek-ai/DeepSeek-V3.2',
          messages: [
            {
              role: 'system',
              content: '你是一个专业的BI数据模拟器。你生成的所有数据都必须是真实的、合理的、符合业务逻辑的。严格按照用户要求的格式返回JSON数据。'
            },
            { role: 'user', content: prompt }
          ],
          temperature: 0.8,
          max_tokens: 2000
        },
        {
          headers: {
            'Authorization': `Bearer ${apiKey}`,
            'Content-Type': 'application/json'
          },
          timeout: 120000 // 2分钟超时
        }
      );

      // 解析AI返回的内容
      const content = response.data.choices[0].message.content;
      return this.parseAIResponse(content);
    } catch (error: any) {
      console.error('[AIDataGenerator] AI调用失败:', error);
      // 如果AI调用失败,返回基于规则的降级数据
      return this.getFallbackData(scenario, previousData);
    }
  }

  /**
   * 获取场景描述
   */
  private getScenarioDescription(scenario: ScenarioType): string {
    const descriptions: Record<ScenarioType, string> = {
      normal: '常规运营状态,业务平稳发展,各项指标在小幅范围内正常波动,整体呈现缓慢上升趋势。',
      promotion: '正在开展大型营销活动,市场反响热烈,各项指标出现明显增长,客流量和销售额大幅提升。',
      off_season: '处于销售淡季,市场需求相对疲软,各项指标有所下降,需要在降本增效上下功夫。',
      anomaly: '出现突发异常情况(如系统故障、供应链问题等),导致数据出现异常波动,需要紧急处理。'
    };
    return descriptions[scenario];
  }
}

关键配置说明:

  • model :使用魔搭社区的deepseek-ai/DeepSeek-V3.2模型
  • temperature:设置为0.8,在保证合理性的同时有一定的随机性
  • max_tokens:设置为2000,确保能生成完整的数据结构
  • timeout:120秒超时,给AI足够的生成时间

6.3 数据生成API路由

后端API路由实现,包含智能降级逻辑:

以下是项目中的真实实现源码:

plain 复制代码
// src/server/routes/data.routes.ts

import express from 'express';
import aiDataGeneratorService from '../services/aiDataGenerator';
import enhancedDataGenerator from '../services/enhancedDataGenerator';

const router = express.Router();

/**
 * POST /api/data/generate
 *
 * 生成业务数据(优先使用增强生成器,AI失败时自动降级)
 */
router.post('/generate', async (req, res) => {
  try {
    const { scenario, scenarioDescription, previousData, useAI = true } = req.body;

    // 验证scenario
    const validScenarios = ['normal', 'promotion', 'off_season', 'anomaly'];
    if (!scenario || !validScenarios.includes(scenario)) {
      return res.status(400).json({
        success: false,
        error: '无效的场景类型'
      });
    }

    let data;

    // 尝试使用AI生成
    if (useAI) {
      const apiKey = req.headers['x-modelscope-api-key'] as string;

      if (!apiKey) {
        // 没有API密钥,使用增强生成器
        console.log('[DataAPI] No API key, using enhanced generator');
        data = enhancedDataGenerator.generateData(scenario, previousData);
        return res.json({ success: true, data, source: 'enhanced' });
      }

      try {
        // 尝试AI生成,设置超时
        data = await Promise.race([
          aiDataGeneratorService.generateData({ scenario, previousData }, apiKey),
          new Promise((_, reject) =>
            setTimeout(() => reject(new Error('AI timeout')), 120000)
          )
        ] as any);

        return res.json({ success: true, data, source: 'ai' });
      } catch (aiError) {
        console.log('[DataAPI] AI generation failed, using enhanced generator');
        // AI失败,使用增强生成器
        data = enhancedDataGenerator.generateData(scenario, previousData);
        return res.json({ success: true, data, source: 'enhanced' });
      }
    }

    // 默认使用增强生成器
    data = enhancedDataGenerator.generateData(scenario, previousData);
    res.json({ success: true, data, source: 'enhanced' });
  } catch (error: any) {
    console.error('[DataAPI] Generate error:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

/**
 * GET /api/data/scenarios
 *
 * 获取可用的场景列表
 */
router.get('/scenarios', (req, res) => {
  res.json({
    success: true,
    scenarios: [
      { value: 'normal', label: '常规运营', description: '业务平稳发展,小幅增长' },
      { value: 'promotion', label: '营销活动', description: '促销带来数据大幅上升' },
      { value: 'off_season', label: '销售淡季', description: '市场需求疲软,数据下降' },
      { value: 'anomaly', label: '特殊事件', description: '突发情况导致数据异常' }
    ]
  });
});

export default router;

API设计的关键点:

  • 智能降级:AI失败时自动切换到增强生成器,确保服务可用
  • 超时控制 :使用Promise.race实现120秒超时
  • source字段:返回数据来源(ai/enhanced),便于调试

7. 核心实现:智能对话服务

7.1 对话服务设计

项目中的对话服务实现了流式响应和降级响应:

以下是项目中的真实实现源码:

plain 复制代码
// src/server/services/chatService.ts

export class ChatService {
  private baseURL = 'https://api-inference.modelscope.cn/v1';
  private model = 'deepseek-ai/DeepSeek-V3.2';

  /**
   * 构建系统提示词
   */
  private buildSystemPrompt(currentData?: any): string {
    let prompt = `你是一个专业的BI数据讲解助手,名为"小数据"。

你的职责:
1. 解读和讲解企业BI业务数据
2. 用通俗易懂的语言解释复杂数据
3. 发现数据中的趋势和异常
4. 回答用户关于数据的各种问题
5. 提供数据洞察和决策建议

讲解风格:
- 专业但不晦涩,善于用比喻解释数据
- 数据敏感,能快速发现异常和趋势
- 主动分享有价值的洞察
- 回答简洁明了,通常不超过3句话`;

    if (currentData) {
      prompt += `\n\n当前业务数据概况:\n`;
      prompt += `场景:${currentData.scenario || '常规运营'}\n`;

      if (currentData.metrics && Array.isArray(currentData.metrics)) {
        prompt += `\n【核心指标】\n`;
        currentData.metrics.slice(0, 7).forEach((m: any) => {
          const trendIcon = m.changePercent > 0 ? '↑' : m.changePercent < 0 ? '↓' : '→';
          prompt += `- ${m.name}: ${m.value.toLocaleString()}${m.unit} (${trendIcon}${Math.abs(m.changePercent).toFixed(2)}%)\n`;
        });
      }

      if (currentData.alerts && currentData.alerts.length > 0) {
        prompt += `\n【当前预警】\n`;
        currentData.alerts.forEach((alert: any) => {
          prompt += `- [${alert.level}] ${alert.message}\n`;
        });
      }
    }

    return prompt;
  }

  /**
   * 流式对话
   */
  async *chatStream(request: ChatRequest): AsyncGenerator<string> {
    const { message, conversationHistory = [], currentData, apiKey } = request;

    // 构建消息列表
    const messages = [
      { role: 'system', content: this.buildSystemPrompt(currentData) },
      ...conversationHistory.slice(-10), // 最近10条历史
      { role: 'user', content: message }
    ];

    try {
      const response = await axios.post(
        `${this.baseURL}/chat/completions`,
        {
          model: this.model,
          messages: messages.map(m => ({ role: m.role, content: m.content })),
          temperature: 0.7,
          max_tokens: 1000,
          stream: true // 启用流式输出
        },
        {
          headers: {
            'Authorization': `Bearer ${apiKey}`,
            'Content-Type': 'application/json'
          },
          responseType: 'stream',
          timeout: 30000
        }
      );

      const stream = response.data;

      for await (const chunk of stream) {
        const lines = chunk.toString().split('\n').filter((line: string) => line.trim() !== '');

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const data = line.slice(6);
            if (data === '[DONE]') return;

            try {
              const parsed = JSON.parse(data);
              const content = parsed.choices[0]?.delta?.content;
              if (content) {
                yield content;
              }
            } catch (e) {
              // 忽略解析错误
            }
          }
        }
      }
    } catch (error: any) {
      console.error('[ChatService] Stream error:', error);
      // AI失败时返回降级响应
      const fallbackResponse = this.getFallbackResponse(message, currentData);
      yield fallbackResponse;
    }
  }
}

对话服务的关键设计:

  • 动态系统提示词:根据当前数据动态构建,包含所有指标、预警、洞察
  • 流式输出 :使用stream: trueAsyncGenerator实现打字机效果
  • 历史消息限制:只保留最近10条历史,控制Token消耗
  • 降级响应:AI失败时返回基于关键词匹配的响应

7.2 降级响应实现

当AI服务不可用时,使用关键词匹配提供基础回答:

plain 复制代码
// src/server/services/chatService.ts(降级响应部分)

private getFallbackResponse(message: string, currentData?: any): string {
  const lowerMessage = message.toLowerCase();

  // 关于营收
  if (lowerMessage.includes('营收') || lowerMessage.includes('收入')) {
    const revenue = currentData?.metrics?.find((m: any) => m.name === '营业收入');
    if (revenue) {
      const trend = revenue.changePercent > 0 ? '增长' : revenue.changePercent < 0 ? '下降' : '持平';
      return `当前营业收入为${revenue.value.toLocaleString()}元,较上期${trend}${Math.abs(revenue.changePercent).toFixed(2)}%。`;
    }
  }

  // 关于地区
  if (lowerMessage.includes('地区') || lowerMessage.includes('区域')) {
    if (currentData?.regionalData && currentData.regionalData.length > 0) {
      const topRegion = currentData.regionalData[0];
      return `${topRegion.name}表现最好,营收为${topRegion.value.toLocaleString()}元,增长${topRegion.changePercent.toFixed(2)}%。`;
    }
  }

  // 关于产品
  if (lowerMessage.includes('产品') || lowerMessage.includes('品类')) {
    if (currentData?.productData && currentData.productData.length > 0) {
      const topProduct = currentData.productData[0];
      return `${topProduct.name}营收最高,为${topProduct.revenue.toLocaleString()}元,毛利率${topProduct.margin.toFixed(2)}%。`;
    }
  }

  // 关于预警
  if (lowerMessage.includes('预警') || lowerMessage.includes('异常')) {
    const alerts = currentData?.alerts || [];
    if (alerts.length > 0) {
      return `当前有${alerts.length}条预警:${alerts.map((a: any) => a.message).join(';')}`;
    }
    return '当前各项指标正常,无预警信息。';
  }

  // 默认响应
  if (currentData?.insight) {
    return currentData.insight;
  }

  return '抱歉,我暂时无法回答这个问题。请确保已配置有效的魔搭API密钥。';
}

降级响应覆盖了常见的业务问题:

  • 营收/收入查询
  • 地区/区域表现
  • 产品/品类分析
  • 预警/异常信息
  • 默认返回数据洞察

8. 前端应用集成

8.1 主应用组件

前端主应用整合了所有功能模块

以下是项目中的真实实现(核心部分):

plain 复制代码
// src/client/App.tsx(核心部分)

function App() {
  const { isConfigured, setConfigured } = useKeyStore();
  const { status } = useAvatarStore();
  
  const [currentScenario, setCurrentScenario] = useState<string>('normal');
  const [aiData, setAiData] = useState<AIGeneratedData | null>(null);
  const [viewMode, setViewMode] = useState<ViewMode>('overview');

  // 数字人播报
  const handleAvatarSpeak = (text: string) => {
    if (status === 'connected') {
      try {
        AvatarController.speak({ text });
      } catch (error) {
        console.error('[Avatar] Speak error:', error);
      }
    }
  };

  // 生成播报内容
  const generateBroadcastContent = (): string => {
    if (!aiData || !aiData.metrics) return '暂无数据可播报';

    const metrics = aiData.metrics;
    const revenue = metrics.find(m => m.name === '营业收入');
    const margin = metrics.find(m => m.name === '毛利率');
    const users = metrics.find(m => m.name === '活跃用户');

    let content = '现在为您播报本次业务数据概况。';

    if (revenue) {
      const trend = revenue.changePercent > 0 ? '增长' : '下降';
      content += `营业收入为${(revenue.value / 10000).toFixed(0)}万元,较上期${trend}${Math.abs(revenue.changePercent).toFixed(2)}%。`;
    }

    if (margin) {
      content += `毛利率为${margin.value.toFixed(2)}%。`;
    }

    if (users) {
      content += `活跃用户数为${users.value.toLocaleString()}人。`;
    }

    // 预警播报(最多2条)
    if (aiData.alerts && aiData.alerts.length > 0) {
      content += `需要注意的是,`;
      aiData.alerts.slice(0, 2).forEach((alert, index) => {
        content += alert.message;
        if (index < Math.min(aiData.alerts.length, 2) - 1) {
          content += ';';
        }
      });
      content += '。';
    }

    if (aiData.insight) {
      content += aiData.insight;
    }

    content += '播报完毕。';
    return content;
  };

  // 生成数据并播报
  const generateData = async (scenario: string, speak: boolean = true) => {
    const data = await dataService.generateData({
      scenario: scenario as any,
      useAI: true,
      previousData: previousData ? { metrics: previousData.metrics } : undefined
    });

    setAiData(data);

    // 自动播报数据摘要
    if (speak && data.insight) {
      setTimeout(() => {
        handleAvatarSpeak(`数据更新完毕。${data.insight}`);
      }, 1000);
    }
  };

  return (
    <DashboardLayout>
      <div className="h-full flex gap-3">
        {/* 主要内容区 - 占4/6 */}
        <div className="w-[66.666%]">
          {/* 场景切换 */}
          <ScenarioSwitcher
            onScenarioChange={handleScenarioChange}
            currentScenario={currentScenario}
          />

          {/* 指标卡片 */}
          <div className="grid grid-cols-4 gap-3">
            {aiData?.metrics.slice(0, 8).map((metric, index) => (
              <MetricCard
                key={index}
                title={metric.name}
                value={metric.value}
                unit={metric.unit}
                change={metric.changePercent}
                icon={iconMap[metric.name]}
              />
            ))}
          </div>

          {/* 图表区域 */}
          {viewMode === 'overview' && (
            <div className="grid grid-cols-3 gap-4">
              <TrendChart data={aiData?.trend} />
              <GaugeChart value={calculateTargetCompletion()} />
            </div>
          )}
        </div>

        {/* 数字人区域 - 占2/6 */}
        <div className="w-[33.333%]">
          <AvatarContainer />
          
          {/* 播报按钮 */}
          {status === 'connected' && (
            <button onClick={handleBroadcast}>
              📢 开始播报
            </button>
          )}
        </div>
      </div>
    </DashboardLayout>
  );
}

前端应用的核心流程:

  1. 初始化:检查密钥配置,连接数字人
  2. 场景切换:触发数据生成,自动播报洞察
  3. 数据展示:7个指标卡片 + 趋势图 + 仪表盘
  4. 播报控制:手动播报完整摘要

8.2 大屏布局适配

项目实现了响应式大屏布局:

以下是项目中的真实实现源码:

plain 复制代码
// src/client/hooks/useScale.ts

import { useState, useEffect } from 'react';

export const useScale = () => {
  const [scale, setScale] = useState(1);

  useEffect(() => {
    const updateScale = () => {
      const width = window.innerWidth;
      const height = window.innerHeight;
      const targetWidth = 1920;
      const targetHeight = 1080;

      const scaleX = width / targetWidth;
      const scaleY = height / targetHeight;
      const newScale = Math.min(scaleX, scaleY);

      setScale(newScale);
    };

    updateScale();
    window.addEventListener('resize', updateScale);
    return () => window.removeEventListener('resize', updateScale);
  }, []);

  return scale;
};

布局适配方案:

  • 设计尺寸:1920x1080(16:9)
  • 缩放策略:保持宽高比,取较小的缩放值
  • 响应式:监听窗口resize事件,动态调整

9. 落地场景与效果展示

9.1 企业展厅场景

在企业展厅中,BI数据讲解智能体可以:

功能 效果 价值
主动讲解 访客进入展厅,数字人自动介绍企业运营数据 提升参观体验
互动问答 访客询问"今年增长情况?",数字人即时回答 增强互动性
场景切换 切换到"营销活动"场景,展示促销效果 展示多维度

9.2 指挥中心场景

在运营指挥中心,智能体可以:

9.3 效果展示

9.4 技术效果对比

指标 传统方案 本方案 提升
交互延迟 1-3秒 <100ms 10-30倍
并发能力 10-50路 1000+路 20-100倍
月度成本 5000+元 0元(仅模型调用) 降本100%
部署难度 需专业运维 一键部署 大幅降低

10. 常见问题与解决方案

Q1:数字人连接失败怎么办?

原因分析

  • App ID或App Secret配置错误
  • 网络无法访问魔珐星云服务
  • 浏览器不支持WebGL

解决方案

检查密钥配置,确保从星云控制台获取正确的App ID和App Secret。检查网络连接,确保能访问https://nebula-agent.xingyun3d.com。检查浏览器支持,建议使用Chrome/Firefox/Edge最新版本。

Q2:AI数据生成超时怎么办?

原因分析

  • 模型推理时间过长
  • 网络请求超时
  • API服务繁忙

解决方案

项目已内置降级机制,AI失败时自动使用增强生成器,确保服务可用。建议设置合理的超时时间(项目中设置为120秒),并实现重试机制。

Q3:如何扩展更多场景?

扩展方式

aiDataGenerator.ts中添加新的场景类型和描述,在enhancedDataGenerator.ts中添加对应的降级数据生成逻辑。项目已预留scenarioDescription参数支持自定义场景。


11. 总结:打破惯性,重构大屏价值

长期以来,行业对大屏的认知固化在静态展示的旧模式里 ,认为其价值仅停留在视觉层面。而 Agent 的出现,彻底打破这一固有惯性 :大屏不再是冰冷的数据墙,而是能实时解读、随问随答、贴合场景的交互智能体。从被动看数据到主动聊数据,从单向展示到双向交互,本质是使用模式的彻底重构------ 真正有价值的实体大屏,从来不是摆设有多精美,而是能否适配场景、解决实际问题。

魔珐星云官网链接:++https://xingyun3d.com/?utm_campaign=daily&utm_source=jixinghuiKoc147++

文章出自:七夜zippoe

原文链接:https://blog.csdn.net/sinat_41617212/article/details/161080860

相关推荐
2的n次方_12 小时前
健身 Agent:不止视频,更有 AI 人物实时跟练交互
人工智能·音视频·交互·魔珐星云
Advancer-12 小时前
黑马点评plus --异步秒杀重构升级
java·spring boot·重构·intellij-idea
池央12 小时前
给自己的官网装上魔珐星云 Agent:24 小时在线的具身交互助手
交互·具身智能·魔珐星云
多年小白12 小时前
今日A股 拉
大数据·人工智能·深度学习·microsoft·ai
Joy T12 小时前
【碳金融】欧盟CBAM逻辑与“磐石·禹衡”系统的技术对冲分析
人工智能·重构·cbam·碳排放·碳核算·磐石
什么半岛铁盒12 小时前
LangChain常用组件学习
学习·microsoft·langchain
cy_cy00213 小时前
地砖感应屏在数字展厅的应用实践
大数据·科技·人机交互·交互·软件构建
Maimai108081 天前
React如何用 @microsoft/fetch-event-source 落地 SSE:比原生 EventSource 更灵活的实时推送方案
前端·javascript·react.js·microsoft·前端框架·reactjs·webassembly
数据法师1 天前
MotrixNext:接棒经典 Motrix,用 Tauri 2+Rust 重构的下一代开源下载神器
重构·rust·开源