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
e6941309
Commit
e6941309
authored
Aug 09, 2024
by
HoMeTown
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 历史聊天记录
parent
bd13d886
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
116 additions
and
58 deletions
+116
-58
src/layouts/HistoryBar/motionAnimate.ts
+1
-2
src/pages/Chat/Chat.tsx
+3
-1
src/pages/Chat/components/ChatContent/index.tsx
+27
-12
src/pages/Chat/components/ChatItem/ChatItem.tsx
+11
-5
src/pages/Chat/components/ChatItem/ChatItemBot.tsx
+4
-2
src/pages/Chat/components/ChatItem/ChatItemUser.tsx
+6
-3
src/pages/Chat/components/ChatMaskBar/index.tsx
+5
-0
src/pages/Chat/components/ChatWelcome/index.tsx
+2
-0
src/store/chatSlice.ts
+21
-33
src/types/chat.ts
+36
-0
No files found.
src/layouts/HistoryBar/motionAnimate.ts
View file @
e6941309
...
@@ -19,7 +19,6 @@ export const item = {
...
@@ -19,7 +19,6 @@ export const item = {
}
}
export
const
variants
=
{
export
const
variants
=
{
hidden
:
{
hidden
:
{
opacity
:
0
,
scale
:
0
,
scale
:
0
,
x
:
'-50%'
,
// 起始位置向左偏移
x
:
'-50%'
,
// 起始位置向左偏移
},
},
...
@@ -39,7 +38,7 @@ export const variants = {
...
@@ -39,7 +38,7 @@ export const variants = {
x
:
'-50%'
,
x
:
'-50%'
,
transition
:
{
transition
:
{
duration
:
0.2
,
duration
:
0.2
,
ease
:
'ease
In'
,
ease
:
'ease
Out'
,
// 改为 easeOut
},
},
},
},
}
}
...
...
src/pages/Chat/Chat.tsx
View file @
e6941309
...
@@ -2,13 +2,15 @@ import React from 'react'
...
@@ -2,13 +2,15 @@ import React from 'react'
import
styles
from
'./Chat.module.less'
import
styles
from
'./Chat.module.less'
import
{
ChatSlogan
}
from
'./components/ChatSlogan'
import
{
ChatSlogan
}
from
'./components/ChatSlogan'
import
{
ChatContent
}
from
'./components/ChatContent'
import
{
ChatContent
}
from
'./components/ChatContent'
import
{
ChatMaskBar
}
from
'./components/ChatMaskBar'
import
{
RECOMMEND_QUESTIONS_OTHER
}
from
'@/config/recommendQuestion'
import
{
RECOMMEND_QUESTIONS_OTHER
}
from
'@/config/recommendQuestion'
import
{
ChatEditor
}
from
'@/components/ChatEditor'
import
{
ChatEditor
}
from
'@/components/ChatEditor'
export
const
Chat
:
React
.
FC
=
()
=>
{
export
const
Chat
:
React
.
FC
=
()
=>
{
return
(
return
(
<
div
className=
{
styles
.
chatPage
}
>
<
div
className=
{
`${styles.chatPage} relative`
}
>
<
ChatSlogan
/>
<
ChatSlogan
/>
<
ChatMaskBar
/>
<
div
className=
{
styles
.
content
}
>
<
div
className=
{
styles
.
content
}
>
<
ChatContent
/>
<
ChatContent
/>
</
div
>
</
div
>
...
...
src/pages/Chat/components/ChatContent/index.tsx
View file @
e6941309
import
{
useEffect
}
from
'react'
import
{
useEffect
}
from
'react'
import
{
useParams
}
from
'react-router-dom'
import
{
useParams
}
from
'react-router-dom'
import
{
Spinner
}
from
'@nextui-org/react'
import
{
Spinner
}
from
'@nextui-org/react'
import
{
ChatWelcome
}
from
'../ChatWelcome'
import
{
Virtuoso
}
from
'react-virtuoso'
import
{
motion
}
from
'framer-motion'
import
{
ChatItem
}
from
'../ChatItem'
import
{
ChatItem
}
from
'../ChatItem'
import
{
useAppDispatch
,
useAppSelector
}
from
'@/store/hook'
import
{
useAppDispatch
,
useAppSelector
}
from
'@/store/hook'
import
{
fetchChatRecords
}
from
'@/store/chatSlice'
import
{
fetchChatRecords
}
from
'@/store/chatSlice'
import
type
{
ChatRecord
}
from
'@/types/chat'
export
const
ChatContent
:
React
.
FC
=
()
=>
{
export
const
ChatContent
:
React
.
FC
=
()
=>
{
const
{
id
}
=
useParams
<
{
id
:
string
}
>
()
const
{
id
}
=
useParams
<
{
id
:
string
}
>
()
const
dispatch
=
useAppDispatch
()
const
dispatch
=
useAppDispatch
()
const
{
records
,
isLoading
,
error
}
=
useAppSelector
(
state
=>
state
.
chat
)
const
{
records
,
isLoading
,
error
}
=
useAppSelector
(
state
=>
state
.
chat
)
const
allItems
:
ChatRecord
[]
=
[{
type
:
'system'
},
...
records
]
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
id
)
{
if
(
id
)
{
dispatch
(
fetchChatRecords
(
id
))
dispatch
(
fetchChatRecords
(
id
))
...
@@ -18,7 +22,7 @@ export const ChatContent: React.FC = () => {
...
@@ -18,7 +22,7 @@ export const ChatContent: React.FC = () => {
},
[
id
,
dispatch
])
},
[
id
,
dispatch
])
if
(
isLoading
)
if
(
isLoading
)
return
<
div
className=
"w-full flex justify-center"
><
Spinner
/></
div
>
return
<
div
className=
"w-full
h-full
flex justify-center"
><
Spinner
/></
div
>
if
(
error
)
{
if
(
error
)
{
return
(
return
(
<
div
>
<
div
>
...
@@ -28,15 +32,26 @@ export const ChatContent: React.FC = () => {
...
@@ -28,15 +32,26 @@ export const ChatContent: React.FC = () => {
)
)
}
}
return
(
return
(
<
div
className=
"max-w-[1000px] mx-auto box-border py-[32px] flex flex-col gap-[32px]"
>
<
motion
.
div
<
ChatWelcome
/>
initial=
{
{
opacity
:
0
,
y
:
-
50
}
}
{
animate=
{
{
opacity
:
1
,
y
:
0
}
}
records
.
map
((
record
)
=>
{
transition=
{
{
return
(
duration
:
0.4
,
<
ChatItem
record=
{
record
}
key=
{
record
.
groupId
}
/>
x
:
{
type
:
'spring'
,
stiffness
:
50
},
)
scale
:
{
type
:
'spring'
,
stiffness
:
50
},
})
opacity
:
{
duration
:
0.7
},
}
}
}
</
div
>
className=
"w-full h-full mx-auto"
>
<
Virtuoso
style=
{
{
height
:
'100%'
}
}
// 设置一个固定高度或使用动态高度
data=
{
allItems
}
itemContent=
{
(
index
,
record
)
=>
(
<
ChatItem
record=
{
record
}
key=
{
record
.
originalData
?.
groupId
}
/>
)
}
initialTopMostItemIndex=
{
records
.
length
-
1
}
// 初始滚动到底部
followOutput=
"smooth"
// 新消息时平滑滚动到底部
/>
</
motion
.
div
>
)
)
}
}
src/pages/Chat/components/ChatItem/ChatItem.tsx
View file @
e6941309
import
{
ChatWelcome
}
from
'../ChatWelcome'
import
{
ChatItemBot
}
from
'./ChatItemBot'
import
{
ChatItemBot
}
from
'./ChatItemBot'
import
{
ChatItemUser
}
from
'./ChatItemUser'
import
{
ChatItemUser
}
from
'./ChatItemUser'
import
type
{
ChatRecord
}
from
'@/
store/chatSlice
'
import
type
{
ChatRecord
}
from
'@/
types/chat
'
interface
ChatItemProps
{
interface
ChatItemProps
{
record
:
ChatRecord
record
:
ChatRecord
...
@@ -8,9 +9,14 @@ interface ChatItemProps {
...
@@ -8,9 +9,14 @@ interface ChatItemProps {
export
const
ChatItem
:
React
.
FC
<
ChatItemProps
>
=
({
record
})
=>
{
export
const
ChatItem
:
React
.
FC
<
ChatItemProps
>
=
({
record
})
=>
{
return
(
return
(
<>
<
div
className=
"chatItem max-w-[1000px] mx-auto"
>
<
ChatItemUser
record=
{
record
}
/>
{
<
ChatItemBot
record=
{
record
}
/>
record
.
type
===
'system'
</>
?
<
ChatWelcome
/>
:
record
.
type
===
'question'
?
<
ChatItemUser
record=
{
record
}
/>
:
record
.
type
===
'answer'
?
<
ChatItemBot
record=
{
record
}
/>
:
null
}
</
div
>
)
)
}
}
src/pages/Chat/components/ChatItem/ChatItemBot.tsx
View file @
e6941309
...
@@ -5,7 +5,7 @@ import rehypeRaw from 'rehype-raw'
...
@@ -5,7 +5,7 @@ import rehypeRaw from 'rehype-raw'
import
rehypeSanitize
from
'rehype-sanitize'
import
rehypeSanitize
from
'rehype-sanitize'
import
{
formatMarkdown
}
from
'./markdownFormatter'
import
{
formatMarkdown
}
from
'./markdownFormatter'
import
AvatarBot
from
'@/assets/avatarBot.png'
import
AvatarBot
from
'@/assets/avatarBot.png'
import
type
{
ChatRecord
}
from
'@/
store/chatSlice
'
import
type
{
ChatRecord
}
from
'@/
types/chat
'
interface
ChatItemBotProps
{
interface
ChatItemBotProps
{
record
:
ChatRecord
record
:
ChatRecord
...
@@ -24,11 +24,13 @@ export const ChatItemBot: React.FC<ChatItemBotProps> = ({ record }) => {
...
@@ -24,11 +24,13 @@ export const ChatItemBot: React.FC<ChatItemBotProps> = ({ record }) => {
rehypePlugins=
{
[
rehypeRaw
,
rehypeSanitize
]
}
rehypePlugins=
{
[
rehypeRaw
,
rehypeSanitize
]
}
className=
"markdown-content"
className=
"markdown-content"
>
>
{
formatMarkdown
(
record
.
answerList
[
0
].
answer
)
}
{
formatMarkdown
(
record
.
originalData
?.
answerList
[
0
].
answer
||
''
)
}
</
ReactMarkdown
>
</
ReactMarkdown
>
</
div
>
</
div
>
</
motion
.
div
>
</
motion
.
div
>
<
div
className=
"w-[130px]"
></
div
>
</
div
>
</
div
>
<
div
className=
"h-[32px] w-full"
></
div
>
</
div
>
</
div
>
)
)
}
}
src/pages/Chat/components/ChatItem/ChatItemUser.tsx
View file @
e6941309
import
{
Avatar
}
from
'@nextui-org/react'
import
{
Avatar
}
from
'@nextui-org/react'
import
type
{
ChatRecord
}
from
'@/store/chatSlice'
import
AvatarUser
from
'@/assets/avatarUser.png'
import
AvatarUser
from
'@/assets/avatarUser.png'
import
type
{
ChatRecord
}
from
'@/types/chat'
interface
ChatItemUserProps
{
interface
ChatItemUserProps
{
record
:
ChatRecord
record
:
ChatRecord
...
@@ -8,9 +8,12 @@ interface ChatItemUserProps {
...
@@ -8,9 +8,12 @@ interface ChatItemUserProps {
export
const
ChatItemUser
:
React
.
FC
<
ChatItemUserProps
>
=
({
record
})
=>
{
export
const
ChatItemUser
:
React
.
FC
<
ChatItemUserProps
>
=
({
record
})
=>
{
return
(
return
(
<
div
className=
"chatItemUser flex justify-end"
>
<
div
className=
"chatItemUser"
>
<
div
className=
"mr-[20px] bg-[#BFE9FE] rounded-[20px] box-border px-[24px] py-[20px] text-[#27353C]"
>
{
record
.
question
}
</
div
>
<
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
>
<
Avatar
className=
"flex-shrink-0"
src=
{
AvatarUser
}
/>
<
Avatar
className=
"flex-shrink-0"
src=
{
AvatarUser
}
/>
</
div
>
</
div
>
<
div
className=
"h-[32px] w-full"
></
div
>
</
div
>
)
)
}
}
src/pages/Chat/components/ChatMaskBar/index.tsx
0 → 100644
View file @
e6941309
export
const
ChatMaskBar
:
React
.
FC
=
()
=>
{
return
(
<
div
className=
"absolute w-full h-[32px] z-[100] top-[110px] bg-gradient-to-b from-[hsl(var(--sdream-background))] to-transparent"
></
div
>
)
}
src/pages/Chat/components/ChatWelcome/index.tsx
View file @
e6941309
...
@@ -5,6 +5,7 @@ import AvatarBot from '@/assets/avatarBot.png'
...
@@ -5,6 +5,7 @@ import AvatarBot from '@/assets/avatarBot.png'
export
const
ChatWelcome
:
React
.
FC
=
()
=>
{
export
const
ChatWelcome
:
React
.
FC
=
()
=>
{
return
(
return
(
<
div
className=
"chatWelcomeContainer w-full"
>
<
div
className=
"chatWelcomeContainer w-full"
>
<
div
className=
"h-[32px] w-full"
></
div
>
<
div
className=
"flex"
>
<
div
className=
"flex"
>
<
Avatar
className=
"flex-shrink-0"
src=
{
AvatarBot
}
/>
<
Avatar
className=
"flex-shrink-0"
src=
{
AvatarBot
}
/>
<
motion
.
div
<
motion
.
div
...
@@ -16,6 +17,7 @@ export const ChatWelcome: React.FC = () => {
...
@@ -16,6 +17,7 @@ export const ChatWelcome: React.FC = () => {
</
div
>
</
div
>
</
motion
.
div
>
</
motion
.
div
>
</
div
>
</
div
>
<
div
className=
"h-[32px] w-full"
></
div
>
</
div
>
</
div
>
)
)
}
}
src/store/chatSlice.ts
View file @
e6941309
import
{
createAsyncThunk
,
createSlice
}
from
'@reduxjs/toolkit'
import
{
createAsyncThunk
,
createSlice
}
from
'@reduxjs/toolkit'
import
{
fetchUserQaRecordPage
}
from
'@/api/conversation'
import
{
fetchUserQaRecordPage
}
from
'@/api/conversation'
import
type
{
ChatRecord
,
ChatState
,
OriginalRecord
}
from
'@/types/chat'
interface
Attachment
{
type
:
string
name
:
string
description
:
string
}
interface
Answer
{
answer
:
string
collectionFlag
:
boolean
feedbackStatus
:
string
groupId
:
string
question
:
string
recordId
:
string
terminateFlag
:
boolean
toolName
:
string
attachmentList
:
Attachment
[]
}
export
interface
ChatRecord
{
groupId
:
string
question
:
string
answerList
:
Answer
[]
productCode
:
string
qaTime
:
string
}
interface
ChatState
{
records
:
ChatRecord
[]
isLoading
:
boolean
error
:
string
|
null
}
const
initialState
:
ChatState
=
{
const
initialState
:
ChatState
=
{
records
:
[],
records
:
[],
...
@@ -39,11 +8,30 @@ const initialState: ChatState = {
...
@@ -39,11 +8,30 @@ const initialState: ChatState = {
error
:
null
,
error
:
null
,
}
}
function
processApiResponse
(
data
:
OriginalRecord
[]):
ChatRecord
[]
{
const
chatRecord
:
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
(
export
const
fetchChatRecords
=
createAsyncThunk
(
'chat/fetchRecords'
,
'chat/fetchRecords'
,
async
(
conversationId
:
string
)
=>
{
async
(
conversationId
:
string
)
=>
{
const
response
=
await
fetchUserQaRecordPage
(
conversationId
)
const
response
=
await
fetchUserQaRecordPage
(
conversationId
)
return
response
.
data
return
processApiResponse
(
response
.
data
)
},
},
)
)
...
...
src/types/chat.ts
0 → 100644
View file @
e6941309
export
interface
Attachment
{
type
:
string
name
:
string
description
:
string
}
export
interface
Answer
{
answer
:
string
collectionFlag
:
boolean
feedbackStatus
:
string
groupId
:
string
question
:
string
recordId
:
string
terminateFlag
:
boolean
toolName
:
string
attachmentList
:
Attachment
[]
}
export
interface
OriginalRecord
{
groupId
:
string
question
:
string
answerList
:
Answer
[]
productCode
:
string
qaTime
:
string
}
export
interface
ChatRecord
{
type
:
'question'
|
'answer'
|
'system'
originalData
?:
OriginalRecord
}
export
interface
ChatState
{
records
:
ChatRecord
[]
isLoading
:
boolean
error
:
string
|
null
}
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