(feat): add image generation from openai
This commit is contained in:
@@ -0,0 +1,120 @@
|
|||||||
|
import {ChatCommand} from "../base/chat-command";
|
||||||
|
import {Message} from "typescript-telegram-bot-api";
|
||||||
|
import {Requirements} from "../base/requirements";
|
||||||
|
import {Requirement} from "../base/requirement";
|
||||||
|
import {bot, openAi, photoDir} from "../index";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import {editMessageText, logError, replyToMessage} from "../util/utils";
|
||||||
|
import {Environment} from "../common/environment";
|
||||||
|
import {APIError} from "openai";
|
||||||
|
|
||||||
|
export class OpenAIGenImage extends ChatCommand {
|
||||||
|
command = ["openAiGenImage", "chatGPTGenImage"];
|
||||||
|
|
||||||
|
title = "/openAIGenImage";
|
||||||
|
description = "Generate image from OpenAI";
|
||||||
|
|
||||||
|
argsMode = "required" as const;
|
||||||
|
requirements = Requirements.Build(Requirement.BOT_CREATOR);
|
||||||
|
|
||||||
|
async execute(msg: Message, match?: RegExpExecArray): Promise<void> {
|
||||||
|
const prompt = match?.[3]?.trim();
|
||||||
|
if (!prompt?.length) return;
|
||||||
|
|
||||||
|
let waitMessage: Message | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const totalParts = 3;
|
||||||
|
const model = Environment.OPENAI_IMAGE_MODEL;
|
||||||
|
const size = "1024x1024";
|
||||||
|
const fileFullName = `${msg.chat.id}_${msg.message_id}.png`;
|
||||||
|
const getFileLocation = (fn: string) => {
|
||||||
|
const genRoot = path.join(photoDir, "gen");
|
||||||
|
if (!fs.existsSync(genRoot)) {
|
||||||
|
fs.mkdirSync(genRoot);
|
||||||
|
}
|
||||||
|
return path.join(genRoot, fn);
|
||||||
|
};
|
||||||
|
|
||||||
|
waitMessage = await replyToMessage({message: msg, text: "🌈 Генерирую изображение..."});
|
||||||
|
|
||||||
|
const stream = await openAi.images.generate({
|
||||||
|
model: model,
|
||||||
|
prompt: prompt,
|
||||||
|
n: 1,
|
||||||
|
size: size,
|
||||||
|
stream: true,
|
||||||
|
partial_images: totalParts,
|
||||||
|
});
|
||||||
|
|
||||||
|
const then = Date.now();
|
||||||
|
|
||||||
|
for await (const event of stream) {
|
||||||
|
switch (event.type) {
|
||||||
|
case "image_generation.partial_image": {
|
||||||
|
console.log(` Partial image ${event.partial_image_index + 1}/3 received`);
|
||||||
|
console.log(` Size: ${event.b64_json.length} characters (base64)`);
|
||||||
|
|
||||||
|
const fileName = `partial_${event.partial_image_index + 1}_${fileFullName}`;
|
||||||
|
const imageBuffer = Buffer.from(event.b64_json, "base64");
|
||||||
|
const fileLocation = getFileLocation(fileName);
|
||||||
|
fs.writeFileSync(fileLocation, imageBuffer);
|
||||||
|
console.log(` 💾 Saved to: ${path.resolve(fileLocation)}`);
|
||||||
|
|
||||||
|
await bot.editMessageMedia({
|
||||||
|
chat_id: msg.chat.id,
|
||||||
|
message_id: waitMessage.message_id,
|
||||||
|
media: {
|
||||||
|
type: "photo",
|
||||||
|
media: imageBuffer,
|
||||||
|
caption: `🌈 Генерирую изображение (${(event.partial_image_index + 1)}/${totalParts})...`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "image_generation.completed": {
|
||||||
|
console.log("\n✅ Final image completed!");
|
||||||
|
console.log(` Size: ${event.b64_json.length} characters (base64)`);
|
||||||
|
|
||||||
|
const imageBuffer = Buffer.from(event.b64_json, "base64");
|
||||||
|
const fileLocation = getFileLocation(fileFullName);
|
||||||
|
fs.writeFileSync(fileLocation, imageBuffer);
|
||||||
|
console.log(` Saved to: ${path.resolve(fileLocation)}`);
|
||||||
|
|
||||||
|
const diff = Date.now() - then;
|
||||||
|
await bot.editMessageMedia({
|
||||||
|
chat_id: msg.chat.id,
|
||||||
|
message_id: waitMessage.message_id,
|
||||||
|
media: {
|
||||||
|
type: "photo",
|
||||||
|
media: imageBuffer,
|
||||||
|
caption: `🌈 Изображение по запросу "${prompt}" сгенерировано моделью "${model}" размеров ${size} за ${diff}ms`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
console.log(`❓ Unknown event: ${event}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logError(e);
|
||||||
|
|
||||||
|
if (e instanceof APIError && e.error.code === "moderation_blocked") {
|
||||||
|
const text = "❌ Мне запрещено такое генерировать 😠";
|
||||||
|
|
||||||
|
if (waitMessage) {
|
||||||
|
await editMessageText(msg.chat.id, waitMessage.message_id, text).catch(logError);
|
||||||
|
} else {
|
||||||
|
await replyToMessage({message: msg, text: text}).catch(logError);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await replyToMessage({
|
||||||
|
message: waitMessage ? waitMessage : msg,
|
||||||
|
text: `Произошла ошибка: ${e}`
|
||||||
|
}).catch(logError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,6 +45,7 @@ export class Environment {
|
|||||||
static OPENAI_BASE_URL?: string;
|
static OPENAI_BASE_URL?: string;
|
||||||
static OPENAI_API_KEY?: string;
|
static OPENAI_API_KEY?: string;
|
||||||
static OPENAI_MODEL: string;
|
static OPENAI_MODEL: string;
|
||||||
|
static OPENAI_IMAGE_MODEL: string;
|
||||||
|
|
||||||
static waitText = "⏳ Дайте-ка подумать...";
|
static waitText = "⏳ Дайте-ка подумать...";
|
||||||
static analyzingPictureText = "🔍 Внимательно изучаю изображение...";
|
static analyzingPictureText = "🔍 Внимательно изучаю изображение...";
|
||||||
@@ -93,6 +94,7 @@ export class Environment {
|
|||||||
Environment.OPENAI_BASE_URL = process.env.OPENAI_BASE_URL;
|
Environment.OPENAI_BASE_URL = process.env.OPENAI_BASE_URL;
|
||||||
Environment.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
Environment.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||||
Environment.OPENAI_MODEL = process.env.OPENAI_MODEL || "gpt-4.1-nano";
|
Environment.OPENAI_MODEL = process.env.OPENAI_MODEL || "gpt-4.1-nano";
|
||||||
|
Environment.OPENAI_IMAGE_MODEL = process.env.OPENAI_IMAGE_MODEL || "gpt-image-1-mini";
|
||||||
}
|
}
|
||||||
|
|
||||||
static setAdmins(admins: Set<number>) {
|
static setAdmins(admins: Set<number>) {
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ import {OpenAIListModels} from "./commands/openai-list-models";
|
|||||||
import {OpenAIGetModel} from "./commands/openai-get-model";
|
import {OpenAIGetModel} from "./commands/openai-get-model";
|
||||||
import {OpenAISetModel} from "./commands/openai-set-model";
|
import {OpenAISetModel} from "./commands/openai-set-model";
|
||||||
import {Info} from "./commands/info";
|
import {Info} from "./commands/info";
|
||||||
|
import {OpenAIGenImage} from "./commands/openai-gen-image";
|
||||||
|
|
||||||
process.setUncaughtExceptionCaptureCallback(logError);
|
process.setUncaughtExceptionCaptureCallback(logError);
|
||||||
|
|
||||||
@@ -210,6 +211,7 @@ if (Environment.OPENAI_API_KEY) {
|
|||||||
new OpenAIListModels(),
|
new OpenAIListModels(),
|
||||||
new OpenAIGetModel(),
|
new OpenAIGetModel(),
|
||||||
new OpenAISetModel(),
|
new OpenAISetModel(),
|
||||||
|
new OpenAIGenImage()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user