mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-17 19:22:55 +08:00
Feature/docs generator (#11858)
### Type of change - [x] New Feature (non-breaking change which adds functionality) ### What problem does this PR solve? This PR introduces a new Docs Generator agent component for producing downloadable PDF, DOCX, or TXT files from Markdown content generated within a RAGFlow workflow. ### **Key Features** **Backend** - New component: DocsGenerator (agent/component/docs_generator.py) - - Markdown → PDF/DOCX/TXT conversion - - Supports tables, lists, code blocks, headings, and rich formatting - - Configurable document style (fonts, margins, colors, page size, orientation) - - Optional header logo and footer with page numbers/timestamps - **Frontend** - New configuration UI for the Docs Generator - - Download button integrated into the chat interface - - Output wired to the Message component - - Full i18n support **Documentation** Added component guide: docs/guides/agent/agent_component_reference/docs_generator.md **Usage** Add the Docs Generator to a workflow, connect Markdown output from an upstream component, configure metadata/style, and feed its output into the Message component. Users will see a document download button directly in the chat. **Contributor Note** We have been following RAGFlow since more than a year and half now and have worked extensively on personalizing the framework and integrating it into several of our internal systems. Over the past year and a half, we have built multiple platforms that rely on RAGFlow as a core component, which has given us a strong appreciation for how flexible and powerful the project is. We also previously contributed the full Italian translation, and we were glad to see it accepted. This new Docs Generator component was created for our own production needs, and we believe that it may be useful for many others in the community as well. We want to sincerely thank the entire RAGFlow team for the remarkable work you have done and continue to do. If there are opportunities to contribute further, we would be glad to help whenever we have time available. It would be a pleasure to support the project in any way we can. If appropriate, we would be glad to be listed among the project’s contributors, but in any case we look forward to continuing to support and contribute to the project. PentaFrame Development Team --------- Co-authored-by: PentaFrame <info@pentaframe.it> Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
This commit is contained in:
@ -52,7 +52,8 @@ RUN --mount=type=cache,id=ragflow_apt,target=/var/cache/apt,sharing=locked \
|
||||
apt install -y nginx unzip curl wget git vim less && \
|
||||
apt install -y ghostscript && \
|
||||
apt install -y pandoc && \
|
||||
apt install -y texlive
|
||||
apt install -y texlive && \
|
||||
apt install -y fonts-freefont-ttf fonts-noto-cjk
|
||||
|
||||
# Install uv
|
||||
RUN --mount=type=bind,from=infiniflow/ragflow_deps:latest,source=/,target=/deps \
|
||||
|
||||
1570
agent/component/docs_generator.py
Normal file
1570
agent/component/docs_generator.py
Normal file
File diff suppressed because it is too large
Load Diff
241
docs/guides/agent/agent_component_reference/docs_generator.md
Normal file
241
docs/guides/agent/agent_component_reference/docs_generator.md
Normal file
@ -0,0 +1,241 @@
|
||||
---
|
||||
sidebar_position: 35
|
||||
slug: /docs_generator
|
||||
---
|
||||
|
||||
# Docs Generator component
|
||||
|
||||
A component that generates downloadable PDF, DOCX, or TXT documents from markdown-style content with full Unicode support.
|
||||
|
||||
---
|
||||
|
||||
The **Docs Generator** component enables you to create professional documents directly within your agent workflow. It accepts markdown-formatted text and converts it into downloadable files, making it ideal for generating reports, summaries, or any structured document output.
|
||||
|
||||
## Key features
|
||||
|
||||
- **Multiple output formats**: PDF, DOCX, and TXT
|
||||
- **Full Unicode support**: Automatic font switching for CJK (Chinese, Japanese, Korean), Arabic, Hebrew, and other non-Latin scripts
|
||||
- **Rich formatting**: Headers, lists, tables, code blocks, and more
|
||||
- **Customizable styling**: Fonts, margins, page size, and orientation
|
||||
- **Document extras**: Logo, watermark, page numbers, and timestamps
|
||||
- **Direct download**: Generates a download button for the chat interface
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Content to be converted into a document (typically from an **Agent** or other text-generating component).
|
||||
|
||||
## Examples
|
||||
|
||||
You can pair an **Agent** component with the **Docs Generator** to create dynamic documents based on user queries. The **Agent** generates the content, and the **Docs Generator** converts it into a downloadable file. Connect the output to a **Message** component to display the download button in the chat.
|
||||
|
||||
A typical workflow looks like:
|
||||
|
||||
```
|
||||
Begin → Agent → Docs Generator → Message
|
||||
```
|
||||
|
||||
In the **Message** component, reference the `download` output variable from the **Docs Generator** to display a download button in the chat interface.
|
||||
|
||||
## Configurations
|
||||
|
||||
### Content
|
||||
|
||||
The main text content to include in the document. Supports markdown formatting:
|
||||
|
||||
- **Bold**: `**text**` or `__text__`
|
||||
- **Italic**: `*text*` or `_text_`
|
||||
- **Inline code**: `` `code` ``
|
||||
- **Headings**: `# Heading 1`, `## Heading 2`, `### Heading 3`
|
||||
- **Bullet lists**: `- item` or `* item`
|
||||
- **Numbered lists**: `1. item`
|
||||
- **Tables**: `| Column 1 | Column 2 |`
|
||||
- **Horizontal lines**: `---`
|
||||
- **Code blocks**: ` ``` code ``` `
|
||||
|
||||
:::tip NOTE
|
||||
Click **(x)** or type `/` to insert variables from upstream components.
|
||||
:::
|
||||
|
||||
### Title
|
||||
|
||||
Optional. The document title displayed at the top of the generated file.
|
||||
|
||||
### Subtitle
|
||||
|
||||
Optional. A subtitle displayed below the title.
|
||||
|
||||
### Output format
|
||||
|
||||
The file format for the generated document:
|
||||
|
||||
- **PDF** (default): Portable Document Format with full styling support.
|
||||
- **DOCX**: Microsoft Word format.
|
||||
- **TXT**: Plain text format.
|
||||
|
||||
### Logo image
|
||||
|
||||
Optional. A logo image to display at the top of the document. You can either:
|
||||
|
||||
- Upload an image file using the file picker
|
||||
- Paste an image path, URL, or base64-encoded data
|
||||
|
||||
### Logo position
|
||||
|
||||
The horizontal position of the logo:
|
||||
|
||||
- **left** (default)
|
||||
- **center**
|
||||
- **right**
|
||||
|
||||
### Logo dimensions
|
||||
|
||||
- **Logo width**: Width in inches (default: `2.0`)
|
||||
- **Logo height**: Height in inches (default: `1.0`)
|
||||
|
||||
### Font family
|
||||
|
||||
The font used throughout the document:
|
||||
|
||||
- **Helvetica** (default)
|
||||
- **Times-Roman**
|
||||
- **Courier**
|
||||
- **Helvetica-Bold**
|
||||
- **Times-Bold**
|
||||
|
||||
### Font size
|
||||
|
||||
The base font size in points. Defaults to `12`.
|
||||
|
||||
### Title font size
|
||||
|
||||
The font size for the document title. Defaults to `24`.
|
||||
|
||||
### Page size
|
||||
|
||||
The paper size for the document:
|
||||
|
||||
- **A4** (default)
|
||||
- **Letter**
|
||||
|
||||
### Orientation
|
||||
|
||||
The page orientation:
|
||||
|
||||
- **Portrait** (default)
|
||||
- **Landscape**
|
||||
|
||||
### Margins
|
||||
|
||||
Page margins in inches:
|
||||
|
||||
- **Margin top**: Defaults to `1.0`
|
||||
- **Margin bottom**: Defaults to `1.0`
|
||||
- **Margin left**: Defaults to `1.0`
|
||||
- **Margin right**: Defaults to `1.0`
|
||||
|
||||
### Filename
|
||||
|
||||
Optional. Custom filename for the generated document. If left empty, a filename is auto-generated with a timestamp.
|
||||
|
||||
### Output directory
|
||||
|
||||
The server directory where generated documents are saved. Defaults to `/tmp/pdf_outputs`.
|
||||
|
||||
### Add page numbers
|
||||
|
||||
When enabled, page numbers are added to the footer of each page. Defaults to `true`.
|
||||
|
||||
### Add timestamp
|
||||
|
||||
When enabled, a generation timestamp is added to the document footer. Defaults to `true`.
|
||||
|
||||
### Watermark text
|
||||
|
||||
Optional. Text to display as a diagonal watermark across each page. Useful for marking documents as "Draft", "Confidential", etc.
|
||||
|
||||
## Output
|
||||
|
||||
The **Docs Generator** component provides the following output variables:
|
||||
|
||||
| Variable name | Type | Description |
|
||||
| ------------- | --------- | --------------------------------------------------------------------------- |
|
||||
| `file_path` | `string` | The server path where the generated document is saved. |
|
||||
| `pdf_base64` | `string` | The document content encoded in base64 format. |
|
||||
| `download` | `string` | JSON containing download information for the chat interface. |
|
||||
| `success` | `boolean` | Indicates whether the document was generated successfully. |
|
||||
|
||||
### Displaying the download button
|
||||
|
||||
To display a download button in the chat, add a **Message** component after the **Docs Generator** and reference the `download` variable:
|
||||
|
||||
1. Connect the **Docs Generator** output to a **Message** component.
|
||||
2. In the **Message** component's content field, type `/` and select `{Docs Generator_0@download}`.
|
||||
3. When the agent runs, a download button will appear in the chat, allowing users to download the generated document.
|
||||
|
||||
The download button automatically handles:
|
||||
- File type detection (PDF, DOCX, TXT)
|
||||
- Proper MIME type for browser downloads
|
||||
- Base64 decoding for direct file delivery
|
||||
|
||||
## Unicode and multi-language support
|
||||
|
||||
The **Docs Generator** includes intelligent font handling for international content:
|
||||
|
||||
### How it works
|
||||
|
||||
1. **Content analysis**: The component scans the text for non-Latin characters.
|
||||
2. **Automatic font switching**: When CJK or other complex scripts are detected, the system automatically switches to a compatible CID font (STSong-Light for Chinese, HeiseiMin-W3 for Japanese, HYSMyeongJo-Medium for Korean).
|
||||
3. **Latin content**: For documents containing only Latin characters (including extended Latin, Cyrillic, and Greek), the user-selected font family is used.
|
||||
|
||||
### Supported scripts
|
||||
|
||||
| Script | Unicode Range | Font Used |
|
||||
| ------ | ------------- | --------- |
|
||||
| Chinese (CJK) | U+4E00–U+9FFF | STSong-Light |
|
||||
| Japanese (Hiragana/Katakana) | U+3040–U+30FF | HeiseiMin-W3 |
|
||||
| Korean (Hangul) | U+AC00–U+D7AF | HYSMyeongJo-Medium |
|
||||
| Arabic | U+0600–U+06FF | CID font fallback |
|
||||
| Hebrew | U+0590–U+05FF | CID font fallback |
|
||||
| Devanagari (Hindi) | U+0900–U+097F | CID font fallback |
|
||||
| Thai | U+0E00–U+0E7F | CID font fallback |
|
||||
|
||||
### Font installation
|
||||
|
||||
For full multi-language support in self-hosted deployments, ensure Unicode fonts are installed:
|
||||
|
||||
**Linux (Debian/Ubuntu):**
|
||||
```bash
|
||||
apt-get install fonts-freefont-ttf fonts-noto-cjk
|
||||
```
|
||||
|
||||
**Docker:** The official RAGFlow Docker image includes these fonts. For custom images, add the font packages to your Dockerfile:
|
||||
```dockerfile
|
||||
RUN apt-get update && apt-get install -y fonts-freefont-ttf fonts-noto-cjk
|
||||
```
|
||||
|
||||
:::tip NOTE
|
||||
CID fonts (STSong-Light, HeiseiMin-W3, etc.) are built into ReportLab and do not require additional installation. They are used automatically when CJK content is detected.
|
||||
:::
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Characters appear as boxes or question marks
|
||||
|
||||
This indicates missing font support. Ensure:
|
||||
1. The content contains supported Unicode characters.
|
||||
2. For self-hosted deployments, Unicode fonts are installed on the server.
|
||||
3. The document is being viewed in a PDF reader that supports embedded fonts.
|
||||
|
||||
### Download button not appearing
|
||||
|
||||
Ensure:
|
||||
1. The **Message** component is connected after the **Docs Generator**.
|
||||
2. The `download` variable is correctly referenced using `/` (which appears as `{Docs Generator_0@download}` when copied).
|
||||
3. The document generation completed successfully (check `success` output).
|
||||
|
||||
### Large tables not rendering correctly
|
||||
|
||||
For tables with many columns or large cell content:
|
||||
- The component automatically converts wide tables to a definition list format for better readability.
|
||||
- Consider splitting large tables into multiple smaller tables.
|
||||
- Use landscape orientation for wide tables.
|
||||
@ -154,8 +154,10 @@ dependencies = [
|
||||
"exceptiongroup>=1.3.0,<2.0.0",
|
||||
"ffmpeg-python>=0.2.0",
|
||||
"imageio-ffmpeg>=0.6.0",
|
||||
"reportlab>=4.4.1",
|
||||
"jinja2>=3.1.0",
|
||||
"boxsdk>=10.1.0",
|
||||
"aiosmtplib>=5.0.0",
|
||||
"aiosmtplib>=5.0.0"
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
|
||||
38
uv.lock
generated
38
uv.lock
generated
@ -1,5 +1,5 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
revision = 2
|
||||
requires-python = ">=3.12, <3.15"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.14' and sys_platform == 'darwin'",
|
||||
@ -3279,7 +3279,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "jupyter-client"
|
||||
version = "8.6.3"
|
||||
version = "8.7.0"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
dependencies = [
|
||||
{ name = "jupyter-core" },
|
||||
@ -3288,9 +3288,9 @@ dependencies = [
|
||||
{ name = "tornado" },
|
||||
{ name = "traitlets" },
|
||||
]
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/27/d10de45e8ad4ce872372c4a3a37b7b35b6b064f6f023a5c14ffcced4d59d/jupyter_client-8.7.0.tar.gz", hash = "sha256:3357212d9cbe01209e59190f67a3a7e1f387a4f4e88d1e0433ad84d7b262531d", size = 344691, upload-time = "2025-12-09T18:37:01.953Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/f5/fddaec430367be9d62a7ed125530e133bfd4a1c0350fe221149ee0f2b526/jupyter_client-8.7.0-py3-none-any.whl", hash = "sha256:3671a94fd25e62f5f2f554f5e95389c2294d89822378a5f2dd24353e1494a9e0", size = 106215, upload-time = "2025-12-09T18:37:00.024Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5979,6 +5979,7 @@ dependencies = [
|
||||
{ name = "infinity-emb" },
|
||||
{ name = "infinity-sdk" },
|
||||
{ name = "itsdangerous" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "jira" },
|
||||
{ name = "json-repair" },
|
||||
{ name = "langfuse" },
|
||||
@ -6036,6 +6037,7 @@ dependencies = [
|
||||
{ name = "ranx" },
|
||||
{ name = "readability-lxml" },
|
||||
{ name = "replicate" },
|
||||
{ name = "reportlab" },
|
||||
{ name = "requests" },
|
||||
{ name = "roman-numbers" },
|
||||
{ name = "ruamel-base" },
|
||||
@ -6148,6 +6150,7 @@ requires-dist = [
|
||||
{ name = "infinity-emb", specifier = ">=0.0.66,<0.0.67" },
|
||||
{ name = "infinity-sdk", specifier = "==0.6.11" },
|
||||
{ name = "itsdangerous", specifier = "==2.1.2" },
|
||||
{ name = "jinja2", specifier = ">=3.1.0" },
|
||||
{ name = "jira", specifier = "==3.10.5" },
|
||||
{ name = "json-repair", specifier = "==0.35.0" },
|
||||
{ name = "langfuse", specifier = ">=2.60.0" },
|
||||
@ -6205,6 +6208,7 @@ requires-dist = [
|
||||
{ name = "ranx", specifier = "==0.3.20" },
|
||||
{ name = "readability-lxml", specifier = ">=0.8.4,<1.0.0" },
|
||||
{ name = "replicate", specifier = "==0.31.0" },
|
||||
{ name = "reportlab", specifier = ">=4.4.1" },
|
||||
{ name = "requests", specifier = ">=2.32.3,<3.0.0" },
|
||||
{ name = "roman-numbers", specifier = "==1.0.2" },
|
||||
{ name = "ruamel-base", specifier = "==1.0.0" },
|
||||
@ -7409,21 +7413,21 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "tornado"
|
||||
version = "6.5.2"
|
||||
version = "6.5.3"
|
||||
source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" }
|
||||
sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/2e/3d22d478f27cb4b41edd4db7f10cd7846d0a28ea443342de3dba97035166/tornado-6.5.3.tar.gz", hash = "sha256:16abdeb0211796ffc73765bc0a20119712d68afeeaf93d1a3f2edf6b3aee8d5a", size = 513348, upload-time = "2025-12-11T04:16:42.225Z" }
|
||||
wheels = [
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/e9/bf22f66e1d5d112c0617974b5ce86666683b32c09b355dfcd59f8d5c8ef6/tornado-6.5.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2dd7d7e8d3e4635447a8afd4987951e3d4e8d1fb9ad1908c54c4002aabab0520", size = 443860, upload-time = "2025-12-11T04:16:26.638Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/9c/594b631f0b8dc5977080c7093d1e96f1377c10552577d2c31bb0208c9362/tornado-6.5.3-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5977a396f83496657779f59a48c38096ef01edfe4f42f1c0634b791dde8165d0", size = 442118, upload-time = "2025-12-11T04:16:28.32Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/f6/685b869f5b5b9d9547571be838c6106172082751696355b60fc32a4988ed/tornado-6.5.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f72ac800be2ac73ddc1504f7aa21069a4137e8d70c387172c063d363d04f2208", size = 445700, upload-time = "2025-12-11T04:16:29.64Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/4c/f0d19edf24912b7f21ae5e941f7798d132ad4d9b71441c1e70917a297265/tornado-6.5.3-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c43c4fc4f5419c6561cfb8b884a8f6db7b142787d47821e1a0e1296253458265", size = 445041, upload-time = "2025-12-11T04:16:30.799Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/2b/e02da94f4a4aef2bb3b923c838ef284a77548a5f06bac2a8682b36b4eead/tornado-6.5.3-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de8b3fed4b3afb65d542d7702ac8767b567e240f6a43020be8eaef59328f117b", size = 445270, upload-time = "2025-12-11T04:16:32.316Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/e2/7a7535d23133443552719dba526dacbb7415f980157da9f14950ddb88ad6/tornado-6.5.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dbc4b4c32245b952566e17a20d5c1648fbed0e16aec3fc7e19f3974b36e0e47c", size = 445957, upload-time = "2025-12-11T04:16:33.913Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/1f/9ff92eca81ff17a86286ec440dcd5eab0400326eb81761aa9a4eecb1ffb9/tornado-6.5.3-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:db238e8a174b4bfd0d0238b8cfcff1c14aebb4e2fcdafbf0ea5da3b81caceb4c", size = 445371, upload-time = "2025-12-11T04:16:35.093Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/b1/1d03ae4526a393b0b839472a844397337f03c7f3a1e6b5c82241f0e18281/tornado-6.5.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:892595c100cd9b53a768cbfc109dfc55dec884afe2de5290611a566078d9692d", size = 445348, upload-time = "2025-12-11T04:16:36.679Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/7d/7c181feadc8941f418d0d26c3790ee34ffa4bd0a294bc5201d44ebd19c1e/tornado-6.5.3-cp39-abi3-win32.whl", hash = "sha256:88141456525fe291e47bbe1ba3ffb7982549329f09b4299a56813923af2bd197", size = 446433, upload-time = "2025-12-11T04:16:38.332Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/98/4f7f938606e21d0baea8c6c39a7c8e95bdf8e50b0595b1bb6f0de2af7a6e/tornado-6.5.3-cp39-abi3-win_amd64.whl", hash = "sha256:ba4b513d221cc7f795a532c1e296f36bcf6a60e54b15efd3f092889458c69af1", size = 446842, upload-time = "2025-12-11T04:16:39.867Z" },
|
||||
{ url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/27/0e3fca4c4edf33fb6ee079e784c63961cd816971a45e5e4cacebe794158d/tornado-6.5.3-cp39-abi3-win_arm64.whl", hash = "sha256:278c54d262911365075dd45e0b6314308c74badd6ff9a54490e7daccdd5ed0ea", size = 445863, upload-time = "2025-12-11T04:16:41.099Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@ -14,6 +14,11 @@ import { cn } from '@/lib/utils';
|
||||
import MarkdownContent from '../markdown-content';
|
||||
import { ReferenceDocumentList } from '../next-message-item/reference-document-list';
|
||||
import { UploadedMessageFiles } from '../next-message-item/uploaded-message-files';
|
||||
import {
|
||||
PDFDownloadButton,
|
||||
extractPDFDownloadInfo,
|
||||
removePDFDownloadInfo,
|
||||
} from '../pdf-download-button';
|
||||
import { RAGFlowAvatar } from '../ragflow-avatar';
|
||||
import { useTheme } from '../theme-provider';
|
||||
import { AssistantGroupButton, UserGroupButton } from './group-button';
|
||||
@ -61,6 +66,20 @@ const MessageItem = ({
|
||||
return reference?.doc_aggs ?? [];
|
||||
}, [reference?.doc_aggs]);
|
||||
|
||||
// Extract PDF download info from message content
|
||||
const pdfDownloadInfo = useMemo(
|
||||
() => extractPDFDownloadInfo(item.content),
|
||||
[item.content],
|
||||
);
|
||||
|
||||
// If we have PDF download info, extract the remaining text
|
||||
const messageContent = useMemo(() => {
|
||||
if (!pdfDownloadInfo) return item.content;
|
||||
|
||||
// Remove the JSON part from the content to avoid showing it
|
||||
return removePDFDownloadInfo(item.content, pdfDownloadInfo);
|
||||
}, [item.content, pdfDownloadInfo]);
|
||||
|
||||
const handleRegenerateMessage = useCallback(() => {
|
||||
regenerateMessage?.(item);
|
||||
}, [regenerateMessage, item]);
|
||||
@ -122,23 +141,34 @@ const MessageItem = ({
|
||||
></UserGroupButton>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
isAssistant
|
||||
? theme === 'dark'
|
||||
? styles.messageTextDark
|
||||
: styles.messageText
|
||||
: styles.messageUserText,
|
||||
{ '!bg-bg-card': !isAssistant },
|
||||
)}
|
||||
>
|
||||
<MarkdownContent
|
||||
loading={loading}
|
||||
content={item.content}
|
||||
reference={reference}
|
||||
clickDocumentButton={clickDocumentButton}
|
||||
></MarkdownContent>
|
||||
</div>
|
||||
{/* Show PDF download button if download info is present */}
|
||||
{pdfDownloadInfo && (
|
||||
<PDFDownloadButton
|
||||
downloadInfo={pdfDownloadInfo}
|
||||
className="mb-2"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Show message content if there's any text besides the download */}
|
||||
{messageContent && (
|
||||
<div
|
||||
className={cn(
|
||||
isAssistant
|
||||
? theme === 'dark'
|
||||
? styles.messageTextDark
|
||||
: styles.messageText
|
||||
: styles.messageUserText,
|
||||
{ '!bg-bg-card': !isAssistant },
|
||||
)}
|
||||
>
|
||||
<MarkdownContent
|
||||
loading={loading}
|
||||
content={messageContent}
|
||||
reference={reference}
|
||||
clickDocumentButton={clickDocumentButton}
|
||||
></MarkdownContent>
|
||||
</div>
|
||||
)}
|
||||
{isAssistant && referenceDocumentList.length > 0 && (
|
||||
<ReferenceDocumentList
|
||||
list={referenceDocumentList}
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
IMessage,
|
||||
IReferenceChunk,
|
||||
IReferenceObject,
|
||||
UploadResponseDataType,
|
||||
} from '@/interfaces/database/chat';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
@ -24,6 +25,11 @@ import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workflow-timeline';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Atom, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import MarkdownContent from '../next-markdown-content';
|
||||
import {
|
||||
PDFDownloadButton,
|
||||
extractPDFDownloadInfo,
|
||||
removePDFDownloadInfo,
|
||||
} from '../pdf-download-button';
|
||||
import { RAGFlowAvatar } from '../ragflow-avatar';
|
||||
import { useTheme } from '../theme-provider';
|
||||
import { Button } from '../ui/button';
|
||||
@ -95,6 +101,20 @@ function MessageItem({
|
||||
return Object.values(docs);
|
||||
}, [reference?.doc_aggs]);
|
||||
|
||||
// Extract PDF download info from message content
|
||||
const pdfDownloadInfo = useMemo(
|
||||
() => extractPDFDownloadInfo(item.content),
|
||||
[item.content],
|
||||
);
|
||||
|
||||
// If we have PDF download info, extract the remaining text
|
||||
const messageContent = useMemo(() => {
|
||||
if (!pdfDownloadInfo) return item.content;
|
||||
|
||||
// Remove the JSON part from the content to avoid showing it
|
||||
return removePDFDownloadInfo(item.content, pdfDownloadInfo);
|
||||
}, [item.content, pdfDownloadInfo]);
|
||||
|
||||
const handleRegenerateMessage = useCallback(() => {
|
||||
regenerateMessage?.(item);
|
||||
}, [regenerateMessage, item]);
|
||||
@ -219,28 +239,39 @@ function MessageItem({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={cn({
|
||||
[theme === 'dark'
|
||||
? styles.messageTextDark
|
||||
: styles.messageText]: isAssistant,
|
||||
[styles.messageUserText]: !isAssistant,
|
||||
'bg-bg-card': !isAssistant,
|
||||
})}
|
||||
>
|
||||
{item.data ? (
|
||||
children
|
||||
) : sendLoading && isEmpty(item.content) ? (
|
||||
<>{!isShare && 'running...'}</>
|
||||
) : (
|
||||
<MarkdownContent
|
||||
loading={loading}
|
||||
content={item.content}
|
||||
reference={reference}
|
||||
clickDocumentButton={clickDocumentButton}
|
||||
></MarkdownContent>
|
||||
)}
|
||||
</div>
|
||||
{/* Show PDF download button if download info is present */}
|
||||
{pdfDownloadInfo && (
|
||||
<PDFDownloadButton
|
||||
downloadInfo={pdfDownloadInfo}
|
||||
className="mb-2"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Show message content if there's any text besides the download */}
|
||||
{messageContent && (
|
||||
<div
|
||||
className={cn({
|
||||
[theme === 'dark'
|
||||
? styles.messageTextDark
|
||||
: styles.messageText]: isAssistant,
|
||||
[styles.messageUserText]: !isAssistant,
|
||||
'bg-bg-card': !isAssistant,
|
||||
})}
|
||||
>
|
||||
{item.data ? (
|
||||
children
|
||||
) : sendLoading && isEmpty(messageContent) ? (
|
||||
<>{!isShare && 'running...'}</>
|
||||
) : (
|
||||
<MarkdownContent
|
||||
loading={loading}
|
||||
content={messageContent}
|
||||
reference={reference}
|
||||
clickDocumentButton={clickDocumentButton}
|
||||
></MarkdownContent>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isAssistant && referenceDocuments.length > 0 && (
|
||||
<ReferenceDocumentList
|
||||
list={referenceDocuments}
|
||||
@ -248,7 +279,9 @@ function MessageItem({
|
||||
)}
|
||||
|
||||
{isUser && (
|
||||
<UploadedMessageFiles files={item.files}></UploadedMessageFiles>
|
||||
<UploadedMessageFiles
|
||||
files={item.files as File[] | UploadResponseDataType[]}
|
||||
></UploadedMessageFiles>
|
||||
)}
|
||||
{/* {isAssistant && item.attachment && item.attachment.doc_id && (
|
||||
<div className="w-full flex items-center justify-end">
|
||||
|
||||
196
web/src/components/pdf-download-button/index.tsx
Normal file
196
web/src/components/pdf-download-button/index.tsx
Normal file
@ -0,0 +1,196 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Download, FileText } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
interface DocumentDownloadInfo {
|
||||
filename: string;
|
||||
base64: string;
|
||||
mime_type: string;
|
||||
}
|
||||
|
||||
interface DocumentDownloadButtonProps {
|
||||
downloadInfo: DocumentDownloadInfo;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function PDFDownloadButton({
|
||||
downloadInfo,
|
||||
className,
|
||||
}: DocumentDownloadButtonProps) {
|
||||
const handleDownload = useCallback(() => {
|
||||
try {
|
||||
// Convert base64 to blob
|
||||
const byteCharacters = atob(downloadInfo.base64);
|
||||
const byteNumbers = new Array(byteCharacters.length);
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
const blob = new Blob([byteArray], { type: downloadInfo.mime_type });
|
||||
|
||||
// Create download link
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = downloadInfo.filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
// Cleanup
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error('Error downloading document:', error);
|
||||
}
|
||||
}, [downloadInfo]);
|
||||
|
||||
// Determine document type from mime_type or filename
|
||||
const getDocumentType = () => {
|
||||
if (downloadInfo.mime_type === 'application/pdf') return 'PDF Document';
|
||||
if (
|
||||
downloadInfo.mime_type ===
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
)
|
||||
return 'Word Document';
|
||||
if (downloadInfo.mime_type === 'text/plain') return 'Text Document';
|
||||
|
||||
// Fallback to file extension
|
||||
const ext = downloadInfo.filename.split('.').pop()?.toUpperCase();
|
||||
if (ext === 'PDF') return 'PDF Document';
|
||||
if (ext === 'DOCX') return 'Word Document';
|
||||
if (ext === 'TXT') return 'Text Document';
|
||||
|
||||
return 'Document';
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center gap-3 p-4 border rounded-lg bg-background-card ${className || ''}`}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<div className="p-2 bg-accent-primary/10 rounded-lg">
|
||||
<FileText className="w-6 h-6 text-accent-primary" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-sm truncate">
|
||||
{downloadInfo.filename}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">{getDocumentType()}</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleDownload}
|
||||
size="sm"
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Download
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function to detect if content contains document download info
|
||||
export function extractPDFDownloadInfo(
|
||||
content: string,
|
||||
): DocumentDownloadInfo | null {
|
||||
try {
|
||||
// Try to parse as JSON first (for pure JSON content)
|
||||
const parsed = JSON.parse(content);
|
||||
if (parsed && parsed.filename && parsed.base64 && parsed.mime_type) {
|
||||
// Accept PDF, DOCX, and TXT formats
|
||||
const validMimeTypes = [
|
||||
'application/pdf',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'text/plain',
|
||||
];
|
||||
if (validMimeTypes.includes(parsed.mime_type)) {
|
||||
return parsed as DocumentDownloadInfo;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// If direct parsing fails, try to extract JSON object from mixed content
|
||||
// Look for a JSON object that contains the required fields
|
||||
// This regex finds a balanced JSON object by counting braces
|
||||
const startPattern = /\{[^{}]*"filename"[^{}]*:/g;
|
||||
let match;
|
||||
|
||||
while ((match = startPattern.exec(content)) !== null) {
|
||||
const startIndex = match.index;
|
||||
let braceCount = 0;
|
||||
let endIndex = startIndex;
|
||||
|
||||
// Find the matching closing brace
|
||||
for (let i = startIndex; i < content.length; i++) {
|
||||
if (content[i] === '{') braceCount++;
|
||||
if (content[i] === '}') braceCount--;
|
||||
|
||||
if (braceCount === 0) {
|
||||
endIndex = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (endIndex > startIndex) {
|
||||
try {
|
||||
const jsonStr = content.substring(startIndex, endIndex);
|
||||
const parsed = JSON.parse(jsonStr);
|
||||
if (parsed && parsed.filename && parsed.base64 && parsed.mime_type) {
|
||||
// Accept PDF, DOCX, and TXT formats
|
||||
const validMimeTypes = [
|
||||
'application/pdf',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'text/plain',
|
||||
];
|
||||
if (validMimeTypes.includes(parsed.mime_type)) {
|
||||
return parsed as DocumentDownloadInfo;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// This wasn't valid JSON, continue searching
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Helper function to remove document download info from content
|
||||
export function removePDFDownloadInfo(
|
||||
content: string,
|
||||
downloadInfo: DocumentDownloadInfo,
|
||||
): string {
|
||||
try {
|
||||
// First, check if the entire content is just the JSON (most common case)
|
||||
try {
|
||||
const parsed = JSON.parse(content);
|
||||
if (
|
||||
parsed &&
|
||||
parsed.filename === downloadInfo.filename &&
|
||||
parsed.base64 === downloadInfo.base64
|
||||
) {
|
||||
// The entire content is just the download JSON, return empty
|
||||
return '';
|
||||
}
|
||||
} catch {
|
||||
// Content is not pure JSON, continue with removal
|
||||
}
|
||||
|
||||
// Try to remove the JSON string from content
|
||||
const jsonStr = JSON.stringify(downloadInfo);
|
||||
let cleaned = content.replace(jsonStr, '').trim();
|
||||
|
||||
// Also try with pretty-printed JSON (with indentation)
|
||||
const prettyJsonStr = JSON.stringify(downloadInfo, null, 2);
|
||||
cleaned = cleaned.replace(prettyJsonStr, '').trim();
|
||||
|
||||
// Also try to find and remove JSON object pattern from mixed content
|
||||
// This handles cases where the JSON might have different formatting
|
||||
const startPattern = /\{[^{}]*"filename"[^{}]*"base64"[^{}]*\}/g;
|
||||
cleaned = cleaned.replace(startPattern, '').trim();
|
||||
|
||||
return cleaned;
|
||||
} catch {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@ -101,6 +101,7 @@ export enum Operator {
|
||||
UserFillUp = 'UserFillUp',
|
||||
StringTransform = 'StringTransform',
|
||||
SearXNG = 'SearXNG',
|
||||
PDFGenerator = 'PDFGenerator',
|
||||
Placeholder = 'Placeholder',
|
||||
DataOperations = 'DataOperations',
|
||||
ListOperations = 'ListOperations',
|
||||
|
||||
@ -878,6 +878,27 @@ export default {
|
||||
searXNG: 'SearXNG',
|
||||
searXNGDescription:
|
||||
'Eine Komponente, die auf https://searxng.org/ sucht und Ihnen ermöglicht, die Anzahl der Suchergebnisse mit TopN anzugeben. Sie ergänzt die vorhandenen Wissensdatenbanken.',
|
||||
pdfGenerator: 'Dokumentengenerator',
|
||||
pDFGenerator: 'Dokumentengenerator',
|
||||
pdfGeneratorDescription: `Eine Komponente, die Dokumente (PDF, DOCX, TXT) aus markdown-formatierten Inhalten mit anpassbarem Stil, Bildern und Tabellen generiert. Unterstützt: **fett**, *kursiv*, # Überschriften, - Listen, Tabellen mit | Syntax.`,
|
||||
pDFGeneratorDescription: `Eine Komponente, die Dokumente (PDF, DOCX, TXT) aus markdown-formatierten Inhalten mit anpassbarem Stil, Bildern und Tabellen generiert. Unterstützt: **fett**, *kursiv*, # Überschriften, - Listen, Tabellen mit | Syntax.`,
|
||||
subtitle: 'Untertitel',
|
||||
logoImage: 'Logo-Bild',
|
||||
logoPosition: 'Logo-Position',
|
||||
logoWidth: 'Logo-Breite',
|
||||
logoHeight: 'Logo-Höhe',
|
||||
fontFamily: 'Schriftfamilie',
|
||||
fontSize: 'Schriftgröße',
|
||||
titleFontSize: 'Titel-Schriftgröße',
|
||||
pageSize: 'Seitengröße',
|
||||
orientation: 'Ausrichtung',
|
||||
marginTop: 'Oberer Rand',
|
||||
marginBottom: 'Unterer Rand',
|
||||
filename: 'Dateiname',
|
||||
outputDirectory: 'Ausgabeverzeichnis',
|
||||
addPageNumbers: 'Seitenzahlen hinzufügen',
|
||||
addTimestamp: 'Zeitstempel hinzufügen',
|
||||
watermarkText: 'Wasserzeichentext',
|
||||
channel: 'Kanal',
|
||||
channelTip:
|
||||
'Führt eine Textsuche oder Nachrichtensuche für die Eingabe der Komponente durch',
|
||||
|
||||
@ -1307,6 +1307,27 @@ Example: Virtual Hosted Style`,
|
||||
searXNG: 'SearXNG',
|
||||
searXNGDescription:
|
||||
'A component that searches via your provided SearXNG instance URL. Specify TopN and the instance URL.',
|
||||
pdfGenerator: 'Docs Generator',
|
||||
pDFGenerator: 'Docs Generator',
|
||||
pdfGeneratorDescription: `A component that generates documents (PDF, DOCX, TXT) from markdown-formatted content with customizable styling, images, and tables. Supports: **bold**, *italic*, # headings, - lists, tables with | syntax.`,
|
||||
pDFGeneratorDescription: `A component that generates documents (PDF, DOCX, TXT) from markdown-formatted content with customizable styling, images, and tables. Supports: **bold**, *italic*, # headings, - lists, tables with | syntax.`,
|
||||
subtitle: 'Subtitle',
|
||||
logoImage: 'Logo Image',
|
||||
logoPosition: 'Logo Position',
|
||||
logoWidth: 'Logo Width',
|
||||
logoHeight: 'Logo Height',
|
||||
fontFamily: 'Font Family',
|
||||
fontSize: 'Font Size',
|
||||
titleFontSize: 'Title Font Size',
|
||||
pageSize: 'Page Size',
|
||||
orientation: 'Orientation',
|
||||
marginTop: 'Margin Top',
|
||||
marginBottom: 'Margin Bottom',
|
||||
filename: 'Filename',
|
||||
outputDirectory: 'Output Directory',
|
||||
addPageNumbers: 'Add Page Numbers',
|
||||
addTimestamp: 'Add Timestamp',
|
||||
watermarkText: 'Watermark Text',
|
||||
channel: 'Channel',
|
||||
channelTip: `Perform text search or news search on the component's input`,
|
||||
text: 'Text',
|
||||
@ -1690,7 +1711,6 @@ This delimiter is used to split the input text into several text pieces echo of
|
||||
datatype: 'MINE type of the HTTP request',
|
||||
insertVariableTip: `Enter / Insert variables`,
|
||||
historyversion: 'Version history',
|
||||
filename: 'File name',
|
||||
version: {
|
||||
created: 'Created',
|
||||
details: 'Version details',
|
||||
|
||||
@ -578,15 +578,31 @@ export default {
|
||||
'Este componente se usa para obtener resultados de búsqueda de www.baidu.com. Típicamente, actúa como un complemento a las bases de conocimiento. Top N especifica el número de resultados de búsqueda que necesitas ajustar.',
|
||||
duckDuckGo: 'DuckDuckGo',
|
||||
duckDuckGoDescription:
|
||||
'Un componente que recupera resultados de búsqueda de duckduckgo.com, con TopN especificando el número de resultados de búsqueda. Complementa las bases de conocimiento existentes.',
|
||||
'Un componente que busca en duckduckgo.com, permitiéndote especificar el número de resultados de búsqueda usando TopN. Supplementa las bases de conocimiento existentes.',
|
||||
searXNG: 'SearXNG',
|
||||
searXNGDescription:
|
||||
'Un componente que realiza búsquedas mediante la URL de la instancia de SearXNG que usted proporcione. Especifique TopN y la URL de la instancia.',
|
||||
channel: 'Canal',
|
||||
channelTip:
|
||||
'Realizar búsqueda de texto o búsqueda de noticias en la entrada del componente.',
|
||||
text: 'Texto',
|
||||
news: 'Noticias',
|
||||
'Un componente que busca a través de la URL de la instancia SearXNG que proporcionas. Especifica TopN y la URL de la instancia.',
|
||||
pdfGenerator: 'Generador de Documentos',
|
||||
pDFGenerator: 'Generador de Documentos',
|
||||
pdfGeneratorDescription: `Un componente que genera documentos (PDF, DOCX, TXT) desde contenido formateado en markdown con estilo personalizable, imágenes y tablas. Soporta: **negrita**, *cursiva*, # encabezados, - listas, tablas con sintaxis |.`,
|
||||
pDFGeneratorDescription: `Un componente que genera documentos (PDF, DOCX, TXT) desde contenido formateado en markdown con estilo personalizable, imágenes y tablas. Soporta: **negrita**, *cursiva*, # encabezados, - listas, tablas con sintaxis |.`,
|
||||
subtitle: 'Subtítulo',
|
||||
logoImage: 'Imagen Logo',
|
||||
logoPosition: 'Posición Logo',
|
||||
logoWidth: 'Ancho Logo',
|
||||
logoHeight: 'Alto Logo',
|
||||
fontFamily: 'Familia Fuente',
|
||||
fontSize: 'Tamaño Fuente',
|
||||
titleFontSize: 'Tamaño Fuente Título',
|
||||
pageSize: 'Tamaño Página',
|
||||
orientation: 'Orientación',
|
||||
marginTop: 'Margen Superior',
|
||||
marginBottom: 'Margen Inferior',
|
||||
filename: 'Nombre Archivo',
|
||||
outputDirectory: 'Directorio Salida',
|
||||
addPageNumbers: 'Agregar Números Página',
|
||||
addTimestamp: 'Agregar Timestamp',
|
||||
watermarkText: 'Texto Marca Agua',
|
||||
messageHistoryWindowSize:
|
||||
'Tamaño de la ventana del historial de mensajes',
|
||||
messageHistoryWindowSizeTip:
|
||||
|
||||
@ -788,15 +788,31 @@ export default {
|
||||
'Un composant qui recherche sur baidu.com, utilisant TopN pour spécifier le nombre de résultats. Il complète les bases de connaissances existantes.',
|
||||
duckDuckGo: 'DuckDuckGo',
|
||||
duckDuckGoDescription:
|
||||
'Un composant qui recherche sur duckduckgo.com, vous permettant de spécifier le nombre de résultats avec TopN. Il complète les bases de connaissances existantes.',
|
||||
'Un composant qui recherche sur duckduckgo.com, vous permettant de spécifier le nombre de résultats de recherche avec TopN. Il complète les bases de connaissances existantes.',
|
||||
searXNG: 'SearXNG',
|
||||
searXNGDescription:
|
||||
"Un composant qui effectue des recherches via la URL de l'instance de SearXNG que vous fournissez. Spécifiez TopN et l'URL de l'instance.",
|
||||
channel: 'Canal',
|
||||
channelTip:
|
||||
"Effectuer une recherche de texte ou d'actualités sur l'entrée du composant",
|
||||
text: 'Texte',
|
||||
news: 'Actualités',
|
||||
pdfGenerator: 'Générateur de Documents',
|
||||
pDFGenerator: 'Générateur de Documents',
|
||||
pdfGeneratorDescription: `Un composant qui génère des documents (PDF, DOCX, TXT) à partir de contenu formaté en markdown avec un style personnalisable, des images et des tableaux. Prend en charge : **gras**, *italique*, # titres, - listes, tableaux avec syntaxe |.`,
|
||||
pDFGeneratorDescription: `Un composant qui génère des documents (PDF, DOCX, TXT) à partir de contenu formaté en markdown avec un style personnalisable, des images et des tableaux. Prend en charge : **gras**, *italique*, # titres, - listes, tableaux avec syntaxe |.`,
|
||||
subtitle: 'Sous-titre',
|
||||
logoImage: 'Image Logo',
|
||||
logoPosition: 'Position Logo',
|
||||
logoWidth: 'Largeur Logo',
|
||||
logoHeight: 'Hauteur Logo',
|
||||
fontFamily: 'Famille Police',
|
||||
fontSize: 'Taille Police',
|
||||
titleFontSize: 'Taille Police Titre',
|
||||
pageSize: 'Taille Page',
|
||||
orientation: 'Orientation',
|
||||
marginTop: 'Marge Supérieure',
|
||||
marginBottom: 'Marge Inférieure',
|
||||
filename: 'Nom Fichier',
|
||||
outputDirectory: 'Répertoire Sortie',
|
||||
addPageNumbers: 'Ajouter Numéros Page',
|
||||
addTimestamp: 'Ajouter Timestamp',
|
||||
watermarkText: 'Texte Filigrane',
|
||||
messageHistoryWindowSize:
|
||||
"Taille de la fenêtre d'historique des messages",
|
||||
messageHistoryWindowSizeTip:
|
||||
@ -1173,7 +1189,6 @@ export default {
|
||||
datatype: 'Type MIME de la requête HTTP',
|
||||
insertVariableTip: `Entrer / Insérer des variables`,
|
||||
historyversion: 'Historique des versions',
|
||||
filename: 'Nom du fichier',
|
||||
version: {
|
||||
created: 'Créé',
|
||||
details: 'Détails de la version',
|
||||
|
||||
@ -770,6 +770,27 @@ export default {
|
||||
searXNG: 'SearXNG',
|
||||
searXNGDescription:
|
||||
'Komponen yang melakukan pencarian menggunakan URL instance SearXNG yang Anda berikan. Spesifikasikan TopN dan URL instance.',
|
||||
pdfGenerator: 'Pembuat Dokumen',
|
||||
pDFGenerator: 'Pembuat Dokumen',
|
||||
pdfGeneratorDescription: `Komponen yang menghasilkan dokumen (PDF, DOCX, TXT) dari konten berformat markdown dengan gaya yang dapat disesuaikan, gambar, dan tabel. Mendukung: **tebal**, *miring*, # judul, - daftar, tabel dengan sintaks |.`,
|
||||
pDFGeneratorDescription: `Komponen yang menghasilkan dokumen (PDF, DOCX, TXT) dari konten berformat markdown dengan gaya yang dapat disesuaikan, gambar, dan tabel. Mendukung: **tebal**, *miring*, # judul, - daftar, tabel dengan sintaks |.`,
|
||||
subtitle: 'Subjudul',
|
||||
logoImage: 'Gambar Logo',
|
||||
logoPosition: 'Posisi Logo',
|
||||
logoWidth: 'Lebar Logo',
|
||||
logoHeight: 'Tinggi Logo',
|
||||
fontFamily: 'Keluarga Font',
|
||||
fontSize: 'Ukuran Font',
|
||||
titleFontSize: 'Ukuran Font Judul',
|
||||
pageSize: 'Ukuran Halaman',
|
||||
orientation: 'Orientasi',
|
||||
marginTop: 'Margin Atas',
|
||||
marginBottom: 'Margin Bawah',
|
||||
filename: 'Nama File',
|
||||
outputDirectory: 'Direktori Output',
|
||||
addPageNumbers: 'Tambahkan Nomor Halaman',
|
||||
addTimestamp: 'Tambahkan Timestamp',
|
||||
watermarkText: 'Teks Watermark',
|
||||
channel: 'Saluran',
|
||||
channelTip: `Lakukan pencarian teks atau pencarian berita pada input komponen`,
|
||||
text: 'Teks',
|
||||
|
||||
@ -930,6 +930,30 @@ Quanto sopra è il contenuto che devi riassumere.`,
|
||||
duckDuckGo: 'DuckDuckGo',
|
||||
duckDuckGoDescription:
|
||||
'Un componente che cerca da duckduckgo.com, permettendo di specificare il numero di risultati di ricerca usando TopN.',
|
||||
searXNG: 'SearXNG',
|
||||
searXNGDescription:
|
||||
'Un componente che cerca tramite lURL dellistanza SearXNG fornita. Specifica TopN e lURL dellistanza.',
|
||||
pdfGenerator: 'Generatore Documenti',
|
||||
pDFGenerator: 'Generatore Documenti',
|
||||
pdfGeneratorDescription: `Un componente che genera documenti (PDF, DOCX, TXT) da contenuti formattati in markdown con stile personalizzabile, immagini e tabelle. Supporta: **grassetto**, *corsivo*, # titoli, - elenchi, tabelle con sintassi |.`,
|
||||
pDFGeneratorDescription: `Un componente che genera documenti (PDF, DOCX, TXT) da contenuti formattati in markdown con stile personalizzabile, immagini e tabelle. Supporta: **grassetto**, *corsivo*, # titoli, - elenchi, tabelle con sintassi |.`,
|
||||
subtitle: 'Sottotitolo',
|
||||
logoImage: 'Immagine Logo',
|
||||
logoPosition: 'Posizione Logo',
|
||||
logoWidth: 'Larghezza Logo',
|
||||
logoHeight: 'Altezza Logo',
|
||||
fontFamily: 'Famiglia Font',
|
||||
fontSize: 'Dimensione Font',
|
||||
titleFontSize: 'Dimensione Font Titolo',
|
||||
pageSize: 'Dimensione Pagina',
|
||||
orientation: 'Orientamento',
|
||||
marginTop: 'Margine Superiore',
|
||||
marginBottom: 'Margine Inferiore',
|
||||
filename: 'Nome File',
|
||||
outputDirectory: 'Directory Output',
|
||||
addPageNumbers: 'Aggiungi Numeri Pagina',
|
||||
addTimestamp: 'Aggiungi Timestamp',
|
||||
watermarkText: 'Testo Filigrana',
|
||||
channel: 'Canale',
|
||||
channelTip: `Esegui ricerca testo o notizie sull'input del componente`,
|
||||
text: 'Testo',
|
||||
|
||||
@ -795,11 +795,27 @@ export default {
|
||||
searXNG: 'SearXNG',
|
||||
searXNGDescription:
|
||||
'SearXNGのインスタンスURLを提供して検索を行うコンポーネント。TopNとインスタンスURLを指定してください。',
|
||||
channel: 'チャンネル',
|
||||
channelTip: `コンポーネントの入力に対してテキスト検索またはニュース検索を実行します`,
|
||||
text: 'テキスト',
|
||||
news: 'ニュース',
|
||||
messageHistoryWindowSize: 'メッセージウィンドウサイズ',
|
||||
pdfGenerator: 'ドキュメント生成',
|
||||
pDFGenerator: 'ドキュメント生成',
|
||||
pdfGeneratorDescription: `マークダウン形式のコンテンツからドキュメント(PDF、DOCX、TXT)を生成するコンポーネント。カスタムスタイル、画像、テーブルをサポート。サポート:**太字**、*斜体*、# 見出し、- リスト、| 構文のテーブル。`,
|
||||
pDFGeneratorDescription: `マークダウン形式のコンテンツからドキュメント(PDF、DOCX、TXT)を生成するコンポーネント。カスタムスタイル、画像、テーブルをサポート。サポート:**太字**、*斜体*、# 見出し、- リスト、| 構文のテーブル。`,
|
||||
subtitle: 'サブタイトル',
|
||||
logoImage: 'ロゴ画像',
|
||||
logoPosition: 'ロゴ位置',
|
||||
logoWidth: 'ロゴ幅',
|
||||
logoHeight: 'ロゴ高さ',
|
||||
fontFamily: 'フォントファミリー',
|
||||
fontSize: 'フォントサイズ',
|
||||
titleFontSize: 'タイトルフォントサイズ',
|
||||
pageSize: 'ページサイズ',
|
||||
orientation: '向き',
|
||||
marginTop: '上余白',
|
||||
marginBottom: '下余白',
|
||||
filename: 'ファイル名',
|
||||
outputDirectory: '出力ディレクトリ',
|
||||
addPageNumbers: 'ページ番号を追加',
|
||||
addTimestamp: 'タイムスタンプを追加',
|
||||
watermarkText: '透かしテキスト',
|
||||
messageHistoryWindowSizeTip:
|
||||
'LLMに表示される会話履歴のウィンドウサイズ。大きいほど良いですが、LLMの最大トークン制限に注意してください。',
|
||||
wikipedia: 'Wikipedia',
|
||||
|
||||
@ -737,11 +737,27 @@ export default {
|
||||
searXNG: 'SearXNG',
|
||||
searXNGDescription:
|
||||
'Um componente que realiza buscas via URL da instância SearXNG que você fornece. Especifique TopN e URL da instância.',
|
||||
channel: 'Canal',
|
||||
channelTip: `Realize uma busca por texto ou por notícias na entrada do componente`,
|
||||
text: 'Texto',
|
||||
news: 'Notícias',
|
||||
messageHistoryWindowSize: 'Tamanho da janela de mensagens',
|
||||
pdfGenerator: 'Gerador de Documentos',
|
||||
pDFGenerator: 'Gerador de Documentos',
|
||||
pdfGeneratorDescription: `Um componente que gera documentos (PDF, DOCX, TXT) de conteúdo formatado em markdown com estilo personalizável, imagens e tabelas. Suporta: **negrito**, *itálico*, # títulos, - listas, tabelas com sintaxe |.`,
|
||||
pDFGeneratorDescription: `Um componente que gera documentos (PDF, DOCX, TXT) de conteúdo formatado em markdown com estilo personalizável, imagens e tabelas. Suporta: **negrito**, *itálico*, # títulos, - listas, tabelas com sintaxe |.`,
|
||||
subtitle: 'Subtítulo',
|
||||
logoImage: 'Imagem Logo',
|
||||
logoPosition: 'Posição Logo',
|
||||
logoWidth: 'Largura Logo',
|
||||
logoHeight: 'Altura Logo',
|
||||
fontFamily: 'Família Fonte',
|
||||
fontSize: 'Tamanho Fonte',
|
||||
titleFontSize: 'Tamanho Fonte Título',
|
||||
pageSize: 'Tamanho Página',
|
||||
orientation: 'Orientação',
|
||||
marginTop: 'Margem Superior',
|
||||
marginBottom: 'Margem Inferior',
|
||||
filename: 'Nome Arquivo',
|
||||
outputDirectory: 'Diretório Saída',
|
||||
addPageNumbers: 'Adicionar Números Página',
|
||||
addTimestamp: 'Adicionar Timestamp',
|
||||
watermarkText: 'Texto Marca Dágua',
|
||||
messageHistoryWindowSizeTip:
|
||||
'O tamanho da janela do histórico de conversa visível para o LLM. Quanto maior, melhor, mas fique atento ao limite máximo de tokens do LLM.',
|
||||
wikipedia: 'Wikipedia',
|
||||
|
||||
@ -1223,6 +1223,27 @@ export default {
|
||||
searXNG: 'SearXNG',
|
||||
searXNGDescription:
|
||||
'Компонент, который выполняет поиск через ваш предоставленный URL экземпляра SearXNG. Укажите TopN и URL экземпляра.',
|
||||
pdfGenerator: 'Генератор документов',
|
||||
pDFGenerator: 'Генератор документов',
|
||||
pdfGeneratorDescription: `Компонент, который генерирует документы (PDF, DOCX, TXT) из содержимого в формате markdown с настраиваемым стилем, изображениями и таблицами. Поддерживает: **жирный**, *курсив*, # заголовки, - списки, таблицы с синтаксисом |.`,
|
||||
pDFGeneratorDescription: `Компонент, который генерирует документы (PDF, DOCX, TXT) из содержимого в формате markdown с настраиваемым стилем, изображениями и таблицами. Поддерживает: **жирный**, *курсив*, # заголовки, - списки, таблицы с синтаксисом |.`,
|
||||
subtitle: 'Подзаголовок',
|
||||
logoImage: 'Изображение логотипа',
|
||||
logoPosition: 'Позиция логотипа',
|
||||
logoWidth: 'Ширина логотипа',
|
||||
logoHeight: 'Высота логотипа',
|
||||
fontFamily: 'Семейство шрифтов',
|
||||
fontSize: 'Размер шрифта',
|
||||
titleFontSize: 'Размер шрифта заголовка',
|
||||
pageSize: 'Размер страницы',
|
||||
orientation: 'Ориентация',
|
||||
marginTop: 'Верхний отступ',
|
||||
marginBottom: 'Нижний отступ',
|
||||
filename: 'Имя файла',
|
||||
outputDirectory: 'Выходной каталог',
|
||||
addPageNumbers: 'Добавить номера страниц',
|
||||
addTimestamp: 'Добавить временную метку',
|
||||
watermarkText: 'Текст водяного знака',
|
||||
channel: 'Канал',
|
||||
channelTip: `Выполняет текстовый поиск или поиск новостей на входе компонента`,
|
||||
text: 'Текст',
|
||||
@ -1604,7 +1625,6 @@ export default {
|
||||
datatype: 'MIME тип HTTP запроса',
|
||||
insertVariableTip: `Введите / Вставьте переменные`,
|
||||
historyversion: 'История версий',
|
||||
filename: 'Имя файла',
|
||||
version: {
|
||||
created: 'Создано',
|
||||
details: 'Детали версии',
|
||||
|
||||
@ -821,15 +821,31 @@ export default {
|
||||
baiduDescription: `Thành phần này được sử dụng để lấy kết quả tìm kiếm từ www.baidu.com. Thông thường, nó hoạt động như một phần bổ sung cho các cơ sở kiến thức. Top N chỉ định số lượng kết quả tìm kiếm bạn cần điều chỉnh.`,
|
||||
duckDuckGo: 'DuckDuckGo',
|
||||
duckDuckGoDescription:
|
||||
'Một thành phần truy xuất kết quả tìm kiếm từ duckduckgo.com, với TopN xác định số lượng kết quả tìm kiếm. Nó bổ sung cho các cơ sở kiến thức hiện có.',
|
||||
'Một thành phần tìm kiếm trên duckduckgo.com, cho phép bạn chỉ định số lượng kết quả tìm kiếm sử dụng TopN. Nó bổ sung cho các cơ sở kiến thức hiện có.',
|
||||
searXNG: 'SearXNG',
|
||||
searXNGDescription:
|
||||
'Một thành phần truy xuất kết quả tìm kiếm từ searxng.com, với TopN xác định số lượng kết quả tìm kiếm. Nó bổ sung cho các cơ sở kiến thức hiện có.',
|
||||
channel: 'Kênh',
|
||||
channelTip: `Thực hiện tìm kiếm văn bản hoặc tìm kiếm tin tức trên đầu vào của thành phần`,
|
||||
text: 'Văn bản',
|
||||
news: 'Tin tức',
|
||||
messageHistoryWindowSize: 'Cửa sổ lịch sử tin nhắn',
|
||||
'Một thành phần tìm kiếm thông qua URL phiên bản SearXNG bạn cung cấp. Chỉ định TopN và URL phiên bản.',
|
||||
pdfGenerator: 'Trình tạo Tài liệu',
|
||||
pDFGenerator: 'Trình tạo Tài liệu',
|
||||
pdfGeneratorDescription: `Một thành phần tạo tài liệu (PDF, DOCX, TXT) từ nội dung định dạng markdown với kiểu tùy chỉnh, hình ảnh và bảng. Hỗ trợ: **in đậm**, *in nghiêng*, # tiêu đề, - danh sách, bảng với cú pháp |.`,
|
||||
pDFGeneratorDescription: `Một thành phần tạo tài liệu (PDF, DOCX, TXT) từ nội dung định dạng markdown với kiểu tùy chỉnh, hình ảnh và bảng. Hỗ trợ: **in đậm**, *in nghiêng*, # tiêu đề, - danh sách, bảng với cú pháp |.`,
|
||||
subtitle: 'Phụ đề',
|
||||
logoImage: 'Hình ảnh Logo',
|
||||
logoPosition: 'Vị trí Logo',
|
||||
logoWidth: 'Chiều rộng Logo',
|
||||
logoHeight: 'Chiều cao Logo',
|
||||
fontFamily: 'Họ phông chữ',
|
||||
fontSize: 'Kích thước phông chữ',
|
||||
titleFontSize: 'Kích thước phông chữ tiêu đề',
|
||||
pageSize: 'Kích thước trang',
|
||||
orientation: 'Hướng',
|
||||
marginTop: 'Lề trên',
|
||||
marginBottom: 'Lề dưới',
|
||||
filename: 'Tên tệp',
|
||||
outputDirectory: 'Thư mục đầu ra',
|
||||
addPageNumbers: 'Thêm số trang',
|
||||
addTimestamp: 'Thêm dấu thời gian',
|
||||
watermarkText: 'Văn bản watermark',
|
||||
messageHistoryWindowSizeTip:
|
||||
'Kích thước cửa sổ lịch sử cuộc trò chuyện hiển thị với LLM. Càng lớn càng tốt, nhưng hãy chú ý đến giới hạn tối đa số token của LLM.',
|
||||
wikipedia: 'Wikipedia',
|
||||
|
||||
@ -849,15 +849,31 @@ export default {
|
||||
baiduDescription: `此組件用於取得www.baidu.com的搜尋結果,一般作為知識庫的補充,Top N指定需要採納的搜尋結果數。`,
|
||||
duckDuckGo: 'DuckDuckGo',
|
||||
duckDuckGoDescription:
|
||||
'此元件用於從 www.duckduckgo.com 取得搜尋結果。通常,它作為知識庫的補充。 Top N 指定您需要採用的搜尋結果數。',
|
||||
'此組件用於從 www.duckduckgo.com 取得搜尋結果,通常充當知識庫的補充。Top N 指定搜尋結果的數量。',
|
||||
searXNG: 'SearXNG',
|
||||
searXNGDescription:
|
||||
'該組件通過您提供的 SearXNG 實例地址進行搜索。請設置 Top N 和實例 URL。',
|
||||
channel: '頻道',
|
||||
channelTip: '針對該組件的輸入進行文字搜尋或新聞搜索',
|
||||
text: '文字',
|
||||
news: '新聞',
|
||||
messageHistoryWindowSize: '歷史訊息視窗大小',
|
||||
'此組件透過您提供的 SearXNG 實例 URL 進行搜尋。請設定 Top N 和實例 URL。',
|
||||
pdfGenerator: '文檔生成器',
|
||||
pPDFGenerator: '文檔生成器',
|
||||
pdfGeneratorDescription: `該組件從 markdown 格式的內容生成文檔(PDF、DOCX、TXT),支援自定義樣式、圖片和表格。支援:**粗體**、*斜體*、# 標題、- 列表、使用 | 語法的表格。`,
|
||||
pPDFGeneratorDescription: `該組件從 markdown 格式的內容生成文檔(PDF、DOCX、TXT),支援自定義樣式、圖片和表格。支援:**粗體**、*斜體*、# 標題、- 列表、使用 | 語法的表格。`,
|
||||
subtitle: '副標題',
|
||||
logoImage: '標誌圖片',
|
||||
logoPosition: '標誌位置',
|
||||
logoWidth: '標誌寬度',
|
||||
logoHeight: '標誌高度',
|
||||
fontFamily: '字體系列',
|
||||
fontSize: '字體大小',
|
||||
titleFontSize: '標題字體大小',
|
||||
pageSize: '頁面大小',
|
||||
orientation: '方向',
|
||||
marginTop: '上邊距',
|
||||
marginBottom: '下邊距',
|
||||
filename: '檔名',
|
||||
outputDirectory: '輸出目錄',
|
||||
addPageNumbers: '添加頁碼',
|
||||
addTimestamp: '添加時間戳',
|
||||
watermarkText: '浮水印文字',
|
||||
messageHistoryWindowSizeTip:
|
||||
'LLM 需要查看的對話歷史視窗大小。越大越好,但要注意 LLM 的最大 Token 數。',
|
||||
wikipedia: '維基百科',
|
||||
|
||||
@ -1187,6 +1187,27 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
searXNG: 'SearXNG',
|
||||
searXNGDescription:
|
||||
'该组件通过您提供的 SearXNG 实例地址进行搜索。请设置 Top N 和实例 URL。',
|
||||
pdfGenerator: '文档生成器',
|
||||
pDFGenerator: '文档生成器',
|
||||
pdfGeneratorDescription: `该组件从 markdown 格式的内容生成文档(PDF、DOCX、TXT),支持自定义样式、图片和表格。支持:**粗体**、*斜体*、# 标题、- 列表、使用 | 语法的表格。`,
|
||||
pDFGeneratorDescription: `该组件从 markdown 格式的内容生成文档(PDF、DOCX、TXT),支持自定义样式、图片和表格。支持:**粗体**、*斜体*、# 标题、- 列表、使用 | 语法的表格。`,
|
||||
subtitle: '副标题',
|
||||
logoImage: '标志图片',
|
||||
logoPosition: '标志位置',
|
||||
logoWidth: '标志宽度',
|
||||
logoHeight: '标志高度',
|
||||
fontFamily: '字体系列',
|
||||
fontSize: '字体大小',
|
||||
titleFontSize: '标题字体大小',
|
||||
pageSize: '页面大小',
|
||||
orientation: '方向',
|
||||
marginTop: '上边距',
|
||||
marginBottom: '下边距',
|
||||
filename: '文件名',
|
||||
outputDirectory: '输出目录',
|
||||
addPageNumbers: '添加页码',
|
||||
addTimestamp: '添加时间戳',
|
||||
watermarkText: '水印文本',
|
||||
channel: '频道',
|
||||
channelTip: '针对该组件的输入进行文本搜索或新闻搜索',
|
||||
text: '文本',
|
||||
|
||||
@ -122,6 +122,7 @@ export function AccordionOperators({
|
||||
Operator.Invoke,
|
||||
Operator.WenCai,
|
||||
Operator.SearXNG,
|
||||
Operator.PDFGenerator,
|
||||
]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
|
||||
@ -932,6 +932,71 @@ export enum AgentVariableType {
|
||||
Conversation = 'conversation',
|
||||
}
|
||||
|
||||
// PDF Generator enums
|
||||
export enum PDFGeneratorFontFamily {
|
||||
Helvetica = 'Helvetica',
|
||||
TimesRoman = 'Times-Roman',
|
||||
Courier = 'Courier',
|
||||
HelveticaBold = 'Helvetica-Bold',
|
||||
TimesBold = 'Times-Bold',
|
||||
}
|
||||
|
||||
export enum PDFGeneratorLogoPosition {
|
||||
Left = 'left',
|
||||
Center = 'center',
|
||||
Right = 'right',
|
||||
}
|
||||
|
||||
export enum PDFGeneratorPageSize {
|
||||
A4 = 'A4',
|
||||
Letter = 'Letter',
|
||||
}
|
||||
|
||||
export enum PDFGeneratorOrientation {
|
||||
Portrait = 'portrait',
|
||||
Landscape = 'landscape',
|
||||
}
|
||||
|
||||
export const initialPDFGeneratorValues = {
|
||||
output_format: 'pdf',
|
||||
content: '',
|
||||
title: '',
|
||||
subtitle: '',
|
||||
header_text: '',
|
||||
footer_text: '',
|
||||
logo_image: '',
|
||||
logo_position: PDFGeneratorLogoPosition.Left,
|
||||
logo_width: 2.0,
|
||||
logo_height: 1.0,
|
||||
font_family: PDFGeneratorFontFamily.Helvetica,
|
||||
font_size: 12,
|
||||
title_font_size: 24,
|
||||
heading1_font_size: 18,
|
||||
heading2_font_size: 16,
|
||||
heading3_font_size: 14,
|
||||
text_color: '#000000',
|
||||
title_color: '#000000',
|
||||
page_size: PDFGeneratorPageSize.A4,
|
||||
orientation: PDFGeneratorOrientation.Portrait,
|
||||
margin_top: 1.0,
|
||||
margin_bottom: 1.0,
|
||||
margin_left: 1.0,
|
||||
margin_right: 1.0,
|
||||
line_spacing: 1.2,
|
||||
filename: '',
|
||||
output_directory: '/tmp/pdf_outputs',
|
||||
add_page_numbers: true,
|
||||
add_timestamp: true,
|
||||
watermark_text: '',
|
||||
enable_toc: false,
|
||||
outputs: {
|
||||
file_path: { type: 'string', value: '' },
|
||||
pdf_base64: { type: 'string', value: '' },
|
||||
download: { type: 'string', value: '' },
|
||||
success: { type: 'boolean', value: false },
|
||||
},
|
||||
};
|
||||
|
||||
export enum WebhookMethod {
|
||||
Post = 'POST',
|
||||
Get = 'GET',
|
||||
|
||||
@ -22,6 +22,7 @@ import ListOperationsForm from '../form/list-operations-form';
|
||||
import LoopForm from '../form/loop-form';
|
||||
import MessageForm from '../form/message-form';
|
||||
import ParserForm from '../form/parser-form';
|
||||
import PDFGeneratorForm from '../form/pdf-generator-form';
|
||||
import PubMedForm from '../form/pubmed-form';
|
||||
import RetrievalForm from '../form/retrieval-form/next';
|
||||
import RewriteQuestionForm from '../form/rewrite-question-form';
|
||||
@ -110,6 +111,9 @@ export const FormConfigMap = {
|
||||
[Operator.SearXNG]: {
|
||||
component: SearXNGForm,
|
||||
},
|
||||
[Operator.PDFGenerator]: {
|
||||
component: PDFGeneratorForm,
|
||||
},
|
||||
[Operator.Note]: {
|
||||
component: () => <></>,
|
||||
},
|
||||
|
||||
535
web/src/pages/agent/form/pdf-generator-form/index.tsx
Normal file
535
web/src/pages/agent/form/pdf-generator-form/index.tsx
Normal file
@ -0,0 +1,535 @@
|
||||
import { FormContainer } from '@/components/form-container';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { t } from 'i18next';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
PDFGeneratorFontFamily,
|
||||
PDFGeneratorLogoPosition,
|
||||
PDFGeneratorOrientation,
|
||||
PDFGeneratorPageSize,
|
||||
} from '../../constant';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { FormWrapper } from '../components/form-wrapper';
|
||||
import { Output, transferOutputs } from '../components/output';
|
||||
import { PromptEditor } from '../components/prompt-editor';
|
||||
import { useValues } from './use-values';
|
||||
import { useWatchFormChange } from './use-watch-form-change';
|
||||
|
||||
function PDFGeneratorForm({ node }: INextOperatorForm) {
|
||||
const values = useValues(node);
|
||||
|
||||
const FormSchema = z.object({
|
||||
output_format: z.string().default('pdf'),
|
||||
content: z.string().min(1, 'Content is required'),
|
||||
title: z.string().optional(),
|
||||
subtitle: z.string().optional(),
|
||||
header_text: z.string().optional(),
|
||||
footer_text: z.string().optional(),
|
||||
logo_image: z.string().optional(),
|
||||
logo_position: z.string(),
|
||||
logo_width: z.number(),
|
||||
logo_height: z.number(),
|
||||
font_family: z.string(),
|
||||
font_size: z.number(),
|
||||
title_font_size: z.number(),
|
||||
heading1_font_size: z.number(),
|
||||
heading2_font_size: z.number(),
|
||||
heading3_font_size: z.number(),
|
||||
text_color: z.string(),
|
||||
title_color: z.string(),
|
||||
page_size: z.string(),
|
||||
orientation: z.string(),
|
||||
margin_top: z.number(),
|
||||
margin_bottom: z.number(),
|
||||
margin_left: z.number(),
|
||||
margin_right: z.number(),
|
||||
line_spacing: z.number(),
|
||||
filename: z.string().optional(),
|
||||
output_directory: z.string(),
|
||||
add_page_numbers: z.boolean(),
|
||||
add_timestamp: z.boolean(),
|
||||
watermark_text: z.string().optional(),
|
||||
enable_toc: z.boolean(),
|
||||
outputs: z
|
||||
.object({
|
||||
file_path: z.object({ type: z.string() }),
|
||||
pdf_base64: z.object({ type: z.string() }),
|
||||
success: z.object({ type: z.string() }),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
defaultValues: values,
|
||||
resolver: zodResolver(FormSchema),
|
||||
});
|
||||
|
||||
const outputList = useMemo(() => {
|
||||
return transferOutputs(values.outputs);
|
||||
}, [values.outputs]);
|
||||
|
||||
useWatchFormChange(node?.id, form);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<FormWrapper>
|
||||
<FormContainer>
|
||||
{/* Output Format Selection */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="output_format"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Output Format</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={[
|
||||
{ label: 'PDF', value: 'pdf' },
|
||||
{ label: 'DOCX', value: 'docx' },
|
||||
{ label: 'TXT', value: 'txt' },
|
||||
]}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Choose the output document format
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Content Section */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="content"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.content')}</FormLabel>
|
||||
<FormControl>
|
||||
<PromptEditor
|
||||
{...field}
|
||||
showToolbar={true}
|
||||
placeholder="Enter content with markdown formatting... **Bold text**, *italic*, # Heading, - List items, etc."
|
||||
></PromptEditor>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
<div className="text-xs space-y-1">
|
||||
<div>
|
||||
<strong>Markdown support:</strong> **bold**, *italic*,
|
||||
`code`, # Heading 1, ## Heading 2
|
||||
</div>
|
||||
<div>
|
||||
<strong>Lists:</strong> - bullet or 1. numbered
|
||||
</div>
|
||||
<div>
|
||||
<strong>Tables:</strong> | Column 1 | Column 2 | (use | to
|
||||
separate columns, <br> or \n for line breaks in
|
||||
cells)
|
||||
</div>
|
||||
<div>
|
||||
<strong>Other:</strong> --- for horizontal line, ``` for
|
||||
code blocks
|
||||
</div>
|
||||
</div>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Title & Subtitle */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="title"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.title')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Document title (optional)" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="subtitle"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.subtitle')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="Document subtitle (optional)"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Logo Settings */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="logo_image"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.logoImage')}</FormLabel>
|
||||
<FormControl>
|
||||
<div className="space-y-2">
|
||||
<Input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={(e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
field.onChange(reader.result as string);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="Or paste image path/URL/base64"
|
||||
className="mt-2"
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Upload an image file or paste a file path/URL/base64
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="logo_position"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.logoPosition')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={Object.values(PDFGeneratorLogoPosition).map(
|
||||
(val) => ({ label: val, value: val }),
|
||||
)}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="logo_width"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.logoWidth')} (inches)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
step="0.1"
|
||||
onChange={(e) =>
|
||||
field.onChange(parseFloat(e.target.value))
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="logo_height"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.logoHeight')} (inches)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
step="0.1"
|
||||
onChange={(e) =>
|
||||
field.onChange(parseFloat(e.target.value))
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Font Settings */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="font_family"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.fontFamily')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={Object.values(PDFGeneratorFontFamily).map(
|
||||
(val) => ({ label: val, value: val }),
|
||||
)}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="font_size"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.fontSize')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
onChange={(e) => field.onChange(parseInt(e.target.value))}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="title_font_size"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.titleFontSize')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
onChange={(e) => field.onChange(parseInt(e.target.value))}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Page Settings */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="page_size"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.pageSize')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={Object.values(PDFGeneratorPageSize).map((val) => ({
|
||||
label: val,
|
||||
value: val,
|
||||
}))}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="orientation"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.orientation')}</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
{...field}
|
||||
options={Object.values(PDFGeneratorOrientation).map(
|
||||
(val) => ({ label: val, value: val }),
|
||||
)}
|
||||
></RAGFlowSelect>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Margins */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="margin_top"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.marginTop')} (inches)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
step="0.1"
|
||||
onChange={(e) =>
|
||||
field.onChange(parseFloat(e.target.value))
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="margin_bottom"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.marginBottom')} (inches)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="number"
|
||||
step="0.1"
|
||||
onChange={(e) =>
|
||||
field.onChange(parseFloat(e.target.value))
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Output Settings */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="filename"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.filename')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="document.pdf (auto-generated if empty)"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="output_directory"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.outputDirectory')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="/tmp/pdf_outputs" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Additional Options */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="add_page_numbers"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>{t('flow.addPageNumbers')}</FormLabel>
|
||||
<FormDescription>
|
||||
Add page numbers to the document
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="add_timestamp"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel>{t('flow.addTimestamp')}</FormLabel>
|
||||
<FormDescription>
|
||||
Add generation timestamp to the document
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="watermark_text"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.watermarkText')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="Watermark text (optional)" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="outputs"
|
||||
render={() => <div></div>}
|
||||
/>
|
||||
</FormContainer>
|
||||
</FormWrapper>
|
||||
<div className="p-5">
|
||||
<Output list={outputList}></Output>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(PDFGeneratorForm);
|
||||
11
web/src/pages/agent/form/pdf-generator-form/use-values.ts
Normal file
11
web/src/pages/agent/form/pdf-generator-form/use-values.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Node } from 'reactflow';
|
||||
import { initialPDFGeneratorValues } from '../../constant';
|
||||
|
||||
export const useValues = (node?: Node) => {
|
||||
const values = useMemo(() => {
|
||||
return node?.data.form ?? initialPDFGeneratorValues;
|
||||
}, [node?.data.form]);
|
||||
|
||||
return values;
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
import { useEffect } from 'react';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import useGraphStore from '../../store';
|
||||
|
||||
export const useWatchFormChange = (
|
||||
nodeId: string | undefined,
|
||||
form: UseFormReturn<any>,
|
||||
) => {
|
||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = form.watch((value) => {
|
||||
if (nodeId) {
|
||||
updateNodeForm(nodeId, value);
|
||||
}
|
||||
});
|
||||
return () => unsubscribe();
|
||||
}, [form, nodeId, updateNodeForm]);
|
||||
};
|
||||
@ -16,6 +16,7 @@ import { IconFontFill } from '@/components/icon-font';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
FileCode,
|
||||
FileText,
|
||||
HousePlus,
|
||||
Infinity as InfinityIcon,
|
||||
LogOut,
|
||||
@ -67,6 +68,7 @@ export const LucideIconMap = {
|
||||
[Operator.DataOperations]: FileCode,
|
||||
[Operator.Loop]: InfinityIcon,
|
||||
[Operator.ExitLoop]: LogOut,
|
||||
[Operator.PDFGenerator]: FileText,
|
||||
};
|
||||
|
||||
const Empty = () => {
|
||||
|
||||
@ -7,11 +7,15 @@ export function useSelectFilters() {
|
||||
const { data } = useFetchAgentList({});
|
||||
|
||||
const canvasCategory = useMemo(() => {
|
||||
return groupListByType(data.canvas, 'canvas_category', 'canvas_category');
|
||||
}, [data.canvas]);
|
||||
return groupListByType(
|
||||
data?.canvas ?? [],
|
||||
'canvas_category',
|
||||
'canvas_category',
|
||||
);
|
||||
}, [data?.canvas]);
|
||||
|
||||
const filters: FilterCollection[] = [
|
||||
buildOwnersFilter(data.canvas),
|
||||
buildOwnersFilter(data?.canvas ?? []),
|
||||
{
|
||||
field: 'canvasCategory',
|
||||
list: canvasCategory,
|
||||
|
||||
Reference in New Issue
Block a user