feat: cache post URIs & prevent recursion loop
This commit is contained in:
parent
9b3be5d52a
commit
e6372de095
3 changed files with 73 additions and 10 deletions
42
src/utils/cache.ts
Normal file
42
src/utils/cache.ts
Normal 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
|
||||||
|
|
@ -11,6 +11,7 @@ import { and, eq } from "drizzle-orm";
|
||||||
import { env } from "../env";
|
import { env } from "../env";
|
||||||
import { bot, ERROR_MESSAGE, MAX_GRAPHEMES } from "../core";
|
import { bot, ERROR_MESSAGE, MAX_GRAPHEMES } from "../core";
|
||||||
import { parsePost, parsePostImages, traverseThread } from "./post";
|
import { parsePost, parsePostImages, traverseThread } from "./post";
|
||||||
|
import { postCache } from "../utils/cache";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Utilities
|
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!);
|
const convoMessages = await getRelevantMessages(row!);
|
||||||
|
|
||||||
let parseResult = null;
|
let parseResult = null;
|
||||||
try {
|
try {
|
||||||
|
const parsedPost = await parsePost(post, true, new Set());
|
||||||
parseResult = {
|
parseResult = {
|
||||||
context: yaml.dump({
|
context: yaml.dump({
|
||||||
post: await parsePost(post, true),
|
post: parsedPost || null,
|
||||||
}),
|
}),
|
||||||
messages: convoMessages.map((message) => {
|
messages: convoMessages.map((message) => {
|
||||||
const role = message.did == env.DID ? "model" : "user";
|
const role = message.did == env.DID ? "model" : "user";
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,21 @@ import {
|
||||||
import * as c from "../core";
|
import * as c from "../core";
|
||||||
import * as yaml from "js-yaml";
|
import * as yaml from "js-yaml";
|
||||||
import type { ParsedPost } from "../types";
|
import type { ParsedPost } from "../types";
|
||||||
|
import { postCache } from "../utils/cache";
|
||||||
|
|
||||||
export async function parsePost(
|
export async function parsePost(
|
||||||
post: Post,
|
post: Post,
|
||||||
includeThread: boolean,
|
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([
|
const [images, quotePost, ancestorPosts] = await Promise.all([
|
||||||
parsePostImages(post),
|
parsePostImages(post),
|
||||||
parseQuote(post),
|
parseQuote(post, seenUris),
|
||||||
includeThread ? traverseThread(post) : Promise.resolve(null),
|
includeThread ? traverseThread(post) : Promise.resolve(null),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -28,23 +35,31 @@ export async function parsePost(
|
||||||
...(quotePost && { quotePost }),
|
...(quotePost && { quotePost }),
|
||||||
...(ancestorPosts && {
|
...(ancestorPosts && {
|
||||||
thread: {
|
thread: {
|
||||||
ancestors: await Promise.all(
|
ancestors: (await Promise.all(
|
||||||
ancestorPosts.map((ancestor) => parsePost(ancestor, false)),
|
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 (
|
if (
|
||||||
!post.embed || (!post.embed.isRecord() && !post.embed.isRecordWithMedia())
|
!post.embed || (!post.embed.isRecord() && !post.embed.isRecordWithMedia())
|
||||||
) return undefined;
|
) return undefined;
|
||||||
|
|
||||||
const record = (post.embed as RecordEmbed || RecordWithMediaEmbed).record;
|
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) {
|
export function parsePostImages(post: Post) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue