update
This commit is contained in:
+60
@@ -32,6 +32,7 @@ _URL_=None
|
||||
# except:
|
||||
# print("##nodes.ChatGPT ImportError")
|
||||
|
||||
from .nodes.ChatGPT import openai_client
|
||||
|
||||
from .nodes.RembgNode import get_rembg_models,U2NET_HOME,run_briarmbg,run_rembg
|
||||
|
||||
@@ -584,6 +585,65 @@ async def mixlab_hander(request):
|
||||
print(e)
|
||||
return web.json_response(data)
|
||||
|
||||
# llm的api key,使用硅基流动
|
||||
@routes.post('/mixlab/llm_api_key')
|
||||
async def mixlab_llm_api_key_handler(request):
|
||||
data = await request.json()
|
||||
api_key = data.get('key')
|
||||
|
||||
app_folder = os.path.join(current_path, "app")
|
||||
key_file_path = os.path.join(app_folder, "llm_api_key.txt")
|
||||
|
||||
if api_key:
|
||||
if not os.path.exists(app_folder):
|
||||
os.makedirs(app_folder)
|
||||
try:
|
||||
with open(key_file_path, 'w') as f:
|
||||
f.write(api_key)
|
||||
return web.json_response({'message': 'API key saved successfully'})
|
||||
except Exception as e:
|
||||
return web.json_response({'error': str(e)}, status=500)
|
||||
else:
|
||||
if os.path.exists(key_file_path):
|
||||
try:
|
||||
with open(key_file_path, 'r') as f:
|
||||
saved_api_key = f.read().strip()
|
||||
return web.json_response({'key': saved_api_key})
|
||||
except Exception as e:
|
||||
return web.json_response({'error': str(e)}, status=500)
|
||||
else:
|
||||
return web.json_response({'error': 'No API key provided and no key found in local storage'}, status=400)
|
||||
|
||||
|
||||
@routes.post('/chat/completions')
|
||||
async def chat_completions(request):
|
||||
data = await request.json()
|
||||
messages = data.get('messages')
|
||||
key=data.get('key')
|
||||
if not messages:
|
||||
return web.json_response({"error": "No messages provided"}, status=400)
|
||||
|
||||
async def generate():
|
||||
try:
|
||||
client=openai_client(key,"https://api.siliconflow.cn/v1")
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="01-ai/Yi-1.5-9B-Chat-16K",
|
||||
messages=messages,
|
||||
stream=True
|
||||
)
|
||||
|
||||
for chunk in response:
|
||||
if hasattr(chunk.choices[0].delta, 'content'):
|
||||
content = chunk.choices[0].delta.content
|
||||
if content is not None:
|
||||
yield content.encode('utf-8') + b"\r\n"
|
||||
|
||||
except Exception as e:
|
||||
yield f"Error: {str(e)}".encode('utf-8') + b"\r\n"
|
||||
|
||||
return web.Response(body=generate(), content_type='text/event-stream')
|
||||
|
||||
|
||||
@routes.get('/mixlab/app')
|
||||
async def mixlab_app_handler(request):
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-mixlab-nodes"
|
||||
description = "3D, ScreenShareNode & FloatingVideoNode, SpeechRecognition & SpeechSynthesis, GPT, LoadImagesFromLocal, Layers, Other Nodes, ..."
|
||||
version = "0.37.0"
|
||||
version = "0.38.0"
|
||||
license = "MIT"
|
||||
dependencies = ["numpy", "pyOpenSSL", "watchdog", "opencv-python-headless", "matplotlib", "openai", "simple-lama-inpainting", "clip-interrogator==0.6.0", "transformers>=4.36.0", "lark-parser", "imageio-ffmpeg", "rembg[gpu]", "omegaconf==2.3.0", "Pillow>=9.5.0", "einops==0.7.0", "trimesh>=4.0.5", "huggingface-hub", "scikit-image"]
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -1,631 +0,0 @@
|
||||
{
|
||||
"last_node_id": 47,
|
||||
"last_link_id": 46,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 27,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
1961.178268896482,
|
||||
527.6060791015625
|
||||
],
|
||||
"size": {
|
||||
"0": 422.84503173828125,
|
||||
"1": 164.31304931640625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 24
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 46,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
21
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
1958.7452854980445,
|
||||
266
|
||||
],
|
||||
"size": {
|
||||
"0": 425.27801513671875,
|
||||
"1": 180.6060791015625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 25
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
22
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"text, watermark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
2434.0233006347635,
|
||||
80
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 20
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 21
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 22
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 23
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
26
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
971428736321335,
|
||||
"randomize",
|
||||
15,
|
||||
8,
|
||||
"euler",
|
||||
"karras",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
2799.0233006347635,
|
||||
80
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {
|
||||
"collapsed": false
|
||||
},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 26
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 27
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
31
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [
|
||||
2069.0233006347635,
|
||||
80
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
23
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
1593.7452854980445,
|
||||
80
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
20
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
24,
|
||||
25
|
||||
],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
27
|
||||
],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"deliberate_v2.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
2439,
|
||||
408
|
||||
],
|
||||
"size": {
|
||||
"0": 563.4671630859375,
|
||||
"1": 720.8866577148438
|
||||
},
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 31
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
1171,
|
||||
425
|
||||
],
|
||||
"size": {
|
||||
"0": 432.46002197265625,
|
||||
"1": 264.40771484375
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 44,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"[\n {\n \"role\": \"system\",\n \"content\": \"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"\"\n }\n]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
1166,
|
||||
740
|
||||
],
|
||||
"size": {
|
||||
"0": 424.0079650878906,
|
||||
"1": 430.6391296386719
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 45,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"[\n {\n \"role\": \"user\",\n \"content\": \"\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"I'm sorry, I'm ChatGLM3-6B, not ChatGPT. I am a language model jointly trained by Tsinghua University KEG Lab and Zhipu AI Company.\"\n }\n]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
1092,
|
||||
262
|
||||
],
|
||||
"size": {
|
||||
"0": 635.8358154296875,
|
||||
"1": 101.46092224121094
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 43,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"I'm sorry, I'm ChatGLM3-6B, not ChatGPT. I am a language model jointly trained by Tsinghua University KEG Lab and Zhipu AI Company."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
585,
|
||||
306
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 342
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
43,
|
||||
46
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
44
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
45
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"gpt-3.5-turbo-16k",
|
||||
2220,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
3,
|
||||
6,
|
||||
0,
|
||||
4,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
6,
|
||||
1,
|
||||
7,
|
||||
0,
|
||||
"JSON"
|
||||
],
|
||||
[
|
||||
8,
|
||||
10,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
12,
|
||||
10,
|
||||
1,
|
||||
14,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
13,
|
||||
10,
|
||||
1,
|
||||
16,
|
||||
0,
|
||||
"JSON"
|
||||
],
|
||||
[
|
||||
20,
|
||||
25,
|
||||
0,
|
||||
24,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
21,
|
||||
27,
|
||||
0,
|
||||
24,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
22,
|
||||
28,
|
||||
0,
|
||||
24,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
23,
|
||||
26,
|
||||
0,
|
||||
24,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
24,
|
||||
25,
|
||||
1,
|
||||
27,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
25,
|
||||
25,
|
||||
1,
|
||||
28,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
26,
|
||||
24,
|
||||
0,
|
||||
29,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
27,
|
||||
25,
|
||||
2,
|
||||
29,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
31,
|
||||
29,
|
||||
0,
|
||||
31,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
43,
|
||||
47,
|
||||
0,
|
||||
45,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
44,
|
||||
47,
|
||||
1,
|
||||
44,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
45,
|
||||
47,
|
||||
2,
|
||||
43,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
46,
|
||||
47,
|
||||
0,
|
||||
27,
|
||||
1,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
{
|
||||
"last_node_id": 5,
|
||||
"last_link_id": 4,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 4,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
-512,
|
||||
-236
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 342
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"描述一个科幻的场景",
|
||||
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"gpt-35-turbo",
|
||||
6933,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
-36,
|
||||
-234
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 342
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"link": 2,
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
3
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"增加丰富的细节和光影,摄影技巧,镜头语言,材质肌理",
|
||||
"gpt-3.5-turbo",
|
||||
2836,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
-30,
|
||||
183
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 342
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"link": 3,
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"翻译成英文,并按照格式输出: 画面、主题、细节、灯光、氛围、艺术家、其他",
|
||||
"gpt-3.5-turbo",
|
||||
1085,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
447,
|
||||
-229
|
||||
],
|
||||
"size": [
|
||||
503.79851499517997,
|
||||
356.0560985581077
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 4,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Picture: The picture is composed of carefully chosen elements, capturing the subject matter in a visually striking way.\n\nTheme: The theme of the photograph could vary, from capturing nature's beauty to showcasing urban landscapes, human emotions, or abstract concepts.\n\nDetails: The photograph captures intricate details, bringing attention to the subject's textures, colors, shapes, and patterns.\n\nLighting: The photographer manipulates lighting, using techniques like natural light, dramatic shadows, or artificial lighting to enhance the mood and atmosphere of the photograph.\n\nAmbiance: The photograph evokes a specific ambiance or mood, whether it's serene, mysterious, joyful, melancholic, or any other emotional response.\n\nArtist: The photographer skillfully crafts the image, demonstrating their artistic vision, technical skills, and creative expression through the composition, framing, and post-processing choices.\n\nOthers: Apart from the elements mentioned above, the photograph may also incorporate other creative techniques like long exposure, multiple exposures, color grading, or unconventional perspectives to create a unique and captivating image.\n\nIn the future, as technology and imagination continue to advance, photography will likely continue to evolve and innovate, offering even more realistic and awe-inspiring visual experiences for humans."
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
3,
|
||||
2,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
5,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
Reference in New Issue
Block a user