diff --git a/mini-program/src/pages/main/ScriptDetailView.vue b/mini-program/src/pages/main/ScriptDetailView.vue index c4a83cf..3a659b8 100644 --- a/mini-program/src/pages/main/ScriptDetailView.vue +++ b/mini-program/src/pages/main/ScriptDetailView.vue @@ -143,22 +143,58 @@ const rebuildResultMessages = (scriptData, messages) => { }) } - // 2. 按 messageOrder 遍历 messages,转换为对应 kind + // 2. 澄清问答按 messageOrder 升序配对(DB 中 answer 可能先于 question 存入) + const clarificationQuestions = messages + .filter(m => m.type === 'clarification_question') + .sort((a, b) => (a.messageOrder || 0) - (b.messageOrder || 0)) + const clarificationAnswers = messages + .filter(m => m.type === 'clarification_answer') + .sort((a, b) => (a.messageOrder || 0) - (b.messageOrder || 0)) + + // 为每个 question 配对它之后第一个未分配的 answer(按 messageOrder 升序) + const answerAssigned = new Set() + const questionAnswerMap = new Map() + const unmatchedAnswers = [] + clarificationQuestions.forEach(q => { + const matched = clarificationAnswers.find(a => + !answerAssigned.has(a.id) && (a.messageOrder || 0) > (q.messageOrder || 0) + ) + if (matched) { + answerAssigned.add(matched.id) + questionAnswerMap.set(q.id, matched) + } + }) + clarificationAnswers.forEach(a => { + if (!answerAssigned.has(a.id)) unmatchedAnswers.push(a) + }) + + // 为每个 question 生成 card 消息(含配对答案) + const cardMessagesByQuestionId = new Map() + clarificationQuestions.forEach(q => { + let card = {} + try { card = JSON.parse(q.content || '{}') } catch (e) { card = {} } + const answer = questionAnswerMap.get(q.id) + cardMessagesByQuestionId.set(q.id, { + id: q.id, + role: 'assistant', + kind: 'card', + card, + answer: answer?.content || '', + selectedValue: answer?.content || '', + submitted: true + }) + }) + + // 3. 按 messageOrder 顺序遍历,构建统一消息流 messages.forEach(m => { if (m.type === 'clarification_question') { - let card = {} - try { card = JSON.parse(m.content || '{}') } catch (e) { card = {} } - result.push({ id: m.id, role: 'assistant', kind: 'card', card, answer: '', submitted: true }) + const cardMsg = cardMessagesByQuestionId.get(m.id) + if (cardMsg) result.push(cardMsg) } else if (m.type === 'clarification_answer') { - const lastCard = [...result].reverse().find(x => x.kind === 'card') - if (lastCard) { - // answer 存用户提交的 value,渲染时通过 card.options 映射成中文 label - lastCard.answer = m.content || '' - lastCard.selectedValue = m.content || '' - } else { - // 无对应 question,作为独立 user 消息 - result.push({ id: m.id, role: 'user', kind: 'text', content: m.content || '' }) - } + // 已配对的 answer 由 question 那一步负责渲染;这里跳过 + if (answerAssigned.has(m.id)) return + // 未配对的 answer 展示为独立 user 文本 + result.push({ id: m.id, role: 'user', kind: 'text', content: m.content || '' }) } else if (m.type === 'outline') { let outline = {} try { outline = JSON.parse(m.content || '{}') } catch (e) { outline = {} } @@ -172,7 +208,7 @@ const rebuildResultMessages = (scriptData, messages) => { } }) - // 3. 如果 messages 中没有 script 类型消息,fallback 到 plotJson.fullContent + // 4. 如果 messages 中没有 script 类型消息,fallback 到 plotJson.fullContent const hasNovel = result.some(m => m.kind === 'novel') if (!hasNovel && scriptData?.plotJson?.fullContent) { result.push({ diff --git a/mini-program/src/pages/main/ScriptView.vue b/mini-program/src/pages/main/ScriptView.vue index 2941906..94a836f 100644 --- a/mini-program/src/pages/main/ScriptView.vue +++ b/mini-program/src/pages/main/ScriptView.vue @@ -1489,43 +1489,75 @@ const openScriptChat = async (payload = {}) => { // 从后端 messages 重建统一对话消息流,复刻生成时的 chat-bubble 展示 // 不再重复 push wishTheme/novelContent(messages 中已包含) const messagesList = messages.value || [] + + // 第一步:先把所有澄清问答按 question-answer 配对(按 messageOrder 升序) + // 原因:DB 中 answer 可能先于 question 存入(messageOrder 更小),不能简单按数组顺序匹配「最后一张 card」 + // 策略:question 按 messageOrder 升序排序,每条 question 配对它之后第一个未分配的 answer + const clarificationQuestions = messagesList + .filter(m => m.type === 'clarification_question') + .sort((a, b) => (a.messageOrder || 0) - (b.messageOrder || 0)) + const clarificationAnswers = messagesList + .filter(m => m.type === 'clarification_answer') + .sort((a, b) => (a.messageOrder || 0) - (b.messageOrder || 0)) + + // 为每个 question 配对一个 answer(按 messageOrder 升序,第一个 > question.messageOrder 且未被占用的) + const answerAssigned = new Set() + const questionAnswerMap = new Map() + const unmatchedAnswers = [] + clarificationQuestions.forEach(q => { + const matched = clarificationAnswers.find(a => + !answerAssigned.has(a.id) && (a.messageOrder || 0) > (q.messageOrder || 0) + ) + if (matched) { + answerAssigned.add(matched.id) + questionAnswerMap.set(q.id, matched) + } + }) + // 未匹配的 answer(如 messageOrder 在所有 question 之前的预填充)按独立 user 消息展示 + clarificationAnswers.forEach(a => { + if (!answerAssigned.has(a.id)) unmatchedAnswers.push(a) + }) + + // 为每个 question 生成 card 消息(含配对答案) + const cardMessagesByQuestionId = new Map() + clarificationQuestions.forEach(q => { + let card = {} + try { card = JSON.parse(q.content || '{}') } catch (e) { card = {} } + const answer = questionAnswerMap.get(q.id) + cardMessagesByQuestionId.set(q.id, { + id: q.id || createMessageId(), + role: 'assistant', + kind: 'card', + content: '', + card, + answer: answer?.content || '', + selectedValue: answer?.content || '', + pending: false, + time: formatMessageTime(), + submitted: true, + confirmed: true + }) + }) + + // 第二步:按 messageOrder 顺序遍历消息,构建统一消息流 messagesList.forEach(m => { if (m.type === 'clarification_question') { - let card = {} - try { card = JSON.parse(m.content || '{}') } catch (e) { card = {} } + const cardMsg = cardMessagesByQuestionId.get(m.id) + if (cardMsg) resultMessages.value.push(cardMsg) + } else if (m.type === 'clarification_answer') { + // 已配对到 question 的 answer 由 question 那一步负责渲染;这里跳过 + if (answerAssigned.has(m.id)) return + // 未配对的 answer(如用户对未识别 question 的回答)展示为独立 user 文本 resultMessages.value.push({ id: m.id || createMessageId(), - role: 'assistant', - kind: 'card', - content: '', - card, - answer: '', + role: 'user', + kind: 'text', + content: m.content || '', pending: false, time: formatMessageTime(), - submitted: true, - confirmed: true + submitted: false, + confirmed: false }) - } else if (m.type === 'clarification_answer') { - // 把答案合并到上一条 card 消息:answer 存用户原始 value,selectedValue 也存 value 用于渲染时映射 label - const lastCard = [...resultMessages.value].reverse().find(x => x.kind === 'card') - if (lastCard) { - const rawValue = m.content || '' - lastCard.answer = rawValue // 原始 value(用于查找 label) - lastCard.selectedValue = rawValue // 选中的 value(渲染时通过 options 映射成 label) - lastCard.submitted = true - } else { - // 无对应 question,作为独立 user 消息 - resultMessages.value.push({ - id: m.id || createMessageId(), - role: 'user', - kind: 'text', - content: m.content || '', - pending: false, - time: formatMessageTime(), - submitted: false, - confirmed: false - }) - } } else if (m.type === 'outline') { let outline = {} try { outline = JSON.parse(m.content || '{}') } catch (e) { outline = {} }