feat: cache post URIs & prevent recursion loop

This commit is contained in:
Index 2025-12-22 16:33:26 -06:00
parent 9b3be5d52a
commit e6372de095
3 changed files with 73 additions and 10 deletions

42
src/utils/cache.ts Normal file
View file

@ -0,0 +1,42 @@
interface CacheEntry<T> {
value: T;
expiry: number;
}
class TimedCache<T> {
private cache = new Map<string, CacheEntry<T>>();
private ttl: number; // Time to live in milliseconds
constructor(ttl: number) {
this.ttl = ttl;
}
get(key: string): T | undefined {
const entry = this.cache.get(key);
if (!entry) {
return undefined;
}
if (Date.now() > entry.expiry) {
this.cache.delete(key); // Entry expired
return undefined;
}
return entry.value;
}
set(key: string, value: T): void {
const expiry = Date.now() + this.ttl;
this.cache.set(key, { value, expiry });
}
delete(key: string): void {
this.cache.delete(key);
}
clear(): void {
this.cache.clear();
}
}
export const postCache = new TimedCache<any>(2 * 60 * 1000); // 2 minutes cache

View file

@ -11,6 +11,7 @@ import { and, eq } from "drizzle-orm";
import { env } from "../env";
import { bot, ERROR_MESSAGE, MAX_GRAPHEMES } from "../core";
import { parsePost, parsePostImages, traverseThread } from "./post";
import { postCache } from "../utils/cache";
/*
Utilities
@ -123,14 +124,19 @@ export async function parseConversation(
});
}
const post = await bot.getPost(row.postUri);
let post = postCache.get(row.postUri);
if (!post) {
post = await bot.getPost(row.postUri);
postCache.set(row.postUri, post);
}
const convoMessages = await getRelevantMessages(row!);
let parseResult = null;
try {
const parsedPost = await parsePost(post, true, new Set());
parseResult = {
context: yaml.dump({
post: await parsePost(post, true),
post: parsedPost || null,
}),
messages: convoMessages.map((message) => {
const role = message.did == env.DID ? "model" : "user";

View file

@ -8,14 +8,21 @@ import {
import * as c from "../core";
import * as yaml from "js-yaml";
import type { ParsedPost } from "../types";
import { postCache } from "../utils/cache";
export async function parsePost(
post: Post,
includeThread: boolean,
): Promise<ParsedPost> {
seenUris: Set<string> = new Set(),
): Promise<ParsedPost | undefined> {
if (seenUris.has(post.uri)) {
return undefined;
}
seenUris.add(post.uri);
const [images, quotePost, ancestorPosts] = await Promise.all([
parsePostImages(post),
parseQuote(post),
parseQuote(post, seenUris),
includeThread ? traverseThread(post) : Promise.resolve(null),
]);
@ -28,23 +35,31 @@ export async function parsePost(
...(quotePost && { quotePost }),
...(ancestorPosts && {
thread: {
ancestors: await Promise.all(
ancestorPosts.map((ancestor) => parsePost(ancestor, false)),
),
ancestors: (await Promise.all(
ancestorPosts.map((ancestor) => parsePost(ancestor, false, seenUris)),
)).filter((post): post is ParsedPost => post !== undefined),
},
}),
};
}
async function parseQuote(post: Post) {
async function parseQuote(post: Post, seenUris: Set<string>) {
if (
!post.embed || (!post.embed.isRecord() && !post.embed.isRecordWithMedia())
) return undefined;
const record = (post.embed as RecordEmbed || RecordWithMediaEmbed).record;
const embedPost = await c.bot.getPost(record.uri);
if (seenUris.has(record.uri)) {
return undefined;
}
return await parsePost(embedPost, false);
let embedPost = postCache.get(record.uri);
if (!embedPost) {
embedPost = await c.bot.getPost(record.uri);
postCache.set(record.uri, embedPost);
}
return await parsePost(embedPost, false, seenUris);
}
export function parsePostImages(post: Post) {