Commit 9d73c1d5 by Liu

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

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