diff --git a/js/msr_character.js b/js/msr_character.js index 15069e7..7663159 100644 --- a/js/msr_character.js +++ b/js/msr_character.js @@ -60,7 +60,22 @@ function resolveCandidateImageUrls(originNode) { async function imageInputToDataUrl(node, inputName) { const originNode = getOriginNodeForInput(node, inputName); const candidateUrls = resolveCandidateImageUrls(originNode); - if (!candidateUrls.length) return null; + if (!candidateUrls.length) { + return { + dataUrl: null, + debug: { + inputName, + originNodeId: originNode?.id ?? null, + originNodeType: originNode?.type ?? null, + candidateUrls: [], + selectedUrl: null, + mimeType: null, + blobBytes: 0, + dataUrlLength: 0, + error: "No candidate image URLs found", + }, + }; + } for (const imageUrl of candidateUrls) { try { @@ -72,17 +87,44 @@ async function imageInputToDataUrl(node, inputName) { if (!blob.type.startsWith("image/")) { throw new Error(`Unexpected blob type: ${blob.type || "unknown"}`); } - return await new Promise((resolve) => { + const dataUrl = await new Promise((resolve) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.readAsDataURL(blob); }); + return { + dataUrl, + debug: { + inputName, + originNodeId: originNode?.id ?? null, + originNodeType: originNode?.type ?? null, + candidateUrls, + selectedUrl: imageUrl, + mimeType: blob.type || null, + blobBytes: blob.size || 0, + dataUrlLength: typeof dataUrl === "string" ? dataUrl.length : 0, + error: null, + }, + }; } catch (error) { console.warn("[MSRCharacter] Failed candidate image source", imageUrl, error); } } - return null; + return { + dataUrl: null, + debug: { + inputName, + originNodeId: originNode?.id ?? null, + originNodeType: originNode?.type ?? null, + candidateUrls, + selectedUrl: null, + mimeType: null, + blobBytes: 0, + dataUrlLength: 0, + error: "All candidate image URLs failed", + }, + }; } function setWidgetValue(node, widget, value) { @@ -330,11 +372,13 @@ async function analyzeCharacterNode(node, buttonWidget) { node.setDirtyCanvas?.(true, true); try { + const imageResults = await Promise.all([ + imageInputToDataUrl(node, "image_1"), + imageInputToDataUrl(node, "image_2"), + ]); + const imageDebug = imageResults.map((result) => result?.debug || null).filter(Boolean); const images = ( - await Promise.all([ - imageInputToDataUrl(node, "image_1"), - imageInputToDataUrl(node, "image_2"), - ]) + imageResults.map((result) => result?.dataUrl || null) ).filter(Boolean); if (!images.length) { @@ -348,10 +392,15 @@ async function analyzeCharacterNode(node, buttonWidget) { base_url: baseUrlWidget?.value || "", model: modelWidget?.value || "", image_b64: images, + image_debug: imageDebug, }), }); const result = await response.json(); if (result.status !== "success") { + console.warn("[MSRCharacter] analyze debug", { + sentImages: imageDebug, + response: result, + }); throw new Error(result.message || "Unknown analysis error"); } diff --git a/ltx_director.py b/ltx_director.py index f9fb690..acf1449 100644 --- a/ltx_director.py +++ b/ltx_director.py @@ -549,10 +549,11 @@ def _resolve_provider(data): async def analyze_character_endpoint(request): try: import aiohttp - data = await request.json() - image_b64 = data.get("image_b64", "") - char_index = int(data.get("char_index", 0)) - provider, base_url, model_name = _resolve_provider(data) + data = await request.json() + image_b64 = data.get("image_b64", "") + image_debug = data.get("image_debug") or [] + char_index = int(data.get("char_index", 0)) + provider, base_url, model_name = _resolve_provider(data) if provider == "off": return web.json_response({"status": "error", "message": "Analyze is set to Off / Manual."}) @@ -639,6 +640,8 @@ async def analyze_character_endpoint(request): "status": "error", "message": "Ollama returned only a truncated analysis response.", "debug_candidates": debug_candidates, + "image_debug": image_debug, + "image_lengths": [len(b64) for b64 in cleaned_b64_list], "description": generated_text, }) else: