Files
2025-05-29 14:45:32 +12:00

245 lines
8.2 KiB
JavaScript

import { commentWidget, bodyWidget } from "./utils.js";
import { api } from "../../../scripts/api.js";
// Global store for incoming messages per node
const incoming_data = {};
const messagePromises = {};
const agents = {};
const images = {};
// Using a promise to be able to wait for messages
function messageHandler(event) {
console.log("Event detail: ", event);
const { text_context, agent, images, id } = event.detail;
agents[id] = agent;
images[id] = images;
incoming_data[id] = { text_context };
// If there's a pending promise for this ID, resolve it
if (messagePromises[id]) {
messagePromises[id].resolve(incoming_data[id]);
delete messagePromises[id];
}
}
function waitForMessage(id, timeout = 5000) {
return new Promise((resolve, reject) => {
// If we already have the data, return it immediately
if (incoming_data[id]) {
return resolve(incoming_data[id]);
}
// Otherwise, store the promise resolvers
messagePromises[id] = { resolve, reject };
// Add timeout
setTimeout(() => {
if (messagePromises[id]) {
delete messagePromises[id];
reject(new Error(`Timeout waiting for message for node ${id}`));
}
}, timeout);
});
}
// create a listner for the chat node event
api.addEventListener("griptape.chat_node", messageHandler);
// Setup the Chat Node itself
export function setupChatNode(nodeType, nodeData, app) {
if (nodeData.name.includes("Griptape Chat")) {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = async function () {
onNodeCreated?.apply(this, arguments);
// ----------------------------------------------------------
// Grab Widgets and Inputs
// Headers and Body Widgets
const commentWidgets = this.widgets.filter((w) =>
w.name.toLowerCase().includes("comment")
);
commentWidgets.forEach((widget) => commentWidget(this, widget));
const bodyWidgets = this.widgets.filter((w) =>
w.name.toLowerCase().includes("body")
);
bodyWidgets.forEach((widget) => bodyWidget(this, widget));
// Inputs
const agent = this.inputs.find((i) => i.name === "agent");
const text_context_input = this.inputs.find(
(i) => i.name === "text_context"
);
const image_context_input = this.inputs.find(
(i) => i.name === "image_context"
);
// Message Widgets
const user_message_widget = this.widgets.find(
(w) => w.name === "user_message"
);
const agent_response_widget = this.widgets.find(
(w) => w.name === "agent_response"
);
const agent_output_widget = this.widgets.find(
(w) => w.name === "agent_output"
);
// Get the output_selector input
const output_selector_widget = this.widgets.find(
(w) => w.name === "output_selector"
);
// ----------------------------------------------------------
// Initialize conversation history
this.conversationHistory = [];
this.output_history = [];
// Add callback to output_selector
output_selector_widget.callback = (value) => {
const index = parseInt(value);
if (index >= 0 && index < this.output_history.length) {
agent_output_widget.value = this.output_history[index];
}
};
// Function to handle clearing chat
const clearChat = () => {
this.conversationHistory = [];
this.output_history = [];
user_message_widget.value = "";
agent_response_widget.value = "";
agent_output_widget.value = "";
output_selector_widget.value = 0;
output_selector_widget.callback?.(0);
};
// Function to refresh context
const refreshContext = async () => {
app.queueNodeIds = [this.id];
await app.queuePrompt();
try {
const messageData = await waitForMessage(this.id);
} catch (error) {
console.error("Failed to get message:", error);
incoming_data[this.id] = null;
}
app.queueNodeIds = null;
};
// Function to handle sending message
const sendMessage = async () => {
if (user_message_widget && user_message_widget.value?.trim()) {
try {
const userMessage = user_message_widget.value;
this.conversationHistory.push({
role: "User",
message: userMessage,
});
const context = incoming_data[this.id];
// Show loading state
agent_response_widget.value = "🤔 Thinking...";
// agent_output_widget.value = "Waiting for response...";
// Make API call
const response = await fetch("/Griptape/prompt_chat", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_message: userMessage,
conversation_history: this.conversationHistory,
context: context,
prev_agent_output: agent_output_widget,
agent: agents[this.id],
image_context: images[this.id],
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Get response data
const data = await response.json();
// Update agent response
let responseText = data.response || "No response received";
agent_response_widget.value = responseText;
// Add assistant response to history
this.conversationHistory.push({
role: "Assistant",
message: data.response || "No response received",
});
// Update prompt output if provided
if (data.prompt) {
// check and see if the prompt is the same as the most recent prompt in the history
// if it is, then don't add it to the history
if (
this.output_history.length === 0 ||
this.output_history[this.output_history.length - 1] !==
data.prompt
) {
this.output_history.push(data.prompt);
}
agent_output_widget.value = data.prompt;
let current_index = this.output_history.length - 1;
output_selector_widget.value = this.output_history.length - 1;
// Tell ComfyUI these widgets need redrawing
output_selector_widget.callback?.(output_selector_widget.value);
this.setDirtyCanvas(true);
}
} catch (error) {
console.error("Error:", error);
agent_response_widget.value =
"## Error\nFailed to get response from agent";
agent_output_widget.value =
"Error occurred while generating prompt";
}
// Select the message text for easy replacement
setTimeout(() => {
const textareas = document.querySelectorAll("textarea");
for (let textarea of textareas) {
if (textarea.value === user_message_widget.value) {
textarea.focus();
textarea.select();
break;
}
}
}, 100);
}
};
// Add enter key handler to the textarea
setTimeout(() => {
const textareas = document.querySelectorAll("textarea");
for (let textarea of textareas) {
textarea.addEventListener("keydown", (e) => {
// Check if it's the prompt textarea
if (textarea.value === user_message_widget.value) {
if (e.key === "Enter" && e.shiftKey) {
e.preventDefault(); // Prevent new line
sendMessage();
}
}
});
}
}, 100);
// Add RefreshButton
this.addWidget("button", "Refresh Context", null, refreshContext);
// Add clear button
this.addWidget("button", "Clear Chat", null, clearChat);
};
setTimeout(() => {
nodeType.prototype.computeSize = function () {
return [400, 500];
};
}, 100);
}
}