From 558d65342b992e6a33bdc938b0f02da9b8de9ddf Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Thu, 14 May 2026 20:14:42 +0700 Subject: [PATCH] feat: add moderation query indexes --- drizzle/migrations/0001_curious_zodiak.sql | 22 + drizzle/migrations/meta/0001_snapshot.json | 799 +++++++++++++++++++++ drizzle/migrations/meta/_journal.json | 7 + src/database/schema.ts | 133 ++++ 4 files changed, 961 insertions(+) create mode 100644 drizzle/migrations/0001_curious_zodiak.sql create mode 100644 drizzle/migrations/meta/0001_snapshot.json diff --git a/drizzle/migrations/0001_curious_zodiak.sql b/drizzle/migrations/0001_curious_zodiak.sql new file mode 100644 index 0000000..92326bf --- /dev/null +++ b/drizzle/migrations/0001_curious_zodiak.sql @@ -0,0 +1,22 @@ +CREATE TABLE "ai_analysis_runs" ( + "id" text PRIMARY KEY NOT NULL, + "conversation_key" text NOT NULL, + "target_message_ids" text NOT NULL, + "model" text NOT NULL, + "request_tokens_estimate" integer, + "response_raw" text, + "status" text DEFAULT 'pending' NOT NULL, + "error" text, + "created_at" bigint NOT NULL, + "completed_at" bigint +); +--> statement-breakpoint +CREATE INDEX "idx_ai_analysis_runs_conversation_key" ON "ai_analysis_runs" USING btree ("conversation_key");--> statement-breakpoint +CREATE INDEX "idx_ai_analysis_runs_status" ON "ai_analysis_runs" USING btree ("status");--> statement-breakpoint +CREATE INDEX "idx_ai_analysis_runs_created_at" ON "ai_analysis_runs" USING btree ("created_at");--> statement-breakpoint +CREATE INDEX "idx_attachments_channel_created" ON "attachments" USING btree ("channel_id","created_at","id");--> statement-breakpoint +CREATE INDEX "idx_attachments_thread_created" ON "attachments" USING btree ("thread_id","created_at","id");--> statement-breakpoint +CREATE INDEX "idx_messages_channel_created" ON "messages" USING btree ("channel_id","created_at","id");--> statement-breakpoint +CREATE INDEX "idx_messages_thread_created" ON "messages" USING btree ("thread_id","created_at","id");--> statement-breakpoint +CREATE INDEX "idx_messages_ai_status_created" ON "messages" USING btree ("ai_status","created_at","id");--> statement-breakpoint +CREATE INDEX "idx_messages_guild_ai_status_created" ON "messages" USING btree ("guild_id","ai_status","created_at","id"); \ No newline at end of file diff --git a/drizzle/migrations/meta/0001_snapshot.json b/drizzle/migrations/meta/0001_snapshot.json new file mode 100644 index 0000000..cbc6990 --- /dev/null +++ b/drizzle/migrations/meta/0001_snapshot.json @@ -0,0 +1,799 @@ +{ + "id": "79348c3a-b314-4bfd-88f3-7ddfbea4427e", + "prevId": "2b9e2347-dd99-4bf8-bbcb-f407af29ca83", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ai_analysis_runs": { + "name": "ai_analysis_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "conversation_key": { + "name": "conversation_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_message_ids": { + "name": "target_message_ids", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_tokens_estimate": { + "name": "request_tokens_estimate", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "response_raw": { + "name": "response_raw", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_ai_analysis_runs_conversation_key": { + "name": "idx_ai_analysis_runs_conversation_key", + "columns": [ + { + "expression": "conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ai_analysis_runs_status": { + "name": "idx_ai_analysis_runs_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ai_analysis_runs_created_at": { + "name": "idx_ai_analysis_runs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.attachments": { + "name": "attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "guild_id": { + "name": "guild_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "discord_url": { + "name": "discord_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_url": { + "name": "uploaded_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "upload_status": { + "name": "upload_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "upload_error": { + "name": "upload_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_attachments_channel": { + "name": "idx_attachments_channel", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_attachments_message": { + "name": "idx_attachments_message", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_attachments_status": { + "name": "idx_attachments_status", + "columns": [ + { + "expression": "upload_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_attachments_channel_created": { + "name": "idx_attachments_channel_created", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_attachments_thread_created": { + "name": "idx_attachments_thread_created", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "fk_attachments_message_id": { + "name": "fk_attachments_message_id", + "tableFrom": "attachments", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "guild_id": { + "name": "guild_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "edited_content": { + "name": "edited_content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "edited_at": { + "name": "edited_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ai_status": { + "name": "ai_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "ai_moderation_flags": { + "name": "ai_moderation_flags", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ai_moderation_score": { + "name": "ai_moderation_score", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "ai_moderation_raw": { + "name": "ai_moderation_raw", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ai_analysis": { + "name": "ai_analysis", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ai_analyzed_at": { + "name": "ai_analyzed_at", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ai_error": { + "name": "ai_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_messages_channel": { + "name": "idx_messages_channel", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_messages_user": { + "name": "idx_messages_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_messages_created": { + "name": "idx_messages_created", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_messages_thread": { + "name": "idx_messages_thread", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_messages_channel_created": { + "name": "idx_messages_channel_created", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_messages_thread_created": { + "name": "idx_messages_thread_created", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_messages_ai_status_created": { + "name": "idx_messages_ai_status_created", + "columns": [ + { + "expression": "ai_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_messages_guild_ai_status_created": { + "name": "idx_messages_guild_ai_status_created", + "columns": [ + { + "expression": "guild_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ai_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.muxer_jobs": { + "name": "muxer_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "maxAttempts": { + "name": "maxAttempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "createdAt": { + "name": "createdAt", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "updatedAt": { + "name": "updatedAt", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_muxer_jobs_status": { + "name": "idx_muxer_jobs_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_muxer_jobs_createdAt": { + "name": "idx_muxer_jobs_createdAt", + "columns": [ + { + "expression": "createdAt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ui_state": { + "name": "ui_state", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/migrations/meta/_journal.json b/drizzle/migrations/meta/_journal.json index 401d196..7fd6f77 100644 --- a/drizzle/migrations/meta/_journal.json +++ b/drizzle/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1778750697764, "tag": "0000_rare_kitty_pryde", "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1778764447718, + "tag": "0001_curious_zodiak", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/database/schema.ts b/src/database/schema.ts index ace2b52..2c86461 100644 --- a/src/database/schema.ts +++ b/src/database/schema.ts @@ -85,6 +85,27 @@ export const pgMessagesTable = pgTable( userIdx: pgIndex("idx_messages_user").on(table.user_id), createdIdx: pgIndex("idx_messages_created").on(table.created_at), threadIdx: pgIndex("idx_messages_thread").on(table.thread_id), + channelCreatedIdx: pgIndex("idx_messages_channel_created").on( + table.channel_id, + table.created_at, + table.id, + ), + threadCreatedIdx: pgIndex("idx_messages_thread_created").on( + table.thread_id, + table.created_at, + table.id, + ), + aiStatusCreatedIdx: pgIndex("idx_messages_ai_status_created").on( + table.ai_status, + table.created_at, + table.id, + ), + guildAiStatusCreatedIdx: pgIndex("idx_messages_guild_ai_status_created").on( + table.guild_id, + table.ai_status, + table.created_at, + table.id, + ), }), ); @@ -119,6 +140,16 @@ export const pgAttachmentsTable = pgTable( channelIdx: pgIndex("idx_attachments_channel").on(table.channel_id), messageIdx: pgIndex("idx_attachments_message").on(table.message_id), statusIdx: pgIndex("idx_attachments_status").on(table.upload_status), + channelCreatedIdx: pgIndex("idx_attachments_channel_created").on( + table.channel_id, + table.created_at, + table.id, + ), + threadCreatedIdx: pgIndex("idx_attachments_thread_created").on( + table.thread_id, + table.created_at, + table.id, + ), messageFk: pgForeignKey({ columns: [table.message_id], foreignColumns: [pgMessagesTable.id], @@ -137,6 +168,39 @@ export const pgUIStateTable = pgTable("ui_state", { updated_at: pgBigint("updated_at", { mode: "number" }).notNull(), }); +/** + * AI Analysis Runs Table (PostgreSQL) + * Tracks AI analysis batch runs for conversation-level moderation + */ +export const pgAIAnalysisRunsTable = pgTable( + "ai_analysis_runs", + { + id: pgText("id").primaryKey(), + conversation_key: pgText("conversation_key").notNull(), + target_message_ids: pgText("target_message_ids").notNull(), // JSON array + model: pgText("model").notNull(), + request_tokens_estimate: pgInteger("request_tokens_estimate"), + response_raw: pgText("response_raw"), + status: pgText("status", { + enum: ["pending", "processing", "completed", "failed"], + }) + .notNull() + .default("pending"), + error: pgText("error"), + created_at: pgBigint("created_at", { mode: "number" }).notNull(), + completed_at: pgBigint("completed_at", { mode: "number" }), + }, + (table) => ({ + conversationKeyIdx: pgIndex("idx_ai_analysis_runs_conversation_key").on( + table.conversation_key, + ), + statusIdx: pgIndex("idx_ai_analysis_runs_status").on(table.status), + createdAtIdx: pgIndex("idx_ai_analysis_runs_created_at").on( + table.created_at, + ), + }), +); + // SQLite Schema // ============= @@ -206,6 +270,24 @@ export const sqliteMessagesTable = sqliteTable( userIdx: sqliteIndex("idx_messages_user").on(table.user_id), createdIdx: sqliteIndex("idx_messages_created").on(table.created_at), threadIdx: sqliteIndex("idx_messages_thread").on(table.thread_id), + channelCreatedIdx: sqliteIndex("idx_messages_channel_created").on( + table.channel_id, + table.created_at, + table.id, + ), + threadCreatedIdx: sqliteIndex("idx_messages_thread_created").on( + table.thread_id, + table.created_at, + table.id, + ), + aiStatusCreatedIdx: sqliteIndex("idx_messages_ai_status_created").on( + table.ai_status, + table.created_at, + table.id, + ), + guildAiStatusCreatedIdx: sqliteIndex( + "idx_messages_guild_ai_status_created", + ).on(table.guild_id, table.ai_status, table.created_at, table.id), }), ); @@ -240,6 +322,16 @@ export const sqliteAttachmentsTable = sqliteTable( channelIdx: sqliteIndex("idx_attachments_channel").on(table.channel_id), messageIdx: sqliteIndex("idx_attachments_message").on(table.message_id), statusIdx: sqliteIndex("idx_attachments_status").on(table.upload_status), + channelCreatedIdx: sqliteIndex("idx_attachments_channel_created").on( + table.channel_id, + table.created_at, + table.id, + ), + threadCreatedIdx: sqliteIndex("idx_attachments_thread_created").on( + table.thread_id, + table.created_at, + table.id, + ), }), ); @@ -253,6 +345,39 @@ export const sqliteUIStateTable = sqliteTable("ui_state", { updated_at: sqliteInteger("updated_at").notNull(), }); +/** + * AI Analysis Runs Table (SQLite) + * Tracks AI analysis batch runs for conversation-level moderation + */ +export const sqliteAIAnalysisRunsTable = sqliteTable( + "ai_analysis_runs", + { + id: sqliteText("id").primaryKey(), + conversation_key: sqliteText("conversation_key").notNull(), + target_message_ids: sqliteText("target_message_ids").notNull(), // JSON array + model: sqliteText("model").notNull(), + request_tokens_estimate: sqliteInteger("request_tokens_estimate"), + response_raw: sqliteText("response_raw"), + status: sqliteText("status", { + enum: ["pending", "processing", "completed", "failed"], + }) + .notNull() + .default("pending"), + error: sqliteText("error"), + created_at: sqliteInteger("created_at").notNull(), + completed_at: sqliteInteger("completed_at"), + }, + (table) => ({ + conversationKeyIdx: sqliteIndex("idx_ai_analysis_runs_conversation_key").on( + table.conversation_key, + ), + statusIdx: sqliteIndex("idx_ai_analysis_runs_status").on(table.status), + createdAtIdx: sqliteIndex("idx_ai_analysis_runs_created_at").on( + table.created_at, + ), + }), +); + // Runtime table selection based on config // ======================================== @@ -270,6 +395,11 @@ export const attachmentsTable = export const uiStateTable = config.DATABASE_TYPE === "postgres" ? pgUIStateTable : sqliteUIStateTable; +export const aiAnalysisRunsTable = + config.DATABASE_TYPE === "postgres" + ? pgAIAnalysisRunsTable + : sqliteAIAnalysisRunsTable; + // Export table types for use in queries export type MuxerJob = typeof muxerJobsTable.$inferSelect; export type MuxerJobInsert = typeof muxerJobsTable.$inferInsert; @@ -282,3 +412,6 @@ export type AttachmentInsert = typeof attachmentsTable.$inferInsert; export type UIState = typeof uiStateTable.$inferSelect; export type UIStateInsert = typeof uiStateTable.$inferInsert; + +export type AIAnalysisRun = typeof aiAnalysisRunsTable.$inferSelect; +export type AIAnalysisRunInsert = typeof aiAnalysisRunsTable.$inferInsert;