Separate model description from version description in metadata, responses, and UI
This commit is contained in:
+79
-6
@@ -362,6 +362,7 @@ class ModelResolverExtension:
|
||||
calculate_file_sha256,
|
||||
dedupe_local_base_directories,
|
||||
extract_safetensors_header_sha256,
|
||||
find_external_metadata_sidecar_path,
|
||||
get_comfy_root_path,
|
||||
get_filename_from_path,
|
||||
get_path_abs,
|
||||
@@ -2286,9 +2287,56 @@ class ModelResolverExtension:
|
||||
if file_location and not file_location.endswith("/"):
|
||||
file_location += "/"
|
||||
|
||||
external_model_description = ""
|
||||
if file_path:
|
||||
external_metadata_path = find_external_metadata_sidecar_path(
|
||||
file_path
|
||||
)
|
||||
external_metadata = (
|
||||
read_json_safe(external_metadata_path, {})
|
||||
if external_metadata_path
|
||||
else {}
|
||||
)
|
||||
if isinstance(external_metadata, dict):
|
||||
external_model_description = str(
|
||||
external_metadata.get("modelDescription")
|
||||
or external_metadata.get("model_description")
|
||||
or external_metadata.get("description")
|
||||
or ""
|
||||
).strip()
|
||||
|
||||
def infer_model_type_from_category(value):
|
||||
return normalize_category_to_model_type(value)
|
||||
|
||||
def split_result_descriptions(result, preferred_model_description=""):
|
||||
result = result if isinstance(result, dict) else {}
|
||||
explicit_model_description = (
|
||||
result.get("model_description")
|
||||
or result.get("modelDescription")
|
||||
or ""
|
||||
)
|
||||
generic_description = result.get("description") or ""
|
||||
model_description = (
|
||||
preferred_model_description
|
||||
or explicit_model_description
|
||||
or generic_description
|
||||
)
|
||||
version_description = (
|
||||
result.get("version_description")
|
||||
or result.get("versionDescription")
|
||||
or ""
|
||||
)
|
||||
if (
|
||||
not version_description
|
||||
and explicit_model_description
|
||||
and generic_description
|
||||
and generic_description != explicit_model_description
|
||||
):
|
||||
version_description = generic_description
|
||||
if version_description == model_description:
|
||||
version_description = ""
|
||||
return model_description, version_description
|
||||
|
||||
def build_info_response(
|
||||
result=None,
|
||||
*,
|
||||
@@ -2298,6 +2346,12 @@ class ModelResolverExtension:
|
||||
local_payload=False,
|
||||
):
|
||||
result = result or {}
|
||||
model_description, version_description = (
|
||||
split_result_descriptions(
|
||||
result,
|
||||
external_model_description,
|
||||
)
|
||||
)
|
||||
size_value = result.get("size")
|
||||
if not size_value and file_path and _os.path.exists(file_path):
|
||||
try:
|
||||
@@ -2345,8 +2399,9 @@ class ModelResolverExtension:
|
||||
"trained_words": result.get("trained_words", []),
|
||||
"images": result.get("images", []),
|
||||
"clip_skip": result.get("clip_skip"),
|
||||
"description": result.get("description", ""),
|
||||
"model_description": result.get("model_description", ""),
|
||||
"description": model_description,
|
||||
"model_description": model_description,
|
||||
"version_description": version_description,
|
||||
"from_metadata": bool(result.get("from_metadata")),
|
||||
"local_only": bool(local_payload),
|
||||
"metadata_checked": bool(civitai_checked),
|
||||
@@ -2589,6 +2644,9 @@ class ModelResolverExtension:
|
||||
):
|
||||
return metadata_path, metadata_saved
|
||||
try:
|
||||
model_description, version_description = (
|
||||
split_result_descriptions(result)
|
||||
)
|
||||
metadata_payload = {
|
||||
"source": source_name,
|
||||
"details_source": result.get("details_source")
|
||||
@@ -2613,8 +2671,9 @@ class ModelResolverExtension:
|
||||
"trained_words": result.get("trained_words", []),
|
||||
"images": result.get("images", []),
|
||||
"clip_skip": result.get("clip_skip"),
|
||||
"description": result.get("description", ""),
|
||||
"model_description": result.get("model_description", ""),
|
||||
"description": model_description,
|
||||
"model_description": model_description,
|
||||
"version_description": version_description,
|
||||
"download_url": result.get("download_url"),
|
||||
"source_url": result.get("version_url") or result.get("url"),
|
||||
"version_url": result.get("version_url") or result.get("url"),
|
||||
@@ -2690,6 +2749,9 @@ class ModelResolverExtension:
|
||||
preview_path = get_existing_model_preview_path(file_path)
|
||||
if preview_path:
|
||||
preview_url = preview_path.replace("\\", "/")
|
||||
model_description, version_description = (
|
||||
split_result_descriptions(result)
|
||||
)
|
||||
metadata_payload = {
|
||||
"source": "metadata_import",
|
||||
"details_source": result.get("source") or "metadata",
|
||||
@@ -2711,8 +2773,9 @@ class ModelResolverExtension:
|
||||
"trained_words": result.get("trained_words", []),
|
||||
"images": result.get("images", []),
|
||||
"clip_skip": result.get("clip_skip"),
|
||||
"description": result.get("description", ""),
|
||||
"model_description": result.get("model_description", ""),
|
||||
"description": model_description,
|
||||
"model_description": model_description,
|
||||
"version_description": version_description,
|
||||
"download_url": result.get("download_url"),
|
||||
"preview_url": preview_url,
|
||||
"source_url": result.get("version_url")
|
||||
@@ -2967,6 +3030,16 @@ class ModelResolverExtension:
|
||||
"base_model": response.get("base_model"),
|
||||
"base_model_source": response.get("base_model_source"),
|
||||
"base_model_inferred": bool(response.get("base_model_inferred")),
|
||||
"description": response.get("model_description")
|
||||
or response.get("description")
|
||||
or "",
|
||||
"model_description": response.get("model_description")
|
||||
or response.get("description")
|
||||
or "",
|
||||
"version_description": response.get(
|
||||
"version_description"
|
||||
)
|
||||
or "",
|
||||
"civitai_deleted": True,
|
||||
"civitai_checked": True,
|
||||
"remote_metadata_missing": True,
|
||||
|
||||
+14
-3
@@ -602,16 +602,26 @@ def build_model_resolver_metadata(
|
||||
source.get("thumbnail_url"),
|
||||
source.get("thumbnailUrl"),
|
||||
)
|
||||
model_description = _first_present(
|
||||
explicit_model_description = _first_present(
|
||||
source.get("modelDescription"),
|
||||
source.get("model_description"),
|
||||
source.get("description"),
|
||||
details.get("description"),
|
||||
_as_dict(source.get("model")).get("description"),
|
||||
)
|
||||
model_description = _first_present(
|
||||
explicit_model_description,
|
||||
source.get("description"),
|
||||
)
|
||||
version_description = _first_present(
|
||||
selected_version.get("description"),
|
||||
source.get("versionDescription"),
|
||||
source.get("version_description"),
|
||||
selected_version.get("description"),
|
||||
(
|
||||
source.get("description")
|
||||
if explicit_model_description
|
||||
and source.get("description") != explicit_model_description
|
||||
else None
|
||||
),
|
||||
)
|
||||
|
||||
is_civitai_source = source_name in {
|
||||
@@ -700,6 +710,7 @@ def build_model_resolver_metadata(
|
||||
"civitai": civitai_payload,
|
||||
"tags": tags,
|
||||
"modelDescription": str(model_description or ""),
|
||||
"version_description": str(version_description or ""),
|
||||
"civitai_deleted": bool(source.get("is_deleted") or source.get("civitai_deleted")),
|
||||
"source": source_name,
|
||||
"details_source": source_name,
|
||||
|
||||
+17
-14
@@ -1909,6 +1909,20 @@ def _metadata_to_model_info(metadata: Dict[str, Any]) -> Dict[str, Any]:
|
||||
else civitai_model_url
|
||||
)
|
||||
page_url = stored_page_url or civitai_version_url
|
||||
model_description = first_non_empty(
|
||||
metadata.get("modelDescription"),
|
||||
metadata.get("model_description"),
|
||||
metadata.get("description"),
|
||||
model_info.get("description"),
|
||||
) or ""
|
||||
version_description = first_non_empty(
|
||||
metadata.get("versionDescription"),
|
||||
metadata.get("version_description"),
|
||||
selected_version.get("description"),
|
||||
civitai_data.get("description"),
|
||||
) or ""
|
||||
if version_description == model_description:
|
||||
version_description = ""
|
||||
|
||||
return {
|
||||
"source": details_source,
|
||||
@@ -1984,20 +1998,9 @@ def _metadata_to_model_info(metadata: Dict[str, Any]) -> Dict[str, Any]:
|
||||
)
|
||||
or "",
|
||||
"clip_skip": civitai_data.get("clipSkip"),
|
||||
"description": first_non_empty(
|
||||
metadata.get("modelDescription"),
|
||||
metadata.get("model_description"),
|
||||
metadata.get("description"),
|
||||
model_info.get("description"),
|
||||
civitai_data.get("description"),
|
||||
)
|
||||
or "",
|
||||
"model_description": first_non_empty(
|
||||
metadata.get("modelDescription"),
|
||||
metadata.get("model_description"),
|
||||
model_info.get("description"),
|
||||
)
|
||||
or "",
|
||||
"description": model_description,
|
||||
"model_description": model_description,
|
||||
"version_description": version_description,
|
||||
"from_metadata": True,
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,38 @@ from core.path_utils import get_model_resolver_sidecar_path
|
||||
|
||||
|
||||
class DownloaderMetadataSidecarTests(unittest.TestCase):
|
||||
def test_keeps_model_description_separate_from_version_notes(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
model_path = os.path.join(tmpdir, "model.safetensors")
|
||||
with open(model_path, "wb") as handle:
|
||||
handle.write(b"abc")
|
||||
|
||||
payload = build_lora_manager_metadata(
|
||||
model_path,
|
||||
{
|
||||
"source": "civitai",
|
||||
"model_id": 123,
|
||||
"version_id": 456,
|
||||
"model_description": "Model page description",
|
||||
"description": "Version-specific release notes",
|
||||
},
|
||||
category="checkpoints",
|
||||
)
|
||||
|
||||
self.assertEqual("Model page description", payload["modelDescription"])
|
||||
self.assertEqual(
|
||||
"Version-specific release notes",
|
||||
payload["version_description"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"Model page description",
|
||||
payload["civitai"]["model"]["description"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"Version-specific release notes",
|
||||
payload["civitai"]["description"],
|
||||
)
|
||||
|
||||
def test_civarchive_metadata_keeps_provider_page_separate_from_hf_mirror(self):
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
model_path = os.path.join(tmpdir, "model.safetensors")
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import test from 'node:test';
|
||||
|
||||
const sourceUrl = new URL(
|
||||
'../web/resolver/views/model_info_methods.js',
|
||||
import.meta.url
|
||||
);
|
||||
const source = await readFile(sourceUrl, 'utf8');
|
||||
|
||||
test('Show info keeps model description separate from version notes', () => {
|
||||
assert.match(source, /'Version notes'/);
|
||||
assert.match(
|
||||
source,
|
||||
/class="mr-info-description-row mr-info-version-description-row mr-hidden-initial"/
|
||||
);
|
||||
assert.ok(
|
||||
source.indexOf("'Version notes'") < source.indexOf("'Description'"),
|
||||
'Version notes should be rendered above Description'
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/const modelDescription = data\.model_description\s*\|\|\s*data\.modelDescription\s*\|\|\s*data\.description/
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/const versionDescription = data\.version_description\s*\|\|\s*data\.versionDescription/
|
||||
);
|
||||
assert.match(source, /contentSelector: '\.mr-info-version-description'/);
|
||||
});
|
||||
|
||||
test('Refetch maps the response into stable model and version fields', () => {
|
||||
assert.match(
|
||||
source,
|
||||
/description: modelDescription,\s*model_description: modelDescription,\s*version_description: result\.version_description/
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
/data-description-target="\.mr-info-version-description"/
|
||||
);
|
||||
});
|
||||
@@ -283,6 +283,76 @@ class SearchOrchestrationTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(body["source"], "civarchive")
|
||||
self.assertEqual(body["sha256"], file_hash)
|
||||
|
||||
async def test_refetch_keeps_external_model_description_and_returns_version_notes(self):
|
||||
file_hash = "b" * 64
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
model_path = os.path.join(temp_dir, "model.safetensors")
|
||||
external_path = os.path.join(temp_dir, "model.metadata.json")
|
||||
resolver_path = f"{model_path}.modelresolver.json"
|
||||
external_payload = {
|
||||
"description": "User-managed model description",
|
||||
"sha256": file_hash,
|
||||
}
|
||||
with open(model_path, "wb") as model_file:
|
||||
model_file.write(b"model")
|
||||
with open(external_path, "w", encoding="utf-8") as metadata_file:
|
||||
json.dump(external_payload, metadata_file)
|
||||
|
||||
mock_request = MagicMock()
|
||||
mock_request.json = AsyncMock(return_value={
|
||||
"filename": "model.safetensors",
|
||||
"category": "checkpoints",
|
||||
"resolved_path": model_path,
|
||||
"sha256": file_hash,
|
||||
"force_refresh": True,
|
||||
})
|
||||
remote_result = {
|
||||
"source": "civitai",
|
||||
"filename": "model.safetensors",
|
||||
"model_name": "Remote model",
|
||||
"model_id": 123,
|
||||
"version_id": 456,
|
||||
"sha256": file_hash,
|
||||
"url": "https://civitai.com/models/123",
|
||||
"version_url": (
|
||||
"https://civitai.com/models/123?modelVersionId=456"
|
||||
),
|
||||
"model_description": "Fresh CivitAI model description",
|
||||
"description": "Fresh CivitAI version notes",
|
||||
}
|
||||
|
||||
with (
|
||||
patch.object(
|
||||
civitai_sources,
|
||||
"get_model_info_by_hash",
|
||||
return_value=remote_result,
|
||||
),
|
||||
patch("folder_paths.get_folder_paths", return_value=[temp_dir]),
|
||||
):
|
||||
response = await civitai_search_handler(mock_request)
|
||||
|
||||
body = json.loads(response.text)
|
||||
with open(external_path, encoding="utf-8") as metadata_file:
|
||||
external_after = json.load(metadata_file)
|
||||
with open(resolver_path, encoding="utf-8") as metadata_file:
|
||||
resolver_metadata = json.load(metadata_file)
|
||||
|
||||
self.assertEqual("User-managed model description", body["description"])
|
||||
self.assertEqual("User-managed model description", body["model_description"])
|
||||
self.assertEqual(
|
||||
"Fresh CivitAI version notes",
|
||||
body["version_description"],
|
||||
)
|
||||
self.assertEqual(external_payload, external_after)
|
||||
self.assertEqual(
|
||||
"Fresh CivitAI model description",
|
||||
resolver_metadata["modelDescription"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"Fresh CivitAI version notes",
|
||||
resolver_metadata["version_description"],
|
||||
)
|
||||
|
||||
async def test_local_matches_by_hash_route_returns_enriched_hash_matches(self):
|
||||
file_hash = "b" * 64
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
|
||||
@@ -107,6 +107,25 @@ class UnifiedHelpersTests(unittest.TestCase):
|
||||
self.assertEqual(result["preview_url"], "E:/Models/video-model.mp4")
|
||||
self.assertEqual(result["images"][0]["type"], "video")
|
||||
|
||||
def test_local_civitai_metadata_separates_model_and_version_descriptions(self):
|
||||
from core.sources.civitai import _metadata_to_model_info
|
||||
|
||||
result = _metadata_to_model_info({
|
||||
"modelDescription": "Model page description",
|
||||
"version_description": "Version-specific release notes",
|
||||
"civitai": {
|
||||
"description": "Nested version notes",
|
||||
"model": {"description": "Nested model description"},
|
||||
},
|
||||
})
|
||||
|
||||
self.assertEqual("Model page description", result["description"])
|
||||
self.assertEqual("Model page description", result["model_description"])
|
||||
self.assertEqual(
|
||||
"Version-specific release notes",
|
||||
result["version_description"],
|
||||
)
|
||||
|
||||
def test_normalize_model_image_preserves_metadata_when_normalized_again(self):
|
||||
workflow = '{"workflow":{"nodes":[{"id":1,"type":"KSampler"}]}}'
|
||||
normalized_image = {
|
||||
|
||||
@@ -1266,13 +1266,24 @@ export const modelInfoMethods = {
|
||||
<td>${this.renderInfoFieldLabel('wrench', 'Clip Skip', 'Recommended Clip Skip value from the model author, if one is provided.')}</td>
|
||||
<td><span class="mr-info-clip-skip"></span></td>
|
||||
</tr>
|
||||
<tr class="mr-info-description-row mr-info-version-description-row mr-hidden-initial">
|
||||
<td>${this.renderInfoFieldLabel('fileText', 'Version notes', 'Description or release notes for the exact model version matched by SHA-256.')}</td>
|
||||
<td>
|
||||
<div class="mr-info-description-wrap">
|
||||
<div class="mr-info-description mr-info-version-description"></div>
|
||||
<div class="mr-info-description-actions mr-info-version-description-actions mr-hidden-initial">
|
||||
<button type="button" class="mr-info-description-toggle mr-info-version-description-toggle" data-description-target=".mr-info-version-description">Show more</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="mr-info-description-row mr-hidden-initial">
|
||||
<td>${this.renderInfoFieldLabel('fileText', 'Description', 'Model description from CivitAI or local metadata. Long descriptions are shortened until you click Show more.')}</td>
|
||||
<td>
|
||||
<div class="mr-info-description-wrap">
|
||||
<div class="mr-info-description"></div>
|
||||
<div class="mr-info-description-actions mr-hidden-initial">
|
||||
<button type="button" class="mr-info-description-toggle">Show more</button>
|
||||
<button type="button" class="mr-info-description-toggle" data-description-target=".mr-info-description">Show more</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -1765,7 +1776,8 @@ export const modelInfoMethods = {
|
||||
|
||||
const descToggleBtn = event.target.closest('.mr-info-description-toggle');
|
||||
if (descToggleBtn && dialog.contains(descToggleBtn)) {
|
||||
const descEl = dialog.querySelector('.mr-info-description');
|
||||
const targetSelector = descToggleBtn.dataset.descriptionTarget || '.mr-info-description';
|
||||
const descEl = dialog.querySelector(targetSelector);
|
||||
if (!descEl) return;
|
||||
|
||||
const isExpanded = descEl.classList.toggle('is-expanded');
|
||||
@@ -1874,6 +1886,17 @@ export const modelInfoMethods = {
|
||||
base_model: modelData?.base_model || modelData?.baseModel || '',
|
||||
tags: modelData?.tags || [],
|
||||
trained_words: modelData?.trained_words || modelData?.trainedWords || [],
|
||||
description: modelData?.model_description
|
||||
|| modelData?.modelDescription
|
||||
|| modelData?.description
|
||||
|| '',
|
||||
model_description: modelData?.model_description
|
||||
|| modelData?.modelDescription
|
||||
|| modelData?.description
|
||||
|| '',
|
||||
version_description: modelData?.version_description
|
||||
|| modelData?.versionDescription
|
||||
|| '',
|
||||
local_only: true,
|
||||
civitai_checked: false
|
||||
};
|
||||
@@ -1981,6 +2004,13 @@ export const modelInfoMethods = {
|
||||
})
|
||||
}, 'Fetch model metadata');
|
||||
|
||||
const modelDescription = result.model_description
|
||||
|| result.modelDescription
|
||||
|| result.description
|
||||
|| data.model_description
|
||||
|| data.modelDescription
|
||||
|| data.description
|
||||
|| '';
|
||||
const merged = {
|
||||
...data,
|
||||
...result,
|
||||
@@ -1989,6 +2019,13 @@ export const modelInfoMethods = {
|
||||
file_path: result.file_path || data.file_path || resolvedPath,
|
||||
resolved_path: result.resolved_path || data.resolved_path || resolvedPath,
|
||||
sha256: result.sha256 || hash,
|
||||
description: modelDescription,
|
||||
model_description: modelDescription,
|
||||
version_description: result.version_description
|
||||
|| result.versionDescription
|
||||
|| data.version_description
|
||||
|| data.versionDescription
|
||||
|| '',
|
||||
metadata_checked: true,
|
||||
civitai_checked: true
|
||||
};
|
||||
@@ -2049,6 +2086,48 @@ export const modelInfoMethods = {
|
||||
return Boolean(data.metadata_checked || data.metadataChecked || data.civitai_checked || data.civitaiChecked);
|
||||
},
|
||||
|
||||
updateInfoDialogDescriptionSection(dialog, {
|
||||
value = '',
|
||||
contentSelector,
|
||||
actionsSelector,
|
||||
toggleSelector
|
||||
} = {}) {
|
||||
const descEl = dialog.querySelector(contentSelector);
|
||||
if (!descEl) return;
|
||||
|
||||
const row = descEl.closest('tr');
|
||||
if (!value) {
|
||||
this.setInfoTableRowVisible(row, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const actionsEl = dialog.querySelector(actionsSelector);
|
||||
const toggleBtn = dialog.querySelector(toggleSelector);
|
||||
let sanitizedHtml = '';
|
||||
try {
|
||||
sanitizedHtml = this.sanitizeDescriptionHtml(value);
|
||||
} catch (error) {
|
||||
console.error('Model Resolver: Failed to sanitize description HTML:', error);
|
||||
}
|
||||
|
||||
const fallbackText = this.escapeHtml(String(value).replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim());
|
||||
const finalHtml = sanitizedHtml && sanitizedHtml.trim() ? sanitizedHtml : `<p>${fallbackText}</p>`;
|
||||
const textOnly = finalHtml.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
|
||||
descEl.innerHTML = finalHtml;
|
||||
descEl.classList.remove('is-expanded');
|
||||
|
||||
const shouldCollapse = textOnly.length > 520 || finalHtml.length > 900;
|
||||
this.setInfoElementVisible(actionsEl, shouldCollapse);
|
||||
if (toggleBtn) {
|
||||
toggleBtn.textContent = 'Show more';
|
||||
}
|
||||
if (!shouldCollapse) {
|
||||
descEl.classList.add('is-expanded');
|
||||
}
|
||||
this.setInfoTableRowVisible(row, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the info dialog with data
|
||||
*/
|
||||
@@ -2242,44 +2321,29 @@ export const modelInfoMethods = {
|
||||
}
|
||||
}
|
||||
|
||||
// Update description
|
||||
const descEl = dialog.querySelector('.mr-info-description');
|
||||
if (descEl) {
|
||||
const desc = data.description || data.model_description || data.modelDescription || '';
|
||||
if (desc) {
|
||||
const actionsEl = dialog.querySelector('.mr-info-description-actions');
|
||||
const toggleBtn = dialog.querySelector('.mr-info-description-toggle');
|
||||
|
||||
let sanitizedHtml = '';
|
||||
try {
|
||||
sanitizedHtml = this.sanitizeDescriptionHtml(desc);
|
||||
} catch (error) {
|
||||
console.error('Model Resolver: Failed to sanitize description HTML:', error);
|
||||
}
|
||||
|
||||
const fallbackText = this.escapeHtml(String(desc).replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim());
|
||||
const finalHtml = sanitizedHtml && sanitizedHtml.trim() ? sanitizedHtml : `<p>${fallbackText}</p>`;
|
||||
const textOnly = finalHtml.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
|
||||
descEl.innerHTML = finalHtml;
|
||||
descEl.classList.remove('is-expanded');
|
||||
|
||||
const shouldCollapse = textOnly.length > 520 || finalHtml.length > 900;
|
||||
this.setInfoElementVisible(actionsEl, shouldCollapse);
|
||||
if (toggleBtn) {
|
||||
toggleBtn.textContent = 'Show more';
|
||||
}
|
||||
if (!shouldCollapse) {
|
||||
descEl.classList.add('is-expanded');
|
||||
}
|
||||
|
||||
const row = descEl.closest('tr');
|
||||
this.setInfoTableRowVisible(row, true);
|
||||
} else {
|
||||
const row = descEl.closest('tr');
|
||||
this.setInfoTableRowVisible(row, false);
|
||||
}
|
||||
}
|
||||
// Keep the model description stable while exposing version-specific
|
||||
// release notes separately.
|
||||
const modelDescription = data.model_description
|
||||
|| data.modelDescription
|
||||
|| data.description
|
||||
|| '';
|
||||
const versionDescription = data.version_description
|
||||
|| data.versionDescription
|
||||
|| '';
|
||||
this.updateInfoDialogDescriptionSection(dialog, {
|
||||
value: modelDescription,
|
||||
contentSelector: '.mr-info-description:not(.mr-info-version-description)',
|
||||
actionsSelector: '.mr-info-description-actions:not(.mr-info-version-description-actions)',
|
||||
toggleSelector: '.mr-info-description-toggle:not(.mr-info-version-description-toggle)'
|
||||
});
|
||||
this.updateInfoDialogDescriptionSection(dialog, {
|
||||
value: versionDescription && versionDescription !== modelDescription
|
||||
? versionDescription
|
||||
: '',
|
||||
contentSelector: '.mr-info-version-description',
|
||||
actionsSelector: '.mr-info-version-description-actions',
|
||||
toggleSelector: '.mr-info-version-description-toggle'
|
||||
});
|
||||
|
||||
// Update preview media
|
||||
this.updateInfoDialogImages(
|
||||
|
||||
Reference in New Issue
Block a user