diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index e1227afbe..793c9e8f3 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -124,6 +124,7 @@ "logs_good_feedback": "Like", "logs_key_config": "Field Configuration", "logs_keys_annotatedCount": "Annotated Answer Count", + "logs_keys_chatDetails": "Conversation details", "logs_keys_createdTime": "Created Time", "logs_keys_customFeedback": "Custom Feedback", "logs_keys_errorCount": "Error Count", diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json index a5e45ec21..c30b73f69 100644 --- a/packages/web/i18n/zh-CN/app.json +++ b/packages/web/i18n/zh-CN/app.json @@ -125,6 +125,7 @@ "logs_good_feedback": "点赞", "logs_key_config": "字段配置", "logs_keys_annotatedCount": "标注答案数量", + "logs_keys_chatDetails": "对话详情", "logs_keys_createdTime": "创建时间", "logs_keys_customFeedback": "自定义反馈", "logs_keys_errorCount": "报错数量", diff --git a/packages/web/i18n/zh-Hant/app.json b/packages/web/i18n/zh-Hant/app.json index 496648e23..89d4fa6ea 100644 --- a/packages/web/i18n/zh-Hant/app.json +++ b/packages/web/i18n/zh-Hant/app.json @@ -124,6 +124,7 @@ "logs_good_feedback": "點贊", "logs_key_config": "字段配置", "logs_keys_annotatedCount": "標記答案數量", + "logs_keys_chatDetails": "對話詳情", "logs_keys_createdTime": "建立時間", "logs_keys_customFeedback": "自訂回饋", "logs_keys_errorCount": "錯誤數量", diff --git a/projects/app/src/pageComponents/app/detail/Logs/LogTable.tsx b/projects/app/src/pageComponents/app/detail/Logs/LogTable.tsx index 8d780039e..647fbdeae 100644 --- a/projects/app/src/pageComponents/app/detail/Logs/LogTable.tsx +++ b/projects/app/src/pageComponents/app/detail/Logs/LogTable.tsx @@ -153,7 +153,7 @@ const LogTable = ({ tmbIds: isSelectAllTmb ? undefined : selectTmbIds, chatSearch, - title: headerTitle, + title: headerTitle + ',' + t('app:logs_keys_chatDetails'), logKeys: enabledKeys, sourcesMap: Object.fromEntries( Object.entries(ChatSourceMap).map(([key, config]) => [ diff --git a/projects/app/src/pages/api/core/app/exportChatLogs.ts b/projects/app/src/pages/api/core/app/exportChatLogs.ts index 71069abe6..841a44a71 100644 --- a/projects/app/src/pages/api/core/app/exportChatLogs.ts +++ b/projects/app/src/pages/api/core/app/exportChatLogs.ts @@ -14,11 +14,16 @@ import { Types } from 'mongoose'; import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; import { ChatItemCollectionName } from '@fastgpt/service/core/chat/chatItemSchema'; 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 { sanitizeCsvField } from '@fastgpt/service/common/file/csv'; 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 & { title: string; sourcesMap: Record; @@ -178,6 +183,35 @@ async function handler(req: ApiRequestProps, res: NextAp 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: { messageCount: { $ifNull: [{ $arrayElemAt: ['$chatItemsData.messageCount', 0] }, 0] }, @@ -206,7 +240,45 @@ async function handler(req: ApiRequestProps, res: NextAp ] }, 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, res: NextAp errorCount: 1, totalPoints: 1, outLinkUid: 1, - tmbId: 1 + tmbId: 1, + userGoodFeedbackItems: 1, + userBadFeedbackItems: 1, + customFeedbackItems: 1, + markItems: 1, + chatDetails: 1 } } ], - { ...readFromSecondary } + { + ...readFromSecondary + } ).cursor({ batchSize: 1000 }); const write = responseWriteController({ @@ -254,7 +333,7 @@ async function handler(req: ApiRequestProps, res: NextAp ? doc.outLinkUid : teamMemberWithContact.find((member) => String(member.memberId) === String(doc.tmbId))?.name; - const valueMap: Partial any>> = { + const valueMap: Record any> = { [AppLogKeysEnum.SOURCE]: () => source, [AppLogKeysEnum.CREATED_TIME]: () => createdTime, [AppLogKeysEnum.LAST_CONVERSATION_TIME]: () => lastConversationTime, @@ -263,21 +342,40 @@ async function handler(req: ApiRequestProps, res: NextAp [AppLogKeysEnum.SESSION_ID]: () => doc.id || '-', [AppLogKeysEnum.MESSAGE_COUNT]: () => doc.messageCount, [AppLogKeysEnum.FEEDBACK]: () => { - const good = doc.userGoodFeedbackCount || 0; - const bad = doc.userBadFeedbackCount || 0; - return `good: ${good}, bad: ${bad}`; + const goodItems = (doc.userGoodFeedbackItems || []).map((item: any) => ({ + chatItemId: item._id, + 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]: () => doc.averageResponseTime ? Number(doc.averageResponseTime).toFixed(2) : 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) => { - const getter = valueMap[key as AppLogKeysEnum]; + const getter = valueMap[key]; const val = getter ? getter() : ''; return sanitizeCsvField(val ?? ''); })