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
752d01fe
Commit
752d01fe
authored
Aug 12, 2024
by
HoMeTown
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 提问
parent
1eb20b85
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
204 additions
and
283 deletions
+204
-283
src/layouts/Navbar/Navbar.tsx
+5
-1
src/pages/Chat/Chat.tsx
+104
-28
src/pages/Chat/components/ChatContent/index.tsx
+0
-61
src/pages/Chat/components/ChatItem/ChatAnswerBox.tsx
+37
-21
src/pages/Chat/components/ChatItem/ChatItem.tsx
+0
-20
src/pages/Chat/components/ChatItem/ChatItemStream.tsx
+0
-51
src/pages/Chat/components/ChatItem/ChatItemUser.tsx
+1
-1
src/pages/Chat/helper.ts
+25
-0
src/pages/Home/Home.tsx
+12
-1
src/pages/Home/components/WelcomeWord/WelcomeWord.tsx
+5
-1
src/store/chatSlice.ts
+0
-87
src/store/conversationSlice.ts
+10
-5
src/store/index.ts
+0
-2
src/types/chat.ts
+4
-4
src/types/conversation.ts
+1
-0
No files found.
src/layouts/Navbar/Navbar.tsx
View file @
752d01fe
...
...
@@ -23,7 +23,11 @@ const NavbarBase: React.FC<NavbarProps & WithAuthProps> = ({ isHistoryVisible, c
const
{
currentConversationId
,
shouldNavigateToNewConversation
}
=
useAppSelector
(
state
=>
state
.
conversation
)
const
handleCreateConversation
=
()
=>
{
dispatch
(
createConversation
({}))
dispatch
(
createConversation
({
conversationData
:
{},
shouldNavigate
:
true
,
shouldSendQuestion
:
''
,
}))
}
const
handleClick
=
(
type
:
string
|
undefined
)
=>
{
...
...
src/pages/Chat/Chat.tsx
View file @
752d01fe
import
React
from
'react'
import
{
useDispatch
}
from
'react-redux'
import
React
,
{
useCallback
,
useEffect
,
useState
}
from
'react'
import
{
useParams
}
from
'react-router-dom'
import
{
Spinner
}
from
'@nextui-org/react'
import
{
Virtuoso
}
from
'react-virtuoso'
import
{
motion
}
from
'framer-motion'
import
{
useDispatch
,
useSelector
}
from
'react-redux'
import
styles
from
'./Chat.module.less'
import
{
ChatSlogan
}
from
'./components/ChatSlogan'
import
{
ChatContent
}
from
'./components/ChatContent'
import
{
ChatMaskBar
}
from
'./components/ChatMaskBar'
import
{
processApiResponse
}
from
'./helper'
import
{
ChatWelcome
}
from
'./components/ChatWelcome'
import
{
ChatItemUser
}
from
'./components/ChatItem/ChatItemUser'
import
{
ChatAnswerBox
}
from
'./components/ChatItem/ChatAnswerBox'
import
{
RECOMMEND_QUESTIONS_OTHER
}
from
'@/config/recommendQuestion'
import
{
ChatEditor
}
from
'@/components/ChatEditor'
import
type
{
ChatRecord
}
from
'@/types/chat'
import
{
addRecord
,
setIsLoading
,
updateLastAnswer
}
from
'@/store/chatSlice
'
import
{
fetchUserQaRecordPage
}
from
'@/api/conversation
'
import
{
fetchCheckTokenApi
,
fetchStreamResponse
}
from
'@/api/chat'
import
{
clearShouldSendQuestion
}
from
'@/store/conversationSlice'
import
type
{
RootState
}
from
'@/store'
// 假设你的 store 文件导出了 RootState 类型
export
const
Chat
:
React
.
FC
=
()
=>
{
const
dispatch
=
useDispatch
()
const
{
id
}
=
useParams
<
{
id
:
string
}
>
()
const
[
isLoading
,
setIsLoading
]
=
useState
(
false
)
const
[
allItems
,
setAllItems
]
=
useState
<
ChatRecord
[]
>
([])
const
dispatch
=
useDispatch
()
const
shouldSendQuestion
=
useSelector
((
state
:
RootState
)
=>
state
.
conversation
.
shouldSendQuestion
)
const
handleQuestion
=
async
(
question
:
string
)
=>
{
// 添加用户问题
const
userRecord
:
ChatRecord
=
{
type
:
'question'
,
originalData
:
{
const
handleSubmitQuestion
=
useCallback
(
async
(
question
:
string
)
=>
{
// 添加用户提问的问题
setAllItems
(
prevItems
=>
[
...
prevItems
,
{
role
:
'user'
,
question
,
answerList
:
[],
},
}
dispatch
(
addRecord
(
userRecord
))
setIsLoading
(
true
)
// 添加空的AI回答
const
aiMessage
:
ChatRecord
=
{
type
:
'streamAnswer'
,
originalData
:
{
answerList
:
[{
answer
:
''
,
attachmentList
:
[]
}]
},
}
dispatch
(
addRecord
(
aiMessage
))
}
as
ChatRecord
,
])
// 检查token
await
fetchCheckTokenApi
()
// 添加一条空的ai问题
setAllItems
(
prevItems
=>
[
...
prevItems
,
{
role
:
'ai'
,
answerList
:
[{}],
}
as
ChatRecord
,
])
let
fetchUrl
=
`/conversation/api/conversation/mobile/v1/submit_question_stream`
const
proxy
=
import
.
meta
.
env
.
MODE
===
'dev'
?
'/api'
:
'/sdream-api'
fetchUrl
=
proxy
+
fetchUrl
...
...
@@ -50,21 +60,87 @@ export const Chat: React.FC = () => {
},
(
msg
)
=>
{
if
(
msg
.
type
===
'DATA'
)
{
dispatch
(
updateLastAnswer
(
msg
.
content
.
data
))
setAllItems
((
prevItems
)
=>
{
const
newItems
=
[...
prevItems
]
// 创建数组的浅拷贝
const
lastIndex
=
newItems
.
length
-
1
if
(
lastIndex
>=
0
)
{
// 创建最后一项的新对象,合并现有数据和新的 answer
newItems
[
lastIndex
]
=
{
...
newItems
[
lastIndex
],
...
msg
.
content
.
data
,
answer
:
(
newItems
[
lastIndex
].
answer
||
''
)
+
msg
.
content
.
data
.
answer
,
}
}
return
newItems
})
}
},
)
}
},
[])
const
getUserQaRecordPage
=
useCallback
(
async
(
conversationId
:
string
)
=>
{
setIsLoading
(
true
)
try
{
const
res
=
await
fetchUserQaRecordPage
(
conversationId
)
const
messages
=
[{
role
:
'system'
}
as
ChatRecord
,
...
processApiResponse
(
res
.
data
)]
setAllItems
(
messages
)
// 假设 API 返回的数据结构符合 ChatRecord[]
if
(
shouldSendQuestion
)
{
handleSubmitQuestion
(
shouldSendQuestion
)
dispatch
(
clearShouldSendQuestion
())
}
}
catch
(
error
)
{
console
.
error
(
'Failed to fetch chat records:'
,
error
)
// 可以在这里添加错误处理逻辑
}
finally
{
setIsLoading
(
false
)
}
},
[])
useEffect
(()
=>
{
if
(
id
)
{
getUserQaRecordPage
(
id
)
}
},
[
id
,
getUserQaRecordPage
])
return
(
<
div
className=
{
`${styles.chatPage} relative`
}
>
<
ChatSlogan
/>
<
ChatMaskBar
/>
<
div
className=
{
styles
.
content
}
>
<
ChatContent
/>
{
isLoading
&&
<
div
className=
"w-full h-full flex justify-center"
><
Spinner
/></
div
>
}
{
!
isLoading
&&
(
<
motion
.
div
initial=
{
{
opacity
:
0
,
y
:
-
50
}
}
animate=
{
{
opacity
:
1
,
y
:
0
}
}
transition=
{
{
duration
:
0.4
,
x
:
{
type
:
'spring'
,
stiffness
:
50
},
scale
:
{
type
:
'spring'
,
stiffness
:
50
},
opacity
:
{
duration
:
0.7
},
}
}
className=
"w-full h-full mx-auto"
>
<
Virtuoso
style=
{
{
height
:
'100%'
}
}
// 设置一个固定高度或使用动态高度
data=
{
allItems
}
itemContent=
{
(
index
,
record
)
=>
(
<
div
className=
"chatItem max-w-[1000px] mx-auto"
>
{
record
.
role
===
'system'
&&
<
ChatWelcome
/>
}
{
record
.
role
===
'user'
&&
<
ChatItemUser
record=
{
record
}
/>
}
{
record
.
role
===
'ai'
&&
<
ChatAnswerBox
isLastAnswer=
{
index
===
allItems
.
length
-
1
}
showIndex=
{
0
}
record=
{
record
}
index=
{
index
}
/>
}
</
div
>
)
}
initialTopMostItemIndex=
{
allItems
.
length
-
1
}
// 初始滚动到底部
followOutput=
"smooth"
// 新消息时平滑滚动到底部
/>
</
motion
.
div
>
)
}
</
div
>
<
div
className=
"box-border px-[0] mx-auto iptContainer w-full max-w-[1000px] flex-shrink-0 sm:px-0 pb-[18px]"
>
<
ChatEditor
onSubmit=
{
handleQuestion
}
placeholders=
{
RECOMMEND_QUESTIONS_OTHER
}
/>
<
ChatEditor
onSubmit=
{
handle
Submit
Question
}
placeholders=
{
RECOMMEND_QUESTIONS_OTHER
}
/>
<
div
className=
"w-full text-center mt-[20px] text-[#3333334d] text-[12px]"
>
内容由AI模型生成,其准确性和完整性无法保证,仅供参考
</
div
>
...
...
src/pages/Chat/components/ChatContent/index.tsx
deleted
100644 → 0
View file @
1eb20b85
import
{
useEffect
}
from
'react'
import
{
useParams
}
from
'react-router-dom'
import
{
Spinner
}
from
'@nextui-org/react'
import
{
Virtuoso
}
from
'react-virtuoso'
import
{
motion
}
from
'framer-motion'
import
{
useSelector
}
from
'react-redux'
import
{
ChatItem
}
from
'../ChatItem'
import
{
useAppDispatch
,
useAppSelector
}
from
'@/store/hook'
import
{
fetchChatRecords
}
from
'@/store/chatSlice'
import
type
{
ChatRecord
}
from
'@/types/chat'
import
type
{
RootState
}
from
'@/store'
export
const
ChatContent
:
React
.
FC
=
()
=>
{
const
{
id
}
=
useParams
<
{
id
:
string
}
>
()
const
dispatch
=
useAppDispatch
()
const
{
isLoading
,
error
}
=
useAppSelector
(
state
=>
state
.
chat
)
const
records
=
useSelector
((
state
:
RootState
)
=>
state
.
chat
.
records
)
const
allItems
:
ChatRecord
[]
=
[{
type
:
'system'
},
...
records
]
useEffect
(()
=>
{
if
(
id
)
{
dispatch
(
fetchChatRecords
(
id
))
}
},
[
id
,
dispatch
])
if
(
isLoading
)
return
<
div
className=
"w-full h-full flex justify-center"
><
Spinner
/></
div
>
if
(
error
)
{
return
(
<
div
>
Error:
{
error
}
</
div
>
)
}
return
(
<
motion
.
div
initial=
{
{
opacity
:
0
,
y
:
-
50
}
}
animate=
{
{
opacity
:
1
,
y
:
0
}
}
transition=
{
{
duration
:
0.4
,
x
:
{
type
:
'spring'
,
stiffness
:
50
},
scale
:
{
type
:
'spring'
,
stiffness
:
50
},
opacity
:
{
duration
:
0.7
},
}
}
className=
"w-full h-full mx-auto"
>
<
Virtuoso
style=
{
{
height
:
'100%'
}
}
// 设置一个固定高度或使用动态高度
data=
{
allItems
}
itemContent=
{
(
index
,
record
)
=>
(
<
ChatItem
record=
{
record
}
key=
{
record
.
originalData
?.
groupId
}
/>
)
}
initialTopMostItemIndex=
{
allItems
.
length
-
1
}
// 初始滚动到底部
followOutput=
"smooth"
// 新消息时平滑滚动到底部
/>
</
motion
.
div
>
)
}
src/pages/Chat/components/ChatItem/Chat
ItemBot
.tsx
→
src/pages/Chat/components/ChatItem/Chat
AnswerBox
.tsx
View file @
752d01fe
...
...
@@ -4,33 +4,49 @@ import ReactMarkdown from 'react-markdown'
import
rehypeRaw
from
'rehype-raw'
import
rehypeSanitize
from
'rehype-sanitize'
import
{
formatMarkdown
}
from
'./markdownFormatter'
import
AvatarBot
from
'@/assets/avatarBot.png'
import
type
{
ChatRecord
}
from
'@/types/chat'
import
AvatarBot
from
'@/assets/avatarBot.png'
interface
Chat
ItemBot
Props
{
interface
Chat
AnswerBox
Props
{
record
:
ChatRecord
showIndex
:
number
isLastAnswer
:
boolean
index
:
number
}
export
const
Chat
ItemBot
:
React
.
FC
<
ChatItemBotProps
>
=
({
record
})
=>
{
export
const
Chat
AnswerBox
:
React
.
FC
<
ChatAnswerBoxProps
>
=
({
record
,
showIndex
})
=>
{
return
(
<
div
className=
"chatItemBotContainer w-full"
>
<
div
className=
"flex"
>
<
Avatar
className=
"flex-shrink-0"
src=
{
AvatarBot
}
/>
<
motion
.
div
className=
"ml-[20px] bg-white rounded-[20px] box-border px-[24px] py-[20px]"
>
<
div
className=
"content"
>
<
ReactMarkdown
rehypePlugins=
{
[
rehypeRaw
,
rehypeSanitize
]
}
className=
"markdown-content"
>
{
formatMarkdown
(
record
.
originalData
?.
answerList
[
0
].
answer
||
''
)
}
</
ReactMarkdown
>
</
div
>
</
motion
.
div
>
<
div
className=
"w-[130px]"
></
div
>
</
div
>
<
div
className=
"h-[32px] w-full"
></
div
>
<
div
>
{
record
.
answerList
.
map
((
item
,
index
)
=>
{
return
(
index
===
showIndex
&&
(
<
div
className=
"chatItemBotContainer w-full"
key=
{
`${item.recordId}-${index}`
}
>
<
div
className=
"flex"
>
<
Avatar
className=
"flex-shrink-0"
src=
{
AvatarBot
}
/>
<
motion
.
div
className=
"ml-[20px] bg-white rounded-[20px] box-border px-[24px] py-[20px]"
>
{
item
.
answer
&&
(
<
div
className=
"content"
>
<
ReactMarkdown
rehypePlugins=
{
[
rehypeRaw
,
rehypeSanitize
]
}
className=
"markdown-content"
>
{
formatMarkdown
(
item
.
answer
||
''
)
}
</
ReactMarkdown
>
</
div
>
)
}
{
!
item
.
answer
&&
(
<
span
>
loading
</
span
>
)
}
</
motion
.
div
>
<
div
className=
"w-[130px]"
></
div
>
</
div
>
<
div
className=
"h-[32px] w-full"
></
div
>
</
div
>
)
)
})
}
</
div
>
)
}
src/pages/Chat/components/ChatItem/ChatItem.tsx
deleted
100644 → 0
View file @
1eb20b85
import
{
ChatWelcome
}
from
'../ChatWelcome'
import
{
ChatItemBot
}
from
'./ChatItemBot'
import
{
ChatItemStream
}
from
'./ChatItemStream'
import
{
ChatItemUser
}
from
'./ChatItemUser'
import
type
{
ChatRecord
}
from
'@/types/chat'
interface
ChatItemProps
{
record
:
ChatRecord
}
export
const
ChatItem
:
React
.
FC
<
ChatItemProps
>
=
({
record
})
=>
{
return
(
<
div
className=
"chatItem max-w-[1000px] mx-auto"
>
{
record
.
type
===
'system'
&&
<
ChatWelcome
/>
}
{
record
.
type
===
'question'
&&
<
ChatItemUser
record=
{
record
}
/>
}
{
record
.
type
===
'answer'
&&
<
ChatItemBot
record=
{
record
}
/>
}
{
record
.
type
===
'streamAnswer'
&&
<
ChatItemStream
record=
{
record
}
/>
}
</
div
>
)
}
src/pages/Chat/components/ChatItem/ChatItemStream.tsx
deleted
100644 → 0
View file @
1eb20b85
import
React
,
{
useEffect
,
useState
}
from
'react'
import
{
Avatar
}
from
'@nextui-org/react'
import
{
motion
}
from
'framer-motion'
import
ReactMarkdown
from
'react-markdown'
import
rehypeRaw
from
'rehype-raw'
import
rehypeSanitize
from
'rehype-sanitize'
import
{
formatMarkdown
}
from
'./markdownFormatter'
import
type
{
ChatRecord
}
from
'@/types/chat'
import
AvatarBot
from
'@/assets/avatarBot.png'
interface
ChatItemStreamProps
{
record
:
ChatRecord
}
export
const
ChatItemStream
:
React
.
FC
<
ChatItemStreamProps
>
=
({
record
})
=>
{
const
[
displayedContent
,
setDisplayedContent
]
=
useState
(
''
)
const
content
=
record
.
originalData
?.
answerList
?.[
0
]?.
answer
||
''
useEffect
(()
=>
{
let
i
=
0
const
timer
=
setInterval
(()
=>
{
setDisplayedContent
(
content
.
slice
(
0
,
i
))
i
++
if
(
i
>
content
.
length
)
{
clearInterval
(
timer
)
}
},
20
)
// 调整速度
return
()
=>
clearInterval
(
timer
)
},
[
content
])
return
(
<
div
className=
"chatItemBotContainer w-full"
>
<
div
className=
"flex"
>
<
Avatar
className=
"flex-shrink-0"
src=
{
AvatarBot
}
/>
<
motion
.
div
className=
"ml-[20px] bg-white rounded-[20px] box-border px-[24px] py-[20px]"
>
<
div
className=
"content"
>
<
ReactMarkdown
rehypePlugins=
{
[
rehypeRaw
,
rehypeSanitize
]
}
className=
"markdown-content"
>
{
formatMarkdown
(
displayedContent
)
}
</
ReactMarkdown
>
</
div
>
</
motion
.
div
>
<
div
className=
"w-[130px]"
></
div
>
</
div
>
<
div
className=
"h-[32px] w-full"
></
div
>
</
div
>
)
}
src/pages/Chat/components/ChatItem/ChatItemUser.tsx
View file @
752d01fe
...
...
@@ -10,7 +10,7 @@ export const ChatItemUser: React.FC<ChatItemUserProps> = ({ record }) => {
return
(
<
div
className=
"chatItemUser"
>
<
div
className=
"flex justify-end"
>
<
div
className=
"mr-[20px] bg-[#BFE9FE] rounded-[20px] box-border px-[24px] py-[20px] text-[#27353C]"
>
{
record
.
originalData
?.
question
}
</
div
>
<
div
className=
"mr-[20px] bg-[#BFE9FE] rounded-[20px] box-border px-[24px] py-[20px] text-[#27353C]"
>
{
record
.
question
}
</
div
>
<
Avatar
className=
"flex-shrink-0"
src=
{
AvatarUser
}
/>
</
div
>
<
div
className=
"h-[32px] w-full"
></
div
>
...
...
src/pages/Chat/helper.ts
0 → 100644
View file @
752d01fe
import
type
{
ChatRecord
,
OriginalRecord
}
from
'@/types/chat'
export
function
processApiResponse
(
data
:
OriginalRecord
[]):
ChatRecord
[]
{
const
chatRecord
:
ChatRecord
[]
=
[]
if
(
data
.
length
===
0
)
return
chatRecord
data
.
forEach
((
record
)
=>
{
chatRecord
.
push
({
role
:
'user'
,
...
record
,
})
if
(
record
.
answerList
&&
record
.
answerList
.
length
>
0
)
{
record
.
answerList
.
forEach
((
answer
)
=>
{
answer
.
isShow
=
true
})
chatRecord
.
push
({
role
:
'ai'
,
...
record
,
})
}
})
return
chatRecord
}
src/pages/Home/Home.tsx
View file @
752d01fe
...
...
@@ -10,6 +10,8 @@ import HomeIcon2 from '@/assets/homeIcon2.png'
import
{
GradientBackground
}
from
'@/components/GradientBackground'
import
{
ChatEditor
}
from
'@/components/ChatEditor'
import
{
RECOMMEND_QUESTIONS_OTHER
,
RECOMMEND_QUESTIONS_PRODUCT
}
from
'@/config/recommendQuestion'
import
{
createConversation
}
from
'@/store/conversationSlice'
import
{
useAppDispatch
}
from
'@/store/hook'
function
getAnimationProps
(
delay
:
number
)
{
return
{
...
...
@@ -38,6 +40,15 @@ function getAnimationProps(delay: number) {
}
export
const
Home
:
React
.
FC
=
()
=>
{
const
dispatch
=
useAppDispatch
()
const
handleCreateConversation
=
(
question
:
string
)
=>
{
dispatch
(
createConversation
({
conversationData
:
{},
shouldNavigate
:
true
,
shouldSendQuestion
:
question
,
}))
}
return
(
<
div
className=
{
styles
.
homePage
}
>
<
GradientBackground
/>
...
...
@@ -64,7 +75,7 @@ export const Home: React.FC = () => {
/>
</
div
>
<
div
className=
"box-border px-[0] mx-auto iptContainer w-full max-w-[1000px] flex-shrink-0 sm:px-0 pb-[18px]"
>
<
ChatEditor
placeholders=
{
RECOMMEND_QUESTIONS_OTHER
}
/>
<
ChatEditor
onSubmit=
{
handleCreateConversation
}
placeholders=
{
RECOMMEND_QUESTIONS_OTHER
}
/>
<
div
className=
"w-full text-center mt-[20px] text-[#3333334d] text-[12px]"
>
内容由AI模型生成,其准确性和完整性无法保证,仅供参考
</
div
>
...
...
src/pages/Home/components/WelcomeWord/WelcomeWord.tsx
View file @
752d01fe
...
...
@@ -59,7 +59,11 @@ const WelcomeWordBase: React.FC<WithAuthProps> = ({ checkAuth }) => {
const
dispatch
=
useAppDispatch
()
const
handleCreateConversation
=
()
=>
{
dispatch
(
createConversation
({}))
dispatch
(
createConversation
({
conversationData
:
{},
shouldNavigate
:
true
,
shouldSendQuestion
:
''
,
}))
}
const
handleGo
=
()
=>
{
if
(
checkAuth
())
{
...
...
src/store/chatSlice.ts
deleted
100644 → 0
View file @
1eb20b85
import
type
{
PayloadAction
}
from
'@reduxjs/toolkit'
import
{
createAsyncThunk
,
createSlice
}
from
'@reduxjs/toolkit'
import
{
fetchUserQaRecordPage
}
from
'@/api/conversation'
import
type
{
ChatRecord
,
ChatState
,
OriginalRecord
}
from
'@/types/chat'
const
initialState
:
ChatState
=
{
records
:
[],
isLoading
:
false
,
error
:
null
,
}
function
processApiResponse
(
data
:
OriginalRecord
[]):
ChatRecord
[]
{
const
chatRecord
:
ChatRecord
[]
=
[]
if
(
data
.
length
===
0
)
return
chatRecord
data
.
forEach
((
record
)
=>
{
chatRecord
.
push
({
type
:
'question'
,
originalData
:
record
,
})
if
(
record
.
answerList
&&
record
.
answerList
.
length
>
0
)
{
chatRecord
.
push
({
type
:
'answer'
,
originalData
:
record
,
})
}
})
return
chatRecord
}
export
const
fetchChatRecords
=
createAsyncThunk
(
'chat/fetchRecords'
,
async
(
conversationId
:
string
)
=>
{
const
response
=
await
fetchUserQaRecordPage
(
conversationId
)
return
processApiResponse
(
response
.
data
)
},
)
const
chatSlice
=
createSlice
({
name
:
'chat'
,
initialState
,
reducers
:
{
addRecord
:
(
state
,
action
:
PayloadAction
<
ChatRecord
>
)
=>
{
state
.
records
.
push
(
action
.
payload
)
},
updateLastAnswer
:
(
state
,
action
:
PayloadAction
<
OriginalRecord
>
)
=>
{
const
lastIndex
=
state
.
records
.
length
-
1
if
(
lastIndex
>=
0
&&
state
.
records
[
lastIndex
].
type
===
'streamAnswer'
)
{
state
.
records
[
lastIndex
]
=
{
...
state
.
records
[
lastIndex
],
originalData
:
{
...
state
.
records
[
lastIndex
].
originalData
,
answerList
:
[
{
...
state
.
records
[
lastIndex
].
originalData
.
answerList
[
0
],
answer
:
state
.
records
[
lastIndex
].
originalData
.
answerList
[
0
].
answer
+
action
.
payload
.
answer
,
},
],
},
}
}
},
setIsLoading
:
(
state
,
action
:
PayloadAction
<
boolean
>
)
=>
{
state
.
isLoading
=
action
.
payload
},
},
extraReducers
:
(
builder
)
=>
{
builder
.
addCase
(
fetchChatRecords
.
pending
,
(
state
)
=>
{
state
.
isLoading
=
true
state
.
error
=
null
})
.
addCase
(
fetchChatRecords
.
fulfilled
,
(
state
,
action
)
=>
{
state
.
isLoading
=
false
state
.
records
=
action
.
payload
})
.
addCase
(
fetchChatRecords
.
rejected
,
(
state
,
action
)
=>
{
state
.
isLoading
=
false
state
.
error
=
action
.
error
.
message
||
'Failed to fetch chat records'
})
},
})
export
const
{
addRecord
,
setIsLoading
,
updateLastAnswer
}
=
chatSlice
.
actions
export
default
chatSlice
.
reducer
src/store/conversationSlice.ts
View file @
752d01fe
...
...
@@ -10,6 +10,7 @@ const initialState: ConversationState = {
isLoading
:
false
,
error
:
null
,
shouldNavigateToNewConversation
:
false
,
shouldSendQuestion
:
''
,
}
export
const
fetchConversations
=
createAsyncThunk
(
...
...
@@ -32,18 +33,18 @@ export const fetchConversations = createAsyncThunk(
)
export
const
createConversation
=
createAsyncThunk
<
{
conversation
:
Conversation
,
shouldNavigate
:
boolean
},
Partial
<
Conversation
>
,
{
conversation
:
Conversation
,
shouldNavigate
:
boolean
,
shouldSendQuestion
:
string
},
{
conversationData
:
Partial
<
Conversation
>
,
shouldNavigate
:
boolean
,
shouldSendQuestion
:
string
}
,
{
state
:
{
conversation
:
ConversationState
}
}
>
(
'conversation/createConversation'
,
async
(
conversationData
,
{
dispatch
})
=>
{
async
(
{
conversationData
,
shouldNavigate
,
shouldSendQuestion
}
,
{
dispatch
})
=>
{
const
response
=
await
fetchCreateConversation
(
conversationData
)
const
newConversation
=
response
.
data
dispatch
(
fetchConversations
())
return
{
conversation
:
newConversation
,
shouldNavigate
:
true
}
return
{
conversation
:
newConversation
,
shouldNavigate
,
shouldSendQuestion
}
},
)
...
...
@@ -57,6 +58,9 @@ const conversationSlice = createSlice({
clearCurrentConversation
:
(
state
)
=>
{
state
.
currentConversationId
=
null
},
clearShouldSendQuestion
:
(
state
)
=>
{
state
.
shouldSendQuestion
=
''
},
addConversation
:
(
state
,
action
:
PayloadAction
<
Conversation
>
)
=>
{
state
.
conversations
.
unshift
(
action
.
payload
)
},
...
...
@@ -92,6 +96,7 @@ const conversationSlice = createSlice({
state
.
isLoading
=
false
state
.
currentConversationId
=
action
.
payload
.
conversation
.
conversationId
state
.
shouldNavigateToNewConversation
=
action
.
payload
.
shouldNavigate
state
.
shouldSendQuestion
=
action
.
payload
.
shouldSendQuestion
})
.
addCase
(
createConversation
.
rejected
,
(
state
,
action
)
=>
{
state
.
isLoading
=
false
...
...
@@ -100,6 +105,6 @@ const conversationSlice = createSlice({
},
})
export
const
{
setCurrentConversation
,
clearCurrentConversation
,
clearNavigationFlag
}
=
conversationSlice
.
actions
export
const
{
setCurrentConversation
,
clearCurrentConversation
,
clearNavigationFlag
,
clearShouldSendQuestion
}
=
conversationSlice
.
actions
export
default
conversationSlice
.
reducer
src/store/index.ts
View file @
752d01fe
import
{
configureStore
}
from
'@reduxjs/toolkit'
import
conversationReducer
from
'./conversationSlice'
import
chatReducer
from
'./chatSlice'
export
const
store
=
configureStore
({
reducer
:
{
conversation
:
conversationReducer
,
chat
:
chatReducer
,
},
})
// 为了在TypeScript中使用,我们导出这些类型
...
...
src/types/chat.ts
View file @
752d01fe
...
...
@@ -5,6 +5,7 @@ export interface Attachment {
}
export
interface
Answer
{
isShow
:
boolean
answer
:
string
collectionFlag
?:
boolean
feedbackStatus
?:
string
...
...
@@ -25,11 +26,10 @@ export interface OriginalRecord {
qaTime
?:
string
}
export
type
ChatRecord
Type
=
'system'
|
'question'
|
'answer'
|
'streamAnswer
'
export
type
ChatRecord
Role
=
'system'
|
'user'
|
'ai
'
export
interface
ChatRecord
{
type
:
ChatRecordType
originalData
:
OriginalRecord
export
interface
ChatRecord
extends
OriginalRecord
{
role
:
ChatRecordRole
}
export
interface
ChatState
{
...
...
src/types/conversation.ts
View file @
752d01fe
...
...
@@ -13,4 +13,5 @@ export interface ConversationState {
isLoading
:
boolean
shouldNavigateToNewConversation
:
boolean
error
:
string
|
null
shouldSendQuestion
:
string
}
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