init
This commit is contained in:
+178
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
INSTALL_METHOD=remote
|
||||
REMOTE_INSTALL_HOST=debug.dify.ai
|
||||
REMOTE_INSTALL_PORT=5003
|
||||
REMOTE_INSTALL_KEY=********-****-****-****-************
|
||||
+171
@@ -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/
|
||||
@@ -0,0 +1 @@
|
||||
3.12
|
||||
@@ -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
@@ -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].
|
||||
|
||||
@@ -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
|
||||
|
||||

|
||||
|
||||
- Image Generation
|
||||
|
||||

|
||||
|
||||
- Music Generation
|
||||
|
||||

|
||||
|
||||
- Voice Clone
|
||||
|
||||

|
||||
|
||||
- Video Generation
|
||||
|
||||

|
||||
|
||||
@@ -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 |
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 |
@@ -0,0 +1,6 @@
|
||||
from dify_plugin import Plugin, DifyPluginEnv
|
||||
|
||||
plugin = Plugin(DifyPluginEnv(MAX_REQUEST_TIMEOUT=120))
|
||||
|
||||
if __name__ == "__main__":
|
||||
plugin.run()
|
||||
@@ -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
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
dify_plugin>=0.3.0,<0.4.0
|
||||
requests>=2.31.0
|
||||
+165
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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"}
|
||||
)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user