mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-24 23:46:52 +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:
@ -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