Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
赵月辉
/
fastgpt-migrated
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
1e770088
authored
Mar 16, 2023
by
Archer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 增加聊天navbar
parent
7529f51e
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
341 additions
and
17 deletions
+341
-17
src/pages/api/chat/chatGpt.ts
+5
-3
src/pages/chat/components/SlideBar.tsx
+189
-8
src/pages/chat/index.tsx
+81
-6
src/store/chat.ts
+48
-0
src/styles/reset.scss
+12
-0
src/types/chat.d.ts
+6
-0
No files found.
src/pages/api/chat/chatGpt.ts
View file @
1e770088
...
...
@@ -59,6 +59,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// 获取 chatAPI
const
chatAPI
=
getOpenAIApi
(
userApiKey
);
let
startTime
=
Date
.
now
();
// 发出请求
const
chatResponse
=
await
chatAPI
.
createChatCompletion
(
{
...
...
@@ -69,14 +70,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
stream
:
true
},
{
timeout
:
2
0000
,
timeout
:
4
0000
,
responseType
:
'stream'
,
httpsAgent
}
);
console
.
log
(
formatPrompts
.
reduce
((
sum
,
item
)
=>
sum
+
item
.
content
.
length
,
0
),
'response success'
'response success'
,
`
${(
Date
.
now
()
-
startTime
)
/
1000
}
s`
,
formatPrompts
.
reduce
((
sum
,
item
)
=>
sum
+
item
.
content
.
length
,
0
)
);
// 创建响应流
...
...
src/pages/chat/components/SlideBar.tsx
View file @
1e770088
import
React
from
'react'
;
import
React
,
{
useState
,
useEffect
}
from
'react'
;
import
{
Box
,
Button
}
from
'@chakra-ui/react'
;
import
{
AddIcon
}
from
'@chakra-ui/icons'
;
import
{
AddIcon
,
ChatIcon
,
EditIcon
,
DeleteIcon
}
from
'@chakra-ui/icons'
;
import
{
Accordion
,
AccordionItem
,
AccordionButton
,
AccordionPanel
,
AccordionIcon
,
Flex
,
Input
,
IconButton
}
from
'@chakra-ui/react'
;
import
{
useUserStore
}
from
'@/store/user'
;
import
{
useChatStore
}
from
'@/store/chat'
;
import
{
useQuery
}
from
'@tanstack/react-query'
;
import
{
useRouter
}
from
'next/router'
;
import
{
useScreen
}
from
'@/hooks/useScreen'
;
const
SlideBar
=
({
name
,
windowId
,
chatId
,
resetChat
,
onClose
}:
{
resetChat
:
()
=>
void
;
name
?:
string
;
windowId
?:
string
;
chatId
:
string
;
onClose
:
()
=>
void
;
})
=>
{
const
router
=
useRouter
();
const
{
isPc
}
=
useScreen
();
const
{
myModels
,
getMyModels
}
=
useUserStore
();
const
{
chatHistory
,
removeChatHistoryByWindowId
,
generateChatWindow
,
updateChatHistory
}
=
useChatStore
();
const
{
isSuccess
}
=
useQuery
([
'init'
],
getMyModels
);
const
[
hasReady
,
setHasReady
]
=
useState
(
false
);
const
[
editHistoryId
,
setEditHistoryId
]
=
useState
<
string
>
();
useEffect
(()
=>
{
setHasReady
(
true
);
},
[]);
const
RenderHistory
=
()
=>
(
<>
{
chatHistory
.
map
((
item
)
=>
(
<
Flex
key=
{
item
.
windowId
}
alignItems=
{
'center'
}
p=
{
3
}
borderRadius=
{
'md'
}
mb=
{
2
}
cursor=
{
'pointer'
}
_hover=
{
{
backgroundColor
:
'rgba(255,255,255,0.1)'
}
}
fontSize=
{
'xs'
}
border=
{
'1px solid transparent'
}
{
...
(
item
.
chatId
===
chatId
&&
item
.
windowId
===
windowId
?
{
borderColor
:
'
rgba
(255,255,255,0
.
5)',
backgroundColor
:
'
rgba
(255,255,255,0
.
1)'
}
:
{})}
onClick=
{
()
=>
{
if
(
(
item
.
chatId
===
chatId
&&
item
.
windowId
===
windowId
)
||
editHistoryId
===
item
.
windowId
)
return
;
router
.
push
(
`/chat?chatId=${item.chatId}&windowId=${item.windowId}&timeStamp=${Date.now()}`
);
onClose
();
}
}
>
<
ChatIcon
mr=
{
2
}
/>
<
Box
flex=
{
'1 0 0'
}
w=
{
0
}
className=
"textEllipsis"
>
{
item
.
title
}
</
Box
>
{
/* <Input
flex={'1 0 0'}
w={0}
value={item.title}
variant={'unstyled'}
disabled={editHistoryId !== item.windowId}
opacity={'1 !important'}
cursor={`${editHistoryId !== item.windowId ? 'pointer' : 'text'} !important`}
onChange={(e) => {
updateChatHistory(item.windowId, e.target.value);
}}
/> */
}
<
Box
>
{
/* <IconButton
icon={<EditIcon />}
variant={'unstyled'}
aria-label={'edit'}
size={'xs'}
onClick={(e) => {
console.log(e);
setEditHistoryId(item.windowId);
}}
/> */
}
<
IconButton
icon=
{
<
DeleteIcon
/>
}
variant=
{
'unstyled'
}
aria
-
label=
{
'edit'
}
size=
{
'xs'
}
onClick=
{
(
e
)
=>
{
removeChatHistoryByWindowId
(
item
.
windowId
);
e
.
stopPropagation
();
}
}
/>
</
Box
>
</
Flex
>
))
}
</>
);
const
SlideBar
=
({
resetChat
}:
{
resetChat
:
()
=>
void
})
=>
{
return
(
<
Box
flex=
{
'0 0 250px
'
}
p=
{
3
}
backgroundColor=
{
'blackAlpha.800'
}
color=
{
'white'
}
>
<
Box
w=
{
'100%'
}
h=
{
'100%
'
}
p=
{
3
}
backgroundColor=
{
'blackAlpha.800'
}
color=
{
'white'
}
>
{
/* 新对话 */
}
<
Button
w=
{
'100%'
}
variant=
{
'white'
}
h=
{
'40px'
}
leftIcon=
{
<
AddIcon
/>
}
onClick=
{
resetChat
}
>
<
Button
w=
{
'100%'
}
variant=
{
'white'
}
h=
{
'40px'
}
mb=
{
4
}
leftIcon=
{
<
AddIcon
/>
}
onClick=
{
resetChat
}
>
新对话
</
Button
>
{
/* 我的模型 */
}
{
/* 历史记录 */
}
{
/* 我的模型 & 历史记录 折叠框*/
}
{
isSuccess
?
(
<
Accordion
defaultIndex=
{
[
0
]
}
allowToggle
>
<
AccordionItem
borderTop=
{
0
}
borderBottom=
{
0
}
>
<
AccordionButton
borderRadius=
{
'md'
}
pl=
{
1
}
>
<
Box
as=
"span"
flex=
"1"
textAlign=
"left"
>
历史记录
</
Box
>
<
AccordionIcon
/>
</
AccordionButton
>
<
AccordionPanel
pb=
{
0
}
px=
{
0
}
>
{
hasReady
&&
<
RenderHistory
/>
}
</
AccordionPanel
>
</
AccordionItem
>
<
AccordionItem
borderTop=
{
0
}
borderBottom=
{
0
}
>
<
AccordionButton
borderRadius=
{
'md'
}
pl=
{
1
}
>
<
Box
as=
"span"
flex=
"1"
textAlign=
"left"
>
其他模型
</
Box
>
<
AccordionIcon
/>
</
AccordionButton
>
<
AccordionPanel
pb=
{
4
}
px=
{
0
}
>
{
myModels
.
map
((
item
)
=>
(
<
Flex
key=
{
item
.
_id
}
alignItems=
{
'center'
}
p=
{
3
}
borderRadius=
{
'md'
}
mb=
{
2
}
cursor=
{
'pointer'
}
_hover=
{
{
backgroundColor
:
'rgba(255,255,255,0.1)'
}
}
fontSize=
{
'xs'
}
border=
{
'1px solid transparent'
}
{
...
(
item
.
name
===
name
?
{
borderColor
:
'
rgba
(255,255,255,0
.
5)',
backgroundColor
:
'
rgba
(255,255,255,0
.
1)'
}
:
{})}
onClick=
{
async
()
=>
{
if
(
item
.
name
===
name
)
return
;
router
.
push
(
`/chat?chatId=${await generateChatWindow(item._id)}&timeStamp=${Date.now()}`
);
onClose
();
}
}
>
<
ChatIcon
mr=
{
2
}
/>
<
Box
className=
{
'textEllipsis'
}
flex=
{
'1 0 0'
}
w=
{
0
}
>
{
item
.
name
}
</
Box
>
</
Flex
>
))
}
</
AccordionPanel
>
</
AccordionItem
>
</
Accordion
>
)
:
(
<
RenderHistory
/>
)
}
</
Box
>
);
};
...
...
src/pages/chat/index.tsx
View file @
1e770088
...
...
@@ -3,7 +3,17 @@ import { useRouter } from 'next/router';
import
Image
from
'next/image'
;
import
{
getInitChatSiteInfo
,
postGPT3SendPrompt
,
delLastMessage
,
postSaveChat
}
from
'@/api/chat'
;
import
{
ChatSiteItemType
,
ChatSiteType
}
from
'@/types/chat'
;
import
{
Textarea
,
Box
,
Flex
,
Button
}
from
'@chakra-ui/react'
;
import
{
Textarea
,
Box
,
Flex
,
Button
,
useDisclosure
,
Drawer
,
DrawerFooter
,
DrawerOverlay
,
DrawerContent
}
from
'@chakra-ui/react'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
Icon
from
'@/components/Icon'
;
import
{
useScreen
}
from
'@/hooks/useScreen'
;
...
...
@@ -11,6 +21,7 @@ import { useQuery } from '@tanstack/react-query';
import
{
OpenAiModelEnum
}
from
'@/constants/model'
;
import
dynamic
from
'next/dynamic'
;
import
{
useGlobalStore
}
from
'@/store/global'
;
import
{
useChatStore
}
from
'@/store/chat'
;
import
{
streamFetch
}
from
'@/api/fetch'
;
import
SlideBar
from
'./components/SlideBar'
;
...
...
@@ -36,10 +47,12 @@ const Chat = ({
const
[
chatSiteData
,
setChatSiteData
]
=
useState
<
ChatSiteType
>
();
// 聊天框整体数据
const
[
chatList
,
setChatList
]
=
useState
<
ChatSiteItemType
[]
>
([]);
// 对话内容
const
[
inputVal
,
setInputVal
]
=
useState
(
''
);
// 输入的内容
const
{
isOpen
:
isOpenSlider
,
onClose
:
onCloseSlider
,
onOpen
:
onOpenSlider
}
=
useDisclosure
();
const
isChatting
=
useMemo
(()
=>
chatList
[
chatList
.
length
-
1
]?.
status
===
'loading'
,
[
chatList
]);
const
lastWordHuman
=
useMemo
(()
=>
chatList
[
chatList
.
length
-
1
]?.
obj
===
'Human'
,
[
chatList
]);
const
{
setLoading
}
=
useGlobalStore
();
const
{
pushChatHistory
}
=
useChatStore
();
// 滚动到底部
const
scrollToBottom
=
useCallback
(()
=>
{
...
...
@@ -102,7 +115,8 @@ const Chat = ({
// 重载对话
const
resetChat
=
useCallback
(()
=>
{
router
.
push
(
`/chat?chatId=
${
chatId
}
&timeStamp=
${
Date
.
now
()}
`
);
},
[
chatId
,
router
]);
onCloseSlider
();
},
[
chatId
,
router
,
onCloseSlider
]);
// gpt3 方法
const
gpt3ChatPrompt
=
useCallback
(
...
...
@@ -242,6 +256,16 @@ const Chat = ({
if
(
typeof
fnMap
[
chatSiteData
.
chatModel
]
===
'function'
)
{
await
fnMap
[
chatSiteData
.
chatModel
](
requestPrompt
);
}
// 如果是 Human 第一次发送,插入历史记录
const
humanChat
=
newChatList
.
filter
((
item
)
=>
item
.
obj
===
'Human'
);
if
(
windowId
&&
humanChat
.
length
===
1
)
{
pushChatHistory
({
chatId
,
windowId
,
title
:
humanChat
[
0
].
value
});
}
}
catch
(
err
:
any
)
{
toast
({
title
:
typeof
err
===
'string'
?
err
:
err
?.
message
||
'聊天出错了~'
,
...
...
@@ -263,7 +287,10 @@ const Chat = ({
isChatting
,
resetInputVal
,
scrollToBottom
,
toast
toast
,
chatId
,
windowId
,
pushChatHistory
]);
// 重新编辑
...
...
@@ -279,9 +306,57 @@ const Chat = ({
},
[
chatList
,
resetInputVal
,
windowId
]);
return
(
<
Flex
h=
{
'100%'
}
>
<
SlideBar
resetChat=
{
resetChat
}
/>
<
Flex
flex=
{
1
}
h=
{
'100%'
}
flexDirection=
{
'column'
}
>
<
Flex
h=
{
'100%'
}
flexDirection=
{
media
(
'row'
,
'column'
)
}
>
{
isPc
?
(
<
Box
flex=
{
'0 0 250px'
}
w=
{
0
}
h=
{
'100%'
}
>
<
SlideBar
resetChat=
{
resetChat
}
name=
{
chatSiteData
?.
name
}
windowId=
{
windowId
}
chatId=
{
chatId
}
onClose=
{
onCloseSlider
}
/>
</
Box
>
)
:
(
<
Box
h=
{
'60px'
}
borderBottom=
{
'1px solid rgba(0,0,0,0.1)'
}
>
<
Flex
alignItems=
{
'center'
}
h=
{
'100%'
}
justifyContent=
{
'space-between'
}
backgroundColor=
{
'white'
}
position=
{
'relative'
}
px=
{
7
}
>
<
Box
onClick=
{
onOpenSlider
}
>
<
Icon
name=
"icon-caidan"
width=
{
20
}
height=
{
20
}
></
Icon
>
</
Box
>
<
Box
>
{
chatSiteData
?.
name
}
</
Box
>
</
Flex
>
<
Drawer
isOpen=
{
isOpenSlider
}
placement=
"left"
size=
{
'xs'
}
onClose=
{
onCloseSlider
}
>
<
DrawerOverlay
backgroundColor=
{
'rgba(255,255,255,0.5)'
}
/>
<
DrawerContent
maxWidth=
{
'250px'
}
>
<
SlideBar
resetChat=
{
resetChat
}
name=
{
chatSiteData
?.
name
}
windowId=
{
windowId
}
chatId=
{
chatId
}
onClose=
{
onCloseSlider
}
/>
<
DrawerFooter
px=
{
2
}
backgroundColor=
{
'blackAlpha.800'
}
>
<
Button
variant=
"white"
onClick=
{
onCloseSlider
}
>
Cancel
</
Button
>
</
DrawerFooter
>
</
DrawerContent
>
</
Drawer
>
</
Box
>
)
}
<
Flex
{
...
media
({
h
:
'100%',
w
:
0
},
{
h
:
0,
w
:
'100%'
})}
flex=
{
'1 0 0'
}
flexDirection=
{
'column'
}
>
{
/* 聊天内容 */
}
<
Box
ref=
{
ChatBox
}
flex=
{
'1 0 0'
}
h=
{
0
}
w=
{
'100%'
}
overflowY=
{
'auto'
}
>
{
chatList
.
map
((
item
,
index
)
=>
(
...
...
src/store/chat.ts
0 → 100644
View file @
1e770088
import
{
create
}
from
'zustand'
;
import
{
devtools
,
persist
}
from
'zustand/middleware'
;
import
{
immer
}
from
'zustand/middleware/immer'
;
import
type
{
HistoryItem
}
from
'@/types/chat'
;
import
{
getChatSiteId
}
from
'@/api/chat'
;
type
Props
=
{
chatHistory
:
HistoryItem
[];
pushChatHistory
:
(
e
:
HistoryItem
)
=>
void
;
updateChatHistory
:
(
windowId
:
string
,
title
:
string
)
=>
void
;
removeChatHistoryByWindowId
:
(
windowId
:
string
)
=>
void
;
generateChatWindow
:
(
modelId
:
string
)
=>
Promise
<
string
>
;
};
export
const
useChatStore
=
create
<
Props
>
()(
devtools
(
persist
(
immer
((
set
,
get
)
=>
({
chatHistory
:
[],
pushChatHistory
(
item
:
HistoryItem
)
{
set
((
state
)
=>
{
state
.
chatHistory
=
[
item
,
...
state
.
chatHistory
];
});
},
updateChatHistory
(
windowId
:
string
,
title
:
string
)
{
set
((
state
)
=>
{
state
.
chatHistory
=
state
.
chatHistory
.
map
((
item
)
=>
({
...
item
,
title
:
item
.
windowId
===
windowId
?
title
:
item
.
title
}));
});
},
removeChatHistoryByWindowId
(
windowId
:
string
)
{
set
((
state
)
=>
{
state
.
chatHistory
=
state
.
chatHistory
.
filter
((
item
)
=>
item
.
windowId
!==
windowId
);
});
},
generateChatWindow
(
modelId
:
string
)
{
return
getChatSiteId
(
modelId
);
}
})),
{
name
:
'chatHistory'
// serialize: JSON.stringify,
// deserialize: (data) => (data ? JSON.parse(data) : []),
}
)
)
);
src/styles/reset.scss
View file @
1e770088
...
...
@@ -59,3 +59,15 @@ svg {
height
:
2px
;
}
}
.textEllipsis
{
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
overflow
:
hidden
;
}
*
{
-moz-outline-style
:
none
;
-webkit-tap-highlight-color
:
rgba
(
0
,
0
,
0
,
0
);
-webkit-focus-ring-color
:
rgba
(
0
,
0
,
0
,
0
);
outline
:
none
;
}
src/types/chat.d.ts
View file @
1e770088
...
...
@@ -14,3 +14,9 @@ export type ChatItemType = {
export
type
ChatSiteItemType
=
{
status
:
'loading'
|
'finish'
;
}
&
ChatItemType
;
export
type
HistoryItem
=
{
chatId
:
string
;
windowId
:
string
;
title
:
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