From dd729dd15cc50510fef5e765d9b6baf03fa76a51 Mon Sep 17 00:00:00 2001 From: pixelworld AI Date: Sun, 7 Sep 2025 08:58:19 -0500 Subject: [PATCH] feat: add unified Seed 1.6 Chat node with session memory - Implements consolidated chat node supporting both seed-1-6-250615 and seed-1-6-flash-250715 models - Features session-based conversation memory for continuous interactions - Supports multimodal input (up to 4 images) with configurable detail levels - Includes thinking mode and reasoning effort controls - Provides external history import and conversation export - Default session memory disabled for casual use, opt-in for persistent conversations --- nodes/byteplus_chat_node.py | 247 ++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 nodes/byteplus_chat_node.py diff --git a/nodes/byteplus_chat_node.py b/nodes/byteplus_chat_node.py new file mode 100644 index 0000000..8750806 --- /dev/null +++ b/nodes/byteplus_chat_node.py @@ -0,0 +1,247 @@ +from .byteplus_utils import ( + BytePlusChatApiHandler, + BytePlusChatUtils, + BytePlusImageUtils +) + + +class SeedChatNode: + """Unified Seed 1.6 Chat Node with Vision Support and Session Memory""" + + # Class-level conversation memory storage + conversation_sessions = {} + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "model": (["seed-1-6-250615", "seed-1-6-flash-250715"], {"default": "seed-1-6-flash-250715"}), + "user_message": ("STRING", {"default": "", "multiline": True}), + "session_id": ("STRING", {"default": "default", "tooltip": "Unique identifier for conversation session"}), + "use_session_memory": ("BOOLEAN", {"default": False, "tooltip": "Keep conversation context in memory"}), + }, + "optional": { + "system_message": ("STRING", {"default": "", "multiline": True}), + "image_1": ("IMAGE",), + "image_2": ("IMAGE",), + "image_3": ("IMAGE",), + "image_4": ("IMAGE",), + "image_detail": (["auto", "high", "low"], {"default": "auto"}), + "thinking_mode": (["enabled", "disabled"], {"default": "enabled"}), + "reasoning_effort": (["low", "medium", "high"], {"default": "medium"}), + "clear_session": ("BOOLEAN", {"default": False, "tooltip": "Clear conversation memory for this session"}), + "external_history": ("STRING", {"default": "", "multiline": True, "tooltip": "Import external conversation history"}), + }, + } + + RETURN_TYPES = ("STRING", "STRING", "STRING") + RETURN_NAMES = ("response", "full_conversation", "session_status") + FUNCTION = "chat_completion" + CATEGORY = "Seed/Chat" + + def chat_completion( + self, + model, + user_message, + session_id, + use_session_memory, + system_message="", + image_1=None, + image_2=None, + image_3=None, + image_4=None, + image_detail="auto", + thinking_mode="enabled", + reasoning_effort="medium", + clear_session=False, + external_history="" + ): + try: + # Handle session clearing + if clear_session and session_id in self.conversation_sessions: + del self.conversation_sessions[session_id] + print(f"Cleared conversation session: {session_id}") + + # Initialize session if not exists + if session_id not in self.conversation_sessions: + self.conversation_sessions[session_id] = { + "messages": [], + "system_message": "", + } + + session = self.conversation_sessions[session_id] + + # Handle system message changes + if system_message.strip() and system_message != session["system_message"]: + # Clear previous system message and update + session["messages"] = [msg for msg in session["messages"] if msg["role"] != "system"] + session["system_message"] = system_message + session["messages"].insert(0, BytePlusChatUtils.format_text_message("system", system_message)) + elif system_message.strip() and not session["system_message"]: + # Add initial system message + session["system_message"] = system_message + session["messages"].insert(0, BytePlusChatUtils.format_text_message("system", system_message)) + + # Handle external history import (overwrites session memory) + if external_history.strip(): + imported_messages = self._parse_conversation_history(external_history) + if use_session_memory: + session["messages"] = imported_messages.copy() + messages = imported_messages.copy() + else: + # Use session memory if enabled + if use_session_memory: + messages = session["messages"].copy() + else: + messages = [] + # Add system message if provided and not using session memory + if system_message.strip(): + messages.append(BytePlusChatUtils.format_text_message("system", system_message)) + + # Collect images for multimodal support + images = [] + for img in [image_1, image_2, image_3, image_4]: + if img is not None: + images.append(img) + + # Add current user message (multimodal if images are present) + if images: + user_msg = BytePlusChatUtils.format_multimodal_message( + "user", user_message, images, image_detail + ) + else: + user_msg = BytePlusChatUtils.format_text_message("user", user_message) + + messages.append(user_msg) + + # Make API call + response = BytePlusChatApiHandler.create_chat_completion( + model=model, + messages=messages, + thinking_type=thinking_mode, + reasoning_effort=reasoning_effort + ) + + if not response: + return BytePlusChatApiHandler.handle_chat_error(model, "No response received") + + # Extract response text + response_text = BytePlusChatUtils.extract_response_text(response) + + # Update session memory if enabled + if use_session_memory and not external_history.strip(): + session["messages"].append(user_msg) + session["messages"].append(BytePlusChatUtils.format_text_message("assistant", response_text)) + + # Trim session memory if it gets too long (keep last 20 messages + system) + system_msgs = [msg for msg in session["messages"] if msg["role"] == "system"] + other_msgs = [msg for msg in session["messages"] if msg["role"] != "system"] + if len(other_msgs) > 20: + other_msgs = other_msgs[-20:] + session["messages"] = system_msgs + other_msgs + + # Build full conversation for output + full_conversation = self._build_conversation_output(messages, response_text) + + # Create session status message + session_status = self._create_session_status(session_id, use_session_memory, len(session["messages"])) + + return (response_text, full_conversation, session_status) + + except Exception as e: + error_msg = f"Error with {model}: {str(e)}" + return (error_msg, error_msg, f"Session {session_id}: Error occurred") + + def _parse_conversation_history(self, history_text): + """Parse conversation history from text format.""" + messages = [] + lines = history_text.strip().split('\n') + current_role = None + current_content = [] + + for line in lines: + line = line.strip() + if not line: + continue + + # Check for role indicators + if line.startswith("User:") or line.startswith("user:"): + if current_role and current_content: + messages.append(BytePlusChatUtils.format_text_message(current_role, '\n'.join(current_content))) + current_role = "user" + current_content = [line[5:].strip()] + elif line.startswith("Assistant:") or line.startswith("assistant:"): + if current_role and current_content: + messages.append(BytePlusChatUtils.format_text_message(current_role, '\n'.join(current_content))) + current_role = "assistant" + current_content = [line[10:].strip()] + elif line.startswith("System:") or line.startswith("system:"): + if current_role and current_content: + messages.append(BytePlusChatUtils.format_text_message(current_role, '\n'.join(current_content))) + current_role = "system" + current_content = [line[7:].strip()] + else: + # Continue current message + if current_role: + current_content.append(line) + + # Add the last message + if current_role and current_content: + messages.append(BytePlusChatUtils.format_text_message(current_role, '\n'.join(current_content))) + + return messages + + def _build_conversation_output(self, messages, response_text): + """Build a formatted conversation output.""" + conversation_parts = [] + + for msg in messages: + role = msg["role"].title() + if isinstance(msg["content"], str): + content = msg["content"] + elif isinstance(msg["content"], list): + # Extract text from multimodal content + text_parts = [] + image_count = 0 + for item in msg["content"]: + if item["type"] == "text": + text_parts.append(item["text"]) + elif item["type"] == "image_url": + image_count += 1 + + content = '\n'.join(text_parts) + if image_count > 0: + content += f"\n[{image_count} image(s) attached]" + else: + content = str(msg["content"]) + + conversation_parts.append(f"{role}: {content}") + + # Add the response + conversation_parts.append(f"Assistant: {response_text}") + + return '\n\n'.join(conversation_parts) + + def _create_session_status(self, session_id, use_memory, message_count): + """Create status message about the session.""" + if use_memory: + return f"Session '{session_id}': Memory enabled, {message_count} messages stored" + else: + return f"Session '{session_id}': Memory disabled, one-shot conversation" + + @classmethod + def clear_all_sessions(cls): + """Utility method to clear all conversation sessions.""" + cls.conversation_sessions.clear() + print("All conversation sessions cleared") + + +# Node class mappings +NODE_CLASS_MAPPINGS = { + "SeedChat": SeedChatNode, +} + +# Node display name mappings +NODE_DISPLAY_NAME_MAPPINGS = { + "SeedChat": "Seed 1.6 Chat", +}