This commit is contained in:
quicksandzn
2025-06-20 11:49:50 +08:00
commit a817a85316
27 changed files with 1502 additions and 0 deletions
+178
View File
@@ -0,0 +1,178 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
# Vscode
.vscode/
# Git
.git/
.gitignore
# Mac
.DS_Store
# Windows
Thumbs.db
+4
View File
@@ -0,0 +1,4 @@
INSTALL_METHOD=remote
REMOTE_INSTALL_HOST=debug.dify.ai
REMOTE_INSTALL_PORT=5003
REMOTE_INSTALL_KEY=********-****-****-****-************
+171
View File
@@ -0,0 +1,171 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
# Vscode
.vscode/
+1
View File
@@ -0,0 +1 @@
3.12
+117
View File
@@ -0,0 +1,117 @@
## User Guide of how to develop a Dify Plugin
Hi there, looks like you have already created a Plugin, now let's get you started with the development!
### Choose a Plugin type you want to develop
Before start, you need some basic knowledge about the Plugin types, Plugin supports to extend the following abilities in Dify:
- **Tool**: Tool Providers like Google Search, Stable Diffusion, etc. it can be used to perform a specific task.
- **Model**: Model Providers like OpenAI, Anthropic, etc. you can use their models to enhance the AI capabilities.
- **Endpoint**: Like Service API in Dify and Ingress in Kubernetes, you can extend a http service as an endpoint and control its logics using your own code.
Based on the ability you want to extend, we have divided the Plugin into three types: **Tool**, **Model**, and **Extension**.
- **Tool**: It's a tool provider, but not only limited to tools, you can implement an endpoint there, for example, you need both `Sending Message` and `Receiving Message` if you are building a Discord Bot, **Tool** and **Endpoint** are both required.
- **Model**: Just a model provider, extending others is not allowed.
- **Extension**: Other times, you may only need a simple http service to extend the functionalities, **Extension** is the right choice for you.
I believe you have chosen the right type for your Plugin while creating it, if not, you can change it later by modifying the `manifest.yaml` file.
### Manifest
Now you can edit the `manifest.yaml` file to describe your Plugin, here is the basic structure of it:
- version(version, required)Plugin's version
- type(type, required)Plugin's type, currently only supports `plugin`, future support `bundle`
- author(string, required)Author, it's the organization name in Marketplace and should also equals to the owner of the repository
- label(label, required)Multi-language name
- created_at(RFC3339, required)Creation time, Marketplace requires that the creation time must be less than the current time
- icon(asset, required)Icon path
- resource (object)Resources to be applied
- memory (int64)Maximum memory usage, mainly related to resource application on SaaS for serverless, unit bytes
- permission(object)Permission application
- tool(object)Reverse call tool permission
- enabled (bool)
- model(object)Reverse call model permission
- enabled(bool)
- llm(bool)
- text_embedding(bool)
- rerank(bool)
- tts(bool)
- speech2text(bool)
- moderation(bool)
- node(object)Reverse call node permission
- enabled(bool)
- endpoint(object)Allow to register endpoint permission
- enabled(bool)
- app(object)Reverse call app permission
- enabled(bool)
- storage(object)Apply for persistent storage permission
- enabled(bool)
- size(int64)Maximum allowed persistent memory, unit bytes
- plugins(object, required)Plugin extension specific ability yaml file list, absolute path in the plugin package, if you need to extend the model, you need to define a file like openai.yaml, and fill in the path here, and the file on the path must exist, otherwise the packaging will fail.
- Format
- tools(list[string]): Extended tool suppliers, as for the detailed format, please refer to [Tool Guide](https://docs.dify.ai/docs/plugins/standard/tool_provider)
- models(list[string])Extended model suppliers, as for the detailed format, please refer to [Model Guide](https://docs.dify.ai/docs/plugins/standard/model_provider)
- endpoints(list[string])Extended Endpoints suppliers, as for the detailed format, please refer to [Endpoint Guide](https://docs.dify.ai/docs/plugins/standard/endpoint_group)
- Restrictions
- Not allowed to extend both tools and models
- Not allowed to have no extension
- Not allowed to extend both models and endpoints
- Currently only supports up to one supplier of each type of extension
- meta(object)
- version(version, required)manifest format version, initial version 0.0.1
- arch(list[string], required)Supported architectures, currently only supports amd64 arm64
- runner(object, required)Runtime configuration
- language(string)Currently only supports python
- version(string)Language version, currently only supports 3.12
- entrypoint(string)Program entry, in python it should be main
### Install Dependencies
- First of all, you need a Python 3.11+ environment, as our SDK requires that.
- Then, install the dependencies:
```bash
pip install -r requirements.txt
```
- If you want to add more dependencies, you can add them to the `requirements.txt` file, once you have set the runner to python in the `manifest.yaml` file, `requirements.txt` will be automatically generated and used for packaging and deployment.
### Implement the Plugin
Now you can start to implement your Plugin, by following these examples, you can quickly understand how to implement your own Plugin:
- [OpenAI](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/openai): best practice for model provider
- [Google Search](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/google): a simple example for tool provider
- [Neko](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/neko): a funny example for endpoint group
### Test and Debug the Plugin
You may already noticed that a `.env.example` file in the root directory of your Plugin, just copy it to `.env` and fill in the corresponding values, there are some environment variables you need to set if you want to debug your Plugin locally.
- `INSTALL_METHOD`: Set this to `remote`, your plugin will connect to a Dify instance through the network.
- `REMOTE_INSTALL_HOST`: The host of your Dify instance, you can use our SaaS instance `https://debug.dify.ai`, or self-hosted Dify instance.
- `REMOTE_INSTALL_PORT`: The port of your Dify instance, default is 5003
- `REMOTE_INSTALL_KEY`: You should get your debugging key from the Dify instance you used, at the right top of the plugin management page, you can see a button with a `debug` icon, click it and you will get the key.
Run the following command to start your Plugin:
```bash
python -m main
```
Refresh the page of your Dify instance, you should be able to see your Plugin in the list now, but it will be marked as `debugging`, you can use it normally, but not recommended for production.
### Package the Plugin
After all, just package your Plugin by running the following command:
```bash
dify-plugin plugin package ./ROOT_DIRECTORY_OF_YOUR_PLUGIN
```
you will get a `plugin.difypkg` file, that's all, you can submit it to the Marketplace now, look forward to your Plugin being listed!
## User Privacy Policy
Please fill in the privacy policy of the plugin if you want to make it published on the Marketplace, refer to [PRIVACY.md](PRIVACY.md) for more details.
+27
View File
@@ -0,0 +1,27 @@
## Privacy
### Introduction
We are committed to protecting your privacy. This Privacy Policy outlines that we do not collect, use, or process any personal data from our users.
### Information Collection
We do not collect any personal information through our plugin.
### Use of Information
Since we do not collect any personal information, there is no data to use for any purpose.
### Sharing of Information
We do not share any information with third parties, as we do not collect any data.
### Data Security
As no data is collected, there is no need for data security measures related to personal information.
### Your Rights
You retain full privacy rights, as no personal data is collected or stored.
### Changes to This Policy
We reserve the right to update or modify this Privacy Policy at any time. Changes will be effective immediately upon posting to our website.
### Contact Us
If you have any questions about this Privacy Policy, please contact us at [quicksandzn@gmail.com].
+35
View File
@@ -0,0 +1,35 @@
## Elasticsearch
- **Author:** [quicksandzn](https://github.com/quicksandznzn)
- **Github:** https://github.com/quicksandznzn/dify-plugin-minimax-kit
- **Version:** 0.0.1
- **Type:** tool
### Description
- Image Generation
- Music Generation
- Voice Clone
- Video Generation
### Usage
- Set Up Authorization
![img.png](_assets/img.png)
- Image Generation
![img.png](_assets/image_generation.png)
- Music Generation
![img.png](_assets/music_generation.png)
- Voice Clone
![img.png](_assets/voice_clone.png)
- Video Generation
![img.png](_assets/video_generation.png)
+1
View File
@@ -0,0 +1 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Minimax</title><defs><linearGradient id="lobe-icons-minimax-fill" x1="0%" x2="100.182%" y1="50.057%" y2="50.057%"><stop offset="0%" stop-color="#E2167E"></stop><stop offset="100%" stop-color="#FE603C"></stop></linearGradient></defs><path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z" fill="url(#lobe-icons-minimax-fill)" fill-rule="nonzero"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

+6
View File
@@ -0,0 +1,6 @@
from dify_plugin import Plugin, DifyPluginEnv
plugin = Plugin(DifyPluginEnv(MAX_REQUEST_TIMEOUT=120))
if __name__ == "__main__":
plugin.run()
+31
View File
@@ -0,0 +1,31 @@
version: 0.0.1
type: plugin
author: quicksandzn
name: minimax_kit
label:
en_US: MiniMax Kit
zh_Hans: MiniMax 工具箱
description:
en_US: MiniMax Kit
zh_Hans: MiniMax 工具箱
icon: icon.svg
resource:
memory: 268435456
permission:
tool:
enabled: true
plugins:
tools:
- provider/minimax.yaml
meta:
version: 0.0.1
arch:
- amd64
- arm64
runner:
language: python
version: "3.12"
entrypoint: main
created_at: 2025-06-17T14:55:25.497297+08:00
privacy: PRIVACY.md
verified: false
+22
View File
@@ -0,0 +1,22 @@
from typing import Any
from dify_plugin import ToolProvider
from dify_plugin.errors.tool import ToolProviderCredentialValidationError
from tools.base import MiniMaxBaseTool
class MiniMaxProvider(ToolProvider):
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
try:
api_key = credentials.get("api_key")
group_id = credentials.get("group_id")
response = MiniMaxBaseTool(api_key=api_key, group_id=group_id).file_list(
purpose="retrieval"
)
response.raise_for_status()
status_code = response.json().get("base_resp", {}).get("status_code", -1)
if status_code != 0:
raise ToolProviderCredentialValidationError(
f"Invalid credentials. Please check your API key and group ID. {response.text}"
)
except Exception as e:
raise ToolProviderCredentialValidationError(str(e))
+45
View File
@@ -0,0 +1,45 @@
identity:
author: quicksandzn
name: minimax_kit
label:
en_US: MiniMax Kit
zh_Hans: MiniMax Kit
description:
en_US: MiniMax Kit
zh_Hans: MiniMax Kit
icon: icon.svg
credentials_for_provider:
api_key:
type: secret-input
required: true
label:
en_US: Api Key
zh_Hans: Api Key
placeholder:
en_US: Please input your Api Key
zh_Hans: 请输入你的 Api Key
help:
en_US: Get your Api Key from MiniMax
zh_Hans: 从 MiniMax 获取您的 Api Key
url: https://platform.minimaxi.com/user-center/basic-information/interface-key
group_id:
type: secret-input
required: true
label:
en_US: Group ID
zh_Hans: Group ID
placeholder:
en_US: Please input your Group ID
zh_Hans: 请输入你的 Group ID
help:
en_US: Get your Group ID from MiniMax
zh_Hans: 从 MiniMax 获取您的 Group ID
url: https://platform.minimaxi.com/user-center/basic-information
tools:
- tools/image_generation.yaml
- tools/music_generation.yaml
- tools/voice_clone.yaml
- tools/video_generation.yaml
extra:
python:
source: provider/minimax.py
+2
View File
@@ -0,0 +1,2 @@
dify_plugin>=0.3.0,<0.4.0
requests>=2.31.0
+165
View File
@@ -0,0 +1,165 @@
import requests
API_ENDPOINT = "https://api.minimaxi.com/v1"
class MiniMaxBaseTool:
def __init__(self, api_key: str, group_id: str):
self.api_key = api_key
self.group_id = group_id
if not self.api_key:
raise ValueError("Api key are required")
if not self.group_id:
raise ValueError("Group id are required")
def _get_headers(self) -> dict:
headers = {
"Authorization": f"Bearer {self.api_key}",
}
return headers
def _request(self, method: str, url: str, **kwargs) -> requests.Response:
return requests.request(method, url, headers=self._get_headers(), **kwargs)
def text_to_image(
self,
model: str,
prompt: str,
aspect_ratio: str,
response_format: str,
prompt_optimizer: bool,
n: int,
) -> requests.Response:
response = self._request(
"POST",
f"{API_ENDPOINT}/image_generation",
json={
"model": model,
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"response_format": response_format,
"prompt_optimizer": prompt_optimizer,
"n": n,
},
)
return response
def music_upload(
self, file_name: str, file_blob: bytes, mime_type: str
) -> requests.Response:
files = [("file", (file_name, file_blob, mime_type))]
response = self._request(
"POST",
f"{API_ENDPOINT}/music_upload",
data={"purpose": "song"},
files=files,
)
return response
def music_generation(
self,
model: str,
refer_voice: str,
refer_instrumental: str,
refer_vocal: str,
lyrics: str,
) -> requests.Response:
response = self._request(
"POST",
f"{API_ENDPOINT}/music_generation",
json={
"model": model,
"refer_voice": refer_voice,
"refer_instrumental": refer_instrumental,
"refer_vocal": refer_vocal,
"lyrics": lyrics,
},
)
return response
def file_upload(
self, file_name: str, file_blob: bytes, mime_type: str, purpose: str
) -> requests.Response:
files = [("file", (file_name, file_blob, mime_type))]
response = self._request(
"POST",
f"{API_ENDPOINT}/files/upload",
params={
"GroupId": self.group_id,
},
data={"purpose": purpose},
files=files,
)
return response
def voice_clone(
self,
model: str,
file_id: str,
voice_id: str,
text: str,
accuracy: float,
need_noise_reduction: bool,
need_volume_normalization: bool,
) -> requests.Response:
response = self._request(
"POST",
f"{API_ENDPOINT}/voice_clone",
json={
"model": model,
"file_id": file_id,
"voice_id": voice_id,
"text": text,
"accuracy": accuracy,
"need_noise_reduction": need_noise_reduction,
"need_volume_normalization": need_volume_normalization,
},
)
return response
def video_generation_task(self, task_id: str):
response = self._request(
"GET",
f"{API_ENDPOINT}/query/video_generation",
params={"task_id": task_id},
)
return response
def video_generation(
self,
model: str,
prompt: str,
prompt_optimizer: bool,
duration: int,
resolution: str,
first_frame_image: str,
) -> requests.Response:
response = self._request(
"POST",
f"{API_ENDPOINT}/video_generation",
data={
"model": model,
"prompt": prompt,
"prompt_optimizer": prompt_optimizer,
"duration": duration,
"resolution": resolution,
"first_frame_image": first_frame_image,
},
)
return response
def file_retrieve(self, file_id: str) -> requests.Response:
response = self._request(
"GET",
f"{API_ENDPOINT}/files/retrieve",
params={"GroupId": self.group_id, "file_id": file_id},
)
return response
def file_list(self, purpose: str) -> requests.Response:
response = self._request(
"GET",
f"{API_ENDPOINT}/files/list",
params={"GroupId": self.group_id, "purpose": purpose},
)
return response
+51
View File
@@ -0,0 +1,51 @@
from collections.abc import Generator
from typing import Any
from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage
from tools.base import MiniMaxBaseTool
class MiniMaxImageGenerationTool(Tool):
def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
api_key = self.runtime.credentials.get("api_key")
group_id = self.runtime.credentials.get("group_id")
minimax = MiniMaxBaseTool(api_key=api_key, group_id=group_id)
model = tool_parameters.get("model")
prompt = tool_parameters.get("prompt")
aspect_ratio = tool_parameters.get("aspect_ratio")
prompt_optimizer = tool_parameters.get("prompt_optimizer")
n = tool_parameters.get("n")
response = minimax.text_to_image(
model=model,
prompt=prompt,
aspect_ratio=aspect_ratio,
response_format="url",
prompt_optimizer=prompt_optimizer,
n=n,
)
if response.status_code != 200:
yield self.create_text_message(
f"Image generation failed {response.status_code} {response.text}"
)
return
status_code = response.json().get("base_resp", {}).get("status_code", -1)
if status_code != 0:
yield self.create_text_message(f"Image generation failed {response.text}")
return
image_data = response.json().get("data", {})
image_urls = image_data.get("image_urls")
if not image_urls:
yield self.create_text_message(f"Image generation failed {response.text}")
return
for image_url in image_urls:
yield self.create_image_message(image_url)
image_data = {
"image_urls": image_urls,
}
yield self.create_json_message(image_data)
+102
View File
@@ -0,0 +1,102 @@
identity:
name: image_generation
author: quicksandzn
label:
en_US: Image Generation
zh_Hans: 图片生成
description:
human:
en_US: Image Generation
zh_Hans: 图片生成
llm: Generate images using text prompt words
parameters:
- name: model
type: select
required: true
options:
- value: image-01
label:
en_US: image-01
zh_Hans: image-01
- value: image-01-live
label:
en_US: image-01-live
zh_Hans: image-01-live
default: image-01
label:
en_US: Model Name
zh_Hans: 模型名称
human_description:
en_US: Model Name
zh_CN: 模型名称
form: form
- name: prompt
type: string
required: true
label:
en_US: Prompt
zh_Hans: 文本提示词
human_description:
en_US: Generate the description of the image.
zh_CN: 生成图像的描述
form: llm
- name: aspect_ratio
type: select
required: false
options:
- value: "1:1"
label:
en_US: "1:1"
zh_Hans: " 1:1"
- value: "16:9"
label:
en_US: "16:9"
zh_Hans: "16:9"
- value: "4:3"
label:
en_US: "4:3"
zh_Hans: "4:3"
- value: "3:2"
label:
en_US: "3:2"
zh_Hans: "3:2"
- value: "2:3"
label:
en_US: "2:3"
zh_Hans: "2:3"
- value: "3:4"
label:
en_US: "3:4"
zh_Hans: "3:4"
- value: "9:16"
label:
en_US: "9:16"
zh_Hans: "9:16"
- value: "21:9"
label:
en_US: "21:9"
zh_Hans: "21:9"
default: "1:1"
label:
en_US: Aspect Ratio
zh_Hans: 宽高比
human_description:
en_US: Used to control the aspect ratio of the generated image
zh_CN: 用于控制生成图像的宽高比
form: form
- name: n
type: number
required: false
default: 1
max: 9
min: 1
label:
en_US: Number of generated
zh_Hans: 生成数量
human_description:
en_US: Generate the description of the image.
zh_CN: Used to control the number of images generated in a single request
form: form
extra:
python:
source: tools/image_generation.py
+65
View File
@@ -0,0 +1,65 @@
from collections.abc import Generator
from typing import Any
from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage
from tools.base import MiniMaxBaseTool
class MiniMaxMusicGenerationTool(Tool):
def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
api_key = self.runtime.credentials.get("api_key")
group_id = self.runtime.credentials.get("group_id")
minimax = MiniMaxBaseTool(api_key=api_key, group_id=group_id)
model = tool_parameters.get("model")
song_file = tool_parameters.get("song")
refer_vocal = tool_parameters.get("refer_vocal")
lyrics = tool_parameters.get("lyrics")
upload_response = minimax.music_upload(
file_name=song_file.filename,
file_blob=song_file.blob,
mime_type=song_file.mime_type,
)
if upload_response.status_code != 200:
yield self.create_text_message(
f"Music generation upload failed {upload_response.status_code} {upload_response.text}"
)
return
upload_data = upload_response.json()
voice_id = upload_data.get("voice_id")
instrumental_id = upload_data.get("instrumental_id")
if not voice_id or not instrumental_id:
yield self.create_text_message(
f"Music generation upload failed {upload_response.text}"
)
return
gen_response = minimax.music_generation(
model=model,
refer_voice=voice_id,
refer_instrumental=instrumental_id,
refer_vocal=None if not refer_vocal else refer_vocal,
lyrics=lyrics,
)
if gen_response.status_code != 200:
yield self.create_text_message(
f"Music generation failed {gen_response.status_code} {gen_response.text}"
)
return
status_code = gen_response.json().get("base_resp", {}).get("status_code", -1)
if status_code != 0:
yield self.create_text_message(
f"Music generation failed {gen_response.text}"
)
return
audio_hex = gen_response.json().get("data", {}).get("audio")
if not audio_hex:
yield self.create_text_message(
f"Music generation failed {gen_response.text}"
)
return
(self.create_text_message("Audio generated successfully"),)
yield self.create_blob_message(
blob=bytes.fromhex(audio_hex), meta={"mime_type": "audio/mpeg"}
)
+64
View File
@@ -0,0 +1,64 @@
identity:
name: music_generation
author: quicksandzn
label:
en_US: Music Generation
zh_Hans: 音乐生成
description:
human:
en_US: Music Generation
zh_Hans: 音乐生成
llm: Music Generation
parameters:
- name: model
type: select
required: true
options:
- value: music-01
label:
en_US: music-01
zh_Hans: music-01
default: music-01
label:
en_US: Model Name
zh_Hans: 模型名称
human_description:
en_US: Model Name
zh_CN: 模型名称
form: form
- name: song
type: file
required: true
label:
en_US: Song File
zh_Hans: 歌曲文件
human_description:
en_US: Song File
zh_CN: 歌曲文件
form: llm
- name: refer_vocal
type: string
required: false
label:
en_US: Voice ID
zh_Hans: 声音ID
human_description:
en_US: The sound ID used to replace the generated music timbre when generating music
zh_CN: 生成音乐时用来替换生成音乐音色的声音ID
form: form
- name: lyrics
type: string
required: false
label:
en_US: Lyrics
zh_Hans: 歌词
help:
en_US: "Lyrics: Use line breaks (\n) to separate each line of lyrics. Use two consecutive line breaks (\n\n) to add pauses in the middle of the lyrics. Use double hyphens (##) at the beginning and end to add accompaniment. Supports up to 200 characters (each Chinese character, punctuation mark, and letter counts as one character)."
zh_CN: "歌词,使用换行符(\n)分隔每行歌词,使用两个连续换行符(\n\n)可以在歌词中间添加停顿,使用双井号(##)添加在首尾可以添加伴奏,支持最长200字符(每个汉字、标点和字母都算1个字符)。"
human_description:
en_US: Lyrics
zh_CN: 歌词
form: llm
extra:
python:
source: tools/music_generation.py
+103
View File
@@ -0,0 +1,103 @@
import time
from collections.abc import Generator
from typing import Any
from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage
from tools.base import MiniMaxBaseTool
import logging
from dify_plugin.config.logger_format import plugin_logger_handler
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.addHandler(plugin_logger_handler)
class MiniMaxVideoGenerationTool(Tool):
def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
api_key = self.runtime.credentials.get("api_key")
group_id = self.runtime.credentials.get("group_id")
minimax = MiniMaxBaseTool(api_key=api_key, group_id=group_id)
model = tool_parameters.get("model")
prompt = tool_parameters.get("prompt")
prompt_optimizer = tool_parameters.get("prompt_optimizer")
duration = tool_parameters.get("duration")
resolution = tool_parameters.get("resolution")
first_frame_image = tool_parameters.get("first_frame_image")
response = minimax.video_generation(
model=model,
prompt=prompt,
prompt_optimizer=prompt_optimizer,
duration=duration,
resolution=resolution,
first_frame_image=None if not first_frame_image else first_frame_image,
)
if response.status_code != 200:
yield self.create_text_message(
f"Video generation failed {response.status_code} {response.text}"
)
return
task_id = response.json().get("task_id")
if not task_id:
yield self.create_text_message(f"Video generation failed {response.text}")
return
yield self.create_text_message(f"Video generation task id {task_id}")
max_retries = 100
retry_count = 0
interval = 5
video_file_id = None
while retry_count < max_retries:
time.sleep(interval)
task_response = minimax.video_generation_task(task_id=task_id)
if task_response.status_code != 200:
yield self.create_text_message(
f"Video generation task failed {task_response.status_code} {task_response.text}"
)
break
task_json = task_response.json()
status_code = task_json.get("base_resp", {}).get("status_code", -1)
if status_code != 0:
yield self.create_text_message(
f"Video generation task failed {task_response.text}"
)
break
task_status = task_json.get("status")
match task_status:
case "Preparing":
logger.debug("Video generation task status preparing")
case "Queueing":
logger.debug("Video generation task status queueing")
case "Processing":
logger.debug("Video generation task status processing")
case "Success":
video_file_id = task_json.get("file_id")
yield self.create_text_message(
"Video generation task status Success"
)
break
case "failed":
yield self.create_text_message(
f"Video generation task status failed {task_response.text}"
)
break
retry_count += 1
if not video_file_id:
yield self.create_text_message("Video generation failed")
return
file_response = minimax.file_retrieve(file_id=video_file_id)
if file_response.status_code != 200:
yield self.create_text_message(
f"Video generation get file failed {file_response.status_code} {file_response.text}"
)
return
video_url = file_response.json().get("file", {}).get("download_url")
yield self.create_image_message(video_url)
video_data = {"video_url": video_url}
yield self.create_json_message(video_data)
+126
View File
@@ -0,0 +1,126 @@
identity:
name: video_generation
author: quicksandzn
label:
en_US: Video Generation
zh_Hans: 视频生成
description:
human:
en_US: Video Generation
zh_Hans: 视频生成
llm: Video Generation
parameters:
- name: model
type: select
required: true
options:
- value: MiniMax-Hailuo-02
label:
en_US: MiniMax-Hailuo-02
zh_Hans: MiniMax-Hailuo-02
- value: T2V-01-Director
label:
en_US: T2V-01-Director
zh_Hans: T2V-01-Director
- value: I2V-01-Director
label:
en_US: I2V-01-Director
zh_Hans: I2V-01-Director
- value: S2V-01
label:
en_US: S2V-01
zh_Hans: S2V-01
- value: I2V-01-live
label:
en_US: I2V-01-live
zh_Hans: I2V-01-live
- value: I2V-01
label:
en_US: I2V-01
zh_Hans: I2V-01
- value: T2V-01
label:
en_US: T2V-01
zh_Hans: T2V-01
default: MiniMax-Hailuo-02
label:
en_US: Model Name
zh_Hans: 模型名称
human_description:
en_US: Model Name
zh_CN: 模型名称
form: form
- name: prompt
type: string
required: false
label:
en_US: Prompt
zh_Hans: 文本提示词
human_description:
en_US: Generate the description of the video.
zh_CN: 生成视频的描述
form: llm
- name: prompt_optimizer
type: boolean
required: false
default: true
label:
en_US: Prompt Optimizer
zh_Hans: 提示词优化
human_description:
en_US: The model will automatically optimize the incoming prompt
zh_CN: 模型会自动优化传入的prompt
form: form
- name: duration
type: select
required: true
options:
- value: "6"
label:
en_US: "6"
zh_Hans: "6"
- value: "10"
label:
en_US: "10"
zh_Hans: "10"
default: "6"
label:
en_US: Video Duration
zh_Hans: 视频时长
human_description:
en_US: Generated video duration
zh_CN: 生成视频时长
form: form
- name: resolution
type: select
required: true
options:
- value: 768P
label:
en_US: 768P
zh_Hans: 768P
- value: 1080P
label:
en_US: 1080P
zh_Hans: 1080P
default: 768P
label:
en_US: Resolution
zh_Hans: 分辨率
human_description:
en_US: Resolution
zh_CN: 分辨率
form: form
- name: first_frame_image
type: string
required: false
label:
en_US: First Frame Image
zh_Hans: 首帧画面
human_description:
en_US: The model will generate a video based on the image passed in this parameter as the first frame
zh_CN: 模型将以此参数中传入的图片为首帧画面来生成视频
form: form
extra:
python:
source: tools/video_generation.py
+77
View File
@@ -0,0 +1,77 @@
import uuid
from collections.abc import Generator
from typing import Any
from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage
from tools.base import MiniMaxBaseTool
class MiniMaxVoiceCloneTool(Tool):
def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
api_key = self.runtime.credentials.get("api_key")
group_id = self.runtime.credentials.get("group_id")
minimax = MiniMaxBaseTool(api_key=api_key, group_id=group_id)
model = tool_parameters.get("model")
ref_voice = tool_parameters.get("ref_voice")
voice_id = tool_parameters.get("voice_id")
if not voice_id:
voice_id = f"voice_{uuid.uuid4()}"
text = tool_parameters.get("text")
accuracy = tool_parameters.get("accuracy", 0.7)
need_noise_reduction = tool_parameters.get("need_noise_reduction", False)
need_volume_normalization = tool_parameters.get(
"need_volume_normalization", False
)
upload_response = minimax.file_upload(
file_name=ref_voice.filename,
file_blob=ref_voice.blob,
mime_type=ref_voice.mime_type,
purpose="voice_clone",
)
if upload_response.status_code != 200:
yield self.create_text_message(
f"Voice clone upload failed {upload_response.status_code} {upload_response.text}"
)
return
upload_data = upload_response.json().get("file", {})
file_id = upload_data.get("file_id")
if not file_id:
yield self.create_text_message(
f"Voice clone upload failed {upload_response.text}"
)
return
clone_response = minimax.voice_clone(
model=model,
file_id=file_id,
voice_id=voice_id,
text=text,
accuracy=accuracy,
need_noise_reduction=need_noise_reduction,
need_volume_normalization=need_volume_normalization,
)
if clone_response.status_code != 200:
yield self.create_text_message(
f"Voice clone failed {clone_response.status_code} {clone_response.text}"
)
return
status_code = clone_response.json().get("base_resp", {}).get("status_code", -1)
if status_code != 0:
yield self.create_text_message(f"Voice clone failed {clone_response.text}")
return
demo_audio = clone_response.json().get("demo_audio")
if demo_audio:
yield self.create_text_message(demo_audio)
# response = requests.get(demo_audio, timeout=60)
# response.raise_for_status()
# yield self.create_blob_message(
# blob=response.content, meta={"mime_type": "audio/mpeg"}
# )
voice_clone_data = {"voice_id": voice_id, "demo_audio": demo_audio}
yield self.create_json_message(voice_clone_data)
+109
View File
@@ -0,0 +1,109 @@
identity:
name: voice_clone
author: quicksandzn
label:
en_US: Voice Clone
zh_Hans: 声音克隆
description:
human:
en_US: Voice Clone
zh_Hans: 声音克隆
llm: Voice Clone
parameters:
- name: text
type: string
required: false
label:
en_US: Text
zh_Hans: 试听文本
human_description:
en_US: Text
zh_CN: 试听文本
form: llm
- name: model
type: select
required: true
options:
- value: speech-02-hd
label:
en_US: speech-02-hd
zh_Hans: speech-02-hd
- value: speech-02-turbo
label:
en_US: speech-02-turbo
zh_Hans: speech-02-turbo
- value: speech-01-hd
label:
en_US: speech-01-hd
zh_Hans: speech-01-hd
- value: speech-01-turbo
label:
en_US: speech-01-turbo
zh_Hans: speech-01-turbo
default: speech-02-hd
label:
en_US: Model Name
zh_Hans: 模型名称
human_description:
en_US: Model Name
zh_CN: 模型名称
form: form
- name: ref_voice
type: file
required: true
label:
en_US: Reference Voice File
zh_Hans: 参考声音文件
human_description:
en_US: Reference Voice File
zh_CN: 参考声音文件
form: llm
- name: voice_id
type: string
required: false
label:
en_US: Voice ID Customize
zh_Hans: 声音ID (自定义)
human_description:
en_US: Voice ID, It will be automatically generated if it is empty
zh_CN: 声音ID,如果为空自动生成
form: form
- name: accuracy
type: number
required: false
min: 0.1
max: 1
default: 0.7
label:
en_US: Accuracy threshold
zh_Hans: 文本校验准确率阈值
human_description:
en_US: Accuracy threshold
zh_CN: 文本校验准确率阈值
form: form
- name: need_noise_reduction
type: boolean
required: false
default: false
label:
en_US: Noise reduction
zh_Hans: 是否开启降噪
human_description:
en_US: Noise reduction
zh_CN: 是否开启降噪
form: form
- name: need_volume_normalization
type: boolean
required: false
default: false
label:
en_US: Volume normalization
zh_Hans: 是否开启音量归一化
human_description:
en_US: Volume normalization
zh_CN: 是否开启音量归一化
form: form
extra:
python:
source: tools/voice_clone.py