improve joels memory
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* Message repository - handles all message-related database operations
|
||||
*/
|
||||
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { and, desc, eq } from "drizzle-orm";
|
||||
import { db } from "../connection";
|
||||
import { messages, users, type InsertMessage, type Message } from "../schema";
|
||||
|
||||
@@ -28,4 +28,57 @@ export const messageRepository = {
|
||||
userName: r.users?.name ?? null,
|
||||
}));
|
||||
},
|
||||
|
||||
async findRecentContext(
|
||||
guildId: string,
|
||||
channelId: string,
|
||||
options?: {
|
||||
maxMessages?: number;
|
||||
withinMinutes?: number;
|
||||
excludeMessageId?: string;
|
||||
}
|
||||
): Promise<Array<{ message: Message; userName: string | null }>> {
|
||||
const maxMessages = options?.maxMessages ?? 5;
|
||||
const withinMinutes = options?.withinMinutes ?? 20;
|
||||
const scanLimit = Math.max(20, maxMessages * 5);
|
||||
|
||||
const results = await db
|
||||
.select()
|
||||
.from(messages)
|
||||
.where(
|
||||
and(eq(messages.guild_id, guildId), eq(messages.channel_id, channelId))
|
||||
)
|
||||
.leftJoin(users, eq(users.id, messages.user_id))
|
||||
.orderBy(desc(messages.timestamp))
|
||||
.limit(scanLimit);
|
||||
|
||||
const now = Date.now();
|
||||
const cutoff = now - withinMinutes * 60 * 1000;
|
||||
|
||||
const filtered = results
|
||||
.filter((r) => {
|
||||
if (options?.excludeMessageId && r.messages.id === options.excludeMessageId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const timestamp = r.messages.timestamp;
|
||||
if (!timestamp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const parsedTs = Date.parse(timestamp);
|
||||
if (Number.isNaN(parsedTs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return parsedTs >= cutoff;
|
||||
})
|
||||
.slice(0, maxMessages)
|
||||
.reverse();
|
||||
|
||||
return filtered.map((r) => ({
|
||||
message: r.messages,
|
||||
userName: r.users?.name ?? null,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { BotClient } from "../../core/client";
|
||||
import { config } from "../../core/config";
|
||||
import { createLogger } from "../../core/logger";
|
||||
import { getAiService, getVisionService, type MessageStyle, type ToolContext, type Attachment } from "../../services/ai";
|
||||
import { db } from "../../database";
|
||||
import { db, messageRepository } from "../../database";
|
||||
import { personalities, botOptions } from "../../database/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { buildStyledPrompt, STYLE_MODIFIERS } from "./personalities";
|
||||
@@ -20,6 +20,8 @@ const logger = createLogger("Features:Joel");
|
||||
const DIRECTED_CLASSIFICATION_LIMIT_PER_HOUR = 20;
|
||||
const DIRECTED_CLASSIFICATION_WINDOW_MS = 60 * 60 * 1000;
|
||||
const directedClassificationBudget = new Map<string, { windowStart: number; count: number }>();
|
||||
const CONVERSATION_CONTEXT_MAX_MESSAGES = 5;
|
||||
const CONVERSATION_CONTEXT_WINDOW_MINUTES = 20;
|
||||
|
||||
type ResponseTrigger = "free-will" | "summoned" | "classifier" | "none";
|
||||
|
||||
@@ -348,6 +350,12 @@ The image URL will appear in your response for the user to see.`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add recent conversation context so Joel can follow ongoing chat
|
||||
const conversationContext = await this.buildConversationContext(message);
|
||||
if (conversationContext) {
|
||||
prompt = `${conversationContext}\n\nCurrent message:\n${prompt}`;
|
||||
}
|
||||
|
||||
// Analyze attachments if present (images, etc.)
|
||||
const attachmentDescriptions = await this.analyzeAttachments(message);
|
||||
if (attachmentDescriptions) {
|
||||
@@ -364,6 +372,55 @@ The image URL will appear in your response for the user to see.`;
|
||||
return response.text || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Build a compact conversation context from recent messages in this channel.
|
||||
*/
|
||||
async buildConversationContext(message: Message<true>): Promise<string | null> {
|
||||
try {
|
||||
const recent = await messageRepository.findRecentContext(
|
||||
message.guildId,
|
||||
message.channelId,
|
||||
{
|
||||
maxMessages: CONVERSATION_CONTEXT_MAX_MESSAGES,
|
||||
withinMinutes: CONVERSATION_CONTEXT_WINDOW_MINUTES,
|
||||
excludeMessageId: message.id,
|
||||
}
|
||||
);
|
||||
|
||||
if (recent.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const contextLines = recent
|
||||
.map(({ message: recentMessage, userName }) => {
|
||||
const author = userName || recentMessage.user_id || "Unknown";
|
||||
const content = (recentMessage.content || "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
.slice(0, 220);
|
||||
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `- ${author}: ${content}`;
|
||||
})
|
||||
.filter((line): line is string => Boolean(line));
|
||||
|
||||
if (contextLines.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
`Recent channel context (last ${CONVERSATION_CONTEXT_MAX_MESSAGES} messages within ${CONVERSATION_CONTEXT_WINDOW_MINUTES} minutes):`,
|
||||
...contextLines,
|
||||
].join("\n");
|
||||
} catch (error) {
|
||||
logger.error("Failed to build conversation context", error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Extract and analyze attachments from a message using vision AI
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user