Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
321 changes: 266 additions & 55 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dify_plugin import Plugin, DifyPluginEnv

plugin = Plugin(DifyPluginEnv(MAX_REQUEST_TIMEOUT=120))
plugin = Plugin(DifyPluginEnv(MAX_REQUEST_TIMEOUT=600))

if __name__ == '__main__':
plugin.run()
2 changes: 1 addition & 1 deletion manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ label:
en_US: MuAPI

description:
en_US: Generate images and videos using MuAPI
en_US: Generate images, videos, and audio using MuAPI. Edit images, remove backgrounds, upscale images, and remix audio with 100+ AI models.

created_at: "2026-06-12T00:00:00.000000Z"

Expand Down
9 changes: 8 additions & 1 deletion provider/muapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ identity:
author: muapi
name: muapi
description:
en_US: Generate images and videos using MuAPI
en_US: Generate images, videos, and audio using MuAPI. Edit images, remove backgrounds, upscale, and remix audio.
icon: icon.png
label:
en_US: MuAPI
Expand All @@ -16,6 +16,13 @@ credentials_for_provider:

tools:
- tools/generate_image.yaml
- tools/generate_video.yaml
- tools/image_to_video.yaml
- tools/edit_image.yaml
- tools/remove_background.yaml
- tools/upscale_image.yaml
- tools/generate_audio.yaml
- tools/remix_audio.yaml

extra:
python:
Expand Down
12 changes: 11 additions & 1 deletion provider/muapi_provider.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import os

from dify_plugin import ToolProvider
from dify_plugin.errors.tool import ToolProviderCredentialValidationError

from muapi import MuAPI


class MuapiProvider(ToolProvider):

Expand All @@ -19,7 +23,13 @@ def validate_credentials(
"MuAPI API key is required"
)

os.environ["MUAPI_API_KEY"] = api_key

client = MuAPI()

client.accounts.balance()

except Exception as e:
raise ToolProviderCredentialValidationError(
str(e)
f"MuAPI credential validation failed: {str(e)}"
)
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
dify-plugin
dify-plugin==0.9.1
git+https://github.com/SamurAIGPT/muapi-python.git
55 changes: 55 additions & 0 deletions tools/edit_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from collections.abc import Generator
from typing import Any
import os

from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage

from muapi import MuAPI


class EditImageTool(Tool):
def _invoke(
self,
tool_parameters: dict[str, Any],
) -> Generator[ToolInvokeMessage]:

api_key = self.runtime.credentials.get(
"muapi_api_key"
)

if not api_key:
yield self.create_json_message(
{
"error": "Missing MuAPI API key"
}
)
return

os.environ["MUAPI_API_KEY"] = api_key

client = MuAPI()

kwargs = {
"image": tool_parameters["image"],
"prompt": tool_parameters["prompt"],
"model": tool_parameters.get(
"model",
"flux-kontext-dev"
),
}

aspect_ratio = tool_parameters.get("aspect_ratio")
if aspect_ratio:
kwargs["aspect_ratio"] = aspect_ratio

result = client.images.edit(**kwargs)

if isinstance(result, dict) and result.get("images"):
yield self.create_image_message(
result["images"][0]
)

yield self.create_json_message(
result if isinstance(result, dict) else {"result": str(result)}
)
56 changes: 56 additions & 0 deletions tools/edit_image.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
identity:
name: edit_image
author: muapi
label:
en_US: Edit Image

description:
human:
en_US: Edit an existing image using a text prompt via MuAPI
llm: Edit or transform an existing image based on a text prompt using AI image editing models via MuAPI

parameters:
- name: image
type: string
required: true
form: llm
label:
en_US: Image URL
human_description:
en_US: URL of the image to edit
llm_description: URL of the source image to edit or transform

- name: prompt
type: string
required: true
form: llm
label:
en_US: Prompt
human_description:
en_US: Text prompt describing the desired edit
llm_description: Text prompt describing how the image should be edited or transformed

- name: model
type: string
required: false
form: form
label:
en_US: Model
human_description:
en_US: The model to use for image editing (e.g. flux-kontext-dev)
llm_description: The model to use for image editing
default: flux-kontext-dev

- name: aspect_ratio
type: string
required: false
form: form
label:
en_US: Aspect Ratio
human_description:
en_US: Aspect ratio of the output image (e.g. 1:1, 16:9, 4:3)
llm_description: Aspect ratio of the edited output image

extra:
python:
source: tools/edit_image.py
64 changes: 64 additions & 0 deletions tools/generate_audio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from collections.abc import Generator
from typing import Any
import os

from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage

from muapi import MuAPI


class GenerateAudioTool(Tool):
def _invoke(
self,
tool_parameters: dict[str, Any],
) -> Generator[ToolInvokeMessage]:

api_key = self.runtime.credentials.get(
"muapi_api_key"
)

if not api_key:
yield self.create_json_message(
{
"error": "Missing MuAPI API key"
}
)
return

os.environ["MUAPI_API_KEY"] = api_key

client = MuAPI()

result = client.audio.create(
prompt=tool_parameters["prompt"],
title=tool_parameters.get(
"title",
""
),
tags=tool_parameters.get(
"tags",
""
),
instrumental=bool(
tool_parameters.get(
"instrumental",
False
)
),
)

audio_url = None

if isinstance(result, dict):
audio_url = result.get("audio") or result.get("url") or result.get("output")

if not audio_url and result.get("audio_url"):
audio_url = result["audio_url"]

if audio_url:
yield self.create_link_message(audio_url)

yield self.create_json_message(
result if isinstance(result, dict) else {"result": str(result)}
)
56 changes: 56 additions & 0 deletions tools/generate_audio.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
identity:
name: generate_audio
author: muapi
label:
en_US: Generate Audio

description:
human:
en_US: Generate audio or music from a text prompt using MuAPI
llm: Generate audio or music from a text description using AI audio generation via MuAPI

parameters:
- name: prompt
type: string
required: true
form: llm
label:
en_US: Prompt
human_description:
en_US: Text prompt describing the audio or music to generate
llm_description: Text prompt describing the audio or music to generate

- name: title
type: string
required: false
form: form
label:
en_US: Title
human_description:
en_US: Title for the generated audio track
llm_description: Title for the generated audio track

- name: tags
type: string
required: false
form: form
label:
en_US: Tags
human_description:
en_US: Comma-separated genre or style tags (e.g. pop, electronic, chill)
llm_description: Comma-separated genre or style tags for the audio

- name: instrumental
type: boolean
required: false
form: form
label:
en_US: Instrumental
human_description:
en_US: Generate instrumental only (no vocals)
llm_description: Whether to generate instrumental audio without vocals
default: false

extra:
python:
source: tools/generate_audio.py
64 changes: 64 additions & 0 deletions tools/generate_video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from collections.abc import Generator
from typing import Any
import os

from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage

from muapi import MuAPI


class GenerateVideoTool(Tool):
def _invoke(
self,
tool_parameters: dict[str, Any],
) -> Generator[ToolInvokeMessage]:

api_key = self.runtime.credentials.get(
"muapi_api_key"
)

if not api_key:
yield self.create_json_message(
{
"error": "Missing MuAPI API key"
}
)
return

os.environ["MUAPI_API_KEY"] = api_key

client = MuAPI()

result = client.videos.generate(
prompt=tool_parameters["prompt"],
model=tool_parameters.get(
"model",
"kling-master"
),
duration=int(
tool_parameters.get(
"duration",
5
)
),
aspect_ratio=tool_parameters.get(
"aspect_ratio",
"16:9"
),
)

video_url = None

if isinstance(result, dict):
video_url = result.get("video") or result.get("url") or result.get("output")

if not video_url and result.get("videos"):
video_url = result["videos"][0]

if video_url:
yield self.create_link_message(video_url)

yield self.create_json_message(
result if isinstance(result, dict) else {"result": str(result)}
)
Loading