import { RespMessage } from "../../constant/message" import { Context } from "../../types" import { genContextManually } from "../../utils/genContext" import llm from "../../utils/llm" import { getTimeRange } from "../../utils/time" import getChatHistory from "./chatHistory" /** * 总结指定聊天记录 * @param {Context} ctx - 请求上下文 * @param {string} timeScope - 时间范围 * @param {any} subscription - 订阅信息 * @returns {Promise} */ const genSummary = async ( ctx: Context, timeScope: "daily" | "weekly", trigger: "auto" | "manual" ) => { const { db, logger, requestId, larkCard, larkService, appInfo, larkService: { message }, } = ctx logger.info("生成摘要", { timeScope, trigger }) const cardGender = larkCard.child("groupAgent") try { if (trigger === "manual") { await message.updateOrReply( cardGender.genPendingCard("总结中,请稍等...") ) } // 获取群聊信息 const chat = await db.chat.getByCtx(ctx) if (!chat) { throw new Error("Failed to get chat info") } const { chatId } = chat // 获取时间范围 const { startTime, endTime } = getTimeRange(timeScope) // 计时开始 const processStart = Date.now() // 获取聊天记录 const { messages: chatHistory } = await getChatHistory( { larkService, logger } as Context, { chatId, startTime, endTime, excludeMentions: [appInfo.appName], } ) if (chatHistory.length === 0) { if (trigger === "manual") { throw new Error("No message in chat") } else { logger.info(`No message in chat ${chatId}`) return } } // 使用大模型总结消息 const llmRes = await llm.invoke( `${timeScope}Summary`, { chatHistory: JSON.stringify(chatHistory), time: new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai", }), }, requestId ) // 计时结束 const processEnd = Date.now() const processingTime = ((processEnd - processStart) / 1000).toFixed(2) logger.info( `LLM takes time: ${processingTime}s, see detail: http://langfuse.ai.srv/project/cm1j2tkj9001gukrgdvc1swuw/sessions/${requestId}` ) // 生成卡片内容 const cardContent = cardGender.genCard("autoReport", { llmRes, timeScope: timeScope === "daily" ? "今日日报" : "本周周报", }) // 发送卡片消息,手动触发时回复原消息 if (trigger === "manual") { await message.updateOrReply(cardContent) } else { await larkService.message.sendCard2Chat(chatId, cardContent) } // 记录总结日志 await db.grpSumLog.create({ chat: chat.id, content: JSON.stringify(cardContent), langfuseLink: `http://langfuse.ai.srv/project/cm1j2tkj9001gukrgdvc1swuw/sessions/${requestId}`, }) } catch (error: any) { logger.error(`Failed to summarize chat: ${error.message}`) const errorCard = cardGender.genErrorCard( `${RespMessage.summaryFailed}: ${error.message}` ) // 手动触发时回复原消息 if (trigger === "manual") { await message.updateOrReply(errorCard) } // 自动触发发送给自己的订阅群 else { await larkService.message.sendCard2Chat(appInfo.errChatId, errorCard) } } } /** * 自动总结聊天记录 * @returns {Promise} */ const genAllReport = async (timeScope: "daily" | "weekly" = "daily") => { const ctx = await genContextManually() const { logger, db } = ctx logger.info("生成所有群聊总结", { timeScope }) try { // 获取所有需要自动总结的群组 const chatList = await db.chat.getNeedSummaryChats("all") logger.debug(`chatList: ${JSON.stringify(chatList)}`) if (!chatList || chatList.length === 0) { logger.info(`No chat need to summarize`) return } // 总结 for (const chat of chatList) { const newCtx = await genContextManually() newCtx.larkBody.chatId = chat.chatId let scope = "daily" as "daily" | "weekly" if (timeScope === "weekly" && chat.weeklySummary) { scope = "weekly" } await genSummary(newCtx, scope, "auto") } } catch (e: any) { logger.error(`Auto summary error: ${e.message}`) } } /** * 立即生成日报或周报(测试用) * @param {Context} ctx - 请求上下文 * @returns {Promise} */ const gen4Test = async (ctx: Context, timeScope: "daily" | "weekly") => { const { logger, larkBody: { chatId }, } = ctx try { logger.info("手动生成日报或者周报", { timeScope }) // 总结 await genSummary(ctx, timeScope, "manual") } catch (error: any) { logger.error(`Failed to summarize chat ${chatId}: ${error.message}`) } } /** * 设置订阅 * @param {Context} ctx - 请求上下文 * @param {string} timeScope - 订阅时间范围 * @param {boolean} value - 订阅值 * @returns {Promise} */ const setSubscription = async ( ctx: Context, timeScope: "daily" | "weekly", value: boolean ) => { const { db, larkService: { message }, logger, larkBody, larkCard, } = ctx const cardGender = larkCard.child("groupAgent") const sendErrorMsg = (msg: string) => message.updateOrReply( cardGender.genErrorCard( `${ value ? RespMessage.registerFailed : RespMessage.cancelFailed }: ${msg}` ) ) try { const { chatId } = larkBody if (!chatId) { throw new Error("Invalid chatId") } // 获取群组信息 const chat = await db.chat.getByCtx(ctx) if (!chat) { throw new Error("Failed to get chat info") } let hasUpdate = false // 更新订阅信息, 如果订阅信息没有变化则不更新 if (chat[`${timeScope}Summary`] !== value) { logger.info("value is different, update subscription") const res = await db.chat.update(chat.id, { [`${timeScope}Summary`]: value, }) if (!res) { throw new Error("Failed to update subscription") } hasUpdate = true } let msg = "" if (!hasUpdate && value) { msg = timeScope === "daily" ? RespMessage.hasRegisteredDaily : RespMessage.hasRegisteredWeekly } else if (!value) { msg = timeScope === "daily" ? RespMessage.cancelDailySuccess : RespMessage.cancelWeeklySuccess } else { msg = timeScope === "daily" ? RespMessage.registerDailySuccess : RespMessage.registerWeeklySuccess } // 发送成功消息 await message.updateOrReply(cardGender.genSuccessCard(msg)) } catch (e: any) { logger.error(`Subscribe error: ${e.message}`) await sendErrorMsg(e.message) } } const report = { genSummary, genAllReport, gen4Test, setSubscription, } export default report