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
5 changes: 4 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,10 @@ async function main(): Promise<void> {
"--preset <id>",
"pill-dark, pill-light, rounded-card, minimal-bar, or voice-orb",
)
.option("--position <corner>", "bottom-right or bottom-left")
.option(
"--position <corner>",
"bottom-right, bottom-left, top-right, top-left, bottom-center, top-center, center-right, center-left, or custom",
)
.option("--mode <mode>", "chat or voice")
.option("--launcher-label <text>", "Launcher button label")
.option("--greeting <text>", "Initial greeting text")
Expand Down
39 changes: 39 additions & 0 deletions src/commands/projects/widget/commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
runProjectsWidgetSet,
runProjectsWidgetShow,
validateWidgetHexColor,
validateWidgetPosition,
validateWidgetPreset,
} from "./commands.js";

Expand Down Expand Up @@ -125,13 +126,40 @@ describe("projects widget commands", () => {
);
});

it("sets top-left position", async () => {
getProjectWidget.mockResolvedValue(sampleWidgetResponse);
updateProjectWidgetDraft.mockResolvedValue({
...sampleWidgetResponse,
draft: {
...sampleWidgetResponse.draft,
position: "top-left",
},
});

await runProjectsWidgetSet({ position: "top-left" });

expect(updateProjectWidgetDraft).toHaveBeenCalledWith("proj-1", {
v: 1,
preset: "pill-dark",
position: "top-left",
mode: "voice",
});
});

it("rejects unknown preset", async () => {
await expect(
runProjectsWidgetSet({ preset: "unknown-preset" }),
).rejects.toThrow(/Unknown widget preset/);
expect(getProjectWidget).not.toHaveBeenCalled();
});

it("rejects unknown position", async () => {
await expect(
runProjectsWidgetSet({ position: "center-stage" }),
).rejects.toThrow(/Unknown widget position/);
expect(getProjectWidget).not.toHaveBeenCalled();
});

it("rejects bad hex colors", async () => {
getProjectWidget.mockResolvedValue(sampleWidgetResponse);

Expand Down Expand Up @@ -162,6 +190,17 @@ describe("projects widget commands", () => {
/Unknown widget preset/,
);
});

it("accepts top-left and custom positions", () => {
expect(validateWidgetPosition("top-left")).toBe("top-left");
expect(validateWidgetPosition("custom")).toBe("custom");
});

it("rejects unknown positions", () => {
expect(() => validateWidgetPosition("middle")).toThrow(
/Unknown widget position/,
);
});
});

describe("runProjectsWidgetDeploy", () => {
Expand Down
12 changes: 11 additions & 1 deletion src/commands/projects/widget/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,17 @@ export const WIDGET_PRESETS = [
"voice-orb",
] as const;

export const WIDGET_POSITIONS = ["bottom-right", "bottom-left"] as const;
export const WIDGET_POSITIONS = [
"bottom-right",
"bottom-left",
"top-right",
"top-left",
"bottom-center",
"top-center",
"center-right",
"center-left",
"custom",
] as const;

export const WIDGET_MODES = ["chat", "voice"] as const;

Expand Down
49 changes: 47 additions & 2 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,40 @@ export interface SessionConversationResponse {
export type ProjectWidgetPublishStatus =
"idle" | "queued" | "publishing" | "published" | "failed";

export type WidgetMessageRoleTheme = {
fontFamily?: string;
fontSize?: string;
color?: string;
bubble?: string;
};

export type VoiceThereWidgetChatTheme = {
incoming?: WidgetMessageRoleTheme;
outgoing?: WidgetMessageRoleTheme;
headerBackground?: string;
inputBackground?: string;
inputColor?: string;
panelWidth?: string;
panelHeight?: string;
panelRadius?: string;
};

export type VoiceThereWidgetTheme = {
primary?: string;
background?: string;
text?: string;
fontFamily?: string;
fontSize?: string;
chat?: VoiceThereWidgetChatTheme;
};

export type WidgetPositionOffset = {
top?: string;
right?: string;
bottom?: string;
left?: string;
};

export type VoiceThereWidgetConfigV1 = {
v: 1;
publicId?: string;
Expand All @@ -686,10 +720,21 @@ export type VoiceThereWidgetConfigV1 = {
revision?: number;
preset?:
"pill-dark" | "pill-light" | "rounded-card" | "minimal-bar" | "voice-orb";
theme?: { primary?: string; background?: string; text?: string };
theme?: VoiceThereWidgetTheme;
launcherLabel?: string;
greeting?: string;
position?: "bottom-right" | "bottom-left";
position?:
| "bottom-right"
| "bottom-left"
| "top-right"
| "top-left"
| "bottom-center"
| "top-center"
| "center-right"
| "center-left"
| "custom";
positionOffset?: WidgetPositionOffset;
customCss?: string;
mode?: "chat" | "voice";
};

Expand Down
Loading