2026-05-21 12:03:31 +00:00
|
|
|
import { config } from "../config.js";
|
|
|
|
|
import { initializeDatabase } from "../database/drizzle.js";
|
|
|
|
|
import { buildConversationPromptMessages } from "./conversationContext.js";
|
|
|
|
|
import { runModerationAnalysis } from "./llmModerationClient.js";
|
2026-05-15 21:40:20 +07:00
|
|
|
import {
|
2026-05-17 23:56:04 +07:00
|
|
|
getAttachmentsForMessages,
|
2026-05-15 21:40:20 +07:00
|
|
|
getConversationContextBefore,
|
2026-05-25 22:14:05 +07:00
|
|
|
updateMessagesAIAnalysisBulk,
|
2026-05-21 12:03:31 +00:00
|
|
|
} from "./messageStore.js";
|
|
|
|
|
import type { MessageRecord } from "./types.js";
|
2026-05-15 21:40:20 +07:00
|
|
|
|
2026-05-16 21:08:39 +07:00
|
|
|
let dbInitialized = false;
|
2026-05-25 22:14:05 +07:00
|
|
|
let dbInitPromise: Promise<any> | null = null;
|
2026-05-16 21:08:39 +07:00
|
|
|
|
2026-05-25 22:14:05 +07:00
|
|
|
async function ensureDb() {
|
|
|
|
|
if (dbInitialized) return;
|
|
|
|
|
if (!dbInitPromise) {
|
|
|
|
|
dbInitPromise = initializeDatabase().then(() => {
|
|
|
|
|
dbInitialized = true;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
await dbInitPromise;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface AnalysisWorkerRequest {
|
2026-05-15 21:40:20 +07:00
|
|
|
conversationKey: string;
|
|
|
|
|
messages: MessageRecord[];
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-25 22:14:05 +07:00
|
|
|
export type AnalysisWorkerResponse =
|
2026-05-15 21:40:20 +07:00
|
|
|
| {
|
|
|
|
|
ok: true;
|
|
|
|
|
conversationKey: string;
|
|
|
|
|
rows: MessageRecord[];
|
|
|
|
|
}
|
|
|
|
|
| {
|
|
|
|
|
ok: false;
|
|
|
|
|
conversationKey: string;
|
|
|
|
|
rows: MessageRecord[];
|
|
|
|
|
error: string;
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-25 22:14:05 +07:00
|
|
|
export default async function processAnalysisRequest({
|
2026-05-15 21:40:20 +07:00
|
|
|
conversationKey,
|
|
|
|
|
messages,
|
|
|
|
|
}: AnalysisWorkerRequest): Promise<AnalysisWorkerResponse> {
|
|
|
|
|
try {
|
2026-05-16 23:34:07 +07:00
|
|
|
try {
|
2026-05-25 22:14:05 +07:00
|
|
|
await ensureDb();
|
2026-05-16 23:34:07 +07:00
|
|
|
} catch (dbError) {
|
|
|
|
|
const msg = dbError instanceof Error ? dbError.message : String(dbError);
|
2026-05-17 01:01:40 +07:00
|
|
|
return {
|
|
|
|
|
ok: false,
|
|
|
|
|
conversationKey,
|
|
|
|
|
rows: [],
|
|
|
|
|
error: `Database init failed: ${msg}`,
|
|
|
|
|
};
|
2026-05-16 21:08:39 +07:00
|
|
|
}
|
2026-05-16 23:34:07 +07:00
|
|
|
|
2026-05-15 21:40:20 +07:00
|
|
|
const firstMessage = messages[0];
|
|
|
|
|
if (!firstMessage) return { ok: true, conversationKey, rows: [] };
|
|
|
|
|
|
|
|
|
|
const contextBefore = await getConversationContextBefore({
|
|
|
|
|
channelId: firstMessage.channel_id,
|
|
|
|
|
threadId: firstMessage.thread_id,
|
|
|
|
|
beforeCreatedAt: firstMessage.created_at,
|
2026-05-21 01:55:50 +07:00
|
|
|
limit: config.AI_ANALYSIS_CONTEXT_MESSAGE_LIMIT,
|
2026-05-15 21:40:20 +07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const promptMessages = buildConversationPromptMessages({
|
|
|
|
|
contextBefore,
|
|
|
|
|
targets: messages,
|
2026-05-21 01:55:50 +07:00
|
|
|
maxTokens: config.AI_ANALYSIS_MAX_CONTEXT_TOKENS,
|
2026-05-15 21:40:20 +07:00
|
|
|
});
|
|
|
|
|
|
2026-05-17 23:56:04 +07:00
|
|
|
const targetIds = messages.map((m) => m.id);
|
|
|
|
|
const contextIds = contextBefore.map((m) => m.id);
|
|
|
|
|
const allMessageIds = [...targetIds, ...contextIds];
|
|
|
|
|
const attachments = await getAttachmentsForMessages(allMessageIds);
|
|
|
|
|
|
2026-05-15 21:40:20 +07:00
|
|
|
const result = await runModerationAnalysis({
|
|
|
|
|
targets: messages,
|
|
|
|
|
contextText: promptMessages.join("\n"),
|
2026-05-17 23:56:04 +07:00
|
|
|
attachments,
|
2026-05-15 21:40:20 +07:00
|
|
|
});
|
|
|
|
|
|
2026-05-25 22:14:05 +07:00
|
|
|
const updates = result.results.map((analysisResult) => ({
|
|
|
|
|
messageId: analysisResult.messageId,
|
|
|
|
|
result: {
|
2026-05-15 21:40:20 +07:00
|
|
|
status: analysisResult.status,
|
|
|
|
|
flags: JSON.stringify(analysisResult.flags),
|
|
|
|
|
score: analysisResult.score,
|
|
|
|
|
raw: JSON.stringify(result.raw),
|
|
|
|
|
analysis: analysisResult.analysis,
|
|
|
|
|
analyzedAt: Date.now(),
|
|
|
|
|
error: null,
|
2026-05-25 22:14:05 +07:00
|
|
|
}
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const rows = await updateMessagesAIAnalysisBulk(updates);
|
2026-05-15 21:40:20 +07:00
|
|
|
|
|
|
|
|
return { ok: true, conversationKey, rows };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
2026-05-18 23:48:02 +07:00
|
|
|
const errorStack = error instanceof Error ? error.stack : undefined;
|
2026-05-15 21:40:20 +07:00
|
|
|
const rows: MessageRecord[] = [];
|
|
|
|
|
|
2026-05-18 23:48:02 +07:00
|
|
|
console.error(
|
|
|
|
|
JSON.stringify({
|
|
|
|
|
level: "ERROR",
|
|
|
|
|
context: "aiAnalysisWorker",
|
|
|
|
|
conversationKey,
|
|
|
|
|
messageCount: messages.length,
|
|
|
|
|
error: errorMessage,
|
|
|
|
|
stack: errorStack,
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
2026-05-15 21:40:20 +07:00
|
|
|
return { ok: false, conversationKey, rows, error: errorMessage };
|
|
|
|
|
}
|
|
|
|
|
}
|