ai helper

This commit is contained in:
2026-02-01 19:14:01 +01:00
parent 253f0700a0
commit 032d25c9af
6 changed files with 970 additions and 6 deletions

Binary file not shown.

278
src/web/ai-helper.ts Normal file
View File

@@ -0,0 +1,278 @@
/**
* AI Helper for personality configuration
* Provides an intelligent assistant to help users create and refine personality prompts
*/
import { Hono } from "hono";
import OpenAI from "openai";
import { config } from "../core/config";
import { createLogger } from "../core/logger";
import { requireAuth } from "./session";
import { JOEL_TOOLS, GIF_SEARCH_TOOL } from "../services/ai/tools";
import { STYLE_MODIFIERS } from "../features/joel/personalities";
const logger = createLogger("Web:AIHelper");
/**
* System prompt for the AI helper - it knows about personality configuration
*/
const AI_HELPER_SYSTEM_PROMPT = `You are a helpful assistant for configuring AI personality prompts for "Joel", a Discord bot.
Your job is to help users create effective system prompts that define Joel's personality and behavior.
You have expert knowledge about:
1. How system prompts work and best practices for writing them
2. The available template variables users can use in their prompts
3. The tools that Joel can use during conversations
4. Style modifiers that affect Joel's behavior
AVAILABLE TEMPLATE VARIABLES:
Users can include these in their prompts - they will be replaced with actual values at runtime:
- {author} - Display name of the user talking to Joel
- {username} - Discord username
- {userId} - Discord user ID
- {channelName} - Current channel name
- {channelId} - Current channel ID
- {guildName} - Server name
- {guildId} - Server ID
- {messageContent} - The user's message
- {memories} - Stored memories about the user (collected from past conversations)
- {style} - Detected message style (story, snarky, insult, explicit, helpful)
- {styleModifier} - Style-specific instructions based on the detected style
- {timestamp} - Current date/time in ISO format
AVAILABLE TOOLS (Joel can use these during conversations):
${JOEL_TOOLS.map(t => `- ${t.function.name}: ${t.function.description}`).join('\n')}
- ${GIF_SEARCH_TOOL.function.name}: ${GIF_SEARCH_TOOL.function.description} (only when GIF search is enabled)
STYLE MODIFIERS (applied based on detected message intent):
${Object.entries(STYLE_MODIFIERS).map(([style, modifier]) => `- ${style}: ${modifier.split('\n')[0]}`).join('\n')}
TIPS FOR GOOD PROMPTS:
1. Be specific about the personality traits you want
2. Use {author} to personalize responses with the user's name
3. Include {memories} if you want Joel to use remembered facts about users
4. Define clear boundaries and behaviors
5. Include example responses if helpful
6. Consider how the style modifiers will interact with your base prompt
When helping users, you should:
- Explain concepts clearly
- Provide examples when helpful
- Suggest improvements to their prompts
- Answer questions about variables and tools
- Help them understand how Joel will interpret their prompts
Keep responses helpful but concise. Format code/prompts in code blocks when showing examples.`;
export function createAiHelperRoutes() {
const app = new Hono();
// Require authentication for all AI helper routes
app.use("/*", requireAuth);
// Get context information for the AI helper UI
app.get("/context", async (c) => {
return c.json({
variables: [
{ name: "{author}", description: "Display name of the user" },
{ name: "{username}", description: "Discord username" },
{ name: "{userId}", description: "Discord user ID" },
{ name: "{channelName}", description: "Current channel name" },
{ name: "{channelId}", description: "Current channel ID" },
{ name: "{guildName}", description: "Server name" },
{ name: "{guildId}", description: "Server ID" },
{ name: "{messageContent}", description: "The user's message" },
{ name: "{memories}", description: "Stored memories about the user" },
{ name: "{style}", description: "Detected message style" },
{ name: "{styleModifier}", description: "Style-specific instructions" },
{ name: "{timestamp}", description: "Current date/time" },
],
tools: [
...JOEL_TOOLS.map(t => ({
name: t.function.name,
description: t.function.description,
parameters: t.function.parameters,
})),
{
name: GIF_SEARCH_TOOL.function.name,
description: GIF_SEARCH_TOOL.function.description + " (requires GIF search to be enabled)",
parameters: GIF_SEARCH_TOOL.function.parameters,
},
],
styles: Object.entries(STYLE_MODIFIERS).map(([name, modifier]) => ({
name,
description: modifier,
})),
});
});
// Chat endpoint for the AI helper
app.post("/chat", async (c) => {
try {
const body = await c.req.json<{
message: string;
history?: { role: "user" | "assistant"; content: string }[];
currentPrompt?: string;
}>();
if (!body.message) {
return c.json({ error: "Message is required" }, 400);
}
const client = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: config.ai.openRouterApiKey,
defaultHeaders: {
"HTTP-Referer": "https://github.com/crunk-bun",
"X-Title": "Joel Bot - AI Helper",
},
});
// Build messages array with history
const messages: { role: "system" | "user" | "assistant"; content: string }[] = [
{ role: "system", content: AI_HELPER_SYSTEM_PROMPT },
];
// Add conversation history
if (body.history && body.history.length > 0) {
messages.push(...body.history);
}
// If there's a current prompt being edited, include it as context
let userMessage = body.message;
if (body.currentPrompt) {
userMessage = `[Current personality prompt being edited:\n\`\`\`\n${body.currentPrompt}\n\`\`\`]\n\n${body.message}`;
}
messages.push({ role: "user", content: userMessage });
const completion = await client.chat.completions.create({
model: config.ai.classificationModel, // Use the lighter model for helper
messages,
max_tokens: 1000,
temperature: 0.7,
});
const response = completion.choices[0]?.message?.content ?? "I couldn't generate a response. Please try again.";
return c.json({ response });
} catch (error) {
logger.error("AI helper chat error", error);
return c.json({ error: "Failed to generate response" }, 500);
}
});
// Generate a personality prompt based on description
app.post("/generate", async (c) => {
try {
const body = await c.req.json<{
description: string;
includeMemories?: boolean;
includeStyles?: boolean;
}>();
if (!body.description) {
return c.json({ error: "Description is required" }, 400);
}
const client = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: config.ai.openRouterApiKey,
defaultHeaders: {
"HTTP-Referer": "https://github.com/crunk-bun",
"X-Title": "Joel Bot - AI Helper",
},
});
const generatePrompt = `Based on the following description, generate a complete system prompt for the Joel Discord bot personality.
User's description: "${body.description}"
Requirements:
- The prompt should define a clear personality
- Include {author} to personalize with the user's name
${body.includeMemories ? '- Include {memories} to use stored facts about users' : ''}
${body.includeStyles ? '- Include {style} and {styleModifier} for style-aware responses' : ''}
- Be specific and actionable
- Keep it focused but comprehensive
Generate ONLY the system prompt text, no explanations or markdown code blocks.`;
const completion = await client.chat.completions.create({
model: config.ai.classificationModel,
messages: [
{ role: "system", content: "You are an expert at writing AI system prompts. Generate clear, effective prompts based on user descriptions." },
{ role: "user", content: generatePrompt },
],
max_tokens: 800,
temperature: 0.8,
});
const generatedPrompt = completion.choices[0]?.message?.content ?? "";
return c.json({ prompt: generatedPrompt });
} catch (error) {
logger.error("AI helper generate error", error);
return c.json({ error: "Failed to generate prompt" }, 500);
}
});
// Improve an existing prompt
app.post("/improve", async (c) => {
try {
const body = await c.req.json<{
prompt: string;
feedback?: string;
}>();
if (!body.prompt) {
return c.json({ error: "Prompt is required" }, 400);
}
const client = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: config.ai.openRouterApiKey,
defaultHeaders: {
"HTTP-Referer": "https://github.com/crunk-bun",
"X-Title": "Joel Bot - AI Helper",
},
});
const improvePrompt = `Review and improve the following system prompt for a Discord bot personality:
Current prompt:
"""
${body.prompt}
"""
${body.feedback ? `User's feedback: "${body.feedback}"` : 'Improve clarity, effectiveness, and make sure it uses available features well.'}
Available template variables: {author}, {username}, {userId}, {channelName}, {channelId}, {guildName}, {guildId}, {messageContent}, {memories}, {style}, {styleModifier}, {timestamp}
Provide:
1. The improved prompt (in a code block)
2. Brief explanation of changes made
Keep the same general intent but make it more effective.`;
const completion = await client.chat.completions.create({
model: config.ai.classificationModel,
messages: [
{ role: "system", content: "You are an expert at improving AI system prompts. Provide clear improvements while maintaining the original intent." },
{ role: "user", content: improvePrompt },
],
max_tokens: 1200,
temperature: 0.7,
});
const response = completion.choices[0]?.message?.content ?? "";
return c.json({ response });
} catch (error) {
logger.error("AI helper improve error", error);
return c.json({ error: "Failed to improve prompt" }, 500);
}
});
return app;
}

View File

@@ -10,7 +10,8 @@ import type { BotClient } from "../core/client";
import * as oauth from "./oauth";
import * as session from "./session";
import { createApiRoutes } from "./api";
import { loginPage, dashboardPage, guildDetailPage } from "./templates";
import { createAiHelperRoutes } from "./ai-helper";
import { loginPage, dashboardPage, guildDetailPage, aiHelperPage } from "./templates";
import { db } from "../database";
import { personalities, botOptions } from "../database/schema";
import { eq } from "drizzle-orm";
@@ -141,6 +142,29 @@ export function createWebServer(client: BotClient) {
// Mount API routes
app.route("/api", createApiRoutes(client));
// Mount AI helper routes
app.route("/ai-helper", createAiHelperRoutes());
// AI Helper page
app.get("/ai-helper", async (c) => {
const sessionId = session.getSessionCookie(c);
const sess = sessionId ? await session.getSession(sessionId) : null;
if (!sess) {
return c.redirect("/");
}
// Check for optional guild context
const guildId = c.req.query("guild");
let guildName: string | undefined;
if (guildId && client.guilds.cache.has(guildId)) {
guildName = client.guilds.cache.get(guildId)?.name;
}
return c.html(aiHelperPage(guildId, guildName));
});
// Dashboard - requires auth
app.get("/", async (c) => {
const sessionId = session.getSessionCookie(c);

View File

@@ -0,0 +1,656 @@
/**
* AI Helper page template
* Provides an interactive chat interface for personality configuration assistance
*/
import { page } from "./base";
const aiHelperStyles = `
.ai-helper-container {
display: grid;
grid-template-columns: 1fr 350px;
gap: 24px;
margin-top: 24px;
}
@media (max-width: 900px) {
.ai-helper-container {
grid-template-columns: 1fr;
}
}
/* Chat section */
.chat-section {
display: flex;
flex-direction: column;
height: calc(100vh - 200px);
min-height: 500px;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
background: #1a1a1a;
border: 1px solid #2a2a2a;
border-radius: 12px 12px 0 0;
display: flex;
flex-direction: column;
gap: 16px;
}
.chat-message {
max-width: 85%;
padding: 12px 16px;
border-radius: 12px;
line-height: 1.5;
}
.chat-message.user {
align-self: flex-end;
background: #5865F2;
color: white;
}
.chat-message.assistant {
align-self: flex-start;
background: #252525;
color: #e0e0e0;
}
.chat-message pre {
background: #1a1a1a;
padding: 12px;
border-radius: 6px;
overflow-x: auto;
margin: 8px 0;
font-size: 12px;
}
.chat-message code {
background: #1a1a1a;
padding: 2px 6px;
border-radius: 4px;
font-size: 13px;
}
.chat-input-container {
display: flex;
gap: 8px;
padding: 16px;
background: #1a1a1a;
border: 1px solid #2a2a2a;
border-top: none;
border-radius: 0 0 12px 12px;
}
.chat-input {
flex: 1;
padding: 12px 16px;
border: 1px solid #3a3a3a;
border-radius: 8px;
background: #2a2a2a;
color: #e0e0e0;
font-size: 14px;
resize: none;
min-height: 44px;
max-height: 120px;
}
.chat-input:focus {
outline: none;
border-color: #5865F2;
}
.chat-send-btn {
padding: 12px 24px;
background: #5865F2;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
transition: background 0.2s;
}
.chat-send-btn:hover {
background: #4752C4;
}
.chat-send-btn:disabled {
background: #4a4a4a;
cursor: not-allowed;
}
/* Reference panel */
.reference-panel {
background: #1a1a1a;
border: 1px solid #2a2a2a;
border-radius: 12px;
padding: 20px;
height: fit-content;
max-height: calc(100vh - 200px);
overflow-y: auto;
position: sticky;
top: 20px;
}
.reference-section {
margin-bottom: 24px;
}
.reference-section:last-child {
margin-bottom: 0;
}
.reference-section h4 {
margin: 0 0 12px 0;
color: #5865F2;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.reference-item {
padding: 8px 10px;
background: #252525;
border-radius: 6px;
margin-bottom: 6px;
font-size: 13px;
}
.reference-item code {
color: #4ade80;
background: #1a2a1a;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
}
.reference-item .desc {
color: #888;
font-size: 11px;
margin-top: 4px;
}
.tool-item {
padding: 8px 10px;
background: #252535;
border-radius: 6px;
margin-bottom: 6px;
}
.tool-item .name {
color: #a78bfa;
font-weight: 500;
font-size: 12px;
}
.tool-item .desc {
color: #888;
font-size: 11px;
margin-top: 4px;
}
/* Quick actions */
.quick-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.quick-action {
padding: 8px 14px;
background: #252525;
border: 1px solid #3a3a3a;
border-radius: 20px;
color: #b0b0b0;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
}
.quick-action:hover {
background: #353535;
color: #fff;
border-color: #5865F2;
}
/* Prompt editor panel */
.prompt-editor-panel {
margin-top: 16px;
padding: 16px;
background: #1a2a1a;
border-radius: 8px;
border: 1px solid #2a3a2a;
}
.prompt-editor-panel h4 {
margin: 0 0 12px 0;
color: #4ade80;
font-size: 14px;
}
.prompt-editor-panel textarea {
width: 100%;
min-height: 150px;
padding: 12px;
background: #252535;
border: 1px solid #3a3a3a;
border-radius: 6px;
color: #e0e0e0;
font-family: monospace;
font-size: 12px;
resize: vertical;
}
.prompt-editor-panel textarea:focus {
outline: none;
border-color: #4ade80;
}
.prompt-actions {
display: flex;
gap: 8px;
margin-top: 12px;
}
.typing-indicator {
display: flex;
gap: 4px;
padding: 12px 16px;
align-self: flex-start;
background: #252525;
border-radius: 12px;
}
.typing-indicator span {
width: 8px;
height: 8px;
background: #5865F2;
border-radius: 50%;
animation: typing 1.4s infinite;
}
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing {
0%, 60%, 100% { transform: translateY(0); opacity: 0.6; }
30% { transform: translateY(-4px); opacity: 1; }
}
.welcome-message {
text-align: center;
padding: 40px 20px;
color: #888;
}
.welcome-message h3 {
color: #fff;
margin-bottom: 16px;
}
.welcome-message p {
margin: 8px 0;
}
`;
const aiHelperScripts = `
let chatHistory = [];
let isProcessing = false;
// Auto-resize textarea
const chatInput = document.getElementById('chat-input');
chatInput.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
// Send on Enter (Shift+Enter for newline)
chatInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
async function sendMessage(customMessage) {
if (isProcessing) return;
const input = document.getElementById('chat-input');
const message = customMessage || input.value.trim();
if (!message) return;
isProcessing = true;
input.value = '';
input.style.height = 'auto';
const sendBtn = document.getElementById('send-btn');
sendBtn.disabled = true;
// Add user message
addMessage('user', message);
chatHistory.push({ role: 'user', content: message });
// Show typing indicator
const messagesContainer = document.getElementById('chat-messages');
const typingDiv = document.createElement('div');
typingDiv.className = 'typing-indicator';
typingDiv.id = 'typing-indicator';
typingDiv.innerHTML = '<span></span><span></span><span></span>';
messagesContainer.appendChild(typingDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
try {
// Get current prompt if any
const promptEditor = document.getElementById('current-prompt');
const currentPrompt = promptEditor ? promptEditor.value.trim() : '';
const response = await fetch('/ai-helper/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message,
history: chatHistory.slice(0, -1), // Don't include the message we just added
currentPrompt: currentPrompt || undefined
})
});
const data = await response.json();
// Remove typing indicator
document.getElementById('typing-indicator')?.remove();
if (data.error) {
addMessage('assistant', 'Sorry, I encountered an error. Please try again.');
} else {
addMessage('assistant', data.response);
chatHistory.push({ role: 'assistant', content: data.response });
}
} catch (error) {
document.getElementById('typing-indicator')?.remove();
addMessage('assistant', 'Sorry, I couldn\\'t connect to the server. Please try again.');
}
isProcessing = false;
sendBtn.disabled = false;
input.focus();
}
function addMessage(role, content) {
const messagesContainer = document.getElementById('chat-messages');
const welcomeMessage = document.querySelector('.welcome-message');
if (welcomeMessage) welcomeMessage.remove();
const messageDiv = document.createElement('div');
messageDiv.className = 'chat-message ' + role;
// Basic markdown rendering
let html = content
.replace(/\`\`\`([\\s\\S]*?)\`\`\`/g, '<pre>$1</pre>')
.replace(/\`([^\`]+)\`/g, '<code>$1</code>')
.replace(/\\n/g, '<br>');
messageDiv.innerHTML = html;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
function quickAction(action) {
const actions = {
'explain-variables': 'Explain all the template variables I can use in my prompts and when to use each one.',
'explain-tools': 'What tools does Joel have access to? How do they work?',
'explain-styles': 'What are the different message styles and how do they affect responses?',
'example-prompt': 'Show me an example of a well-written personality prompt with explanations.',
'improve-prompt': 'Can you review my current prompt and suggest improvements?',
'create-sarcastic': 'Help me create a sarcastic but funny personality.',
'create-helpful': 'Help me create a helpful assistant personality.',
'create-character': 'Help me create a personality based on a fictional character.'
};
if (actions[action]) {
sendMessage(actions[action]);
}
}
async function generatePrompt() {
const description = document.getElementById('generate-description').value.trim();
if (!description) return;
const btn = event.target;
btn.disabled = true;
btn.textContent = 'Generating...';
try {
const includeMemories = document.getElementById('include-memories').checked;
const includeStyles = document.getElementById('include-styles').checked;
const response = await fetch('/ai-helper/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ description, includeMemories, includeStyles })
});
const data = await response.json();
if (data.prompt) {
document.getElementById('current-prompt').value = data.prompt;
addMessage('assistant', 'I\\'ve generated a prompt based on your description! You can see it in the "Current Prompt" editor below. Feel free to ask me to modify it or explain any part.');
chatHistory.push({ role: 'assistant', content: 'Generated a new prompt based on user description.' });
}
} catch (error) {
addMessage('assistant', 'Sorry, I couldn\\'t generate the prompt. Please try again.');
}
btn.disabled = false;
btn.textContent = 'Generate';
}
async function improvePrompt() {
const prompt = document.getElementById('current-prompt').value.trim();
if (!prompt) {
addMessage('assistant', 'Please add a prompt to the editor first, then I can help improve it.');
return;
}
sendMessage('Please review and improve my current prompt. Make it more effective while keeping the same general intent.');
}
function copyPrompt() {
const prompt = document.getElementById('current-prompt').value;
navigator.clipboard.writeText(prompt);
const btn = event.target;
const originalText = btn.textContent;
btn.textContent = 'Copied!';
setTimeout(() => btn.textContent = originalText, 2000);
}
`;
export function aiHelperPage(guildId?: string, guildName?: string): string {
return page({
title: "AI Personality Helper - Joel Bot",
styles: aiHelperStyles,
content: `
<div class="container">
<div class="header">
<div>
<h1>🧠 AI Personality Helper</h1>
<p style="color: #888; margin: 4px 0 0 0;">Get help creating and refining Joel's personality prompts</p>
</div>
<div class="user-info">
${guildId ? `<span>Configuring: ${escapeHtml(guildName || guildId)}</span>` : ''}
<a href="/dashboard" class="btn btn-secondary btn-sm">← Back to Dashboard</a>
</div>
</div>
<div class="quick-actions">
<button class="quick-action" onclick="quickAction('explain-variables')">📝 Variables</button>
<button class="quick-action" onclick="quickAction('explain-tools')">🔧 Tools</button>
<button class="quick-action" onclick="quickAction('explain-styles')">🎭 Styles</button>
<button class="quick-action" onclick="quickAction('example-prompt')">💡 Example</button>
<button class="quick-action" onclick="quickAction('create-sarcastic')">😏 Sarcastic</button>
<button class="quick-action" onclick="quickAction('create-helpful')">🤝 Helpful</button>
<button class="quick-action" onclick="quickAction('create-character')">🎬 Character</button>
</div>
<div class="ai-helper-container">
<div>
<!-- Chat section -->
<div class="chat-section">
<div class="chat-messages" id="chat-messages">
<div class="welcome-message">
<h3>👋 Hi! I'm here to help you create personality prompts.</h3>
<p>Ask me anything about:</p>
<p>• Template variables and how to use them</p>
<p>• Available tools Joel can use</p>
<p>• Style modifiers and their effects</p>
<p>• Best practices for prompt writing</p>
<p style="margin-top: 16px; color: #5865F2;">Try one of the quick action buttons above, or just ask a question!</p>
</div>
</div>
<div class="chat-input-container">
<textarea
id="chat-input"
class="chat-input"
placeholder="Ask about variables, tools, or get help with your prompt..."
rows="1"
></textarea>
<button id="send-btn" class="chat-send-btn" onclick="sendMessage()">Send</button>
</div>
</div>
<!-- Prompt editor -->
<div class="prompt-editor-panel">
<h4>📋 Current Prompt (Working Area)</h4>
<textarea id="current-prompt" placeholder="Paste or write your prompt here. The AI helper can see this and help you improve it."></textarea>
<div class="prompt-actions">
<button class="btn btn-sm" onclick="improvePrompt()">✨ Improve This</button>
<button class="btn btn-sm btn-secondary" onclick="copyPrompt()">📋 Copy</button>
</div>
</div>
<!-- Quick generate -->
<div class="card" style="margin-top: 16px;">
<h3>⚡ Quick Generate</h3>
<p style="color: #888; margin-bottom: 12px;">Describe the personality you want and I'll generate a prompt for you.</p>
<div class="form-group">
<textarea id="generate-description" placeholder="e.g., A grumpy wizard who speaks in riddles and gets annoyed easily..." style="min-height: 80px;"></textarea>
</div>
<div style="display: flex; gap: 16px; align-items: center; flex-wrap: wrap;">
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer; color: #888;">
<input type="checkbox" id="include-memories" checked style="width: 16px; height: 16px;">
Include memories
</label>
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer; color: #888;">
<input type="checkbox" id="include-styles" checked style="width: 16px; height: 16px;">
Include style handling
</label>
<button class="btn" onclick="generatePrompt()">Generate</button>
</div>
</div>
</div>
<!-- Reference panel -->
<div class="reference-panel">
<div class="reference-section">
<h4>📝 Template Variables</h4>
<div class="reference-item">
<code>{author}</code>
<div class="desc">User's display name</div>
</div>
<div class="reference-item">
<code>{username}</code>
<div class="desc">Discord username</div>
</div>
<div class="reference-item">
<code>{memories}</code>
<div class="desc">Stored memories about user</div>
</div>
<div class="reference-item">
<code>{style}</code>
<div class="desc">Detected message style</div>
</div>
<div class="reference-item">
<code>{styleModifier}</code>
<div class="desc">Style instructions</div>
</div>
<div class="reference-item">
<code>{channelName}</code>
<div class="desc">Current channel</div>
</div>
<div class="reference-item">
<code>{guildName}</code>
<div class="desc">Server name</div>
</div>
<div class="reference-item">
<code>{timestamp}</code>
<div class="desc">Current date/time</div>
</div>
</div>
<div class="reference-section">
<h4>🔧 Available Tools</h4>
<div class="tool-item">
<div class="name">lookup_user_memories</div>
<div class="desc">Look up what's remembered about a user</div>
</div>
<div class="tool-item">
<div class="name">save_memory</div>
<div class="desc">Save info about a user for later</div>
</div>
<div class="tool-item">
<div class="name">search_memories</div>
<div class="desc">Search all memories by keyword</div>
</div>
<div class="tool-item">
<div class="name">get_memory_stats</div>
<div class="desc">Get memory statistics</div>
</div>
<div class="tool-item">
<div class="name">search_gif</div>
<div class="desc">Search for GIFs (when enabled)</div>
</div>
</div>
<div class="reference-section">
<h4>🎭 Message Styles</h4>
<div class="reference-item">
<code>story</code>
<div class="desc">Creative storytelling mode</div>
</div>
<div class="reference-item">
<code>snarky</code>
<div class="desc">Sarcastic and witty</div>
</div>
<div class="reference-item">
<code>insult</code>
<div class="desc">Brutal roast mode</div>
</div>
<div class="reference-item">
<code>explicit</code>
<div class="desc">Unfiltered adult content</div>
</div>
<div class="reference-item">
<code>helpful</code>
<div class="desc">Actually useful responses</div>
</div>
</div>
</div>
</div>
</div>
`,
scripts: aiHelperScripts,
});
}
function escapeHtml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}

View File

@@ -38,6 +38,7 @@ export function dashboardPage(user: User, guilds: Guild[]): string {
<h1>🤖 Joel Bot Dashboard</h1>
<div class="user-info">
<span>${user.global_name || user.username}</span>
<a href="/ai-helper" class="btn btn-sm" style="background: #9333ea;">🧠 AI Helper</a>
<button class="btn btn-secondary btn-sm" hx-post="/auth/logout" hx-redirect="/">Logout</button>
</div>
</div>
@@ -86,11 +87,15 @@ export function guildDetailPage(guildId: string, guildName: string, options: Bot
<!-- System Prompts Tab -->
<div id="tab-prompts" class="tab-content active">
<div class="card">
<h3>Custom System Prompts</h3>
<p style="color: #888; margin-bottom: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<div>
<h3 style="margin: 0;">Custom System Prompts</h3>
<p style="color: #888; margin: 8px 0 0 0;">
Create custom personalities for Joel by defining different system prompts.
The active personality will be used when Joel responds in this server.
</p>
</div>
<a href="/ai-helper?guild=${guildId}" class="btn btn-sm" style="background: #9333ea;">🧠 AI Helper</a>
</div>
<div id="personalities-list">
${personalities.length === 0

View File

@@ -11,3 +11,4 @@ export {
viewPromptModal,
editPromptModal
} from "./dashboard";
export { aiHelperPage } from "./ai-helper";