Compare commits

..

10 Commits

Author SHA1 Message Date
cfdccebb17 Feat: Fixed an issue where modifying fields in the agent operator caused the loss of structured data. #10427 (#11388)
### What problem does this PR solve?

Feat: Fixed an issue where modifying fields in the agent operator caused
the loss of structured data. #10427

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-11-19 20:11:53 +08:00
980a883033 Docs: minor (#11385)
### What problem does this PR solve?

### Type of change

- [x] Documentation Update
2025-11-19 19:41:21 +08:00
02d429f0ca Doc: Optimize read me (#11386)
### What problem does this PR solve?

Users currently can’t view `git checkout v0.22.1` directly. They need to
scroll the code block all the way to the right to see it.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-11-19 19:40:55 +08:00
9c24d5d44a Fix some multilingual issues (#11382)
### What problem does this PR solve?

Fix some multilingual issues

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-11-19 19:14:43 +08:00
0cc5d7a8a6 Feat: If a query variable in a data manipulation operator is deleted, a warning message should be displayed to the user. #10427 #11255 (#11384)
### What problem does this PR solve?

Feat: If a query variable in a data manipulation operator is deleted, a
warning message should be displayed to the user. #10427 #11255

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-11-19 19:10:57 +08:00
c43bf1dcf5 Fix: refine error msg. (#11380)
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-11-19 19:10:45 +08:00
f76b8279dd Doc: Added v0.22.1 release notes (#11383)
### What problem does this PR solve?


### Type of change


- [x] Documentation Update
2025-11-19 18:40:06 +08:00
db5ec89dc5 Feat: The key for the begin operator can only contain alphanumeric characters and underscores. #10427 (#11377)
### What problem does this PR solve?

Feat: The key for the begin operator can only contain alphanumeric
characters and underscores. #10427

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-11-19 16:16:57 +08:00
1c201c4d54 Fix: circle imports issue. (#11374)
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-11-19 16:13:21 +08:00
ba78d0f0c2 Feat: Structured data will still be stored in outputs for compatibility with older versions. #10427 (#11368)
### What problem does this PR solve?

Feat: Structured data will still be stored in outputs for compatibility
with older versions. #10427

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-11-19 15:15:51 +08:00
34 changed files with 192 additions and 104 deletions

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -191,7 +191,8 @@
```bash
$ cd ragflow/docker
# 可選使用穩定版標籤查看發佈https://github.com/infiniflow/ragflow/releasesgit checkout v0.22.1
# git checkout v0.22.1
# 可選使用穩定版標籤查看發佈https://github.com/infiniflow/ragflow/releases
# 此步驟確保程式碼中的 entrypoint.sh 檔案與 Docker 映像版本一致。
# Use CPU for DeepDoc tasks:

View File

@ -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:

View File

@ -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):

View File

@ -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

View File

@ -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({

View File

@ -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

View File

@ -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

View File

@ -4848,7 +4848,7 @@
]
},
{
"name": "JieKou.AI",
"name": "Jiekou.AI",
"logo": "",
"tags": "LLM,TEXT EMBEDDING,TEXT RE-RANK",
"status": "1",

View File

@ -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)

View File

@ -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'))[

View File

@ -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>
);

View File

@ -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) {

View File

@ -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} />;
},
);

View File

@ -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>
);
}

View File

@ -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',

View File

@ -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: '添加智能体',

View File

@ -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>
);

View File

@ -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',

View File

@ -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>
)}

View File

@ -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 {

View File

@ -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);
}

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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(),

View File

@ -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>

View File

@ -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;

View File

@ -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>
);
}

View File

@ -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>