mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-01-04 03:25:30 +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
|
||||
$ 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.
|
||||
|
||||
# Use CPU for DeepDoc tasks:
|
||||
|
||||
@ -192,7 +192,8 @@ Coba demo kami di [https://demo.ragflow.io](https://demo.ragflow.io).
|
||||
```bash
|
||||
$ 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.
|
||||
|
||||
# Use CPU for DeepDoc tasks:
|
||||
|
||||
@ -172,7 +172,8 @@
|
||||
```bash
|
||||
$ 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 イメージのバージョンと一致していることを確認します。
|
||||
|
||||
# Use CPU for DeepDoc tasks:
|
||||
|
||||
@ -174,7 +174,8 @@
|
||||
```bash
|
||||
$ 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 이미지 버전과 일치하도록 보장합니다.
|
||||
|
||||
# Use CPU for DeepDoc tasks:
|
||||
|
||||
@ -192,7 +192,8 @@ Experimente nossa demo em [https://demo.ragflow.io](https://demo.ragflow.io).
|
||||
```bash
|
||||
$ 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.
|
||||
|
||||
# Use CPU for DeepDoc tasks:
|
||||
|
||||
@ -191,7 +191,8 @@
|
||||
```bash
|
||||
$ 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 映像版本一致。
|
||||
|
||||
# Use CPU for DeepDoc tasks:
|
||||
|
||||
@ -192,7 +192,8 @@
|
||||
```bash
|
||||
$ 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 镜像的版本保持一致。
|
||||
|
||||
# Use CPU for DeepDoc tasks:
|
||||
|
||||
@ -31,7 +31,7 @@ from common.constants import ActiveEnum, StatusEnum
|
||||
from api.utils.crypt import decrypt
|
||||
from common.misc_utils import get_uuid
|
||||
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
|
||||
|
||||
|
||||
@ -130,7 +130,7 @@ def login_admin(email: str, password: str):
|
||||
user.last_login_time = get_format_time()
|
||||
user.save()
|
||||
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):
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
@ -20,9 +21,7 @@ import uuid
|
||||
from html import escape
|
||||
from typing import Any
|
||||
|
||||
import flask
|
||||
import trio
|
||||
from quart import request
|
||||
from quart import request, make_response
|
||||
from google_auth_oauthlib.flow import Flow
|
||||
|
||||
from api.db import InputType
|
||||
@ -59,7 +58,7 @@ async def set_connector():
|
||||
}
|
||||
ConnectorService.save(**conn)
|
||||
|
||||
await trio.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
e, conn = ConnectorService.get_by_id(req["id"])
|
||||
|
||||
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}
|
||||
|
||||
|
||||
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"
|
||||
auto_close = "window.close();" if success else ""
|
||||
escaped_message = escape(message)
|
||||
@ -165,7 +164,7 @@ def _render_web_oauth_popup(flow_id: str, success: bool, message: str):
|
||||
payload_json=payload_json,
|
||||
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"
|
||||
return response
|
||||
|
||||
@ -232,31 +231,31 @@ async def start_google_drive_web_oauth():
|
||||
|
||||
|
||||
@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")
|
||||
error = request.args.get("error")
|
||||
error_description = request.args.get("error_description") or error
|
||||
|
||||
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))
|
||||
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)
|
||||
client_config = state_obj.get("client_config")
|
||||
if not client_config:
|
||||
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:
|
||||
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")
|
||||
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:
|
||||
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
|
||||
logging.exception("Failed to exchange Google OAuth code: %s", exc)
|
||||
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()
|
||||
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.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
|
||||
|
||||
@ -886,6 +886,7 @@ async def check_embedding():
|
||||
|
||||
try:
|
||||
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"])
|
||||
title_w = 0.1
|
||||
qv_mix = title_w * v[0] + (1 - title_w) * v[1]
|
||||
@ -895,8 +896,8 @@ async def check_embedding():
|
||||
if sim_mix > sim:
|
||||
sim = sim_mix
|
||||
mode = "title+content"
|
||||
except Exception:
|
||||
return get_error_data_result(message="embedding failure")
|
||||
except Exception as e:
|
||||
return get_error_data_result(message=f"Embedding failure. {e}")
|
||||
|
||||
eff_sims.append(sim)
|
||||
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 rag.llm.cv_model import GptV4
|
||||
from common import settings
|
||||
from api.apps import current_user
|
||||
|
||||
|
||||
class FileService(CommonService):
|
||||
@ -184,6 +183,7 @@ class FileService(CommonService):
|
||||
@classmethod
|
||||
@DB.connection_context()
|
||||
def create_folder(cls, file, parent_id, name, count):
|
||||
from api.apps import current_user
|
||||
# Recursively create folder structure
|
||||
# Args:
|
||||
# file: Current file object
|
||||
@ -508,6 +508,7 @@ class FileService(CommonService):
|
||||
@staticmethod
|
||||
def parse(filename, blob, img_base64=True, tenant_id=None):
|
||||
from rag.app import audio, email, naive, picture, presentation
|
||||
from api.apps import current_user
|
||||
|
||||
def dummy(prog=None, msg=""):
|
||||
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-Expose-Headers"] = "Authorization"
|
||||
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": "",
|
||||
"tags": "LLM,TEXT EMBEDDING,TEXT RE-RANK",
|
||||
"status": "1",
|
||||
|
||||
@ -7,25 +7,29 @@ slug: /release_notes
|
||||
|
||||
Key features, improvements and bug fixes in the latest releases.
|
||||
|
||||
## v0.22.1
|
||||
|
||||
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
|
||||
|
||||
- **Agent**:
|
||||
- Added support for exporting Agent outputs in Word formats.
|
||||
- Introduced new list operations and enhanced the **Variable Aggregator** component capabilities.
|
||||
- **Data Sources**:
|
||||
- Expanded data source support to include S3-compatible storage services.
|
||||
- Added new integration support for JIRA.
|
||||
- **User Profile**: Optimized and beautified the layout of the personal center interface.
|
||||
- Agent:
|
||||
- Supports exporting Agent outputs in Word or Markdown formats.
|
||||
- Adds a **List operations** component.
|
||||
- Adds a **Variable aggregator** component.
|
||||
- Data sources:
|
||||
- Supports S3-compatible data sources, e.g., MinIO.
|
||||
- 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
|
||||
|
||||
@ -99,7 +103,7 @@ Released on October 15, 2025.
|
||||
- Redesigns RAGFlow's Login and Registration pages.
|
||||
- Upgrades RAGFlow's document engine Infinity to v0.6.0.
|
||||
|
||||
### Support new models
|
||||
### Newly supported models
|
||||
|
||||
- Tongyi Qwen 3 series
|
||||
- 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).
|
||||
- Chat: Re-enables **Reasoning** and **Cross-language search**.
|
||||
|
||||
### Support new models
|
||||
### Newly supported models
|
||||
|
||||
- Meituan LongCat
|
||||
- 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.
|
||||
- Enhances HTML parsing, supporting bs4-based HTML tag traversal.
|
||||
|
||||
### Support new models
|
||||
### Newly supported models
|
||||
|
||||
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 user interface now includes a French language option.
|
||||
|
||||
### Support new models
|
||||
### Newly supported models
|
||||
|
||||
- GPT-5
|
||||
- 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)
|
||||
- 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)
|
||||
- Voyage Multimodal 3. [#7987](https://github.com/infiniflow/ragflow/pull/7987)
|
||||
|
||||
@ -140,6 +140,16 @@
|
||||
<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>
|
||||
</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>'),
|
||||
((h) => {
|
||||
var a = (l = (l = document.getElementsByTagName('script'))[
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { FileIconMap } from '@/constants/file';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { getExtension } from '@/utils/document-util';
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
type IconFontType = {
|
||||
name: string;
|
||||
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
};
|
||||
|
||||
export const IconFont = ({ name, className }: IconFontType) => (
|
||||
<svg className={cn('size-4', className)}>
|
||||
export const IconFont = ({ name, className, style }: IconFontType) => (
|
||||
<svg className={cn('size-4', className)} style={style}>
|
||||
<use xlinkHref={`#icon-${name}`} />
|
||||
</svg>
|
||||
);
|
||||
|
||||
@ -48,7 +48,7 @@ const JsonSchemaVisualizer: FC<JsonSchemaVisualizerProps> = ({
|
||||
|
||||
try {
|
||||
const parsedJson = JSON.parse(value);
|
||||
if (onChange) {
|
||||
if (onChange && typeof parsedJson !== 'number') {
|
||||
onChange(parsedJson);
|
||||
}
|
||||
} catch (_error) {
|
||||
|
||||
@ -8,7 +8,10 @@ type KeyInputProps = {
|
||||
} & Omit<InputProps, 'onChange'>;
|
||||
|
||||
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(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value ?? '';
|
||||
@ -18,6 +21,6 @@ export const KeyInput = forwardRef<HTMLInputElement, KeyInputProps>(
|
||||
[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 (
|
||||
<IconFont
|
||||
name={icon}
|
||||
className={cn('size-4', {
|
||||
'rotate-180': value === ComparisonOperator.GreatThan,
|
||||
})}
|
||||
className={cn('size-4')}
|
||||
style={
|
||||
value === ComparisonOperator.GreatThan
|
||||
? { transform: 'rotate(180deg)' }
|
||||
: undefined
|
||||
}
|
||||
></IconFont>
|
||||
);
|
||||
}
|
||||
|
||||
@ -818,8 +818,7 @@ Example: https://fsn1.your-objectstorage.com`,
|
||||
modelsToBeAdded: 'Models to be added',
|
||||
addTheModel: 'Add',
|
||||
apiKey: 'API-Key',
|
||||
apiKeyMessage:
|
||||
'Please enter the API key (for locally deployed model,ignore this).',
|
||||
apiKeyMessage: 'Please enter the API key',
|
||||
apiKeyTip:
|
||||
'The API key can be obtained by registering the corresponding LLM supplier.',
|
||||
showMoreModels: 'View models',
|
||||
@ -1646,6 +1645,7 @@ The variable aggregation node (originally the variable assignment node) is a cru
|
||||
beginInputTip:
|
||||
'By defining input parameters, this content can be accessed by other components in subsequent processes.',
|
||||
query: 'Query variables',
|
||||
queryRequired: 'Query is required',
|
||||
queryTip: 'Select the variable you want to use',
|
||||
agent: 'Agent',
|
||||
addAgent: 'Add Agent',
|
||||
@ -1855,7 +1855,7 @@ Important structured information may include: names, dates, locations, events, k
|
||||
desc: 'Descending',
|
||||
},
|
||||
variableAssignerLogicalOperatorOptions: {
|
||||
overwrite: 'Overwrite',
|
||||
overwrite: 'Overwritten By',
|
||||
clear: 'Clear',
|
||||
set: 'Set',
|
||||
'+=': 'Add',
|
||||
|
||||
@ -788,7 +788,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
modelsToBeAdded: '待添加的模型',
|
||||
addTheModel: '添加',
|
||||
apiKey: 'API-Key',
|
||||
apiKeyMessage: '请输入api key(如果是本地部署的模型,请忽略它)',
|
||||
apiKeyMessage: '请输入api key',
|
||||
apiKeyTip: 'API key可以通过注册相应的LLM供应商来获取。',
|
||||
showMoreModels: '展示更多模型',
|
||||
hideModels: '隐藏模型',
|
||||
@ -1549,6 +1549,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
task: '任务',
|
||||
beginInputTip: '通过定义输入参数,此内容可以被后续流程中的其他组件访问。',
|
||||
query: '查询变量',
|
||||
queryRequired: '查询变量是必填项',
|
||||
queryTip: '选择您想要使用的变量',
|
||||
agent: '智能体',
|
||||
addAgent: '添加智能体',
|
||||
|
||||
@ -11,11 +11,12 @@ export function DataOperationsNode({
|
||||
}: NodeProps<BaseNode<DataOperationsFormSchemaType>>) {
|
||||
const { data } = props;
|
||||
const { t } = useTranslation();
|
||||
const operations = data.form?.operations;
|
||||
|
||||
return (
|
||||
<RagNode {...props}>
|
||||
<LabelCard>
|
||||
{t(`flow.operationsOptions.${camelCase(data.form?.operations)}`)}
|
||||
{operations && t(`flow.operationsOptions.${camelCase(operations)}`)}
|
||||
</LabelCard>
|
||||
</RagNode>
|
||||
);
|
||||
|
||||
@ -5,7 +5,6 @@ import {
|
||||
import {
|
||||
AgentGlobals,
|
||||
AgentGlobalsSysQueryWithBrace,
|
||||
AgentStructuredOutputField,
|
||||
CodeTemplateStrMap,
|
||||
ComparisonOperator,
|
||||
JsonSchemaDataType,
|
||||
@ -465,7 +464,6 @@ export const initialAgentValues = {
|
||||
mcp: [],
|
||||
cite: true,
|
||||
showStructuredOutput: false,
|
||||
[AgentStructuredOutputField]: {},
|
||||
outputs: {
|
||||
content: {
|
||||
type: 'string',
|
||||
@ -870,7 +868,7 @@ export enum VariableAssignerLogicalArrayOperator {
|
||||
}
|
||||
|
||||
export enum ExportFileType {
|
||||
PDF = 'pdf',
|
||||
// PDF = 'pdf',
|
||||
HTML = 'html',
|
||||
Markdown = 'md',
|
||||
DOCX = 'docx',
|
||||
|
||||
@ -22,7 +22,7 @@ import { Switch } from '@/components/ui/switch';
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useFindLlmByUuid } from '@/hooks/use-llm-request';
|
||||
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 { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
@ -74,7 +74,6 @@ const FormSchema = z.object({
|
||||
...LargeModelFilterFormSchema,
|
||||
cite: z.boolean().optional(),
|
||||
showStructuredOutput: z.boolean().optional(),
|
||||
[AgentStructuredOutputField]: z.record(z.any()),
|
||||
});
|
||||
|
||||
export type AgentFormSchemaType = z.infer<typeof FormSchema>;
|
||||
@ -127,7 +126,18 @@ function AgentForm({ node }: INextOperatorForm) {
|
||||
structuredOutputDialogVisible,
|
||||
hideStructuredOutputDialog,
|
||||
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(() => {
|
||||
if (exceptionMethod !== AgentExceptionMethod.Goto) {
|
||||
@ -284,9 +294,7 @@ function AgentForm({ node }: INextOperatorForm) {
|
||||
)}
|
||||
</section>
|
||||
</Collapse>
|
||||
<RAGFlowFormItem name={AgentStructuredOutputField} className="hidden">
|
||||
<Input></Input>
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<Output list={outputList}>
|
||||
<RAGFlowFormItem name="showStructuredOutput">
|
||||
{(field) => (
|
||||
@ -297,7 +305,10 @@ function AgentForm({ node }: INextOperatorForm) {
|
||||
<Switch
|
||||
id="airplane-mode"
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
onCheckedChange={(val) => {
|
||||
handleShowStructuredOutput(val);
|
||||
field.onChange(val);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1,25 +1,27 @@
|
||||
import { JSONSchema } from '@/components/jsonjoy-builder';
|
||||
import { AgentStructuredOutputField } from '@/constants/agent';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
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 {
|
||||
visible: structuredOutputDialogVisible,
|
||||
showModal: showStructuredOutputDialog,
|
||||
hideModal: hideStructuredOutputDialog,
|
||||
} = useSetModalState();
|
||||
const { updateNodeForm, getNode } = useGraphStore((state) => state);
|
||||
|
||||
const initialStructuredOutput = form.getValues(AgentStructuredOutputField);
|
||||
const initialStructuredOutput = getNode(nodeId)?.data.form.outputs.structured;
|
||||
|
||||
const handleStructuredOutputDialogOk = useCallback(
|
||||
(values: JSONSchema) => {
|
||||
// Sync data to canvas
|
||||
form.setValue(AgentStructuredOutputField, values);
|
||||
if (nodeId) {
|
||||
updateNodeForm(nodeId, values, ['outputs', 'structured']);
|
||||
}
|
||||
hideStructuredOutputDialog();
|
||||
},
|
||||
[form, hideStructuredOutputDialog],
|
||||
[hideStructuredOutputDialog, nodeId, updateNodeForm],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@ -17,19 +17,13 @@ export function useWatchFormChange(id?: string, form?: UseFormReturn<any>) {
|
||||
prompts: [{ role: PromptRole.User, content: values.prompts }],
|
||||
};
|
||||
|
||||
if (values.showStructuredOutput) {
|
||||
nextValues = {
|
||||
...nextValues,
|
||||
outputs: {
|
||||
...values.outputs,
|
||||
[AgentStructuredOutputField]: values[AgentStructuredOutputField],
|
||||
},
|
||||
};
|
||||
} else {
|
||||
if (!values.showStructuredOutput) {
|
||||
nextValues = {
|
||||
...nextValues,
|
||||
outputs: omit(values.outputs, [AgentStructuredOutputField]),
|
||||
};
|
||||
} else {
|
||||
nextValues = omit(nextValues, 'outputs');
|
||||
}
|
||||
updateNodeForm(id, nextValues);
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { KeyInput } from '@/components/key-input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
@ -113,7 +114,6 @@ function ParameterForm({
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
const values = { ...data, options: data.options?.map((x) => x.value) };
|
||||
console.log('🚀 ~ onSubmit ~ values:', values);
|
||||
|
||||
submit(values);
|
||||
}
|
||||
@ -153,7 +153,11 @@ function ParameterForm({
|
||||
<FormItem>
|
||||
<FormLabel>{t('key')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} autoComplete="off" onBlur={handleKeyChange} />
|
||||
<KeyInput
|
||||
{...field}
|
||||
autoComplete="off"
|
||||
onBlur={handleKeyChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@ -7,17 +7,24 @@ export type FormListHeaderProps = {
|
||||
label: ReactNode;
|
||||
tooltip?: string;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export function DynamicFormHeader({
|
||||
label,
|
||||
tooltip,
|
||||
onClick,
|
||||
disabled = false,
|
||||
}: FormListHeaderProps) {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<FormLabel tooltip={tooltip}>{label}</FormLabel>
|
||||
<Button variant={'ghost'} type="button" onClick={onClick}>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Plus />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -2,6 +2,10 @@ import { Button } from '@/components/ui/button';
|
||||
import { X } from 'lucide-react';
|
||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import { JsonSchemaDataType } from '../../constant';
|
||||
import {
|
||||
flatOptions,
|
||||
useFilterQueryVariableOptionsByTypes,
|
||||
} from '../../hooks/use-get-begin-query';
|
||||
import { DynamicFormHeader, FormListHeaderProps } from './dynamic-fom-header';
|
||||
import { QueryVariable } from './query-variable';
|
||||
|
||||
@ -16,6 +20,10 @@ export function QueryVariableList({
|
||||
const form = useFormContext();
|
||||
const name = 'query';
|
||||
|
||||
let options = useFilterQueryVariableOptionsByTypes(types);
|
||||
|
||||
const secondOptions = flatOptions(options);
|
||||
|
||||
const { fields, remove, append } = useFieldArray({
|
||||
name: name,
|
||||
control: form.control,
|
||||
@ -26,14 +34,15 @@ export function QueryVariableList({
|
||||
<DynamicFormHeader
|
||||
label={label}
|
||||
tooltip={tooltip}
|
||||
onClick={() => append({ input: '' })}
|
||||
onClick={() => append({ input: secondOptions.at(0)?.value })}
|
||||
disabled={!secondOptions.length}
|
||||
></DynamicFormHeader>
|
||||
<div className="space-y-5">
|
||||
{fields.map((field, index) => {
|
||||
const nameField = `${name}.${index}.input`;
|
||||
|
||||
return (
|
||||
<div key={field.id} className="flex items-center gap-2">
|
||||
<div key={field.id} className="flex gap-2">
|
||||
<QueryVariable
|
||||
name={nameField}
|
||||
hideLabel
|
||||
|
||||
@ -4,6 +4,7 @@ import { Form } from '@/components/ui/form';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { buildOptions } from '@/utils/form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { t } from 'i18next';
|
||||
import { memo } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -25,7 +26,11 @@ import { SelectKeys } from './select-keys';
|
||||
import { Updates } from './updates';
|
||||
|
||||
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(),
|
||||
select_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';
|
||||
|
||||
import { FormContainer } from '@/components/form-container';
|
||||
import { KeyInput } from '@/components/key-input';
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { BlockButton, Button } from '@/components/ui/button';
|
||||
import {
|
||||
@ -9,7 +10,6 @@ import {
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { t } from 'i18next';
|
||||
@ -67,10 +67,10 @@ export function DynamicOutputForm({ node }: IProps) {
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormControl>
|
||||
<Input
|
||||
<KeyInput
|
||||
{...field}
|
||||
placeholder={t('common.pleaseInput')}
|
||||
></Input>
|
||||
></KeyInput>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@ -317,14 +317,18 @@ export const useGetComponentLabelByValue = (nodeId: string) => {
|
||||
return getLabel;
|
||||
};
|
||||
|
||||
export function flatOptions(options: DefaultOptionType[]) {
|
||||
return options.reduce<DefaultOptionType[]>((pre, cur) => {
|
||||
return [...pre, ...cur.options];
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function useFlattenQueryVariableOptions(nodeId?: string) {
|
||||
const { getNode } = useGraphStore((state) => state);
|
||||
const nextOptions = useBuildQueryVariableOptions(getNode(nodeId));
|
||||
|
||||
const flattenOptions = useMemo(() => {
|
||||
return nextOptions.reduce<DefaultOptionType[]>((pre, cur) => {
|
||||
return [...pre, ...cur.options];
|
||||
}, []);
|
||||
return flatOptions(nextOptions);
|
||||
}, [nextOptions]);
|
||||
|
||||
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 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 { Columns3, Equal, FileCode, HousePlus, Variable } from 'lucide-react';
|
||||
import { FileCode, HousePlus } from 'lucide-react';
|
||||
import { Operator } from './constant';
|
||||
|
||||
interface IProps {
|
||||
@ -37,6 +37,9 @@ export const OperatorIconMap = {
|
||||
[Operator.ExeSQL]: 'executesql-0',
|
||||
[Operator.Invoke]: 'httprequest-0',
|
||||
[Operator.Email]: 'sendemail-0',
|
||||
[Operator.ListOperations]: 'a-listoperations',
|
||||
[Operator.VariableAssigner]: 'a-ariableassigner',
|
||||
[Operator.VariableAggregator]: 'aggregator',
|
||||
};
|
||||
|
||||
export const SVGIconMap = {
|
||||
@ -57,9 +60,6 @@ export const SVGIconMap = {
|
||||
};
|
||||
export const LucideIconMap = {
|
||||
[Operator.DataOperations]: FileCode,
|
||||
[Operator.ListOperations]: Columns3,
|
||||
[Operator.VariableAssigner]: Equal,
|
||||
[Operator.VariableAggregator]: Variable,
|
||||
};
|
||||
|
||||
const Empty = () => {
|
||||
@ -86,7 +86,10 @@ const OperatorIcon = ({ name, className }: IProps) => {
|
||||
|
||||
if (Icon) {
|
||||
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
|
||||
value={profile.avatar}
|
||||
onChange={handleAvatarUpload}
|
||||
tips={'This will be displayed on your profile.'}
|
||||
tips={t('avatarTip')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user