diff --git a/ui/plugins/ui/merge.plugin.js b/ui/plugins/ui/merge.plugin.js
new file mode 100644
index 00000000..68884f79
--- /dev/null
+++ b/ui/plugins/ui/merge.plugin.js
@@ -0,0 +1,373 @@
+(function() {
+ "use strict"
+
+ ///////////////////// Function section
+ function smoothstep(x) {
+ return x * x * (3 - 2 * x)
+ }
+
+ function smootherstep(x) {
+ return x * x * x * (x * (x * 6 - 15) + 10)
+ }
+
+ function smootheststep(x) {
+ let y = -20 * Math.pow(x, 7)
+ y += 70 * Math.pow(x, 6)
+ y -= 84 * Math.pow(x, 5)
+ y += 35 * Math.pow(x, 4)
+ return y
+ }
+ function getCurrentTime() {
+ const now = new Date();
+ let hours = now.getHours();
+ let minutes = now.getMinutes();
+ let seconds = now.getSeconds();
+
+ hours = hours < 10 ? `0${hours}` : hours;
+ minutes = minutes < 10 ? `0${minutes}` : minutes;
+ seconds = seconds < 10 ? `0${seconds}` : seconds;
+
+ return `${hours}:${minutes}:${seconds}`;
+ }
+
+ function addLogMessage(message) {
+ const logContainer = document.getElementById('merge-log');
+ logContainer.innerHTML += `${getCurrentTime()} ${message}
`;
+
+ // Scroll to the bottom of the log
+ logContainer.scrollTop = logContainer.scrollHeight;
+ }
+
+ function addLogSeparator() {
+ const logContainer = document.getElementById('merge-log');
+ logContainer.innerHTML += '
'
+
+ logContainer.scrollTop = logContainer.scrollHeight;
+ }
+
+ function drawDiagram(fn) {
+ const canvas = document.getElementById('merge-canvas');
+ canvas.width=canvas.width
+ const ctx = canvas.getContext('2d');
+
+ // Draw coordinate system
+ ctx.scale(1, -1);
+ ctx.translate(0, -canvas.height);
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+
+ ctx.strokeStyle = 'white'
+ ctx.moveTo(0,0); ctx.lineTo(0,400); ctx.lineTo(400,400); ctx.lineTo(400,0); ctx.lineTo(0,0); ctx.lineTo(400,400);
+ ctx.stroke()
+ ctx.beginPath()
+ ctx.setLineDash([1,2])
+ for (let i=40; i<400; i+=40) {
+ ctx.moveTo(0,i)
+ ctx.lineTo(400,i)
+ ctx.moveTo(i,0)
+ ctx.lineTo(i,400)
+ }
+ ctx.stroke()
+ ctx.beginPath()
+ ctx.setLineDash([])
+ ctx.beginPath();
+ ctx.strokeStyle = 'black'
+ ctx.lineWidth = 3;
+ // Plot function
+ const numSamples = 20;
+ for (let i = 0; i <= numSamples; i++) {
+ const x = i / numSamples;
+ const y = fn(x);
+
+ const canvasX = x * 400;
+ const canvasY = y * 400;
+
+ if (i === 0) {
+ ctx.moveTo(canvasX, canvasY);
+ } else {
+ ctx.lineTo(canvasX, canvasY);
+ }
+ }
+ ctx.stroke()
+ // Plot alpha values (yellow boxes)
+ let start = parseFloat( document.querySelector('#merge-start').value )
+ let step = parseFloat( document.querySelector('#merge-step').value )
+ let iterations = document.querySelector('#merge-count').value>>0
+ ctx.beginPath()
+ ctx.fillStyle = "yellow"
+ for (let i=0; i< iterations; i++) {
+ const alpha = ( start + i * step ) / 100
+ const x = alpha*400
+ const y = fn(alpha) * 400
+ if (x <= 400) {
+ ctx.rect(x-3,y-3,6,6)
+ ctx.fill()
+ } else {
+ ctx.strokeStyle = 'red'
+ ctx.moveTo(0,0); ctx.lineTo(0,400); ctx.lineTo(400,400); ctx.lineTo(400,0); ctx.lineTo(0,0); ctx.lineTo(400,400);
+ ctx.stroke()
+ addLogMessage('Warning: maximum ratio is ≥ 100%')
+ }
+ }
+ }
+
+ function updateChart() {
+ let fn = (x) => x
+ switch (document.querySelector('#merge-interpolation').value) {
+ case 'SmoothStep':
+ fn = smoothstep
+ break
+ case 'SmootherStep':
+ fn = smootherstep
+ break
+ case 'SmoothestStep':
+ fn = smootheststep
+ break
+ }
+ drawDiagram(fn)
+ }
+
+ /////////////////////// Tab implementation
+ document.querySelector('#tab-container')?.insertAdjacentHTML('beforeend', `
+
+ Merge models
+
+ `)
+
+ document.querySelector('#tab-content-wrapper')?.insertAdjacentHTML('beforeend', `
+
+ `)
+
+ 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', `
+
+ `)
+
+ merge.innerHTML = `
+ `
+
+
+ /////////////////////// Event Listener
+ document.addEventListener('tabClick', (e) => {
+ if (e.detail.name == 'merge') {
+ console.log('Activate')
+ let modelList = stableDiffusionModelField.cloneNode(true)
+ modelList.id = "mergeModelA"
+ modelList.size = 8
+ document.querySelector("#mergeModelA").replaceWith(modelList)
+ modelList = stableDiffusionModelField.cloneNode(true)
+ modelList.id = "mergeModelB"
+ modelList.size = 8
+ document.querySelector("#mergeModelB").replaceWith(modelList)
+ updateChart()
+ }
+ })
+
+ document.querySelector('.merge-config').addEventListener('change', updateChart)
+
+ document.querySelector('#merge-button').addEventListener('click', async function(e) {
+ // Build request template
+ let model0 = document.querySelector('#mergeModelA').value
+ let model1 = document.querySelector('#mergeModelB').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 )
+ addLogMessage(`start = ${start}%`)
+ addLogMessage(`step = ${step}%`)
+
+ if (start + iterations * (step-1) >= 100) {
+ addLogMessage('Aborting: maximum ratio is ≥ 100%')
+ addLogMessage('Reduce the number of steps or the step size')
+ addLogSeparator()
+ document.querySelector('#merge-count').focus()
+ return
+ }
+
+ if (document.querySelector('#merge-filename').value == "") {
+ addLogMessage('Aborting: No output file name specified')
+ 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; iDone. The models have been saved to your models/stable-diffusion 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()
+ })
+
+})()