mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-12 18:49:01 +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