Compare commits

...

17 Commits

Author SHA1 Message Date
9433f64fe2 Feat: added functionality to choose all datasets if no id is provided (#9184)
### What problem does this PR solve?

Using the mcp server in n8n sometimes (with smaller models) results in
errors because the llm misses a char or adds one to the list of
dataset_ids provided. It first asks for the list of datasets and if you
got a larger list of them it makes a error recalling the list
completely. So adding the feature to just search through all available
datasets solves this and makes the retrieval of data more stable. The
functionality to just call special datasets by id is not changed, the
dataset_ids are now not required anymore (only the "question" is). You
can provide (like before) a list of datasets, a empty list or no list at
all.

### Type of change

- [X] New Feature (non-breaking change which adds functionality)
<img width="1897" height="880" alt="mcp error dataset id"
src="https://github.com/user-attachments/assets/71076d24-f875-4663-a69a-60839fc7a545"
/>
2025-08-11 17:20:35 +08:00
d7c9611d45 docs(sandbox): update /etc/hosts entry to include required services (#9144)
Fixes an issue where running the sandbox (code component) fails due to
unresolved hostnames. Added missing service names (es01, infinity,
mysql, minio, redis) to 127.0.0.1 in the /etc/hosts example.

Reference: https://github.com/infiniflow/ragflow/issues/8226

## What this PR does

Updates the sandbox quickstart documentation to fix a known issue where
the sandbox fails to resolve required service hostnames.

## Why

Following the original instruction leads to a `Failed to resolve 'none'`
error, as discussed in issue #8226. Adding the missing service names to
`127.0.0.1` resolves the problem.

## Related issue

https://github.com/infiniflow/ragflow/issues/8226

## Note

It might be better to add `127.0.0.1 es01 infinity mysql minio redis` to
docs/quickstart.mdx, but since no issues appeared at the time without
adding this line—and the problem occurred while working with the code
component—I added it here.

### Type of change

- [X] Documentation Update
2025-08-11 17:18:56 +08:00
79399f7f25 Support the case of one cell split by multiple columns. (#9225)
### What problem does this PR solve?
Support the case of one cell split by multiple columns. Besides, the
codes are compatible with the common cell case.
#8606 can be fixed.
### Type of change

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

I provide a case of one cell split by multiple columns:

[test.xlsx](https://github.com/user-attachments/files/21578693/test.xlsx)

The chunk res:
<img width="236" height="57" alt="2025-06-17 16-04-07 的屏幕截图"
src="https://github.com/user-attachments/assets/b0a499ac-349d-4c3d-8c6e-0931c8fc26de"
/>
2025-08-11 17:17:56 +08:00
23522f1ea8 Fix: handle missing dataset_ids when creating chat assistant (#9324)
- Root cause: accessing req.get("dataset_ids") returns None when the key
is absent, causing KeyError.
- Fix: use req.get("dataset_ids", []) to default to empty list.
2025-08-11 17:17:20 +08:00
46dc3f1c48 Fix: Update test assertions and add GraphRAG config in dataset tests (#9386)
### What problem does this PR solve?

- Modify error message assertion in chunk update test to check for
document ownership
- Add GraphRAG configuration with `use_graphrag: False` in dataset
update tests
- Fix actions:
https://github.com/infiniflow/ragflow/actions/runs/16863637898/job/47767511582
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-11 17:15:48 +08:00
c9b156fa6d Fix: Remove default dataset_ids from Chat class initialization (#9381)
### What problem does this PR solve?

- The default dataset_ids "kb1" was removed from the Chat class. 
- The HTTP API response does not include the dataset_ids field.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-11 17:15:30 +08:00
83939b1a63 Feat: add full list of supported AWS Bedrock regions (#9378)
### What problem does this PR solve?

Add full list of supported AWS Bedrock regions.

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2025-08-11 17:15:07 +08:00
7f08ba47d7 Fix "no tc element at grid_offset" (#9375)
### What problem does this PR solve?

fix "no `tc` element at grid_offset", just log warning and ignore.
stacktrace:
```
Traceback (most recent call last):
  File "/ragflow/rag/svr/task_executor.py", line 620, in handle_task
    await do_handle_task(task)
  File "/ragflow/rag/svr/task_executor.py", line 553, in do_handle_task
    chunks = await build_chunks(task, progress_callback)
  File "/ragflow/rag/svr/task_executor.py", line 257, in build_chunks
    cks = await trio.to_thread.run_sync(lambda: chunker.chunk(task["name"], binary=binary, from_page=task["from_page"],
  File "/ragflow/.venv/lib/python3.10/site-packages/trio/_threads.py", line 447, in to_thread_run_sync
    return msg_from_thread.unwrap()
  File "/ragflow/.venv/lib/python3.10/site-packages/outcome/_impl.py", line 213, in unwrap
    raise captured_error
  File "/ragflow/.venv/lib/python3.10/site-packages/trio/_threads.py", line 373, in do_release_then_return_result
    return result.unwrap()
  File "/ragflow/.venv/lib/python3.10/site-packages/outcome/_impl.py", line 213, in unwrap
    raise captured_error
  File "/ragflow/.venv/lib/python3.10/site-packages/trio/_threads.py", line 392, in worker_fn
    ret = context.run(sync_fn, *args)
  File "/ragflow/rag/svr/task_executor.py", line 257, in <lambda>
    cks = await trio.to_thread.run_sync(lambda: chunker.chunk(task["name"], binary=binary, from_page=task["from_page"],
  File "/ragflow/rag/app/naive.py", line 384, in chunk
    sections, tables = Docx()(filename, binary)
  File "/ragflow/rag/app/naive.py", line 230, in __call__
    while i < len(r.cells):
  File "/ragflow/.venv/lib/python3.10/site-packages/docx/table.py", line 438, in cells
    return tuple(_iter_row_cells())
  File "/ragflow/.venv/lib/python3.10/site-packages/docx/table.py", line 436, in _iter_row_cells
    yield from iter_tc_cells(tc)
  File "/ragflow/.venv/lib/python3.10/site-packages/docx/table.py", line 424, in iter_tc_cells
    yield from iter_tc_cells(tc._tc_above)  # pyright: ignore[reportPrivateUsage]
  File "/ragflow/.venv/lib/python3.10/site-packages/docx/oxml/table.py", line 741, in _tc_above
    return self._tr_above.tc_at_grid_offset(self.grid_offset)
  File "/ragflow/.venv/lib/python3.10/site-packages/docx/oxml/table.py", line 98, in tc_at_grid_offset
    raise ValueError(f"no `tc` element at grid_offset={grid_offset}")
ValueError: no `tc` element at grid_offset=10
```

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-11 17:13:10 +08:00
ce3dd019c3 Fix broken data stream when writing image file (#9354)
### What problem does this PR solve?

fix "broken data stream when writing image file", just log warning and
ignore

Close #8379 

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-11 17:07:49 +08:00
476c56868d Agent plans tasks by referring to its own prompt. (#9315)
### What problem does this PR solve?

Fixes the issue in the analyze_task execution flow where the Lead Agent
was not utilizing its own sys_prompt during task analysis, resulting in
incorrect or incomplete task planning.
https://github.com/infiniflow/ragflow/issues/9294
### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2025-08-11 17:05:06 +08:00
b9c4954c2f Fix: Replace StrEnum with strenum in code_exec.py (#9376)
### What problem does this PR solve?

- The enum import was changed from Python's built-in StrEnum to the
strenum package.
- Fix error `Warning: Failed to import module code_exec: cannot import
name 'StrEnum' from 'enum' (/usr/lib/python3.10/enum.py)`

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-11 15:32:04 +08:00
a060672b31 Feat: Run eslint when the project is running to standardize everyone's code #9377 (#9379)
### What problem does this PR solve?

Feat: Run eslint when the project is running to standardize everyone's
code #9377

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-08-11 15:31:38 +08:00
f022504ef9 Support Russian in UI Update config.ts (#9361)
add ru

### What problem does this PR solve?

_Briefly describe what this PR aims to solve. Include background context
that will help reviewers understand the purpose of the PR._

### Type of change

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

Co-authored-by: Yingfeng <yingfeng.zhang@gmail.com>
2025-08-11 15:30:35 +08:00
1a78b8b295 Support Russian in UI (#9362)
### What problem does this PR solve?

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2025-08-11 14:06:18 +08:00
017dd85ccf Feat: Modify the agent list return field name #3221 (#9373)
### What problem does this PR solve?

Feat: Modify the agent list return field name #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-08-11 14:03:52 +08:00
4c7b2ef46e Feat: New search page components and features (#9344)
### What problem does this PR solve?

Feat: New search page components and features #3221

- Added search homepage, search settings, and ongoing search components
- Implemented features such as search app list, creating search apps,
and deleting search apps
- Optimized the multi-select component, adding disabled state and suffix
display
- Adjusted navigation hooks to support search page navigation

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
2025-08-11 10:34:22 +08:00
597d88bf9a Doc: updated supported model name (#9343)
### What problem does this PR solve?


### Type of change

- [x] Documentation Update
2025-08-11 10:05:39 +08:00
99 changed files with 3434 additions and 339 deletions

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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)

View File

@ -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"],
)

View File

@ -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), ""))

View File

@ -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:

View File

@ -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?

View File

@ -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)

View File

@ -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()))

View File

@ -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)

View File

@ -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)

View File

@ -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})

View File

@ -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: ['&apos;', '&#39;'],
},
{
char: '"',
alternatives: ['&quot;', '&#34;'],
},
],
},
],
},
};

View File

@ -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
View File

@ -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"

View File

@ -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",

View File

@ -80,7 +80,6 @@ export function ChunkMethodDialog({
hideModal,
onOk,
parserId,
documentId,
documentExtension,
visible,
parserConfig,

View File

@ -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>
);

View File

@ -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

View File

@ -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';

View File

@ -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>
);

View File

@ -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';

View File

@ -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';

View File

@ -124,7 +124,7 @@ interface TimelineIndicatorProps extends React.HTMLAttributes<HTMLDivElement> {
}
function TimelineIndicator({
asChild = false,
// asChild = false,
className,
children,
...props

View File

@ -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> */}

View File

@ -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(

View File

@ -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}

View File

@ -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) => {

View File

@ -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>
);

View File

@ -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;

View File

@ -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) => () => {

View File

@ -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);
});

View File

@ -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';

View File

@ -73,6 +73,7 @@ export declare interface IFlow {
user_id: string;
permission: string;
nickname: string;
operator_permission: number;
}
export interface IFlowTemplate {

View File

@ -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(
() => [

View File

@ -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',

View File

@ -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',

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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',

View File

@ -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 ({

View File

@ -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,
})),

View File

@ -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();

View File

@ -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);

View File

@ -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'),

View File

@ -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,

View File

@ -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]);

View File

@ -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<

View File

@ -19,7 +19,7 @@ import {
JsonViewer,
toLowerCaseStringAndDeleteChar,
typeMap,
} from './workFlowTimeline';
} from './workflow-timeline';
type IToolIcon =
| Operator.ArXiv
| Operator.GitHub

View File

@ -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'

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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>
);

View File

@ -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>
);

View File

@ -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 {

View File

@ -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;

View File

@ -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);
},

View File

@ -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">

View File

@ -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';

View File

@ -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,

View File

@ -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();

View File

@ -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(
() => {

View File

@ -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]);

View File

@ -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 {

View 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;
}

View File

@ -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>
);
}

View 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>
);
}

View 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 };

View 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>
);
}

View 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;

View 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 };
};

View File

@ -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>
);
}

View File

@ -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>

View 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>
);
}

View File

@ -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(() => {

View File

@ -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';

View File

@ -1,5 +1,5 @@
import { translationTable } from '@/locales/config';
import TranslationTable from './TranslationTable';
import TranslationTable from './translation-table';
function UserSettingLocale() {
return (

View File

@ -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')));

View File

@ -121,7 +121,7 @@ const BedrockModal = ({
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({ getFieldValue }) => ({
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));

View File

@ -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',
];

View File

@ -103,7 +103,7 @@ const FishAudioModal = ({
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({ getFieldValue }) => ({
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));

View File

@ -110,7 +110,7 @@ const GoogleModal = ({
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({ getFieldValue }) => ({
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));

View File

@ -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,

View File

@ -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,

View File

@ -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';

View File

@ -140,7 +140,7 @@ const SparkModal = ({
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({ getFieldValue }) => ({
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));

View File

@ -113,7 +113,7 @@ const VolcEngineModal = ({
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({ getFieldValue }) => ({
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));

View File

@ -108,7 +108,7 @@ const YiyanModal = ({
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({ getFieldValue }) => ({
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));

View File

@ -230,7 +230,7 @@ const routes = [
],
},
{
path: Routes.Search,
path: `${Routes.Search}/:id`,
layout: false,
component: `@/pages${Routes.Search}`,
},

View File

@ -4,7 +4,7 @@ import { registerNextServer } from '@/utils/register-server';
const {
getDialog,
setDialog,
listDialog,
// listDialog,
removeDialog,
getConversation,
getConversationSSE,

View 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;

View File

@ -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`,
};

View File

@ -1,3 +1,4 @@
/* eslint-disable guard-for-in */
import { AxiosRequestConfig, AxiosResponse } from 'axios';
import { isObject } from 'lodash';
import omit from 'lodash/omit';