mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 05:12:39 +00:00
feat: custom title and set history top
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { GET, POST, DELETE } from './request';
|
import { GET, POST, DELETE, PUT } from './request';
|
||||||
import type { HistoryItemType } from '@/types/chat';
|
import type { HistoryItemType } from '@/types/chat';
|
||||||
import type { InitChatResponse, InitShareChatResponse } from './response/chat';
|
import type { InitChatResponse, InitShareChatResponse } from './response/chat';
|
||||||
import { RequestPaging } from '../types/index';
|
import { RequestPaging } from '../types/index';
|
||||||
@@ -6,6 +6,7 @@ import type { ShareChatSchema } from '@/types/mongoSchema';
|
|||||||
import type { ShareChatEditType } from '@/types/model';
|
import type { ShareChatEditType } from '@/types/model';
|
||||||
import { Obj2Query } from '@/utils/tools';
|
import { Obj2Query } from '@/utils/tools';
|
||||||
import { QuoteItemType } from '@/pages/api/openapi/kb/appKbSearch';
|
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) =>
|
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
|
* get history quotes
|
||||||
*/
|
*/
|
||||||
export const getHistoryQuote = (params: { chatId: string; historyId: string }) =>
|
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
|
* update history quote status
|
||||||
@@ -37,7 +38,7 @@ export const updateHistoryQuote = (params: {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
historyId: string;
|
historyId: string;
|
||||||
quoteId: 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}`);
|
DELETE(`/chat/delChatRecordByContentId?chatId=${chatId}&contentId=${contentId}`);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改历史记录标题
|
* 修改历史记录: 标题/置顶
|
||||||
*/
|
*/
|
||||||
export const updateChatHistoryTitle = (data: {
|
export const putChatHistory = (data: UpdateHistoryProps) =>
|
||||||
chatId: string;
|
PUT('/chat/history/updateChatHistory', data);
|
||||||
modelId: string;
|
|
||||||
newTitle: string;
|
|
||||||
}) => POST<string>('/chat/updateChatHistoryTitle', data);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create a shareChat
|
* create a shareChat
|
||||||
|
@@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
|||||||
import { jsonRes } from '@/service/response';
|
import { jsonRes } from '@/service/response';
|
||||||
import { connectToDatabase, Chat } from '@/service/mongo';
|
import { connectToDatabase, Chat } from '@/service/mongo';
|
||||||
import { authUser } from '@/service/utils/auth';
|
import { authUser } from '@/service/utils/auth';
|
||||||
|
import type { HistoryItemType } from '@/types/chat';
|
||||||
|
|
||||||
/* 获取历史记录 */
|
/* 获取历史记录 */
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
@@ -14,13 +15,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
{
|
{
|
||||||
userId
|
userId
|
||||||
},
|
},
|
||||||
'_id title modelId updateTime latestChat'
|
'_id title top customTitle modelId updateTime latestChat'
|
||||||
)
|
)
|
||||||
.sort({ updateTime: -1 })
|
.sort({ top: -1, updateTime: -1 })
|
||||||
.limit(20);
|
.limit(20);
|
||||||
|
|
||||||
jsonRes(res, {
|
jsonRes<HistoryItemType[]>(res, {
|
||||||
data
|
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) {
|
} catch (err) {
|
||||||
jsonRes(res, {
|
jsonRes(res, {
|
@@ -1,30 +1,32 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { jsonRes } from '@/service/response';
|
import { jsonRes } from '@/service/response';
|
||||||
import { connectToDatabase, Chat } from '@/service/mongo';
|
import { connectToDatabase, Chat } from '@/service/mongo';
|
||||||
import { authModel } from '@/service/utils/auth';
|
|
||||||
import { authUser } 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) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
try {
|
try {
|
||||||
const { chatId, modelId, newTitle } = req.body as {
|
const { chatId, customTitle, top } = req.body as Props;
|
||||||
chatId: '' | string;
|
|
||||||
modelId: '' | string;
|
|
||||||
newTitle: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const { userId } = await authUser({ req, authToken: true });
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
await connectToDatabase();
|
await connectToDatabase();
|
||||||
|
|
||||||
await authModel({ modelId, userId, authOwner: false });
|
await Chat.findOneAndUpdate(
|
||||||
|
|
||||||
await Chat.findByIdAndUpdate(
|
|
||||||
chatId,
|
|
||||||
{
|
{
|
||||||
title: newTitle,
|
_id: chatId,
|
||||||
customTitle: true
|
userId
|
||||||
} // 自定义标题}
|
},
|
||||||
|
{
|
||||||
|
...(customTitle ? { customTitle } : {}),
|
||||||
|
...(top ? { top } : { top: null })
|
||||||
|
}
|
||||||
);
|
);
|
||||||
jsonRes(res);
|
jsonRes(res);
|
||||||
} catch (err) {
|
} catch (err) {
|
@@ -77,15 +77,13 @@ export async function saveChat({
|
|||||||
});
|
});
|
||||||
return _id;
|
return _id;
|
||||||
} else {
|
} else {
|
||||||
// 已经有记录,追加入库
|
|
||||||
const chat = await Chat.findById(chatId);
|
|
||||||
await Chat.findByIdAndUpdate(chatId, {
|
await Chat.findByIdAndUpdate(chatId, {
|
||||||
$push: {
|
$push: {
|
||||||
content: {
|
content: {
|
||||||
$each: content
|
$each: content
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...(chat && !chat.customTitle ? { title: content[0].value.slice(0, 20) } : {}),
|
title: content[0].value.slice(0, 20),
|
||||||
latestChat: content[1].value,
|
latestChat: content[1].value,
|
||||||
updateTime: new Date()
|
updateTime: new Date()
|
||||||
});
|
});
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
import React, { useCallback, useRef, useState, useMemo, useEffect } from 'react';
|
import React, { useCallback, useRef, useState, useMemo } from 'react';
|
||||||
import type { MouseEvent } from 'react';
|
import type { MouseEvent } from 'react';
|
||||||
import { AddIcon, EditIcon, CheckIcon, CloseIcon, DeleteIcon } from '@chakra-ui/icons';
|
import { AddIcon } from '@chakra-ui/icons';
|
||||||
import {
|
import {
|
||||||
Input,
|
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Flex,
|
Flex,
|
||||||
@@ -20,61 +19,13 @@ import { useUserStore } from '@/store/user';
|
|||||||
import MyIcon from '@/components/Icon';
|
import MyIcon from '@/components/Icon';
|
||||||
import type { HistoryItemType, ExportChatType } from '@/types/chat';
|
import type { HistoryItemType, ExportChatType } from '@/types/chat';
|
||||||
import { useChatStore } from '@/store/chat';
|
import { useChatStore } from '@/store/chat';
|
||||||
import { updateChatHistoryTitle } from '@/api/chat';
|
|
||||||
import ModelList from './ModelList';
|
import ModelList from './ModelList';
|
||||||
import { useGlobalStore } from '@/store/global';
|
import { useGlobalStore } from '@/store/global';
|
||||||
import styles from '../index.module.scss';
|
import styles from '../index.module.scss';
|
||||||
|
import { useEditTitle } from './useEditTitle';
|
||||||
type UseEditTitleReturnType = {
|
import { putChatHistory } from '@/api/chat';
|
||||||
editingHistoryId: string | null;
|
import { useToast } from '@/hooks/useToast';
|
||||||
setEditingHistoryId: React.Dispatch<React.SetStateAction<string | null>>;
|
import { formatTimeToChatTime, getErrText } from '@/utils/tools';
|
||||||
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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const PcSliderBar = ({
|
const PcSliderBar = ({
|
||||||
onclickDelHistory,
|
onclickDelHistory,
|
||||||
@@ -83,24 +34,14 @@ const PcSliderBar = ({
|
|||||||
onclickDelHistory: (historyId: string) => Promise<void>;
|
onclickDelHistory: (historyId: string) => Promise<void>;
|
||||||
onclickExportChat: (type: ExportChatType) => void;
|
onclickExportChat: (type: ExportChatType) => void;
|
||||||
}) => {
|
}) => {
|
||||||
// 使用自定义的useEditTitle hook来管理聊天标题的编辑状态
|
|
||||||
const {
|
|
||||||
editingHistoryId,
|
|
||||||
editedTitle,
|
|
||||||
setEditedTitle,
|
|
||||||
inputRef,
|
|
||||||
onEditClick,
|
|
||||||
onSaveClick,
|
|
||||||
onCloseClick
|
|
||||||
} = useEditTitle();
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { toast } = useToast();
|
||||||
const { modelId = '', chatId = '' } = router.query as { modelId: string; chatId: string };
|
const { modelId = '', chatId = '' } = router.query as { modelId: string; chatId: string };
|
||||||
|
const ContextMenuRef = useRef(null);
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { isPc } = useGlobalStore();
|
const { isPc } = useGlobalStore();
|
||||||
|
|
||||||
const ContextMenuRef = useRef(null);
|
|
||||||
|
|
||||||
const { Loading, setIsLoading } = useLoading();
|
const { Loading, setIsLoading } = useLoading();
|
||||||
const [contextMenuData, setContextMenuData] = useState<{
|
const [contextMenuData, setContextMenuData] = useState<{
|
||||||
left: number;
|
left: number;
|
||||||
@@ -114,7 +55,12 @@ const PcSliderBar = ({
|
|||||||
() => [...myModels, ...myCollectionModels],
|
() => [...myModels, ...myCollectionModels],
|
||||||
[myCollectionModels, myModels]
|
[myCollectionModels, myModels]
|
||||||
);
|
);
|
||||||
useQuery(['loadModels'], () => loadMyModels(false));
|
|
||||||
|
// custom title edit
|
||||||
|
const { onOpenModal, EditModal: EditTitleModal } = useEditTitle({
|
||||||
|
title: '自定义历史记录标题',
|
||||||
|
placeholder: '如果设置为空,会自动跟随聊天记录。'
|
||||||
|
});
|
||||||
|
|
||||||
// close contextMenu
|
// close contextMenu
|
||||||
useOutsideClick({
|
useOutsideClick({
|
||||||
@@ -122,13 +68,9 @@ const PcSliderBar = ({
|
|||||||
handler: () =>
|
handler: () =>
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setContextMenuData(undefined);
|
setContextMenuData(undefined);
|
||||||
})
|
}, 10)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { isLoading: isLoadingHistory } = useQuery(['loadingHistory'], () =>
|
|
||||||
loadHistory({ pageNum: 1 })
|
|
||||||
);
|
|
||||||
|
|
||||||
const onclickContextMenu = useCallback(
|
const onclickContextMenu = useCallback(
|
||||||
(e: MouseEvent<HTMLDivElement>, history: HistoryItemType) => {
|
(e: MouseEvent<HTMLDivElement>, history: HistoryItemType) => {
|
||||||
e.preventDefault(); // 阻止默认右键菜单
|
e.preventDefault(); // 阻止默认右键菜单
|
||||||
@@ -144,6 +86,12 @@ const PcSliderBar = ({
|
|||||||
[isPc]
|
[isPc]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useQuery(['loadModels'], () => loadMyModels(false));
|
||||||
|
|
||||||
|
const { isLoading: isLoadingHistory } = useQuery(['loadingHistory'], () =>
|
||||||
|
loadHistory({ pageNum: 1 })
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
position={'relative'}
|
position={'relative'}
|
||||||
@@ -211,14 +159,16 @@ const PcSliderBar = ({
|
|||||||
borderLeft={['none', '5px solid transparent']}
|
borderLeft={['none', '5px solid transparent']}
|
||||||
userSelect={'none'}
|
userSelect={'none'}
|
||||||
_hover={{
|
_hover={{
|
||||||
backgroundColor: ['', '#dee0e3']
|
bg: ['', '#dee0e3']
|
||||||
}}
|
}}
|
||||||
{...(item._id === chatId
|
{...(item._id === chatId
|
||||||
? {
|
? {
|
||||||
backgroundColor: '#eff0f1',
|
bg: 'myGray.100 !important',
|
||||||
borderLeftColor: 'myBlue.600 !important'
|
borderLeftColor: 'myBlue.600 !important'
|
||||||
}
|
}
|
||||||
: {})}
|
: {
|
||||||
|
bg: item.top ? 'myBlue.200' : ''
|
||||||
|
})}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (item._id === chatId) return;
|
if (item._id === chatId) return;
|
||||||
if (isPc) {
|
if (isPc) {
|
||||||
@@ -232,46 +182,12 @@ const PcSliderBar = ({
|
|||||||
<ChatIcon fontSize={'16px'} color={'myGray.500'} />
|
<ChatIcon fontSize={'16px'} color={'myGray.500'} />
|
||||||
<Box flex={'1 0 0'} w={0} ml={3}>
|
<Box flex={'1 0 0'} w={0} ml={3}>
|
||||||
<Flex alignItems={'center'}>
|
<Flex alignItems={'center'}>
|
||||||
{editingHistoryId === item._id ? (
|
<Box flex={'1 0 0'} w={0} className="textEllipsis" color={'myGray.1000'}>
|
||||||
<Input
|
{item.title}
|
||||||
ref={inputRef}
|
</Box>
|
||||||
value={editedTitle}
|
<Box color={'myGray.400'} fontSize={'sm'}>
|
||||||
onChange={(e: { target: { value: React.SetStateAction<string> } }) =>
|
{formatTimeToChatTime(item.updateTime)}
|
||||||
setEditedTitle(e.target.value)
|
</Box>
|
||||||
}
|
|
||||||
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}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box className="textEllipsis" mt={1} fontSize={'sm'} color={'myGray.500'}>
|
<Box className="textEllipsis" mt={1} fontSize={'sm'} color={'myGray.500'}>
|
||||||
{item.latestChat || '……'}
|
{item.latestChat || '……'}
|
||||||
@@ -312,6 +228,19 @@ const PcSliderBar = ({
|
|||||||
<Box ref={ContextMenuRef}></Box>
|
<Box ref={ContextMenuRef}></Box>
|
||||||
<Menu isOpen>
|
<Menu isOpen>
|
||||||
<MenuList>
|
<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
|
<MenuItem
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -329,15 +258,31 @@ const PcSliderBar = ({
|
|||||||
删除记录
|
删除记录
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
onClick={() => {
|
onClick={() =>
|
||||||
try {
|
onOpenModal({
|
||||||
onEditClick(contextMenuData.history._id, contextMenuData.history.title);
|
defaultVal: contextMenuData.history.title,
|
||||||
} catch (error) {
|
onSuccess: async (val: string) => {
|
||||||
console.log(error);
|
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>
|
||||||
<MenuItem onClick={() => onclickExportChat('html')}>导出HTML格式</MenuItem>
|
<MenuItem onClick={() => onclickExportChat('html')}>导出HTML格式</MenuItem>
|
||||||
<MenuItem onClick={() => onclickExportChat('pdf')}>导出PDF格式</MenuItem>
|
<MenuItem onClick={() => onclickExportChat('pdf')}>导出PDF格式</MenuItem>
|
||||||
@@ -346,7 +291,7 @@ const PcSliderBar = ({
|
|||||||
</Menu>
|
</Menu>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
<EditTitleModal />
|
||||||
<Loading loading={isLoadingHistory} fixed={false} />
|
<Loading loading={isLoadingHistory} fixed={false} />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
91
src/pages/chat/components/useEditTitle.tsx
Normal file
91
src/pages/chat/components/useEditTitle.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
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
|
||||||
|
};
|
||||||
|
};
|
@@ -32,13 +32,16 @@ const ChatSchema = new Schema({
|
|||||||
default: '历史记录'
|
default: '历史记录'
|
||||||
},
|
},
|
||||||
customTitle: {
|
customTitle: {
|
||||||
type: Boolean,
|
type: String,
|
||||||
default: false
|
default: ''
|
||||||
},
|
},
|
||||||
latestChat: {
|
latestChat: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
top: {
|
||||||
|
type: Boolean
|
||||||
|
},
|
||||||
content: {
|
content: {
|
||||||
type: [
|
type: [
|
||||||
{
|
{
|
||||||
|
@@ -6,13 +6,14 @@ export const connectPg = async () => {
|
|||||||
return global.pgClient;
|
return global.pgClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxLink = Number(process.env.VECTOR_MAX_PROCESS || 10);
|
||||||
global.pgClient = new Pool({
|
global.pgClient = new Pool({
|
||||||
host: process.env.PG_HOST,
|
host: process.env.PG_HOST,
|
||||||
port: process.env.PG_PORT ? +process.env.PG_PORT : 5432,
|
port: process.env.PG_PORT ? +process.env.PG_PORT : 5432,
|
||||||
user: process.env.PG_USER,
|
user: process.env.PG_USER,
|
||||||
password: process.env.PG_PASSWORD,
|
password: process.env.PG_PASSWORD,
|
||||||
database: process.env.PG_DB_NAME,
|
database: process.env.PG_DB_NAME,
|
||||||
max: 20,
|
max: maxLink,
|
||||||
idleTimeoutMillis: 60000,
|
idleTimeoutMillis: 60000,
|
||||||
connectionTimeoutMillis: 20000
|
connectionTimeoutMillis: 20000
|
||||||
});
|
});
|
||||||
|
1
src/types/chat.d.ts
vendored
1
src/types/chat.d.ts
vendored
@@ -33,6 +33,7 @@ export type HistoryItemType = {
|
|||||||
modelId: string;
|
modelId: string;
|
||||||
title: string;
|
title: string;
|
||||||
latestChat: string;
|
latestChat: string;
|
||||||
|
top: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShareChatHistoryItemType = {
|
export type ShareChatHistoryItemType = {
|
||||||
|
6
src/types/mongoSchema.d.ts
vendored
6
src/types/mongoSchema.d.ts
vendored
@@ -85,10 +85,12 @@ export interface ChatSchema {
|
|||||||
userId: string;
|
userId: string;
|
||||||
modelId: string;
|
modelId: string;
|
||||||
expiredTime: number;
|
expiredTime: number;
|
||||||
loadAmount: number;
|
|
||||||
updateTime: Date;
|
updateTime: Date;
|
||||||
|
title: string;
|
||||||
|
customTitle: string;
|
||||||
|
latestChat: string;
|
||||||
|
top: boolean;
|
||||||
content: ChatItemType[];
|
content: ChatItemType[];
|
||||||
customTitle: Boolean;
|
|
||||||
}
|
}
|
||||||
export interface ChatPopulate extends ChatSchema {
|
export interface ChatPopulate extends ChatSchema {
|
||||||
userId: UserModelSchema;
|
userId: UserModelSchema;
|
||||||
|
Reference in New Issue
Block a user