diff --git a/README.md b/README.md index 2836224..4a2aece 100644 --- a/README.md +++ b/README.md @@ -198,23 +198,27 @@ git clone -b 1.6.7-lts https://gitee.com/kyrzy0416/rconsole-plugin.git -### 🤖 关于识图 [beta功能] +### 微信文章总结 (完全免费总结) -R 插件集成了我的新作品`gpt2txt`:https://github.com/zhiyu1998/gpt2txt +官方Kimi API 暂时没有看到可以联网搜索的选项,所以选用开源的[kimi-free-api](https://github.com/LLM-Red-Team/kimi-free-api) + +1. 部署 kimi-free-api + +```shell +docker run -it -d --init --name kimi-free-api -p 8000:8000 -e TZ=Asia/Shanghai vinlic/kimi-free-api:latest +``` + +2. 更改下面两个选项,自行修改 `tools.yaml` 或者锅巴: -使用需要在锅巴 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`,其他用户都需要填写!效果展示如下: +- aiBaseURL:你服务器的地址 +- aiApiKey:kimi 的 `refresh_token` (F12 -> 应用(Application) -> Local Storage -> `https://kimi.moonshot.cn` -> 找到) -![imageRecognition.webp](./img/imageRecognition.webp) - -- [Kimi开放平台](https://platform.moonshot.cn/console/info) -- [OpenAI](https://platform.openai.com/api-keys) +3. 开始游玩 ## 🤺 R插件交流群 扫码不行就:575663150 @@ -272,6 +276,7 @@ aiModel: 'claude-3-haiku-20240307' # 模型,使用kimi不用填写,其他要 🌸 感谢以下框架的开源: - [yt-dlp:A youtube-dl fork with additional features and fixes](https://github.com/yt-dlp/yt-dlp) - [freyr-js](https://github.com/miraclx/freyr-js) +- [kimi-free-api](https://github.com/LLM-Red-Team/kimi-free-api) ## ☕ 请我喝一杯瑞幸咖啡 如果你觉得插件能帮助到你增进好友关系,那么你可以在有条件的情况下[请我喝一杯瑞幸咖啡](https://afdian.net/a/zhiyu1998),这是我开源这个插件的最大动力! diff --git a/apps/query.js b/apps/query.js index 59c43f9..ac01ff7 100644 --- a/apps/query.js +++ b/apps/query.js @@ -4,19 +4,14 @@ 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, OCR_PROMPT } from "../constants/constant.js"; +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 { /** @@ -63,10 +58,6 @@ export class query extends plugin { { reg: "^#(wiki|百科)(.*)$", fnc: "wiki", - }, - { - reg: "^识图", - fnc: "openAiOCR" } ], }); @@ -74,12 +65,6 @@ export class query extends plugin { 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) { @@ -338,69 +323,6 @@ 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) - .setPrompt(OCR_PROMPT) - .setPath(imgPath) - .build(); - const ocrAns = ans.split("▲"); - logger.info(ocrAns) - this.e.reply(`「R插件 x ${ model }」联合识别:\n描述:${ ocrAns[1] } \nOCR识别结果:${ ocrAns[2] }`); - await checkAndRemoveFile(filenameWithExtension); - } catch (err) { - this.e.reply("「R插件 x 月之暗面 Kimi」联合识别提醒你:无法找到图片路径!") - logger.error(err); - } - return true; - } - /** * 限制用户调用(默认1分钟1次) * @param e diff --git a/apps/tools.js b/apps/tools.js index 2ca1402..ea6a88f 100644 --- a/apps/tools.js +++ b/apps/tools.js @@ -27,6 +27,7 @@ import { douyinTypeMap, REDIS_YUNZAI_ISOVERSEA, REDIS_YUNZAI_LAGRANGE, + SUMMARY_PROMPT, transMap, TWITTER_BEARER_TOKEN, XHS_NO_WATERMARK_HEADER, @@ -68,6 +69,7 @@ import GeneralLinkAdapter from "../utils/general-link-adapter.js"; import { mid2id } from "../utils/weibo.js"; import { LagrangeAdapter } from "../utils/lagrange-adapter.js"; import path from "path"; +import { OpenaiBuilder } from "../utils/openai-builder.js"; export class tools extends plugin { /** @@ -171,6 +173,10 @@ export class tools extends plugin { { reg: "(music.apple.com|open.spotify.com)", fnc: "freyr" + }, + { + reg: "mp.weixin", + fnc: "weixin" } ], }); @@ -198,6 +204,12 @@ export class tools extends plugin { this.queue = new PQueue({ concurrency: Number(this.toolsConfig.queueConcurrency) }); // 视频下载的并发数量 this.videoDownloadConcurrency = this.toolsConfig.videoDownloadConcurrency; + // ai接口 + this.aiBaseURL = this.toolsConfig.aiBaseURL; + // ai api key + this.aiApiKey = this.toolsConfig.aiApiKey; + // ai模型 + this.aiModel = this.toolsConfig.aiModel; } // 翻译插件 @@ -1446,7 +1458,7 @@ export class tools extends plugin { logger.info(result.toString()); // 获取信息 const { title, album, artist } = await this.parseFreyrLog(result.toString()); - e.reply(`识别:${freyrName},${ title }--${ artist }\n${ album }`); + e.reply(`识别:${ freyrName },${ title }--${ artist }\n${ album }`); // 判断是否是海外服务器 const isOversea = await this.isOverseasServer(); // 国内服务器解决方案 @@ -1481,7 +1493,7 @@ export class tools extends plugin { // 读取目录中的所有文件和文件夹 fs.readdir(musicPath, (err, files) => { if (err) { - e.reply(`${freyrName}解析出错,请查看日志!`) + e.reply(`${ freyrName }解析出错,请查看日志!`) logger.error('读取目录时出错:', err); return; } @@ -1496,11 +1508,11 @@ export class tools extends plugin { }); }); } else { - e.reply(`下载失败!没有找到${freyrName}下载下来文件!`); + e.reply(`下载失败!没有找到${ freyrName }下载下来文件!`); } // 计数 tools.#amCount += 1; - logger.info(`当前${freyrName}已经下载了:${ tools.#amCount }次`); + logger.info(`当前${ freyrName }已经下载了:${ tools.#amCount }次`); // 定时清理 if (tools.#amCount >= 5) { await deleteFolderRecursive(currentWorkingDirectory + "/am"); @@ -1528,6 +1540,21 @@ export class tools extends plugin { return { title, album, artist }; } + async weixin(e) { + const urlReg = /(?:https?:\/\/)?mp\.weixin\.qq\.com\/[A-Za-z\d._?%&+\-=\/#]*/g; + const wxUrl = urlReg.exec(e.msg)?.[0]; + const builder = await new OpenaiBuilder() + .setBaseURL(this.aiBaseURL) + .setApiKey(this.aiApiKey) + .setModel(this.aiModel) + .setPrompt(SUMMARY_PROMPT) + .build(); + e.reply(`识别:微信文章,正在为您总结,请稍等...`); + const { ans: kimiAns, model } = await builder.kimi(wxUrl); + e.reply(`「R插件 x ${ model }」联合为您总结内容:\n${ kimiAns }`) + return true; + } + /** * 哔哩哔哩下载 * @param title diff --git a/constants/constant.js b/constants/constant.js index b519c1f..af0b886 100644 --- a/constants/constant.js +++ b/constants/constant.js @@ -89,4 +89,6 @@ export const OCR_PROMPT = ` ▲ 首先,对将要用作替代文本的图像进行简短描述。不要在描述中描述或提取文本。 ▲ 图片中提取的文本,在适当的地方使用换行符。如果文本被某物遮挡,请使其不受阻隔,以便阅读。如果图像中没有文本,只需回复描述。不要包含任何其他信息。 示例:▲ 文本编辑器中的代码行。▲ const x = 5; const y = 10; const z = x + y; console.log(z); -` \ No newline at end of file +` + +export const SUMMARY_PROMPT = `请返回您仔细阅读正文后精心写成的详尽笔记` \ No newline at end of file diff --git a/utils/openai-builder.js b/utils/openai-builder.js index 65e3d7f..dc6283f 100644 --- a/utils/openai-builder.js +++ b/utils/openai-builder.js @@ -39,31 +39,43 @@ export class OpenaiBuilder { } 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); + return this; + } + + async kimi(query) { + // 请求Kimi + const completion = await this.client.chat.completions.create({ + model: "moonshot-v1-8k", + messages: [ + { + "role": "system", + "content": this.prompt, + }, + { + role: "user", + content: query + }, + ], + }); + return { + "model": "月之暗面 Kimi", + "ans": completion.choices[0].message.content } } - async kimi(path) { + async kimi_pic(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 + // 请求Kimi const completion = await this.client.chat.completions.create({ model: "moonshot-v1-8k", messages: [ @@ -84,7 +96,7 @@ export class OpenaiBuilder { } } - async openai(path) { + async openai_pic(path) { // 转换base64 const pic = await toBase64(path); const completion = await this.client.chat.completions.create({