Parcourir la source

诗词课:新增仿写评价、背诵评价

liyanbo il y a 1 mois
Parent
commit
866dc6d16b

+ 2 - 2
src/api/audio.js

@@ -27,7 +27,7 @@ export async function uploadAudio(audioBlob) {
   return response.data;
 }
 
-export async function evaluateReading(audioUrl, onData, roleId = 10) {
+export async function evaluateReading(audioUrl, onData, roleId = 10, prompt = '请评价这段朗读') {
   const abortController = new AbortController();
   const token = localStorage.getItem('token');
 
@@ -45,7 +45,7 @@ export async function evaluateReading(audioUrl, onData, roleId = 10) {
       body: JSON.stringify({
         modelId,
         roleId,
-        prompt: '请评价这段朗读',
+        prompt,
         audioUrl
       })
     });

+ 30 - 5
src/components/aiCourse/DialogEngine.vue

@@ -33,6 +33,7 @@
         :poem-content="currentPoemContent"
         @user-input-submit="handleUserInputSubmit"
         @single-choice-submit="handleSingleChoiceSubmit"
+        @write-reading-submit="handleWriteReadingSubmit"
         @video-ended="handleVideoEnded"
         @poem-reading-complete="handlePoemReadingComplete"
       />
@@ -101,6 +102,9 @@ const props = defineProps({
 // 事件定义
 const emit = defineEmits(['dialogueEnded', 'goBack']);
 
+const evaluationDialogueTypes = ['poem_reading', 'write_reading', 'recite_reading'];
+const isEvaluationDialogue = (type) => evaluationDialogueTypes.includes(type);
+
 /** 状态定义 **/
 // 对话位置状态
 const currentSectionIndex = ref(0);       // 当前章节索引
@@ -373,7 +377,7 @@ const playSequence = () => {
         }
       }, 500);
     }
-  } else if (currentDialogue.value?.type === 'poem_reading') {
+  } else if (isEvaluationDialogue(currentDialogue.value?.type)) {
     showPoem.value = false;
   } else {
     playDialogueAudio(true);
@@ -481,7 +485,7 @@ const playNext = (isAutoPlay = false) => {
     }
 
     // 诗词朗读评价处理
-    if (currentDialogue.value?.type === 'poem_reading') {
+    if (isEvaluationDialogue(currentDialogue.value?.type)) {
       showPoem.value = false;
       return true;
     }
@@ -508,7 +512,7 @@ const playNext = (isAutoPlay = false) => {
             playNext(isAutoPlay);
           }, 100);
         }
-      } else if (currentDialogue.value?.type === 'poem_reading') {
+      } else if (isEvaluationDialogue(currentDialogue.value?.type)) {
         showPoem.value = false;
       } else {
         playDialogueAudio(isPlaying.value);
@@ -548,7 +552,7 @@ const startPlayback = () => {
         playNext();
       }, 500);
     }
-  } else if (currentDialogue.value?.type === 'poem_reading') {
+  } else if (isEvaluationDialogue(currentDialogue.value?.type)) {
     showPoem.value = false;
   } else {
     playDialogueAudio();
@@ -562,7 +566,7 @@ const startPlayback = () => {
 const handleKeydown = (event) => {
   if (showMask.value) return;
   if (currentDialogue.value?.type === 'user') return;
-  if (currentDialogue.value?.type === 'poem_reading') return;
+  if (isEvaluationDialogue(currentDialogue.value?.type)) return;
 
   switch (event.key) {
     case 'ArrowLeft':
@@ -612,6 +616,27 @@ const handleUserInputSubmit = async (data) => {
   });
 };
 
+/**
+ * 处理诗词仿写提交,并将原诗词与学生作品一并发送给 AI 评价。
+ * @param {Object} data - 学生输入数据
+ */
+const handleWriteReadingSubmit = async (data) => {
+  const originalPoem = currentDialogue.value?.content?.trim();
+  const imitation = data.content?.trim();
+  if (!originalPoem || !imitation) return;
+
+  await createAiChart();
+  if (!activeConversationId.value) return;
+
+  const content = `你是一位古诗词仿写指导老师。请根据“原诗词”和“学生仿写作品”,给出友善、具体、鼓励式的评价。\n\n原诗词:\n${originalPoem}\n\n学生仿写:\n${imitation}\n\n请从主题意境、结构句式、语言表达和创新性四方面评价。先肯定亮点,再指出1~2个可操作的改进建议;不苛求严格格律;控制在150字以内。`;
+
+  await doSendMessageStream({
+    conversationId: activeConversationId.value,
+    content,
+    contentAnswer: null,
+  });
+};
+
 /**
  * 处理单选问题提交
  * @param {Object} data - 用户选择数据

+ 28 - 5
src/components/aiCourse/dialog/DialogCard.vue

@@ -37,10 +37,25 @@
 
     <!-- 诗词朗读评价 -->
     <PoemReadingDialogue
-      v-if="dialogue?.type === 'poem_reading'"
-      :content="dialogue?.content"
-      :role-id="getRoleIdByRoleName(dialogue?.roleName, scriptRoles)"
-      @evaluation-complete="$emit('poem-reading-complete', $event)"
+        v-if="dialogue?.type === 'poem_reading'"
+        :content="dialogue?.content"
+        :role-id="getRoleIdByRoleName(dialogue?.roleName, scriptRoles)"
+        @evaluation-complete="$emit('poem-reading-complete', $event)"
+    />
+
+    <!-- 写诗评价 -->
+    <WriteReadingDialogue
+        v-if="dialogue?.type === 'write_reading'"
+        :content="dialogue?.content"
+        @submit="$emit('write-reading-submit', $event)"
+    />
+
+    <!-- 背诵诗词评价 -->
+    <ReciteReadingDialogue
+        v-if="dialogue?.type === 'recite_reading'"
+        :content="dialogue?.content"
+        :role-id="getRoleIdByRoleName(dialogue?.roleName, scriptRoles)"
+        @evaluation-complete="$emit('poem-reading-complete', $event)"
     />
 
     <!-- 诗词显示 -->
@@ -58,6 +73,8 @@ import UserDialogue from './dialogType/UserDialogue.vue';
 import PoemDisplay from './dialogType/PoemDisplay.vue';
 import VideoDisplay from './dialogType/VideoDisplay.vue';
 import PoemReadingDialogue from './dialogType/PoemReadingDialogue.vue';
+import WriteReadingDialogue from './dialogType/WriteReadingDialogue.vue';
+import ReciteReadingDialogue from './dialogType/ReciteReadingDialogue.vue';
 
 /**
  * DialogCard - 统一对话卡片容器
@@ -107,7 +124,13 @@ defineProps({
  * @event video-ended - 视频播放结束
  * @event poem-reading-complete - 诗词朗读评价完成
  */
-defineEmits(['user-input-submit', 'single-choice-submit', 'video-ended', 'poem-reading-complete']);
+defineEmits([
+  'user-input-submit',
+  'single-choice-submit',
+  'write-reading-submit',
+  'video-ended',
+  'poem-reading-complete'
+]);
 
 /**
  * 获取问题类型

+ 20 - 3
src/components/aiCourse/dialog/dialogType/PoemReadingDialogue.vue

@@ -1,6 +1,6 @@
 <template>
     <!-- 诗词展示区域 - 使用现成的 PoemDisplay 组件 -->
-    <PoemDisplay :content="content" />
+    <PoemDisplay v-if="showPoem" :content="content" />
 
     <!-- 录音控制区域 -->
     <div class="recording-area">
@@ -43,7 +43,7 @@
     <!-- 评价结果展示 -->
     <div class="evaluation-result" v-if="evaluationResult">
       <div class="result-header">
-        <span class="result-title">朗读评价</span>
+        <span class="result-title">{{ resultTitle }}</span>
         <div class="result-score" :class="scoreLevel">{{ evaluationResult.score }}分</div>
       </div>
       <div class="result-content">
@@ -77,6 +77,18 @@ const props = defineProps({
   roleId: {
     type: Number,
     default: 10
+  },
+  showPoem: {
+    type: Boolean,
+    default: true
+  },
+  evaluationPrompt: {
+    type: String,
+    default: '请评价这段朗读'
+  },
+  resultTitle: {
+    type: String,
+    default: '朗读评价'
   }
 });
 
@@ -239,7 +251,12 @@ const convertBlobToBase64AndEvaluate = async () => {
 
   try {
     const base64Audio = await blobToBase64(recordedBlob.value);
-    abortController.value = await evaluateReading(base64Audio, handleStreamData, props.roleId);
+    abortController.value = await evaluateReading(
+      base64Audio,
+      handleStreamData,
+      props.roleId,
+      props.evaluationPrompt
+    );
   } catch (error) {
     console.error('评价请求失败:', error);
     ElMessage.error('评价请求失败,请重试');

+ 30 - 0
src/components/aiCourse/dialog/dialogType/ReciteReadingDialogue.vue

@@ -0,0 +1,30 @@
+<template>
+  <PoemReadingDialogue
+    :content="content"
+    :role-id="roleId"
+    :show-poem="false"
+    :evaluation-prompt="evaluationPrompt"
+    result-title="背诵评价"
+    @evaluation-complete="$emit('evaluation-complete', $event)"
+  />
+</template>
+
+<script setup>
+import { computed } from 'vue';
+import PoemReadingDialogue from './PoemReadingDialogue.vue';
+
+const props = defineProps({
+  content: {
+    type: String,
+    default: ''
+  },
+  roleId: {
+    type: [Number, String],
+    default: 10
+  }
+});
+
+defineEmits(['evaluation-complete']);
+
+const evaluationPrompt = computed(() => `你是一位古诗词背诵指导老师。请以“待背诵原文”为依据,评价学生录音。\n\n待背诵原文:\n${props.content}\n\n请重点评价背诵准确性(错字、漏字、顺序)、停顿节奏、发音清晰度和情感表达。先给总体评价,再说明具体问题或改进建议;准确时给予明确鼓励。`);
+</script>

+ 29 - 0
src/components/aiCourse/dialog/dialogType/WriteReadingDialogue.vue

@@ -0,0 +1,29 @@
+<template>
+  <div class="write-reading-dialogue">
+    <PoemDisplay :content="content" />
+    <UserInputCard @submit="$emit('submit', $event)" />
+  </div>
+</template>
+
+<script setup>
+import PoemDisplay from './PoemDisplay.vue';
+import UserInputCard from './questType/UserInputCard.vue';
+
+defineProps({
+  content: {
+    type: String,
+    default: ''
+  }
+});
+
+defineEmits(['submit']);
+</script>
+
+<style scoped lang="scss">
+.write-reading-dialogue {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 100%;
+}
+</style>