AI群演请就位—个人博客(七)
本项目是一个基于多智能体系统的AI驱动互动叙事游戏,后端使用Flask + SocketIO实现多Agent协作,前端需要对接后端API实现实时对话、游戏状态管理和角色互动功能。我负责前端Vue项目的完整开发,包括Vuex状态管理、WebSocket实时通信、API接口对接、UI组件开发等。这周我的主要工作内容是在我之前完善的前端框架的基础上,实现与我队友完成的后端部分进行对接。
一、核心功能实现
API接口封装
HTTP接口处理:我封装了统一的axios实例,配置了请求拦截器和响应拦截器。请求拦截器中自动添加sessionId到请求头,这样后续的所有请求都能识别当前游戏会话。响应拦截器统一处理错误码和异常情况,让业务代码更加简洁。
跨域问题解决:在对接过程中遇到了CORS跨域问题,这是因为前端的请求头中包含了自定义字段X-Session-Id,而后端的CORS配置中没有允许这个头。我协调后端同学在Flask的CORS配置中添加了该请求头的白名单,同时前端也做好了兼容处理。
难点:后端存在CORS跨域问题,需要配置请求头白名单。
// src/api/index.js
import axios from 'axios'
const API_BASE_URL = process.env.VUE_APP_API_URL || 'http://localhost:5000'
const request = axios.create({
baseURL: API_BASE_URL,
timeout: 30000,
headers: { 'Content-Type': 'application/json' }
})
// 请求拦截器 - 添加sessionId
request.interceptors.request.use(config => {
const sessionId = localStorage.getItem('sessionId')
if (sessionId) {
config.headers['X-Session-Id'] = sessionId
}
return config
})
// 游戏初始化API
export const initGameSession = (data) => {
return request.post('/api/chat/init', data)
}
// 发送消息API(HTTP备用)
export const sendMessage = (data) => {
return request.post('/api/chat/send', data)
}
WebSocket实时通信管理器
连接管理:当用户进入游戏页面后,前端首先调用初始化接口获取sessionId,然后用这个sessionId建立WebSocket连接。连接成功后,后端会返回确认消息,前端更新连接状态指示器。
消息收发:用户输入消息后,前端通过WebSocket发送user_message事件,携带sessionId和消息内容。后端处理后会通过message事件返回AI的回复,前端监听该事件并将消息添加到对话列表中。
断线重连机制:网络不稳定可能导致WebSocket断开。我实现了自动重连机制,当连接断开时,前端会尝试重新连接,并保留原有的sessionId,确保游戏进度不会丢失。同时界面上显示连接状态指示器,让用户了解当前网络状况。
难点:WebSocket连接超时、断线重连、事件管理。
// src/api/websocket.js
import { io } from 'socket.io-client'
class WebSocketManager {
constructor() {
this.socket = null
this.sessionId = null
this.listeners = new Map()
this.connectionStatus = 'disconnected'
}
connect(sessionId) {
this.socket = io(WS_BASE_URL, {
transports: ['polling', 'websocket'],
reconnection: true,
reconnectionAttempts: 5,
timeout: 20000
})
this.socket.on('connect', () => {
console.log('WebSocket connected')
this.connectionStatus = 'connected'
})
this.socket.on('message', (data) => {
this._emit('message', data)
})
}
sendMessage(content) {
this.socket.emit('user_message', {
sessionId: this.sessionId,
content
})
}
}
export const wsManager = new WebSocketManager()
Vuex状态管理设计
游戏的状态非常复杂,我将其划分为以下几个维度:
会话状态:包括sessionId、连接状态等,用于标识当前游戏会话。
玩家状态:包括玩家名称、选择的角色、属性值(生命值、心厄值)、背包物品、已知线索等。
世界状态:包括当前场景、场景描述、游戏回合数、任务标记等。
NPC状态:包括所有NPC的信任值、怀疑值、情绪状态等。
对话历史:存储所有用户和AI的消息记录。
难点:游戏状态复杂,需要合理划分模块。
// src/store/index.js
export default new Vuex.Store({
state: {
sessionId: null, // 游戏会话ID
player: { // 玩家信息
name: '',
avatar: '👑',
selectedCharacter: 'hua_jianke'
},
gameState: { // 游戏状态
world: { location: '渡厄峰', scene: '', tick: 0 },
player: { inventory: [], known_clues: [], stats: {} },
npcs: {}
},
messages: [], // 对话历史
quests: [], // 任务列表
storyProgress: 0, // 剧情进度
isLoading: false,
isTyping: false
},
mutations: {
SET_GAME_STATE(state, gameState) {
state.gameState = { ...state.gameState, ...gameState }
},
ADD_MESSAGE(state, message) {
state.messages.push({ id: Date.now(), ...message })
}
},
actions: {
async initGame({ commit }, { selectedCharacter }) {
const data = await initGameSession({ selected_character: selectedCharacter })
commit('SET_SESSION_ID', data.sessionId)
commit('SET_GAME_STATE', data.initial_state)
},
async sendMessage({ commit, state }, content) {
const data = await sendMessage({ sessionId: state.sessionId, message: content })
// 处理响应...
}
}
})
二、遇到的问题与解决方案
无限递归调用问题
问题现象:页面加载后控制台报错“Maximum call stack size exceeded”,导致页面白屏。
问题分析:经过调试发现,mounted钩子中调用了initGame方法,而initGame方法内部又触发了某些操作导致自身被重复调用。这是因为我将组件方法命名为initGame,同时Vuex的action也叫initGame,两者发生了冲突。
解决方案:我将组件中的方法重命名为initGameSession,避免与Vuex action同名。同时添加了isInitialized标志,防止重复初始化。这个问题的教训是:在Vue组件中使用mapActions时,要注意避免方法名冲突。
WebSocket连接超时
问题现象:WebSocket频繁连接超时,控制台不断输出timeout错误。
问题分析:经过排查,原因是后端的SocketIO配置不够完善,且前端超时设置过短。此外,在某些网络环境下,WebSocket连接可能被防火墙阻挡。
解决方案:我采取了多层次的处理。首先协调后端同学安装eventlet库并调整async_mode配置。其次增加前端的超时时间到20秒,并调整传输协议顺序(先使用polling再升级到websocket)。最后实现了HTTP备用模式,当WebSocket连接失败时自动降级使用HTTP API,确保游戏可以正常运行。
右侧面板滚动问题
问题现象:任务进度内容较多时,右侧面板无法滚动查看完整内容。
问题分析:这是因为右侧面板没有设置overflow-y: auto样式,导致内容溢出容器但没有滚动条。
解决方案:我为.sidebar-right添加了overflow-y: auto属性,并为任务进度区域单独设置了max-height: 350px和overflow-y: auto。同时美化了滚动条样式,让它更符合整体设计风格。
CORS跨域问题
问题现象:浏览器控制台报错“Request header field x-session-id is not allowed”。
问题分析:前端在请求头中添加了X-Session-Id字段,但后端CORS配置中没有允许这个自定义头。
解决方案:我协调后端同学在Flask的CORS配置的allow_headers列表中添加了X-Session-Id。同时前端也做好了错误处理,当请求失败时给出友好提示。
三、UI设计理念
交互细节优化
消息动画:新消息出现时有淡入动画,让界面变化更加自然。输入框获得焦点时有边框高亮效果,提升操作反馈感。
状态指示:连接状态用不同颜色的圆点表示——绿色已连接、黄色连接中、红色离线。用户发送消息后,AI响应前会显示动态打字动画,让用户知道系统正在处理。
响应式设计:针对不同屏幕尺寸做了适配。在大屏幕上使用三栏布局,在小屏幕(如平板、手机)上自动切换为垂直布局,确保移动端也能获得良好的游戏体验。
信息层级组织
左侧角色信息区按重要性排列——玩家信息在最上方,然后是角色列表,最后是背包和线索。右侧剧情面板同样如此——剧情进度在最显眼位置,接着是当前任务,最后是详细的任务标记。这样的信息层级让用户能快速找到关心的内容。
成果总结
目前实现界面如图,中间为对话区域,ai根据用户行为以及发言作出回应,分别有旁白以及npc发言,随着对话进行,完成目标以及任务后,用户得知的线索会增加,最终走向剧情关键节点。
技术收获
-
WebSocket深入理解:掌握了Socket.IO客户端的使用、断线重连、事件管理等
-
Vuex状态管理:大型应用状态管理的最佳实践
-
组件化设计:可复用组件的设计与封装
-
错误处理:网络异常、WebSocket超时等的降级方案
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)