;
+export type IAgentNode = BaseNode;
export type RAGFlowNodeType =
| IBeginNode
diff --git a/web/src/interfaces/database/user-setting.ts b/web/src/interfaces/database/user-setting.ts
index 24c6257cafe..ff4094d4b4a 100644
--- a/web/src/interfaces/database/user-setting.ts
+++ b/web/src/interfaces/database/user-setting.ts
@@ -16,6 +16,7 @@ export interface IUserInfo {
nickname: string;
password: string;
status: string;
+ timezone: string;
update_date: string;
update_time: number;
}
diff --git a/web/src/interfaces/request/base.ts b/web/src/interfaces/request/base.ts
index b780abe8dec..789be810462 100644
--- a/web/src/interfaces/request/base.ts
+++ b/web/src/interfaces/request/base.ts
@@ -1,7 +1,7 @@
export interface IPaginationRequestBody {
keywords?: string;
page?: number;
- page_size?: number; // name|create|doc_num|create_time|update_time,default:create_time
+ page_size?: number; // name|create|doc_num|create_time|update_time, default:create_time
orderby?: string;
desc?: string;
}
diff --git a/web/src/locales/de.ts b/web/src/locales/de.ts
index 858bb4b0c77..572c6ff340e 100644
--- a/web/src/locales/de.ts
+++ b/web/src/locales/de.ts
@@ -144,7 +144,7 @@ export default {
toMessage: 'Endseitennummer fehlt (ausgeschlossen)',
layoutRecognize: 'Dokumentenparser',
layoutRecognizeTip:
- 'Verwendet ein visuelles Modell für die PDF-Layout-Analyse, um Dokumententitel, Textblöcke, Bilder und Tabellen effektiv zu lokalisieren. Wenn die einfache Option gewählt wird, wird nur der reine Text im PDF abgerufen. Bitte beachten Sie, dass diese Option derzeit NUR für PDF-Dokumente funktioniert.',
+ 'Verwendet ein visuelles Modell für die PDF-Layout-Analyse, um Dokumententitel, Textblöcke, Bilder und Tabellen effektiv zu lokalisieren. Wenn die einfache Option gewählt wird, wird nur der reine Text im PDF abgerufen. Bitte beachten Sie, dass diese Option derzeit NUR für PDF-Dokumente funktioniert. Weitere Informationen finden Sie unter https://ragflow.io/docs/dev/select_pdf_parser.',
taskPageSize: 'Aufgabenseitengröße',
taskPageSizeMessage: 'Bitte geben Sie die Größe der Aufgabenseite ein!',
taskPageSizeTip:
@@ -176,10 +176,10 @@ export default {
'Verwenden Sie dies zusammen mit der General-Schnittmethode. Wenn deaktiviert, werden Tabellenkalkulationsdateien (XLSX, XLS (Excel 97-2003)) zeilenweise in Schlüssel-Wert-Paare analysiert. Wenn aktiviert, werden Tabellenkalkulationsdateien in HTML-Tabellen umgewandelt. Wenn die ursprüngliche Tabelle mehr als 12 Zeilen enthält, teilt das System sie automatisch alle 12 Zeilen in mehrere HTML-Tabellen auf. Für weitere Informationen siehe https://ragflow.io/docs/dev/enable_excel2html.',
autoKeywords: 'Auto-Schlüsselwort',
autoKeywordsTip:
- 'Extrahieren Sie automatisch N Schlüsselwörter für jeden Abschnitt, um deren Ranking in Abfragen mit diesen Schlüsselwörtern zu verbessern. Beachten Sie, dass zusätzliche Tokens vom in den "Systemmodelleinstellungen" angegebenen Chat-Modell verbraucht werden. Sie können die hinzugefügten Schlüsselwörter eines Abschnitts in der Abschnittsliste überprüfen oder aktualisieren.',
+ 'Extrahieren Sie automatisch N Schlüsselwörter für jeden Abschnitt, um deren Ranking in Abfragen mit diesen Schlüsselwörtern zu verbessern. Beachten Sie, dass zusätzliche Tokens vom in den "Systemmodelleinstellungen" angegebenen Chat-Modell verbraucht werden. Sie können die hinzugefügten Schlüsselwörter eines Abschnitts in der Abschnittsliste überprüfen oder aktualisieren. Für weitere Informationen siehe https://ragflow.io/docs/dev/autokeyword_autoquestion.',
autoQuestions: 'Auto-Frage',
autoQuestionsTip:
- 'Um die Ranking-Ergebnisse zu verbessern, extrahieren Sie N Fragen für jeden Wissensdatenbank-Chunk mithilfe des im "Systemmodell-Setup" definierten Chatmodells. Beachten Sie, dass dies zusätzliche Token verbraucht. Die Ergebnisse können in der Chunk-Liste eingesehen und bearbeitet werden. Fehler bei der Fragenextraktion blockieren den Chunking-Prozess nicht; leere Ergebnisse werden dem ursprünglichen Chunk hinzugefügt.',
+ 'Um die Ranking-Ergebnisse zu verbessern, extrahieren Sie N Fragen für jeden Wissensdatenbank-Chunk mithilfe des im "Systemmodell-Setup" definierten Chatmodells. Beachten Sie, dass dies zusätzliche Token verbraucht. Die Ergebnisse können in der Chunk-Liste eingesehen und bearbeitet werden. Fehler bei der Fragenextraktion blockieren den Chunking-Prozess nicht; leere Ergebnisse werden dem ursprünglichen Chunk hinzugefügt. Für weitere Informationen siehe https://ragflow.io/docs/dev/autokeyword_autoquestion.',
redo: 'Möchten Sie die vorhandenen {{chunkNum}} Chunks löschen?',
setMetaData: 'Metadaten festlegen',
pleaseInputJson: 'Bitte JSON eingeben',
@@ -241,7 +241,7 @@ export default {
methodTitle: 'Beschreibung der Chunk-Methode',
methodExamples: 'Beispiele',
methodExamplesDescription:
- 'Die folgenden Screenshots dienen zur Verdeutlichung.',
+ 'Um Ihnen das Verständnis zu erleichtern, haben wir relevante Screenshots als Referenz bereitgestellt.',
dialogueExamplesTitle: 'Dialogbeispiele',
methodEmpty:
'Hier wird eine visuelle Erklärung der Wissensdatenbank-Kategorien angezeigt',
@@ -255,7 +255,7 @@ export default {
manual: `Nur PDF wird unterstützt.
Wir gehen davon aus, dass das Handbuch eine hierarchische Abschnittsstruktur aufweist und verwenden die Titel der untersten Abschnitte als Grundeinheit für die Aufteilung der Dokumente. Daher werden Abbildungen und Tabellen im selben Abschnitt nicht getrennt, was zu größeren Chunk-Größen führen kann.
`,
- naive: `Unterstützte Dateiformate sind DOCX, XLSX, XLS (Excel 97-2003), PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML .
+ naive: `Unterstützte Dateiformate sind MD, MDX, DOCX, XLSX, XLS (Excel 97-2003), PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML .
Diese Methode teilt Dateien mit einer 'naiven' Methode auf:
Verwenden eines Erkennungsmodells, um die Texte in kleinere Segmente aufzuteilen.
@@ -565,6 +565,7 @@ export default {
},
setting: {
profile: 'Profil',
+ avatar: 'Avatar',
profileDescription:
'Aktualisieren Sie hier Ihr Foto und Ihre persönlichen Daten.',
maxTokens: 'Maximale Tokens',
diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts
index 08b086e0f22..63fa4516369 100644
--- a/web/src/locales/en.ts
+++ b/web/src/locales/en.ts
@@ -162,7 +162,7 @@ export default {
cancel: 'Cancel',
rerankModel: 'Rerank model',
rerankPlaceholder: 'Please select',
- rerankTip: `If left empty, RAGFlow will use a combination of weighted keyword similarity and weighted vector cosine similarity; if a rerank model is selected, a weighted reranking score will replace the weighted vector cosine similarity. Please be aware that using a rerank model will significantly increase the system's response time.`,
+ rerankTip: `Optional. If left empty, RAGFlow will use a combination of weighted keyword similarity and weighted vector cosine similarity; if a rerank model is selected, a weighted reranking score will replace the weighted vector cosine similarity. Please be aware that using a rerank model will significantly increase the system's response time. If you wish to use a rerank model, ensure you use a SaaS reranker; if you prefer a locally deployed rerank model, ensure you start RAGFlow with docker-compose-gpu.yml.`,
topK: 'Top-K',
topKTip: `Used together with the Rerank model, this setting defines the number of text chunks to be sent to the specified reranking model.`,
delimiter: `Delimiter for text`,
@@ -171,9 +171,9 @@ export default {
html4excel: 'Excel to HTML',
html4excelTip: `Use with the General chunking method. When disabled, spreadsheets (XLSX or XLS(Excel 97-2003)) in the knowledge base will be parsed into key-value pairs. When enabled, they will be parsed into HTML tables, splitting every 12 rows if the original table has more than 12 rows.`,
autoKeywords: 'Auto-keyword',
- autoKeywordsTip: `Automatically extract N keywords for each chunk to increase their ranking for queries containing those keywords. Be aware that extra tokens will be consumed by the chat model specified in 'System model settings'. You can check or update the added keywords for a chunk from the chunk list. `,
+ autoKeywordsTip: `Automatically extract N keywords for each chunk to increase their ranking for queries containing those keywords. Be aware that extra tokens will be consumed by the chat model specified in 'System model settings'. You can check or update the added keywords for a chunk from the chunk list. For details, see https://ragflow.io/docs/dev/autokeyword_autoquestion.`,
autoQuestions: 'Auto-question',
- autoQuestionsTip: `Automatically extract N questions for each chunk to increase their ranking for queries containing those questions. You can check or update the added questions for a chunk from the chunk list. This feature will not disrupt the chunking process if an error occurs, except that it may add an empty result to the original chunk. Be aware that extra tokens will be consumed by the LLM specified in 'System model settings'.`,
+ autoQuestionsTip: `Automatically extract N questions for each chunk to increase their ranking for queries containing those questions. You can check or update the added questions for a chunk from the chunk list. This feature will not disrupt the chunking process if an error occurs, except that it may add an empty result to the original chunk. Be aware that extra tokens will be consumed by the LLM specified in 'System model settings'. For details, see https://ragflow.io/docs/dev/autokeyword_autoquestion.`,
redo: 'Do you want to clear the existing {{chunkNum}} chunks?',
setMetaData: 'Set Meta Data',
pleaseInputJson: 'Please enter JSON',
@@ -236,7 +236,7 @@ export default {
methodTitle: 'Chunking method description',
methodExamples: 'Examples',
methodExamplesDescription:
- 'The following screenshots are provided for clarity.',
+ 'The following screenshots are provided for clarification.',
dialogueExamplesTitle: 'view',
methodEmpty:
'This will display a visual explanation of the knowledge base categories',
@@ -250,7 +250,7 @@ export default {
manual: `Only PDF is supported.
We assume that the manual has a hierarchical section structure, using the lowest section titles as basic unit for chunking documents. Therefore, figures and tables in the same section will not be separated, which may result in larger chunk sizes.
`,
- naive: `Supported file formats are DOCX, XLSX, XLS (Excel 97-2003), PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML .
+ naive: `Supported file formats are MD, MDX, DOCX, XLSX, XLS (Excel 97-2003), PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML .
This method chunks files using a 'naive' method:
Use vision detection model to split the texts into smaller segments.
@@ -455,7 +455,8 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
modelTip: 'Large language chat model',
modelMessage: 'Please select!',
modelEnabledTools: 'Enabled tools',
- modelEnabledToolsTip: 'Please select one or more tools for the chat model to use. It takes no effect for models not supporting tool call.',
+ modelEnabledToolsTip:
+ 'Please select one or more tools for the chat model to use. It takes no effect for models not supporting tool call.',
freedom: 'Freedom',
improvise: 'Improvise',
precise: 'Precise',
@@ -550,6 +551,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
},
setting: {
profile: 'Profile',
+ avatar: 'Avatar',
profileDescription: 'Update your photo and personal details here.',
maxTokens: 'Max Tokens',
maxTokensMessage: 'Max Tokens is required',
@@ -788,7 +790,9 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
examples: 'Examples',
to: 'To',
msg: 'Messages',
- messagePlaceholder: 'message',
+ msgTip:
+ 'Output the variable content of the upstream component or the text entered by yourself.',
+ messagePlaceholder: `Please enter your message content, use '/' to quickly insert variables.`,
messageMsg: 'Please input message or delete this field.',
addField: 'Add option',
addMessage: 'Add message',
@@ -812,7 +816,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
relevantDescription: `A component that uses the LLM to assess whether the upstream output is relevant to the user's latest query. Ensure you specify the next component for each judge result.`,
rewriteQuestionDescription: `A component that rewrites a user query from the Interact component, based on the context of previous dialogues.`,
messageDescription:
- "A component that sends out a static message. If multiple messages are supplied, it randomly selects one to send. Ensure its downstream is 'Interact', the interface component.",
+ 'This component returns the final data output of the workflow along with predefined message content. ',
keywordDescription: `A component that retrieves top N search results from user's input. Ensure the TopN value is set properly before use.`,
switchDescription: `A component that evaluates conditions based on the output of previous components and directs the flow of execution accordingly. It allows for complex branching logic by defining cases and specifying actions for each case or default action if no conditions are met.`,
wikipediaDescription: `A component that searches from wikipedia.org, using TopN to specify the number of search results. It supplements the existing knowledge bases.`,
@@ -1268,14 +1272,26 @@ This delimiter is used to split the input text into several text pieces echo of
codeDescription: 'It allows developers to write custom Python logic.',
inputVariables: 'Input variables',
runningHintText: 'is running...🕞',
+ openingSwitch: 'Opening switch',
+ openingCopy: 'Opening copy',
+ openingSwitchTip:
+ 'Your users will see this welcome message at the beginning.',
+ modeTip: 'The mode defines how the workflow is initiated.',
+ beginInputTip:
+ 'By defining input parameters, this content can be accessed by other components in subsequent processes.',
+ query: 'Query variables',
+ agent: 'Agent',
+ agentDescription:
+ 'Builds agent components equipped with reasoning, tool usage, and multi-agent collaboration. ',
},
llmTools: {
bad_calculator: {
- name: "Calculator",
- description: "A tool to calculate the sum of two numbers (will give wrong answer)",
+ name: 'Calculator',
+ description:
+ 'A tool to calculate the sum of two numbers (will give wrong answer)',
params: {
- a: "The first number",
- b: "The second number",
+ a: 'The first number',
+ b: 'The second number',
},
},
},
diff --git a/web/src/locales/es.ts b/web/src/locales/es.ts
index 57a8a1f3ac3..274dbbbb11e 100644
--- a/web/src/locales/es.ts
+++ b/web/src/locales/es.ts
@@ -133,7 +133,7 @@ export default {
toMessage: 'Falta el número de página final (excluido)',
layoutRecognize: 'Reconocimiento de disposición',
layoutRecognizeTip:
- 'Usa modelos visuales para el análisis de disposición y así identificar mejor la estructura del documento, encontrar dónde están los títulos, bloques de texto, imágenes y tablas. Sin esta función, solo se obtendrá el texto plano del PDF.',
+ 'Usa modelos visuales para el análisis de disposición y así identificar mejor la estructura del documento, encontrar dónde están los títulos, bloques de texto, imágenes y tablas. Sin esta función, solo se obtendrá el texto plano del PDF. Para más información, consulte https://ragflow.io/docs/dev/select_pdf_parser.',
taskPageSize: 'Tamaño de la tarea por página',
taskPageSizeMessage:
'¡Por favor ingresa el tamaño de la tarea por página!',
@@ -151,7 +151,7 @@ export default {
cancel: 'Cancelar',
rerankModel: 'Modelo de reordenamiento',
rerankPlaceholder: 'Por favor selecciona',
- rerankTip: `Si está vacío, se utilizan los embeddings de la consulta y los fragmentos para calcular la similitud coseno del vector. De lo contrario, se usa la puntuación de reordenamiento en lugar de la similitud coseno del vector.`,
+ rerankTip: `Opcional. Si se deja vacío, RAGFlow utilizará una combinación de similitud ponderada de palabras clave y similitud ponderada del coseno vectorial; si se selecciona un modelo de reordenamiento, una puntuación ponderada de reordenamiento reemplazará la similitud ponderada del coseno vectorial. Tenga en cuenta que usar un modelo de reordenamiento aumentará significativamente el tiempo de respuesta del sistema. Si desea usar un modelo de reordenamiento, asegúrese de usar un reranker SaaS; si prefiere un modelo de reordenamiento desplegado localmente, asegúrese de iniciar RAGFlow con docker-compose-gpu.yml.`,
topK: 'Top-K',
topKTip: `Utilizado junto con el Rerank model, esta configuración define el número de fragmentos de texto que se enviarán al modelo reranking especificado.`,
delimiter: `Delimitadores para segmentación de texto`,
@@ -282,6 +282,7 @@ export default {
},
setting: {
profile: 'Perfil',
+ avatar: 'Avatar',
profileDescription: 'Actualiza tu foto y tus datos personales aquí.',
maxTokens: 'Máximo de tokens',
maxTokensMessage: 'El máximo de tokens es obligatorio',
diff --git a/web/src/locales/id.ts b/web/src/locales/id.ts
index dbd89561bea..60cff62bd2e 100644
--- a/web/src/locales/id.ts
+++ b/web/src/locales/id.ts
@@ -138,7 +138,7 @@ export default {
toMessage: 'Nomor halaman akhir hilang (tidak termasuk)',
layoutRecognize: 'Pengenalan tata letak',
layoutRecognizeTip:
- 'Gunakan model visual untuk analisis tata letak untuk lebih mengidentifikasi struktur dokumen, menemukan di mana judul, blok teks, gambar, dan tabel berada. Tanpa fitur ini, hanya teks biasa dari PDF yang dapat diperoleh.',
+ 'Gunakan model visual untuk analisis tata letak untuk lebih mengidentifikasi struktur dokumen, menemukan di mana judul, blok teks, gambar, dan tabel berada. Tanpa fitur ini, hanya teks biasa dari PDF yang dapat diperoleh. Untuk informasi lebih lanjut, lihat https://ragflow.io/docs/dev/select_pdf_parser.',
taskPageSize: 'Ukuran halaman tugas',
taskPageSizeMessage: 'Silakan masukkan ukuran halaman tugas Anda!',
taskPageSizeTip: `Jika menggunakan pengenalan tata letak, file PDF akan dibagi menjadi kelompok berturut-turut. Analisis tata letak akan dilakukan secara paralel antar kelompok untuk meningkatkan kecepatan pemrosesan. 'Ukuran halaman tugas' menentukan ukuran kelompok. Semakin besar ukuran halaman, semakin kecil kemungkinan teks berkelanjutan antara halaman dibagi menjadi potongan yang berbeda.`,
@@ -155,7 +155,7 @@ export default {
cancel: 'Batal',
rerankModel: 'Model Rerank',
rerankPlaceholder: 'Silakan pilih',
- rerankTip: `Jika kosong. Ini menggunakan embedding dari kueri dan potongan untuk menghitung kesamaan kosinus vektor. Jika tidak, ini menggunakan skor rerank sebagai pengganti kesamaan kosinus vektor.`,
+ rerankTip: `Opsional. Jika dikosongkan, RAGFlow akan menggunakan kombinasi kesamaan kata kunci berbobot dan kesamaan kosinus vektor berbobot; jika model rerank dipilih, skor reranking berbobot akan menggantikan kesamaan kosinus vektor berbobot. Harap diperhatikan bahwa menggunakan model rerank akan secara signifikan meningkatkan waktu respons sistem. Jika Anda ingin menggunakan model rerank, pastikan menggunakan SaaS reranker; jika Anda lebih memilih model rerank yang dijalankan secara lokal, pastikan memulai RAGFlow dengan docker-compose-gpu.yml.`,
topK: 'Top-K',
topKTip: `Digunakan bersama dengan Rerank model, pengaturan ini menentukan jumlah potongan teks yang akan dikirim ke model reranking yang ditentukan.`,
delimiter: `Pemisah untuk segmentasi teks`,
@@ -195,7 +195,7 @@ export default {
methodTitle: 'Deskripsi Metode Pemotongan',
methodExamples: 'Contoh',
methodExamplesDescription:
- 'Cuplikan berikut disajikan untuk memudahkan pemahaman.',
+ 'Untuk membantu Anda memahami lebih baik, kami menyediakan tangkapan layar terkait sebagai referensi.',
dialogueExamplesTitle: 'Contoh Dialog',
methodEmpty:
'Ini akan menampilkan penjelasan visual dari kategori basis pengetahuan',
@@ -211,7 +211,7 @@ export default {
Kami mengasumsikan manual memiliki struktur bagian hierarkis. Kami menggunakan judul bagian terendah sebagai poros untuk memotong dokumen.
Jadi, gambar dan tabel dalam bagian yang sama tidak akan dipisahkan, dan ukuran potongan mungkin besar.
`,
- naive: `Format file yang didukung adalah DOCX, XLSX, XLS (Excel 97-2003), PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML .
+ naive: `Format file yang didukung adalah MD, MDX, DOCX, XLSX, XLS (Excel 97-2003), PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML .
Metode ini menerapkan cara naif untuk memotong file:
Teks berturut-turut akan dipotong menjadi potongan menggunakan model deteksi visual.
@@ -456,6 +456,7 @@ export default {
},
setting: {
profile: 'Profil',
+ avatar: 'Avatar',
profileDescription: 'Perbarui foto dan detail pribadi Anda di sini.',
maxTokens: 'Token Maksimum',
maxTokensMessage: 'Token Maksimum diperlukan',
diff --git a/web/src/locales/ja.ts b/web/src/locales/ja.ts
index aa57438ecbf..0a044b74217 100644
--- a/web/src/locales/ja.ts
+++ b/web/src/locales/ja.ts
@@ -138,7 +138,7 @@ export default {
toMessage: '終了ページ番号が不足しています(除外)',
layoutRecognize: 'レイアウト認識',
layoutRecognizeTip:
- 'レイアウト分析のためにビジュアルモデルを使用し、文書の構造を理解しやすくします。',
+ 'レイアウト分析のためにビジュアルモデルを使用し、文書の構造を理解しやすくします。詳細については、https://ragflow.io/docs/dev/select_pdf_parser をご覧ください。',
taskPageSize: 'タスクページサイズ',
taskPageSizeMessage: 'タスクページサイズを入力してください',
taskPageSizeTip: `レイアウト認識中、PDFファイルはチャンクに分割され、処理速度を向上させるために並列処理されます。`,
@@ -156,7 +156,7 @@ export default {
cancel: 'キャンセル',
rerankModel: 'リランキングモデル',
rerankPlaceholder: '選択してください',
- rerankTip: `オプション:Rerankモデルを選択しない場合、システムはデフォルトでキーワードの類似度とベクトルのコサイン類似度を組み合わせたハイブリッド検索方式を採用します。Rerankモデルを設定した場合、ハイブリッド検索のベクトル類似度部分はrerankのスコアに置き換えられます。`,
+ rerankTip: `任意です。空欄の場合、RAGFlowは加重キーワード類似度と加重ベクトルコサイン類似度の組み合わせを使用します。リランキングモデルが選択された場合は、加重リランキングスコアが加重ベクトルコサイン類似度に代わります。リランキングモデルを使用すると、システムの応答時間が大幅に増加することにご注意ください。リランキングモデルを使用する場合は、SaaSリランカーを使用してください。ローカルにデプロイされたリランキングモデルを使用する場合は、docker-compose-gpu.ymlでRAGFlowを起動してください。`,
topK: 'トップK',
topKTip: `Rerank modelと一緒に使用する場合、この設定は指定されたreranking modelに送信するテキストのチャンク数を定義します。`,
delimiter: `テキストセグメンテーションの区切り文字`,
@@ -165,9 +165,9 @@ export default {
html4excel: 'ExcelをHTMLに変換',
html4excelTip: `General切片方法と併用してください。無効の場合、表計算ファイル(XLSX、XLS(Excel 97-2003))は行ごとにキーと値のペアとして解析されます。有効の場合、表計算ファイルはHTML表として解析されます。元の表が12行を超える場合、システムは自動的に12行ごとに複数のHTML表に分割します。詳細については、https://ragflow.io/docs/dev/enable_excel2html をご覧ください。`,
autoKeywords: '自動キーワード',
- autoKeywordsTip: `各チャンクに含まれるキーワードのランキングを向上させるために、自動的にN個のキーワードを抽出します。「システムモデル設定」で指定されたチャットモデルによって追加のトークンが消費されることに注意してください。チャンクリストから追加されたキーワードを確認または更新することができます。`,
+ autoKeywordsTip: `各チャンクに含まれるキーワードのランキングを向上させるために、自動的にN個のキーワードを抽出します。「システムモデル設定」で指定されたチャットモデルによって追加のトークンが消費されることに注意してください。チャンクリストから追加されたキーワードを確認または更新することができます。詳細は https://ragflow.io/docs/dev/autokeyword_autoquestion をご覧ください。`,
autoQuestions: '自動質問',
- autoQuestionsTip: `ランキングスコアを向上させるために、「システムモデル設定」で定義されたチャットモデルを使用して、ナレッジベースのチャンクごとにN個の質問を抽出します。 これにより、追加のトークンが消費されることに注意してください。 結果はチャンクリストで表示および編集できます。 質問抽出エラーはチャンク処理をブロックしません。空の結果が元のチャンクに追加されます。`,
+ autoQuestionsTip: `ランキングスコアを向上させるために、「システムモデル設定」で定義されたチャットモデルを使用して、ナレッジベースのチャンクごとにN個の質問を抽出します。 これにより、追加のトークンが消費されることに注意してください。 結果はチャンクリストで表示および編集できます。 質問抽出エラーはチャンク処理をブロックしません。空の結果が元のチャンクに追加されます。詳細は https://ragflow.io/docs/dev/autokeyword_autoquestion をご覧ください。`,
},
knowledgeConfiguration: {
titleDescription:
@@ -202,7 +202,7 @@ export default {
methodTitle: 'チャンク方法の説明',
methodExamples: '例',
methodExamplesDescription:
- '以下のスクリーンショットは明確な説明のために提供されています。',
+ '理解を深めるために、関連するスクリーンショットを参考として提供しております。',
dialogueExamplesTitle: '会話の例',
methodEmpty: 'ナレッジベースカテゴリの視覚的説明がここに表示されます',
book: `対応ファイル形式はDOCX , PDF , TXT です。
@@ -215,7 +215,7 @@ export default {
manual: `
対応するのはPDF のみです。
マニュアルは階層的なセクション構造を持つと仮定され、最下位のセクションタイトルを基にチャンク分割を行います。そのため、同じセクション内の図表は分割されませんが、大きなチャンクサイズになる可能性があります。
`,
- naive: `対応ファイル形式はDOCX, XLSX, XLS (Excel 97-2003), PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML です。
+ naive: `対応ファイル形式はMD, MDX, DOCX, XLSX, XLS (Excel 97-2003), PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML です。
この方法では、'ナイーブ'な方法でファイルを分割します:
視覚認識モデルを使用してテキストを小さなセグメントに分割します。
@@ -453,6 +453,7 @@ export default {
},
setting: {
profile: 'プロファイル',
+ avatar: 'アバター',
profileDescription: 'ここで写真と個人情報を更新してください。',
maxTokens: '最大トークン数',
maxTokensMessage: '最大トークン数は必須です',
diff --git a/web/src/locales/pt-br.ts b/web/src/locales/pt-br.ts
index de20891e881..c0144d2ab2e 100644
--- a/web/src/locales/pt-br.ts
+++ b/web/src/locales/pt-br.ts
@@ -141,7 +141,7 @@ export default {
toMessage: 'Página final ausente (excluída)',
layoutRecognize: 'Reconhecimento de layout',
layoutRecognizeTip:
- 'Use modelos visuais para análise de layout para entender melhor a estrutura do documento e localizar efetivamente títulos, blocos de texto, imagens e tabelas. Se desativado, apenas o texto simples no PDF será recuperado.',
+ 'Use modelos visuais para análise de layout para entender melhor a estrutura do documento e localizar efetivamente títulos, blocos de texto, imagens e tabelas. Se desativado, apenas o texto simples no PDF será recuperado. Para mais informações, acesse https://ragflow.io/docs/dev/select_pdf_parser.',
taskPageSize: 'Tamanho da página da tarefa',
taskPageSizeMessage: 'Por favor, insira o tamanho da página da tarefa!',
taskPageSizeTip:
@@ -161,7 +161,7 @@ export default {
rerankModel: 'Modelo de reranking',
rerankPlaceholder: 'Por favor, selecione',
rerankTip:
- 'Se deixado vazio, o RAGFlow usará uma combinação de similaridade de palavras-chave ponderada e similaridade de cosseno vetorial ponderada; se um modelo de reranking for selecionado, uma pontuação de reranking ponderada substituirá a similaridade de cosseno vetorial ponderada. Esteja ciente de que usar um modelo de reranking aumentará significativamente o tempo de resposta do sistema.',
+ 'Opcional. Se deixar em branco, o RAGFlow usará uma combinação de similaridade ponderada por palavra-chave e similaridade ponderada do cosseno vetorial; se um modelo de rerank for selecionado, uma pontuação ponderada de reranking substituirá a similaridade ponderada do cosseno vetorial. Esteja ciente de que usar um modelo de rerank aumentará significativamente o tempo de resposta do sistema. Se desejar usar um modelo de rerank, certifique-se de usar um reranker SaaS; se preferir um modelo de rerank implantado localmente, certifique-se de iniciar o RAGFlow com docker-compose-gpu.yml.',
topK: 'Top-K',
topKTip:
'Usado em conjunto com o Rerank model, essa configuração define o número de trechos de texto a serem enviados ao modelo reranking especificado.',
@@ -173,9 +173,9 @@ export default {
'Use em conjunto com o método de fragmentação General. Quando desativado, arquivos de planilhas (XLSX, XLS (Excel 97-2003)) serão analisados linha por linha como pares chave-valor. Quando ativado, os arquivos de planilhas serão convertidos em tabelas HTML. Se a tabela original tiver mais de 12 linhas, o sistema dividirá automaticamente em várias tabelas HTML a cada 12 linhas. Para mais informações, consulte https://ragflow.io/docs/dev/enable_excel2html.',
autoKeywords: 'Palavras-chave automáticas',
autoKeywordsTip:
- 'Extraia automaticamente N palavras-chave de cada bloco para aumentar sua classificação em consultas que contenham essas palavras-chave. Esteja ciente de que o modelo de chat especificado nas "Configurações do modelo do sistema" consumirá tokens adicionais. Você pode verificar ou atualizar as palavras-chave adicionadas a um bloco na lista de blocos.',
+ 'Extraia automaticamente N palavras-chave de cada bloco para aumentar sua classificação em consultas que contenham essas palavras-chave. Esteja ciente de que o modelo de chat especificado nas "Configurações do modelo do sistema" consumirá tokens adicionais. Você pode verificar ou atualizar as palavras-chave adicionadas a um bloco na lista de blocos. Para mais detalhes, consulte https://ragflow.io/docs/dev/autokeyword_autoquestion.',
autoQuestions: 'Perguntas automáticas',
- autoQuestionsTip: `Para aumentar as pontuações de classificação, extraia N perguntas para cada bloco da base de conhecimento usando o modelo de bate-papo definido em "Configurações do Modelo do Sistema". Observe que isso consome tokens extras. Os resultados podem ser visualizados e editados na lista de blocos. Erros na extração de perguntas não bloquearão o processo de fragmentação; resultados vazios serão adicionados ao bloco original.`,
+ autoQuestionsTip: `Para aumentar as pontuações de classificação, extraia N perguntas para cada bloco da base de conhecimento usando o modelo de bate-papo definido em "Configurações do Modelo do Sistema". Observe que isso consome tokens extras. Os resultados podem ser visualizados e editados na lista de blocos. Erros na extração de perguntas não bloquearão o processo de fragmentação; resultados vazios serão adicionados ao bloco original. Para mais detalhes, consulte https://ragflow.io/docs/dev/autokeyword_autoquestion.`,
redo: 'Deseja limpar os {{chunkNum}} fragmentos existentes?',
setMetaData: 'Definir Metadados',
pleaseInputJson: 'Por favor, insira um JSON',
@@ -235,7 +235,7 @@ export default {
methodTitle: 'Descrição do método de fragmentação',
methodExamples: 'Exemplos',
methodExamplesDescription:
- 'As capturas de tela a seguir são fornecidas para maior clareza.',
+ 'Para ajudá-lo(a) a entender melhor, disponibilizamos capturas de tela relevantes para referência.',
dialogueExamplesTitle: 'Exemplos de diálogos',
methodEmpty:
'Aqui será exibida uma explicação visual das categorias da base de conhecimento',
@@ -246,7 +246,7 @@ export default {
Os fragmentos terão granularidade compatível com 'ARTIGO', garantindo que todo o texto de nível superior seja incluído no fragmento.`,
manual: `Apenas PDF é suportado.
Assumimos que o manual tem uma estrutura hierárquica de seções, usando os títulos das seções inferiores como unidade básica para fragmentação. Assim, figuras e tabelas na mesma seção não serão separadas, o que pode resultar em fragmentos maiores.
`,
- naive: `Os formatos de arquivo suportados são DOCX, XLSX, XLS (Excel 97-2003), PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML .
+ naive: `Os formatos de arquivo suportados são MD, MDX, DOCX, XLSX, XLS (Excel 97-2003), PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML .
Este método fragmenta arquivos de maneira 'simples':
Usa um modelo de detecção visual para dividir os textos em segmentos menores.
@@ -451,6 +451,7 @@ export default {
},
setting: {
profile: 'Perfil',
+ avatar: 'Avatar',
profileDescription: 'Atualize sua foto e detalhes pessoais aqui.',
maxTokens: 'Máximo de Tokens',
maxTokensMessage: 'Máximo de Tokens é obrigatório',
diff --git a/web/src/locales/vi.ts b/web/src/locales/vi.ts
index 29f9aabc91b..1b643bab346 100644
--- a/web/src/locales/vi.ts
+++ b/web/src/locales/vi.ts
@@ -144,7 +144,7 @@ export default {
toMessage: 'Thiếu số trang kết thúc (được loại trừ)',
layoutRecognize: 'Nhận dạng bố cục',
layoutRecognizeTip:
- 'Sử dụng các mô hình trực quan để phân tích bố cục nhằm xác định tốt hơn cấu trúc tài liệu, tìm vị trí của tiêu đề, khối văn bản, hình ảnh và bảng. Nếu không có tính năng này, chỉ có thể lấy được văn bản thuần của PDF.',
+ 'Sử dụng các mô hình trực quan để phân tích bố cục nhằm xác định tốt hơn cấu trúc tài liệu, tìm vị trí của tiêu đề, khối văn bản, hình ảnh và bảng. Nếu không có tính năng này, chỉ có thể lấy được văn bản thuần của PDF. Để biết thêm thông tin, hãy xem https://ragflow.io/docs/dev/select_pdf_parser.',
taskPageSize: 'Kích thước trang tác vụ',
taskPageSizeMessage: 'Vui lòng nhập kích thước trang tác vụ của bạn!',
taskPageSizeTip: `Nếu sử dụng nhận dạng bố cục, tệp PDF sẽ được chia thành các nhóm trang liên tiếp. Phân tích bố cục sẽ được thực hiện song song giữa các nhóm để tăng tốc độ xử lý. 'Kích thước trang tác vụ' xác định kích thước của các nhóm. Kích thước trang càng lớn, khả năng chia tách văn bản liên tục giữa các trang thành các khối khác nhau càng thấp.`,
@@ -161,16 +161,16 @@ export default {
cancel: 'Hủy bỏ',
rerankModel: 'Mô hình xếp hạng lại',
rerankPlaceholder: 'Vui lòng chọn',
- rerankTip: `Nếu để trống, RAGFlow sẽ sử dụng kết hợp giữa độ tương đồng từ khóa được trọng số và độ tương đồng vectơ cosin được trọng số; nếu chọn mô hình xếp hạng lại, điểm xếp hạng được tính lại sẽ thay thế độ tương đồng vectơ cosin được trọng số.`,
+ rerankTip: `Tùy chọn. Nếu để trống, RAGFlow sẽ sử dụng kết hợp giữa độ tương đồng từ khóa có trọng số và độ tương đồng cosine vector có trọng số; nếu chọn mô hình rerank, điểm rerank có trọng số sẽ thay thế độ tương đồng cosine vector có trọng số. Xin lưu ý rằng việc sử dụng mô hình rerank sẽ làm tăng đáng kể thời gian phản hồi của hệ thống. Nếu bạn muốn sử dụng mô hình rerank, hãy đảm bảo sử dụng SaaS reranker; nếu bạn muốn sử dụng mô hình rerank triển khai cục bộ, hãy khởi động RAGFlow bằng docker-compose-gpu.yml.`,
topK: 'Top-K',
topKTip: `Sử dụng cùng với Rerank model, thiết lập này xác định số lượng đoạn văn cần gửi đến mô hình reranking được chỉ định.`,
delimiter: 'Dấu phân cách cho phân đoạn văn bản',
html4excel: 'Excel sang HTML',
html4excelTip: `Sử dụng cùng với phương pháp cắt khúc General. Khi chưa được bật, tệp bảng tính (XLSX, XLS (Excel 97-2003)) sẽ được phân tích theo dòng thành các cặp khóa-giá trị. Khi bật, tệp bảng tính sẽ được phân tích thành bảng HTML. Nếu bảng gốc vượt quá 12 dòng, hệ thống sẽ tự động chia thành nhiều bảng HTML mỗi 12 dòng. Để biết thêm thông tin, vui lòng xem https://ragflow.io/docs/dev/enable_excel2html.`,
autoKeywords: 'Từ khóa tự động',
- autoKeywordsTip: `Tự động trích xuất N từ khóa cho mỗi khối để tăng thứ hạng của chúng trong các truy vấn chứa các từ khóa đó. Lưu ý rằng các token bổ sung sẽ được tiêu thụ bởi mô hình trò chuyện được chỉ định trong "Cài đặt mô hình hệ thống". Bạn có thể kiểm tra hoặc cập nhật các từ khóa đã thêm cho một khối từ danh sách khối.`,
+ autoKeywordsTip: `Tự động trích xuất N từ khóa cho mỗi khối để tăng thứ hạng của chúng trong các truy vấn chứa các từ khóa đó. Lưu ý rằng các token bổ sung sẽ được tiêu thụ bởi mô hình trò chuyện được chỉ định trong "Cài đặt mô hình hệ thống". Bạn có thể kiểm tra hoặc cập nhật các từ khóa đã thêm cho một khối từ danh sách khối. Để biết chi tiết, vui lòng xem https://ragflow.io/docs/dev/autokeyword_autoquestion.`,
autoQuestions: 'Câu hỏi tự động',
- autoQuestionsTip: `Để tăng điểm xếp hạng, hãy trích xuất N câu hỏi cho mỗi đoạn kiến thức bằng mô hình trò chuyện được xác định trong "Cài đặt mô hình hệ thống". Lưu ý rằng việc này sẽ tiêu tốn thêm token. Kết quả có thể được xem và chỉnh sửa trong danh sách các đoạn. Lỗi trích xuất câu hỏi sẽ không chặn quá trình phân đoạn; kết quả trống sẽ được thêm vào đoạn gốc.`,
+ autoQuestionsTip: `Để tăng điểm xếp hạng, hãy trích xuất N câu hỏi cho mỗi đoạn kiến thức bằng mô hình trò chuyện được xác định trong "Cài đặt mô hình hệ thống". Lưu ý rằng việc này sẽ tiêu tốn thêm token. Kết quả có thể được xem và chỉnh sửa trong danh sách các đoạn. Lỗi trích xuất câu hỏi sẽ không chặn quá trình phân đoạn; kết quả trống sẽ được thêm vào đoạn gốc. Để biết chi tiết, vui lòng xem https://ragflow.io/docs/dev/autokeyword_autoquestion.`,
delimiterTip: `Hỗ trợ nhiều ký tự phân cách, và các ký tự phân cách nhiều ký tự được bao bọc bởi dấu . Ví dụ: nếu được cấu hình như thế này: "##"; thì văn bản sẽ được phân tách bởi dấu xuống dòng, hai dấu # và dấu chấm phẩy, sau đó được lắp ráp theo kích thước của "số token". Thiết lập các dấu phân cách chỉ sau khi hiểu cơ chế phân đoạn và phân khối văn bản.`,
redo: `Bạn có muốn xóa các đoạn {{chunkNum}} hiện có không?`,
knowledgeGraph: 'Đồ thị tri thức',
@@ -214,7 +214,7 @@ export default {
methodTitle: 'Mô tả phương thức phân khối',
methodExamples: 'Ví dụ',
methodExamplesDescription:
- 'Các ảnh chụp màn hình sau được cung cấp để minh họa.',
+ 'Để giúp bạn hiểu rõ hơn, chúng tôi đã cung cấp ảnh chụp màn hình liên quan để tham khảo.',
dialogueExamplesTitle: 'Ví dụ hội thoại',
methodEmpty: 'Mô tả bằng hình ảnh các danh mục cơ sở kiến thức',
book: `Các định dạng tệp được hỗ trợ là DOCX , PDF , TXT .
@@ -231,7 +231,7 @@ export default {
Sử dụng mô hình nhận dạng thị giác để chia các văn bản thành các phân đoạn nhỏ hơn.
Sau đó, kết hợp các phân đoạn liền kề cho đến khi số lượng token vượt quá ngưỡng được chỉ định bởi 'Số token khối', tại thời điểm đó, một khối được tạo.
- Các định dạng tệp được hỗ trợ là DOCX, XLSX, XLS (Excel 97-2003), PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML .
`,
+ Các định dạng tệp được hỗ trợ là MD, MDX, DOCX, XLSX, XLS (Excel 97-2003), PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML .
`,
paper: `Chỉ hỗ trợ tệp PDF .
Bài báo sẽ được chia theo các phần, chẳng hạn như tóm tắt, 1.1, 1.2 .
Cách tiếp cận này cho phép LLM tóm tắt bài báo hiệu quả hơn và cung cấp các phản hồi toàn diện, dễ hiểu hơn.
@@ -505,6 +505,7 @@ export default {
},
setting: {
profile: 'Hồ sơ',
+ avatar: 'Avatar',
profileDescription: 'Cập nhật ảnh và thông tin cá nhân của bạn tại đây.',
maxTokens: 'Token tối đa',
maxTokensMessage: 'Token tối đa là bắt buộc',
diff --git a/web/src/locales/zh-traditional.ts b/web/src/locales/zh-traditional.ts
index 219386d2983..923dc1c0e25 100644
--- a/web/src/locales/zh-traditional.ts
+++ b/web/src/locales/zh-traditional.ts
@@ -143,7 +143,7 @@ export default {
toMessage: '缺少結束頁碼(不包含)',
layoutRecognize: 'PDF解析器',
layoutRecognizeTip:
- '使用視覺模型進行 PDF 布局分析,以更好地識別文檔結構,找到標題、文字塊、圖像和表格的位置。若選擇 Naive 選項,則只能取得 PDF 的純文字。請注意此功能僅適用於 PDF 文檔,對其他文檔不生效。',
+ '使用視覺模型進行 PDF 布局分析,以更好地識別文檔結構,找到標題、文字塊、圖像和表格的位置。若選擇 Naive 選項,則只能取得 PDF 的純文字。請注意此功能僅適用於 PDF 文檔,對其他文檔不生效。如需更多資訊,請參閱 https://ragflow.io/docs/dev/select_pdf_parser。',
taskPageSize: '任務頁面大小',
taskPageSizeMessage: '請輸入您的任務頁面大小!',
taskPageSizeTip: `如果使用佈局識別,PDF 文件將被分成連續的組。佈局分析將在組之間並行執行,以提高處理速度。“任務頁面大小”決定組的大小。頁面大小越大,將頁面之間的連續文本分割成不同塊的機會就越低。`,
@@ -160,7 +160,7 @@ export default {
cancel: '取消',
rerankModel: 'rerank模型',
rerankPlaceholder: '請選擇',
- rerankTip: `如果是空的。它使用查詢和塊的嵌入來構成矢量餘弦相似性。否則,它使用rerank評分代替矢量餘弦相似性。`,
+ rerankTip: `非必選項:若不選擇 rerank 模型,系統將默認採用關鍵詞相似度與向量餘弦相似度相結合的混合查詢方式;如果設定了 rerank 模型,則混合查詢中的向量相似度部分將被 rerank 打分替代。請注意:採用 rerank 模型會非常耗時。如需選用 rerank 模型,建議使用 SaaS 的 rerank 模型服務;如果你傾向使用本地部署的 rerank 模型,請務必確保你使用 docker-compose-gpu.yml 啟動 RAGFlow。`,
topK: 'Top-K',
topKTip: `與 Rerank 模型配合使用,用於設定傳給 Rerank 模型的文本塊數量。`,
delimiter: `文字分段標識符`,
@@ -169,9 +169,9 @@ export default {
html4excel: '表格轉HTML',
html4excelTip: `與 General 切片方法配合使用。未開啟狀態下,表格檔案(XLSX、XLS(Excel 97-2003)會按行解析為鍵值對。開啟後,表格檔案會被解析為 HTML 表格。若原始表格超過 12 行,系統會自動按每 12 行拆分為多個 HTML 表格。欲了解更多資訊,請參閱 https://ragflow.io/docs/dev/enable_excel2html。`,
autoKeywords: '自動關鍵字',
- autoKeywordsTip: `自動為每個文字區塊中提取 N 個關鍵詞,以提升查詢精度。請注意:此功能採用「系統模型設定」中設定的預設聊天模型提取關鍵詞,因此也會產生更多 Token 消耗。此外,你也可以手動更新生成的關鍵詞。`,
+ autoKeywordsTip: `自動為每個文字區塊中提取 N 個關鍵詞,以提升查詢精度。請注意:此功能採用「系統模型設定」中設定的預設聊天模型提取關鍵詞,因此也會產生更多 Token 消耗。此外,你也可以手動更新生成的關鍵詞。詳情請參見 https://ragflow.io/docs/dev/autokeyword_autoquestion。`,
autoQuestions: '自動問題',
- autoQuestionsTip: `為了提高排名分數,請使用「系統模型設定」中定義的聊天模型,為每個知識庫區塊提取 N 個問題。 請注意:這會消耗額外的 token。 結果可在區塊列表中查看和編輯。 問題提取錯誤不會阻止分塊過程; 空結果將被添加到原始區塊。 `,
+ autoQuestionsTip: `為了提高排名分數,請使用「系統模型設定」中定義的聊天模型,為每個知識庫區塊提取 N 個問題。 請注意:這會消耗額外的 token。 結果可在區塊列表中查看和編輯。 問題提取錯誤不會阻止分塊過程; 空結果將被添加到原始區塊。詳情請參見 https://ragflow.io/docs/dev/autokeyword_autoquestion。 `,
redo: '是否清空已有 {{chunkNum}}個 chunk?',
setMetaData: '設定元數據',
pleaseInputJson: '請輸入JSON',
@@ -231,7 +231,7 @@ export default {
cancel: '取消',
methodTitle: '分塊方法說明',
methodExamples: '示例',
- methodExamplesDescription: '提出以下屏幕截圖以促進理解。',
+ methodExamplesDescription: '為方便您理解,我們附上相關截圖供您參考。',
dialogueExamplesTitle: '對話示例',
methodEmpty: '這將顯示知識庫類別的可視化解釋',
book: `
支持的文件格式為DOCX 、PDF 、TXT 。
@@ -246,7 +246,7 @@ export default {
我們假設手冊具有分層部分結構。我們使用最低的部分標題作為對文檔進行切片的樞軸。
因此,同一部分中的圖和表不會被分割,並且塊大小可能會很大。
`,
- naive: `支持的文件格式為DOCX、XLSX、XLS (Excel 97-2003)、PPT、PDF、TXT、JPEG、JPG、PNG、TIF、GIF、CSV、JSON、EML、HTML 。
+ naive: `支持的文件格式為MD、MDX、DOCX、XLSX、XLS (Excel 97-2003)、PPT、PDF、TXT、JPEG、JPG、PNG、TIF、GIF、CSV、JSON、EML、HTML 。
此方法將簡單的方法應用於塊文件:
系統將使用視覺檢測模型將連續文本分割成多個片段。
@@ -534,6 +534,7 @@ export default {
},
setting: {
profile: '概述',
+ avatar: '头像',
profileDescription: '在此更新您的照片和個人詳細信息。',
maxTokens: '最大token數',
maxTokensMessage: '最大token數是必填項',
@@ -761,7 +762,8 @@ export default {
examples: '範例',
to: '下一步',
msg: '訊息',
- messagePlaceholder: '訊息',
+ msgTip: '輸出上游組件的變數內容或自行輸入的文字。',
+ messagePlaceholder: '請輸入您的訊息內容,使用‘/’快速插入變數。',
messageMsg: '請輸入訊息或刪除此欄位。',
addField: '新增字段',
addMessage: '新增訊息',
@@ -786,7 +788,7 @@ export default {
relevantDescription: `此元件用來判斷upstream的輸出是否與使用者最新的問題相關,『是』代表相關,『否』代表不相關。`,
rewriteQuestionDescription: `此元件用於細化使用者的提問。通常,當使用者的原始提問無法從知識庫中檢索相關資訊時,此元件可協助您將問題變更為更符合知識庫表達方式的適當問題。`,
messageDescription:
- '此元件用於向使用者發送靜態訊息。您可以準備幾條訊息,這些訊息將隨機選擇。',
+ '此元件用來傳回工作流程最後產生的資料內容和原先設定的文字內容。',
keywordDescription: `該組件用於從用戶的問題中提取關鍵字。 Top N指定需要提取的關鍵字數量。`,
switchDescription: `該組件用於根據前面組件的輸出評估條件,並相應地引導執行流程。通過定義各種情況並指定操作,或在不滿足條件時採取默認操作,實現複雜的分支邏輯。`,
wikipediaDescription: `此元件用於從 https://www.wikipedia.org/ 取得搜尋結果。通常,它作為知識庫的補充。 Top N 指定您需要調整的搜尋結果數。`,
@@ -1162,6 +1164,9 @@ export default {
codeDescription: '它允許開發人員編寫自訂 Python 邏輯。',
inputVariables: '輸入變數',
runningHintText: '正在運行...🕞',
+ openingSwitchTip: '您的用戶將在開始時看到此歡迎訊息。',
+ modeTip: '模式定義工作流程如何啟動。 ',
+ beginInputTip: `透過定義輸入參數,這些內容可以在後續流程中被其他元件存取。`,
},
footer: {
profile: '“保留所有權利 @ react”',
diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts
index 67c7472138f..2bbb025c154 100644
--- a/web/src/locales/zh.ts
+++ b/web/src/locales/zh.ts
@@ -143,7 +143,7 @@ export default {
toMessage: '缺少结束页码(不包含)',
layoutRecognize: 'PDF解析器',
layoutRecognizeTip:
- '使用视觉模型进行 PDF 布局分析,以更好地识别文档结构,找到标题、文本块、图像和表格的位置。 如果选择 Naive 选项,则只能获取 PDF 的纯文本。请注意该功能只适用于 PDF 文档,对其他文档不生效。',
+ '使用视觉模型进行 PDF 布局分析,以更好地识别文档结构,找到标题、文本块、图像和表格的位置。 如果选择 Naive 选项,则只能获取 PDF 的纯文本。请注意该功能只适用于 PDF 文档,对其他文档不生效。欲了解更多信息,请参阅 https://ragflow.io/docs/dev/select_pdf_parser。',
taskPageSize: '任务页面大小',
taskPageSizeMessage: '请输入您的任务页面大小!',
taskPageSizeTip: `如果使用布局识别,PDF 文件将被分成连续的组。 布局分析将在组之间并行执行,以提高处理速度。 “任务页面大小”决定组的大小。 页面大小越大,将页面之间的连续文本分割成不同块的机会就越低。`,
@@ -160,7 +160,7 @@ export default {
cancel: '取消',
rerankModel: 'Rerank模型',
rerankPlaceholder: '请选择',
- rerankTip: `非必选项:若不选择 rerank 模型,系统将默认采用关键词相似度与向量余弦相似度相结合的混合查询方式;如果设置了 rerank 模型,则混合查询中的向量相似度部分将被 rerank 打分替代。请注意:采用 rerank 模型会非常耗时。`,
+ rerankTip: `非必选项:若不选择 rerank 模型,系统将默认采用关键词相似度与向量余弦相似度相结合的混合查询方式;如果设置了 rerank 模型,则混合查询中的向量相似度部分将被 rerank 打分替代。请注意:采用 rerank 模型会非常耗时。如需选用 rerank 模型,建议使用 SaaS 的 rerank 模型服务;如果你倾向使用本地部署的 rerank 模型,请务必确保你使用 docker-compose-gpu.yml 启动 RAGFlow。`,
topK: 'Top-K',
topKTip: `与 Rerank 模型配合使用,用于设置传给 Rerank 模型的文本块数量。`,
delimiter: `文本分段标识符`,
@@ -169,9 +169,9 @@ export default {
html4excel: '表格转HTML',
html4excelTip: `与 General 切片方法配合使用。未开启状态下,表格文件(XLSX、XLS(Excel 97-2003))会按行解析为键值对。开启后,表格文件会被解析为 HTML 表格。若原始表格超过 12 行,系统会自动按每 12 行拆分为多个 HTML 表格。欲了解更多详情,请参阅 https://ragflow.io/docs/dev/enable_excel2html。`,
autoKeywords: '自动关键词提取',
- autoKeywordsTip: `自动为每个文本块中提取 N 个关键词,用以提升查询精度。请注意:该功能采用“系统模型设置”中设置的默认聊天模型提取关键词,因此也会产生更多 Token 消耗。另外,你也可以手动更新生成的关键词。`,
+ autoKeywordsTip: `自动为每个文本块中提取 N 个关键词,用以提升查询精度。请注意:该功能采用“系统模型设置”中设置的默认聊天模型提取关键词,因此也会产生更多 Token 消耗。另外,你也可以手动更新生成的关键词。详情请见 https://ragflow.io/docs/dev/autokeyword_autoquestion。`,
autoQuestions: '自动问题提取',
- autoQuestionsTip: `利用“系统模型设置”中设置的 chat model 对知识库的每个文本块提取 N 个问题以提高其排名得分。请注意,开启后将消耗额外的 token。您可以在块列表中查看、编辑结果。如果自动问题提取发生错误,不会妨碍整个分块过程,只会将空结果添加到原始文本块。`,
+ autoQuestionsTip: `利用“系统模型设置”中设置的 chat model 对知识库的每个文本块提取 N 个问题以提高其排名得分。请注意,开启后将消耗额外的 token。您可以在块列表中查看、编辑结果。如果自动问题提取发生错误,不会妨碍整个分块过程,只会将空结果添加到原始文本块。详情请见 https://ragflow.io/docs/dev/autokeyword_autoquestion。`,
redo: '是否清空已有 {{chunkNum}}个 chunk?',
setMetaData: '设置元数据',
pleaseInputJson: '请输入JSON',
@@ -232,7 +232,8 @@ export default {
cancel: '取消',
methodTitle: '分块方法说明',
methodExamples: '示例',
- methodExamplesDescription: '提出以下屏幕截图以促进理解。',
+ methodExamplesDescription:
+ '为帮助您更好地理解,我们提供了相关截图供您参考。',
dialogueExamplesTitle: '对话示例',
methodEmpty: '这将显示知识库类别的可视化解释',
book: `支持的文件格式为DOCX 、PDF 、TXT 。
@@ -247,7 +248,7 @@ export default {
我们假设手册具有分层部分结构。 我们使用最低的部分标题作为对文档进行切片的枢轴。
因此,同一部分中的图和表不会被分割,并且块大小可能会很大。
`,
- naive: `支持的文件格式为DOCX、XLSX、XLS (Excel 97-2003)、PPT、PDF、TXT、JPEG、JPG、PNG、TIF、GIF、CSV、JSON、EML、HTML 。
+ naive: `支持的文件格式为MD、MDX、DOCX、XLSX、XLS (Excel 97-2003)、PPT、PDF、TXT、JPEG、JPG、PNG、TIF、GIF、CSV、JSON、EML、HTML 。
此方法将简单的方法应用于块文件:
系统将使用视觉检测模型将连续文本分割成多个片段。
@@ -462,7 +463,8 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
modelTip: '大语言聊天模型',
modelMessage: '请选择',
modelEnabledTools: '可用的工具',
- modelEnabledToolsTip: '请选择一个或多个可供该模型所使用的工具。仅对支持工具调用的模型生效。',
+ modelEnabledToolsTip:
+ '请选择一个或多个可供该模型所使用的工具。仅对支持工具调用的模型生效。',
freedom: '自由度',
improvise: '即兴创作',
precise: '精确',
@@ -553,6 +555,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
},
setting: {
profile: '概要',
+ avatar: '头像',
profileDescription: '在此更新您的照片和个人详细信息。',
maxTokens: '最大token数',
maxTokensMessage: '最大token数是必填项',
@@ -788,7 +791,8 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
examples: '示例',
to: '下一步',
msg: '消息',
- messagePlaceholder: '消息',
+ msgTip: '输出上游组件的变量内容或者自己输入的文本。',
+ messagePlaceholder: '请输入您的消息内容,使用‘/’快速插入变量。',
messageMsg: '请输入消息或删除此字段。',
addField: '新增字段',
addMessage: '新增消息',
@@ -812,7 +816,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
relevantDescription: `该组件用来判断upstream的输出是否与用户最新的问题相关,‘是’代表相关,‘否’代表不相关。`,
rewriteQuestionDescription: `此组件用于细化用户的提问。通常,当用户的原始提问无法从知识库中检索到相关信息时,此组件可帮助您将问题更改为更符合知识库表达方式的适当问题。`,
messageDescription:
- '此组件用于向用户发送静态信息。您可以准备几条消息,这些消息将被随机选择。',
+ '该组件用来返回工作流最后产生的数据内容和原先设置的文本内容。',
keywordDescription: `该组件用于从用户的问题中提取关键词。Top N指定需要提取的关键词数量。`,
switchDescription: `该组件用于根据前面组件的输出评估条件,并相应地引导执行流程。通过定义各种情况并指定操作,或在不满足条件时采取默认操作,实现复杂的分支逻辑。`,
wikipediaDescription: `此组件用于从 https://www.wikipedia.org/ 获取搜索结果。通常,它作为知识库的补充。Top N 指定您需要调整的搜索结果数量。`,
@@ -1224,6 +1228,14 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
inputVariables: '输入变量',
addVariable: '新增变量',
runningHintText: '正在运行中...🕞',
+ openingSwitch: '开场白开关',
+ openingCopy: '开场白文案',
+ openingSwitchTip: '您的用户将在开始时看到此欢迎消息。',
+ modeTip: '模式定义了工作流的启动方式。',
+ beginInputTip: '通过定义输入参数,此内容可以被后续流程中的其他组件访问。',
+ query: '查询变量',
+ agent: 'Agent',
+ agentDescription: '构建具备推理、工具调用和多智能体协同的智能体组件。',
},
footer: {
profile: 'All rights reserved @ React',
@@ -1235,11 +1247,11 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
},
llmTools: {
bad_calculator: {
- name: "计算器",
- description: "用于计算两个数的和的工具(会给出错误答案)",
+ name: '计算器',
+ description: '用于计算两个数的和的工具(会给出错误答案)',
params: {
- a: "第一个数",
- b: "第二个数",
+ a: '第一个数',
+ b: '第二个数',
},
},
},
diff --git a/web/src/pages/add-knowledge/components/knowledge-testing/index.tsx b/web/src/pages/add-knowledge/components/knowledge-testing/index.tsx
index e5a22f13597..d7140c92618 100644
--- a/web/src/pages/add-knowledge/components/knowledge-testing/index.tsx
+++ b/web/src/pages/add-knowledge/components/knowledge-testing/index.tsx
@@ -1,13 +1,19 @@
-import { useTestChunkRetrieval } from '@/hooks/knowledge-hooks';
+import {
+ useTestChunkAllRetrieval,
+ useTestChunkRetrieval,
+} from '@/hooks/knowledge-hooks';
import { Flex, Form } from 'antd';
import TestingControl from './testing-control';
import TestingResult from './testing-result';
+import { useState } from 'react';
import styles from './index.less';
const KnowledgeTesting = () => {
const [form] = Form.useForm();
const { testChunk } = useTestChunkRetrieval();
+ const { testChunkAll } = useTestChunkAllRetrieval();
+ const [selectedDocumentIds, setSelectedDocumentIds] = useState([]);
const handleTesting = async (documentIds: string[] = []) => {
const values = await form.validateFields();
@@ -16,6 +22,12 @@ const KnowledgeTesting = () => {
doc_ids: Array.isArray(documentIds) ? documentIds : [],
vector_similarity_weight: 1 - values.vector_similarity_weight,
});
+
+ testChunkAll({
+ ...values,
+ doc_ids: [],
+ vector_similarity_weight: 1 - values.vector_similarity_weight,
+ });
};
return (
@@ -23,8 +35,13 @@ const KnowledgeTesting = () => {
-
+
);
};
diff --git a/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx b/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx
index 18f347051df..8efb4c195ef 100644
--- a/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx
+++ b/web/src/pages/add-knowledge/components/knowledge-testing/testing-control/index.tsx
@@ -18,10 +18,15 @@ type FieldType = {
interface IProps {
form: FormInstance;
- handleTesting: () => Promise;
+ handleTesting: (documentIds?: string[]) => Promise;
+ selectedDocumentIds: string[];
}
-const TestingControl = ({ form, handleTesting }: IProps) => {
+const TestingControl = ({
+ form,
+ handleTesting,
+ selectedDocumentIds,
+}: IProps) => {
const question = Form.useWatch('question', { form, preserve: true });
const loading = useChunkIsTesting();
const { t } = useTranslate('knowledgeDetails');
@@ -29,6 +34,10 @@ const TestingControl = ({ form, handleTesting }: IProps) => {
const buttonDisabled =
!question || (typeof question === 'string' && question.trim() === '');
+ const onClick = () => {
+ handleTesting(selectedDocumentIds);
+ };
+
return (
@@ -53,7 +62,7 @@ const TestingControl = ({ form, handleTesting }: IProps) => {
diff --git a/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.tsx b/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.tsx
index d3c100c8fdd..a8b95926e4b 100644
--- a/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.tsx
+++ b/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/index.tsx
@@ -15,13 +15,15 @@ import camelCase from 'lodash/camelCase';
import SelectFiles from './select-files';
import {
+ useAllTestingResult,
+ useAllTestingSuccess,
useSelectIsTestingSuccess,
useSelectTestingResult,
} from '@/hooks/knowledge-hooks';
import { useGetPaginationWithRouter } from '@/hooks/logic-hooks';
import { api_host } from '@/utils/api';
import { showImage } from '@/utils/chat';
-import { useCallback, useState } from 'react';
+import { useCallback } from 'react';
import styles from './index.less';
const similarityList: Array<{ field: keyof ITestingChunk; label: string }> = [
@@ -48,14 +50,21 @@ const ChunkTitle = ({ item }: { item: ITestingChunk }) => {
interface IProps {
handleTesting: (documentIds?: string[]) => Promise;
+ selectedDocumentIds: string[];
+ setSelectedDocumentIds: (ids: string[]) => void;
}
-const TestingResult = ({ handleTesting }: IProps) => {
- const [selectedDocumentIds, setSelectedDocumentIds] = useState([]);
+const TestingResult = ({
+ handleTesting,
+ selectedDocumentIds,
+ setSelectedDocumentIds,
+}: IProps) => {
const { documents, chunks, total } = useSelectTestingResult();
+ const { documents: documentsAll, total: totalAll } = useAllTestingResult();
const { t } = useTranslate('knowledgeDetails');
const { pagination, setPagination } = useGetPaginationWithRouter();
const isSuccess = useSelectIsTestingSuccess();
+ const isAllSuccess = useAllTestingSuccess();
const onChange: PaginationProps['onChange'] = (pageNumber, pageSize) => {
pagination.onChange?.(pageNumber, pageSize);
@@ -88,7 +97,8 @@ const TestingResult = ({ handleTesting }: IProps) => {
>
- {selectedDocumentIds?.length ?? 0}/{documents?.length ?? 0}
+ {selectedDocumentIds?.length ?? 0}/
+ {documentsAll?.length ?? 0}
{t('filesSelected')}
diff --git a/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/select-files.tsx b/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/select-files.tsx
index 07647b501f0..93545e09e01 100644
--- a/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/select-files.tsx
+++ b/web/src/pages/add-knowledge/components/knowledge-testing/testing-result/select-files.tsx
@@ -1,6 +1,6 @@
import NewDocumentLink from '@/components/new-document-link';
import { useTranslate } from '@/hooks/common-hooks';
-import { useSelectTestingResult } from '@/hooks/knowledge-hooks';
+import { useAllTestingResult } from '@/hooks/knowledge-hooks';
import { ITestingDocument } from '@/interfaces/database/knowledge';
import { EyeOutlined } from '@ant-design/icons';
import { Button, Table, TableProps, Tooltip } from 'antd';
@@ -11,7 +11,7 @@ interface IProps {
}
const SelectFiles = ({ setSelectedDocumentIds, handleTesting }: IProps) => {
- const { documents } = useSelectTestingResult();
+ const { documents } = useAllTestingResult();
const { t } = useTranslate('fileManager');
const columns: TableProps['columns'] = [
@@ -50,8 +50,8 @@ const SelectFiles = ({ setSelectedDocumentIds, handleTesting }: IProps) => {
const rowSelection = {
onChange: (selectedRowKeys: React.Key[]) => {
- handleTesting(selectedRowKeys as string[]);
setSelectedDocumentIds(selectedRowKeys as string[]);
+ handleTesting(selectedRowKeys as string[]);
},
getCheckboxProps: (record: ITestingDocument) => ({
disabled: record.doc_name === 'Disabled User', // Column configuration not to be checked
diff --git a/web/src/pages/agent/agent-sidebar.tsx b/web/src/pages/agent/agent-sidebar.tsx
index 42f399a8227..dd642981e47 100644
--- a/web/src/pages/agent/agent-sidebar.tsx
+++ b/web/src/pages/agent/agent-sidebar.tsx
@@ -14,13 +14,14 @@ import {
SidebarHeader,
SidebarMenu,
} from '@/components/ui/sidebar';
-import { useMemo } from 'react';
+import { memo, useMemo } from 'react';
import {
AgentOperatorList,
Operator,
componentMenuList,
operatorMap,
} from './constant';
+import { useHandleDrag } from './hooks';
import OperatorIcon from './operator-icon';
type OperatorItem = {
@@ -28,8 +29,14 @@ type OperatorItem = {
};
function OperatorCard({ name }: OperatorItem) {
+ const { handleDragStart } = useHandleDrag();
+
return (
-
+
{
return componentMenuList.filter((x) =>
AgentOperatorList.some((y) => y === x.name),
@@ -101,3 +108,5 @@ export function AgentSidebar() {
);
}
+
+export const AgentSidebar = memo(InnerAgentSidebar);
diff --git a/web/src/pages/agent/canvas/edge/index.tsx b/web/src/pages/agent/canvas/edge/index.tsx
index 52f939b8d7a..2b67cdbadd1 100644
--- a/web/src/pages/agent/canvas/edge/index.tsx
+++ b/web/src/pages/agent/canvas/edge/index.tsx
@@ -7,7 +7,7 @@ import {
import useGraphStore from '../../store';
import { useTheme } from '@/components/theme-provider';
-import { useFetchFlow } from '@/hooks/flow-hooks';
+import { useFetchAgent } from '@/hooks/use-agent-request';
import { useMemo } from 'react';
import styles from './index.less';
@@ -44,7 +44,7 @@ export function ButtonEdge({
};
// highlight the nodes that the workflow passes through
- const { data: flowDetail } = useFetchFlow();
+ const { data: flowDetail } = useFetchAgent();
const graphPath = useMemo(() => {
// TODO: this will be called multiple times
@@ -57,7 +57,7 @@ export function ButtonEdge({
if (previousGraphPath.length > 0 && previousLatestElement) {
graphPath = [previousLatestElement, ...graphPath];
}
- return graphPath;
+ return Array.isArray(graphPath) ? graphPath : [];
}, [flowDetail.dsl?.path]);
const highlightStyle = useMemo(() => {
diff --git a/web/src/pages/agent/canvas/index.tsx b/web/src/pages/agent/canvas/index.tsx
index c7acb9df130..adae43a751a 100644
--- a/web/src/pages/agent/canvas/index.tsx
+++ b/web/src/pages/agent/canvas/index.tsx
@@ -5,20 +5,29 @@ import {
ReactFlow,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
-// import ChatDrawer from '../chat/drawer';
+import { useEffect } from 'react';
+import { ChatSheet } from '../chat/chat-sheet';
+import {
+ AgentChatContext,
+ AgentChatLogContext,
+ AgentInstanceContext,
+} from '../context';
import FormSheet from '../form-sheet/next';
import {
useHandleDrop,
useSelectCanvasData,
useValidateConnection,
- useWatchNodeFormDataChange,
} from '../hooks';
+import { useAddNode } from '../hooks/use-add-node';
import { useBeforeDelete } from '../hooks/use-before-delete';
-import { useShowDrawer } from '../hooks/use-show-drawer';
-// import RunDrawer from '../run-drawer';
+import { useCacheChatLog } from '../hooks/use-cache-chat-log';
+import { useShowDrawer, useShowLogSheet } from '../hooks/use-show-drawer';
+import { LogSheet } from '../log-sheet';
+import RunSheet from '../run-sheet';
import { ButtonEdge } from './edge';
import styles from './index.less';
import { RagNode } from './node';
+import { AgentNode } from './node/agent-node';
import { BeginNode } from './node/begin-node';
import { CategorizeNode } from './node/categorize-node';
import { EmailNode } from './node/email-node';
@@ -34,6 +43,7 @@ import { RetrievalNode } from './node/retrieval-node';
import { RewriteNode } from './node/rewrite-node';
import { SwitchNode } from './node/switch-node';
import { TemplateNode } from './node/template-node';
+import { ToolNode } from './node/tool-node';
const nodeTypes: NodeTypes = {
ragNode: RagNode,
@@ -53,6 +63,8 @@ const nodeTypes: NodeTypes = {
emailNode: EmailNode,
group: IterationNode,
iterationStartNode: IterationStartNode,
+ agentNode: AgentNode,
+ toolNode: ToolNode,
};
const edgeTypes = {
@@ -64,7 +76,7 @@ interface IProps {
hideDrawer(): void;
}
-function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
+function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
const {
nodes,
edges,
@@ -75,7 +87,8 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
} = useSelectCanvasData();
const isValidConnection = useValidateConnection();
- const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop();
+ const { onDrop, onDragOver, setReactFlowInstance, reactFlowInstance } =
+ useHandleDrop();
const {
onNodeClick,
@@ -95,9 +108,26 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
hideDrawer,
});
+ const {
+ addEventList,
+ setCurrentMessageId,
+ currentEventListWithoutMessage,
+ clearEventList,
+ } = useCacheChatLog();
+
+ const { showLogSheet, logSheetVisible, hideLogSheet } = useShowLogSheet({
+ setCurrentMessageId,
+ });
+
const { handleBeforeDelete } = useBeforeDelete();
- useWatchNodeFormDataChange();
+ const { addCanvasNode } = useAddNode(reactFlowInstance);
+
+ useEffect(() => {
+ if (!chatVisible) {
+ clearEventList();
+ }
+ }, [chatVisible, clearEventList]);
return (
@@ -121,63 +151,75 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
-
-
-
+
+
+
+
+
{formDrawerVisible && (
-
+
+
+
)}
- {/* {chatVisible && (
-
+ {chatVisible && (
+
+
+
+
+
)}
-
{runVisible && (
-
- )} */}
+ >
+ )}
+ {logSheetVisible && (
+
+ )}
);
}
-export default FlowCanvas;
+export default AgentCanvas;
diff --git a/web/src/pages/agent/canvas/node/agent-node.tsx b/web/src/pages/agent/canvas/node/agent-node.tsx
new file mode 100644
index 00000000000..2a4057e3184
--- /dev/null
+++ b/web/src/pages/agent/canvas/node/agent-node.tsx
@@ -0,0 +1,79 @@
+import { IAgentNode } from '@/interfaces/database/flow';
+import { Handle, NodeProps, Position } from '@xyflow/react';
+import { memo, useMemo } from 'react';
+import { NodeHandleId, Operator } from '../../constant';
+import useGraphStore from '../../store';
+import { CommonHandle } from './handle';
+import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
+import styles from './index.less';
+import NodeHeader from './node-header';
+import { NodeWrapper } from './node-wrapper';
+import { ToolBar } from './toolbar';
+
+function InnerAgentNode({
+ id,
+ data,
+ isConnectable = true,
+ selected,
+}: NodeProps) {
+ const getNode = useGraphStore((state) => state.getNode);
+ const edges = useGraphStore((state) => state.edges);
+
+ const isNotParentAgent = useMemo(() => {
+ const edge = edges.find((x) => x.target === id);
+ const label = getNode(edge?.source)?.data.label;
+ return label !== Operator.Agent;
+ }, [edges, getNode, id]);
+
+ return (
+
+
+ {isNotParentAgent && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+
+
+ );
+}
+
+export const AgentNode = memo(InnerAgentNode);
diff --git a/web/src/pages/agent/canvas/node/begin-node.tsx b/web/src/pages/agent/canvas/node/begin-node.tsx
index 83e36652b36..10df9eeef8b 100644
--- a/web/src/pages/agent/canvas/node/begin-node.tsx
+++ b/web/src/pages/agent/canvas/node/begin-node.tsx
@@ -1,54 +1,44 @@
-import { useTheme } from '@/components/theme-provider';
import { IBeginNode } from '@/interfaces/database/flow';
-import { Handle, NodeProps, Position } from '@xyflow/react';
+import { NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd';
-import classNames from 'classnames';
import get from 'lodash/get';
+import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import {
BeginQueryType,
BeginQueryTypeIconMap,
+ NodeHandleId,
Operator,
- operatorMap,
} from '../../constant';
import { BeginQuery } from '../../interface';
import OperatorIcon from '../../operator-icon';
+import { CommonHandle } from './handle';
import { RightHandleStyle } from './handle-icon';
import styles from './index.less';
+import { NodeWrapper } from './node-wrapper';
// TODO: do not allow other nodes to connect to this node
-export function BeginNode({ selected, data }: NodeProps) {
+function InnerBeginNode({ data, id }: NodeProps) {
const { t } = useTranslation();
const query: BeginQuery[] = get(data, 'form.query', []);
- const { theme } = useTheme();
+
return (
-
-
+
+ nodeId={id}
+ id={NodeHandleId.Start}
+ >
-
-
+
+
{query.map((x, idx) => {
const Icon = BeginQueryTypeIconMap[x.type as BeginQueryType];
@@ -67,6 +57,8 @@ export function BeginNode({ selected, data }: NodeProps) {
);
})}
-
+
);
}
+
+export const BeginNode = memo(InnerBeginNode);
diff --git a/web/src/pages/agent/canvas/node/categorize-handle.tsx b/web/src/pages/agent/canvas/node/categorize-handle.tsx
index ce1fc3624ff..f1eeff76521 100644
--- a/web/src/pages/agent/canvas/node/categorize-handle.tsx
+++ b/web/src/pages/agent/canvas/node/categorize-handle.tsx
@@ -1,6 +1,6 @@
import { Handle, Position } from '@xyflow/react';
-import React from 'react';
+import React, { memo } from 'react';
import styles from './index.less';
const DEFAULT_HANDLE_STYLE = {
@@ -37,4 +37,4 @@ const CategorizeHandle = ({ top, right, id, children }: IProps) => {
);
};
-export default CategorizeHandle;
+export default memo(CategorizeHandle);
diff --git a/web/src/pages/agent/canvas/node/categorize-node.tsx b/web/src/pages/agent/canvas/node/categorize-node.tsx
index 18c3cdff0e9..864cb151336 100644
--- a/web/src/pages/agent/canvas/node/categorize-node.tsx
+++ b/web/src/pages/agent/canvas/node/categorize-node.tsx
@@ -1,68 +1,62 @@
import LLMLabel from '@/components/llm-select/llm-label';
-import { useTheme } from '@/components/theme-provider';
import { ICategorizeNode } from '@/interfaces/database/flow';
-import { Handle, NodeProps, Position } from '@xyflow/react';
-import { Flex } from 'antd';
-import classNames from 'classnames';
+import { NodeProps, Position } from '@xyflow/react';
import { get } from 'lodash';
+import { memo } from 'react';
+import { NodeHandleId } from '../../constant';
+import { CommonHandle } from './handle';
import { RightHandleStyle } from './handle-icon';
-import { useBuildCategorizeHandlePositions } from './hooks';
-import styles from './index.less';
import NodeHeader from './node-header';
+import { NodeWrapper } from './node-wrapper';
+import { ToolBar } from './toolbar';
+import { useBuildCategorizeHandlePositions } from './use-build-categorize-handle-positions';
-export function CategorizeNode({
+export function InnerCategorizeNode({
id,
data,
selected,
}: NodeProps) {
const { positions } = useBuildCategorizeHandlePositions({ data, id });
- const { theme } = useTheme();
return (
-
-
+
+
+
-
+
-
-
-
-
- {positions.map((position, idx) => {
- return (
-
- );
- })}
-
-
+
+
+
+
+ {positions.map((position, idx) => {
+ return (
+
+
+ {position.text}
+
+
+
+ );
+ })}
+
+
+
);
}
+
+export const CategorizeNode = memo(InnerCategorizeNode);
diff --git a/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx
new file mode 100644
index 00000000000..e741286f293
--- /dev/null
+++ b/web/src/pages/agent/canvas/node/dropdown/next-step-dropdown.tsx
@@ -0,0 +1,111 @@
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from '@/components/ui/accordion';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import { Operator } from '@/pages/agent/constant';
+import { AgentInstanceContext, HandleContext } from '@/pages/agent/context';
+import OperatorIcon from '@/pages/agent/operator-icon';
+import { PropsWithChildren, useContext } from 'react';
+
+type OperatorItemProps = { operators: Operator[] };
+
+function OperatorItemList({ operators }: OperatorItemProps) {
+ const { addCanvasNode } = useContext(AgentInstanceContext);
+ const { nodeId, id, type, position } = useContext(HandleContext);
+
+ return (
+
+ {operators.map((x) => {
+ return (
+
+
+ {x}
+
+ );
+ })}
+
+ );
+}
+
+function AccordionOperators() {
+ return (
+
+
+ AI
+
+
+
+
+
+ Dialogue
+
+
+
+
+
+ Flow
+
+
+
+
+
+
+ Data Manipulation
+
+
+
+
+
+
+ Tools
+
+
+
+
+
+ );
+}
+
+export function NextStepDropdown({ children }: PropsWithChildren) {
+ return (
+
+ {children}
+ e.stopPropagation()}
+ className="w-[300px] font-semibold"
+ >
+ Next Step
+
+
+
+ );
+}
diff --git a/web/src/pages/agent/canvas/node/email-node.tsx b/web/src/pages/agent/canvas/node/email-node.tsx
index ae4af848c65..9482194f3d6 100644
--- a/web/src/pages/agent/canvas/node/email-node.tsx
+++ b/web/src/pages/agent/canvas/node/email-node.tsx
@@ -2,12 +2,12 @@ import { IEmailNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd';
import classNames from 'classnames';
-import { useState } from 'react';
+import { memo, useState } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
-export function EmailNode({
+export function InnerEmailNode({
id,
data,
isConnectable = true,
@@ -76,3 +76,5 @@ export function EmailNode({
);
}
+
+export const EmailNode = memo(InnerEmailNode);
diff --git a/web/src/pages/agent/canvas/node/generate-node.tsx b/web/src/pages/agent/canvas/node/generate-node.tsx
index 255eccd993a..8ffbbd79c77 100644
--- a/web/src/pages/agent/canvas/node/generate-node.tsx
+++ b/web/src/pages/agent/canvas/node/generate-node.tsx
@@ -4,11 +4,12 @@ import { IGenerateNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import classNames from 'classnames';
import { get } from 'lodash';
+import { memo } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
-export function GenerateNode({
+export function InnerGenerateNode({
id,
data,
isConnectable = true,
@@ -55,3 +56,5 @@ export function GenerateNode({
);
}
+
+export const GenerateNode = memo(InnerGenerateNode);
diff --git a/web/src/pages/agent/canvas/node/handle.tsx b/web/src/pages/agent/canvas/node/handle.tsx
new file mode 100644
index 00000000000..ad76a87eb63
--- /dev/null
+++ b/web/src/pages/agent/canvas/node/handle.tsx
@@ -0,0 +1,41 @@
+import { cn } from '@/lib/utils';
+import { Handle, HandleProps } from '@xyflow/react';
+import { Plus } from 'lucide-react';
+import { useMemo } from 'react';
+import { HandleContext } from '../../context';
+import { NextStepDropdown } from './dropdown/next-step-dropdown';
+
+export function CommonHandle({
+ className,
+ nodeId,
+ ...props
+}: HandleProps & { nodeId: string }) {
+ const value = useMemo(
+ () => ({
+ nodeId,
+ id: props.id,
+ type: props.type,
+ position: props.position,
+ }),
+ [nodeId, props.id, props.position, props.type],
+ );
+
+ return (
+
+
+ {
+ e.stopPropagation();
+ }}
+ >
+
+
+
+
+ );
+}
diff --git a/web/src/pages/agent/canvas/node/index.tsx b/web/src/pages/agent/canvas/node/index.tsx
index 32191f5ccc7..b68edf931aa 100644
--- a/web/src/pages/agent/canvas/node/index.tsx
+++ b/web/src/pages/agent/canvas/node/index.tsx
@@ -1,45 +1,43 @@
-import { useTheme } from '@/components/theme-provider';
import { IRagNode } from '@/interfaces/database/flow';
-import { Handle, NodeProps, Position } from '@xyflow/react';
-import classNames from 'classnames';
+import { NodeProps, Position } from '@xyflow/react';
+import { memo } from 'react';
+import { NodeHandleId } from '../../constant';
+import { CommonHandle } from './handle';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
-import styles from './index.less';
import NodeHeader from './node-header';
+import { NodeWrapper } from './node-wrapper';
+import { ToolBar } from './toolbar';
-export function RagNode({
+function InnerRagNode({
id,
data,
isConnectable = true,
selected,
}: NodeProps) {
- const { theme } = useTheme();
return (
-
+
+
+
+
+
+
+
);
}
+
+export const RagNode = memo(InnerRagNode);
diff --git a/web/src/pages/agent/canvas/node/invoke-node.tsx b/web/src/pages/agent/canvas/node/invoke-node.tsx
index 42d109f3d06..cf1e28d0264 100644
--- a/web/src/pages/agent/canvas/node/invoke-node.tsx
+++ b/web/src/pages/agent/canvas/node/invoke-node.tsx
@@ -4,12 +4,13 @@ import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd';
import classNames from 'classnames';
import { get } from 'lodash';
+import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
-export function InvokeNode({
+function InnerInvokeNode({
id,
data,
isConnectable = true,
@@ -57,3 +58,5 @@ export function InvokeNode({
);
}
+
+export const InvokeNode = memo(InnerInvokeNode);
diff --git a/web/src/pages/agent/canvas/node/iteration-node.tsx b/web/src/pages/agent/canvas/node/iteration-node.tsx
index c15b4fc6c6a..53a84835ded 100644
--- a/web/src/pages/agent/canvas/node/iteration-node.tsx
+++ b/web/src/pages/agent/canvas/node/iteration-node.tsx
@@ -6,6 +6,7 @@ import {
import { cn } from '@/lib/utils';
import { Handle, NodeProps, NodeResizeControl, Position } from '@xyflow/react';
import { ListRestart } from 'lucide-react';
+import { memo } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
@@ -43,7 +44,7 @@ const controlStyle = {
cursor: 'nwse-resize',
};
-export function IterationNode({
+export function InnerIterationNode({
id,
data,
isConnectable = true,
@@ -98,7 +99,7 @@ export function IterationNode({
);
}
-export function IterationStartNode({
+function InnerIterationStartNode({
isConnectable = true,
selected,
}: NodeProps) {
@@ -125,3 +126,7 @@ export function IterationStartNode({
);
}
+
+export const IterationStartNode = memo(InnerIterationStartNode);
+
+export const IterationNode = memo(InnerIterationNode);
diff --git a/web/src/pages/agent/canvas/node/keyword-node.tsx b/web/src/pages/agent/canvas/node/keyword-node.tsx
index f607d431780..012dcf26cd7 100644
--- a/web/src/pages/agent/canvas/node/keyword-node.tsx
+++ b/web/src/pages/agent/canvas/node/keyword-node.tsx
@@ -4,11 +4,12 @@ import { IKeywordNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import classNames from 'classnames';
import { get } from 'lodash';
+import { memo } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
-export function KeywordNode({
+export function InnerKeywordNode({
id,
data,
isConnectable = true,
@@ -55,3 +56,5 @@ export function KeywordNode({
);
}
+
+export const KeywordNode = memo(InnerKeywordNode);
diff --git a/web/src/pages/agent/canvas/node/logic-node.tsx b/web/src/pages/agent/canvas/node/logic-node.tsx
index 28215617b4f..a98efc3719d 100644
--- a/web/src/pages/agent/canvas/node/logic-node.tsx
+++ b/web/src/pages/agent/canvas/node/logic-node.tsx
@@ -1,45 +1,41 @@
-import { useTheme } from '@/components/theme-provider';
import { ILogicNode } from '@/interfaces/database/flow';
-import { Handle, NodeProps, Position } from '@xyflow/react';
-import classNames from 'classnames';
+import { NodeProps, Position } from '@xyflow/react';
+import { memo } from 'react';
+import { CommonHandle } from './handle';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
-import styles from './index.less';
import NodeHeader from './node-header';
+import { NodeWrapper } from './node-wrapper';
+import { ToolBar } from './toolbar';
-export function LogicNode({
+export function InnerLogicNode({
id,
data,
isConnectable = true,
selected,
}: NodeProps) {
- const { theme } = useTheme();
return (
-
+
+
+
+
+
+
+
);
}
+
+export const LogicNode = memo(InnerLogicNode);
diff --git a/web/src/pages/agent/canvas/node/message-node.tsx b/web/src/pages/agent/canvas/node/message-node.tsx
index 5b3a1736eef..8d4c3199d71 100644
--- a/web/src/pages/agent/canvas/node/message-node.tsx
+++ b/web/src/pages/agent/canvas/node/message-node.tsx
@@ -1,65 +1,65 @@
-import { useTheme } from '@/components/theme-provider';
import { IMessageNode } from '@/interfaces/database/flow';
-import { Handle, NodeProps, Position } from '@xyflow/react';
+import { NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd';
import classNames from 'classnames';
import { get } from 'lodash';
+import { memo } from 'react';
+import { NodeHandleId } from '../../constant';
+import { CommonHandle } from './handle';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
+import { NodeWrapper } from './node-wrapper';
+import { ToolBar } from './toolbar';
-export function MessageNode({
+function InnerMessageNode({
id,
data,
isConnectable = true,
selected,
}: NodeProps) {
const messages: string[] = get(data, 'form.messages', []);
- const { theme } = useTheme();
return (
-
-
-
- 0,
- })}
- >
+
+
+
+
+ 0,
+ })}
+ >
-
- {messages.map((message, idx) => {
- return (
-
- {message}
-
- );
- })}
-
-
+
+ {messages.map((message, idx) => {
+ return (
+
+ {message}
+
+ );
+ })}
+
+
+
);
}
+
+export const MessageNode = memo(InnerMessageNode);
diff --git a/web/src/pages/agent/canvas/node/node-header.tsx b/web/src/pages/agent/canvas/node/node-header.tsx
index 99a37dc1e6f..9647af1ed56 100644
--- a/web/src/pages/agent/canvas/node/node-header.tsx
+++ b/web/src/pages/agent/canvas/node/node-header.tsx
@@ -1,13 +1,7 @@
-import { useTranslate } from '@/hooks/common-hooks';
-import { Flex } from 'antd';
-import { Play } from 'lucide-react';
-import { Operator, operatorMap } from '../../constant';
+import { cn } from '@/lib/utils';
+import { memo } from 'react';
+import { Operator } from '../../constant';
import OperatorIcon from '../../operator-icon';
-import { needsSingleStepDebugging } from '../../utils';
-import NodeDropdown from './dropdown';
-import { NextNodePopover } from './popover';
-
-import { RunTooltip } from '../../flow-tooltip';
interface IProps {
id: string;
label: string;
@@ -17,57 +11,24 @@ interface IProps {
wrapperClassName?: string;
}
-const ExcludedRunStateOperators = [Operator.Answer];
-
-export function RunStatus({ id, name, label }: IProps) {
- const { t } = useTranslate('flow');
- return (
-
- {needsSingleStepDebugging(label) && (
-
-
- // data-play is used to trigger single step debugging
- )}
-
-
- {t('operationResults')}
-
-
-
- );
-}
-
-const NodeHeader = ({
+const InnerNodeHeader = ({
label,
- id,
name,
- gap = 4,
className,
wrapperClassName,
}: IProps) => {
return (
-
- {!ExcludedRunStateOperators.includes(label as Operator) && (
-
- )}
-
-
+
);
};
+const NodeHeader = memo(InnerNodeHeader);
+
export default NodeHeader;
diff --git a/web/src/pages/agent/canvas/node/node-wrapper.tsx b/web/src/pages/agent/canvas/node/node-wrapper.tsx
new file mode 100644
index 00000000000..e0010cad28e
--- /dev/null
+++ b/web/src/pages/agent/canvas/node/node-wrapper.tsx
@@ -0,0 +1,18 @@
+import { cn } from '@/lib/utils';
+import { HTMLAttributes, PropsWithChildren } from 'react';
+
+export function NodeWrapper({
+ children,
+ className,
+}: PropsWithChildren & HTMLAttributes) {
+ return (
+
+ );
+}
diff --git a/web/src/pages/agent/canvas/node/note-node.tsx b/web/src/pages/agent/canvas/node/note-node.tsx
index 1917a81509e..237942133e0 100644
--- a/web/src/pages/agent/canvas/node/note-node.tsx
+++ b/web/src/pages/agent/canvas/node/note-node.tsx
@@ -8,10 +8,8 @@ import { useTheme } from '@/components/theme-provider';
import { INoteNode } from '@/interfaces/database/flow';
import { memo, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
-import {
- useHandleFormValuesChange,
- useHandleNodeNameChange,
-} from '../../hooks';
+import { useHandleNodeNameChange } from '../../hooks';
+import { useHandleFormValuesChange } from '../../hooks/use-watch-form-change';
import styles from './index.less';
const { TextArea } = Input;
diff --git a/web/src/pages/agent/canvas/node/popover.tsx b/web/src/pages/agent/canvas/node/popover.tsx
index 342ce40ebab..d445386568e 100644
--- a/web/src/pages/agent/canvas/node/popover.tsx
+++ b/web/src/pages/agent/canvas/node/popover.tsx
@@ -1,4 +1,3 @@
-import { useFetchFlow } from '@/hooks/flow-hooks';
import get from 'lodash/get';
import React, { MouseEventHandler, useCallback, useMemo } from 'react';
import JsonView from 'react18-json-view';
@@ -20,6 +19,7 @@ import {
TableRow,
} from '@/components/ui/table';
import { useTranslate } from '@/hooks/common-hooks';
+import { useFetchAgent } from '@/hooks/use-agent-request';
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
interface IProps extends React.PropsWithChildren {
@@ -30,7 +30,7 @@ interface IProps extends React.PropsWithChildren {
export function NextNodePopover({ children, nodeId, name }: IProps) {
const { t } = useTranslate('flow');
- const { data } = useFetchFlow();
+ const { data } = useFetchAgent();
const { theme } = useTheme();
const component = useMemo(() => {
return get(data, ['dsl', 'components', nodeId], {});
diff --git a/web/src/pages/agent/canvas/node/relevant-node.tsx b/web/src/pages/agent/canvas/node/relevant-node.tsx
index acc098d69b1..410a7accdd1 100644
--- a/web/src/pages/agent/canvas/node/relevant-node.tsx
+++ b/web/src/pages/agent/canvas/node/relevant-node.tsx
@@ -6,11 +6,12 @@ import { RightHandleStyle } from './handle-icon';
import { useTheme } from '@/components/theme-provider';
import { IRelevantNode } from '@/interfaces/database/flow';
import { get } from 'lodash';
+import { memo } from 'react';
import { useReplaceIdWithName } from '../../hooks';
import styles from './index.less';
import NodeHeader from './node-header';
-export function RelevantNode({ id, data, selected }: NodeProps) {
+function InnerRelevantNode({ id, data, selected }: NodeProps) {
const yes = get(data, 'form.yes');
const no = get(data, 'form.no');
const replaceIdWithName = useReplaceIdWithName();
@@ -68,3 +69,5 @@ export function RelevantNode({ id, data, selected }: NodeProps) {
);
}
+
+export const RelevantNode = memo(InnerRelevantNode);
diff --git a/web/src/pages/agent/canvas/node/retrieval-node.tsx b/web/src/pages/agent/canvas/node/retrieval-node.tsx
index 0fd2760ede5..4187b2c44b4 100644
--- a/web/src/pages/agent/canvas/node/retrieval-node.tsx
+++ b/web/src/pages/agent/canvas/node/retrieval-node.tsx
@@ -1,24 +1,26 @@
-import { useTheme } from '@/components/theme-provider';
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
import { IRetrievalNode } from '@/interfaces/database/flow';
import { UserOutlined } from '@ant-design/icons';
-import { Handle, NodeProps, Position } from '@xyflow/react';
+import { NodeProps, Position } from '@xyflow/react';
import { Avatar, Flex } from 'antd';
import classNames from 'classnames';
import { get } from 'lodash';
-import { useMemo } from 'react';
+import { memo, useMemo } from 'react';
+import { NodeHandleId } from '../../constant';
+import { CommonHandle } from './handle';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
+import { NodeWrapper } from './node-wrapper';
+import { ToolBar } from './toolbar';
-export function RetrievalNode({
+function InnerRetrievalNode({
id,
data,
isConnectable = true,
selected,
}: NodeProps) {
const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []);
- const { theme } = useTheme();
const { list: knowledgeList } = useFetchKnowledgeList(true);
const knowledgeBases = useMemo(() => {
return knowledgeBaseIds.map((x) => {
@@ -32,57 +34,56 @@ export function RetrievalNode({
}, [knowledgeList, knowledgeBaseIds]);
return (
-
-
-
- 0,
- })}
- >
-
- {knowledgeBases.map((knowledge) => {
- return (
-
-
- }
- src={knowledge.avatar}
- />
-
- {knowledge.name}
+
+
+
+
+ 0,
+ })}
+ >
+
+ {knowledgeBases.map((knowledge) => {
+ return (
+
+
+ }
+ src={knowledge.avatar}
+ />
+
+ {knowledge.name}
+
-
-
- );
- })}
-
-
+
+ );
+ })}
+
+
+
);
}
+
+export const RetrievalNode = memo(InnerRetrievalNode);
diff --git a/web/src/pages/agent/canvas/node/rewrite-node.tsx b/web/src/pages/agent/canvas/node/rewrite-node.tsx
index 093b2c80ea3..134899c8bea 100644
--- a/web/src/pages/agent/canvas/node/rewrite-node.tsx
+++ b/web/src/pages/agent/canvas/node/rewrite-node.tsx
@@ -4,11 +4,12 @@ import { IRewriteNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import classNames from 'classnames';
import { get } from 'lodash';
+import { memo } from 'react';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
-export function RewriteNode({
+function InnerRewriteNode({
id,
data,
isConnectable = true,
@@ -55,3 +56,5 @@ export function RewriteNode({
);
}
+
+export const RewriteNode = memo(InnerRewriteNode);
diff --git a/web/src/pages/agent/canvas/node/switch-node.tsx b/web/src/pages/agent/canvas/node/switch-node.tsx
index 860a0ba9618..c386731a0e0 100644
--- a/web/src/pages/agent/canvas/node/switch-node.tsx
+++ b/web/src/pages/agent/canvas/node/switch-node.tsx
@@ -1,13 +1,16 @@
-import { useTheme } from '@/components/theme-provider';
+import { IconFont } from '@/components/icon-font';
+import { Card, CardContent } from '@/components/ui/card';
import { ISwitchCondition, ISwitchNode } from '@/interfaces/database/flow';
-import { Handle, NodeProps, Position } from '@xyflow/react';
-import { Divider, Flex } from 'antd';
-import classNames from 'classnames';
+import { NodeProps, Position } from '@xyflow/react';
+import { memo, useCallback } from 'react';
+import { NodeHandleId, SwitchOperatorOptions } from '../../constant';
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
+import { CommonHandle } from './handle';
import { RightHandleStyle } from './handle-icon';
-import { useBuildSwitchHandlePositions } from './hooks';
-import styles from './index.less';
import NodeHeader from './node-header';
+import { NodeWrapper } from './node-wrapper';
+import { ToolBar } from './toolbar';
+import { useBuildSwitchHandlePositions } from './use-build-switch-handle-positions';
const getConditionKey = (idx: number, length: number) => {
if (idx === 0 && length !== 1) {
@@ -28,87 +31,80 @@ const ConditionBlock = ({
}) => {
const items = condition?.items ?? [];
const getLabel = useGetComponentLabelByValue(nodeId);
+
+ const renderOperatorIcon = useCallback((operator?: string) => {
+ const name = SwitchOperatorOptions.find((x) => x.value === operator)?.icon;
+ return ;
+ }, []);
+
return (
-
- {items.map((x, idx) => (
-
-
-
- {getLabel(x?.cpn_id)}
-
- {x?.operator}
-
- {x?.value}
-
-
- {idx + 1 < items.length && (
-
- {condition?.logical_operator}
-
- )}
-
- ))}
-
+
+
+ {items.map((x, idx) => (
+
+
+
+ {getLabel(x?.cpn_id)}
+
+ {renderOperatorIcon(x?.operator)}
+ {x?.value}
+
+
+ ))}
+
+
);
};
-export function SwitchNode({ id, data, selected }: NodeProps) {
+function InnerSwitchNode({ id, data, selected }: NodeProps) {
const { positions } = useBuildSwitchHandlePositions({ data, id });
- const { theme } = useTheme();
return (
-
-
-
-
- {positions.map((position, idx) => {
- return (
-
-
-
- {idx < positions.length - 1 && position.text}
- {getConditionKey(idx, positions.length)}
-
- {position.condition && (
-
- )}
-
-
-
- );
- })}
-
-
+
+
+
+
+
+ {positions.map((position, idx) => {
+ return (
+
+
+
+
+ {idx < positions.length - 1 &&
+ position.condition?.logical_operator?.toUpperCase()}
+
+ {getConditionKey(idx, positions.length)}
+
+ {position.condition && (
+
+ )}
+
+
+
+ );
+ })}
+
+
+
);
}
+
+export const SwitchNode = memo(InnerSwitchNode);
diff --git a/web/src/pages/agent/canvas/node/template-node.tsx b/web/src/pages/agent/canvas/node/template-node.tsx
index 971fbab3842..b204717ab2a 100644
--- a/web/src/pages/agent/canvas/node/template-node.tsx
+++ b/web/src/pages/agent/canvas/node/template-node.tsx
@@ -9,9 +9,10 @@ import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import NodeHeader from './node-header';
import { ITemplateNode } from '@/interfaces/database/flow';
+import { memo } from 'react';
import styles from './index.less';
-export function TemplateNode({
+function InnerTemplateNode({
id,
data,
isConnectable = true,
@@ -73,3 +74,5 @@ export function TemplateNode({
);
}
+
+export const TemplateNode = memo(InnerTemplateNode);
diff --git a/web/src/pages/agent/canvas/node/tool-node.tsx b/web/src/pages/agent/canvas/node/tool-node.tsx
new file mode 100644
index 00000000000..ba3f621be70
--- /dev/null
+++ b/web/src/pages/agent/canvas/node/tool-node.tsx
@@ -0,0 +1,52 @@
+import { IAgentForm, IToolNode } from '@/interfaces/database/agent';
+import { Handle, NodeProps, Position } from '@xyflow/react';
+import { get } from 'lodash';
+import { memo, useCallback } from 'react';
+import { NodeHandleId } from '../../constant';
+import { ToolCard } from '../../form/agent-form/agent-tools';
+import useGraphStore from '../../store';
+import { NodeWrapper } from './node-wrapper';
+
+function InnerToolNode({
+ id,
+ data,
+ isConnectable = true,
+ selected,
+}: NodeProps) {
+ const { edges, getNode } = useGraphStore((state) => state);
+ const upstreamAgentNodeId = edges.find((x) => x.target === id)?.source;
+ const upstreamAgentNode = getNode(upstreamAgentNodeId);
+
+ const handleClick = useCallback(() => {}, []);
+
+ const tools: IAgentForm['tools'] = get(
+ upstreamAgentNode,
+ 'data.form.tools',
+ [],
+ );
+
+ return (
+
+
+
+ {tools.map((x) => (
+
+ {x.component_name}
+
+ ))}
+
+
+ );
+}
+
+export const ToolNode = memo(InnerToolNode);
diff --git a/web/src/pages/agent/canvas/node/toolbar.tsx b/web/src/pages/agent/canvas/node/toolbar.tsx
new file mode 100644
index 00000000000..53a5f8a115c
--- /dev/null
+++ b/web/src/pages/agent/canvas/node/toolbar.tsx
@@ -0,0 +1,74 @@
+import {
+ TooltipContent,
+ TooltipNode,
+ TooltipTrigger,
+} from '@/components/xyflow/tooltip-node';
+import { Position } from '@xyflow/react';
+import { Copy, Play, Trash2 } from 'lucide-react';
+import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
+import { Operator } from '../../constant';
+import { useDuplicateNode } from '../../hooks';
+import useGraphStore from '../../store';
+
+function IconWrapper({ children }: PropsWithChildren) {
+ return (
+
+ {children}
+
+ );
+}
+
+type ToolBarProps = {
+ selected?: boolean | undefined;
+ label: string;
+ id: string;
+} & PropsWithChildren;
+
+export function ToolBar({ selected, children, label, id }: ToolBarProps) {
+ const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
+ const deleteIterationNodeById = useGraphStore(
+ (store) => store.deleteIterationNodeById,
+ );
+
+ const deleteNode: MouseEventHandler = useCallback(
+ (e) => {
+ e.stopPropagation();
+ if (label === Operator.Iteration) {
+ deleteIterationNodeById(id);
+ } else {
+ deleteNodeById(id);
+ }
+ },
+ [deleteIterationNodeById, deleteNodeById, id, label],
+ );
+
+ const duplicateNode = useDuplicateNode();
+
+ const handleDuplicate: MouseEventHandler = useCallback(
+ (e) => {
+ e.stopPropagation();
+ duplicateNode(id, label);
+ },
+ [duplicateNode, id, label],
+ );
+
+ return (
+
+ {children}
+
+
+
+
+
+ );
+}
diff --git a/web/src/pages/agent/canvas/node/use-build-categorize-handle-positions.ts b/web/src/pages/agent/canvas/node/use-build-categorize-handle-positions.ts
new file mode 100644
index 00000000000..e5249ae56eb
--- /dev/null
+++ b/web/src/pages/agent/canvas/node/use-build-categorize-handle-positions.ts
@@ -0,0 +1,45 @@
+import { ICategorizeItemResult } from '@/interfaces/database/agent';
+import { RAGFlowNodeType } from '@/interfaces/database/flow';
+import { useUpdateNodeInternals } from '@xyflow/react';
+import { get } from 'lodash';
+import { useEffect, useMemo } from 'react';
+
+export const useBuildCategorizeHandlePositions = ({
+ data,
+ id,
+}: {
+ id: string;
+ data: RAGFlowNodeType['data'];
+}) => {
+ const updateNodeInternals = useUpdateNodeInternals();
+
+ const categoryData: ICategorizeItemResult = useMemo(() => {
+ return get(data, `form.category_description`, {});
+ }, [data]);
+
+ const positions = useMemo(() => {
+ const list: Array<{
+ text: string;
+ top: number;
+ idx: number;
+ }> = [];
+
+ Object.keys(categoryData)
+ .sort((a, b) => categoryData[a].index - categoryData[b].index)
+ .forEach((x, idx) => {
+ list.push({
+ text: x,
+ idx,
+ top: idx === 0 ? 86 : list[idx - 1].top + 8 + 24,
+ });
+ });
+
+ return list;
+ }, [categoryData]);
+
+ useEffect(() => {
+ updateNodeInternals(id);
+ }, [id, updateNodeInternals, categoryData]);
+
+ return { positions };
+};
diff --git a/web/src/pages/agent/canvas/node/hooks.ts b/web/src/pages/agent/canvas/node/use-build-switch-handle-positions.ts
similarity index 51%
rename from web/src/pages/agent/canvas/node/hooks.ts
rename to web/src/pages/agent/canvas/node/use-build-switch-handle-positions.ts
index fbea8f1668b..eb0d0c5f108 100644
--- a/web/src/pages/agent/canvas/node/hooks.ts
+++ b/web/src/pages/agent/canvas/node/use-build-switch-handle-positions.ts
@@ -1,55 +1,10 @@
+import { ISwitchCondition, RAGFlowNodeType } from '@/interfaces/database/flow';
import { useUpdateNodeInternals } from '@xyflow/react';
import get from 'lodash/get';
import { useEffect, useMemo } from 'react';
import { SwitchElseTo } from '../../constant';
-
-import {
- ICategorizeItemResult,
- ISwitchCondition,
- RAGFlowNodeType,
-} from '@/interfaces/database/flow';
import { generateSwitchHandleText } from '../../utils';
-export const useBuildCategorizeHandlePositions = ({
- data,
- id,
-}: {
- id: string;
- data: RAGFlowNodeType['data'];
-}) => {
- const updateNodeInternals = useUpdateNodeInternals();
-
- const categoryData: ICategorizeItemResult = useMemo(() => {
- return get(data, `form.category_description`, {});
- }, [data]);
-
- const positions = useMemo(() => {
- const list: Array<{
- text: string;
- top: number;
- idx: number;
- }> = [];
-
- Object.keys(categoryData)
- .sort((a, b) => categoryData[a].index - categoryData[b].index)
- .forEach((x, idx) => {
- list.push({
- text: x,
- idx,
- top: idx === 0 ? 98 + 20 : list[idx - 1].top + 8 + 26,
- });
- });
-
- return list;
- }, [categoryData]);
-
- useEffect(() => {
- updateNodeInternals(id);
- }, [id, updateNodeInternals, categoryData]);
-
- return { positions };
-};
-
export const useBuildSwitchHandlePositions = ({
data,
id,
@@ -63,6 +18,10 @@ export const useBuildSwitchHandlePositions = ({
return get(data, 'form.conditions', []);
}, [data]);
+ useEffect(() => {
+ console.info('xxx0000');
+ }, [conditions]);
+
const positions = useMemo(() => {
const list: Array<{
text: string;
@@ -72,13 +31,13 @@ export const useBuildSwitchHandlePositions = ({
}> = [];
[...conditions, ''].forEach((x, idx) => {
- let top = idx === 0 ? 58 + 20 : list[idx - 1].top + 32; // case number (Case 1) height + flex gap
- if (idx - 1 >= 0) {
+ let top = idx === 0 ? 53 : list[idx - 1].top + 10 + 14; // case number (Case 1) height + flex gap
+ if (idx >= 1) {
const previousItems = conditions[idx - 1]?.items ?? [];
if (previousItems.length > 0) {
- top += 12; // ConditionBlock padding
- top += previousItems.length * 22; // condition variable height
- top += (previousItems.length - 1) * 25; // operator height
+ // top += 12; // ConditionBlock padding
+ top += previousItems.length * 26; // condition variable height
+ // top += (previousItems.length - 1) * 25; // operator height
}
}
diff --git a/web/src/pages/agent/chat/box.tsx b/web/src/pages/agent/chat/box.tsx
new file mode 100644
index 00000000000..6daabd17e88
--- /dev/null
+++ b/web/src/pages/agent/chat/box.tsx
@@ -0,0 +1,91 @@
+import { MessageType } from '@/constants/chat';
+import { useGetFileIcon } from '@/pages/chat/hooks';
+import { buildMessageItemReference } from '@/pages/chat/utils';
+import { Spin } from 'antd';
+
+import { useSendNextMessage } from './hooks';
+
+import MessageInput from '@/components/message-input';
+import MessageItem from '@/components/next-message-item';
+import PdfDrawer from '@/components/pdf-drawer';
+import { useClickDrawer } from '@/components/pdf-drawer/hooks';
+import { useFetchAgent } from '@/hooks/use-agent-request';
+import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
+import { buildMessageUuidWithRole } from '@/utils/chat';
+
+const AgentChatBox = () => {
+ const {
+ sendLoading,
+ handleInputChange,
+ handlePressEnter,
+ value,
+ loading,
+ ref,
+ derivedMessages,
+ reference,
+ stopOutputMessage,
+ } = useSendNextMessage();
+
+ const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
+ useClickDrawer();
+ useGetFileIcon();
+ const { data: userInfo } = useFetchUserInfo();
+ const { data: canvasInfo } = useFetchAgent();
+
+ return (
+ <>
+
+
+
+
+ {derivedMessages?.map((message, i) => {
+ return (
+
+ );
+ })}
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default AgentChatBox;
diff --git a/web/src/pages/agent/chat/chat-sheet.tsx b/web/src/pages/agent/chat/chat-sheet.tsx
new file mode 100644
index 00000000000..1050c460ff1
--- /dev/null
+++ b/web/src/pages/agent/chat/chat-sheet.tsx
@@ -0,0 +1,26 @@
+import {
+ Sheet,
+ SheetContent,
+ SheetHeader,
+ SheetTitle,
+} from '@/components/ui/sheet';
+import { IModalProps } from '@/interfaces/common';
+import { cn } from '@/lib/utils';
+import AgentChatBox from './box';
+
+export function ChatSheet({ hideModal }: IModalProps) {
+ return (
+
+
+ e.preventDefault()}
+ >
+
+ Are you absolutely sure?
+
+
+
+
+ );
+}
diff --git a/web/src/pages/agent/chat/hooks.ts b/web/src/pages/agent/chat/hooks.ts
new file mode 100644
index 00000000000..e813c90c479
--- /dev/null
+++ b/web/src/pages/agent/chat/hooks.ts
@@ -0,0 +1,181 @@
+import { MessageType } from '@/constants/chat';
+import {
+ useHandleMessageInputChange,
+ useSelectDerivedMessages,
+} from '@/hooks/logic-hooks';
+import { useFetchAgent } from '@/hooks/use-agent-request';
+import {
+ IEventList,
+ IMessageEvent,
+ MessageEventType,
+ useSendMessageBySSE,
+} from '@/hooks/use-send-message';
+import { Message } from '@/interfaces/database/chat';
+import i18n from '@/locales/config';
+import api from '@/utils/api';
+import { message } from 'antd';
+import { get } from 'lodash';
+import trim from 'lodash/trim';
+import { useCallback, useContext, useEffect, useMemo } from 'react';
+import { useParams } from 'umi';
+import { v4 as uuid } from 'uuid';
+import { BeginId } from '../constant';
+import { AgentChatLogContext } from '../context';
+import useGraphStore from '../store';
+import { receiveMessageError } from '../utils';
+
+const antMessage = message;
+
+export const useSelectNextMessages = () => {
+ const { data: flowDetail, loading } = useFetchAgent();
+ const reference = flowDetail.dsl.retrieval;
+ const {
+ derivedMessages,
+ ref,
+ addNewestQuestion,
+ addNewestAnswer,
+ removeLatestMessage,
+ removeMessageById,
+ removeMessagesAfterCurrentMessage,
+ } = useSelectDerivedMessages();
+
+ return {
+ reference,
+ loading,
+ derivedMessages,
+ ref,
+ addNewestQuestion,
+ addNewestAnswer,
+ removeLatestMessage,
+ removeMessageById,
+ removeMessagesAfterCurrentMessage,
+ };
+};
+
+function findMessageFromList(eventList: IEventList) {
+ const messageEventList = eventList.filter(
+ (x) => x.event === MessageEventType.Message,
+ ) as IMessageEvent[];
+ return {
+ id: messageEventList[0]?.message_id,
+ content: messageEventList.map((x) => x.data.content).join(''),
+ };
+}
+
+const useGetBeginNodePrologue = () => {
+ const getNode = useGraphStore((state) => state.getNode);
+
+ return useMemo(() => {
+ const formData = get(getNode(BeginId), 'data.form', {});
+ if (formData?.enablePrologue) {
+ return formData?.prologue;
+ }
+ }, [getNode]);
+};
+
+export const useSendNextMessage = () => {
+ const {
+ reference,
+ loading,
+ derivedMessages,
+ ref,
+ addNewestQuestion,
+ addNewestAnswer,
+ removeLatestMessage,
+ removeMessageById,
+ } = useSelectNextMessages();
+ const { id: agentId } = useParams();
+ const { handleInputChange, value, setValue } = useHandleMessageInputChange();
+ const { refetch } = useFetchAgent();
+ const { addEventList } = useContext(AgentChatLogContext);
+
+ const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE(
+ api.runCanvas,
+ );
+
+ const prologue = useGetBeginNodePrologue();
+
+ const sendMessage = useCallback(
+ async ({ message }: { message: Message; messages?: Message[] }) => {
+ const params: Record = {
+ id: agentId,
+ };
+ params.running_hint_text = i18n.t('flow.runningHintText', {
+ defaultValue: 'is running...🕞',
+ });
+ if (message.content) {
+ params.query = message.content;
+ // params.message_id = message.id;
+ params.inputs = {}; // begin operator inputs
+ }
+ const res = await send(params);
+
+ if (receiveMessageError(res)) {
+ antMessage.error(res?.data?.message);
+
+ // cancel loading
+ setValue(message.content);
+ removeLatestMessage();
+ } else {
+ refetch(); // pull the message list after sending the message successfully
+ }
+ },
+ [agentId, send, setValue, removeLatestMessage, refetch],
+ );
+
+ const handleSendMessage = useCallback(
+ async (message: Message) => {
+ sendMessage({ message });
+ },
+ [sendMessage],
+ );
+
+ useEffect(() => {
+ const { content, id } = findMessageFromList(answerList);
+ if (content) {
+ addNewestAnswer({
+ answer: content,
+ id: id,
+ });
+ }
+ }, [answerList, addNewestAnswer]);
+
+ const handlePressEnter = useCallback(() => {
+ if (trim(value) === '') return;
+ const id = uuid();
+ if (done) {
+ setValue('');
+ handleSendMessage({ id, content: value.trim(), role: MessageType.User });
+ }
+ addNewestQuestion({
+ content: value,
+ id,
+ role: MessageType.User,
+ });
+ }, [addNewestQuestion, handleSendMessage, done, setValue, value]);
+
+ useEffect(() => {
+ if (prologue) {
+ addNewestAnswer({
+ answer: prologue,
+ });
+ }
+ }, [addNewestAnswer, prologue]);
+
+ useEffect(() => {
+ addEventList(answerList);
+ }, [addEventList, answerList]);
+
+ return {
+ handlePressEnter,
+ handleInputChange,
+ value,
+ sendLoading: !done,
+ reference,
+ loading,
+ derivedMessages,
+ ref,
+ removeMessageById,
+ stopOutputMessage,
+ };
+};
diff --git a/web/src/pages/agent/constant.tsx b/web/src/pages/agent/constant.tsx
index f308e07278f..3f1d192e1f2 100644
--- a/web/src/pages/agent/constant.tsx
+++ b/web/src/pages/agent/constant.tsx
@@ -1,39 +1,23 @@
import {
- GitHubIcon,
- KeywordIcon,
- QWeatherIcon,
- WikipediaIcon,
-} from '@/assets/icon/Icon';
-import { ReactComponent as AkShareIcon } from '@/assets/svg/akshare.svg';
-import { ReactComponent as ArXivIcon } from '@/assets/svg/arxiv.svg';
-import { ReactComponent as baiduFanyiIcon } from '@/assets/svg/baidu-fanyi.svg';
-import { ReactComponent as BaiduIcon } from '@/assets/svg/baidu.svg';
-import { ReactComponent as BeginIcon } from '@/assets/svg/begin.svg';
-import { ReactComponent as BingIcon } from '@/assets/svg/bing.svg';
-import { ReactComponent as ConcentratorIcon } from '@/assets/svg/concentrator.svg';
-import { ReactComponent as CrawlerIcon } from '@/assets/svg/crawler.svg';
-import { ReactComponent as DeepLIcon } from '@/assets/svg/deepl.svg';
-import { ReactComponent as DuckIcon } from '@/assets/svg/duck.svg';
-import { ReactComponent as EmailIcon } from '@/assets/svg/email.svg';
-import { ReactComponent as ExeSqlIcon } from '@/assets/svg/exesql.svg';
-import { ReactComponent as GoogleScholarIcon } from '@/assets/svg/google-scholar.svg';
-import { ReactComponent as GoogleIcon } from '@/assets/svg/google.svg';
-import { ReactComponent as InvokeIcon } from '@/assets/svg/invoke-ai.svg';
-import { ReactComponent as Jin10Icon } from '@/assets/svg/jin10.svg';
-import { ReactComponent as NoteIcon } from '@/assets/svg/note.svg';
-import { ReactComponent as PubMedIcon } from '@/assets/svg/pubmed.svg';
-import { ReactComponent as SwitchIcon } from '@/assets/svg/switch.svg';
-import { ReactComponent as TemplateIcon } from '@/assets/svg/template.svg';
-import { ReactComponent as TuShareIcon } from '@/assets/svg/tushare.svg';
-import { ReactComponent as WenCaiIcon } from '@/assets/svg/wencai.svg';
-import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.svg';
-
-// 邮件功能
+ initialKeywordsSimilarityWeightValue,
+ initialSimilarityThresholdValue,
+} from '@/components/similarity-slider';
+import {
+ AgentGlobals,
+ CodeTemplateStrMap,
+ ProgrammingLanguage,
+} from '@/constants/agent';
+
+export enum AgentDialogueMode {
+ Conversational = 'conversational',
+ Task = 'task',
+}
import {
ChatVariableEnabledField,
variableEnabledFieldMap,
} from '@/constants/chat';
+import { ModelVariableType } from '@/constants/knowledge';
import i18n from '@/locales/config';
import { setInitialChatVariableEnabledFieldValue } from '@/utils/chat';
@@ -43,20 +27,14 @@ export enum Channel {
News = 'news',
}
-import {
- BranchesOutlined,
- DatabaseOutlined,
- FormOutlined,
- MergeCellsOutlined,
- MessageOutlined,
- RocketOutlined,
- SendOutlined,
-} from '@ant-design/icons';
+export enum PromptRole {
+ User = 'user',
+ Assistant = 'assistant',
+}
+
import upperFirst from 'lodash/upperFirst';
import {
- CirclePower,
CloudUpload,
- IterationCcw,
ListOrdered,
OptionIcon,
TextCursorInput,
@@ -103,8 +81,15 @@ export enum Operator {
Email = 'Email',
Iteration = 'Iteration',
IterationStart = 'IterationItem',
+ Code = 'Code',
+ WaitingDialogue = 'WaitingDialogue',
+ Agent = 'Agent',
+ Tool = 'Tool',
+ Tavily = 'Tavily',
}
+export const SwitchLogicOperatorOptions = ['and', 'or'];
+
export const CommonOperatorList = Object.values(Operator).filter(
(x) => x !== Operator.Note,
);
@@ -121,48 +106,11 @@ export const AgentOperatorList = [
Operator.Concentrator,
Operator.Template,
Operator.Iteration,
+ Operator.WaitingDialogue,
Operator.Note,
+ Operator.Agent,
];
-export const operatorIconMap = {
- [Operator.Retrieval]: RocketOutlined,
- [Operator.Generate]: MergeCellsOutlined,
- [Operator.Answer]: SendOutlined,
- [Operator.Begin]: BeginIcon,
- [Operator.Categorize]: DatabaseOutlined,
- [Operator.Message]: MessageOutlined,
- [Operator.Relevant]: BranchesOutlined,
- [Operator.RewriteQuestion]: FormOutlined,
- [Operator.KeywordExtract]: KeywordIcon,
- [Operator.DuckDuckGo]: DuckIcon,
- [Operator.Baidu]: BaiduIcon,
- [Operator.Wikipedia]: WikipediaIcon,
- [Operator.PubMed]: PubMedIcon,
- [Operator.ArXiv]: ArXivIcon,
- [Operator.Google]: GoogleIcon,
- [Operator.Bing]: BingIcon,
- [Operator.GoogleScholar]: GoogleScholarIcon,
- [Operator.DeepL]: DeepLIcon,
- [Operator.GitHub]: GitHubIcon,
- [Operator.BaiduFanyi]: baiduFanyiIcon,
- [Operator.QWeather]: QWeatherIcon,
- [Operator.ExeSQL]: ExeSqlIcon,
- [Operator.Switch]: SwitchIcon,
- [Operator.WenCai]: WenCaiIcon,
- [Operator.AkShare]: AkShareIcon,
- [Operator.YahooFinance]: YahooFinanceIcon,
- [Operator.Jin10]: Jin10Icon,
- [Operator.Concentrator]: ConcentratorIcon,
- [Operator.TuShare]: TuShareIcon,
- [Operator.Note]: NoteIcon,
- [Operator.Crawler]: CrawlerIcon,
- [Operator.Invoke]: InvokeIcon,
- [Operator.Template]: TemplateIcon,
- [Operator.Email]: EmailIcon,
- [Operator.Iteration]: IterationCcw,
- [Operator.IterationStart]: CirclePower,
-};
-
export const operatorMap: Record<
Operator,
{
@@ -299,6 +247,10 @@ export const operatorMap: Record<
[Operator.Email]: { backgroundColor: '#e6f7ff' },
[Operator.Iteration]: { backgroundColor: '#e6f7ff' },
[Operator.IterationStart]: { backgroundColor: '#e6f7ff' },
+ [Operator.Code]: { backgroundColor: '#4c5458' },
+ [Operator.WaitingDialogue]: { backgroundColor: '#a5d65c' },
+ [Operator.Agent]: { backgroundColor: '#a5d65c' },
+ [Operator.Tavily]: { backgroundColor: '#a5d65c' },
};
export const componentMenuList = [
@@ -336,6 +288,15 @@ export const componentMenuList = [
{
name: Operator.Iteration,
},
+ {
+ name: Operator.Code,
+ },
+ {
+ name: Operator.WaitingDialogue,
+ },
+ {
+ name: Operator.Agent,
+ },
{
name: Operator.Note,
},
@@ -404,18 +365,46 @@ export const componentMenuList = [
},
];
+export const SwitchOperatorOptions = [
+ { value: '=', label: 'equal', icon: 'equal' },
+ { value: '≠', label: 'notEqual', icon: 'not-equals' },
+ { value: '>', label: 'gt', icon: 'Less' },
+ { value: '≥', label: 'ge', icon: 'Greater-or-equal' },
+ { value: '<', label: 'lt', icon: 'Less' },
+ { value: '≤', label: 'le', icon: 'less-or-equal' },
+ { value: 'contains', label: 'contains', icon: 'Contains' },
+ { value: 'not contains', label: 'notContains', icon: 'not-contains' },
+ { value: 'start with', label: 'startWith', icon: 'list-start' },
+ { value: 'end with', label: 'endWith', icon: 'list-end' },
+ { value: 'empty', label: 'empty', icon: 'circle' },
+ { value: 'not empty', label: 'notEmpty', icon: 'circle-slash-2' },
+];
+
+export const SwitchElseTo = 'end_cpn_ids';
+
const initialQueryBaseValues = {
query: [],
};
export const initialRetrievalValues = {
- similarity_threshold: 0.2,
- keywords_similarity_weight: 0.3,
+ query: '',
top_n: 8,
- ...initialQueryBaseValues,
+ top_k: 1024,
+ kb_ids: [],
+ rerank_id: '',
+ empty_response: '',
+ ...initialSimilarityThresholdValue,
+ ...initialKeywordsSimilarityWeightValue,
+ outputs: {
+ formalized_content: {
+ type: 'string',
+ value: '',
+ },
+ },
};
export const initialBeginValues = {
+ mode: AgentDialogueMode.Conversational,
prologue: `Hi! I'm your assistant, what can I do for you?`,
};
@@ -457,6 +446,7 @@ export const initialRelevantValues = {
export const initialCategorizeValues = {
...initialLlmBaseValues,
+ parameter: ModelVariableType.Precise,
message_history_window_size: 1,
category_description: {},
...initialQueryBaseValues,
@@ -563,7 +553,20 @@ export const initialExeSqlValues = {
...initialQueryBaseValues,
};
-export const initialSwitchValues = { conditions: [] };
+export const initialSwitchValues = {
+ conditions: [
+ {
+ logical_operator: SwitchLogicOperatorOptions[0],
+ items: [
+ {
+ operator: SwitchOperatorOptions[0].value,
+ },
+ ],
+ to: [],
+ },
+ ],
+ [SwitchElseTo]: [],
+};
export const initialWenCaiValues = {
top_n: 20,
@@ -645,6 +648,44 @@ export const initialIterationValues = {
};
export const initialIterationStartValues = {};
+export const initialCodeValues = {
+ lang: 'python',
+ script: CodeTemplateStrMap[ProgrammingLanguage.Python],
+ arguments: [
+ {
+ name: 'arg1',
+ },
+ {
+ name: 'arg2',
+ },
+ ],
+};
+
+export const initialWaitingDialogueValues = {};
+
+export const initialAgentValues = {
+ ...initialLlmBaseValues,
+ sys_prompt: ``,
+ prompts: [{ role: PromptRole.User, content: `{${AgentGlobals.SysQuery}}` }],
+ message_history_window_size: 12,
+ tools: [],
+ outputs: {
+ structured_output: {
+ // topic: {
+ // type: 'string',
+ // description:
+ // 'default:general. The category of the search.news is useful for retrieving real-time updates, particularly about politics, sports, and major current events covered by mainstream media sources. general is for broader, more general-purpose searches that may include a wide range of sources.',
+ // enum: ['general', 'news'],
+ // default: 'general',
+ // },
+ },
+ content: {
+ type: 'string',
+ value: '',
+ },
+ },
+};
+
export const CategorizeAnchorPointPositions = [
{ top: 1, right: 34 },
{ top: 8, right: 18 },
@@ -726,6 +767,9 @@ export const RestrictedUpstreamMap = {
[Operator.Email]: [Operator.Begin],
[Operator.Iteration]: [Operator.Begin],
[Operator.IterationStart]: [Operator.Begin],
+ [Operator.Code]: [Operator.Begin],
+ [Operator.WaitingDialogue]: [Operator.Begin],
+ [Operator.Agent]: [Operator.Begin],
};
export const NodeMap = {
@@ -765,6 +809,10 @@ export const NodeMap = {
[Operator.Email]: 'emailNode',
[Operator.Iteration]: 'group',
[Operator.IterationStart]: 'iterationStartNode',
+ [Operator.Code]: 'ragNode',
+ [Operator.WaitingDialogue]: 'ragNode',
+ [Operator.Agent]: 'agentNode',
+ [Operator.Tool]: 'toolNode',
};
export const LanguageOptions = [
@@ -2903,25 +2951,6 @@ export const ExeSQLOptions = ['mysql', 'postgresql', 'mariadb', 'mssql'].map(
}),
);
-export const SwitchElseTo = 'end_cpn_id';
-
-export const SwitchOperatorOptions = [
- { value: '=', label: 'equal' },
- { value: '≠', label: 'notEqual' },
- { value: '>', label: 'gt' },
- { value: '≥', label: 'ge' },
- { value: '<', label: 'lt' },
- { value: '≤', label: 'le' },
- { value: 'contains', label: 'contains' },
- { value: 'not contains', label: 'notContains' },
- { value: 'start with', label: 'startWith' },
- { value: 'end with', label: 'endWith' },
- { value: 'empty', label: 'empty' },
- { value: 'not empty', label: 'notEmpty' },
-];
-
-export const SwitchLogicOperatorOptions = ['and', 'or'];
-
export const WenCaiQueryTypeOptions = [
'stock',
'zhishu',
@@ -2983,3 +3012,9 @@ export const NoDebugOperatorsList = [
Operator.Switch,
Operator.Iteration,
];
+
+export enum NodeHandleId {
+ Start = 'start',
+ End = 'end',
+ Tool = 'tool',
+}
diff --git a/web/src/pages/agent/context.ts b/web/src/pages/agent/context.ts
index fe51d8d6dac..eda8c50ee03 100644
--- a/web/src/pages/agent/context.ts
+++ b/web/src/pages/agent/context.ts
@@ -1,6 +1,48 @@
import { RAGFlowNodeType } from '@/interfaces/database/flow';
+import { HandleType, Position } from '@xyflow/react';
import { createContext } from 'react';
+import { useAddNode } from './hooks/use-add-node';
+import { useCacheChatLog } from './hooks/use-cache-chat-log';
+import { useShowLogSheet } from './hooks/use-show-drawer';
-export const FlowFormContext = createContext(
+export const AgentFormContext = createContext(
undefined,
);
+
+type AgentInstanceContextType = Pick<
+ ReturnType,
+ 'addCanvasNode'
+>;
+
+export const AgentInstanceContext = createContext(
+ {} as AgentInstanceContextType,
+);
+
+type AgentChatContextType = Pick<
+ ReturnType,
+ 'showLogSheet'
+>;
+
+export const AgentChatContext = createContext(
+ {} as AgentChatContextType,
+);
+
+type AgentChatLogContextType = Pick<
+ ReturnType,
+ 'addEventList' | 'setCurrentMessageId'
+>;
+
+export const AgentChatLogContext = createContext(
+ {} as AgentChatLogContextType,
+);
+
+export type HandleContextType = {
+ nodeId?: string;
+ id?: string;
+ type: HandleType;
+ position: Position;
+};
+
+export const HandleContext = createContext(
+ {} as HandleContextType,
+);
diff --git a/web/src/pages/agent/debug-content/index.less b/web/src/pages/agent/debug-content/index.less
deleted file mode 100644
index fda707810ba..00000000000
--- a/web/src/pages/agent/debug-content/index.less
+++ /dev/null
@@ -1,5 +0,0 @@
-.formWrapper {
- :global(.ant-form-item-label) {
- font-weight: 600 !important;
- }
-}
diff --git a/web/src/pages/agent/debug-content/index.tsx b/web/src/pages/agent/debug-content/index.tsx
index f5493c76483..377c53e04e3 100644
--- a/web/src/pages/agent/debug-content/index.tsx
+++ b/web/src/pages/agent/debug-content/index.tsx
@@ -1,30 +1,42 @@
-import { Authorization } from '@/constants/authorization';
-import { useSetModalState } from '@/hooks/common-hooks';
-import { useSetSelectedRecord } from '@/hooks/logic-hooks';
-import { useHandleSubmittable } from '@/hooks/login-hooks';
-import api from '@/utils/api';
-import { getAuthorization } from '@/utils/authorization-util';
-import { UploadOutlined } from '@ant-design/icons';
+import { FileUploader } from '@/components/file-uploader';
+import { ButtonLoading } from '@/components/ui/button';
import {
- Button,
Form,
- FormItemProps,
- Input,
- InputNumber,
- Select,
- Switch,
- Upload,
-} from 'antd';
+ FormControl,
+ 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 { Textarea } from '@/components/ui/textarea';
+import { useSetModalState } from '@/hooks/common-hooks';
+import { useSetSelectedRecord } from '@/hooks/logic-hooks';
+import { zodResolver } from '@hookform/resolvers/zod';
import { UploadChangeParam, UploadFile } from 'antd/es/upload';
-import { pick } from 'lodash';
-import { Link } from 'lucide-react';
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useMemo, useState } from 'react';
+import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
+import { z } from 'zod';
import { BeginQueryType } from '../constant';
import { BeginQuery } from '../interface';
-import { PopoverForm } from './popover-form';
-import styles from './index.less';
+export const BeginQueryComponentMap = {
+ [BeginQueryType.Line]: 'string',
+ [BeginQueryType.Paragraph]: 'string',
+ [BeginQueryType.Options]: 'string',
+ [BeginQueryType.File]: 'file',
+ [BeginQueryType.Integer]: 'number',
+ [BeginQueryType.Boolean]: 'boolean',
+};
+
+const StringFields = [
+ BeginQueryType.Line,
+ BeginQueryType.Paragraph,
+ BeginQueryType.Options,
+];
interface IProps {
parameters: BeginQuery[];
@@ -34,6 +46,8 @@ interface IProps {
submitButtonDisabled?: boolean;
}
+const values = {};
+
const DebugContent = ({
parameters,
ok,
@@ -42,15 +56,48 @@ const DebugContent = ({
submitButtonDisabled = false,
}: IProps) => {
const { t } = useTranslation();
- const [form] = Form.useForm();
+
+ const FormSchema = useMemo(() => {
+ const obj = parameters.reduce((pre, cur, idx) => {
+ const type = cur.type;
+ let fieldSchema;
+ if (StringFields.some((x) => x === type)) {
+ fieldSchema = z.string();
+ } else if (type === BeginQueryType.Boolean) {
+ fieldSchema = z.boolean();
+ } else if (type === BeginQueryType.Integer) {
+ fieldSchema = z.coerce.number();
+ } else {
+ fieldSchema = z.instanceof(File);
+ }
+
+ if (cur.optional) {
+ fieldSchema.optional();
+ }
+
+ pre[idx.toString()] = fieldSchema;
+
+ return pre;
+ }, {});
+
+ return z.object(obj);
+ }, [parameters]);
+
+ const form = useForm({
+ defaultValues: values,
+ resolver: zodResolver(FormSchema),
+ });
+
const {
visible,
hideModal: hidePopover,
switchVisible,
showModal: showPopover,
} = useSetModalState();
+
const { setRecord, currentRecord } = useSetSelectedRecord();
- const { submittable } = useHandleSubmittable(form);
+ // const { submittable } = useHandleSubmittable(form);
+ const submittable = true;
const [isUploading, setIsUploading] = useState(false);
const handleShowPopover = useCallback(
@@ -79,8 +126,8 @@ const DebugContent = ({
);
const renderWidget = useCallback(
- (q: BeginQuery, idx: number) => {
- const props: FormItemProps & { key: number } = {
+ (q: BeginQuery, idx: string) => {
+ const props = {
key: idx,
label: q.name ?? q.key,
name: idx,
@@ -89,80 +136,119 @@ const DebugContent = ({
props.rules = [{ required: true }];
}
- const urlList: { url: string; result: string }[] =
- form.getFieldValue(idx) || [];
+ // const urlList: { url: string; result: string }[] =
+ // form.getFieldValue(idx) || [];
+
+ const urlList: { url: string; result: string }[] = [];
const BeginQueryTypeMap = {
[BeginQueryType.Line]: (
-
-
-
+ (
+
+ {props.label}
+
+
+
+
+
+ )}
+ />
),
[BeginQueryType.Paragraph]: (
-
-
-
+ (
+
+ {props.label}
+
+
+
+
+
+ )}
+ />
),
[BeginQueryType.Options]: (
-
- ({ label: x, value: x })) ?? []}
- >
-
+ (
+
+ {props.label}
+
+ ({ label: x, value: x })) ?? []
+ }
+ {...field}
+ >
+
+
+
+ )}
+ />
),
[BeginQueryType.File]: (
-
-
-
-
- }>
- {t('common.upload')}
-
-
-
-
0 ? 'mb-1' : ''}
- noStyle
- >
-
- }
- >
- {t('flow.pasteFileLink')}
-
-
-
-
-
-
+ (
+
+
+ {t('assistantAvatar')}
+
+
+
+
+
+
+ )}
+ />
),
[BeginQueryType.Integer]: (
-
-
-
+ (
+
+ {props.label}
+
+
+
+
+
+ )}
+ />
),
[BeginQueryType.Boolean]: (
-
-
-
+ (
+
+ {props.label}
+
+
+
+
+
+ )}
+ />
),
};
@@ -171,66 +257,53 @@ const DebugContent = ({
BeginQueryTypeMap[BeginQueryType.Paragraph]
);
},
- [form, handleShowPopover, onChange, switchVisible, t, visible],
+ [form, t],
);
- const onOk = useCallback(async () => {
- const values = await form.validateFields();
- const nextValues = Object.entries(values).map(([key, value]) => {
- const item = parameters[Number(key)];
- let nextValue = value;
- if (Array.isArray(value)) {
- nextValue = ``;
-
- value.forEach((x) => {
- nextValue +=
- x?.originFileObj instanceof File
- ? `${x.name}\n${x.response?.data}\n----\n`
- : `${x.url}\n${x.result}\n----\n`;
- });
- }
- return { ...item, value: nextValue };
- });
+ const onSubmit = useCallback(
+ (values: z.infer) => {
+ console.log('🚀 ~ values:', values);
+ return values;
+ const nextValues = Object.entries(values).map(([key, value]) => {
+ const item = parameters[Number(key)];
+ let nextValue = value;
+ if (Array.isArray(value)) {
+ nextValue = ``;
+
+ value.forEach((x) => {
+ nextValue +=
+ x?.originFileObj instanceof File
+ ? `${x.name}\n${x.response?.data}\n----\n`
+ : `${x.url}\n${x.result}\n----\n`;
+ });
+ }
+ return { ...item, value: nextValue };
+ });
- ok(nextValues);
- }, [form, ok, parameters]);
+ ok(nextValues);
+ },
+ [ok, parameters],
+ );
return (
<>
-
- {
- if (name === 'urlForm') {
- const { basicForm } = forms;
- const urlInfo = basicForm.getFieldValue(currentRecord) || [];
- basicForm.setFieldsValue({
- [currentRecord]: [...urlInfo, { ...values, name: values.url }],
- });
- hidePopover();
- }
- }}
- >
-
+
+ {t(isNext ? 'common.next' : 'flow.run')}
+
+
+
-
- {t(isNext ? 'common.next' : 'flow.run')}
-
>
);
};
diff --git a/web/src/pages/agent/debug-content/popover-form.tsx b/web/src/pages/agent/debug-content/popover-form.tsx
index 557e3185bc3..9465d903b46 100644
--- a/web/src/pages/agent/debug-content/popover-form.tsx
+++ b/web/src/pages/agent/debug-content/popover-form.tsx
@@ -1,74 +1,103 @@
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { Popover, PopoverContent } from '@/components/ui/popover';
import { useParseDocument } from '@/hooks/document-hooks';
-import { useResetFormOnCloseModal } from '@/hooks/logic-hooks';
import { IModalProps } from '@/interfaces/common';
-import { Button, Form, Input, Popover } from 'antd';
+import { zodResolver } from '@hookform/resolvers/zod';
import { PropsWithChildren } from 'react';
+import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
+import { z } from 'zod';
const reg =
/^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/;
+const FormSchema = z.object({
+ url: z.string(),
+ result: z.any(),
+});
+
+const values = {
+ url: '',
+ result: null,
+};
+
export const PopoverForm = ({
children,
visible,
switchVisible,
}: PropsWithChildren>) => {
- const [form] = Form.useForm();
+ const form = useForm({
+ defaultValues: values,
+ resolver: zodResolver(FormSchema),
+ });
const { parseDocument, loading } = useParseDocument();
const { t } = useTranslation();
- useResetFormOnCloseModal({
- form,
- visible,
- });
+ // useResetFormOnCloseModal({
+ // form,
+ // visible,
+ // });
- const onOk = async () => {
- const values = await form.validateFields();
+ async function onSubmit(values: z.infer) {
const val = values.url;
if (reg.test(val)) {
const ret = await parseDocument(val);
if (ret?.data?.code === 0) {
- form.setFieldValue('result', ret?.data?.data);
- form.submit();
+ form.setValue('result', ret?.data?.data);
}
}
- };
+ }
const content = (
-
-
- e.preventDefault()}
- placeholder={t('flow.pasteFileLink')}
- suffix={
-
- {t('common.submit')}
-
- }
+
+
+ (
+
+
+ e.preventDefault()}
+ placeholder={t('flow.pasteFileLink')}
+ // suffix={
+ //
+ // {t('common.submit')}
+ //
+ // }
+ />
+
+
+
+ )}
+ />
+ <>>}
/>
-
-
+
);
return (
-
+
{children}
+ {content}
);
};
diff --git a/web/src/pages/agent/form-sheet/next.tsx b/web/src/pages/agent/form-sheet/next.tsx
index 7335844c2d6..fb500458efd 100644
--- a/web/src/pages/agent/form-sheet/next.tsx
+++ b/web/src/pages/agent/form-sheet/next.tsx
@@ -9,20 +9,14 @@ import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { cn } from '@/lib/utils';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { get, isPlainObject, lowerFirst } from 'lodash';
+import { lowerFirst } from 'lodash';
import { Play, X } from 'lucide-react';
-import { useEffect, useRef } from 'react';
-import { useForm } from 'react-hook-form';
-import { BeginId, Operator, operatorMap } from '../constant';
-import { FlowFormContext } from '../context';
+import { BeginId, Operator } from '../constant';
+import { AgentFormContext } from '../context';
import { RunTooltip } from '../flow-tooltip';
-import { useHandleFormValuesChange, useHandleNodeNameChange } from '../hooks';
+import { useHandleNodeNameChange } from '../hooks';
import OperatorIcon from '../operator-icon';
-import {
- buildCategorizeListFromObject,
- needsSingleStepDebugging,
-} from '../utils';
+import { needsSingleStepDebugging } from '../utils';
import SingleDebugDrawer from './single-debug-drawer';
import { useFormConfigMap } from './use-form-config-map';
@@ -51,61 +45,21 @@ const FormSheet = ({
const OperatorForm = currentFormMap.component ?? EmptyContent;
- const form = useForm({
- defaultValues: currentFormMap.defaultValues,
- resolver: zodResolver(currentFormMap.schema),
- });
-
const { name, handleNameBlur, handleNameChange } = useHandleNodeNameChange({
id: node?.id,
data: node?.data,
});
- const previousId = useRef(node?.id);
-
const { t } = useTranslate('flow');
- const { handleValuesChange } = useHandleFormValuesChange(
- operatorName,
- node?.id,
- form,
- );
-
- useEffect(() => {
- if (visible && !form.formState.isDirty) {
- if (node?.id !== previousId.current) {
- form.reset();
- form.clearErrors();
- }
-
- if (operatorName === Operator.Categorize) {
- const items = buildCategorizeListFromObject(
- get(node, 'data.form.category_description', {}),
- );
- const formData = node?.data?.form;
- if (isPlainObject(formData)) {
- // form.setFieldsValue({ ...formData, items });
- form.reset({ ...formData, items });
- }
- } else {
- // form.setFieldsValue(node?.data?.form);
- form.reset(node?.data?.form);
- }
- previousId.current = node?.id;
- }
- }, [visible, form, node?.data?.form, node?.id, node, operatorName]);
-
return (
-
-
+
+
-
+
{t('title')}
{node?.id === BeginId ? (
@@ -132,15 +86,11 @@ const FormSheet = ({
{t(`${lowerFirst(operatorName)}Description`)}
-
+
{visible && (
-
-
-
+
+
+
)}
diff --git a/web/src/pages/agent/form-sheet/use-form-config-map.tsx b/web/src/pages/agent/form-sheet/use-form-config-map.tsx
index 4371b747171..d887bf58c35 100644
--- a/web/src/pages/agent/form-sheet/use-form-config-map.tsx
+++ b/web/src/pages/agent/form-sheet/use-form-config-map.tsx
@@ -1,6 +1,9 @@
+import { LlmSettingSchema } from '@/components/llm-setting-items/next';
+import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { Operator } from '../constant';
+import AgentForm from '../form/agent-form';
import AkShareForm from '../form/akshare-form';
import AnswerForm from '../form/answer-form';
import ArXivForm from '../form/arxiv-form';
@@ -9,6 +12,7 @@ import BaiduForm from '../form/baidu-form';
import BeginForm from '../form/begin-form';
import BingForm from '../form/bing-form';
import CategorizeForm from '../form/categorize-form';
+import CodeForm from '../form/code-form';
import CrawlerForm from '../form/crawler-form';
import DeepLForm from '../form/deepl-form';
import DuckDuckGoForm from '../form/duckduckgo-form';
@@ -30,6 +34,7 @@ import RetrievalForm from '../form/retrieval-form/next';
import RewriteQuestionForm from '../form/rewrite-question-form';
import SwitchForm from '../form/switch-form';
import TemplateForm from '../form/template-form';
+import ToolForm from '../form/tool-form';
import TuShareForm from '../form/tushare-form';
import WenCaiForm from '../form/wencai-form';
import WikipediaForm from '../form/wikipedia-form';
@@ -43,18 +48,27 @@ export function useFormConfigMap() {
component: BeginForm,
defaultValues: {},
schema: z.object({
- name: z
+ enablePrologue: z.boolean().optional(),
+ prologue: z
.string()
.min(1, {
message: t('common.namePlaceholder'),
})
- .trim(),
- age: z
- .string()
- .min(1, {
- message: t('common.namePlaceholder'),
- })
- .trim(),
+ .trim()
+ .optional(),
+ mode: z.string(),
+ query: z
+ .array(
+ z.object({
+ key: z.string(),
+ type: z.string(),
+ value: z.string(),
+ optional: z.boolean(),
+ name: z.string(),
+ options: z.array(z.union([z.number(), z.string(), z.boolean()])),
+ }),
+ )
+ .optional(),
}),
},
[Operator.Retrieval]: {
@@ -99,20 +113,40 @@ export function useFormConfigMap() {
},
[Operator.Categorize]: {
component: CategorizeForm,
- defaultValues: { message_history_window_size: 1 },
+ defaultValues: {},
schema: z.object({
- message_history_window_size: z.number(),
+ parameter: z.string().optional(),
+ ...LlmSettingSchema,
+ message_history_window_size: z.coerce.number(),
items: z.array(
- z.object({
- name: z.string().min(1, t('flow.nameMessage')).trim(),
- }),
+ z
+ .object({
+ name: z.string().min(1, t('flow.nameMessage')).trim(),
+ description: z.string().optional(),
+ // examples: z
+ // .array(
+ // z.object({
+ // value: z.string(),
+ // }),
+ // )
+ // .optional(),
+ })
+ .optional(),
),
}),
},
[Operator.Message]: {
component: MessageForm,
defaultValues: {},
- schema: z.object({}),
+ schema: z.object({
+ content: z
+ .array(
+ z.object({
+ value: z.string(),
+ }),
+ )
+ .optional(),
+ }),
},
[Operator.Relevant]: {
component: RelevantForm,
@@ -130,6 +164,41 @@ export function useFormConfigMap() {
language: z.string(),
}),
},
+ [Operator.Code]: {
+ component: CodeForm,
+ defaultValues: {
+ lang: ProgrammingLanguage.Python,
+ script: CodeTemplateStrMap[ProgrammingLanguage.Python],
+ arguments: [],
+ },
+ schema: z.object({
+ lang: z.string(),
+ script: z.string(),
+ arguments: z.array(
+ z.object({ name: z.string(), component_id: z.string() }),
+ ),
+ return: z.union([
+ z
+ .array(z.object({ name: z.string(), component_id: z.string() }))
+ .optional(),
+ z.object({ name: z.string(), component_id: z.string() }),
+ ]),
+ }),
+ },
+ [Operator.WaitingDialogue]: {
+ component: CodeForm,
+ defaultValues: {},
+ schema: z.object({
+ arguments: z.array(
+ z.object({ name: z.string(), component_id: z.string() }),
+ ),
+ }),
+ },
+ [Operator.Agent]: {
+ component: AgentForm,
+ defaultValues: {},
+ schema: z.object({}),
+ },
[Operator.Baidu]: {
component: BaiduForm,
defaultValues: { top_n: 10 },
@@ -301,6 +370,11 @@ export function useFormConfigMap() {
defaultValues: {},
schema: z.object({}),
},
+ [Operator.Tool]: {
+ component: ToolForm,
+ defaultValues: {},
+ schema: z.object({}),
+ },
};
return FormConfigMap;
diff --git a/web/src/pages/agent/form-sheet/use-values.ts b/web/src/pages/agent/form-sheet/use-values.ts
new file mode 100644
index 00000000000..eccee9c7737
--- /dev/null
+++ b/web/src/pages/agent/form-sheet/use-values.ts
@@ -0,0 +1,42 @@
+import { RAGFlowNodeType } from '@/interfaces/database/flow';
+import { get, isEmpty, isPlainObject, omit } from 'lodash';
+import { useMemo, useRef } from 'react';
+import { Operator } from '../constant';
+import { buildCategorizeListFromObject, convertToObjectArray } from '../utils';
+import { useFormConfigMap } from './use-form-config-map';
+
+export function useValues(node?: RAGFlowNodeType, isDirty?: boolean) {
+ const operatorName: Operator = node?.data.label as Operator;
+ const previousId = useRef(node?.id);
+
+ const FormConfigMap = useFormConfigMap();
+
+ const currentFormMap = FormConfigMap[operatorName];
+
+ const values = useMemo(() => {
+ const formData = node?.data?.form;
+ if (operatorName === Operator.Categorize) {
+ const items = buildCategorizeListFromObject(
+ get(node, 'data.form.category_description', {}),
+ );
+ if (isPlainObject(formData)) {
+ console.info('xxx');
+ const nextValues = {
+ ...omit(formData, 'category_description'),
+ items,
+ };
+
+ return nextValues;
+ }
+ } else if (operatorName === Operator.Message) {
+ return {
+ ...formData,
+ content: convertToObjectArray(formData.content),
+ };
+ } else {
+ return isEmpty(formData) ? currentFormMap : formData;
+ }
+ }, [currentFormMap, node, operatorName]);
+
+ return values;
+}
diff --git a/web/src/pages/agent/form/agent-form/agent-tools.tsx b/web/src/pages/agent/form/agent-form/agent-tools.tsx
new file mode 100644
index 00000000000..25bbead6d71
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/agent-tools.tsx
@@ -0,0 +1,53 @@
+import { BlockButton } from '@/components/ui/button';
+import { cn } from '@/lib/utils';
+import { PencilLine, X } from 'lucide-react';
+import { PropsWithChildren } from 'react';
+import { ToolPopover } from './tool-popover';
+import { useDeleteAgentNodeTools } from './tool-popover/use-update-tools';
+import { useGetAgentToolNames } from './use-get-tools';
+
+export function ToolCard({
+ children,
+ className,
+ ...props
+}: PropsWithChildren & React.HTMLAttributes) {
+ return (
+
+ {children}
+
+ );
+}
+
+export function AgentTools() {
+ const { toolNames } = useGetAgentToolNames();
+ const { deleteNodeTool } = useDeleteAgentNodeTools();
+
+ return (
+
+ Tools
+
+ {toolNames.map((x) => (
+
+ {x}
+
+
+ ))}
+
+
+ Add Tool
+
+
+ );
+}
diff --git a/web/src/pages/agent/form/agent-form/dynamic-prompt.tsx b/web/src/pages/agent/form/agent-form/dynamic-prompt.tsx
new file mode 100644
index 00000000000..1cda9fbd508
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/dynamic-prompt.tsx
@@ -0,0 +1,93 @@
+import { BlockButton, Button } from '@/components/ui/button';
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { RAGFlowSelect } from '@/components/ui/select';
+import { X } from 'lucide-react';
+import { memo } from 'react';
+import { useFieldArray, useFormContext } from 'react-hook-form';
+import { useTranslation } from 'react-i18next';
+import { PromptRole } from '../../constant';
+import { PromptEditor } from '../components/prompt-editor';
+
+const options = [
+ { label: 'User', value: PromptRole.User },
+ { label: 'Assistant', value: PromptRole.Assistant },
+];
+
+const DynamicPrompt = () => {
+ const { t } = useTranslation();
+ const form = useFormContext();
+ const name = 'prompts';
+
+ const { fields, append, remove } = useFieldArray({
+ name: name,
+ control: form.control,
+ });
+
+ return (
+
+ {t('flow.msg')}
+
+ {fields.map((field, index) => (
+
+
+
(
+
+
+
+
+
+
+
+ )}
+ />
+
+ (
+
+
+
+
+
+ )}
+ />
+
+
remove(index)}
+ >
+
+
+
+ ))}
+
+
+ append({ content: '', role: PromptRole.User })}
+ >
+ Add
+
+
+ );
+};
+
+export default memo(DynamicPrompt);
diff --git a/web/src/pages/agent/form/agent-form/dynamic-tool.tsx b/web/src/pages/agent/form/agent-form/dynamic-tool.tsx
new file mode 100644
index 00000000000..afda465b652
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/dynamic-tool.tsx
@@ -0,0 +1,63 @@
+import { BlockButton, Button } from '@/components/ui/button';
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui/form';
+import { X } from 'lucide-react';
+import { memo } from 'react';
+import { useFieldArray, useFormContext } from 'react-hook-form';
+import { PromptEditor } from '../components/prompt-editor';
+
+const DynamicTool = () => {
+ const form = useFormContext();
+ const name = 'tools';
+
+ const { fields, append, remove } = useFieldArray({
+ name: name,
+ control: form.control,
+ });
+
+ return (
+
+
+ {fields.map((field, index) => (
+
+
+
(
+
+
+
+
+
+ )}
+ />
+
+
remove(index)}
+ >
+
+
+
+ ))}
+
+
+ append({ component_name: '' })}>
+ Add
+
+
+ );
+};
+
+export default memo(DynamicTool);
diff --git a/web/src/pages/agent/form/agent-form/index.tsx b/web/src/pages/agent/form/agent-form/index.tsx
new file mode 100644
index 00000000000..4825f20c908
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/index.tsx
@@ -0,0 +1,131 @@
+import { FormContainer } from '@/components/form-container';
+import { LargeModelFormField } from '@/components/large-model-form-field';
+import { LlmSettingSchema } from '@/components/llm-setting-items/next';
+import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
+import { BlockButton } from '@/components/ui/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+} from '@/components/ui/form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Position } from '@xyflow/react';
+import { useContext, useMemo } from 'react';
+import { useForm } from 'react-hook-form';
+import { useTranslation } from 'react-i18next';
+import { z } from 'zod';
+import { Operator, initialAgentValues } from '../../constant';
+import { AgentInstanceContext } from '../../context';
+import { INextOperatorForm } from '../../interface';
+import { Output } from '../components/output';
+import { PromptEditor } from '../components/prompt-editor';
+import { AgentTools } from './agent-tools';
+import { useValues } from './use-values';
+import { useWatchFormChange } from './use-watch-change';
+
+const FormSchema = z.object({
+ sys_prompt: z.string(),
+ prompts: z.string().optional(),
+ // prompts: z
+ // .array(
+ // z.object({
+ // role: z.string(),
+ // content: z.string(),
+ // }),
+ // )
+ // .optional(),
+ message_history_window_size: z.coerce.number(),
+ tools: z
+ .array(
+ z.object({
+ component_name: z.string(),
+ }),
+ )
+ .optional(),
+ ...LlmSettingSchema,
+});
+
+const AgentForm = ({ node }: INextOperatorForm) => {
+ const { t } = useTranslation();
+
+ const defaultValues = useValues(node);
+
+ const outputList = useMemo(() => {
+ return [
+ { title: 'content', type: initialAgentValues.outputs.content.type },
+ ];
+ }, []);
+
+ const form = useForm({
+ defaultValues: defaultValues,
+ resolver: zodResolver(FormSchema),
+ });
+
+ useWatchFormChange(node?.id, form);
+
+ const { addCanvasNode } = useContext(AgentInstanceContext);
+
+ return (
+
+ {
+ e.preventDefault();
+ }}
+ >
+
+
+ (
+
+ Prompt
+
+
+
+
+ )}
+ />
+
+
+
+ {/* */}
+ (
+
+
+
+
+
+ )}
+ />
+
+
+
+
+ Add Agent
+
+
+
+
+
+ );
+};
+
+export default AgentForm;
diff --git a/web/src/pages/agent/form/agent-form/tool-popover/index.tsx b/web/src/pages/agent/form/agent-form/tool-popover/index.tsx
new file mode 100644
index 00000000000..7c6bbda79f7
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/tool-popover/index.tsx
@@ -0,0 +1,47 @@
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui/popover';
+import { Operator } from '@/pages/agent/constant';
+import { AgentFormContext, AgentInstanceContext } from '@/pages/agent/context';
+import { Position } from '@xyflow/react';
+import { PropsWithChildren, useCallback, useContext } from 'react';
+import { useDeleteToolNode } from '../use-delete-tool-node';
+import { useGetAgentToolNames } from '../use-get-tools';
+import { ToolCommand } from './tool-command';
+import { useUpdateAgentNodeTools } from './use-update-tools';
+
+export function ToolPopover({ children }: PropsWithChildren) {
+ const { addCanvasNode } = useContext(AgentInstanceContext);
+ const node = useContext(AgentFormContext);
+ const { updateNodeTools } = useUpdateAgentNodeTools();
+ const { toolNames } = useGetAgentToolNames();
+ const { deleteToolNode } = useDeleteToolNode();
+
+ const handleChange = useCallback(
+ (value: string[]) => {
+ if (Array.isArray(value) && node?.id) {
+ updateNodeTools(value);
+ if (value.length > 0) {
+ addCanvasNode(Operator.Tool, {
+ position: Position.Bottom,
+ nodeId: node?.id,
+ })();
+ } else {
+ deleteToolNode(node.id); // TODO: The tool node should be derived from the agent tools data
+ }
+ }
+ },
+ [addCanvasNode, deleteToolNode, node?.id, updateNodeTools],
+ );
+
+ return (
+
+ {children}
+
+
+
+
+ );
+}
diff --git a/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx b/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx
new file mode 100644
index 00000000000..5d948fe4715
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/tool-popover/tool-command.tsx
@@ -0,0 +1,113 @@
+import { Calendar, CheckIcon } from 'lucide-react';
+
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from '@/components/ui/command';
+import { cn } from '@/lib/utils';
+import { Operator } from '@/pages/agent/constant';
+import { useCallback, useEffect, useState } from 'react';
+
+const Menus = [
+ {
+ label: 'Search',
+ list: [
+ Operator.Tavily,
+ Operator.Google,
+ Operator.Bing,
+ Operator.DuckDuckGo,
+ Operator.Wikipedia,
+ Operator.YahooFinance,
+ Operator.PubMed,
+ Operator.GoogleScholar,
+ ],
+ },
+ {
+ label: 'Communication',
+ list: [Operator.Email],
+ },
+ {
+ label: 'Productivity',
+ list: [],
+ },
+ {
+ label: 'Developer',
+ list: [
+ Operator.GitHub,
+ Operator.ExeSQL,
+ Operator.Invoke,
+ Operator.Crawler,
+ Operator.Code,
+ ],
+ },
+];
+
+type ToolCommandProps = {
+ value?: string[];
+ onChange?(values: string[]): void;
+};
+
+export function ToolCommand({ value, onChange }: ToolCommandProps) {
+ const [currentValue, setCurrentValue] = useState([]);
+
+ const toggleOption = useCallback(
+ (option: string) => {
+ const newSelectedValues = currentValue.includes(option)
+ ? currentValue.filter((value) => value !== option)
+ : [...currentValue, option];
+ setCurrentValue(newSelectedValues);
+ onChange?.(newSelectedValues);
+ },
+ [currentValue, onChange],
+ );
+
+ useEffect(() => {
+ if (Array.isArray(value)) {
+ setCurrentValue(value);
+ }
+ }, [value]);
+
+ return (
+
+
+
+ No results found.
+ {Menus.map((x) => (
+
+ {x.list.map((y) => {
+ const isSelected = currentValue.includes(y);
+ return (
+ toggleOption(y)}
+ >
+
+
+
+ {/* {option.icon && (
+
+ )} */}
+ {/* {option.label} */}
+
+ {y}
+
+ );
+ })}
+
+ ))}
+
+
+ );
+}
diff --git a/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts b/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts
new file mode 100644
index 00000000000..3bcf844847b
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/tool-popover/use-update-tools.ts
@@ -0,0 +1,67 @@
+import { IAgentForm } from '@/interfaces/database/agent';
+import { AgentFormContext } from '@/pages/agent/context';
+import useGraphStore from '@/pages/agent/store';
+import { get } from 'lodash';
+import { useCallback, useContext, useMemo } from 'react';
+import { useDeleteToolNode } from '../use-delete-tool-node';
+
+export function useGetNodeTools() {
+ const node = useContext(AgentFormContext);
+
+ return useMemo(() => {
+ const tools: IAgentForm['tools'] = get(node, 'data.form.tools');
+ return tools;
+ }, [node]);
+}
+
+export function useUpdateAgentNodeTools() {
+ const { updateNodeForm } = useGraphStore((state) => state);
+ const node = useContext(AgentFormContext);
+ const tools = useGetNodeTools();
+
+ const updateNodeTools = useCallback(
+ (value: string[]) => {
+ if (node?.id) {
+ const nextValue = value.reduce((pre, cur) => {
+ const tool = tools.find((x) => x.component_name === cur);
+ pre.push(tool ? tool : { component_name: cur, params: {} });
+ return pre;
+ }, []);
+
+ updateNodeForm(node?.id, nextValue, ['tools']);
+ }
+ },
+ [node?.id, tools, updateNodeForm],
+ );
+
+ const deleteNodeTool = useCallback(
+ (value: string) => {
+ updateNodeTools([value]);
+ },
+ [updateNodeTools],
+ );
+
+ return { updateNodeTools, deleteNodeTool };
+}
+
+export function useDeleteAgentNodeTools() {
+ const { updateNodeForm } = useGraphStore((state) => state);
+ const tools = useGetNodeTools();
+ const node = useContext(AgentFormContext);
+ const { deleteToolNode } = useDeleteToolNode();
+
+ const deleteNodeTool = useCallback(
+ (value: string) => () => {
+ const nextTools = tools.filter((x) => x.component_name !== value);
+ if (node?.id) {
+ updateNodeForm(node?.id, nextTools, ['tools']);
+ if (nextTools.length === 0) {
+ deleteToolNode(node?.id);
+ }
+ }
+ },
+ [deleteToolNode, node?.id, tools, updateNodeForm],
+ );
+
+ return { deleteNodeTool };
+}
diff --git a/web/src/pages/agent/form/agent-form/use-delete-tool-node.ts b/web/src/pages/agent/form/agent-form/use-delete-tool-node.ts
new file mode 100644
index 00000000000..b3227459581
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/use-delete-tool-node.ts
@@ -0,0 +1,24 @@
+import { useCallback } from 'react';
+import { NodeHandleId } from '../../constant';
+import useGraphStore from '../../store';
+
+export function useDeleteToolNode() {
+ const { edges, deleteEdgeById, deleteNodeById } = useGraphStore(
+ (state) => state,
+ );
+ const deleteToolNode = useCallback(
+ (agentNodeId: string) => {
+ const edge = edges.find(
+ (x) => x.source === agentNodeId && x.sourceHandle === NodeHandleId.Tool,
+ );
+
+ if (edge) {
+ deleteEdgeById(edge.id);
+ deleteNodeById(edge.target);
+ }
+ },
+ [deleteEdgeById, deleteNodeById, edges],
+ );
+
+ return { deleteToolNode };
+}
diff --git a/web/src/pages/agent/form/agent-form/use-get-tools.ts b/web/src/pages/agent/form/agent-form/use-get-tools.ts
new file mode 100644
index 00000000000..c9f37113de5
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/use-get-tools.ts
@@ -0,0 +1,15 @@
+import { IAgentForm } from '@/interfaces/database/agent';
+import { get } from 'lodash';
+import { useContext, useMemo } from 'react';
+import { AgentFormContext } from '../../context';
+
+export function useGetAgentToolNames() {
+ const node = useContext(AgentFormContext);
+
+ const toolNames = useMemo(() => {
+ const tools: IAgentForm['tools'] = get(node, 'data.form.tools', []);
+ return tools.map((x) => x.component_name);
+ }, [node]);
+
+ return { toolNames };
+}
diff --git a/web/src/pages/agent/form/agent-form/use-values.ts b/web/src/pages/agent/form/agent-form/use-values.ts
new file mode 100644
index 00000000000..3e6b057a6a6
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/use-values.ts
@@ -0,0 +1,30 @@
+import { useFetchModelId } from '@/hooks/logic-hooks';
+import { RAGFlowNodeType } from '@/interfaces/database/flow';
+import { get, isEmpty } from 'lodash';
+import { useMemo } from 'react';
+import { initialAgentValues } from '../../constant';
+
+export function useValues(node?: RAGFlowNodeType) {
+ const llmId = useFetchModelId();
+
+ const defaultValues = useMemo(
+ () => ({
+ ...initialAgentValues,
+ llm_id: llmId,
+ prompts: '',
+ }),
+ [llmId],
+ );
+
+ const values = useMemo(() => {
+ const formData = node?.data?.form;
+
+ if (isEmpty(formData)) {
+ return defaultValues;
+ }
+
+ return { ...formData, prompts: get(formData, 'prompts.0.content', '') };
+ }, [defaultValues, node?.data?.form]);
+
+ return values;
+}
diff --git a/web/src/pages/agent/form/agent-form/use-watch-change.ts b/web/src/pages/agent/form/agent-form/use-watch-change.ts
new file mode 100644
index 00000000000..8640a45184a
--- /dev/null
+++ b/web/src/pages/agent/form/agent-form/use-watch-change.ts
@@ -0,0 +1,22 @@
+import { useEffect } from 'react';
+import { UseFormReturn, useWatch } from 'react-hook-form';
+import { PromptRole } from '../../constant';
+import useGraphStore from '../../store';
+
+export function useWatchFormChange(id?: string, form?: UseFormReturn) {
+ let values = useWatch({ control: form?.control });
+ const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
+
+ useEffect(() => {
+ // Manually triggered form updates are synchronized to the canvas
+ if (id && form?.formState.isDirty) {
+ values = form?.getValues();
+ let nextValues: any = {
+ ...values,
+ prompts: [{ role: PromptRole.User, content: values.prompts }],
+ };
+
+ updateNodeForm(id, nextValues);
+ }
+ }, [form?.formState.isDirty, id, updateNodeForm, values]);
+}
diff --git a/web/src/pages/agent/form/begin-form/begin-dynamic-options.tsx b/web/src/pages/agent/form/begin-form/begin-dynamic-options.tsx
index bcc9c578843..d71da8d2bbf 100644
--- a/web/src/pages/agent/form/begin-form/begin-dynamic-options.tsx
+++ b/web/src/pages/agent/form/begin-form/begin-dynamic-options.tsx
@@ -1,68 +1,57 @@
-import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
-import { Button, Form, Input } from 'antd';
+'use client';
+
+import { BlockButton, Button } from '@/components/ui/button';
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { X } from 'lucide-react';
+import { useFieldArray, useFormContext } from 'react-hook-form';
+import { useTranslation } from 'react-i18next';
+
+export function BeginDynamicOptions() {
+ const { t } = useTranslation();
+ const form = useFormContext();
+ const name = 'options';
+
+ const { fields, remove, append } = useFieldArray({
+ name: name,
+ control: form.control,
+ });
-const BeginDynamicOptions = () => {
return (
- {
- if (!names || names.length < 1) {
- return Promise.reject(new Error('At least 1 option'));
- }
- },
- },
- ]}
- >
- {(fields, { add, remove }, { errors }) => (
- <>
- {fields.map((field, index) => (
-
-
-
-
- {fields.length > 1 ? (
- remove(field.name)}
- />
- ) : null}
-
- ))}
-
- add()}
- icon={ }
- block
- >
- Add option
+
+ {fields.map((field, index) => {
+ const typeField = `${name}.${index}.value`;
+ return (
+
+ (
+
+
+
+
+
+
+ )}
+ />
+ remove(index)}>
+
-
-
- >
- )}
-
+
+ );
+ })}
+
append({ value: '' })} type="button">
+ {t('flow.addVariable')}
+
+
);
-};
-
-export default BeginDynamicOptions;
+}
diff --git a/web/src/pages/agent/form/begin-form/index.less b/web/src/pages/agent/form/begin-form/index.less
deleted file mode 100644
index 0a03d47433c..00000000000
--- a/web/src/pages/agent/form/begin-form/index.less
+++ /dev/null
@@ -1,24 +0,0 @@
-.dynamicInputVariable {
- background-color: #ebe9e950;
- :global(.ant-collapse-content) {
- background-color: #f6f6f657;
- }
- :global(.ant-collapse-content-box) {
- padding: 0 !important;
- }
- margin-bottom: 20px;
- .title {
- font-weight: 600;
- font-size: 16px;
- }
-
- .addButton {
- color: rgb(22, 119, 255);
- font-weight: 600;
- }
-}
-
-.addButton {
- color: rgb(22, 119, 255);
- font-weight: 600;
-}
diff --git a/web/src/pages/agent/form/begin-form/index.tsx b/web/src/pages/agent/form/begin-form/index.tsx
index 8df1181f1fa..95c519abbab 100644
--- a/web/src/pages/agent/form/begin-form/index.tsx
+++ b/web/src/pages/agent/form/begin-form/index.tsx
@@ -1,20 +1,74 @@
-import { PlusOutlined } from '@ant-design/icons';
-import { Button, Form, Input } from 'antd';
-import { useCallback } from 'react';
+import { Collapse } from '@/components/collapse';
+import { Button } from '@/components/ui/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { RAGFlowSelect } from '@/components/ui/select';
+import { Switch } from '@/components/ui/switch';
+import { Textarea } from '@/components/ui/textarea';
+import { FormTooltip } from '@/components/ui/tooltip';
+import { buildSelectOptions } from '@/utils/component-util';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Plus } from 'lucide-react';
+import { useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
-import { BeginQuery, IOperatorForm } from '../../interface';
-import { useEditQueryRecord } from './hooks';
-import { ModalForm } from './paramater-modal';
-import QueryTable from './query-table';
+import { z } from 'zod';
+import { AgentDialogueMode } from '../../constant';
+import { INextOperatorForm } from '../../interface';
+import { ParameterDialog } from './parameter-dialog';
+import { QueryTable } from './query-table';
+import { useEditQueryRecord } from './use-edit-query';
+import { useValues } from './use-values';
+import { useWatchFormChange } from './use-watch-change';
-import styles from './index.less';
+const ModeOptions = buildSelectOptions([
+ AgentDialogueMode.Conversational,
+ AgentDialogueMode.Task,
+]);
-type FieldType = {
- prologue?: string;
-};
-
-const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
+const BeginForm = ({ node }: INextOperatorForm) => {
const { t } = useTranslation();
+
+ const values = useValues(node);
+
+ const FormSchema = z.object({
+ enablePrologue: z.boolean().optional(),
+ prologue: z.string().trim().optional(),
+ mode: z.string(),
+ inputs: z
+ .array(
+ z.object({
+ key: z.string(),
+ type: z.string(),
+ value: z.string(),
+ optional: z.boolean(),
+ name: z.string(),
+ options: z.array(z.union([z.number(), z.string(), z.boolean()])),
+ }),
+ )
+ .optional(),
+ });
+
+ const form = useForm({
+ defaultValues: values,
+ resolver: zodResolver(FormSchema),
+ });
+
+ useWatchFormChange(node?.id, form);
+
+ const inputs = useWatch({ control: form.control, name: 'inputs' });
+ const mode = useWatch({ control: form.control, name: 'mode' });
+
+ const enablePrologue = useWatch({
+ control: form.control,
+ name: 'enablePrologue',
+ });
+
const {
ok,
currentRecord,
@@ -22,89 +76,115 @@ const BeginForm = ({ onValuesChange, form }: IOperatorForm) => {
hideModal,
showModal,
otherThanCurrentQuery,
+ handleDeleteRecord,
} = useEditQueryRecord({
form,
- onValuesChange,
+ node,
});
- const handleDeleteRecord = useCallback(
- (idx: number) => {
- const query = form?.getFieldValue('query') || [];
- const nextQuery = query.filter(
- (item: BeginQuery, index: number) => index !== idx,
- );
- onValuesChange?.(
- { query: nextQuery },
- { query: nextQuery, prologue: form?.getFieldValue('prologue') },
- );
- },
- [form, onValuesChange],
- );
-
return (
- {
- if (name === 'queryForm') {
- ok(values as BeginQuery);
- }
- }}
- >
-
-
- name={'prologue'}
- label={t('chat.setAnOpener')}
- tooltip={t('chat.setAnOpenerTip')}
- initialValue={t('chat.setAnOpenerInitial')}
- >
-
-
+
+
+ (
+
+ Mode
+
+
+
+
+
+ )}
+ />
+ {mode === AgentDialogueMode.Conversational && (
+ (
+
+
+ {t('flow.openingSwitch')}
+
+
+
+
+
+
+ )}
+ />
+ )}
+ {enablePrologue && (
+ (
+
+
+ {t('flow.openingCopy')}
+
+
+
+
+
+
+ )}
+ />
+ )}
{/* Create a hidden field to make Form instance record this */}
-
-
-
- prevValues.query !== curValues.query
+
}
+ />
+
+ {t('flow.input')}
+
+
+ }
+ rightContent={
+
{
+ e.preventDefault();
+ showModal();
+ }}
+ >
+
+
}
>
- {({ getFieldValue }) => {
- const query: BeginQuery[] = getFieldValue('query') || [];
- return (
-
- );
- }}
-
+
+
-
showModal()}
- icon={ }
- block
- className={styles.addButton}
- >
- {t('flow.addItem')}
-
{visible && (
-
+ submit={ok}
+ >
)}
-
+
);
};
diff --git a/web/src/pages/agent/form/begin-form/paramater-modal.tsx b/web/src/pages/agent/form/begin-form/paramater-modal.tsx
deleted file mode 100644
index 7d689601b14..00000000000
--- a/web/src/pages/agent/form/begin-form/paramater-modal.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import { useResetFormOnCloseModal } from '@/hooks/logic-hooks';
-import { IModalProps } from '@/interfaces/common';
-import { Form, Input, Modal, Select, Switch } from 'antd';
-import { DefaultOptionType } from 'antd/es/select';
-import { useEffect, useMemo } from 'react';
-import { useTranslation } from 'react-i18next';
-import { BeginQueryType, BeginQueryTypeIconMap } from '../../constant';
-import { BeginQuery } from '../../interface';
-import BeginDynamicOptions from './begin-dynamic-options';
-
-export const ModalForm = ({
- visible,
- initialValue,
- hideModal,
- otherThanCurrentQuery,
-}: IModalProps
& {
- initialValue: BeginQuery;
- otherThanCurrentQuery: BeginQuery[];
-}) => {
- const { t } = useTranslation();
- const [form] = Form.useForm();
- const options = useMemo(() => {
- return Object.values(BeginQueryType).reduce(
- (pre, cur) => {
- const Icon = BeginQueryTypeIconMap[cur];
-
- return [
- ...pre,
- {
- label: (
-
-
- {cur}
-
- ),
- value: cur,
- },
- ];
- },
- [],
- );
- }, []);
-
- useResetFormOnCloseModal({
- form,
- visible: visible,
- });
-
- useEffect(() => {
- form.setFieldsValue(initialValue);
- }, [form, initialValue]);
-
- const onOk = () => {
- form.submit();
- };
-
- return (
-
-
-
-
-
- ({
- validator(_, value) {
- if (
- !value ||
- !otherThanCurrentQuery.some((x) => x.key === value)
- ) {
- return Promise.resolve();
- }
- return Promise.reject(new Error('The key cannot be repeated!'));
- },
- }),
- ]}
- >
-
-
-
-
-
-
-
-
-
- prevValues.type !== curValues.type
- }
- >
- {({ getFieldValue }) => {
- const type: BeginQueryType = getFieldValue('type');
- return (
- type === BeginQueryType.Options && (
-
- )
- );
- }}
-
-
-
- );
-};
diff --git a/web/src/pages/agent/form/begin-form/parameter-dialog.tsx b/web/src/pages/agent/form/begin-form/parameter-dialog.tsx
new file mode 100644
index 00000000000..1ed08a132f3
--- /dev/null
+++ b/web/src/pages/agent/form/begin-form/parameter-dialog.tsx
@@ -0,0 +1,217 @@
+import { Button } from '@/components/ui/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { RAGFlowSelect, RAGFlowSelectOptionType } from '@/components/ui/select';
+import { Switch } from '@/components/ui/switch';
+import { IModalProps } from '@/interfaces/common';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { isEmpty } from 'lodash';
+import { useEffect, useMemo } from 'react';
+import { useForm, useWatch } from 'react-hook-form';
+import { useTranslation } from 'react-i18next';
+import { z } from 'zod';
+import { BeginQueryType, BeginQueryTypeIconMap } from '../../constant';
+import { BeginQuery } from '../../interface';
+import { BeginDynamicOptions } from './begin-dynamic-options';
+
+type ModalFormProps = {
+ initialValue: BeginQuery;
+ otherThanCurrentQuery: BeginQuery[];
+ submit(values: any): void;
+};
+
+const FormId = 'BeginParameterForm';
+
+function ParameterForm({
+ initialValue,
+ otherThanCurrentQuery,
+ submit,
+}: ModalFormProps) {
+ const FormSchema = z.object({
+ type: z.string(),
+ key: z
+ .string()
+ .trim()
+ .min(1)
+ .refine(
+ (value) =>
+ !value || !otherThanCurrentQuery.some((x) => x.key === value),
+ { message: 'The key cannot be repeated!' },
+ ),
+ optional: z.boolean(),
+ name: z.string().trim().min(1),
+ options: z
+ .array(z.object({ value: z.string().or(z.boolean()).or(z.number()) }))
+ .optional(),
+ });
+
+ const form = useForm>({
+ resolver: zodResolver(FormSchema),
+ mode: 'onChange',
+ defaultValues: {
+ type: BeginQueryType.Line,
+ optional: false,
+ key: '',
+ name: '',
+ options: [],
+ },
+ });
+
+ const options = useMemo(() => {
+ return Object.values(BeginQueryType).reduce(
+ (pre, cur) => {
+ const Icon = BeginQueryTypeIconMap[cur];
+
+ return [
+ ...pre,
+ {
+ label: (
+
+
+ {cur}
+
+ ),
+ value: cur,
+ },
+ ];
+ },
+ [],
+ );
+ }, []);
+
+ const type = useWatch({
+ control: form.control,
+ name: 'type',
+ });
+
+ useEffect(() => {
+ if (!isEmpty(initialValue)) {
+ form.reset({
+ ...initialValue,
+ options: initialValue.options?.map((x) => ({ value: x })),
+ });
+ }
+ }, [form, initialValue]);
+
+ function onSubmit(data: z.infer) {
+ const values = { ...data, options: data.options?.map((x) => x.value) };
+ console.log('🚀 ~ onSubmit ~ values:', values);
+
+ submit(values);
+ }
+
+ return (
+
+
+ (
+
+ Type
+
+
+
+
+
+ )}
+ />
+ (
+
+ Key
+
+
+
+
+
+ )}
+ />
+ (
+
+ Name
+
+
+
+
+
+ )}
+ />
+ (
+
+ Optional
+
+
+
+
+
+ )}
+ />
+ {type === BeginQueryType.Options && (
+
+ )}
+
+
+ );
+}
+
+export function ParameterDialog({
+ initialValue,
+ hideModal,
+ otherThanCurrentQuery,
+ submit,
+}: ModalFormProps & IModalProps) {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+ {t('flow.variableSettings')}
+
+
+
+
+ Confirm
+
+
+
+
+ );
+}
diff --git a/web/src/pages/agent/form/begin-form/query-table.tsx b/web/src/pages/agent/form/begin-form/query-table.tsx
index c7614e682b4..463a2fa645b 100644
--- a/web/src/pages/agent/form/begin-form/query-table.tsx
+++ b/web/src/pages/agent/form/begin-form/query-table.tsx
@@ -1,10 +1,38 @@
-import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
-import type { TableProps } from 'antd';
-import { Collapse, Space, Table, Tooltip } from 'antd';
-import { BeginQuery } from '../../interface';
+'use client';
+
+import {
+ ColumnDef,
+ ColumnFiltersState,
+ SortingState,
+ VisibilityState,
+ flexRender,
+ getCoreRowModel,
+ getFilteredRowModel,
+ getPaginationRowModel,
+ getSortedRowModel,
+ useReactTable,
+} from '@tanstack/react-table';
+import { Pencil, Trash2 } from 'lucide-react';
+import * as React from 'react';
+import { TableEmpty } from '@/components/table-skeleton';
+import { Button } from '@/components/ui/button';
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '@/components/ui/table';
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from '@/components/ui/tooltip';
+import { cn } from '@/lib/utils';
import { useTranslation } from 'react-i18next';
-import styles from './index.less';
+import { BeginQuery } from '../../interface';
interface IProps {
data: BeginQuery[];
@@ -12,81 +40,150 @@ interface IProps {
showModal(index: number, record: BeginQuery): void;
}
-const QueryTable = ({ data, deleteRecord, showModal }: IProps) => {
+export function QueryTable({ data = [], deleteRecord, showModal }: IProps) {
const { t } = useTranslation();
- const columns: TableProps['columns'] = [
+ const [sorting, setSorting] = React.useState([]);
+ const [columnFilters, setColumnFilters] = React.useState(
+ [],
+ );
+ const [columnVisibility, setColumnVisibility] =
+ React.useState({});
+
+ const columns: ColumnDef[] = [
{
- title: 'Key',
- dataIndex: 'key',
- key: 'key',
- ellipsis: {
- showTitle: false,
+ accessorKey: 'key',
+ header: 'key',
+ meta: { cellClassName: 'max-w-16' },
+ cell: ({ row }) => {
+ const key: string = row.getValue('key');
+ return (
+
+
+ {key}
+
+
+ {key}
+
+
+ );
},
- render: (key) => (
-
- {key}
-
- ),
},
{
- title: t('flow.name'),
- dataIndex: 'name',
- key: 'name',
- ellipsis: {
- showTitle: false,
+ accessorKey: 'name',
+ header: t('flow.name'),
+ meta: { cellClassName: 'max-w-20' },
+ cell: ({ row }) => {
+ const name: string = row.getValue('name');
+ return (
+
+
+ {name}
+
+
+ {name}
+
+
+ );
},
- render: (name) => (
-
- {name}
-
- ),
},
{
- title: t('flow.type'),
- dataIndex: 'type',
- key: 'type',
+ accessorKey: 'type',
+ header: t('flow.type'),
+ cell: ({ row }) => {row.getValue('type')}
,
},
{
- title: t('flow.optional'),
- dataIndex: 'optional',
- key: 'optional',
- render: (optional) => (optional ? 'Yes' : 'No'),
+ accessorKey: 'optional',
+ header: t('flow.optional'),
+ cell: ({ row }) => {row.getValue('optional') ? 'Yes' : 'No'}
,
},
{
- title: t('common.action'),
- key: 'action',
- render: (_, record, idx) => (
-
- showModal(idx, record)} />
- deleteRecord(idx)}
- />
-
- ),
+ id: 'actions',
+ enableHiding: false,
+ header: t('common.action'),
+ cell: ({ row }) => {
+ const record = row.original;
+ const idx = row.index;
+
+ return (
+
+
showModal(idx, record)}>
+
+
+
deleteRecord(idx)}>
+
+
+
+ );
+ },
},
];
+ const table = useReactTable({
+ data,
+ columns,
+ onSortingChange: setSorting,
+ onColumnFiltersChange: setColumnFilters,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ onColumnVisibilityChange: setColumnVisibility,
+ state: {
+ sorting,
+ columnFilters,
+ columnVisibility,
+ },
+ });
+
return (
- {t('flow.input')},
- children: (
-
- columns={columns}
- dataSource={data}
- pagination={false}
- />
- ),
- },
- ]}
- />
+
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ );
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext(),
+ )}
+
+ ))}
+
+ ))
+ ) : (
+
+ )}
+
+
+
+
);
-};
-
-export default QueryTable;
+}
diff --git a/web/src/pages/agent/form/begin-form/hooks.ts b/web/src/pages/agent/form/begin-form/use-edit-query.ts
similarity index 50%
rename from web/src/pages/agent/form/begin-form/hooks.ts
rename to web/src/pages/agent/form/begin-form/use-edit-query.ts
index b045f5dc50d..a1bec8b3d10 100644
--- a/web/src/pages/agent/form/begin-form/hooks.ts
+++ b/web/src/pages/agent/form/begin-form/use-edit-query.ts
@@ -1,32 +1,34 @@
import { useSetModalState } from '@/hooks/common-hooks';
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
import { useCallback, useMemo, useState } from 'react';
-import { BeginQuery, IOperatorForm } from '../../interface';
+import { BeginQuery, INextOperatorForm } from '../../interface';
-export const useEditQueryRecord = ({ form, onValuesChange }: IOperatorForm) => {
+export const useEditQueryRecord = ({ form, node }: INextOperatorForm) => {
const { setRecord, currentRecord } = useSetSelectedRecord();
const { visible, hideModal, showModal } = useSetModalState();
const [index, setIndex] = useState(-1);
const otherThanCurrentQuery = useMemo(() => {
- const query: BeginQuery[] = form?.getFieldValue('query') || [];
- return query.filter((item, idx) => idx !== index);
+ const inputs: BeginQuery[] = form?.getValues('inputs') || [];
+ return inputs.filter((item, idx) => idx !== index);
}, [form, index]);
const handleEditRecord = useCallback(
(record: BeginQuery) => {
- const query: BeginQuery[] = form?.getFieldValue('query') || [];
+ const inputs: BeginQuery[] = form?.getValues('inputs') || [];
+ console.log('🚀 ~ useEditQueryRecord ~ inputs:', inputs);
const nextQuery: BeginQuery[] =
- index > -1 ? query.toSpliced(index, 1, record) : [...query, record];
+ index > -1 ? inputs.toSpliced(index, 1, record) : [...inputs, record];
+
+ form.setValue('inputs', nextQuery, {
+ shouldDirty: true,
+ shouldTouch: true,
+ });
- onValuesChange?.(
- { query: nextQuery },
- { query: nextQuery, prologue: form?.getFieldValue('prologue') },
- );
hideModal();
},
- [form, hideModal, index, onValuesChange],
+ [form, hideModal, index],
);
const handleShowModal = useCallback(
@@ -38,6 +40,18 @@ export const useEditQueryRecord = ({ form, onValuesChange }: IOperatorForm) => {
[setRecord, showModal],
);
+ const handleDeleteRecord = useCallback(
+ (idx: number) => {
+ const inputs = form?.getValues('inputs') || [];
+ const nextQuery = inputs.filter(
+ (item: BeginQuery, index: number) => index !== idx,
+ );
+
+ form.setValue('inputs', nextQuery, { shouldDirty: true });
+ },
+ [form],
+ );
+
return {
ok: handleEditRecord,
currentRecord,
@@ -46,5 +60,6 @@ export const useEditQueryRecord = ({ form, onValuesChange }: IOperatorForm) => {
hideModal,
showModal: handleShowModal,
otherThanCurrentQuery,
+ handleDeleteRecord,
};
};
diff --git a/web/src/pages/agent/form/begin-form/use-values.ts b/web/src/pages/agent/form/begin-form/use-values.ts
new file mode 100644
index 00000000000..10326bae83c
--- /dev/null
+++ b/web/src/pages/agent/form/begin-form/use-values.ts
@@ -0,0 +1,34 @@
+import { RAGFlowNodeType } from '@/interfaces/database/flow';
+import { isEmpty } from 'lodash';
+import { useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { AgentDialogueMode } from '../../constant';
+import { buildBeginInputListFromObject } from './utils';
+
+export function useValues(node?: RAGFlowNodeType) {
+ const { t } = useTranslation();
+
+ const defaultValues = useMemo(
+ () => ({
+ enablePrologue: true,
+ prologue: t('chat.setAnOpenerInitial'),
+ mode: AgentDialogueMode.Conversational,
+ inputs: [],
+ }),
+ [t],
+ );
+
+ const values = useMemo(() => {
+ const formData = node?.data?.form;
+
+ if (isEmpty(formData)) {
+ return defaultValues;
+ }
+
+ const inputs = buildBeginInputListFromObject(formData?.inputs);
+
+ return { ...(formData || {}), inputs };
+ }, [defaultValues, node?.data?.form]);
+
+ return values;
+}
diff --git a/web/src/pages/agent/form/begin-form/use-watch-change.ts b/web/src/pages/agent/form/begin-form/use-watch-change.ts
new file mode 100644
index 00000000000..3dc45126589
--- /dev/null
+++ b/web/src/pages/agent/form/begin-form/use-watch-change.ts
@@ -0,0 +1,31 @@
+import { omit } from 'lodash';
+import { useEffect } from 'react';
+import { UseFormReturn, useWatch } from 'react-hook-form';
+import { BeginQuery } from '../../interface';
+import useGraphStore from '../../store';
+
+function transferInputsArrayToObject(inputs: BeginQuery[] = []) {
+ return inputs.reduce>>((pre, cur) => {
+ pre[cur.key] = omit(cur, 'key');
+
+ return pre;
+ }, {});
+}
+
+export function useWatchFormChange(id?: string, form?: UseFormReturn) {
+ let values = useWatch({ control: form?.control });
+ const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
+
+ useEffect(() => {
+ if (id && form?.formState.isDirty) {
+ values = form?.getValues();
+
+ const nextValues = {
+ ...values,
+ inputs: transferInputsArrayToObject(values.inputs),
+ };
+
+ updateNodeForm(id, nextValues);
+ }
+ }, [form?.formState.isDirty, id, updateNodeForm, values]);
+}
diff --git a/web/src/pages/agent/form/begin-form/utils.ts b/web/src/pages/agent/form/begin-form/utils.ts
new file mode 100644
index 00000000000..36038c4f6d2
--- /dev/null
+++ b/web/src/pages/agent/form/begin-form/utils.ts
@@ -0,0 +1,14 @@
+import { BeginQuery } from '../../interface';
+
+export function buildBeginInputListFromObject(
+ inputs: Record>,
+) {
+ return Object.entries(inputs || {}).reduce(
+ (pre, [key, value]) => {
+ pre.push({ ...(value || {}), key });
+
+ return pre;
+ },
+ [],
+ );
+}
diff --git a/web/src/pages/agent/form/categorize-form/dynamic-categorize.tsx b/web/src/pages/agent/form/categorize-form/dynamic-categorize.tsx
index 6302e032a22..7deb4f4ff1e 100644
--- a/web/src/pages/agent/form/categorize-form/dynamic-categorize.tsx
+++ b/web/src/pages/agent/form/categorize-form/dynamic-categorize.tsx
@@ -12,8 +12,7 @@ import {
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
-import { RAGFlowSelect } from '@/components/ui/select';
-import { Textarea } from '@/components/ui/textarea';
+import { BlurTextarea } from '@/components/ui/textarea';
import { useTranslate } from '@/hooks/common-hooks';
import { PlusOutlined } from '@ant-design/icons';
import { useUpdateNodeInternals } from '@xyflow/react';
@@ -23,6 +22,7 @@ import { ChevronsUpDown, X } from 'lucide-react';
import {
ChangeEventHandler,
FocusEventHandler,
+ memo,
useCallback,
useEffect,
useState,
@@ -30,6 +30,7 @@ import {
import { UseFormReturn, useFieldArray, useFormContext } from 'react-hook-form';
import { Operator } from '../../constant';
import { useBuildFormSelectOptions } from '../../form-hooks';
+import DynamicExample from './dynamic-example';
interface IProps {
nodeId?: string;
@@ -55,7 +56,7 @@ const getOtherFieldValues = (
x !== form.getValues(`${formListName}.${index}.${latestField}`),
);
-const NameInput = ({
+const InnerNameInput = ({
value,
onChange,
otherNames,
@@ -104,7 +105,9 @@ const NameInput = ({
);
};
-const FormSet = ({ nodeId, index }: IProps & { index: number }) => {
+const NameInput = memo(InnerNameInput);
+
+const InnerFormSet = ({ nodeId, index }: IProps & { index: number }) => {
const form = useFormContext();
const { t } = useTranslate('flow');
const buildCategorizeToOptions = useBuildFormSelectOptions(
@@ -152,61 +155,19 @@ const FormSet = ({ nodeId, index }: IProps & { index: number }) => {
{t('description')}
-