mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-03 09:05:07 +08:00
Compare commits
10 Commits
add8c63458
...
v0.22.1
| Author | SHA1 | Date | |
|---|---|---|---|
| cfdccebb17 | |||
| 980a883033 | |||
| 02d429f0ca | |||
| 9c24d5d44a | |||
| 0cc5d7a8a6 | |||
| c43bf1dcf5 | |||
| f76b8279dd | |||
| db5ec89dc5 | |||
| 1c201c4d54 | |||
| ba78d0f0c2 |
@ -192,7 +192,8 @@ releases! 🌟
|
|||||||
```bash
|
```bash
|
||||||
$ cd ragflow/docker
|
$ cd ragflow/docker
|
||||||
|
|
||||||
# Optional: use a stable tag (see releases: https://github.com/infiniflow/ragflow/releases), e.g.: git checkout v0.22.1
|
# git checkout v0.22.1
|
||||||
|
# Optional: use a stable tag (see releases: https://github.com/infiniflow/ragflow/releases)
|
||||||
# This steps ensures the **entrypoint.sh** file in the code matches the Docker image version.
|
# This steps ensures the **entrypoint.sh** file in the code matches the Docker image version.
|
||||||
|
|
||||||
# Use CPU for DeepDoc tasks:
|
# Use CPU for DeepDoc tasks:
|
||||||
|
|||||||
@ -192,7 +192,8 @@ Coba demo kami di [https://demo.ragflow.io](https://demo.ragflow.io).
|
|||||||
```bash
|
```bash
|
||||||
$ cd ragflow/docker
|
$ cd ragflow/docker
|
||||||
|
|
||||||
# Opsional: gunakan tag stabil (lihat releases: https://github.com/infiniflow/ragflow/releases), contoh: git checkout v0.22.1
|
# git checkout v0.22.1
|
||||||
|
# Opsional: gunakan tag stabil (lihat releases: https://github.com/infiniflow/ragflow/releases)
|
||||||
# This steps ensures the **entrypoint.sh** file in the code matches the Docker image version.
|
# This steps ensures the **entrypoint.sh** file in the code matches the Docker image version.
|
||||||
|
|
||||||
# Use CPU for DeepDoc tasks:
|
# Use CPU for DeepDoc tasks:
|
||||||
|
|||||||
@ -172,7 +172,8 @@
|
|||||||
```bash
|
```bash
|
||||||
$ cd ragflow/docker
|
$ cd ragflow/docker
|
||||||
|
|
||||||
# 任意: 安定版タグを利用 (一覧: https://github.com/infiniflow/ragflow/releases) 例: git checkout v0.22.1
|
# git checkout v0.22.1
|
||||||
|
# 任意: 安定版タグを利用 (一覧: https://github.com/infiniflow/ragflow/releases)
|
||||||
# この手順は、コード内の entrypoint.sh ファイルが Docker イメージのバージョンと一致していることを確認します。
|
# この手順は、コード内の entrypoint.sh ファイルが Docker イメージのバージョンと一致していることを確認します。
|
||||||
|
|
||||||
# Use CPU for DeepDoc tasks:
|
# Use CPU for DeepDoc tasks:
|
||||||
|
|||||||
@ -174,7 +174,8 @@
|
|||||||
```bash
|
```bash
|
||||||
$ cd ragflow/docker
|
$ cd ragflow/docker
|
||||||
|
|
||||||
# Optional: use a stable tag (see releases: https://github.com/infiniflow/ragflow/releases), e.g.: git checkout v0.22.1
|
# git checkout v0.22.1
|
||||||
|
# Optional: use a stable tag (see releases: https://github.com/infiniflow/ragflow/releases)
|
||||||
# 이 단계는 코드의 entrypoint.sh 파일이 Docker 이미지 버전과 일치하도록 보장합니다.
|
# 이 단계는 코드의 entrypoint.sh 파일이 Docker 이미지 버전과 일치하도록 보장합니다.
|
||||||
|
|
||||||
# Use CPU for DeepDoc tasks:
|
# Use CPU for DeepDoc tasks:
|
||||||
|
|||||||
@ -192,7 +192,8 @@ Experimente nossa demo em [https://demo.ragflow.io](https://demo.ragflow.io).
|
|||||||
```bash
|
```bash
|
||||||
$ cd ragflow/docker
|
$ cd ragflow/docker
|
||||||
|
|
||||||
# Opcional: use uma tag estável (veja releases: https://github.com/infiniflow/ragflow/releases), ex.: git checkout v0.22.1
|
# git checkout v0.22.1
|
||||||
|
# Opcional: use uma tag estável (veja releases: https://github.com/infiniflow/ragflow/releases)
|
||||||
# Esta etapa garante que o arquivo entrypoint.sh no código corresponda à versão da imagem do Docker.
|
# Esta etapa garante que o arquivo entrypoint.sh no código corresponda à versão da imagem do Docker.
|
||||||
|
|
||||||
# Use CPU for DeepDoc tasks:
|
# Use CPU for DeepDoc tasks:
|
||||||
|
|||||||
@ -191,7 +191,8 @@
|
|||||||
```bash
|
```bash
|
||||||
$ cd ragflow/docker
|
$ cd ragflow/docker
|
||||||
|
|
||||||
# 可選:使用穩定版標籤(查看發佈:https://github.com/infiniflow/ragflow/releases),例:git checkout v0.22.1
|
# git checkout v0.22.1
|
||||||
|
# 可選:使用穩定版標籤(查看發佈:https://github.com/infiniflow/ragflow/releases)
|
||||||
# 此步驟確保程式碼中的 entrypoint.sh 檔案與 Docker 映像版本一致。
|
# 此步驟確保程式碼中的 entrypoint.sh 檔案與 Docker 映像版本一致。
|
||||||
|
|
||||||
# Use CPU for DeepDoc tasks:
|
# Use CPU for DeepDoc tasks:
|
||||||
|
|||||||
@ -192,7 +192,8 @@
|
|||||||
```bash
|
```bash
|
||||||
$ cd ragflow/docker
|
$ cd ragflow/docker
|
||||||
|
|
||||||
# 可选:使用稳定版本标签(查看发布:https://github.com/infiniflow/ragflow/releases),例如:git checkout v0.22.1
|
# git checkout v0.22.1
|
||||||
|
# 可选:使用稳定版本标签(查看发布:https://github.com/infiniflow/ragflow/releases)
|
||||||
# 这一步确保代码中的 entrypoint.sh 文件与 Docker 镜像的版本保持一致。
|
# 这一步确保代码中的 entrypoint.sh 文件与 Docker 镜像的版本保持一致。
|
||||||
|
|
||||||
# Use CPU for DeepDoc tasks:
|
# Use CPU for DeepDoc tasks:
|
||||||
|
|||||||
@ -31,7 +31,7 @@ from common.constants import ActiveEnum, StatusEnum
|
|||||||
from api.utils.crypt import decrypt
|
from api.utils.crypt import decrypt
|
||||||
from common.misc_utils import get_uuid
|
from common.misc_utils import get_uuid
|
||||||
from common.time_utils import current_timestamp, datetime_format, get_format_time
|
from common.time_utils import current_timestamp, datetime_format, get_format_time
|
||||||
from common.connection_utils import construct_response
|
from common.connection_utils import sync_construct_response
|
||||||
from common import settings
|
from common import settings
|
||||||
|
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ def login_admin(email: str, password: str):
|
|||||||
user.last_login_time = get_format_time()
|
user.last_login_time = get_format_time()
|
||||||
user.save()
|
user.save()
|
||||||
msg = "Welcome back!"
|
msg = "Welcome back!"
|
||||||
return construct_response(data=resp, auth=user.get_id(), message=msg)
|
return sync_construct_response(data=resp, auth=user.get_id(), message=msg)
|
||||||
|
|
||||||
|
|
||||||
def check_admin(username: str, password: str):
|
def check_admin(username: str, password: str):
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
@ -20,9 +21,7 @@ import uuid
|
|||||||
from html import escape
|
from html import escape
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import flask
|
from quart import request, make_response
|
||||||
import trio
|
|
||||||
from quart import request
|
|
||||||
from google_auth_oauthlib.flow import Flow
|
from google_auth_oauthlib.flow import Flow
|
||||||
|
|
||||||
from api.db import InputType
|
from api.db import InputType
|
||||||
@ -59,7 +58,7 @@ async def set_connector():
|
|||||||
}
|
}
|
||||||
ConnectorService.save(**conn)
|
ConnectorService.save(**conn)
|
||||||
|
|
||||||
await trio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
e, conn = ConnectorService.get_by_id(req["id"])
|
e, conn = ConnectorService.get_by_id(req["id"])
|
||||||
|
|
||||||
return get_json_result(data=conn.to_dict())
|
return get_json_result(data=conn.to_dict())
|
||||||
@ -147,7 +146,7 @@ def _get_web_client_config(credentials: dict[str, Any]) -> dict[str, Any]:
|
|||||||
return {"web": web_section}
|
return {"web": web_section}
|
||||||
|
|
||||||
|
|
||||||
def _render_web_oauth_popup(flow_id: str, success: bool, message: str):
|
async def _render_web_oauth_popup(flow_id: str, success: bool, message: str):
|
||||||
status = "success" if success else "error"
|
status = "success" if success else "error"
|
||||||
auto_close = "window.close();" if success else ""
|
auto_close = "window.close();" if success else ""
|
||||||
escaped_message = escape(message)
|
escaped_message = escape(message)
|
||||||
@ -165,7 +164,7 @@ def _render_web_oauth_popup(flow_id: str, success: bool, message: str):
|
|||||||
payload_json=payload_json,
|
payload_json=payload_json,
|
||||||
auto_close=auto_close,
|
auto_close=auto_close,
|
||||||
)
|
)
|
||||||
response = flask.make_response(html, 200)
|
response = await make_response(html, 200)
|
||||||
response.headers["Content-Type"] = "text/html; charset=utf-8"
|
response.headers["Content-Type"] = "text/html; charset=utf-8"
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@ -232,31 +231,31 @@ async def start_google_drive_web_oauth():
|
|||||||
|
|
||||||
|
|
||||||
@manager.route("/google-drive/oauth/web/callback", methods=["GET"]) # noqa: F821
|
@manager.route("/google-drive/oauth/web/callback", methods=["GET"]) # noqa: F821
|
||||||
def google_drive_web_oauth_callback():
|
async def google_drive_web_oauth_callback():
|
||||||
state_id = request.args.get("state")
|
state_id = request.args.get("state")
|
||||||
error = request.args.get("error")
|
error = request.args.get("error")
|
||||||
error_description = request.args.get("error_description") or error
|
error_description = request.args.get("error_description") or error
|
||||||
|
|
||||||
if not state_id:
|
if not state_id:
|
||||||
return _render_web_oauth_popup("", False, "Missing OAuth state parameter.")
|
return await _render_web_oauth_popup("", False, "Missing OAuth state parameter.")
|
||||||
|
|
||||||
state_cache = REDIS_CONN.get(_web_state_cache_key(state_id))
|
state_cache = REDIS_CONN.get(_web_state_cache_key(state_id))
|
||||||
if not state_cache:
|
if not state_cache:
|
||||||
return _render_web_oauth_popup(state_id, False, "Authorization session expired. Please restart from the main window.")
|
return await _render_web_oauth_popup(state_id, False, "Authorization session expired. Please restart from the main window.")
|
||||||
|
|
||||||
state_obj = json.loads(state_cache)
|
state_obj = json.loads(state_cache)
|
||||||
client_config = state_obj.get("client_config")
|
client_config = state_obj.get("client_config")
|
||||||
if not client_config:
|
if not client_config:
|
||||||
REDIS_CONN.delete(_web_state_cache_key(state_id))
|
REDIS_CONN.delete(_web_state_cache_key(state_id))
|
||||||
return _render_web_oauth_popup(state_id, False, "Authorization session was invalid. Please retry.")
|
return await _render_web_oauth_popup(state_id, False, "Authorization session was invalid. Please retry.")
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
REDIS_CONN.delete(_web_state_cache_key(state_id))
|
REDIS_CONN.delete(_web_state_cache_key(state_id))
|
||||||
return _render_web_oauth_popup(state_id, False, error_description or "Authorization was cancelled.")
|
return await _render_web_oauth_popup(state_id, False, error_description or "Authorization was cancelled.")
|
||||||
|
|
||||||
code = request.args.get("code")
|
code = request.args.get("code")
|
||||||
if not code:
|
if not code:
|
||||||
return _render_web_oauth_popup(state_id, False, "Missing authorization code from Google.")
|
return await _render_web_oauth_popup(state_id, False, "Missing authorization code from Google.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
flow = Flow.from_client_config(client_config, scopes=GOOGLE_SCOPES[DocumentSource.GOOGLE_DRIVE])
|
flow = Flow.from_client_config(client_config, scopes=GOOGLE_SCOPES[DocumentSource.GOOGLE_DRIVE])
|
||||||
@ -265,7 +264,7 @@ def google_drive_web_oauth_callback():
|
|||||||
except Exception as exc: # pragma: no cover - defensive
|
except Exception as exc: # pragma: no cover - defensive
|
||||||
logging.exception("Failed to exchange Google OAuth code: %s", exc)
|
logging.exception("Failed to exchange Google OAuth code: %s", exc)
|
||||||
REDIS_CONN.delete(_web_state_cache_key(state_id))
|
REDIS_CONN.delete(_web_state_cache_key(state_id))
|
||||||
return _render_web_oauth_popup(state_id, False, "Failed to exchange tokens with Google. Please retry.")
|
return await _render_web_oauth_popup(state_id, False, "Failed to exchange tokens with Google. Please retry.")
|
||||||
|
|
||||||
creds_json = flow.credentials.to_json()
|
creds_json = flow.credentials.to_json()
|
||||||
result_payload = {
|
result_payload = {
|
||||||
@ -275,7 +274,7 @@ def google_drive_web_oauth_callback():
|
|||||||
REDIS_CONN.set_obj(_web_result_cache_key(state_id), result_payload, WEB_FLOW_TTL_SECS)
|
REDIS_CONN.set_obj(_web_result_cache_key(state_id), result_payload, WEB_FLOW_TTL_SECS)
|
||||||
REDIS_CONN.delete(_web_state_cache_key(state_id))
|
REDIS_CONN.delete(_web_state_cache_key(state_id))
|
||||||
|
|
||||||
return _render_web_oauth_popup(state_id, True, "Authorization completed successfully.")
|
return await _render_web_oauth_popup(state_id, True, "Authorization completed successfully.")
|
||||||
|
|
||||||
|
|
||||||
@manager.route("/google-drive/oauth/web/result", methods=["POST"]) # noqa: F821
|
@manager.route("/google-drive/oauth/web/result", methods=["POST"]) # noqa: F821
|
||||||
|
|||||||
@ -886,6 +886,7 @@ async def check_embedding():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
v, _ = emb_mdl.encode([title, txt_in])
|
v, _ = emb_mdl.encode([title, txt_in])
|
||||||
|
assert len(v[1]) == len(ck["vector"]), f"The dimension ({len(v[1])}) of given embedding model is different from the original ({len(ck['vector'])})"
|
||||||
sim_content = _cos_sim(v[1], ck["vector"])
|
sim_content = _cos_sim(v[1], ck["vector"])
|
||||||
title_w = 0.1
|
title_w = 0.1
|
||||||
qv_mix = title_w * v[0] + (1 - title_w) * v[1]
|
qv_mix = title_w * v[0] + (1 - title_w) * v[1]
|
||||||
@ -895,8 +896,8 @@ async def check_embedding():
|
|||||||
if sim_mix > sim:
|
if sim_mix > sim:
|
||||||
sim = sim_mix
|
sim = sim_mix
|
||||||
mode = "title+content"
|
mode = "title+content"
|
||||||
except Exception:
|
except Exception as e:
|
||||||
return get_error_data_result(message="embedding failure")
|
return get_error_data_result(message=f"Embedding failure. {e}")
|
||||||
|
|
||||||
eff_sims.append(sim)
|
eff_sims.append(sim)
|
||||||
results.append({
|
results.append({
|
||||||
|
|||||||
@ -33,7 +33,6 @@ from api.db.services.task_service import TaskService
|
|||||||
from api.utils.file_utils import filename_type, read_potential_broken_pdf, thumbnail_img, sanitize_path
|
from api.utils.file_utils import filename_type, read_potential_broken_pdf, thumbnail_img, sanitize_path
|
||||||
from rag.llm.cv_model import GptV4
|
from rag.llm.cv_model import GptV4
|
||||||
from common import settings
|
from common import settings
|
||||||
from api.apps import current_user
|
|
||||||
|
|
||||||
|
|
||||||
class FileService(CommonService):
|
class FileService(CommonService):
|
||||||
@ -184,6 +183,7 @@ class FileService(CommonService):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@DB.connection_context()
|
@DB.connection_context()
|
||||||
def create_folder(cls, file, parent_id, name, count):
|
def create_folder(cls, file, parent_id, name, count):
|
||||||
|
from api.apps import current_user
|
||||||
# Recursively create folder structure
|
# Recursively create folder structure
|
||||||
# Args:
|
# Args:
|
||||||
# file: Current file object
|
# file: Current file object
|
||||||
@ -508,6 +508,7 @@ class FileService(CommonService):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def parse(filename, blob, img_base64=True, tenant_id=None):
|
def parse(filename, blob, img_base64=True, tenant_id=None):
|
||||||
from rag.app import audio, email, naive, picture, presentation
|
from rag.app import audio, email, naive, picture, presentation
|
||||||
|
from api.apps import current_user
|
||||||
|
|
||||||
def dummy(prog=None, msg=""):
|
def dummy(prog=None, msg=""):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@ -120,3 +120,23 @@ async def construct_response(code=RetCode.SUCCESS, message="success", data=None,
|
|||||||
response.headers["Access-Control-Allow-Headers"] = "*"
|
response.headers["Access-Control-Allow-Headers"] = "*"
|
||||||
response.headers["Access-Control-Expose-Headers"] = "Authorization"
|
response.headers["Access-Control-Expose-Headers"] = "Authorization"
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def sync_construct_response(code=RetCode.SUCCESS, message="success", data=None, auth=None):
|
||||||
|
import flask
|
||||||
|
result_dict = {"code": code, "message": message, "data": data}
|
||||||
|
response_dict = {}
|
||||||
|
for key, value in result_dict.items():
|
||||||
|
if value is None and key != "code":
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
response_dict[key] = value
|
||||||
|
response = flask.make_response(flask.jsonify(response_dict))
|
||||||
|
if auth:
|
||||||
|
response.headers["Authorization"] = auth
|
||||||
|
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
|
response.headers["Access-Control-Allow-Method"] = "*"
|
||||||
|
response.headers["Access-Control-Allow-Headers"] = "*"
|
||||||
|
response.headers["Access-Control-Allow-Headers"] = "*"
|
||||||
|
response.headers["Access-Control-Expose-Headers"] = "Authorization"
|
||||||
|
return response
|
||||||
|
|||||||
@ -4848,7 +4848,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "JieKou.AI",
|
"name": "Jiekou.AI",
|
||||||
"logo": "",
|
"logo": "",
|
||||||
"tags": "LLM,TEXT EMBEDDING,TEXT RE-RANK",
|
"tags": "LLM,TEXT EMBEDDING,TEXT RE-RANK",
|
||||||
"status": "1",
|
"status": "1",
|
||||||
|
|||||||
@ -7,25 +7,29 @@ slug: /release_notes
|
|||||||
|
|
||||||
Key features, improvements and bug fixes in the latest releases.
|
Key features, improvements and bug fixes in the latest releases.
|
||||||
|
|
||||||
|
## v0.22.1
|
||||||
|
|
||||||
Released on November 19, 2025.
|
Released on November 19, 2025.
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
- **Knowledge Base Embedding Models**: Fixed an issue where knowledge base embedding models became unavailable since v0.22.0.
|
|
||||||
- **Document Parsing**: Fixing image merging issues.
|
|
||||||
- **Chat History**: Fixed a bug where images and text were not correctly displayed together in historical chat records.
|
|
||||||
|
|
||||||
### Improvements
|
### Improvements
|
||||||
|
|
||||||
- **Agent**:
|
- Agent:
|
||||||
- Added support for exporting Agent outputs in Word formats.
|
- Supports exporting Agent outputs in Word or Markdown formats.
|
||||||
- Introduced new list operations and enhanced the **Variable Aggregator** component capabilities.
|
- Adds a **List operations** component.
|
||||||
- **Data Sources**:
|
- Adds a **Variable aggregator** component.
|
||||||
- Expanded data source support to include S3-compatible storage services.
|
- Data sources:
|
||||||
- Added new integration support for JIRA.
|
- Supports S3-compatible data sources, e.g., MinIO.
|
||||||
- **User Profile**: Optimized and beautified the layout of the personal center interface.
|
- Adds data synchronization with JIRA.
|
||||||
|
- Continues the redesign of the **Profile** page layouts.
|
||||||
|
- Upgrades the Flask web framework from synchronous to asynchronous, increasing concurrency and preventing blocking issues caused when requesting upstream LLM services.
|
||||||
|
|
||||||
### Support new models
|
### Fixed issues
|
||||||
|
|
||||||
|
- A v0.22.0 issue: Users failed to parse uploaded files or switch embedding model in a dataset containing parsed files using a built-in model from a `-full` RAGFlow edition.
|
||||||
|
- Image concatenated in Word documents. [#11310](https://github.com/infiniflow/ragflow/pull/11310)
|
||||||
|
- Mixed images and text were not correctly displayed in the chat history.
|
||||||
|
|
||||||
|
### Newly supported models
|
||||||
|
|
||||||
- Gemini 3 Pro Preview
|
- Gemini 3 Pro Preview
|
||||||
|
|
||||||
@ -99,7 +103,7 @@ Released on October 15, 2025.
|
|||||||
- Redesigns RAGFlow's Login and Registration pages.
|
- Redesigns RAGFlow's Login and Registration pages.
|
||||||
- Upgrades RAGFlow's document engine Infinity to v0.6.0.
|
- Upgrades RAGFlow's document engine Infinity to v0.6.0.
|
||||||
|
|
||||||
### Support new models
|
### Newly supported models
|
||||||
|
|
||||||
- Tongyi Qwen 3 series
|
- Tongyi Qwen 3 series
|
||||||
- Claude Sonnet 4.5
|
- Claude Sonnet 4.5
|
||||||
@ -122,7 +126,7 @@ Released on September 10, 2025.
|
|||||||
- **Execute SQL** component enhanced: Replaces the original variable reference component with a text input field, allowing users to write free-form SQL queries and reference variables. See [here](./guides/agent/agent_component_reference/execute_sql.md).
|
- **Execute SQL** component enhanced: Replaces the original variable reference component with a text input field, allowing users to write free-form SQL queries and reference variables. See [here](./guides/agent/agent_component_reference/execute_sql.md).
|
||||||
- Chat: Re-enables **Reasoning** and **Cross-language search**.
|
- Chat: Re-enables **Reasoning** and **Cross-language search**.
|
||||||
|
|
||||||
### Support new models
|
### Newly supported models
|
||||||
|
|
||||||
- Meituan LongCat
|
- Meituan LongCat
|
||||||
- Kimi: kimi-k2-turbo-preview and kimi-k2-0905-preview
|
- Kimi: kimi-k2-turbo-preview and kimi-k2-0905-preview
|
||||||
@ -161,7 +165,7 @@ Released on August 27, 2025.
|
|||||||
- Improves Markdown file parsing, with AST support to avoid unintended chunking.
|
- Improves Markdown file parsing, with AST support to avoid unintended chunking.
|
||||||
- Enhances HTML parsing, supporting bs4-based HTML tag traversal.
|
- Enhances HTML parsing, supporting bs4-based HTML tag traversal.
|
||||||
|
|
||||||
### Support new models
|
### Newly supported models
|
||||||
|
|
||||||
ZHIPU GLM-4.5
|
ZHIPU GLM-4.5
|
||||||
|
|
||||||
@ -222,7 +226,7 @@ Released on August 8, 2025.
|
|||||||
- The **Retrieval** component now supports the dynamic specification of dataset names using variables.
|
- The **Retrieval** component now supports the dynamic specification of dataset names using variables.
|
||||||
- The user interface now includes a French language option.
|
- The user interface now includes a French language option.
|
||||||
|
|
||||||
### Support new models
|
### Newly supported models
|
||||||
|
|
||||||
- GPT-5
|
- GPT-5
|
||||||
- Claude 4.1
|
- Claude 4.1
|
||||||
@ -286,7 +290,7 @@ Released on June 23, 2025.
|
|||||||
- Added support for models installed via Ollama or VLLM when creating a dataset through the API. [#8069](https://github.com/infiniflow/ragflow/pull/8069)
|
- Added support for models installed via Ollama or VLLM when creating a dataset through the API. [#8069](https://github.com/infiniflow/ragflow/pull/8069)
|
||||||
- Enabled role-based authentication for S3 bucket access. [#8149](https://github.com/infiniflow/ragflow/pull/8149)
|
- Enabled role-based authentication for S3 bucket access. [#8149](https://github.com/infiniflow/ragflow/pull/8149)
|
||||||
|
|
||||||
### Support new models
|
### Newly supported models
|
||||||
|
|
||||||
- Qwen 3 Embedding. [#8184](https://github.com/infiniflow/ragflow/pull/8184)
|
- Qwen 3 Embedding. [#8184](https://github.com/infiniflow/ragflow/pull/8184)
|
||||||
- Voyage Multimodal 3. [#7987](https://github.com/infiniflow/ragflow/pull/7987)
|
- Voyage Multimodal 3. [#7987](https://github.com/infiniflow/ragflow/pull/7987)
|
||||||
|
|||||||
@ -140,6 +140,16 @@
|
|||||||
<path d="M0 0h1024v1024H0z" opacity=".01"></path>
|
<path d="M0 0h1024v1024H0z" opacity=".01"></path>
|
||||||
<path d="M867.072 141.184H156.032a32 32 0 0 0 0 64h711.04a32 32 0 0 0 0-64z m0.832 226.368H403.2a32 32 0 0 0 0 64h464.704a32 32 0 0 0 0-64zM403.2 573.888h464.704a32 32 0 0 1 0 64H403.2a32 32 0 0 1 0-64z m464.704 226.368H156.864a32 32 0 0 0 0 64h711.04a32 32 0 0 0 0-64zM137.472 367.552v270.336l174.528-122.24-174.528-148.096z" ></path>
|
<path d="M867.072 141.184H156.032a32 32 0 0 0 0 64h711.04a32 32 0 0 0 0-64z m0.832 226.368H403.2a32 32 0 0 0 0 64h464.704a32 32 0 0 0 0-64zM403.2 573.888h464.704a32 32 0 0 1 0 64H403.2a32 32 0 0 1 0-64z m464.704 226.368H156.864a32 32 0 0 0 0 64h711.04a32 32 0 0 0 0-64zM137.472 367.552v270.336l174.528-122.24-174.528-148.096z" ></path>
|
||||||
</symbol>` +
|
</symbol>` +
|
||||||
|
` <symbol id="icon-a-listoperations" viewBox="0 0 1024 1024">
|
||||||
|
<path d="M341.376 96a32 32 0 0 1 0 64h-128a10.688 10.688 0 0 0-10.688 10.688v682.624a10.752 10.752 0 0 0 10.688 10.688h128a32 32 0 0 1 0 64h-128a74.688 74.688 0 0 1-74.688-74.688V170.688A74.688 74.688 0 0 1 213.376 96h128z m469.312 0a74.688 74.688 0 0 1 74.688 74.688v682.624a74.752 74.752 0 0 1-74.688 74.688h-128a32 32 0 1 1 0-64h128a10.752 10.752 0 0 0 10.688-10.688V170.688a10.752 10.752 0 0 0-10.688-10.688h-128a32 32 0 1 1 0-64h128zM357.248 464.256a48 48 0 0 1 0 95.488l-4.928 0.256H352a48 48 0 0 1 0-96h0.32l4.928 0.256z m155.072-0.256a48 48 0 1 1 0 96H512a48 48 0 0 1 0-96h0.32z m160 0a48 48 0 0 1 0 96H672a48 48 0 0 1 0-96h0.32z" ></path>
|
||||||
|
</symbol>` +
|
||||||
|
`<symbol id="icon-aggregator" viewBox="0 0 1024 1024">
|
||||||
|
<path d="M949.312 533.312a32 32 0 0 1-9.344 22.592l-170.688 170.688a32 32 0 0 1-45.248-45.248l116.032-116.032H478.208l-10.176-0.128a202.688 202.688 0 0 1-135.36-59.264L41.344 214.592a32 32 0 1 1 45.312-45.248l291.264 291.328 10.24 9.344a138.688 138.688 0 0 0 89.344 31.296h362.56L724.032 385.28a32 32 0 0 1 45.248-45.248l170.688 170.624a32 32 0 0 1 9.344 22.656zM299.968 638.656a32 32 0 0 1 0 45.248L86.656 897.28a32 32 0 0 1-45.312-45.248L254.72 638.72a32 32 0 0 1 45.312 0z" ></path>
|
||||||
|
</symbol>` +
|
||||||
|
`<symbol id="icon-a-ariableassigner" viewBox="0 0 1024 1024">
|
||||||
|
<path d="M509.056 64c123.136 0 235.072 48.512 317.12 130.56l-41.024 37.312C714.24 161.024 617.216 119.936 509.056 119.936a391.808 391.808 0 1 0 0 783.552 392.448 392.448 0 0 0 294.784-134.272l41.024 37.312c-82.048 93.248-201.472 149.248-335.808 149.248-246.272 3.712-447.744-197.76-447.744-444.032S262.784 64 509.056 64z m-63.424 186.56a29.184 29.184 0 0 1 14.912 14.912l160.448 444.032c3.712 14.912-3.712 26.112-18.56 33.536-14.976 3.776-26.24-3.648-33.664-14.848l-48.512-149.248H341.12l-59.712 149.248a27.648 27.648 0 0 1-33.6 14.848c-14.912-3.712-18.56-18.624-14.848-33.536l179.008-444.032c3.776-11.136 22.4-18.624 33.6-14.912zM889.6 530.432c14.976 0 26.176 11.2 26.176 26.112a25.472 25.472 0 0 1-26.176 26.112h-212.608a25.472 25.472 0 0 1-26.112-26.112c0-14.912 11.2-26.112 26.112-26.112H889.6z m-529.792 0h141.824L434.432 351.36l-74.624 179.2zM889.6 411.008c14.912 0 26.176 11.2 26.176 26.112a25.536 25.536 0 0 1-26.176 26.112h-212.608a25.536 25.536 0 0 1-26.112-26.112c0-14.912 11.2-26.112 26.112-26.112H889.6z" ></path>
|
||||||
|
</symbol>
|
||||||
|
` +
|
||||||
'</svg>'),
|
'</svg>'),
|
||||||
((h) => {
|
((h) => {
|
||||||
var a = (l = (l = document.getElementsByTagName('script'))[
|
var a = (l = (l = document.getElementsByTagName('script'))[
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
import { FileIconMap } from '@/constants/file';
|
import { FileIconMap } from '@/constants/file';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { getExtension } from '@/utils/document-util';
|
import { getExtension } from '@/utils/document-util';
|
||||||
|
import { CSSProperties } from 'react';
|
||||||
|
|
||||||
type IconFontType = {
|
type IconFontType = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
style?: CSSProperties;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IconFont = ({ name, className }: IconFontType) => (
|
export const IconFont = ({ name, className, style }: IconFontType) => (
|
||||||
<svg className={cn('size-4', className)}>
|
<svg className={cn('size-4', className)} style={style}>
|
||||||
<use xlinkHref={`#icon-${name}`} />
|
<use xlinkHref={`#icon-${name}`} />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -48,7 +48,7 @@ const JsonSchemaVisualizer: FC<JsonSchemaVisualizerProps> = ({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const parsedJson = JSON.parse(value);
|
const parsedJson = JSON.parse(value);
|
||||||
if (onChange) {
|
if (onChange && typeof parsedJson !== 'number') {
|
||||||
onChange(parsedJson);
|
onChange(parsedJson);
|
||||||
}
|
}
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
|
|||||||
@ -8,7 +8,10 @@ type KeyInputProps = {
|
|||||||
} & Omit<InputProps, 'onChange'>;
|
} & Omit<InputProps, 'onChange'>;
|
||||||
|
|
||||||
export const KeyInput = forwardRef<HTMLInputElement, KeyInputProps>(
|
export const KeyInput = forwardRef<HTMLInputElement, KeyInputProps>(
|
||||||
function KeyInput({ value, onChange, searchValue = /[^a-zA-Z0-9_]/g }, ref) {
|
function KeyInput(
|
||||||
|
{ value, onChange, searchValue = /[^a-zA-Z0-9_]/g, ...props },
|
||||||
|
ref,
|
||||||
|
) {
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) => {
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = e.target.value ?? '';
|
const value = e.target.value ?? '';
|
||||||
@ -18,6 +21,6 @@ export const KeyInput = forwardRef<HTMLInputElement, KeyInputProps>(
|
|||||||
[onChange, searchValue],
|
[onChange, searchValue],
|
||||||
);
|
);
|
||||||
|
|
||||||
return <Input value={value} onChange={handleChange} ref={ref} />;
|
return <Input {...props} value={value} onChange={handleChange} ref={ref} />;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -12,9 +12,12 @@ export const LogicalOperatorIcon = function OperatorIcon({
|
|||||||
return (
|
return (
|
||||||
<IconFont
|
<IconFont
|
||||||
name={icon}
|
name={icon}
|
||||||
className={cn('size-4', {
|
className={cn('size-4')}
|
||||||
'rotate-180': value === ComparisonOperator.GreatThan,
|
style={
|
||||||
})}
|
value === ComparisonOperator.GreatThan
|
||||||
|
? { transform: 'rotate(180deg)' }
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
></IconFont>
|
></IconFont>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -818,8 +818,7 @@ Example: https://fsn1.your-objectstorage.com`,
|
|||||||
modelsToBeAdded: 'Models to be added',
|
modelsToBeAdded: 'Models to be added',
|
||||||
addTheModel: 'Add',
|
addTheModel: 'Add',
|
||||||
apiKey: 'API-Key',
|
apiKey: 'API-Key',
|
||||||
apiKeyMessage:
|
apiKeyMessage: 'Please enter the API key',
|
||||||
'Please enter the API key (for locally deployed model,ignore this).',
|
|
||||||
apiKeyTip:
|
apiKeyTip:
|
||||||
'The API key can be obtained by registering the corresponding LLM supplier.',
|
'The API key can be obtained by registering the corresponding LLM supplier.',
|
||||||
showMoreModels: 'View models',
|
showMoreModels: 'View models',
|
||||||
@ -1646,6 +1645,7 @@ The variable aggregation node (originally the variable assignment node) is a cru
|
|||||||
beginInputTip:
|
beginInputTip:
|
||||||
'By defining input parameters, this content can be accessed by other components in subsequent processes.',
|
'By defining input parameters, this content can be accessed by other components in subsequent processes.',
|
||||||
query: 'Query variables',
|
query: 'Query variables',
|
||||||
|
queryRequired: 'Query is required',
|
||||||
queryTip: 'Select the variable you want to use',
|
queryTip: 'Select the variable you want to use',
|
||||||
agent: 'Agent',
|
agent: 'Agent',
|
||||||
addAgent: 'Add Agent',
|
addAgent: 'Add Agent',
|
||||||
@ -1855,7 +1855,7 @@ Important structured information may include: names, dates, locations, events, k
|
|||||||
desc: 'Descending',
|
desc: 'Descending',
|
||||||
},
|
},
|
||||||
variableAssignerLogicalOperatorOptions: {
|
variableAssignerLogicalOperatorOptions: {
|
||||||
overwrite: 'Overwrite',
|
overwrite: 'Overwritten By',
|
||||||
clear: 'Clear',
|
clear: 'Clear',
|
||||||
set: 'Set',
|
set: 'Set',
|
||||||
'+=': 'Add',
|
'+=': 'Add',
|
||||||
|
|||||||
@ -788,7 +788,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
modelsToBeAdded: '待添加的模型',
|
modelsToBeAdded: '待添加的模型',
|
||||||
addTheModel: '添加',
|
addTheModel: '添加',
|
||||||
apiKey: 'API-Key',
|
apiKey: 'API-Key',
|
||||||
apiKeyMessage: '请输入api key(如果是本地部署的模型,请忽略它)',
|
apiKeyMessage: '请输入api key',
|
||||||
apiKeyTip: 'API key可以通过注册相应的LLM供应商来获取。',
|
apiKeyTip: 'API key可以通过注册相应的LLM供应商来获取。',
|
||||||
showMoreModels: '展示更多模型',
|
showMoreModels: '展示更多模型',
|
||||||
hideModels: '隐藏模型',
|
hideModels: '隐藏模型',
|
||||||
@ -1549,6 +1549,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
task: '任务',
|
task: '任务',
|
||||||
beginInputTip: '通过定义输入参数,此内容可以被后续流程中的其他组件访问。',
|
beginInputTip: '通过定义输入参数,此内容可以被后续流程中的其他组件访问。',
|
||||||
query: '查询变量',
|
query: '查询变量',
|
||||||
|
queryRequired: '查询变量是必填项',
|
||||||
queryTip: '选择您想要使用的变量',
|
queryTip: '选择您想要使用的变量',
|
||||||
agent: '智能体',
|
agent: '智能体',
|
||||||
addAgent: '添加智能体',
|
addAgent: '添加智能体',
|
||||||
|
|||||||
@ -11,11 +11,12 @@ export function DataOperationsNode({
|
|||||||
}: NodeProps<BaseNode<DataOperationsFormSchemaType>>) {
|
}: NodeProps<BaseNode<DataOperationsFormSchemaType>>) {
|
||||||
const { data } = props;
|
const { data } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const operations = data.form?.operations;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RagNode {...props}>
|
<RagNode {...props}>
|
||||||
<LabelCard>
|
<LabelCard>
|
||||||
{t(`flow.operationsOptions.${camelCase(data.form?.operations)}`)}
|
{operations && t(`flow.operationsOptions.${camelCase(operations)}`)}
|
||||||
</LabelCard>
|
</LabelCard>
|
||||||
</RagNode>
|
</RagNode>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
AgentGlobals,
|
AgentGlobals,
|
||||||
AgentGlobalsSysQueryWithBrace,
|
AgentGlobalsSysQueryWithBrace,
|
||||||
AgentStructuredOutputField,
|
|
||||||
CodeTemplateStrMap,
|
CodeTemplateStrMap,
|
||||||
ComparisonOperator,
|
ComparisonOperator,
|
||||||
JsonSchemaDataType,
|
JsonSchemaDataType,
|
||||||
@ -465,7 +464,6 @@ export const initialAgentValues = {
|
|||||||
mcp: [],
|
mcp: [],
|
||||||
cite: true,
|
cite: true,
|
||||||
showStructuredOutput: false,
|
showStructuredOutput: false,
|
||||||
[AgentStructuredOutputField]: {},
|
|
||||||
outputs: {
|
outputs: {
|
||||||
content: {
|
content: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -870,7 +868,7 @@ export enum VariableAssignerLogicalArrayOperator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum ExportFileType {
|
export enum ExportFileType {
|
||||||
PDF = 'pdf',
|
// PDF = 'pdf',
|
||||||
HTML = 'html',
|
HTML = 'html',
|
||||||
Markdown = 'md',
|
Markdown = 'md',
|
||||||
DOCX = 'docx',
|
DOCX = 'docx',
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import { Switch } from '@/components/ui/switch';
|
|||||||
import { LlmModelType } from '@/constants/knowledge';
|
import { LlmModelType } from '@/constants/knowledge';
|
||||||
import { useFindLlmByUuid } from '@/hooks/use-llm-request';
|
import { useFindLlmByUuid } from '@/hooks/use-llm-request';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { memo, useEffect, useMemo } from 'react';
|
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@ -74,7 +74,6 @@ const FormSchema = z.object({
|
|||||||
...LargeModelFilterFormSchema,
|
...LargeModelFilterFormSchema,
|
||||||
cite: z.boolean().optional(),
|
cite: z.boolean().optional(),
|
||||||
showStructuredOutput: z.boolean().optional(),
|
showStructuredOutput: z.boolean().optional(),
|
||||||
[AgentStructuredOutputField]: z.record(z.any()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AgentFormSchemaType = z.infer<typeof FormSchema>;
|
export type AgentFormSchemaType = z.infer<typeof FormSchema>;
|
||||||
@ -127,7 +126,18 @@ function AgentForm({ node }: INextOperatorForm) {
|
|||||||
structuredOutputDialogVisible,
|
structuredOutputDialogVisible,
|
||||||
hideStructuredOutputDialog,
|
hideStructuredOutputDialog,
|
||||||
handleStructuredOutputDialogOk,
|
handleStructuredOutputDialogOk,
|
||||||
} = useShowStructuredOutputDialog(form);
|
} = useShowStructuredOutputDialog(node?.id);
|
||||||
|
|
||||||
|
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
||||||
|
|
||||||
|
const handleShowStructuredOutput = useCallback(
|
||||||
|
(val: boolean) => {
|
||||||
|
if (node?.id && val) {
|
||||||
|
updateNodeForm(node?.id, {}, ['outputs', AgentStructuredOutputField]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[node?.id, updateNodeForm],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (exceptionMethod !== AgentExceptionMethod.Goto) {
|
if (exceptionMethod !== AgentExceptionMethod.Goto) {
|
||||||
@ -284,9 +294,7 @@ function AgentForm({ node }: INextOperatorForm) {
|
|||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
<RAGFlowFormItem name={AgentStructuredOutputField} className="hidden">
|
|
||||||
<Input></Input>
|
|
||||||
</RAGFlowFormItem>
|
|
||||||
<Output list={outputList}>
|
<Output list={outputList}>
|
||||||
<RAGFlowFormItem name="showStructuredOutput">
|
<RAGFlowFormItem name="showStructuredOutput">
|
||||||
{(field) => (
|
{(field) => (
|
||||||
@ -297,7 +305,10 @@ function AgentForm({ node }: INextOperatorForm) {
|
|||||||
<Switch
|
<Switch
|
||||||
id="airplane-mode"
|
id="airplane-mode"
|
||||||
checked={field.value}
|
checked={field.value}
|
||||||
onCheckedChange={field.onChange}
|
onCheckedChange={(val) => {
|
||||||
|
handleShowStructuredOutput(val);
|
||||||
|
field.onChange(val);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,25 +1,27 @@
|
|||||||
import { JSONSchema } from '@/components/jsonjoy-builder';
|
import { JSONSchema } from '@/components/jsonjoy-builder';
|
||||||
import { AgentStructuredOutputField } from '@/constants/agent';
|
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { UseFormReturn } from 'react-hook-form';
|
import useGraphStore from '../../store';
|
||||||
|
|
||||||
export function useShowStructuredOutputDialog(form: UseFormReturn<any>) {
|
export function useShowStructuredOutputDialog(nodeId?: string) {
|
||||||
const {
|
const {
|
||||||
visible: structuredOutputDialogVisible,
|
visible: structuredOutputDialogVisible,
|
||||||
showModal: showStructuredOutputDialog,
|
showModal: showStructuredOutputDialog,
|
||||||
hideModal: hideStructuredOutputDialog,
|
hideModal: hideStructuredOutputDialog,
|
||||||
} = useSetModalState();
|
} = useSetModalState();
|
||||||
|
const { updateNodeForm, getNode } = useGraphStore((state) => state);
|
||||||
|
|
||||||
const initialStructuredOutput = form.getValues(AgentStructuredOutputField);
|
const initialStructuredOutput = getNode(nodeId)?.data.form.outputs.structured;
|
||||||
|
|
||||||
const handleStructuredOutputDialogOk = useCallback(
|
const handleStructuredOutputDialogOk = useCallback(
|
||||||
(values: JSONSchema) => {
|
(values: JSONSchema) => {
|
||||||
// Sync data to canvas
|
// Sync data to canvas
|
||||||
form.setValue(AgentStructuredOutputField, values);
|
if (nodeId) {
|
||||||
|
updateNodeForm(nodeId, values, ['outputs', 'structured']);
|
||||||
|
}
|
||||||
hideStructuredOutputDialog();
|
hideStructuredOutputDialog();
|
||||||
},
|
},
|
||||||
[form, hideStructuredOutputDialog],
|
[hideStructuredOutputDialog, nodeId, updateNodeForm],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -17,19 +17,13 @@ export function useWatchFormChange(id?: string, form?: UseFormReturn<any>) {
|
|||||||
prompts: [{ role: PromptRole.User, content: values.prompts }],
|
prompts: [{ role: PromptRole.User, content: values.prompts }],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (values.showStructuredOutput) {
|
if (!values.showStructuredOutput) {
|
||||||
nextValues = {
|
|
||||||
...nextValues,
|
|
||||||
outputs: {
|
|
||||||
...values.outputs,
|
|
||||||
[AgentStructuredOutputField]: values[AgentStructuredOutputField],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
nextValues = {
|
nextValues = {
|
||||||
...nextValues,
|
...nextValues,
|
||||||
outputs: omit(values.outputs, [AgentStructuredOutputField]),
|
outputs: omit(values.outputs, [AgentStructuredOutputField]),
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
nextValues = omit(nextValues, 'outputs');
|
||||||
}
|
}
|
||||||
updateNodeForm(id, nextValues);
|
updateNodeForm(id, nextValues);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { KeyInput } from '@/components/key-input';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -113,7 +114,6 @@ function ParameterForm({
|
|||||||
|
|
||||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||||
const values = { ...data, options: data.options?.map((x) => x.value) };
|
const values = { ...data, options: data.options?.map((x) => x.value) };
|
||||||
console.log('🚀 ~ onSubmit ~ values:', values);
|
|
||||||
|
|
||||||
submit(values);
|
submit(values);
|
||||||
}
|
}
|
||||||
@ -153,7 +153,11 @@ function ParameterForm({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('key')}</FormLabel>
|
<FormLabel>{t('key')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} autoComplete="off" onBlur={handleKeyChange} />
|
<KeyInput
|
||||||
|
{...field}
|
||||||
|
autoComplete="off"
|
||||||
|
onBlur={handleKeyChange}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@ -7,17 +7,24 @@ export type FormListHeaderProps = {
|
|||||||
label: ReactNode;
|
label: ReactNode;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function DynamicFormHeader({
|
export function DynamicFormHeader({
|
||||||
label,
|
label,
|
||||||
tooltip,
|
tooltip,
|
||||||
onClick,
|
onClick,
|
||||||
|
disabled = false,
|
||||||
}: FormListHeaderProps) {
|
}: FormListHeaderProps) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<FormLabel tooltip={tooltip}>{label}</FormLabel>
|
<FormLabel tooltip={tooltip}>{label}</FormLabel>
|
||||||
<Button variant={'ghost'} type="button" onClick={onClick}>
|
<Button
|
||||||
|
variant={'ghost'}
|
||||||
|
type="button"
|
||||||
|
onClick={onClick}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
<Plus />
|
<Plus />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,6 +2,10 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||||
import { JsonSchemaDataType } from '../../constant';
|
import { JsonSchemaDataType } from '../../constant';
|
||||||
|
import {
|
||||||
|
flatOptions,
|
||||||
|
useFilterQueryVariableOptionsByTypes,
|
||||||
|
} from '../../hooks/use-get-begin-query';
|
||||||
import { DynamicFormHeader, FormListHeaderProps } from './dynamic-fom-header';
|
import { DynamicFormHeader, FormListHeaderProps } from './dynamic-fom-header';
|
||||||
import { QueryVariable } from './query-variable';
|
import { QueryVariable } from './query-variable';
|
||||||
|
|
||||||
@ -16,6 +20,10 @@ export function QueryVariableList({
|
|||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
const name = 'query';
|
const name = 'query';
|
||||||
|
|
||||||
|
let options = useFilterQueryVariableOptionsByTypes(types);
|
||||||
|
|
||||||
|
const secondOptions = flatOptions(options);
|
||||||
|
|
||||||
const { fields, remove, append } = useFieldArray({
|
const { fields, remove, append } = useFieldArray({
|
||||||
name: name,
|
name: name,
|
||||||
control: form.control,
|
control: form.control,
|
||||||
@ -26,14 +34,15 @@ export function QueryVariableList({
|
|||||||
<DynamicFormHeader
|
<DynamicFormHeader
|
||||||
label={label}
|
label={label}
|
||||||
tooltip={tooltip}
|
tooltip={tooltip}
|
||||||
onClick={() => append({ input: '' })}
|
onClick={() => append({ input: secondOptions.at(0)?.value })}
|
||||||
|
disabled={!secondOptions.length}
|
||||||
></DynamicFormHeader>
|
></DynamicFormHeader>
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
{fields.map((field, index) => {
|
{fields.map((field, index) => {
|
||||||
const nameField = `${name}.${index}.input`;
|
const nameField = `${name}.${index}.input`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={field.id} className="flex items-center gap-2">
|
<div key={field.id} className="flex gap-2">
|
||||||
<QueryVariable
|
<QueryVariable
|
||||||
name={nameField}
|
name={nameField}
|
||||||
hideLabel
|
hideLabel
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { Form } from '@/components/ui/form';
|
|||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { buildOptions } from '@/utils/form';
|
import { buildOptions } from '@/utils/form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { t } from 'i18next';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useForm, useWatch } from 'react-hook-form';
|
import { useForm, useWatch } from 'react-hook-form';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -25,7 +26,11 @@ import { SelectKeys } from './select-keys';
|
|||||||
import { Updates } from './updates';
|
import { Updates } from './updates';
|
||||||
|
|
||||||
export const RetrievalPartialSchema = {
|
export const RetrievalPartialSchema = {
|
||||||
query: z.array(z.object({ input: z.string().optional() })),
|
query: z.array(
|
||||||
|
z.object({
|
||||||
|
input: z.string().min(1, { message: t('flow.queryRequired') }),
|
||||||
|
}),
|
||||||
|
),
|
||||||
operations: z.string(),
|
operations: z.string(),
|
||||||
select_keys: z.array(z.object({ name: z.string().optional() })).optional(),
|
select_keys: z.array(z.object({ name: z.string().optional() })).optional(),
|
||||||
remove_keys: z.array(z.object({ name: z.string().optional() })).optional(),
|
remove_keys: z.array(z.object({ name: z.string().optional() })).optional(),
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { FormContainer } from '@/components/form-container';
|
import { FormContainer } from '@/components/form-container';
|
||||||
|
import { KeyInput } from '@/components/key-input';
|
||||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
import { BlockButton, Button } from '@/components/ui/button';
|
import { BlockButton, Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
@ -9,7 +10,6 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { Input } from '@/components/ui/input';
|
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
@ -67,10 +67,10 @@ export function DynamicOutputForm({ node }: IProps) {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex-1">
|
<FormItem className="flex-1">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<KeyInput
|
||||||
{...field}
|
{...field}
|
||||||
placeholder={t('common.pleaseInput')}
|
placeholder={t('common.pleaseInput')}
|
||||||
></Input>
|
></KeyInput>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@ -317,14 +317,18 @@ export const useGetComponentLabelByValue = (nodeId: string) => {
|
|||||||
return getLabel;
|
return getLabel;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function flatOptions(options: DefaultOptionType[]) {
|
||||||
|
return options.reduce<DefaultOptionType[]>((pre, cur) => {
|
||||||
|
return [...pre, ...cur.options];
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
export function useFlattenQueryVariableOptions(nodeId?: string) {
|
export function useFlattenQueryVariableOptions(nodeId?: string) {
|
||||||
const { getNode } = useGraphStore((state) => state);
|
const { getNode } = useGraphStore((state) => state);
|
||||||
const nextOptions = useBuildQueryVariableOptions(getNode(nodeId));
|
const nextOptions = useBuildQueryVariableOptions(getNode(nodeId));
|
||||||
|
|
||||||
const flattenOptions = useMemo(() => {
|
const flattenOptions = useMemo(() => {
|
||||||
return nextOptions.reduce<DefaultOptionType[]>((pre, cur) => {
|
return flatOptions(nextOptions);
|
||||||
return [...pre, ...cur.options];
|
|
||||||
}, []);
|
|
||||||
}, [nextOptions]);
|
}, [nextOptions]);
|
||||||
|
|
||||||
return flattenOptions;
|
return flattenOptions;
|
||||||
|
|||||||
@ -12,9 +12,9 @@ import { ReactComponent as WenCaiIcon } from '@/assets/svg/wencai.svg';
|
|||||||
import { ReactComponent as WikipediaIcon } from '@/assets/svg/wikipedia.svg';
|
import { ReactComponent as WikipediaIcon } from '@/assets/svg/wikipedia.svg';
|
||||||
import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.svg';
|
import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.svg';
|
||||||
|
|
||||||
import { IconFont } from '@/components/icon-font';
|
import { IconFontFill } from '@/components/icon-font';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Columns3, Equal, FileCode, HousePlus, Variable } from 'lucide-react';
|
import { FileCode, HousePlus } from 'lucide-react';
|
||||||
import { Operator } from './constant';
|
import { Operator } from './constant';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
@ -37,6 +37,9 @@ export const OperatorIconMap = {
|
|||||||
[Operator.ExeSQL]: 'executesql-0',
|
[Operator.ExeSQL]: 'executesql-0',
|
||||||
[Operator.Invoke]: 'httprequest-0',
|
[Operator.Invoke]: 'httprequest-0',
|
||||||
[Operator.Email]: 'sendemail-0',
|
[Operator.Email]: 'sendemail-0',
|
||||||
|
[Operator.ListOperations]: 'a-listoperations',
|
||||||
|
[Operator.VariableAssigner]: 'a-ariableassigner',
|
||||||
|
[Operator.VariableAggregator]: 'aggregator',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SVGIconMap = {
|
export const SVGIconMap = {
|
||||||
@ -57,9 +60,6 @@ export const SVGIconMap = {
|
|||||||
};
|
};
|
||||||
export const LucideIconMap = {
|
export const LucideIconMap = {
|
||||||
[Operator.DataOperations]: FileCode,
|
[Operator.DataOperations]: FileCode,
|
||||||
[Operator.ListOperations]: Columns3,
|
|
||||||
[Operator.VariableAssigner]: Equal,
|
|
||||||
[Operator.VariableAggregator]: Variable,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Empty = () => {
|
const Empty = () => {
|
||||||
@ -86,7 +86,10 @@ const OperatorIcon = ({ name, className }: IProps) => {
|
|||||||
|
|
||||||
if (Icon) {
|
if (Icon) {
|
||||||
return (
|
return (
|
||||||
<IconFont name={Icon} className={cn('size-5 ', className)}></IconFont>
|
<IconFontFill
|
||||||
|
name={Icon}
|
||||||
|
className={cn('size-5 ', className)}
|
||||||
|
></IconFontFill>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -160,7 +160,7 @@ const ProfilePage: FC = () => {
|
|||||||
<AvatarUpload
|
<AvatarUpload
|
||||||
value={profile.avatar}
|
value={profile.avatar}
|
||||||
onChange={handleAvatarUpload}
|
onChange={handleAvatarUpload}
|
||||||
tips={'This will be displayed on your profile.'}
|
tips={t('avatarTip')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user