feat: 优化口语防重复问答、课程推进流转、键盘输入流畅度与竖屏锁定

- AI口语约束:在对话系统提示词与单轮指导中增加防重复问答约束,避免重复寒暄及索取已提供信息,根据历史自然向前推进
- 课程流转:修复多段课程学完后今日任务卡片流转下一课,及退出重进定位逻辑
- 竖屏锁定:在 Flutter、Android 及 iOS 平台配置仅支持竖屏显示
- 对话交互:优化对话界面文本输入重绘与词汇查询,解决键盘输入卡顿
- 测试用例:补充并更新单轮约束、状态流转与返回键退出测试,212项测试全绿
This commit is contained in:
shen
2026-09-18 07:38:54 -07:00
parent 0a4f97d105
commit 5b9cba793e
16 changed files with 568 additions and 242 deletions
+10 -3
View File
@@ -7,6 +7,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http;
import 'models.dart';
import 'courses/courses.dart';
import 'generated_content.dart';
class AiConnectionResult {
@@ -796,7 +797,8 @@ class AiService {
);
final turnNote =
'[Turn] Your next line must do this: $aiGoal '
'After your line the learner has to: $learnerTask.';
'After your line the learner has to: $learnerTask. '
'Do not repeat any questions or greetings already asked or answered.';
// Earlier AI lines are stored as plain English. Sent that way they teach
// the model to answer in plain text (or, in JSON mode, with blanks), so
// they are replayed in the JSON shape the system prompt asks for.
@@ -854,6 +856,9 @@ class AiService {
'Each user message ends with a [Turn] note saying what your next line '
'must do and what the learner has to say after it. '
'Do not say the learner sentence for them, and do not ask for anything else. '
'Strictly do not repeat any question, greeting, or inquiry that has already been asked or answered in earlier turns. '
'Check the conversation history carefully: never ask for information the learner has already given (e.g. name, location, feelings, etc.). '
'If the turn goal asks about something already provided in history, acknowledge it naturally and advance the conversation forward instead of re-asking. '
'Reply with one short sentence or question, at most 20 English words, in English only. '
'Do not explain grammar. '
'Return JSON only: {"reply": "your English line", '
@@ -994,9 +999,11 @@ Learner wrote: $answer''';
bool repairAttempt = false,
}) async {
if (provider == AiProviderType.mock) return null;
final lessonId = 'ai-a0-${targetItemId.toLowerCase()}-1';
final level = itemLevel(targetItemId);
final lessonId = 'ai-${level.toLowerCase()}-${targetItemId.toLowerCase()}-1';
final stageVersion = '$level-1.0';
final instruction =
'Return JSON only with exactly: schemaVersion, lessonId, revision, stageVersion, source, status, abilityIds, prerequisiteIds, targetItemIds, receptiveChunks, newItemIds, previewItemIds, estimatedMinutes, tasks. Use schemaVersion lesson-2, the lessonId given below, revision 1, stageVersion A0-1.0, source aiGenerated, status validated, targetItemIds [target item id], and empty receptiveChunks, newItemIds, previewItemIds. Create exactly four tasks, one listening listenChoice, speaking repeat, reading readAnswer, writing writeAnswer. Every task has exactly taskId, skill, type, prompt, stimulus, answer, targetItemIds, answerSpec and targets [target item id]. answerSpec has exactly requiredAnyPhrases (1-4 lists, each contains 1-4 accepted English phrases), acceptedAnswers (1-4 complete accepted English answers), forbiddenPhrases (possibly empty list). Make answer satisfy its answerSpec. Lesson duration is 8 to 15 minutes. Use only very simple A0 English for the target expression. No new vocabulary, markdown, real phone numbers, or personal data.\n'
'Return JSON only with exactly: schemaVersion, lessonId, revision, stageVersion, source, status, abilityIds, prerequisiteIds, targetItemIds, receptiveChunks, newItemIds, previewItemIds, estimatedMinutes, tasks. Use schemaVersion lesson-2, the lessonId given below, revision 1, stageVersion $stageVersion, source aiGenerated, status validated, targetItemIds [target item id], and empty receptiveChunks, newItemIds, previewItemIds. Create exactly four tasks, one listening listenChoice, speaking repeat, reading readAnswer, writing writeAnswer. Every task has exactly taskId, skill, type, prompt, stimulus, answer, targetItemIds, answerSpec and targets [target item id]. answerSpec has exactly requiredAnyPhrases (1-4 lists, each contains 1-4 accepted English phrases), acceptedAnswers (1-4 complete accepted English answers), forbiddenPhrases (possibly empty list). Make answer satisfy its answerSpec. Lesson duration is 8 to 15 minutes. Use only very simple $level English for the target expression. No new vocabulary, markdown, real phone numbers, or personal data.\n'
'lessonId: $lessonId\n'
'Target item id: $targetItemId\n'
'Target expression: $targetLabel'