diff --git a/js/msr_character.js b/js/msr_character.js index 957c075..b9f7fc7 100644 --- a/js/msr_character.js +++ b/js/msr_character.js @@ -1,5 +1,6 @@ const { app } = window.comfyAPI.app; const { api } = window.comfyAPI.api; +const DEFAULT_ANALYZE_PROMPT = "Describe the character's physical appearance in two concise sentences. Specify their hair color/style, face details, and their clothing type/color. Keep the entire response very brief."; function findWidget(node, name) { return (node.widgets || []).find((widget) => widget.name === name); @@ -369,15 +370,19 @@ function buildCharacterUi(node) { const providerWidget = findWidget(node, "analyze_provider"); const baseUrlWidget = findWidget(node, "analyze_base_url"); const modelWidget = findWidget(node, "analyze_model"); + const promptWidget = findWidget(node, "analyze_prompt"); const provider = window.prompt("Analyze provider: ollama, lmstudio, custom, off", providerWidget?.value || "ollama"); if (provider == null) return; const baseUrl = window.prompt("Analyze base URL (blank = default)", baseUrlWidget?.value || ""); if (baseUrl == null) return; const model = window.prompt("Analyze model (blank = provider default)", modelWidget?.value || ""); if (model == null) return; + const prompt = window.prompt("Analyze prompt (blank = default)", promptWidget?.value || DEFAULT_ANALYZE_PROMPT); + if (prompt == null) return; setWidgetValue(node, providerWidget, provider.trim() || "ollama"); setWidgetValue(node, baseUrlWidget, baseUrl.trim()); setWidgetValue(node, modelWidget, model.trim()); + setWidgetValue(node, promptWidget, prompt.trim()); syncFormFromWidgets(node); node.setDirtyCanvas?.(true, true); }); @@ -461,6 +466,7 @@ async function analyzeCharacterNode(node, buttonWidget) { const providerWidget = findWidget(node, "analyze_provider"); const baseUrlWidget = findWidget(node, "analyze_base_url"); const modelWidget = findWidget(node, "analyze_model"); + const promptWidget = findWidget(node, "analyze_prompt"); buttonWidget.label = "Analyzing..."; if (node._msrAnalyzeButton) { @@ -489,6 +495,7 @@ async function analyzeCharacterNode(node, buttonWidget) { provider: providerWidget?.value || "ollama", base_url: baseUrlWidget?.value || "", model: modelWidget?.value || "", + prompt: promptWidget?.value || "", image_b64: images, image_debug: imageDebug, }), @@ -560,6 +567,9 @@ app.registerExtension({ if (!findWidget(this, "analyze_model")) { this.addWidget("text", "analyze_model", ""); } + if (!findWidget(this, "analyze_prompt")) { + this.addWidget("text", "analyze_prompt", DEFAULT_ANALYZE_PROMPT); + } if (!this._msrPreviewWidget) { const previewContainer = buildCharacterUi(this); @@ -587,7 +597,7 @@ app.registerExtension({ return result; }; - ["alias", "description", "analyze_provider", "analyze_base_url", "analyze_model"].forEach((widgetName) => { + ["alias", "description", "analyze_provider", "analyze_base_url", "analyze_model", "analyze_prompt"].forEach((widgetName) => { hideNodeWidget(findWidget(this, widgetName)); }); hideNodeWidget((this.widgets || []).find((widget) => widget.msrAnalyze)); diff --git a/ltx_director.py b/ltx_director.py index d3e48e5..48fcbec 100644 --- a/ltx_director.py +++ b/ltx_director.py @@ -512,6 +512,11 @@ _ANALYZE_PROMPT = ( ) +def _resolve_analyze_prompt(data: dict) -> str: + prompt = (data.get("prompt") or "").strip() + return prompt or _ANALYZE_PROMPT + + def _extract_ollama_generated_text(resp_json: dict) -> str: if not isinstance(resp_json, dict): return "" @@ -621,6 +626,7 @@ async def analyze_character_endpoint(request): image_debug = data.get("image_debug") or [] char_index = int(data.get("char_index", 0)) provider, base_url, model_name = _resolve_provider(data) + analyze_prompt = _resolve_analyze_prompt(data) if provider == "off": return web.json_response({"status": "error", "message": "Analyze is set to Off / Manual."}) @@ -652,7 +658,7 @@ async def analyze_character_endpoint(request): payload = { "model": model_name, "system": _ANALYZE_SYSTEM_PROMPT, - "prompt": _ANALYZE_PROMPT, + "prompt": analyze_prompt, "images": analysis_images, "stream": False, "keep_alive": 0, @@ -691,7 +697,7 @@ async def analyze_character_endpoint(request): }, { "role": "user", - "content": _ANALYZE_PROMPT, + "content": analyze_prompt, "images": analysis_images, }, ], @@ -720,7 +726,7 @@ async def analyze_character_endpoint(request): }) else: # OpenAI-compatible vision chat (LM Studio / Custom). - content = [{"type": "text", "text": _ANALYZE_PROMPT}] + content = [{"type": "text", "text": analyze_prompt}] for b64 in cleaned_b64_list: content.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}"}}) payload = {