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" },