Skip to content

Commit 2a6cf59

Browse files
authored
Merge pull request #335 from CommandDash/web-app-ui-fixes
Web app UI fixes
2 parents 8697288 + 63307cf commit 2a6cf59

File tree

19 files changed

+922
-367
lines changed

19 files changed

+922
-367
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
2+
# More GitHub Actions for Azure: https://github.com/Azure/actions
3+
4+
name: Build and deploy Node.js app to Azure Web App - discord-app
5+
6+
on:
7+
push:
8+
branches:
9+
- discord-bot
10+
workflow_dispatch:
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Set up Node.js version
20+
uses: actions/setup-node@v3
21+
with:
22+
node-version: '18.x'
23+
24+
- name: npm install, build, and test
25+
run: |
26+
cd discord
27+
npm install
28+
npm run build --if-present
29+
npm run test --if-present
30+
31+
- name: Zip artifact for deployment
32+
run: |
33+
cd discord
34+
zip -r ../release.zip ./*
35+
36+
- name: Upload artifact for deployment job
37+
uses: actions/upload-artifact@v4
38+
with:
39+
name: node-app
40+
path: release.zip
41+
42+
deploy:
43+
runs-on: ubuntu-latest
44+
needs: build
45+
environment:
46+
name: 'Production'
47+
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
48+
permissions:
49+
id-token: write #This is required for requesting the JWT
50+
51+
steps:
52+
- name: Download artifact from build job
53+
uses: actions/download-artifact@v4
54+
with:
55+
name: node-app
56+
57+
- name: Unzip artifact for deployment
58+
run: unzip release.zip
59+
60+
- name: Login to Azure
61+
uses: azure/login@v2
62+
with:
63+
client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_F79B5525A24E4DD1B0474AE8E88AC734 }}
64+
tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_82993AB6E0F44B589C524B05273985DB }}
65+
subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_BD59F4177F7646289B41D15D6119C77B }}
66+
67+
- name: 'Deploy to Azure Web App'
68+
id: deploy-to-webapp
69+
uses: azure/webapps-deploy@v3
70+
with:
71+
app-name: 'discord-app'
72+
slot-name: 'Production'
73+
package: .
74+

discord/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.env
2+
./node_modules/
3+
package-lock.json

discord/index.js

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
require('dotenv').config();
2+
const { Client, GatewayIntentBits, ChannelType } = require('discord.js');
3+
const express = require('express');
4+
const app = express();
5+
const port = process.env.PORT || 3000;
6+
7+
const client = new Client({
8+
intents: [
9+
GatewayIntentBits.Guilds,
10+
GatewayIntentBits.GuildMessages,
11+
GatewayIntentBits.MessageContent,
12+
GatewayIntentBits.GuildMessageReactions,
13+
GatewayIntentBits.GuildMembers
14+
]
15+
});
16+
17+
// Parse the GUILD_AGENT_MAP from the environment variable
18+
const guildAgentMap = JSON.parse(process.env.GUILD_AGENT_MAP);
19+
20+
client.once('ready', () => {
21+
console.log(`Logged in as ${client.user.tag}!`);
22+
});
23+
24+
client.on('messageCreate', async message => {
25+
console.log('message received');
26+
27+
// Ignore messages from the bot itself
28+
if (message.author.bot) return;
29+
30+
// Check if the bot is tagged in the message
31+
if (message.mentions.has(client.user)) {
32+
const fetch = (await import('node-fetch')).default;
33+
34+
// Function to split message into chunks of 2000 characters or less
35+
function splitMessage(message, maxLength = 2000) {
36+
if (message.length <= maxLength) return [message];
37+
const parts = [];
38+
let currentPart = '';
39+
let inCodeBlock = false;
40+
41+
const lines = message.split('\n');
42+
for (const line of lines) {
43+
if (line.startsWith('```')) {
44+
inCodeBlock = !inCodeBlock;
45+
}
46+
47+
if (currentPart.length + line.length + 1 > maxLength) {
48+
if (inCodeBlock) {
49+
currentPart += '\n```';
50+
parts.push(currentPart);
51+
currentPart = '```';
52+
} else {
53+
parts.push(currentPart);
54+
currentPart = '';
55+
}
56+
}
57+
58+
currentPart += (currentPart.length > 0 ? '\n' : '') + line;
59+
60+
if (!inCodeBlock && currentPart.length >= maxLength) {
61+
parts.push(currentPart);
62+
currentPart = '';
63+
}
64+
}
65+
66+
if (currentPart.length > 0) {
67+
parts.push(currentPart);
68+
}
69+
70+
return parts;
71+
}
72+
73+
// Function to keep typing indicator active
74+
function keepTyping(channel) {
75+
const interval = setInterval(() => {
76+
channel.sendTyping();
77+
}, 5000); // Typing indicator lasts for 10 seconds, so we refresh it every 5 seconds
78+
return interval;
79+
}
80+
81+
// Function to stop typing indicator
82+
function stopTyping(interval) {
83+
clearInterval(interval);
84+
}
85+
86+
// Determine the channel (thread or main channel)
87+
let channel;
88+
if (message.channel.type === ChannelType.PublicThread || message.channel.type === ChannelType.PrivateThread) {
89+
channel = message.channel
90+
} else {
91+
channel = await message.startThread({
92+
name: `Thread for ${message.author.username}`,
93+
autoArchiveDuration: 60,
94+
});
95+
96+
// 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)");
97+
}
98+
99+
// Start typing indicator
100+
const typingInterval = keepTyping(channel);
101+
102+
// Fetch agent details
103+
const guildId = message.guild.id;
104+
const agentName = guildAgentMap[guildId];
105+
if (!agentName) {
106+
channel.send('Sorry, I could not find the agent for this guild.');
107+
stopTyping(typingInterval); // Stop typing indicator
108+
return;
109+
}
110+
111+
let agentDetails;
112+
try {
113+
const response = await fetch("https://api.commanddash.dev/agent/get-latest-agent", {
114+
method: "POST",
115+
headers: {
116+
"Content-Type": "application/json",
117+
},
118+
body: JSON.stringify({ name: agentName }),
119+
});
120+
121+
if (!response.ok) {
122+
throw new Error(`Failed to load the agent: ${await response.json()}`);
123+
}
124+
125+
agentDetails = await response.json();
126+
console.log(agentDetails)
127+
} catch (error) {
128+
console.error('Error fetching agent details:', error);
129+
channel.send('Sorry, I could not fetch the agent details.');
130+
stopTyping(typingInterval); // Stop typing indicator
131+
return;
132+
}
133+
134+
try {
135+
// Fetch all messages in the thread if it's a thread
136+
let history = [];
137+
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) " });
138+
if (channel.type === ChannelType.PublicThread || channel.type === ChannelType.PrivateThread) {
139+
const messages = await channel.messages.fetch({ limit: 100 });
140+
history = messages.map(msg => ({
141+
"role": msg.author.id === client.user.id ? "model" : "user",
142+
"text": msg.content
143+
}));
144+
}
145+
146+
history.push({ "role": "user", "text": message.content });
147+
148+
// Prepare data for agent answer API
149+
const agentData = {
150+
agent_name: agentDetails.name,
151+
agent_version: agentDetails.version,
152+
chat_history: history,
153+
included_references: [],
154+
private: agentDetails.testing,
155+
};
156+
157+
// Get answer from agent
158+
const response = await fetch("https://api.commanddash.dev/v2/ai/agent/answer", {
159+
method: "POST",
160+
body: JSON.stringify(agentData),
161+
headers: {
162+
"Content-Type": "application/json",
163+
},
164+
});
165+
const modelResponse = await response.json();
166+
console.log(modelResponse);
167+
168+
// Split the response into chunks and send each chunk
169+
const responseChunks = splitMessage(modelResponse.response);
170+
for (const chunk of responseChunks) {
171+
await channel.send(chunk);
172+
}
173+
} catch (error) {
174+
console.error('Error getting answer from agent:', error);
175+
channel.send('Sorry, I could not get an answer from the agent.');
176+
} finally {
177+
stopTyping(typingInterval); // Stop typing indicator
178+
}
179+
}
180+
});
181+
182+
// Handle errors to prevent the bot from crashing
183+
client.on('error', error => {
184+
console.error('An error occurred:', error);
185+
});
186+
187+
client.login(process.env.DISCORD_TOKEN);
188+
189+
// API endpoint to return the Discord token
190+
app.get('/api/token', (req, res) => {
191+
res.json({ token: process.env.DISCORD_TOKEN });
192+
});
193+
194+
app.listen(port, () => {
195+
console.log(`Server is running on port ${port}`);
196+
});

discord/package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "discord",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "node index.js"
8+
},
9+
"keywords": [],
10+
"author": "",
11+
"license": "ISC",
12+
"dependencies": {
13+
"discord.js": "^14.15.3",
14+
"dotenv": "^16.4.5",
15+
"express": "^4.19.2",
16+
"node-fetch": "^3.3.2"
17+
}
18+
}

discord/web.config

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<system.webServer>
4+
<handlers>
5+
<add name="iisnode" path="index.js" verb="*" modules="iisnode" />
6+
</handlers>
7+
<rewrite>
8+
<rules>
9+
<rule name="DynamicContent">
10+
<conditions>
11+
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True" />
12+
</conditions>
13+
<action type="Rewrite" url="index.js" />
14+
</rule>
15+
</rules>
16+
</rewrite>
17+
<security>
18+
<requestFiltering>
19+
<hiddenSegments>
20+
<add segment="node_modules" />
21+
</hiddenSegments>
22+
</requestFiltering>
23+
</security>
24+
</system.webServer>
25+
</configuration>

readme.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
<h1 align="center">CommandDash</h1>
77
<div align = "center">
88

9-
[![VScode Downloads](https://img.shields.io/visual-studio-marketplace/d/WelltestedAI.fluttergpt)](https://marketplace.visualstudio.com/items?itemName=WelltestedAI.fluttergpt&ssr=false#overview) [![License: APACHE](https://img.shields.io/badge/License-APACHE%202.0-yellow)](/LICENSE)
9+
[![VScode Downloads](https://img.shields.io/visual-studio-marketplace/d/WelltestedAI.fluttergpt?style=for-the-badge)](https://marketplace.visualstudio.com/items?itemName=WelltestedAI.fluttergpt&ssr=false#overview) [![License: APACHE](https://img.shields.io/badge/License-APACHE%202.0-yellow?style=for-the-badge)](/LICENSE) <a href="https://app.commanddash.io/agent/dash"><img src="https://img.shields.io/badge/Ask-AI%20Assist-EB9FDA?style=for-the-badge"></a>
10+
1011
</div>
1112

1213
<h3 align="center">Integrate APIs, SDKs or Packages with AI Agents</h3>

vscode/media/agent-ui-builder/agent-ui-builder.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class AgentUIBuilder {
1919
textHtml = textHtml.replace(`<${input.id}>`, inputElement.outerHTML);
2020
});
2121

22-
activeCommandsAttach.textContent = `${slug}`;
22+
// activeCommandsAttach.textContent = `${slug}`;
2323
this.container.innerHTML = `${textHtml}`;
2424
this.ref.appendChild(this.container);
2525
this.registerCodeInputListener();

vscode/media/command-deck/command-deck.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,19 @@ class CommandDeck {
133133
this.ref.innerHTML = textContent.substring(0, atIndex) + textContent.substring(atIndex + 1);
134134
}
135135
if (option?.name.startsWith('@')) {
136+
console.log('agents options', option?.metadata);
136137
activeAgentAttach.style = "color: #497BEF; !important";
137-
activeAgentAttach.textContent = `@${option?.metadata.display_name}`;
138+
139+
agentName = option?.metadata.display_name;
140+
headerLogo.src = option.metadata.avatar_id;
141+
headerText.classList.add("hidden");
142+
headerAgentName.classList.remove("hidden");
143+
headerAgentName.textContent = option?.metadata.display_name;
144+
headerAgentDescription.classList.remove("hidden");
145+
headerAgentDescription.textContent = option?.metadata.description;
138146
activeAgent = true;
139147
commandEnable = false;
140-
activeCommandsAttach.style = "color: var(--vscode-input-placeholderForeground); !important";
141-
activeCommandsAttach.textContent = "/";
148+
142149
currentActiveAgent = option.name;
143150
this.closeMenu();
144151
// Move the cursor to the end of the word
@@ -164,7 +171,7 @@ class CommandDeck {
164171
setTimeout(() => {
165172
adjustHeight();
166173
commandEnable = true;
167-
activeCommandsAttach.style = "color: rgb(236 72 153); !important";
174+
// activeCommandsAttach.style = "color: rgb(236 72 153); !important";
168175
}, 0);
169176
}
170177
};
@@ -247,10 +254,10 @@ class CommandDeck {
247254
setTimeout(() => {
248255
if (this.ref.textContent.trim() === "") {
249256
commandEnable = false;
250-
activeCommandsAttach.style = "color: var(--vscode-input-placeholderForeground); !important";
257+
// activeCommandsAttach.style = "color: var(--vscode-input-placeholderForeground); !important";
251258
agentInputsJson.length = 0;
252259
codeInputId = 0;
253-
activeCommandsAttach.textContent = "/";
260+
// activeCommandsAttach.textContent = "/";
254261
}
255262
}, 0);
256263
}

0 commit comments

Comments
 (0)