299 lines
7.6 KiB
TypeScript

import { LarkService } from "@egg/net-tool"
import { appMap } from "../../constant/appMap"
import { RespMessage } from "../../constant/message"
import prisma from "../../prisma"
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.Data} ctx - 请求上下文
* @param {string} timeScope - 时间范围
* @param {any} subscription - 订阅信息
* @returns {Promise<void>}
*/
const genReport = async (
ctx: Context.Data,
timeScope: "daily" | "weekly",
subscription: {
id: bigint
chat_id: string
robot_id: string
initiator: string
}
) => {
const { logger, requestId, larkCard } = ctx
const cardGender = larkCard.child("groupAgent")
try {
const { chat_id: chatId, robot_id: robotId } = subscription
// 获取接口信息
const appInfo = appMap[robotId]
if (!appInfo) {
logger.error(`Failed to get app info for ${robotId}`)
return
}
// 组织接口
const larkService = new LarkService({
appId: appInfo.app_id,
appSecret: appInfo.app_secret,
requestId,
})
// 获取时间范围
const { startTime, endTime } = getTimeRange(timeScope)
// 计时开始
const processStart = Date.now()
// 获取聊天记录
const chatHistory = await getChatHistory(
{ larkService, logger } as Context.Data,
{
chatId,
startTime,
endTime,
}
)
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}`
)
// 发送卡片消息
await larkService.message.sendCard2Chat(
chatId,
cardGender.genCard("autoReport", {
llmRes,
timeScope: timeScope === "daily" ? "今日日报" : "本周周报",
})
)
// 记录发送的卡片
await prisma.chat_agent_message_log.create({
data: {
subscription_id: subscription.id,
initiator: subscription.initiator,
langfuse_link: `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(""))
const { logger } = ctx
try {
// 获取全部需要自动总结的群组
const subscriptionList =
await prisma.chat_agent_summary_subscription.findMany({
where: {
terminator: "",
},
})
if (subscriptionList.length === 0) {
logger.info("No group needs to be summarized")
return
}
// 一个一个群组的总结,避免触发频率限制
for (const subscription of subscriptionList) {
await genReport(ctx, timeScope, subscription)
}
} catch (e: any) {
logger.error(`Auto summary error: ${e.message}`)
}
}
/**
* 立即生成日报或周报(测试用)
* @param {Context.Data} ctx - 请求上下文
* @returns {Promise<void>}
*/
const gen4Test = async (ctx: Context.Data, 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 subscription = await prisma.chat_agent_summary_subscription.findFirst(
{
where: {
chat_id: chatId,
terminator: "",
},
}
)
// 没有订阅信息
if (!subscription) {
logger.error(`No subscription found for chat ${chatId}`)
await larkService.message.sendCard2Chat(
chatId,
larkCard.genErrorCard("本群未订阅日报、周报")
)
return
}
// 总结
await genReport(ctx, timeScope, subscription)
} catch (error: any) {
logger.error(`Failed to summarize chat ${chatId}: ${error.message}`)
}
}
/**
* 注册消息总结的订阅
* @returns
*/
const subscribe = async ({
app,
larkService,
logger,
larkBody,
larkCard,
}: Context.Data) => {
try {
const cardGender = larkCard.child("groupAgent")
// 判断是否有 chatId 和 userId
if (!larkBody.chatId || !larkBody.userId) {
logger.error(`chatId or userId is empty`)
return
}
// 先查询是否已经存在订阅
const subscription = await prisma.chat_agent_summary_subscription.findFirst(
{
where: {
chat_id: larkBody.chatId,
terminator: "",
},
}
)
// 如果已经存在订阅,则返回已经注册过了
if (subscription) {
logger.info(`chatId: ${larkBody.chatId} has been registered`)
// 发送已经注册过的消息
await larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genSuccessCard(RespMessage.hasRegistered)
)
return
}
// 注册订阅
await prisma.chat_agent_summary_subscription.create({
data: {
chat_id: larkBody.chatId,
robot_id: app,
initiator: larkBody.userId,
},
})
// 发送成功消息
await larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genSuccessCard(RespMessage.registerSuccess)
)
} catch (e: any) {
logger.error(`Subscribe error: ${e.message}`)
}
}
/**
* 取消消息总结的订阅
* @returns
*/
const unsubscribe = async ({
logger,
larkBody,
larkService,
larkCard,
}: Context.Data) => {
try {
const cardGender = larkCard.child("groupAgent")
// 判断是否有 chatId 和 userId
if (!larkBody.chatId || !larkBody.userId) {
logger.error(`chatId or userId is empty`)
return
}
// 查找现有的订阅
const subscription = await prisma.chat_agent_summary_subscription.findFirst(
{
where: {
chat_id: larkBody.chatId,
terminator: "",
},
}
)
// 如果没有找到订阅,则返回错误
if (!subscription) {
logger.info(`chatId: ${larkBody.chatId} has not been registered`)
// 发送已经取消订阅的消息
await larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genSuccessCard(RespMessage.cancelSuccess)
)
return
}
// 更新订阅,设置终止者和终止时间
await prisma.chat_agent_summary_subscription.update({
where: {
id: subscription.id,
},
data: {
terminator: larkBody.userId,
},
})
// 发送成功消息
await larkService.message.sendCard2Chat(
larkBody.chatId,
cardGender.genSuccessCard(RespMessage.cancelSuccess)
)
} catch (e: any) {
logger.error(`Unsubscribe error: ${e.message}`)
}
}
const report = {
genReport,
genAllReport,
gen4Test,
subscribe,
unsubscribe,
}
export default report