Compare commits

...

9 Commits

Author SHA1 Message Date
0af57ff772 fix(dataset, next-chats): Fix data form data acquisition logic And Optimize the chat settings interface and add language selection (#9629)
### What problem does this PR solve?

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

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

### Type of change

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

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

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

### Type of change

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

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

### Type of change

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

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

### Type of change


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


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

- Updated background and text colors for multiple components

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

- Unified status icons and color mapping

- Optimized responsive layout to improve compatibility across different
screen sizes

### Type of change

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

#9611
#9603 #9597

### Type of change

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

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

### Type of change

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

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

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

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

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


### Type of change

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

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

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-08-21 09:32:04 +08:00
48 changed files with 457 additions and 157 deletions

View File

@ -426,7 +426,7 @@ class Canvas:
convs = [] convs = []
if window_size <= 0: if window_size <= 0:
return convs return convs
for role, obj in self.history[window_size * -1:]: for role, obj in self.history[window_size * -2:]:
if isinstance(obj, dict): if isinstance(obj, dict):
convs.append({"role": role, "content": obj.get("content", "")}) convs.append({"role": role, "content": obj.get("content", "")})
else: else:

View File

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

View File

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

View File

@ -44,9 +44,6 @@ def retrieval(tenant_id):
if not e: if not e:
return build_error_result(message="Knowledgebase not found!", code=settings.RetCode.NOT_FOUND) return build_error_result(message="Knowledgebase not found!", code=settings.RetCode.NOT_FOUND)
if kb.tenant_id != tenant_id:
return build_error_result(message="Knowledgebase not found!", code=settings.RetCode.NOT_FOUND)
embd_mdl = LLMBundle(kb.tenant_id, LLMType.EMBEDDING.value, llm_name=kb.embd_id) embd_mdl = LLMBundle(kb.tenant_id, LLMType.EMBEDDING.value, llm_name=kb.embd_id)
ranks = settings.retrievaler.retrieval( ranks = settings.retrievaler.retrieval(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -42,7 +42,7 @@ class RecursiveAbstractiveProcessing4TreeOrganizedRetrieval:
self._prompt = prompt self._prompt = prompt
self._max_token = max_token self._max_token = max_token
@timeout(60*3) @timeout(60*20)
async def _chat(self, system, history, gen_conf): async def _chat(self, system, history, gen_conf):
response = get_llm_cache(self._llm_model.llm_name, system, history, gen_conf) response = get_llm_cache(self._llm_model.llm_name, system, history, gen_conf)
if response: if response:
@ -56,7 +56,7 @@ class RecursiveAbstractiveProcessing4TreeOrganizedRetrieval:
set_llm_cache(self._llm_model.llm_name, system, response, history, gen_conf) set_llm_cache(self._llm_model.llm_name, system, response, history, gen_conf)
return response return response
@timeout(2) @timeout(20)
async def _embedding_encode(self, txt): async def _embedding_encode(self, txt):
response = get_embed_cache(self._embd_model.llm_name, txt) response = get_embed_cache(self._embd_model.llm_name, txt)
if response is not None: if response is not None:
@ -86,7 +86,7 @@ class RecursiveAbstractiveProcessing4TreeOrganizedRetrieval:
layers = [(0, len(chunks))] layers = [(0, len(chunks))]
start, end = 0, len(chunks) start, end = 0, len(chunks)
@timeout(60*3) @timeout(60*20)
async def summarize(ck_idx: list[int]): async def summarize(ck_idx: list[int]):
nonlocal chunks nonlocal chunks
texts = [chunks[i][0] for i in ck_idx] texts = [chunks[i][0] for i in ck_idx]

6
uv.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,6 +23,7 @@ export interface SegmentedProps
prefixCls?: string; prefixCls?: string;
direction?: 'ltr' | 'rtl'; direction?: 'ltr' | 'rtl';
motionName?: string; motionName?: string;
activeClassName?: string;
} }
export function Segmented({ export function Segmented({
@ -30,6 +31,7 @@ export function Segmented({
value, value,
onChange, onChange,
className, className,
activeClassName,
}: SegmentedProps) { }: SegmentedProps) {
const [selectedValue, setSelectedValue] = React.useState< const [selectedValue, setSelectedValue] = React.useState<
SegmentedValue | undefined SegmentedValue | undefined
@ -60,6 +62,9 @@ export function Segmented({
'text-bg-base bg-metallic-gradient border-b-[#00BEB4] border-b-2': 'text-bg-base bg-metallic-gradient border-b-[#00BEB4] border-b-2':
selectedValue === actualValue, selectedValue === actualValue,
}, },
activeClassName && selectedValue === actualValue
? activeClassName
: '',
)} )}
onClick={() => handleOnChange(actualValue)} onClick={() => handleOnChange(actualValue)}
> >

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -187,8 +187,8 @@ export function GeneralForm() {
disabled={submitLoading} disabled={submitLoading}
onClick={() => { onClick={() => {
(async () => { (async () => {
let isValidate = await form.formControl.trigger('name'); let isValidate = await form.trigger('name');
const { name, description } = form.formState.values; const { name, description } = form.getValues();
const avatar = avatarBase64Str; const avatar = avatarBase64Str;
if (isValidate) { if (isValidate) {

View File

@ -41,7 +41,7 @@ export function SeeAllCard() {
const { navigateToDatasetList } = useNavigatePage(); const { navigateToDatasetList } = useNavigatePage();
return ( return (
<Card className="w-40" onClick={navigateToDatasetList}> <Card className="w-40 flex-none" onClick={navigateToDatasetList}>
<CardContent className="p-2.5 pt-1 w-full h-full flex items-center justify-center gap-1.5 text-text-secondary"> <CardContent className="p-2.5 pt-1 w-full h-full flex items-center justify-center gap-1.5 text-text-secondary">
See All <ChevronRight className="size-4" /> See All <ChevronRight className="size-4" />
</CardContent> </CardContent>

View File

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

View File

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

View File

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

View File

@ -30,7 +30,7 @@ export function Datasets() {
<CardSkeleton /> <CardSkeleton />
</div> </div>
) : ( ) : (
<div className="flex gap-4 flex-1"> <div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-8 max-h-[78vh] overflow-auto">
{kbs.slice(0, 6).map((dataset) => ( {kbs.slice(0, 6).map((dataset) => (
<DatasetCard <DatasetCard
key={dataset.id} key={dataset.id}

View File

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

View File

@ -35,13 +35,18 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
shouldUnregister: true, shouldUnregister: true,
defaultValues: { defaultValues: {
name: '', name: '',
icon: [],
language: 'English', language: 'English',
description: '',
kb_ids: [],
prompt_config: { prompt_config: {
quote: true, quote: true,
keyword: false, keyword: false,
tts: false, tts: false,
use_kg: false, use_kg: false,
refine_multiturn: true, refine_multiturn: true,
system: '',
parameters: [],
}, },
top_n: 8, top_n: 8,
vector_similarity_weight: 0.2, vector_similarity_weight: 0.2,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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