347 lines
9.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { LarkService } from "@egg/net-tool"
import { APP_MAP } from "../../constant/config"
import { RespMessage } from "../../constant/message"
import db from "../../db"
import { GrpSumSubWithApp } from "../../db/grpSumSub"
import { Context } from "../../types"
import genContext 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 genReport = async (
ctx: Context,
timeScope: "daily" | "weekly",
subscription: GrpSumSubWithApp
) => {
const { logger, requestId, larkCard } = ctx
const cardGender = larkCard.child("groupAgent")
try {
const {
chatId,
expand: {
app: { appId, appSecret, appName },
},
} = subscription
// 组织接口
const larkService = new LarkService({
appId,
appSecret,
requestId,
})
// 获取时间范围
const { startTime, endTime } = getTimeRange(timeScope)
// 计时开始
const processStart = Date.now()
// 获取聊天记录
const { messages: chatHistory } = await getChatHistory(
{ larkService, logger } as Context,
{
chatId,
startTime,
endTime,
excludeMentions: [appName],
}
)
if (chatHistory.length === 0) {
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" ? "今日日报" : "本周周报",
})
// 发送卡片消息
await larkService.message.sendCard2Chat(chatId, cardContent)
// 记录总结日志
await db.grpSumLog.create({
subscription: subscription.id,
content: JSON.stringify(cardContent),
langfuseLink: `http://langfuse.ai.srv/project/cm1j2tkj9001gukrgdvc1swuw/sessions/${requestId}`,
})
} catch (error: any) {
logger.error(
`Failed to summarize chat ${subscription.chat_id}: ${error.message}`
)
}
}
/**
* 自动总结聊天记录
* @returns {Promise<void>}
*/
const genAllReport = async (timeScope: "daily" | "weekly" = "daily") => {
const ctx = await genContext(new Request("https://baidu.com"))
const { logger } = ctx
try {
// 获取全部需要自动总结的群组
let subList = await db.grpSumSub.getAll(
`terminator = ""${timeScope === "daily" ? ' && timeScope = "daily"' : ""}`
)
// 没有需要总结的群组
if (!subList || subList.length === 0) {
logger.info("No group needs to be summarized")
return
}
// 如果是周五获取了需要日报和周报的订阅根据chatId过滤掉需要周报的日报订阅
if (timeScope === "weekly") {
const dailySubList = subList.filter((sub) => sub.timeScope === "daily")
const weeklySubList = subList.filter((sub) => sub.timeScope === "weekly")
// 过滤掉需要周报的日报订阅
subList = dailySubList
.filter(
(dailySub) =>
!weeklySubList.find(
(weeklySub) => weeklySub.chatId === dailySub.chatId
)
)
.concat(weeklySubList)
}
// 一个一个群组的总结,避免触发频率限制
for (const sub of subList) {
await genReport(ctx, sub.timeScope, sub)
}
} 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,
larkCard,
larkService,
larkBody: { chatId },
} = ctx
try {
logger.info(`timeScope: ${timeScope}`)
// 获取需要总结的chatId
if (!chatId) {
logger.error("Invalid request body")
return
}
// 获取订阅信息
const sub = await db.grpSumSub.getByFilter(
`terminator = "" && chatId = "${chatId}" && timeScope = "${timeScope}"`
)
// 没有订阅信息
if (!sub) {
logger.error(`No subscription found for chat ${chatId}`)
await larkService.message.sendCard2Chat(
chatId,
larkCard.genErrorCard(
`本群未订阅${timeScope === "daily" ? "日报" : "周报"}`
)
)
return
}
// 总结
await genReport(ctx, timeScope, sub)
} catch (error: any) {
logger.error(`Failed to summarize chat ${chatId}: ${error.message}`)
}
}
/**
* 注册消息总结的订阅
* @returns
*/
const subscribe = async (
{ app, larkService, logger, larkBody, larkCard }: Context,
timeScope: "daily" | "weekly"
) => {
const cardGender = larkCard.child("groupAgent")
const sendErrorMsg = () =>
larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genErrorCard(RespMessage.registerFailed)
)
try {
// 判断是否有 chatId 和 userId
if (!larkBody.chatId || !larkBody.userId) {
logger.error(`chatId or userId is empty`)
return
}
// 获取用户信息
const user = await db.user.getByCtx({ larkBody, larkService } as Context)
if (!user) {
logger.error(`Failed to get user info`)
await sendErrorMsg()
return
}
// 先查询是否已经存在订阅
const sub = await db.grpSumSub.getByFilter(
`terminator = "" && chatId = "${larkBody.chatId} && timeScope = "${timeScope}"`
)
if (sub) {
logger.info(
`chatId: ${larkBody.chatId} has been registered, timeScope: ${timeScope}`
)
// 发送已经注册过了的消息
await larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genSuccessCard(
timeScope === "daily"
? RespMessage.hasRegisteredDaily
: RespMessage.hasRegisteredWeekly
)
)
return
}
// 注册订阅
const createRes = await db.grpSumSub.create({
app: APP_MAP[app].id,
initiator: user.id,
terminator: "",
chatId: larkBody.chatId,
timeScope,
})
if (!createRes) {
logger.error(
`Failed to register chatId: ${larkBody.chatId}, timeScope: ${timeScope}`
)
await sendErrorMsg()
return
}
// 发送成功消息
await larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genSuccessCard(
timeScope === "daily"
? RespMessage.registerDailySuccess
: RespMessage.registerWeeklySuccess
)
)
} catch (e: any) {
logger.error(`Subscribe error: ${e.message}`)
await sendErrorMsg()
}
}
/**
* 取消消息总结的订阅
* @returns
*/
const unsubscribe = async (
{ logger, larkBody, larkService, larkCard }: Context,
timeScope: "daily" | "weekly"
) => {
const cardGender = larkCard.child("groupAgent")
const sendErrorMsg = () =>
larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genErrorCard(RespMessage.cancelFailed)
)
try {
// 判断是否有 chatId 和 userId
if (!larkBody.chatId || !larkBody.userId) {
logger.error(`chatId or userId is empty`)
return
}
// 获取用户信息
const user = await db.user.getByCtx({ larkBody, larkService } as Context)
if (!user) {
logger.error(`Failed to get user info`)
await sendErrorMsg()
return
}
// 先查询是否已经存在订阅
const sub = await db.grpSumSub.getByFilter(
`terminator = "" && chatId = "${larkBody.chatId} && timeScope = "${timeScope}"`
)
if (!sub) {
logger.info(
`chatId: ${larkBody.chatId} has not been registered, timeScope: ${timeScope}`
)
// 发送未注册的消息
await larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genSuccessCard(
timeScope === "daily"
? RespMessage.cancelDailySuccess
: RespMessage.cancelWeeklySuccess
)
)
return
}
// 更新订阅
const updateRes = await db.grpSumSub.update(sub.id, {
terminator: user.id,
})
if (!updateRes) {
logger.error(
`Failed to cancel chatId: ${larkBody.chatId}, timeScope: ${timeScope}`
)
await sendErrorMsg()
return
}
// 发送成功消息
await larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genSuccessCard(
timeScope === "daily"
? RespMessage.cancelDailySuccess
: RespMessage.cancelWeeklySuccess
)
)
} catch (e: any) {
logger.error(`Unsubscribe error: ${e.message}`)
await sendErrorMsg()
}
}
const report = {
genReport,
genAllReport,
gen4Test,
subscribe,
unsubscribe,
}
export default report