diff --git a/apps/web/utils/ai/assistant/chat.ts b/apps/web/utils/ai/assistant/chat.ts index e0ba397b41..4507e8ff4a 100644 --- a/apps/web/utils/ai/assistant/chat.ts +++ b/apps/web/utils/ai/assistant/chat.ts @@ -826,11 +826,19 @@ Examples: } if (patternsToSave.length > 0) { - await saveLearnedPatterns({ + const result = await saveLearnedPatterns({ emailAccountId, ruleName: rule.name, patterns: patternsToSave, }); + + if ("error" in result) { + return { + success: false, + ruleId: rule.id, + error: result.error, + }; + } } return { success: true, ruleId: rule.id }; diff --git a/apps/web/utils/rule/learned-patterns.ts b/apps/web/utils/rule/learned-patterns.ts index 8aeec9eeae..cbb78a7718 100644 --- a/apps/web/utils/rule/learned-patterns.ts +++ b/apps/web/utils/rule/learned-patterns.ts @@ -1,6 +1,7 @@ import prisma from "@/utils/prisma"; import { createScopedLogger } from "@/utils/logger"; import { GroupItemType } from "@prisma/client"; +import { isDuplicateError } from "@/utils/prisma-helpers"; const logger = createScopedLogger("rule/learned-patterns"); @@ -94,45 +95,81 @@ export async function saveLearnedPatterns({ if (!rule) { logger.error("Rule not found", { emailAccountId, ruleName }); - return; + return { error: "Rule not found" }; } let groupId = rule.groupId; if (!groupId) { - // Create a new group for this rule if one doesn't exist - const newGroup = await prisma.group.create({ - data: { - emailAccountId, - name: ruleName, - rule: { connect: { id: rule.id } }, - }, - }); + try { + const newGroup = await prisma.group.create({ + data: { + emailAccountId, + name: ruleName, + rule: { connect: { id: rule.id } }, + }, + }); - groupId = newGroup.id; + groupId = newGroup.id; + } catch (error) { + if (isDuplicateError(error)) { + logger.error("Group already exists", { emailAccountId, ruleName }); + const newGroup2 = await prisma.group.create({ + data: { + emailAccountId, + name: `${ruleName} (${new Date().toISOString()})`, + rule: { connect: { id: rule.id } }, + }, + }); + groupId = newGroup2.id; + } else { + logger.error("Error creating learned patterns group", { error }); + return { error: "Error creating learned patterns group" }; + } + } } + const errors: string[] = []; + // Process all patterns in a single function for (const pattern of patterns) { // Store pattern with the exclude flag properly set in the database // This maps directly to the new exclude field in the GroupItem model - await prisma.groupItem.upsert({ - where: { - groupId_type_value: { + try { + await prisma.groupItem.upsert({ + where: { + groupId_type_value: { + groupId, + type: pattern.type, + value: pattern.value, + }, + }, + update: { + exclude: pattern.exclude || false, + }, + create: { groupId, type: pattern.type, value: pattern.value, + exclude: pattern.exclude || false, }, - }, - update: { - exclude: pattern.exclude || false, - }, - create: { - groupId, - type: pattern.type, - value: pattern.value, - exclude: pattern.exclude || false, - }, - }); + }); + } catch (error) { + const message = `${pattern.value} (${pattern.type}) ${ + pattern.exclude ? "excluded" : "" + }`; + + if (isDuplicateError(error)) { + errors.push(`Duplicate pattern: ${message}`); + } else { + errors.push(`Error saving pattern: ${message}`); + } + } } + + if (errors.length > 0) { + return { error: errors.join(", ") }; + } + + return { success: true }; } diff --git a/version.txt b/version.txt index 35170e7e76..00b0c3d735 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v1.9.12 \ No newline at end of file +v1.9.13 \ No newline at end of file