export chat detail (#5454)

* export chat detail

* fix

* key name
This commit is contained in:
heheer
2025-08-14 16:25:40 +08:00
committed by GitHub
parent 8f3424cea1
commit c5cabd0efc
5 changed files with 115 additions and 14 deletions

View File

@@ -124,6 +124,7 @@
"logs_good_feedback": "Like", "logs_good_feedback": "Like",
"logs_key_config": "Field Configuration", "logs_key_config": "Field Configuration",
"logs_keys_annotatedCount": "Annotated Answer Count", "logs_keys_annotatedCount": "Annotated Answer Count",
"logs_keys_chatDetails": "Conversation details",
"logs_keys_createdTime": "Created Time", "logs_keys_createdTime": "Created Time",
"logs_keys_customFeedback": "Custom Feedback", "logs_keys_customFeedback": "Custom Feedback",
"logs_keys_errorCount": "Error Count", "logs_keys_errorCount": "Error Count",

View File

@@ -125,6 +125,7 @@
"logs_good_feedback": "点赞", "logs_good_feedback": "点赞",
"logs_key_config": "字段配置", "logs_key_config": "字段配置",
"logs_keys_annotatedCount": "标注答案数量", "logs_keys_annotatedCount": "标注答案数量",
"logs_keys_chatDetails": "对话详情",
"logs_keys_createdTime": "创建时间", "logs_keys_createdTime": "创建时间",
"logs_keys_customFeedback": "自定义反馈", "logs_keys_customFeedback": "自定义反馈",
"logs_keys_errorCount": "报错数量", "logs_keys_errorCount": "报错数量",

View File

@@ -124,6 +124,7 @@
"logs_good_feedback": "點贊", "logs_good_feedback": "點贊",
"logs_key_config": "字段配置", "logs_key_config": "字段配置",
"logs_keys_annotatedCount": "標記答案數量", "logs_keys_annotatedCount": "標記答案數量",
"logs_keys_chatDetails": "對話詳情",
"logs_keys_createdTime": "建立時間", "logs_keys_createdTime": "建立時間",
"logs_keys_customFeedback": "自訂回饋", "logs_keys_customFeedback": "自訂回饋",
"logs_keys_errorCount": "錯誤數量", "logs_keys_errorCount": "錯誤數量",

View File

@@ -153,7 +153,7 @@ const LogTable = ({
tmbIds: isSelectAllTmb ? undefined : selectTmbIds, tmbIds: isSelectAllTmb ? undefined : selectTmbIds,
chatSearch, chatSearch,
title: headerTitle, title: headerTitle + ',' + t('app:logs_keys_chatDetails'),
logKeys: enabledKeys, logKeys: enabledKeys,
sourcesMap: Object.fromEntries( sourcesMap: Object.fromEntries(
Object.entries(ChatSourceMap).map(([key, config]) => [ Object.entries(ChatSourceMap).map(([key, config]) => [

View File

@@ -14,11 +14,16 @@ import { Types } from 'mongoose';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { ChatItemCollectionName } from '@fastgpt/service/core/chat/chatItemSchema'; import { ChatItemCollectionName } from '@fastgpt/service/core/chat/chatItemSchema';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema'; import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
import type { ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; import { type ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { AppLogKeysEnum } from '@fastgpt/global/core/app/logs/constants'; import { AppLogKeysEnum } from '@fastgpt/global/core/app/logs/constants';
import { sanitizeCsvField } from '@fastgpt/service/common/file/csv'; import { sanitizeCsvField } from '@fastgpt/service/common/file/csv';
import { AppReadChatLogPerVal } from '@fastgpt/global/support/permission/app/constant'; import { AppReadChatLogPerVal } from '@fastgpt/global/support/permission/app/constant';
const formatJsonString = (data: any) => {
if (data == null) return '';
return JSON.stringify(data).replace(/\n/g, '\\n');
};
export type ExportChatLogsBody = GetAppChatLogsProps & { export type ExportChatLogsBody = GetAppChatLogsProps & {
title: string; title: string;
sourcesMap: Record<string, { label: string }>; sourcesMap: Record<string, { label: string }>;
@@ -178,6 +183,35 @@ async function handler(req: ApiRequestProps<ExportChatLogsBody, {}>, res: NextAp
as: 'chatItemsData' as: 'chatItemsData'
} }
}, },
{
$lookup: {
from: ChatItemCollectionName,
let: { chatId: '$chatId' },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: ['$appId', new Types.ObjectId(appId)] },
{ $eq: ['$chatId', '$$chatId'] }
]
}
}
},
{ $sort: { _id: 1 } },
{
$project: {
value: 1,
userGoodFeedback: 1,
userBadFeedback: 1,
customFeedbacks: 1,
adminFeedback: 1
}
}
],
as: 'chatitems'
}
},
{ {
$addFields: { $addFields: {
messageCount: { $ifNull: [{ $arrayElemAt: ['$chatItemsData.messageCount', 0] }, 0] }, messageCount: { $ifNull: [{ $arrayElemAt: ['$chatItemsData.messageCount', 0] }, 0] },
@@ -206,7 +240,45 @@ async function handler(req: ApiRequestProps<ExportChatLogsBody, {}>, res: NextAp
] ]
}, },
errorCount: { $ifNull: [{ $arrayElemAt: ['$chatItemsData.errorCount', 0] }, 0] }, errorCount: { $ifNull: [{ $arrayElemAt: ['$chatItemsData.errorCount', 0] }, 0] },
totalPoints: { $ifNull: [{ $arrayElemAt: ['$chatItemsData.totalPoints', 0] }, 0] } totalPoints: { $ifNull: [{ $arrayElemAt: ['$chatItemsData.totalPoints', 0] }, 0] },
userGoodFeedbackItems: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $ifNull: ['$$item.userGoodFeedback', false] }
}
},
userBadFeedbackItems: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $ifNull: ['$$item.userBadFeedback', false] }
}
},
customFeedbackItems: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $gt: [{ $size: { $ifNull: ['$$item.customFeedbacks', []] } }, 0] }
}
},
markItems: {
$filter: {
input: '$chatitems',
as: 'item',
cond: { $ifNull: ['$$item.adminFeedback', false] }
}
},
chatDetails: {
$map: {
input: { $slice: ['$chatitems', -1000] },
as: 'item',
in: {
id: '$$item._id',
value: '$$item.value'
}
}
}
} }
}, },
{ {
@@ -227,11 +299,18 @@ async function handler(req: ApiRequestProps<ExportChatLogsBody, {}>, res: NextAp
errorCount: 1, errorCount: 1,
totalPoints: 1, totalPoints: 1,
outLinkUid: 1, outLinkUid: 1,
tmbId: 1 tmbId: 1,
userGoodFeedbackItems: 1,
userBadFeedbackItems: 1,
customFeedbackItems: 1,
markItems: 1,
chatDetails: 1
} }
} }
], ],
{ ...readFromSecondary } {
...readFromSecondary
}
).cursor({ batchSize: 1000 }); ).cursor({ batchSize: 1000 });
const write = responseWriteController({ const write = responseWriteController({
@@ -254,7 +333,7 @@ async function handler(req: ApiRequestProps<ExportChatLogsBody, {}>, res: NextAp
? doc.outLinkUid ? doc.outLinkUid
: teamMemberWithContact.find((member) => String(member.memberId) === String(doc.tmbId))?.name; : teamMemberWithContact.find((member) => String(member.memberId) === String(doc.tmbId))?.name;
const valueMap: Partial<Record<AppLogKeysEnum, () => any>> = { const valueMap: Record<string, () => any> = {
[AppLogKeysEnum.SOURCE]: () => source, [AppLogKeysEnum.SOURCE]: () => source,
[AppLogKeysEnum.CREATED_TIME]: () => createdTime, [AppLogKeysEnum.CREATED_TIME]: () => createdTime,
[AppLogKeysEnum.LAST_CONVERSATION_TIME]: () => lastConversationTime, [AppLogKeysEnum.LAST_CONVERSATION_TIME]: () => lastConversationTime,
@@ -263,21 +342,40 @@ async function handler(req: ApiRequestProps<ExportChatLogsBody, {}>, res: NextAp
[AppLogKeysEnum.SESSION_ID]: () => doc.id || '-', [AppLogKeysEnum.SESSION_ID]: () => doc.id || '-',
[AppLogKeysEnum.MESSAGE_COUNT]: () => doc.messageCount, [AppLogKeysEnum.MESSAGE_COUNT]: () => doc.messageCount,
[AppLogKeysEnum.FEEDBACK]: () => { [AppLogKeysEnum.FEEDBACK]: () => {
const good = doc.userGoodFeedbackCount || 0; const goodItems = (doc.userGoodFeedbackItems || []).map((item: any) => ({
const bad = doc.userBadFeedbackCount || 0; chatItemId: item._id,
return `good: ${good}, bad: ${bad}`; feedback: item.userGoodFeedback
}));
const badItems = (doc.userBadFeedbackItems || []).map((item: any) => ({
chatItemId: item._id,
feedback: item.userBadFeedback
}));
return formatJsonString({ good: goodItems, bad: badItems });
},
[AppLogKeysEnum.CUSTOM_FEEDBACK]: () => {
const customItems = (doc.customFeedbackItems || []).map((item: any) => ({
chatItemId: item._id,
feedbacks: item.customFeedbacks || []
}));
return formatJsonString(customItems);
},
[AppLogKeysEnum.ANNOTATED_COUNT]: () => {
const markItems = (doc.markItems || []).map((item: any) => ({
chatItemId: item._id,
feedback: item.adminFeedback
}));
return formatJsonString(markItems);
}, },
[AppLogKeysEnum.CUSTOM_FEEDBACK]: () => doc.customFeedbacksCount || 0,
[AppLogKeysEnum.ANNOTATED_COUNT]: () => doc.markCount || 0,
[AppLogKeysEnum.RESPONSE_TIME]: () => [AppLogKeysEnum.RESPONSE_TIME]: () =>
doc.averageResponseTime ? Number(doc.averageResponseTime).toFixed(2) : 0, doc.averageResponseTime ? Number(doc.averageResponseTime).toFixed(2) : 0,
[AppLogKeysEnum.ERROR_COUNT]: () => doc.errorCount || 0, [AppLogKeysEnum.ERROR_COUNT]: () => doc.errorCount || 0,
[AppLogKeysEnum.POINTS]: () => (doc.totalPoints ? Number(doc.totalPoints).toFixed(2) : 0) [AppLogKeysEnum.POINTS]: () => (doc.totalPoints ? Number(doc.totalPoints).toFixed(2) : 0),
chatDetails: () => formatJsonString(doc.chatDetails || [])
}; };
const row = logKeys const row = [...logKeys, 'chatDetails']
.map((key) => { .map((key) => {
const getter = valueMap[key as AppLogKeysEnum]; const getter = valueMap[key];
const val = getter ? getter() : ''; const val = getter ? getter() : '';
return sanitizeCsvField(val ?? ''); return sanitizeCsvField(val ?? '');
}) })