Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
S
sdream-ai-fe
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
侯明涛
sdream-ai-fe
Commits
9d73c1d5
Commit
9d73c1d5
authored
Dec 16, 2025
by
Liu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix:调用自动提问接口和重新分析时不展示点赞点踩复制按钮
parent
48808e81
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
79 additions
and
38 deletions
+79
-38
src/pages/Chat/components/ChatItem/ChatAnswerBox.tsx
+16
-2
src/pages/Chat/components/ChatItem/ChatAnswerOperate.tsx
+26
-17
src/pages/Chat/components/ChatItem/ChatAnswerParser.tsx
+8
-4
src/pages/Chat/components/ChatItem/ChatAnswerRecommend.tsx
+23
-13
src/pages/ChatTactics/TacticsChat.tsx
+6
-2
No files found.
src/pages/Chat/components/ChatItem/ChatAnswerBox.tsx
View file @
9d73c1d5
...
@@ -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
>
...
...
src/pages/Chat/components/ChatItem/ChatAnswerOperate.tsx
View file @
9d73c1d5
...
@@ -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"
>
...
...
src/pages/Chat/components/ChatItem/ChatAnswerParser.tsx
View file @
9d73c1d5
...
@@ -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
[
hideOperate
ByCard
,
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-'
)))
setHideOperate
ByCard
((
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
>
...
...
src/pages/Chat/components/ChatItem/ChatAnswerRecommend.tsx
View file @
9d73c1d5
...
@@ -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
(
...
...
src/pages/ChatTactics/TacticsChat.tsx
View file @
9d73c1d5
...
@@ -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
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment