Compare commits

...

20 Commits

Author SHA1 Message Date
2d89863fdd Fix: search list permission (#9767)
### What problem does this PR solve?

Search list permission.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-27 18:50:02 +08:00
6cb3e08381 Revert: broken agent completion by #9631 (#9760)
### What problem does this PR solve?

Revert broken agent completion by #9631.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-27 17:16:55 +08:00
986b9cbb1a Docs: Update version references to v0.20.4 in READMEs and docs (#9758)
### What problem does this PR solve?

- Update version tags in README files (including translations) from
v0.20.3 to v0.20.4
- Modify Docker image references and documentation to reflect new
version
- Update version badges and image descriptions
- Maintain consistency across all language variants of README files

### Type of change

- [x] Documentation Update
2025-08-27 16:56:55 +08:00
9c456adffd Added v0.20.4 release notes (#9757)
### What problem does this PR solve?

### Type of change

- [x] Documentation Update
2025-08-27 15:29:09 +08:00
c15b138839 Create ecommerce_customer_service_workflow.json (#9755)
### What problem does this PR solve?

Update workflow template.

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2025-08-27 15:15:24 +08:00
ff11348f7c Fix: Optimize the MultiSelect component and system prompt templates #3221 (#9752)
### What problem does this PR solve?

Fix: Optimize the MultiSelect component and system prompt templates
#3221

- Modify the conditional statements in the MultiSelect component, using
the ?. operator to improve code readability
- Optimize the formatting of the system prompt template to make it more
standardized and easier to read
- Update the Chinese translation, changing "ExeSQL" to "Execute SQL"

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)

---------

Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
2025-08-27 15:12:12 +08:00
cbdabbb58f Fix: Fixed the issue that the agent embedded page needs to be logged in #9750 (#9751)
### What problem does this PR solve?

Fix: Fixed the issue that the agent embedded page needs to be logged in
#9750

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-27 14:18:00 +08:00
cf0011be67 Feat: Upgrade html parser (#9675)
### What problem does this PR solve?

parse more html content.

### Type of change

- [x] Other (please describe):
2025-08-27 12:43:55 +08:00
1f47001c82 Fix: Optimize tooltips and I118n #3221 (#9744)
### What problem does this PR solve?

Fix: Optimize tooltips and I118n #3221

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)

---------

Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
2025-08-27 11:46:51 +08:00
a914535344 Fix: add mode for embeded agent. (#9741)
### What problem does this PR solve?

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-27 11:46:15 +08:00
ba1063c2b9 Docs: Miscellaneous updates (#9729)
### What problem does this PR solve?


### Type of change

- [x] Documentation Update
2025-08-26 19:35:29 +08:00
2b4bca4447 Fix(i18n): Added new translations #3221 (#9727)
### What problem does this PR solve?

Fix(i18n): Added new translations #3221

- Added and updated internationalization translations in multiple
components


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)

---------

Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
2025-08-26 17:57:53 +08:00
11cf6ae313 Fix: After deleting the knowledge graph, jump to the dataset page #9722 (#9723)
### What problem does this PR solve?

Fix: After deleting the knowledge graph, jump to the dataset page #9722
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-26 17:57:41 +08:00
88db5d90d1 Fix: Try to fix the issue of not being able to log in through Oauth2 #9601 (#9717)
### What problem does this PR solve?

Fix: Try to fix the issue of not being able to log in through Oauth2
#9601

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-26 14:06:28 +08:00
209ef09dc3 Feat: add Zhipu GLM-4.5 model series (#9715)
### What problem does this PR solve?

Add Zhipu GLM-4.5 model series. #9708.

### Type of change

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

Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
2025-08-26 13:48:00 +08:00
ycz
370c8bc25b Update llm_factories.json (#9714)
### What problem does this PR solve?

add ZhipuAI GLM-4.5 model series

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-08-26 11:49:01 +08:00
e90a959b4d Fix: Chunk error when re-parsing created file #9665 (#9711)
### What problem does this PR solve?

Fix: Chunk error when re-parsing created file

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)

Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
2025-08-26 10:50:30 +08:00
ca320a8c30 Refactor: for total_token_count method use if to check first. (#9707)
### What problem does this PR solve?

for total_token_count method use if to check first, to improve the
performance when we need to handle exception cases

### Type of change

- [x] Refactoring
2025-08-26 10:47:20 +08:00
ae505e6165 Fix: Optimize table style #3221 (#9703)
### What problem does this PR solve?

Fix: Optimize table style
-Modify the style of the table scrollbar and remove unnecessary
scrollbars
-Adjust the header style of the table, add background color and
hierarchy
-Optimize the style of datasets and file tables
-Add a new background color variable

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-26 10:46:54 +08:00
63b5c2292d Fix: Delete the uploaded file in the chat input box, the corresponding file ID is not deleted #9701 (#9702)
### What problem does this PR solve?

Fix: Delete the uploaded file in the chat input box, the corresponding
file ID is not deleted #9701
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-26 09:27:49 +08:00
100 changed files with 2099 additions and 426 deletions

View File

@ -22,7 +22,7 @@
<img alt="Static Badge" src="https://img.shields.io/badge/Online-Demo-4e6b99">
</a>
<a href="https://hub.docker.com/r/infiniflow/ragflow" target="_blank">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.3">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.4">
</a>
<a href="https://github.com/infiniflow/ragflow/releases/latest">
<img src="https://img.shields.io/github/v/release/infiniflow/ragflow?color=blue&label=Latest%20Release" alt="Latest Release">
@ -190,7 +190,7 @@ releases! 🌟
> All Docker images are built for x86 platforms. We don't currently offer Docker images for ARM64.
> If you are on an ARM64 platform, follow [this guide](https://ragflow.io/docs/dev/build_docker_image) to build a Docker image compatible with your system.
> The command below downloads the `v0.20.3-slim` edition of the RAGFlow Docker image. See the following table for descriptions of different RAGFlow editions. To download a RAGFlow edition different from `v0.20.3-slim`, update the `RAGFLOW_IMAGE` variable accordingly in **docker/.env** before using `docker compose` to start the server. For example: set `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.3` for the full edition `v0.20.3`.
> The command below downloads the `v0.20.4-slim` edition of the RAGFlow Docker image. See the following table for descriptions of different RAGFlow editions. To download a RAGFlow edition different from `v0.20.4-slim`, update the `RAGFLOW_IMAGE` variable accordingly in **docker/.env** before using `docker compose` to start the server. For example: set `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.4` for the full edition `v0.20.4`.
```bash
$ cd ragflow/docker
@ -203,8 +203,8 @@ releases! 🌟
| RAGFlow image tag | Image size (GB) | Has embedding models? | Stable? |
|-------------------|-----------------|-----------------------|--------------------------|
| v0.20.3 | &approx;9 | :heavy_check_mark: | Stable release |
| v0.20.3-slim | &approx;2 | ❌ | Stable release |
| v0.20.4 | &approx;9 | :heavy_check_mark: | Stable release |
| v0.20.4-slim | &approx;2 | ❌ | Stable release |
| nightly | &approx;9 | :heavy_check_mark: | _Unstable_ nightly build |
| nightly-slim | &approx;2 | ❌ | _Unstable_ nightly build |

View File

@ -22,7 +22,7 @@
<img alt="Lencana Daring" src="https://img.shields.io/badge/Online-Demo-4e6b99">
</a>
<a href="https://hub.docker.com/r/infiniflow/ragflow" target="_blank">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.3">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.4">
</a>
<a href="https://github.com/infiniflow/ragflow/releases/latest">
<img src="https://img.shields.io/github/v/release/infiniflow/ragflow?color=blue&label=Rilis%20Terbaru" alt="Rilis Terbaru">
@ -181,7 +181,7 @@ Coba demo kami di [https://demo.ragflow.io](https://demo.ragflow.io).
> Semua gambar Docker dibangun untuk platform x86. Saat ini, kami tidak menawarkan gambar Docker untuk ARM64.
> Jika Anda menggunakan platform ARM64, [silakan gunakan panduan ini untuk membangun gambar Docker yang kompatibel dengan sistem Anda](https://ragflow.io/docs/dev/build_docker_image).
> Perintah di bawah ini mengunduh edisi v0.20.3-slim dari gambar Docker RAGFlow. Silakan merujuk ke tabel berikut untuk deskripsi berbagai edisi RAGFlow. Untuk mengunduh edisi RAGFlow yang berbeda dari v0.20.3-slim, perbarui variabel RAGFLOW_IMAGE di docker/.env sebelum menggunakan docker compose untuk memulai server. Misalnya, atur RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.3 untuk edisi lengkap v0.20.3.
> Perintah di bawah ini mengunduh edisi v0.20.4-slim dari gambar Docker RAGFlow. Silakan merujuk ke tabel berikut untuk deskripsi berbagai edisi RAGFlow. Untuk mengunduh edisi RAGFlow yang berbeda dari v0.20.4-slim, perbarui variabel RAGFLOW_IMAGE di docker/.env sebelum menggunakan docker compose untuk memulai server. Misalnya, atur RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.4 untuk edisi lengkap v0.20.4.
```bash
$ cd ragflow/docker
@ -194,8 +194,8 @@ $ docker compose -f docker-compose.yml up -d
| RAGFlow image tag | Image size (GB) | Has embedding models? | Stable? |
| ----------------- | --------------- | --------------------- | ------------------------ |
| v0.20.3 | &approx;9 | :heavy_check_mark: | Stable release |
| v0.20.3-slim | &approx;2 | ❌ | Stable release |
| v0.20.4 | &approx;9 | :heavy_check_mark: | Stable release |
| v0.20.4-slim | &approx;2 | ❌ | Stable release |
| nightly | &approx;9 | :heavy_check_mark: | _Unstable_ nightly build |
| nightly-slim | &approx;2 | ❌ | _Unstable_ nightly build |

View File

@ -22,7 +22,7 @@
<img alt="Static Badge" src="https://img.shields.io/badge/Online-Demo-4e6b99">
</a>
<a href="https://hub.docker.com/r/infiniflow/ragflow" target="_blank">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.3">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.4">
</a>
<a href="https://github.com/infiniflow/ragflow/releases/latest">
<img src="https://img.shields.io/github/v/release/infiniflow/ragflow?color=blue&label=Latest%20Release" alt="Latest Release">
@ -160,7 +160,7 @@
> 現在、公式に提供されているすべての Docker イメージは x86 アーキテクチャ向けにビルドされており、ARM64 用の Docker イメージは提供されていません。
> ARM64 アーキテクチャのオペレーティングシステムを使用している場合は、[このドキュメント](https://ragflow.io/docs/dev/build_docker_image)を参照して Docker イメージを自分でビルドしてください。
> 以下のコマンドは、RAGFlow Docker イメージの v0.20.3-slim エディションをダウンロードします。異なる RAGFlow エディションの説明については、以下の表を参照してください。v0.20.3-slim とは異なるエディションをダウンロードするには、docker/.env ファイルの RAGFLOW_IMAGE 変数を適宜更新し、docker compose を使用してサーバーを起動してください。例えば、完全版 v0.20.3 をダウンロードするには、RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.3 と設定します。
> 以下のコマンドは、RAGFlow Docker イメージの v0.20.4-slim エディションをダウンロードします。異なる RAGFlow エディションの説明については、以下の表を参照してください。v0.20.4-slim とは異なるエディションをダウンロードするには、docker/.env ファイルの RAGFLOW_IMAGE 変数を適宜更新し、docker compose を使用してサーバーを起動してください。例えば、完全版 v0.20.4 をダウンロードするには、RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.4 と設定します。
```bash
$ cd ragflow/docker
@ -173,8 +173,8 @@
| RAGFlow image tag | Image size (GB) | Has embedding models? | Stable? |
| ----------------- | --------------- | --------------------- | ------------------------ |
| v0.20.3 | &approx;9 | :heavy_check_mark: | Stable release |
| v0.20.3-slim | &approx;2 | ❌ | Stable release |
| v0.20.4 | &approx;9 | :heavy_check_mark: | Stable release |
| v0.20.4-slim | &approx;2 | ❌ | Stable release |
| nightly | &approx;9 | :heavy_check_mark: | _Unstable_ nightly build |
| nightly-slim | &approx;2 | ❌ | _Unstable_ nightly build |

View File

@ -22,7 +22,7 @@
<img alt="Static Badge" src="https://img.shields.io/badge/Online-Demo-4e6b99">
</a>
<a href="https://hub.docker.com/r/infiniflow/ragflow" target="_blank">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.3">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.4">
</a>
<a href="https://github.com/infiniflow/ragflow/releases/latest">
<img src="https://img.shields.io/github/v/release/infiniflow/ragflow?color=blue&label=Latest%20Release" alt="Latest Release">
@ -160,7 +160,7 @@
> 모든 Docker 이미지는 x86 플랫폼을 위해 빌드되었습니다. 우리는 현재 ARM64 플랫폼을 위한 Docker 이미지를 제공하지 않습니다.
> ARM64 플랫폼을 사용 중이라면, [시스템과 호환되는 Docker 이미지를 빌드하려면 이 가이드를 사용해 주세요](https://ragflow.io/docs/dev/build_docker_image).
> 아래 명령어는 RAGFlow Docker 이미지의 v0.20.3-slim 버전을 다운로드합니다. 다양한 RAGFlow 버전에 대한 설명은 다음 표를 참조하십시오. v0.20.3-slim과 다른 RAGFlow 버전을 다운로드하려면, docker/.env 파일에서 RAGFLOW_IMAGE 변수를 적절히 업데이트한 후 docker compose를 사용하여 서버를 시작하십시오. 예를 들어, 전체 버전인 v0.20.3을 다운로드하려면 RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.3로 설정합니다.
> 아래 명령어는 RAGFlow Docker 이미지의 v0.20.4-slim 버전을 다운로드합니다. 다양한 RAGFlow 버전에 대한 설명은 다음 표를 참조하십시오. v0.20.4-slim과 다른 RAGFlow 버전을 다운로드하려면, docker/.env 파일에서 RAGFLOW_IMAGE 변수를 적절히 업데이트한 후 docker compose를 사용하여 서버를 시작하십시오. 예를 들어, 전체 버전인 v0.20.4을 다운로드하려면 RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.4로 설정합니다.
```bash
$ cd ragflow/docker
@ -173,8 +173,8 @@
| RAGFlow image tag | Image size (GB) | Has embedding models? | Stable? |
| ----------------- | --------------- | --------------------- | ------------------------ |
| v0.20.3 | &approx;9 | :heavy_check_mark: | Stable release |
| v0.20.3-slim | &approx;2 | ❌ | Stable release |
| v0.20.4 | &approx;9 | :heavy_check_mark: | Stable release |
| v0.20.4-slim | &approx;2 | ❌ | Stable release |
| nightly | &approx;9 | :heavy_check_mark: | _Unstable_ nightly build |
| nightly-slim | &approx;2 | ❌ | _Unstable_ nightly build |

View File

@ -22,7 +22,7 @@
<img alt="Badge Estático" src="https://img.shields.io/badge/Online-Demo-4e6b99">
</a>
<a href="https://hub.docker.com/r/infiniflow/ragflow" target="_blank">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.3">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.4">
</a>
<a href="https://github.com/infiniflow/ragflow/releases/latest">
<img src="https://img.shields.io/github/v/release/infiniflow/ragflow?color=blue&label=Última%20Relese" alt="Última Versão">
@ -180,7 +180,7 @@ Experimente nossa demo em [https://demo.ragflow.io](https://demo.ragflow.io).
> Todas as imagens Docker são construídas para plataformas x86. Atualmente, não oferecemos imagens Docker para ARM64.
> Se você estiver usando uma plataforma ARM64, por favor, utilize [este guia](https://ragflow.io/docs/dev/build_docker_image) para construir uma imagem Docker compatível com o seu sistema.
> O comando abaixo baixa a edição `v0.20.3-slim` da imagem Docker do RAGFlow. Consulte a tabela a seguir para descrições de diferentes edições do RAGFlow. Para baixar uma edição do RAGFlow diferente da `v0.20.3-slim`, atualize a variável `RAGFLOW_IMAGE` conforme necessário no **docker/.env** antes de usar `docker compose` para iniciar o servidor. Por exemplo: defina `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.3` para a edição completa `v0.20.3`.
> O comando abaixo baixa a edição `v0.20.4-slim` da imagem Docker do RAGFlow. Consulte a tabela a seguir para descrições de diferentes edições do RAGFlow. Para baixar uma edição do RAGFlow diferente da `v0.20.4-slim`, atualize a variável `RAGFLOW_IMAGE` conforme necessário no **docker/.env** antes de usar `docker compose` para iniciar o servidor. Por exemplo: defina `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.4` para a edição completa `v0.20.4`.
```bash
$ cd ragflow/docker
@ -193,8 +193,8 @@ Experimente nossa demo em [https://demo.ragflow.io](https://demo.ragflow.io).
| Tag da imagem RAGFlow | Tamanho da imagem (GB) | Possui modelos de incorporação? | Estável? |
| --------------------- | ---------------------- | ------------------------------- | ------------------------ |
| v0.20.3 | ~9 | :heavy_check_mark: | Lançamento estável |
| v0.20.3-slim | ~2 | ❌ | Lançamento estável |
| v0.20.4 | ~9 | :heavy_check_mark: | Lançamento estável |
| v0.20.4-slim | ~2 | ❌ | Lançamento estável |
| nightly | ~9 | :heavy_check_mark: | _Instável_ build noturno |
| nightly-slim | ~2 | ❌ | _Instável_ build noturno |

View File

@ -22,7 +22,7 @@
<img alt="Static Badge" src="https://img.shields.io/badge/Online-Demo-4e6b99">
</a>
<a href="https://hub.docker.com/r/infiniflow/ragflow" target="_blank">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.3">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.4">
</a>
<a href="https://github.com/infiniflow/ragflow/releases/latest">
<img src="https://img.shields.io/github/v/release/infiniflow/ragflow?color=blue&label=Latest%20Release" alt="Latest Release">
@ -183,7 +183,7 @@
> 所有 Docker 映像檔都是為 x86 平台建置的。目前,我們不提供 ARM64 平台的 Docker 映像檔。
> 如果您使用的是 ARM64 平台,請使用 [這份指南](https://ragflow.io/docs/dev/build_docker_image) 來建置適合您系統的 Docker 映像檔。
> 執行以下指令會自動下載 RAGFlow slim Docker 映像 `v0.20.3-slim`。請參考下表查看不同 Docker 發行版的說明。如需下載不同於 `v0.20.3-slim` 的 Docker 映像,請在執行 `docker compose` 啟動服務之前先更新 **docker/.env** 檔案內的 `RAGFLOW_IMAGE` 變數。例如,你可以透過設定 `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.3` 來下載 RAGFlow 鏡像的 `v0.20.3` 完整發行版。
> 執行以下指令會自動下載 RAGFlow slim Docker 映像 `v0.20.4-slim`。請參考下表查看不同 Docker 發行版的說明。如需下載不同於 `v0.20.4-slim` 的 Docker 映像,請在執行 `docker compose` 啟動服務之前先更新 **docker/.env** 檔案內的 `RAGFLOW_IMAGE` 變數。例如,你可以透過設定 `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.4` 來下載 RAGFlow 鏡像的 `v0.20.4` 完整發行版。
```bash
$ cd ragflow/docker
@ -196,8 +196,8 @@
| RAGFlow image tag | Image size (GB) | Has embedding models? | Stable? |
| ----------------- | --------------- | --------------------- | ------------------------ |
| v0.20.3 | &approx;9 | :heavy_check_mark: | Stable release |
| v0.20.3-slim | &approx;2 | ❌ | Stable release |
| v0.20.4 | &approx;9 | :heavy_check_mark: | Stable release |
| v0.20.4-slim | &approx;2 | ❌ | Stable release |
| nightly | &approx;9 | :heavy_check_mark: | _Unstable_ nightly build |
| nightly-slim | &approx;2 | ❌ | _Unstable_ nightly build |

View File

@ -22,7 +22,7 @@
<img alt="Static Badge" src="https://img.shields.io/badge/Online-Demo-4e6b99">
</a>
<a href="https://hub.docker.com/r/infiniflow/ragflow" target="_blank">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.3">
<img src="https://img.shields.io/docker/pulls/infiniflow/ragflow?label=Docker%20Pulls&color=0db7ed&logo=docker&logoColor=white&style=flat-square" alt="docker pull infiniflow/ragflow:v0.20.4">
</a>
<a href="https://github.com/infiniflow/ragflow/releases/latest">
<img src="https://img.shields.io/github/v/release/infiniflow/ragflow?color=blue&label=Latest%20Release" alt="Latest Release">
@ -183,7 +183,7 @@
> 请注意,目前官方提供的所有 Docker 镜像均基于 x86 架构构建,并不提供基于 ARM64 的 Docker 镜像。
> 如果你的操作系统是 ARM64 架构,请参考[这篇文档](https://ragflow.io/docs/dev/build_docker_image)自行构建 Docker 镜像。
> 运行以下命令会自动下载 RAGFlow slim Docker 镜像 `v0.20.3-slim`。请参考下表查看不同 Docker 发行版的描述。如需下载不同于 `v0.20.3-slim` 的 Docker 镜像,请在运行 `docker compose` 启动服务之前先更新 **docker/.env** 文件内的 `RAGFLOW_IMAGE` 变量。比如,你可以通过设置 `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.3` 来下载 RAGFlow 镜像的 `v0.20.3` 完整发行版。
> 运行以下命令会自动下载 RAGFlow slim Docker 镜像 `v0.20.4-slim`。请参考下表查看不同 Docker 发行版的描述。如需下载不同于 `v0.20.4-slim` 的 Docker 镜像,请在运行 `docker compose` 启动服务之前先更新 **docker/.env** 文件内的 `RAGFLOW_IMAGE` 变量。比如,你可以通过设置 `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.4` 来下载 RAGFlow 镜像的 `v0.20.4` 完整发行版。
```bash
$ cd ragflow/docker
@ -196,8 +196,8 @@
| RAGFlow image tag | Image size (GB) | Has embedding models? | Stable? |
| ----------------- | --------------- | --------------------- | ------------------------ |
| v0.20.3 | &approx;9 | :heavy_check_mark: | Stable release |
| v0.20.3-slim | &approx;2 | ❌ | Stable release |
| v0.20.4 | &approx;9 | :heavy_check_mark: | Stable release |
| v0.20.4-slim | &approx;2 | ❌ | Stable release |
| nightly | &approx;9 | :heavy_check_mark: | _Unstable_ nightly build |
| nightly-slim | &approx;2 | ❌ | _Unstable_ nightly build |

View File

@ -469,6 +469,9 @@ class Canvas:
def get_prologue(self):
return self.components["begin"]["obj"]._param.prologue
def get_mode(self):
return self.components["begin"]["obj"]._param.mode
def set_global_param(self, **kwargs):
self.globals.update(kwargs)

File diff suppressed because one or more lines are too long

View File

@ -16,8 +16,10 @@
import json
import re
import time
import tiktoken
from flask import Response, jsonify, request
from agent.canvas import Canvas
from api import settings
from api.db import LLMType, StatusEnum
@ -27,7 +29,8 @@ from api.db.services.canvas_service import UserCanvasService, completionOpenAI
from api.db.services.canvas_service import completion as agent_completion
from api.db.services.conversation_service import ConversationService, iframe_completion
from api.db.services.conversation_service import completion as rag_completion
from api.db.services.dialog_service import DialogService, ask, chat, gen_mindmap
from api.db.services.dialog_service import DialogService, ask, chat, gen_mindmap, meta_filter
from api.db.services.document_service import DocumentService
from api.db.services.knowledgebase_service import KnowledgebaseService
from api.db.services.llm_service import LLMBundle
from api.db.services.search_service import SearchService
@ -37,7 +40,7 @@ from api.utils.api_utils import check_duplicate_ids, get_data_openai, get_error_
from rag.app.tag import label_question
from rag.prompts import chunks_format
from rag.prompts.prompt_template import load_prompt
from rag.prompts.prompts import cross_languages, keyword_extraction
from rag.prompts.prompts import cross_languages, gen_meta_filter, keyword_extraction
@manager.route("/chats/<chat_id>/sessions", methods=["POST"]) # noqa: F821
@ -81,10 +84,10 @@ def create_agent_session(tenant_id, agent_id):
if not isinstance(cvs.dsl, str):
cvs.dsl = json.dumps(cvs.dsl, ensure_ascii=False)
session_id=get_uuid()
session_id = get_uuid()
canvas = Canvas(cvs.dsl, tenant_id, agent_id)
canvas.reset()
cvs.dsl = json.loads(str(canvas))
conv = {"id": session_id, "dialog_id": cvs.id, "user_id": user_id, "message": [{"role": "assistant", "content": canvas.get_prologue()}], "source": "agent", "dsl": cvs.dsl}
API4ConversationService.save(**conv)
@ -442,26 +445,46 @@ def agents_completion_openai_compatibility(tenant_id, agent_id):
def agent_completions(tenant_id, agent_id):
req = request.json
ans = {}
if req.get("stream", True):
def generate():
for answer in agent_completion(tenant_id=tenant_id, agent_id=agent_id, **req):
if isinstance(answer, str):
try:
ans = json.loads(answer[5:]) # remove "data:"
except Exception:
continue
if ans.get("event") != "message" or not ans.get("data", {}).get("reference", None):
continue
yield answer
yield "data:[DONE]\n\n"
if req.get("stream", True):
resp = Response(agent_completion(tenant_id=tenant_id, agent_id=agent_id, **req), mimetype="text/event-stream")
resp = Response(generate(), mimetype="text/event-stream")
resp.headers.add_header("Cache-control", "no-cache")
resp.headers.add_header("Connection", "keep-alive")
resp.headers.add_header("X-Accel-Buffering", "no")
resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8")
return resp
result = {}
full_content = ""
for answer in agent_completion(tenant_id=tenant_id, agent_id=agent_id, **req):
try:
ans = json.loads(answer[5:]) # remove "data:"
if not result:
result = ans.copy()
else:
result["data"]["answer"] += ans["data"]["answer"]
result["data"]["reference"] = ans["data"].get("reference", [])
ans = json.loads(answer[5:])
if ans["event"] == "message":
full_content += ans["data"]["content"]
if ans.get("data", {}).get("reference", None):
ans["data"]["content"] = full_content
return get_result(data=ans)
except Exception as e:
return get_error_data_result(str(e))
return result
return get_result(data=f"**ERROR**: {str(e)}")
return get_result(data=ans)
@manager.route("/chats/<chat_id>/sessions", methods=["GET"]) # noqa: F821
@ -556,10 +579,7 @@ def list_agent_session(tenant_id, agent_id):
if message_num != 0 and messages[message_num]["role"] != "user":
chunk_list = []
# Add boundary and type checks to prevent KeyError
if (chunk_num < len(conv["reference"]) and
conv["reference"][chunk_num] is not None and
isinstance(conv["reference"][chunk_num], dict) and
"chunks" in conv["reference"][chunk_num]):
if chunk_num < len(conv["reference"]) and conv["reference"][chunk_num] is not None and isinstance(conv["reference"][chunk_num], dict) and "chunks" in conv["reference"][chunk_num]:
chunks = conv["reference"][chunk_num]["chunks"]
for chunk in chunks:
# Ensure chunk is a dictionary before calling get method
@ -860,14 +880,7 @@ def begin_inputs(agent_id):
return get_error_data_result(f"Can't find agent by ID: {agent_id}")
canvas = Canvas(json.dumps(cvs.dsl), objs[0].tenant_id)
return get_result(
data={
"title": cvs.title,
"avatar": cvs.avatar,
"inputs": canvas.get_component_input_form("begin"),
"prologue": canvas.get_prologue()
}
)
return get_result(data={"title": cvs.title, "avatar": cvs.avatar, "inputs": canvas.get_component_input_form("begin"), "prologue": canvas.get_prologue(), "mode": canvas.get_mode()})
@manager.route("/searchbots/ask", methods=["POST"]) # noqa: F821
@ -907,7 +920,7 @@ def ask_about_embedded():
return resp
@manager.route("/searchbots/retrieval_test", methods=['POST']) # noqa: F821
@manager.route("/searchbots/retrieval_test", methods=["POST"]) # noqa: F821
@validate_request("kb_id", "question")
def retrieval_test_embedded():
token = request.headers.get("Authorization").split()
@ -937,18 +950,30 @@ def retrieval_test_embedded():
if not tenant_id:
return get_error_data_result(message="permission denined.")
if req.get("search_id", ""):
search_config = SearchService.get_detail(req.get("search_id", "")).get("search_config", {})
meta_data_filter = search_config.get("meta_data_filter", {})
metas = DocumentService.get_meta_by_kbs(kb_ids)
if meta_data_filter.get("method") == "auto":
chat_mdl = LLMBundle(tenant_id, LLMType.CHAT, llm_name=search_config.get("chat_id", ""))
filters = gen_meta_filter(chat_mdl, metas, question)
doc_ids.extend(meta_filter(metas, filters))
if not doc_ids:
doc_ids = None
elif meta_data_filter.get("method") == "manual":
doc_ids.extend(meta_filter(metas, meta_data_filter["manual"]))
if not doc_ids:
doc_ids = None
try:
tenants = UserTenantService.query(user_id=tenant_id)
for kb_id in kb_ids:
for tenant in tenants:
if KnowledgebaseService.query(
tenant_id=tenant.tenant_id, id=kb_id):
if KnowledgebaseService.query(tenant_id=tenant.tenant_id, id=kb_id):
tenant_ids.append(tenant.tenant_id)
break
else:
return get_json_result(
data=False, message='Only owner of knowledgebase authorized for this operation.',
code=settings.RetCode.OPERATING_ERROR)
return get_json_result(data=False, message="Only owner of knowledgebase authorized for this operation.", code=settings.RetCode.OPERATING_ERROR)
e, kb = KnowledgebaseService.get_by_id(kb_ids[0])
if not e:
@ -968,17 +993,11 @@ def retrieval_test_embedded():
question += keyword_extraction(chat_mdl, question)
labels = label_question(question, [kb])
ranks = settings.retrievaler.retrieval(question, embd_mdl, tenant_ids, kb_ids, page, size,
similarity_threshold, vector_similarity_weight, top,
doc_ids, rerank_mdl=rerank_mdl, highlight=req.get("highlight"),
rank_feature=labels
)
ranks = settings.retrievaler.retrieval(
question, embd_mdl, tenant_ids, kb_ids, page, size, similarity_threshold, vector_similarity_weight, top, doc_ids, rerank_mdl=rerank_mdl, highlight=req.get("highlight"), rank_feature=labels
)
if use_kg:
ck = settings.kg_retrievaler.retrieval(question,
tenant_ids,
kb_ids,
embd_mdl,
LLMBundle(kb.tenant_id, LLMType.CHAT))
ck = settings.kg_retrievaler.retrieval(question, tenant_ids, kb_ids, embd_mdl, LLMBundle(kb.tenant_id, LLMType.CHAT))
if ck["content_with_weight"]:
ranks["chunks"].insert(0, ck)
@ -989,8 +1008,7 @@ def retrieval_test_embedded():
return get_json_result(data=ranks)
except Exception as e:
if str(e).find("not_found") > 0:
return get_json_result(data=False, message='No chunk found! Check the chunk status please!',
code=settings.RetCode.DATA_ERROR)
return get_json_result(data=False, message="No chunk found! Check the chunk status please!", code=settings.RetCode.DATA_ERROR)
return server_error_response(e)

View File

@ -155,8 +155,9 @@ def list_search_app():
owner_ids = req.get("owner_ids", [])
try:
if not owner_ids:
tenants = TenantService.get_joined_tenants_by_user_id(current_user.id)
tenants = [m["tenant_id"] for m in tenants]
# tenants = TenantService.get_joined_tenants_by_user_id(current_user.id)
# tenants = [m["tenant_id"] for m in tenants]
tenants = []
search_apps, total = SearchService.get_by_tenant_ids(tenants, current_user.id, page_number, items_per_page, orderby, desc, keywords)
else:
tenants = owner_ids

View File

@ -135,24 +135,6 @@ class UserCanvasService(CommonService):
return True
def structure_answer(conv, ans, message_id, session_id):
if not conv:
return ans
content = ""
if ans["event"] == "message":
if ans["data"].get("start_to_think") is True:
content = "<think>"
elif ans["data"].get("end_to_think") is True:
content = "</think>"
else:
content = ans["data"]["content"]
reference = ans["data"].get("reference")
result = {"id": message_id, "session_id": session_id, "answer": content}
if reference:
result["reference"] = [reference]
return result
def completion(tenant_id, agent_id, session_id=None, **kwargs):
query = kwargs.get("query", "") or kwargs.get("question", "")
files = kwargs.get("files", [])
@ -196,14 +178,13 @@ def completion(tenant_id, agent_id, session_id=None, **kwargs):
})
txt = ""
for ans in canvas.run(query=query, files=files, user_id=user_id, inputs=inputs):
ans = structure_answer(conv, ans, message_id, session_id)
txt += ans["answer"]
if ans.get("answer") or ans.get("reference"):
yield "data:" + json.dumps({"code": 0, "data": ans},
ensure_ascii=False) + "\n\n"
ans["session_id"] = session_id
if ans["event"] == "message":
txt += ans["data"]["content"]
yield "data:" + json.dumps(ans, ensure_ascii=False) + "\n\n"
conv.message.append({"role": "assistant", "content": txt, "created_at": time.time(), "id": message_id})
conv.reference.append(canvas.get_reference())
conv.reference = canvas.get_reference()
conv.errors = canvas.error
conv.dsl = str(canvas)
conv = conv.to_dict()
@ -232,9 +213,9 @@ def completionOpenAI(tenant_id, agent_id, question, session_id=None, stream=True
except Exception as e:
logging.exception(f"Agent OpenAI-Compatible completionOpenAI parse answer failed: {e}")
continue
if not ans["data"]["answer"]:
if ans.get("event") != "message" or not ans.get("data", {}).get("reference", None):
continue
content_piece = ans["data"]["answer"]
content_piece = ans["data"]["content"]
completion_tokens += len(tiktokenenc.encode(content_piece))
yield "data: " + json.dumps(
@ -279,9 +260,9 @@ def completionOpenAI(tenant_id, agent_id, question, session_id=None, stream=True
):
if isinstance(ans, str):
ans = json.loads(ans[5:])
if not ans["data"]["answer"]:
if ans.get("event") != "message" or not ans.get("data", {}).get("reference", None):
continue
all_content += ans["data"]["answer"]
all_content += ans["data"]["content"]
completion_tokens = len(tiktokenenc.encode(all_content))

View File

@ -354,7 +354,7 @@ def get_parser_config(chunk_method, parser_config):
if not chunk_method:
chunk_method = "naive"
# Define default configurations for each chunk method
# Define default configurations for each chunking method
key_mapping = {
"naive": {"chunk_token_num": 512, "delimiter": r"\n", "html4excel": False, "layout_recognize": "DeepDOC", "raptor": {"use_raptor": False}, "graphrag": {"use_graphrag": False}},
"qa": {"raptor": {"use_raptor": False}, "graphrag": {"use_graphrag": False}},

View File

@ -532,23 +532,65 @@
"tags": "LLM,TEXT EMBEDDING,SPEECH2TEXT,MODERATION",
"status": "1",
"llm": [
{
"llm_name": "glm-4.5",
"tags": "LLM,CHAT,128K",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "glm-4.5-x",
"tags": "LLM,CHAT,128k",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "glm-4.5-air",
"tags": "LLM,CHAT,128K",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "glm-4.5-airx",
"tags": "LLM,CHAT,128k",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "glm-4.5-flash",
"tags": "LLM,CHAT,128k",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "glm-4.5v",
"tags": "LLM,IMAGE2TEXT,64,",
"max_tokens": 64000,
"model_type": "image2text",
"is_tools": false
},
{
"llm_name": "glm-4-plus",
"tags": "LLM,CHAT,",
"tags": "LLM,CHAT,128K",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "glm-4-0520",
"tags": "LLM,CHAT,",
"tags": "LLM,CHAT,128K",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "glm-4",
"tags": "LLM,CHAT,",
"tags":"LLM,CHAT,128K",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true

View File

@ -15,35 +15,200 @@
# limitations under the License.
#
from rag.nlp import find_codec
import readability
import html_text
from rag.nlp import find_codec, rag_tokenizer
import uuid
import chardet
from bs4 import BeautifulSoup, NavigableString, Tag, Comment
import html
def get_encoding(file):
with open(file,'rb') as f:
tmp = chardet.detect(f.read())
return tmp['encoding']
BLOCK_TAGS = [
"h1", "h2", "h3", "h4", "h5", "h6",
"p", "div", "article", "section", "aside",
"ul", "ol", "li",
"table", "pre", "code", "blockquote",
"figure", "figcaption"
]
TITLE_TAGS = {"h1": "#", "h2": "##", "h3": "###", "h4": "#####", "h5": "#####", "h6": "######"}
class RAGFlowHtmlParser:
def __call__(self, fnm, binary=None):
def __call__(self, fnm, binary=None, chunk_token_num=None):
if binary:
encoding = find_codec(binary)
txt = binary.decode(encoding, errors="ignore")
else:
with open(fnm, "r",encoding=get_encoding(fnm)) as f:
txt = f.read()
return self.parser_txt(txt)
return self.parser_txt(txt, chunk_token_num)
@classmethod
def parser_txt(cls, txt):
def parser_txt(cls, txt, chunk_token_num):
if not isinstance(txt, str):
raise TypeError("txt type should be string!")
html_doc = readability.Document(txt)
title = html_doc.title()
content = html_text.extract_text(html_doc.summary(html_partial=True))
txt = f"{title}\n{content}"
sections = txt.split("\n")
temp_sections = []
soup = BeautifulSoup(txt, "html5lib")
# delete <style> tag
for style_tag in soup.find_all(["style", "script"]):
style_tag.decompose()
# delete <script> tag in <div>
for div_tag in soup.find_all("div"):
for script_tag in div_tag.find_all("script"):
script_tag.decompose()
# delete inline style
for tag in soup.find_all(True):
if 'style' in tag.attrs:
del tag.attrs['style']
# delete HTML comment
for comment in soup.find_all(string=lambda text: isinstance(text, Comment)):
comment.extract()
cls.read_text_recursively(soup.body, temp_sections, chunk_token_num=chunk_token_num)
block_txt_list, table_list = cls.merge_block_text(temp_sections)
sections = cls.chunk_block(block_txt_list, chunk_token_num=chunk_token_num)
for table in table_list:
sections.append(table.get("content", ""))
return sections
@classmethod
def split_table(cls, html_table, chunk_token_num=512):
soup = BeautifulSoup(html_table, "html.parser")
rows = soup.find_all("tr")
tables = []
current_table = []
current_count = 0
table_str_list = []
for row in rows:
tks_str = rag_tokenizer.tokenize(str(row))
token_count = len(tks_str.split(" ")) if tks_str else 0
if current_count + token_count > chunk_token_num:
tables.append(current_table)
current_table = []
current_count = 0
current_table.append(row)
current_count += token_count
if current_table:
tables.append(current_table)
for table_rows in tables:
new_table = soup.new_tag("table")
for row in table_rows:
new_table.append(row)
table_str_list.append(str(new_table))
return table_str_list
@classmethod
def read_text_recursively(cls, element, parser_result, chunk_token_num=512, parent_name=None, block_id=None):
if isinstance(element, NavigableString):
content = element.strip()
def is_valid_html(content):
try:
soup = BeautifulSoup(content, "html.parser")
return bool(soup.find())
except Exception:
return False
return_info = []
if content:
if is_valid_html(content):
soup = BeautifulSoup(content, "html.parser")
child_info = cls.read_text_recursively(soup, parser_result, chunk_token_num, element.name, block_id)
parser_result.extend(child_info)
else:
info = {"content": element.strip(), "tag_name": "inner_text", "metadata": {"block_id": block_id}}
if parent_name:
info["tag_name"] = parent_name
return_info.append(info)
return return_info
elif isinstance(element, Tag):
if str.lower(element.name) == "table":
table_info_list = []
table_id = str(uuid.uuid1())
table_list = [html.unescape(str(element))]
for t in table_list:
table_info_list.append({"content": t, "tag_name": "table",
"metadata": {"table_id": table_id, "index": table_list.index(t)}})
return table_info_list
else:
block_id = None
if str.lower(element.name) in BLOCK_TAGS:
block_id = str(uuid.uuid1())
for child in element.children:
child_info = cls.read_text_recursively(child, parser_result, chunk_token_num, element.name,
block_id)
parser_result.extend(child_info)
return []
@classmethod
def merge_block_text(cls, parser_result):
block_content = []
current_content = ""
table_info_list = []
lask_block_id = None
for item in parser_result:
content = item.get("content")
tag_name = item.get("tag_name")
title_flag = tag_name in TITLE_TAGS
block_id = item.get("metadata", {}).get("block_id")
if block_id:
if title_flag:
content = f"{TITLE_TAGS[tag_name]} {content}"
if lask_block_id != block_id:
if lask_block_id is not None:
block_content.append(current_content)
current_content = content
lask_block_id = block_id
else:
current_content += (" " if current_content else "") + content
else:
if tag_name == "table":
table_info_list.append(item)
else:
current_content += (" " if current_content else "" + content)
if current_content:
block_content.append(current_content)
return block_content, table_info_list
@classmethod
def chunk_block(cls, block_txt_list, chunk_token_num=512):
chunks = []
current_block = ""
current_token_count = 0
for block in block_txt_list:
tks_str = rag_tokenizer.tokenize(block)
block_token_count = len(tks_str.split(" ")) if tks_str else 0
if block_token_count > chunk_token_num:
if current_block:
chunks.append(current_block)
start = 0
tokens = tks_str.split(" ")
while start < len(tokens):
end = start + chunk_token_num
split_tokens = tokens[start:end]
chunks.append(" ".join(split_tokens))
start = end
current_block = ""
current_token_count = 0
else:
if current_token_count + block_token_count <= chunk_token_num:
current_block += ("\n" if current_block else "") + block
current_token_count += block_token_count
else:
chunks.append(current_block)
current_block = block
current_token_count = block_token_count
if current_block:
chunks.append(current_block)
return chunks

View File

@ -93,13 +93,13 @@ REDIS_PASSWORD=infini_rag_flow
SVR_HTTP_PORT=9380
# The RAGFlow Docker image to download.
# Defaults to the v0.20.1-slim edition, which is the RAGFlow Docker image without embedding models.
RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.3-slim
# Defaults to the v0.20.4-slim edition, which is the RAGFlow Docker image without embedding models.
RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.4-slim
#
# To download the RAGFlow Docker image with embedding models, uncomment the following line instead:
# RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.1
# RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.4
#
# The Docker image of the v0.20.1 edition includes built-in embedding models:
# The Docker image of the v0.20.4 edition includes built-in embedding models:
# - BAAI/bge-large-zh-v1.5
# - maidalun1020/bce-embedding-base_v1
#

View File

@ -79,8 +79,8 @@ The [.env](./.env) file contains important environment variables for Docker.
- `RAGFLOW-IMAGE`
The Docker image edition. Available editions:
- `infiniflow/ragflow:v0.20.3-slim` (default): The RAGFlow Docker image without embedding models.
- `infiniflow/ragflow:v0.20.3`: The RAGFlow Docker image with embedding models including:
- `infiniflow/ragflow:v0.20.4-slim` (default): The RAGFlow Docker image without embedding models.
- `infiniflow/ragflow:v0.20.4`: The RAGFlow Docker image with embedding models including:
- Built-in embedding models:
- `BAAI/bge-large-zh-v1.5`
- `maidalun1020/bce-embedding-base_v1`

View File

@ -99,8 +99,8 @@ RAGFlow utilizes MinIO as its object storage solution, leveraging its scalabilit
- `RAGFLOW-IMAGE`
The Docker image edition. Available editions:
- `infiniflow/ragflow:v0.20.3-slim` (default): The RAGFlow Docker image without embedding models.
- `infiniflow/ragflow:v0.20.3`: The RAGFlow Docker image with embedding models including:
- `infiniflow/ragflow:v0.20.4-slim` (default): The RAGFlow Docker image without embedding models.
- `infiniflow/ragflow:v0.20.4`: The RAGFlow Docker image with embedding models including:
- Built-in embedding models:
- `BAAI/bge-large-zh-v1.5`
- `maidalun1020/bce-embedding-base_v1`

View File

@ -11,7 +11,7 @@ An API key is required for the RAGFlow server to authenticate your HTTP/Python o
2. Click **API** to switch to the **API** page.
3. Obtain a RAGFlow API key:
![ragflow_api_key](https://github.com/user-attachments/assets/f461ed61-04c6-4faf-b3d8-6b5fa56be4e7)
![ragflow_api_key](https://raw.githubusercontent.com/infiniflow/ragflow-docs/main/images/ragflow_api_key.jpg)
:::tip NOTE
See the [RAGFlow HTTP API reference](../references/http_api_reference.md) or the [RAGFlow Python API reference](../references/python_api_reference.md) for a complete reference of RAGFlow's HTTP or Python APIs.

View File

@ -77,7 +77,7 @@ After building the infiniflow/ragflow:nightly-slim image, you are ready to launc
1. Edit Docker Compose Configuration
Open the `docker/.env` file. Find the `RAGFLOW_IMAGE` setting and change the image reference from `infiniflow/ragflow:v0.20.3-slim` to `infiniflow/ragflow:nightly-slim` to use the pre-built image.
Open the `docker/.env` file. Find the `RAGFLOW_IMAGE` setting and change the image reference from `infiniflow/ragflow:v0.20.4-slim` to `infiniflow/ragflow:nightly-slim` to use the pre-built image.
2. Launch the Service

View File

@ -30,17 +30,17 @@ The "garbage in garbage out" status quo remains unchanged despite the fact that
Each RAGFlow release is available in two editions:
- **Slim edition**: excludes built-in embedding models and is identified by a **-slim** suffix added to the version name. Example: `infiniflow/ragflow:v0.20.3-slim`
- **Full edition**: includes built-in embedding models and has no suffix added to the version name. Example: `infiniflow/ragflow:v0.20.3`
- **Slim edition**: excludes built-in embedding models and is identified by a **-slim** suffix added to the version name. Example: `infiniflow/ragflow:v0.20.4-slim`
- **Full edition**: includes built-in embedding models and has no suffix added to the version name. Example: `infiniflow/ragflow:v0.20.4`
---
### Which embedding models can be deployed locally?
RAGFlow offers two Docker image editions, `v0.20.3-slim` and `v0.20.3`:
RAGFlow offers two Docker image editions, `v0.20.4-slim` and `v0.20.4`:
- `infiniflow/ragflow:v0.20.3-slim` (default): The RAGFlow Docker image without embedding models.
- `infiniflow/ragflow:v0.20.3`: The RAGFlow Docker image with embedding models including:
- `infiniflow/ragflow:v0.20.4-slim` (default): The RAGFlow Docker image without embedding models.
- `infiniflow/ragflow:v0.20.4`: The RAGFlow Docker image with embedding models including:
- Built-in embedding models:
- `BAAI/bge-large-zh-v1.5`
- `maidalun1020/bce-embedding-base_v1`

View File

@ -9,7 +9,7 @@ The component equipped with reasoning, tool usage, and multi-agent collaboration
---
An **Agent** component fine-tunes the LLM and sets its prompt. From v0.20.3 onwards, an **Agent** component is able to work independently and with the following capabilities:
An **Agent** component fine-tunes the LLM and sets its prompt. From v0.20.4 onwards, an **Agent** component is able to work independently and with the following capabilities:
- Autonomous reasoning with reflection and adjustment based on environmental feedback.
- Use of tools or subagents to complete tasks.

View File

@ -9,7 +9,7 @@ A component that retrieves information from specified datasets.
## Scenarios
A **Retrieval** component is essential in most RAG scenarios, where information is extracted from designated knowledge bases before being sent to the LLM for content generation. As of v0.20.3, a **Retrieval** component can operate either as a workflow component or as a tool of an **Agent**, enabling the Agent to control its invocation and search queries.
A **Retrieval** component is essential in most RAG scenarios, where information is extracted from designated knowledge bases before being sent to the LLM for content generation. As of v0.20.4, a **Retrieval** component can operate either as a workflow component or as a tool of an **Agent**, enabling the Agent to control its invocation and search queries.
## Configurations

View File

@ -48,7 +48,7 @@ You start an AI conversation by creating an assistant.
- If no target language is selected, the system will search only in the language of your query, which may cause relevant information in other languages to be missed.
- **Variable** refers to the variables (keys) to be used in the system prompt. `{knowledge}` is a reserved variable. Click **Add** to add more variables for the system prompt.
- If you are uncertain about the logic behind **Variable**, leave it *as-is*.
- As of v0.20.3, if you add custom variables here, the only way you can pass in their values is to call:
- As of v0.20.4, if you add custom variables here, the only way you can pass in their values is to call:
- HTTP method [Converse with chat assistant](../../references/http_api_reference.md#converse-with-chat-assistant), or
- Python method [Converse with chat assistant](../../references/python_api_reference.md#converse-with-chat-assistant).

View File

@ -128,7 +128,7 @@ See [Run retrieval test](./run_retrieval_test.md) for details.
## Search for knowledge base
As of RAGFlow v0.20.3, the search feature is still in a rudimentary form, supporting only knowledge base search by name.
As of RAGFlow v0.20.4, the search feature is still in a rudimentary form, supporting only knowledge base search by name.
![search knowledge base](https://github.com/infiniflow/ragflow/assets/93570324/836ae94c-2438-42be-879e-c7ad2a59693e)

View File

@ -87,4 +87,4 @@ RAGFlow's file management allows you to download an uploaded file:
![download_file](https://github.com/infiniflow/ragflow/assets/93570324/cf3b297f-7d9b-4522-bf5f-4f45743e4ed5)
> As of RAGFlow v0.20.3, bulk download is not supported, nor can you download an entire folder.
> As of RAGFlow v0.20.4, bulk download is not supported, nor can you download an entire folder.

View File

@ -18,7 +18,7 @@ RAGFlow ships with a built-in [Langfuse](https://langfuse.com) integration so th
Langfuse stores traces, spans and prompt payloads in a purpose-built observability backend and offers filtering and visualisations on top.
:::info NOTE
• RAGFlow **≥ 0.20.3** (contains the Langfuse connector)
• RAGFlow **≥ 0.20.4** (contains the Langfuse connector)
• A Langfuse workspace (cloud or self-hosted) with a _Project Public Key_ and _Secret Key_
:::

View File

@ -66,10 +66,10 @@ To upgrade RAGFlow, you must upgrade **both** your code **and** your Docker imag
git clone https://github.com/infiniflow/ragflow.git
```
2. Switch to the latest, officially published release, e.g., `v0.20.3`:
2. Switch to the latest, officially published release, e.g., `v0.20.4`:
```bash
git checkout -f v0.20.3
git checkout -f v0.20.4
```
3. Update **ragflow/docker/.env**:
@ -83,14 +83,14 @@ To upgrade RAGFlow, you must upgrade **both** your code **and** your Docker imag
<TabItem value="slim">
```bash
RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.3-slim
RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.4-slim
```
</TabItem>
<TabItem value="full">
```bash
RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.3
RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.4
```
</TabItem>
@ -114,10 +114,10 @@ No, you do not need to. Upgrading RAGFlow in itself will *not* remove your uploa
1. From an environment with Internet access, pull the required Docker image.
2. Save the Docker image to a **.tar** file.
```bash
docker save -o ragflow.v0.20.3.tar infiniflow/ragflow:v0.20.3
docker save -o ragflow.v0.20.4.tar infiniflow/ragflow:v0.20.4
```
3. Copy the **.tar** file to the target server.
4. Load the **.tar** file into Docker:
```bash
docker load -i ragflow.v0.20.3.tar
docker load -i ragflow.v0.20.4.tar
```

View File

@ -44,7 +44,7 @@ This section provides instructions on setting up the RAGFlow server on Linux. If
`vm.max_map_count`. This value sets the maximum number of memory map areas a process may have. Its default value is 65530. While most applications require fewer than a thousand maps, reducing this value can result in abnormal behaviors, and the system will throw out-of-memory errors when a process reaches the limitation.
RAGFlow v0.20.3 uses Elasticsearch or [Infinity](https://github.com/infiniflow/infinity) for multiple recall. Setting the value of `vm.max_map_count` correctly is crucial to the proper functioning of the Elasticsearch component.
RAGFlow v0.20.4 uses Elasticsearch or [Infinity](https://github.com/infiniflow/infinity) for multiple recall. Setting the value of `vm.max_map_count` correctly is crucial to the proper functioning of the Elasticsearch component.
<Tabs
defaultValue="linux"
@ -184,13 +184,13 @@ This section provides instructions on setting up the RAGFlow server on Linux. If
```bash
$ git clone https://github.com/infiniflow/ragflow.git
$ cd ragflow/docker
$ git checkout -f v0.20.3
$ git checkout -f v0.20.4
```
3. Use the pre-built Docker images and start up the server:
:::tip NOTE
The command below downloads the `v0.20.3-slim` edition of the RAGFlow Docker image. Refer to the following table for descriptions of different RAGFlow editions. To download a RAGFlow edition different from `v0.20.3-slim`, update the `RAGFLOW_IMAGE` variable accordingly in **docker/.env** before using `docker compose` to start the server. For example: set `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.3` for the full edition `v0.20.3`.
The command below downloads the `v0.20.4-slim` edition of the RAGFlow Docker image. Refer to the following table for descriptions of different RAGFlow editions. To download a RAGFlow edition different from `v0.20.4-slim`, update the `RAGFLOW_IMAGE` variable accordingly in **docker/.env** before using `docker compose` to start the server. For example: set `RAGFLOW_IMAGE=infiniflow/ragflow:v0.20.4` for the full edition `v0.20.4`.
:::
```bash
@ -207,8 +207,8 @@ This section provides instructions on setting up the RAGFlow server on Linux. If
| RAGFlow image tag | Image size (GB) | Has embedding models and Python packages? | Stable? |
| ------------------- | --------------- | ----------------------------------------- | ------------------------ |
| `v0.20.3` | &approx;9 | :heavy_check_mark: | Stable release |
| `v0.20.3-slim` | &approx;2 | ❌ | Stable release |
| `v0.20.4` | &approx;9 | :heavy_check_mark: | Stable release |
| `v0.20.4-slim` | &approx;2 | ❌ | Stable release |
| `nightly` | &approx;9 | :heavy_check_mark: | *Unstable* nightly build |
| `nightly-slim` | &approx;2 | ❌ | *Unstable* nightly build |
@ -217,7 +217,7 @@ This section provides instructions on setting up the RAGFlow server on Linux. If
```
:::danger IMPORTANT
The embedding models included in `v0.20.3` and `nightly` are:
The embedding models included in `v0.20.4` and `nightly` are:
- BAAI/bge-large-zh-v1.5
- maidalun1020/bce-embedding-base_v1

View File

@ -19,7 +19,7 @@ import TOCInline from '@theme/TOCInline';
### Cross-language search
Cross-language search (also known as cross-lingual retrieval) is a feature introduced in version 0.20.3. It enables users to submit queries in one language (for example, English) and retrieve relevant documents written in other languages such as Chinese or Spanish. This feature is enabled by the systems default chat model, which translates queries to ensure accurate matching of semantic meaning across languages.
Cross-language search (also known as cross-lingual retrieval) is a feature introduced in version 0.20.4. It enables users to submit queries in one language (for example, English) and retrieve relevant documents written in other languages such as Chinese or Spanish. This feature is enabled by the systems default chat model, which translates queries to ensure accurate matching of semantic meaning across languages.
By enabling cross-language search, users can effortlessly access a broader range of information regardless of language barriers, significantly enhancing the systems usability and inclusiveness.

View File

@ -5,7 +5,7 @@ slug: /http_api_reference
# HTTP API
A complete reference for RAGFlow's RESTful API. Before proceeding, please ensure you [have your RAGFlow API key ready for authentication](../develop/acquire_ragflow_api_key.md).
A complete reference for RAGFlow's RESTful API. Before proceeding, please ensure you [have your RAGFlow API key ready for authentication](https://ragflow.io/docs/dev/acquire_ragflow_api_key).
---
@ -383,7 +383,7 @@ curl --request POST \
- `"layout_recognize"`: `string`
- Defaults to `DeepDOC`
- `"tag_kb_ids"`: `array<string>` refer to [Use tag set](https://ragflow.io/docs/dev/use_tag_sets)
- Must include a list of dataset IDs, where each dataset is parsed using the Tag Chunk Method
- Must include a list of dataset IDs, where each dataset is parsed using the Tag Chunking Method
- `"task_page_size"`: `int` For PDF only.
- Defaults to `12`
- Minimum: `1`
@ -604,7 +604,7 @@ curl --request PUT \
- `"layout_recognize"`: `string`
- Defaults to `DeepDOC`
- `"tag_kb_ids"`: `array<string>` refer to [Use tag set](https://ragflow.io/docs/dev/use_tag_sets)
- Must include a list of dataset IDs, where each dataset is parsed using the Tag Chunk Method
- Must include a list of dataset IDs, where each dataset is parsed using the Tag Chunking Method
- `"task_page_size"`: `int` For PDF only.
- Defaults to `12`
- Minimum: `1`
@ -731,7 +731,7 @@ Failure:
```
---
## Get dataset's knowledge graph
### Get knowledge graph
**GET** `/api/v1/datasets/{dataset_id}/knowledge_graph`
@ -810,7 +810,7 @@ Failure:
```
---
## Delete dataset's knowledge graph
### Delete knowledge graph
**DELETE** `/api/v1/datasets/{dataset_id}/knowledge_graph`

View File

@ -5,7 +5,7 @@ slug: /python_api_reference
# Python API
A complete reference for RAGFlow's Python APIs. Before proceeding, please ensure you [have your RAGFlow API key ready for authentication](../develop/acquire_ragflow_api_key.md).
A complete reference for RAGFlow's Python APIs. Before proceeding, please ensure you [have your RAGFlow API key ready for authentication](https://ragflow.io/docs/dev/acquire_ragflow_api_key).
:::tip NOTE
Run the following command to download the Python SDK:

View File

@ -9,8 +9,8 @@ Key features, improvements and bug fixes in the latest releases.
:::info
Each RAGFlow release is available in two editions:
- **Slim edition**: excludes built-in embedding models and is identified by a **-slim** suffix added to the version name. Example: `infiniflow/ragflow:v0.20.1-slim`
- **Full edition**: includes built-in embedding models and has no suffix added to the version name. Example: `infiniflow/ragflow:v0.20.1`
- **Slim edition**: excludes built-in embedding models and is identified by a **-slim** suffix added to the version name. Example: `infiniflow/ragflow:v0.20.4-slim`
- **Full edition**: includes built-in embedding models and has no suffix added to the version name. Example: `infiniflow/ragflow:v0.20.4`
:::
:::danger IMPORTANT
@ -22,6 +22,38 @@ The embedding models included in a full edition are:
These two embedding models are optimized specifically for English and Chinese, so performance may be compromised if you use them to embed documents in other languages.
:::
## v0.20.4
Released on August 27, 2025.
### Improvements
- Agent component: Completes Chinese localization for the Agent component.
- Introduces the `ENABLE_TIMEOUT_ASSERTION` environment variable to enable or disable timeout assertions for file parsing tasks.
- Dataset:
- Improves Markdown file parsing, with AST support to avoid unintended chunking.
- Enhances HTML parsing, supporting bs4-based HTML tag traversal.
### Added models
ZHIPU GLM-4.5
### New Agent templates
Ecommerce Customer Service Workflow: A template designed to handle enquiries about product features and multi-product comparisons using the internal knowledge base, as well as to manage installation appointment bookings.
### Fixed issues
- Dataset:
- Unable to share resources with the team.
- Inappropriate restrictions on the number and size of uploaded files.
- Chat:
- Unable to preview referenced files in responses.
- Unable to send out messages after file uploads.
- An OAuth2 authentication failure.
- A logical error in multi-conditioned metadata searches within a dataset.
- Citations infinitely increased in multi-turn conversations.
## v0.20.3
Released on August 20, 2025.

View File

@ -56,7 +56,7 @@ env:
ragflow:
image:
repository: infiniflow/ragflow
tag: v0.20.3-slim
tag: v0.20.4-slim
pullPolicy: IfNotPresent
pullSecrets: []
# Optional service configuration overrides

View File

@ -1,6 +1,6 @@
[project]
name = "ragflow"
version = "0.20.3"
version = "0.20.4"
description = "[RAGFlow](https://ragflow.io/) is an open-source RAG (Retrieval-Augmented Generation) engine based on deep document understanding. It offers a streamlined RAG workflow for businesses of any scale, combining LLM (Large Language Models) to provide truthful question-answering capabilities, backed by well-founded citations from various complex formatted data."
authors = [{ name = "Zhichang Yu", email = "yuzhichang@gmail.com" }]
license-files = ["LICENSE"]

View File

@ -517,7 +517,8 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
elif re.search(r"\.(htm|html)$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
sections = HtmlParser()(filename, binary)
chunk_token_num = int(parser_config.get("chunk_token_num", 128))
sections = HtmlParser()(filename, binary, chunk_token_num)
sections = [(_, "") for _ in sections if _]
callback(0.8, "Finish parsing.")

View File

@ -44,14 +44,17 @@ class Base(ABC):
raise NotImplementedError("Please implement encode method!")
def total_token_count(self, resp):
try:
return resp.usage.total_tokens
except Exception:
pass
try:
return resp["usage"]["total_tokens"]
except Exception:
pass
if hasattr(resp, "usage") and hasattr(resp.usage, "total_tokens"):
try:
return resp.usage.total_tokens
except Exception:
pass
if 'usage' in resp and 'total_tokens' in resp['usage']:
try:
return resp["usage"]["total_tokens"]
except Exception:
pass
return 0

View File

@ -1,6 +1,6 @@
[project]
name = "ragflow-sdk"
version = "0.20.3"
version = "0.20.4"
description = "Python client sdk of [RAGFlow](https://github.com/infiniflow/ragflow). RAGFlow is an open-source RAG (Retrieval-Augmented Generation) engine based on deep document understanding."
authors = [{ name = "Zhichang Yu", email = "yuzhichang@gmail.com" }]
license = { text = "Apache License, Version 2.0" }

2
sdk/python/uv.lock generated
View File

@ -342,7 +342,7 @@ wheels = [
[[package]]
name = "ragflow-sdk"
version = "0.20.3"
version = "0.20.4"
source = { virtual = "." }
dependencies = [
{ name = "beartype" },

2
uv.lock generated
View File

@ -5268,7 +5268,7 @@ wheels = [
[[package]]
name = "ragflow"
version = "0.20.3"
version = "0.20.4"
source = { virtual = "." }
dependencies = [
{ name = "akshare" },

View File

@ -158,6 +158,8 @@ interface FileUploaderProps extends React.HTMLAttributes<HTMLDivElement> {
* @example disabled
*/
disabled?: boolean;
description?: string;
}
export function FileUploader(props: FileUploaderProps) {
@ -174,6 +176,7 @@ export function FileUploader(props: FileUploaderProps) {
multiple = false,
disabled = false,
className,
description,
...dropzoneProps
} = props;
const { t } = useTranslation();
@ -302,7 +305,7 @@ export function FileUploader(props: FileUploaderProps) {
{t('knowledgeDetails.uploadTitle')}
</p>
<p className="text-sm text-muted-foreground/70">
{t('knowledgeDetails.uploadDescription')}
{description || t('knowledgeDetails.uploadDescription')}
{/* You can upload
{maxFileCount > 1
? ` ${maxFileCount === Infinity ? 'multiple' : maxFileCount}

View File

@ -12,6 +12,7 @@ import {
FormMessage,
} from '@/components/ui/form';
import { LlmModelType } from '@/constants/knowledge';
import { t } from 'i18next';
import { Funnel } from 'lucide-react';
import { useFormContext, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
@ -21,15 +22,15 @@ import { Button } from './ui/button';
const ModelTypes = [
{
title: 'All Models',
title: t('flow.allModels'),
value: 'all',
},
{
title: 'Text-only Models',
title: t('flow.textOnlyModels'),
value: LlmModelType.Chat,
},
{
title: 'Multimodal Models',
title: t('flow.multimodalModels'),
value: LlmModelType.Image2text,
},
];

View File

@ -34,6 +34,7 @@ interface IProps {
createConversationBeforeUploadDocument?(message: string): Promise<any>;
stopOutputMessage?(): void;
onUpload?: NonNullable<FileUploadProps['onUpload']>;
removeFile?(file: File): void;
}
export function NextMessageInput({
@ -47,6 +48,7 @@ export function NextMessageInput({
onInputChange,
stopOutputMessage,
onPressEnter,
removeFile,
}: IProps) {
const [files, setFiles] = React.useState<File[]>([]);
@ -77,6 +79,13 @@ export function NextMessageInput({
[submit],
);
const handleRemoveFile = React.useCallback(
(file: File) => () => {
removeFile?.(file);
},
[removeFile],
);
return (
<FileUpload
value={files}
@ -121,6 +130,7 @@ export function NextMessageInput({
variant="secondary"
size="icon"
className="-top-1 -right-1 absolute size-4 shrink-0 cursor-pointer rounded-full"
onClick={handleRemoveFile(file)}
>
<X className="size-2.5" />
</Button>

View File

@ -145,9 +145,9 @@ export const SelectWithSearch = forwardRef<
align="start"
>
<Command>
<CommandInput placeholder="Search ..." />
<CommandInput placeholder={t('common.search') + '...'} />
<CommandList>
<CommandEmpty>No data found.</CommandEmpty>
<CommandEmpty>{t('common.noDataFound')}</CommandEmpty>
{options.map((group, idx) => {
if (group.options) {
return (

View File

@ -1,3 +1,4 @@
import { t } from 'i18next';
import { Loader2 } from 'lucide-react';
import { PropsWithChildren } from 'react';
import { TableCell, TableRow } from './ui/table';
@ -28,5 +29,5 @@ export function TableSkeleton({
}
export function TableEmpty({ columnsLength }: { columnsLength: number }) {
return <Row columnsLength={columnsLength}>No results.</Row>;
return <Row columnsLength={columnsLength}>{t('common.noResults')}</Row>;
}

View File

@ -209,13 +209,13 @@ export const MultiSelect = React.forwardRef<
const [isAnimating, setIsAnimating] = React.useState(false);
React.useEffect(() => {
if (!selectedValues && props.value) {
if (!selectedValues?.length && props.value) {
setSelectedValues(props.value as string[]);
}
}, [props.value, selectedValues]);
React.useEffect(() => {
if (!selectedValues && !props.value && defaultValue) {
if (!selectedValues?.length && !props.value && defaultValue) {
setSelectedValues(defaultValue);
}
}, [defaultValue, props.value, selectedValues]);

View File

@ -8,7 +8,7 @@ const Table = React.forwardRef<
>(({ className, rootClassName, ...props }, ref) => (
<div
className={cn(
'relative w-full overflow-auto rounded-2xl bg-bg-card',
'relative w-full overflow-auto rounded-2xl bg-bg-card scrollbar-none',
rootClassName,
)}
>
@ -27,7 +27,7 @@ const TableHeader = React.forwardRef<
>(({ className, ...props }, ref) => (
<thead
ref={ref}
className={cn('[&_tr]:border-b top-0 sticky', className)}
className={cn('[&_tr]:border-b top-0 sticky bg-bg-title z-10', className)}
{...props}
/>
));
@ -67,7 +67,7 @@ const TableRow = React.forwardRef<
<tr
ref={ref}
className={cn(
'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
'border-b border-border-button transition-colors hover:bg-bg-card data-[state=selected]:bg-bg-card',
className,
)}
{...props}
@ -82,7 +82,7 @@ const TableHead = React.forwardRef<
<th
ref={ref}
className={cn(
'h-12 px-4 text-left align-middle font-normal text-text-secondary [&:has([role=checkbox])]:pr-0',
'h-12 px-4 text-left align-middle font-normal text-text-secondary [&:has([role=checkbox])]:pr-0 ',
className,
)}
{...props}

View File

@ -33,7 +33,7 @@ export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
export const FormTooltip = ({ tooltip }: { tooltip: React.ReactNode }) => {
return (
<Tooltip>
<TooltipTrigger>
<TooltipTrigger tabIndex={-1}>
<Info className="size-3 ml-2" />
</TooltipTrigger>
<TooltipContent>

View File

@ -1,5 +1,5 @@
import message from '@/components/ui/message';
import authorizationUtil from '@/utils/authorization-util';
import { message } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import { useNavigate, useSearchParams } from 'umi';
@ -28,7 +28,7 @@ export const useOAuthCallback = () => {
authorizationUtil.setAuthorization(auth);
newQueryParameters.delete('auth');
setSearchParams(newQueryParameters);
navigate('/knowledge');
navigate('/');
}
}, [
error,

View File

@ -390,7 +390,7 @@ export const useUploadCanvasFileWithProgress = (
files.forEach((file) => {
onError(file, error as Error);
});
message.error('error', error?.message);
message.error(error?.message);
}
},
});

View File

@ -1,3 +1,4 @@
import { FileUploadProps } from '@/components/file-upload';
import message from '@/components/ui/message';
import { ChatSearchParams } from '@/constants/chat';
import {
@ -14,7 +15,7 @@ import { buildMessageListWithUuid, getConversationId } from '@/utils/chat';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useDebounce } from 'ahooks';
import { has } from 'lodash';
import { useCallback, useMemo } from 'react';
import { useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams, useSearchParams } from 'umi';
import {
@ -395,9 +396,14 @@ export const useDeleteMessage = () => {
return { data, loading, deleteMessage: mutateAsync };
};
type UploadParameters = Parameters<NonNullable<FileUploadProps['onUpload']>>;
type X = { file: UploadParameters[0][0]; options: UploadParameters[1] };
export function useUploadAndParseFile() {
const { conversationId } = useGetChatSearchParams();
const { t } = useTranslation();
const controller = useRef(new AbortController());
const {
data,
@ -405,22 +411,48 @@ export function useUploadAndParseFile() {
mutateAsync,
} = useMutation({
mutationKey: [ChatApiAction.UploadAndParse],
mutationFn: async (file: File) => {
const formData = new FormData();
formData.append('file', file);
formData.append('conversation_id', conversationId);
mutationFn: async ({
file,
options: { onProgress, onSuccess, onError },
}: X) => {
try {
const formData = new FormData();
formData.append('file', file);
formData.append('conversation_id', conversationId);
const { data } = await chatService.uploadAndParse(formData);
const { data } = await chatService.uploadAndParse(
{
signal: controller.current.signal,
data: formData,
onUploadProgress: ({ progress }) => {
onProgress(file, (progress || 0) * 100 - 1);
},
},
true,
);
if (data.code === 0) {
message.success(t(`message.uploaded`));
onProgress(file, 100);
if (data.code === 0) {
onSuccess(file);
message.success(t(`message.uploaded`));
} else {
onError(file, new Error(data.message));
}
return data;
} catch (error) {
onError(file, error as Error);
}
return data;
},
});
return { data, loading, uploadAndParseFile: mutateAsync };
const cancel = useCallback(() => {
controller.current.abort();
controller.current = new AbortController();
}, [controller]);
return { data, loading, uploadAndParseFile: mutateAsync, cancel };
}
export const useFetchExternalChatInfo = () => {

View File

@ -1,6 +1,7 @@
export default {
translation: {
common: {
noResults: 'No results.',
selectPlaceholder: 'select value',
delete: 'Delete',
deleteModalTitle: 'Are you sure to delete this item?',
@ -40,6 +41,9 @@ export default {
previousPage: 'Previous',
nextPage: 'Next',
add: 'Add',
remove: 'Remove',
search: 'Search',
noDataFound: 'No data found.',
promptPlaceholder: `Please input or use / to quickly insert variables.`,
mcp: {
namePlaceholder: 'My MCP Server',
@ -192,7 +196,7 @@ export default {
delimiterTip:
'A delimiter or separator can consist of one or multiple special characters. If it is multiple characters, ensure they are enclosed in backticks( ``). For example, if you configure your delimiters like this: \\n`##`;, then your texts will be separated at line breaks, double hash symbols (##), and semicolons.',
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.`,
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. See https://ragflow.io/docs/dev/enable_excel2html for details.`,
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. For details, see https://ragflow.io/docs/dev/autokeyword_autoquestion.`,
autoQuestions: 'Auto-question',
@ -837,7 +841,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
fileManager: {
name: 'Name',
uploadDate: 'Upload Date',
knowledgeBase: 'Knowledge Base',
knowledgeBase: 'Dataset',
size: 'Size',
action: 'Action',
addToKnowledge: 'Link to Knowledge Base',
@ -860,11 +864,87 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
pleaseUploadAtLeastOneFile: 'Please upload at least one file',
},
flow: {
days: 'Days',
beginInput: 'Begin Input',
ref: 'Variable',
stockCode: 'Stock Code',
apiKeyPlaceholder:
'YOUR_API_KEY (obtained from https://serpapi.com/manage-api-key)',
flowStart: 'Start',
flowNum: 'Num',
test: 'Test',
extractDepth: 'Extract Depth',
format: 'Format',
basic: 'basic',
advanced: 'advanced',
general: 'general',
searchDepth: 'Search Depth',
tavilyTopic: 'Tavily Topic',
maxResults: 'Max Results',
includeAnswer: 'Include Answer',
includeRawContent: 'Include Raw Content',
includeImages: 'Include Images',
includeImageDescriptions: 'Include Image Descriptions',
includeDomains: 'Include Domains',
ExcludeDomains: 'Exclude Domains',
Days: 'Days',
comma: 'Comma',
semicolon: 'Semicolon',
period: 'Period',
lineBreak: 'Line Break',
tab: 'Tab',
space: 'Space',
delimiters: 'Delimiters',
merge: 'Merge',
split: 'Split',
script: 'Script',
iterationItemDescription:
'It represents the current element in the iteration, which can be referenced and manipulated in subsequent steps.',
guidingQuestion: 'Guidance Question',
onFailure: 'On Failure',
userPromptDefaultValue:
'This is the order you need to send to the agent.',
search: 'Search',
communication: 'Communication',
developer: 'Developer',
typeCommandOrsearch: 'Type a command or search...',
builtIn: 'Built-in',
ExceptionDefaultValue: 'Exception default value',
exceptionMethod: 'Exception method',
maxRounds: 'Max rounds',
delayEfterError: 'Delay after error',
maxRetries: 'Max retries',
advancedSettings: 'Advanced Settings',
addTools: 'Add Tools',
sysPromptDefultValue: `
<role>
You are {{agent_name}}, an AI assistant specialized in {{domain_or_task}}.
</role>
<instructions>
1. Understand the users request.
2. Decompose it into logical subtasks.
3. Execute each subtask step by step, reasoning transparently.
4. Validate accuracy and consistency.
5. Summarize the final result clearly.
</instructions>`,
singleLineText: 'Single-line text',
multimodalModels: 'Multimodal Models',
textOnlyModels: 'Text-only Models',
allModels: 'All Models',
codeExecDescription: 'Write your custom Python or Javascript logic.',
stringTransformDescription:
'Modifies text content. Currently supports: Splitting or concatenating text.',
foundation: 'Foundation',
tools: 'Tools',
dataManipulation: 'Data Manipulation',
flow: 'Flow',
dialog: 'Dialogue',
cite: 'Cite',
citeTip: 'citeTip',
name: 'Name',
nameMessage: 'Please input name',
description: 'Description',
descriptionMessage: 'This is an agent for a specific task.',
examples: 'Examples',
to: 'To',
msg: 'Messages',
@ -1289,6 +1369,7 @@ This delimiter is used to split the input text into several text pieces echo of
variableSettings: 'Variable settings',
globalVariables: 'Global variables',
systemPrompt: 'System prompt',
userPrompt: 'User prompt',
addCategory: 'Add category',
categoryName: 'Category name',
nextStep: 'Next step',
@ -1352,10 +1433,15 @@ This delimiter is used to split the input text into several text pieces echo of
openingSwitchTip:
'Your users will see this welcome message at the beginning.',
modeTip: 'The mode defines how the workflow is initiated.',
mode: 'Mode',
conversational: 'conversational',
task: 'task',
beginInputTip:
'By defining input parameters, this content can be accessed by other components in subsequent processes.',
query: 'Query variables',
queryTip: 'Select the variable you want to use',
agent: 'Agent',
addAgent: 'Add Agent',
agentDescription:
'Builds agent components equipped with reasoning, tool usage, and multi-agent collaboration. ',
maxRecords: 'Max records',

View File

@ -69,7 +69,7 @@ export default {
setting: '用戶設置',
logout: '登出',
fileManager: '文件管理',
flow: 'Agent',
flow: '智能體',
search: '搜尋',
welcome: '歡迎來到',
},
@ -764,6 +764,23 @@ export default {
destinationFolder: '目標資料夾',
},
flow: {
line: '單行文本',
paragraph: '段落文字',
options: '選項',
file: '文件',
integer: '數字',
boolean: '布爾值',
multimodalModels: '多模態模型',
textOnlyModels: '進文本模型',
allModels: '所有模型',
codeExecDescription: '用 Python 或者 Javascript 編寫自定義邏輯',
stringTransformDescription:
'修改文本内容,目前支持文本分割、文本拼接操作',
foundation: '基礎',
tools: '工具',
dataManipulation: '數據操控',
flow: '流程',
dialog: '對話',
cite: '引用',
citeTip: 'citeTip',
name: '名稱',
@ -805,7 +822,7 @@ export default {
promptText: `請總結以下段落。注意數字,不要胡編亂造。段落如下:
{input}
以上就是你需要總結的內容。`,
createGraph: '建立 Agent',
createGraph: '創建智能體',
createFromTemplates: '從模板創建',
retrieval: '知識檢索',
generate: '生成回答',

View File

@ -1,6 +1,7 @@
export default {
translation: {
common: {
noResults: '无结果。',
selectPlaceholder: '请选择',
delete: '删除',
deleteModalTitle: '确定删除吗?',
@ -39,6 +40,9 @@ export default {
previousPage: '上一页',
nextPage: '下一页',
add: '添加',
remove: '移除',
search: '搜索',
noDataFound: '没有找到数据。',
promptPlaceholder: '请输入或使用 / 快速插入变量。',
},
login: {
@ -71,7 +75,7 @@ export default {
setting: '用户设置',
logout: '登出',
fileManager: '文件管理',
flow: 'Agent',
flow: '智能体',
search: '搜索',
welcome: '欢迎来到',
dataset: '数据集',
@ -814,6 +818,90 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
pleaseUploadAtLeastOneFile: '请上传至少一个文件',
},
flow: {
beginInput: '开始输入',
seconds: '秒',
ref: '引用变量',
stockCode: '股票代码',
apiKeyPlaceholder: '您的API密钥(从https://serpapi.com获取)',
flowStart: '开始',
flowNum: '编号',
test: '测试',
extractDepth: '深度提取',
format: '格式',
basic: '基本',
advanced: '高级',
general: '通用',
searchDepth: '深度搜索',
tavilyTopic: 'Tavily话题',
maxResults: '最大结果数',
includeAnswer: '包含答案',
includeRawContent: '包含原始内容',
includeImages: '包含图片',
includeImageDescriptions: '包含图片描述',
includeDomains: '包含域名',
ExcludeDomains: '排除域名',
days: '天数',
comma: '逗号',
semicolon: '分号',
period: '句点',
linebreak: '换行符',
tab: '制表符',
space: '空格',
delimiters: '分隔符',
merge: '合并',
split: '拆分',
script: '脚本',
iterationItemDescription:
'它是迭代过程中的当前元素,可以被后续流程引用和操作。',
guidingQuestion: '引导问题',
onFailure: '异常时',
userPromptDefaultValue:
'This is the order you need to send to the agent.',
descriptionMessage: '这是一个用于特定任务的代理。',
search: '搜索',
communication: '通信',
developer: '开发者',
typeCommandOrsearch: '输入命令或或搜索...',
builtIn: '内置',
goto: '异常分支',
comment: '默认值',
ExceptionDefaultValue: '异常处理默认值',
exceptionMethod: '异常处理方法',
maxRounds: '最大轮数',
delayEfterError: '错误后延迟',
maxRetries: '最大重试次数',
advancedSettings: '高级设置',
addTools: '添加工具',
sysPromptDefultValue: `
<role>
你是{{agent_name}},一位专注于{{领域_or_任务}}的AI助手。
</role>
<instructions>
1. 理解用户请求。
2. 将其分解为逻辑子任务。
3. 逐步执行每个子任务,并清晰地进行推理。
4. 验证准确性和一致性。
5. 清晰地总结最终结果。
</instructions>`,
line: '单行文本',
paragraph: '段落文字',
options: '选项',
file: '文件',
integer: '数字',
boolean: '布尔值',
name: '名称',
singleLineText: '单行文本',
variableSettings: '变量设置',
multimodalModels: '多模态模型',
textOnlyModels: '仅文本模型',
allModels: '所有模型',
codeExecDescription: '用 Python 或者 Javascript 编写自定义逻辑',
stringTransformDescription:
'修改文本内容,目前支持文本分割、文本拼接操作',
foundation: '基础',
tools: '工具',
dataManipulation: '数据操控',
dialog: '对话',
flow: '工作流',
noMoreData: '没有更多数据了',
historyversion: '历史版本',
@ -823,7 +911,6 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
},
cite: '引用',
citeTip: '引用',
name: '名称',
nameMessage: '请输入名称',
description: '描述',
examples: '示例',
@ -839,7 +926,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
'loop为当前组件循环次数上限当循环次数超过loop的值时说明组件不能完成当前任务请重新优化agent',
yes: '是',
no: '否',
key: 'key',
key: '',
componentId: '组件ID',
add: '新增',
operation: '操作',
@ -861,7 +948,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
promptText: `请总结以下段落。注意数字,不要胡编乱造。段落如下:
{input}
以上就是你需要总结的内容。`,
createGraph: '创建 Agent',
createGraph: '创建智能体',
createFromTemplates: '从模板创建',
retrieval: '知识检索',
generate: '生成回答',
@ -911,7 +998,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
bing: 'Bing',
bingDescription:
'此组件用于从 https://www.bing.com/ 获取搜索结果。通常它作为知识库的补充。Top N 和 Bing Subscription-Key 指定您需要调整的搜索结果数量。',
apiKey: 'API KEY',
apiKey: 'API密钥',
country: '国家和地区',
language: '语言',
googleScholar: '谷歌学术',
@ -1043,7 +1130,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
'30d': '30天',
},
publish: 'API',
exeSQL: 'ExeSQL',
exeSQL: '执行 SQL',
exeSQLDescription:
'该组件通过SQL语句从相应的关系数据库中查询结果。支持MySQLPostgreSQLMariaDB。',
dbType: '数据库类型',
@ -1255,6 +1342,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
team: '团队',
},
systemPrompt: '系统提示词',
userPrompt: '用户提示词',
prompt: '提示词',
promptMessage: '提示词是必填项',
promptTip:
@ -1270,21 +1358,26 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
openingCopy: '开场白文案',
openingSwitchTip: '您的用户将在开始时看到此欢迎消息。',
modeTip: '模式定义了工作流的启动方式。',
mode: '模式',
conversational: '对话式',
task: '任务',
beginInputTip: '通过定义输入参数,此内容可以被后续流程中的其他组件访问。',
query: '查询变量',
queryTip: '选择您想要使用的变量',
agent: '智能体',
addAgent: '添加智能体',
agentDescription: '构建具备推理、工具调用和多智能体协同的智能体组件。',
maxRecords: '最大记录数',
createAgent: 'Create Agent',
createAgent: '创建智能体',
stringTransform: '文本处理',
userFillUp: '等待输入',
userFillUpDescription: `此组件会暂停当前的流程并等待用户发送消息,接收到消息之后再进行之后的流程。`,
codeExec: '代码',
tavilySearch: 'Tavily Search',
tavilySearch: 'Tavily 搜索',
tavilySearchDescription: '通过 Tavily 服务搜索结果',
tavilyExtract: 'Tavily Extract',
tavilyExtractDescription: 'Tavily Extract',
tavilyExtract: 'Tavily 提取',
tavilyExtractDescription: 'Tavily 提取',
log: '日志',
management: '管理',
import: '导入',

View File

@ -89,7 +89,7 @@ function InnerAgentNode({
{(isGotoMethod ||
exceptionMethod === AgentExceptionMethod.Comment) && (
<div className="bg-bg-card rounded-sm p-1 flex justify-between gap-2">
<span className="text-text-secondary">On Failure</span>
<span className="text-text-secondary">{t('flow.onFailure')}</span>
<span className="truncate flex-1 text-right">
{t(`flow.${exceptionMethod}`)}
</span>

View File

@ -21,6 +21,7 @@ import { Operator } from '@/pages/agent/constant';
import { AgentInstanceContext, HandleContext } from '@/pages/agent/context';
import OperatorIcon from '@/pages/agent/operator-icon';
import { Position } from '@xyflow/react';
import { t } from 'i18next';
import { lowerFirst } from 'lodash';
import {
PropsWithChildren,
@ -128,7 +129,9 @@ function AccordionOperators({
defaultValue={['item-1', 'item-2', 'item-3', 'item-4', 'item-5']}
>
<AccordionItem value="item-1">
<AccordionTrigger className="text-xl">Foundation</AccordionTrigger>
<AccordionTrigger className="text-xl">
{t('flow.foundation')}
</AccordionTrigger>
<AccordionContent className="flex flex-col gap-4 text-balance">
<OperatorItemList
operators={[Operator.Agent, Operator.Retrieval]}
@ -138,7 +141,9 @@ function AccordionOperators({
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger className="text-xl">Dialogue </AccordionTrigger>
<AccordionTrigger className="text-xl">
{t('flow.dialog')}
</AccordionTrigger>
<AccordionContent className="flex flex-col gap-4 text-balance">
<OperatorItemList
operators={[Operator.Message, Operator.UserFillUp]}
@ -148,7 +153,9 @@ function AccordionOperators({
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger className="text-xl">Flow</AccordionTrigger>
<AccordionTrigger className="text-xl">
{t('flow.flow')}
</AccordionTrigger>
<AccordionContent className="flex flex-col gap-4 text-balance">
<OperatorItemList
operators={[
@ -163,7 +170,7 @@ function AccordionOperators({
</AccordionItem>
<AccordionItem value="item-4">
<AccordionTrigger className="text-xl">
Data Manipulation
{t('flow.dataManipulation')}
</AccordionTrigger>
<AccordionContent className="flex flex-col gap-4 text-balance">
<OperatorItemList
@ -174,7 +181,9 @@ function AccordionOperators({
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-5">
<AccordionTrigger className="text-xl">Tools</AccordionTrigger>
<AccordionTrigger className="text-xl">
{t('flow.tools')}
</AccordionTrigger>
<AccordionContent className="flex flex-col gap-4 text-balance">
<OperatorItemList
operators={[
@ -244,7 +253,7 @@ export function InnerNextStepDropdown({
>
<div className="w-[300px] font-semibold bg-bg-base border border-border rounded-md shadow-lg">
<div className="px-3 py-2 border-b border-border">
<div className="text-sm font-medium">Next Step</div>
<div className="text-sm font-medium">{t('flow.nextStep')}</div>
</div>
<HideModalContext.Provider value={hideModal}>
<OnNodeCreatedContext.Provider value={onNodeCreated}>
@ -273,7 +282,7 @@ export function InnerNextStepDropdown({
onClick={(e) => e.stopPropagation()}
className="w-[300px] font-semibold"
>
<DropdownMenuLabel>Next Step</DropdownMenuLabel>
<DropdownMenuLabel>{t('flow.nextStep')}</DropdownMenuLabel>
<HideModalContext.Provider value={hideModal}>
<AccordionOperators></AccordionOperators>
</HideModalContext.Provider>

View File

@ -4,7 +4,6 @@ import {
useHandleMessageInputChange,
useSelectDerivedMessages,
} from '@/hooks/logic-hooks';
import { useFetchAgent } from '@/hooks/use-agent-request';
import {
IEventList,
IInputEvent,
@ -189,7 +188,7 @@ export const useSendAgentMessage = (
return answerList[0]?.message_id;
}, [answerList]);
const { refetch } = useFetchAgent();
// const { refetch } = useFetchAgent();
const { findReferenceByMessageId } = useFindMessageReference(answerList);
const prologue = useGetBeginNodePrologue();
@ -247,7 +246,7 @@ export const useSendAgentMessage = (
setValue(message.content);
removeLatestMessage();
} else {
refetch(); // pull the message list after sending the message successfully
// refetch(); // pull the message list after sending the message successfully
}
} catch (error) {
console.log('🚀 ~ useSendAgentMessage ~ error:', error);
@ -263,7 +262,6 @@ export const useSendAgentMessage = (
clearUploadResponseList,
setValue,
removeLatestMessage,
refetch,
],
);
@ -276,9 +274,9 @@ export const useSendAgentMessage = (
role: MessageType.User,
});
await send({ ...body, session_id: sessionId });
refetch();
// refetch();
},
[addNewestOneQuestion, refetch, send, sessionId],
[addNewestOneQuestion, send, sessionId],
);
// reset session

View File

@ -20,6 +20,7 @@ import {
import { ModelVariableType } from '@/constants/knowledge';
import i18n from '@/locales/config';
import { setInitialChatVariableEnabledFieldValue } from '@/utils/chat';
import { t } from 'i18next';
// DuckDuckGo's channel options
export enum Channel {
@ -630,16 +631,7 @@ export const initialAgentValues = {
...initialLlmBaseValues,
description: '',
user_prompt: '',
sys_prompt: `<role>
You are {{agent_name}}, an AI assistant specialized in {{domain_or_task}}.
</role>
<instructions>
1. Understand the users request.
2. Decompose it into logical subtasks.
3. Execute each subtask step by step, reasoning transparently.
4. Validate accuracy and consistency.
5. Summarize the final result clearly.
</instructions>`,
sys_prompt: t('flow.sysPromptDefultValue'),
prompts: [{ role: PromptRole.User, content: `{${AgentGlobals.SysQuery}}` }],
message_history_window_size: 12,
max_retries: 3,

View File

@ -6,6 +6,7 @@ import {
} from '@/components/ui/tooltip';
import { cn } from '@/lib/utils';
import { Position } from '@xyflow/react';
import { t } from 'i18next';
import { PencilLine, X } from 'lucide-react';
import {
MouseEventHandler,
@ -106,7 +107,7 @@ export function AgentTools() {
return (
<section className="space-y-2.5">
<span className="text-text-secondary">Tools</span>
<span className="text-text-secondary">{t('flow.tools')}</span>
<ul className="space-y-2">
{toolNames.map((x) => (
<ToolCard key={x}>
@ -133,7 +134,7 @@ export function AgentTools() {
))}
</ul>
<ToolPopover>
<BlockButton>Add Tool</BlockButton>
<BlockButton>{t('flow.addTools')}</BlockButton>
</ToolPopover>
</section>
);
@ -160,7 +161,7 @@ export function Agents({ node }: INextOperatorForm) {
return (
<section className="space-y-2.5">
<span className="text-text-secondary">Agents</span>
<span className="text-text-secondary">{t('flow.agent')}</span>
<ul className="space-y-2">
{subBottomAgentNodeIds.map((id) => {
const currentNode = getNode(id);
@ -183,7 +184,7 @@ export function Agents({ node }: INextOperatorForm) {
position: Position.Bottom,
})}
>
Add Agent
{t('flow.addAgent')}
</BlockButton>
</section>
);

View File

@ -144,7 +144,7 @@ function AgentForm({ node }: INextOperatorForm) {
name={`sys_prompt`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>System Prompt</FormLabel>
<FormLabel>{t('flow.systemPrompt')}</FormLabel>
<FormControl>
<PromptEditor
{...field}
@ -164,7 +164,7 @@ function AgentForm({ node }: INextOperatorForm) {
name={`prompts`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>User Prompt</FormLabel>
<FormLabel>{t('flow.userPrompt')}</FormLabel>
<FormControl>
<section>
<PromptEditor
@ -183,7 +183,7 @@ function AgentForm({ node }: INextOperatorForm) {
<AgentTools></AgentTools>
<Agents node={node}></Agents>
</FormContainer>
<Collapse title={<div>Advanced Settings</div>}>
<Collapse title={<div>{t('flow.advancedSettings')}</div>}>
<FormContainer>
<MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField>
<FormField
@ -208,7 +208,7 @@ function AgentForm({ node }: INextOperatorForm) {
name={`max_retries`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Max retries</FormLabel>
<FormLabel>{t('flow.maxRetries')}</FormLabel>
<FormControl>
<NumberInput {...field} max={8}></NumberInput>
</FormControl>
@ -220,7 +220,7 @@ function AgentForm({ node }: INextOperatorForm) {
name={`delay_after_error`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Delay after error</FormLabel>
<FormLabel>{t('flow.delayEfterError')}</FormLabel>
<FormControl>
<NumberInput {...field} max={5} step={0.1}></NumberInput>
</FormControl>
@ -232,7 +232,7 @@ function AgentForm({ node }: INextOperatorForm) {
name={`max_rounds`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Max rounds</FormLabel>
<FormLabel>{t('flow.maxRounds')}</FormLabel>
<FormControl>
<NumberInput {...field}></NumberInput>
</FormControl>
@ -244,7 +244,7 @@ function AgentForm({ node }: INextOperatorForm) {
name={`exception_method`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Exception method</FormLabel>
<FormLabel>{t('flow.exceptionMethod')}</FormLabel>
<FormControl>
<SelectWithSearch
{...field}
@ -261,7 +261,7 @@ function AgentForm({ node }: INextOperatorForm) {
name={`exception_default_value`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Exception default value</FormLabel>
<FormLabel>{t('flow.ExceptionDefaultValue')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>

View File

@ -8,6 +8,7 @@ import { Operator } from '@/pages/agent/constant';
import { AgentFormContext, AgentInstanceContext } from '@/pages/agent/context';
import useGraphStore from '@/pages/agent/store';
import { Position } from '@xyflow/react';
import { t } from 'i18next';
import { PropsWithChildren, useCallback, useContext, useEffect } from 'react';
import { useGetAgentMCPIds, useGetAgentToolNames } from '../use-get-tools';
import { MCPCommand, ToolCommand } from './tool-command';
@ -65,8 +66,12 @@ export function ToolPopover({ children }: PropsWithChildren) {
<PopoverContent className="w-80 p-4">
<Tabs defaultValue={ToolType.Common}>
<TabsList>
<TabsTrigger value={ToolType.Common}>Built-in</TabsTrigger>
<TabsTrigger value={ToolType.MCP}>MCP</TabsTrigger>
<TabsTrigger value={ToolType.Common} className="bg-bg-card">
{t('flow.builtIn')}
</TabsTrigger>
<TabsTrigger value={ToolType.MCP} className="bg-bg-card">
MCP
</TabsTrigger>
</TabsList>
<TabsContent value={ToolType.Common}>
<ToolCommand

View File

@ -12,13 +12,14 @@ import { useListMcpServer } from '@/hooks/use-mcp-request';
import { cn } from '@/lib/utils';
import { Operator } from '@/pages/agent/constant';
import OperatorIcon from '@/pages/agent/operator-icon';
import { t } from 'i18next';
import { lowerFirst } from 'lodash';
import { PropsWithChildren, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
const Menus = [
{
label: 'Search',
label: t('flow.search'),
list: [
Operator.TavilySearch,
Operator.TavilyExtract,
@ -34,7 +35,7 @@ const Menus = [
],
},
{
label: 'Communication',
label: t('flow.communication'),
list: [Operator.Email],
},
// {
@ -42,7 +43,7 @@ const Menus = [
// list: [],
// },
{
label: 'Developer',
label: t('flow.developer'),
list: [Operator.GitHub, Operator.ExeSQL, Operator.Code, Operator.Retrieval],
},
];
@ -116,7 +117,7 @@ export function ToolCommand({ value, onChange }: ToolCommandProps) {
return (
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandInput placeholder={t('flow.typeCommandOrsearch')} />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
{Menus.map((x) => (

View File

@ -12,8 +12,8 @@ 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 { t } from 'i18next';
import { Plus } from 'lucide-react';
import { memo, useEffect, useRef } from 'react';
import { useForm, useWatch } from 'react-hook-form';
@ -27,10 +27,10 @@ import { useEditQueryRecord } from './use-edit-query';
import { useValues } from './use-values';
import { useWatchFormChange } from './use-watch-change';
const ModeOptions = buildSelectOptions([
AgentDialogueMode.Conversational,
AgentDialogueMode.Task,
]);
const ModeOptions = [
{ value: AgentDialogueMode.Conversational, label: t('flow.conversational') },
{ value: AgentDialogueMode.Task, label: t('flow.task') },
];
function BeginForm({ node }: INextOperatorForm) {
const { t } = useTranslation();
@ -103,7 +103,9 @@ function BeginForm({ node }: INextOperatorForm) {
name={'mode'}
render={({ field }) => (
<FormItem>
<FormLabel tooltip={t('flow.modeTip')}>Mode</FormLabel>
<FormLabel tooltip={t('flow.modeTip')}>
{t('flow.mode')}
</FormLabel>
<FormControl>
<RAGFlowSelect
placeholder={t('common.pleaseSelect')}

View File

@ -138,7 +138,7 @@ function ParameterForm({
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Type</FormLabel>
<FormLabel>{t('type')}</FormLabel>
<FormControl>
<RAGFlowSelect {...field} options={options} />
</FormControl>
@ -151,7 +151,7 @@ function ParameterForm({
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Key</FormLabel>
<FormLabel>{t('key')}</FormLabel>
<FormControl>
<Input {...field} autoComplete="off" onBlur={handleKeyChange} />
</FormControl>
@ -164,7 +164,7 @@ function ParameterForm({
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@ -177,7 +177,7 @@ function ParameterForm({
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel>Optional</FormLabel>
<FormLabel>{t('optional')}</FormLabel>
<FormControl>
<Switch
checked={field.value}
@ -217,7 +217,7 @@ export function ParameterDialog({
></ParameterForm>
<DialogFooter>
<Button type="submit" form={FormId}>
Confirm
{t('modal.okText')}
</Button>
</DialogFooter>
</DialogContent>

View File

@ -53,7 +53,7 @@ export function QueryTable({ data = [], deleteRecord, showModal }: IProps) {
const columns: ColumnDef<BeginQuery>[] = [
{
accessorKey: 'key',
header: 'Key',
header: t('flow.key'),
meta: { cellClassName: 'max-w-30' },
cell: ({ row }) => {
const key: string = row.getValue('key');

View File

@ -6,6 +6,7 @@ import {
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { t } from 'i18next';
import { useFormContext } from 'react-hook-form';
interface IApiKeyFieldProps {
@ -19,7 +20,7 @@ export function ApiKeyField({ placeholder }: IApiKeyFieldProps) {
name="api_key"
render={({ field }) => (
<FormItem>
<FormLabel>Api Key</FormLabel>
<FormLabel>{t('flow.apiKey')}</FormLabel>
<FormControl>
<Input type="password" {...field} placeholder={placeholder}></Input>
</FormControl>

View File

@ -5,6 +5,7 @@ import {
FormLabel,
} from '@/components/ui/form';
import { Textarea } from '@/components/ui/textarea';
import { t } from 'i18next';
import { useFormContext } from 'react-hook-form';
export function DescriptionField() {
@ -15,7 +16,7 @@ export function DescriptionField() {
name={`description`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Description</FormLabel>
<FormLabel>{t('flow.description')}</FormLabel>
<FormControl>
<Textarea {...field}></Textarea>
</FormControl>

View File

@ -1,3 +1,5 @@
import { t } from 'i18next';
export type OutputType = {
title: string;
type?: string;
@ -17,7 +19,7 @@ export function transferOutputs(outputs: Record<string, any>) {
export function Output({ list }: OutputProps) {
return (
<section className="space-y-2">
<div>Output</div>
<div>{t('flow.output')}</div>
<ul>
{list.map((x, idx) => (
<li

View File

@ -47,7 +47,7 @@ export function QueryVariable({
render={({ field }) => (
<FormItem>
{label || (
<FormLabel tooltip={t('chat.modelTip')}>
<FormLabel tooltip={t('flow.queryTip')}>
{t('flow.query')}
</FormLabel>
)}

View File

@ -132,7 +132,7 @@ export function ExeSQLFormWidgets({ loading }: { loading: boolean }) {
<div className="flex justify-end">
<ButtonLoading loading={loading} type="submit">
Test
{t('test')}
</ButtonLoading>
</div>
</>

View File

@ -99,13 +99,13 @@ const GoogleForm = ({ node }: INextOperatorForm) => {
<QueryVariable name="q"></QueryVariable>
</FormContainer>
<FormContainer>
<ApiKeyField placeholder="YOUR_API_KEY (obtained from https://serpapi.com/manage-api-key)"></ApiKeyField>
<ApiKeyField placeholder={t('apiKeyPlaceholder')}></ApiKeyField>
<FormField
control={form.control}
name={`start`}
render={({ field }) => (
<FormItem>
<FormLabel>{t('start')}</FormLabel>
<FormLabel>{t('flowStart')}</FormLabel>
<FormControl>
<NumberInput {...field} className="w-full"></NumberInput>
</FormControl>
@ -118,7 +118,7 @@ const GoogleForm = ({ node }: INextOperatorForm) => {
name={`num`}
render={({ field }) => (
<FormItem>
<FormLabel>{t('num')}</FormLabel>
<FormLabel>{t('flowNum')}</FormLabel>
<FormControl>
<NumberInput {...field} className="w-full"></NumberInput>
</FormControl>

View File

@ -134,7 +134,7 @@ export function VariableDialog({
></VariableForm>
<DialogFooter>
<Button type="submit" form={FormId}>
Confirm
{t('modal.okText')}
</Button>
</DialogFooter>
</DialogContent>

View File

@ -61,7 +61,7 @@ export function VariableTable({
const columns: ColumnDef<VariableFormSchemaType>[] = [
{
accessorKey: 'key',
header: 'key',
header: t('flow.key'),
meta: { cellClassName: 'max-w-30' },
cell: ({ row }) => {
const key: string = row.getValue('key');

View File

@ -12,6 +12,7 @@ import {
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { t } from 'i18next';
import { X } from 'lucide-react';
import { ReactNode, useCallback, useMemo } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
@ -107,7 +108,7 @@ export function DynamicOutputForm({ node }: IProps) {
);
})}
<BlockButton onClick={() => append({ name: '', ref: undefined })}>
Add
{t('common.add')}
</BlockButton>
</div>
);
@ -120,7 +121,7 @@ export function VariableTitle({ title }: { title: ReactNode }) {
export function DynamicOutput({ node }: IProps) {
return (
<FormContainer>
<VariableTitle title={'Output'}></VariableTitle>
<VariableTitle title={t('flow.output')}></VariableTitle>
<DynamicOutputForm node={node}></DynamicOutputForm>
</FormContainer>
);

View File

@ -104,7 +104,7 @@ function RetrievalForm({ node }: INextOperatorForm) {
</RAGFlowFormItem>
<KnowledgeBaseFormField showVariable></KnowledgeBaseFormField>
</FormContainer>
<Collapse title={<div>Advanced Settings</div>}>
<Collapse title={<div>{t('flow.advancedSettings')}</div>}>
<FormContainer>
<SimilaritySliderFormField
vectorSimilarityWeightName="keywords_similarity_weight"

View File

@ -9,8 +9,9 @@ import {
} from '@/components/ui/form';
import { MultiSelect } from '@/components/ui/multi-select';
import { RAGFlowSelect } from '@/components/ui/select';
import { buildOptions } from '@/utils/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from 'i18next';
import { toLower } from 'lodash';
import { memo, useCallback, useMemo } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { z } from 'zod';
@ -28,7 +29,7 @@ import { useValues } from './use-values';
import { useWatchFormChange } from './use-watch-form-change';
const DelimiterOptions = Object.entries(StringTransformDelimiter).map(
([key, val]) => ({ label: key, value: val }),
([key, val]) => ({ label: t('flow.' + toLower(key)), value: val }),
);
function StringTransformForm({ node }: INextOperatorForm) {
@ -84,11 +85,13 @@ function StringTransformForm({ node }: INextOperatorForm) {
name="method"
render={({ field }) => (
<FormItem>
<FormLabel>method</FormLabel>
<FormLabel>{t('flow.method')}</FormLabel>
<FormControl>
<RAGFlowSelect
{...field}
options={buildOptions(StringTransformMethod)}
options={Object.values(StringTransformMethod).map(
(val) => ({ label: t('flow.' + val), value: val }),
)}
onChange={(value) => {
handleMethodChange(value);
field.onChange(value);
@ -111,7 +114,7 @@ function StringTransformForm({ node }: INextOperatorForm) {
name="script"
render={({ field }) => (
<FormItem>
<FormLabel>script</FormLabel>
<FormLabel>{t('flow.script')}</FormLabel>
<FormControl>
<PromptEditor {...field} showToolbar={false}></PromptEditor>
</FormControl>
@ -125,7 +128,7 @@ function StringTransformForm({ node }: INextOperatorForm) {
name="delimiters"
render={({ field }) => (
<FormItem>
<FormLabel>delimiters</FormLabel>
<FormLabel>{t('flow.delimiters')}</FormLabel>
<FormControl>
{isSplit ? (
<MultiSelect

View File

@ -15,6 +15,7 @@ import { Separator } from '@/components/ui/separator';
import { Textarea } from '@/components/ui/textarea';
import { cn } from '@/lib/utils';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from 'i18next';
import { toLower } from 'lodash';
import { X } from 'lucide-react';
import { memo, useCallback, useMemo } from 'react';
@ -197,7 +198,7 @@ function ConditionCards({
className="mt-6"
onClick={() => append({ operator: switchOperatorOptions[0].value })}
>
Add
{t('common.add')}
</BlockButton>
</div>
</section>
@ -268,7 +269,7 @@ function SwitchForm({ node }: IOperatorForm) {
className="-translate-y-1"
onClick={() => remove(index)}
>
Remove <X />
{t('common.remove')} <X />
</Button>
)}
</div>
@ -317,7 +318,7 @@ function SwitchForm({ node }: IOperatorForm) {
})
}
>
Add
{t('common.add')}
</BlockButton>
</FormWrapper>
</Form>

View File

@ -10,6 +10,7 @@ import {
import { RAGFlowSelect } from '@/components/ui/select';
import { buildOptions } from '@/utils/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from 'i18next';
import { memo } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
@ -79,12 +80,12 @@ function TavilyExtractForm({ node }: INextOperatorForm) {
name="extract_depth"
render={({ field }) => (
<FormItem>
<FormLabel>Extract Depth</FormLabel>
<FormLabel>{t('flow.extractDepth')}</FormLabel>
<FormControl>
<RAGFlowSelect
placeholder="shadcn"
{...field}
options={buildOptions(TavilyExtractDepth)}
options={buildOptions(TavilyExtractDepth, t, 'flow')}
/>
</FormControl>
<FormMessage />
@ -96,7 +97,7 @@ function TavilyExtractForm({ node }: INextOperatorForm) {
name="format"
render={({ field }) => (
<FormItem>
<FormLabel>Format</FormLabel>
<FormLabel>{t('flow.format')}</FormLabel>
<FormControl>
<RAGFlowSelect
placeholder="shadcn"

View File

@ -7,6 +7,7 @@ import {
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { t } from 'i18next';
import { X } from 'lucide-react';
import { ReactNode } from 'react';
import { useFieldArray, useFormContext } from 'react-hook-form';
@ -51,7 +52,9 @@ export const DynamicDomain = ({ name, label }: DynamicDomainProps) => {
))}
</div>
<FormMessage />
<BlockButton onClick={() => append({ value: '' })}>Add</BlockButton>
<BlockButton onClick={() => append({ value: '' })}>
{t('common.add')}
</BlockButton>
</FormItem>
);
};

View File

@ -12,6 +12,7 @@ import { RAGFlowSelect } from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { buildOptions } from '@/utils/form';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from 'i18next';
import { memo } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
@ -74,12 +75,12 @@ function TavilyForm({ node }: INextOperatorForm) {
name="search_depth"
render={({ field }) => (
<FormItem>
<FormLabel>Search Depth</FormLabel>
<FormLabel>{t('flow.searchDepth')}</FormLabel>
<FormControl>
<RAGFlowSelect
placeholder="shadcn"
{...field}
options={buildOptions(TavilySearchDepth)}
options={buildOptions(TavilySearchDepth, t, 'flow')}
/>
</FormControl>
<FormMessage />
@ -91,12 +92,12 @@ function TavilyForm({ node }: INextOperatorForm) {
name="topic"
render={({ field }) => (
<FormItem>
<FormLabel>TavilyTopic</FormLabel>
<FormLabel>{t('flow.tavilyTopic')}</FormLabel>
<FormControl>
<RAGFlowSelect
placeholder="shadcn"
{...field}
options={buildOptions(TavilyTopic)}
options={buildOptions(TavilyTopic, t, 'flow')}
/>
</FormControl>
<FormMessage />
@ -108,7 +109,7 @@ function TavilyForm({ node }: INextOperatorForm) {
name="max_results"
render={({ field }) => (
<FormItem>
<FormLabel>Max Results</FormLabel>
<FormLabel>{t('flow.maxResults')}</FormLabel>
<FormControl>
<Input type={'number'} {...field}></Input>
</FormControl>
@ -121,7 +122,7 @@ function TavilyForm({ node }: INextOperatorForm) {
name="days"
render={({ field }) => (
<FormItem>
<FormLabel>Days</FormLabel>
<FormLabel>{t('flow.days')}</FormLabel>
<FormControl>
<Input type={'number'} {...field}></Input>
</FormControl>
@ -134,7 +135,7 @@ function TavilyForm({ node }: INextOperatorForm) {
name="include_answer"
render={({ field }) => (
<FormItem>
<FormLabel>Include Answer</FormLabel>
<FormLabel>{t('flow.includeAnswer')}</FormLabel>
<FormControl>
<Switch
checked={field.value}
@ -150,7 +151,7 @@ function TavilyForm({ node }: INextOperatorForm) {
name="include_raw_content"
render={({ field }) => (
<FormItem>
<FormLabel>Include Raw Content</FormLabel>
<FormLabel>{t('flow.includeRawContent')}</FormLabel>
<FormControl>
<Switch
checked={field.value}
@ -166,7 +167,7 @@ function TavilyForm({ node }: INextOperatorForm) {
name="include_images"
render={({ field }) => (
<FormItem>
<FormLabel>Include Images</FormLabel>
<FormLabel>{t('flow.includeImages')}</FormLabel>
<FormControl>
<Switch
checked={field.value}
@ -182,7 +183,7 @@ function TavilyForm({ node }: INextOperatorForm) {
name="include_image_descriptions"
render={({ field }) => (
<FormItem>
<FormLabel>Include Image Descriptions</FormLabel>
<FormLabel>{t('flow.includeImageDescriptions')}</FormLabel>
<FormControl>
<Switch
checked={field.value}
@ -195,11 +196,11 @@ function TavilyForm({ node }: INextOperatorForm) {
/>
<DynamicDomain
name="include_domains"
label={'Include Domains'}
label={t('flow.includeDomains')}
></DynamicDomain>
<DynamicDomain
name="exclude_domains"
label={'Exclude Domains'}
label={t('flow.ExcludeDomains')}
></DynamicDomain>
</FormContainer>
</FormWrapper>

View File

@ -8,6 +8,7 @@ import { TopNFormField } from '@/components/top-n-item';
import { Form } from '@/components/ui/form';
import { UseKnowledgeGraphFormField } from '@/components/use-knowledge-graph-item';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from 'i18next';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { DescriptionField } from '../../components/description-field';
@ -41,7 +42,7 @@ const RetrievalForm = () => {
<DescriptionField></DescriptionField>
<KnowledgeBaseFormField showVariable></KnowledgeBaseFormField>
</FormContainer>
<Collapse title={<div>Advanced Settings</div>}>
<Collapse title={<div>{t('flow.advancedSettings')}</div>}>
<FormContainer>
<SimilaritySliderFormField
vectorSimilarityWeightName="keywords_similarity_weight"

View File

@ -86,7 +86,7 @@ function UserFillUpForm({ node }: INextOperatorForm) {
render={({ field }) => (
<FormItem>
<FormLabel tooltip={t('flow.openingSwitchTip')}>
Guiding Question
{t('flow.guidingQuestion')}
</FormLabel>
<FormControl>
<Switch
@ -104,7 +104,9 @@ function UserFillUpForm({ node }: INextOperatorForm) {
name={'tips'}
render={({ field }) => (
<FormItem>
<FormLabel tooltip={t('chat.setAnOpenerTip')}>Message</FormLabel>
<FormLabel tooltip={t('chat.setAnOpenerTip')}>
{t('flow.msg')}
</FormLabel>
<FormControl>
<Textarea
rows={5}

View File

@ -1,6 +1,7 @@
import { useFetchModelId } from '@/hooks/logic-hooks';
import { Connection, Node, Position, ReactFlowInstance } from '@xyflow/react';
import humanId from 'human-id';
import { t } from 'i18next';
import { lowerFirst } from 'lodash';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@ -122,8 +123,8 @@ export const useInitializeOperatorParams = () => {
if (isBottomSubAgent(operatorName, position)) {
return {
...initialValues,
description: 'This is an agent for a specific task.',
user_prompt: 'This is the order you need to send to the agent.',
description: t('flow.descriptionMessage'),
user_prompt: t('flow.userPromptDefaultValue'),
};
}

View File

@ -3,6 +3,7 @@ import { useFetchAgent } from '@/hooks/use-agent-request';
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { Edge } from '@xyflow/react';
import { DefaultOptionType } from 'antd/es/select';
import { t } from 'i18next';
import { isEmpty } from 'lodash';
import get from 'lodash/get';
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
@ -145,7 +146,7 @@ export function useBuildBeginVariableOptions() {
const options = useMemo(() => {
return [
{
label: <span>Begin Input</span>,
label: <span>{t('flow.beginInput')}</span>,
title: 'Begin Input',
options: inputs.map((x) => ({
label: x.name,

View File

@ -11,6 +11,7 @@ import { useSetModalState } from '@/hooks/common-hooks';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useFetchAgentTemplates, useSetAgent } from '@/hooks/use-agent-request';
import { IFlowTemplate } from '@/interfaces/database/flow';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { CreateAgentDialog } from './create-agent-dialog';
@ -83,13 +84,16 @@ export default function AgentTemplates() {
selectMenuItem?.toLocaleLowerCase() || index === 0,
);
}, [selectMenuItem, templateList]);
return (
<section>
<PageHeader>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink onClick={navigateToAgents}>Agent</BreadcrumbLink>
<BreadcrumbLink onClick={navigateToAgents}>
{t('flow.agent')}
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>

View File

@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button';
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useFetchAgentListByPage } from '@/hooks/use-agent-request';
import { t } from 'i18next';
import { pick } from 'lodash';
import { Plus } from 'lucide-react';
import { useCallback } from 'react';
@ -41,7 +42,7 @@ export default function Agents() {
>
<Button onClick={navigateToAgentTemplates}>
<Plus className="mr-2 h-4 w-4" />
Create Agent
{t('flow.createGraph')}
</Button>
</ListFilterBar>
</div>

View File

@ -50,9 +50,10 @@ export function DatasetActionCell({
}, [record, showRenameModal]);
return (
<section className="flex gap-4 items-center text-text-sub-title-invert">
<section className="flex gap-4 items-center text-text-sub-title-invert opacity-0 group-hover:opacity-100 transition-opacity">
<Button
variant={'ghost'}
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
disabled={isRunning}
onClick={handleRename}
@ -61,7 +62,12 @@ export function DatasetActionCell({
</Button>
<HoverCard>
<HoverCardTrigger>
<Button variant="ghost" disabled={isRunning} size={'sm'}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
disabled={isRunning}
size={'sm'}
>
<Eye />
</Button>
</HoverCardTrigger>
@ -88,7 +94,8 @@ export function DatasetActionCell({
{isVirtualDocument || (
<Button
variant={'ghost'}
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
onClick={onDownloadDocument}
disabled={isRunning}
size={'sm'}
@ -97,7 +104,12 @@ export function DatasetActionCell({
</Button>
)}
<ConfirmDeleteDialog onOk={handleRemove}>
<Button variant={'ghost'} size={'sm'} disabled={isRunning}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
disabled={isRunning}
>
<Trash2 />
</Button>
</ConfirmDeleteDialog>

View File

@ -119,7 +119,7 @@ export function DatasetTable({
return (
<div className="w-full">
<Table rootClassName="max-h-[82vh]">
<Table rootClassName="max-h-[calc(100vh-222px)]">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
@ -144,6 +144,7 @@ export function DatasetTable({
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
className="group"
>
{row.getVisibleCells().map((cell) => (
<TableCell

View File

@ -15,9 +15,9 @@ import { Progress } from '@/components/ui/progress';
import { Separator } from '@/components/ui/separator';
import { IDocumentInfo } from '@/interfaces/database/document';
import { CircleX, RefreshCw } from 'lucide-react';
import { useCallback } from 'react';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { RunningStatus } from './constant';
import { DocumentType, RunningStatus } from './constant';
import { ParsingCard, PopoverContent } from './parsing-card';
import { UseChangeDocumentParserShowType } from './use-change-document-parser';
import { useHandleRunDocumentByIds } from './use-run-document';
@ -61,6 +61,10 @@ export function ParsingStatusCell({
showSetMetaModal(record);
}, [record, showSetMetaModal]);
const showParse = useMemo(() => {
return record.type !== DocumentType.Virtual;
}, [record]);
return (
<section className="flex gap-8 items-center">
<div className="w-fit flex items-center justify-between">
@ -80,38 +84,42 @@ export function ParsingStatusCell({
</DropdownMenuContent>
</DropdownMenu>
</div>
<ConfirmDeleteDialog
title={t(`knowledgeDetails.redo`, { chunkNum: chunk_num })}
hidden={isZeroChunk || isRunning}
onOk={handleOperationIconClick(true)}
onCancel={handleOperationIconClick(false)}
>
<div
className="cursor-pointer flex items-center gap-3"
onClick={
isZeroChunk || isRunning
? handleOperationIconClick(false)
: () => {}
}
>
<Separator orientation="vertical" className="h-2.5" />
{operationIcon}
</div>
</ConfirmDeleteDialog>
{isParserRunning(run) ? (
<HoverCard>
<HoverCardTrigger asChild>
<div className="flex items-center gap-1">
<Progress value={p} className="h-1 flex-1 min-w-10" />
{p}%
{showParse && (
<>
<ConfirmDeleteDialog
title={t(`knowledgeDetails.redo`, { chunkNum: chunk_num })}
hidden={isZeroChunk || isRunning}
onOk={handleOperationIconClick(true)}
onCancel={handleOperationIconClick(false)}
>
<div
className="cursor-pointer flex items-center gap-3"
onClick={
isZeroChunk || isRunning
? handleOperationIconClick(false)
: () => {}
}
>
<Separator orientation="vertical" className="h-2.5" />
{operationIcon}
</div>
</HoverCardTrigger>
<HoverCardContent className="w-[40vw]">
<PopoverContent record={record}></PopoverContent>
</HoverCardContent>
</HoverCard>
) : (
<ParsingCard record={record}></ParsingCard>
</ConfirmDeleteDialog>
{isParserRunning(run) ? (
<HoverCard>
<HoverCardTrigger asChild>
<div className="flex items-center gap-1">
<Progress value={p} className="h-1 flex-1 min-w-10" />
{p}%
</div>
</HoverCardTrigger>
<HoverCardContent className="w-[40vw]">
<PopoverContent record={record}></PopoverContent>
</HoverCardContent>
</HoverCard>
) : (
<ParsingCard record={record}></ParsingCard>
)}
</>
)}
</section>
);

View File

@ -1,21 +1,19 @@
import {
useKnowledgeBaseId,
useRemoveKnowledgeGraph,
} from '@/hooks/knowledge-hooks';
import { useRemoveKnowledgeGraph } from '@/hooks/knowledge-hooks';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useCallback } from 'react';
import { useNavigate } from 'umi';
import { useParams } from 'umi';
export function useDeleteKnowledgeGraph() {
const { removeKnowledgeGraph, loading } = useRemoveKnowledgeGraph();
const navigate = useNavigate();
const knowledgeBaseId = useKnowledgeBaseId();
const { navigateToDataset } = useNavigatePage();
const { id } = useParams();
const handleDeleteKnowledgeGraph = useCallback(async () => {
const ret = await removeKnowledgeGraph();
if (ret === 0) {
navigate(`/knowledge/dataset?id=${knowledgeBaseId}`);
if (ret === 0 && id) {
navigateToDataset(id)();
}
}, [knowledgeBaseId, navigate, removeKnowledgeGraph]);
}, [id, navigateToDataset, removeKnowledgeGraph]);
return { handleDeleteKnowledgeGraph, loading };
}

View File

@ -61,24 +61,40 @@ export function ActionCell({
}, [record, showMoveFileModal]);
return (
<section className="flex gap-4 items-center text-text-sub-title-invert">
<section className="flex gap-4 items-center text-text-sub-title-invert opacity-0 group-hover:opacity-100 transition-opacity">
<Button
variant="ghost"
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
onClick={handleShowConnectToKnowledgeModal}
>
<Link2 />
</Button>
<Button variant="ghost" size={'sm'} onClick={handleShowMoveFileModal}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
onClick={handleShowMoveFileModal}
>
<FolderInput />
</Button>
<Button variant="ghost" size={'sm'} onClick={handleShowFileRenameModal}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
onClick={handleShowFileRenameModal}
>
<FolderPen />
</Button>
{isFolder || (
<Button variant={'ghost'} size={'sm'} onClick={onDownloadDocument}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
onClick={onDownloadDocument}
>
<ArrowDownToLine />
</Button>
)}
@ -89,7 +105,11 @@ export function ActionCell({
documentName={record.name}
className="text-text-sub-title-invert"
>
<Button variant={'ghost'} size={'sm'}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
>
<Eye />
</Button>
</NewDocumentLink>
@ -97,7 +117,8 @@ export function ActionCell({
{/* <DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size={'sm'}>
<Button variant="transparent"
className="border-none" size={'sm'}>
<EllipsisVertical />
</Button>
</DropdownMenuTrigger>
@ -118,7 +139,11 @@ export function ActionCell({
</DropdownMenuContent>
</DropdownMenu> */}
<ConfirmDeleteDialog>
<Button variant="ghost" size={'sm'}>
<Button
variant="transparent"
className="border-none hover:bg-bg-card text-text-primary"
size={'sm'}
>
<Trash2 />
</Button>
</ConfirmDeleteDialog>

View File

@ -213,6 +213,7 @@ export function FilesTable({
id: 'actions',
header: t('action'),
enableHiding: false,
enablePinning: true,
cell: ({ row }) => {
return (
<ActionCell
@ -259,51 +260,56 @@ export function FilesTable({
});
return (
<div className="w-full">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{loading ? (
<TableSkeleton columnsLength={columns.length}></TableSkeleton>
) : table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className={cell.column.columnDef.meta?.cellClassName}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
<>
<div className="w-full">
<Table rootClassName="max-h-[calc(100vh-242px)] overflow-auto">
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))
) : (
<TableEmpty columnsLength={columns.length}></TableEmpty>
)}
</TableBody>
</Table>
))}
</TableHeader>
<TableBody className="max-h-96 overflow-y-auto">
{loading ? (
<TableSkeleton columnsLength={columns.length}></TableSkeleton>
) : table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
className="group"
>
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className={cell.column.columnDef.meta?.cellClassName}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableEmpty columnsLength={columns.length}></TableEmpty>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end py-4">
<div className="space-x-2">
<RAGFlowPagination
@ -331,6 +337,6 @@ export function FilesTable({
loading={fileRenameLoading}
></RenameDialog>
)}
</div>
</>
);
}

View File

@ -13,8 +13,14 @@ import {
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Textarea } from '@/components/ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { useTranslate } from '@/hooks/common-hooks';
import { useFormContext } from 'react-hook-form';
@ -47,6 +53,9 @@ export default function ChatBasicSetting() {
value={field.value}
onValueChange={field.onChange}
maxFileCount={1}
description={t('photoTip', {
keyPrefix: 'knowledgeConfiguration',
})}
/>
</FormControl>
<FormMessage />

View File

@ -36,6 +36,7 @@ export function SingleChatBox({ controller }: IProps) {
removeMessageById,
stopOutputMessage,
handleUploadFile,
removeFile,
} = useSendMessage(controller);
const { data: userInfo } = useFetchUserInfo();
const { data: currentDialog } = useFetchDialog();
@ -97,6 +98,7 @@ export function SingleChatBox({ controller }: IProps) {
stopOutputMessage={stopOutputMessage}
onUpload={handleUploadFile}
isUploading={isUploading}
removeFile={removeFile}
/>
{visible && (
<PdfDrawer

View File

@ -138,7 +138,7 @@ export const useSendMessage = (controller: AbortController) => {
const { conversationId, isNew } = useGetChatSearchParams();
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { handleUploadFile, fileIds, clearFileIds, isUploading } =
const { handleUploadFile, fileIds, clearFileIds, isUploading, removeFile } =
useUploadFile();
const { send, answer, done } = useSendMessageWithSse(
@ -287,5 +287,6 @@ export const useSendMessage = (controller: AbortController) => {
stopOutputMessage,
handleUploadFile,
isUploading,
removeFile,
};
};

View File

@ -3,16 +3,21 @@ import { useUploadAndParseFile } from '@/hooks/use-chat-request';
import { useCallback, useState } from 'react';
export function useUploadFile() {
const { uploadAndParseFile, loading } = useUploadAndParseFile();
const { uploadAndParseFile, loading, cancel } = useUploadAndParseFile();
const [fileIds, setFileIds] = useState<string[]>([]);
const [fileMap, setFileMap] = useState<Map<File, string>>(new Map());
const handleUploadFile: NonNullable<FileUploadProps['onUpload']> =
useCallback(
async (files) => {
async (files, options) => {
if (Array.isArray(files) && files.length) {
const ret = await uploadAndParseFile(files[0]);
const ret = await uploadAndParseFile({ file: files[0], options });
if (ret.code === 0 && Array.isArray(ret.data)) {
setFileIds((list) => [...list, ...ret.data]);
setFileMap((map) => {
map.set(files[0], ret.data[0]);
return map;
});
}
}
},
@ -21,7 +26,28 @@ export function useUploadFile() {
const clearFileIds = useCallback(() => {
setFileIds([]);
setFileMap(new Map());
}, []);
return { handleUploadFile, clearFileIds, fileIds, isUploading: loading };
const removeFile = useCallback(
(file: File) => {
if (loading) {
cancel();
return;
}
const id = fileMap.get(file);
if (id) {
setFileIds((list) => list.filter((item) => item !== id));
}
},
[cancel, fileMap, loading],
);
return {
handleUploadFile,
clearFileIds,
fileIds,
isUploading: loading,
removeFile,
};
}

View File

@ -151,6 +151,7 @@ const routes = [
path: Routes.Root,
layout: false,
component: '@/layouts/next',
wrappers: ['@/wrappers/auth'],
routes: [
{
path: Routes.Root,

View File

@ -1,4 +1,5 @@
import { variableEnabledFieldMap } from '@/constants/chat';
import { TFunction } from 'i18next';
import omit from 'lodash/omit';
// chat model setting and generate operator
@ -27,7 +28,17 @@ export const removeUselessFieldsFromValues = (values: any, prefix?: string) => {
return nextValues;
};
export function buildOptions(data: Record<string, any>) {
export function buildOptions(
data: Record<string, any>,
t?: TFunction<['translation', ...string[]], undefined>,
prefix?: string,
) {
if (t) {
return Object.values(data).map((val) => ({
label: t(`${prefix ? prefix + '.' : ''}${val.toLowerCase()}`),
value: val,
}));
}
return Object.values(data).map((val) => ({ label: val, value: val }));
}

View File

@ -55,7 +55,7 @@ module.exports = {
'input-border': 'var(--input-border)',
/* design colors */
'bg-title': 'var(--bg-title)',
'bg-base': 'var(--bg-base)',
'bg-card': 'var(--bg-card)',
'bg-component': 'var(--bg-component)',

View File

@ -91,6 +91,8 @@
--input-border: rgba(22, 22, 24, 0.2);
--metallic: #46464a;
--bg-title: #f6f6f7;
/* design colors */
--bg-base: #ffffff;
@ -235,6 +237,8 @@
--input-border: rgba(255, 255, 255, 0.2);
--metallic: #fafafa;
--bg-title: #38383a;
/* design colors */
--bg-base: #161618;