Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c1e7753
Setup api calls
finnar-bin Mar 30, 2026
6352406
Wire create new session and adding new prompt log
finnar-bin Mar 31, 2026
f7c2ba6
Generate suggestions prompt logging
finnar-bin Mar 31, 2026
5c39dcc
Wire create new chat session via button click
finnar-bin Mar 31, 2026
5eab696
Hydrate chat history using data from api
finnar-bin Apr 1, 2026
dfe6b23
Merge branch 'dev' into enhancement/4031-ai-drawer-chat-history
finnar-bin Apr 1, 2026
1b2a503
Merge branch 'dev' into enhancement/4031-ai-drawer-chat-history
finnar-bin Apr 6, 2026
724fbaa
Move prompt adding to backend
finnar-bin Apr 7, 2026
c0fad38
Parse json string prompt responses stored in db
finnar-bin Apr 7, 2026
4078bbf
Ensure url-chatZUID mapping to only load relevant chat history on active
finnar-bin Apr 7, 2026
ca21ea4
Update the response body structure so it's easy to get the promptZUID
finnar-bin Apr 7, 2026
4ce5adc
Update update approval endpoint
finnar-bin Apr 7, 2026
f4b7db7
Move the input field into a separate component to prevent re-rendering
finnar-bin Apr 7, 2026
8b25329
Resolved issue where new responses aren't animated
finnar-bin Apr 7, 2026
514a719
Add agent skills to gitignore
finnar-bin Apr 8, 2026
030b5e5
Add data-cy tags and prevent ai drawer from creating a chat session if
finnar-bin Apr 8, 2026
bcb6725
Added data-cy tags
finnar-bin Apr 8, 2026
205fcac
Re-added logic to auto-apply responses
finnar-bin Apr 8, 2026
1bdb970
Added .nyc_output to git ignore
finnar-bin Apr 8, 2026
9fc74ae
Added data-cy tags
finnar-bin Apr 8, 2026
aad005c
Fixed failing tests
finnar-bin Apr 8, 2026
aebf8ba
Added tests for ai drawer
finnar-bin Apr 8, 2026
0551751
Fixed incorrect format for the getBySelector command
finnar-bin Apr 8, 2026
9c10ef8
Moved new chat session creation to the backend
finnar-bin Apr 13, 2026
2b3e2a5
Updated autoapply logic so it depends on the latest prompt zuid received
finnar-bin Apr 13, 2026
91ac653
Updated cypress tests
finnar-bin Apr 14, 2026
3d3db3e
Resolve bug where clear chat button doesn't properly update the
finnar-bin Apr 14, 2026
256323e
Use %-based borderRadius
finnar-bin Apr 14, 2026
e4b43be
Resolve failing tests due to changes in getBySelector helper
finnar-bin Apr 14, 2026
5498197
Increased timeouut when posting to /client
finnar-bin Apr 15, 2026
50e0303
Merge branch 'dev' into enhancement/4031-ai-drawer-chat-history
finnar-bin Apr 19, 2026
b98a7da
Increased timeout for api calls to /client
finnar-bin Apr 19, 2026
f94dc4d
Remove chat history reverse
finnar-bin Apr 22, 2026
ad4d494
Merge branch 'dev' into enhancement/4031-ai-drawer-chat-history
finnar-bin Apr 24, 2026
7115723
Remove userZUID from request param
finnar-bin Apr 27, 2026
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
36 changes: 17 additions & 19 deletions cypress/e2e/content/actions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe("Actions in content editor", () => {
);
});

cy.getBySelector(`"field:markdown"`)
cy.getBySelector("field:markdown")
.find("textarea")
.click()
.clear()
Expand All @@ -53,15 +53,15 @@ describe("Actions in content editor", () => {
cy.getBySelector("toast").contains("Missing Data in Required Fields", {
matchCase: false,
});
cy.getBySelector(`"field:markdown"`)
cy.getBySelector("field:markdown")
.find("textarea")
.click()
.clear()
.type("markdown");
});

it("Must not save when exceeding or lacking characters", () => {
cy.getBySelector(`"field:text"`)
cy.getBySelector("field:text")
.find("input")
.click()
.clear()
Expand All @@ -77,7 +77,7 @@ describe("Actions in content editor", () => {
.find("li")
.first()
.contains("Requires 8 more characters.");
cy.getBySelector(`"field:text"`)
cy.getBySelector("field:text")
.find("input")
.click()
.clear()
Expand All @@ -93,7 +93,7 @@ describe("Actions in content editor", () => {
.find("li")
.first()
.contains("Exceeding by 5 characters.");
cy.getBySelector(`"field:text"`)
cy.getBySelector("field:text")
.find("input")
.click()
.clear()
Expand All @@ -109,15 +109,15 @@ describe("Actions in content editor", () => {
matchCase: false,
}
);
cy.getBySelector(`"field:text"`)
cy.getBySelector("field:text")
.find("input")
.click()
.clear()
.type("Mitchell Wilder");
});

it("Must not save when regex is not matched", () => {
cy.getBySelector(`"field:textarea"`)
cy.getBySelector("field:textarea")
.find("textarea")
.first()
.click()
Expand All @@ -133,7 +133,7 @@ describe("Actions in content editor", () => {
.find("li")
.first()
.contains("Must be an email (e.g. hello@zesty.io)");
cy.getBySelector(`"field:textarea"`)
cy.getBySelector("field:textarea")
.find("textarea")
.first()
.click()
Expand All @@ -150,7 +150,7 @@ describe("Actions in content editor", () => {
matchCase: false,
}
);
cy.getBySelector(`"field:textarea"`)
cy.getBySelector("field:textarea")
.find("textarea")
.first()
.click()
Expand All @@ -166,7 +166,7 @@ describe("Actions in content editor", () => {
cy.get(`[data-cy="field:fontawesome"] input`).should("not.exist");

// Make an edit to enable save button
cy.getBySelector(`"field:text"`)
cy.getBySelector("field:text")
.find("input")
.click()
.clear()
Expand Down Expand Up @@ -361,20 +361,20 @@ describe("Actions in content editor", () => {
cy.visit(`/content/${Cypress.env("modelZUID")}/new`);
});

cy.getBySelector(`"field:text"`)
cy.getBySelector("field:text")
.find("input")
.should(
"have.value",
FIELDS.find((field) => field.name === "text").settings.defaultValue
);
cy.getBySelector(`"field:textarea"`)
cy.getBySelector("field:textarea")
.find("textarea")
.first()
.should(
"have.value",
FIELDS.find((field) => field.name === "textarea").settings.defaultValue
);
cy.getBySelector(`"field:markdown"`)
cy.getBySelector("field:markdown")
.find("textarea")
.should(
"have.value",
Expand All @@ -387,7 +387,7 @@ describe("Actions in content editor", () => {
cy.visit(`/content/${Cypress.env("modelZUID")}/new`);
});

cy.getBySelector(`"field:text"`)
cy.getBySelector("field:text")
.find("input")
.click()
.clear()
Expand Down Expand Up @@ -464,24 +464,22 @@ describe("Actions in content editor", () => {
// Increase timeout to account for longer AI generation times.
const aiDataGenerationTimeout = { timeout: 60_000 };

cy.getBySelector(`"field:text"`)
cy.getBySelector("field:text")
.find("input")
.click()
.clear()
.type(TEST_DATA?.ai);

// Generate AI content for markdown
cy.getBySelector(`"field:markdown"`).find("[data-cy='AIOpen']").click();
cy.getBySelector("field:markdown").find("[data-cy='AIOpen']").click();
cy.getBySelector("AITopicField").type("biking");
cy.getBySelector("AIAudienceField").type("young adults");
cy.getBySelector("AIGenerate").click();

cy.get("[data-cy='AIApprove']", aiDataGenerationTimeout).click();

// Generate AI content for wysiwyg_basic
cy.getBySelector(`"field:wysiwyg_basic"`)
.find("[data-cy='AIOpen']")
.click();
cy.getBySelector("field:wysiwyg_basic").find("[data-cy='AIOpen']").click();
cy.getBySelector("AITopicField").type("biking");
cy.getBySelector("AIAudienceField").type("young adults");
cy.getBySelector("AIGenerate").click();
Expand Down
4 changes: 2 additions & 2 deletions cypress/e2e/content/item-list-table.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe("Content item list table", () => {
});

cy.getBySelector("sortByFilter_default").click();
cy.getBySelector(`"sort:text"`).click();
cy.getBySelector("sort:text").click();
cy.getBySelector("listItemTable")
.find('[data-cy="itemListRow"]')
.first()
Expand All @@ -37,7 +37,7 @@ describe("Content item list table", () => {

cy.intercept("/search/items*").as("searchItems");
cy.intercept("/v1/content/models*").as("contentModels");
cy.getBySelector(`"field:text"`)
cy.getBySelector("field:text")
.find("input")
.clear()
.type(`Delete me ${NOW}`);
Expand Down
4 changes: 2 additions & 2 deletions cypress/e2e/content/meta.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ describe("Content Meta", () => {
});
});

cy.getBySelector(`"field:text"`).find("input").type(today);
cy.getBySelector("field:text").find("input").type(today);
cy.wait(500); // wait for debounced input to settle
cy.getBySelector("CreateItemSaveButton").click();
cy.getBySelector("toast").contains("Created Item");
Expand All @@ -129,7 +129,7 @@ describe("Content Meta", () => {
cy.iframe("#wysiwyg_basic_ifr")
.click()
.type(`{selectall}{backspace}meta description`);
cy.getBySelector(`"field:text"`)
cy.getBySelector("field:text")
.find("input")
.clear()
.type(`{selectall}{backspace} meta title ${today}`);
Expand Down
184 changes: 184 additions & 0 deletions cypress/e2e/shell/ai-drawer.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Purposedly setting this to 1 minute as the client call sometimes takes a while to respond
// causing some tests to fail intermittently
const AI_CLIENT_TIMEOUT = { timeout: 60000 };

describe("AI drawer", () => {
before(() => {
cy.task("seed:content", "fixtures/ai-drawer.json").then(
({ model, items }) => {
Cypress.env("modelZUID", model?.ZUID);
Cypress.env("itemZUID", items[0]?.meta?.ZUID);
}
);
});

after(() => {
cy.deleteModel(Cypress.env("modelZUID"));
});

it("should only be available to the content, code and blocks app", () => {
cy.visit("/launchpad");
cy.getBySelector("AIDrawerToggle").click();
cy.getBySelector("AIDrawerDisabled").should("not.exist");

[
"/schema",
"/media",
"/leads",
"/redirects",
"/reports",
"/apps",
"/settings/instance/general",
].forEach((path) => {
cy.visit(path);
cy.getBySelector("AIDrawerDisabled").should("exist");
});

[
"/content/6-a1a600-k0b6f0/7-a1be38-1b42ht",
"/content/6-a1a600-k0b6f0/7-a1be38-1b42ht/meta",
"/code/file/views/11-eb8dec-6nsjbf",
"/blocks/6-d8b088cc9c-gwk3w7/7-ee94b5e98d-ss015b",
].forEach((path) => {
cy.visit(path);
cy.getBySelector("AIDrawerEnabled").should("exist");
});
});

it("should be able to load a chat history", () => {
cy.clearLocalStorage();
cy.waitOn("/v1/content/models*", () => {
cy.visit(
`/content/${Cypress.env("modelZUID")}/${Cypress.env("itemZUID")}`
);
});
cy.getBySelector("DuoModeToggle").click({ force: true });

const prompt = "Hello world";

cy.intercept("GET", "**/chats/*?*").as("getChat");
cy.intercept("POST", "**/client").as("postClient");

cy.getBySelector("AIDrawerToggle").click();
cy.getBySelector("AIDrawerEnabled").should("exist");
cy.wait(500);
cy.getBySelector("AIDrawerComposer").type(prompt);
cy.getBySelector("AIDrawerSubmitPrompt").click();
cy.wait("@postClient", AI_CLIENT_TIMEOUT);
cy.wait("@getChat");
cy.getBySelector("AIDrawerUserInput").should("contain", prompt);
cy.getBySelector("AIDrawerSystemOutput").should("have.length", 1);
cy.reload();
cy.wait("@getChat");
cy.getBySelector("AIDrawerUserInput").should("contain", prompt);
cy.getBySelector("AIDrawerSystemOutput").should("have.length", 1);
});

it("should be able to generate text content and apply them to fields", () => {
cy.intercept("GET", "**/chats/*?*").as("getChat");
cy.intercept("POST", "**/client").as("postClient");

cy.getBySelector("AIDrawerComposer").type(
"Generate a sensational title for the page_title field"
);
cy.getBySelector("AIDrawerSubmitPrompt").click();
cy.wait("@postClient", AI_CLIENT_TIMEOUT);
cy.wait("@getChat");
cy.getBySelector("AIDrawerSetValue").first().should("exist").click();
cy.getBySelector("field:page_title")
.find("input")
.invoke("val")
.should("not.be.empty");
});

it("should be able to generate images and apply them to image fields", () => {
cy.intercept("GET", "**/chats/*?*").as("getChat");
cy.intercept("POST", "**/client").as("postClient");

cy.getBySelector("AIDrawerComposer").type(
"Generate an image of the alps for the page_image field"
);
cy.getBySelector("AIDrawerSubmitPrompt").click();
cy.wait("@postClient", AI_CLIENT_TIMEOUT);
cy.wait("@getChat");
cy.getBySelector("AIDrawerSetValue").eq(1).should("exist").click();
cy.getBySelector("field:page_image")
.find('[data-cy="mediaItem-container"]')
.should("exist");
});

it("should be able to generate suggestions", () => {
cy.intercept("GET", "**/chats/*?*").as("getChat");
cy.intercept("POST", "**/client").as("postClient");

cy.getBySelector("AIDrawerGenerateSuggestions").click();
cy.wait("@postClient", AI_CLIENT_TIMEOUT);
cy.wait("@getChat");
cy.getBySelector("AIDrawerSystemSuggestion").should("have.length", 3);
cy.getBySelector("AIDrawerSystemSuggestion").first().click();
cy.getBySelector("AIDrawerComposer")
.find("textarea")
.first()
.invoke("val")
.should("not.be.empty");
});

it("should be able to generate blocks", () => {
cy.intercept("GET", "**/chats/*?*").as("getChat");
cy.intercept("POST", "**/client").as("postClient");

cy.getBySelector("AIDrawerComposer").type(
"{selectall}{del}Generate a block for a dog profile"
);
cy.getBySelector("AIDrawerSubmitPrompt").click();
cy.wait("@postClient", AI_CLIENT_TIMEOUT);
cy.wait("@getChat");
cy.getBySelector("AIDrawerNavigate").should("exist");
});

it("should be able to auto apply generated content", () => {
cy.intercept("GET", "**/chats/*?*").as("getChat");
cy.intercept("POST", "**/client").as("postClient");

cy.getBySelector("field:page_title")
.find("input")
.clear()
.should("have.value", "");
cy.getBySelector("AIDrawerSettings").click();
cy.getBySelector("AIDrawerAutoApplyToggle").click();
cy.getBySelector("AIDrawerComposer").type(
"Generate a title for people who love to fish for the page_title field"
);
cy.getBySelector("AIDrawerSubmitPrompt").click();
cy.wait("@postClient", AI_CLIENT_TIMEOUT);
cy.wait("@getChat");
cy.getBySelector("field:page_title")
.find("input")
.invoke("val")
.should("not.be.empty");
});

it("should be able to create a new chat session", () => {
cy.getBySelector("AIDrawerClearChat").click();
cy.getBySelector("AIDrawerUserInput").should("not.exist");
});

it("should be able to edit code", () => {
cy.intercept("GET", "**/chats/*?*").as("getChat");
cy.intercept("POST", "**/client").as("postClient");
cy.intercept("GET", "**/v1/web/views/*").as("getView");

cy.getBySelector("ContentItemMoreButton").click();
cy.getBySelector("EditTemplate").click();
cy.contains("Don't Save").click();
cy.wait("@getView");
cy.getBySelector("AIDrawerEnabled").should("exist");
cy.getBySelector("AIDrawerComposer").type(
"Generate a code with an H1 wrapping the page_title field"
);
cy.getBySelector("AIDrawerSubmitPrompt").click();
cy.wait("@postClient", AI_CLIENT_TIMEOUT);
cy.wait("@getChat");
cy.getBySelector("AIDrawerSetValue").should("exist");
});
});
Loading