fix: 修复插件对接的 BUG

This commit is contained in:
lrhh123
2024-07-01 23:33:20 +08:00
parent 9643649efa
commit fbb02b544c
12 changed files with 83 additions and 175 deletions
+14 -8
View File
@@ -1,12 +1,12 @@
// 固定会传递的上下文参数
export const CTX_APP_NAME = 'app_name';
export const CTX_APP_ID = 'app_id';
export const CTX_INSTANCE_ID = 'instance_id';
export const CTX_APP_NAME = 'CTX_APP_NAME';
export const CTX_APP_ID = 'CTX_APP_ID';
export const CTX_INSTANCE_ID = 'CTX_INSTANCE_ID';
export const CTX_USERNAME = 'username'; // 当前操作的用户名
export const CTX_PLATFORM = 'platform'; // 当前所在平台
export const CTX_HAS_NEW_MESSAGE = 'has_new_message'; // 是否有新消息
export const CTX_HAS_GROUP_MESSAGE = 'has_group_message'; // 是否有群消息
export const CTX_USERNAME = 'CTX_USERNAME'; // 当前操作的用户名
export const CTX_PLATFORM = 'CTX_PLATFORM'; // 当前所在平台
export const CTX_HAS_NEW_MESSAGE = 'CTX_HAS_NEW_MESSAGE'; // 是否有新消息
export const CTX_HAS_GROUP_MESSAGE = 'CTX_HAS_GROUP_MESSAGE'; // 是否有群消息
// 电商平台
export const CTX_CURRENT_GOODS = 'CTX_CURRENT_GOODS'; // 当前商品
@@ -15,6 +15,12 @@ export const CTX_MEMBER_TAG = 'CTX_MEMBER_TAG'; // 会员标签
export const CTX_FAN_TAG = 'CTX_FAN_TAG'; // 粉丝标签
export const CTX_NEW_CUSTOMER_TAG = 'CTX_NEW_CUSTOMER_TAG'; // 新客标签
export const CTX_ORDER_STATUS = 'CTX_ORDER_STATUS'; // 订单状态
export const CTX_ORDER_ID = 'CTX_ORDER_ID'; // 订单 ID
export const CTX_ORDER_AMOUNT = 'CTX_ORDER_AMOUNT'; // PDD 平台特有 [订单金额]
export const CTX_GOODS_SPEC = 'CTX_GOODS_SPEC'; // PDD 平台特有 [商品规格]
export const CTX_LOGISTICS_STATUS = 'CTX_LOGISTICS_STATUS'; // 物流状态
export const PluginDefaultRunCode = `
const cc = require('config_srv');
const rp = require('reply_srv');
@@ -27,5 +33,5 @@ const rp = require('reply_srv');
*/
async function main(ctx, messages) {
const cfg = await cc.get(ctx);
return await rp.getDefaultReply(cfg, ctx, messages);
return await rp.getReply(cfg, ctx, messages);
}`;
+2 -2
View File
@@ -86,7 +86,7 @@ export async function checkAndAddFields(sequelize: Sequelize) {
.addColumn('n_config', 'truncate_word_count', {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 210,
defaultValue: 4000,
});
}
@@ -260,7 +260,7 @@ export function initConfig(sequelize: Sequelize) {
truncate_word_count: {
type: DataTypes.INTEGER,
allowNull: true,
defaultValue: 210,
defaultValue: 4000,
},
truncate_word_key: {
type: DataTypes.STRING,
+5 -6
View File
@@ -97,16 +97,15 @@ export class DispatchService {
},使用默认回复`,
);
reply = {
content: cfg.default_reply || 'Failed to execute plugin',
type: 'TEXT',
};
reply = await this.messageService.getDefaultReply(cfg);
}
callback(reply);
// 回复后保存消息
await this.messageController.saveMessages(ctxMap, reply, msgs);
if (reply.type !== 'NO_REPLY') {
// 回复后保存消息
await this.messageController.saveMessages(ctxMap, reply, msgs);
}
});
}
+32 -19
View File
@@ -66,11 +66,33 @@ export class MessageService {
* @param messages
* @returns
*/
public async getDefaultReply(
public async getDefaultReply(cfg: Config): Promise<ReplyDTO> {
let reply = {
type: 'TEXT' as MessageType,
content: cfg.default_reply || '当前消息有点多,我稍后再回复你',
};
const replyContent = await this.choseRandomReply(reply.content);
reply = {
type: reply.type as MessageType,
content: replyContent,
};
return reply;
}
/**
* 获取回复
* @param cfg
* @param ctx
* @param messages
* @returns
*/
public async getReply(
cfg: Config,
ctx: Context,
messages: MessageDTO[],
): Promise<{ type: string; content: string }> {
): Promise<ReplyDTO> {
// 先检查是否存在用户的消息
const lastUserMsg = messages
.slice()
@@ -78,15 +100,9 @@ export class MessageService {
.find((msg) => msg.role === 'OTHER');
let hasDefaultReply = true;
let reply = null;
let reply = {
type: 'TEXT',
content: cfg.default_reply || '当前消息有点多,我稍后再回复你',
};
if (!lastUserMsg) {
this.log.warn(`未匹配到用户消息,所以使用默认回复: ${reply.content}`);
} else {
if (lastUserMsg) {
if (cfg.has_transfer) {
// 检查是否需要转接
const isTransfer = await this.matchTransferKeyword(ctx, lastUserMsg);
@@ -94,8 +110,8 @@ export class MessageService {
this.log.info('需要转接');
hasDefaultReply = false;
return {
type: 'TRANSFER',
content: '',
type: 'TRANSFER' as MessageType,
content: '',
};
}
}
@@ -143,20 +159,17 @@ export class MessageService {
}
if (hasDefaultReply) {
const replyContent = await this.choseRandomReply(reply.content);
reply = {
type: reply.type as MessageType,
content: replyContent,
};
reply = await this.getDefaultReply(cfg);
this.log.warn(`未匹配到用户消息,所以使用默认回复: ${reply.content}`);
}
if (cfg.has_replace) {
if (reply.type === 'TEXT') {
if (reply && reply.type === 'TEXT') {
reply.content = await this.matchReplaceKeyword(ctx, reply.content);
}
}
return reply;
return reply as ReplyDTO;
}
public async createTextReply(content: string) {
+6 -12
View File
@@ -95,18 +95,12 @@ export class PluginService {
throw new Error('Plugin not found');
}
const cfg = await this.configController.get(ctx);
try {
const { data } = await this.executePluginCode(plugin.code, ctx, messages);
return { ...data };
} catch (error) {
console.error('Plugin execution error:', error);
return {
type: 'TEXT',
content:
cfg.default_reply || 'An error occurred while executing the plugin',
};
this.log.error(`插件执行失败: ${error}`);
throw error;
}
}
@@ -211,15 +205,15 @@ export class PluginService {
return { data: data as ReplyDTO, consoleOutput };
}
this.log.error('未返回有效响应');
throw new Error('Plugin function did not return a valid response');
this.log.error(`未返回有效响应,插件返回数据: ${JSON.stringify(data)}`);
throw new Error('Plugin function did not return a valid response', data);
} catch (error: any) {
console.error('Plugin execution error:', error);
this.log.error(`运行插件的日志信息: ${JSON.stringify(consoleOutput)}`);
this.log.error(
`回复失败: ${error instanceof Error ? error.message : String(error)}`,
);
error.consoleOutput = consoleOutput;
throw error;
}
+1 -1
View File
@@ -13,7 +13,7 @@ export enum PlatformTypeEnum {
}
export type RoleType = 'SELF' | 'OTHER' | 'SYSTEM';
export type MessageType = 'TEXT' | 'IMAGE' | 'VIDEO' | 'FILE';
export type MessageType = 'TEXT' | 'IMAGE' | 'VIDEO' | 'FILE' | 'NO_REPLY';
export type Context = Map<string, string>;
+1 -1
View File
@@ -24,7 +24,7 @@ export interface PlatformSettings {
}
export type RoleType = 'SELF' | 'OTHER' | 'SYSTEM';
export type MessageType = 'TEXT' | 'IMAGE' | 'VIDEO' | 'FILE';
export type MessageType = 'TEXT' | 'IMAGE' | 'VIDEO' | 'FILE' | 'NO_REPLY';
export interface Reply {
content: string;
+20 -9
View File
@@ -16,7 +16,7 @@ export const PluginExtraLib = `
type RoleType = 'SELF' | 'OTHER' | 'SYSTEM';
type MessageType = 'TEXT' | 'IMAGE' | 'VIDEO' | 'FILE';
type MessageType = 'TEXT' | 'IMAGE' | 'VIDEO' | 'FILE' | 'NO_REPLY';
type Message = {
sender: string; // 发送者
@@ -42,7 +42,7 @@ const rp = require('reply_srv');
*/
async function main(ctx, messages) {
const cfg = await cc.get(ctx);
return await rp.getDefaultReply(cfg, ctx, messages);
return await rp.getReply(cfg, ctx, messages);
}`;
export const LLMTypeList = [
@@ -168,14 +168,14 @@ export const ModelList = [
];
// 固定会传递的上下文参数
export const CTX_APP_NAME = 'app_name';
export const CTX_APP_ID = 'app_id';
export const CTX_INSTANCE_ID = 'instance_id';
export const CTX_APP_NAME = 'CTX_APP_NAME';
export const CTX_APP_ID = 'CTX_APP_ID';
export const CTX_INSTANCE_ID = 'CTX_INSTANCE_ID';
export const CTX_USERNAME = 'username'; // 当前操作的用户名
export const CTX_PLATFORM = 'platform'; // 当前所在平台
export const CTX_HAS_NEW_MESSAGE = 'has_new_message'; // 是否有新消息
export const CTX_HAS_GROUP_MESSAGE = 'has_group_message'; // 是否有群消息
export const CTX_USERNAME = 'CTX_USERNAME'; // 当前操作的用户名
export const CTX_PLATFORM = 'CTX_PLATFORM'; // 当前所在平台
export const CTX_HAS_NEW_MESSAGE = 'CTX_HAS_NEW_MESSAGE'; // 是否有新消息
export const CTX_HAS_GROUP_MESSAGE = 'CTX_HAS_GROUP_MESSAGE'; // 是否有群消息
// 电商平台
export const CTX_CURRENT_GOODS = 'CTX_CURRENT_GOODS'; // 当前商品
@@ -184,6 +184,12 @@ export const CTX_MEMBER_TAG = 'CTX_MEMBER_TAG'; // 会员标签
export const CTX_FAN_TAG = 'CTX_FAN_TAG'; // 粉丝标签
export const CTX_NEW_CUSTOMER_TAG = 'CTX_NEW_CUSTOMER_TAG'; // 新客标签
export const CTX_ORDER_STATUS = 'CTX_ORDER_STATUS'; // 订单状态
export const CTX_ORDER_ID = 'CTX_ORDER_ID'; // 订单 ID
export const CTX_ORDER_AMOUNT = 'CTX_ORDER_AMOUNT'; // PDD 平台特有 [订单金额]
export const CTX_GOODS_SPEC = 'CTX_GOODS_SPEC'; // PDD 平台特有 [商品规格]
export const CTX_LOGISTICS_STATUS = 'CTX_LOGISTICS_STATUS'; // 物流状态
export const ContextKeys = [
CTX_APP_NAME,
CTX_APP_ID,
@@ -197,6 +203,11 @@ export const ContextKeys = [
CTX_MEMBER_TAG,
CTX_FAN_TAG,
CTX_NEW_CUSTOMER_TAG,
CTX_ORDER_STATUS,
CTX_ORDER_ID,
CTX_ORDER_AMOUNT,
CTX_GOODS_SPEC,
CTX_LOGISTICS_STATUS,
];
export const MockCtx = new Map<string, string>([
@@ -9,5 +9,5 @@ const rp = require('reply_srv');
*/
async function main(ctx, messages) {
const cfg = await cc.get(ctx);
return await rp.getDefaultReply(cfg, ctx, messages);
return await rp.getReply(cfg, ctx, messages);
}`;
@@ -1,52 +0,0 @@
export const QIANNIU_GOODS_PLUGIN = `const cc = require('config_srv');
const rp = require('reply_srv');
/**
* 插件主函数
* @param {AppContext} ctx - 上下文信息
* @param {Message[]} messages - 消息数组
* @returns {Reply} 插件执行结果
*/
async function main(ctx, messages) {
const appId = ctx.get('app_id');
const cfg = cc.get(ctx);
if (appId === 'win_qianniu') {
// 取得用户最后一条消息
const lastMessage = messages[messages.length - 1];
// 可以取得它的消息内容
const goodsId = ctx.get('CTX_CURRENT_GOODS_ID');
const goodsName = ctx.get('CTX_CURRENT_GOODS');
// 构建一个消息
const msg = {
sender: 'SYSTEM',
role: 'OTHER',
content: \`1. 当前客户询问的商品 ID 是:
"""
\${goodsId}
"""
2. 咨询的商品名称是:
"""
\${goodsName}
"""
3. 当前客户的问题是:
"""
\${lastMessage.content}
"""
\`,
type: 'TEXT',
};
// 替换用户的这条消息
messages = [msg];
const reply = await rp.getDefaultReply(cfg, ctx, messages);
return reply;
}
const reply = await rp.getDefaultReply(cfg, ctx, messages);
return reply;
}
`;
@@ -1,63 +0,0 @@
export const WECHAT_NEWS_PLUGIN = `const cc = require('config_srv');
const rp = require('reply_srv');
const axios = require('axios');
/**
* 插件主函数
* @param {AppContext} ctx - 上下文信息
* @param {Message[]} messages - 消息数组
* @returns {Reply} 插件执行结果
*/
async function main(ctx, messages) {
const appId = ctx.get('app_id');
const cfg = cc.get(ctx);
if (appId === 'win_wechat') {
// 取得用户最后一条消息
const lastMessage = messages[messages.length - 1];
// 可以取得它的消息内容
const hasGroup = ctx.get('CTX_HAS_GROUP_MESSAGE');
// 检查当前消息是否携带 @BOT 指令
const hasBotCommand = lastMessage.content.includes('@BOT');
// 如果还携带了 [热榜] 关键词
const hasNewsKeyword = lastMessage.content.includes('[热榜]');
if (hasGroup && hasBotCommand && hasNewsKeyword) {
try {
// 使用 axios 请求新闻数据
const response = await axios.get('https://api.vvhan.com/api/hotlist/all');
const newsData = response.data;
if (newsData.success) {
const newsList = newsData.data;
let replyContent = \`\${new Date().toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long' })}\\n\\n\`;
// 遍历每个平台的新闻
newsList.forEach(platform => {
replyContent += \`\${platform.name} \${platform.subtitle} 更新于 \${platform.update_time}\\n\`;
platform.data.slice(0, 3).forEach((news, index) => {
replyContent += \`\${index + 1}. \${news.title} 热度: \${news.hot}\\n\`;
});
replyContent += '\\n';
});
// 发送回复
return rp.createTextReply(replyContent);
} else {
return rp.createTextReply('无法获取新闻数据,请稍后再试。');
}
} catch (error) {
console.error('获取新闻数据出错:', error);
return rp.createTextReply('获取新闻数据时出错,请稍后再试。');
}
}
}
// 默认回复
const reply = await rp.getDefaultReply(cfg, ctx, messages);
return reply;
}
`;
@@ -308,7 +308,7 @@ const GeneralSettings = ({
</Flex>
<Slider
min={50}
max={210}
max={4000}
step={5}
value={config.truncateWordCount}
onChange={(truncateWordCount) =>