Avatar Customization + Update Agent Brain (#23)
Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com>
This commit is contained in:
@@ -1,34 +1,23 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { createDefaultAgentAvatarProfile } from "@/lib/avatars/profile";
|
||||
import { stubStudioRoute } from "./helpers/studioRoute";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.route("**/api/studio", async (route, request) => {
|
||||
if (request.method() === "PUT") {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
settings: { version: 1, gateway: null, focused: {}, avatars: {} },
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.method() !== "GET") {
|
||||
await route.fallback();
|
||||
return;
|
||||
}
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
settings: { version: 1, gateway: null, focused: {}, avatars: {} },
|
||||
}),
|
||||
});
|
||||
await stubStudioRoute(page, {
|
||||
version: 1,
|
||||
gateway: null,
|
||||
focused: {},
|
||||
avatars: {
|
||||
"ws://localhost:18789": {
|
||||
"agent-1": createDefaultAgentAvatarProfile("seed-1"),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("empty focused view shows zero agents when disconnected", async ({ page }) => {
|
||||
test("structured avatar settings fixture does not break focused load", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await expect(page.getByTestId("studio-menu-toggle")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Connect" }).first()).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Open headquarters sidebar" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "CHAT" })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -1,44 +1,23 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { stubStudioRoute } from "./helpers/studioRoute";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.route("**/api/studio", async (route, request) => {
|
||||
if (request.method() === "PUT") {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
settings: { version: 1, gateway: null, focused: {}, avatars: {} },
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.method() !== "GET") {
|
||||
await route.fallback();
|
||||
return;
|
||||
}
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
settings: { version: 1, gateway: null, focused: {}, avatars: {} },
|
||||
}),
|
||||
});
|
||||
});
|
||||
await stubStudioRoute(page);
|
||||
});
|
||||
|
||||
test("shows_connection_settings_control_in_header", async ({ page }) => {
|
||||
test("shows_office_header_controls", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await expect(page.getByTestId("brain-files-toggle")).toHaveCount(0);
|
||||
await page.getByTestId("studio-menu-toggle").click();
|
||||
await expect(page.getByTestId("gateway-settings-toggle")).toBeVisible();
|
||||
await expect(page.getByTitle("Voice reply settings")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "CHAT" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("mobile_header_shows_connection_control", async ({ page }) => {
|
||||
test("mobile_header_shows_office_controls", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto("/");
|
||||
|
||||
await expect(page.getByTestId("brain-files-toggle")).toHaveCount(0);
|
||||
await page.getByTestId("studio-menu-toggle").click();
|
||||
await expect(page.getByTestId("gateway-settings-toggle")).toBeVisible();
|
||||
await expect(page.getByTitle("Voice reply settings")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "CHAT" })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -5,13 +5,10 @@ test.beforeEach(async ({ page }) => {
|
||||
await stubStudioRoute(page);
|
||||
});
|
||||
|
||||
test("connection panel reflects disconnected state", async ({ page }) => {
|
||||
test("office settings panel reflects current gateway state", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await page.getByTestId("studio-menu-toggle").click();
|
||||
await page.getByTestId("gateway-settings-toggle").click();
|
||||
await expect(page.getByLabel("Upstream URL")).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("button", { name: /^(Connect|Disconnect)$/ })
|
||||
).toBeVisible();
|
||||
await page.getByTitle("Voice reply settings").click();
|
||||
await expect(page.getByRole("button", { name: "Disconnect gateway" })).toBeVisible();
|
||||
await expect(page.getByText("Current studio connection and endpoint details.")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { stubStudioRoute } from "./helpers/studioRoute";
|
||||
|
||||
test("connection settings persist to the studio settings API", async ({ page }) => {
|
||||
test("voice reply settings persist to the studio settings API", async ({ page }) => {
|
||||
await stubStudioRoute(page);
|
||||
|
||||
await page.goto("/");
|
||||
await page.getByTestId("studio-menu-toggle").click();
|
||||
await page.getByTestId("gateway-settings-toggle").click();
|
||||
await expect(page.getByLabel("Upstream URL")).toBeVisible();
|
||||
await page.getByTitle("Voice reply settings").click();
|
||||
await expect(page.getByRole("switch", { name: "Voice replies" })).toBeVisible();
|
||||
await page.waitForFunction(() => {
|
||||
const element = document.querySelector('[aria-label="Voice replies"]');
|
||||
return element instanceof HTMLButtonElement && !element.disabled;
|
||||
});
|
||||
|
||||
await page.getByLabel("Upstream URL").fill("ws://gateway.example:18789");
|
||||
await page.getByLabel("Upstream token").fill("token-123");
|
||||
|
||||
const request = await page.waitForRequest((req) => {
|
||||
const requestPromise = page.waitForRequest((req) => {
|
||||
if (!req.url().includes("/api/studio") || req.method() !== "PUT") {
|
||||
return false;
|
||||
}
|
||||
const payload = JSON.parse(req.postData() ?? "{}") as Record<string, unknown>;
|
||||
const gateway = (payload.gateway ?? {}) as { url?: string; token?: string };
|
||||
return gateway.url === "ws://gateway.example:18789" && gateway.token === "token-123";
|
||||
const voiceReplies = (payload.voiceReplies ?? {}) as Record<string, { enabled?: boolean }>;
|
||||
return Object.values(voiceReplies).some((entry) => entry.enabled === true);
|
||||
});
|
||||
await page.getByRole("switch", { name: "Voice replies" }).click();
|
||||
const request = await requestPromise;
|
||||
|
||||
const payload = JSON.parse(request.postData() ?? "{}") as Record<string, unknown>;
|
||||
const gateway = (payload.gateway ?? {}) as { url?: string; token?: string };
|
||||
expect(gateway.url).toBe("ws://gateway.example:18789");
|
||||
expect(gateway.token).toBe("token-123");
|
||||
await expect(
|
||||
page.getByRole("button", { name: /^(Connect|Disconnect)$/ })
|
||||
).toBeVisible();
|
||||
const voiceReplies = (payload.voiceReplies ?? {}) as Record<string, { enabled?: boolean }>;
|
||||
expect(Object.keys(voiceReplies).length).toBeGreaterThan(0);
|
||||
expect(Object.values(voiceReplies).some((entry) => entry.enabled === true)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -5,11 +5,14 @@ test.beforeEach(async ({ page }) => {
|
||||
await stubStudioRoute(page);
|
||||
});
|
||||
|
||||
test("shows_disconnected_connect_surface", async ({ page }) => {
|
||||
test("shows_office_shell_from_root_redirect", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await expect(page.getByLabel("Upstream URL")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: /^(Connect|Connecting…)$/ })).toBeVisible();
|
||||
await expect
|
||||
.poll(() => new URL(page.url()).pathname)
|
||||
.toBe("/office");
|
||||
await expect(page.getByRole("button", { name: "Open headquarters sidebar" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "CHAT" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("persists_gateway_fields_to_studio_settings", async ({ page }) => {
|
||||
@@ -35,17 +38,20 @@ test("persists_gateway_fields_to_studio_settings", async ({ page }) => {
|
||||
test("focused_preferences_persist_across_reload", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await page.getByTestId("studio-menu-toggle").click();
|
||||
await expect(page.getByTestId("gateway-settings-toggle")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Open headquarters sidebar" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "CHAT" })).toBeVisible();
|
||||
|
||||
await page.reload();
|
||||
|
||||
await expect(page.getByTestId("studio-menu-toggle")).toBeVisible();
|
||||
await expect
|
||||
.poll(() => new URL(page.url()).pathname)
|
||||
.toBe("/office");
|
||||
await expect(page.getByRole("button", { name: "Open headquarters sidebar" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("clears_unseen_indicator_on_focus", async ({ page }) => {
|
||||
test("shows_chat_entrypoint_in_office_shell", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
await page.getByTestId("studio-menu-toggle").click();
|
||||
await expect(page.getByTestId("gateway-settings-toggle")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "CHAT" })).toBeVisible();
|
||||
await expect(page.getByTitle("Voice reply settings")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -1,32 +1,13 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { stubStudioRoute } from "./helpers/studioRoute";
|
||||
|
||||
test("loads focused studio empty state", async ({ page }) => {
|
||||
await page.route("**/api/studio", async (route, request) => {
|
||||
if (request.method() === "PUT") {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
settings: { version: 1, gateway: null, focused: {}, avatars: {} },
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (request.method() !== "GET") {
|
||||
await route.fallback();
|
||||
return;
|
||||
}
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
settings: { version: 1, gateway: null, focused: {}, avatars: {} },
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
test("loads office shell from root", async ({ page }) => {
|
||||
await stubStudioRoute(page);
|
||||
await page.goto("/");
|
||||
|
||||
await expect(page.getByTestId("studio-menu-toggle")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Connect" }).first()).toBeVisible();
|
||||
await expect
|
||||
.poll(() => new URL(page.url()).pathname)
|
||||
.toBe("/office");
|
||||
await expect(page.getByRole("button", { name: "Open headquarters sidebar" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "CHAT" })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { Page, Route, Request } from "@playwright/test";
|
||||
import type { AgentAvatarProfile } from "@/lib/avatars/profile";
|
||||
|
||||
export type StudioSettingsFixture = {
|
||||
version: 1;
|
||||
gateway: { url: string; token: string } | null;
|
||||
focused: Record<string, { mode: "focused"; filter: string; selectedAgentId: string | null }>;
|
||||
avatars: Record<string, Record<string, string>>;
|
||||
avatars: Record<string, Record<string, AgentAvatarProfile>>;
|
||||
};
|
||||
|
||||
const DEFAULT_SETTINGS: StudioSettingsFixture = {
|
||||
@@ -65,25 +66,31 @@ const createStudioRoute = (initial: StudioSettingsFixture = DEFAULT_SETTINGS) =>
|
||||
}
|
||||
|
||||
if (patch.avatars && typeof patch.avatars === "object") {
|
||||
const avatarsPatch = patch.avatars as Record<string, Record<string, string | null> | null>;
|
||||
const avatarsPatch = patch.avatars as
|
||||
| Record<string, Record<string, AgentAvatarProfile | null> | null>
|
||||
| null;
|
||||
const avatarsNext: StudioSettingsFixture["avatars"] = { ...next.avatars };
|
||||
for (const [gatewayKey, gatewayPatch] of Object.entries(avatarsPatch)) {
|
||||
for (const [gatewayKey, gatewayPatch] of Object.entries(avatarsPatch ?? {})) {
|
||||
if (gatewayPatch === null) {
|
||||
delete avatarsNext[gatewayKey];
|
||||
continue;
|
||||
}
|
||||
const existing = avatarsNext[gatewayKey] ? { ...avatarsNext[gatewayKey] } : {};
|
||||
for (const [agentId, seedPatch] of Object.entries(gatewayPatch)) {
|
||||
if (seedPatch === null) {
|
||||
for (const [agentId, avatarPatch] of Object.entries(gatewayPatch)) {
|
||||
if (avatarPatch === null) {
|
||||
delete existing[agentId];
|
||||
continue;
|
||||
}
|
||||
const seed = typeof seedPatch === "string" ? seedPatch.trim() : "";
|
||||
if (!seed) {
|
||||
if (
|
||||
typeof avatarPatch !== "object" ||
|
||||
avatarPatch === null ||
|
||||
typeof avatarPatch.seed !== "string" ||
|
||||
avatarPatch.seed.trim().length === 0
|
||||
) {
|
||||
delete existing[agentId];
|
||||
continue;
|
||||
}
|
||||
existing[agentId] = seed;
|
||||
existing[agentId] = avatarPatch;
|
||||
}
|
||||
avatarsNext[gatewayKey] = existing;
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ test.beforeEach(async ({ page }) => {
|
||||
await stubStudioRoute(page);
|
||||
});
|
||||
|
||||
test("redirects unknown app routes to root", async ({ page }) => {
|
||||
test("redirects unknown app routes to office", async ({ page }) => {
|
||||
await page.goto("/not-a-real-route");
|
||||
await expect
|
||||
.poll(() => new URL(page.url()).pathname, {
|
||||
message: "Expected invalid route to redirect to root path.",
|
||||
message: "Expected invalid route to redirect to office path.",
|
||||
})
|
||||
.toBe("/");
|
||||
await expect(page.getByTestId("studio-menu-toggle")).toBeVisible();
|
||||
.toBe("/office");
|
||||
await expect(page.getByRole("button", { name: "Open headquarters sidebar" })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -5,16 +5,13 @@ test.beforeEach(async ({ page }) => {
|
||||
await stubStudioRoute(page);
|
||||
});
|
||||
|
||||
test("settings route shows connect UI while disconnected and can return to chat", async ({ page }) => {
|
||||
test("settings route redirects to office", async ({ page }) => {
|
||||
await page.goto("/agents/main/settings");
|
||||
|
||||
await expect(page.getByRole("button", { name: "Back to chat" })).toBeVisible();
|
||||
await expect(page.getByLabel("Upstream URL")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Back to chat" }).click();
|
||||
await expect
|
||||
.poll(() => new URL(page.url()).pathname, {
|
||||
message: "Expected back button to return to chat route.",
|
||||
message: "Expected settings route to redirect to office.",
|
||||
})
|
||||
.toBe("/");
|
||||
.toBe("/office");
|
||||
await expect(page.getByRole("button", { name: "Open headquarters sidebar" })).toBeVisible();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user