Merge pull request #1116 from ogmaresca/createTab-utility-function

Add a utility function to create tabs with lazy loading functionality
This commit is contained in:
cmdr2 2023-04-26 16:21:51 +05:30 committed by GitHub
commit 382dee1fd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 382 additions and 286 deletions

View File

@ -720,9 +720,140 @@ Array.prototype.addEventListener = function(method, callback) {
const originalFunction = this[method] const originalFunction = this[method]
if (originalFunction) { if (originalFunction) {
this[method] = function() { this[method] = function() {
console.log(`Array.${method}()`, arguments)
originalFunction.apply(this, arguments) originalFunction.apply(this, arguments)
callback.apply(this, arguments) callback.apply(this, arguments)
} }
} }
} }
/**
* @typedef {object} TabOpenDetails
* @property {HTMLElement} contentElement
* @property {HTMLElement} labelElement
* @property {number} timesOpened
* @property {boolean} firstOpen
*/
/**
* @typedef {object} CreateTabRequest
* @property {string} id
* @property {string | Node | (() => (string | Node))} label
* Label text or an HTML element
* @property {string} icon
* @property {string | Node | Promise<string | Node> | (() => (string | Node | Promise<string | Node>)) | undefined} content
* HTML string or HTML element
* @property {((TabOpenDetails, Event) => (undefined | string | Node | Promise<string | Node>)) | undefined} onOpen
* If an HTML string or HTML element is returned, then that will replace the tab content
* @property {string | undefined} css
*/
/**
* @param {CreateTabRequest} request
*/
function createTab(request) {
if (!request?.id) {
console.error('createTab() error - id is required', Error().stack)
return
}
if (!request.label) {
console.error('createTab() error - label is required', Error().stack)
return
}
if (!request.icon) {
console.error('createTab() error - icon is required', Error().stack)
return
}
if (!request.content && !request.onOpen) {
console.error('createTab() error - content or onOpen required', Error().stack)
return
}
const tabsContainer = document.querySelector('.tab-container')
if (!tabsContainer) {
return
}
const tabsContentWrapper = document.querySelector('#tab-content-wrapper')
if (!tabsContentWrapper) {
return
}
console.debug('creating tab: ', request)
if (request.css) {
document.querySelector('body').insertAdjacentElement(
'beforeend',
createElement('style', { id: `tab-${request.id}-css` }, undefined, request.css),
)
}
const label = typeof request.label === 'function' ? request.label() : request.label
const labelElement = label instanceof Node ? label : createElement('span', undefined, undefined, label)
const tab = createElement(
'span',
{ id: `tab-${request.id}`, 'data-times-opened': 0 },
['tab'],
createElement(
'span',
undefined,
undefined,
[
createElement(
'i',
{ style: 'margin-right: 0.25em' },
['fa-solid', `${request.icon.startsWith('fa-') ? '' : 'fa-'}${request.icon}`, 'icon'],
),
labelElement,
],
)
)
tabsContainer.insertAdjacentElement('beforeend', tab)
const wrapper = createElement('div', { id: request.id }, ['tab-content-inner'], 'Loading..')
const tabContent = createElement('div', { id: `tab-content-${request.id}` }, ['tab-content'], wrapper)
tabsContentWrapper.insertAdjacentElement('beforeend', tabContent)
linkTabContents(tab)
function replaceContent(resultFactory) {
if (resultFactory === undefined || resultFactory === null) {
return
}
const result = typeof resultFactory === 'function' ? resultFactory() : resultFactory
if (result instanceof Promise) {
result.then(replaceContent)
} else if (result instanceof Node) {
wrapper.replaceChildren(result)
} else {
wrapper.innerHTML = result
}
}
replaceContent(request.content)
tab.addEventListener('click', (e) => {
const timesOpened = +(tab.dataset.timesOpened || 0) + 1
tab.dataset.timesOpened = timesOpened
if (request.onOpen) {
const result = request.onOpen(
{
contentElement: wrapper,
labelElement,
timesOpened,
firstOpen: timesOpened === 1,
},
e,
)
replaceContent(result)
}
})
}

View File

@ -130,34 +130,11 @@
} }
drawDiagram(fn) drawDiagram(fn)
} }
createTab({
/////////////////////// Tab implementation id: 'merge',
document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', ` icon: 'fa-code-merge',
<span id="tab-merge" class="tab"> label: 'Merge models',
<span><i class="fa fa-code-merge icon"></i> Merge models</span> css: `
</span>
`)
document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', `
<div id="tab-content-merge" class="tab-content">
<div id="merge" class="tab-content-inner">
Loading..
</div>
</div>
`)
const tabMerge = document.querySelector('#tab-merge')
if (tabMerge) {
linkTabContents(tabMerge)
}
const merge = document.querySelector('#merge')
if (!merge) {
// merge tab not found, dont exec plugin code.
return
}
document.querySelector('body').insertAdjacentHTML('beforeend', `
<style>
#tab-content-merge .tab-content-inner { #tab-content-merge .tab-content-inner {
max-width: 100%; max-width: 100%;
padding: 10pt; padding: 10pt;
@ -233,226 +210,229 @@
} }
.merge-container #merge-warning { .merge-container #merge-warning {
color: rgb(153, 153, 153); color: rgb(153, 153, 153);
} }`,
</style> content: `
`) <div class="merge-container panel-box">
<div class="merge-input">
merge.innerHTML = ` <p><label for="#mergeModelA">Select Model A:</label></p>
<div class="merge-container panel-box"> <input id="mergeModelA" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
<div class="merge-input"> <p><label for="#mergeModelB">Select Model B:</label></p>
<p><label for="#mergeModelA">Select Model A:</label></p> <input id="mergeModelB" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
<input id="mergeModelA" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" /> <br/><br/>
<p><label for="#mergeModelB">Select Model B:</label></p> <p id="merge-warning"><small><b>Important:</b> Please merge models of similar type.<br/>For e.g. <code>SD 1.4</code> models with only <code>SD 1.4/1.5</code> models,<br/><code>SD 2.0</code> with <code>SD 2.0</code>-type, and <code>SD 2.1</code> with <code>SD 2.1</code>-type models.</small></p>
<input id="mergeModelB" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" /> <br/>
<br/><br/> <table>
<p id="merge-warning"><small><b>Important:</b> Please merge models of similar type.<br/>For e.g. <code>SD 1.4</code> models with only <code>SD 1.4/1.5</code> models,<br/><code>SD 2.0</code> with <code>SD 2.0</code>-type, and <code>SD 2.1</code> with <code>SD 2.1</code>-type models.</small></p> <tr>
<br/> <td><label for="#merge-filename">Output file name:</label></td>
<table> <td><input id="merge-filename" size=24> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Base name of the output file.<br>Mix ratio and file suffix will be appended to this.</span></i></td>
<tr> </tr>
<td><label for="#merge-filename">Output file name:</label></td> <tr>
<td><input id="merge-filename" size=24> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Base name of the output file.<br>Mix ratio and file suffix will be appended to this.</span></i></td> <td><label for="#merge-fp">Output precision:</label></td>
</tr> <td><select id="merge-fp">
<tr> <option value="fp16">fp16 (smaller file size)</option>
<td><label for="#merge-fp">Output precision:</label></td> <option value="fp32">fp32 (larger file size)</option>
<td><select id="merge-fp"> </select>
<option value="fp16">fp16 (smaller file size)</option> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Image generation uses fp16, so it's a good choice.<br>Use fp32 if you want to use the result models for more mixes</span></i>
<option value="fp32">fp32 (larger file size)</option> </td>
</select> </tr>
<i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Image generation uses fp16, so it's a good choice.<br>Use fp32 if you want to use the result models for more mixes</span></i> <tr>
</td> <td><label for="#merge-format">Output file format:</label></td>
</tr> <td><select id="merge-format">
<tr> <option value="safetensors">Safetensors (recommended)</option>
<td><label for="#merge-format">Output file format:</label></td> <option value="ckpt">CKPT/Pickle (legacy format)</option>
<td><select id="merge-format"> </select>
<option value="safetensors">Safetensors (recommended)</option> </td>
<option value="ckpt">CKPT/Pickle (legacy format)</option> </tr>
</select> </table>
</td> <br/>
</tr> <div id="merge-log-container">
</table> <p><label for="#merge-log">Log messages:</label></p>
<br/> <div id="merge-log"></div>
<div id="merge-log-container"> </div>
<p><label for="#merge-log">Log messages:</label></p> </div>
<div id="merge-log"></div> <div class="merge-config">
</div> <div class="tab-container">
</div> <span id="tab-merge-opts-single" class="tab active">
<div class="merge-config"> <span>Make a single file</small></span>
<div class="tab-container"> </span>
<span id="tab-merge-opts-single" class="tab active"> <span id="tab-merge-opts-batch" class="tab">
<span>Make a single file</small></span> <span>Make multiple variations</small></span>
</span> </span>
<span id="tab-merge-opts-batch" class="tab"> </div>
<span>Make multiple variations</small></span> <div>
</span> <div id="tab-content-merge-opts-single" class="tab-content active">
</div> <div class="tab-content-inner">
<div> <small>Saves a single merged model file, at the specified merge ratio.</small><br/><br/>
<div id="tab-content-merge-opts-single" class="tab-content active"> <label for="#single-merge-ratio-slider">Merge ratio:</label>
<div class="tab-content-inner"> <input id="single-merge-ratio-slider" name="single-merge-ratio-slider" class="editor-slider" value="50" type="range" min="1" max="1000">
<small>Saves a single merged model file, at the specified merge ratio.</small><br/><br/> <input id="single-merge-ratio" size=2 value="5">%
<label for="#single-merge-ratio-slider">Merge ratio:</label> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Model A's contribution to the mix. The rest will be from Model B.</span></i>
<input id="single-merge-ratio-slider" name="single-merge-ratio-slider" class="editor-slider" value="50" type="range" min="1" max="1000"> </div>
<input id="single-merge-ratio" size=2 value="5">%
<i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Model A's contribution to the mix. The rest will be from Model B.</span></i>
</div> </div>
</div> <div id="tab-content-merge-opts-batch" class="tab-content">
<div id="tab-content-merge-opts-batch" class="tab-content"> <div class="tab-content-inner">
<div class="tab-content-inner"> <small>Saves multiple variations of the model, at different merge ratios.<br/>Each variation will be saved as a separate file.</small><br/><br/>
<small>Saves multiple variations of the model, at different merge ratios.<br/>Each variation will be saved as a separate file.</small><br/><br/> <table>
<table> <tr><td><label for="#merge-count">Number of variations:</label></td>
<tr><td><label for="#merge-count">Number of variations:</label></td> <td> <input id="merge-count" size=2 value="5"></td>
<td> <input id="merge-count" size=2 value="5"></td> <td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Number of models to create</span></i></td></tr>
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Number of models to create</span></i></td></tr> <tr><td><label for="#merge-start">Starting merge ratio:</label></td>
<tr><td><label for="#merge-start">Starting merge ratio:</label></td> <td> <input id="merge-start" size=2 value="5">%</td>
<td> <input id="merge-start" size=2 value="5">%</td> <td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Smallest share of model A in the mix</span></i></td></tr>
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Smallest share of model A in the mix</span></i></td></tr> <tr><td><label for="#merge-step">Increment each step:</label></td>
<tr><td><label for="#merge-step">Increment each step:</label></td> <td> <input id="merge-step" size=2 value="10">%</td>
<td> <input id="merge-step" size=2 value="10">%</td> <td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Share of model A added into the mix per step</span></i></td></tr>
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Share of model A added into the mix per step</span></i></td></tr> <tr><td><label for="#merge-interpolation">Interpolation model:</label></td>
<tr><td><label for="#merge-interpolation">Interpolation model:</label></td> <td> <select id="merge-interpolation">
<td> <select id="merge-interpolation"> <option>Exact</option>
<option>Exact</option> <option>SmoothStep</option>
<option>SmoothStep</option> <option>SmootherStep</option>
<option>SmootherStep</option> <option>SmoothestStep</option>
<option>SmoothestStep</option> </select></td>
</select></td> <td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Sigmoid function to be applied to the model share before mixing</span></i></td></tr>
<td> <i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip top-left">Sigmoid function to be applied to the model share before mixing</span></i></td></tr> </table>
</table> <br/>
<br/> <small>Preview of variation ratios:</small><br/>
<small>Preview of variation ratios:</small><br/> <canvas id="merge-canvas" width="400" height="400"></canvas>
<canvas id="merge-canvas" width="400" height="400"></canvas> </div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="merge-buttons">
<div class="merge-buttons"> <button id="merge-button" class="primaryButton">Merge models</button>
<button id="merge-button" class="primaryButton">Merge models</button> </div>
</div> </div>`,
</div>` onOpen: ({ firstOpen }) => {
if (!firstOpen) {
const tabSettingsSingle = document.querySelector('#tab-merge-opts-single') return
const tabSettingsBatch = document.querySelector('#tab-merge-opts-batch')
linkTabContents(tabSettingsSingle)
linkTabContents(tabSettingsBatch)
console.log('Activate')
let mergeModelAField = new ModelDropdown(document.querySelector('#mergeModelA'), 'stable-diffusion')
let mergeModelBField = new ModelDropdown(document.querySelector('#mergeModelB'), 'stable-diffusion')
updateChart()
// slider
const singleMergeRatioField = document.querySelector('#single-merge-ratio')
const singleMergeRatioSlider = document.querySelector('#single-merge-ratio-slider')
function updateSingleMergeRatio() {
singleMergeRatioField.value = singleMergeRatioSlider.value / 10
singleMergeRatioField.dispatchEvent(new Event("change"))
}
function updateSingleMergeRatioSlider() {
if (singleMergeRatioField.value < 0) {
singleMergeRatioField.value = 0
} else if (singleMergeRatioField.value > 100) {
singleMergeRatioField.value = 100
}
singleMergeRatioSlider.value = singleMergeRatioField.value * 10
singleMergeRatioSlider.dispatchEvent(new Event("change"))
}
singleMergeRatioSlider.addEventListener('input', updateSingleMergeRatio)
singleMergeRatioField.addEventListener('input', updateSingleMergeRatioSlider)
updateSingleMergeRatio()
document.querySelector('.merge-config').addEventListener('change', updateChart)
document.querySelector('#merge-button').addEventListener('click', async function(e) {
// Build request template
let model0 = mergeModelAField.value
let model1 = mergeModelBField.value
let request = { model0: model0, model1: model1 }
request['use_fp16'] = document.querySelector('#merge-fp').value == 'fp16'
let iterations = document.querySelector('#merge-count').value>>0
let start = parseFloat( document.querySelector('#merge-start').value )
let step = parseFloat( document.querySelector('#merge-step').value )
if (isTabActive(tabSettingsSingle)) {
start = parseFloat(singleMergeRatioField.value)
step = 0
iterations = 1
addLogMessage(`merge ratio = ${start}%`)
} else {
addLogMessage(`start = ${start}%`)
addLogMessage(`step = ${step}%`)
}
if (start + (iterations-1) * step >= 100) {
addLogMessage('<i>Aborting: maximum ratio is &#8805; 100%</i>')
addLogMessage('Reduce the number of variations or the step size')
addLogSeparator()
document.querySelector('#merge-count').focus()
return
}
if (document.querySelector('#merge-filename').value == "") {
addLogMessage('<i>Aborting: No output file name specified</i>')
addLogSeparator()
document.querySelector('#merge-filename').focus()
return
}
// Disable merge button
e.target.disabled=true
e.target.classList.add('disabled')
let cursor = $("body").css("cursor");
let label = document.querySelector('#merge-button').innerHTML
$("body").css("cursor", "progress");
document.querySelector('#merge-button').innerHTML = 'Merging models ...'
addLogMessage("Merging models")
addLogMessage("Model A: "+model0)
addLogMessage("Model B: "+model1)
// Batch main loop
for (let i=0; i<iterations; i++) {
let alpha = ( start + i * step ) / 100
switch (document.querySelector('#merge-interpolation').value) {
case 'SmoothStep':
alpha = smoothstep(alpha)
break
case 'SmootherStep':
alpha = smootherstep(alpha)
break
case 'SmoothestStep':
alpha = smootheststep(alpha)
break
} }
addLogMessage(`merging batch job ${i+1}/${iterations}, alpha = ${alpha.toFixed(5)}...`)
request['out_path'] = document.querySelector('#merge-filename').value const tabSettingsSingle = document.querySelector('#tab-merge-opts-single')
request['out_path'] += '-' + alpha.toFixed(5) + '.' + document.querySelector('#merge-format').value const tabSettingsBatch = document.querySelector('#tab-merge-opts-batch')
addLogMessage(`&nbsp;&nbsp;filename: ${request['out_path']}`) linkTabContents(tabSettingsSingle)
linkTabContents(tabSettingsBatch)
request['ratio'] = alpha console.log('Activate')
let res = await fetch('/model/merge', { let mergeModelAField = new ModelDropdown(document.querySelector('#mergeModelA'), 'stable-diffusion')
method: 'POST', let mergeModelBField = new ModelDropdown(document.querySelector('#mergeModelB'), 'stable-diffusion')
headers: { 'Content-Type': 'application/json' }, updateChart()
body: JSON.stringify(request) })
const data = await res.json();
addLogMessage(JSON.stringify(data))
}
addLogMessage("<b>Done.</b> The models have been saved to your <tt>models/stable-diffusion</tt> folder.")
addLogSeparator()
// Re-enable merge button
$("body").css("cursor", cursor);
document.querySelector('#merge-button').innerHTML = label
e.target.disabled=false
e.target.classList.remove('disabled')
// Update model list // slider
stableDiffusionModelField.innerHTML = '' const singleMergeRatioField = document.querySelector('#single-merge-ratio')
vaeModelField.innerHTML = '' const singleMergeRatioSlider = document.querySelector('#single-merge-ratio-slider')
hypernetworkModelField.innerHTML = ''
await getModels() function updateSingleMergeRatio() {
singleMergeRatioField.value = singleMergeRatioSlider.value / 10
singleMergeRatioField.dispatchEvent(new Event("change"))
}
function updateSingleMergeRatioSlider() {
if (singleMergeRatioField.value < 0) {
singleMergeRatioField.value = 0
} else if (singleMergeRatioField.value > 100) {
singleMergeRatioField.value = 100
}
singleMergeRatioSlider.value = singleMergeRatioField.value * 10
singleMergeRatioSlider.dispatchEvent(new Event("change"))
}
singleMergeRatioSlider.addEventListener('input', updateSingleMergeRatio)
singleMergeRatioField.addEventListener('input', updateSingleMergeRatioSlider)
updateSingleMergeRatio()
document.querySelector('.merge-config').addEventListener('change', updateChart)
document.querySelector('#merge-button').addEventListener('click', async function(e) {
// Build request template
let model0 = mergeModelAField.value
let model1 = mergeModelBField.value
let request = { model0: model0, model1: model1 }
request['use_fp16'] = document.querySelector('#merge-fp').value == 'fp16'
let iterations = document.querySelector('#merge-count').value>>0
let start = parseFloat( document.querySelector('#merge-start').value )
let step = parseFloat( document.querySelector('#merge-step').value )
if (isTabActive(tabSettingsSingle)) {
start = parseFloat(singleMergeRatioField.value)
step = 0
iterations = 1
addLogMessage(`merge ratio = ${start}%`)
} else {
addLogMessage(`start = ${start}%`)
addLogMessage(`step = ${step}%`)
}
if (start + (iterations-1) * step >= 100) {
addLogMessage('<i>Aborting: maximum ratio is &#8805; 100%</i>')
addLogMessage('Reduce the number of variations or the step size')
addLogSeparator()
document.querySelector('#merge-count').focus()
return
}
if (document.querySelector('#merge-filename').value == "") {
addLogMessage('<i>Aborting: No output file name specified</i>')
addLogSeparator()
document.querySelector('#merge-filename').focus()
return
}
// Disable merge button
e.target.disabled=true
e.target.classList.add('disabled')
let cursor = $("body").css("cursor");
let label = document.querySelector('#merge-button').innerHTML
$("body").css("cursor", "progress");
document.querySelector('#merge-button').innerHTML = 'Merging models ...'
addLogMessage("Merging models")
addLogMessage("Model A: "+model0)
addLogMessage("Model B: "+model1)
// Batch main loop
for (let i=0; i<iterations; i++) {
let alpha = ( start + i * step ) / 100
switch (document.querySelector('#merge-interpolation').value) {
case 'SmoothStep':
alpha = smoothstep(alpha)
break
case 'SmootherStep':
alpha = smootherstep(alpha)
break
case 'SmoothestStep':
alpha = smootheststep(alpha)
break
}
addLogMessage(`merging batch job ${i+1}/${iterations}, alpha = ${alpha.toFixed(5)}...`)
request['out_path'] = document.querySelector('#merge-filename').value
request['out_path'] += '-' + alpha.toFixed(5) + '.' + document.querySelector('#merge-format').value
addLogMessage(`&nbsp;&nbsp;filename: ${request['out_path']}`)
request['ratio'] = alpha
let res = await fetch('/model/merge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request) })
const data = await res.json();
addLogMessage(JSON.stringify(data))
}
addLogMessage("<b>Done.</b> The models have been saved to your <tt>models/stable-diffusion</tt> folder.")
addLogSeparator()
// Re-enable merge button
$("body").css("cursor", cursor);
document.querySelector('#merge-button').innerHTML = label
e.target.disabled=false
e.target.classList.remove('disabled')
// Update model list
stableDiffusionModelField.innerHTML = ''
vaeModelField.innerHTML = ''
hypernetworkModelField.innerHTML = ''
await getModels()
})
},
}) })
})() })()

View File

@ -9,56 +9,41 @@
} }
} }
document.querySelector('.tab-container')?.insertAdjacentHTML('beforeend', ` createTab({
<span id="tab-news" class="tab"> id: 'news',
<span><i class="fa fa-bolt icon"></i> What's new?</span> icon: 'fa-bolt',
</span> label: "What's new",
`) css: `
document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', `
<div id="tab-content-news" class="tab-content">
<div id="news" class="tab-content-inner">
Loading..
</div>
</div>
`)
const tabNews = document.querySelector('#tab-news')
if (tabNews) {
linkTabContents(tabNews)
}
const news = document.querySelector('#news')
if (!news) {
// news tab not found, dont exec plugin code.
return
}
document.querySelector('body').insertAdjacentHTML('beforeend', `
<style>
#tab-content-news .tab-content-inner { #tab-content-news .tab-content-inner {
max-width: 100%; max-width: 100%;
text-align: left; text-align: left;
padding: 10pt; padding: 10pt;
} }
</style> `,
`) onOpen: async ({ firstOpen }) => {
if (firstOpen) {
const loadMarkedScriptPromise = loadScript('/media/js/marked.min.js')
loadScript('/media/js/marked.min.js').then(async function() { let appConfig = await fetch('/get/app_config')
let appConfig = await fetch('/get/app_config') if (!appConfig.ok) {
if (!appConfig.ok) { console.error('[release-notes] Failed to get app_config.')
console.error('[release-notes] Failed to get app_config.') return
return }
} appConfig = await appConfig.json()
appConfig = await appConfig.json()
const updateBranch = appConfig.update_branch || 'main'
let releaseNotes = await fetch(`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/${updateBranch}/CHANGES.md`)
if (!releaseNotes.ok) {
console.error('[release-notes] Failed to get CHANGES.md.')
return
}
releaseNotes = await releaseNotes.text()
const updateBranch = appConfig.update_branch || 'main' await loadMarkedScriptPromise
let releaseNotes = await fetch(`https://raw.githubusercontent.com/cmdr2/stable-diffusion-ui/${updateBranch}/CHANGES.md`) return marked.parse(releaseNotes)
if (!releaseNotes.ok) { }
console.error('[release-notes] Failed to get CHANGES.md.') },
return
}
releaseNotes = await releaseNotes.text()
news.innerHTML = marked.parse(releaseNotes)
}) })
})() })()