mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-06 10:35:06 +08:00
Compare commits
11 Commits
17b8bb62b6
...
7fef285af5
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fef285af5 | |||
| b1efb905e5 | |||
| 6400bf87ba | |||
| f239bc02d3 | |||
| 5776fa73a7 | |||
| fc6af1998b | |||
| 0588fe79b9 | |||
| f545265f93 | |||
| c987d33649 | |||
| d72debf0db | |||
| c33134ea2c |
@ -86,8 +86,9 @@ class Agent(LLM, ToolBase):
|
|||||||
self.tools = {}
|
self.tools = {}
|
||||||
for idx, cpn in enumerate(self._param.tools):
|
for idx, cpn in enumerate(self._param.tools):
|
||||||
cpn = self._load_tool_obj(cpn)
|
cpn = self._load_tool_obj(cpn)
|
||||||
name = cpn.get_meta()["function"]["name"]
|
original_name = cpn.get_meta()["function"]["name"]
|
||||||
self.tools[f"{name}_{idx}"] = cpn
|
indexed_name = f"{original_name}_{idx}"
|
||||||
|
self.tools[indexed_name] = cpn
|
||||||
|
|
||||||
self.chat_mdl = LLMBundle(self._canvas.get_tenant_id(), TenantLLMService.llm_id2llm_type(self._param.llm_id), self._param.llm_id,
|
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,
|
max_retries=self._param.max_retries,
|
||||||
@ -95,7 +96,12 @@ class Agent(LLM, ToolBase):
|
|||||||
max_rounds=self._param.max_rounds,
|
max_rounds=self._param.max_rounds,
|
||||||
verbose_tool_use=True
|
verbose_tool_use=True
|
||||||
)
|
)
|
||||||
self.tool_meta = [v.get_meta() for _,v in self.tools.items()]
|
self.tool_meta = []
|
||||||
|
for indexed_name, tool_obj in self.tools.items():
|
||||||
|
original_meta = tool_obj.get_meta()
|
||||||
|
indexed_meta = deepcopy(original_meta)
|
||||||
|
indexed_meta["function"]["name"] = indexed_name
|
||||||
|
self.tool_meta.append(indexed_meta)
|
||||||
|
|
||||||
for mcp in self._param.mcp:
|
for mcp in self._param.mcp:
|
||||||
_, mcp_server = MCPServerService.get_by_id(mcp["mcp_id"])
|
_, mcp_server = MCPServerService.get_by_id(mcp["mcp_id"])
|
||||||
@ -109,7 +115,8 @@ class Agent(LLM, ToolBase):
|
|||||||
|
|
||||||
def _load_tool_obj(self, cpn: dict) -> object:
|
def _load_tool_obj(self, cpn: dict) -> object:
|
||||||
from agent.component import component_class
|
from agent.component import component_class
|
||||||
param = component_class(cpn["component_name"] + "Param")()
|
tool_name = cpn["component_name"]
|
||||||
|
param = component_class(tool_name + "Param")()
|
||||||
param.update(cpn["params"])
|
param.update(cpn["params"])
|
||||||
try:
|
try:
|
||||||
param.check()
|
param.check()
|
||||||
@ -277,19 +284,15 @@ class Agent(LLM, ToolBase):
|
|||||||
else:
|
else:
|
||||||
user_request = history[-1]["content"]
|
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:
|
def build_task_desc(prompt: str, user_request: str, user_defined_prompt: dict | None = None) -> str:
|
||||||
"""Build a minimal task_desc by concatenating prompt, query, and tool schemas."""
|
"""Build a minimal task_desc by concatenating prompt, query, and tool schemas."""
|
||||||
user_defined_prompt = user_defined_prompt or {}
|
user_defined_prompt = user_defined_prompt or {}
|
||||||
|
|
||||||
tools_json = json.dumps(tool_metas, ensure_ascii=False, indent=2)
|
|
||||||
|
|
||||||
task_desc = (
|
task_desc = (
|
||||||
"### Agent Prompt\n"
|
"### Agent Prompt\n"
|
||||||
f"{prompt}\n\n"
|
f"{prompt}\n\n"
|
||||||
"### User Request\n"
|
"### User Request\n"
|
||||||
f"{user_request}\n\n"
|
f"{user_request}\n\n"
|
||||||
"### Tools (schemas)\n"
|
|
||||||
f"{tools_json}\n"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if user_defined_prompt:
|
if user_defined_prompt:
|
||||||
@ -368,7 +371,7 @@ class Agent(LLM, ToolBase):
|
|||||||
hist.append({"role": "user", "content": content})
|
hist.append({"role": "user", "content": content})
|
||||||
|
|
||||||
st = timer()
|
st = timer()
|
||||||
task_desc = build_task_desc(prompt, user_request, tool_metas, user_defined_prompt)
|
task_desc = build_task_desc(prompt, user_request, user_defined_prompt)
|
||||||
self.callback("analyze_task", {}, task_desc, elapsed_time=timer()-st)
|
self.callback("analyze_task", {}, task_desc, elapsed_time=timer()-st)
|
||||||
for _ in range(self._param.max_rounds + 1):
|
for _ in range(self._param.max_rounds + 1):
|
||||||
if self.check_if_canceled("Agent streaming"):
|
if self.check_if_canceled("Agent streaming"):
|
||||||
|
|||||||
@ -56,7 +56,6 @@ class LLMParam(ComponentParamBase):
|
|||||||
self.check_nonnegative_number(int(self.max_tokens), "[Agent] Max tokens")
|
self.check_nonnegative_number(int(self.max_tokens), "[Agent] Max tokens")
|
||||||
self.check_decimal_float(float(self.top_p), "[Agent] Top P")
|
self.check_decimal_float(float(self.top_p), "[Agent] Top P")
|
||||||
self.check_empty(self.llm_id, "[Agent] LLM")
|
self.check_empty(self.llm_id, "[Agent] LLM")
|
||||||
self.check_empty(self.sys_prompt, "[Agent] System prompt")
|
|
||||||
self.check_empty(self.prompts, "[Agent] User prompt")
|
self.check_empty(self.prompts, "[Agent] User prompt")
|
||||||
|
|
||||||
def gen_conf(self):
|
def gen_conf(self):
|
||||||
|
|||||||
@ -33,6 +33,7 @@ from api.db.db_models import DB, Document, Knowledgebase, Task, Tenant, UserTena
|
|||||||
from api.db.db_utils import bulk_insert_into_db
|
from api.db.db_utils import bulk_insert_into_db
|
||||||
from api.db.services.common_service import CommonService
|
from api.db.services.common_service import CommonService
|
||||||
from api.db.services.knowledgebase_service import KnowledgebaseService
|
from api.db.services.knowledgebase_service import KnowledgebaseService
|
||||||
|
from common.metadata_utils import dedupe_list
|
||||||
from common.misc_utils import get_uuid
|
from common.misc_utils import get_uuid
|
||||||
from common.time_utils import current_timestamp, get_format_time
|
from common.time_utils import current_timestamp, get_format_time
|
||||||
from common.constants import LLMType, ParserType, StatusEnum, TaskStatus, SVR_CONSUMER_GROUP_NAME
|
from common.constants import LLMType, ParserType, StatusEnum, TaskStatus, SVR_CONSUMER_GROUP_NAME
|
||||||
@ -696,10 +697,12 @@ class DocumentService(CommonService):
|
|||||||
for k,v in r.meta_fields.items():
|
for k,v in r.meta_fields.items():
|
||||||
if k not in meta:
|
if k not in meta:
|
||||||
meta[k] = {}
|
meta[k] = {}
|
||||||
v = str(v)
|
if not isinstance(v, list):
|
||||||
if v not in meta[k]:
|
v = [v]
|
||||||
meta[k][v] = []
|
for vv in v:
|
||||||
meta[k][v].append(doc_id)
|
if vv not in meta[k]:
|
||||||
|
meta[k][vv] = []
|
||||||
|
meta[k][vv].append(doc_id)
|
||||||
return meta
|
return meta
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -797,7 +800,10 @@ class DocumentService(CommonService):
|
|||||||
match_provided = "match" in upd
|
match_provided = "match" in upd
|
||||||
if isinstance(meta[key], list):
|
if isinstance(meta[key], list):
|
||||||
if not match_provided:
|
if not match_provided:
|
||||||
meta[key] = new_value
|
if isinstance(new_value, list):
|
||||||
|
meta[key] = dedupe_list(new_value)
|
||||||
|
else:
|
||||||
|
meta[key] = new_value
|
||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
match_value = upd.get("match")
|
match_value = upd.get("match")
|
||||||
@ -810,7 +816,7 @@ class DocumentService(CommonService):
|
|||||||
else:
|
else:
|
||||||
new_list.append(item)
|
new_list.append(item)
|
||||||
if replaced:
|
if replaced:
|
||||||
meta[key] = new_list
|
meta[key] = dedupe_list(new_list)
|
||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
if not match_provided:
|
if not match_provided:
|
||||||
|
|||||||
@ -117,6 +117,8 @@ class MemoryService(CommonService):
|
|||||||
if len(memory_name) > MEMORY_NAME_LIMIT:
|
if len(memory_name) > MEMORY_NAME_LIMIT:
|
||||||
return False, f"Memory name {memory_name} exceeds limit of {MEMORY_NAME_LIMIT}."
|
return False, f"Memory name {memory_name} exceeds limit of {MEMORY_NAME_LIMIT}."
|
||||||
|
|
||||||
|
timestamp = current_timestamp()
|
||||||
|
format_time = get_format_time()
|
||||||
# build create dict
|
# build create dict
|
||||||
memory_info = {
|
memory_info = {
|
||||||
"id": get_uuid(),
|
"id": get_uuid(),
|
||||||
@ -126,10 +128,10 @@ class MemoryService(CommonService):
|
|||||||
"embd_id": embd_id,
|
"embd_id": embd_id,
|
||||||
"llm_id": llm_id,
|
"llm_id": llm_id,
|
||||||
"system_prompt": PromptAssembler.assemble_system_prompt({"memory_type": memory_type}),
|
"system_prompt": PromptAssembler.assemble_system_prompt({"memory_type": memory_type}),
|
||||||
"create_time": current_timestamp(),
|
"create_time": timestamp,
|
||||||
"create_date": get_format_time(),
|
"create_date": format_time,
|
||||||
"update_time": current_timestamp(),
|
"update_time": timestamp,
|
||||||
"update_date": get_format_time(),
|
"update_date": format_time,
|
||||||
}
|
}
|
||||||
obj = cls.model(**memory_info).save(force_insert=True)
|
obj = cls.model(**memory_info).save(force_insert=True)
|
||||||
|
|
||||||
|
|||||||
@ -44,21 +44,27 @@ def meta_filter(metas: dict, filters: list[dict], logic: str = "and"):
|
|||||||
def filter_out(v2docs, operator, value):
|
def filter_out(v2docs, operator, value):
|
||||||
ids = []
|
ids = []
|
||||||
for input, docids in v2docs.items():
|
for input, docids in v2docs.items():
|
||||||
|
|
||||||
if operator in ["=", "≠", ">", "<", "≥", "≤"]:
|
if operator in ["=", "≠", ">", "<", "≥", "≤"]:
|
||||||
try:
|
try:
|
||||||
|
if isinstance(input, list):
|
||||||
|
input = input[0]
|
||||||
input = float(input)
|
input = float(input)
|
||||||
value = float(value)
|
value = float(value)
|
||||||
except Exception:
|
except Exception:
|
||||||
input = str(input)
|
pass
|
||||||
value = str(value)
|
if isinstance(input, str):
|
||||||
|
input = input.lower()
|
||||||
|
if isinstance(value, str):
|
||||||
|
value = value.lower()
|
||||||
|
|
||||||
for conds in [
|
for conds in [
|
||||||
(operator == "contains", str(value).lower() in str(input).lower()),
|
(operator == "contains", input in value if not isinstance(input, list) else all([i in value for i in input])),
|
||||||
(operator == "not contains", str(value).lower() not in str(input).lower()),
|
(operator == "not contains", input not in value if not isinstance(input, list) else all([i not in value for i in input])),
|
||||||
(operator == "in", str(input).lower() in str(value).lower()),
|
(operator == "in", input in value if not isinstance(input, list) else all([i in value for i in input])),
|
||||||
(operator == "not in", str(input).lower() not in str(value).lower()),
|
(operator == "not in", input not in value if not isinstance(input, list) else all([i not in value for i in input])),
|
||||||
(operator == "start with", str(input).lower().startswith(str(value).lower())),
|
(operator == "start with", str(input).lower().startswith(str(value).lower()) if not isinstance(input, list) else "".join([str(i).lower() for i in input]).startswith(str(value).lower())),
|
||||||
(operator == "end with", str(input).lower().endswith(str(value).lower())),
|
(operator == "end with", str(input).lower().endswith(str(value).lower()) if not isinstance(input, list) else "".join([str(i).lower() for i in input]).endswith(str(value).lower())),
|
||||||
(operator == "empty", not input),
|
(operator == "empty", not input),
|
||||||
(operator == "not empty", input),
|
(operator == "not empty", input),
|
||||||
(operator == "=", input == value),
|
(operator == "=", input == value),
|
||||||
@ -145,6 +151,18 @@ async def apply_meta_data_filter(
|
|||||||
return doc_ids
|
return doc_ids
|
||||||
|
|
||||||
|
|
||||||
|
def dedupe_list(values: list) -> list:
|
||||||
|
seen = set()
|
||||||
|
deduped = []
|
||||||
|
for item in values:
|
||||||
|
key = str(item)
|
||||||
|
if key in seen:
|
||||||
|
continue
|
||||||
|
seen.add(key)
|
||||||
|
deduped.append(item)
|
||||||
|
return deduped
|
||||||
|
|
||||||
|
|
||||||
def update_metadata_to(metadata, meta):
|
def update_metadata_to(metadata, meta):
|
||||||
if not meta:
|
if not meta:
|
||||||
return metadata
|
return metadata
|
||||||
@ -156,11 +174,13 @@ def update_metadata_to(metadata, meta):
|
|||||||
return metadata
|
return metadata
|
||||||
if not isinstance(meta, dict):
|
if not isinstance(meta, dict):
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
for k, v in meta.items():
|
for k, v in meta.items():
|
||||||
if isinstance(v, list):
|
if isinstance(v, list):
|
||||||
v = [vv for vv in v if isinstance(vv, str)]
|
v = [vv for vv in v if isinstance(vv, str)]
|
||||||
if not v:
|
if not v:
|
||||||
continue
|
continue
|
||||||
|
v = dedupe_list(v)
|
||||||
if not isinstance(v, list) and not isinstance(v, str):
|
if not isinstance(v, list) and not isinstance(v, str):
|
||||||
continue
|
continue
|
||||||
if k not in metadata:
|
if k not in metadata:
|
||||||
@ -171,6 +191,7 @@ def update_metadata_to(metadata, meta):
|
|||||||
metadata[k].extend(v)
|
metadata[k].extend(v)
|
||||||
else:
|
else:
|
||||||
metadata[k].append(v)
|
metadata[k].append(v)
|
||||||
|
metadata[k] = dedupe_list(metadata[k])
|
||||||
else:
|
else:
|
||||||
metadata[k] = v
|
metadata[k] = v
|
||||||
|
|
||||||
|
|||||||
90
docs/guides/agent/agent_component_reference/http.md
Normal file
90
docs/guides/agent/agent_component_reference/http.md
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 30
|
||||||
|
slug: /http_request_component
|
||||||
|
---
|
||||||
|
|
||||||
|
# HTTP request component
|
||||||
|
|
||||||
|
A component that calls remote services.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
An **HTTP request** component lets you access remote APIs or services by providing a URL and an HTTP method, and then receive the response. You can customize headers, parameters, proxies, and timeout settings, and use common methods like GET and POST. It’s useful for exchanging data with external systems in a workflow.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- An accessible remote API or service.
|
||||||
|
- Add a Token or credentials to the request header, if the target service requires authentication.
|
||||||
|
|
||||||
|
## Configurations
|
||||||
|
|
||||||
|
### Url
|
||||||
|
|
||||||
|
*Required*. The complete request address, for example: http://api.example.com/data.
|
||||||
|
|
||||||
|
### Method
|
||||||
|
|
||||||
|
The HTTP request method to select. Available options:
|
||||||
|
|
||||||
|
- GET
|
||||||
|
- POST
|
||||||
|
- PUT
|
||||||
|
|
||||||
|
### Timeout
|
||||||
|
|
||||||
|
The maximum waiting time for the request, in seconds. Defaults to `60`.
|
||||||
|
|
||||||
|
### Headers
|
||||||
|
|
||||||
|
Custom HTTP headers can be set here, for example:
|
||||||
|
|
||||||
|
```http
|
||||||
|
{
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Cache-Control": "no-cache",
|
||||||
|
"Connection": "keep-alive"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Proxy
|
||||||
|
|
||||||
|
Optional. The proxy server address to use for this request.
|
||||||
|
|
||||||
|
### Clean HTML
|
||||||
|
|
||||||
|
`Boolean`: Whether to remove HTML tags from the returned results and keep plain text only.
|
||||||
|
|
||||||
|
### Parameter
|
||||||
|
|
||||||
|
*Optional*. Parameters to send with the HTTP request. Supports key-value pairs:
|
||||||
|
|
||||||
|
- To assign a value using a dynamic system variable, set it as Variable.
|
||||||
|
- To override these dynamic values under certain conditions and use a fixed static value instead, Value is the appropriate choice.
|
||||||
|
|
||||||
|
|
||||||
|
:::tip NOTE
|
||||||
|
- For GET requests, these parameters are appended to the end of the URL.
|
||||||
|
- For POST/PUT requests, they are sent as the request body.
|
||||||
|
:::
|
||||||
|
|
||||||
|
#### Example setting
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### Example response
|
||||||
|
|
||||||
|
```html
|
||||||
|
{ "args": { "App": "RAGFlow", "Query": "How to do?", "Userid": "241ed25a8e1011f0b979424ebc5b108b" }, "headers": { "Accept": "/", "Accept-Encoding": "gzip, deflate, br, zstd", "Cache-Control": "no-cache", "Host": "httpbin.org", "User-Agent": "python-requests/2.32.2", "X-Amzn-Trace-Id": "Root=1-68c9210c-5aab9088580c130a2f065523" }, "origin": "185.36.193.38", "url": "https://httpbin.org/get?Userid=241ed25a8e1011f0b979424ebc5b108b&App=RAGFlow&Query=How+to+do%3F" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output
|
||||||
|
|
||||||
|
The global variable name for the output of the HTTP request component, which can be referenced by other components in the workflow.
|
||||||
|
|
||||||
|
- `Result`: `string` The response returned by the remote service.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
This is a usage example: a workflow sends a GET request from the **Begin** component to `https://httpbin.org/get` via the **HTTP Request_0** component, passes parameters to the server, and finally outputs the result through the **Message_0** component.
|
||||||
|
|
||||||
|

|
||||||
@ -348,7 +348,8 @@ def tokenize_table(tbls, doc, eng, batch_size=10):
|
|||||||
d["doc_type_kwd"] = "table"
|
d["doc_type_kwd"] = "table"
|
||||||
if img:
|
if img:
|
||||||
d["image"] = img
|
d["image"] = img
|
||||||
d["doc_type_kwd"] = "image"
|
if d["content_with_weight"].find("<tr>") < 0:
|
||||||
|
d["doc_type_kwd"] = "image"
|
||||||
if poss:
|
if poss:
|
||||||
add_positions(d, poss)
|
add_positions(d, poss)
|
||||||
res.append(d)
|
res.append(d)
|
||||||
@ -361,7 +362,8 @@ def tokenize_table(tbls, doc, eng, batch_size=10):
|
|||||||
d["doc_type_kwd"] = "table"
|
d["doc_type_kwd"] = "table"
|
||||||
if img:
|
if img:
|
||||||
d["image"] = img
|
d["image"] = img
|
||||||
d["doc_type_kwd"] = "image"
|
if d["content_with_weight"].find("<tr>") < 0:
|
||||||
|
d["doc_type_kwd"] = "image"
|
||||||
add_positions(d, poss)
|
add_positions(d, poss)
|
||||||
res.append(d)
|
res.append(d)
|
||||||
return res
|
return res
|
||||||
|
|||||||
@ -339,7 +339,7 @@ def tool_schema(tools_description: list[dict], complete_task=False):
|
|||||||
}
|
}
|
||||||
for idx, tool in enumerate(tools_description):
|
for idx, tool in enumerate(tools_description):
|
||||||
name = tool["function"]["name"]
|
name = tool["function"]["name"]
|
||||||
desc[f"{name}_{idx}"] = tool
|
desc[name] = 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())])
|
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())])
|
||||||
|
|
||||||
|
|||||||
@ -94,7 +94,7 @@ This content will NOT be shown to the user.
|
|||||||
## Step 2: Structured Reflection (MANDATORY before `complete_task`)
|
## Step 2: Structured Reflection (MANDATORY before `complete_task`)
|
||||||
|
|
||||||
### Context
|
### Context
|
||||||
- Goal: {{ task_analysis }}
|
- Goal: Reflect on the current task based on the full conversation context
|
||||||
- Executed tool calls so far (if any): reflect from conversation history
|
- Executed tool calls so far (if any): reflect from conversation history
|
||||||
|
|
||||||
### Task Complexity Assessment
|
### Task Complexity Assessment
|
||||||
|
|||||||
@ -395,9 +395,9 @@ async def build_chunks(task, progress_callback):
|
|||||||
await asyncio.gather(*tasks, return_exceptions=True)
|
await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
raise
|
raise
|
||||||
metadata = {}
|
metadata = {}
|
||||||
for ck in cks:
|
for doc in docs:
|
||||||
metadata = update_metadata_to(metadata, ck["metadata_obj"])
|
metadata = update_metadata_to(metadata, doc["metadata_obj"])
|
||||||
del ck["metadata_obj"]
|
del doc["metadata_obj"]
|
||||||
if metadata:
|
if metadata:
|
||||||
e, doc = DocumentService.get_by_id(task["doc_id"])
|
e, doc = DocumentService.get_by_id(task["doc_id"])
|
||||||
if e:
|
if e:
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import { cn } from '@/lib/utils';
|
|||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
import { MultiSelect, MultiSelectOptionType } from './ui/multi-select';
|
import { MultiSelect, MultiSelectOptionType } from './ui/multi-select';
|
||||||
|
import { Switch } from './ui/switch';
|
||||||
|
|
||||||
// Field type enumeration
|
// Field type enumeration
|
||||||
export enum FormFieldType {
|
export enum FormFieldType {
|
||||||
@ -46,6 +47,7 @@ export enum FormFieldType {
|
|||||||
Select = 'select',
|
Select = 'select',
|
||||||
MultiSelect = 'multi-select',
|
MultiSelect = 'multi-select',
|
||||||
Checkbox = 'checkbox',
|
Checkbox = 'checkbox',
|
||||||
|
Switch = 'switch',
|
||||||
Tag = 'tag',
|
Tag = 'tag',
|
||||||
Custom = 'custom',
|
Custom = 'custom',
|
||||||
}
|
}
|
||||||
@ -154,6 +156,7 @@ export const generateSchema = (fields: FormFieldConfig[]): ZodSchema<any> => {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FormFieldType.Checkbox:
|
case FormFieldType.Checkbox:
|
||||||
|
case FormFieldType.Switch:
|
||||||
fieldSchema = z.boolean();
|
fieldSchema = z.boolean();
|
||||||
break;
|
break;
|
||||||
case FormFieldType.Tag:
|
case FormFieldType.Tag:
|
||||||
@ -193,6 +196,8 @@ export const generateSchema = (fields: FormFieldConfig[]): ZodSchema<any> => {
|
|||||||
if (
|
if (
|
||||||
field.type !== FormFieldType.Number &&
|
field.type !== FormFieldType.Number &&
|
||||||
field.type !== FormFieldType.Checkbox &&
|
field.type !== FormFieldType.Checkbox &&
|
||||||
|
field.type !== FormFieldType.Switch &&
|
||||||
|
field.type !== FormFieldType.Custom &&
|
||||||
field.type !== FormFieldType.Tag &&
|
field.type !== FormFieldType.Tag &&
|
||||||
field.required
|
field.required
|
||||||
) {
|
) {
|
||||||
@ -289,7 +294,10 @@ const generateDefaultValues = <T extends FieldValues>(
|
|||||||
const lastKey = keys[keys.length - 1];
|
const lastKey = keys[keys.length - 1];
|
||||||
if (field.defaultValue !== undefined) {
|
if (field.defaultValue !== undefined) {
|
||||||
current[lastKey] = field.defaultValue;
|
current[lastKey] = field.defaultValue;
|
||||||
} else if (field.type === FormFieldType.Checkbox) {
|
} else if (
|
||||||
|
field.type === FormFieldType.Checkbox ||
|
||||||
|
field.type === FormFieldType.Switch
|
||||||
|
) {
|
||||||
current[lastKey] = false;
|
current[lastKey] = false;
|
||||||
} else if (field.type === FormFieldType.Tag) {
|
} else if (field.type === FormFieldType.Tag) {
|
||||||
current[lastKey] = [];
|
current[lastKey] = [];
|
||||||
@ -299,7 +307,10 @@ const generateDefaultValues = <T extends FieldValues>(
|
|||||||
} else {
|
} else {
|
||||||
if (field.defaultValue !== undefined) {
|
if (field.defaultValue !== undefined) {
|
||||||
defaultValues[field.name] = field.defaultValue;
|
defaultValues[field.name] = field.defaultValue;
|
||||||
} else if (field.type === FormFieldType.Checkbox) {
|
} else if (
|
||||||
|
field.type === FormFieldType.Checkbox ||
|
||||||
|
field.type === FormFieldType.Switch
|
||||||
|
) {
|
||||||
defaultValues[field.name] = false;
|
defaultValues[field.name] = false;
|
||||||
} else if (
|
} else if (
|
||||||
field.type === FormFieldType.Tag ||
|
field.type === FormFieldType.Tag ||
|
||||||
@ -502,6 +513,32 @@ export const RenderField = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
case FormFieldType.Switch:
|
||||||
|
return (
|
||||||
|
<RAGFlowFormItem
|
||||||
|
{...field}
|
||||||
|
labelClassName={labelClassName || field.labelClassName}
|
||||||
|
>
|
||||||
|
{(fieldProps) => {
|
||||||
|
const finalFieldProps = field.onChange
|
||||||
|
? {
|
||||||
|
...fieldProps,
|
||||||
|
onChange: (checked: boolean) => {
|
||||||
|
fieldProps.onChange(checked);
|
||||||
|
field.onChange?.(checked);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: fieldProps;
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
checked={finalFieldProps.value as boolean}
|
||||||
|
onCheckedChange={(checked) => finalFieldProps.onChange(checked)}
|
||||||
|
disabled={field.disabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
);
|
||||||
|
|
||||||
case FormFieldType.Tag:
|
case FormFieldType.Tag:
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -31,14 +31,16 @@ const handleCheckChange = ({
|
|||||||
(value: string) => value !== item.id.toString(),
|
(value: string) => value !== item.id.toString(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const newValue = {
|
const newValue = newParentValues?.length
|
||||||
...currentValue,
|
? {
|
||||||
[parentId]: newParentValues,
|
...currentValue,
|
||||||
};
|
[parentId]: newParentValues,
|
||||||
|
}
|
||||||
|
: { ...currentValue };
|
||||||
|
|
||||||
if (newValue[parentId].length === 0) {
|
// if (newValue[parentId].length === 0) {
|
||||||
delete newValue[parentId];
|
// delete newValue[parentId];
|
||||||
}
|
// }
|
||||||
|
|
||||||
return field.onChange(newValue);
|
return field.onChange(newValue);
|
||||||
} else {
|
} else {
|
||||||
@ -66,20 +68,31 @@ const FilterItem = memo(
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex items-center justify-between text-text-primary text-xs ${level > 0 ? 'ml-4' : ''}`}
|
className={`flex items-center justify-between text-text-primary text-xs ${level > 0 ? 'ml-1' : ''}`}
|
||||||
>
|
>
|
||||||
<FormItem className="flex flex-row space-x-3 space-y-0 items-center">
|
<FormItem className="flex flex-row space-x-3 space-y-0 items-center ">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Checkbox
|
<div className="flex space-x-3">
|
||||||
checked={field.value?.includes(item.id.toString())}
|
<Checkbox
|
||||||
onCheckedChange={(checked: boolean) =>
|
checked={field.value?.includes(item.id.toString())}
|
||||||
handleCheckChange({ checked, field, item })
|
onCheckedChange={(checked: boolean) =>
|
||||||
}
|
handleCheckChange({ checked, field, item })
|
||||||
/>
|
}
|
||||||
|
// className="hidden group-hover:block"
|
||||||
|
/>
|
||||||
|
<FormLabel
|
||||||
|
onClick={() =>
|
||||||
|
handleCheckChange({
|
||||||
|
checked: !field.value?.includes(item.id.toString()),
|
||||||
|
field,
|
||||||
|
item,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</FormLabel>
|
||||||
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel onClick={(e) => e.stopPropagation()}>
|
|
||||||
{item.label}
|
|
||||||
</FormLabel>
|
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{item.count !== undefined && (
|
{item.count !== undefined && (
|
||||||
<span className="text-sm">{item.count}</span>
|
<span className="text-sm">{item.count}</span>
|
||||||
@ -107,11 +120,11 @@ export const FilterField = memo(
|
|||||||
<FormField
|
<FormField
|
||||||
key={item.id}
|
key={item.id}
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name={parent.field as string}
|
name={parent.field?.toString() as string}
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
if (hasNestedList) {
|
if (hasNestedList) {
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col gap-2 ${level > 0 ? 'ml-4' : ''}`}>
|
<div className={`flex flex-col gap-2 ${level > 0 ? 'ml-1' : ''}`}>
|
||||||
<div
|
<div
|
||||||
className="flex items-center justify-between cursor-pointer"
|
className="flex items-center justify-between cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -138,23 +151,6 @@ export const FilterField = memo(
|
|||||||
}}
|
}}
|
||||||
level={level + 1}
|
level={level + 1}
|
||||||
/>
|
/>
|
||||||
// <FilterItem key={child.id} item={child} field={child.field} level={level+1} />
|
|
||||||
// <div
|
|
||||||
// className="flex flex-row space-x-3 space-y-0 items-center"
|
|
||||||
// key={child.id}
|
|
||||||
// >
|
|
||||||
// <FormControl>
|
|
||||||
// <Checkbox
|
|
||||||
// checked={field.value?.includes(child.id.toString())}
|
|
||||||
// onCheckedChange={(checked) =>
|
|
||||||
// handleCheckChange({ checked, field, item: child })
|
|
||||||
// }
|
|
||||||
// />
|
|
||||||
// </FormControl>
|
|
||||||
// <FormLabel onClick={(e) => e.stopPropagation()}>
|
|
||||||
// {child.label}
|
|
||||||
// </FormLabel>
|
|
||||||
// </div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { FieldPath, useForm } from 'react-hook-form';
|
||||||
import { ZodArray, ZodString, z } from 'zod';
|
import { ZodArray, ZodString, z } from 'zod';
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -178,7 +178,9 @@ function CheckboxFormMultiple({
|
|||||||
<FormField
|
<FormField
|
||||||
key={x.field}
|
key={x.field}
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name={x.field}
|
name={
|
||||||
|
x.field.toString() as FieldPath<z.infer<typeof FormSchema>>
|
||||||
|
}
|
||||||
render={() => (
|
render={() => (
|
||||||
<FormItem className="space-y-4">
|
<FormItem className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
@ -186,19 +188,20 @@ function CheckboxFormMultiple({
|
|||||||
{x.label}
|
{x.label}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</div>
|
</div>
|
||||||
{x.list.map((item) => {
|
{x.list?.length &&
|
||||||
return (
|
x.list.map((item) => {
|
||||||
<FilterField
|
return (
|
||||||
key={item.id}
|
<FilterField
|
||||||
item={{ ...item }}
|
key={item.id}
|
||||||
parent={{
|
item={{ ...item }}
|
||||||
...x,
|
parent={{
|
||||||
id: x.field,
|
...x,
|
||||||
// field: `${x.field}${item.field ? '.' + item.field : ''}`,
|
id: x.field,
|
||||||
}}
|
// field: `${x.field}${item.field ? '.' + item.field : ''}`,
|
||||||
/>
|
}}
|
||||||
);
|
/>
|
||||||
})}
|
);
|
||||||
|
})}
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -176,6 +176,11 @@ Procedural Memory: Learned skills, habits, and automated procedures.`,
|
|||||||
},
|
},
|
||||||
knowledgeDetails: {
|
knowledgeDetails: {
|
||||||
metadata: {
|
metadata: {
|
||||||
|
valueExists:
|
||||||
|
'Value already exists. Confirm to merge duplicates and combine all associated files.',
|
||||||
|
fieldNameExists:
|
||||||
|
'Field name already exists. Confirm to merge duplicates and combine all associated files.',
|
||||||
|
fieldExists: 'Field already exists.',
|
||||||
fieldSetting: 'Field settings',
|
fieldSetting: 'Field settings',
|
||||||
changesAffectNewParses: 'Changes affect new parses only.',
|
changesAffectNewParses: 'Changes affect new parses only.',
|
||||||
editMetadataForDataset: 'View and edit metadata for ',
|
editMetadataForDataset: 'View and edit metadata for ',
|
||||||
@ -190,6 +195,7 @@ Procedural Memory: Learned skills, habits, and automated procedures.`,
|
|||||||
description: 'Description',
|
description: 'Description',
|
||||||
fieldName: 'Field name',
|
fieldName: 'Field name',
|
||||||
editMetadata: 'Edit metadata',
|
editMetadata: 'Edit metadata',
|
||||||
|
deleteWarn: 'This {{field}} will be removed from all associated files',
|
||||||
},
|
},
|
||||||
metadataField: 'Metadata field',
|
metadataField: 'Metadata field',
|
||||||
systemAttribute: 'System attribute',
|
systemAttribute: 'System attribute',
|
||||||
|
|||||||
@ -182,6 +182,10 @@ export default {
|
|||||||
description: '描述',
|
description: '描述',
|
||||||
fieldName: '字段名',
|
fieldName: '字段名',
|
||||||
editMetadata: '编辑元数据',
|
editMetadata: '编辑元数据',
|
||||||
|
valueExists: '值已存在。确认合并重复项并组合所有关联文件。',
|
||||||
|
fieldNameExists: '字段名已存在。确认合并重复项并组合所有关联文件。',
|
||||||
|
fieldExists: '字段名已存在。',
|
||||||
|
deleteWarn: '此 {{field}} 将从所有关联文件中移除',
|
||||||
},
|
},
|
||||||
localUpload: '本地上传',
|
localUpload: '本地上传',
|
||||||
fileSize: '文件大小',
|
fileSize: '文件大小',
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useSendAgentMessage } from './use-send-agent-message';
|
|||||||
|
|
||||||
import { FileUploadProps } from '@/components/file-upload';
|
import { FileUploadProps } from '@/components/file-upload';
|
||||||
import { NextMessageInput } from '@/components/message-input/next';
|
import { NextMessageInput } from '@/components/message-input/next';
|
||||||
|
import MarkdownContent from '@/components/next-markdown-content';
|
||||||
import MessageItem from '@/components/next-message-item';
|
import MessageItem from '@/components/next-message-item';
|
||||||
import PdfSheet from '@/components/pdf-drawer';
|
import PdfSheet from '@/components/pdf-drawer';
|
||||||
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
|
||||||
@ -102,8 +103,10 @@ function AgentChatBox() {
|
|||||||
{message.role === MessageType.Assistant &&
|
{message.role === MessageType.Assistant &&
|
||||||
derivedMessages.length - 1 !== i && (
|
derivedMessages.length - 1 !== i && (
|
||||||
<div>
|
<div>
|
||||||
<div>{message?.data?.tips}</div>
|
<MarkdownContent
|
||||||
|
content={message?.data?.tips}
|
||||||
|
loading={false}
|
||||||
|
></MarkdownContent>
|
||||||
<div>
|
<div>
|
||||||
{buildInputList(message)?.map((item) => item.value)}
|
{buildInputList(message)?.map((item) => item.value)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import MarkdownContent from '@/components/next-markdown-content';
|
||||||
import { ButtonLoading } from '@/components/ui/button';
|
import { ButtonLoading } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@ -234,7 +235,14 @@ const DebugContent = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section>
|
<section>
|
||||||
{message?.data?.tips && <div className="mb-2">{message.data.tips}</div>}
|
{message?.data?.tips && (
|
||||||
|
<div className="mb-2">
|
||||||
|
<MarkdownContent
|
||||||
|
content={message?.data?.tips}
|
||||||
|
loading={false}
|
||||||
|
></MarkdownContent>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||||
{parameters.map((x, idx) => {
|
{parameters.map((x, idx) => {
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import message from '@/components/ui/message';
|
import message from '@/components/ui/message';
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
import { useSetDocumentMeta } from '@/hooks/use-document-request';
|
import {
|
||||||
|
DocumentApiAction,
|
||||||
|
useSetDocumentMeta,
|
||||||
|
} from '@/hooks/use-document-request';
|
||||||
import kbService, {
|
import kbService, {
|
||||||
getMetaDataService,
|
getMetaDataService,
|
||||||
updateMetaData,
|
updateMetaData,
|
||||||
} from '@/services/knowledge-service';
|
} from '@/services/knowledge-service';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useParams } from 'umi';
|
import { useParams } from 'umi';
|
||||||
@ -191,7 +194,7 @@ export const useManageMetaDataModal = (
|
|||||||
const { data, loading } = useFetchMetaDataManageData(type);
|
const { data, loading } = useFetchMetaDataManageData(type);
|
||||||
|
|
||||||
const [tableData, setTableData] = useState<IMetaDataTableData[]>(metaData);
|
const [tableData, setTableData] = useState<IMetaDataTableData[]>(metaData);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const { operations, addDeleteRow, addDeleteValue, addUpdateValue } =
|
const { operations, addDeleteRow, addDeleteValue, addUpdateValue } =
|
||||||
useMetadataOperations();
|
useMetadataOperations();
|
||||||
|
|
||||||
@ -259,11 +262,14 @@ export const useManageMetaDataModal = (
|
|||||||
data: operations,
|
data: operations,
|
||||||
});
|
});
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [DocumentApiAction.FetchDocumentList],
|
||||||
|
});
|
||||||
message.success(t('message.operated'));
|
message.success(t('message.operated'));
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[operations, id, t],
|
[operations, id, t, queryClient],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSaveUpdateSingle = useCallback(
|
const handleSaveUpdateSingle = useCallback(
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export type IManageModalProps = {
|
|||||||
|
|
||||||
export interface IManageValuesProps {
|
export interface IManageValuesProps {
|
||||||
title: ReactNode;
|
title: ReactNode;
|
||||||
|
existsKeys: string[];
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
isEditField?: boolean;
|
isEditField?: boolean;
|
||||||
isAddValue?: boolean;
|
isAddValue?: boolean;
|
||||||
@ -46,6 +47,7 @@ export interface IManageValuesProps {
|
|||||||
isShowValueSwitch?: boolean;
|
isShowValueSwitch?: boolean;
|
||||||
isVerticalShowValue?: boolean;
|
isVerticalShowValue?: boolean;
|
||||||
data: IMetaDataTableData;
|
data: IMetaDataTableData;
|
||||||
|
type: MetadataType;
|
||||||
hideModal: () => void;
|
hideModal: () => void;
|
||||||
onSave: (data: IMetaDataTableData) => void;
|
onSave: (data: IMetaDataTableData) => void;
|
||||||
addUpdateValue: (key: string, value: string | string[]) => void;
|
addUpdateValue: (key: string, value: string | string[]) => void;
|
||||||
|
|||||||
@ -54,6 +54,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
values: [],
|
values: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [currentValueIndex, setCurrentValueIndex] = useState<number>(0);
|
||||||
const [deleteDialogContent, setDeleteDialogContent] = useState({
|
const [deleteDialogContent, setDeleteDialogContent] = useState({
|
||||||
visible: false,
|
visible: false,
|
||||||
title: '',
|
title: '',
|
||||||
@ -94,12 +95,12 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
description: '',
|
description: '',
|
||||||
values: [],
|
values: [],
|
||||||
});
|
});
|
||||||
// setCurrentValueIndex(tableData.length || 0);
|
setCurrentValueIndex(tableData.length || 0);
|
||||||
showManageValuesModal();
|
showManageValuesModal();
|
||||||
};
|
};
|
||||||
const handleEditValueRow = useCallback(
|
const handleEditValueRow = useCallback(
|
||||||
(data: IMetaDataTableData) => {
|
(data: IMetaDataTableData, index: number) => {
|
||||||
// setCurrentValueIndex(index);
|
setCurrentValueIndex(index);
|
||||||
setValueData(data);
|
setValueData(data);
|
||||||
showManageValuesModal();
|
showManageValuesModal();
|
||||||
},
|
},
|
||||||
@ -153,10 +154,33 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
variant={'delete'}
|
variant={'delete'}
|
||||||
className="p-0 bg-transparent"
|
className="p-0 bg-transparent"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDeleteSingleValue(
|
setDeleteDialogContent({
|
||||||
row.getValue('field'),
|
visible: true,
|
||||||
value,
|
title:
|
||||||
);
|
t('common.delete') +
|
||||||
|
' ' +
|
||||||
|
t('knowledgeDetails.metadata.metadata'),
|
||||||
|
name: row.getValue('field') + '/' + value,
|
||||||
|
warnText: t(
|
||||||
|
'knowledgeDetails.metadata.deleteWarn',
|
||||||
|
{
|
||||||
|
field:
|
||||||
|
t('knowledgeDetails.metadata.field') +
|
||||||
|
'/' +
|
||||||
|
t('knowledgeDetails.metadata.values'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onOk: () => {
|
||||||
|
hideDeleteModal();
|
||||||
|
handleDeleteSingleValue(
|
||||||
|
row.getValue('field'),
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
hideDeleteModal();
|
||||||
|
},
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Trash2 />
|
<Trash2 />
|
||||||
@ -185,7 +209,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
variant={'ghost'}
|
variant={'ghost'}
|
||||||
className="bg-transparent px-1 py-0"
|
className="bg-transparent px-1 py-0"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleEditValueRow(row.original);
|
handleEditValueRow(row.original, row.index);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Settings />
|
<Settings />
|
||||||
@ -201,7 +225,9 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
' ' +
|
' ' +
|
||||||
t('knowledgeDetails.metadata.metadata'),
|
t('knowledgeDetails.metadata.metadata'),
|
||||||
name: row.getValue('field'),
|
name: row.getValue('field'),
|
||||||
warnText: t('knowledgeDetails.metadata.deleteWarn'),
|
warnText: t('knowledgeDetails.metadata.deleteWarn', {
|
||||||
|
field: t('knowledgeDetails.metadata.field'),
|
||||||
|
}),
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
hideDeleteModal();
|
hideDeleteModal();
|
||||||
handleDeleteSingleRow(row.getValue('field'));
|
handleDeleteSingleRow(row.getValue('field'));
|
||||||
@ -243,12 +269,26 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
|
|
||||||
const handleSaveValues = (data: IMetaDataTableData) => {
|
const handleSaveValues = (data: IMetaDataTableData) => {
|
||||||
setTableData((prev) => {
|
setTableData((prev) => {
|
||||||
//If the keys are the same, they need to be merged.
|
let newData;
|
||||||
const fieldMap = new Map<string, any>();
|
if (currentValueIndex >= prev.length) {
|
||||||
|
// Add operation
|
||||||
|
newData = [...prev, data];
|
||||||
|
} else {
|
||||||
|
// Edit operation
|
||||||
|
newData = prev.map((item, index) => {
|
||||||
|
if (index === currentValueIndex) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
prev.forEach((item) => {
|
// Deduplicate by field and merge values
|
||||||
|
const fieldMap = new Map<string, IMetaDataTableData>();
|
||||||
|
newData.forEach((item) => {
|
||||||
if (fieldMap.has(item.field)) {
|
if (fieldMap.has(item.field)) {
|
||||||
const existingItem = fieldMap.get(item.field);
|
// Merge values if field exists
|
||||||
|
const existingItem = fieldMap.get(item.field)!;
|
||||||
const mergedValues = [
|
const mergedValues = [
|
||||||
...new Set([...existingItem.values, ...item.values]),
|
...new Set([...existingItem.values, ...item.values]),
|
||||||
];
|
];
|
||||||
@ -258,20 +298,14 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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 {
|
|
||||||
fieldMap.set(data.field, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(fieldMap.values());
|
return Array.from(fieldMap.values());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const existsKeys = useMemo(() => {
|
||||||
|
return tableData.map((item) => item.field);
|
||||||
|
}, [tableData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
@ -357,6 +391,8 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
: t('knowledgeDetails.metadata.editMetadata')}
|
: t('knowledgeDetails.metadata.editMetadata')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
type={metadataType}
|
||||||
|
existsKeys={existsKeys}
|
||||||
visible={manageValuesVisible}
|
visible={manageValuesVisible}
|
||||||
hideModal={hideManageValuesModal}
|
hideModal={hideManageValuesModal}
|
||||||
data={valueData}
|
data={valueData}
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
import {
|
||||||
|
ConfirmDeleteDialog,
|
||||||
|
ConfirmDeleteDialogNode,
|
||||||
|
} from '@/components/confirm-delete-dialog';
|
||||||
import EditTag from '@/components/edit-tag';
|
import EditTag from '@/components/edit-tag';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@ -7,6 +11,7 @@ import { Textarea } from '@/components/ui/textarea';
|
|||||||
import { Plus, Trash2 } from 'lucide-react';
|
import { Plus, Trash2 } from 'lucide-react';
|
||||||
import { memo, useCallback, useEffect, useState } from 'react';
|
import { memo, useCallback, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { MetadataType } from './hook';
|
||||||
import { IManageValuesProps, IMetaDataTableData } from './interface';
|
import { IManageValuesProps, IMetaDataTableData } from './interface';
|
||||||
|
|
||||||
// Create a separate input component, wrapped with memo to avoid unnecessary re-renders
|
// Create a separate input component, wrapped with memo to avoid unnecessary re-renders
|
||||||
@ -63,17 +68,62 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
onSave,
|
onSave,
|
||||||
addUpdateValue,
|
addUpdateValue,
|
||||||
addDeleteValue,
|
addDeleteValue,
|
||||||
|
existsKeys,
|
||||||
|
type,
|
||||||
} = props;
|
} = props;
|
||||||
const [metaData, setMetaData] = useState(data);
|
const [metaData, setMetaData] = useState(data);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [valueError, setValueError] = useState<Record<string, string>>({
|
||||||
|
field: '',
|
||||||
|
values: '',
|
||||||
|
});
|
||||||
|
const [deleteDialogContent, setDeleteDialogContent] = useState({
|
||||||
|
visible: false,
|
||||||
|
title: '',
|
||||||
|
name: '',
|
||||||
|
warnText: '',
|
||||||
|
onOk: () => {},
|
||||||
|
onCancel: () => {},
|
||||||
|
});
|
||||||
|
const hideDeleteModal = () => {
|
||||||
|
setDeleteDialogContent({
|
||||||
|
visible: false,
|
||||||
|
title: '',
|
||||||
|
name: '',
|
||||||
|
warnText: '',
|
||||||
|
onOk: () => {},
|
||||||
|
onCancel: () => {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Use functional update to avoid closure issues
|
// Use functional update to avoid closure issues
|
||||||
const handleChange = useCallback((field: string, value: any) => {
|
const handleChange = useCallback(
|
||||||
setMetaData((prev) => ({
|
(field: string, value: any) => {
|
||||||
...prev,
|
if (field === 'field' && existsKeys.includes(value)) {
|
||||||
[field]: value,
|
setValueError((prev) => {
|
||||||
}));
|
return {
|
||||||
}, []);
|
...prev,
|
||||||
|
field:
|
||||||
|
type === MetadataType.Setting
|
||||||
|
? t('knowledgeDetails.metadata.fieldExists')
|
||||||
|
: t('knowledgeDetails.metadata.fieldNameExists'),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else if (field === 'field' && !existsKeys.includes(value)) {
|
||||||
|
setValueError((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
field: '',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setMetaData((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[field]: value,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
[existsKeys, type, t],
|
||||||
|
);
|
||||||
|
|
||||||
// Maintain separate state for each input box
|
// Maintain separate state for each input box
|
||||||
const [tempValues, setTempValues] = useState<string[]>([...data.values]);
|
const [tempValues, setTempValues] = useState<string[]>([...data.values]);
|
||||||
@ -89,6 +139,9 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
}, [hideModal]);
|
}, [hideModal]);
|
||||||
|
|
||||||
const handleSave = useCallback(() => {
|
const handleSave = useCallback(() => {
|
||||||
|
if (type === MetadataType.Setting && valueError.field) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!metaData.restrictDefinedValues && isShowValueSwitch) {
|
if (!metaData.restrictDefinedValues && isShowValueSwitch) {
|
||||||
const newMetaData = { ...metaData, values: [] };
|
const newMetaData = { ...metaData, values: [] };
|
||||||
onSave(newMetaData);
|
onSave(newMetaData);
|
||||||
@ -96,17 +149,35 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
onSave(metaData);
|
onSave(metaData);
|
||||||
}
|
}
|
||||||
handleHideModal();
|
handleHideModal();
|
||||||
}, [metaData, onSave, handleHideModal, isShowValueSwitch]);
|
}, [metaData, onSave, handleHideModal, isShowValueSwitch, type, valueError]);
|
||||||
|
|
||||||
// Handle value changes, only update temporary state
|
// Handle value changes, only update temporary state
|
||||||
const handleValueChange = useCallback((index: number, value: string) => {
|
const handleValueChange = useCallback(
|
||||||
setTempValues((prev) => {
|
(index: number, value: string) => {
|
||||||
const newValues = [...prev];
|
setTempValues((prev) => {
|
||||||
newValues[index] = value;
|
if (prev.includes(value)) {
|
||||||
|
setValueError((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
values: t('knowledgeDetails.metadata.valueExists'),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setValueError((prev) => {
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
values: '',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const newValues = [...prev];
|
||||||
|
newValues[index] = value;
|
||||||
|
|
||||||
return newValues;
|
return newValues;
|
||||||
});
|
});
|
||||||
}, []);
|
},
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
// Handle blur event, synchronize to main state
|
// Handle blur event, synchronize to main state
|
||||||
const handleValueBlur = useCallback(() => {
|
const handleValueBlur = useCallback(() => {
|
||||||
@ -137,6 +208,27 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
[addDeleteValue, metaData],
|
[addDeleteValue, metaData],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const showDeleteModal = (item: string, callback: () => void) => {
|
||||||
|
setDeleteDialogContent({
|
||||||
|
visible: true,
|
||||||
|
title: t('common.delete') + ' ' + t('knowledgeDetails.metadata.metadata'),
|
||||||
|
name: metaData.field + '/' + item,
|
||||||
|
warnText: t('knowledgeDetails.metadata.deleteWarn', {
|
||||||
|
field:
|
||||||
|
t('knowledgeDetails.metadata.field') +
|
||||||
|
'/' +
|
||||||
|
t('knowledgeDetails.metadata.values'),
|
||||||
|
}),
|
||||||
|
onOk: () => {
|
||||||
|
hideDeleteModal();
|
||||||
|
callback();
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
hideDeleteModal();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Handle adding new value
|
// Handle adding new value
|
||||||
const handleAddValue = useCallback(() => {
|
const handleAddValue = useCallback(() => {
|
||||||
setTempValues((prev) => [...new Set([...prev, ''])]);
|
setTempValues((prev) => [...new Set([...prev, ''])]);
|
||||||
@ -172,9 +264,13 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
<Input
|
<Input
|
||||||
value={metaData.field}
|
value={metaData.field}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
handleChange('field', e.target?.value || '');
|
const value = e.target?.value || '';
|
||||||
|
if (/^[a-zA-Z_]*$/.test(value)) {
|
||||||
|
handleChange('field', value);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<div className="text-state-error text-sm">{valueError.field}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -230,7 +326,11 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
item={item}
|
item={item}
|
||||||
index={index}
|
index={index}
|
||||||
onValueChange={handleValueChange}
|
onValueChange={handleValueChange}
|
||||||
onDelete={handleDelete}
|
onDelete={(idx: number) => {
|
||||||
|
showDeleteModal(item, () => {
|
||||||
|
handleDelete(idx);
|
||||||
|
});
|
||||||
|
}}
|
||||||
onBlur={handleValueBlur}
|
onBlur={handleValueBlur}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -240,11 +340,41 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
{!isVerticalShowValue && (
|
{!isVerticalShowValue && (
|
||||||
<EditTag
|
<EditTag
|
||||||
value={metaData.values}
|
value={metaData.values}
|
||||||
onChange={(value) => handleChange('values', value)}
|
onChange={(value) => {
|
||||||
|
// find deleted value
|
||||||
|
const item = metaData.values.find(
|
||||||
|
(item) => !value.includes(item),
|
||||||
|
);
|
||||||
|
if (item) {
|
||||||
|
showDeleteModal(item, () => {
|
||||||
|
// handleDelete(idx);
|
||||||
|
handleChange('values', value);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleChange('values', value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<div className="text-state-error text-sm">{valueError.values}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{deleteDialogContent.visible && (
|
||||||
|
<ConfirmDeleteDialog
|
||||||
|
open={deleteDialogContent.visible}
|
||||||
|
onCancel={deleteDialogContent.onCancel}
|
||||||
|
onOk={deleteDialogContent.onOk}
|
||||||
|
title={deleteDialogContent.title}
|
||||||
|
content={{
|
||||||
|
node: (
|
||||||
|
<ConfirmDeleteDialogNode
|
||||||
|
name={deleteDialogContent.name}
|
||||||
|
warnText={deleteDialogContent.warnText}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { FormLayout } from '@/constants/form';
|
import { FormLayout } from '@/constants/form';
|
||||||
|
import { useFetchTenantInfo } from '@/hooks/use-user-setting-request';
|
||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
@ -33,6 +34,7 @@ const FormId = 'dataset-creating-form';
|
|||||||
|
|
||||||
export function InputForm({ onOk }: IModalProps<any>) {
|
export function InputForm({ onOk }: IModalProps<any>) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { data: tenantInfo } = useFetchTenantInfo();
|
||||||
|
|
||||||
const FormSchema = z
|
const FormSchema = z
|
||||||
.object({
|
.object({
|
||||||
@ -80,7 +82,7 @@ export function InputForm({ onOk }: IModalProps<any>) {
|
|||||||
name: '',
|
name: '',
|
||||||
parseType: 1,
|
parseType: 1,
|
||||||
parser_id: '',
|
parser_id: '',
|
||||||
embd_id: '',
|
embd_id: tenantInfo?.embd_id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -263,7 +263,7 @@ export const documentFilter = (kb_id: string) =>
|
|||||||
export const getMetaDataService = ({ kb_id }: { kb_id: string }) =>
|
export const getMetaDataService = ({ kb_id }: { kb_id: string }) =>
|
||||||
request.post(api.getMetaData, { data: { kb_id } });
|
request.post(api.getMetaData, { data: { kb_id } });
|
||||||
export const updateMetaData = ({ kb_id, data }: { kb_id: string; data: any }) =>
|
export const updateMetaData = ({ kb_id, data }: { kb_id: string; data: any }) =>
|
||||||
request.post(api.updateMetaData, { data: { kb_id, data } });
|
request.post(api.updateMetaData, { data: { kb_id, ...data } });
|
||||||
|
|
||||||
export const listDataPipelineLogDocument = (
|
export const listDataPipelineLogDocument = (
|
||||||
params?: IFetchKnowledgeListRequestParams,
|
params?: IFetchKnowledgeListRequestParams,
|
||||||
|
|||||||
Reference in New Issue
Block a user