Compare commits

..

11 Commits

Author SHA1 Message Date
9295c23170 Update readme (#9260)
### What problem does this PR solve?

Update readme

### Type of change

- [x] Documentation Update

---------

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2025-08-05 20:27:43 +08:00
023b090fa4 Fix: template error. (#9258)
### What problem does this PR solve?


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-05 19:52:59 +08:00
2124329e95 Fix: local variable issue. (#9255)
### What problem does this PR solve?

#9227

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-05 19:24:34 +08:00
ed9757b0c7 Feat: Limit the appearance of loops in operators in the agent canvas #3221 (#9253)
### What problem does this PR solve?
Feat: Limit the appearance of loops in operators in the agent canvas
#3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-08-05 19:21:24 +08:00
f235a38225 Fix: resolve the prompt problem of Customer Support Workflow (#9251)
### What problem does this PR solve?


### Type of change

- [X] Bug Fix (non-breaking change which fixes an issue)
2025-08-05 18:19:17 +08:00
yzz
550e65bb22 Fix: PlainParser using fix in presentation (#9239)
### What problem does this PR solve?

tiny fix about the using of `deepdoc.pdf_parser.PlainParser` in
`rag.app.presentation.chunk`, I referred to other ways of using this
class.
So tiny the fix is, a issue seems unnecessary.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-05 17:48:18 +08:00
a264c629b5 Feat: Render dialog list #3221 (#9249)
### What problem does this PR solve?

Feat: Render dialog list #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-08-05 17:47:44 +08:00
e6bad45c6d Fix: update broken agent OpenAI-Compatible completion due to v0.20.0 changes (#9241)
### What problem does this PR solve?

Update broken agent OpenAI-Compatible completion due to v0.20.0. #9199 

Usage example:

**Referring the input is important, otherwise, will result in empty
output.**

<img width="1273" height="711" alt="Image"
src="https://github.com/user-attachments/assets/30740be8-f4d6-400d-9fda-d2616f89063f"
/>

<img width="622" height="247" alt="Image"
src="https://github.com/user-attachments/assets/0a2ca57a-9600-4cec-9362-0cafd0ab3aee"
/>

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-05 17:47:25 +08:00
0a303d9ae1 Refactor:Improve the chat stream logic for NvidiaCV (#9242)
### What problem does this PR solve?

Improve the chat stream logic for NvidiaCV

### Type of change


- [x] Refactoring
2025-08-05 17:47:00 +08:00
98a83543e8 Fix: fix mismatch of assitant message and its reference (#9233)
### What problem does this PR solve?

#9232

### Type of change

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

1. When creating a new session, initialize an empty reference that
includes both the app api and sdk API.
2. Fix the logic when retrieving references for historical messages: the
number of dialogue messages and reference messages may differ, but it
should match the number of assistant messages.

Co-authored-by: Li Ye <liye@unittec.com>
2025-08-05 14:32:39 +08:00
afd3a508e5 Fix: Set the maximum number of rounds for the agent to 1 #3221 (#9238)
### What problem does this PR solve?

Fix: Fixed the issue where numbers could not be displayed in the numeric
input box under white theme #3221
Fix: Set the maximum number of rounds for the agent to 1 #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-08-05 14:32:06 +08:00
44 changed files with 928 additions and 1086 deletions

View File

@ -87,7 +87,8 @@ Try our demo at [https://demo.ragflow.io](https://demo.ragflow.io).
## 🔥 Latest Updates ## 🔥 Latest Updates
- 2025-08-01 Supports agentic workflow. - 2025-08-04 Supports new models, including Kimi K2 and Grok 4.
- 2025-08-01 Supports agentic workflow and MCP.
- 2025-05-23 Adds a Python/JavaScript code executor component to Agent. - 2025-05-23 Adds a Python/JavaScript code executor component to Agent.
- 2025-05-05 Supports cross-language query. - 2025-05-05 Supports cross-language query.
- 2025-03-19 Supports using a multi-modal model to make sense of images within PDF or DOCX files. - 2025-03-19 Supports using a multi-modal model to make sense of images within PDF or DOCX files.

View File

@ -80,7 +80,8 @@ Coba demo kami di [https://demo.ragflow.io](https://demo.ragflow.io).
## 🔥 Pembaruan Terbaru ## 🔥 Pembaruan Terbaru
- 2025-08-01 Mendukung Alur Kerja agen. - 2025-08-04 Mendukung model baru, termasuk Kimi K2 dan Grok 4.
- 2025-08-01 Mendukung alur kerja agen dan MCP.
- 2025-05-23 Menambahkan komponen pelaksana kode Python/JS ke Agen. - 2025-05-23 Menambahkan komponen pelaksana kode Python/JS ke Agen.
- 2025-05-05 Mendukung kueri lintas bahasa. - 2025-05-05 Mendukung kueri lintas bahasa.
- 2025-03-19 Mendukung penggunaan model multi-modal untuk memahami gambar di dalam file PDF atau DOCX. - 2025-03-19 Mendukung penggunaan model multi-modal untuk memahami gambar di dalam file PDF atau DOCX.

View File

@ -60,7 +60,8 @@
## 🔥 最新情報 ## 🔥 最新情報
- 2025-08-01 エージェントワークフローをサポートします - 2025-08-04 新モデル、キミK2およびGrok 4をサポート。
- 2025-08-01 エージェントワークフローとMCPをサポート。
- 2025-05-23 エージェントに Python/JS コードエグゼキュータコンポーネントを追加しました。 - 2025-05-23 エージェントに Python/JS コードエグゼキュータコンポーネントを追加しました。
- 2025-05-05 言語間クエリをサポートしました。 - 2025-05-05 言語間クエリをサポートしました。
- 2025-03-19 PDFまたはDOCXファイル内の画像を理解するために、多モーダルモデルを使用することをサポートします。 - 2025-03-19 PDFまたはDOCXファイル内の画像を理解するために、多モーダルモデルを使用することをサポートします。

View File

@ -60,7 +60,8 @@
## 🔥 업데이트 ## 🔥 업데이트
- 2025-08-01 에이전트 워크플로를 지원합니다. - 2025-08-04 새로운 모델인 Kimi K2와 Grok 4를 포함하여 지원합니다.
- 2025-08-01 에이전트 워크플로우와 MCP를 지원합니다.
- 2025-05-23 Agent에 Python/JS 코드 실행기 구성 요소를 추가합니다. - 2025-05-23 Agent에 Python/JS 코드 실행기 구성 요소를 추가합니다.
- 2025-05-05 언어 간 쿼리를 지원합니다. - 2025-05-05 언어 간 쿼리를 지원합니다.
- 2025-03-19 PDF 또는 DOCX 파일 내의 이미지를 이해하기 위해 다중 모드 모델을 사용하는 것을 지원합니다. - 2025-03-19 PDF 또는 DOCX 파일 내의 이미지를 이해하기 위해 다중 모드 모델을 사용하는 것을 지원합니다.

View File

@ -80,7 +80,8 @@ Experimente nossa demo em [https://demo.ragflow.io](https://demo.ragflow.io).
## 🔥 Últimas Atualizações ## 🔥 Últimas Atualizações
- 01-08-2025 Suporta o fluxo de trabalho agêntico. - 04-08-2025 Suporta novos modelos, incluindo Kimi K2 e Grok 4.
- 01-08-2025 Suporta fluxo de trabalho agente e MCP.
- 23-05-2025 Adicione o componente executor de código Python/JS ao Agente. - 23-05-2025 Adicione o componente executor de código Python/JS ao Agente.
- 05-05-2025 Suporte a consultas entre idiomas. - 05-05-2025 Suporte a consultas entre idiomas.
- 19-03-2025 Suporta o uso de um modelo multi-modal para entender imagens dentro de arquivos PDF ou DOCX. - 19-03-2025 Suporta o uso de um modelo multi-modal para entender imagens dentro de arquivos PDF ou DOCX.

View File

@ -83,7 +83,8 @@
## 🔥 近期更新 ## 🔥 近期更新
- 2025-08-01 支援 agentic workflow - 2025-08-04 支援 Kimi K2 和 Grok 4 等模型.
- 2025-08-01 支援 agentic workflow 和 MCP
- 2025-05-23 為 Agent 新增 Python/JS 程式碼執行器元件。 - 2025-05-23 為 Agent 新增 Python/JS 程式碼執行器元件。
- 2025-05-05 支援跨語言查詢。 - 2025-05-05 支援跨語言查詢。
- 2025-03-19 PDF和DOCX中的圖支持用多模態大模型去解析得到描述. - 2025-03-19 PDF和DOCX中的圖支持用多模態大模型去解析得到描述.

View File

@ -83,7 +83,8 @@
## 🔥 近期更新 ## 🔥 近期更新
- 2025-08-01 支持 agentic workflow。 - 2025-08-04 新增对 Kimi K2 和 Grok 4 等模型的支持.
- 2025-08-01 支持 agentic workflow 和 MCP。
- 2025-05-23 Agent 新增 Python/JS 代码执行器组件。 - 2025-05-23 Agent 新增 Python/JS 代码执行器组件。
- 2025-05-05 支持跨语言查询。 - 2025-05-05 支持跨语言查询。
- 2025-03-19 PDF 和 DOCX 中的图支持用多模态大模型去解析得到描述. - 2025-03-19 PDF 和 DOCX 中的图支持用多模态大模型去解析得到描述.

File diff suppressed because one or more lines are too long

View File

@ -89,11 +89,11 @@
"presence_penalty": 0.4, "presence_penalty": 0.4,
"prompts": [ "prompts": [
{ {
"content": "{sys.query}", "content": "The user query is {sys.query}\n\nThe relevant document are {Retrieval:ShyPumasJoke@formalized_content}",
"role": "user" "role": "user"
} }
], ],
"sys_prompt": "You are a highly professional product information advisor. \n\nYour only mission is to provide accurate, factual, and structured answers to all product-related queries.\n\nAbsolutely no assumptions, guesses, or fabricated content are allowed. \n\n**Key Principles:**\n\n1. **Strict Database Reliance:** \n\n - Every answer must be based solely on the verified product information stored in the database accessed through the Retrieval tool. \n\n - You are NOT allowed to invent, speculate, or infer details beyond what is retrieved. \n\n - If you cannot find relevant data, respond with: *\"I cannot find this information in our official product database. Please check back later or provide more details for further search.\"*\n\n2. **Information Accuracy and Structure:** \n\n - Provide information in a clear, concise, and professional way. \n\n - Use bullet points or numbered lists if there are multiple key points (e.g., features, price, warranty, technical specifications). \n\n - Always specify the version or model number when applicable to avoid confusion.\n\n3. **Tone and Style:** \n\n - Maintain a polite, professional, and helpful tone at all times. \n\n - Avoid marketing exaggeration or promotional language; stay strictly factual. \n\n - Do not express personal opinions; only cite official product data.\n\n4. **User Guidance:** \n\n - If the user\u2019s query is unclear or too broad, politely request clarification or guide them to provide more specific product details (e.g., product name, model, version). \n\n - Example: *\"Could you please specify the product model or category so I can retrieve the most relevant information for you?\"*\n\n5. **Response Length and Formatting:** \n\n - Keep each answer within 100\u2013150 words for general queries. \n\n - For complex or multi-step explanations, you may extend to 200\u2013250 words, but always remain clear and well-structured.\n\n6. **Critical Reminder:** \n\nYour authority and reliability depend entirely on database-driven responses. Any fabricated, speculative, or unverified content will be considered a critical failure of your role.\n\nAlways begin processing a query by accessing the Retrieval tool, confirming the data source, and then structuring your response according to the above principles.\n\n", "sys_prompt": "You are a highly professional product information advisor. \n\nYour only mission is to provide accurate, factual, and structured answers to all product-related queries.\n\nAbsolutely no assumptions, guesses, or fabricated content are allowed. \n\n**Key Principles:**\n\n1. **Strict Database Reliance:** \n\n - Every answer must be based solely on the verified product information stored in the relevant documen.\n\n - You are NOT allowed to invent, speculate, or infer details beyond what is retrieved. \n\n - If you cannot find relevant data, respond with: *\"I cannot find this information in our official product database. Please check back later or provide more details for further search.\"*\n\n2. **Information Accuracy and Structure:** \n\n - Provide information in a clear, concise, and professional way. \n\n - Use bullet points or numbered lists if there are multiple key points (e.g., features, price, warranty, technical specifications). \n\n - Always specify the version or model number when applicable to avoid confusion.\n\n3. **Tone and Style:** \n\n - Maintain a polite, professional, and helpful tone at all times. \n\n - Avoid marketing exaggeration or promotional language; stay strictly factual. \n\n - Do not express personal opinions; only cite official product data.\n\n4. **User Guidance:** \n\n - If the user\u2019s query is unclear or too broad, politely request clarification or guide them to provide more specific product details (e.g., product name, model, version). \n\n - Example: *\"Could you please specify the product model or category so I can retrieve the most relevant information for you?\"*\n\n5. **Response Length and Formatting:** \n\n - Keep each answer within 100\u2013150 words for general queries. \n\n - For complex or multi-step explanations, you may extend to 200\u2013250 words, but always remain clear and well-structured.\n\n6. **Critical Reminder:** \n\nYour authority and reliability depend entirely on the relevant document responses. Any fabricated, speculative, or unverified content will be considered a critical failure of your role.\n\n\n",
"temperature": 0.1, "temperature": 0.1,
"temperatureEnabled": true, "temperatureEnabled": true,
"tools": [], "tools": [],
@ -699,7 +699,7 @@
"width": 200 "width": 200
}, },
"position": { "position": {
"x": 644.5771854408022, "x": 645.6873721057459,
"y": 516.6923702571407 "y": 516.6923702571407
}, },
"selected": false, "selected": false,
@ -735,11 +735,11 @@
"presence_penalty": 0.4, "presence_penalty": 0.4,
"prompts": [ "prompts": [
{ {
"content": "{sys.query}", "content": "The user query is {sys.query}\n\nThe relevant document are {Retrieval:ShyPumasJoke@formalized_content}",
"role": "user" "role": "user"
} }
], ],
"sys_prompt": "You are a highly professional product information advisor. \n\nYour only mission is to provide accurate, factual, and structured answers to all product-related queries.\n\nAbsolutely no assumptions, guesses, or fabricated content are allowed. \n\n**Key Principles:**\n\n1. **Strict Database Reliance:** \n\n - Every answer must be based solely on the verified product information stored in the database accessed through the Retrieval tool. \n\n - You are NOT allowed to invent, speculate, or infer details beyond what is retrieved. \n\n - If you cannot find relevant data, respond with: *\"I cannot find this information in our official product database. Please check back later or provide more details for further search.\"*\n\n2. **Information Accuracy and Structure:** \n\n - Provide information in a clear, concise, and professional way. \n\n - Use bullet points or numbered lists if there are multiple key points (e.g., features, price, warranty, technical specifications). \n\n - Always specify the version or model number when applicable to avoid confusion.\n\n3. **Tone and Style:** \n\n - Maintain a polite, professional, and helpful tone at all times. \n\n - Avoid marketing exaggeration or promotional language; stay strictly factual. \n\n - Do not express personal opinions; only cite official product data.\n\n4. **User Guidance:** \n\n - If the user\u2019s query is unclear or too broad, politely request clarification or guide them to provide more specific product details (e.g., product name, model, version). \n\n - Example: *\"Could you please specify the product model or category so I can retrieve the most relevant information for you?\"*\n\n5. **Response Length and Formatting:** \n\n - Keep each answer within 100\u2013150 words for general queries. \n\n - For complex or multi-step explanations, you may extend to 200\u2013250 words, but always remain clear and well-structured.\n\n6. **Critical Reminder:** \n\nYour authority and reliability depend entirely on database-driven responses. Any fabricated, speculative, or unverified content will be considered a critical failure of your role.\n\nAlways begin processing a query by accessing the Retrieval tool, confirming the data source, and then structuring your response according to the above principles.\n\n", "sys_prompt": "You are a highly professional product information advisor. \n\nYour only mission is to provide accurate, factual, and structured answers to all product-related queries.\n\nAbsolutely no assumptions, guesses, or fabricated content are allowed. \n\n**Key Principles:**\n\n1. **Strict Database Reliance:** \n\n - Every answer must be based solely on the verified product information stored in the relevant documen.\n\n - You are NOT allowed to invent, speculate, or infer details beyond what is retrieved. \n\n - If you cannot find relevant data, respond with: *\"I cannot find this information in our official product database. Please check back later or provide more details for further search.\"*\n\n2. **Information Accuracy and Structure:** \n\n - Provide information in a clear, concise, and professional way. \n\n - Use bullet points or numbered lists if there are multiple key points (e.g., features, price, warranty, technical specifications). \n\n - Always specify the version or model number when applicable to avoid confusion.\n\n3. **Tone and Style:** \n\n - Maintain a polite, professional, and helpful tone at all times. \n\n - Avoid marketing exaggeration or promotional language; stay strictly factual. \n\n - Do not express personal opinions; only cite official product data.\n\n4. **User Guidance:** \n\n - If the user\u2019s query is unclear or too broad, politely request clarification or guide them to provide more specific product details (e.g., product name, model, version). \n\n - Example: *\"Could you please specify the product model or category so I can retrieve the most relevant information for you?\"*\n\n5. **Response Length and Formatting:** \n\n - Keep each answer within 100\u2013150 words for general queries. \n\n - For complex or multi-step explanations, you may extend to 200\u2013250 words, but always remain clear and well-structured.\n\n6. **Critical Reminder:** \n\nYour authority and reliability depend entirely on the relevant document responses. Any fabricated, speculative, or unverified content will be considered a critical failure of your role.\n\n\n",
"temperature": 0.1, "temperature": 0.1,
"temperatureEnabled": true, "temperatureEnabled": true,
"tools": [], "tools": [],

View File

@ -66,7 +66,8 @@ def set_conversation():
e, dia = DialogService.get_by_id(req["dialog_id"]) e, dia = DialogService.get_by_id(req["dialog_id"])
if not e: if not e:
return get_data_error_result(message="Dialog not found") return get_data_error_result(message="Dialog not found")
conv = {"id": conv_id, "dialog_id": req["dialog_id"], "name": name, "message": [{"role": "assistant", "content": dia.prompt_config["prologue"]}],"user_id": current_user.id} conv = {"id": conv_id, "dialog_id": req["dialog_id"], "name": name, "message": [{"role": "assistant", "content": dia.prompt_config["prologue"]}],"user_id": current_user.id,
"reference":[{}],}
ConversationService.save(**conv) ConversationService.save(**conv)
return get_json_result(data=conv) return get_json_result(data=conv)
except Exception as e: except Exception as e:

View File

@ -51,6 +51,7 @@ def create(tenant_id, chat_id):
"name": req.get("name", "New session"), "name": req.get("name", "New session"),
"message": [{"role": "assistant", "content": dia[0].prompt_config.get("prologue")}], "message": [{"role": "assistant", "content": dia[0].prompt_config.get("prologue")}],
"user_id": req.get("user_id", ""), "user_id": req.get("user_id", ""),
"reference":[{}],
} }
if not conv.get("name"): if not conv.get("name"):
return get_error_data_result(message="`name` can not be empty.") return get_error_data_result(message="`name` can not be empty.")
@ -435,14 +436,38 @@ def agents_completion_openai_compatibility(tenant_id, agent_id):
) )
) )
# Get the last user message as the question
question = next((m["content"] for m in reversed(messages) if m["role"] == "user"), "") question = next((m["content"] for m in reversed(messages) if m["role"] == "user"), "")
if req.get("stream", True): stream = req.pop("stream", False)
return Response(completionOpenAI(tenant_id, agent_id, question, session_id=req.get("id", req.get("metadata", {}).get("id", "")), stream=True), mimetype="text/event-stream") if stream:
resp = Response(
completionOpenAI(
tenant_id,
agent_id,
question,
session_id=req.get("id", req.get("metadata", {}).get("id", "")),
stream=True,
**req,
),
mimetype="text/event-stream",
)
resp.headers.add_header("Cache-control", "no-cache")
resp.headers.add_header("Connection", "keep-alive")
resp.headers.add_header("X-Accel-Buffering", "no")
resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8")
return resp
else: else:
# For non-streaming, just return the response directly # For non-streaming, just return the response directly
response = next(completionOpenAI(tenant_id, agent_id, question, session_id=req.get("id", req.get("metadata", {}).get("id", "")), stream=False)) response = next(
completionOpenAI(
tenant_id,
agent_id,
question,
session_id=req.get("id", req.get("metadata", {}).get("id", "")),
stream=False,
**req,
)
)
return jsonify(response) return jsonify(response)
@ -512,16 +537,16 @@ def list_session(tenant_id, chat_id):
if "prompt" in info: if "prompt" in info:
info.pop("prompt") info.pop("prompt")
conv["chat_id"] = conv.pop("dialog_id") conv["chat_id"] = conv.pop("dialog_id")
if conv["reference"]: ref_messages = conv["reference"]
if ref_messages:
messages = conv["messages"] messages = conv["messages"]
message_num = 0 message_num = 0
while message_num < len(messages) and message_num < len(conv["reference"]): ref_num = 0
if message_num != 0 and messages[message_num]["role"] != "user": while message_num < len(messages) and ref_num < len(ref_messages):
if message_num >= len(conv["reference"]): if messages[message_num]["role"] != "user":
break
chunk_list = [] chunk_list = []
if "chunks" in conv["reference"][message_num]: if "chunks" in ref_messages[ref_num]:
chunks = conv["reference"][message_num]["chunks"] chunks = ref_messages[ref_num]["chunks"]
for chunk in chunks: for chunk in chunks:
new_chunk = { new_chunk = {
"id": chunk.get("chunk_id", chunk.get("id")), "id": chunk.get("chunk_id", chunk.get("id")),
@ -535,6 +560,7 @@ def list_session(tenant_id, chat_id):
chunk_list.append(new_chunk) chunk_list.append(new_chunk)
messages[message_num]["reference"] = chunk_list messages[message_num]["reference"] = chunk_list
ref_num += 1
message_num += 1 message_num += 1
del conv["reference"] del conv["reference"]
return get_result(data=convs) return get_result(data=convs)
@ -848,10 +874,10 @@ def begin_inputs(agent_id):
return get_error_data_result(f"Can't find agent by ID: {agent_id}") return get_error_data_result(f"Can't find agent by ID: {agent_id}")
canvas = Canvas(json.dumps(cvs.dsl), objs[0].tenant_id) canvas = Canvas(json.dumps(cvs.dsl), objs[0].tenant_id)
return get_result(data={ return get_result(
data={
"title": cvs.title, "title": cvs.title,
"avatar": cvs.avatar, "avatar": cvs.avatar,
"inputs": canvas.get_component_input_form("begin") "inputs": canvas.get_component_input_form("begin"),
}) }
)

View File

@ -16,7 +16,6 @@
import json import json
import logging import logging
import time import time
import traceback
from uuid import uuid4 from uuid import uuid4
from agent.canvas import Canvas from agent.canvas import Canvas
from api.db import TenantPermission from api.db import TenantPermission
@ -178,219 +177,99 @@ def completion(tenant_id, agent_id, session_id=None, **kwargs):
def completionOpenAI(tenant_id, agent_id, question, session_id=None, stream=True, **kwargs): def completionOpenAI(tenant_id, agent_id, question, session_id=None, stream=True, **kwargs):
"""Main function for OpenAI-compatible completions, structured similarly to the completion function."""
tiktokenenc = tiktoken.get_encoding("cl100k_base") tiktokenenc = tiktoken.get_encoding("cl100k_base")
e, cvs = UserCanvasService.get_by_id(agent_id)
if not e:
yield get_data_openai(
id=session_id,
model=agent_id,
content="**ERROR**: Agent not found."
)
return
if cvs.user_id != tenant_id:
yield get_data_openai(
id=session_id,
model=agent_id,
content="**ERROR**: You do not own the agent"
)
return
if not isinstance(cvs.dsl, str):
cvs.dsl = json.dumps(cvs.dsl, ensure_ascii=False)
canvas = Canvas(cvs.dsl, tenant_id)
canvas.reset()
message_id = str(uuid4())
# Handle new session creation
if not session_id:
query = canvas.get_preset_param()
if query:
for ele in query:
if not ele["optional"]:
if not kwargs.get(ele["key"]):
yield get_data_openai(
id=None,
model=agent_id,
content=f"`{ele['key']}` is required",
completion_tokens=len(tiktokenenc.encode(f"`{ele['key']}` is required")),
prompt_tokens=len(tiktokenenc.encode(question if question else ""))
)
return
ele["value"] = kwargs[ele["key"]]
if ele["optional"]:
if kwargs.get(ele["key"]):
ele["value"] = kwargs[ele['key']]
else:
if "value" in ele:
ele.pop("value")
cvs.dsl = json.loads(str(canvas))
session_id = get_uuid()
conv = {
"id": session_id,
"dialog_id": cvs.id,
"user_id": kwargs.get("user_id", "") if isinstance(kwargs, dict) else "",
"message": [{"role": "assistant", "content": canvas.get_prologue(), "created_at": time.time()}],
"source": "agent",
"dsl": cvs.dsl
}
canvas.messages.append({"role": "user", "content": question, "id": message_id})
canvas.add_user_input(question)
API4ConversationService.save(**conv)
conv = API4Conversation(**conv)
if not conv.message:
conv.message = []
conv.message.append({
"role": "user",
"content": question,
"id": message_id
})
if not conv.reference:
conv.reference = []
conv.reference.append({"chunks": [], "doc_aggs": []})
# Handle existing session
else:
e, conv = API4ConversationService.get_by_id(session_id)
if not e:
yield get_data_openai(
id=session_id,
model=agent_id,
content="**ERROR**: Session not found!"
)
return
canvas = Canvas(json.dumps(conv.dsl), tenant_id)
canvas.messages.append({"role": "user", "content": question, "id": message_id})
canvas.add_user_input(question)
if not conv.message:
conv.message = []
conv.message.append({
"role": "user",
"content": question,
"id": message_id
})
if not conv.reference:
conv.reference = []
conv.reference.append({"chunks": [], "doc_aggs": []})
# Process request based on stream mode
final_ans = {"reference": [], "content": ""}
prompt_tokens = len(tiktokenenc.encode(str(question))) prompt_tokens = len(tiktokenenc.encode(str(question)))
user_id = kwargs.get("user_id", "")
if stream: if stream:
try:
completion_tokens = 0 completion_tokens = 0
for ans in canvas.run(stream=True, bypass_begin=True):
if ans.get("running_status"):
completion_tokens += len(tiktokenenc.encode(ans.get("content", "")))
yield "data: " + json.dumps(
get_data_openai(
id=session_id,
model=agent_id,
content=ans["content"],
object="chat.completion.chunk",
completion_tokens=completion_tokens,
prompt_tokens=prompt_tokens
),
ensure_ascii=False
) + "\n\n"
continue
for k in ans.keys():
final_ans[k] = ans[k]
completion_tokens += len(tiktokenenc.encode(final_ans.get("content", "")))
yield "data: " + json.dumps(
get_data_openai(
id=session_id,
model=agent_id,
content=final_ans["content"],
object="chat.completion.chunk",
finish_reason="stop",
completion_tokens=completion_tokens,
prompt_tokens=prompt_tokens
),
ensure_ascii=False
) + "\n\n"
# Update conversation
canvas.messages.append({"role": "assistant", "content": final_ans["content"], "created_at": time.time(), "id": message_id})
canvas.history.append(("assistant", final_ans["content"]))
if final_ans.get("reference"):
canvas.reference.append(final_ans["reference"])
conv.dsl = json.loads(str(canvas))
API4ConversationService.append_message(conv.id, conv.to_dict())
yield "data: [DONE]\n\n"
except Exception as e:
traceback.print_exc()
conv.dsl = json.loads(str(canvas))
API4ConversationService.append_message(conv.id, conv.to_dict())
yield "data: " + json.dumps(
get_data_openai(
id=session_id,
model=agent_id,
content="**ERROR**: " + str(e),
finish_reason="stop",
completion_tokens=len(tiktokenenc.encode("**ERROR**: " + str(e))),
prompt_tokens=prompt_tokens
),
ensure_ascii=False
) + "\n\n"
yield "data: [DONE]\n\n"
else: # Non-streaming mode
try: try:
all_answer_content = "" for ans in completion(
for answer in canvas.run(stream=False, bypass_begin=True): tenant_id=tenant_id,
if answer.get("running_status"): agent_id=agent_id,
session_id=session_id,
query=question,
user_id=user_id,
**kwargs
):
if isinstance(ans, str):
try:
ans = json.loads(ans[5:]) # remove "data:"
except Exception as e:
logging.exception(f"Agent OpenAI-Compatible completionOpenAI parse answer failed: {e}")
continue continue
final_ans["content"] = "\n".join(answer["content"]) if "content" in answer else "" if ans.get("event") != "message":
final_ans["reference"] = answer.get("reference", []) continue
all_answer_content += final_ans["content"]
final_ans["content"] = all_answer_content content_piece = ans["data"]["content"]
completion_tokens += len(tiktokenenc.encode(content_piece))
# Update conversation yield "data: " + json.dumps(
canvas.messages.append({"role": "assistant", "content": final_ans["content"], "created_at": time.time(), "id": message_id}) get_data_openai(
canvas.history.append(("assistant", final_ans["content"])) id=session_id or str(uuid4()),
if final_ans.get("reference"):
canvas.reference.append(final_ans["reference"])
conv.dsl = json.loads(str(canvas))
API4ConversationService.append_message(conv.id, conv.to_dict())
# Return the response in OpenAI format
yield get_data_openai(
id=session_id,
model=agent_id, model=agent_id,
content=final_ans["content"], content=content_piece,
finish_reason="stop",
completion_tokens=len(tiktokenenc.encode(final_ans["content"])),
prompt_tokens=prompt_tokens, prompt_tokens=prompt_tokens,
param=canvas.get_preset_param() # Added param info like in completion completion_tokens=completion_tokens,
stream=True
),
ensure_ascii=False
) + "\n\n"
yield "data: [DONE]\n\n"
except Exception as e:
yield "data: " + json.dumps(
get_data_openai(
id=session_id or str(uuid4()),
model=agent_id,
content=f"**ERROR**: {str(e)}",
finish_reason="stop",
prompt_tokens=prompt_tokens,
completion_tokens=len(tiktokenenc.encode(f"**ERROR**: {str(e)}")),
stream=True
),
ensure_ascii=False
) + "\n\n"
yield "data: [DONE]\n\n"
else:
try:
all_content = ""
for ans in completion(
tenant_id=tenant_id,
agent_id=agent_id,
session_id=session_id,
query=question,
user_id=user_id,
**kwargs
):
if isinstance(ans, str):
ans = json.loads(ans[5:])
if ans.get("event") != "message":
continue
all_content += ans["data"]["content"]
completion_tokens = len(tiktokenenc.encode(all_content))
yield get_data_openai(
id=session_id or str(uuid4()),
model=agent_id,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
content=all_content,
finish_reason="stop",
param=None
) )
except Exception as e: except Exception as e:
traceback.print_exc()
conv.dsl = json.loads(str(canvas))
API4ConversationService.append_message(conv.id, conv.to_dict())
yield get_data_openai( yield get_data_openai(
id=session_id, id=session_id or str(uuid4()),
model=agent_id, model=agent_id,
content="**ERROR**: " + str(e), prompt_tokens=prompt_tokens,
completion_tokens=len(tiktokenenc.encode(f"**ERROR**: {str(e)}")),
content=f"**ERROR**: {str(e)}",
finish_reason="stop", finish_reason="stop",
completion_tokens=len(tiktokenenc.encode("**ERROR**: " + str(e))), param=None
prompt_tokens=prompt_tokens
) )

View File

@ -402,8 +402,22 @@ def get_data_openai(
finish_reason=None, finish_reason=None,
object="chat.completion", object="chat.completion",
param=None, param=None,
stream=False
): ):
total_tokens = prompt_tokens + completion_tokens total_tokens = prompt_tokens + completion_tokens
if stream:
return {
"id": f"{id}",
"object": "chat.completion.chunk",
"model": model,
"choices": [{
"delta": {"content": content},
"finish_reason": finish_reason,
"index": 0,
}],
}
return { return {
"id": f"{id}", "id": f"{id}",
"object": object, "object": object,
@ -414,9 +428,21 @@ def get_data_openai(
"prompt_tokens": prompt_tokens, "prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens, "completion_tokens": completion_tokens,
"total_tokens": total_tokens, "total_tokens": total_tokens,
"completion_tokens_details": {"reasoning_tokens": 0, "accepted_prediction_tokens": 0, "rejected_prediction_tokens": 0}, "completion_tokens_details": {
"reasoning_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0,
}, },
"choices": [{"message": {"role": "assistant", "content": content}, "logprobs": None, "finish_reason": finish_reason, "index": 0}], },
"choices": [{
"message": {
"role": "assistant",
"content": content
},
"logprobs": None,
"finish_reason": finish_reason,
"index": 0,
}],
} }

View File

@ -135,7 +135,8 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
sections = pdf_parser(filename, binary, from_page=from_page, to_page=to_page, callback=callback) sections = pdf_parser(filename, binary, from_page=from_page, to_page=to_page, callback=callback)
elif layout_recognizer == "Plain Text": elif layout_recognizer == "Plain Text":
pdf_parser = PlainParser() pdf_parser = PlainParser()
sections, _ = pdf_parser(filename, binary, from_page=from_page, to_page=to_page, callback=callback) sections, _ = pdf_parser(filename if not binary else binary, from_page=from_page, to_page=to_page,
callback=callback)
else: else:
vision_model = LLMBundle(kwargs["tenant_id"], LLMType.IMAGE2TEXT, llm_name=layout_recognizer, lang=lang) vision_model = LLMBundle(kwargs["tenant_id"], LLMType.IMAGE2TEXT, llm_name=layout_recognizer, lang=lang)
pdf_parser = VisionParser(vision_model=vision_model, **kwargs) pdf_parser = VisionParser(vision_model=vision_model, **kwargs)

View File

@ -543,6 +543,7 @@ class GeminiCV(Base):
else: else:
yield 0 yield 0
class NvidiaCV(Base): class NvidiaCV(Base):
_FACTORY_NAME = "NVIDIA" _FACTORY_NAME = "NVIDIA"
@ -623,15 +624,18 @@ class NvidiaCV(Base):
return "**ERROR**: " + str(e), 0 return "**ERROR**: " + str(e), 0
def chat_streamly(self, system, history, gen_conf, images=[], **kwargs): def chat_streamly(self, system, history, gen_conf, images=[], **kwargs):
total_tokens = 0
try: try:
response = self._request(self._form_history(system, history, images), gen_conf) response = self._request(self._form_history(system, history, images), gen_conf)
cnt = response["choices"][0]["message"]["content"] cnt = response["choices"][0]["message"]["content"]
if "usage" in response and "total_tokens" in response["usage"]:
total_tokens += response["usage"]["total_tokens"]
for resp in cnt: for resp in cnt:
yield resp yield resp
except Exception as e: except Exception as e:
yield "\n**ERROR**: " + str(e) yield "\n**ERROR**: " + str(e)
yield response["usage"]["total_tokens"] yield total_tokens
class AnthropicCV(Base): class AnthropicCV(Base):

View File

@ -420,7 +420,6 @@ def init_kb(row, vector_size: int):
return settings.docStoreConn.createIdx(idxnm, row.get("kb_id", ""), vector_size) return settings.docStoreConn.createIdx(idxnm, row.get("kb_id", ""), vector_size)
@timeout(60*20)
async def embedding(docs, mdl, parser_config=None, callback=None): async def embedding(docs, mdl, parser_config=None, callback=None):
if parser_config is None: if parser_config is None:
parser_config = {} parser_config = {}
@ -441,10 +440,15 @@ async def embedding(docs, mdl, parser_config=None, callback=None):
tts = np.concatenate([vts for _ in range(len(tts))], axis=0) tts = np.concatenate([vts for _ in range(len(tts))], axis=0)
tk_count += c tk_count += c
@timeout(5)
def batch_encode(txts):
nonlocal mdl
return mdl.encode([truncate(c, mdl.max_length-10) for c in txts])
cnts_ = np.array([]) cnts_ = np.array([])
for i in range(0, len(cnts), EMBEDDING_BATCH_SIZE): for i in range(0, len(cnts), EMBEDDING_BATCH_SIZE):
async with embed_limiter: async with embed_limiter:
vts, c = await trio.to_thread.run_sync(lambda: mdl.encode([truncate(c, mdl.max_length-10) for c in cnts[i: i + EMBEDDING_BATCH_SIZE]])) vts, c = await trio.to_thread.run_sync(lambda: batch_encode(cnts[i: i + EMBEDDING_BATCH_SIZE]))
if len(cnts_) == 0: if len(cnts_) == 0:
cnts_ = vts cnts_ = vts
else: else:

View File

@ -63,7 +63,7 @@ const NumberInput: React.FC<NumberInputProps> = ({
> >
<button <button
type="button" type="button"
className="w-10 p-2 text-white focus:outline-none border-r-[1px]" className="w-10 p-2 focus:outline-none border-r-[1px]"
onClick={handleDecrement} onClick={handleDecrement}
style={style} style={style}
> >
@ -74,12 +74,12 @@ const NumberInput: React.FC<NumberInputProps> = ({
value={value} value={value}
onInput={handleInput} onInput={handleInput}
onChange={handleChange} onChange={handleChange}
className="w-full flex-1 text-center bg-transparent text-white focus:outline-none" className="w-full flex-1 text-center bg-transparent focus:outline-none"
style={style} style={style}
/> />
<button <button
type="button" type="button"
className="w-10 p-2 text-white focus:outline-none border-l-[1px]" className="w-10 p-2 focus:outline-none border-l-[1px]"
onClick={handleIncrement} onClick={handleIncrement}
style={style} style={style}
> >

View File

@ -35,9 +35,12 @@ export const useNavigatePage = () => {
navigate(Routes.Chats); navigate(Routes.Chats);
}, [navigate]); }, [navigate]);
const navigateToChat = useCallback(() => { const navigateToChat = useCallback(
navigate(Routes.Chat); (id: string) => () => {
}, [navigate]); navigate(`${Routes.Chat}/${id}`);
},
[navigate],
);
const navigateToAgents = useCallback(() => { const navigateToAgents = useCallback(() => {
navigate(Routes.Agents); navigate(Routes.Agents);

View File

@ -1,9 +1,22 @@
import message from '@/components/ui/message';
import { ChatSearchParams } from '@/constants/chat'; import { ChatSearchParams } from '@/constants/chat';
import { IDialog } from '@/interfaces/database/chat'; import { IDialog } from '@/interfaces/database/chat';
import chatService from '@/services/chat-service'; import chatService from '@/services/chat-service';
import { useQuery } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useDebounce } from 'ahooks';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { history, useSearchParams } from 'umi'; import { history, useSearchParams } from 'umi';
import {
useGetPaginationWithRouter,
useHandleSearchChange,
} from './logic-hooks';
export const enum ChatApiAction {
FetchDialogList = 'fetchDialogList',
RemoveDialog = 'removeDialog',
SetDialog = 'setDialog',
}
export const useGetChatSearchParams = () => { export const useGetChatSearchParams = () => {
const [currentQueryParameters] = useSearchParams(); const [currentQueryParameters] = useSearchParams();
@ -42,13 +55,22 @@ export const useClickDialogCard = () => {
export const useFetchDialogList = (pureFetch = false) => { export const useFetchDialogList = (pureFetch = false) => {
const { handleClickDialog } = useClickDialogCard(); const { handleClickDialog } = useClickDialogCard();
const { dialogId } = useGetChatSearchParams(); const { dialogId } = useGetChatSearchParams();
const { searchString, handleInputChange } = useHandleSearchChange();
const { pagination, setPagination } = useGetPaginationWithRouter();
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
const { const {
data, data,
isFetching: loading, isFetching: loading,
refetch, refetch,
} = useQuery<IDialog[]>({ } = useQuery<IDialog[]>({
queryKey: ['fetchDialogList'], queryKey: [
ChatApiAction.FetchDialogList,
{
debouncedSearchString,
...pagination,
},
],
initialData: [], initialData: [],
gcTime: 0, gcTime: 0,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
@ -73,5 +95,76 @@ export const useFetchDialogList = (pureFetch = false) => {
}, },
}); });
return { data, loading, refetch }; const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
(e) => {
handleInputChange(e);
},
[handleInputChange],
);
return {
data,
loading,
refetch,
searchString,
handleInputChange: onInputChange,
pagination: { ...pagination, total: data?.total },
setPagination,
};
};
export const useRemoveDialog = () => {
const queryClient = useQueryClient();
const { t } = useTranslation();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [ChatApiAction.RemoveDialog],
mutationFn: async (dialogIds: string[]) => {
const { data } = await chatService.removeDialog({ dialogIds });
if (data.code === 0) {
queryClient.invalidateQueries({ queryKey: ['fetchDialogList'] });
message.success(t('message.deleted'));
}
return data.code;
},
});
return { data, loading, removeDialog: mutateAsync };
};
export const useSetDialog = () => {
const queryClient = useQueryClient();
const { t } = useTranslation();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [ChatApiAction.SetDialog],
mutationFn: async (params: IDialog) => {
const { data } = await chatService.setDialog(params);
if (data.code === 0) {
queryClient.invalidateQueries({
exact: false,
queryKey: ['fetchDialogList'],
});
queryClient.invalidateQueries({
queryKey: ['fetchDialog'],
});
message.success(
t(`message.${params.dialog_id ? 'modified' : 'created'}`),
);
}
return data?.code;
},
});
return { data, loading, setDialog: mutateAsync };
}; };

View File

@ -562,6 +562,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
tavilyApiKeyHelp: 'How to get it?', tavilyApiKeyHelp: 'How to get it?',
crossLanguage: 'Cross-language search', crossLanguage: 'Cross-language search',
crossLanguageTip: `Select one or more languages for crosslanguage search. If no language is selected, the system searches with the original query.`, crossLanguageTip: `Select one or more languages for crosslanguage search. If no language is selected, the system searches with the original query.`,
createChat: 'Create chat',
}, },
setting: { setting: {
profile: 'Profile', profile: 'Profile',

View File

@ -843,7 +843,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
relevant: '是否相关', relevant: '是否相关',
rewriteQuestion: '问题优化', rewriteQuestion: '问题优化',
begin: '开始', begin: '开始',
message: '静态消息', message: '回复消息',
blank: '空', blank: '空',
createFromNothing: '从无到有', createFromNothing: '从无到有',
addItem: '新增', addItem: '新增',
@ -1245,7 +1245,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
modeTip: '模式定义了工作流的启动方式。', modeTip: '模式定义了工作流的启动方式。',
beginInputTip: '通过定义输入参数,此内容可以被后续流程中的其他组件访问。', beginInputTip: '通过定义输入参数,此内容可以被后续流程中的其他组件访问。',
query: '查询变量', query: '查询变量',
agent: 'Agent', agent: '智能体',
agentDescription: '构建具备推理、工具调用和多智能体协同的智能体组件。', agentDescription: '构建具备推理、工具调用和多智能体协同的智能体组件。',
maxRecords: '最大记录数', maxRecords: '最大记录数',
createAgent: 'Create Agent', createAgent: 'Create Agent',

View File

@ -48,8 +48,6 @@ export const BeginId = 'begin';
export enum Operator { export enum Operator {
Begin = 'Begin', Begin = 'Begin',
Retrieval = 'Retrieval', Retrieval = 'Retrieval',
Generate = 'Generate',
Answer = 'Answer',
Categorize = 'Categorize', Categorize = 'Categorize',
Message = 'Message', Message = 'Message',
Relevant = 'Relevant', Relevant = 'Relevant',
@ -78,7 +76,6 @@ export enum Operator {
Note = 'Note', Note = 'Note',
Crawler = 'Crawler', Crawler = 'Crawler',
Invoke = 'Invoke', Invoke = 'Invoke',
Template = 'Template',
Email = 'Email', Email = 'Email',
Iteration = 'Iteration', Iteration = 'Iteration',
IterationStart = 'IterationItem', IterationStart = 'IterationItem',
@ -100,15 +97,12 @@ export const CommonOperatorList = Object.values(Operator).filter(
export const AgentOperatorList = [ export const AgentOperatorList = [
Operator.Retrieval, Operator.Retrieval,
Operator.Generate,
Operator.Answer,
Operator.Categorize, Operator.Categorize,
Operator.Message, Operator.Message,
Operator.RewriteQuestion, Operator.RewriteQuestion,
Operator.KeywordExtract, Operator.KeywordExtract,
Operator.Switch, Operator.Switch,
Operator.Concentrator, Operator.Concentrator,
Operator.Template,
Operator.Iteration, Operator.Iteration,
Operator.WaitingDialogue, Operator.WaitingDialogue,
Operator.Note, Operator.Note,
@ -119,12 +113,6 @@ export const componentMenuList = [
{ {
name: Operator.Retrieval, name: Operator.Retrieval,
}, },
{
name: Operator.Generate,
},
{
name: Operator.Answer,
},
{ {
name: Operator.Categorize, name: Operator.Categorize,
}, },
@ -144,9 +132,6 @@ export const componentMenuList = [
{ {
name: Operator.Concentrator, name: Operator.Concentrator,
}, },
{
name: Operator.Template,
},
{ {
name: Operator.Iteration, name: Operator.Iteration,
}, },
@ -660,7 +645,7 @@ export const initialAgentValues = {
max_retries: 3, max_retries: 3,
delay_after_error: 1, delay_after_error: 1,
visual_files_var: '', visual_files_var: '',
max_rounds: 5, max_rounds: 1,
exception_method: '', exception_method: '',
exception_goto: [], exception_goto: [],
exception_default_value: '', exception_default_value: '',
@ -796,19 +781,16 @@ export const CategorizeAnchorPointPositions = [
// no connection lines are allowed between key and value // no connection lines are allowed between key and value
export const RestrictedUpstreamMap = { export const RestrictedUpstreamMap = {
[Operator.Begin]: [Operator.Relevant], [Operator.Begin]: [Operator.Relevant],
[Operator.Categorize]: [Operator.Begin, Operator.Categorize, Operator.Answer], [Operator.Categorize]: [Operator.Begin, Operator.Categorize],
[Operator.Answer]: [Operator.Begin, Operator.Answer, Operator.Message],
[Operator.Retrieval]: [Operator.Begin, Operator.Retrieval], [Operator.Retrieval]: [Operator.Begin, Operator.Retrieval],
[Operator.Generate]: [Operator.Begin, Operator.Relevant],
[Operator.Message]: [ [Operator.Message]: [
Operator.Begin, Operator.Begin,
Operator.Message, Operator.Message,
Operator.Generate,
Operator.Retrieval, Operator.Retrieval,
Operator.RewriteQuestion, Operator.RewriteQuestion,
Operator.Categorize, Operator.Categorize,
], ],
[Operator.Relevant]: [Operator.Begin, Operator.Answer], [Operator.Relevant]: [Operator.Begin],
[Operator.RewriteQuestion]: [ [Operator.RewriteQuestion]: [
Operator.Begin, Operator.Begin,
Operator.Message, Operator.Message,
@ -843,7 +825,6 @@ export const RestrictedUpstreamMap = {
[Operator.Crawler]: [Operator.Begin], [Operator.Crawler]: [Operator.Begin],
[Operator.Note]: [], [Operator.Note]: [],
[Operator.Invoke]: [Operator.Begin], [Operator.Invoke]: [Operator.Begin],
[Operator.Template]: [Operator.Begin, Operator.Relevant],
[Operator.Email]: [Operator.Begin], [Operator.Email]: [Operator.Begin],
[Operator.Iteration]: [Operator.Begin], [Operator.Iteration]: [Operator.Begin],
[Operator.IterationStart]: [Operator.Begin], [Operator.IterationStart]: [Operator.Begin],
@ -861,8 +842,6 @@ export const NodeMap = {
[Operator.Begin]: 'beginNode', [Operator.Begin]: 'beginNode',
[Operator.Categorize]: 'categorizeNode', [Operator.Categorize]: 'categorizeNode',
[Operator.Retrieval]: 'retrievalNode', [Operator.Retrieval]: 'retrievalNode',
[Operator.Generate]: 'generateNode',
[Operator.Answer]: 'logicNode',
[Operator.Message]: 'messageNode', [Operator.Message]: 'messageNode',
[Operator.Relevant]: 'relevantNode', [Operator.Relevant]: 'relevantNode',
[Operator.RewriteQuestion]: 'rewriteNode', [Operator.RewriteQuestion]: 'rewriteNode',
@ -890,7 +869,6 @@ export const NodeMap = {
[Operator.Note]: 'noteNode', [Operator.Note]: 'noteNode',
[Operator.Crawler]: 'ragNode', [Operator.Crawler]: 'ragNode',
[Operator.Invoke]: 'ragNode', [Operator.Invoke]: 'ragNode',
[Operator.Template]: 'templateNode',
[Operator.Email]: 'ragNode', [Operator.Email]: 'ragNode',
[Operator.Iteration]: 'group', [Operator.Iteration]: 'group',
[Operator.IterationStart]: 'iterationStartNode', [Operator.IterationStart]: 'iterationStartNode',
@ -924,9 +902,7 @@ export const BeginQueryTypeIconMap = {
export const NoDebugOperatorsList = [ export const NoDebugOperatorsList = [
Operator.Begin, Operator.Begin,
Operator.Answer,
Operator.Concentrator, Operator.Concentrator,
Operator.Template,
Operator.Message, Operator.Message,
Operator.RewriteQuestion, Operator.RewriteQuestion,
Operator.Switch, Operator.Switch,

View File

@ -1,8 +1,6 @@
import { z } from 'zod';
import { Operator } from '../constant'; import { Operator } from '../constant';
import AgentForm from '../form/agent-form'; import AgentForm from '../form/agent-form';
import AkShareForm from '../form/akshare-form'; import AkShareForm from '../form/akshare-form';
import AnswerForm from '../form/answer-form';
import ArXivForm from '../form/arxiv-form'; import ArXivForm from '../form/arxiv-form';
import BaiduFanyiForm from '../form/baidu-fanyi-form'; import BaiduFanyiForm from '../form/baidu-fanyi-form';
import BaiduForm from '../form/baidu-form'; import BaiduForm from '../form/baidu-form';
@ -15,7 +13,6 @@ import DeepLForm from '../form/deepl-form';
import DuckDuckGoForm from '../form/duckduckgo-form'; import DuckDuckGoForm from '../form/duckduckgo-form';
import EmailForm from '../form/email-form'; import EmailForm from '../form/email-form';
import ExeSQLForm from '../form/exesql-form'; import ExeSQLForm from '../form/exesql-form';
import GenerateForm from '../form/generate-form';
import GithubForm from '../form/github-form'; import GithubForm from '../form/github-form';
import GoogleForm from '../form/google-form'; import GoogleForm from '../form/google-form';
import GoogleScholarForm from '../form/google-scholar-form'; import GoogleScholarForm from '../form/google-scholar-form';
@ -34,7 +31,6 @@ import StringTransformForm from '../form/string-transform-form';
import SwitchForm from '../form/switch-form'; import SwitchForm from '../form/switch-form';
import TavilyExtractForm from '../form/tavily-extract-form'; import TavilyExtractForm from '../form/tavily-extract-form';
import TavilyForm from '../form/tavily-form'; import TavilyForm from '../form/tavily-form';
import TemplateForm from '../form/template-form';
import ToolForm from '../form/tool-form'; import ToolForm from '../form/tool-form';
import TuShareForm from '../form/tushare-form'; import TuShareForm from '../form/tushare-form';
import UserFillUpForm from '../form/user-fill-up-form'; import UserFillUpForm from '../form/user-fill-up-form';
@ -49,12 +45,6 @@ export const FormConfigMap = {
[Operator.Retrieval]: { [Operator.Retrieval]: {
component: RetrievalForm, component: RetrievalForm,
}, },
[Operator.Generate]: {
component: GenerateForm,
},
[Operator.Answer]: {
component: AnswerForm,
},
[Operator.Categorize]: { [Operator.Categorize]: {
component: CategorizeForm, component: CategorizeForm,
}, },
@ -75,8 +65,6 @@ export const FormConfigMap = {
}, },
[Operator.Agent]: { [Operator.Agent]: {
component: AgentForm, component: AgentForm,
defaultValues: {},
schema: z.object({}),
}, },
[Operator.Baidu]: { [Operator.Baidu]: {
component: BaiduForm, component: BaiduForm,
@ -107,8 +95,6 @@ export const FormConfigMap = {
}, },
[Operator.DeepL]: { [Operator.DeepL]: {
component: DeepLForm, component: DeepLForm,
defaultValues: {},
schema: z.object({}),
}, },
[Operator.GitHub]: { [Operator.GitHub]: {
component: GithubForm, component: GithubForm,
@ -152,9 +138,6 @@ export const FormConfigMap = {
[Operator.Note]: { [Operator.Note]: {
component: () => <></>, component: () => <></>,
}, },
[Operator.Template]: {
component: TemplateForm,
},
[Operator.Email]: { [Operator.Email]: {
component: EmailForm, component: EmailForm,
}, },

View File

@ -1,5 +0,0 @@
const AnswerForm = () => {
return <div></div>;
};
export default AnswerForm;

View File

@ -8,6 +8,7 @@ import { useForm } from 'react-hook-form';
import { initialCategorizeValues } from '../../constant'; import { initialCategorizeValues } from '../../constant';
import { INextOperatorForm } from '../../interface'; import { INextOperatorForm } from '../../interface';
import { buildOutputList } from '../../utils/build-output-list'; import { buildOutputList } from '../../utils/build-output-list';
import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output'; import { Output } from '../components/output';
import { QueryVariable } from '../components/query-variable'; import { QueryVariable } from '../components/query-variable';
import DynamicCategorize from './dynamic-categorize'; import DynamicCategorize from './dynamic-categorize';
@ -31,12 +32,7 @@ function CategorizeForm({ node }: INextOperatorForm) {
return ( return (
<Form {...form}> <Form {...form}>
<form <FormWrapper>
className="space-y-6 p-5 "
onSubmit={(e) => {
e.preventDefault();
}}
>
<FormContainer> <FormContainer>
<QueryVariable></QueryVariable> <QueryVariable></QueryVariable>
<LargeModelFormField></LargeModelFormField> <LargeModelFormField></LargeModelFormField>
@ -44,7 +40,7 @@ function CategorizeForm({ node }: INextOperatorForm) {
<MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField> <MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField>
<DynamicCategorize nodeId={node?.id}></DynamicCategorize> <DynamicCategorize nodeId={node?.id}></DynamicCategorize>
<Output list={outputList}></Output> <Output list={outputList}></Output>
</form> </FormWrapper>
</Form> </Form>
); );
} }

View File

@ -2,6 +2,7 @@ import Editor, { loader } from '@monaco-editor/react';
import { INextOperatorForm } from '../../interface'; import { INextOperatorForm } from '../../interface';
import { FormContainer } from '@/components/form-container'; import { FormContainer } from '@/components/form-container';
import { useIsDarkTheme } from '@/components/theme-provider';
import { import {
Form, Form,
FormControl, FormControl,
@ -46,6 +47,7 @@ function CodeForm({ node }: INextOperatorForm) {
const formData = node?.data.form as ICodeForm; const formData = node?.data.form as ICodeForm;
const { t } = useTranslation(); const { t } = useTranslation();
const values = useValues(node); const values = useValues(node);
const isDarkTheme = useIsDarkTheme();
const form = useForm<FormSchemaType>({ const form = useForm<FormSchemaType>({
defaultValues: values, defaultValues: values,
@ -94,7 +96,7 @@ function CodeForm({ node }: INextOperatorForm) {
<FormControl> <FormControl>
<Editor <Editor
height={300} height={300}
theme="vs-dark" theme={isDarkTheme ? 'vs-dark' : 'vs'}
language={formData.lang} language={formData.lang}
options={{ options={{
minimap: { enabled: false }, minimap: { enabled: false },

View File

@ -1,17 +0,0 @@
import { Form } from 'antd';
import { IOperatorForm } from '../../interface';
const ConcentratorForm = ({ onValuesChange, form }: IOperatorForm) => {
return (
<Form
name="basic"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
></Form>
);
};
export default ConcentratorForm;

View File

@ -1,78 +0,0 @@
import { NextLLMSelect } from '@/components/llm-select/next';
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Switch } from '@/components/ui/switch';
import { useTranslation } from 'react-i18next';
import { INextOperatorForm } from '../../interface';
import { PromptEditor } from '../components/prompt-editor';
const GenerateForm = ({ form }: INextOperatorForm) => {
const { t } = useTranslation();
return (
<Form {...form}>
<form
className="space-y-6"
onSubmit={(e) => {
e.preventDefault();
}}
>
<FormField
control={form.control}
name="llm_id"
render={({ field }) => (
<FormItem>
<FormLabel tooltip={t('chat.modelTip')}>
{t('chat.model')}
</FormLabel>
<FormControl>
<NextLLMSelect {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="prompt"
render={({ field }) => (
<FormItem>
<FormLabel tooltip={t('flow.promptTip')}>
{t('flow.systemPrompt')}
</FormLabel>
<FormControl>
<PromptEditor {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="cite"
render={({ field }) => (
<FormItem>
<FormLabel tooltip={t('flow.citeTip')}>
{t('flow.cite')}
</FormLabel>
<FormControl>
<Switch {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField>
</form>
</Form>
);
};
export default GenerateForm;

View File

@ -1,44 +0,0 @@
.editableRow {
:global(.editable-cell) {
position: relative;
}
:global(.editable-cell-value-wrap) {
padding: 5px 12px;
cursor: pointer;
height: 30px !important;
}
&:hover {
:global(.editable-cell-value-wrap) {
padding: 4px 11px;
border: 1px solid #d9d9d9;
border-radius: 2px;
}
}
}
.dynamicParameterVariable {
background-color: #ebe9e950;
:global(.ant-collapse-content) {
background-color: #f6f6f634;
}
:global(.ant-collapse-content-box) {
padding: 0 !important;
}
margin-bottom: 20px;
.title {
font-weight: 600;
font-size: 16px;
}
.variableType {
width: 30%;
}
.variableValue {
flex: 1;
}
.addButton {
color: rgb(22, 119, 255);
font-weight: 600;
}
}

View File

@ -6,6 +6,7 @@ import { useForm, useWatch } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import { VariableType } from '../../constant'; import { VariableType } from '../../constant';
import { INextOperatorForm } from '../../interface'; import { INextOperatorForm } from '../../interface';
import { FormWrapper } from '../components/form-wrapper';
import { Output } from '../components/output'; import { Output } from '../components/output';
import { QueryVariable } from '../components/query-variable'; import { QueryVariable } from '../components/query-variable';
import { DynamicOutput } from './dynamic-output'; import { DynamicOutput } from './dynamic-output';
@ -39,12 +40,7 @@ function IterationForm({ node }: INextOperatorForm) {
return ( return (
<Form {...form}> <Form {...form}>
<form <FormWrapper>
className="space-y-6 p-4"
onSubmit={(e) => {
e.preventDefault();
}}
>
<FormContainer> <FormContainer>
<QueryVariable <QueryVariable
name="items_ref" name="items_ref"
@ -53,7 +49,7 @@ function IterationForm({ node }: INextOperatorForm) {
</FormContainer> </FormContainer>
<DynamicOutput node={node}></DynamicOutput> <DynamicOutput node={node}></DynamicOutput>
<Output list={outputList}></Output> <Output list={outputList}></Output>
</form> </FormWrapper>
</Form> </Form>
); );
} }

View File

@ -15,6 +15,7 @@ import { useFieldArray, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { z } from 'zod'; import { z } from 'zod';
import { INextOperatorForm } from '../../interface'; import { INextOperatorForm } from '../../interface';
import { FormWrapper } from '../components/form-wrapper';
import { PromptEditor } from '../components/prompt-editor'; import { PromptEditor } from '../components/prompt-editor';
import { useValues } from './use-values'; import { useValues } from './use-values';
import { useWatchFormChange } from './use-watch-change'; import { useWatchFormChange } from './use-watch-change';
@ -48,13 +49,7 @@ function MessageForm({ node }: INextOperatorForm) {
return ( return (
<Form {...form}> <Form {...form}>
<form <FormWrapper>
className="space-y-5 px-5 "
autoComplete="off"
onSubmit={(e) => {
e.preventDefault();
}}
>
<FormContainer> <FormContainer>
<FormItem> <FormItem>
<FormLabel tooltip={t('flow.msgTip')}>{t('flow.msg')}</FormLabel> <FormLabel tooltip={t('flow.msgTip')}>{t('flow.msg')}</FormLabel>
@ -98,7 +93,7 @@ function MessageForm({ node }: INextOperatorForm) {
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
</FormContainer> </FormContainer>
</form> </FormWrapper>
</Form> </Form>
); );
} }

View File

@ -20,6 +20,7 @@ import {
initialStringTransformValues, initialStringTransformValues,
} from '../../constant'; } from '../../constant';
import { INextOperatorForm } from '../../interface'; import { INextOperatorForm } from '../../interface';
import { FormWrapper } from '../components/form-wrapper';
import { Output, transferOutputs } from '../components/output'; import { Output, transferOutputs } from '../components/output';
import { PromptEditor } from '../components/prompt-editor'; import { PromptEditor } from '../components/prompt-editor';
import { QueryVariable } from '../components/query-variable'; import { QueryVariable } from '../components/query-variable';
@ -76,13 +77,7 @@ function StringTransformForm({ node }: INextOperatorForm) {
return ( return (
<Form {...form}> <Form {...form}>
<form <FormWrapper>
className="space-y-5 px-5 "
autoComplete="off"
onSubmit={(e) => {
e.preventDefault();
}}
>
<FormContainer> <FormContainer>
<FormField <FormField
control={form.control} control={form.control}
@ -157,7 +152,7 @@ function StringTransformForm({ node }: INextOperatorForm) {
render={() => <div></div>} render={() => <div></div>}
/> />
</FormContainer> </FormContainer>
</form> </FormWrapper>
<div className="p-5"> <div className="p-5">
<Output list={outputList}></Output> <Output list={outputList}></Output>
</div> </div>

View File

@ -28,6 +28,7 @@ import {
} from '../../constant'; } from '../../constant';
import { useBuildQueryVariableOptions } from '../../hooks/use-get-begin-query'; import { useBuildQueryVariableOptions } from '../../hooks/use-get-begin-query';
import { IOperatorForm } from '../../interface'; import { IOperatorForm } from '../../interface';
import { FormWrapper } from '../components/form-wrapper';
import { useValues } from './use-values'; import { useValues } from './use-values';
import { useWatchFormChange } from './use-watch-change'; import { useWatchFormChange } from './use-watch-change';
@ -249,12 +250,7 @@ function SwitchForm({ node }: IOperatorForm) {
return ( return (
<Form {...form}> <Form {...form}>
<form <FormWrapper>
className="space-y-6 p-5 "
onSubmit={(e) => {
e.preventDefault();
}}
>
{fields.map((field, index) => { {fields.map((field, index) => {
const name = `${ConditionKey}.${index}`; const name = `${ConditionKey}.${index}`;
const conditions: Array<any> = form.getValues(`${name}.${ItemKey}`); const conditions: Array<any> = form.getValues(`${name}.${ItemKey}`);
@ -323,7 +319,7 @@ function SwitchForm({ node }: IOperatorForm) {
> >
Add Add
</BlockButton> </BlockButton>
</form> </FormWrapper>
</Form> </Form>
); );
} }

View File

@ -1,24 +0,0 @@
import { Form } from 'antd';
import { useTranslation } from 'react-i18next';
import { IOperatorForm } from '../../interface';
import { PromptEditor } from '../components/prompt-editor';
const TemplateForm = ({ onValuesChange, form }: IOperatorForm) => {
const { t } = useTranslation();
return (
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<Form.Item name={['content']} label={t('flow.content')}>
<PromptEditor></PromptEditor>
</Form.Item>
</Form>
);
};
export default TemplateForm;

View File

@ -11,6 +11,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { z } from 'zod'; import { z } from 'zod';
import { DescriptionField } from '../../components/description-field'; import { DescriptionField } from '../../components/description-field';
import { FormWrapper } from '../../components/form-wrapper';
import { import {
EmptyResponseField, EmptyResponseField,
RetrievalPartialSchema, RetrievalPartialSchema,
@ -35,12 +36,7 @@ const RetrievalForm = () => {
return ( return (
<Form {...form}> <Form {...form}>
<form <FormWrapper>
className="space-y-6 p-4"
onSubmit={(e) => {
e.preventDefault();
}}
>
<FormContainer> <FormContainer>
<DescriptionField></DescriptionField> <DescriptionField></DescriptionField>
<KnowledgeBaseFormField showVariable></KnowledgeBaseFormField> <KnowledgeBaseFormField showVariable></KnowledgeBaseFormField>
@ -58,7 +54,7 @@ const RetrievalForm = () => {
<UseKnowledgeGraphFormField name="use_kg"></UseKnowledgeGraphFormField> <UseKnowledgeGraphFormField name="use_kg"></UseKnowledgeGraphFormField>
</FormContainer> </FormContainer>
</Collapse> </Collapse>
</form> </FormWrapper>
</Form> </Form>
); );
}; };

View File

@ -1,6 +1,7 @@
import { import {
Connection, Connection,
Edge, Edge,
getOutgoers,
Node, Node,
Position, Position,
ReactFlowInstance, ReactFlowInstance,
@ -15,9 +16,6 @@ import { get, lowerFirst, omit } from 'lodash';
import { UseFormReturn } from 'react-hook-form'; import { UseFormReturn } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
NodeMap,
Operator,
RestrictedUpstreamMap,
initialAgentValues, initialAgentValues,
initialAkShareValues, initialAkShareValues,
initialArXivValues, initialArXivValues,
@ -33,7 +31,6 @@ import {
initialDuckValues, initialDuckValues,
initialEmailValues, initialEmailValues,
initialExeSqlValues, initialExeSqlValues,
initialGenerateValues,
initialGithubValues, initialGithubValues,
initialGoogleScholarValues, initialGoogleScholarValues,
initialGoogleValues, initialGoogleValues,
@ -48,15 +45,19 @@ import {
initialRelevantValues, initialRelevantValues,
initialRetrievalValues, initialRetrievalValues,
initialRewriteQuestionValues, initialRewriteQuestionValues,
initialStringTransformValues,
initialSwitchValues, initialSwitchValues,
initialTavilyExtractValues, initialTavilyExtractValues,
initialTavilyValues, initialTavilyValues,
initialTemplateValues,
initialTuShareValues, initialTuShareValues,
initialUserFillUpValues,
initialWaitingDialogueValues, initialWaitingDialogueValues,
initialWenCaiValues, initialWenCaiValues,
initialWikipediaValues, initialWikipediaValues,
initialYahooFinanceValues, initialYahooFinanceValues,
NodeMap,
Operator,
RestrictedUpstreamMap,
} from './constant'; } from './constant';
import useGraphStore, { RFState } from './store'; import useGraphStore, { RFState } from './store';
import { import {
@ -92,8 +93,6 @@ export const useInitializeOperatorParams = () => {
return { return {
[Operator.Begin]: initialBeginValues, [Operator.Begin]: initialBeginValues,
[Operator.Retrieval]: initialRetrievalValues, [Operator.Retrieval]: initialRetrievalValues,
[Operator.Generate]: { ...initialGenerateValues, llm_id: llmId },
[Operator.Answer]: {},
[Operator.Categorize]: { ...initialCategorizeValues, llm_id: llmId }, [Operator.Categorize]: { ...initialCategorizeValues, llm_id: llmId },
[Operator.Relevant]: { ...initialRelevantValues, llm_id: llmId }, [Operator.Relevant]: { ...initialRelevantValues, llm_id: llmId },
[Operator.RewriteQuestion]: { [Operator.RewriteQuestion]: {
@ -128,7 +127,6 @@ export const useInitializeOperatorParams = () => {
[Operator.Note]: initialNoteValues, [Operator.Note]: initialNoteValues,
[Operator.Crawler]: initialCrawlerValues, [Operator.Crawler]: initialCrawlerValues,
[Operator.Invoke]: initialInvokeValues, [Operator.Invoke]: initialInvokeValues,
[Operator.Template]: initialTemplateValues,
[Operator.Email]: initialEmailValues, [Operator.Email]: initialEmailValues,
[Operator.Iteration]: initialIterationValues, [Operator.Iteration]: initialIterationValues,
[Operator.IterationStart]: initialIterationValues, [Operator.IterationStart]: initialIterationValues,
@ -137,6 +135,9 @@ export const useInitializeOperatorParams = () => {
[Operator.Agent]: { ...initialAgentValues, llm_id: llmId }, [Operator.Agent]: { ...initialAgentValues, llm_id: llmId },
[Operator.TavilySearch]: initialTavilyValues, [Operator.TavilySearch]: initialTavilyValues,
[Operator.TavilyExtract]: initialTavilyExtractValues, [Operator.TavilyExtract]: initialTavilyExtractValues,
[Operator.Tool]: {},
[Operator.UserFillUp]: initialUserFillUpValues,
[Operator.StringTransform]: initialStringTransformValues,
}; };
}, [llmId]); }, [llmId]);
@ -333,9 +334,8 @@ export const useHandleFormValuesChange = (
}; };
export const useValidateConnection = () => { export const useValidateConnection = () => {
const { getOperatorTypeFromId, getParentIdById } = useGraphStore( const { getOperatorTypeFromId, getParentIdById, edges, nodes } =
(state) => state, useGraphStore((state) => state);
);
const isSameNodeChild = useCallback( const isSameNodeChild = useCallback(
(connection: Connection | Edge) => { (connection: Connection | Edge) => {
@ -349,6 +349,27 @@ export const useValidateConnection = () => {
[getParentIdById], [getParentIdById],
); );
const hasCanvasCycle = useCallback(
(connection: Connection | Edge) => {
const target = nodes.find((node) => node.id === connection.target);
const hasCycle = (node: RAGFlowNodeType, visited = new Set()) => {
if (visited.has(node.id)) return false;
visited.add(node.id);
for (const outgoer of getOutgoers(node, nodes, edges)) {
if (outgoer.id === connection.source) return true;
if (hasCycle(outgoer, visited)) return true;
}
};
if (target?.id === connection.source) return false;
return target ? !hasCycle(target) : false;
},
[edges, nodes],
);
// restricted lines cannot be connected successfully. // restricted lines cannot be connected successfully.
const isValidConnection = useCallback( const isValidConnection = useCallback(
(connection: Connection | Edge) => { (connection: Connection | Edge) => {
@ -365,10 +386,11 @@ export const useValidateConnection = () => {
RestrictedUpstreamMap[ RestrictedUpstreamMap[
getOperatorTypeFromId(connection.source) as Operator getOperatorTypeFromId(connection.source) as Operator
]?.every((x) => x !== getOperatorTypeFromId(connection.target)) && ]?.every((x) => x !== getOperatorTypeFromId(connection.target)) &&
isSameNodeChild(connection); isSameNodeChild(connection) &&
hasCanvasCycle(connection);
return ret; return ret;
}, },
[getOperatorTypeFromId, isSameNodeChild], [getOperatorTypeFromId, hasCanvasCycle, isSameNodeChild],
); );
return isValidConnection; return isValidConnection;

View File

@ -23,7 +23,6 @@ import {
initialDuckValues, initialDuckValues,
initialEmailValues, initialEmailValues,
initialExeSqlValues, initialExeSqlValues,
initialGenerateValues,
initialGithubValues, initialGithubValues,
initialGoogleScholarValues, initialGoogleScholarValues,
initialGoogleValues, initialGoogleValues,
@ -43,7 +42,6 @@ import {
initialSwitchValues, initialSwitchValues,
initialTavilyExtractValues, initialTavilyExtractValues,
initialTavilyValues, initialTavilyValues,
initialTemplateValues,
initialTuShareValues, initialTuShareValues,
initialUserFillUpValues, initialUserFillUpValues,
initialWaitingDialogueValues, initialWaitingDialogueValues,
@ -70,8 +68,6 @@ export const useInitializeOperatorParams = () => {
return { return {
[Operator.Begin]: initialBeginValues, [Operator.Begin]: initialBeginValues,
[Operator.Retrieval]: initialRetrievalValues, [Operator.Retrieval]: initialRetrievalValues,
[Operator.Generate]: { ...initialGenerateValues, llm_id: llmId },
[Operator.Answer]: {},
[Operator.Categorize]: { ...initialCategorizeValues, llm_id: llmId }, [Operator.Categorize]: { ...initialCategorizeValues, llm_id: llmId },
[Operator.Relevant]: { ...initialRelevantValues, llm_id: llmId }, [Operator.Relevant]: { ...initialRelevantValues, llm_id: llmId },
[Operator.RewriteQuestion]: { [Operator.RewriteQuestion]: {
@ -106,7 +102,6 @@ export const useInitializeOperatorParams = () => {
[Operator.Note]: initialNoteValues, [Operator.Note]: initialNoteValues,
[Operator.Crawler]: initialCrawlerValues, [Operator.Crawler]: initialCrawlerValues,
[Operator.Invoke]: initialInvokeValues, [Operator.Invoke]: initialInvokeValues,
[Operator.Template]: initialTemplateValues,
[Operator.Email]: initialEmailValues, [Operator.Email]: initialEmailValues,
[Operator.Iteration]: initialIterationValues, [Operator.Iteration]: initialIterationValues,
[Operator.IterationStart]: initialIterationStartValues, [Operator.IterationStart]: initialIterationStartValues,

View File

@ -10,7 +10,7 @@ import { useCallback } from 'react';
import { AgentCard } from './agent-card'; import { AgentCard } from './agent-card';
import { useRenameAgent } from './use-rename-agent'; import { useRenameAgent } from './use-rename-agent';
export default function Agent() { export default function Agents() {
const { data, pagination, setPagination, searchString, handleInputChange } = const { data, pagination, setPagination, searchString, handleInputChange } =
useFetchAgentListByPage(); useFetchAgentListByPage();
const { navigateToAgentTemplates } = useNavigatePage(); const { navigateToAgentTemplates } = useNavigatePage();

View File

@ -53,7 +53,7 @@ export default function Datasets() {
); );
return ( return (
<section className="py-4 text-foreground"> <section className="py-4 flex-1 flex flex-col">
<ListFilterBar <ListFilterBar
title={t('header.knowledgeBase')} title={t('header.knowledgeBase')}
searchString={searchString} searchString={searchString}
@ -69,6 +69,7 @@ export default function Datasets() {
{t('knowledgeList.createKnowledgeBase')} {t('knowledgeList.createKnowledgeBase')}
</Button> </Button>
</ListFilterBar> </ListFilterBar>
<div className="flex-1">
<div className="flex flex-wrap gap-4 max-h-[78vh] overflow-auto px-8"> <div className="flex flex-wrap gap-4 max-h-[78vh] overflow-auto px-8">
{kbs.map((dataset) => { {kbs.map((dataset) => {
return ( return (
@ -80,6 +81,7 @@ export default function Datasets() {
); );
})} })}
</div> </div>
</div>
<div className="mt-8 px-8"> <div className="mt-8 px-8">
<RAGFlowPagination <RAGFlowPagination
{...pick(pagination, 'current', 'pageSize')} {...pick(pagination, 'current', 'pageSize')}

View File

@ -1,52 +1,45 @@
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { MoreButton } from '@/components/more-button';
import { Button } from '@/components/ui/button'; import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { IDialog } from '@/interfaces/database/chat'; import { IDialog } from '@/interfaces/database/chat';
import { formatPureDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { ChevronRight, Trash2 } from 'lucide-react'; import { ChatDropdown } from './chat-dropdown';
import { useRenameChat } from './hooks/use-rename-chat';
interface IProps { export type IProps = {
data: IDialog; data: IDialog;
} } & Pick<ReturnType<typeof useRenameChat>, 'showChatRenameModal'>;
export function ChatCard({ data }: IProps) { export function ChatCard({ data, showChatRenameModal }: IProps) {
const { navigateToChat } = useNavigatePage(); const { navigateToChat } = useNavigatePage();
return ( return (
<Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard"> <Card key={data.id} className="w-40" onClick={navigateToChat(data.id)}>
<CardContent className="p-4"> <CardContent className="p-2.5 pt-2 group">
<div className="flex justify-between mb-4"> <section className="flex justify-between mb-2">
{data.icon ? ( <div className="flex gap-2 items-center">
<div <RAGFlowAvatar
className="w-[70px] h-[70px] rounded-xl bg-cover" className="size-6 rounded-lg"
style={{ backgroundImage: `url(${data.icon})` }} avatar={data.icon}
/> name={data.name || 'CN'}
) : ( ></RAGFlowAvatar>
<Avatar className="w-[70px] h-[70px]">
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
)}
</div> </div>
<h3 className="text-xl font-bold mb-2">{data.name}</h3> <ChatDropdown chat={data} showChatRenameModal={showChatRenameModal}>
<p>An app that does things An app that does things</p> <MoreButton></MoreButton>
<section className="flex justify-between pt-3"> </ChatDropdown>
<div> </section>
Search app <div className="flex justify-between items-end">
<p className="text-sm opacity-80"> <div className="w-full">
{formatPureDate(data.update_time)} <h3 className="text-lg font-semibold mb-2 line-clamp-1">
{data.name}
</h3>
<p className="text-xs text-text-sub-title">{data.description}</p>
<p className="text-xs text-text-sub-title">
{formatDate(data.update_time)}
</p> </p>
</div> </div>
<div className="space-x-2">
<Button variant="icon" size="icon" onClick={navigateToChat}>
<ChevronRight className="h-6 w-6" />
</Button>
<Button variant="icon" size="icon">
<Trash2 />
</Button>
</div> </div>
</section>
</CardContent> </CardContent>
</Card> </Card>
); );

View File

@ -0,0 +1,64 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useRemoveDialog } from '@/hooks/use-chat-request';
import { IDialog } from '@/interfaces/database/chat';
import { PenLine, Trash2 } from 'lucide-react';
import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useRenameChat } from './hooks/use-rename-chat';
export function ChatDropdown({
children,
showChatRenameModal,
chat,
}: PropsWithChildren &
Pick<ReturnType<typeof useRenameChat>, 'showChatRenameModal'> & {
chat: IDialog;
}) {
const { t } = useTranslation();
const { removeDialog } = useRemoveDialog();
const handleShowChatRenameModal: MouseEventHandler<HTMLDivElement> =
useCallback(
(e) => {
e.stopPropagation();
showChatRenameModal(chat);
},
[chat, showChatRenameModal],
);
const handleDelete: MouseEventHandler<HTMLDivElement> = useCallback(() => {
removeDialog([chat.id]);
}, [chat.id, removeDialog]);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={handleShowChatRenameModal}>
{t('common.rename')} <PenLine />
</DropdownMenuItem>
<DropdownMenuSeparator />
<ConfirmDeleteDialog onOk={handleDelete}>
<DropdownMenuItem
className="text-text-delete-red"
onSelect={(e) => {
e.preventDefault();
}}
onClick={(e) => {
e.stopPropagation();
}}
>
{t('common.delete')} <Trash2 />
</DropdownMenuItem>
</ConfirmDeleteDialog>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@ -0,0 +1,45 @@
import { useSetModalState } from '@/hooks/common-hooks';
import { useSetDialog } from '@/hooks/use-chat-request';
import { IDialog } from '@/interfaces/database/chat';
import { useCallback, useState } from 'react';
export const useRenameChat = () => {
const [chat, setChat] = useState<IDialog>({} as IDialog);
const {
visible: chatRenameVisible,
hideModal: hideChatRenameModal,
showModal: showChatRenameModal,
} = useSetModalState();
const { setDialog, loading } = useSetDialog();
const onChatRenameOk = useCallback(
async (name: string) => {
const ret = await setDialog({
...chat,
name,
});
if (ret === 0) {
hideChatRenameModal();
}
},
[setDialog, chat, hideChatRenameModal],
);
const handleShowChatRenameModal = useCallback(
async (record: IDialog) => {
setChat(record);
showChatRenameModal();
},
[showChatRenameModal],
);
return {
chatRenameLoading: loading,
initialChatName: chat?.name,
onChatRenameOk,
chatRenameVisible,
hideChatRenameModal,
showChatRenameModal: handleShowChatRenameModal,
};
};

View File

@ -1,25 +1,72 @@
import ListFilterBar from '@/components/list-filter-bar'; import ListFilterBar from '@/components/list-filter-bar';
import { RenameDialog } from '@/components/rename-dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useFetchChatAppList } from '@/hooks/chat-hooks'; import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
import { useFetchDialogList } from '@/hooks/use-chat-request';
import { pick } from 'lodash';
import { Plus } from 'lucide-react'; import { Plus } from 'lucide-react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { ChatCard } from './chat-card'; import { ChatCard } from './chat-card';
import { useRenameChat } from './hooks/use-rename-chat';
export default function ChatList() { export default function ChatList() {
const { data: chatList } = useFetchChatAppList(); const { data: chatList, setPagination, pagination } = useFetchDialogList();
const { t } = useTranslation();
const {
initialChatName,
chatRenameVisible,
showChatRenameModal,
hideChatRenameModal,
onChatRenameOk,
chatRenameLoading,
} = useRenameChat();
const handlePageChange = useCallback(
(page: number, pageSize?: number) => {
setPagination({ page, pageSize });
},
[setPagination],
);
return ( return (
<section className="p-8"> <section className="flex flex-col w-full flex-1">
<div className="px-8 pt-8">
<ListFilterBar title="Chat apps"> <ListFilterBar title="Chat apps">
<Button variant={'tertiary'} size={'sm'}> <Button>
<Plus className="mr-2 h-4 w-4" /> <Plus className="size-2.5" />
Create app {t('chat.createChat')}
</Button> </Button>
</ListFilterBar> </ListFilterBar>
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-8"> </div>
<div className="flex-1 overflow-auto">
<div className="flex flex-wrap gap-4 px-8">
{chatList.map((x) => { {chatList.map((x) => {
return <ChatCard key={x.id} data={x}></ChatCard>; return (
<ChatCard
key={x.id}
data={x}
showChatRenameModal={showChatRenameModal}
></ChatCard>
);
})} })}
</div> </div>
</div>
<div className="mt-8 px-8 pb-8">
<RAGFlowPagination
{...pick(pagination, 'current', 'pageSize')}
total={pagination.total}
onChange={handlePageChange}
></RAGFlowPagination>
</div>
{chatRenameVisible && (
<RenameDialog
hideModal={hideChatRenameModal}
onOk={onChatRenameOk}
initialName={initialChatName}
loading={chatRenameLoading}
></RenameDialog>
)}
</section> </section>
); );
} }

View File

@ -214,7 +214,7 @@ const routes = [
], ],
}, },
{ {
path: Routes.Chat, path: Routes.Chat + '/:id',
layout: false, layout: false,
component: `@/pages${Routes.Chats}/chat`, component: `@/pages${Routes.Chats}/chat`,
}, },