mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-21 11:43:56 +00:00
feat:自定义历史聊天标题 (#41)
* feat:自定义历史聊天标题 * Update chat.ts * perf:自定义聊天标题 * feat: google auth * perf:将修改标题移入右键菜单 * perf:updatetitle --------- Co-authored-by: archer <545436317@qq.com>
This commit is contained in:
@@ -45,6 +45,15 @@ export const updateHistoryQuote = (params: {
|
||||
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);
|
||||
|
||||
/**
|
||||
* create a shareChat
|
||||
*/
|
||||
|
@@ -78,13 +78,14 @@ export async function saveChat({
|
||||
return _id;
|
||||
} else {
|
||||
// 已经有记录,追加入库
|
||||
const chat = await Chat.findById(chatId);
|
||||
await Chat.findByIdAndUpdate(chatId, {
|
||||
$push: {
|
||||
content: {
|
||||
$each: content
|
||||
}
|
||||
},
|
||||
title: content[0].value.slice(0, 20),
|
||||
...(chat && !chat.customTitle ? { title: content[0].value.slice(0, 20) } : {}),
|
||||
latestChat: content[1].value,
|
||||
updateTime: new Date()
|
||||
});
|
||||
|
36
src/pages/api/chat/updateChatHistoryTitle.ts
Normal file
36
src/pages/api/chat/updateChatHistoryTitle.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
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 default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { chatId, modelId, newTitle } = req.body as {
|
||||
chatId: '' | string;
|
||||
modelId: '' | string;
|
||||
newTitle: string;
|
||||
};
|
||||
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
await authModel({ modelId, userId, authOwner: false });
|
||||
|
||||
await Chat.findByIdAndUpdate(
|
||||
chatId,
|
||||
{
|
||||
title: newTitle,
|
||||
customTitle: true
|
||||
} // 自定义标题}
|
||||
);
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,7 +1,8 @@
|
||||
import React, { useCallback, useRef, useState, useMemo } from 'react';
|
||||
import React, { useCallback, useRef, useState, useMemo, useEffect } from 'react';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import { AddIcon, EditIcon, CheckIcon, CloseIcon, DeleteIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
Input,
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
@@ -16,15 +17,65 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useLoading } from '@/hooks/useLoading';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { formatTimeToChatTime } from '@/utils/tools';
|
||||
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
|
||||
};
|
||||
};
|
||||
|
||||
const PcSliderBar = ({
|
||||
onclickDelHistory,
|
||||
onclickExportChat
|
||||
@@ -32,6 +83,17 @@ 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 { modelId = '', chatId = '' } = router.query as { modelId: string; chatId: string };
|
||||
const theme = useTheme();
|
||||
@@ -170,12 +232,46 @@ const PcSliderBar = ({
|
||||
<ChatIcon fontSize={'16px'} color={'myGray.500'} />
|
||||
<Box flex={'1 0 0'} w={0} ml={3}>
|
||||
<Flex alignItems={'center'}>
|
||||
<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>
|
||||
{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}
|
||||
</Flex>
|
||||
<Box className="textEllipsis" mt={1} fontSize={'sm'} color={'myGray.500'}>
|
||||
{item.latestChat || '……'}
|
||||
@@ -232,6 +328,17 @@ const PcSliderBar = ({
|
||||
>
|
||||
删除记录
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
try {
|
||||
onEditClick(contextMenuData.history._id, contextMenuData.history.title);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
编辑标题
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => onclickExportChat('html')}>导出HTML格式</MenuItem>
|
||||
<MenuItem onClick={() => onclickExportChat('pdf')}>导出PDF格式</MenuItem>
|
||||
<MenuItem onClick={() => onclickExportChat('md')}>导出Markdown格式</MenuItem>
|
||||
|
@@ -31,6 +31,10 @@ const ChatSchema = new Schema({
|
||||
type: String,
|
||||
default: '历史记录'
|
||||
},
|
||||
customTitle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
latestChat: {
|
||||
type: String,
|
||||
default: ''
|
||||
|
1
src/types/mongoSchema.d.ts
vendored
1
src/types/mongoSchema.d.ts
vendored
@@ -88,6 +88,7 @@ export interface ChatSchema {
|
||||
loadAmount: number;
|
||||
updateTime: Date;
|
||||
content: ChatItemType[];
|
||||
customTitle: Boolean;
|
||||
}
|
||||
export interface ChatPopulate extends ChatSchema {
|
||||
userId: UserModelSchema;
|
||||
|
Reference in New Issue
Block a user