You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 14:12:49