From 9e7f8be2cfdbd3bd900b421936d015bc88693c93 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 19 Apr 2026 20:09:44 +0000 Subject: [PATCH] Support worker adapter selection Co-authored-by: Luke The Dev --- docs/studio-ai-worker-contract.md | 6 ++++++ server/studio-ai-worker.js | 11 ++++++++++ src/app/api/studio-world/route.ts | 5 +++++ .../screens/StudioWorldScreen.tsx | 20 +++++++++++++++++++ src/lib/studio-world/provider.ts | 6 ++++++ src/lib/studio-world/types.ts | 9 +++++++++ tests/unit/studioAiWorker.test.ts | 1 + 7 files changed, 58 insertions(+) diff --git a/docs/studio-ai-worker-contract.md b/docs/studio-ai-worker-contract.md index 03d93e0..696520c 100644 --- a/docs/studio-ai-worker-contract.md +++ b/docs/studio-ai-worker-contract.md @@ -74,6 +74,12 @@ Current supported optional fields: - `topology` - `target_polycount` - `should_remesh` +- `adapter_id` + +Current adapter values: + +- `portrait-volume` +- `heightfield-relief` Current response: diff --git a/server/studio-ai-worker.js b/server/studio-ai-worker.js index 9308644..5a06834 100644 --- a/server/studio-ai-worker.js +++ b/server/studio-ai-worker.js @@ -764,6 +764,7 @@ const createTaskStore = () => { const toTaskObject = (task, baseUrl) => ({ id: task.id, type: "image-to-3d", + adapter_id: task.adapterId, model_urls: task.modelPath ? { glb: `${baseUrl}/openapi/v1/image-to-3d/${task.id}/output/model.glb`, @@ -845,6 +846,9 @@ const createTaskStore = () => { }; return { + listAdapters() { + return adapterRegistry.listAdapters(); + }, initialize() { for (const entry of fs.readdirSync(rootDir, { withFileTypes: true })) { if (!entry.isDirectory()) continue; @@ -900,6 +904,13 @@ const createStudioAiWorkerServer = (params = {}) => { return; } + if (req.method === "GET" && pathname === "/openapi/v1/image-to-3d/adapters") { + respondJson(res, 200, { + adapters: taskStore.listAdapters(), + }); + return; + } + if (req.method === "POST" && pathname === "/openapi/v1/image-to-3d") { const rawBody = await readRequestBody(req); const body = JSON.parse(rawBody.toString("utf8")); diff --git a/src/app/api/studio-world/route.ts b/src/app/api/studio-world/route.ts index fc54907..a3c466e 100644 --- a/src/app/api/studio-world/route.ts +++ b/src/app/api/studio-world/route.ts @@ -104,6 +104,10 @@ const parseGenerationInput = (value: unknown): StudioGenerationInput | null => { sourceImage, imageMode: value.imageMode === "mesh" ? "mesh" : "avatar", provider: parseProvider(value.provider), + adapterId: + value.adapterId === "portrait_volume" || value.adapterId === "heightfield_relief" + ? value.adapterId + : undefined, }; }; @@ -358,6 +362,7 @@ export async function POST(request: Request) { sourceImage: input.sourceImage, prompt: input.prompt, mode: "image_mesh", + adapterId: input.adapterId, }); const project = createStudioPendingProject({ input: { diff --git a/src/features/studio-world/screens/StudioWorldScreen.tsx b/src/features/studio-world/screens/StudioWorldScreen.tsx index 70b1842..4d9ffab 100644 --- a/src/features/studio-world/screens/StudioWorldScreen.tsx +++ b/src/features/studio-world/screens/StudioWorldScreen.tsx @@ -10,6 +10,7 @@ import type { StudioProviderAvailability, StudioProjectRecord, StudioSourceImageRecord, + StudioWorkerAdapterKind, StudioWorldFocus, StudioWorldGenerationProvider, StudioWorldScale, @@ -120,6 +121,7 @@ export function StudioWorldScreen() { const [uploadingImage, setUploadingImage] = useState(false); const [imageMode, setImageMode] = useState<"avatar" | "mesh">("avatar"); const [provider, setProvider] = useState("local"); + const [workerAdapter, setWorkerAdapter] = useState("portrait_volume"); const [providerAvailability, setProviderAvailability] = useState(null); const uploadInputRef = useRef(null); @@ -156,6 +158,7 @@ export function StudioWorldScreen() { if (!selectedProject) return; setUploadedImage(selectedProject.sourceImages[0] ?? null); setProvider(selectedProject.provider ?? "local"); + setWorkerAdapter(selectedProject.externalModel?.adapterId ?? "portrait_volume"); }, [selectedProject]); useEffect(() => { @@ -258,6 +261,7 @@ export function StudioWorldScreen() { sourceImage: uploadedImage, imageMode, provider, + workerAdapter, }, }), }); @@ -602,6 +606,17 @@ export function StudioWorldScreen() { +