diff --git a/pyproject.toml b/pyproject.toml index 74548d646..65cbeabc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -125,6 +125,7 @@ dependencies = [ "twelvelabs>=0.4.7", "docling_core>=2.36.1", "filelock>=3.18.0", + "jigsawstack==0.2.7", ] [dependency-groups] diff --git a/src/backend/base/langflow/components/jigsawstack/__init__.py b/src/backend/base/langflow/components/jigsawstack/__init__.py new file mode 100644 index 000000000..c32d56ee7 --- /dev/null +++ b/src/backend/base/langflow/components/jigsawstack/__init__.py @@ -0,0 +1,23 @@ +from .ai_scrape import JigsawStackAIScraperComponent +from .ai_web_search import JigsawStackAIWebSearchComponent +from .file_read import JigsawStackFileReadComponent +from .file_upload import JigsawStackFileUploadComponent +from .image_generation import JigsawStackImageGenerationComponent +from .nsfw import JigsawStackNSFWComponent +from .object_detection import JigsawStackObjectDetectionComponent +from .sentiment import JigsawStackSentimentComponent +from .text_to_sql import JigsawStackTextToSQLComponent +from .vocr import JigsawStackVOCRComponent + +__all__ = [ + "JigsawStackAIScraperComponent", + "JigsawStackAIWebSearchComponent", + "JigsawStackFileReadComponent", + "JigsawStackFileUploadComponent", + "JigsawStackImageGenerationComponent", + "JigsawStackNSFWComponent", + "JigsawStackObjectDetectionComponent", + "JigsawStackSentimentComponent", + "JigsawStackTextToSQLComponent", + "JigsawStackVOCRComponent", +] diff --git a/src/backend/base/langflow/components/jigsawstack/ai_scrape.py b/src/backend/base/langflow/components/jigsawstack/ai_scrape.py new file mode 100644 index 000000000..eb535ba0f --- /dev/null +++ b/src/backend/base/langflow/components/jigsawstack/ai_scrape.py @@ -0,0 +1,126 @@ +from langflow.custom.custom_component.component import Component +from langflow.io import MessageTextInput, Output, SecretStrInput +from langflow.schema.data import Data + +MAX_ELEMENT_PROMPTS = 5 + + +class JigsawStackAIScraperComponent(Component): + display_name = "AI Scraper" + description = "Scrape any website instantly and get consistent structured data \ + in seconds without writing any css selector code" + documentation = "https://jigsawstack.com/docs/api-reference/ai/scrape" + icon = "JigsawStack" + name = "JigsawStackAIScraper" + + inputs = [ + SecretStrInput( + name="api_key", + display_name="JigsawStack API Key", + info="Your JigsawStack API key for authentication", + required=True, + ), + MessageTextInput( + name="url", + display_name="URL", + info="URL of the page to scrape. Either url or html is required, but not both.", + required=False, + tool_mode=True, + ), + MessageTextInput( + name="html", + display_name="HTML", + info="HTML content to scrape. Either url or html is required, but not both.", + required=False, + tool_mode=True, + ), + MessageTextInput( + name="element_prompts", + display_name="Element Prompts", + info="Items on the page to be scraped (maximum 5). E.g. 'Plan price', 'Plan title'", + required=True, + tool_mode=True, + ), + MessageTextInput( + name="root_element_selector", + display_name="Root Element Selector", + info="CSS selector to limit the scope of scraping to a specific element and its children", + required=False, + value="main", + ), + ] + + outputs = [ + Output(display_name="AI Scraper Results", name="scrape_results", method="scrape"), + ] + + def scrape(self) -> Data: + try: + from jigsawstack import JigsawStack, JigsawStackError + except ImportError as e: + jigsawstack_import_error = ( + "JigsawStack package not found. Please install it using: pip install jigsawstack>=0.2.7" + ) + raise ImportError(jigsawstack_import_error) from e + + try: + client = JigsawStack(api_key=self.api_key) + + # Build request object + scrape_params: dict = {} + if self.url: + scrape_params["url"] = self.url + if self.html: + scrape_params["html"] = self.html + + url_value = scrape_params.get("url", "") + html_value = scrape_params.get("html", "") + if (not url_value or not url_value.strip()) and (not html_value or not html_value.strip()): + url_or_html_error = "Either 'url' or 'html' must be provided for scraping" + raise ValueError(url_or_html_error) + + # Process element_prompts with proper type handling + element_prompts_list: list[str] = [] + if self.element_prompts: + element_prompts_value: str | list[str] = self.element_prompts + + if isinstance(element_prompts_value, str): + if "," not in element_prompts_value: + element_prompts_list = [element_prompts_value] + else: + element_prompts_list = element_prompts_value.split(",") + elif isinstance(element_prompts_value, list): + element_prompts_list = element_prompts_value + else: + # Fallback for other types + element_prompts_list = str(element_prompts_value).split(",") + + if len(element_prompts_list) > MAX_ELEMENT_PROMPTS: + max_elements_error = "Maximum of 5 element prompts allowed" + raise ValueError(max_elements_error) + if len(element_prompts_list) == 0: + invalid_elements_error = "Element prompts cannot be empty" + raise ValueError(invalid_elements_error) + + scrape_params["element_prompts"] = element_prompts_list + + if self.root_element_selector: + scrape_params["root_element_selector"] = self.root_element_selector + + # Call web scraping + response = client.web.ai_scrape(scrape_params) + + if not response.get("success", False): + fail_error = "JigsawStack API request failed." + raise ValueError(fail_error) + + result_data = response + + self.status = "AI scrape process is now complete." + + return Data(data=result_data) + + except JigsawStackError as e: + error_data = {"error": str(e), "success": False} + self.status = f"Error: {e!s}" + return Data(data=error_data) diff --git a/src/backend/base/langflow/components/jigsawstack/ai_web_search.py b/src/backend/base/langflow/components/jigsawstack/ai_web_search.py new file mode 100644 index 000000000..b41ddc49a --- /dev/null +++ b/src/backend/base/langflow/components/jigsawstack/ai_web_search.py @@ -0,0 +1,136 @@ +from langflow.custom.custom_component.component import Component +from langflow.io import BoolInput, DropdownInput, Output, QueryInput, SecretStrInput +from langflow.schema.data import Data +from langflow.schema.message import Message + + +class JigsawStackAIWebSearchComponent(Component): + display_name = "AI Web Search" + description = "Effortlessly search the Web and get access to high-quality results powered with AI." + documentation = "https://jigsawstack.com/docs/api-reference/web/ai-search" + icon = "JigsawStack" + name = "JigsawStackAISearch" + inputs = [ + SecretStrInput( + name="api_key", + display_name="JigsawStack API Key", + info="Your JigsawStack API key for authentication", + required=True, + ), + QueryInput( + name="query", + display_name="Query", + info="The search value. The maximum query character length is 400", + required=True, + tool_mode=True, + ), + BoolInput( + name="ai_overview", + display_name="AI Overview", + info="Include AI powered overview in the search results", + required=False, + value=True, + ), + DropdownInput( + name="safe_search", + display_name="Safe Search", + info="Enable safe search to filter out adult content", + required=False, + options=["moderate", "strict", "off"], + value="off", + ), + BoolInput( + name="spell_check", + display_name="Spell Check", + info="Spell check the search query", + required=False, + value=True, + ), + ] + + outputs = [ + Output(display_name="AI Search Results", name="search_results", method="search"), + Output(display_name="Content Text", name="content_text", method="get_content_text"), + ] + + def search(self) -> Data: + try: + from jigsawstack import JigsawStack, JigsawStackError + except ImportError as e: + jigsawstack_import_error = ( + "JigsawStack package not found. Please install it using: pip install jigsawstack>=0.2.7" + ) + raise ImportError(jigsawstack_import_error) from e + + try: + client = JigsawStack(api_key=self.api_key) + + # build request object + search_params = {} + if self.query: + search_params["query"] = self.query + if self.ai_overview is not None: + search_params["ai_overview"] = self.ai_overview + if self.safe_search: + search_params["safe_search"] = self.safe_search + if self.spell_check is not None: + search_params["spell_check"] = self.spell_check + + # Call web scraping + response = client.web.search(search_params) + + api_error_msg = "JigsawStack API returned unsuccessful response" + if not response.get("success", False): + raise ValueError(api_error_msg) + + # Create comprehensive data object + result_data = { + "query": self.query, + "ai_overview": response.get("ai_overview", ""), + "spell_fixed": response.get("spell_fixed", False), + "is_safe": response.get("is_safe", True), + "results": response.get("results", []), + "success": True, + } + + self.status = f"Search complete for: {response.get('query', '')}" + + return Data(data=result_data) + + except JigsawStackError as e: + error_data = {"error": str(e), "success": False} + self.status = f"Error: {e!s}" + return Data(data=error_data) + + def get_content_text(self) -> Message: + try: + from jigsawstack import JigsawStack, JigsawStackError + except ImportError: + return Message(text="Error: JigsawStack package not found.") + + try: + # Initialize JigsawStack client + client = JigsawStack(api_key=self.api_key) + search_params = {} + if self.query: + search_params["query"] = self.query + if self.ai_overview is not None: + search_params["ai_overview"] = self.ai_overview + if self.safe_search: + search_params["safe_search"] = self.safe_search + if self.spell_check is not None: + search_params["spell_check"] = self.spell_check + + # Call web scraping + response = client.web.search(search_params) + + request_failed_msg = "Request Failed" + if not response.get("success", False): + raise JigsawStackError(request_failed_msg) + + # Return the content as text + content = response.get("ai_overview", "") + return Message(text=content) + + except JigsawStackError as e: + return Message(text=f"Error while using AI Search: {e!s}") diff --git a/src/backend/base/langflow/components/jigsawstack/file_read.py b/src/backend/base/langflow/components/jigsawstack/file_read.py new file mode 100644 index 000000000..1bd41ba57 --- /dev/null +++ b/src/backend/base/langflow/components/jigsawstack/file_read.py @@ -0,0 +1,115 @@ +import tempfile + +from langflow.custom.custom_component.component import Component +from langflow.io import Output, SecretStrInput, StrInput +from langflow.schema.data import Data + + +class JigsawStackFileReadComponent(Component): + display_name = "File Read" + description = "Read any previously uploaded file seamlessly from \ + JigsawStack File Storage and use it in your AI applications." + documentation = "https://jigsawstack.com/docs/api-reference/store/file/get" + icon = "JigsawStack" + name = "JigsawStackFileRead" + + inputs = [ + SecretStrInput( + name="api_key", + display_name="JigsawStack API Key", + info="Your JigsawStack API key for authentication", + required=True, + ), + StrInput( + name="key", + display_name="Key", + info="The key used to retrieve the file from JigsawStack File Storage.", + required=True, + tool_mode=True, + ), + ] + + outputs = [ + Output(display_name="File Path", name="file_path", method="read_and_save_file"), + ] + + def read_and_save_file(self) -> Data: + """Read file from JigsawStack and save to temp file, return file path.""" + try: + from jigsawstack import JigsawStack, JigsawStackError + except ImportError as e: + jigsawstack_import_error = ( + "JigsawStack package not found. Please install it using: pip install jigsawstack>=0.2.7" + ) + raise ImportError(jigsawstack_import_error) from e + + try: + client = JigsawStack(api_key=self.api_key) + if not self.key or self.key.strip() == "": + invalid_key_error = "Key is required to read a file from JigsawStack File Storage." + raise ValueError(invalid_key_error) + + # Download file content + response = client.store.get(self.key) + + # Determine file extension + file_extension = self._detect_file_extension(response) + + # Create temporary file + with tempfile.NamedTemporaryFile( + delete=False, suffix=file_extension, prefix=f"jigsawstack_{self.key}_" + ) as temp_file: + if isinstance(response, bytes): + temp_file.write(response) + else: + # Handle string content + temp_file.write(response.encode("utf-8")) + + temp_path = temp_file.name + + return Data( + data={ + "file_path": temp_path, + "key": self.key, + "file_extension": file_extension, + "size": len(response) if isinstance(response, bytes) else len(str(response)), + "success": True, + } + ) + + except JigsawStackError as e: + error_data = {"error": str(e), "success": False} + self.status = f"Error: {e!s}" + return Data(data=error_data) + + def _detect_file_extension(self, content) -> str: + """Detect file extension based on content headers.""" + if isinstance(content, bytes): + # Check magic numbers for common file types + if content.startswith(b"\xff\xd8\xff"): + return ".jpg" + if content.startswith(b"\x89PNG\r\n\x1a\n"): + return ".png" + if content.startswith((b"GIF87a", b"GIF89a")): + return ".gif" + if content.startswith(b"%PDF"): + return ".pdf" + if content.startswith(b"PK\x03\x04"): # ZIP/DOCX/XLSX + return ".zip" + if content.startswith(b"\x00\x00\x01\x00"): # ICO + return ".ico" + if content.startswith(b"RIFF") and b"WEBP" in content[:12]: + return ".webp" + if content.startswith((b"\xff\xfb", b"\xff\xf3", b"\xff\xf2")): + return ".mp3" + if content.startswith((b"ftypmp4", b"\x00\x00\x00\x20ftypmp4")): + return ".mp4" + # Try to decode as text + try: + content.decode("utf-8") + return ".txt" # noqa: TRY300 + except UnicodeDecodeError: + return ".bin" # Binary file + else: + # String content + return ".txt" diff --git a/src/backend/base/langflow/components/jigsawstack/file_upload.py b/src/backend/base/langflow/components/jigsawstack/file_upload.py new file mode 100644 index 000000000..e5e2eb715 --- /dev/null +++ b/src/backend/base/langflow/components/jigsawstack/file_upload.py @@ -0,0 +1,94 @@ +from pathlib import Path + +from langflow.custom.custom_component.component import Component +from langflow.io import BoolInput, FileInput, Output, SecretStrInput, StrInput +from langflow.schema.data import Data + + +class JigsawStackFileUploadComponent(Component): + display_name = "File Upload" + description = "Store any file seamlessly on JigsawStack File Storage and use it in your AI applications. \ + Supports various file types including images, documents, and more." + documentation = "https://jigsawstack.com/docs/api-reference/store/file/add" + icon = "JigsawStack" + name = "JigsawStackFileUpload" + + inputs = [ + SecretStrInput( + name="api_key", + display_name="JigsawStack API Key", + info="Your JigsawStack API key for authentication", + required=True, + ), + FileInput( + name="file", + display_name="File", + info="Upload file to be stored on JigsawStack File Storage.", + required=True, + file_types=["pdf", "png", "jpg", "jpeg", "mp4", "mp3", "txt", "docx", "xlsx"], + ), + StrInput( + name="key", + display_name="Key", + info="The key used to store the file on JigsawStack File Storage. \ + If not provided, a unique key will be generated.", + required=False, + tool_mode=True, + ), + BoolInput( + name="overwrite", + display_name="Overwrite Existing File", + info="If true, will overwrite the existing file with the same key. \ + If false, will return an error if the file already exists.", + required=False, + value=True, + ), + BoolInput( + name="temp_public_url", + display_name="Return Temporary Public URL", + info="If true, will return a temporary public URL which lasts for a limited time. \ + If false, will return the file store key which can only be accessed by the owner.", + required=False, + value=False, + tool_mode=True, + ), + ] + + outputs = [ + Output(display_name="File Store Result", name="file_upload_result", method="upload_file"), + ] + + def upload_file(self) -> Data: + try: + from jigsawstack import JigsawStack, JigsawStackError + except ImportError as e: + jigsawstack_import_error = ( + "JigsawStack package not found. Please install it using: pip install jigsawstack>=0.2.7" + ) + raise ImportError(jigsawstack_import_error) from e + + try: + client = JigsawStack(api_key=self.api_key) + + file_path = Path(self.file) + with Path.open(file_path, "rb") as f: + file_content = f.read() + params = {} + + if self.key: + # if key is provided, use it as the file store key + params["key"] = self.key + if self.overwrite is not None: + # if overwrite is provided, use it to determine if the file should be overwritten + params["overwrite"] = self.overwrite + if self.temp_public_url is not None: + # if temp_public_url is provided, use it to determine if a temporary public URL should + params["temp_public_url"] = self.temp_public_url + + response = client.store.upload(file_content, params) + return Data(data=response) + + except JigsawStackError as e: + error_data = {"error": str(e), "success": False} + self.status = f"Error: {e!s}" + return Data(data=error_data) diff --git a/src/backend/base/langflow/components/jigsawstack/image_generation.py b/src/backend/base/langflow/components/jigsawstack/image_generation.py new file mode 100644 index 000000000..cb56809d3 --- /dev/null +++ b/src/backend/base/langflow/components/jigsawstack/image_generation.py @@ -0,0 +1,205 @@ +from langflow.custom.custom_component.component import Component +from langflow.io import DropdownInput, IntInput, MessageTextInput, Output, SecretStrInput +from langflow.schema.data import Data + + +class JigsawStackImageGenerationComponent(Component): + display_name = "Image Generation" + description = "Generate an image based on the given text by employing AI models like Flux, \ + Stable Diffusion, and other top models." + documentation = "https://jigsawstack.com/docs/api-reference/ai/image-generation" + icon = "JigsawStack" + name = "JigsawStackImageGeneration" + + inputs = [ + SecretStrInput( + name="api_key", + display_name="JigsawStack API Key", + info="Your JigsawStack API key for authentication", + required=True, + ), + MessageTextInput( + name="prompt", + display_name="Prompt", + info="The text prompt to generate the image from. Must be between 1-5000 characters.", + required=True, + tool_mode=True, + ), + MessageTextInput( + name="aspect_ratio", + display_name="Aspect Ratio", + info="The aspect ratio of the generated image. Must be one of the following:\ + '1:1', '16:9', '21:9', '3:2', '2:3', '4:5', '5:4', '3:4', '4:3', '9:16', '9:21' \ + Default is 1:1.", + required=False, + tool_mode=True, + ), + MessageTextInput( + name="url", + display_name="URL", + info="A valid URL where the generated image will be sent.", + required=False, + ), + MessageTextInput( + name="file_store_key", + display_name="File Store Key", + info="The key used to store the image on Jigsawstack File Storage. Not required if url is specified.", + required=False, + tool_mode=True, + ), + IntInput( + name="width", + display_name="Width", + info="The width of the image. Must be between 256-1920 pixels.", + required=False, + ), + IntInput( + name="height", + display_name="Height", + info="The height of the image. Must be between 256-1920 pixels.", + required=False, + ), + IntInput( + name="steps", + display_name="Steps", + info="The number of denoising steps. Must be between 1-90. \ + Higher values produce better quality images but take more time to generate.", + required=False, + ), + DropdownInput( + name="output_format", + display_name="Output Format", + info="The output format of the generated image. Must be one of the following values:\ + png or svg", + required=False, + options=["png", "svg"], + value="png", + ), + MessageTextInput( + name="negative_prompt", + display_name="Negative Prompt", + info="The text prompt to avoid in the generated image. \ + Must be between 1-5000 characters.", + required=False, + tool_mode=True, + advanced=True, + ), + IntInput( + name="seed", + display_name="Seed", + info="Makes generation deterministic.\ + Using the same seed and set of parameters will produce identical image each time.", + required=False, + tool_mode=True, + advanced=True, + ), + IntInput( + name="guidance", + display_name="Guidance Scale", + info="Higher guidance forces the model to better follow the prompt, \ + but may result in lower quality output. Must be between 1-28.", + required=False, + tool_mode=True, + advanced=True, + ), + ] + + outputs = [ + Output(display_name="Image Generation Results", name="image_generation_results", method="generate_image"), + ] + + def generate_image(self) -> Data: + try: + from jigsawstack import JigsawStack, JigsawStackError + except ImportError as e: + jigsawstack_import_error = ( + "JigsawStack package not found. Please install it using: pip install jigsawstack>=0.2.7" + ) + raise ImportError(jigsawstack_import_error) from e + + try: + min_character_length = 1 + max_character_length = 5000 + min_width = 256 + max_width = 1920 + min_height = 256 + max_height = 1920 + min_steps = 1 + max_steps = 90 + client = JigsawStack(api_key=self.api_key) + + if not self.prompt or len(self.prompt) < min_character_length or len(self.prompt) > max_character_length: + invalid_prompt_error = f"Prompts must be between \ + {min_character_length}-{max_character_length} characters." + raise ValueError(invalid_prompt_error) + + if self.aspect_ratio and self.aspect_ratio not in [ + "1:1", + "16:9", + "21:9", + "3:2", + "2:3", + "4:5", + "5:4", + "3:4", + "4:3", + "9:16", + "9:21", + ]: + invalid_aspect_ratio_error = ( + "Aspect ratio must be one of the following: '1:1', '16:9', '21:9', '3:2', '2:3', " + "'4:5', '5:4', '3:4', '4:3', '9:16', '9:21'." + ) + raise ValueError(invalid_aspect_ratio_error) + if self.width and (self.width < min_width or self.width > max_width): + invalid_width_error = f"Width must be between {min_width}-{max_width} pixels." + raise ValueError(invalid_width_error) + if self.height and (self.height < min_height or self.height > max_height): + invalid_height_error = f"Height must be between {min_height}-{max_height} pixels." + raise ValueError(invalid_height_error) + if self.steps and (self.steps < min_steps or self.steps > max_steps): + invalid_steps_error = f"Steps must be between {min_steps}-{max_steps}." + raise ValueError(invalid_steps_error) + + params = {} + if self.prompt: + params["prompt"] = self.prompt.strip() + if self.aspect_ratio: + params["aspect_ratio"] = self.aspect_ratio.strip() + if self.url: + params["url"] = self.url.strip() + if self.file_store_key: + params["file_store_key"] = self.file_store_key.strip() + if self.width: + params["width"] = self.width + if self.height: + params["height"] = self.height + params["return_type"] = "url" + if self.output_format: + params["output_format"] = self.output_format.strip() + if self.steps: + params["steps"] = self.steps + + # Initialize advance_config if any advanced parameters are provided + if self.negative_prompt or self.seed or self.guidance: + params["advance_config"] = {} + if self.negative_prompt: + params["advance_config"]["negative_prompt"] = self.negative_prompt + if self.seed: + params["advance_config"]["seed"] = self.seed + if self.guidance: + params["advance_config"]["guidance"] = self.guidance + + # Call image generation + response = client.image_generation(params) + + if response.get("url", None) is None or response.get("url", None).strip() == "": + failed_response_error = "JigsawStack API returned unsuccessful response" + raise ValueError(failed_response_error) + + return Data(data=response) + + except JigsawStackError as e: + error_data = {"error": str(e), "success": False} + self.status = f"Error: {e!s}" + return Data(data=error_data) diff --git a/src/backend/base/langflow/components/jigsawstack/nsfw.py b/src/backend/base/langflow/components/jigsawstack/nsfw.py new file mode 100644 index 000000000..2f9c60ee6 --- /dev/null +++ b/src/backend/base/langflow/components/jigsawstack/nsfw.py @@ -0,0 +1,60 @@ +from langflow.custom.custom_component.component import Component +from langflow.io import Output, SecretStrInput, StrInput +from langflow.schema.data import Data + + +class JigsawStackNSFWComponent(Component): + display_name = "NSFW Detection" + description = "Detect if image/video contains NSFW content" + documentation = "https://jigsawstack.com/docs/api-reference/ai/nsfw" + icon = "JigsawStack" + name = "JigsawStackNSFW" + + inputs = [ + SecretStrInput( + name="api_key", + display_name="JigsawStack API Key", + info="Your JigsawStack API key for authentication", + required=True, + ), + StrInput( + name="url", + display_name="URL", + info="URL of the image or video to analyze", + required=True, + ), + ] + + outputs = [ + Output(display_name="NSFW Analysis", name="nsfw_result", method="detect_nsfw"), + ] + + def detect_nsfw(self) -> Data: + try: + from jigsawstack import JigsawStack, JigsawStackError + except ImportError as e: + jigsawstack_import_error = ( + "JigsawStack package not found. Please install it using: pip install jigsawstack>=0.2.7" + ) + raise ImportError(jigsawstack_import_error) from e + + try: + client = JigsawStack(api_key=self.api_key) + + # Build request parameters + params = {"url": self.url} + + response = client.validate.nsfw(params) + + api_error_msg = "JigsawStack API returned unsuccessful response" + if not response.get("success", False): + raise ValueError(api_error_msg) + + return Data(data=response) + + except ValueError: + raise + except JigsawStackError as e: + error_data = {"error": str(e), "success": False} + self.status = f"Error: {e!s}" + return Data(data=error_data) diff --git a/src/backend/base/langflow/components/jigsawstack/object_detection.py b/src/backend/base/langflow/components/jigsawstack/object_detection.py new file mode 100644 index 000000000..ff9918194 --- /dev/null +++ b/src/backend/base/langflow/components/jigsawstack/object_detection.py @@ -0,0 +1,124 @@ +from langflow.custom.custom_component.component import Component +from langflow.io import BoolInput, DropdownInput, MessageTextInput, Output, SecretStrInput +from langflow.schema.data import Data + + +class JigsawStackObjectDetectionComponent(Component): + display_name = "Object Detection" + description = "Perform object detection on images using JigsawStack's Object Detection Model, \ + capable of image grounding, segmentation and computer use." + documentation = "https://jigsawstack.com/docs/api-reference/ai/object-detection" + icon = "JigsawStack" + name = "JigsawStackObjectDetection" + + inputs = [ + SecretStrInput( + name="api_key", + display_name="JigsawStack API Key", + info="Your JigsawStack API key for authentication", + required=True, + ), + MessageTextInput( + name="prompts", + display_name="Prompts", + info="The prompts to ground the object detection model. \ + You can pass a list of comma-separated prompts to extract different information from the image.", + required=False, + tool_mode=True, + ), + MessageTextInput( + name="url", + display_name="URL", + info="The image URL. Not required if file_store_key is specified.", + required=False, + tool_mode=True, + ), + MessageTextInput( + name="file_store_key", + display_name="File Store Key", + info="The key used to store the image on Jigsawstack File Storage. Not required if url is specified.", + required=False, + tool_mode=True, + ), + BoolInput( + name="annotated_image", + display_name="Return Annotated Image", + info="If true, will return an url for annotated image with detected objects.", + required=False, + value=True, + ), + DropdownInput( + name="features", + display_name="Features", + info="Select the features to enable for object detection", + required=False, + options=["object_detection", "gui"], + value=["object_detection", "gui"], + ), + DropdownInput( + name="return_type", + display_name="Return Type", + info="Select the return type for the object detection results such as masks or annotations.", + required=False, + options=["url", "base64"], + value="url", + ), + ] + + outputs = [ + Output(display_name="Object Detection results", name="object_detection_results", method="detect_objects"), + ] + + def detect_objects(self) -> Data: + try: + from jigsawstack import JigsawStack, JigsawStackError + except ImportError as e: + jigsawstack_import_error = ( + "JigsawStack package not found. Please install it using: pip install jigsawstack>=0.2.7" + ) + raise ImportError(jigsawstack_import_error) from e + + try: + client = JigsawStack(api_key=self.api_key) + + # build request object + params = {} + if self.prompts: + if isinstance(self.prompts, list): + params["prompt"] = self.prompts + elif isinstance(self.prompts, str): + if "," in self.prompts: + # Split by comma and strip whitespace + params["prompt"] = [p.strip() for p in self.prompts.split(",")] + else: + params["prompt"] = [self.prompts.strip()] + else: + invalid_prompt_error = "Prompt must be a list of strings or a single string" + raise ValueError(invalid_prompt_error) + if self.url: + params["url"] = self.url + if self.file_store_key: + params["file_store_key"] = self.file_store_key + + # if both url and file_store_key are not provided, raise an error + if not self.url and not self.file_store_key: + missing_url_error = "Either URL or File Store Key must be provided to perform object detection" + raise ValueError(missing_url_error) + + params["annotated_image"] = self.annotated_image + if self.features: + params["features"] = self.features + + # Call web scraping + response = client.vision.object_detection(params) + + if not response.get("success", False): + failed_response_error = "JigsawStack API returned unsuccessful response" + raise ValueError(failed_response_error) + + return Data(data=response) + + except JigsawStackError as e: + error_data = {"error": str(e), "success": False} + self.status = f"Error: {e!s}" + return Data(data=error_data) diff --git a/src/backend/base/langflow/components/jigsawstack/sentiment.py b/src/backend/base/langflow/components/jigsawstack/sentiment.py new file mode 100644 index 000000000..3ea91258b --- /dev/null +++ b/src/backend/base/langflow/components/jigsawstack/sentiment.py @@ -0,0 +1,112 @@ +from langflow.custom.custom_component.component import Component +from langflow.io import MessageTextInput, Output, SecretStrInput +from langflow.schema.data import Data +from langflow.schema.message import Message + + +class JigsawStackSentimentComponent(Component): + display_name = "Sentiment Analysis" + description = "Analyze sentiment of text using JigsawStack AI" + documentation = "https://jigsawstack.com/docs/api-reference/ai/sentiment" + icon = "JigsawStack" + name = "JigsawStackSentiment" + + inputs = [ + SecretStrInput( + name="api_key", + display_name="JigsawStack API Key", + info="Your JigsawStack API key for authentication", + required=True, + ), + MessageTextInput( + name="text", + display_name="Text", + info="Text to analyze for sentiment", + required=True, + tool_mode=True, + ), + ] + + outputs = [ + Output(display_name="Sentiment Data", name="sentiment_data", method="analyze_sentiment"), + Output(display_name="Sentiment Text", name="sentiment_text", method="get_sentiment_text"), + ] + + def analyze_sentiment(self) -> Data: + try: + from jigsawstack import JigsawStack, JigsawStackError + except ImportError as e: + jigsawstack_import_error = ( + "JigsawStack package not found. Please install it using: pip install jigsawstack>=0.2.7" + ) + raise ImportError(jigsawstack_import_error) from e + + try: + client = JigsawStack(api_key=self.api_key) + response = client.sentiment({"text": self.text}) + + api_error_msg = "JigsawStack API returned unsuccessful response" + if not response.get("success", False): + raise ValueError(api_error_msg) + + sentiment_data = response.get("sentiment", {}) + + result_data = { + "text_analyzed": self.text, + "sentiment": sentiment_data.get("sentiment", "Unknown"), + "emotion": sentiment_data.get("emotion", "Unknown"), + "score": sentiment_data.get("score", 0.0), + "sentences": response.get("sentences", []), + "success": True, + } + + self.status = ( + f"Sentiment: {sentiment_data.get('sentiment', 'Unknown')} | " + f"Emotion: {sentiment_data.get('emotion', 'Unknown')} | " + f"Score: {sentiment_data.get('score', 0.0):.3f}" + ) + + return Data(data=result_data) + + except JigsawStackError as e: + error_data = {"error": str(e), "text_analyzed": self.text, "success": False} + self.status = f"Error: {e!s}" + return Data(data=error_data) + + def get_sentiment_text(self) -> Message: + try: + from jigsawstack import JigsawStack, JigsawStackError + except ImportError: + return Message(text="Error: JigsawStack package not found. Please install it with: pip install jigsawstack") + + try: + client = JigsawStack(api_key=self.api_key) + response = client.sentiment({"text": self.text}) + + sentiment_data = response.get("sentiment", {}) + sentences = response.get("sentences", []) + + # Format the output + formatted_output = f"""Sentiment Analysis Results: + +Text: {self.text} + +Overall Sentiment: {sentiment_data.get("sentiment", "Unknown")} +Emotion: {sentiment_data.get("emotion", "Unknown")} +Score: {sentiment_data.get("score", 0.0):.3f} + +Sentence-by-sentence Analysis: +""" + + for i, sentence in enumerate(sentences, 1): + formatted_output += ( + f"{i}. {sentence.get('text', '')}\n" + f" Sentiment: {sentence.get('sentiment', 'Unknown')} | " + f"Emotion: {sentence.get('emotion', 'Unknown')} | " + f"Score: {sentence.get('score', 0.0):.3f}\n" + ) + + return Message(text=formatted_output) + + except JigsawStackError as e: + return Message(text=f"Error analyzing sentiment: {e!s}") diff --git a/src/backend/base/langflow/components/jigsawstack/text_to_sql.py b/src/backend/base/langflow/components/jigsawstack/text_to_sql.py new file mode 100644 index 000000000..eefd15a5a --- /dev/null +++ b/src/backend/base/langflow/components/jigsawstack/text_to_sql.py @@ -0,0 +1,90 @@ +from langflow.custom.custom_component.component import Component +from langflow.io import MessageTextInput, Output, QueryInput, SecretStrInput, StrInput +from langflow.schema.data import Data + + +class JigsawStackTextToSQLComponent(Component): + display_name = "Text to SQL" + description = "Convert natural language to SQL queries using JigsawStack AI" + documentation = "https://jigsawstack.com/docs/api-reference/ai/text-to-sql" + icon = "JigsawStack" + name = "JigsawStackTextToSQL" + + inputs = [ + SecretStrInput( + name="api_key", + display_name="JigsawStack API Key", + info="Your JigsawStack API key for authentication", + required=True, + ), + QueryInput( + name="prompt", + display_name="Prompt", + info="Natural language description of the SQL query you want to generate", + required=True, + tool_mode=True, + ), + MessageTextInput( + name="sql_schema", + display_name="SQL Schema", + info=( + "The database schema information. Can be a CREATE TABLE statement or schema description. " + "Specifying this parameter improves SQL generation accuracy by applying " + "database-specific syntax and optimizations." + ), + required=False, + tool_mode=True, + ), + StrInput( + name="file_store_key", + display_name="File Store Key", + info=( + "The key used to store the database schema on Jigsawstack file Storage. " + "Not required if sql_schema is specified." + ), + required=False, + tool_mode=True, + ), + ] + + outputs = [ + Output(display_name="SQL Query", name="sql_query", method="generate_sql"), + ] + + def generate_sql(self) -> Data: + try: + from jigsawstack import JigsawStack, JigsawStackError + except ImportError as e: + jigsawstack_import_error = ( + "JigsawStack package not found. Please install it using: pip install jigsawstack>=0.2.7" + ) + raise ImportError(jigsawstack_import_error) from e + + try: + schema_error = "Either 'sql_schema' or 'file_store_key' must be provided" + if not self.sql_schema and not self.file_store_key: + raise ValueError(schema_error) + + # build request object + params = {"prompt": self.prompt} + + if self.sql_schema: + params["sql_schema"] = self.sql_schema + if self.file_store_key: + params["file_store_key"] = self.file_store_key + + client = JigsawStack(api_key=self.api_key) + response = client.text_to_sql(params) + + api_error_msg = "JigsawStack API returned unsuccessful response" + if not response.get("success", False): + raise ValueError(api_error_msg) + + return Data(data=response) + + except ValueError: + raise + except JigsawStackError as e: + error_data = {"error": str(e), "success": False} + self.status = f"Error: {e!s}" + return Data(data=error_data) diff --git a/src/backend/base/langflow/components/jigsawstack/text_translate.py b/src/backend/base/langflow/components/jigsawstack/text_translate.py new file mode 100644 index 000000000..cbeff3b6d --- /dev/null +++ b/src/backend/base/langflow/components/jigsawstack/text_translate.py @@ -0,0 +1,77 @@ +from langflow.custom.custom_component.component import Component +from langflow.io import MessageTextInput, Output, SecretStrInput, StrInput +from langflow.schema.data import Data + + +class JigsawStackTextTranslateComponent(Component): + display_name = "Text Translate" + description = "Translate text from one language to another with support for multiple text formats." + documentation = "https://jigsawstack.com/docs/api-reference/ai/translate" + icon = "JigsawStack" + name = "JigsawStackTextTranslate" + inputs = [ + SecretStrInput( + name="api_key", + display_name="JigsawStack API Key", + info="Your JigsawStack API key for authentication", + required=True, + ), + StrInput( + name="target_language", + display_name="Target Language", + info="The language code of the target language to translate to. \ + Language code is identified by a unique ISO 639-1 two-letter code", + required=True, + tool_mode=True, + ), + MessageTextInput( + name="text", + display_name="Text", + info="The text to translate. This can be a single string or a list of strings. \ + If a list is provided, each string will be translated separately.", + required=True, + is_list=True, + tool_mode=True, + ), + ] + + outputs = [ + Output(display_name="Translation Results", name="translation_results", method="translation"), + ] + + def translation(self) -> Data: + try: + from jigsawstack import JigsawStack, JigsawStackError + except ImportError as e: + jigsawstack_import_error = ( + "JigsawStack package not found. Please install it using: pip install jigsawstack>=0.2.7" + ) + raise ImportError(jigsawstack_import_error) from e + + try: + client = JigsawStack(api_key=self.api_key) + + # build request object + params = {} + if self.target_language: + params["target_language"] = self.target_language + + if self.text: + if isinstance(self.text, list): + params["text"] = self.text + else: + params["text"] = [self.text] + + # Call web scraping + response = client.translate.text(params) + + if not response.get("success", False): + failed_response_error = "JigsawStack API returned unsuccessful response" + raise ValueError(failed_response_error) + + return Data(data=response) + + except JigsawStackError as e: + error_data = {"error": str(e), "success": False} + self.status = f"Error: {e!s}" + return Data(data=error_data) diff --git a/src/backend/base/langflow/components/jigsawstack/vocr.py b/src/backend/base/langflow/components/jigsawstack/vocr.py new file mode 100644 index 000000000..cc5a595ef --- /dev/null +++ b/src/backend/base/langflow/components/jigsawstack/vocr.py @@ -0,0 +1,107 @@ +from langflow.custom.custom_component.component import Component +from langflow.io import IntInput, MessageTextInput, Output, SecretStrInput, StrInput +from langflow.schema.data import Data + + +class JigsawStackVOCRComponent(Component): + display_name = "VOCR" + description = "Extract data from any document type in a consistent structure with fine-tuned \ + vLLMs for the highest accuracy" + documentation = "https://jigsawstack.com/docs/api-reference/ai/vocr" + icon = "JigsawStack" + name = "JigsawStackVOCR" + + inputs = [ + SecretStrInput( + name="api_key", + display_name="JigsawStack API Key", + info="Your JigsawStack API key for authentication", + required=True, + ), + MessageTextInput( + name="prompts", + display_name="Prompts", + info="The prompts used to describe the image. Default prompt is Describe the image in detail. \ + You can pass a list of comma-separated prompts to extract different information from the image.", + required=False, + tool_mode=True, + ), + StrInput( + name="url", + display_name="URL", + info="The image or document url. Not required if file_store_key is specified.", + required=False, + tool_mode=True, + ), + StrInput( + name="file_store_key", + display_name="File Store Key", + info="The key used to store the image on Jigsawstack File Storage. Not required if url is specified.", + required=False, + tool_mode=True, + ), + IntInput( + name="page_range_start", + display_name="Page Range", + info="Page range start limit for the document. If not specified, all pages will be processed.", + required=False, + ), + IntInput( + name="page_range_end", + display_name="Page Range End", + info="Page range end limit for the document. If not specified, all pages will be processed.", + required=False, + ), + ] + + outputs = [ + Output(display_name="VOCR results", name="vocr_results", method="vocr"), + ] + + def vocr(self) -> Data: + try: + from jigsawstack import JigsawStack, JigsawStackError + except ImportError as e: + jigsawstack_import_error = ( + "JigsawStack package not found. Please install it using: pip install jigsawstack>=0.2.7" + ) + raise ImportError(jigsawstack_import_error) from e + + try: + client = JigsawStack(api_key=self.api_key) + + # build request object + params = {} + if self.prompts: + if isinstance(self.prompts, list): + params["prompt"] = self.prompts + elif isinstance(self.prompts, str): + if "," in self.prompts: + # Split by comma and strip whitespace + params["prompt"] = [p.strip() for p in self.prompts.split(",")] + else: + params["prompt"] = [self.prompts.strip()] + else: + invalid_prompt_error = "Prompt must be a list of strings or a single string" + raise ValueError(invalid_prompt_error) + if self.url: + params["url"] = self.url + if self.file_store_key: + params["file_store_key"] = self.file_store_key + + if self.page_range_start and self.page_range_end: + params["page_range"] = [self.page_range_start, self.page_range_end] + + # Call VOCR + response = client.vision.vocr(params) + + if not response.get("success", False): + failed_response_error = "JigsawStack API returned unsuccessful response" + raise ValueError(failed_response_error) + + return Data(data=response) + + except JigsawStackError as e: + error_data = {"error": str(e), "success": False} + self.status = f"Error: {e!s}" + return Data(data=error_data) diff --git a/src/frontend/src/icons/JigsawStack/JigsawStackIcon.jsx b/src/frontend/src/icons/JigsawStack/JigsawStackIcon.jsx new file mode 100644 index 000000000..26a404fc7 --- /dev/null +++ b/src/frontend/src/icons/JigsawStack/JigsawStackIcon.jsx @@ -0,0 +1,45 @@ +const JigsawStackIconSVG = ({ isdark, ...props }) => ( + + + + + + + + + + + + + +); + +export default JigsawStackIconSVG; diff --git a/src/frontend/src/icons/JigsawStack/index.tsx b/src/frontend/src/icons/JigsawStack/index.tsx new file mode 100644 index 000000000..1da99f0d1 --- /dev/null +++ b/src/frontend/src/icons/JigsawStack/index.tsx @@ -0,0 +1,9 @@ +import React, { forwardRef } from "react"; +import JigsawStackIconSVG from "./JigsawStackIcon"; + +export const JigsawStackIcon = forwardRef< + SVGSVGElement, + React.PropsWithChildren<{}> +>((props, ref) => { + return ; +}); diff --git a/src/frontend/src/icons/JigsawStack/jigsawstack-icon.svg b/src/frontend/src/icons/JigsawStack/jigsawstack-icon.svg new file mode 100644 index 000000000..d9db706de --- /dev/null +++ b/src/frontend/src/icons/JigsawStack/jigsawstack-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/frontend/src/icons/eagerIconImports.ts b/src/frontend/src/icons/eagerIconImports.ts index 3b8e61758..9160c9876 100644 --- a/src/frontend/src/icons/eagerIconImports.ts +++ b/src/frontend/src/icons/eagerIconImports.ts @@ -112,6 +112,7 @@ import { WolframIcon } from "@/icons/Wolfram"; import { XAIIcon } from "@/icons/xAI"; import { YouTubeSvgIcon as YouTubeIcon } from "@/icons/Youtube"; import { ZepMemoryIcon } from "@/icons/ZepMemory"; +import { JigsawStackIcon } from "./JigsawStack"; import { WindsurfIcon } from "./Windsurf"; // Export the eagerly loaded icons map @@ -172,6 +173,7 @@ export const eagerIconsMapping = { Icosa: IcosaIcon, IFixIt: IFixIcon, javascript: JSIcon, + JigsawStack: JigsawStackIcon, LangChain: LangChainIcon, Langwatch: LangwatchIcon, LMStudio: LMStudioIcon, diff --git a/src/frontend/src/icons/lazyIconImports.ts b/src/frontend/src/icons/lazyIconImports.ts index e096079f9..2efd43c44 100644 --- a/src/frontend/src/icons/lazyIconImports.ts +++ b/src/frontend/src/icons/lazyIconImports.ts @@ -155,6 +155,10 @@ export const lazyIconsMapping = { import("@/icons/IFixIt").then((mod) => ({ default: mod.IFixIcon })), javascript: () => import("@/icons/JSicon").then((mod) => ({ default: mod.JSIcon })), + JigsawStack: () => + import("@/icons/JigsawStack").then((mod) => ({ + default: mod.JigsawStackIcon, + })), LangChain: () => import("@/icons/LangChain").then((mod) => ({ default: mod.LangChainIcon })), Langwatch: () => diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 0af59f141..52ed7fa62 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -271,6 +271,7 @@ export const SIDEBAR_BUNDLES = [ { display_name: "HuggingFace", name: "huggingface", icon: "HuggingFace" }, { display_name: "IBM", name: "ibm", icon: "WatsonxAI" }, { display_name: "Icosa Computing", name: "icosacomputing", icon: "Icosa" }, + { display_name: "JigsawStack", name: "jigsawstack", icon: "JigsawStack" }, { display_name: "LangChain", name: "langchain_utilities", icon: "LangChain" }, { display_name: "LangWatch", name: "langwatch", icon: "Langwatch" }, { display_name: "LMStudio", name: "lmstudio", icon: "LMStudio" }, diff --git a/uv.lock b/uv.lock index 65ce70cd7..8fa0840aa 100644 --- a/uv.lock +++ b/uv.lock @@ -3938,6 +3938,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, ] +[[package]] +name = "jigsawstack" +version = "0.2.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/c6/94806e49af97a0a0313ea3931b0d3a58dd59d1d4afa194c91d9df1c480ff/jigsawstack-0.2.7.tar.gz", hash = "sha256:e4fcafe388c36c5b6785c879e934562df7fe1202bda9ae5c0e729b99e40ad3b4", size = 25410, upload-time = "2025-06-24T17:49:46.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/fb/0b9728a6caeb737b2801895b5af6c05a65daaf9d02404e4196fdd8118fda/jigsawstack-0.2.7-py3-none-any.whl", hash = "sha256:295bb5d10e6d686094a594864fd31c95f1a4f50d80e710f2959fc7fef5771f50", size = 30828, upload-time = "2025-06-24T17:49:43.807Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -4709,6 +4723,7 @@ dependencies = [ { name = "graph-retriever" }, { name = "huggingface-hub", extra = ["inference"] }, { name = "ibm-watsonx-ai" }, + { name = "jigsawstack" }, { name = "jq" }, { name = "json-repair" }, { name = "kubernetes" }, @@ -4905,6 +4920,7 @@ requires-dist = [ { name = "graph-retriever", specifier = "==0.6.1" }, { name = "huggingface-hub", extras = ["inference"], specifier = ">=0.23.2,<1.0.0" }, { name = "ibm-watsonx-ai", specifier = ">=1.3.1" }, + { name = "jigsawstack", specifier = "==0.2.7" }, { name = "jq", specifier = "==1.8.0" }, { name = "json-repair", specifier = "==0.30.3" }, { name = "kubernetes", specifier = "==31.0.0" },