chat log soft delete (#6110)

* chat log soft delete

* perf: history api

* add history test

* Update packages/web/i18n/en/app.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* zod parse error

* fix: ts

---------

Co-authored-by: archer <545436317@qq.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
heheer
2025-12-18 10:17:10 +08:00
committed by GitHub
parent 463b02d127
commit 09b9fa517b
48 changed files with 1830 additions and 251 deletions

View File

@@ -1,4 +1,4 @@
import type { OutLinkChatAuthProps } from '../../support/permission/chat.d';
import type { OutLinkChatAuthProps } from '../../support/permission/chat';
export type preUploadImgProps = OutLinkChatAuthProps & {
// expiredTime?: Date;

View File

@@ -37,7 +37,7 @@ export const AppLogKeysEnumMap = {
};
export const DefaultAppLogKeys = [
{ key: AppLogKeysEnum.SOURCE, enable: true },
{ key: AppLogKeysEnum.SOURCE, enable: false },
{ key: AppLogKeysEnum.USER, enable: true },
{ key: AppLogKeysEnum.TITLE, enable: true },
{ key: AppLogKeysEnum.SESSION_ID, enable: false },

View File

@@ -51,6 +51,8 @@ export type ChatSchemaType = {
hasBadFeedback?: boolean;
hasUnreadGoodFeedback?: boolean;
hasUnreadBadFeedback?: boolean;
deleteTime?: Date | null;
};
export type ChatWithAppSchema = Omit<ChatSchemaType, 'appId'> & {
@@ -197,7 +199,7 @@ export type HistoryItemType = {
};
export type ChatHistoryItemType = HistoryItemType & {
appId: string;
top: boolean;
top?: boolean;
};
/* ------- response data ------------ */

View File

@@ -1,7 +1,17 @@
import { z } from 'zod';
export const PaginationSchema = z.object({
pageSize: z.union([z.number(), z.string()]),
pageSize: z.union([z.number(), z.string()]).optional(),
offset: z.union([z.number(), z.string()]).optional(),
pageNum: z.union([z.number(), z.string()]).optional()
});
export type PaginationType = z.infer<typeof PaginationSchema>;
export const PaginationResponseSchema = <T extends z.ZodTypeAny>(itemSchema: T) =>
z.object({
total: z.number().optional().default(0),
list: z.array(itemSchema).optional().default([])
});
export type PaginationResponseType<T extends z.ZodTypeAny> = z.infer<
ReturnType<typeof PaginationResponseSchema<T>>
>;

View File

@@ -34,7 +34,7 @@ export const ChatLogItemSchema = z.object({
title: z.string().optional().meta({ example: '用户对话', description: '对话标题' }),
customTitle: z.string().nullish().meta({ example: '自定义标题', description: '自定义对话标题' }),
source: z.enum(ChatSourceEnum).meta({ example: ChatSourceEnum.api, description: '对话来源' }),
sourceName: z.string().optional().meta({ example: 'API调用', description: '来源名称' }),
sourceName: z.string().nullish().meta({ example: 'API调用', description: '来源名称' }),
updateTime: z.date().meta({ example: '2024-01-01T00:30:00.000Z', description: '更新时间' }),
createTime: z.date().meta({ example: '2024-01-01T00:00:00.000Z', description: '创建时间' }),
messageCount: z.int().nullish().meta({ example: 10, description: '消息数量' }),
@@ -50,7 +50,7 @@ export const ChatLogItemSchema = z.object({
totalPoints: z.number().nullish().meta({ example: 150.5, description: '总积分消耗' }),
outLinkUid: z.string().nullish().meta({ example: 'outLink123', description: '外链用户 ID' }),
tmbId: z.string().nullish().meta({ example: 'tmb123', description: '团队成员 ID' }),
sourceMember: SourceMemberSchema.optional().meta({ description: '来源成员信息' }),
sourceMember: SourceMemberSchema.nullish().meta({ description: '来源成员信息' }),
versionName: z.string().nullish().meta({ example: 'v1.0.0', description: '版本名称' }),
region: z.string().nullish().meta({ example: '中国', description: '区域' })
});

View File

@@ -105,11 +105,11 @@ export const UpdateUserFeedbackBodySchema = z.object({
example: 'data123',
description: '消息数据 ID'
}),
userGoodFeedback: z.string().optional().nullable().meta({
userGoodFeedback: z.string().nullish().meta({
example: '回答很好',
description: '用户好评反馈内容'
}),
userBadFeedback: z.string().optional().nullable().meta({
userBadFeedback: z.string().nullish().meta({
example: '回答不准确',
description: '用户差评反馈内容'
})

View File

@@ -0,0 +1,73 @@
import z from 'zod';
import { ObjectIdSchema } from '../../../../common/type/mongo';
import { OutLinkChatAuthSchema } from '../../../../support/permission/chat';
import { ChatSourceEnum } from '../../../../core/chat/constants';
import { PaginationSchema, PaginationResponseSchema } from '../../../api';
// Get chat histories schema
export const GetHistoriesBodySchema = PaginationSchema.and(
OutLinkChatAuthSchema.and(
z.object({
appId: ObjectIdSchema.optional().describe('应用ID'),
source: z.enum(ChatSourceEnum).optional().describe('对话来源'),
startCreateTime: z.string().optional().describe('创建时间开始'),
endCreateTime: z.string().optional().describe('创建时间结束'),
startUpdateTime: z.string().optional().describe('更新时间开始'),
endUpdateTime: z.string().optional().describe('更新时间结束')
})
)
);
export type GetHistoriesBodyType = z.infer<typeof GetHistoriesBodySchema>;
export const GetHistoriesResponseSchema = PaginationResponseSchema(
z.object({
chatId: z.string(),
updateTime: z.date(),
appId: z.string(),
customTitle: z.string().optional(),
title: z.string(),
top: z.boolean().optional()
})
);
export type GetHistoriesResponseType = z.infer<typeof GetHistoriesResponseSchema>;
// Update chat history schema
export const UpdateHistoryBodySchema = OutLinkChatAuthSchema.and(
z.object({
appId: ObjectIdSchema.describe('应用ID'),
chatId: z.string().min(1).describe('对话ID'),
title: z.string().optional().describe('标题'),
customTitle: z.string().optional().describe('自定义标题'),
top: z.boolean().optional().describe('是否置顶')
})
);
export type UpdateHistoryBodyType = z.infer<typeof UpdateHistoryBodySchema>;
// Delete single chat history schema
export const DelChatHistorySchema = OutLinkChatAuthSchema.and(
z.object({
appId: ObjectIdSchema.describe('应用ID'),
chatId: z.string().min(1).describe('对话ID')
})
);
export type DelChatHistoryType = z.infer<typeof DelChatHistorySchema>;
// Clear all chat histories schema
export const ClearChatHistoriesSchema = OutLinkChatAuthSchema.and(
z.object({
appId: ObjectIdSchema.describe('应用ID')
})
);
export type ClearChatHistoriesType = z.infer<typeof ClearChatHistoriesSchema>;
// Batch delete chat histories schema (for log manager)
export const ChatBatchDeleteBodySchema = z.object({
appId: ObjectIdSchema,
chatIds: z
.array(z.string().min(1))
.min(1)
.meta({
description: '对话ID列表',
example: ['chat_123456', 'chat_789012']
})
});
export type ChatBatchDeleteBodyType = z.infer<typeof ChatBatchDeleteBodySchema>;

View File

@@ -0,0 +1,109 @@
import type { OpenAPIPath } from '../../../type';
import { TagsMap } from '../../../tag';
import {
GetHistoriesBodySchema,
GetHistoriesResponseSchema,
UpdateHistoryBodySchema,
ChatBatchDeleteBodySchema,
DelChatHistorySchema,
ClearChatHistoriesSchema
} from './api';
export const ChatHistoryPath: OpenAPIPath = {
'/core/chat/history/getHistories': {
post: {
summary: '获取对话历史列表',
description: '分页获取指定应用的对话历史记录',
tags: [TagsMap.chatHistory],
requestBody: {
content: {
'application/json': {
schema: GetHistoriesBodySchema
}
}
},
responses: {
200: {
description: '成功获取对话历史列表',
content: {
'application/json': {
schema: GetHistoriesResponseSchema
}
}
}
}
}
},
'/core/chat/history/updateHistory': {
put: {
summary: '修改对话历史',
description: '修改对话历史的标题、自定义标题或置顶状态',
tags: [TagsMap.chatHistory],
requestBody: {
content: {
'application/json': {
schema: UpdateHistoryBodySchema
}
}
},
responses: {
200: {
description: '成功修改对话历史'
}
}
}
},
'/core/chat/history/delHistory': {
delete: {
summary: '删除单个对话历史',
description: '软删除指定的单个对话记录',
tags: [TagsMap.chatHistory],
requestBody: {
content: {
'application/json': {
schema: DelChatHistorySchema
}
}
},
responses: {
200: {
description: '成功删除对话'
}
}
}
},
'/core/chat/history/clearHistories': {
delete: {
summary: '清空应用对话历史',
description: '清空指定应用的所有对话记录(软删除)',
tags: [TagsMap.chatHistory],
requestParams: {
query: ClearChatHistoriesSchema
},
responses: {
200: {
description: '成功清空对话历史'
}
}
}
},
'/core/chat/history/batchDelete': {
post: {
summary: '批量删除对话历史',
description: '批量删除指定应用的多个对话记录(真实删除),需应用日志权限。',
tags: [TagsMap.chatHistory],
requestBody: {
content: {
'application/json': {
schema: ChatBatchDeleteBodySchema
}
}
},
responses: {
200: {
description: '成功删除对话'
}
}
}
}
};

View File

@@ -2,6 +2,7 @@ import type { OpenAPIPath } from '../../type';
import { ChatSettingPath } from './setting';
import { ChatFavouriteAppPath } from './favourite/index';
import { ChatFeedbackPath } from './feedback/index';
import { ChatHistoryPath } from './history/index';
import { z } from 'zod';
import { CreatePostPresignedUrlResultSchema } from '../../../../service/common/s3/type';
import { PresignChatFileGetUrlSchema, PresignChatFilePostUrlSchema } from '../../../core/chat/api';
@@ -11,6 +12,7 @@ export const ChatPath: OpenAPIPath = {
...ChatSettingPath,
...ChatFavouriteAppPath,
...ChatFeedbackPath,
...ChatHistoryPath,
'/core/chat/presignChatFileGetUrl': {
post: {

View File

@@ -30,7 +30,7 @@ export const openAPIDocument = createDocument({
},
{
name: '对话管理',
tags: [TagsMap.chatSetting, TagsMap.chatPage, TagsMap.chatFeedback]
tags: [TagsMap.chatHistory, TagsMap.chatPage, TagsMap.chatFeedback, TagsMap.chatSetting]
},
{
name: '插件系统',

View File

@@ -4,7 +4,8 @@ export const TagsMap = {
appLog: 'Agent 日志',
// Chat - home
chatPage: '对话页',
chatPage: '对话页操作',
chatHistory: '对话历史管理',
chatSetting: '门户页配置',
chatFeedback: '对话反馈',

View File

@@ -1,9 +0,0 @@
type ShareChatAuthProps = {
shareId?: string;
outLinkUid?: string;
};
type TeamChatAuthProps = {
teamId?: string;
teamToken?: string;
};
export type OutLinkChatAuthProps = ShareChatAuthProps & TeamChatAuthProps;

View File

@@ -0,0 +1,16 @@
import z from 'zod';
export const ShareChatAuthSchema = z.object({
shareId: z.string().optional().describe('分享链接ID'),
outLinkUid: z.string().optional().describe('外链用户ID')
});
export type ShareChatAuthProps = z.infer<typeof ShareChatAuthSchema>;
export const TeamChatAuthSchema = z.object({
teamId: z.string().optional().describe('团队ID'),
teamToken: z.string().optional().describe('团队Token')
});
export type TeamChatAuthProps = z.infer<typeof TeamChatAuthSchema>;
export const OutLinkChatAuthSchema = ShareChatAuthSchema.and(TeamChatAuthSchema);
export type OutLinkChatAuthProps = z.infer<typeof OutLinkChatAuthSchema>;

View File

@@ -38,9 +38,7 @@ export type UserType = {
export const SourceMemberSchema = z.object({
name: z.string().meta({ example: '张三', description: '成员名称' }),
avatar: z
.string()
.meta({ example: 'https://cloud.fastgpt.cn/avatar.png', description: '成员头像' }),
avatar: z.string().nullish().meta({ description: '成员头像' }),
status: z
.enum(TeamMemberStatusEnum)
.meta({ example: TeamMemberStatusEnum.active, description: '成员状态' })