mirror of
https://github.com/iamlukethedev/Claw3D.git
synced 2026-07-30 03:02:37 +00:00
Support worker adapter selection
Co-authored-by: Luke The Dev <iamlukethedev@users.noreply.github.com>
This commit is contained in:
co-authored by
Luke The Dev
parent
015aa61297
commit
9e7f8be2cf
@@ -74,6 +74,12 @@ Current supported optional fields:
|
||||
- `topology`
|
||||
- `target_polycount`
|
||||
- `should_remesh`
|
||||
- `adapter_id`
|
||||
|
||||
Current adapter values:
|
||||
|
||||
- `portrait-volume`
|
||||
- `heightfield-relief`
|
||||
|
||||
Current response:
|
||||
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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<StudioWorldGenerationProvider>("local");
|
||||
const [workerAdapter, setWorkerAdapter] = useState<StudioWorkerAdapterKind>("portrait_volume");
|
||||
const [providerAvailability, setProviderAvailability] = useState<StudioProviderAvailability | null>(null);
|
||||
const uploadInputRef = useRef<HTMLInputElement | null>(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() {
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="mb-1.5 block text-xs font-medium text-foreground">Worker strategy</span>
|
||||
<select
|
||||
className="ui-input w-full"
|
||||
value={workerAdapter}
|
||||
onChange={(event) => setWorkerAdapter(event.target.value as StudioWorkerAdapterKind)}
|
||||
>
|
||||
<option value="portrait_volume">Portrait volume</option>
|
||||
<option value="heightfield_relief">Heightfield relief</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="block">
|
||||
<span className="mb-1.5 block text-xs font-medium text-foreground">Style</span>
|
||||
<select className="ui-input w-full" value={style} onChange={(event) => setStyle(event.target.value as StudioWorldStyle)}>
|
||||
@@ -774,6 +789,11 @@ export function StudioWorldScreen() {
|
||||
<span className="rounded-full bg-muted px-2 py-1 font-mono text-[10px] uppercase tracking-[0.14em] text-muted-foreground">
|
||||
{project.provider}
|
||||
</span>
|
||||
{project.externalModel?.adapterId ? (
|
||||
<span className="rounded-full bg-muted px-2 py-1 font-mono text-[10px] uppercase tracking-[0.14em] text-muted-foreground">
|
||||
{project.externalModel.adapterId}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</button>
|
||||
<div className="mt-3 text-[11px] text-muted-foreground">
|
||||
|
||||
@@ -161,6 +161,7 @@ export const createSelfHostedImageTo3dTask = async (params: {
|
||||
sourceImage: StudioSourceImageRecord;
|
||||
prompt: string;
|
||||
mode: StudioWorldGenerationMode;
|
||||
adapterId?: string | null;
|
||||
}) => {
|
||||
const { baseUrl, apiKey } = resolveSelfHostedProviderConfig();
|
||||
const payload = {
|
||||
@@ -184,6 +185,11 @@ export const createSelfHostedImageTo3dTask = async (params: {
|
||||
texture_prompt: params.prompt.trim().slice(0, 600),
|
||||
}
|
||||
: {}),
|
||||
...(params.adapterId?.trim()
|
||||
? {
|
||||
adapter_id: params.adapterId.trim(),
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
const response = await fetch(`${baseUrl}/image-to-3d`, {
|
||||
method: "POST",
|
||||
|
||||
@@ -95,6 +95,8 @@ export type StudioWorldDraft = {
|
||||
assets: StudioWorldAssetDraft[];
|
||||
};
|
||||
|
||||
export type StudioWorkerAdapterKind = "portrait_volume" | "heightfield_relief";
|
||||
|
||||
export type StudioGenerationInput = {
|
||||
name: string;
|
||||
prompt: string;
|
||||
@@ -105,6 +107,7 @@ export type StudioGenerationInput = {
|
||||
sourceImage?: StudioSourceImageRecord | null;
|
||||
imageMode?: "avatar" | "mesh";
|
||||
provider?: StudioWorldGenerationProvider;
|
||||
adapterId?: StudioWorkerAdapterKind | null;
|
||||
};
|
||||
|
||||
export type StudioGenerationJobRecord = {
|
||||
@@ -126,9 +129,13 @@ export type StudioExternalModelRecord = {
|
||||
taskId: string;
|
||||
status: "pending" | "in_progress" | "completed" | "failed";
|
||||
progress: number;
|
||||
adapterId?: StudioWorkerAdapterKind | null;
|
||||
glbUrl?: string | null;
|
||||
thumbnailUrl?: string | null;
|
||||
textureUrls?: Array<Record<string, string>>;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
palette?: string[];
|
||||
errorMessage?: string | null;
|
||||
usingTestMode?: boolean;
|
||||
};
|
||||
@@ -139,6 +146,8 @@ export type StudioProviderAvailability = {
|
||||
configured: boolean;
|
||||
usingTestMode?: boolean;
|
||||
message?: string;
|
||||
adapterIds?: StudioWorkerAdapterKind[];
|
||||
defaultAdapterId?: StudioWorkerAdapterKind | null;
|
||||
};
|
||||
|
||||
export type StudioProjectRecord = {
|
||||
|
||||
@@ -47,6 +47,7 @@ describe("studio AI worker contract", () => {
|
||||
body: JSON.stringify({
|
||||
image_url: `data:image/png;base64,${ONE_BY_ONE_PNG.toString("base64")}`,
|
||||
model_type: "standard",
|
||||
adapter_id: "portrait-volume",
|
||||
texture_prompt: "portrait",
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user