Merge pull request #29 from nathannlu/vindennt/chatbot
Vindennt/chatbot
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { nimbus } from "../resource/index.js";
|
||||
|
||||
export async function getBotResponse(message) {
|
||||
try {
|
||||
const data = await nimbus.chatbot.sendMessage({
|
||||
message: message,
|
||||
origin: "ComfyUI Cloud Chat",
|
||||
});
|
||||
|
||||
return data;
|
||||
} catch (e) {
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidWorkflow(workflow) {
|
||||
return workflow && workflow.nodes && workflow.edges;
|
||||
}
|
||||
|
||||
export function parseWorkflowFromBot(response) {
|
||||
const cleanedResponse = response.responses.bot
|
||||
.replace(/^\s*```json\s*/, "")
|
||||
.replace(/\s*```\s*$/, "");
|
||||
|
||||
try {
|
||||
return JSON.parse(cleanedResponse);
|
||||
} catch (error) {
|
||||
console.error("Failed to parse JSON:", error);
|
||||
return cleanedResponse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
export function getInputType(name) {
|
||||
const nameTypeMap = {
|
||||
LATENT: "LATENT",
|
||||
MODEL: "MODEL",
|
||||
CLIP: "CLIP",
|
||||
VAE: "VAE",
|
||||
CONDITIONING: "CONDITIONING",
|
||||
IMAGE: "IMAGE",
|
||||
MASK: "MASK",
|
||||
CLIP_VISION_OUTPUT: "CLIP_VISION_OUTPUT",
|
||||
positive: "CONDITIONING",
|
||||
negative: "CONDITIONING",
|
||||
CONTROL_NET: "CONTROL_NET",
|
||||
STYLE_MODEL: "STYLE_MODEL",
|
||||
CLIP_VISION: "CLIP_VISION",
|
||||
GLIGEN: "GLIGEN",
|
||||
latent: "LATENT",
|
||||
UPSCALE_MODEL: "UPSCALE_MODEL",
|
||||
output: "LATENT",
|
||||
denoised_output: "LATENT",
|
||||
SIGMAS: "SIGMAS",
|
||||
SAMPLER: "SAMPLER",
|
||||
high_sigmas: "SIGMAS",
|
||||
low_sigmas: "SIGMAS",
|
||||
GUIDER: "GUIDER",
|
||||
NOISE: "NOISE",
|
||||
PHOTOMAKER: "PHOTOMAKER",
|
||||
stage_c: "LATENT",
|
||||
stage_b: "LATENT",
|
||||
controlnet_input: "IMAGE",
|
||||
STRING: "STRING",
|
||||
"*": "*",
|
||||
INT: "INT",
|
||||
FLOAT: "FLOAT",
|
||||
BLIP_MODEL: "BLIP_MODEL",
|
||||
bus: "BUS",
|
||||
model: "MODEL",
|
||||
clip: "CLIP",
|
||||
vae: "VAE",
|
||||
latent_filename: "STRING",
|
||||
latent_image: "LATENT",
|
||||
image_filename: "STRING",
|
||||
conditioning_filename: "STRING",
|
||||
NAME_STRING: "STRING",
|
||||
conditioning: "CONDITIONING",
|
||||
parsed_text: "STRING",
|
||||
raw_text: "STRING",
|
||||
NUMBER: "NUMBER",
|
||||
image_a_pass: "IMAGE",
|
||||
image_b_pass: "IMAGE",
|
||||
filepath_text: "STRING",
|
||||
filename_text: "STRING",
|
||||
MASK_IMAGE: "IMAGE",
|
||||
clipseg_model: "CLIPSEG_MODEL",
|
||||
IMAGES_BATCH: "IMAGE",
|
||||
MASKS_BATCH: "MASK",
|
||||
MASK_IMAGES_BATCH: "IMAGE",
|
||||
IMAGES: "IMAGE",
|
||||
DICT: "DICT",
|
||||
BOOLEAN: "BOOLEAN",
|
||||
composited_images: "IMAGE",
|
||||
ssao_images: "IMAGE",
|
||||
specular_mask_images: "IMAGE",
|
||||
ssdo_images: "IMAGE",
|
||||
ssdo_image_masks: "IMAGE",
|
||||
light_source_image_masks: "IMAGE",
|
||||
aspect_number: "NUMBER",
|
||||
aspect_float: "FLOAT",
|
||||
is_landscape_bool: "NUMBER",
|
||||
aspect_ratio_common: "STRING",
|
||||
aspect_type: "STRING",
|
||||
image: "IMAGE",
|
||||
images: "IMAGE",
|
||||
color_palettes: "LIST",
|
||||
CROP_DATA: "CROP_DATA",
|
||||
mask: "MASK",
|
||||
shadow_map: "IMAGE",
|
||||
highlight_map: "IMAGE",
|
||||
width_num: "NUMBER",
|
||||
height_num: "NUMBER",
|
||||
width_float: "FLOAT",
|
||||
height_float: "FLOAT",
|
||||
width_int: "INT",
|
||||
height_int: "INT",
|
||||
MASKS: "MASK",
|
||||
INT_PLACES: "INT",
|
||||
"latent(s)": "LATENT",
|
||||
tensor_w_num: "NUMBER",
|
||||
tensor_h_num: "NUMBER",
|
||||
masks: "MASK",
|
||||
cropped_mask: "MASK",
|
||||
crop_data: "CROP_DATA",
|
||||
top_int: "INT",
|
||||
left_int: "INT",
|
||||
right_int: "INT",
|
||||
bottom_int: "INT",
|
||||
midas_model: "MIDAS_MODEL",
|
||||
number: "NUMBER",
|
||||
float: "FLOAT",
|
||||
int: "INT",
|
||||
SEED: "SEED",
|
||||
positive_string: "STRING",
|
||||
negative_string: "STRING",
|
||||
seed: "SEED",
|
||||
SAM_MODEL: "SAM_MODEL",
|
||||
SAM_PARAMETERS: "SAM_PARAMETERS",
|
||||
samples: "LATENT",
|
||||
IMAGE_BOUNDS: "IMAGE_BOUNDS",
|
||||
LIST: "LIST",
|
||||
TEXT_A_PASS: "STRING",
|
||||
TEXT_B_PASS: "STRING",
|
||||
SCORE_NUMBER: "NUMBER",
|
||||
COMPARISON_TEXT: "STRING",
|
||||
result_text: "STRING",
|
||||
replacement_count_number: "NUMBER",
|
||||
replacement_count_float: "FLOAT",
|
||||
replacement_count_int: "INT",
|
||||
f: "B",
|
||||
o: "O",
|
||||
u: "O",
|
||||
n: "L",
|
||||
d: "E",
|
||||
line_text: "STRING",
|
||||
dictionary: "DICT",
|
||||
MODEL_NAME_TEXT: "STRING",
|
||||
image_pass: "IMAGE",
|
||||
IMAGE_PASS: "IMAGE",
|
||||
output_path: "STRING",
|
||||
processed_count: "NUMBER",
|
||||
BBOX_DETECTOR: "BBOX_DETECTOR",
|
||||
SEGS: "SEGS",
|
||||
cropped: "IMAGE",
|
||||
cropped_refined: "IMAGE",
|
||||
cropped_refined_alpha: "IMAGE",
|
||||
cnet_images: "IMAGE",
|
||||
segs: "SEGS",
|
||||
basic_pipe: "BASIC_PIPE",
|
||||
combined_mask: "MASK",
|
||||
batch_masks: "MASK",
|
||||
cropped_enhanced_alpha: "IMAGE",
|
||||
detailer_pipe: "DETAILER_PIPE",
|
||||
refiner_basic_pipe_opt: "BASIC_PIPE",
|
||||
bbox_detector: "BBOX_DETECTOR",
|
||||
sam_model_opt: "SAM_MODEL",
|
||||
segm_detector_opt: "SEGM_DETECTOR",
|
||||
detailer_hook: "DETAILER_HOOK",
|
||||
refiner_model: "MODEL",
|
||||
refiner_clip: "CLIP",
|
||||
refiner_positive: "CONDITIONING",
|
||||
refiner_negative: "CONDITIONING",
|
||||
base_basic_pipe: "BASIC_PIPE",
|
||||
refiner_basic_pipe: "BASIC_PIPE",
|
||||
UPSCALER: "UPSCALER",
|
||||
PK_HOOK: "PK_HOOK",
|
||||
DETAILER_HOOK: "DETAILER_HOOK",
|
||||
UPSCALER_HOOK: "UPSCALER_HOOK",
|
||||
SEGS_HEADER: "SEGS_HEADER",
|
||||
SEG_ELT: "SEG_ELT",
|
||||
seg_elt: "SEG_ELT",
|
||||
cropped_image: "IMAGE",
|
||||
crop_region: "SEG_ELT_crop_region",
|
||||
bbox: "SEG_ELT_bbox",
|
||||
control_net_wrapper: "SEG_ELT_control_net_wrapper",
|
||||
confidence: "FLOAT",
|
||||
label: "STRING",
|
||||
left: "INT",
|
||||
top: "INT",
|
||||
right: "INT",
|
||||
bottom: "INT",
|
||||
KSAMPLER: "KSAMPLER",
|
||||
KSAMPLER_ADVANCED: "KSAMPLER_ADVANCED",
|
||||
selected_value: "*",
|
||||
selected_label: "STRING",
|
||||
selected_index: "INT",
|
||||
output1: "*",
|
||||
populated_text: "STRING",
|
||||
BASIC_PIPE: "BASIC_PIPE",
|
||||
REGIONAL_PROMPTS: "REGIONAL_PROMPTS",
|
||||
filtered_SEGS: "SEGS",
|
||||
remained_SEGS: "SEGS",
|
||||
signal_opt: "*",
|
||||
bool: "BOOLEAN",
|
||||
signal: "*",
|
||||
batch: "INT",
|
||||
height: "INT",
|
||||
width: "INT",
|
||||
channel: "INT",
|
||||
wildcard: "STRING",
|
||||
segs_labels: "STRING",
|
||||
count: "INT",
|
||||
total: "INT",
|
||||
value: "*",
|
||||
TRANSFORMERS_CLASSIFIER: "TRANSFORMERS_CLASSIFIER",
|
||||
scheduler: [
|
||||
"normal",
|
||||
"karras",
|
||||
"exponential",
|
||||
"sgm_uniform",
|
||||
"simple",
|
||||
"ddim_uniform",
|
||||
],
|
||||
SEGM_DETECTOR: "SEGM_DETECTOR",
|
||||
TIMESTEP_KF: "TIMESTEP_KEYFRAME",
|
||||
LATENT_KF: "LATENT_KEYFRAME",
|
||||
model_opt: "MODEL",
|
||||
CN_WEIGHTS: "CONTROL_NET_WEIGHTS",
|
||||
TK_SHORTCUT: "TIMESTEP_KEYFRAME",
|
||||
proc_IMAGE: "IMAGE",
|
||||
SPARSE_METHOD: "SPARSE_METHOD",
|
||||
cn_extras: "CN_WEIGHTS_EXTRAS",
|
||||
"ABG_CHARACTER_MASK (MASK)": "MASK",
|
||||
POSE_KEYPOINT: "POSE_KEYPOINT",
|
||||
tracking: "TRACKING",
|
||||
prompt: "STRING",
|
||||
OPTICAL_FLOW: "OPTICAL_FLOW",
|
||||
PREVIEW_IMAGE: "IMAGE",
|
||||
INPAINTING_MASK: "MASK",
|
||||
preprocessor: [
|
||||
"none",
|
||||
"TilePreprocessor",
|
||||
"TTPlanet_TileGF_Preprocessor",
|
||||
"TTPlanet_TileSimple_Preprocessor",
|
||||
"AnimeFace_SemSegPreprocessor",
|
||||
"BinaryPreprocessor",
|
||||
"Zoe-DepthMapPreprocessor",
|
||||
"ColorPreprocessor",
|
||||
"DepthAnythingPreprocessor",
|
||||
"Zoe_DepthAnythingPreprocessor",
|
||||
"DensePosePreprocessor",
|
||||
"OneFormer-COCO-SemSegPreprocessor",
|
||||
"OneFormer-ADE20K-SemSegPreprocessor",
|
||||
"M-LSDPreprocessor",
|
||||
"AnyLineArtPreprocessor_aux",
|
||||
"DWPreprocessor",
|
||||
"AnimalPosePreprocessor",
|
||||
"DSINE-NormalMapPreprocessor",
|
||||
"MediaPipe-FaceMeshPreprocessor",
|
||||
"LineArtPreprocessor",
|
||||
"MiDaS-NormalMapPreprocessor",
|
||||
"MiDaS-DepthMapPreprocessor",
|
||||
"CannyEdgePreprocessor",
|
||||
"ShufflePreprocessor",
|
||||
"Metric3D-DepthMapPreprocessor",
|
||||
"Metric3D-NormalMapPreprocessor",
|
||||
"AnimeLineArtPreprocessor",
|
||||
"SAMPreprocessor",
|
||||
"HEDPreprocessor",
|
||||
"FakeScribblePreprocessor",
|
||||
"ScribblePreprocessor",
|
||||
"Scribble_XDoG_Preprocessor",
|
||||
"Scribble_PiDiNet_Preprocessor",
|
||||
"DiffusionEdge_Preprocessor",
|
||||
"SavePoseKpsAsJsonFile",
|
||||
"FacialPartColoringFromPoseKps",
|
||||
"UpperBodyTrackingFromPoseKps",
|
||||
"PiDiNetPreprocessor",
|
||||
"OpenposePreprocessor",
|
||||
"LeReS-DepthMapPreprocessor",
|
||||
"Unimatch_OptFlowPreprocessor",
|
||||
"MaskOptFlow",
|
||||
"ImageLuminanceDetector",
|
||||
"ImageIntensityDetector",
|
||||
"UniFormer-SemSegPreprocessor",
|
||||
"SemSegPreprocessor",
|
||||
"MeshGraphormer-DepthMapPreprocessor",
|
||||
"MeshGraphormer+ImpactDetector-DepthMapPreprocessor",
|
||||
"Manga2Anime_LineArt_Preprocessor",
|
||||
"BAE-NormalMapPreprocessor",
|
||||
"TEEDPreprocessor",
|
||||
"LineartStandardPreprocessor",
|
||||
],
|
||||
"RESOLUTION (INT)": "INT",
|
||||
"IMAGE_GEN_WIDTH (INT)": "INT",
|
||||
"IMAGE_GEN_HEIGHT (INT)": "INT",
|
||||
"Full prompt": "STRING",
|
||||
"Short prompt": "STRING",
|
||||
"Compiled prompt": "CONDITIONING",
|
||||
Value: "STRING",
|
||||
Value2: "STRING",
|
||||
mix: "INT",
|
||||
Filenames: "VHS_FILENAMES",
|
||||
frame_count: "INT",
|
||||
audio: "VHS_AUDIO",
|
||||
video_info: "VHS_VIDEOINFO",
|
||||
meta_batch: "VHS_BatchManager",
|
||||
"source_fps🟨": "FLOAT",
|
||||
"source_frame_count🟨": "INT",
|
||||
"source_duration🟨": "FLOAT",
|
||||
"source_width🟨": "INT",
|
||||
"source_height🟨": "INT",
|
||||
"loaded_fps🟦": "FLOAT",
|
||||
"loaded_frame_count🟦": "INT",
|
||||
"loaded_duration🟦": "FLOAT",
|
||||
"loaded_width🟦": "INT",
|
||||
"loaded_height🟦": "INT",
|
||||
"fps🟨": "FLOAT",
|
||||
"frame_count🟨": "INT",
|
||||
"duration🟨": "FLOAT",
|
||||
"width🟨": "INT",
|
||||
"height🟨": "INT",
|
||||
"fps🟦": "FLOAT",
|
||||
"frame_count🟦": "INT",
|
||||
"duration🟦": "FLOAT",
|
||||
"width🟦": "INT",
|
||||
"height🟦": "INT",
|
||||
LATENT_A: "LATENT",
|
||||
A_count: "INT",
|
||||
LATENT_B: "LATENT",
|
||||
B_count: "INT",
|
||||
IMAGE_A: "IMAGE",
|
||||
IMAGE_B: "IMAGE",
|
||||
MASK_A: "MASK",
|
||||
MASK_B: "MASK",
|
||||
modelname: "STRING",
|
||||
sampler_name: [
|
||||
"euler",
|
||||
"euler_ancestral",
|
||||
"heun",
|
||||
"heunpp2",
|
||||
"dpm_2",
|
||||
"dpm_2_ancestral",
|
||||
"lms",
|
||||
"dpm_fast",
|
||||
"dpm_adaptive",
|
||||
"dpmpp_2s_ancestral",
|
||||
"dpmpp_sde",
|
||||
"dpmpp_sde_gpu",
|
||||
"dpmpp_2m",
|
||||
"dpmpp_2m_sde",
|
||||
"dpmpp_2m_sde_gpu",
|
||||
"dpmpp_3m_sde",
|
||||
"dpmpp_3m_sde_gpu",
|
||||
"ddpm",
|
||||
"lcm",
|
||||
"ddim",
|
||||
"uni_pc",
|
||||
"uni_pc_bh2",
|
||||
],
|
||||
scheduler_name: "STRING",
|
||||
s: "STRING",
|
||||
x: "INT",
|
||||
y: "INT",
|
||||
REMBG_SESSION: "REMBG_SESSION",
|
||||
red: "MASK",
|
||||
green: "MASK",
|
||||
blue: "MASK",
|
||||
cyan: "MASK",
|
||||
magenta: "MASK",
|
||||
yellow: "MASK",
|
||||
black: "MASK",
|
||||
white: "MASK",
|
||||
CLIP_SEG: "CLIP_SEG",
|
||||
description: "STRING",
|
||||
response: "STRING",
|
||||
context: "STRING",
|
||||
};
|
||||
|
||||
const matchName = nameTypeMap[name];
|
||||
if (matchName) {
|
||||
return matchName;
|
||||
} else {
|
||||
console.log("not matching name, will return unknown: ", name);
|
||||
return "XXX UNKNOWN INPUT TYPE XXX";
|
||||
}
|
||||
}
|
||||
|
||||
// originally was in in file that imported app
|
||||
function scanNodeInputOutput() {
|
||||
const nameTypeMap = new Map();
|
||||
|
||||
function updateNameTypeMap(input, output) {
|
||||
const maxLength = Math.min(input.length, output.length);
|
||||
|
||||
for (let i = 0; i < maxLength; i++) {
|
||||
const inputName = input[i];
|
||||
const outputName = output[i];
|
||||
|
||||
// Add to nameTypeMap if inputName is not already present
|
||||
if (!nameTypeMap.has(inputName)) {
|
||||
nameTypeMap[inputName] = outputName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(LiteGraph.registered_node_types);
|
||||
if (LiteGraph.registered_node_types) {
|
||||
// Get the first node in the list
|
||||
for (const [key, nodeType] of Object.entries(
|
||||
LiteGraph.registered_node_types
|
||||
)) {
|
||||
console.log(key);
|
||||
if (nodeType.nodeData?.output !== undefined) {
|
||||
updateNameTypeMap(
|
||||
nodeType.nodeData.output_name,
|
||||
nodeType.nodeData.output
|
||||
);
|
||||
}
|
||||
}
|
||||
console.log(JSON.stringify(nameTypeMap));
|
||||
} else {
|
||||
console.log("No registered nodes found.");
|
||||
}
|
||||
return nameTypeMap;
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
// import { api, app } from './comfy/comfy.js'
|
||||
import { app } from "../comfy/comfy.js";
|
||||
import {
|
||||
getBotResponse,
|
||||
isValidWorkflow,
|
||||
parseWorkflowFromBot,
|
||||
} from "./chatbot.js";
|
||||
import { loadGraphFromPrompt } from "./workflow.js";
|
||||
import { setButtonDefault } from "../button/ui.js";
|
||||
import { authDialog } from "../auth/index.js";
|
||||
import { getApiToken } from "../utils.js";
|
||||
|
||||
const CHAT_BUTTON_ID = "chat-button";
|
||||
const CHAT_BOX_ID = "chat-box";
|
||||
const CHAT_INPUT_ID = "chat-input";
|
||||
const CHAT_MESSAGES_ID = "chat-messages";
|
||||
const CHAT_SEND_BUTTON_ID = "chat-send-button";
|
||||
|
||||
const apiToken = getApiToken();
|
||||
const doesApiTokenExist = !!apiToken;
|
||||
|
||||
let canSendMessage = true;
|
||||
|
||||
const toggleChatBox = async () => {
|
||||
const chatBox = document.getElementById(CHAT_BOX_ID);
|
||||
if (chatBox.style.display === "none") {
|
||||
chatBox.style.display = "block";
|
||||
} else {
|
||||
chatBox.style.display = "none";
|
||||
}
|
||||
};
|
||||
|
||||
const createChatButton = () => {
|
||||
const chatButton = document.createElement("div");
|
||||
chatButton.id = CHAT_BUTTON_ID;
|
||||
|
||||
// Set up the button styles and gradient background
|
||||
chatButton.style.position = "fixed";
|
||||
chatButton.style.bottom = "0";
|
||||
chatButton.style.right = "0";
|
||||
chatButton.style.margin = "20px";
|
||||
chatButton.style.background = "linear-gradient(160deg, #8A2BE2, #1E90FF)"; // Gradient background
|
||||
chatButton.style.color = "white";
|
||||
chatButton.style.borderRadius = "50%";
|
||||
chatButton.style.width = "60px";
|
||||
chatButton.style.height = "60px";
|
||||
chatButton.style.zIndex = 9999;
|
||||
chatButton.style.display = "flex";
|
||||
chatButton.style.alignItems = "center";
|
||||
chatButton.style.justifyContent = "center";
|
||||
chatButton.style.cursor = "pointer";
|
||||
|
||||
// Create and append SVG chat bubble icon
|
||||
const chatIcon = document.createElementNS(
|
||||
"http://www.w3.org/2000/svg",
|
||||
"svg"
|
||||
);
|
||||
chatIcon.setAttribute("viewBox", "0 0 512 512");
|
||||
chatIcon.setAttribute("width", "20"); // Adjust size as needed
|
||||
chatIcon.setAttribute("height", "20"); // Adjust size as needed
|
||||
chatIcon.setAttribute("fill", "currentColor"); // Use current text color
|
||||
|
||||
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
path.setAttribute(
|
||||
"d",
|
||||
"M416,0H96C43.072,0,0,43.072,0,96v405.333c0,4.48,2.816,8.491,7.04,10.027c1.195,0.427,2.411,0.64,3.627,0.64c3.115,0,6.123-1.344,8.192-3.84L122.325,384H416c52.928,0,96-43.072,96-96V96C512,43.072,468.928,0,416,0z"
|
||||
);
|
||||
chatIcon.appendChild(path);
|
||||
|
||||
chatButton.appendChild(chatIcon);
|
||||
|
||||
return chatButton;
|
||||
};
|
||||
|
||||
const createChatBox = () => {
|
||||
const chatBox = document.createElement("div");
|
||||
chatBox.id = CHAT_BOX_ID;
|
||||
chatBox.style.position = "fixed";
|
||||
chatBox.style.bottom = "100px";
|
||||
chatBox.style.right = "0";
|
||||
chatBox.style.backgroundColor = "white";
|
||||
chatBox.style.width = "300px";
|
||||
chatBox.style.height = "500px";
|
||||
chatBox.style.zIndex = 9999;
|
||||
chatBox.style.borderRadius = "10px";
|
||||
chatBox.style.display = "none"; // Initially hidden
|
||||
|
||||
// Messages section
|
||||
const chatMessages = document.createElement("div");
|
||||
chatMessages.id = CHAT_MESSAGES_ID;
|
||||
chatMessages.style.height = "80%";
|
||||
chatMessages.style.overflowY = "auto";
|
||||
chatMessages.style.padding = "10px";
|
||||
chatMessages.style.color = "black";
|
||||
chatBox.appendChild(chatMessages);
|
||||
// Input section
|
||||
const chatInputContainer = document.createElement("div");
|
||||
chatInputContainer.style.position = "absolute";
|
||||
chatInputContainer.style.bottom = "0";
|
||||
chatInputContainer.style.width = "100%";
|
||||
chatInputContainer.style.padding = "10px";
|
||||
chatInputContainer.style.boxSizing = "border-box";
|
||||
chatInputContainer.style.backgroundColor = "#f1f1f1";
|
||||
chatInputContainer.style.borderBottomLeftRadius = "10px";
|
||||
chatInputContainer.style.borderBottomRightRadius = "10px";
|
||||
|
||||
// Flexbox layout for horizontal alignment
|
||||
chatInputContainer.style.display = "flex";
|
||||
chatInputContainer.style.alignItems = "center";
|
||||
|
||||
// Input field
|
||||
const chatInput = document.createElement("input");
|
||||
chatInput.id = CHAT_INPUT_ID;
|
||||
chatInput.type = "text";
|
||||
chatInput.style.flex = "1"; // Allow the input to grow and take up available space
|
||||
chatInput.style.padding = "10px";
|
||||
chatInput.style.boxSizing = "border-box";
|
||||
chatInput.style.border = "1px solid #ccc";
|
||||
chatInput.style.borderRadius = "4px";
|
||||
chatInput.placeholder = "Describe your desired workflow...";
|
||||
|
||||
// Send button
|
||||
const chatSendButton = document.createElement("button");
|
||||
chatSendButton.id = CHAT_SEND_BUTTON_ID;
|
||||
chatSendButton.style.width = "40px";
|
||||
chatSendButton.style.height = "40px";
|
||||
chatSendButton.style.marginLeft = "10px";
|
||||
chatSendButton.style.padding = "0"; // Remove default padding
|
||||
chatSendButton.style.border = "none";
|
||||
chatSendButton.style.backgroundColor = "#007acc";
|
||||
chatSendButton.style.color = "white";
|
||||
chatSendButton.style.cursor = "pointer";
|
||||
chatSendButton.style.borderRadius = "50%";
|
||||
chatSendButton.style.display = "flex";
|
||||
chatSendButton.style.alignItems = "center";
|
||||
chatSendButton.style.justifyContent = "center";
|
||||
|
||||
// Create and style the SVG icon
|
||||
const sendIcon = document.createElementNS(
|
||||
"http://www.w3.org/2000/svg",
|
||||
"svg"
|
||||
);
|
||||
sendIcon.setAttribute("fill", "#ffffff"); // Use white color for the icon
|
||||
sendIcon.setAttribute("viewBox", "0 0 52 52");
|
||||
sendIcon.setAttribute("enable-background", "new 0 0 52 52");
|
||||
sendIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
||||
sendIcon.setAttribute("class", "send-icon");
|
||||
sendIcon.setAttribute("xml:space", "preserve");
|
||||
sendIcon.setAttribute("width", "18px"); // Adjust size as needed
|
||||
sendIcon.setAttribute("height", "18px"); // Adjust size as needed
|
||||
|
||||
// Create the path element for the SVG
|
||||
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
||||
path.setAttribute(
|
||||
"d",
|
||||
"M2.1,44.5l4.4-16.3h18.6c0.5,0,1-0.5,1-1v-2c0-0.5-0.5-1-1-1H6.5l-4.3-16l0,0C2.1,8,2,7.7,2,7.4C2,6.7,2.7,6,3.5,6.1c0.2,0,0.3,0.1,0.5,0.1l0,0l0,0l0,0l0,0l45,18.5c0.6,0.2,1,0.8,1,1.4s-0.4,1.1-0.9,1.3l0,0L4,46.4l0,0c-0.2,0.1-0.4,0.1-0.6,0.1C2.6,46.4,2,45.8,2,45C2,44.8,2,44.7,2.1,44.5L2.1,44.5z"
|
||||
);
|
||||
sendIcon.appendChild(path);
|
||||
|
||||
// Append SVG to button
|
||||
chatSendButton.appendChild(sendIcon);
|
||||
|
||||
// Add event listener
|
||||
chatSendButton.addEventListener("click", sendMessage);
|
||||
|
||||
// Append input and button to container
|
||||
chatInputContainer.appendChild(chatInput);
|
||||
chatInputContainer.appendChild(chatSendButton);
|
||||
chatBox.appendChild(chatInputContainer);
|
||||
|
||||
return chatBox;
|
||||
};
|
||||
|
||||
const updateMessageBoxStatus = (canSendMessage) => {
|
||||
const button = document.getElementById(CHAT_SEND_BUTTON_ID);
|
||||
if (button) {
|
||||
button.style.backgroundColor = canSendMessage ? "#007acc" : "#cccccc"; // Change color based on `isActive`
|
||||
}
|
||||
const input = document.getElementById(CHAT_INPUT_ID);
|
||||
if (input) {
|
||||
input.placeholder = canSendMessage ? "Describe your desired workflow..." : "Generating workflow...";
|
||||
input.disabled = !canSendMessage;
|
||||
}
|
||||
};
|
||||
|
||||
const sendMessage = async () => {
|
||||
if (!doesApiTokenExist) {
|
||||
toggleChatBox();
|
||||
setButtonDefault();
|
||||
return authDialog.show();
|
||||
}
|
||||
|
||||
const chatInput = document.getElementById(CHAT_INPUT_ID);
|
||||
const chatMessages = document.getElementById(CHAT_MESSAGES_ID);
|
||||
const message = chatInput.value.trim();
|
||||
|
||||
if (message) {
|
||||
if (!canSendMessage) {
|
||||
return;
|
||||
} else {
|
||||
canSendMessage = false;
|
||||
updateMessageBoxStatus(canSendMessage);
|
||||
}
|
||||
|
||||
const messageElement = document.createElement("div");
|
||||
messageElement.innerText = message;
|
||||
messageElement.style.marginBottom = "10px";
|
||||
messageElement.style.padding = "10px";
|
||||
messageElement.style.backgroundColor = "#A7DBFD";
|
||||
messageElement.style.borderRadius = "4px";
|
||||
|
||||
chatMessages.appendChild(messageElement);
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight; // Scroll to the bottom
|
||||
chatInput.value = "";
|
||||
|
||||
try {
|
||||
const response = await getBotResponse(message);
|
||||
let botMessage = "";
|
||||
|
||||
const parsedBotResponse = parseWorkflowFromBot(response);
|
||||
|
||||
if (isValidWorkflow(parsedBotResponse)) {
|
||||
botMessage = JSON.stringify(parsedBotResponse);
|
||||
} else {
|
||||
botMessage = response.responses.bot;
|
||||
}
|
||||
|
||||
const botMessageElement = document.createElement("div");
|
||||
botMessageElement.innerText = botMessage;
|
||||
botMessageElement.style.marginBottom = "10px";
|
||||
botMessageElement.style.padding = "10px";
|
||||
botMessageElement.style.backgroundColor = "#DFE8EE";
|
||||
botMessageElement.style.borderRadius = "4px";
|
||||
botMessageElement.style.wordWrap = "break-word"; // Wrap long words
|
||||
botMessageElement.style.overflow = "hidden"; // Hide overflow text
|
||||
botMessageElement.style.textOverflow = "ellipsis"; // Show ellipsis if text overflows
|
||||
|
||||
// Optionally, set a max-width if you want to control the width of the message box
|
||||
botMessageElement.style.maxWidth = "calc(100% - 20px)"; // Adjust max-width based on container width and padding
|
||||
|
||||
chatMessages.appendChild(botMessageElement);
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight; // Scroll to the bottom
|
||||
|
||||
loadGraphFromPrompt(parsedBotResponse);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
canSendMessage = true;
|
||||
updateMessageBoxStatus(canSendMessage);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const registerChat = () => {
|
||||
// With js, append an bottom that opens up a chatbox in the bottom right hand corner
|
||||
const chatButton = createChatButton();
|
||||
const chatBox = createChatBox();
|
||||
|
||||
document.body.appendChild(chatBox);
|
||||
document.body.appendChild(chatButton);
|
||||
chatButton.addEventListener("click", toggleChatBox);
|
||||
};
|
||||
|
||||
/** @typedef {import('../../../web/types/comfy.js').ComfyExtension} ComfyExtension*/
|
||||
/** @type {ComfyExtension} */
|
||||
const ext = {
|
||||
name: "nathannlu.ComfyUI-Chat",
|
||||
|
||||
// ComfyUI extension init
|
||||
init() {
|
||||
registerChat();
|
||||
},
|
||||
};
|
||||
|
||||
app.registerExtension(ext);
|
||||
@@ -0,0 +1,116 @@
|
||||
import { app } from "../comfy/comfy.js";
|
||||
import { getInputType } from "./edgeTypes.js";
|
||||
|
||||
function getInputIndex(node, inputName) {
|
||||
return node.inputs.findIndex(
|
||||
(input) => input.name === getInputType(inputName)
|
||||
);
|
||||
}
|
||||
|
||||
// translates chatgpt input to immediately loadable graph format
|
||||
function transformInputToGraph(input) {
|
||||
const baseGraph = {
|
||||
last_node_id: input.nodes.length,
|
||||
last_link_id: input.edges.length,
|
||||
nodes: [],
|
||||
links: [],
|
||||
groups: [],
|
||||
config: {},
|
||||
extra: {},
|
||||
version: 0.4,
|
||||
};
|
||||
|
||||
|
||||
const xStep = 300; // step for node positions
|
||||
const yStep = 300; // step for node positions
|
||||
const yBase = 100; // base y position
|
||||
const nodesById = {}; // pointer of nodes by id for easier/faster search
|
||||
|
||||
let x = 100; // starting x post
|
||||
let y = yBase; // starting y post
|
||||
let columnCounter = 0; // counter for columns
|
||||
// Create nodes with positions in order
|
||||
input.nodes.forEach((node, index) => {
|
||||
const newNode = {
|
||||
id: node.id,
|
||||
type: node.class_type,
|
||||
pos: [x, y],
|
||||
size: [200, 50], // TODO: write a function to get the node's actual height for better placement
|
||||
flags: {},
|
||||
order: index,
|
||||
mode: 0,
|
||||
inputs: [],
|
||||
outputs: [],
|
||||
properties: {},
|
||||
widgets_values: node.widget_values ? Object.values(node.widget_values) : []
|
||||
};
|
||||
|
||||
baseGraph.nodes.push(newNode);
|
||||
nodesById[node.id] = newNode;
|
||||
|
||||
if (columnCounter % 3 === 0) {
|
||||
x += xStep;
|
||||
y = yBase;
|
||||
} else {
|
||||
// y += newNode.size[1] + yStep;
|
||||
y += yStep;
|
||||
}
|
||||
columnCounter++;
|
||||
});
|
||||
|
||||
// Insert edges
|
||||
input.edges.forEach((edgeData, index) => {
|
||||
const fromNode = nodesById[edgeData.from];
|
||||
const toNode = nodesById[edgeData.to];
|
||||
|
||||
// Adding output to fromNode
|
||||
let outputIndex = edgeData.from_output;
|
||||
// add inputs
|
||||
let inputIndex = getInputIndex(toNode, edgeData.to_input);
|
||||
|
||||
// if input doesnt already exist
|
||||
if (inputIndex < 0) {
|
||||
toNode.inputs.push({
|
||||
name: edgeData.to_input,
|
||||
type: getInputType(edgeData.to_input),
|
||||
link: index + 1,
|
||||
});
|
||||
}
|
||||
|
||||
// add outputs
|
||||
fromNode.outputs.push({
|
||||
name: getInputType(edgeData.to_input),
|
||||
// name: edgeData.input,
|
||||
type: getInputType(edgeData.to_input),
|
||||
links: [],
|
||||
slot_index: fromNode.outputs.length,
|
||||
});
|
||||
|
||||
|
||||
outputIndex = fromNode.outputs.length - 1;
|
||||
inputIndex = fromNode.inputs.length - 1;
|
||||
|
||||
// add links
|
||||
const link = [
|
||||
index + 1, // Link id
|
||||
fromNode.id, // Origin node id
|
||||
edgeData.from_output,
|
||||
// getOutputIndex(fromNode, edgeData.to_input), // origin index
|
||||
toNode.id, // Destination node id
|
||||
getInputIndex(toNode, edgeData.to_input), // destination index
|
||||
getInputType(edgeData.to_input),
|
||||
];
|
||||
|
||||
fromNode.outputs[outputIndex].links.push(link[0]);
|
||||
baseGraph.links.push(link);
|
||||
});
|
||||
|
||||
|
||||
return baseGraph;
|
||||
}
|
||||
|
||||
export function loadGraphFromPrompt(generatedWorkflow) {
|
||||
const translatedData = transformInputToGraph(generatedWorkflow);
|
||||
app.loadGraphData(translatedData, true);
|
||||
app.graph.change();
|
||||
}
|
||||
+31
-27
@@ -17,14 +17,14 @@ const workflowRun = {
|
||||
retrieveOutput: {
|
||||
method: "GET",
|
||||
path: "/workflow/{workflow_id}/runs/{run_id}/outputs",
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const workflow = {
|
||||
// upload local
|
||||
init: {
|
||||
method: "POST",
|
||||
path: "/workflow/init"
|
||||
path: "/workflow/init",
|
||||
},
|
||||
create: {
|
||||
method: "POST",
|
||||
@@ -40,71 +40,75 @@ const workflow = {
|
||||
},
|
||||
error: {
|
||||
method: "POST",
|
||||
path: "/workflow/error"
|
||||
}
|
||||
}
|
||||
path: "/workflow/error",
|
||||
},
|
||||
};
|
||||
|
||||
const billing = {
|
||||
retrieveCustomerSession: {
|
||||
method: "GET",
|
||||
path: "/stripe/get-customer-session"
|
||||
path: "/stripe/get-customer-session",
|
||||
},
|
||||
retrieveUsage: {
|
||||
method: "GET",
|
||||
path: "/stripe/usage"
|
||||
path: "/stripe/usage",
|
||||
},
|
||||
retrieveCredits: {
|
||||
method: "GET",
|
||||
path: "/stripe/credits"
|
||||
path: "/stripe/credits",
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const auth = {
|
||||
register: {
|
||||
method: "POST",
|
||||
path: "/auth/register"
|
||||
path: "/auth/register",
|
||||
},
|
||||
login: {
|
||||
method: "POST",
|
||||
path: "/auth/login"
|
||||
}
|
||||
}
|
||||
path: "/auth/login",
|
||||
},
|
||||
};
|
||||
|
||||
const support = {
|
||||
feedback: {
|
||||
method: "POST",
|
||||
path: "/support/feedback"
|
||||
path: "/support/feedback",
|
||||
},
|
||||
support: {
|
||||
method: "POST",
|
||||
path: "/support/support"
|
||||
path: "/support/support",
|
||||
},
|
||||
docs: {
|
||||
method: "POST",
|
||||
path: "/support/docs"
|
||||
path: "/support/docs",
|
||||
},
|
||||
tooltipHover: {
|
||||
method: "POST",
|
||||
path: "/support/tooltipHover"
|
||||
path: "/support/tooltipHover",
|
||||
},
|
||||
tooltipDocs: {
|
||||
method: "POST",
|
||||
path: "/support/tooltipDocs"
|
||||
path: "/support/tooltipDocs",
|
||||
},
|
||||
assistant: {
|
||||
method: "POST",
|
||||
path: "/support/assistant"
|
||||
}
|
||||
}
|
||||
path: "/support/assistant",
|
||||
},
|
||||
};
|
||||
|
||||
const chatbot = {
|
||||
sendMessage: {
|
||||
method: "POST",
|
||||
path: "/chat/send-message",
|
||||
},
|
||||
};
|
||||
|
||||
export const apiEndpoints = {
|
||||
workflowRun,
|
||||
workflow,
|
||||
billing,
|
||||
auth,
|
||||
support
|
||||
}
|
||||
|
||||
|
||||
|
||||
support,
|
||||
chatbot
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user