Added initial progress on textInference
This commit is contained in:
+13
@@ -42,6 +42,11 @@ from .modules.providerSettings.briaProviderMask import RunwareBriaProviderMask
|
||||
from .modules.audioInference import RunwareAudioInference
|
||||
from .modules.audioInferenceSpeech import RunwareAudioInferenceSpeech
|
||||
from .modules.audioModelSearch import RunwareAudioModelSearch
|
||||
from .modules.textModelSearch import RunwareTextModelSearch
|
||||
from .modules.textInferenceMessages import RunwareTextInferenceMessages
|
||||
from .modules.textInferenceMessage import RunwareTextInferenceMessage
|
||||
from .modules.textInference import RunwareTextInference
|
||||
from .modules.textInferenceSettings import RunwareTextInferenceSettings
|
||||
from .modules.audioSections import RunwareAudioSections
|
||||
from .modules.audioInferenceInputs import audioInferenceInputs
|
||||
from .modules.audioSettings import RunwareAudioSettings
|
||||
@@ -96,6 +101,7 @@ from .modules.threeDInferenceSettingsSparseStructure import Runware3DInferenceSe
|
||||
from .modules.threeDInferenceSettingsShapeSlat import Runware3DInferenceSettingsShapeSlat
|
||||
from .modules.threeDInferenceSettingsTexSlat import Runware3DInferenceSettingsTexSlat
|
||||
from .modules.save3D import RunwareSave3D
|
||||
from .modules.saveText import RunwareSaveText
|
||||
from .modules.loadMesh import RunwareLoadMesh
|
||||
|
||||
RUNWARE_COMFYUI_VERSION = "1.4.0 Beta"
|
||||
@@ -151,6 +157,11 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Runware Audio Inference": RunwareAudioInference,
|
||||
"Runware Audio Inference Speech": RunwareAudioInferenceSpeech,
|
||||
"Runware Audio Model Search": RunwareAudioModelSearch,
|
||||
"Runware Text Model Search": RunwareTextModelSearch,
|
||||
"Runware Text Inference Messages": RunwareTextInferenceMessages,
|
||||
"Runware Text Inference Message": RunwareTextInferenceMessage,
|
||||
"Runware Text Inference": RunwareTextInference,
|
||||
"Runware Text Inference Settings": RunwareTextInferenceSettings,
|
||||
"Runware Audio Sections": RunwareAudioSections,
|
||||
"Runware Audio Inference Inputs": audioInferenceInputs,
|
||||
"Runware Audio Inference Settings": RunwareAudioSettings,
|
||||
@@ -207,6 +218,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Runware 3D Inference Settings Shape Slat": Runware3DInferenceSettingsShapeSlat,
|
||||
"Runware 3D Inference Settings Tex Slat": Runware3DInferenceSettingsTexSlat,
|
||||
"Runware Save 3D": RunwareSave3D,
|
||||
"Runware Save Text": RunwareSaveText,
|
||||
"Runware Load Mesh": RunwareLoadMesh,
|
||||
}
|
||||
|
||||
@@ -218,6 +230,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"Runware Multi Inference": "Runware Multi Inference [BETA]",
|
||||
"Runware Video Model Search": "Runware Video Model",
|
||||
"Runware Audio Model Search": "Runware Audio Model",
|
||||
"Runware Text Model Search": "Runware Text Model",
|
||||
"Runware Video Transcription": "Runware Video Caption",
|
||||
}
|
||||
|
||||
|
||||
+6
-1
@@ -1,6 +1,6 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, mediaUUIDHandler, save3DFilepathHandler, videoTranscriptionHandler, videoOutputsHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, vectorizeModelSearchFilterHandler, vectorizeToggleHandler, useParameterToggleHandler, imageInferenceToggleHandler, imageInferenceAdvancedFeaturesToggleHandler, watermarkAdvancedFeatureToggleHandler, videoInferenceSpeechInputToggleHandler, regionalPromptingRegionsToggleHandler, upscalerToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, audioInferenceSpeechToggleHandler, audioSettingsToggleHandler, videoSettingsToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, xaiProviderSettingsToggleHandler, viduProviderSettingsToggleHandler, sourcefulProviderSettingsToggleHandler, sourcefulProviderSettingsFontsToggleHandler, threeDInferenceToggleHandler, threeDInferenceSettingsToggleHandler, threeDInferenceSettingsLatToggleHandler, ultralyticsProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, mireloProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, audioInputToggleHandler, speechInputToggleHandler, briaProviderMaskToggleHandler, wanAnimateAdvancedFeatureSettingsToggleHandler, videoAdvancedFeatureInputsToggleHandler, audioInferenceInputsToggleHandler } from "./utils.js";
|
||||
import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, saveTextHandler, mediaUUIDHandler, save3DFilepathHandler, videoTranscriptionHandler, videoOutputsHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, textModelSearchFilterHandler, vectorizeModelSearchFilterHandler, vectorizeToggleHandler, useParameterToggleHandler, imageInferenceToggleHandler, imageInferenceAdvancedFeaturesToggleHandler, watermarkAdvancedFeatureToggleHandler, videoInferenceSpeechInputToggleHandler, regionalPromptingRegionsToggleHandler, upscalerToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, audioInferenceSpeechToggleHandler, audioSettingsToggleHandler, textInferenceSettingsToggleHandler, videoSettingsToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, xaiProviderSettingsToggleHandler, viduProviderSettingsToggleHandler, sourcefulProviderSettingsToggleHandler, sourcefulProviderSettingsFontsToggleHandler, threeDInferenceToggleHandler, threeDInferenceSettingsToggleHandler, threeDInferenceSettingsLatToggleHandler, ultralyticsProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, mireloProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, audioInputToggleHandler, speechInputToggleHandler, briaProviderMaskToggleHandler, wanAnimateAdvancedFeatureSettingsToggleHandler, videoAdvancedFeatureInputsToggleHandler, audioInferenceInputsToggleHandler } from "./utils.js";
|
||||
import { RUNWARE_NODE_TYPES, RUNWARE_NODE_PROPS, SEARCH_TERMS } from "./types.js";
|
||||
|
||||
const nodeInitList = [];
|
||||
@@ -9,6 +9,7 @@ app.registerExtension({
|
||||
async setup() {
|
||||
api.addEventListener('runwareError', handleCustomErrors);
|
||||
api.addEventListener('runwareImageCaption', captionNodeHandler);
|
||||
api.addEventListener('runwareSaveText', saveTextHandler);
|
||||
api.addEventListener('runwareMediaUUID', mediaUUIDHandler);
|
||||
api.addEventListener('runwareSave3DFilepath', save3DFilepathHandler);
|
||||
api.addEventListener('runwareVideoTranscription', videoTranscriptionHandler);
|
||||
@@ -90,6 +91,8 @@ app.registerExtension({
|
||||
audioInferenceSpeechToggleHandler(node);
|
||||
} else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOSETTINGS) {
|
||||
audioSettingsToggleHandler(node);
|
||||
} else if(nodeClass === RUNWARE_NODE_TYPES.TEXTINFERENCESETTINGS) {
|
||||
textInferenceSettingsToggleHandler(node);
|
||||
} else if(nodeClass === RUNWARE_NODE_TYPES.VIDEOSETTINGS) {
|
||||
videoSettingsToggleHandler(node);
|
||||
} else if(nodeClass === RUNWARE_NODE_TYPES.ACCELERATOROPTIONS) {
|
||||
@@ -159,6 +162,8 @@ app.registerExtension({
|
||||
videoModelSearchFilterHandler(node);
|
||||
} else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOMODELSEARCH) {
|
||||
audioModelSearchFilterHandler(node);
|
||||
} else if(nodeClass === RUNWARE_NODE_TYPES.TEXTMODELSEARCH) {
|
||||
textModelSearchFilterHandler(node);
|
||||
} else if(nodeClass === RUNWARE_NODE_TYPES.VECTORIZE) {
|
||||
vectorizeModelSearchFilterHandler(node);
|
||||
vectorizeToggleHandler(node);
|
||||
|
||||
@@ -82,6 +82,11 @@ const RUNWARE_NODE_TYPES = {
|
||||
AUDIOINFERENCE: "Runware Audio Inference",
|
||||
AUDIOINFERENCESPEECH: "Runware Audio Inference Speech",
|
||||
AUDIOMODELSEARCH: "Runware Audio Model Search",
|
||||
TEXTMODELSEARCH: "Runware Text Model Search",
|
||||
TEXTINFERENCE: "Runware Text Inference",
|
||||
TEXTINFERENCEMESSAGES: "Runware Text Inference Messages",
|
||||
TEXTINFERENCEMESSAGE: "Runware Text Inference Message",
|
||||
TEXTINFERENCESETTINGS: "Runware Text Inference Settings",
|
||||
AUDIOSECTIONS: "Runware Audio Sections",
|
||||
AUDIOINFERENCEINPUTS: "Runware Audio Inference Inputs",
|
||||
AUDIOSETTINGS: "Runware Audio Inference Settings",
|
||||
@@ -136,6 +141,7 @@ const RUNWARE_NODE_TYPES = {
|
||||
VIDEOADVANCEDFEATUREINPUTS: "Runware Video Advanced Feature Inputs",
|
||||
WANANIMATEADVANCEDFEATURESETTINGS: "Runware Wan Animate Advanced Feature Settings",
|
||||
SAVEIMAGE: "Runware Save Image",
|
||||
SAVETEXT: "Runware Save Text",
|
||||
VIDEOINFERENCEOUTPUTS: "Runware Video Inference Outputs",
|
||||
THREEDINFERENCE: "Runware 3D Inference",
|
||||
THREEDINFERENCEINPUTS: "Runware 3D Inference Inputs",
|
||||
@@ -285,6 +291,25 @@ const RUNWARE_NODE_PROPS = {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
liveSearch: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.TEXTMODELSEARCH]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
liveSearch: false,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.TEXTINFERENCE]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.TEXTINFERENCEMESSAGES]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.TEXTINFERENCEMESSAGE]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.TEXTINFERENCESETTINGS]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.AUDIOSECTIONS]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
@@ -513,6 +538,10 @@ const RUNWARE_NODE_PROPS = {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.SAVETEXT]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.VIDEOINFERENCEOUTPUTS]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
|
||||
@@ -113,6 +113,28 @@ function captionNodeHandler(msgEvent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function saveTextHandler(msgEvent) {
|
||||
const data = msgEvent.detail;
|
||||
const displayText = data.text;
|
||||
const nodeID = parseInt(data.nodeID, 10);
|
||||
if (!data.success || Number.isNaN(nodeID)) return false;
|
||||
const node = app.graph.getNodeById(nodeID);
|
||||
if (node === null || node === undefined) return false;
|
||||
const widgetName = data.widgetName || "text";
|
||||
const textWidget = node.widgets.find((w) => w.name === widgetName);
|
||||
if (textWidget) {
|
||||
if (textWidget.value !== undefined) {
|
||||
textWidget.value = displayText;
|
||||
}
|
||||
if (textWidget.inputEl) {
|
||||
textWidget.inputEl.value = displayText;
|
||||
textWidget.inputEl.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
textWidget.inputEl.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function videoTranscriptionHandler(msgEvent) {
|
||||
const transcriptionData = msgEvent.detail;
|
||||
const transcriptionText = transcriptionData.transcriptionText;
|
||||
@@ -1169,6 +1191,43 @@ function audioSettingsToggleHandler(settingsNode) {
|
||||
if (useTextNormalizationWidget && textNormalizationWidget) toggleWidgetState(useTextNormalizationWidget, textNormalizationWidget, "textNormalization");
|
||||
}
|
||||
|
||||
function textInferenceSettingsToggleHandler(settingsNode) {
|
||||
if (!settingsNode?.widgets) return;
|
||||
|
||||
const useSystemPromptWidget = settingsNode.widgets.find(w => w && w.name === "useSystemPrompt");
|
||||
const systemWidget = settingsNode.widgets.find(w => w && w.name === "system");
|
||||
const useMaxTokensWidget = settingsNode.widgets.find(w => w && w.name === "useMaxTokens");
|
||||
const maxTokensWidget = settingsNode.widgets.find(w => w && w.name === "maxTokens");
|
||||
const useTemperatureWidget = settingsNode.widgets.find(w => w && w.name === "useTemperature");
|
||||
const temperatureWidget = settingsNode.widgets.find(w => w && w.name === "temperature");
|
||||
const useTopPWidget = settingsNode.widgets.find(w => w && w.name === "useTopP");
|
||||
const topPWidget = settingsNode.widgets.find(w => w && w.name === "topP");
|
||||
const useTopKWidget = settingsNode.widgets.find(w => w && w.name === "useTopK");
|
||||
const topKWidget = settingsNode.widgets.find(w => w && w.name === "topK");
|
||||
|
||||
function toggleWidgetState(useWidget, paramWidget) {
|
||||
if (!useWidget || !paramWidget) return;
|
||||
function applyState() {
|
||||
const enabled = useWidget.value === true;
|
||||
toggleWidgetEnabled(paramWidget, enabled, settingsNode);
|
||||
if (paramWidget.options && paramWidget.options.element) {
|
||||
paramWidget.options.element.disabled = !enabled;
|
||||
paramWidget.options.element.style.opacity = enabled ? "1" : "0.5";
|
||||
paramWidget.options.element.style.pointerEvents = enabled ? "auto" : "none";
|
||||
}
|
||||
settingsNode.setDirtyCanvas(true);
|
||||
}
|
||||
setTimeout(applyState, 100);
|
||||
appendWidgetCB(useWidget, () => setTimeout(applyState, 50));
|
||||
}
|
||||
|
||||
if (useSystemPromptWidget && systemWidget) toggleWidgetState(useSystemPromptWidget, systemWidget);
|
||||
if (useMaxTokensWidget && maxTokensWidget) toggleWidgetState(useMaxTokensWidget, maxTokensWidget);
|
||||
if (useTemperatureWidget && temperatureWidget) toggleWidgetState(useTemperatureWidget, temperatureWidget);
|
||||
if (useTopPWidget && topPWidget) toggleWidgetState(useTopPWidget, topPWidget);
|
||||
if (useTopKWidget && topKWidget) toggleWidgetState(useTopKWidget, topKWidget);
|
||||
}
|
||||
|
||||
function videoSettingsToggleHandler(settingsNode) {
|
||||
if (!settingsNode?.widgets) return;
|
||||
|
||||
@@ -2748,6 +2807,46 @@ function videoModelSearchFilterHandler(videoModelSearchNode) {
|
||||
updateDimensions();
|
||||
}
|
||||
|
||||
function textModelSearchFilterHandler(textModelSearchNode) {
|
||||
const modelProviderWidget = textModelSearchNode.widgets.find(w => w.name === "Model Provider");
|
||||
const textListWidget = textModelSearchNode.widgets.find(w => w.name === "TextList");
|
||||
|
||||
if (!modelProviderWidget || !textListWidget) return;
|
||||
|
||||
const TEXT_MODELS = {
|
||||
"MiniMax": [
|
||||
"minimax:m2.7@0 (MiniMax-M2.7)",
|
||||
"minimax:m2.7@highspeed (MiniMax-M2.7 Highspeed)",
|
||||
"minimax:m2.5@0 (MiniMax-M2.5)",
|
||||
],
|
||||
};
|
||||
|
||||
function filterModelList() {
|
||||
const selectedProvider = modelProviderWidget.value;
|
||||
let filteredModels = [];
|
||||
|
||||
if (selectedProvider === "All") {
|
||||
Object.values(TEXT_MODELS).forEach(models => filteredModels.push(...models));
|
||||
} else if (TEXT_MODELS[selectedProvider]) {
|
||||
filteredModels = TEXT_MODELS[selectedProvider];
|
||||
}
|
||||
|
||||
if (filteredModels.length > 0) {
|
||||
const currentValue = textListWidget.value;
|
||||
textListWidget.options.values = filteredModels;
|
||||
|
||||
if (!filteredModels.includes(currentValue)) {
|
||||
textListWidget.value = filteredModels[0];
|
||||
}
|
||||
|
||||
textModelSearchNode.setDirtyCanvas(true);
|
||||
}
|
||||
}
|
||||
|
||||
appendWidgetCB(modelProviderWidget, filterModelList);
|
||||
filterModelList();
|
||||
}
|
||||
|
||||
function audioModelSearchFilterHandler(audioModelSearchNode) {
|
||||
const modelProviderWidget = audioModelSearchNode.widgets.find(w => w.name === "Model Provider");
|
||||
const audioListWidget = audioModelSearchNode.widgets.find(w => w.name === "AudioList");
|
||||
@@ -4193,6 +4292,7 @@ export {
|
||||
mediaUUIDHandler,
|
||||
save3DFilepathHandler,
|
||||
captionNodeHandler,
|
||||
saveTextHandler,
|
||||
videoTranscriptionHandler,
|
||||
videoOutputsHandler,
|
||||
handleCustomErrors,
|
||||
@@ -4200,6 +4300,7 @@ export {
|
||||
videoInferenceDimensionsHandler,
|
||||
videoModelSearchFilterHandler,
|
||||
audioModelSearchFilterHandler,
|
||||
textModelSearchFilterHandler,
|
||||
vectorizeModelSearchFilterHandler,
|
||||
vectorizeToggleHandler,
|
||||
useParameterToggleHandler,
|
||||
@@ -4213,6 +4314,7 @@ export {
|
||||
audioInferenceToggleHandler,
|
||||
audioInferenceSpeechToggleHandler,
|
||||
audioSettingsToggleHandler,
|
||||
textInferenceSettingsToggleHandler,
|
||||
videoSettingsToggleHandler,
|
||||
acceleratorOptionsToggleHandler,
|
||||
bytedanceProviderSettingsToggleHandler,
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
|
||||
class RunwareSaveText:
|
||||
"""Show text in the node (e.g. from Runware Text Inference) and forward it for downstream nodes."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"tooltip": "Connect the 'text' output from Runware Text Inference. Text is mirrored here after run.",
|
||||
}),
|
||||
},
|
||||
"hidden": {"node_id": "UNIQUE_ID"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "forward_text"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "Runware"
|
||||
DESCRIPTION = "Display text in the node and forward the same string to other nodes."
|
||||
|
||||
def forward_text(self, text, **kwargs):
|
||||
if text is None:
|
||||
text = ""
|
||||
elif not isinstance(text, str):
|
||||
text = str(text)
|
||||
|
||||
node_id = kwargs.get("node_id")
|
||||
if node_id is not None:
|
||||
rwUtils.sendSaveText(text, node_id)
|
||||
|
||||
return (text,)
|
||||
@@ -0,0 +1,102 @@
|
||||
import uuid
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
|
||||
class RunwareTextInference:
|
||||
"""Runware textInference API: chat completion via selected text model and messages."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"model": ("RUNWARETEXTMODEL", {
|
||||
"tooltip": "Connect Runware Text Model Search output.",
|
||||
}),
|
||||
"messages": ("RUNWARETEXTINFERENCEMESSAGES", {
|
||||
"tooltip": "Connect Runware Text Inference Messages output.",
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"settings": ("RUNWARETEXTINFERENCESETTINGS", {
|
||||
"tooltip": "Connect Runware Text Inference Settings (setting + thinking).",
|
||||
}),
|
||||
"providerSettings": ("RUNWAREPROVIDERSETTINGS", {
|
||||
"tooltip": "Optional provider-specific settings (wrapped by provider prefix from the model AIR).",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "infer_text"
|
||||
CATEGORY = "Runware/Text"
|
||||
DESCRIPTION = "Run text/chat inference using Runware textInference. Connect Text Model and Text Inference Messages."
|
||||
|
||||
@staticmethod
|
||||
def _extract_text_from_data_item(item: Dict[str, Any]) -> Optional[str]:
|
||||
if not isinstance(item, dict):
|
||||
return None
|
||||
if "text" in item and item["text"] is not None:
|
||||
return str(item["text"])
|
||||
if "content" in item and isinstance(item["content"], str):
|
||||
return item["content"]
|
||||
choices = item.get("choices")
|
||||
if isinstance(choices, list) and len(choices) > 0:
|
||||
ch0 = choices[0]
|
||||
if isinstance(ch0, dict):
|
||||
msg = ch0.get("message")
|
||||
if isinstance(msg, dict) and "content" in msg:
|
||||
return str(msg["content"])
|
||||
if "text" in ch0:
|
||||
return str(ch0["text"])
|
||||
return None
|
||||
|
||||
def _finalize_text(self, gen_result: Dict[str, Any]) -> str:
|
||||
if "data" not in gen_result or not gen_result["data"]:
|
||||
raise Exception("No data in text inference response.")
|
||||
item = gen_result["data"][0]
|
||||
text = self._extract_text_from_data_item(item)
|
||||
if text is None:
|
||||
raise Exception(
|
||||
"Could not read assistant text from response. "
|
||||
f"Keys on data[0]: {list(item.keys()) if isinstance(item, dict) else type(item)}"
|
||||
)
|
||||
return text
|
||||
|
||||
def infer_text(self, **kwargs):
|
||||
model = (kwargs.get("model") or "").strip()
|
||||
messages: List[Dict[str, str]] = kwargs.get("messages") or []
|
||||
text_settings = kwargs.get("settings")
|
||||
provider_settings = kwargs.get("providerSettings")
|
||||
|
||||
if not model:
|
||||
raise Exception("model is empty.")
|
||||
if not messages:
|
||||
raise Exception("messages are empty.")
|
||||
|
||||
task_uuid = str(uuid.uuid4())
|
||||
task: Dict[str, Any] = {
|
||||
"taskType": "textInference",
|
||||
"taskUUID": task_uuid,
|
||||
"model": model,
|
||||
"deliveryMethod": "sync",
|
||||
"messages": messages,
|
||||
"includeCost": True,
|
||||
}
|
||||
|
||||
if text_settings is not None and isinstance(text_settings, dict) and len(text_settings) > 0:
|
||||
task["settings"] = text_settings
|
||||
|
||||
if provider_settings is not None and isinstance(provider_settings, dict) and len(provider_settings) > 0:
|
||||
provider_name = model.split(":")[0] if ":" in model else model
|
||||
task["providerSettings"] = {provider_name: provider_settings}
|
||||
|
||||
gen_config = [task]
|
||||
print(f"[Runware Text Inference] Request: {rwUtils.safe_json_dumps(gen_config, indent=2)}")
|
||||
|
||||
gen_result = rwUtils.inferenecRequest(gen_config)
|
||||
print(f"[Runware Text Inference] Response: {rwUtils.safe_json_dumps(gen_result, indent=2)}")
|
||||
|
||||
return (self._finalize_text(gen_result),)
|
||||
@@ -0,0 +1,33 @@
|
||||
class RunwareTextInferenceMessage:
|
||||
"""One chat message (role + content) for Runware Text Inference Messages combiner."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"role": (["user", "assistant"], {
|
||||
"default": "user",
|
||||
"tooltip": "Speaker role for this message.",
|
||||
}),
|
||||
"content": ("STRING", {
|
||||
"multiline": True,
|
||||
"default": "What's Photosynthesis?",
|
||||
"tooltip": "Message text sent to the model.",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("RUNWARETEXTINFERENCEMESSAGE",)
|
||||
RETURN_NAMES = ("message",)
|
||||
FUNCTION = "create_message"
|
||||
CATEGORY = "Runware/Text"
|
||||
DESCRIPTION = (
|
||||
"One chat message (role + content). Connect to Runware Text Inference Messages."
|
||||
)
|
||||
|
||||
def create_message(self, role: str, content: str):
|
||||
r = (role or "user").strip().lower()
|
||||
if r not in ("user", "assistant"):
|
||||
r = "user"
|
||||
body = (content or "").strip()
|
||||
return ({"role": r, "content": body},)
|
||||
@@ -0,0 +1,58 @@
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
class RunwareTextInferenceMessages:
|
||||
"""Merge Runware Text Inference Message segments into the API `messages` array (order = segment 1 … N)."""
|
||||
|
||||
_SEGMENT_COUNT = 8
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
optional = {}
|
||||
for i in range(2, cls._SEGMENT_COUNT + 1):
|
||||
optional[f"Message Segment {i}"] = (
|
||||
"RUNWARETEXTINFERENCEMESSAGE",
|
||||
{"tooltip": f"Optional message {i}. Connect Runware Text Inference Message."},
|
||||
)
|
||||
return {
|
||||
"required": {
|
||||
"Message Segment 1": ("RUNWARETEXTINFERENCEMESSAGE", {
|
||||
"tooltip": "First message. Connect Runware Text Inference Message.",
|
||||
}),
|
||||
},
|
||||
"optional": optional,
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("RUNWARETEXTINFERENCEMESSAGES",)
|
||||
RETURN_NAMES = ("messages",)
|
||||
FUNCTION = "merge_messages"
|
||||
CATEGORY = "Runware/Text"
|
||||
DESCRIPTION = (
|
||||
"Combine one or more Runware Text Inference Message nodes into a messages list for Runware Text Inference "
|
||||
"(same idea as Kling MultiPrompt Segment → Kling Provider Settings MultiPrompt)."
|
||||
)
|
||||
|
||||
def merge_messages(self, **kwargs) -> tuple:
|
||||
messages: List[Dict[str, str]] = []
|
||||
|
||||
for i in range(1, self._SEGMENT_COUNT + 1):
|
||||
key = f"Message Segment {i}"
|
||||
seg = kwargs.get(key)
|
||||
if seg is None:
|
||||
continue
|
||||
if not isinstance(seg, dict):
|
||||
continue
|
||||
content = (seg.get("content") or "").strip()
|
||||
if not content:
|
||||
continue
|
||||
role = seg.get("role", "user")
|
||||
if role not in ("user", "assistant"):
|
||||
role = "user"
|
||||
messages.append({"role": role, "content": content})
|
||||
|
||||
if not messages:
|
||||
raise Exception(
|
||||
"No messages: connect Runware Text Inference Message to Message Segment 1 with non-empty content."
|
||||
)
|
||||
|
||||
return (messages,)
|
||||
@@ -0,0 +1,113 @@
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
|
||||
class RunwareTextInferenceSettings:
|
||||
"""Optional generation controls for Runware Text Inference (flat task.settings per API)."""
|
||||
|
||||
_MAX_SYSTEM_CHARS = 200000
|
||||
_MAX_TOKENS_CAP = 196608
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {},
|
||||
"optional": {
|
||||
"useSystemPrompt": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Include systemPrompt in settings.",
|
||||
}),
|
||||
"system": ("STRING", {
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"tooltip": "System instruction (max 200,000 characters). Sent as systemPrompt.",
|
||||
}),
|
||||
"useMaxTokens": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Include maxTokens in settings.",
|
||||
}),
|
||||
"maxTokens": ("INT", {
|
||||
"default": 32768,
|
||||
"min": 1,
|
||||
"max": cls._MAX_TOKENS_CAP,
|
||||
"step": 1,
|
||||
"tooltip": f"maxTokens (1–{cls._MAX_TOKENS_CAP}).",
|
||||
}),
|
||||
"useTemperature": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Include temperature in settings.",
|
||||
}),
|
||||
"temperature": ("FLOAT", {
|
||||
"default": 1.0,
|
||||
"min": 0.01,
|
||||
"max": 1.0,
|
||||
"step": 0.01,
|
||||
"tooltip": "Sampling temperature on (0, 1].",
|
||||
}),
|
||||
"useTopP": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Include topP in settings.",
|
||||
}),
|
||||
"topP": ("FLOAT", {
|
||||
"default": 0.95,
|
||||
"min": 0.0,
|
||||
"max": 1.0,
|
||||
"step": 0.01,
|
||||
"tooltip": "Nucleus sampling (0.0–1.0).",
|
||||
}),
|
||||
"useTopK": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Include topK in settings.",
|
||||
}),
|
||||
"topK": ("INT", {
|
||||
"default": 40,
|
||||
"min": 1,
|
||||
"max": 128,
|
||||
"step": 1,
|
||||
"tooltip": "Top-k sampling (1–128).",
|
||||
}),
|
||||
"thinkingLevel": (["off", "minimal", "low", "medium", "high"], {
|
||||
"default": "off",
|
||||
"tooltip": "thinkingLevel in settings (omit when off).",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("RUNWARETEXTINFERENCESETTINGS",)
|
||||
RETURN_NAMES = ("settings",)
|
||||
FUNCTION = "create_settings"
|
||||
CATEGORY = "Runware/Text"
|
||||
DESCRIPTION = (
|
||||
"Optional text inference settings (flat: systemPrompt, maxTokens, temperature, topP, topK, thinkingLevel). "
|
||||
"Connect to Runware Text Inference."
|
||||
)
|
||||
|
||||
def create_settings(self, **kwargs) -> Tuple[Dict[str, Any], ...]:
|
||||
out: Dict[str, Any] = {}
|
||||
|
||||
if kwargs.get("useSystemPrompt", False):
|
||||
sys_text = (kwargs.get("system") or "").strip()
|
||||
if sys_text:
|
||||
if len(sys_text) > self._MAX_SYSTEM_CHARS:
|
||||
sys_text = sys_text[: self._MAX_SYSTEM_CHARS]
|
||||
out["systemPrompt"] = sys_text
|
||||
|
||||
if kwargs.get("useMaxTokens", False):
|
||||
out["maxTokens"] = int(kwargs.get("maxTokens", 32768))
|
||||
|
||||
if kwargs.get("useTemperature", False):
|
||||
out["temperature"] = float(kwargs.get("temperature", 1.0))
|
||||
|
||||
if kwargs.get("useTopP", False):
|
||||
out["topP"] = float(kwargs.get("topP", 0.95))
|
||||
|
||||
if kwargs.get("useTopK", False):
|
||||
out["topK"] = int(kwargs.get("topK", 40))
|
||||
|
||||
tl = kwargs.get("thinkingLevel", "off")
|
||||
if tl != "off":
|
||||
out["thinkingLevel"] = tl
|
||||
|
||||
if not out:
|
||||
return ({},)
|
||||
|
||||
return (out,)
|
||||
@@ -0,0 +1,73 @@
|
||||
class RunwareTextModelSearch:
|
||||
"""Text model search node: pick or type a text/chat AIR model for Runware Text Inference."""
|
||||
|
||||
TEXT_MODELS = {
|
||||
"MiniMax": [
|
||||
"minimax:m2.7@0 (MiniMax-M2.7)",
|
||||
"minimax:m2.7@highspeed (MiniMax-M2.7 Highspeed)",
|
||||
"minimax:m2.5@0 (MiniMax-M2.5)",
|
||||
],
|
||||
}
|
||||
|
||||
MODEL_PROVIDERS = [
|
||||
"All",
|
||||
"MiniMax",
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
all_models = cls._get_all_models()
|
||||
default_model = "minimax:m2.7@0 (MiniMax-M2.7)"
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"Model Search": ("STRING", {
|
||||
"tooltip": "Search or type a text model AIR code (e.g. minimax:m2.7@0). Used when 'Use Search Value' is enabled.",
|
||||
}),
|
||||
"Model Provider": (cls.MODEL_PROVIDERS, {
|
||||
"tooltip": "Filter the model list by provider.",
|
||||
"default": "MiniMax",
|
||||
}),
|
||||
"TextList": (all_models, {
|
||||
"tooltip": "Choose a model from the filtered list (AIR code is taken before the first space).",
|
||||
"default": default_model,
|
||||
}),
|
||||
"Use Search Value": ("BOOLEAN", {
|
||||
"tooltip": "When enabled, 'Model Search' is used as the model AIR instead of the selection in TextList.",
|
||||
"default": False,
|
||||
"label_on": "Enabled",
|
||||
"label_off": "Disabled",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _get_all_models(cls):
|
||||
all_models = []
|
||||
for models in cls.TEXT_MODELS.values():
|
||||
all_models.extend(models)
|
||||
return all_models
|
||||
|
||||
RETURN_TYPES = ("RUNWARETEXTMODEL",)
|
||||
RETURN_NAMES = ("model",)
|
||||
FUNCTION = "searchModels"
|
||||
CATEGORY = "Runware"
|
||||
DESCRIPTION = "Search and select text/chat models for Runware Text Inference."
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, TextList):
|
||||
return True
|
||||
|
||||
def searchModels(self, **kwargs):
|
||||
use_search = kwargs.get("Use Search Value", False)
|
||||
search_input = (kwargs.get("Model Search") or "").strip()
|
||||
|
||||
if use_search:
|
||||
if not search_input:
|
||||
raise Exception("Use Search Value is enabled but Model Search is empty.")
|
||||
model_air = search_input
|
||||
else:
|
||||
current = kwargs.get("TextList")
|
||||
model_air = current.split(" (")[0] if current else ""
|
||||
|
||||
return (model_air,)
|
||||
@@ -318,6 +318,19 @@ def sendImageCaption(captionText, nodeID):
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def sendSaveText(text, nodeID):
|
||||
"""Push text into Runware Save Text node's textarea (ComfyUI does not mirror linked STRING inputs visually)."""
|
||||
PromptServer.instance.send_sync(
|
||||
"runwareSaveText",
|
||||
{
|
||||
"success": True,
|
||||
"text": text if text is not None else "",
|
||||
"nodeID": nodeID,
|
||||
"widgetName": "text",
|
||||
},
|
||||
)
|
||||
|
||||
def sendVideoTranscription(transcriptionText, nodeID):
|
||||
PromptServer.instance.send_sync(
|
||||
"runwareVideoTranscription",
|
||||
@@ -698,7 +711,7 @@ def convertVideoB64List(videoDataObject, width=None, height=None):
|
||||
return videos
|
||||
|
||||
def pollVideoResult(taskUUID):
|
||||
"""Poll for video generation result using task UUID with getResponse"""
|
||||
"""Poll async task result with taskType getResponse (video, audio, text inference, etc.)."""
|
||||
global RUNWARE_API_KEY, RUNWARE_API_BASE_URL, SESSION_TIMEOUT
|
||||
|
||||
pollConfig = [
|
||||
@@ -740,3 +753,7 @@ def pollVideoResult(taskUUID):
|
||||
except Exception as e:
|
||||
print(f"[Debugging] Poll request failed: {str(e)}")
|
||||
return None
|
||||
|
||||
|
||||
# Alias: getResponse is the generic async poll for any task type.
|
||||
pollGetResponse = pollVideoResult
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user