part of '../lesson_flow.dart'; class _WritingStep extends StatefulWidget { const _WritingStep({ required this.state, required this.lessonId, required this.segmentId, required this.activity, required this.controller, required this.showHelp, required this.canContinue, required this.onChanged, required this.onToggleHelp, required this.onContinue, }); final String lessonId; final AppState state; final String segmentId; final LessonActivity activity; final TextEditingController controller; final bool showHelp; final bool canContinue; final VoidCallback onChanged; final VoidCallback onToggleHelp; final ValueChanged onContinue; @override State<_WritingStep> createState() => _WritingStepState(); } class _WritingStepState extends State<_WritingStep> { WritingCheckResult? result; WritingAiFeedback? aiFeedback; String? aiFeedbackError; bool requestingAiFeedback = false; Future _submit() async { if (!widget.canContinue || requestingAiFeedback) return; final answer = widget.controller.text.trim(); final local = WritingFeedback.check(widget.segmentId, answer); setState(() { result = local; aiFeedback = null; aiFeedbackError = null; }); if (!local.complete) return; if (widget.state.aiProvider == AiProviderType.mock) { widget.onContinue(false); return; } setState(() { requestingAiFeedback = true; aiFeedbackError = null; }); final feedback = await AiService.instance.capabilities.evaluation .evaluateWriting( provider: widget.state.aiProvider, endpoint: widget.state.aiEndpoint, model: widget.state.aiModel, lessonId: widget.lessonId, taskPrompt: widget.activity.writingPrompt, answer: answer, level: lessonLevel(widget.lessonId), ); if (!mounted || widget.controller.text.trim() != answer) { if (mounted) setState(() => requestingAiFeedback = false); return; } if (feedback?.verdict == 'accepted') { widget.onContinue(true); return; } setState(() { requestingAiFeedback = false; aiFeedback = feedback; aiFeedbackError = feedback == null || feedback.verdict == 'uncertain' ? '答案暂时无法核验。可以重试,或仅保存这次练习继续学习。' : null; }); } void _continueUnverified() => widget.onContinue(false); @override Widget build(BuildContext context) => _LessonScaffold( step: 5, child: SpacedColumn( children: [ const Eyebrow('写一写'), Text( widget.activity.writingPrompt, style: Theme.of(context).textTheme.headlineMedium, ), if (widget.state.aiProvider == AiProviderType.mock) SectionCard( tint: AppColors.warm, child: const Text('尚未配置 AI 服务。本次可继续练习,但开放回答不会计入独立掌握。'), ), SectionCard( tint: AppColors.surfaceMuted, child: Text('小提示:${grammarNoteForSegment(widget.segmentId)}'), ), SegmentUsage(segmentId: widget.segmentId, state: widget.state), if (widget.showHelp) SectionCard( tint: AppColors.softGreen, child: Text( widget.activity.writingExample, style: TextStyle(fontSize: 18, height: 1.5), ), ), TextField( controller: widget.controller, onChanged: (_) { setState(() { result = null; aiFeedback = null; aiFeedbackError = null; }); widget.onChanged(); }, minLines: 3, maxLines: 4, decoration: InputDecoration( labelText: '你的答案', hintText: widget.showHelp ? widget.activity.writingExample : '请输入完整英文答案', filled: true, fillColor: AppColors.surface, border: const OutlineInputBorder(), ), ), TextButton( onPressed: widget.onToggleHelp, child: Text(widget.showHelp ? '收起示例,自己试一次' : '需要帮助,查看示例'), ), if (aiFeedback != null) SectionCard( tint: aiFeedback!.verdict == 'accepted' ? AppColors.softGreen : AppColors.warm, child: SpacedColumn( spacing: 6, children: [ Text('AI 建议:${aiFeedback!.feedback}'), if (aiFeedback!.missing.isNotEmpty) Text('还可补充:${aiFeedback!.missing.join('、')}'), if (aiFeedback!.suggestion != null) Text('可参考改写:${aiFeedback!.suggestion}'), Text( aiFeedback!.verdict == 'rewrite' ? '请按自己的意思修改,再提交检查。看过改正后,本次只算辅助练习。' : '答案尚未核验;可以重试检查或仅保存练习。', style: TextStyle(fontSize: 12, color: AppColors.muted), ), ], ), ), if (aiFeedbackError != null) SectionCard( tint: AppColors.warm, child: Text( aiFeedbackError!, style: TextStyle(color: AppColors.warmInk), ), ), if (result != null && !result!.complete) SectionCard( tint: AppColors.warm, child: Text( result!.message, style: TextStyle(color: AppColors.warmInk), ), ), PrimaryButton( label: requestingAiFeedback ? '正在自动检查…' : '提交并继续', onPressed: widget.canContinue && !requestingAiFeedback ? _submit : null, ), if (aiFeedbackError != null) TextButton( onPressed: _continueUnverified, child: const Text('未核验,保存练习并继续'), ), ], ), ); }