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
4e0c8761
authored
May 12, 2023
by
archer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
perf: chat ui
parent
3e5118c4
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
162 additions
and
107 deletions
+162
-107
public/docs/csvSelect.md
+5
-4
src/api/chat.ts
+1
-1
src/components/Layout/navbar.tsx
+6
-6
src/components/Layout/navbarPhone.tsx
+6
-6
src/pages/api/chat/saveChat.ts
+5
-4
src/pages/chat/components/PhoneSliderBar.tsx
+1
-1
src/pages/chat/index.tsx
+111
-83
src/pages/model/components/detail/components/ModelEditForm.tsx
+1
-1
src/pages/model/share/index.tsx
+1
-1
src/utils/tools.ts
+25
-0
No files found.
public/docs/csvSelect.md
View file @
4e0c8761
接受一个csv文件,表格头包含 question 和 answer。question 代表问题,answer 代表答案。
接受一个 csv 文件,表格头包含 question 和 answer。question 代表问题,answer 代表答案。
导入前会进行去重,如果问题和答案完全相同,则不会被导入,所以最终导入的内容可能会比文件的内容少。但是,对于带有换行的内容,目前无法去重。
导入前会进行去重,如果问题和答案完全相同,则不会被导入,所以最终导入的内容可能会比文件的内容少。但是,对于带有换行的内容,目前无法去重。
**请保证 csv 文件为 utf-8 编码**
| question | answer |
| question | answer |
| --- | --- |
| --- | --- |
| 什么是 laf
| laf 是一个云函数开发平台…… |
| 什么是 laf | laf 是一个云函数开发平台…… |
| 什么是 sealos | Sealos 是以 kubernetes 为内核的云操作系统发行版,可以…… |
| 什么是 sealos | Sealos 是以 kubernetes 为内核的云操作系统发行版,可以…… |
src/api/chat.ts
View file @
4e0c8761
...
@@ -27,7 +27,7 @@ export const postSaveChat = (data: {
...
@@ -27,7 +27,7 @@ export const postSaveChat = (data: {
modelId
:
string
;
modelId
:
string
;
newChatId
:
''
|
string
;
newChatId
:
''
|
string
;
chatId
:
''
|
string
;
chatId
:
''
|
string
;
prompts
:
ChatItemType
[
];
prompts
:
[
ChatItemType
,
ChatItemType
];
})
=>
POST
<
string
>
(
'/chat/saveChat'
,
data
);
})
=>
POST
<
string
>
(
'/chat/saveChat'
,
data
);
/**
/**
...
...
src/components/Layout/navbar.tsx
View file @
4e0c8761
...
@@ -17,12 +17,6 @@ const Navbar = () => {
...
@@ -17,12 +17,6 @@ const Navbar = () => {
const
navbarList
=
useMemo
(
const
navbarList
=
useMemo
(
()
=>
[
()
=>
[
{
{
label
:
'AI助手'
,
icon
:
'model'
,
link
:
`/model?modelId=
${
lastModelId
}
`
,
activeLink
:
[
'/model'
]
},
{
label
:
'聊天'
,
label
:
'聊天'
,
icon
:
'chat'
,
icon
:
'chat'
,
link
:
`/chat?modelId=
${
lastChatModelId
}
&chatId=
${
lastChatId
}
`
,
link
:
`/chat?modelId=
${
lastChatModelId
}
&chatId=
${
lastChatId
}
`
,
...
@@ -30,6 +24,12 @@ const Navbar = () => {
...
@@ -30,6 +24,12 @@ const Navbar = () => {
},
},
{
{
label
:
'AI助手'
,
icon
:
'model'
,
link
:
`/model?modelId=
${
lastModelId
}
`
,
activeLink
:
[
'/model'
]
},
{
label
:
'共享'
,
label
:
'共享'
,
icon
:
'shareMarket'
,
icon
:
'shareMarket'
,
link
:
'/model/share'
,
link
:
'/model/share'
,
...
...
src/components/Layout/navbarPhone.tsx
View file @
4e0c8761
...
@@ -10,18 +10,18 @@ const NavbarPhone = () => {
...
@@ -10,18 +10,18 @@ const NavbarPhone = () => {
const
navbarList
=
useMemo
(
const
navbarList
=
useMemo
(
()
=>
[
()
=>
[
{
{
label
:
'AI助手'
,
icon
:
'tabbarModel'
,
link
:
`/model`
,
activeLink
:
[
'/model'
]
},
{
label
:
'聊天'
,
label
:
'聊天'
,
icon
:
'tabbarChat'
,
icon
:
'tabbarChat'
,
link
:
`/chat?modelId=
${
lastChatModelId
}
&chatId=
${
lastChatId
}
`
,
link
:
`/chat?modelId=
${
lastChatModelId
}
&chatId=
${
lastChatId
}
`
,
activeLink
:
[
'/chat'
]
activeLink
:
[
'/chat'
]
},
},
{
{
label
:
'AI助手'
,
icon
:
'tabbarModel'
,
link
:
`/model`
,
activeLink
:
[
'/model'
]
},
{
label
:
'发现'
,
label
:
'发现'
,
icon
:
'tabbarMore'
,
icon
:
'tabbarMore'
,
link
:
'/tools'
,
link
:
'/tools'
,
...
...
src/pages/api/chat/saveChat.ts
View file @
4e0c8761
...
@@ -13,7 +13,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -13,7 +13,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
newChatId
:
''
|
string
;
newChatId
:
''
|
string
;
chatId
:
''
|
string
;
chatId
:
''
|
string
;
modelId
:
string
;
modelId
:
string
;
prompts
:
ChatItemType
[
];
prompts
:
[
ChatItemType
,
ChatItemType
];
};
};
if
(
!
prompts
)
{
if
(
!
prompts
)
{
...
@@ -41,7 +41,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -41,7 +41,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
modelId
,
modelId
,
content
,
content
,
title
:
content
[
0
].
value
.
slice
(
0
,
20
),
title
:
content
[
0
].
value
.
slice
(
0
,
20
),
latestChat
:
content
[
content
.
length
-
1
].
value
latestChat
:
content
[
1
].
value
});
});
return
jsonRes
(
res
,
{
return
jsonRes
(
res
,
{
data
:
_id
data
:
_id
...
@@ -54,8 +54,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
...
@@ -54,8 +54,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
$each
:
content
$each
:
content
}
}
},
},
updateTime
:
new
Date
(),
title
:
content
[
0
].
value
.
slice
(
0
,
20
),
latestChat
:
content
[
content
.
length
-
1
].
value
latestChat
:
content
[
1
].
value
,
updateTime
:
new
Date
()
});
});
}
}
jsonRes
(
res
);
jsonRes
(
res
);
...
...
src/pages/chat/components/PhoneSliderBar.tsx
View file @
4e0c8761
...
@@ -172,7 +172,7 @@ const PhoneSliderBar = ({
...
@@ -172,7 +172,7 @@ const PhoneSliderBar = ({
<
Divider
my=
{
3
}
colorScheme=
{
useColorModeValue
(
'gray'
,
'white'
)
}
/>
<
Divider
my=
{
3
}
colorScheme=
{
useColorModeValue
(
'gray'
,
'white'
)
}
/>
<
RenderButton
onClick=
{
()
=>
router
.
push
(
'/'
)
}
>
<
RenderButton
onClick=
{
()
=>
router
.
push
(
'/
model
'
)
}
>
<>
<>
<
MyIcon
name=
"out"
fill=
{
'white'
}
w=
{
'18px'
}
h=
{
'18px'
}
mr=
{
4
}
/>
<
MyIcon
name=
"out"
fill=
{
'white'
}
w=
{
'18px'
}
h=
{
'18px'
}
mr=
{
4
}
/>
退出聊天
退出聊天
...
...
src/pages/chat/index.tsx
View file @
4e0c8761
...
@@ -29,19 +29,21 @@ import {
...
@@ -29,19 +29,21 @@ import {
DrawerContent
,
DrawerContent
,
Card
,
Card
,
Tooltip
,
Tooltip
,
useOutsideClick
useOutsideClick
,
useTheme
}
from
'@chakra-ui/react'
;
}
from
'@chakra-ui/react'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
useScreen
}
from
'@/hooks/useScreen'
;
import
{
useScreen
}
from
'@/hooks/useScreen'
;
import
{
useQuery
}
from
'@tanstack/react-query'
;
import
{
useQuery
}
from
'@tanstack/react-query'
;
import
dynamic
from
'next/dynamic'
;
import
dynamic
from
'next/dynamic'
;
import
{
useCopyData
}
from
'@/utils/tools'
;
import
{
useCopyData
,
voiceBroadcast
}
from
'@/utils/tools'
;
import
{
streamFetch
}
from
'@/api/fetch'
;
import
{
streamFetch
}
from
'@/api/fetch'
;
import
MyIcon
from
'@/components/Icon'
;
import
MyIcon
from
'@/components/Icon'
;
import
{
throttle
}
from
'lodash'
;
import
{
throttle
}
from
'lodash'
;
import
{
Types
}
from
'mongoose'
;
import
{
Types
}
from
'mongoose'
;
import
Markdown
from
'@/components/Markdown'
;
import
Markdown
from
'@/components/Markdown'
;
import
{
LOGO_ICON
}
from
'@/constants/chat'
;
import
{
LOGO_ICON
}
from
'@/constants/chat'
;
import
{
ChatModelMap
}
from
'@/constants/model'
;
import
{
useChatStore
}
from
'@/store/chat'
;
import
{
useChatStore
}
from
'@/store/chat'
;
import
{
useLoading
}
from
'@/hooks/useLoading'
;
import
{
useLoading
}
from
'@/hooks/useLoading'
;
import
{
fileDownload
}
from
'@/utils/file'
;
import
{
fileDownload
}
from
'@/utils/file'
;
...
@@ -75,7 +77,9 @@ const Chat = ({
...
@@ -75,7 +77,9 @@ const Chat = ({
chatId
:
string
;
chatId
:
string
;
isPcDevice
:
boolean
;
isPcDevice
:
boolean
;
})
=>
{
})
=>
{
const
hasVoiceApi
=
!!
window
.
speechSynthesis
;
const
router
=
useRouter
();
const
router
=
useRouter
();
const
theme
=
useTheme
();
const
ChatBox
=
useRef
<
HTMLDivElement
>
(
null
);
const
ChatBox
=
useRef
<
HTMLDivElement
>
(
null
);
const
TextareaDom
=
useRef
<
HTMLTextAreaElement
>
(
null
);
const
TextareaDom
=
useRef
<
HTMLTextAreaElement
>
(
null
);
...
@@ -118,6 +122,7 @@ const Chat = ({
...
@@ -118,6 +122,7 @@ const Chat = ({
const
{
Loading
,
setIsLoading
}
=
useLoading
();
const
{
Loading
,
setIsLoading
}
=
useLoading
();
const
{
userInfo
}
=
useUserStore
();
const
{
userInfo
}
=
useUserStore
();
const
{
isOpen
:
isOpenSlider
,
onClose
:
onCloseSlider
,
onOpen
:
onOpenSlider
}
=
useDisclosure
();
const
{
isOpen
:
isOpenSlider
,
onClose
:
onCloseSlider
,
onOpen
:
onOpenSlider
}
=
useDisclosure
();
// close contextMenu
// close contextMenu
useOutsideClick
({
useOutsideClick
({
ref
:
ContextMenuRef
,
ref
:
ContextMenuRef
,
...
@@ -575,19 +580,64 @@ const Chat = ({
...
@@ -575,19 +580,64 @@ const Chat = ({
// abort stream
// abort stream
useEffect(() => {
useEffect(() => {
return () => {
return () => {
window.speechSynthesis?.cancel();
isLeavePage.current = true;
isLeavePage.current = true;
controller.current?.abort();
controller.current?.abort();
};
};
}, [modelId, chatId]);
}, [modelId, chatId]);
// context menu component
const RenderContextMenu = useCallback(
({
history,
index,
AiDetail = false
}: {
history: ChatSiteItemType;
index: number;
AiDetail?: boolean;
}) => (
<MenuList fontSize={'sm'} minW={'100px !important'}>
{AiDetail && chatData.model.canUse && history.obj === 'AI' && (
<MenuItem
borderBottom={theme.borders.base}
onClick={() => router.push(`/model?modelId=${chatData.modelId}`)}
>
AI助手详情
</MenuItem>
)}
<MenuItem onClick={() => onclickCopy(history.value)}>复制</MenuItem>
{hasVoiceApi && (
<MenuItem
borderBottom={theme.borders.base}
onClick={() => voiceBroadcast({ text: history.value })}
>
语音播报
</MenuItem>
)}
<MenuItem onClick={() => delChatRecord(index, history._id)}>删除</MenuItem>
</MenuList>
),
[
chatData.model.canUse,
chatData.modelId,
delChatRecord,
hasVoiceApi,
onclickCopy,
router,
theme.borders.base
]
);
return (
return (
<Flex
<Flex
h={'100%'}
h={'100%'}
flexDirection={['column', 'row']}
flexDirection={['column', 'row']}
backgroundColor={useColorModeValue('#f
efefe
', '')}
backgroundColor={useColorModeValue('#f
dfdfd
', '')}
>
>
{/* pc always show history. phone is only show when modelId is present */}
{/* pc always show history. phone is only show when modelId is present */}
{
isPc || !modelId ?
(
{
(isPc || !modelId) &&
(
<Box flex={[1, '0 0 250px']} w={['100%', 0]} h={'100%'}>
<Box flex={[1, '0 0 250px']} w={['100%', 0]} h={'100%'}>
<History
<History
onclickDelHistory={onclickDelHistory}
onclickDelHistory={onclickDelHistory}
...
@@ -595,29 +645,47 @@ const Chat = ({
...
@@ -595,29 +645,47 @@ const Chat = ({
isPcDevice={isPcDevice}
isPcDevice={isPcDevice}
/>
/>
</Box>
</Box>
) : (
)}
<Box
h={'44px'}
{/* 聊天内容 */}
borderBottom={'1px solid '}
{modelId && (
borderBottomColor={useColorModeValue('gray.200', 'gray.700')}
<Flex
position={'relative'}
h={[0, '100%']}
w={['100%', 0]}
flex={'1 0 0'}
flexDirection={'column'}
>
>
<Flex
<Flex
alignItems={'center'}
alignItems={'center'}
h={'100%'}
justifyContent={'space-between'}
justifyContent={'space-between'}
backgroundColor={useColorModeValue('white', 'gray.700')}
py={[3, 5]}
color={useColorModeValue('blackAlpha.700', 'white')}
px={5}
px={5}
borderBottom={'1px solid '}
borderBottomColor={useColorModeValue('gray.200', 'gray.700')}
color={useColorModeValue('myGray.900', 'white')}
>
>
<MyIcon
{!isPc && (
name={'tabbarMore'}
<MyIcon
w={'14px'}
name={'tabbarMore'}
h={'14px'}
w={'14px'}
color={useColorModeValue('blackAlpha.700', 'white')}
h={'14px'}
onClick={onOpenSlider}
color={useColorModeValue('blackAlpha.700', 'white')}
/>
onClick={onOpenSlider}
<Box>{chatData.model.name}</Box>
/>
{chatId && (
)}
<Box
cursor={'pointer'}
lineHeight={1.2}
textAlign={'center'}
px={3}
fontSize={['sm', 'md']}
onClick={() => router.push(`/model?modelId=${chatData.modelId}`)}
>
{chatData.model.name} {ChatModelMap[chatData.chatModel].name}
{chatData.history.length > 0 ? ` (${chatData.history.length})` : ''}
</Box>
{chatId ? (
<Menu autoSelect={false}>
<Menu autoSelect={false}>
<MenuButton lineHeight={1}>
<MenuButton lineHeight={1}>
<MyIcon
<MyIcon
...
@@ -650,27 +718,10 @@ const Chat = ({
...
@@ -650,27 +718,10 @@ const Chat = ({
<MenuItem onClick={() => onclickExportChat('md')}>导出Markdown格式</MenuItem>
<MenuItem onClick={() => onclickExportChat('md')}>导出Markdown格式</MenuItem>
</MenuList>
</MenuList>
</Menu>
</Menu>
) : (
<Box w={'16px'} h={'16px'} />
)}
)}
</Flex>
</Flex>
<Drawer isOpen={isOpenSlider} placement="
left
" size={'xs'} onClose={onCloseSlider}>
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
<DrawerContent maxWidth={'250px'}>
<PhoneSliderBar chatId={chatId} modelId={modelId} onClose={onCloseSlider} />
</DrawerContent>
</Drawer>
</Box>
)}
{/* 聊天内容 */}
{modelId && (
<Flex
position={'relative'}
h={[0, '100%']}
w={['100%', 0]}
pt={[2, 4]}
flex={'1 0 0'}
flexDirection={'column'}
>
<Box ref={ChatBox} pb={[4, 0]} flex={'1 0 0'} h={0} w={'100%'} overflow={'overlay'}>
<Box ref={ChatBox} pb={[4, 0]} flex={'1 0 0'} h={0} w={'100%'} overflow={'overlay'}>
<Box id={'history'} maxW={['auto', '800px', '1000px']} m={'auto'}>
<Box id={'history'} maxW={['auto', '800px', '1000px']} m={'auto'}>
{chatData.history.map((item, index) => (
{chatData.history.map((item, index) => (
...
@@ -683,13 +734,13 @@ const Chat = ({
...
@@ -683,13 +734,13 @@ const Chat = ({
{...(item.obj === 'AI'
{...(item.obj === 'AI'
? {
? {
order: 1,
order: 1,
mr: ['6px',
4
],
mr: ['6px',
2
],
cursor: 'pointer',
cursor: 'pointer',
onClick: () => isPc && router.push(`/model?modelId=${chatData.modelId}`)
onClick: () => isPc && router.push(`/model?modelId=${chatData.modelId}`)
}
}
: {
: {
order: 3,
order: 3,
ml: ['6px',
4
]
ml: ['6px',
2
]
})}
})}
>
>
<Tooltip label={item.obj === 'AI' ? 'AI助手详情' : ''}>
<Tooltip label={item.obj === 'AI' ? 'AI助手详情' : ''}>
...
@@ -708,29 +759,12 @@ const Chat = ({
...
@@ -708,29 +759,12 @@ const Chat = ({
/>
/>
</Tooltip>
</Tooltip>
</MenuButton>
</MenuButton>
{!isPc && (
{!isPc && <RenderContextMenu history={item} index={index} AiDetail />}
<MenuList fontSize={'sm'} minW={'100px !important'}>
{chatData.model.canUse && item.obj === 'AI' && (
<MenuItem
onClick={() => router.push(`/model?modelId=${chatData.modelId}`)}
>
AI助手详情
</MenuItem>
)}
<MenuItem onClick={() => onclickCopy(item.value)}>复制</MenuItem>
<MenuItem onClick={() => delChatRecord(index, item._id)}>删除</MenuItem>
</MenuList>
)}
</Menu>
</Menu>
{/* message */}
{/* message */}
<Flex order={2} maxW={['calc(100% - 50px)', '80%']}>
<Flex order={2}
pt={2}
maxW={['calc(100% - 50px)', '80%']}>
{item.obj === 'AI' ? (
{item.obj === 'AI' ? (
<Box w={'100%'}>
<Box w={'100%'}>
{isPc && (
<Box color={'myGray.600'} fontSize={'sm'} mb={1}>
{chatData.model.name}
</Box>
)}
<Card
<Card
bg={'white'}
bg={'white'}
px={4}
px={4}
...
@@ -759,11 +793,6 @@ const Chat = ({
...
@@ -759,11 +793,6 @@ const Chat = ({
</Box>
</Box>
) : (
) : (
<Box>
<Box>
{isPc && (
<Box color={'myGray.600'} mb={1} fontSize={'sm'} textAlign={'right'}>
Human
</Box>
)}
<Card
<Card
className="
markdown
"
className="
markdown
"
whiteSpace={'pre-wrap'}
whiteSpace={'pre-wrap'}
...
@@ -879,6 +908,15 @@ const Chat = ({
...
@@ -879,6 +908,15 @@ const Chat = ({
</Flex>
</Flex>
)}
)}
{/* phone slider */}
{!isPc && (
<Drawer isOpen={isOpenSlider} placement="
left
"
size
=
{
'xs'
}
onClose
=
{
onCloseSlider
}
>
<
DrawerOverlay
backgroundColor=
{
'rgba(255,255,255,0.5)'
}
/>
<
DrawerContent
maxWidth=
{
'250px'
}
>
<
PhoneSliderBar
chatId=
{
chatId
}
modelId=
{
modelId
}
onClose=
{
onCloseSlider
}
/>
</
DrawerContent
>
<
/
Drawer
>
)}
{
/* system prompt show modal */
}
{
/* system prompt show modal */
}
{
{
<
Modal
isOpen=
{
!!
showSystemPrompt
}
onClose=
{
()
=>
setShowSystemPrompt
(
''
)
}
>
<
Modal
isOpen=
{
!!
showSystemPrompt
}
onClose=
{
()
=>
setShowSystemPrompt
(
''
)
}
>
...
@@ -901,23 +939,13 @@ const Chat = ({
...
@@ -901,23 +939,13 @@ const Chat = ({
>
>
<
Box
ref=
{
ContextMenuRef
}
></
Box
>
<
Box
ref=
{
ContextMenuRef
}
></
Box
>
<
Menu
isOpen
>
<
Menu
isOpen
>
<
MenuList
minW=
{
`100px !important`
}
>
<
RenderContextMenu
<
MenuItem
onClick=
{
()
=>
onclickCopy
(
messageContextMenuData
.
message
.
value
)
}
>
history=
{
messageContextMenuData
.
message
}
复制
index=
{
chatData
.
history
.
findIndex
(
</
MenuItem
>
(
item
)
=>
item
.
_id
===
messageContextMenuData
.
message
.
_id
<
MenuItem
)
}
onClick=
{
()
=>
AiDetail=
{
!
isPc
}
delChatRecord
(
/>
chatData
.
history
.
findIndex
(
(
item
)
=>
item
.
_id
===
messageContextMenuData
.
message
.
_id
),
messageContextMenuData
.
message
.
_id
)
}
>
删除
</
MenuItem
>
</
MenuList
>
</
Menu
>
</
Menu
>
</
Box
>
</
Box
>
)
}
)
}
...
...
src/pages/model/components/detail/components/ModelEditForm.tsx
View file @
4e0c8761
...
@@ -145,7 +145,7 @@ const ModelEditForm = ({
...
@@ -145,7 +145,7 @@ const ModelEditForm = ({
</
Flex
>
</
Flex
>
{
isOwner
&&
(
{
isOwner
&&
(
<
Flex
mt=
{
5
}
alignItems=
{
'center'
}
>
<
Flex
mt=
{
5
}
alignItems=
{
'center'
}
>
<
Box
flex=
{
'0 0 1
5
0px'
}
>
删除AI和知识库
</
Box
>
<
Box
flex=
{
'0 0 1
2
0px'
}
>
删除AI和知识库
</
Box
>
<
Button
<
Button
colorScheme=
{
'gray'
}
colorScheme=
{
'gray'
}
variant=
{
'outline'
}
variant=
{
'outline'
}
...
...
src/pages/model/share/index.tsx
View file @
4e0c8761
...
@@ -17,7 +17,7 @@ const modelList = () => {
...
@@ -17,7 +17,7 @@ const modelList = () => {
/* 加载模型 */
/* 加载模型 */
const
{
data
,
isLoading
,
Pagination
,
getData
,
pageNum
}
=
usePagination
<
ShareModelItem
>
({
const
{
data
,
isLoading
,
Pagination
,
getData
,
pageNum
}
=
usePagination
<
ShareModelItem
>
({
api
:
getShareModelList
,
api
:
getShareModelList
,
pageSize
:
2
0
,
pageSize
:
2
4
,
params
:
{
params
:
{
searchText
searchText
}
}
...
...
src/utils/tools.ts
View file @
4e0c8761
...
@@ -88,3 +88,28 @@ export const formatTimeToChatTime = (time: Date) => {
...
@@ -88,3 +88,28 @@ export const formatTimeToChatTime = (time: Date) => {
// 如果是更久之前,展示某年某月某日
// 如果是更久之前,展示某年某月某日
return
target
.
format
(
'YYYY/M/D'
);
return
target
.
format
(
'YYYY/M/D'
);
};
};
/**
* voice broadcast
*/
export
const
voiceBroadcast
=
({
text
}:
{
text
:
string
})
=>
{
window
.
speechSynthesis
?.
cancel
();
const
msg
=
new
SpeechSynthesisUtterance
(
text
);
const
voices
=
window
.
speechSynthesis
?.
getVoices
?.();
// 获取语言包
const
voice
=
voices
.
find
((
item
)
=>
{
return
item
.
name
===
'Microsoft Yaoyao - Chinese (Simplified, PRC)'
;
});
if
(
voice
)
{
msg
.
voice
=
voice
;
}
window
.
speechSynthesis
?.
speak
(
msg
);
msg
.
onerror
=
(
e
)
=>
{
console
.
log
(
e
);
};
return
{
cancel
:
()
=>
window
.
speechSynthesis
?.
cancel
()
};
};
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