diff --git a/README.md b/README.md
index 173deda..197a352 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,7 @@ git clone https://gitee.com/kyrzy0416/rconsole-plugin.git ./plugins/rconsole-plu
git clone https://github.com/zhiyu1998/rconsole-plugin.git ./plugins/rconsole-plugin/
```
-2.【必要】在`Yunzai-Bot / Miao-Yunzai`目录下安装axios(0.27.2)、魔法工具(tunnel)、二维码处理工具(qrcode)、高性能下载队列(p-queue)
+2.【必要】在`Yunzai-Bot / Miao-Yunzai`目录下安装axios(0.27.2)、魔法工具(tunnel)、二维码处理工具(qrcode)、高性能下载队列(p-queue)、用于拉格朗日(ws)、用于识图(openai)
```shell
@@ -198,6 +198,21 @@ git clone -b 1.6.7-lts https://gitee.com/kyrzy0416/rconsole-plugin.git
+### 🤖 关于识图 [beta功能]
+
+R 插件集成了我的新作品`gpt2txt`:https://github.com/zhiyu1998/gpt2txt
+
+使用需要在锅巴 or tools.yaml修改以下内容:
+```yaml
+aiBaseURL: '' # 用于识图的接口,kimi默认接口为:https://api.moonshot.cn,其他服务商自己填写
+aiApiKey: '' # 用于识图的api key,kimi接口申请:https://platform.moonshot.cn/console/api-keys
+aiModel: 'claude-3-haiku-20240307' # 模型,使用kimi不用填写,其他要填写
+```
+
+`Kimi`用户只需填写`aiBaseURL` 和 `aiApiKey`,其他用户都需要填写!效果展示如下:
+
+![]()
+
## 🤺 R插件交流群
扫码不行就:575663150
diff --git a/apps/query.js b/apps/query.js
index 4f161d3..885e24d 100644
--- a/apps/query.js
+++ b/apps/query.js
@@ -4,12 +4,19 @@ import fetch from "node-fetch";
import puppeteer from "../../../lib/puppeteer/puppeteer.js";
// http库
import axios from "axios";
+// url库
+import url from 'url';
// 常量
import { CAT_LIMIT } from "../constants/constant.js";
+// 配置文件
+import config from "../model/index.js";
// 书库
import { getYiBook, getZBook, getZHelper } from "../utils/books.js";
// 工具类
import TokenBucket from '../utils/token-bucket.js'
+import { downloadImg } from "../utils/common.js";
+import { checkAndRemoveFile, toBase64 } from "../utils/file.js";
+import { OpenaiBuilder } from "../utils/openai-builder.js";
export class query extends plugin {
/**
@@ -56,9 +63,23 @@ export class query extends plugin {
{
reg: "^#(wiki|百科)(.*)$",
fnc: "wiki",
+ },
+ {
+ reg: "^识图",
+ fnc: "openAiOCR"
}
],
});
+ // 配置文件
+ this.toolsConfig = config.getConfig("tools");
+ // 视频保存路径
+ this.defaultPath = this.toolsConfig.defaultPath;
+ // ai接口
+ this.aiBaseURL = this.toolsConfig.aiBaseURL;
+ // ai api key
+ this.aiApiKey = this.toolsConfig.aiApiKey;
+ // ai模型
+ this.aiModel = this.toolsConfig.aiModel;
}
async doctor(e) {
@@ -317,6 +338,66 @@ export class query extends plugin {
return true;
}
+ // 识图
+ async openAiOCR(e) {
+ if (e.source) {
+ let reply;
+ if (e.isGroup) {
+ reply = (await e.group.getChatHistory(this.e.source.seq, 1)).pop()?.message;
+ } else {
+ reply = (await e.friend.getChatHistory(this.e.source.time, 1)).pop()?.message;
+ }
+ if (reply) {
+ for (let val of reply) {
+ if (val.type == "image") {
+ e.img = [val.url];
+ break;
+ }
+ }
+ }
+ }
+
+ if (!e.img) {
+ this.setContext('openAiProcess');
+ await e.reply("「R插件 x 月之暗面 Kimi」联合识别提醒你:请发送图片!", false, { at: true });
+ } else {
+ this.openAiProcess();
+ }
+ }
+
+ /**
+ * AI引擎提供图像识别能力
+ * @return {Promise}
+ */
+ async openAiProcess() {
+ if (!this.e.img) {
+ e.reply("「R插件 x 月之暗面 Kimi」联合识别提醒你:无法找到图片!")
+ return true;
+ }
+ const img = this.e.img.find(item => item.startsWith("http"));
+ const parsedUrl = url.parse(img);
+ const pathArray = parsedUrl.pathname.split('/');
+ const filenameWithExtension = pathArray[pathArray.length - 1];
+ const path = `${ this.defaultPath }${ this.e.group_id || this.e.user_id }`
+ // 下载图片
+ const imgPath = await downloadImg(img, path, filenameWithExtension);
+ // 构造OpenAI引擎
+ try {
+ const { model, ans } = await new OpenaiBuilder()
+ .setBaseURL(this.aiBaseURL)
+ .setApiKey(this.aiApiKey)
+ .setModel(this.aiModel)
+ .setPath(imgPath)
+ .build();
+ this.e.reply(`「R插件 x ${ model }」联合识别:\n${ ans }`);
+ await checkAndRemoveFile(filenameWithExtension);
+ } catch (err) {
+ e.reply("「R插件 x 月之暗面 Kimi」联合识别提醒你:无法找到图片路径!")
+ logger.error(err);
+ }
+ return true;
+ }
+
/**
* 限制用户调用(默认1分钟1次)
* @param e
diff --git a/config/tools.yaml b/config/tools.yaml
index 50ab878..0776534 100644
--- a/config/tools.yaml
+++ b/config/tools.yaml
@@ -18,3 +18,7 @@ videoDownloadConcurrency: 1 # 下载视频是否使用多线程,如果不使
lagrangeForwardWebSocket: 'ws://127.0.0.1:9091/' # 格式:ws://地址:端口/,拉格朗日正向连接地址,用于适配拉格朗日上传群文件,解决部分用户无法查看视频问题
autoclearTrashtime: '0 0 8 * * ?' #每天早上8点自动清理视频缓存,cron可自定义时间
+
+aiBaseURL: '' # 用于识图的接口,kimi默认接口为:https://api.moonshot.cn,其他服务商自己填写
+aiApiKey: '' # 用于识图的api key,kimi接口申请:https://platform.moonshot.cn/console/api-keys
+aiModel: 'claude-3-haiku-20240307' # 模型,使用kimi不用填写,其他要填写
\ No newline at end of file
diff --git a/guoba.support.js b/guoba.support.js
index 0ad8b85..4cd5927 100644
--- a/guoba.support.js
+++ b/guoba.support.js
@@ -151,6 +151,39 @@ export function supportGuoba() {
componentProps: {
placeholder: "请输入拉格朗日正向WebSocket连接地址",
},
+ },
+ {
+ field: "tools.aiBaseURL",
+ label: "AI接口地址",
+ bottomHelpMessage:
+ "支持Kimi、OpenAI、Claude等",
+ component: "Input",
+ required: false,
+ componentProps: {
+ placeholder: "请输入AI接口地址",
+ },
+ },
+ {
+ field: "tools.aiApiKey",
+ label: "AI的key",
+ bottomHelpMessage:
+ "服务商提供的api key",
+ component: "Input",
+ required: false,
+ componentProps: {
+ placeholder: "请输入AI的key",
+ },
+ },
+ {
+ field: "tools.aiModel",
+ label: "AI的模型",
+ bottomHelpMessage:
+ "默认使用的是Claude,也可以自定义模型",
+ component: "Input",
+ required: false,
+ componentProps: {
+ placeholder: "请输入AI的模型,例如:claude-3-haiku-20240307,使用kimi则不用填写",
+ },
}
],
getConfigData() {
diff --git a/img/imageRecognition.webp b/img/imageRecognition.webp
new file mode 100644
index 0000000..15071d2
Binary files /dev/null and b/img/imageRecognition.webp differ
diff --git a/package.json b/package.json
index 770224e..92420a7 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"tunnel": "^0.0.6",
"qrcode": "^1.5.3",
"p-queue": "^8.0.1",
- "ws": "^8.17.0"
+ "ws": "^8.17.0",
+ "openai": "^4.47.1"
}
}
diff --git a/utils/file.js b/utils/file.js
index 88d4b11..3592f12 100644
--- a/utils/file.js
+++ b/utils/file.js
@@ -101,3 +101,41 @@ export async function copyFiles(srcDir, destDir) {
}
return null;
}
+
+/**
+ * 转换路径图片为base64格式
+ * @param filePath 图片路径
+ * @return {Promise}
+ */
+export async function toBase64(filePath) {
+ try {
+ // 读取文件数据
+ const fileData = await fs.readFileSync(filePath);
+ // 将文件数据转换为Base64字符串
+ const base64Data = fileData.toString('base64');
+ // 返回Base64字符串
+ return `data:${getMimeType(filePath)};base64,${base64Data}`;
+ } catch (error) {
+ throw new Error(`读取文件时出错: ${error.message}`);
+ }
+}
+
+/**
+ * 辅助函数:根据文件扩展名获取MIME类型
+ * @param filePath
+ * @return {*|string}
+ */
+function getMimeType(filePath) {
+ const mimeTypes = {
+ '.jpg': 'image/jpeg',
+ '.jpeg': 'image/jpeg',
+ '.png': 'image/png',
+ '.gif': 'image/gif',
+ '.pdf': 'application/pdf',
+ '.txt': 'text/plain',
+ // 添加其他文件类型和MIME类型的映射
+ };
+
+ const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();
+ return mimeTypes[ext] || 'application/octet-stream';
+}
diff --git a/utils/openai-builder.js b/utils/openai-builder.js
new file mode 100644
index 0000000..65e3d7f
--- /dev/null
+++ b/utils/openai-builder.js
@@ -0,0 +1,117 @@
+import { toBase64 } from "./file.js";
+// openai库
+import OpenAI from 'openai';
+// fs
+import fs from "node:fs";
+
+export class OpenaiBuilder {
+ constructor() {
+ this.baseURL = "https://api.moonshot.cn"; // 默认模型
+ this.apiKey = ""; // 默认API密钥
+ this.prompt = "描述一下这个图片"; // 默认提示
+ this.model = 'claude-3-haiku-20240307'
+ this.path = ''; // 上传文件的路径
+ }
+
+ setBaseURL(baseURL) {
+ this.baseURL = baseURL + "/v1";
+ return this;
+ }
+
+ setApiKey(apiKey) {
+ this.apiKey = apiKey;
+ return this;
+ }
+
+ setPrompt(prompt) {
+ this.prompt = prompt;
+ return this;
+ }
+
+ setModel(model) {
+ this.model = model;
+ return this;
+ }
+
+ setPath(path) {
+ this.path = path;
+ return this;
+ }
+
+ async build() {
+ if (this.path === '') {
+ throw Error("无法获取到文件路径");
+ return null;
+ }
+ // logger.info(this.baseURL, this.apiKey)
+ // 创建客户端
+ this.client = new OpenAI({
+ baseURL: this.baseURL,
+ apiKey: this.apiKey
+ });
+ // 构建
+ if (this.baseURL.includes("api.moonshot.cn")) {
+ return await this.kimi(this.path);
+ } else {
+ return await this.openai(this.path);
+ }
+ }
+
+ async kimi(path) {
+ let file_object = await this.client.files.create({
+ file: fs.createReadStream(path),
+ purpose: "file-extract"
+ })
+ let file_content = await (await this.client.files.content(file_object.id)).text()
+ // 请求OpenAI
+ const completion = await this.client.chat.completions.create({
+ model: "moonshot-v1-8k",
+ messages: [
+ {
+ "role": "system",
+ "content": file_content,
+ },
+ {
+ role: "user",
+ content: this.prompt
+ },
+ ],
+ });
+
+ return {
+ "model": "月之暗面 Kimi",
+ "ans": completion.choices[0].message.content
+ }
+ }
+
+ async openai(path) {
+ // 转换base64
+ const pic = await toBase64(path);
+ const completion = await this.client.chat.completions.create({
+ model: this.model,
+ messages: [
+ {
+ role: "user",
+ content: [
+ {
+ type: "image_url",
+ image_url: {
+ url: pic,
+ },
+ },
+ {
+ type: "text",
+ text: this.prompt,
+ },
+ ],
+ },
+ ],
+ use_search: false,
+ });
+
+ return {
+ "model": "OpenAI",
+ "ans": completion.choices[0].message.content
+ }
+ }
+}
\ No newline at end of file