diff --git a/.github/workflows/discord-bot_discord-app.yml b/.github/workflows/discord-bot_discord-app.yml
new file mode 100644
index 00000000..2e495c5a
--- /dev/null
+++ b/.github/workflows/discord-bot_discord-app.yml
@@ -0,0 +1,74 @@
+# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
+# More GitHub Actions for Azure: https://github.com/Azure/actions
+
+name: Build and deploy Node.js app to Azure Web App - discord-app
+
+on:
+ push:
+ branches:
+ - discord-bot
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Node.js version
+ uses: actions/setup-node@v3
+ with:
+ node-version: '18.x'
+
+ - name: npm install, build, and test
+ run: |
+ cd discord
+ npm install
+ npm run build --if-present
+ npm run test --if-present
+
+ - name: Zip artifact for deployment
+ run: |
+ cd discord
+ zip -r ../release.zip ./*
+
+ - name: Upload artifact for deployment job
+ uses: actions/upload-artifact@v4
+ with:
+ name: node-app
+ path: release.zip
+
+ deploy:
+ runs-on: ubuntu-latest
+ needs: build
+ environment:
+ name: 'Production'
+ url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
+ permissions:
+ id-token: write #This is required for requesting the JWT
+
+ steps:
+ - name: Download artifact from build job
+ uses: actions/download-artifact@v4
+ with:
+ name: node-app
+
+ - name: Unzip artifact for deployment
+ run: unzip release.zip
+
+ - name: Login to Azure
+ uses: azure/login@v2
+ with:
+ client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_F79B5525A24E4DD1B0474AE8E88AC734 }}
+ tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_82993AB6E0F44B589C524B05273985DB }}
+ subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_BD59F4177F7646289B41D15D6119C77B }}
+
+ - name: 'Deploy to Azure Web App'
+ id: deploy-to-webapp
+ uses: azure/webapps-deploy@v3
+ with:
+ app-name: 'discord-app'
+ slot-name: 'Production'
+ package: .
+
\ No newline at end of file
diff --git a/discord/.gitignore b/discord/.gitignore
new file mode 100644
index 00000000..b21c85fd
--- /dev/null
+++ b/discord/.gitignore
@@ -0,0 +1,3 @@
+.env
+./node_modules/
+package-lock.json
\ No newline at end of file
diff --git a/discord/index.js b/discord/index.js
new file mode 100644
index 00000000..2533e4e9
--- /dev/null
+++ b/discord/index.js
@@ -0,0 +1,196 @@
+require('dotenv').config();
+const { Client, GatewayIntentBits, ChannelType } = require('discord.js');
+const express = require('express');
+const app = express();
+const port = process.env.PORT || 3000;
+
+const client = new Client({
+ intents: [
+ GatewayIntentBits.Guilds,
+ GatewayIntentBits.GuildMessages,
+ GatewayIntentBits.MessageContent,
+ GatewayIntentBits.GuildMessageReactions,
+ GatewayIntentBits.GuildMembers
+ ]
+});
+
+// Parse the GUILD_AGENT_MAP from the environment variable
+const guildAgentMap = JSON.parse(process.env.GUILD_AGENT_MAP);
+
+client.once('ready', () => {
+ console.log(`Logged in as ${client.user.tag}!`);
+});
+
+client.on('messageCreate', async message => {
+ console.log('message received');
+
+ // Ignore messages from the bot itself
+ if (message.author.bot) return;
+
+ // Check if the bot is tagged in the message
+ if (message.mentions.has(client.user)) {
+ const fetch = (await import('node-fetch')).default;
+
+ // Function to split message into chunks of 2000 characters or less
+ function splitMessage(message, maxLength = 2000) {
+ if (message.length <= maxLength) return [message];
+ const parts = [];
+ let currentPart = '';
+ let inCodeBlock = false;
+
+ const lines = message.split('\n');
+ for (const line of lines) {
+ if (line.startsWith('```')) {
+ inCodeBlock = !inCodeBlock;
+ }
+
+ if (currentPart.length + line.length + 1 > maxLength) {
+ if (inCodeBlock) {
+ currentPart += '\n```';
+ parts.push(currentPart);
+ currentPart = '```';
+ } else {
+ parts.push(currentPart);
+ currentPart = '';
+ }
+ }
+
+ currentPart += (currentPart.length > 0 ? '\n' : '') + line;
+
+ if (!inCodeBlock && currentPart.length >= maxLength) {
+ parts.push(currentPart);
+ currentPart = '';
+ }
+ }
+
+ if (currentPart.length > 0) {
+ parts.push(currentPart);
+ }
+
+ return parts;
+ }
+
+ // Function to keep typing indicator active
+ function keepTyping(channel) {
+ const interval = setInterval(() => {
+ channel.sendTyping();
+ }, 5000); // Typing indicator lasts for 10 seconds, so we refresh it every 5 seconds
+ return interval;
+ }
+
+ // Function to stop typing indicator
+ function stopTyping(interval) {
+ clearInterval(interval);
+ }
+
+ // Determine the channel (thread or main channel)
+ let channel;
+ if (message.channel.type === ChannelType.PublicThread || message.channel.type === ChannelType.PrivateThread) {
+ channel = message.channel
+ } else {
+ channel = await message.startThread({
+ name: `Thread for ${message.author.username}`,
+ autoArchiveDuration: 60,
+ });
+
+ // await channel.send("Hold tight, I'm preparing your answer!\n\nQuick tip ⚡️, I can help you better from your IDE. Install the [VSCode extension](https://marketplace.visualstudio.com/items?itemName=WelltestedAI.fluttergpt)");
+ }
+
+ // Start typing indicator
+ const typingInterval = keepTyping(channel);
+
+ // Fetch agent details
+ const guildId = message.guild.id;
+ const agentName = guildAgentMap[guildId];
+ if (!agentName) {
+ channel.send('Sorry, I could not find the agent for this guild.');
+ stopTyping(typingInterval); // Stop typing indicator
+ return;
+ }
+
+ let agentDetails;
+ try {
+ const response = await fetch("https://api.commanddash.dev/agent/get-latest-agent", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ name: agentName }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to load the agent: ${await response.json()}`);
+ }
+
+ agentDetails = await response.json();
+ console.log(agentDetails)
+ } catch (error) {
+ console.error('Error fetching agent details:', error);
+ channel.send('Sorry, I could not fetch the agent details.');
+ stopTyping(typingInterval); // Stop typing indicator
+ return;
+ }
+
+ try {
+ // Fetch all messages in the thread if it's a thread
+ let history = [];
+ history.push({ "role": "user", "text": "This conversation is happening on Discord, so please keep response concise and quote snippets only when necessary (unless of course explicity requested) " });
+ if (channel.type === ChannelType.PublicThread || channel.type === ChannelType.PrivateThread) {
+ const messages = await channel.messages.fetch({ limit: 100 });
+ history = messages.map(msg => ({
+ "role": msg.author.id === client.user.id ? "model" : "user",
+ "text": msg.content
+ }));
+ }
+
+ history.push({ "role": "user", "text": message.content });
+
+ // Prepare data for agent answer API
+ const agentData = {
+ agent_name: agentDetails.name,
+ agent_version: agentDetails.version,
+ chat_history: history,
+ included_references: [],
+ private: agentDetails.testing,
+ };
+
+ // Get answer from agent
+ const response = await fetch("https://api.commanddash.dev/v2/ai/agent/answer", {
+ method: "POST",
+ body: JSON.stringify(agentData),
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+ const modelResponse = await response.json();
+ console.log(modelResponse);
+
+ // Split the response into chunks and send each chunk
+ const responseChunks = splitMessage(modelResponse.response);
+ for (const chunk of responseChunks) {
+ await channel.send(chunk);
+ }
+ } catch (error) {
+ console.error('Error getting answer from agent:', error);
+ channel.send('Sorry, I could not get an answer from the agent.');
+ } finally {
+ stopTyping(typingInterval); // Stop typing indicator
+ }
+ }
+});
+
+// Handle errors to prevent the bot from crashing
+client.on('error', error => {
+ console.error('An error occurred:', error);
+});
+
+client.login(process.env.DISCORD_TOKEN);
+
+// API endpoint to return the Discord token
+app.get('/api/token', (req, res) => {
+ res.json({ token: process.env.DISCORD_TOKEN });
+});
+
+app.listen(port, () => {
+ console.log(`Server is running on port ${port}`);
+});
\ No newline at end of file
diff --git a/discord/package.json b/discord/package.json
new file mode 100644
index 00000000..3e73e7c1
--- /dev/null
+++ b/discord/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "discord",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "start": "node index.js"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "discord.js": "^14.15.3",
+ "dotenv": "^16.4.5",
+ "express": "^4.19.2",
+ "node-fetch": "^3.3.2"
+ }
+}
diff --git a/discord/web.config b/discord/web.config
new file mode 100644
index 00000000..8113473f
--- /dev/null
+++ b/discord/web.config
@@ -0,0 +1,25 @@
+
+
# Ask Dash
- @ + Type "@" to switch agent
-/ -
+ -->