This commit is contained in:
2026-03-02 17:12:17 +08:00
parent b30af4aff8
commit 843146cdd7
2489 changed files with 7434 additions and 61841 deletions

184
difyPlugin/pdf/.difyignore Normal file
View File

@@ -0,0 +1,184 @@
# 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
.github/
# Mac
.DS_Store
# Windows
Thumbs.db
# Dify plugin packages
# To prevent packaging repetitively
*.difypkg

View File

@@ -0,0 +1,3 @@
INSTALL_METHOD=remote
REMOTE_INSTALL_URL=debug.dify.ai:5003
REMOTE_INSTALL_KEY=********-****-****-****-************

View File

@@ -0,0 +1,109 @@
name: Plugin Publish Workflow
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Download CLI tool
run: |
mkdir -p $RUNNER_TEMP/bin
cd $RUNNER_TEMP/bin
wget https://github.com/langgenius/dify-plugin-daemon/releases/download/0.0.6/dify-plugin-linux-amd64
chmod +x dify-plugin-linux-amd64
echo "CLI tool location:"
pwd
ls -la dify-plugin-linux-amd64
- name: Get basic info from manifest
id: get_basic_info
run: |
PLUGIN_NAME=$(grep "^name:" manifest.yaml | cut -d' ' -f2)
echo "Plugin name: $PLUGIN_NAME"
echo "plugin_name=$PLUGIN_NAME" >> $GITHUB_OUTPUT
VERSION=$(grep "^version:" manifest.yaml | cut -d' ' -f2)
echo "Plugin version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
# If the author's name is not your github username, you can change the author here
AUTHOR=$(grep "^author:" manifest.yaml | cut -d' ' -f2)
echo "Plugin author: $AUTHOR"
echo "author=$AUTHOR" >> $GITHUB_OUTPUT
- name: Package Plugin
id: package
run: |
cd $GITHUB_WORKSPACE
PACKAGE_NAME="${{ steps.get_basic_info.outputs.plugin_name }}-${{ steps.get_basic_info.outputs.version }}.difypkg"
$RUNNER_TEMP/bin/dify-plugin-linux-amd64 plugin package . -o "$PACKAGE_NAME"
echo "Package result:"
ls -la "$PACKAGE_NAME"
echo "package_name=$PACKAGE_NAME" >> $GITHUB_OUTPUT
echo "\nFull file path:"
pwd
echo "\nDirectory structure:"
tree || ls -R
- name: Checkout target repo
uses: actions/checkout@v3
with:
repository: ${{steps.get_basic_info.outputs.author}}/dify-plugins
path: dify-plugins
token: ${{ secrets.PLUGIN_ACTION }}
fetch-depth: 1
persist-credentials: true
- name: Prepare and create PR
run: |
PACKAGE_NAME="${{ steps.get_basic_info.outputs.plugin_name }}-${{ steps.get_basic_info.outputs.version }}.difypkg"
mkdir -p dify-plugins/${{ steps.get_basic_info.outputs.author }}/${{ steps.get_basic_info.outputs.plugin_name }}
mv "$PACKAGE_NAME" dify-plugins/${{ steps.get_basic_info.outputs.author }}/${{ steps.get_basic_info.outputs.plugin_name }}/
cd dify-plugins
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git fetch origin main
git checkout main
git pull origin main
BRANCH_NAME="bump-${{ steps.get_basic_info.outputs.plugin_name }}-plugin-${{ steps.get_basic_info.outputs.version }}"
git checkout -b "$BRANCH_NAME"
git add .
git commit -m "bump ${{ steps.get_basic_info.outputs.plugin_name }} plugin to version ${{ steps.get_basic_info.outputs.version }}"
git push -u origin "$BRANCH_NAME" --force
git branch -a
echo "Waiting for branch to sync..."
sleep 10 # Wait 10 seconds for branch sync
- name: Create PR via GitHub API
env:
# How to config the token:
# 1. Profile -> Settings -> Developer settings -> Personal access tokens -> Generate new token (with repo scope) -> Copy the token
# 2. Go to the target repository -> Settings -> Secrets and variables -> Actions -> New repository secret -> Add the token as PLUGIN_ACTION
GH_TOKEN: ${{ secrets.PLUGIN_ACTION }}
run: |
gh pr create \
--repo langgenius/dify-plugins \
--head "${{ steps.get_basic_info.outputs.author }}:${{ steps.get_basic_info.outputs.plugin_name }}-${{ steps.get_basic_info.outputs.version }}" \
--base main \
--title "bump ${{ steps.get_basic_info.outputs.plugin_name }} plugin to version ${{ steps.get_basic_info.outputs.version }}" \
--body "bump ${{ steps.get_basic_info.outputs.plugin_name }} plugin package to version ${{ steps.get_basic_info.outputs.version }}
Changes:
- Updated plugin package file" || echo "PR already exists or creation skipped." # Handle cases where PR already exists

176
difyPlugin/pdf/.gitignore vendored Normal file
View File

@@ -0,0 +1,176 @@
# 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/
# macOS
.DS_Store
.AppleDouble
.LSOverride

137
difyPlugin/pdf/GUIDE.md Normal file
View File

@@ -0,0 +1,137 @@
# Dify Plugin Development Guide
Welcome to Dify plugin development! This guide will help you get started quickly.
## Plugin Types
Dify plugins extend three main capabilities:
| Type | Description | Example |
|------|-------------|---------|
| **Tool** | Perform specific tasks | Google Search, Stable Diffusion |
| **Model** | AI model integrations | OpenAI, Anthropic |
| **Endpoint** | HTTP services | Custom APIs, integrations |
You can create:
- **Tool**: Tool provider with optional endpoints (e.g., Discord bot)
- **Model**: Model provider only
- **Extension**: Simple HTTP service
## Setup
### Requirements
- Python 3.11+
- Dependencies: `pip install -r requirements.txt`
## Development Process
<details>
<summary><b>1. Manifest Structure</b></summary>
Edit `manifest.yaml` to describe your plugin:
```yaml
version: 0.1.0 # Required: Plugin version
type: plugin # Required: plugin or bundle
author: YourOrganization # Required: Organization name
label: # Required: Multi-language names
en_US: Plugin Name
zh_Hans: 插件名称
created_at: 2023-01-01T00:00:00Z # Required: Creation time (RFC3339)
icon: assets/icon.png # Required: Icon path
# Resources and permissions
resource:
memory: 268435456 # Max memory (bytes)
permission:
tool:
enabled: true # Tool permission
model:
enabled: true # Model permission
llm: true
text_embedding: false
# Other model types...
# Other permissions...
# Extensions definition
plugins:
tools:
- tools/my_tool.yaml # Tool definition files
models:
- models/my_model.yaml # Model definition files
endpoints:
- endpoints/my_api.yaml # Endpoint definition files
# Runtime metadata
meta:
version: 0.0.1 # Manifest format version
arch:
- amd64
- arm64
runner:
language: python
version: "3.12"
entrypoint: main
```
**Restrictions:**
- Cannot extend both tools and models
- Must have at least one extension
- Cannot extend both models and endpoints
- Limited to one supplier per extension type
</details>
<details>
<summary><b>2. Implementation Examples</b></summary>
Study these examples to understand plugin implementation:
- [OpenAI](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/openai) - Model provider
- [Google Search](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/google) - Tool provider
- [Neko](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/neko) - Endpoint group
</details>
<details>
<summary><b>3. Testing & Debugging</b></summary>
1. Copy `.env.example` to `.env` and configure:
```
INSTALL_METHOD=remote
REMOTE_INSTALL_URL=debug.dify.ai:5003
REMOTE_INSTALL_KEY=your-debug-key
```
2. Run your plugin:
```bash
python -m main
```
3. Refresh your Dify instance to see the plugin (marked as "debugging")
</details>
<details>
<summary><b>4. Publishing</b></summary>
#### Manual Packaging
```bash
dify-plugin plugin package ./YOUR_PLUGIN_DIR
```
#### Automated GitHub Workflow
Configure GitHub Actions to automate PR creation:
1. Create a Personal Access Token for your forked repository
2. Add it as `PLUGIN_ACTION` secret in your source repo
3. Create `.github/workflows/plugin-publish.yml`
When you create a release, the action will:
- Package your plugin
- Create a PR to your fork
[Detailed workflow documentation](https://docs.dify.ai/plugins/publish-plugins/plugin-auto-publish-pr)
</details>
## Privacy Policy
If publishing to the Marketplace, provide a privacy policy in [PRIVACY.md](PRIVACY.md).

View File

@@ -0,0 +1,3 @@
## Privacy
!!! Please fill in the privacy policy of the plugin.

10
difyPlugin/pdf/README.md Normal file
View File

@@ -0,0 +1,10 @@
## pdf
**Author:** yslg
**Version:** 0.0.1
**Type:** tool
### Description

View File

@@ -0,0 +1,55 @@
<!--
~ Dify Marketplace Template Icon
~ Dify 市场模板图标
~ Dify マーケットプレイステンプレートアイコン
~
~ WARNING / 警告 / 警告:
~
~ English: This is a TEMPLATE icon from Dify Marketplace only. You MUST NOT use this default icon in any way.
~ Please replace it with your own custom icon before submit this plugin.
~
~ 中文: 这只是来自 Dify 市场的模板图标。您绝对不能以任何方式使用此默认图标。
~ 请在提交此插件之前将其替换为您自己的自定义图标。
~
~ 日本語: これは Dify マーケットプレイスのテンプレートアイコンです。このデフォルトアイコンをいかなる方法でも使用してはいけません。
~ このプラグインを提出する前に、独自のカスタムアイコンに置き換えてください。
~
~ DIFY_MARKETPLACE_TEMPLATE_ICON_DO_NOT_USE
-->
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_15253_95095)">
<rect width="40" height="40" fill="#0033FF"/>
<g filter="url(#filter0_n_15253_95095)">
<rect width="40" height="40" fill="url(#paint0_linear_15253_95095)"/>
</g>
<path d="M28 10C28.5523 10 29 10.4477 29 11V16C29 16.5523 28.5523 17 28 17H23V30C23 30.5523 22.5523 31 22 31H18C17.4477 31 17 30.5523 17 30V17H11.5C10.9477 17 10.5 16.5523 10.5 16V13.618C10.5 13.2393 10.714 12.893 11.0528 12.7236L16.5 10H28ZM23 12H16.9721L12.5 14.2361V15H19V29H21V15H23V12ZM27 12H25V15H27V12Z" fill="white"/>
</g>
<defs>
<filter id="filter0_n_15253_95095" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feTurbulence type="fractalNoise" baseFrequency="2 2" stitchTiles="stitch" numOctaves="3" result="noise" seed="8033" />
<feComponentTransfer in="noise" result="coloredNoise1">
<feFuncR type="linear" slope="2" intercept="-0.5" />
<feFuncG type="linear" slope="2" intercept="-0.5" />
<feFuncB type="linear" slope="2" intercept="-0.5" />
<feFuncA type="discrete" tableValues="1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "/>
</feComponentTransfer>
<feComposite operator="in" in2="shape" in="coloredNoise1" result="noise1Clipped" />
<feComponentTransfer in="noise1Clipped" result="color1">
<feFuncA type="table" tableValues="0 0.06" />
</feComponentTransfer>
<feMerge result="effect1_noise_15253_95095">
<feMergeNode in="shape" />
<feMergeNode in="color1" />
</feMerge>
</filter>
<linearGradient id="paint0_linear_15253_95095" x1="0" y1="0" x2="40" y2="40" gradientUnits="userSpaceOnUse">
<stop stop-color="#1443FF"/>
<stop offset="1" stop-color="#0031F5"/>
</linearGradient>
<clipPath id="clip0_15253_95095">
<rect width="40" height="40" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,55 @@
<!--
~ Dify Marketplace Template Icon
~ Dify 市场模板图标
~ Dify マーケットプレイステンプレートアイコン
~
~ WARNING / 警告 / 警告:
~
~ English: This is a TEMPLATE icon from Dify Marketplace only. You MUST NOT use this default icon in any way.
~ Please replace it with your own custom icon before submit this plugin.
~
~ 中文: 这只是来自 Dify 市场的模板图标。您绝对不能以任何方式使用此默认图标。
~ 请在提交此插件之前将其替换为您自己的自定义图标。
~
~ 日本語: これは Dify マーケットプレイスのテンプレートアイコンです。このデフォルトアイコンをいかなる方法でも使用してはいけません。
~ このプラグインを提出する前に、独自のカスタムアイコンに置き換えてください。
~
~ DIFY_MARKETPLACE_TEMPLATE_ICON_DO_NOT_USE
-->
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_15255_46435)">
<rect width="40" height="40" fill="#0033FF"/>
<g filter="url(#filter0_n_15255_46435)">
<rect width="40" height="40" fill="url(#paint0_linear_15255_46435)"/>
</g>
<path d="M28 10C28.5523 10 29 10.4477 29 11V16C29 16.5523 28.5523 17 28 17H23V30C23 30.5523 22.5523 31 22 31H18C17.4477 31 17 30.5523 17 30V17H11.5C10.9477 17 10.5 16.5523 10.5 16V13.618C10.5 13.2393 10.714 12.893 11.0528 12.7236L16.5 10H28ZM23 12H16.9721L12.5 14.2361V15H19V29H21V15H23V12ZM27 12H25V15H27V12Z" fill="white"/>
</g>
<defs>
<filter id="filter0_n_15255_46435" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feTurbulence type="fractalNoise" baseFrequency="2 2" stitchTiles="stitch" numOctaves="3" result="noise" seed="8033" />
<feComponentTransfer in="noise" result="coloredNoise1">
<feFuncR type="linear" slope="2" intercept="-0.5" />
<feFuncG type="linear" slope="2" intercept="-0.5" />
<feFuncB type="linear" slope="2" intercept="-0.5" />
<feFuncA type="discrete" tableValues="1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "/>
</feComponentTransfer>
<feComposite operator="in" in2="shape" in="coloredNoise1" result="noise1Clipped" />
<feComponentTransfer in="noise1Clipped" result="color1">
<feFuncA type="table" tableValues="0 0.06" />
</feComponentTransfer>
<feMerge result="effect1_noise_15255_46435">
<feMergeNode in="shape" />
<feMergeNode in="color1" />
</feMerge>
</filter>
<linearGradient id="paint0_linear_15255_46435" x1="0" y1="0" x2="40" y2="40" gradientUnits="userSpaceOnUse">
<stop stop-color="#1F4CFF"/>
<stop offset="1" stop-color="#0033FF"/>
</linearGradient>
<clipPath id="clip0_15255_46435">
<rect width="40" height="40" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

6
difyPlugin/pdf/main.py Normal file
View File

@@ -0,0 +1,6 @@
from dify_plugin import Plugin, DifyPluginEnv
plugin = Plugin(DifyPluginEnv(MAX_REQUEST_TIMEOUT=120))
if __name__ == '__main__':
plugin.run()

View File

@@ -0,0 +1,37 @@
version: 0.0.1
type: plugin
author: yslg
name: pdf
label:
en_US: pdf
ja_JP: pdf
zh_Hans: pdf
pt_BR: pdf
description:
en_US: pdfTools
ja_JP: pdfTools
zh_Hans: pdfTools
pt_BR: pdfTools
icon: icon.svg
icon_dark: icon-dark.svg
resource:
memory: 268435456
permission:
tool:
enabled: true
plugins:
tools:
- provider/pdf.yaml
meta:
version: 0.0.1
arch:
- amd64
- arm64
runner:
language: python
version: "3.12"
entrypoint: main
minimum_dify_version: null
created_at: 2026-03-02T13:21:03.2806864+08:00
privacy: PRIVACY.md
verified: false

View File

@@ -0,0 +1,64 @@
{
"name": "pdf-plugin",
"version": "1.0.0",
"description": "PDF plugin for analyzing table of contents and extracting text",
"author": "System",
"type": "tool",
"main": "main.py",
"requirements": "requirements.txt",
"icon": "https://neeko-copilot.bytedance.net/api/text2image?prompt=PDF%20document%20icon&size=square",
"settings": [
{
"key": "debug",
"type": "boolean",
"default": false,
"description": "Enable debug mode"
}
],
"functions": [
{
"name": "analyze_toc",
"description": "Analyze PDF and find table of contents",
"parameters": {
"type": "object",
"properties": {
"file": {
"type": "file",
"description": "PDF file to analyze",
"fileTypes": ["pdf"]
}
},
"required": ["file"]
}
},
{
"name": "extract_text",
"description": "Extract text from specified page range",
"parameters": {
"type": "object",
"properties": {
"file": {
"type": "file",
"description": "PDF file to extract text from",
"fileTypes": ["pdf"]
},
"page_range": {
"type": "object",
"properties": {
"start": {
"type": "integer",
"default": 0,
"description": "Start page index"
},
"end": {
"type": "integer",
"description": "End page index"
}
}
}
},
"required": ["file"]
}
}
]
}

View File

@@ -0,0 +1,53 @@
from typing import Any
from dify_plugin import ToolProvider
from dify_plugin.errors.tool import ToolProviderCredentialValidationError
class PdfProvider(ToolProvider):
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
try:
"""
IMPLEMENT YOUR VALIDATION HERE
"""
except Exception as e:
raise ToolProviderCredentialValidationError(str(e))
#########################################################################################
# If OAuth is supported, uncomment the following functions.
# Warning: please make sure that the sdk version is 0.4.2 or higher.
#########################################################################################
# def _oauth_get_authorization_url(self, redirect_uri: str, system_credentials: Mapping[str, Any]) -> str:
# """
# Generate the authorization URL for pdf OAuth.
# """
# try:
# """
# IMPLEMENT YOUR AUTHORIZATION URL GENERATION HERE
# """
# except Exception as e:
# raise ToolProviderOAuthError(str(e))
# return ""
# def _oauth_get_credentials(
# self, redirect_uri: str, system_credentials: Mapping[str, Any], request: Request
# ) -> Mapping[str, Any]:
# """
# Exchange code for access_token.
# """
# try:
# """
# IMPLEMENT YOUR CREDENTIALS EXCHANGE HERE
# """
# except Exception as e:
# raise ToolProviderOAuthError(str(e))
# return dict()
# def _oauth_refresh_credentials(
# self, redirect_uri: str, system_credentials: Mapping[str, Any], credentials: Mapping[str, Any]
# ) -> OAuthCredentials:
# """
# Refresh the credentials
# """
# return OAuthCredentials(credentials=credentials, expires_at=-1)

View File

@@ -0,0 +1,63 @@
identity:
author: "yslg"
name: "pdf"
label:
en_US: "pdf"
zh_Hans: "pdf"
pt_BR: "pdf"
ja_JP: "pdf"
description:
en_US: "pdfTools"
zh_Hans: "pdfTools"
pt_BR: "pdfTools"
ja_JP: "pdfTools"
icon: "icon.svg"
#########################################################################################
# If you want to support OAuth, you can uncomment the following code.
#########################################################################################
# oauth_schema:
# client_schema:
# - name: "client_id"
# type: "secret-input"
# required: true
# url: https://example.com/oauth/authorize
# placeholder:
# en_US: "Please input your Client ID"
# zh_Hans: "请输入你的 Client ID"
# pt_BR: "Insira seu Client ID"
# help:
# en_US: "Client ID is used to authenticate requests to the example.com API."
# zh_Hans: "Client ID 用于认证请求到 example.com API。"
# pt_BR: "Client ID é usado para autenticar solicitações à API do example.com."
# label:
# zh_Hans: "Client ID"
# en_US: "Client ID"
# - name: "client_secret"
# type: "secret-input"
# required: true
# url: https://example.com/oauth/authorize
# placeholder:
# en_US: "Please input your Client Secret"
# zh_Hans: "请输入你的 Client Secret"
# pt_BR: "Insira seu Client Secret"
# help:
# en_US: "Client Secret is used to authenticate requests to the example.com API."
# zh_Hans: "Client Secret 用于认证请求到 example.com API。"
# pt_BR: "Client Secret é usado para autenticar solicitações à API do example.com."
# label:
# zh_Hans: "Client Secret"
# en_US: "Client Secret"
# credentials_schema:
# - name: "access_token"
# type: "secret-input"
# label:
# zh_Hans: "Access Token"
# en_US: "Access Token"
tools:
- tools/pdf.yaml
- tools/pdf_single_page.yaml
extra:
python:
source: provider/pdf.py

View File

@@ -0,0 +1,2 @@
dify_plugin>=0.4.0,<0.7.0
PyPDF2>=3.0.1

View File

@@ -0,0 +1,61 @@
import re
from collections.abc import Generator
from io import BytesIO
from typing import Any
import PyPDF2
from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage
class PdfTool(Tool):
def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
file = tool_parameters.get("file")
if not file:
yield self.create_text_message("Error: file is required")
return
# file.blob returns bytes
pdf_bytes = file.blob
reader = PyPDF2.PdfReader(BytesIO(pdf_bytes))
num_pages = len(reader.pages)
toc_start = None
toc_end = None
toc_patterns = [
r'目录',
r'Table of Contents',
r'Contents',
r'目次'
]
for page_num in range(num_pages):
page = reader.pages[page_num]
text = page.extract_text() or ""
if any(re.search(pattern, text, re.IGNORECASE) for pattern in toc_patterns):
if toc_start is None:
toc_start = page_num
toc_end = page_num
elif toc_start is not None and toc_end is not None:
break
if toc_start is None:
yield self.create_json_message({
"start": None,
"end": None,
"pages": []
})
return
toc_pages = []
for page_num in range(toc_start, toc_end + 1):
page = reader.pages[page_num]
toc_pages.append(page.extract_text() or "")
yield self.create_json_message({
"start": toc_start,
"end": toc_end,
"pages": toc_pages
})

View File

@@ -0,0 +1,36 @@
identity:
name: "pdf"
author: "yslg"
label:
en_US: "Extract TOC Pages and Content"
zh_Hans: "提取目录页和内容"
pt_BR: "Extrair páginas de sumário e conteúdo"
ja_JP: "目次ページと内容を抽出"
description:
human:
en_US: "Extract table-of-contents page range and all page text in that range"
zh_Hans: "提取目录页范围以及该范围内所有页文本"
pt_BR: "Extrair intervalo de páginas de sumário e todo o texto nesse intervalo"
ja_JP: "目次ページ範囲とその範囲内の全ページテキストを抽出"
llm: "Extract table-of-contents page range and all page text in that range"
parameters:
- name: file
type: file
required: true
label:
en_US: PDF File
zh_Hans: PDF 文件
pt_BR: Arquivo PDF
ja_JP: PDFファイル
human_description:
en_US: "PDF file to process"
zh_Hans: "要处理的 PDF 文件"
pt_BR: "Arquivo PDF para processar"
ja_JP: "処理するPDFファイル"
llm_description: "PDF file to process, output contains start/end/pages"
form: llm
fileTypes:
- "pdf"
extra:
python:
source: tools/pdf.py

View File

@@ -0,0 +1,36 @@
from collections.abc import Generator
from io import BytesIO
from typing import Any
import PyPDF2
from dify_plugin import Tool
from dify_plugin.entities.tool import ToolInvokeMessage
class PdfSinglePageTool(Tool):
def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
file = tool_parameters.get("file")
page = tool_parameters.get("page", 0)
if not file:
yield self.create_text_message("Error: file is required")
return
pdf_bytes = file.blob
reader = PyPDF2.PdfReader(BytesIO(pdf_bytes))
num_pages = len(reader.pages)
page_index = int(page)
if page_index < 0:
page_index = 0
if page_index >= num_pages:
page_index = num_pages - 1
selected_page = reader.pages[page_index]
text = selected_page.extract_text() or ""
yield self.create_json_message({
"start": page_index,
"end": page_index,
"pages": [text]
})

View File

@@ -0,0 +1,52 @@
identity:
name: "pdf_single_page"
author: "yslg"
label:
en_US: "Extract Single-Page Text"
zh_Hans: "提取单页文字"
pt_BR: "Extrair texto de página única"
ja_JP: "単一ページのテキストを抽出"
description:
human:
en_US: "Extract text from one specified page"
zh_Hans: "提取指定单页文字"
pt_BR: "Extrair texto de uma página especificada"
ja_JP: "指定した1ページのテキストを抽出"
llm: "Extract text from one specified page"
parameters:
- name: file
type: file
required: true
label:
en_US: PDF File
zh_Hans: PDF 文件
pt_BR: Arquivo PDF
ja_JP: PDFファイル
human_description:
en_US: "PDF file to process"
zh_Hans: "要处理的 PDF 文件"
pt_BR: "Arquivo PDF para processar"
ja_JP: "処理するPDFファイル"
llm_description: "PDF file to process"
form: llm
fileTypes:
- "pdf"
- name: page
type: number
required: true
label:
en_US: Page Index
zh_Hans: 页码
pt_BR: Índice da Página
ja_JP: ページ番号
human_description:
en_US: "Single page index to extract"
zh_Hans: "要提取的单页页码"
pt_BR: "Índice da página única para extrair"
ja_JP: "抽出対象のページ番号"
llm_description: "Single page index to extract"
form: llm
default: 0
extra:
python:
source: tools/pdf_single_page.py