feat: multimodal video detection + reply/forward/crosspost + batch optimization

- Video frame extraction via ffmpeg (4 key frames per video → vision LLM)
- Video display in FE MessageCard with HTML5 <video> player
- Reply/forward/crosspost indicator in FE + pipeline in DG/BE
- Fix: missing sanitizeAiContent + escapeXml in media path (prompt injection)
- Optimize: text-only batch results saved to DB immediately, no longer wait for media analysis
- BE mapper/schema/repo: add reference fields (is_reply, is_forward, etc.)
This commit is contained in:
MythEclipse
2026-06-22 09:46:06 +07:00
parent dc62b283c0
commit f502918b27
7 changed files with 261 additions and 48 deletions
@@ -134,6 +134,12 @@ export class MessagesRepository {
type: data.type ?? "text",
metadata: null,
ai_status: "pending",
is_reply: data.isReply ?? false,
is_forward: data.isForward ?? false,
is_crosspost: data.isCrosspost ?? false,
reference_message_id: data.referenceMessageId ?? null,
reference_channel_id: data.referenceChannelId ?? null,
reference_guild_id: data.referenceGuildId ?? null,
})
.returning();
@@ -260,6 +266,12 @@ export class MessagesRepository {
ai_severity: pgMessagesTable.ai_severity,
ai_confidence: pgMessagesTable.ai_confidence,
ai_analysis: pgMessagesTable.ai_analysis,
is_reply: pgMessagesTable.is_reply,
is_forward: pgMessagesTable.is_forward,
is_crosspost: pgMessagesTable.is_crosspost,
reference_message_id: pgMessagesTable.reference_message_id,
reference_channel_id: pgMessagesTable.reference_channel_id,
reference_guild_id: pgMessagesTable.reference_guild_id,
})
.from(pgMessagesTable)
.where(and(...conditions))
@@ -19,6 +19,12 @@ export const messageCreateSchema = z.object({
avatarUrl: z.string().optional(),
content: z.string(),
type: z.enum(["text", "edited", "deleted"]).default("text"),
isReply: z.boolean().optional(),
isForward: z.boolean().optional(),
isCrosspost: z.boolean().optional(),
referenceMessageId: z.string().optional(),
referenceChannelId: z.string().optional(),
referenceGuildId: z.string().optional(),
});
export const messageUpdateSchema = z.object({
@@ -25,6 +25,12 @@ export interface MappedMessage {
ai_recommended_action: string | null;
ai_analyzed_at: number | null;
ai_error: string | null;
is_reply: boolean | null;
is_forward: boolean | null;
is_crosspost: boolean | null;
reference_message_id: string | null;
reference_channel_id: string | null;
reference_guild_id: string | null;
}
export function mapMessageRow(row: Record<string, unknown>): MappedMessage {
@@ -53,5 +59,11 @@ export function mapMessageRow(row: Record<string, unknown>): MappedMessage {
ai_recommended_action: (row.ai_recommended_action as string | null) ?? null,
ai_analyzed_at: (row.ai_analyzed_at as number | null) ?? null,
ai_error: (row.ai_error as string | null) ?? null,
is_reply: row.is_reply === null ? null : Boolean(row.is_reply),
is_forward: row.is_forward === null ? null : Boolean(row.is_forward),
is_crosspost: row.is_crosspost === null ? null : Boolean(row.is_crosspost),
reference_message_id: (row.reference_message_id as string | null) ?? null,
reference_channel_id: (row.reference_channel_id as string | null) ?? null,
reference_guild_id: (row.reference_guild_id as string | null) ?? null,
};
}