ai helper
This commit is contained in:
Binary file not shown.
278
src/web/ai-helper.ts
Normal file
278
src/web/ai-helper.ts
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
656
src/web/templates/ai-helper.ts
Normal file
656
src/web/templates/ai-helper.ts
Normal 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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -11,3 +11,4 @@ export {
|
||||
viewPromptModal,
|
||||
editPromptModal
|
||||
} from "./dashboard";
|
||||
export { aiHelperPage } from "./ai-helper";
|
||||
|
||||
Reference in New Issue
Block a user