UNPKG

koishi-plugin-koishiaimix

Version:

基于AiHubMix API的AI对话和绘图插件,支持多种AI模型的聊天对话和图像生成功能,现已支持xAI Grok绘图

1,465 lines (1,448 loc) 49.7 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name2 in all) __defProp(target, name2, { get: all[name2], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Config: () => Config, apply: () => apply, name: () => name, usage: () => usage }); module.exports = __toCommonJS(src_exports); var import_koishi4 = require("koishi"); // src/services/api.ts var import_axios = __toESM(require("axios")); var import_koishi = require("koishi"); var import_form_data = __toESM(require("form-data")); var AiHubMixAPI = class { constructor(apiKey, baseUrl, timeout = 6e4, retryCount = 2) { this.apiKey = apiKey; this.baseUrl = baseUrl; this.timeout = timeout; this.retryCount = retryCount; this.logger = new import_koishi.Logger("aihubmix-api"); this.client = import_axios.default.create({ baseURL: baseUrl, timeout, headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json", "User-Agent": "KoishiAiMix/1.0.0" } }); this.client.interceptors.request.use( (config) => { this.logger.debug(`发送请求: ${config.method?.toUpperCase()} ${config.url}`); return config; }, (error) => { this.logger.error("请求拦截器错误:", error); return Promise.reject(error); } ); this.client.interceptors.response.use( (response) => { this.logger.debug(`收到响应: ${response.status} ${response.statusText}`); return response; }, (error) => { this.logger.error("响应拦截器错误:", error.response?.data || error.message); return Promise.reject(error); } ); } static { __name(this, "AiHubMixAPI"); } client; logger; /** * 发送聊天请求 */ async chat(request) { try { this.logger.info(`发送聊天请求,模型: ${request.model}`); const response = await this.client.post("/chat/completions", request); this.logger.info(`聊天请求成功,使用token: ${response.data.usage?.total_tokens || 0}`); return response.data; } catch (error) { this.logger.error("聊天请求失败:", error); throw this.handleError(error); } } /** * 发送图像生成请求 */ async generateImage(request) { try { this.logger.info(`发送绘图请求,模型: ${request.model}`); const isReverseModel = request.model.includes("gpt-4o-image"); const timeout = isReverseModel ? 18e4 : this.timeout; if (isReverseModel) { this.logger.info(`检测到逆向模型,设置超时时间为 ${timeout / 1e3} 秒`); } const response = await this.client.post("/images/generations", request, { timeout }); this.logger.info(`绘图请求成功,生成图片数量: ${response.data.data.length}`); this.logger.debug(`完整API响应:`, JSON.stringify(response.data, null, 2)); return response.data; } catch (error) { this.logger.error("绘图请求失败:", error); throw this.handleError(error); } } /** * 发送Gemini智绘请求 */ async generateImageWithGemini(request) { try { this.logger.info(`发送Gemini智绘请求,模型: ${request.model}`); const timeout = 18e4; this.logger.info(`Gemini智绘设置超时时间为 ${timeout / 1e3} 秒`); const response = await this.client.post("/chat/completions", request, { timeout }); this.logger.info(`Gemini智绘请求成功`); this.logger.debug(`Gemini完整API响应:`, JSON.stringify(response.data, null, 2)); return response.data; } catch (error) { this.logger.error("Gemini智绘请求失败:", error); throw this.handleError(error); } } /** * 发送Imagen绘图请求 */ async generateImageWithImagen(request) { try { this.logger.info(`发送Imagen绘图请求,模型: ${request.model}`); const imagenClient = import_axios.default.create({ baseURL: "https://aihubmix.com/gemini", headers: { "Content-Type": "application/json", "User-Agent": "KoishiAiMix/1.0.0" }, timeout: 18e4 // Imagen可能需要更长时间 }); const imagenPayload = { instances: [ { prompt: request.prompt } ], parameters: { numberOfImages: 1, // 强制设置为1,忽略请求中的数量 aspectRatio: request.config.aspect_ratio // 使用驼峰命名 } }; this.logger.info(`Imagen设置超时时间为 180 秒`); this.logger.info(`Imagen API请求参数:`, JSON.stringify(imagenPayload, null, 2)); const response = await imagenClient.post(`/v1beta/models/${request.model}:predict?key=${this.apiKey}`, imagenPayload); this.logger.info(`Imagen绘图请求成功`); this.logger.info(`Imagen完整API响应:`, JSON.stringify(response.data, null, 2)); const imagenResponse = { generated_images: [] }; if (response.data.predictions && response.data.predictions.length > 0) { for (const prediction of response.data.predictions) { if (prediction.bytesBase64Encoded) { imagenResponse.generated_images.push({ image: { image_bytes: prediction.bytesBase64Encoded } }); } } } return imagenResponse; } catch (error) { this.logger.error("Imagen绘图请求失败:", error); throw this.handleError(error); } } /** * 发送xAI绘图请求 */ async generateImageWithXAI(request, xaiApiKey, xaiBaseUrl) { try { this.logger.info(`发送xAI绘图请求,模型: ${request.model}`); const xaiClient = import_axios.default.create({ baseURL: xaiBaseUrl, headers: { "Authorization": `Bearer ${xaiApiKey}`, "Content-Type": "application/json", "User-Agent": "KoishiAiMix/1.0.0" }, timeout: 12e4 // xAI图像生成可能需要较长时间 }); this.logger.info(`xAI设置超时时间为 120 秒`); this.logger.debug(`xAI API请求参数:`, JSON.stringify(request, null, 2)); const response = await xaiClient.post("/images/generations", request); this.logger.info(`xAI绘图请求成功,生成图片数量: ${response.data.data.length}`); this.logger.debug(`xAI完整API响应:`, JSON.stringify(response.data, null, 2)); return response.data; } catch (error) { this.logger.error("xAI绘图请求失败:", error); throw this.handleError(error); } } /** * 获取可用模型列表 */ async getModels() { try { this.logger.info("获取模型列表"); const response = await this.client.get("/models"); this.logger.info(`获取到 ${response.data.data.length} 个模型`); return response.data; } catch (error) { this.logger.error("获取模型列表失败:", error); throw this.handleError(error); } } /** * 发送图像编辑请求 */ async editImage(image, prompt, mask, options = {}) { try { this.logger.info("发送图像编辑请求"); const formData = new import_form_data.default(); formData.append("image", image, "image.png"); formData.append("prompt", prompt); if (mask) { formData.append("mask", mask, "mask.png"); } Object.entries(options).forEach(([key, value]) => { if (value !== void 0) { formData.append(key, value.toString()); } }); const response = await this.client.post("/images/edits", formData, { headers: { ...formData.getHeaders(), "Authorization": `Bearer ${this.apiKey}` } }); this.logger.info(`图像编辑请求成功,生成图片数量: ${response.data.data.length}`); return response.data; } catch (error) { this.logger.error("图像编辑请求失败:", error); throw this.handleError(error); } } /** * 错误处理 */ handleError(error) { if (error.response) { const status = error.response.status; const message = error.response.data?.error?.message || error.response.statusText; switch (status) { case 401: return new Error("API密钥无效或已过期"); case 403: return new Error("API访问被拒绝,请检查权限"); case 429: return new Error("请求频率过高,请稍后重试"); case 500: return new Error("服务器内部错误,请稍后重试"); default: return new Error(`API请求失败: ${status} ${message}`); } } else if (error.request) { return new Error("网络连接失败,请检查网络设置"); } else { return new Error(`请求配置错误: ${error.message}`); } } /** * 测试API连接 */ async testConnection() { try { await this.getModels(); return true; } catch (error) { this.logger.error("API连接测试失败:", error); return false; } } }; // src/services/chat.ts var import_koishi2 = require("koishi"); var ChatService = class { // 30分钟 constructor(api, config) { this.api = api; this.config = config; this.logger = new import_koishi2.Logger("chat-service"); setInterval(() => { this.cleanupExpiredContexts(); }, 5 * 60 * 1e3); } static { __name(this, "ChatService"); } logger; contexts = /* @__PURE__ */ new Map(); CONTEXT_TIMEOUT = 30 * 60 * 1e3; /** * 处理聊天消息 */ async handleChat(session, message) { try { const contextKey = this.getContextKey(session); const context = this.getOrCreateContext(session); context.messages.push({ role: "user", content: message }); this.limitContextLength(context); const request = { model: this.config.chatModel, messages: context.messages, max_tokens: this.config.maxTokens, temperature: this.config.temperature }; this.logger.info(`用户 ${session.userId} 发送聊天消息`); const response = await this.api.chat(request); if (!response.choices || response.choices.length === 0) { throw new Error("API返回空响应"); } const assistantMessage = response.choices[0].message.content; context.messages.push({ role: "assistant", content: assistantMessage }); context.lastActivity = Date.now(); this.logger.info(`聊天回复成功,使用token: ${response.usage?.total_tokens || 0}`); return assistantMessage; } catch (error) { this.logger.error("聊天处理失败:", error); throw error; } } /** * 处理图文聊天 */ async handleImageChat(session, message, imageUrls) { try { const context = this.getOrCreateContext(session); const content = [ { type: "text", text: message } ]; imageUrls.forEach((url) => { content.push({ type: "image_url", image_url: { url } }); }); context.messages.push({ role: "user", content }); this.limitContextLength(context); const request = { model: this.config.chatModel, messages: context.messages, max_tokens: this.config.maxTokens, temperature: this.config.temperature }; this.logger.info(`用户 ${session.userId} 发送图文聊天消息`); const response = await this.api.chat(request); if (!response.choices || response.choices.length === 0) { throw new Error("API返回空响应"); } const assistantMessage = response.choices[0].message.content; context.messages.push({ role: "assistant", content: assistantMessage }); context.lastActivity = Date.now(); this.logger.info(`图文聊天回复成功,使用token: ${response.usage?.total_tokens || 0}`); return assistantMessage; } catch (error) { this.logger.error("图文聊天处理失败:", error); throw error; } } /** * 清除用户对话上下文 */ clearContext(session) { const contextKey = this.getContextKey(session); this.contexts.delete(contextKey); this.logger.info(`清除用户 ${session.userId} 的对话上下文`); } /** * 获取或创建对话上下文 */ getOrCreateContext(session) { const contextKey = this.getContextKey(session); if (!this.contexts.has(contextKey)) { this.contexts.set(contextKey, { userId: session.userId, channelId: session.channelId, messages: [ { role: "system", content: "你是一个有用的AI助手,请用简洁友好的方式回答用户的问题。" } ], lastActivity: Date.now() }); this.logger.info(`为用户 ${session.userId} 创建新的对话上下文`); } const context = this.contexts.get(contextKey); context.lastActivity = Date.now(); return context; } /** * 获取上下文键 */ getContextKey(session) { return `${session.userId}-${session.channelId}`; } /** * 限制上下文长度 */ limitContextLength(context) { const maxMessages = 20; if (context.messages.length > maxMessages) { const systemMessages = context.messages.filter((msg) => msg.role === "system"); const recentMessages = context.messages.slice(-maxMessages + systemMessages.length); context.messages = [...systemMessages, ...recentMessages.filter((msg) => msg.role !== "system")]; this.logger.debug(`限制上下文长度,当前消息数: ${context.messages.length}`); } } /** * 清理过期的对话上下文 */ cleanupExpiredContexts() { const now = Date.now(); let cleanedCount = 0; for (const [key, context] of this.contexts.entries()) { if (now - context.lastActivity > this.CONTEXT_TIMEOUT) { this.contexts.delete(key); cleanedCount++; } } if (cleanedCount > 0) { this.logger.info(`清理了 ${cleanedCount} 个过期的对话上下文`); } } /** * 获取上下文统计信息 */ getStats() { const now = Date.now(); const activeThreshold = 5 * 60 * 1e3; let activeContexts = 0; for (const context of this.contexts.values()) { if (now - context.lastActivity < activeThreshold) { activeContexts++; } } return { totalContexts: this.contexts.size, activeContexts }; } }; // src/services/image.ts var import_koishi3 = require("koishi"); var import_axios2 = __toESM(require("axios")); var ImageService = class { constructor(api, config) { this.api = api; this.config = config; this.logger = new import_koishi3.Logger("image-service"); } static { __name(this, "ImageService"); } logger; /** * 检测图像模型类型 */ detectModelType(model) { if (model.includes("gemini") && model.includes("image-generation")) { return "gemini" /* GEMINI */; } if (model.includes("imagen")) { return "imagen" /* IMAGEN */; } if (model.includes("grok") && (model.includes("image") || model.includes("2-image"))) { return "xai" /* XAI */; } if (model.includes("gpt-4o-image")) { return "reverse" /* REVERSE */; } return "official" /* OFFICIAL */; } /** * 生成图像 */ async generateImage(session, options) { try { this.logger.info(`用户 ${session.userId} 请求生成图像: ${options.prompt.substring(0, 50)}...`); const model = options.model || this.config.imageModel; const modelType = this.detectModelType(model); this.logger.info(`检测到模型类型: ${modelType}`); if (modelType === "gemini" /* GEMINI */) { return await this.generateImageWithGemini(session, options, model); } else if (modelType === "imagen" /* IMAGEN */) { return await this.generateImageWithImagen(session, options, model); } else if (modelType === "xai" /* XAI */) { return await this.generateImageWithXAI(session, options, model); } else { return await this.generateImageWithStandard(session, options, model, modelType); } } catch (error) { this.logger.error("图像生成失败:", error); throw error; } } /** * 使用Gemini智绘生成图像 */ async generateImageWithGemini(session, options, model) { this.logger.info(`使用Gemini智绘模型: ${model}`); const request = { model, messages: [ { role: "user", content: `请生成图片:${options.prompt}` } ], modalities: ["text", "image"], temperature: 0.7 }; const response = await this.api.generateImageWithGemini(request); if (!response.choices || response.choices.length === 0) { throw new Error("Gemini API返回空响应"); } const imageUrls = []; this.logger.debug(`Gemini完整响应:`, JSON.stringify(response, null, 2)); for (const choice of response.choices) { this.logger.debug(`处理Gemini choice:`, JSON.stringify(choice, null, 2)); if (choice.message.multi_mod_content) { this.logger.debug(`找到multi_mod_content,内容数量: ${choice.message.multi_mod_content.length}`); for (const content of choice.message.multi_mod_content) { this.logger.debug(`处理content项:`, JSON.stringify(content, null, 2)); if (content.inline_data && content.inline_data.data) { this.logger.debug(`找到Gemini图片数据,类型: ${content.inline_data.mimeType},数据长度: ${content.inline_data.data.length}`); const imageUrl = this.convertBase64ToDataUrl(content.inline_data.data); imageUrls.push(imageUrl); } else if (content.text) { this.logger.debug(`跳过文本内容: ${content.text?.substring(0, 50)}...`); } else { this.logger.warn(`content项既无图片数据也无文本内容:`, Object.keys(content)); } } } else { this.logger.warn(`choice.message缺少multi_mod_content字段:`, Object.keys(choice.message)); } } this.logger.info(`Gemini智绘成功,生成 ${imageUrls.length} 张图片`); return imageUrls; } /** * 使用Imagen模型生成图像 */ async generateImageWithImagen(session, options, model) { this.logger.info(`使用Imagen模型: ${model}`); const request = { model, prompt: options.prompt, config: { number_of_images: 1, // Imagen模型固定生成1张图片 aspect_ratio: this.convertSizeToAspectRatio(options.size || this.config.imageSize) } }; const response = await this.api.generateImageWithImagen(request); if (!response.generated_images || response.generated_images.length === 0) { throw new Error("Imagen API返回空响应"); } const imageUrls = []; for (const generatedImage of response.generated_images) { if (generatedImage.image && generatedImage.image.image_bytes) { this.logger.debug(`找到Imagen图片数据,数据长度: ${generatedImage.image.image_bytes.length}`); const imageUrl = this.convertBase64ToDataUrl(generatedImage.image.image_bytes); imageUrls.push(imageUrl); } } this.logger.info(`Imagen绘图成功,生成 ${imageUrls.length} 张图片`); return imageUrls; } /** * 使用xAI生成图像 */ async generateImageWithXAI(session, options, model) { this.logger.info(`使用xAI模型: ${model}`); if (!this.config.enableXAI) { throw new Error("xAI功能未启用,请在插件配置中启用xAI功能"); } if (!this.config.xaiApiKey) { throw new Error("xAI API密钥未配置,请在插件配置中设置xAI API密钥"); } const request = { model, prompt: options.prompt, n: Math.min(options.n || 1, 4), // xAI最多支持4张图片 size: options.size || this.config.imageSize, quality: options.quality || this.config.imageQuality }; const response = await this.api.generateImageWithXAI( request, this.config.xaiApiKey, this.config.xaiBaseUrl ); if (!response.data || response.data.length === 0) { throw new Error("xAI API返回空响应"); } const imageUrls = []; this.logger.debug(`xAI返回数据结构:`, JSON.stringify(response.data, null, 2)); for (const item of response.data) { this.logger.debug(`处理xAI图片项:`, JSON.stringify(item, null, 2)); if (item.url) { this.logger.debug(`找到xAI图片URL: ${item.url}`); imageUrls.push(item.url); } else if (item.b64_json) { this.logger.debug(`找到xAI base64数据,长度: ${item.b64_json.length}`); const imageUrl = this.convertBase64ToDataUrl(item.b64_json); imageUrls.push(imageUrl); } else { this.logger.warn(`xAI图片项既无url也无b64_json字段:`, Object.keys(item)); } } this.logger.info(`xAI绘图成功,生成 ${imageUrls.length} 张图片`); return imageUrls; } /** * 将尺寸转换为Imagen支持的宽高比 */ convertSizeToAspectRatio(size) { const sizeMap = { "1024x1024": "1:1", "1024x1536": "3:4", "1536x1024": "4:3", "1024x768": "4:3", "768x1024": "3:4", "1280x720": "16:9", "720x1280": "9:16" }; return sizeMap[size] || "1:1"; } /** * 使用标准API生成图像 */ async generateImageWithStandard(session, options, model, modelType) { const request = { model, prompt: options.prompt, n: options.n || 1, size: options.size || this.config.imageSize, moderation: "auto", background: "auto" }; if (modelType !== "reverse" /* REVERSE */) { request.quality = options.quality || this.config.imageQuality; } else { this.logger.info(`使用逆向模型 ${model},跳过quality参数`); } const response = await this.api.generateImage(request); if (!response.data || response.data.length === 0) { throw new Error("API返回空响应"); } const imageUrls = []; this.logger.debug(`API返回数据结构:`, JSON.stringify(response.data, null, 2)); for (const item of response.data) { this.logger.debug(`处理图片项:`, JSON.stringify(item, null, 2)); if (item.b64_json) { this.logger.debug(`找到b64_json数据,长度: ${item.b64_json.length}`); const imageUrl = this.convertBase64ToDataUrl(item.b64_json); imageUrls.push(imageUrl); } else if (item.url) { this.logger.debug(`找到url数据: ${item.url}`); imageUrls.push(item.url); } else { this.logger.warn(`图片项既无b64_json也无url字段:`, Object.keys(item)); } } this.logger.info(`图像生成成功,生成 ${imageUrls.length} 张图片`); return imageUrls; } /** * 编辑图像 */ async editImage(session, options) { try { this.logger.info(`用户 ${session.userId} 请求编辑图像`); const imageBuffer = await this.downloadImage(options.imageUrl); let maskBuffer; if (options.maskUrl) { maskBuffer = await this.downloadImage(options.maskUrl); } const response = await this.api.editImage( imageBuffer, options.prompt, maskBuffer, { model: this.config.imageModel, size: options.size || this.config.imageSize, quality: options.quality || this.config.imageQuality, n: options.n || 1 } ); if (!response.data || response.data.length === 0) { throw new Error("API返回空响应"); } const imageUrls = []; for (const item of response.data) { if (item.b64_json) { const imageUrl = this.convertBase64ToDataUrl(item.b64_json); imageUrls.push(imageUrl); } else if (item.url) { imageUrls.push(item.url); } } this.logger.info(`图像编辑成功,生成 ${imageUrls.length} 张图片`); return imageUrls; } catch (error) { this.logger.error("图像编辑失败:", error); throw error; } } /** * 下载图像 */ async downloadImage(url) { try { const response = await import_axios2.default.get(url, { responseType: "arraybuffer", timeout: 3e4 }); return Buffer.from(response.data); } catch (error) { this.logger.error(`下载图像失败: ${url}`, error); throw new Error("图像下载失败"); } } /** * 将base64数据转换为data URL */ convertBase64ToDataUrl(base64Data) { if (base64Data.startsWith("data:")) { return base64Data; } return `data:image/png;base64,${base64Data}`; } /** * 保存base64图像 * 注意:这里需要根据实际情况实现图像保存逻辑 * 可以保存到本地文件系统或上传到图床 */ async saveBase64Image(base64Data) { try { const base64Image = base64Data.replace(/^data:image\/[a-z]+;base64,/, ""); const imageBuffer = Buffer.from(base64Image, "base64"); const fileName = `generated_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.png`; this.logger.warn("Base64图像保存功能需要实现,当前返回占位符URL"); return `data:image/png;base64,${base64Data}`; } catch (error) { this.logger.error("保存base64图像失败:", error); throw new Error("图像保存失败"); } } /** * 验证图像URL */ async validateImageUrl(url) { try { const response = await import_axios2.default.head(url, { timeout: 1e4 }); const contentType = response.headers["content-type"]; return contentType && contentType.startsWith("image/"); } catch (error) { this.logger.debug(`图像URL验证失败: ${url}`); return false; } } /** * 获取支持的图像尺寸 */ getSupportedSizes() { return ["1024x1024", "1024x1536", "1536x1024"]; } /** * 获取支持的图像质量 */ getSupportedQualities() { return ["low", "medium", "high"]; } /** * 解析图像尺寸参数 */ parseImageSize(input) { const sizeMap = { "正方形": "1024x1024", "方形": "1024x1024", "1:1": "1024x1024", "竖版": "1024x1536", "竖图": "1024x1536", "2:3": "1024x1536", "横版": "1536x1024", "横图": "1536x1024", "3:2": "1536x1024" }; if (this.getSupportedSizes().includes(input)) { return input; } const normalized = input.toLowerCase().trim(); for (const [alias, size] of Object.entries(sizeMap)) { if (normalized.includes(alias.toLowerCase())) { return size; } } return this.config.imageSize; } /** * 解析图像质量参数 */ parseImageQuality(input) { const qualityMap = { "低": "low", "低质量": "low", "中": "medium", "中等": "medium", "中质量": "medium", "高": "high", "高质量": "high", "最高": "high" }; if (this.getSupportedQualities().includes(input.toLowerCase())) { return input.toLowerCase(); } const normalized = input.toLowerCase().trim(); for (const [alias, quality] of Object.entries(qualityMap)) { if (normalized.includes(alias)) { return quality; } } return this.config.imageQuality; } /** * 获取可用模型列表 */ async getAvailableModels() { try { const response = await this.api.getModels(); const chatModels = []; const imageModels = []; for (const model of response.data) { const modelId = model.id; if (this.isImageModel(modelId)) { imageModels.push(modelId); } else if (this.isChatModel(modelId)) { chatModels.push(modelId); } } this.logger.info(`获取到 ${chatModels.length} 个对话模型,${imageModels.length} 个绘图模型`); return { chatModels, imageModels }; } catch (error) { this.logger.error("获取模型列表失败:", error); throw error; } } /** * 判断是否为绘图模型 */ isImageModel(modelId) { const imageKeywords = [ "image", "draw", "paint", "generate", "dall-e", "midjourney", "stable-diffusion" ]; const lowerModelId = modelId.toLowerCase(); return imageKeywords.some((keyword) => lowerModelId.includes(keyword)) || lowerModelId.includes("gpt-image") || lowerModelId.includes("gpt-4o-image") || lowerModelId.includes("imagen") || lowerModelId.includes("gemini") && lowerModelId.includes("image-generation") || lowerModelId.includes("grok") && lowerModelId.includes("image"); } /** * 判断是否为对话模型 */ isChatModel(modelId) { const chatKeywords = [ "gpt", "claude", "gemini", "llama", "qwen", "chat", "turbo" ]; const lowerModelId = modelId.toLowerCase(); if (this.isImageModel(modelId)) { return false; } return chatKeywords.some((keyword) => lowerModelId.includes(keyword)); } }; // src/utils/index.ts var Utils = class { static { __name(this, "Utils"); } /** * 从消息中提取图片URL */ static extractImageUrls(session) { const imageUrls = []; if (session.elements) { for (const element of session.elements) { if (element.type === "img" && element.attrs?.src) { imageUrls.push(element.attrs.src); } } } return imageUrls; } /** * 清理文本内容,移除HTML标签和特殊字符 */ static cleanText(text) { return text.replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim(); } /** * 截断文本到指定长度 */ static truncateText(text, maxLength) { if (text.length <= maxLength) { return text; } return text.substring(0, maxLength - 3) + "..."; } /** * 格式化错误消息 */ static formatError(error) { if (typeof error === "string") { return error; } if (error instanceof Error) { return error.message; } if (error?.message) { return error.message; } return "未知错误"; } /** * 验证API密钥格式 */ static validateApiKey(apiKey) { if (!apiKey || typeof apiKey !== "string") { return false; } return apiKey.startsWith("sk-") && apiKey.length > 10; } /** * 验证xAI API密钥格式 */ static validateXAIApiKey(apiKey) { if (!apiKey || typeof apiKey !== "string") { return false; } return apiKey.startsWith("xai-") && apiKey.length > 10; } /** * 生成随机ID */ static generateId() { return Math.random().toString(36).substr(2, 9); } /** * 延迟执行 */ static delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * 重试执行函数 */ static async retry(fn, maxRetries = 3, delayMs = 1e3) { let lastError; for (let i = 0; i <= maxRetries; i++) { try { return await fn(); } catch (error) { lastError = error; if (i < maxRetries) { await this.delay(delayMs * Math.pow(2, i)); } } } throw lastError; } /** * 解析命令参数 */ static parseArgs(input) { const parts = input.trim().split(/\s+/); const command = parts[0] || ""; const args = []; const options = {}; for (let i = 1; i < parts.length; i++) { const part = parts[i]; if (part.startsWith("--")) { const [key, value] = part.substring(2).split("=", 2); if (value !== void 0) { options[key] = value; } else if (i + 1 < parts.length && !parts[i + 1].startsWith("-")) { options[key] = parts[++i]; } else { options[key] = "true"; } } else if (part.startsWith("-")) { const key = part.substring(1); if (i + 1 < parts.length && !parts[i + 1].startsWith("-")) { options[key] = parts[++i]; } else { options[key] = "true"; } } else { args.push(part); } } return { command, args, options }; } /** * 格式化文件大小 */ static formatFileSize(bytes) { const units = ["B", "KB", "MB", "GB"]; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(1)} ${units[unitIndex]}`; } /** * 格式化时间间隔 */ static formatDuration(ms) { const seconds = Math.floor(ms / 1e3); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); if (hours > 0) { return `${hours}小时${minutes % 60}分钟`; } else if (minutes > 0) { return `${minutes}分钟${seconds % 60}秒`; } else { return `${seconds}秒`; } } /** * 检查URL是否有效 */ static isValidUrl(url) { try { new URL(url); return true; } catch { return false; } } /** * 获取文件扩展名 */ static getFileExtension(filename) { const lastDot = filename.lastIndexOf("."); return lastDot === -1 ? "" : filename.substring(lastDot + 1).toLowerCase(); } /** * 检查是否为图片文件 */ static isImageFile(filename) { const imageExtensions = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"]; const extension = this.getFileExtension(filename); return imageExtensions.includes(extension); } /** * 转义HTML字符 */ static escapeHtml(text) { const htmlEscapes = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }; return text.replace(/[&<>"']/g, (char) => htmlEscapes[char]); } /** * 创建进度条 */ static createProgressBar(current, total, width = 20) { const percentage = Math.min(current / total, 1); const filled = Math.floor(percentage * width); const empty = width - filled; const bar = "█".repeat(filled) + "░".repeat(empty); const percent = Math.floor(percentage * 100); return `${bar} ${percent}%`; } /** * 生成帮助文本 */ static generateHelpText(commands) { let help = "📖 **命令帮助**\n\n"; for (const cmd of commands) { help += `**${cmd.name}** `; help += `${cmd.description} `; if (cmd.usage) { help += `用法: ${cmd.usage} `; } help += "\n"; } return help; } }; // src/index.ts var name = "koishiaimix"; var usage = ` ## 功能介绍 基于AiHubMix API的AI对话和绘图插件,提供以下功能: - 🤖 **AI对话**: 支持多种AI模型的智能对话 - 🎨 **AI绘图**: 支持文生图和图文生图功能 - ⚙️ **灵活配置**: 可配置API密钥、模型选择、参数设置等 - 📝 **指令丰富**: 提供友好的指令系统 ## 使用方法 1. 配置API密钥:在插件配置中填入您的AiHubMix API密钥 2. 选择模型:根据需要选择对话模型和绘图模型 3. 开始使用: - \`aimix <消息>\` - AI对话 - \`aimix.draw <描述>\` - AI绘图 - \`aimix.config\` - 查看配置 ## 获取API密钥 请访问 [AiHubMix](https://aihubmix.com/token) 获取API密钥。 `; var Config = import_koishi4.Schema.intersect([ import_koishi4.Schema.object({ apiKey: import_koishi4.Schema.string().description("AiHubMix API密钥").role("secret").required(), baseUrl: import_koishi4.Schema.string().description("API基础URL").default("https://aihubmix.com/v1") }).description("API配置"), import_koishi4.Schema.object({ enableXAI: import_koishi4.Schema.boolean().description("启用xAI绘图功能").default(false), xaiApiKey: import_koishi4.Schema.string().description("xAI API密钥(启用xAI功能时必填)").role("secret").default(""), xaiBaseUrl: import_koishi4.Schema.string().description("xAI API基础URL").default("https://api.x.ai/v1") }).description("xAI配置"), import_koishi4.Schema.object({ chatModel: import_koishi4.Schema.string().description("对话模型").default("gpt-3.5-turbo"), maxTokens: import_koishi4.Schema.number().description("最大token数").min(1).max(4096).default(2048), temperature: import_koishi4.Schema.number().description("温度参数(0-2)").min(0).max(2).step(0.1).default(0.7) }).description("对话配置"), import_koishi4.Schema.object({ imageModel: import_koishi4.Schema.string().description("绘图模型").default("gpt-image-1"), imageSize: import_koishi4.Schema.union([ import_koishi4.Schema.const("1024x1024").description("正方形"), import_koishi4.Schema.const("1024x1536").description("竖版"), import_koishi4.Schema.const("1536x1024").description("横版") ]).description("图片尺寸").default("1024x1024"), imageQuality: import_koishi4.Schema.union([ import_koishi4.Schema.const("low").description("低质量"), import_koishi4.Schema.const("medium").description("中等质量"), import_koishi4.Schema.const("high").description("高质量") ]).description("图片质量").default("medium") }).description("绘图配置"), import_koishi4.Schema.object({ enableChat: import_koishi4.Schema.boolean().description("启用AI对话功能").default(true), enableImage: import_koishi4.Schema.boolean().description("启用AI绘图功能").default(true) }).description("功能开关"), import_koishi4.Schema.object({ timeout: import_koishi4.Schema.number().description("请求超时时间(秒)").min(5).max(300).default(60), retryCount: import_koishi4.Schema.number().description("重试次数").min(0).max(5).default(2), logLevel: import_koishi4.Schema.union([ import_koishi4.Schema.const("debug").description("调试"), import_koishi4.Schema.const("info").description("信息"), import_koishi4.Schema.const("warn").description("警告"), import_koishi4.Schema.const("error").description("错误") ]).description("日志级别").default("info") }).description("高级配置") ]); var logger = new import_koishi4.Logger("koishiaimix"); function apply(ctx, config) { logger.level = config.logLevel; logger.info("KoishiAiMix插件正在启动..."); if (!config.apiKey) { logger.error("API密钥未配置,插件无法正常工作"); return; } if (!Utils.validateApiKey(config.apiKey)) { logger.error("API密钥格式无效,请检查配置"); return; } if (config.enableXAI) { if (!config.xaiApiKey) { logger.warn("xAI功能已启用但API密钥未配置,xAI绘图功能将不可用"); } else { logger.info(`xAI功能已启用,基础URL: ${config.xaiBaseUrl}`); } } logger.info(`API基础URL: ${config.baseUrl}`); logger.info(`对话模型: ${config.chatModel}`); logger.info(`绘图模型: ${config.imageModel}`); const api = new AiHubMixAPI( config.apiKey, config.baseUrl, config.timeout * 1e3, config.retryCount ); let chatService = null; if (config.enableChat) { chatService = new ChatService(api, config); logger.info("聊天服务已启用"); } let imageService = null; if (config.enableImage) { imageService = new ImageService(api, config); logger.info("绘图服务已启用"); } api.testConnection().then((success) => { if (success) { logger.info("API连接测试成功"); } else { logger.warn("API连接测试失败,请检查网络和配置"); } }).catch((error) => { logger.error("API连接测试异常:", error); }); ctx.command("aimix.config", "查看插件配置信息").action(async ({ session }) => { const stats = chatService?.getStats() || { totalContexts: 0, activeContexts: 0 }; return [ "🔧 **KoishiAiMix 配置信息**", "", `**API配置**`, `• 基础URL: ${config.baseUrl}`, `• 超时时间: ${config.timeout}秒`, `• 重试次数: ${config.retryCount}次`, "", `**功能状态**`, `• 聊天功能: ${config.enableChat ? "✅ 已启用" : "❌ 已禁用"}`, `• 绘图功能: ${config.enableImage ? "✅ 已启用" : "❌ 已禁用"}`, `• xAI功能: ${config.enableXAI ? "✅ 已启用" : "❌ 已禁用"}`, "", `**模型配置**`, `• 对话模型: ${config.chatModel}`, `• 绘图模型: ${config.imageModel}`, `• 最大Token: ${config.maxTokens}`, `• 温度参数: ${config.temperature}`, "", `**绘图配置**`, `• 图片尺寸: ${config.imageSize}`, `• 图片质量: ${config.imageQuality}`, config.enableXAI ? `• xAI基础URL: ${config.xaiBaseUrl}` : "", "", `**运行状态**`, `• 总对话上下文: ${stats.totalContexts}`, `• 活跃对话: ${stats.activeContexts}`, `• 日志级别: ${config.logLevel}` ].filter(Boolean).join("\n"); }); ctx.command("aimix.test", "测试API连接").action(async ({ session }) => { try { session?.send("🔄 正在测试API连接..."); const success = await api.testConnection(); if (success) { return "✅ API连接测试成功!"; } else { return "❌ API连接测试失败,请检查网络和配置"; } } catch (error) { logger.error("API测试失败:", error); return `❌ API连接测试失败: ${Utils.formatError(error)}`; } }); if (config.enableChat && chatService) { ctx.command("aimix <message:text>", "AI对话聊天").alias("ai").usage("与AI进行对话交流").example("aimix 你好,请介绍一下自己").action(async ({ session }, message) => { if (!message) { return "请输入要发送的消息"; } try { const imageUrls = Utils.extractImageUrls(session); session?.send("🤖 正在思考中..."); let response; if (imageUrls.length > 0) { response = await chatService.handleImageChat(session, message, imageUrls); } else { response = await chatService.handleChat(session, message); } return response; } catch (error) { logger.error("聊天处理失败:", error); return `❌ 聊天失败: ${Utils.formatError(error)}`; } }); ctx.command("aimix.clear", "清除对话上下文").action(async ({ session }) => { try { chatService.clearContext(session); return "✅ 对话上下文已清除"; } catch (error) { logger.error("清除上下文失败:", error); return `❌ 清除失败: ${Utils.formatError(error)}`; } }); } if (config.enableImage && imageService) { ctx.command("aimix.draw <prompt:text>", "AI绘图生成").alias("draw", "paint").usage("使用AI生成图片").option("size", "-s <size:string> 图片尺寸 (1024x1024, 1024x1536, 1536x1024)").option("quality", "-q <quality:string> 图片质量 (low, medium, high)").option("count", "-n <count:number> 生成数量 (1-4)").example("aimix.draw 一只可爱的小猫").example("aimix.draw 夕阳下的城市 -s 1536x1024 -q high").action(async ({ session, options }, prompt) => { if (!prompt) { return "请输入绘图描述"; } try { const isReverseModel = config.imageModel.includes("gpt-4o-image"); const isXAIModel = config.imageModel.includes("grok") && config.imageModel.includes("image"); if (isReverseModel) { session?.send("🎨 正在生成图片,使用逆向模型可能需要2-3分钟,请耐心等待..."); } else if (isXAIModel) { session?.send("🎨 正在使用xAI Grok模型生成图片,请稍候..."); } else { session?.send("🎨 正在生成图片,请稍候..."); } const imageUrls = await imageService.generateImage(session, { prompt, size: options.size ? imageService.parseImageSize(options.size) : void 0, quality: options.quality ? imageService.parseImageQuality(options.quality) : void 0, n: Math.min(Math.max(options.count || 1, 1), 4) }); if (imageUrls.length === 0) { return "❌ 图片生成失败,未返回图片"; } const images = imageUrls.map((url) => import_koishi4.h.image(url)).join(""); return `✅ 图片生成完成! ${images} 📝 描述: ${Utils.truncateText(prompt, 100)}`; } catch (error) { logger.error("绘图处理失败:", error); return `❌ 绘图失败: ${Utils.formatError(error)}`; } }); ctx.command("aimix.edit <prompt:text>", "编辑图片").usage("编辑上传的图片").example("aimix.edit 把背景改成蓝天白云").action(async ({ session }, prompt) => { if (!prompt) { return "请输入编辑描述"; } try { const imageUrls = Utils.extractImageUrls(session); if (imageUrls.length === 0) { return "请先上传要编辑的图片"; } session?.send("🎨 正在编辑图片,请稍候..."); const editedUrls = await imageService.editImage(session, { prompt, imageUrl: imageUrls[0] }); if (editedUrls.length === 0) { return "❌ 图片编辑失败,未返回图片"; } const images = editedUrls.map((url) => import_koishi4.h.image(url)).join(""); return `✅ 图片编辑完成! ${images} 📝 编辑描述: ${Utils.truncateText(prompt, 100)}`; } catch (error) { logger.error("图片编辑失败:", error); return `❌ 编辑失败: ${Utils.formatError(error)}`; } }); } ctx.command("aimix.models", "获取可用模型列表").action(async ({ session }) => { if (!imageService) { return "❌ 绘图服务未启用"; } try { session?.send("🔍 正在获取模型列表..."); const models = await imageService.getAvailableModels(); const chatModelsList = models.chatModels.slice(0, 20).join("\n• "); const imageModelsList = models.imageModels.slice(0, 20).join("\n• "); return [ "📋 **可用模型列表**", "", `🤖 **对话模型** (${models.chatModels.length}个):`, `• ${chatModelsList}`, models.chatModels.length > 20 ? `... 还有 ${models.chatModels.length - 20} 个模型` : "", "", `🎨 **绘图模型** (${models.imageModels.length}个):`, `• ${imageModelsList}`, models.imageModels.length > 20 ? `... 还有 ${models.imageModels.length - 20} 个模型` : "", "", "💡 **使用方法**:", "1. 复制想要的模型名称", "2. 在Koishi Web控制台的插件配置中粘贴", "3. 保存配置即可使用" ].filter(Boolean).join("\n"); } catch (error) { logger.error("获取模型列表失败:", error); return `❌ 获取模型列表失败: ${Utils.formatError(error)}`; } }); ctx.command("aimix.help", "显示帮助信息").action(() => { const commands = [ { name: "aimix <消息>", description: "AI对话聊天", usage: "aimix 你好" }, { name: "aimix.draw <描述>", description: "AI绘图生成", usage: "aimix.draw 一只猫 -s 1024x1024 -q high" }, { name: "aimix.edit <描述>", description: "编辑图片", usage: "aimix.edit 改成卡通风格" }, { name: "aimix.clear", description: "清除对话上下文" }, { name: "aimix.config", description: "查看配置信息" }, { name: "aimix.test", description: "测试API连接" }, { name: "aimix.models", description: "获取可用模型列表" } ]; return Utils.generateHelpText(commands); }); logger.info("KoishiAiMix插件启动完成"); } __name(apply, "apply"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Config, apply, name, usage });