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