Explorar o código

feat(tool): getimg.ai integration (#6260)

Matri hai 9 meses
pai
achega
49ef9ef225

+ 1 - 0
api/core/tools/provider/builtin/getimgai/_assets/icon.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><rect x="32" y="48" width="192" height="160" rx="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><circle cx="156" cy="100" r="12"/><path d="M147.31,164,173,138.34a8,8,0,0,1,11.31,0L224,178.06" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M32,168.69l54.34-54.35a8,8,0,0,1,11.32,0L191.31,208" fill="none" stroke="#1553ed" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>

+ 22 - 0
api/core/tools/provider/builtin/getimgai/getimgai.py

@@ -0,0 +1,22 @@
+from core.tools.errors import ToolProviderCredentialValidationError
+from core.tools.provider.builtin.getimgai.tools.text2image import Text2ImageTool
+from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
+
+
+class GetImgAIProvider(BuiltinToolProviderController):
+    def _validate_credentials(self, credentials: dict) -> None:
+        try:
+            # Example validation using the text2image tool
+            Text2ImageTool().fork_tool_runtime(
+                runtime={"credentials": credentials}
+            ).invoke(
+                user_id='',
+                tool_parameters={
+                    "prompt": "A fire egg",
+                    "response_format": "url",
+                    "style": "photorealism",
+                }
+            )
+        except Exception as e:
+            raise ToolProviderCredentialValidationError(str(e))
+        

+ 29 - 0
api/core/tools/provider/builtin/getimgai/getimgai.yaml

@@ -0,0 +1,29 @@
+identity:
+  author: Matri Qi
+  name: getimgai
+  label:
+    en_US: getimg.ai
+    zh_CN: getimg.ai
+  description:
+    en_US: GetImg API integration for image generation and scraping.
+  icon: icon.svg
+  tags:
+    - image
+credentials_for_provider:
+  getimg_api_key:
+    type: secret-input
+    required: true
+    label:
+      en_US: getimg.ai API Key
+    placeholder:
+      en_US: Please input your getimg.ai API key
+    help:
+      en_US: Get your getimg.ai API key from your getimg.ai account settings. If you are using a self-hosted version, you may enter any key at your convenience.
+    url: https://dashboard.getimg.ai/api-keys
+  base_url:
+    type: text-input
+    required: false
+    label:
+      en_US: getimg.ai server's Base URL
+    placeholder:
+      en_US: https://api.getimg.ai/v1

+ 59 - 0
api/core/tools/provider/builtin/getimgai/getimgai_appx.py

@@ -0,0 +1,59 @@
+import logging
+import time
+from collections.abc import Mapping
+from typing import Any
+
+import requests
+from requests.exceptions import HTTPError
+
+logger = logging.getLogger(__name__)
+
+class GetImgAIApp:
+    def __init__(self, api_key: str | None = None, base_url: str | None = None):
+        self.api_key = api_key
+        self.base_url = base_url or 'https://api.getimg.ai/v1'
+        if not self.api_key:
+            raise ValueError("API key is required")
+
+    def _prepare_headers(self):
+        headers = {
+            'Content-Type': 'application/json',
+            'Authorization': f'Bearer {self.api_key}'
+        }
+        return headers
+
+    def _request(
+        self,
+        method: str,
+        url: str,
+        data: Mapping[str, Any] | None = None,
+        headers: Mapping[str, str] | None = None,
+        retries: int = 3,
+        backoff_factor: float = 0.3,
+    ) -> Mapping[str, Any] | None:
+        for i in range(retries):
+            try:
+                response = requests.request(method, url, json=data, headers=headers)
+                response.raise_for_status()
+                return response.json()
+            except requests.exceptions.RequestException as e:
+                if i < retries - 1 and isinstance(e, HTTPError) and e.response.status_code >= 500:
+                    time.sleep(backoff_factor * (2 ** i))
+                else:
+                    raise
+        return None
+
+    def text2image(
+        self, mode: str, **kwargs
+    ):
+        data = kwargs['params']
+        if not data.get('prompt'):
+            raise ValueError("Prompt is required")
+
+        endpoint = f'{self.base_url}/{mode}/text-to-image'
+        headers = self._prepare_headers()
+        logger.debug(f"Send request to {endpoint=} body={data}")
+        response = self._request('POST', endpoint, data, headers)
+        if response is None:
+            raise HTTPError("Failed to initiate getimg.ai after multiple retries")
+        return response

+ 39 - 0
api/core/tools/provider/builtin/getimgai/tools/text2image.py

@@ -0,0 +1,39 @@
+import json
+from typing import Any, Union
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.provider.builtin.getimgai.getimgai_appx import GetImgAIApp
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class Text2ImageTool(BuiltinTool):
+    def _invoke(self, user_id: str, tool_parameters: dict[str, Any]) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        app = GetImgAIApp(api_key=self.runtime.credentials['getimg_api_key'], base_url=self.runtime.credentials['base_url'])
+
+        options = {
+            'style': tool_parameters.get('style'),
+            'prompt': tool_parameters.get('prompt'),
+            'aspect_ratio': tool_parameters.get('aspect_ratio'),
+            'output_format': tool_parameters.get('output_format', 'jpeg'),
+            'response_format': tool_parameters.get('response_format', 'url'),
+            'width': tool_parameters.get('width'),
+            'height': tool_parameters.get('height'),
+            'steps': tool_parameters.get('steps'),
+            'negative_prompt': tool_parameters.get('negative_prompt'),
+            'prompt_2': tool_parameters.get('prompt_2'),
+        }
+        options = {k: v for k, v in options.items() if v}
+
+        text2image_result = app.text2image(
+            mode=tool_parameters.get('mode', 'essential-v2'),
+            params=options,
+            wait=True
+        )
+
+        if not isinstance(text2image_result, str):
+            text2image_result = json.dumps(text2image_result, ensure_ascii=False, indent=4)
+
+        if not text2image_result:
+            return self.create_text_message("getimg.ai request failed.")
+
+        return self.create_text_message(text2image_result)

+ 167 - 0
api/core/tools/provider/builtin/getimgai/tools/text2image.yaml

@@ -0,0 +1,167 @@
+identity:
+  name: text2image
+  author: Matri Qi
+  label:
+    en_US: text2image
+  icon: icon.svg
+description:
+  human:
+    en_US: Generate image via getimg.ai.
+  llm: This tool is used to generate image from prompt or image via https://getimg.ai.
+parameters:
+  - name: prompt
+    type: string
+    required: true
+    label:
+      en_US: prompt
+    human_description:
+      en_US: The text prompt used to generate the image. The getimg.aier will generate an image based on this prompt.
+    llm_description: this prompt text will be used to generate image.
+    form: llm
+  - name: mode
+    type: select
+    required: false
+    label:
+      en_US: mode
+    human_description:
+      en_US: The getimg.ai mode to use. The mode determines the endpoint used to generate the image.
+    form: form
+    options:
+      - value: "essential-v2"
+        label:
+          en_US: essential-v2
+      - value: stable-diffusion-xl
+        label:
+          en_US: stable-diffusion-xl
+      - value: stable-diffusion
+        label:
+          en_US: stable-diffusion
+      - value: latent-consistency
+        label:
+          en_US: latent-consistency
+  - name: style
+    type: select
+    required: false
+    label:
+      en_US: style
+    human_description:
+      en_US: The style preset to use. The style preset guides the generation towards a particular style. It's just efficient for `Essential V2` mode.
+    form: form
+    options:
+      - value: photorealism
+        label:
+          en_US: photorealism
+      - value: anime
+        label:
+          en_US: anime
+      - value: art
+        label:
+          en_US: art
+  - name: aspect_ratio
+    type: select
+    required: false
+    label:
+      en_US: "aspect ratio"
+    human_description:
+      en_US: The aspect ratio of the generated image. It's just efficient for `Essential V2` mode.
+    form: form
+    options:
+      - value: "1:1"
+        label:
+          en_US: "1:1"
+      - value: "4:5"
+        label:
+          en_US: "4:5"
+      - value: "5:4"
+        label:
+          en_US: "5:4"
+      - value: "2:3"
+        label:
+          en_US: "2:3"
+      - value: "3:2"
+        label:
+          en_US: "3:2"
+      - value: "4:7"
+        label:
+          en_US: "4:7"
+      - value: "7:4"
+        label:
+          en_US: "7:4"
+  - name: output_format
+    type: select
+    required: false
+    label:
+      en_US: "output format"
+    human_description:
+      en_US: The file format of the generated image.
+    form: form
+    options:
+      - value: jpeg
+        label:
+          en_US: jpeg
+      - value: png
+        label:
+          en_US: png
+  - name: response_format
+    type: select
+    required: false
+    label:
+      en_US: "response format"
+    human_description:
+      en_US: The format in which the generated images are returned. Must be one of url or b64. URLs are only valid for 1 hour after the image has been generated.
+    form: form
+    options:
+      - value: url
+        label:
+          en_US: url
+      - value: b64
+        label:
+          en_US: b64
+  - name: model
+    type: string
+    required: false
+    label:
+      en_US: model
+    human_description:
+      en_US: Model ID supported by this pipeline and family. It's just efficient for `Stable Diffusion XL`, `Stable Diffusion`, `Latent Consistency` mode.
+    form: form
+  - name: negative_prompt
+    type: string
+    required: false
+    label:
+      en_US: negative prompt
+    human_description:
+      en_US: Text input that will not guide the image generation. It's just efficient for `Stable Diffusion XL`, `Stable Diffusion`, `Latent Consistency` mode.
+    form: form
+  - name: prompt_2
+    type: string
+    required: false
+    label:
+      en_US: prompt2
+    human_description:
+      en_US: Prompt sent to second tokenizer and text encoder. If not defined, prompt is used in both text-encoders. It's just efficient for `Stable Diffusion XL` mode.
+    form: form
+  - name: width
+    type: number
+    required: false
+    label:
+      en_US: width
+    human_description:
+      en_US: he width of the generated image in pixels. Width needs to be multiple of 64.
+    form: form
+  - name: height
+    type: number
+    required: false
+    label:
+      en_US: height
+    human_description:
+      en_US: he height of the generated image in pixels. Height needs to be multiple of 64.
+    form: form
+  - name: steps
+    type: number
+    required: false
+    label:
+      en_US: steps
+    human_description:
+      en_US: The number of denoising steps. More steps usually can produce higher quality images, but take more time to generate.  It's just efficient for `Stable Diffusion XL`, `Stable Diffusion`, `Latent Consistency` mode.
+    form: form