人们眼中的天才之所以卓越非凡,并非天资超人一等而是付出了持续不断的努力。1万小时的锤炼是任何人从平凡变成超凡的必要条件。------------ 马尔科姆·格拉德威尔

🌟 Hello,我是Xxtaoaooo!
🌈 "代码是逻辑的诗篇,架构是思想的交响"
项目简介
大家好!今天给大家分享我最近做的一个开源项目------ 灵魂讲述者(Soul Teller)。
这是一个基于魔珐星云具身智能平台的AI交互式叙事应用。简单来说:你选择一个故事世界,3D数字人实时给你讲故事,你可以通过对话和分支选择影响剧情走向,每一条选择都会带来不同的结局。
和传统的文字冒险游戏不同,这个故事没有预设脚本------所有剧情都由AI实时生成,每一次体验都是独一无二的。
新用户注册魔珐星云可以填写邀请码:J36AKZEFMK,可免费获取积分体验数字人,快来试试吧!
魔法星云地址: https://xingyun3d.com/
GitHub 项目地址: https://github.com/xtdexw/soul-teller
一、效果展示
1.1 故事世界选择
用户进入应用后,首先选择一个故事世界。目前支持赛博朋克、奇幻森林、悬疑推理等多种风格。

1.2 互动播放室
进入故事后,界面分为两个区域:
- 左侧:3D数字人实时渲染,配合语音讲述剧情
- 右侧:1. 分支选项面板,AI生成3个分支供选择 2. 剧情面板,展示当前剧情内容 3. Ai对话面板:实时沟通改变分支、剧情走向
同时支持故事剧情导出。



1.3 设置面板
所有API密钥都在页面内配置,密钥存储在浏览器本地,支持掩码显示,安全开箱即用。

二、核心功能
2.1 3D数字人实时讲述
通过魔珐星云SDK实现数字人实时渲染与语音合成:
// 数字人初始化
const sdk = new XmovAvatar({
containerId: '#avatar-container',
appId: config.appId,
appSecret: config.appSecret,
gatewayServer: config.gatewayServer,
});
数字人支持:
- 流式语音合成(TTS),边生成边朗读
- 文本分块朗读与状态同步
- 手动/自动连接控制
2.2 AI分支剧情系统
这是整个项目最核心的部分。一次API调用同时获取:续写内容(200-400字)+ 3个分支选项
// 统一故事生成API
async generateStory(context: StoryContext): Promise<StoryNode> {
const response = await fetch(config.apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.apiKey}`,
},
body: JSON.stringify({
model: 'qwen2.5-72b-instruct',
messages: this.buildPrompt(context),
}),
});
// 同时返回剧情续写 + 3个分支选项
return this.parseStoryResponse(response);
}
用户选择任意一个分支,AI会基于你的选择继续生成下一段剧情,形成真正的分支叙事体验。
2.3 智能上下文管理
为了让AI记住之前的剧情走向,我实现了一套混合上下文策略:
// 混合上下文策略
buildContext(currentNode: StoryNode): string {
return [
this.getRecentStory(10), // 最近10段剧情
this.vectorSearch(2), // 语义检索2个最相关历史节点
this.getUserDialogue(), // 用户的实时对话输入
].join('\n');
}
- 最近剧情:内存中保存最近10段,保证连贯性
- 向量检索:基于IndexedDB实现的本地向量存储,语义检索最相关的历史节点
- 用户对话:实时纳入用户的对话输入
这套机制确保AI在长篇故事中也能"记住"之前的情节和伏笔。
2.4 动态背景系统
根据故事进展自动切换背景,10张背景图片循环使用,配合1秒淡入淡出的平滑过渡:
// 根据节点序号切换背景
const bgIndex = (nodeIndex % 10) + 1;
setBackgroundWithTransition(`/images/back${bgIndex}.webp`);
2.5 智能连接状态管理
未连接数字人时,AI对话和分支选择会被禁用,避免用户在未就绪时操作:
// 连接状态控制
const isConnected = useStore(state => state.isConnected);
// 未连接时禁用交互
disabled={!isConnected}
三、技术架构
3.1 技术栈
|------------|--------------------------------------------|
| 技术 | 说明 |
| 前端框架 | React 18 + TypeScript |
| 构建工具 | Vite |
| 状态管理 | Zustand + persist(持久化) |
| 样式方案 | TailwindCSS |
| 数字人SDK | 魔珐星云具身驱动SDK (xmovAvatar) |
| 大语言模型 | 通义千问 Qwen2.5-72B-Instruct (ModelScope API) |
| 向量模型 | 通义千问 Embedding-8B |
| 向量存储 | 自研轻量级向量存储(基于IndexedDB) |
3.2 项目结构
soul-teller/
├── src/
│ ├── components/ # UI组件
│ │ ├── StoryHub/ # 故事世界选择
│ │ ├── PlayRoom/ # 互动播放室(核心页面)
│ │ ├── Settings/ # 设置面板
│ │ ├── Dialogue/ # AI对话面板
│ │ └── StoryTeller/ # 数字人容器
│ ├── services/ # 服务层
│ │ ├── StoryEngine.ts # 故事引擎(核心调度)
│ │ ├── StoryGenerator.ts # 故事生成器(AI调用)
│ │ ├── VectorStore.ts # 向量存储(IndexedDB)
│ │ └── XingyunSDK.ts # 星云SDK封装
│ ├── hooks/ # 自定义Hooks
│ │ └── useAvatar.ts # 数字人生命周期管理
│ ├── store/ # Zustand状态管理
│ │ └── useStore.ts # 全局状态(含persist)
│ ├── utils/ # 工具函数
│ │ ├── secureStorage.ts # 安全存储(密钥掩码)
│ │ └── textChunker.ts # 文本分块(TTS朗读)
│ ├── types/ # TypeScript类型定义
│ │ ├── story.ts # 故事节点、分支类型
│ │ └── interaction.ts # 交互事件类型
│ └── App.tsx
├── public/ # 静态资源(背景图片等)
└── package.json
3.3 核心架构图
flowchart TB
%%{init: {'theme':'base', 'themeVariables':{'primaryColor':'#7c3aed'}}}%%
subgraph UI ["UI Layer"]
A["StoryHub<br/>故事世界选择"]:::ui
B["PlayRoom<br/>互动播放室"]:::ui
C["Dialogue<br/>AI对话面板"]:::ui
D["StoryTeller<br/>数字人容器"]:::ui
end
subgraph Service ["Service Layer"]
E["StoryEngine<br/>故事引擎"]:::svc
F["StoryGenerator<br/>AI故事生成"]:::svc
G["VectorStore<br/>向量存储"]:::svc
end
subgraph External ["External APIs"]
H["魔珐星云SDK<br/>3D数字人"]:::ext
I["通义千问API<br/>Qwen2.5-72B"]:::ext
J["IndexedDB<br/>本地存储"]:::ext
end
B --> E
C --> E
D --> H
E --> F
E --> G
F --> I
G --> J
classDef ui fill:#ede9fe,stroke:#7c3aed,stroke-width:2px
classDef svc fill:#dbeafe,stroke:#2563eb,stroke-width:2px
classDef ext fill:#fef3c7,stroke:#d97706,stroke-width:2px
图1:灵魂讲述者系统架构图
四、快速开始
4.1 克隆项目
git clone https://github.com/xtdexw/soul-teller.git
cd soul-teller
4.2 安装依赖
npm install
4.3 启动开发服务器
npm run dev
4.4 配置API密钥
打开应用后,点击右上角设置按钮:
通义千问配置:
- 访问 ModelScope
- 注册并获取API Key
- 在设置面板的"API密钥"标签中填入密钥
魔珐星云配置:
- 访问 魔珐星云官网
- 注册账号(填写邀请码 J36AKZEFMK 可免费获取积分体验数字人!)
- 创建应用,获取App ID和App Secret
- 在设置面板的"数字人连接"标签中配置
4.5 开始体验
连接数字人 → 选择故事世界 → 点击"开始冒险" → 选择分支或对话推动剧情
五、项目亮点
5.1 一个API调用搞定剧情 + 分支
传统做法是分两次调用------先生成剧情,再生成选项。我把这两步合并成一次调用,既减少了延迟,又保证了选项和剧情的语义连贯性。
5.2 本地向量存储,零服务端依赖
基于IndexedDB自研了一套轻量级向量存储,不需要部署Pinecone或Milvus这类向量数据库,全部在浏览器本地完成。对于这种中小规模的上下文检索场景,完全够用。
5.3 密钥安全设计
- 所有API密钥存储在浏览器localStorage中,不上传任何服务器
- 设置面板中密钥仅显示前4位和后4位,防止泄露
- 用户完全控制数字人的连接与断开
六、使用流程
flowchart TD
%%{init: {'theme':'base', 'themeVariables':{'primaryColor':'#059669'}}}%%
A["启动应用"]:::step --> B["连接数字人"]:::step
B --> C["选择故事世界"]:::step
C --> D["点击开始冒险"]:::step
D --> E["数字人朗读开场剧情"]:::step
E --> F{"选择分支 or AI对话?"}:::choice
F -->|选择分支| G["AI生成新剧情"]:::step
F -->|自由对话| H["AI调整分支选项"]:::step
G --> E
H --> E
classDef step fill:#d1fae5,stroke:#059669,stroke-width:2px
classDef choice fill:#fef3c7,stroke:#d97706,stroke-width:2px
图2:用户使用流程图
七、写在最后
这个项目探索了一种新的互动叙事形式------不是预设脚本的选择题,而是AI实时生成的开放世界故事。每一次对话、每一个选择,都会让故事走向不同的方向。
如果你对AI叙事、数字人、向量存储这些方向感兴趣,欢迎一起交流贡献!
如果觉得这个项目对你有帮助,欢迎给个Star ⭐ ️!
GitHub地址: https://github.com/xtdexw/soul-teller
🌟 嗨,我是Xxtaoaooo!
⚙️ 【点赞】让更多同行看见深度干货
🚀 【关注】持续获取行业前沿技术与经验
🧩 【评论】分享你的实战经验或技术困惑
作为一名技术实践者,我始终相信:
每一次技术探讨都是认知升级的契机,期待在评论区与你碰撞灵感火花🔥