mirror of
https://github.com/SrIzan10/vinci.git
synced 2026-06-06 01:07:00 +00:00
feat: ai messaging
This commit is contained in:
8
TODO.md
8
TODO.md
@@ -52,13 +52,5 @@
|
||||
# Database
|
||||
- [x] Migration to sqlite
|
||||
|
||||
# Command Features to Preserve
|
||||
- [ ] Autocomplete functionality for various commands
|
||||
- [ ] Interactive components (buttons, select menus)
|
||||
- [ ] File attachments and image processing
|
||||
- [ ] API integrations (TheCatAPI, TheDogAPI, Genius, etc.)
|
||||
- [ ] Canvas-based image generation
|
||||
- [ ] Modal forms and user input handling
|
||||
|
||||
# Other
|
||||
- [ ] Figure out fonts
|
||||
5
bun.lock
5
bun.lock
@@ -6,12 +6,13 @@
|
||||
"dependencies": {
|
||||
"@napi-rs/canvas": "^0.1.72",
|
||||
"@prisma/client": "^6.10.1",
|
||||
"@sern/handler": "^4.0.0",
|
||||
"@sern/handler": "^4.2.4",
|
||||
"@sern/publisher": "1.1.2",
|
||||
"discord.js": "^14.21.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"mongodb": "^6.17.0",
|
||||
"node-html-parser": "^7.0.1",
|
||||
"openai": "^5.10.2",
|
||||
"rockpaperscissors-checker": "^1.2.0",
|
||||
"sharp": "^0.34.2",
|
||||
},
|
||||
@@ -368,6 +369,8 @@
|
||||
|
||||
"onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="],
|
||||
|
||||
"openai": ["openai@5.10.2", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-n+vi74LzHtvlKcDPn9aApgELGiu5CwhaLG40zxLTlFQdoSJCLACORIPC2uVQ3JEYAbqapM+XyRKFy2Thej7bIw=="],
|
||||
|
||||
"ora": ["ora@6.3.1", "", { "dependencies": { "chalk": "^5.0.0", "cli-cursor": "^4.0.0", "cli-spinners": "^2.6.1", "is-interactive": "^2.0.0", "is-unicode-supported": "^1.1.0", "log-symbols": "^5.1.0", "stdin-discarder": "^0.1.0", "strip-ansi": "^7.0.1", "wcwidth": "^1.0.1" } }, "sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ=="],
|
||||
|
||||
"p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="],
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "sern build",
|
||||
"start": "bun run .",
|
||||
"start": "bun run --inspect .",
|
||||
"install": "sern build",
|
||||
"commands:publish": "sern commands publish",
|
||||
"dev": "sern build -w --watch-command \"bun start\"",
|
||||
"db:mongo": "bun run src/utils/db/migrateMongo.ts"
|
||||
"db:mongo": "bun run src/utils/db/migrateMongo.ts"
|
||||
},
|
||||
"keywords": [
|
||||
"typescript",
|
||||
@@ -20,12 +20,13 @@
|
||||
"dependencies": {
|
||||
"@napi-rs/canvas": "^0.1.72",
|
||||
"@prisma/client": "^6.10.1",
|
||||
"@sern/handler": "^4.0.0",
|
||||
"@sern/handler": "^4.2.4",
|
||||
"@sern/publisher": "1.1.2",
|
||||
"discord.js": "^14.21.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"mongodb": "^6.17.0",
|
||||
"node-html-parser": "^7.0.1",
|
||||
"openai": "^5.10.2",
|
||||
"rockpaperscissors-checker": "^1.2.0",
|
||||
"sharp": "^0.34.2"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//commands directory. REQUIRED
|
||||
export const commands = './dist/commands'
|
||||
// events directory.
|
||||
// export const events = './dist/events'
|
||||
export const events = './dist/events'
|
||||
|
||||
// schedule tasks and declare them here
|
||||
// export const tasks = './dist/tasks'
|
||||
|
||||
11
src/events/ai/message.ts
Normal file
11
src/events/ai/message.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { aiHandle } from '#/aiHandle';
|
||||
import { EventType, eventModule } from '@sern/handler';
|
||||
import { ChannelType } from 'discord.js';
|
||||
|
||||
export default eventModule({
|
||||
type: EventType.Discord,
|
||||
name: 'messageCreate',
|
||||
execute: async (msg) => {
|
||||
await aiHandle(msg, msg.channel.type === ChannelType.PublicThread);
|
||||
}
|
||||
});
|
||||
@@ -8,8 +8,12 @@ import prisma from './utils/db/index.js';
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.MessageContent,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.GuildMessageReactions,
|
||||
GatewayIntentBits.GuildVoiceStates,
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
92
src/utils/aiHandle.ts
Normal file
92
src/utils/aiHandle.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { Message, OmitPartialGroupDMChannel, PublicThreadChannel } from 'discord.js';
|
||||
import { ChatCompletionMessageParam } from 'openai/resources/index';
|
||||
import { openai } from './openai';
|
||||
import prisma from './db';
|
||||
|
||||
export async function aiHandle(msg: OmitPartialGroupDMChannel<Message<boolean>>, isThread = false) {
|
||||
if (msg.author.bot) return;
|
||||
const threadCh = msg.channel as PublicThreadChannel<false>;
|
||||
if (isThread && (threadCh.parentId !== process.env.CHATGPT_CHANNEL)) return;
|
||||
if (!isThread && (msg.channelId !== process.env.CHATGPT_CHANNEL)) return;
|
||||
if (msg.content.startsWith('!')) return;
|
||||
|
||||
let aiChatId;
|
||||
const systemMsg =
|
||||
'You are Vinci, a friendly and helpful Discord bot assistant dedicated to answering all user questions clearly and naturally, as if texting a friend. Avoid mentioning that you are an assistant, since users already know this. When it is useful, you can use markdown. You will interact with Spanish-speaking users, so all your responses, including any future ones, must be written exclusively in Spanish without exception.';
|
||||
|
||||
const messages: ChatCompletionMessageParam[] = [];
|
||||
|
||||
if (isThread) {
|
||||
const dbMsgs = await prisma.aiChat.findFirst({
|
||||
where: { threadid: threadCh.id },
|
||||
select: { messages: true, id: true },
|
||||
});
|
||||
if (dbMsgs) {
|
||||
messages.push(
|
||||
...dbMsgs.messages.map((m) => ({
|
||||
content: m.content,
|
||||
role: m.role as 'user' | 'assistant' | 'system',
|
||||
}))
|
||||
);
|
||||
}
|
||||
messages.push({ role: 'user', content: msg.content });
|
||||
aiChatId = dbMsgs?.id;
|
||||
} else {
|
||||
messages.push({ role: 'system', content: systemMsg }, { role: 'user', content: msg.content });
|
||||
}
|
||||
|
||||
const sentMsg = await msg.reply(':sparkles: Pensando...');
|
||||
const stream = await openai.chat.completions.create({
|
||||
model: 'llama-3.3-70b-versatile',
|
||||
messages,
|
||||
max_tokens: 2000,
|
||||
max_completion_tokens: 2000,
|
||||
temperature: 0.7,
|
||||
});
|
||||
const message = stream.choices[0].message.content!
|
||||
await sentMsg.edit(message.slice(0, 2000));
|
||||
|
||||
messages.push({ role: 'assistant', content: message.replace(/^\n{2}/, '') });
|
||||
|
||||
if (!isThread) {
|
||||
const titleMessage = (
|
||||
await openai.chat.completions.create({
|
||||
model: 'llama-3.3-70b-versatile',
|
||||
messages: [
|
||||
{ role: 'system', content: systemMsg },
|
||||
{
|
||||
role: 'user',
|
||||
content: `Give a short title for the following AI prompt. Do NOT use markdown. USE SPANISH:\n\n${message}`,
|
||||
},
|
||||
],
|
||||
max_tokens: 50,
|
||||
temperature: 0.7,
|
||||
})
|
||||
).choices[0].message
|
||||
.content!.trim()
|
||||
.slice(0, 100);
|
||||
const thread = await sentMsg.startThread({
|
||||
name: titleMessage || 'Nuevo hilo',
|
||||
});
|
||||
|
||||
await prisma.aiChat.create({
|
||||
data: {
|
||||
messageid: msg.id,
|
||||
threadid: thread.id,
|
||||
messages: {
|
||||
createMany: {
|
||||
data: messages as { role: string; content: string }[],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await prisma.aiMessage.createMany({
|
||||
data: messages.map((m) => ({
|
||||
role: m.role,
|
||||
content: m.content,
|
||||
aiChatId: aiChatId!,
|
||||
})) as { role: string; content: string; aiChatId: number }[],
|
||||
});
|
||||
}
|
||||
}
|
||||
6
src/utils/openai.ts
Normal file
6
src/utils/openai.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import OpenAI from "openai";
|
||||
|
||||
export const openai = new OpenAI({
|
||||
apiKey: process.env.AI_KEY,
|
||||
baseURL: "https://api.groq.com/openai/v1"
|
||||
});
|
||||
Reference in New Issue
Block a user