import 'package:flutter/material.dart'; import 'package:ainoval/utils/web_theme.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../blocs/setting_generation/setting_generation_bloc.dart'; import '../../../blocs/setting_generation/setting_generation_event.dart'; import '../../../blocs/setting_generation/setting_generation_state.dart'; import '../../../models/setting_node.dart'; import 'setting_node_widget.dart'; import 'ai_shimmer_placeholder.dart'; import '../../../utils/logger.dart'; import '../../../widgets/common/top_toast.dart'; /// 节点与层级信息的包装类 class _NodeWithLevel { final SettingNode node; final int level; const _NodeWithLevel({ required this.node, required this.level, }); } /// 设定树组件 class SettingsTreeWidget extends StatelessWidget { final String? lastInitialPrompt; final String? lastStrategy; final String? lastModelConfigId; final String? novelId; final String? userId; const SettingsTreeWidget({ Key? key, this.lastInitialPrompt, this.lastStrategy, this.lastModelConfigId, this.novelId, this.userId, }) : super(key: key); @override Widget build(BuildContext context) { return BlocBuilder( buildWhen: (previous, current) { // 类型变化:一定重建 if (previous.runtimeType != current.runtimeType) return true; // 进行中:当节点树/渲染相关/选中/视图模式或操作文案改变时才重建 if (previous is SettingGenerationInProgress && current is SettingGenerationInProgress) { return previous.activeSession.rootNodes != current.activeSession.rootNodes || previous.renderedNodeIds != current.renderedNodeIds || previous.selectedNodeId != current.selectedNodeId || previous.viewMode != current.viewMode || previous.currentOperation != current.currentOperation; } // 完成:当节点树/渲染集合/选中/视图模式/活跃会话切换时才重建 if (previous is SettingGenerationCompleted && current is SettingGenerationCompleted) { return previous.activeSession.rootNodes != current.activeSession.rootNodes || previous.renderedNodeIds != current.renderedNodeIds || previous.selectedNodeId != current.selectedNodeId || previous.viewMode != current.viewMode || previous.activeSessionId != current.activeSessionId; } // 修改中:当节点树/渲染集合/选中/修改目标/是否更新中变化时才重建 if (previous is SettingGenerationNodeUpdating && current is SettingGenerationNodeUpdating) { return previous.activeSession.rootNodes != current.activeSession.rootNodes || previous.renderedNodeIds != current.renderedNodeIds || previous.selectedNodeId != current.selectedNodeId || previous.updatingNodeId != current.updatingNodeId || previous.isUpdating != current.isUpdating; } // 就绪:会话/活跃会话/视图模式变化 if (previous is SettingGenerationReady && current is SettingGenerationReady) { return previous.sessions != current.sessions || previous.activeSessionId != current.activeSessionId || previous.viewMode != current.viewMode; } // 其他状态:保守起见重建 return true; }, builder: (context, state) { // 🔧 新增:详细的状态日志 AppLogger.i('SettingsTreeWidget', '🔄 状态变更: ${state.runtimeType}'); // 加载状态 if (state is SettingGenerationLoading) { AppLogger.i('SettingsTreeWidget', '⏳ 显示加载状态'); return const AIShimmerPlaceholder(); } // 生成进行中状态 if (state is SettingGenerationInProgress) { AppLogger.i('SettingsTreeWidget', '🚀 显示生成进行中状态 - 已渲染节点: ${state.renderedNodeIds.length}'); return _buildInProgressView(context, state); } // 🔧 新增:节点修改中状态 if (state is SettingGenerationNodeUpdating) { AppLogger.i('SettingsTreeWidget', '🔧 显示节点修改中状态 - 修改节点: ${state.updatingNodeId}'); return _buildNodeUpdatingView(context, state); } // 生成完成状态 if (state is SettingGenerationCompleted) { AppLogger.i('SettingsTreeWidget', '✅ 显示完成状态 - 会话: ${state.activeSessionId}'); return _buildCompletedView(context, state); } // 保存成功状态 - 仍然显示完成视图,避免界面闪烁 if (state is SettingGenerationSaved) { AppLogger.i('SettingsTreeWidget', '💾 显示保存成功状态,会话数: ${state.sessions.length}'); return _buildSavedView(context, state); } // 无会话状态 if (state is SettingGenerationReady) { AppLogger.i('SettingsTreeWidget', '🎯 显示就绪状态,会话数: ${state.sessions.length}'); return _buildNoSessionView(context, state); } // 错误状态 if (state is SettingGenerationError) { AppLogger.w('SettingsTreeWidget', '❌ 显示错误状态: ${state.message}'); return _buildErrorView(context, state); } // 默认状态(初始状态等) AppLogger.w('SettingsTreeWidget', '🤔 未知状态: ${state.runtimeType}'); return _buildNoSessionView(context, state); }, ); } Widget _buildInProgressView(BuildContext context, SettingGenerationInProgress state) { // 如果没有任何已渲染的节点(不管渲染状态如何),显示等待状态 if (state.renderedNodeIds.isEmpty) { return const AIShimmerPlaceholder(); } // 显示流式渲染界面(进度/提示统一由父级状态条显示,避免重复) return Column( children: [ Expanded( child: _buildStreamingTreeView(context, state), ), ], ); } Widget _buildCompletedView(BuildContext context, SettingGenerationCompleted state) { // 🔧 新增:详细的渲染日志 AppLogger.i('SettingsTreeWidget', '🎨 渲染完成状态视图 - 节点数: ${state.activeSession.rootNodes.length}, 会话ID: ${state.activeSessionId}'); // 🔧 修复:当没有节点数据时,显示空状态提示 if (state.activeSession.rootNodes.isEmpty) { AppLogger.w('SettingsTreeWidget', '⚠️ 会话中没有设定节点数据,显示空状态提示'); return _buildEmptyStateView(context, '此历史记录暂无设定数据'); } return _buildTreeView( context, state.activeSession.rootNodes, state.selectedNodeId, state.viewMode, state.renderedNodeIds, ); } Widget _buildStreamingTreeView(BuildContext context, SettingGenerationInProgress state) { final isDark = Theme.of(context).brightness == Brightness.dark; return Container( decoration: BoxDecoration( color: isDark ? const Color(0xFF1F2937).withOpacity(0.3) : const Color(0xFFF9FAFB).withOpacity(0.3), borderRadius: BorderRadius.circular(8), border: Border.all( color: isDark ? const Color(0xFF1F2937) : const Color(0xFFE5E7EB), width: 1, ), ), child: state.renderedNodeIds.isEmpty ? _buildWaitingForFirstNode(context) : _buildRenderableNodesListView( context, state.activeSession.rootNodes, state.selectedNodeId, state.viewMode, state.renderedNodeIds, state.nodeRenderStates, ), ); } Widget _buildWaitingForFirstNode(BuildContext context) { return Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 32, height: 32, child: CircularProgressIndicator( strokeWidth: 3, valueColor: AlwaysStoppedAnimation( WebTheme.getPrimaryColor(context), ), ), ), const SizedBox(height: 16), Text( 'AI 正在构思第一个设定节点...', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).textTheme.bodySmall?.color, ), textAlign: TextAlign.center, ), ], ), ), ); } /// 构建可渲染的节点列表视图 Widget _buildRenderableNodesListView( BuildContext context, List nodes, String? selectedNodeId, String viewMode, Set renderedNodeIds, Map nodeRenderStates, ) { // 获取所有需要渲染的节点(扁平化列表) final renderableNodes = _getRenderableNodesList( nodes, renderedNodeIds, nodeRenderStates, ); return ListView.builder( padding: const EdgeInsets.all(4), itemCount: renderableNodes.length, itemBuilder: (context, index) { final nodeInfo = renderableNodes[index]; final node = nodeInfo.node; final level = nodeInfo.level; return Padding( padding: EdgeInsets.only(bottom: index < renderableNodes.length - 1 ? 4 : 0), child: SettingNodeWidget( node: node, selectedNodeId: selectedNodeId, viewMode: viewMode, level: level, renderedNodeIds: renderedNodeIds, nodeRenderStates: nodeRenderStates, renderChildren: false, onTap: (nodeId) { context.read().add( SelectNodeEvent(nodeId), ); }, ), ); }, ); } /// 获取所有需要渲染的节点列表(扁平化,包含层级信息) List<_NodeWithLevel> _getRenderableNodesList( List nodes, Set renderedNodeIds, Map nodeRenderStates, { int level = 0, }) { final List<_NodeWithLevel> result = []; for (final node in nodes) { // 只添加已经渲染的节点或正在渲染的节点 if (renderedNodeIds.contains(node.id) || nodeRenderStates[node.id]?.state == NodeRenderState.rendering) { result.add(_NodeWithLevel(node: node, level: level)); // 递归添加子节点 if (node.children != null && node.children!.isNotEmpty) { result.addAll(_getRenderableNodesList( node.children!, renderedNodeIds, nodeRenderStates, level: level + 1, )); } } } return result; } Widget _buildTreeView( BuildContext context, List nodes, String? selectedNodeId, String viewMode, Set renderedNodeIds, ) { // 🔧 新增:日志和空状态处理 AppLogger.i('SettingsTreeWidget', '🌳 构建设定树视图 - 节点数: ${nodes.length}, 选中节点: $selectedNodeId'); // 🔧 修复:当节点列表为空时,显示空状态提示 if (nodes.isEmpty) { AppLogger.w('SettingsTreeWidget', '⚠️ 节点列表为空,显示空状态提示'); return _buildEmptyStateView(context, '暂无设定数据'); } // 🔧 如果 renderedNodeIds 为空(通常发生在生成已完成的状态), // 将所有可见节点都视为已渲染,避免由于 Opacity=0 导致的内容不可见。 Set effectiveRenderedIds = renderedNodeIds; if (effectiveRenderedIds.isEmpty) { effectiveRenderedIds = _collectAllNodeIds(nodes).toSet(); AppLogger.i('SettingsTreeWidget', '🔧 renderedNodeIds 为空,自动填充所有节点ID (${effectiveRenderedIds.length})'); } final isDark = Theme.of(context).brightness == Brightness.dark; return Container( decoration: BoxDecoration( color: isDark ? const Color(0xFF1F2937).withOpacity(0.3) : const Color(0xFFF9FAFB).withOpacity(0.3), borderRadius: BorderRadius.circular(8), border: Border.all( color: isDark ? const Color(0xFF1F2937) : const Color(0xFFE5E7EB), width: 1, ), ), child: ListView.builder( padding: const EdgeInsets.all(4), itemCount: nodes.length, itemBuilder: (context, index) { final node = nodes[index]; return Padding( padding: EdgeInsets.only(bottom: index < nodes.length - 1 ? 4 : 0), child: SettingNodeWidget( node: node, selectedNodeId: selectedNodeId, viewMode: viewMode, level: 0, renderedNodeIds: effectiveRenderedIds, nodeRenderStates: const {}, // 完成状态下不需要渲染状态 onTap: (nodeId) { context.read().add( SelectNodeEvent(nodeId), ); }, ), ); }, ), ); } /// 递归收集所有节点 ID List _collectAllNodeIds(List nodes) { final List ids = []; for (final node in nodes) { ids.add(node.id); if (node.children != null && node.children!.isNotEmpty) { ids.addAll(_collectAllNodeIds(node.children!)); } } return ids; } Widget _buildErrorView(BuildContext context, SettingGenerationError state) { final isDark = Theme.of(context).brightness == Brightness.dark; return Container( decoration: BoxDecoration( color: isDark ? const Color(0xFF1F2937).withOpacity(0.3) : const Color(0xFFF9FAFB).withOpacity(0.3), borderRadius: BorderRadius.circular(8), border: Border.all( color: isDark ? const Color(0xFF1F2937) : const Color(0xFFE5E7EB), width: 1, ), ), child: Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.error_outline, size: 48, color: Colors.red.withOpacity(0.7), ), const SizedBox(height: 16), Text( '生成失败', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(height: 8), Text( _getFriendlyErrorMessage(state.message), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: isDark ? const Color(0xFF9CA3AF) : const Color(0xFF6B7280), ), textAlign: TextAlign.center, ), const SizedBox(height: 24), // 重试按钮 if (state.isRecoverable && _canRetry()) Row( mainAxisSize: MainAxisSize.min, children: [ OutlinedButton.icon( onPressed: () => _retryGeneration(context), icon: const Icon(Icons.refresh, size: 18), label: const Text('重试生成'), style: OutlinedButton.styleFrom( foregroundColor: isDark ? const Color(0xFFF9FAFB) : const Color(0xFF111827), side: BorderSide( color: isDark ? const Color(0xFF374151) : const Color(0xFFD1D5DB), ), ), ), const SizedBox(width: 12), TextButton.icon( onPressed: () => _resetAndReload(context), icon: const Icon(Icons.settings_backup_restore, size: 18), label: const Text('重新开始'), style: TextButton.styleFrom( foregroundColor: isDark ? const Color(0xFF9CA3AF) : const Color(0xFF6B7280), ), ), ], ), ], ), ), ), ); } /// 将后端错误信息转换为用户友好的提示 String _getFriendlyErrorMessage(String originalMessage) { // 检查常见的错误模式并返回友好提示 final message = originalMessage.toLowerCase(); if (message.contains('timeout') || message.contains('超时')) { return 'AI生成响应时间过长,请稍后重试'; } if (message.contains('network') || message.contains('connection') || message.contains('网络') || message.contains('连接')) { return '网络连接不稳定,请检查网络后重试'; } if (message.contains('rate limit') || message.contains('too many') || message.contains('频率') || message.contains('限制')) { return '请求过于频繁,请稍等片刻后重试'; } if (message.contains('invalid') || message.contains('无效') || message.contains('bad request')) { return '请求参数有误,请重新配置后重试'; } if (message.contains('unauthorized') || message.contains('permission') || message.contains('未授权') || message.contains('权限')) { return '授权已过期,请重新登录后重试'; } if (message.contains('server error') || message.contains('internal') || message.contains('服务器') || message.contains('内部错误')) { return '服务器暂时无法处理请求,请稍后重试'; } if (message.contains('model') || message.contains('模型')) { return 'AI模型暂时不可用,请尝试切换其他模型'; } if (message.contains('quota') || message.contains('balance') || message.contains('额度') || message.contains('余额')) { return '账户余额不足或已达到使用限额'; } // 如果无法识别具体错误类型,返回通用友好提示 return '生成过程中遇到问题,请重试或联系客服'; } /// 检查是否可以重试 bool _canRetry() { return lastInitialPrompt != null && lastStrategy != null && lastModelConfigId != null; } /// 重试生成 void _retryGeneration(BuildContext context) { if (!_canRetry()) return; // 重试时无法保证仍保留公共模型对象,这里仅传基础参数;若有需要可在Bloc中从上次session metadata取回 context.read().add( StartGenerationEvent( initialPrompt: lastInitialPrompt!, promptTemplateId: lastStrategy!, novelId: novelId, modelConfigId: lastModelConfigId!, userId: userId ?? 'current_user', ), ); } /// 重置并重新加载 void _resetAndReload(BuildContext context) { context.read().add(const LoadStrategiesEvent()); } /// 构建空状态提示视图 Widget _buildEmptyStateView(BuildContext context, String message) { final isDark = Theme.of(context).brightness == Brightness.dark; return Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.info_outline, size: 48, color: isDark ? const Color(0xFF9CA3AF) : const Color(0xFF6B7280), ), const SizedBox(height: 16), Text( message, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: isDark ? const Color(0xFF9CA3AF) : const Color(0xFF6B7280), ), textAlign: TextAlign.center, ), ], ), ), ); } /// 🔧 新增:构建节点修改中视图 Widget _buildNodeUpdatingView(BuildContext context, SettingGenerationNodeUpdating state) { AppLogger.i('SettingsTreeWidget', '🔧 渲染节点修改中状态 - 修改节点: ${state.updatingNodeId}'); // 使用TopToast显示修改提示 if (state.isUpdating && state.message.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { TopToast.info( context, state.message, ); }); } // 显示设定树,突出显示正在修改的节点 return _buildTreeView( context, state.activeSession.rootNodes, state.selectedNodeId, state.viewMode, state.renderedNodeIds, ); } /// 🔧 新增:构建保存成功视图 Widget _buildSavedView(BuildContext context, SettingGenerationSaved state) { AppLogger.i('SettingsTreeWidget', '💾 渲染保存成功状态'); // 尝试从sessions中找到当前活跃会话以渲染 if (state.sessions.isNotEmpty && state.activeSessionId != null) { final session = state.sessions.firstWhere( (s) => s.sessionId == state.activeSessionId, orElse: () => state.sessions.first, ); return _buildTreeView( context, session.rootNodes, null, // 保存操作后保持原选中节点逻辑,可根据需要扩展 'compact', const {}, ); } // 如果找不到会话,显示空状态 AppLogger.w('SettingsTreeWidget', '⚠️ 保存状态下找不到活跃会话,显示空状态'); return _buildEmptyStateView(context, '设定已保存,但无法显示内容'); } /// 🔧 新增:构建无会话视图 Widget _buildNoSessionView(BuildContext context, dynamic state) { // 检查是否有活跃会话 if (state is SettingGenerationReady) { AppLogger.i('SettingsTreeWidget', '📋 渲染就绪状态 - 活跃会话: ${state.activeSessionId}'); // 如果有活跃会话,显示对应的设定树 if (state.activeSessionId != null && state.sessions.isNotEmpty) { final session = state.sessions.firstWhere( (s) => s.sessionId == state.activeSessionId, orElse: () => state.sessions.first, ); // 如果会话有内容,显示设定树 if (session.rootNodes.isNotEmpty) { AppLogger.i('SettingsTreeWidget', '🌳 就绪状态下显示设定树 - 节点数: ${session.rootNodes.length}'); return _buildTreeView( context, session.rootNodes, null, // SettingGenerationReady 没有 selectedNodeId state.viewMode, const {}, ); } } } // 默认显示无会话提示 AppLogger.i('SettingsTreeWidget', '📝 显示无会话提示'); return _buildEmptyStateView(context, '请开始生成设定或选择已有历史记录'); } }