diff --git a/packages/sdk/README.md b/packages/sdk/README.md index feffdf4..c96cee6 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -62,6 +62,16 @@ realtimeClient.setPrompt("Cyberpunk city"); realtimeClient.disconnect(); ``` +#### Portrait mode + +Pass `{ orientation: "portrait" }` to swap width and height for vertical streams: + +```typescript +const model = models.realtime("lucy_2_rt", { orientation: "portrait" }); +// model.width → 720 +// model.height → 1280 +``` + ### Async Processing (Queue API) For video generation jobs, use the queue API to submit jobs and poll for results: diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index caddfa2..3aef58b 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -56,6 +56,7 @@ export { type Model, type ModelDefinition, models, + type RealtimeModelOptions, type RealTimeModels, type VideoModelDefinition, type VideoModels, diff --git a/packages/sdk/src/shared/model.ts b/packages/sdk/src/shared/model.ts index d2f6d4b..d6d0152 100644 --- a/packages/sdk/src/shared/model.ts +++ b/packages/sdk/src/shared/model.ts @@ -378,12 +378,41 @@ const _models = { }, } as const; +/** + * Options for configuring realtime model output. + */ +export type RealtimeModelOptions = { + /** The orientation of the output. When `"portrait"`, width and height are swapped. Defaults to `"landscape"`. */ + orientation?: "landscape" | "portrait"; +}; + export const models = { - realtime: (model: T): ModelDefinition => { + /** + * Get a realtime model definition. + * + * Available models: + * - `"mirage"` - Mirage v1 + * - `"mirage_v2"` - Mirage v2 + * - `"lucy_v2v_720p_rt"` - Lucy v2v 720p realtime + * - `"lucy_2_rt"` - Lucy 2 realtime + * - `"live_avatar"` - Live avatar + * + * @param model - The realtime model to use. + * @param options - Optional configuration. Set `orientation` to `"portrait"` to swap width and height. + * @returns The model definition, with dimensions adjusted for the requested orientation. + */ + realtime: (model: T, options?: RealtimeModelOptions): ModelDefinition => { const modelDefinition = _models.realtime[model]; if (!modelDefinition) { throw createModelNotFoundError(model); } + if (options?.orientation === "portrait") { + return { + ...modelDefinition, + width: modelDefinition.height, + height: modelDefinition.width, + } as ModelDefinition; + } return modelDefinition as ModelDefinition; }, /** diff --git a/packages/sdk/tests/unit.test.ts b/packages/sdk/tests/unit.test.ts index 627303e..866e6f0 100644 --- a/packages/sdk/tests/unit.test.ts +++ b/packages/sdk/tests/unit.test.ts @@ -990,6 +990,45 @@ describe("Lucy 2 realtime", () => { }); }); +describe("models.realtime", () => { + it("returns correct landscape dimensions by default", () => { + const model = models.realtime("lucy_2_rt"); + expect(model.width).toBe(1280); + expect(model.height).toBe(720); + expect(model.fps).toBe(20); + }); + + it("returns correct landscape dimensions when explicitly set", () => { + const model = models.realtime("lucy_2_rt", { orientation: "landscape" }); + expect(model.width).toBe(1280); + expect(model.height).toBe(720); + expect(model.fps).toBe(20); + }); + + it("returns swapped dimensions in portrait mode", () => { + const model = models.realtime("lucy_2_rt", { orientation: "portrait" }); + expect(model.width).toBe(720); + expect(model.height).toBe(1280); + expect(model.fps).toBe(20); + }); + + it("portrait mode preserves model name and urlPath", () => { + const model = models.realtime("lucy_2_rt", { orientation: "portrait" }); + expect(model.name).toBe("lucy_2_rt"); + expect(model.urlPath).toBe("/v1/stream"); + }); + + it("throws on unknown model", () => { + expect(() => models.realtime("nonexistent_model" as any)).toThrow(); + }); + + it("swaps dimensions for mirage in portrait mode", () => { + const model = models.realtime("mirage", { orientation: "portrait" }); + expect(model.width).toBe(704); + expect(model.height).toBe(1280); + }); +}); + describe("WebRTCConnection", () => { describe("setImageBase64", () => { it("rejects immediately when WebSocket is not open", async () => {