|
|
@@ -88,7 +88,7 @@
|
|
|
|
|
|
<!-- 用户输入卡片 -->
|
|
|
<div
|
|
|
- v-if="currentDialogue.type === 'user'"
|
|
|
+ v-if="currentDialogue.type === 'user' && !isUserSingleChoice"
|
|
|
class="dialogue-card user-input-card"
|
|
|
>
|
|
|
<div class="dialogue-header">
|
|
|
@@ -109,6 +109,38 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ <!-- 单选问题卡片(在user类型时显示) -->
|
|
|
+ <div
|
|
|
+ v-if="isUserSingleChoice"
|
|
|
+ class="dialogue-card single-choice-card"
|
|
|
+ >
|
|
|
+ <div class="dialogue-header">
|
|
|
+ <span class="role-name">我</span>
|
|
|
+ </div>
|
|
|
+ <div class="single-choice-content">
|
|
|
+ <!-- 问题描述 -->
|
|
|
+ <div class="question-text" v-html="parseMarkdown(previousQuestDialogue?.content || '')"></div>
|
|
|
+ <!-- 选项列表 -->
|
|
|
+ <div class="options-list">
|
|
|
+ <div
|
|
|
+ v-for="(option, index) in previousQuestDialogue?.options"
|
|
|
+ :key="index"
|
|
|
+ class="option-item"
|
|
|
+ :class="{ 'selected': selectedOption === optionLabels[index] }"
|
|
|
+ @click="selectOption(optionLabels[index])"
|
|
|
+ >
|
|
|
+ <span class="option-label">{{ optionLabels[index] }}</span>
|
|
|
+ <span class="option-content">{{ option.content }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 操作按钮 -->
|
|
|
+ <div class="input-actions">
|
|
|
+ <button class="cancel-btn" @click="cancelSingleChoice">清空选择</button>
|
|
|
+ <button class="submit-btn" :disabled="!selectedOption" @click="submitSingleChoice">提交答案</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<!-- 输入按钮区域 -->
|
|
|
<div class="input-buttons-container" >
|
|
|
<!-- 上一个对话按钮 -->
|
|
|
@@ -211,6 +243,10 @@ const voiceRecognizedText = ref("")
|
|
|
const showPoem = ref(false)
|
|
|
// 当前诗词内容
|
|
|
const currentPoemContent = ref('')
|
|
|
+// 单选问题选中答案
|
|
|
+const selectedOption = ref('')
|
|
|
+// 选项标签映射
|
|
|
+const optionLabels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
|
|
|
|
|
|
// 音频对象
|
|
|
// 背景音频
|
|
|
@@ -299,6 +335,32 @@ const handleRecordingStatusChanged = (isRecording) => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 获取上一条quest对话
|
|
|
+const previousQuestDialogue = computed(() => {
|
|
|
+ const prevDialogue = getPreviousDialogue()
|
|
|
+ if (prevDialogue?.type === 'quest') {
|
|
|
+ return prevDialogue
|
|
|
+ }
|
|
|
+ return null
|
|
|
+})
|
|
|
+
|
|
|
+// 判断当前user对话是否应该显示单选框
|
|
|
+const isUserSingleChoice = computed(() => {
|
|
|
+ if (currentDialogue.value?.type !== 'user') return false
|
|
|
+ const prevDialogue = previousQuestDialogue.value
|
|
|
+ return prevDialogue?.questionType === 'singleChoice' &&
|
|
|
+ prevDialogue?.options &&
|
|
|
+ prevDialogue.options.length > 0
|
|
|
+})
|
|
|
+
|
|
|
+// 判断是否是单选问题(quest类型时)
|
|
|
+const isSingleChoiceQuestion = computed(() => {
|
|
|
+ return currentDialogue.value?.type === 'quest' &&
|
|
|
+ currentDialogue.value?.questionType === 'singleChoice' &&
|
|
|
+ currentDialogue.value?.options &&
|
|
|
+ currentDialogue.value.options.length > 0
|
|
|
+})
|
|
|
+
|
|
|
// 提交用户输入
|
|
|
const submitUserInput = async () => {
|
|
|
if (userInput.value.trim()) {
|
|
|
@@ -314,6 +376,26 @@ const cancelUserInput = () => {
|
|
|
userInput.value = ''
|
|
|
}
|
|
|
|
|
|
+// 选择单选选项
|
|
|
+const selectOption = (label) => {
|
|
|
+ selectedOption.value = label
|
|
|
+}
|
|
|
+
|
|
|
+// 取消单选选择
|
|
|
+const cancelSingleChoice = () => {
|
|
|
+ selectedOption.value = ''
|
|
|
+}
|
|
|
+
|
|
|
+// 提交单选答案
|
|
|
+const submitSingleChoice = async () => {
|
|
|
+ if (!selectedOption.value) return
|
|
|
+
|
|
|
+ console.log('用户选择:', selectedOption.value)
|
|
|
+
|
|
|
+ await createAiChart();
|
|
|
+ await doSendSingleChoiceMessage();
|
|
|
+}
|
|
|
+
|
|
|
// 解析 Markdown 内容
|
|
|
const parseMarkdown = (content) => {
|
|
|
if (!content) return ''
|
|
|
@@ -883,6 +965,51 @@ const doSendMessage = async () => {
|
|
|
userInput.value = ''
|
|
|
}
|
|
|
|
|
|
+/** 发送单选问题消息 */
|
|
|
+const doSendSingleChoiceMessage = async () => {
|
|
|
+ if (activeConversationId.value == null) {
|
|
|
+ console.error('还没创建对话,不能发送!')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用上一条quest对话的数据
|
|
|
+ const dialogue = previousQuestDialogue.value
|
|
|
+ if (!dialogue) {
|
|
|
+ console.error('找不到上一条quest对话!')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建选项字符串
|
|
|
+ const optionsStr = dialogue.options.map((opt, idx) => `${optionLabels[idx]}. ${opt.content}`).join(';')
|
|
|
+
|
|
|
+ // 构建发送内容:包含问题、选项、用户答案
|
|
|
+ const content = `问题:${dialogue.content}\n选项:${optionsStr}\n我的答案:${selectedOption.value}\n正确答案:${dialogue.answer}\n\n请判断我的答案是否正确,并给予鼓励或夸赞,回复请精简,控制在50字内。`
|
|
|
+
|
|
|
+ console.log('发送单选问题:', content)
|
|
|
+
|
|
|
+ // 执行发送
|
|
|
+ await doSendMessageStream({
|
|
|
+ conversationId: activeConversationId.value,
|
|
|
+ content: content,
|
|
|
+ contentAnswer: null,
|
|
|
+ })
|
|
|
+
|
|
|
+ // 清空选择
|
|
|
+ selectedOption.value = ''
|
|
|
+}
|
|
|
+
|
|
|
+// 获取上一条对话
|
|
|
+const getPreviousDialogue = () => {
|
|
|
+ const section = props.scriptData.sections[currentSectionIndex.value]
|
|
|
+ if (!section) return null
|
|
|
+ if (currentDialogueIndex.value > 0) {
|
|
|
+ return section.dialogues[currentDialogueIndex.value - 1]
|
|
|
+ }
|
|
|
+ return null
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
/** 显示问题回答对话 */
|
|
|
const showQuestAnswerDialogue = () => {
|
|
|
// 缓存当前对话
|
|
|
@@ -1468,6 +1595,94 @@ onUnmounted(() => {
|
|
|
display: none;
|
|
|
}
|
|
|
|
|
|
+.single-choice-card {
|
|
|
+ max-width: 55%;
|
|
|
+ position: absolute;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ bottom: rpx(50);
|
|
|
+ right: auto;
|
|
|
+ animation: dialogueEnterCenter 0.6s ease forwards;
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+
|
|
|
+.single-choice-card::before {
|
|
|
+ display: none;
|
|
|
+}
|
|
|
+
|
|
|
+.single-choice-content {
|
|
|
+ font-size: rpx(12);
|
|
|
+ line-height: 1.4;
|
|
|
+ color: #333;
|
|
|
+ text-align: left;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.question-text {
|
|
|
+ margin-bottom: rpx(10);
|
|
|
+ padding-bottom: rpx(8);
|
|
|
+ border-bottom: rpx(1) dashed #ddd;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.options-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: rpx(6);
|
|
|
+ margin-bottom: rpx(12);
|
|
|
+}
|
|
|
+
|
|
|
+.option-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: rpx(8) rpx(12);
|
|
|
+ background: #f8f9fa;
|
|
|
+ border: rpx(1) solid #e9ecef;
|
|
|
+ border-radius: rpx(6);
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: #e8f4fd;
|
|
|
+ border-color: #409EFF;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.selected {
|
|
|
+ background: #e8f4fd;
|
|
|
+ border-color: #409EFF;
|
|
|
+ box-shadow: 0 rpx(2) rpx(8) rgba(64, 158, 255, 0.2);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.option-label {
|
|
|
+ width: rpx(24);
|
|
|
+ height: rpx(24);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: #fff;
|
|
|
+ border: rpx(1) solid #ddd;
|
|
|
+ border-radius: 50%;
|
|
|
+ font-size: rpx(10);
|
|
|
+ font-weight: 600;
|
|
|
+ color: #666;
|
|
|
+ margin-right: rpx(8);
|
|
|
+ flex-shrink: 0;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ .option-item.selected & {
|
|
|
+ background: #409EFF;
|
|
|
+ border-color: #409EFF;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.option-content {
|
|
|
+ font-size: rpx(11);
|
|
|
+ color: #333;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
@keyframes dialogueEnterCenter {
|
|
|
from {
|
|
|
opacity: 0;
|