Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"root": true,
"extends": ["@inboxzero/eslint-config"],
"extends": ["@inboxzero/eslint-config/next.js"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": true
Expand Down
3 changes: 2 additions & 1 deletion apps/web/__tests__/ai-choose-rule.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect, test, vi } from "vitest";
import { chooseRule } from "@/utils/ai/choose-rule/choose";
import { Action, ActionType, RuleType } from "@prisma/client";
import { type Action, ActionType, RuleType } from "@prisma/client";

vi.mock("server-only", () => ({}));

Expand Down Expand Up @@ -119,6 +119,7 @@ function getRule(instructions: string, actions: Action[] = []) {
body: null,
to: null,
type: RuleType.AI,
enabled: true,
};
}

Expand Down
57 changes: 57 additions & 0 deletions apps/web/__tests__/ai-diff-rules.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, it, expect, vi } from "vitest";
import { aiDiffRules } from "@/utils/ai/rule/diff-rules";

vi.mock("server-only", () => ({}));

describe("aiDiffRules", () => {
it("should correctly identify added, edited, and removed rules", async () => {
const user = {
email: "user@test.com",
aiModel: null,
aiProvider: null,
aiApiKey: null,
};

const oldPromptFile = `
* Label receipts as "Receipt"
* Archive all newsletters and label them "Newsletter"
* Archive all marketing emails and label them "Marketing"
* Label all emails from mycompany.com as "Internal"
`.trim();

const newPromptFile = `
* Archive all newsletters and label them "Newsletter Updates"
* Archive all marketing emails and label them "Marketing"
* Label all emails from mycompany.com as "Internal"
* Label all emails from support@company.com as "Support"
`.trim();

const result = await aiDiffRules({ user, oldPromptFile, newPromptFile });

expect(result).toEqual({
addedRules: ['Label all emails from support@company.com as "Support"'],
editedRules: [
{
oldRule: `Archive all newsletters and label them "Newsletter"`,
newRule: `Archive all newsletters and label them "Newsletter Updates"`,
},
],
removedRules: ['Label receipts as "Receipt"'],
});
}, 15_000);

it("should handle errors gracefully", async () => {
const user = {
email: "test@example.com",
aiProvider: null,
aiModel: null,
aiApiKey: "invalid-api-key",
};
const oldPromptFile = "Some old prompt";
const newPromptFile = "Some new prompt";

await expect(
aiDiffRules({ user, oldPromptFile, newPromptFile }),
).rejects.toThrow();
});
});
124 changes: 124 additions & 0 deletions apps/web/__tests__/ai-prompt-to-rules.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { describe, it, expect, vi } from "vitest";
import { aiPromptToRules } from "@/utils/ai/rule/prompt-to-rules";
import { createRuleSchema } from "@/utils/ai/rule/create-rule-schema";
import { ActionType, RuleType } from "@prisma/client";

vi.mock("server-only", () => ({}));

describe("aiPromptToRules", () => {
it("should convert prompt file to rules", async () => {
const user = {
email: "user@test.com",
aiModel: null,
aiProvider: null,
aiApiKey: null,
};

const prompts = [
`* Label receipts as "Receipt"`,
`* Archive all newsletters and label them "Newsletter"`,
`* Archive all marketing emails and label them "Marketing"`,
`* Label all emails from mycompany.com as "Internal"`,
];

const promptFile = prompts.join("\n");

const result = await aiPromptToRules({
user,
promptFile,
isEditing: false,
});

console.log(JSON.stringify(result, null, 2));

expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(prompts.length);

// receipts
expect(result[0]).toEqual({
name: expect.any(String),
condition: {
type: RuleType.GROUP,
group: "Receipts",
},
actions: [
{
type: ActionType.LABEL,
label: "Receipt",
},
],
});

// newsletters
expect(result[1]).toEqual({
name: expect.any(String),
condition: {
type: RuleType.GROUP,
group: "Newsletters",
},
actions: [
{
type: ActionType.ARCHIVE,
},
{
type: ActionType.LABEL,
label: "Newsletter",
},
],
});

// marketing
expect(result[2]).toEqual({
name: expect.any(String),
condition: {
type: RuleType.AI,
aiInstructions: expect.any(String),
},
actions: [
{
type: ActionType.ARCHIVE,
},
{
type: ActionType.LABEL,
label: "Marketing",
},
],
});

// internal
expect(result[3]).toEqual({
name: expect.any(String),
condition: {
type: RuleType.STATIC,
static: {
from: "mycompany.com",
},
},
actions: [
{
type: ActionType.LABEL,
label: "Internal",
},
],
});

// Validate each rule against the schema
result.forEach((rule) => {
expect(() => createRuleSchema.parse(rule)).not.toThrow();
});
}, 15_000);

it("should handle errors gracefully", async () => {
const user = {
email: "test@example.com",
aiProvider: null,
aiModel: null,
aiApiKey: "invalid-api-key",
};
const promptFile = "Some prompt";

await expect(
aiPromptToRules({ user, promptFile, isEditing: false }),
).rejects.toThrow();
});
});
4 changes: 3 additions & 1 deletion apps/web/app/(app)/automation/BulkRunRules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useRef, useState } from "react";
import useSWR from "swr";
import { useAtomValue } from "jotai";
import { LayersIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { ButtonLoader } from "@/components/Loading";
import { useModal, Modal } from "@/components/Modal";
Expand Down Expand Up @@ -40,7 +41,8 @@ export function BulkRunRules() {
return (
<div>
<Button type="button" variant="outline" onClick={openModal}>
Bulk Run on Inbox
<LayersIcon className="mr-2 h-4 w-4" />
Configure Bulk Run
</Button>
<Modal
isOpen={isModalOpen}
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(app)/automation/History.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function History() {
) : (
<AlertBasic
title="No history"
description="No AI email assistant actions have been run yet."
description="No AI personal assistant actions have been run yet."
/>
)}
</LoadingContent>
Expand Down
Loading