This commit is contained in:
shadowcz007
2024-08-17 14:22:31 +08:00
parent 496ff41782
commit bfcadde5c3
15 changed files with 544 additions and 2661 deletions
+4 -33
View File
@@ -2,35 +2,7 @@ import { app } from '../../../scripts/app.js'
import { api } from '../../../scripts/api.js'
import { $el } from '../../../scripts/ui.js'
let isScriptLoaded = {}
function loadExternalScript(url,type) {
return new Promise((resolve, reject) => {
if (isScriptLoaded[url]) {
resolve();
return;
}
const existingScript = document.querySelector(`script[src="${url}"]`);
if (existingScript) {
existingScript.onload = () => {
isScriptLoaded[url] = true;
resolve();
};
existingScript.onerror = reject;
return;
}
const script = document.createElement('script');
script.src = url;
script.type = type; // Add this line to load the script as an ES module
script.onload = () => {
isScriptLoaded[url] = true;
resolve();
};
script.onerror = reject;
document.head.appendChild(script);
});
}
import { loadExternalScript } from './common.js'
const getLocalData = key => {
let data = {}
@@ -130,9 +102,9 @@ function get_position_style (ctx, widget_width, y, node_height) {
transformOrigin: '0 0',
transform: transform,
left:
document.querySelector('.comfy-menu').style.display === 'none'
? `60px`
: `0`,
document.querySelector('.comfy-menu').style.display === 'none'
? `60px`
: `0`,
top: `0`,
cursor: 'pointer',
position: 'absolute',
@@ -299,7 +271,6 @@ app.registerExtension({
if (nodeType.comfyClass == '3DImage') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = async function () {
await loadExternalScript(
'/mixlab/app/lib/model-viewer.min.js',
'module'
+1 -61
View File
@@ -4,28 +4,11 @@ import { api } from '../../../scripts/api.js'
import { td_bg } from './td_background.js'
// console.log('td_bg', td_bg)
import { getUrl, base64Df, get_position_style, getObjectInfo } from './common.js'
//本机安装的插件节点全集
window._nodesAll = null
//获取当前系统的插件,节点清单
function getObjectInfo () {
return new Promise(async (resolve, reject) => {
let url = getUrl()
try {
const response = await fetch(`${url}/object_info`)
const data = await response.json()
resolve(data)
} catch (error) {
reject(error)
}
})
}
const base64Df =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAALZJREFUKFOFkLERwjAQBPdbgBkInECGaMLUQDsE0AkRVRAYWqAByxldPPOWHwnw4OBGye1p50UDSoA+W2ABLPN7i+C5dyC6R/uiAUXRQCs0bXoNIu4QPQzAxDKxHoALOrZcqtiyR/T6CXw7+3IGHhkYcy6BOR2izwT8LptG8rbMiCRAUb+CQ6WzQVb0SNOi5Z2/nX35DRyb/ENazhpWKoGwrpD6nICp5c2qogc4of+c7QcrhgF4Aa/aoAFHiL+RAAAAAElFTkSuQmCC'
const parseImageToBase64 = url => {
return new Promise((res, rej) => {
fetch(url)
@@ -45,42 +28,6 @@ const parseImageToBase64 = url => {
})
}
function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 12 // the margin around the html element
/* Create a transform that deals with all the scrolling and zooming */
const elRect = ctx.canvas.getBoundingClientRect()
const transform = new DOMMatrix()
.scaleSelf(
elRect.width / ctx.canvas.width,
elRect.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, MARGIN + y)
return {
transformOrigin: '0 0',
transform: transform,
left:
document.querySelector('.comfy-menu').style.display === 'none'
? `60px`
: `0`,
top: `0`,
cursor: 'pointer',
position: 'absolute',
maxWidth: `${widget_width - MARGIN * 2}px`,
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
width: `${widget_width - MARGIN * 2}px`,
// height: `${node_height * 0.3 - MARGIN * 2}px`,
// background: '#EEEEEE',
display: 'flex',
flexDirection: 'column',
// alignItems: 'center',
justifyContent: 'flex-start',
zIndex: 9999999
}
}
async function drawImageToCanvas (imageUrl, sFactor = 320) {
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
@@ -282,13 +229,6 @@ async function extractInputAndOutputData (
return { input, output, seed, seedTitle }
}
function getUrl () {
let api_host = `${window.location.hostname}:${window.location.port}`
let api_base = ''
let url = `${window.location.protocol}//${api_host}${api_base}`
return url
}
const getLocalData = key => {
let data = {}
try {
+98 -2
View File
@@ -1,3 +1,5 @@
import { getUrl } from "./common.js"
async function* completion (url, messages, controller) {
let data = {
model: 'gpt-3.5-turbo-16k',
@@ -92,8 +94,9 @@ async function* completion (url, messages, controller) {
// return (await response.json()).content
}
export async function completion_ (url, messages, controller, callback) {
let request = await completion(url, messages, controller)
export async function completion_ (apiKey,url, messages, controller, callback) {
// let request = await completion(url, messages, controller)
let request=await chatCompletion(apiKey,url, messages, controller)
for await (const chunk of request) {
let content = chunk.data.choices[0].delta.content || ''
if (chunk.data.choices[0].role == 'assistant') {
@@ -104,3 +107,96 @@ export async function completion_ (url, messages, controller, callback) {
if (callback) callback(content)
}
}
export async function* chatCompletion(apiKey, url,messages,controller){
// const apiKey = 'YOUR_API_KEY'
url = `${getUrl()}/chat/completions`
const requestBody = {
model: '01-ai/Yi-1.5-9B-Chat-16K',
messages: messages,
stream: true,
key:apiKey
}
let response=await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
signal: controller.signal
},
body: JSON.stringify(requestBody),
mode: 'cors' // This is to ensure the request is made with CORS
})
const reader = response.body.getReader()
const decoder = new TextDecoder()
let content = ''
let leftover = '' // Buffer for partially read lines
try {
let cont = true
while (cont) {
let result = await reader.read()
if (result.done) {
break
}
// Add any leftover data to the current chunk of data
const text = leftover + decoder.decode(result.value)
// Check if the last character is a line break
const endsWithLineBreak = text.endsWith('\n')
// Split the text into lines
let lines = text.split('\n')
// If the text doesn't end with a line break, then the last line is incomplete
// Store it in leftover to be added to the next chunk of data
if (!endsWithLineBreak) {
leftover = lines.pop()
} else {
leftover = '' // Reset leftover if we have a line break at the end
}
// Parse all sse events and add them to result
const regex = /^(\S+):\s(.*)$/gm
for (const line of lines) {
const match = regex.exec(line)
if (match) {
result[match[1]] = match[2]
// since we know this is llama.cpp, let's just decode the json in data
if (result.data) {
result.data = JSON.parse(result.data)
console.log('#result.data',result.data)
content += result.data.choices[0].delta?.content || ''
// yield
yield result
// if we got a stop token from server, we will break here
if (result.data.choices[0].finish_reason == 'stop') {
if (result.data.generation_settings) {
// generation_settings = result.data.generation_settings;
}
cont = false
break
}
}
}
}
}
} catch (e) {
console.error('llama error: ', e)
throw e
} finally {
controller.abort()
}
return content
}
-697
View File
@@ -1,697 +0,0 @@
function get_url () {
// 如果有缓存记录
let hostUrl = localStorage.getItem('_hostUrl') || ''
if (hostUrl) {
return hostUrl
}
let api_host = `${window.location.hostname}:${window.location.port}`
let api_base = ''
let url = `${window.location.protocol}//${api_host}${api_base}`
return url
}
function getFilenameAndCategoryFromUrl (url) {
const queryString = url.split('?')[1]
if (!queryString) {
return {}
}
const params = new URLSearchParams(queryString)
const filename = params.get('filename')
? decodeURIComponent(params.get('filename'))
: null
const category = params.get('category')
? decodeURIComponent(params.get('category') || '')
: ''
return { category, filename }
}
async function get_my_app (category = '', filename = null) {
let url = get_url()
const res = await fetch(`${url}/mixlab/workflow`, {
method: 'POST',
mode: 'cors', // 允许跨域请求
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
task: 'my_app',
filename,
category
})
})
let result = await res.json()
let data = []
try {
for (const res of result.data) {
let { output, app } = res.data
if (app.filename)
data.push({
...app,
data: output,
date: res.date
})
}
} catch (error) {}
return data
}
async function getAppInit () {
const { category, filename } = getFilenameAndCategoryFromUrl(
window.location.href
)
return await get_my_app(category, filename)
}
function success (isSuccess, btn, text) {
isSuccess ? (btn.innerText = 'success') : text
setTimeout(() => {
btn.innerText = text
}, 5000)
}
async function interrupt () {
try {
await fetch(`${get_url()}/interrupt`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: undefined
})
} catch (error) {
console.error(error)
}
return true
}
async function getQueue (clientId) {
try {
const res = await fetch(`${get_url()}/queue`)
const data = await res.json()
return {
// Running action uses a different endpoint for cancelling
Running: Array.from(data.queue_running, prompt => {
if (prompt[3].client_id === clientId) {
let prompt_id = prompt[1]
return {
prompt_id,
remove: () => interrupt()
}
}
}),
Pending: data.queue_pending.map(prompt => ({ prompt }))
}
} catch (error) {
console.error(error)
return { Running: [], Pending: [] }
}
}
// 请求历史数据
async function getPromptResult (category) {
let url = get_url()
try {
const response = await fetch(`${url}/mixlab/prompt_result`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'all'
})
})
if (response.ok) {
const data = await response.json()
console.log('#getPromptResult:', category, data)
return data.result.filter(r => r.appInfo.category == category)
// 处理返回的数据
} else {
console.log('Error:', response.status)
// 处理错误情况
}
} catch (error) {
console.log('Error:', error)
// 处理异常情况
}
}
// 新的运行工作流的接口
function queuePromptNew (
filename,
category,
seed,
input,
client_id,
apps = null
) {
let url = get_url()
// var filename = "Text-to-Image_1.json", category = "";
// 随机seed
// promptWorkflow = randomSeed(seed, promptWorkflow);
let d = { filename, category, seed, input, client_id }
if (apps) {
d.apps = apps
}
const data = JSON.stringify(d)
return new Promise((res, rej) => {
fetch(`${url}/mixlab/prompt`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: data
})
.then(response => {
if (!response.ok) {
// Handle HTTP error responses
if (response.status === 400) {
return response.json().then(errorData => {
// Process the error data
console.error('Error 400:', errorData)
alert(JSON.stringify(errorData, null, 2))
res(null)
})
}
throw new Error('Network response was not ok')
}
return response.json() // Process the response data
})
.then(data => {
// Handle the response data
console.log('Success:', data)
res(true)
})
.catch(error => {
// Handle fetch errors
console.error('Fetch error:', error)
res(null)
})
})
}
// 保存历史数据
async function savePromptResult (data) {
let url = get_url()
try {
const response = await fetch(`${url}/mixlab/prompt_result`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'save',
data
})
})
if (response.ok) {
const res = await response.json()
console.log('Response:', res)
return res
// 处理返回的数据
} else {
console.log('Error:', response.status)
// 处理错误情况
}
} catch (error) {
console.log('Error:', error)
// 处理异常情况
}
}
async function uploadImage (blob, fileType = '.png', filename) {
const body = new FormData()
body.append(
'image',
new File([blob], (filename || new Date().getTime()) + fileType)
)
const url = get_url()
const resp = await fetch(`${url}/upload/image`, {
method: 'POST',
body
})
let data = await resp.json()
// console.log(data)
let { name, subfolder } = data
let src = `${url}/view?filename=${encodeURIComponent(
name
)}&type=input&subfolder=${subfolder}&rand=${Math.random()}`
return { url: src, name }
}
async function uploadMask (arrayBuffer, imgurl) {
const body = new FormData()
const filename = 'clipspace-mask-' + performance.now() + '.png'
let original_url = new URL(imgurl)
const original_ref = { filename: original_url.searchParams.get('filename') }
let original_subfolder = original_url.searchParams.get('subfolder')
if (original_subfolder) original_ref.subfolder = original_subfolder
let original_type = original_url.searchParams.get('type')
if (original_type) original_ref.type = original_type
body.append('image', arrayBuffer, filename)
body.append('original_ref', JSON.stringify(original_ref))
body.append('type', 'input')
body.append('subfolder', 'clipspace')
const url = get_url()
const resp = await fetch(`${url}/upload/mask`, {
method: 'POST',
body
})
// console.log(resp)
let data = await resp.json()
let { name, subfolder, type } = data
let src = `${url}/view?filename=${encodeURIComponent(
name
)}&type=${type}&subfolder=${subfolder}&rand=${Math.random()}`
return { url: src, name: 'clipspace/' + name }
}
const parseImageToBase64 = url => {
return new Promise((res, rej) => {
fetch(url)
.then(response => response.blob())
.then(blob => {
const reader = new FileReader()
reader.onloadend = () => {
const base64data = reader.result
res(base64data)
// 在这里可以将base64数据用于进一步处理或显示图片
}
reader.readAsDataURL(blob)
})
.catch(error => {
console.log('发生错误:', error)
})
})
}
function createImage (url) {
let im = new Image()
return new Promise((res, rej) => {
im.onload = () => res(im)
im.src = url
})
}
function convertImageToBlackBasedOnAlpha (image) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
// Draw the image onto the canvas
canvas.width = image.width
canvas.height = image.height
ctx.drawImage(image, 0, 0)
// Get the image data from the canvas
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
const pixels = imageData.data
// Modify the RGB values based on the alpha channel
for (let i = 0; i < pixels.length; i += 4) {
const alpha = pixels[i + 3]
if (alpha !== 0) {
// Set non-transparent pixels to black
// 蒙版是黑色?
pixels[i] = 0 // Red
pixels[i + 1] = 255 // Green
pixels[i + 2] = 0 // Blue
}
}
// Put the modified image data back onto the canvas
ctx.putImageData(imageData, 0, 0)
// Convert the modified canvas to base64 data URL
const base64ImageData = canvas.toDataURL('image/png') // Replace 'png' with your desired image format
return base64ImageData
}
const blobToBase64 = blob => {
return new Promise((res, rej) => {
const reader = new FileReader()
reader.onloadend = () => {
const base64data = reader.result
res(base64data)
// 在这里可以将base64数据用于进一步处理或显示图片
}
reader.readAsDataURL(blob)
})
}
function base64ToBlob (base64) {
// 去除base64编码中的前缀
const base64WithoutPrefix = base64.replace(/^data:image\/\w+;base64,/, '')
// 将base64编码转换为字节数组
const byteCharacters = atob(base64WithoutPrefix)
// 创建一个存储字节数组的数组
const byteArrays = []
// 将字节数组放入数组中
for (let offset = 0; offset < byteCharacters.length; offset += 1024) {
const slice = byteCharacters.slice(offset, offset + 1024)
const byteNumbers = new Array(slice.length)
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i)
}
const byteArray = new Uint8Array(byteNumbers)
byteArrays.push(byteArray)
}
// 创建blob对象
const blob = new Blob(byteArrays, { type: 'image/png' }) // 根据实际情况设置MIME类型
return blob
}
async function calculateImageHash (blob) {
const buffer = await blob.arrayBuffer()
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray
.map(byte => byte.toString(16).padStart(2, '0'))
.join('')
return hashHex
}
// 获取 rembg 模型
async function get_rembg_models () {
try {
const response = await fetch(`${get_url()}/mixlab/folder_paths`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'rembg'
})
})
const data = await response.json()
// console.log(data)
return data.names
} catch (error) {
console.error(error)
}
}
//自动抠图
async function run_rembg (model, base64) {
try {
const response = await fetch(`${get_url()}/mixlab/rembg`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
model,
base64
})
})
const data = await response.json()
// console.log(data)
return data.data
} catch (error) {
console.error(error)
}
}
function copyHtmlWithImagesToClipboard (data, cb) {
// 创建一个临时div元素
const tempDiv = document.createElement('div')
// 将HTML字符串赋值给div的innerHTML属性
tempDiv.innerHTML = data
// 获取div中的所有图像元素
const images = tempDiv.getElementsByTagName('img')
// 遍历图像元素,并将图像数据转换为Base64编码
for (let i = 0; i < images.length; i++) {
const image = images[i]
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
// 设置canvas尺寸与图像尺寸相同
canvas.width = image.width
canvas.height = image.height
// 在canvas上绘制图像
context.drawImage(image, 0, 0)
// 将canvas转换为Base64编码
const imageData = canvas.toDataURL()
// 将Base64编码替换图像元素的src属性
image.src = imageData
}
let richText = tempDiv.innerHTML
// 创建一个新的Blob对象,并将富文本字符串作为数据传递进去
const blob = new Blob([richText], { type: 'text/html' })
// 创建一个ClipboardItem对象,并将Blob对象添加到其中
const clipboardItem = new ClipboardItem({ 'text/html': blob })
// 使用Clipboard API将内容复制到剪贴板
navigator.clipboard
.write([clipboardItem])
.then(() => {
console.log('富文本已成功复制到剪贴板')
tempDiv.remove()
if (cb) cb(true)
})
.catch(error => {
console.error('复制到剪贴板失败:', error)
tempDiv.remove()
if (cb) cb(false)
})
}
function copyImagesToClipboard (html, cb) {
const tempDiv = document.createElement('div')
tempDiv.innerHTML = html
const images = tempDiv.querySelectorAll('img')
const promises = Array.from(images).map(image => {
return new Promise(resolve => {
const img = new Image()
img.src = image.src
img.onload = () => {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
canvas.width = img.width
canvas.height = img.height
context.drawImage(img, 0, 0)
canvas.toBlob(blob => {
const clipboardItem = new ClipboardItem({ 'image/png': blob })
navigator.clipboard
.write([clipboardItem])
.then(() => {
resolve()
tempDiv.remove()
if (cb) cb(true)
})
.catch(error => {
reject(error)
tempDiv.remove()
if (cb) cb(false)
})
})
}
})
})
Promise.all([...promises])
.then(() => {
console.log('所有图片已成功复制到剪贴板')
if (cb) cb(true)
tempDiv.remove()
})
.catch(error => {
console.error('复制到剪贴板失败:', error)
if (cb) cb(false)
tempDiv.remove()
})
}
function copyTextToClipboard (html, cb) {
const tempDiv = document.createElement('div')
tempDiv.innerHTML = html
const text = tempDiv.innerText
const textData = new ClipboardItem({
'text/plain': new Blob([text], { type: 'text/plain' })
})
navigator.clipboard
.write([textData])
.then(() => {
console.log('所有文本已成功复制到剪贴板', text)
if (cb) cb(true)
tempDiv.remove()
})
.catch(error => {
console.error('复制到剪贴板失败:', error)
if (cb) cb(false)
tempDiv.remove()
})
}
// ComfyUI\web\extensions\core\dynamicPrompts.js
// 官方实现修改
// Allows for simple dynamic prompt replacement
// Inputs in the format {a|b} will have a random value of a or b chosen when the prompt is queued.
/*
* Strips C-style line and block comments from a string
*/
function dynamicPrompts (prompt) {
prompt = prompt.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '')
while (
prompt.replace('\\{', '').includes('{') &&
prompt.replace('\\}', '').includes('}')
) {
const startIndex = prompt.replace('\\{', '00').indexOf('{')
const endIndex = prompt.replace('\\}', '00').indexOf('}')
const optionsString = prompt.substring(startIndex + 1, endIndex)
const options = optionsString.split('|')
const randomIndex = Math.floor(Math.random() * options.length)
const randomOption = options[randomIndex]
prompt =
prompt.substring(0, startIndex) +
randomOption +
prompt.substring(endIndex + 1)
}
return prompt
}
// 遍历所有组合,语法同 动态提示
function generateAllCombinations (prompt) {
prompt = prompt.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '')
// Helper function to get all combinations
function getAllCombinations (parts) {
if (parts.length === 0) return ['']
const [firstPart, ...restParts] = parts
const restCombinations = getAllCombinations(restParts)
const allCombinations = []
firstPart.forEach(option => {
restCombinations.forEach(combination => {
allCombinations.push(option + combination)
})
})
return allCombinations
}
// Split prompt into static parts and dynamic parts
let parts = []
let startIndex = 0
while (
prompt.replace('\\{', '').includes('{') &&
prompt.replace('\\}', '').includes('}')
) {
startIndex = prompt.replace('\\{', '00').indexOf('{')
const endIndex = prompt.replace('\\}', '00').indexOf('}')
const staticPart = prompt.substring(0, startIndex)
const optionsString = prompt.substring(startIndex + 1, endIndex)
const options = optionsString.split('|')
parts.push([staticPart])
parts.push(options)
prompt = prompt.substring(endIndex + 1)
}
// Add the remaining static part
parts.push([prompt])
// Get all combinations
const combinations = getAllCombinations(parts)
return combinations
}
const _textNodes = [
'TextInput_',
'CLIPTextEncode',
'PromptSimplification',
'ChinesePrompt_Mix'
],
_loraNodes = ['CheckpointLoaderSimple', 'LoraLoader'],
_numberNodes = ['FloatSlider', 'IntNumber'],
_slideNodes = ['PromptSlide'],
_imageNodes = [
'LoadImage',
'VHS_LoadVideo',
'ImagesPrompt_',
'LoadImagesToBatch'
],
_colorNodes = ['Color'],
_audioNodes = ['LoadAndCombinedAudio_']
export default {
get_url,
get_my_app,
getAppInit,
getFilenameAndCategoryFromUrl,
success,
interrupt,
getQueue,
queuePromptNew,
savePromptResult,
uploadImage,
uploadMask,
run_rembg,
get_rembg_models,
parseImageToBase64,
createImage,
convertImageToBlackBasedOnAlpha,
blobToBase64,
base64ToBlob,
calculateImageHash,
copyHtmlWithImagesToClipboard,
copyImagesToClipboard,
copyTextToClipboard,
dynamicPrompts,
generateAllCombinations,
_textNodes,
_loraNodes,
_numberNodes,
_slideNodes,
_imageNodes,
_colorNodes,
_audioNodes
}
+168
View File
@@ -0,0 +1,168 @@
export const base64Df =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAALZJREFUKFOFkLERwjAQBPdbgBkInECGaMLUQDsE0AkRVRAYWqAByxldPPOWHwnw4OBGye1p50UDSoA+W2ABLPN7i+C5dyC6R/uiAUXRQCs0bXoNIu4QPQzAxDKxHoALOrZcqtiyR/T6CXw7+3IGHhkYcy6BOR2izwT8LptG8rbMiCRAUb+CQ6WzQVb0SNOi5Z2/nX35DRyb/ENazhpWKoGwrpD6nICp5c2qogc4of+c7QcrhgF4Aa/aoAFHiL+RAAAAAElFTkSuQmCC'
export function getUrl () {
let api_host = `${window.location.hostname}:${window.location.port}`
let api_base = ''
let url = `${window.location.protocol}//${api_host}${api_base}`
return url
}
// 更新或者获取key
export const updateLLMAPIKey = async key => {
try {
const res = await fetch(`${getUrl()}/mixlab/llm_api_key`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
key: key || null
})
})
const data = await res.json()
if (!res.ok) {
console.error('Error:', data.error)
return
}
if (key) {
console.log('API key saved successfully:', data.message)
return key
} else {
console.log('Retrieved API key:', data.key)
return data.key
}
} catch (error) {
console.error('Request failed:', error)
}
}
//获取当前系统的插件,节点清单
export function getObjectInfo () {
return new Promise(async (resolve, reject) => {
let url = getUrl()
try {
const response = await fetch(`${url}/object_info`)
const data = await response.json()
resolve(data)
} catch (error) {
reject(error)
}
})
}
export function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 0 // the margin around the html element
/* Create a transform that deals with all the scrolling and zooming */
const elRect = ctx.canvas.getBoundingClientRect()
const transform = new DOMMatrix()
.scaleSelf(
elRect.width / ctx.canvas.width,
elRect.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, MARGIN + y)
return {
transformOrigin: '0 0',
transform: transform,
left:
document.querySelector('.comfy-menu').style.display === 'none'
? `60px`
: `0`,
top: `0`,
cursor: 'pointer',
position: 'absolute',
maxWidth: `${widget_width - MARGIN * 2}px`,
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
// width: `${widget_width - MARGIN * 2}px`,
height: `${node_height * 0.3 - MARGIN * 2}px`,
// background: '#EEEEEE',
display: 'flex',
flexDirection: 'column',
// alignItems: 'center',
justifyContent: 'flex-start',
zIndex: 99
}
}
export function loadExternalScript (url, type) {
return new Promise((resolve, reject) => {
const existingScript = document.querySelector(`script[src="${url}"]`)
if (existingScript) {
existingScript.onload = () => {
resolve()
}
existingScript.onerror = reject
return
}
const script = document.createElement('script')
script.src = url
if (type) script.type = type // Add this line to load the script as an ES module
script.onload = () => {
resolve()
}
script.onerror = reject
document.head.appendChild(script)
})
}
export async function getQueue () {
try {
const res = await fetch(`${getUrl()}/queue`)
const data = await res.json()
// console.log(data.queue_running,data.queue_pending)
return {
// Running action uses a different endpoint for cancelling
Running: data.queue_running.length,
Pending: data.queue_pending.length
}
} catch (error) {
console.error(error)
return { Running: 0, Pending: 0 }
}
}
export async function interrupt () {
const resp = await fetch(`${getUrl()}/interrupt`, {
method: 'POST'
})
}
export async function sleep (t = 200) {
return new Promise((res, rej) => {
setTimeout(() => {
res(true)
}, t)
})
}
export function createImage (url) {
let im = new Image()
return new Promise((res, rej) => {
im.onload = () => res(im)
im.src = url
})
}
export const getLocalData = key => {
let data = {}
try {
data = JSON.parse(localStorage.getItem(key)) || {}
} catch (error) {
return {}
}
return data
}
export const saveLocalData = (key, id, val) => {
let data = getLocalData(key)
data[id] = val
localStorage.setItem(key, JSON.stringify(data))
}
+11 -81
View File
@@ -3,31 +3,17 @@ import { app } from '../../../scripts/app.js'
import { ComfyWidgets } from '../../../scripts/widgets.js'
import { $el } from '../../../scripts/ui.js'
let api_host = `${window.location.hostname}:${window.location.port}`
let api_base = ''
let url = `${window.location.protocol}//${api_host}${api_base}`
import {
getQueue,
interrupt,
get_position_style,
base64Df,
getUrl,
createImage,
sleep
} from './common.js'
async function getQueue () {
try {
const res = await fetch(`${url}/queue`)
const data = await res.json()
// console.log(data.queue_running,data.queue_pending)
return {
// Running action uses a different endpoint for cancelling
Running: data.queue_running.length,
Pending: data.queue_pending.length
}
} catch (error) {
console.error(error)
return { Running: 0, Pending: 0 }
}
}
async function interrupt () {
const resp = await fetch(`${url}/interrupt`, {
method: 'POST'
})
}
// let url = getUrl()
async function clipboardWriteImage (win, url) {
const canvas = document.createElement('canvas')
@@ -208,22 +194,7 @@ async function shareScreen (
}
}
async function sleep (t = 200) {
return new Promise((res, rej) => {
setTimeout(() => {
res(true)
}, t)
})
}
function createImage (url) {
let im = new Image()
return new Promise((res, rej) => {
im.onload = () => res(im)
im.src = url
})
}
async function compareImages (threshold, previousImage, currentImage) {
// 将 base64 转换为 Image 对象
var previousImg = await createImage(previousImage)
@@ -458,47 +429,6 @@ async function requestCamera () {
return false
}
/*
A method that returns the required style for the html
*/
function get_position_style (ctx, widget_width, y, node_height, top) {
const MARGIN = 4 // the margin around the html element
/* Create a transform that deals with all the scrolling and zooming */
const elRect = ctx.canvas.getBoundingClientRect()
const transform = new DOMMatrix()
.scaleSelf(
elRect.width / ctx.canvas.width,
elRect.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, MARGIN + y)
return {
transformOrigin: '0 0',
transform: transform,
left:
document.querySelector('.comfy-menu').style.display === 'none'
? `60px`
: `0`,
top: `${top}px`,
cursor: 'pointer',
position: 'absolute',
maxWidth: `${widget_width - MARGIN * 2}px`,
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
width: `${widget_width - MARGIN * 2}px`,
// height: `${node_height - MARGIN * 2}px`,
// background: '#EEEEEE',
display: 'flex',
flexDirection: 'column',
// alignItems: 'center',
justifyContent: 'space-around'
}
}
const base64Df =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAALZJREFUKFOFkLERwjAQBPdbgBkInECGaMLUQDsE0AkRVRAYWqAByxldPPOWHwnw4OBGye1p50UDSoA+W2ABLPN7i+C5dyC6R/uiAUXRQCs0bXoNIu4QPQzAxDKxHoALOrZcqtiyR/T6CXw7+3IGHhkYcy6BOR2izwT8LptG8rbMiCRAUb+CQ6WzQVb0SNOi5Z2/nX35DRyb/ENazhpWKoGwrpD6nICp5c2qogc4of+c7QcrhgF4Aa/aoAFHiL+RAAAAAElFTkSuQmCC'
app.registerExtension({
name: 'Mixlab.image.ScreenShareNode',
async getCustomWidgets (app) {
+171 -144
View File
@@ -11,6 +11,8 @@ import { smart_init, addSmartMenu } from './smart_connect.js'
import { completion_ } from './chat.js'
import { getLocalData, saveLocalData, updateLLMAPIKey } from './common.js'
function showTextByLanguage (key, json) {
// 获取浏览器语言
var language = navigator.language
@@ -28,33 +30,40 @@ function showTextByLanguage (key, json) {
//系统prompt
// const systemPrompt = `You are a prompt creator, your task is to create prompts for the user input request, the prompts are image descriptions that include keywords for (an adjective, type of image, framing/composition, subject, subject appearance/action, environment, lighting situation, details of the shoot/illustration, visuals aesthetics and artists), brake keywords by comas, provide high quality, non-verboose, coherent, brief, concise, and not superfluous prompts, the subject from the input request must be included verbatim on the prompt,the prompt is english`
let tool ={
"name": "create_prompt",
"description": "Create a prompt with a given subject, content, and style based on user input for image descriptions.",
"parameter": {
"type": "object",
"properties": {
"subject": {
"type": "string",
"description": "The subject of the prompt, included verbatim from the input request.",
"required": true
let tool = {
name: 'create_prompt',
description:
'Create a prompt with a given subject, content, and style based on user input for image descriptions.',
parameter: {
type: 'object',
properties: {
subject: {
type: 'string',
description:
'The subject of the prompt, included verbatim from the input request.',
required: true
},
"content": {
"type": "string",
"description": "The content of the prompt, primarily focusing on the scene and objects, including keywords for adjective, type of image, framing/composition, subject appearance/action, and environment.",
"required": true
content: {
type: 'string',
description:
'The content of the prompt, primarily focusing on the scene and objects, including keywords for adjective, type of image, framing/composition, subject appearance/action, and environment.',
required: true
},
"style": {
"type": "string",
"description": "The style of the prompt, including lighting situation, details of the shoot/illustration, visual aesthetics, and artists. Ensure it is high quality, non-verbose, coherent, brief, concise, and not superfluous.",
"required": true
style: {
type: 'string',
description:
'The style of the prompt, including lighting situation, details of the shoot/illustration, visual aesthetics, and artists. Ensure it is high quality, non-verbose, coherent, brief, concise, and not superfluous.',
required: true
}
}
}
}
const systemPrompt=`You are a helpful assistant with access to the following functions. Use them if required - ${JSON.stringify(tool,null,2)}`
const systemPrompt = `You are a helpful assistant with access to the following functions. Use them if required - ${JSON.stringify(
tool,
null,
2
)}`
if (!localStorage.getItem('_mixlab_system_prompt')) {
localStorage.setItem('_mixlab_system_prompt', systemPrompt)
@@ -100,7 +109,7 @@ async function start_llama (model = 'Phi-3-mini-4k-instruct-Q5_K_S.gguf') {
})
const data = await response.json()
if (data.llama_cpp_error||!data.port) {
if (data.llama_cpp_error || !data.port) {
return
}
@@ -145,14 +154,15 @@ function resizeImage (base64Image) {
})
}
const createMixlabBtn=()=>{
const createMixlabBtn = () => {
const appsButton = document.createElement('button')
appsButton.id = 'mixlab_chatbot_by_llamacpp'
appsButton.className="comfyui-button"
appsButton.className = 'comfyui-button'
appsButton.textContent = '♾️Mixlab'
// appsButton.onclick = () =>
appsButton.onclick = async () => {
let llm_key = await updateLLMAPIKey()
// if (window._mixlab_llamacpp&&window._mixlab_llamacpp.model&&window._mixlab_llamacpp.model.length>0) {
// //显示运行的模型
// createModelsModal([
@@ -164,9 +174,7 @@ const createMixlabBtn=()=>{
// // ms = ms.filter(m => !m.match('-mmproj-'))
// // if (ms.length > 0) createModelsModal(ms)
// }
createModelsModal([
])
createModelsModal([], llm_key)
}
return appsButton
}
@@ -182,50 +190,49 @@ async function createMenu () {
`
menu.append(separator)
if(menu.style.display==="none"&&document.querySelector('.comfyui-menu-push')){
if (
menu.style.display === 'none' &&
document.querySelector('.comfyui-menu-push')
) {
//新版ui
document.querySelector('.comfyui-menu-push').append(createMixlabBtn())
}else{
} else {
if (!menu.querySelector('#mixlab_chatbot_by_llamacpp')) {
menu.append(createMixlabBtn())
}
}
}
let isScriptLoaded = {}
function loadExternalScript(url) {
function loadExternalScript (url) {
return new Promise((resolve, reject) => {
if (isScriptLoaded[url]) {
resolve();
return;
resolve()
return
}
const existingScript = document.querySelector(`script[src="${url}"]`);
const existingScript = document.querySelector(`script[src="${url}"]`)
if (existingScript) {
existingScript.onload = () => {
isScriptLoaded[url] = true;
resolve();
};
existingScript.onerror = reject;
return;
isScriptLoaded[url] = true
resolve()
}
existingScript.onerror = reject
return
}
const script = document.createElement('script');
script.src = url;
const script = document.createElement('script')
script.src = url
script.onload = () => {
isScriptLoaded[url] = true;
resolve();
};
script.onerror = reject;
document.head.appendChild(script);
});
isScriptLoaded[url] = true
resolve()
}
script.onerror = reject
document.head.appendChild(script)
})
}
//
function createChart (chartDom, nodes) {
@@ -257,9 +264,7 @@ function createChart (chartDom, nodes) {
}
async function createNodesCharts () {
await loadExternalScript(
'/mixlab/app/lib/echarts.min.js'
)
await loadExternalScript('/mixlab/app/lib/echarts.min.js')
const templates = await loadTemplate()
var nodes = {}
Array.from(templates, t => {
@@ -691,9 +696,9 @@ function get_position_style (ctx, widget_width, y, node_height) {
transformOrigin: '0 0',
transform: transform,
left:
document.querySelector('.comfy-menu').style.display === 'none'
? `60px`
: `0`,
document.querySelector('.comfy-menu').style.display === 'none'
? `60px`
: `0`,
top: `0`,
cursor: 'pointer',
position: 'absolute',
@@ -830,21 +835,67 @@ async function fetchReadmeContent (url) {
async function startLLM (model) {
let res = await start_llama(model)
window._mixlab_llamacpp = res||{ model:[] }
window._mixlab_llamacpp = res || { model: [] }
localStorage.setItem('_mixlab_llama_select', res?.model||'')
localStorage.setItem('_mixlab_llama_select', res?.model || '')
if (document.body.querySelector('#mixlab_chatbot_by_llamacpp')&&window._mixlab_llamacpp?.url) {
if (
document.body.querySelector('#mixlab_chatbot_by_llamacpp') &&
window._mixlab_llamacpp?.url
) {
document.body
.querySelector('#mixlab_chatbot_by_llamacpp')
.setAttribute('title', window._mixlab_llamacpp.url)
}
if (document.body.querySelector('#llm_status_btn')&&window._mixlab_llamacpp) {
document.body.querySelector('#llm_status_btn').innerText = window._mixlab_llamacpp.model
if (
document.body.querySelector('#llm_status_btn') &&
window._mixlab_llamacpp
) {
document.body.querySelector('#llm_status_btn').innerText =
window._mixlab_llamacpp.model
}
}
function createModelsModal (models) {
function createInputOfLabel(labelText,key,id){
const label = document.createElement('p')
label.innerText = labelText
const input = document.createElement('input')
input.type = 'text'
input.style = `color: var(--input-text);
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
height: 26px;
padding: 4px 10px;
width: 150px;
margin-left: 12px;`
input.value = getLocalData(key)["-"] ||Object.values(getLocalData(key))[0] || 'by Mixlab'
input.addEventListener('change', e => {
e.stopPropagation()
e.preventDefault()
saveLocalData(key, '-', input.value)
})
const div=document.createElement('div');
div.style=`display: flex;
justify-content: flex-start;
align-items: baseline;padding: 0 18px;`
div.addEventListener('click', e => {
e.stopPropagation()
})
div.appendChild(label)
div.appendChild(input)
return div
}
function createModelsModal (models, llmKey) {
var div =
document.querySelector('#model-modal') || document.createElement('div')
div.id = 'model-modal'
@@ -910,8 +961,6 @@ function createModelsModal (models) {
user-select: none;
`
// headTitleElement.href = 'https://github.com/shadowcz007/comfyui-mixlab-nodes'
// headTitleElement.target = '_blank'
const linkIcon = document.createElement('small')
linkIcon.textContent = showTextByLanguage('Auto Open', {
'Auto Open': '自动开启'
@@ -923,7 +972,7 @@ function createModelsModal (models) {
Status: 'OFF'
})
statusIcon.id = 'llm_status_btn'
statusIcon.style=`padding: 4px;
statusIcon.style = `padding: 4px;
background-color: rgb(102, 255, 108);
color: black;
font-size: 12px;
@@ -939,35 +988,12 @@ function createModelsModal (models) {
// startLLM()
})
const n_gpu = document.createElement('input')
n_gpu.type = 'number'
n_gpu.setAttribute('min', -1)
n_gpu.setAttribute('max', 9999)
n_gpu.style = `color: var(--input-text);
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
height: 26px;
padding: 4px 10px;
width: 48px;
margin-left: 12px;`
if (localStorage.getItem('_mixlab_llama_n_gpu')) {
n_gpu.value = parseInt(localStorage.getItem('_mixlab_llama_n_gpu'))
} else {
n_gpu.value = -1
localStorage.setItem('_mixlab_llama_n_gpu', -1)
}
const n_gpu_p = document.createElement('p')
n_gpu_p.innerText = 'n_gpu_layers'
const batchPageBtn = document.createElement('div')
batchPageBtn.style = `display: flex;
justify-content: center;
align-items: center;
font-size: 12px;`
batchPageBtn.innerHTML=`<a href="${get_url()}/mixlab/app" target="_blank" style="color: var(--input-text);
batchPageBtn.innerHTML = `<a href="${get_url()}/mixlab/app" target="_blank" style="color: var(--input-text);
background-color: var(--comfy-input-bg);">App</a>`
const title = document.createElement('p')
@@ -983,20 +1009,16 @@ function createModelsModal (models) {
font-size: 12px;
flex-direction: column; `
left_d.appendChild(title)
// title.appendChild(statusIcon)
// left_d.appendChild(linkIcon)
left_d.appendChild(batchPageBtn)
headTitleElement.appendChild(left_d)
// headTitleElement.appendChild(n_gpu_div)
//重启
const reStart = document.createElement('small')
reStart.textContent = showTextByLanguage('restart', {
restart: '重启'
})
reStart.style=`padding: 8px;
reStart.style = `padding: 8px;
font-size: 16px;
outline: 1px solid;
padding-top: 4px;
@@ -1029,23 +1051,23 @@ function createModelsModal (models) {
})
})
n_gpu.addEventListener('click', e => {
e.stopPropagation()
localStorage.setItem('_mixlab_llama_n_gpu', n_gpu.value)
})
modal.appendChild(headTitleElement)
// Create modal content area
var modalContent = document.createElement('div')
modalContent.classList.add('modal-content')
let llmKeyDiv=createInputOfLabel('LLM Key','_mixlab_llm_api_key',"-")
saveLocalData("_mixlab_llm_api_url","-","https://api.siliconflow.cn/v1")
let llmAPIDiv=createInputOfLabel('LLM API','_mixlab_llm_api_url',"-")
modalContent.appendChild(llmKeyDiv);
modalContent.appendChild(llmAPIDiv)
var inputForSystemPrompt = document.createElement('textarea')
inputForSystemPrompt.className = 'comfy-multiline-input'
inputForSystemPrompt.style = ` height: 260px;
width: 480px;
font-size: 16px;
padding: 18px;`
inputForSystemPrompt.style = `height: 260px;width: 480px;font-size: 16px;padding: 18px;`
inputForSystemPrompt.value = localStorage.getItem('_mixlab_system_prompt')
inputForSystemPrompt.addEventListener('change', e => {
@@ -1057,9 +1079,9 @@ function createModelsModal (models) {
e.stopPropagation()
})
// modalContent.appendChild(inputForSystemPrompt)
modalContent.appendChild(inputForSystemPrompt)
if (!window._mixlab_llamacpp||(window._mixlab_llamacpp?.model?.length==0)) {
if (!window._mixlab_llamacpp || window._mixlab_llamacpp?.model?.length == 0) {
for (const m of models) {
let d = document.createElement('div')
d.innerText = `${showTextByLanguage('Run', {
@@ -1443,7 +1465,7 @@ app.registerExtension({
.querySelector('#mixlab_chatbot_by_llamacpp')
.setAttribute('title', res.url)
})
}else{
} else {
// startLLM('')
}
@@ -1470,19 +1492,18 @@ app.registerExtension({
LGraphCanvas.prototype.fixTheNode = function (node) {
let new_node = LiteGraph.createNode(node.comfyClass)
console.log(node)
if(new_node){
if (new_node) {
new_node.pos = [node.pos[0], node.pos[1]]
app.canvas.graph.add(new_node, false)
copyNodeValues(node, new_node)
app.canvas.graph.remove(node)
}
}
smart_init()
LGraphCanvas.prototype.text2text = async function (node) {
// console.log(node)
let widget = node.widgets.filter(
w => w.name === 'text' && typeof w.value == 'string'
)[0]
@@ -1494,10 +1515,12 @@ app.registerExtension({
let userInput = widget.value
widget.value = widget.value.trim()
widget.value += '\n'
let jsonStr="";
let jsonStr = ''
try {
await completion_(
window._mixlab_llamacpp.url + '/v1/chat/completions',
getLocalData('_mixlab_llm_api_key')['-']||Object.values(getLocalData('_mixlab_llm_api_key'))[0],
getLocalData("_mixlab_llm_api_url")['-']||Object.values(getLocalData("_mixlab_llm_api_url"))[0],
[
{
role: 'system',
@@ -1509,7 +1532,7 @@ app.registerExtension({
t => {
// console.log(t)
widget.value += t
jsonStr+=t
jsonStr += t
}
)
} catch (error) {
@@ -1533,29 +1556,34 @@ app.registerExtension({
],
controller,
t => {
// console.log(t)
console.log(t)
widget.value += t
jsonStr+=t
jsonStr += t
}
)
})
}
}
let json=null;
// let json = jsonStr
// widget.value = widget.value.trim()+json
// console.log(jsonStr)
// try {
// json = JSON.parse(jsonStr.trim())
// } catch (error) {
// try {
// json = JSON.parse(jsonStr.trim() + '}')
// } catch (error) {
// }
// }
try {
json=JSON.parse(jsonStr.trim())
} catch (error) {
json=JSON.parse(jsonStr.trim()+"}")
}
if(json){
widget.value = [json.subject,json.content,json.style].join('\n')
}else{
widget.value = widget.value.trim()
}
// if (json) {
// widget.value = [json.subject, json.content, json.style].join('\n')
// } else {
// widget.value = widget.value.trim()
// }
}
}
@@ -1836,15 +1864,14 @@ app.registerExtension({
)
let text_input = node.inputs?.filter(
inp => inp.name == 'text' && inp.type == 'STRING'
inp => inp.name == 'text' && (inp.type == 'STRING' )
)
if (
text_input &&
text_input.length == 0 &&
text_widget &&
text_widget.length == 1 &&
window._mixlab_llamacpp &&
false &&
node.type != 'ShowTextForGPT'
) {
opts.push({
@@ -1855,19 +1882,19 @@ app.registerExtension({
})
}
if (
node.imgs &&
node.imgs.length > 0 &&
window._mixlab_llamacpp &&
window._mixlab_llamacpp.chat_format === 'llava-1-5'
) {
opts.push({
content: 'Image-to-Text ♾️Mixlab', // with a name
callback: () => {
LGraphCanvas.prototype.image2text(node)
} // and the callback
})
}
// if (
// node.imgs &&
// node.imgs.length > 0 &&
// window._mixlab_llamacpp &&
// window._mixlab_llamacpp.chat_format === 'llava-1-5'
// ) {
// opts.push({
// content: 'Image-to-Text ♾️Mixlab', // with a name
// callback: () => {
// LGraphCanvas.prototype.image2text(node)
// } // and the callback
// })
// }
}
return [...opts, null, ...options] // and return the options
+30 -54
View File
@@ -1,49 +1,13 @@
import { app } from '../../../scripts/app.js'
import { $el } from '../../../scripts/ui.js'
import {
loadExternalScript,
updateLLMAPIKey,
get_position_style,
getLocalData
} from './common.js'
const getLocalData = key => {
let data = {}
try {
data = JSON.parse(localStorage.getItem(key)) || {}
} catch (error) {
return {}
}
return data
}
function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 4 // the margin around the html element
/* Create a transform that deals with all the scrolling and zooming */
const elRect = ctx.canvas.getBoundingClientRect()
const transform = new DOMMatrix()
.scaleSelf(
elRect.width / ctx.canvas.width,
elRect.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, MARGIN + y)
return {
transformOrigin: '0 0',
transform: transform,
left:
document.querySelector('.comfy-menu').style.display === 'none'
? `60px`
: `0`,
top: `0`,
cursor: 'pointer',
position: 'absolute',
maxWidth: `${widget_width - MARGIN * 2}px`,
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
width: `${widget_width - MARGIN * 2}px`,
// height: `${node_height * 0.3 - MARGIN * 2}px`,
// background: '#EEEEEE',
display: 'flex',
flexDirection: 'column',
// alignItems: 'center',
justifyContent: 'space-around'
}
}
loadExternalScript('/mixlab/app/lib/pickr.min.js')
function hexToRGBA (hexColor) {
var hex = hexColor.replace('#', '')
@@ -125,7 +89,7 @@ app.registerExtension({
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
console.log('Color nodeData', this.div)
// console.log('Color nodeData', this.div)
const widget = {
type: 'div',
@@ -366,7 +330,7 @@ app.registerExtension({
return [128, 32] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
let data = getLocalData('_mixlab_api_key')
let data = getLocalData('_mixlab_llm_api_key')
return data[node.id] || 'by Mixlab'
}
}
@@ -383,13 +347,14 @@ app.registerExtension({
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
const rowHeight = this.rowHeight
const widget = {
type: 'div',
name: 'input_key',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, 24, node.size[1])
get_position_style(ctx, widget_width, y, node.size[1])
)
}
}
@@ -417,11 +382,11 @@ app.registerExtension({
// ip.value = placeholder
ip.style = `margin-left:8px;
outline: none;
border: none;
padding:12px;
width: 100%;
`
outline: none;
border: none;
padding:12px;
width: 100%;
`
div.appendChild(ip)
@@ -429,12 +394,13 @@ app.registerExtension({
let data = getLocalData(key)
data[this.id] = ip.value.trim()
localStorage.setItem(key, JSON.stringify(data))
updateLLMAPIKey(data[this.id])
})
return div
}
let inputKey = inputDiv('_mixlab_api_key', 'Key')
let inputKey = inputDiv('_mixlab_llm_api_key', 'Key')
widget.div.appendChild(inputKey)
@@ -447,6 +413,12 @@ app.registerExtension({
return onRemoved?.()
}
// const processMouseWheel=app.canvas.processMouseWheel
// app.canvas.processMouseWheel=()=>{
// console.log(app.canvas.ds.scale)
// return processMouseWheel?.()
// }
this.serialize_widgets = true //需要保存参数
}
}
@@ -455,11 +427,13 @@ app.registerExtension({
if (node.type === 'KeyInput') {
let widget = node.widgets.filter(w => w.div)[0]
let apiKey = getLocalData('_mixlab_api_key')
let apiKey = getLocalData('_mixlab_llm_api_key')
let id = node.id
if (widget.div.querySelector('.Key'))
widget.div.querySelector('.Key').value = apiKey[id] || 'by Mixlab'
if (apiKey[id]) updateLLMAPIKey(apiKey[id])
}
},
nodeCreated (node, app) {
@@ -469,12 +443,14 @@ app.registerExtension({
if (node.type === 'KeyInput') {
let widget = node.widgets.filter(w => w.div)[0]
let apiKey = getLocalData('_mixlab_api_key')
let apiKey = getLocalData('_mixlab_llm_api_key')
let id = node.id
if (widget.div.querySelector('.Key'))
widget.div.querySelector('.Key').value = apiKey[id] || 'by Mixlab'
if (apiKey[id]) updateLLMAPIKey(apiKey[id])
}
}, 1000)
}