舆情监控的终极形态不是人盯屏幕,而是数字人替你盯------采集、分析、预警、播报,全链路具身交互智能驱动。本文从魔珐星云控制台配置到 Flask + Jinja2 全栈工程源码,拆解本平台如何以参数流 SDK 为 voice,让数字人成为舆情战情室的值班分析师。
一、写在前面
做舆情监控的人都有一个痛点:数据采回来了,情感分析跑完了,热度评分算好了,预警规则也命中了------然后就是盯着屏幕看。一堆图表、一堆数字、一堆告警列表,你得自己判断哪条重要、哪个趋势危险、哪条需要立刻处理。数据不会说话,它只是躺在那里等你去看。本平台的出发点就是改变这一点:不只是做一个数据采集和分析的工具,而是给这套工具加一个有形象、有声音、有肢体表达的数字人分析师,站在数据看板旁边自动播报关键发现、主动预警高危事件,用语音把数据变成洞察。这就是具身交互智能的价值:让数据从「被看」变成「被听」。
魔珐星云PC端官方链接:https://xingyun3d.com?utm_campaign=daily\&utm_source=CSDNwanfen3\&utm_medium=\&utm_term=\&utm_content=
二、产品形态:舆情战情室 + 数字人值班官
市面上大多数舆情监控系统的交互模式都一样:左侧数据列表,右侧图表看板,底部告警条。用户需要自己在三个区域之间来回扫视,手动关联信息。我们做了一版之后发现一个根本问题:用户的注意力是碎片化的。 图表很好看,但用户不知道先看哪个;告警很多,但用户不知道哪个最紧急。
灵感来自一次很自然的想法:舆情中心都有一个值班的人,他负责盯着屏幕,有情况就喊------那如果这个值班的人不是人,而是一个数字人呢?它看到高危告警就主动播报,看到热度飙升就语音提醒。这个想法把整个产品逻辑串通了:数据看板 = 信息载体,数字人 = 具身交互智能入口,流水线 = 触发机制。 三者形成一个完整闭环------采集完成触发分析,分析完成触发预警,预警命中触发播报。用户不需要盯着屏幕,数字人会替他盯着。
最终的产品形态如下:
| 模块 | 功能 | 数字人协同 |
|---|---|---|
| 数据看板 | 统计卡片、来源分布、情感分布、热度排行 | 数据加载后自动播报全局概览 |
| 数据记录 | 多源舆情数据列表,多维度筛选 | 筛选结果语音摘要 |
| 预警管理 | 高危/中危/低危分级告警 | 高危告警即时语音提醒 |
| 采集配置 | 网页采集 API 对接,一键采集 | 采集完成语音播报结果 |
三、魔珐星云控制台配置
数字人「舆析」的形象、场景、音色、表演均由魔珐星云控制台驱动。以下五步完成驱动应用创建与参数配置,是代码层接入 SDK 前的必要前提。
步骤1:魔珐星云控制台创建驱动应用
在魔珐星云控制台创建驱动应用,获取 appId 与 appSecret。这两个密钥是魔珐星云参数流 SDK 的身份凭证,填入项目 avatar.js 的 AVATAR_CONFIG。

步骤2:形象配置
为数字人舆析选择形象资源。舆情分析师的形象需要专业、沉稳、可信赖------这是具身交互智能中「有形象」这一要素的直接来源。

步骤3:场景配置
配置数字人所在的虚拟场景。场景与前端 avatar-stage 容器配合,决定舆析在页面右下角舞台中的视觉呈现。

步骤4:音色配置
选择舆情分析师音色------清晰、有节奏感、专业。音色配置直接影响播报告警时的听感,是具身交互智能中「能说」这一要素的质量基础。

步骤5:表演配置
配置肢体动作与表情,包括待机动作、说话时的手势。表演配置让数字人在播报高危告警时有自然的肢体表达------这是具身交互智能区别于纯语音助手的视觉支柱。

四、核心代码深度解析


4.1 数字人服务:228 行原生 JS 的具身交互智能中枢
avatar.js 是整个系统的 voice 层。它用 IIFE 封装,通过 window.AvatarService 暴露全局接口,管理魔珐星云 SDK 连接、语音播报、字幕同步与实时打断。以下是完整源码(密钥已脱敏):
js
/* ============================================================
* Dataify · 数字人舆情分析服务(原生 JS 版)
* 复用魔珐 XmovAvatar SDK,提供连接/播报/字幕/打断能力
* ============================================================ */
(() => {
'use strict';
/* ============== 配置 ============== */
const AVATAR_CONFIG = {
appId: 'your_avatar_app_id',
appSecret: 'your_avatar_app_secret',
gatewayUrl: 'https://nebula-agent.xingyun3d.com/user/v1/ttsa/session',
dataSource: '2',
customId: 'demo',
};
const AVATAR_IDENTITY = {
name: '舆析',
role: '舆情分析助手',
greeting: '欢迎来到 Dataify 多源舆情监控系统。我是你的舆情分析助手舆析,帮你实时监控多源数据、分析舆情趋势、预警热点事件。',
};
/* ============== 状态 ============== */
const avatarState = {
connected: false,
connecting: false,
instance: null,
subtitle: '',
avatarState: '',
listeners: new Set(),
};
/* ============== 订阅机制 ============== */
function subscribe(listener) {
avatarState.listeners.add(listener);
return () => avatarState.listeners.delete(listener);
}
function notify() {
avatarState.listeners.forEach(l => l());
}
function patch(partial) {
Object.assign(avatarState, partial);
notify();
}
/* ============== DOM 引用 ============== */
const $ = (sel) => document.querySelector(sel);
const containerId = 'xmov-avatar-container';
function getSubtitleEl() {
return $('#avatar-subtitle');
}
function getLoadingEl() {
return $('#avatar-loading');
}
/* ============== 连接 ============== */
async function connect() {
if (avatarState.connected || avatarState.connecting) return;
const containerEl = document.getElementById(containerId);
if (!containerEl) {
console.error('[Avatar] 容器不存在:', containerId);
return;
}
patch({ connecting: true });
updateLoadingUI(true);
try {
const url = new URL(AVATAR_CONFIG.gatewayUrl);
url.searchParams.append('data_source', AVATAR_CONFIG.dataSource);
url.searchParams.append('custom_id', AVATAR_CONFIG.customId);
let resolveConnect;
let rejectConnect;
const connectPromise = new Promise((res, rej) => {
resolveConnect = res;
rejectConnect = rej;
});
const options = {
containerId: `#${containerId}`,
appId: AVATAR_CONFIG.appId,
appSecret: AVATAR_CONFIG.appSecret,
enableDebugger: false,
gatewayServer: url.toString(),
onProxyWidgetEvent: (event) => console.log('[Avatar SDK事件]', event),
onStateChange: (state) => {
patch({ avatarState: state });
},
onMessage: async (error) => {
if (!avatarState.connected) {
rejectConnect(new Error(error.message || 'SDK连接失败'));
}
},
onVoiceStateChange: (status) => {
if (status.includes('end')) {
patch({ avatarState: 'interactive_idle' });
}
},
};
const avatar = new window.XmovAvatar(options);
await new Promise(r => setTimeout(r, 3000));
await avatar.init({
onDownloadProgress: (progress) => {
if (progress >= 100) {
resolveConnect(true);
}
},
onClose: () => {
patch({ avatarState: '', connected: false });
},
});
const timeout = new Promise((_, rej) => setTimeout(() => rej(new Error('连接超时')), 15000));
try {
await Promise.race([connectPromise, timeout]);
} catch (e) {
console.warn('[Avatar] 连接等待结束:', e.message);
}
avatarState.instance = avatar;
patch({ connected: true, connecting: false });
updateLoadingUI(false);
console.log('[Avatar] 连接成功');
setTimeout(() => {
speakText(AVATAR_IDENTITY.greeting);
}, 800);
} catch (err) {
console.error('[Avatar] 连接失败:', err);
patch({ connected: false, connecting: false });
updateLoadingUI(false);
}
}
function disconnect() {
if (avatarState.instance) {
try {
avatarState.instance.stop();
avatarState.instance.destroy();
} catch (e) {
console.error('[Avatar] 断开失败:', e);
}
avatarState.instance = null;
patch({ connected: false, avatarState: '' });
}
}
/* ============== 播报 ============== */
function speakText(text) {
if (!avatarState.instance || !text) return;
const ssml = `<speak>${text}</speak>`;
avatarState.instance.speak(ssml, true, true);
patch({ avatarState: 'speak' });
}
function interrupt() {
if (!avatarState.instance) return;
try {
if (typeof avatarState.instance.interactiveidle === 'function') {
avatarState.instance.interactiveidle();
} else if (typeof avatarState.instance.interrupt === 'function') {
avatarState.instance.interrupt();
}
patch({ avatarState: 'interactive_idle' });
} catch (e) {
console.error('[Avatar] 打断失败:', e);
}
}
/* ============== 字幕 ============== */
function setSubtitle(text) {
patch({ subtitle: text });
const el = getSubtitleEl();
if (el) {
el.textContent = text;
el.classList.toggle('is-on', !!text);
}
}
/* ============== UI 更新 ============== */
function updateLoadingUI(show) {
const el = getLoadingEl();
if (el) {
el.style.display = show ? 'flex' : 'none';
}
}
/* ============== 初始化 ============== */
function init() {
console.log('[Avatar] 初始化数字人服务');
connect();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
/* ============== 暴露全局接口 ============== */
window.AvatarService = {
connect,
disconnect,
speakText,
interrupt,
setSubtitle,
subscribe,
getState: () => ({
connected: avatarState.connected,
connecting: avatarState.connecting,
subtitle: avatarState.subtitle,
avatarState: avatarState.avatarState,
}),
IDENTITY: AVATAR_IDENTITY,
};
})();
设计要点:
- IIFE 封装 + 全局****桥接:IIFE 避免污染全局命名空间,window.AvatarService 暴露 connect/disconnect/speakText/interrupt/setSubtitle/subscribe。Flask 模板中的任何页面都可以通过这个全局接口让舆析播报------不需要框架,不需要构建工具,一个 script 标签即可串联具身交互智能链路
- 发布-订阅模式:subscribe + patch 让任何页面模块都能感知数字人状态变化。预警页面可以订阅告警事件,采集页面可以订阅完成事件------这是具身交互智能跨页面复用的架构基础
- SSML 语音驱动:speakText 将文本包裹为 SSML 发送给魔珐星云参数流 SDK,SDK 同步驱动发音与肢体动作。speak 的第二、三个参数 true 分别表示打断当前播报和启用表情动画
- 15 秒超时兜底 + 自动初始化:Promise.race 配合 15 秒超时,防止魔珐云端连接异常时页面无限等待。DOMContentLoaded 后自动调用 init() → connect(),页面加载即连接。连接失败不阻塞用户使用------具身交互智能是增强层,不是阻断层
4.2 舆情分析引擎:45 行 Python 的情感打分与热度计算
sentiment.py 是数据流水线的分析核心。它为每条采集记录打上情感标签和热度分数,这些结果最终由数字人舆析播报给用户:
js
# src/sentiment.py
class SentimentScorer:
def __init__(self, sentiment_cfg: dict, heat_weights: dict):
self.positive = [w.lower() for w in sentiment_cfg.get("positive", [])]
self.negative = [w.lower() for w in sentiment_cfg.get("negative", [])]
self.weights = heat_weights or {}
def analyze(self, item: dict) -> dict:
"""为单条记录补充 sentiment、sentiment_score、heat_score 字段。"""
text = (item.get("content", "") + " " + item.get("title", "")).lower()
pos_hits = sum(text.count(w) for w in self.positive)
neg_hits = sum(text.count(w) for w in self.negative)
score = pos_hits - neg_hits
if score > 0:
sentiment = "positive"
elif score < 0:
sentiment = "negative"
else:
sentiment = "neutral"
item["sentiment"] = sentiment
item["sentiment_score"] = score
item["heat_score"] = self._heat(item)
return item
def _heat(self, item: dict) -> float:
w = self.weights
heat = (
item.get("likes", 0) * w.get("likes", 1.0)
+ item.get("comments", 0) * w.get("comments", 2.0)
+ item.get("shares", 0) * w.get("shares", 3.0)
+ len(item.get("content", "")) * w.get("content_length", 0.01)
)
return round(heat, 2)
def analyze_batch(self, items: list) -> list:
return [self.analyze(item) for item in items]
设计要点:
- 正负词典累计打分:不依赖机器学习模型,用配置化的正/负面词典做轻量级情感判断。正面词命中数减去负面词命中数,大于 0 为正面,小于 0 为负面,等于 0 为中性。简单但有效,且完全可控
- 热度加权公式:点赞 × 1.0 + 评论 × 2.0 + 分享 × 3.0 + 文本长度 × 0.01。分享的权重最高(3 倍),因为分享行为代表内容的传播意愿,是舆情扩散的最强信号
- 数字人播报衔接:分析结果写入 sentiment 和 heat_score 字段,预警引擎据此判断是否触发告警,告警命中后由舆析语音播报------从数据入库到语音输出,整条具身交互智能链路全自动
4.3 关键词预警:45 行 Python 的规则引擎
alert.py 根据配置的预警规则对每条记录进行评估,命中关键词或热度阈值即生成告警。高危告警由数字人舆析即时播报:
js
# src/alert.py
class AlertEngine:
def __init__(self, alert_rules: list):
self.rules = alert_rules or []
def evaluate(self, item: dict) -> list:
"""对单条记录评估所有规则,返回命中的告警。"""
alerts = []
text = (item.get("content", "") + " " + item.get("title", "")).lower()
for rule in self.rules:
hit_reason = None
keywords = rule.get("keywords", [])
matched = [kw for kw in keywords if kw.lower() in text]
if matched:
hit_reason = f"命中关键词: {', '.join(matched)}"
min_heat = rule.get("min_heat_score")
if min_heat is not None and item.get("heat_score", 0) >= min_heat:
reason = f"热度 {item.get('heat_score')} ≥ {min_heat}"
hit_reason = f"{hit_reason}; {reason}" if hit_reason else reason
if hit_reason:
alerts.append({
"rule": rule.get("name", "未命名规则"),
"level": rule.get("level", "low"),
"reason": hit_reason,
"source": item.get("source", ""),
"content": item.get("content", "")[:120],
"heat_score": item.get("heat_score", 0),
"sentiment": item.get("sentiment", ""),
})
return alerts
def evaluate_batch(self, items: list) -> list:
all_alerts = []
for item in items:
all_alerts.extend(self.evaluate(item))
# 高危优先
order = {"high": 0, "medium": 1, "low": 2}
all_alerts.sort(key=lambda a: order.get(a["level"], 3))
return all_alerts
设计要点:
- 双条件触发:关键词匹配 + 热度阈值,两个条件可以独立触发也可以组合触发。hit_reason 字段记录具体命中原因,方便舆析播报时告诉用户「为什么报警」
- 高危优先排序:evaluate_batch 对告警按 high > medium > low 排序,确保数字人播报时先说最紧急的
- 具身交互智能闭环:预警引擎输出告警列表 → Flask 推送给前端 → 前端调用 window.AvatarService.speakText 让舆析播报。从数据异常到语音提醒,全链路不超过 1 秒
4.4 三段式采集:网页采集 API 客户端的提交-轮询-拉取架构
客户端封装了网页采集 API 的三段式调用流程。这是整条具身交互智能链路的数据源头:
js
# src/dataify_client.py
class DataifyClient:
def __init__(self, api_key: str, timeout: int = 60):
if not api_key:
raise ValueError("api_key 不能为空")
self.api_key = api_key
self.timeout = timeout
self.session = requests.Session()
# ---------- 1) 提交任务 ----------
def submit(self, spider_name: str, spider_id: str, parameters: list,
file_name: str = "TasksID") -> str:
"""提交采集任务,返回 task_id。"""
payload = {
"spider_name": spider_name,
"spider_id": spider_id,
"spider_parameters": json.dumps(parameters, ensure_ascii=False),
"spider_errors": "true",
"file_name": file_name,
}
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/x-www-form-urlencoded",
}
resp = self.session.post(BUILDER_URL, headers=headers, data=payload,
timeout=self.timeout)
data = self._parse_json(resp)
task_id = (data.get("data") or {}).get("task_id")
if not task_id:
raise DataifyError(f"提交任务失败,未返回 task_id: {resp.text}")
return task_id
# ---------- 2) 轮询获取下载地址 ----------
def fetch_download_url(self, task_id: str, result_type: str = "json",
poll_interval: int = 15, max_wait: int = 600) -> str:
"""轮询任务,直到就绪并返回结果下载地址。"""
headers = {"ApiKey": self.api_key, "Content-Type": "application/json"}
body = {"task_id": task_id, "type": result_type}
waited = 0
while True:
resp = self.session.post(DOWNLOAD_API_URL, headers=headers, json=body,
timeout=self.timeout)
data = self._parse_json(resp)
download = (data.get("data") or {}).get("download") if data.get("data") else None
if download:
return self._normalize_url(download)
message = data.get("message", "")
if data.get("code") not in (200, 500) or "not found" not in message:
if data.get("code") not in (200, 500):
raise DataifyError(f"查询任务异常: {resp.text}")
if waited >= max_wait:
raise DataifyError(
f"任务 {task_id} 等待超时({max_wait}s),最后响应: {resp.text}")
time.sleep(poll_interval)
waited += poll_interval
# ---------- 3) 拉取结果内容 ----------
def download_result(self, download_url: str) -> Any:
resp = self.session.get(download_url, timeout=self.timeout)
return self._parse_json(resp)
# ---------- 组合:一站式采集 ----------
def run(self, spider_name: str, spider_id: str, parameters: list,
result_type: str = "json", poll_interval: int = 15,
max_wait: int = 600) -> Any:
task_id = self.submit(spider_name, spider_id, parameters)
url = self.fetch_download_url(task_id, result_type, poll_interval, max_wait)
return self.download_result(url)
设计要点:
- 三段式架构:提交任务 → 轮询等待 → 拉取结果。采集 API 是异步的,提交后返回 task_id,需要轮询直到数据就绪。fetch_download_url 以 15 秒间隔轮询,最长等待 600 秒
- run() 一站式封装:将三段式调用封装为一个同步方法,调用方只需传入爬虫名和参数,无需关心异步细节
- 具身交互智能****数据源:采集完成后,Flask 后端自动执行分析 → 预警 → 入库流水线,前端收到结果后触发舆析播报「采集完成,新增 X 条记录,发现 Y 条告警」------从数据采集到语音汇报全自动
4.5 流水线串联:app.py 的采集-分析-预警-入库全链路
前面四个模块各自独立,但真正让它们协同工作的是 app.py 中的 _run_pipeline 函数。它是整条具身交互智能链路的中枢调度器:
js
# app.py
def _run_pipeline(items: list) -> dict:
"""执行流水线:情感分析 → 预警检测 → 入库。返回统计信息。"""
kw_cfg = config.load_keywords_config()
scorer = SentimentScorer(kw_cfg.get("sentiment", {}), kw_cfg.get("heat_weights", {}))
items = scorer.analyze_batch(items)
engine = AlertEngine(kw_cfg.get("alert_rules", []))
alerts = engine.evaluate_batch(items)
inserted, updated = storage.upsert_records(items)
saved = storage.save_alerts(alerts)
return {
"message": f"新增 {inserted} 条,更新 {updated} 条,告警 {saved} 条",
"inserted": inserted,
"updated": updated,
"alerts_count": saved,
"total_records": storage.count_records(),
}
设计要点:
- 四步串行:加载配置 → 情感分析 → 预警检测 → 入库存储。每一步的输出是下一步的输入,数据在流水线中逐级丰富
- 配置驱动:SentimentScorer 和 AlertEngine 的词典、权重、规则全部来自 config 模块,修改配置即可调整分析策略,无需改代码
- 具身交互智能触发点:_run_pipeline 返回的 message 字段(如「新增 12 条,更新 3 条,告警 2 条」)正是前端调用 window.AvatarService.speakText 让舆析播报的内容------流水线跑完,数字人开口汇报
4.6 SQLite 增量入库:去重 + Upsert 的持久化策略
storage.py 用 SQLite 实现数据的去重入库。dedup_key 作为主键确保同一条舆情不会重复入库,核心是 INSERT ON CONFLICT 的 upsert 语义:
js
# src/storage.py
class Storage:
def __init__(self, db_path: Path):
self.db_path = str(db_path)
self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
self.conn.row_factory = sqlite3.Row
self._init_schema()
def _init_schema(self) -> None:
self.conn.executescript("""
CREATE TABLE IF NOT EXISTS records (
dedup_key TEXT PRIMARY KEY,
source TEXT,
title TEXT,
content TEXT,
author TEXT,
publish_time TEXT,
url TEXT,
likes INTEGER,
comments INTEGER,
shares INTEGER,
sentiment TEXT,
sentiment_score INTEGER,
heat_score REAL,
collected_at TEXT DEFAULT (datetime('now','localtime'))
);
CREATE TABLE IF NOT EXISTS alerts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
rule TEXT,
level TEXT,
reason TEXT,
source TEXT,
content TEXT,
heat_score REAL,
sentiment TEXT,
created_at TEXT DEFAULT (datetime('now','localtime'))
);
""")
self.conn.commit()
def upsert_records(self, records: list) -> tuple:
"""增量入库:新增返回 inserted,已存在返回 updated。"""
inserted, updated = 0, 0
for item in records:
exists = self.conn.execute(
"SELECT 1 FROM records WHERE dedup_key = ?",
(item["dedup_key"],),
).fetchone()
self.conn.execute("""
INSERT INTO records
(dedup_key, source, title, content, author, publish_time,
url, likes, comments, shares, sentiment, sentiment_score, heat_score)
VALUES (:dedup_key, :source, :title, :content, :author, :publish_time,
:url, :likes, :comments, :shares, :sentiment, :sentiment_score, :heat_score)
ON CONFLICT(dedup_key) DO UPDATE SET
likes=excluded.likes, comments=excluded.comments,
shares=excluded.shares, heat_score=excluded.heat_score,
sentiment=excluded.sentiment, sentiment_score=excluded.sentiment_score
""",
{k: item.get(k) for k in [
"dedup_key", "source", "title", "content", "author",
"publish_time", "url", "likes", "comments", "shares",
"sentiment", "sentiment_score", "heat_score"]},
)
if exists:
updated += 1
else:
inserted += 1
self.conn.commit()
return inserted, updated
设计要点:
- INSERT ON CONFLICT upsert:SQLite 的原生 upsert 语法,dedup_key 冲突时自动更新互动数据(likes/comments/shares)和情感/热度字段。新数据插入,旧数据更新,一条 SQL 搞定
- records + alerts 双表:records 存舆情数据,alerts 存告警记录,两张表通过 content 和 source 关联
- 数字人播报数据基础:Storage 的 get_dashboard_stats 方法为前端看板提供统计数据(来源分布、情感分布、热度 Top10、最新告警),这些统计数据正是舆析播报时的内容来源
五、技术架构:以魔珐星云数字人为 voice 的四层流水线
整个系统的技术架构可以概括为四层流水线,以魔珐星云数字人为 voice 层协同工作:
bash
采集层 → 分析层 → 预警层 → 展示层 + voice层
| 层级 | 职责 | 核心模块 |
|---|---|---|
| 采集层 | 网页采集 API 三段式调用,多源数据归一化 | scraper_client.py + normalizer.py |
| 分析层 | 情感打分、热度评分 | sentiment.py |
| 预警层 | 关键词匹配、热度阈值、分级告警 | alert.py |
| 展示层 + voice层 | Flask Web 看板 + 魔珐星云数字人播报 | app.py + avatar.js + Jinja2 模板 |
魔珐星云数字人独立于业务流水线,通过 window.AvatarService 全局接口与前端页面桥接。采集完成、预警命中、数据加载------每个关键节点都可以触发舆析的语音播报。这是具身交互智能在 Flask + Jinja2 传统 Web 项目中的落地方案:不需要前端框架,不需要状态管理库,一个 IIFE 封装的 JS 服务即可串联全链路。
六、写在最后
回顾整个项目,最大的收获不是技术实现,而是一个认知转变:
具身交互智能的价值不在于「数字人能说话」,而在于「让数据有了声音」。
一个没有数字人的舆情监控系统,用户需要自己盯着屏幕看图表、翻告警、判断优先级。但一个有数字人值班官的系统,用户打开页面就听到「当前共采集 256 条记录,发现 3 条高危告警,最高热度 892 分」------信息从「被看」变成了「被听」,用户的认知负担大幅降低。
这就是场景的力量。技术是基础,场景是灵魂。
本平台的具身交互智能实践才刚刚开始。下一步,我们计划加入定时采集自动播报、舆情趋势预测语音摘要、以及基于告警级别的智能播报优先级调度。如果你也对具身交互智能感兴趣,欢迎交流。
魔珐星云PC端官方链接:https://xingyun3d.com?utm_campaign=daily\&utm_source=CSDNwanfen3\&utm_medium=\&utm_term=\&utm_content=