258 lines
6.8 KiB
TypeScript

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<void>}
*/
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<void>}
*/
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<void>}
*/
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<void>}
*/
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