+42
src/utils/cache.ts
+42
src/utils/cache.ts
···
1
+
interface CacheEntry<T> {
2
+
value: T;
3
+
expiry: number;
4
+
}
5
+
6
+
class TimedCache<T> {
7
+
private cache = new Map<string, CacheEntry<T>>();
8
+
private ttl: number; // Time to live in milliseconds
9
+
10
+
constructor(ttl: number) {
11
+
this.ttl = ttl;
12
+
}
13
+
14
+
get(key: string): T | undefined {
15
+
const entry = this.cache.get(key);
16
+
if (!entry) {
17
+
return undefined;
18
+
}
19
+
20
+
if (Date.now() > entry.expiry) {
21
+
this.cache.delete(key); // Entry expired
22
+
return undefined;
23
+
}
24
+
25
+
return entry.value;
26
+
}
27
+
28
+
set(key: string, value: T): void {
29
+
const expiry = Date.now() + this.ttl;
30
+
this.cache.set(key, { value, expiry });
31
+
}
32
+
33
+
delete(key: string): void {
34
+
this.cache.delete(key);
35
+
}
36
+
37
+
clear(): void {
38
+
this.cache.clear();
39
+
}
40
+
}
41
+
42
+
export const postCache = new TimedCache<any>(2 * 60 * 1000); // 2 minutes cache
+8
-2
src/utils/conversation.ts
+8
-2
src/utils/conversation.ts
···
11
11
import { env } from "../env";
12
12
import { bot, ERROR_MESSAGE, MAX_GRAPHEMES } from "../core";
13
13
import { parsePost, parsePostImages, traverseThread } from "./post";
14
+
import { postCache } from "../utils/cache";
14
15
15
16
/*
16
17
Utilities
···
123
124
});
124
125
}
125
126
126
-
const post = await bot.getPost(row.postUri);
127
+
let post = postCache.get(row.postUri);
128
+
if (!post) {
129
+
post = await bot.getPost(row.postUri);
130
+
postCache.set(row.postUri, post);
131
+
}
127
132
const convoMessages = await getRelevantMessages(row!);
128
133
129
134
let parseResult = null;
130
135
try {
136
+
const parsedPost = await parsePost(post, true, new Set());
131
137
parseResult = {
132
138
context: yaml.dump({
133
-
post: await parsePost(post, true),
139
+
post: parsedPost || null,
134
140
}),
135
141
messages: convoMessages.map((message) => {
136
142
const role = message.did == env.DID ? "model" : "user";
+23
-8
src/utils/post.ts
+23
-8
src/utils/post.ts
···
8
8
import * as c from "../core";
9
9
import * as yaml from "js-yaml";
10
10
import type { ParsedPost } from "../types";
11
+
import { postCache } from "../utils/cache";
11
12
12
13
export async function parsePost(
13
14
post: Post,
14
15
includeThread: boolean,
15
-
): Promise<ParsedPost> {
16
+
seenUris: Set<string> = new Set(),
17
+
): Promise<ParsedPost | undefined> {
18
+
if (seenUris.has(post.uri)) {
19
+
return undefined;
20
+
}
21
+
seenUris.add(post.uri);
22
+
16
23
const [images, quotePost, ancestorPosts] = await Promise.all([
17
24
parsePostImages(post),
18
-
parseQuote(post),
25
+
parseQuote(post, seenUris),
19
26
includeThread ? traverseThread(post) : Promise.resolve(null),
20
27
]);
21
28
···
28
35
...(quotePost && { quotePost }),
29
36
...(ancestorPosts && {
30
37
thread: {
31
-
ancestors: await Promise.all(
32
-
ancestorPosts.map((ancestor) => parsePost(ancestor, false)),
33
-
),
38
+
ancestors: (await Promise.all(
39
+
ancestorPosts.map((ancestor) => parsePost(ancestor, false, seenUris)),
40
+
)).filter((post): post is ParsedPost => post !== undefined),
34
41
},
35
42
}),
36
43
};
37
44
}
38
45
39
-
async function parseQuote(post: Post) {
46
+
async function parseQuote(post: Post, seenUris: Set<string>) {
40
47
if (
41
48
!post.embed || (!post.embed.isRecord() && !post.embed.isRecordWithMedia())
42
49
) return undefined;
43
50
44
51
const record = (post.embed as RecordEmbed || RecordWithMediaEmbed).record;
45
-
const embedPost = await c.bot.getPost(record.uri);
52
+
if (seenUris.has(record.uri)) {
53
+
return undefined;
54
+
}
55
+
56
+
let embedPost = postCache.get(record.uri);
57
+
if (!embedPost) {
58
+
embedPost = await c.bot.getPost(record.uri);
59
+
postCache.set(record.uri, embedPost);
60
+
}
46
61
47
-
return await parsePost(embedPost, false);
62
+
return await parsePost(embedPost, false, seenUris);
48
63
}
49
64
50
65
export function parsePostImages(post: Post) {