Replicate is a bit better

This commit is contained in:
Atridad Lahiji 2023-12-03 13:17:38 -07:00
parent 4d2a7a5331
commit 43b7b3fd2e
No known key found for this signature in database
8 changed files with 77 additions and 242 deletions

View file

@ -11,20 +11,21 @@
"dependencies": {
"@sapphire/decorators": "^6.0.2",
"@sapphire/discord.js-utilities": "7.1.2",
"@sapphire/framework": "^4.8.2",
"@sapphire/framework": "^4.8.4",
"@sapphire/plugin-logger": "^3.0.7",
"@sapphire/utilities": "^3.13.0",
"@skyra/env-utilities": "^1.2.1",
"colorette": "^2.0.20",
"discord.js": "^14.14.1",
"openai": "^4.20.1"
"openai": "^4.20.1",
"replicate": "^0.22.0"
},
"devDependencies": {
"@flydotio/dockerfile": "^0.4.11",
"@sapphire/cli": "^1.8.0",
"@sapphire/cli": "^1.9.0",
"@sapphire/prettier-config": "^2.0.0",
"@sapphire/ts-config": "^5.0.0",
"@types/node": "^20.10.0",
"@types/node": "^20.10.2",
"@types/ws": "^8.5.10",
"prettier": "^3.1.0",
"tsc-watch": "^6.0.4",

58
pnpm-lock.yaml generated
View file

@ -12,8 +12,8 @@ dependencies:
specifier: 7.1.2
version: 7.1.2
'@sapphire/framework':
specifier: ^4.8.2
version: 4.8.2
specifier: ^4.8.4
version: 4.8.4
'@sapphire/plugin-logger':
specifier: ^3.0.7
version: 3.0.7
@ -32,14 +32,17 @@ dependencies:
openai:
specifier: ^4.20.1
version: 4.20.1
replicate:
specifier: ^0.22.0
version: 0.22.0
devDependencies:
'@flydotio/dockerfile':
specifier: ^0.4.11
version: 0.4.11
'@sapphire/cli':
specifier: ^1.8.0
version: 1.8.0
specifier: ^1.9.0
version: 1.9.0
'@sapphire/prettier-config':
specifier: ^2.0.0
version: 2.0.0
@ -47,8 +50,8 @@ devDependencies:
specifier: ^5.0.0
version: 5.0.0
'@types/node':
specifier: ^20.10.0
version: 20.10.0
specifier: ^20.10.2
version: 20.10.2
'@types/ws':
specifier: ^8.5.10
version: 8.5.10
@ -102,7 +105,7 @@ packages:
'@discordjs/util': 1.0.2
'@sapphire/async-queue': 1.5.0
'@sapphire/snowflake': 3.5.1
'@vladfrangu/async_event_emitter': 2.2.2
'@vladfrangu/async_event_emitter': 2.2.4
discord-api-types: 0.37.61
magic-bytes.js: 1.5.0
tslib: 2.6.2
@ -123,7 +126,7 @@ packages:
'@discordjs/util': 1.0.2
'@sapphire/async-queue': 1.5.0
'@types/ws': 8.5.10
'@vladfrangu/async_event_emitter': 2.2.2
'@vladfrangu/async_event_emitter': 2.2.4
discord-api-types: 0.37.61
tslib: 2.6.2
ws: 8.14.2
@ -161,12 +164,13 @@ packages:
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
dev: false
/@sapphire/cli@1.8.0:
resolution: {integrity: sha512-lOxt3cgMU8/2VChUE85SO46m09AadtM/PmSubSDHrgPwtGcERlHrDj1lPut15sb9t07+PNNfwc0yXEuxwQxDNw==}
/@sapphire/cli@1.9.0:
resolution: {integrity: sha512-2WNMscS9c1+Y3W3Aem+BdgqgMnkbGrwmGaYr7kH+aGGG2Od12Drkq+umdNzF8DbiAiSaB6CK8sHe6HOqBvXfOA==}
engines: {node: '>=v18'}
hasBin: true
dependencies:
'@favware/colorette-spinner': 1.0.1
'@sapphire/node-utilities': 1.0.0
'@sapphire/result': 2.6.4
colorette: 2.0.20
commander: 11.1.0
@ -206,8 +210,8 @@ packages:
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
dev: false
/@sapphire/framework@4.8.2:
resolution: {integrity: sha512-MJH6NkdZtZ9DgcPD3ExQnjO7UYdZm2WQBC29jb9A6kmYzd/y+8/unSjURgDQQhw6+60QWN7tPJXCVQJmmlPVCw==}
/@sapphire/framework@4.8.4:
resolution: {integrity: sha512-ErnsCcvgtC/8/KS2gvV88EFtN4uteFzLKDfEjYebdXU7AVr9AoOBJtymYu42GHVZJ3eaYKGadUBJn6015LxaLw==}
engines: {node: '>=v18', npm: '>=7'}
dependencies:
'@discordjs/builders': 1.7.0
@ -228,6 +232,11 @@ packages:
'@sapphire/result': 2.6.4
dev: false
/@sapphire/node-utilities@1.0.0:
resolution: {integrity: sha512-xFC4UyzSKs6juyFtsUV4VNybg0KIpwaThED2TH3TGtNT5b0VFpILTXQtqXpPf+Rfmj+O/mLCm319xy4ohsQqxQ==}
engines: {node: '>=v16.0.0', npm: '>=7.0.0'}
dev: true
/@sapphire/pieces@3.10.0:
resolution: {integrity: sha512-iBaux50dA+VYjtBqmaceWcskdmw7ua51ojEPkyaSJyg2t9ln/Wc9NqYoQheRCWltZeDTERCUBIYYMqDuCs1Okw==}
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
@ -310,34 +319,34 @@ packages:
/@types/node-fetch@2.6.9:
resolution: {integrity: sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==}
dependencies:
'@types/node': 20.10.0
'@types/node': 20.10.2
form-data: 4.0.0
dev: false
/@types/node@18.18.13:
resolution: {integrity: sha512-vXYZGRrSCreZmq1rEjMRLXJhiy8MrIeVasx+PCVlP414N7CJLHnMf+juVvjdprHyH+XRy3zKZLHeNueOpJCn0g==}
/@types/node@18.19.1:
resolution: {integrity: sha512-mZJ9V11gG5Vp0Ox2oERpeFDl+JvCwK24PGy76vVY/UgBtjwJWc5rYBThFxmbnYOm9UPZNm6wEl/sxHt2SU7x9A==}
dependencies:
undici-types: 5.26.5
dev: false
/@types/node@20.10.0:
resolution: {integrity: sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==}
/@types/node@20.10.2:
resolution: {integrity: sha512-37MXfxkb0vuIlRKHNxwCkb60PNBpR94u4efQuN4JgIAm66zfCDXGSAFCef9XUWFovX2R1ok6Z7MHhtdVXXkkIw==}
dependencies:
undici-types: 5.26.5
/@types/ws@8.5.10:
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
dependencies:
'@types/node': 20.10.0
'@types/node': 20.10.2
/@types/ws@8.5.9:
resolution: {integrity: sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==}
dependencies:
'@types/node': 20.10.0
'@types/node': 20.10.2
dev: false
/@vladfrangu/async_event_emitter@2.2.2:
resolution: {integrity: sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==}
/@vladfrangu/async_event_emitter@2.2.4:
resolution: {integrity: sha512-ButUPz9E9cXMLgvAW8aLAKKJJsPu1dY1/l/E8xzLFuysowXygs6GBcyunK9rnGC4zTsnIc2mQo71rGw9U+Ykug==}
engines: {node: '>=v14.0.0', npm: '>=7.0.0'}
dev: false
@ -798,7 +807,7 @@ packages:
resolution: {integrity: sha512-Dd3q8EvINfganZFtg6V36HjrMaihqRgIcKiHua4Nq9aw/PxOP48dhbsk8x5klrxajt5Lpnc1KTOG5i1S6BKAJA==}
hasBin: true
dependencies:
'@types/node': 18.18.13
'@types/node': 18.19.1
'@types/node-fetch': 2.6.9
abort-controller: 3.0.0
agentkeepalive: 4.5.0
@ -868,6 +877,11 @@ packages:
event-stream: 3.3.4
dev: true
/replicate@0.22.0:
resolution: {integrity: sha512-jUfvYCcnnzG0l3NzgNLUBRfv7e5ZcZpBWzxGqx3v8bkvJmY0jK19JRtFtBgJFRBye61ggH1zC9cubGkburF82g==}
engines: {git: '>=2.11.0', node: '>=18.0.0', npm: '>=7.19.0', yarn: '>=1.7.0'}
dev: false
/require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}

View file

@ -1,4 +1,4 @@
# Tokens
DISCORD_TOKEN=
OPENAI_API_KEY=
STABILITY_API_KEY=
REPLICATE_API_TOKEN=

View file

@ -1,51 +0,0 @@
import { ApplyOptions } from '@sapphire/decorators';
import { Command } from '@sapphire/framework';
import { Message } from 'discord.js';
// @ts-ignore
@ApplyOptions<Command.Options>({
description: 'Check how many credits I have left!'
})
export class UserCommand extends Command {
// Register Chat Input and Context Menu command
public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand((builder) => builder.setName(this.name).setDescription(this.description));
}
// Message command
public async messageRun(message: Message) {
return this.credits(message);
}
// Chat Input (slash) command
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
return this.credits(interaction);
}
private async credits(interactionOrMessage: Message | Command.ChatInputCommandInteraction | Command.ContextMenuCommandInteraction) {
const askMessage =
interactionOrMessage instanceof Message
? await interactionOrMessage.channel.send({ content: '🤔 Thinking... 🤔' })
: await interactionOrMessage.reply({ content: '🤔 Thinking... 🤔', fetchReply: true });
const creditCountResponse = await fetch(`https://api.stability.ai/v1/user/balance`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.STABILITY_API_KEY}`
}
});
const balance = ((await creditCountResponse.json()) as { credits: number }).credits || 0;
const content = `I have ${balance} credits remaining for image generation!`;
if (interactionOrMessage instanceof Message) {
return askMessage.edit({ content });
}
return interactionOrMessage.editReply({
content: content
});
}
}

View file

@ -1,46 +0,0 @@
import { ApplyOptions } from '@sapphire/decorators';
import { Command } from '@sapphire/framework';
import { Message } from 'discord.js';
// @ts-ignore
@ApplyOptions<Command.Options>({
description: 'Dad joke for daddies only!'
})
export class UserCommand extends Command {
// Register Chat Input and Context Menu command
public override registerApplicationCommands(registry: Command.Registry) {
// Register Chat Input command
registry.registerChatInputCommand({
name: this.name,
description: this.description
});
}
// Message command
public async messageRun(message: Message) {
return this.sendJoke(message);
}
// Chat Input (slash) command
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
return this.sendJoke(interaction);
}
private async sendJoke(interactionOrMessage: Message | Command.ChatInputCommandInteraction | Command.ContextMenuCommandInteraction) {
const jokeResponse = await fetch('https://icanhazdadjoke.com/', {
headers: {
Accept: 'application/json'
}
});
const jokeData = (await jokeResponse.json()) as { status: number; joke: string };
interactionOrMessage instanceof Message
? await interactionOrMessage.channel.send({
content: jokeData.status === 200 ? jokeData.joke : '404 Joke Not Found'
})
: await interactionOrMessage.reply({
content: jokeData.status === 200 ? jokeData.joke : '404 Joke Not Found',
fetchReply: true
});
}
}

View file

@ -9,9 +9,9 @@ const openai = new OpenAI({
// @ts-ignore
@ApplyOptions<Command.Options>({
description: 'Pic... but better! Cooldown of 8 Minutes!',
description: 'Generate an image using OpenAI! Cooldown of 10 Minutes due to cost!',
options: ['prompt'],
cooldownDelay: 1_000_000,
cooldownDelay: 480_000,
cooldownLimit: 1,
// Yes... I did hardcode myself.
cooldownFilteredUsers: ['83679718401904640'],

View file

@ -1,15 +1,18 @@
import { ApplyOptions } from '@sapphire/decorators';
import { Args, BucketScope, Command } from '@sapphire/framework';
import { AttachmentBuilder, Message } from 'discord.js';
import { Message } from 'discord.js';
import Replicate from 'replicate';
// This is literally the worlds messiest TS code. Please don't judge me...
const replicate = new Replicate({
auth: process.env.REPLICATE_API_TOKEN
});
// @ts-ignore
@ApplyOptions<Command.Options>({
description: 'Make a picture... but high res!',
options: ['prompt', 'number of pictures'],
description: 'Generate an image using Stability AI! Cooldown 1 Minute to prevent spam!',
options: ['prompt'],
// 10mins
cooldownDelay: 100,
cooldownDelay: 100_000,
cooldownLimit: 1,
// Yes... I did hardcode myself.
cooldownFilteredUsers: ['83679718401904640'],
@ -25,135 +28,39 @@ export class UserCommand extends Command {
.addStringOption((option) =>
option.setName('prompt').setDescription('The prompt you will use to generate an image!').setRequired(true)
)
.addStringOption((option) =>
option
.setName('amount')
.setDescription('The number of images you would like to generate. Maximum 2.')
.setChoices(
...[
{ name: '1', value: '1' },
{ name: '2', value: '2' }
]
)
)
);
}
// Message command
public async messageRun(message: Message, args: Args) {
const amount = Math.abs(Number(args.getOption('amount')));
return this.picHr(message, args.getOption('prompt') || 'Scold me for not passing any prompt in.', amount <= 2 ? amount : 1);
return this.picHr(message, args.getOption('prompt') || 'Scold me for not passing any prompt in.');
}
// Chat Input (slash) command
public async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
const amount = Number(interaction.options.getString('amount'));
return this.picHr(interaction, interaction.options.getString('prompt') || 'NOTHING', amount || 1);
return this.picHr(interaction, interaction.options.getString('prompt') || 'NOTHING');
}
private async picHr(
interactionOrMessage: Message | Command.ChatInputCommandInteraction | Command.ContextMenuCommandInteraction,
prompt: string,
amount: number
) {
private async picHr(interactionOrMessage: Message | Command.ChatInputCommandInteraction | Command.ContextMenuCommandInteraction, prompt: string) {
const askMessage =
interactionOrMessage instanceof Message
? await interactionOrMessage.channel.send({ content: '🤔 Thinking... 🤔' })
: await interactionOrMessage.reply({ content: '🤔 Thinking... 🤔', fetchReply: true });
const creditCountResponse = await fetch(`https://api.stability.ai/v1/user/balance`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.STABILITY_API_KEY}`
let result = (await replicate.run('stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b', {
input: {
prompt,
disable_safety_checker: true,
refine: 'expert_ensemble_refiner',
num_inference_steps: 50,
scheduler: 'K_EULER',
width: 1024,
height: 1024
}
});
})) as string[];
const balance = ((await creditCountResponse.json()) as { credits: number }).credits || 0;
if (balance > 5) {
const imageGenResponse = await fetch(`https://api.stability.ai/v1/generation/stable-diffusion-xl-1024-v1-0/text-to-image`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${process.env.STABILITY_API_KEY}`
},
body: JSON.stringify({
text_prompts: [
{
text: prompt
}
],
cfg_scale: 6,
clip_guidance_preset: 'FAST_BLUE',
height: 1024,
width: 1024,
samples: amount,
steps: 32,
seed: Number(String(interactionOrMessage.member?.user.id).substring(0, 5)) || 0
})
});
interface GenerationResponse {
artifacts: Array<{
base64: string;
seed: number;
finishReason: string;
}>;
}
if (!imageGenResponse.ok) {
const content = `Sorry, I can't complete the prompt for: ${prompt}`;
if (interactionOrMessage instanceof Message) {
return askMessage.edit({ content });
}
return interactionOrMessage.editReply({
content: content
});
} else {
const responseJSON = (await imageGenResponse.json()) as GenerationResponse;
const imageAttachment: AttachmentBuilder[] = [];
for (let i = 0; i < responseJSON.artifacts.length; i++) {
imageAttachment.push(
new AttachmentBuilder(Buffer.from(responseJSON.artifacts[i].base64, 'base64'), {
name: 'response.jpg',
description: "Himbot's Response"
})
);
}
const newCreditCountResponse = await fetch(`https://api.stability.ai/v1/user/balance`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.STABILITY_API_KEY}`
}
});
const newBalance = ((await newCreditCountResponse.json()) as { credits: number }).credits || 0;
const content =
`Credits Used: ${balance - newBalance}\nPrompt: ${prompt}${
balance <= 300
? `\n\n⚠I am now at ${balance} credits. If you'd like to help fund this command, please type "/support" for details!`
: ''
}` || 'ERROR!';
if (interactionOrMessage instanceof Message) {
return askMessage.edit({ content, files: imageAttachment });
}
return interactionOrMessage.editReply({
content,
files: imageAttachment
});
}
} else {
const content = `Oops! We're out of credits for this. If you'd like to help fund this command, please type "/support" for details!`;
if (result.length <= 0) {
const content = `Sorry, I can't complete the prompt for: ${prompt}`;
if (interactionOrMessage instanceof Message) {
return askMessage.edit({ content });
@ -162,6 +69,16 @@ export class UserCommand extends Command {
return interactionOrMessage.editReply({
content: content
});
} else {
const content = `Prompt: ${prompt}\n URL: ${result[0]}`;
if (interactionOrMessage instanceof Message) {
return askMessage.edit({ content });
}
return interactionOrMessage.editReply({
content
});
}
}
}