(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_API_KEY?: string;
|
||||
static OPENAI_MODEL: string;
|
||||
static OPENAI_IMAGE_MODEL: string;
|
||||
|
||||
static waitText = "⏳ Дайте-ка подумать...";
|
||||
static analyzingPictureText = "🔍 Внимательно изучаю изображение...";
|
||||
@@ -93,6 +94,7 @@ export class Environment {
|
||||
Environment.OPENAI_BASE_URL = process.env.OPENAI_BASE_URL;
|
||||
Environment.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
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>) {
|
||||
|
||||
@@ -79,6 +79,7 @@ import {OpenAIListModels} from "./commands/openai-list-models";
|
||||
import {OpenAIGetModel} from "./commands/openai-get-model";
|
||||
import {OpenAISetModel} from "./commands/openai-set-model";
|
||||
import {Info} from "./commands/info";
|
||||
import {OpenAIGenImage} from "./commands/openai-gen-image";
|
||||
|
||||
process.setUncaughtExceptionCaptureCallback(logError);
|
||||
|
||||
@@ -210,6 +211,7 @@ if (Environment.OPENAI_API_KEY) {
|
||||
new OpenAIListModels(),
|
||||
new OpenAIGetModel(),
|
||||
new OpenAISetModel(),
|
||||
new OpenAIGenImage()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user