feat(group-agent): 新增支持群组问答
Some checks failed
Egg Server MIflow / build-image (push) Failing after 5m7s
Some checks failed
Egg Server MIflow / build-image (push) Failing after 5m7s
This commit is contained in:
parent
88ccafed92
commit
b992ee0b21
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -5,14 +5,17 @@
|
|||||||
"Chakroun",
|
"Chakroun",
|
||||||
"commitlint",
|
"commitlint",
|
||||||
"dbaeumer",
|
"dbaeumer",
|
||||||
|
"deepseek",
|
||||||
"devcontainer",
|
"devcontainer",
|
||||||
"devcontainers",
|
"devcontainers",
|
||||||
"eamodio",
|
"eamodio",
|
||||||
"esbenp",
|
"esbenp",
|
||||||
"Gruntfuggly",
|
"Gruntfuggly",
|
||||||
|
"langchain",
|
||||||
"metas",
|
"metas",
|
||||||
"mina",
|
"mina",
|
||||||
"mindnote",
|
"mindnote",
|
||||||
|
"openai",
|
||||||
"openchat",
|
"openchat",
|
||||||
"tseslint",
|
"tseslint",
|
||||||
"userid",
|
"userid",
|
||||||
|
@ -2,13 +2,14 @@ FROM micr.cloud.mioffice.cn/zhaoyingbo/bun:alpine-cn
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package*.json ./
|
# COPY package*.json ./
|
||||||
|
|
||||||
COPY bun.lockb ./
|
# COPY bun.lockb ./
|
||||||
|
|
||||||
COPY .npmrc ./
|
# COPY .npmrc ./
|
||||||
|
|
||||||
RUN bun install
|
|
||||||
|
# RUN bun install
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
40
db/appConfig/index.ts
Normal file
40
db/appConfig/index.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { RecordModel } from "pocketbase"
|
||||||
|
|
||||||
|
import { managePb404 } from "../../utils/pbTools"
|
||||||
|
import pbClient from "../pbClient"
|
||||||
|
|
||||||
|
interface AppConfigRecordModel extends RecordModel {
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取配置
|
||||||
|
* @param key
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const get = async (key: string) => {
|
||||||
|
const config = await managePb404<AppConfigRecordModel>(() =>
|
||||||
|
pbClient.collection("config").getFirstListItem(`key='${key}'`)
|
||||||
|
)
|
||||||
|
if (!config) return ""
|
||||||
|
return config.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Deepseek的apiKey
|
||||||
|
* @returns {string} ak
|
||||||
|
*/
|
||||||
|
const getDeepseekApiKey = async () => get("deepseek_api_key")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取OpenAI的key
|
||||||
|
* @returns {string} ak
|
||||||
|
*/
|
||||||
|
const getOpenAIApiKey = async () => get("openai_api_key")
|
||||||
|
|
||||||
|
const appConfig = {
|
||||||
|
getOpenAIApiKey,
|
||||||
|
getDeepseekApiKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default appConfig
|
27
db/groupAgentConfig/index.ts
Normal file
27
db/groupAgentConfig/index.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { DB } from "../../types"
|
||||||
|
import { managePb404 } from "../../utils/pbTools"
|
||||||
|
import pbClient from "../pbClient"
|
||||||
|
|
||||||
|
const get = async (userId: string) =>
|
||||||
|
managePb404<DB.GroupAgentConfig>(() =>
|
||||||
|
pbClient
|
||||||
|
.collection("group_agent_config")
|
||||||
|
.getFirstListItem(`user_id='${userId}'`)
|
||||||
|
)
|
||||||
|
|
||||||
|
const upsert = async (data: Partial<DB.GroupAgentConfig>) => {
|
||||||
|
const { user_id } = data
|
||||||
|
const old = await get(user_id!)
|
||||||
|
if (old) {
|
||||||
|
await pbClient.collection("group_agent_config").update(old.id, data)
|
||||||
|
return old.id
|
||||||
|
}
|
||||||
|
return pbClient.collection("group_agent_config").create(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupAgentConfig = {
|
||||||
|
get,
|
||||||
|
upsert,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default groupAgentConfig
|
@ -1,5 +1,7 @@
|
|||||||
import apiKey from "./apiKey"
|
import apiKey from "./apiKey"
|
||||||
|
import appConfig from "./appConfig"
|
||||||
import appInfo from "./appInfo"
|
import appInfo from "./appInfo"
|
||||||
|
import groupAgentConfig from "./groupAgentConfig"
|
||||||
import log from "./log"
|
import log from "./log"
|
||||||
import messageGroup from "./messageGroup"
|
import messageGroup from "./messageGroup"
|
||||||
import tenantAccessToken from "./tenantAccessToken"
|
import tenantAccessToken from "./tenantAccessToken"
|
||||||
@ -10,6 +12,8 @@ const db = {
|
|||||||
messageGroup,
|
messageGroup,
|
||||||
log,
|
log,
|
||||||
tenantAccessToken,
|
tenantAccessToken,
|
||||||
|
groupAgentConfig,
|
||||||
|
appConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default db
|
export default db
|
||||||
|
@ -36,9 +36,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@egg/hooks": "^1.2.0",
|
"@egg/hooks": "^1.2.0",
|
||||||
"@egg/lark-msg-tool": "^1.2.1",
|
"@egg/lark-msg-tool": "^1.2.1",
|
||||||
"@egg/logger": "^1.4.2",
|
"@egg/logger": "^1.4.3",
|
||||||
"@egg/net-tool": "^1.6.3",
|
"@egg/net-tool": "^1.6.5",
|
||||||
"@egg/path-tool": "^1.3.0",
|
"@egg/path-tool": "^1.3.0",
|
||||||
|
"@langchain/openai": "^0.3.0",
|
||||||
"joi": "^17.13.3",
|
"joi": "^17.13.3",
|
||||||
"node-schedule": "^2.1.1",
|
"node-schedule": "^2.1.1",
|
||||||
"p-limit": "^6.1.0",
|
"p-limit": "^6.1.0",
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import type { LarkAction } from "@egg/lark-msg-tool"
|
|
||||||
import { getActionType, getIsActionMsg } from "@egg/lark-msg-tool"
|
import { getActionType, getIsActionMsg } from "@egg/lark-msg-tool"
|
||||||
import { sleep } from "bun"
|
import { sleep } from "bun"
|
||||||
|
|
||||||
import { Context } from "../../types"
|
import { Context } from "../../types"
|
||||||
|
import groupAgent from "./groupAgent"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回ChatId卡片
|
* 返回ChatId卡片
|
||||||
* @param {LarkAction.Data} body
|
* @param {LarkAction.Data} body
|
||||||
* @returns {Promise<string>} 返回包含ChatId卡片的JSON字符串
|
* @returns {Promise<string>} 返回包含ChatId卡片的JSON字符串
|
||||||
*/
|
*/
|
||||||
const makeChatIdCard = async (body: LarkAction.Data): Promise<string> => {
|
const makeChatIdCard = async ({ body }: Context.Data): Promise<string> => {
|
||||||
await sleep(500)
|
await sleep(500)
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
type: "template",
|
type: "template",
|
||||||
@ -27,6 +27,7 @@ const makeChatIdCard = async (body: LarkAction.Data): Promise<string> => {
|
|||||||
|
|
||||||
const ACTION_MAP = {
|
const ACTION_MAP = {
|
||||||
chat_id: makeChatIdCard,
|
chat_id: makeChatIdCard,
|
||||||
|
group_selector: groupAgent.setChatGroupContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,11 +35,8 @@ const ACTION_MAP = {
|
|||||||
* @param {Context.Data} ctx - 上下文数据,包含body, larkService和logger
|
* @param {Context.Data} ctx - 上下文数据,包含body, larkService和logger
|
||||||
* @returns {Promise<void>} 无返回值
|
* @returns {Promise<void>} 无返回值
|
||||||
*/
|
*/
|
||||||
const manageBtnClick = async ({
|
const manageBtnClick = async (ctx: Context.Data): Promise<void> => {
|
||||||
body,
|
const { body, larkService, logger } = ctx
|
||||||
larkService,
|
|
||||||
logger,
|
|
||||||
}: Context.Data): Promise<void> => {
|
|
||||||
const { action } = body?.action?.value as {
|
const { action } = body?.action?.value as {
|
||||||
action: keyof typeof ACTION_MAP
|
action: keyof typeof ACTION_MAP
|
||||||
}
|
}
|
||||||
@ -46,7 +44,7 @@ const manageBtnClick = async ({
|
|||||||
if (!action) return
|
if (!action) return
|
||||||
const func = ACTION_MAP[action]
|
const func = ACTION_MAP[action]
|
||||||
if (!func) return
|
if (!func) return
|
||||||
const card = await func(body)
|
const card = await func(ctx)
|
||||||
if (!card) return
|
if (!card) return
|
||||||
// 更新飞书的卡片
|
// 更新飞书的卡片
|
||||||
await larkService.message.update(body.open_message_id, card)
|
await larkService.message.update(body.open_message_id, card)
|
||||||
@ -64,5 +62,6 @@ export const manageActionMsg = (ctx: Context.Data): boolean => {
|
|||||||
}
|
}
|
||||||
const actionType = getActionType(ctx.body)
|
const actionType = getActionType(ctx.body)
|
||||||
if (actionType === "button") manageBtnClick(ctx)
|
if (actionType === "button") manageBtnClick(ctx)
|
||||||
|
if (actionType === "select_static") manageBtnClick(ctx)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
import { LarkService } from "../../services"
|
import { LarkService } from "../../services"
|
||||||
import { Context } from "../../types"
|
import { Context } from "../../types"
|
||||||
import createKVTemp from "../sheet/createKVTemp"
|
import createKVTemp from "../sheet/createKVTemp"
|
||||||
|
import groupAgent from "./groupAgent"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否为P2P或者群聊并且艾特了小煎蛋
|
* 是否为P2P或者群聊并且艾特了小煎蛋
|
||||||
@ -37,12 +38,17 @@ const filterIllegalMsg = ({
|
|||||||
}: Context.Data): boolean => {
|
}: Context.Data): boolean => {
|
||||||
// 没有chatId的消息不处理
|
// 没有chatId的消息不处理
|
||||||
const chatId = getChatId(body)
|
const chatId = getChatId(body)
|
||||||
logger.debug(`bot req chatId: ${chatId}`)
|
logger.info(`bot req chatId: ${chatId}`)
|
||||||
if (!chatId) return true
|
if (!chatId) return true
|
||||||
|
|
||||||
|
// 非私聊和群聊中艾特小煎蛋的消息不处理
|
||||||
|
if (!getIsP2pOrGroupAtBot(body)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// 获取msgType
|
// 获取msgType
|
||||||
const msgType = getMsgType(body)
|
const msgType = getMsgType(body)
|
||||||
logger.debug(`bot req msgType: ${msgType}`)
|
logger.info(`bot req msgType: ${msgType}`)
|
||||||
// 放行纯文本消息
|
// 放行纯文本消息
|
||||||
if (msgType === "text") {
|
if (msgType === "text") {
|
||||||
// 过滤艾特全体成员的消息
|
// 过滤艾特全体成员的消息
|
||||||
@ -79,19 +85,19 @@ const filterIllegalMsg = ({
|
|||||||
* @param {LarkService} service - Lark服务实例
|
* @param {LarkService} service - Lark服务实例
|
||||||
*/
|
*/
|
||||||
const manageIdMsg = (chatId: string, service: LarkService): void => {
|
const manageIdMsg = (chatId: string, service: LarkService): void => {
|
||||||
const content = JSON.stringify({
|
service.message.sendTemp("chat_id", chatId, "ctp_AAi3NnHb6zgK", {
|
||||||
type: "template",
|
chat_id: chatId,
|
||||||
data: {
|
})
|
||||||
config: {
|
}
|
||||||
update_multi: true,
|
|
||||||
},
|
/**
|
||||||
template_id: "ctp_AAi3NnHb6zgK",
|
* 回复引导消息
|
||||||
template_variable: {
|
* @param {Context.Data} ctx - 上下文数据,包含body, larkService和logger
|
||||||
chat_id: chatId,
|
*/
|
||||||
},
|
const manageHelpMsg = (chatId: string, service: LarkService): void => {
|
||||||
},
|
service.message.sendTemp("chat_id", chatId, "ctp_AAyVx5R39xU9", {
|
||||||
|
chat_id: chatId,
|
||||||
})
|
})
|
||||||
service.message.send("chat_id", chatId, "interactive", content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,15 +108,20 @@ const manageIdMsg = (chatId: string, service: LarkService): void => {
|
|||||||
const manageCMDMsg = (ctx: Context.Data): boolean => {
|
const manageCMDMsg = (ctx: Context.Data): boolean => {
|
||||||
const { body, logger, larkService, attachService } = ctx
|
const { body, logger, larkService, attachService } = ctx
|
||||||
const text = getMsgText(body)
|
const text = getMsgText(body)
|
||||||
logger.debug(`bot req text: ${text}`)
|
logger.info(`bot req text: ${text}`)
|
||||||
const chatId = getChatId(body)
|
const chatId = getChatId(body)
|
||||||
if (!chatId) return false
|
|
||||||
// 处理命令消息
|
// 处理命令消息
|
||||||
if (text.trim() === "/id") {
|
if (text.trim() === "/id") {
|
||||||
logger.info(`bot command is /id, chatId: ${chatId}`)
|
logger.info(`bot command is /id, chatId: ${chatId}`)
|
||||||
manageIdMsg(chatId, larkService)
|
manageIdMsg(chatId, larkService)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
// 帮助
|
||||||
|
if (text.trim() === "/help") {
|
||||||
|
logger.info(`bot command is /help, chatId: ${chatId}`)
|
||||||
|
manageHelpMsg(chatId, larkService)
|
||||||
|
return true
|
||||||
|
}
|
||||||
// CI监控
|
// CI监控
|
||||||
if (text.trim() === "/ci") {
|
if (text.trim() === "/ci") {
|
||||||
logger.info(`bot command is /ci, chatId: ${chatId}`)
|
logger.info(`bot command is /ci, chatId: ${chatId}`)
|
||||||
@ -136,33 +147,21 @@ const manageCMDMsg = (ctx: Context.Data): boolean => {
|
|||||||
createKVTemp.createFromEvent(ctx)
|
createKVTemp.createFromEvent(ctx)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
// 选择群组信息
|
||||||
|
if (text.trim() === "/sg") {
|
||||||
|
logger.info(`bot command is /sg, chatId: ${chatId}`)
|
||||||
|
groupAgent.sendGroupSelector(ctx)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// 获取当前群组信息
|
||||||
|
if (text.trim() === "/cg") {
|
||||||
|
logger.info(`bot command is /cg, chatId: ${chatId}`)
|
||||||
|
groupAgent.getCurrentGroup(ctx)
|
||||||
|
return true
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 回复引导消息
|
|
||||||
* @param {Context.Data} ctx - 上下文数据,包含body, larkService和logger
|
|
||||||
*/
|
|
||||||
const replyGuideMsg = ({ body, larkService, logger }: Context.Data): void => {
|
|
||||||
const chatId = getChatId(body)
|
|
||||||
logger.info(`reply guide message, chatId: ${chatId}`)
|
|
||||||
if (!chatId) return
|
|
||||||
const content = JSON.stringify({
|
|
||||||
type: "template",
|
|
||||||
data: {
|
|
||||||
config: {
|
|
||||||
enable_forward: false,
|
|
||||||
update_multi: true,
|
|
||||||
},
|
|
||||||
template_id: "ctp_AAyVx5R39xU9",
|
|
||||||
template_variable: {
|
|
||||||
chat_id: chatId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
larkService.message.send("chat_id", chatId, "interactive", content)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理Event消息
|
* 处理Event消息
|
||||||
* @param {Context.Data} ctx - 上下文数据
|
* @param {Context.Data} ctx - 上下文数据
|
||||||
@ -181,7 +180,7 @@ export const manageEventMsg = (ctx: Context.Data): boolean => {
|
|||||||
if (manageCMDMsg(ctx)) {
|
if (manageCMDMsg(ctx)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// 返回引导消息
|
// 群组消息处理
|
||||||
replyGuideMsg(ctx)
|
groupAgent.manageGroupMsg(ctx)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
74
routes/bot/groupAgent/groupManager.ts
Normal file
74
routes/bot/groupAgent/groupManager.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { getChatId, LarkAction, LarkEvent } from "@egg/lark-msg-tool"
|
||||||
|
|
||||||
|
import db from "../../../db"
|
||||||
|
import { Context } from "../../../types"
|
||||||
|
import { genGroupAgentSuccessMsg } from "../../../utils/genMsg"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送群组选择器
|
||||||
|
* @param ctx - 上下文数据,包含body和larkService
|
||||||
|
*/
|
||||||
|
const sendGroupSelector = async ({ larkService, body }: Context.Data) => {
|
||||||
|
const chatId = getChatId(body)!
|
||||||
|
const { data: innerList } = await larkService.chat.getInnerList()
|
||||||
|
// 组织群组数据
|
||||||
|
const groups = innerList.map((v) => ({
|
||||||
|
text: v.name,
|
||||||
|
value: `${v.chat_id}|${v.name}`,
|
||||||
|
}))
|
||||||
|
larkService.message.sendTemp("chat_id", chatId, "ctp_AA00oqPWPTdc", {
|
||||||
|
groups,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前群组
|
||||||
|
* @param ctx - 上下文数据,包含body和larkService
|
||||||
|
*/
|
||||||
|
const getCurrentGroup = async (ctx: Context.Data, needSendMsg = true) => {
|
||||||
|
const body = ctx.body as LarkEvent.Data
|
||||||
|
const chatId = getChatId(body)!
|
||||||
|
const group = await db.groupAgentConfig.get(
|
||||||
|
body.event.sender.sender_id.user_id
|
||||||
|
)
|
||||||
|
if (!needSendMsg) return group
|
||||||
|
if (!group) {
|
||||||
|
await sendGroupSelector(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const msg = genGroupAgentSuccessMsg(`当前群组:${group.chat_name}`)
|
||||||
|
ctx.larkService.message.send("chat_id", chatId, "interactive", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置群组上下文
|
||||||
|
* @param ctx - 上下文数据,包含body, larkService和logger
|
||||||
|
*/
|
||||||
|
const setChatGroupContext = async (ctx: Context.Data) => {
|
||||||
|
const { larkService, logger } = ctx
|
||||||
|
const body = ctx.body as LarkAction.Data
|
||||||
|
const targetId = body?.action?.option?.split?.("|")[0]
|
||||||
|
const targetName = body?.action?.option?.split?.("|")[1]
|
||||||
|
if (!targetId || !targetName) {
|
||||||
|
logger.error(
|
||||||
|
`invalid targetId or targetName: ${JSON.stringify(body?.action)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 更新群组数据
|
||||||
|
await db.groupAgentConfig.upsert({
|
||||||
|
user_id: body.user_id,
|
||||||
|
chat_id: targetId,
|
||||||
|
chat_name: targetName,
|
||||||
|
})
|
||||||
|
// 更新成功消息
|
||||||
|
const successMsg = genGroupAgentSuccessMsg(`已将群组切换至 ${targetName}`)
|
||||||
|
larkService.message.update(body.open_message_id, successMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupManager = {
|
||||||
|
sendGroupSelector,
|
||||||
|
setChatGroupContext,
|
||||||
|
getCurrentGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default groupManager
|
118
routes/bot/groupAgent/index.ts
Normal file
118
routes/bot/groupAgent/index.ts
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
import { getChatId, getMsgText, LarkEvent } from "@egg/lark-msg-tool"
|
||||||
|
|
||||||
|
import db from "../../../db"
|
||||||
|
import { Context } from "../../../types"
|
||||||
|
import {
|
||||||
|
genGroupAgentErrorMsg,
|
||||||
|
genGroupAgentSuccessMsg,
|
||||||
|
} from "../../../utils/genMsg"
|
||||||
|
import llm from "../../../utils/llm"
|
||||||
|
import groupManager from "./groupManager"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据聊天历史回答用户的问题
|
||||||
|
* @param ctx - 上下文数据
|
||||||
|
* @param userInput - 用户输入
|
||||||
|
* @param targetChatId - 目标群组ID
|
||||||
|
* @param loadingMsgId - loading消息ID
|
||||||
|
*/
|
||||||
|
const chat2llm = async (
|
||||||
|
ctx: Context.Data,
|
||||||
|
userInput: string,
|
||||||
|
targetChatId: string,
|
||||||
|
loadingMsgId?: string
|
||||||
|
) => {
|
||||||
|
const { logger, body } = ctx
|
||||||
|
// 发送消息给Deepseek模型解析时间,返回格式为 YYYY-MM-DD HH:mm:ss
|
||||||
|
const { startTime, endTime } = await llm.parseTime(userInput)
|
||||||
|
logger.info(`Parsed result: startTime = ${startTime}, endTime = ${endTime}`)
|
||||||
|
// 获取服务器的时区偏移量(以分钟为单位)
|
||||||
|
const serverTimezoneOffset = new Date().getTimezoneOffset()
|
||||||
|
// 上海时区的偏移量(UTC+8,以分钟为单位)
|
||||||
|
const shanghaiTimezoneOffset = -8 * 60
|
||||||
|
// 计算时间戳,调整为上海时区
|
||||||
|
const startTimeTimestamp =
|
||||||
|
Math.round(new Date(startTime).getTime() / 1000) +
|
||||||
|
(shanghaiTimezoneOffset - serverTimezoneOffset) * 60
|
||||||
|
const endTimeTimestamp =
|
||||||
|
Math.round(new Date(endTime).getTime() / 1000) +
|
||||||
|
(shanghaiTimezoneOffset - serverTimezoneOffset) * 60
|
||||||
|
|
||||||
|
// 获取群聊中的历史记录
|
||||||
|
const { data: chatHistory } = await ctx.larkService.message.getHistory(
|
||||||
|
targetChatId,
|
||||||
|
String(startTimeTimestamp),
|
||||||
|
String(endTimeTimestamp)
|
||||||
|
)
|
||||||
|
// 如果没有历史记录则返回错误消息
|
||||||
|
if (chatHistory.length === 0) {
|
||||||
|
logger.error("Chat history is empty")
|
||||||
|
const content = genGroupAgentErrorMsg("未找到聊天记录")
|
||||||
|
if (loadingMsgId) {
|
||||||
|
await ctx.larkService.message.update(loadingMsgId, content)
|
||||||
|
} else {
|
||||||
|
await ctx.larkService.message.sendInteractive2Chat(
|
||||||
|
getChatId(body),
|
||||||
|
content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.debug(`Chat history: ${JSON.stringify(chatHistory)}`)
|
||||||
|
const llmRes = await llm.queryWithChatHistory(
|
||||||
|
userInput,
|
||||||
|
JSON.stringify(chatHistory)
|
||||||
|
)
|
||||||
|
logger.info(`LLM result: ${llmRes.content}`)
|
||||||
|
const successMsg = genGroupAgentSuccessMsg(llmRes.content as string)
|
||||||
|
// 发送LLM结果
|
||||||
|
if (loadingMsgId) {
|
||||||
|
await ctx.larkService.message.update(loadingMsgId, successMsg)
|
||||||
|
} else {
|
||||||
|
await ctx.larkService.message.sendInteractive2Chat(
|
||||||
|
getChatId(body),
|
||||||
|
successMsg
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 群组代理处理消息
|
||||||
|
* @param ctx - 上下文数据
|
||||||
|
*/
|
||||||
|
const manageGroupMsg = async (ctx: Context.Data) => {
|
||||||
|
const { logger } = ctx
|
||||||
|
logger.info("Start to manage group message")
|
||||||
|
const body = ctx.body as LarkEvent.Data
|
||||||
|
// 获取用户输入
|
||||||
|
const userInput = getMsgText(body)
|
||||||
|
// 先获取当前对话的目标群组ID
|
||||||
|
const group = await groupManager.getCurrentGroup(ctx, false)
|
||||||
|
// 没有目标群组ID则发送群组选择器,并保存当前问题,在选择后立即处理
|
||||||
|
if (!group || !group.chat_id) {
|
||||||
|
logger.info("No group found, send group selector")
|
||||||
|
groupManager.sendGroupSelector(ctx)
|
||||||
|
db.groupAgentConfig.upsert({
|
||||||
|
user_id: body.event.sender.sender_id.user_id,
|
||||||
|
pre_query: userInput,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 发送一个loading的消息
|
||||||
|
const loadingRes = await ctx.larkService.message.sendInteractive2Chat(
|
||||||
|
getChatId(body),
|
||||||
|
genGroupAgentSuccessMsg("小煎蛋正在爬楼中,请稍等...")
|
||||||
|
)
|
||||||
|
if (loadingRes.code !== 0) {
|
||||||
|
logger.error("Failed to send loading message")
|
||||||
|
}
|
||||||
|
const { message_id } = loadingRes.data
|
||||||
|
await chat2llm(ctx, userInput, group.chat_id, message_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupAgent = {
|
||||||
|
...groupManager,
|
||||||
|
manageGroupMsg,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default groupAgent
|
@ -1,7 +1,7 @@
|
|||||||
import { getChatId, getChatType, getUserId } from "@egg/lark-msg-tool"
|
import { getChatId, getChatType, getUserId } from "@egg/lark-msg-tool"
|
||||||
|
|
||||||
import { Context, LarkServer } from "../../types"
|
import { Context, LarkServer } from "../../types"
|
||||||
import { genSheetDbErrorMsg, genTempMsg } from "../../utils/genMsg"
|
import { genSheetDbErrorMsg } from "../../utils/genMsg"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建键值多维表格
|
* 创建键值多维表格
|
||||||
@ -93,8 +93,12 @@ const createFromEvent = async (ctx: Context.Data) => {
|
|||||||
if (addRes.code !== 0) throw new Error(addRes.message)
|
if (addRes.code !== 0) throw new Error(addRes.message)
|
||||||
}
|
}
|
||||||
// 全部成功,发送成功消息
|
// 全部成功,发送成功消息
|
||||||
const successMsg = genTempMsg("ctp_AA00oqPWPXtG", createRes.data)
|
ctx.larkService.message.sendTemp(
|
||||||
ctx.larkService.message.send("chat_id", chatId, "interactive", successMsg)
|
"chat_id",
|
||||||
|
chatId,
|
||||||
|
"ctp_AA00oqPWPXtG",
|
||||||
|
createRes.data
|
||||||
|
)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
ctx.logger.error(`create KV bitable failed: ${e.message}`)
|
ctx.logger.error(`create KV bitable failed: ${e.message}`)
|
||||||
const errorMsg = genSheetDbErrorMsg(e.message)
|
const errorMsg = genSheetDbErrorMsg(e.message)
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import LarkBaseService from "./base"
|
import LarkBaseService from "./base"
|
||||||
|
|
||||||
class LarkAuthService extends LarkBaseService {
|
class LarkAuthService extends LarkBaseService {
|
||||||
getAk(app_id: string, app_secret: string) {
|
getAk(appId: string, appSecret: string) {
|
||||||
return this.post<{ tenant_access_token: string; code: number }>(
|
return this.post<{ tenant_access_token: string; code: number }>(
|
||||||
"/auth/v3/tenant_access_token/internal",
|
"/auth/v3/tenant_access_token/internal",
|
||||||
{
|
{
|
||||||
app_id,
|
app_id: appId,
|
||||||
app_secret,
|
app_secret: appSecret,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ class LarkBaseService extends NetToolBase {
|
|||||||
data: error.data,
|
data: error.data,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
} as T
|
} as T
|
||||||
this.logger.error("larkNetTool catch error: ", JSON.stringify(res))
|
this.logger.error(`larkNetTool catch error: ${JSON.stringify(res)}`)
|
||||||
return res
|
return res
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
33
services/lark/chat.ts
Normal file
33
services/lark/chat.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { LarkServer } from "../../types"
|
||||||
|
import LarkBaseService from "./base"
|
||||||
|
|
||||||
|
class LarkChatService extends LarkBaseService {
|
||||||
|
/**
|
||||||
|
* 获取机器人所在群列表
|
||||||
|
*/
|
||||||
|
async getInnerList() {
|
||||||
|
const path = "/im/v1/chats"
|
||||||
|
const chatList = []
|
||||||
|
let hasMore = true
|
||||||
|
let pageToken = ""
|
||||||
|
while (hasMore) {
|
||||||
|
const { data, code } = await this.get<
|
||||||
|
LarkServer.BaseListRes<LarkServer.ChatGroupData>
|
||||||
|
>(path, {
|
||||||
|
page_size: 100,
|
||||||
|
page_token: pageToken,
|
||||||
|
})
|
||||||
|
if (code !== 0) break
|
||||||
|
chatList.push(...data.items)
|
||||||
|
hasMore = data.has_more
|
||||||
|
pageToken = data.page_token
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
code: 0,
|
||||||
|
data: chatList,
|
||||||
|
message: "ok",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LarkChatService
|
@ -6,14 +6,14 @@ class LarkDriveService extends LarkBaseService {
|
|||||||
* 批量获取文档元数据。
|
* 批量获取文档元数据。
|
||||||
*
|
*
|
||||||
* @param docTokens - 文档令牌数组。
|
* @param docTokens - 文档令牌数组。
|
||||||
* @param doc_type - 文档类型,默认为 "doc"。
|
* @param docType - 文档类型,默认为 "doc"。
|
||||||
* @param user_id_type - 用户ID类型,默认为 "user_id"。
|
* @param userIdType - 用户ID类型,默认为 "user_id"。
|
||||||
* @returns 包含元数据和失败列表的响应对象。
|
* @returns 包含元数据和失败列表的响应对象。
|
||||||
*/
|
*/
|
||||||
async batchGetMeta(
|
async batchGetMeta(
|
||||||
docTokens: string[],
|
docTokens: string[],
|
||||||
doc_type = "doc",
|
docType = "doc",
|
||||||
user_id_type = "user_id"
|
userIdType = "user_id"
|
||||||
) {
|
) {
|
||||||
const path = "/drive/v1/metas/batch_query"
|
const path = "/drive/v1/metas/batch_query"
|
||||||
// 如果docTokens长度超出150,需要分批请求
|
// 如果docTokens长度超出150,需要分批请求
|
||||||
@ -27,11 +27,11 @@ class LarkDriveService extends LarkBaseService {
|
|||||||
const data = {
|
const data = {
|
||||||
request_docs: docTokensSlice.map((id) => ({
|
request_docs: docTokensSlice.map((id) => ({
|
||||||
doc_token: id,
|
doc_token: id,
|
||||||
doc_type,
|
doc_type: docType,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
return this.post<LarkServer.BatchDocMetaRes>(path, data, {
|
return this.post<LarkServer.BatchDocMetaRes>(path, data, {
|
||||||
user_id_type,
|
user_id_type: userIdType,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -40,7 +40,7 @@ class LarkDriveService extends LarkBaseService {
|
|||||||
return res.data?.metas || []
|
return res.data?.metas || []
|
||||||
})
|
})
|
||||||
|
|
||||||
const failed_list = responses.flatMap((res) => {
|
const failedList = responses.flatMap((res) => {
|
||||||
return res.data?.failed_list || []
|
return res.data?.failed_list || []
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ class LarkDriveService extends LarkBaseService {
|
|||||||
code: 0,
|
code: 0,
|
||||||
data: {
|
data: {
|
||||||
metas,
|
metas,
|
||||||
failed_list,
|
failedList,
|
||||||
},
|
},
|
||||||
message: "success",
|
message: "success",
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import LarkAuthService from "./auth"
|
import LarkAuthService from "./auth"
|
||||||
|
import LarkChatService from "./chat"
|
||||||
import LarkDriveService from "./drive"
|
import LarkDriveService from "./drive"
|
||||||
import LarkMessageService from "./message"
|
import LarkMessageService from "./message"
|
||||||
import LarkSheetService from "./sheet"
|
import LarkSheetService from "./sheet"
|
||||||
@ -10,6 +11,7 @@ class LarkService {
|
|||||||
user: LarkUserService
|
user: LarkUserService
|
||||||
sheet: LarkSheetService
|
sheet: LarkSheetService
|
||||||
auth: LarkAuthService
|
auth: LarkAuthService
|
||||||
|
chat: LarkChatService
|
||||||
requestId: string
|
requestId: string
|
||||||
|
|
||||||
constructor(appName: string, requestId: string) {
|
constructor(appName: string, requestId: string) {
|
||||||
@ -18,6 +20,7 @@ class LarkService {
|
|||||||
this.user = new LarkUserService(appName, requestId)
|
this.user = new LarkUserService(appName, requestId)
|
||||||
this.sheet = new LarkSheetService(appName, requestId)
|
this.sheet = new LarkSheetService(appName, requestId)
|
||||||
this.auth = new LarkAuthService(appName, requestId)
|
this.auth = new LarkAuthService(appName, requestId)
|
||||||
|
this.chat = new LarkChatService(appName, requestId)
|
||||||
this.requestId = requestId
|
this.requestId = requestId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,40 +1,114 @@
|
|||||||
import { LarkServer } from "../../types/larkServer"
|
import { LarkServer } from "../../types/larkServer"
|
||||||
|
import { genTempMsg } from "../../utils/genMsg"
|
||||||
import LarkBaseService from "./base"
|
import LarkBaseService from "./base"
|
||||||
|
|
||||||
class LarkMessageService extends LarkBaseService {
|
class LarkMessageService extends LarkBaseService {
|
||||||
/**
|
/**
|
||||||
* 发送卡片
|
* 发送卡片
|
||||||
* @param {LarkServer.ReceiveIDType} receive_id_type 消息接收者id类型 open_id/user_id/union_id/email/chat_id
|
* @param receiveIdType 消息接收者id类型 open_id/user_id/union_id/email/chat_id
|
||||||
* @param {string} receive_id 消息接收者的ID,ID类型应与查询参数receive_id_type 对应
|
* @param receiveId 消息接收者的ID,ID类型应与查询参数receiveIdType 对应
|
||||||
* @param {MsgType} msg_type 消息类型 包括:text、post、image、file、audio、media、sticker、interactive、share_chat、share_user
|
* @param msgType 消息类型 包括:text、post、image、file、audio、media、sticker、interactive、share_chat、share_user
|
||||||
* @param {string} content 消息内容,JSON结构序列化后的字符串。不同msg_type对应不同内容
|
* @param content 消息内容,JSON结构序列化后的字符串。不同msgType对应不同内容
|
||||||
*/
|
*/
|
||||||
async send(
|
async send(
|
||||||
receive_id_type: LarkServer.ReceiveIDType,
|
receiveIdType: LarkServer.ReceiveIDType,
|
||||||
receive_id: string,
|
receiveId: string,
|
||||||
msg_type: LarkServer.MsgType,
|
msgType: LarkServer.MsgType,
|
||||||
content: string
|
content: string
|
||||||
) {
|
) {
|
||||||
const path = `/im/v1/messages?receive_id_type=${receive_id_type}`
|
const path = `/im/v1/messages?receive_id_type=${receiveIdType}`
|
||||||
if (msg_type === "text" && !content.includes('"text"')) {
|
if (msgType === "text" && !content.includes('"text"')) {
|
||||||
content = JSON.stringify({ text: content })
|
content = JSON.stringify({ text: content })
|
||||||
}
|
}
|
||||||
return this.post<LarkServer.BaseRes>(path, {
|
return this.post<LarkServer.BaseRes<{ message_id: string }>>(path, {
|
||||||
receive_id,
|
receive_id: receiveId,
|
||||||
msg_type,
|
msg_type: msgType,
|
||||||
content,
|
content,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新卡片
|
* 发送模板消息
|
||||||
* @param {string} message_id 消息id
|
* @param receiveIdType 消息接收者id类型 open_id/user_id/union_id/email/chat_id
|
||||||
* @param {string} content 消息内容,JSON结构序列化后的字符串。不同msg_type对应不同内容
|
* @param receiveId 消息接收者的ID,ID类型应与查询参数receiveIdType 对应
|
||||||
|
* @param templateId 模板ID
|
||||||
|
* @param variable 模板变量
|
||||||
*/
|
*/
|
||||||
async update(message_id: string, content: string) {
|
async sendTemp(
|
||||||
const path = `/im/v1/messages/${message_id}`
|
receiveIdType: LarkServer.ReceiveIDType,
|
||||||
|
receiveId: string,
|
||||||
|
templateId: string,
|
||||||
|
variable: any
|
||||||
|
) {
|
||||||
|
return this.send(
|
||||||
|
receiveIdType,
|
||||||
|
receiveId,
|
||||||
|
"interactive",
|
||||||
|
genTempMsg(templateId, variable)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送富文本信息
|
||||||
|
* @param receiveId 消息接收者的ID,ID类型应与查询参数receiveIdType 对应
|
||||||
|
* @param content 消息内容
|
||||||
|
*/
|
||||||
|
async sendInteractive2Chat(receiveId: string, content: string) {
|
||||||
|
return this.send("chat_id", receiveId, "interactive", content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送文本信息
|
||||||
|
* @param receiveId 消息接收者的ID,ID类型应与查询参数receiveIdType 对应
|
||||||
|
* @param content 消息内容
|
||||||
|
*/
|
||||||
|
async sendText2Chat(receiveId: string, content: string) {
|
||||||
|
return this.send("chat_id", receiveId, "text", content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新卡片
|
||||||
|
* @param messageId 消息id
|
||||||
|
* @param content 消息内容,JSON结构序列化后的字符串。不同msgType对应不同内容
|
||||||
|
*/
|
||||||
|
async update(messageId: string, content: string) {
|
||||||
|
const path = `/im/v1/messages/${messageId}`
|
||||||
return this.patch<LarkServer.BaseRes>(path, { content })
|
return this.patch<LarkServer.BaseRes>(path, { content })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取消息历史记录
|
||||||
|
* @param chatId 会话ID
|
||||||
|
* @param startTime 开始时间 秒级时间戳
|
||||||
|
* @param endTime 结束时间 秒级时间戳
|
||||||
|
*/
|
||||||
|
async getHistory(chatId: string, startTime: string, endTime: string) {
|
||||||
|
const path = `/im/v1/messages`
|
||||||
|
const messageList = [] as LarkServer.MessageData[]
|
||||||
|
let hasMore = true
|
||||||
|
let pageToken = ""
|
||||||
|
while (hasMore) {
|
||||||
|
const { code, data } = await this.get<
|
||||||
|
LarkServer.BaseListRes<LarkServer.MessageData>
|
||||||
|
>(path, {
|
||||||
|
container_id_type: "chat",
|
||||||
|
container_id: chatId,
|
||||||
|
start_time: startTime,
|
||||||
|
end_time: endTime,
|
||||||
|
page_size: 50,
|
||||||
|
page_token: pageToken,
|
||||||
|
})
|
||||||
|
if (code !== 0) break
|
||||||
|
messageList.push(...data.items)
|
||||||
|
hasMore = data.has_more
|
||||||
|
pageToken = data.page_token
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
code: 0,
|
||||||
|
data: messageList,
|
||||||
|
message: "ok",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LarkMessageService
|
export default LarkMessageService
|
||||||
|
@ -4,10 +4,10 @@ import LarkBaseService from "./base"
|
|||||||
class LarkSheetService extends LarkBaseService {
|
class LarkSheetService extends LarkBaseService {
|
||||||
/**
|
/**
|
||||||
* 向电子表格中插入行。
|
* 向电子表格中插入行。
|
||||||
* @param {string} sheetToken - 表格令牌。
|
* @param sheetToken 表格令牌。
|
||||||
* @param {string} range - 插入数据的范围。
|
* @param range 插入数据的范围。
|
||||||
* @param {string[][]} values - 要插入的值。
|
* @param values 要插入的值。
|
||||||
* @returns {Promise<LarkServer.BaseRes>} 返回一个包含响应数据的Promise。
|
* @returns 返回一个包含响应数据的Promise。
|
||||||
*/
|
*/
|
||||||
async insertRows(sheetToken: string, range: string, values: string[][]) {
|
async insertRows(sheetToken: string, range: string, values: string[][]) {
|
||||||
const path = `/sheets/v2/spreadsheets/${sheetToken}/values_append?insertDataOption=INSERT_ROWS`
|
const path = `/sheets/v2/spreadsheets/${sheetToken}/values_append?insertDataOption=INSERT_ROWS`
|
||||||
@ -21,9 +21,9 @@ class LarkSheetService extends LarkBaseService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定范围内的电子表格数据。
|
* 获取指定范围内的电子表格数据。
|
||||||
* @param {string} sheetToken - 表格令牌。
|
* @param sheetToken 表格令牌。
|
||||||
* @param {string} range - 要获取数据的范围。
|
* @param range 要获取数据的范围。
|
||||||
* @returns {Promise<LarkServer.SpreadsheetRes>} 返回一个包含响应数据的Promise。
|
* @returns 返回一个包含响应数据的Promise。
|
||||||
*/
|
*/
|
||||||
async getRange(sheetToken: string, range: string) {
|
async getRange(sheetToken: string, range: string) {
|
||||||
const path = `/sheets/v2/spreadsheets/${sheetToken}/values/${range}?valueRenderOption=ToString`
|
const path = `/sheets/v2/spreadsheets/${sheetToken}/values/${range}?valueRenderOption=ToString`
|
||||||
@ -32,35 +32,38 @@ class LarkSheetService extends LarkBaseService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定表格的所有数据表(多维表格专用)
|
* 获取指定表格的所有数据表(多维表格专用)
|
||||||
* @param {string} appToken - 表格令牌。
|
* @param appToken 表格令牌。
|
||||||
* @returns {Promise<LarkServer.BaseRes} 返回一个包含响应数据的Promise。
|
* @returns 返回一个包含响应数据的Promise。
|
||||||
*/
|
*/
|
||||||
async getTables(appToken: string) {
|
async getTables(appToken: string) {
|
||||||
const path = `/bitable/v1/apps/${appToken}/tables`
|
const path = `/bitable/v1/apps/${appToken}/tables`
|
||||||
let has_more = true
|
const tableList = [] as LarkServer.TableData[]
|
||||||
const res = [] as LarkServer.TableData[]
|
let hasMore = true
|
||||||
while (has_more) {
|
let pageToken = ""
|
||||||
|
while (hasMore) {
|
||||||
const { data, code } = await this.get<
|
const { data, code } = await this.get<
|
||||||
LarkServer.BaseListRes<LarkServer.TableData>
|
LarkServer.BaseListRes<LarkServer.TableData>
|
||||||
>(path, {
|
>(path, {
|
||||||
page_size: 100,
|
page_size: 100,
|
||||||
|
page_token: pageToken,
|
||||||
})
|
})
|
||||||
if (code !== 0) break
|
if (code !== 0) break
|
||||||
res.push(...data.items)
|
tableList.push(...data.items)
|
||||||
has_more = data.has_more
|
hasMore = data.has_more
|
||||||
|
pageToken = data.page_token
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
code: 0,
|
code: 0,
|
||||||
data: res,
|
data: tableList,
|
||||||
message: "ok",
|
message: "ok",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定数据表的所有视图(多维表格专用)
|
* 获取指定数据表的所有视图(多维表格专用)
|
||||||
* @param {string} appToken - 表格令牌。
|
* @param appToken 表格令牌。
|
||||||
* @param {string} tableId - 表格ID。
|
* @param tableId 表格ID。
|
||||||
* @returns {Promise<LarkServer.BaseRes} 返回一个包含响应数据的Promise。
|
* @returns 返回一个包含响应数据的Promise。
|
||||||
*/
|
*/
|
||||||
async getViews(appToken: string, tableId: string) {
|
async getViews(appToken: string, tableId: string) {
|
||||||
const path = `/bitable/v1/apps/${appToken}/tables/${tableId}/views`
|
const path = `/bitable/v1/apps/${appToken}/tables/${tableId}/views`
|
||||||
@ -85,9 +88,9 @@ class LarkSheetService extends LarkBaseService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定数据表的记录。(多维表格专用)
|
* 获取指定数据表的记录。(多维表格专用)
|
||||||
* @param {string} appToken - 表格令牌。
|
* @param appToken 表格令牌。
|
||||||
* @param {string} tableId - 表格ID。
|
* @param tableId 表格ID。
|
||||||
* @returns {Promise<LarkServer.BaseRes>} 返回一个包含响应数据的Promise。
|
* @returns 返回一个包含响应数据的Promise。
|
||||||
*/
|
*/
|
||||||
async getRecords(appToken: string, tableId: string) {
|
async getRecords(appToken: string, tableId: string) {
|
||||||
const path = `/bitable/v1/apps/${appToken}/tables/${tableId}/records`
|
const path = `/bitable/v1/apps/${appToken}/tables/${tableId}/records`
|
||||||
|
@ -4,7 +4,7 @@ import LarkBaseService from "./base"
|
|||||||
class LarkUserService extends LarkBaseService {
|
class LarkUserService extends LarkBaseService {
|
||||||
/**
|
/**
|
||||||
* 登录凭证校验
|
* 登录凭证校验
|
||||||
* @param {string} code 登录凭证
|
* @param code 登录凭证
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async code2Login(code: string) {
|
async code2Login(code: string) {
|
||||||
@ -14,38 +14,38 @@ class LarkUserService extends LarkBaseService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户信息
|
* 获取用户信息
|
||||||
* @param {string} user_id 用户ID
|
* @param userId 用户ID
|
||||||
* @param {"open_id" | "user_id"} user_id_type 用户ID类型
|
* @param userIdType 用户ID类型
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async getOne(user_id: string, user_id_type: "open_id" | "user_id") {
|
async getOne(userId: string, userIdType: "open_id" | "user_id") {
|
||||||
const path = `/contact/v3/users/${user_id}`
|
const path = `/contact/v3/users/${userId}`
|
||||||
return this.get<LarkServer.UserInfoRes>(path, {
|
return this.get<LarkServer.UserInfoRes>(path, {
|
||||||
user_id_type,
|
user_id_type: userIdType,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量获取用户信息
|
* 批量获取用户信息
|
||||||
* @param {string[]} user_ids 用户ID数组
|
* @param userIds 用户ID数组
|
||||||
* @param {"open_id" | "user_id"} user_id_type 用户ID类型
|
* @param userIdType 用户ID类型
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async batchGet(user_ids: string[], user_id_type: "open_id" | "user_id") {
|
async batchGet(userIds: string[], userIdType: "open_id" | "user_id") {
|
||||||
const path = `/contact/v3/users/batch`
|
const path = `/contact/v3/users/batch`
|
||||||
|
|
||||||
// 如果user_id长度超出50,需要分批请求,
|
// 如果user_id长度超出50,需要分批请求,
|
||||||
const userCount = user_ids.length
|
const userCount = userIds.length
|
||||||
const maxLen = 50
|
const maxLen = 50
|
||||||
|
|
||||||
const requestMap = Array.from(
|
const requestMap = Array.from(
|
||||||
{ length: Math.ceil(userCount / maxLen) },
|
{ length: Math.ceil(userCount / maxLen) },
|
||||||
(_, index) => {
|
(_, index) => {
|
||||||
const start = index * maxLen
|
const start = index * maxLen
|
||||||
const user_idsSlice = user_ids.slice(start, start + maxLen)
|
const getParams = `${userIds
|
||||||
const getParams = `${user_idsSlice
|
.slice(start, start + maxLen)
|
||||||
.map((id) => `user_ids=${id}`)
|
.map((id) => `user_ids=${id}`)
|
||||||
.join("&")}&user_id_type=${user_id_type}`
|
.join("&")}&user_id_type=${userIdType}`
|
||||||
return this.get<LarkServer.BatchUserInfoRes>(path, getParams)
|
return this.get<LarkServer.BatchUserInfoRes>(path, getParams)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
14
test/getChatHistory.ts
Normal file
14
test/getChatHistory.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { LarkService } from "../services"
|
||||||
|
|
||||||
|
const service = new LarkService("egg", "")
|
||||||
|
|
||||||
|
const currentTime = Math.floor(new Date().getTime() / 1000)
|
||||||
|
const yesterdayTime = currentTime - 24 * 60 * 60
|
||||||
|
|
||||||
|
const res = await service.message.getHistory(
|
||||||
|
"oc_c83f627bde3da39b01bbbfb026a00111",
|
||||||
|
yesterdayTime.toString(),
|
||||||
|
currentTime.toString()
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(JSON.stringify(res, null, 2))
|
7
test/getInnerList.ts
Normal file
7
test/getInnerList.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { LarkService } from "../services"
|
||||||
|
|
||||||
|
const service = new LarkService("egg", "")
|
||||||
|
|
||||||
|
const res = await service.chat.getInnerList()
|
||||||
|
|
||||||
|
console.log(JSON.stringify(res, null, 2))
|
@ -1,6 +1,13 @@
|
|||||||
import { RecordModel } from "pocketbase"
|
import { RecordModel } from "pocketbase"
|
||||||
|
|
||||||
export namespace DB {
|
export namespace DB {
|
||||||
|
export interface GroupAgentConfig extends RecordModel {
|
||||||
|
user_id: string
|
||||||
|
chat_id: string
|
||||||
|
chat_name: string
|
||||||
|
pre_query?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface AppInfo extends RecordModel {
|
export interface AppInfo extends RecordModel {
|
||||||
name: string
|
name: string
|
||||||
app_id: string
|
app_id: string
|
||||||
|
@ -95,6 +95,39 @@ export namespace LarkServer {
|
|||||||
view_private_owner_id?: string
|
view_private_owner_id?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MessageData {
|
||||||
|
message_id: string
|
||||||
|
root_id: string
|
||||||
|
parent_id: string
|
||||||
|
msg_type: MsgType
|
||||||
|
create_time: string
|
||||||
|
update_time: string
|
||||||
|
deleted: boolean
|
||||||
|
updated: boolean
|
||||||
|
chat_id: string
|
||||||
|
sender: {
|
||||||
|
id: string
|
||||||
|
id_type: "open_id" | "app_id"
|
||||||
|
sender_type: "user" | "app"
|
||||||
|
}
|
||||||
|
body: {
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
mentions: any[]
|
||||||
|
upper_message_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatGroupData {
|
||||||
|
avatar: string
|
||||||
|
chat_id: string
|
||||||
|
description: string
|
||||||
|
external: boolean
|
||||||
|
name: string
|
||||||
|
owner_id: string
|
||||||
|
owner_id_type: "open_id" | "user_id"
|
||||||
|
tenant_key: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface BaseRes<T = any> {
|
export interface BaseRes<T = any> {
|
||||||
code: number
|
code: number
|
||||||
data: T
|
data: T
|
||||||
|
@ -56,6 +56,7 @@ export const genTempMsg = (id: string, variable: any) =>
|
|||||||
data: {
|
data: {
|
||||||
config: {
|
config: {
|
||||||
update_multi: true,
|
update_multi: true,
|
||||||
|
enable_forward: false,
|
||||||
},
|
},
|
||||||
template_id: id,
|
template_id: id,
|
||||||
template_variable: variable,
|
template_variable: variable,
|
||||||
@ -68,7 +69,7 @@ export const genTempMsg = (id: string, variable: any) =>
|
|||||||
* @returns {string} JSON 字符串
|
* @returns {string} JSON 字符串
|
||||||
*/
|
*/
|
||||||
export const genSheetDbErrorMsg = (content: string) =>
|
export const genSheetDbErrorMsg = (content: string) =>
|
||||||
genErrorMsg("🍳 小煎蛋 Sheet DB 错误提醒", content)
|
genErrorMsg("🍪 小煎蛋 Sheet DB 错误提醒", content)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成 Sheet DB 成功消息的 JSON 字符串
|
* 生成 Sheet DB 成功消息的 JSON 字符串
|
||||||
@ -76,4 +77,20 @@ export const genSheetDbErrorMsg = (content: string) =>
|
|||||||
* @returns {string} JSON 字符串
|
* @returns {string} JSON 字符串
|
||||||
*/
|
*/
|
||||||
export const genSheetDbSuccessMsg = (content: string) =>
|
export const genSheetDbSuccessMsg = (content: string) =>
|
||||||
genSuccessMsg("🍳 感谢使用小煎蛋 Sheet DB", content)
|
genSuccessMsg("🍪 感谢使用小煎蛋 Sheet DB", content)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 Group Agent 错误消息的 JSON 字符串
|
||||||
|
* @param {string} content - 消息内容
|
||||||
|
* @returns {string} JSON 字符串
|
||||||
|
*/
|
||||||
|
export const genGroupAgentErrorMsg = (content: string) =>
|
||||||
|
genErrorMsg("🧑💻 小煎蛋 Group Agent 错误提醒", content)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 Group Agent 成功消息的 JSON 字符串
|
||||||
|
* @param {string} content - 消息内容
|
||||||
|
* @returns {string} JSON 字符串
|
||||||
|
*/
|
||||||
|
export const genGroupAgentSuccessMsg = (content: string) =>
|
||||||
|
genSuccessMsg("🧑💻 感谢使用小煎蛋 Group Agent", content)
|
||||||
|
75
utils/llm.ts
Normal file
75
utils/llm.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { ChatOpenAI } from "@langchain/openai"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import db from "../db"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Deepseek模型
|
||||||
|
* @param temperature 温度
|
||||||
|
*/
|
||||||
|
const getDeepseekModel = async (temperature = 0) => {
|
||||||
|
const model = "deepseek-chat"
|
||||||
|
const apiKey = await db.appConfig.getDeepseekApiKey()
|
||||||
|
const baseURL = "https://api.deepseek.com"
|
||||||
|
return new ChatOpenAI({ apiKey, temperature, model }, { baseURL })
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeConfig = z.object({
|
||||||
|
startTime: z.string().describe("开始时间,格式为 YYYY-MM-DD HH:mm:ss"),
|
||||||
|
endTime: z.string().describe("结束时间,格式为 YYYY-MM-DD HH:mm:ss"),
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析时间
|
||||||
|
* @param userInput 用户输入
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const parseTime = async (userInput: string) => {
|
||||||
|
const model = await getDeepseekModel()
|
||||||
|
const structuredLlm = model.withStructuredOutput(timeConfig, { name: "time" })
|
||||||
|
return await structuredLlm.invoke(
|
||||||
|
`
|
||||||
|
当前时间为 ${new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })}
|
||||||
|
你是一个专业的语义解析工程师,给定以下用户输入,帮我解析出开始时间和结束时间
|
||||||
|
如果不包含时间信息,请返回当天的起始时间到当前时间
|
||||||
|
用户输入:
|
||||||
|
\`\`\`
|
||||||
|
${userInput.replaceAll("`", " ")}
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据聊天历史回答用户的问题
|
||||||
|
* @param userInput 用户输入
|
||||||
|
* @param chatHistory 聊天历史
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const queryWithChatHistory = async (userInput: string, chatHistory: string) => {
|
||||||
|
const model = await getDeepseekModel(0.5)
|
||||||
|
return await model.invoke(
|
||||||
|
`
|
||||||
|
当前时间为 ${new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })}
|
||||||
|
你是一个专业的聊天记录分析工程师,给定以下用户输入和聊天历史,帮我回答用户的问题
|
||||||
|
如果无法回答或者用户的问题不清晰,请引导用户问出更具体的问题
|
||||||
|
|
||||||
|
用户输入:
|
||||||
|
\`\`\`
|
||||||
|
${userInput.replaceAll("`", " ")}
|
||||||
|
\`\`\`
|
||||||
|
聊天历史:
|
||||||
|
\`\`\`
|
||||||
|
${chatHistory.replaceAll("`", " ")}
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const llm = {
|
||||||
|
getDeepseekModel,
|
||||||
|
parseTime,
|
||||||
|
queryWithChatHistory,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default llm
|
Loading…
x
Reference in New Issue
Block a user