Flutter离线聊天机器人ListView.builder多轮对话循环问题求助
解决Flutter离线聊天机器人循环对话问题
核心问题分析
你的聊天机器人仅能处理第一轮对话,本质是没有跟踪当前对话节点状态,用户选择回答后,程序没有根据预定义的响应关系跳转到对应的后续对话块,始终停留在初始节点。
解决方案步骤
1. 规范对话JSON结构(确保每个节点包含跳转标识)
调整JSON结构为带节点唯一ID、回答与后续节点绑定的格式:
[ { "id": "node_1", "message": "你好,有什么可以帮你的?", "options": [ {"text": "查询订单", "nextNodeId": "node_2"}, {"text": "咨询售后", "nextNodeId": "node_3"} ] }, { "id": "node_2", "message": "请提供你的订单编号:", "options": [ {"text": "返回主菜单", "nextNodeId": "node_1"} ] }, { "id": "node_3", "message": "售后问题请拨打400-XXXXXXX,或留下联系方式我们联系你", "options": [ {"text": "返回主菜单", "nextNodeId": "node_1"} ] } ]
2. 定义对话节点数据模型
创建Dart类映射JSON结构,简化数据处理:
class DialogueNode { final String id; final String message; final List<DialogueOption> options; DialogueNode({ required this.id, required this.message, required this.options, }); factory DialogueNode.fromJson(Map<String, dynamic> json) { var optionsList = json['options'] as List; List<DialogueOption> options = optionsList.map((i) => DialogueOption.fromJson(i)).toList(); return DialogueNode( id: json['id'], message: json['message'], options: options, ); } } class DialogueOption { final String text; final String nextNodeId; DialogueOption({required this.text, required this.nextNodeId}); factory DialogueOption.fromJson(Map<String, dynamic> json) { return DialogueOption( text: json['text'], nextNodeId: json['nextNodeId'], ); } }
3. 维护当前对话节点状态(StatefulWidget实现)
在State类中保存当前节点和所有节点列表,用户选择回答后切换节点并刷新UI:
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; class OfflineChatBot extends StatefulWidget { const OfflineChatBot({super.key}); @override State<OfflineChatBot> createState() => _OfflineChatBotState(); } class _OfflineChatBotState extends State<OfflineChatBot> { late List<DialogueNode> _allNodes; late DialogueNode _currentNode; @override void initState() { super.initState(); _loadDialogueData(); } // 加载本地JSON文件 Future<void> _loadDialogueData() async { String jsonString = await rootBundle.loadString('assets/dialogues.json'); List<dynamic> jsonData = jsonDecode(jsonString); _allNodes = jsonData.map((node) => DialogueNode.fromJson(node)).toList(); // 默认加载初始节点 _currentNode = _allNodes.firstWhere((node) => node.id == 'node_1'); setState(() {}); } // 切换到下一个对话节点 void _navigateToNode(String nextNodeId) { setState(() { _currentNode = _allNodes.firstWhere((node) => node.id == nextNodeId); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('离线聊天机器人')), body: _allNodes.isEmpty ? const Center(child: CircularProgressIndicator()) : Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 机器人消息 Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(8), ), child: Text(_currentNode.message), ), const SizedBox(height: 20), // 用户选项按钮 Expanded( child: ListView.builder( itemCount: _currentNode.options.length, itemBuilder: (context, index) { var option = _currentNode.options[index]; return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: ElevatedButton( onPressed: () => _navigateToNode(option.nextNodeId), child: Text(option.text), ), ); }, ), ), ], ), ), ); } }
4. 关键注意事项
- 将JSON文件放在
assets目录下,并在pubspec.yaml中声明资源:flutter: assets: - assets/dialogues.json - 若原始JSON结构不同,只需调整
DialogueNode的解析逻辑,核心保留节点唯一标识和回答与后续节点的绑定关系即可。 - 若存在无选项的结束节点,可添加固定的返回主菜单按钮,避免对话停滞。
内容的提问来源于stack exchange,提问作者James Baum
相关产品推荐
相关产品推荐

