koishi-plugin-koishiaimix
Version:
基于AiHubMix API的AI对话和绘图插件,支持多种AI模型的聊天对话和图像生成功能,现已支持xAI Grok绘图
1,465 lines (1,448 loc) • 49.7 kB
JavaScript
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 = {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'"
};
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
});