fix(docs-generator): Add multi-language font support and reportlab dependency

- Add reportlab>=4.4.1 to dependencies (pyproject.toml)
- Add fonts-freefont-ttf and fonts-noto-cjk to Dockerfile for Unicode support
- Implement automatic CJK font detection and switching:
  - Latin content uses user-selected font (Helvetica, Times-Roman, Courier)
  - CJK/Arabic/Hebrew/Thai/Hindi content auto-switches to STSong-Light CID font
- Add documentation about automatic font switching behavior
- Fix frontend null safety in use-selelct-filters.ts
- Add DocsGenerator component to agent constants

Fixes missing glyphs (black squares) issue for Chinese, Japanese, Korean,
Arabic, Hebrew, Thai, and Hindi characters in generated PDFs.
This commit is contained in:
PentaFrame-Development 2025-12-11 11:29:22 +01:00
parent a7b49c0c45
commit fb3fedbbcf
7 changed files with 271 additions and 50 deletions

View file

@ -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 \

View file

@ -3,6 +3,7 @@ import json
import os
import re
import base64
import unicodedata
from datetime import datetime
from abc import ABC
from io import BytesIO
@ -15,6 +16,9 @@ from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, Table, TableStyle, PageBreak, LongTable
from reportlab.lib import colors
from reportlab.pdfgen import canvas as pdf_canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase.cidfonts import UnicodeCIDFont, CIDFont
from jinja2 import Template as Jinja2Template
from agent.component.base import ComponentParamBase
@ -97,6 +101,145 @@ class PDFGeneratorParam(ComponentParamBase):
class PDFGenerator(Message, ABC):
component_name = "PDFGenerator"
# Track if Unicode fonts have been registered
_unicode_fonts_registered = False
_unicode_font_name = None
_unicode_font_bold_name = None
@classmethod
def _reset_font_cache(cls):
"""Reset font registration cache - useful for testing"""
cls._unicode_fonts_registered = False
cls._unicode_font_name = None
cls._unicode_font_bold_name = None
@classmethod
def _register_unicode_fonts(cls):
"""Register Unicode-compatible fonts for multi-language support.
Uses CID fonts (STSong-Light) for reliable CJK rendering as TTF fonts
have issues with glyph mapping in some ReportLab versions.
"""
# If already registered successfully, return True
if cls._unicode_fonts_registered and cls._unicode_font_name is not None:
return True
# Reset and try again if previous registration failed
cls._unicode_fonts_registered = True
cls._unicode_font_name = None
cls._unicode_font_bold_name = None
# Use CID fonts for reliable CJK support
# These are built into ReportLab and work reliably across all platforms
cid_fonts = [
'STSong-Light', # Simplified Chinese
'HeiseiMin-W3', # Japanese
'HYSMyeongJo-Medium', # Korean
]
for cid_font in cid_fonts:
try:
pdfmetrics.registerFont(UnicodeCIDFont(cid_font))
cls._unicode_font_name = cid_font
cls._unicode_font_bold_name = cid_font # CID fonts don't have bold variants
print(f"Registered CID font: {cid_font}")
break
except Exception as e:
print(f"Failed to register CID font {cid_font}: {e}")
continue
# If CID fonts fail, try TTF fonts as fallback
if not cls._unicode_font_name:
font_paths = [
'/usr/share/fonts/truetype/freefont/FreeSans.ttf',
'/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
]
for font_path in font_paths:
if os.path.exists(font_path):
try:
pdfmetrics.registerFont(TTFont('UnicodeFont', font_path))
cls._unicode_font_name = 'UnicodeFont'
cls._unicode_font_bold_name = 'UnicodeFont'
print(f"Registered TTF font from: {font_path}")
# Register font family
from reportlab.pdfbase.pdfmetrics import registerFontFamily
registerFontFamily('UnicodeFont', normal='UnicodeFont', bold='UnicodeFont')
break
except Exception as e:
print(f"Failed to register TTF font {font_path}: {e}")
continue
return cls._unicode_font_name is not None
@staticmethod
def _needs_unicode_font(text: str) -> bool:
"""Check if text contains CJK or other complex scripts that need special fonts.
Standard PDF fonts (Helvetica, Times, Courier) support:
- Basic Latin, Extended Latin, Cyrillic, Greek
CID fonts are needed for:
- CJK (Chinese, Japanese, Korean)
- Arabic, Hebrew (RTL scripts)
- Thai, Hindi, and other Indic scripts
"""
if not text:
return False
for char in text:
code = ord(char)
# CJK Unified Ideographs and related ranges
if 0x4E00 <= code <= 0x9FFF: # CJK Unified Ideographs
return True
if 0x3400 <= code <= 0x4DBF: # CJK Extension A
return True
if 0x3000 <= code <= 0x303F: # CJK Symbols and Punctuation
return True
if 0x3040 <= code <= 0x309F: # Hiragana
return True
if 0x30A0 <= code <= 0x30FF: # Katakana
return True
if 0xAC00 <= code <= 0xD7AF: # Hangul Syllables
return True
if 0x1100 <= code <= 0x11FF: # Hangul Jamo
return True
# Arabic and Hebrew (RTL scripts)
if 0x0600 <= code <= 0x06FF: # Arabic
return True
if 0x0590 <= code <= 0x05FF: # Hebrew
return True
# Indic scripts
if 0x0900 <= code <= 0x097F: # Devanagari (Hindi)
return True
if 0x0E00 <= code <= 0x0E7F: # Thai
return True
return False
def _get_font_for_content(self, content: str) -> tuple:
"""Get appropriate font based on content, returns (regular_font, bold_font)"""
if self._needs_unicode_font(content):
if self._register_unicode_fonts() and self._unicode_font_name:
return (self._unicode_font_name, self._unicode_font_bold_name or self._unicode_font_name)
else:
print("Warning: Content contains non-Latin characters but no Unicode font available")
# Fall back to configured font
return (self._param.font_family, self._get_bold_font_name())
def _get_active_font(self) -> str:
"""Get the currently active font (Unicode or configured)"""
return getattr(self, '_active_font', self._param.font_family)
def _get_active_bold_font(self) -> str:
"""Get the currently active bold font (Unicode or configured)"""
return getattr(self, '_active_bold_font', self._get_bold_font_name())
def _get_bold_font_name(self) -> str:
"""Get the correct bold variant of the current font family"""
@ -136,8 +279,8 @@ class PDFGenerator(Message, ABC):
title = self._param.title or ""
subtitle = self._param.subtitle or ""
# Add debug logging
print(f"Starting PDF generation for title: {title}")
# Log PDF generation start
print(f"Starting PDF generation for title: {title}, content length: {len(content)} chars")
# Resolve variable references in content using canvas
if content and self._canvas.is_reff(content):
@ -205,9 +348,6 @@ class PDFGenerator(Message, ABC):
if not content:
content = kwargs.get("content", "")
# Add debug logging
print(f"Starting PDF generation with content length: {len(content)} characters")
# Generate document based on format
try:
output_format = self._param.output_format or "pdf"
@ -317,7 +457,15 @@ class PDFGenerator(Message, ABC):
# Build story (content elements)
story = []
styles = self._create_styles()
# Combine all text content for Unicode font detection
all_text = f"{title} {subtitle} {content}"
# IMPORTANT: Register Unicode fonts BEFORE creating any styles or Paragraphs
# This ensures the font family is available for ReportLab's HTML parser
if self._needs_unicode_font(all_text):
self._register_unicode_fonts()
styles = self._create_styles(all_text)
# Add logo if provided
if self._param.logo_image:
@ -397,13 +545,41 @@ class PDFGenerator(Message, ABC):
except Exception as close_error:
print(f"Error closing buffer: {close_error}")
def _create_styles(self):
"""Create custom paragraph styles"""
def _create_styles(self, content: str = ""):
"""Create custom paragraph styles with Unicode font support if needed"""
# Check if content contains CJK characters that need special fonts
needs_cjk = self._needs_unicode_font(content)
if needs_cjk:
# Use CID fonts for CJK content
if self._register_unicode_fonts() and self._unicode_font_name:
regular_font = self._unicode_font_name
bold_font = self._unicode_font_bold_name or self._unicode_font_name
print(f"Using CID font for CJK content: {regular_font}")
else:
# Fall back to configured font if CID fonts unavailable
regular_font = self._param.font_family
bold_font = self._get_bold_font_name()
print(f"Warning: CJK content detected but no CID font available, using {regular_font}")
else:
# Use user-selected font for Latin-only content
regular_font = self._param.font_family
bold_font = self._get_bold_font_name()
print(f"Using configured font: {regular_font}")
# Store active fonts as instance variables for use in other methods
self._active_font = regular_font
self._active_bold_font = bold_font
# Get fresh style sheet
styles = getSampleStyleSheet()
# Helper function to get the correct bold font name
def get_bold_font(font_family):
"""Get the correct bold variant of a font family"""
# If using Unicode font, return the Unicode bold
if font_family in ('UnicodeFont', self._unicode_font_name):
return bold_font
font_map = {
'Helvetica': 'Helvetica-Bold',
'Times-Roman': 'Times-Bold',
@ -413,6 +589,10 @@ class PDFGenerator(Message, ABC):
return font_family
return font_map.get(font_family, 'Helvetica-Bold')
# Use detected font instead of configured font for non-Latin content
active_font = regular_font
active_bold_font = bold_font
# Helper function to add or update style
def add_or_update_style(name, **kwargs):
if name in styles:
@ -424,13 +604,23 @@ class PDFGenerator(Message, ABC):
# Add new style
styles.add(ParagraphStyle(name=name, **kwargs))
# IMPORTANT: Update base styles to use Unicode font for non-Latin content
# This ensures ALL text uses the correct font, not just our custom styles
add_or_update_style('Normal', fontName=active_font)
add_or_update_style('BodyText', fontName=active_font)
add_or_update_style('Bullet', fontName=active_font)
add_or_update_style('Heading1', fontName=active_bold_font)
add_or_update_style('Heading2', fontName=active_bold_font)
add_or_update_style('Heading3', fontName=active_bold_font)
add_or_update_style('Title', fontName=active_bold_font)
# Title style
add_or_update_style(
'PDFTitle',
parent=styles['Heading1'],
fontSize=self._param.title_font_size,
textColor=colors.HexColor(self._param.title_color),
fontName=get_bold_font(self._param.font_family),
fontName=active_bold_font,
alignment=TA_CENTER,
spaceAfter=12
)
@ -441,7 +631,7 @@ class PDFGenerator(Message, ABC):
parent=styles['Heading2'],
fontSize=self._param.heading2_font_size,
textColor=colors.HexColor(self._param.text_color),
fontName=self._param.font_family,
fontName=active_font,
alignment=TA_CENTER,
spaceAfter=12
)
@ -451,7 +641,7 @@ class PDFGenerator(Message, ABC):
'CustomHeading1',
parent=styles['Heading1'],
fontSize=self._param.heading1_font_size,
fontName=get_bold_font(self._param.font_family),
fontName=active_bold_font,
textColor=colors.HexColor(self._param.text_color),
spaceAfter=12,
spaceBefore=12
@ -461,7 +651,7 @@ class PDFGenerator(Message, ABC):
'CustomHeading2',
parent=styles['Heading2'],
fontSize=self._param.heading2_font_size,
fontName=get_bold_font(self._param.font_family),
fontName=active_bold_font,
textColor=colors.HexColor(self._param.text_color),
spaceAfter=10,
spaceBefore=10
@ -471,7 +661,7 @@ class PDFGenerator(Message, ABC):
'CustomHeading3',
parent=styles['Heading3'],
fontSize=self._param.heading3_font_size,
fontName=get_bold_font(self._param.font_family),
fontName=active_bold_font,
textColor=colors.HexColor(self._param.text_color),
spaceAfter=8,
spaceBefore=8
@ -482,7 +672,7 @@ class PDFGenerator(Message, ABC):
'CustomBody',
parent=styles['BodyText'],
fontSize=self._param.font_size,
fontName=self._param.font_family,
fontName=active_font,
textColor=colors.HexColor(self._param.text_color),
leading=self._param.font_size * self._param.line_spacing,
alignment=TA_JUSTIFY
@ -493,13 +683,13 @@ class PDFGenerator(Message, ABC):
'CustomBullet',
parent=styles['BodyText'],
fontSize=self._param.font_size,
fontName=self._param.font_family,
fontName=active_font,
textColor=colors.HexColor(self._param.text_color),
leftIndent=20,
bulletIndent=10
)
# Code style
# Code style (keep Courier for code blocks)
add_or_update_style(
'PDFCode',
parent=styles.get('Code', styles['Normal']),
@ -516,7 +706,7 @@ class PDFGenerator(Message, ABC):
'Italic',
parent=styles['Normal'],
fontSize=self._param.font_size,
fontName=self._param.font_family,
fontName=active_font,
textColor=colors.HexColor(self._param.text_color)
)
@ -571,7 +761,8 @@ class PDFGenerator(Message, ABC):
bullet_items = []
while i < len(lines) and (lines[i].strip().startswith('- ') or lines[i].strip().startswith('* ')):
item_text = lines[i].strip()[2:].strip()
bullet_items.append(f"{self._format_inline(item_text)}")
formatted = self._format_inline(item_text)
bullet_items.append(f"{formatted}")
i += 1
for item in bullet_items:
elements.append(Paragraph(item, styles['CustomBullet']))
@ -754,7 +945,7 @@ class PDFGenerator(Message, ABC):
'TableHeader',
parent=styles['Normal'],
fontSize=self._param.font_size,
fontName=get_bold_font(self._param.font_family),
fontName=self._get_active_bold_font(),
textColor=colors.whitesmoke,
alignment=TA_CENTER,
leading=self._param.font_size * 1.2,
@ -766,7 +957,7 @@ class PDFGenerator(Message, ABC):
'TableCell',
parent=styles['Normal'],
fontSize=font_size,
fontName=self._param.font_family,
fontName=self._get_active_font(),
textColor=colors.black,
alignment=TA_LEFT,
leading=font_size * 1.15,
@ -795,7 +986,7 @@ class PDFGenerator(Message, ABC):
'TableHeader',
parent=styles['Normal'],
fontSize=base_font_size + 1,
fontName=self._get_bold_font_name(),
fontName=self._get_active_bold_font(),
textColor=colors.HexColor('#2c3e50'),
spaceAfter=6,
backColor=colors.HexColor('#f8f9fa'),
@ -810,7 +1001,7 @@ class PDFGenerator(Message, ABC):
'TableBody',
parent=styles['Normal'],
fontSize=base_font_size,
fontName=getattr(self._param, 'font_family', 'Helvetica'),
fontName=self._get_active_font(),
textColor=colors.HexColor(getattr(self._param, 'text_color', '#000000')),
spaceAfter=6,
leading=base_font_size * 1.2
@ -820,7 +1011,7 @@ class PDFGenerator(Message, ABC):
label_style = ParagraphStyle(
'LabelStyle',
parent=body_style,
fontName=self._get_bold_font_name(),
fontName=self._get_active_bold_font(),
textColor=colors.HexColor('#2c3e50'),
fontSize=base_font_size,
spaceAfter=4,
@ -1121,7 +1312,8 @@ class PDFGenerator(Message, ABC):
font_size = self._param.font_size - 1 if row_idx > 0 else self._param.font_size
try:
style = self._get_cell_style(row_idx, is_header=(row_idx == 0), font_size=font_size)
processed_row.append(Paragraph(self._escape_html(cell_text), style))
escaped_text = self._escape_html(cell_text)
processed_row.append(Paragraph(escaped_text, style))
except Exception as e:
processed_row.append(self._escape_html(cell_text))
@ -1146,7 +1338,7 @@ class PDFGenerator(Message, ABC):
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2c3e50')), # Darker header
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), self._get_bold_font_name()),
('FONTNAME', (0, 0), (-1, 0), self._get_active_bold_font()),
('FONTSIZE', (0, 0), (-1, -1), self._param.font_size - 1),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.HexColor('#f8f9fa')), # Lighter background
@ -1232,9 +1424,12 @@ class PDFGenerator(Message, ABC):
"""Add header, footer, page numbers, watermark"""
canvas.saveState()
# Get active font for decorations
active_font = self._get_active_font()
# Add watermark
if self._param.watermark_text:
canvas.setFont(self._param.font_family, 60)
canvas.setFont(active_font, 60)
canvas.setFillColorRGB(0.9, 0.9, 0.9, alpha=0.3)
canvas.saveState()
canvas.translate(doc.pagesize[0] / 2, doc.pagesize[1] / 2)
@ -1244,13 +1439,13 @@ class PDFGenerator(Message, ABC):
# Add header
if self._param.header_text:
canvas.setFont(self._param.font_family, 9)
canvas.setFont(active_font, 9)
canvas.setFillColorRGB(0.5, 0.5, 0.5)
canvas.drawString(doc.leftMargin, doc.pagesize[1] - 0.5 * inch, self._param.header_text)
# Add footer
if self._param.footer_text:
canvas.setFont(self._param.font_family, 9)
canvas.setFont(active_font, 9)
canvas.setFillColorRGB(0.5, 0.5, 0.5)
canvas.drawString(doc.leftMargin, 0.5 * inch, self._param.footer_text)
@ -1258,7 +1453,7 @@ class PDFGenerator(Message, ABC):
if self._param.add_page_numbers:
page_num = canvas.getPageNumber()
text = f"Page {page_num}"
canvas.setFont(self._param.font_family, 9)
canvas.setFont(active_font, 9)
canvas.setFillColorRGB(0.5, 0.5, 0.5)
canvas.drawRightString(doc.pagesize[0] - doc.rightMargin, 0.5 * inch, text)

View file

@ -71,6 +71,10 @@ The font used throughout the document:
- **Times-Roman**
- **Courier**
:::tip NOTE
When the document contains CJK (Chinese, Japanese, Korean) or other non-Latin characters, the system automatically switches to a compatible Unicode font (STSong-Light) to ensure proper rendering. The selected font family is used for Latin-only content.
:::
### Font size
The base font size in points. Defaults to `12`.
@ -132,3 +136,13 @@ To display a download button in the chat, add a **Message** component after the
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.
## Multi-language support
The **Docs Generator** automatically detects non-Latin characters (Chinese, Japanese, Korean, Arabic, Hebrew, Cyrillic, etc.) and uses appropriate Unicode fonts when available on the server.
:::tip NOTE
For full multi-language support, ensure Unicode fonts are installed on the RAGFlow server:
- **Linux**: `fonts-freefont-ttf`, `fonts-noto-cjk`, or `fonts-droid-fallback`
- **Docker**: Add font packages to the Dockerfile if needed
:::

View file

@ -154,6 +154,8 @@ 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",
]
[dependency-groups]

38
uv.lock generated
View file

@ -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'",
@ -3257,7 +3257,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" },
@ -3266,9 +3266,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]]
@ -5955,6 +5955,7 @@ dependencies = [
{ name = "infinity-emb" },
{ name = "infinity-sdk" },
{ name = "itsdangerous" },
{ name = "jinja2" },
{ name = "jira" },
{ name = "json-repair" },
{ name = "langfuse" },
@ -6012,6 +6013,7 @@ dependencies = [
{ name = "ranx" },
{ name = "readability-lxml" },
{ name = "replicate" },
{ name = "reportlab" },
{ name = "requests" },
{ name = "roman-numbers" },
{ name = "ruamel-base" },
@ -6122,6 +6124,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" },
@ -6179,6 +6182,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" },
@ -7383,21 +7387,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]]

View file

@ -996,6 +996,7 @@ export const initialPDFGeneratorValues = {
success: { type: 'boolean', value: false },
},
};
export enum WebhookMethod {
Post = 'POST',
Get = 'GET',

View file

@ -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,