Commit eb862599 by weiyudumei

feat: 实现 workFlowSessionId 的传递逻辑,从会话历史中查找最后一个有 workFlowSessionId 的答案并传递,重新分析时不传递

parent 974b0191
...@@ -206,7 +206,9 @@ export const Chat: React.FC = () => { ...@@ -206,7 +206,9 @@ export const Chat: React.FC = () => {
question: string, question: string,
productCode?: string, productCode?: string,
toolId?: string, toolId?: string,
extraParams?: { paramMap?: Record<string, string>, recordId?: string, recordType?: string, workFlowSessionId?: string }, extraParams?: { paramMap?: Record<string, string>, recordId?: string, recordType?: string, workFlowSessionId?: string, isReask?: boolean },
workFlowSessionId?: string,
recordId?: string,
) => { ) => {
// 优先读取缓存中的 toolId,再回退到传参或 Redux // 优先读取缓存中的 toolId,再回退到传参或 Redux
const sessionToolId = sessionStorage.getItem('currentToolId') ?? undefined const sessionToolId = sessionStorage.getItem('currentToolId') ?? undefined
...@@ -265,12 +267,39 @@ export const Chat: React.FC = () => { ...@@ -265,12 +267,39 @@ export const Chat: React.FC = () => {
if (extraParams?.paramMap) { if (extraParams?.paramMap) {
requestBody.paramMap = extraParams.paramMap requestBody.paramMap = extraParams.paramMap
} }
// 优先使用 extraParams 中的值,其次使用直接传递的参数
if (extraParams?.recordId) { if (extraParams?.recordId) {
requestBody.recordId = extraParams.recordId requestBody.recordId = extraParams.recordId
} }
else if (recordId) {
requestBody.recordId = recordId
}
// workFlowSessionId 的传递逻辑:
// 1. 如果是重新问答(isReask === true),不传递 workFlowSessionId
// 2. 优先使用 extraParams 中的值
// 3. 其次使用直接传递的参数
// 4. 如果都没有,从会话历史中找到最后一个有 workFlowSessionId 的答案
const isReask = extraParams?.isReask === true
if (!isReask) {
if (extraParams?.workFlowSessionId) { if (extraParams?.workFlowSessionId) {
requestBody.workFlowSessionId = extraParams.workFlowSessionId requestBody.workFlowSessionId = extraParams.workFlowSessionId
} }
else if (workFlowSessionId) {
requestBody.workFlowSessionId = workFlowSessionId
}
else {
// 从会话历史中找到最后一个有 workFlowSessionId 的答案
// 注意:这里使用 allItems 的当前值(在添加新记录之前),所以需要从倒数第二个开始查找
for (let i = allItems.length - 1; i >= 0; i--) {
const item = allItems[i]
if (item.role === 'ai' && item.answerList?.[0]?.workFlowSessionId) {
requestBody.workFlowSessionId = item.answerList[0].workFlowSessionId
break
}
}
}
}
// 如果传入了 recordType,优先使用传入的值(workflow 提交场景) // 如果传入了 recordType,优先使用传入的值(workflow 提交场景)
if (extraParams?.recordType) { if (extraParams?.recordType) {
...@@ -940,6 +969,7 @@ export const Chat: React.FC = () => { ...@@ -940,6 +969,7 @@ export const Chat: React.FC = () => {
index={index} index={index}
onWorkflowSubmit={handleWorkflowSubmit} onWorkflowSubmit={handleWorkflowSubmit}
isWorkflowSubmitting={isAsking} isWorkflowSubmitting={isAsking}
allItems={allItems}
/> />
)} )}
</div> </div>
......
...@@ -16,7 +16,7 @@ import { FilePreviewModal } from '@/components/FilePreviewModal' ...@@ -16,7 +16,7 @@ import { FilePreviewModal } from '@/components/FilePreviewModal'
interface ChatAnswerAttachmentProps { interface ChatAnswerAttachmentProps {
answer: Answer answer: Answer
isLastAnswer?: boolean isLastAnswer?: boolean
onSubmitQuestion?: (question: string, productCode?: string) => void onSubmitQuestion?: (question: string, productCode?: string, workFlowSessionId?: string, recordId?: string) => void
onWorkflowSubmit?: (paramMap: Record<string, string>, recordId?: string, workFlowSessionId?: string) => void onWorkflowSubmit?: (paramMap: Record<string, string>, recordId?: string, workFlowSessionId?: string) => void
isWorkflowSubmitting?: boolean isWorkflowSubmitting?: boolean
isAsking?: boolean isAsking?: boolean
...@@ -36,6 +36,9 @@ export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({ ...@@ -36,6 +36,9 @@ export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({
const handleClickBoxItem = (produceName: string, productCode: string) => { const handleClickBoxItem = (produceName: string, productCode: string) => {
if (onSubmitQuestion) { if (onSubmitQuestion) {
// 注意:workFlowSessionId 和 recordId 应该从会话历史中获取,而不是从当前的 answer
// 这里先传递 undefined,让上层的 handleSubmitQuestionWithWorkflow 来处理
// 因为 ChatAnswerAttachment 无法访问整个会话历史
onSubmitQuestion(produceName, productCode) onSubmitQuestion(produceName, productCode)
} }
} }
...@@ -175,7 +178,7 @@ export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({ ...@@ -175,7 +178,7 @@ export const ChatAnswerAttachment: React.FC<ChatAnswerAttachmentProps> = ({
<CardWorkflowForm <CardWorkflowForm
attachment={attachment} attachment={attachment}
answer={answer} answer={answer}
onSubmitWorkflow={onWorkflowSubmit || (() => {})} onSubmitWorkflow={onWorkflowSubmit || (() => { })}
isSubmitting={isWorkflowSubmitting} isSubmitting={isWorkflowSubmitting}
isCompleted={false} isCompleted={false}
/> />
......
...@@ -17,13 +17,14 @@ interface ChatAnswerBoxProps { ...@@ -17,13 +17,14 @@ interface ChatAnswerBoxProps {
showIndex: number showIndex: number
isLastAnswer: boolean isLastAnswer: boolean
index: number index: number
onSubmitQuestion: (question: string, productCode?: string) => void onSubmitQuestion: (question: string, productCode?: string, workFlowSessionId?: string, recordId?: string) => void
onRecommendLoadingChange?: (loading: boolean) => void onRecommendLoadingChange?: (loading: boolean) => void
onWorkflowSubmit?: (paramMap: Record<string, string>, recordId?: string) => void onWorkflowSubmit?: (paramMap: Record<string, string>, recordId?: string) => void
isWorkflowSubmitting?: boolean isWorkflowSubmitting?: boolean
allItems?: ChatRecord[] // 传入整个会话历史,用于查找最后一个 workFlowSessionId
} }
export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, isLastAnswer, onSubmitQuestion, onRecommendLoadingChange, onWorkflowSubmit, isWorkflowSubmitting }) => { export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, isLastAnswer, onSubmitQuestion, onRecommendLoadingChange, onWorkflowSubmit, isWorkflowSubmitting, allItems = [] }) => {
const [isShowRecommend, setIsShowRecommend] = useState(false) const [isShowRecommend, setIsShowRecommend] = useState(false)
const [recommendUseAnswer, setRecommendUseAnswer] = useState<Answer>() const [recommendUseAnswer, setRecommendUseAnswer] = useState<Answer>()
const [innerRecord, setInnerRecord] = useState<ChatRecord>(record) const [innerRecord, setInnerRecord] = useState<ChatRecord>(record)
...@@ -58,9 +59,37 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, ...@@ -58,9 +59,37 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex,
{innerRecord.answerList.map((item, index) => { {innerRecord.answerList.map((item, index) => {
// 当前默认始终展示点赞 / 点踩 / 复制等操作区(是否展示由下层基于卡片类型自行决定) // 当前默认始终展示点赞 / 点踩 / 复制等操作区(是否展示由下层基于卡片类型自行决定)
const shouldHideOperate = false const shouldHideOperate = false
// 如果 firstQaFlag 为 true 且存在 workflow 卡片,隐藏整个 chatItem(表单会自动提交,用户不需要看到这次的结果)
// 但保留组件的渲染,确保 CardWorkflowForm 的自动提交逻辑能够执行
const hasWorkflowCard = (item.cardList || []).some(attachment => attachment.type === 'card-workflow')
const shouldHideChatItem = item.firstQaFlag === true && hasWorkflowCard
// 创建包装函数,从会话历史中找到最后一个有 workFlowSessionId 的答案,并在追问时传递
const handleSubmitQuestionWithWorkflow = (question: string, productCode?: string) => {
// 从会话历史中找到最后一个有 workFlowSessionId 的答案(从后往前查找)
let lastWorkFlowSessionId: string | undefined
let lastRecordId: string | undefined
for (let i = allItems.length - 1; i >= 0; i--) {
const historyItem = allItems[i]
if (historyItem.role === 'ai' && historyItem.answerList?.[0]?.workFlowSessionId) {
lastWorkFlowSessionId = historyItem.answerList[0].workFlowSessionId
lastRecordId = historyItem.answerList[0].recordId
break
}
}
// 调用父组件传递的 onSubmitQuestion,传递 workFlowSessionId 和 recordId
onSubmitQuestion(question, productCode, lastWorkFlowSessionId, lastRecordId)
}
return ( return (
index === showIndex && ( index === showIndex && (
<div className="chatItemBotContainer w-full" key={`${item.recordId}-${index}`}> <div
className="chatItemBotContainer w-full"
key={`${item.recordId}-${index}`}
style={shouldHideChatItem ? { visibility: 'hidden', height: 0, overflow: 'hidden', margin: 0, padding: 0 } : undefined}
>
<div className="flex"> <div className="flex">
<Avatar <Avatar
className="sm:mr-[20px] hidden sm:block flex-shrink-0" className="sm:mr-[20px] hidden sm:block flex-shrink-0"
...@@ -75,7 +104,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, ...@@ -75,7 +104,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex,
<div className="content"> <div className="content">
{item.isShow && ( {item.isShow && (
<ChatAnswerShower <ChatAnswerShower
onSubmitQuestion={onSubmitQuestion} onSubmitQuestion={handleSubmitQuestionWithWorkflow}
isLastAnswer={isLastAnswer} isLastAnswer={isLastAnswer}
answer={item} answer={item}
hideOperate={shouldHideOperate} hideOperate={shouldHideOperate}
...@@ -86,7 +115,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, ...@@ -86,7 +115,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex,
)} )}
{!item.isShow && !item.isChatMaxCount && ( {!item.isShow && !item.isChatMaxCount && (
<ChatAnswerParser <ChatAnswerParser
onSubmitQuestion={onSubmitQuestion} onSubmitQuestion={handleSubmitQuestionWithWorkflow}
isLastAnswer={isLastAnswer} isLastAnswer={isLastAnswer}
isStopTyping={item.isStopTyping} isStopTyping={item.isStopTyping}
onTyping={handleTyping} onTyping={handleTyping}
...@@ -117,7 +146,10 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, ...@@ -117,7 +146,10 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex,
{isLastAnswer && !item.isChatMaxCount && isShowRecommend && recommendUseAnswer && ( {isLastAnswer && !item.isChatMaxCount && isShowRecommend && recommendUseAnswer && (
<ChatAnswerRecommend <ChatAnswerRecommend
onSubmitQuestion={onSubmitQuestion} onSubmitQuestion={(question) => {
// 使用 handleSubmitQuestionWithWorkflow,它会从会话历史中查找 workFlowSessionId
handleSubmitQuestionWithWorkflow(question)
}}
answer={recommendUseAnswer} answer={recommendUseAnswer}
onLoadingChange={onRecommendLoadingChange} onLoadingChange={onRecommendLoadingChange}
/> />
......
...@@ -14,7 +14,7 @@ interface ChatAnswerParserProps { ...@@ -14,7 +14,7 @@ interface ChatAnswerParserProps {
isLastAnswer: boolean isLastAnswer: boolean
onTyping: () => void onTyping: () => void
onComplate: () => void onComplate: () => void
onSubmitQuestion: (question: string, productCode?: string) => void onSubmitQuestion: (question: string, productCode?: string, workFlowSessionId?: string, recordId?: string) => void
/** 是否在当前回答中隐藏点赞/点踩/复制按钮(例如策略页自动分析、重新分析) */ /** 是否在当前回答中隐藏点赞/点踩/复制按钮(例如策略页自动分析、重新分析) */
hideOperate?: boolean hideOperate?: boolean
onWorkflowSubmit?: (paramMap: Record<string, string>, recordId?: string) => void onWorkflowSubmit?: (paramMap: Record<string, string>, recordId?: string) => void
...@@ -178,15 +178,9 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer ...@@ -178,15 +178,9 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer
const shouldHideOperate = hideOperateByCard || hideOperate const shouldHideOperate = hideOperateByCard || hideOperate
// 如果 firstQaFlag 为 true 且存在 workflow 卡片,隐藏答案文本(表单会自动提交,用户不需要看到这次的结果)
// 但保留 ChatAnswerAttachment 的渲染,确保 CardWorkflowForm 的自动提交逻辑能够执行
const hasWorkflowCard = (answer.cardList || []).some(attachment => attachment.type === 'card-workflow')
const shouldHideAnswerText = answer.firstQaFlag === true && hasWorkflowCard
return ( return (
<div className="answerParser"> <div className="answerParser">
{!shouldHideAnswerText && (
<div className="mb-[8px]"> <div className="mb-[8px]">
{/* <Chip color="primary" className="mb-[12px]">{answer.step?.message}</Chip> */} {/* <Chip color="primary" className="mb-[12px]">{answer.step?.message}</Chip> */}
{ answer.step?.step === 'answering' && ( { answer.step?.step === 'answering' && (
...@@ -200,9 +194,8 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer ...@@ -200,9 +194,8 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer
</Chip> </Chip>
)} )}
</div> </div>
)}
{!!displayedText.length && !shouldHideAnswerText && ( {!!displayedText.length && (
<div style={{ background: '#F7FAFD' }} className={answer.cardList?.length ? 'mb-[20px]' : ''}> <div style={{ background: '#F7FAFD' }} className={answer.cardList?.length ? 'mb-[20px]' : ''}>
<MarkdownDetail> <MarkdownDetail>
{displayedText} {displayedText}
...@@ -224,9 +217,9 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer ...@@ -224,9 +217,9 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer
)} )}
{/* 操作区:仅根据卡片类型 / 上层显式 hideOperate 控制是否展示 */} {/* 操作区:仅根据卡片类型 / 上层显式 hideOperate 控制是否展示 */}
{!isTyping && !shouldHideOperate && !shouldHideAnswerText && <ChatAnswerOperate answer={answer} />} {!isTyping && !shouldHideOperate && <ChatAnswerOperate answer={answer} />}
{!isTyping && !shouldHideAnswerText && <div className="flex text-[10px] right-[16px] text-[#d0d1d2] bottom-[4px]">AI生成</div>} {!isTyping && <div className="flex text-[10px] right-[16px] text-[#d0d1d2] bottom-[4px]">AI生成</div>}
</div> </div>
) )
} }
...@@ -8,7 +8,7 @@ import { MarkdownDetail } from '@/components/MarkdownDetail' ...@@ -8,7 +8,7 @@ import { MarkdownDetail } from '@/components/MarkdownDetail'
interface ChatAnswerShowerProps { interface ChatAnswerShowerProps {
answer: Answer answer: Answer
isLastAnswer: boolean isLastAnswer: boolean
onSubmitQuestion: (question: string) => void onSubmitQuestion: (question: string, productCode?: string, workFlowSessionId?: string, recordId?: string) => void
hideOperate?: boolean hideOperate?: boolean
onWorkflowSubmit?: (paramMap: Record<string, string>, recordId?: string) => void onWorkflowSubmit?: (paramMap: Record<string, string>, recordId?: string) => void
isWorkflowSubmitting?: boolean isWorkflowSubmitting?: boolean
...@@ -19,14 +19,9 @@ export const ChatAnswerShower: React.FC<ChatAnswerShowerProps> = ({ answer, isLa ...@@ -19,14 +19,9 @@ export const ChatAnswerShower: React.FC<ChatAnswerShowerProps> = ({ answer, isLa
const hideOperateByCard = (answer.cardList || []).some(attachment => attachment.type === 'box' || attachment?.type?.includes('card-')) const hideOperateByCard = (answer.cardList || []).some(attachment => attachment.type === 'box' || attachment?.type?.includes('card-'))
const hideOperate = hideOperateByCard || hideOperateProp const hideOperate = hideOperateByCard || hideOperateProp
// 如果 firstQaFlag 为 true 且存在 workflow 卡片,隐藏答案文本(表单会自动提交,用户不需要看到这次的结果)
// 但保留 ChatAnswerAttachment 的渲染,确保 CardWorkflowForm 的自动提交逻辑能够执行
const hasWorkflowCard = (answer.cardList || []).some(attachment => attachment.type === 'card-workflow')
const shouldHideAnswerText = answer.firstQaFlag === true && hasWorkflowCard
return ( return (
<div className="answerShower"> <div className="answerShower">
{answer.answer && !shouldHideAnswerText && ( {answer.answer && (
<div className={answer.cardList?.length ? 'mb-[12px] sm:mb-[20px]' : ''}> <div className={answer.cardList?.length ? 'mb-[12px] sm:mb-[20px]' : ''}>
<MarkdownDetail> <MarkdownDetail>
{formatMarkdown(answer.answer || '')} {formatMarkdown(answer.answer || '')}
...@@ -44,8 +39,8 @@ export const ChatAnswerShower: React.FC<ChatAnswerShowerProps> = ({ answer, isLa ...@@ -44,8 +39,8 @@ export const ChatAnswerShower: React.FC<ChatAnswerShowerProps> = ({ answer, isLa
/> />
)} )}
{/* 操作区:仅根据卡片类型 / 上层显式 hideOperate 控制是否展示 */} {/* 操作区:仅根据卡片类型 / 上层显式 hideOperate 控制是否展示 */}
{!hideOperate && !shouldHideAnswerText && <ChatAnswerOperate answer={answer} />} {!hideOperate && <ChatAnswerOperate answer={answer} />}
{!shouldHideAnswerText && <div className="flex text-[10px] right-[16px] text-[#d0d1d2] bottom-[4px]">AI生成</div>} <div className="flex text-[10px] right-[16px] text-[#d0d1d2] bottom-[4px]">AI生成</div>
</div> </div>
) )
} }
...@@ -500,9 +500,28 @@ export const TacticsChat: React.FC = () => { ...@@ -500,9 +500,28 @@ export const TacticsChat: React.FC = () => {
if (extra?.recordId) { if (extra?.recordId) {
requestBody.recordId = extra.recordId requestBody.recordId = extra.recordId
} }
// workFlowSessionId 的传递逻辑:
// 1. 如果是重新分析(isReanalyze === true),不传递 workFlowSessionId
// 2. 优先使用 extra 中的值
// 3. 如果都没有,从会话历史中找到最后一个有 workFlowSessionId 的答案
const isReanalyze = extra?.isReanalyze === true
if (!isReanalyze) {
if (extra?.workFlowSessionId) { if (extra?.workFlowSessionId) {
requestBody.workFlowSessionId = extra.workFlowSessionId requestBody.workFlowSessionId = extra.workFlowSessionId
} }
else {
// 从会话历史中找到最后一个有 workFlowSessionId 的答案
// 注意:这里使用 allItems 的当前值(在添加新记录之前),所以需要从倒数第二个开始查找
for (let i = allItems.length - 1; i >= 0; i--) {
const item = allItems[i]
if (item.role === 'ai' && item.answerList?.[0]?.workFlowSessionId) {
requestBody.workFlowSessionId = item.answerList[0].workFlowSessionId
break
}
}
}
}
// 调试日志:检查最终请求参数 // 调试日志:检查最终请求参数
console.log('[TacticsChat] handleSubmitQuestion: final requestBody', { console.log('[TacticsChat] handleSubmitQuestion: final requestBody', {
workOrderIds: requestBody.workOrderIds, workOrderIds: requestBody.workOrderIds,
...@@ -1036,7 +1055,15 @@ export const TacticsChat: React.FC = () => { ...@@ -1036,7 +1055,15 @@ export const TacticsChat: React.FC = () => {
// 提供给 ChatAnswerBox/推荐问题的提交方法,保持与手动提问一致的 recordType 逻辑 // 提供给 ChatAnswerBox/推荐问题的提交方法,保持与手动提问一致的 recordType 逻辑
const handleAnswerBoxSubmit = useCallback( const handleAnswerBoxSubmit = useCallback(
(question: string, productCode?: string) => { (question: string, productCode?: string, workFlowSessionId?: string, recordId?: string) => {
const extraParams: any = {}
if (workFlowSessionId) {
extraParams.workFlowSessionId = workFlowSessionId
}
if (recordId) {
extraParams.recordId = recordId
}
if (orderMeta) { if (orderMeta) {
return handleSubmitQuestion( return handleSubmitQuestion(
question, question,
...@@ -1048,6 +1075,7 @@ export const TacticsChat: React.FC = () => { ...@@ -1048,6 +1075,7 @@ export const TacticsChat: React.FC = () => {
includeQuestion: true, includeQuestion: true,
includeTacticsMeta: false, includeTacticsMeta: false,
includeOrderMeta: true, includeOrderMeta: true,
...extraParams,
}, },
) )
} }
...@@ -1064,6 +1092,7 @@ export const TacticsChat: React.FC = () => { ...@@ -1064,6 +1092,7 @@ export const TacticsChat: React.FC = () => {
includeQuestion: true, includeQuestion: true,
includeTacticsMeta: false, includeTacticsMeta: false,
includeUserMeta: true, includeUserMeta: true,
...extraParams,
}, },
) )
} }
...@@ -1076,6 +1105,7 @@ export const TacticsChat: React.FC = () => { ...@@ -1076,6 +1105,7 @@ export const TacticsChat: React.FC = () => {
recordType: 'A01', recordType: 'A01',
includeQuestion: true, includeQuestion: true,
includeTacticsMeta: true, includeTacticsMeta: true,
...extraParams,
}, },
) )
}, },
...@@ -1197,6 +1227,7 @@ export const TacticsChat: React.FC = () => { ...@@ -1197,6 +1227,7 @@ export const TacticsChat: React.FC = () => {
onRecommendLoadingChange={index === allItems.length - 1 ? setIsRecommendLoading : undefined} onRecommendLoadingChange={index === allItems.length - 1 ? setIsRecommendLoading : undefined}
onWorkflowSubmit={handleWorkflowSubmit} onWorkflowSubmit={handleWorkflowSubmit}
isWorkflowSubmitting={isAsking} isWorkflowSubmitting={isAsking}
allItems={allItems}
/> />
)} )}
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment