Add mobile responsiveness and AI chat functionality

Introduces a mobile-friendly navigation system for the dashboard with a slide-out sidebar and overlay, alongside a new AI chat command utilizing the OpenAI API for image generation and conversational interactions.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: a4c8a9aa-aa7e-4928-95e7-0c95279d5dc6
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/3tJ1Z1J
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sirpiglr 2025-12-13 09:46:50 +00:00
parent 074b4157ec
commit 8f198b393b
5 changed files with 288 additions and 4 deletions

View file

@ -12,6 +12,7 @@ deploymentTarget = "gce"
[agent]
expertMode = true
integrations = ["javascript_openai_ai_integrations:1.0.0"]
[[ports]]
localPort = 5000

165
aethex-bot/commands/ai.js Normal file
View file

@ -0,0 +1,165 @@
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const OpenAI = require('openai');
const openai = new OpenAI({
apiKey: process.env.AI_INTEGRATIONS_OPENAI_API_KEY,
baseURL: process.env.AI_INTEGRATIONS_OPENAI_BASE_URL
});
const conversationHistory = new Map();
const MAX_HISTORY = 10;
module.exports = {
data: new SlashCommandBuilder()
.setName('ai')
.setDescription('Chat with AeThex AI assistant')
.addSubcommand(sub =>
sub.setName('chat')
.setDescription('Send a message to the AI')
.addStringOption(opt =>
opt.setName('message')
.setDescription('Your message to the AI')
.setRequired(true)))
.addSubcommand(sub =>
sub.setName('clear')
.setDescription('Clear your conversation history'))
.addSubcommand(sub =>
sub.setName('image')
.setDescription('Generate an image with AI')
.addStringOption(opt =>
opt.setName('prompt')
.setDescription('Describe the image you want to generate')
.setRequired(true))),
async execute(interaction, supabase, client) {
const subcommand = interaction.options.getSubcommand();
if (subcommand === 'clear') {
conversationHistory.delete(interaction.user.id);
return interaction.reply({
embeds: [new EmbedBuilder()
.setColor(0x5865f2)
.setDescription('Your conversation history has been cleared.')],
ephemeral: true
});
}
if (subcommand === 'image') {
await interaction.deferReply();
const prompt = interaction.options.getString('prompt');
try {
const response = await openai.images.generate({
model: 'gpt-image-1',
prompt: prompt,
n: 1,
size: '1024x1024'
});
const imageUrl = response.data[0]?.url || response.data[0]?.b64_json;
if (!imageUrl) {
return interaction.editReply({
embeds: [new EmbedBuilder()
.setColor(0xff0000)
.setDescription('Failed to generate image. Please try again.')]
});
}
const embed = new EmbedBuilder()
.setColor(0x5865f2)
.setTitle('AI Generated Image')
.setDescription(`**Prompt:** ${prompt}`)
.setImage(imageUrl)
.setFooter({ text: `Requested by ${interaction.user.username}`, iconURL: interaction.user.displayAvatarURL() })
.setTimestamp();
return interaction.editReply({ embeds: [embed] });
} catch (error) {
console.error('AI image error:', error);
return interaction.editReply({
embeds: [new EmbedBuilder()
.setColor(0xff0000)
.setDescription('Failed to generate image. Please try again later.')]
});
}
}
if (subcommand === 'chat') {
await interaction.deferReply();
const userMessage = interaction.options.getString('message');
try {
let history = conversationHistory.get(interaction.user.id) || [];
history.push({
role: 'user',
content: userMessage
});
const systemPrompt = `You are AeThex AI, a helpful and friendly assistant for the AeThex Discord community. You help users with questions about Discord, gaming, technology, and general topics. Keep responses concise but helpful. Be friendly and engaging. If asked about AeThex, explain it's a creative technology community focused on gaming, development, and digital arts.`;
const messages = [
{ role: 'system', content: systemPrompt },
...history
];
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: messages,
max_tokens: 1000,
temperature: 0.7
});
const aiResponse = completion.choices[0]?.message?.content || 'I could not generate a response.';
history.push({
role: 'assistant',
content: aiResponse
});
if (history.length > MAX_HISTORY * 2) {
history = history.slice(-MAX_HISTORY * 2);
}
conversationHistory.set(interaction.user.id, history);
let displayResponse = aiResponse;
if (displayResponse.length > 4000) {
displayResponse = displayResponse.substring(0, 4000) + '...';
}
const embed = new EmbedBuilder()
.setColor(0x5865f2)
.setAuthor({
name: 'AeThex AI',
iconURL: client.user?.displayAvatarURL() || interaction.guild?.iconURL()
})
.setDescription(displayResponse)
.setFooter({
text: `Requested by ${interaction.user.username}`,
iconURL: interaction.user.displayAvatarURL()
})
.setTimestamp();
return interaction.editReply({ embeds: [embed] });
} catch (error) {
console.error('AI chat error:', error);
let errorMessage = 'Something went wrong while processing your request.';
if (error.message?.includes('rate limit')) {
errorMessage = 'Too many requests. Please wait a moment and try again.';
} else if (error.message?.includes('quota')) {
errorMessage = 'AI service quota exceeded. Please try again later.';
}
return interaction.editReply({
embeds: [new EmbedBuilder()
.setColor(0xff0000)
.setDescription(errorMessage)]
});
}
}
}
};

View file

@ -1088,16 +1088,79 @@
gap: 0.75rem;
}
.mobile-menu-btn {
display: none;
position: fixed;
top: 1rem;
left: 1rem;
z-index: 200;
width: 44px;
height: 44px;
background: var(--card);
border: 1px solid var(--card-border);
border-radius: 10px;
cursor: pointer;
align-items: center;
justify-content: center;
backdrop-filter: blur(10px);
transition: all 0.2s;
}
.mobile-menu-btn:hover {
border-color: var(--primary);
background: rgba(99, 102, 241, 0.1);
}
.mobile-menu-btn svg {
color: var(--foreground);
}
.sidebar-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 90;
backdrop-filter: blur(4px);
}
.sidebar-overlay.open {
display: block;
}
@media (max-width: 768px) {
.mobile-menu-btn {
display: flex;
}
.sidebar {
transform: translateX(-100%);
z-index: 100;
transition: transform 0.3s;
transition: transform 0.3s ease;
}
.sidebar.open {
transform: translateX(0);
box-shadow: 4px 0 24px rgba(0, 0, 0, 0.5);
}
.main {
margin-left: 0;
max-width: 100%;
padding: 1rem;
padding-top: 4.5rem;
}
.sidebar.open { transform: translateX(0); }
.main { margin-left: 0; max-width: 100%; padding: 1.5rem; }
.stats-grid { grid-template-columns: repeat(2, 1fr); }
.page-title { font-size: 1.5rem; }
.page-header { margin-bottom: 1.5rem; }
.form-grid { grid-template-columns: 1fr; }
.achievement-grid { grid-template-columns: 1fr; }
.shop-grid { grid-template-columns: repeat(2, 1fr); }
.tabs { flex-wrap: wrap; }
.modal-content { margin: 1rem; max-height: calc(100vh - 2rem); }
}
@media (max-width: 480px) {
.stats-grid { grid-template-columns: 1fr; }
.shop-grid { grid-template-columns: 1fr; }
.main { padding: 0.75rem; padding-top: 4.5rem; }
}
</style>
</head>
@ -1115,7 +1178,15 @@
</div>
<div id="app" class="app hidden">
<aside class="sidebar">
<button class="mobile-menu-btn" id="mobileMenuBtn" onclick="toggleMobileSidebar()">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<div class="sidebar-overlay" id="sidebarOverlay" onclick="closeMobileSidebar()"></div>
<aside class="sidebar" id="sidebar">
<a href="/" class="logo">
<img src="/logo.png" alt="AeThex" class="logo-icon">
<span class="logo-text">AeThex | Warden</span>
@ -4509,6 +4580,30 @@
}
}
function toggleMobileSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.toggle('open');
overlay.classList.toggle('open');
document.body.style.overflow = sidebar.classList.contains('open') ? 'hidden' : '';
}
function closeMobileSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.remove('open');
overlay.classList.remove('open');
document.body.style.overflow = '';
}
document.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', () => {
if (window.innerWidth <= 768) {
closeMobileSidebar();
}
});
});
init();
</script>
</body>

22
package-lock.json generated
View file

@ -19,6 +19,7 @@
"express-session": "^1.18.2",
"ffmpeg-static": "^5.3.0",
"kazagumo": "^3.4.0",
"openai": "^6.10.0",
"pg": "^8.16.3",
"shoukaku": "^4.2.0",
"stripe": "^20.0.0",
@ -2695,6 +2696,27 @@
"wrappy": "1"
}
},
"node_modules/openai": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-6.10.0.tgz",
"integrity": "sha512-ITxOGo7rO3XRMiKA5l7tQ43iNNu+iXGFAcf2t+aWVzzqRaS0i7m1K2BhxNdaveB+5eENhO0VY1FkiZzhBk4v3A==",
"license": "Apache-2.0",
"bin": {
"openai": "bin/cli"
},
"peerDependencies": {
"ws": "^8.18.0",
"zod": "^3.25 || ^4.0"
},
"peerDependenciesMeta": {
"ws": {
"optional": true
},
"zod": {
"optional": true
}
}
},
"node_modules/parse-cache-control": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",

View file

@ -20,6 +20,7 @@
"express-session": "^1.18.2",
"ffmpeg-static": "^5.3.0",
"kazagumo": "^3.4.0",
"openai": "^6.10.0",
"pg": "^8.16.3",
"shoukaku": "^4.2.0",
"stripe": "^20.0.0",