Compare commits

...

29 Commits

Author SHA1 Message Date
cc167ae619 Fix: Display the invited icon in the header #9634 (#9659)
### What problem does this PR solve?

Fix: Display the invited icon in the header #9634

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-22 15:05:56 +08:00
f8847e7bcd Fix: embedded search AI summary (#9658)
### What problem does this PR solve?

Fix search app AI summary ERROR: 'dict' object has no attribute 'split'.
#9649

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-22 12:55:29 +08:00
3baebd709b Refactoring: Agent completions API change response structure (#9631)
### What problem does this PR solve?

Resolve #9549 and #9436 , In v0.20.x,Agent completions API changed a
lot,such as without reference and so on

### Type of change

- [x] Refactoring
2025-08-22 12:04:15 +08:00
3e6a4b2628 Fix: Document Previewer is not working #9606 (#9656)
### What problem does this PR solve?
Fix: Document Previewer is not working #9606
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-22 12:03:51 +08:00
312635cb13 Refactor: based on async await to handle Redis when raptor (#9576)
### What problem does this PR solve?

based on async await to handle Redis when raptor

### Type of change

- [x] Refactoring
- [x] Performance Improvement
2025-08-22 10:58:02 +08:00
756d454122 fix(sdk): add default empty dict for metadata_condition (#9640)
### What problem does this PR solve?

### Type of change

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

---------

Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
2025-08-22 10:57:48 +08:00
a4cab371fa Update fr.ts - RAPTOR Issue prompt (#9646)
Removed a line break causing problems with execution in Raptor.

### What problem does this PR solve?

When I activate Raptor without changing anything in French, I encounter
a problem that I don't have with the English version. I noticed in the
logs that there was an extra line break, so I suggest removing it.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-22 09:54:49 +08:00
0d7e52338e Fix: Fixed an issue where knowledge base could not be shared #9634 (#9642)
### What problem does this PR solve?

Fix: Fixed an issue where knowledge base could not be shared #9634

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-22 09:34:11 +08:00
4110f7f5ce Fix: The buttons at the bottom of the dataset settings page are not visible on small screens #9638 (#9639)
### What problem does this PR solve?

Fix: The buttons at the bottom of the dataset settings page are not
visible on small screens #9638
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-21 19:25:14 +08:00
0af57ff772 fix(dataset, next-chats): Fix data form data acquisition logic And Optimize the chat settings interface and add language selection (#9629)
### What problem does this PR solve?

fix(dataset): data form data acquisition logic
fix(next-chats): Optimize the chat settings interface and add language
selection

- Replace form.formControl.trigger with form.trigger
- Use form.getValues() instead of form.formState.values
- Add language selection to support multiple languages
- Add default chat settings values
- Add new settings: icon, description, knowledge base ID, etc.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
- [x] New Feature (non-breaking change which adds functionality)
- [ ] Documentation Update
- [ ] Refactoring
- [ ] Performance Improvement
- [ ] Other (please describe):
2025-08-21 16:57:46 +08:00
0bd58038a8 Fixes (web): Optimized search page style and functionality #3221 (#9627)
### What problem does this PR solve?

Fixes (web): Optimized search page style and functionality #3221

- Updated search page and view title styles
- Modified dataset list and multi-select control styles
- Optimized text field and button styles
- Updated filter button icons
- Adjusted metadata filter styles
- Added default descriptions for the smart assistant

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-21 16:57:14 +08:00
0cbcfcfedf Chore: Update infinity-sdk from 0.6.0.dev4 to 0.6.0.dev5 (#9628)
### What problem does this PR solve?

Bump infinity-sdk dependency to the latest development version
(0.6.0.dev5) in both pyproject.toml and uv.lock files to incorporate
recent changes and fixes from the SDK.

### Type of change

- [x] Other (please describe): Update deps
2025-08-21 16:56:57 +08:00
fbdde0259a Feat: Allow users to parse documents directly after uploading files #3221 (#9633)
### What problem does this PR solve?

Feat: Allow users to parse documents directly after uploading files
#3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-08-21 16:56:22 +08:00
d482173c9b Fix (style): Optimized Datasets color scheme and layout #3221 (#9620)
### What problem does this PR solve?


Fix (style): Optimized Datasets color scheme and layout #3221

- Updated background and text colors for multiple components

- Adjusted some layout structures, such as the paging position of
dataset tables

- Unified status icons and color mapping

- Optimized responsive layout to improve compatibility across different
screen sizes

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-21 12:14:56 +08:00
929dc97509 Fix: duplicated role... (#9622)
### What problem does this PR solve?

#9611
#9603 #9597

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-21 12:14:43 +08:00
30005c0203 Fix: Remove the file size and quantity restrictions of the upload control #9613 #9598 (#9618)
### What problem does this PR solve?

Fix: Remove the file size and quantity restrictions of the upload
control #9613 #9598

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-21 10:54:17 +08:00
382458ace7 Feat: advanced markdown parsing (#9607)
### What problem does this PR solve?

Using AST parsing to handle markdown more accurately, preventing
components from being cut off by chunking. #9564

<img width="1746" height="993" alt="image"
src="https://github.com/user-attachments/assets/4aaf4bf6-5714-4d48-a9cf-864f59633f7f"
/>

<img width="1739" height="982" alt="image"
src="https://github.com/user-attachments/assets/dc00233f-7a55-434f-bbb7-74ce7f57a6cf"
/>

<img width="559" height="100" alt="image"
src="https://github.com/user-attachments/assets/4a556b5b-d9c6-4544-a486-8ac342bd504e"
/>


### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2025-08-21 09:36:18 +08:00
4080f6a54a Feature (web): Optimize dataset pages and segmented components #3221 (#9605)
### What problem does this PR solve?

Feature (web): Optimize dataset pages and segmented components #3221
-Add the activeClassName property to Segmented components to customize
the selected state style
-Update the icons and captions of the relevant components on the dataset
page
-Modify the parsing status column title of the dataset table
-Optimize the Segmented component style of the homepage application
section

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-21 09:32:04 +08:00
09570c7eef Feat: expand the capabilities of the MCP Server (#8707)
### What problem does this PR solve?

Expand the capabilities of the MCP Server. #8644.

Special thanks to @Drasek, this change is largely based on his original
implementation, it is super neat and well-structured to me. I basically
just integrated his code into the codebase with minimal modifications.

My main contribution is implementing a proper cache layer for dataset
and document metadata, using the LRU strategy with a 300s ± random 30s
TTL. The original code did not actually perform caching.

### Type of change

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

---------

Co-authored-by: Caspar Armster <caspar@armster.de>
2025-08-20 19:30:25 +08:00
312f1a0477 Fix: enlarge raptor timeout limits. (#9600)
### What problem does this PR solve?


### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-20 17:29:15 +08:00
1ca226e43b Feat: Updated some colors according to the design draft #3221 (#9599)
### What problem does this PR solve?

Feat: Updated some colors according to the design draft #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-08-20 16:32:29 +08:00
830cda6a3a Fix (web): Optimize text display effect #3221 (#9594)
### What problem does this PR solve?

Fix (web): Optimize text display effect
-Add text ellipsis and overflow hidden classes to the HomeCard component
to achieve text overflow hiding and ellipsis effects
-Add text ellipsis and overflow hidden classes to the DatasetSidebar
component to improve the display of dataset names

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-20 15:42:21 +08:00
c66dbbe433 Fix: Fixed the issue where the save button at the bottom of the chat page could not be displayed on small screens #3221 (#9596)
### What problem does this PR solve?

Fix: Fixed the issue where the save button at the bottom of the chat
page could not be displayed on small screens #3221

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-20 15:42:09 +08:00
3b218b2dc0 fix:passing empty database array when updating assistant (#9570)
### What problem does this PR solve?

When the `dataset_ids` parameter is omitted in the **update assistant**
request, Passing an empty array `[]` triggers a misleading
message"Dataset use different embedding models", while omitting the
field does not.
To fix this, we:
- Provide a default empty list: `ids = req.get("dataset_ids", [])`.  
- Replace the `is not None` check with a truthy check: `if ids:`.

**Files changed**  
`api/apps/sdk/chat.py`  
- L153: `ids = req.get("dataset_ids")` → `ids = req.get("dataset_ids",
[])`
- L156: `if ids is not None:` → `if ids:`

### Type of change
- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-20 13:40:05 +08:00
d58ef6127f Fix:KeyError: 'globals' KeyError: 'globals' (#9571)
### What problem does this PR solve?

https://github.com/infiniflow/ragflow/issues/9545
add backward compatible logics

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-20 13:39:38 +08:00
55173c7201 Fix (web): Update the style of segmented controls and add metallic texture gradients (#9591)
### What problem does this PR solve?

Fix (web): Update the style of segmented controls and add metallic
texture gradients #3221
-Modified the selected state style of Segmented components, adding
metallic texture gradient and lower border
-Added a metallic gradient background image in tailwind.diag.js
-Added the -- metallic variable in tailwind.css to define metallic
texture colors

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-20 13:39:23 +08:00
f860bdf0ad Revert "Feat: reference should also be list after 0.20.x" (#9592)
Reverts infiniflow/ragflow#9582
2025-08-20 13:38:57 +08:00
997627861a Feat: reference should also be list after 0.20.x (#9582)
### What problem does this PR solve?

In 0.19.0 reference is list,and it should be a list,otherwise last
conversation's reference will be lost

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-20 13:38:14 +08:00
9f9d32d2cd Feat: Make the old page accessible via URL #3221 (#9589)
### What problem does this PR solve?

Feat: Make the old page accessible via URL #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-08-20 13:37:06 +08:00
77 changed files with 1530 additions and 397 deletions

View File

@ -131,7 +131,16 @@ class Canvas:
self.path = self.dsl["path"] self.path = self.dsl["path"]
self.history = self.dsl["history"] self.history = self.dsl["history"]
self.globals = self.dsl["globals"] if "globals" in self.dsl:
self.globals = self.dsl["globals"]
else:
self.globals = {
"sys.query": "",
"sys.user_id": "",
"sys.conversation_turns": 0,
"sys.files": []
}
self.retrieval = self.dsl["retrieval"] self.retrieval = self.dsl["retrieval"]
self.memory = self.dsl.get("memory", []) self.memory = self.dsl.get("memory", [])
@ -417,7 +426,7 @@ class Canvas:
convs = [] convs = []
if window_size <= 0: if window_size <= 0:
return convs return convs
for role, obj in self.history[window_size * -1:]: for role, obj in self.history[window_size * -2:]:
if isinstance(obj, dict): if isinstance(obj, dict):
convs.append({"role": role, "content": obj.get("content", "")}) convs.append({"role": role, "content": obj.get("content", "")})
else: else:

View File

@ -36,7 +36,7 @@ _IS_RAW_CONF = "_is_raw_conf"
class ComponentParamBase(ABC): class ComponentParamBase(ABC):
def __init__(self): def __init__(self):
self.message_history_window_size = 22 self.message_history_window_size = 13
self.inputs = {} self.inputs = {}
self.outputs = {} self.outputs = {}
self.description = "" self.description = ""

View File

@ -18,11 +18,8 @@ import logging
import os import os
import re import re
from typing import Any, Generator from typing import Any, Generator
import json_repair import json_repair
from copy import deepcopy
from functools import partial from functools import partial
from api.db import LLMType from api.db import LLMType
from api.db.services.llm_service import LLMBundle from api.db.services.llm_service import LLMBundle
from api.db.services.tenant_llm_service import TenantLLMService from api.db.services.tenant_llm_service import TenantLLMService
@ -130,7 +127,7 @@ class LLM(ComponentBase):
args = {} args = {}
vars = self.get_input_elements() if not self._param.debug_inputs else self._param.debug_inputs vars = self.get_input_elements() if not self._param.debug_inputs else self._param.debug_inputs
prompt = self._param.sys_prompt sys_prompt = self._param.sys_prompt
for k, o in vars.items(): for k, o in vars.items():
args[k] = o["value"] args[k] = o["value"]
if not isinstance(args[k], str): if not isinstance(args[k], str):
@ -141,14 +138,18 @@ class LLM(ComponentBase):
self.set_input_value(k, args[k]) self.set_input_value(k, args[k])
msg = self._canvas.get_history(self._param.message_history_window_size)[:-1] msg = self._canvas.get_history(self._param.message_history_window_size)[:-1]
msg.extend(deepcopy(self._param.prompts)) for p in self._param.prompts:
prompt = self.string_format(prompt, args) if msg and msg[-1]["role"] == p["role"]:
continue
msg.append(p)
sys_prompt = self.string_format(sys_prompt, args)
for m in msg: for m in msg:
m["content"] = self.string_format(m["content"], args) m["content"] = self.string_format(m["content"], args)
if self._param.cite and self._canvas.get_reference()["chunks"]: if self._param.cite and self._canvas.get_reference()["chunks"]:
prompt += citation_prompt() sys_prompt += citation_prompt()
return prompt, msg return sys_prompt, msg
def _generate(self, msg:list[dict], **kwargs) -> str: def _generate(self, msg:list[dict], **kwargs) -> str:
if not self.imgs: if not self.imgs:

View File

@ -150,10 +150,10 @@ def update(tenant_id, chat_id):
if not DialogService.query(tenant_id=tenant_id, id=chat_id, status=StatusEnum.VALID.value): if not DialogService.query(tenant_id=tenant_id, id=chat_id, status=StatusEnum.VALID.value):
return get_error_data_result(message="You do not own the chat") return get_error_data_result(message="You do not own the chat")
req = request.json req = request.json
ids = req.get("dataset_ids") ids = req.get("dataset_ids", [])
if "show_quotation" in req: if "show_quotation" in req:
req["do_refer"] = req.pop("show_quotation") req["do_refer"] = req.pop("show_quotation")
if ids is not None: if ids:
for kb_id in ids: for kb_id in ids:
kbs = KnowledgebaseService.accessible(kb_id=kb_id, user_id=tenant_id) kbs = KnowledgebaseService.accessible(kb_id=kb_id, user_id=tenant_id)
if not kbs: if not kbs:

View File

@ -24,6 +24,7 @@ from api.db.services.llm_service import LLMBundle
from api import settings from api import settings
from api.utils.api_utils import validate_request, build_error_result, apikey_required from api.utils.api_utils import validate_request, build_error_result, apikey_required
from rag.app.tag import label_question from rag.app.tag import label_question
from api.db.services.dialog_service import meta_filter
@manager.route('/dify/retrieval', methods=['POST']) # noqa: F821 @manager.route('/dify/retrieval', methods=['POST']) # noqa: F821
@ -37,18 +38,23 @@ def retrieval(tenant_id):
retrieval_setting = req.get("retrieval_setting", {}) retrieval_setting = req.get("retrieval_setting", {})
similarity_threshold = float(retrieval_setting.get("score_threshold", 0.0)) similarity_threshold = float(retrieval_setting.get("score_threshold", 0.0))
top = int(retrieval_setting.get("top_k", 1024)) top = int(retrieval_setting.get("top_k", 1024))
metadata_condition = req.get("metadata_condition",{})
metas = DocumentService.get_meta_by_kbs([kb_id])
doc_ids = []
try: try:
e, kb = KnowledgebaseService.get_by_id(kb_id) e, kb = KnowledgebaseService.get_by_id(kb_id)
if not e: if not e:
return build_error_result(message="Knowledgebase not found!", code=settings.RetCode.NOT_FOUND) return build_error_result(message="Knowledgebase not found!", code=settings.RetCode.NOT_FOUND)
if kb.tenant_id != tenant_id:
return build_error_result(message="Knowledgebase not found!", code=settings.RetCode.NOT_FOUND)
embd_mdl = LLMBundle(kb.tenant_id, LLMType.EMBEDDING.value, llm_name=kb.embd_id) embd_mdl = LLMBundle(kb.tenant_id, LLMType.EMBEDDING.value, llm_name=kb.embd_id)
print(metadata_condition)
print("after",convert_conditions(metadata_condition))
doc_ids.extend(meta_filter(metas, convert_conditions(metadata_condition)))
print("doc_ids",doc_ids)
if not doc_ids and metadata_condition is not None:
doc_ids = ['-999']
ranks = settings.retrievaler.retrieval( ranks = settings.retrievaler.retrieval(
question, question,
embd_mdl, embd_mdl,
@ -59,6 +65,7 @@ def retrieval(tenant_id):
similarity_threshold=similarity_threshold, similarity_threshold=similarity_threshold,
vector_similarity_weight=0.3, vector_similarity_weight=0.3,
top=top, top=top,
doc_ids=doc_ids,
rank_feature=label_question(question, [kb]) rank_feature=label_question(question, [kb])
) )
@ -67,6 +74,7 @@ def retrieval(tenant_id):
[tenant_id], [tenant_id],
[kb_id], [kb_id],
embd_mdl, embd_mdl,
doc_ids,
LLMBundle(kb.tenant_id, LLMType.CHAT)) LLMBundle(kb.tenant_id, LLMType.CHAT))
if ck["content_with_weight"]: if ck["content_with_weight"]:
ranks["chunks"].insert(0, ck) ranks["chunks"].insert(0, ck)
@ -93,3 +101,20 @@ def retrieval(tenant_id):
) )
logging.exception(e) logging.exception(e)
return build_error_result(message=str(e), code=settings.RetCode.SERVER_ERROR) return build_error_result(message=str(e), code=settings.RetCode.SERVER_ERROR)
def convert_conditions(metadata_condition):
if metadata_condition is None:
metadata_condition = {}
op_mapping = {
"is": "=",
"not is": ""
}
return [
{
"op": op_mapping.get(cond["comparison_operator"], cond["comparison_operator"]),
"key": cond["name"],
"value": cond["value"]
}
for cond in metadata_condition.get("conditions", [])
]

View File

@ -450,37 +450,26 @@ def agents_completion_openai_compatibility(tenant_id, agent_id):
def agent_completions(tenant_id, agent_id): def agent_completions(tenant_id, agent_id):
req = request.json req = request.json
ans = {}
if req.get("stream", True): if req.get("stream", True):
resp = Response(agent_completion(tenant_id=tenant_id, agent_id=agent_id, **req), mimetype="text/event-stream")
def generate():
for answer in agent_completion(tenant_id=tenant_id, agent_id=agent_id, **req):
if isinstance(answer, str):
try:
ans = json.loads(answer[5:]) # remove "data:"
except Exception:
continue
if ans.get("event") != "message":
continue
yield answer
yield "data:[DONE]\n\n"
resp = Response(generate(), mimetype="text/event-stream")
resp.headers.add_header("Cache-control", "no-cache") resp.headers.add_header("Cache-control", "no-cache")
resp.headers.add_header("Connection", "keep-alive") resp.headers.add_header("Connection", "keep-alive")
resp.headers.add_header("X-Accel-Buffering", "no") resp.headers.add_header("X-Accel-Buffering", "no")
resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8") resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8")
return resp return resp
result = {}
for answer in agent_completion(tenant_id=tenant_id, agent_id=agent_id, **req): for answer in agent_completion(tenant_id=tenant_id, agent_id=agent_id, **req):
try: try:
ans = json.loads(answer[5:]) # remove "data:" ans = json.loads(answer[5:]) # remove "data:"
if not result:
result = ans.copy()
else:
result["data"]["answer"] += ans["data"]["answer"]
result["data"]["reference"] = ans["data"].get("reference", [])
except Exception as e: except Exception as e:
return get_result(data=f"**ERROR**: {str(e)}") return get_error_data_result(str(e))
return get_result(data=ans) return result
@manager.route("/chats/<chat_id>/sessions", methods=["GET"]) # noqa: F821 @manager.route("/chats/<chat_id>/sessions", methods=["GET"]) # noqa: F821
@ -909,7 +898,7 @@ def ask_about_embedded():
def stream(): def stream():
nonlocal req, uid nonlocal req, uid
try: try:
for ans in ask(req["question"], req["kb_ids"], uid, search_config): for ans in ask(req["question"], req["kb_ids"], uid, search_config=search_config):
yield "data:" + json.dumps({"code": 0, "message": "", "data": ans}, ensure_ascii=False) + "\n\n" yield "data:" + json.dumps({"code": 0, "message": "", "data": ans}, ensure_ascii=False) + "\n\n"
except Exception as e: except Exception as e:
yield "data:" + json.dumps({"code": 500, "message": str(e), "data": {"answer": "**ERROR**: " + str(e), "reference": []}}, ensure_ascii=False) + "\n\n" yield "data:" + json.dumps({"code": 500, "message": str(e), "data": {"answer": "**ERROR**: " + str(e), "reference": []}}, ensure_ascii=False) + "\n\n"

View File

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

View File

@ -14,13 +14,15 @@
# limitations under the License. # limitations under the License.
# #
from .pdf_parser import RAGFlowPdfParser as PdfParser, PlainParser
from .docx_parser import RAGFlowDocxParser as DocxParser from .docx_parser import RAGFlowDocxParser as DocxParser
from .excel_parser import RAGFlowExcelParser as ExcelParser from .excel_parser import RAGFlowExcelParser as ExcelParser
from .ppt_parser import RAGFlowPptParser as PptParser
from .html_parser import RAGFlowHtmlParser as HtmlParser from .html_parser import RAGFlowHtmlParser as HtmlParser
from .json_parser import RAGFlowJsonParser as JsonParser from .json_parser import RAGFlowJsonParser as JsonParser
from .markdown_parser import MarkdownElementExtractor
from .markdown_parser import RAGFlowMarkdownParser as MarkdownParser from .markdown_parser import RAGFlowMarkdownParser as MarkdownParser
from .pdf_parser import PlainParser
from .pdf_parser import RAGFlowPdfParser as PdfParser
from .ppt_parser import RAGFlowPptParser as PptParser
from .txt_parser import RAGFlowTxtParser as TxtParser from .txt_parser import RAGFlowTxtParser as TxtParser
__all__ = [ __all__ = [
@ -33,4 +35,6 @@ __all__ = [
"JsonParser", "JsonParser",
"MarkdownParser", "MarkdownParser",
"TxtParser", "TxtParser",
] "MarkdownElementExtractor",
]

View File

@ -17,8 +17,10 @@
import re import re
import mistune
from markdown import markdown from markdown import markdown
class RAGFlowMarkdownParser: class RAGFlowMarkdownParser:
def __init__(self, chunk_token_num=128): def __init__(self, chunk_token_num=128):
self.chunk_token_num = int(chunk_token_num) self.chunk_token_num = int(chunk_token_num)
@ -35,40 +37,44 @@ class RAGFlowMarkdownParser:
table_list.append(raw_table) table_list.append(raw_table)
if separate_tables: if separate_tables:
# Skip this match (i.e., remove it) # Skip this match (i.e., remove it)
new_text += working_text[last_end:match.start()] + "\n\n" new_text += working_text[last_end : match.start()] + "\n\n"
else: else:
# Replace with rendered HTML # Replace with rendered HTML
html_table = markdown(raw_table, extensions=['markdown.extensions.tables']) if render else raw_table html_table = markdown(raw_table, extensions=["markdown.extensions.tables"]) if render else raw_table
new_text += working_text[last_end:match.start()] + html_table + "\n\n" new_text += working_text[last_end : match.start()] + html_table + "\n\n"
last_end = match.end() last_end = match.end()
new_text += working_text[last_end:] new_text += working_text[last_end:]
return new_text return new_text
if "|" in markdown_text: # for optimize performance if "|" in markdown_text: # for optimize performance
# Standard Markdown table # Standard Markdown table
border_table_pattern = re.compile( border_table_pattern = re.compile(
r''' r"""
(?:\n|^) (?:\n|^)
(?:\|.*?\|.*?\|.*?\n) (?:\|.*?\|.*?\|.*?\n)
(?:\|(?:\s*[:-]+[-| :]*\s*)\|.*?\n) (?:\|(?:\s*[:-]+[-| :]*\s*)\|.*?\n)
(?:\|.*?\|.*?\|.*?\n)+ (?:\|.*?\|.*?\|.*?\n)+
''', re.VERBOSE) """,
re.VERBOSE,
)
working_text = replace_tables_with_rendered_html(border_table_pattern, tables) working_text = replace_tables_with_rendered_html(border_table_pattern, tables)
# Borderless Markdown table # Borderless Markdown table
no_border_table_pattern = re.compile( no_border_table_pattern = re.compile(
r''' r"""
(?:\n|^) (?:\n|^)
(?:\S.*?\|.*?\n) (?:\S.*?\|.*?\n)
(?:(?:\s*[:-]+[-| :]*\s*).*?\n) (?:(?:\s*[:-]+[-| :]*\s*).*?\n)
(?:\S.*?\|.*?\n)+ (?:\S.*?\|.*?\n)+
''', re.VERBOSE) """,
re.VERBOSE,
)
working_text = replace_tables_with_rendered_html(no_border_table_pattern, tables) working_text = replace_tables_with_rendered_html(no_border_table_pattern, tables)
if "<table>" in working_text.lower(): # for optimize performance if "<table>" in working_text.lower(): # for optimize performance
#HTML table extraction - handle possible html/body wrapper tags # HTML table extraction - handle possible html/body wrapper tags
html_table_pattern = re.compile( html_table_pattern = re.compile(
r''' r"""
(?:\n|^) (?:\n|^)
\s* \s*
(?: (?:
@ -83,9 +89,10 @@ class RAGFlowMarkdownParser:
) )
\s* \s*
(?=\n|$) (?=\n|$)
''', """,
re.VERBOSE | re.DOTALL | re.IGNORECASE re.VERBOSE | re.DOTALL | re.IGNORECASE,
) )
def replace_html_tables(): def replace_html_tables():
nonlocal working_text nonlocal working_text
new_text = "" new_text = ""
@ -94,9 +101,9 @@ class RAGFlowMarkdownParser:
raw_table = match.group() raw_table = match.group()
tables.append(raw_table) tables.append(raw_table)
if separate_tables: if separate_tables:
new_text += working_text[last_end:match.start()] + "\n\n" new_text += working_text[last_end : match.start()] + "\n\n"
else: else:
new_text += working_text[last_end:match.start()] + raw_table + "\n\n" new_text += working_text[last_end : match.start()] + raw_table + "\n\n"
last_end = match.end() last_end = match.end()
new_text += working_text[last_end:] new_text += working_text[last_end:]
working_text = new_text working_text = new_text
@ -104,3 +111,163 @@ class RAGFlowMarkdownParser:
replace_html_tables() replace_html_tables()
return working_text, tables return working_text, tables
class MarkdownElementExtractor:
def __init__(self, markdown_content):
self.markdown_content = markdown_content
self.lines = markdown_content.split("\n")
self.ast_parser = mistune.create_markdown(renderer="ast")
self.ast_nodes = self.ast_parser(markdown_content)
def extract_elements(self):
"""Extract individual elements (headers, code blocks, lists, etc.)"""
sections = []
i = 0
while i < len(self.lines):
line = self.lines[i]
if re.match(r"^#{1,6}\s+.*$", line):
# header
element = self._extract_header(i)
sections.append(element["content"])
i = element["end_line"] + 1
elif line.strip().startswith("```"):
# code block
element = self._extract_code_block(i)
sections.append(element["content"])
i = element["end_line"] + 1
elif re.match(r"^\s*[-*+]\s+.*$", line) or re.match(r"^\s*\d+\.\s+.*$", line):
# list block
element = self._extract_list_block(i)
sections.append(element["content"])
i = element["end_line"] + 1
elif line.strip().startswith(">"):
# blockquote
element = self._extract_blockquote(i)
sections.append(element["content"])
i = element["end_line"] + 1
elif line.strip():
# text block (paragraphs and inline elements until next block element)
element = self._extract_text_block(i)
sections.append(element["content"])
i = element["end_line"] + 1
else:
i += 1
sections = [section for section in sections if section.strip()]
return sections
def _extract_header(self, start_pos):
return {
"type": "header",
"content": self.lines[start_pos],
"start_line": start_pos,
"end_line": start_pos,
}
def _extract_code_block(self, start_pos):
end_pos = start_pos
content_lines = [self.lines[start_pos]]
# Find the end of the code block
for i in range(start_pos + 1, len(self.lines)):
content_lines.append(self.lines[i])
end_pos = i
if self.lines[i].strip().startswith("```"):
break
return {
"type": "code_block",
"content": "\n".join(content_lines),
"start_line": start_pos,
"end_line": end_pos,
}
def _extract_list_block(self, start_pos):
end_pos = start_pos
content_lines = []
i = start_pos
while i < len(self.lines):
line = self.lines[i]
# check if this line is a list item or continuation of a list
if (
re.match(r"^\s*[-*+]\s+.*$", line)
or re.match(r"^\s*\d+\.\s+.*$", line)
or (i > start_pos and not line.strip())
or (i > start_pos and re.match(r"^\s{2,}[-*+]\s+.*$", line))
or (i > start_pos and re.match(r"^\s{2,}\d+\.\s+.*$", line))
or (i > start_pos and re.match(r"^\s+\w+.*$", line))
):
content_lines.append(line)
end_pos = i
i += 1
else:
break
return {
"type": "list_block",
"content": "\n".join(content_lines),
"start_line": start_pos,
"end_line": end_pos,
}
def _extract_blockquote(self, start_pos):
end_pos = start_pos
content_lines = []
i = start_pos
while i < len(self.lines):
line = self.lines[i]
if line.strip().startswith(">") or (i > start_pos and not line.strip()):
content_lines.append(line)
end_pos = i
i += 1
else:
break
return {
"type": "blockquote",
"content": "\n".join(content_lines),
"start_line": start_pos,
"end_line": end_pos,
}
def _extract_text_block(self, start_pos):
"""Extract a text block (paragraphs, inline elements) until next block element"""
end_pos = start_pos
content_lines = [self.lines[start_pos]]
i = start_pos + 1
while i < len(self.lines):
line = self.lines[i]
# stop if we encounter a block element
if re.match(r"^#{1,6}\s+.*$", line) or line.strip().startswith("```") or re.match(r"^\s*[-*+]\s+.*$", line) or re.match(r"^\s*\d+\.\s+.*$", line) or line.strip().startswith(">"):
break
elif not line.strip():
# check if the next line is a block element
if i + 1 < len(self.lines) and (
re.match(r"^#{1,6}\s+.*$", self.lines[i + 1])
or self.lines[i + 1].strip().startswith("```")
or re.match(r"^\s*[-*+]\s+.*$", self.lines[i + 1])
or re.match(r"^\s*\d+\.\s+.*$", self.lines[i + 1])
or self.lines[i + 1].strip().startswith(">")
):
break
else:
content_lines.append(line)
end_pos = i
i += 1
else:
content_lines.append(line)
end_pos = i
i += 1
return {
"type": "text_block",
"content": "\n".join(content_lines),
"start_line": start_pos,
"end_line": end_pos,
}

View File

@ -169,7 +169,7 @@ class EntityResolution(Extractor):
logging.info(f"Created resolution prompt {len(text)} bytes for {len(candidate_resolution_i[1])} entity pairs of type {candidate_resolution_i[0]}") logging.info(f"Created resolution prompt {len(text)} bytes for {len(candidate_resolution_i[1])} entity pairs of type {candidate_resolution_i[0]}")
async with chat_limiter: async with chat_limiter:
try: try:
with trio.move_on_after(240) as cancel_scope: with trio.move_on_after(280) as cancel_scope:
response = await trio.to_thread.run_sync(self._chat, text, [{"role": "user", "content": "Output:"}], {}) response = await trio.to_thread.run_sync(self._chat, text, [{"role": "user", "content": "Output:"}], {})
if cancel_scope.cancelled_caught: if cancel_scope.cancelled_caught:
logging.warning("_resolve_candidate._chat timeout, skipping...") logging.warning("_resolve_candidate._chat timeout, skipping...")

View File

@ -47,7 +47,7 @@ class Extractor:
self._language = language self._language = language
self._entity_types = entity_types or DEFAULT_ENTITY_TYPES self._entity_types = entity_types or DEFAULT_ENTITY_TYPES
@timeout(60*5) @timeout(60*20)
def _chat(self, system, history, gen_conf={}): def _chat(self, system, history, gen_conf={}):
hist = deepcopy(history) hist = deepcopy(history)
conf = deepcopy(gen_conf) conf = deepcopy(gen_conf)

View File

@ -16,6 +16,9 @@
import json import json
import logging import logging
import random
import time
from collections import OrderedDict
from collections.abc import AsyncIterator from collections.abc import AsyncIterator
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from functools import wraps from functools import wraps
@ -53,6 +56,13 @@ JSON_RESPONSE = True
class RAGFlowConnector: class RAGFlowConnector:
_MAX_DATASET_CACHE = 32
_MAX_DOCUMENT_CACHE = 128
_CACHE_TTL = 300
_dataset_metadata_cache: OrderedDict[str, tuple[dict, float | int]] = OrderedDict() # "dataset_id" -> (metadata, expiry_ts)
_document_metadata_cache: OrderedDict[str, tuple[list[tuple[str, dict]], float | int]] = OrderedDict() # "dataset_id" -> ([(document_id, doc_metadata)], expiry_ts)
def __init__(self, base_url: str, version="v1"): def __init__(self, base_url: str, version="v1"):
self.base_url = base_url self.base_url = base_url
self.version = version self.version = version
@ -72,6 +82,43 @@ class RAGFlowConnector:
res = requests.get(url=self.api_url + path, params=params, headers=self.authorization_header, json=json) res = requests.get(url=self.api_url + path, params=params, headers=self.authorization_header, json=json)
return res return res
def _is_cache_valid(self, ts):
return time.time() < ts
def _get_expiry_timestamp(self):
offset = random.randint(-30, 30)
return time.time() + self._CACHE_TTL + offset
def _get_cached_dataset_metadata(self, dataset_id):
entry = self._dataset_metadata_cache.get(dataset_id)
if entry:
data, ts = entry
if self._is_cache_valid(ts):
self._dataset_metadata_cache.move_to_end(dataset_id)
return data
return None
def _set_cached_dataset_metadata(self, dataset_id, metadata):
self._dataset_metadata_cache[dataset_id] = (metadata, self._get_expiry_timestamp())
self._dataset_metadata_cache.move_to_end(dataset_id)
if len(self._dataset_metadata_cache) > self._MAX_DATASET_CACHE:
self._dataset_metadata_cache.popitem(last=False)
def _get_cached_document_metadata_by_dataset(self, dataset_id):
entry = self._document_metadata_cache.get(dataset_id)
if entry:
data_list, ts = entry
if self._is_cache_valid(ts):
self._document_metadata_cache.move_to_end(dataset_id)
return {doc_id: doc_meta for doc_id, doc_meta in data_list}
return None
def _set_cached_document_metadata_by_dataset(self, dataset_id, doc_id_meta_list):
self._document_metadata_cache[dataset_id] = (doc_id_meta_list, self._get_expiry_timestamp())
self._document_metadata_cache.move_to_end(dataset_id)
if len(self._document_metadata_cache) > self._MAX_DOCUMENT_CACHE:
self._document_metadata_cache.popitem(last=False)
def list_datasets(self, page: int = 1, page_size: int = 1000, orderby: str = "create_time", desc: bool = True, id: str | None = None, name: str | None = None): def list_datasets(self, page: int = 1, page_size: int = 1000, orderby: str = "create_time", desc: bool = True, id: str | None = None, name: str | None = None):
res = self._get("/datasets", {"page": page, "page_size": page_size, "orderby": orderby, "desc": desc, "id": id, "name": name}) res = self._get("/datasets", {"page": page, "page_size": page_size, "orderby": orderby, "desc": desc, "id": id, "name": name})
if not res: if not res:
@ -87,10 +134,38 @@ class RAGFlowConnector:
return "" return ""
def retrieval( def retrieval(
self, dataset_ids, document_ids=None, question="", page=1, page_size=30, similarity_threshold=0.2, vector_similarity_weight=0.3, top_k=1024, rerank_id: str | None = None, keyword: bool = False self,
dataset_ids,
document_ids=None,
question="",
page=1,
page_size=30,
similarity_threshold=0.2,
vector_similarity_weight=0.3,
top_k=1024,
rerank_id: str | None = None,
keyword: bool = False,
force_refresh: bool = False,
): ):
if document_ids is None: if document_ids is None:
document_ids = [] document_ids = []
# If no dataset_ids provided or empty list, get all available dataset IDs
if not dataset_ids:
dataset_list_str = self.list_datasets()
dataset_ids = []
# Parse the dataset list to extract IDs
if dataset_list_str:
for line in dataset_list_str.strip().split('\n'):
if line.strip():
try:
dataset_info = json.loads(line.strip())
dataset_ids.append(dataset_info["id"])
except (json.JSONDecodeError, KeyError):
# Skip malformed lines
continue
data_json = { data_json = {
"page": page, "page": page,
"page_size": page_size, "page_size": page_size,
@ -110,12 +185,127 @@ class RAGFlowConnector:
res = res.json() res = res.json()
if res.get("code") == 0: if res.get("code") == 0:
data = res["data"]
chunks = [] chunks = []
for chunk_data in res["data"].get("chunks"):
chunks.append(json.dumps(chunk_data, ensure_ascii=False)) # Cache document metadata and dataset information
return [types.TextContent(type="text", text="\n".join(chunks))] document_cache, dataset_cache = self._get_document_metadata_cache(dataset_ids, force_refresh=force_refresh)
# Process chunks with enhanced field mapping including per-chunk metadata
for chunk_data in data.get("chunks", []):
enhanced_chunk = self._map_chunk_fields(chunk_data, dataset_cache, document_cache)
chunks.append(enhanced_chunk)
# Build structured response (no longer need response-level document_metadata)
response = {
"chunks": chunks,
"pagination": {
"page": data.get("page", page),
"page_size": data.get("page_size", page_size),
"total_chunks": data.get("total", len(chunks)),
"total_pages": (data.get("total", len(chunks)) + page_size - 1) // page_size,
},
"query_info": {
"question": question,
"similarity_threshold": similarity_threshold,
"vector_weight": vector_similarity_weight,
"keyword_search": keyword,
"dataset_count": len(dataset_ids),
},
}
return [types.TextContent(type="text", text=json.dumps(response, ensure_ascii=False))]
raise Exception([types.TextContent(type="text", text=res.get("message"))]) raise Exception([types.TextContent(type="text", text=res.get("message"))])
def _get_document_metadata_cache(self, dataset_ids, force_refresh=False):
"""Cache document metadata for all documents in the specified datasets"""
document_cache = {}
dataset_cache = {}
try:
for dataset_id in dataset_ids:
dataset_meta = None if force_refresh else self._get_cached_dataset_metadata(dataset_id)
if not dataset_meta:
# First get dataset info for name
dataset_res = self._get("/datasets", {"id": dataset_id, "page_size": 1})
if dataset_res and dataset_res.status_code == 200:
dataset_data = dataset_res.json()
if dataset_data.get("code") == 0 and dataset_data.get("data"):
dataset_info = dataset_data["data"][0]
dataset_meta = {"name": dataset_info.get("name", "Unknown"), "description": dataset_info.get("description", "")}
self._set_cached_dataset_metadata(dataset_id, dataset_meta)
if dataset_meta:
dataset_cache[dataset_id] = dataset_meta
docs = None if force_refresh else self._get_cached_document_metadata_by_dataset(dataset_id)
if docs is None:
docs_res = self._get(f"/datasets/{dataset_id}/documents")
docs_data = docs_res.json()
if docs_data.get("code") == 0 and docs_data.get("data", {}).get("docs"):
doc_id_meta_list = []
docs = {}
for doc in docs_data["data"]["docs"]:
doc_id = doc.get("id")
if not doc_id:
continue
doc_meta = {
"document_id": doc_id,
"name": doc.get("name", ""),
"location": doc.get("location", ""),
"type": doc.get("type", ""),
"size": doc.get("size"),
"chunk_count": doc.get("chunk_count"),
# "chunk_method": doc.get("chunk_method", ""),
"create_date": doc.get("create_date", ""),
"update_date": doc.get("update_date", ""),
# "process_begin_at": doc.get("process_begin_at", ""),
# "process_duration": doc.get("process_duration"),
# "progress": doc.get("progress"),
# "progress_msg": doc.get("progress_msg", ""),
# "status": doc.get("status", ""),
# "run": doc.get("run", ""),
"token_count": doc.get("token_count"),
# "source_type": doc.get("source_type", ""),
"thumbnail": doc.get("thumbnail", ""),
"dataset_id": doc.get("dataset_id", dataset_id),
"meta_fields": doc.get("meta_fields", {}),
# "parser_config": doc.get("parser_config", {})
}
doc_id_meta_list.append((doc_id, doc_meta))
docs[doc_id] = doc_meta
self._set_cached_document_metadata_by_dataset(dataset_id, doc_id_meta_list)
if docs:
document_cache.update(docs)
except Exception:
# Gracefully handle metadata cache failures
pass
return document_cache, dataset_cache
def _map_chunk_fields(self, chunk_data, dataset_cache, document_cache):
"""Preserve all original API fields and add per-chunk document metadata"""
# Start with ALL raw data from API (preserve everything like original version)
mapped = dict(chunk_data)
# Add dataset name enhancement
dataset_id = chunk_data.get("dataset_id") or chunk_data.get("kb_id")
if dataset_id and dataset_id in dataset_cache:
mapped["dataset_name"] = dataset_cache[dataset_id]["name"]
else:
mapped["dataset_name"] = "Unknown"
# Add document name convenience field
mapped["document_name"] = chunk_data.get("document_keyword", "")
# Add per-chunk document metadata
document_id = chunk_data.get("document_id")
if document_id and document_id in document_cache:
mapped["document_metadata"] = document_cache[document_id]
return mapped
class RAGFlowCtx: class RAGFlowCtx:
def __init__(self, connector: RAGFlowConnector): def __init__(self, connector: RAGFlowConnector):
@ -195,7 +385,58 @@ async def list_tools(*, connector) -> list[types.Tool]:
"items": {"type": "string"}, "items": {"type": "string"},
"description": "Optional array of document IDs to search within." "description": "Optional array of document IDs to search within."
}, },
"question": {"type": "string", "description": "The question or query to search for."}, "question": {
"type": "string",
"description": "The question or query to search for."
},
"page": {
"type": "integer",
"description": "Page number for pagination",
"default": 1,
"minimum": 1,
},
"page_size": {
"type": "integer",
"description": "Number of results to return per page (default: 10, max recommended: 50 to avoid token limits)",
"default": 10,
"minimum": 1,
"maximum": 100,
},
"similarity_threshold": {
"type": "number",
"description": "Minimum similarity threshold for results",
"default": 0.2,
"minimum": 0.0,
"maximum": 1.0,
},
"vector_similarity_weight": {
"type": "number",
"description": "Weight for vector similarity vs term similarity",
"default": 0.3,
"minimum": 0.0,
"maximum": 1.0,
},
"keyword": {
"type": "boolean",
"description": "Enable keyword-based search",
"default": False,
},
"top_k": {
"type": "integer",
"description": "Maximum results to consider before ranking",
"default": 1024,
"minimum": 1,
"maximum": 1024,
},
"rerank_id": {
"type": "string",
"description": "Optional reranking model identifier",
},
"force_refresh": {
"type": "boolean",
"description": "Set to true only if fresh dataset and document metadata is explicitly required. Otherwise, cached metadata is used (default: false).",
"default": False,
},
}, },
"required": ["question"], "required": ["question"],
}, },
@ -209,6 +450,16 @@ async def call_tool(name: str, arguments: dict, *, connector) -> list[types.Text
if name == "ragflow_retrieval": if name == "ragflow_retrieval":
document_ids = arguments.get("document_ids", []) document_ids = arguments.get("document_ids", [])
dataset_ids = arguments.get("dataset_ids", []) dataset_ids = arguments.get("dataset_ids", [])
question = arguments.get("question", "")
page = arguments.get("page", 1)
page_size = arguments.get("page_size", 10)
similarity_threshold = arguments.get("similarity_threshold", 0.2)
vector_similarity_weight = arguments.get("vector_similarity_weight", 0.3)
keyword = arguments.get("keyword", False)
top_k = arguments.get("top_k", 1024)
rerank_id = arguments.get("rerank_id")
force_refresh = arguments.get("force_refresh", False)
# If no dataset_ids provided or empty list, get all available dataset IDs # If no dataset_ids provided or empty list, get all available dataset IDs
if not dataset_ids: if not dataset_ids:
@ -229,7 +480,15 @@ async def call_tool(name: str, arguments: dict, *, connector) -> list[types.Text
return connector.retrieval( return connector.retrieval(
dataset_ids=dataset_ids, dataset_ids=dataset_ids,
document_ids=document_ids, document_ids=document_ids,
question=arguments["question"], question=question,
page=page,
page_size=page_size,
similarity_threshold=similarity_threshold,
vector_similarity_weight=vector_similarity_weight,
keyword=keyword,
top_k=top_k,
rerank_id=rerank_id,
force_refresh=force_refresh,
) )
raise ValueError(f"Tool not found: {name}") raise ValueError(f"Tool not found: {name}")

View File

@ -45,7 +45,7 @@ dependencies = [
"html-text==0.6.2", "html-text==0.6.2",
"httpx[socks]==0.27.2", "httpx[socks]==0.27.2",
"huggingface-hub>=0.25.0,<0.26.0", "huggingface-hub>=0.25.0,<0.26.0",
"infinity-sdk==0.6.0-dev4", "infinity-sdk==0.6.0.dev5",
"infinity-emb>=0.0.66,<0.0.67", "infinity-emb>=0.0.66,<0.0.67",
"itsdangerous==2.1.2", "itsdangerous==2.1.2",
"json-repair==0.35.0", "json-repair==0.35.0",

View File

@ -30,7 +30,7 @@ from tika import parser
from api.db import LLMType from api.db import LLMType
from api.db.services.llm_service import LLMBundle from api.db.services.llm_service import LLMBundle
from deepdoc.parser import DocxParser, ExcelParser, HtmlParser, JsonParser, MarkdownParser, PdfParser, TxtParser from deepdoc.parser import DocxParser, ExcelParser, HtmlParser, JsonParser, MarkdownElementExtractor, MarkdownParser, PdfParser, TxtParser
from deepdoc.parser.figure_parser import VisionFigureParser, vision_figure_parser_figure_data_wrapper from deepdoc.parser.figure_parser import VisionFigureParser, vision_figure_parser_figure_data_wrapper
from deepdoc.parser.pdf_parser import PlainParser, VisionParser from deepdoc.parser.pdf_parser import PlainParser, VisionParser
from rag.nlp import concat_img, find_codec, naive_merge, naive_merge_with_images, naive_merge_docx, rag_tokenizer, tokenize_chunks, tokenize_chunks_with_images, tokenize_table from rag.nlp import concat_img, find_codec, naive_merge, naive_merge_with_images, naive_merge_docx, rag_tokenizer, tokenize_chunks, tokenize_chunks_with_images, tokenize_table
@ -289,7 +289,7 @@ class Pdf(PdfParser):
return [(b["text"], self._line_tag(b, zoomin)) for b in self.boxes], tbls, figures return [(b["text"], self._line_tag(b, zoomin)) for b in self.boxes], tbls, figures
else: else:
tbls = self._extract_table_figure(True, zoomin, True, True) tbls = self._extract_table_figure(True, zoomin, True, True)
# self._naive_vertical_merge() self._naive_vertical_merge()
self._concat_downward() self._concat_downward()
# self._filter_forpages() # self._filter_forpages()
logging.info("layouts cost: {}s".format(timer() - first_start)) logging.info("layouts cost: {}s".format(timer() - first_start))
@ -350,17 +350,14 @@ class Markdown(MarkdownParser):
else: else:
with open(filename, "r") as f: with open(filename, "r") as f:
txt = f.read() txt = f.read()
remainder, tables = self.extract_tables_and_remainder(f'{txt}\n', separate_tables=separate_tables) remainder, tables = self.extract_tables_and_remainder(f'{txt}\n', separate_tables=separate_tables)
sections = []
extractor = MarkdownElementExtractor(txt)
element_sections = extractor.extract_elements()
sections = [(element, "") for element in element_sections]
tbls = [] tbls = []
for sec in remainder.split("\n"):
if sec.strip().find("#") == 0:
sections.append((sec, ""))
elif sections and sections[-1][0].strip().find("#") == 0:
sec_, _ = sections.pop(-1)
sections.append((sec_ + "\n" + sec, ""))
else:
sections.append((sec, ""))
for table in tables: for table in tables:
tbls.append(((None, markdown(table, extensions=['markdown.extensions.tables'])), "")) tbls.append(((None, markdown(table, extensions=['markdown.extensions.tables'])), ""))
return sections, tbls return sections, tbls

View File

@ -42,9 +42,12 @@ class RecursiveAbstractiveProcessing4TreeOrganizedRetrieval:
self._prompt = prompt self._prompt = prompt
self._max_token = max_token self._max_token = max_token
@timeout(60) @timeout(60*20)
async def _chat(self, system, history, gen_conf): async def _chat(self, system, history, gen_conf):
response = get_llm_cache(self._llm_model.llm_name, system, history, gen_conf) response = await trio.to_thread.run_sync(
lambda: get_llm_cache(self._llm_model.llm_name, system, history, gen_conf)
)
if response: if response:
return response return response
response = await trio.to_thread.run_sync( response = await trio.to_thread.run_sync(
@ -53,19 +56,23 @@ class RecursiveAbstractiveProcessing4TreeOrganizedRetrieval:
response = re.sub(r"^.*</think>", "", response, flags=re.DOTALL) response = re.sub(r"^.*</think>", "", response, flags=re.DOTALL)
if response.find("**ERROR**") >= 0: if response.find("**ERROR**") >= 0:
raise Exception(response) raise Exception(response)
set_llm_cache(self._llm_model.llm_name, system, response, history, gen_conf) await trio.to_thread.run_sync(
lambda: set_llm_cache(self._llm_model.llm_name, system, response, history, gen_conf)
)
return response return response
@timeout(2) @timeout(20)
async def _embedding_encode(self, txt): async def _embedding_encode(self, txt):
response = get_embed_cache(self._embd_model.llm_name, txt) response = await trio.to_thread.run_sync(
lambda: get_embed_cache(self._embd_model.llm_name, txt)
)
if response is not None: if response is not None:
return response return response
embds, _ = await trio.to_thread.run_sync(lambda: self._embd_model.encode([txt])) embds, _ = await trio.to_thread.run_sync(lambda: self._embd_model.encode([txt]))
if len(embds) < 1 or len(embds[0]) < 1: if len(embds) < 1 or len(embds[0]) < 1:
raise Exception("Embedding error: ") raise Exception("Embedding error: ")
embds = embds[0] embds = embds[0]
set_embed_cache(self._embd_model.llm_name, txt, embds) await trio.to_thread.run_sync(lambda: set_embed_cache(self._embd_model.llm_name, txt, embds))
return embds return embds
def _get_optimal_clusters(self, embeddings: np.ndarray, random_state: int): def _get_optimal_clusters(self, embeddings: np.ndarray, random_state: int):
@ -86,7 +93,7 @@ class RecursiveAbstractiveProcessing4TreeOrganizedRetrieval:
layers = [(0, len(chunks))] layers = [(0, len(chunks))]
start, end = 0, len(chunks) start, end = 0, len(chunks)
@timeout(60) @timeout(60*20)
async def summarize(ck_idx: list[int]): async def summarize(ck_idx: list[int]):
nonlocal chunks nonlocal chunks
texts = [chunks[i][0] for i in ck_idx] texts = [chunks[i][0] for i in ck_idx]

6
uv.lock generated
View File

@ -2603,7 +2603,7 @@ wheels = [
[[package]] [[package]]
name = "infinity-sdk" name = "infinity-sdk"
version = "0.6.0.dev4" version = "0.6.0.dev5"
source = { registry = "https://mirrors.aliyun.com/pypi/simple" } source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
dependencies = [ dependencies = [
{ name = "numpy" }, { name = "numpy" },
@ -2620,7 +2620,7 @@ dependencies = [
{ name = "thrift" }, { name = "thrift" },
] ]
wheels = [ wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/d4/cc/645ed8de15952940c7308a788036376583a5fc29fdcf3e4bc75b5ad0c881/infinity_sdk-0.6.0.dev4-py3-none-any.whl", hash = "sha256:f8f4bd8a44e3fae7b4228b5c9e9a16559b4905f50d2d7d0a3d18f39974613e7a" }, { url = "https://mirrors.aliyun.com/pypi/packages/fe/a4/6079bf9790f16badc01e7b79a28c90bec407cfcaa8a2ed37e4a68120f87a/infinity_sdk-0.6.0.dev5-py3-none-any.whl", hash = "sha256:510ac408d5cd9d3d4df33c7c0877f55c5ae8a6019e465190c86d58012a319179" },
] ]
[[package]] [[package]]
@ -5471,7 +5471,7 @@ requires-dist = [
{ name = "httpx", extras = ["socks"], specifier = "==0.27.2" }, { name = "httpx", extras = ["socks"], specifier = "==0.27.2" },
{ name = "huggingface-hub", specifier = ">=0.25.0,<0.26.0" }, { name = "huggingface-hub", specifier = ">=0.25.0,<0.26.0" },
{ name = "infinity-emb", specifier = ">=0.0.66,<0.0.67" }, { name = "infinity-emb", specifier = ">=0.0.66,<0.0.67" },
{ name = "infinity-sdk", specifier = "==0.6.0.dev4" }, { name = "infinity-sdk", specifier = "==0.6.0.dev5" },
{ name = "itsdangerous", specifier = "==2.1.2" }, { name = "itsdangerous", specifier = "==2.1.2" },
{ name = "json-repair", specifier = "==0.35.0" }, { name = "json-repair", specifier = "==0.35.0" },
{ name = "langfuse", specifier = ">=2.60.0" }, { name = "langfuse", specifier = ">=2.60.0" },

View File

@ -14,7 +14,7 @@ module.exports = {
'error', 'error',
{ {
'**/*.{jsx,tsx}': 'KEBAB_CASE', '**/*.{jsx,tsx}': 'KEBAB_CASE',
'**/*.{js,ts}': 'KEBAB_CASE', '**/*.{js,ts}': '[a-z0-9.-]*',
}, },
], ],
'check-file/folder-naming-convention': [ 'check-file/folder-naming-convention': [

View File

@ -85,7 +85,7 @@ function Root({ children }: React.PropsWithChildren) {
<Sonner position={'top-right'} expand richColors closeButton></Sonner> <Sonner position={'top-right'} expand richColors closeButton></Sonner>
<Toaster /> <Toaster />
</ConfigProvider> </ConfigProvider>
<ReactQueryDevtools buttonPosition={'top-left'} /> <ReactQueryDevtools buttonPosition={'top-left'} initialIsOpen={false} />
</> </>
); );
} }

View File

@ -8,47 +8,93 @@ import {
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { IModalProps } from '@/interfaces/common'; import { IModalProps } from '@/interfaces/common';
import { Dispatch, SetStateAction, useCallback, useState } from 'react'; import { zodResolver } from '@hookform/resolvers/zod';
import { TFunction } from 'i18next';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { FileUploader } from '../file-uploader'; import { FileUploader } from '../file-uploader';
import { RAGFlowFormItem } from '../ragflow-form';
import { Form } from '../ui/form';
import { Switch } from '../ui/switch';
type UploaderTabsProps = { function buildUploadFormSchema(t: TFunction) {
setFiles: Dispatch<SetStateAction<File[]>>; const FormSchema = z.object({
parseOnCreation: z.boolean().optional(),
fileList: z
.array(z.instanceof(File))
.min(1, { message: t('fileManager.pleaseUploadAtLeastOneFile') }),
});
return FormSchema;
}
export type UploadFormSchemaType = z.infer<
ReturnType<typeof buildUploadFormSchema>
>;
const UploadFormId = 'UploadFormId';
type UploadFormProps = {
submit: (values?: UploadFormSchemaType) => void;
showParseOnCreation?: boolean;
}; };
function UploadForm({ submit, showParseOnCreation }: UploadFormProps) {
export function UploaderTabs({ setFiles }: UploaderTabsProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const FormSchema = buildUploadFormSchema(t);
type UploadFormSchemaType = z.infer<typeof FormSchema>;
const form = useForm<UploadFormSchemaType>({
resolver: zodResolver(FormSchema),
defaultValues: {
parseOnCreation: false,
fileList: [],
},
});
return ( return (
<Tabs defaultValue="account"> <Form {...form}>
<TabsList className="grid w-full grid-cols-2 mb-4"> <form
<TabsTrigger value="account">{t('fileManager.local')}</TabsTrigger> onSubmit={form.handleSubmit(submit)}
<TabsTrigger value="password">{t('fileManager.s3')}</TabsTrigger> id={UploadFormId}
</TabsList> className="space-y-4"
<TabsContent value="account"> >
<FileUploader {showParseOnCreation && (
maxFileCount={8} <RAGFlowFormItem
maxSize={8 * 1024 * 1024} name="parseOnCreation"
onValueChange={setFiles} label={t('fileManager.parseOnCreation')}
accept={{ '*': [] }} >
/> {(field) => (
</TabsContent> <Switch
<TabsContent value="password">{t('common.comingSoon')}</TabsContent> onCheckedChange={field.onChange}
</Tabs> checked={field.value}
></Switch>
)}
</RAGFlowFormItem>
)}
<RAGFlowFormItem name="fileList" label={t('fileManager.file')}>
{(field) => (
<FileUploader
value={field.value}
onValueChange={field.onChange}
accept={{ '*': [] }}
/>
)}
</RAGFlowFormItem>
</form>
</Form>
); );
} }
type FileUploadDialogProps = IModalProps<UploadFormSchemaType> &
Pick<UploadFormProps, 'showParseOnCreation'>;
export function FileUploadDialog({ export function FileUploadDialog({
hideModal, hideModal,
onOk, onOk,
loading, loading,
}: IModalProps<File[]>) { showParseOnCreation = false,
}: FileUploadDialogProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [files, setFiles] = useState<File[]>([]);
const handleOk = useCallback(() => {
onOk?.(files);
}, [files, onOk]);
return ( return (
<Dialog open onOpenChange={hideModal}> <Dialog open onOpenChange={hideModal}>
@ -56,9 +102,21 @@ export function FileUploadDialog({
<DialogHeader> <DialogHeader>
<DialogTitle>{t('fileManager.uploadFile')}</DialogTitle> <DialogTitle>{t('fileManager.uploadFile')}</DialogTitle>
</DialogHeader> </DialogHeader>
<UploaderTabs setFiles={setFiles}></UploaderTabs> <Tabs defaultValue="account">
<TabsList className="grid w-full grid-cols-2 mb-4">
<TabsTrigger value="account">{t('fileManager.local')}</TabsTrigger>
<TabsTrigger value="password">{t('fileManager.s3')}</TabsTrigger>
</TabsList>
<TabsContent value="account">
<UploadForm
submit={onOk!}
showParseOnCreation={showParseOnCreation}
></UploadForm>
</TabsContent>
<TabsContent value="password">{t('common.comingSoon')}</TabsContent>
</Tabs>
<DialogFooter> <DialogFooter>
<ButtonLoading type="submit" onClick={handleOk} loading={loading}> <ButtonLoading type="submit" loading={loading} form={UploadFormId}>
{t('common.save')} {t('common.save')}
</ButtonLoading> </ButtonLoading>
</DialogFooter> </DialogFooter>

View File

@ -15,6 +15,7 @@ import { Progress } from '@/components/ui/progress';
import { ScrollArea } from '@/components/ui/scroll-area'; import { ScrollArea } from '@/components/ui/scroll-area';
import { useControllableState } from '@/hooks/use-controllable-state'; import { useControllableState } from '@/hooks/use-controllable-state';
import { cn, formatBytes } from '@/lib/utils'; import { cn, formatBytes } from '@/lib/utils';
import { useTranslation } from 'react-i18next';
function isFileWithPreview(file: File): file is File & { preview: string } { function isFileWithPreview(file: File): file is File & { preview: string } {
return 'preview' in file && typeof file.preview === 'string'; return 'preview' in file && typeof file.preview === 'string';
@ -168,14 +169,14 @@ export function FileUploader(props: FileUploaderProps) {
accept = { accept = {
'image/*': [], 'image/*': [],
}, },
maxSize = 1024 * 1024 * 2, maxSize = 1024 * 1024 * 10000000,
maxFileCount = 1, maxFileCount = 100000000000,
multiple = false, multiple = false,
disabled = false, disabled = false,
className, className,
...dropzoneProps ...dropzoneProps
} = props; } = props;
const { t } = useTranslation();
const [files, setFiles] = useControllableState({ const [files, setFiles] = useControllableState({
prop: valueProp, prop: valueProp,
onChange: onValueChange, onChange: onValueChange,
@ -267,7 +268,7 @@ export function FileUploader(props: FileUploaderProps) {
<div <div
{...getRootProps()} {...getRootProps()}
className={cn( className={cn(
'group relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed border-muted-foreground/25 px-5 py-2.5 text-center transition hover:bg-muted/25', 'group relative grid h-72 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed border-muted-foreground/25 px-5 py-2.5 text-center transition hover:bg-muted/25',
'ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', 'ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
isDragActive && 'border-muted-foreground/50', isDragActive && 'border-muted-foreground/50',
isDisabled && 'pointer-events-none opacity-60', isDisabled && 'pointer-events-none opacity-60',
@ -298,14 +299,15 @@ export function FileUploader(props: FileUploaderProps) {
</div> </div>
<div className="flex flex-col gap-px"> <div className="flex flex-col gap-px">
<p className="font-medium text-muted-foreground"> <p className="font-medium text-muted-foreground">
Drag {`'n'`} drop files here, or click to select files {t('knowledgeDetails.uploadTitle')}
</p> </p>
<p className="text-sm text-muted-foreground/70"> <p className="text-sm text-muted-foreground/70">
You can upload {t('knowledgeDetails.uploadDescription')}
{/* You can upload
{maxFileCount > 1 {maxFileCount > 1
? ` ${maxFileCount === Infinity ? 'multiple' : maxFileCount} ? ` ${maxFileCount === Infinity ? 'multiple' : maxFileCount}
files (up to ${formatBytes(maxSize)} each)` files (up to ${formatBytes(maxSize)} each)`
: ` a file with ${formatBytes(maxSize)}`} : ` a file with ${formatBytes(maxSize)}`} */}
</p> </p>
</div> </div>
</div> </div>

View File

@ -1,6 +1,7 @@
import { RAGFlowAvatar } from '@/components/ragflow-avatar'; import { RAGFlowAvatar } from '@/components/ragflow-avatar';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { ReactNode } from 'react';
interface IProps { interface IProps {
data: { data: {
@ -11,8 +12,9 @@ interface IProps {
}; };
onClick?: () => void; onClick?: () => void;
moreDropdown: React.ReactNode; moreDropdown: React.ReactNode;
sharedBadge?: ReactNode;
} }
export function HomeCard({ data, onClick, moreDropdown }: IProps) { export function HomeCard({ data, onClick, moreDropdown, sharedBadge }: IProps) {
return ( return (
<Card <Card
className="bg-bg-card border-colors-outline-neutral-standard" className="bg-bg-card border-colors-outline-neutral-standard"
@ -31,7 +33,7 @@ export function HomeCard({ data, onClick, moreDropdown }: IProps) {
</div> </div>
<div className="flex flex-col justify-between gap-1 flex-1 h-full w-[calc(100%-50px)]"> <div className="flex flex-col justify-between gap-1 flex-1 h-full w-[calc(100%-50px)]">
<section className="flex justify-between"> <section className="flex justify-between">
<div className="text-[20px] font-bold w-80% leading-5"> <div className="text-[20px] font-bold w-80% leading-5 text-ellipsis overflow-hidden">
{data.name} {data.name}
</div> </div>
{moreDropdown} {moreDropdown}
@ -41,10 +43,11 @@ export function HomeCard({ data, onClick, moreDropdown }: IProps) {
<div className="whitespace-nowrap overflow-hidden text-ellipsis"> <div className="whitespace-nowrap overflow-hidden text-ellipsis">
{data.description} {data.description}
</div> </div>
<div> <div className="flex justify-between items-center">
<p className="text-sm opacity-80"> <p className="text-sm opacity-80">
{formatDate(data.update_time)} {formatDate(data.update_time)}
</p> </p>
{sharedBadge}
</div> </div>
</section> </section>
</div> </div>

View File

@ -1,5 +1,5 @@
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { ChevronDown } from 'lucide-react'; import { Funnel } from 'lucide-react';
import React, { import React, {
ChangeEventHandler, ChangeEventHandler,
PropsWithChildren, PropsWithChildren,
@ -25,20 +25,20 @@ export const FilterButton = React.forwardRef<
>(({ count = 0, ...props }, ref) => { >(({ count = 0, ...props }, ref) => {
return ( return (
<Button variant="secondary" {...props} ref={ref}> <Button variant="secondary" {...props} ref={ref}>
<span {/* <span
className={cn({ className={cn({
'text-text-primary': count > 0, 'text-text-primary': count > 0,
'text-text-sub-title-invert': count === 0, 'text-text-sub-title-invert': count === 0,
})} })}
> >
Filter Filter
</span> </span> */}
{count > 0 && ( {count > 0 && (
<span className="rounded-full bg-text-badge px-1 text-xs "> <span className="rounded-full bg-text-badge px-1 text-xs ">
{count} {count}
</span> </span>
)} )}
<ChevronDown /> <Funnel />
</Button> </Button>
); );
}); });

View File

@ -2,6 +2,7 @@ import { LlmModelType } from '@/constants/knowledge';
import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks'; import { useComposeLlmOptionsByModelTypes } from '@/hooks/llm-hooks';
import * as SelectPrimitive from '@radix-ui/react-select'; import * as SelectPrimitive from '@radix-ui/react-select';
import { forwardRef, memo, useMemo, useState } from 'react'; import { forwardRef, memo, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { LlmSettingFieldItems } from '../llm-setting-items/next'; import { LlmSettingFieldItems } from '../llm-setting-items/next';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
import { Select, SelectTrigger, SelectValue } from '../ui/select'; import { Select, SelectTrigger, SelectValue } from '../ui/select';
@ -20,6 +21,7 @@ const NextInnerLLMSelect = forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>, React.ElementRef<typeof SelectPrimitive.Trigger>,
NextInnerLLMSelectProps NextInnerLLMSelectProps
>(({ value, disabled, filter, showSpeech2TextModel = false }, ref) => { >(({ value, disabled, filter, showSpeech2TextModel = false }, ref) => {
const { t } = useTranslation();
const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const ttsModel = useMemo(() => { const ttsModel = useMemo(() => {
@ -49,7 +51,7 @@ const NextInnerLLMSelect = forwardRef<
}} }}
ref={ref} ref={ref}
> >
<SelectValue> <SelectValue placeholder={t('common.pleaseSelect')}>
{ {
modelOptions modelOptions
.flatMap((x) => x.options) .flatMap((x) => x.options)

View File

@ -58,7 +58,10 @@ export function MetadataFilter({ prefix = '' }: MetadataFilterProps) {
name={methodName} name={methodName}
tooltip={t('metadataTip')} tooltip={t('metadataTip')}
> >
<SelectWithSearch options={MetadataOptions} /> <SelectWithSearch
options={MetadataOptions}
triggerClassName="!bg-bg-input"
/>
</RAGFlowFormItem> </RAGFlowFormItem>
)} )}
{hasKnowledge && metadata === DatasetMetadata.Manual && ( {hasKnowledge && metadata === DatasetMetadata.Manual && (

View File

@ -5,6 +5,7 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from '@/components/ui/form'; } from '@/components/ui/form';
import { cn } from '@/lib/utils';
import { ReactNode, cloneElement, isValidElement } from 'react'; import { ReactNode, cloneElement, isValidElement } from 'react';
import { ControllerRenderProps, useFormContext } from 'react-hook-form'; import { ControllerRenderProps, useFormContext } from 'react-hook-form';
@ -13,6 +14,7 @@ type RAGFlowFormItemProps = {
label: ReactNode; label: ReactNode;
tooltip?: ReactNode; tooltip?: ReactNode;
children: ReactNode | ((field: ControllerRenderProps) => ReactNode); children: ReactNode | ((field: ControllerRenderProps) => ReactNode);
horizontal?: boolean;
}; };
export function RAGFlowFormItem({ export function RAGFlowFormItem({
@ -20,6 +22,7 @@ export function RAGFlowFormItem({
label, label,
tooltip, tooltip,
children, children,
horizontal = false,
}: RAGFlowFormItemProps) { }: RAGFlowFormItemProps) {
const form = useFormContext(); const form = useFormContext();
return ( return (
@ -27,8 +30,14 @@ export function RAGFlowFormItem({
control={form.control} control={form.control}
name={name} name={name}
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem
<FormLabel tooltip={tooltip}>{label}</FormLabel> className={cn({
'flex items-center': horizontal,
})}
>
<FormLabel tooltip={tooltip} className={cn({ 'w-1/4': horizontal })}>
{label}
</FormLabel>
<FormControl> <FormControl>
{typeof children === 'function' {typeof children === 'function'
? children(field) ? children(field)

View File

@ -8,9 +8,5 @@ export function SharedBadge({ children }: PropsWithChildren) {
return null; return null;
} }
return ( return <span className="bg-bg-card rounded-sm px-1 text-xs">{children}</span>;
<span className="bg-text-secondary rounded-sm px-1 text-bg-base text-xs">
{children}
</span>
);
} }

View File

@ -17,7 +17,7 @@ const buttonVariants = cva(
outline: outline:
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50', 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
secondary: secondary:
'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80', 'bg-bg-input text-secondary-foreground shadow-xs hover:bg-bg-input/80',
ghost: ghost:
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50', 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline', link: 'text-primary underline-offset-4 hover:underline',

View File

@ -116,7 +116,10 @@ export { ExpandedInput, Input, SearchInput };
type NumberInputProps = { onChange?(value: number): void } & InputProps; type NumberInputProps = { onChange?(value: number): void } & InputProps;
export const NumberInput = ({ onChange, ...props }: NumberInputProps) => { export const NumberInput = React.forwardRef<
HTMLInputElement,
NumberInputProps & { value: Value; onChange(value: Value): void }
>(function NumberInput({ onChange, ...props }, ref) {
return ( return (
<Input <Input
type="number" type="number"
@ -125,6 +128,7 @@ export const NumberInput = ({ onChange, ...props }: NumberInputProps) => {
onChange?.(value === '' ? 0 : Number(value)); // convert to number onChange?.(value === '' ? 0 : Number(value)); // convert to number
}} }}
{...props} {...props}
ref={ref}
></Input> ></Input>
); );
}; });

View File

@ -12,13 +12,13 @@ const Progress = React.forwardRef<
<ProgressPrimitive.Root <ProgressPrimitive.Root
ref={ref} ref={ref}
className={cn( className={cn(
'relative h-4 w-full overflow-hidden rounded-full bg-secondary', 'relative h-4 w-full overflow-hidden rounded-full bg-bg-accent',
className, className,
)} )}
{...props} {...props}
> >
<ProgressPrimitive.Indicator <ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all" className="h-full w-full flex-1 bg-accent-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/> />
</ProgressPrimitive.Root> </ProgressPrimitive.Root>

View File

@ -23,6 +23,7 @@ export interface SegmentedProps
prefixCls?: string; prefixCls?: string;
direction?: 'ltr' | 'rtl'; direction?: 'ltr' | 'rtl';
motionName?: string; motionName?: string;
activeClassName?: string;
} }
export function Segmented({ export function Segmented({
@ -30,6 +31,7 @@ export function Segmented({
value, value,
onChange, onChange,
className, className,
activeClassName,
}: SegmentedProps) { }: SegmentedProps) {
const [selectedValue, setSelectedValue] = React.useState< const [selectedValue, setSelectedValue] = React.useState<
SegmentedValue | undefined SegmentedValue | undefined
@ -57,9 +59,12 @@ export function Segmented({
className={cn( className={cn(
'inline-flex items-center px-6 py-2 text-base font-normal rounded-3xl cursor-pointer', 'inline-flex items-center px-6 py-2 text-base font-normal rounded-3xl cursor-pointer',
{ {
'bg-text-primary': selectedValue === actualValue, 'text-bg-base bg-metallic-gradient border-b-[#00BEB4] border-b-2':
'text-bg-base': selectedValue === actualValue, selectedValue === actualValue,
}, },
activeClassName && selectedValue === actualValue
? activeClassName
: '',
)} )}
onClick={() => handleOnChange(actualValue)} onClick={() => handleOnChange(actualValue)}
> >

View File

@ -54,7 +54,7 @@ const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
return ( return (
<textarea <textarea
className={cn( className={cn(
'flex min-h-[80px] w-full bg-bg-card rounded-md border border-input px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm overflow-hidden', 'flex min-h-[80px] w-full bg-bg-input rounded-md border border-input px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm overflow-hidden',
className, className,
)} )}
rows={autoSize?.minRows ?? props.rows ?? undefined} rows={autoSize?.minRows ?? props.rows ?? undefined}

View File

@ -0,0 +1,4 @@
export enum PermissionRole {
Me = 'me',
Team = 'team',
}

View File

@ -1,4 +1,5 @@
import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit'; import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit';
import { ResponseType } from '@/interfaces/database/base';
import { import {
IDocumentInfo, IDocumentInfo,
IDocumentInfoFilter, IDocumentInfoFilter,
@ -45,9 +46,9 @@ export const useUploadNextDocument = () => {
data, data,
isPending: loading, isPending: loading,
mutateAsync, mutateAsync,
} = useMutation({ } = useMutation<ResponseType<IDocumentInfo[]>, Error, File[]>({
mutationKey: [DocumentApiAction.UploadDocument], mutationKey: [DocumentApiAction.UploadDocument],
mutationFn: async (fileList: File[]) => { mutationFn: async (fileList) => {
const formData = new FormData(); const formData = new FormData();
formData.append('kb_id', id!); formData.append('kb_id', id!);
fileList.forEach((file: any) => { fileList.forEach((file: any) => {

View File

@ -8,9 +8,13 @@ import {
} from '@/interfaces/database/llm'; } from '@/interfaces/database/llm';
import { buildLlmUuid } from '@/utils/llm-util'; import { buildLlmUuid } from '@/utils/llm-util';
export const enum LLMApiAction {
LlmList = 'llmList',
}
export const useFetchLlmList = (modelType?: LlmModelType) => { export const useFetchLlmList = (modelType?: LlmModelType) => {
const { data } = useQuery<IThirdAiModelCollection>({ const { data } = useQuery<IThirdAiModelCollection>({
queryKey: ['llmList'], queryKey: [LLMApiAction.LlmList],
initialData: {}, initialData: {},
queryFn: async () => { queryFn: async () => {
const { data } = await userService.llm_list({ model_type: modelType }); const { data } = await userService.llm_list({ model_type: modelType });

View File

@ -0,0 +1,464 @@
import message from '@/components/ui/message';
import { LanguageTranslationMap } from '@/constants/common';
import { ResponseGetType } from '@/interfaces/database/base';
import { IToken } from '@/interfaces/database/chat';
import { ITenantInfo } from '@/interfaces/database/knowledge';
import { ILangfuseConfig } from '@/interfaces/database/system';
import {
ISystemStatus,
ITenant,
ITenantUser,
IUserInfo,
} from '@/interfaces/database/user-setting';
import { ISetLangfuseConfigRequestBody } from '@/interfaces/request/system';
import userService, {
addTenantUser,
agreeTenant,
deleteTenantUser,
listTenant,
listTenantUser,
} from '@/services/user-service';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Modal } from 'antd';
import DOMPurify from 'dompurify';
import { isEmpty } from 'lodash';
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { history } from 'umi';
export const enum UserSettingApiAction {
UserInfo = 'userInfo',
TenantInfo = 'tenantInfo',
SaveSetting = 'saveSetting',
FetchManualSystemTokenList = 'fetchManualSystemTokenList',
FetchSystemTokenList = 'fetchSystemTokenList',
RemoveSystemToken = 'removeSystemToken',
CreateSystemToken = 'createSystemToken',
ListTenantUser = 'listTenantUser',
AddTenantUser = 'addTenantUser',
DeleteTenantUser = 'deleteTenantUser',
ListTenant = 'listTenant',
AgreeTenant = 'agreeTenant',
SetLangfuseConfig = 'setLangfuseConfig',
DeleteLangfuseConfig = 'deleteLangfuseConfig',
FetchLangfuseConfig = 'fetchLangfuseConfig',
}
export const useFetchUserInfo = (): ResponseGetType<IUserInfo> => {
const { i18n } = useTranslation();
const { data, isFetching: loading } = useQuery({
queryKey: [UserSettingApiAction.UserInfo],
initialData: {},
gcTime: 0,
queryFn: async () => {
const { data } = await userService.user_info();
if (data.code === 0) {
i18n.changeLanguage(
LanguageTranslationMap[
data.data.language as keyof typeof LanguageTranslationMap
],
);
}
return data?.data ?? {};
},
});
return { data, loading };
};
export const useFetchTenantInfo = (
showEmptyModelWarn = false,
): ResponseGetType<ITenantInfo> => {
const { t } = useTranslation();
const { data, isFetching: loading } = useQuery({
queryKey: [UserSettingApiAction.TenantInfo],
initialData: {},
gcTime: 0,
queryFn: async () => {
const { data: res } = await userService.get_tenant_info();
if (res.code === 0) {
// llm_id is chat_id
// asr_id is speech2txt
const { data } = res;
if (
showEmptyModelWarn &&
(isEmpty(data.embd_id) || isEmpty(data.llm_id))
) {
Modal.warning({
title: t('common.warn'),
content: (
<div
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(t('setting.modelProvidersWarn')),
}}
></div>
),
onOk() {
history.push('/user-setting/model');
},
});
}
data.chat_id = data.llm_id;
data.speech2text_id = data.asr_id;
return data;
}
return res;
},
});
return { data, loading };
};
export const useSelectParserList = (): Array<{
value: string;
label: string;
}> => {
const { data: tenantInfo } = useFetchTenantInfo(true);
const parserList = useMemo(() => {
const parserArray: Array<string> = tenantInfo?.parser_ids?.split(',') ?? [];
return parserArray.map((x) => {
const arr = x.split(':');
return { value: arr[0], label: arr[1] };
});
}, [tenantInfo]);
return parserList;
};
export const useSaveSetting = () => {
const queryClient = useQueryClient();
const { t } = useTranslation();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [UserSettingApiAction.SaveSetting],
mutationFn: async (
userInfo: { new_password: string } | Partial<IUserInfo>,
) => {
const { data } = await userService.setting(userInfo);
if (data.code === 0) {
message.success(t('message.modified'));
queryClient.invalidateQueries({ queryKey: ['userInfo'] });
}
return data?.code;
},
});
return { data, loading, saveSetting: mutateAsync };
};
export const useFetchSystemVersion = () => {
const [version, setVersion] = useState('');
const [loading, setLoading] = useState(false);
const fetchSystemVersion = useCallback(async () => {
try {
setLoading(true);
const { data } = await userService.getSystemVersion();
if (data.code === 0) {
setVersion(data.data);
setLoading(false);
}
} catch (error) {
setLoading(false);
}
}, []);
return { fetchSystemVersion, version, loading };
};
export const useFetchSystemStatus = () => {
const [systemStatus, setSystemStatus] = useState<ISystemStatus>(
{} as ISystemStatus,
);
const [loading, setLoading] = useState(false);
const fetchSystemStatus = useCallback(async () => {
setLoading(true);
const { data } = await userService.getSystemStatus();
if (data.code === 0) {
setSystemStatus(data.data);
setLoading(false);
}
}, []);
return {
systemStatus,
fetchSystemStatus,
loading,
};
};
export const useFetchManualSystemTokenList = () => {
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [UserSettingApiAction.FetchManualSystemTokenList],
mutationFn: async () => {
const { data } = await userService.listToken();
return data?.data ?? [];
},
});
return { data, loading, fetchSystemTokenList: mutateAsync };
};
export const useFetchSystemTokenList = () => {
const {
data,
isFetching: loading,
refetch,
} = useQuery<IToken[]>({
queryKey: [UserSettingApiAction.FetchSystemTokenList],
initialData: [],
gcTime: 0,
queryFn: async () => {
const { data } = await userService.listToken();
return data?.data ?? [];
},
});
return { data, loading, refetch };
};
export const useRemoveSystemToken = () => {
const queryClient = useQueryClient();
const { t } = useTranslation();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [UserSettingApiAction.RemoveSystemToken],
mutationFn: async (token: string) => {
const { data } = await userService.removeToken({}, token);
if (data.code === 0) {
message.success(t('message.deleted'));
queryClient.invalidateQueries({
queryKey: [UserSettingApiAction.FetchSystemTokenList],
});
}
return data?.data ?? [];
},
});
return { data, loading, removeToken: mutateAsync };
};
export const useCreateSystemToken = () => {
const queryClient = useQueryClient();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [UserSettingApiAction.CreateSystemToken],
mutationFn: async (params: Record<string, any>) => {
const { data } = await userService.createToken(params);
if (data.code === 0) {
queryClient.invalidateQueries({
queryKey: [UserSettingApiAction.FetchSystemTokenList],
});
}
return data?.data ?? [];
},
});
return { data, loading, createToken: mutateAsync };
};
export const useListTenantUser = () => {
const { data: tenantInfo } = useFetchTenantInfo();
const tenantId = tenantInfo.tenant_id;
const {
data,
isFetching: loading,
refetch,
} = useQuery<ITenantUser[]>({
queryKey: [UserSettingApiAction.ListTenantUser, tenantId],
initialData: [],
gcTime: 0,
enabled: !!tenantId,
queryFn: async () => {
const { data } = await listTenantUser(tenantId);
return data?.data ?? [];
},
});
return { data, loading, refetch };
};
export const useAddTenantUser = () => {
const { data: tenantInfo } = useFetchTenantInfo();
const queryClient = useQueryClient();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [UserSettingApiAction.AddTenantUser],
mutationFn: async (email: string) => {
const { data } = await addTenantUser(tenantInfo.tenant_id, email);
if (data.code === 0) {
queryClient.invalidateQueries({
queryKey: [UserSettingApiAction.ListTenantUser],
});
}
return data?.code;
},
});
return { data, loading, addTenantUser: mutateAsync };
};
export const useDeleteTenantUser = () => {
const { data: tenantInfo } = useFetchTenantInfo();
const queryClient = useQueryClient();
const { t } = useTranslation();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [UserSettingApiAction.DeleteTenantUser],
mutationFn: async ({
userId,
tenantId,
}: {
userId: string;
tenantId?: string;
}) => {
const { data } = await deleteTenantUser({
tenantId: tenantId ?? tenantInfo.tenant_id,
userId,
});
if (data.code === 0) {
message.success(t('message.deleted'));
queryClient.invalidateQueries({
queryKey: [UserSettingApiAction.ListTenantUser],
});
queryClient.invalidateQueries({
queryKey: [UserSettingApiAction.ListTenant],
});
}
return data?.data ?? [];
},
});
return { data, loading, deleteTenantUser: mutateAsync };
};
export const useListTenant = () => {
const { data: tenantInfo } = useFetchTenantInfo();
const tenantId = tenantInfo.tenant_id;
const {
data,
isFetching: loading,
refetch,
} = useQuery<ITenant[]>({
queryKey: [UserSettingApiAction.ListTenant, tenantId],
initialData: [],
gcTime: 0,
enabled: !!tenantId,
queryFn: async () => {
const { data } = await listTenant();
return data?.data ?? [];
},
});
return { data, loading, refetch };
};
export const useAgreeTenant = () => {
const queryClient = useQueryClient();
const { t } = useTranslation();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [UserSettingApiAction.AgreeTenant],
mutationFn: async (tenantId: string) => {
const { data } = await agreeTenant(tenantId);
if (data.code === 0) {
message.success(t('message.operated'));
queryClient.invalidateQueries({
queryKey: [UserSettingApiAction.ListTenant],
});
}
return data?.data ?? [];
},
});
return { data, loading, agreeTenant: mutateAsync };
};
export const useSetLangfuseConfig = () => {
const { t } = useTranslation();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [UserSettingApiAction.SetLangfuseConfig],
mutationFn: async (params: ISetLangfuseConfigRequestBody) => {
const { data } = await userService.setLangfuseConfig(params);
if (data.code === 0) {
message.success(t('message.operated'));
}
return data?.code;
},
});
return { data, loading, setLangfuseConfig: mutateAsync };
};
export const useDeleteLangfuseConfig = () => {
const { t } = useTranslation();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [UserSettingApiAction.DeleteLangfuseConfig],
mutationFn: async () => {
const { data } = await userService.deleteLangfuseConfig();
if (data.code === 0) {
message.success(t('message.deleted'));
}
return data?.code;
},
});
return { data, loading, deleteLangfuseConfig: mutateAsync };
};
export const useFetchLangfuseConfig = () => {
const { data, isFetching: loading } = useQuery<ILangfuseConfig>({
queryKey: [UserSettingApiAction.FetchLangfuseConfig],
gcTime: 0,
queryFn: async () => {
const { data } = await userService.getLangfuseConfig();
return data?.data;
},
});
return { data, loading };
};

View File

@ -12,10 +12,13 @@ import { LanguageList, LanguageMap, ThemeEnum } from '@/constants/common';
import { useChangeLanguage } from '@/hooks/logic-hooks'; import { useChangeLanguage } from '@/hooks/logic-hooks';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useNavigateWithFromState } from '@/hooks/route-hook'; import { useNavigateWithFromState } from '@/hooks/route-hook';
import { useListTenant } from '@/hooks/use-user-setting-request';
import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
import { TenantRole } from '@/pages/user-setting/constants';
import { Routes } from '@/routes'; import { Routes } from '@/routes';
import { camelCase } from 'lodash'; import { camelCase } from 'lodash';
import { import {
BellRing,
ChevronDown, ChevronDown,
CircleHelp, CircleHelp,
Cpu, Cpu,
@ -53,11 +56,11 @@ export function Header() {
changeLanguage(key); changeLanguage(key);
}; };
// const { data } = useListTenant(); const { data } = useListTenant();
// const showBell = useMemo(() => { const showBell = useMemo(() => {
// return data.some((x) => x.role === TenantRole.Invite); return data.some((x) => x.role === TenantRole.Invite);
// }, [data]); }, [data]);
const items = LanguageList.map((x) => ({ const items = LanguageList.map((x) => ({
key: x, key: x,
@ -68,9 +71,9 @@ export function Header() {
setTheme(theme === ThemeEnum.Dark ? ThemeEnum.Light : ThemeEnum.Dark); setTheme(theme === ThemeEnum.Dark ? ThemeEnum.Light : ThemeEnum.Dark);
}, [setTheme, theme]); }, [setTheme, theme]);
// const handleBellClick = useCallback(() => { const handleBellClick = useCallback(() => {
// navigate('/user-setting/team'); navigate('/user-setting/team');
// }, [navigate]); }, [navigate]);
const tagsData = useMemo( const tagsData = useMemo(
() => [ () => [
@ -160,6 +163,14 @@ export function Header() {
<Button variant={'ghost'} onClick={onThemeClick}> <Button variant={'ghost'} onClick={onThemeClick}>
{theme === 'light' ? <Sun /> : <Moon />} {theme === 'light' ? <Sun /> : <Moon />}
</Button> </Button>
{showBell && (
<Button variant={'ghost'} onClick={handleBellClick}>
<div className="relative">
<BellRing className="size-4 " />
<span className="absolute size-1 rounded -right-1 -top-1 bg-red-600"></span>
</div>
</Button>
)}
<div className="relative"> <div className="relative">
<RAGFlowAvatar <RAGFlowAvatar
name={nickname} name={nickname}

View File

@ -70,7 +70,7 @@ export default {
review: 'from 500+ reviews', review: 'from 500+ reviews',
}, },
header: { header: {
knowledgeBase: 'Knowledge Base', knowledgeBase: 'Dataset',
chat: 'Chat', chat: 'Chat',
register: 'Register', register: 'Register',
signin: 'Sign in', signin: 'Sign in',
@ -86,7 +86,7 @@ export default {
knowledgeList: { knowledgeList: {
welcome: 'Welcome back', welcome: 'Welcome back',
description: 'Which knowledge bases will you use today?', description: 'Which knowledge bases will you use today?',
createKnowledgeBase: 'Create knowledge base', createKnowledgeBase: 'Create Dataset',
name: 'Name', name: 'Name',
namePlaceholder: 'Please input name!', namePlaceholder: 'Please input name!',
doc: 'Docs', doc: 'Docs',
@ -845,6 +845,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
uploadLimit: uploadLimit:
'Each file must not exceed 10MB, and the total number of files must not exceed 128.', 'Each file must not exceed 10MB, and the total number of files must not exceed 128.',
destinationFolder: 'Destination folder', destinationFolder: 'Destination folder',
pleaseUploadAtLeastOneFile: 'Please upload at least one file',
}, },
flow: { flow: {
cite: 'Cite', cite: 'Cite',
@ -1441,6 +1442,7 @@ This delimiter is used to split the input text into several text pieces echo of
showQueryMindmap: 'Show Query Mindmap', showQueryMindmap: 'Show Query Mindmap',
embedApp: 'Embed App', embedApp: 'Embed App',
relatedSearch: 'Related Search', relatedSearch: 'Related Search',
descriptionValue: 'You are an intelligent assistant.',
okText: 'Save', okText: 'Save',
cancelText: 'Cancel', cancelText: 'Cancel',
}, },

View File

@ -240,9 +240,8 @@ export default {
promptTip: promptTip:
'Décrivez la tâche attendue du LLM, ses réponses, ses exigences, etc. Utilisez `/` pour afficher les variables disponibles.', 'Décrivez la tâche attendue du LLM, ses réponses, ses exigences, etc. Utilisez `/` pour afficher les variables disponibles.',
promptMessage: 'Le prompt est requis', promptMessage: 'Le prompt est requis',
promptText: `Veuillez résumer les paragraphes suivants. Attention aux chiffres, ne pas inventer. Paragraphes suivants : {cluster_content promptText: `Veuillez résumer les paragraphes suivants. Attention aux chiffres, ne pas inventer. Paragraphes suivants : {cluster_content}
} Le contenu à résumer est ci-dessus.`,
Le contenu à résumer est ci-dessus.`,
maxToken: 'Nombre maximal de tokens', maxToken: 'Nombre maximal de tokens',
maxTokenTip: 'Nombre maximal de tokens générés par résumé.', maxTokenTip: 'Nombre maximal de tokens générés par résumé.',
maxTokenMessage: 'Nombre maximal de tokens requis', maxTokenMessage: 'Nombre maximal de tokens requis',

View File

@ -799,6 +799,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
fileError: '文件错误', fileError: '文件错误',
uploadLimit: '文件大小不能超过10M文件总数不超过128个', uploadLimit: '文件大小不能超过10M文件总数不超过128个',
destinationFolder: '目标文件夹', destinationFolder: '目标文件夹',
pleaseUploadAtLeastOneFile: '请上传至少一个文件',
}, },
flow: { flow: {
flow: '工作流', flow: '工作流',
@ -1344,6 +1345,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
showQueryMindmap: '显示查询思维导图', showQueryMindmap: '显示查询思维导图',
embedApp: '嵌入网站', embedApp: '嵌入网站',
relatedSearch: '相关搜索', relatedSearch: '相关搜索',
descriptionValue: '你是一位智能助手。',
okText: '保存', okText: '保存',
cancelText: '返回', cancelText: '返回',
}, },

View File

@ -63,7 +63,6 @@ export function UploadAgentForm({ hideModal, onOk }: IModalProps<any>) {
value={field.value} value={field.value}
onValueChange={field.onChange} onValueChange={field.onChange}
maxFileCount={1} maxFileCount={1}
maxSize={4 * 1024 * 1024}
accept={{ '*.json': [FileMimeType.Json] }} accept={{ '*.json': [FileMimeType.Json] }}
/> />
</FormControl> </FormControl>

View File

@ -3,15 +3,15 @@ import { RunningStatus } from '@/constants/knowledge';
export const RunningStatusMap = { export const RunningStatusMap = {
[RunningStatus.UNSTART]: { [RunningStatus.UNSTART]: {
label: 'UNSTART', label: 'UNSTART',
color: 'cyan', color: 'var(--accent-primary)',
}, },
[RunningStatus.RUNNING]: { [RunningStatus.RUNNING]: {
label: 'Parsing', label: 'Parsing',
color: 'blue', color: 'var(--team-member)',
}, },
[RunningStatus.CANCEL]: { label: 'CANCEL', color: 'orange' }, [RunningStatus.CANCEL]: { label: 'CANCEL', color: 'var(--state-warning)' },
[RunningStatus.DONE]: { label: 'SUCCESS', color: 'blue' }, [RunningStatus.DONE]: { label: 'SUCCESS', color: 'var(--state-success)' },
[RunningStatus.FAIL]: { label: 'FAIL', color: 'red' }, [RunningStatus.FAIL]: { label: 'FAIL', color: 'var(--state-error' },
}; };
export * from '@/constants/knowledge'; export * from '@/constants/knowledge';

View File

@ -11,7 +11,7 @@ import { IDocumentInfo } from '@/interfaces/database/document';
import { formatFileSize } from '@/utils/common-util'; import { formatFileSize } from '@/utils/common-util';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { downloadDocument } from '@/utils/file-util'; import { downloadDocument } from '@/utils/file-util';
import { ArrowDownToLine, FolderPen, ScrollText, Trash2 } from 'lucide-react'; import { Download, Eye, PenLine, Trash2 } from 'lucide-react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { UseRenameDocumentShowType } from './use-rename-document'; import { UseRenameDocumentShowType } from './use-rename-document';
import { isParserRunning } from './utils'; import { isParserRunning } from './utils';
@ -57,12 +57,12 @@ export function DatasetActionCell({
disabled={isRunning} disabled={isRunning}
onClick={handleRename} onClick={handleRename}
> >
<FolderPen /> <PenLine />
</Button> </Button>
<HoverCard> <HoverCard>
<HoverCardTrigger> <HoverCardTrigger>
<Button variant="ghost" disabled={isRunning} size={'sm'}> <Button variant="ghost" disabled={isRunning} size={'sm'}>
<ScrollText /> <Eye />
</Button> </Button>
</HoverCardTrigger> </HoverCardTrigger>
<HoverCardContent className="w-[40vw] max-h-[40vh] overflow-auto"> <HoverCardContent className="w-[40vw] max-h-[40vh] overflow-auto">
@ -93,7 +93,7 @@ export function DatasetActionCell({
disabled={isRunning} disabled={isRunning}
size={'sm'} size={'sm'}
> >
<ArrowDownToLine /> <Download />
</Button> </Button>
)} )}
<ConfirmDeleteDialog onOk={handleRemove}> <ConfirmDeleteDialog onOk={handleRemove}>

View File

@ -164,7 +164,7 @@ export function DatasetTable({
)} )}
</TableBody> </TableBody>
</Table> </Table>
<div className="flex items-center justify-end py-4"> <div className="flex items-center justify-end py-4 absolute bottom-3 right-3">
<div className="space-x-2"> <div className="space-x-2">
<RAGFlowPagination <RAGFlowPagination
{...pick(pagination, 'current', 'pageSize')} {...pick(pagination, 'current', 'pageSize')}

View File

@ -111,6 +111,7 @@ export default function Dataset() {
hideModal={hideDocumentUploadModal} hideModal={hideDocumentUploadModal}
onOk={onDocumentUploadOk} onOk={onDocumentUploadOk}
loading={documentUploadLoading} loading={documentUploadLoading}
showParseOnCreation
></FileUploadDialog> ></FileUploadDialog>
)} )}
{createVisible && ( {createVisible && (

View File

@ -17,7 +17,7 @@ function Dot({ run }: { run: RunningStatus }) {
const runningStatus = RunningStatusMap[run]; const runningStatus = RunningStatusMap[run];
return ( return (
<span <span
className={'size-2 inline-block rounded'} className={'size-1 inline-block rounded'}
style={{ backgroundColor: runningStatus.color }} style={{ backgroundColor: runningStatus.color }}
></span> ></span>
); );
@ -89,7 +89,7 @@ export function ParsingCard({ record }: IProps) {
return ( return (
<HoverCard> <HoverCard>
<HoverCardTrigger asChild> <HoverCardTrigger asChild>
<Button variant={'ghost'} size={'sm'}> <Button variant={'transparent'} className="border-none" size={'sm'}>
<Dot run={record.run}></Dot> <Dot run={record.run}></Dot>
</Button> </Button>
</HoverCardTrigger> </HoverCardTrigger>

View File

@ -14,7 +14,7 @@ import {
import { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { IDocumentInfo } from '@/interfaces/database/document'; import { IDocumentInfo } from '@/interfaces/database/document';
import { CircleX, Play, RefreshCw } from 'lucide-react'; import { CircleX, RefreshCw } from 'lucide-react';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { RunningStatus } from './constant'; import { RunningStatus } from './constant';
@ -24,11 +24,13 @@ import { useHandleRunDocumentByIds } from './use-run-document';
import { UseSaveMetaShowType } from './use-save-meta'; import { UseSaveMetaShowType } from './use-save-meta';
import { isParserRunning } from './utils'; import { isParserRunning } from './utils';
const IconMap = { const IconMap = {
[RunningStatus.UNSTART]: <Play />, [RunningStatus.UNSTART]: (
[RunningStatus.RUNNING]: <CircleX />, <div className="w-0 h-0 border-l-[10px] border-l-accent-primary border-t-8 border-r-4 border-b-8 border-transparent"></div>
[RunningStatus.CANCEL]: <RefreshCw />, ),
[RunningStatus.DONE]: <RefreshCw />, [RunningStatus.RUNNING]: <CircleX size={14} color="var(--state-error)" />,
[RunningStatus.FAIL]: <RefreshCw />, [RunningStatus.CANCEL]: <RefreshCw size={14} color="var(--accent-primary)" />,
[RunningStatus.DONE]: <RefreshCw size={14} color="var(--accent-primary)" />,
[RunningStatus.FAIL]: <RefreshCw size={14} color="var(--accent-primary)" />,
}; };
export function ParsingStatusCell({ export function ParsingStatusCell({
@ -60,11 +62,11 @@ export function ParsingStatusCell({
}, [record, showSetMetaModal]); }, [record, showSetMetaModal]);
return ( return (
<section className="flex gap-2 items-center"> <section className="flex gap-8 items-center">
<div className="w-28 flex items-center justify-between"> <div className="w-fit flex items-center justify-between">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant={'ghost'} size={'sm'}> <Button variant={'transparent'} className="border-none" size={'sm'}>
{parser_id === 'naive' ? 'general' : parser_id} {parser_id === 'naive' ? 'general' : parser_id}
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
@ -77,7 +79,6 @@ export function ParsingStatusCell({
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<Separator orientation="vertical" className="h-2.5" />
</div> </div>
<ConfirmDeleteDialog <ConfirmDeleteDialog
title={t(`knowledgeDetails.redo`, { chunkNum: chunk_num })} title={t(`knowledgeDetails.redo`, { chunkNum: chunk_num })}
@ -85,17 +86,17 @@ export function ParsingStatusCell({
onOk={handleOperationIconClick(true)} onOk={handleOperationIconClick(true)}
onCancel={handleOperationIconClick(false)} onCancel={handleOperationIconClick(false)}
> >
<Button <div
variant={'ghost'} className="cursor-pointer flex items-center gap-3"
size={'sm'}
onClick={ onClick={
isZeroChunk || isRunning isZeroChunk || isRunning
? handleOperationIconClick(false) ? handleOperationIconClick(false)
: () => {} : () => {}
} }
> >
<Separator orientation="vertical" className="h-2.5" />
{operationIcon} {operationIcon}
</Button> </div>
</ConfirmDeleteDialog> </ConfirmDeleteDialog>
{isParserRunning(run) ? ( {isParserRunning(run) ? (
<HoverCard> <HoverCard>

View File

@ -65,7 +65,8 @@ export function useDatasetTableColumns({
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
variant="ghost" variant="transparent"
className="border-none"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')} onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
> >
{t('name')} {t('name')}
@ -103,7 +104,8 @@ export function useDatasetTableColumns({
header: ({ column }) => { header: ({ column }) => {
return ( return (
<Button <Button
variant="ghost" variant="transparent"
className="border-none"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')} onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
> >
{t('uploadDate')} {t('uploadDate')}
@ -141,7 +143,7 @@ export function useDatasetTableColumns({
}, },
{ {
accessorKey: 'run', accessorKey: 'run',
header: t('parsingStatus'), header: t('Parse'),
// meta: { cellClassName: 'min-w-[20vw]' }, // meta: { cellClassName: 'min-w-[20vw]' },
cell: ({ row }) => { cell: ({ row }) => {
return ( return (

View File

@ -1,5 +1,9 @@
import { UploadFormSchemaType } from '@/components/file-upload-dialog';
import { useSetModalState } from '@/hooks/common-hooks'; import { useSetModalState } from '@/hooks/common-hooks';
import { useUploadNextDocument } from '@/hooks/use-document-request'; import {
useRunDocument,
useUploadNextDocument,
} from '@/hooks/use-document-request';
import { getUnSupportedFilesCount } from '@/utils/document-util'; import { getUnSupportedFilesCount } from '@/utils/document-util';
import { useCallback } from 'react'; import { useCallback } from 'react';
@ -10,14 +14,24 @@ export const useHandleUploadDocument = () => {
showModal: showDocumentUploadModal, showModal: showDocumentUploadModal,
} = useSetModalState(); } = useSetModalState();
const { uploadDocument, loading } = useUploadNextDocument(); const { uploadDocument, loading } = useUploadNextDocument();
const { runDocumentByIds } = useRunDocument();
const onDocumentUploadOk = useCallback( const onDocumentUploadOk = useCallback(
async (fileList: File[]): Promise<number | undefined> => { async ({ fileList, parseOnCreation }: UploadFormSchemaType) => {
if (fileList.length > 0) { if (fileList.length > 0) {
const ret: any = await uploadDocument(fileList); const ret = await uploadDocument(fileList);
if (typeof ret?.message !== 'string') { if (typeof ret?.message !== 'string') {
return; return;
} }
if (ret.code === 0 && parseOnCreation) {
runDocumentByIds({
documentIds: ret.data.map((x) => x.id),
run: 1,
shouldDelete: false,
});
}
const count = getUnSupportedFilesCount(ret?.message); const count = getUnSupportedFilesCount(ret?.message);
/// 500 error code indicates that some file types are not supported /// 500 error code indicates that some file types are not supported
let code = ret?.code; let code = ret?.code;
@ -31,7 +45,7 @@ export const useHandleUploadDocument = () => {
return code; return code;
} }
}, },
[uploadDocument, hideDocumentUploadModal], [uploadDocument, runDocumentByIds, hideDocumentUploadModal],
); );
return { return {

View File

@ -19,7 +19,7 @@ export default function DatasetWrapper() {
const { data } = useFetchKnowledgeBaseConfiguration(); const { data } = useFetchKnowledgeBaseConfiguration();
return ( return (
<section> <section className="flex h-full flex-col w-full">
<PageHeader> <PageHeader>
<Breadcrumb> <Breadcrumb>
<BreadcrumbList> <BreadcrumbList>
@ -35,7 +35,7 @@ export default function DatasetWrapper() {
</BreadcrumbList> </BreadcrumbList>
</Breadcrumb> </Breadcrumb>
</PageHeader> </PageHeader>
<div className="flex flex-1"> <div className="flex flex-1 min-h-0">
<SideBar></SideBar> <SideBar></SideBar>
<div className="flex-1"> <div className="flex-1">
<Outlet /> <Outlet />

View File

@ -66,10 +66,10 @@ export function ChunkMethodForm() {
}, [finalParserId]); }, [finalParserId]);
return ( return (
<> <section className="h-full flex flex-col">
<section className="overflow-auto max-h-[76vh]"> <div className="overflow-auto flex-1 min-h-0">
<ConfigurationComponent></ConfigurationComponent> <ConfigurationComponent></ConfigurationComponent>
</section> </div>
<div className="text-right pt-4 flex justify-end gap-3"> <div className="text-right pt-4 flex justify-end gap-3">
<Button <Button
type="reset" type="reset"
@ -112,6 +112,6 @@ export function ChunkMethodForm() {
{t('knowledgeConfiguration.save')} {t('knowledgeConfiguration.save')}
</Button> </Button>
</div> </div>
</> </section>
); );
} }

View File

@ -1,4 +1,5 @@
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import CategoryPanel from './category-panel'; import CategoryPanel from './category-panel';
@ -14,20 +15,22 @@ export default ({
return ( return (
<div <div
style={{ className={cn('hidden flex-1', {
display: tab === 'chunkMethodForm' ? 'block' : 'none', 'flex flex-col': tab === 'chunkMethodForm',
}} })}
> >
<Button <div>
variant="outline" <Button
onClick={() => { variant="outline"
setVisible(!visible); onClick={() => {
}} setVisible(!visible);
> }}
Learn More >
</Button> Learn More
</Button>
</div>
<div <div
className="bg-[#FFF]/10 p-[20px] rounded-[12px] mt-[10px] relative" className="bg-[#FFF]/10 p-[20px] rounded-[12px] mt-[10px] relative flex-1 overflow-auto"
style={{ display: visible ? 'block' : 'none' }} style={{ display: visible ? 'block' : 'none' }}
> >
<CategoryPanel chunkMethod={parserId}></CategoryPanel> <CategoryPanel chunkMethod={parserId}></CategoryPanel>

View File

@ -1,6 +1,8 @@
import { FormContainer } from '@/components/form-container'; import { FormContainer } from '@/components/form-container';
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button'; import { Button, ButtonLoading } from '@/components/ui/button';
import { import {
FormControl, FormControl,
FormField, FormField,
@ -9,9 +11,10 @@ import {
FormMessage, FormMessage,
} from '@/components/ui/form'; } from '@/components/ui/form';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { PermissionRole } from '@/constants/permission';
import { useUpdateKnowledge } from '@/hooks/knowledge-hooks'; import { useUpdateKnowledge } from '@/hooks/knowledge-hooks';
import { transformFile2Base64 } from '@/utils/file-util'; import { transformFile2Base64 } from '@/utils/file-util';
import { Loader2Icon, Pencil, Upload } from 'lucide-react'; import { Pencil, Upload } from 'lucide-react';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -33,6 +36,13 @@ export function GeneralForm() {
const parser_id = defaultValues['parser_id']; const parser_id = defaultValues['parser_id'];
const { id: kb_id } = useParams(); const { id: kb_id } = useParams();
const teamOptions = useMemo(() => {
return Object.values(PermissionRole).map((x) => ({
label: t('knowledgeConfiguration.' + x),
value: x,
}));
}, [t]);
// init avatar file if it exists in defaultValues // init avatar file if it exists in defaultValues
useEffect(() => { useEffect(() => {
if (!avatarFile) { if (!avatarFile) {
@ -171,24 +181,35 @@ export function GeneralForm() {
); );
}} }}
/> />
<RAGFlowFormItem
name="permission"
label={t('knowledgeConfiguration.permissions')}
tooltip={t('knowledgeConfiguration.permissionsTip')}
horizontal
>
<SelectWithSearch
options={teamOptions}
triggerClassName="w-3/4"
></SelectWithSearch>
</RAGFlowFormItem>
</FormContainer> </FormContainer>
<div className="text-right pt-4 flex justify-end gap-3"> <div className="text-right pt-4 flex justify-end gap-3">
<Button <Button
type="reset" type="reset"
className="bg-transparent text-color-white hover:bg-transparent border-gray-500 border-[1px]" variant={'outline'}
onClick={() => { onClick={() => {
form.reset(); form.reset();
}} }}
> >
{t('knowledgeConfiguration.cancel')} {t('knowledgeConfiguration.cancel')}
</Button> </Button>
<Button <ButtonLoading
type="button" type="button"
disabled={submitLoading} loading={submitLoading}
onClick={() => { onClick={() => {
(async () => { (async () => {
let isValidate = await form.formControl.trigger('name'); let isValidate = await form.trigger('name');
const { name, description } = form.formState.values; const { name, description, permission } = form.getValues();
const avatar = avatarBase64Str; const avatar = avatarBase64Str;
if (isValidate) { if (isValidate) {
@ -198,14 +219,14 @@ export function GeneralForm() {
name, name,
description, description,
avatar, avatar,
permission,
}); });
} }
})(); })();
}} }}
> >
{submitLoading && <Loader2Icon className="animate-spin" />}
{t('knowledgeConfiguration.save')} {t('knowledgeConfiguration.save')}
</Button> </ButtonLoading>
</div> </div>
</> </>
); );

View File

@ -6,6 +6,7 @@ import {
TabsTrigger, TabsTrigger,
} from '@/components/ui/tabs-underlined'; } from '@/components/ui/tabs-underlined';
import { DocumentParserType } from '@/constants/knowledge'; import { DocumentParserType } from '@/constants/knowledge';
import { PermissionRole } from '@/constants/permission';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useState } from 'react'; import { useState } from 'react';
import { useForm, useWatch } from 'react-hook-form'; import { useForm, useWatch } from 'react-hook-form';
@ -43,7 +44,7 @@ export default function DatasetSettings() {
defaultValues: { defaultValues: {
name: '', name: '',
parser_id: DocumentParserType.Naive, parser_id: DocumentParserType.Naive,
permission: 'me', permission: PermissionRole.Me,
parser_config: { parser_config: {
layout_recognize: DocumentType.DeepDOC, layout_recognize: DocumentType.DeepDOC,
chunk_token_num: 512, chunk_token_num: 512,
@ -81,22 +82,23 @@ export default function DatasetSettings() {
} }
return ( return (
<section className="p-5 "> <section className="p-5 h-full flex flex-col">
<TopTitle <TopTitle
title={t('knowledgeDetails.configuration')} title={t('knowledgeDetails.configuration')}
description={t('knowledgeConfiguration.titleDescription')} description={t('knowledgeConfiguration.titleDescription')}
></TopTitle> ></TopTitle>
<div className="flex gap-14"> <div className="flex gap-14 flex-1 min-h-0">
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit(onSubmit)} onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6 basis-full min-w-[1000px] max-w-[1000px]" className="space-y-6 flex-1"
> >
<Tabs <Tabs
defaultValue="generalForm" defaultValue="generalForm"
onValueChange={(val) => { onValueChange={(val) => {
setCurrentTab(val); setCurrentTab(val);
}} }}
className="h-full flex flex-col"
> >
<TabsList className="grid bg-transparent grid-cols-2 rounded-none text-foreground"> <TabsList className="grid bg-transparent grid-cols-2 rounded-none text-foreground">
<TabsTrigger <TabsTrigger
@ -120,10 +122,10 @@ export default function DatasetSettings() {
</div> </div>
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="generalForm"> <TabsContent value="generalForm" className="flex-1 min-h-0">
<GeneralForm></GeneralForm> <GeneralForm></GeneralForm>
</TabsContent> </TabsContent>
<TabsContent value="chunkMethodForm"> <TabsContent value="chunkMethodForm" className="flex-1 min-h-0">
<ChunkMethodForm></ChunkMethodForm> <ChunkMethodForm></ChunkMethodForm>
</TabsContent> </TabsContent>
</Tabs> </Tabs>

View File

@ -62,8 +62,8 @@ export function SideBar({ refreshCount }: PropType) {
name={data.name} name={data.name}
className="size-16" className="size-16"
></RAGFlowAvatar> ></RAGFlowAvatar>
<div className=" text-text-secondary text-xs space-y-1"> <div className=" text-text-secondary text-xs space-y-1 overflow-hidden">
<h3 className="text-lg font-semibold line-clamp-1 text-text-primary"> <h3 className="text-lg font-semibold line-clamp-1 text-text-primary text-ellipsis overflow-hidden">
{data.name} {data.name}
</h3> </h3>
<div className="flex justify-between"> <div className="flex justify-between">

View File

@ -1,11 +1,11 @@
import { HomeCard } from '@/components/home-card'; import { HomeCard } from '@/components/home-card';
import { MoreButton } from '@/components/more-button'; import { MoreButton } from '@/components/more-button';
import { SharedBadge } from '@/components/shared-badge';
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 { IKnowledge } from '@/interfaces/database/knowledge'; import { IKnowledge } from '@/interfaces/database/knowledge';
import { ChevronRight } from 'lucide-react'; import { ChevronRight } from 'lucide-react';
import { DatasetDropdown } from './dataset-dropdown'; import { DatasetDropdown } from './dataset-dropdown';
import { useDisplayOwnerName } from './use-display-owner';
import { useRenameDataset } from './use-rename-dataset'; import { useRenameDataset } from './use-rename-dataset';
export type DatasetCardProps = { export type DatasetCardProps = {
@ -17,9 +17,6 @@ export function DatasetCard({
showDatasetRenameModal, showDatasetRenameModal,
}: DatasetCardProps) { }: DatasetCardProps) {
const { navigateToDataset } = useNavigatePage(); const { navigateToDataset } = useNavigatePage();
const displayOwnerName = useDisplayOwnerName();
const owner = displayOwnerName(dataset.tenant_id, dataset.nickname);
return ( return (
<HomeCard <HomeCard
@ -32,6 +29,7 @@ export function DatasetCard({
<MoreButton></MoreButton> <MoreButton></MoreButton>
</DatasetDropdown> </DatasetDropdown>
} }
sharedBadge={<SharedBadge>{dataset.nickname}</SharedBadge>}
onClick={navigateToDataset(dataset.id)} onClick={navigateToDataset(dataset.id)}
/> />
); );
@ -41,7 +39,7 @@ export function SeeAllCard() {
const { navigateToDatasetList } = useNavigatePage(); const { navigateToDatasetList } = useNavigatePage();
return ( return (
<Card className="w-40" onClick={navigateToDatasetList}> <Card className="w-40 flex-none" onClick={navigateToDatasetList}>
<CardContent className="p-2.5 pt-1 w-full h-full flex items-center justify-center gap-1.5 text-text-secondary"> <CardContent className="p-2.5 pt-1 w-full h-full flex items-center justify-center gap-1.5 text-text-secondary">
See All <ChevronRight className="size-4" /> See All <ChevronRight className="size-4" />
</CardContent> </CardContent>

View File

@ -1,4 +1,4 @@
import { Button } from '@/components/ui/button'; import { ButtonLoading } from '@/components/ui/button';
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -74,7 +74,11 @@ export function InputForm({ onOk }: IModalProps<any>) {
); );
} }
export function DatasetCreatingDialog({ hideModal, onOk }: IModalProps<any>) { export function DatasetCreatingDialog({
hideModal,
onOk,
loading,
}: IModalProps<any>) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@ -85,9 +89,9 @@ export function DatasetCreatingDialog({ hideModal, onOk }: IModalProps<any>) {
</DialogHeader> </DialogHeader>
<InputForm onOk={onOk}></InputForm> <InputForm onOk={onOk}></InputForm>
<DialogFooter> <DialogFooter>
<Button type="submit" form={FormId}> <ButtonLoading type="submit" form={FormId} loading={loading}>
{t('common.save')} {t('common.save')}
</Button> </ButtonLoading>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@ -1,3 +1,4 @@
import { UploadFormSchemaType } from '@/components/file-upload-dialog';
import { useSetModalState } from '@/hooks/common-hooks'; import { useSetModalState } from '@/hooks/common-hooks';
import { useUploadFile } from '@/hooks/use-file-request'; import { useUploadFile } from '@/hooks/use-file-request';
import { useCallback } from 'react'; import { useCallback } from 'react';
@ -13,7 +14,7 @@ export const useHandleUploadFile = () => {
const id = useGetFolderId(); const id = useGetFolderId();
const onFileUploadOk = useCallback( const onFileUploadOk = useCallback(
async (fileList: File[]): Promise<number | undefined> => { async ({ fileList }: UploadFormSchemaType): Promise<number | undefined> => {
if (fileList.length > 0) { if (fileList.length > 0) {
const ret: number = await uploadFile({ fileList, parentId: id }); const ret: number = await uploadFile({ fileList, parentId: id });
if (ret === 0) { if (ret === 0) {

View File

@ -51,7 +51,8 @@ export function Applications() {
options={options} options={options}
value={val} value={val}
onChange={handleChange} onChange={handleChange}
className="bg-transparent" className="bg-bg-card border border-border-button rounded-full"
activeClassName="bg-text-primary border-none"
></Segmented> ></Segmented>
</div> </div>
<div className="flex flex-wrap gap-4"> <div className="flex flex-wrap gap-4">

View File

@ -30,7 +30,7 @@ export function Datasets() {
<CardSkeleton /> <CardSkeleton />
</div> </div>
) : ( ) : (
<div className="flex gap-4 flex-1"> <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 max-h-[78vh] overflow-auto">
{kbs.slice(0, 6).map((dataset) => ( {kbs.slice(0, 6).map((dataset) => (
<DatasetCard <DatasetCard
key={dataset.id} key={dataset.id}

View File

@ -14,6 +14,7 @@ import {
} from '@/components/ui/form'; } from '@/components/ui/form';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
@ -21,6 +22,17 @@ export default function ChatBasicSetting() {
const { t } = useTranslate('chat'); const { t } = useTranslate('chat');
const form = useFormContext(); const form = useFormContext();
const languageOptions = [
{ value: 'English', label: 'English' },
{ value: 'Chinese', label: 'Chinese' },
{ value: 'Spanish', label: 'Spanish' },
{ value: 'French', label: 'French' },
{ value: 'German', label: 'German' },
{ value: 'Japanese', label: 'Japanese' },
{ value: 'Korean', label: 'Korean' },
{ value: 'Vietnamese', label: 'Vietnamese' },
];
return ( return (
<div className="space-y-8"> <div className="space-y-8">
<FormField <FormField
@ -35,7 +47,6 @@ export default function ChatBasicSetting() {
value={field.value} value={field.value}
onValueChange={field.onChange} onValueChange={field.onChange}
maxFileCount={1} maxFileCount={1}
maxSize={4 * 1024 * 1024}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -56,6 +67,30 @@ export default function ChatBasicSetting() {
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={form.control}
name="language"
render={({ field }) => (
<FormItem>
<FormLabel>{t('language')}</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder={t('common.languagePlaceholder')} />
</SelectTrigger>
</FormControl>
<SelectContent>
{languageOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="description" name="description"

View File

@ -35,13 +35,18 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
shouldUnregister: true, shouldUnregister: true,
defaultValues: { defaultValues: {
name: '', name: '',
icon: [],
language: 'English', language: 'English',
description: '',
kb_ids: [],
prompt_config: { prompt_config: {
quote: true, quote: true,
keyword: false, keyword: false,
tts: false, tts: false,
use_kg: false, use_kg: false,
refine_multiturn: true, refine_multiturn: true,
system: '',
parameters: [],
}, },
top_n: 8, top_n: 8,
vector_similarity_weight: 0.2, vector_similarity_weight: 0.2,
@ -89,25 +94,28 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
}, [data, form]); }, [data, form]);
return ( return (
<section className="p-5 w-[440px] border-l"> <section className="p-5 w-[440px] border-l flex flex-col">
<div className="flex justify-between items-center text-base pb-2"> <div className="flex justify-between items-center text-base pb-2">
{t('chat.chatSetting')} {t('chat.chatSetting')}
<X className="size-4 cursor-pointer" onClick={switchSettingVisible} /> <X className="size-4 cursor-pointer" onClick={switchSettingVisible} />
</div> </div>
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit, onInvalid)}> <form
<section className="space-y-6 overflow-auto max-h-[82vh] pr-4"> onSubmit={form.handleSubmit(onSubmit, onInvalid)}
className="flex-1 flex flex-col min-h-0"
>
<section className="space-y-6 overflow-auto flex-1 pr-4 min-h-0">
<ChatBasicSetting></ChatBasicSetting> <ChatBasicSetting></ChatBasicSetting>
<Separator /> <Separator />
<ChatPromptEngine></ChatPromptEngine> <ChatPromptEngine></ChatPromptEngine>
<Separator /> <Separator />
<ChatModelSettings></ChatModelSettings> <ChatModelSettings></ChatModelSettings>
</section> </section>
<div className="space-x-5 text-right"> <div className="space-x-5 text-right pt-4">
<Button variant={'outline'} onClick={switchSettingVisible}> <Button variant={'outline'} onClick={switchSettingVisible}>
{t('chat.cancel')} {t('chat.cancel')}
</Button> </Button>
<ButtonLoading className=" my-4" type="submit" loading={loading}> <ButtonLoading type="submit" loading={loading}>
{t('common.save')} {t('common.save')}
</ButtonLoading> </ButtonLoading>
</div> </div>

View File

@ -34,11 +34,11 @@ export function useChatSettingSchema() {
name: z.string().min(1, { message: t('assistantNameMessage') }), name: z.string().min(1, { message: t('assistantNameMessage') }),
icon: z.array(z.instanceof(File)), icon: z.array(z.instanceof(File)),
language: z.string().min(1, { language: z.string().min(1, {
message: 'Username must be at least 2 characters.', message: t('languageMessage'),
}), }),
description: z.string(), description: z.string().optional(),
kb_ids: z.array(z.string()).min(0, { kb_ids: z.array(z.string()).min(0, {
message: 'Username must be at least 1 characters.', message: t('knowledgeBasesMessage'),
}), }),
prompt_config: promptConfigSchema, prompt_config: promptConfigSchema,
...rerankFormSchema, ...rerankFormSchema,

View File

@ -2,6 +2,8 @@ import { LargeModelFormFieldWithoutFilter } from '@/components/large-model-form-
import { LlmSettingSchema } from '@/components/llm-setting-items/next'; import { LlmSettingSchema } from '@/components/llm-setting-items/next';
import { NextMessageInput } from '@/components/message-input/next'; import { NextMessageInput } from '@/components/message-input/next';
import MessageItem from '@/components/message-item'; import MessageItem from '@/components/message-item';
import PdfDrawer from '@/components/pdf-drawer';
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Form } from '@/components/ui/form'; import { Form } from '@/components/ui/form';
@ -54,7 +56,8 @@ type ChatCardProps = {
} & Pick< } & Pick<
MultipleChatBoxProps, MultipleChatBoxProps,
'controller' | 'removeChatBox' | 'addChatBox' | 'chatBoxIds' 'controller' | 'removeChatBox' | 'addChatBox' | 'chatBoxIds'
>; > &
Pick<ReturnType<typeof useClickDrawer>, 'clickDocumentButton'>;
const ChatCard = forwardRef(function ChatCard( const ChatCard = forwardRef(function ChatCard(
{ {
@ -66,6 +69,7 @@ const ChatCard = forwardRef(function ChatCard(
chatBoxIds, chatBoxIds,
derivedMessages, derivedMessages,
sendLoading, sendLoading,
clickDocumentButton,
}: ChatCardProps, }: ChatCardProps,
ref, ref,
) { ) {
@ -178,6 +182,7 @@ const ChatCard = forwardRef(function ChatCard(
removeMessageById={removeMessageById} removeMessageById={removeMessageById}
regenerateMessage={regenerateMessage} regenerateMessage={regenerateMessage}
sendLoading={sendLoading} sendLoading={sendLoading}
clickDocumentButton={clickDocumentButton}
></MessageItem> ></MessageItem>
); );
})} })}
@ -211,6 +216,8 @@ export function MultipleChatBox({
const { conversationId } = useGetChatSearchParams(); const { conversationId } = useGetChatSearchParams();
const disabled = useGetSendButtonDisabled(); const disabled = useGetSendButtonDisabled();
const sendDisabled = useSendButtonDisabled(value); const sendDisabled = useSendButtonDisabled(value);
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
useClickDrawer();
return ( return (
<section className="h-full flex flex-col px-5"> <section className="h-full flex flex-col px-5">
@ -227,6 +234,7 @@ export function MultipleChatBox({
derivedMessages={messageRecord[id]} derivedMessages={messageRecord[id]}
ref={setFormRef(id)} ref={setFormRef(id)}
sendLoading={sendLoading} sendLoading={sendLoading}
clickDocumentButton={clickDocumentButton}
></ChatCard> ></ChatCard>
))} ))}
</div> </div>
@ -246,6 +254,14 @@ export function MultipleChatBox({
onUpload={handleUploadFile} onUpload={handleUploadFile}
/> />
</div> </div>
{visible && (
<PdfDrawer
visible={visible}
hideModal={hideModal}
documentId={documentId}
chunk={selectedChunk}
></PdfDrawer>
)}
</section> </section>
); );
} }

View File

@ -1,5 +1,7 @@
import { NextMessageInput } from '@/components/message-input/next'; import { NextMessageInput } from '@/components/message-input/next';
import MessageItem from '@/components/message-item'; import MessageItem from '@/components/message-item';
import PdfDrawer from '@/components/pdf-drawer';
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
import { MessageType } from '@/constants/chat'; import { MessageType } from '@/constants/chat';
import { import {
useFetchConversation, useFetchConversation,
@ -43,6 +45,8 @@ export function SingleChatBox({ controller }: IProps) {
const { data: conversation } = useFetchConversation(); const { data: conversation } = useFetchConversation();
const disabled = useGetSendButtonDisabled(); const disabled = useGetSendButtonDisabled();
const sendDisabled = useSendButtonDisabled(value); const sendDisabled = useSendButtonDisabled(value);
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
useClickDrawer();
return ( return (
<section className="flex flex-col p-5 h-full"> <section className="flex flex-col p-5 h-full">
@ -68,7 +72,7 @@ export function SingleChatBox({ controller }: IProps) {
}, },
message, message,
)} )}
// clickDocumentButton={clickDocumentButton} clickDocumentButton={clickDocumentButton}
index={i} index={i}
removeMessageById={removeMessageById} removeMessageById={removeMessageById}
regenerateMessage={regenerateMessage} regenerateMessage={regenerateMessage}
@ -94,6 +98,14 @@ export function SingleChatBox({ controller }: IProps) {
onUpload={handleUploadFile} onUpload={handleUploadFile}
isUploading={isUploading} isUploading={isUploading}
/> />
{visible && (
<PdfDrawer
visible={visible}
hideModal={hideModal}
documentId={documentId}
chunk={selectedChunk}
></PdfDrawer>
)}
</section> </section>
); );
} }

View File

@ -109,13 +109,12 @@ export default function Chat() {
<Card className="flex-1 min-w-0 bg-transparent border h-full"> <Card className="flex-1 min-w-0 bg-transparent border h-full">
<CardContent className="flex p-0 h-full"> <CardContent className="flex p-0 h-full">
<Card className="flex flex-col flex-1 bg-transparent"> <Card className="flex flex-col flex-1 bg-transparent min-w-0">
<CardHeader <CardHeader
className={cn('p-5', { 'border-b': hasSingleChatBox })} className={cn('p-5', { 'border-b': hasSingleChatBox })}
> >
<CardTitle className="flex justify-between items-center text-base"> <CardTitle className="flex justify-between items-center text-base">
<div>{conversation.name}</div> <div className="truncate">{conversation.name}</div>
<Button <Button
variant={'ghost'} variant={'ghost'}
onClick={switchDebugMode} onClick={switchDebugMode}

View File

@ -90,8 +90,8 @@ export function Sessions({
'bg-bg-card': conversationId === x.id, 'bg-bg-card': conversationId === x.id,
})} })}
> >
<CardContent className="px-3 py-2 flex justify-between items-center group"> <CardContent className="px-3 py-2 flex justify-between items-center group gap-1">
{x.name} <div className="truncate">{x.name}</div>
<ConversationDropdown conversation={x}> <ConversationDropdown conversation={x}>
<MoreButton></MoreButton> <MoreButton></MoreButton>
</ConversationDropdown> </ConversationDropdown>

View File

@ -1,5 +1,6 @@
import { useSetModalState } from '@/hooks/common-hooks'; import { useSetModalState } from '@/hooks/common-hooks';
import { useSetDialog } from '@/hooks/use-chat-request'; import { useSetDialog } from '@/hooks/use-chat-request';
import { useFetchTenantInfo } from '@/hooks/use-user-setting-request';
import { IDialog } from '@/interfaces/database/chat'; import { IDialog } from '@/interfaces/database/chat';
import { isEmpty, omit } from 'lodash'; import { isEmpty, omit } from 'lodash';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
@ -14,6 +15,7 @@ export const useRenameChat = () => {
} = useSetModalState(); } = useSetModalState();
const { setDialog, loading } = useSetDialog(); const { setDialog, loading } = useSetDialog();
const { t } = useTranslation(); const { t } = useTranslation();
const tenantInfo = useFetchTenantInfo();
const InitialData = useMemo( const InitialData = useMemo(
() => ({ () => ({
@ -32,13 +34,13 @@ export const useRenameChat = () => {
reasoning: false, reasoning: false,
parameters: [{ key: 'knowledge', optional: false }], parameters: [{ key: 'knowledge', optional: false }],
}, },
llm_id: '', llm_id: tenantInfo.data.llm_id,
llm_setting: {}, llm_setting: {},
similarity_threshold: 0.2, similarity_threshold: 0.2,
vector_similarity_weight: 0.30000000000000004, vector_similarity_weight: 0.30000000000000004,
top_n: 8, top_n: 8,
}), }),
[t], [t, tenantInfo.data.llm_id],
); );
const onChatRenameOk = useCallback( const onChatRenameOk = useCallback(

View File

@ -1,3 +1,4 @@
import showMessage from '@/components/ui/message';
import { MessageType } from '@/constants/chat'; import { MessageType } from '@/constants/chat';
import { import {
useHandleMessageInputChange, useHandleMessageInputChange,
@ -159,7 +160,7 @@ export function useSendMultipleChatMessage(
if (res && (res?.response.status !== 200 || res?.data?.code !== 0)) { if (res && (res?.response.status !== 200 || res?.data?.code !== 0)) {
// cancel loading // cancel loading
setValue(message.content); setValue(message.content);
console.info('removeLatestMessage111'); showMessage.error(res.data.message);
removeLatestMessage(chatBoxId); removeLatestMessage(chatBoxId);
} }
}, },

View File

@ -128,7 +128,7 @@ export default function SearchPage() {
</div> </div>
<div className="absolute right-5 top-4 "> <div className="absolute right-5 top-4 ">
<Button <Button
className="bg-text-primary text-bg-base border-b-[#00BEB4] border-b-2" className="bg-text-primary text-bg-base border-b-accent-primary border-b-2"
onClick={() => { onClick={() => {
handleOperate().then((res) => { handleOperate().then((res) => {
console.log(res, 'res'); console.log(res, 'res');

View File

@ -27,7 +27,7 @@ export default function SearchPage({
<div className="relative z-10 px-8 pt-8 flex text-transparent flex-col justify-center items-center w-[780px]"> <div className="relative z-10 px-8 pt-8 flex text-transparent flex-col justify-center items-center w-[780px]">
<h1 <h1
className={cn( className={cn(
'text-4xl font-bold bg-gradient-to-r from-sky-600 from-30% via-sky-500 via-60% to-emerald-500 bg-clip-text', 'text-4xl font-bold bg-gradient-to-l from-[#40EBE3] to-[#4A51FF] bg-clip-text',
)} )}
> >
RAGFlow RAGFlow

View File

@ -113,7 +113,7 @@ export function LlmSettingFieldItems({
<FormControl> <FormControl>
<SelectWithSearch <SelectWithSearch
options={options || modelOptions} options={options || modelOptions}
triggerClassName="bg-bg-card" triggerClassName="!bg-bg-input"
{...field} {...field}
></SelectWithSearch> ></SelectWithSearch>
</FormControl> </FormControl>

View File

@ -114,8 +114,8 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64 const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64
const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]); const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]);
const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState(''); const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState('');
const descriptionDefaultValue = 'You are an intelligent assistant.';
const { t } = useTranslation(); const { t } = useTranslation();
const descriptionDefaultValue = t('search.descriptionValue');
const resetForm = useCallback(() => { const resetForm = useCallback(() => {
formMethods.reset({ formMethods.reset({
search_id: data?.id, search_id: data?.id,
@ -415,7 +415,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
<FormLabel>{t('search.description')}</FormLabel> <FormLabel>{t('search.description')}</FormLabel>
<FormControl> <FormControl>
<Textarea <Textarea
placeholder="You are an intelligent assistant." placeholder={descriptionDefaultValue}
{...field} {...field}
onFocus={() => { onFocus={() => {
if (field.value === descriptionDefaultValue) { if (field.value === descriptionDefaultValue) {
@ -444,7 +444,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
<span className="text-destructive mr-1"> *</span> <span className="text-destructive mr-1"> *</span>
{t('search.datasets')} {t('search.datasets')}
</FormLabel> </FormLabel>
<FormControl> <FormControl className="bg-bg-input">
<MultiSelect <MultiSelect
options={datasetList} options={datasetList}
onValueChange={(value) => { onValueChange={(value) => {
@ -452,7 +452,6 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
}} }}
showSelectAll={false} showSelectAll={false}
placeholder={t('chat.knowledgeBasesMessage')} placeholder={t('chat.knowledgeBasesMessage')}
variant="inverted"
maxCount={10} maxCount={10}
defaultValue={field.value} defaultValue={field.value}
{...field} {...field}
@ -568,6 +567,7 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
<RAGFlowSelect <RAGFlowSelect
{...field} {...field}
options={rerankModelOptions} options={rerankModelOptions}
triggerClassName={'bg-bg-input'}
// disabled={disabled} // disabled={disabled}
placeholder={'model'} placeholder={'model'}
/> />

View File

@ -83,7 +83,7 @@ export default function SearchingView({
> >
<h1 <h1
className={cn( className={cn(
'text-4xl font-bold bg-gradient-to-r from-sky-600 from-30% via-sky-500 via-60% to-emerald-500 bg-clip-text cursor-pointer', 'text-4xl font-bold bg-gradient-to-l from-[#40EBE3] to-[#4A51FF] bg-clip-text cursor-pointer',
)} )}
onClick={() => { onClick={() => {
setIsSearching?.(false); setIsSearching?.(false);

View File

@ -59,8 +59,6 @@ export function ImportMcpForm({ hideModal, onOk }: IModalProps<any>) {
<FileUploader <FileUploader
value={field.value} value={field.value}
onValueChange={field.onChange} onValueChange={field.onChange}
maxFileCount={1}
maxSize={4 * 1024 * 1024}
accept={{ '*.json': [FileMimeType.Json] }} accept={{ '*.json': [FileMimeType.Json] }}
/> />
</FormControl> </FormControl>

View File

@ -70,116 +70,73 @@ const routes = [
component: `@/pages${Routes.AgentShare}`, component: `@/pages${Routes.AgentShare}`,
layout: false, layout: false,
}, },
// { {
// path: '/', path: Routes.Home,
// component: '@/layouts', component: '@/layouts',
// layout: false, layout: false,
// wrappers: ['@/wrappers/auth'], redirect: '/knowledge',
// routes: [ },
// { path: '/', redirect: '/knowledge' }, {
// { path: '/knowledge',
// path: '/knowledge', component: '@/pages/knowledge',
// component: '@/pages/knowledge', },
// // component: '@/pages/knowledge/datasets', {
// }, path: '/knowledge',
// { component: '@/pages/add-knowledge',
// path: '/knowledge', routes: [
// component: '@/pages/add-knowledge', {
// routes: [ path: 'dataset',
// { component: '@/pages/add-knowledge/components/knowledge-dataset',
// path: '/knowledge/dataset', routes: [
// component: '@/pages/add-knowledge/components/knowledge-dataset', {
// routes: [ path: '',
// { component: '@/pages/add-knowledge/components/knowledge-file',
// path: '/knowledge/dataset', },
// component: '@/pages/add-knowledge/components/knowledge-file', {
// }, path: 'chunk',
// { component: '@/pages/add-knowledge/components/knowledge-chunk',
// path: '/knowledge/dataset/chunk', },
// component: '@/pages/add-knowledge/components/knowledge-chunk', ],
// }, },
// ], {
// }, path: 'configuration',
// { component: '@/pages/add-knowledge/components/knowledge-setting',
// path: '/knowledge/configuration', },
// component: '@/pages/add-knowledge/components/knowledge-setting', {
// }, path: 'testing',
// { component: '@/pages/add-knowledge/components/knowledge-testing',
// path: '/knowledge/testing', },
// component: '@/pages/add-knowledge/components/knowledge-testing', {
// }, path: 'knowledgeGraph',
// { component: '@/pages/add-knowledge/components/knowledge-graph',
// path: '/knowledge/knowledgeGraph', },
// component: '@/pages/add-knowledge/components/knowledge-graph', ],
// }, },
// ],
// }, {
// { path: '/chat',
// path: '/chat', component: '@/pages/chat',
// component: '@/pages/chat', },
// }, {
// { path: '/file',
// path: '/user-setting', component: '@/pages/file-manager',
// component: '@/pages/user-setting', },
// routes: [ {
// { path: '/user-setting', redirect: '/user-setting/profile' }, path: '/flow',
// { component: '@/pages/flow/list',
// path: '/user-setting/profile', },
// // component: '@/pages/user-setting/setting-profile', {
// component: '@/pages/user-setting/setting-profile', path: Routes.AgentList,
// }, component: `@/pages/${Routes.Agents}`,
// { },
// path: '/user-setting/locale', {
// component: '@/pages/user-setting/setting-locale', path: '/flow/:id',
// }, component: '@/pages/flow',
// { },
// path: '/user-setting/password', {
// component: '@/pages/user-setting/setting-password', path: '/search',
// }, component: '@/pages/search',
// { },
// path: '/user-setting/model',
// component: '@/pages/user-setting/setting-model',
// },
// {
// path: '/user-setting/team',
// component: '@/pages/user-setting/setting-team',
// },
// {
// path: '/user-setting/system',
// component: '@/pages/user-setting/setting-system',
// },
// {
// path: '/user-setting/api',
// component: '@/pages/user-setting/setting-api',
// },
// {
// path: `/user-setting${Routes.Mcp}`,
// component: `@/pages${Routes.ProfileMcp}`,
// },
// ],
// },
// {
// path: '/file',
// component: '@/pages/file-manager',
// },
// {
// path: '/flow',
// component: '@/pages/flow/list',
// },
// {
// path: Routes.AgentList,
// component: `@/pages/${Routes.Agents}`,
// },
// {
// path: '/flow/:id',
// component: '@/pages/flow',
// },
// {
// path: '/search',
// component: '@/pages/search',
// },
// ],
// },
{ {
path: '/document/:id', path: '/document/:id',
component: '@/pages/document-viewer', component: '@/pages/document-viewer',

View File

@ -58,6 +58,8 @@ module.exports = {
'bg-base': 'var(--bg-base)', 'bg-base': 'var(--bg-base)',
'bg-card': 'var(--bg-card)', 'bg-card': 'var(--bg-card)',
'bg-component': 'var(--bg-component)',
'bg-input': 'var(--bg-input)',
'text-primary': 'var(--text-primary)', 'text-primary': 'var(--text-primary)',
'text-secondary': 'var(--text-secondary)', 'text-secondary': 'var(--text-secondary)',
'text-disabled': 'var(--text-disabled)', 'text-disabled': 'var(--text-disabled)',
@ -206,6 +208,10 @@ module.exports = {
ring: 'hsl(var(--sidebar-ring))', ring: 'hsl(var(--sidebar-ring))',
}, },
}, },
backgroundImage: {
'metallic-gradient':
'linear-gradient(104deg, var(--text-primary) 30%, var(--metallic) 50%, var(--text-primary) 70%)',
},
borderRadius: { borderRadius: {
lg: `var(--radius)`, lg: `var(--radius)`,
md: `calc(var(--radius) - 2px)`, md: `calc(var(--radius) - 2px)`,

View File

@ -90,11 +90,15 @@
--input-border: rgba(22, 22, 24, 0.2); --input-border: rgba(22, 22, 24, 0.2);
--metallic: #46464a;
/* design colors */ /* design colors */
--bg-base: #f6f6f7; --bg-base: #ffffff;
/* card color , dividing line */ /* card color , dividing line */
--bg-card: rgba(0, 0, 0, 0.05); --bg-card: rgba(0, 0, 0, 0.05);
--bg-component: #ffffff;
--bg-input: rgba(255, 255, 255, 0);
--bg-accent: rgba(76, 164, 231, 0.05);
/* Button ,Body text, Input completed text */ /* Button ,Body text, Input completed text */
--text-primary: #161618; --text-primary: #161618;
--text-secondary: #75787a; --text-secondary: #75787a;
@ -107,7 +111,7 @@
--border-accent: #000000; --border-accent: #000000;
--border-button: rgba(0, 0, 0, 0.1); --border-button: rgba(0, 0, 0, 0.1);
/* Regulators, parsing, switches, variables */ /* Regulators, parsing, switches, variables */
--accent-primary: #4ca4e7; --accent-primary: #00beb4;
/* Output Variables Box */ /* Output Variables Box */
--bg-accent: rgba(76, 164, 231, 0.05); --bg-accent: rgba(76, 164, 231, 0.05);
@ -230,10 +234,13 @@
--input-border: rgba(255, 255, 255, 0.2); --input-border: rgba(255, 255, 255, 0.2);
--metallic: #fafafa;
/* design colors */ /* design colors */
--bg-base: #161618; --bg-base: #161618;
--bg-card: rgba(255, 255, 255, 0.05); --bg-card: rgba(255, 255, 255, 0.05);
--bg-component: #202025;
--bg-input: rgba(255, 255, 255, 0.05);
--text-primary: #f6f6f7; --text-primary: #f6f6f7;
--text-secondary: #b2b5b7; --text-secondary: #b2b5b7;
--text-disabled: #75787a; --text-disabled: #75787a;