diff --git a/src/database/db.sqlite3 b/src/database/db.sqlite3 index b54da94..04fcd5a 100644 Binary files a/src/database/db.sqlite3 and b/src/database/db.sqlite3 differ diff --git a/src/web/ai-helper.ts b/src/web/ai-helper.ts new file mode 100644 index 0000000..c767602 --- /dev/null +++ b/src/web/ai-helper.ts @@ -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; +} diff --git a/src/web/index.ts b/src/web/index.ts index 019e8b7..f830c50 100644 --- a/src/web/index.ts +++ b/src/web/index.ts @@ -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); diff --git a/src/web/templates/ai-helper.ts b/src/web/templates/ai-helper.ts new file mode 100644 index 0000000..93e71c0 --- /dev/null +++ b/src/web/templates/ai-helper.ts @@ -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 = ''; + 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, '
$1
') + .replace(/\`([^\`]+)\`/g, '$1') + .replace(/\\n/g, '
'); + + 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: ` +
+
+
+

🧠 AI Personality Helper

+

Get help creating and refining Joel's personality prompts

+
+ +
+ +
+ + + + + + + +
+ +
+
+ +
+
+
+

👋 Hi! I'm here to help you create personality prompts.

+

Ask me anything about:

+

• Template variables and how to use them

+

• Available tools Joel can use

+

• Style modifiers and their effects

+

• Best practices for prompt writing

+

Try one of the quick action buttons above, or just ask a question!

+
+
+
+ + +
+
+ + +
+

📋 Current Prompt (Working Area)

+ +
+ + +
+
+ + +
+

⚡ Quick Generate

+

Describe the personality you want and I'll generate a prompt for you.

+
+ +
+
+ + + +
+
+
+ + +
+
+

📝 Template Variables

+
+ {author} +
User's display name
+
+
+ {username} +
Discord username
+
+
+ {memories} +
Stored memories about user
+
+
+ {style} +
Detected message style
+
+
+ {styleModifier} +
Style instructions
+
+
+ {channelName} +
Current channel
+
+
+ {guildName} +
Server name
+
+
+ {timestamp} +
Current date/time
+
+
+ +
+

🔧 Available Tools

+
+
lookup_user_memories
+
Look up what's remembered about a user
+
+
+
save_memory
+
Save info about a user for later
+
+
+
search_memories
+
Search all memories by keyword
+
+
+
get_memory_stats
+
Get memory statistics
+
+
+
search_gif
+
Search for GIFs (when enabled)
+
+
+ +
+

🎭 Message Styles

+
+ story +
Creative storytelling mode
+
+
+ snarky +
Sarcastic and witty
+
+
+ insult +
Brutal roast mode
+
+
+ explicit +
Unfiltered adult content
+
+
+ helpful +
Actually useful responses
+
+
+
+
+
+ `, + scripts: aiHelperScripts, + }); +} + +function escapeHtml(text: string): string { + return text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} diff --git a/src/web/templates/dashboard.ts b/src/web/templates/dashboard.ts index 3b250de..aaa3298 100644 --- a/src/web/templates/dashboard.ts +++ b/src/web/templates/dashboard.ts @@ -38,6 +38,7 @@ export function dashboardPage(user: User, guilds: Guild[]): string {

🤖 Joel Bot Dashboard

${user.global_name || user.username} + 🧠 AI Helper
@@ -86,11 +87,15 @@ export function guildDetailPage(guildId: string, guildName: string, options: Bot
-

Custom System Prompts

-

- Create custom personalities for Joel by defining different system prompts. - The active personality will be used when Joel responds in this server. -

+
+
+

Custom System Prompts

+

+ Create custom personalities for Joel by defining different system prompts. +

+
+ 🧠 AI Helper +
${personalities.length === 0 diff --git a/src/web/templates/index.ts b/src/web/templates/index.ts index f087392..fb4f87e 100644 --- a/src/web/templates/index.ts +++ b/src/web/templates/index.ts @@ -11,3 +11,4 @@ export { viewPromptModal, editPromptModal } from "./dashboard"; +export { aiHelperPage } from "./ai-helper";