feat: 完善听力口语辅助状态记录、练习分块与复习抽查间隔机制
This commit is contained in:
@@ -34,23 +34,18 @@ class _WritingStepState extends State<_WritingStep> {
|
||||
String? aiFeedbackError;
|
||||
bool requestingAiFeedback = false;
|
||||
|
||||
void _checkOrContinue() {
|
||||
if (result?.complete == true) {
|
||||
widget.onContinue(aiFeedback != null);
|
||||
return;
|
||||
}
|
||||
setState(
|
||||
() => result = WritingFeedback.check(
|
||||
widget.segmentId,
|
||||
widget.controller.text,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _requestAiFeedback() async {
|
||||
if (widget.controller.text.trim().isEmpty || requestingAiFeedback) return;
|
||||
Future<void> _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) {
|
||||
setState(() => aiFeedbackError = '请先在“我的”配置 AI 服务;本地检查仍可继续学习。');
|
||||
widget.onContinue(false);
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
@@ -64,19 +59,28 @@ class _WritingStepState extends State<_WritingStep> {
|
||||
model: widget.state.aiModel,
|
||||
lessonId: widget.lessonId,
|
||||
taskPrompt: widget.activity.writingPrompt,
|
||||
answer: widget.controller.text.trim(),
|
||||
answer: answer,
|
||||
level: lessonLevel(widget.lessonId),
|
||||
);
|
||||
if (!mounted) return;
|
||||
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
|
||||
? '暂时无法获得 AI 反馈。你的答案保留在这里,可稍后重试或继续本地练习。'
|
||||
aiFeedbackError = feedback == null || feedback.verdict == 'uncertain'
|
||||
? '答案暂时无法核验。可以重试,或仅保存这次练习继续学习。'
|
||||
: null;
|
||||
});
|
||||
}
|
||||
|
||||
void _continueUnverified() => widget.onContinue(false);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _LessonScaffold(
|
||||
step: 5,
|
||||
@@ -87,6 +91,11 @@ class _WritingStepState extends State<_WritingStep> {
|
||||
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)}'),
|
||||
@@ -126,19 +135,6 @@ class _WritingStepState extends State<_WritingStep> {
|
||||
onPressed: widget.onToggleHelp,
|
||||
child: Text(widget.showHelp ? '收起示例,自己试一次' : '需要帮助,查看示例'),
|
||||
),
|
||||
OutlinedButton.icon(
|
||||
onPressed: widget.canContinue && !requestingAiFeedback
|
||||
? _requestAiFeedback
|
||||
: null,
|
||||
icon: requestingAiFeedback
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.auto_awesome_outlined),
|
||||
label: Text(requestingAiFeedback ? '正在获取反馈…' : '获取 AI 写作建议(可选)'),
|
||||
),
|
||||
if (aiFeedback != null)
|
||||
SectionCard(
|
||||
tint: aiFeedback!.verdict == 'accepted'
|
||||
@@ -153,7 +149,9 @@ class _WritingStepState extends State<_WritingStep> {
|
||||
if (aiFeedback!.suggestion != null)
|
||||
Text('可参考改写:${aiFeedback!.suggestion}'),
|
||||
Text(
|
||||
'这是学习帮助;请按自己的意思重写后再检查,系统不会仅凭 AI 建议记为掌握。',
|
||||
aiFeedback!.verdict == 'rewrite'
|
||||
? '请按自己的意思修改,再提交检查。看过改正后,本次只算辅助练习。'
|
||||
: '答案尚未核验;可以重试检查或仅保存练习。',
|
||||
style: TextStyle(fontSize: 12, color: AppColors.muted),
|
||||
),
|
||||
],
|
||||
@@ -167,20 +165,25 @@ class _WritingStepState extends State<_WritingStep> {
|
||||
style: TextStyle(color: AppColors.warmInk),
|
||||
),
|
||||
),
|
||||
if (result != null)
|
||||
if (result != null && !result!.complete)
|
||||
SectionCard(
|
||||
tint: result!.complete ? AppColors.softGreen : AppColors.warm,
|
||||
tint: AppColors.warm,
|
||||
child: Text(
|
||||
result!.message,
|
||||
style: TextStyle(
|
||||
color: result!.complete ? AppColors.green : AppColors.warmInk,
|
||||
),
|
||||
style: TextStyle(color: AppColors.warmInk),
|
||||
),
|
||||
),
|
||||
PrimaryButton(
|
||||
label: result?.complete == true ? '进入课程对话' : '检查句子',
|
||||
onPressed: widget.canContinue ? _checkOrContinue : null,
|
||||
label: requestingAiFeedback ? '正在自动检查…' : '提交并继续',
|
||||
onPressed: widget.canContinue && !requestingAiFeedback
|
||||
? _submit
|
||||
: null,
|
||||
),
|
||||
if (aiFeedbackError != null)
|
||||
TextButton(
|
||||
onPressed: _continueUnverified,
|
||||
child: const Text('未核验,保存练习并继续'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user