From 16d51a1d2940fe479586f7442162c52cbca1b940 Mon Sep 17 00:00:00 2001 From: Index Date: Sat, 2 Aug 2025 15:16:43 -0500 Subject: [PATCH 1/2] feat: basic parsing --- .env.example | 4 +- bun.lock | 6 +- package.json | 8 +-- src/handlers/posts.ts | 28 ++++++++- src/utils/memory.ts | 130 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 src/utils/memory.ts diff --git a/.env.example b/.env.example index 9edcee6..f07277f 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,9 @@ # Comma-separated list of users who can use the bot (delete var if you want everyone to be able to use it) AUTHORIZED_USERS="" -SERVICE="https://pds.indexx.dev" # PDS service URL (optional) + # PDS service URL (optional) +SERVICE="https://pds.indexx.dev" + DB_PATH="data/sqlite.db" GEMINI_MODEL="gemini-2.0-flash-lite" diff --git a/bun.lock b/bun.lock index d4b5196..6580c1d 100644 --- a/bun.lock +++ b/bun.lock @@ -107,7 +107,7 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.8", "", { "os": "win32", "cpu": "x64" }, "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="], - "@google/genai": ["@google/genai@1.10.0", "", { "dependencies": { "google-auth-library": "^9.14.2", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.11.0" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-PR4tLuiIFMrpAiiCko2Z16ydikFsPF1c5TBfI64hlZcv3xBEApSCceLuDYu1pNMq2SkNh4r66J4AG+ZexBnMLw=="], + "@google/genai": ["@google/genai@1.11.0", "", { "dependencies": { "google-auth-library": "^9.14.2", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.11.0" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-4XFAHCvU91ewdWOU3RUdSeXpDuZRJHNYLqT9LKw7WqPjRQcEJvVU+VOU49ocruaSp8VuLKMecl0iadlQK+Zgfw=="], "@skyware/bot": ["@skyware/bot@0.3.12", "", { "dependencies": { "@atcute/bluesky": "^1.0.7", "@atcute/bluesky-richtext-builder": "^1.0.1", "@atcute/client": "^2.0.3", "@atcute/ozone": "^1.0.5", "quick-lru": "^7.0.0", "rate-limit-threshold": "^0.1.5" }, "optionalDependencies": { "@skyware/firehose": "^0.3.2", "@skyware/jetstream": "^0.2.2" } }, "sha512-5OqTtwItYsBFMh0nwrxfsqgXrvRaJzg1P+ghMV4rlRGwHhdRgBJcnYQYgUqqREFcB247yGo73LNyqq7kHEwV7Q=="], @@ -145,7 +145,7 @@ "drizzle-kit": ["drizzle-kit@0.31.4", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-tCPWVZWZqWVx2XUsVpJRnH9Mx0ClVOf5YUHerZ5so1OKSlqww4zy1R5ksEdGRcO3tM3zj0PYN6V48TbQCL1RfA=="], - "drizzle-orm": ["drizzle-orm@0.44.3", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-8nIiYQxOpgUicEL04YFojJmvC4DNO4KoyXsEIqN44+g6gNBr6hmVpWk3uyAt4CaTiRGDwoU+alfqNNeonLAFOQ=="], + "drizzle-orm": ["drizzle-orm@0.44.4", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-ZyzKFpTC/Ut3fIqc2c0dPZ6nhchQXriTsqTNs4ayRgl6sZcFlMs9QZKPSHXK4bdOf41GHGWf+FrpcDDYwW+W6Q=="], "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], @@ -217,7 +217,7 @@ "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], - "zod": ["zod@4.0.5", "", {}, "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA=="], + "zod": ["zod@4.0.14", "", {}, "sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw=="], "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], diff --git a/package.json b/package.json index 5683d6d..fc47e96 100644 --- a/package.json +++ b/package.json @@ -14,17 +14,17 @@ "drizzle-kit": "^0.31.4" }, "peerDependencies": { - "typescript": "^5" + "typescript": "^5.8.3" }, "dependencies": { "@atproto/syntax": "^0.4.0", - "@google/genai": "^1.10.0", + "@google/genai": "^1.11.0", "@skyware/bot": "^0.3.12", "@types/js-yaml": "^4.0.9", "consola": "^3.4.2", - "drizzle-orm": "^0.44.3", + "drizzle-orm": "^0.44.4", "js-yaml": "^4.1.0", - "zod": "^4.0.5" + "zod": "^4.0.14" }, "repository": { "url": "https://github.com/indexxing/echo" diff --git a/src/handlers/posts.ts b/src/handlers/posts.ts index e48d7b3..933cba2 100644 --- a/src/handlers/posts.ts +++ b/src/handlers/posts.ts @@ -2,18 +2,19 @@ import { isAuthorizedUser, logInteraction } from "../utils/interactions"; import * as threadUtils from "../utils/thread"; import modelPrompt from "../model/prompt.txt"; import { GoogleGenAI } from "@google/genai"; -import { interactions } from "../db/schema"; import { type Post } from "@skyware/bot"; import * as c from "../constants"; import * as tools from "../tools"; import consola from "consola"; import { env } from "../env"; +import { MemoryHandler } from "../utils/memory"; +import * as yaml from "js-yaml"; const logger = consola.withTag("Post Handler"); type SupportedFunctionCall = typeof c.SUPPORTED_FUNCTION_CALLS[number]; -async function generateAIResponse(parsedThread: string) { +async function generateAIResponse(memory: string, parsedThread: string) { const genai = new GoogleGenAI({ apiKey: env.GEMINI_API_KEY, }); @@ -40,6 +41,9 @@ async function generateAIResponse(parsedThread: string) { env.ADMIN_HANDLE, ), }, + { + text: memory, + }, ], }, { @@ -138,7 +142,25 @@ export async function handler(post: Post): Promise { const parsedThread = threadUtils.parseThread(thread); logger.success("Generated thread context:", parsedThread); - const inference = await generateAIResponse(parsedThread); + const botMemory = new MemoryHandler( + env.DID, + await MemoryHandler.getBlocks(env.DID), + ); + const userMemory = new MemoryHandler( + post.author.did, + await MemoryHandler.getBlocks(post.author.did), + ); + + const memory = yaml.dump({ + users_with_memory_blocks: { + [env.HANDLE]: botMemory.parseBlocks(), + [post.author.handle]: userMemory.parseBlocks(), + }, + }); + + logger.log("Parsed memory blocks: ", memory); + + const inference = await generateAIResponse(memory, parsedThread); logger.success("Generated text:", inference.text); const responseText = inference.text; diff --git a/src/utils/memory.ts b/src/utils/memory.ts new file mode 100644 index 0000000..25b9214 --- /dev/null +++ b/src/utils/memory.ts @@ -0,0 +1,130 @@ +import { and, desc, eq } from "drizzle-orm"; +import db from "../db"; +import { memory_block_entries, memory_blocks } from "../db/schema"; +import * as yaml from "js-yaml"; + +type MemoryBlock = { + id: number; + name: string; + description: string; + mutable: boolean; + entries: Entry[]; +}; + +type Entry = { + id: number; + block_id: number; + label: string; + value: string; + added_by: string | null; + created_at: Date | null; +}; + +export class MemoryHandler { + did: string; + blocks: MemoryBlockHandler[]; + + constructor(did: string, blocks: MemoryBlockHandler[]) { + this.did = did; + this.blocks = blocks; + } + + static async getBlocks(did: string) { + const blocks = await db + .select({ + id: memory_blocks.id, + name: memory_blocks.name, + description: memory_blocks.description, + mutable: memory_blocks.mutable, + }) + .from(memory_blocks) + .where(eq(memory_blocks.did, did)); + + const hydratedBlocks = []; + + for (const block of blocks) { + const entries = await db + .select() + .from(memory_block_entries) + .where(eq(memory_block_entries.block_id, block.id)) + .orderBy(desc(memory_block_entries.id)) + .limit(15); + + hydratedBlocks.push({ + ...block, + entries, + }); + } + + if (hydratedBlocks.length == 0) { + const [newBlock] = await db + .insert(memory_blocks) + .values([ + { + did, + name: "memory", + description: "User memory", + mutable: false, + }, + ]) + .returning(); + + hydratedBlocks.push({ + ...newBlock, + entries: [], + }); + } + + return hydratedBlocks.map( + (block) => + new MemoryBlockHandler( + block as MemoryBlock, + ), + ); + } + + public parseBlocks() { + return this.blocks.map((handler) => ({ + name: handler.block.name, + description: handler.block.description, + entries: handler.block.entries.map((entry) => ({ + label: entry.label, + value: entry.value, + added_by: entry.added_by || "nobody", + })), + })); + } +} + +export class MemoryBlockHandler { + block: MemoryBlock; + + constructor(block: MemoryBlock) { + this.block = block; + } + + public async createEntry(label: string, value: string) { + const [entry] = await db + .insert(memory_block_entries) + .values([ + { + block_id: this.block.id, + label, + value, + }, + ]) + .returning(); + + if (!entry) { + return { + added_to_memory: false, + }; + } + + this.block.entries.push(entry); + + return { + added_to_memory: true, + }; + } +} From f6132057a215e85de017ba897249233ed90a7a66 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 2 Aug 2025 20:34:14 +0000 Subject: [PATCH 2/2] I've made some improvements to how I learn from our interactions. I've updated the way I log our conversations so I can better remember your posts, my responses, and whether a particular conversation is muted. To give you more relevant and personalized responses, I'll also review our last five interactions to get better context for your requests. I'll be sure to exclude messages from our current conversation to avoid repeating myself. --- bun.lock | 8 +- drizzle/0003_flowery_korvac.sql | 3 + drizzle/meta/0003_snapshot.json | 255 ++++++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + sqlite.db | Bin 0 -> 45056 bytes src/db/index.ts | 3 +- src/db/schema.ts | 3 + src/handlers/posts.ts | 26 +++- src/utils/interactions.ts | 43 +++++- 9 files changed, 333 insertions(+), 15 deletions(-) create mode 100644 drizzle/0003_flowery_korvac.sql create mode 100644 drizzle/meta/0003_snapshot.json create mode 100644 sqlite.db diff --git a/bun.lock b/bun.lock index 6580c1d..ae7e23e 100644 --- a/bun.lock +++ b/bun.lock @@ -5,20 +5,20 @@ "name": "bsky-echo", "dependencies": { "@atproto/syntax": "^0.4.0", - "@google/genai": "^1.10.0", + "@google/genai": "^1.11.0", "@skyware/bot": "^0.3.12", "@types/js-yaml": "^4.0.9", "consola": "^3.4.2", - "drizzle-orm": "^0.44.3", + "drizzle-orm": "^0.44.4", "js-yaml": "^4.1.0", - "zod": "^4.0.5", + "zod": "^4.0.14", }, "devDependencies": { "@types/bun": "^1.2.19", "drizzle-kit": "^0.31.4", }, "peerDependencies": { - "typescript": "^5", + "typescript": "^5.8.3", }, }, }, diff --git a/drizzle/0003_flowery_korvac.sql b/drizzle/0003_flowery_korvac.sql new file mode 100644 index 0000000..d9fe9ba --- /dev/null +++ b/drizzle/0003_flowery_korvac.sql @@ -0,0 +1,3 @@ +ALTER TABLE `interactions` ADD `post` text;--> statement-breakpoint +ALTER TABLE `interactions` ADD `response` text;--> statement-breakpoint +ALTER TABLE `interactions` ADD `muted` integer; \ No newline at end of file diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000..21cce7e --- /dev/null +++ b/drizzle/meta/0003_snapshot.json @@ -0,0 +1,255 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "30d38111-8e11-4d7d-99e8-cbafd962ca62", + "prevId": "11e8b31f-8e38-4013-8d50-bec6177b015a", + "tables": { + "interactions": { + "name": "interactions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "did": { + "name": "did", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "post": { + "name": "post", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "response": { + "name": "response", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "muted": { + "name": "muted", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "interactions_uri_unique": { + "name": "interactions_uri_unique", + "columns": [ + "uri" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "memory_block_entries": { + "name": "memory_block_entries", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "block_id": { + "name": "block_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "memory_block_entries_block_id_memory_blocks_id_fk": { + "name": "memory_block_entries_block_id_memory_blocks_id_fk", + "tableFrom": "memory_block_entries", + "tableTo": "memory_blocks", + "columnsFrom": [ + "block_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "memory_blocks": { + "name": "memory_blocks", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "did": { + "name": "did", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'memory'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'User memory'" + }, + "mutable": { + "name": "mutable", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "muted_threads": { + "name": "muted_threads", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rkey": { + "name": "rkey", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "muted_at": { + "name": "muted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "muted_threads_uri_unique": { + "name": "muted_threads_uri_unique", + "columns": [ + "uri" + ], + "isUnique": true + }, + "muted_threads_rkey_unique": { + "name": "muted_threads_rkey_unique", + "columns": [ + "rkey" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index e6a1257..bb4556e 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1753682242260, "tag": "0002_green_millenium_guard", "breakpoints": true + }, + { + "idx": 3, + "version": "6", + "when": 1754166687687, + "tag": "0003_flowery_korvac", + "breakpoints": true } ] } \ No newline at end of file diff --git a/sqlite.db b/sqlite.db new file mode 100644 index 0000000000000000000000000000000000000000..de6eb41d4964cee790fd1c5167a093fc11dfbec6 GIT binary patch literal 45056 zcmeI*PjB1i9S3kyk}X?_qwP?5Sq^)eAaK+=86}FMlwoMpM9Bm@c4AA|j2)zhBT1H}JA_D*l;N)n{54G%`GUxsCV9H>9e*>@ zt>3+|cJXs5mwhhf)A>)c&)1%>4KI9nv3~I%tKY2r?K@&o1OW&@00Izzz<)(xw;H5Xe|x(@y2+w0xshA#I#!p6+(QDTFr-z{Z{?qqueIx4*ZZ;jl)SdVBvuGp3O4)L*Ce|rNZfJx$OGJ zhWyltN8D$9KRD?au5Z2@&aGUSAM;$oPpRo z zyc5LK?L<9(Kv-cskEbiym)MgP&+0vQM2@%iy$`|QT{e*|e2~qq|8hef{mk(!c0AYX z4&Ov)=s8b@Y%YIgGMe^I^KrXDnmhLzzu}b0K_x=F!fw8l4>>-e6fWJ7ba8Z_PG)hk ziMjU1fXlh%_%!;_N_PD%-sfuz`YeXFsMD+8qtl7lZ|QXmS2$YAuK(h?Jh~EZKY4mh zj60dVyl~^m8i~O1rjvy;uSc2X^*7&?58~+-K5_Z2UngXI|9B>QX?1CC%Dq&0L!84l zd1N8~jr66sqMY81+e~gXx%>mZq7+62IX)GR#^=sX#|oVH-*;K3?;Hj+zBn$*OU(gY zT~y_&xLES!SY*Gk*Q{?RFP8ii!&8rjz4%%-J2#K>U!1LoH|(>(F-7&lgVgf+Z#7wp zC(dN2&|&1q>zze?=oA;_rulKOB94VM{>ohXla&7~Z|5)b1tbVS00Izz00bZa0SG_< z0uX?}4_sg?vm}kymJ~~4hE=t#TFqd3O}Dgah1s@R)rv)(8WvMc+b)%iic+aCrZU5< zGD}fZy~?z5(Jsr++z*caxRs6@(`}_{6l=O68ZDc8xmvcZibl0sxh6twqgXXcrlQw0 z-7HmTRaF(cL~BK}rrO1dAwS#wxluf%-u2y(+Owo!)Nz;m~rdX<}nY3ssys=L{&E@z$&E&t9^8d>JGyiwKfCK>u zKmY;|fB*y_009U<00Izz!1)%)q$N2c-qPYNl}<~m;`4w0rGF#{KmY;|fB*y_009U< z00Izz00jPr1;qV-D*qQL|22O@f&c^{009U<00Izz00bZa0SG|gya@axmDc3s50gxP znI-dMIlVDg5@y8r|MB_%dD*q-Cj=k>0SG_<0uX=z1Rwwb2teR$0{H&_+1wBX0SG_< z0uX=z1Rwwb2tWV=5I9c)`27Do?Je{b0uX=z1Rwwb2tWV=5P$##AaFJT-2b1=4N(w) z00bZa0SG_<0uX=z1Rwx`^CTd?|DRdo|Nr0b^A9c!`2rFIAOHafKmY;|fB*y_009V` ze}U0bW<`>(j2=sBX262b_h>t00SVZXAq%6BocKdR2Lt9=B$)nftQGJl2aJr+=x)LWu3@=f2J)!HP!$B4lk?~q*O6cu0e zxB7lKh&$tW#M#{^`#nF1%wZHR7*R8omE_l`q^2Sc+3GnV;r}QRYYC9PADj@A4yfU{ zPUNsfO}X-lwEFYWnxs;z?|4oa1vK&l-sR{((9h@gDBp}H3SV@+M?K4BVzYO6{w>F| z*zvI#{DO|h_y75yO8GDHQT|ta0SN*SfB*y_009U<00Izz00bcLe^XvCk2*rLri Nd?O^2BK`>C { return; } - await logInteraction(post); - - if (await threadUtils.isThreadMuted(post)) { + const isMuted = await threadUtils.isThreadMuted(post); + if (isMuted) { logger.warn("Thread is muted."); + await logInteraction(post, { + responseText: null, + wasMuted: true, + }); return; } @@ -151,11 +158,17 @@ export async function handler(post: Post): Promise { await MemoryHandler.getBlocks(post.author.did), ); + const recentInteractions = await getRecentInteractions( + post.author.did, + thread, + ); + const memory = yaml.dump({ users_with_memory_blocks: { [env.HANDLE]: botMemory.parseBlocks(), [post.author.handle]: userMemory.parseBlocks(), }, + recent_interactions: recentInteractions, }); logger.log("Parsed memory blocks: ", memory); @@ -167,6 +180,11 @@ export async function handler(post: Post): Promise { if (responseText) { await sendResponse(post, responseText); } + + await logInteraction(post, { + responseText: responseText ?? null, + wasMuted: false, + }); } catch (error) { logger.error("Error in post handler:", error); diff --git a/src/utils/interactions.ts b/src/utils/interactions.ts index 16d61bb..31a78b3 100644 --- a/src/utils/interactions.ts +++ b/src/utils/interactions.ts @@ -1,5 +1,6 @@ import { interactions } from "../db/schema"; import type { Post } from "@skyware/bot"; +import { desc, notInArray } from "drizzle-orm"; import { env } from "../env"; import db from "../db"; @@ -9,11 +10,41 @@ export function isAuthorizedUser(did: string) { : env.AUTHORIZED_USERS.includes(did as any); } -export async function logInteraction(post: Post): Promise { - await db.insert(interactions).values([{ - uri: post.uri, - did: post.author.did, - }]); +export async function logInteraction( + post: Post, + options: { + responseText: string | null; + wasMuted: boolean; + }, +): Promise { + await db.insert(interactions).values([ + { + uri: post.uri, + did: post.author.did, + post: post.text, + response: options.responseText, + muted: options.wasMuted, + }, + ]); - console.log(`Logged interaction, initiated by @${post.author.handle}`); + console.log(`Logged interaction, initiated by @${post.author.handle}`); +} + +export async function getRecentInteractions(did: string, thread: Post[]) { + const threadUris = thread.map((p) => p.uri); + + const recentInteractions = await db.query.interactions.findMany({ + where: (interactions, { eq, and, notInArray }) => + and( + eq(interactions.did, did), + notInArray(interactions.uri, threadUris), + ), + orderBy: (interactions, { desc }) => [desc(interactions.created_at)], + limit: 5, + }); + + return recentInteractions.map((i) => ({ + post: i.post, + response: i.response, + })); }