Browse Source

feat: add Novita AI image generation tool, implemented model search, text-to-image and create tile functionalities (#5308)

Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
Xiao Ley 10 months ago
parent
commit
132f5fb3de

BIN
api/core/tools/provider/builtin/novitaai/_assets/icon.ico


+ 30 - 0
api/core/tools/provider/builtin/novitaai/novitaai.py

@@ -0,0 +1,30 @@
+from typing import Any
+
+from core.tools.errors import ToolProviderCredentialValidationError
+from core.tools.provider.builtin.novitaai.tools.novitaai_txt2img import NovitaAiTxt2ImgTool
+from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
+
+
+class NovitaAIProvider(BuiltinToolProviderController):
+    def _validate_credentials(self, credentials: dict[str, Any]) -> None:
+        try:
+            result = NovitaAiTxt2ImgTool().fork_tool_runtime(
+                runtime={
+                    "credentials": credentials,
+                }
+            ).invoke(
+                user_id='',
+                tool_parameters={
+                    'model_name': 'cinenautXLATRUE_cinenautV10_392434.safetensors',
+                    'prompt': 'a futuristic city with flying cars',
+                    'negative_prompt': '',
+                    'width': 128,
+                    'height': 128,
+                    'image_num': 1,
+                    'guidance_scale': 7.5,
+                    'seed': -1,
+                    'steps': 1,
+                },
+            )
+        except Exception as e:
+            raise ToolProviderCredentialValidationError(str(e))

+ 32 - 0
api/core/tools/provider/builtin/novitaai/novitaai.yaml

@@ -0,0 +1,32 @@
+identity:
+  author: Xiao Ley
+  name: novitaai
+  label:
+    en_US: Novita AI
+    zh_Hans: Novita AI
+    pt_BR: Novita AI
+  description:
+    en_US: Innovative AI for Image Generation
+    zh_Hans: 用于图像生成的创新人工智能。
+    pt_BR: Innovative AI for Image Generation
+  icon: icon.ico
+  tags:
+    - image
+    - productivity
+credentials_for_provider:
+  api_key:
+    type: secret-input
+    required: true
+    label:
+      en_US: API Key
+      zh_Hans: API 密钥
+      pt_BR: Chave API
+    placeholder:
+      en_US: Please enter your Novita AI API key
+      zh_Hans: 请输入你的 Novita AI API 密钥
+      pt_BR: Por favor, insira sua chave de API do Novita AI
+    help:
+      en_US: Get your Novita AI API key from Novita AI
+      zh_Hans:  Novita AI 获取您的 Novita AI API 密钥
+      pt_BR: Obtenha sua chave de API do Novita AI na Novita AI
+    url: https://novita.ai

+ 51 - 0
api/core/tools/provider/builtin/novitaai/tools/novitaai_createtile.py

@@ -0,0 +1,51 @@
+from base64 import b64decode
+from copy import deepcopy
+from typing import Any, Union
+
+from novita_client import (
+    NovitaClient,
+)
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.errors import ToolProviderCredentialValidationError
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class NovitaAiCreateTileTool(BuiltinTool):
+    def _invoke(self,
+                user_id: str,
+                tool_parameters: dict[str, Any],
+                ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+            invoke tools
+        """
+        if 'api_key' not in self.runtime.credentials or not self.runtime.credentials.get('api_key'):
+            raise ToolProviderCredentialValidationError("Novita AI API Key is required.")
+
+        api_key = self.runtime.credentials.get('api_key')
+
+        client = NovitaClient(api_key=api_key)
+        param = self._process_parameters(tool_parameters)
+        client_result = client.create_tile(**param)
+
+        results = []
+        results.append(
+            self.create_blob_message(blob=b64decode(client_result.image_file),
+                                     meta={'mime_type': f'image/{client_result.image_type}'},
+                                     save_as=self.VARIABLE_KEY.IMAGE.value)
+        )
+
+        return results
+
+    def _process_parameters(self, parameters: dict[str, Any]) -> dict[str, Any]:
+        """
+            process parameters
+        """
+        res_parameters = deepcopy(parameters)
+
+        # delete none and empty
+        keys_to_delete = [k for k, v in res_parameters.items() if v is None or v == '']
+        for k in keys_to_delete:
+            del res_parameters[k]
+
+        return res_parameters

+ 80 - 0
api/core/tools/provider/builtin/novitaai/tools/novitaai_createtile.yaml

@@ -0,0 +1,80 @@
+identity:
+  name: novitaai_createtile
+  author: Xiao Ley
+  label:
+    en_US: Novita AI Create Tile
+    zh_Hans: Novita AI 创建平铺图案
+description:
+  human:
+    en_US: This feature produces images designed for seamless tiling, ideal for creating continuous patterns in fabrics, wallpapers, and various textures.
+    zh_Hans: 该功能生成设计用于无缝平铺的图像,非常适合用于制作连续图案的织物、壁纸和各种纹理。
+  llm: A tool for create images designed for seamless tiling, ideal for creating continuous patterns in fabrics, wallpapers, and various textures.
+parameters:
+  - name: prompt
+    type: string
+    required: true
+    label:
+      en_US: prompt
+      zh_Hans: 提示
+    human_description:
+      en_US: Positive prompt word of the created tile, divided by `,`, Range [1, 512]. Only English input is allowed.
+      zh_Hans: 生成平铺图案的正向提示,用 `,` 分隔,范围 [1, 512]。仅允许输入英文。
+    llm_description: Image prompt of Novita AI, you should describe the image you want to generate as a list of words as possible as detailed, divided by `,`, Range [1, 512]. Only English input is allowed.
+    form: llm
+  - name: negative_prompt
+    type: string
+    required: false
+    label:
+      en_US: negative prompt
+      zh_Hans: 负向提示
+    human_description:
+      en_US: Negtive prompt word of the created tile, divided by `,`, Range [1, 512]. Only English input is allowed.
+      zh_Hans: 生成平铺图案的负向提示,用 `,` 分隔,范围 [1, 512]。仅允许输入英文。
+    llm_description: Image negative prompt of Novita AI, divided by `,`, Range [1, 512]. Only English input is allowed.
+    form: llm
+  - name: width
+    type: number
+    default: 256
+    min: 128
+    max: 1024
+    required: true
+    label:
+      en_US: width
+      zh_Hans: 
+    human_description:
+      en_US: Image width, Range [128, 1024].
+      zh_Hans: 图像宽度,范围 [128, 1024]
+    form: form
+  - name: height
+    type: number
+    default: 256
+    min: 128
+    max: 1024
+    required: true
+    label:
+      en_US: height
+      zh_Hans: 
+    human_description:
+      en_US: Image height, Range [128, 1024].
+      zh_Hans: 图像高度,范围 [128, 1024]
+    form: form
+  - name: response_image_type
+    type: select
+    default: jpeg
+    required: false
+    label:
+      en_US: response image type
+      zh_Hans: 响应图像类型
+    human_description:
+      en_US: Response image type, png or jpeg
+      zh_Hans: 响应图像类型,png  jpeg
+    form: form
+    options:
+      - value: jpeg
+        label:
+          en_US: jpeg
+          zh_Hans: jpeg
+      - value: png
+        label:
+          en_US: png
+          zh_Hans: png

+ 137 - 0
api/core/tools/provider/builtin/novitaai/tools/novitaai_modelquery.py

@@ -0,0 +1,137 @@
+import json
+from copy import deepcopy
+from typing import Any, Union
+
+from pandas import DataFrame
+from yarl import URL
+
+from core.helper import ssrf_proxy
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.errors import ToolProviderCredentialValidationError
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class NovitaAiModelQueryTool(BuiltinTool):
+    _model_query_endpoint = 'https://api.novita.ai/v3/model'
+
+    def _invoke(self,
+                user_id: str,
+                tool_parameters: dict[str, Any],
+                ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+            invoke tools
+        """
+        if 'api_key' not in self.runtime.credentials or not self.runtime.credentials.get('api_key'):
+            raise ToolProviderCredentialValidationError("Novita AI API Key is required.")
+
+        api_key = self.runtime.credentials.get('api_key')
+        headers = {
+            'Content-Type': 'application/json',
+            'Authorization': "Bearer " + api_key
+        }
+        params = self._process_parameters(tool_parameters)
+        result_type = params.get('result_type')
+        del params['result_type']
+
+        models_data = self._query_models(
+            models_data=[],
+            headers=headers,
+            params=params,
+            recursive=False if result_type == 'first sd_name' or result_type == 'first name sd_name pair' else True
+        )
+
+        result_str = ''
+        if result_type == 'first sd_name':
+            result_str = models_data[0]['sd_name_in_api']
+        elif result_type == 'first name sd_name pair':
+            result_str = json.dumps({'name': models_data[0]['name'], 'sd_name': models_data[0]['sd_name_in_api']})
+        elif result_type == 'sd_name array':
+            sd_name_array = [model['sd_name_in_api'] for model in models_data]
+            result_str = json.dumps(sd_name_array)
+        elif result_type == 'name array':
+            name_array = [model['name'] for model in models_data]
+            result_str = json.dumps(name_array)
+        elif result_type == 'name sd_name pair array':
+            name_sd_name_pair_array = [{'name': model['name'], 'sd_name': model['sd_name_in_api']} for model in models_data]
+            result_str = json.dumps(name_sd_name_pair_array)
+        elif result_type == 'whole info array':
+            result_str = json.dumps(models_data)
+        else:
+            raise NotImplementedError
+
+        return self.create_text_message(result_str)
+
+    def _query_models(self, models_data: list, headers: dict[str, Any],
+                      params: dict[str, Any], pagination_cursor: str = '', recursive: bool = True) -> list:
+        """
+            query models
+        """
+        inside_params = deepcopy(params)
+
+        if pagination_cursor != '':
+            inside_params['pagination.cursor'] = pagination_cursor
+
+        response = ssrf_proxy.get(
+            url=str(URL(self._model_query_endpoint)),
+            headers=headers,
+            params=params,
+            timeout=(10, 60)
+        )
+
+        res_data = response.json()
+
+        models_data.extend(res_data['models'])
+
+        res_data_len = len(res_data['models'])
+        if res_data_len == 0 or res_data_len < int(params['pagination.limit']) or recursive is False:
+            # deduplicate
+            df = DataFrame.from_dict(models_data)
+            df_unique = df.drop_duplicates(subset=['id'])
+            models_data = df_unique.to_dict('records')
+            return models_data
+
+        return self._query_models(
+            models_data=models_data,
+            headers=headers,
+            params=inside_params,
+            pagination_cursor=res_data['pagination']['next_cursor']
+        )
+
+    def _process_parameters(self, parameters: dict[str, Any]) -> dict[str, Any]:
+        """
+            process parameters
+        """
+        process_parameters = deepcopy(parameters)
+        res_parameters = {}
+
+        # delete none or empty
+        keys_to_delete = [k for k, v in process_parameters.items() if v is None or v == '']
+        for k in keys_to_delete:
+            del process_parameters[k]
+
+        if 'query' in process_parameters and process_parameters.get('query') != 'unspecified':
+            res_parameters['filter.query'] = process_parameters['query']
+
+        if 'visibility' in process_parameters and process_parameters.get('visibility') != 'unspecified':
+            res_parameters['filter.visibility'] = process_parameters['visibility']
+
+        if 'source' in process_parameters and process_parameters.get('source') != 'unspecified':
+            res_parameters['filter.source'] = process_parameters['source']
+
+        if 'type' in process_parameters and process_parameters.get('type') != 'unspecified':
+            res_parameters['filter.types'] = process_parameters['type']
+
+        if 'is_sdxl' in process_parameters:
+            if process_parameters['is_sdxl'] == 'true':
+                res_parameters['filter.is_sdxl'] = True
+            elif process_parameters['is_sdxl'] == 'false':
+                res_parameters['filter.is_sdxl'] = False
+
+        res_parameters['result_type'] = process_parameters.get('result_type', 'first sd_name')
+
+        res_parameters['pagination.limit'] = 1 \
+            if res_parameters.get('result_type') == 'first sd_name' \
+            or res_parameters.get('result_type') == 'first name sd_name pair'\
+            else 100
+
+        return res_parameters

+ 174 - 0
api/core/tools/provider/builtin/novitaai/tools/novitaai_modelquery.yaml

@@ -0,0 +1,174 @@
+identity:
+  name: novitaai_modelquery
+  author: Xiao Ley
+  label:
+    en_US: Novita AI Model Query
+    zh_Hans: Novita AI 模型查询
+description:
+  human:
+    en_US: Retrieve information on both public and private models. It allows users to access details such as model specifications, status, and usage guidelines, ensuring comprehensive insight into the available modeling resources.
+    zh_Hans: 检索公开和私有模型信息。它允许用户访问模型规范、状态和使用指南等详细信息,确保了解可用的建模资源。
+  llm: A tool for retrieve information on both public and private Novita AI models.
+parameters:
+  - name: query
+    type: string
+    required: false
+    label:
+      en_US: query
+      zh_Hans: 查询
+    human_description:
+      en_US: Seaching the content of sd_name, name, tags.
+      zh_Hans: 搜索 sd_name、name、tags 中的内容
+    form: form
+  - name: result_type
+    type: select
+    default: "first sd_name"
+    required: true
+    label:
+      en_US: result format
+      zh_Hans: 结果格式
+    human_description:
+      en_US: The format of result
+      zh_Hans: 请求结果的格式
+    form: form
+    options:
+      - value: "first sd_name"
+        label:
+          en_US: "first sd_name"
+          zh_Hans: "第一个 sd_name"
+      - value: "first name sd_name pair"
+        label:
+          en_US: "first name and sd_name pair: {name, sd_name}"
+          zh_Hans: "第一个 name sd_name 组合:{name, sd_name}"
+      - value: "sd_name array"
+        label:
+          en_US: "sd_name array: [sd_name]"
+          zh_Hans: "sd_name 数组:[sd_name]"
+      - value: "name array"
+        label:
+          en_US: "name array: [name]"
+          zh_Hans: "name 数组:[name]"
+      - value: "name sd_name pair array"
+        label:
+          en_US: "name and sd_name pair array: [{name, sd_name}]"
+          zh_Hans: "name sd_name 组合数组:[{name, sd_name}]"
+      - value: "whole info array"
+        label:
+          en_US: whole info array
+          zh_Hans: 完整信息数组
+  - name: visibility
+    type: select
+    default: unspecified
+    required: false
+    label:
+      en_US: visibility
+      zh_Hans: 可见性
+    human_description:
+      en_US: Whether the model is public or private
+      zh_Hans: 模型是否公开或私有
+    form: form
+    options:
+      - value: unspecified
+        label:
+          en_US: Unspecified
+          zh_Hans: 未指定
+      - value: public
+        label:
+          en_US: Public
+          zh_Hans: 公开
+      - value: private
+        label:
+          en_US: Private
+          zh_Hans: 私有
+  - name: source
+    type: select
+    default: unspecified
+    required: false
+    label:
+      en_US: source
+      zh_Hans: 来源
+    human_description:
+      en_US: Source of the model
+      zh_Hans: 模型来源
+    form: form
+    options:
+      - value: unspecified
+        label:
+          en_US: Unspecified
+          zh_Hans: 未指定
+      - value: civitai
+        label:
+          en_US: Civitai
+          zh_Hans: Civitai
+      - value: training
+        label:
+          en_US: Training
+          zh_Hans: 训练
+      - value: uploading
+        label:
+          en_US: Uploading
+          zh_Hans: 上传
+  - name: type
+    type: select
+    default: unspecified
+    required: false
+    label:
+      en_US: type
+      zh_Hans: 类型
+    human_description:
+      en_US: Specifies the type of models to include in the query.
+      zh_Hans: 指定要查询的模型类型
+    form: form
+    options:
+      - value: unspecified
+        label:
+          en_US: Unspecified
+          zh_Hans: 未指定
+      - value: checkpoint
+        label:
+          en_US: Checkpoint
+          zh_Hans: Checkpoint
+      - value: lora
+        label:
+          en_US: LoRA
+          zh_Hans: LoRA
+      - value: vae
+        label:
+          en_US: VAE
+          zh_Hans: VAE
+      - value: controlnet
+        label:
+          en_US: ControlNet
+          zh_Hans: ControlNet
+      - value: upscaler
+        label:
+          en_US: Upscaler
+          zh_Hans: Upscaler
+      - value: textualinversion
+        label:
+          en_US: Textual inversion
+          zh_Hans: Textual Inversion
+  - name: is_sdxl
+    type: select
+    default: unspecified
+    required: false
+    label:
+      en_US: is sdxl
+      zh_Hans: 是否是 SDXL
+    human_description:
+      en_US: Whether sdxl model or not. Setting this parameter to `true` includes only sdxl models in the query results, which are typically large-scale, high-performance models designed for extensive data processing tasks. Conversely, setting it to `false` excludes these models from the results. If left unspecified, the filter will not discriminate based on the sdxl classification, including all model types in the search results.
+      zh_Hans: 是否是 SDXL 模型。设置此参数为 `是`,只查询 SDXL 模型,并包含大规模,高性能的模型。相反,设置为 `否`,将排除这些模型。如果未指定,将不会根据 SDXL 分类进行区分,包括查询结果中的所有模型类型。
+    form: form
+    options:
+      - value: unspecified
+        label:
+          en_US: Unspecified
+          zh_Hans: 未指定
+      - value: "true"
+        label:
+          en_US: "True"
+          zh_Hans: 
+      - value: "false"
+        label:
+          en_US: "False"
+          zh_Hans: 

+ 137 - 0
api/core/tools/provider/builtin/novitaai/tools/novitaai_txt2img.py

@@ -0,0 +1,137 @@
+from base64 import b64decode
+from copy import deepcopy
+from typing import Any, Union
+
+from novita_client import (
+    NovitaClient,
+    Txt2ImgV3Embedding,
+    Txt2ImgV3HiresFix,
+    Txt2ImgV3LoRA,
+    Txt2ImgV3Refiner,
+    V3TaskImage,
+)
+
+from core.tools.entities.tool_entities import ToolInvokeMessage
+from core.tools.errors import ToolProviderCredentialValidationError
+from core.tools.tool.builtin_tool import BuiltinTool
+
+
+class NovitaAiTxt2ImgTool(BuiltinTool):
+    def _invoke(self,
+                user_id: str,
+                tool_parameters: dict[str, Any],
+                ) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
+        """
+            invoke tools
+        """
+        if 'api_key' not in self.runtime.credentials or not self.runtime.credentials.get('api_key'):
+            raise ToolProviderCredentialValidationError("Novita AI API Key is required.")
+
+        api_key = self.runtime.credentials.get('api_key')
+
+        client = NovitaClient(api_key=api_key)
+        param = self._process_parameters(tool_parameters)
+        client_result = client.txt2img_v3(**param)
+
+        results = []
+        for image_encoded, image in zip(client_result.images_encoded, client_result.images):
+            if self._is_hit_nsfw_detection(image, 0.8):
+                results = self.create_text_message(text='NSFW detected!')
+                break
+
+            results.append(
+                self.create_blob_message(blob=b64decode(image_encoded),
+                                         meta={'mime_type': f'image/{image.image_type}'},
+                                         save_as=self.VARIABLE_KEY.IMAGE.value)
+            )
+
+        return results
+
+    def _process_parameters(self, parameters: dict[str, Any]) -> dict[str, Any]:
+        """
+            process parameters
+        """
+        res_parameters = deepcopy(parameters)
+
+        # delete none and empty
+        keys_to_delete = [k for k, v in res_parameters.items() if v is None or v == '']
+        for k in keys_to_delete:
+            del res_parameters[k]
+
+        if 'clip_skip' in res_parameters and res_parameters.get('clip_skip') == 0:
+            del res_parameters['clip_skip']
+
+        if 'refiner_switch_at' in res_parameters and res_parameters.get('refiner_switch_at') == 0:
+            del res_parameters['refiner_switch_at']
+
+        if 'enabled_enterprise_plan' in res_parameters:
+            res_parameters['enterprise_plan'] = {'enabled': res_parameters['enabled_enterprise_plan']}
+            del res_parameters['enabled_enterprise_plan']
+
+        if 'nsfw_detection_level' in res_parameters:
+            res_parameters['nsfw_detection_level'] = int(res_parameters['nsfw_detection_level'])
+
+        # process loras
+        if 'loras' in res_parameters:
+            loras_ori_list = res_parameters.get('loras').strip().split(';')
+            locals_list = []
+            for lora_str in loras_ori_list:
+                lora_info = lora_str.strip().split(',')
+                lora = Txt2ImgV3LoRA(
+                    model_name=lora_info[0].strip(),
+                    strength=float(lora_info[1]),
+                )
+                locals_list.append(lora)
+
+            res_parameters['loras'] = locals_list
+
+        # process embeddings
+        if 'embeddings' in res_parameters:
+            embeddings_ori_list = res_parameters.get('embeddings').strip().split(';')
+            locals_list = []
+            for embedding_str in embeddings_ori_list:
+                embedding = Txt2ImgV3Embedding(
+                    model_name=embedding_str.strip()
+                )
+                locals_list.append(embedding)
+
+            res_parameters['embeddings'] = locals_list
+
+        # process hires_fix
+        if 'hires_fix' in res_parameters:
+            hires_fix_ori = res_parameters.get('hires_fix')
+            hires_fix_info = hires_fix_ori.strip().split(',')
+            if 'upscaler' in hires_fix_info:
+                hires_fix = Txt2ImgV3HiresFix(
+                    target_width=int(hires_fix_info[0]),
+                    target_height=int(hires_fix_info[1]),
+                    strength=float(hires_fix_info[2]),
+                    upscaler=hires_fix_info[3].strip()
+                )
+            else:
+                hires_fix = Txt2ImgV3HiresFix(
+                    target_width=int(hires_fix_info[0]),
+                    target_height=int(hires_fix_info[1]),
+                    strength=float(hires_fix_info[2])
+                )
+
+            res_parameters['hires_fix'] = hires_fix
+
+            if 'refiner_switch_at' in res_parameters:
+                refiner = Txt2ImgV3Refiner(
+                    switch_at=float(res_parameters.get('refiner_switch_at'))
+                )
+                del res_parameters['refiner_switch_at']
+                res_parameters['refiner'] = refiner
+
+        return res_parameters
+
+    def _is_hit_nsfw_detection(self, image: V3TaskImage, confidence_threshold: float) -> bool:
+        """
+            is hit nsfw
+        """
+        if image.nsfw_detection_result is None:
+            return False
+        if image.nsfw_detection_result.valid and image.nsfw_detection_result.confidence >= confidence_threshold:
+            return True
+        return False

+ 341 - 0
api/core/tools/provider/builtin/novitaai/tools/novitaai_txt2img.yaml

@@ -0,0 +1,341 @@
+identity:
+  name: novitaai_txt2img
+  author: Xiao Ley
+  label:
+    en_US: Novita AI Text to Image
+    zh_Hans: Novita AI 文字转图像
+description:
+  human:
+    en_US: Generate images from text prompts using Stable Diffusion models
+    zh_Hans: 通过 Stable Diffusion 模型根据文字提示生成图像
+  llm: A tool for generate images from English text prompts.
+parameters:
+  - name: model_name
+    type: string
+    required: true
+    label:
+      en_US: model name
+      zh_Hans: 模块名字
+    human_description:
+      en_US: Specify the name of the model checkpoint. You can use the "Novita AI Model Query" tool to query the corresponding "sd_name" value (type select "Checkpoint").
+      zh_Hans: 指定 Model Checkpoint 名称。可通过“Novita AI 模型请求”工具查询对应的“sd_name”值(类型选择“Checkpoint”)。
+    form: form
+  - name: prompt
+    type: string
+    required: true
+    label:
+      en_US: prompt
+      zh_Hans: 提示
+    human_description:
+      en_US: Text input required to guide the image generation, divided by `,`, Range [1, 1024]. Only English input is allowed.
+      zh_Hans: 生成图像的正向提示,用 `,` 分隔,范围 [1, 1024]。仅允许输入英文。
+    llm_description: Image prompt of Novita AI, you should describe the image you want to generate as a list of words as possible as detailed, divided by `,`, Range [1, 1024]. Only English input is allowed.
+    form: llm
+  - name: negative_prompt
+    type: string
+    required: false
+    label:
+      en_US: negative prompt
+      zh_Hans: 负向提示
+    human_description:
+      en_US: Text input that will not guide the image generation, divided by `,`, Range [1, 1024]. Only English input is allowed.
+      zh_Hans: 生成图像的负向提示,用 `,` 分隔,范围 [1, 1024]。仅允许输入英文。
+    llm_description: Image negative prompt of Novita AI, divided by `,`, Range [1, 1024]. Only English input is allowed.
+    form: llm
+  - name: width
+    type: number
+    default: 512
+    min: 128
+    max: 2048
+    required: true
+    label:
+      en_US: width
+      zh_Hans: 
+    human_description:
+      en_US: Image width, Range [128, 2048].
+      zh_Hans: 图像宽度,范围 [128, 2048]
+    form: form
+  - name: height
+    type: number
+    default: 512
+    min: 128
+    max: 2048
+    required: true
+    label:
+      en_US: height
+      zh_Hans: 
+    human_description:
+      en_US: Image height, Range [128, 2048].
+      zh_Hans: 图像高度,范围 [128, 2048]
+    form: form
+  - name: image_num
+    type: number
+    default: 1
+    min: 1
+    max: 8
+    required: true
+    label:
+      en_US: image num
+      zh_Hans: 图片数
+    human_description:
+      en_US: Image num, Range [1, 8].
+      zh_Hans: 图片数,范围 [1, 8]
+    form: form
+  - name: steps
+    type: number
+    default: 20
+    min: 1
+    max: 100
+    required: true
+    label:
+      en_US: steps
+      zh_Hans: 步数
+    human_description:
+      en_US: The number of denoising steps. More steps usually can produce higher quality images, but take more time to generate, Range [1, 100].
+      zh_Hans: 生成步数。更多步数可能会产生更好的图像,但生成时间更长,范围 [1, 100]
+    form: form
+  - name: seed
+    type: number
+    default: -1
+    required: true
+    label:
+      en_US: seed
+      zh_Hans: 种子
+    human_description:
+      en_US: A seed is a number from which Stable Diffusion generates noise, which, makes generation deterministic. Using the same seed and set of parameters will produce identical image each time, minimum -1.
+      zh_Hans: 种子是 Stable Diffusion 生成噪声的数字,它使生成具有确定性。使用相同的种子和参数设置将生成每次生成相同的图像,最小值 -1
+    form: form
+  - name: clip_skip
+    type: number
+    min: 1
+    max: 12
+    required: false
+    label:
+      en_US: clip skip
+      zh_Hans: 层跳过数
+    human_description:
+      en_US: This parameter indicates the number of layers to stop from the bottom during optimization, so clip_skip on 2 would mean, that in SD1.x model where the CLIP has 12 layers, you would stop at 10th layer, Range [1, 12], get reference at https://novita.ai/get-started/Misc.html#what-s-clip-skip.
+      zh_Hans: 此参数表示优化过程中从底部停止的层数,因此 clip_skip 的值为 2,表示在 SD1.x 模型中,CLIP  12 层,你将停止在 10 层,范围 [1, 12],参考 https://novita.ai/get-started/Misc.html#what-s-clip-skip。
+    form: form
+  - name: guidance_scale
+    type: number
+    default: "7.5"
+    min: 1.0
+    max: 30.0
+    required: true
+    label:
+      en_US: guidance scale
+      zh_Hans: 提示词遵守程度
+    human_description:
+      en_US: This setting says how close the Stable Diffusion will listen to your prompt, higer guidance forces the model to better follow the prompt, but result in lower quality output.Range [1, 30].
+      zh_Hans: 此设置表明 Stable Diffusion 如何听从您的提示,较高的 guidance_scale 会强制模型更好跟随提示,但结果会更低质量输出。范围 [1.0, 30.0]。
+    form: form
+  - name: sampler_name
+    type: select
+    required: true
+    label:
+      en_US: sampler name
+      zh_Hans: 采样器名称
+    human_description:
+      en_US: This parameter determines the denoising algorithm employed during the sampling phase of Stable Diffusion. Get reference at https://novita.ai/get-started/Misc.htmll#what-is-samplers.
+      zh_Hans: 此参数决定了在稳定扩散采样阶段使用的去噪算法。参考 https://novita.ai/get-started/Misc.htmll#what-is-samplers。
+    form: form
+    options:
+      - value: "Euler a"
+        label:
+          en_US: Euler a
+          zh_Hans: Euler a
+      - value: "Euler"
+        label:
+          en_US: Euler
+          zh_Hans: Euler
+      - value: "LMS"
+        label:
+          en_US: LMS
+          zh_Hans: LMS
+      - value: "Heun"
+        label:
+          en_US: Heun
+          zh_Hans: Heun
+      - value: "DPM2"
+        label:
+          en_US: DPM2
+          zh_Hans: DPM2
+      - value: "DPM2 a"
+        label:
+          en_US: DPM2 a
+          zh_Hans: DPM2 a
+      - value: "DPM++ 2S a"
+        label:
+          en_US: DPM++ 2S a
+          zh_Hans: DPM++ 2S a
+      - value: "DPM++ 2M"
+        label:
+          en_US: DPM++ 2M
+          zh_Hans: DPM++ 2M
+      - value: "DPM++ SDE"
+        label:
+          en_US: DPM++ SDE
+          zh_Hans: DPM++ SDE
+      - value: "DPM fast"
+        label:
+          en_US: DPM fast
+          zh_Hans: DPM fast
+      - value: "DPM adaptive"
+        label:
+          en_US: DPM adaptive
+          zh_Hans: DPM adaptive
+      - value: "LMS Karras"
+        label:
+          en_US: LMS Karras
+          zh_Hans: LMS Karras
+      - value: "DPM2 Karras"
+        label:
+          en_US: DPM2 Karras
+          zh_Hans: DPM2 Karras
+      - value: "DPM2 a Karras"
+        label:
+          en_US: DPM2 a Karras
+          zh_Hans: DPM2 a Karras
+      - value: "DPM++ 2S a Karras"
+        label:
+          en_US: DPM++ 2S a Karras
+          zh_Hans: DPM++ 2S a Karras
+      - value: "DPM++ 2M Karras"
+        label:
+          en_US: DPM++ 2M Karras
+          zh_Hans: DPM++ 2M Karras
+      - value: "DPM++ SDE Karras"
+        label:
+          en_US: DPM++ SDE Karras
+          zh_Hans: DPM++ SDE Karras
+      - value: "DDIM"
+        label:
+          en_US: DDIM
+          zh_Hans: DDIM
+      - value: "PLMS"
+        label:
+          en_US: PLMS
+          zh_Hans: PLMS
+      - value: "UniPC"
+        label:
+          en_US: UniPC
+          zh_Hans: UniPC
+  - name: sd_vae
+    type: string
+    required: false
+    label:
+      en_US: sd vae
+      zh_Hans: sd vae
+    human_description:
+      en_US: VAE(Variational Autoencoder), get reference at https://novita.ai/get-started/Misc.html#what-s-variational-autoencoders-vae. You can use the "Novita AI Model Query" tool to query the corresponding "sd_name" value (type select "VAE").
+      zh_Hans: VAE(变分自编码器),参考 https://novita.ai/get-started/Misc.html#what-s-variational-autoencoders-vae。可通过“Novita AI 模型请求”工具查询对应的“sd_name”值(类型选择“VAE”)。
+    form: form
+  - name: loras
+    type: string
+    required: false
+    label:
+      en_US: loRAs
+      zh_Hans: loRAs
+    human_description:
+      en_US: LoRA models. Currenlty supports up to 5 LoRAs. You can use the "Novita AI Model Query" tool to query the corresponding "sd_name" value (type select "LoRA"). Input template is "<sd_name>,<strength [0-1.0]>;<sd_name>,<strength [0-1.0]>;...". Such as"Film Grain style_331903,0.5;DoggystylePOV_9600,0.5"
+      zh_Hans: LoRA 模型。目前仅支持 5  LoRA。可通过“Novita AI 模型请求”工具查询对应的“sd_name”值(类型选择“LoRA”)。输入模板:“<sd_name>,<strength [0-1.0]>;<sd_name>,<strength [0-1.0]>;...”,例如:“Film Grain style_331903,0.5;DoggystylePOV_9600,0.5”
+    form: form
+  - name: embeddings
+    type: string
+    required: false
+    label:
+      en_US: text embeddings
+      zh_Hans: 文本嵌入
+    human_description:
+      en_US: Textual Inversion is a training method for personalizing models by learning new text embeddings from a few example images, currenlty supports up to 5 embeddings. You can use the "Novita AI Model Query" tool to query the corresponding "sd_name" value (type select "Text Inversion"). Input template is "<sd_name>;<sd_name>;...". Such as "EasyNegativeV2_75525;AS-YoungerV2"
+      zh_Hans: 文本反转是一种通过从一些示例图像中学习新的文本嵌入来个性化模型的训练方法,目前仅支持 5 个嵌入。可通过“Novita AI 模型请求”工具查询对应的“sd_name”值(类型选择“Text Inversion”)。输入模板:“<sd_name>;<sd_name>;...”,例如:“EasyNegativeV2_75525;AS-YoungerV2”
+    form: form
+  - name: hires_fix
+    type: string
+    required: false
+    label:
+      en_US: hires fix
+      zh_Hans: 高分辨率修复
+    human_description:
+      en_US: Use high resolution image fix. Input template is "<target_width [128, 4096]>,<target_height [128, 4096]>,<strength [0, 1.0]>,<upscaler (optional, selec type, `RealESRGAN_x4plus_anime_6B`, `RealESRNet_x4plus` or `Latent`)>". Such as "1024,1024,0.8", "1024,1024,0.8,RealESRGAN_x4plus_anime_6B"
+      zh_Hans: 使用高分辨率修复。输入模板 “<target_width [128, 4096]>,<target_height [128, 4096]>,<strength [0, 1.0]>,<upscaler (可选, 选项类型, `RealESRGAN_x4plus_anime_6B`, `RealESRNet_x4plus`  `Latent`)>”。例如 “1024,1024,0.8”、“1024,1024,0.8,RealESRGAN_x4plus_anime_6B”
+    form: form
+  - name: refiner_switch_at
+    type: number
+    min: 0.0
+    max: 1.0
+    required: false
+    label:
+      en_US: refiner switch at
+      zh_Hans: 重采样参与时刻
+    human_description:
+      en_US: This parameter in the context of a refiner allows you to set the extent to which the refiner alters the output of a model. When set to 0, the refiner has no effect; at 1, it's fully active. Intermediate values like 0.5 provide a balanced effect, where the refiner is moderately engaged, enhancing or adjusting the output without dominating the original model's characteristics. This setting is particularly useful for fine-tuning the output to achieve a desired balance between refinement and the original generative features, Range [0, 1.0]. Is not all models support refiners!
+      zh_Hans: 此参数允许您设置重采样更改模型输出的程度。当设置为0时,重采样不起作用;1时,它处于完全活动状态。像0.5这样的中间值提供了一种平衡效果,其中重采样适度参与,增强或调整输出,而不会主导原始模型的特性。此设置对于微调输出特别有用,范围 [0, 1.0]。不是所有模型都支持重采样!
+    form: form
+  - name: response_image_type
+    type: select
+    default: jpeg
+    required: false
+    label:
+      en_US: response image type
+      zh_Hans: 响应图像类型
+    human_description:
+      en_US: Response image type, png or jpeg
+      zh_Hans: 响应图像类型,png  jpeg
+    form: form
+    options:
+      - value: jpeg
+        label:
+          en_US: jpeg
+          zh_Hans: jpeg
+      - value: png
+        label:
+          en_US: png
+          zh_Hans: png
+  - name: enabled_enterprise_plan
+    type: boolean
+    default: false
+    required: false
+    label:
+      en_US: enterprise plan enabled
+      zh_Hans: 企业版计划启用
+    human_description:
+      en_US: Enable enterprise plan
+      zh_Hans: 启用企业版计划
+    form: form
+  - name: enable_nsfw_detection
+    type: boolean
+    default: false
+    required: false
+    label:
+      en_US: enable nsfw detection
+      zh_Hans: 启用 NSFW 检测
+    human_description:
+      en_US: Enable nsfw detection
+      zh_Hans: 启用 NSFW 检测
+    form: form
+  - name: nsfw_detection_level
+    type: select
+    default: "2"
+    required: false
+    label:
+      en_US: nsfw detection level
+      zh_Hans: NSFW 检测级别
+    human_description:
+      en_US: Nsfw detection level, from low to high
+      zh_Hans: NSFW 检测级别,越高越严格
+    form: form
+    options:
+      - value: "0"
+        label:
+          en_US: low
+          zh_Hans: 
+      - value: "1"
+        label:
+          en_US: middle
+          zh_Hans: 
+      - value: "2"
+        label:
+          en_US: high
+          zh_Hans: 

+ 1 - 0
api/requirements.txt

@@ -88,5 +88,6 @@ google-cloud-aiplatform==1.49.0
 vanna[postgres,mysql,clickhouse,duckdb]==0.5.5
 tencentcloud-sdk-python-hunyuan~=3.0.1158
 chromadb~=0.5.0
+novita_client~=0.5.6
 tenacity~=8.3.0
 cos-python-sdk-v5==1.9.30