fix:澄清问答配对逻辑修复(按 messageOrder 升序匹配)
问题:DB 中 clarification_answer 可能先于 question 存入(messageOrder 更小), 导致原「找最后一张 card」的匹配逻辑把 answer 错误渲染成独立 user 文本消息 (如截图中的「love」紫色气泡)。 修复: - rebuildResultMessages 改用 messageOrder 升序配对策略: 1. 先分别提取所有 clarification_question 和 clarification_answer,按 messageOrder 排序 2. 为每个 question 配对它之后第一个未分配的 answer 3. 遍历时 question 替换为含答案的 card 消息,已配对 answer 跳过,未配对 answer 作为独立 user 文本 影响: - ScriptView.vue(chat-bubble 流程视图) - ScriptDetailView.vue(详情页只读视图) 数据基础:后端 answer 仍只存 value(语义化),前端渲染时通过 options 映射 label。
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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 = {} }
|
||||
|
||||
Reference in New Issue
Block a user