Compare commits

...

12 Commits

Author SHA1 Message Date
8e6ddd7c1b Fix: Metadata bugs. (#12111)
### What problem does this PR solve?

Fix: Metadata bugs.

### Type of change

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

---------

Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
2025-12-23 14:16:57 +08:00
d1bc7ad2ee Fix only one of multiple retrieval tools is effective (#12110)
### What problem does this PR solve?

Fix only one of multiple retrieval tools is effective

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-12-23 14:08:25 +08:00
321474fb97 Fix: update method call to use simplified async tool reaction (#12108)
### What problem does this PR solve?
pr:#12091
change:update method call to use simplified async tool reaction

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-12-23 13:36:58 +08:00
ea89e4e0c6 Feat: add GLM-4.7 (#12102)
### What problem does this PR solve?

 Add GLM-4.7.

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2025-12-23 12:38:56 +08:00
9e31631d8f Feat: Add memory multi-select dropdown to recall and message operator forms. #4213 (#12106)
### What problem does this PR solve?

Feat: Add memory multi-select dropdown to recall and message operator
forms. #4213

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-12-23 11:54:32 +08:00
712d537d66 Fix: vision_figure_parser_docx/pdf_wrapper (#12104)
### What problem does this PR solve?

Fix: vision_figure_parser_docx/pdf_wrapper  #11735

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-12-23 11:51:28 +08:00
bd4eb19393 Fix:Bugs fix (Reduce metadata saving steps ...) (#12095)
### What problem does this PR solve?

Fix:Bugs fix
- Configure memory and metadata (in Chinese)
- Add indexing modal
- Reduce metadata saving steps

### Type of change

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

---------

Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
2025-12-23 11:50:35 +08:00
02efab7c11 Feat: Hide part of the message field in webhook mode #10427 (#12100)
### What problem does this PR solve?

Feat: Hide part of the message field in webhook mode  #10427

### Type of change


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

---------

Co-authored-by: balibabu <assassin_cike@163.com>
2025-12-23 10:45:05 +08:00
8ce129bc51 Update workflow (#12101)
### What problem does this PR solve?

As title

### Type of change

- [x] Other (please describe): Update GitHub action

Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2025-12-23 10:03:24 +08:00
d5a44e913d Fix: fix task cancel (#12093)
### What problem does this PR solve?

Fix: fix task cancel

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-12-23 09:38:25 +08:00
1444de981c Feat: enhance webhook response to include status and success fields and simplify ReAct agent (#12091)
### What problem does this PR solve?

change:
enhance webhook response to include status and success fields and
simplify ReAct agent

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2025-12-23 09:36:08 +08:00
bd76b8ff1a Fix: Tika server upgrades. (#12073)
### What problem does this PR solve?

#12037

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-12-23 09:35:52 +08:00
56 changed files with 923 additions and 268 deletions

View File

@ -209,7 +209,7 @@ jobs:
echo "Waiting for service to be available..."
sleep 5
done
source .venv/bin/activate && pytest -s --tb=short --level=${HTTP_API_TEST_LEVEL} test/testcases/test_sdk_api
source .venv/bin/activate && pytest -s --tb=short --level=${HTTP_API_TEST_LEVEL} test/testcases/test_sdk_api | tee es_sdk_test.log
- name: Run frontend api tests against Elasticsearch
run: |
@ -218,7 +218,7 @@ jobs:
echo "Waiting for service to be available..."
sleep 5
done
source .venv/bin/activate && pytest -s --tb=short sdk/python/test/test_frontend_api/get_email.py sdk/python/test/test_frontend_api/test_dataset.py
source .venv/bin/activate && pytest -s --tb=short sdk/python/test/test_frontend_api/get_email.py sdk/python/test/test_frontend_api/test_dataset.py | tee es_api_test.log
- name: Run http api tests against Elasticsearch
run: |
@ -227,7 +227,7 @@ jobs:
echo "Waiting for service to be available..."
sleep 5
done
source .venv/bin/activate && pytest -s --tb=short --level=${HTTP_API_TEST_LEVEL} test/testcases/test_http_api
source .venv/bin/activate && pytest -s --tb=short --level=${HTTP_API_TEST_LEVEL} test/testcases/test_http_api | tee es_http_api_test.log
- name: Stop ragflow:nightly
if: always() # always run this step even if previous steps failed
@ -247,7 +247,7 @@ jobs:
echo "Waiting for service to be available..."
sleep 5
done
source .venv/bin/activate && DOC_ENGINE=infinity pytest -s --tb=short --level=${HTTP_API_TEST_LEVEL} test/testcases/test_sdk_api > infinity_sdk_test.log
source .venv/bin/activate && DOC_ENGINE=infinity pytest -s --tb=short --level=${HTTP_API_TEST_LEVEL} test/testcases/test_sdk_api | tee infinity_sdk_test.log
- name: Run frontend api tests against Infinity
run: |
@ -256,7 +256,7 @@ jobs:
echo "Waiting for service to be available..."
sleep 5
done
source .venv/bin/activate && DOC_ENGINE=infinity pytest -s --tb=short sdk/python/test/test_frontend_api/get_email.py sdk/python/test/test_frontend_api/test_dataset.py > infinity_api_test.log
source .venv/bin/activate && DOC_ENGINE=infinity pytest -s --tb=short sdk/python/test/test_frontend_api/get_email.py sdk/python/test/test_frontend_api/test_dataset.py | tee infinity_api_test.log
- name: Run http api tests against Infinity
run: |
@ -265,7 +265,7 @@ jobs:
echo "Waiting for service to be available..."
sleep 5
done
source .venv/bin/activate && DOC_ENGINE=infinity pytest -s --tb=short --level=${HTTP_API_TEST_LEVEL} test/testcases/test_http_api > infinity_http_api_test.log
source .venv/bin/activate && DOC_ENGINE=infinity pytest -s --tb=short --level=${HTTP_API_TEST_LEVEL} test/testcases/test_http_api | tee infinity_http_api_test.log
- name: Stop ragflow:nightly
if: always() # always run this step even if previous steps failed

View File

@ -540,6 +540,8 @@ class Canvas(Graph):
cite = re.search(r"\[ID:[ 0-9]+\]", cpn_obj.output("content"))
message_end = {}
if cpn_obj.get_param("status"):
message_end["status"] = cpn_obj.get_param("status")
if isinstance(cpn_obj.output("attachment"), dict):
message_end["attachment"] = cpn_obj.output("attachment")
if cite:

View File

@ -29,8 +29,8 @@ from api.db.services.llm_service import LLMBundle
from api.db.services.tenant_llm_service import TenantLLMService
from api.db.services.mcp_server_service import MCPServerService
from common.connection_utils import timeout
from rag.prompts.generator import next_step_async, COMPLETE_TASK, analyze_task_async, \
citation_prompt, reflect_async, kb_prompt, citation_plus, full_question, message_fit_in, structured_output_prompt
from rag.prompts.generator import next_step_async, COMPLETE_TASK, \
citation_prompt, kb_prompt, citation_plus, full_question, message_fit_in, structured_output_prompt
from common.mcp_tool_call_conn import MCPToolCallSession, mcp_tool_metadata_to_openai_tool
from agent.component.llm import LLMParam, LLM
@ -84,9 +84,10 @@ class Agent(LLM, ToolBase):
def __init__(self, canvas, id, param: LLMParam):
LLM.__init__(self, canvas, id, param)
self.tools = {}
for cpn in self._param.tools:
for idx, cpn in enumerate(self._param.tools):
cpn = self._load_tool_obj(cpn)
self.tools[cpn.get_meta()["function"]["name"]] = cpn
name = cpn.get_meta()["function"]["name"]
self.tools[f"{name}_{idx}"] = cpn
self.chat_mdl = LLMBundle(self._canvas.get_tenant_id(), TenantLLMService.llm_id2llm_type(self._param.llm_id), self._param.llm_id,
max_retries=self._param.max_retries,
@ -202,7 +203,7 @@ class Agent(LLM, ToolBase):
_, msg = message_fit_in([{"role": "system", "content": prompt}, *msg], int(self.chat_mdl.max_length * 0.97))
use_tools = []
ans = ""
async for delta_ans, _tk in self._react_with_tools_streamly_async(prompt, msg, use_tools, user_defined_prompt,schema_prompt=schema_prompt):
async for delta_ans, _tk in self._react_with_tools_streamly_async_simple(prompt, msg, use_tools, user_defined_prompt,schema_prompt=schema_prompt):
if self.check_if_canceled("Agent processing"):
return
ans += delta_ans
@ -246,7 +247,7 @@ class Agent(LLM, ToolBase):
_, msg = message_fit_in([{"role": "system", "content": prompt}, *msg], int(self.chat_mdl.max_length * 0.97))
answer_without_toolcall = ""
use_tools = []
async for delta_ans, _ in self._react_with_tools_streamly_async(prompt, msg, use_tools, user_defined_prompt):
async for delta_ans, _ in self._react_with_tools_streamly_async_simple(prompt, msg, use_tools, user_defined_prompt):
if self.check_if_canceled("Agent streaming"):
return
@ -264,7 +265,7 @@ class Agent(LLM, ToolBase):
if use_tools:
self.set_output("use_tools", use_tools)
async def _react_with_tools_streamly_async(self, prompt, history: list[dict], use_tools, user_defined_prompt={}, schema_prompt: str = ""):
async def _react_with_tools_streamly_async_simple(self, prompt, history: list[dict], use_tools, user_defined_prompt={}, schema_prompt: str = ""):
token_count = 0
tool_metas = self.tool_meta
hist = deepcopy(history)
@ -276,6 +277,28 @@ class Agent(LLM, ToolBase):
else:
user_request = history[-1]["content"]
def build_task_desc(prompt: str, user_request: str, tool_metas: list[dict], user_defined_prompt: dict | None = None) -> str:
"""Build a minimal task_desc by concatenating prompt, query, and tool schemas."""
user_defined_prompt = user_defined_prompt or {}
tools_json = json.dumps(tool_metas, ensure_ascii=False, indent=2)
task_desc = (
"### Agent Prompt\n"
f"{prompt}\n\n"
"### User Request\n"
f"{user_request}\n\n"
"### Tools (schemas)\n"
f"{tools_json}\n"
)
if user_defined_prompt:
udp_json = json.dumps(user_defined_prompt, ensure_ascii=False, indent=2)
task_desc += "\n### User Defined Prompts\n" + udp_json + "\n"
return task_desc
async def use_tool_async(name, args):
nonlocal hist, use_tools, last_calling
logging.info(f"{last_calling=} == {name=}")
@ -286,9 +309,6 @@ class Agent(LLM, ToolBase):
"arguments": args,
"results": tool_response
})
# self.callback("add_memory", {}, "...")
#self.add_memory(hist[-2]["content"], hist[-1]["content"], name, args, str(tool_response), user_defined_prompt)
return name, tool_response
async def complete():
@ -326,6 +346,21 @@ class Agent(LLM, ToolBase):
self.callback("gen_citations", {}, txt, elapsed_time=timer()-st)
def build_observation(tool_call_res: list[tuple]) -> str:
"""
Build a Observation from tool call results.
No LLM involved.
"""
if not tool_call_res:
return ""
lines = ["Observation:"]
for name, result in tool_call_res:
lines.append(f"[{name} result]")
lines.append(str(result))
return "\n".join(lines)
def append_user_content(hist, content):
if hist[-1]["role"] == "user":
hist[-1]["content"] += content
@ -333,7 +368,7 @@ class Agent(LLM, ToolBase):
hist.append({"role": "user", "content": content})
st = timer()
task_desc = await analyze_task_async(self.chat_mdl, prompt, user_request, tool_metas, user_defined_prompt)
task_desc = build_task_desc(prompt, user_request, tool_metas, user_defined_prompt)
self.callback("analyze_task", {}, task_desc, elapsed_time=timer()-st)
for _ in range(self._param.max_rounds + 1):
if self.check_if_canceled("Agent streaming"):
@ -364,7 +399,7 @@ class Agent(LLM, ToolBase):
results = await asyncio.gather(*tool_tasks) if tool_tasks else []
st = timer()
reflection = await reflect_async(self.chat_mdl, hist, results, user_defined_prompt)
reflection = build_observation(results)
append_user_content(hist, reflection)
self.callback("reflection", {}, str(reflection), elapsed_time=timer()-st)
@ -393,6 +428,135 @@ Respond immediately with your final comprehensive answer.
async for txt, tkcnt in complete():
yield txt, tkcnt
# async def _react_with_tools_streamly_async(self, prompt, history: list[dict], use_tools, user_defined_prompt={}, schema_prompt: str = ""):
# token_count = 0
# tool_metas = self.tool_meta
# hist = deepcopy(history)
# last_calling = ""
# if len(hist) > 3:
# st = timer()
# user_request = await full_question(messages=history, chat_mdl=self.chat_mdl)
# self.callback("Multi-turn conversation optimization", {}, user_request, elapsed_time=timer()-st)
# else:
# user_request = history[-1]["content"]
# async def use_tool_async(name, args):
# nonlocal hist, use_tools, last_calling
# logging.info(f"{last_calling=} == {name=}")
# last_calling = name
# tool_response = await self.toolcall_session.tool_call_async(name, args)
# use_tools.append({
# "name": name,
# "arguments": args,
# "results": tool_response
# })
# # self.callback("add_memory", {}, "...")
# #self.add_memory(hist[-2]["content"], hist[-1]["content"], name, args, str(tool_response), user_defined_prompt)
# return name, tool_response
# async def complete():
# nonlocal hist
# need2cite = self._param.cite and self._canvas.get_reference()["chunks"] and self._id.find("-->") < 0
# if schema_prompt:
# need2cite = False
# cited = False
# if hist and hist[0]["role"] == "system":
# if schema_prompt:
# hist[0]["content"] += "\n" + schema_prompt
# if need2cite and len(hist) < 7:
# hist[0]["content"] += citation_prompt()
# cited = True
# yield "", token_count
# _hist = hist
# if len(hist) > 12:
# _hist = [hist[0], hist[1], *hist[-10:]]
# entire_txt = ""
# async for delta_ans in self._generate_streamly(_hist):
# if not need2cite or cited:
# yield delta_ans, 0
# entire_txt += delta_ans
# if not need2cite or cited:
# return
# st = timer()
# txt = ""
# async for delta_ans in self._gen_citations_async(entire_txt):
# if self.check_if_canceled("Agent streaming"):
# return
# yield delta_ans, 0
# txt += delta_ans
# self.callback("gen_citations", {}, txt, elapsed_time=timer()-st)
# def append_user_content(hist, content):
# if hist[-1]["role"] == "user":
# hist[-1]["content"] += content
# else:
# hist.append({"role": "user", "content": content})
# st = timer()
# task_desc = await analyze_task_async(self.chat_mdl, prompt, user_request, tool_metas, user_defined_prompt)
# self.callback("analyze_task", {}, task_desc, elapsed_time=timer()-st)
# for _ in range(self._param.max_rounds + 1):
# if self.check_if_canceled("Agent streaming"):
# return
# response, tk = await next_step_async(self.chat_mdl, hist, tool_metas, task_desc, user_defined_prompt)
# # self.callback("next_step", {}, str(response)[:256]+"...")
# token_count += tk or 0
# hist.append({"role": "assistant", "content": response})
# try:
# functions = json_repair.loads(re.sub(r"```.*", "", response))
# if not isinstance(functions, list):
# raise TypeError(f"List should be returned, but `{functions}`")
# for f in functions:
# if not isinstance(f, dict):
# raise TypeError(f"An object type should be returned, but `{f}`")
# tool_tasks = []
# for func in functions:
# name = func["name"]
# args = func["arguments"]
# if name == COMPLETE_TASK:
# append_user_content(hist, f"Respond with a formal answer. FORGET(DO NOT mention) about `{COMPLETE_TASK}`. The language for the response MUST be as the same as the first user request.\n")
# async for txt, tkcnt in complete():
# yield txt, tkcnt
# return
# tool_tasks.append(asyncio.create_task(use_tool_async(name, args)))
# results = await asyncio.gather(*tool_tasks) if tool_tasks else []
# st = timer()
# reflection = await reflect_async(self.chat_mdl, hist, results, user_defined_prompt)
# append_user_content(hist, reflection)
# self.callback("reflection", {}, str(reflection), elapsed_time=timer()-st)
# except Exception as e:
# logging.exception(msg=f"Wrong JSON argument format in LLM ReAct response: {e}")
# e = f"\nTool call error, please correct the input parameter of response format and call it again.\n *** Exception ***\n{e}"
# append_user_content(hist, str(e))
# logging.warning( f"Exceed max rounds: {self._param.max_rounds}")
# final_instruction = f"""
# {user_request}
# IMPORTANT: You have reached the conversation limit. Based on ALL the information and research you have gathered so far, please provide a DIRECT and COMPREHENSIVE final answer to the original request.
# Instructions:
# 1. SYNTHESIZE all information collected during this conversation
# 2. Provide a COMPLETE response using existing data - do not suggest additional research
# 3. Structure your response as a FINAL DELIVERABLE, not a plan
# 4. If information is incomplete, state what you found and provide the best analysis possible with available data
# 5. DO NOT mention conversation limits or suggest further steps
# 6. Focus on delivering VALUE with the information already gathered
# Respond immediately with your final comprehensive answer.
# """
# if self.check_if_canceled("Agent final instruction"):
# return
# append_user_content(hist, final_instruction)
# async for txt, tkcnt in complete():
# yield txt, tkcnt
async def _gen_citations_async(self, text):
retrievals = self._canvas.get_reference()
retrievals = {"chunks": list(retrievals["chunks"].values()), "doc_aggs": list(retrievals["doc_aggs"].values())}

View File

@ -76,6 +76,7 @@ async def list_chunk():
"image_id": sres.field[id].get("img_id", ""),
"available_int": int(sres.field[id].get("available_int", 1)),
"positions": sres.field[id].get("position_int", []),
"doc_type_kwd": sres.field[id].get("doc_type_kwd")
}
assert isinstance(d["positions"], list)
assert len(d["positions"]) == 0 or (isinstance(d["positions"][0], list) and len(d["positions"][0]) == 5)
@ -176,10 +177,9 @@ async def set():
settings.docStoreConn.update({"id": req["chunk_id"]}, _d, search.index_name(tenant_id), doc.kb_id)
# update image
image_id = req.get("img_id")
bkt, name = image_id.split("-")
image_base64 = req.get("image_base64", None)
if image_base64:
bkt, name = req.get("img_id", "-").split("-")
image_binary = base64.b64decode(image_base64)
settings.STORAGE_IMPL.put(bkt, name, image_binary)
return get_json_result(data=True)

View File

@ -150,6 +150,21 @@ async def update():
return server_error_response(e)
@manager.route('/update_metadata_setting', methods=['post']) # noqa: F821
@login_required
@validate_request("kb_id", "metadata")
async def update_metadata_setting():
req = await get_request_json()
e, kb = KnowledgebaseService.get_by_id(req["kb_id"])
if not e:
return get_data_error_result(
message="Database error (Knowledgebase rename)!")
kb = kb.to_dict()
kb["parser_config"]["metadata"] = req["metadata"]
KnowledgebaseService.update_by_id(kb["id"], kb)
return get_json_result(data=kb)
@manager.route('/detail', methods=['GET']) # noqa: F821
@login_required
def detail():

View File

@ -326,7 +326,6 @@ async def webhook(agent_id: str):
secret = jwt_cfg.get("secret")
if not secret:
raise Exception("JWT secret not configured")
required_claims = jwt_cfg.get("required_claims", [])
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("Bearer "):
@ -750,7 +749,7 @@ async def webhook(agent_id: str):
async def sse():
nonlocal canvas
contents: list[str] = []
status = 200
try:
async for ans in canvas.run(
query="",
@ -765,6 +764,8 @@ async def webhook(agent_id: str):
content = "</think>"
if content:
contents.append(content)
if ans["event"] == "message_end":
status = int(ans["data"].get("status", status))
if is_test:
append_webhook_trace(
agent_id,
@ -782,7 +783,11 @@ async def webhook(agent_id: str):
}
)
final_content = "".join(contents)
yield json.dumps(final_content, ensure_ascii=False)
return {
"message": final_content,
"success": True,
"code": status,
}
except Exception as e:
if is_test:
@ -804,10 +809,14 @@ async def webhook(agent_id: str):
"success": False,
}
)
yield json.dumps({"code": 500, "message": str(e)}, ensure_ascii=False)
return {"code": 400, "message": str(e),"success":False}
resp = Response(sse(), mimetype="application/json")
return resp
result = await sse()
return Response(
json.dumps(result),
status=result["code"],
mimetype="application/json",
)
@manager.route("/webhook_trace/<agent_id>", methods=["GET"]) # noqa: F821

View File

@ -411,6 +411,8 @@ class KnowledgebaseService(CommonService):
ok, _t = TenantService.get_by_id(tenant_id)
if not ok:
return False, get_data_error_result(message="Tenant not found.")
if kwargs.get("parser_config") and isinstance(kwargs["parser_config"], dict) and not kwargs["parser_config"].get("llm_id"):
kwargs["parser_config"]["llm_id"] = _t.llm_id
# Build payload
kb_id = get_uuid()

View File

@ -31,6 +31,7 @@
"entity_type_kwd": {"type": "varchar", "default": "", "analyzer": "whitespace-#"},
"source_id": {"type": "varchar", "default": "", "analyzer": "whitespace-#"},
"n_hop_with_weight": {"type": "varchar", "default": ""},
"mom_with_weight": {"type": "varchar", "default": ""},
"removed_kwd": {"type": "varchar", "default": "", "analyzer": "whitespace-#"},
"doc_type_kwd": {"type": "varchar", "default": "", "analyzer": "whitespace-#"},
"toc_kwd": {"type": "varchar", "default": "", "analyzer": "whitespace-#"},

View File

@ -762,6 +762,13 @@
"status": "1",
"rank": "940",
"llm": [
{
"llm_name": "glm-4.7",
"tags": "LLM,CHAT,128K",
"max_tokens": 128000,
"model_type": "chat",
"is_tools": true
},
{
"llm_name": "glm-4.5",
"tags": "LLM,CHAT,128K",

View File

@ -38,8 +38,8 @@ def vision_figure_parser_figure_data_wrapper(figures_data_without_positions):
def vision_figure_parser_docx_wrapper(sections, tbls, callback=None,**kwargs):
if not tbls:
return []
if not sections:
return tbls
try:
vision_model = LLMBundle(kwargs["tenant_id"], LLMType.IMAGE2TEXT)
callback(0.7, "Visual model detected. Attempting to enhance figure extraction...")

View File

@ -23,8 +23,8 @@ def get_urls(use_china_mirrors=False) -> list[Union[str, list[str]]]:
return [
"http://mirrors.tuna.tsinghua.edu.cn/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb",
"http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_arm64.deb",
"https://repo.huaweicloud.com/repository/maven/org/apache/tika/tika-server-standard/3.0.0/tika-server-standard-3.0.0.jar",
"https://repo.huaweicloud.com/repository/maven/org/apache/tika/tika-server-standard/3.0.0/tika-server-standard-3.0.0.jar.md5",
"https://repo.huaweicloud.com/repository/maven/org/apache/tika/tika-server-standard/3.2.3/tika-server-standard-3.2.3.jar",
"https://repo.huaweicloud.com/repository/maven/org/apache/tika/tika-server-standard/3.2.3/tika-server-standard-3.2.3.jar.md5",
"https://openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktoken",
["https://registry.npmmirror.com/-/binary/chrome-for-testing/121.0.6167.85/linux64/chrome-linux64.zip", "chrome-linux64-121-0-6167-85"],
["https://registry.npmmirror.com/-/binary/chrome-for-testing/121.0.6167.85/linux64/chromedriver-linux64.zip", "chromedriver-linux64-121-0-6167-85"],
@ -34,8 +34,8 @@ def get_urls(use_china_mirrors=False) -> list[Union[str, list[str]]]:
return [
"http://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_amd64.deb",
"http://ports.ubuntu.com/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2_arm64.deb",
"https://repo1.maven.org/maven2/org/apache/tika/tika-server-standard/3.0.0/tika-server-standard-3.0.0.jar",
"https://repo1.maven.org/maven2/org/apache/tika/tika-server-standard/3.0.0/tika-server-standard-3.0.0.jar.md5",
"https://repo1.maven.org/maven2/org/apache/tika/tika-server-standard/3.2.3/tika-server-standard-3.2.3.jar",
"https://repo1.maven.org/maven2/org/apache/tika/tika-server-standard/3.2.3/tika-server-standard-3.2.3.jar.md5",
"https://openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktoken",
["https://storage.googleapis.com/chrome-for-testing-public/121.0.6167.85/linux64/chrome-linux64.zip", "chrome-linux64-121-0-6167-85"],
["https://storage.googleapis.com/chrome-for-testing-public/121.0.6167.85/linux64/chromedriver-linux64.zip", "chromedriver-linux64-121-0-6167-85"],

View File

@ -91,7 +91,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
filename, binary=binary, from_page=from_page, to_page=to_page)
remove_contents_table(sections, eng=is_english(
random_choices([t for t, _ in sections], k=200)))
tbls=vision_figure_parser_docx_wrapper(sections=sections,tbls=tbls,callback=callback,**kwargs)
tbls = vision_figure_parser_docx_wrapper(sections=sections,tbls=tbls,callback=callback,**kwargs)
# tbls = [((None, lns), None) for lns in tbls]
sections=[(item[0],item[1] if item[1] is not None else "") for item in sections if not isinstance(item[1], Image.Image)]
callback(0.8, "Finish parsing.")
@ -147,9 +147,16 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
elif re.search(r"\.doc$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
with BytesIO(binary) as binary:
binary = BytesIO(binary)
doc_parsed = parser.from_buffer(binary)
try:
from tika import parser as tika_parser
except Exception as e:
callback(0.8, f"tika not available: {e}. Unsupported .doc parsing.")
logging.warning(f"tika not available: {e}. Unsupported .doc parsing for {filename}.")
return []
binary = BytesIO(binary)
doc_parsed = tika_parser.from_buffer(binary)
if doc_parsed.get('content', None) is not None:
sections = doc_parsed['content'].split('\n')
sections = [(line, "") for line in sections if line]
remove_contents_table(sections, eng=is_english(

View File

@ -312,7 +312,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
tk_cnt = num_tokens_from_string(txt)
if sec_id > -1:
last_sid = sec_id
tbls=vision_figure_parser_pdf_wrapper(tbls=tbls,callback=callback,**kwargs)
tbls = vision_figure_parser_pdf_wrapper(tbls=tbls,callback=callback,**kwargs)
res = tokenize_table(tbls, doc, eng)
res.extend(tokenize_chunks(chunks, doc, eng, pdf_parser))
table_ctx = max(0, int(parser_config.get("table_context_size", 0) or 0))
@ -325,7 +325,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
docx_parser = Docx()
ti_list, tbls = docx_parser(filename, binary,
from_page=0, to_page=10000, callback=callback)
tbls=vision_figure_parser_docx_wrapper(sections=ti_list,tbls=tbls,callback=callback,**kwargs)
tbls = vision_figure_parser_docx_wrapper(sections=ti_list,tbls=tbls,callback=callback,**kwargs)
res = tokenize_table(tbls, doc, eng)
for text, image in ti_list:
d = copy.deepcopy(doc)

View File

@ -76,7 +76,7 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
if re.search(r"\.docx$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
sections, tbls = naive.Docx()(filename, binary)
tbls=vision_figure_parser_docx_wrapper(sections=sections,tbls=tbls,callback=callback,**kwargs)
tbls = vision_figure_parser_docx_wrapper(sections=sections, tbls=tbls, callback=callback, **kwargs)
sections = [s for s, _ in sections if s]
for (_, html), _ in tbls:
sections.append(html)
@ -142,10 +142,18 @@ def chunk(filename, binary=None, from_page=0, to_page=100000,
elif re.search(r"\.doc$", filename, re.IGNORECASE):
callback(0.1, "Start to parse.")
try:
from tika import parser as tika_parser
except Exception as e:
callback(0.8, f"tika not available: {e}. Unsupported .doc parsing.")
logging.warning(f"tika not available: {e}. Unsupported .doc parsing for {filename}.")
return []
binary = BytesIO(binary)
doc_parsed = parser.from_buffer(binary)
sections = doc_parsed['content'].split('\n')
sections = [s for s in sections if s]
doc_parsed = tika_parser.from_buffer(binary)
if doc_parsed.get('content', None) is not None:
sections = doc_parsed['content'].split('\n')
sections = [s for s in sections if s]
callback(0.8, "Finish parsing.")
else:

View File

@ -650,7 +650,7 @@ class Parser(ProcessBase):
tmpf.flush()
tmp_path = os.path.abspath(tmpf.name)
seq2txt_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.SPEECH2TEXT)
seq2txt_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.SPEECH2TEXT, llm_name=conf["llm_id"])
txt = seq2txt_mdl.transcription(tmp_path)
self.set_output("text", txt)

View File

@ -324,8 +324,9 @@ def tool_schema(tools_description: list[dict], complete_task=False):
}
}
}
for tool in tools_description:
desc[tool["function"]["name"]] = tool
for idx, tool in enumerate(tools_description):
name = tool["function"]["name"]
desc[f"{name}_{idx}"] = tool
return "\n\n".join([f"## {i+1}. {fnm}\n{json.dumps(des, ensure_ascii=False, indent=4)}" for i, (fnm, des) in enumerate(desc.items())])

View File

@ -374,7 +374,7 @@ async def build_chunks(task, progress_callback):
chat_mdl = LLMBundle(task["tenant_id"], LLMType.CHAT, llm_name=task["llm_id"], lang=task["language"])
async def gen_metadata_task(chat_mdl, d):
cached = get_llm_cache(chat_mdl.llm_name, d["content_with_weight"], "metadata")
cached = get_llm_cache(chat_mdl.llm_name, d["content_with_weight"], "metadata", {})
if not cached:
async with chat_limiter:
cached = await gen_metadata(chat_mdl,
@ -852,7 +852,7 @@ async def do_handle_task(task):
task_tenant_id = task["tenant_id"]
task_embedding_id = task["embd_id"]
task_language = task["language"]
task_llm_id = task["llm_id"]
task_llm_id = task["parser_config"].get("llm_id") or task["llm_id"]
task_dataset_id = task["kb_id"]
task_doc_id = task["doc_id"]
task_document_name = task["name"]
@ -1024,33 +1024,65 @@ async def do_handle_task(task):
chunk_count = len(set([chunk["id"] for chunk in chunks]))
start_ts = timer()
e = await insert_es(task_id, task_tenant_id, task_dataset_id, chunks, progress_callback)
if not e:
return
logging.info("Indexing doc({}), page({}-{}), chunks({}), elapsed: {:.2f}".format(task_document_name, task_from_page,
task_to_page, len(chunks),
timer() - start_ts))
async def _maybe_insert_es(_chunks):
if has_canceled(task_id):
return True
e = await insert_es(task_id, task_tenant_id, task_dataset_id, _chunks, progress_callback)
return bool(e)
try:
if not await _maybe_insert_es(chunks):
return
DocumentService.increment_chunk_num(task_doc_id, task_dataset_id, token_count, chunk_count, 0)
logging.info(
"Indexing doc({}), page({}-{}), chunks({}), elapsed: {:.2f}".format(
task_document_name, task_from_page, task_to_page, len(chunks), timer() - start_ts
)
)
time_cost = timer() - start_ts
progress_callback(msg="Indexing done ({:.2f}s).".format(time_cost))
if toc_thread:
d = toc_thread.result()
if d:
e = await insert_es(task_id, task_tenant_id, task_dataset_id, [d], progress_callback)
if not e:
return
DocumentService.increment_chunk_num(task_doc_id, task_dataset_id, 0, 1, 0)
DocumentService.increment_chunk_num(task_doc_id, task_dataset_id, token_count, chunk_count, 0)
task_time_cost = timer() - task_start_ts
progress_callback(prog=1.0, msg="Task done ({:.2f}s)".format(task_time_cost))
logging.info(
"Chunk doc({}), page({}-{}), chunks({}), token({}), elapsed:{:.2f}".format(task_document_name, task_from_page,
task_to_page, len(chunks),
token_count, task_time_cost))
progress_callback(msg="Indexing done ({:.2f}s).".format(timer() - start_ts))
if toc_thread:
d = toc_thread.result()
if d:
if not await _maybe_insert_es([d]):
return
DocumentService.increment_chunk_num(task_doc_id, task_dataset_id, 0, 1, 0)
if has_canceled(task_id):
progress_callback(-1, msg="Task has been canceled.")
return
task_time_cost = timer() - task_start_ts
progress_callback(prog=1.0, msg="Task done ({:.2f}s)".format(task_time_cost))
logging.info(
"Chunk doc({}), page({}-{}), chunks({}), token({}), elapsed:{:.2f}".format(
task_document_name, task_from_page, task_to_page, len(chunks), token_count, task_time_cost
)
)
finally:
if has_canceled(task_id):
try:
exists = await asyncio.to_thread(
settings.docStoreConn.indexExist,
search.index_name(task_tenant_id),
task_dataset_id,
)
if exists:
await asyncio.to_thread(
settings.docStoreConn.delete,
{"doc_id": task_doc_id},
search.index_name(task_tenant_id),
task_dataset_id,
)
except Exception:
logging.exception(
f"Remove doc({task_doc_id}) from docStore failed when task({task_id}) canceled."
)
async def handle_task():

View File

@ -0,0 +1,10 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22 12H2M22 12V18C22 18.5304 21.7893 19.0391 21.4142 19.4142C21.0391 19.7893 20.5304 20 20 20H4C3.46957 20 2.96086 19.7893 2.58579 19.4142C2.21071 19.0391 2 18.5304 2 18V12M22 12L18.55 5.11C18.3844 4.77679 18.1292 4.49637 17.813 4.30028C17.4967 4.10419 17.1321 4.0002 16.76 4H7.24C6.86792 4.0002 6.50326 4.10419 6.18704 4.30028C5.87083 4.49637 5.61558 4.77679 5.45 5.11L2 12" stroke="url(#paint0_linear_1415_84974)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 16H6.01M10 16H10.01" stroke="#00BEB4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear_1415_84974" x1="12.5556" y1="4" x2="12.5556" y2="20" gradientUnits="userSpaceOnUse">
<stop stop-color="#161618"/>
<stop offset="1" stop-color="#666666"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 935 B

View File

@ -118,7 +118,7 @@ export const FilterField = memo(
setShowAll(!showAll);
}}
>
<FormLabel className="text-text-primary">
<FormLabel className="text-text-secondary text-sm">
{item.label}
</FormLabel>
{showAll ? (

View File

@ -33,6 +33,7 @@ export type CheckboxFormMultipleProps = {
onChange?: FilterChange;
onOpenChange?: (open: boolean) => void;
setOpen(open: boolean): void;
filterGroup?: Record<string, string[]>;
};
function CheckboxFormMultiple({
@ -40,6 +41,7 @@ function CheckboxFormMultiple({
value,
onChange,
setOpen,
filterGroup,
}: CheckboxFormMultipleProps) {
const [resolvedFilters, setResolvedFilters] =
useState<FilterCollection[]>(filters);
@ -120,6 +122,20 @@ function CheckboxFormMultiple({
}
}, [form, value, resolvedFilters, fieldsDict]);
const filterList = useMemo(() => {
const filterSet = filterGroup
? Object.values(filterGroup).reduce<Set<string>>((pre, cur) => {
cur.forEach((item) => pre.add(item));
return pre;
}, new Set())
: new Set();
return [...filterSet];
}, [filterGroup]);
const notInfilterGroup = useMemo(() => {
return filters.filter((x) => !filterList.includes(x.field));
}, [filterList, filters]);
return (
<Form {...form}>
<form
@ -127,34 +143,70 @@ function CheckboxFormMultiple({
className="space-y-8 px-5 py-2.5"
onReset={() => form.reset()}
>
{filters.map((x) => (
<FormField
key={x.field}
control={form.control}
name={x.field}
render={() => (
<FormItem className="space-y-4">
<div>
<FormLabel className="text-text-primary">{x.label}</FormLabel>
<div className="space-y-4">
{filterGroup &&
Object.keys(filterGroup).map((key) => {
const filterKeys = filterGroup[key];
const thisFilters = filters.filter((x) =>
filterKeys.includes(x.field),
);
return (
<div
key={key}
className="flex flex-col space-y-4 border-b border-border-button pb-4"
>
<div className="text-text-primary text-sm">{key}</div>
<div className="flex flex-col space-y-4">
{thisFilters.map((x) => (
<FilterField
key={x.field}
item={{ ...x, id: x.field }}
parent={{
...x,
id: x.field,
field: ``,
}}
/>
))}
</div>
</div>
{x.list.map((item) => {
return (
<FilterField
key={item.id}
item={{ ...item }}
parent={{
...x,
id: x.field,
// field: `${x.field}${item.field ? '.' + item.field : ''}`,
}}
/>
);
})}
<FormMessage />
</FormItem>
)}
/>
))}
);
})}
{notInfilterGroup &&
notInfilterGroup.map((x) => {
return (
<FormField
key={x.field}
control={form.control}
name={x.field}
render={() => (
<FormItem className="space-y-4">
<div>
<FormLabel className="text-text-primary text-sm">
{x.label}
</FormLabel>
</div>
{x.list.map((item) => {
return (
<FilterField
key={item.id}
item={{ ...item }}
parent={{
...x,
id: x.field,
// field: `${x.field}${item.field ? '.' + item.field : ''}`,
}}
/>
);
})}
<FormMessage />
</FormItem>
)}
/>
);
})}
</div>
<div className="flex justify-end gap-5">
<Button
type="button"
@ -185,6 +237,7 @@ export function FilterPopover({
onChange,
onOpenChange,
filters,
filterGroup,
}: PropsWithChildren & Omit<CheckboxFormMultipleProps, 'setOpen'>) {
const [open, setOpen] = useState(false);
const onOpenChangeFun = useCallback(
@ -203,6 +256,7 @@ export function FilterPopover({
value={value}
filters={filters}
setOpen={setOpen}
filterGroup={filterGroup}
></CheckboxFormMultiple>
</PopoverContent>
</Popover>

View File

@ -58,9 +58,11 @@ export default function ListFilterBar({
filters,
className,
icon,
filterGroup,
}: PropsWithChildren<IProps & Omit<CheckboxFormMultipleProps, 'setOpen'>> & {
className?: string;
icon?: ReactNode;
filterGroup?: Record<string, string[]>;
}) {
const filterCount = useMemo(() => {
return typeof value === 'object' && value !== null
@ -99,6 +101,7 @@ export default function ListFilterBar({
value={value}
onChange={onChange}
filters={filters}
filterGroup={filterGroup}
onOpenChange={onOpenChange}
>
<FilterButton count={filterCount}></FilterButton>

View File

@ -0,0 +1,33 @@
import { useFetchAllMemoryList } from '@/hooks/use-memory-request';
import { useTranslation } from 'react-i18next';
import { RAGFlowFormItem } from './ragflow-form';
import { MultiSelect } from './ui/multi-select';
type MemoriesFormFieldProps = {
label: string;
};
export function MemoriesFormField({ label }: MemoriesFormFieldProps) {
const { t } = useTranslation();
const memoryList = useFetchAllMemoryList();
const options = memoryList.data?.map((memory) => ({
label: memory.name,
value: memory.id,
}));
return (
<RAGFlowFormItem name="memory_ids" label={label}>
{(field) => (
<MultiSelect
options={options || []}
placeholder={t('common.pleaseSelect')}
maxCount={100}
onValueChange={field.onChange}
defaultValue={field.value}
modalPopover
/>
)}
</RAGFlowFormItem>
);
}

View File

@ -1,6 +1,7 @@
import { DocumentParserType } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks';
import { cn } from '@/lib/utils';
import { LLMModelItem } from '@/pages/dataset/dataset-setting/configuration/common-item';
import {
GenerateLogButton,
GenerateType,
@ -136,6 +137,10 @@ const GraphRagItems = ({
return (
<FormContainer className={cn({ 'mb-4': marginBottom }, className)}>
<LLMModelItem
label={t('globalIndexModel')}
name={'parser_config.llm_id'}
/>
<UseGraphRagFormField
data={data}
onDelete={onDelete}

View File

@ -122,9 +122,9 @@ export const useFetchDocumentList = () => {
page: pagination.current,
},
{
suffix: filterValue.type,
run_status: filterValue.run,
metadata: filterValue.metadata,
suffix: filterValue.type as string[],
run_status: filterValue.run as string[],
metadata: filterValue.metadata as Record<string, string[]>,
},
);
if (ret.data.code === 0) {

View File

@ -0,0 +1,30 @@
import { IMemory } from '@/interfaces/database/memory';
import memoryService from '@/services/memory-service';
import { useQuery } from '@tanstack/react-query';
export const enum MemoryApiAction {
FetchMemoryList = 'fetchMemoryList',
}
export const useFetchAllMemoryList = () => {
const { data, isLoading, isError, refetch } = useQuery<IMemory[], Error>({
queryKey: [MemoryApiAction.FetchMemoryList],
queryFn: async () => {
const { data: response } = await memoryService.getMemoryList(
{
params: { page_size: 100000000, page: 1 },
data: {},
},
true,
);
return response.data.memory_list ?? [];
},
});
return {
data,
isLoading,
isError,
refetch,
};
};

View File

@ -0,0 +1,11 @@
export interface IMemory {
avatar: null;
description: null;
id: string;
memory_type: string[];
name: string;
owner_name: string;
permissions: string;
storage_type: string;
tenant_id: string;
}

View File

@ -33,4 +33,5 @@ export interface IFetchKnowledgeListRequestParams {
export interface IFetchDocumentListRequestBody {
suffix?: string[];
run_status?: string[];
metadata?: Record<string, string[]>;
}

View File

@ -176,6 +176,7 @@ Procedural Memory: Learned skills, habits, and automated procedures.`,
},
knowledgeDetails: {
metadata: {
fieldSetting: 'Field settings',
changesAffectNewParses: 'Changes affect new parses only.',
editMetadataForDataset: 'View and edit metadata for ',
restrictDefinedValues: 'Restrict to defined values',
@ -190,6 +191,8 @@ Procedural Memory: Learned skills, habits, and automated procedures.`,
fieldName: 'Field name',
editMetadata: 'Edit metadata',
},
metadataField: 'Metadata field',
systemAttribute: 'System attribute',
localUpload: 'Local upload',
fileSize: 'File size',
fileType: 'File type',
@ -363,7 +366,11 @@ Procedural Memory: Learned skills, habits, and automated procedures.`,
reRankModelWaring: 'Re-rank model is very time consuming.',
},
knowledgeConfiguration: {
globalIndexModelTip:
'Used to generate Knowledge graphs, RAPTOR, auto-metadata, auto-keyword and auto-question. Model performance will affects generation quality.',
globalIndexModel: 'Indexing model',
settings: 'Settings',
autoMetadataTip: `Automatically generate metadata. Applies to new files during parsing. Existing files require re-parsing to update (chunks remain preserved). Be aware that extra tokens will be consumed by the indexing model specified in 'Configuration'.`,
imageContextWindow: 'Image context window',
imageContextWindowTip:
'Captures N tokens of text above and below the image to provide richer background context for the image chunk.',
@ -2120,9 +2127,11 @@ Important structured information may include: names, dates, locations, events, k
queryParameters: 'Query parameters',
headerParameters: 'Header parameters',
requestBodyParameters: 'Request body parameters',
streaming: 'Accepted response',
immediately: 'Final response',
immediately: 'Accepted response',
streaming: 'Final response',
},
saveToMemory: 'Save to memory',
memory: 'Memory',
},
llmTools: {
bad_calculator: {

View File

@ -93,6 +93,67 @@ export default {
search: '搜索',
welcome: '欢迎来到',
dataset: '知识库',
Memories: '记忆',
},
memories: {
llmTooltip: '分析对话内容,提取关键信息,并生成结构化的记忆摘要。',
embeddingModelTooltip:
'将文本转换为数值向量,用于语义相似度搜索和记忆检索。',
embeddingModelError: '记忆类型为必填项,且"原始"类型不可删除。',
memoryTypeTooltip: `原始: 用户与代理之间的原始对话内容(默认必需)。
语义记忆: 关于用户和世界的通用知识和事实。
情景记忆: 带时间戳的特定事件和经历记录。
程序记忆: 学习的技能、习惯和自动化程序。`,
editName: '编辑名称',
memory: '记忆',
createMemory: '创建记忆',
name: '名称',
memoryNamePlaceholder: '记忆名称',
memoryType: '记忆类型',
embeddingModel: '嵌入模型',
selectModel: '选择模型',
llm: '大语言模型',
delMemoryWarn: `删除后,此记忆中的所有消息都将被删除,代理将无法检索。`,
},
memory: {
messages: {
messageDescription:
'记忆检索已在高级设置中配置相似度阈值、关键词相似度权重和前N个结果。',
copied: '已复制!',
contentEmbed: '内容嵌入',
content: '内容',
delMessageWarn: `遗忘后,代理将无法检索此消息。`,
forgetMessage: '遗忘消息',
sessionId: '会话ID',
agent: '代理',
type: '类型',
validDate: '有效日期',
forgetAt: '遗忘于',
source: '来源',
enable: '启用',
action: '操作',
},
config: {
avatar: '头像',
description: '描述',
memorySize: '记忆大小',
advancedSettings: '高级设置',
permission: '权限',
onlyMe: '仅自己',
team: '团队',
storageType: '存储类型',
storageTypePlaceholder: '请选择存储类型',
forgetPolicy: '遗忘策略',
temperature: '温度',
systemPrompt: '系统提示词',
systemPromptPlaceholder: '请输入系统提示词',
userPrompt: '用户提示词',
userPromptPlaceholder: '请输入用户提示词',
},
sideBar: {
messages: '消息',
configuration: '配置',
},
},
knowledgeList: {
welcome: '欢迎回来',
@ -106,6 +167,22 @@ export default {
parserRequired: '分块方法必填',
},
knowledgeDetails: {
metadata: {
fieldSetting: '字段设置',
changesAffectNewParses: '更改仅影响新解析。',
editMetadataForDataset: '查看和编辑元数据于 ',
restrictDefinedValues: '限制为已定义值',
metadataGenerationSettings: '元数据生成设置',
manageMetadataForDataset: '管理此数据集的元数据',
manageMetadata: '管理元数据',
metadata: '元数据',
values: '值',
action: '操作',
field: '字段',
description: '描述',
fieldName: '字段名',
editMetadata: '编辑元数据',
},
localUpload: '本地上传',
fileSize: '文件大小',
fileType: '文件类型',
@ -1863,6 +1940,8 @@ Tokenizer 会根据所选方式将内容存储为对应的数据结构。`,
headerParameters: '请求头参数',
requestBodyParameters: '请求体参数',
},
saveToMemory: '保存到Memory',
memory: 'Memory',
},
footer: {
profile: 'All rights reserved @ React',

View File

@ -7,7 +7,6 @@ import {
import {
IAttachment,
IEventList,
IInputEvent,
IMessageEndData,
IMessageEndEvent,
IMessageEvent,
@ -93,7 +92,7 @@ export function findMessageFromList(eventList: IEventList) {
export function findInputFromList(eventList: IEventList) {
const inputEvent = eventList.find(
(x) => x.event === MessageEventType.UserInputs,
) as IInputEvent;
);
if (!inputEvent) {
return {};

View File

@ -1,4 +1,4 @@
import { FormContainer } from '@/components/form-container';
import { MemoriesFormField } from '@/components/memories-form-field';
import { BlockButton, Button } from '@/components/ui/button';
import {
Form,
@ -41,6 +41,7 @@ function MessageForm({ node }: INextOperatorForm) {
output_format: z.string().optional(),
auto_play: z.boolean().optional(),
status: z.number().optional(),
memory_ids: z.array(z.string()).optional(),
});
const form = useForm({
@ -67,99 +68,100 @@ function MessageForm({ node }: INextOperatorForm) {
{showWebhookResponseStatus && (
<WebHookResponseStatusFormField name="status"></WebHookResponseStatusFormField>
)}
<FormContainer>
<FormItem>
<FormLabel tooltip={t('flow.msgTip')}>{t('flow.msg')}</FormLabel>
<div className="space-y-4">
{fields.map((field, index) => (
<div key={field.id} className="flex items-start gap-2">
<FormField
control={form.control}
name={`content.${index}.value`}
render={({ field }) => (
<FormItem className="flex-1">
<FormControl>
<PromptEditor
{...field}
placeholder={t('flow.messagePlaceholder')}
></PromptEditor>
</FormControl>
</FormItem>
)}
/>
{fields.length > 1 && (
<Button
type="button"
variant={'ghost'}
onClick={() => remove(index)}
>
<X />
</Button>
<FormItem>
<FormLabel tooltip={t('flow.msgTip')}>{t('flow.msg')}</FormLabel>
<div className="space-y-4">
{fields.map((field, index) => (
<div key={field.id} className="flex items-start gap-2">
<FormField
control={form.control}
name={`content.${index}.value`}
render={({ field }) => (
<FormItem className="flex-1">
<FormControl>
<PromptEditor
{...field}
placeholder={t('flow.messagePlaceholder')}
></PromptEditor>
</FormControl>
</FormItem>
)}
</div>
))}
/>
{fields.length > 1 && (
<Button
type="button"
variant={'ghost'}
onClick={() => remove(index)}
>
<X />
</Button>
)}
</div>
))}
<BlockButton
type="button"
onClick={() => append({ value: '' })} // "" will cause the inability to add, refer to: https://github.com/orgs/react-hook-form/discussions/8485#discussioncomment-2961861
>
{t('flow.addMessage')}
</BlockButton>
</div>
<FormMessage />
</FormItem>
</FormContainer>
<FormContainer>
<FormItem>
<FormLabel tooltip={t('flow.downloadFileTypeTip')}>
{t('flow.downloadFileType')}
</FormLabel>
<FormField
control={form.control}
name={`output_format`}
render={({ field }) => (
<FormItem className="flex-1">
<FormControl>
<RAGFlowSelect
options={Object.keys(ExportFileType).map(
(key: string) => {
return {
value:
ExportFileType[
key as keyof typeof ExportFileType
],
label: key,
};
},
)}
{...field}
onValueChange={field.onChange}
placeholder={t('common.selectPlaceholder')}
allowClear
></RAGFlowSelect>
</FormControl>
</FormItem>
)}
/>
</FormItem>
<FormItem>
<FormLabel>{t('flow.autoPlay')}</FormLabel>
<FormField
control={form.control}
name={`auto_play`}
render={({ field }) => (
<FormItem className="flex-1">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</FormItem>
</FormContainer>
<BlockButton
type="button"
onClick={() => append({ value: '' })} // "" will cause the inability to add, refer to: https://github.com/orgs/react-hook-form/discussions/8485#discussioncomment-2961861
>
{t('flow.addMessage')}
</BlockButton>
</div>
<FormMessage />
</FormItem>
{!showWebhookResponseStatus && (
<>
<FormItem>
<FormLabel tooltip={t('flow.downloadFileTypeTip')}>
{t('flow.downloadFileType')}
</FormLabel>
<FormField
control={form.control}
name={`output_format`}
render={({ field }) => (
<FormItem className="flex-1">
<FormControl>
<RAGFlowSelect
options={Object.keys(ExportFileType).map(
(key: string) => {
return {
value:
ExportFileType[
key as keyof typeof ExportFileType
],
label: key,
};
},
)}
{...field}
onValueChange={field.onChange}
placeholder={t('common.selectPlaceholder')}
allowClear
></RAGFlowSelect>
</FormControl>
</FormItem>
)}
/>
</FormItem>
<FormItem>
<FormLabel>{t('flow.autoPlay')}</FormLabel>
<FormField
control={form.control}
name={`auto_play`}
render={({ field }) => (
<FormItem className="flex-1">
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
</FormItem>
</>
)}
<MemoriesFormField label={t('flow.saveToMemory')}></MemoriesFormField>
</FormWrapper>
</Form>
);

View File

@ -15,16 +15,16 @@ export function useShowWebhookResponseStatus(form: UseFormReturn<any>) {
const showWebhookResponseStatus = useMemo(() => {
const formData: BeginFormSchemaType = getNode(BeginId)?.data.form;
return (
formData.mode === AgentDialogueMode.Webhook &&
formData?.mode === AgentDialogueMode.Webhook &&
formData.execution_mode === WebhookExecutionMode.Streaming
);
}, []);
}, [getNode]);
useEffect(() => {
if (showWebhookResponseStatus && isEmpty(form.getValues('status'))) {
form.setValue('status', 200, { shouldValidate: true, shouldDirty: true });
}
}, []);
}, [form, showWebhookResponseStatus]);
return showWebhookResponseStatus;
}

View File

@ -2,6 +2,7 @@ import { Collapse } from '@/components/collapse';
import { CrossLanguageFormField } from '@/components/cross-language-form-field';
import { FormContainer } from '@/components/form-container';
import { KnowledgeBaseFormField } from '@/components/knowledge-base-item';
import { MemoriesFormField } from '@/components/memories-form-field';
import {
MetadataFilter,
MetadataFilterSchema,
@ -46,6 +47,7 @@ export const RetrievalPartialSchema = {
use_kg: z.boolean(),
toc_enhance: z.boolean(),
...MetadataFilterSchema,
memory_ids: z.array(z.string()).optional(),
};
export const FormSchema = z.object({
@ -109,12 +111,11 @@ function RetrievalForm({ node }: INextOperatorForm) {
return (
<Form {...form}>
<FormWrapper>
<FormContainer>
<RAGFlowFormItem name="query" label={t('flow.query')}>
<PromptEditor></PromptEditor>
</RAGFlowFormItem>
<KnowledgeBaseFormField showVariable></KnowledgeBaseFormField>
</FormContainer>
<RAGFlowFormItem name="query" label={t('flow.query')}>
<PromptEditor></PromptEditor>
</RAGFlowFormItem>
<KnowledgeBaseFormField showVariable></KnowledgeBaseFormField>
<MemoriesFormField label={t('flow.memory')}></MemoriesFormField>
<Collapse title={<div>{t('flow.advancedSettings')}</div>}>
<FormContainer>
<SimilaritySliderFormField

View File

@ -2,6 +2,7 @@ import { Collapse } from '@/components/collapse';
import { CrossLanguageFormField } from '@/components/cross-language-form-field';
import { FormContainer } from '@/components/form-container';
import { KnowledgeBaseFormField } from '@/components/knowledge-base-item';
import { MemoriesFormField } from '@/components/memories-form-field';
import { MetadataFilter } from '@/components/metadata-filter';
import { RerankFormFields } from '@/components/rerank';
import { SimilaritySliderFormField } from '@/components/similarity-slider';
@ -40,10 +41,9 @@ const RetrievalForm = () => {
return (
<Form {...form}>
<FormWrapper>
<FormContainer>
<DescriptionField></DescriptionField>
<KnowledgeBaseFormField showVariable></KnowledgeBaseFormField>
</FormContainer>
<DescriptionField></DescriptionField>
<KnowledgeBaseFormField showVariable></KnowledgeBaseFormField>
<MemoriesFormField label={t('flow.memory')}></MemoriesFormField>
<Collapse title={<div>{t('flow.advancedSettings')}</div>}>
<FormContainer>
<SimilaritySliderFormField

View File

@ -1,7 +1,7 @@
import message from '@/components/ui/message';
import { useSetModalState } from '@/hooks/common-hooks';
import { useSetDocumentMeta } from '@/hooks/use-document-request';
import {
import kbService, {
getMetaDataService,
updateMetaData,
} from '@/services/knowledge-service';
@ -198,20 +198,24 @@ export const useManageMetaDataModal = (
const { setDocumentMeta } = useSetDocumentMeta();
useEffect(() => {
if (data) {
setTableData(data);
} else {
setTableData([]);
if (type === MetadataType.Manage) {
if (data) {
setTableData(data);
} else {
setTableData([]);
}
}
}, [data]);
}, [data, type]);
useEffect(() => {
if (metaData) {
setTableData(metaData);
} else {
setTableData([]);
if (type !== MetadataType.Manage) {
if (metaData) {
setTableData(metaData);
} else {
setTableData([]);
}
}
}, [metaData]);
}, [metaData, type]);
const handleDeleteSingleValue = useCallback(
(field: string, value: string) => {
@ -255,7 +259,7 @@ export const useManageMetaDataModal = (
data: operations,
});
if (res.code === 0) {
message.success(t('message.success'));
message.success(t('message.operated'));
callback();
}
},
@ -282,11 +286,18 @@ export const useManageMetaDataModal = (
const handleSaveSettings = useCallback(
async (callback: () => void) => {
const data = util.tableDataToMetaDataSettingJSON(tableData);
callback();
const { data: res } = await kbService.kbUpdateMetaData({
kb_id: id,
metadata: data,
});
if (res.code === 0) {
message.success(t('message.operated'));
callback?.();
}
return data;
},
[tableData],
[tableData, id],
);
const handleSave = useCallback(

View File

@ -27,7 +27,7 @@ import {
import { Plus, Settings, Trash2 } from 'lucide-react';
import { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useManageMetaDataModal } from './hook';
import { MetadataType, useManageMetaDataModal } from './hook';
import { IManageModalProps, IMetaDataTableData } from './interface';
import { ManageValuesModal } from './manage-values-modal';
export const ManageMetadataModal = (props: IManageModalProps) => {
@ -54,7 +54,6 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
values: [],
});
const [currentValueIndex, setCurrentValueIndex] = useState<number>(0);
const [deleteDialogContent, setDeleteDialogContent] = useState({
visible: false,
title: '',
@ -95,12 +94,12 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
description: '',
values: [],
});
setCurrentValueIndex(tableData.length || 0);
// setCurrentValueIndex(tableData.length || 0);
showManageValuesModal();
};
const handleEditValueRow = useCallback(
(data: IMetaDataTableData, index: number) => {
setCurrentValueIndex(index);
(data: IMetaDataTableData) => {
// setCurrentValueIndex(index);
setValueData(data);
showManageValuesModal();
},
@ -186,7 +185,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
variant={'ghost'}
className="bg-transparent px-1 py-0"
onClick={() => {
handleEditValueRow(row.original, row.index);
handleEditValueRow(row.original);
}}
>
<Settings />
@ -244,16 +243,32 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
const handleSaveValues = (data: IMetaDataTableData) => {
setTableData((prev) => {
if (currentValueIndex >= prev.length) {
return [...prev, data];
//If the keys are the same, they need to be merged.
const fieldMap = new Map<string, any>();
prev.forEach((item) => {
if (fieldMap.has(item.field)) {
const existingItem = fieldMap.get(item.field);
const mergedValues = [
...new Set([...existingItem.values, ...item.values]),
];
fieldMap.set(item.field, { ...existingItem, values: mergedValues });
} else {
fieldMap.set(item.field, item);
}
});
if (fieldMap.has(data.field)) {
const existingItem = fieldMap.get(data.field);
const mergedValues = [
...new Set([...existingItem.values, ...data.values]),
];
fieldMap.set(data.field, { ...existingItem, values: mergedValues });
} else {
return prev.map((item, index) => {
if (index === currentValueIndex) {
return data;
}
return item;
});
fieldMap.set(data.field, data);
}
return Array.from(fieldMap.values());
});
};
@ -335,7 +350,13 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
</Modal>
{manageValuesVisible && (
<ManageValuesModal
title={<div>{t('knowledgeDetails.metadata.editMetadata')}</div>}
title={
<div>
{metadataType === MetadataType.Setting
? t('knowledgeDetails.metadata.fieldSetting')
: t('knowledgeDetails.metadata.editMetadata')}
</div>
}
visible={manageValuesVisible}
hideModal={hideManageValuesModal}
data={valueData}

View File

@ -110,8 +110,8 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
// Handle blur event, synchronize to main state
const handleValueBlur = useCallback(() => {
addUpdateValue(metaData.field, [...tempValues]);
handleChange('values', [...tempValues]);
addUpdateValue(metaData.field, [...new Set([...tempValues])]);
handleChange('values', [...new Set([...tempValues])]);
}, [handleChange, tempValues, metaData, addUpdateValue]);
// Handle delete operation
@ -139,12 +139,12 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
// Handle adding new value
const handleAddValue = useCallback(() => {
setTempValues((prev) => [...prev, '']);
setTempValues((prev) => [...new Set([...prev, ''])]);
// Synchronize to main state
setMetaData((prev) => ({
...prev,
values: [...prev.values, ''],
values: [...new Set([...prev.values, ''])],
}));
}, []);
@ -154,7 +154,7 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
open={visible}
onCancel={handleHideModal}
className="!w-[460px]"
okText={t('common.save')}
okText={t('common.confirm')}
onOk={handleSave}
maskClosable={false}
footer={null}

View File

@ -3,11 +3,13 @@ import {
AutoQuestionsFormField,
} from '@/components/auto-keywords-form-field';
import { ConfigurationFormContainer } from '../configuration-form-container';
import { AutoMetadata } from './common-item';
export function AudioConfiguration() {
return (
<ConfigurationFormContainer>
<>
<AutoMetadata />
<AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField>
</>

View File

@ -7,6 +7,7 @@ import {
ConfigurationFormContainer,
MainContainer,
} from '../configuration-form-container';
import { AutoMetadata } from './common-item';
export function BookConfiguration() {
return (
@ -16,6 +17,7 @@ export function BookConfiguration() {
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<AutoMetadata />
<AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField>
</ConfigurationFormContainer>

View File

@ -3,7 +3,10 @@ import {
FormFieldType,
RenderField,
} from '@/components/dynamic-form';
import { SelectWithSearch } from '@/components/originui/select-with-search';
import {
SelectWithSearch,
SelectWithSearchFlagOptionType,
} from '@/components/originui/select-with-search';
import { SliderInputFormField } from '@/components/slider-input-form-field';
import { Button } from '@/components/ui/button';
import {
@ -16,7 +19,9 @@ import {
import { Radio } from '@/components/ui/radio';
import { Spin } from '@/components/ui/spin';
import { Switch } from '@/components/ui/switch';
import { LlmModelType } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks';
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
import { cn } from '@/lib/utils';
import { t } from 'i18next';
import { Settings } from 'lucide-react';
@ -41,6 +46,8 @@ import {
interface IProps {
line?: 1 | 2;
isEdit?: boolean;
label?: string;
name?: string;
}
export function ChunkMethodItem(props: IProps) {
const { line } = props;
@ -368,7 +375,7 @@ export function AutoMetadata() {
type: FormFieldType.Custom,
horizontal: true,
defaultValue: true,
tooltip: t('knowledgeConfiguration.autoMetadataTip'),
render: (fieldProps: ControllerRenderProps) => (
<div className="flex items-center justify-between">
<Button
@ -432,3 +439,80 @@ export function AutoMetadata() {
</>
);
}
export const LLMSelect = ({
isEdit,
field,
disabled = false,
}: {
isEdit: boolean;
field: FieldValues;
name?: string;
disabled?: boolean;
}) => {
const { t } = useTranslate('knowledgeConfiguration');
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Chat,
LlmModelType.Image2text,
]);
return (
<SelectWithSearch
onChange={async (value) => {
field.onChange(value);
}}
disabled={disabled && !isEdit}
value={field.value}
options={modelOptions as SelectWithSearchFlagOptionType[]}
placeholder={t('embeddingModelPlaceholder')}
/>
);
};
export function LLMModelItem({ line = 1, isEdit, label, name }: IProps) {
const { t } = useTranslate('knowledgeConfiguration');
const form = useFormContext();
const disabled = useHasParsedDocument(isEdit);
return (
<>
<FormField
control={form.control}
name={name ?? 'llm_id'}
render={({ field }) => (
<FormItem className={cn(' items-center space-y-0 ')}>
<div
className={cn('flex', {
' items-center': line === 1,
'flex-col gap-1': line === 2,
})}
>
<FormLabel
required
tooltip={t('globalIndexModelTip')}
className={cn('text-sm whitespace-wrap ', {
'w-1/4': line === 1,
})}
>
{label ?? t('llmModel')}
</FormLabel>
<div
className={cn('text-text-secondary', { 'w-3/4': line === 1 })}
>
<FormControl>
<LLMSelect
isEdit={!!isEdit}
field={field}
disabled={disabled}
></LLMSelect>
</FormControl>
</div>
</div>
<div className="flex pt-1">
<div className={line === 1 ? 'w-1/4' : ''}></div>
<FormMessage />
</div>
</FormItem>
)}
/>
</>
);
}

View File

@ -3,11 +3,13 @@ import {
AutoQuestionsFormField,
} from '@/components/auto-keywords-form-field';
import { ConfigurationFormContainer } from '../configuration-form-container';
import { AutoMetadata } from './common-item';
export function EmailConfiguration() {
return (
<ConfigurationFormContainer>
<>
<AutoMetadata />
<AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField>
</>

View File

@ -7,6 +7,7 @@ import {
ConfigurationFormContainer,
MainContainer,
} from '../configuration-form-container';
import { AutoMetadata } from './common-item';
export function LawsConfiguration() {
return (
@ -16,6 +17,7 @@ export function LawsConfiguration() {
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<AutoMetadata />
<AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField>
</ConfigurationFormContainer>

View File

@ -7,6 +7,7 @@ import {
ConfigurationFormContainer,
MainContainer,
} from '../configuration-form-container';
import { AutoMetadata } from './common-item';
export function ManualConfiguration() {
return (
@ -16,6 +17,7 @@ export function ManualConfiguration() {
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<AutoMetadata />
<AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField>
</ConfigurationFormContainer>

View File

@ -4,12 +4,14 @@ import {
} from '@/components/auto-keywords-form-field';
import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field';
import { ConfigurationFormContainer } from '../configuration-form-container';
import { AutoMetadata } from './common-item';
export function OneConfiguration() {
return (
<ConfigurationFormContainer>
<LayoutRecognizeFormField></LayoutRecognizeFormField>
<>
<AutoMetadata />
<AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField>
</>

View File

@ -7,6 +7,7 @@ import {
ConfigurationFormContainer,
MainContainer,
} from '../configuration-form-container';
import { AutoMetadata } from './common-item';
export function PaperConfiguration() {
return (
@ -16,6 +17,7 @@ export function PaperConfiguration() {
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<AutoMetadata />
<AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField>
</ConfigurationFormContainer>

View File

@ -3,11 +3,13 @@ import {
AutoQuestionsFormField,
} from '@/components/auto-keywords-form-field';
import { ConfigurationFormContainer } from '../configuration-form-container';
import { AutoMetadata } from './common-item';
export function PictureConfiguration() {
return (
<ConfigurationFormContainer>
<>
<AutoMetadata />
<AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField>
</>

View File

@ -7,6 +7,7 @@ import {
ConfigurationFormContainer,
MainContainer,
} from '../configuration-form-container';
import { AutoMetadata } from './common-item';
export function PresentationConfiguration() {
return (
@ -16,6 +17,7 @@ export function PresentationConfiguration() {
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<AutoMetadata />
<AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField>
</ConfigurationFormContainer>

View File

@ -96,6 +96,7 @@ export const formSchema = z
)
.optional(),
enable_metadata: z.boolean().optional(),
llm_id: z.string().optional(),
})
.optional(),
pagerank: z.number(),

View File

@ -93,6 +93,7 @@ export default function DatasetSettings() {
},
metadata: [],
enable_metadata: false,
llm_id: '',
},
pipeline_id: '',
parseType: 1,

View File

@ -16,7 +16,7 @@ import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-reques
import { Pen, Upload } from 'lucide-react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useManageMetadata } from '../components/metedata/hook';
import { MetadataType, useManageMetadata } from '../components/metedata/hook';
import { ManageMetadataModal } from '../components/metedata/manage-modal';
import { DatasetTable } from './dataset-table';
import Generate from './generate-button/generate';
@ -53,7 +53,7 @@ export default function Dataset() {
const { data: dataSetData } = useFetchKnowledgeBaseConfiguration({
refreshCount,
});
const { filters, onOpenChange } = useSelectDatasetFilters();
const { filters, onOpenChange, filterGroup } = useSelectDatasetFilters();
const {
createLoading,
@ -90,6 +90,7 @@ export default function Dataset() {
onSearchChange={handleInputChange}
searchString={searchString}
value={filterValue}
filterGroup={filterGroup}
onChange={handleFilterSubmit}
onOpenChange={onOpenChange}
filters={filters}
@ -105,7 +106,25 @@ export default function Dataset() {
<Button
variant={'ghost'}
className="border border-border-button"
onClick={() => showManageMetadataModal()}
onClick={() =>
showManageMetadataModal({
type: MetadataType.Manage,
isCanAdd: false,
isEditField: true,
title: (
<div className="flex flex-col gap-2">
<div className="text-base font-normal">
{t('knowledgeDetails.metadata.manageMetadata')}
</div>
<div className="text-sm text-text-secondary">
{t(
'knowledgeDetails.metadata.manageMetadataForDataset',
)}
</div>
</div>
),
})
}
>
<div className="flex gap-1 items-center">
<Pen size={14} />
@ -179,6 +198,7 @@ export default function Dataset() {
// selectedRowKeys={selectedRowKeys}
tableData={tableData}
isCanAdd={metadataConfig.isCanAdd}
isEditField={metadataConfig.isEditField}
isDeleteSingleValue={metadataConfig.isDeleteSingleValue}
type={metadataConfig.type}
otherData={metadataConfig.record}

View File

@ -49,9 +49,13 @@ export function useSelectDatasetFilters() {
return [
{ field: 'type', label: 'File Type', list: fileTypes },
{ field: 'run', label: 'Status', list: fileStatus },
{ field: 'metadata', label: 'metadata', list: metaDataList },
{ field: 'metadata', label: 'Metadata field', list: metaDataList },
] as FilterCollection[];
}, [fileStatus, fileTypes, metaDataList]);
return { filters, onOpenChange };
const filterGroup = {
[t('systemAttribute')]: ['type', 'run'],
// [t('metadataField')]: ['metadata'],
};
return { filters, onOpenChange, filterGroup };
}

View File

@ -1,5 +1,4 @@
import { DynamicForm } from '@/components/dynamic-form';
import { useModelOptions } from '@/components/llm-setting-items/llm-form-field';
import { HomeIcon } from '@/components/svg-icon';
import { Modal } from '@/components/ui/modal/modal';
import { memo, useMemo } from 'react';
@ -18,7 +17,7 @@ type IProps = {
export const AddOrEditModal = memo((props: IProps) => {
const { open, onClose, onSubmit, initialMemory, isCreate } = props;
const { t } = useTranslation();
const { modelOptions } = useModelOptions();
// const { modelOptions } = useModelOptions();
const fields = useMemo(() => {
if (!isCreate) {
@ -26,21 +25,22 @@ export const AddOrEditModal = memo((props: IProps) => {
(field: any) => field.name === 'name',
);
} else {
const tempFields = createMemoryFields(t).map((field: any) => {
if (field.name === 'llm_id') {
return {
...field,
options: modelOptions,
};
} else {
return {
...field,
};
}
});
return tempFields;
// const tempFields = createMemoryFields(t).map((field: any) => {
// if (field.name === 'llm_id') {
// return {
// ...field,
// options: modelOptions,
// };
// } else {
// return {
// ...field,
// };
// }
// });
// return tempFields;
return createMemoryFields(t);
}
}, [modelOptions, isCreate]);
}, [isCreate, t]);
return (
<Modal

View File

@ -1,5 +1,8 @@
import { FormFieldConfig, FormFieldType } from '@/components/dynamic-form';
import { EmbeddingSelect } from '@/pages/dataset/dataset-setting/configuration/common-item';
import {
EmbeddingSelect,
LLMSelect,
} from '@/pages/dataset/dataset-setting/configuration/common-item';
import { TFunction } from 'i18next';
export enum MemoryType {
Raw = 'raw',
@ -52,6 +55,7 @@ export const createMemoryFields = (t: TFunction) =>
required: true,
type: FormFieldType.Select,
tooltip: t('memories.llmTooltip'),
render: (field) => <LLMSelect field={field} isEdit={false} />,
},
] as FormFieldConfig[];

View File

@ -47,6 +47,7 @@ const {
runRaptor,
traceRaptor,
check_embedding,
kbUpdateMetaData,
} = api;
const methods = {
@ -215,6 +216,10 @@ const methods = {
url: check_embedding,
method: 'post',
},
kbUpdateMetaData: {
url: kbUpdateMetaData,
method: 'post',
},
// getMetaData: {
// url: getMetaData,
// method: 'get',

View File

@ -1,6 +1,6 @@
import api from '@/utils/api';
import request from '@/utils/next-request';
import { registerNextServer } from '@/utils/register-server';
import request from '@/utils/request';
const {
createMemory,

View File

@ -79,6 +79,7 @@ export default {
pipelineRerun: `${api_host}/canvas/rerun`,
getMetaData: `${api_host}/document/metadata/summary`,
updateMetaData: `${api_host}/document/metadata/update`,
kbUpdateMetaData: `${api_host}/kb/update_metadata_setting`,
// tags
listTag: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/tags`,