Feat: Refact pipeline (#13826)

### What problem does this PR solve?

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
- [x] Refactoring

---------

Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Magicbook1108
2026-04-03 19:26:45 +08:00
committed by GitHub
parent 6d9430a125
commit 69264b3a70
71 changed files with 3055 additions and 990 deletions

View File

@@ -9,6 +9,7 @@ export type LLMFormFieldProps = {
name?: string;
testId?: string;
optionTestIdPrefix?: string;
config?: any;
};
export const useModelOptions = () => {
@@ -26,6 +27,7 @@ export function LLMFormField({
name,
testId,
optionTestIdPrefix,
config,
}: LLMFormFieldProps) {
const { t } = useTranslation();
const { modelOptions } = useModelOptions();
@@ -36,6 +38,7 @@ export function LLMFormField({
options={options || modelOptions}
testId={testId}
optionTestIdPrefix={optionTestIdPrefix}
{...config}
></SelectWithSearch>
</RAGFlowFormItem>
);

View File

@@ -70,8 +70,8 @@ export enum DataflowOperator {
Note = 'Note',
Parser = 'Parser',
Tokenizer = 'Tokenizer',
Splitter = 'Splitter',
HierarchicalMerger = 'HierarchicalMerger',
TokenChunker = 'TokenChunker',
TitleChunker = 'TitleChunker',
Extractor = 'Extractor',
}
@@ -117,8 +117,8 @@ export enum Operator {
File = 'File', // pipeline
Parser = 'Parser',
Tokenizer = 'Tokenizer',
Splitter = 'Splitter',
HierarchicalMerger = 'HierarchicalMerger',
TokenChunker = 'TokenChunker',
TitleChunker = 'TitleChunker',
Extractor = 'Extractor',
Loop = 'Loop',
LoopStart = 'LoopItem',

View File

@@ -2063,12 +2063,10 @@ export default {
tokenizerRequired: 'الرجاء إضافة عقدة المفهرس أولاً',
tokenizerDescription:
'يحول النص إلى بنية البيانات المطلوبة (على سبيل المثال، التضمينات المتجهة لبحث التضمين) اعتمادًا على طريقة البحث المختارة.',
splitter: 'رمز مميز',
splitterDescription:
tokenChunkerDescription:
'قم بتقسيم النص إلى أجزاء حسب طول الرمز المميز باستخدام محددات وتداخلات اختيارية.',
hierarchicalMergerDescription:
titleChunkerDescription:
'قم بتقسيم المستندات إلى أقسام حسب التسلسل الهرمي للعناوين باستخدام قواعد regex للتحكم بشكل أفضل.',
hierarchicalMerger: 'عنوان',
extractor: 'محول',
extractorDescription:
'استخدم LLM لاستخراج الرؤى المنظمة من أجزاء المستند، مثل الملخصات والتصنيفات وما إلى ذلك.',
@@ -2080,6 +2078,8 @@ export default {
image: 'صورة',
email: 'بريد إلكتروني',
'text&markdown': 'النص والترميز',
code: 'Code',
html: 'HTML',
word: 'كلمة',
slides: 'PPTX',
audio: 'صوتي',

View File

@@ -2118,12 +2118,10 @@ The above is the content you need to summarize.`,
tokenizerRequired: 'Моля, първо добавете възел Индексатор',
tokenizerDescription:
'Трансформира текст в необходимата структура от данни (напр. векторни вграждания за Embedding Search) в зависимост от избрания метод за търсене.',
splitter: 'Токен',
splitterDescription:
tokenChunkerDescription:
'Разделя текст на фрагменти по дължина на токени с незадължителни разделители и припокриване.',
hierarchicalMergerDescription:
titleChunkerDescription:
'Разделя документи на секции по йерархия на заглавия с regex правила за по-фин контрол.',
hierarchicalMerger: 'Заглавие',
extractor: 'Трансформатор',
extractorDescription:
'Използва LLM за извличане на структурирани прозрения от фрагменти на документи — като обобщения, класификации и др.',
@@ -2135,6 +2133,8 @@ The above is the content you need to summarize.`,
image: 'Изображение',
email: 'Имейл',
'text&markdown': 'Текст и маркиране',
code: 'Code',
html: 'HTML',
word: 'Word',
slides: 'PPTX',
audio: 'Аудио',

View File

@@ -2176,12 +2176,10 @@ Dieser Prozess aggregiert Variablen aus mehreren Zweigen in eine einzelne Variab
tokenizerRequired: 'Bitte fügen Sie zuerst den Indexer-Knoten hinzu',
tokenizerDescription:
'Transformiert Text in die erforderliche Datenstruktur (z.B. Vektoreinbettungen für die Embedding-Suche) abhängig von der gewählten Suchmethode.',
splitter: 'Token',
splitterDescription:
tokenChunkerDescription:
'Teilt Text in Chunks nach Token-Länge mit optionalen Trennzeichen und Überlappung.',
hierarchicalMergerDescription:
titleChunkerDescription:
'Teilt Dokumente in Abschnitte nach Titelhierarchie mit Regex-Regeln für feinere Kontrolle.',
hierarchicalMerger: 'Titel',
extractor: 'Transformer',
extractorDescription:
'Verwendet ein LLM, um strukturierte Erkenntnisse aus Dokument-Chunks zu extrahieren wie Zusammenfassungen, Klassifizierungen usw.',
@@ -2193,6 +2191,8 @@ Dieser Prozess aggregiert Variablen aus mehreren Zweigen in eine einzelne Variab
image: 'Bild',
email: 'E-Mail',
'text&markdown': 'Text & Markup',
code: 'Code',
html: 'HTML',
word: 'Word',
slides: 'PPTX',
audio: 'Audio',

View File

@@ -2228,12 +2228,12 @@ This process aggregates variables from multiple branches into a single variable
tokenizerRequired: 'Please add the Indexer node first',
tokenizerDescription:
'Transforms text into the required data structure (e.g., vector embeddings for Embedding Search) depending on the chosen search method.',
splitter: 'Token',
splitterDescription:
tokenChunker: 'Token Chunker',
tokenChunkerDescription:
'Split text into chunks by token length with optional delimiters and overlap.',
hierarchicalMergerDescription:
titleChunkerDescription:
'Split documents into sections by title hierarchy with regex rules for finer control.',
hierarchicalMerger: 'Title',
titleChunker: 'Title Chunker',
extractor: 'Transformer',
extractorDescription:
'Use an LLM to extract structured insights from document chunks—such as summaries, classifications, etc.',
@@ -2245,6 +2245,8 @@ This process aggregates variables from multiple branches into a single variable
image: 'Image',
email: 'Email',
'text&markdown': 'Text & Markup',
code: 'Code',
html: 'HTML',
word: 'Word',
slides: 'PPTX',
audio: 'Audio',

View File

@@ -326,7 +326,7 @@ Prosedürel Bellek: Öğrenilen beceriler, alışkanlıklar ve otomatik prosedü
action: 'İşlem',
parsingStatus: 'Ayrıştırma durumu',
parsingStatusTip:
'Belge ayrıştırma süresi çeşitli faktörlere bağlıdır. Bilgi Grafiği, RAPTOR, Otomatik Soru Çıkarma veya Otomatik Anahtar Kelime Çıkarma gibi özelliklerin etkinleştirilmesi işlem süresini önemli ölçüde artıracaktır. İlerleme çubuğu durursa, lütfen şu SSS\'ye başvurun: https://ragflow.io/docs/dev/faq#why-does-my-document-parsing-stall-at-under-one-percent.',
"Belge ayrıştırma süresi çeşitli faktörlere bağlıdır. Bilgi Grafiği, RAPTOR, Otomatik Soru Çıkarma veya Otomatik Anahtar Kelime Çıkarma gibi özelliklerin etkinleştirilmesi işlem süresini önemli ölçüde artıracaktır. İlerleme çubuğu durursa, lütfen şu SSS'ye başvurun: https://ragflow.io/docs/dev/faq#why-does-my-document-parsing-stall-at-under-one-percent.",
processBeginAt: 'Başlangıç zamanı',
processDuration: 'Süre',
progressMsg: 'İlerleme',
@@ -370,7 +370,7 @@ Prosedürel Bellek: Öğrenilen beceriler, alışkanlıklar ve otomatik prosedü
toMessage: 'Bitiş sayfa numarası eksik (hariç)',
layoutRecognize: 'PDF ayrıştırıcı',
layoutRecognizeTip:
'PDF düzen analizi için görsel model kullanın. Belge başlıklarını, metin bloklarını, görüntüleri ve tabloları etkili bir şekilde konumlandırır. Naive seçeneği seçilirse yalnızca PDF\'deki düz metin alınır. Bu seçeneğin şu anda YALNIZCA PDF belgeleri için çalıştığını lütfen unutmayın.',
"PDF düzen analizi için görsel model kullanın. Belge başlıklarını, metin bloklarını, görüntüleri ve tabloları etkili bir şekilde konumlandırır. Naive seçeneği seçilirse yalnızca PDF'deki düz metin alınır. Bu seçeneğin şu anda YALNIZCA PDF belgeleri için çalıştığını lütfen unutmayın.",
taskPageSize: 'Görev sayfa boyutu',
taskPageSizeMessage: 'Lütfen görev sayfa boyutunu girin!',
taskPageSizeTip: `Düzen tanıma sırasında bir PDF dosyası parçalara bölünür ve işleme hızını artırmak için paralel olarak işlenir. Bu parametre her parçanın boyutunu ayarlar. Daha büyük parça boyutu, sayfalar arasındaki sürekli metni bölme olasılığını azaltır.`,
@@ -529,7 +529,8 @@ Prosedürel Bellek: Öğrenilen beceriler, alışkanlıklar ve otomatik prosedü
chunkTokenNumberMessage: 'Metin için parça token sayısı gereklidir',
embeddingModelTip: `Dataset tarafından kullanılan varsayılan embedding model. Dataset içinde parçalar varken embedding modelini değiştirirken, sistem uyumluluk kontrolü için rastgele birkaç parça örnekler, yeni embedding modeli ile yeniden gömer ve eski ile yeni vektörler arasındaki kosinüs benzerliğini hesaplar. Değiştirmeye ancak örneklerin ortalama benzerliği ≥ 0.9 olduğunda izin verilir. Aksi takdirde, değiştirmeden önce datasetteki tüm parçaları silmeniz gerekir.`,
permissionsTip: `'Takım' olarak ayarlandığında, tüm takım üyeleri bu dataset'i yönetebilir.`,
chunkTokenNumberTip: 'Bir parça oluşturmak için token eşiğini belirler. Bu eşiğin altındaki tokenlı bir bölüm, token sayısı eşiği aşana kadar sonraki bölümlerle birleştirilir ve bu noktada bir parça oluşturulur. Eşik aşılsa bile bir sınırlayıcı ile karşılaşılmadıkça yeni parça oluşturulmaz.',
chunkTokenNumberTip:
'Bir parça oluşturmak için token eşiğini belirler. Bu eşiğin altındaki tokenlı bir bölüm, token sayısı eşiği aşana kadar sonraki bölümlerle birleştirilir ve bu noktada bir parça oluşturulur. Eşik aşılsa bile bir sınırlayıcı ile karşılaşılmadıkça yeni parça oluşturulmaz.',
chunkMethod: 'Parçalama yöntemi',
chunkMethodTip: 'Sağdaki ipuçlarına bakın.',
upload: 'Yükle',
@@ -654,7 +655,7 @@ Prosedürel Bellek: Öğrenilen beceriler, alışkanlıklar ve otomatik prosedü
`,
useRaptor: 'RAPTOR',
useRaptorTip:
'RAPTOR çok adımlı soru-cevap görevleri için kullanılabilir. Dosyalar sayfasına gidin, Oluştur > RAPTOR\'a tıklayarak etkinleştirin. Ayrıntılar için bkz. https://ragflow.io/docs/dev/enable_raptor.',
"RAPTOR çok adımlı soru-cevap görevleri için kullanılabilir. Dosyalar sayfasına gidin, Oluştur > RAPTOR'a tıklayarak etkinleştirin. Ayrıntılar için bkz. https://ragflow.io/docs/dev/enable_raptor.",
prompt: 'İstem',
promptTip:
'Görevi tanımlamak, nasıl yanıt vermesi gerektiğini belirtmek ve diğer çeşitli gereksinimleri belirlemek için sistem istemini kullanın. Sistem istemi genellikle LLM için çeşitli veri girdileri olarak hizmet eden anahtarlarla (değişkenlerle) birlikte kullanılır. Kullanacağınız anahtarları göstermek için eğik çizgi `/` veya (x) düğmesini kullanın.',
@@ -709,7 +710,7 @@ Bu otomatik etiketleme özelliği, mevcut datasete alanına özgü bilgi katman
resolutionTip: `Varlık tekilleştirme anahtarı. Etkinleştirildiğinde LLM benzer varlıkları birleştirir - örneğin '2025' ve '2025 yılı' veya 'BT' ve 'Bilgi Teknolojisi' - daha doğru bir grafik oluşturmak için`,
community: 'Topluluk raporları',
communityTip:
'Bir bilgi grafiğinde, topluluk ilişkilerle bağlı varlıkların bir kümesidir. LLM\'in her topluluk için bir özet oluşturmasını sağlayabilirsiniz, bu topluluk raporu olarak bilinir. Daha fazla bilgi için bkz: https://www.microsoft.com/en-us/research/blog/graphrag-improving-global-search-via-dynamic-community-selection/',
"Bir bilgi grafiğinde, topluluk ilişkilerle bağlı varlıkların bir kümesidir. LLM'in her topluluk için bir özet oluşturmasını sağlayabilirsiniz, bu topluluk raporu olarak bilinir. Daha fazla bilgi için bkz: https://www.microsoft.com/en-us/research/blog/graphrag-improving-global-search-via-dynamic-community-selection/",
theDocumentBeingParsedCannotBeDeleted: 'Ayrıştırılan belge silinemez',
lastWeek: 'geçen haftadan',
},
@@ -907,7 +908,8 @@ Bu otomatik etiketleme özelliği, mevcut datasete alanına özgü bilgi katman
'Çok adımlı soru-cevap için alım sırasında belirtilen dataset(lerdeki) bilgi grafikleri kullanılsın mı. Etkinleştirildiğinde varlık, ilişki ve topluluk raporu parçaları üzerinde yinelemeli aramalar içererek alım süresini önemli ölçüde artırır.',
keyword: 'Anahtar kelime analizi',
keywordTip: `Kullanıcının sorularını analiz etmek, alaka düzeyi hesaplaması sırasında vurgulanacak anahtar kelimeleri çıkarmak için LLM kullanın. Uzun sorgularda iyi çalışır ancak yanıt süresini artırır.`,
languageTip: 'Belirtilen dille cümle yeniden yazmaya izin verir veya seçilmezse en son soruyu varsayılan olarak kullanır.',
languageTip:
'Belirtilen dille cümle yeniden yazmaya izin verir veya seçilmezse en son soruyu varsayılan olarak kullanır.',
avatarHidden: 'Avatarı gizle',
locale: 'Yerel ayar',
selectLanguage: 'Dil seçin',
@@ -1019,7 +1021,8 @@ Bu otomatik etiketleme özelliği, mevcut datasete alanına özgü bilgi katman
'Taranacak Google Drive klasör bağlantıları (virgülle ayrılmış).',
gmailPrimaryAdminTip:
'Gmail / Workspace erişimi olan birincil yönetici e-postası, alan kullanıcılarını listeleme ve varsayılan senkronizasyon hesabı olarak kullanılır.',
gmailTokenTip: "Google Console'dan oluşturulan OAuth JSON'ını yükleyin. Yalnızca istemci kimlik bilgilerini içeriyorsa, uzun ömürlü yenileme tokenları oluşturmak için tarayıcı tabanlı doğrulamayı bir kez çalıştırın.",
gmailTokenTip:
"Google Console'dan oluşturulan OAuth JSON'ını yükleyin. Yalnızca istemci kimlik bilgilerini içeriyorsa, uzun ömürlü yenileme tokenları oluşturmak için tarayıcı tabanlı doğrulamayı bir kez çalıştırın.",
dropboxDescription:
"Seçilen bir hesaptan dosya ve klasörleri senkronize etmek için Dropbox'ınızı bağlayın.",
bitbucketDescription:
@@ -1045,12 +1048,13 @@ Bu otomatik etiketleme özelliği, mevcut datasete alanına özgü bilgi katman
imapDescription:
'Bilgi alımı için e-postaları senkronize etmek üzere IMAP posta kutunuza bağlanın.',
dropboxAccessTokenTip:
'Dropbox Uygulama Konsolunda files.metadata.read, files.content.read ve sharing.read kapsamlarıyla uzun ömürlü erişim token\'ı oluşturun.',
"Dropbox Uygulama Konsolunda files.metadata.read, files.content.read ve sharing.read kapsamlarıyla uzun ömürlü erişim token'ı oluşturun.",
moodleDescription:
"Ders içeriği, forumlar ve kaynakları senkronize etmek için Moodle LMS'nize bağlanın.",
moodleUrlTip: 'Moodle örneğinizin temel URL\'si (örn. https://moodle.university.edu). /webservice veya /login eklemeyin.',
moodleUrlTip:
"Moodle örneğinizin temel URL'si (örn. https://moodle.university.edu). /webservice veya /login eklemeyin.",
moodleTokenTip:
'Moodle\'da bir web servisi token\'ı oluşturun: Site yönetimi → Sunucu → Web servisleri → Tokenları yönet bölümüne gidin. Kullanıcı senkronize etmek istediğiniz kurslara kayıtlı olmalıdır.',
"Moodle'da bir web servisi token'ı oluşturun: Site yönetimi → Sunucu → Web servisleri → Tokenları yönet bölümüne gidin. Kullanıcı senkronize etmek istediğiniz kurslara kayıtlı olmalıdır.",
seafileDescription:
'SeaFile sunucunuza bağlanarak kitaplıklarınızdaki dosya ve belgeleri senkronize edin.',
seafileUrlTip: "Protokol dahil SeaFile sunucunuzun tam URL'si.",
@@ -1069,10 +1073,11 @@ Bu otomatik etiketleme özelliği, mevcut datasete alanına özgü bilgi katman
seafileValidationDirectoryPathRequired: 'Dizin Yolu gereklidir',
seafileSyncScopeTip:
'Nelerin senkronize edileceğini kontrol eder: ' +
'(1) Tüm Hesap - Token\'larınızın erişebildiği tüm kitaplıkları senkronize eder. Hesap API Token\'ı gerektirir. ' +
'(2) Tek Kitaplık - Belirli bir kitaplıktaki tüm dosyaları senkronize eder. Kitaplık Kimliği ve Hesap API Token\'ı veya Kitaplık API Token\'ı gerektirir. ' +
'(3) Belirli Dizin - Bir kitaplık içindeki belirli bir klasördeki dosyaları senkronize eder. Kitaplık Kimliği, klasör yolu ve kimlik doğrulama token\'ı gerektirir.',
seafileTokenTip: 'Hesap düzeyinde SeaFile API token\'ınız. ' +
"(1) Tüm Hesap - Token'larınızın erişebildiği tüm kitaplıkları senkronize eder. Hesap API Token'ı gerektirir. " +
"(2) Tek Kitaplık - Belirli bir kitaplıktaki tüm dosyaları senkronize eder. Kitaplık Kimliği ve Hesap API Token'ı veya Kitaplık API Token'ı gerektirir. " +
"(3) Belirli Dizin - Bir kitaplık içindeki belirli bir klasördeki dosyaları senkronize eder. Kitaplık Kimliği, klasör yolu ve kimlik doğrulama token'ı gerektirir.",
seafileTokenTip:
"Hesap düzeyinde SeaFile API token'ınız. " +
'Hesabınıza görünür tüm kitaplıklara erişim sağlar. ' +
'Senkronizasyon kapsamı "Tüm Hesap" olduğunda gereklidir. ' +
'"Tek Kitaplık" veya "Belirli Dizin" için bu token\'ı veya Kitaplık API Token\'ını kullanabilirsiniz.',
@@ -2174,12 +2179,10 @@ Bu otomatik etiketleme özelliği, mevcut datasete alanına özgü bilgi katman
tokenizerRequired: 'Lütfen önce İndeksleyici düğümü ekleyin',
tokenizerDescription:
'Metni seçilen arama yöntemine bağlı olarak gerekli veri yapısına dönüştürür.',
splitter: 'Token',
splitterDescription:
tokenChunkerDescription:
'Metni isteğe bağlı sınırlayıcılar ve örtüşme ile token uzunluğuna göre parçalara böler.',
hierarchicalMergerDescription:
titleChunkerDescription:
'Belgeleri daha ince kontrol için regex kurallarıyla başlık hiyerarşisine göre bölümlere böler.',
hierarchicalMerger: 'Başlık',
extractor: 'Dönüştürücü',
extractorDescription:
'Belge parçalarından yapılandırılmış içgörüler çıkarmak için LLM kullanır.',
@@ -2191,6 +2194,8 @@ Bu otomatik etiketleme özelliği, mevcut datasete alanına özgü bilgi katman
image: 'Görüntü',
email: 'E-posta',
'text&markdown': 'Metin ve Biçimlendirme',
code: 'Code',
html: 'HTML',
word: 'Word',
slides: 'PPTX',
audio: 'Ses',

View File

@@ -1927,17 +1927,30 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
tokenizerRequired: '请先添加Tokenizer节点',
tokenizerDescription:
'根据所选的搜索方法,将文本转换为所需的数据结构(例如,用于嵌入搜索的向量嵌入)。',
splitter: '按字符分割',
splitterDescription:
tokenChunker: '按 Token 分块',
tokenChunkerDescription:
'根据分词器长度将文本拆分成块,并带有可选的分隔符和重叠。',
hierarchicalMergerDescription:
titleChunkerDescription:
'使用正则表达式规则按标题层次结构将文档拆分成多个部分,以实现更精细的控制。',
hierarchicalMerger: '按标题分',
titleChunker: '按标题分',
extractor: '提取器',
extractorDescription:
'使用 LLM 从文档块(例如摘要、分类等)中提取结构化见解。',
outputFormat: '输出格式',
fileFormats: '文件类型',
fileFormatOptions: {
pdf: 'PDF',
spreadsheet: '表格',
image: '图片',
email: '邮件',
'text&markdown': '文本与标记',
code: '代码',
html: 'HTML',
word: 'Word',
slides: 'PPTX',
audio: '音频',
video: '视频',
},
fields: '字段',
addParser: '增加解析器',
hierarchy: '层次结构',

View File

@@ -54,6 +54,7 @@ import { RagNode } from './node';
import { AgentNode } from './node/agent-node';
import { BeginNode } from './node/begin-node';
import { CategorizeNode } from './node/categorize-node';
import { ChunkerNode } from './node/chunker-node';
import { DataOperationsNode } from './node/data-operations-node';
import { NextStepDropdown } from './node/dropdown/next-step-dropdown';
import { ExitLoopNode } from './node/exit-loop-node';
@@ -69,7 +70,6 @@ import ParserNode from './node/parser-node';
import { PlaceholderNode } from './node/placeholder-node';
import { RetrievalNode } from './node/retrieval-node';
import { RewriteNode } from './node/rewrite-node';
import { SplitterNode } from './node/splitter-node';
import { SwitchNode } from './node/switch-node';
import TokenizerNode from './node/tokenizer-node';
import { ToolNode } from './node/tool-node';
@@ -95,7 +95,7 @@ export const nodeTypes: NodeTypes = {
fileNode: FileNode,
parserNode: ParserNode,
tokenizerNode: TokenizerNode,
splitterNode: SplitterNode,
chunkerNode: ChunkerNode,
contextNode: ExtractorNode,
dataOperationsNode: DataOperationsNode,
listOperationsNode: ListOperationsNode,

View File

@@ -11,7 +11,7 @@ import { NodeWrapper } from './node-wrapper';
import { ToolBar } from './toolbar';
type RagNodeProps = NodeProps<IRagNode> & PropsWithChildren;
function InnerSplitterNode({
function InnerChunkerNode({
id,
data,
isConnectable = true,
@@ -47,7 +47,7 @@ function InnerSplitterNode({
id={id}
name={'Chunker'}
label={data.label}
icon={<OperatorIcon name={Operator.Splitter}></OperatorIcon>}
icon={<OperatorIcon name={data.label as Operator}></OperatorIcon>}
></NodeHeader>
<LabelCard>{data.name}</LabelCard>
</NodeWrapper>
@@ -55,4 +55,4 @@ function InnerSplitterNode({
);
}
export const SplitterNode = memo(InnerSplitterNode);
export const ChunkerNode = memo(InnerChunkerNode);

View File

@@ -176,8 +176,8 @@ export function PipelineAccordionOperators({
const chunkerOperators = useMemo(() => {
return [
...restrictSingleOperatorOnCanvas([
Operator.Splitter,
Operator.HierarchicalMerger,
Operator.TokenChunker,
Operator.TitleChunker,
]),
];
}, [restrictSingleOperatorOnCanvas]);

View File

@@ -684,8 +684,8 @@ export const RestrictedUpstreamMap = {
[Operator.VariableAssigner]: [Operator.Begin],
[Operator.VariableAggregator]: [Operator.Begin],
[Operator.Parser]: [Operator.Begin], // pipeline
[Operator.Splitter]: [Operator.Begin],
[Operator.HierarchicalMerger]: [Operator.Begin],
[Operator.TokenChunker]: [Operator.Begin],
[Operator.TitleChunker]: [Operator.Begin],
[Operator.Tokenizer]: [Operator.Begin],
[Operator.Extractor]: [Operator.Begin],
[Operator.File]: [Operator.Begin],
@@ -732,8 +732,8 @@ export const NodeMap = {
[Operator.File]: 'fileNode',
[Operator.Parser]: 'parserNode',
[Operator.Tokenizer]: 'tokenizerNode',
[Operator.Splitter]: 'splitterNode',
[Operator.HierarchicalMerger]: 'splitterNode',
[Operator.TokenChunker]: 'chunkerNode',
[Operator.TitleChunker]: 'chunkerNode',
[Operator.Extractor]: 'contextNode',
[Operator.DataOperations]: 'dataOperationsNode',
[Operator.ListOperations]: 'listOperationsNode',
@@ -775,8 +775,8 @@ export const NoDebugOperatorsList = [
Operator.File,
Operator.Parser,
Operator.Tokenizer,
Operator.Splitter,
Operator.HierarchicalMerger,
Operator.TokenChunker,
Operator.TitleChunker,
Operator.Extractor,
Operator.Tool,
];
@@ -785,8 +785,8 @@ export const NoCopyOperatorsList = [
Operator.File,
Operator.Parser,
Operator.Tokenizer,
Operator.Splitter,
Operator.HierarchicalMerger,
Operator.TokenChunker,
Operator.TitleChunker,
Operator.Extractor,
];

View File

@@ -10,6 +10,8 @@ export enum FileType {
Image = 'image',
Email = 'email',
TextMarkdown = 'text&markdown',
Code = 'code',
Html = 'html',
Docx = 'word',
PowerPoint = 'slides',
Video = 'video',
@@ -62,6 +64,8 @@ export const OutputFormatMap = {
[FileType.Image]: ImageOutputFormat,
[FileType.Email]: EmailOutputFormat,
[FileType.TextMarkdown]: TextMarkdownOutputFormat,
[FileType.Code]: TextMarkdownOutputFormat,
[FileType.Html]: TextMarkdownOutputFormat,
[FileType.Docx]: DocxOutputFormat,
[FileType.PowerPoint]: PptOutputFormat,
[FileType.Video]: VideoOutputFormat,
@@ -74,6 +78,8 @@ export const InitialOutputFormatMap = {
[FileType.Image]: ImageOutputFormat.Text,
[FileType.Email]: EmailOutputFormat.Text,
[FileType.TextMarkdown]: TextMarkdownOutputFormat.Text,
[FileType.Code]: TextMarkdownOutputFormat.Text,
[FileType.Html]: TextMarkdownOutputFormat.Text,
[FileType.Docx]: DocxOutputFormat.Json,
[FileType.PowerPoint]: PptOutputFormat.Json,
[FileType.Video]: VideoOutputFormat.Text,
@@ -208,6 +214,16 @@ export const initialParserValues = {
output_format: TextMarkdownOutputFormat.Text,
preprocess: PreprocessValue.main_content,
},
{
fileFormat: FileType.Code,
output_format: TextMarkdownOutputFormat.Text,
preprocess: PreprocessValue.main_content,
},
{
fileFormat: FileType.Html,
output_format: TextMarkdownOutputFormat.Text,
preprocess: PreprocessValue.main_content,
},
{
fileFormat: FileType.Docx,
output_format: DocxOutputFormat.Json,
@@ -222,7 +238,7 @@ export const initialParserValues = {
],
};
export const initialSplitterValues = {
export const initialTokenChunkerValues = {
outputs: {
chunks: { type: 'Array<Object>', value: [] },
},
@@ -239,18 +255,74 @@ export enum Hierarchy {
H4 = '4',
H5 = '5',
}
export const initialHierarchicalMergerValues = {
const rules = [
{
// levels: [
// { expression: '^#[^#]' },
// { expression: '^##[^#]' },
// { expression: '^###[^#]' },
// { expression: '^####[^#]' },
// ],
levels: [
{ expression: '^#[^#]' },
{ expression: '^##[^#]' },
{ expression: '^###[^#]' },
{ expression: '^####[^#]' },
],
},
{
levels: [
{ expression: '第[零一二三四五六七八九十百0-9]+(分?编|部分)' },
{ expression: '第[零一二三四五六七八九十百0-9]+章' },
{ expression: '第[零一二三四五六七八九十百0-9]+节' },
{ expression: '第[零一二三四五六七八九十百0-9]+条' },
{ expression: '[\\(][零一二三四五六七八九十百]+[\\)]' },
],
},
{
levels: [
{ expression: '第[0-9]+章' },
{ expression: '第[0-9]+节' },
{ expression: '[0-9]{1,2}[\\. 、]' },
{ expression: '[0-9]{1,2}\\.[0-9]{1,2}($|[^a-zA-Z/%~.-])' },
{ expression: '[0-9]{1,2}\\.[0-9]{1,2}\\.[0-9]{1,2}' },
],
},
{
levels: [
{ expression: '第[零一二三四五六七八九十百0-9]+章' },
{ expression: '第[零一二三四五六七八九十百0-9]+节' },
{ expression: '[零一二三四五六七八九十百]+[ 、]' },
{ expression: '[\\(][零一二三四五六七八九十百]+[\\)]' },
{ expression: '[\\(][0-9]{,2}[\\)]' },
],
},
{
levels: [
{
expression: 'PART (ONE|TWO|THREE|FOUR|FIVE|SIX|SEVEN|EIGHT|NINE|TEN)',
},
{ expression: 'Chapter (I+V?|VI*|XI|IX|X)' },
{ expression: 'Section [0-9]+' },
{ expression: 'Article [0-9]+' },
],
},
];
export const initialTitleChunkerValues = {
outputs: {
chunks: { type: 'Array<Object>', value: [] },
},
method: 'hierarchy',
hierarchy: Hierarchy.H3,
levels: [
{ expressions: [{ expression: '^#[^#]' }] },
{ expressions: [{ expression: '^##[^#]' }] },
{ expressions: [{ expression: '^###[^#]' }] },
{ expressions: [{ expression: '^####[^#]' }] },
],
include_heading_content: false,
rules: rules,
};
export const initialGroupValues = {
method: 'group',
hierarchy: '0',
include_heading_content: false,
rules: rules,
};
export const initialExtractorValues = {
@@ -269,6 +341,22 @@ export const FileTypeSuffixMap = {
[FileType.Image]: ['jpg', 'jpeg', 'png', 'gif'],
[FileType.Email]: ['eml', 'msg'],
[FileType.TextMarkdown]: ['md', 'markdown', 'mdx', 'txt'],
[FileType.Code]: [
'py',
'js',
'java',
'c',
'cpp',
'h',
'php',
'go',
'ts',
'sh',
'cs',
'kt',
'sql',
],
[FileType.Html]: ['htm', 'html'],
[FileType.Docx]: ['doc', 'docx'],
[FileType.PowerPoint]: ['pptx', 'ppt'],
[FileType.Video]: ['mp4', 'avi', 'mkv'],
@@ -293,7 +381,7 @@ export const FileTypeSuffixMap = {
export const SingleOperators = [
Operator.Tokenizer,
Operator.Splitter,
Operator.HierarchicalMerger,
Operator.TokenChunker,
Operator.TitleChunker,
Operator.Parser,
];

View File

@@ -14,7 +14,6 @@ import ExtractorForm from '../form/extractor-form';
import GithubForm from '../form/github-form';
import GoogleForm from '../form/google-form';
import GoogleScholarForm from '../form/google-scholar-form';
import HierarchicalMergerForm from '../form/hierarchical-merger-form';
import InvokeForm from '../form/invoke-form';
import IterationForm from '../form/iteration-form';
import IterationStartForm from '../form/iteration-start-from';
@@ -27,11 +26,12 @@ import PubMedForm from '../form/pubmed-form';
import RetrievalForm from '../form/retrieval-form/next';
import RewriteQuestionForm from '../form/rewrite-question-form';
import SearXNGForm from '../form/searxng-form';
import SplitterForm from '../form/splitter-form';
import StringTransformForm from '../form/string-transform-form';
import SwitchForm from '../form/switch-form';
import TavilyExtractForm from '../form/tavily-extract-form';
import TavilyForm from '../form/tavily-form';
import TitleChunkerForm from '../form/title-chunker-form';
import TokenChunkerForm from '../form/token-chunker-form';
import TokenizerForm from '../form/tokenizer-form';
import ToolForm from '../form/tool-form';
import UserFillUpForm from '../form/user-fill-up-form';
@@ -154,11 +154,11 @@ export const FormConfigMap = {
[Operator.Tokenizer]: {
component: TokenizerForm,
},
[Operator.Splitter]: {
component: SplitterForm,
[Operator.TokenChunker]: {
component: TokenChunkerForm,
},
[Operator.HierarchicalMerger]: {
component: HierarchicalMergerForm,
[Operator.TitleChunker]: {
component: TitleChunkerForm,
},
[Operator.Extractor]: {
component: ExtractorForm,

View File

@@ -1,191 +0,0 @@
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { BlockButton, Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { Form, FormLabel } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { zodResolver } from '@hookform/resolvers/zod';
import { Plus, Trash2 } from 'lucide-react';
import { memo } from 'react';
import { useFieldArray, useForm, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import {
Hierarchy,
initialHierarchicalMergerValues,
} from '../../constant/pipeline';
import { useFormValues } from '../../hooks/use-form-values';
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
import { INextOperatorForm } from '../../interface';
import { buildOutputList } from '../../utils/build-output-list';
import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output';
const outputList = buildOutputList(initialHierarchicalMergerValues.outputs);
const HierarchyOptions = [
{ label: 'H1', value: Hierarchy.H1 },
{ label: 'H2', value: Hierarchy.H2 },
{ label: 'H3', value: Hierarchy.H3 },
{ label: 'H4', value: Hierarchy.H4 },
{ label: 'H5', value: Hierarchy.H5 },
];
export const FormSchema = z.object({
hierarchy: z.string(),
levels: z.array(
z.object({
expressions: z.array(
z.object({
expression: z.string().refine(
(val) => {
try {
// Try converting the string to a RegExp
new RegExp(val);
return true;
} catch {
return false;
}
},
{
message: 'Must be a valid regular expression string',
},
),
}),
),
}),
),
});
export type HierarchicalMergerFormSchemaType = z.infer<typeof FormSchema>;
type RegularExpressionsProps = {
index: number;
parentName: string;
removeParent: (index: number) => void;
isLatest: boolean;
};
export function RegularExpressions({
index,
parentName,
isLatest,
removeParent,
}: RegularExpressionsProps) {
const { t } = useTranslation();
const form = useFormContext();
const name = `${parentName}.${index}.expressions`;
const { fields, append, remove } = useFieldArray({
name: name,
control: form.control,
});
return (
<Card>
<CardHeader className="flex-row justify-between items-center">
<span>H{index + 1}</span>
{isLatest && (
<Button
type="button"
variant={'ghost'}
onClick={() => removeParent(index)}
>
<Trash2 />
</Button>
)}
</CardHeader>
<CardContent>
<FormLabel required className="mb-2 text-text-secondary">
{t('flow.regularExpressions')}
</FormLabel>
<section className="space-y-4">
{fields.map((field, index) => (
<div key={field.id} className="flex items-center gap-2">
<div className="space-y-2 flex-1">
<RAGFlowFormItem
name={`${name}.${index}.expression`}
label={'expression'}
labelClassName="!hidden"
>
<Input className="!m-0"></Input>
</RAGFlowFormItem>
</div>
{index === 0 ? (
<Button
onClick={() => append({ expression: '' })}
variant={'ghost'}
>
<Plus></Plus>
</Button>
) : (
<Button
type="button"
variant={'ghost'}
onClick={() => remove(index)}
>
<Trash2 />
</Button>
)}
</div>
))}
</section>
</CardContent>
</Card>
);
}
const HierarchicalMergerForm = ({ node }: INextOperatorForm) => {
const { t } = useTranslation();
const defaultValues = useFormValues(initialHierarchicalMergerValues, node);
const form = useForm<HierarchicalMergerFormSchemaType>({
defaultValues,
resolver: zodResolver(FormSchema),
mode: 'onChange',
});
const name = 'levels';
const { fields, append, remove } = useFieldArray({
name: name,
control: form.control,
});
useWatchFormChange(node?.id, form);
return (
<Form {...form}>
<FormWrapper>
<RAGFlowFormItem name={'hierarchy'} label={t('flow.hierarchy')}>
<SelectWithSearch options={HierarchyOptions}></SelectWithSearch>
</RAGFlowFormItem>
{fields.map((field, index) => (
<div key={field.id} className="flex items-center">
<div className="flex-1">
<RegularExpressions
parentName={name}
index={index}
removeParent={remove}
isLatest={index === fields.length - 1}
></RegularExpressions>
</div>
</div>
))}
{fields.length < 5 && (
<BlockButton
onClick={() => append({ expressions: [{ expression: '' }] })}
>
{t('common.add')}
</BlockButton>
)}
</FormWrapper>
<div className="p-5">
<Output list={outputList}></Output>
</div>
</Form>
);
};
export default memo(HierarchicalMergerForm);

View File

@@ -9,6 +9,7 @@ import {
SelectWithSearchFlagOptionType,
} from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { Checkbox } from '@/components/ui/checkbox';
import { upperCase, upperFirst } from 'lodash';
import { useTranslation } from 'react-i18next';
import {
@@ -80,12 +81,55 @@ export function LargeModelFormField({
}: CommonProps & Pick<LLMFormFieldProps, 'options'>) {
return (
<LLMFormField
name={buildFieldNameWithPrefix('llm_id', prefix)}
name={buildFieldNameWithPrefix('vlm.llm_id', prefix)}
options={options}
config={{ allowClear: true }}
></LLMFormField>
);
}
export function TwoColumnCheckFormField({ prefix }: CommonProps) {
const { t } = useTranslation();
return (
<RAGFlowFormItem
name={buildFieldNameWithPrefix(`enable_multi_column`, prefix)}
label={t('flow.enableMultiColumn', 'Enable multi column')}
horizontal={true}
labelClassName="w-[200px]"
>
{(field) => (
<Checkbox
checked={field.value}
onCheckedChange={(checked) => {
field.onChange?.(checked);
}}
/>
)}
</RAGFlowFormItem>
);
}
export function RmdirFormField({ prefix }: CommonProps) {
const { t } = useTranslation();
return (
<RAGFlowFormItem
name={buildFieldNameWithPrefix(`remove_toc`, prefix)}
label={t('flow.remove_toc', 'Remove TOC')}
horizontal={true}
labelClassName="w-[200px]"
>
{(field) => (
<Checkbox
checked={field.value}
onCheckedChange={(checked) => {
field.onChange?.(checked);
}}
/>
)}
</RAGFlowFormItem>
);
}
export function LanguageFormField({ prefix }: CommonProps) {
const { t } = useTranslation();

View File

@@ -39,8 +39,13 @@ import { ImageFormFields } from './image-form-fields';
import { PdfFormFields } from './pdf-form-fields';
import { PptFormFields } from './ppt-form-fields';
import { SpreadsheetFormFields } from './spreadsheet-form-fields';
import {
HtmlFormFields,
TextMarkdownFormFields,
} from './text-html-form-fields';
import { buildFieldNameWithPrefix } from './utils';
import { AudioFormFields, VideoFormFields } from './video-form-fields';
import { WordFormFields } from './word-form-fields';
const outputList = buildOutputList(initialParserValues.outputs);
@@ -72,6 +77,11 @@ const PreprocessOptionConfigsMap: Partial<
{ value: MAIN_CONTENT_PREPROCESS_VALUE, required: true },
{ value: PreprocessValue.section_title },
],
[FileType.Code]: [{ value: MAIN_CONTENT_PREPROCESS_VALUE, required: true }],
[FileType.Html]: [
{ value: MAIN_CONTENT_PREPROCESS_VALUE, required: true },
{ value: PreprocessValue.section_title },
],
[FileType.Docx]: [
{ value: MAIN_CONTENT_PREPROCESS_VALUE, required: true },
{ value: PreprocessValue.section_title },
@@ -116,10 +126,13 @@ const FileFormatWidgetMap = {
[FileType.PDF]: PdfFormFields,
[FileType.Spreadsheet]: SpreadsheetFormFields,
[FileType.PowerPoint]: PptFormFields,
[FileType.Docx]: WordFormFields,
[FileType.Video]: VideoFormFields,
[FileType.Audio]: AudioFormFields,
[FileType.Email]: EmailFormFields,
[FileType.Image]: ImageFormFields,
[FileType.TextMarkdown]: TextMarkdownFormFields,
[FileType.Html]: HtmlFormFields,
};
type ParserItemProps = {
@@ -139,10 +152,12 @@ export const FormSchema = z.object({
parse_method: z.string().optional(),
lang: z.string().optional(),
fields: z.array(z.string()).optional(),
llm_id: z.string().optional(),
vlm: z.object({ llm_id: z.string().optional() }).optional(),
system_prompt: z.string().optional(),
table_result_type: z.string().optional(),
markdown_image_response_type: z.string().optional(),
enable_multi_column: z.boolean().optional(),
remove_toc: z.boolean().optional(),
}),
),
});

View File

@@ -4,11 +4,19 @@ import {
SelectWithSearchFlagOptionType,
} from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { LlmModelType } from '@/constants/knowledge';
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
import { isEmpty } from 'lodash';
import { useEffect, useMemo } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { LanguageFormField, ParserMethodFormField } from './common-form-fields';
import {
LanguageFormField,
LargeModelFormField,
ParserMethodFormField,
RmdirFormField,
TwoColumnCheckFormField,
} from './common-form-fields';
import { CommonProps } from './interface';
import { useSetInitialLanguage } from './use-set-initial-language';
import { buildFieldNameWithPrefix } from './utils';
@@ -28,7 +36,9 @@ export function PdfFormFields({ prefix }: CommonProps) {
const form = useFormContext();
const parseMethodName = buildFieldNameWithPrefix('parse_method', prefix);
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Image2text,
]);
const parseMethod = useWatch({
name: parseMethodName,
});
@@ -88,7 +98,14 @@ export function PdfFormFields({ prefix }: CommonProps) {
return (
<>
<TwoColumnCheckFormField prefix={prefix} />
<RmdirFormField prefix={prefix} />
<ParserMethodFormField prefix={prefix}></ParserMethodFormField>
<LargeModelFormField
prefix={prefix}
options={modelOptions}
></LargeModelFormField>
{languageShown && <LanguageFormField prefix={prefix}></LanguageFormField>}
{tcadpOptionsShown && (
<>

View File

@@ -4,11 +4,16 @@ import {
SelectWithSearchFlagOptionType,
} from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { LlmModelType } from '@/constants/knowledge';
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
import { isEmpty } from 'lodash';
import { useEffect, useMemo } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { ParserMethodFormField } from './common-form-fields';
import {
LargeModelFormField,
ParserMethodFormField,
} from './common-form-fields';
import { CommonProps } from './interface';
import { buildFieldNameWithPrefix } from './utils';
@@ -25,6 +30,9 @@ const markdownImageResponseTypeOptions: SelectWithSearchFlagOptionType[] = [
export function SpreadsheetFormFields({ prefix }: CommonProps) {
const { t } = useTranslation();
const form = useFormContext();
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Image2text,
]);
const parseMethodName = buildFieldNameWithPrefix('parse_method', prefix);
@@ -89,6 +97,10 @@ export function SpreadsheetFormFields({ prefix }: CommonProps) {
prefix={prefix}
optionsWithoutLLM={optionsWithoutLLM}
></ParserMethodFormField>
<LargeModelFormField
prefix={prefix}
options={modelOptions}
></LargeModelFormField>
{tcadpOptionsShown && (
<>
<RAGFlowFormItem

View File

@@ -0,0 +1,24 @@
import { LlmModelType } from '@/constants/knowledge';
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
import { LargeModelFormField, RmdirFormField } from './common-form-fields';
import { CommonProps } from './interface';
export function TextMarkdownFormFields({ prefix }: CommonProps) {
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Image2text,
]);
return (
<>
<RmdirFormField prefix={prefix} />
<LargeModelFormField
prefix={prefix}
options={modelOptions}
></LargeModelFormField>
</>
);
}
export function HtmlFormFields({ prefix }: CommonProps) {
return <RmdirFormField prefix={prefix} />;
}

View File

@@ -0,0 +1,24 @@
import { LlmModelType } from '@/constants/knowledge';
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
import {
LargeModelFormField,
OutputFormatFormFieldProps,
RmdirFormField,
} from './common-form-fields';
export function WordFormFields({ prefix }: OutputFormatFormFieldProps) {
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Image2text,
]);
return (
<>
<RmdirFormField prefix={prefix} />
{/* Multimodal Model */}
<LargeModelFormField
prefix={prefix}
options={modelOptions}
></LargeModelFormField>
</>
);
}

View File

@@ -0,0 +1,197 @@
import { isEmpty } from 'lodash';
import { useEffect, useMemo } from 'react';
import { UseFormReturn, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { TitleChunkerFormSchemaType } from '.';
import { Hierarchy, initialTitleChunkerValues } from '../../constant/pipeline';
// type initialValuesType = typeof initialHierarchicalMergerValues;
function transformLevelsToRules(levels: any[]) {
if (!Array.isArray(levels)) {
return initialTitleChunkerValues.rules;
}
return levels
.map((levelGroup) => {
if (Array.isArray(levelGroup)) {
const filteredExpressions = levelGroup.filter(
(expression: string) => expression && expression.trim() !== '',
);
if (filteredExpressions.length === 0) {
return null;
}
return {
levels: filteredExpressions.map((expression: string) => ({
expression,
})),
};
}
return { levels: [{ expression: '' }] };
})
.filter((rule) => rule !== null);
}
function filterEmptyRules(rules: any[]) {
if (!Array.isArray(rules)) {
return [];
}
return rules
.map((rule) => {
if (!rule || !Array.isArray(rule.levels)) {
return null;
}
const filteredLevels = rule.levels.filter(
(level: any) => level.expression && level.expression.trim() !== '',
);
if (filteredLevels.length === 0) {
return null;
}
return { levels: filteredLevels };
})
.filter((rule) => rule !== null);
}
// function isRulesFormatCorrect(rules: any): boolean {
// if (!rules || !Array.isArray(rules)) {
// return false;
// }
// if (rules.length === 0) {
// return false;
// }
// if (!rules[0] || typeof rules[0] !== 'object') {
// return false;
// }
// if (!Array.isArray(rules[0].levels)) {
// return false;
// }
// return true;
// }
function transformApiResponseToForm(
apiData: Record<string, any>,
): TitleChunkerFormSchemaType {
if (!apiData) {
return apiData;
}
if (isEmpty(apiData)) {
return apiData as TitleChunkerFormSchemaType;
}
const method = apiData.method as 'hierarchy' | 'group';
let hierarchy = apiData.hierarchy;
if (typeof hierarchy === 'number') {
hierarchy = String(hierarchy);
}
if (method === 'group' && !hierarchy) {
hierarchy = '0';
}
let rules = apiData.rules;
const hasLevelsData = apiData.levels && Array.isArray(apiData.levels);
if (hasLevelsData) {
rules = transformLevelsToRules(apiData.levels);
} else if (rules && Array.isArray(rules)) {
rules = filterEmptyRules(rules);
}
// const rulesFormatCorrect = isRulesFormatCorrect(rules);
// if (method === 'group') {
// if (rulesFormatCorrect) {
// return {
// method,
// hierarchy,
// rules,
// };
// }
// return {
// method,
// hierarchy,
// rules,
// };
// }
// if (rulesFormatCorrect && method === 'hierarchy') {
// return {
// method,
// hierarchy,
// rules,
// };
// }
return {
method,
hierarchy,
include_heading_content: Boolean(apiData.include_heading_content),
rules,
};
}
type HierarchyOption = {
label: string;
value: string;
};
function getDynamicHierarchyOptions(maxLevel: number): HierarchyOption[] {
if (maxLevel < 1) {
maxLevel = 1;
}
return Array.from({ length: maxLevel }, (_, i) => ({
label: `H${i + 1}`,
value: String(i + 1) as Hierarchy,
}));
}
function calculateMaxLevelCount(
rules: Array<{ levels: Array<{ expression: string }> }>,
): number {
if (!rules || rules.length === 0) {
return 1;
}
return Math.max(...rules.map((rule) => rule.levels.length), 1);
}
export function useDynamicHierarchyOptions(
form: UseFormReturn<any>,
name: string,
): HierarchyOption[] {
const { t } = useTranslation();
const rules = useWatch({ name, control: form?.control });
const method = useWatch({ name: 'method', control: form?.control });
const currentHierarchy = form.watch('hierarchy');
const hierarchyOptions = useMemo(() => {
const maxLevelCount = calculateMaxLevelCount(rules);
const options = getDynamicHierarchyOptions(maxLevelCount);
if (method === 'group') {
return [
{ label: t('common.automatic', 'Automatic'), value: '0' },
...options,
];
}
return options;
}, [method, rules, t]);
useEffect(() => {
if (!currentHierarchy || !form) {
return;
}
const maxOptionValue = hierarchyOptions[hierarchyOptions.length - 1]?.value;
if (maxOptionValue && currentHierarchy > maxOptionValue) {
form.setValue('hierarchy', maxOptionValue);
}
}, [currentHierarchy, hierarchyOptions, form]);
return hierarchyOptions;
}
export { transformApiResponseToForm };

View File

@@ -0,0 +1,376 @@
import { FormFieldType, RenderField } from '@/components/dynamic-form';
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { BlockButton, Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
import { Form } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trash2 } from 'lucide-react';
import { memo, useEffect, useRef } from 'react';
import { useFieldArray, useForm, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import {
Hierarchy,
initialGroupValues,
initialTitleChunkerValues,
} from '../../constant/pipeline';
import { useFormValues } from '../../hooks/use-form-values';
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
import { INextOperatorForm } from '../../interface';
import { buildOutputList } from '../../utils/build-output-list';
import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output';
import { transformApiResponseToForm, useDynamicHierarchyOptions } from './hook';
type FormModeValues = {
hierarchy?: string;
include_heading_content?: boolean;
rules: Array<{ levels: Array<{ expression: string }> }>;
};
const outputList = buildOutputList(initialTitleChunkerValues.outputs);
const rulesSchema = z.array(
z.object({
levels: z.array(
z.object({
expression: z.string().refine(
(val) => {
try {
new RegExp(val);
return true;
} catch {
return false;
}
},
{
message: 'Must be a valid regular expression string',
},
),
}),
),
}),
);
export const FormSchema = z.object({
method: z.enum(['hierarchy', 'group']),
hierarchy: z.string().optional(),
include_heading_content: z.boolean().optional(),
rules: rulesSchema,
});
export type TitleChunkerFormSchemaType = z.infer<typeof FormSchema>;
type LevelItemProps = {
index: number;
parentName: string;
removeParent: (index: number) => void;
isLatest: boolean;
};
function LevelItem({
index,
parentName,
isLatest,
removeParent,
}: LevelItemProps) {
const { t } = useTranslation();
const name = `${parentName}.${index}.expression`;
return (
<div className="flex items-center">
<div className="flex-1">
<RAGFlowFormItem
name={name}
label={`${t('flow.regularExpressions')} H${index + 1}`}
// labelClassName="!hidden"
>
<Input className="!m-0" />
</RAGFlowFormItem>
</div>
{isLatest && index > 0 && (
<Button
className="self-end"
type="button"
variant={'ghost'}
size="sm"
onClick={() => removeParent(index)}
>
<Trash2 className="h-3 w-3" />
</Button>
)}
</div>
);
}
type CardBodyProps = {
cardIndex: number;
cardName: string;
};
function CardBody({ cardName }: CardBodyProps) {
const { t } = useTranslation();
const form = useFormContext();
const levelsName = `${cardName}.levels`;
const {
fields: levelFields,
append: appendLevel,
remove: removeLevel,
} = useFieldArray({
name: levelsName,
control: form.control,
});
return (
<CardContent className="p-4">
<div className="space-y-4">
{levelFields.map((levelField, levelIndex) => (
<LevelItem
key={levelField.id}
parentName={levelsName}
index={levelIndex}
removeParent={removeLevel}
isLatest={levelIndex === levelFields.length - 1}
/>
))}
</div>
<BlockButton
onClick={() => appendLevel({ expression: '' })}
className="mt-4"
>
{t('flow.addLevel', 'Add Level')}
</BlockButton>
</CardContent>
);
}
// type GroupCardBodyProps = {
// cardName: string;
// };
// function GroupCardBody({ cardName }: GroupCardBodyProps) {
// const { t } = useTranslation();
// const form = useFormContext();
// const levelsName = `${cardName}.levels`;
// const { fields: levelFields } = useFieldArray({
// name: levelsName,
// control: form.control,
// });
// return (
// <CardContent className="p-4">
// <div className="space-y-4">
// {levelFields.map((levelField, levelIndex) => (
// <RAGFlowFormItem
// key={levelField.id}
// name={`${levelsName}.${levelIndex}.expression`}
// label={`${t('flow.regularExpressions')}`}
// >
// <Input />
// </RAGFlowFormItem>
// ))}
// </div>
// </CardContent>
// );
// }
const TitleChunkerForm = ({ node }: INextOperatorForm) => {
const { t } = useTranslation();
const initialValues = useFormValues(initialTitleChunkerValues, node);
const hierarchyModeValues = useRef<FormModeValues | null>(null);
const groupValues = useRef<FormModeValues | null>(null);
const form = useForm<TitleChunkerFormSchemaType>({
defaultValues: transformApiResponseToForm(initialValues),
resolver: zodResolver(FormSchema),
mode: 'onChange',
});
const isInitialized = useRef(false);
const initialMode = useRef<string | undefined>(undefined);
const method = form.watch('method');
const name = 'rules';
const hierarchyOptions = useDynamicHierarchyOptions(form, name);
useEffect(() => {
if (!isInitialized.current) {
initialMode.current = method;
isInitialized.current = true;
return;
}
if (method !== initialMode.current) {
const currentMode = initialMode.current;
const hierarchyValue = form.getValues('hierarchy');
const rulesValue = form.getValues('rules');
if (currentMode === 'hierarchy') {
hierarchyModeValues.current = {
hierarchy: hierarchyValue,
include_heading_content: form.getValues('include_heading_content'),
rules: rulesValue,
};
} else if (currentMode === 'group') {
groupValues.current = {
hierarchy: hierarchyValue,
include_heading_content: form.getValues('include_heading_content'),
rules: rulesValue,
};
}
initialMode.current = method;
if (method === 'group') {
const modeValues = groupValues.current;
form.reset({
method: 'group',
hierarchy: modeValues?.hierarchy ?? '0',
include_heading_content: false,
rules: modeValues?.rules || initialGroupValues.rules,
});
} else {
const defaultHierarchy = Hierarchy.H3;
let modeValues: FormModeValues | null = null;
modeValues = hierarchyModeValues.current;
if (modeValues) {
form.reset({
method: method,
hierarchy: modeValues.hierarchy || defaultHierarchy,
include_heading_content:
modeValues.include_heading_content || false,
rules: modeValues.rules,
});
} else {
const newModeValues: FormModeValues = {
hierarchy: defaultHierarchy,
include_heading_content: false,
rules: JSON.parse(JSON.stringify(initialTitleChunkerValues.rules)),
};
form.reset({
method: method,
hierarchy: defaultHierarchy,
include_heading_content: newModeValues.include_heading_content,
rules: newModeValues.rules,
});
}
}
}
}, [method, form]);
const { fields, append, remove } = useFieldArray({
name: name,
control: form.control,
});
useWatchFormChange(node?.id, form);
return (
<Form {...form}>
<FormWrapper>
<RenderField
field={{
name: 'method',
type: FormFieldType.Segmented,
label: '',
options: [
{ label: t('flow.hierarchy'), value: 'hierarchy' },
// { label: t('flow.tree', 'Tree'), value: 'tree' },
{ label: t('flow.group', 'Group'), value: 'group' },
],
}}
/>
<RAGFlowFormItem name={'hierarchy'} label={''}>
<SelectWithSearch options={hierarchyOptions}></SelectWithSearch>
</RAGFlowFormItem>
{method === 'hierarchy' && (
<RAGFlowFormItem
name="include_heading_content"
label={t('flow.includeHeadingContent', 'Include heading content')}
tooltip={t(
'flow.includeHeadingContentTip',
'When enabled, content directly under a heading is kept as its own chunk. Child chunks keep only the heading path.',
)}
horizontal={true}
labelClassName="w-[200px]"
>
{(field) => (
<Checkbox
checked={field.value}
onCheckedChange={(checked) => {
field.onChange?.(checked);
}}
/>
)}
</RAGFlowFormItem>
)}
{/* {method === 'group' ? (
<Card>
<CardHeader className="flex flex-row justify-between items-center py-3 px-4 border-b bg-muted/20">
<span className="font-medium text-sm">
{t('flow.rule', 'Rule')} 1
</span>
</CardHeader>
<GroupCardBody cardName={`${name}.0`} />
</Card>
) : ( */}
<div className="space-y-4">
{fields.map((cardField, cardIndex) => (
<Card key={cardField.id}>
<CardHeader className="flex flex-row justify-between items-center py-3 px-4 border-b bg-muted/20">
<div className="flex items-center gap-2">
<span className="font-medium text-sm">
{t('flow.rule', 'Rule')} {cardIndex + 1}
</span>
</div>
{fields.length > 1 && (
<Button
type="button"
variant={'ghost'}
size="sm"
onClick={() => remove(cardIndex)}
className="h-7 w-7 p-0 text-muted-foreground hover:text-destructive"
>
<Trash2 className="h-4 w-4" />
</Button>
)}
</CardHeader>
<CardBody
cardIndex={cardIndex}
cardName={`${name}.${cardIndex}`}
/>
</Card>
))}
</div>
{/* )} */}
{/* {method !== 'group' && ( */}
<BlockButton
onClick={() =>
append({
levels: [{ expression: '' }],
})
}
className="mt-4"
>
{t('flow.rule', 'Add Rule')}
</BlockButton>
{/* )} */}
</FormWrapper>
<div className="p-5">
<Output list={outputList}></Output>
</div>
</Form>
);
};
export default memo(TitleChunkerForm);

View File

@@ -1,4 +1,5 @@
import { DelimiterInput } from '@/components/delimiter-form-field';
import { FormFieldType, RenderField } from '@/components/dynamic-form';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { SliderInputFormField } from '@/components/slider-input-form-field';
import { BlockButton, Button } from '@/components/ui/button';
@@ -10,7 +11,7 @@ import { memo } from 'react';
import { useFieldArray, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { initialSplitterValues } from '../../constant/pipeline';
import { initialTokenChunkerValues } from '../../constant/pipeline';
import { useFormValues } from '../../hooks/use-form-values';
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
import { INextOperatorForm } from '../../interface';
@@ -18,7 +19,7 @@ import { buildOutputList } from '../../utils/build-output-list';
import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output';
const outputList = buildOutputList(initialSplitterValues.outputs);
const outputList = buildOutputList(initialTokenChunkerValues.outputs);
export const FormSchema = z.object({
chunk_token_size: z.number(),
@@ -34,19 +35,27 @@ export const FormSchema = z.object({
value: z.string().optional(),
}),
),
overlapped_percent: z.number(), // 0.0 - 0.3 , 0% - 30%
overlapped_percent: z.number(),
delimiter_mode: z.enum(['token_size', 'delimiter']).optional(),
});
export type SplitterFormSchemaType = z.infer<typeof FormSchema>;
export type TokenChunkerFormSchemaType = z.infer<typeof FormSchema>;
const SplitterForm = ({ node }: INextOperatorForm) => {
const defaultValues = useFormValues(initialSplitterValues, node);
const TokenChunkerForm = ({ node }: INextOperatorForm) => {
const defaultValues = useFormValues(initialTokenChunkerValues, node);
const { t } = useTranslation();
const formDefaultValues = {
...defaultValues,
delimiter_mode: defaultValues.delimiter_mode || 'token_size',
};
const form = useForm<SplitterFormSchemaType>({
defaultValues,
defaultValues: formDefaultValues,
resolver: zodResolver(FormSchema),
});
const delimiterMode = form.watch('delimiter_mode');
const name = 'delimiters';
const { fields, append, remove } = useFieldArray({
@@ -64,52 +73,73 @@ const SplitterForm = ({ node }: INextOperatorForm) => {
return (
<Form {...form}>
<FormWrapper>
<SliderInputFormField
name="chunk_token_size"
max={2048}
label={t('knowledgeConfiguration.chunkTokenNumber')}
></SliderInputFormField>
<SliderInputFormField
name="overlapped_percent"
max={30}
min={0}
label={t('flow.overlappedPercent')}
></SliderInputFormField>
<SliderInputFormField
name="image_table_context_window"
max={256}
min={0}
label={t('knowledgeConfiguration.imageTableContextWindow')}
tooltip={t('knowledgeConfiguration.imageTableContextWindowTip')}
></SliderInputFormField>
<section>
<span className="mb-2 inline-block">{t('flow.delimiters')}</span>
<div className="space-y-4">
{fields.map((field, index) => (
<div key={field.id} className="flex items-center gap-2">
<div className="space-y-2 flex-1">
<RAGFlowFormItem
name={`${name}.${index}.value`}
label="delimiter"
labelClassName="!hidden"
>
<DelimiterInput className="!m-0"></DelimiterInput>
</RAGFlowFormItem>
</div>
<Button
type="button"
variant={'ghost'}
onClick={() => remove(index)}
>
<Trash2 />
</Button>
<RenderField
field={{
name: 'delimiter_mode',
type: FormFieldType.Segmented,
label: '',
options: [
{ label: 'Token Size', value: 'token_size' },
{ label: t('flow.delimiters'), value: 'delimiter' },
],
}}
/>
{delimiterMode === 'token_size' && (
<>
<SliderInputFormField
name="chunk_token_size"
max={2048}
label={t('knowledgeConfiguration.chunkTokenNumber')}
/>
<SliderInputFormField
name="overlapped_percent"
max={30}
min={0}
label={t('flow.overlappedPercent')}
/>
<SliderInputFormField
name="image_table_context_window"
max={256}
min={0}
label={t('knowledgeConfiguration.imageTableContextWindow')}
tooltip={t('knowledgeConfiguration.imageTableContextWindowTip')}
/>
</>
)}
{delimiterMode === 'delimiter' && (
<>
<section>
<span className="mb-2 inline-block">{t('flow.delimiters')}</span>
<div className="space-y-4">
{fields.map((field, index) => (
<div key={field.id} className="flex items-center gap-2">
<div className="space-y-2 flex-1">
<RAGFlowFormItem
name={`${name}.${index}.value`}
label="delimiter"
labelClassName="!hidden"
>
<DelimiterInput className="!m-0"></DelimiterInput>
</RAGFlowFormItem>
</div>
<Button
type="button"
variant={'ghost'}
onClick={() => remove(index)}
>
<Trash2 />
</Button>
</div>
))}
</div>
))}
</div>
</section>
<BlockButton onClick={() => append({ value: '\n' })}>
{t('common.add')}
</BlockButton>
</section>
<BlockButton onClick={() => append({ value: '\n' })}>
{t('common.add')}
</BlockButton>
</>
)}
<fieldset>
<div className="mb-2 flex justify-between items-center gap-1">
@@ -171,4 +201,4 @@ const SplitterForm = ({ node }: INextOperatorForm) => {
);
};
export default memo(SplitterForm);
export default memo(TokenChunkerForm);

View File

@@ -24,7 +24,6 @@ import {
initialGithubValues,
initialGoogleScholarValues,
initialGoogleValues,
initialHierarchicalMergerValues,
initialInvokeValues,
initialIterationStartValues,
initialIterationValues,
@@ -38,11 +37,12 @@ import {
initialRetrievalValues,
initialRewriteQuestionValues,
initialSearXNGValues,
initialSplitterValues,
initialStringTransformValues,
initialSwitchValues,
initialTavilyExtractValues,
initialTavilyValues,
initialTitleChunkerValues,
initialTokenChunkerValues,
initialTokenizerValues,
initialUserFillUpValues,
initialVariableAggregatorValues,
@@ -165,8 +165,8 @@ export const useInitializeOperatorParams = () => {
[Operator.File]: {},
[Operator.Parser]: initialParserValues,
[Operator.Tokenizer]: initialTokenizerValues,
[Operator.Splitter]: initialSplitterValues,
[Operator.HierarchicalMerger]: initialHierarchicalMergerValues,
[Operator.TokenChunker]: initialTokenChunkerValues,
[Operator.TitleChunker]: initialTitleChunkerValues,
[Operator.Extractor]: {
...initialExtractorValues,
llm_id: llmId,

View File

@@ -39,9 +39,9 @@ import {
import { BeginFormSchemaType } from './form/begin-form/schema';
import { DataOperationsFormSchemaType } from './form/data-operations-form';
import { ExtractorFormSchemaType } from './form/extractor-form';
import { HierarchicalMergerFormSchemaType } from './form/hierarchical-merger-form';
import { ParserFormSchemaType } from './form/parser-form';
import { SplitterFormSchemaType } from './form/splitter-form';
import { TitleChunkerFormSchemaType } from './form/title-chunker-form';
import { TokenChunkerFormSchemaType } from './form/token-chunker-form';
import { BeginQuery, IPosition } from './interface';
function buildAgentExceptionGoto(edges: Edge[], nodeId: string) {
@@ -211,7 +211,10 @@ function transformParserParams(params: ParserFormSchemaType) {
>((pre, cur) => {
if (cur.fileFormat) {
let filteredSetup: Partial<
ParserFormSchemaType['setups'][0] & { suffix: string[] }
ParserFormSchemaType['setups'][0] & { suffix: string[] } & {
two_column_check: boolean;
enable_multi_column: boolean;
}
> = {
output_format: cur.output_format,
preprocess: cur.preprocess,
@@ -224,6 +227,9 @@ function transformParserParams(params: ParserFormSchemaType) {
...filteredSetup,
parse_method: cur.parse_method,
lang: cur.lang,
vlm: { llm_id: cur.vlm?.llm_id },
enable_multi_column: cur.enable_multi_column,
remove_toc: cur.remove_toc,
};
// Only include TCADP parameters if TCADP Parser is selected
if (cur.parse_method?.toLowerCase() === 'tcadp parser') {
@@ -236,6 +242,7 @@ function transformParserParams(params: ParserFormSchemaType) {
filteredSetup = {
...filteredSetup,
parse_method: cur.parse_method,
vlm: { llm_id: cur.vlm?.llm_id },
};
// Only include TCADP parameters if TCADP Parser is selected
if (cur.parse_method?.toLowerCase() === 'tcadp parser') {
@@ -271,10 +278,12 @@ function transformParserParams(params: ParserFormSchemaType) {
};
break;
case FileType.Video:
case FileType.Docx:
case FileType.Audio:
case FileType.TextMarkdown:
filteredSetup = {
...filteredSetup,
llm_id: cur.llm_id,
vlm: { llm_id: cur.vlm?.llm_id },
};
break;
default:
@@ -289,13 +298,16 @@ function transformParserParams(params: ParserFormSchemaType) {
return { ...params, setups };
}
function transformSplitterParams(params: SplitterFormSchemaType) {
function transformTokenChunkerParams(params: TokenChunkerFormSchemaType) {
const { image_table_context_window, ...rest } = params;
const imageTableContextWindow = Number(image_table_context_window || 0);
return {
...rest,
overlapped_percent: Number(params.overlapped_percent) / 100,
delimiters: transformObjectArrayToPureArray(params.delimiters, 'value'),
delimiters:
params.delimiter_mode === 'delimiter'
? transformObjectArrayToPureArray(params.delimiters, 'value')
: [],
table_context_size: imageTableContextWindow,
image_context_size: imageTableContextWindow,
@@ -306,14 +318,17 @@ function transformSplitterParams(params: SplitterFormSchemaType) {
};
}
function transformHierarchicalMergerParams(
params: HierarchicalMergerFormSchemaType,
) {
const levels = params.levels.map((x) =>
transformObjectArrayToPureArray(x.expressions, 'expression'),
function transformTitleChunkerParams(params: TitleChunkerFormSchemaType) {
const levels = params.rules.map((rule) =>
transformObjectArrayToPureArray(rule.levels, 'expression'),
);
return { ...params, hierarchy: Number(params.hierarchy), levels };
return {
method: params.method,
hierarchy: Number(params.hierarchy || 0),
include_heading_content: Boolean(params.include_heading_content),
levels,
};
}
function transformExtractorParams(params: ExtractorFormSchemaType) {
@@ -437,12 +452,12 @@ export const buildDslComponentsByGraph = (
params = transformParserParams(params);
break;
case Operator.Splitter:
params = transformSplitterParams(params);
case Operator.TokenChunker:
params = transformTokenChunkerParams(params);
break;
case Operator.HierarchicalMerger:
params = transformHierarchicalMergerParams(params);
case Operator.TitleChunker:
params = transformTitleChunkerParams(params);
break;
case Operator.Extractor:
params = transformExtractorParams(params);

View File

@@ -32,12 +32,12 @@ export const TimelineNodeObj = {
title: 'Context Generator',
icon: <LucideFileStack className="size-[1em]" />,
},
[TimelineNodeType.titleSplitter]: {
title: 'Title Splitter',
[TimelineNodeType.titleChunker]: {
title: 'Title Chunker',
icon: <LucideHeading className="size-[1em]" />,
},
[TimelineNodeType.characterSplitter]: {
title: 'Character Splitter',
[TimelineNodeType.tokenChunker]: {
title: 'Token Chunker',
icon: <LucideBlocks className="size-[1em]" />,
},
[TimelineNodeType.tokenizer]: {

View File

@@ -7,8 +7,8 @@ export enum TimelineNodeType {
begin = 'file',
parser = 'parser',
contextGenerator = 'extractor',
titleSplitter = 'hierarchicalMerger',
characterSplitter = 'splitter',
titleChunker = 'titleChunker',
tokenChunker = 'tokenChunker',
tokenizer = 'tokenizer',
end = 'end',
}

View File

@@ -233,10 +233,10 @@ export const useTimelineDataFlow = (data: IPipelineFileLogDetail) => {
} else if (name === TimelineNodeType.tokenizer) {
tempType = TimelineNodeType.tokenizer;
} else if (
name === TimelineNodeType.characterSplitter ||
name === TimelineNodeType.titleSplitter
name === TimelineNodeType.tokenChunker ||
name === TimelineNodeType.titleChunker
) {
tempType = TimelineNodeType.characterSplitter;
tempType = name;
}
const timeNode = {
...TimelineNodeObj[name],

View File

@@ -205,8 +205,8 @@ const DataflowResult = () => {
)} */}
{/* {currentTimeNode?.type === TimelineNodeType.parser && ( */}
{(currentTimeNode?.type === TimelineNodeType.parser ||
currentTimeNode?.type === TimelineNodeType.characterSplitter ||
currentTimeNode?.type === TimelineNodeType.titleSplitter ||
currentTimeNode?.type === TimelineNodeType.tokenChunker ||
currentTimeNode?.type === TimelineNodeType.titleChunker ||
currentTimeNode?.type === TimelineNodeType.contextGenerator) && (
<ParserContainer
isReadonly={isReadOnly}

View File

@@ -130,8 +130,8 @@ const ParserContainer = (props: IProps) => {
);
const isChunck =
step?.type === TimelineNodeType.characterSplitter ||
step?.type === TimelineNodeType.titleSplitter;
step?.type === TimelineNodeType.tokenChunker ||
step?.type === TimelineNodeType.titleChunker;
const handleCreateChunk = useCallback(
(text: string) => {
@@ -214,8 +214,8 @@ const ParserContainer = (props: IProps) => {
isChunck={isChunck}
textMode={textMode}
isDelete={
step?.type === TimelineNodeType.characterSplitter ||
step?.type === TimelineNodeType.titleSplitter
step?.type === TimelineNodeType.tokenChunker ||
step?.type === TimelineNodeType.titleChunker
}
clickChunk={clickChunk}
handleCheckboxClick={handleCheckboxClick}