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
2fce7620
authored
May 29, 2023
by
archer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: custom title and set history top
parent
7fe39c25
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
210 additions
and
161 deletions
+210
-161
src/api/chat.ts
+8
-10
src/pages/api/chat/history/getHistory.ts
+12
-4
src/pages/api/chat/history/updateChatHistory.ts
+15
-13
src/pages/api/chat/history/updateHistoryQuote.ts
+0
-0
src/pages/api/chat/saveChat.ts
+1
-3
src/pages/chat/components/History.tsx
+71
-126
src/pages/chat/components/useEditTitle.tsx
+91
-0
src/service/models/chat.ts
+5
-2
src/service/pg.ts
+2
-1
src/types/chat.d.ts
+1
-0
src/types/mongoSchema.d.ts
+4
-2
No files found.
src/api/chat.ts
View file @
2fce7620
import
{
GET
,
POST
,
DELETE
}
from
'./request'
;
import
{
GET
,
POST
,
DELETE
,
PUT
}
from
'./request'
;
import
type
{
HistoryItemType
}
from
'@/types/chat'
;
import
type
{
InitChatResponse
,
InitShareChatResponse
}
from
'./response/chat'
;
import
{
RequestPaging
}
from
'../types/index'
;
...
...
@@ -6,6 +6,7 @@ import type { ShareChatSchema } from '@/types/mongoSchema';
import
type
{
ShareChatEditType
}
from
'@/types/model'
;
import
{
Obj2Query
}
from
'@/utils/tools'
;
import
{
QuoteItemType
}
from
'@/pages/api/openapi/kb/appKbSearch'
;
import
type
{
Props
as
UpdateHistoryProps
}
from
'@/pages/api/chat/history/updateChatHistory'
;
/**
* 获取初始化聊天内容
...
...
@@ -17,7 +18,7 @@ export const getInitChatSiteInfo = (modelId: '' | string, chatId: '' | string) =
* 获取历史记录
*/
export
const
getChatHistory
=
(
data
:
RequestPaging
)
=>
POST
<
HistoryItemType
[]
>
(
'/chat/getHistory'
,
data
);
POST
<
HistoryItemType
[]
>
(
'/chat/
history/
getHistory'
,
data
);
/**
* 删除一条历史记录
...
...
@@ -28,7 +29,7 @@ export const delChatHistoryById = (id: string) => GET(`/chat/removeHistory?id=${
* get history quotes
*/
export
const
getHistoryQuote
=
(
params
:
{
chatId
:
string
;
historyId
:
string
})
=>
GET
<
(
QuoteItemType
&
{
_id
:
string
})[]
>
(
`/chat/getHistoryQuote`
,
params
);
GET
<
(
QuoteItemType
&
{
_id
:
string
})[]
>
(
`/chat/
history/
getHistoryQuote`
,
params
);
/**
* update history quote status
...
...
@@ -37,7 +38,7 @@ export const updateHistoryQuote = (params: {
chatId
:
string
;
historyId
:
string
;
quoteId
:
string
;
})
=>
GET
(
`/chat/updateHistoryQuote`
,
params
);
})
=>
GET
(
`/chat/
history/
updateHistoryQuote`
,
params
);
/**
* 删除一句对话
...
...
@@ -46,13 +47,10 @@ export const delChatRecordByIndex = (chatId: string, contentId: string) =>
DELETE
(
`/chat/delChatRecordByContentId?chatId=
${
chatId
}
&contentId=
${
contentId
}
`
);
/**
* 修改历史记录
标题
* 修改历史记录
: 标题/置顶
*/
export
const
updateChatHistoryTitle
=
(
data
:
{
chatId
:
string
;
modelId
:
string
;
newTitle
:
string
;
})
=>
POST
<
string
>
(
'/chat/updateChatHistoryTitle'
,
data
);
export
const
putChatHistory
=
(
data
:
UpdateHistoryProps
)
=>
PUT
(
'/chat/history/updateChatHistory'
,
data
);
/**
* create a shareChat
...
...
src/pages/api/chat/getHistory.ts
→
src/pages/api/chat/
history/
getHistory.ts
View file @
2fce7620
...
...
@@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import
{
jsonRes
}
from
'@/service/response'
;
import
{
connectToDatabase
,
Chat
}
from
'@/service/mongo'
;
import
{
authUser
}
from
'@/service/utils/auth'
;
import
type
{
HistoryItemType
}
from
'@/types/chat'
;
/* 获取历史记录 */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
...
...
@@ -14,13 +15,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
{
userId
},
'_id title modelId updateTime latestChat'
'_id title
top customTitle
modelId updateTime latestChat'
)
.
sort
({
updateTime
:
-
1
})
.
sort
({
top
:
-
1
,
updateTime
:
-
1
})
.
limit
(
20
);
jsonRes
(
res
,
{
data
jsonRes
<
HistoryItemType
[]
>
(
res
,
{
data
:
data
.
map
((
item
)
=>
({
_id
:
item
.
_id
,
updateTime
:
item
.
updateTime
,
modelId
:
item
.
modelId
,
title
:
item
.
customTitle
||
item
.
title
,
latestChat
:
item
.
latestChat
,
top
:
item
.
top
}))
});
}
catch
(
err
)
{
jsonRes
(
res
,
{
...
...
src/pages/api/chat/
updateChatHistoryTitle
.ts
→
src/pages/api/chat/
history/updateChatHistory
.ts
View file @
2fce7620
import
type
{
NextApiRequest
,
NextApiResponse
}
from
'next'
;
import
{
jsonRes
}
from
'@/service/response'
;
import
{
connectToDatabase
,
Chat
}
from
'@/service/mongo'
;
import
{
authModel
}
from
'@/service/utils/auth'
;
import
{
authUser
}
from
'@/service/utils/auth'
;
export
type
Props
=
{
chatId
:
''
|
string
;
customTitle
?:
string
;
top
?:
boolean
;
};
/* 更新聊天标题 */
export
default
async
function
handler
(
req
:
NextApiRequest
,
res
:
NextApiResponse
)
{
try
{
const
{
chatId
,
modelId
,
newTitle
}
=
req
.
body
as
{
chatId
:
''
|
string
;
modelId
:
''
|
string
;
newTitle
:
string
;
};
const
{
chatId
,
customTitle
,
top
}
=
req
.
body
as
Props
;
const
{
userId
}
=
await
authUser
({
req
,
authToken
:
true
});
await
connectToDatabase
();
await
authModel
({
modelId
,
userId
,
authOwner
:
false
});
await
Chat
.
findByIdAndUpdate
(
chatId
,
await
Chat
.
findOneAndUpdate
(
{
_id
:
chatId
,
userId
},
{
title
:
newTitle
,
customTitle
:
true
}
// 自定义标题}
...(
customTitle
?
{
customTitle
}
:
{})
,
...(
top
?
{
top
}
:
{
top
:
null
})
}
);
jsonRes
(
res
);
}
catch
(
err
)
{
...
...
src/pages/api/chat/updateHistoryQuote.ts
→
src/pages/api/chat/
history/
updateHistoryQuote.ts
View file @
2fce7620
File moved
src/pages/api/chat/saveChat.ts
View file @
2fce7620
...
...
@@ -77,15 +77,13 @@ export async function saveChat({
});
return
_id
;
}
else
{
// 已经有记录,追加入库
const
chat
=
await
Chat
.
findById
(
chatId
);
await
Chat
.
findByIdAndUpdate
(
chatId
,
{
$push
:
{
content
:
{
$each
:
content
}
},
...(
chat
&&
!
chat
.
customTitle
?
{
title
:
content
[
0
].
value
.
slice
(
0
,
20
)
}
:
{}
),
title
:
content
[
0
].
value
.
slice
(
0
,
20
),
latestChat
:
content
[
1
].
value
,
updateTime
:
new
Date
()
});
...
...
src/pages/chat/components/History.tsx
View file @
2fce7620
import
React
,
{
useCallback
,
useRef
,
useState
,
useMemo
,
useEffect
}
from
'react'
;
import
React
,
{
useCallback
,
useRef
,
useState
,
useMemo
}
from
'react'
;
import
type
{
MouseEvent
}
from
'react'
;
import
{
AddIcon
,
EditIcon
,
CheckIcon
,
CloseIcon
,
DeleteIcon
}
from
'@chakra-ui/icons'
;
import
{
AddIcon
}
from
'@chakra-ui/icons'
;
import
{
Input
,
Box
,
Button
,
Flex
,
...
...
@@ -20,61 +19,13 @@ import { useUserStore } from '@/store/user';
import
MyIcon
from
'@/components/Icon'
;
import
type
{
HistoryItemType
,
ExportChatType
}
from
'@/types/chat'
;
import
{
useChatStore
}
from
'@/store/chat'
;
import
{
updateChatHistoryTitle
}
from
'@/api/chat'
;
import
ModelList
from
'./ModelList'
;
import
{
useGlobalStore
}
from
'@/store/global'
;
import
styles
from
'../index.module.scss'
;
type
UseEditTitleReturnType
=
{
editingHistoryId
:
string
|
null
;
setEditingHistoryId
:
React
.
Dispatch
<
React
.
SetStateAction
<
string
|
null
>>
;
editedTitle
:
string
;
setEditedTitle
:
React
.
Dispatch
<
React
.
SetStateAction
<
string
>>
;
inputRef
:
React
.
RefObject
<
HTMLInputElement
>
;
onEditClick
:
(
id
:
string
,
title
:
string
)
=>
void
;
onSaveClick
:
(
chatId
:
string
,
modelId
:
string
,
editedTitle
:
string
)
=>
Promise
<
void
>
;
onCloseClick
:
()
=>
void
;
};
const
useEditTitle
=
():
UseEditTitleReturnType
=>
{
const
[
editingHistoryId
,
setEditingHistoryId
]
=
useState
<
string
|
null
>
(
null
);
const
[
editedTitle
,
setEditedTitle
]
=
useState
<
string
>
(
''
);
const
inputRef
=
useRef
<
HTMLInputElement
|
null
>
(
null
);
const
onEditClick
=
(
id
:
string
,
title
:
string
)
=>
{
setEditingHistoryId
(
id
);
setEditedTitle
(
title
);
inputRef
.
current
&&
inputRef
.
current
.
focus
();
};
const
onSaveClick
=
async
(
chatId
:
string
,
modelId
:
string
,
editedTitle
:
string
)
=>
{
setEditingHistoryId
(
null
);
await
updateChatHistoryTitle
({
chatId
:
chatId
,
modelId
:
modelId
,
newTitle
:
editedTitle
});
};
const
onCloseClick
=
()
=>
{
setEditingHistoryId
(
null
);
};
useEffect
(()
=>
{
if
(
editingHistoryId
)
{
inputRef
.
current
&&
inputRef
.
current
.
focus
();
}
},
[
editingHistoryId
]);
return
{
editingHistoryId
,
setEditingHistoryId
,
editedTitle
,
setEditedTitle
,
inputRef
,
onEditClick
,
onSaveClick
,
onCloseClick
};
};
import
{
useEditTitle
}
from
'./useEditTitle'
;
import
{
putChatHistory
}
from
'@/api/chat'
;
import
{
useToast
}
from
'@/hooks/useToast'
;
import
{
formatTimeToChatTime
,
getErrText
}
from
'@/utils/tools'
;
const
PcSliderBar
=
({
onclickDelHistory
,
...
...
@@ -83,24 +34,14 @@ const PcSliderBar = ({
onclickDelHistory
:
(
historyId
:
string
)
=>
Promise
<
void
>
;
onclickExportChat
:
(
type
:
ExportChatType
)
=>
void
;
})
=>
{
// 使用自定义的useEditTitle hook来管理聊天标题的编辑状态
const
{
editingHistoryId
,
editedTitle
,
setEditedTitle
,
inputRef
,
onEditClick
,
onSaveClick
,
onCloseClick
}
=
useEditTitle
();
const
router
=
useRouter
();
const
{
toast
}
=
useToast
();
const
{
modelId
=
''
,
chatId
=
''
}
=
router
.
query
as
{
modelId
:
string
;
chatId
:
string
};
const
ContextMenuRef
=
useRef
(
null
);
const
theme
=
useTheme
();
const
{
isPc
}
=
useGlobalStore
();
const
ContextMenuRef
=
useRef
(
null
);
const
{
Loading
,
setIsLoading
}
=
useLoading
();
const
[
contextMenuData
,
setContextMenuData
]
=
useState
<
{
left
:
number
;
...
...
@@ -114,7 +55,12 @@ const PcSliderBar = ({
()
=>
[...
myModels
,
...
myCollectionModels
],
[
myCollectionModels
,
myModels
]
);
useQuery
([
'loadModels'
],
()
=>
loadMyModels
(
false
));
// custom title edit
const
{
onOpenModal
,
EditModal
:
EditTitleModal
}
=
useEditTitle
({
title
:
'自定义历史记录标题'
,
placeholder
:
'如果设置为空,会自动跟随聊天记录。'
});
// close contextMenu
useOutsideClick
({
...
...
@@ -122,13 +68,9 @@ const PcSliderBar = ({
handler
:
()
=>
setTimeout
(()
=>
{
setContextMenuData
(
undefined
);
})
}
,
10
)
});
const
{
isLoading
:
isLoadingHistory
}
=
useQuery
([
'loadingHistory'
],
()
=>
loadHistory
({
pageNum
:
1
})
);
const
onclickContextMenu
=
useCallback
(
(
e
:
MouseEvent
<
HTMLDivElement
>
,
history
:
HistoryItemType
)
=>
{
e
.
preventDefault
();
// 阻止默认右键菜单
...
...
@@ -144,6 +86,12 @@ const PcSliderBar = ({
[
isPc
]
);
useQuery
([
'loadModels'
],
()
=>
loadMyModels
(
false
));
const
{
isLoading
:
isLoadingHistory
}
=
useQuery
([
'loadingHistory'
],
()
=>
loadHistory
({
pageNum
:
1
})
);
return
(
<
Flex
position=
{
'relative'
}
...
...
@@ -211,14 +159,16 @@ const PcSliderBar = ({
borderLeft=
{
[
'none'
,
'5px solid transparent'
]
}
userSelect=
{
'none'
}
_hover=
{
{
b
ackgroundColor
:
[
''
,
'#dee0e3'
]
b
g
:
[
''
,
'#dee0e3'
]
}
}
{
...
(
item
.
_id
===
chatId
?
{
b
ackgroundColor
:
'#
eff0f1
',
b
g
:
'
myGray
.
100
!
important
',
borderLeftColor
:
'
myBlue
.
600
!
important
'
}
:
{})}
:
{
bg
:
item
.
top
?
'
myBlue
.
200'
:
''
})}
onClick=
{
()
=>
{
if
(
item
.
_id
===
chatId
)
return
;
if
(
isPc
)
{
...
...
@@ -232,46 +182,12 @@ const PcSliderBar = ({
<
ChatIcon
fontSize=
{
'16px'
}
color=
{
'myGray.500'
}
/>
<
Box
flex=
{
'1 0 0'
}
w=
{
0
}
ml=
{
3
}
>
<
Flex
alignItems=
{
'center'
}
>
{
editingHistoryId
===
item
.
_id
?
(
<
Input
ref=
{
inputRef
}
value=
{
editedTitle
}
onChange=
{
(
e
:
{
target
:
{
value
:
React
.
SetStateAction
<
string
>
}
})
=>
setEditedTitle
(
e
.
target
.
value
)
}
onBlur=
{
onCloseClick
}
height=
{
'1.5em'
}
paddingLeft=
{
'0.5'
}
style=
{
{
width
:
'65%'
}
}
// 设置输入框宽度为父元素宽度的一半
/>
)
:
(
<
Box
flex=
{
'1 0 0'
}
w=
{
0
}
className=
"textEllipsis"
color=
{
'myGray.1000'
}
>
{
item
.
title
}
</
Box
>
)
}
{
/* 编辑状态下显示确认和取消按钮 */
}
{
editingHistoryId
===
item
.
_id
?
(
<>
<
Box
mx=
{
1
}
>
<
CheckIcon
onMouseDown=
{
async
(
event
)
=>
{
event
.
preventDefault
();
setIsLoading
(
true
);
try
{
await
onSaveClick
(
item
.
_id
,
item
.
modelId
,
editedTitle
);
await
loadHistory
({
pageNum
:
1
,
init
:
true
});
}
catch
(
error
)
{
console
.
log
(
error
);
}
setIsLoading
(
false
);
}
}
_hover=
{
{
color
:
'blue.500'
}
}
paddingLeft=
{
'1'
}
/>
</
Box
>
</>
)
:
null
}
<
Box
flex=
{
'1 0 0'
}
w=
{
0
}
className=
"textEllipsis"
color=
{
'myGray.1000'
}
>
{
item
.
title
}
</
Box
>
<
Box
color=
{
'myGray.400'
}
fontSize=
{
'sm'
}
>
{
formatTimeToChatTime
(
item
.
updateTime
)
}
</
Box
>
</
Flex
>
<
Box
className=
"textEllipsis"
mt=
{
1
}
fontSize=
{
'sm'
}
color=
{
'myGray.500'
}
>
{
item
.
latestChat
||
'……'
}
...
...
@@ -314,6 +230,19 @@ const PcSliderBar = ({
<
MenuList
>
<
MenuItem
onClick=
{
async
()
=>
{
try
{
await
putChatHistory
({
chatId
:
contextMenuData
.
history
.
_id
,
top
:
!
contextMenuData
.
history
.
top
});
loadHistory
({
pageNum
:
1
,
init
:
true
});
}
catch
(
error
)
{}
}
}
>
{
contextMenuData
.
history
.
top
?
'取消置顶'
:
'置顶'
}
</
MenuItem
>
<
MenuItem
onClick=
{
async
()
=>
{
setIsLoading
(
true
);
try
{
await
onclickDelHistory
(
contextMenuData
.
history
.
_id
);
...
...
@@ -329,15 +258,31 @@ const PcSliderBar = ({
删除记录
</
MenuItem
>
<
MenuItem
onClick=
{
()
=>
{
try
{
onEditClick
(
contextMenuData
.
history
.
_id
,
contextMenuData
.
history
.
title
);
}
catch
(
error
)
{
console
.
log
(
error
);
}
}
}
onClick=
{
()
=>
onOpenModal
({
defaultVal
:
contextMenuData
.
history
.
title
,
onSuccess
:
async
(
val
:
string
)
=>
{
await
putChatHistory
({
chatId
:
contextMenuData
.
history
.
_id
,
customTitle
:
val
,
top
:
contextMenuData
.
history
.
top
});
toast
({
title
:
'自定义标题成功'
,
status
:
'success'
});
loadHistory
({
pageNum
:
1
,
init
:
true
});
},
onError
(
err
)
{
toast
({
title
:
getErrText
(
err
),
status
:
'error'
});
}
})
}
>
编辑
标题
自定义
标题
</
MenuItem
>
<
MenuItem
onClick=
{
()
=>
onclickExportChat
(
'html'
)
}
>
导出HTML格式
</
MenuItem
>
<
MenuItem
onClick=
{
()
=>
onclickExportChat
(
'pdf'
)
}
>
导出PDF格式
</
MenuItem
>
...
...
@@ -346,7 +291,7 @@ const PcSliderBar = ({
</
Menu
>
</
Box
>
)
}
<
EditTitleModal
/>
<
Loading
loading=
{
isLoadingHistory
}
fixed=
{
false
}
/>
</
Flex
>
);
...
...
src/pages/chat/components/useEditTitle.tsx
0 → 100644
View file @
2fce7620
import
React
,
{
useCallback
,
useRef
,
useState
,
memo
}
from
'react'
;
import
{
Modal
,
ModalOverlay
,
ModalContent
,
ModalHeader
,
ModalFooter
,
ModalBody
,
ModalCloseButton
,
Input
,
useDisclosure
,
Button
}
from
'@chakra-ui/react'
;
export
const
useEditTitle
=
({
title
,
placeholder
=
''
}:
{
title
:
string
;
placeholder
?:
string
;
})
=>
{
const
{
isOpen
,
onOpen
,
onClose
}
=
useDisclosure
();
const
inputRef
=
useRef
<
HTMLInputElement
|
null
>
(
null
);
const
onSuccessCb
=
useRef
<
(
content
:
string
)
=>
void
|
Promise
<
void
>>
();
const
onErrorCb
=
useRef
<
(
err
:
any
)
=>
void
>
();
const
defaultValue
=
useRef
(
''
);
const
onOpenModal
=
useCallback
(
({
defaultVal
,
onSuccess
,
onError
}:
{
defaultVal
:
string
;
onSuccess
:
(
content
:
string
)
=>
any
;
onError
?:
(
err
:
any
)
=>
void
;
})
=>
{
onOpen
();
onSuccessCb
.
current
=
onSuccess
;
onErrorCb
.
current
=
onError
;
defaultValue
.
current
=
defaultVal
;
},
[
onOpen
]
);
const
onclickConfirm
=
useCallback
(
async
()
=>
{
if
(
!
inputRef
.
current
)
return
;
try
{
const
val
=
inputRef
.
current
.
value
;
await
onSuccessCb
.
current
?.(
val
);
onClose
();
}
catch
(
err
)
{
onErrorCb
.
current
?.(
err
);
}
},
[
onClose
]);
// eslint-disable-next-line react/display-name
const
EditModal
=
useCallback
(
()
=>
(
<
Modal
isOpen=
{
isOpen
}
onClose=
{
onClose
}
>
<
ModalOverlay
/>
<
ModalContent
>
<
ModalHeader
>
{
title
}
</
ModalHeader
>
<
ModalCloseButton
/>
<
ModalBody
>
<
Input
ref=
{
inputRef
}
defaultValue=
{
defaultValue
.
current
}
placeholder=
{
placeholder
}
autoFocus
maxLength=
{
20
}
/>
</
ModalBody
>
<
ModalFooter
>
<
Button
mr=
{
3
}
variant=
{
'outline'
}
onClick=
{
onClose
}
>
取消
</
Button
>
<
Button
onClick=
{
onclickConfirm
}
>
确认
</
Button
>
</
ModalFooter
>
</
ModalContent
>
</
Modal
>
),
[
isOpen
,
onClose
,
onclickConfirm
,
placeholder
,
title
]
);
return
{
onOpenModal
,
EditModal
};
};
src/service/models/chat.ts
View file @
2fce7620
...
...
@@ -32,13 +32,16 @@ const ChatSchema = new Schema({
default
:
'历史记录'
},
customTitle
:
{
type
:
Boolean
,
default
:
false
type
:
String
,
default
:
''
},
latestChat
:
{
type
:
String
,
default
:
''
},
top
:
{
type
:
Boolean
},
content
:
{
type
:
[
{
...
...
src/service/pg.ts
View file @
2fce7620
...
...
@@ -6,13 +6,14 @@ export const connectPg = async () => {
return
global
.
pgClient
;
}
const
maxLink
=
Number
(
process
.
env
.
VECTOR_MAX_PROCESS
||
10
);
global
.
pgClient
=
new
Pool
({
host
:
process
.
env
.
PG_HOST
,
port
:
process
.
env
.
PG_PORT
?
+
process
.
env
.
PG_PORT
:
5432
,
user
:
process
.
env
.
PG_USER
,
password
:
process
.
env
.
PG_PASSWORD
,
database
:
process
.
env
.
PG_DB_NAME
,
max
:
20
,
max
:
maxLink
,
idleTimeoutMillis
:
60000
,
connectionTimeoutMillis
:
20000
});
...
...
src/types/chat.d.ts
View file @
2fce7620
...
...
@@ -33,6 +33,7 @@ export type HistoryItemType = {
modelId
:
string
;
title
:
string
;
latestChat
:
string
;
top
:
boolean
;
};
export
type
ShareChatHistoryItemType
=
{
...
...
src/types/mongoSchema.d.ts
View file @
2fce7620
...
...
@@ -85,10 +85,12 @@ export interface ChatSchema {
userId
:
string
;
modelId
:
string
;
expiredTime
:
number
;
loadAmount
:
number
;
updateTime
:
Date
;
title
:
string
;
customTitle
:
string
;
latestChat
:
string
;
top
:
boolean
;
content
:
ChatItemType
[];
customTitle
:
Boolean
;
}
export
interface
ChatPopulate
extends
ChatSchema
{
userId
:
UserModelSchema
;
...
...
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