Added support for Anthropic models
This commit is contained in:
+10
@@ -53,6 +53,11 @@ from .modules.textInferenceMessages import RunwareTextInferenceMessages
|
||||
from .modules.textInferenceMessage import RunwareTextInferenceMessage
|
||||
from .modules.textInference import RunwareTextInference
|
||||
from .modules.textInferenceSettings import RunwareTextInferenceSettings
|
||||
from .modules.textInferenceToolChoice import RunwareTextInferenceToolChoice
|
||||
from .modules.textInferenceSettingsCache import RunwareTextInferenceSettingsCache
|
||||
from .modules.textInferenceInputs import RunwareTextInferenceInputs
|
||||
from .modules.textInferenceInputsImages import RunwareTextInferenceInputsImages
|
||||
from .modules.textInferenceInputsVideos import RunwareTextInferenceInputsVideos
|
||||
from .modules.audioSections import RunwareAudioSections
|
||||
from .modules.audioInferenceInputs import audioInferenceInputs
|
||||
from .modules.audioSettings import RunwareAudioSettings
|
||||
@@ -177,6 +182,11 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Runware Text Inference Message": RunwareTextInferenceMessage,
|
||||
"Runware Text Inference": RunwareTextInference,
|
||||
"Runware Text Inference Settings": RunwareTextInferenceSettings,
|
||||
"Runware Text Inference Tool Choice": RunwareTextInferenceToolChoice,
|
||||
"Runware Text Inference Settings Cache": RunwareTextInferenceSettingsCache,
|
||||
"Runware Text Inference Inputs": RunwareTextInferenceInputs,
|
||||
"Runware Text Inference Inputs Images": RunwareTextInferenceInputsImages,
|
||||
"Runware Text Inference Inputs Videos": RunwareTextInferenceInputsVideos,
|
||||
"Runware Audio Sections": RunwareAudioSections,
|
||||
"Runware Audio Inference Inputs": audioInferenceInputs,
|
||||
"Runware Audio Inference Settings": RunwareAudioSettings,
|
||||
|
||||
@@ -89,6 +89,11 @@ const RUNWARE_NODE_TYPES = {
|
||||
TEXTINFERENCEMESSAGES: "Runware Text Inference Messages",
|
||||
TEXTINFERENCEMESSAGE: "Runware Text Inference Message",
|
||||
TEXTINFERENCESETTINGS: "Runware Text Inference Settings",
|
||||
TEXTINFERENCETOOLCHOICE: "Runware Text Inference Tool Choice",
|
||||
TEXTINFERENCESETTINGSCACHE: "Runware Text Inference Settings Cache",
|
||||
TEXTINFERENCEINPUTS: "Runware Text Inference Inputs",
|
||||
TEXTINFERENCEINPUTSIMAGES: "Runware Text Inference Inputs Images",
|
||||
TEXTINFERENCEINPUTSVIDEOS: "Runware Text Inference Inputs Videos",
|
||||
AUDIOSECTIONS: "Runware Audio Sections",
|
||||
AUDIOINFERENCEINPUTS: "Runware Audio Inference Inputs",
|
||||
AUDIOSETTINGS: "Runware Audio Inference Settings",
|
||||
@@ -328,6 +333,26 @@ const RUNWARE_NODE_PROPS = {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.TEXTINFERENCETOOLCHOICE]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.TEXTINFERENCESETTINGSCACHE]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.TEXTINFERENCEINPUTSIMAGES]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.TEXTINFERENCEINPUTSVIDEOS]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.TEXTINFERENCEINPUTS]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
},
|
||||
[RUNWARE_NODE_TYPES.TEXTINFERENCESETTINGS]: {
|
||||
bgColor: DEFAULT_BGCOLOR,
|
||||
colorModeOnly: true,
|
||||
|
||||
@@ -3275,6 +3275,11 @@ function textModelSearchFilterHandler(textModelSearchNode) {
|
||||
"openai:gpt@5.4-mini (GPT-5.4 Mini)",
|
||||
"openai:gpt@5.4-nano (GPT-5.4 Nano)",
|
||||
],
|
||||
"Anthropic": [
|
||||
"anthropic:claude@opus-4.7 (Claude Opus 4.7)",
|
||||
"anthropic:claude@sonnet-4.6 (Claude Sonnet 4.6)",
|
||||
"anthropic:claude@haiku-4.5 (Claude Haiku 4.5)",
|
||||
],
|
||||
"Google": [
|
||||
"google:gemini@3-flash (Gemini 3 Flash)",
|
||||
],
|
||||
|
||||
@@ -25,6 +25,12 @@ class RunwareTextInference:
|
||||
"providerSettings": ("RUNWAREPROVIDERSETTINGS", {
|
||||
"tooltip": "Optional provider-specific settings (wrapped by provider prefix from the model AIR).",
|
||||
}),
|
||||
"toolChoice": ("RUNWARETEXTINFERENCETOOLCHOICE", {
|
||||
"tooltip": "Connect Runware Text Inference Tool Choice for tool-calling strategy (auto / any / tool / none).",
|
||||
}),
|
||||
"inputs": ("RUNWARETEXTINFERENCEINPUTS", {
|
||||
"tooltip": "Connect Runware Text Inference Inputs for inputs.images / inputs.videos (URLs or mediaUUIDs).",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -32,7 +38,7 @@ class RunwareTextInference:
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "infer_text"
|
||||
CATEGORY = "Runware/Text"
|
||||
DESCRIPTION = "Run text/chat inference using Runware textInference. Connect Text Model and Text Inference Messages."
|
||||
DESCRIPTION = "Run text/chat inference using Runware textInference. Connect Text Model and Text Inference Messages. Optional: Settings, provider settings, tool choice, inputs (images/videos)."
|
||||
|
||||
@staticmethod
|
||||
def _extract_text_from_data_item(item: Dict[str, Any]) -> Optional[str]:
|
||||
@@ -70,6 +76,8 @@ class RunwareTextInference:
|
||||
messages: List[Dict[str, str]] = kwargs.get("messages") or []
|
||||
text_settings = kwargs.get("settings")
|
||||
provider_settings = kwargs.get("providerSettings")
|
||||
tool_choice = kwargs.get("toolChoice")
|
||||
text_inputs = kwargs.get("inputs")
|
||||
|
||||
if not model:
|
||||
raise Exception("model is empty.")
|
||||
@@ -94,6 +102,12 @@ class RunwareTextInference:
|
||||
provider_name = model.split(":")[0] if ":" in model else model
|
||||
task["providerSettings"] = {provider_name: provider_settings}
|
||||
|
||||
if tool_choice is not None and isinstance(tool_choice, dict) and len(tool_choice) > 0:
|
||||
task["toolChoice"] = tool_choice
|
||||
|
||||
if text_inputs is not None and isinstance(text_inputs, dict) and len(text_inputs) > 0:
|
||||
task["inputs"] = dict(text_inputs)
|
||||
|
||||
gen_config = [task]
|
||||
print(f"[Runware Text Inference] Request: {rwUtils.safe_json_dumps(gen_config, indent=2)}")
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Runware Text Inference Inputs — merge Images + Videos blocks into task.inputs for textInference."""
|
||||
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
|
||||
class RunwareTextInferenceInputs:
|
||||
"""Merge Runware Text Inference Inputs Images and/or Videos into a single inputs dict."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {},
|
||||
"optional": {
|
||||
"images": ("RUNWARETEXTINFERENCEINPUTSIMAGES", {
|
||||
"tooltip": "Connect Runware Text Inference Inputs Images for inputs.images[].",
|
||||
}),
|
||||
"videos": ("RUNWARETEXTINFERENCEINPUTSVIDEOS", {
|
||||
"tooltip": "Connect Runware Text Inference Inputs Videos for inputs.videos[].",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("RUNWARETEXTINFERENCEINPUTS",)
|
||||
RETURN_NAMES = ("inputs",)
|
||||
FUNCTION = "merge_inputs"
|
||||
CATEGORY = "Runware/Text"
|
||||
DESCRIPTION = (
|
||||
"Combine Runware Text Inference Inputs Images and/or Videos into inputs for Runware Text Inference "
|
||||
"(inputs.images, inputs.videos)."
|
||||
)
|
||||
|
||||
def merge_inputs(self, **kwargs) -> Tuple[Dict[str, Any], ...]:
|
||||
images_block = kwargs.get("images")
|
||||
videos_block = kwargs.get("videos")
|
||||
|
||||
out: Dict[str, Any] = {}
|
||||
|
||||
if isinstance(images_block, dict) and images_block.get("images"):
|
||||
imgs = images_block["images"]
|
||||
if isinstance(imgs, list) and len(imgs) > 0:
|
||||
out["images"] = [str(x).strip() for x in imgs if x is not None and str(x).strip()]
|
||||
|
||||
if isinstance(videos_block, dict) and videos_block.get("videos"):
|
||||
vids = videos_block["videos"]
|
||||
if isinstance(vids, list) and len(vids) > 0:
|
||||
out["videos"] = [str(x).strip() for x in vids if x is not None and str(x).strip()]
|
||||
|
||||
return (out,)
|
||||
@@ -0,0 +1,50 @@
|
||||
"""Runware Text Inference Inputs Images — builds inputs.images from ComfyUI IMAGE tensors."""
|
||||
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
|
||||
class RunwareTextInferenceInputsImages:
|
||||
"""Collect IMAGE tensors and convert them for textInference task.inputs.images.
|
||||
|
||||
Each connected IMAGE is converted via runwareUtils.convertTensor2IMG, which returns
|
||||
either a cached mediaUUID (when caching is enabled) or a base64 data URI.
|
||||
"""
|
||||
|
||||
_MAX_IMAGES = 8
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
optional: Dict[str, tuple] = {}
|
||||
for i in range(1, cls._MAX_IMAGES + 1):
|
||||
ordinal = rwUtils.getOrdinal(i)
|
||||
optional[f"Image {i}"] = ("IMAGE", {
|
||||
"tooltip": (
|
||||
f"{ordinal.capitalize()} image for the model. Connect a Load Image (or any IMAGE source) node. "
|
||||
"Tensor is converted to base64 / mediaUUID before being sent to the API."
|
||||
),
|
||||
})
|
||||
return {"required": {}, "optional": optional}
|
||||
|
||||
RETURN_TYPES = ("RUNWARETEXTINFERENCEINPUTSIMAGES",)
|
||||
RETURN_NAMES = ("images",)
|
||||
FUNCTION = "createImages"
|
||||
CATEGORY = "Runware/Text"
|
||||
DESCRIPTION = (
|
||||
"Collect one or more IMAGE tensors (Load Image, generated images, etc.) for Runware Text Inference "
|
||||
"inputs.images. Connect the output to Runware Text Inference Inputs."
|
||||
)
|
||||
|
||||
def createImages(self, **kwargs) -> Tuple[Dict[str, Any], ...]:
|
||||
urls: List[str] = []
|
||||
for i in range(1, self._MAX_IMAGES + 1):
|
||||
tensor = kwargs.get(f"Image {i}", None)
|
||||
if tensor is None:
|
||||
continue
|
||||
urls.append(rwUtils.convertTensor2IMG(tensor))
|
||||
|
||||
if not urls:
|
||||
return ({},)
|
||||
|
||||
return ({"images": urls},)
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Runware Text Inference Inputs Videos — builds inputs.videos (URL or mediaUUID per item)."""
|
||||
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
from .utils import runwareUtils as rwUtils
|
||||
|
||||
|
||||
class RunwareTextInferenceInputsVideos:
|
||||
"""Collect video references for textInference task.inputs.videos."""
|
||||
|
||||
_MAX_VIDEOS = 4
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
optional: Dict[str, tuple] = {
|
||||
"useVideos": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Enable to include inputs.videos in the text inference request.",
|
||||
}),
|
||||
}
|
||||
for i in range(1, cls._MAX_VIDEOS + 1):
|
||||
ordinal = rwUtils.getOrdinal(i)
|
||||
optional[f"Video{i}"] = ("STRING", {
|
||||
"default": "",
|
||||
"tooltip": f"Video URL or mediaUUID for the {ordinal} video. Only used when 'Use Videos' is enabled.",
|
||||
})
|
||||
return {"required": {}, "optional": optional}
|
||||
|
||||
RETURN_TYPES = ("RUNWARETEXTINFERENCEINPUTSVIDEOS",)
|
||||
RETURN_NAMES = ("videos",)
|
||||
FUNCTION = "createVideos"
|
||||
CATEGORY = "Runware/Text"
|
||||
DESCRIPTION = (
|
||||
"Collect one or more video references (URL or mediaUUID) for Runware Text Inference "
|
||||
"inputs.videos. Connect the output to Runware Text Inference Inputs."
|
||||
)
|
||||
|
||||
def createVideos(self, **kwargs) -> Tuple[Dict[str, Any], ...]:
|
||||
if not kwargs.get("useVideos", False):
|
||||
return ({},)
|
||||
|
||||
urls: List[str] = []
|
||||
for i in range(1, self._MAX_VIDEOS + 1):
|
||||
raw = kwargs.get(f"Video{i}", "")
|
||||
if raw is not None and str(raw).strip():
|
||||
urls.append(str(raw).strip())
|
||||
|
||||
if not urls:
|
||||
return ({},)
|
||||
|
||||
return ({"videos": urls},)
|
||||
@@ -70,9 +70,13 @@ class RunwareTextInferenceSettings:
|
||||
"tooltip": f"Top-k sampling (1–{cls._MAX_TOP_K}).",
|
||||
}),
|
||||
# Keep this in the original slot to avoid shifting legacy widgets_values.
|
||||
"thinkingLevel": (["none", "low", "medium", "high"], {
|
||||
"thinkingLevel": (["none", "low", "medium", "high", "max", "adaptive"], {
|
||||
"default": "none",
|
||||
"tooltip": "Extended reasoning level: none, low, medium, high.",
|
||||
"tooltip": (
|
||||
"Extended reasoning level: none, low, medium, high, max, adaptive. "
|
||||
"off -> disabled. low/medium/high/max -> adaptive thinking with the chosen effort. "
|
||||
"adaptive -> model picks budget."
|
||||
),
|
||||
}),
|
||||
"useStopSequences": ("BOOLEAN", {
|
||||
"default": False,
|
||||
@@ -123,6 +127,9 @@ class RunwareTextInferenceSettings:
|
||||
"default": "{}",
|
||||
"tooltip": "JSON object for toolChoice.",
|
||||
}),
|
||||
"cache": ("RUNWARETEXTINFERENCESETTINGSCACHE", {
|
||||
"tooltip": "Connect Runware Text Inference Settings Cache for settings.cache.{scope, ttl}.",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -132,7 +139,7 @@ class RunwareTextInferenceSettings:
|
||||
CATEGORY = "Runware/Text"
|
||||
DESCRIPTION = (
|
||||
"Optional text inference settings (systemPrompt, maxTokens, temperature, topP, topK, "
|
||||
"stopSequences, presencePenalty, frequencyPenalty, tools, toolChoice, thinkingLevel). "
|
||||
"stopSequences, presencePenalty, frequencyPenalty, tools, toolChoice, thinkingLevel, cache). "
|
||||
"Connect to Runware Text Inference."
|
||||
)
|
||||
|
||||
@@ -191,7 +198,7 @@ class RunwareTextInferenceSettings:
|
||||
"minimal": "low",
|
||||
}
|
||||
value = aliases.get(value, value)
|
||||
if value not in ("none", "low", "medium", "high"):
|
||||
if value not in ("none", "low", "medium", "high", "max", "adaptive"):
|
||||
return "none"
|
||||
return value
|
||||
|
||||
@@ -242,6 +249,10 @@ class RunwareTextInferenceSettings:
|
||||
if tl != "none":
|
||||
out["thinkingLevel"] = tl
|
||||
|
||||
cache_cfg = kwargs.get("cache")
|
||||
if cache_cfg is not None and isinstance(cache_cfg, dict) and len(cache_cfg) > 0:
|
||||
out["cache"] = cache_cfg
|
||||
|
||||
if not out:
|
||||
return ({},)
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
Runware Text Inference Settings Cache Node
|
||||
Builds the cache object for Runware Text Inference (settings.cache.{scope, ttl}).
|
||||
|
||||
Maps cache_control placement (Anthropic):
|
||||
- scope: where cache_control is attached
|
||||
* system -> system block only
|
||||
* system+history -> system block + last user message block (default)
|
||||
- ttl: cache lifetime (5m or 1h)
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
class RunwareTextInferenceSettingsCache:
|
||||
"""Runware Text Inference Settings Cache node."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"scope": (["system", "system+history"], {
|
||||
"default": "system+history",
|
||||
"tooltip": (
|
||||
"Where cache_control is attached.\n"
|
||||
"- system: cache the system block only.\n"
|
||||
"- system+history: cache the system block and the last user message block."
|
||||
),
|
||||
}),
|
||||
"ttl": (["5m", "1h"], {
|
||||
"default": "5m",
|
||||
"tooltip": "Cache lifetime: 5m (5 minutes) or 1h (1 hour).",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("RUNWARETEXTINFERENCESETTINGSCACHE",)
|
||||
RETURN_NAMES = ("cache",)
|
||||
FUNCTION = "createCache"
|
||||
CATEGORY = "Runware/Text"
|
||||
DESCRIPTION = (
|
||||
"Build a settings.cache object (scope + ttl) and connect it to Runware Text Inference. "
|
||||
"Merged into the request as settings.cache.{scope, ttl}."
|
||||
)
|
||||
|
||||
def createCache(self, **kwargs) -> tuple[Dict[str, Any]]:
|
||||
scope = (kwargs.get("scope") or "system+history").strip().lower()
|
||||
if scope not in ("system", "system+history"):
|
||||
scope = "system+history"
|
||||
|
||||
ttl = (kwargs.get("ttl") or "5m").strip().lower()
|
||||
if ttl not in ("5m", "1h"):
|
||||
ttl = "5m"
|
||||
|
||||
return ({"scope": scope, "ttl": ttl},)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"RunwareTextInferenceSettingsCache": RunwareTextInferenceSettingsCache,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RunwareTextInferenceSettingsCache": "Runware Text Inference Settings Cache",
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
Runware Text Inference Tool Choice Node
|
||||
Builds the toolChoice object for Runware Text Inference (textInference).
|
||||
|
||||
Strategies (type):
|
||||
- auto: model decides whether to call a tool (recommended default)
|
||||
- any: model must call at least one tool but chooses which one
|
||||
- tool: model must call the specific tool identified by `name`
|
||||
- none: model will not call any tool
|
||||
"""
|
||||
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
class RunwareTextInferenceToolChoice:
|
||||
"""Runware Text Inference Tool Choice node."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"type": (["auto", "any", "tool", "none"], {
|
||||
"default": "auto",
|
||||
"tooltip": (
|
||||
"Strategy the model uses to decide when and which tools to call.\n"
|
||||
"- auto: model decides (recommended default).\n"
|
||||
"- any: model must call at least one tool, of its choosing.\n"
|
||||
"- tool: model must call the specific tool named below.\n"
|
||||
"- none: model will not call any tool."
|
||||
),
|
||||
}),
|
||||
"name": ("STRING", {
|
||||
"default": "",
|
||||
"tooltip": "Name of the specific tool the model must call. Required when type is 'tool'; ignored otherwise.",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("RUNWARETEXTINFERENCETOOLCHOICE",)
|
||||
RETURN_NAMES = ("toolChoice",)
|
||||
FUNCTION = "createToolChoice"
|
||||
CATEGORY = "Runware/Text"
|
||||
DESCRIPTION = (
|
||||
"Build a toolChoice object (type + optional tool name) and connect it to "
|
||||
"Runware Text Inference. Required when type is 'tool'."
|
||||
)
|
||||
|
||||
def createToolChoice(self, **kwargs) -> tuple[Dict[str, Any]]:
|
||||
choice_type = (kwargs.get("type") or "auto").strip().lower()
|
||||
if choice_type not in ("auto", "any", "tool", "none"):
|
||||
choice_type = "auto"
|
||||
|
||||
name = (kwargs.get("name") or "").strip()
|
||||
|
||||
result: Dict[str, Any] = {"type": choice_type}
|
||||
if choice_type == "tool":
|
||||
if not name:
|
||||
raise Exception(
|
||||
"Tool Choice: 'name' is required when type is 'tool'. "
|
||||
"Provide the exact tool name the model must call."
|
||||
)
|
||||
result["name"] = name
|
||||
|
||||
return (result,)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"RunwareTextInferenceToolChoice": RunwareTextInferenceToolChoice,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RunwareTextInferenceToolChoice": "Runware Text Inference Tool Choice",
|
||||
}
|
||||
@@ -13,6 +13,11 @@ class RunwareTextModelSearch:
|
||||
"openai:gpt@5.4-mini (GPT-5.4 Mini)",
|
||||
"openai:gpt@5.4-nano (GPT-5.4 Nano)",
|
||||
],
|
||||
"Anthropic": [
|
||||
"anthropic:claude@opus-4.7 (Claude Opus 4.7)",
|
||||
"anthropic:claude@sonnet-4.6 (Claude Sonnet 4.6)",
|
||||
"anthropic:claude@haiku-4.5 (Claude Haiku 4.5)",
|
||||
],
|
||||
"Google": [
|
||||
"google:gemini@3-flash (Gemini 3 Flash)",
|
||||
],
|
||||
@@ -23,6 +28,7 @@ class RunwareTextModelSearch:
|
||||
|
||||
MODEL_PROVIDERS = [
|
||||
"All",
|
||||
"Anthropic",
|
||||
"Google",
|
||||
"MiniMax",
|
||||
"OpenAI",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user