Commit 9d73c1d5 by Liu

fix:调用自动提问接口和重新分析时不展示点赞点踩复制按钮

parent 48808e81
...@@ -17,9 +17,10 @@ interface ChatAnswerBoxProps { ...@@ -17,9 +17,10 @@ interface ChatAnswerBoxProps {
isLastAnswer: boolean isLastAnswer: boolean
index: number index: number
onSubmitQuestion: (question: string, productCode?: string) => void onSubmitQuestion: (question: string, productCode?: string) => void
onRecommendLoadingChange?: (loading: boolean) => void
} }
export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, isLastAnswer, onSubmitQuestion }) => { export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, isLastAnswer, onSubmitQuestion, onRecommendLoadingChange }) => {
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)
...@@ -48,6 +49,13 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, ...@@ -48,6 +49,13 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex,
setInnerRecord(record) setInnerRecord(record)
}, [record]) }, [record])
/**
* 当当前 AI 记录没有对应的问题文本时(例如策略页进入会话后的自动分析、
* 点击「重新分析」触发的自动问题),需要隐藏点赞 / 点踩 / 复制按钮。
* 对于正常问答(有用户问题)则保持原有行为。
*/
const hideOperateForRecord = !innerRecord.question
return ( return (
<div> <div>
{innerRecord.answerList.map((item, index) => { {innerRecord.answerList.map((item, index) => {
...@@ -71,6 +79,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, ...@@ -71,6 +79,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex,
onSubmitQuestion={onSubmitQuestion} onSubmitQuestion={onSubmitQuestion}
isLastAnswer={isLastAnswer} isLastAnswer={isLastAnswer}
answer={item} answer={item}
hideOperate={hideOperateForRecord}
/> />
)} )}
{!item.isShow && !item.isChatMaxCount && ( {!item.isShow && !item.isChatMaxCount && (
...@@ -81,6 +90,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, ...@@ -81,6 +90,7 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex,
onTyping={handleTyping} onTyping={handleTyping}
onComplate={() => handleComplate(item)} onComplate={() => handleComplate(item)}
answer={item} answer={item}
hideOperate={hideOperateForRecord}
/> />
)} )}
{!item.isShow && item.isChatMaxCount && <ChatMaxCount />} {!item.isShow && item.isChatMaxCount && <ChatMaxCount />}
...@@ -101,7 +111,11 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex, ...@@ -101,7 +111,11 @@ export const ChatAnswerBox: React.FC<ChatAnswerBoxProps> = ({ record, showIndex,
)} )}
{isLastAnswer && !item.isChatMaxCount && isShowRecommend && recommendUseAnswer && ( {isLastAnswer && !item.isChatMaxCount && isShowRecommend && recommendUseAnswer && (
<ChatAnswerRecommend onSubmitQuestion={onSubmitQuestion} answer={recommendUseAnswer} /> <ChatAnswerRecommend
onSubmitQuestion={onSubmitQuestion}
answer={recommendUseAnswer}
onLoadingChange={onRecommendLoadingChange}
/>
)} )}
<div className="h-[20px] sm:h-[32px] w-full"></div> <div className="h-[20px] sm:h-[32px] w-full"></div>
</div> </div>
......
...@@ -15,8 +15,12 @@ import { UnLikeModal } from '@/components/UnLikeModal' ...@@ -15,8 +15,12 @@ import { UnLikeModal } from '@/components/UnLikeModal'
interface ChatAnswerOperateProps { interface ChatAnswerOperateProps {
answer: Answer answer: Answer
/**
* 是否隐藏点赞 / 点踩 / 复制按钮(用于策略页自动分析、重新分析等场景)
*/
hideFeedbackAndCopy?: boolean
} }
export const ChatAnswerOperate: React.FC<ChatAnswerOperateProps> = ({ answer }) => { export const ChatAnswerOperate: React.FC<ChatAnswerOperateProps> = ({ answer, hideFeedbackAndCopy }) => {
const showToast = useToast() const showToast = useToast()
// 兜底读取缓存的 tacticsMeta(会话从 tactics 打开的元信息) // 兜底读取缓存的 tacticsMeta(会话从 tactics 打开的元信息)
const tacticsMetaFromStorage = useMemo(() => { const tacticsMetaFromStorage = useMemo(() => {
...@@ -149,22 +153,27 @@ export const ChatAnswerOperate: React.FC<ChatAnswerOperateProps> = ({ answer }) ...@@ -149,22 +153,27 @@ export const ChatAnswerOperate: React.FC<ChatAnswerOperateProps> = ({ answer })
} }
return ( return (
<div className="sm:mt-[12px] flex gap-[4px] justify-end"> <div className="sm:mt-[12px] flex gap-[4px] justify-end">
{/* 点赞 */} {/* 点赞 / 点踩 / 复制:在自动分析 / 重新分析等场景下整体隐藏 */}
<Tooltip color="foreground" content={isLike ? '取消点赞' : '点赞'} className="capitalize"> {!hideFeedbackAndCopy && (
<Button variant="light" isIconOnly aria-label="LikeIcon" onPress={handleLike.run}> <>
{isLike ? <LikeIconA /> : <LikeIcon />} {/* 点赞 */}
</Button> <Tooltip color="foreground" content={isLike ? '取消点赞' : '点赞'} className="capitalize">
</Tooltip> <Button variant="light" isIconOnly aria-label="LikeIcon" onPress={handleLike.run}>
{/* 点踩 */} {isLike ? <LikeIconA /> : <LikeIcon />}
<Tooltip color="foreground" content={isUnLike ? '取消点踩' : '点踩'} className="capitalize"> </Button>
<Button variant="light" isIconOnly aria-label="UnLikeIcon" onPress={handleUnLike}> </Tooltip>
{isUnLike ? <UnLikeIconA /> : <UnLikeIcon />} {/* 点踩 */}
</Button> <Tooltip color="foreground" content={isUnLike ? '取消点踩' : '点踩'} className="capitalize">
</Tooltip> <Button variant="light" isIconOnly aria-label="UnLikeIcon" onPress={handleUnLike}>
{/* 复制 */} {isUnLike ? <UnLikeIconA /> : <UnLikeIcon />}
<Tooltip color="foreground" content="复制" className="capitalize"> </Button>
<Button variant="light" isIconOnly aria-label="CopyIcon" onPress={handleCopy}><CopyIcon /></Button> </Tooltip>
</Tooltip> {/* 复制 */}
<Tooltip color="foreground" content="复制" className="capitalize">
<Button variant="light" isIconOnly aria-label="CopyIcon" onPress={handleCopy}><CopyIcon /></Button>
</Tooltip>
</>
)}
{/* 收藏(当路由未标记 from=tactics 且不存在 userMeta 缓存时才展示) */} {/* 收藏(当路由未标记 from=tactics 且不存在 userMeta 缓存时才展示) */}
{!shouldHideCollect && ( {!shouldHideCollect && (
<Tooltip color="foreground" content={isCollect ? '取消收藏' : '收藏'} className="capitalize"> <Tooltip color="foreground" content={isCollect ? '取消收藏' : '收藏'} className="capitalize">
......
...@@ -15,6 +15,8 @@ interface ChatAnswerParserProps { ...@@ -15,6 +15,8 @@ interface ChatAnswerParserProps {
onTyping: () => void onTyping: () => void
onComplate: () => void onComplate: () => void
onSubmitQuestion: (question: string, productCode?: string) => void onSubmitQuestion: (question: string, productCode?: string) => void
/** 是否在当前回答中隐藏点赞/点踩/复制按钮(例如策略页自动分析、重新分析) */
hideOperate?: boolean
} }
function CheckIcon({ ...props }) { function CheckIcon({ ...props }) {
...@@ -35,12 +37,12 @@ function CheckIcon({ ...props }) { ...@@ -35,12 +37,12 @@ function CheckIcon({ ...props }) {
) )
} }
export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer, onTyping, onComplate, answer, isStopTyping, onSubmitQuestion }) => { export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer, onTyping, onComplate, answer, isStopTyping, onSubmitQuestion, hideOperate }) => {
const formatAnswer = formatMarkdown(answer.answer || '') const formatAnswer = formatMarkdown(answer.answer || '')
const [displayedText, setDisplayedText] = useState('') const [displayedText, setDisplayedText] = useState('')
const [currentIndex, setCurrentIndex] = useState(0) const [currentIndex, setCurrentIndex] = useState(0)
const [isTyping, setIsTyping] = useState(false) const [isTyping, setIsTyping] = useState(false)
const [hideOperate, setHideOperate] = useState(false) const [hideOperateByCard, setHideOperateByCard] = useState(false)
const [isImageAnswer, setIsImageAnswer] = useState(false) const [isImageAnswer, setIsImageAnswer] = useState(false)
const [hasProcessedCardList, setHasProcessedCardList] = useState(false) // 添加标记,避免重复处理cardList const [hasProcessedCardList, setHasProcessedCardList] = useState(false) // 添加标记,避免重复处理cardList
...@@ -168,9 +170,11 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer ...@@ -168,9 +170,11 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer
}, [isStopTyping]) }, [isStopTyping])
useEffect(() => { useEffect(() => {
setHideOperate((answer.cardList || []).some(attachment => attachment?.type === 'box' || attachment?.type?.includes('card-'))) setHideOperateByCard((answer.cardList || []).some(attachment => attachment?.type === 'box' || attachment?.type?.includes('card-')))
}, [answer.cardList]) }, [answer.cardList])
const shouldHideOperate = hideOperateByCard || hideOperate
return ( return (
<div className="answerParser"> <div className="answerParser">
...@@ -200,7 +204,7 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer ...@@ -200,7 +204,7 @@ export const ChatAnswerParser: React.FC<ChatAnswerParserProps> = ({ isLastAnswer
&& answer.cardList?.length !== 0 && answer.cardList?.length !== 0
&& <ChatAnswerAttachment fromParser isLastAnswer={isLastAnswer} onSubmitQuestion={onSubmitQuestion} answer={answer} />} && <ChatAnswerAttachment fromParser isLastAnswer={isLastAnswer} onSubmitQuestion={onSubmitQuestion} answer={answer} />}
{!isTyping && !hideOperate && <ChatAnswerOperate answer={answer} />} {!isTyping && !shouldHideOperate && <ChatAnswerOperate answer={answer} hideFeedbackAndCopy={hideOperate} />}
{!isTyping && <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>
......
...@@ -7,24 +7,31 @@ import SendIcon from '@/assets/svg/sendBlack.svg?react' ...@@ -7,24 +7,31 @@ import SendIcon from '@/assets/svg/sendBlack.svg?react'
interface ChatAnswerRecommendProps { interface ChatAnswerRecommendProps {
answer: Answer answer: Answer
onSubmitQuestion: (question: string) => void onSubmitQuestion: (question: string) => void
onLoadingChange?: (loading: boolean) => void
} }
export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer, onSubmitQuestion }) => { export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer, onSubmitQuestion, onLoadingChange }) => {
let isGet = false let isGet = false
const [questionList, setQuestionList] = useState<string[]>([]) const [questionList, setQuestionList] = useState<string[]>([])
const [loading, setLoading] = useState<boolean>(false) const [loading, setLoading] = useState<boolean>(false)
const getAnswerRecommend = async () => { const getAnswerRecommend = async () => {
setLoading(true) setLoading(true)
onLoadingChange?.(true)
// 从 sessionStorage 中获取 toolId // 从 sessionStorage 中获取 toolId
const toolId = typeof window !== 'undefined' ? sessionStorage.getItem('currentToolId') : null const toolId = typeof window !== 'undefined' ? sessionStorage.getItem('currentToolId') : null
const res = await fetchQueryRecommendQuestion( try {
answer.conversationId || '', const res = await fetchQueryRecommendQuestion(
answer.recordId || '', answer.conversationId || '',
toolId || undefined, answer.recordId || '',
) toolId || undefined,
if (res.ok) { )
setQuestionList(res.data.questionList) if (res.ok) {
setQuestionList(res.data.questionList)
}
}
finally {
setLoading(false)
onLoadingChange?.(false)
} }
setLoading(false)
} }
useEffect(() => { useEffect(() => {
...@@ -36,11 +43,14 @@ export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer ...@@ -36,11 +43,14 @@ export const ChatAnswerRecommend: React.FC<ChatAnswerRecommendProps> = ({ answer
} }
const shouldSkipFetch = sessionStorage.getItem('showToolQuestion') === 'true' const shouldSkipFetch = sessionStorage.getItem('showToolQuestion') === 'true'
if (shouldSkipFetch) { if (!shouldSkipFetch) {
return // skip calling recommend API when tool question mode is enabled // 正常问答模式:发起推荐问题请求,并通过 onLoadingChange 通知外层 loading 状态
getAnswerRecommend()
}
else {
// 工具问答模式:跳过推荐问题请求,直接告知外层无需等待
onLoadingChange?.(false)
} }
getAnswerRecommend()
} }
}, []) }, [])
return ( return (
......
...@@ -36,6 +36,8 @@ export const TacticsChat: React.FC = () => { ...@@ -36,6 +36,8 @@ export const TacticsChat: React.FC = () => {
const [showClearConfirm, setShowClearConfirm] = useState(false) const [showClearConfirm, setShowClearConfirm] = useState(false)
// 标记当前会话是否已有历史记录;null 表示尚未完成查询 // 标记当前会话是否已有历史记录;null 表示尚未完成查询
const [hasHistory, setHasHistory] = useState<boolean | null>(null) const [hasHistory, setHasHistory] = useState<boolean | null>(null)
// 标记当前会话中最后一条 AI 回答的推荐问题是否仍在加载中(v1/query_recommend_question)
const [isRecommendLoading, setIsRecommendLoading] = useState(false)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { const {
shouldSendQuestion: shouldSendQuestionFromState, shouldSendQuestion: shouldSendQuestionFromState,
...@@ -823,9 +825,9 @@ export const TacticsChat: React.FC = () => { ...@@ -823,9 +825,9 @@ export const TacticsChat: React.FC = () => {
<button <button
type="button" type="button"
onClick={handleReanalyze} onClick={handleReanalyze}
disabled={!currentIdRef.current || isLoading || isAsking} disabled={!currentIdRef.current || isLoading || isAsking || isRecommendLoading}
className={`flex items-center gap-[4px] text-[14px] transition-opacity bg-transparent border-none outline-none ${ className={`flex items-center gap-[4px] text-[14px] transition-opacity bg-transparent border-none outline-none ${
!currentIdRef.current || isLoading || isAsking !currentIdRef.current || isLoading || isAsking || isRecommendLoading
? 'text-[#B2B8C1] cursor-not-allowed opacity-60' ? 'text-[#B2B8C1] cursor-not-allowed opacity-60'
: 'text-[#4A90E2] hover:opacity-80 cursor-pointer' : 'text-[#4A90E2] hover:opacity-80 cursor-pointer'
}`} }`}
...@@ -902,6 +904,8 @@ export const TacticsChat: React.FC = () => { ...@@ -902,6 +904,8 @@ export const TacticsChat: React.FC = () => {
showIndex={0} showIndex={0}
record={record} record={record}
index={index} index={index}
// 仅对最后一条 AI 回答的推荐问题 loading 状态进行跟踪
onRecommendLoadingChange={index === allItems.length - 1 ? setIsRecommendLoading : undefined}
/> />
)} )}
</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