mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-01-04 03:25:30 +08:00
Compare commits
17 Commits
9b026fc5b6
...
9433f64fe2
| Author | SHA1 | Date | |
|---|---|---|---|
| 9433f64fe2 | |||
| d7c9611d45 | |||
| 79399f7f25 | |||
| 23522f1ea8 | |||
| 46dc3f1c48 | |||
| c9b156fa6d | |||
| 83939b1a63 | |||
| 7f08ba47d7 | |||
| ce3dd019c3 | |||
| 476c56868d | |||
| b9c4954c2f | |||
| a060672b31 | |||
| f022504ef9 | |||
| 1a78b8b295 | |||
| 017dd85ccf | |||
| 4c7b2ef46e | |||
| 597d88bf9a |
@ -165,7 +165,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 = ""
|
||||
for delta_ans, tk in self._react_with_tools_streamly(msg, use_tools):
|
||||
for delta_ans, tk in self._react_with_tools_streamly(prompt, msg, use_tools):
|
||||
ans += delta_ans
|
||||
|
||||
if ans.find("**ERROR**") >= 0:
|
||||
@ -185,7 +185,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 = []
|
||||
for delta_ans,_ in self._react_with_tools_streamly(msg, use_tools):
|
||||
for delta_ans,_ in self._react_with_tools_streamly(prompt, msg, use_tools):
|
||||
if delta_ans.find("**ERROR**") >= 0:
|
||||
if self.get_exception_default_value():
|
||||
self.set_output("content", self.get_exception_default_value())
|
||||
@ -208,7 +208,7 @@ class Agent(LLM, ToolBase):
|
||||
]):
|
||||
yield delta_ans
|
||||
|
||||
def _react_with_tools_streamly(self, history: list[dict], use_tools):
|
||||
def _react_with_tools_streamly(self, prompt, history: list[dict], use_tools):
|
||||
token_count = 0
|
||||
tool_metas = self.tool_meta
|
||||
hist = deepcopy(history)
|
||||
@ -221,7 +221,7 @@ class Agent(LLM, ToolBase):
|
||||
|
||||
def use_tool(name, args):
|
||||
nonlocal hist, use_tools, token_count,last_calling,user_request
|
||||
print(f"{last_calling=} == {name=}", )
|
||||
logging.info(f"{last_calling=} == {name=}")
|
||||
# Summarize of function calling
|
||||
#if all([
|
||||
# isinstance(self.toolcall_session.get_tool_obj(name), Agent),
|
||||
@ -275,7 +275,7 @@ class Agent(LLM, ToolBase):
|
||||
else:
|
||||
hist.append({"role": "user", "content": content})
|
||||
|
||||
task_desc = analyze_task(self.chat_mdl, user_request, tool_metas)
|
||||
task_desc = analyze_task(self.chat_mdl, prompt, user_request, tool_metas)
|
||||
self.callback("analyze_task", {}, task_desc)
|
||||
for _ in range(self._param.max_rounds + 1):
|
||||
response, tk = next_step(self.chat_mdl, hist, tool_metas, task_desc)
|
||||
|
||||
@ -17,7 +17,7 @@ import base64
|
||||
import logging
|
||||
import os
|
||||
from abc import ABC
|
||||
from enum import StrEnum
|
||||
from strenum import StrEnum
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from agent.tools.base import ToolParamBase, ToolBase, ToolMeta
|
||||
|
||||
@ -139,7 +139,7 @@ def create(tenant_id):
|
||||
res["llm"] = res.pop("llm_setting")
|
||||
res["llm"]["model_name"] = res.pop("llm_id")
|
||||
del res["kb_ids"]
|
||||
res["dataset_ids"] = req["dataset_ids"]
|
||||
res["dataset_ids"] = req.get("dataset_ids", [])
|
||||
res["avatar"] = res.pop("icon")
|
||||
return get_result(data=res)
|
||||
|
||||
|
||||
@ -63,7 +63,7 @@ docker build -t sandbox-executor-manager:latest ./executor_manager
|
||||
3. Add the following entry to your /etc/hosts file to resolve the executor manager service:
|
||||
|
||||
```bash
|
||||
127.0.0.1 sandbox-executor-manager
|
||||
127.0.0.1 es01 infinity mysql minio redis sandbox-executor-manager
|
||||
```
|
||||
|
||||
4. Start the RAGFlow service as usual.
|
||||
|
||||
@ -9,8 +9,8 @@ Key features, improvements and bug fixes in the latest releases.
|
||||
|
||||
:::info
|
||||
Each RAGFlow release is available in two editions:
|
||||
- **Slim edition**: excludes built-in embedding models and is identified by a **-slim** suffix added to the version name. Example: `infiniflow/ragflow:v0.19.1-slim`
|
||||
- **Full edition**: includes built-in embedding models and has no suffix added to the version name. Example: `infiniflow/ragflow:v0.19.1`
|
||||
- **Slim edition**: excludes built-in embedding models and is identified by a **-slim** suffix added to the version name. Example: `infiniflow/ragflow:v0.20.1-slim`
|
||||
- **Full edition**: includes built-in embedding models and has no suffix added to the version name. Example: `infiniflow/ragflow:v0.20.1`
|
||||
:::
|
||||
|
||||
:::danger IMPORTANT
|
||||
@ -33,10 +33,10 @@ Released on August 8, 2025.
|
||||
|
||||
### Added Models
|
||||
|
||||
- ChatGPT 5
|
||||
- GPT-5
|
||||
- Claude 4.1
|
||||
|
||||
### New agent Templates (both workflow and agentic)
|
||||
### New agent templates (both workflow and agentic)
|
||||
|
||||
- SQL Assistant Workflow: Empowers non-technical teams (e.g., operations, product) to independently query business data.
|
||||
- Choose Your Knowledge Base Workflow: Lets users select a knowledge base to query during conversations. [#9325](https://github.com/infiniflow/ragflow/pull/9325)
|
||||
|
||||
@ -180,7 +180,7 @@ async def list_tools(*, connector) -> list[types.Tool]:
|
||||
return [
|
||||
types.Tool(
|
||||
name="ragflow_retrieval",
|
||||
description="Retrieve relevant chunks from the RAGFlow retrieve interface based on the question, using the specified dataset_ids and optionally document_ids. Below is the list of all available datasets, including their descriptions and IDs. If you're unsure which datasets are relevant to the question, simply pass all dataset IDs to the function."
|
||||
description="Retrieve relevant chunks from the RAGFlow retrieve interface based on the question. You can optionally specify dataset_ids to search only specific datasets, or omit dataset_ids entirely to search across ALL available datasets. You can also optionally specify document_ids to search within specific documents. When dataset_ids is not provided or is empty, the system will automatically search across all available datasets. Below is the list of all available datasets, including their descriptions and IDs:"
|
||||
+ dataset_description,
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
@ -188,14 +188,16 @@ async def list_tools(*, connector) -> list[types.Tool]:
|
||||
"dataset_ids": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional array of dataset IDs to search. If not provided or empty, all datasets will be searched."
|
||||
},
|
||||
"document_ids": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "Optional array of document IDs to search within."
|
||||
},
|
||||
"question": {"type": "string"},
|
||||
"question": {"type": "string", "description": "The question or query to search for."},
|
||||
},
|
||||
"required": ["dataset_ids", "question"],
|
||||
"required": ["question"],
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -206,8 +208,26 @@ async def list_tools(*, connector) -> list[types.Tool]:
|
||||
async def call_tool(name: str, arguments: dict, *, connector) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
||||
if name == "ragflow_retrieval":
|
||||
document_ids = arguments.get("document_ids", [])
|
||||
dataset_ids = arguments.get("dataset_ids", [])
|
||||
|
||||
# If no dataset_ids provided or empty list, get all available dataset IDs
|
||||
if not dataset_ids:
|
||||
dataset_list_str = connector.list_datasets()
|
||||
dataset_ids = []
|
||||
|
||||
# Parse the dataset list to extract IDs
|
||||
if dataset_list_str:
|
||||
for line in dataset_list_str.strip().split('\n'):
|
||||
if line.strip():
|
||||
try:
|
||||
dataset_info = json.loads(line.strip())
|
||||
dataset_ids.append(dataset_info["id"])
|
||||
except (json.JSONDecodeError, KeyError):
|
||||
# Skip malformed lines
|
||||
continue
|
||||
|
||||
return connector.retrieval(
|
||||
dataset_ids=arguments["dataset_ids"],
|
||||
dataset_ids=dataset_ids,
|
||||
document_ids=document_ids,
|
||||
question=arguments["question"],
|
||||
)
|
||||
|
||||
@ -226,17 +226,20 @@ class Docx(DocxParser):
|
||||
for r in tb.rows:
|
||||
html += "<tr>"
|
||||
i = 0
|
||||
while i < len(r.cells):
|
||||
span = 1
|
||||
c = r.cells[i]
|
||||
for j in range(i + 1, len(r.cells)):
|
||||
if c.text == r.cells[j].text:
|
||||
span += 1
|
||||
i = j
|
||||
else:
|
||||
break
|
||||
i += 1
|
||||
html += f"<td>{c.text}</td>" if span == 1 else f"<td colspan='{span}'>{c.text}</td>"
|
||||
try:
|
||||
while i < len(r.cells):
|
||||
span = 1
|
||||
c = r.cells[i]
|
||||
for j in range(i + 1, len(r.cells)):
|
||||
if c.text == r.cells[j].text:
|
||||
span += 1
|
||||
i = j
|
||||
else:
|
||||
break
|
||||
i += 1
|
||||
html += f"<td>{c.text}</td>" if span == 1 else f"<td colspan='{span}'>{c.text}</td>"
|
||||
except Exception as e:
|
||||
logging.warning(f"Error parsing table, ignore: {e}")
|
||||
html += "</tr>"
|
||||
html += "</table>"
|
||||
tbls.append(((None, html), ""))
|
||||
|
||||
194
rag/app/table.py
194
rag/app/table.py
@ -40,7 +40,6 @@ class Excel(ExcelParser):
|
||||
total = 0
|
||||
for sheetname in wb.sheetnames:
|
||||
total += len(list(wb[sheetname].rows))
|
||||
|
||||
res, fails, done = [], [], 0
|
||||
rn = 0
|
||||
for sheetname in wb.sheetnames:
|
||||
@ -48,31 +47,204 @@ class Excel(ExcelParser):
|
||||
rows = list(ws.rows)
|
||||
if not rows:
|
||||
continue
|
||||
headers = [cell.value for cell in rows[0]]
|
||||
missed = set([i for i, h in enumerate(headers) if h is None])
|
||||
headers = [cell.value for i, cell in enumerate(rows[0]) if i not in missed]
|
||||
headers, header_rows = self._parse_headers(ws, rows)
|
||||
if not headers:
|
||||
continue
|
||||
data = []
|
||||
for i, r in enumerate(rows[1:]):
|
||||
for i, r in enumerate(rows[header_rows:]):
|
||||
rn += 1
|
||||
if rn - 1 < from_page:
|
||||
continue
|
||||
if rn - 1 >= to_page:
|
||||
break
|
||||
row = [cell.value for ii, cell in enumerate(r) if ii not in missed]
|
||||
if len(row) != len(headers):
|
||||
row_data = self._extract_row_data(ws, r, header_rows + i, len(headers))
|
||||
if row_data is None:
|
||||
fails.append(str(i))
|
||||
continue
|
||||
data.append(row)
|
||||
if self._is_empty_row(row_data):
|
||||
continue
|
||||
data.append(row_data)
|
||||
done += 1
|
||||
if np.array(data).size == 0:
|
||||
if len(data) == 0:
|
||||
continue
|
||||
res.append(pd.DataFrame(np.array(data), columns=headers))
|
||||
|
||||
df = pd.DataFrame(data, columns=headers)
|
||||
res.append(df)
|
||||
callback(0.3, ("Extract records: {}~{}".format(from_page + 1, min(to_page, from_page + rn)) + (f"{len(fails)} failure, line: %s..." % (",".join(fails[:3])) if fails else "")))
|
||||
return res
|
||||
|
||||
def _parse_headers(self, ws, rows):
|
||||
if len(rows) == 0:
|
||||
return [], 0
|
||||
has_complex_structure = self._has_complex_header_structure(ws, rows)
|
||||
if has_complex_structure:
|
||||
return self._parse_multi_level_headers(ws, rows)
|
||||
else:
|
||||
return self._parse_simple_headers(rows)
|
||||
|
||||
def _has_complex_header_structure(self, ws, rows):
|
||||
if len(rows) < 1:
|
||||
return False
|
||||
merged_ranges = list(ws.merged_cells.ranges)
|
||||
# 检查前两行是否涉及合并单元格
|
||||
for rng in merged_ranges:
|
||||
if rng.min_row <= 2: # 只要合并区域涉及第1或第2行
|
||||
return True
|
||||
return False
|
||||
|
||||
def _row_looks_like_header(self, row):
|
||||
header_like_cells = 0
|
||||
data_like_cells = 0
|
||||
non_empty_cells = 0
|
||||
for cell in row:
|
||||
if cell.value is not None:
|
||||
non_empty_cells += 1
|
||||
val = str(cell.value).strip()
|
||||
if self._looks_like_header(val):
|
||||
header_like_cells += 1
|
||||
elif self._looks_like_data(val):
|
||||
data_like_cells += 1
|
||||
if non_empty_cells == 0:
|
||||
return False
|
||||
return header_like_cells >= data_like_cells
|
||||
|
||||
def _parse_simple_headers(self, rows):
|
||||
if not rows:
|
||||
return [], 0
|
||||
header_row = rows[0]
|
||||
headers = []
|
||||
for cell in header_row:
|
||||
if cell.value is not None:
|
||||
header_value = str(cell.value).strip()
|
||||
if header_value:
|
||||
headers.append(header_value)
|
||||
else:
|
||||
pass
|
||||
final_headers = []
|
||||
for i, cell in enumerate(header_row):
|
||||
if cell.value is not None:
|
||||
header_value = str(cell.value).strip()
|
||||
if header_value:
|
||||
final_headers.append(header_value)
|
||||
else:
|
||||
final_headers.append(f"Column_{i + 1}")
|
||||
else:
|
||||
final_headers.append(f"Column_{i + 1}")
|
||||
return final_headers, 1
|
||||
|
||||
def _parse_multi_level_headers(self, ws, rows):
|
||||
if len(rows) < 2:
|
||||
return [], 0
|
||||
header_rows = self._detect_header_rows(rows)
|
||||
if header_rows == 1:
|
||||
return self._parse_simple_headers(rows)
|
||||
else:
|
||||
return self._build_hierarchical_headers(ws, rows, header_rows), header_rows
|
||||
|
||||
def _detect_header_rows(self, rows):
|
||||
if len(rows) < 2:
|
||||
return 1
|
||||
header_rows = 1
|
||||
max_check_rows = min(5, len(rows))
|
||||
for i in range(1, max_check_rows):
|
||||
row = rows[i]
|
||||
if self._row_looks_like_header(row):
|
||||
header_rows = i + 1
|
||||
else:
|
||||
break
|
||||
return header_rows
|
||||
|
||||
def _looks_like_header(self, value):
|
||||
if len(value) < 1:
|
||||
return False
|
||||
if any(ord(c) > 127 for c in value):
|
||||
return True
|
||||
if len([c for c in value if c.isalpha()]) >= 2:
|
||||
return True
|
||||
if any(c in value for c in ["(", ")", ":", ":", "(", ")", "_", "-"]):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _looks_like_data(self, value):
|
||||
if len(value) == 1 and value.upper() in ["Y", "N", "M", "X", "/", "-"]:
|
||||
return True
|
||||
if value.replace(".", "").replace("-", "").replace(",", "").isdigit():
|
||||
return True
|
||||
if value.startswith("0x") and len(value) <= 10:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _build_hierarchical_headers(self, ws, rows, header_rows):
|
||||
headers = []
|
||||
max_col = max(len(row) for row in rows[:header_rows]) if header_rows > 0 else 0
|
||||
merged_ranges = list(ws.merged_cells.ranges)
|
||||
for col_idx in range(max_col):
|
||||
header_parts = []
|
||||
for row_idx in range(header_rows):
|
||||
if col_idx < len(rows[row_idx]):
|
||||
cell_value = rows[row_idx][col_idx].value
|
||||
merged_value = self._get_merged_cell_value(ws, row_idx + 1, col_idx + 1, merged_ranges)
|
||||
if merged_value is not None:
|
||||
cell_value = merged_value
|
||||
if cell_value is not None:
|
||||
cell_value = str(cell_value).strip()
|
||||
if cell_value and cell_value not in header_parts and self._is_valid_header_part(cell_value):
|
||||
header_parts.append(cell_value)
|
||||
if header_parts:
|
||||
header = "-".join(header_parts)
|
||||
headers.append(header)
|
||||
else:
|
||||
headers.append(f"Column_{col_idx + 1}")
|
||||
final_headers = [h for h in headers if h and h != "-"]
|
||||
return final_headers
|
||||
|
||||
def _is_valid_header_part(self, value):
|
||||
if len(value) == 1 and value.upper() in ["Y", "N", "M", "X"]:
|
||||
return False
|
||||
if value.replace(".", "").replace("-", "").replace(",", "").isdigit():
|
||||
return False
|
||||
if value in ["/", "-", "+", "*", "="]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _get_merged_cell_value(self, ws, row, col, merged_ranges):
|
||||
for merged_range in merged_ranges:
|
||||
if merged_range.min_row <= row <= merged_range.max_row and merged_range.min_col <= col <= merged_range.max_col:
|
||||
return ws.cell(merged_range.min_row, merged_range.min_col).value
|
||||
return None
|
||||
|
||||
def _extract_row_data(self, ws, row, absolute_row_idx, expected_cols):
|
||||
row_data = []
|
||||
merged_ranges = list(ws.merged_cells.ranges)
|
||||
actual_row_num = absolute_row_idx + 1
|
||||
for col_idx in range(expected_cols):
|
||||
cell_value = None
|
||||
actual_col_num = col_idx + 1
|
||||
try:
|
||||
cell_value = ws.cell(row=actual_row_num, column=actual_col_num).value
|
||||
except ValueError:
|
||||
if col_idx < len(row):
|
||||
cell_value = row[col_idx].value
|
||||
if cell_value is None:
|
||||
merged_value = self._get_merged_cell_value(ws, actual_row_num, actual_col_num, merged_ranges)
|
||||
if merged_value is not None:
|
||||
cell_value = merged_value
|
||||
else:
|
||||
cell_value = self._get_inherited_value(ws, actual_row_num, actual_col_num, merged_ranges)
|
||||
row_data.append(cell_value)
|
||||
return row_data
|
||||
|
||||
def _get_inherited_value(self, ws, row, col, merged_ranges):
|
||||
for merged_range in merged_ranges:
|
||||
if merged_range.min_row <= row <= merged_range.max_row and merged_range.min_col <= col <= merged_range.max_col:
|
||||
return ws.cell(merged_range.min_row, merged_range.min_col).value
|
||||
return None
|
||||
|
||||
def _is_empty_row(self, row_data):
|
||||
for val in row_data:
|
||||
if val is not None and str(val).strip() != "":
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def trans_datatime(s):
|
||||
try:
|
||||
|
||||
@ -4,6 +4,9 @@ Task: {{ task }}
|
||||
|
||||
Context: {{ context }}
|
||||
|
||||
**Agent Prompt**
|
||||
{{ agent_prompt }}
|
||||
|
||||
**Analysis Requirements:**
|
||||
1. Is it just a small talk? (If yes, no further plan or analysis is needed)
|
||||
2. What is the core objective of the task?
|
||||
|
||||
@ -335,13 +335,13 @@ def form_history(history, limit=-6):
|
||||
return context
|
||||
|
||||
|
||||
def analyze_task(chat_mdl, task_name, tools_description: list[dict]):
|
||||
def analyze_task(chat_mdl, prompt, task_name, tools_description: list[dict]):
|
||||
tools_desc = tool_schema(tools_description)
|
||||
context = ""
|
||||
|
||||
template = PROMPT_JINJA_ENV.from_string(ANALYZE_TASK_USER)
|
||||
|
||||
kwd = chat_mdl.chat(ANALYZE_TASK_SYSTEM,[{"role": "user", "content": template.render(task=task_name, context=context, tools_desc=tools_desc)}], {})
|
||||
context = template.render(task=task_name, context=context, agent_prompt=prompt, tools_desc=tools_desc)
|
||||
kwd = chat_mdl.chat(ANALYZE_TASK_SYSTEM,[{"role": "user", "content": context}], {})
|
||||
if isinstance(kwd, tuple):
|
||||
kwd = kwd[0]
|
||||
kwd = re.sub(r"^.*</think>", "", kwd, flags=re.DOTALL)
|
||||
|
||||
@ -304,7 +304,11 @@ async def build_chunks(task, progress_callback):
|
||||
converted_image = d["image"].convert("RGB")
|
||||
d["image"].close() # Close original image
|
||||
d["image"] = converted_image
|
||||
d["image"].save(output_buffer, format='JPEG')
|
||||
try:
|
||||
d["image"].save(output_buffer, format='JPEG')
|
||||
except OSError as e:
|
||||
logging.warning(
|
||||
"Saving image of chunk {}/{}/{} got exception, ignore: {}".format(task["location"], task["name"], d["id"], str(e)))
|
||||
|
||||
async with minio_limiter:
|
||||
await trio.to_thread.run_sync(lambda: STORAGE_IMPL.put(task["kb_id"], d["id"], output_buffer.getvalue()))
|
||||
|
||||
@ -24,7 +24,6 @@ class Chat(Base):
|
||||
self.id = ""
|
||||
self.name = "assistant"
|
||||
self.avatar = "path/to/avatar"
|
||||
self.dataset_ids = ["kb1"]
|
||||
self.llm = Chat.LLM(rag, {})
|
||||
self.prompt = Chat.Prompt(rag, {})
|
||||
super().__init__(rag, res_dict)
|
||||
|
||||
@ -151,4 +151,4 @@ class TestUpdatedChunk:
|
||||
|
||||
with pytest.raises(Exception) as excinfo:
|
||||
chunks[0].update({})
|
||||
assert f"Can't find this chunk {chunks[0].id}" in str(excinfo.value), str(excinfo.value)
|
||||
assert f"You don't own the document {chunks[0].document_id}" in str(excinfo.value), str(excinfo.value)
|
||||
|
||||
@ -693,6 +693,7 @@ class TestDatasetUpdate:
|
||||
client,
|
||||
{
|
||||
"raptor": {"use_raptor": False},
|
||||
"graphrag": {"use_graphrag": False},
|
||||
},
|
||||
)
|
||||
dataset.update({"chunk_method": "qa"})
|
||||
@ -708,6 +709,7 @@ class TestDatasetUpdate:
|
||||
client,
|
||||
{
|
||||
"raptor": {"use_raptor": False},
|
||||
"graphrag": {"use_graphrag": False},
|
||||
},
|
||||
)
|
||||
dataset.update({"chunk_method": "qa", "parser_config": None})
|
||||
|
||||
@ -1,5 +1,43 @@
|
||||
// .eslintrc.js
|
||||
module.exports = {
|
||||
// Umi 项目
|
||||
extends: [require.resolve('umi/eslint'), 'plugin:react-hooks/recommended'],
|
||||
plugins: ['check-file'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-use-before-define': [
|
||||
'warn',
|
||||
{
|
||||
functions: false,
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
'check-file/filename-naming-convention': [
|
||||
'error',
|
||||
{
|
||||
'**/*.{jsx,tsx}': 'KEBAB_CASE',
|
||||
'**/*.{js,ts}': 'KEBAB_CASE',
|
||||
},
|
||||
],
|
||||
'check-file/folder-naming-convention': [
|
||||
'error',
|
||||
{
|
||||
'src/**/': 'KEBAB_CASE',
|
||||
'mocks/*/': 'KEBAB_CASE',
|
||||
},
|
||||
],
|
||||
'react/no-unescaped-entities': [
|
||||
'warn',
|
||||
{
|
||||
forbid: [
|
||||
{
|
||||
char: "'",
|
||||
alternatives: [''', '''],
|
||||
},
|
||||
{
|
||||
char: '"',
|
||||
alternatives: ['"', '"'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import TerserPlugin from 'terser-webpack-plugin';
|
||||
import { defineConfig } from 'umi';
|
||||
import { appName } from './src/conf.json';
|
||||
import routes from './src/routes';
|
||||
const ESLintPlugin = require('eslint-webpack-plugin');
|
||||
|
||||
export default defineConfig({
|
||||
title: appName,
|
||||
@ -52,6 +53,15 @@ export default defineConfig({
|
||||
|
||||
memo.optimization.minimizer('terser').use(TerserPlugin); // Fixed the issue that the page displayed an error after packaging lexical with terser
|
||||
|
||||
memo.plugin('eslint').use(ESLintPlugin, [
|
||||
{
|
||||
extensions: ['js', 'ts', 'tsx'],
|
||||
failOnError: true,
|
||||
exclude: ['**/node_modules/**', '**/mfsu**', '**/mfsu-virtual-entry**'],
|
||||
files: ['src/**/*.{js,ts,tsx}'],
|
||||
},
|
||||
]);
|
||||
|
||||
return memo;
|
||||
},
|
||||
tailwindcss: {},
|
||||
|
||||
179
web/package-lock.json
generated
179
web/package-lock.json
generated
@ -121,6 +121,8 @@
|
||||
"@umijs/plugins": "^4.1.0",
|
||||
"@welldone-software/why-did-you-render": "^8.0.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint-plugin-check-file": "^2.8.0",
|
||||
"eslint-webpack-plugin": "^4.1.0",
|
||||
"html-loader": "^5.1.0",
|
||||
"husky": "^9.0.11",
|
||||
"jest": "^29.7.0",
|
||||
@ -9147,9 +9149,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "8.56.1",
|
||||
"resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-8.56.1.tgz",
|
||||
"integrity": "sha512-18PLWRzhy9glDQp3+wOgfLYRWlhgX0azxgJ63rdpoUHyrC9z0f5CkFburjQx4uD7ZCruw85ZtMt6K+L+R8fLJQ==",
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-9.6.1.tgz",
|
||||
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
@ -16334,6 +16337,27 @@
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-check-file": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-plugin-check-file/-/eslint-plugin-check-file-2.8.0.tgz",
|
||||
"integrity": "sha512-FvvafMTam2WJYH9uj+FuMxQ1y+7jY3Z6P9T4j2214cH0FBxNzTcmeCiGTj1Lxp3mI6kbbgsXvmgewvf+llKYyw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.3",
|
||||
"micromatch": "^4.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "ko_fi",
|
||||
"url": "https://ko-fi.com/huanluo"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.28.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-jest": {
|
||||
"version": "27.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-plugin-jest/-/eslint-plugin-jest-27.2.3.tgz",
|
||||
@ -16437,6 +16461,141 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-webpack-plugin": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/eslint-webpack-plugin/-/eslint-webpack-plugin-4.1.0.tgz",
|
||||
"integrity": "sha512-C3wAG2jyockIhN0YRLuKieKj2nx/gnE/VHmoHemD5ifnAtY6ZU+jNPfzPoX4Zd6RIbUyWTiZUh/ofUlBhoAX7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint": "^8.56.5",
|
||||
"jest-worker": "^29.7.0",
|
||||
"micromatch": "^4.0.5",
|
||||
"normalize-path": "^3.0.0",
|
||||
"schema-utils": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.15.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.0.0",
|
||||
"webpack": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-webpack-plugin/node_modules/@types/eslint": {
|
||||
"version": "8.56.12",
|
||||
"resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-8.56.12.tgz",
|
||||
"integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-webpack-plugin/node_modules/ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-webpack-plugin/node_modules/ajv-keywords": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
|
||||
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ajv": "^8.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-webpack-plugin/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-webpack-plugin/node_modules/jest-worker": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/jest-worker/-/jest-worker-29.7.0.tgz",
|
||||
"integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"jest-util": "^29.7.0",
|
||||
"merge-stream": "^2.0.0",
|
||||
"supports-color": "^8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-webpack-plugin/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/eslint-webpack-plugin/node_modules/schema-utils": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.2.tgz",
|
||||
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"ajv": "^8.9.0",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"ajv-keywords": "^5.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-webpack-plugin/node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
@ -17222,9 +17381,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.2.9",
|
||||
"resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.2.9.tgz",
|
||||
"integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz",
|
||||
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
|
||||
"license": "ISC",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/flatten": {
|
||||
@ -24121,9 +24281,10 @@
|
||||
"integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w=="
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.7.tgz",
|
||||
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
|
||||
@ -132,6 +132,8 @@
|
||||
"@umijs/plugins": "^4.1.0",
|
||||
"@welldone-software/why-did-you-render": "^8.0.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint-plugin-check-file": "^2.8.0",
|
||||
"eslint-webpack-plugin": "^4.1.0",
|
||||
"html-loader": "^5.1.0",
|
||||
"husky": "^9.0.11",
|
||||
"jest": "^29.7.0",
|
||||
|
||||
@ -80,7 +80,6 @@ export function ChunkMethodDialog({
|
||||
hideModal,
|
||||
onOk,
|
||||
parserId,
|
||||
documentId,
|
||||
documentExtension,
|
||||
visible,
|
||||
parserConfig,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Form, FormInstance, Input, InputRef, Typography } from 'antd';
|
||||
import { omit } from 'lodash';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
|
||||
const EditableContext = React.createContext<FormInstance<any> | null>(null);
|
||||
@ -15,15 +16,12 @@ interface Item {
|
||||
address: string;
|
||||
}
|
||||
|
||||
export const EditableRow: React.FC<EditableRowProps> = ({
|
||||
index,
|
||||
...props
|
||||
}) => {
|
||||
export const EditableRow: React.FC<EditableRowProps> = ({ ...props }) => {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<Form form={form} component={false}>
|
||||
<EditableContext.Provider value={form}>
|
||||
<tr {...props} />
|
||||
<tr {...omit(props, 'index')} />
|
||||
</EditableContext.Provider>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@ -31,7 +31,7 @@ const HightLightMarkdown = ({
|
||||
components={
|
||||
{
|
||||
code(props: any) {
|
||||
const { children, className, node, ...rest } = props;
|
||||
const { children, className, ...rest } = props;
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return match ? (
|
||||
<SyntaxHighlighter
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { PromptIcon } from '@/assets/icon/Icon';
|
||||
import { PromptIcon } from '@/assets/icon/next-icon';
|
||||
import CopyToClipboard from '@/components/copy-to-clipboard';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
import { cn } from '@/lib/utils';
|
||||
import { currentReg, replaceTextByOldReg } from '@/pages/chat/utils';
|
||||
import classNames from 'classnames';
|
||||
import { omit } from 'lodash';
|
||||
import { pipe } from 'lodash/fp';
|
||||
import { CircleAlert } from 'lucide-react';
|
||||
import { Button } from '../ui/button';
|
||||
@ -256,11 +257,12 @@ function MarkdownContent({
|
||||
'custom-typography': ({ children }: { children: string }) =>
|
||||
renderReference(children),
|
||||
code(props: any) {
|
||||
const { children, className, node, ...rest } = props;
|
||||
const { children, className, ...rest } = props;
|
||||
const restProps = omit(rest, 'node');
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return match ? (
|
||||
<SyntaxHighlighter
|
||||
{...rest}
|
||||
{...restProps}
|
||||
PreTag="div"
|
||||
language={match[1]}
|
||||
wrapLongLines
|
||||
@ -268,7 +270,10 @@ function MarkdownContent({
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code {...rest} className={classNames(className, 'text-wrap')}>
|
||||
<code
|
||||
{...restProps}
|
||||
className={classNames(className, 'text-wrap')}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { PromptIcon } from '@/assets/icon/Icon';
|
||||
import { PromptIcon } from '@/assets/icon/next-icon';
|
||||
import CopyToClipboard from '@/components/copy-to-clipboard';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||
|
||||
@ -17,7 +17,7 @@ import { IRegenerateMessage, IRemoveMessageById } from '@/hooks/logic-hooks';
|
||||
import { INodeEvent, MessageEventType } from '@/hooks/use-send-message';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { AgentChatContext } from '@/pages/agent/context';
|
||||
import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workFlowTimeline';
|
||||
import { WorkFlowTimeline } from '@/pages/agent/log-sheet/workflow-timeline';
|
||||
import { IMessage } from '@/pages/chat/interface';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Atom, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
|
||||
@ -124,7 +124,7 @@ interface TimelineIndicatorProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
}
|
||||
|
||||
function TimelineIndicator({
|
||||
asChild = false,
|
||||
// asChild = false,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { Input } from '@/components/originui/input';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { EyeIcon, EyeOffIcon } from 'lucide-react';
|
||||
import { ChangeEvent, LegacyRef, forwardRef, useId, useState } from 'react';
|
||||
import { ChangeEvent, forwardRef, useId, useState } from 'react';
|
||||
|
||||
type PropType = {
|
||||
name: string;
|
||||
@ -10,17 +9,12 @@ type PropType = {
|
||||
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||
};
|
||||
|
||||
function PasswordInput(
|
||||
props: PropType,
|
||||
ref: LegacyRef<HTMLInputElement> | undefined,
|
||||
) {
|
||||
function PasswordInput(props: PropType) {
|
||||
const id = useId();
|
||||
const [isVisible, setIsVisible] = useState<boolean>(false);
|
||||
|
||||
const toggleVisibility = () => setIsVisible((prevState) => !prevState);
|
||||
|
||||
const { t } = useTranslate('setting');
|
||||
|
||||
return (
|
||||
<div className="*:not-first:mt-2 w-full">
|
||||
{/* <Label htmlFor={id}>Show/hide password input</Label> */}
|
||||
|
||||
@ -23,8 +23,8 @@ const getColorForName = (name: string): { from: string; to: string } => {
|
||||
const hue = hash % 360;
|
||||
|
||||
return {
|
||||
from: `hsl(${hue}, 70%, 80%)`,
|
||||
to: `hsl(${hue}, 60%, 30%)`,
|
||||
to: `hsl(${hue}, 70%, 80%)`,
|
||||
from: `hsl(${hue}, 60%, 30%)`,
|
||||
};
|
||||
};
|
||||
export const RAGFlowAvatar = memo(
|
||||
|
||||
@ -39,7 +39,7 @@ const CommandInput = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
||||
<div className="flex items-center border-b px-3" data-cmdk-input-wrapper="">
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
ref={ref}
|
||||
|
||||
@ -34,6 +34,7 @@ export type MultiSelectOptionType = {
|
||||
label: React.ReactNode;
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
suffix?: React.ReactNode;
|
||||
icon?: React.ComponentType<{ className?: string }>;
|
||||
};
|
||||
|
||||
@ -54,23 +55,41 @@ function MultiCommandItem({
|
||||
return (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
onSelect={() => toggleOption(option.value)}
|
||||
className="cursor-pointer"
|
||||
onSelect={() => {
|
||||
if (option.disabled) return false;
|
||||
toggleOption(option.value);
|
||||
}}
|
||||
className={cn('cursor-pointer', {
|
||||
'cursor-not-allowed text-text-disabled': option.disabled,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
|
||||
isSelected
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'opacity-50 [&_svg]:invisible',
|
||||
isSelected ? 'bg-primary ' : 'opacity-50 [&_svg]:invisible',
|
||||
|
||||
{ 'text-primary-foreground': !option.disabled },
|
||||
{ 'text-text-disabled': option.disabled },
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</div>
|
||||
{option.icon && (
|
||||
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
<option.icon
|
||||
className={cn('mr-2 h-4 w-4 ', {
|
||||
'text-text-disabled': option.disabled,
|
||||
'text-muted-foreground': !option.disabled,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
<span className={cn({ 'text-text-disabled': option.disabled })}>
|
||||
{option.label}
|
||||
</span>
|
||||
{option.suffix && (
|
||||
<span className={cn({ 'text-text-disabled': option.disabled })}>
|
||||
{option.suffix}
|
||||
</span>
|
||||
)}
|
||||
<span>{option.label}</span>
|
||||
</CommandItem>
|
||||
);
|
||||
}
|
||||
@ -156,6 +175,11 @@ interface MultiSelectProps
|
||||
* Optional, can be used to add custom styles.
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* If true, renders the multi-select component with a select all option.
|
||||
*/
|
||||
showSelectAll?: boolean;
|
||||
}
|
||||
|
||||
export const MultiSelect = React.forwardRef<
|
||||
@ -172,8 +196,9 @@ export const MultiSelect = React.forwardRef<
|
||||
animation = 0,
|
||||
maxCount = 3,
|
||||
modalPopover = false,
|
||||
asChild = false,
|
||||
// asChild = false,
|
||||
className,
|
||||
showSelectAll = true,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
@ -340,23 +365,25 @@ export const MultiSelect = React.forwardRef<
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
key="all"
|
||||
onSelect={toggleAll}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
|
||||
selectedValues.length === flatOptions.length
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'opacity-50 [&_svg]:invisible',
|
||||
)}
|
||||
{showSelectAll && (
|
||||
<CommandItem
|
||||
key="all"
|
||||
onSelect={toggleAll}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</div>
|
||||
<span>(Select All)</span>
|
||||
</CommandItem>
|
||||
<div
|
||||
className={cn(
|
||||
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
|
||||
selectedValues.length === flatOptions.length
|
||||
? 'bg-primary text-primary-foreground'
|
||||
: 'opacity-50 [&_svg]:invisible',
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</div>
|
||||
<span>(Select All)</span>
|
||||
</CommandItem>
|
||||
)}
|
||||
{!options.some((x) => 'options' in x) &&
|
||||
(options as unknown as MultiSelectOptionType[]).map(
|
||||
(option) => {
|
||||
|
||||
@ -26,7 +26,7 @@ const SIDEBAR_WIDTH_MOBILE = '18rem';
|
||||
const SIDEBAR_WIDTH_ICON = '3rem';
|
||||
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
||||
|
||||
type SidebarContext = {
|
||||
type SidebarContextType = {
|
||||
state: 'expanded' | 'collapsed';
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
@ -36,7 +36,7 @@ type SidebarContext = {
|
||||
toggleSidebar: () => void;
|
||||
};
|
||||
|
||||
const SidebarContext = React.createContext<SidebarContext | null>(null);
|
||||
const SidebarContext = React.createContext<SidebarContextType | null>(null);
|
||||
|
||||
function useSidebar() {
|
||||
const context = React.useContext(SidebarContext);
|
||||
@ -116,7 +116,7 @@ const SidebarProvider = React.forwardRef<
|
||||
// This makes it easier to style the sidebar with Tailwind classes.
|
||||
const state = open ? 'expanded' : 'collapsed';
|
||||
|
||||
const contextValue = React.useMemo<SidebarContext>(
|
||||
const contextValue = React.useMemo<SidebarContextType>(
|
||||
() => ({
|
||||
state,
|
||||
open,
|
||||
@ -580,11 +580,8 @@ const SidebarMenuButton = React.forwardRef<
|
||||
return button;
|
||||
}
|
||||
|
||||
if (typeof tooltip === 'string') {
|
||||
tooltip = {
|
||||
children: tooltip,
|
||||
};
|
||||
}
|
||||
const tooltipContent =
|
||||
typeof tooltip === 'string' ? { children: tooltip } : tooltip;
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
@ -593,7 +590,7 @@ const SidebarMenuButton = React.forwardRef<
|
||||
side="right"
|
||||
align="center"
|
||||
hidden={state !== 'collapsed' || isMobile}
|
||||
{...tooltip}
|
||||
{...tooltipContent}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@ -27,7 +27,7 @@ import { useTranslate } from './common-hooks';
|
||||
import { useSetPaginationParams } from './route-hook';
|
||||
import { useFetchTenantInfo, useSaveSetting } from './user-setting-hooks';
|
||||
|
||||
function usePrevious<T>(value: T) {
|
||||
export function usePrevious<T>(value: T) {
|
||||
const ref = useRef<T>();
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
|
||||
@ -72,9 +72,12 @@ export const useNavigatePage = () => {
|
||||
navigate(Routes.Searches);
|
||||
}, [navigate]);
|
||||
|
||||
const navigateToSearch = useCallback(() => {
|
||||
navigate(Routes.Search);
|
||||
}, [navigate]);
|
||||
const navigateToSearch = useCallback(
|
||||
(id: string) => {
|
||||
navigate(`${Routes.Search}/${id}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToChunkParsedResult = useCallback(
|
||||
(id: string, knowledgeId?: string) => () => {
|
||||
|
||||
@ -2,11 +2,13 @@ import { FileUploadProps } from '@/components/file-upload';
|
||||
import message from '@/components/ui/message';
|
||||
import { AgentGlobals } from '@/constants/agent';
|
||||
import {
|
||||
DSL,
|
||||
IAgentLogsRequest,
|
||||
IAgentLogsResponse,
|
||||
IFlow,
|
||||
IFlowTemplate,
|
||||
ITraceData,
|
||||
} from '@/interfaces/database/agent';
|
||||
import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow';
|
||||
import { IDebugSingleRequestBody } from '@/interfaces/request/agent';
|
||||
import i18n from '@/locales/config';
|
||||
import { BeginId } from '@/pages/agent/constant';
|
||||
@ -122,7 +124,7 @@ export const useFetchAgentListByPage = () => {
|
||||
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||
|
||||
const { data, isFetching: loading } = useQuery<{
|
||||
kbs: IFlow[];
|
||||
canvas: IFlow[];
|
||||
total: number;
|
||||
}>({
|
||||
queryKey: [
|
||||
@ -132,7 +134,7 @@ export const useFetchAgentListByPage = () => {
|
||||
...pagination,
|
||||
},
|
||||
],
|
||||
initialData: { kbs: [], total: 0 },
|
||||
initialData: { canvas: [], total: 0 },
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await agentService.listCanvasTeam(
|
||||
@ -146,7 +148,7 @@ export const useFetchAgentListByPage = () => {
|
||||
true,
|
||||
);
|
||||
|
||||
return data?.data ?? [];
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
@ -159,7 +161,7 @@ export const useFetchAgentListByPage = () => {
|
||||
);
|
||||
|
||||
return {
|
||||
data: data.kbs,
|
||||
data: data.canvas,
|
||||
loading,
|
||||
searchString,
|
||||
handleInputChange: onInputChange,
|
||||
@ -366,16 +368,7 @@ export const useUploadCanvasFileWithProgress = (
|
||||
{
|
||||
url: api.uploadAgentFile(identifier || id),
|
||||
data: formData,
|
||||
onUploadProgress: ({
|
||||
loaded,
|
||||
total,
|
||||
progress,
|
||||
bytes,
|
||||
estimated,
|
||||
rate,
|
||||
upload,
|
||||
lengthComputable,
|
||||
}) => {
|
||||
onUploadProgress: ({ progress }) => {
|
||||
files.forEach((file) => {
|
||||
onProgress(file, (progress || 0) * 100);
|
||||
});
|
||||
|
||||
@ -5,7 +5,7 @@ import { IAskRequestBody } from '@/interfaces/request/chat';
|
||||
import { IClientConversation } from '@/pages/next-chats/chat/interface';
|
||||
import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message';
|
||||
import { isConversationIdExist } from '@/pages/next-chats/utils';
|
||||
import chatService from '@/services/next-chat-service ';
|
||||
import chatService from '@/services/next-chat-service';
|
||||
import { buildMessageListWithUuid, getConversationId } from '@/utils/chat';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useDebounce } from 'ahooks';
|
||||
|
||||
@ -73,6 +73,7 @@ export declare interface IFlow {
|
||||
user_id: string;
|
||||
permission: string;
|
||||
nickname: string;
|
||||
operator_permission: number;
|
||||
}
|
||||
|
||||
export interface IFlowTemplate {
|
||||
|
||||
@ -13,8 +13,7 @@ import { LanguageList, LanguageMap, ThemeEnum } from '@/constants/common';
|
||||
import { useChangeLanguage } from '@/hooks/logic-hooks';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useNavigateWithFromState } from '@/hooks/route-hook';
|
||||
import { useFetchUserInfo, useListTenant } from '@/hooks/user-setting-hooks';
|
||||
import { TenantRole } from '@/pages/user-setting/constants';
|
||||
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
||||
import { Routes } from '@/routes';
|
||||
import { camelCase } from 'lodash';
|
||||
import {
|
||||
@ -55,11 +54,11 @@ export function Header() {
|
||||
changeLanguage(key);
|
||||
};
|
||||
|
||||
const { data } = useListTenant();
|
||||
// const { data } = useListTenant();
|
||||
|
||||
const showBell = useMemo(() => {
|
||||
return data.some((x) => x.role === TenantRole.Invite);
|
||||
}, [data]);
|
||||
// const showBell = useMemo(() => {
|
||||
// return data.some((x) => x.role === TenantRole.Invite);
|
||||
// }, [data]);
|
||||
|
||||
const items = LanguageList.map((x) => ({
|
||||
key: x,
|
||||
@ -70,9 +69,9 @@ export function Header() {
|
||||
setTheme(theme === ThemeEnum.Dark ? ThemeEnum.Light : ThemeEnum.Dark);
|
||||
}, [setTheme, theme]);
|
||||
|
||||
const handleBellClick = useCallback(() => {
|
||||
navigate('/user-setting/team');
|
||||
}, [navigate]);
|
||||
// const handleBellClick = useCallback(() => {
|
||||
// navigate('/user-setting/team');
|
||||
// }, [navigate]);
|
||||
|
||||
const tagsData = useMemo(
|
||||
() => [
|
||||
|
||||
@ -12,6 +12,7 @@ import translation_ja from './ja';
|
||||
import translation_pt_br from './pt-br';
|
||||
import { createTranslationTable, flattenObject } from './until';
|
||||
import translation_vi from './vi';
|
||||
import translation_ru from './ru';
|
||||
import translation_zh from './zh';
|
||||
import translation_zh_traditional from './zh-traditional';
|
||||
|
||||
@ -23,12 +24,14 @@ const resources = {
|
||||
[LanguageAbbreviation.Ja]: translation_ja,
|
||||
[LanguageAbbreviation.Es]: translation_es,
|
||||
[LanguageAbbreviation.Vi]: translation_vi,
|
||||
[LanguageAbbreviation.Ru]: translation_ru,
|
||||
[LanguageAbbreviation.PtBr]: translation_pt_br,
|
||||
[LanguageAbbreviation.De]: translation_de,
|
||||
[LanguageAbbreviation.Fr]: translation_fr,
|
||||
};
|
||||
const enFlattened = flattenObject(translation_en);
|
||||
const viFlattened = flattenObject(translation_vi);
|
||||
const ruFlattened = flattenObject(translation_ru);
|
||||
const esFlattened = flattenObject(translation_es);
|
||||
const zhFlattened = flattenObject(translation_zh);
|
||||
const jaFlattened = flattenObject(translation_ja);
|
||||
@ -40,6 +43,7 @@ export const translationTable = createTranslationTable(
|
||||
[
|
||||
enFlattened,
|
||||
viFlattened,
|
||||
ruFlattened,
|
||||
esFlattened,
|
||||
zhFlattened,
|
||||
zh_traditionalFlattened,
|
||||
@ -51,6 +55,7 @@ export const translationTable = createTranslationTable(
|
||||
[
|
||||
'English',
|
||||
'Vietnamese',
|
||||
'Rus',
|
||||
'Spanish',
|
||||
'zh',
|
||||
'zh-TRADITIONAL',
|
||||
|
||||
@ -252,7 +252,7 @@ export default {
|
||||
book: `<p>Supported file formats are <b>DOCX</b>, <b>PDF</b>, <b>TXT</b>.</p><p>
|
||||
For each book in PDF, please set the <i>page ranges</i> to remove unwanted information and reduce analysis time.</p>`,
|
||||
laws: `<p>Supported file formats are <b>DOCX</b>, <b>PDF</b>, <b>TXT</b>.</p><p>
|
||||
Legal documents typically follow a rigorous writing format. We use text feature to identify split point.
|
||||
Legal documents typically follow a rigorous writing format. We use text feature to identify split point.
|
||||
</p><p>
|
||||
The chunk has a granularity consistent with 'ARTICLE', ensuring all upper level text is included in the chunk.
|
||||
</p>`,
|
||||
@ -266,7 +266,7 @@ export default {
|
||||
<li>Then, combine adjacent segments until the token count exceeds the threshold specified by 'Chunk token number for text', at which point a chunk is created.</li></p>`,
|
||||
paper: `<p>Only <b>PDF</b> file is supported.</p><p>
|
||||
Papers will be split by section, such as <i>abstract, 1.1, 1.2</i>. </p><p>
|
||||
This approach enables the LLM to summarize the paper more effectively and to provide more comprehensive, understandable responses.
|
||||
This approach enables the LLM to summarize the paper more effectively and to provide more comprehensive, understandable responses.
|
||||
However, it also increases the context for AI conversations and adds to the computational cost for the LLM. So during a conversation, consider reducing the value of ‘<b>topN</b>’.</p>`,
|
||||
presentation: `<p>Supported file formats are <b>PDF</b>, <b>PPTX</b>.</p><p>
|
||||
Every page in the slides is treated as a chunk, with its thumbnail image stored.</p><p>
|
||||
@ -670,13 +670,41 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
|
||||
bedrockSKMessage: 'Please input your SECRET KEY',
|
||||
bedrockRegion: 'AWS Region',
|
||||
bedrockRegionMessage: 'Please select!',
|
||||
'us-east-2': 'US East (Ohio)',
|
||||
'us-east-1': 'US East (N. Virginia)',
|
||||
'us-west-1': 'US West (N. California)',
|
||||
'us-west-2': 'US West (Oregon)',
|
||||
'af-south-1': 'Africa (Cape Town)',
|
||||
'ap-east-1': 'Asia Pacific (Hong Kong)',
|
||||
'ap-south-2': 'Asia Pacific (Hyderabad)',
|
||||
'ap-southeast-3': 'Asia Pacific (Jakarta)',
|
||||
'ap-southeast-5': 'Asia Pacific (Malaysia)',
|
||||
'ap-southeast-4': 'Asia Pacific (Melbourne)',
|
||||
'ap-south-1': 'Asia Pacific (Mumbai)',
|
||||
'ap-northeast-3': 'Asia Pacific (Osaka)',
|
||||
'ap-northeast-2': 'Asia Pacific (Seoul)',
|
||||
'ap-southeast-1': 'Asia Pacific (Singapore)',
|
||||
'ap-northeast-1': 'Asia Pacific (Tokyo)',
|
||||
'eu-central-1': 'Europe (Frankfurt)',
|
||||
'us-gov-west-1': 'AWS GovCloud (US-West)',
|
||||
'ap-southeast-2': 'Asia Pacific (Sydney)',
|
||||
'ap-east-2': 'Asia Pacific (Taipei)',
|
||||
'ap-southeast-7': 'Asia Pacific (Thailand)',
|
||||
'ap-northeast-1': 'Asia Pacific (Tokyo)',
|
||||
'ca-central-1': 'Canada (Central)',
|
||||
'ca-west-1': 'Canada West (Calgary)',
|
||||
'eu-central-1': 'Europe (Frankfurt)',
|
||||
'eu-west-1': 'Europe (Ireland)',
|
||||
'eu-west-2': 'Europe (London)',
|
||||
'eu-south-1': 'Europe (Milan)',
|
||||
'eu-west-3': 'Europe (Paris)',
|
||||
'eu-south-2': 'Europe (Spain)',
|
||||
'eu-north-1': 'Europe (Stockholm)',
|
||||
'eu-central-2': 'Europe (Zurich)',
|
||||
'il-central-1': 'Israel (Tel Aviv)',
|
||||
'mx-central-1': 'Mexico (Central)',
|
||||
'me-south-1': 'Middle East (Bahrain)',
|
||||
'me-central-1': 'Middle East (UAE)',
|
||||
'sa-east-1': 'South America (São Paulo)',
|
||||
'us-gov-east-1': 'AWS GovCloud (US-East)',
|
||||
'us-gov-west-1': 'AWS GovCloud (US-West)',
|
||||
addHunyuanSID: 'Hunyuan Secret ID',
|
||||
HunyuanSIDMessage: 'Please input your Secret ID',
|
||||
addHunyuanSK: 'Hunyuan Secret Key',
|
||||
|
||||
@ -203,7 +203,7 @@ export default {
|
||||
Karena buku panjang dan tidak semua bagian berguna, jika itu adalah PDF,
|
||||
silakan atur <i>rentang halaman</i> untuk setiap buku untuk menghilangkan efek negatif dan menghemat waktu komputasi untuk analisis.</p>`,
|
||||
laws: `<p>Format file yang didukung adalah <b>DOCX</b>, <b>PDF</b>, <b>TXT</b>.</p><p>
|
||||
Dokumen hukum memiliki format penulisan yang sangat ketat. Kami menggunakan fitur teks untuk mendeteksi titik pemisah.
|
||||
Dokumen hukum memiliki format penulisan yang sangat ketat. Kami menggunakan fitur teks untuk mendeteksi titik pemisah.
|
||||
</p><p>
|
||||
Granularitas potongan konsisten dengan 'ARTIKEL', dan semua teks tingkat atas akan disertakan dalam potongan.
|
||||
</p>`,
|
||||
@ -218,9 +218,9 @@ export default {
|
||||
<li>Selanjutnya, potongan berturut-turut ini digabungkan menjadi potongan yang jumlah tokennya tidak lebih dari 'Jumlah token'.</li></p>`,
|
||||
paper: `<p>Hanya file <b>PDF</b> yang didukung.</p><p>
|
||||
Jika model kami bekerja dengan baik, makalah akan dipotong berdasarkan bagiannya, seperti <i>abstrak, 1.1, 1.2</i>, dll. </p><p>
|
||||
Manfaat dari melakukan ini adalah LLM dapat lebih baik merangkum konten bagian yang relevan dalam makalah,
|
||||
menghasilkan jawaban yang lebih komprehensif yang membantu pembaca lebih memahami makalah.
|
||||
Kelemahannya adalah meningkatkan konteks percakapan LLM dan menambah biaya komputasi,
|
||||
Manfaat dari melakukan ini adalah LLM dapat lebih baik merangkum konten bagian yang relevan dalam makalah,
|
||||
menghasilkan jawaban yang lebih komprehensif yang membantu pembaca lebih memahami makalah.
|
||||
Kelemahannya adalah meningkatkan konteks percakapan LLM dan menambah biaya komputasi,
|
||||
jadi selama percakapan, Anda dapat mempertimbangkan untuk mengurangi pengaturan ‘<b>topN</b>’.</p>`,
|
||||
presentation: `<p>Format file yang didukung adalah <b>PDF</b>, <b>PPTX</b>.</p><p>
|
||||
Setiap halaman akan diperlakukan sebagai potongan. Dan thumbnail setiap halaman akan disimpan.</p><p>
|
||||
@ -249,7 +249,7 @@ export default {
|
||||
</p><p>
|
||||
Resume datang dalam berbagai format, seperti kepribadian seseorang, tetapi kita sering harus mengaturnya menjadi data terstruktur yang memudahkan pencarian.
|
||||
</p><p>
|
||||
Alih-alih memotong resume, kami memparsing resume menjadi data terstruktur. Sebagai HR, Anda dapat membuang semua resume yang Anda miliki,
|
||||
Alih-alih memotong resume, kami memparsing resume menjadi data terstruktur. Sebagai HR, Anda dapat membuang semua resume yang Anda miliki,
|
||||
maka Anda dapat mencantumkan semua kandidat yang memenuhi kualifikasi hanya dengan berbicara dengan <i>'assistxsuite'</i>.
|
||||
</p>
|
||||
`,
|
||||
@ -283,11 +283,11 @@ export default {
|
||||
Jika Anda ingin merangkum sesuatu yang membutuhkan semua konteks dari sebuah artikel dan panjang konteks LLM yang dipilih mencakup panjang dokumen, Anda dapat mencoba metode ini.
|
||||
</p>`,
|
||||
knowledgeGraph: `<p>Format file yang didukung adalah <b>DOCX, EXCEL, PPT, IMAGE, PDF, TXT, MD, JSON, EML</b>
|
||||
|
||||
|
||||
<p>Setelah file dipotong, digunakan potongan untuk mengekstrak grafik pengetahuan dan peta pikiran dari seluruh dokumen. Metode ini menerapkan cara naif untuk memotong file:
|
||||
Teks berturut-turut akan dipotong menjadi potongan masing-masing yang berjumlah sekitar 512 token.</p>
|
||||
<p>Selanjutnya, potongan akan dikirim ke LLM untuk mengekstrak node dan hubungan dari grafik pengetahuan, dan peta pikiran.</p>
|
||||
|
||||
|
||||
Perhatikan jenis entitas yang perlu Anda tentukan.</p>`,
|
||||
useRaptor: 'Gunakan RAPTOR untuk meningkatkan pengambilan',
|
||||
useRaptorTip:
|
||||
@ -558,13 +558,41 @@ export default {
|
||||
bedrockSKMessage: 'Silakan masukkan SECRET KEY Anda',
|
||||
bedrockRegion: 'Wilayah AWS',
|
||||
bedrockRegionMessage: 'Silakan pilih!',
|
||||
'us-east-2': 'US East (Ohio)',
|
||||
'us-east-1': 'US East (N. Virginia)',
|
||||
'us-west-1': 'US West (N. California)',
|
||||
'us-west-2': 'US West (Oregon)',
|
||||
'af-south-1': 'Africa (Cape Town)',
|
||||
'ap-east-1': 'Asia Pacific (Hong Kong)',
|
||||
'ap-south-2': 'Asia Pacific (Hyderabad)',
|
||||
'ap-southeast-3': 'Asia Pacific (Jakarta)',
|
||||
'ap-southeast-5': 'Asia Pacific (Malaysia)',
|
||||
'ap-southeast-4': 'Asia Pacific (Melbourne)',
|
||||
'ap-south-1': 'Asia Pacific (Mumbai)',
|
||||
'ap-northeast-3': 'Asia Pacific (Osaka)',
|
||||
'ap-northeast-2': 'Asia Pacific (Seoul)',
|
||||
'ap-southeast-1': 'Asia Pacific (Singapore)',
|
||||
'ap-northeast-1': 'Asia Pacific (Tokyo)',
|
||||
'eu-central-1': 'Europe (Frankfurt)',
|
||||
'us-gov-west-1': 'AWS GovCloud (US-West)',
|
||||
'ap-southeast-2': 'Asia Pacific (Sydney)',
|
||||
'ap-east-2': 'Asia Pacific (Taipei)',
|
||||
'ap-southeast-7': 'Asia Pacific (Thailand)',
|
||||
'ap-northeast-1': 'Asia Pacific (Tokyo)',
|
||||
'ca-central-1': 'Canada (Central)',
|
||||
'ca-west-1': 'Canada West (Calgary)',
|
||||
'eu-central-1': 'Europe (Frankfurt)',
|
||||
'eu-west-1': 'Europe (Ireland)',
|
||||
'eu-west-2': 'Europe (London)',
|
||||
'eu-south-1': 'Europe (Milan)',
|
||||
'eu-west-3': 'Europe (Paris)',
|
||||
'eu-south-2': 'Europe (Spain)',
|
||||
'eu-north-1': 'Europe (Stockholm)',
|
||||
'eu-central-2': 'Europe (Zurich)',
|
||||
'il-central-1': 'Israel (Tel Aviv)',
|
||||
'mx-central-1': 'Mexico (Central)',
|
||||
'me-south-1': 'Middle East (Bahrain)',
|
||||
'me-central-1': 'Middle East (UAE)',
|
||||
'sa-east-1': 'South America (São Paulo)',
|
||||
'us-gov-east-1': 'AWS GovCloud (US-East)',
|
||||
'us-gov-west-1': 'AWS GovCloud (US-West)',
|
||||
addHunyuanSID: 'Hunyuan Secret ID',
|
||||
HunyuanSIDMessage: 'Silakan masukkan Secret ID Anda',
|
||||
addHunyuanSK: 'Hunyuan Secret Key',
|
||||
|
||||
1361
web/src/locales/ru.ts
Normal file
1361
web/src/locales/ru.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -220,7 +220,7 @@ export default {
|
||||
book: `<p>Các định dạng tệp được hỗ trợ là <b>DOCX</b>, <b>PDF</b>, <b>TXT</b>.</p><p>
|
||||
Đối với mỗi sách trong PDF, vui lòng đặt <i>phạm vi trang</i> để loại bỏ thông tin không mong muốn và giảm thời gian phân tích.</p>`,
|
||||
laws: `<p>Các định dạng tệp được hỗ trợ là <b>DOCX</b>, <b>PDF</b>, <b>TXT</b>.</p><p>
|
||||
Các tài liệu pháp lý thường tuân theo định dạng viết nghiêm ngặt. Chúng tôi sử dụng tính năng văn bản để xác định điểm phân chia.
|
||||
Các tài liệu pháp lý thường tuân theo định dạng viết nghiêm ngặt. Chúng tôi sử dụng tính năng văn bản để xác định điểm phân chia.
|
||||
</p><p>
|
||||
Khối có độ chi tiết nhất quán với 'ARTICLE', đảm bảo tất cả văn bản cấp trên được bao gồm trong khối.
|
||||
</p>`,
|
||||
@ -234,7 +234,7 @@ export default {
|
||||
<p>Các định dạng tệp được hỗ trợ là <b>MD, MDX, DOCX, XLSX, XLS (Excel 97-2003), PPT, PDF, TXT, JPEG, JPG, PNG, TIF, GIF, CSV, JSON, EML, HTML</b>.</p>`,
|
||||
paper: `<p>Chỉ hỗ trợ tệp <b>PDF</b>.</p><p>
|
||||
Bài báo sẽ được chia theo các phần, chẳng hạn như <i>tóm tắt, 1.1, 1.2</i>. </p><p>
|
||||
Cách tiếp cận này cho phép LLM tóm tắt bài báo hiệu quả hơn và cung cấp các phản hồi toàn diện, dễ hiểu hơn.
|
||||
Cách tiếp cận này cho phép LLM tóm tắt bài báo hiệu quả hơn và cung cấp các phản hồi toàn diện, dễ hiểu hơn.
|
||||
Tuy nhiên, nó cũng làm tăng ngữ cảnh cho các cuộc hội thoại AI và tăng thêm chi phí tính toán cho LLM. Vì vậy, trong quá trình trò chuyện, hãy cân nhắc giảm giá trị của '<b>topN</b>'.</p>`,
|
||||
presentation: `<p>Các định dạng tệp được hỗ trợ là <b>PDF</b>, <b>PPTX</b>.</p><p>
|
||||
Mỗi trang trong slide được coi là một khối, với hình thu nhỏ của nó được lưu trữ.</p><p>
|
||||
@ -290,7 +290,7 @@ export default {
|
||||
Áp dụng khi bạn yêu cầu LLM tóm tắt toàn bộ tài liệu, với điều kiện nó có thể xử lý được lượng ngữ cảnh đó.
|
||||
</p>`,
|
||||
knowledgeGraph: `<p>Các định dạng tệp được hỗ trợ là <b>DOCX, EXCEL, PPT, IMAGE, PDF, TXT, MD, JSON, EML</b>
|
||||
|
||||
|
||||
<p>Cách tiếp cận này phân đoạn tệp bằng phương pháp 'ngây thơ'/'Tổng hợp'. Nó chia tài liệu thành các phân đoạn và sau đó kết hợp các phân đoạn liền kề cho đến khi số lượng token vượt quá ngưỡng được chỉ định bởi 'Số token khối', tại thời điểm đó, một khối được tạo.</p>
|
||||
<p>Các khối sau đó được đưa vào LLM để trích xuất các thực thể và mối quan hệ cho biểu đồ tri thức và sơ đồ tư duy.</p>
|
||||
<p>Đảm bảo bạn đã đặt <b>Loại thực thể</b>.</p>`,
|
||||
@ -603,13 +603,41 @@ export default {
|
||||
bedrockSKMessage: 'Vui lòng nhập KHÓA BÍ MẬT của bạn',
|
||||
bedrockRegion: 'Vùng AWS',
|
||||
bedrockRegionMessage: 'Vui lòng chọn!',
|
||||
'us-east-2': 'US East (Ohio)',
|
||||
'us-east-1': 'US East (N. Virginia)',
|
||||
'us-west-1': 'US West (N. California)',
|
||||
'us-west-2': 'US West (Oregon)',
|
||||
'af-south-1': 'Africa (Cape Town)',
|
||||
'ap-east-1': 'Asia Pacific (Hong Kong)',
|
||||
'ap-south-2': 'Asia Pacific (Hyderabad)',
|
||||
'ap-southeast-3': 'Asia Pacific (Jakarta)',
|
||||
'ap-southeast-5': 'Asia Pacific (Malaysia)',
|
||||
'ap-southeast-4': 'Asia Pacific (Melbourne)',
|
||||
'ap-south-1': 'Asia Pacific (Mumbai)',
|
||||
'ap-northeast-3': 'Asia Pacific (Osaka)',
|
||||
'ap-northeast-2': 'Asia Pacific (Seoul)',
|
||||
'ap-southeast-1': 'Asia Pacific (Singapore)',
|
||||
'ap-northeast-1': 'Asia Pacific (Tokyo)',
|
||||
'eu-central-1': 'Europe (Frankfurt)',
|
||||
'us-gov-west-1': 'AWS GovCloud (US-West)',
|
||||
'ap-southeast-2': 'Asia Pacific (Sydney)',
|
||||
'ap-east-2': 'Asia Pacific (Taipei)',
|
||||
'ap-southeast-7': 'Asia Pacific (Thailand)',
|
||||
'ap-northeast-1': 'Asia Pacific (Tokyo)',
|
||||
'ca-central-1': 'Canada (Central)',
|
||||
'ca-west-1': 'Canada West (Calgary)',
|
||||
'eu-central-1': 'Europe (Frankfurt)',
|
||||
'eu-west-1': 'Europe (Ireland)',
|
||||
'eu-west-2': 'Europe (London)',
|
||||
'eu-south-1': 'Europe (Milan)',
|
||||
'eu-west-3': 'Europe (Paris)',
|
||||
'eu-south-2': 'Europe (Spain)',
|
||||
'eu-north-1': 'Europe (Stockholm)',
|
||||
'eu-central-2': 'Europe (Zurich)',
|
||||
'il-central-1': 'Israel (Tel Aviv)',
|
||||
'mx-central-1': 'Mexico (Central)',
|
||||
'me-south-1': 'Middle East (Bahrain)',
|
||||
'me-central-1': 'Middle East (UAE)',
|
||||
'sa-east-1': 'South America (São Paulo)',
|
||||
'us-gov-east-1': 'AWS GovCloud (US-East)',
|
||||
'us-gov-west-1': 'AWS GovCloud (US-West)',
|
||||
addHunyuanSID: 'Hunyuan Secret ID',
|
||||
HunyuanSIDMessage: 'Vui lòng nhập ID bí mật của bạn',
|
||||
addHunyuanSK: 'Hunyuan Secret Key',
|
||||
|
||||
@ -147,7 +147,7 @@ export const useHandleUploadDocument = () => {
|
||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||
const [uploadProgress, setUploadProgress] = useState<number>(0);
|
||||
const { uploadDocument, loading } = useUploadNextDocument();
|
||||
const { runDocumentByIds, loading: _ } = useRunNextDocument();
|
||||
const { runDocumentByIds } = useRunNextDocument();
|
||||
|
||||
const onDocumentUploadOk = useCallback(
|
||||
async ({
|
||||
|
||||
@ -168,7 +168,7 @@ const KnowledgeFile = () => {
|
||||
),
|
||||
dataIndex: 'run',
|
||||
key: 'run',
|
||||
filters: Object.entries(RunningStatus).map(([key, value]) => ({
|
||||
filters: Object.values(RunningStatus).map((value) => ({
|
||||
text: t(`runningStatus${value}`),
|
||||
value: value,
|
||||
})),
|
||||
|
||||
@ -27,7 +27,6 @@ const KnowledgeSidebar = () => {
|
||||
const { knowledgeId } = useGetKnowledgeSearchParams();
|
||||
|
||||
const [windowWidth, setWindowWidth] = useState(getWidth());
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration();
|
||||
|
||||
@ -92,14 +91,6 @@ const KnowledgeSidebar = () => {
|
||||
return list;
|
||||
}, [data, getItem]);
|
||||
|
||||
useEffect(() => {
|
||||
if (windowWidth.width > 957) {
|
||||
setCollapsed(false);
|
||||
} else {
|
||||
setCollapsed(true);
|
||||
}
|
||||
}, [windowWidth.width]);
|
||||
|
||||
useEffect(() => {
|
||||
const widthSize = () => {
|
||||
const width = getWidth();
|
||||
|
||||
@ -4,7 +4,7 @@ import { IRetrievalNode } from '@/interfaces/database/flow';
|
||||
import { NodeProps, Position } from '@xyflow/react';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lodash';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { NodeHandleId } from '../../constant';
|
||||
import { useGetVariableLabelByValue } from '../../hooks/use-get-begin-query';
|
||||
import { CommonHandle } from './handle';
|
||||
@ -21,18 +21,7 @@ function InnerRetrievalNode({
|
||||
selected,
|
||||
}: NodeProps<IRetrievalNode>) {
|
||||
const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []);
|
||||
console.log('🚀 ~ InnerRetrievalNode ~ knowledgeBaseIds:', knowledgeBaseIds);
|
||||
const { list: knowledgeList } = useFetchKnowledgeList(true);
|
||||
const knowledgeBases = useMemo(() => {
|
||||
return knowledgeBaseIds.map((x) => {
|
||||
const item = knowledgeList.find((y) => x === y.id);
|
||||
return {
|
||||
name: item?.name,
|
||||
avatar: item?.avatar,
|
||||
id: x,
|
||||
};
|
||||
});
|
||||
}, [knowledgeList, knowledgeBaseIds]);
|
||||
|
||||
const getLabel = useGetVariableLabelByValue(id);
|
||||
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { ModelVariableType } from '@/constants/knowledge';
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||
import { get, isEmpty, isPlainObject } from 'lodash';
|
||||
import { isEmpty, isPlainObject } from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
import { buildCategorizeListFromObject } from '../../utils';
|
||||
|
||||
const defaultValues = {
|
||||
parameter: ModelVariableType.Precise,
|
||||
@ -21,9 +20,6 @@ export function useValues(node?: RAGFlowNodeType) {
|
||||
if (isEmpty(formData)) {
|
||||
return defaultValues;
|
||||
}
|
||||
const items = buildCategorizeListFromObject(
|
||||
get(node, 'data.form.category_description', {}),
|
||||
);
|
||||
if (isPlainObject(formData)) {
|
||||
// const nextValues = {
|
||||
// ...omit(formData, 'category_description'),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { SideDown } from '@/assets/icon/Icon';
|
||||
import { SideDown } from '@/assets/icon/next-icon';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Collapsible,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { Edge } from '@xyflow/react';
|
||||
import pick from 'lodash/pick';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { IOperatorForm } from '../../interface';
|
||||
@ -21,16 +20,12 @@ export const useBuildRelevantOptions = () => {
|
||||
return buildRelevantOptions;
|
||||
};
|
||||
|
||||
const getTargetOfEdge = (edges: Edge[], sourceHandle: string) =>
|
||||
edges.find((x) => x.sourceHandle === sourceHandle)?.target;
|
||||
|
||||
/**
|
||||
* monitor changes in the connection and synchronize the target to the yes and no fields of the form
|
||||
* similar to the categorize-form's useHandleFormValuesChange method
|
||||
* @param param0
|
||||
*/
|
||||
export const useWatchConnectionChanges = ({ nodeId, form }: IOperatorForm) => {
|
||||
const edges = useGraphStore((state) => state.edges);
|
||||
const getNode = useGraphStore((state) => state.getNode);
|
||||
const node = getNode(nodeId);
|
||||
|
||||
@ -40,13 +35,6 @@ export const useWatchConnectionChanges = ({ nodeId, form }: IOperatorForm) => {
|
||||
}
|
||||
}, [node, form]);
|
||||
|
||||
const watchConnectionChanges = useCallback(() => {
|
||||
const edgeList = edges.filter((x) => x.source === nodeId);
|
||||
const yes = getTargetOfEdge(edgeList, 'yes');
|
||||
const no = getTargetOfEdge(edgeList, 'no');
|
||||
form?.setFieldsValue({ yes, no });
|
||||
}, [edges, nodeId, form]);
|
||||
|
||||
useEffect(() => {
|
||||
watchFormChanges();
|
||||
}, [watchFormChanges]);
|
||||
|
||||
@ -8,7 +8,7 @@ import { IModalProps } from '@/interfaces/common';
|
||||
import { NotebookText } from 'lucide-react';
|
||||
import 'react18-json-view/src/style.css';
|
||||
import { useCacheChatLog } from '../hooks/use-cache-chat-log';
|
||||
import { WorkFlowTimeline } from './workFlowTimeline';
|
||||
import { WorkFlowTimeline } from './workflow-timeline';
|
||||
|
||||
type LogSheetProps = IModalProps<any> &
|
||||
Pick<
|
||||
|
||||
@ -19,7 +19,7 @@ import {
|
||||
JsonViewer,
|
||||
toLowerCaseStringAndDeleteChar,
|
||||
typeMap,
|
||||
} from './workFlowTimeline';
|
||||
} from './workflow-timeline';
|
||||
type IToolIcon =
|
||||
| Operator.ArXiv
|
||||
| Operator.GitHub
|
||||
@ -28,7 +28,7 @@ import JsonView from 'react18-json-view';
|
||||
import { Operator } from '../constant';
|
||||
import { useCacheChatLog } from '../hooks/use-cache-chat-log';
|
||||
import OperatorIcon from '../operator-icon';
|
||||
import ToolTimelineItem from './toolTimelineItem';
|
||||
import ToolTimelineItem from './tool-timeline-item';
|
||||
type LogFlowTimelineProps = Pick<
|
||||
ReturnType<typeof useCacheChatLog>,
|
||||
'currentEventListWithoutMessage' | 'currentMessageId'
|
||||
@ -3,7 +3,7 @@ import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import { SharedBadge } from '@/components/shared-badge';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { IFlow } from '@/interfaces/database/flow';
|
||||
import { IFlow } from '@/interfaces/database/agent';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { AgentDropdown } from './agent-dropdown';
|
||||
import { useRenameAgent } from './use-rename-agent';
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useDeleteAgent } from '@/hooks/use-agent-request';
|
||||
import { IFlow } from '@/interfaces/database/flow';
|
||||
import { IFlow } from '@/interfaces/database/agent';
|
||||
import { PenLine, Trash2 } from 'lucide-react';
|
||||
import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useUpdateAgentSetting } from '@/hooks/use-agent-request';
|
||||
import { IFlow } from '@/interfaces/database/flow';
|
||||
import { IFlow } from '@/interfaces/database/agent';
|
||||
import { pick } from 'lodash';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Form, FormInstance, Input, InputRef } from 'antd';
|
||||
import { omit } from 'lodash';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
|
||||
const EditableContext = React.createContext<FormInstance<any> | null>(null);
|
||||
@ -14,15 +15,12 @@ interface Item {
|
||||
address: string;
|
||||
}
|
||||
|
||||
export const EditableRow: React.FC<EditableRowProps> = ({
|
||||
index,
|
||||
...props
|
||||
}) => {
|
||||
export const EditableRow: React.FC<EditableRowProps> = ({ ...props }) => {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<Form form={form} component={false}>
|
||||
<EditableContext.Provider value={form}>
|
||||
<tr {...props} />
|
||||
<tr {...omit(props, 'index')} />
|
||||
</EditableContext.Provider>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@ -28,6 +28,7 @@ import {
|
||||
import { currentReg, replaceTextByOldReg } from '../utils';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { omit } from 'lodash';
|
||||
import { pipe } from 'lodash/fp';
|
||||
import styles from './index.less';
|
||||
|
||||
@ -247,11 +248,12 @@ const MarkdownContent = ({
|
||||
'custom-typography': ({ children }: { children: string }) =>
|
||||
renderReference(children),
|
||||
code(props: any) {
|
||||
const { children, className, node, ...rest } = props;
|
||||
const { children, className, ...rest } = props;
|
||||
const restProps = omit(rest, 'node');
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return match ? (
|
||||
<SyntaxHighlighter
|
||||
{...rest}
|
||||
{...restProps}
|
||||
PreTag="div"
|
||||
language={match[1]}
|
||||
wrapLongLines
|
||||
@ -259,7 +261,10 @@ const MarkdownContent = ({
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code {...rest} className={classNames(className, 'text-wrap')}>
|
||||
<code
|
||||
{...restProps}
|
||||
className={classNames(className, 'text-wrap')}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
|
||||
@ -26,7 +26,7 @@ export const useGetSharedChatSearchParams = () => {
|
||||
const data = Object.fromEntries(
|
||||
searchParams
|
||||
.entries()
|
||||
.filter(([key, value]) => key.startsWith(data_prefix))
|
||||
.filter(([key]) => key.startsWith(data_prefix))
|
||||
.map(([key, value]) => [key.replace(data_prefix, ''), value]),
|
||||
);
|
||||
return {
|
||||
|
||||
@ -19,7 +19,6 @@ import { Switch } from '@/components/ui/switch';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useFetchChunk } from '@/hooks/chunk-hooks';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { IChunk } from '@/interfaces/database/knowledge';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { FieldValues, FormProvider, useForm } from 'react-hook-form';
|
||||
@ -31,11 +30,6 @@ import {
|
||||
} from '../../utils';
|
||||
import { TagFeatureItem } from './tag-feature-item';
|
||||
|
||||
type FieldType = Pick<
|
||||
IChunk,
|
||||
'content_with_weight' | 'tag_kwd' | 'question_kwd' | 'important_kwd'
|
||||
>;
|
||||
|
||||
interface kFProps {
|
||||
doc_id: string;
|
||||
chunkId: string | undefined;
|
||||
|
||||
@ -36,7 +36,7 @@ const CSVFileViewer: React.FC<FileViewerProps> = () => {
|
||||
const res = await request(url, {
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
onError: (err) => {
|
||||
onError: () => {
|
||||
message.error('file load failed');
|
||||
setIsLoading(false);
|
||||
},
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
import { useTestRetrieval } from '@/hooks/use-knowledge-request';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { TopTitle } from '../dataset-title';
|
||||
import TestingForm from './testing-form';
|
||||
import { TestingResult } from './testing-result';
|
||||
|
||||
function Vertical() {
|
||||
return <div>xxx</div>;
|
||||
}
|
||||
|
||||
export default function RetrievalTesting() {
|
||||
const {
|
||||
loading,
|
||||
@ -21,15 +17,7 @@ export default function RetrievalTesting() {
|
||||
filterValue,
|
||||
} = useTestRetrieval();
|
||||
|
||||
const [count, setCount] = useState(1);
|
||||
|
||||
const addCount = useCallback(() => {
|
||||
setCount(2);
|
||||
}, []);
|
||||
|
||||
const removeCount = useCallback(() => {
|
||||
setCount(1);
|
||||
}, []);
|
||||
const [count] = useState(1);
|
||||
|
||||
return (
|
||||
<div className="p-5">
|
||||
|
||||
@ -3,7 +3,7 @@ import {
|
||||
KeywordIcon,
|
||||
QWeatherIcon,
|
||||
WikipediaIcon,
|
||||
} from '@/assets/icon/Icon';
|
||||
} from '@/assets/icon/next-icon';
|
||||
import { ReactComponent as AkShareIcon } from '@/assets/svg/akshare.svg';
|
||||
import { ReactComponent as ArXivIcon } from '@/assets/svg/arxiv.svg';
|
||||
import { ReactComponent as baiduFanyiIcon } from '@/assets/svg/baidu-fanyi.svg';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { CommaIcon, SemicolonIcon } from '@/assets/icon/Icon';
|
||||
import { CommaIcon, SemicolonIcon } from '@/assets/icon/next-icon';
|
||||
import { Form, Select } from 'antd';
|
||||
import {
|
||||
CornerDownLeft,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { Edge } from '@xyflow/react';
|
||||
import pick from 'lodash/pick';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { IOperatorForm } from '../../interface';
|
||||
@ -21,8 +20,8 @@ export const useBuildRelevantOptions = () => {
|
||||
return buildRelevantOptions;
|
||||
};
|
||||
|
||||
const getTargetOfEdge = (edges: Edge[], sourceHandle: string) =>
|
||||
edges.find((x) => x.sourceHandle === sourceHandle)?.target;
|
||||
// const getTargetOfEdge = (edges: Edge[], sourceHandle: string) =>
|
||||
// edges.find((x) => x.sourceHandle === sourceHandle)?.target;
|
||||
|
||||
/**
|
||||
* monitor changes in the connection and synchronize the target to the yes and no fields of the form
|
||||
@ -30,7 +29,7 @@ const getTargetOfEdge = (edges: Edge[], sourceHandle: string) =>
|
||||
* @param param0
|
||||
*/
|
||||
export const useWatchConnectionChanges = ({ nodeId, form }: IOperatorForm) => {
|
||||
const edges = useGraphStore((state) => state.edges);
|
||||
// const edges = useGraphStore((state) => state.edges);
|
||||
const getNode = useGraphStore((state) => state.getNode);
|
||||
const node = getNode(nodeId);
|
||||
|
||||
@ -40,12 +39,12 @@ export const useWatchConnectionChanges = ({ nodeId, form }: IOperatorForm) => {
|
||||
}
|
||||
}, [node, form]);
|
||||
|
||||
const watchConnectionChanges = useCallback(() => {
|
||||
const edgeList = edges.filter((x) => x.source === nodeId);
|
||||
const yes = getTargetOfEdge(edgeList, 'yes');
|
||||
const no = getTargetOfEdge(edgeList, 'no');
|
||||
form?.setFieldsValue({ yes, no });
|
||||
}, [edges, nodeId, form]);
|
||||
// const watchConnectionChanges = useCallback(() => {
|
||||
// const edgeList = edges.filter((x) => x.source === nodeId);
|
||||
// const yes = getTargetOfEdge(edgeList, 'yes');
|
||||
// const no = getTargetOfEdge(edgeList, 'no');
|
||||
// form?.setFieldsValue({ yes, no });
|
||||
// }, [edges, nodeId, form]);
|
||||
|
||||
useEffect(() => {
|
||||
watchFormChanges();
|
||||
|
||||
@ -50,10 +50,14 @@ export const useSaveGraphBeforeOpeningDebugDrawer = (show: () => void) => {
|
||||
};
|
||||
|
||||
export const useWatchAgentChange = (chatDrawerVisible: boolean) => {
|
||||
console.log(
|
||||
'🚀 ~ useWatchAgentChange ~ chatDrawerVisible:',
|
||||
chatDrawerVisible,
|
||||
);
|
||||
const [time, setTime] = useState<string>();
|
||||
const nodes = useGraphStore((state) => state.nodes);
|
||||
const edges = useGraphStore((state) => state.edges);
|
||||
const { saveGraph } = useSaveGraph();
|
||||
// const { saveGraph } = useSaveGraph();
|
||||
const { data: flowDetail } = useFetchFlow();
|
||||
|
||||
const setSaveTime = useCallback((updateTime: number) => {
|
||||
@ -64,12 +68,12 @@ export const useWatchAgentChange = (chatDrawerVisible: boolean) => {
|
||||
setSaveTime(flowDetail?.update_time);
|
||||
}, [flowDetail, setSaveTime]);
|
||||
|
||||
const saveAgent = useCallback(async () => {
|
||||
if (!chatDrawerVisible) {
|
||||
const ret = await saveGraph();
|
||||
setSaveTime(ret.data.update_time);
|
||||
}
|
||||
}, [chatDrawerVisible, saveGraph, setSaveTime]);
|
||||
// const saveAgent = useCallback(async () => {
|
||||
// if (!chatDrawerVisible) {
|
||||
// const ret = await saveGraph();
|
||||
// setSaveTime(ret.data.update_time);
|
||||
// }
|
||||
// }, [chatDrawerVisible, saveGraph, setSaveTime]);
|
||||
|
||||
useDebounceEffect(
|
||||
() => {
|
||||
|
||||
@ -11,6 +11,7 @@ export enum Step {
|
||||
|
||||
export const useSwitchStep = (step: Step) => {
|
||||
const [_, setSearchParams] = useSearchParams();
|
||||
console.log('🚀 ~ useSwitchStep ~ _:', _);
|
||||
const switchStep = useCallback(() => {
|
||||
setSearchParams(new URLSearchParams({ step: step.toString() }));
|
||||
}, [setSearchParams, step]);
|
||||
|
||||
@ -16,7 +16,7 @@ export const useGetSharedChatSearchParams = () => {
|
||||
const data = Object.fromEntries(
|
||||
searchParams
|
||||
.entries()
|
||||
.filter(([key, value]) => key.startsWith(data_prefix))
|
||||
.filter(([key]) => key.startsWith(data_prefix))
|
||||
.map(([key, value]) => [key.replace(data_prefix, ''), value]),
|
||||
);
|
||||
return {
|
||||
|
||||
108
web/src/pages/next-search/index.less
Normal file
108
web/src/pages/next-search/index.less
Normal file
@ -0,0 +1,108 @@
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInLeft {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(-50%, 0, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
@keyframes fadeInRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(50%, 0, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
@keyframes fadeOutRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate3d(120%, 0, 0);
|
||||
}
|
||||
}
|
||||
@keyframes fadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, -50%, 0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fade-in-up {
|
||||
animation-name: fadeInUp;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.animate-fade-in-down {
|
||||
animation-name: fadeInDown;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.animate-fade-in-left {
|
||||
animation-name: fadeInLeft;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.animate-fade-in-right {
|
||||
animation-name: fadeInRight;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
.animate-fade-out-right {
|
||||
animation-name: fadeOutRight;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.delay-100 {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
.delay-200 {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.delay-300 {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.delay-400 {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.delay-500 {
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
.delay-600 {
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
.delay-700 {
|
||||
animation-delay: 0.7s;
|
||||
}
|
||||
@ -1,21 +1,89 @@
|
||||
import { PageHeader } from '@/components/page-header';
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '@/components/ui/breadcrumb';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { EllipsisVertical } from 'lucide-react';
|
||||
import { Settings } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
ISearchAppDetailProps,
|
||||
useFetchSearchDetail,
|
||||
} from '../next-searches/hooks';
|
||||
import './index.less';
|
||||
import SearchHome from './search-home';
|
||||
import { SearchSetting } from './search-setting';
|
||||
import SearchingPage from './searching';
|
||||
|
||||
export default function SearchPage() {
|
||||
const { navigateToSearchList } = useNavigatePage();
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const { data: SearchData } = useFetchSearchDetail();
|
||||
|
||||
const [openSetting, setOpenSetting] = useState(false);
|
||||
return (
|
||||
<section>
|
||||
<PageHeader back={navigateToSearchList} title="Search app 01">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant={'icon'} size={'icon'}>
|
||||
<EllipsisVertical />
|
||||
</Button>
|
||||
<Button size={'sm'}>Publish</Button>
|
||||
</div>
|
||||
<PageHeader>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink onClick={navigateToSearchList}>
|
||||
Search
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>{SearchData?.name}</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</PageHeader>
|
||||
<div className="flex gap-3 w-full">
|
||||
<div className="flex-1">
|
||||
{!isSearching && (
|
||||
<div className="animate-fade-in-down">
|
||||
<SearchHome
|
||||
setIsSearching={setIsSearching}
|
||||
isSearching={isSearching}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isSearching && (
|
||||
<div className="animate-fade-in-up">
|
||||
<SearchingPage
|
||||
setIsSearching={setIsSearching}
|
||||
isSearching={isSearching}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* {openSetting && (
|
||||
<div className=" w-[440px]"> */}
|
||||
<SearchSetting
|
||||
className="mt-20 mr-2"
|
||||
open={openSetting}
|
||||
setOpen={setOpenSetting}
|
||||
data={SearchData as ISearchAppDetailProps}
|
||||
/>
|
||||
{/* </div>
|
||||
)} */}
|
||||
</div>
|
||||
|
||||
<div className="absolute left-5 bottom-12 ">
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="bg-bg-card"
|
||||
onClick={() => setOpenSetting(!openSetting)}
|
||||
>
|
||||
<Settings className="text-text-secondary" />
|
||||
<div className="text-text-secondary">Search Settings</div>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
93
web/src/pages/next-search/search-home.tsx
Normal file
93
web/src/pages/next-search/search-home.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import { Input } from '@/components/originui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Search } from 'lucide-react';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import './index.less';
|
||||
import Spotlight from './spotlight';
|
||||
|
||||
export default function SearchPage({
|
||||
isSearching,
|
||||
setIsSearching,
|
||||
}: {
|
||||
isSearching: boolean;
|
||||
setIsSearching: Dispatch<SetStateAction<boolean>>;
|
||||
}) {
|
||||
return (
|
||||
<section className="relative w-full flex transition-all justify-center items-center mt-32">
|
||||
<div className="relative z-10 px-8 pt-8 flex text-transparent flex-col justify-center items-center w-[780px]">
|
||||
<h1
|
||||
className={cn(
|
||||
'text-4xl font-bold bg-gradient-to-r from-sky-600 from-30% via-sky-500 via-60% to-emerald-500 bg-clip-text',
|
||||
)}
|
||||
>
|
||||
RAGFlow
|
||||
</h1>
|
||||
|
||||
<div className="rounded-lg text-primary text-xl sticky flex justify-center w-full transform scale-100 mt-8 p-6 h-[230px] border">
|
||||
{!isSearching && <Spotlight className="z-0" />}
|
||||
<div className="flex flex-col justify-center items-center w-2/3">
|
||||
{!isSearching && (
|
||||
<>
|
||||
<p className="mb-4 transition-opacity">👋 Hi there</p>
|
||||
<p className="mb-10 transition-opacity">Welcome back, KiKi</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="relative w-full ">
|
||||
<Input
|
||||
placeholder="How can I help you today?"
|
||||
className="w-full rounded-full py-6 px-4 pr-10 text-white text-lg bg-background delay-700"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 transform rounded-full bg-white p-2 text-gray-800 shadow w-12"
|
||||
onClick={() => {
|
||||
setIsSearching(!isSearching);
|
||||
}}
|
||||
>
|
||||
<Search size={22} className="m-auto" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-14 w-full overflow-hidden opacity-100 max-h-96">
|
||||
<p className="text-text-primary mb-2 text-xl">Related Search</p>
|
||||
<div className="mt-2 flex flex-wrap justify-start gap-2">
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="bg-bg-card text-text-secondary"
|
||||
>
|
||||
Related Search
|
||||
</Button>
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="bg-bg-card text-text-secondary"
|
||||
>
|
||||
Related Search Related SearchRelated Search
|
||||
</Button>
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="bg-bg-card text-text-secondary"
|
||||
>
|
||||
Related Search Search
|
||||
</Button>
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="bg-bg-card text-text-secondary"
|
||||
>
|
||||
Related Search Related SearchRelated Search
|
||||
</Button>
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="bg-bg-card text-text-secondary"
|
||||
>
|
||||
Related Search
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
496
web/src/pages/next-search/search-setting.tsx
Normal file
496
web/src/pages/next-search/search-setting.tsx
Normal file
@ -0,0 +1,496 @@
|
||||
// src/pages/next-search/search-setting.tsx
|
||||
|
||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { SingleFormSlider } from '@/components/ui/dual-range-slider';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
MultiSelect,
|
||||
MultiSelectOptionType,
|
||||
} from '@/components/ui/multi-select';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
||||
import { IKnowledge } from '@/interfaces/database/knowledge';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { transformFile2Base64 } from '@/utils/file-util';
|
||||
import { t } from 'i18next';
|
||||
import { PanelRightClose, Pencil, Upload } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { ISearchAppDetailProps } from '../next-searches/hooks';
|
||||
|
||||
interface SearchSettingProps {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
className?: string;
|
||||
data: ISearchAppDetailProps;
|
||||
}
|
||||
|
||||
const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
open = false,
|
||||
setOpen,
|
||||
className,
|
||||
data,
|
||||
}) => {
|
||||
const [width0, setWidth0] = useState('w-[440px]');
|
||||
// "avatar": null,
|
||||
// "created_by": "c3fb861af27a11efa69751e139332ced",
|
||||
// "description": "My first search app",
|
||||
// "id": "22e874584b4511f0aa1ac57b9ea5a68b",
|
||||
// "name": "updated search app",
|
||||
// "search_config": {
|
||||
// "cross_languages": [],
|
||||
// "doc_ids": [],
|
||||
// "highlight": false,
|
||||
// "kb_ids": [],
|
||||
// "keyword": false,
|
||||
// "query_mindmap": false,
|
||||
// "related_search": false,
|
||||
// "rerank_id": "",
|
||||
// "similarity_threshold": 0.5,
|
||||
// "summary": false,
|
||||
// "top_k": 1024,
|
||||
// "use_kg": true,
|
||||
// "vector_similarity_weight": 0.3,
|
||||
// "web_search": false
|
||||
// },
|
||||
// "tenant_id": "c3fb861af27a11efa69751e139332ced",
|
||||
// "update_time": 1750144129641
|
||||
const formMethods = useForm({
|
||||
defaultValues: {
|
||||
id: '',
|
||||
name: '',
|
||||
avatar: '',
|
||||
description: 'You are an intelligent assistant.',
|
||||
datasets: '',
|
||||
keywordSimilarityWeight: 20,
|
||||
rerankModel: false,
|
||||
aiSummary: false,
|
||||
topK: true,
|
||||
searchMethod: '',
|
||||
model: '',
|
||||
enableWebSearch: false,
|
||||
enableRelatedSearch: true,
|
||||
showQueryMindmap: true,
|
||||
},
|
||||
});
|
||||
const [avatarFile, setAvatarFile] = useState<File | null>(null);
|
||||
const [avatarBase64Str, setAvatarBase64Str] = useState(''); // Avatar Image base64
|
||||
const [datasetList, setDatasetList] = useState<MultiSelectOptionType[]>([]);
|
||||
const [datasetSelectEmbdId, setDatasetSelectEmbdId] = useState('');
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setTimeout(() => {
|
||||
setWidth0('w-0 hidden');
|
||||
}, 500);
|
||||
} else {
|
||||
setWidth0('w-[440px]');
|
||||
}
|
||||
}, [open]);
|
||||
useEffect(() => {
|
||||
if (!avatarFile) {
|
||||
setAvatarBase64Str(data?.avatar);
|
||||
}
|
||||
}, [avatarFile, data?.avatar]);
|
||||
useEffect(() => {
|
||||
if (avatarFile) {
|
||||
(async () => {
|
||||
// make use of img compression transformFile2Base64
|
||||
setAvatarBase64Str(await transformFile2Base64(avatarFile));
|
||||
})();
|
||||
}
|
||||
}, [avatarFile]);
|
||||
const { list: datasetListOrigin } = useFetchKnowledgeList();
|
||||
|
||||
useEffect(() => {
|
||||
const datasetListMap = datasetListOrigin.map((item: IKnowledge) => {
|
||||
return {
|
||||
label: item.name,
|
||||
suffix: (
|
||||
<div className="text-xs px-4 p-1 bg-bg-card text-text-secondary rounded-lg border border-bg-card">
|
||||
{item.embd_id}
|
||||
</div>
|
||||
),
|
||||
value: item.id,
|
||||
disabled:
|
||||
item.embd_id !== datasetSelectEmbdId && datasetSelectEmbdId !== '',
|
||||
};
|
||||
});
|
||||
setDatasetList(datasetListMap);
|
||||
}, [datasetListOrigin, datasetSelectEmbdId]);
|
||||
|
||||
const handleDatasetSelectChange = (value, onChange) => {
|
||||
console.log(value);
|
||||
if (value.length) {
|
||||
const data = datasetListOrigin?.find((item) => item.id === value[0]);
|
||||
setDatasetSelectEmbdId(data?.embd_id ?? '');
|
||||
} else {
|
||||
setDatasetSelectEmbdId('');
|
||||
}
|
||||
onChange?.(value);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'text-text-primary border p-4 rounded-lg',
|
||||
{
|
||||
'animate-fade-in-right': open,
|
||||
'animate-fade-out-right': !open,
|
||||
},
|
||||
width0,
|
||||
className,
|
||||
)}
|
||||
style={{ height: 'calc(100dvh - 170px)' }}
|
||||
>
|
||||
<div className="flex justify-between items-center text-base mb-8">
|
||||
<div className="text-text-primary">Search Settings</div>
|
||||
<div onClick={() => setOpen(false)}>
|
||||
<PanelRightClose
|
||||
size={16}
|
||||
className="text-text-primary cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{ height: 'calc(100dvh - 270px)' }}
|
||||
className="overflow-y-auto scrollbar-auto p-1 text-text-secondary"
|
||||
>
|
||||
<Form {...formMethods}>
|
||||
<form
|
||||
onSubmit={formMethods.handleSubmit((data) => console.log(data))}
|
||||
className="space-y-6"
|
||||
>
|
||||
{/* Name */}
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="name"
|
||||
rules={{ required: 'Name is required' }}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<span className="text-destructive mr-1"> *</span>Name
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Name" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Avatar */}
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="avatar"
|
||||
render={() => (
|
||||
<FormItem>
|
||||
<FormLabel>Avatar</FormLabel>
|
||||
<FormControl>
|
||||
<div className="relative group">
|
||||
{!avatarBase64Str ? (
|
||||
<div className="w-[64px] h-[64px] grid place-content-center border border-dashed rounded-md">
|
||||
<div className="flex flex-col items-center">
|
||||
<Upload />
|
||||
<p>{t('common.upload')}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-[64px] h-[64px] relative grid place-content-center">
|
||||
<RAGFlowAvatar
|
||||
avatar={avatarBase64Str}
|
||||
name={data.name}
|
||||
className="w-[64px] h-[64px] rounded-md block"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-[#000]/20 group-hover:bg-[#000]/60">
|
||||
<Pencil
|
||||
size={20}
|
||||
className="absolute right-2 bottom-0 opacity-50 hidden group-hover:block"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Input
|
||||
placeholder=""
|
||||
// {...field}
|
||||
type="file"
|
||||
title=""
|
||||
accept="image/*"
|
||||
className="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer"
|
||||
onChange={(ev) => {
|
||||
const file = ev.target?.files?.[0];
|
||||
if (
|
||||
/\.(jpg|jpeg|png|webp|bmp)$/i.test(file?.name ?? '')
|
||||
) {
|
||||
setAvatarFile(file!);
|
||||
}
|
||||
ev.target.value = '';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Description */}
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Description"
|
||||
{...field}
|
||||
defaultValue="You are an intelligent assistant."
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Datasets */}
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="datasets"
|
||||
rules={{ required: 'Datasets is required' }}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<span className="text-destructive mr-1"> *</span>Datasets
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<MultiSelect
|
||||
options={datasetList}
|
||||
onValueChange={(value) => {
|
||||
handleDatasetSelectChange(value, field.onChange);
|
||||
}}
|
||||
showSelectAll={false}
|
||||
placeholder={t('chat.knowledgeBasesMessage')}
|
||||
variant="inverted"
|
||||
maxCount={10}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Keyword Similarity Weight */}
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="keywordSimilarityWeight"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>Keyword Similarity Weight</FormLabel>
|
||||
<FormControl>
|
||||
<div className="flex justify-between items-center">
|
||||
<SingleFormSlider
|
||||
max={100}
|
||||
step={1}
|
||||
value={field.value as number}
|
||||
onChange={(values) => field.onChange(values)}
|
||||
></SingleFormSlider>
|
||||
<Label className="w-10 h-6 bg-bg-card flex justify-center items-center rounded-lg ml-20">
|
||||
{field.value}
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Rerank Model */}
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="rerankModel"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>Rerank Model</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* AI Summary */}
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="aiSummary"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>AI Summary</FormLabel>
|
||||
<Label className="text-sm text-muted-foreground">
|
||||
默认不打开
|
||||
</Label>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Top K */}
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="topK"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>Top K</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Search Method */}
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="searchMethod"
|
||||
rules={{ required: 'Search Method is required' }}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<span className="text-destructive mr-1"> *</span>Search
|
||||
Method
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select search method..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="method1">Method 1</SelectItem>
|
||||
<SelectItem value="method2">Method 2</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Model */}
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="model"
|
||||
rules={{ required: 'Model is required' }}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<span className="text-destructive mr-1"> *</span>Model
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select model..." />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="model1">Model 1</SelectItem>
|
||||
<SelectItem value="model2">Model 2</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Feature Controls */}
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="enableWebSearch"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>Enable Web Search</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="enableRelatedSearch"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>Enable Related Search</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={formMethods.control}
|
||||
name="showQueryMindmap"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>Show Query Mindmap</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{/* Submit Button */}
|
||||
<div className="flex justify-end">
|
||||
<Button type="submit">Confirm</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { SearchSetting };
|
||||
64
web/src/pages/next-search/searching.tsx
Normal file
64
web/src/pages/next-search/searching.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { Input } from '@/components/originui/input';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Search, X } from 'lucide-react';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import './index.less';
|
||||
|
||||
export default function SearchingPage({
|
||||
isSearching,
|
||||
setIsSearching,
|
||||
}: {
|
||||
isSearching: boolean;
|
||||
setIsSearching: Dispatch<SetStateAction<boolean>>;
|
||||
}) {
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
'relative w-full flex transition-all justify-start items-center',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'relative z-10 px-8 pt-8 flex text-transparent justify-start items-start w-full',
|
||||
)}
|
||||
>
|
||||
<h1
|
||||
className={cn(
|
||||
'text-4xl font-bold bg-gradient-to-r from-sky-600 from-30% via-sky-500 via-60% to-emerald-500 bg-clip-text',
|
||||
)}
|
||||
>
|
||||
RAGFlow
|
||||
</h1>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
' rounded-lg text-primary text-xl sticky flex justify-center w-2/3 max-w-[780px] transform scale-100 ml-16 ',
|
||||
)}
|
||||
>
|
||||
<div className={cn('flex flex-col justify-start items-start w-full')}>
|
||||
<div className="relative w-full text-primary">
|
||||
<Input
|
||||
placeholder="How can I help you today?"
|
||||
className={cn(
|
||||
'w-full rounded-full py-6 pl-4 !pr-[8rem] text-primary text-lg bg-background',
|
||||
)}
|
||||
/>
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 transform flex items-center gap-1">
|
||||
<X />|
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-full bg-white p-1 text-gray-800 shadow w-12 h-8 ml-4"
|
||||
onClick={() => {
|
||||
setIsSearching(!isSearching);
|
||||
}}
|
||||
>
|
||||
<Search size={22} className="m-auto" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
28
web/src/pages/next-search/spotlight.tsx
Normal file
28
web/src/pages/next-search/spotlight.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
|
||||
interface SpotlightProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Spotlight: React.FC<SpotlightProps> = ({ className }) => {
|
||||
return (
|
||||
<div
|
||||
className={`absolute inset-0 opacity-80 ${className} rounded-lg`}
|
||||
style={{
|
||||
backdropFilter: 'blur(30px)',
|
||||
zIndex: -1,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
'radial-gradient(circle at 50% 190%, #fff4 0%, #fff0 60%)',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Spotlight;
|
||||
209
web/src/pages/next-searches/hooks.ts
Normal file
209
web/src/pages/next-searches/hooks.ts
Normal file
@ -0,0 +1,209 @@
|
||||
// src/pages/next-searches/hooks.ts
|
||||
|
||||
import searchService from '@/services/search-service';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { message } from 'antd';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'umi';
|
||||
|
||||
interface CreateSearchProps {
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface CreateSearchResponse {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const useCreateSearch = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isError,
|
||||
mutateAsync: createSearchMutation,
|
||||
} = useMutation<CreateSearchResponse, Error, CreateSearchProps>({
|
||||
mutationKey: ['createSearch'],
|
||||
mutationFn: async (props) => {
|
||||
const { data: response } = await searchService.createSearch(props);
|
||||
if (response.code !== 0) {
|
||||
throw new Error(response.message || 'Failed to create search');
|
||||
}
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
message.success(t('message.created'));
|
||||
},
|
||||
onError: (error) => {
|
||||
message.error(t('message.error', { error: error.message }));
|
||||
},
|
||||
});
|
||||
|
||||
const createSearch = useCallback(
|
||||
(props: CreateSearchProps) => {
|
||||
return createSearchMutation(props);
|
||||
},
|
||||
[createSearchMutation],
|
||||
);
|
||||
|
||||
return { data, isLoading, isError, createSearch };
|
||||
};
|
||||
|
||||
export interface SearchListParams {
|
||||
keywords?: string;
|
||||
parser_id?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
orderby?: string;
|
||||
desc?: boolean;
|
||||
owner_ids?: string;
|
||||
}
|
||||
export interface ISearchAppProps {
|
||||
avatar: any;
|
||||
create_time: number;
|
||||
created_by: string;
|
||||
description: string;
|
||||
id: string;
|
||||
name: string;
|
||||
nickname: string;
|
||||
status: string;
|
||||
tenant_avatar: any;
|
||||
tenant_id: string;
|
||||
update_time: number;
|
||||
}
|
||||
interface SearchListResponse {
|
||||
code: number;
|
||||
data: {
|
||||
search_apps: Array<ISearchAppProps>;
|
||||
total: number;
|
||||
};
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const useFetchSearchList = (params?: SearchListParams) => {
|
||||
const [searchParams, setSearchParams] = useState<SearchListParams>({
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
...params,
|
||||
});
|
||||
|
||||
const { data, isLoading, isError } = useQuery<SearchListResponse, Error>({
|
||||
queryKey: ['searchList', searchParams],
|
||||
queryFn: async () => {
|
||||
const { data: response } =
|
||||
await searchService.getSearchList(searchParams);
|
||||
if (response.code !== 0) {
|
||||
throw new Error(response.message || 'Failed to fetch search list');
|
||||
}
|
||||
return response;
|
||||
},
|
||||
});
|
||||
|
||||
const setSearchListParams = (newParams: SearchListParams) => {
|
||||
setSearchParams((prevParams) => ({
|
||||
...prevParams,
|
||||
...newParams,
|
||||
}));
|
||||
};
|
||||
|
||||
return { data, isLoading, isError, searchParams, setSearchListParams };
|
||||
};
|
||||
|
||||
interface DeleteSearchProps {
|
||||
search_id: string;
|
||||
}
|
||||
|
||||
interface DeleteSearchResponse {
|
||||
code: number;
|
||||
data: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const useDeleteSearch = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isError,
|
||||
mutateAsync: deleteSearchMutation,
|
||||
} = useMutation<DeleteSearchResponse, Error, DeleteSearchProps>({
|
||||
mutationKey: ['deleteSearch'],
|
||||
mutationFn: async (props) => {
|
||||
const response = await searchService.deleteSearch(props);
|
||||
if (response.code !== 0) {
|
||||
throw new Error(response.message || 'Failed to delete search');
|
||||
}
|
||||
return response;
|
||||
},
|
||||
onSuccess: () => {
|
||||
message.success(t('message.deleted'));
|
||||
},
|
||||
onError: (error) => {
|
||||
message.error(t('message.error', { error: error.message }));
|
||||
},
|
||||
});
|
||||
|
||||
const deleteSearch = useCallback(
|
||||
(props: DeleteSearchProps) => {
|
||||
return deleteSearchMutation(props);
|
||||
},
|
||||
[deleteSearchMutation],
|
||||
);
|
||||
|
||||
return { data, isLoading, isError, deleteSearch };
|
||||
};
|
||||
|
||||
export interface ISearchAppDetailProps {
|
||||
avatar: any;
|
||||
created_by: string;
|
||||
description: string;
|
||||
id: string;
|
||||
name: string;
|
||||
search_config: {
|
||||
cross_languages: string[];
|
||||
doc_ids: string[];
|
||||
highlight: boolean;
|
||||
kb_ids: string[];
|
||||
keyword: boolean;
|
||||
query_mindmap: boolean;
|
||||
related_search: boolean;
|
||||
rerank_id: string;
|
||||
similarity_threshold: number;
|
||||
summary: boolean;
|
||||
top_k: number;
|
||||
use_kg: boolean;
|
||||
vector_similarity_weight: number;
|
||||
web_search: boolean;
|
||||
};
|
||||
tenant_id: string;
|
||||
update_time: number;
|
||||
}
|
||||
|
||||
interface SearchDetailResponse {
|
||||
code: number;
|
||||
data: ISearchAppDetailProps;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const useFetchSearchDetail = () => {
|
||||
const { id } = useParams();
|
||||
const { data, isLoading, isError } = useQuery<SearchDetailResponse, Error>({
|
||||
queryKey: ['searchDetail', id],
|
||||
queryFn: async () => {
|
||||
const { data: response } = await searchService.getSearchDetail({
|
||||
search_id: id,
|
||||
});
|
||||
if (response.code !== 0) {
|
||||
throw new Error(response.message || 'Failed to fetch search detail');
|
||||
}
|
||||
return response;
|
||||
},
|
||||
});
|
||||
|
||||
return { data: data?.data, isLoading, isError };
|
||||
};
|
||||
@ -1,20 +1,65 @@
|
||||
import ListFilterBar from '@/components/list-filter-bar';
|
||||
import { Input } from '@/components/originui/input';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useFetchFlowList } from '@/hooks/flow-hooks';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { pick } from 'lodash';
|
||||
import { Plus, Search } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { useCreateSearch, useFetchSearchList } from './hooks';
|
||||
import { SearchCard } from './search-card';
|
||||
const searchFormSchema = z.object({
|
||||
name: z.string().min(1, {
|
||||
message: 'Name is required',
|
||||
}),
|
||||
});
|
||||
|
||||
type SearchFormValues = z.infer<typeof searchFormSchema>;
|
||||
export default function SearchList() {
|
||||
const { data } = useFetchFlowList();
|
||||
// const { data } = useFetchFlowList();
|
||||
const { t } = useTranslate('search');
|
||||
const [searchName, setSearchName] = useState('');
|
||||
const { isLoading, createSearch } = useCreateSearch();
|
||||
const {
|
||||
data: list,
|
||||
searchParams,
|
||||
setSearchListParams,
|
||||
} = useFetchSearchList();
|
||||
const [openCreateModal, setOpenCreateModal] = useState(false);
|
||||
const form = useForm<SearchFormValues>({
|
||||
resolver: zodResolver(searchFormSchema),
|
||||
defaultValues: {
|
||||
name: '',
|
||||
},
|
||||
});
|
||||
const handleSearchChange = (value: string) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
const onSubmit = async (values: SearchFormValues) => {
|
||||
await createSearch({ name: values.name });
|
||||
if (!isLoading) {
|
||||
setOpenCreateModal(false);
|
||||
}
|
||||
form.reset({ name: '' });
|
||||
};
|
||||
const openCreateModalFun = () => {
|
||||
setOpenCreateModal(true);
|
||||
};
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
setSearchListParams({ ...searchParams, page, page_size: pageSize });
|
||||
};
|
||||
return (
|
||||
<section>
|
||||
<div className="px-8 pt-8">
|
||||
@ -31,35 +76,7 @@ export default function SearchList() {
|
||||
<Button
|
||||
variant={'default'}
|
||||
onClick={() => {
|
||||
Modal.show({
|
||||
title: (
|
||||
<div className="rounded-sm bg-emerald-400 bg-gradient-to-t from-emerald-400 via-emerald-400 to-emerald-200 p-1 size-6 flex justify-center items-center">
|
||||
<Search size={14} className="font-bold m-auto" />
|
||||
</div>
|
||||
),
|
||||
titleClassName: 'border-none',
|
||||
footerClassName: 'border-none',
|
||||
visible: true,
|
||||
children: (
|
||||
<div>
|
||||
<div>{t('createSearch')}</div>
|
||||
<div>name:</div>
|
||||
<Input
|
||||
defaultValue={searchName}
|
||||
onChange={(e) => {
|
||||
console.log(e.target.value, e);
|
||||
setSearchName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
onOk: () => {
|
||||
console.log('ok', searchName);
|
||||
},
|
||||
onVisibleChange: (e) => {
|
||||
Modal.hide();
|
||||
},
|
||||
});
|
||||
openCreateModalFun();
|
||||
}}
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
@ -68,10 +85,72 @@ export default function SearchList() {
|
||||
</ListFilterBar>
|
||||
</div>
|
||||
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 max-h-[84vh] overflow-auto px-8">
|
||||
{data.map((x) => {
|
||||
{list?.data.search_apps.map((x) => {
|
||||
return <SearchCard key={x.id} data={x}></SearchCard>;
|
||||
})}
|
||||
{/* {data.map((x) => {
|
||||
return <SearchCard key={x.id} data={x}></SearchCard>;
|
||||
})} */}
|
||||
</div>
|
||||
{list?.data.total && (
|
||||
<RAGFlowPagination
|
||||
{...pick(searchParams, 'current', 'pageSize')}
|
||||
total={list?.data.total}
|
||||
onChange={handlePageChange}
|
||||
on
|
||||
/>
|
||||
)}
|
||||
<Modal
|
||||
open={openCreateModal}
|
||||
onOpenChange={(open) => {
|
||||
setOpenCreateModal(open);
|
||||
}}
|
||||
title={
|
||||
<div className="rounded-sm bg-emerald-400 bg-gradient-to-t from-emerald-400 via-emerald-400 to-emerald-200 p-1 size-6 flex justify-center items-center">
|
||||
<Search size={14} className="font-bold m-auto" />
|
||||
</div>
|
||||
}
|
||||
className="!w-[480px] rounded-xl"
|
||||
titleClassName="border-none"
|
||||
footerClassName="border-none"
|
||||
showfooter={false}
|
||||
maskClosable={false}
|
||||
>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<div className="text-base mb-4">{t('createSearch')}</div>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
<span className="text-destructive mr-1"> *</span>Name
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex justify-end gap-2 mt-8 mb-6">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setOpenCreateModal(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={isLoading}>
|
||||
{isLoading ? 'Confirm...' : 'Confirm'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</Modal>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,53 +1,58 @@
|
||||
import { MoreButton } from '@/components/more-button';
|
||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { IFlow } from '@/interfaces/database/flow';
|
||||
import { formatPureDate } from '@/utils/date';
|
||||
import { ChevronRight, Trash2 } from 'lucide-react';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { ISearchAppProps } from './hooks';
|
||||
import { SearchDropdown } from './search-dropdown';
|
||||
|
||||
interface IProps {
|
||||
data: IFlow;
|
||||
data: ISearchAppProps;
|
||||
}
|
||||
|
||||
export function SearchCard({ data }: IProps) {
|
||||
const { navigateToSearch } = useNavigatePage();
|
||||
|
||||
return (
|
||||
<Card className="border-colors-outline-neutral-standard">
|
||||
<Card
|
||||
className="bg-bg-card border-colors-outline-neutral-standard"
|
||||
onClick={() => {
|
||||
navigateToSearch(data?.id);
|
||||
}}
|
||||
>
|
||||
<CardContent className="p-4 flex gap-2 items-start group">
|
||||
<div className="flex justify-between mb-4">
|
||||
<RAGFlowAvatar
|
||||
className="w-[70px] h-[70px]"
|
||||
className="w-[32px] h-[32px]"
|
||||
avatar={data.avatar}
|
||||
name={data.title}
|
||||
name={data.name}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex flex-col gap-1 flex-1">
|
||||
<section className="flex justify-between">
|
||||
<div className="text-[20px] font-bold size-7 leading-5">
|
||||
{data.title}
|
||||
<div className="text-[20px] font-bold w-80% leading-5">
|
||||
{data.name}
|
||||
</div>
|
||||
<MoreButton></MoreButton>
|
||||
<SearchDropdown dataset={data}>
|
||||
<MoreButton></MoreButton>
|
||||
</SearchDropdown>
|
||||
</section>
|
||||
|
||||
<div>An app that does things An app that does things</div>
|
||||
<div>{data.description}</div>
|
||||
<section className="flex justify-between">
|
||||
<div>
|
||||
Search app
|
||||
<p className="text-sm opacity-80">
|
||||
{formatPureDate(data.update_time)}
|
||||
{formatDate(data.update_time)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-x-2 invisible group-hover:visible">
|
||||
{/* <div className="space-x-2 invisible group-hover:visible">
|
||||
<Button variant="icon" size="icon" onClick={navigateToSearch}>
|
||||
<ChevronRight className="h-6 w-6" />
|
||||
</Button>
|
||||
<Button variant="icon" size="icon">
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</div>
|
||||
</div> */}
|
||||
</section>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
50
web/src/pages/next-searches/search-dropdown.tsx
Normal file
50
web/src/pages/next-searches/search-dropdown.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ISearchAppProps, useDeleteSearch } from './hooks';
|
||||
|
||||
export function SearchDropdown({
|
||||
children,
|
||||
dataset,
|
||||
}: PropsWithChildren & {
|
||||
dataset: ISearchAppProps;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { deleteSearch } = useDeleteSearch();
|
||||
|
||||
const handleDelete: MouseEventHandler<HTMLDivElement> = useCallback(() => {
|
||||
deleteSearch({ search_id: dataset.id });
|
||||
}, [dataset.id, deleteSearch]);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{/* <DropdownMenuItem onClick={handleShowDatasetRenameModal}>
|
||||
{t('common.rename')} <PenLine />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator /> */}
|
||||
<ConfirmDeleteDialog onOk={handleDelete}>
|
||||
<DropdownMenuItem
|
||||
className="text-text-delete-red"
|
||||
onSelect={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{t('common.delete')} <Trash2 />
|
||||
</DropdownMenuItem>
|
||||
</ConfirmDeleteDialog>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
@ -24,7 +24,7 @@ export const useSendQuestion = (kbIds: string[]) => {
|
||||
api.ask,
|
||||
);
|
||||
const { testChunk, loading } = useTestChunkRetrieval();
|
||||
const { testChunkAll, loading: loadingAll } = useTestChunkAllRetrieval();
|
||||
const { testChunkAll } = useTestChunkAllRetrieval();
|
||||
const [sendingLoading, setSendingLoading] = useState(false);
|
||||
const [currentAnswer, setCurrentAnswer] = useState({} as IAnswer);
|
||||
const { fetchRelatedQuestions, data: relatedQuestions } =
|
||||
@ -102,7 +102,14 @@ export const useSendQuestion = (kbIds: string[]) => {
|
||||
size,
|
||||
});
|
||||
},
|
||||
[sendingLoading, searchStr, kbIds, testChunk, selectedDocumentIds],
|
||||
[
|
||||
searchStr,
|
||||
sendingLoading,
|
||||
testChunk,
|
||||
kbIds,
|
||||
selectedDocumentIds,
|
||||
testChunkAll,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
PasswordIcon,
|
||||
ProfileIcon,
|
||||
TeamIcon,
|
||||
} from '@/assets/icon/Icon';
|
||||
} from '@/assets/icon/next-icon';
|
||||
import { IconFont } from '@/components/icon-font';
|
||||
import { LLMFactory } from '@/constants/llm';
|
||||
import { UserSettingRouteKey } from '@/constants/setting';
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { translationTable } from '@/locales/config';
|
||||
import TranslationTable from './TranslationTable';
|
||||
import TranslationTable from './translation-table';
|
||||
|
||||
function UserSettingLocale() {
|
||||
return (
|
||||
|
||||
@ -46,7 +46,7 @@ const AzureOpenAIModal = ({
|
||||
{ value: 'image2text', label: 'image2text' },
|
||||
],
|
||||
};
|
||||
const getOptions = (factory: string) => {
|
||||
const getOptions = () => {
|
||||
return optionsMap.Default;
|
||||
};
|
||||
const handleKeyDown = async (e: React.KeyboardEvent) => {
|
||||
@ -132,7 +132,7 @@ const AzureOpenAIModal = ({
|
||||
type: 'number',
|
||||
message: t('maxTokensInvalidMessage'),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
({}) => ({
|
||||
validator(_, value) {
|
||||
if (value < 0) {
|
||||
return Promise.reject(new Error(t('maxTokensMinMessage')));
|
||||
|
||||
@ -121,7 +121,7 @@ const BedrockModal = ({
|
||||
type: 'number',
|
||||
message: t('maxTokensInvalidMessage'),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
({}) => ({
|
||||
validator(_, value) {
|
||||
if (value < 0) {
|
||||
return Promise.reject(new Error(t('maxTokensMinMessage')));
|
||||
|
||||
@ -1,9 +1,37 @@
|
||||
export const BedrockRegionList = [
|
||||
'us-east-2',
|
||||
'us-east-1',
|
||||
'us-west-1',
|
||||
'us-west-2',
|
||||
'af-south-1',
|
||||
'ap-east-1',
|
||||
'ap-south-2',
|
||||
'ap-southeast-3',
|
||||
'ap-southeast-5',
|
||||
'ap-southeast-4',
|
||||
'ap-south-1',
|
||||
'ap-northeast-3',
|
||||
'ap-northeast-2',
|
||||
'ap-southeast-1',
|
||||
'ap-northeast-1',
|
||||
'eu-central-1',
|
||||
'us-gov-west-1',
|
||||
'ap-southeast-2',
|
||||
'ap-east-2',
|
||||
'ap-southeast-7',
|
||||
'ap-northeast-1',
|
||||
'ca-central-1',
|
||||
'ca-west-1',
|
||||
'eu-central-1',
|
||||
'eu-west-1',
|
||||
'eu-west-2',
|
||||
'eu-south-1',
|
||||
'eu-west-3',
|
||||
'eu-south-2',
|
||||
'eu-north-1',
|
||||
'eu-central-2',
|
||||
'il-central-1',
|
||||
'mx-central-1',
|
||||
'me-south-1',
|
||||
'me-central-1',
|
||||
'sa-east-1',
|
||||
'us-gov-east-1',
|
||||
'us-gov-west-1',
|
||||
];
|
||||
|
||||
@ -103,7 +103,7 @@ const FishAudioModal = ({
|
||||
type: 'number',
|
||||
message: t('maxTokensInvalidMessage'),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
({}) => ({
|
||||
validator(_, value) {
|
||||
if (value < 0) {
|
||||
return Promise.reject(new Error(t('maxTokensMinMessage')));
|
||||
|
||||
@ -110,7 +110,7 @@ const GoogleModal = ({
|
||||
type: 'number',
|
||||
message: t('maxTokensInvalidMessage'),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
({}) => ({
|
||||
validator(_, value) {
|
||||
if (value < 0) {
|
||||
return Promise.reject(new Error(t('maxTokensMinMessage')));
|
||||
|
||||
@ -114,7 +114,6 @@ export const useSubmitOllama = () => {
|
||||
const [initialValues, setInitialValues] = useState<
|
||||
Partial<IAddLlmRequestBody> | undefined
|
||||
>();
|
||||
const [originalModelName, setOriginalModelName] = useState<string>('');
|
||||
const { addLlm, loading } = useAddLlm();
|
||||
const {
|
||||
visible: llmAddingVisible,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { Form, Input, Modal, Select } from 'antd';
|
||||
import { Form, Input, Modal } from 'antd';
|
||||
import omit from 'lodash/omit';
|
||||
|
||||
type FieldType = IAddLlmRequestBody & {
|
||||
@ -10,8 +10,6 @@ type FieldType = IAddLlmRequestBody & {
|
||||
hunyuan_sk: string;
|
||||
};
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const HunyuanModal = ({
|
||||
visible,
|
||||
hideModal,
|
||||
|
||||
@ -34,7 +34,6 @@ import { CircleHelp } from 'lucide-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import SettingTitle from '../components/setting-title';
|
||||
import { isLocalLlmFactory } from '../utils';
|
||||
import TencentCloudModal from './Tencent-modal';
|
||||
import ApiKeyModal from './api-key-modal';
|
||||
import AzureOpenAIModal from './azure-openai-modal';
|
||||
import BedrockModal from './bedrock-modal';
|
||||
@ -58,6 +57,7 @@ import {
|
||||
} from './hooks';
|
||||
import HunyuanModal from './hunyuan-modal';
|
||||
import styles from './index.less';
|
||||
import TencentCloudModal from './next-tencent-modal';
|
||||
import OllamaModal from './ollama-modal';
|
||||
import SparkModal from './spark-modal';
|
||||
import SystemModelSettingModal from './system-model-setting-modal';
|
||||
|
||||
@ -140,7 +140,7 @@ const SparkModal = ({
|
||||
type: 'number',
|
||||
message: t('maxTokensInvalidMessage'),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
({}) => ({
|
||||
validator(_, value) {
|
||||
if (value < 0) {
|
||||
return Promise.reject(new Error(t('maxTokensMinMessage')));
|
||||
|
||||
@ -113,7 +113,7 @@ const VolcEngineModal = ({
|
||||
type: 'number',
|
||||
message: t('maxTokensInvalidMessage'),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
({}) => ({
|
||||
validator(_, value) {
|
||||
if (value < 0) {
|
||||
return Promise.reject(new Error(t('maxTokensMinMessage')));
|
||||
|
||||
@ -108,7 +108,7 @@ const YiyanModal = ({
|
||||
type: 'number',
|
||||
message: t('maxTokensInvalidMessage'),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
({}) => ({
|
||||
validator(_, value) {
|
||||
if (value < 0) {
|
||||
return Promise.reject(new Error(t('maxTokensMinMessage')));
|
||||
|
||||
@ -230,7 +230,7 @@ const routes = [
|
||||
],
|
||||
},
|
||||
{
|
||||
path: Routes.Search,
|
||||
path: `${Routes.Search}/:id`,
|
||||
layout: false,
|
||||
component: `@/pages${Routes.Search}`,
|
||||
},
|
||||
|
||||
@ -4,7 +4,7 @@ import { registerNextServer } from '@/utils/register-server';
|
||||
const {
|
||||
getDialog,
|
||||
setDialog,
|
||||
listDialog,
|
||||
// listDialog,
|
||||
removeDialog,
|
||||
getConversation,
|
||||
getConversationSSE,
|
||||
23
web/src/services/search-service.ts
Normal file
23
web/src/services/search-service.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import api from '@/utils/api';
|
||||
import registerServer from '@/utils/register-server';
|
||||
import request from '@/utils/request';
|
||||
|
||||
const { createSearch, getSearchList, deleteSearch, getSearchDetail } = api;
|
||||
const methods = {
|
||||
createSearch: {
|
||||
url: createSearch,
|
||||
method: 'post',
|
||||
},
|
||||
getSearchList: {
|
||||
url: getSearchList,
|
||||
method: 'post',
|
||||
},
|
||||
deleteSearch: { url: deleteSearch, method: 'post' },
|
||||
getSearchDetail: {
|
||||
url: getSearchDetail,
|
||||
method: 'get',
|
||||
},
|
||||
} as const;
|
||||
const searchService = registerServer<keyof typeof methods>(methods, request);
|
||||
|
||||
export default searchService;
|
||||
@ -174,4 +174,10 @@ export default {
|
||||
testMcpServerTool: `${api_host}/mcp_server/test_tool`,
|
||||
cacheMcpServerTool: `${api_host}/mcp_server/cache_tools`,
|
||||
testMcpServer: `${api_host}/mcp_server/test_mcp`,
|
||||
|
||||
// next-search
|
||||
createSearch: `${api_host}/search/create`,
|
||||
getSearchList: `${api_host}/search/list`,
|
||||
deleteSearch: `${api_host}/search/rm`,
|
||||
getSearchDetail: `${api_host}/search/detail`,
|
||||
};
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable guard-for-in */
|
||||
import { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { isObject } from 'lodash';
|
||||
import omit from 'lodash/omit';
|
||||
|
||||
Reference in New Issue
Block a user