mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-12 08:09:04 +08:00
feat: slidespeak slides generation (#10955)
This commit is contained in:
parent
e8868a7fb9
commit
817b85001f
BIN
api/core/tools/provider/builtin/slidespeak/_assets/icon.png
Normal file
BIN
api/core/tools/provider/builtin/slidespeak/_assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 512 B |
28
api/core/tools/provider/builtin/slidespeak/slidespeak.py
Normal file
28
api/core/tools/provider/builtin/slidespeak/slidespeak.py
Normal file
@ -0,0 +1,28 @@
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from yarl import URL
|
||||
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||
|
||||
|
||||
class SlideSpeakProvider(BuiltinToolProviderController):
|
||||
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
|
||||
api_key = credentials.get("slidespeak_api_key")
|
||||
base_url = credentials.get("base_url")
|
||||
|
||||
if not api_key:
|
||||
raise ToolProviderCredentialValidationError("API key is missing")
|
||||
|
||||
if base_url:
|
||||
base_url = str(URL(base_url) / "v1")
|
||||
|
||||
headers = {"Content-Type": "application/json", "X-API-Key": api_key}
|
||||
|
||||
test_task_id = "xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
url = f"{base_url or 'https://api.slidespeak.co/api/v1'}/task_status/{test_task_id}"
|
||||
|
||||
response = requests.get(url, headers=headers)
|
||||
if response.status_code != 200:
|
||||
raise ToolProviderCredentialValidationError("Invalid SlidePeak API key")
|
22
api/core/tools/provider/builtin/slidespeak/slidespeak.yaml
Normal file
22
api/core/tools/provider/builtin/slidespeak/slidespeak.yaml
Normal file
@ -0,0 +1,22 @@
|
||||
identity:
|
||||
author: Kalo Chin
|
||||
name: slidespeak
|
||||
label:
|
||||
en_US: SlideSpeak
|
||||
zh_Hans: SlideSpeak
|
||||
description:
|
||||
en_US: Generate presentation slides using SlideSpeak API
|
||||
zh_Hans: 使用 SlideSpeak API 生成演示幻灯片
|
||||
icon: icon.png
|
||||
|
||||
credentials_for_provider:
|
||||
slidespeak_api_key:
|
||||
type: secret-input
|
||||
required: true
|
||||
label:
|
||||
en_US: API Key
|
||||
zh_Hans: API 密钥
|
||||
placeholder:
|
||||
en_US: Enter your SlideSpeak API key
|
||||
zh_Hans: 输入您的 SlideSpeak API 密钥
|
||||
url: https://app.slidespeak.co/settings/developer
|
@ -0,0 +1,163 @@
|
||||
import asyncio
|
||||
from dataclasses import asdict, dataclass
|
||||
from enum import Enum
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
import aiohttp
|
||||
from pydantic import ConfigDict
|
||||
|
||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||
from core.tools.errors import ToolProviderCredentialValidationError
|
||||
from core.tools.tool.builtin_tool import BuiltinTool
|
||||
|
||||
|
||||
class SlidesGeneratorTool(BuiltinTool):
|
||||
"""
|
||||
Tool for generating presentations using the SlideSpeak API.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
headers: Optional[dict[str, str]] = None
|
||||
base_url: Optional[str] = None
|
||||
timeout: Optional[aiohttp.ClientTimeout] = None
|
||||
poll_interval: Optional[int] = None
|
||||
|
||||
class TaskState(Enum):
|
||||
FAILURE = "FAILURE"
|
||||
REVOKED = "REVOKED"
|
||||
SUCCESS = "SUCCESS"
|
||||
PENDING = "PENDING"
|
||||
RECEIVED = "RECEIVED"
|
||||
STARTED = "STARTED"
|
||||
|
||||
@dataclass
|
||||
class PresentationRequest:
|
||||
plain_text: str
|
||||
length: Optional[int] = None
|
||||
theme: Optional[str] = None
|
||||
|
||||
async def _generate_presentation(
|
||||
self,
|
||||
session: aiohttp.ClientSession,
|
||||
request: PresentationRequest,
|
||||
) -> dict[str, Any]:
|
||||
"""Generate a new presentation asynchronously"""
|
||||
async with session.post(
|
||||
f"{self.base_url}/presentation/generate",
|
||||
headers=self.headers,
|
||||
json=asdict(request),
|
||||
timeout=self.timeout,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
return await response.json()
|
||||
|
||||
async def _get_task_status(
|
||||
self,
|
||||
session: aiohttp.ClientSession,
|
||||
task_id: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Get the status of a task asynchronously"""
|
||||
async with session.get(
|
||||
f"{self.base_url}/task_status/{task_id}",
|
||||
headers=self.headers,
|
||||
timeout=self.timeout,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
return await response.json()
|
||||
|
||||
async def _wait_for_completion(
|
||||
self,
|
||||
session: aiohttp.ClientSession,
|
||||
task_id: str,
|
||||
) -> str:
|
||||
"""Wait for task completion and return download URL"""
|
||||
while True:
|
||||
status = await self._get_task_status(session, task_id)
|
||||
task_status = self.TaskState(status["task_status"])
|
||||
if task_status == self.TaskState.SUCCESS:
|
||||
return status["task_result"]["url"]
|
||||
if task_status in [self.TaskState.FAILURE, self.TaskState.REVOKED]:
|
||||
raise Exception(f"Task failed with status: {task_status.value}")
|
||||
await asyncio.sleep(self.poll_interval)
|
||||
|
||||
async def _generate_slides(
|
||||
self,
|
||||
plain_text: str,
|
||||
length: Optional[int],
|
||||
theme: Optional[str],
|
||||
) -> str:
|
||||
"""Generate slides and return the download URL"""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
request = self.PresentationRequest(
|
||||
plain_text=plain_text,
|
||||
length=length,
|
||||
theme=theme,
|
||||
)
|
||||
result = await self._generate_presentation(session, request)
|
||||
task_id = result["task_id"]
|
||||
download_url = await self._wait_for_completion(session, task_id)
|
||||
return download_url
|
||||
|
||||
async def _fetch_presentation(
|
||||
self,
|
||||
session: aiohttp.ClientSession,
|
||||
download_url: str,
|
||||
) -> bytes:
|
||||
"""Fetch the presentation file from the download URL"""
|
||||
async with session.get(download_url, timeout=self.timeout) as response:
|
||||
response.raise_for_status()
|
||||
return await response.read()
|
||||
|
||||
def _invoke(
|
||||
self,
|
||||
user_id: str,
|
||||
tool_parameters: dict[str, Any],
|
||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||
"""Synchronous invoke method that runs asynchronous code"""
|
||||
|
||||
async def async_invoke():
|
||||
# Extract parameters
|
||||
plain_text = tool_parameters.get("plain_text", "")
|
||||
length = tool_parameters.get("length")
|
||||
theme = tool_parameters.get("theme")
|
||||
|
||||
# Ensure runtime and credentials
|
||||
if not self.runtime or not self.runtime.credentials:
|
||||
raise ToolProviderCredentialValidationError("Tool runtime or credentials are missing")
|
||||
|
||||
# Get API key from credentials
|
||||
api_key = self.runtime.credentials.get("slidespeak_api_key")
|
||||
if not api_key:
|
||||
raise ToolProviderCredentialValidationError("SlideSpeak API key is missing")
|
||||
|
||||
# Set configuration
|
||||
self.headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-API-Key": api_key,
|
||||
}
|
||||
self.base_url = "https://api.slidespeak.co/api/v1"
|
||||
self.timeout = aiohttp.ClientTimeout(total=30)
|
||||
self.poll_interval = 2
|
||||
|
||||
# Run the asynchronous slide generation
|
||||
try:
|
||||
download_url = await self._generate_slides(plain_text, length, theme)
|
||||
|
||||
# Fetch the presentation file
|
||||
async with aiohttp.ClientSession() as session:
|
||||
presentation_bytes = await self._fetch_presentation(session, download_url)
|
||||
|
||||
return [
|
||||
self.create_text_message("Presentation generated successfully"),
|
||||
self.create_blob_message(
|
||||
blob=presentation_bytes,
|
||||
meta={"mime_type": "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
|
||||
),
|
||||
]
|
||||
except Exception as e:
|
||||
return [self.create_text_message(f"An error occurred: {str(e)}")]
|
||||
|
||||
# Run the asynchronous code synchronously
|
||||
result = asyncio.run(async_invoke())
|
||||
return result
|
@ -0,0 +1,102 @@
|
||||
identity:
|
||||
name: slide_generator
|
||||
author: Kalo Chin
|
||||
label:
|
||||
en_US: Slides Generator
|
||||
zh_Hans: 幻灯片生成器
|
||||
description:
|
||||
human:
|
||||
en_US: Generate presentation slides from text using SlideSpeak API.
|
||||
zh_Hans: 使用 SlideSpeak API 从文本生成演示幻灯片。
|
||||
llm: This tool converts text input into a presentation using the SlideSpeak API service, with options for slide length and theme.
|
||||
parameters:
|
||||
- name: plain_text
|
||||
type: string
|
||||
required: true
|
||||
label:
|
||||
en_US: Topic or Content
|
||||
zh_Hans: 主题或内容
|
||||
human_description:
|
||||
en_US: The topic or content to be converted into presentation slides.
|
||||
zh_Hans: 需要转换为幻灯片的内容或主题。
|
||||
llm_description: A string containing the topic or content to be transformed into presentation slides.
|
||||
form: llm
|
||||
- name: length
|
||||
type: number
|
||||
required: false
|
||||
label:
|
||||
en_US: Number of Slides
|
||||
zh_Hans: 幻灯片数量
|
||||
human_description:
|
||||
en_US: The desired number of slides in the presentation (optional).
|
||||
zh_Hans: 演示文稿中所需的幻灯片数量(可选)。
|
||||
llm_description: Optional parameter specifying the number of slides to generate.
|
||||
form: form
|
||||
- name: theme
|
||||
type: select
|
||||
required: false
|
||||
label:
|
||||
en_US: Presentation Theme
|
||||
zh_Hans: 演示主题
|
||||
human_description:
|
||||
en_US: The visual theme for the presentation (optional).
|
||||
zh_Hans: 演示文稿的视觉主题(可选)。
|
||||
llm_description: Optional parameter specifying the presentation theme.
|
||||
options:
|
||||
- label:
|
||||
en_US: Adam
|
||||
zh_Hans: Adam
|
||||
value: adam
|
||||
- label:
|
||||
en_US: Aurora
|
||||
zh_Hans: Aurora
|
||||
value: aurora
|
||||
- label:
|
||||
en_US: Bruno
|
||||
zh_Hans: Bruno
|
||||
value: bruno
|
||||
- label:
|
||||
en_US: Clyde
|
||||
zh_Hans: Clyde
|
||||
value: clyde
|
||||
- label:
|
||||
en_US: Daniel
|
||||
zh_Hans: Daniel
|
||||
value: daniel
|
||||
- label:
|
||||
en_US: Default
|
||||
zh_Hans: Default
|
||||
value: default
|
||||
- label:
|
||||
en_US: Eddy
|
||||
zh_Hans: Eddy
|
||||
value: eddy
|
||||
- label:
|
||||
en_US: Felix
|
||||
zh_Hans: Felix
|
||||
value: felix
|
||||
- label:
|
||||
en_US: Gradient
|
||||
zh_Hans: Gradient
|
||||
value: gradient
|
||||
- label:
|
||||
en_US: Iris
|
||||
zh_Hans: Iris
|
||||
value: iris
|
||||
- label:
|
||||
en_US: Lavender
|
||||
zh_Hans: Lavender
|
||||
value: lavender
|
||||
- label:
|
||||
en_US: Monolith
|
||||
zh_Hans: Monolith
|
||||
value: monolith
|
||||
- label:
|
||||
en_US: Nebula
|
||||
zh_Hans: Nebula
|
||||
value: nebula
|
||||
- label:
|
||||
en_US: Nexus
|
||||
zh_Hans: Nexus
|
||||
value: nexus
|
||||
form: form
|
Loading…
x
Reference in New Issue
Block a user