feat: 周报和日报迁移至本项目
This commit is contained in:
parent
c3b827787a
commit
754cecf35e
@ -12,4 +12,6 @@ LANGFUSE_BASE_URL=
|
|||||||
# Map of apps such as '{"michat": { "app_id": "123", "app_secret": "456", "app_name": "Mi Chat" }}'
|
# Map of apps such as '{"michat": { "app_id": "123", "app_secret": "456", "app_name": "Mi Chat" }}'
|
||||||
LARK_APP_MAP=
|
LARK_APP_MAP=
|
||||||
|
|
||||||
ATTACH_APP_SECRET=
|
ATTACH_APP_SECRET=
|
||||||
|
|
||||||
|
DATABASE_URL=
|
3
cache.ts
3
cache.ts
@ -1,3 +0,0 @@
|
|||||||
import { parseJsonString } from "@egg/hooks"
|
|
||||||
|
|
||||||
console.log(parseJsonString(Bun.env.LARK_APP_MAP, []), [])
|
|
13
constant/appMap.ts
Normal file
13
constant/appMap.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { parseJsonString } from "@egg/hooks"
|
||||||
|
|
||||||
|
export interface AppInfo {
|
||||||
|
app_id: string
|
||||||
|
app_secret: string
|
||||||
|
app_name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有应用信息
|
||||||
|
export const appMap = parseJsonString(process.env.LARK_APP_MAP, {}) as Record<
|
||||||
|
string,
|
||||||
|
AppInfo
|
||||||
|
>
|
@ -149,6 +149,29 @@ const resultReport = {
|
|||||||
header: cardComponent.successHeader,
|
header: cardComponent.successHeader,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const autoReport = {
|
||||||
|
config: {
|
||||||
|
update_multi: true,
|
||||||
|
},
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
tag: "markdown",
|
||||||
|
content: "${llmRes}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "hr",
|
||||||
|
},
|
||||||
|
cardComponent.commonNote,
|
||||||
|
],
|
||||||
|
header: {
|
||||||
|
template: "turquoise",
|
||||||
|
title: {
|
||||||
|
content: "${xIcon} ${xName} ${timeScope}",
|
||||||
|
tag: "plain_text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
export const functionOptionList = [
|
export const functionOptionList = [
|
||||||
{
|
{
|
||||||
id: "summary-qwen-72b-instruct-int4",
|
id: "summary-qwen-72b-instruct-int4",
|
||||||
@ -161,6 +184,7 @@ const cardMap = {
|
|||||||
timeScopeSelector,
|
timeScopeSelector,
|
||||||
resultReport,
|
resultReport,
|
||||||
groupSelector,
|
groupSelector,
|
||||||
|
autoReport,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default cardMap
|
export default cardMap
|
||||||
|
@ -5,7 +5,7 @@ const functionMap = {
|
|||||||
xIcon: "🍳",
|
xIcon: "🍳",
|
||||||
},
|
},
|
||||||
groupAgent: {
|
groupAgent: {
|
||||||
xName: "Group Agent",
|
xName: "Mi Chat群聊助手",
|
||||||
xAuthor: "AI创新应用组",
|
xAuthor: "AI创新应用组",
|
||||||
xIcon: "🔥",
|
xIcon: "🔥",
|
||||||
},
|
},
|
||||||
|
5
constant/message.ts
Normal file
5
constant/message.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export enum RespMessage {
|
||||||
|
hasRegistered = "本群已订阅日报,周报",
|
||||||
|
registerSuccess = "周报、日报订阅成功",
|
||||||
|
cancelSuccess = "周报、日报订阅取消成功",
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import { parseJsonString } from "@egg/hooks"
|
import { parseJsonString } from "@egg/hooks"
|
||||||
import { LarkEvent } from "@egg/lark-msg-tool"
|
import { LarkEvent } from "@egg/lark-msg-tool"
|
||||||
|
import { Lark } from "@egg/net-tool"
|
||||||
|
|
||||||
import { Context, LarkServer } from "../../../types"
|
import { Context } from "../../types"
|
||||||
|
|
||||||
interface Message {
|
interface Message {
|
||||||
user: string
|
user: string
|
||||||
@ -163,7 +164,7 @@ const getChatHistory = async (
|
|||||||
* @param chat - 聊天消息数据
|
* @param chat - 聊天消息数据
|
||||||
* @returns 文本消息内容
|
* @returns 文本消息内容
|
||||||
*/
|
*/
|
||||||
const getText = (chat: LarkServer.MessageData) => {
|
const getText = (chat: Lark.MessageData) => {
|
||||||
let { text } = parseJsonString(chat.body.content, { text: "" }) as {
|
let { text } = parseJsonString(chat.body.content, { text: "" }) as {
|
||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
@ -187,7 +188,7 @@ const getChatHistory = async (
|
|||||||
* @param chat - 聊天消息数据
|
* @param chat - 聊天消息数据
|
||||||
* @returns post 消息内容
|
* @returns post 消息内容
|
||||||
*/
|
*/
|
||||||
const getPost = (chat: LarkServer.MessageData) => {
|
const getPost = (chat: Lark.MessageData) => {
|
||||||
const content = parseJsonString(chat.body.content, null)
|
const content = parseJsonString(chat.body.content, null)
|
||||||
if (!content) return ""
|
if (!content) return ""
|
||||||
return extractTextFromJson(content)
|
return extractTextFromJson(content)
|
@ -1,8 +1,8 @@
|
|||||||
import { genCardOptions, LarkEvent } from "@egg/lark-msg-tool"
|
import { genCardOptions, LarkEvent } from "@egg/lark-msg-tool"
|
||||||
|
|
||||||
import { functionOptionList } from "../../../constant/card"
|
import { functionOptionList } from "../../constant/card"
|
||||||
import { Context, LarkServer } from "../../../types"
|
import { Context, LarkServer } from "../../types"
|
||||||
import llm from "../../../utils/llm"
|
import llm from "../../utils/llm"
|
||||||
import getChatHistory from "./chatHistory"
|
import getChatHistory from "./chatHistory"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -290,7 +290,7 @@ const manageEventMsg = async (ctx: Context.Data) => {
|
|||||||
// 私聊发送正常的群组选择器
|
// 私聊发送正常的群组选择器
|
||||||
if (chatType === "p2p") {
|
if (chatType === "p2p") {
|
||||||
logger.info("Send group selector to p2p chat")
|
logger.info("Send group selector to p2p chat")
|
||||||
sendCard(genGroupSelector(ctx, innerList, { mentions }))
|
await sendCard(genGroupSelector(ctx, innerList, { mentions }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 如果是群聊,获取群聊名称并发送功能
|
// 如果是群聊,获取群聊名称并发送功能
|
||||||
@ -298,7 +298,7 @@ const manageEventMsg = async (ctx: Context.Data) => {
|
|||||||
data: { name: chatName },
|
data: { name: chatName },
|
||||||
} = await larkService.chat.getChatInfo(rawChatId)
|
} = await larkService.chat.getChatInfo(rawChatId)
|
||||||
logger.info(`Send function selector to group chat: ${chatName}`)
|
logger.info(`Send function selector to group chat: ${chatName}`)
|
||||||
sendCard(genFunctionSelector(ctx, { chatName, mentions }))
|
await sendCard(genFunctionSelector(ctx, { chatName, mentions }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,11 +396,17 @@ const manageActionMsg = async (ctx: Context.Data) => {
|
|||||||
const groupAgent = async (ctx: Context.Data) => {
|
const groupAgent = async (ctx: Context.Data) => {
|
||||||
const {
|
const {
|
||||||
larkBody: { isEvent, isAction },
|
larkBody: { isEvent, isAction },
|
||||||
|
logger,
|
||||||
} = ctx
|
} = ctx
|
||||||
// 如果是Event,则解析自然语言并发送对应的卡片
|
try {
|
||||||
if (isEvent) return manageEventMsg(ctx)
|
// 如果是Event,则解析自然语言并发送对应的卡片
|
||||||
// 如果是Action,则取出用户选的值并判断是否需要继续发送表单卡片或者开始大模型推理
|
if (isEvent) return await manageEventMsg(ctx)
|
||||||
if (isAction) return manageActionMsg(ctx)
|
// 如果是Action,则取出用户选的值并判断是否需要继续发送表单卡片或者开始大模型推理
|
||||||
|
if (isAction) return await manageActionMsg(ctx)
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error(`Group agent error: ${e.message}`)
|
||||||
|
return ctx.larkCard.child("groupAgent").genErrorCard("Group agent error")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default groupAgent
|
export default groupAgent
|
190
controller/groupAgent/report.ts
Normal file
190
controller/groupAgent/report.ts
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
import { LarkService } from "@egg/net-tool"
|
||||||
|
|
||||||
|
import { appMap } from "../../constant/appMap"
|
||||||
|
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"
|
||||||
|
|
||||||
|
interface Subscription {
|
||||||
|
id: bigint
|
||||||
|
chat_id: string
|
||||||
|
robot_id: string
|
||||||
|
initiator: string
|
||||||
|
terminator: string
|
||||||
|
created_at: Date
|
||||||
|
updated_at: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 总结指定聊天记录
|
||||||
|
* @param {Context.Data} ctx - 请求上下文
|
||||||
|
* @param {string} timeScope - 时间范围
|
||||||
|
* @param {Subscription} subscription - 订阅信息
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const genReport = async (
|
||||||
|
ctx: Context.Data,
|
||||||
|
timeScope: "daily" | "weekly",
|
||||||
|
subscription: Subscription
|
||||||
|
) => {
|
||||||
|
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}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const report = {
|
||||||
|
genReport,
|
||||||
|
genAllReport,
|
||||||
|
gen4Test,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default report
|
120
controller/groupAgent/subscription.ts
Normal file
120
controller/groupAgent/subscription.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { RespMessage } from "../../constant/message"
|
||||||
|
import prisma from "../../prisma"
|
||||||
|
import { Context } from "../../types"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册消息总结的订阅
|
||||||
|
* @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 subscription = {
|
||||||
|
subscribe,
|
||||||
|
unsubscribe,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default subscription
|
@ -2,10 +2,16 @@ FROM micr.cloud.mioffice.cn/zhaoyingbo/bun:alpine-cn
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
COPY package*.json ./
|
||||||
|
|
||||||
|
COPY bun.lockb ./
|
||||||
|
|
||||||
RUN bun install
|
RUN bun install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN bunx prisma generate
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD ["bun", "run", "start"]
|
CMD ["bun", "start"]
|
16
docker/mysql/docker-compose.yml
Normal file
16
docker/mysql/docker-compose.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: micr.cloud.mioffice.cn/zhaoyingbo/mysql:8.0
|
||||||
|
container_name: mysql
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: rootpassword
|
||||||
|
MYSQL_DATABASE: testdb
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
volumes:
|
||||||
|
- mysql-data:/var/lib/mysql
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql-data:
|
12
index.ts
12
index.ts
@ -1,5 +1,6 @@
|
|||||||
import logger from "@egg/logger"
|
import logger from "@egg/logger"
|
||||||
|
|
||||||
|
import prisma from "./prisma"
|
||||||
import { manageBotReq } from "./routes/bot"
|
import { manageBotReq } from "./routes/bot"
|
||||||
import { manageMessageReq } from "./routes/message"
|
import { manageMessageReq } from "./routes/message"
|
||||||
import { manageMicroAppReq } from "./routes/microApp"
|
import { manageMicroAppReq } from "./routes/microApp"
|
||||||
@ -43,7 +44,18 @@ const server = Bun.serve({
|
|||||||
return genResp.serverError(error.message || "server error")
|
return genResp.serverError(error.message || "server error")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
error(error) {
|
||||||
|
logger.error(`Error: ${error}`)
|
||||||
|
logger.error(`Stack: ${error.stack}`)
|
||||||
|
return new Response("Internal Error", { status: 500 })
|
||||||
|
},
|
||||||
port: 3000,
|
port: 3000,
|
||||||
})
|
})
|
||||||
|
|
||||||
logger.info(`Listening on ${server.hostname}:${server.port}`)
|
logger.info(`Listening on ${server.hostname}:${server.port}`)
|
||||||
|
|
||||||
|
// 关闭数据库连接
|
||||||
|
process.on("SIGINT", async () => {
|
||||||
|
await prisma.$disconnect()
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
@ -37,12 +37,13 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@egg/hooks": "^1.2.0",
|
"@egg/hooks": "^1.2.0",
|
||||||
"@egg/lark-msg-tool": "^1.15.1",
|
"@egg/lark-msg-tool": "^1.17.0",
|
||||||
"@egg/logger": "^1.6.0",
|
"@egg/logger": "^1.6.0",
|
||||||
"@egg/net-tool": "^1.16.1",
|
"@egg/net-tool": "^1.16.1",
|
||||||
"@egg/path-tool": "^1.4.1",
|
"@egg/path-tool": "^1.4.1",
|
||||||
"@langchain/core": "^0.3.19",
|
"@langchain/core": "^0.3.19",
|
||||||
"@langchain/openai": "^0.3.14",
|
"@langchain/openai": "^0.3.14",
|
||||||
|
"@prisma/client": "^5.22.0",
|
||||||
"joi": "^17.13.3",
|
"joi": "^17.13.3",
|
||||||
"langfuse-langchain": "^3.31.0",
|
"langfuse-langchain": "^3.31.0",
|
||||||
"node-schedule": "^2.1.1",
|
"node-schedule": "^2.1.1",
|
||||||
|
5
prisma/index.ts
Normal file
5
prisma/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { PrismaClient } from "@prisma/client"
|
||||||
|
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
export default prisma
|
31
prisma/schema.prisma
Normal file
31
prisma/schema.prisma
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||||
|
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "mysql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
model chat_agent_summary_subscription {
|
||||||
|
id BigInt @id @default(autoincrement()) // 摘要订阅 ID
|
||||||
|
chat_id String @default("") // 关联的聊天 ID
|
||||||
|
robot_id String @default("") // 机器人 ID
|
||||||
|
initiator String @default("") // 发起者 ID
|
||||||
|
terminator String @default("") // 终止者 ID
|
||||||
|
created_at DateTime @default(now()) // 创建时间
|
||||||
|
updated_at DateTime @updatedAt // 更新时间
|
||||||
|
}
|
||||||
|
|
||||||
|
model chat_agent_message_log {
|
||||||
|
id BigInt @id @default(autoincrement()) // 消息日志 ID
|
||||||
|
subscription_id BigInt @default(0) // 关联的摘要订阅 ID
|
||||||
|
initiator String @default("") // 发起者 ID
|
||||||
|
langfuse_link String @default("") // Langfuse 日志
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
|
import groupAgent from "../../controller/groupAgent"
|
||||||
import { Context } from "../../types"
|
import { Context } from "../../types"
|
||||||
import groupAgent from "./groupAgent"
|
|
||||||
|
|
||||||
const GROUP_MAP = {
|
const GROUP_MAP = {
|
||||||
groupAgent,
|
groupAgent,
|
||||||
|
@ -1,24 +1,9 @@
|
|||||||
import { LarkBody } from "@egg/lark-msg-tool"
|
|
||||||
|
|
||||||
import tempMap from "../../constant/template"
|
import tempMap from "../../constant/template"
|
||||||
|
import groupAgent from "../../controller/groupAgent"
|
||||||
|
import report from "../../controller/groupAgent/report"
|
||||||
|
import subscription from "../../controller/groupAgent/subscription"
|
||||||
import { Context } from "../../types"
|
import { Context } from "../../types"
|
||||||
import createKVTemp from "../sheet/createKVTemp"
|
import createKVTemp from "../sheet/createKVTemp"
|
||||||
import groupAgent from "./groupAgent"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否为P2P或者群聊并且艾特了机器人
|
|
||||||
* @param larkBody
|
|
||||||
*/
|
|
||||||
const getIsP2pOrGroupAtBot = async (
|
|
||||||
larkBody: LarkBody,
|
|
||||||
appInfo: Context.APP_INFO
|
|
||||||
): Promise<boolean> => {
|
|
||||||
const isP2p = larkBody.chatType === "p2p"
|
|
||||||
const isAtBot = larkBody.mentions?.some?.(
|
|
||||||
(mention) => mention.name === appInfo.app_name
|
|
||||||
)
|
|
||||||
return Boolean(isP2p || isAtBot)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 过滤出非法消息,如果发表情包就直接发回去
|
* 过滤出非法消息,如果发表情包就直接发回去
|
||||||
@ -38,7 +23,7 @@ const filterIllegalMsg = async ({
|
|||||||
if (!chatId) return true
|
if (!chatId) return true
|
||||||
|
|
||||||
// 非私聊和群聊中艾特机器人的消息不处理
|
// 非私聊和群聊中艾特机器人的消息不处理
|
||||||
if (!(await getIsP2pOrGroupAtBot(larkBody, appInfo))) {
|
if (!larkBody.isP2P && !larkBody.isAtBot(appInfo.app_name)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +99,7 @@ const manageCMDMsg = (ctx: Context.Data) => {
|
|||||||
logger,
|
logger,
|
||||||
larkService,
|
larkService,
|
||||||
attachService,
|
attachService,
|
||||||
larkBody: { msgText, chatId, chatType },
|
larkBody: { msgText, chatId, isInGroup },
|
||||||
} = ctx
|
} = ctx
|
||||||
logger.info(`bot req text: ${msgText}`)
|
logger.info(`bot req text: ${msgText}`)
|
||||||
|
|
||||||
@ -126,29 +111,29 @@ const manageCMDMsg = (ctx: Context.Data) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 仅限群组功能
|
// 仅限群组功能
|
||||||
if (chatType === "group") {
|
if (isInGroup) {
|
||||||
// 注册群组
|
// 注册群组
|
||||||
if (msgText === "开启日报、周报") {
|
if (msgText === "开启日报、周报") {
|
||||||
logger.info(`bot command is register, chatId: ${chatId}`)
|
logger.info(`bot command is register, chatId: ${chatId}`)
|
||||||
attachService.groupAgent(app, body, "register")
|
subscription.subscribe(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 注销群组
|
// 注销群组
|
||||||
if (msgText === "关闭日报、周报") {
|
if (msgText === "关闭日报、周报") {
|
||||||
logger.info(`bot command is unregister, chatId: ${chatId}`)
|
logger.info(`bot command is unregister, chatId: ${chatId}`)
|
||||||
attachService.groupAgent(app, body, "unregister")
|
subscription.unsubscribe(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 立即发送日简报
|
// 立即发送日简报
|
||||||
if (msgText === "总结日报") {
|
if (msgText === "总结日报") {
|
||||||
logger.info(`bot command is summary, chatId: ${chatId}`)
|
logger.info(`bot command is summary, chatId: ${chatId}`)
|
||||||
attachService.groupAgent(app, body, "summary", "daily")
|
report.gen4Test(ctx, "daily")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 立即发送周简报
|
// 立即发送周简报
|
||||||
if (msgText === "总结周报") {
|
if (msgText === "总结周报") {
|
||||||
logger.info(`bot command is summary, chatId: ${chatId}`)
|
logger.info(`bot command is summary, chatId: ${chatId}`)
|
||||||
attachService.groupAgent(app, body, "summary", "weekly")
|
report.gen4Test(ctx, "weekly")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { stringifyJson } from "@egg/hooks"
|
import { stringifyJson } from "@egg/hooks"
|
||||||
import { LarkService } from "@egg/net-tool"
|
import { LarkService } from "@egg/net-tool"
|
||||||
|
|
||||||
|
import { appMap } from "../../constant/appMap"
|
||||||
import db from "../../db"
|
import db from "../../db"
|
||||||
import { Context, DB, LarkServer, MsgProxy } from "../../types"
|
import { Context, DB, LarkServer, MsgProxy } from "../../types"
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ const validateMessageReq = ({
|
|||||||
export const manageMessageReq = async (
|
export const manageMessageReq = async (
|
||||||
ctx: Context.Data
|
ctx: Context.Data
|
||||||
): Promise<Response> => {
|
): Promise<Response> => {
|
||||||
const { body: rawBody, genResp, appMap, requestId } = ctx
|
const { body: rawBody, genResp, requestId } = ctx
|
||||||
const body = rawBody as MsgProxy.Body
|
const body = rawBody as MsgProxy.Body
|
||||||
|
|
||||||
// 校验参数
|
// 校验参数
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { LarkService } from "@egg/net-tool"
|
import { LarkService } from "@egg/net-tool"
|
||||||
|
|
||||||
|
import { appMap } from "../../constant/appMap"
|
||||||
import { Context } from "../../types"
|
import { Context } from "../../types"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -8,7 +9,7 @@ import { Context } from "../../types"
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const manageLogin = async (ctx: Context.Data) => {
|
const manageLogin = async (ctx: Context.Data) => {
|
||||||
const { req, genResp, logger, appMap, requestId } = ctx
|
const { req, genResp, logger, requestId } = ctx
|
||||||
logger.info("micro app login")
|
logger.info("micro app login")
|
||||||
const url = new URL(req.url)
|
const url = new URL(req.url)
|
||||||
const code = url.searchParams.get("code")
|
const code = url.searchParams.get("code")
|
||||||
@ -52,7 +53,7 @@ const manageLogin = async (ctx: Context.Data) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const manageBatchUser = async (ctx: Context.Data) => {
|
const manageBatchUser = async (ctx: Context.Data) => {
|
||||||
const { body, genResp, logger, appMap, requestId } = ctx
|
const { body, genResp, logger, requestId } = ctx
|
||||||
logger.info("batch get user info")
|
logger.info("batch get user info")
|
||||||
if (!body) return genResp.badRequest("req body is empty")
|
if (!body) return genResp.badRequest("req body is empty")
|
||||||
const { user_ids, user_id_type, app_name } = body
|
const { user_ids, user_id_type, app_name } = body
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { LarkService } from "@egg/net-tool"
|
import { LarkService } from "@egg/net-tool"
|
||||||
import Joi from "joi"
|
import Joi from "joi"
|
||||||
|
|
||||||
|
import { appMap } from "../../constant/appMap"
|
||||||
import db from "../../db"
|
import db from "../../db"
|
||||||
import { Context } from "../../types"
|
import { Context } from "../../types"
|
||||||
import { SheetProxy } from "../../types/sheetProxy"
|
import { SheetProxy } from "../../types/sheetProxy"
|
||||||
@ -64,7 +65,7 @@ const validateSheetReq = async ({
|
|||||||
* @returns {Promise<Response>} 返回响应对象
|
* @returns {Promise<Response>} 返回响应对象
|
||||||
*/
|
*/
|
||||||
export const manageSheetReq = async (ctx: Context.Data): Promise<Response> => {
|
export const manageSheetReq = async (ctx: Context.Data): Promise<Response> => {
|
||||||
const { body: rawBody, genResp, appMap, requestId } = ctx
|
const { body: rawBody, genResp, requestId } = ctx
|
||||||
const body = rawBody as SheetProxy.InsertData
|
const body = rawBody as SheetProxy.InsertData
|
||||||
|
|
||||||
// 校验参数
|
// 校验参数
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
|
import schedule from "node-schedule"
|
||||||
|
|
||||||
|
import report from "../controller/groupAgent/report"
|
||||||
|
|
||||||
export const initSchedule = async () => {
|
export const initSchedule = async () => {
|
||||||
// // 定时任务,每15分钟刷新一次token
|
// 定时任务,每周一到周四晚上 8 点 0 分 0 秒执行
|
||||||
// schedule.scheduleJob("*/15 * * * *", resetAccessToken)
|
schedule.scheduleJob("0 20 * * 1-4", () => report.genAllReport("daily"))
|
||||||
// // 立即执行一次
|
|
||||||
// resetAccessToken()
|
// 定时任务,每周五晚上 8 点 0 分 0 秒执行
|
||||||
|
schedule.scheduleJob("0 20 * * 5", () => report.genAllReport("weekly"))
|
||||||
}
|
}
|
||||||
|
@ -41,25 +41,6 @@ class AttachService extends NetToolBase {
|
|||||||
await this.post(URL, body).catch(() => "")
|
await this.post(URL, body).catch(() => "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 代理MiChat事件
|
|
||||||
* @param {LarkEvent.Data} body - 事件数据。
|
|
||||||
* @returns {Promise<void>} 返回空。
|
|
||||||
*/
|
|
||||||
async groupAgent(
|
|
||||||
app: string,
|
|
||||||
body: LarkEvent.Data,
|
|
||||||
func: "register" | "unregister" | "summary",
|
|
||||||
timeScope?: "daily" | "weekly"
|
|
||||||
) {
|
|
||||||
const URL = `${this.hostMap[Bun.env.NODE_ENV!]}/chat_agent/${func}`
|
|
||||||
return this.post(URL, body, {
|
|
||||||
timeScope,
|
|
||||||
app,
|
|
||||||
secret: Bun.env.ATTACH_APP_SECRET,
|
|
||||||
}).catch(() => "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AttachService
|
export default AttachService
|
||||||
|
4
test/groupAgent/register.http
Normal file
4
test/groupAgent/register.http
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
POST http://localhost:3000/bot?app=egg HTTP/1.1
|
||||||
|
content-type: application/json
|
||||||
|
|
||||||
|
{"schema":"2.0","header":{"event_id":"c94518fbcb9d66cc93b4144cc69e4f0c","token":"tV9djUKSjzVnekV7xTg2Od06NFTcsBnj","create_time":"1732612280153","event_type":"im.message.receive_v1","tenant_key":"2ee61fe50f4f1657","app_id":"cli_a1eff35b43b89063"},"event":{"message":{"chat_id":"oc_8c789ce8f4ecc6695bb63ca6ec4c61ea","chat_type":"group","content":"{\"text\":\"@_user_1 开启日报、周报\"}","create_time":"1732612279943","mentions":[{"id":{"open_id":"ou_032f507d08f9a7f28b042fcd086daef5","union_id":"on_7111660fddd8302ce47bf1999147c011","user_id":""},"key":"@_user_1","name":"小煎蛋","tenant_key":"2ee61fe50f4f1657"}],"message_id":"om_4ab57cb6e889eeb81ca061b137238189","message_type":"text","update_time":"1732612279943"},"sender":{"sender_id":{"open_id":"ou_470ac13b8b50fc472d9d8ee71e03de26","union_id":"on_9dacc59a539023df8b168492f5e5433c","user_id":"zhaoyingbo"},"sender_type":"user","tenant_key":"2ee61fe50f4f1657"}}}
|
4
test/groupAgent/summary.http
Normal file
4
test/groupAgent/summary.http
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
POST http://localhost:3000/bot?app=egg HTTP/1.1
|
||||||
|
content-type: application/json
|
||||||
|
|
||||||
|
{"schema":"2.0","header":{"event_id":"ad5a706e2175e4bc539da53fd54b6fa0","token":"tV9djUKSjzVnekV7xTg2Od06NFTcsBnj","create_time":"1732619538646","event_type":"im.message.receive_v1","tenant_key":"2ee61fe50f4f1657","app_id":"cli_a1eff35b43b89063"},"event":{"message":{"chat_id":"oc_8c789ce8f4ecc6695bb63ca6ec4c61ea","chat_type":"group","content":"{\"text\":\"@_user_1 总结日报\"}","create_time":"1732619538450","mentions":[{"id":{"open_id":"ou_032f507d08f9a7f28b042fcd086daef5","union_id":"on_7111660fddd8302ce47bf1999147c011","user_id":""},"key":"@_user_1","name":"小煎蛋","tenant_key":"2ee61fe50f4f1657"}],"message_id":"om_988659d8dcb79a54a02016ea1884e3df","message_type":"text","update_time":"1732619538450"},"sender":{"sender_id":{"open_id":"ou_470ac13b8b50fc472d9d8ee71e03de26","union_id":"on_9dacc59a539023df8b168492f5e5433c","user_id":"zhaoyingbo"},"sender_type":"user","tenant_key":"2ee61fe50f4f1657"}}}
|
4
test/groupAgent/unregister.http
Normal file
4
test/groupAgent/unregister.http
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
POST http://localhost:3000/bot?app=egg HTTP/1.1
|
||||||
|
content-type: application/json
|
||||||
|
|
||||||
|
{"schema":"2.0","header":{"event_id":"6d212cfdfa9e5bc91e008eabef0c6288","token":"tV9djUKSjzVnekV7xTg2Od06NFTcsBnj","create_time":"1732613767405","event_type":"im.message.receive_v1","tenant_key":"2ee61fe50f4f1657","app_id":"cli_a1eff35b43b89063"},"event":{"message":{"chat_id":"oc_8c789ce8f4ecc6695bb63ca6ec4c61ea","chat_type":"group","content":"{\"text\":\"@_user_1 关闭日报、周报\"}","create_time":"1732613767202","mentions":[{"id":{"open_id":"ou_032f507d08f9a7f28b042fcd086daef5","union_id":"on_7111660fddd8302ce47bf1999147c011","user_id":""},"key":"@_user_1","name":"小煎蛋","tenant_key":"2ee61fe50f4f1657"}],"message_id":"om_344daf29eed184d808f52a73a1c2644d","message_type":"text","update_time":"1732613767202"},"sender":{"sender_id":{"open_id":"ou_470ac13b8b50fc472d9d8ee71e03de26","union_id":"on_9dacc59a539023df8b168492f5e5433c","user_id":"zhaoyingbo"},"sender_type":"user","tenant_key":"2ee61fe50f4f1657"}}}
|
@ -3,17 +3,13 @@ import { LarkService, NetTool } from "@egg/net-tool"
|
|||||||
import { PathCheckTool } from "@egg/path-tool"
|
import { PathCheckTool } from "@egg/path-tool"
|
||||||
import { Logger } from "winston"
|
import { Logger } from "winston"
|
||||||
|
|
||||||
|
import { AppInfo } from "../constant/appMap"
|
||||||
import cardMap from "../constant/card"
|
import cardMap from "../constant/card"
|
||||||
import functionMap from "../constant/function"
|
import functionMap from "../constant/function"
|
||||||
import tempMap from "../constant/template"
|
import tempMap from "../constant/template"
|
||||||
import { AttachService } from "../services"
|
import { AttachService } from "../services"
|
||||||
|
|
||||||
export namespace Context {
|
export namespace Context {
|
||||||
export interface APP_INFO {
|
|
||||||
app_id: string
|
|
||||||
app_secret: string
|
|
||||||
app_name: string
|
|
||||||
}
|
|
||||||
export interface Data {
|
export interface Data {
|
||||||
req: Request
|
req: Request
|
||||||
requestId: string
|
requestId: string
|
||||||
@ -28,7 +24,6 @@ export namespace Context {
|
|||||||
path: PathCheckTool
|
path: PathCheckTool
|
||||||
searchParams: URLSearchParams
|
searchParams: URLSearchParams
|
||||||
app: "michat" | "egg" | string
|
app: "michat" | "egg" | string
|
||||||
appInfo: APP_INFO
|
appInfo: AppInfo
|
||||||
appMap: Record<string, APP_INFO>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,16 @@
|
|||||||
import { parseJsonString } from "@egg/hooks"
|
|
||||||
import { LarkBody, LarkCard } from "@egg/lark-msg-tool"
|
import { LarkBody, LarkCard } from "@egg/lark-msg-tool"
|
||||||
import loggerIns from "@egg/logger"
|
import loggerIns from "@egg/logger"
|
||||||
import { LarkService, NetTool } from "@egg/net-tool"
|
import { LarkService, NetTool } from "@egg/net-tool"
|
||||||
import { PathCheckTool } from "@egg/path-tool"
|
import { PathCheckTool } from "@egg/path-tool"
|
||||||
import { v4 as uuid } from "uuid"
|
import { v4 as uuid } from "uuid"
|
||||||
|
|
||||||
|
import { appMap } from "../constant/appMap"
|
||||||
import cardMap from "../constant/card"
|
import cardMap from "../constant/card"
|
||||||
import functionMap from "../constant/function"
|
import functionMap from "../constant/function"
|
||||||
import tempMap from "../constant/template"
|
import tempMap from "../constant/template"
|
||||||
import { AttachService } from "../services"
|
import { AttachService } from "../services"
|
||||||
import { Context } from "../types"
|
import { Context } from "../types"
|
||||||
|
|
||||||
// 获取所有应用信息
|
|
||||||
const appMap = parseJsonString(process.env.LARK_APP_MAP, {}) as Record<
|
|
||||||
string,
|
|
||||||
Context.APP_INFO
|
|
||||||
>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取之前的requestId。
|
* 获取之前的requestId。
|
||||||
*
|
*
|
||||||
@ -83,7 +77,6 @@ const genContext = async (req: Request) => {
|
|||||||
searchParams,
|
searchParams,
|
||||||
app,
|
app,
|
||||||
appInfo,
|
appInfo,
|
||||||
appMap,
|
|
||||||
} as Context.Data
|
} as Context.Data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
30
utils/time.ts
Normal file
30
utils/time.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* 获取指定时间范围的开始时间和结束时间
|
||||||
|
* @param {("daily" | "weekly")} timeScope - 时间范围,可以是 "daily" 或 "weekly"
|
||||||
|
* @returns {{ startTime: string, endTime: string }} - 格式化后的开始时间和结束时间
|
||||||
|
*/
|
||||||
|
export const getTimeRange = (timeScope: "daily" | "weekly") => {
|
||||||
|
const formatDate = (date: Date) => {
|
||||||
|
const pad = (num: number) => (num < 10 ? "0" + num : num)
|
||||||
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const startTime = new Date(now)
|
||||||
|
if (timeScope === "daily") {
|
||||||
|
startTime.setHours(0, 0, 0, 0)
|
||||||
|
} else {
|
||||||
|
const day = now.getDay()
|
||||||
|
const diff = day === 0 ? 6 : day - 1 // adjust when day is sunday
|
||||||
|
startTime.setDate(now.getDate() - diff)
|
||||||
|
startTime.setHours(0, 0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = new Date(now)
|
||||||
|
endTime.setHours(23, 59, 59, 999)
|
||||||
|
|
||||||
|
return {
|
||||||
|
startTime: formatDate(startTime),
|
||||||
|
endTime: formatDate(endTime),
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user