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
dee25152
Commit
dee25152
authored
Dec 09, 2025
by
Liu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix:进入时调用提问接口&&重新分析&&清除记录
parent
8a2d3591
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
235 additions
and
15 deletions
+235
-15
src/pages/ChatTactics/Chat.module.less
+22
-0
src/pages/ChatTactics/Chat.module.less.d.ts
+4
-0
src/pages/ChatTactics/Chat.tsx
+208
-14
src/pages/ChatTactics/components/ChatWelcome/index.tsx
+1
-1
No files found.
src/pages/ChatTactics/Chat.module.less
View file @
dee25152
...
...
@@ -41,3 +41,25 @@
width: 100%;
padding-bottom: 32px;
}
.historyDivider {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
width: 100%;
color: #9ea3b1;
font-size: 12px;
line-height: 18px;
margin: 20px 0;
}
.historyDividerLine {
flex: 1;
height: 1px;
background: #e5e7ed;
}
.historyDividerText {
white-space: nowrap;
}
src/pages/ChatTactics/Chat.module.less.d.ts
View file @
dee25152
// This file is automatically generated.
// Please do not change this file!
interface
CssExports
{
[
key
:
string
]:
string
chatPage
:
string
content
:
string
historyDivider
:
string
historyDividerLine
:
string
historyDividerText
:
string
inter
:
string
scrollView
:
string
scrollable
:
string
...
...
src/pages/ChatTactics/Chat.tsx
View file @
dee25152
...
...
@@ -12,7 +12,7 @@ import { ChatEditor } from '@/components/ChatEditorTactics'
import
type
{
ChatRecord
}
from
'@/types/chat'
import
{
fetchUserQaRecordPage
}
from
'@/api/conversation'
import
{
fetchCheckTokenApi
,
fetchStreamResponse
}
from
'@/api/chat'
import
{
fetchEfficiencyQuestionList
,
fetchToolList
}
from
'@/api/home'
import
{
deleteUserConversation
,
fetchEfficiencyQuestionList
,
fetchToolList
}
from
'@/api/home'
// import { mockFetchToolList } from '@/api/mock/home'
import
{
clearCurrentToolId
,
clearShouldSendQuestion
,
fetchConversations
,
setCurrentToolId
}
from
'@/store/conversationSlice'
import
{
getUserRolesForApi
}
from
'@/lib/utils'
...
...
@@ -22,6 +22,17 @@ import ScrollBtoIcon from '@/assets/svg/scrollBto.svg?react'
import
{
setIsAsking
}
from
'@/store/chatSlice'
import
SdreamLoading
from
'@/components/SdreamLoading'
function
formatDateTime
(
date
:
Date
)
{
const
pad
=
(
num
:
number
)
=>
num
.
toString
().
padStart
(
2
,
'0'
)
const
year
=
date
.
getFullYear
()
const
month
=
pad
(
date
.
getMonth
()
+
1
)
const
day
=
pad
(
date
.
getDate
())
const
hours
=
pad
(
date
.
getHours
())
const
minutes
=
pad
(
date
.
getMinutes
())
const
seconds
=
pad
(
date
.
getSeconds
())
return
`
${
year
}
-
${
month
}
-
${
day
}
${
hours
}
:
${
minutes
}
:
${
seconds
}
`
}
export
const
Chat
:
React
.
FC
=
()
=>
{
const
{
id
}
=
useParams
<
{
id
:
string
}
>
()
const
location
=
useLocation
()
...
...
@@ -43,8 +54,32 @@ export const Chat: React.FC = () => {
fromState
:
toolIdFromState
,
final
:
initialToolId
,
})
// 从 URL 获取策略相关参数
const
taskId
=
searchParams
.
get
(
'taskId'
)
?
Number
(
searchParams
.
get
(
'taskId'
))
:
undefined
const
version
=
searchParams
.
get
(
'version'
)
?
Number
(
searchParams
.
get
(
'version'
))
:
undefined
const
pinBeginTime
=
searchParams
.
get
(
'pinBeginTime'
)
?
Number
(
searchParams
.
get
(
'pinBeginTime'
))
:
undefined
const
pinEndTime
=
searchParams
.
get
(
'pinEndTime'
)
?
Number
(
searchParams
.
get
(
'pinEndTime'
))
:
undefined
const
partOrAll
=
searchParams
.
get
(
'partOrAll'
)
||
undefined
const
channel
=
searchParams
.
get
(
'channel'
)
||
undefined
const
channelName
=
searchParams
.
get
(
'channelName'
)
||
undefined
// 调试日志:输出获取到的策略参数
// eslint-disable-next-line no-console
console
.
log
(
'[Chat] 策略参数:'
,
{
taskId
,
version
,
pinBeginTime
,
pinEndTime
,
partOrAll
,
channel
,
channelName
,
})
const
[
isLoading
,
setIsLoading
]
=
useState
(
false
)
const
[
allItems
,
setAllItems
]
=
useState
<
ChatRecord
[]
>
([])
const
[
hasHistoryRecord
,
setHasHistoryRecord
]
=
useState
(
false
)
const
[
historyItemsCount
,
setHistoryItemsCount
]
=
useState
(
0
)
const
[
historyTimestamp
,
setHistoryTimestamp
]
=
useState
(
''
)
const
dispatch
=
useAppDispatch
()
const
{
shouldSendQuestion
,
currentToolId
,
conversations
}
=
useAppSelector
((
state
:
RootState
)
=>
state
.
conversation
)
const
scrollableRef
=
useRef
<
HTMLDivElement
|
any
>
(
null
)
...
...
@@ -55,7 +90,11 @@ export const Chat: React.FC = () => {
const
[
currentToolName
,
setCurrentToolName
]
=
useState
<
string
|
undefined
>
(
undefined
)
// 使用 ref 保存从 location.state 传递的 toolId,避免被异步操作覆盖
const
toolIdFromStateRef
=
useRef
<
string
|
null
|
undefined
>
(
undefined
)
// 使用 ref 跟踪是否已经自动调用过submit接口,避免重复调用
const
hasAutoSubmittedRef
=
useRef
<
boolean
>
(
false
)
const
isFromTactics
=
searchParams
.
get
(
'from'
)
===
'tactics'
const
[
showClearConfirm
,
setShowClearConfirm
]
=
useState
(
false
)
const
[
isClearingHistory
,
setIsClearingHistory
]
=
useState
(
false
)
// 当外部系统直接以 /chat/:id 链接进入(没有 location.state,且 URL 中也没有 toolId)时,
// 视为一次新的会话入口:重置为通用模式,清除历史遗留的工具模式状态
...
...
@@ -169,9 +208,8 @@ export const Chat: React.FC = () => {
// 去除 [参考文档《任意内容》 《任意内容》...] 格式的内容
filteredAnswer
=
filteredAnswer
.
replace
(
/
\[
参考文档
(?:
《
[^
》
]
*》
\s
*
)
+
\]
/g
,
''
).
trim
()
newItems
[
lastIndex
]
=
{
const
updatedItem
:
ChatRecord
=
{
...
newItems
[
lastIndex
],
question
,
answerList
:
[
{
...
msg
.
content
.
data
,
...
...
@@ -180,6 +218,11 @@ export const Chat: React.FC = () => {
},
],
}
// 只有当question不为空时才设置question字段
if
(
question
)
{
updatedItem
.
question
=
question
}
newItems
[
lastIndex
]
=
updatedItem
}
return
newItems
})
...
...
@@ -219,9 +262,8 @@ export const Chat: React.FC = () => {
const
lastIndex
=
newItems
.
length
-
1
if
(
lastIndex
>=
0
)
{
// 创建最后一项的新对象,合并现有数据和新的 answer
newItems
[
lastIndex
]
=
{
const
updatedItem
:
ChatRecord
=
{
...
newItems
[
lastIndex
],
question
,
answerList
:
[
{
...
msg
.
content
.
data
,
...
...
@@ -232,16 +274,22 @@ export const Chat: React.FC = () => {
},
],
}
// 只有当question不为空时才设置question字段
if
(
question
)
{
updatedItem
.
question
=
question
}
newItems
[
lastIndex
]
=
updatedItem
}
return
newItems
})
}
/** 提交问题 */
const
handleSubmitQuestion
=
async
(
question
:
string
,
productCode
?:
string
,
toolId
?:
string
)
=>
{
const
handleSubmitQuestion
=
async
(
question
:
string
,
productCode
?:
string
,
toolId
?:
string
,
isAutoCall
?:
boolean
)
=>
{
const
resolvedToolId
=
toolId
??
currentToolId
??
undefined
const
busiType
=
'02'
const
recordType
=
'A02'
// 根据是否是自动调用设置不同的参数
const
busiType
=
isAutoCall
?
'01'
:
'02'
const
recordType
=
isAutoCall
?
'A02'
:
'A01'
// 停止之前的请求
if
(
abortControllerRef
.
current
)
{
abortControllerRef
.
current
.
abort
()
...
...
@@ -253,6 +301,8 @@ export const Chat: React.FC = () => {
// 检查token
await
fetchCheckTokenApi
()
// 如果是自动调用(question为空),只添加空的AI回答,不添加用户问题
if
(
question
)
{
// 一次性添加用户问题和空的AI回答
setAllItems
(
prevItems
=>
[
...
prevItems
,
...
...
@@ -265,6 +315,17 @@ export const Chat: React.FC = () => {
answerList
:
[{
answer
:
''
}],
}
as
ChatRecord
,
])
}
else
{
// 自动调用时,只添加空的AI回答
setAllItems
(
prevItems
=>
[
...
prevItems
,
{
role
:
'ai'
,
answerList
:
[{
answer
:
''
}],
}
as
ChatRecord
,
])
}
// 创建新的 AbortController
abortControllerRef
.
current
=
new
AbortController
()
...
...
@@ -283,9 +344,8 @@ export const Chat: React.FC = () => {
fetchUrl
=
proxy
+
fetchUrl
fetchStreamResponse
(
fetchUrl
,
{
// 构建请求参数,包含路由参数
const
requestParams
:
Record
<
string
,
any
>
=
{
question
,
conversationId
:
currentIdRef
.
current
,
stream
:
true
,
...
...
@@ -293,7 +353,34 @@ export const Chat: React.FC = () => {
toolId
:
resolvedToolId
,
busiType
,
recordType
,
},
}
// 添加路由参数(如果存在)
if
(
taskId
!==
undefined
)
{
requestParams
.
taskId
=
taskId
}
if
(
version
!==
undefined
)
{
requestParams
.
version
=
version
}
if
(
pinBeginTime
!==
undefined
)
{
requestParams
.
pinBeginTime
=
pinBeginTime
}
if
(
pinEndTime
!==
undefined
)
{
requestParams
.
pinEndTime
=
pinEndTime
}
if
(
partOrAll
!==
undefined
)
{
requestParams
.
partOrAll
=
partOrAll
}
if
(
channel
!==
undefined
)
{
requestParams
.
channel
=
channel
}
if
(
channelName
!==
undefined
)
{
requestParams
.
channelName
=
channelName
}
fetchStreamResponse
(
fetchUrl
,
requestParams
,
(
msg
)
=>
{
// 检查是否已被取消
if
(
abortControllerRef
.
current
?.
signal
.
aborted
)
{
...
...
@@ -332,6 +419,9 @@ export const Chat: React.FC = () => {
/** 获取qa记录 */
const
getUserQaRecordPage
=
useCallback
(
async
(
conversationId
:
string
)
=>
{
setIsLoading
(
true
)
setHasHistoryRecord
(
false
)
setHistoryItemsCount
(
0
)
setHistoryTimestamp
(
''
)
try
{
// 检测是否从收藏页返回
const
fromCollect
=
location
.
state
?.
fromCollect
...
...
@@ -339,6 +429,7 @@ export const Chat: React.FC = () => {
console
.
log
(
'[Chat] 开始获取历史记录:'
,
conversationId
)
const
res
=
await
fetchUserQaRecordPage
(
conversationId
)
const
qaRecords
=
res
.
data
||
[]
const
hasQaRecords
=
qaRecords
.
length
>
0
const
messages
=
[{
role
:
'system'
}
as
ChatRecord
,
...
processApiResponse
(
qaRecords
)]
// 处理历史记录中的参考文档标记
const
processedMessages
=
messages
.
map
((
item
)
=>
{
...
...
@@ -357,9 +448,13 @@ export const Chat: React.FC = () => {
return
item
})
setAllItems
(
processedMessages
)
setHasHistoryRecord
(
hasQaRecords
)
setHistoryItemsCount
(
hasQaRecords
?
processedMessages
.
length
:
0
)
if
(
hasQaRecords
)
{
setHistoryTimestamp
(
formatDateTime
(
new
Date
()))
}
// 优先从 qaRecords 中查找 toolId(这是实际使用的)
const
latestToolId
=
[...
qaRecords
].
reverse
().
find
(
item
=>
Boolean
(
item
.
toolId
))?.
toolId
?.
trim
?.()
const
hasQaRecords
=
qaRecords
.
length
>
0
// 如果 qaRecords 中没有 toolId,尝试从 conversations 中查找当前会话的 toolId(会话级别)
const
conversationToolId
=
latestToolId
||
(
conversations
.
find
(
conv
=>
conv
.
conversationId
===
conversationId
)?.
toolId
?.
trim
?.())
...
...
@@ -490,6 +585,7 @@ export const Chat: React.FC = () => {
currentIdRef
.
current
=
id
lastSentQuestionRef
.
current
=
''
// 重置标记
hasAutoSubmittedRef
.
current
=
false
// 重置自动提交标记
getUserQaRecordPage
(
id
)
}
},
[
id
])
...
...
@@ -512,6 +608,35 @@ export const Chat: React.FC = () => {
}
},
[
shouldSendQuestion
,
isLoading
,
currentToolId
])
// 页面加载时自动调用submit接口
useEffect
(()
=>
{
if
(
currentIdRef
.
current
&&
!
isLoading
&&
!
hasAutoSubmittedRef
.
current
&&
isFromTactics
)
{
hasAutoSubmittedRef
.
current
=
true
// 确保历史记录加载完成后再调用submit接口
setTimeout
(()
=>
{
handleSubmitQuestion
(
''
,
undefined
,
currentToolId
,
true
)
// 自动调用,传递 isAutoCall = true
},
100
)
}
},
[
isLoading
,
currentToolId
,
isFromTactics
])
// 监听“重新分析”事件,重新发起一次自动提问(仍使用路由参数)
useEffect
(()
=>
{
const
handleReAnalyze
=
()
=>
{
if
(
currentIdRef
.
current
&&
!
isLoading
)
{
handleSubmitQuestion
(
''
,
undefined
,
currentToolId
,
true
)
}
}
window
.
addEventListener
(
'tacticsReAnalyze'
,
handleReAnalyze
as
EventListener
)
return
()
=>
{
window
.
removeEventListener
(
'tacticsReAnalyze'
,
handleReAnalyze
as
EventListener
)
}
},
[
isLoading
,
currentToolId
])
// 根据 currentToolId 获取对应的 toolName
useEffect
(()
=>
{
const
getToolNameFromToolId
=
async
()
=>
{
...
...
@@ -565,11 +690,68 @@ export const Chat: React.FC = () => {
}
},
[
dispatch
])
const
handleConfirmClearHistory
=
useCallback
(
async
()
=>
{
if
(
!
id
)
return
try
{
setIsClearingHistory
(
true
)
await
deleteUserConversation
({
conversationIdList
:
[
id
]
})
// 通知其他监听方
window
.
dispatchEvent
(
new
CustomEvent
(
'tacticsClearHistoryConfirm'
))
// 本地同步清空
setAllItems
([{
role
:
'system'
}
as
ChatRecord
])
setHasHistoryRecord
(
false
)
setHistoryItemsCount
(
0
)
setHistoryTimestamp
(
''
)
}
catch
(
error
)
{
console
.
error
(
'清空历史记录失败:'
,
error
)
}
finally
{
setShowClearConfirm
(
false
)
setIsClearingHistory
(
false
)
}
},
[
id
])
// 监听“清除记录”事件,在顶部展示确认弹窗(无遮罩)
useEffect
(()
=>
{
const
handleShowClearConfirm
=
()
=>
setShowClearConfirm
(
true
)
window
.
addEventListener
(
'tacticsClearHistory'
,
handleShowClearConfirm
as
EventListener
)
return
()
=>
{
window
.
removeEventListener
(
'tacticsClearHistory'
,
handleShowClearConfirm
as
EventListener
)
}
},
[])
return
(
<
div
className=
{
styles
.
scrollView
}
>
<
div
className=
{
`${styles.chatPage} relative`
}
>
{
/* <ChatSlogan />
<ChatMaskBar /> */
}
{
showClearConfirm
&&
(
<
div
className=
"absolute left-1/2 top-[64px] z-[120] w-[340px] -translate-x-1/2"
>
<
div
className=
"rounded-[12px] border border-[#E5E6EB] bg-white shadow-md p-4"
>
<
div
className=
"text-[16px] font-medium text-[#1D2129] mb-2"
>
是否确定清空历史记录?
</
div
>
<
div
className=
"flex justify-end gap-3 mt-4"
>
<
Button
size=
"sm"
variant=
"bordered"
className=
"text-[#1D2129] border-[#E5E6EB]"
onPress=
{
()
=>
setShowClearConfirm
(
false
)
}
>
取消
</
Button
>
<
Button
size=
"sm"
color=
"primary"
isLoading=
{
isClearingHistory
}
onPress=
{
handleConfirmClearHistory
}
>
确定
</
Button
>
</
div
>
</
div
>
</
div
>
)
}
<
div
className=
{
`${styles.content}`
}
>
{
isLoading
&&
(
<
div
className=
"w-full h-full flex justify-center items-center"
>
...
...
@@ -593,10 +775,14 @@ export const Chat: React.FC = () => {
const
uniqueKey
=
recordId
?
`${record.role}-${recordId}`
:
`${record.role}-${record.question || record.answerList?.[0]?.answer || ''}-${index}`
const
shouldShowHistoryDivider
=
hasHistoryRecord
&&
historyItemsCount
>
0
&&
index
===
historyItemsCount
-
1
const
historyDividerText
=
`以上为历史分析数据 ${historyTimestamp || formatDateTime(new Date())}`
return
(
<
React
.
Fragment
key=
{
uniqueKey
}
>
<
div
className=
"w-full chatItem mx-auto"
key=
{
uniqueKey
}
>
{
record
.
role
===
'system'
&&
<
ChatWelcome
toolName=
{
currentToolName
}
/>
}
{
record
.
role
===
'user'
&&
<
ChatItemUser
record=
{
record
}
/>
}
...
...
@@ -610,6 +796,14 @@ export const Chat: React.FC = () => {
/>
)
}
</
div
>
{
shouldShowHistoryDivider
&&
(
<
div
className=
{
styles
.
historyDivider
}
>
<
div
className=
{
styles
.
historyDividerLine
}
/>
<
span
className=
{
styles
.
historyDividerText
}
>
{
historyDividerText
}
</
span
>
<
div
className=
{
styles
.
historyDividerLine
}
/>
</
div
>
)
}
</
React
.
Fragment
>
)
})
}
</
div
>
...
...
src/pages/ChatTactics/components/ChatWelcome/index.tsx
View file @
dee25152
...
...
@@ -22,7 +22,7 @@ export const ChatWelcome: React.FC<ChatWelcomeProps> = ({ toolName: _toolName })
return
'HI~我是您的提质增效助手,有什么可以帮您?'
}
return
'
您好,有什么我可以帮您的吗?
'
return
'
正在为您分析策略,请耐心等待一会儿哦~
'
}
return
(
...
...
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