feat(lora): add lora_syntax_format setting for syntax version toggle (#917)

Adds lora_syntax_format setting (full/legacy) that controls whether <lora:...> syntax uses relative paths (full) or filename only (legacy). Default is legacy for backward compatibility with A1111 convention. The full path format (<lora:relative/path/filename:strength>) enables lossless model resolution across subfolders.

Ultraworked with Sisyphus (https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Will Miao
2026-05-22 21:03:29 +08:00
parent 15dfaed462
commit 3b602a3698
18 changed files with 146 additions and 20 deletions

View File

@@ -577,7 +577,13 @@
}, },
"misc": { "misc": {
"includeTriggerWords": "Trigger Words in LoRA-Syntax einschließen", "includeTriggerWords": "Trigger Words in LoRA-Syntax einschließen",
"includeTriggerWordsHelp": "Trainierte Trigger Words beim Kopieren der LoRA-Syntax in die Zwischenablage einschließen" "includeTriggerWordsHelp": "Trainierte Trigger Words beim Kopieren der LoRA-Syntax in die Zwischenablage einschließen",
"loraSyntaxFormat": "[TODO: Translate] LoRA Syntax Format",
"loraSyntaxFormatHelp": "[TODO: Translate] LoRA syntax format. Full includes subfolder path (<lora:style/anime/x:1.0>) for lossless model resolution. Legacy uses filename only (<lora:x:1.0>) — A1111 convention, may be ambiguous with duplicate filenames across folders.",
"loraSyntaxFormatOptions": {
"full": "[TODO: Translate] Full path (subfolder/name)",
"legacy": "[TODO: Translate] Legacy A1111 (name only)"
}
}, },
"metadataArchive": { "metadataArchive": {
"enableArchiveDb": "Metadaten-Archiv-Datenbank aktivieren", "enableArchiveDb": "Metadaten-Archiv-Datenbank aktivieren",

View File

@@ -577,7 +577,13 @@
}, },
"misc": { "misc": {
"includeTriggerWords": "Include Trigger Words in LoRA Syntax", "includeTriggerWords": "Include Trigger Words in LoRA Syntax",
"includeTriggerWordsHelp": "Include trained trigger words when copying LoRA syntax to clipboard" "includeTriggerWordsHelp": "Include trained trigger words when copying LoRA syntax to clipboard",
"loraSyntaxFormat": "LoRA Syntax Format",
"loraSyntaxFormatHelp": "LoRA syntax format. Full includes subfolder path (<lora:style/anime/x:1.0>) for lossless model resolution. Legacy uses filename only (<lora:x:1.0>) — A1111 convention, may be ambiguous with duplicate filenames across folders.",
"loraSyntaxFormatOptions": {
"full": "Full path (subfolder/name)",
"legacy": "Legacy A1111 (name only)"
}
}, },
"metadataArchive": { "metadataArchive": {
"enableArchiveDb": "Enable Metadata Archive Database", "enableArchiveDb": "Enable Metadata Archive Database",

View File

@@ -577,7 +577,13 @@
}, },
"misc": { "misc": {
"includeTriggerWords": "Incluir palabras clave en la sintaxis de LoRA", "includeTriggerWords": "Incluir palabras clave en la sintaxis de LoRA",
"includeTriggerWordsHelp": "Incluir palabras clave entrenadas al copiar la sintaxis de LoRA al portapapeles" "includeTriggerWordsHelp": "Incluir palabras clave entrenadas al copiar la sintaxis de LoRA al portapapeles",
"loraSyntaxFormat": "[TODO: Translate] LoRA Syntax Format",
"loraSyntaxFormatHelp": "[TODO: Translate] LoRA syntax format. Full includes subfolder path (<lora:style/anime/x:1.0>) for lossless model resolution. Legacy uses filename only (<lora:x:1.0>) — A1111 convention, may be ambiguous with duplicate filenames across folders.",
"loraSyntaxFormatOptions": {
"full": "[TODO: Translate] Full path (subfolder/name)",
"legacy": "[TODO: Translate] Legacy A1111 (name only)"
}
}, },
"metadataArchive": { "metadataArchive": {
"enableArchiveDb": "Habilitar base de datos de archivo de metadatos", "enableArchiveDb": "Habilitar base de datos de archivo de metadatos",

View File

@@ -577,7 +577,13 @@
}, },
"misc": { "misc": {
"includeTriggerWords": "Inclure les mots-clés dans la syntaxe LoRA", "includeTriggerWords": "Inclure les mots-clés dans la syntaxe LoRA",
"includeTriggerWordsHelp": "Inclure les mots-clés d'entraînement lors de la copie de la syntaxe LoRA dans le presse-papiers" "includeTriggerWordsHelp": "Inclure les mots-clés d'entraînement lors de la copie de la syntaxe LoRA dans le presse-papiers",
"loraSyntaxFormat": "[TODO: Translate] LoRA Syntax Format",
"loraSyntaxFormatHelp": "[TODO: Translate] LoRA syntax format. Full includes subfolder path (<lora:style/anime/x:1.0>) for lossless model resolution. Legacy uses filename only (<lora:x:1.0>) — A1111 convention, may be ambiguous with duplicate filenames across folders.",
"loraSyntaxFormatOptions": {
"full": "[TODO: Translate] Full path (subfolder/name)",
"legacy": "[TODO: Translate] Legacy A1111 (name only)"
}
}, },
"metadataArchive": { "metadataArchive": {
"enableArchiveDb": "Activer la base de données d'archive des métadonnées", "enableArchiveDb": "Activer la base de données d'archive des métadonnées",

View File

@@ -577,7 +577,13 @@
}, },
"misc": { "misc": {
"includeTriggerWords": "כלול מילות טריגר בתחביר LoRA", "includeTriggerWords": "כלול מילות טריגר בתחביר LoRA",
"includeTriggerWordsHelp": "כלול מילות טריגר מאומנות בעת העתקת תחביר LoRA ללוח" "includeTriggerWordsHelp": "כלול מילות טריגר מאומנות בעת העתקת תחביר LoRA ללוח",
"loraSyntaxFormat": "[TODO: Translate] LoRA Syntax Format",
"loraSyntaxFormatHelp": "[TODO: Translate] LoRA syntax format. Full includes subfolder path (<lora:style/anime/x:1.0>) for lossless model resolution. Legacy uses filename only (<lora:x:1.0>) — A1111 convention, may be ambiguous with duplicate filenames across folders.",
"loraSyntaxFormatOptions": {
"full": "[TODO: Translate] Full path (subfolder/name)",
"legacy": "[TODO: Translate] Legacy A1111 (name only)"
}
}, },
"metadataArchive": { "metadataArchive": {
"enableArchiveDb": "הפעל מסד נתונים של ארכיון מטא-דאטה", "enableArchiveDb": "הפעל מסד נתונים של ארכיון מטא-דאטה",

View File

@@ -577,7 +577,13 @@
}, },
"misc": { "misc": {
"includeTriggerWords": "LoRA構文にトリガーワードを含める", "includeTriggerWords": "LoRA構文にトリガーワードを含める",
"includeTriggerWordsHelp": "LoRA構文をクリップボードにコピーする際、学習済みトリガーワードを含めます" "includeTriggerWordsHelp": "LoRA構文をクリップボードにコピーする際、学習済みトリガーワードを含めます",
"loraSyntaxFormat": "[TODO: Translate] LoRA Syntax Format",
"loraSyntaxFormatHelp": "[TODO: Translate] LoRA syntax format. Full includes subfolder path (<lora:style/anime/x:1.0>) for lossless model resolution. Legacy uses filename only (<lora:x:1.0>) — A1111 convention, may be ambiguous with duplicate filenames across folders.",
"loraSyntaxFormatOptions": {
"full": "[TODO: Translate] Full path (subfolder/name)",
"legacy": "[TODO: Translate] Legacy A1111 (name only)"
}
}, },
"metadataArchive": { "metadataArchive": {
"enableArchiveDb": "メタデータアーカイブデータベースを有効化", "enableArchiveDb": "メタデータアーカイブデータベースを有効化",

View File

@@ -577,7 +577,13 @@
}, },
"misc": { "misc": {
"includeTriggerWords": "LoRA 문법에 트리거 단어 포함", "includeTriggerWords": "LoRA 문법에 트리거 단어 포함",
"includeTriggerWordsHelp": "LoRA 문법을 클립보드에 복사할 때 학습된 트리거 단어를 포함합니다" "includeTriggerWordsHelp": "LoRA 문법을 클립보드에 복사할 때 학습된 트리거 단어를 포함합니다",
"loraSyntaxFormat": "[TODO: Translate] LoRA Syntax Format",
"loraSyntaxFormatHelp": "[TODO: Translate] LoRA syntax format. Full includes subfolder path (<lora:style/anime/x:1.0>) for lossless model resolution. Legacy uses filename only (<lora:x:1.0>) — A1111 convention, may be ambiguous with duplicate filenames across folders.",
"loraSyntaxFormatOptions": {
"full": "[TODO: Translate] Full path (subfolder/name)",
"legacy": "[TODO: Translate] Legacy A1111 (name only)"
}
}, },
"metadataArchive": { "metadataArchive": {
"enableArchiveDb": "메타데이터 아카이브 데이터베이스 활성화", "enableArchiveDb": "메타데이터 아카이브 데이터베이스 활성화",

View File

@@ -577,7 +577,13 @@
}, },
"misc": { "misc": {
"includeTriggerWords": "Включать триггерные слова в синтаксис LoRA", "includeTriggerWords": "Включать триггерные слова в синтаксис LoRA",
"includeTriggerWordsHelp": "Включать обученные триггерные слова при копировании синтаксиса LoRA в буфер обмена" "includeTriggerWordsHelp": "Включать обученные триггерные слова при копировании синтаксиса LoRA в буфер обмена",
"loraSyntaxFormat": "[TODO: Translate] LoRA Syntax Format",
"loraSyntaxFormatHelp": "[TODO: Translate] LoRA syntax format. Full includes subfolder path (<lora:style/anime/x:1.0>) for lossless model resolution. Legacy uses filename only (<lora:x:1.0>) — A1111 convention, may be ambiguous with duplicate filenames across folders.",
"loraSyntaxFormatOptions": {
"full": "[TODO: Translate] Full path (subfolder/name)",
"legacy": "[TODO: Translate] Legacy A1111 (name only)"
}
}, },
"metadataArchive": { "metadataArchive": {
"enableArchiveDb": "Включить архив метаданных", "enableArchiveDb": "Включить архив метаданных",

View File

@@ -577,7 +577,13 @@
}, },
"misc": { "misc": {
"includeTriggerWords": "复制 LoRA 语法时包含触发词", "includeTriggerWords": "复制 LoRA 语法时包含触发词",
"includeTriggerWordsHelp": "复制 LoRA 语法到剪贴板时包含训练触发词" "includeTriggerWordsHelp": "复制 LoRA 语法到剪贴板时包含训练触发词",
"loraSyntaxFormat": "LoRA 语法格式",
"loraSyntaxFormatHelp": "LoRA 语法格式。完整路径Full包含子文件夹路径 (<lora:style/anime/x:1.0>)解析精确无歧义。旧版Legacy仅使用文件名 (<lora:x:1.0>)——A1111 原始约定,同名文件跨文件夹时可能产生歧义。",
"loraSyntaxFormatOptions": {
"full": "完整路径(子文件夹/名称)",
"legacy": "旧版 A1111仅名称"
}
}, },
"metadataArchive": { "metadataArchive": {
"enableArchiveDb": "启用元数据归档数据库", "enableArchiveDb": "启用元数据归档数据库",

View File

@@ -577,7 +577,13 @@
}, },
"misc": { "misc": {
"includeTriggerWords": "在 LoRA 語法中包含觸發詞", "includeTriggerWords": "在 LoRA 語法中包含觸發詞",
"includeTriggerWordsHelp": "複製 LoRA 語法到剪貼簿時包含訓練觸發詞" "includeTriggerWordsHelp": "複製 LoRA 語法到剪貼簿時包含訓練觸發詞",
"loraSyntaxFormat": "LoRA 語法格式",
"loraSyntaxFormatHelp": "LoRA 語法格式。完整路徑Full包含子資料夾路徑 (<lora:style/anime/x:1.0>)解析精確無歧義。舊版Legacy僅使用檔名 (<lora:x:1.0>)——A1111 原始約定,同名檔案跨資料夾時可能產生歧義。",
"loraSyntaxFormatOptions": {
"full": "完整路徑(子資料夾/名稱)",
"legacy": "舊版 A1111僅名稱"
}
}, },
"metadataArchive": { "metadataArchive": {
"enableArchiveDb": "啟用中繼資料封存資料庫", "enableArchiveDb": "啟用中繼資料封存資料庫",

View File

@@ -9,6 +9,7 @@ from ..utils.utils import get_lora_info_absolute
from .utils import ( from .utils import (
FlexibleOptionalInputType, FlexibleOptionalInputType,
any_type, any_type,
apply_lora_syntax_format,
detect_nunchaku_model_kind, detect_nunchaku_model_kind,
extract_lora_name, extract_lora_name,
get_loras_list, get_loras_list,
@@ -52,7 +53,7 @@ def _collect_widget_entries(kwargs):
for lora in get_loras_list(kwargs): for lora in get_loras_list(kwargs):
if not lora.get("active", False): if not lora.get("active", False):
continue continue
lora_name = lora["name"] lora_name = apply_lora_syntax_format(lora["name"])
model_strength = float(lora["strength"]) model_strength = float(lora["strength"])
clip_strength = float(lora.get("clipStrength", model_strength)) clip_strength = float(lora.get("clipStrength", model_strength))
lora_path, trigger_words = get_lora_info_absolute(lora_name) lora_path, trigger_words = get_lora_info_absolute(lora_name)

View File

@@ -1,6 +1,6 @@
import os import os
from ..utils.utils import get_lora_info from ..utils.utils import get_lora_info
from .utils import FlexibleOptionalInputType, any_type, extract_lora_name, get_loras_list from .utils import FlexibleOptionalInputType, any_type, apply_lora_syntax_format, extract_lora_name, get_loras_list
import logging import logging
@@ -48,7 +48,7 @@ class LoraStackerLM:
if not lora.get('active', False): if not lora.get('active', False):
continue continue
lora_name = lora['name'] lora_name = apply_lora_syntax_format(lora['name'])
model_strength = float(lora['strength']) model_strength = float(lora['strength'])
# Get clip strength - use model strength as default if not specified # Get clip strength - use model strength as default if not specified
clip_strength = float(lora.get('clipStrength', model_strength)) clip_strength = float(lora.get('clipStrength', model_strength))

View File

@@ -44,14 +44,29 @@ import folder_paths # type: ignore
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def get_lora_syntax_format():
try:
from ..services.settings_manager import get_settings_manager
return get_settings_manager().get("lora_syntax_format", "legacy")
except Exception:
return "legacy"
def apply_lora_syntax_format(name):
fmt = get_lora_syntax_format()
if fmt == "legacy":
return name.replace("\\", "/").rstrip("/").split("/")[-1]
return name
def extract_lora_name(lora_path): def extract_lora_name(lora_path):
normalized = lora_path.replace("\\", "/") normalized = lora_path.replace("\\", "/")
basename = os.path.basename(normalized) basename = os.path.basename(normalized)
name_no_ext = os.path.splitext(basename)[0] name_no_ext = os.path.splitext(basename)[0]
dirname = os.path.dirname(normalized) dirname = os.path.dirname(normalized)
if dirname and dirname not in (".", "/") and not normalized.startswith("/"): if dirname and dirname not in (".", "/") and not normalized.startswith("/"):
return f"{dirname}/{name_no_ext}" return apply_lora_syntax_format(f"{dirname}/{name_no_ext}")
return name_no_ext return apply_lora_syntax_format(name_no_ext)
def get_loras_list(kwargs): def get_loras_list(kwargs):

View File

@@ -96,6 +96,7 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
"compact_mode": False, "compact_mode": False,
"priority_tags": DEFAULT_PRIORITY_TAG_CONFIG.copy(), "priority_tags": DEFAULT_PRIORITY_TAG_CONFIG.copy(),
"model_name_display": "model_name", "model_name_display": "model_name",
"lora_syntax_format": "legacy",
"model_card_footer_action": "replace_preview", "model_card_footer_action": "replace_preview",
"show_version_on_card": True, "show_version_on_card": True,
"update_flag_strategy": "same_base", "update_flag_strategy": "same_base",

View File

@@ -37,6 +37,7 @@ const DEFAULT_SETTINGS_BASE = Object.freeze({
card_info_display: 'always', card_info_display: 'always',
show_folder_sidebar: true, show_folder_sidebar: true,
model_name_display: 'model_name', model_name_display: 'model_name',
lora_syntax_format: 'legacy',
model_card_footer_action: 'example_images', model_card_footer_action: 'example_images',
show_version_on_card: true, show_version_on_card: true,
include_trigger_words: false, include_trigger_words: false,

View File

@@ -420,12 +420,16 @@ export function getLoraStrengthsFromUsageTips(usageTips = {}) {
export function buildLoraSyntax(fileName, usageTips = {}) { export function buildLoraSyntax(fileName, usageTips = {}) {
const { strength, hasStrength, clipStrength, hasClipStrength } = getLoraStrengthsFromUsageTips(usageTips); const { strength, hasStrength, clipStrength, hasClipStrength } = getLoraStrengthsFromUsageTips(usageTips);
const effectiveName = state.global.settings?.lora_syntax_format === 'legacy'
? fileName.split('/').pop()
: fileName;
if (hasClipStrength) { if (hasClipStrength) {
const modelStrength = hasStrength ? strength : 1; const modelStrength = hasStrength ? strength : 1;
return `<lora:${fileName}:${modelStrength}:${clipStrength}>`; return `<lora:${effectiveName}:${modelStrength}:${clipStrength}>`;
} }
return `<lora:${fileName}:${strength}>`; return `<lora:${effectiveName}:${strength}>`;
} }
export function copyLoraSyntax(card) { export function copyLoraSyntax(card) {

View File

@@ -536,7 +536,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -595,6 +595,22 @@
<div class="settings-subsection-header"> <div class="settings-subsection-header">
<h4>{{ t('settings.sections.misc') }}</h4> <h4>{{ t('settings.sections.misc') }}</h4>
</div> </div>
<div class="setting-item">
<div class="setting-row">
<div class="setting-info">
<label for="loraSyntaxFormat">
{{ t('settings.misc.loraSyntaxFormat') }}
<i class="fas fa-info-circle info-icon" data-tooltip="{{ t('settings.misc.loraSyntaxFormatHelp') }}"></i>
</label>
</div>
<div class="setting-control select-control">
<select id="loraSyntaxFormat" onchange="settingsManager.saveSelectSetting('loraSyntaxFormat', 'lora_syntax_format')">
<option value="full">{{ t('settings.misc.loraSyntaxFormatOptions.full') }}</option>
<option value="legacy">{{ t('settings.misc.loraSyntaxFormatOptions.legacy') }}</option>
</select>
</div>
</div>
</div>
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">

View File

@@ -104,6 +104,30 @@ function removeLoraExtension(fileName = '') {
return fileName.replace(/\.(safetensors|ckpt|pt|bin)$/i, ''); return fileName.replace(/\.(safetensors|ckpt|pt|bin)$/i, '');
} }
let _loraSyntaxFormatCache = null;
function _getLoraSyntaxFormat() {
if (typeof _loraSyntaxFormatCache !== 'undefined' && _loraSyntaxFormatCache !== null) {
return _loraSyntaxFormatCache;
}
return 'legacy';
}
async function _initLoraSyntaxFormat() {
try {
const response = await api.fetchApi('/lm/settings');
if (response.ok) {
const data = await response.json();
if (data.success && data.settings) {
_loraSyntaxFormatCache = data.settings.lora_syntax_format || 'legacy';
return;
}
}
} catch (e) {
}
_loraSyntaxFormatCache = 'legacy';
}
_initLoraSyntaxFormat();
function parseSearchTokens(term = '') { function parseSearchTokens(term = '') {
const include = []; const include = [];
const exclude = []; const exclude = [];
@@ -231,6 +255,10 @@ const MODEL_BEHAVIORS = {
const folder = directories.length ? directories.join('/') + '/' : ''; const folder = directories.length ? directories.join('/') + '/' : '';
const loraName = folder + baseName; const loraName = folder + baseName;
const resultName = _getLoraSyntaxFormat() === 'legacy'
? baseName
: loraName;
let strength = 1.0; let strength = 1.0;
let hasStrength = false; let hasStrength = false;
let clipStrength = null; let clipStrength = null;
@@ -265,9 +293,9 @@ const MODEL_BEHAVIORS = {
} }
if (clipStrength !== null) { if (clipStrength !== null) {
return formatAutocompleteInsertion(`<lora:${loraName}:${strength}:${clipStrength}>`); return formatAutocompleteInsertion(`<lora:${resultName}:${strength}:${clipStrength}>`);
} }
return formatAutocompleteInsertion(`<lora:${loraName}:${strength}>`); return formatAutocompleteInsertion(`<lora:${resultName}:${strength}>`);
} }
}, },
embeddings: { embeddings: {