diff --git a/lollms/personality.py b/lollms/personality.py index 511feec..cc1dd54 100644 --- a/lollms/personality.py +++ b/lollms/personality.py @@ -31,7 +31,7 @@ from ascii_colors import ASCIIColors import time from lollms.types import MSG_OPERATION_TYPE, SUMMARY_MODE import json -from typing import Any, List, Optional, Type, Callable, Dict, Any, Union +from typing import Any, List, Optional, Type, Callable, Dict, Any, Union, Tuple import json from lollmsvectordb.vector_database import VectorDatabase from lollmsvectordb.text_document_loader import TextDocumentsLoader @@ -3123,28 +3123,84 @@ Use this structure: prompt_parts[sacrifice_id] = sacrifice_text return self.separator_template.join([s for s in prompt_parts if s!=""]) # ================================================= Sending commands to ui =========================================== - def add_collapsible_entry(self, title, content, subtitle="", open_by_default=False): + def add_collapsible_entry(self, title, content, subtitle="", open_by_default=False, icon=None, type="default"): + """ + Creates a collapsible entry with enhanced styling and animations. + + Args: + title (str): The main title of the collapsible + content (str): The content to be displayed when expanded + subtitle (str): Optional subtitle text + open_by_default (bool): Whether the collapsible should be open by default + icon (str): Optional custom icon SVG string + type (str): Type of collapsible ('default', 'success', 'warning', 'error', 'info') + + Returns: + str: HTML string for the collapsible element + """ + # Color schemes for different types + color_schemes = { + "default": "border-gray-200 bg-white hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:hover:bg-gray-750", + "success": "border-green-200 bg-green-50 hover:bg-green-100 dark:border-green-700 dark:bg-green-900/20 dark:hover:bg-green-900/30", + "warning": "border-yellow-200 bg-yellow-50 hover:bg-yellow-100 dark:border-yellow-700 dark:bg-yellow-900/20 dark:hover:bg-yellow-900/30", + "error": "border-red-200 bg-red-50 hover:bg-red-100 dark:border-red-700 dark:bg-red-900/20 dark:hover:bg-red-900/30", + "info": "border-blue-200 bg-blue-50 hover:bg-blue-100 dark:border-blue-700 dark:bg-blue-900/20 dark:hover:bg-blue-900/30" + } + + # Default arrow icon if no custom icon is provided + default_icon = ''' + + + + ''' + + icon_html = icon if icon else default_icon + color_scheme = color_schemes.get(type, color_schemes["default"]) open_attr = 'open' if open_by_default else '' + return "\n".join([ - f'
', - f' ', - f'
', - f'
', - f' ', - f' ', - f' ', - f'
', - f'
', - f'

{title}

', - f'

{subtitle}

', - f'
', - f'
', - f'
', - f'
', - f'

{content}

', - f'
', - f'
\n' - ]) + f''' +
+ +
+
+{icon_html} +
+
+

+{title} +

+{f'

{subtitle}

' if subtitle else ''} +
+
+
+ + + +
+
+
+
+{content} +
+
+
+''' +]) @@ -3651,29 +3707,35 @@ Use this structure: return updated_content, True # Section updated successfully - def extract_code_blocks(self, text: str) -> List[dict]: + def extract_code_blocks(self, text: str, return_remaining_text: bool = False) -> Union[List[dict], Tuple[List[dict], str]]: """ - This function extracts code blocks from a given text. + This function extracts code blocks from a given text and optionally returns the text without code blocks. Parameters: text (str): The text from which to extract code blocks. Code blocks are identified by triple backticks (```). + return_remaining_text (bool): If True, also returns the text with code blocks removed. Returns: - List[dict]: A list of dictionaries where each dictionary represents a code block and contains the following keys: - - 'index' (int): The index of the code block in the text. - - 'file_name' (str): The name of the file extracted from the preceding line, if available. - - 'content' (str): The content of the code block. - - 'type' (str): The type of the code block. If the code block starts with a language specifier (like 'python' or 'java'), this field will contain that specifier. Otherwise, it will be set to 'language-specific'. - - 'is_complete' (bool): True if the block has a closing tag, False otherwise. - - Note: - The function assumes that the number of triple backticks in the text is even. - If the number of triple backticks is odd, it will consider the rest of the text as the last code block. + Union[List[dict], Tuple[List[dict], str]]: + - If return_remaining_text is False: Returns only the list of code block dictionaries + - If return_remaining_text is True: Returns a tuple containing: + * List of code block dictionaries + * String containing the text with all code blocks removed + + Each code block dictionary contains: + - 'index' (int): The index of the code block in the text + - 'file_name' (str): The name of the file extracted from the preceding line, if available + - 'content' (str): The content of the code block + - 'type' (str): The type of the code block + - 'is_complete' (bool): True if the block has a closing tag, False otherwise """ remaining = text bloc_index = 0 first_index = 0 indices = [] + text_without_blocks = text + + # Find all code block delimiters while len(remaining) > 0: try: index = remaining.index("```") @@ -3689,16 +3751,27 @@ Use this structure: code_blocks = [] is_start = True + + # Process code blocks and build text without blocks if requested + if return_remaining_text: + text_parts = [] + last_end = 0 + for index, code_delimiter_position in enumerate(indices): - block_infos = { - 'index': index, - 'file_name': "", - 'section': "", - 'content': "", - 'type': "", - 'is_complete': False - } if is_start: + block_infos = { + 'index': len(code_blocks), + 'file_name': "", + 'section': "", + 'content': "", + 'type': "", + 'is_complete': False + } + + # Store text before code block if returning remaining text + if return_remaining_text: + text_parts.append(text[last_end:code_delimiter_position].strip()) + # Check the preceding line for file name preceding_text = text[:code_delimiter_position].strip().splitlines() if preceding_text: @@ -3727,6 +3800,7 @@ Use this structure: if '{' in sub_text[:next_index]: next_index = 0 start_pos = next_index + if code_delimiter_position + 3 < len(text) and text[code_delimiter_position + 3] in ["\n", " ", "\t"]: block_infos["type"] = 'language-specific' else: @@ -3740,17 +3814,32 @@ Use this structure: else: block_infos["content"] = sub_text[start_pos:next_pos].strip() block_infos["is_complete"] = False + + if return_remaining_text: + last_end = indices[index + 1] + 3 else: block_infos["content"] = sub_text[start_pos:].strip() block_infos["is_complete"] = False + + if return_remaining_text: + last_end = len(text) + code_blocks.append(block_infos) is_start = False else: is_start = True - continue - + + if return_remaining_text: + # Add any remaining text after the last code block + if last_end < len(text): + text_parts.append(text[last_end:].strip()) + # Join all non-code parts with newlines + text_without_blocks = '\n'.join(filter(None, text_parts)) + return code_blocks, text_without_blocks + return code_blocks + def build_and_execute_python_code(self,context, instructions, execution_function_signature, extra_imports=""): start_header_id_template = self.config.start_header_id_template end_header_id_template = self.config.end_header_id_template @@ -4185,9 +4274,9 @@ Use this structure: self.print_prompt("Generated", generated_text) # Extract the function calls from the generated text. - function_calls = self.extract_function_calls_as_json(generated_text) + function_calls, text_without_code = self.extract_function_calls_as_json(generated_text) - return generated_text, function_calls + return generated_text, function_calls, text_without_code def generate_with_function_calls_and_images(self, context_details: dict, images:list, functions: List[Dict[str, Any]], max_answer_length: Optional[int] = None, callback = None) -> List[Dict[str, Any]]: @@ -4209,9 +4298,9 @@ Use this structure: generated_text = self.fast_gen_with_images(upgraded_prompt, images, max_answer_length, callback=callback) # Extract the function calls from the generated text. - function_calls = self.extract_function_calls_as_json(generated_text) + function_calls, text_without_code = self.extract_function_calls_as_json(generated_text) - return generated_text, function_calls + return generated_text, function_calls, text_without_code def execute_function(self, code, function_definitions = None): function_call = json.loads(code) @@ -4361,7 +4450,7 @@ Use this structure: """ # Extract markdown code blocks that contain JSON. - code_blocks = self.extract_code_blocks(text) + code_blocks, text_without_code = self.extract_code_blocks(text, True) # Filter out and parse JSON entries. function_calls = [] @@ -4379,7 +4468,7 @@ Use this structure: # If the content is not valid JSON, skip it. continue - return function_calls + return function_calls, text_without_code def interact( @@ -4408,15 +4497,15 @@ Use this structure: final_output = "" if len(self.personality.image_files)>0: - out, function_calls = self.generate_with_function_calls_and_images(context_details, self.personality.image_files, function_definitions, callback=callback) + out, function_calls, text_without_code = self.generate_with_function_calls_and_images(context_details, self.personality.image_files, function_definitions, callback=callback) else: - out, function_calls = self.generate_with_function_calls(context_details, function_definitions, callback=callback) + out, function_calls, text_without_code = self.generate_with_function_calls(context_details, function_definitions, callback=callback) nested_function_calls = 0 while len(function_calls)>0 and nested_function_calls0: - out, function_calls = self.generate_with_function_calls_and_images(context_details, self.personality.image_files, function_definitions, callback=callback) + out, function_calls, twc = self.generate_with_function_calls_and_images(context_details, self.personality.image_files, function_definitions, callback=callback) else: - out, function_calls = self.generate_with_function_calls(context_details, function_definitions, callback=callback) + out, function_calls, twc = self.generate_with_function_calls(context_details, function_definitions, callback=callback) final_output += "\n" + out + text_without_code += twc else: final_output = out return final_output