【StoryEcho】前后端基础框架搭建与交互实现
目录
一、整体架构设计思路
在实际开发阶段,为了让故事引擎能够以服务的形式对外提供能力,同时让用户拥有一个直观的操作界面,我们决定采用前后端分离的架构。后端使用 FastAPI 框架提供 RESTful API,负责故事逻辑处理、会话管理和数据持久化;前端则基于 Vue3 构建单页应用,负责界面渲染、用户交互和状态展示。这样的设计不仅让代码职责清晰,也便于后续扩展——比如后期可以独立升级前端UI或替换后端引擎。我们希望能搭建一个可运行的完整链条:用户在浏览器中输入行动,请求发送到后端,后端经过意图解析、逻辑判定和叙事生成后返回响应,前端更新界面。整个流程不依赖外部AI服务(初期使用规则引擎),确保项目可以独立演示和测试。
对应代码部分,后端入口文件 app.py 中创建了 FastAPI 应用,配置了 CORS 中间件,并定义了 /api/stories、/api/story/start、/api/story/action 三个核心接口。前端 index.html 通过 Vue 的 CDN 和 axios 库引入,app.js 中封装了 API 调用和响应式状态管理。
# backend/app.py 片段
app = FastAPI(title="StoryEcho API")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
二、后端实现:故事引擎与API服务
后端的核心是故事引擎。在初版实现中,为了保证稳定性和快速迭代,我们先实现了一个轻量级的规则引擎(位于 app.py 中的 StoryEngine 类),暂不使用 story_engine.py 。它内置了两个故事模板(奇幻与科幻),每个模板包含初始属性、背包、场景描述和任务目标。引擎接收用户的输入字符串,通过关键词匹配识别意图(探索、攻击、使用药水、查看背包等),然后根据当前状态和随机数计算结果,更新属性、背包,并生成叙事文本。同时,引擎会检测游戏是否触发结局(生命值归零或回合数达到上限)。这种规则驱动的方式虽然没有大语言模型那么灵活,但响应速度快、行为可预测,非常适合作为项目的基础版本。此外,我们还预留了 database.py 用于后续的存档和结局收集功能。
API 服务使用 Pydantic 模型定义请求体,每次开始游戏会生成一个 UUID 作为会话 ID,并将游戏状态存储在内存字典 active_sessions 中。执行行动时,后端根据会话 ID 取出状态,调用引擎的 process_action 方法,再存回字典。
# 后端启动故事和行动处理的端点
@app.post("/api/story/start")
async def start_story(req: StartRequest):
session_id = str(uuid.uuid4())
state = engine.start_story(req.story_id, req.user_id)
active_sessions[session_id] = state
return {"session_id": session_id, "state": {...}}
@app.post("/api/story/action")
async def process_action(req: ActionRequest):
state = active_sessions[req.session_id]
new_state = engine.process_action(state, req.user_input)
active_sessions[req.session_id] = new_state
return {"state": {...}, "session_id": req.session_id}
上图是两个 API 接口:/api/story/start 用于开始新游戏,它生成一个唯一的会话 ID,调用故事引擎初始化游戏状态,并将状态存入内存字典中返回给前端;/api/story/action 用于处理玩家的行动输入,它根据会话 ID 取出对应的游戏状态,调用引擎更新状态后再存回字典,最后返回更新后的状态。
三、前端实现:Vue3驱动的沉浸式界面
在前端,我们创建了一个两栏布局:左侧边栏展示角色属性、背包和任务进度,右侧主区域是叙事窗口和行动输入区。技术实现上,我们直接通过 CDN 引入 Vue3 和 axios,在 app.js 中创建 Vue应用,使用 ref 定义响应式数据。页面初始化时会请求后端故事列表并渲染卡片,玩家点击某个故事后调用开始游戏接口,将返回的消息显示在叙事窗口中。在游戏过程中,用户既可以在输入框中自由输入文字,也可以点击“探索”“背包”“状态”等快捷按钮快速发送指令。每次发送行动后,前端会滚动到消息列表底部,并显示加载状态防止重复提交。
// frontend/app.js 核心交互
const sendAction = async () => {
const action = userInput.value;
gameState.value.messages.push({ role: 'user', content: action });
const response = await axios.post(`${API_BASE}/api/story/action`, {
user_input: action,
session_id: sessionId.value
});
gameState.value = response.data.state;
if (response.data.ending) {
ending.value = true;
endingType.value = getEndingName(response.data.ending);
}
};
上图代码是前端发送玩家行动的核心逻辑:它先从输入框获取用户指令,立即在消息列表中显示用户消息,然后通过axios向后端的行动接口发送请求,收到响应后更新整个游戏状态,并检测是否触发了结局。
四、运行与联调:启动脚本
为了让项目能够快速展示,我们编写了一个 run.py 脚本,它会自动启动后端 uvicorn 服务器,并在控制台输出前端访问方式的提示。后端默认监听 8000 端口,前端可以通过 Live Server 或者 Python 自带的 HTTP 服务器来提供静态文件。由于前端代码中配置的 API_BASE 为 http://localhost:8000,需要确保前后端端口没有冲突并且 CORS 设置正确。我们在后端添加了 allow_origins=["*"],这样在开发阶段可以避免跨域问题。整个项目目前的依赖非常精简(fastapi、uvicorn、pydantic 等),不需要安装大型机器学习框架,执行 pip install -r requirements_simple.txt 即可安装依赖,然后运行 python run.py 即可。
# run.py 启动脚本
backend_process = subprocess.Popen(
[sys.executable, "-m", "uvicorn", "backend.app:app",
"--host", "0.0.0.0", "--port", "8000", "--reload"]
)
print("后端服务已启动: http://localhost:8000")
print("前端请使用 Live Server 打开 frontend/index.html")
运行效果
运行代码,目前可以得到一个初步的界面,如下:

以第一个故事为例,点击进入:

由于目前还未接入大模型,回复都是一些写死的内容,我们后续会在此基础上添加具体功能:

五、AI辅助开发
在实际编码阶段,我们借助了 AI 来搭建出可运行的基础框架,在此基础上再进一步修改。在与AI交互的过程中,我们将之前撰写的项目计划书和任务书上传给 AI,明确告诉它我们需要一个前后端分离的互动叙事平台,后端使用 FastAPI 和 LangGraph(暂不实现),前端使用Vue3,并支持故事选择、行动交互、状态展示等核心功能。AI 根据这些材料生成了初始的项目结构、后端 API 服务文件 app.py、故事引擎原型 story_engine.py(暂未使用)、数据模型 models.py、数据库操作 database.py(暂未使用)以及前端页面(index.html、style.css、app.js)和启动脚本(run.py)。我们得到的是一个完整的代码框架,但其中部分逻辑并不完全符合我们的预期——例如前端与后端的API字段命名不完全匹配。于是我们针对这些细节进行了人工修改:统一了前后端关于游戏状态的数据结构,在前端增加了 mock 模式,以便在后端尚未完全就绪时能够单独测试界面交互等等。

添加 mock 模式
在前端 app.js 中,mock 模式的实现主要通过两个函数完成:startMockGame 用于模拟开始新游戏,mockResponse 用于模拟发送行动后的后端响应。当真实的后端 API 请求失败(例如后端服务未启动或网络错误)时,程序会自动降级到 mock 模式,保证前端界面仍可独立演示和测试交互流程。
以下是 app.js 中关于 mock 模式的完整代码:
// 模拟游戏开始(当后端不可用时)
const startMockGame = (story) => {
const mockState = {
scene_id: 'start',
current_description: '站在森林入口,高大的古树遮天蔽日...',
messages: [
{
role: 'assistant',
content: `欢迎来到《${story.title}》\n\n${story.background}\n\n你想怎么做?`
}
],
attributes: {
hp: 100,
strength: 15,
intelligence: 10,
luck: 8
},
inventory: ['长剑', '面包x3'],
relationships: {},
task_progress: {
main: '探索迷雾森林'
},
turn_count: 0
};
gameState.value = mockState;
gameStarted.value = true;
sessionId.value = 'mock_' + Date.now();
scrollToBottom();
};
// 模拟响应
const mockResponse = (action) => {
const responses = [
`你${action}。周围一片寂静,只有风吹过树叶的声音。\n你感到一阵莫名的紧张,但冒险的热情让你继续前进。\n\n【状态】生命:95 能量:100`,
`你${action},突然发现前方有一个闪光的东西!\n走近一看,是一枚古老的硬币。\n你把它捡起来放进口袋。\n\n【状态】获得物品:古老硬币`
];
const mockMsg = responses[Math.floor(Math.random() * responses.length)];
gameState.value.messages.push({
role: 'assistant',
content: mockMsg
});
// 更新属性
gameState.value.attributes.hp = Math.max(0, gameState.value.attributes.hp - 5);
gameState.value.turn_count++;
scrollToBottom();
};
在 selectStory 函数中,当 axios.post 请求失败时,会调用 startMockGame(story):
try {
const response = await axios.post(`${API_BASE}/api/story/start`, {
story_id: story.story_id,
user_id: 'user_' + Date.now()
});
// ... 正常处理
} catch (error) {
console.error('开始游戏失败:', error);
// 模拟开始游戏
startMockGame(story);
}
在 sendAction 函数中,当发送行动请求失败时,会调用 mockResponse(action):
try {
const response = await axios.post(`${API_BASE}/api/story/action`, {
user_input: action,
session_id: sessionId.value
});
// ... 正常处理
} catch (error) {
console.error('发送行动失败:', error);
// 模拟响应
mockResponse(action);
}
通过这种设计,即使后端完全未启动,用户依然可以在前端选择故事、输入指令,并获得模拟的叙事反馈和属性变化。
六、项目进展总结与下一步计划
至此,我们已经成功搭建了一个可运行的互动叙事平台原型。玩家可以自由选择故事,通过文字指令推动剧情,系统会根据内置规则引擎实时反馈状态变化和结局。不过当前的故事引擎仍然比较简单,剧情分支和NPC交互还有很大的提升空间,且所有叙事内容都是基于规则和随机模板生成的,缺乏个性化体验。
因此,我们的下一步计划是优先接入大语言模型 API。我们打算选择一个合适的模型接口,将用户输入和当前游戏状态作为上下文发送给模型,让模型生成更丰富、更符合剧情逻辑的叙事文本。在成功跑通大模型调用流程之后,我们计划将 LangGraph 状态图工作流完善并整合到引擎中。目前 story_engine.py 虽然已经定义了 intent_parser、logic_referee、narrative_director 三个节点以及状态图结构,但实际运行时仍然采用的是手动顺序调用节点的方式,并未充分体现 LangGraph 的条件边、循环等高级特性。我们计划重新梳理状态图的设计,加入更丰富的分支逻辑(例如根据意图或属性值跳转到不同节点),并利用 LangGraph 的 invoke 方法统一编排流程,使故事引擎具备更强的可扩展性和可维护性。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)